162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci#include <linux/kernel.h> 362306a36Sopenharmony_ci#include <linux/module.h> 462306a36Sopenharmony_ci#include <linux/slab.h> 562306a36Sopenharmony_ci#include <linux/delay.h> 662306a36Sopenharmony_ci#include <linux/ioport.h> 762306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 862306a36Sopenharmony_ci#include <linux/platform_device.h> 962306a36Sopenharmony_ci#include <linux/bcma/bcma.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include "bcm47xxsflash.h" 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1462306a36Sopenharmony_ciMODULE_DESCRIPTION("Serial flash driver for BCMA bus"); 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistatic const char * const probes[] = { "bcm47xxpart", NULL }; 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci/************************************************** 1962306a36Sopenharmony_ci * Various helpers 2062306a36Sopenharmony_ci **************************************************/ 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic void bcm47xxsflash_cmd(struct bcm47xxsflash *b47s, u32 opcode) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci int i; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHCTL, BCMA_CC_FLASHCTL_START | opcode); 2762306a36Sopenharmony_ci for (i = 0; i < 1000; i++) { 2862306a36Sopenharmony_ci if (!(b47s->cc_read(b47s, BCMA_CC_FLASHCTL) & 2962306a36Sopenharmony_ci BCMA_CC_FLASHCTL_BUSY)) 3062306a36Sopenharmony_ci return; 3162306a36Sopenharmony_ci cpu_relax(); 3262306a36Sopenharmony_ci } 3362306a36Sopenharmony_ci pr_err("Control command failed (timeout)!\n"); 3462306a36Sopenharmony_ci} 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic int bcm47xxsflash_poll(struct bcm47xxsflash *b47s, int timeout) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci unsigned long deadline = jiffies + timeout; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci do { 4162306a36Sopenharmony_ci switch (b47s->type) { 4262306a36Sopenharmony_ci case BCM47XXSFLASH_TYPE_ST: 4362306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_ST_RDSR); 4462306a36Sopenharmony_ci if (!(b47s->cc_read(b47s, BCMA_CC_FLASHDATA) & 4562306a36Sopenharmony_ci SR_ST_WIP)) 4662306a36Sopenharmony_ci return 0; 4762306a36Sopenharmony_ci break; 4862306a36Sopenharmony_ci case BCM47XXSFLASH_TYPE_ATMEL: 4962306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_AT_STATUS); 5062306a36Sopenharmony_ci if (b47s->cc_read(b47s, BCMA_CC_FLASHDATA) & 5162306a36Sopenharmony_ci SR_AT_READY) 5262306a36Sopenharmony_ci return 0; 5362306a36Sopenharmony_ci break; 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci cpu_relax(); 5762306a36Sopenharmony_ci udelay(1); 5862306a36Sopenharmony_ci } while (!time_after_eq(jiffies, deadline)); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci pr_err("Timeout waiting for flash to be ready!\n"); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci return -EBUSY; 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci/************************************************** 6662306a36Sopenharmony_ci * MTD ops 6762306a36Sopenharmony_ci **************************************************/ 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic int bcm47xxsflash_erase(struct mtd_info *mtd, struct erase_info *erase) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci struct bcm47xxsflash *b47s = mtd->priv; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci switch (b47s->type) { 7462306a36Sopenharmony_ci case BCM47XXSFLASH_TYPE_ST: 7562306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_ST_WREN); 7662306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHADDR, erase->addr); 7762306a36Sopenharmony_ci /* Newer flashes have "sub-sectors" which can be erased 7862306a36Sopenharmony_ci * independently with a new command: ST_SSE. The ST_SE command 7962306a36Sopenharmony_ci * erases 64KB just as before. 8062306a36Sopenharmony_ci */ 8162306a36Sopenharmony_ci if (b47s->blocksize < (64 * 1024)) 8262306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_ST_SSE); 8362306a36Sopenharmony_ci else 8462306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_ST_SE); 8562306a36Sopenharmony_ci break; 8662306a36Sopenharmony_ci case BCM47XXSFLASH_TYPE_ATMEL: 8762306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHADDR, erase->addr << 1); 8862306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_AT_PAGE_ERASE); 8962306a36Sopenharmony_ci break; 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci return bcm47xxsflash_poll(b47s, HZ); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic int bcm47xxsflash_read(struct mtd_info *mtd, loff_t from, size_t len, 9662306a36Sopenharmony_ci size_t *retlen, u_char *buf) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci struct bcm47xxsflash *b47s = mtd->priv; 9962306a36Sopenharmony_ci size_t orig_len = len; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci /* Check address range */ 10262306a36Sopenharmony_ci if ((from + len) > mtd->size) 10362306a36Sopenharmony_ci return -EINVAL; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci /* Read as much as possible using fast MMIO window */ 10662306a36Sopenharmony_ci if (from < BCM47XXSFLASH_WINDOW_SZ) { 10762306a36Sopenharmony_ci size_t memcpy_len; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci memcpy_len = min(len, (size_t)(BCM47XXSFLASH_WINDOW_SZ - from)); 11062306a36Sopenharmony_ci memcpy_fromio(buf, b47s->window + from, memcpy_len); 11162306a36Sopenharmony_ci from += memcpy_len; 11262306a36Sopenharmony_ci len -= memcpy_len; 11362306a36Sopenharmony_ci buf += memcpy_len; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* Use indirect access for content out of the window */ 11762306a36Sopenharmony_ci for (; len; len--) { 11862306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHADDR, from++); 11962306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_ST_READ4B); 12062306a36Sopenharmony_ci *buf++ = b47s->cc_read(b47s, BCMA_CC_FLASHDATA); 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci *retlen = orig_len; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return orig_len; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic int bcm47xxsflash_write_st(struct mtd_info *mtd, u32 offset, size_t len, 12962306a36Sopenharmony_ci const u_char *buf) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct bcm47xxsflash *b47s = mtd->priv; 13262306a36Sopenharmony_ci int written = 0; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci /* Enable writes */ 13562306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_ST_WREN); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci /* Write first byte */ 13862306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHADDR, offset); 13962306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHDATA, *buf++); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci /* Program page */ 14262306a36Sopenharmony_ci if (b47s->bcma_cc->core->id.rev < 20) { 14362306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_ST_PP); 14462306a36Sopenharmony_ci return 1; /* 1B written */ 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci /* Program page and set CSA (on newer chips we can continue writing) */ 14862306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_ST_CSA | OPCODE_ST_PP); 14962306a36Sopenharmony_ci offset++; 15062306a36Sopenharmony_ci len--; 15162306a36Sopenharmony_ci written++; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci while (len > 0) { 15462306a36Sopenharmony_ci /* Page boundary, another function call is needed */ 15562306a36Sopenharmony_ci if ((offset & 0xFF) == 0) 15662306a36Sopenharmony_ci break; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_ST_CSA | *buf++); 15962306a36Sopenharmony_ci offset++; 16062306a36Sopenharmony_ci len--; 16162306a36Sopenharmony_ci written++; 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* All done, drop CSA & poll */ 16562306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHCTL, 0); 16662306a36Sopenharmony_ci udelay(1); 16762306a36Sopenharmony_ci if (bcm47xxsflash_poll(b47s, HZ / 10)) 16862306a36Sopenharmony_ci pr_err("Flash rejected dropping CSA\n"); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci return written; 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic int bcm47xxsflash_write_at(struct mtd_info *mtd, u32 offset, size_t len, 17462306a36Sopenharmony_ci const u_char *buf) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct bcm47xxsflash *b47s = mtd->priv; 17762306a36Sopenharmony_ci u32 mask = b47s->blocksize - 1; 17862306a36Sopenharmony_ci u32 page = (offset & ~mask) << 1; 17962306a36Sopenharmony_ci u32 byte = offset & mask; 18062306a36Sopenharmony_ci int written = 0; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* If we don't overwrite whole page, read it to the buffer first */ 18362306a36Sopenharmony_ci if (byte || (len < b47s->blocksize)) { 18462306a36Sopenharmony_ci int err; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHADDR, page); 18762306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_LOAD); 18862306a36Sopenharmony_ci /* 250 us for AT45DB321B */ 18962306a36Sopenharmony_ci err = bcm47xxsflash_poll(b47s, HZ / 1000); 19062306a36Sopenharmony_ci if (err) { 19162306a36Sopenharmony_ci pr_err("Timeout reading page 0x%X info buffer\n", page); 19262306a36Sopenharmony_ci return err; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci /* Change buffer content with our data */ 19762306a36Sopenharmony_ci while (len > 0) { 19862306a36Sopenharmony_ci /* Page boundary, another function call is needed */ 19962306a36Sopenharmony_ci if (byte == b47s->blocksize) 20062306a36Sopenharmony_ci break; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHADDR, byte++); 20362306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHDATA, *buf++); 20462306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_WRITE); 20562306a36Sopenharmony_ci len--; 20662306a36Sopenharmony_ci written++; 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci /* Program page with the buffer content */ 21062306a36Sopenharmony_ci b47s->cc_write(b47s, BCMA_CC_FLASHADDR, page); 21162306a36Sopenharmony_ci bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_PROGRAM); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci return written; 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_cistatic int bcm47xxsflash_write(struct mtd_info *mtd, loff_t to, size_t len, 21762306a36Sopenharmony_ci size_t *retlen, const u_char *buf) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci struct bcm47xxsflash *b47s = mtd->priv; 22062306a36Sopenharmony_ci int written; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci /* Writing functions can return without writing all passed data, for 22362306a36Sopenharmony_ci * example when the hardware is too old or when we git page boundary. 22462306a36Sopenharmony_ci */ 22562306a36Sopenharmony_ci while (len > 0) { 22662306a36Sopenharmony_ci switch (b47s->type) { 22762306a36Sopenharmony_ci case BCM47XXSFLASH_TYPE_ST: 22862306a36Sopenharmony_ci written = bcm47xxsflash_write_st(mtd, to, len, buf); 22962306a36Sopenharmony_ci break; 23062306a36Sopenharmony_ci case BCM47XXSFLASH_TYPE_ATMEL: 23162306a36Sopenharmony_ci written = bcm47xxsflash_write_at(mtd, to, len, buf); 23262306a36Sopenharmony_ci break; 23362306a36Sopenharmony_ci default: 23462306a36Sopenharmony_ci BUG_ON(1); 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci if (written < 0) { 23762306a36Sopenharmony_ci pr_err("Error writing at offset 0x%llX\n", to); 23862306a36Sopenharmony_ci return written; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci to += (loff_t)written; 24162306a36Sopenharmony_ci len -= written; 24262306a36Sopenharmony_ci *retlen += written; 24362306a36Sopenharmony_ci buf += written; 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci return 0; 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistatic void bcm47xxsflash_fill_mtd(struct bcm47xxsflash *b47s, 25062306a36Sopenharmony_ci struct device *dev) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct mtd_info *mtd = &b47s->mtd; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci mtd->priv = b47s; 25562306a36Sopenharmony_ci mtd->dev.parent = dev; 25662306a36Sopenharmony_ci mtd->name = "bcm47xxsflash"; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci mtd->type = MTD_NORFLASH; 25962306a36Sopenharmony_ci mtd->flags = MTD_CAP_NORFLASH; 26062306a36Sopenharmony_ci mtd->size = b47s->size; 26162306a36Sopenharmony_ci mtd->erasesize = b47s->blocksize; 26262306a36Sopenharmony_ci mtd->writesize = 1; 26362306a36Sopenharmony_ci mtd->writebufsize = 1; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci mtd->_erase = bcm47xxsflash_erase; 26662306a36Sopenharmony_ci mtd->_read = bcm47xxsflash_read; 26762306a36Sopenharmony_ci mtd->_write = bcm47xxsflash_write; 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci/************************************************** 27162306a36Sopenharmony_ci * BCMA 27262306a36Sopenharmony_ci **************************************************/ 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_cistatic int bcm47xxsflash_bcma_cc_read(struct bcm47xxsflash *b47s, u16 offset) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci return bcma_cc_read32(b47s->bcma_cc, offset); 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic void bcm47xxsflash_bcma_cc_write(struct bcm47xxsflash *b47s, u16 offset, 28062306a36Sopenharmony_ci u32 value) 28162306a36Sopenharmony_ci{ 28262306a36Sopenharmony_ci bcma_cc_write32(b47s->bcma_cc, offset, value); 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic int bcm47xxsflash_bcma_probe(struct platform_device *pdev) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci struct device *dev = &pdev->dev; 28862306a36Sopenharmony_ci struct bcma_sflash *sflash = dev_get_platdata(dev); 28962306a36Sopenharmony_ci struct bcm47xxsflash *b47s; 29062306a36Sopenharmony_ci struct resource *res; 29162306a36Sopenharmony_ci int err; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci b47s = devm_kzalloc(dev, sizeof(*b47s), GFP_KERNEL); 29462306a36Sopenharmony_ci if (!b47s) 29562306a36Sopenharmony_ci return -ENOMEM; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 29862306a36Sopenharmony_ci if (!res) { 29962306a36Sopenharmony_ci dev_err(dev, "invalid resource\n"); 30062306a36Sopenharmony_ci return -EINVAL; 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci if (!devm_request_mem_region(dev, res->start, resource_size(res), 30362306a36Sopenharmony_ci res->name)) { 30462306a36Sopenharmony_ci dev_err(dev, "can't request region for resource %pR\n", res); 30562306a36Sopenharmony_ci return -EBUSY; 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci b47s->bcma_cc = container_of(sflash, struct bcma_drv_cc, sflash); 30962306a36Sopenharmony_ci b47s->cc_read = bcm47xxsflash_bcma_cc_read; 31062306a36Sopenharmony_ci b47s->cc_write = bcm47xxsflash_bcma_cc_write; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* 31362306a36Sopenharmony_ci * On old MIPS devices cache was magically invalidated when needed, 31462306a36Sopenharmony_ci * allowing us to use cached access and gain some performance. Trying 31562306a36Sopenharmony_ci * the same on ARM based BCM53573 results in flash corruptions, we need 31662306a36Sopenharmony_ci * to use uncached access for it. 31762306a36Sopenharmony_ci * 31862306a36Sopenharmony_ci * It may be arch specific, but right now there is only 1 ARM SoC using 31962306a36Sopenharmony_ci * this driver, so let's follow Broadcom's reference code and check 32062306a36Sopenharmony_ci * ChipCommon revision. 32162306a36Sopenharmony_ci */ 32262306a36Sopenharmony_ci if (b47s->bcma_cc->core->id.rev == 54) 32362306a36Sopenharmony_ci b47s->window = ioremap(res->start, resource_size(res)); 32462306a36Sopenharmony_ci else 32562306a36Sopenharmony_ci b47s->window = ioremap_cache(res->start, resource_size(res)); 32662306a36Sopenharmony_ci if (!b47s->window) { 32762306a36Sopenharmony_ci dev_err(dev, "ioremap failed for resource %pR\n", res); 32862306a36Sopenharmony_ci return -ENOMEM; 32962306a36Sopenharmony_ci } 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci switch (b47s->bcma_cc->capabilities & BCMA_CC_CAP_FLASHT) { 33262306a36Sopenharmony_ci case BCMA_CC_FLASHT_STSER: 33362306a36Sopenharmony_ci b47s->type = BCM47XXSFLASH_TYPE_ST; 33462306a36Sopenharmony_ci break; 33562306a36Sopenharmony_ci case BCMA_CC_FLASHT_ATSER: 33662306a36Sopenharmony_ci b47s->type = BCM47XXSFLASH_TYPE_ATMEL; 33762306a36Sopenharmony_ci break; 33862306a36Sopenharmony_ci } 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci b47s->blocksize = sflash->blocksize; 34162306a36Sopenharmony_ci b47s->numblocks = sflash->numblocks; 34262306a36Sopenharmony_ci b47s->size = sflash->size; 34362306a36Sopenharmony_ci bcm47xxsflash_fill_mtd(b47s, &pdev->dev); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci platform_set_drvdata(pdev, b47s); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci err = mtd_device_parse_register(&b47s->mtd, probes, NULL, NULL, 0); 34862306a36Sopenharmony_ci if (err) { 34962306a36Sopenharmony_ci pr_err("Failed to register MTD device: %d\n", err); 35062306a36Sopenharmony_ci iounmap(b47s->window); 35162306a36Sopenharmony_ci return err; 35262306a36Sopenharmony_ci } 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci if (bcm47xxsflash_poll(b47s, HZ / 10)) 35562306a36Sopenharmony_ci pr_warn("Serial flash busy\n"); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci return 0; 35862306a36Sopenharmony_ci} 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_cistatic int bcm47xxsflash_bcma_remove(struct platform_device *pdev) 36162306a36Sopenharmony_ci{ 36262306a36Sopenharmony_ci struct bcm47xxsflash *b47s = platform_get_drvdata(pdev); 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci mtd_device_unregister(&b47s->mtd); 36562306a36Sopenharmony_ci iounmap(b47s->window); 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci return 0; 36862306a36Sopenharmony_ci} 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_cistatic struct platform_driver bcma_sflash_driver = { 37162306a36Sopenharmony_ci .probe = bcm47xxsflash_bcma_probe, 37262306a36Sopenharmony_ci .remove = bcm47xxsflash_bcma_remove, 37362306a36Sopenharmony_ci .driver = { 37462306a36Sopenharmony_ci .name = "bcma_sflash", 37562306a36Sopenharmony_ci }, 37662306a36Sopenharmony_ci}; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci/************************************************** 37962306a36Sopenharmony_ci * Init 38062306a36Sopenharmony_ci **************************************************/ 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_cimodule_platform_driver(bcma_sflash_driver); 383