@@ -41,6 +41,10 @@ void bcma_init_bus(struct bcma_bus *bus);
/* sprom.c */
int bcma_sprom_get(struct bcma_bus *bus);
+int bcma_sprom_valid(const u16 *sprom);
+void bcma_sprom_extract_r8(struct bcma_bus *bus, const u16 *sprom);
+int bcma_sprom_fromhex(u16 *sprom, const char *dump, size_t len);
+int bcma_sprom_tohex(const u16 *sprom, char *buf, size_t buf_len);
/* driver_chipcommon.c */
#ifdef CONFIG_BCMA_DRIVER_MIPS
@@ -22,6 +22,149 @@ static inline u32 bcma_cc_write32_masked(struct bcma_drv_cc *cc, u16 offset,
return value;
}
+static u16 bcma_cc_srom_cmd(struct bcma_device *cc, u32 cmd,
+ uint wordoff, u16 data)
+{
+ u16 res = 0xffff;
+ uint wait_cnt = 1000;
+
+ if ((cmd == BCMA_CC_SROM_CONTROL_OP_READ) ||
+ (cmd == BCMA_CC_SROM_CONTROL_OP_WRITE)) {
+ bcma_write32(cc, BCMA_CC_SROM_ADDRESS, wordoff * 2);
+ if (cmd == BCMA_CC_SROM_CONTROL_OP_WRITE)
+ bcma_write32(cc, BCMA_CC_SROM_DATA, data);
+ }
+
+ bcma_write32(cc, BCMA_CC_SROM_CONTROL,
+ BCMA_CC_SROM_CONTROL_START | cmd);
+
+ while (wait_cnt--) {
+ uint tmp = bcma_read32(cc, BCMA_CC_SROM_CONTROL);
+ if ((tmp & BCMA_CC_SROM_CONTROL_BUSY) == 0)
+ break;
+ }
+
+ if (!wait_cnt)
+ bcma_warn(cc->bus, "timed out waiting for busy to clear.\n");
+ else if (cmd == BCMA_CC_SROM_CONTROL_OP_READ)
+ res = (u16)bcma_read32(cc, BCMA_CC_SROM_DATA);
+
+ return res;
+}
+
+static ssize_t bcma_core_cc_attr_sprom_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u16 *sprom;
+ int i;
+ ssize_t res = -ERESTARTSYS;
+
+ struct bcma_device *cc = container_of(dev, struct bcma_device, dev);
+
+ sprom = kcalloc(SSB_SPROMSIZE_WORDS_R4, sizeof(u16),
+ GFP_KERNEL);
+ if (!sprom)
+ return -ENOMEM;
+
+ if (mutex_lock_interruptible(&cc->dev.mutex))
+ goto out_kfree;
+
+ if (cc->bus->chipinfo.id == BCMA_CHIP_ID_BCM4331 ||
+ cc->bus->chipinfo.id == BCMA_CHIP_ID_BCM43431)
+ bcma_chipco_bcm4331_ext_pa_lines_ctl(&cc->bus->drv_cc,
+ false);
+
+ for (i = 0; i < SSB_SPROMSIZE_WORDS_R4; i++)
+ sprom[i] = bcma_cc_srom_cmd(cc, BCMA_CC_SROM_CONTROL_OP_READ,
+ i, 0);
+
+ if (cc->bus->chipinfo.id == BCMA_CHIP_ID_BCM4331 ||
+ cc->bus->chipinfo.id == BCMA_CHIP_ID_BCM43431)
+ bcma_chipco_bcm4331_ext_pa_lines_ctl(&cc->bus->drv_cc,
+ true);
+
+ mutex_unlock(&cc->dev.mutex);
+
+ res = bcma_sprom_tohex(sprom, buf, PAGE_SIZE);
+
+out_kfree:
+ kfree(sprom);
+
+ return res;
+}
+
+static ssize_t bcma_core_cc_attr_sprom_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u16 *sprom;
+ int err, i;
+
+ struct bcma_device *core = container_of(dev, struct bcma_device, dev);
+
+ sprom = kcalloc(SSB_SPROMSIZE_WORDS_R4, sizeof(u16), GFP_KERNEL);
+ if (!sprom)
+ return -ENOMEM;
+
+ err = bcma_sprom_fromhex(sprom, buf, count);
+ if (!err)
+ err = bcma_sprom_valid(sprom);
+ if (err)
+ goto out_kfree;
+
+ if (mutex_lock_interruptible(&core->dev.mutex)) {
+ err = -ERESTARTSYS;
+ goto out_kfree;
+ }
+
+ if (core->bus->chipinfo.id == BCMA_CHIP_ID_BCM4331 ||
+ core->bus->chipinfo.id == BCMA_CHIP_ID_BCM43431)
+ bcma_chipco_bcm4331_ext_pa_lines_ctl(&core->bus->drv_cc,
+ false);
+
+ bcma_warn(core->bus, "Writing SPROM. Do NOT turn off the power!\n");
+
+ bcma_cc_srom_cmd(core, BCMA_CC_SROM_CONTROL_OP_WREN, 0, 0);
+
+ msleep(500);
+
+ for (i = 0; i < SSB_SPROMSIZE_WORDS_R4; i++) {
+ if (i == SSB_SPROMSIZE_WORDS_R4 / 4)
+ bcma_warn(core->bus, "SPROM write 25%% complete.\n");
+ else if (i == SSB_SPROMSIZE_WORDS_R4 / 2)
+ bcma_warn(core->bus, "SPROM write 50%% complete.\n");
+ else if (i == (SSB_SPROMSIZE_WORDS_R4 * 3) / 4)
+ bcma_warn(core->bus, "SPROM write 75%% complete.\n");
+ bcma_cc_srom_cmd(core, BCMA_CC_SROM_CONTROL_OP_WRITE,
+ i, sprom[i]);
+ msleep(20);
+ }
+
+ bcma_cc_srom_cmd(core, BCMA_CC_SROM_CONTROL_OP_WRDIS, 0, 0);
+
+ msleep(500);
+
+ bcma_warn(core->bus, "SPROM wrte complete.\n");
+
+ if (core->bus->chipinfo.id == BCMA_CHIP_ID_BCM4331 ||
+ core->bus->chipinfo.id == BCMA_CHIP_ID_BCM43431)
+ bcma_chipco_bcm4331_ext_pa_lines_ctl(&core->bus->drv_cc,
+ true);
+
+ mutex_unlock(&core->dev.mutex);
+
+ bcma_sprom_extract_r8(core->bus, sprom);
+
+out_kfree:
+ kfree(sprom);
+
+ return err ? err : count;
+}
+
+static DEVICE_ATTR(sprom, 0600, bcma_core_cc_attr_sprom_show,
+ bcma_core_cc_attr_sprom_store);
+
static int bcma_core_cc_initialize(struct bcma_device *core)
{
u32 leddc_on = 10;
@@ -60,6 +203,23 @@ static int bcma_core_cc_initialize(struct bcma_device *core)
return 0;
}
+static int bcma_core_cc_probe(struct bcma_device *core)
+{
+ bcma_core_cc_initialize(core);
+ if (core->id.rev >= 31 &&
+ core->bus->drv_cc.capabilities & BCMA_CC_CAP_SPROM)
+ device_create_file(&core->dev, &dev_attr_sprom);
+
+ return 0;
+}
+
+static void bcma_core_cc_remove(struct bcma_device *core)
+{
+ if (core->id.rev >= 31 &&
+ core->bus->drv_cc.capabilities & BCMA_CC_CAP_SPROM)
+ device_remove_file(&core->dev, &dev_attr_sprom);
+}
+
static struct bcma_device_id bcma_core_cc_id_table[] = {
BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_CHIPCOMMON,
BCMA_ANY_REV, BCMA_ANY_CLASS),
@@ -70,8 +230,9 @@ static struct bcma_device_id bcma_core_cc_id_table[] = {
struct bcma_driver bcma_core_cc_driver = {
.name = "bcma-cc-core",
- .probe = bcma_core_cc_initialize,
+ .probe = bcma_core_cc_probe,
.id_table = bcma_core_cc_id_table,
+ .remove = bcma_core_cc_remove,
#ifdef CONFIG_PM
.resume = bcma_core_cc_initialize,
#endif
@@ -15,6 +15,7 @@
#include <linux/io.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
+#include <linux/ctype.h>
static int(*get_fallback_sprom)(struct bcma_bus *dev, struct ssb_sprom *out);
@@ -154,7 +155,7 @@ static int bcma_sprom_check_crc(const u16 *sprom)
return 0;
}
-static int bcma_sprom_valid(const u16 *sprom)
+int bcma_sprom_valid(const u16 *sprom)
{
u16 revision;
int err;
@@ -197,7 +198,7 @@ static int bcma_sprom_valid(const u16 *sprom)
SPEX(_field[7], _offset + 14, _mask, _shift); \
} while (0)
-static void bcma_sprom_extract_r8(struct bcma_bus *bus, const u16 *sprom)
+void bcma_sprom_extract_r8(struct bcma_bus *bus, const u16 *sprom)
{
u16 v, o;
int i;
@@ -602,3 +603,45 @@ out:
kfree(sprom);
return err;
}
+
+int bcma_sprom_tohex(const u16 *sprom, char *buf, size_t buf_len)
+{
+ int i, pos = 0;
+
+ for (i = 0; i < SSB_SPROMSIZE_WORDS_R4; i++)
+ pos += snprintf(buf + pos, buf_len - pos - 1,
+ "%04X", swab16(sprom[i]) & 0xFFFF);
+ pos += snprintf(buf + pos, buf_len - pos - 1, "\n");
+
+ return pos + 1;
+}
+
+int bcma_sprom_fromhex(u16 *sprom, const char *dump, size_t len)
+{
+ char c, tmp[5] = { 0 };
+ int err, cnt = 0;
+ unsigned long parsed;
+
+ /* Strip whitespace at the end. */
+ while (len) {
+ c = dump[len - 1];
+ if (!isspace(c) && c != '\0')
+ break;
+ len--;
+ }
+
+ /* Length must match exactly. */
+ if (len != SSB_SPROMSIZE_WORDS_R4 * 4)
+ return -EINVAL;
+
+ while (cnt < SSB_SPROMSIZE_WORDS_R4) {
+ memcpy(tmp, dump, 4);
+ dump += 4;
+ err = kstrtoul(tmp, 16, &parsed);
+ if (err)
+ return err;
+ sprom[cnt++] = swab16((u16)parsed);
+ }
+
+ return 0;
+}
@@ -251,6 +251,8 @@
#define BCMA_CC_FLASH_CFG_DS 0x0010 /* Data size, 0=8bit, 1=16bit */
#define BCMA_CC_FLASH_WAITCNT 0x012C
#define BCMA_CC_SROM_CONTROL 0x0190
+#define BCMA_CC_SROM_ADDRESS 0x0194
+#define BCMA_CC_SROM_DATA 0x0198
#define BCMA_CC_SROM_CONTROL_START 0x80000000
#define BCMA_CC_SROM_CONTROL_BUSY 0x80000000
#define BCMA_CC_SROM_CONTROL_OPCODE 0x60000000
On BCMA devices with a ChipCommon core of revision 31 or higher, the device SPROM can be accessed through CC core registers. This patch exposes the SPROM on such devices for read/write access as a sysfs attribute. Tested on a MacBookPro8,2 with BCM4331. Signed-off-by: Saul St. John <saul.stjohn@gmail.com> --- drivers/bcma/bcma_private.h | 4 + drivers/bcma/driver_chipcommon.c | 163 ++++++++++++++++++++++++++- drivers/bcma/sprom.c | 47 +++++++- include/linux/bcma/bcma_driver_chipcommon.h | 2 + 4 files changed, 213 insertions(+), 3 deletions(-)