diff options
Diffstat (limited to 'drivers/mtd/nand/alauda.c')
-rw-r--r-- | drivers/mtd/nand/alauda.c | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/drivers/mtd/nand/alauda.c b/drivers/mtd/nand/alauda.c new file mode 100644 index 000000000000..2c29854b4241 --- /dev/null +++ b/drivers/mtd/nand/alauda.c @@ -0,0 +1,742 @@ +/* + * MTD driver for Alauda chips + * + * Copyright (C) 2007 Joern Engel <joern@logfs.org> + * + * Based on drivers/usb/usb-skeleton.c which is: + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * and on drivers/usb/storage/alauda.c, which is: + * (c) 2005 Daniel Drake <dsd@gentoo.org> + * + * Idea and initial work by Arnd Bergmann <arnd@arndb.de> + */ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand_ecc.h> + +/* Control commands */ +#define ALAUDA_GET_XD_MEDIA_STATUS 0x08 +#define ALAUDA_ACK_XD_MEDIA_CHANGE 0x0a +#define ALAUDA_GET_XD_MEDIA_SIG 0x86 + +/* Common prefix */ +#define ALAUDA_BULK_CMD 0x40 + +/* The two ports */ +#define ALAUDA_PORT_XD 0x00 +#define ALAUDA_PORT_SM 0x01 + +/* Bulk commands */ +#define ALAUDA_BULK_READ_PAGE 0x84 +#define ALAUDA_BULK_READ_OOB 0x85 /* don't use, there's a chip bug */ +#define ALAUDA_BULK_READ_BLOCK 0x94 +#define ALAUDA_BULK_ERASE_BLOCK 0xa3 +#define ALAUDA_BULK_WRITE_PAGE 0xa4 +#define ALAUDA_BULK_WRITE_BLOCK 0xb4 +#define ALAUDA_BULK_RESET_MEDIA 0xe0 + +/* Address shifting */ +#define PBA_LO(pba) ((pba & 0xF) << 5) +#define PBA_HI(pba) (pba >> 3) +#define PBA_ZONE(pba) (pba >> 11) + +#define TIMEOUT HZ + +static struct usb_device_id alauda_table [] = { + { USB_DEVICE(0x0584, 0x0008) }, /* Fujifilm DPC-R1 */ + { USB_DEVICE(0x07b4, 0x010a) }, /* Olympus MAUSB-10 */ + { } +}; +MODULE_DEVICE_TABLE(usb, alauda_table); + +struct alauda_card { + u8 id; /* id byte */ + u8 chipshift; /* 1<<chipshift total size */ + u8 pageshift; /* 1<<pageshift page size */ + u8 blockshift; /* 1<<blockshift block size */ +}; + +struct alauda { + struct usb_device *dev; + struct usb_interface *interface; + struct mtd_info *mtd; + struct alauda_card *card; + struct mutex card_mutex; + u32 pagemask; + u32 bytemask; + u32 blockmask; + unsigned int write_out; + unsigned int bulk_in; + unsigned int bulk_out; + u8 port; + struct kref kref; +}; + +static struct alauda_card alauda_card_ids[] = { + /* NAND flash */ + { 0x6e, 20, 8, 12}, /* 1 MB */ + { 0xe8, 20, 8, 12}, /* 1 MB */ + { 0xec, 20, 8, 12}, /* 1 MB */ + { 0x64, 21, 8, 12}, /* 2 MB */ + { 0xea, 21, 8, 12}, /* 2 MB */ + { 0x6b, 22, 9, 13}, /* 4 MB */ + { 0xe3, 22, 9, 13}, /* 4 MB */ + { 0xe5, 22, 9, 13}, /* 4 MB */ + { 0xe6, 23, 9, 13}, /* 8 MB */ + { 0x73, 24, 9, 14}, /* 16 MB */ + { 0x75, 25, 9, 14}, /* 32 MB */ + { 0x76, 26, 9, 14}, /* 64 MB */ + { 0x79, 27, 9, 14}, /* 128 MB */ + { 0x71, 28, 9, 14}, /* 256 MB */ + + /* MASK ROM */ + { 0x5d, 21, 9, 13}, /* 2 MB */ + { 0xd5, 22, 9, 13}, /* 4 MB */ + { 0xd6, 23, 9, 13}, /* 8 MB */ + { 0x57, 24, 9, 13}, /* 16 MB */ + { 0x58, 25, 9, 13}, /* 32 MB */ + { } +}; + +static struct alauda_card *get_card(u8 id) +{ + struct alauda_card *card; + + for (card = alauda_card_ids; card->id; card++) + if (card->id == id) + return card; + return NULL; +} + +static void alauda_delete(struct kref *kref) +{ + struct alauda *al = container_of(kref, struct alauda, kref); + + if (al->mtd) { + del_mtd_device(al->mtd); + kfree(al->mtd); + } + usb_put_dev(al->dev); + kfree(al); +} + +static int alauda_get_media_status(struct alauda *al, void *buf) +{ + int ret; + + mutex_lock(&al->card_mutex); + ret = usb_control_msg(al->dev, usb_rcvctrlpipe(al->dev, 0), + ALAUDA_GET_XD_MEDIA_STATUS, 0xc0, 0, 1, buf, 2, HZ); + mutex_unlock(&al->card_mutex); + return ret; +} + +static int alauda_ack_media(struct alauda *al) +{ + int ret; + + mutex_lock(&al->card_mutex); + ret = usb_control_msg(al->dev, usb_sndctrlpipe(al->dev, 0), + ALAUDA_ACK_XD_MEDIA_CHANGE, 0x40, 0, 1, NULL, 0, HZ); + mutex_unlock(&al->card_mutex); + return ret; +} + +static int alauda_get_media_signatures(struct alauda *al, void *buf) +{ + int ret; + + mutex_lock(&al->card_mutex); + ret = usb_control_msg(al->dev, usb_rcvctrlpipe(al->dev, 0), + ALAUDA_GET_XD_MEDIA_SIG, 0xc0, 0, 0, buf, 4, HZ); + mutex_unlock(&al->card_mutex); + return ret; +} + +static void alauda_reset(struct alauda *al) +{ + u8 command[] = { + ALAUDA_BULK_CMD, ALAUDA_BULK_RESET_MEDIA, 0, 0, + 0, 0, 0, 0, al->port + }; + mutex_lock(&al->card_mutex); + usb_bulk_msg(al->dev, al->bulk_out, command, 9, NULL, HZ); + mutex_unlock(&al->card_mutex); +} + +static void correct_data(void *buf, void *read_ecc, + int *corrected, int *uncorrected) +{ + u8 calc_ecc[3]; + int err; + + nand_calculate_ecc(NULL, buf, calc_ecc); + err = nand_correct_data(NULL, buf, read_ecc, calc_ecc); + if (err) { + if (err > 0) + (*corrected)++; + else + (*uncorrected)++; + } +} + +struct alauda_sg_request { + struct urb *urb[3]; + struct completion comp; +}; + +static void alauda_complete(struct urb *urb) +{ + struct completion *comp = urb->context; + + if (comp) + complete(comp); +} + +static int __alauda_read_page(struct mtd_info *mtd, loff_t from, void *buf, + void *oob) +{ + struct alauda_sg_request sg; + struct alauda *al = mtd->priv; + u32 pba = from >> al->card->blockshift; + u32 page = (from >> al->card->pageshift) & al->pagemask; + u8 command[] = { + ALAUDA_BULK_CMD, ALAUDA_BULK_READ_PAGE, PBA_HI(pba), + PBA_ZONE(pba), 0, PBA_LO(pba) + page, 1, 0, al->port + }; + int i, err; + + for (i=0; i<3; i++) + sg.urb[i] = NULL; + + err = -ENOMEM; + for (i=0; i<3; i++) { + sg.urb[i] = usb_alloc_urb(0, GFP_NOIO); + if (!sg.urb[i]) + goto out; + } + init_completion(&sg.comp); + usb_fill_bulk_urb(sg.urb[0], al->dev, al->bulk_out, command, 9, + alauda_complete, NULL); + usb_fill_bulk_urb(sg.urb[1], al->dev, al->bulk_in, buf, mtd->writesize, + alauda_complete, NULL); + usb_fill_bulk_urb(sg.urb[2], al->dev, al->bulk_in, oob, 16, + alauda_complete, &sg.comp); + + mutex_lock(&al->card_mutex); + for (i=0; i<3; i++) { + err = usb_submit_urb(sg.urb[i], GFP_NOIO); + if (err) + goto cancel; + } + if (!wait_for_completion_timeout(&sg.comp, TIMEOUT)) { + err = -ETIMEDOUT; +cancel: + for (i=0; i<3; i++) { + usb_kill_urb(sg.urb[i]); + } + } + mutex_unlock(&al->card_mutex); + +out: + usb_free_urb(sg.urb[0]); + usb_free_urb(sg.urb[1]); + usb_free_urb(sg.urb[2]); + return err; +} + +static int alauda_read_page(struct mtd_info *mtd, loff_t from, + void *buf, u8 *oob, int *corrected, int *uncorrected) +{ + int err; + + err = __alauda_read_page(mtd, from, buf, oob); + if (err) + return err; + correct_data(buf, oob+13, corrected, uncorrected); + correct_data(buf+256, oob+8, corrected, uncorrected); + return 0; +} + +static int alauda_write_page(struct mtd_info *mtd, loff_t to, void *buf, + void *oob) +{ + struct alauda_sg_request sg; + struct alauda *al = mtd->priv; + u32 pba = to >> al->card->blockshift; + u32 page = (to >> al->card->pageshift) & al->pagemask; + u8 command[] = { + ALAUDA_BULK_CMD, ALAUDA_BULK_WRITE_PAGE, PBA_HI(pba), + PBA_ZONE(pba), 0, PBA_LO(pba) + page, 32, 0, al->port + }; + int i, err; + + for (i=0; i<3; i++) + sg.urb[i] = NULL; + + err = -ENOMEM; + for (i=0; i<3; i++) { + sg.urb[i] = usb_alloc_urb(0, GFP_NOIO); + if (!sg.urb[i]) + goto out; + } + init_completion(&sg.comp); + usb_fill_bulk_urb(sg.urb[0], al->dev, al->bulk_out, command, 9, + alauda_complete, NULL); + usb_fill_bulk_urb(sg.urb[1], al->dev, al->write_out, buf,mtd->writesize, + alauda_complete, NULL); + usb_fill_bulk_urb(sg.urb[2], al->dev, al->write_out, oob, 16, + alauda_complete, &sg.comp); + + mutex_lock(&al->card_mutex); + for (i=0; i<3; i++) { + err = usb_submit_urb(sg.urb[i], GFP_NOIO); + if (err) + goto cancel; + } + if (!wait_for_completion_timeout(&sg.comp, TIMEOUT)) { + err = -ETIMEDOUT; +cancel: + for (i=0; i<3; i++) { + usb_kill_urb(sg.urb[i]); + } + } + mutex_unlock(&al->card_mutex); + +out: + usb_free_urb(sg.urb[0]); + usb_free_urb(sg.urb[1]); + usb_free_urb(sg.urb[2]); + return err; +} + +static int alauda_erase_block(struct mtd_info *mtd, loff_t ofs) +{ + struct alauda_sg_request sg; + struct alauda *al = mtd->priv; + u32 pba = ofs >> al->card->blockshift; + u8 command[] = { + ALAUDA_BULK_CMD, ALAUDA_BULK_ERASE_BLOCK, PBA_HI(pba), + PBA_ZONE(pba), 0, PBA_LO(pba), 0x02, 0, al->port + }; + u8 buf[2]; + int i, err; + + for (i=0; i<2; i++) + sg.urb[i] = NULL; + + err = -ENOMEM; + for (i=0; i<2; i++) { + sg.urb[i] = usb_alloc_urb(0, GFP_NOIO); + if (!sg.urb[i]) + goto out; + } + init_completion(&sg.comp); + usb_fill_bulk_urb(sg.urb[0], al->dev, al->bulk_out, command, 9, + alauda_complete, NULL); + usb_fill_bulk_urb(sg.urb[1], al->dev, al->bulk_in, buf, 2, + alauda_complete, &sg.comp); + + mutex_lock(&al->card_mutex); + for (i=0; i<2; i++) { + err = usb_submit_urb(sg.urb[i], GFP_NOIO); + if (err) + goto cancel; + } + if (!wait_for_completion_timeout(&sg.comp, TIMEOUT)) { + err = -ETIMEDOUT; +cancel: + for (i=0; i<2; i++) { + usb_kill_urb(sg.urb[i]); + } + } + mutex_unlock(&al->card_mutex); + +out: + usb_free_urb(sg.urb[0]); + usb_free_urb(sg.urb[1]); + return err; +} + +static int alauda_read_oob(struct mtd_info *mtd, loff_t from, void *oob) +{ + static u8 ignore_buf[512]; /* write only */ + + return __alauda_read_page(mtd, from, ignore_buf, oob); +} + +static int popcount8(u8 c) +{ + int ret = 0; + + for ( ; c; c>>=1) + ret += c & 1; + return ret; +} + +static int alauda_isbad(struct mtd_info *mtd, loff_t ofs) +{ + u8 oob[16]; + int err; + + err = alauda_read_oob(mtd, ofs, oob); + if (err) + return err; + + /* A block is marked bad if two or more bits are zero */ + return popcount8(oob[5]) >= 7 ? 0 : 1; +} + +static int alauda_bounce_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct alauda *al = mtd->priv; + void *bounce_buf; + int err, corrected=0, uncorrected=0; + + bounce_buf = kmalloc(mtd->writesize, GFP_KERNEL); + if (!bounce_buf) + return -ENOMEM; + + *retlen = len; + while (len) { + u8 oob[16]; + u32 byte = from & al->bytemask; + size_t cplen = min(len, mtd->writesize - byte); + + err = alauda_read_page(mtd, from, bounce_buf, oob, + &corrected, &uncorrected); + if (err) + goto out; + + memcpy(buf, bounce_buf + byte, cplen); + buf += cplen; + from += cplen; + len -= cplen; + } + err = 0; + if (corrected) + err = -EUCLEAN; + if (uncorrected) + err = -EBADMSG; +out: + kfree(bounce_buf); + return err; +} + +static int alauda_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct alauda *al = mtd->priv; + int err, corrected=0, uncorrected=0; + + if ((from & al->bytemask) || (len & al->bytemask)) + return alauda_bounce_read(mtd, from, len, retlen, buf); + + *retlen = len; + while (len) { + u8 oob[16]; + + err = alauda_read_page(mtd, from, buf, oob, + &corrected, &uncorrected); + if (err) + return err; + + buf += mtd->writesize; + from += mtd->writesize; + len -= mtd->writesize; + } + err = 0; + if (corrected) + err = -EUCLEAN; + if (uncorrected) + err = -EBADMSG; + return err; +} + +static int alauda_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct alauda *al = mtd->priv; + int err; + + if ((to & al->bytemask) || (len & al->bytemask)) + return -EINVAL; + + *retlen = len; + while (len) { + u32 page = (to >> al->card->pageshift) & al->pagemask; + u8 oob[16] = { 'h', 'e', 'l', 'l', 'o', 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + /* don't write to bad blocks */ + if (page == 0) { + err = alauda_isbad(mtd, to); + if (err) { + return -EIO; + } + } + nand_calculate_ecc(mtd, buf, &oob[13]); + nand_calculate_ecc(mtd, buf+256, &oob[8]); + + err = alauda_write_page(mtd, to, (void*)buf, oob); + if (err) + return err; + + buf += mtd->writesize; + to += mtd->writesize; + len -= mtd->writesize; + } + return 0; +} + +static int __alauda_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct alauda *al = mtd->priv; + u32 ofs = instr->addr; + u32 len = instr->len; + int err; + + if ((ofs & al->blockmask) || (len & al->blockmask)) + return -EINVAL; + + while (len) { + /* don't erase bad blocks */ + err = alauda_isbad(mtd, ofs); + if (err > 0) + err = -EIO; + if (err < 0) + return err; + + err = alauda_erase_block(mtd, ofs); + if (err < 0) + return err; + + ofs += mtd->erasesize; + len -= mtd->erasesize; + } + return 0; +} + +static int alauda_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + int err; + + err = __alauda_erase(mtd, instr); + instr->state = err ? MTD_ERASE_FAILED : MTD_ERASE_DONE; + mtd_erase_callback(instr); + return err; +} + +static int alauda_init_media(struct alauda *al) +{ + u8 buf[4], *b0=buf, *b1=buf+1; + struct alauda_card *card; + struct mtd_info *mtd; + int err; + + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); + if (!mtd) + return -ENOMEM; + + for (;;) { + err = alauda_get_media_status(al, buf); + if (err < 0) + goto error; + if (*b0 & 0x10) + break; + msleep(20); + } + + err = alauda_ack_media(al); + if (err) + goto error; + + msleep(10); + + err = alauda_get_media_status(al, buf); + if (err < 0) + goto error; + + if (*b0 != 0x14) { + /* media not ready */ + err = -EIO; + goto error; + } + err = alauda_get_media_signatures(al, buf); + if (err < 0) + goto error; + + card = get_card(*b1); + if (!card) { + printk(KERN_ERR"Alauda: unknown card id %02x\n", *b1); + err = -EIO; + goto error; + } + printk(KERN_INFO"pagesize=%x\nerasesize=%x\nsize=%xMiB\n", + 1<<card->pageshift, 1<<card->blockshift, + 1<<(card->chipshift-20)); + al->card = card; + al->pagemask = (1 << (card->blockshift - card->pageshift)) - 1; + al->bytemask = (1 << card->pageshift) - 1; + al->blockmask = (1 << card->blockshift) - 1; + + mtd->name = "alauda"; + mtd->size = 1<<card->chipshift; + mtd->erasesize = 1<<card->blockshift; + mtd->writesize = 1<<card->pageshift; + mtd->type = MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->read = alauda_read; + mtd->write = alauda_write; + mtd->erase = alauda_erase; + mtd->block_isbad = alauda_isbad; + mtd->priv = al; + mtd->owner = THIS_MODULE; + + err = add_mtd_device(mtd); + if (err) { + err = -ENFILE; + goto error; + } + + al->mtd = mtd; + alauda_reset(al); /* no clue whether this is necessary */ + return 0; +error: + kfree(mtd); + return err; +} + +static int alauda_check_media(struct alauda *al) +{ + u8 buf[2], *b0 = buf, *b1 = buf+1; + int err; + + err = alauda_get_media_status(al, buf); + if (err < 0) + return err; + + if ((*b1 & 0x01) == 0) { + /* door open */ + return -EIO; + } + if ((*b0 & 0x80) || ((*b0 & 0x1F) == 0x10)) { + /* no media ? */ + return -EIO; + } + if (*b0 & 0x08) { + /* media change ? */ + return alauda_init_media(al); + } + return 0; +} + +static int alauda_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct alauda *al; + struct usb_host_interface *iface; + struct usb_endpoint_descriptor *ep, + *ep_in=NULL, *ep_out=NULL, *ep_wr=NULL; + int i, err = -ENOMEM; + + al = kzalloc(2*sizeof(*al), GFP_KERNEL); + if (!al) + goto error; + + kref_init(&al->kref); + usb_set_intfdata(interface, al); + + al->dev = usb_get_dev(interface_to_usbdev(interface)); + al->interface = interface; + + iface = interface->cur_altsetting; + for (i = 0; i < iface->desc.bNumEndpoints; ++i) { + ep = &iface->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(ep)) { + ep_in = ep; + } else if (usb_endpoint_is_bulk_out(ep)) { + if (i==0) + ep_wr = ep; + else + ep_out = ep; + } + } + err = -EIO; + if (!ep_wr || !ep_in || !ep_out) + goto error; + + al->write_out = usb_sndbulkpipe(al->dev, + ep_wr->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + al->bulk_in = usb_rcvbulkpipe(al->dev, + ep_in->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + al->bulk_out = usb_sndbulkpipe(al->dev, + ep_out->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + + /* second device is identical up to now */ + memcpy(al+1, al, sizeof(*al)); + + mutex_init(&al[0].card_mutex); + mutex_init(&al[1].card_mutex); + + al[0].port = ALAUDA_PORT_XD; + al[1].port = ALAUDA_PORT_SM; + + info("alauda probed"); + alauda_check_media(al); + alauda_check_media(al+1); + + return 0; + +error: + if (al) + kref_put(&al->kref, alauda_delete); + return err; +} + +static void alauda_disconnect(struct usb_interface *interface) +{ + struct alauda *al; + + al = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + /* FIXME: prevent more I/O from starting */ + + /* decrement our usage count */ + if (al) + kref_put(&al->kref, alauda_delete); + + info("alauda gone"); +} + +static struct usb_driver alauda_driver = { + .name = "alauda", + .probe = alauda_probe, + .disconnect = alauda_disconnect, + .id_table = alauda_table, +}; + +static int __init alauda_init(void) +{ + return usb_register(&alauda_driver); +} + +static void __exit alauda_exit(void) +{ + usb_deregister(&alauda_driver); +} + +module_init(alauda_init); +module_exit(alauda_exit); + +MODULE_LICENSE("GPL"); |