at45d.c revision 164742
1201806Sbz/*-
2201806Sbz * Copyright (c) 2006 M. Warner Losh.  All rights reserved.
3201806Sbz *
4201806Sbz * Redistribution and use in source and binary forms, with or without
5201806Sbz * modification, are permitted provided that the following conditions
6201806Sbz * are met:
7201806Sbz * 1. Redistributions of source code must retain the above copyright
8201806Sbz *    notice, this list of conditions and the following disclaimer.
9201806Sbz * 2. Redistributions in binary form must reproduce the above copyright
10201806Sbz *    notice, this list of conditions and the following disclaimer in the
11201806Sbz *    documentation and/or other materials provided with the distribution.
12201806Sbz *
13201806Sbz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14201806Sbz * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15201806Sbz * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16201806Sbz * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17201806Sbz * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18201806Sbz * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19201806Sbz * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20201806Sbz * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21201806Sbz * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22201806Sbz * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23201806Sbz */
24201806Sbz
25201806Sbz#include <sys/cdefs.h>
26201806Sbz__FBSDID("$FreeBSD: head/sys/dev/flash/at45d.c 164742 2006-11-29 08:05:55Z imp $");
27201806Sbz
28201806Sbz#include <sys/param.h>
29201806Sbz#include <sys/systm.h>
30201806Sbz#include <sys/bio.h>
31201806Sbz#include <sys/bus.h>
32201806Sbz#include <sys/conf.h>
33201806Sbz#include <sys/gpio.h>
34201806Sbz#include <sys/kernel.h>
35201806Sbz#include <sys/kthread.h>
36201806Sbz#include <sys/lock.h>
37201806Sbz#include <sys/mbuf.h>
38201806Sbz#include <sys/malloc.h>
39201806Sbz#include <sys/module.h>
40201806Sbz#include <sys/mutex.h>
41201806Sbz#include <geom/geom_disk.h>
42201806Sbz
43201897Sbz#include <dev/spibus/spi.h>
44201806Sbz#include "spibus_if.h"
45201806Sbz
46201806Sbzstruct at45d_softc
47201806Sbz{
48201806Sbz	struct intr_config_hook config_intrhook;
49201806Sbz	device_t dev;
50201806Sbz	struct mtx sc_mtx;
51201806Sbz	struct disk *disk;
52201806Sbz	struct proc *p;
53201806Sbz	struct bio_queue_head bio_queue;
54201806Sbz};
55201806Sbz
56201806Sbz#define AT45D_LOCK(_sc)		mtx_lock(&(_sc)->sc_mtx)
57201806Sbz#define	AT45D_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
58201806Sbz#define AT45D_LOCK_INIT(_sc) \
59201806Sbz	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
60201806Sbz	    "at45d", MTX_DEF)
61201806Sbz#define AT45D_LOCK_DESTROY(_sc)	mtx_destroy(&_sc->sc_mtx);
62201806Sbz#define AT45D_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_OWNED);
63201806Sbz#define AT45D_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
64201806Sbz
65201806Sbzstatic void at45d_delayed_attach(void *xsc);
66201806Sbz
67201806Sbz/* disk routines */
68201806Sbzstatic int at45d_open(struct disk *dp);
69201806Sbzstatic int at45d_close(struct disk *dp);
70201806Sbzstatic void at45d_strategy(struct bio *bp);
71201806Sbzstatic void at45d_task(void *arg);
72201806Sbz
73201806Sbz#define CONTINUOUS_ARRAY_READ		0xE8
74201806Sbz#define CONTINUOUS_ARRAY_READ_HF	0x0B
75201806Sbz#define CONTINUOUS_ARRAY_READ_LF	0x03
76201806Sbz#define STATUS_REGISTER_READ		0xD7
77201806Sbz#define PROGRAM_THROUGH_BUFFER		0x82
78201806Sbz#define MANUFACTURER_ID			0x9F
79201806Sbz
80201806Sbzstatic uint8_t
81201806Sbzat45d_get_status(device_t dev)
82201806Sbz{
83201806Sbz	uint8_t txBuf[8], rxBuf[8];
84201806Sbz	struct spi_command cmd;
85201806Sbz	int err;
86201806Sbz
87201806Sbz	memset(&cmd, 0, sizeof(cmd));
88201806Sbz	memset(txBuf, 0, sizeof(txBuf));
89201806Sbz	memset(rxBuf, 0, sizeof(rxBuf));
90201806Sbz
91201806Sbz	txBuf[0] = STATUS_REGISTER_READ;
92201806Sbz	cmd.tx_cmd = txBuf;
93201806Sbz	cmd.rx_cmd = rxBuf;
94201806Sbz	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
100static void
101at45d_wait_for_device_ready(device_t dev)
102{
103	while (!(at45d_get_status(dev) & 0x80))
104		continue;
105}
106
107static int
108at45d_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
133static int
134at45d_probe(device_t dev)
135{
136	device_set_desc(dev, "AT45 Flash Family");
137	return (0);
138}
139
140static int
141at45d_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
157static int
158at45d_detach(device_t dev)
159{
160	return EIO;
161}
162
163static void
164at45d_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
189static int
190at45d_open(struct disk *dp)
191{
192	return 0;
193}
194
195static int
196at45d_close(struct disk *dp)
197{
198	return 0;
199}
200
201static void
202at45d_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
213static void
214at45d_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
267static devclass_t at45d_devclass;
268
269static 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
278static driver_t at45d_driver = {
279	"at45d",
280	at45d_methods,
281	sizeof(struct at45d_softc),
282};
283
284DRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, 0, 0);
285