xref: /freebsd/sys/dev/flash/at45d.c (revision f0a75d274af375d15b97b830966b99a02b7db911)
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/gpio.h>
34 #include <sys/kernel.h>
35 #include <sys/kthread.h>
36 #include <sys/lock.h>
37 #include <sys/mbuf.h>
38 #include <sys/malloc.h>
39 #include <sys/module.h>
40 #include <sys/mutex.h>
41 #include <geom/geom_disk.h>
42 
43 #include <dev/spibus/spi.h>
44 #include "spibus_if.h"
45 
46 struct at45d_softc
47 {
48 	struct intr_config_hook config_intrhook;
49 	device_t dev;
50 	struct mtx sc_mtx;
51 	struct disk *disk;
52 	struct proc *p;
53 	struct bio_queue_head bio_queue;
54 };
55 
56 #define AT45D_LOCK(_sc)		mtx_lock(&(_sc)->sc_mtx)
57 #define	AT45D_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
58 #define AT45D_LOCK_INIT(_sc) \
59 	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
60 	    "at45d", MTX_DEF)
61 #define AT45D_LOCK_DESTROY(_sc)	mtx_destroy(&_sc->sc_mtx);
62 #define AT45D_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_OWNED);
63 #define AT45D_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
64 
65 static void at45d_delayed_attach(void *xsc);
66 
67 /* disk routines */
68 static int at45d_open(struct disk *dp);
69 static int at45d_close(struct disk *dp);
70 static void at45d_strategy(struct bio *bp);
71 static void at45d_task(void *arg);
72 
73 #define CONTINUOUS_ARRAY_READ		0xE8
74 #define CONTINUOUS_ARRAY_READ_HF	0x0B
75 #define CONTINUOUS_ARRAY_READ_LF	0x03
76 #define STATUS_REGISTER_READ		0xD7
77 #define PROGRAM_THROUGH_BUFFER		0x82
78 #define MANUFACTURER_ID			0x9F
79 
80 static uint8_t
81 at45d_get_status(device_t dev)
82 {
83 	uint8_t txBuf[8], rxBuf[8];
84 	struct spi_command cmd;
85 	int err;
86 
87 	memset(&cmd, 0, sizeof(cmd));
88 	memset(txBuf, 0, sizeof(txBuf));
89 	memset(rxBuf, 0, sizeof(rxBuf));
90 
91 	txBuf[0] = STATUS_REGISTER_READ;
92 	cmd.tx_cmd = txBuf;
93 	cmd.rx_cmd = rxBuf;
94 	cmd.rx_cmd_sz = 2;
95 	cmd.tx_cmd_sz = 2;
96 	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
97 	return (rxBuf[1]);
98 }
99 
100 static void
101 at45d_wait_for_device_ready(device_t dev)
102 {
103 	while (!(at45d_get_status(dev) & 0x80))
104 		continue;
105 }
106 
107 static int
108 at45d_get_mfg_info(device_t dev, uint8_t *resp)
109 {
110 	uint8_t txBuf[8], rxBuf[8];
111 	struct spi_command cmd;
112 	int err;
113 
114 	memset(&cmd, 0, sizeof(cmd));
115 	memset(txBuf, 0, sizeof(txBuf));
116 	memset(rxBuf, 0, sizeof(rxBuf));
117 
118 	txBuf[0] = MANUFACTURER_ID;
119 	cmd.tx_cmd = &txBuf;
120 	cmd.rx_cmd = &rxBuf;
121 	cmd.tx_cmd_sz = 5;
122 	cmd.rx_cmd_sz = 5;
123 	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
124 	if (err)
125 		return (err);
126 	memcpy(resp, rxBuf + 1, 4);
127 	// XXX We really should 'decode' the reply into some kind of
128 	// XXX structure.  To be generic (and not just for atmel parts)
129 	// XXX we'd have to loop until we got a full reply.
130 	return (0);
131 }
132 
133 static int
134 at45d_probe(device_t dev)
135 {
136 	device_set_desc(dev, "AT45 Flash Family");
137 	return (0);
138 }
139 
140 static int
141 at45d_attach(device_t dev)
142 {
143 	struct at45d_softc *sc;
144 
145 	sc = device_get_softc(dev);
146 	sc->dev = dev;
147 	AT45D_LOCK_INIT(sc);
148 
149 	/* We'll see what kind of flash we have later... */
150 	sc->config_intrhook.ich_func = at45d_delayed_attach;
151 	sc->config_intrhook.ich_arg = sc;
152 	if (config_intrhook_establish(&sc->config_intrhook) != 0)
153 		device_printf(dev, "config_intrhook_establish failed\n");
154 	return (0);
155 }
156 
157 static int
158 at45d_detach(device_t dev)
159 {
160 	return EIO;
161 }
162 
163 static void
164 at45d_delayed_attach(void *xsc)
165 {
166 	struct at45d_softc *sc = xsc;
167 	uint8_t buf[4];
168 
169 	at45d_get_mfg_info(sc->dev, buf);
170 	at45d_wait_for_device_ready(sc->dev);
171 
172 	sc->disk = disk_alloc();
173 	sc->disk->d_open = at45d_open;
174 	sc->disk->d_close = at45d_close;
175 	sc->disk->d_strategy = at45d_strategy;
176 	sc->disk->d_name = "flash/spi";
177 	sc->disk->d_drv1 = sc;
178 	sc->disk->d_maxsize = DFLTPHYS;
179 	sc->disk->d_sectorsize = 1056;		/* XXX */
180 	sc->disk->d_mediasize = 8192 * 1056;	/* XXX */
181 	sc->disk->d_unit = device_get_unit(sc->dev);
182 	disk_create(sc->disk, DISK_VERSION);
183 	bioq_init(&sc->bio_queue);
184 	kthread_create(&at45d_task, sc, &sc->p, 0, 0, "task: at45d flash");
185 
186 	config_intrhook_disestablish(&sc->config_intrhook);
187 }
188 
189 static int
190 at45d_open(struct disk *dp)
191 {
192 	return 0;
193 }
194 
195 static int
196 at45d_close(struct disk *dp)
197 {
198 	return 0;
199 }
200 
201 static void
202 at45d_strategy(struct bio *bp)
203 {
204 	struct at45d_softc *sc;
205 
206 	sc = (struct at45d_softc *)bp->bio_disk->d_drv1;
207 	AT45D_LOCK(sc);
208 	bioq_disksort(&sc->bio_queue, bp);
209 	wakeup(sc);
210 	AT45D_UNLOCK(sc);
211 }
212 
213 static void
214 at45d_task(void *arg)
215 {
216 	struct at45d_softc *sc = (struct at45d_softc*)arg;
217 	struct bio *bp;
218 	uint8_t txBuf[8], rxBuf[8];
219 	struct spi_command cmd;
220 	int sz;
221 	daddr_t block, end;
222 	device_t dev, pdev;
223 	int err;
224 
225 	for (;;) {
226 		dev = sc->dev;
227 		pdev = device_get_parent(dev);
228 		AT45D_LOCK(sc);
229 		do {
230 			bp = bioq_first(&sc->bio_queue);
231 			if (bp == NULL)
232 				msleep(sc, &sc->sc_mtx, PRIBIO, "jobqueue", 0);
233 		} while (bp == NULL);
234 		bioq_remove(&sc->bio_queue, bp);
235 		AT45D_UNLOCK(sc);
236 		sz = sc->disk->d_sectorsize;
237 		end = bp->bio_pblkno + (bp->bio_bcount / sz);
238 		for (block = bp->bio_pblkno; block < end; block++) {
239 			char *vaddr = bp->bio_data + (block - bp->bio_pblkno) * sz;
240 			if (bp->bio_cmd == BIO_READ) {
241 				txBuf[0] = CONTINUOUS_ARRAY_READ_HF;
242 				cmd.tx_cmd_sz = 5;
243 				cmd.rx_cmd_sz = 5;
244 			} else {
245 				txBuf[0] = PROGRAM_THROUGH_BUFFER;
246 				cmd.tx_cmd_sz = 4;
247 				cmd.rx_cmd_sz = 4;
248 			}
249 			// XXX only works on certain devices...  Fixme
250 			txBuf[1] = ((block >> 5) & 0xFF);
251 			txBuf[2] = ((block << 3) & 0xF8);
252 			txBuf[3] = 0;
253 			txBuf[4] = 0;
254 			cmd.tx_cmd = txBuf;
255 			cmd.rx_cmd = rxBuf;
256 			cmd.tx_data = vaddr;
257 			cmd.tx_data_sz = sz;
258 			cmd.rx_data = vaddr;
259 			cmd.rx_data_sz = sz;
260 			err = SPIBUS_TRANSFER(pdev, dev, &cmd);
261 			// XXX err check?
262 		}
263 		biodone(bp);
264 	}
265 }
266 
267 static devclass_t at45d_devclass;
268 
269 static device_method_t at45d_methods[] = {
270 	/* Device interface */
271 	DEVMETHOD(device_probe,		at45d_probe),
272 	DEVMETHOD(device_attach,	at45d_attach),
273 	DEVMETHOD(device_detach,	at45d_detach),
274 
275 	{ 0, 0 }
276 };
277 
278 static driver_t at45d_driver = {
279 	"at45d",
280 	at45d_methods,
281 	sizeof(struct at45d_softc),
282 };
283 
284 DRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, 0, 0);
285