xref: /freebsd/sys/dev/flash/at45d.c (revision 9a14aa017b21c292740c00ee098195cd46642730)
1 /*-
2  * Copyright (c) 2006 M. Warner Losh.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24 
25 #include <sys/cdefs.h>
26 __FBSDID("$FreeBSD$");
27 
28 #include <sys/param.h>
29 #include <sys/systm.h>
30 #include <sys/bio.h>
31 #include <sys/bus.h>
32 #include <sys/conf.h>
33 #include <sys/kernel.h>
34 #include <sys/kthread.h>
35 #include <sys/lock.h>
36 #include <sys/mbuf.h>
37 #include <sys/malloc.h>
38 #include <sys/module.h>
39 #include <sys/mutex.h>
40 #include <geom/geom_disk.h>
41 
42 #include <dev/spibus/spi.h>
43 #include "spibus_if.h"
44 
45 struct at45d_softc
46 {
47 	struct intr_config_hook config_intrhook;
48 	device_t dev;
49 	struct mtx sc_mtx;
50 	struct disk *disk;
51 	struct proc *p;
52 	struct bio_queue_head bio_queue;
53 };
54 
55 #define AT45D_LOCK(_sc)		mtx_lock(&(_sc)->sc_mtx)
56 #define	AT45D_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
57 #define AT45D_LOCK_INIT(_sc) \
58 	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
59 	    "at45d", MTX_DEF)
60 #define AT45D_LOCK_DESTROY(_sc)	mtx_destroy(&_sc->sc_mtx);
61 #define AT45D_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_OWNED);
62 #define AT45D_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
63 
64 static void at45d_delayed_attach(void *xsc);
65 
66 /* disk routines */
67 static int at45d_open(struct disk *dp);
68 static int at45d_close(struct disk *dp);
69 static void at45d_strategy(struct bio *bp);
70 static void at45d_task(void *arg);
71 
72 #define CONTINUOUS_ARRAY_READ		0xE8
73 #define CONTINUOUS_ARRAY_READ_HF	0x0B
74 #define CONTINUOUS_ARRAY_READ_LF	0x03
75 #define STATUS_REGISTER_READ		0xD7
76 #define PROGRAM_THROUGH_BUFFER		0x82
77 #define MANUFACTURER_ID			0x9F
78 
79 static uint8_t
80 at45d_get_status(device_t dev)
81 {
82 	uint8_t txBuf[8], rxBuf[8];
83 	struct spi_command cmd;
84 	int err;
85 
86 	memset(&cmd, 0, sizeof(cmd));
87 	memset(txBuf, 0, sizeof(txBuf));
88 	memset(rxBuf, 0, sizeof(rxBuf));
89 
90 	txBuf[0] = STATUS_REGISTER_READ;
91 	cmd.tx_cmd = txBuf;
92 	cmd.rx_cmd = rxBuf;
93 	cmd.rx_cmd_sz = 2;
94 	cmd.tx_cmd_sz = 2;
95 	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
96 	return (rxBuf[1]);
97 }
98 
99 static void
100 at45d_wait_for_device_ready(device_t dev)
101 {
102 	while (!(at45d_get_status(dev) & 0x80))
103 		continue;
104 }
105 
106 static int
107 at45d_get_mfg_info(device_t dev, uint8_t *resp)
108 {
109 	uint8_t txBuf[8], rxBuf[8];
110 	struct spi_command cmd;
111 	int err;
112 
113 	memset(&cmd, 0, sizeof(cmd));
114 	memset(txBuf, 0, sizeof(txBuf));
115 	memset(rxBuf, 0, sizeof(rxBuf));
116 
117 	txBuf[0] = MANUFACTURER_ID;
118 	cmd.tx_cmd = &txBuf;
119 	cmd.rx_cmd = &rxBuf;
120 	cmd.tx_cmd_sz = 5;
121 	cmd.rx_cmd_sz = 5;
122 	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
123 	if (err)
124 		return (err);
125 	memcpy(resp, rxBuf + 1, 4);
126 	// XXX We really should 'decode' the reply into some kind of
127 	// XXX structure.  To be generic (and not just for atmel parts)
128 	// XXX we'd have to loop until we got a full reply.
129 	return (0);
130 }
131 
132 static int
133 at45d_probe(device_t dev)
134 {
135 	device_set_desc(dev, "AT45 Flash Family");
136 	return (0);
137 }
138 
139 static int
140 at45d_attach(device_t dev)
141 {
142 	struct at45d_softc *sc;
143 
144 	sc = device_get_softc(dev);
145 	sc->dev = dev;
146 	AT45D_LOCK_INIT(sc);
147 
148 	/* We'll see what kind of flash we have later... */
149 	sc->config_intrhook.ich_func = at45d_delayed_attach;
150 	sc->config_intrhook.ich_arg = sc;
151 	if (config_intrhook_establish(&sc->config_intrhook) != 0)
152 		device_printf(dev, "config_intrhook_establish failed\n");
153 	return (0);
154 }
155 
156 static int
157 at45d_detach(device_t dev)
158 {
159 	return EIO;
160 }
161 
162 static void
163 at45d_delayed_attach(void *xsc)
164 {
165 	struct at45d_softc *sc = xsc;
166 	uint8_t buf[4];
167 
168 	at45d_get_mfg_info(sc->dev, buf);
169 	at45d_wait_for_device_ready(sc->dev);
170 
171 	sc->disk = disk_alloc();
172 	sc->disk->d_open = at45d_open;
173 	sc->disk->d_close = at45d_close;
174 	sc->disk->d_strategy = at45d_strategy;
175 	sc->disk->d_name = "flash/spi";
176 	sc->disk->d_drv1 = sc;
177 	sc->disk->d_maxsize = DFLTPHYS;
178 	sc->disk->d_sectorsize = 1056;		/* XXX */
179 	sc->disk->d_mediasize = 8192 * 1056;	/* XXX */
180 	sc->disk->d_unit = device_get_unit(sc->dev);
181 	disk_create(sc->disk, DISK_VERSION);
182 	bioq_init(&sc->bio_queue);
183 	kproc_create(&at45d_task, sc, &sc->p, 0, 0, "task: at45d flash");
184 
185 	config_intrhook_disestablish(&sc->config_intrhook);
186 }
187 
188 static int
189 at45d_open(struct disk *dp)
190 {
191 	return 0;
192 }
193 
194 static int
195 at45d_close(struct disk *dp)
196 {
197 	return 0;
198 }
199 
200 static void
201 at45d_strategy(struct bio *bp)
202 {
203 	struct at45d_softc *sc;
204 
205 	sc = (struct at45d_softc *)bp->bio_disk->d_drv1;
206 	AT45D_LOCK(sc);
207 	bioq_disksort(&sc->bio_queue, bp);
208 	wakeup(sc);
209 	AT45D_UNLOCK(sc);
210 }
211 
212 static void
213 at45d_task(void *arg)
214 {
215 	struct at45d_softc *sc = (struct at45d_softc*)arg;
216 	struct bio *bp;
217 	uint8_t txBuf[8], rxBuf[8];
218 	struct spi_command cmd;
219 	int sz;
220 	daddr_t block, end;
221 	device_t dev, pdev;
222 	int err;
223 
224 	for (;;) {
225 		dev = sc->dev;
226 		pdev = device_get_parent(dev);
227 		AT45D_LOCK(sc);
228 		do {
229 			bp = bioq_first(&sc->bio_queue);
230 			if (bp == NULL)
231 				msleep(sc, &sc->sc_mtx, PRIBIO, "jobqueue", 0);
232 		} while (bp == NULL);
233 		bioq_remove(&sc->bio_queue, bp);
234 		AT45D_UNLOCK(sc);
235 		sz = sc->disk->d_sectorsize;
236 		end = bp->bio_pblkno + (bp->bio_bcount / sz);
237 		for (block = bp->bio_pblkno; block < end; block++) {
238 			char *vaddr = bp->bio_data + (block - bp->bio_pblkno) * sz;
239 			if (bp->bio_cmd == BIO_READ) {
240 				txBuf[0] = CONTINUOUS_ARRAY_READ_HF;
241 				cmd.tx_cmd_sz = 5;
242 				cmd.rx_cmd_sz = 5;
243 			} else {
244 				txBuf[0] = PROGRAM_THROUGH_BUFFER;
245 				cmd.tx_cmd_sz = 4;
246 				cmd.rx_cmd_sz = 4;
247 			}
248 			// XXX only works on certain devices...  Fixme
249 			txBuf[1] = ((block >> 5) & 0xFF);
250 			txBuf[2] = ((block << 3) & 0xF8);
251 			txBuf[3] = 0;
252 			txBuf[4] = 0;
253 			cmd.tx_cmd = txBuf;
254 			cmd.rx_cmd = rxBuf;
255 			cmd.tx_data = vaddr;
256 			cmd.tx_data_sz = sz;
257 			cmd.rx_data = vaddr;
258 			cmd.rx_data_sz = sz;
259 			err = SPIBUS_TRANSFER(pdev, dev, &cmd);
260 			// XXX err check?
261 		}
262 		biodone(bp);
263 	}
264 }
265 
266 static devclass_t at45d_devclass;
267 
268 static device_method_t at45d_methods[] = {
269 	/* Device interface */
270 	DEVMETHOD(device_probe,		at45d_probe),
271 	DEVMETHOD(device_attach,	at45d_attach),
272 	DEVMETHOD(device_detach,	at45d_detach),
273 
274 	{ 0, 0 }
275 };
276 
277 static driver_t at45d_driver = {
278 	"at45d",
279 	at45d_methods,
280 	sizeof(struct at45d_softc),
281 };
282 
283 DRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, 0, 0);
284