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: stable/11/sys/dev/flash/at45d.c 346557 2019-04-22 15:04:11Z ian $");
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>
36346557Sian#include <sys/endian.h>
37164742Simp#include <sys/kernel.h>
38164742Simp#include <sys/kthread.h>
39164742Simp#include <sys/lock.h>
40164742Simp#include <sys/mbuf.h>
41164742Simp#include <sys/malloc.h>
42164742Simp#include <sys/module.h>
43164742Simp#include <sys/mutex.h>
44164742Simp#include <geom/geom_disk.h>
45164742Simp
46164742Simp#include <dev/spibus/spi.h>
47164742Simp#include "spibus_if.h"
48164742Simp
49346557Sian#include "opt_platform.h"
50346557Sian
51346557Sian#ifdef FDT
52346557Sian#include <dev/fdt/fdt_common.h>
53346557Sian#include <dev/ofw/ofw_bus_subr.h>
54346557Sian#include <dev/ofw/openfirm.h>
55346557Sian
56346557Sianstatic struct ofw_compat_data compat_data[] = {
57346557Sian	{ "atmel,at45",		1 },
58346557Sian	{ "atmel,dataflash",	1 },
59346557Sian	{ NULL,			0 },
60346557Sian};
61346557Sian#endif
62346557Sian
63346557Sian/* This is the information returned by the MANUFACTURER_ID command. */
64346557Sianstruct at45d_mfg_info {
65346557Sian	uint32_t	jedec_id; /* Mfg ID, DevId1, DevId2, ExtLen */
66346557Sian	uint16_t	ext_id;   /* ExtId1, ExtId2 */
67346557Sian};
68346557Sian
69346557Sian/*
70346557Sian * This is an entry in our table of metadata describing the chips.  We match on
71346557Sian * both jedec id and extended id info returned by the MANUFACTURER_ID command.
72346557Sian */
73236496Smariusstruct at45d_flash_ident
74164742Simp{
75236496Smarius	const char	*name;
76236496Smarius	uint32_t	jedec;
77346557Sian	uint16_t	extid;
78346557Sian	uint16_t	extmask;
79236496Smarius	uint16_t	pagecount;
80236496Smarius	uint16_t	pageoffset;
81236496Smarius	uint16_t	pagesize;
82236496Smarius	uint16_t	pagesize2n;
83164742Simp};
84164742Simp
85236496Smariusstruct at45d_softc
86236496Smarius{
87236496Smarius	struct bio_queue_head	bio_queue;
88236496Smarius	struct mtx		sc_mtx;
89236496Smarius	struct disk		*disk;
90236496Smarius	struct proc		*p;
91236496Smarius	device_t		dev;
92346557Sian	u_int			taskstate;
93236496Smarius	uint16_t		pagecount;
94236496Smarius	uint16_t		pageoffset;
95236496Smarius	uint16_t		pagesize;
96346557Sian	void			*dummybuf;
97236496Smarius};
98236496Smarius
99346557Sian#define	TSTATE_STOPPED	0
100346557Sian#define	TSTATE_STOPPING	1
101346557Sian#define	TSTATE_RUNNING	2
102346557Sian
103236496Smarius#define	AT45D_LOCK(_sc)			mtx_lock(&(_sc)->sc_mtx)
104164742Simp#define	AT45D_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
105236496Smarius#define	AT45D_LOCK_INIT(_sc) \
106164742Simp	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
107164742Simp	    "at45d", MTX_DEF)
108236496Smarius#define	AT45D_LOCK_DESTROY(_sc)		mtx_destroy(&_sc->sc_mtx);
109236496Smarius#define	AT45D_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_OWNED);
110236496Smarius#define	AT45D_ASSERT_UNLOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
111164742Simp
112236496Smarius/* bus entry points */
113236496Smariusstatic device_attach_t at45d_attach;
114236496Smariusstatic device_detach_t at45d_detach;
115236496Smariusstatic device_probe_t at45d_probe;
116164742Simp
117164742Simp/* disk routines */
118236496Smariusstatic int at45d_close(struct disk *dp);
119164742Simpstatic int at45d_open(struct disk *dp);
120346557Sianstatic int at45d_getattr(struct bio *bp);
121164742Simpstatic void at45d_strategy(struct bio *bp);
122164742Simpstatic void at45d_task(void *arg);
123164742Simp
124236496Smarius/* helper routines */
125236496Smariusstatic void at45d_delayed_attach(void *xsc);
126346557Sianstatic int at45d_get_mfg_info(device_t dev, struct at45d_mfg_info *resp);
127236496Smariusstatic int at45d_get_status(device_t dev, uint8_t *status);
128236496Smariusstatic int at45d_wait_ready(device_t dev, uint8_t *status);
129164742Simp
130346557Sian#define	PAGE_TO_BUFFER_TRANSFER		0x53
131346557Sian#define	PAGE_TO_BUFFER_COMPARE		0x60
132236496Smarius#define	PROGRAM_THROUGH_BUFFER		0x82
133236496Smarius#define	MANUFACTURER_ID			0x9f
134236496Smarius#define	STATUS_REGISTER_READ		0xd7
135236496Smarius#define	CONTINUOUS_ARRAY_READ		0xe8
136236496Smarius
137346557Sian#define	STATUS_READY			(1u << 7)
138346557Sian#define	STATUS_CMPFAIL			(1u << 6)
139346557Sian#define	STATUS_PAGE2N			(1u << 0)
140346557Sian
141236496Smarius/*
142346557Sian * Metadata for supported chips.
143346557Sian *
144346557Sian * The jedec id in this table includes the extended id length byte.  A match is
145346557Sian * based on both jedec id and extended id matching.  The chip's extended id (not
146346557Sian * present in most chips) is ANDed with ExtMask and the result is compared to
147346557Sian * ExtId.  If a chip only returns 1 ext id byte it will be in the upper 8 bits
148346557Sian * of ExtId in this table.
149346557Sian *
150236496Smarius * A sectorsize2n != 0 is used to indicate that a device optionally supports
151236496Smarius * 2^N byte pages.  If support for the latter is enabled, the sector offset
152236496Smarius * has to be reduced by one.
153236496Smarius */
154242625Sdimstatic const struct at45d_flash_ident at45d_flash_devices[] = {
155346557Sian	/* Part Name    Jedec ID    ExtId   ExtMask PgCnt Offs PgSz PgSz2n */
156346557Sian	{ "AT45DB011B", 0x1f220000, 0x0000, 0x0000,   512,  9,  264,  256 },
157346557Sian	{ "AT45DB021B", 0x1f230000, 0x0000, 0x0000,  1024,  9,  264,  256 },
158346557Sian	{ "AT45DB041x", 0x1f240000, 0x0000, 0x0000,  2028,  9,  264,  256 },
159346557Sian	{ "AT45DB081B", 0x1f250000, 0x0000, 0x0000,  4096,  9,  264,  256 },
160346557Sian	{ "AT45DB161x", 0x1f260000, 0x0000, 0x0000,  4096, 10,  528,  512 },
161346557Sian	{ "AT45DB321x", 0x1f270000, 0x0000, 0x0000,  8192, 10,  528,    0 },
162346557Sian	{ "AT45DB321x", 0x1f270100, 0x0000, 0x0000,  8192, 10,  528,  512 },
163346557Sian	{ "AT45DB641E", 0x1f280001, 0x0000, 0xff00, 32768,  9,  264,  256 },
164346557Sian	{ "AT45DB642x", 0x1f280000, 0x0000, 0x0000,  8192, 11, 1056, 1024 },
165236496Smarius};
166236496Smarius
167236496Smariusstatic int
168236496Smariusat45d_get_status(device_t dev, uint8_t *status)
169164742Simp{
170236496Smarius	uint8_t rxBuf[8], txBuf[8];
171298148Sadrian	struct spi_command cmd;
172164742Simp	int err;
173164742Simp
174164742Simp	memset(&cmd, 0, sizeof(cmd));
175164742Simp	memset(txBuf, 0, sizeof(txBuf));
176164742Simp	memset(rxBuf, 0, sizeof(rxBuf));
177164742Simp
178164742Simp	txBuf[0] = STATUS_REGISTER_READ;
179164742Simp	cmd.tx_cmd = txBuf;
180164742Simp	cmd.rx_cmd = rxBuf;
181236496Smarius	cmd.rx_cmd_sz = cmd.tx_cmd_sz = 2;
182164742Simp	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
183236496Smarius	*status = rxBuf[1];
184236496Smarius	return (err);
185164742Simp}
186164742Simp
187164742Simpstatic int
188346557Sianat45d_get_mfg_info(device_t dev, struct at45d_mfg_info *resp)
189164742Simp{
190236496Smarius	uint8_t rxBuf[8], txBuf[8];
191298148Sadrian	struct spi_command cmd;
192164742Simp	int err;
193164742Simp
194164742Simp	memset(&cmd, 0, sizeof(cmd));
195164742Simp	memset(txBuf, 0, sizeof(txBuf));
196164742Simp	memset(rxBuf, 0, sizeof(rxBuf));
197164742Simp
198164742Simp	txBuf[0] = MANUFACTURER_ID;
199164742Simp	cmd.tx_cmd = &txBuf;
200164742Simp	cmd.rx_cmd = &rxBuf;
201346557Sian	cmd.tx_cmd_sz = cmd.rx_cmd_sz = 7;
202164742Simp	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
203164742Simp	if (err)
204164742Simp		return (err);
205346557Sian
206346557Sian	resp->jedec_id = be32dec(rxBuf + 1);
207346557Sian	resp->ext_id   = be16dec(rxBuf + 5);
208346557Sian
209164742Simp	return (0);
210164742Simp}
211164742Simp
212164742Simpstatic int
213236496Smariusat45d_wait_ready(device_t dev, uint8_t *status)
214236496Smarius{
215236496Smarius	struct timeval now, tout;
216236496Smarius	int err;
217236496Smarius
218236496Smarius	getmicrouptime(&tout);
219236496Smarius	tout.tv_sec += 3;
220236496Smarius	do {
221236496Smarius		getmicrouptime(&now);
222236496Smarius		if (now.tv_sec > tout.tv_sec)
223236496Smarius			err = ETIMEDOUT;
224236496Smarius		else
225236496Smarius			err = at45d_get_status(dev, status);
226346557Sian	} while (err == 0 && !(*status & STATUS_READY));
227236496Smarius	return (err);
228236496Smarius}
229236496Smarius
230236496Smariusstatic int
231164742Simpat45d_probe(device_t dev)
232164742Simp{
233346557Sian	int rv;
234236496Smarius
235346557Sian#ifdef FDT
236346557Sian	if (!ofw_bus_status_okay(dev))
237346557Sian		return (ENXIO);
238346557Sian
239346557Sian	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
240346557Sian		return (ENXIO);
241346557Sian
242346557Sian	rv = BUS_PROBE_DEFAULT;
243346557Sian#else
244346557Sian	rv = BUS_PROBE_NOWILDCARD;
245346557Sian#endif
246346557Sian
247236496Smarius	device_set_desc(dev, "AT45D Flash Family");
248346557Sian	return (rv);
249164742Simp}
250164742Simp
251164742Simpstatic int
252164742Simpat45d_attach(device_t dev)
253164742Simp{
254164742Simp	struct at45d_softc *sc;
255164742Simp
256164742Simp	sc = device_get_softc(dev);
257164742Simp	sc->dev = dev;
258164742Simp	AT45D_LOCK_INIT(sc);
259164742Simp
260346557Sian	config_intrhook_oneshot(at45d_delayed_attach, sc);
261164742Simp	return (0);
262164742Simp}
263164742Simp
264164742Simpstatic int
265164742Simpat45d_detach(device_t dev)
266164742Simp{
267346557Sian	struct at45d_softc *sc;
268346557Sian	int err;
269236496Smarius
270346557Sian	sc = device_get_softc(dev);
271346557Sian	err = 0;
272346557Sian
273346557Sian	AT45D_LOCK(sc);
274346557Sian	if (sc->taskstate == TSTATE_RUNNING) {
275346557Sian		sc->taskstate = TSTATE_STOPPING;
276346557Sian		wakeup(sc);
277346557Sian		while (err == 0 && sc->taskstate != TSTATE_STOPPED) {
278346557Sian			err = msleep(sc, &sc->sc_mtx, 0, "at45dt", hz * 3);
279346557Sian			if (err != 0) {
280346557Sian				sc->taskstate = TSTATE_RUNNING;
281346557Sian				device_printf(sc->dev,
282346557Sian				    "Failed to stop queue task\n");
283346557Sian			}
284346557Sian		}
285346557Sian	}
286346557Sian	AT45D_UNLOCK(sc);
287346557Sian
288346557Sian	if (err == 0 && sc->taskstate == TSTATE_STOPPED) {
289346557Sian		if (sc->disk) {
290346557Sian			disk_destroy(sc->disk);
291346557Sian			bioq_flush(&sc->bio_queue, NULL, ENXIO);
292346557Sian			free(sc->dummybuf, M_DEVBUF);
293346557Sian		}
294346557Sian		AT45D_LOCK_DESTROY(sc);
295346557Sian	}
296346557Sian	return (err);
297164742Simp}
298164742Simp
299164742Simpstatic void
300164742Simpat45d_delayed_attach(void *xsc)
301164742Simp{
302236496Smarius	struct at45d_softc *sc;
303346557Sian	struct at45d_mfg_info mfginfo;
304236496Smarius	const struct at45d_flash_ident *ident;
305236496Smarius	u_int i;
306346557Sian	int sectorsize;
307236496Smarius	uint32_t jedec;
308236496Smarius	uint16_t pagesize;
309346557Sian	uint8_t status;
310164742Simp
311236496Smarius	sc = xsc;
312236496Smarius	ident = NULL;
313236496Smarius	jedec = 0;
314164742Simp
315346557Sian	if (at45d_wait_ready(sc->dev, &status) != 0) {
316236496Smarius		device_printf(sc->dev, "Error waiting for device-ready.\n");
317346557Sian		return;
318346557Sian	}
319346557Sian	if (at45d_get_mfg_info(sc->dev, &mfginfo) != 0) {
320236496Smarius		device_printf(sc->dev, "Failed to get ID.\n");
321346557Sian		return;
322346557Sian	}
323346557Sian	for (i = 0; i < nitems(at45d_flash_devices); i++) {
324346557Sian		ident = &at45d_flash_devices[i];
325346557Sian		if (mfginfo.jedec_id == ident->jedec &&
326346557Sian		    (mfginfo.ext_id & ident->extmask) == ident->extid) {
327346557Sian			break;
328236496Smarius		}
329236496Smarius	}
330346557Sian	if (i == nitems(at45d_flash_devices)) {
331236496Smarius		device_printf(sc->dev, "JEDEC 0x%x not in list.\n", jedec);
332346557Sian		return;
333346557Sian	}
334236496Smarius
335346557Sian	sc->pagecount = ident->pagecount;
336346557Sian	sc->pageoffset = ident->pageoffset;
337346557Sian	if (ident->pagesize2n != 0 && (status & STATUS_PAGE2N)) {
338346557Sian		sc->pageoffset -= 1;
339346557Sian		pagesize = ident->pagesize2n;
340346557Sian	} else
341346557Sian		pagesize = ident->pagesize;
342346557Sian	sc->pagesize = pagesize;
343346557Sian
344346557Sian	/*
345346557Sian	 * By default we set up a disk with a sector size that matches the
346346557Sian	 * device page size.  If there is a device hint or fdt property
347346557Sian	 * requesting a different size, use that, as long as it is a multiple of
348346557Sian	 * the device page size).
349346557Sian	 */
350346557Sian	sectorsize = pagesize;
351346557Sian#ifdef FDT
352346557Sian	{
353346557Sian		pcell_t size;
354346557Sian		if (OF_getencprop(ofw_bus_get_node(sc->dev),
355346557Sian		    "freebsd,sectorsize", &size, sizeof(size)) > 0)
356346557Sian			sectorsize = size;
357236496Smarius	}
358346557Sian#endif
359346557Sian	resource_int_value(device_get_name(sc->dev), device_get_unit(sc->dev),
360346557Sian	    "sectorsize", &sectorsize);
361236496Smarius
362346557Sian	if ((sectorsize % pagesize) != 0) {
363346557Sian		device_printf(sc->dev, "Invalid sectorsize %d, "
364346557Sian		    "must be a multiple of %d\n", sectorsize, pagesize);
365346557Sian		return;
366346557Sian	}
367346557Sian
368346557Sian	sc->dummybuf = malloc(pagesize, M_DEVBUF, M_WAITOK | M_ZERO);
369346557Sian
370346557Sian	sc->disk = disk_alloc();
371346557Sian	sc->disk->d_open = at45d_open;
372346557Sian	sc->disk->d_close = at45d_close;
373346557Sian	sc->disk->d_strategy = at45d_strategy;
374346557Sian	sc->disk->d_getattr = at45d_getattr;
375346557Sian	sc->disk->d_name = "flash/at45d";
376346557Sian	sc->disk->d_drv1 = sc;
377346557Sian	sc->disk->d_maxsize = DFLTPHYS;
378346557Sian	sc->disk->d_sectorsize = sectorsize;
379346557Sian	sc->disk->d_mediasize = pagesize * ident->pagecount;
380346557Sian	sc->disk->d_unit = device_get_unit(sc->dev);
381346557Sian	disk_create(sc->disk, DISK_VERSION);
382346557Sian	bioq_init(&sc->bio_queue);
383346557Sian	kproc_create(&at45d_task, sc, &sc->p, 0, 0, "task: at45d flash");
384346557Sian	sc->taskstate = TSTATE_RUNNING;
385346557Sian	device_printf(sc->dev,
386346557Sian	    "%s, %d bytes per page, %d pages; %d KBytes; disk sector size %d\n",
387346557Sian	    ident->name, pagesize, ident->pagecount,
388346557Sian	    (pagesize * ident->pagecount) / 1024, sectorsize);
389164742Simp}
390164742Simp
391164742Simpstatic int
392164742Simpat45d_open(struct disk *dp)
393164742Simp{
394236496Smarius
395236496Smarius	return (0);
396164742Simp}
397164742Simp
398164742Simpstatic int
399164742Simpat45d_close(struct disk *dp)
400164742Simp{
401236496Smarius
402236496Smarius	return (0);
403164742Simp}
404164742Simp
405346557Sianstatic int
406346557Sianat45d_getattr(struct bio *bp)
407346557Sian{
408346557Sian	struct at45d_softc *sc;
409346557Sian
410346557Sian	/*
411346557Sian	 * This function exists to support geom_flashmap and fdt_slicer.
412346557Sian	 */
413346557Sian
414346557Sian	if (bp->bio_disk == NULL || bp->bio_disk->d_drv1 == NULL)
415346557Sian		return (ENXIO);
416346557Sian	if (strcmp(bp->bio_attribute, "SPI::device") != 0)
417346557Sian		return (-1);
418346557Sian	sc = bp->bio_disk->d_drv1;
419346557Sian	if (bp->bio_length != sizeof(sc->dev))
420346557Sian		return (EFAULT);
421346557Sian	bcopy(&sc->dev, bp->bio_data, sizeof(sc->dev));
422346557Sian	return (0);
423346557Sian}
424346557Sian
425164742Simpstatic void
426164742Simpat45d_strategy(struct bio *bp)
427164742Simp{
428164742Simp	struct at45d_softc *sc;
429164742Simp
430164742Simp	sc = (struct at45d_softc *)bp->bio_disk->d_drv1;
431164742Simp	AT45D_LOCK(sc);
432164742Simp	bioq_disksort(&sc->bio_queue, bp);
433164742Simp	wakeup(sc);
434164742Simp	AT45D_UNLOCK(sc);
435164742Simp}
436164742Simp
437164742Simpstatic void
438164742Simpat45d_task(void *arg)
439164742Simp{
440236496Smarius	uint8_t rxBuf[8], txBuf[8];
441236496Smarius	struct at45d_softc *sc;
442164742Simp	struct bio *bp;
443298148Sadrian	struct spi_command cmd;
444164742Simp	device_t dev, pdev;
445236496Smarius	caddr_t buf;
446236496Smarius	u_long len, resid;
447236496Smarius	u_int addr, berr, err, offset, page;
448236496Smarius	uint8_t status;
449164742Simp
450236496Smarius	sc = (struct at45d_softc*)arg;
451236496Smarius	dev = sc->dev;
452236496Smarius	pdev = device_get_parent(dev);
453236496Smarius	memset(&cmd, 0, sizeof(cmd));
454236496Smarius	memset(txBuf, 0, sizeof(txBuf));
455236496Smarius	memset(rxBuf, 0, sizeof(rxBuf));
456236496Smarius	cmd.tx_cmd = txBuf;
457236496Smarius	cmd.rx_cmd = rxBuf;
458236496Smarius
459164742Simp	for (;;) {
460164742Simp		AT45D_LOCK(sc);
461164742Simp		do {
462346557Sian			if (sc->taskstate == TSTATE_STOPPING) {
463346557Sian				sc->taskstate = TSTATE_STOPPED;
464346557Sian				AT45D_UNLOCK(sc);
465346557Sian				wakeup(sc);
466346557Sian				kproc_exit(0);
467346557Sian			}
468236496Smarius			bp = bioq_takefirst(&sc->bio_queue);
469164742Simp			if (bp == NULL)
470346557Sian				msleep(sc, &sc->sc_mtx, PRIBIO, "at45dq", 0);
471164742Simp		} while (bp == NULL);
472164742Simp		AT45D_UNLOCK(sc);
473236496Smarius
474236496Smarius		berr = 0;
475236496Smarius		buf = bp->bio_data;
476236496Smarius		len = resid = bp->bio_bcount;
477236496Smarius		page = bp->bio_offset / sc->pagesize;
478236496Smarius		offset = bp->bio_offset % sc->pagesize;
479236496Smarius
480236496Smarius		switch (bp->bio_cmd) {
481236496Smarius		case BIO_READ:
482236496Smarius			txBuf[0] = CONTINUOUS_ARRAY_READ;
483236496Smarius			cmd.tx_cmd_sz = cmd.rx_cmd_sz = 8;
484346557Sian			cmd.tx_data = sc->dummybuf;
485346557Sian			cmd.rx_data = buf;
486236496Smarius			break;
487236496Smarius		case BIO_WRITE:
488236496Smarius			cmd.tx_cmd_sz = cmd.rx_cmd_sz = 4;
489346557Sian			cmd.tx_data = buf;
490346557Sian			cmd.rx_data = sc->dummybuf;
491236496Smarius			if (resid + offset > sc->pagesize)
492236496Smarius				len = sc->pagesize - offset;
493236496Smarius			break;
494236496Smarius		default:
495236496Smarius			berr = EINVAL;
496236496Smarius			goto out;
497236496Smarius		}
498236496Smarius
499236496Smarius		/*
500236496Smarius		 * NB: for BIO_READ, this loop is only traversed once.
501236496Smarius		 */
502236496Smarius		while (resid > 0) {
503236496Smarius			if (page > sc->pagecount) {
504236496Smarius				berr = EINVAL;
505236496Smarius				goto out;
506236496Smarius			}
507236496Smarius			addr = page << sc->pageoffset;
508236496Smarius			if (bp->bio_cmd == BIO_WRITE) {
509346557Sian				/*
510346557Sian				 * If writing less than a full page, transfer
511346557Sian				 * the existing page to the buffer, so that our
512346557Sian				 * PROGRAM_THROUGH_BUFFER below will preserve
513346557Sian				 * the parts of the page we're not writing.
514346557Sian				 */
515236496Smarius				if (len != sc->pagesize) {
516346557Sian					txBuf[0] = PAGE_TO_BUFFER_TRANSFER;
517236496Smarius					txBuf[1] = ((addr >> 16) & 0xff);
518236496Smarius					txBuf[2] = ((addr >> 8) & 0xff);
519236496Smarius					txBuf[3] = 0;
520236496Smarius					cmd.tx_data_sz = cmd.rx_data_sz = 0;
521346557Sian					err = SPIBUS_TRANSFER(pdev, dev, &cmd);
522236496Smarius					if (err == 0)
523236496Smarius						err = at45d_wait_ready(dev,
524236496Smarius						    &status);
525236496Smarius					if (err != 0) {
526236496Smarius						berr = EIO;
527236496Smarius						goto out;
528236496Smarius					}
529236496Smarius				}
530164742Simp				txBuf[0] = PROGRAM_THROUGH_BUFFER;
531164742Simp			}
532236496Smarius
533236496Smarius			addr += offset;
534236496Smarius			txBuf[1] = ((addr >> 16) & 0xff);
535236496Smarius			txBuf[2] = ((addr >> 8) & 0xff);
536236496Smarius			txBuf[3] = (addr & 0xff);
537236496Smarius			cmd.tx_data_sz = cmd.rx_data_sz = len;
538164742Simp			err = SPIBUS_TRANSFER(pdev, dev, &cmd);
539236496Smarius			if (err == 0 && bp->bio_cmd != BIO_READ)
540236496Smarius				err = at45d_wait_ready(dev, &status);
541236496Smarius			if (err != 0) {
542236496Smarius				berr = EIO;
543236496Smarius				goto out;
544236496Smarius			}
545236496Smarius			if (bp->bio_cmd == BIO_WRITE) {
546236496Smarius				addr = page << sc->pageoffset;
547346557Sian				txBuf[0] = PAGE_TO_BUFFER_COMPARE;
548236496Smarius				txBuf[1] = ((addr >> 16) & 0xff);
549236496Smarius				txBuf[2] = ((addr >> 8) & 0xff);
550236496Smarius				txBuf[3] = 0;
551236496Smarius				cmd.tx_data_sz = cmd.rx_data_sz = 0;
552236496Smarius				err = SPIBUS_TRANSFER(pdev, dev, &cmd);
553236496Smarius				if (err == 0)
554236496Smarius					err = at45d_wait_ready(dev, &status);
555346557Sian				if (err != 0 || (status & STATUS_CMPFAIL)) {
556236496Smarius					device_printf(dev, "comparing page "
557346557Sian					    "%d failed (status=0x%x)\n", page,
558236496Smarius					    status);
559236496Smarius					berr = EIO;
560236496Smarius					goto out;
561236496Smarius				}
562236496Smarius			}
563236496Smarius			page++;
564236496Smarius			buf += len;
565236496Smarius			offset = 0;
566236496Smarius			resid -= len;
567236496Smarius			if (resid > sc->pagesize)
568236496Smarius				len = sc->pagesize;
569236496Smarius			else
570236496Smarius				len = resid;
571346557Sian			if (bp->bio_cmd == BIO_READ)
572346557Sian				cmd.rx_data = buf;
573346557Sian			else
574346557Sian				cmd.tx_data = buf;
575164742Simp		}
576236496Smarius out:
577236496Smarius		if (berr != 0) {
578236496Smarius			bp->bio_flags |= BIO_ERROR;
579236496Smarius			bp->bio_error = berr;
580236496Smarius		}
581236496Smarius		bp->bio_resid = resid;
582164742Simp		biodone(bp);
583164742Simp	}
584164742Simp}
585164742Simp
586164742Simpstatic devclass_t at45d_devclass;
587164742Simp
588164742Simpstatic device_method_t at45d_methods[] = {
589164742Simp	/* Device interface */
590164742Simp	DEVMETHOD(device_probe,		at45d_probe),
591164742Simp	DEVMETHOD(device_attach,	at45d_attach),
592164742Simp	DEVMETHOD(device_detach,	at45d_detach),
593164742Simp
594236496Smarius	DEVMETHOD_END
595164742Simp};
596164742Simp
597164742Simpstatic driver_t at45d_driver = {
598164742Simp	"at45d",
599164742Simp	at45d_methods,
600164742Simp	sizeof(struct at45d_softc),
601164742Simp};
602164742Simp
603236496SmariusDRIVER_MODULE(at45d, spibus, at45d_driver, at45d_devclass, NULL, NULL);
604331506SianMODULE_DEPEND(at45d, spibus, 1, 1, 1);
605346557Sian#ifdef FDT
606346557SianMODULE_DEPEND(at45d, fdt_slicer, 1, 1, 1);
607346557Sian#endif
608346557Sian
609