--- a/drivers/mtd/devices/bcm47xxsflash.c +++ b/drivers/mtd/devices/bcm47xxsflash.c @@ -1,47 +1,153 @@ -#include +/* + * Broadcom SiliconBackplane chipcommon serial flash interface + * + * Copyright 2011, 2012, Hauke Mehrtens + * Copyright 2006, Broadcom Corporation + * All Rights Reserved. + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#define pr_fmt(fmt) "bcm47xxsflash: " fmt #include #include +#include +#include #include +#include +#include +#include +#include #include -#include - -#include "bcm47xxsflash.h" +#include MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Serial flash driver for BCMA bus"); +MODULE_DESCRIPTION("BCM47XX serial flash driver"); static const char *probes[] = { "bcm47xxpart", NULL }; +static int +sflash_mtd_poll(struct bcm47xxsflash *sflash, unsigned int offset, int timeout) +{ + unsigned long now = jiffies; + + for (;;) { + if (!sflash->poll(sflash, offset)) { + break; + } + if (time_after(jiffies, now + timeout)) { + pr_err("timeout while polling\n"); + return -ETIMEDOUT; + + } + cpu_relax(); + udelay(1); + } + + return 0; +} + static int bcm47xxsflash_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { - struct bcm47xxsflash *b47s = mtd->priv; + struct bcm47xxsflash *sflash = (struct bcm47xxsflash *)mtd->priv; /* Check address range */ + if (!len) + return 0; + if ((from + len) > mtd->size) return -EINVAL; - memcpy_fromio(buf, (void __iomem *)KSEG0ADDR(b47s->window + from), + memcpy_fromio(buf, (void __iomem *)KSEG0ADDR(sflash->window + from), len); *retlen = len; return len; } -static void bcm47xxsflash_fill_mtd(struct bcm47xxsflash *b47s) +static int +sflash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { - struct mtd_info *mtd = &b47s->mtd; + int bytes; + int ret; + struct bcm47xxsflash *sflash = (struct bcm47xxsflash *)mtd->priv; - mtd->priv = b47s; - mtd->name = "bcm47xxsflash"; - mtd->owner = THIS_MODULE; - mtd->type = MTD_ROM; - mtd->size = b47s->size; - mtd->_read = bcm47xxsflash_read; + /* Check address range */ + if (!len) + return 0; + + if ((to + len) > mtd->size) + return -EINVAL; + + *retlen = 0; + while (len) { + ret = sflash->write(sflash, to, len, buf); + if (ret < 0) + return ret; + + bytes = ret; + + ret = sflash_mtd_poll(sflash, (unsigned int) to, HZ / 10); + if (ret) + return ret; + + to += (loff_t) bytes; + len -= bytes; + buf += bytes; + *retlen += bytes; + } - /* TODO: implement writing support and verify/change following code */ - mtd->flags = MTD_CAP_ROM; - mtd->writebufsize = mtd->writesize = 1; + return 0; +} + +static int +sflash_mtd_erase(struct mtd_info *mtd, struct erase_info *erase) +{ + struct bcm47xxsflash *sflash = (struct bcm47xxsflash *) mtd->priv; + int i, j, ret = 0; + unsigned int addr, len; + + /* Check address range */ + if (!erase->len) + return 0; + if ((erase->addr + erase->len) > mtd->size) + return -EINVAL; + + addr = erase->addr; + len = erase->len; + + /* Ensure that requested regions are aligned */ + for (i = 0; i < mtd->numeraseregions; i++) { + for (j = 0; j < mtd->eraseregions[i].numblocks; j++) { + if (addr == mtd->eraseregions[i].offset + + mtd->eraseregions[i].erasesize * j && + len >= mtd->eraseregions[i].erasesize) { + ret = sflash->erase(sflash, addr); + if (ret < 0) + break; + ret = sflash_mtd_poll(sflash, addr, 10 * HZ); + if (ret) + break; + addr += mtd->eraseregions[i].erasesize; + len -= mtd->eraseregions[i].erasesize; + } + } + if (ret) + break; + } + + /* Set erase status */ + if (ret) + erase->state = MTD_ERASE_FAILED; + else + erase->state = MTD_ERASE_DONE; + + /* Call erase callback */ + if (erase->callback) + erase->callback(erase); + + return ret; } /************************************************** @@ -50,53 +156,94 @@ static void bcm47xxsflash_fill_mtd(struc static int bcm47xxsflash_bcma_probe(struct platform_device *pdev) { - struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev); - struct bcm47xxsflash *b47s; - int err; + struct bcm47xxsflash *sflash = dev_get_platdata(&pdev->dev); + struct mtd_info *mtd; + struct mtd_erase_region_info *eraseregions; + int ret = 0; + + mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); + if (!mtd){ + ret = -ENOMEM; + goto err_out; + } - b47s = kzalloc(sizeof(*b47s), GFP_KERNEL); - if (!b47s) { - err = -ENOMEM; - goto out; - } - sflash->priv = b47s; - - b47s->window = sflash->window; - b47s->blocksize = sflash->blocksize; - b47s->numblocks = sflash->numblocks; - b47s->size = sflash->size; - bcm47xxsflash_fill_mtd(b47s); - - err = mtd_device_parse_register(&b47s->mtd, probes, NULL, NULL, 0); - if (err) { - pr_err("Failed to register MTD device: %d\n", err); - goto err_dev_reg; + eraseregions = kzalloc(sizeof(struct mtd_erase_region_info), GFP_KERNEL); + if (!eraseregions) { + ret = -ENOMEM; + goto err_free_mtd; } + pr_info("found serial flash: blocksize=%dKB, numblocks=%d, size=%dKB\n", + sflash->blocksize / 1024, sflash->numblocks, sflash->size / 1024); + + /* Setup region info */ + eraseregions->offset = 0; + eraseregions->erasesize = sflash->blocksize; + eraseregions->numblocks = sflash->numblocks; + if (eraseregions->erasesize > mtd->erasesize) + mtd->erasesize = eraseregions->erasesize; + mtd->size = sflash->size; + mtd->numeraseregions = 1; + + /* Register with MTD */ + mtd->name = "bcm47xx-sflash"; + mtd->type = MTD_NORFLASH; + mtd->flags = MTD_CAP_NORFLASH; + mtd->eraseregions = eraseregions; + mtd->_erase = sflash_mtd_erase; + mtd->_read = bcm47xxsflash_read; + mtd->_write = sflash_mtd_write; + mtd->writesize = 1; + mtd->priv = sflash; + ret = dev_set_drvdata(&pdev->dev, mtd); + mtd->owner = THIS_MODULE; + if (ret) { + pr_err("adding private data failed\n"); + goto err_free_eraseregions; + } + + ret = mtd_device_parse_register(mtd, probes, NULL, NULL, 0); + + if (ret) { + pr_err("mtd_device_register failed\n"); + goto err_free_eraseregions; + } return 0; -err_dev_reg: - kfree(&b47s->mtd); -out: - return err; +err_free_eraseregions: + kfree(eraseregions); +err_free_mtd: + kfree(mtd); +err_out: + return ret; } static int bcm47xxsflash_bcma_remove(struct platform_device *pdev) { - struct bcma_sflash *sflash = dev_get_platdata(&pdev->dev); - struct bcm47xxsflash *b47s = sflash->priv; - - mtd_device_unregister(&b47s->mtd); - kfree(b47s); + struct mtd_info *mtd = dev_get_drvdata(&pdev->dev); + if (mtd) { + mtd_device_unregister(mtd); + map_destroy(mtd); + kfree(mtd->eraseregions); + kfree(mtd); + dev_set_drvdata(&pdev->dev, NULL); + } return 0; } +static const struct platform_device_id bcm47xxsflash_table[] = { + { "bcm47xx-sflash", 0 }, + { } +}; +MODULE_DEVICE_TABLE(platform, bcm47xxsflash_table); + static struct platform_driver bcma_sflash_driver = { + .id_table = bcm47xxsflash_table, .probe = bcm47xxsflash_bcma_probe, .remove = bcm47xxsflash_bcma_remove, .driver = { - .name = "bcma_sflash", + .name = "bcm47xx-sflash", .owner = THIS_MODULE, }, }; @@ -111,8 +258,7 @@ static int __init bcm47xxsflash_init(voi err = platform_driver_register(&bcma_sflash_driver); if (err) - pr_err("Failed to register BCMA serial flash driver: %d\n", - err); + pr_err("error registering platform driver: %i\n", err); return err; } --- /dev/null +++ b/include/linux/mtd/bcm47xxsflash.h @@ -0,0 +1,33 @@ +#ifndef LINUX_MTD_BCM47XX_SFLASH_H_ +#define LINUX_MTD_BCM47XX_SFLASH_H_ + +#include + +enum bcm47xxsflash_type { + BCM47XX_SFLASH_SSB, + BCM47XX_SFLASH_BCMA, +}; + +struct ssb_chipcommon; +struct bcma_drv_cc; + +struct bcm47xxsflash { + enum bcm47xxsflash_type type; + union { + struct ssb_chipcommon *scc; + struct bcma_drv_cc *bcc; + }; + + bool present; + u16 numblocks; + u32 window; + u32 blocksize; + u32 size; + + int (*poll)(struct bcm47xxsflash *dev, u32 offset); + int (*write)(struct bcm47xxsflash *dev, u32 offset, u32 len, const u8 *buf); + int (*erase)(struct bcm47xxsflash *dev, u32 offset); + + struct mtd_info *mtd; +}; +#endif /* LINUX_MTD_BCM47XX_SFLASH_H_ */