X-Git-Url: https://git.openpandora.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=drivers%2Fssb%2Fpcmcia.c;h=24c2a46c14760cbb879f1e367aff8774e01ac7b2;hb=4bb0057f996b1491f93a64879f4c53c83bc0f0c7;hp=b6abee846f023a81af3a4ecf3fb015ac642a5e53;hpb=fb9fc395174138983a49f2da982ed14caabbe741;p=pandora-kernel.git diff --git a/drivers/ssb/pcmcia.c b/drivers/ssb/pcmcia.c index b6abee846f02..24c2a46c1476 100644 --- a/drivers/ssb/pcmcia.c +++ b/drivers/ssb/pcmcia.c @@ -3,7 +3,7 @@ * PCMCIA-Hostbus related functions * * Copyright 2006 Johannes Berg - * Copyright 2007 Michael Buesch + * Copyright 2007-2008 Michael Buesch * * Licensed under the GNU/GPL. See COPYING for details. */ @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -26,59 +27,127 @@ #define SSB_VERBOSE_PCMCIACORESWITCH_DEBUG 0 +/* PCMCIA configuration registers */ +#define SSB_PCMCIA_ADDRESS0 0x2E +#define SSB_PCMCIA_ADDRESS1 0x30 +#define SSB_PCMCIA_ADDRESS2 0x32 +#define SSB_PCMCIA_MEMSEG 0x34 +#define SSB_PCMCIA_SPROMCTL 0x36 +#define SSB_PCMCIA_SPROMCTL_IDLE 0 +#define SSB_PCMCIA_SPROMCTL_WRITE 1 +#define SSB_PCMCIA_SPROMCTL_READ 2 +#define SSB_PCMCIA_SPROMCTL_WRITEEN 4 +#define SSB_PCMCIA_SPROMCTL_WRITEDIS 7 +#define SSB_PCMCIA_SPROMCTL_DONE 8 +#define SSB_PCMCIA_SPROM_DATALO 0x38 +#define SSB_PCMCIA_SPROM_DATAHI 0x3A +#define SSB_PCMCIA_SPROM_ADDRLO 0x3C +#define SSB_PCMCIA_SPROM_ADDRHI 0x3E + +/* Hardware invariants CIS tuples */ +#define SSB_PCMCIA_CIS 0x80 +#define SSB_PCMCIA_CIS_ID 0x01 +#define SSB_PCMCIA_CIS_BOARDREV 0x02 +#define SSB_PCMCIA_CIS_PA 0x03 +#define SSB_PCMCIA_CIS_PA_PA0B0_LO 0 +#define SSB_PCMCIA_CIS_PA_PA0B0_HI 1 +#define SSB_PCMCIA_CIS_PA_PA0B1_LO 2 +#define SSB_PCMCIA_CIS_PA_PA0B1_HI 3 +#define SSB_PCMCIA_CIS_PA_PA0B2_LO 4 +#define SSB_PCMCIA_CIS_PA_PA0B2_HI 5 +#define SSB_PCMCIA_CIS_PA_ITSSI 6 +#define SSB_PCMCIA_CIS_PA_MAXPOW 7 +#define SSB_PCMCIA_CIS_OEMNAME 0x04 +#define SSB_PCMCIA_CIS_CCODE 0x05 +#define SSB_PCMCIA_CIS_ANTENNA 0x06 +#define SSB_PCMCIA_CIS_ANTGAIN 0x07 +#define SSB_PCMCIA_CIS_BFLAGS 0x08 +#define SSB_PCMCIA_CIS_LEDS 0x09 + +/* PCMCIA SPROM size. */ +#define SSB_PCMCIA_SPROM_SIZE 256 +#define SSB_PCMCIA_SPROM_SIZE_BYTES (SSB_PCMCIA_SPROM_SIZE * sizeof(u16)) + + +/* Write to a PCMCIA configuration register. */ +static int ssb_pcmcia_cfg_write(struct ssb_bus *bus, u8 offset, u8 value) +{ + conf_reg_t reg; + int res; + + memset(®, 0, sizeof(reg)); + reg.Offset = offset; + reg.Action = CS_WRITE; + reg.Value = value; + res = pcmcia_access_configuration_register(bus->host_pcmcia, ®); + if (unlikely(res != CS_SUCCESS)) + return -EBUSY; + + return 0; +} + +/* Read from a PCMCIA configuration register. */ +static int ssb_pcmcia_cfg_read(struct ssb_bus *bus, u8 offset, u8 *value) +{ + conf_reg_t reg; + int res; + + memset(®, 0, sizeof(reg)); + reg.Offset = offset; + reg.Action = CS_READ; + res = pcmcia_access_configuration_register(bus->host_pcmcia, ®); + if (unlikely(res != CS_SUCCESS)) + return -EBUSY; + *value = reg.Value; + + return 0; +} + int ssb_pcmcia_switch_coreidx(struct ssb_bus *bus, u8 coreidx) { - struct pcmcia_device *pdev = bus->host_pcmcia; int err; int attempts = 0; u32 cur_core; - conf_reg_t reg; u32 addr; u32 read_addr; + u8 val; addr = (coreidx * SSB_CORE_SIZE) + SSB_ENUM_BASE; while (1) { - reg.Action = CS_WRITE; - reg.Offset = 0x2E; - reg.Value = (addr & 0x0000F000) >> 12; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_ADDRESS0, + (addr & 0x0000F000) >> 12); + if (err) goto error; - reg.Offset = 0x30; - reg.Value = (addr & 0x00FF0000) >> 16; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_ADDRESS1, + (addr & 0x00FF0000) >> 16); + if (err) goto error; - reg.Offset = 0x32; - reg.Value = (addr & 0xFF000000) >> 24; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_ADDRESS2, + (addr & 0xFF000000) >> 24); + if (err) goto error; read_addr = 0; - reg.Action = CS_READ; - reg.Offset = 0x2E; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_ADDRESS0, &val); + if (err) goto error; - read_addr |= (reg.Value & 0xF) << 12; - reg.Offset = 0x30; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + read_addr |= ((u32)(val & 0x0F)) << 12; + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_ADDRESS1, &val); + if (err) goto error; - read_addr |= reg.Value << 16; - reg.Offset = 0x32; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + read_addr |= ((u32)val) << 16; + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_ADDRESS2, &val); + if (err) goto error; - read_addr |= reg.Value << 24; + read_addr |= ((u32)val) << 24; cur_core = (read_addr - SSB_ENUM_BASE) / SSB_CORE_SIZE; if (cur_core == coreidx) break; + err = -ETIMEDOUT; if (attempts++ > SSB_BAR0_MAX_RETRIES) goto error; udelay(10); @@ -87,14 +156,13 @@ int ssb_pcmcia_switch_coreidx(struct ssb_bus *bus, return 0; error: ssb_printk(KERN_ERR PFX "Failed to switch to core %u\n", coreidx); - return -ENODEV; + return err; } int ssb_pcmcia_switch_core(struct ssb_bus *bus, struct ssb_device *dev) { int err; - unsigned long flags; #if SSB_VERBOSE_PCMCIACORESWITCH_DEBUG ssb_printk(KERN_INFO PFX @@ -103,11 +171,9 @@ int ssb_pcmcia_switch_core(struct ssb_bus *bus, dev->core_index); #endif - spin_lock_irqsave(&bus->bar_lock, flags); err = ssb_pcmcia_switch_coreidx(bus, dev->core_index); if (!err) bus->mapped_device = dev; - spin_unlock_irqrestore(&bus->bar_lock, flags); return err; } @@ -115,132 +181,631 @@ int ssb_pcmcia_switch_core(struct ssb_bus *bus, int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg) { int attempts = 0; - unsigned long flags; - conf_reg_t reg; - int res, err = 0; + int err; + u8 val; SSB_WARN_ON((seg != 0) && (seg != 1)); - reg.Offset = 0x34; - reg.Function = 0; - spin_lock_irqsave(&bus->bar_lock, flags); while (1) { - reg.Action = CS_WRITE; - reg.Value = seg; - res = pcmcia_access_configuration_register(bus->host_pcmcia, ®); - if (unlikely(res != CS_SUCCESS)) + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_MEMSEG, seg); + if (err) goto error; - reg.Value = 0xFF; - reg.Action = CS_READ; - res = pcmcia_access_configuration_register(bus->host_pcmcia, ®); - if (unlikely(res != CS_SUCCESS)) + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_MEMSEG, &val); + if (err) goto error; - - if (reg.Value == seg) + if (val == seg) break; + err = -ETIMEDOUT; if (unlikely(attempts++ > SSB_BAR0_MAX_RETRIES)) goto error; udelay(10); } bus->mapped_pcmcia_seg = seg; -out_unlock: - spin_unlock_irqrestore(&bus->bar_lock, flags); - return err; + + return 0; error: ssb_printk(KERN_ERR PFX "Failed to switch pcmcia segment\n"); - err = -ENODEV; - goto out_unlock; + return err; } -/* These are the main device register access functions. - * do_select_core is inline to have the likely hotpath inline. - * All unlikely codepaths are out-of-line. */ -static inline int do_select_core(struct ssb_bus *bus, - struct ssb_device *dev, - u16 *offset) +static int select_core_and_segment(struct ssb_device *dev, + u16 *offset) { + struct ssb_bus *bus = dev->bus; int err; - u8 need_seg = (*offset >= 0x800) ? 1 : 0; + u8 need_segment; + + if (*offset >= 0x800) { + *offset -= 0x800; + need_segment = 1; + } else + need_segment = 0; if (unlikely(dev != bus->mapped_device)) { err = ssb_pcmcia_switch_core(bus, dev); if (unlikely(err)) return err; } - if (unlikely(need_seg != bus->mapped_pcmcia_seg)) { - err = ssb_pcmcia_switch_segment(bus, need_seg); + if (unlikely(need_segment != bus->mapped_pcmcia_seg)) { + err = ssb_pcmcia_switch_segment(bus, need_segment); if (unlikely(err)) return err; } - if (need_seg == 1) - *offset -= 0x800; return 0; } +static u8 ssb_pcmcia_read8(struct ssb_device *dev, u16 offset) +{ + struct ssb_bus *bus = dev->bus; + unsigned long flags; + int err; + u8 value = 0xFF; + + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (likely(!err)) + value = readb(bus->mmio + offset); + spin_unlock_irqrestore(&bus->bar_lock, flags); + + return value; +} + static u16 ssb_pcmcia_read16(struct ssb_device *dev, u16 offset) { struct ssb_bus *bus = dev->bus; - u16 x; + unsigned long flags; + int err; + u16 value = 0xFFFF; - if (unlikely(do_select_core(bus, dev, &offset))) - return 0xFFFF; - x = readw(bus->mmio + offset); + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (likely(!err)) + value = readw(bus->mmio + offset); + spin_unlock_irqrestore(&bus->bar_lock, flags); - return x; + return value; } static u32 ssb_pcmcia_read32(struct ssb_device *dev, u16 offset) { struct ssb_bus *bus = dev->bus; - u32 x; + unsigned long flags; + int err; + u32 lo = 0xFFFFFFFF, hi = 0xFFFFFFFF; + + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (likely(!err)) { + lo = readw(bus->mmio + offset); + hi = readw(bus->mmio + offset + 2); + } + spin_unlock_irqrestore(&bus->bar_lock, flags); + + return (lo | (hi << 16)); +} + +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_pcmcia_block_read(struct ssb_device *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + unsigned long flags; + void __iomem *addr = bus->mmio + offset; + int err; + + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (unlikely(err)) { + memset(buffer, 0xFF, count); + goto unlock; + } + switch (reg_width) { + case sizeof(u8): { + u8 *buf = buffer; + + while (count) { + *buf = __raw_readb(addr); + buf++; + count--; + } + break; + } + case sizeof(u16): { + __le16 *buf = buffer; + + SSB_WARN_ON(count & 1); + while (count) { + *buf = (__force __le16)__raw_readw(addr); + buf++; + count -= 2; + } + break; + } + case sizeof(u32): { + __le16 *buf = buffer; + + SSB_WARN_ON(count & 3); + while (count) { + *buf = (__force __le16)__raw_readw(addr); + buf++; + *buf = (__force __le16)__raw_readw(addr + 2); + buf++; + count -= 4; + } + break; + } + default: + SSB_WARN_ON(1); + } +unlock: + spin_unlock_irqrestore(&bus->bar_lock, flags); +} +#endif /* CONFIG_SSB_BLOCKIO */ - if (unlikely(do_select_core(bus, dev, &offset))) - return 0xFFFFFFFF; - x = readl(bus->mmio + offset); +static void ssb_pcmcia_write8(struct ssb_device *dev, u16 offset, u8 value) +{ + struct ssb_bus *bus = dev->bus; + unsigned long flags; + int err; - return x; + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (likely(!err)) + writeb(value, bus->mmio + offset); + mmiowb(); + spin_unlock_irqrestore(&bus->bar_lock, flags); } static void ssb_pcmcia_write16(struct ssb_device *dev, u16 offset, u16 value) { struct ssb_bus *bus = dev->bus; + unsigned long flags; + int err; - if (unlikely(do_select_core(bus, dev, &offset))) - return; - writew(value, bus->mmio + offset); + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (likely(!err)) + writew(value, bus->mmio + offset); + mmiowb(); + spin_unlock_irqrestore(&bus->bar_lock, flags); } static void ssb_pcmcia_write32(struct ssb_device *dev, u16 offset, u32 value) { struct ssb_bus *bus = dev->bus; + unsigned long flags; + int err; - if (unlikely(do_select_core(bus, dev, &offset))) - return; - readw(bus->mmio + offset); - writew(value >> 16, bus->mmio + offset + 2); - readw(bus->mmio + offset); - writew(value, bus->mmio + offset); + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (likely(!err)) { + writew((value & 0x0000FFFF), bus->mmio + offset); + writew(((value & 0xFFFF0000) >> 16), bus->mmio + offset + 2); + } + mmiowb(); + spin_unlock_irqrestore(&bus->bar_lock, flags); +} + +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_pcmcia_block_write(struct ssb_device *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + unsigned long flags; + void __iomem *addr = bus->mmio + offset; + int err; + + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (unlikely(err)) + goto unlock; + switch (reg_width) { + case sizeof(u8): { + const u8 *buf = buffer; + + while (count) { + __raw_writeb(*buf, addr); + buf++; + count--; + } + break; + } + case sizeof(u16): { + const __le16 *buf = buffer; + + SSB_WARN_ON(count & 1); + while (count) { + __raw_writew((__force u16)(*buf), addr); + buf++; + count -= 2; + } + break; + } + case sizeof(u32): { + const __le16 *buf = buffer; + + SSB_WARN_ON(count & 3); + while (count) { + __raw_writew((__force u16)(*buf), addr); + buf++; + __raw_writew((__force u16)(*buf), addr + 2); + buf++; + count -= 4; + } + break; + } + default: + SSB_WARN_ON(1); + } +unlock: + mmiowb(); + spin_unlock_irqrestore(&bus->bar_lock, flags); } +#endif /* CONFIG_SSB_BLOCKIO */ /* Not "static", as it's used in main.c */ const struct ssb_bus_ops ssb_pcmcia_ops = { + .read8 = ssb_pcmcia_read8, .read16 = ssb_pcmcia_read16, .read32 = ssb_pcmcia_read32, + .write8 = ssb_pcmcia_write8, .write16 = ssb_pcmcia_write16, .write32 = ssb_pcmcia_write32, +#ifdef CONFIG_SSB_BLOCKIO + .block_read = ssb_pcmcia_block_read, + .block_write = ssb_pcmcia_block_write, +#endif }; +static int ssb_pcmcia_sprom_command(struct ssb_bus *bus, u8 command) +{ + unsigned int i; + int err; + u8 value; + + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROMCTL, command); + if (err) + return err; + for (i = 0; i < 1000; i++) { + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_SPROMCTL, &value); + if (err) + return err; + if (value & SSB_PCMCIA_SPROMCTL_DONE) + return 0; + udelay(10); + } + + return -ETIMEDOUT; +} + +/* offset is the 16bit word offset */ +static int ssb_pcmcia_sprom_read(struct ssb_bus *bus, u16 offset, u16 *value) +{ + int err; + u8 lo, hi; + + offset *= 2; /* Make byte offset */ + + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_ADDRLO, + (offset & 0x00FF)); + if (err) + return err; + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_ADDRHI, + (offset & 0xFF00) >> 8); + if (err) + return err; + err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_READ); + if (err) + return err; + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_SPROM_DATALO, &lo); + if (err) + return err; + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_SPROM_DATAHI, &hi); + if (err) + return err; + *value = (lo | (((u16)hi) << 8)); + + return 0; +} + +/* offset is the 16bit word offset */ +static int ssb_pcmcia_sprom_write(struct ssb_bus *bus, u16 offset, u16 value) +{ + int err; + + offset *= 2; /* Make byte offset */ + + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_ADDRLO, + (offset & 0x00FF)); + if (err) + return err; + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_ADDRHI, + (offset & 0xFF00) >> 8); + if (err) + return err; + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_DATALO, + (value & 0x00FF)); + if (err) + return err; + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_DATAHI, + (value & 0xFF00) >> 8); + if (err) + return err; + err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_WRITE); + if (err) + return err; + msleep(20); + + return 0; +} + +/* Read the SPROM image. bufsize is in 16bit words. */ +static int ssb_pcmcia_sprom_read_all(struct ssb_bus *bus, u16 *sprom) +{ + int err, i; + + for (i = 0; i < SSB_PCMCIA_SPROM_SIZE; i++) { + err = ssb_pcmcia_sprom_read(bus, i, &sprom[i]); + if (err) + return err; + } + + return 0; +} + +/* Write the SPROM image. size is in 16bit words. */ +static int ssb_pcmcia_sprom_write_all(struct ssb_bus *bus, const u16 *sprom) +{ + int i, err; + bool failed = 0; + size_t size = SSB_PCMCIA_SPROM_SIZE; + + ssb_printk(KERN_NOTICE PFX + "Writing SPROM. Do NOT turn off the power! " + "Please stand by...\n"); + err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_WRITEEN); + if (err) { + ssb_printk(KERN_NOTICE PFX + "Could not enable SPROM write access.\n"); + return -EBUSY; + } + ssb_printk(KERN_NOTICE PFX "[ 0%%"); + msleep(500); + for (i = 0; i < size; i++) { + if (i == size / 4) + ssb_printk("25%%"); + else if (i == size / 2) + ssb_printk("50%%"); + else if (i == (size * 3) / 4) + ssb_printk("75%%"); + else if (i % 2) + ssb_printk("."); + err = ssb_pcmcia_sprom_write(bus, i, sprom[i]); + if (err) { + ssb_printk("\n" KERN_NOTICE PFX + "Failed to write to SPROM.\n"); + failed = 1; + break; + } + } + err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_WRITEDIS); + if (err) { + ssb_printk("\n" KERN_NOTICE PFX + "Could not disable SPROM write access.\n"); + failed = 1; + } + msleep(500); + if (!failed) { + ssb_printk("100%% ]\n"); + ssb_printk(KERN_NOTICE PFX "SPROM written.\n"); + } + + return failed ? -EBUSY : 0; +} + +static int ssb_pcmcia_sprom_check_crc(const u16 *sprom, size_t size) +{ + //TODO + return 0; +} + +#define GOTO_ERROR_ON(condition, description) do { \ + if (unlikely(condition)) { \ + error_description = description; \ + goto error; \ + } \ + } while (0) + int ssb_pcmcia_get_invariants(struct ssb_bus *bus, struct ssb_init_invariants *iv) { - //TODO + tuple_t tuple; + int res; + unsigned char buf[32]; + struct ssb_sprom *sprom = &iv->sprom; + struct ssb_boardinfo *bi = &iv->boardinfo; + const char *error_description; + + memset(sprom, 0xFF, sizeof(*sprom)); + sprom->revision = 1; + sprom->boardflags_lo = 0; + sprom->boardflags_hi = 0; + + /* First fetch the MAC address. */ + memset(&tuple, 0, sizeof(tuple)); + tuple.DesiredTuple = CISTPL_FUNCE; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + res = pcmcia_get_first_tuple(bus->host_pcmcia, &tuple); + GOTO_ERROR_ON(res != CS_SUCCESS, "MAC first tpl"); + res = pcmcia_get_tuple_data(bus->host_pcmcia, &tuple); + GOTO_ERROR_ON(res != CS_SUCCESS, "MAC first tpl data"); + while (1) { + GOTO_ERROR_ON(tuple.TupleDataLen < 1, "MAC tpl < 1"); + if (tuple.TupleData[0] == CISTPL_FUNCE_LAN_NODE_ID) + break; + res = pcmcia_get_next_tuple(bus->host_pcmcia, &tuple); + GOTO_ERROR_ON(res != CS_SUCCESS, "MAC next tpl"); + res = pcmcia_get_tuple_data(bus->host_pcmcia, &tuple); + GOTO_ERROR_ON(res != CS_SUCCESS, "MAC next tpl data"); + } + GOTO_ERROR_ON(tuple.TupleDataLen != ETH_ALEN + 2, "MAC tpl size"); + memcpy(sprom->il0mac, &tuple.TupleData[2], ETH_ALEN); + + /* Fetch the vendor specific tuples. */ + memset(&tuple, 0, sizeof(tuple)); + tuple.DesiredTuple = SSB_PCMCIA_CIS; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + res = pcmcia_get_first_tuple(bus->host_pcmcia, &tuple); + GOTO_ERROR_ON(res != CS_SUCCESS, "VEN first tpl"); + res = pcmcia_get_tuple_data(bus->host_pcmcia, &tuple); + GOTO_ERROR_ON(res != CS_SUCCESS, "VEN first tpl data"); + while (1) { + GOTO_ERROR_ON(tuple.TupleDataLen < 1, "VEN tpl < 1"); + switch (tuple.TupleData[0]) { + case SSB_PCMCIA_CIS_ID: + GOTO_ERROR_ON((tuple.TupleDataLen != 5) && + (tuple.TupleDataLen != 7), + "id tpl size"); + bi->vendor = tuple.TupleData[1] | + ((u16)tuple.TupleData[2] << 8); + break; + case SSB_PCMCIA_CIS_BOARDREV: + GOTO_ERROR_ON(tuple.TupleDataLen != 2, + "boardrev tpl size"); + sprom->board_rev = tuple.TupleData[1]; + break; + case SSB_PCMCIA_CIS_PA: + GOTO_ERROR_ON(tuple.TupleDataLen != 9, + "pa tpl size"); + sprom->pa0b0 = tuple.TupleData[1] | + ((u16)tuple.TupleData[2] << 8); + sprom->pa0b1 = tuple.TupleData[3] | + ((u16)tuple.TupleData[4] << 8); + sprom->pa0b2 = tuple.TupleData[5] | + ((u16)tuple.TupleData[6] << 8); + sprom->itssi_a = tuple.TupleData[7]; + sprom->itssi_bg = tuple.TupleData[7]; + sprom->maxpwr_a = tuple.TupleData[8]; + sprom->maxpwr_bg = tuple.TupleData[8]; + break; + case SSB_PCMCIA_CIS_OEMNAME: + /* We ignore this. */ + break; + case SSB_PCMCIA_CIS_CCODE: + GOTO_ERROR_ON(tuple.TupleDataLen != 2, + "ccode tpl size"); + sprom->country_code = tuple.TupleData[1]; + break; + case SSB_PCMCIA_CIS_ANTENNA: + GOTO_ERROR_ON(tuple.TupleDataLen != 2, + "ant tpl size"); + sprom->ant_available_a = tuple.TupleData[1]; + sprom->ant_available_bg = tuple.TupleData[1]; + break; + case SSB_PCMCIA_CIS_ANTGAIN: + GOTO_ERROR_ON(tuple.TupleDataLen != 2, + "antg tpl size"); + sprom->antenna_gain.ghz24.a0 = tuple.TupleData[1]; + sprom->antenna_gain.ghz24.a1 = tuple.TupleData[1]; + sprom->antenna_gain.ghz24.a2 = tuple.TupleData[1]; + sprom->antenna_gain.ghz24.a3 = tuple.TupleData[1]; + sprom->antenna_gain.ghz5.a0 = tuple.TupleData[1]; + sprom->antenna_gain.ghz5.a1 = tuple.TupleData[1]; + sprom->antenna_gain.ghz5.a2 = tuple.TupleData[1]; + sprom->antenna_gain.ghz5.a3 = tuple.TupleData[1]; + break; + case SSB_PCMCIA_CIS_BFLAGS: + GOTO_ERROR_ON(tuple.TupleDataLen != 3, + "bfl tpl size"); + sprom->boardflags_lo = tuple.TupleData[1] | + ((u16)tuple.TupleData[2] << 8); + break; + case SSB_PCMCIA_CIS_LEDS: + GOTO_ERROR_ON(tuple.TupleDataLen != 5, + "leds tpl size"); + sprom->gpio0 = tuple.TupleData[1]; + sprom->gpio1 = tuple.TupleData[2]; + sprom->gpio2 = tuple.TupleData[3]; + sprom->gpio3 = tuple.TupleData[4]; + break; + } + res = pcmcia_get_next_tuple(bus->host_pcmcia, &tuple); + if (res == CS_NO_MORE_ITEMS) + break; + GOTO_ERROR_ON(res != CS_SUCCESS, "VEN next tpl"); + res = pcmcia_get_tuple_data(bus->host_pcmcia, &tuple); + GOTO_ERROR_ON(res != CS_SUCCESS, "VEN next tpl data"); + } + return 0; +error: + ssb_printk(KERN_ERR PFX + "PCMCIA: Failed to fetch device invariants: %s\n", + error_description); + return -ENODEV; } -int ssb_pcmcia_init(struct ssb_bus *bus) +static ssize_t ssb_pcmcia_attr_sprom_show(struct device *pcmciadev, + struct device_attribute *attr, + char *buf) +{ + struct pcmcia_device *pdev = + container_of(pcmciadev, struct pcmcia_device, dev); + struct ssb_bus *bus; + + bus = ssb_pcmcia_dev_to_bus(pdev); + if (!bus) + return -ENODEV; + + return ssb_attr_sprom_show(bus, buf, + ssb_pcmcia_sprom_read_all); +} + +static ssize_t ssb_pcmcia_attr_sprom_store(struct device *pcmciadev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pcmcia_device *pdev = + container_of(pcmciadev, struct pcmcia_device, dev); + struct ssb_bus *bus; + + bus = ssb_pcmcia_dev_to_bus(pdev); + if (!bus) + return -ENODEV; + + return ssb_attr_sprom_store(bus, buf, count, + ssb_pcmcia_sprom_check_crc, + ssb_pcmcia_sprom_write_all); +} + +static DEVICE_ATTR(ssb_sprom, 0600, + ssb_pcmcia_attr_sprom_show, + ssb_pcmcia_attr_sprom_store); + +static int ssb_pcmcia_cor_setup(struct ssb_bus *bus, u8 cor) +{ + u8 val; + int err; + + err = ssb_pcmcia_cfg_read(bus, cor, &val); + if (err) + return err; + val &= ~COR_SOFT_RESET; + val |= COR_FUNC_ENA | COR_IREQ_ENA | COR_LEVEL_REQ; + err = ssb_pcmcia_cfg_write(bus, cor, val); + if (err) + return err; + msleep(40); + + return 0; +} + +/* Initialize the PCMCIA hardware. This is called on Init and Resume. */ +int ssb_pcmcia_hardware_setup(struct ssb_bus *bus) { - conf_reg_t reg; int err; if (bus->bustype != SSB_BUSTYPE_PCMCIA) @@ -249,24 +814,45 @@ int ssb_pcmcia_init(struct ssb_bus *bus) /* Switch segment to a known state and sync * bus->mapped_pcmcia_seg with hardware state. */ ssb_pcmcia_switch_segment(bus, 0); + /* Init the COR register. */ + err = ssb_pcmcia_cor_setup(bus, CISREG_COR); + if (err) + return err; + /* Some cards also need this register to get poked. */ + err = ssb_pcmcia_cor_setup(bus, CISREG_COR + 0x80); + if (err) + return err; - /* Init IRQ routing */ - reg.Action = CS_READ; - reg.Function = 0; - if (bus->chip_id == 0x4306) - reg.Offset = 0x00; - else - reg.Offset = 0x80; - err = pcmcia_access_configuration_register(bus->host_pcmcia, ®); - if (err != CS_SUCCESS) + return 0; +} + +void ssb_pcmcia_exit(struct ssb_bus *bus) +{ + if (bus->bustype != SSB_BUSTYPE_PCMCIA) + return; + + device_remove_file(&bus->host_pcmcia->dev, &dev_attr_ssb_sprom); +} + +int ssb_pcmcia_init(struct ssb_bus *bus) +{ + int err; + + if (bus->bustype != SSB_BUSTYPE_PCMCIA) + return 0; + + err = ssb_pcmcia_hardware_setup(bus); + if (err) goto error; - reg.Action = CS_WRITE; - reg.Value |= 0x04 | 0x01; - err = pcmcia_access_configuration_register(bus->host_pcmcia, ®); - if (err != CS_SUCCESS) + + bus->sprom_size = SSB_PCMCIA_SPROM_SIZE; + mutex_init(&bus->sprom_mutex); + err = device_create_file(&bus->host_pcmcia->dev, &dev_attr_ssb_sprom); + if (err) goto error; return 0; error: - return -ENODEV; + ssb_printk(KERN_ERR PFX "Failed to initialize PCMCIA host device\n"); + return err; }