From ffc7689ddae5cbe12bde437ae0f2b386d568b5cd Mon Sep 17 00:00:00 2001 From: Michael Buesch Date: Wed, 20 Feb 2008 19:08:10 +0100 Subject: ssb: Add support for 8bit register access This adds support for 8bit wide register reads/writes. This is needed in order to support the gigabit ethernet core. Signed-off-by: Michael Buesch Signed-off-by: John W. Linville --- drivers/ssb/pcmcia.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'drivers/ssb/pcmcia.c') diff --git a/drivers/ssb/pcmcia.c b/drivers/ssb/pcmcia.c index 46816cda8b98..84b3a845a8a8 100644 --- a/drivers/ssb/pcmcia.c +++ b/drivers/ssb/pcmcia.c @@ -172,6 +172,22 @@ static int select_core_and_segment(struct ssb_device *dev, 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; @@ -206,6 +222,20 @@ static u32 ssb_pcmcia_read32(struct ssb_device *dev, u16 offset) return (lo | (hi << 16)); } +static void ssb_pcmcia_write8(struct ssb_device *dev, u16 offset, u8 value) +{ + struct ssb_bus *bus = dev->bus; + unsigned long flags; + int err; + + 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; @@ -238,8 +268,10 @@ static void ssb_pcmcia_write32(struct ssb_device *dev, u16 offset, u32 value) /* 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, }; -- cgit v1.2.3-59-g8ed1b From e7ec2e3230633a858af1b0b359f6c4670dbeb997 Mon Sep 17 00:00:00 2001 From: Michael Buesch Date: Mon, 10 Mar 2008 17:26:32 +0100 Subject: ssb: Add SPROM/invariants support for PCMCIA devices This adds support for reading/writing the SPROM invariants for PCMCIA based devices. Signed-off-by: Michael Buesch Signed-off-by: John W. Linville --- drivers/ssb/Kconfig | 6 + drivers/ssb/Makefile | 1 + drivers/ssb/main.c | 23 +- drivers/ssb/pci.c | 113 +--------- drivers/ssb/pcmcia.c | 518 ++++++++++++++++++++++++++++++++++++++++------ drivers/ssb/sprom.c | 133 ++++++++++++ drivers/ssb/ssb_private.h | 17 ++ include/linux/ssb/ssb.h | 4 +- 8 files changed, 648 insertions(+), 167 deletions(-) create mode 100644 drivers/ssb/sprom.c (limited to 'drivers/ssb/pcmcia.c') diff --git a/drivers/ssb/Kconfig b/drivers/ssb/Kconfig index f69ef0ba2613..0f7cce2560d1 100644 --- a/drivers/ssb/Kconfig +++ b/drivers/ssb/Kconfig @@ -20,6 +20,10 @@ config SSB If unsure, say N. +# Common SPROM support routines +config SSB_SPROM + bool + config SSB_PCIHOST_POSSIBLE bool depends on SSB && (PCI = y || PCI = SSB) @@ -28,6 +32,7 @@ config SSB_PCIHOST_POSSIBLE config SSB_PCIHOST bool "Support for SSB on PCI-bus host" depends on SSB_PCIHOST_POSSIBLE + select SSB_SPROM default y help Support for a Sonics Silicon Backplane on top @@ -48,6 +53,7 @@ config SSB_PCMCIAHOST_POSSIBLE config SSB_PCMCIAHOST bool "Support for SSB on PCMCIA-bus host (EXPERIMENTAL)" depends on SSB_PCMCIAHOST_POSSIBLE + select SSB_SPROM help Support for a Sonics Silicon Backplane on top of a PCMCIA device. diff --git a/drivers/ssb/Makefile b/drivers/ssb/Makefile index 910f35e32fc9..6f255e9c5af9 100644 --- a/drivers/ssb/Makefile +++ b/drivers/ssb/Makefile @@ -1,6 +1,7 @@ # core ssb-y += main.o scan.o ssb-$(CONFIG_SSB_EMBEDDED) += embedded.o +ssb-$(CONFIG_SSB_SPROM) += sprom.o # host support ssb-$(CONFIG_SSB_PCIHOST) += pci.o pcihost_wrapper.o diff --git a/drivers/ssb/main.c b/drivers/ssb/main.c index 49d7bbb9bea7..e12371916444 100644 --- a/drivers/ssb/main.c +++ b/drivers/ssb/main.c @@ -69,6 +69,25 @@ found: } #endif /* CONFIG_SSB_PCIHOST */ +#ifdef CONFIG_SSB_PCMCIAHOST +struct ssb_bus *ssb_pcmcia_dev_to_bus(struct pcmcia_device *pdev) +{ + struct ssb_bus *bus; + + ssb_buses_lock(); + list_for_each_entry(bus, &buses, list) { + if (bus->bustype == SSB_BUSTYPE_PCMCIA && + bus->host_pcmcia == pdev) + goto found; + } + bus = NULL; +found: + ssb_buses_unlock(); + + return bus; +} +#endif /* CONFIG_SSB_PCMCIAHOST */ + int ssb_for_each_bus_call(unsigned long data, int (*func)(struct ssb_bus *bus, unsigned long data)) { @@ -398,7 +417,7 @@ void ssb_bus_unregister(struct ssb_bus *bus) list_del(&bus->list); ssb_buses_unlock(); - /* ssb_pcmcia_exit(bus); */ + ssb_pcmcia_exit(bus); ssb_pci_exit(bus); ssb_iounmap(bus); } @@ -663,7 +682,7 @@ out: err_dequeue: list_del(&bus->list); err_pcmcia_exit: -/* ssb_pcmcia_exit(bus); */ + ssb_pcmcia_exit(bus); err_pci_exit: ssb_pci_exit(bus); err_unmap: diff --git a/drivers/ssb/pci.c b/drivers/ssb/pci.c index 1facc7620fc8..f1514b33cfae 100644 --- a/drivers/ssb/pci.c +++ b/drivers/ssb/pci.c @@ -227,7 +227,7 @@ static u8 ssb_sprom_crc(const u16 *sprom, u16 size) return crc; } -static int sprom_check_crc(const u16 *sprom, u16 size) +static int sprom_check_crc(const u16 *sprom, size_t size) { u8 crc; u8 expected_crc; @@ -242,12 +242,14 @@ static int sprom_check_crc(const u16 *sprom, u16 size) return 0; } -static void sprom_do_read(struct ssb_bus *bus, u16 *sprom) +static int sprom_do_read(struct ssb_bus *bus, u16 *sprom) { int i; for (i = 0; i < bus->sprom_size; i++) sprom[i] = ioread16(bus->mmio + SSB_SPROM_BASE + (i * 2)); + + return 0; } static int sprom_do_write(struct ssb_bus *bus, const u16 *sprom) @@ -660,71 +662,18 @@ const struct ssb_bus_ops ssb_pci_ops = { .write32 = ssb_pci_write32, }; -static int sprom2hex(const u16 *sprom, char *buf, size_t buf_len, u16 size) -{ - int i, pos = 0; - - for (i = 0; i < size; 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; -} - -static int hex2sprom(u16 *sprom, const char *dump, size_t len, u16 size) -{ - char tmp[5] = { 0 }; - int cnt = 0; - unsigned long parsed; - - if (len < size * 2) - return -EINVAL; - - while (cnt < size) { - memcpy(tmp, dump, 4); - dump += 4; - parsed = simple_strtoul(tmp, NULL, 16); - sprom[cnt++] = swab16((u16)parsed); - } - - return 0; -} - static ssize_t ssb_pci_attr_sprom_show(struct device *pcidev, struct device_attribute *attr, char *buf) { struct pci_dev *pdev = container_of(pcidev, struct pci_dev, dev); struct ssb_bus *bus; - u16 *sprom; - int err = -ENODEV; - ssize_t count = 0; bus = ssb_pci_dev_to_bus(pdev); if (!bus) - goto out; - err = -ENOMEM; - sprom = kcalloc(bus->sprom_size, sizeof(u16), GFP_KERNEL); - if (!sprom) - goto out; + return -ENODEV; - /* Use interruptible locking, as the SPROM write might - * be holding the lock for several seconds. So allow userspace - * to cancel operation. */ - err = -ERESTARTSYS; - if (mutex_lock_interruptible(&bus->pci_sprom_mutex)) - goto out_kfree; - sprom_do_read(bus, sprom); - mutex_unlock(&bus->pci_sprom_mutex); - - count = sprom2hex(sprom, buf, PAGE_SIZE, bus->sprom_size); - err = 0; - -out_kfree: - kfree(sprom); -out: - return err ? err : count; + return ssb_attr_sprom_show(bus, buf, sprom_do_read); } static ssize_t ssb_pci_attr_sprom_store(struct device *pcidev, @@ -733,55 +682,13 @@ static ssize_t ssb_pci_attr_sprom_store(struct device *pcidev, { struct pci_dev *pdev = container_of(pcidev, struct pci_dev, dev); struct ssb_bus *bus; - u16 *sprom; - int res = 0, err = -ENODEV; bus = ssb_pci_dev_to_bus(pdev); if (!bus) - goto out; - err = -ENOMEM; - sprom = kcalloc(bus->sprom_size, sizeof(u16), GFP_KERNEL); - if (!sprom) - goto out; - err = hex2sprom(sprom, buf, count, bus->sprom_size); - if (err) { - err = -EINVAL; - goto out_kfree; - } - err = sprom_check_crc(sprom, bus->sprom_size); - if (err) { - err = -EINVAL; - goto out_kfree; - } + return -ENODEV; - /* Use interruptible locking, as the SPROM write might - * be holding the lock for several seconds. So allow userspace - * to cancel operation. */ - err = -ERESTARTSYS; - if (mutex_lock_interruptible(&bus->pci_sprom_mutex)) - goto out_kfree; - err = ssb_devices_freeze(bus); - if (err == -EOPNOTSUPP) { - ssb_printk(KERN_ERR PFX "SPROM write: Could not freeze devices. " - "No suspend support. Is CONFIG_PM enabled?\n"); - goto out_unlock; - } - if (err) { - ssb_printk(KERN_ERR PFX "SPROM write: Could not freeze all devices\n"); - goto out_unlock; - } - res = sprom_do_write(bus, sprom); - err = ssb_devices_thaw(bus); - if (err) - ssb_printk(KERN_ERR PFX "SPROM write: Could not thaw all devices\n"); -out_unlock: - mutex_unlock(&bus->pci_sprom_mutex); -out_kfree: - kfree(sprom); -out: - if (res) - return res; - return err ? err : count; + return ssb_attr_sprom_store(bus, buf, count, + sprom_check_crc, sprom_do_write); } static DEVICE_ATTR(ssb_sprom, 0600, @@ -808,7 +715,7 @@ int ssb_pci_init(struct ssb_bus *bus) return 0; pdev = bus->host_pci; - mutex_init(&bus->pci_sprom_mutex); + mutex_init(&bus->sprom_mutex); err = device_create_file(&pdev->dev, &dev_attr_ssb_sprom); if (err) goto out; diff --git a/drivers/ssb/pcmcia.c b/drivers/ssb/pcmcia.c index 84b3a845a8a8..cd49f7c65531 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,132 @@ #define SSB_VERBOSE_PCMCIACORESWITCH_DEBUG 0 +/* PCMCIA configuration registers */ +#define SSB_PCMCIA_CORECTL 0x00 +#define SSB_PCMCIA_CORECTL_RESET 0x80 /* Core reset */ +#define SSB_PCMCIA_CORECTL_IRQEN 0x04 /* IRQ enable */ +#define SSB_PCMCIA_CORECTL_FUNCEN 0x01 /* Function enable */ +#define SSB_PCMCIA_CORECTL2 0x80 +#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 |= ((u32)(reg.Value & 0x0F)) << 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 |= ((u32)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 |= ((u32)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,7 +161,7 @@ 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, @@ -112,27 +186,21 @@ int ssb_pcmcia_switch_core(struct ssb_bus *bus, int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg) { int attempts = 0; - conf_reg_t reg; - int res; + int err; + u8 val; SSB_WARN_ON((seg != 0) && (seg != 1)); - reg.Offset = 0x34; - reg.Function = 0; 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); @@ -142,7 +210,7 @@ int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg) return 0; error: ssb_printk(KERN_ERR PFX "Failed to switch pcmcia segment\n"); - return -ENODEV; + return err; } static int select_core_and_segment(struct ssb_device *dev, @@ -276,18 +344,344 @@ const struct ssb_bus_ops ssb_pcmcia_ops = { .write32 = ssb_pcmcia_write32, }; -#include +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 - random_ether_addr(iv->sprom.il0mac); + 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; +} + +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); + +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) { - conf_reg_t reg; + u8 val, offset; int err; if (bus->bustype != SSB_BUSTYPE_PCMCIA) @@ -298,22 +692,26 @@ int ssb_pcmcia_init(struct ssb_bus *bus) ssb_pcmcia_switch_segment(bus, 0); /* Init IRQ routing */ - reg.Action = CS_READ; - reg.Function = 0; if (bus->chip_id == 0x4306) - reg.Offset = 0x00; + offset = SSB_PCMCIA_CORECTL; else - reg.Offset = 0x80; - err = pcmcia_access_configuration_register(bus->host_pcmcia, ®); - if (err != CS_SUCCESS) + offset = SSB_PCMCIA_CORECTL2; + err = ssb_pcmcia_cfg_read(bus, offset, &val); + if (err) goto error; - reg.Action = CS_WRITE; - reg.Value |= 0x04 | 0x01; - err = pcmcia_access_configuration_register(bus->host_pcmcia, ®); - if (err != CS_SUCCESS) + val |= SSB_PCMCIA_CORECTL_IRQEN | SSB_PCMCIA_CORECTL_FUNCEN; + err = ssb_pcmcia_cfg_write(bus, offset, val); + if (err) + goto error; + + 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; } diff --git a/drivers/ssb/sprom.c b/drivers/ssb/sprom.c new file mode 100644 index 000000000000..3668edb39315 --- /dev/null +++ b/drivers/ssb/sprom.c @@ -0,0 +1,133 @@ +/* + * Sonics Silicon Backplane + * Common SPROM support routines + * + * Copyright (C) 2005-2008 Michael Buesch + * Copyright (C) 2005 Martin Langer + * Copyright (C) 2005 Stefano Brivio + * Copyright (C) 2005 Danny van Dyk + * Copyright (C) 2005 Andreas Jaggi + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include "ssb_private.h" + + +static int sprom2hex(const u16 *sprom, char *buf, size_t buf_len, + size_t sprom_size_words) +{ + int i, pos = 0; + + for (i = 0; i < sprom_size_words; 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; +} + +static int hex2sprom(u16 *sprom, const char *dump, size_t len, + size_t sprom_size_words) +{ + char tmp[5] = { 0 }; + int cnt = 0; + unsigned long parsed; + + if (len < sprom_size_words * 2) + return -EINVAL; + + while (cnt < sprom_size_words) { + memcpy(tmp, dump, 4); + dump += 4; + parsed = simple_strtoul(tmp, NULL, 16); + sprom[cnt++] = swab16((u16)parsed); + } + + return 0; +} + +/* Common sprom device-attribute show-handler */ +ssize_t ssb_attr_sprom_show(struct ssb_bus *bus, char *buf, + int (*sprom_read)(struct ssb_bus *bus, u16 *sprom)) +{ + u16 *sprom; + int err = -ENOMEM; + ssize_t count = 0; + size_t sprom_size_words = bus->sprom_size; + + sprom = kcalloc(sprom_size_words, sizeof(u16), GFP_KERNEL); + if (!sprom) + goto out; + + /* Use interruptible locking, as the SPROM write might + * be holding the lock for several seconds. So allow userspace + * to cancel operation. */ + err = -ERESTARTSYS; + if (mutex_lock_interruptible(&bus->sprom_mutex)) + goto out_kfree; + err = sprom_read(bus, sprom); + mutex_unlock(&bus->sprom_mutex); + + if (!err) + count = sprom2hex(sprom, buf, PAGE_SIZE, sprom_size_words); + +out_kfree: + kfree(sprom); +out: + return err ? err : count; +} + +/* Common sprom device-attribute store-handler */ +ssize_t ssb_attr_sprom_store(struct ssb_bus *bus, + const char *buf, size_t count, + int (*sprom_check_crc)(const u16 *sprom, size_t size), + int (*sprom_write)(struct ssb_bus *bus, const u16 *sprom)) +{ + u16 *sprom; + int res = 0, err = -ENOMEM; + size_t sprom_size_words = bus->sprom_size; + + sprom = kcalloc(bus->sprom_size, sizeof(u16), GFP_KERNEL); + if (!sprom) + goto out; + err = hex2sprom(sprom, buf, count, sprom_size_words); + if (err) { + err = -EINVAL; + goto out_kfree; + } + err = sprom_check_crc(sprom, sprom_size_words); + if (err) { + err = -EINVAL; + goto out_kfree; + } + + /* Use interruptible locking, as the SPROM write might + * be holding the lock for several seconds. So allow userspace + * to cancel operation. */ + err = -ERESTARTSYS; + if (mutex_lock_interruptible(&bus->sprom_mutex)) + goto out_kfree; + err = ssb_devices_freeze(bus); + if (err == -EOPNOTSUPP) { + ssb_printk(KERN_ERR PFX "SPROM write: Could not freeze devices. " + "No suspend support. Is CONFIG_PM enabled?\n"); + goto out_unlock; + } + if (err) { + ssb_printk(KERN_ERR PFX "SPROM write: Could not freeze all devices\n"); + goto out_unlock; + } + res = sprom_write(bus, sprom); + err = ssb_devices_thaw(bus); + if (err) + ssb_printk(KERN_ERR PFX "SPROM write: Could not thaw all devices\n"); +out_unlock: + mutex_unlock(&bus->sprom_mutex); +out_kfree: + kfree(sprom); +out: + if (res) + return res; + return err ? err : count; +} diff --git a/drivers/ssb/ssb_private.h b/drivers/ssb/ssb_private.h index d03b20983b1e..a83bf7a4d80b 100644 --- a/drivers/ssb/ssb_private.h +++ b/drivers/ssb/ssb_private.h @@ -81,6 +81,7 @@ extern int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg); extern int ssb_pcmcia_get_invariants(struct ssb_bus *bus, struct ssb_init_invariants *iv); +extern void ssb_pcmcia_exit(struct ssb_bus *bus); extern int ssb_pcmcia_init(struct ssb_bus *bus); extern const struct ssb_bus_ops ssb_pcmcia_ops; #else /* CONFIG_SSB_PCMCIAHOST */ @@ -99,6 +100,9 @@ static inline int ssb_pcmcia_switch_segment(struct ssb_bus *bus, { return 0; } +static inline void ssb_pcmcia_exit(struct ssb_bus *bus) +{ +} static inline int ssb_pcmcia_init(struct ssb_bus *bus) { return 0; @@ -113,6 +117,17 @@ extern int ssb_bus_scan(struct ssb_bus *bus, extern void ssb_iounmap(struct ssb_bus *ssb); +/* sprom.c */ +extern +ssize_t ssb_attr_sprom_show(struct ssb_bus *bus, char *buf, + int (*sprom_read)(struct ssb_bus *bus, u16 *sprom)); +extern +ssize_t ssb_attr_sprom_store(struct ssb_bus *bus, + const char *buf, size_t count, + int (*sprom_check_crc)(const u16 *sprom, size_t size), + int (*sprom_write)(struct ssb_bus *bus, const u16 *sprom)); + + /* core.c */ extern u32 ssb_calc_clock_rate(u32 plltype, u32 n, u32 m); extern int ssb_devices_freeze(struct ssb_bus *bus); @@ -120,6 +135,8 @@ extern int ssb_devices_thaw(struct ssb_bus *bus); extern struct ssb_bus *ssb_pci_dev_to_bus(struct pci_dev *pdev); int ssb_for_each_bus_call(unsigned long data, int (*func)(struct ssb_bus *bus, unsigned long data)); +extern struct ssb_bus *ssb_pcmcia_dev_to_bus(struct pcmcia_device *pdev); + /* b43_pci_bridge.c */ #ifdef CONFIG_SSB_B43_PCI_BRIDGE diff --git a/include/linux/ssb/ssb.h b/include/linux/ssb/ssb.h index b7c388972fcf..8644e03cf588 100644 --- a/include/linux/ssb/ssb.h +++ b/include/linux/ssb/ssb.h @@ -245,9 +245,9 @@ struct ssb_bus { /* Pointer to the PCMCIA device (only if bustype == SSB_BUSTYPE_PCMCIA). */ struct pcmcia_device *host_pcmcia; -#ifdef CONFIG_SSB_PCIHOST +#ifdef CONFIG_SSB_SPROM /* Mutex to protect the SPROM writing. */ - struct mutex pci_sprom_mutex; + struct mutex sprom_mutex; #endif /* ID information about the Chip. */ -- cgit v1.2.3-59-g8ed1b From 9788ba7500c3a6268ceb63296a0f37f34d450beb Mon Sep 17 00:00:00 2001 From: Michael Buesch Date: Fri, 28 Mar 2008 10:34:55 +0100 Subject: ssb-pcmcia: IRQ and DMA related fixes Here come some IRQ and DMA related fixes for the ssb PCMCIA-host code. Not much to say, actually. I think the patch explains itself. Signed-off-by: Michael Buesch Signed-off-by: John W. Linville --- drivers/ssb/main.c | 2 +- drivers/ssb/pcmcia.c | 36 ++++++++++++++++++++++-------------- 2 files changed, 23 insertions(+), 15 deletions(-) (limited to 'drivers/ssb/pcmcia.c') diff --git a/drivers/ssb/main.c b/drivers/ssb/main.c index e12371916444..2fcfd73b3b6e 100644 --- a/drivers/ssb/main.c +++ b/drivers/ssb/main.c @@ -1064,9 +1064,9 @@ u32 ssb_dma_translation(struct ssb_device *dev) { switch (dev->bus->bustype) { case SSB_BUSTYPE_SSB: + case SSB_BUSTYPE_PCMCIA: return 0; case SSB_BUSTYPE_PCI: - case SSB_BUSTYPE_PCMCIA: return SSB_PCI_DMA; } return 0; diff --git a/drivers/ssb/pcmcia.c b/drivers/ssb/pcmcia.c index cd49f7c65531..d674cef7210d 100644 --- a/drivers/ssb/pcmcia.c +++ b/drivers/ssb/pcmcia.c @@ -28,11 +28,6 @@ /* PCMCIA configuration registers */ -#define SSB_PCMCIA_CORECTL 0x00 -#define SSB_PCMCIA_CORECTL_RESET 0x80 /* Core reset */ -#define SSB_PCMCIA_CORECTL_IRQEN 0x04 /* IRQ enable */ -#define SSB_PCMCIA_CORECTL_FUNCEN 0x01 /* Function enable */ -#define SSB_PCMCIA_CORECTL2 0x80 #define SSB_PCMCIA_ADDRESS0 0x2E #define SSB_PCMCIA_ADDRESS1 0x30 #define SSB_PCMCIA_ADDRESS2 0x32 @@ -671,6 +666,24 @@ 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; +} + void ssb_pcmcia_exit(struct ssb_bus *bus) { if (bus->bustype != SSB_BUSTYPE_PCMCIA) @@ -681,7 +694,6 @@ void ssb_pcmcia_exit(struct ssb_bus *bus) int ssb_pcmcia_init(struct ssb_bus *bus) { - u8 val, offset; int err; if (bus->bustype != SSB_BUSTYPE_PCMCIA) @@ -691,16 +703,12 @@ int ssb_pcmcia_init(struct ssb_bus *bus) * bus->mapped_pcmcia_seg with hardware state. */ ssb_pcmcia_switch_segment(bus, 0); - /* Init IRQ routing */ - if (bus->chip_id == 0x4306) - offset = SSB_PCMCIA_CORECTL; - else - offset = SSB_PCMCIA_CORECTL2; - err = ssb_pcmcia_cfg_read(bus, offset, &val); + /* Init the COR register. */ + err = ssb_pcmcia_cor_setup(bus, CISREG_COR); if (err) goto error; - val |= SSB_PCMCIA_CORECTL_IRQEN | SSB_PCMCIA_CORECTL_FUNCEN; - err = ssb_pcmcia_cfg_write(bus, offset, val); + /* Some cards also need this register to get poked. */ + err = ssb_pcmcia_cor_setup(bus, CISREG_COR + 0x80); if (err) goto error; -- cgit v1.2.3-59-g8ed1b From 8fe2b65a18e49bfde56a59ed4ab3fc7aa0c2f325 Mon Sep 17 00:00:00 2001 From: Michael Buesch Date: Sun, 30 Mar 2008 00:10:50 +0100 Subject: ssb: Turn suspend/resume upside down Turn the SSB bus suspend mechanism upside down. Instead of deciding by an internal reference count when to suspend/resume, let the parent bus call us in their suspend/resume routine. Signed-off-by: Michael Buesch Signed-off-by: John W. Linville --- drivers/net/wireless/b43/pcmcia.c | 10 ++-- drivers/ssb/driver_chipcommon.c | 2 +- drivers/ssb/main.c | 81 +++++++++++++------------------ drivers/ssb/pcihost_wrapper.c | 10 ++++ drivers/ssb/pcmcia.c | 34 +++++++++---- drivers/ssb/ssb_private.h | 5 ++ include/linux/ssb/ssb.h | 10 ++-- include/linux/ssb/ssb_driver_chipcommon.h | 3 +- 8 files changed, 88 insertions(+), 67 deletions(-) (limited to 'drivers/ssb/pcmcia.c') diff --git a/drivers/net/wireless/b43/pcmcia.c b/drivers/net/wireless/b43/pcmcia.c index 371e4a119511..b8aa16307f79 100644 --- a/drivers/net/wireless/b43/pcmcia.c +++ b/drivers/net/wireless/b43/pcmcia.c @@ -43,14 +43,16 @@ MODULE_DEVICE_TABLE(pcmcia, b43_pcmcia_tbl); #ifdef CONFIG_PM static int b43_pcmcia_suspend(struct pcmcia_device *dev) { - //TODO - return 0; + struct ssb_bus *ssb = dev->priv; + + return ssb_bus_suspend(ssb); } static int b43_pcmcia_resume(struct pcmcia_device *dev) { - //TODO - return 0; + struct ssb_bus *ssb = dev->priv; + + return ssb_bus_resume(ssb); } #else /* CONFIG_PM */ # define b43_pcmcia_suspend NULL diff --git a/drivers/ssb/driver_chipcommon.c b/drivers/ssb/driver_chipcommon.c index 45b672a69003..571f4fd55236 100644 --- a/drivers/ssb/driver_chipcommon.c +++ b/drivers/ssb/driver_chipcommon.c @@ -251,7 +251,7 @@ void ssb_chipcommon_init(struct ssb_chipcommon *cc) calc_fast_powerup_delay(cc); } -void ssb_chipco_suspend(struct ssb_chipcommon *cc, pm_message_t state) +void ssb_chipco_suspend(struct ssb_chipcommon *cc) { if (!cc->dev) return; diff --git a/drivers/ssb/main.c b/drivers/ssb/main.c index 2fcfd73b3b6e..c0cbdba07aee 100644 --- a/drivers/ssb/main.c +++ b/drivers/ssb/main.c @@ -120,35 +120,12 @@ static void ssb_device_put(struct ssb_device *dev) put_device(dev->dev); } -static int ssb_bus_resume(struct ssb_bus *bus) -{ - int err; - - ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 1); - err = ssb_pcmcia_init(bus); - if (err) { - /* No need to disable XTAL, as we don't have one on PCMCIA. */ - return err; - } - ssb_chipco_resume(&bus->chipco); - - return 0; -} - static int ssb_device_resume(struct device *dev) { struct ssb_device *ssb_dev = dev_to_ssb_dev(dev); struct ssb_driver *ssb_drv; - struct ssb_bus *bus; int err = 0; - bus = ssb_dev->bus; - if (bus->suspend_cnt == bus->nr_devices) { - err = ssb_bus_resume(bus); - if (err) - return err; - } - bus->suspend_cnt--; if (dev->driver) { ssb_drv = drv_to_ssb_drv(dev->driver); if (ssb_drv && ssb_drv->resume) @@ -160,27 +137,10 @@ out: return err; } -static void ssb_bus_suspend(struct ssb_bus *bus, pm_message_t state) -{ - ssb_chipco_suspend(&bus->chipco, state); - ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 0); - - /* Reset HW state information in memory, so that HW is - * completely reinitialized on resume. */ - bus->mapped_device = NULL; -#ifdef CONFIG_SSB_DRIVER_PCICORE - bus->pcicore.setup_done = 0; -#endif -#ifdef CONFIG_SSB_DEBUG - bus->powered_up = 0; -#endif -} - static int ssb_device_suspend(struct device *dev, pm_message_t state) { struct ssb_device *ssb_dev = dev_to_ssb_dev(dev); struct ssb_driver *ssb_drv; - struct ssb_bus *bus; int err = 0; if (dev->driver) { @@ -190,17 +150,44 @@ static int ssb_device_suspend(struct device *dev, pm_message_t state) if (err) goto out; } +out: + return err; +} + +int ssb_bus_resume(struct ssb_bus *bus) +{ + int err; - bus = ssb_dev->bus; - bus->suspend_cnt++; - if (bus->suspend_cnt == bus->nr_devices) { - /* All devices suspended. Shutdown the bus. */ - ssb_bus_suspend(bus, state); + /* Reset HW state information in memory, so that HW is + * completely reinitialized. */ + bus->mapped_device = NULL; +#ifdef CONFIG_SSB_DRIVER_PCICORE + bus->pcicore.setup_done = 0; +#endif + + err = ssb_bus_powerup(bus, 0); + if (err) + return err; + err = ssb_pcmcia_hardware_setup(bus); + if (err) { + ssb_bus_may_powerdown(bus); + return err; } + ssb_chipco_resume(&bus->chipco); + ssb_bus_may_powerdown(bus); -out: - return err; + return 0; +} +EXPORT_SYMBOL(ssb_bus_resume); + +int ssb_bus_suspend(struct ssb_bus *bus) +{ + ssb_chipco_suspend(&bus->chipco); + ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 0); + + return 0; } +EXPORT_SYMBOL(ssb_bus_suspend); #ifdef CONFIG_SSB_PCIHOST int ssb_devices_freeze(struct ssb_bus *bus) diff --git a/drivers/ssb/pcihost_wrapper.c b/drivers/ssb/pcihost_wrapper.c index 82a10abef640..e82db4aaa050 100644 --- a/drivers/ssb/pcihost_wrapper.c +++ b/drivers/ssb/pcihost_wrapper.c @@ -18,6 +18,12 @@ #ifdef CONFIG_PM static int ssb_pcihost_suspend(struct pci_dev *dev, pm_message_t state) { + struct ssb_bus *ssb = pci_get_drvdata(dev); + int err; + + err = ssb_bus_suspend(ssb); + if (err) + return err; pci_save_state(dev); pci_disable_device(dev); pci_set_power_state(dev, pci_choose_state(dev, state)); @@ -27,6 +33,7 @@ static int ssb_pcihost_suspend(struct pci_dev *dev, pm_message_t state) static int ssb_pcihost_resume(struct pci_dev *dev) { + struct ssb_bus *ssb = pci_get_drvdata(dev); int err; pci_set_power_state(dev, 0); @@ -34,6 +41,9 @@ static int ssb_pcihost_resume(struct pci_dev *dev) if (err) return err; pci_restore_state(dev); + err = ssb_bus_resume(ssb); + if (err) + return err; return 0; } diff --git a/drivers/ssb/pcmcia.c b/drivers/ssb/pcmcia.c index d674cef7210d..dcaf2412bea7 100644 --- a/drivers/ssb/pcmcia.c +++ b/drivers/ssb/pcmcia.c @@ -684,6 +684,29 @@ static int ssb_pcmcia_cor_setup(struct ssb_bus *bus, u8 cor) return 0; } +/* Initialize the PCMCIA hardware. This is called on Init and Resume. */ +int ssb_pcmcia_hardware_setup(struct ssb_bus *bus) +{ + int err; + + if (bus->bustype != SSB_BUSTYPE_PCMCIA) + return 0; + + /* 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; + + return 0; +} + void ssb_pcmcia_exit(struct ssb_bus *bus) { if (bus->bustype != SSB_BUSTYPE_PCMCIA) @@ -699,16 +722,7 @@ int ssb_pcmcia_init(struct ssb_bus *bus) if (bus->bustype != SSB_BUSTYPE_PCMCIA) return 0; - /* 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) - goto error; - /* Some cards also need this register to get poked. */ - err = ssb_pcmcia_cor_setup(bus, CISREG_COR + 0x80); + err = ssb_pcmcia_hardware_setup(bus); if (err) goto error; diff --git a/drivers/ssb/ssb_private.h b/drivers/ssb/ssb_private.h index a83bf7a4d80b..ebc32d8fe15f 100644 --- a/drivers/ssb/ssb_private.h +++ b/drivers/ssb/ssb_private.h @@ -81,6 +81,7 @@ extern int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg); extern int ssb_pcmcia_get_invariants(struct ssb_bus *bus, struct ssb_init_invariants *iv); +extern int ssb_pcmcia_hardware_setup(struct ssb_bus *bus); extern void ssb_pcmcia_exit(struct ssb_bus *bus); extern int ssb_pcmcia_init(struct ssb_bus *bus); extern const struct ssb_bus_ops ssb_pcmcia_ops; @@ -100,6 +101,10 @@ static inline int ssb_pcmcia_switch_segment(struct ssb_bus *bus, { return 0; } +static inline int ssb_pcmcia_hardware_setup(struct ssb_bus *bus) +{ + return 0; +} static inline void ssb_pcmcia_exit(struct ssb_bus *bus) { } diff --git a/include/linux/ssb/ssb.h b/include/linux/ssb/ssb.h index 8644e03cf588..a8ca396f810a 100644 --- a/include/linux/ssb/ssb.h +++ b/include/linux/ssb/ssb.h @@ -260,9 +260,6 @@ struct ssb_bus { struct ssb_device devices[SSB_MAX_NR_CORES]; u8 nr_devices; - /* Reference count. Number of suspended devices. */ - u8 suspend_cnt; - /* Software ID number for this bus. */ unsigned int busnumber; @@ -334,6 +331,13 @@ extern int ssb_bus_pcmciabus_register(struct ssb_bus *bus, extern void ssb_bus_unregister(struct ssb_bus *bus); +/* Suspend a SSB bus. + * Call this from the parent bus suspend routine. */ +extern int ssb_bus_suspend(struct ssb_bus *bus); +/* Resume a SSB bus. + * Call this from the parent bus resume routine. */ +extern int ssb_bus_resume(struct ssb_bus *bus); + extern u32 ssb_clockspeed(struct ssb_bus *bus); /* Is the device enabled in hardware? */ diff --git a/include/linux/ssb/ssb_driver_chipcommon.h b/include/linux/ssb/ssb_driver_chipcommon.h index b548a54ff1f5..7d7e03dcf77c 100644 --- a/include/linux/ssb/ssb_driver_chipcommon.h +++ b/include/linux/ssb/ssb_driver_chipcommon.h @@ -367,8 +367,7 @@ static inline bool ssb_chipco_available(struct ssb_chipcommon *cc) extern void ssb_chipcommon_init(struct ssb_chipcommon *cc); -#include -extern void ssb_chipco_suspend(struct ssb_chipcommon *cc, pm_message_t state); +extern void ssb_chipco_suspend(struct ssb_chipcommon *cc); extern void ssb_chipco_resume(struct ssb_chipcommon *cc); extern void ssb_chipco_get_clockcpu(struct ssb_chipcommon *cc, -- cgit v1.2.3-59-g8ed1b From d625a29ba649a4df6027520ffc378f23c0e6883e Mon Sep 17 00:00:00 2001 From: Michael Buesch Date: Wed, 2 Apr 2008 19:46:56 +0200 Subject: ssb: Add support for block-I/O This adds support for block based I/O to SSB. This is needed in order to efficiently support PIO data transfers to the card. The block-I/O support is only compiled, if it's selected by the weird driver that needs it. So there's no overhead for sane devices. Signed-off-by: Michael Buesch Signed-off-by: John W. Linville --- drivers/ssb/Kconfig | 5 ++ drivers/ssb/main.c | 102 +++++++++++++++++++++++++++++++++++++++++ drivers/ssb/pci.c | 70 ++++++++++++++++++++++++++++ drivers/ssb/pcmcia.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/ssb/ssb.h | 19 ++++++++ 5 files changed, 315 insertions(+) (limited to 'drivers/ssb/pcmcia.c') diff --git a/drivers/ssb/Kconfig b/drivers/ssb/Kconfig index 0f7cce2560d1..cd845b8acd17 100644 --- a/drivers/ssb/Kconfig +++ b/drivers/ssb/Kconfig @@ -24,6 +24,11 @@ config SSB config SSB_SPROM bool +# Support for Block-I/O. SELECT this from the driver that needs it. +config SSB_BLOCKIO + bool + depends on SSB + config SSB_PCIHOST_POSSIBLE bool depends on SSB && (PCI = y || PCI = SSB) diff --git a/drivers/ssb/main.c b/drivers/ssb/main.c index 3e58db7ef608..19ddd2bd1057 100644 --- a/drivers/ssb/main.c +++ b/drivers/ssb/main.c @@ -555,6 +555,55 @@ static u32 ssb_ssb_read32(struct ssb_device *dev, u16 offset) return readl(bus->mmio + offset); } +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_ssb_block_read(struct ssb_device *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + void __iomem *addr; + + offset += dev->core_index * SSB_CORE_SIZE; + addr = bus->mmio + offset; + + 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): { + __le32 *buf = buffer; + + SSB_WARN_ON(count & 3); + while (count) { + *buf = (__force __le32)__raw_readl(addr); + buf++; + count -= 4; + } + break; + } + default: + SSB_WARN_ON(1); + } +} +#endif /* CONFIG_SSB_BLOCKIO */ + static void ssb_ssb_write8(struct ssb_device *dev, u16 offset, u8 value) { struct ssb_bus *bus = dev->bus; @@ -579,6 +628,55 @@ static void ssb_ssb_write32(struct ssb_device *dev, u16 offset, u32 value) writel(value, bus->mmio + offset); } +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_ssb_block_write(struct ssb_device *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + void __iomem *addr; + + offset += dev->core_index * SSB_CORE_SIZE; + addr = bus->mmio + offset; + + 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 __le32 *buf = buffer; + + SSB_WARN_ON(count & 3); + while (count) { + __raw_writel((__force u32)(*buf), addr); + buf++; + count -= 4; + } + break; + } + default: + SSB_WARN_ON(1); + } +} +#endif /* CONFIG_SSB_BLOCKIO */ + /* Ops for the plain SSB bus without a host-device (no PCI or PCMCIA). */ static const struct ssb_bus_ops ssb_ssb_ops = { .read8 = ssb_ssb_read8, @@ -587,6 +685,10 @@ static const struct ssb_bus_ops ssb_ssb_ops = { .write8 = ssb_ssb_write8, .write16 = ssb_ssb_write16, .write32 = ssb_ssb_write32, +#ifdef CONFIG_SSB_BLOCKIO + .block_read = ssb_ssb_block_read, + .block_write = ssb_ssb_block_write, +#endif }; static int ssb_fetch_invariants(struct ssb_bus *bus, diff --git a/drivers/ssb/pci.c b/drivers/ssb/pci.c index f1514b33cfae..904b1a8d0885 100644 --- a/drivers/ssb/pci.c +++ b/drivers/ssb/pci.c @@ -613,6 +613,41 @@ static u32 ssb_pci_read32(struct ssb_device *dev, u16 offset) return ioread32(bus->mmio + offset); } +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_pci_block_read(struct ssb_device *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + void __iomem *addr = bus->mmio + offset; + + if (unlikely(ssb_pci_assert_buspower(bus))) + goto error; + if (unlikely(bus->mapped_device != dev)) { + if (unlikely(ssb_pci_switch_core(bus, dev))) + goto error; + } + switch (reg_width) { + case sizeof(u8): + ioread8_rep(addr, buffer, count); + break; + case sizeof(u16): + SSB_WARN_ON(count & 1); + ioread16_rep(addr, buffer, count >> 1); + break; + case sizeof(u32): + SSB_WARN_ON(count & 3); + ioread32_rep(addr, buffer, count >> 2); + break; + default: + SSB_WARN_ON(1); + } + + return; +error: + memset(buffer, 0xFF, count); +} +#endif /* CONFIG_SSB_BLOCKIO */ + static void ssb_pci_write8(struct ssb_device *dev, u16 offset, u8 value) { struct ssb_bus *bus = dev->bus; @@ -652,6 +687,37 @@ static void ssb_pci_write32(struct ssb_device *dev, u16 offset, u32 value) iowrite32(value, bus->mmio + offset); } +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_pci_block_write(struct ssb_device *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + void __iomem *addr = bus->mmio + offset; + + if (unlikely(ssb_pci_assert_buspower(bus))) + return; + if (unlikely(bus->mapped_device != dev)) { + if (unlikely(ssb_pci_switch_core(bus, dev))) + return; + } + switch (reg_width) { + case sizeof(u8): + iowrite8_rep(addr, buffer, count); + break; + case sizeof(u16): + SSB_WARN_ON(count & 1); + iowrite16_rep(addr, buffer, count >> 1); + break; + case sizeof(u32): + SSB_WARN_ON(count & 3); + iowrite32_rep(addr, buffer, count >> 2); + break; + default: + SSB_WARN_ON(1); + } +} +#endif /* CONFIG_SSB_BLOCKIO */ + /* Not "static", as it's used in main.c */ const struct ssb_bus_ops ssb_pci_ops = { .read8 = ssb_pci_read8, @@ -660,6 +726,10 @@ const struct ssb_bus_ops ssb_pci_ops = { .write8 = ssb_pci_write8, .write16 = ssb_pci_write16, .write32 = ssb_pci_write32, +#ifdef CONFIG_SSB_BLOCKIO + .block_read = ssb_pci_block_read, + .block_write = ssb_pci_block_write, +#endif }; static ssize_t ssb_pci_attr_sprom_show(struct device *pcidev, diff --git a/drivers/ssb/pcmcia.c b/drivers/ssb/pcmcia.c index dcaf2412bea7..24c2a46c1476 100644 --- a/drivers/ssb/pcmcia.c +++ b/drivers/ssb/pcmcia.c @@ -285,6 +285,64 @@ static u32 ssb_pcmcia_read32(struct ssb_device *dev, u16 offset) 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 */ + static void ssb_pcmcia_write8(struct ssb_device *dev, u16 offset, u8 value) { struct ssb_bus *bus = dev->bus; @@ -329,6 +387,63 @@ static void ssb_pcmcia_write32(struct ssb_device *dev, u16 offset, u32 value) 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, @@ -337,6 +452,10 @@ const struct ssb_bus_ops ssb_pcmcia_ops = { .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) diff --git a/include/linux/ssb/ssb.h b/include/linux/ssb/ssb.h index a8ca396f810a..9f95afd0e9e3 100644 --- a/include/linux/ssb/ssb.h +++ b/include/linux/ssb/ssb.h @@ -78,6 +78,12 @@ struct ssb_bus_ops { void (*write8)(struct ssb_device *dev, u16 offset, u8 value); void (*write16)(struct ssb_device *dev, u16 offset, u16 value); void (*write32)(struct ssb_device *dev, u16 offset, u32 value); +#ifdef CONFIG_SSB_BLOCKIO + void (*block_read)(struct ssb_device *dev, void *buffer, + size_t count, u16 offset, u8 reg_width); + void (*block_write)(struct ssb_device *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width); +#endif }; @@ -374,6 +380,19 @@ static inline void ssb_write32(struct ssb_device *dev, u16 offset, u32 value) { dev->ops->write32(dev, offset, value); } +#ifdef CONFIG_SSB_BLOCKIO +static inline void ssb_block_read(struct ssb_device *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + dev->ops->block_read(dev, buffer, count, offset, reg_width); +} + +static inline void ssb_block_write(struct ssb_device *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + dev->ops->block_write(dev, buffer, count, offset, reg_width); +} +#endif /* CONFIG_SSB_BLOCKIO */ /* Translation (routing) bits that need to be ORed to DMA -- cgit v1.2.3-59-g8ed1b