1164742Simp/*-
2237384Smarius * Copyright (c) 2006 M. Warner Losh
3237384Smarius * Copyright (c) 2011-2012 Ian Lepore
4237384Smarius * Copyright (c) 2012 Marius Strobl <marius@FreeBSD.org>
5237384Smarius * All rights reserved.
6164742Simp *
7164742Simp * Redistribution and use in source and binary forms, with or without
8164742Simp * modification, are permitted provided that the following conditions
9164742Simp * are met:
10164742Simp * 1. Redistributions of source code must retain the above copyright
11164742Simp *    notice, this list of conditions and the following disclaimer.
12164742Simp * 2. Redistributions in binary form must reproduce the above copyright
13164742Simp *    notice, this list of conditions and the following disclaimer in the
14164742Simp *    documentation and/or other materials provided with the distribution.
15164742Simp *
16164742Simp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17164742Simp * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18164742Simp * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19164742Simp * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20164742Simp * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21164742Simp * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22164742Simp * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23164742Simp * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24164742Simp * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25164742Simp * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26164742Simp */
27164742Simp
28164742Simp#include <sys/cdefs.h>
29164742Simp__FBSDID("$FreeBSD$");
30164742Simp
31164742Simp#include <sys/param.h>
32164742Simp#include <sys/systm.h>
33164742Simp#include <sys/bio.h>
34164742Simp#include <sys/bus.h>
35164742Simp#include <sys/conf.h>
36164742Simp#include <sys/kernel.h>
37164742Simp#include <sys/kthread.h>
38164742Simp#include <sys/lock.h>
39164742Simp#include <sys/mbuf.h>
40164742Simp#include <sys/malloc.h>
41164742Simp#include <sys/module.h>
42164742Simp#include <sys/mutex.h>
43164742Simp#include <geom/geom_disk.h>
44164742Simp
45164742Simp#include <dev/spibus/spi.h>
46164742Simp#include "spibus_if.h"
47164742Simp
48237384Smariusstruct at45d_flash_ident
49164742Simp{
50237384Smarius	const char	*name;
51237384Smarius	uint32_t	jedec;
52237384Smarius	uint16_t	pagecount;
53237384Smarius	uint16_t	pageoffset;
54237384Smarius	uint16_t	pagesize;
55237384Smarius	uint16_t	pagesize2n;
56164742Simp};
57164742Simp
58237384Smariusstruct at45d_softc
59237384Smarius{
60237384Smarius	struct bio_queue_head	bio_queue;
61237384Smarius	struct mtx		sc_mtx;
62237384Smarius	struct disk		*disk;
63237384Smarius	struct proc		*p;
64237384Smarius	struct intr_config_hook	config_intrhook;
65237384Smarius	device_t		dev;
66237384Smarius	uint16_t		pagecount;
67237384Smarius	uint16_t		pageoffset;
68237384Smarius	uint16_t		pagesize;
69237384Smarius};
70237384Smarius
71237384Smarius#define	AT45D_LOCK(_sc)			mtx_lock(&(_sc)->sc_mtx)
72164742Simp#define	AT45D_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
73237384Smarius#define	AT45D_LOCK_INIT(_sc) \
74164742Simp	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
75164742Simp	    "at45d", MTX_DEF)
76237384Smarius#define	AT45D_LOCK_DESTROY(_sc)		mtx_destroy(&_sc->sc_mtx);
77237384Smarius#define	AT45D_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_OWNED);
78237384Smarius#define	AT45D_ASSERT_UNLOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
79164742Simp
80237384Smarius/* bus entry points */
81237384Smariusstatic device_attach_t at45d_attach;
82237384Smariusstatic device_detach_t at45d_detach;
83237384Smariusstatic device_probe_t at45d_probe;
84164742Simp
85164742Simp/* disk routines */
86237384Smariusstatic int at45d_close(struct disk *dp);
87164742Simpstatic int at45d_open(struct disk *dp);
88164742Simpstatic void at45d_strategy(struct bio *bp);
89164742Simpstatic void at45d_task(void *arg);
90164742Simp
91237384Smarius/* helper routines */
92237384Smariusstatic void at45d_delayed_attach(void *xsc);
93237384Smariusstatic int at45d_get_mfg_info(device_t dev, uint8_t *resp);
94237384Smariusstatic int at45d_get_status(device_t dev, uint8_t *status);
95237384Smariusstatic int at45d_wait_ready(device_t dev, uint8_t *status);
96164742Simp
97237384Smarius#define	BUFFER_TRANSFER			0x53
98237384Smarius#define	BUFFER_COMPARE			0x60
99237384Smarius#define	PROGRAM_THROUGH_BUFFER		0x82
100237384Smarius#define	MANUFACTURER_ID			0x9f
101237384Smarius#define	STATUS_REGISTER_READ		0xd7
102237384Smarius#define	CONTINUOUS_ARRAY_READ		0xe8
103237384Smarius
104237384Smarius/*
105237384Smarius * A sectorsize2n != 0 is used to indicate that a device optionally supports
106237384Smarius * 2^N byte pages.  If support for the latter is enabled, the sector offset
107237384Smarius * has to be reduced by one.
108237384Smarius */
109242908Sdimstatic const struct at45d_flash_ident at45d_flash_devices[] = {
110237384Smarius	{ "AT45DB011B", 0x1f2200, 512, 9, 264, 256 },
111237384Smarius	{ "AT45DB021B", 0x1f2300, 1024, 9, 264, 256 },
112237384Smarius	{ "AT45DB041x", 0x1f2400, 2028, 9, 264, 256 },
113237384Smarius	{ "AT45DB081B", 0x1f2500, 4096, 9, 264, 256 },
114237384Smarius	{ "AT45DB161x", 0x1f2600, 4096, 10, 528, 512 },
115237384Smarius	{ "AT45DB321x", 0x1f2700, 8192, 10, 528, 0 },
116237384Smarius	{ "AT45DB321x", 0x1f2701, 8192, 10, 528, 512 },
117237384Smarius	{ "AT45DB642x", 0x1f2800, 8192, 11, 1056, 1024 }
118237384Smarius};
119237384Smarius
120237384Smariusstatic int
121237384Smariusat45d_get_status(device_t dev, uint8_t *status)
122164742Simp{
123237384Smarius	uint8_t rxBuf[8], txBuf[8];
124164742Simp	struct spi_command cmd;
125164742Simp	int err;
126164742Simp
127164742Simp	memset(&cmd, 0, sizeof(cmd));
128164742Simp	memset(txBuf, 0, sizeof(txBuf));
129164742Simp	memset(rxBuf, 0, sizeof(rxBuf));
130164742Simp
131164742Simp	txBuf[0] = STATUS_REGISTER_READ;
132164742Simp	cmd.tx_cmd = txBuf;
133164742Simp	cmd.rx_cmd = rxBuf;
134237384Smarius	cmd.rx_cmd_sz = cmd.tx_cmd_sz = 2;
135164742Simp	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
136237384Smarius	*status = rxBuf[1];
137237384Smarius	return (err);
138164742Simp}
139164742Simp
140164742Simpstatic int
141164742Simpat45d_get_mfg_info(device_t dev, uint8_t *resp)
142164742Simp{
143237384Smarius	uint8_t rxBuf[8], txBuf[8];
144164742Simp	struct spi_command cmd;
145164742Simp	int err;
146164742Simp
147164742Simp	memset(&cmd, 0, sizeof(cmd));
148164742Simp	memset(txBuf, 0, sizeof(txBuf));
149164742Simp	memset(rxBuf, 0, sizeof(rxBuf));
150164742Simp
151164742Simp	txBuf[0] = MANUFACTURER_ID;
152164742Simp	cmd.tx_cmd = &txBuf;
153164742Simp	cmd.rx_cmd = &rxBuf;
154237384Smarius	cmd.tx_cmd_sz = cmd.rx_cmd_sz = 5;
155164742Simp	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
156164742Simp	if (err)
157164742Simp		return (err);
158164742Simp	memcpy(resp, rxBuf + 1, 4);
159164742Simp	return (0);
160164742Simp}
161164742Simp
162164742Simpstatic int
163237384Smariusat45d_wait_ready(device_t dev, uint8_t *status)
164237384Smarius{
165237384Smarius	struct timeval now, tout;
166237384Smarius	int err;
167237384Smarius
168237384Smarius	getmicrouptime(&tout);
169237384Smarius	tout.tv_sec += 3;
170237384Smarius	do {
171237384Smarius		getmicrouptime(&now);
172237384Smarius		if (now.tv_sec > tout.tv_sec)
173237384Smarius			err = ETIMEDOUT;
174237384Smarius		else
175237384Smarius			err = at45d_get_status(dev, status);
176237384Smarius	} while (err == 0 && (*status & 0x80) == 0);
177237384Smarius	return (err);
178237384Smarius}
179237384Smarius
180237384Smariusstatic int
181164742Simpat45d_probe(device_t dev)
182164742Simp{
183237384Smarius
184237384Smarius	device_set_desc(dev, "AT45D Flash Family");
185164742Simp	return (0);
186164742Simp}
187164742Simp
188164742Simpstatic int
189164742Simpat45d_attach(device_t dev)
190164742Simp{
191164742Simp	struct at45d_softc *sc;
192164742Simp
193164742Simp	sc = device_get_softc(dev);
194164742Simp	sc->dev = dev;
195164742Simp	AT45D_LOCK_INIT(sc);
196164742Simp
197164742Simp	/* We'll see what kind of flash we have later... */
198164742Simp	sc->config_intrhook.ich_func = at45d_delayed_attach;
199164742Simp	sc->config_intrhook.ich_arg = sc;
200164742Simp	if (config_intrhook_establish(&sc->config_intrhook) != 0)
201164742Simp		device_printf(dev, "config_intrhook_establish failed\n");
202164742Simp	return (0);
203164742Simp}
204164742Simp
205164742Simpstatic int
206164742Simpat45d_detach(device_t dev)
207164742Simp{
208237384Smarius
209237384Smarius	return (EBUSY) /* XXX */;
210164742Simp}
211164742Simp
212164742Simpstatic void
213164742Simpat45d_delayed_attach(void *xsc)
214164742Simp{
215237384Smarius	struct at45d_softc *sc;
216237384Smarius	const struct at45d_flash_ident *ident;
217237384Smarius	u_int i;
218237384Smarius	uint32_t jedec;
219237384Smarius	uint16_t pagesize;
220237384Smarius	uint8_t buf[4], status;
221164742Simp
222237384Smarius	sc = xsc;
223237384Smarius	ident = NULL;
224237384Smarius	jedec = 0;
225164742Simp
226237384Smarius	if (at45d_wait_ready(sc->dev, &status) != 0)
227237384Smarius		device_printf(sc->dev, "Error waiting for device-ready.\n");
228237384Smarius	else if (at45d_get_mfg_info(sc->dev, buf) != 0)
229237384Smarius		device_printf(sc->dev, "Failed to get ID.\n");
230237384Smarius	else {
231237384Smarius		jedec = buf[0] << 16 | buf[1] << 8 | buf[2];
232237384Smarius		for (i = 0; i < nitems(at45d_flash_devices); i++) {
233237384Smarius			if (at45d_flash_devices[i].jedec == jedec) {
234237384Smarius				ident = &at45d_flash_devices[i];
235237384Smarius				break;
236237384Smarius			}
237237384Smarius		}
238237384Smarius	}
239237384Smarius	if (ident == NULL)
240237384Smarius		device_printf(sc->dev, "JEDEC 0x%x not in list.\n", jedec);
241237384Smarius	else {
242237384Smarius		sc->pagecount = ident->pagecount;
243237384Smarius		sc->pageoffset = ident->pageoffset;
244237384Smarius		if (ident->pagesize2n != 0 && (status & 0x01) != 0) {
245237384Smarius			sc->pageoffset -= 1;
246237384Smarius			pagesize = ident->pagesize2n;
247237384Smarius		} else
248237384Smarius			pagesize = ident->pagesize;
249237384Smarius		sc->pagesize = pagesize;
250237384Smarius
251237384Smarius		sc->disk = disk_alloc();
252237384Smarius		sc->disk->d_open = at45d_open;
253237384Smarius		sc->disk->d_close = at45d_close;
254237384Smarius		sc->disk->d_strategy = at45d_strategy;
255237384Smarius		sc->disk->d_name = "flash/spi";
256237384Smarius		sc->disk->d_drv1 = sc;
257237384Smarius		sc->disk->d_maxsize = DFLTPHYS;
258237384Smarius		sc->disk->d_sectorsize = pagesize;
259237384Smarius		sc->disk->d_mediasize = pagesize * ident->pagecount;
260237384Smarius		sc->disk->d_unit = device_get_unit(sc->dev);
261237384Smarius		disk_create(sc->disk, DISK_VERSION);
262237384Smarius		bioq_init(&sc->bio_queue);
263237384Smarius		kproc_create(&at45d_task, sc, &sc->p, 0, 0,
264237384Smarius		    "task: at45d flash");
265237384Smarius		device_printf(sc->dev, "%s, %d bytes per page, %d pages\n",
266237384Smarius		    ident->name, pagesize, ident->pagecount);
267237384Smarius	}
268237384Smarius
269164742Simp	config_intrhook_disestablish(&sc->config_intrhook);
270164742Simp}
271164742Simp
272164742Simpstatic int
273164742Simpat45d_open(struct disk *dp)
274164742Simp{
275237384Smarius
276237384Smarius	return (0);
277164742Simp}
278164742Simp
279164742Simpstatic int
280164742Simpat45d_close(struct disk *dp)
281164742Simp{
282237384Smarius
283237384Smarius	return (0);
284164742Simp}
285164742Simp
286164742Simpstatic void
287164742Simpat45d_strategy(struct bio *bp)
288164742Simp{
289164742Simp	struct at45d_softc *sc;
290164742Simp
291164742Simp	sc = (struct at45d_softc *)bp->bio_disk->d_drv1;
292164742Simp	AT45D_LOCK(sc);
293164742Simp	bioq_disksort(&sc->bio_queue, bp);
294164742Simp	wakeup(sc);
295164742Simp	AT45D_UNLOCK(sc);
296164742Simp}
297164742Simp
298164742Simpstatic void
299164742Simpat45d_task(void *arg)
300164742Simp{
301237384Smarius	uint8_t rxBuf[8], txBuf[8];
302237384Smarius	struct at45d_softc *sc;
303164742Simp	struct bio *bp;
304164742Simp	struct spi_command cmd;
305164742Simp	device_t dev, pdev;
306237384Smarius	caddr_t buf;
307237384Smarius	u_long len, resid;
308237384Smarius	u_int addr, berr, err, offset, page;
309237384Smarius	uint8_t status;
310164742Simp
311237384Smarius	sc = (struct at45d_softc*)arg;
312237384Smarius	dev = sc->dev;
313237384Smarius	pdev = device_get_parent(dev);
314237384Smarius	memset(&cmd, 0, sizeof(cmd));
315237384Smarius	memset(txBuf, 0, sizeof(txBuf));
316237384Smarius	memset(rxBuf, 0, sizeof(rxBuf));
317237384Smarius	cmd.tx_cmd = txBuf;
318237384Smarius	cmd.rx_cmd = rxBuf;
319237384Smarius
320164742Simp	for (;;) {
321164742Simp		AT45D_LOCK(sc);
322164742Simp		do {
323237384Smarius			bp = bioq_takefirst(&sc->bio_queue);
324164742Simp			if (bp == NULL)
325164742Simp				msleep(sc, &sc->sc_mtx, PRIBIO, "jobqueue", 0);
326164742Simp		} while (bp == NULL);
327164742Simp		AT45D_UNLOCK(sc);
328237384Smarius
329237384Smarius		berr = 0;
330237384Smarius		buf = bp->bio_data;
331237384Smarius		len = resid = bp->bio_bcount;
332237384Smarius		page = bp->bio_offset / sc->pagesize;
333237384Smarius		offset = bp->bio_offset % sc->pagesize;
334237384Smarius
335237384Smarius		switch (bp->bio_cmd) {
336237384Smarius		case BIO_READ:
337237384Smarius			txBuf[0] = CONTINUOUS_ARRAY_READ;
338237384Smarius			cmd.tx_cmd_sz = cmd.rx_cmd_sz = 8;
339237384Smarius			cmd.tx_data = cmd.rx_data = buf;
340237384Smarius			break;
341237384Smarius		case BIO_WRITE:
342237384Smarius			cmd.tx_cmd_sz = cmd.rx_cmd_sz = 4;
343237384Smarius			cmd.tx_data = cmd.rx_data = buf;
344237384Smarius			if (resid + offset > sc->pagesize)
345237384Smarius				len = sc->pagesize - offset;
346237384Smarius			break;
347237384Smarius		default:
348237384Smarius			berr = EINVAL;
349237384Smarius			goto out;
350237384Smarius		}
351237384Smarius
352237384Smarius		/*
353237384Smarius		 * NB: for BIO_READ, this loop is only traversed once.
354237384Smarius		 */
355237384Smarius		while (resid > 0) {
356237384Smarius			if (page > sc->pagecount) {
357237384Smarius				berr = EINVAL;
358237384Smarius				goto out;
359237384Smarius			}
360237384Smarius			addr = page << sc->pageoffset;
361237384Smarius			if (bp->bio_cmd == BIO_WRITE) {
362237384Smarius				if (len != sc->pagesize) {
363237384Smarius					txBuf[0] = BUFFER_TRANSFER;
364237384Smarius					txBuf[1] = ((addr >> 16) & 0xff);
365237384Smarius					txBuf[2] = ((addr >> 8) & 0xff);
366237384Smarius					txBuf[3] = 0;
367237384Smarius					cmd.tx_data_sz = cmd.rx_data_sz = 0;
368237384Smarius					err = SPIBUS_TRANSFER(pdev, dev,
369237384Smarius					    &cmd);
370237384Smarius					if (err == 0)
371237384Smarius						err = at45d_wait_ready(dev,
372237384Smarius						    &status);
373237384Smarius					if (err != 0) {
374237384Smarius						berr = EIO;
375237384Smarius						goto out;
376237384Smarius					}
377237384Smarius				}
378164742Simp				txBuf[0] = PROGRAM_THROUGH_BUFFER;
379164742Simp			}
380237384Smarius
381237384Smarius			addr += offset;
382237384Smarius			txBuf[1] = ((addr >> 16) & 0xff);
383237384Smarius			txBuf[2] = ((addr >> 8) & 0xff);
384237384Smarius			txBuf[3] = (addr & 0xff);
385237384Smarius			cmd.tx_data_sz = cmd.rx_data_sz = len;
386164742Simp			err = SPIBUS_TRANSFER(pdev, dev, &cmd);
387237384Smarius			if (err == 0 && bp->bio_cmd != BIO_READ)
388237384Smarius				err = at45d_wait_ready(dev, &status);
389237384Smarius			if (err != 0) {
390237384Smarius				berr = EIO;
391237384Smarius				goto out;
392237384Smarius			}
393237384Smarius			if (bp->bio_cmd == BIO_WRITE) {
394237384Smarius				addr = page << sc->pageoffset;
395237384Smarius				txBuf[0] = BUFFER_COMPARE;
396237384Smarius				txBuf[1] = ((addr >> 16) & 0xff);
397237384Smarius				txBuf[2] = ((addr >> 8) & 0xff);
398237384Smarius				txBuf[3] = 0;
399237384Smarius				cmd.tx_data_sz = cmd.rx_data_sz = 0;
400237384Smarius				err = SPIBUS_TRANSFER(pdev, dev, &cmd);
401237384Smarius				if (err == 0)
402237384Smarius					err = at45d_wait_ready(dev, &status);
403237384Smarius				if (err != 0 || (status & 0x40) != 0) {
404237384Smarius					device_printf(dev, "comparing page "
405237384Smarius					    "%d failed (status=0x%x)\n", addr,
406237384Smarius					    status);
407237384Smarius					berr = EIO;
408237384Smarius					goto out;
409237384Smarius				}
410237384Smarius			}
411237384Smarius			page++;
412237384Smarius			buf += len;
413237384Smarius			offset = 0;
414237384Smarius			resid -= len;
415237384Smarius			if (resid > sc->pagesize)
416237384Smarius				len = sc->pagesize;
417237384Smarius			else
418237384Smarius				len = resid;
419237384Smarius			cmd.tx_data = cmd.rx_data = buf;
420164742Simp		}
421237384Smarius out:
422237384Smarius		if (berr != 0) {
423237384Smarius			bp->bio_flags |= BIO_ERROR;
424237384Smarius			bp->bio_error = berr;
425237384Smarius		}
426237384Smarius		bp->bio_resid = resid;
427164742Simp		biodone(bp);
428164742Simp	}
429164742Simp}
430164742Simp
431164742Simpstatic devclass_t at45d_devclass;
432164742Simp
433164742Simpstatic device_method_t at45d_methods[] = {
434164742Simp	/* Device interface */
435164742Simp	DEVMETHOD(device_probe,		at45d_probe),
436164742Simp	DEVMETHOD(device_attach,	at45d_attach),
437164742Simp	DEVMETHOD(device_detach,	at45d_detach),
438164742Simp
439237384Smarius	DEVMETHOD_END
440164742Simp};
441164742Simp
442164742Simpstatic driver_t at45d_driver = {
443164742Simp	"at45d",
444164742Simp	at45d_methods,
445164742Simp	sizeof(struct at45d_softc),
446164742Simp};
447164742Simp
448237384SmariusDRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, NULL, NULL);
449