From 1d6432fe10c3e724e307dd7137cd293a0edcae80 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Sun, 8 Jan 2006 13:34:22 -0800 Subject: [PATCH] spi: mtd dataflash driver This is a conversion of the AT91rm9200 DataFlash MTD driver to use the lightweight SPI framework, and no longer be AT91-specific. It compiles down to less than 3KBytes on ARM. The driver allows board-specific init code to provide platform_data with the relevant MTD partitioning information, and hotplugs. This version has been lightly tested. Its parent at91_dataflash driver has been pretty well banged on, although kernel.org JFFS2 dataflash support was acting broken the last time I tried it. Signed-off-by: David Brownell Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/mtd/devices/mtd_dataflash.c | 623 ++++++++++++++++++++++++++++++++++++ 1 file changed, 623 insertions(+) create mode 100644 drivers/mtd/devices/mtd_dataflash.c (limited to 'drivers/mtd/devices/mtd_dataflash.c') diff --git a/drivers/mtd/devices/mtd_dataflash.c b/drivers/mtd/devices/mtd_dataflash.c new file mode 100644 index 00000000000..a39b3b6b266 --- /dev/null +++ b/drivers/mtd/devices/mtd_dataflash.c @@ -0,0 +1,623 @@ +/* + * Atmel AT45xxx DataFlash MTD driver for lightweight SPI framework + * + * Largely derived from at91_dataflash.c: + * Copyright (C) 2003-2005 SAN People (Pty) Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +/* + * DataFlash is a kind of SPI flash. Most AT45 chips have two buffers in + * each chip, which may be used for double buffered I/O; but this driver + * doesn't (yet) use these for any kind of i/o overlap or prefetching. + * + * Sometimes DataFlash is packaged in MMC-format cards, although the + * MMC stack can't use SPI (yet), or distinguish between MMC and DataFlash + * protocols during enumeration. + */ + +#define CONFIG_DATAFLASH_WRITE_VERIFY + +/* reads can bypass the buffers */ +#define OP_READ_CONTINUOUS 0xE8 +#define OP_READ_PAGE 0xD2 + +/* group B requests can run even while status reports "busy" */ +#define OP_READ_STATUS 0xD7 /* group B */ + +/* move data between host and buffer */ +#define OP_READ_BUFFER1 0xD4 /* group B */ +#define OP_READ_BUFFER2 0xD6 /* group B */ +#define OP_WRITE_BUFFER1 0x84 /* group B */ +#define OP_WRITE_BUFFER2 0x87 /* group B */ + +/* erasing flash */ +#define OP_ERASE_PAGE 0x81 +#define OP_ERASE_BLOCK 0x50 + +/* move data between buffer and flash */ +#define OP_TRANSFER_BUF1 0x53 +#define OP_TRANSFER_BUF2 0x55 +#define OP_MREAD_BUFFER1 0xD4 +#define OP_MREAD_BUFFER2 0xD6 +#define OP_MWERASE_BUFFER1 0x83 +#define OP_MWERASE_BUFFER2 0x86 +#define OP_MWRITE_BUFFER1 0x88 /* sector must be pre-erased */ +#define OP_MWRITE_BUFFER2 0x89 /* sector must be pre-erased */ + +/* write to buffer, then write-erase to flash */ +#define OP_PROGRAM_VIA_BUF1 0x82 +#define OP_PROGRAM_VIA_BUF2 0x85 + +/* compare buffer to flash */ +#define OP_COMPARE_BUF1 0x60 +#define OP_COMPARE_BUF2 0x61 + +/* read flash to buffer, then write-erase to flash */ +#define OP_REWRITE_VIA_BUF1 0x58 +#define OP_REWRITE_VIA_BUF2 0x59 + +/* newer chips report JEDEC manufacturer and device IDs; chip + * serial number and OTP bits; and per-sector writeprotect. + */ +#define OP_READ_ID 0x9F +#define OP_READ_SECURITY 0x77 +#define OP_WRITE_SECURITY 0x9A /* OTP bits */ + + +struct dataflash { + u8 command[4]; + char name[24]; + + unsigned partitioned:1; + + unsigned short page_offset; /* offset in flash address */ + unsigned int page_size; /* of bytes per page */ + + struct semaphore lock; + struct spi_device *spi; + + struct mtd_info mtd; +}; + +#ifdef CONFIG_MTD_PARTITIONS +#define mtd_has_partitions() (1) +#else +#define mtd_has_partitions() (0) +#endif + +/* ......................................................................... */ + +/* + * Return the status of the DataFlash device. + */ +static inline int dataflash_status(struct spi_device *spi) +{ + /* NOTE: at45db321c over 25 MHz wants to write + * a dummy byte after the opcode... + */ + return spi_w8r8(spi, OP_READ_STATUS); +} + +/* + * Poll the DataFlash device until it is READY. + * This usually takes 5-20 msec or so; more for sector erase. + */ +static int dataflash_waitready(struct spi_device *spi) +{ + int status; + + for (;;) { + status = dataflash_status(spi); + if (status < 0) { + DEBUG(MTD_DEBUG_LEVEL1, "%s: status %d?\n", + spi->dev.bus_id, status); + status = 0; + } + + if (status & (1 << 7)) /* RDY/nBSY */ + return status; + + msleep(3); + } +} + +/* ......................................................................... */ + +/* + * Erase pages of flash. + */ +static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct dataflash *priv = (struct dataflash *)mtd->priv; + struct spi_device *spi = priv->spi; + struct spi_transfer x[1] = { { .tx_dma = 0, }, }; + struct spi_message msg; + unsigned blocksize = priv->page_size << 3; + u8 *command; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: erase addr=0x%x len 0x%x\n", + spi->dev.bus_id, + instr->addr, instr->len); + + /* Sanity checks */ + if ((instr->addr + instr->len) > mtd->size + || (instr->len % priv->page_size) != 0 + || (instr->addr % priv->page_size) != 0) + return -EINVAL; + + x[0].tx_buf = command = priv->command; + x[0].len = 4; + msg.transfers = x; + msg.n_transfer = 1; + + down(&priv->lock); + while (instr->len > 0) { + unsigned int pageaddr; + int status; + int do_block; + + /* Calculate flash page address; use block erase (for speed) if + * we're at a block boundary and need to erase the whole block. + */ + pageaddr = instr->addr / priv->page_size; + do_block = (pageaddr & 0x7) == 0 && instr->len <= blocksize; + pageaddr = pageaddr << priv->page_offset; + + command[0] = do_block ? OP_ERASE_BLOCK : OP_ERASE_PAGE; + command[1] = (u8)(pageaddr >> 16); + command[2] = (u8)(pageaddr >> 8); + command[3] = 0; + + DEBUG(MTD_DEBUG_LEVEL3, "ERASE %s: (%x) %x %x %x [%i]\n", + do_block ? "block" : "page", + command[0], command[1], command[2], command[3], + pageaddr); + + status = spi_sync(spi, &msg); + (void) dataflash_waitready(spi); + + if (status < 0) { + printk(KERN_ERR "%s: erase %x, err %d\n", + spi->dev.bus_id, pageaddr, status); + /* REVISIT: can retry instr->retries times; or + * giveup and instr->fail_addr = instr->addr; + */ + continue; + } + + if (do_block) { + instr->addr += blocksize; + instr->len -= blocksize; + } else { + instr->addr += priv->page_size; + instr->len -= priv->page_size; + } + } + up(&priv->lock); + + /* Inform MTD subsystem that erase is complete */ + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + return 0; +} + +/* + * Read from the DataFlash device. + * from : Start offset in flash device + * len : Amount to read + * retlen : About of data actually read + * buf : Buffer containing the data + */ +static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct dataflash *priv = (struct dataflash *)mtd->priv; + struct spi_transfer x[2] = { { .tx_dma = 0, }, }; + struct spi_message msg; + unsigned int addr; + u8 *command; + int status; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: read 0x%x..0x%x\n", + priv->spi->dev.bus_id, (unsigned)from, (unsigned)(from + len)); + + *retlen = 0; + + /* Sanity checks */ + if (!len) + return 0; + if (from + len > mtd->size) + return -EINVAL; + + /* Calculate flash page/byte address */ + addr = (((unsigned)from / priv->page_size) << priv->page_offset) + + ((unsigned)from % priv->page_size); + + command = priv->command; + + DEBUG(MTD_DEBUG_LEVEL3, "READ: (%x) %x %x %x\n", + command[0], command[1], command[2], command[3]); + + x[0].tx_buf = command; + x[0].len = 8; + x[1].rx_buf = buf; + x[1].len = len; + msg.transfers = x; + msg.n_transfer = 2; + + down(&priv->lock); + + /* Continuous read, max clock = f(car) which may be less than + * the peak rate available. Some chips support commands with + * fewer "don't care" bytes. Both buffers stay unchanged. + */ + command[0] = OP_READ_CONTINUOUS; + command[1] = (u8)(addr >> 16); + command[2] = (u8)(addr >> 8); + command[3] = (u8)(addr >> 0); + /* plus 4 "don't care" bytes */ + + status = spi_sync(priv->spi, &msg); + up(&priv->lock); + + if (status >= 0) { + *retlen = msg.actual_length - 8; + status = 0; + } else + DEBUG(MTD_DEBUG_LEVEL1, "%s: read %x..%x --> %d\n", + priv->spi->dev.bus_id, + (unsigned)from, (unsigned)(from + len), + status); + return status; +} + +/* + * Write to the DataFlash device. + * to : Start offset in flash device + * len : Amount to write + * retlen : Amount of data actually written + * buf : Buffer containing the data + */ +static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t * retlen, const u_char * buf) +{ + struct dataflash *priv = (struct dataflash *)mtd->priv; + struct spi_device *spi = priv->spi; + struct spi_transfer x[2] = { { .tx_dma = 0, }, }; + struct spi_message msg; + unsigned int pageaddr, addr, offset, writelen; + size_t remaining = len; + u_char *writebuf = (u_char *) buf; + int status = -EINVAL; + u8 *command; + + DEBUG(MTD_DEBUG_LEVEL2, "%s: write 0x%x..0x%x\n", + spi->dev.bus_id, (unsigned)to, (unsigned)(to + len)); + + *retlen = 0; + + /* Sanity checks */ + if (!len) + return 0; + if ((to + len) > mtd->size) + return -EINVAL; + + x[0].tx_buf = command = priv->command; + x[0].len = 4; + msg.transfers = x; + + pageaddr = ((unsigned)to / priv->page_size); + offset = ((unsigned)to % priv->page_size); + if (offset + len > priv->page_size) + writelen = priv->page_size - offset; + else + writelen = len; + + down(&priv->lock); + while (remaining > 0) { + DEBUG(MTD_DEBUG_LEVEL3, "write @ %i:%i len=%i\n", + pageaddr, offset, writelen); + + /* REVISIT: + * (a) each page in a sector must be rewritten at least + * once every 10K sibling erase/program operations. + * (b) for pages that are already erased, we could + * use WRITE+MWRITE not PROGRAM for ~30% speedup. + * (c) WRITE to buffer could be done while waiting for + * a previous MWRITE/MWERASE to complete ... + * (d) error handling here seems to be mostly missing. + * + * Two persistent bits per page, plus a per-sector counter, + * could support (a) and (b) ... we might consider using + * the second half of sector zero, which is just one block, + * to track that state. (On AT91, that sector should also + * support boot-from-DataFlash.) + */ + + addr = pageaddr << priv->page_offset; + + /* (1) Maybe transfer partial page to Buffer1 */ + if (writelen != priv->page_size) { + command[0] = OP_TRANSFER_BUF1; + command[1] = (addr & 0x00FF0000) >> 16; + command[2] = (addr & 0x0000FF00) >> 8; + command[3] = 0; + + DEBUG(MTD_DEBUG_LEVEL3, "TRANSFER: (%x) %x %x %x\n", + command[0], command[1], command[2], command[3]); + + msg.n_transfer = 1; + status = spi_sync(spi, &msg); + if (status < 0) + DEBUG(MTD_DEBUG_LEVEL1, "%s: xfer %u -> %d \n", + spi->dev.bus_id, addr, status); + + (void) dataflash_waitready(priv->spi); + } + + /* (2) Program full page via Buffer1 */ + addr += offset; + command[0] = OP_PROGRAM_VIA_BUF1; + command[1] = (addr & 0x00FF0000) >> 16; + command[2] = (addr & 0x0000FF00) >> 8; + command[3] = (addr & 0x000000FF); + + DEBUG(MTD_DEBUG_LEVEL3, "PROGRAM: (%x) %x %x %x\n", + command[0], command[1], command[2], command[3]); + + x[1].tx_buf = writebuf; + x[1].len = writelen; + msg.n_transfer = 2; + status = spi_sync(spi, &msg); + if (status < 0) + DEBUG(MTD_DEBUG_LEVEL1, "%s: pgm %u/%u -> %d \n", + spi->dev.bus_id, addr, writelen, status); + + (void) dataflash_waitready(priv->spi); + +#ifdef CONFIG_DATAFLASH_WRITE_VERIFY + + /* (3) Compare to Buffer1 */ + addr = pageaddr << priv->page_offset; + command[0] = OP_COMPARE_BUF1; + command[1] = (addr & 0x00FF0000) >> 16; + command[2] = (addr & 0x0000FF00) >> 8; + command[3] = 0; + + DEBUG(MTD_DEBUG_LEVEL3, "COMPARE: (%x) %x %x %x\n", + command[0], command[1], command[2], command[3]); + + msg.n_transfer = 1; + status = spi_sync(spi, &msg); + if (status < 0) + DEBUG(MTD_DEBUG_LEVEL1, "%s: compare %u -> %d \n", + spi->dev.bus_id, addr, status); + + status = dataflash_waitready(priv->spi); + + /* Check result of the compare operation */ + if ((status & (1 << 6)) == 1) { + printk(KERN_ERR "%s: compare page %u, err %d\n", + spi->dev.bus_id, pageaddr, status); + remaining = 0; + status = -EIO; + break; + } else + status = 0; + +#endif /* CONFIG_DATAFLASH_WRITE_VERIFY */ + + remaining = remaining - writelen; + pageaddr++; + offset = 0; + writebuf += writelen; + *retlen += writelen; + + if (remaining > priv->page_size) + writelen = priv->page_size; + else + writelen = remaining; + } + up(&priv->lock); + + return status; +} + +/* ......................................................................... */ + +/* + * Register DataFlash device with MTD subsystem. + */ +static int __devinit +add_dataflash(struct spi_device *spi, char *name, + int nr_pages, int pagesize, int pageoffset) +{ + struct dataflash *priv; + struct mtd_info *device; + struct flash_platform_data *pdata = spi->dev.platform_data; + + priv = (struct dataflash *) kzalloc(sizeof *priv, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + init_MUTEX(&priv->lock); + priv->spi = spi; + priv->page_size = pagesize; + priv->page_offset = pageoffset; + + /* name must be usable with cmdlinepart */ + sprintf(priv->name, "spi%d.%d-%s", + spi->master->bus_num, spi->chip_select, + name); + + device = &priv->mtd; + device->name = (pdata && pdata->name) ? pdata->name : priv->name; + device->size = nr_pages * pagesize; + device->erasesize = pagesize; + device->owner = THIS_MODULE; + device->type = MTD_DATAFLASH; + device->flags = MTD_CAP_NORFLASH; + device->erase = dataflash_erase; + device->read = dataflash_read; + device->write = dataflash_write; + device->priv = priv; + + dev_info(&spi->dev, "%s (%d KBytes)\n", name, device->size/1024); + dev_set_drvdata(&spi->dev, priv); + + if (mtd_has_partitions()) { + struct mtd_partition *parts; + int nr_parts = 0; + +#ifdef CONFIG_MTD_CMDLINE_PARTS + static const char *part_probes[] = { "cmdlinepart", NULL, }; + + nr_parts = parse_mtd_partitions(device, part_probes, &parts, 0); +#endif + + if (nr_parts <= 0 && pdata && pdata->parts) { + parts = pdata->parts; + nr_parts = pdata->nr_parts; + } + + if (nr_parts > 0) { + priv->partitioned = 1; + return add_mtd_partitions(device, parts, nr_parts); + } + } else if (pdata->nr_parts) + dev_warn(&spi->dev, "ignoring %d default partitions on %s\n", + pdata->nr_parts, device->name); + + return add_mtd_device(device) == 1 ? -ENODEV : 0; +} + +/* + * Detect and initialize DataFlash device: + * + * Device Density ID code #Pages PageSize Offset + * AT45DB011B 1Mbit (128K) xx0011xx (0x0c) 512 264 9 + * AT45DB021B 2Mbit (256K) xx0101xx (0x14) 1025 264 9 + * AT45DB041B 4Mbit (512K) xx0111xx (0x1c) 2048 264 9 + * AT45DB081B 8Mbit (1M) xx1001xx (0x24) 4096 264 9 + * AT45DB0161B 16Mbit (2M) xx1011xx (0x2c) 4096 528 10 + * AT45DB0321B 32Mbit (4M) xx1101xx (0x34) 8192 528 10 + * AT45DB0642 64Mbit (8M) xx111xxx (0x3c) 8192 1056 11 + * AT45DB1282 128Mbit (16M) xx0100xx (0x10) 16384 1056 11 + */ +static int __devinit dataflash_probe(struct spi_device *spi) +{ + int status; + + status = dataflash_status(spi); + if (status <= 0 || status == 0xff) { + DEBUG(MTD_DEBUG_LEVEL1, "%s: status error %d\n", + spi->dev.bus_id, status); + if (status == 0xff) + status = -ENODEV; + return status; + } + + /* if there's a device there, assume it's dataflash. + * board setup should have set spi->max_speed_max to + * match f(car) for continuous reads, mode 0 or 3. + */ + switch (status & 0x3c) { + case 0x0c: /* 0 0 1 1 x x */ + status = add_dataflash(spi, "AT45DB011B", 512, 264, 9); + break; + case 0x14: /* 0 1 0 1 x x */ + status = add_dataflash(spi, "AT45DB021B", 1025, 264, 9); + break; + case 0x1c: /* 0 1 1 1 x x */ + status = add_dataflash(spi, "AT45DB041x", 2048, 264, 9); + break; + case 0x24: /* 1 0 0 1 x x */ + status = add_dataflash(spi, "AT45DB081B", 4096, 264, 9); + break; + case 0x2c: /* 1 0 1 1 x x */ + status = add_dataflash(spi, "AT45DB161x", 4096, 528, 10); + break; + case 0x34: /* 1 1 0 1 x x */ + status = add_dataflash(spi, "AT45DB321x", 8192, 528, 10); + break; + case 0x38: /* 1 1 1 x x x */ + case 0x3c: + status = add_dataflash(spi, "AT45DB642x", 8192, 1056, 11); + break; + /* obsolete AT45DB1282 not (yet?) supported */ + default: + DEBUG(MTD_DEBUG_LEVEL1, "%s: unsupported device (%x)\n", + spi->dev.bus_id, status & 0x3c); + status = -ENODEV; + } + + if (status < 0) + DEBUG(MTD_DEBUG_LEVEL1, "%s: add_dataflash --> %d\n", + spi->dev.bus_id, status); + + return status; +} + +static int __devexit dataflash_remove(struct spi_device *spi) +{ + struct dataflash *flash = dev_get_drvdata(&spi->dev); + int status; + + DEBUG(MTD_DEBUG_LEVEL1, "%s: remove\n", spi->dev.bus_id); + + if (mtd_has_partitions() && flash->partitioned) + status = del_mtd_partitions(&flash->mtd); + else + status = del_mtd_device(&flash->mtd); + if (status == 0) + kfree(flash); + return status; +} + +static struct spi_driver dataflash_driver = { + .driver = { + .name = "mtd_dataflash", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + + .probe = dataflash_probe, + .remove = __devexit_p(dataflash_remove), + + /* FIXME: investigate suspend and resume... */ +}; + +static int __init dataflash_init(void) +{ + return spi_register_driver(&dataflash_driver); +} +module_init(dataflash_init); + +static void __exit dataflash_exit(void) +{ + spi_unregister_driver(&dataflash_driver); +} +module_exit(dataflash_exit); + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andrew Victor, David Brownell"); +MODULE_DESCRIPTION("MTD DataFlash driver"); -- cgit v1.2.3-70-g09d2 From 8275c642ccdce09a2146d0a9eb022e3698ee927e Mon Sep 17 00:00:00 2001 From: Vitaly Wool Date: Sun, 8 Jan 2006 13:34:28 -0800 Subject: [PATCH] spi: use linked lists rather than an array This makes the SPI core and its users access transfers in the SPI message structure as linked list not as an array, as discussed on LKML. From: David Brownell Updates including doc, bugfixes to the list code, add spi_message_add_tail(). Plus, initialize things _before_ grabbing the locks in some cases (in case it grows more expensive). This also merges some bitbang updates of mine that didn't yet make it into the mm tree. Signed-off-by: Vitaly Wool Signed-off-by: Dmitry Pervushin Signed-off-by: David Brownell Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/input/touchscreen/ads7846.c | 12 +++-- drivers/mtd/devices/m25p80.c | 50 ++++++++++---------- drivers/mtd/devices/mtd_dataflash.c | 28 ++++++----- drivers/spi/spi.c | 18 +++++--- drivers/spi/spi_bitbang.c | 86 +++++++++++++++++++--------------- include/linux/spi/spi.h | 92 +++++++++++++++++++++++++------------ include/linux/spi/spi_bitbang.h | 7 +++ 7 files changed, 180 insertions(+), 113 deletions(-) (limited to 'drivers/mtd/devices/mtd_dataflash.c') diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c index c741776ef3b..dd8c6a9ffc7 100644 --- a/drivers/input/touchscreen/ads7846.c +++ b/drivers/input/touchscreen/ads7846.c @@ -155,10 +155,13 @@ static int ads7846_read12_ser(struct device *dev, unsigned command) struct ser_req *req = kzalloc(sizeof *req, SLAB_KERNEL); int status; int sample; + int i; if (!req) return -ENOMEM; + INIT_LIST_HEAD(&req->msg.transfers); + /* activate reference, so it has time to settle; */ req->xfer[0].tx_buf = &ref_on; req->xfer[0].len = 1; @@ -192,8 +195,8 @@ static int ads7846_read12_ser(struct device *dev, unsigned command) /* group all the transfers together, so we can't interfere with * reading touchscreen state; disable penirq while sampling */ - req->msg.transfers = req->xfer; - req->msg.n_transfer = 6; + for (i = 0; i < 6; i++) + spi_message_add_tail(&req->xfer[i], &req->msg); disable_irq(spi->irq); status = spi_sync(spi, &req->msg); @@ -398,6 +401,7 @@ static int __devinit ads7846_probe(struct spi_device *spi) struct ads7846 *ts; struct ads7846_platform_data *pdata = spi->dev.platform_data; struct spi_transfer *x; + int i; if (!spi->irq) { dev_dbg(&spi->dev, "no IRQ?\n"); @@ -500,8 +504,8 @@ static int __devinit ads7846_probe(struct spi_device *spi) CS_CHANGE(x[-1]); - ts->msg.transfers = ts->xfer; - ts->msg.n_transfer = x - ts->xfer; + for (i = 0; i < x - ts->xfer; i++) + spi_message_add_tail(&ts->xfer[i], &ts->msg); ts->msg.complete = ads7846_rx; ts->msg.context = ts; diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index 71a072103a7..45108ed8558 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -245,6 +245,21 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len, if (from + len > flash->mtd.size) return -EINVAL; + spi_message_init(&m); + memset(t, 0, (sizeof t)); + + t[0].tx_buf = flash->command; + t[0].len = sizeof(flash->command); + spi_message_add_tail(&t[0], &m); + + t[1].rx_buf = buf; + t[1].len = len; + spi_message_add_tail(&t[1], &m); + + /* Byte count starts at zero. */ + if (retlen) + *retlen = 0; + down(&flash->lock); /* Wait till previous write/erase is done. */ @@ -254,8 +269,6 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len, return 1; } - memset(t, 0, (sizeof t)); - /* NOTE: OPCODE_FAST_READ (if available) is faster... */ /* Set up the write data buffer. */ @@ -264,19 +277,6 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len, flash->command[2] = from >> 8; flash->command[3] = from; - /* Byte count starts at zero. */ - if (retlen) - *retlen = 0; - - t[0].tx_buf = flash->command; - t[0].len = sizeof(flash->command); - - t[1].rx_buf = buf; - t[1].len = len; - - m.transfers = t; - m.n_transfer = 2; - spi_sync(flash->spi, &m); *retlen = m.actual_length - sizeof(flash->command); @@ -313,6 +313,16 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len, if (to + len > flash->mtd.size) return -EINVAL; + spi_message_init(&m); + memset(t, 0, (sizeof t)); + + t[0].tx_buf = flash->command; + t[0].len = sizeof(flash->command); + spi_message_add_tail(&t[0], &m); + + t[1].tx_buf = buf; + spi_message_add_tail(&t[1], &m); + down(&flash->lock); /* Wait until finished previous write command. */ @@ -321,26 +331,17 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len, write_enable(flash); - memset(t, 0, (sizeof t)); - /* Set up the opcode in the write buffer. */ flash->command[0] = OPCODE_PP; flash->command[1] = to >> 16; flash->command[2] = to >> 8; flash->command[3] = to; - t[0].tx_buf = flash->command; - t[0].len = sizeof(flash->command); - - m.transfers = t; - m.n_transfer = 2; - /* what page do we start with? */ page_offset = to % FLASH_PAGESIZE; /* do all the bytes fit onto one page? */ if (page_offset + len <= FLASH_PAGESIZE) { - t[1].tx_buf = buf; t[1].len = len; spi_sync(flash->spi, &m); @@ -352,7 +353,6 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len, /* the size of data remaining on the first page */ page_size = FLASH_PAGESIZE - page_offset; - t[1].tx_buf = buf; t[1].len = page_size; spi_sync(flash->spi, &m); diff --git a/drivers/mtd/devices/mtd_dataflash.c b/drivers/mtd/devices/mtd_dataflash.c index a39b3b6b266..99d3a0320fc 100644 --- a/drivers/mtd/devices/mtd_dataflash.c +++ b/drivers/mtd/devices/mtd_dataflash.c @@ -147,7 +147,7 @@ static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) { struct dataflash *priv = (struct dataflash *)mtd->priv; struct spi_device *spi = priv->spi; - struct spi_transfer x[1] = { { .tx_dma = 0, }, }; + struct spi_transfer x = { .tx_dma = 0, }; struct spi_message msg; unsigned blocksize = priv->page_size << 3; u8 *command; @@ -162,10 +162,11 @@ static int dataflash_erase(struct mtd_info *mtd, struct erase_info *instr) || (instr->addr % priv->page_size) != 0) return -EINVAL; - x[0].tx_buf = command = priv->command; - x[0].len = 4; - msg.transfers = x; - msg.n_transfer = 1; + spi_message_init(&msg); + + x.tx_buf = command = priv->command; + x.len = 4; + spi_message_add_tail(&x, &msg); down(&priv->lock); while (instr->len > 0) { @@ -256,12 +257,15 @@ static int dataflash_read(struct mtd_info *mtd, loff_t from, size_t len, DEBUG(MTD_DEBUG_LEVEL3, "READ: (%x) %x %x %x\n", command[0], command[1], command[2], command[3]); + spi_message_init(&msg); + x[0].tx_buf = command; x[0].len = 8; + spi_message_add_tail(&x[0], &msg); + x[1].rx_buf = buf; x[1].len = len; - msg.transfers = x; - msg.n_transfer = 2; + spi_message_add_tail(&x[1], &msg); down(&priv->lock); @@ -320,9 +324,11 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, if ((to + len) > mtd->size) return -EINVAL; + spi_message_init(&msg); + x[0].tx_buf = command = priv->command; x[0].len = 4; - msg.transfers = x; + spi_message_add_tail(&x[0], &msg); pageaddr = ((unsigned)to / priv->page_size); offset = ((unsigned)to % priv->page_size); @@ -364,7 +370,6 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, DEBUG(MTD_DEBUG_LEVEL3, "TRANSFER: (%x) %x %x %x\n", command[0], command[1], command[2], command[3]); - msg.n_transfer = 1; status = spi_sync(spi, &msg); if (status < 0) DEBUG(MTD_DEBUG_LEVEL1, "%s: xfer %u -> %d \n", @@ -385,14 +390,16 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, x[1].tx_buf = writebuf; x[1].len = writelen; - msg.n_transfer = 2; + spi_message_add_tail(x + 1, &msg); status = spi_sync(spi, &msg); + spi_transfer_del(x + 1); if (status < 0) DEBUG(MTD_DEBUG_LEVEL1, "%s: pgm %u/%u -> %d \n", spi->dev.bus_id, addr, writelen, status); (void) dataflash_waitready(priv->spi); + #ifdef CONFIG_DATAFLASH_WRITE_VERIFY /* (3) Compare to Buffer1 */ @@ -405,7 +412,6 @@ static int dataflash_write(struct mtd_info *mtd, loff_t to, size_t len, DEBUG(MTD_DEBUG_LEVEL3, "COMPARE: (%x) %x %x %x\n", command[0], command[1], command[2], command[3]); - msg.n_transfer = 1; status = spi_sync(spi, &msg); if (status < 0) DEBUG(MTD_DEBUG_LEVEL1, "%s: compare %u -> %d \n", diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 3ecedccdb96..cdb242de901 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -557,6 +557,17 @@ int spi_write_then_read(struct spi_device *spi, if ((n_tx + n_rx) > SPI_BUFSIZ) return -EINVAL; + spi_message_init(&message); + memset(x, 0, sizeof x); + if (n_tx) { + x[0].len = n_tx; + spi_message_add_tail(&x[0], &message); + } + if (n_rx) { + x[1].len = n_rx; + spi_message_add_tail(&x[1], &message); + } + /* ... unless someone else is using the pre-allocated buffer */ if (down_trylock(&lock)) { local_buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); @@ -565,18 +576,11 @@ int spi_write_then_read(struct spi_device *spi, } else local_buf = buf; - memset(x, 0, sizeof x); - memcpy(local_buf, txbuf, n_tx); x[0].tx_buf = local_buf; - x[0].len = n_tx; - x[1].rx_buf = local_buf + n_tx; - x[1].len = n_rx; /* do the i/o */ - message.transfers = x; - message.n_transfer = ARRAY_SIZE(x); status = spi_sync(spi, &message); if (status == 0) { memcpy(rxbuf, x[1].rx_buf, n_rx); diff --git a/drivers/spi/spi_bitbang.c b/drivers/spi/spi_bitbang.c index 44aff198eb9..f037e559326 100644 --- a/drivers/spi/spi_bitbang.c +++ b/drivers/spi/spi_bitbang.c @@ -146,6 +146,9 @@ int spi_bitbang_setup(struct spi_device *spi) struct spi_bitbang_cs *cs = spi->controller_state; struct spi_bitbang *bitbang; + if (!spi->max_speed_hz) + return -EINVAL; + if (!cs) { cs = kzalloc(sizeof *cs, SLAB_KERNEL); if (!cs) @@ -172,13 +175,8 @@ int spi_bitbang_setup(struct spi_device *spi) if (!cs->txrx_word) return -EINVAL; - if (!spi->max_speed_hz) - spi->max_speed_hz = 500 * 1000; - - /* nsecs = max(50, (clock period)/2), be optimistic */ + /* nsecs = (clock period)/2 */ cs->nsecs = (1000000000/2) / (spi->max_speed_hz); - if (cs->nsecs < 50) - cs->nsecs = 50; if (cs->nsecs > MAX_UDELAY_MS * 1000) return -EINVAL; @@ -194,7 +192,7 @@ int spi_bitbang_setup(struct spi_device *spi) /* deselect chip (low or high) */ spin_lock(&bitbang->lock); if (!bitbang->busy) { - bitbang->chipselect(spi, 0); + bitbang->chipselect(spi, BITBANG_CS_INACTIVE); ndelay(cs->nsecs); } spin_unlock(&bitbang->lock); @@ -244,9 +242,9 @@ static void bitbang_work(void *_bitbang) struct spi_message *m; struct spi_device *spi; unsigned nsecs; - struct spi_transfer *t; + struct spi_transfer *t = NULL; unsigned tmp; - unsigned chipselect; + unsigned cs_change; int status; m = container_of(bitbang->queue.next, struct spi_message, @@ -254,37 +252,49 @@ static void bitbang_work(void *_bitbang) list_del_init(&m->queue); spin_unlock_irqrestore(&bitbang->lock, flags); -// FIXME this is made-up -nsecs = 100; + /* FIXME this is made-up ... the correct value is known to + * word-at-a-time bitbang code, and presumably chipselect() + * should enforce these requirements too? + */ + nsecs = 100; spi = m->spi; - t = m->transfers; tmp = 0; - chipselect = 0; + cs_change = 1; status = 0; - for (;;t++) { + list_for_each_entry (t, &m->transfers, transfer_list) { if (bitbang->shutdown) { status = -ESHUTDOWN; break; } - /* set up default clock polarity, and activate chip */ - if (!chipselect) { - bitbang->chipselect(spi, 1); + /* set up default clock polarity, and activate chip; + * this implicitly updates clock and spi modes as + * previously recorded for this device via setup(). + * (and also deselects any other chip that might be + * selected ...) + */ + if (cs_change) { + bitbang->chipselect(spi, BITBANG_CS_ACTIVE); ndelay(nsecs); } + cs_change = t->cs_change; if (!t->tx_buf && !t->rx_buf && t->len) { status = -EINVAL; break; } - /* transfer data */ + /* transfer data. the lower level code handles any + * new dma mappings it needs. our caller always gave + * us dma-safe buffers. + */ if (t->len) { - /* FIXME if bitbang->use_dma, dma_map_single() - * before the transfer, and dma_unmap_single() - * afterwards, for either or both buffers... + /* REVISIT dma API still needs a designated + * DMA_ADDR_INVALID; ~0 might be better. */ + if (!m->is_dma_mapped) + t->rx_dma = t->tx_dma = 0; status = bitbang->txrx_bufs(spi, t); } if (status != t->len) { @@ -299,29 +309,31 @@ nsecs = 100; if (t->delay_usecs) udelay(t->delay_usecs); - tmp++; - if (tmp >= m->n_transfer) - break; - - chipselect = !t->cs_change; - if (chipselect); + if (!cs_change) continue; + if (t->transfer_list.next == &m->transfers) + break; - bitbang->chipselect(spi, 0); - - /* REVISIT do we want the udelay here instead? */ - msleep(1); + /* sometimes a short mid-message deselect of the chip + * may be needed to terminate a mode or command + */ + ndelay(nsecs); + bitbang->chipselect(spi, BITBANG_CS_INACTIVE); + ndelay(nsecs); } - tmp = m->n_transfer - 1; - tmp = m->transfers[tmp].cs_change; - m->status = status; m->complete(m->context); - ndelay(2 * nsecs); - bitbang->chipselect(spi, status == 0 && tmp); - ndelay(nsecs); + /* normally deactivate chipselect ... unless no error and + * cs_change has hinted that the next message will probably + * be for this chip too. + */ + if (!(status == 0 && cs_change)) { + ndelay(nsecs); + bitbang->chipselect(spi, BITBANG_CS_INACTIVE); + ndelay(nsecs); + } spin_lock_irqsave(&bitbang->lock, flags); } diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 6a41e2650b2..939afd3a2e7 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -263,15 +263,16 @@ extern struct spi_master *spi_busnum_to_master(u16 busnum); /** * struct spi_transfer - a read/write buffer pair - * @tx_buf: data to be written (dma-safe address), or NULL - * @rx_buf: data to be read (dma-safe address), or NULL - * @tx_dma: DMA address of buffer, if spi_message.is_dma_mapped - * @rx_dma: DMA address of buffer, if spi_message.is_dma_mapped + * @tx_buf: data to be written (dma-safe memory), or NULL + * @rx_buf: data to be read (dma-safe memory), or NULL + * @tx_dma: DMA address of tx_buf, if spi_message.is_dma_mapped + * @rx_dma: DMA address of rx_buf, if spi_message.is_dma_mapped * @len: size of rx and tx buffers (in bytes) * @cs_change: affects chipselect after this transfer completes * @delay_usecs: microseconds to delay after this transfer before * (optionally) changing the chipselect status, then starting * the next transfer or completing this spi_message. + * @transfer_list: transfers are sequenced through spi_message.transfers * * SPI transfers always write the same number of bytes as they read. * Protocol drivers should always provide rx_buf and/or tx_buf. @@ -279,11 +280,16 @@ extern struct spi_master *spi_busnum_to_master(u16 busnum); * the data being transferred; that may reduce overhead, when the * underlying driver uses dma. * - * All SPI transfers start with the relevant chipselect active. Drivers - * can change behavior of the chipselect after the transfer finishes - * (including any mandatory delay). The normal behavior is to leave it - * selected, except for the last transfer in a message. Setting cs_change - * allows two additional behavior options: + * If the transmit buffer is null, undefined data will be shifted out + * while filling rx_buf. If the receive buffer is null, the data + * shifted in will be discarded. Only "len" bytes shift out (or in). + * It's an error to try to shift out a partial word. (For example, by + * shifting out three bytes with word size of sixteen or twenty bits; + * the former uses two bytes per word, the latter uses four bytes.) + * + * All SPI transfers start with the relevant chipselect active. Normally + * it stays selected until after the last transfer in a message. Drivers + * can affect the chipselect signal using cs_change: * * (i) If the transfer isn't the last one in the message, this flag is * used to make the chipselect briefly go inactive in the middle of the @@ -299,7 +305,8 @@ extern struct spi_master *spi_busnum_to_master(u16 busnum); * The code that submits an spi_message (and its spi_transfers) * to the lower layers is responsible for managing its memory. * Zero-initialize every field you don't set up explicitly, to - * insulate against future API updates. + * insulate against future API updates. After you submit a message + * and its transfers, ignore them until its completion callback. */ struct spi_transfer { /* it's ok if tx_buf == rx_buf (right?) @@ -316,12 +323,13 @@ struct spi_transfer { unsigned cs_change:1; u16 delay_usecs; + + struct list_head transfer_list; }; /** * struct spi_message - one multi-segment SPI transaction - * @transfers: the segements of the transaction - * @n_transfer: how many segments + * @transfers: list of transfer segments in this transaction * @spi: SPI device to which the transaction is queued * @is_dma_mapped: if true, the caller provided both dma and cpu virtual * addresses for each transfer buffer @@ -333,14 +341,22 @@ struct spi_transfer { * @queue: for use by whichever driver currently owns the message * @state: for use by whichever driver currently owns the message * + * An spi_message is used to execute an atomic sequence of data transfers, + * each represented by a struct spi_transfer. The sequence is "atomic" + * in the sense that no other spi_message may use that SPI bus until that + * sequence completes. On some systems, many such sequences can execute as + * as single programmed DMA transfer. On all systems, these messages are + * queued, and might complete after transactions to other devices. Messages + * sent to a given spi_device are alway executed in FIFO order. + * * The code that submits an spi_message (and its spi_transfers) * to the lower layers is responsible for managing its memory. * Zero-initialize every field you don't set up explicitly, to - * insulate against future API updates. + * insulate against future API updates. After you submit a message + * and its transfers, ignore them until its completion callback. */ struct spi_message { - struct spi_transfer *transfers; - unsigned n_transfer; + struct list_head transfers; struct spi_device *spi; @@ -371,6 +387,24 @@ struct spi_message { void *state; }; +static inline void spi_message_init(struct spi_message *m) +{ + memset(m, 0, sizeof *m); + INIT_LIST_HEAD(&m->transfers); +} + +static inline void +spi_message_add_tail(struct spi_transfer *t, struct spi_message *m) +{ + list_add_tail(&t->transfer_list, &m->transfers); +} + +static inline void +spi_transfer_del(struct spi_transfer *t) +{ + list_del(&t->transfer_list); +} + /* It's fine to embed message and transaction structures in other data * structures so long as you don't free them while they're in use. */ @@ -383,8 +417,12 @@ static inline struct spi_message *spi_message_alloc(unsigned ntrans, gfp_t flags + ntrans * sizeof(struct spi_transfer), flags); if (m) { - m->transfers = (void *)(m + 1); - m->n_transfer = ntrans; + int i; + struct spi_transfer *t = (struct spi_transfer *)(m + 1); + + INIT_LIST_HEAD(&m->transfers); + for (i = 0; i < ntrans; i++, t++) + spi_message_add_tail(t, m); } return m; } @@ -402,6 +440,8 @@ static inline void spi_message_free(struct spi_message *m) * device doesn't work with the mode 0 default. They may likewise need * to update clock rates or word sizes from initial values. This function * changes those settings, and must be called from a context that can sleep. + * The changes take effect the next time the device is selected and data + * is transferred to or from it. */ static inline int spi_setup(struct spi_device *spi) @@ -468,15 +508,12 @@ spi_write(struct spi_device *spi, const u8 *buf, size_t len) { struct spi_transfer t = { .tx_buf = buf, - .rx_buf = NULL, .len = len, - .cs_change = 0, - }; - struct spi_message m = { - .transfers = &t, - .n_transfer = 1, }; + struct spi_message m; + spi_message_init(&m); + spi_message_add_tail(&t, &m); return spi_sync(spi, &m); } @@ -493,16 +530,13 @@ static inline int spi_read(struct spi_device *spi, u8 *buf, size_t len) { struct spi_transfer t = { - .tx_buf = NULL, .rx_buf = buf, .len = len, - .cs_change = 0, - }; - struct spi_message m = { - .transfers = &t, - .n_transfer = 1, }; + struct spi_message m; + spi_message_init(&m); + spi_message_add_tail(&t, &m); return spi_sync(spi, &m); } diff --git a/include/linux/spi/spi_bitbang.h b/include/linux/spi/spi_bitbang.h index 8dfe61a445f..c961fe9bf3e 100644 --- a/include/linux/spi/spi_bitbang.h +++ b/include/linux/spi/spi_bitbang.h @@ -31,8 +31,15 @@ struct spi_bitbang { struct spi_master *master; void (*chipselect)(struct spi_device *spi, int is_on); +#define BITBANG_CS_ACTIVE 1 /* normally nCS, active low */ +#define BITBANG_CS_INACTIVE 0 + /* txrx_bufs() may handle dma mapping for transfers that don't + * already have one (transfer.{tx,rx}_dma is zero), or use PIO + */ int (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t); + + /* txrx_word[SPI_MODE_*]() just looks like a shift register */ u32 (*txrx_word[4])(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits); -- cgit v1.2.3-70-g09d2 From 7111763d391b0c5a949a4f2575aa88cd585f0ff6 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Sun, 8 Jan 2006 13:34:29 -0800 Subject: [PATCH] spi: misc fixes This collects some small SPI patches that seem to be missing from the MM tree: - spi_butterfly kbuild hooks got dropped somehow; this restores them - quick fix for a (theoretical?) m25p80_write() oops noted by Andrew - quick fix for a potential config-specific oops for mtd_dataflash() - minor doc tweaks Signed-off-by: David Brownell Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- Documentation/spi/spi-summary | 13 +++++++++++++ drivers/mtd/devices/m25p80.c | 4 +++- drivers/mtd/devices/mtd_dataflash.c | 2 +- drivers/spi/Kconfig | 10 ++++++++++ drivers/spi/Makefile | 1 + 5 files changed, 28 insertions(+), 2 deletions(-) (limited to 'drivers/mtd/devices/mtd_dataflash.c') diff --git a/Documentation/spi/spi-summary b/Documentation/spi/spi-summary index 761debf748e..a5ffba33a35 100644 --- a/Documentation/spi/spi-summary +++ b/Documentation/spi/spi-summary @@ -115,6 +115,9 @@ shows up in sysfs in several locations: /sys/devices/.../CTLR/spiB.C ... spi_device for on bus "B", chipselect C, accessed through CTLR. + /sys/devices/.../CTLR/spiB.C/modalias ... identifies the driver + that should be used with this device (for hotplug/coldplug) + /sys/bus/spi/devices/spiB.C ... symlink to the physical spiB-C device @@ -247,6 +250,12 @@ driver is registered: Like with other static board-specific setup, you won't unregister those. +The widely used "card" style computers bundle memory, cpu, and little else +onto a card that's maybe just thirty square centimeters. On such systems, +your arch/.../mach-.../board-*.c file would primarily provide information +about the devices on the mainboard into which such a card is plugged. That +certainly includes SPI devices hooked up through the card connectors! + NON-STATIC CONFIGURATIONS @@ -258,6 +267,10 @@ up the spi bus master, and will likely need spi_new_device() to provide the board info based on the board that was hotplugged. Of course, you'd later call at least spi_unregister_device() when that board is removed. +When Linux includes support for MMC/SD/SDIO/DataFlash cards through SPI, those +configurations will also be dynamic. Fortunately, those devices all support +basic device identification probes, so that support should hotplug normally. + How do I write an "SPI Protocol Driver"? ---------------------------------------- diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index 45108ed8558..d5f24089be7 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -378,7 +378,9 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len, spi_sync(flash->spi, &m); - *retlen += m.actual_length - sizeof(flash->command); + if (retlen) + *retlen += m.actual_length + - sizeof(flash->command); } } diff --git a/drivers/mtd/devices/mtd_dataflash.c b/drivers/mtd/devices/mtd_dataflash.c index 99d3a0320fc..155737e7483 100644 --- a/drivers/mtd/devices/mtd_dataflash.c +++ b/drivers/mtd/devices/mtd_dataflash.c @@ -508,7 +508,7 @@ add_dataflash(struct spi_device *spi, char *name, priv->partitioned = 1; return add_mtd_partitions(device, parts, nr_parts); } - } else if (pdata->nr_parts) + } else if (pdata && pdata->nr_parts) dev_warn(&spi->dev, "ignoring %d default partitions on %s\n", pdata->nr_parts, device->name); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 9b21c5d77b4..7a75faeb052 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -65,6 +65,16 @@ config SPI_BITBANG need it. You only need to select this explicitly to support driver modules that aren't part of this kernel tree. +config SPI_BUTTERFLY + tristate "Parallel port adapter for AVR Butterfly (DEVELOPMENT)" + depends on SPI_MASTER && PARPORT && EXPERIMENTAL + select SPI_BITBANG + help + This uses a custom parallel port cable to connect to an AVR + Butterfly , an + inexpensive battery powered microcontroller evaluation board. + This same cable can be used to flash new firmware. + # # Add new SPI master controllers in alphabetical order above this line # diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 5da6a4df401..c2c87e845ab 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SPI_MASTER) += spi.o # SPI master controller drivers (bus) obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o +obj-$(CONFIG_SPI_BUTTERFLY) += spi_butterfly.o # ... add above this line ... # SPI protocol drivers (device/link on bus) -- cgit v1.2.3-70-g09d2