at45d.c revision 242625
1227825Stheraven/*-
2227825Stheraven * Copyright (c) 2006 M. Warner Losh
3227825Stheraven * Copyright (c) 2011-2012 Ian Lepore
4227825Stheraven * Copyright (c) 2012 Marius Strobl <marius@FreeBSD.org>
5227825Stheraven * All rights reserved.
6227825Stheraven *
7227825Stheraven * Redistribution and use in source and binary forms, with or without
8227825Stheraven * modification, are permitted provided that the following conditions
9227825Stheraven * are met:
10227825Stheraven * 1. Redistributions of source code must retain the above copyright
11227825Stheraven *    notice, this list of conditions and the following disclaimer.
12227825Stheraven * 2. Redistributions in binary form must reproduce the above copyright
13227825Stheraven *    notice, this list of conditions and the following disclaimer in the
14227825Stheraven *    documentation and/or other materials provided with the distribution.
15227825Stheraven *
16227825Stheraven * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17227825Stheraven * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18227825Stheraven * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19227825Stheraven * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20227825Stheraven * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21232950Stheraven * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22232950Stheraven * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23262801Sdim * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24262801Sdim * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25262801Sdim * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26262801Sdim */
27262801Sdim
28262801Sdim#include <sys/cdefs.h>
29227825Stheraven__FBSDID("$FreeBSD: head/sys/dev/flash/at45d.c 242625 2012-11-05 19:16:27Z dim $");
30227825Stheraven
31227825Stheraven#include <sys/param.h>
32227825Stheraven#include <sys/systm.h>
33227825Stheraven#include <sys/bio.h>
34227825Stheraven#include <sys/bus.h>
35250514Sdim#include <sys/conf.h>
36227825Stheraven#include <sys/kernel.h>
37227825Stheraven#include <sys/kthread.h>
38227825Stheraven#include <sys/lock.h>
39227825Stheraven#include <sys/mbuf.h>
40227825Stheraven#include <sys/malloc.h>
41227825Stheraven#include <sys/module.h>
42227825Stheraven#include <sys/mutex.h>
43227825Stheraven#include <geom/geom_disk.h>
44227825Stheraven
45227825Stheraven#include <dev/spibus/spi.h>
46227825Stheraven#include "spibus_if.h"
47227825Stheraven
48227825Stheravenstruct at45d_flash_ident
49227825Stheraven{
50227825Stheraven	const char	*name;
51227825Stheraven	uint32_t	jedec;
52227825Stheraven	uint16_t	pagecount;
53227825Stheraven	uint16_t	pageoffset;
54227825Stheraven	uint16_t	pagesize;
55227825Stheraven	uint16_t	pagesize2n;
56227825Stheraven};
57227825Stheraven
58227825Stheravenstruct at45d_softc
59227825Stheraven{
60227825Stheraven	struct bio_queue_head	bio_queue;
61227825Stheraven	struct mtx		sc_mtx;
62227825Stheraven	struct disk		*disk;
63227825Stheraven	struct proc		*p;
64227825Stheraven	struct intr_config_hook	config_intrhook;
65227825Stheraven	device_t		dev;
66243376Sdim	uint16_t		pagecount;
67243376Sdim	uint16_t		pageoffset;
68243376Sdim	uint16_t		pagesize;
69243376Sdim};
70243376Sdim
71243376Sdim#define	AT45D_LOCK(_sc)			mtx_lock(&(_sc)->sc_mtx)
72243376Sdim#define	AT45D_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
73243376Sdim#define	AT45D_LOCK_INIT(_sc) \
74243376Sdim	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
75243376Sdim	    "at45d", MTX_DEF)
76243376Sdim#define	AT45D_LOCK_DESTROY(_sc)		mtx_destroy(&_sc->sc_mtx);
77243376Sdim#define	AT45D_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_OWNED);
78243376Sdim#define	AT45D_ASSERT_UNLOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
79243376Sdim
80243376Sdim/* bus entry points */
81243376Sdimstatic device_attach_t at45d_attach;
82243376Sdimstatic device_detach_t at45d_detach;
83243376Sdimstatic device_probe_t at45d_probe;
84243376Sdim
85243376Sdim/* disk routines */
86243376Sdimstatic int at45d_close(struct disk *dp);
87227825Stheravenstatic int at45d_open(struct disk *dp);
88262801Sdimstatic void at45d_strategy(struct bio *bp);
89262801Sdimstatic void at45d_task(void *arg);
90262801Sdim
91227825Stheraven/* helper routines */
92262801Sdimstatic void at45d_delayed_attach(void *xsc);
93227825Stheravenstatic int at45d_get_mfg_info(device_t dev, uint8_t *resp);
94227825Stheravenstatic int at45d_get_status(device_t dev, uint8_t *status);
95262801Sdimstatic int at45d_wait_ready(device_t dev, uint8_t *status);
96227825Stheraven
97227825Stheraven#define	BUFFER_TRANSFER			0x53
98227825Stheraven#define	BUFFER_COMPARE			0x60
99227825Stheraven#define	PROGRAM_THROUGH_BUFFER		0x82
100227825Stheraven#define	MANUFACTURER_ID			0x9f
101227825Stheraven#define	STATUS_REGISTER_READ		0xd7
102227825Stheraven#define	CONTINUOUS_ARRAY_READ		0xe8
103227825Stheraven
104227825Stheraven/*
105227825Stheraven * A sectorsize2n != 0 is used to indicate that a device optionally supports
106227825Stheraven * 2^N byte pages.  If support for the latter is enabled, the sector offset
107227825Stheraven * has to be reduced by one.
108227825Stheraven */
109227825Stheravenstatic const struct at45d_flash_ident at45d_flash_devices[] = {
110227825Stheraven	{ "AT45DB011B", 0x1f2200, 512, 9, 264, 256 },
111227825Stheraven	{ "AT45DB021B", 0x1f2300, 1024, 9, 264, 256 },
112227825Stheraven	{ "AT45DB041x", 0x1f2400, 2028, 9, 264, 256 },
113227825Stheraven	{ "AT45DB081B", 0x1f2500, 4096, 9, 264, 256 },
114262801Sdim	{ "AT45DB161x", 0x1f2600, 4096, 10, 528, 512 },
115262801Sdim	{ "AT45DB321x", 0x1f2700, 8192, 10, 528, 0 },
116262801Sdim	{ "AT45DB321x", 0x1f2701, 8192, 10, 528, 512 },
117262801Sdim	{ "AT45DB642x", 0x1f2800, 8192, 11, 1056, 1024 }
118262801Sdim};
119262801Sdim
120262801Sdimstatic int
121262801Sdimat45d_get_status(device_t dev, uint8_t *status)
122262801Sdim{
123227825Stheraven	uint8_t rxBuf[8], txBuf[8];
124262801Sdim	struct spi_command cmd;
125262801Sdim	int err;
126227825Stheraven
127262801Sdim	memset(&cmd, 0, sizeof(cmd));
128262801Sdim	memset(txBuf, 0, sizeof(txBuf));
129262801Sdim	memset(rxBuf, 0, sizeof(rxBuf));
130262801Sdim
131262801Sdim	txBuf[0] = STATUS_REGISTER_READ;
132262801Sdim	cmd.tx_cmd = txBuf;
133227825Stheraven	cmd.rx_cmd = rxBuf;
134262801Sdim	cmd.rx_cmd_sz = cmd.tx_cmd_sz = 2;
135262801Sdim	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
136262801Sdim	*status = rxBuf[1];
137262801Sdim	return (err);
138227825Stheraven}
139227825Stheraven
140262801Sdimstatic int
141262801Sdimat45d_get_mfg_info(device_t dev, uint8_t *resp)
142262801Sdim{
143262801Sdim	uint8_t rxBuf[8], txBuf[8];
144262801Sdim	struct spi_command cmd;
145262801Sdim	int err;
146262801Sdim
147262801Sdim	memset(&cmd, 0, sizeof(cmd));
148262801Sdim	memset(txBuf, 0, sizeof(txBuf));
149262801Sdim	memset(rxBuf, 0, sizeof(rxBuf));
150262801Sdim
151262801Sdim	txBuf[0] = MANUFACTURER_ID;
152262801Sdim	cmd.tx_cmd = &txBuf;
153262801Sdim	cmd.rx_cmd = &rxBuf;
154262801Sdim	cmd.tx_cmd_sz = cmd.rx_cmd_sz = 5;
155262801Sdim	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
156262801Sdim	if (err)
157262801Sdim		return (err);
158262801Sdim	memcpy(resp, rxBuf + 1, 4);
159262801Sdim	return (0);
160262801Sdim}
161262801Sdim
162262801Sdimstatic int
163262801Sdimat45d_wait_ready(device_t dev, uint8_t *status)
164262801Sdim{
165262801Sdim	struct timeval now, tout;
166262801Sdim	int err;
167262801Sdim
168262801Sdim	getmicrouptime(&tout);
169262801Sdim	tout.tv_sec += 3;
170262801Sdim	do {
171262801Sdim		getmicrouptime(&now);
172227825Stheraven		if (now.tv_sec > tout.tv_sec)
173227825Stheraven			err = ETIMEDOUT;
174262801Sdim		else
175262801Sdim			err = at45d_get_status(dev, status);
176262801Sdim	} while (err == 0 && (*status & 0x80) == 0);
177262801Sdim	return (err);
178227825Stheraven}
179227825Stheraven
180227825Stheravenstatic int
181227825Stheravenat45d_probe(device_t dev)
182227825Stheraven{
183227825Stheraven
184227825Stheraven	device_set_desc(dev, "AT45D Flash Family");
185227825Stheraven	return (0);
186227825Stheraven}
187227825Stheraven
188227825Stheravenstatic int
189227825Stheravenat45d_attach(device_t dev)
190227825Stheraven{
191227825Stheraven	struct at45d_softc *sc;
192262801Sdim
193262801Sdim	sc = device_get_softc(dev);
194262801Sdim	sc->dev = dev;
195227825Stheraven	AT45D_LOCK_INIT(sc);
196227825Stheraven
197262801Sdim	/* We'll see what kind of flash we have later... */
198227825Stheraven	sc->config_intrhook.ich_func = at45d_delayed_attach;
199227825Stheraven	sc->config_intrhook.ich_arg = sc;
200262801Sdim	if (config_intrhook_establish(&sc->config_intrhook) != 0)
201227825Stheraven		device_printf(dev, "config_intrhook_establish failed\n");
202262801Sdim	return (0);
203262801Sdim}
204262801Sdim
205262801Sdimstatic int
206262801Sdimat45d_detach(device_t dev)
207262801Sdim{
208262801Sdim
209227825Stheraven	return (EBUSY) /* XXX */;
210227825Stheraven}
211227825Stheraven
212262801Sdimstatic void
213227825Stheravenat45d_delayed_attach(void *xsc)
214227825Stheraven{
215262801Sdim	struct at45d_softc *sc;
216262801Sdim	const struct at45d_flash_ident *ident;
217262801Sdim	u_int i;
218262801Sdim	uint32_t jedec;
219227825Stheraven	uint16_t pagesize;
220227825Stheraven	uint8_t buf[4], status;
221227825Stheraven
222262801Sdim	sc = xsc;
223227825Stheraven	ident = NULL;
224227825Stheraven	jedec = 0;
225227825Stheraven
226227825Stheraven	if (at45d_wait_ready(sc->dev, &status) != 0)
227227825Stheraven		device_printf(sc->dev, "Error waiting for device-ready.\n");
228227825Stheraven	else if (at45d_get_mfg_info(sc->dev, buf) != 0)
229227825Stheraven		device_printf(sc->dev, "Failed to get ID.\n");
230227825Stheraven	else {
231227825Stheraven		jedec = buf[0] << 16 | buf[1] << 8 | buf[2];
232227825Stheraven		for (i = 0; i < nitems(at45d_flash_devices); i++) {
233227825Stheraven			if (at45d_flash_devices[i].jedec == jedec) {
234227825Stheraven				ident = &at45d_flash_devices[i];
235227825Stheraven				break;
236227825Stheraven			}
237227825Stheraven		}
238227825Stheraven	}
239227825Stheraven	if (ident == NULL)
240227825Stheraven		device_printf(sc->dev, "JEDEC 0x%x not in list.\n", jedec);
241227825Stheraven	else {
242227825Stheraven		sc->pagecount = ident->pagecount;
243227825Stheraven		sc->pageoffset = ident->pageoffset;
244227825Stheraven		if (ident->pagesize2n != 0 && (status & 0x01) != 0) {
245227825Stheraven			sc->pageoffset -= 1;
246227825Stheraven			pagesize = ident->pagesize2n;
247227825Stheraven		} else
248227825Stheraven			pagesize = ident->pagesize;
249227825Stheraven		sc->pagesize = pagesize;
250227825Stheraven
251227825Stheraven		sc->disk = disk_alloc();
252227825Stheraven		sc->disk->d_open = at45d_open;
253262801Sdim		sc->disk->d_close = at45d_close;
254262801Sdim		sc->disk->d_strategy = at45d_strategy;
255262801Sdim		sc->disk->d_name = "flash/spi";
256262801Sdim		sc->disk->d_drv1 = sc;
257262801Sdim		sc->disk->d_maxsize = DFLTPHYS;
258262801Sdim		sc->disk->d_sectorsize = pagesize;
259262801Sdim		sc->disk->d_mediasize = pagesize * ident->pagecount;
260262801Sdim		sc->disk->d_unit = device_get_unit(sc->dev);
261262801Sdim		disk_create(sc->disk, DISK_VERSION);
262227825Stheraven		bioq_init(&sc->bio_queue);
263227825Stheraven		kproc_create(&at45d_task, sc, &sc->p, 0, 0,
264227825Stheraven		    "task: at45d flash");
265262801Sdim		device_printf(sc->dev, "%s, %d bytes per page, %d pages\n",
266262801Sdim		    ident->name, pagesize, ident->pagecount);
267262801Sdim	}
268262801Sdim
269262801Sdim	config_intrhook_disestablish(&sc->config_intrhook);
270227825Stheraven}
271262801Sdim
272262801Sdimstatic int
273227825Stheravenat45d_open(struct disk *dp)
274262801Sdim{
275262801Sdim
276262801Sdim	return (0);
277262801Sdim}
278262801Sdim
279262801Sdimstatic int
280227825Stheravenat45d_close(struct disk *dp)
281262801Sdim{
282262801Sdim
283262801Sdim	return (0);
284262801Sdim}
285227825Stheraven
286227825Stheravenstatic void
287262801Sdimat45d_strategy(struct bio *bp)
288262801Sdim{
289262801Sdim	struct at45d_softc *sc;
290262801Sdim
291262801Sdim	sc = (struct at45d_softc *)bp->bio_disk->d_drv1;
292262801Sdim	AT45D_LOCK(sc);
293262801Sdim	bioq_disksort(&sc->bio_queue, bp);
294262801Sdim	wakeup(sc);
295262801Sdim	AT45D_UNLOCK(sc);
296262801Sdim}
297262801Sdim
298262801Sdimstatic void
299262801Sdimat45d_task(void *arg)
300262801Sdim{
301262801Sdim	uint8_t rxBuf[8], txBuf[8];
302262801Sdim	struct at45d_softc *sc;
303262801Sdim	struct bio *bp;
304262801Sdim	struct spi_command cmd;
305262801Sdim	device_t dev, pdev;
306262801Sdim	caddr_t buf;
307262801Sdim	u_long len, resid;
308262801Sdim	u_int addr, berr, err, offset, page;
309262801Sdim	uint8_t status;
310262801Sdim
311262801Sdim	sc = (struct at45d_softc*)arg;
312262801Sdim	dev = sc->dev;
313262801Sdim	pdev = device_get_parent(dev);
314262801Sdim	memset(&cmd, 0, sizeof(cmd));
315262801Sdim	memset(txBuf, 0, sizeof(txBuf));
316262801Sdim	memset(rxBuf, 0, sizeof(rxBuf));
317262801Sdim	cmd.tx_cmd = txBuf;
318262801Sdim	cmd.rx_cmd = rxBuf;
319227825Stheraven
320227825Stheraven	for (;;) {
321262801Sdim		AT45D_LOCK(sc);
322262801Sdim		do {
323262801Sdim			bp = bioq_takefirst(&sc->bio_queue);
324262801Sdim			if (bp == NULL)
325227825Stheraven				msleep(sc, &sc->sc_mtx, PRIBIO, "jobqueue", 0);
326227825Stheraven		} while (bp == NULL);
327227825Stheraven		AT45D_UNLOCK(sc);
328227825Stheraven
329227825Stheraven		berr = 0;
330227825Stheraven		buf = bp->bio_data;
331227825Stheraven		len = resid = bp->bio_bcount;
332227825Stheraven		page = bp->bio_offset / sc->pagesize;
333227825Stheraven		offset = bp->bio_offset % sc->pagesize;
334227825Stheraven
335227825Stheraven		switch (bp->bio_cmd) {
336227825Stheraven		case BIO_READ:
337227825Stheraven			txBuf[0] = CONTINUOUS_ARRAY_READ;
338227825Stheraven			cmd.tx_cmd_sz = cmd.rx_cmd_sz = 8;
339262801Sdim			cmd.tx_data = cmd.rx_data = buf;
340262801Sdim			break;
341262801Sdim		case BIO_WRITE:
342227825Stheraven			cmd.tx_cmd_sz = cmd.rx_cmd_sz = 4;
343227825Stheraven			cmd.tx_data = cmd.rx_data = buf;
344262801Sdim			if (resid + offset > sc->pagesize)
345227825Stheraven				len = sc->pagesize - offset;
346227825Stheraven			break;
347262801Sdim		default:
348227825Stheraven			berr = EINVAL;
349262801Sdim			goto out;
350262801Sdim		}
351262801Sdim
352262801Sdim		/*
353262801Sdim		 * NB: for BIO_READ, this loop is only traversed once.
354262801Sdim		 */
355262801Sdim		while (resid > 0) {
356227825Stheraven			if (page > sc->pagecount) {
357227825Stheraven				berr = EINVAL;
358227825Stheraven				goto out;
359262801Sdim			}
360227825Stheraven			addr = page << sc->pageoffset;
361227825Stheraven			if (bp->bio_cmd == BIO_WRITE) {
362262801Sdim				if (len != sc->pagesize) {
363262801Sdim					txBuf[0] = BUFFER_TRANSFER;
364262801Sdim					txBuf[1] = ((addr >> 16) & 0xff);
365227825Stheraven					txBuf[2] = ((addr >> 8) & 0xff);
366227825Stheraven					txBuf[3] = 0;
367262801Sdim					cmd.tx_data_sz = cmd.rx_data_sz = 0;
368227825Stheraven					err = SPIBUS_TRANSFER(pdev, dev,
369227825Stheraven					    &cmd);
370262801Sdim					if (err == 0)
371227825Stheraven						err = at45d_wait_ready(dev,
372227825Stheraven						    &status);
373227825Stheraven					if (err != 0) {
374227825Stheraven						berr = EIO;
375227825Stheraven						goto out;
376227825Stheraven					}
377227825Stheraven				}
378227825Stheraven				txBuf[0] = PROGRAM_THROUGH_BUFFER;
379227825Stheraven			}
380227825Stheraven
381227825Stheraven			addr += offset;
382227825Stheraven			txBuf[1] = ((addr >> 16) & 0xff);
383227825Stheraven			txBuf[2] = ((addr >> 8) & 0xff);
384227825Stheraven			txBuf[3] = (addr & 0xff);
385227825Stheraven			cmd.tx_data_sz = cmd.rx_data_sz = len;
386227825Stheraven			err = SPIBUS_TRANSFER(pdev, dev, &cmd);
387227825Stheraven			if (err == 0 && bp->bio_cmd != BIO_READ)
388227825Stheraven				err = at45d_wait_ready(dev, &status);
389227825Stheraven			if (err != 0) {
390227825Stheraven				berr = EIO;
391227825Stheraven				goto out;
392262801Sdim			}
393262801Sdim			if (bp->bio_cmd == BIO_WRITE) {
394262801Sdim				addr = page << sc->pageoffset;
395262801Sdim				txBuf[0] = BUFFER_COMPARE;
396262801Sdim				txBuf[1] = ((addr >> 16) & 0xff);
397262801Sdim				txBuf[2] = ((addr >> 8) & 0xff);
398227825Stheraven				txBuf[3] = 0;
399262801Sdim				cmd.tx_data_sz = cmd.rx_data_sz = 0;
400262801Sdim				err = SPIBUS_TRANSFER(pdev, dev, &cmd);
401227825Stheraven				if (err == 0)
402262801Sdim					err = at45d_wait_ready(dev, &status);
403262801Sdim				if (err != 0 || (status & 0x40) != 0) {
404262801Sdim					device_printf(dev, "comparing page "
405262801Sdim					    "%d failed (status=0x%x)\n", addr,
406262801Sdim					    status);
407262801Sdim					berr = EIO;
408262801Sdim					goto out;
409262801Sdim				}
410227825Stheraven			}
411262801Sdim			page++;
412262801Sdim			buf += len;
413262801Sdim			offset = 0;
414262801Sdim			resid -= len;
415227825Stheraven			if (resid > sc->pagesize)
416227825Stheraven				len = sc->pagesize;
417262801Sdim			else
418262801Sdim				len = resid;
419262801Sdim			cmd.tx_data = cmd.rx_data = buf;
420262801Sdim		}
421262801Sdim out:
422262801Sdim		if (berr != 0) {
423262801Sdim			bp->bio_flags |= BIO_ERROR;
424262801Sdim			bp->bio_error = berr;
425262801Sdim		}
426262801Sdim		bp->bio_resid = resid;
427262801Sdim		biodone(bp);
428262801Sdim	}
429262801Sdim}
430262801Sdim
431262801Sdimstatic devclass_t at45d_devclass;
432262801Sdim
433262801Sdimstatic device_method_t at45d_methods[] = {
434262801Sdim	/* Device interface */
435262801Sdim	DEVMETHOD(device_probe,		at45d_probe),
436262801Sdim	DEVMETHOD(device_attach,	at45d_attach),
437262801Sdim	DEVMETHOD(device_detach,	at45d_detach),
438262801Sdim
439262801Sdim	DEVMETHOD_END
440262801Sdim};
441262801Sdim
442262801Sdimstatic driver_t at45d_driver = {
443262801Sdim	"at45d",
444262801Sdim	at45d_methods,
445262801Sdim	sizeof(struct at45d_softc),
446262801Sdim};
447262801Sdim
448262801SdimDRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, NULL, NULL);
449262801Sdim