diff options
Diffstat (limited to 'target/linux/brcm47xx/patches-3.9/060-ssb-add-serial-flash-driver.patch')
-rw-r--r-- | target/linux/brcm47xx/patches-3.9/060-ssb-add-serial-flash-driver.patch | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/target/linux/brcm47xx/patches-3.9/060-ssb-add-serial-flash-driver.patch b/target/linux/brcm47xx/patches-3.9/060-ssb-add-serial-flash-driver.patch new file mode 100644 index 0000000000..1148a489db --- /dev/null +++ b/target/linux/brcm47xx/patches-3.9/060-ssb-add-serial-flash-driver.patch @@ -0,0 +1,372 @@ +--- a/drivers/ssb/Kconfig ++++ b/drivers/ssb/Kconfig +@@ -139,7 +139,7 @@ config SSB_DRIVER_MIPS + + config SSB_SFLASH + bool "SSB serial flash support" +- depends on SSB_DRIVER_MIPS && BROKEN ++ depends on SSB_DRIVER_MIPS + default y + + # Assumption: We are on embedded, if we compile the MIPS core. +--- a/drivers/ssb/driver_chipcommon_sflash.c ++++ b/drivers/ssb/driver_chipcommon_sflash.c +@@ -1,14 +1,35 @@ + /* + * Sonics Silicon Backplane + * ChipCommon serial flash interface ++ * Copyright 2011, Jonas Gorski <jonas.gorski@gmail.com> ++ * Copyright 2011, 2012, Hauke Mehrtens <hauke@hauke-m.de> ++ * Copyright 2010, Broadcom Corporation + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + ++#include <linux/platform_device.h> ++#include <linux/delay.h> + #include <linux/ssb/ssb.h> ++#include <linux/ssb/ssb_driver_chipcommon.h> + + #include "ssb_private.h" + ++#define NUM_RETRIES 3 ++ ++static struct resource ssb_sflash_resource = { ++ .name = "ssb_sflash", ++ .start = SSB_FLASH2, ++ .end = 0, ++ .flags = IORESOURCE_MEM | IORESOURCE_READONLY, ++}; ++ ++struct platform_device ssb_sflash_dev = { ++ .name = "bcm47xx-sflash", ++ .resource = &ssb_sflash_resource, ++ .num_resources = 1, ++}; ++ + struct ssb_sflash_tbl_e { + char *name; + u32 id; +@@ -16,7 +37,7 @@ struct ssb_sflash_tbl_e { + u16 numblocks; + }; + +-static struct ssb_sflash_tbl_e ssb_sflash_st_tbl[] = { ++static const struct ssb_sflash_tbl_e ssb_sflash_st_tbl[] = { + { "M25P20", 0x11, 0x10000, 4, }, + { "M25P40", 0x12, 0x10000, 8, }, + +@@ -27,7 +48,7 @@ static struct ssb_sflash_tbl_e ssb_sflas + { 0 }, + }; + +-static struct ssb_sflash_tbl_e ssb_sflash_sst_tbl[] = { ++static const struct ssb_sflash_tbl_e ssb_sflash_sst_tbl[] = { + { "SST25WF512", 1, 0x1000, 16, }, + { "SST25VF512", 0x48, 0x1000, 16, }, + { "SST25WF010", 2, 0x1000, 32, }, +@@ -45,7 +66,7 @@ static struct ssb_sflash_tbl_e ssb_sflas + { 0 }, + }; + +-static struct ssb_sflash_tbl_e ssb_sflash_at_tbl[] = { ++static const struct ssb_sflash_tbl_e ssb_sflash_at_tbl[] = { + { "AT45DB011", 0xc, 256, 512, }, + { "AT45DB021", 0x14, 256, 1024, }, + { "AT45DB041", 0x1c, 256, 2048, }, +@@ -70,10 +91,186 @@ static void ssb_sflash_cmd(struct ssb_ch + pr_err("SFLASH control command failed (timeout)!\n"); + } + ++static void ssb_sflash_write_u8(struct ssb_chipcommon *chipco, u32 offset, u8 byte) ++{ ++ chipco_write32(chipco, SSB_CHIPCO_FLASHADDR, offset); ++ chipco_write32(chipco, SSB_CHIPCO_FLASHDATA, byte); ++} ++ ++/* Poll for command completion. Returns zero when complete. */ ++static int ssb_sflash_poll(struct bcm47xxsflash *dev, u32 offset) ++{ ++ struct ssb_chipcommon *chipco = dev->scc; ++ ++ if (offset >= chipco->sflash.size) ++ return -22; ++ ++ switch (chipco->capabilities & SSB_CHIPCO_CAP_FLASHT) { ++ case SSB_CHIPCO_FLASHT_STSER: ++ /* Check for ST Write In Progress bit */ ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_ST_RDSR); ++ return chipco_read32(chipco, SSB_CHIPCO_FLASHDATA) ++ & SSB_CHIPCO_FLASHDATA_ST_WIP; ++ case SSB_CHIPCO_FLASHT_ATSER: ++ /* Check for Atmel Ready bit */ ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_AT_STATUS); ++ return !(chipco_read32(chipco, SSB_CHIPCO_FLASHDATA) ++ & SSB_CHIPCO_FLASHDATA_AT_READY); ++ } ++ ++ return 0; ++} ++ ++ ++static int sflash_st_write(struct bcm47xxsflash *dev, u32 offset, u32 len, ++ const u8 *buf) ++{ ++ int written = 1; ++ struct ssb_chipcommon *chipco = dev->scc; ++ ++ /* Enable writes */ ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_ST_WREN); ++ ssb_sflash_write_u8(chipco, offset, *buf++); ++ /* Issue a page program with CSA bit set */ ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_ST_CSA | SSB_CHIPCO_FLASHCTL_ST_PP); ++ offset++; ++ len--; ++ while (len > 0) { ++ if ((offset & 255) == 0) { ++ /* Page boundary, poll droping cs and return */ ++ chipco_write32(chipco, SSB_CHIPCO_FLASHCTL, 0); ++ udelay(1); ++ if (!ssb_sflash_poll(dev, offset)) { ++ /* Flash rejected command */ ++ return -EAGAIN; ++ } ++ return written; ++ } else { ++ /* Write single byte */ ++ ssb_sflash_cmd(chipco, ++ SSB_CHIPCO_FLASHCTL_ST_CSA | ++ *buf++); ++ } ++ written++; ++ offset++; ++ len--; ++ } ++ /* All done, drop cs & poll */ ++ chipco_write32(chipco, SSB_CHIPCO_FLASHCTL, 0); ++ udelay(1); ++ if (!ssb_sflash_poll(dev, offset)) { ++ /* Flash rejected command */ ++ return -EAGAIN; ++ } ++ return written; ++} ++ ++static int sflash_at_write(struct bcm47xxsflash *dev, u32 offset, u32 len, ++ const u8 *buf) ++{ ++ struct ssb_chipcommon *chipco = dev->scc; ++ u32 page, byte, mask; ++ int ret = 0; ++ ++ mask = dev->blocksize - 1; ++ page = (offset & ~mask) << 1; ++ byte = offset & mask; ++ /* Read main memory page into buffer 1 */ ++ if (byte || (len < dev->blocksize)) { ++ int i = 100; ++ chipco_write32(chipco, SSB_CHIPCO_FLASHADDR, page); ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_AT_BUF1_LOAD); ++ /* 250 us for AT45DB321B */ ++ while (i > 0 && ssb_sflash_poll(dev, offset)) { ++ udelay(10); ++ i--; ++ } ++ BUG_ON(!ssb_sflash_poll(dev, offset)); ++ } ++ /* Write into buffer 1 */ ++ for (ret = 0; (ret < (int)len) && (byte < dev->blocksize); ret++) { ++ ssb_sflash_write_u8(chipco, byte++, *buf++); ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_AT_BUF1_WRITE); ++ } ++ /* Write buffer 1 into main memory page */ ++ chipco_write32(chipco, SSB_CHIPCO_FLASHADDR, page); ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_AT_BUF1_PROGRAM); ++ ++ return ret; ++} ++ ++/* Write len bytes starting at offset into buf. Returns number of bytes ++ * written. Caller should poll for completion. ++ */ ++static int ssb_sflash_write(struct bcm47xxsflash *dev, u32 offset, u32 len, ++ const u8 *buf) ++{ ++ int ret = 0, tries = NUM_RETRIES; ++ struct ssb_chipcommon *chipco = dev->scc; ++ ++ if (!len) ++ return 0; ++ ++ if ((offset + len) > chipco->sflash.size) ++ return -EINVAL; ++ ++ switch (chipco->capabilities & SSB_CHIPCO_CAP_FLASHT) { ++ case SSB_CHIPCO_FLASHT_STSER: ++ do { ++ ret = sflash_st_write(dev, offset, len, buf); ++ tries--; ++ } while (ret == -EAGAIN && tries > 0); ++ ++ if (ret == -EAGAIN && tries == 0) { ++ pr_info("ST Flash rejected write\n"); ++ ret = -EIO; ++ } ++ break; ++ case SSB_CHIPCO_FLASHT_ATSER: ++ ret = sflash_at_write(dev, offset, len, buf); ++ break; ++ } ++ ++ return ret; ++} ++ ++/* Erase a region. Returns number of bytes scheduled for erasure. ++ * Caller should poll for completion. ++ */ ++static int ssb_sflash_erase(struct bcm47xxsflash *dev, u32 offset) ++{ ++ struct ssb_chipcommon *chipco = dev->scc; ++ ++ if (offset >= chipco->sflash.size) ++ return -EINVAL; ++ ++ switch (chipco->capabilities & SSB_CHIPCO_CAP_FLASHT) { ++ case SSB_CHIPCO_FLASHT_STSER: ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_ST_WREN); ++ chipco_write32(chipco, SSB_CHIPCO_FLASHADDR, offset); ++ /* Newer flashes have "sub-sectors" which can be erased independently ++ * with a new command: ST_SSE. The ST_SE command erases 64KB just as ++ * before. ++ */ ++ if (dev->blocksize < (64 * 1024)) ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_ST_SSE); ++ else ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_ST_SE); ++ return dev->blocksize; ++ case SSB_CHIPCO_FLASHT_ATSER: ++ chipco_write32(chipco, SSB_CHIPCO_FLASHADDR, offset << 1); ++ ssb_sflash_cmd(chipco, SSB_CHIPCO_FLASHCTL_AT_PAGE_ERASE); ++ return dev->blocksize; ++ } ++ ++ return 0; ++} ++ + /* Initialize serial flash access */ + int ssb_sflash_init(struct ssb_chipcommon *cc) + { +- struct ssb_sflash_tbl_e *e; ++ struct bcm47xxsflash *sflash = &cc->sflash; ++ const struct ssb_sflash_tbl_e *e; + u32 id, id2; + + switch (cc->capabilities & SSB_CHIPCO_CAP_FLASHT) { +@@ -131,10 +328,26 @@ int ssb_sflash_init(struct ssb_chipcommo + return -ENOTSUPP; + } + +- pr_info("Found %s serial flash (blocksize: 0x%X, blocks: %d)\n", +- e->name, e->blocksize, e->numblocks); +- +- pr_err("Serial flash support is not implemented yet!\n"); ++ sflash->window = SSB_FLASH2; ++ sflash->blocksize = e->blocksize; ++ sflash->numblocks = e->numblocks; ++ sflash->size = sflash->blocksize * sflash->numblocks; ++ sflash->present = true; ++ sflash->poll = ssb_sflash_poll; ++ sflash->write = ssb_sflash_write; ++ sflash->erase = ssb_sflash_erase; ++ sflash->type = BCM47XX_SFLASH_SSB; ++ sflash->scc = cc; ++ ++ pr_info("Found %s serial flash (size: %dKiB, blocksize: 0x%X, blocks: %d)\n", ++ e->name, sflash->size / 1024, sflash->blocksize, ++ sflash->numblocks); ++ ++ /* Prepare platform device, but don't register it yet. It's too early, ++ * malloc (required by device_private_init) is not available yet. */ ++ ssb_sflash_dev.resource[0].end = ssb_sflash_dev.resource[0].start + ++ sflash->size; ++ ssb_sflash_dev.dev.platform_data = sflash; + +- return -ENOTSUPP; ++ return 0; + } +--- a/drivers/ssb/main.c ++++ b/drivers/ssb/main.c +@@ -545,6 +545,15 @@ static int ssb_devices_register(struct s + dev_idx++; + } + ++#ifdef CONFIG_SSB_SFLASH ++ if (bus->chipco.sflash.present) { ++ err = platform_device_register(&ssb_sflash_dev); ++ if (err) ++ ssb_printk(KERN_ERR PFX ++ "Error registering serial flash\n"); ++ } ++#endif ++ + #ifdef CONFIG_SSB_DRIVER_MIPS + if (bus->mipscore.pflash.present) { + err = platform_device_register(&ssb_pflash_dev); +--- a/drivers/ssb/ssb_private.h ++++ b/drivers/ssb/ssb_private.h +@@ -231,6 +231,7 @@ extern u32 ssb_chipco_watchdog_timer_set + /* driver_chipcommon_sflash.c */ + #ifdef CONFIG_SSB_SFLASH + int ssb_sflash_init(struct ssb_chipcommon *cc); ++extern struct platform_device ssb_sflash_dev; + #else + static inline int ssb_sflash_init(struct ssb_chipcommon *cc) + { +--- a/include/linux/ssb/ssb_driver_chipcommon.h ++++ b/include/linux/ssb/ssb_driver_chipcommon.h +@@ -13,6 +13,8 @@ + * Licensed under the GPL version 2. See COPYING for details. + */ + ++#include <linux/mtd/bcm47xxsflash.h> ++ + /** ChipCommon core registers. **/ + + #define SSB_CHIPCO_CHIPID 0x0000 +@@ -121,6 +123,17 @@ + #define SSB_CHIPCO_FLASHCTL_BUSY SSB_CHIPCO_FLASHCTL_START + #define SSB_CHIPCO_FLASHADDR 0x0044 + #define SSB_CHIPCO_FLASHDATA 0x0048 ++/* Status register bits for ST flashes */ ++#define SSB_CHIPCO_FLASHDATA_ST_WIP 0x01 /* Write In Progress */ ++#define SSB_CHIPCO_FLASHDATA_ST_WEL 0x02 /* Write Enable Latch */ ++#define SSB_CHIPCO_FLASHDATA_ST_BP_MASK 0x1c /* Block Protect */ ++#define SSB_CHIPCO_FLASHDATA_ST_BP_SHIFT 2 ++#define SSB_CHIPCO_FLASHDATA_ST_SRWD 0x80 /* Status Register Write Disable */ ++/* Status register bits for Atmel flashes */ ++#define SSB_CHIPCO_FLASHDATA_AT_READY 0x80 ++#define SSB_CHIPCO_FLASHDATA_AT_MISMATCH 0x40 ++#define SSB_CHIPCO_FLASHDATA_AT_ID_MASK 0x38 ++#define SSB_CHIPCO_FLASHDATA_AT_ID_SHIFT 3 + #define SSB_CHIPCO_BCAST_ADDR 0x0050 + #define SSB_CHIPCO_BCAST_DATA 0x0054 + #define SSB_CHIPCO_GPIOPULLUP 0x0058 /* Rev >= 20 only */ +@@ -504,7 +517,7 @@ + #define SSB_CHIPCO_FLASHCTL_ST_PP 0x0302 /* Page Program */ + #define SSB_CHIPCO_FLASHCTL_ST_SE 0x02D8 /* Sector Erase */ + #define SSB_CHIPCO_FLASHCTL_ST_BE 0x00C7 /* Bulk Erase */ +-#define SSB_CHIPCO_FLASHCTL_ST_DP 0x00B9 /* Deep Power-down */ ++#define SSB_CHIPCO_FLASHCTL_ST_DP 0x00D9 /* Deep Power-down */ + #define SSB_CHIPCO_FLASHCTL_ST_RES 0x03AB /* Read Electronic Signature */ + #define SSB_CHIPCO_FLASHCTL_ST_CSA 0x1000 /* Keep chip select asserted */ + #define SSB_CHIPCO_FLASHCTL_ST_SSE 0x0220 /* Sub-sector Erase */ +@@ -595,6 +608,9 @@ struct ssb_chipcommon { + struct ssb_chipcommon_pmu pmu; + u32 ticks_per_ms; + u32 max_timer_ms; ++#ifdef CONFIG_SSB_SFLASH ++ struct bcm47xxsflash sflash; ++#endif + }; + + static inline bool ssb_chipco_available(struct ssb_chipcommon *cc) |