1164742Simp/*-
2236496Smarius * Copyright (c) 2006 M. Warner Losh
3236496Smarius * Copyright (c) 2011-2012 Ian Lepore
4236496Smarius * Copyright (c) 2012 Marius Strobl <marius@FreeBSD.org>
5236496Smarius * 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
48236496Smariusstruct at45d_flash_ident
49164742Simp{
50236496Smarius	const char	*name;
51236496Smarius	uint32_t	jedec;
52236496Smarius	uint16_t	pagecount;
53236496Smarius	uint16_t	pageoffset;
54236496Smarius	uint16_t	pagesize;
55236496Smarius	uint16_t	pagesize2n;
56164742Simp};
57164742Simp
58236496Smariusstruct at45d_softc
59236496Smarius{
60236496Smarius	struct bio_queue_head	bio_queue;
61236496Smarius	struct mtx		sc_mtx;
62236496Smarius	struct disk		*disk;
63236496Smarius	struct proc		*p;
64236496Smarius	struct intr_config_hook	config_intrhook;
65236496Smarius	device_t		dev;
66236496Smarius	uint16_t		pagecount;
67236496Smarius	uint16_t		pageoffset;
68236496Smarius	uint16_t		pagesize;
69236496Smarius};
70236496Smarius
71236496Smarius#define	AT45D_LOCK(_sc)			mtx_lock(&(_sc)->sc_mtx)
72164742Simp#define	AT45D_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
73236496Smarius#define	AT45D_LOCK_INIT(_sc) \
74164742Simp	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
75164742Simp	    "at45d", MTX_DEF)
76236496Smarius#define	AT45D_LOCK_DESTROY(_sc)		mtx_destroy(&_sc->sc_mtx);
77236496Smarius#define	AT45D_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_OWNED);
78236496Smarius#define	AT45D_ASSERT_UNLOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
79164742Simp
80236496Smarius/* bus entry points */
81236496Smariusstatic device_attach_t at45d_attach;
82236496Smariusstatic device_detach_t at45d_detach;
83236496Smariusstatic device_probe_t at45d_probe;
84164742Simp
85164742Simp/* disk routines */
86236496Smariusstatic 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
91236496Smarius/* helper routines */
92236496Smariusstatic void at45d_delayed_attach(void *xsc);
93236496Smariusstatic int at45d_get_mfg_info(device_t dev, uint8_t *resp);
94236496Smariusstatic int at45d_get_status(device_t dev, uint8_t *status);
95236496Smariusstatic int at45d_wait_ready(device_t dev, uint8_t *status);
96164742Simp
97236496Smarius#define	BUFFER_TRANSFER			0x53
98236496Smarius#define	BUFFER_COMPARE			0x60
99236496Smarius#define	PROGRAM_THROUGH_BUFFER		0x82
100236496Smarius#define	MANUFACTURER_ID			0x9f
101236496Smarius#define	STATUS_REGISTER_READ		0xd7
102236496Smarius#define	CONTINUOUS_ARRAY_READ		0xe8
103236496Smarius
104236496Smarius/*
105236496Smarius * A sectorsize2n != 0 is used to indicate that a device optionally supports
106236496Smarius * 2^N byte pages.  If support for the latter is enabled, the sector offset
107236496Smarius * has to be reduced by one.
108236496Smarius */
109242625Sdimstatic const struct at45d_flash_ident at45d_flash_devices[] = {
110236496Smarius	{ "AT45DB011B", 0x1f2200, 512, 9, 264, 256 },
111236496Smarius	{ "AT45DB021B", 0x1f2300, 1024, 9, 264, 256 },
112236496Smarius	{ "AT45DB041x", 0x1f2400, 2028, 9, 264, 256 },
113236496Smarius	{ "AT45DB081B", 0x1f2500, 4096, 9, 264, 256 },
114236496Smarius	{ "AT45DB161x", 0x1f2600, 4096, 10, 528, 512 },
115236496Smarius	{ "AT45DB321x", 0x1f2700, 8192, 10, 528, 0 },
116236496Smarius	{ "AT45DB321x", 0x1f2701, 8192, 10, 528, 512 },
117236496Smarius	{ "AT45DB642x", 0x1f2800, 8192, 11, 1056, 1024 }
118236496Smarius};
119236496Smarius
120236496Smariusstatic int
121236496Smariusat45d_get_status(device_t dev, uint8_t *status)
122164742Simp{
123236496Smarius	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;
134236496Smarius	cmd.rx_cmd_sz = cmd.tx_cmd_sz = 2;
135164742Simp	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
136236496Smarius	*status = rxBuf[1];
137236496Smarius	return (err);
138164742Simp}
139164742Simp
140164742Simpstatic int
141164742Simpat45d_get_mfg_info(device_t dev, uint8_t *resp)
142164742Simp{
143236496Smarius	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;
154236496Smarius	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
163236496Smariusat45d_wait_ready(device_t dev, uint8_t *status)
164236496Smarius{
165236496Smarius	struct timeval now, tout;
166236496Smarius	int err;
167236496Smarius
168236496Smarius	getmicrouptime(&tout);
169236496Smarius	tout.tv_sec += 3;
170236496Smarius	do {
171236496Smarius		getmicrouptime(&now);
172236496Smarius		if (now.tv_sec > tout.tv_sec)
173236496Smarius			err = ETIMEDOUT;
174236496Smarius		else
175236496Smarius			err = at45d_get_status(dev, status);
176236496Smarius	} while (err == 0 && (*status & 0x80) == 0);
177236496Smarius	return (err);
178236496Smarius}
179236496Smarius
180236496Smariusstatic int
181164742Simpat45d_probe(device_t dev)
182164742Simp{
183236496Smarius
184236496Smarius	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{
208236496Smarius
209236496Smarius	return (EBUSY) /* XXX */;
210164742Simp}
211164742Simp
212164742Simpstatic void
213164742Simpat45d_delayed_attach(void *xsc)
214164742Simp{
215236496Smarius	struct at45d_softc *sc;
216236496Smarius	const struct at45d_flash_ident *ident;
217236496Smarius	u_int i;
218236496Smarius	uint32_t jedec;
219236496Smarius	uint16_t pagesize;
220236496Smarius	uint8_t buf[4], status;
221164742Simp
222236496Smarius	sc = xsc;
223236496Smarius	ident = NULL;
224236496Smarius	jedec = 0;
225164742Simp
226236496Smarius	if (at45d_wait_ready(sc->dev, &status) != 0)
227236496Smarius		device_printf(sc->dev, "Error waiting for device-ready.\n");
228236496Smarius	else if (at45d_get_mfg_info(sc->dev, buf) != 0)
229236496Smarius		device_printf(sc->dev, "Failed to get ID.\n");
230236496Smarius	else {
231236496Smarius		jedec = buf[0] << 16 | buf[1] << 8 | buf[2];
232236496Smarius		for (i = 0; i < nitems(at45d_flash_devices); i++) {
233236496Smarius			if (at45d_flash_devices[i].jedec == jedec) {
234236496Smarius				ident = &at45d_flash_devices[i];
235236496Smarius				break;
236236496Smarius			}
237236496Smarius		}
238236496Smarius	}
239236496Smarius	if (ident == NULL)
240236496Smarius		device_printf(sc->dev, "JEDEC 0x%x not in list.\n", jedec);
241236496Smarius	else {
242236496Smarius		sc->pagecount = ident->pagecount;
243236496Smarius		sc->pageoffset = ident->pageoffset;
244236496Smarius		if (ident->pagesize2n != 0 && (status & 0x01) != 0) {
245236496Smarius			sc->pageoffset -= 1;
246236496Smarius			pagesize = ident->pagesize2n;
247236496Smarius		} else
248236496Smarius			pagesize = ident->pagesize;
249236496Smarius		sc->pagesize = pagesize;
250236496Smarius
251236496Smarius		sc->disk = disk_alloc();
252236496Smarius		sc->disk->d_open = at45d_open;
253236496Smarius		sc->disk->d_close = at45d_close;
254236496Smarius		sc->disk->d_strategy = at45d_strategy;
255236496Smarius		sc->disk->d_name = "flash/spi";
256236496Smarius		sc->disk->d_drv1 = sc;
257236496Smarius		sc->disk->d_maxsize = DFLTPHYS;
258236496Smarius		sc->disk->d_sectorsize = pagesize;
259236496Smarius		sc->disk->d_mediasize = pagesize * ident->pagecount;
260236496Smarius		sc->disk->d_unit = device_get_unit(sc->dev);
261236496Smarius		disk_create(sc->disk, DISK_VERSION);
262236496Smarius		bioq_init(&sc->bio_queue);
263236496Smarius		kproc_create(&at45d_task, sc, &sc->p, 0, 0,
264236496Smarius		    "task: at45d flash");
265236496Smarius		device_printf(sc->dev, "%s, %d bytes per page, %d pages\n",
266236496Smarius		    ident->name, pagesize, ident->pagecount);
267236496Smarius	}
268236496Smarius
269164742Simp	config_intrhook_disestablish(&sc->config_intrhook);
270164742Simp}
271164742Simp
272164742Simpstatic int
273164742Simpat45d_open(struct disk *dp)
274164742Simp{
275236496Smarius
276236496Smarius	return (0);
277164742Simp}
278164742Simp
279164742Simpstatic int
280164742Simpat45d_close(struct disk *dp)
281164742Simp{
282236496Smarius
283236496Smarius	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{
301236496Smarius	uint8_t rxBuf[8], txBuf[8];
302236496Smarius	struct at45d_softc *sc;
303164742Simp	struct bio *bp;
304164742Simp	struct spi_command cmd;
305164742Simp	device_t dev, pdev;
306236496Smarius	caddr_t buf;
307236496Smarius	u_long len, resid;
308236496Smarius	u_int addr, berr, err, offset, page;
309236496Smarius	uint8_t status;
310164742Simp
311236496Smarius	sc = (struct at45d_softc*)arg;
312236496Smarius	dev = sc->dev;
313236496Smarius	pdev = device_get_parent(dev);
314236496Smarius	memset(&cmd, 0, sizeof(cmd));
315236496Smarius	memset(txBuf, 0, sizeof(txBuf));
316236496Smarius	memset(rxBuf, 0, sizeof(rxBuf));
317236496Smarius	cmd.tx_cmd = txBuf;
318236496Smarius	cmd.rx_cmd = rxBuf;
319236496Smarius
320164742Simp	for (;;) {
321164742Simp		AT45D_LOCK(sc);
322164742Simp		do {
323236496Smarius			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);
328236496Smarius
329236496Smarius		berr = 0;
330236496Smarius		buf = bp->bio_data;
331236496Smarius		len = resid = bp->bio_bcount;
332236496Smarius		page = bp->bio_offset / sc->pagesize;
333236496Smarius		offset = bp->bio_offset % sc->pagesize;
334236496Smarius
335236496Smarius		switch (bp->bio_cmd) {
336236496Smarius		case BIO_READ:
337236496Smarius			txBuf[0] = CONTINUOUS_ARRAY_READ;
338236496Smarius			cmd.tx_cmd_sz = cmd.rx_cmd_sz = 8;
339236496Smarius			cmd.tx_data = cmd.rx_data = buf;
340236496Smarius			break;
341236496Smarius		case BIO_WRITE:
342236496Smarius			cmd.tx_cmd_sz = cmd.rx_cmd_sz = 4;
343236496Smarius			cmd.tx_data = cmd.rx_data = buf;
344236496Smarius			if (resid + offset > sc->pagesize)
345236496Smarius				len = sc->pagesize - offset;
346236496Smarius			break;
347236496Smarius		default:
348236496Smarius			berr = EINVAL;
349236496Smarius			goto out;
350236496Smarius		}
351236496Smarius
352236496Smarius		/*
353236496Smarius		 * NB: for BIO_READ, this loop is only traversed once.
354236496Smarius		 */
355236496Smarius		while (resid > 0) {
356236496Smarius			if (page > sc->pagecount) {
357236496Smarius				berr = EINVAL;
358236496Smarius				goto out;
359236496Smarius			}
360236496Smarius			addr = page << sc->pageoffset;
361236496Smarius			if (bp->bio_cmd == BIO_WRITE) {
362236496Smarius				if (len != sc->pagesize) {
363236496Smarius					txBuf[0] = BUFFER_TRANSFER;
364236496Smarius					txBuf[1] = ((addr >> 16) & 0xff);
365236496Smarius					txBuf[2] = ((addr >> 8) & 0xff);
366236496Smarius					txBuf[3] = 0;
367236496Smarius					cmd.tx_data_sz = cmd.rx_data_sz = 0;
368236496Smarius					err = SPIBUS_TRANSFER(pdev, dev,
369236496Smarius					    &cmd);
370236496Smarius					if (err == 0)
371236496Smarius						err = at45d_wait_ready(dev,
372236496Smarius						    &status);
373236496Smarius					if (err != 0) {
374236496Smarius						berr = EIO;
375236496Smarius						goto out;
376236496Smarius					}
377236496Smarius				}
378164742Simp				txBuf[0] = PROGRAM_THROUGH_BUFFER;
379164742Simp			}
380236496Smarius
381236496Smarius			addr += offset;
382236496Smarius			txBuf[1] = ((addr >> 16) & 0xff);
383236496Smarius			txBuf[2] = ((addr >> 8) & 0xff);
384236496Smarius			txBuf[3] = (addr & 0xff);
385236496Smarius			cmd.tx_data_sz = cmd.rx_data_sz = len;
386164742Simp			err = SPIBUS_TRANSFER(pdev, dev, &cmd);
387236496Smarius			if (err == 0 && bp->bio_cmd != BIO_READ)
388236496Smarius				err = at45d_wait_ready(dev, &status);
389236496Smarius			if (err != 0) {
390236496Smarius				berr = EIO;
391236496Smarius				goto out;
392236496Smarius			}
393236496Smarius			if (bp->bio_cmd == BIO_WRITE) {
394236496Smarius				addr = page << sc->pageoffset;
395236496Smarius				txBuf[0] = BUFFER_COMPARE;
396236496Smarius				txBuf[1] = ((addr >> 16) & 0xff);
397236496Smarius				txBuf[2] = ((addr >> 8) & 0xff);
398236496Smarius				txBuf[3] = 0;
399236496Smarius				cmd.tx_data_sz = cmd.rx_data_sz = 0;
400236496Smarius				err = SPIBUS_TRANSFER(pdev, dev, &cmd);
401236496Smarius				if (err == 0)
402236496Smarius					err = at45d_wait_ready(dev, &status);
403236496Smarius				if (err != 0 || (status & 0x40) != 0) {
404236496Smarius					device_printf(dev, "comparing page "
405236496Smarius					    "%d failed (status=0x%x)\n", addr,
406236496Smarius					    status);
407236496Smarius					berr = EIO;
408236496Smarius					goto out;
409236496Smarius				}
410236496Smarius			}
411236496Smarius			page++;
412236496Smarius			buf += len;
413236496Smarius			offset = 0;
414236496Smarius			resid -= len;
415236496Smarius			if (resid > sc->pagesize)
416236496Smarius				len = sc->pagesize;
417236496Smarius			else
418236496Smarius				len = resid;
419236496Smarius			cmd.tx_data = cmd.rx_data = buf;
420164742Simp		}
421236496Smarius out:
422236496Smarius		if (berr != 0) {
423236496Smarius			bp->bio_flags |= BIO_ERROR;
424236496Smarius			bp->bio_error = berr;
425236496Smarius		}
426236496Smarius		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
439236496Smarius	DEVMETHOD_END
440164742Simp};
441164742Simp
442164742Simpstatic driver_t at45d_driver = {
443164742Simp	"at45d",
444164742Simp	at45d_methods,
445164742Simp	sizeof(struct at45d_softc),
446164742Simp};
447164742Simp
448236496SmariusDRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, NULL, NULL);
449