Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-2.6
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 30 May 2010 16:13:08 +0000 (09:13 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sun, 30 May 2010 16:13:08 +0000 (09:13 -0700)
* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-2.6: (47 commits)
  mfd: Rename twl5031 sih modules
  mfd: Storage class for timberdale should be before const qualifier
  mfd: Remove unneeded and dangerous clearing of clientdata
  mfd: New AB8500 driver
  gpio: Fix inverted rdc321x gpio data out registers
  mfd: Change rdc321x resources flags to IORESOURCE_IO
  mfd: Move pcf50633 irq related functions to its own file.
  mfd: Use threaded irq for pcf50633
  mfd: pcf50633-adc: Fix potential race in pcf50633_adc_sync_read
  mfd: Fix pcf50633 bitfield logic in interrupt handler
  gpio: rdc321x needs to select MFD_CORE
  mfd: Use menuconfig for quicker config editing
  ARM: AB3550 board configuration and irq for U300
  mfd: AB3550 core driver
  mfd: AB3100 register access change to abx500 API
  mfd: Renamed ab3100.h to abx500.h
  gpio: Add TC35892 GPIO driver
  mfd: Add Toshiba's TC35892 MFD core
  mfd: Delay to mask tsc irq in max8925
  mfd: Remove incorrect wm8350 kfree
  ...

62 files changed:
arch/arm/mach-davinci/board-da850-evm.c
arch/arm/mach-u300/i2c.c
arch/arm/mach-u300/include/mach/irqs.h
arch/arm/mach-ux500/board-mop500.c
arch/x86/include/asm/rdc321x_defs.h [deleted file]
drivers/gpio/Kconfig
drivers/gpio/Makefile
drivers/gpio/janz-ttl.c [new file with mode: 0644]
drivers/gpio/rdc321x-gpio.c [new file with mode: 0644]
drivers/gpio/tc35892-gpio.c [new file with mode: 0644]
drivers/input/touchscreen/Kconfig
drivers/input/touchscreen/Makefile
drivers/input/touchscreen/tps6507x-ts.c [new file with mode: 0644]
drivers/mfd/88pm860x-core.c
drivers/mfd/88pm860x-i2c.c
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/ab3100-core.c
drivers/mfd/ab3100-otp.c
drivers/mfd/ab3550-core.c [new file with mode: 0644]
drivers/mfd/ab4500-core.c [deleted file]
drivers/mfd/ab8500-core.c [new file with mode: 0644]
drivers/mfd/ab8500-spi.c [new file with mode: 0644]
drivers/mfd/abx500-core.c [new file with mode: 0644]
drivers/mfd/da903x.c
drivers/mfd/janz-cmodio.c [new file with mode: 0644]
drivers/mfd/max8925-core.c
drivers/mfd/max8925-i2c.c
drivers/mfd/menelaus.c
drivers/mfd/mfd-core.c
drivers/mfd/pcf50633-adc.c
drivers/mfd/pcf50633-core.c
drivers/mfd/pcf50633-irq.c [new file with mode: 0644]
drivers/mfd/rdc321x-southbridge.c [new file with mode: 0644]
drivers/mfd/t7l66xb.c
drivers/mfd/tc35892.c [new file with mode: 0644]
drivers/mfd/timberdale.c
drivers/mfd/timberdale.h
drivers/mfd/tps65010.c
drivers/mfd/tps6507x.c [new file with mode: 0644]
drivers/mfd/twl4030-irq.c
drivers/mfd/wm831x-core.c
drivers/mfd/wm831x-irq.c
drivers/mfd/wm8350-i2c.c
drivers/mfd/wm8400-core.c
drivers/net/can/Kconfig
drivers/net/can/Makefile
drivers/net/can/janz-ican3.c [new file with mode: 0644]
drivers/regulator/ab3100.c
drivers/regulator/tps6507x-regulator.c
drivers/rtc/rtc-ab3100.c
drivers/watchdog/rdc321x_wdt.c
include/linux/input/tps6507x-ts.h [new file with mode: 0644]
include/linux/mfd/88pm860x.h
include/linux/mfd/ab4500.h [deleted file]
include/linux/mfd/ab8500.h [new file with mode: 0644]
include/linux/mfd/abx500.h [moved from include/linux/mfd/ab3100.h with 51% similarity]
include/linux/mfd/janz.h [new file with mode: 0644]
include/linux/mfd/rdc321x.h [new file with mode: 0644]
include/linux/mfd/tc35892.h [new file with mode: 0644]
include/linux/mfd/tps6507x.h [new file with mode: 0644]
include/linux/mfd/wm831x/core.h

index abd04932917b1c99b24e162fad97d2d298c24481..2ec3095ffb7b564e61a54bcc4e366f32b87e671c 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/i2c.h>
 #include <linux/i2c/at24.h>
 #include <linux/i2c/pca953x.h>
+#include <linux/mfd/tps6507x.h>
 #include <linux/gpio.h>
 #include <linux/platform_device.h>
 #include <linux/mtd/mtd.h>
@@ -24,6 +25,8 @@
 #include <linux/mtd/partitions.h>
 #include <linux/mtd/physmap.h>
 #include <linux/regulator/machine.h>
+#include <linux/mfd/tps6507x.h>
+#include <linux/input/tps6507x-ts.h>
 
 #include <asm/mach-types.h>
 #include <asm/mach/arch.h>
@@ -533,10 +536,24 @@ struct regulator_init_data tps65070_regulator_data[] = {
        },
 };
 
+static struct touchscreen_init_data tps6507x_touchscreen_data = {
+       .poll_period =  30,     /* ms between touch samples */
+       .min_pressure = 0x30,   /* minimum pressure to trigger touch */
+       .vref = 0,              /* turn off vref when not using A/D */
+       .vendor = 0,            /* /sys/class/input/input?/id/vendor */
+       .product = 65070,       /* /sys/class/input/input?/id/product */
+       .version = 0x100,       /* /sys/class/input/input?/id/version */
+};
+
+static struct tps6507x_board tps_board = {
+       .tps6507x_pmic_init_data = &tps65070_regulator_data[0],
+       .tps6507x_ts_init_data = &tps6507x_touchscreen_data,
+};
+
 static struct i2c_board_info __initdata da850evm_tps65070_info[] = {
        {
                I2C_BOARD_INFO("tps6507x", 0x48),
-               .platform_data = &tps65070_regulator_data[0],
+               .platform_data = &tps_board,
        },
 };
 
index c73ed06b6065fb30dc5593b0dafaa0bfa4909709..f0394baa11fa03ec0864bb66462039b88a5fae9d 100644 (file)
@@ -9,7 +9,7 @@
  */
 #include <linux/kernel.h>
 #include <linux/i2c.h>
-#include <linux/mfd/ab3100.h>
+#include <linux/mfd/abx500.h>
 #include <linux/regulator/machine.h>
 #include <linux/amba/bus.h>
 #include <mach/irqs.h>
@@ -46,6 +46,7 @@
 /* BUCK SLEEP 0xAC: 1.05V, Not used, SLEEP_A and B, Not used */
 #define BUCK_SLEEP_SETTING     0xAC
 
+#ifdef CONFIG_AB3100_CORE
 static struct regulator_consumer_supply supply_ldo_c[] = {
        {
                .dev_name = "ab3100-codec",
@@ -253,14 +254,68 @@ static struct ab3100_platform_data ab3100_plf_data = {
                LDO_D_SETTING,
        },
 };
+#endif
+
+#ifdef CONFIG_AB3550_CORE
+static struct abx500_init_settings ab3550_init_settings[] = {
+       {
+               .bank = 0,
+               .reg = AB3550_IMR1,
+               .setting = 0xff
+       },
+       {
+               .bank = 0,
+               .reg = AB3550_IMR2,
+               .setting = 0xff
+       },
+       {
+               .bank = 0,
+               .reg = AB3550_IMR3,
+               .setting = 0xff
+       },
+       {
+               .bank = 0,
+               .reg = AB3550_IMR4,
+               .setting = 0xff
+       },
+       {
+               .bank = 0,
+               .reg = AB3550_IMR5,
+               /* The two most significant bits are not used */
+               .setting = 0x3f
+       },
+};
+
+static struct ab3550_platform_data ab3550_plf_data = {
+       .irq = {
+               .base = IRQ_AB3550_BASE,
+               .count = (IRQ_AB3550_END - IRQ_AB3550_BASE + 1),
+       },
+       .dev_data = {
+       },
+       .init_settings = ab3550_init_settings,
+       .init_settings_sz = ARRAY_SIZE(ab3550_init_settings),
+};
+#endif
 
 static struct i2c_board_info __initdata bus0_i2c_board_info[] = {
+#if defined(CONFIG_AB3550_CORE)
+       {
+               .type = "ab3550",
+               .addr = 0x4A,
+               .irq = IRQ_U300_IRQ0_EXT,
+               .platform_data = &ab3550_plf_data,
+       },
+#elif defined(CONFIG_AB3100_CORE)
        {
                .type = "ab3100",
                .addr = 0x48,
                .irq = IRQ_U300_IRQ0_EXT,
                .platform_data = &ab3100_plf_data,
        },
+#else
+       { },
+#endif
 };
 
 static struct i2c_board_info __initdata bus1_i2c_board_info[] = {
index a6867b12773ed474d851d84d27511403d5f0fecc..09b1b28fa8fd948f611d8dbede95877233ec2045 100644 (file)
 #define U300_NR_IRQS                   48
 #endif
 
+#ifdef CONFIG_AB3550_CORE
+#define IRQ_AB3550_BASE                        (U300_NR_IRQS)
+#define IRQ_AB3550_END                 (IRQ_AB3550_BASE + 37)
+
+#define NR_IRQS                                (IRQ_AB3550_END + 1)
+#else
 #define NR_IRQS U300_NR_IRQS
+#endif
 
 #endif
index 072196c57263ecd3c5a0b34d4a1e2e33b9df6f8c..bb8d7b771817b774a7ff3406e87d46552cf8391c 100644 (file)
@@ -50,7 +50,7 @@ struct pl022_config_chip ab4500_chip_info = {
 
 static struct spi_board_info u8500_spi_devices[] = {
        {
-               .modalias = "ab4500",
+               .modalias = "ab8500",
                .controller_data = &ab4500_chip_info,
                .max_speed_hz = 12000000,
                .bus_num = 0,
diff --git a/arch/x86/include/asm/rdc321x_defs.h b/arch/x86/include/asm/rdc321x_defs.h
deleted file mode 100644 (file)
index c8e9c8b..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#define PFX    "rdc321x: "
-
-/* General purpose configuration and data registers */
-#define RDC3210_CFGREG_ADDR     0x0CF8
-#define RDC3210_CFGREG_DATA     0x0CFC
-
-#define RDC321X_GPIO_CTRL_REG1 0x48
-#define RDC321X_GPIO_CTRL_REG2 0x84
-#define RDC321X_GPIO_DATA_REG1 0x4c
-#define RDC321X_GPIO_DATA_REG2 0x88
-
-#define RDC321X_MAX_GPIO       58
index 4fd0f276df5a5656d9b4d2970ce1f2e202f1de86..724038dab4caa72db78309def86ec448f259407d 100644 (file)
@@ -195,6 +195,13 @@ config GPIO_PCF857X
          This driver provides an in-kernel interface to those GPIOs using
          platform-neutral GPIO calls.
 
+config GPIO_TC35892
+       bool "TC35892 GPIOs"
+       depends on MFD_TC35892
+       help
+         This enables support for the GPIOs found on the TC35892
+         I/O Expander.
+
 config GPIO_TWL4030
        tristate "TWL4030, TWL5030, and TPS659x0 GPIOs"
        depends on TWL4030_CORE
@@ -282,6 +289,15 @@ config GPIO_TIMBERDALE
        ---help---
        Add support for the GPIO IP in the timberdale FPGA.
 
+config GPIO_RDC321X
+       tristate "RDC R-321x GPIO support"
+       depends on PCI && GPIOLIB
+       select MFD_CORE
+       select MFD_RDC321X
+       help
+         Support for the RDC R321x SoC GPIOs over southbridge
+         PCI configuration space.
+
 comment "SPI GPIO expanders:"
 
 config GPIO_MAX7301
@@ -317,4 +333,14 @@ config GPIO_UCB1400
          To compile this driver as a module, choose M here: the
          module will be called ucb1400_gpio.
 
+comment "MODULbus GPIO expanders:"
+
+config GPIO_JANZ_TTL
+       tristate "Janz VMOD-TTL Digital IO Module"
+       depends on MFD_JANZ_CMODIO
+       help
+         This enables support for the Janz VMOD-TTL Digital IO module.
+         This driver provides support for driving the pins in output
+         mode only. Input mode is not supported.
+
 endif
index 10f3f8d958b1f7c2d93b9f84f7d24f2e87201e72..51c3cdd41b5aac95514b41e58c105208ba278e60 100644 (file)
@@ -16,6 +16,7 @@ obj-$(CONFIG_GPIO_MCP23S08)   += mcp23s08.o
 obj-$(CONFIG_GPIO_PCA953X)     += pca953x.o
 obj-$(CONFIG_GPIO_PCF857X)     += pcf857x.o
 obj-$(CONFIG_GPIO_PL061)       += pl061.o
+obj-$(CONFIG_GPIO_TC35892)     += tc35892-gpio.o
 obj-$(CONFIG_GPIO_TIMBERDALE)  += timbgpio.o
 obj-$(CONFIG_GPIO_TWL4030)     += twl4030-gpio.o
 obj-$(CONFIG_GPIO_UCB1400)     += ucb1400_gpio.o
@@ -27,4 +28,6 @@ obj-$(CONFIG_GPIO_VR41XX)     += vr41xx_giu.o
 obj-$(CONFIG_GPIO_WM831X)      += wm831x-gpio.o
 obj-$(CONFIG_GPIO_WM8350)      += wm8350-gpiolib.o
 obj-$(CONFIG_GPIO_WM8994)      += wm8994-gpio.o
-obj-$(CONFIG_GPIO_SCH)         += sch_gpio.o
\ No newline at end of file
+obj-$(CONFIG_GPIO_SCH)         += sch_gpio.o
+obj-$(CONFIG_GPIO_RDC321X)     += rdc321x-gpio.o
+obj-$(CONFIG_GPIO_JANZ_TTL)    += janz-ttl.o
diff --git a/drivers/gpio/janz-ttl.c b/drivers/gpio/janz-ttl.c
new file mode 100644 (file)
index 0000000..813ac07
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * Janz MODULbus VMOD-TTL GPIO Driver
+ *
+ * Copyright (c) 2010 Ira W. Snyder <iws@ovro.caltech.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/janz.h>
+
+#define DRV_NAME "janz-ttl"
+
+#define PORTA_DIRECTION                0x23
+#define PORTB_DIRECTION                0x2B
+#define PORTC_DIRECTION                0x06
+#define PORTA_IOCTL            0x24
+#define PORTB_IOCTL            0x2C
+#define PORTC_IOCTL            0x07
+
+#define MASTER_INT_CTL         0x00
+#define MASTER_CONF_CTL                0x01
+
+#define CONF_PAE               (1 << 2)
+#define CONF_PBE               (1 << 7)
+#define CONF_PCE               (1 << 4)
+
+struct ttl_control_regs {
+       __be16 portc;
+       __be16 portb;
+       __be16 porta;
+       __be16 control;
+};
+
+struct ttl_module {
+       struct gpio_chip gpio;
+
+       /* base address of registers */
+       struct ttl_control_regs __iomem *regs;
+
+       u8 portc_shadow;
+       u8 portb_shadow;
+       u8 porta_shadow;
+
+       spinlock_t lock;
+};
+
+static int ttl_get_value(struct gpio_chip *gpio, unsigned offset)
+{
+       struct ttl_module *mod = dev_get_drvdata(gpio->dev);
+       u8 *shadow;
+       int ret;
+
+       if (offset < 8) {
+               shadow = &mod->porta_shadow;
+       } else if (offset < 16) {
+               shadow = &mod->portb_shadow;
+               offset -= 8;
+       } else {
+               shadow = &mod->portc_shadow;
+               offset -= 16;
+       }
+
+       spin_lock(&mod->lock);
+       ret = *shadow & (1 << offset);
+       spin_unlock(&mod->lock);
+       return ret;
+}
+
+static void ttl_set_value(struct gpio_chip *gpio, unsigned offset, int value)
+{
+       struct ttl_module *mod = dev_get_drvdata(gpio->dev);
+       void __iomem *port;
+       u8 *shadow;
+
+       if (offset < 8) {
+               port = &mod->regs->porta;
+               shadow = &mod->porta_shadow;
+       } else if (offset < 16) {
+               port = &mod->regs->portb;
+               shadow = &mod->portb_shadow;
+               offset -= 8;
+       } else {
+               port = &mod->regs->portc;
+               shadow = &mod->portc_shadow;
+               offset -= 16;
+       }
+
+       spin_lock(&mod->lock);
+       if (value)
+               *shadow |= (1 << offset);
+       else
+               *shadow &= ~(1 << offset);
+
+       iowrite16be(*shadow, port);
+       spin_unlock(&mod->lock);
+}
+
+static void __devinit ttl_write_reg(struct ttl_module *mod, u8 reg, u16 val)
+{
+       iowrite16be(reg, &mod->regs->control);
+       iowrite16be(val, &mod->regs->control);
+}
+
+static void __devinit ttl_setup_device(struct ttl_module *mod)
+{
+       /* reset the device to a known state */
+       iowrite16be(0x0000, &mod->regs->control);
+       iowrite16be(0x0001, &mod->regs->control);
+       iowrite16be(0x0000, &mod->regs->control);
+
+       /* put all ports in open-drain mode */
+       ttl_write_reg(mod, PORTA_IOCTL, 0x00ff);
+       ttl_write_reg(mod, PORTB_IOCTL, 0x00ff);
+       ttl_write_reg(mod, PORTC_IOCTL, 0x000f);
+
+       /* set all ports as outputs */
+       ttl_write_reg(mod, PORTA_DIRECTION, 0x0000);
+       ttl_write_reg(mod, PORTB_DIRECTION, 0x0000);
+       ttl_write_reg(mod, PORTC_DIRECTION, 0x0000);
+
+       /* set all ports to drive zeroes */
+       iowrite16be(0x0000, &mod->regs->porta);
+       iowrite16be(0x0000, &mod->regs->portb);
+       iowrite16be(0x0000, &mod->regs->portc);
+
+       /* enable all ports */
+       ttl_write_reg(mod, MASTER_CONF_CTL, CONF_PAE | CONF_PBE | CONF_PCE);
+}
+
+static int __devinit ttl_probe(struct platform_device *pdev)
+{
+       struct janz_platform_data *pdata;
+       struct device *dev = &pdev->dev;
+       struct ttl_module *mod;
+       struct gpio_chip *gpio;
+       struct resource *res;
+       int ret;
+
+       pdata = pdev->dev.platform_data;
+       if (!pdata) {
+               dev_err(dev, "no platform data\n");
+               ret = -ENXIO;
+               goto out_return;
+       }
+
+       mod = kzalloc(sizeof(*mod), GFP_KERNEL);
+       if (!mod) {
+               dev_err(dev, "unable to allocate private data\n");
+               ret = -ENOMEM;
+               goto out_return;
+       }
+
+       platform_set_drvdata(pdev, mod);
+       spin_lock_init(&mod->lock);
+
+       /* get access to the MODULbus registers for this module */
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(dev, "MODULbus registers not found\n");
+               ret = -ENODEV;
+               goto out_free_mod;
+       }
+
+       mod->regs = ioremap(res->start, resource_size(res));
+       if (!mod->regs) {
+               dev_err(dev, "MODULbus registers not ioremap\n");
+               ret = -ENOMEM;
+               goto out_free_mod;
+       }
+
+       ttl_setup_device(mod);
+
+       /* Initialize the GPIO data structures */
+       gpio = &mod->gpio;
+       gpio->dev = &pdev->dev;
+       gpio->label = pdev->name;
+       gpio->get = ttl_get_value;
+       gpio->set = ttl_set_value;
+       gpio->owner = THIS_MODULE;
+
+       /* request dynamic allocation */
+       gpio->base = -1;
+       gpio->ngpio = 20;
+
+       ret = gpiochip_add(gpio);
+       if (ret) {
+               dev_err(dev, "unable to add GPIO chip\n");
+               goto out_iounmap_regs;
+       }
+
+       dev_info(&pdev->dev, "module %d: registered GPIO device\n",
+                            pdata->modno);
+       return 0;
+
+out_iounmap_regs:
+       iounmap(mod->regs);
+out_free_mod:
+       kfree(mod);
+out_return:
+       return ret;
+}
+
+static int __devexit ttl_remove(struct platform_device *pdev)
+{
+       struct ttl_module *mod = platform_get_drvdata(pdev);
+       struct device *dev = &pdev->dev;
+       int ret;
+
+       ret = gpiochip_remove(&mod->gpio);
+       if (ret) {
+               dev_err(dev, "unable to remove GPIO chip\n");
+               return ret;
+       }
+
+       iounmap(mod->regs);
+       kfree(mod);
+       return 0;
+}
+
+static struct platform_driver ttl_driver = {
+       .driver         = {
+               .name   = DRV_NAME,
+               .owner  = THIS_MODULE,
+       },
+       .probe          = ttl_probe,
+       .remove         = __devexit_p(ttl_remove),
+};
+
+static int __init ttl_init(void)
+{
+       return platform_driver_register(&ttl_driver);
+}
+
+static void __exit ttl_exit(void)
+{
+       platform_driver_unregister(&ttl_driver);
+}
+
+MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>");
+MODULE_DESCRIPTION("Janz MODULbus VMOD-TTL Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:janz-ttl");
+
+module_init(ttl_init);
+module_exit(ttl_exit);
diff --git a/drivers/gpio/rdc321x-gpio.c b/drivers/gpio/rdc321x-gpio.c
new file mode 100644 (file)
index 0000000..2762698
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * RDC321x GPIO driver
+ *
+ * Copyright (C) 2008, Volker Weiss <dev@tintuc.de>
+ * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/mfd/rdc321x.h>
+#include <linux/slab.h>
+
+struct rdc321x_gpio {
+       spinlock_t              lock;
+       struct pci_dev          *sb_pdev;
+       u32                     data_reg[2];
+       int                     reg1_ctrl_base;
+       int                     reg1_data_base;
+       int                     reg2_ctrl_base;
+       int                     reg2_data_base;
+       struct gpio_chip        chip;
+};
+
+/* read GPIO pin */
+static int rdc_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
+{
+       struct rdc321x_gpio *gpch;
+       u32 value = 0;
+       int reg;
+
+       gpch = container_of(chip, struct rdc321x_gpio, chip);
+       reg = gpio < 32 ? gpch->reg1_data_base : gpch->reg2_data_base;
+
+       spin_lock(&gpch->lock);
+       pci_write_config_dword(gpch->sb_pdev, reg,
+                                       gpch->data_reg[gpio < 32 ? 0 : 1]);
+       pci_read_config_dword(gpch->sb_pdev, reg, &value);
+       spin_unlock(&gpch->lock);
+
+       return (1 << (gpio & 0x1f)) & value ? 1 : 0;
+}
+
+static void rdc_gpio_set_value_impl(struct gpio_chip *chip,
+                               unsigned gpio, int value)
+{
+       struct rdc321x_gpio *gpch;
+       int reg = (gpio < 32) ? 0 : 1;
+
+       gpch = container_of(chip, struct rdc321x_gpio, chip);
+
+       if (value)
+               gpch->data_reg[reg] |= 1 << (gpio & 0x1f);
+       else
+               gpch->data_reg[reg] &= ~(1 << (gpio & 0x1f));
+
+       pci_write_config_dword(gpch->sb_pdev,
+                       reg ? gpch->reg2_data_base : gpch->reg1_data_base,
+                       gpch->data_reg[reg]);
+}
+
+/* set GPIO pin to value */
+static void rdc_gpio_set_value(struct gpio_chip *chip,
+                               unsigned gpio, int value)
+{
+       struct rdc321x_gpio *gpch;
+
+       gpch = container_of(chip, struct rdc321x_gpio, chip);
+       spin_lock(&gpch->lock);
+       rdc_gpio_set_value_impl(chip, gpio, value);
+       spin_unlock(&gpch->lock);
+}
+
+static int rdc_gpio_config(struct gpio_chip *chip,
+                               unsigned gpio, int value)
+{
+       struct rdc321x_gpio *gpch;
+       int err;
+       u32 reg;
+
+       gpch = container_of(chip, struct rdc321x_gpio, chip);
+
+       spin_lock(&gpch->lock);
+       err = pci_read_config_dword(gpch->sb_pdev, gpio < 32 ?
+                       gpch->reg1_ctrl_base : gpch->reg2_ctrl_base, &reg);
+       if (err)
+               goto unlock;
+
+       reg |= 1 << (gpio & 0x1f);
+
+       err = pci_write_config_dword(gpch->sb_pdev, gpio < 32 ?
+                       gpch->reg1_ctrl_base : gpch->reg2_ctrl_base, reg);
+       if (err)
+               goto unlock;
+
+       rdc_gpio_set_value_impl(chip, gpio, value);
+
+unlock:
+       spin_unlock(&gpch->lock);
+
+       return err;
+}
+
+/* configure GPIO pin as input */
+static int rdc_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
+{
+       return rdc_gpio_config(chip, gpio, 1);
+}
+
+/*
+ * Cache the initial value of both GPIO data registers
+ */
+static int __devinit rdc321x_gpio_probe(struct platform_device *pdev)
+{
+       int err;
+       struct resource *r;
+       struct rdc321x_gpio *rdc321x_gpio_dev;
+       struct rdc321x_gpio_pdata *pdata;
+
+       pdata = pdev->dev.platform_data;
+       if (!pdata) {
+               dev_err(&pdev->dev, "no platform data supplied\n");
+               return -ENODEV;
+       }
+
+       rdc321x_gpio_dev = kzalloc(sizeof(struct rdc321x_gpio), GFP_KERNEL);
+       if (!rdc321x_gpio_dev) {
+               dev_err(&pdev->dev, "failed to allocate private data\n");
+               return -ENOMEM;
+       }
+
+       r = platform_get_resource_byname(pdev, IORESOURCE_IO, "gpio-reg1");
+       if (!r) {
+               dev_err(&pdev->dev, "failed to get gpio-reg1 resource\n");
+               err = -ENODEV;
+               goto out_free;
+       }
+
+       spin_lock_init(&rdc321x_gpio_dev->lock);
+       rdc321x_gpio_dev->sb_pdev = pdata->sb_pdev;
+       rdc321x_gpio_dev->reg1_ctrl_base = r->start;
+       rdc321x_gpio_dev->reg1_data_base = r->start + 0x4;
+
+       r = platform_get_resource_byname(pdev, IORESOURCE_IO, "gpio-reg2");
+       if (!r) {
+               dev_err(&pdev->dev, "failed to get gpio-reg2 resource\n");
+               err = -ENODEV;
+               goto out_free;
+       }
+
+       rdc321x_gpio_dev->reg2_ctrl_base = r->start;
+       rdc321x_gpio_dev->reg2_data_base = r->start + 0x4;
+
+       rdc321x_gpio_dev->chip.label = "rdc321x-gpio";
+       rdc321x_gpio_dev->chip.direction_input = rdc_gpio_direction_input;
+       rdc321x_gpio_dev->chip.direction_output = rdc_gpio_config;
+       rdc321x_gpio_dev->chip.get = rdc_gpio_get_value;
+       rdc321x_gpio_dev->chip.set = rdc_gpio_set_value;
+       rdc321x_gpio_dev->chip.base = 0;
+       rdc321x_gpio_dev->chip.ngpio = pdata->max_gpios;
+
+       platform_set_drvdata(pdev, rdc321x_gpio_dev);
+
+       /* This might not be, what others (BIOS, bootloader, etc.)
+          wrote to these registers before, but it's a good guess. Still
+          better than just using 0xffffffff. */
+       err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev,
+                                       rdc321x_gpio_dev->reg1_data_base,
+                                       &rdc321x_gpio_dev->data_reg[0]);
+       if (err)
+               goto out_drvdata;
+
+       err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev,
+                                       rdc321x_gpio_dev->reg2_data_base,
+                                       &rdc321x_gpio_dev->data_reg[1]);
+       if (err)
+               goto out_drvdata;
+
+       dev_info(&pdev->dev, "registering %d GPIOs\n",
+                                       rdc321x_gpio_dev->chip.ngpio);
+       return gpiochip_add(&rdc321x_gpio_dev->chip);
+
+out_drvdata:
+       platform_set_drvdata(pdev, NULL);
+out_free:
+       kfree(rdc321x_gpio_dev);
+       return err;
+}
+
+static int __devexit rdc321x_gpio_remove(struct platform_device *pdev)
+{
+       int ret;
+       struct rdc321x_gpio *rdc321x_gpio_dev = platform_get_drvdata(pdev);
+
+       ret = gpiochip_remove(&rdc321x_gpio_dev->chip);
+       if (ret)
+               dev_err(&pdev->dev, "failed to unregister chip\n");
+
+       kfree(rdc321x_gpio_dev);
+       platform_set_drvdata(pdev, NULL);
+
+       return ret;
+}
+
+static struct platform_driver rdc321x_gpio_driver = {
+       .driver.name    = "rdc321x-gpio",
+       .driver.owner   = THIS_MODULE,
+       .probe          = rdc321x_gpio_probe,
+       .remove         = __devexit_p(rdc321x_gpio_remove),
+};
+
+static int __init rdc321x_gpio_init(void)
+{
+       return platform_driver_register(&rdc321x_gpio_driver);
+}
+
+static void __exit rdc321x_gpio_exit(void)
+{
+       platform_driver_unregister(&rdc321x_gpio_driver);
+}
+
+module_init(rdc321x_gpio_init);
+module_exit(rdc321x_gpio_exit);
+
+MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
+MODULE_DESCRIPTION("RDC321x GPIO driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rdc321x-gpio");
diff --git a/drivers/gpio/tc35892-gpio.c b/drivers/gpio/tc35892-gpio.c
new file mode 100644 (file)
index 0000000..1be6288
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Hanumath Prasad <hanumath.prasad@stericsson.com> for ST-Ericsson
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/tc35892.h>
+
+/*
+ * These registers are modified under the irq bus lock and cached to avoid
+ * unnecessary writes in bus_sync_unlock.
+ */
+enum { REG_IBE, REG_IEV, REG_IS, REG_IE };
+
+#define CACHE_NR_REGS  4
+#define CACHE_NR_BANKS 3
+
+struct tc35892_gpio {
+       struct gpio_chip chip;
+       struct tc35892 *tc35892;
+       struct device *dev;
+       struct mutex irq_lock;
+
+       int irq_base;
+
+       /* Caches of interrupt control registers for bus_lock */
+       u8 regs[CACHE_NR_REGS][CACHE_NR_BANKS];
+       u8 oldregs[CACHE_NR_REGS][CACHE_NR_BANKS];
+};
+
+static inline struct tc35892_gpio *to_tc35892_gpio(struct gpio_chip *chip)
+{
+       return container_of(chip, struct tc35892_gpio, chip);
+}
+
+static int tc35892_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+       struct tc35892_gpio *tc35892_gpio = to_tc35892_gpio(chip);
+       struct tc35892 *tc35892 = tc35892_gpio->tc35892;
+       u8 reg = TC35892_GPIODATA0 + (offset / 8) * 2;
+       u8 mask = 1 << (offset % 8);
+       int ret;
+
+       ret = tc35892_reg_read(tc35892, reg);
+       if (ret < 0)
+               return ret;
+
+       return ret & mask;
+}
+
+static void tc35892_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+       struct tc35892_gpio *tc35892_gpio = to_tc35892_gpio(chip);
+       struct tc35892 *tc35892 = tc35892_gpio->tc35892;
+       u8 reg = TC35892_GPIODATA0 + (offset / 8) * 2;
+       unsigned pos = offset % 8;
+       u8 data[] = {!!val << pos, 1 << pos};
+
+       tc35892_block_write(tc35892, reg, ARRAY_SIZE(data), data);
+}
+
+static int tc35892_gpio_direction_output(struct gpio_chip *chip,
+                                        unsigned offset, int val)
+{
+       struct tc35892_gpio *tc35892_gpio = to_tc35892_gpio(chip);
+       struct tc35892 *tc35892 = tc35892_gpio->tc35892;
+       u8 reg = TC35892_GPIODIR0 + offset / 8;
+       unsigned pos = offset % 8;
+
+       tc35892_gpio_set(chip, offset, val);
+
+       return tc35892_set_bits(tc35892, reg, 1 << pos, 1 << pos);
+}
+
+static int tc35892_gpio_direction_input(struct gpio_chip *chip,
+                                       unsigned offset)
+{
+       struct tc35892_gpio *tc35892_gpio = to_tc35892_gpio(chip);
+       struct tc35892 *tc35892 = tc35892_gpio->tc35892;
+       u8 reg = TC35892_GPIODIR0 + offset / 8;
+       unsigned pos = offset % 8;
+
+       return tc35892_set_bits(tc35892, reg, 1 << pos, 0);
+}
+
+static int tc35892_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+       struct tc35892_gpio *tc35892_gpio = to_tc35892_gpio(chip);
+
+       return tc35892_gpio->irq_base + offset;
+}
+
+static struct gpio_chip template_chip = {
+       .label                  = "tc35892",
+       .owner                  = THIS_MODULE,
+       .direction_input        = tc35892_gpio_direction_input,
+       .get                    = tc35892_gpio_get,
+       .direction_output       = tc35892_gpio_direction_output,
+       .set                    = tc35892_gpio_set,
+       .to_irq                 = tc35892_gpio_to_irq,
+       .can_sleep              = 1,
+};
+
+static int tc35892_gpio_irq_set_type(unsigned int irq, unsigned int type)
+{
+       struct tc35892_gpio *tc35892_gpio = get_irq_chip_data(irq);
+       int offset = irq - tc35892_gpio->irq_base;
+       int regoffset = offset / 8;
+       int mask = 1 << (offset % 8);
+
+       if (type == IRQ_TYPE_EDGE_BOTH) {
+               tc35892_gpio->regs[REG_IBE][regoffset] |= mask;
+               return 0;
+       }
+
+       tc35892_gpio->regs[REG_IBE][regoffset] &= ~mask;
+
+       if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH)
+               tc35892_gpio->regs[REG_IS][regoffset] |= mask;
+       else
+               tc35892_gpio->regs[REG_IS][regoffset] &= ~mask;
+
+       if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH)
+               tc35892_gpio->regs[REG_IEV][regoffset] |= mask;
+       else
+               tc35892_gpio->regs[REG_IEV][regoffset] &= ~mask;
+
+       return 0;
+}
+
+static void tc35892_gpio_irq_lock(unsigned int irq)
+{
+       struct tc35892_gpio *tc35892_gpio = get_irq_chip_data(irq);
+
+       mutex_lock(&tc35892_gpio->irq_lock);
+}
+
+static void tc35892_gpio_irq_sync_unlock(unsigned int irq)
+{
+       struct tc35892_gpio *tc35892_gpio = get_irq_chip_data(irq);
+       struct tc35892 *tc35892 = tc35892_gpio->tc35892;
+       static const u8 regmap[] = {
+               [REG_IBE]       = TC35892_GPIOIBE0,
+               [REG_IEV]       = TC35892_GPIOIEV0,
+               [REG_IS]        = TC35892_GPIOIS0,
+               [REG_IE]        = TC35892_GPIOIE0,
+       };
+       int i, j;
+
+       for (i = 0; i < CACHE_NR_REGS; i++) {
+               for (j = 0; j < CACHE_NR_BANKS; j++) {
+                       u8 old = tc35892_gpio->oldregs[i][j];
+                       u8 new = tc35892_gpio->regs[i][j];
+
+                       if (new == old)
+                               continue;
+
+                       tc35892_gpio->oldregs[i][j] = new;
+                       tc35892_reg_write(tc35892, regmap[i] + j * 8, new);
+               }
+       }
+
+       mutex_unlock(&tc35892_gpio->irq_lock);
+}
+
+static void tc35892_gpio_irq_mask(unsigned int irq)
+{
+       struct tc35892_gpio *tc35892_gpio = get_irq_chip_data(irq);
+       int offset = irq - tc35892_gpio->irq_base;
+       int regoffset = offset / 8;
+       int mask = 1 << (offset % 8);
+
+       tc35892_gpio->regs[REG_IE][regoffset] &= ~mask;
+}
+
+static void tc35892_gpio_irq_unmask(unsigned int irq)
+{
+       struct tc35892_gpio *tc35892_gpio = get_irq_chip_data(irq);
+       int offset = irq - tc35892_gpio->irq_base;
+       int regoffset = offset / 8;
+       int mask = 1 << (offset % 8);
+
+       tc35892_gpio->regs[REG_IE][regoffset] |= mask;
+}
+
+static struct irq_chip tc35892_gpio_irq_chip = {
+       .name                   = "tc35892-gpio",
+       .bus_lock               = tc35892_gpio_irq_lock,
+       .bus_sync_unlock        = tc35892_gpio_irq_sync_unlock,
+       .mask                   = tc35892_gpio_irq_mask,
+       .unmask                 = tc35892_gpio_irq_unmask,
+       .set_type               = tc35892_gpio_irq_set_type,
+};
+
+static irqreturn_t tc35892_gpio_irq(int irq, void *dev)
+{
+       struct tc35892_gpio *tc35892_gpio = dev;
+       struct tc35892 *tc35892 = tc35892_gpio->tc35892;
+       u8 status[CACHE_NR_BANKS];
+       int ret;
+       int i;
+
+       ret = tc35892_block_read(tc35892, TC35892_GPIOMIS0,
+                                ARRAY_SIZE(status), status);
+       if (ret < 0)
+               return IRQ_NONE;
+
+       for (i = 0; i < ARRAY_SIZE(status); i++) {
+               unsigned int stat = status[i];
+               if (!stat)
+                       continue;
+
+               while (stat) {
+                       int bit = __ffs(stat);
+                       int line = i * 8 + bit;
+
+                       handle_nested_irq(tc35892_gpio->irq_base + line);
+                       stat &= ~(1 << bit);
+               }
+
+               tc35892_reg_write(tc35892, TC35892_GPIOIC0 + i, status[i]);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int tc35892_gpio_irq_init(struct tc35892_gpio *tc35892_gpio)
+{
+       int base = tc35892_gpio->irq_base;
+       int irq;
+
+       for (irq = base; irq < base + tc35892_gpio->chip.ngpio; irq++) {
+               set_irq_chip_data(irq, tc35892_gpio);
+               set_irq_chip_and_handler(irq, &tc35892_gpio_irq_chip,
+                                        handle_simple_irq);
+               set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+               set_irq_flags(irq, IRQF_VALID);
+#else
+               set_irq_noprobe(irq);
+#endif
+       }
+
+       return 0;
+}
+
+static void tc35892_gpio_irq_remove(struct tc35892_gpio *tc35892_gpio)
+{
+       int base = tc35892_gpio->irq_base;
+       int irq;
+
+       for (irq = base; irq < base + tc35892_gpio->chip.ngpio; irq++) {
+#ifdef CONFIG_ARM
+               set_irq_flags(irq, 0);
+#endif
+               set_irq_chip_and_handler(irq, NULL, NULL);
+               set_irq_chip_data(irq, NULL);
+       }
+}
+
+static int __devinit tc35892_gpio_probe(struct platform_device *pdev)
+{
+       struct tc35892 *tc35892 = dev_get_drvdata(pdev->dev.parent);
+       struct tc35892_gpio_platform_data *pdata;
+       struct tc35892_gpio *tc35892_gpio;
+       int ret;
+       int irq;
+
+       pdata = tc35892->pdata->gpio;
+       if (!pdata)
+               return -ENODEV;
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0)
+               return irq;
+
+       tc35892_gpio = kzalloc(sizeof(struct tc35892_gpio), GFP_KERNEL);
+       if (!tc35892_gpio)
+               return -ENOMEM;
+
+       mutex_init(&tc35892_gpio->irq_lock);
+
+       tc35892_gpio->dev = &pdev->dev;
+       tc35892_gpio->tc35892 = tc35892;
+
+       tc35892_gpio->chip = template_chip;
+       tc35892_gpio->chip.ngpio = tc35892->num_gpio;
+       tc35892_gpio->chip.dev = &pdev->dev;
+       tc35892_gpio->chip.base = pdata->gpio_base;
+
+       tc35892_gpio->irq_base = tc35892->irq_base + TC35892_INT_GPIO(0);
+
+       /* Bring the GPIO module out of reset */
+       ret = tc35892_set_bits(tc35892, TC35892_RSTCTRL,
+                              TC35892_RSTCTRL_GPIRST, 0);
+       if (ret < 0)
+               goto out_free;
+
+       ret = tc35892_gpio_irq_init(tc35892_gpio);
+       if (ret)
+               goto out_free;
+
+       ret = request_threaded_irq(irq, NULL, tc35892_gpio_irq, IRQF_ONESHOT,
+                                  "tc35892-gpio", tc35892_gpio);
+       if (ret) {
+               dev_err(&pdev->dev, "unable to get irq: %d\n", ret);
+               goto out_removeirq;
+       }
+
+       ret = gpiochip_add(&tc35892_gpio->chip);
+       if (ret) {
+               dev_err(&pdev->dev, "unable to add gpiochip: %d\n", ret);
+               goto out_freeirq;
+       }
+
+       platform_set_drvdata(pdev, tc35892_gpio);
+
+       return 0;
+
+out_freeirq:
+       free_irq(irq, tc35892_gpio);
+out_removeirq:
+       tc35892_gpio_irq_remove(tc35892_gpio);
+out_free:
+       kfree(tc35892_gpio);
+       return ret;
+}
+
+static int __devexit tc35892_gpio_remove(struct platform_device *pdev)
+{
+       struct tc35892_gpio *tc35892_gpio = platform_get_drvdata(pdev);
+       int irq = platform_get_irq(pdev, 0);
+       int ret;
+
+       ret = gpiochip_remove(&tc35892_gpio->chip);
+       if (ret < 0) {
+               dev_err(tc35892_gpio->dev,
+                       "unable to remove gpiochip: %d\n", ret);
+               return ret;
+       }
+
+       free_irq(irq, tc35892_gpio);
+       tc35892_gpio_irq_remove(tc35892_gpio);
+
+       platform_set_drvdata(pdev, NULL);
+       kfree(tc35892_gpio);
+
+       return 0;
+}
+
+static struct platform_driver tc35892_gpio_driver = {
+       .driver.name    = "tc35892-gpio",
+       .driver.owner   = THIS_MODULE,
+       .probe          = tc35892_gpio_probe,
+       .remove         = __devexit_p(tc35892_gpio_remove),
+};
+
+static int __init tc35892_gpio_init(void)
+{
+       return platform_driver_register(&tc35892_gpio_driver);
+}
+subsys_initcall(tc35892_gpio_init);
+
+static void __exit tc35892_gpio_exit(void)
+{
+       platform_driver_unregister(&tc35892_gpio_driver);
+}
+module_exit(tc35892_gpio_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("TC35892 GPIO driver");
+MODULE_AUTHOR("Hanumath Prasad, Rabin Vincent");
index b9f58ca82fd107b41938e1fe665b6ae94499f0a8..6703c6b9800a4c290fde19dc5f1ab10c4ed2dc1f 100644 (file)
@@ -590,4 +590,17 @@ config TOUCHSCREEN_PCAP
 
          To compile this driver as a module, choose M here: the
          module will be called pcap_ts.
+
+config TOUCHSCREEN_TPS6507X
+       tristate "TPS6507x based touchscreens"
+       depends on I2C
+       help
+         Say Y here if you have a TPS6507x based touchscreen
+         controller.
+
+         If unsure, say N.
+
+         To compile this driver as a module, choose M here: the
+         module will be called tps6507x_ts.
+
 endif
index 8ad36eef90a2687f13004a4cc6bef5a57e0a697a..497964a7a2146dfe39f27361cd134480b35f2c78 100644 (file)
@@ -46,3 +46,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_ATMEL)        += atmel-wm97xx.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE)     += mainstone-wm97xx.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE)      += zylonite-wm97xx.o
 obj-$(CONFIG_TOUCHSCREEN_W90X900)      += w90p910_ts.o
+obj-$(CONFIG_TOUCHSCREEN_TPS6507X)     += tps6507x-ts.o
diff --git a/drivers/input/touchscreen/tps6507x-ts.c b/drivers/input/touchscreen/tps6507x-ts.c
new file mode 100644 (file)
index 0000000..5de80a1
--- /dev/null
@@ -0,0 +1,400 @@
+/*
+ * drivers/input/touchscreen/tps6507x_ts.c
+ *
+ * Touchscreen driver for the tps6507x chip.
+ *
+ * Copyright (c) 2009 RidgeRun (todd.fischer@ridgerun.com)
+ *
+ * Credits:
+ *
+ *    Using code from tsc2007, MtekVision Co., Ltd.
+ *
+ * For licencing details see kernel-base/COPYING
+ *
+ * TPS65070, TPS65073, TPS650731, and TPS650732 support
+ * 10 bit touch screen interface.
+ */
+
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/tps6507x.h>
+#include <linux/input/tps6507x-ts.h>
+#include <linux/delay.h>
+
+#define TSC_DEFAULT_POLL_PERIOD 30 /* ms */
+#define TPS_DEFAULT_MIN_PRESSURE 0x30
+#define MAX_10BIT ((1 << 10) - 1)
+
+#define        TPS6507X_ADCONFIG_CONVERT_TS (TPS6507X_ADCONFIG_AD_ENABLE | \
+                                        TPS6507X_ADCONFIG_START_CONVERSION | \
+                                        TPS6507X_ADCONFIG_INPUT_REAL_TSC)
+#define        TPS6507X_ADCONFIG_POWER_DOWN_TS (TPS6507X_ADCONFIG_INPUT_REAL_TSC)
+
+struct ts_event {
+       u16     x;
+       u16     y;
+       u16     pressure;
+};
+
+struct tps6507x_ts {
+       struct input_dev        *input_dev;
+       struct device           *dev;
+       char                    phys[32];
+       struct workqueue_struct *wq;
+       struct delayed_work     work;
+       unsigned                polling;        /* polling is active */
+       struct ts_event         tc;
+       struct tps6507x_dev     *mfd;
+       u16                     model;
+       unsigned                pendown;
+       int                     irq;
+       void                    (*clear_penirq)(void);
+       unsigned long           poll_period;    /* ms */
+       u16                     min_pressure;
+       int                     vref;           /* non-zero to leave vref on */
+};
+
+static int tps6507x_read_u8(struct tps6507x_ts *tsc, u8 reg, u8 *data)
+{
+       int err;
+
+       err = tsc->mfd->read_dev(tsc->mfd, reg, 1, data);
+
+       if (err)
+               return err;
+
+       return 0;
+}
+
+static int tps6507x_write_u8(struct tps6507x_ts *tsc, u8 reg, u8 data)
+{
+       return tsc->mfd->write_dev(tsc->mfd, reg, 1, &data);
+}
+
+static s32 tps6507x_adc_conversion(struct tps6507x_ts *tsc,
+                                  u8 tsc_mode, u16 *value)
+{
+       s32 ret;
+       u8 adc_status;
+       u8 result;
+
+       /* Route input signal to A/D converter */
+
+       ret = tps6507x_write_u8(tsc, TPS6507X_REG_TSCMODE, tsc_mode);
+       if (ret) {
+               dev_err(tsc->dev, "TSC mode read failed\n");
+               goto err;
+       }
+
+       /* Start A/D conversion */
+
+       ret = tps6507x_write_u8(tsc, TPS6507X_REG_ADCONFIG,
+                               TPS6507X_ADCONFIG_CONVERT_TS);
+       if (ret) {
+               dev_err(tsc->dev, "ADC config write failed\n");
+               return ret;
+       }
+
+       do {
+               ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADCONFIG,
+                                      &adc_status);
+               if (ret) {
+                       dev_err(tsc->dev, "ADC config read failed\n");
+                       goto err;
+               }
+       } while (adc_status & TPS6507X_ADCONFIG_START_CONVERSION);
+
+       ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADRESULT_2, &result);
+       if (ret) {
+               dev_err(tsc->dev, "ADC result 2 read failed\n");
+               goto err;
+       }
+
+       *value = (result & TPS6507X_REG_ADRESULT_2_MASK) << 8;
+
+       ret = tps6507x_read_u8(tsc, TPS6507X_REG_ADRESULT_1, &result);
+       if (ret) {
+               dev_err(tsc->dev, "ADC result 1 read failed\n");
+               goto err;
+       }
+
+       *value |= result;
+
+       dev_dbg(tsc->dev, "TSC channel %d = 0x%X\n", tsc_mode, *value);
+
+err:
+       return ret;
+}
+
+/* Need to call tps6507x_adc_standby() after using A/D converter for the
+ * touch screen interrupt to work properly.
+ */
+
+static s32 tps6507x_adc_standby(struct tps6507x_ts *tsc)
+{
+       s32 ret;
+       s32 loops = 0;
+       u8 val;
+
+       ret = tps6507x_write_u8(tsc,  TPS6507X_REG_ADCONFIG,
+                               TPS6507X_ADCONFIG_INPUT_TSC);
+       if (ret)
+               return ret;
+
+       ret = tps6507x_write_u8(tsc, TPS6507X_REG_TSCMODE,
+                               TPS6507X_TSCMODE_STANDBY);
+       if (ret)
+               return ret;
+
+       ret = tps6507x_read_u8(tsc, TPS6507X_REG_INT, &val);
+       if (ret)
+               return ret;
+
+       while (val & TPS6507X_REG_TSC_INT) {
+               mdelay(10);
+               ret = tps6507x_read_u8(tsc, TPS6507X_REG_INT, &val);
+               if (ret)
+                       return ret;
+               loops++;
+       }
+
+       return ret;
+}
+
+static void tps6507x_ts_handler(struct work_struct *work)
+{
+       struct tps6507x_ts *tsc =  container_of(work,
+                               struct tps6507x_ts, work.work);
+       struct input_dev *input_dev = tsc->input_dev;
+       int pendown;
+       int schd;
+       int poll = 0;
+       s32 ret;
+
+       ret =  tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_PRESSURE,
+                                      &tsc->tc.pressure);
+       if (ret)
+               goto done;
+
+       pendown = tsc->tc.pressure > tsc->min_pressure;
+
+       if (unlikely(!pendown && tsc->pendown)) {
+               dev_dbg(tsc->dev, "UP\n");
+               input_report_key(input_dev, BTN_TOUCH, 0);
+               input_report_abs(input_dev, ABS_PRESSURE, 0);
+               input_sync(input_dev);
+               tsc->pendown = 0;
+       }
+
+       if (pendown) {
+
+               if (!tsc->pendown) {
+                       dev_dbg(tsc->dev, "DOWN\n");
+                       input_report_key(input_dev, BTN_TOUCH, 1);
+               } else
+                       dev_dbg(tsc->dev, "still down\n");
+
+               ret =  tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_X_POSITION,
+                                              &tsc->tc.x);
+               if (ret)
+                       goto done;
+
+               ret =  tps6507x_adc_conversion(tsc, TPS6507X_TSCMODE_Y_POSITION,
+                                              &tsc->tc.y);
+               if (ret)
+                       goto done;
+
+               input_report_abs(input_dev, ABS_X, tsc->tc.x);
+               input_report_abs(input_dev, ABS_Y, tsc->tc.y);
+               input_report_abs(input_dev, ABS_PRESSURE, tsc->tc.pressure);
+               input_sync(input_dev);
+               tsc->pendown = 1;
+               poll = 1;
+       }
+
+done:
+       /* always poll if not using interrupts */
+       poll = 1;
+
+       if (poll) {
+               schd = queue_delayed_work(tsc->wq, &tsc->work,
+                                         tsc->poll_period * HZ / 1000);
+               if (schd)
+                       tsc->polling = 1;
+               else {
+                       tsc->polling = 0;
+                       dev_err(tsc->dev, "re-schedule failed");
+               }
+       } else
+               tsc->polling = 0;
+
+       ret = tps6507x_adc_standby(tsc);
+}
+
+static int tps6507x_ts_probe(struct platform_device *pdev)
+{
+       int error;
+       struct tps6507x_ts *tsc;
+       struct tps6507x_dev *tps6507x_dev = dev_get_drvdata(pdev->dev.parent);
+       struct touchscreen_init_data *init_data;
+       struct input_dev *input_dev;
+       struct tps6507x_board *tps_board;
+       int schd;
+
+       /**
+        * tps_board points to pmic related constants
+        * coming from the board-evm file.
+        */
+
+       tps_board = (struct tps6507x_board *)tps6507x_dev->dev->platform_data;
+
+       if (!tps_board) {
+               dev_err(tps6507x_dev->dev,
+                       "Could not find tps6507x platform data\n");
+               return -EIO;
+       }
+
+       /**
+        * init_data points to array of regulator_init structures
+        * coming from the board-evm file.
+        */
+
+       init_data = tps_board->tps6507x_ts_init_data;
+
+       tsc = kzalloc(sizeof(struct tps6507x_ts), GFP_KERNEL);
+       if (!tsc) {
+               dev_err(tps6507x_dev->dev, "failed to allocate driver data\n");
+               error = -ENOMEM;
+               goto err0;
+       }
+
+       tps6507x_dev->ts = tsc;
+       tsc->mfd = tps6507x_dev;
+       tsc->dev = tps6507x_dev->dev;
+       input_dev = input_allocate_device();
+       if (!input_dev) {
+               dev_err(tsc->dev, "Failed to allocate input device.\n");
+               error = -ENOMEM;
+               goto err1;
+       }
+
+       input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+       input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+
+       input_set_abs_params(input_dev, ABS_X, 0, MAX_10BIT, 0, 0);
+       input_set_abs_params(input_dev, ABS_Y, 0, MAX_10BIT, 0, 0);
+       input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_10BIT, 0, 0);
+
+       input_dev->name = "TPS6507x Touchscreen";
+       input_dev->id.bustype = BUS_I2C;
+       input_dev->dev.parent = tsc->dev;
+
+       snprintf(tsc->phys, sizeof(tsc->phys),
+                "%s/input0", dev_name(tsc->dev));
+       input_dev->phys = tsc->phys;
+
+       dev_dbg(tsc->dev, "device: %s\n", input_dev->phys);
+
+       input_set_drvdata(input_dev, tsc);
+
+       tsc->input_dev = input_dev;
+
+       INIT_DELAYED_WORK(&tsc->work, tps6507x_ts_handler);
+       tsc->wq = create_workqueue("TPS6507x Touchscreen");
+
+       if (init_data) {
+               tsc->poll_period = init_data->poll_period;
+               tsc->vref = init_data->vref;
+               tsc->min_pressure = init_data->min_pressure;
+               input_dev->id.vendor = init_data->vendor;
+               input_dev->id.product = init_data->product;
+               input_dev->id.version = init_data->version;
+       } else {
+               tsc->poll_period = TSC_DEFAULT_POLL_PERIOD;
+               tsc->min_pressure = TPS_DEFAULT_MIN_PRESSURE;
+       }
+
+       error = tps6507x_adc_standby(tsc);
+       if (error)
+               goto err2;
+
+       error = input_register_device(input_dev);
+       if (error)
+               goto err2;
+
+       schd = queue_delayed_work(tsc->wq, &tsc->work,
+                                 tsc->poll_period * HZ / 1000);
+
+       if (schd)
+               tsc->polling = 1;
+       else {
+               tsc->polling = 0;
+               dev_err(tsc->dev, "schedule failed");
+               goto err2;
+        }
+
+       return 0;
+
+err2:
+       cancel_delayed_work(&tsc->work);
+       flush_workqueue(tsc->wq);
+       destroy_workqueue(tsc->wq);
+       tsc->wq = 0;
+       input_free_device(input_dev);
+err1:
+       kfree(tsc);
+       tps6507x_dev->ts = NULL;
+err0:
+       return error;
+}
+
+static int __devexit tps6507x_ts_remove(struct platform_device *pdev)
+{
+       struct tps6507x_dev *tps6507x_dev = platform_get_drvdata(pdev);
+       struct tps6507x_ts *tsc = tps6507x_dev->ts;
+       struct input_dev *input_dev = tsc->input_dev;
+
+       if (!tsc)
+               return 0;
+
+       cancel_delayed_work(&tsc->work);
+       flush_workqueue(tsc->wq);
+       destroy_workqueue(tsc->wq);
+       tsc->wq = 0;
+
+       input_free_device(input_dev);
+
+       tps6507x_dev->ts = NULL;
+       kfree(tsc);
+
+       return 0;
+}
+
+static struct platform_driver tps6507x_ts_driver = {
+       .driver = {
+               .name = "tps6507x-ts",
+               .owner = THIS_MODULE,
+       },
+       .probe = tps6507x_ts_probe,
+       .remove = __devexit_p(tps6507x_ts_remove),
+};
+
+static int __init tps6507x_ts_init(void)
+{
+       return platform_driver_register(&tps6507x_ts_driver);
+}
+module_init(tps6507x_ts_init);
+
+static void __exit tps6507x_ts_exit(void)
+{
+       platform_driver_unregister(&tps6507x_ts_driver);
+}
+module_exit(tps6507x_ts_exit);
+
+MODULE_AUTHOR("Todd Fischer <todd.fischer@ridgerun.com>");
+MODULE_DESCRIPTION("TPS6507x - TouchScreen driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:tps6507x-tsc");
index 405d2d5183cf626c2bdd1d771f7173466f5636bb..2c65a2c57294bf00aca078072253ea7d140c94e8 100644 (file)
@@ -566,7 +566,7 @@ out:
        return ret;
 }
 
-static void __devexit device_irq_exit(struct pm860x_chip *chip)
+static void device_irq_exit(struct pm860x_chip *chip)
 {
        if (chip->core_irq)
                free_irq(chip->core_irq, chip);
@@ -703,7 +703,7 @@ out:
        return;
 }
 
-int pm860x_device_init(struct pm860x_chip *chip,
+int __devinit pm860x_device_init(struct pm860x_chip *chip,
                       struct pm860x_platform_data *pdata)
 {
        chip->core_irq = 0;
@@ -731,7 +731,7 @@ int pm860x_device_init(struct pm860x_chip *chip,
        return 0;
 }
 
-void pm860x_device_exit(struct pm860x_chip *chip)
+void __devexit pm860x_device_exit(struct pm860x_chip *chip)
 {
        device_irq_exit(chip);
        mfd_remove_devices(chip->dev);
index 4a6e7186334e31ce4eccea8b0937312c20b5e29f..c933b64d1283645071301eb4571b45e7ede1909a 100644 (file)
@@ -200,8 +200,8 @@ static int __devexit pm860x_remove(struct i2c_client *client)
 
        pm860x_device_exit(chip);
        i2c_unregister_device(chip->companion);
-       i2c_set_clientdata(chip->companion, NULL);
        i2c_set_clientdata(chip->client, NULL);
+       i2c_set_clientdata(client, NULL);
        kfree(chip);
        return 0;
 }
index 3c6a9860dd9c2945d5722afe21e057efbbfc21e7..9da0e504bbe9c67fe5264ae9c6188e157e036506 100644 (file)
@@ -2,8 +2,14 @@
 # Multifunction miscellaneous devices
 #
 
-menu "Multifunction device drivers"
+menuconfig MFD_SUPPORT
+       bool "Multifunction device drivers"
        depends on HAS_IOMEM
+       default y
+       help
+         Configure MFD device drivers.
+
+if MFD_SUPPORT
 
 config MFD_CORE
        tristate
@@ -116,6 +122,18 @@ config TPS65010
          This driver can also be built as a module.  If so, the module
          will be called tps65010.
 
+config TPS6507X
+       tristate "TPS6507x Power Management / Touch Screen chips"
+       select MFD_CORE
+       depends on I2C
+       help
+         If you say yes here you get support for the TPS6507x series of
+         Power Management / Touch Screen chips.  These include voltage
+         regulators, lithium ion/polymer battery charging, touch screen
+         and other features that are often used in portable devices.
+         This driver can also be built as a module.  If so, the module
+         will be called tps6507x.
+
 config MENELAUS
        bool "Texas Instruments TWL92330/Menelaus PM chip"
        depends on I2C=y && ARCH_OMAP2
@@ -159,6 +177,17 @@ config TWL4030_CODEC
        select MFD_CORE
        default n
 
+config MFD_TC35892
+       bool "Support Toshiba TC35892"
+       depends on I2C=y && GENERIC_HARDIRQS
+       select MFD_CORE
+       help
+         Support for the Toshiba TC35892 I/O Expander.
+
+         This driver provides common support for accessing the device,
+         additional drivers must be enabled in order to use the
+         functionality of the device.
+
 config MFD_TMIO
        bool
        default n
@@ -351,9 +380,19 @@ config PCF50633_GPIO
         Say yes here if you want to include support GPIO for pins on
         the PCF50633 chip.
 
+config ABX500_CORE
+       bool "ST-Ericsson ABX500 Mixed Signal Circuit register functions"
+       default y if ARCH_U300
+       help
+         Say yes here if you have the ABX500 Mixed Signal IC family
+         chips. This core driver expose register access functions.
+         Functionality specific drivers using these functions can
+         remain unchanged when IC changes. Binding of the functions to
+         actual register access is done by the IC core driver.
+
 config AB3100_CORE
        bool "ST-Ericsson AB3100 Mixed Signal Circuit core functions"
-       depends on I2C=y
+       depends on I2C=y && ABX500_CORE
        default y if ARCH_U300
        help
          Select this to enable the AB3100 Mixed Signal IC core
@@ -381,15 +420,30 @@ config EZX_PCAP
          This enables the PCAP ASIC present on EZX Phones. This is
          needed for MMC, TouchScreen, Sound, USB, etc..
 
-config AB4500_CORE
-       tristate "ST-Ericsson's AB4500 Mixed Signal Power management chip"
-       depends on SPI
+config AB8500_CORE
+       bool "ST-Ericsson AB8500 Mixed Signal Power Management chip"
+       depends on SPI=y && GENERIC_HARDIRQS
+       select MFD_CORE
        help
-         Select this option to enable access to AB4500 power management
+         Select this option to enable access to AB8500 power management
          chip. This connects to U8500 on the SSP/SPI bus and exports
          read/write functions for the devices to get access to this chip.
          This chip embeds various other multimedia funtionalities as well.
 
+config AB3550_CORE
+        bool "ST-Ericsson AB3550 Mixed Signal Circuit core functions"
+       select MFD_CORE
+       depends on I2C=y && GENERIC_HARDIRQS && ABX500_CORE
+       help
+         Select this to enable the AB3550 Mixed Signal IC core
+         functionality. This connects to a AB3550 on the I2C bus
+         and expose a number of symbols needed for dependent devices
+         to read and write registers and subscribe to events from
+         this multi-functional IC. This is needed to use other features
+         of the AB3550 such as battery-backed RTC, charging control,
+         LEDs, vibrator, system power and temperature, power management
+         and ALSA sound.
+
 config MFD_TIMBERDALE
        tristate "Support for the Timberdale FPGA"
        select MFD_CORE
@@ -409,7 +463,26 @@ config LPC_SCH
          LPC bridge function of the Intel SCH provides support for
          System Management Bus and General Purpose I/O.
 
-endmenu
+config MFD_RDC321X
+       tristate "Support for RDC-R321x southbridge"
+       select MFD_CORE
+       depends on PCI
+       help
+         Say yes here if you want to have support for the RDC R-321x SoC
+         southbridge which provides access to GPIOs and Watchdog using the
+         southbridge PCI device configuration space.
+
+config MFD_JANZ_CMODIO
+       tristate "Support for Janz CMOD-IO PCI MODULbus Carrier Board"
+       select MFD_CORE
+       depends on PCI
+       help
+         This is the core driver for the Janz CMOD-IO PCI MODULbus
+         carrier board. This device is a PCI to MODULbus bridge which may
+         host many different types of MODULbus daughterboards, including
+         CAN and GPIO controllers.
+
+endif # MFD_SUPPORT
 
 menu "Multimedia Capabilities Port drivers"
        depends on ARCH_SA1100
index 87935f967aa0e67e0ab82fe7ba6ecce4439855e5..fb503e77dc60eb9935f1f9cf120d84e9f9d5ecec 100644 (file)
@@ -15,6 +15,7 @@ obj-$(CONFIG_HTC_I2CPLD)      += htc-i2cpld.o
 obj-$(CONFIG_MFD_DAVINCI_VOICECODEC)   += davinci_voicecodec.o
 obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o
 
+obj-$(CONFIG_MFD_TC35892)      += tc35892.o
 obj-$(CONFIG_MFD_T7L66XB)      += t7l66xb.o tmio_core.o
 obj-$(CONFIG_MFD_TC6387XB)     += tc6387xb.o tmio_core.o
 obj-$(CONFIG_MFD_TC6393XB)     += tc6393xb.o tmio_core.o
@@ -29,6 +30,7 @@ obj-$(CONFIG_MFD_WM8350_I2C)  += wm8350-i2c.o
 obj-$(CONFIG_MFD_WM8994)       += wm8994-core.o wm8994-irq.o
 
 obj-$(CONFIG_TPS65010)         += tps65010.o
+obj-$(CONFIG_TPS6507X)         += tps6507x.o
 obj-$(CONFIG_MENELAUS)         += menelaus.o
 
 obj-$(CONFIG_TWL4030_CORE)     += twl-core.o twl4030-irq.o twl6030-irq.o
@@ -55,12 +57,17 @@ obj-$(CONFIG_PMIC_DA903X)   += da903x.o
 max8925-objs                   := max8925-core.o max8925-i2c.o
 obj-$(CONFIG_MFD_MAX8925)      += max8925.o
 
-obj-$(CONFIG_MFD_PCF50633)     += pcf50633-core.o
+pcf50633-objs                  := pcf50633-core.o pcf50633-irq.o
+obj-$(CONFIG_MFD_PCF50633)     += pcf50633.o
 obj-$(CONFIG_PCF50633_ADC)     += pcf50633-adc.o
 obj-$(CONFIG_PCF50633_GPIO)    += pcf50633-gpio.o
+obj-$(CONFIG_ABX500_CORE)      += abx500-core.o
 obj-$(CONFIG_AB3100_CORE)      += ab3100-core.o
 obj-$(CONFIG_AB3100_OTP)       += ab3100-otp.o
-obj-$(CONFIG_AB4500_CORE)      += ab4500-core.o
+obj-$(CONFIG_AB3550_CORE)      += ab3550-core.o
+obj-$(CONFIG_AB8500_CORE)      += ab8500-core.o ab8500-spi.o
 obj-$(CONFIG_MFD_TIMBERDALE)    += timberdale.o
 obj-$(CONFIG_PMIC_ADP5520)     += adp5520.o
-obj-$(CONFIG_LPC_SCH)          += lpc_sch.o
\ No newline at end of file
+obj-$(CONFIG_LPC_SCH)          += lpc_sch.o
+obj-$(CONFIG_MFD_RDC321X)      += rdc321x-southbridge.o
+obj-$(CONFIG_MFD_JANZ_CMODIO)  += janz-cmodio.o
index e4ca5909e4242261ac9a2588954607ec129f18d4..53ebfee548fa3bfbc5311d34c98bfc5b166290b7 100644 (file)
@@ -19,7 +19,7 @@
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
 #include <linux/uaccess.h>
-#include <linux/mfd/ab3100.h>
+#include <linux/mfd/abx500.h>
 
 /* These are the only registers inside AB3100 used in this main file */
 
  * The AB3100 is usually assigned address 0x48 (7-bit)
  * The chip is defined in the platform i2c_board_data section.
  */
-
-u8 ab3100_get_chip_type(struct ab3100 *ab3100)
+static int ab3100_get_chip_id(struct device *dev)
 {
-       u8 chip = ABUNKNOWN;
-
-       switch (ab3100->chip_id & 0xf0) {
-       case  0xa0:
-               chip = AB3000;
-               break;
-       case  0xc0:
-               chip = AB3100;
-               break;
-       }
-       return chip;
+       struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
+
+       return (int)ab3100->chip_id;
 }
-EXPORT_SYMBOL(ab3100_get_chip_type);
 
-int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval)
+static int ab3100_set_register_interruptible(struct ab3100 *ab3100,
+       u8 reg, u8 regval)
 {
        u8 regandval[2] = {reg, regval};
        int err;
@@ -108,8 +99,14 @@ int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval)
        mutex_unlock(&ab3100->access_mutex);
        return err;
 }
-EXPORT_SYMBOL(ab3100_set_register_interruptible);
 
+static int set_register_interruptible(struct device *dev,
+       u8 bank, u8 reg, u8 value)
+{
+       struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
+
+       return ab3100_set_register_interruptible(ab3100, reg, value);
+}
 
 /*
  * The test registers exist at an I2C bus address up one
@@ -148,8 +145,8 @@ static int ab3100_set_test_register_interruptible(struct ab3100 *ab3100,
        return err;
 }
 
-
-int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval)
+static int ab3100_get_register_interruptible(struct ab3100 *ab3100,
+       u8 reg, u8 *regval)
 {
        int err;
 
@@ -203,10 +200,16 @@ int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval)
        mutex_unlock(&ab3100->access_mutex);
        return err;
 }
-EXPORT_SYMBOL(ab3100_get_register_interruptible);
 
+static int get_register_interruptible(struct device *dev, u8 bank, u8 reg,
+       u8 *value)
+{
+       struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
+
+       return ab3100_get_register_interruptible(ab3100, reg, value);
+}
 
-int ab3100_get_register_page_interruptible(struct ab3100 *ab3100,
+static int ab3100_get_register_page_interruptible(struct ab3100 *ab3100,
                             u8 first_reg, u8 *regvals, u8 numregs)
 {
        int err;
@@ -260,10 +263,17 @@ int ab3100_get_register_page_interruptible(struct ab3100 *ab3100,
        mutex_unlock(&ab3100->access_mutex);
        return err;
 }
-EXPORT_SYMBOL(ab3100_get_register_page_interruptible);
 
+static int get_register_page_interruptible(struct device *dev, u8 bank,
+       u8 first_reg, u8 *regvals, u8 numregs)
+{
+       struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
+
+       return ab3100_get_register_page_interruptible(ab3100,
+                       first_reg, regvals, numregs);
+}
 
-int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100,
+static int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100,
                                 u8 reg, u8 andmask, u8 ormask)
 {
        u8 regandval[2] = {reg, 0};
@@ -331,8 +341,15 @@ int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100,
        mutex_unlock(&ab3100->access_mutex);
        return err;
 }
-EXPORT_SYMBOL(ab3100_mask_and_set_register_interruptible);
 
+static int mask_and_set_register_interruptible(struct device *dev, u8 bank,
+       u8 reg, u8 bitmask, u8 bitvalues)
+{
+       struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
+
+       return ab3100_mask_and_set_register_interruptible(ab3100,
+                       reg, bitmask, (bitmask & bitvalues));
+}
 
 /*
  * Register a simple callback for handling any AB3100 events.
@@ -357,15 +374,27 @@ int ab3100_event_unregister(struct ab3100 *ab3100,
 EXPORT_SYMBOL(ab3100_event_unregister);
 
 
-int ab3100_event_registers_startup_state_get(struct ab3100 *ab3100,
-                                            u32 *fatevent)
+static int ab3100_event_registers_startup_state_get(struct device *dev,
+                                            u8 *event)
 {
+       struct ab3100 *ab3100 = dev_get_drvdata(dev->parent);
        if (!ab3100->startup_events_read)
                return -EAGAIN; /* Try again later */
-       *fatevent = ab3100->startup_events;
+       memcpy(event, ab3100->startup_events, 3);
        return 0;
 }
-EXPORT_SYMBOL(ab3100_event_registers_startup_state_get);
+
+static struct abx500_ops ab3100_ops = {
+       .get_chip_id = ab3100_get_chip_id,
+       .set_register = set_register_interruptible,
+       .get_register = get_register_interruptible,
+       .get_register_page = get_register_page_interruptible,
+       .set_register_page = NULL,
+       .mask_and_set_register = mask_and_set_register_interruptible,
+       .event_registers_startup_state_get =
+               ab3100_event_registers_startup_state_get,
+       .startup_irq_enabled = NULL,
+};
 
 /*
  * This is a threaded interrupt handler so we can make some
@@ -390,7 +419,9 @@ static irqreturn_t ab3100_irq_handler(int irq, void *data)
                event_regs[2];
 
        if (!ab3100->startup_events_read) {
-               ab3100->startup_events = fatevent;
+               ab3100->startup_events[0] = event_regs[0];
+               ab3100->startup_events[1] = event_regs[1];
+               ab3100->startup_events[2] = event_regs[2];
                ab3100->startup_events_read = true;
        }
        /*
@@ -703,7 +734,8 @@ static int __init ab3100_setup(struct ab3100 *ab3100)
                dev_warn(ab3100->dev,
                         "AB3100 P1E variant detected, "
                         "forcing chip to 32KHz\n");
-               err = ab3100_set_test_register_interruptible(ab3100, 0x02, 0x08);
+               err = ab3100_set_test_register_interruptible(ab3100,
+                       0x02, 0x08);
        }
 
  exit_no_setup:
@@ -898,6 +930,10 @@ static int __init ab3100_probe(struct i2c_client *client,
        if (err)
                goto exit_no_irq;
 
+       err = abx500_register_ops(&client->dev, &ab3100_ops);
+       if (err)
+               goto exit_no_ops;
+
        /* Set parent and a pointer back to the container in device data */
        for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++) {
                ab3100_platform_devs[i]->dev.parent =
@@ -915,11 +951,13 @@ static int __init ab3100_probe(struct i2c_client *client,
 
        return 0;
 
+ exit_no_ops:
  exit_no_irq:
  exit_no_setup:
        i2c_unregister_device(ab3100->testreg_client);
  exit_no_testreg_client:
  exit_no_detect:
+       i2c_set_clientdata(client, NULL);
        kfree(ab3100);
        return err;
 }
@@ -941,6 +979,7 @@ static int __exit ab3100_remove(struct i2c_client *client)
         * their notifiers so deactivate IRQ
         */
        free_irq(client->irq, ab3100);
+       i2c_set_clientdata(client, NULL);
        kfree(ab3100);
        return 0;
 }
index 2d14655fdebdfb722b57cfaa15f24e798d5fa18c..63d2b727ddbbb82e7a98bf935b7f054d528822f4 100644 (file)
@@ -12,7 +12,7 @@
 #include <linux/slab.h>
 #include <linux/init.h>
 #include <linux/platform_device.h>
-#include <linux/mfd/ab3100.h>
+#include <linux/mfd/abx500.h>
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
 
@@ -30,7 +30,6 @@
 /**
  * struct ab3100_otp
  * @dev containing device
- * @ab3100 a pointer to the parent ab3100 device struct
  * @locked whether the OTP is locked, after locking, no more bits
  *       can be changed but before locking it is still possible
  *       to change bits from 1->0.
@@ -49,7 +48,6 @@
  */
 struct ab3100_otp {
        struct device *dev;
-       struct ab3100 *ab3100;
        bool locked;
        u32 freq;
        bool paf;
@@ -63,19 +61,19 @@ struct ab3100_otp {
 
 static int __init ab3100_otp_read(struct ab3100_otp *otp)
 {
-       struct ab3100 *ab = otp->ab3100;
        u8 otpval[8];
        u8 otpp;
        int err;
 
-       err = ab3100_get_register_interruptible(ab, AB3100_OTPP, &otpp);
+       err = abx500_get_register_interruptible(otp->dev, 0,
+               AB3100_OTPP, &otpp);
        if (err) {
                dev_err(otp->dev, "unable to read OTPP register\n");
                return err;
        }
 
-       err = ab3100_get_register_page_interruptible(ab, AB3100_OTP0,
-                                                    otpval, 8);
+       err = abx500_get_register_page_interruptible(otp->dev, 0,
+               AB3100_OTP0, otpval, 8);
        if (err) {
                dev_err(otp->dev, "unable to read OTP register page\n");
                return err;
@@ -197,7 +195,6 @@ static int __init ab3100_otp_probe(struct platform_device *pdev)
        otp->dev = &pdev->dev;
 
        /* Replace platform data coming in with a local struct */
-       otp->ab3100 = platform_get_drvdata(pdev);
        platform_set_drvdata(pdev, otp);
 
        err = ab3100_otp_read(otp);
diff --git a/drivers/mfd/ab3550-core.c b/drivers/mfd/ab3550-core.c
new file mode 100644 (file)
index 0000000..1060f8e
--- /dev/null
@@ -0,0 +1,1401 @@
+/*
+ * Copyright (C) 2007-2010 ST-Ericsson
+ * License terms: GNU General Public License (GPL) version 2
+ * Low-level core for exclusive access to the AB3550 IC on the I2C bus
+ * and some basic chip-configuration.
+ * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com>
+ * Author: Mattias Nilsson <mattias.i.nilsson@stericsson.com>
+ * Author: Mattias Wallin <mattias.wallin@stericsson.com>
+ * Author: Rickard Andersson <rickard.andersson@stericsson.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/random.h>
+#include <linux/workqueue.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/mfd/abx500.h>
+#include <linux/list.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/mfd/core.h>
+
+#define AB3550_NAME_STRING "ab3550"
+#define AB3550_ID_FORMAT_STRING "AB3550 %s"
+#define AB3550_NUM_BANKS 2
+#define AB3550_NUM_EVENT_REG 5
+
+/* These are the only registers inside AB3550 used in this main file */
+
+/* Chip ID register */
+#define AB3550_CID_REG           0x20
+
+/* Interrupt event registers */
+#define AB3550_EVENT_BANK        0
+#define AB3550_EVENT_REG         0x22
+
+/* Read/write operation values. */
+#define AB3550_PERM_RD (0x01)
+#define AB3550_PERM_WR (0x02)
+
+/* Read/write permissions. */
+#define AB3550_PERM_RO (AB3550_PERM_RD)
+#define AB3550_PERM_RW (AB3550_PERM_RD | AB3550_PERM_WR)
+
+/**
+ * struct ab3550
+ * @access_mutex: lock out concurrent accesses to the AB registers
+ * @i2c_client: I2C client for this chip
+ * @chip_name: name of this chip variant
+ * @chip_id: 8 bit chip ID for this chip variant
+ * @mask_work: a worker for writing to mask registers
+ * @event_lock: a lock to protect the event_mask
+ * @event_mask: a local copy of the mask event registers
+ * @startup_events: a copy of the first reading of the event registers
+ * @startup_events_read: whether the first events have been read
+ */
+struct ab3550 {
+       struct mutex access_mutex;
+       struct i2c_client *i2c_client[AB3550_NUM_BANKS];
+       char chip_name[32];
+       u8 chip_id;
+       struct work_struct mask_work;
+       spinlock_t event_lock;
+       u8 event_mask[AB3550_NUM_EVENT_REG];
+       u8 startup_events[AB3550_NUM_EVENT_REG];
+       bool startup_events_read;
+#ifdef CONFIG_DEBUG_FS
+       unsigned int debug_bank;
+       unsigned int debug_address;
+#endif
+};
+
+/**
+ * struct ab3550_reg_range
+ * @first: the first address of the range
+ * @last: the last address of the range
+ * @perm: access permissions for the range
+ */
+struct ab3550_reg_range {
+       u8 first;
+       u8 last;
+       u8 perm;
+};
+
+/**
+ * struct ab3550_reg_ranges
+ * @count: the number of ranges in the list
+ * @range: the list of register ranges
+ */
+struct ab3550_reg_ranges {
+       u8 count;
+       const struct ab3550_reg_range *range;
+};
+
+/*
+ * Permissible register ranges for reading and writing per device and bank.
+ *
+ * The ranges must be listed in increasing address order, and no overlaps are
+ * allowed. It is assumed that write permission implies read permission
+ * (i.e. only RO and RW permissions should be used).  Ranges with write
+ * permission must not be split up.
+ */
+
+#define NO_RANGE {.count = 0, .range = NULL,}
+
+static struct
+ab3550_reg_ranges ab3550_reg_ranges[AB3550_NUM_DEVICES][AB3550_NUM_BANKS] = {
+       [AB3550_DEVID_DAC] = {
+               NO_RANGE,
+               {
+                       .count = 2,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0xb0,
+                                       .last = 0xba,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                               {
+                                       .first = 0xbc,
+                                       .last = 0xc3,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                       },
+               },
+       },
+       [AB3550_DEVID_LEDS] = {
+               NO_RANGE,
+               {
+                       .count = 2,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x5a,
+                                       .last = 0x88,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                               {
+                                       .first = 0x8a,
+                                       .last = 0xad,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                       }
+               },
+       },
+       [AB3550_DEVID_POWER] = {
+               {
+                       .count = 1,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x21,
+                                       .last = 0x21,
+                                       .perm = AB3550_PERM_RO,
+                               },
+                       }
+               },
+               NO_RANGE,
+       },
+       [AB3550_DEVID_REGULATORS] = {
+               {
+                       .count = 1,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x69,
+                                       .last = 0xa3,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                       }
+               },
+               {
+                       .count = 1,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x14,
+                                       .last = 0x16,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                       }
+               },
+       },
+       [AB3550_DEVID_SIM] = {
+               {
+                       .count = 1,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x21,
+                                       .last = 0x21,
+                                       .perm = AB3550_PERM_RO,
+                               },
+                       }
+               },
+               {
+                       .count = 1,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x14,
+                                       .last = 0x17,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                       }
+
+               },
+       },
+       [AB3550_DEVID_UART] = {
+               NO_RANGE,
+               NO_RANGE,
+       },
+       [AB3550_DEVID_RTC] = {
+               {
+                       .count = 1,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x00,
+                                       .last = 0x0c,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                       }
+               },
+               NO_RANGE,
+       },
+       [AB3550_DEVID_CHARGER] = {
+               {
+                       .count = 2,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x10,
+                                       .last = 0x1a,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                               {
+                                       .first = 0x21,
+                                       .last = 0x21,
+                                       .perm = AB3550_PERM_RO,
+                               },
+                       }
+               },
+               NO_RANGE,
+       },
+       [AB3550_DEVID_ADC] = {
+               NO_RANGE,
+               {
+                       .count = 1,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x20,
+                                       .last = 0x56,
+                                       .perm = AB3550_PERM_RW,
+                               },
+
+                       }
+               },
+       },
+       [AB3550_DEVID_FUELGAUGE] = {
+               {
+                       .count = 1,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x21,
+                                       .last = 0x21,
+                                       .perm = AB3550_PERM_RO,
+                               },
+                       }
+               },
+               {
+                       .count = 1,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x00,
+                                       .last = 0x0e,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                       }
+               },
+       },
+       [AB3550_DEVID_VIBRATOR] = {
+               NO_RANGE,
+               {
+                       .count = 1,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x10,
+                                       .last = 0x13,
+                                       .perm = AB3550_PERM_RW,
+                               },
+
+                       }
+               },
+       },
+       [AB3550_DEVID_CODEC] = {
+               {
+                       .count = 2,
+                       .range = (struct ab3550_reg_range[]) {
+                               {
+                                       .first = 0x31,
+                                       .last = 0x63,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                               {
+                                       .first = 0x65,
+                                       .last = 0x68,
+                                       .perm = AB3550_PERM_RW,
+                               },
+                       }
+               },
+               NO_RANGE,
+       },
+};
+
+static struct mfd_cell ab3550_devs[AB3550_NUM_DEVICES] = {
+       [AB3550_DEVID_DAC] = {
+               .name = "ab3550-dac",
+               .id = AB3550_DEVID_DAC,
+               .num_resources = 0,
+       },
+       [AB3550_DEVID_LEDS] = {
+               .name = "ab3550-leds",
+               .id = AB3550_DEVID_LEDS,
+       },
+       [AB3550_DEVID_POWER] = {
+               .name = "ab3550-power",
+               .id = AB3550_DEVID_POWER,
+       },
+       [AB3550_DEVID_REGULATORS] = {
+               .name = "ab3550-regulators",
+               .id = AB3550_DEVID_REGULATORS,
+       },
+       [AB3550_DEVID_SIM] = {
+               .name = "ab3550-sim",
+               .id = AB3550_DEVID_SIM,
+       },
+       [AB3550_DEVID_UART] = {
+               .name = "ab3550-uart",
+               .id = AB3550_DEVID_UART,
+       },
+       [AB3550_DEVID_RTC] = {
+               .name = "ab3550-rtc",
+               .id = AB3550_DEVID_RTC,
+       },
+       [AB3550_DEVID_CHARGER] = {
+               .name = "ab3550-charger",
+               .id = AB3550_DEVID_CHARGER,
+       },
+       [AB3550_DEVID_ADC] = {
+               .name = "ab3550-adc",
+               .id = AB3550_DEVID_ADC,
+               .num_resources = 10,
+               .resources = (struct resource[]) {
+                       {
+                               .name = "TRIGGER-0",
+                               .flags = IORESOURCE_IRQ,
+                               .start = 16,
+                               .end = 16,
+                       },
+                       {
+                               .name = "TRIGGER-1",
+                               .flags = IORESOURCE_IRQ,
+                               .start = 17,
+                               .end = 17,
+                       },
+                       {
+                               .name = "TRIGGER-2",
+                               .flags = IORESOURCE_IRQ,
+                               .start = 18,
+                               .end = 18,
+                       },
+                       {
+                               .name = "TRIGGER-3",
+                               .flags = IORESOURCE_IRQ,
+                               .start = 19,
+                               .end = 19,
+                       },
+                       {
+                               .name = "TRIGGER-4",
+                               .flags = IORESOURCE_IRQ,
+                               .start = 20,
+                               .end = 20,
+                       },
+                       {
+                               .name = "TRIGGER-5",
+                               .flags = IORESOURCE_IRQ,
+                               .start = 21,
+                               .end = 21,
+                       },
+                       {
+                               .name = "TRIGGER-6",
+                               .flags = IORESOURCE_IRQ,
+                               .start = 22,
+                               .end = 22,
+                       },
+                       {
+                               .name = "TRIGGER-7",
+                               .flags = IORESOURCE_IRQ,
+                               .start = 23,
+                               .end = 23,
+                       },
+                       {
+                               .name = "TRIGGER-VBAT-TXON",
+                               .flags = IORESOURCE_IRQ,
+                               .start = 13,
+                               .end = 13,
+                       },
+                       {
+                               .name = "TRIGGER-VBAT",
+                               .flags = IORESOURCE_IRQ,
+                               .start = 12,
+                               .end = 12,
+                       },
+               },
+       },
+       [AB3550_DEVID_FUELGAUGE] = {
+               .name = "ab3550-fuelgauge",
+               .id = AB3550_DEVID_FUELGAUGE,
+       },
+       [AB3550_DEVID_VIBRATOR] = {
+               .name = "ab3550-vibrator",
+               .id = AB3550_DEVID_VIBRATOR,
+       },
+       [AB3550_DEVID_CODEC] = {
+               .name = "ab3550-codec",
+               .id = AB3550_DEVID_CODEC,
+       },
+};
+
+/*
+ * I2C transactions with error messages.
+ */
+static int ab3550_i2c_master_send(struct ab3550 *ab, u8 bank, u8 *data,
+       u8 count)
+{
+       int err;
+
+       err = i2c_master_send(ab->i2c_client[bank], data, count);
+       if (err < 0) {
+               dev_err(&ab->i2c_client[0]->dev, "send error: %d\n", err);
+               return err;
+       }
+       return 0;
+}
+
+static int ab3550_i2c_master_recv(struct ab3550 *ab, u8 bank, u8 *data,
+       u8 count)
+{
+       int err;
+
+       err = i2c_master_recv(ab->i2c_client[bank], data, count);
+       if (err < 0) {
+               dev_err(&ab->i2c_client[0]->dev, "receive error: %d\n", err);
+               return err;
+       }
+       return 0;
+}
+
+/*
+ * Functionality for getting/setting register values.
+ */
+static int get_register_interruptible(struct ab3550 *ab, u8 bank, u8 reg,
+       u8 *value)
+{
+       int err;
+
+       err = mutex_lock_interruptible(&ab->access_mutex);
+       if (err)
+               return err;
+
+       err = ab3550_i2c_master_send(ab, bank, &reg, 1);
+       if (!err)
+               err = ab3550_i2c_master_recv(ab, bank, value, 1);
+
+       mutex_unlock(&ab->access_mutex);
+       return err;
+}
+
+static int get_register_page_interruptible(struct ab3550 *ab, u8 bank,
+       u8 first_reg, u8 *regvals, u8 numregs)
+{
+       int err;
+
+       err = mutex_lock_interruptible(&ab->access_mutex);
+       if (err)
+               return err;
+
+       err = ab3550_i2c_master_send(ab, bank, &first_reg, 1);
+       if (!err)
+               err = ab3550_i2c_master_recv(ab, bank, regvals, numregs);
+
+       mutex_unlock(&ab->access_mutex);
+       return err;
+}
+
+static int mask_and_set_register_interruptible(struct ab3550 *ab, u8 bank,
+       u8 reg, u8 bitmask, u8 bitvalues)
+{
+       int err = 0;
+
+       if (likely(bitmask)) {
+               u8 reg_bits[2] = {reg, 0};
+
+               err = mutex_lock_interruptible(&ab->access_mutex);
+               if (err)
+                       return err;
+
+               if (bitmask == 0xFF) /* No need to read in this case. */
+                       reg_bits[1] = bitvalues;
+               else { /* Read and modify the register value. */
+                       u8 bits;
+
+                       err = ab3550_i2c_master_send(ab, bank, &reg, 1);
+                       if (err)
+                               goto unlock_and_return;
+                       err = ab3550_i2c_master_recv(ab, bank, &bits, 1);
+                       if (err)
+                               goto unlock_and_return;
+                       reg_bits[1] = ((~bitmask & bits) |
+                               (bitmask & bitvalues));
+               }
+               /* Write the new value. */
+               err = ab3550_i2c_master_send(ab, bank, reg_bits, 2);
+unlock_and_return:
+               mutex_unlock(&ab->access_mutex);
+       }
+       return err;
+}
+
+/*
+ * Read/write permission checking functions.
+ */
+static bool page_write_allowed(const struct ab3550_reg_ranges *ranges,
+       u8 first_reg, u8 last_reg)
+{
+       u8 i;
+
+       if (last_reg < first_reg)
+               return false;
+
+       for (i = 0; i < ranges->count; i++) {
+               if (first_reg < ranges->range[i].first)
+                       break;
+               if ((last_reg <= ranges->range[i].last) &&
+                       (ranges->range[i].perm & AB3550_PERM_WR))
+                       return true;
+       }
+       return false;
+}
+
+static bool reg_write_allowed(const struct ab3550_reg_ranges *ranges, u8 reg)
+{
+       return page_write_allowed(ranges, reg, reg);
+}
+
+static bool page_read_allowed(const struct ab3550_reg_ranges *ranges,
+       u8 first_reg, u8 last_reg)
+{
+       u8 i;
+
+       if (last_reg < first_reg)
+               return false;
+       /* Find the range (if it exists in the list) that includes first_reg. */
+       for (i = 0; i < ranges->count; i++) {
+               if (first_reg < ranges->range[i].first)
+                       return false;
+               if (first_reg <= ranges->range[i].last)
+                       break;
+       }
+       /* Make sure that the entire range up to and including last_reg is
+        * readable. This may span several of the ranges in the list.
+        */
+       while ((i < ranges->count) &&
+               (ranges->range[i].perm & AB3550_PERM_RD)) {
+               if (last_reg <= ranges->range[i].last)
+                       return true;
+               if ((++i >= ranges->count) ||
+                       (ranges->range[i].first !=
+                        (ranges->range[i - 1].last + 1))) {
+                       break;
+               }
+       }
+       return false;
+}
+
+static bool reg_read_allowed(const struct ab3550_reg_ranges *ranges, u8 reg)
+{
+       return page_read_allowed(ranges, reg, reg);
+}
+
+/*
+ * The exported register access functionality.
+ */
+int ab3550_get_chip_id(struct device *dev)
+{
+       struct ab3550 *ab = dev_get_drvdata(dev->parent);
+       return (int)ab->chip_id;
+}
+
+int ab3550_mask_and_set_register_interruptible(struct device *dev, u8 bank,
+       u8 reg, u8 bitmask, u8 bitvalues)
+{
+       struct ab3550 *ab;
+       struct platform_device *pdev = to_platform_device(dev);
+
+       if ((AB3550_NUM_BANKS <= bank) ||
+               !reg_write_allowed(&ab3550_reg_ranges[pdev->id][bank], reg))
+               return -EINVAL;
+
+       ab = dev_get_drvdata(dev->parent);
+       return mask_and_set_register_interruptible(ab, bank, reg,
+               bitmask, bitvalues);
+}
+
+int ab3550_set_register_interruptible(struct device *dev, u8 bank, u8 reg,
+       u8 value)
+{
+       return ab3550_mask_and_set_register_interruptible(dev, bank, reg, 0xFF,
+               value);
+}
+
+int ab3550_get_register_interruptible(struct device *dev, u8 bank, u8 reg,
+       u8 *value)
+{
+       struct ab3550 *ab;
+       struct platform_device *pdev = to_platform_device(dev);
+
+       if ((AB3550_NUM_BANKS <= bank) ||
+               !reg_read_allowed(&ab3550_reg_ranges[pdev->id][bank], reg))
+               return -EINVAL;
+
+       ab = dev_get_drvdata(dev->parent);
+       return get_register_interruptible(ab, bank, reg, value);
+}
+
+int ab3550_get_register_page_interruptible(struct device *dev, u8 bank,
+       u8 first_reg, u8 *regvals, u8 numregs)
+{
+       struct ab3550 *ab;
+       struct platform_device *pdev = to_platform_device(dev);
+
+       if ((AB3550_NUM_BANKS <= bank) ||
+               !page_read_allowed(&ab3550_reg_ranges[pdev->id][bank],
+                       first_reg, (first_reg + numregs - 1)))
+               return -EINVAL;
+
+       ab = dev_get_drvdata(dev->parent);
+       return get_register_page_interruptible(ab, bank, first_reg, regvals,
+               numregs);
+}
+
+int ab3550_event_registers_startup_state_get(struct device *dev, u8 *event)
+{
+       struct ab3550 *ab;
+
+       ab = dev_get_drvdata(dev->parent);
+       if (!ab->startup_events_read)
+               return -EAGAIN; /* Try again later */
+
+       memcpy(event, ab->startup_events, AB3550_NUM_EVENT_REG);
+       return 0;
+}
+
+int ab3550_startup_irq_enabled(struct device *dev, unsigned int irq)
+{
+       struct ab3550 *ab;
+       struct ab3550_platform_data *plf_data;
+       bool val;
+
+       ab = get_irq_chip_data(irq);
+       plf_data = ab->i2c_client[0]->dev.platform_data;
+       irq -= plf_data->irq.base;
+       val = ((ab->startup_events[irq / 8] & BIT(irq % 8)) != 0);
+
+       return val;
+}
+
+static struct abx500_ops ab3550_ops = {
+       .get_chip_id = ab3550_get_chip_id,
+       .get_register = ab3550_get_register_interruptible,
+       .set_register = ab3550_set_register_interruptible,
+       .get_register_page = ab3550_get_register_page_interruptible,
+       .set_register_page = NULL,
+       .mask_and_set_register = ab3550_mask_and_set_register_interruptible,
+       .event_registers_startup_state_get =
+               ab3550_event_registers_startup_state_get,
+       .startup_irq_enabled = ab3550_startup_irq_enabled,
+};
+
+static irqreturn_t ab3550_irq_handler(int irq, void *data)
+{
+       struct ab3550 *ab = data;
+       int err;
+       unsigned int i;
+       u8 e[AB3550_NUM_EVENT_REG];
+       u8 *events;
+       unsigned long flags;
+
+       events = (ab->startup_events_read ? e : ab->startup_events);
+
+       err = get_register_page_interruptible(ab, AB3550_EVENT_BANK,
+               AB3550_EVENT_REG, events, AB3550_NUM_EVENT_REG);
+       if (err)
+               goto err_event_rd;
+
+       if (!ab->startup_events_read) {
+               dev_info(&ab->i2c_client[0]->dev,
+                       "startup events 0x%x,0x%x,0x%x,0x%x,0x%x\n",
+                       ab->startup_events[0], ab->startup_events[1],
+                       ab->startup_events[2], ab->startup_events[3],
+                       ab->startup_events[4]);
+               ab->startup_events_read = true;
+               goto out;
+       }
+
+       /* The two highest bits in event[4] are not used. */
+       events[4] &= 0x3f;
+
+       spin_lock_irqsave(&ab->event_lock, flags);
+       for (i = 0; i < AB3550_NUM_EVENT_REG; i++)
+               events[i] &= ~ab->event_mask[i];
+       spin_unlock_irqrestore(&ab->event_lock, flags);
+
+       for (i = 0; i < AB3550_NUM_EVENT_REG; i++) {
+               u8 bit;
+               u8 event_reg;
+
+               dev_dbg(&ab->i2c_client[0]->dev, "IRQ Event[%d]: 0x%2x\n",
+                       i, events[i]);
+
+               event_reg = events[i];
+               for (bit = 0; event_reg; bit++, event_reg /= 2) {
+                       if (event_reg % 2) {
+                               unsigned int irq;
+                               struct ab3550_platform_data *plf_data;
+
+                               plf_data = ab->i2c_client[0]->dev.platform_data;
+                               irq = plf_data->irq.base + (i * 8) + bit;
+                               handle_nested_irq(irq);
+                       }
+               }
+       }
+out:
+       return IRQ_HANDLED;
+
+err_event_rd:
+       dev_dbg(&ab->i2c_client[0]->dev, "error reading event registers\n");
+       return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static struct ab3550_reg_ranges debug_ranges[AB3550_NUM_BANKS] = {
+       {
+               .count = 6,
+               .range = (struct ab3550_reg_range[]) {
+                       {
+                               .first = 0x00,
+                               .last = 0x0e,
+                       },
+                       {
+                               .first = 0x10,
+                               .last = 0x1a,
+                       },
+                       {
+                               .first = 0x1e,
+                               .last = 0x4f,
+                       },
+                       {
+                               .first = 0x51,
+                               .last = 0x63,
+                       },
+                       {
+                               .first = 0x65,
+                               .last = 0xa3,
+                       },
+                       {
+                               .first = 0xa5,
+                               .last = 0xa8,
+                       },
+               }
+       },
+       {
+               .count = 8,
+               .range = (struct ab3550_reg_range[]) {
+                       {
+                               .first = 0x00,
+                               .last = 0x0e,
+                       },
+                       {
+                               .first = 0x10,
+                               .last = 0x17,
+                       },
+                       {
+                               .first = 0x1a,
+                               .last = 0x1c,
+                       },
+                       {
+                               .first = 0x20,
+                               .last = 0x56,
+                       },
+                       {
+                               .first = 0x5a,
+                               .last = 0x88,
+                       },
+                       {
+                               .first = 0x8a,
+                               .last = 0xad,
+                       },
+                       {
+                               .first = 0xb0,
+                               .last = 0xba,
+                       },
+                       {
+                               .first = 0xbc,
+                               .last = 0xc3,
+                       },
+               }
+       },
+};
+
+static int ab3550_registers_print(struct seq_file *s, void *p)
+{
+       struct ab3550 *ab = s->private;
+       int bank;
+
+       seq_printf(s, AB3550_NAME_STRING " register values:\n");
+
+       for (bank = 0; bank < AB3550_NUM_BANKS; bank++) {
+               unsigned int i;
+
+               seq_printf(s, " bank %d:\n", bank);
+               for (i = 0; i < debug_ranges[bank].count; i++) {
+                       u8 reg;
+
+                       for (reg = debug_ranges[bank].range[i].first;
+                               reg <= debug_ranges[bank].range[i].last;
+                               reg++) {
+                               u8 value;
+
+                               get_register_interruptible(ab, bank, reg,
+                                       &value);
+                               seq_printf(s, "  [%d/0x%02X]: 0x%02X\n", bank,
+                                       reg, value);
+                       }
+               }
+       }
+       return 0;
+}
+
+static int ab3550_registers_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, ab3550_registers_print, inode->i_private);
+}
+
+static const struct file_operations ab3550_registers_fops = {
+       .open = ab3550_registers_open,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+       .owner = THIS_MODULE,
+};
+
+static int ab3550_bank_print(struct seq_file *s, void *p)
+{
+       struct ab3550 *ab = s->private;
+
+       seq_printf(s, "%d\n", ab->debug_bank);
+       return 0;
+}
+
+static int ab3550_bank_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, ab3550_bank_print, inode->i_private);
+}
+
+static ssize_t ab3550_bank_write(struct file *file,
+       const char __user *user_buf,
+       size_t count, loff_t *ppos)
+{
+       struct ab3550 *ab = ((struct seq_file *)(file->private_data))->private;
+       char buf[32];
+       int buf_size;
+       unsigned long user_bank;
+       int err;
+
+       /* Get userspace string and assure termination */
+       buf_size = min(count, (sizeof(buf) - 1));
+       if (copy_from_user(buf, user_buf, buf_size))
+               return -EFAULT;
+       buf[buf_size] = 0;
+
+       err = strict_strtoul(buf, 0, &user_bank);
+       if (err)
+               return -EINVAL;
+
+       if (user_bank >= AB3550_NUM_BANKS) {
+               dev_err(&ab->i2c_client[0]->dev,
+                       "debugfs error input > number of banks\n");
+               return -EINVAL;
+       }
+
+       ab->debug_bank = user_bank;
+
+       return buf_size;
+}
+
+static int ab3550_address_print(struct seq_file *s, void *p)
+{
+       struct ab3550 *ab = s->private;
+
+       seq_printf(s, "0x%02X\n", ab->debug_address);
+       return 0;
+}
+
+static int ab3550_address_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, ab3550_address_print, inode->i_private);
+}
+
+static ssize_t ab3550_address_write(struct file *file,
+       const char __user *user_buf,
+       size_t count, loff_t *ppos)
+{
+       struct ab3550 *ab = ((struct seq_file *)(file->private_data))->private;
+       char buf[32];
+       int buf_size;
+       unsigned long user_address;
+       int err;
+
+       /* Get userspace string and assure termination */
+       buf_size = min(count, (sizeof(buf) - 1));
+       if (copy_from_user(buf, user_buf, buf_size))
+               return -EFAULT;
+       buf[buf_size] = 0;
+
+       err = strict_strtoul(buf, 0, &user_address);
+       if (err)
+               return -EINVAL;
+       if (user_address > 0xff) {
+               dev_err(&ab->i2c_client[0]->dev,
+                       "debugfs error input > 0xff\n");
+               return -EINVAL;
+       }
+       ab->debug_address = user_address;
+       return buf_size;
+}
+
+static int ab3550_val_print(struct seq_file *s, void *p)
+{
+       struct ab3550 *ab = s->private;
+       int err;
+       u8 regvalue;
+
+       err = get_register_interruptible(ab, (u8)ab->debug_bank,
+               (u8)ab->debug_address, &regvalue);
+       if (err)
+               return -EINVAL;
+       seq_printf(s, "0x%02X\n", regvalue);
+
+       return 0;
+}
+
+static int ab3550_val_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, ab3550_val_print, inode->i_private);
+}
+
+static ssize_t ab3550_val_write(struct file *file,
+       const char __user *user_buf,
+       size_t count, loff_t *ppos)
+{
+       struct ab3550 *ab = ((struct seq_file *)(file->private_data))->private;
+       char buf[32];
+       int buf_size;
+       unsigned long user_val;
+       int err;
+       u8 regvalue;
+
+       /* Get userspace string and assure termination */
+       buf_size = min(count, (sizeof(buf)-1));
+       if (copy_from_user(buf, user_buf, buf_size))
+               return -EFAULT;
+       buf[buf_size] = 0;
+
+       err = strict_strtoul(buf, 0, &user_val);
+       if (err)
+               return -EINVAL;
+       if (user_val > 0xff) {
+               dev_err(&ab->i2c_client[0]->dev,
+                       "debugfs error input > 0xff\n");
+               return -EINVAL;
+       }
+       err = mask_and_set_register_interruptible(
+               ab, (u8)ab->debug_bank,
+               (u8)ab->debug_address, 0xFF, (u8)user_val);
+       if (err)
+               return -EINVAL;
+
+       get_register_interruptible(ab, (u8)ab->debug_bank,
+               (u8)ab->debug_address, &regvalue);
+       if (err)
+               return -EINVAL;
+
+       return buf_size;
+}
+
+static const struct file_operations ab3550_bank_fops = {
+       .open = ab3550_bank_open,
+       .write = ab3550_bank_write,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+       .owner = THIS_MODULE,
+};
+
+static const struct file_operations ab3550_address_fops = {
+       .open = ab3550_address_open,
+       .write = ab3550_address_write,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+       .owner = THIS_MODULE,
+};
+
+static const struct file_operations ab3550_val_fops = {
+       .open = ab3550_val_open,
+       .write = ab3550_val_write,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+       .owner = THIS_MODULE,
+};
+
+static struct dentry *ab3550_dir;
+static struct dentry *ab3550_reg_file;
+static struct dentry *ab3550_bank_file;
+static struct dentry *ab3550_address_file;
+static struct dentry *ab3550_val_file;
+
+static inline void ab3550_setup_debugfs(struct ab3550 *ab)
+{
+       ab->debug_bank = 0;
+       ab->debug_address = 0x00;
+
+       ab3550_dir = debugfs_create_dir(AB3550_NAME_STRING, NULL);
+       if (!ab3550_dir)
+               goto exit_no_debugfs;
+
+       ab3550_reg_file = debugfs_create_file("all-registers",
+               S_IRUGO, ab3550_dir, ab, &ab3550_registers_fops);
+       if (!ab3550_reg_file)
+               goto exit_destroy_dir;
+
+       ab3550_bank_file = debugfs_create_file("register-bank",
+               (S_IRUGO | S_IWUGO), ab3550_dir, ab, &ab3550_bank_fops);
+       if (!ab3550_bank_file)
+               goto exit_destroy_reg;
+
+       ab3550_address_file = debugfs_create_file("register-address",
+               (S_IRUGO | S_IWUGO), ab3550_dir, ab, &ab3550_address_fops);
+       if (!ab3550_address_file)
+               goto exit_destroy_bank;
+
+       ab3550_val_file = debugfs_create_file("register-value",
+               (S_IRUGO | S_IWUGO), ab3550_dir, ab, &ab3550_val_fops);
+       if (!ab3550_val_file)
+               goto exit_destroy_address;
+
+       return;
+
+exit_destroy_address:
+       debugfs_remove(ab3550_address_file);
+exit_destroy_bank:
+       debugfs_remove(ab3550_bank_file);
+exit_destroy_reg:
+       debugfs_remove(ab3550_reg_file);
+exit_destroy_dir:
+       debugfs_remove(ab3550_dir);
+exit_no_debugfs:
+       dev_err(&ab->i2c_client[0]->dev, "failed to create debugfs entries.\n");
+       return;
+}
+
+static inline void ab3550_remove_debugfs(void)
+{
+       debugfs_remove(ab3550_val_file);
+       debugfs_remove(ab3550_address_file);
+       debugfs_remove(ab3550_bank_file);
+       debugfs_remove(ab3550_reg_file);
+       debugfs_remove(ab3550_dir);
+}
+
+#else /* !CONFIG_DEBUG_FS */
+static inline void ab3550_setup_debugfs(struct ab3550 *ab)
+{
+}
+static inline void ab3550_remove_debugfs(void)
+{
+}
+#endif
+
+/*
+ * Basic set-up, datastructure creation/destruction and I2C interface.
+ * This sets up a default config in the AB3550 chip so that it
+ * will work as expected.
+ */
+static int __init ab3550_setup(struct ab3550 *ab)
+{
+       int err = 0;
+       int i;
+       struct ab3550_platform_data *plf_data;
+       struct abx500_init_settings *settings;
+
+       plf_data = ab->i2c_client[0]->dev.platform_data;
+       settings = plf_data->init_settings;
+
+       for (i = 0; i < plf_data->init_settings_sz; i++) {
+               err = mask_and_set_register_interruptible(ab,
+                       settings[i].bank,
+                       settings[i].reg,
+                       0xFF, settings[i].setting);
+               if (err)
+                       goto exit_no_setup;
+
+               /* If event mask register update the event mask in ab3550 */
+               if ((settings[i].bank == 0) &&
+                       (AB3550_IMR1 <= settings[i].reg) &&
+                       (settings[i].reg <= AB3550_IMR5)) {
+                       ab->event_mask[settings[i].reg - AB3550_IMR1] =
+                               settings[i].setting;
+               }
+       }
+exit_no_setup:
+       return err;
+}
+
+static void ab3550_mask_work(struct work_struct *work)
+{
+       struct ab3550 *ab = container_of(work, struct ab3550, mask_work);
+       int i;
+       unsigned long flags;
+       u8 mask[AB3550_NUM_EVENT_REG];
+
+       spin_lock_irqsave(&ab->event_lock, flags);
+       for (i = 0; i < AB3550_NUM_EVENT_REG; i++)
+               mask[i] = ab->event_mask[i];
+       spin_unlock_irqrestore(&ab->event_lock, flags);
+
+       for (i = 0; i < AB3550_NUM_EVENT_REG; i++) {
+               int err;
+
+               err = mask_and_set_register_interruptible(ab, 0,
+                       (AB3550_IMR1 + i), ~0, mask[i]);
+               if (err)
+                       dev_err(&ab->i2c_client[0]->dev,
+                               "ab3550_mask_work failed 0x%x,0x%x\n",
+                               (AB3550_IMR1 + i), mask[i]);
+       }
+}
+
+static void ab3550_mask(unsigned int irq)
+{
+       unsigned long flags;
+       struct ab3550 *ab;
+       struct ab3550_platform_data *plf_data;
+
+       ab = get_irq_chip_data(irq);
+       plf_data = ab->i2c_client[0]->dev.platform_data;
+       irq -= plf_data->irq.base;
+
+       spin_lock_irqsave(&ab->event_lock, flags);
+       ab->event_mask[irq / 8] |= BIT(irq % 8);
+       spin_unlock_irqrestore(&ab->event_lock, flags);
+
+       schedule_work(&ab->mask_work);
+}
+
+static void ab3550_unmask(unsigned int irq)
+{
+       unsigned long flags;
+       struct ab3550 *ab;
+       struct ab3550_platform_data *plf_data;
+
+       ab = get_irq_chip_data(irq);
+       plf_data = ab->i2c_client[0]->dev.platform_data;
+       irq -= plf_data->irq.base;
+
+       spin_lock_irqsave(&ab->event_lock, flags);
+       ab->event_mask[irq / 8] &= ~BIT(irq % 8);
+       spin_unlock_irqrestore(&ab->event_lock, flags);
+
+       schedule_work(&ab->mask_work);
+}
+
+static void noop(unsigned int irq)
+{
+}
+
+static struct irq_chip ab3550_irq_chip = {
+       .name           = "ab3550-core", /* Keep the same name as the request */
+       .startup        = NULL, /* defaults to enable */
+       .shutdown       = NULL, /* defaults to disable */
+       .enable         = NULL, /* defaults to unmask */
+       .disable        = ab3550_mask, /* No default to mask in chip.c */
+       .ack            = noop,
+       .mask           = ab3550_mask,
+       .unmask         = ab3550_unmask,
+       .end            = NULL,
+};
+
+struct ab_family_id {
+       u8      id;
+       char    *name;
+};
+
+static const struct ab_family_id ids[] __initdata = {
+       /* AB3550 */
+       {
+               .id = AB3550_P1A,
+               .name = "P1A"
+       },
+       /* Terminator */
+       {
+               .id = 0x00,
+       }
+};
+
+static int __init ab3550_probe(struct i2c_client *client,
+       const struct i2c_device_id *id)
+{
+       struct ab3550 *ab;
+       struct ab3550_platform_data *ab3550_plf_data =
+               client->dev.platform_data;
+       int err;
+       int i;
+       int num_i2c_clients = 0;
+
+       ab = kzalloc(sizeof(struct ab3550), GFP_KERNEL);
+       if (!ab) {
+               dev_err(&client->dev,
+                       "could not allocate " AB3550_NAME_STRING " device\n");
+               return -ENOMEM;
+       }
+
+       /* Initialize data structure */
+       mutex_init(&ab->access_mutex);
+       spin_lock_init(&ab->event_lock);
+       ab->i2c_client[0] = client;
+
+       i2c_set_clientdata(client, ab);
+
+       /* Read chip ID register */
+       err = get_register_interruptible(ab, 0, AB3550_CID_REG, &ab->chip_id);
+       if (err) {
+               dev_err(&client->dev, "could not communicate with the analog "
+                       "baseband chip\n");
+               goto exit_no_detect;
+       }
+
+       for (i = 0; ids[i].id != 0x0; i++) {
+               if (ids[i].id == ab->chip_id) {
+                       snprintf(&ab->chip_name[0], sizeof(ab->chip_name) - 1,
+                               AB3550_ID_FORMAT_STRING, ids[i].name);
+                       break;
+               }
+       }
+
+       if (ids[i].id == 0x0) {
+               dev_err(&client->dev, "unknown analog baseband chip id: 0x%x\n",
+                       ab->chip_id);
+               dev_err(&client->dev, "driver not started!\n");
+               goto exit_no_detect;
+       }
+
+       dev_info(&client->dev, "detected AB chip: %s\n", &ab->chip_name[0]);
+
+       /* Attach other dummy I2C clients. */
+       while (++num_i2c_clients < AB3550_NUM_BANKS) {
+               ab->i2c_client[num_i2c_clients] =
+                       i2c_new_dummy(client->adapter,
+                               (client->addr + num_i2c_clients));
+               if (!ab->i2c_client[num_i2c_clients]) {
+                       err = -ENOMEM;
+                       goto exit_no_dummy_client;
+               }
+               strlcpy(ab->i2c_client[num_i2c_clients]->name, id->name,
+                       sizeof(ab->i2c_client[num_i2c_clients]->name));
+       }
+
+       err = ab3550_setup(ab);
+       if (err)
+               goto exit_no_setup;
+
+       INIT_WORK(&ab->mask_work, ab3550_mask_work);
+
+       for (i = 0; i < ab3550_plf_data->irq.count; i++) {
+               unsigned int irq;
+
+               irq = ab3550_plf_data->irq.base + i;
+               set_irq_chip_data(irq, ab);
+               set_irq_chip_and_handler(irq, &ab3550_irq_chip,
+                       handle_simple_irq);
+               set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+               set_irq_flags(irq, IRQF_VALID);
+#else
+               set_irq_noprobe(irq);
+#endif
+       }
+
+       err = request_threaded_irq(client->irq, NULL, ab3550_irq_handler,
+               IRQF_ONESHOT, "ab3550-core", ab);
+       /* This real unpredictable IRQ is of course sampled for entropy */
+       rand_initialize_irq(client->irq);
+
+       if (err)
+               goto exit_no_irq;
+
+       err = abx500_register_ops(&client->dev, &ab3550_ops);
+       if (err)
+               goto exit_no_ops;
+
+       /* Set up and register the platform devices. */
+       for (i = 0; i < AB3550_NUM_DEVICES; i++) {
+               ab3550_devs[i].platform_data = ab3550_plf_data->dev_data[i];
+               ab3550_devs[i].data_size = ab3550_plf_data->dev_data_sz[i];
+       }
+
+       err = mfd_add_devices(&client->dev, 0, ab3550_devs,
+               ARRAY_SIZE(ab3550_devs), NULL,
+               ab3550_plf_data->irq.base);
+
+       ab3550_setup_debugfs(ab);
+
+       return 0;
+
+exit_no_ops:
+exit_no_irq:
+exit_no_setup:
+exit_no_dummy_client:
+       /* Unregister the dummy i2c clients. */
+       while (--num_i2c_clients)
+               i2c_unregister_device(ab->i2c_client[num_i2c_clients]);
+exit_no_detect:
+       kfree(ab);
+       return err;
+}
+
+static int __exit ab3550_remove(struct i2c_client *client)
+{
+       struct ab3550 *ab = i2c_get_clientdata(client);
+       int num_i2c_clients = AB3550_NUM_BANKS;
+
+       mfd_remove_devices(&client->dev);
+       ab3550_remove_debugfs();
+
+       while (--num_i2c_clients)
+               i2c_unregister_device(ab->i2c_client[num_i2c_clients]);
+
+       /*
+        * At this point, all subscribers should have unregistered
+        * their notifiers so deactivate IRQ
+        */
+       free_irq(client->irq, ab);
+       i2c_set_clientdata(client, NULL);
+       kfree(ab);
+       return 0;
+}
+
+static const struct i2c_device_id ab3550_id[] = {
+       {AB3550_NAME_STRING, 0},
+       {}
+};
+MODULE_DEVICE_TABLE(i2c, ab3550_id);
+
+static struct i2c_driver ab3550_driver = {
+       .driver = {
+               .name   = AB3550_NAME_STRING,
+               .owner  = THIS_MODULE,
+       },
+       .id_table       = ab3550_id,
+       .probe          = ab3550_probe,
+       .remove         = __exit_p(ab3550_remove),
+};
+
+static int __init ab3550_i2c_init(void)
+{
+       return i2c_add_driver(&ab3550_driver);
+}
+
+static void __exit ab3550_i2c_exit(void)
+{
+       i2c_del_driver(&ab3550_driver);
+}
+
+subsys_initcall(ab3550_i2c_init);
+module_exit(ab3550_i2c_exit);
+
+MODULE_AUTHOR("Mattias Wallin <mattias.wallin@stericsson.com>");
+MODULE_DESCRIPTION("AB3550 core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/ab4500-core.c b/drivers/mfd/ab4500-core.c
deleted file mode 100644 (file)
index c275daa..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2009 ST-Ericsson
- *
- * Author: Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com>
- *
- * This program is free software; you can redistribute it
- * and/or modify it under the terms of the GNU General Public
- * License version 2, as published by the Free Software Foundation.
- *
- * AB4500 is a companion power management chip used with U8500.
- * On this platform, this is interfaced with SSP0 controller
- * which is a ARM primecell pl022.
- *
- * At the moment the module just exports read/write features.
- * Interrupt management to be added - TODO.
- */
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/spi/spi.h>
-#include <linux/mfd/ab4500.h>
-
-/* just required if probe fails, we need to
- * unregister the device
- */
-static struct spi_driver ab4500_driver;
-
-/*
- * This funtion writes to any AB4500 registers using
- * SPI protocol &  before it writes it packs the data
- * in the below 24 bit frame format
- *
- *      *|------------------------------------|
- *      *| 23|22...18|17.......10|9|8|7......0|
- *      *| r/w  bank       adr          data  |
- *      * ------------------------------------
- *
- * This function shouldn't be called from interrupt
- * context
- */
-int ab4500_write(struct ab4500 *ab4500, unsigned char block,
-               unsigned long addr, unsigned char data)
-{
-       struct spi_transfer xfer;
-       struct spi_message      msg;
-       int err;
-       unsigned long spi_data =
-               block << 18 | addr << 10 | data;
-
-       mutex_lock(&ab4500->lock);
-       ab4500->tx_buf[0] = spi_data;
-       ab4500->rx_buf[0] = 0;
-
-       xfer.tx_buf     = ab4500->tx_buf;
-       xfer.rx_buf     = NULL;
-       xfer.len        = sizeof(unsigned long);
-
-       spi_message_init(&msg);
-       spi_message_add_tail(&xfer, &msg);
-
-       err = spi_sync(ab4500->spi, &msg);
-       mutex_unlock(&ab4500->lock);
-
-       return err;
-}
-EXPORT_SYMBOL(ab4500_write);
-
-int ab4500_read(struct ab4500 *ab4500, unsigned char block,
-               unsigned long addr)
-{
-       struct spi_transfer xfer;
-       struct spi_message      msg;
-       unsigned long spi_data =
-               1 << 23 | block << 18 | addr << 10;
-
-       mutex_lock(&ab4500->lock);
-       ab4500->tx_buf[0] = spi_data;
-       ab4500->rx_buf[0] = 0;
-
-       xfer.tx_buf     = ab4500->tx_buf;
-       xfer.rx_buf     = ab4500->rx_buf;
-       xfer.len        = sizeof(unsigned long);
-
-       spi_message_init(&msg);
-       spi_message_add_tail(&xfer, &msg);
-
-       spi_sync(ab4500->spi, &msg);
-       mutex_unlock(&ab4500->lock);
-
-       return  ab4500->rx_buf[0];
-}
-EXPORT_SYMBOL(ab4500_read);
-
-/* ref: ab3100 core */
-#define AB4500_DEVICE(devname, devid)                          \
-static struct platform_device ab4500_##devname##_device = {    \
-       .name   = devid,                                        \
-       .id     = -1,                                           \
-}
-
-/* list of childern devices of ab4500 - all are
- * not populated here - TODO
- */
-AB4500_DEVICE(charger, "ab4500-charger");
-AB4500_DEVICE(audio, "ab4500-audio");
-AB4500_DEVICE(usb, "ab4500-usb");
-AB4500_DEVICE(tvout, "ab4500-tvout");
-AB4500_DEVICE(sim, "ab4500-sim");
-AB4500_DEVICE(gpadc, "ab4500-gpadc");
-AB4500_DEVICE(clkmgt, "ab4500-clkmgt");
-AB4500_DEVICE(misc, "ab4500-misc");
-
-static struct platform_device *ab4500_platform_devs[] = {
-       &ab4500_charger_device,
-       &ab4500_audio_device,
-       &ab4500_usb_device,
-       &ab4500_tvout_device,
-       &ab4500_sim_device,
-       &ab4500_gpadc_device,
-       &ab4500_clkmgt_device,
-       &ab4500_misc_device,
-};
-
-static int __init ab4500_probe(struct spi_device *spi)
-{
-       struct ab4500   *ab4500;
-       unsigned char revision;
-       int err = 0;
-       int i;
-
-       ab4500 = kzalloc(sizeof *ab4500, GFP_KERNEL);
-       if (!ab4500) {
-               dev_err(&spi->dev, "could not allocate AB4500\n");
-               err = -ENOMEM;
-               goto not_detect;
-       }
-
-       ab4500->spi = spi;
-       spi_set_drvdata(spi, ab4500);
-
-       mutex_init(&ab4500->lock);
-
-       /* read the revision register */
-       revision = ab4500_read(ab4500, AB4500_MISC, AB4500_REV_REG);
-
-       /* revision id 0x0 is for early drop, 0x10 is for cut1.0 */
-       if (revision == 0x0 || revision == 0x10)
-               dev_info(&spi->dev, "Detected chip: %s, revision = %x\n",
-                       ab4500_driver.driver.name, revision);
-       else    {
-               dev_err(&spi->dev, "unknown chip: 0x%x\n", revision);
-               goto not_detect;
-       }
-
-       for (i = 0; i < ARRAY_SIZE(ab4500_platform_devs); i++)  {
-               ab4500_platform_devs[i]->dev.parent =
-                       &spi->dev;
-               platform_set_drvdata(ab4500_platform_devs[i], ab4500);
-       }
-
-       /* register the ab4500 platform devices */
-       platform_add_devices(ab4500_platform_devs,
-                       ARRAY_SIZE(ab4500_platform_devs));
-
-       return err;
-
- not_detect:
-       spi_unregister_driver(&ab4500_driver);
-       kfree(ab4500);
-       return err;
-}
-
-static int __devexit ab4500_remove(struct spi_device *spi)
-{
-       struct ab4500 *ab4500 =
-               spi_get_drvdata(spi);
-
-       kfree(ab4500);
-
-       return 0;
-}
-
-static struct spi_driver ab4500_driver = {
-       .driver = {
-               .name = "ab4500",
-               .owner = THIS_MODULE,
-       },
-       .probe = ab4500_probe,
-       .remove = __devexit_p(ab4500_remove)
-};
-
-static int __devinit ab4500_init(void)
-{
-       return spi_register_driver(&ab4500_driver);
-}
-
-static void __exit ab4500_exit(void)
-{
-       spi_unregister_driver(&ab4500_driver);
-}
-
-subsys_initcall(ab4500_init);
-module_exit(ab4500_exit);
-
-MODULE_AUTHOR("Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com");
-MODULE_DESCRIPTION("AB4500 core driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c
new file mode 100644 (file)
index 0000000..f3d26fa
--- /dev/null
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License v2
+ * Author: Srinidhi Kasagar <srinidhi.kasagar@stericsson.com>
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/ab8500.h>
+
+/*
+ * Interrupt register offsets
+ * Bank : 0x0E
+ */
+#define AB8500_IT_SOURCE1_REG          0x0E00
+#define AB8500_IT_SOURCE2_REG          0x0E01
+#define AB8500_IT_SOURCE3_REG          0x0E02
+#define AB8500_IT_SOURCE4_REG          0x0E03
+#define AB8500_IT_SOURCE5_REG          0x0E04
+#define AB8500_IT_SOURCE6_REG          0x0E05
+#define AB8500_IT_SOURCE7_REG          0x0E06
+#define AB8500_IT_SOURCE8_REG          0x0E07
+#define AB8500_IT_SOURCE19_REG         0x0E12
+#define AB8500_IT_SOURCE20_REG         0x0E13
+#define AB8500_IT_SOURCE21_REG         0x0E14
+#define AB8500_IT_SOURCE22_REG         0x0E15
+#define AB8500_IT_SOURCE23_REG         0x0E16
+#define AB8500_IT_SOURCE24_REG         0x0E17
+
+/*
+ * latch registers
+ */
+#define AB8500_IT_LATCH1_REG           0x0E20
+#define AB8500_IT_LATCH2_REG           0x0E21
+#define AB8500_IT_LATCH3_REG           0x0E22
+#define AB8500_IT_LATCH4_REG           0x0E23
+#define AB8500_IT_LATCH5_REG           0x0E24
+#define AB8500_IT_LATCH6_REG           0x0E25
+#define AB8500_IT_LATCH7_REG           0x0E26
+#define AB8500_IT_LATCH8_REG           0x0E27
+#define AB8500_IT_LATCH9_REG           0x0E28
+#define AB8500_IT_LATCH10_REG          0x0E29
+#define AB8500_IT_LATCH19_REG          0x0E32
+#define AB8500_IT_LATCH20_REG          0x0E33
+#define AB8500_IT_LATCH21_REG          0x0E34
+#define AB8500_IT_LATCH22_REG          0x0E35
+#define AB8500_IT_LATCH23_REG          0x0E36
+#define AB8500_IT_LATCH24_REG          0x0E37
+
+/*
+ * mask registers
+ */
+
+#define AB8500_IT_MASK1_REG            0x0E40
+#define AB8500_IT_MASK2_REG            0x0E41
+#define AB8500_IT_MASK3_REG            0x0E42
+#define AB8500_IT_MASK4_REG            0x0E43
+#define AB8500_IT_MASK5_REG            0x0E44
+#define AB8500_IT_MASK6_REG            0x0E45
+#define AB8500_IT_MASK7_REG            0x0E46
+#define AB8500_IT_MASK8_REG            0x0E47
+#define AB8500_IT_MASK9_REG            0x0E48
+#define AB8500_IT_MASK10_REG           0x0E49
+#define AB8500_IT_MASK11_REG           0x0E4A
+#define AB8500_IT_MASK12_REG           0x0E4B
+#define AB8500_IT_MASK13_REG           0x0E4C
+#define AB8500_IT_MASK14_REG           0x0E4D
+#define AB8500_IT_MASK15_REG           0x0E4E
+#define AB8500_IT_MASK16_REG           0x0E4F
+#define AB8500_IT_MASK17_REG           0x0E50
+#define AB8500_IT_MASK18_REG           0x0E51
+#define AB8500_IT_MASK19_REG           0x0E52
+#define AB8500_IT_MASK20_REG           0x0E53
+#define AB8500_IT_MASK21_REG           0x0E54
+#define AB8500_IT_MASK22_REG           0x0E55
+#define AB8500_IT_MASK23_REG           0x0E56
+#define AB8500_IT_MASK24_REG           0x0E57
+
+#define AB8500_REV_REG                 0x1080
+
+/*
+ * Map interrupt numbers to the LATCH and MASK register offsets, Interrupt
+ * numbers are indexed into this array with (num / 8).
+ *
+ * This is one off from the register names, i.e. AB8500_IT_MASK1_REG is at
+ * offset 0.
+ */
+static const int ab8500_irq_regoffset[AB8500_NUM_IRQ_REGS] = {
+       0, 1, 2, 3, 4, 6, 7, 8, 9, 18, 19, 20, 21,
+};
+
+static int __ab8500_write(struct ab8500 *ab8500, u16 addr, u8 data)
+{
+       int ret;
+
+       dev_vdbg(ab8500->dev, "wr: addr %#x <= %#x\n", addr, data);
+
+       ret = ab8500->write(ab8500, addr, data);
+       if (ret < 0)
+               dev_err(ab8500->dev, "failed to write reg %#x: %d\n",
+                       addr, ret);
+
+       return ret;
+}
+
+/**
+ * ab8500_write() - write an AB8500 register
+ * @ab8500: device to write to
+ * @addr: address of the register
+ * @data: value to write
+ */
+int ab8500_write(struct ab8500 *ab8500, u16 addr, u8 data)
+{
+       int ret;
+
+       mutex_lock(&ab8500->lock);
+       ret = __ab8500_write(ab8500, addr, data);
+       mutex_unlock(&ab8500->lock);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(ab8500_write);
+
+static int __ab8500_read(struct ab8500 *ab8500, u16 addr)
+{
+       int ret;
+
+       ret = ab8500->read(ab8500, addr);
+       if (ret < 0)
+               dev_err(ab8500->dev, "failed to read reg %#x: %d\n",
+                       addr, ret);
+
+       dev_vdbg(ab8500->dev, "rd: addr %#x => data %#x\n", addr, ret);
+
+       return ret;
+}
+
+/**
+ * ab8500_read() - read an AB8500 register
+ * @ab8500: device to read from
+ * @addr: address of the register
+ */
+int ab8500_read(struct ab8500 *ab8500, u16 addr)
+{
+       int ret;
+
+       mutex_lock(&ab8500->lock);
+       ret = __ab8500_read(ab8500, addr);
+       mutex_unlock(&ab8500->lock);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(ab8500_read);
+
+/**
+ * ab8500_set_bits() - set a bitfield in an AB8500 register
+ * @ab8500: device to read from
+ * @addr: address of the register
+ * @mask: mask of the bitfield to modify
+ * @data: value to set to the bitfield
+ */
+int ab8500_set_bits(struct ab8500 *ab8500, u16 addr, u8 mask, u8 data)
+{
+       int ret;
+
+       mutex_lock(&ab8500->lock);
+
+       ret = __ab8500_read(ab8500, addr);
+       if (ret < 0)
+               goto out;
+
+       ret &= ~mask;
+       ret |= data;
+
+       ret = __ab8500_write(ab8500, addr, ret);
+
+out:
+       mutex_unlock(&ab8500->lock);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(ab8500_set_bits);
+
+static void ab8500_irq_lock(unsigned int irq)
+{
+       struct ab8500 *ab8500 = get_irq_chip_data(irq);
+
+       mutex_lock(&ab8500->irq_lock);
+}
+
+static void ab8500_irq_sync_unlock(unsigned int irq)
+{
+       struct ab8500 *ab8500 = get_irq_chip_data(irq);
+       int i;
+
+       for (i = 0; i < AB8500_NUM_IRQ_REGS; i++) {
+               u8 old = ab8500->oldmask[i];
+               u8 new = ab8500->mask[i];
+               int reg;
+
+               if (new == old)
+                       continue;
+
+               ab8500->oldmask[i] = new;
+
+               reg = AB8500_IT_MASK1_REG + ab8500_irq_regoffset[i];
+               ab8500_write(ab8500, reg, new);
+       }
+
+       mutex_unlock(&ab8500->irq_lock);
+}
+
+static void ab8500_irq_mask(unsigned int irq)
+{
+       struct ab8500 *ab8500 = get_irq_chip_data(irq);
+       int offset = irq - ab8500->irq_base;
+       int index = offset / 8;
+       int mask = 1 << (offset % 8);
+
+       ab8500->mask[index] |= mask;
+}
+
+static void ab8500_irq_unmask(unsigned int irq)
+{
+       struct ab8500 *ab8500 = get_irq_chip_data(irq);
+       int offset = irq - ab8500->irq_base;
+       int index = offset / 8;
+       int mask = 1 << (offset % 8);
+
+       ab8500->mask[index] &= ~mask;
+}
+
+static struct irq_chip ab8500_irq_chip = {
+       .name                   = "ab8500",
+       .bus_lock               = ab8500_irq_lock,
+       .bus_sync_unlock        = ab8500_irq_sync_unlock,
+       .mask                   = ab8500_irq_mask,
+       .unmask                 = ab8500_irq_unmask,
+};
+
+static irqreturn_t ab8500_irq(int irq, void *dev)
+{
+       struct ab8500 *ab8500 = dev;
+       int i;
+
+       dev_vdbg(ab8500->dev, "interrupt\n");
+
+       for (i = 0; i < AB8500_NUM_IRQ_REGS; i++) {
+               int regoffset = ab8500_irq_regoffset[i];
+               int status;
+
+               status = ab8500_read(ab8500, AB8500_IT_LATCH1_REG + regoffset);
+               if (status <= 0)
+                       continue;
+
+               do {
+                       int bit = __ffs(status);
+                       int line = i * 8 + bit;
+
+                       handle_nested_irq(ab8500->irq_base + line);
+                       status &= ~(1 << bit);
+               } while (status);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int ab8500_irq_init(struct ab8500 *ab8500)
+{
+       int base = ab8500->irq_base;
+       int irq;
+
+       for (irq = base; irq < base + AB8500_NR_IRQS; irq++) {
+               set_irq_chip_data(irq, ab8500);
+               set_irq_chip_and_handler(irq, &ab8500_irq_chip,
+                                        handle_simple_irq);
+               set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+               set_irq_flags(irq, IRQF_VALID);
+#else
+               set_irq_noprobe(irq);
+#endif
+       }
+
+       return 0;
+}
+
+static void ab8500_irq_remove(struct ab8500 *ab8500)
+{
+       int base = ab8500->irq_base;
+       int irq;
+
+       for (irq = base; irq < base + AB8500_NR_IRQS; irq++) {
+#ifdef CONFIG_ARM
+               set_irq_flags(irq, 0);
+#endif
+               set_irq_chip_and_handler(irq, NULL, NULL);
+               set_irq_chip_data(irq, NULL);
+       }
+}
+
+static struct resource ab8500_gpadc_resources[] = {
+       {
+               .name   = "HW_CONV_END",
+               .start  = AB8500_INT_GP_HW_ADC_CONV_END,
+               .end    = AB8500_INT_GP_HW_ADC_CONV_END,
+               .flags  = IORESOURCE_IRQ,
+       },
+       {
+               .name   = "SW_CONV_END",
+               .start  = AB8500_INT_GP_SW_ADC_CONV_END,
+               .end    = AB8500_INT_GP_SW_ADC_CONV_END,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct resource ab8500_rtc_resources[] = {
+       {
+               .name   = "60S",
+               .start  = AB8500_INT_RTC_60S,
+               .end    = AB8500_INT_RTC_60S,
+               .flags  = IORESOURCE_IRQ,
+       },
+       {
+               .name   = "ALARM",
+               .start  = AB8500_INT_RTC_ALARM,
+               .end    = AB8500_INT_RTC_ALARM,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct mfd_cell ab8500_devs[] = {
+       {
+               .name = "ab8500-gpadc",
+               .num_resources = ARRAY_SIZE(ab8500_gpadc_resources),
+               .resources = ab8500_gpadc_resources,
+       },
+       {
+               .name = "ab8500-rtc",
+               .num_resources = ARRAY_SIZE(ab8500_rtc_resources),
+               .resources = ab8500_rtc_resources,
+       },
+       { .name = "ab8500-charger", },
+       { .name = "ab8500-audio", },
+       { .name = "ab8500-usb", },
+       { .name = "ab8500-pwm", },
+};
+
+int __devinit ab8500_init(struct ab8500 *ab8500)
+{
+       struct ab8500_platform_data *plat = dev_get_platdata(ab8500->dev);
+       int ret;
+       int i;
+
+       if (plat)
+               ab8500->irq_base = plat->irq_base;
+
+       mutex_init(&ab8500->lock);
+       mutex_init(&ab8500->irq_lock);
+
+       ret = ab8500_read(ab8500, AB8500_REV_REG);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * 0x0 - Early Drop
+        * 0x10 - Cut 1.0
+        * 0x11 - Cut 1.1
+        */
+       if (ret == 0x0 || ret == 0x10 || ret == 0x11) {
+               ab8500->revision = ret;
+               dev_info(ab8500->dev, "detected chip, revision: %#x\n", ret);
+       } else {
+               dev_err(ab8500->dev, "unknown chip, revision: %#x\n", ret);
+               return -EINVAL;
+       }
+
+       if (plat && plat->init)
+               plat->init(ab8500);
+
+       /* Clear and mask all interrupts */
+       for (i = 0; i < 10; i++) {
+               ab8500_read(ab8500, AB8500_IT_LATCH1_REG + i);
+               ab8500_write(ab8500, AB8500_IT_MASK1_REG + i, 0xff);
+       }
+
+       for (i = 18; i < 24; i++) {
+               ab8500_read(ab8500, AB8500_IT_LATCH1_REG + i);
+               ab8500_write(ab8500, AB8500_IT_MASK1_REG + i, 0xff);
+       }
+
+       for (i = 0; i < AB8500_NUM_IRQ_REGS; i++)
+               ab8500->mask[i] = ab8500->oldmask[i] = 0xff;
+
+       if (ab8500->irq_base) {
+               ret = ab8500_irq_init(ab8500);
+               if (ret)
+                       return ret;
+
+               ret = request_threaded_irq(ab8500->irq, NULL, ab8500_irq,
+                                          IRQF_ONESHOT, "ab8500", ab8500);
+               if (ret)
+                       goto out_removeirq;
+       }
+
+       ret = mfd_add_devices(ab8500->dev, -1, ab8500_devs,
+                             ARRAY_SIZE(ab8500_devs), NULL,
+                             ab8500->irq_base);
+       if (ret)
+               goto out_freeirq;
+
+       return ret;
+
+out_freeirq:
+       if (ab8500->irq_base) {
+               free_irq(ab8500->irq, ab8500);
+out_removeirq:
+               ab8500_irq_remove(ab8500);
+       }
+       return ret;
+}
+
+int __devexit ab8500_exit(struct ab8500 *ab8500)
+{
+       mfd_remove_devices(ab8500->dev);
+       if (ab8500->irq_base) {
+               free_irq(ab8500->irq, ab8500);
+               ab8500_irq_remove(ab8500);
+       }
+
+       return 0;
+}
+
+MODULE_AUTHOR("Srinidhi Kasagar, Rabin Vincent");
+MODULE_DESCRIPTION("AB8500 MFD core");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mfd/ab8500-spi.c b/drivers/mfd/ab8500-spi.c
new file mode 100644 (file)
index 0000000..b81d4f7
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License v2
+ * Author: Srinidhi Kasagar <srinidhi.kasagar@stericsson.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/mfd/ab8500.h>
+
+/*
+ * This funtion writes to any AB8500 registers using
+ * SPI protocol &  before it writes it packs the data
+ * in the below 24 bit frame format
+ *
+ *      *|------------------------------------|
+ *      *| 23|22...18|17.......10|9|8|7......0|
+ *      *| r/w  bank       adr          data  |
+ *      * ------------------------------------
+ *
+ * This function shouldn't be called from interrupt
+ * context
+ */
+static int ab8500_spi_write(struct ab8500 *ab8500, u16 addr, u8 data)
+{
+       struct spi_device *spi = container_of(ab8500->dev, struct spi_device,
+                                             dev);
+       unsigned long spi_data = addr << 10 | data;
+       struct spi_transfer xfer;
+       struct spi_message msg;
+
+       ab8500->tx_buf[0] = spi_data;
+       ab8500->rx_buf[0] = 0;
+
+       xfer.tx_buf     = ab8500->tx_buf;
+       xfer.rx_buf     = NULL;
+       xfer.len        = sizeof(unsigned long);
+
+       spi_message_init(&msg);
+       spi_message_add_tail(&xfer, &msg);
+
+       return spi_sync(spi, &msg);
+}
+
+static int ab8500_spi_read(struct ab8500 *ab8500, u16 addr)
+{
+       struct spi_device *spi = container_of(ab8500->dev, struct spi_device,
+                                             dev);
+       unsigned long spi_data = 1 << 23 | addr << 10;
+       struct spi_transfer xfer;
+       struct spi_message msg;
+       int ret;
+
+       ab8500->tx_buf[0] = spi_data;
+       ab8500->rx_buf[0] = 0;
+
+       xfer.tx_buf     = ab8500->tx_buf;
+       xfer.rx_buf     = ab8500->rx_buf;
+       xfer.len        = sizeof(unsigned long);
+
+       spi_message_init(&msg);
+       spi_message_add_tail(&xfer, &msg);
+
+       ret = spi_sync(spi, &msg);
+       if (!ret)
+               ret = ab8500->rx_buf[0];
+
+       return ret;
+}
+
+static int __devinit ab8500_spi_probe(struct spi_device *spi)
+{
+       struct ab8500 *ab8500;
+       int ret;
+
+       ab8500 = kzalloc(sizeof *ab8500, GFP_KERNEL);
+       if (!ab8500)
+               return -ENOMEM;
+
+       ab8500->dev = &spi->dev;
+       ab8500->irq = spi->irq;
+
+       ab8500->read = ab8500_spi_read;
+       ab8500->write = ab8500_spi_write;
+
+       spi_set_drvdata(spi, ab8500);
+
+       ret = ab8500_init(ab8500);
+       if (ret)
+               kfree(ab8500);
+
+       return ret;
+}
+
+static int __devexit ab8500_spi_remove(struct spi_device *spi)
+{
+       struct ab8500 *ab8500 = spi_get_drvdata(spi);
+
+       ab8500_exit(ab8500);
+       kfree(ab8500);
+
+       return 0;
+}
+
+static struct spi_driver ab8500_spi_driver = {
+       .driver = {
+               .name = "ab8500",
+               .owner = THIS_MODULE,
+       },
+       .probe  = ab8500_spi_probe,
+       .remove = __devexit_p(ab8500_spi_remove)
+};
+
+static int __init ab8500_spi_init(void)
+{
+       return spi_register_driver(&ab8500_spi_driver);
+}
+subsys_initcall(ab8500_spi_init);
+
+static void __exit ab8500_spi_exit(void)
+{
+       spi_unregister_driver(&ab8500_spi_driver);
+}
+module_exit(ab8500_spi_exit);
+
+MODULE_AUTHOR("Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com");
+MODULE_DESCRIPTION("AB8500 SPI");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mfd/abx500-core.c b/drivers/mfd/abx500-core.c
new file mode 100644 (file)
index 0000000..3b3b97e
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2007-2010 ST-Ericsson
+ * License terms: GNU General Public License (GPL) version 2
+ * Register access functions for the ABX500 Mixed Signal IC family.
+ * Author: Mattias Wallin <mattias.wallin@stericsson.com>
+ */
+
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/mfd/abx500.h>
+
+static LIST_HEAD(abx500_list);
+
+struct abx500_device_entry {
+       struct list_head list;
+       struct abx500_ops ops;
+       struct device *dev;
+};
+
+static void lookup_ops(struct device *dev, struct abx500_ops **ops)
+{
+       struct abx500_device_entry *dev_entry;
+
+       *ops = NULL;
+       list_for_each_entry(dev_entry, &abx500_list, list) {
+               if (dev_entry->dev == dev) {
+                       *ops = &dev_entry->ops;
+                       return;
+               }
+       }
+}
+
+int abx500_register_ops(struct device *dev, struct abx500_ops *ops)
+{
+       struct abx500_device_entry *dev_entry;
+
+       dev_entry = kzalloc(sizeof(struct abx500_device_entry), GFP_KERNEL);
+       if (IS_ERR(dev_entry)) {
+               dev_err(dev, "register_ops kzalloc failed");
+               return -ENOMEM;
+       }
+       dev_entry->dev = dev;
+       memcpy(&dev_entry->ops, ops, sizeof(struct abx500_ops));
+
+       list_add_tail(&dev_entry->list, &abx500_list);
+       return 0;
+}
+EXPORT_SYMBOL(abx500_register_ops);
+
+void abx500_remove_ops(struct device *dev)
+{
+       struct abx500_device_entry *dev_entry, *tmp;
+
+       list_for_each_entry_safe(dev_entry, tmp, &abx500_list, list)
+       {
+               if (dev_entry->dev == dev) {
+                       list_del(&dev_entry->list);
+                       kfree(dev_entry);
+               }
+       }
+}
+EXPORT_SYMBOL(abx500_remove_ops);
+
+int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg,
+       u8 value)
+{
+       struct abx500_ops *ops;
+
+       lookup_ops(dev->parent, &ops);
+       if ((ops != NULL) && (ops->set_register != NULL))
+               return ops->set_register(dev, bank, reg, value);
+       else
+               return -ENOTSUPP;
+}
+EXPORT_SYMBOL(abx500_set_register_interruptible);
+
+int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg,
+       u8 *value)
+{
+       struct abx500_ops *ops;
+
+       lookup_ops(dev->parent, &ops);
+       if ((ops != NULL) && (ops->get_register != NULL))
+               return ops->get_register(dev, bank, reg, value);
+       else
+               return -ENOTSUPP;
+}
+EXPORT_SYMBOL(abx500_get_register_interruptible);
+
+int abx500_get_register_page_interruptible(struct device *dev, u8 bank,
+       u8 first_reg, u8 *regvals, u8 numregs)
+{
+       struct abx500_ops *ops;
+
+       lookup_ops(dev->parent, &ops);
+       if ((ops != NULL) && (ops->get_register_page != NULL))
+               return ops->get_register_page(dev, bank,
+                       first_reg, regvals, numregs);
+       else
+               return -ENOTSUPP;
+}
+EXPORT_SYMBOL(abx500_get_register_page_interruptible);
+
+int abx500_mask_and_set_register_interruptible(struct device *dev, u8 bank,
+       u8 reg, u8 bitmask, u8 bitvalues)
+{
+       struct abx500_ops *ops;
+
+       lookup_ops(dev->parent, &ops);
+       if ((ops != NULL) && (ops->mask_and_set_register != NULL))
+               return ops->mask_and_set_register(dev, bank,
+                       reg, bitmask, bitvalues);
+       else
+               return -ENOTSUPP;
+}
+EXPORT_SYMBOL(abx500_mask_and_set_register_interruptible);
+
+int abx500_get_chip_id(struct device *dev)
+{
+       struct abx500_ops *ops;
+
+       lookup_ops(dev->parent, &ops);
+       if ((ops != NULL) && (ops->get_chip_id != NULL))
+               return ops->get_chip_id(dev);
+       else
+               return -ENOTSUPP;
+}
+EXPORT_SYMBOL(abx500_get_chip_id);
+
+int abx500_event_registers_startup_state_get(struct device *dev, u8 *event)
+{
+       struct abx500_ops *ops;
+
+       lookup_ops(dev->parent, &ops);
+       if ((ops != NULL) && (ops->event_registers_startup_state_get != NULL))
+               return ops->event_registers_startup_state_get(dev, event);
+       else
+               return -ENOTSUPP;
+}
+EXPORT_SYMBOL(abx500_event_registers_startup_state_get);
+
+int abx500_startup_irq_enabled(struct device *dev, unsigned int irq)
+{
+       struct abx500_ops *ops;
+
+       lookup_ops(dev->parent, &ops);
+       if ((ops != NULL) && (ops->startup_irq_enabled != NULL))
+               return ops->startup_irq_enabled(dev, irq);
+       else
+               return -ENOTSUPP;
+}
+EXPORT_SYMBOL(abx500_startup_irq_enabled);
+
+MODULE_AUTHOR("Mattias Wallin <mattias.wallin@stericsson.com>");
+MODULE_DESCRIPTION("ABX500 core driver");
+MODULE_LICENSE("GPL");
index 67181b147ab3a2265b1ea27be0fe3a21b6145044..3ad915d0589c0626234ffdbc357afbbe2ff37af3 100644 (file)
@@ -544,6 +544,7 @@ static int __devexit da903x_remove(struct i2c_client *client)
        struct da903x_chip *chip = i2c_get_clientdata(client);
 
        da903x_remove_subdevs(chip);
+       i2c_set_clientdata(client, NULL);
        kfree(chip);
        return 0;
 }
diff --git a/drivers/mfd/janz-cmodio.c b/drivers/mfd/janz-cmodio.c
new file mode 100644 (file)
index 0000000..9ed6307
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ * Janz CMOD-IO MODULbus Carrier Board PCI Driver
+ *
+ * Copyright (c) 2010 Ira W. Snyder <iws@ovro.caltech.edu>
+ *
+ * Lots of inspiration and code was copied from drivers/mfd/sm501.c
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+
+#include <linux/mfd/janz.h>
+
+#define DRV_NAME "janz-cmodio"
+
+/* Size of each MODULbus module in PCI BAR4 */
+#define CMODIO_MODULBUS_SIZE   0x200
+
+/* Maximum number of MODULbus modules on a CMOD-IO carrier board */
+#define CMODIO_MAX_MODULES     4
+
+/* Module Parameters */
+static unsigned int num_modules = CMODIO_MAX_MODULES;
+static unsigned char *modules[CMODIO_MAX_MODULES] = {
+       "empty", "empty", "empty", "empty",
+};
+
+module_param_array(modules, charp, &num_modules, S_IRUGO);
+MODULE_PARM_DESC(modules, "MODULbus modules attached to the carrier board");
+
+/* Unique Device Id */
+static unsigned int cmodio_id;
+
+struct cmodio_device {
+       /* Parent PCI device */
+       struct pci_dev *pdev;
+
+       /* PLX control registers */
+       struct janz_cmodio_onboard_regs __iomem *ctrl;
+
+       /* hex switch position */
+       u8 hex;
+
+       /* mfd-core API */
+       struct mfd_cell cells[CMODIO_MAX_MODULES];
+       struct resource resources[3 * CMODIO_MAX_MODULES];
+       struct janz_platform_data pdata[CMODIO_MAX_MODULES];
+};
+
+/*
+ * Subdevices using the mfd-core API
+ */
+
+static int __devinit cmodio_setup_subdevice(struct cmodio_device *priv,
+                                           char *name, unsigned int devno,
+                                           unsigned int modno)
+{
+       struct janz_platform_data *pdata;
+       struct mfd_cell *cell;
+       struct resource *res;
+       struct pci_dev *pci;
+
+       pci = priv->pdev;
+       cell = &priv->cells[devno];
+       res = &priv->resources[devno * 3];
+       pdata = &priv->pdata[devno];
+
+       cell->name = name;
+       cell->resources = res;
+       cell->num_resources = 3;
+
+       /* Setup the subdevice ID -- must be unique */
+       cell->id = cmodio_id++;
+
+       /* Add platform data */
+       pdata->modno = modno;
+       cell->platform_data = pdata;
+       cell->data_size = sizeof(*pdata);
+
+       /* MODULbus registers -- PCI BAR3 is big-endian MODULbus access */
+       res->flags = IORESOURCE_MEM;
+       res->parent = &pci->resource[3];
+       res->start = pci->resource[3].start + (CMODIO_MODULBUS_SIZE * modno);
+       res->end = res->start + CMODIO_MODULBUS_SIZE - 1;
+       res++;
+
+       /* PLX Control Registers -- PCI BAR4 is interrupt and other registers */
+       res->flags = IORESOURCE_MEM;
+       res->parent = &pci->resource[4];
+       res->start = pci->resource[4].start;
+       res->end = pci->resource[4].end;
+       res++;
+
+       /*
+        * IRQ
+        *
+        * The start and end fields are used as an offset to the irq_base
+        * parameter passed into the mfd_add_devices() function call. All
+        * devices share the same IRQ.
+        */
+       res->flags = IORESOURCE_IRQ;
+       res->parent = NULL;
+       res->start = 0;
+       res->end = 0;
+       res++;
+
+       return 0;
+}
+
+/* Probe each submodule using kernel parameters */
+static int __devinit cmodio_probe_submodules(struct cmodio_device *priv)
+{
+       struct pci_dev *pdev = priv->pdev;
+       unsigned int num_probed = 0;
+       char *name;
+       int i;
+
+       for (i = 0; i < num_modules; i++) {
+               name = modules[i];
+               if (!strcmp(name, "") || !strcmp(name, "empty"))
+                       continue;
+
+               dev_dbg(&priv->pdev->dev, "MODULbus %d: name %s\n", i, name);
+               cmodio_setup_subdevice(priv, name, num_probed, i);
+               num_probed++;
+       }
+
+       /* print an error message if no modules were probed */
+       if (num_probed == 0) {
+               dev_err(&priv->pdev->dev, "no MODULbus modules specified, "
+                                         "please set the ``modules'' kernel "
+                                         "parameter according to your "
+                                         "hardware configuration\n");
+               return -ENODEV;
+       }
+
+       return mfd_add_devices(&pdev->dev, 0, priv->cells,
+                              num_probed, NULL, pdev->irq);
+}
+
+/*
+ * SYSFS Attributes
+ */
+
+static ssize_t mbus_show(struct device *dev, struct device_attribute *attr,
+                        char *buf)
+{
+       struct cmodio_device *priv = dev_get_drvdata(dev);
+
+       return snprintf(buf, PAGE_SIZE, "%x\n", priv->hex);
+}
+
+static DEVICE_ATTR(modulbus_number, S_IRUGO, mbus_show, NULL);
+
+static struct attribute *cmodio_sysfs_attrs[] = {
+       &dev_attr_modulbus_number.attr,
+       NULL,
+};
+
+static const struct attribute_group cmodio_sysfs_attr_group = {
+       .attrs = cmodio_sysfs_attrs,
+};
+
+/*
+ * PCI Driver
+ */
+
+static int __devinit cmodio_pci_probe(struct pci_dev *dev,
+                                     const struct pci_device_id *id)
+{
+       struct cmodio_device *priv;
+       int ret;
+
+       priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+       if (!priv) {
+               dev_err(&dev->dev, "unable to allocate private data\n");
+               ret = -ENOMEM;
+               goto out_return;
+       }
+
+       pci_set_drvdata(dev, priv);
+       priv->pdev = dev;
+
+       /* Hardware Initialization */
+       ret = pci_enable_device(dev);
+       if (ret) {
+               dev_err(&dev->dev, "unable to enable device\n");
+               goto out_free_priv;
+       }
+
+       pci_set_master(dev);
+       ret = pci_request_regions(dev, DRV_NAME);
+       if (ret) {
+               dev_err(&dev->dev, "unable to request regions\n");
+               goto out_pci_disable_device;
+       }
+
+       /* Onboard configuration registers */
+       priv->ctrl = pci_ioremap_bar(dev, 4);
+       if (!priv->ctrl) {
+               dev_err(&dev->dev, "unable to remap onboard regs\n");
+               ret = -ENOMEM;
+               goto out_pci_release_regions;
+       }
+
+       /* Read the hex switch on the carrier board */
+       priv->hex = ioread8(&priv->ctrl->int_enable);
+
+       /* Add the MODULbus number (hex switch value) to the device's sysfs */
+       ret = sysfs_create_group(&dev->dev.kobj, &cmodio_sysfs_attr_group);
+       if (ret) {
+               dev_err(&dev->dev, "unable to create sysfs attributes\n");
+               goto out_unmap_ctrl;
+       }
+
+       /*
+        * Disable all interrupt lines, each submodule will enable its
+        * own interrupt line if needed
+        */
+       iowrite8(0xf, &priv->ctrl->int_disable);
+
+       /* Register drivers for all submodules */
+       ret = cmodio_probe_submodules(priv);
+       if (ret) {
+               dev_err(&dev->dev, "unable to probe submodules\n");
+               goto out_sysfs_remove_group;
+       }
+
+       return 0;
+
+out_sysfs_remove_group:
+       sysfs_remove_group(&dev->dev.kobj, &cmodio_sysfs_attr_group);
+out_unmap_ctrl:
+       iounmap(priv->ctrl);
+out_pci_release_regions:
+       pci_release_regions(dev);
+out_pci_disable_device:
+       pci_disable_device(dev);
+out_free_priv:
+       kfree(priv);
+out_return:
+       return ret;
+}
+
+static void __devexit cmodio_pci_remove(struct pci_dev *dev)
+{
+       struct cmodio_device *priv = pci_get_drvdata(dev);
+
+       mfd_remove_devices(&dev->dev);
+       sysfs_remove_group(&dev->dev.kobj, &cmodio_sysfs_attr_group);
+       iounmap(priv->ctrl);
+       pci_release_regions(dev);
+       pci_disable_device(dev);
+       kfree(priv);
+}
+
+#define PCI_VENDOR_ID_JANZ             0x13c3
+
+/* The list of devices that this module will support */
+static DEFINE_PCI_DEVICE_TABLE(cmodio_pci_ids) = {
+       { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9030, PCI_VENDOR_ID_JANZ, 0x0101 },
+       { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_JANZ, 0x0100 },
+       { 0, }
+};
+MODULE_DEVICE_TABLE(pci, cmodio_pci_ids);
+
+static struct pci_driver cmodio_pci_driver = {
+       .name     = DRV_NAME,
+       .id_table = cmodio_pci_ids,
+       .probe    = cmodio_pci_probe,
+       .remove   = __devexit_p(cmodio_pci_remove),
+};
+
+/*
+ * Module Init / Exit
+ */
+
+static int __init cmodio_init(void)
+{
+       return pci_register_driver(&cmodio_pci_driver);
+}
+
+static void __exit cmodio_exit(void)
+{
+       pci_unregister_driver(&cmodio_pci_driver);
+}
+
+MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>");
+MODULE_DESCRIPTION("Janz CMOD-IO PCI MODULbus Carrier Board Driver");
+MODULE_LICENSE("GPL");
+
+module_init(cmodio_init);
+module_exit(cmodio_exit);
index 85d63c04749b360f17ca2633eee17fd661dbd3c4..f621bcea3d02bfbda6610f409621199b1fa5a78e 100644 (file)
@@ -508,7 +508,7 @@ static int max8925_irq_init(struct max8925_chip *chip, int irq,
        max8925_reg_read(chip->i2c, MAX8925_ON_OFF_IRQ2);
        max8925_reg_read(chip->rtc, MAX8925_RTC_IRQ);
        max8925_reg_read(chip->adc, MAX8925_TSC_IRQ);
-       /* mask all interrupts */
+       /* mask all interrupts except for TSC */
        max8925_reg_write(chip->rtc, MAX8925_ALARM0_CNTL, 0);
        max8925_reg_write(chip->rtc, MAX8925_ALARM1_CNTL, 0);
        max8925_reg_write(chip->i2c, MAX8925_CHG_IRQ1_MASK, 0xff);
@@ -516,7 +516,6 @@ static int max8925_irq_init(struct max8925_chip *chip, int irq,
        max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ1_MASK, 0xff);
        max8925_reg_write(chip->i2c, MAX8925_ON_OFF_IRQ2_MASK, 0xff);
        max8925_reg_write(chip->rtc, MAX8925_RTC_IRQ_MASK, 0xff);
-       max8925_reg_write(chip->adc, MAX8925_TSC_IRQ_MASK, 0xff);
 
        mutex_init(&chip->irq_lock);
        chip->core_irq = irq;
@@ -547,7 +546,11 @@ static int max8925_irq_init(struct max8925_chip *chip, int irq,
                dev_err(chip->dev, "Failed to request core IRQ: %d\n", ret);
                chip->core_irq = 0;
        }
+
 tsc_irq:
+       /* mask TSC interrupt */
+       max8925_reg_write(chip->adc, MAX8925_TSC_IRQ_MASK, 0x0f);
+
        if (!pdata->tsc_irq) {
                dev_warn(chip->dev, "No interrupt support on TSC IRQ\n");
                return 0;
index d9fd8785da4d76a78a9e9a95661d1f93c7d46e72..e73f3f5252a89cf138dac35254263ed58630f72c 100644 (file)
@@ -173,8 +173,6 @@ static int __devexit max8925_remove(struct i2c_client *client)
        max8925_device_exit(chip);
        i2c_unregister_device(chip->adc);
        i2c_unregister_device(chip->rtc);
-       i2c_set_clientdata(chip->adc, NULL);
-       i2c_set_clientdata(chip->rtc, NULL);
        i2c_set_clientdata(chip->i2c, NULL);
        kfree(chip);
        return 0;
index a94b131a18efc72a2a7a6ebb4ec8bce6ef8ada2a..721948be12c77a7ad622e3559abc2f3953c1365a 100644 (file)
@@ -1228,6 +1228,7 @@ fail2:
        free_irq(client->irq, menelaus);
        flush_scheduled_work();
 fail1:
+       i2c_set_clientdata(client, NULL);
        kfree(menelaus);
        return err;
 }
@@ -1237,8 +1238,8 @@ static int __exit menelaus_remove(struct i2c_client *client)
        struct menelaus_chip    *menelaus = i2c_get_clientdata(client);
 
        free_irq(client->irq, menelaus);
-       kfree(menelaus);
        i2c_set_clientdata(client, NULL);
+       kfree(menelaus);
        the_menelaus = NULL;
        return 0;
 }
index 8ffbb7a85a7e67091105408f643e170f58bdcb1d..7dd76bceaae812ea439d69390dce8f71ca565ef7 100644 (file)
@@ -48,7 +48,7 @@ static int mfd_add_device(struct device *parent, int id,
                res[r].flags = cell->resources[r].flags;
 
                /* Find out base to use */
-               if (cell->resources[r].flags & IORESOURCE_MEM) {
+               if ((cell->resources[r].flags & IORESOURCE_MEM) && mem_base) {
                        res[r].parent = mem_base;
                        res[r].start = mem_base->start +
                                cell->resources[r].start;
index fe8f922f6654b6b43efe26670cdaae96b32c6eb7..aed0d2a9b032e0b3e5babe7949037df19470e1c7 100644 (file)
 struct pcf50633_adc_request {
        int mux;
        int avg;
-       int result;
        void (*callback)(struct pcf50633 *, void *, int);
        void *callback_param;
+};
 
-       /* Used in case of sync requests */
+struct pcf50633_adc_sync_request {
+       int result;
        struct completion completion;
-
 };
 
 #define PCF50633_MAX_ADC_FIFO_DEPTH 8
@@ -109,10 +109,10 @@ adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req)
        return 0;
 }
 
-static void
-pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result)
+static void pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param,
+       int result)
 {
-       struct pcf50633_adc_request *req = param;
+       struct pcf50633_adc_sync_request *req = param;
 
        req->result = result;
        complete(&req->completion);
@@ -120,28 +120,19 @@ pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result)
 
 int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg)
 {
-       struct pcf50633_adc_request *req;
-       int err;
+       struct pcf50633_adc_sync_request req;
+       int ret;
 
-       /* req is freed when the result is ready, in interrupt handler */
-       req = kzalloc(sizeof(*req), GFP_KERNEL);
-       if (!req)
-               return -ENOMEM;
-
-       req->mux = mux;
-       req->avg = avg;
-       req->callback =  pcf50633_adc_sync_read_callback;
-       req->callback_param = req;
+       init_completion(&req.completion);
 
-       init_completion(&req->completion);
-       err = adc_enqueue_request(pcf, req);
-       if (err)
-               return err;
+       ret = pcf50633_adc_async_read(pcf, mux, avg,
+               pcf50633_adc_sync_read_callback, &req);
+       if (ret)
+               return ret;
 
-       wait_for_completion(&req->completion);
+       wait_for_completion(&req.completion);
 
-       /* FIXME by this time req might be already freed */
-       return req->result;
+       return req.result;
 }
 EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read);
 
index dc95ddb708f1f19278c0c0f7f70f45ea903d9071..704736e6e9b9b2efea2c66f4cf3a6ee36183eaa4 100644 (file)
 #include <linux/workqueue.h>
 #include <linux/platform_device.h>
 #include <linux/i2c.h>
-#include <linux/irq.h>
 #include <linux/slab.h>
 
 #include <linux/mfd/pcf50633/core.h>
 
-/* Two MBCS registers used during cold start */
-#define PCF50633_REG_MBCS1             0x4b
-#define PCF50633_REG_MBCS2             0x4c
-#define PCF50633_MBCS1_USBPRES                 0x01
-#define PCF50633_MBCS1_ADAPTPRES       0x01
+int pcf50633_irq_init(struct pcf50633 *pcf, int irq);
+void pcf50633_irq_free(struct pcf50633 *pcf);
+#ifdef CONFIG_PM
+int pcf50633_irq_suspend(struct pcf50633 *pcf);
+int pcf50633_irq_resume(struct pcf50633 *pcf);
+#endif
 
 static int __pcf50633_read(struct pcf50633 *pcf, u8 reg, int num, u8 *data)
 {
@@ -215,244 +215,6 @@ static struct attribute_group pcf_attr_group = {
        .attrs  = pcf_sysfs_entries,
 };
 
-int pcf50633_register_irq(struct pcf50633 *pcf, int irq,
-                       void (*handler) (int, void *), void *data)
-{
-       if (irq < 0 || irq > PCF50633_NUM_IRQ || !handler)
-               return -EINVAL;
-
-       if (WARN_ON(pcf->irq_handler[irq].handler))
-               return -EBUSY;
-
-       mutex_lock(&pcf->lock);
-       pcf->irq_handler[irq].handler = handler;
-       pcf->irq_handler[irq].data = data;
-       mutex_unlock(&pcf->lock);
-
-       return 0;
-}
-EXPORT_SYMBOL_GPL(pcf50633_register_irq);
-
-int pcf50633_free_irq(struct pcf50633 *pcf, int irq)
-{
-       if (irq < 0 || irq > PCF50633_NUM_IRQ)
-               return -EINVAL;
-
-       mutex_lock(&pcf->lock);
-       pcf->irq_handler[irq].handler = NULL;
-       mutex_unlock(&pcf->lock);
-
-       return 0;
-}
-EXPORT_SYMBOL_GPL(pcf50633_free_irq);
-
-static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask)
-{
-       u8 reg, bits, tmp;
-       int ret = 0, idx;
-
-       idx = irq >> 3;
-       reg =  PCF50633_REG_INT1M + idx;
-       bits = 1 << (irq & 0x07);
-
-       mutex_lock(&pcf->lock);
-
-       if (mask) {
-               ret = __pcf50633_read(pcf, reg, 1, &tmp);
-               if (ret < 0)
-                       goto out;
-
-               tmp |= bits;
-
-               ret = __pcf50633_write(pcf, reg, 1, &tmp);
-               if (ret < 0)
-                       goto out;
-
-               pcf->mask_regs[idx] &= ~bits;
-               pcf->mask_regs[idx] |= bits;
-       } else {
-               ret = __pcf50633_read(pcf, reg, 1, &tmp);
-               if (ret < 0)
-                       goto out;
-
-               tmp &= ~bits;
-
-               ret = __pcf50633_write(pcf, reg, 1, &tmp);
-               if (ret < 0)
-                       goto out;
-
-               pcf->mask_regs[idx] &= ~bits;
-       }
-out:
-       mutex_unlock(&pcf->lock);
-
-       return ret;
-}
-
-int pcf50633_irq_mask(struct pcf50633 *pcf, int irq)
-{
-       dev_dbg(pcf->dev, "Masking IRQ %d\n", irq);
-
-       return __pcf50633_irq_mask_set(pcf, irq, 1);
-}
-EXPORT_SYMBOL_GPL(pcf50633_irq_mask);
-
-int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq)
-{
-       dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq);
-
-       return __pcf50633_irq_mask_set(pcf, irq, 0);
-}
-EXPORT_SYMBOL_GPL(pcf50633_irq_unmask);
-
-int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq)
-{
-       u8 reg, bits;
-
-       reg =  irq >> 3;
-       bits = 1 << (irq & 0x07);
-
-       return pcf->mask_regs[reg] & bits;
-}
-EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get);
-
-static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq)
-{
-       if (pcf->irq_handler[irq].handler)
-               pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data);
-}
-
-/* Maximum amount of time ONKEY is held before emergency action is taken */
-#define PCF50633_ONKEY1S_TIMEOUT 8
-
-static void pcf50633_irq_worker(struct work_struct *work)
-{
-       struct pcf50633 *pcf;
-       int ret, i, j;
-       u8 pcf_int[5], chgstat;
-
-       pcf = container_of(work, struct pcf50633, irq_work);
-
-       /* Read the 5 INT regs in one transaction */
-       ret = pcf50633_read_block(pcf, PCF50633_REG_INT1,
-                                               ARRAY_SIZE(pcf_int), pcf_int);
-       if (ret != ARRAY_SIZE(pcf_int)) {
-               dev_err(pcf->dev, "Error reading INT registers\n");
-
-               /*
-                * If this doesn't ACK the interrupt to the chip, we'll be
-                * called once again as we're level triggered.
-                */
-               goto out;
-       }
-
-       /* defeat 8s death from lowsys on A5 */
-       pcf50633_reg_write(pcf, PCF50633_REG_OOCSHDWN,  0x04);
-
-       /* We immediately read the usb and adapter status. We thus make sure
-        * only of USBINS/USBREM IRQ handlers are called */
-       if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) {
-               chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2);
-               if (chgstat & (0x3 << 4))
-                       pcf_int[0] &= ~(1 << PCF50633_INT1_USBREM);
-               else
-                       pcf_int[0] &= ~(1 << PCF50633_INT1_USBINS);
-       }
-
-       /* Make sure only one of ADPINS or ADPREM is set */
-       if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) {
-               chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2);
-               if (chgstat & (0x3 << 4))
-                       pcf_int[0] &= ~(1 << PCF50633_INT1_ADPREM);
-               else
-                       pcf_int[0] &= ~(1 << PCF50633_INT1_ADPINS);
-       }
-
-       dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x "
-                       "INT4=0x%02x INT5=0x%02x\n", pcf_int[0],
-                       pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]);
-
-       /* Some revisions of the chip don't have a 8s standby mode on
-        * ONKEY1S press. We try to manually do it in such cases. */
-       if ((pcf_int[0] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) {
-               dev_info(pcf->dev, "ONKEY1S held for %d secs\n",
-                                                       pcf->onkey1s_held);
-               if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT)
-                       if (pcf->pdata->force_shutdown)
-                               pcf->pdata->force_shutdown(pcf);
-       }
-
-       if (pcf_int[2] & PCF50633_INT3_ONKEY1S) {
-               dev_info(pcf->dev, "ONKEY1S held\n");
-               pcf->onkey1s_held = 1 ;
-
-               /* Unmask IRQ_SECOND */
-               pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M,
-                                               PCF50633_INT1_SECOND);
-
-               /* Unmask IRQ_ONKEYR */
-               pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M,
-                                               PCF50633_INT2_ONKEYR);
-       }
-
-       if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) {
-               pcf->onkey1s_held = 0;
-
-               /* Mask SECOND and ONKEYR interrupts */
-               if (pcf->mask_regs[0] & PCF50633_INT1_SECOND)
-                       pcf50633_reg_set_bit_mask(pcf,
-                                       PCF50633_REG_INT1M,
-                                       PCF50633_INT1_SECOND,
-                                       PCF50633_INT1_SECOND);
-
-               if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR)
-                       pcf50633_reg_set_bit_mask(pcf,
-                                       PCF50633_REG_INT2M,
-                                       PCF50633_INT2_ONKEYR,
-                                       PCF50633_INT2_ONKEYR);
-       }
-
-       /* Have we just resumed ? */
-       if (pcf->is_suspended) {
-               pcf->is_suspended = 0;
-
-               /* Set the resume reason filtering out non resumers */
-               for (i = 0; i < ARRAY_SIZE(pcf_int); i++)
-                       pcf->resume_reason[i] = pcf_int[i] &
-                                               pcf->pdata->resumers[i];
-
-               /* Make sure we don't pass on any ONKEY events to
-                * userspace now */
-               pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_ONKEYF);
-       }
-
-       for (i = 0; i < ARRAY_SIZE(pcf_int); i++) {
-               /* Unset masked interrupts */
-               pcf_int[i] &= ~pcf->mask_regs[i];
-
-               for (j = 0; j < 8 ; j++)
-                       if (pcf_int[i] & (1 << j))
-                               pcf50633_irq_call_handler(pcf, (i * 8) + j);
-       }
-
-out:
-       put_device(pcf->dev);
-       enable_irq(pcf->irq);
-}
-
-static irqreturn_t pcf50633_irq(int irq, void *data)
-{
-       struct pcf50633 *pcf = data;
-
-       dev_dbg(pcf->dev, "pcf50633_irq\n");
-
-       get_device(pcf->dev);
-       disable_irq_nosync(pcf->irq);
-       queue_work(pcf->work_queue, &pcf->irq_work);
-
-       return IRQ_HANDLED;
-}
-
 static void
 pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name,
                                                struct platform_device **pdev)
@@ -479,70 +241,17 @@ pcf50633_client_dev_register(struct pcf50633 *pcf, const char *name,
 static int pcf50633_suspend(struct i2c_client *client, pm_message_t state)
 {
        struct pcf50633 *pcf;
-       int ret = 0, i;
-       u8 res[5];
-
        pcf = i2c_get_clientdata(client);
 
-       /* Make sure our interrupt handlers are not called
-        * henceforth */
-       disable_irq(pcf->irq);
-
-       /* Make sure that any running IRQ worker has quit */
-       cancel_work_sync(&pcf->irq_work);
-
-       /* Save the masks */
-       ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M,
-                               ARRAY_SIZE(pcf->suspend_irq_masks),
-                                       pcf->suspend_irq_masks);
-       if (ret < 0) {
-               dev_err(pcf->dev, "error saving irq masks\n");
-               goto out;
-       }
-
-       /* Write wakeup irq masks */
-       for (i = 0; i < ARRAY_SIZE(res); i++)
-               res[i] = ~pcf->pdata->resumers[i];
-
-       ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M,
-                                       ARRAY_SIZE(res), &res[0]);
-       if (ret < 0) {
-               dev_err(pcf->dev, "error writing wakeup irq masks\n");
-               goto out;
-       }
-
-       pcf->is_suspended = 1;
-
-out:
-       return ret;
+       return pcf50633_irq_suspend(pcf);
 }
 
 static int pcf50633_resume(struct i2c_client *client)
 {
        struct pcf50633 *pcf;
-       int ret;
-
        pcf = i2c_get_clientdata(client);
 
-       /* Write the saved mask registers */
-       ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M,
-                               ARRAY_SIZE(pcf->suspend_irq_masks),
-                                       pcf->suspend_irq_masks);
-       if (ret < 0)
-               dev_err(pcf->dev, "Error restoring saved suspend masks\n");
-
-       /* Restore regulators' state */
-
-
-       get_device(pcf->dev);
-
-       /*
-        * Clear any pending interrupts and set resume reason if any.
-        * This will leave with enable_irq()
-        */
-       pcf50633_irq_worker(&pcf->irq_work);
-
-       return 0;
+       return pcf50633_irq_resume(pcf);
 }
 #else
 #define pcf50633_suspend NULL
@@ -573,43 +282,19 @@ static int __devinit pcf50633_probe(struct i2c_client *client,
        i2c_set_clientdata(client, pcf);
        pcf->dev = &client->dev;
        pcf->i2c_client = client;
-       pcf->irq = client->irq;
-       pcf->work_queue = create_singlethread_workqueue("pcf50633");
-
-       if (!pcf->work_queue) {
-               dev_err(&client->dev, "Failed to alloc workqueue\n");
-               ret = -ENOMEM;
-               goto err_free;
-       }
-
-       INIT_WORK(&pcf->irq_work, pcf50633_irq_worker);
 
        version = pcf50633_reg_read(pcf, 0);
        variant = pcf50633_reg_read(pcf, 1);
        if (version < 0 || variant < 0) {
                dev_err(pcf->dev, "Unable to probe pcf50633\n");
                ret = -ENODEV;
-               goto err_destroy_workqueue;
+               goto err_free;
        }
 
        dev_info(pcf->dev, "Probed device version %d variant %d\n",
                                                        version, variant);
 
-       /* Enable all interrupts except RTC SECOND */
-       pcf->mask_regs[0] = 0x80;
-       pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]);
-       pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00);
-       pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00);
-       pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00);
-       pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00);
-
-       ret = request_irq(client->irq, pcf50633_irq,
-                                       IRQF_TRIGGER_LOW, "pcf50633", pcf);
-
-       if (ret) {
-               dev_err(pcf->dev, "Failed to request IRQ %d\n", ret);
-               goto err_destroy_workqueue;
-       }
+       pcf50633_irq_init(pcf, client->irq);
 
        /* Create sub devices */
        pcf50633_client_dev_register(pcf, "pcf50633-input",
@@ -641,10 +326,6 @@ static int __devinit pcf50633_probe(struct i2c_client *client,
                platform_device_add(pdev);
        }
 
-       if (enable_irq_wake(client->irq) < 0)
-               dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source"
-                       "in this hardware revision", client->irq);
-
        ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group);
        if (ret)
                dev_err(pcf->dev, "error creating sysfs entries\n");
@@ -654,8 +335,6 @@ static int __devinit pcf50633_probe(struct i2c_client *client,
 
        return 0;
 
-err_destroy_workqueue:
-       destroy_workqueue(pcf->work_queue);
 err_free:
        i2c_set_clientdata(client, NULL);
        kfree(pcf);
@@ -668,8 +347,7 @@ static int __devexit pcf50633_remove(struct i2c_client *client)
        struct pcf50633 *pcf = i2c_get_clientdata(client);
        int i;
 
-       free_irq(pcf->irq, pcf);
-       destroy_workqueue(pcf->work_queue);
+       pcf50633_irq_free(pcf);
 
        platform_device_unregister(pcf->input_pdev);
        platform_device_unregister(pcf->rtc_pdev);
@@ -679,6 +357,7 @@ static int __devexit pcf50633_remove(struct i2c_client *client)
        for (i = 0; i < PCF50633_NUM_REGULATORS; i++)
                platform_device_unregister(pcf->regulator_pdev[i]);
 
+       i2c_set_clientdata(client, NULL);
        kfree(pcf);
 
        return 0;
diff --git a/drivers/mfd/pcf50633-irq.c b/drivers/mfd/pcf50633-irq.c
new file mode 100644 (file)
index 0000000..1b0192f
--- /dev/null
@@ -0,0 +1,318 @@
+/* NXP PCF50633 Power Management Unit (PMU) driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Harald Welte <laforge@openmoko.org>
+ *        Balaji Rao <balajirrao@openmoko.org>
+ * All rights reserved.
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/pcf50633/core.h>
+
+/* Two MBCS registers used during cold start */
+#define PCF50633_REG_MBCS1             0x4b
+#define PCF50633_REG_MBCS2             0x4c
+#define PCF50633_MBCS1_USBPRES                 0x01
+#define PCF50633_MBCS1_ADAPTPRES       0x01
+
+int pcf50633_register_irq(struct pcf50633 *pcf, int irq,
+                       void (*handler) (int, void *), void *data)
+{
+       if (irq < 0 || irq >= PCF50633_NUM_IRQ || !handler)
+               return -EINVAL;
+
+       if (WARN_ON(pcf->irq_handler[irq].handler))
+               return -EBUSY;
+
+       mutex_lock(&pcf->lock);
+       pcf->irq_handler[irq].handler = handler;
+       pcf->irq_handler[irq].data = data;
+       mutex_unlock(&pcf->lock);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50633_register_irq);
+
+int pcf50633_free_irq(struct pcf50633 *pcf, int irq)
+{
+       if (irq < 0 || irq >= PCF50633_NUM_IRQ)
+               return -EINVAL;
+
+       mutex_lock(&pcf->lock);
+       pcf->irq_handler[irq].handler = NULL;
+       mutex_unlock(&pcf->lock);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50633_free_irq);
+
+static int __pcf50633_irq_mask_set(struct pcf50633 *pcf, int irq, u8 mask)
+{
+       u8 reg, bit;
+       int ret = 0, idx;
+
+       idx = irq >> 3;
+       reg = PCF50633_REG_INT1M + idx;
+       bit = 1 << (irq & 0x07);
+
+       pcf50633_reg_set_bit_mask(pcf, reg, bit, mask ? bit : 0);
+
+       mutex_lock(&pcf->lock);
+
+       if (mask)
+               pcf->mask_regs[idx] |= bit;
+       else
+               pcf->mask_regs[idx] &= ~bit;
+
+       mutex_unlock(&pcf->lock);
+
+       return ret;
+}
+
+int pcf50633_irq_mask(struct pcf50633 *pcf, int irq)
+{
+       dev_dbg(pcf->dev, "Masking IRQ %d\n", irq);
+
+       return __pcf50633_irq_mask_set(pcf, irq, 1);
+}
+EXPORT_SYMBOL_GPL(pcf50633_irq_mask);
+
+int pcf50633_irq_unmask(struct pcf50633 *pcf, int irq)
+{
+       dev_dbg(pcf->dev, "Unmasking IRQ %d\n", irq);
+
+       return __pcf50633_irq_mask_set(pcf, irq, 0);
+}
+EXPORT_SYMBOL_GPL(pcf50633_irq_unmask);
+
+int pcf50633_irq_mask_get(struct pcf50633 *pcf, int irq)
+{
+       u8 reg, bits;
+
+       reg =  irq >> 3;
+       bits = 1 << (irq & 0x07);
+
+       return pcf->mask_regs[reg] & bits;
+}
+EXPORT_SYMBOL_GPL(pcf50633_irq_mask_get);
+
+static void pcf50633_irq_call_handler(struct pcf50633 *pcf, int irq)
+{
+       if (pcf->irq_handler[irq].handler)
+               pcf->irq_handler[irq].handler(irq, pcf->irq_handler[irq].data);
+}
+
+/* Maximum amount of time ONKEY is held before emergency action is taken */
+#define PCF50633_ONKEY1S_TIMEOUT 8
+
+static irqreturn_t pcf50633_irq(int irq, void *data)
+{
+       struct pcf50633 *pcf = data;
+       int ret, i, j;
+       u8 pcf_int[5], chgstat;
+
+       /* Read the 5 INT regs in one transaction */
+       ret = pcf50633_read_block(pcf, PCF50633_REG_INT1,
+                                               ARRAY_SIZE(pcf_int), pcf_int);
+       if (ret != ARRAY_SIZE(pcf_int)) {
+               dev_err(pcf->dev, "Error reading INT registers\n");
+
+               /*
+                * If this doesn't ACK the interrupt to the chip, we'll be
+                * called once again as we're level triggered.
+                */
+               goto out;
+       }
+
+       /* defeat 8s death from lowsys on A5 */
+       pcf50633_reg_write(pcf, PCF50633_REG_OOCSHDWN,  0x04);
+
+       /* We immediately read the usb and adapter status. We thus make sure
+        * only of USBINS/USBREM IRQ handlers are called */
+       if (pcf_int[0] & (PCF50633_INT1_USBINS | PCF50633_INT1_USBREM)) {
+               chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2);
+               if (chgstat & (0x3 << 4))
+                       pcf_int[0] &= ~PCF50633_INT1_USBREM;
+               else
+                       pcf_int[0] &= ~PCF50633_INT1_USBINS;
+       }
+
+       /* Make sure only one of ADPINS or ADPREM is set */
+       if (pcf_int[0] & (PCF50633_INT1_ADPINS | PCF50633_INT1_ADPREM)) {
+               chgstat = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2);
+               if (chgstat & (0x3 << 4))
+                       pcf_int[0] &= ~PCF50633_INT1_ADPREM;
+               else
+                       pcf_int[0] &= ~PCF50633_INT1_ADPINS;
+       }
+
+       dev_dbg(pcf->dev, "INT1=0x%02x INT2=0x%02x INT3=0x%02x "
+                       "INT4=0x%02x INT5=0x%02x\n", pcf_int[0],
+                       pcf_int[1], pcf_int[2], pcf_int[3], pcf_int[4]);
+
+       /* Some revisions of the chip don't have a 8s standby mode on
+        * ONKEY1S press. We try to manually do it in such cases. */
+       if ((pcf_int[0] & PCF50633_INT1_SECOND) && pcf->onkey1s_held) {
+               dev_info(pcf->dev, "ONKEY1S held for %d secs\n",
+                                                       pcf->onkey1s_held);
+               if (pcf->onkey1s_held++ == PCF50633_ONKEY1S_TIMEOUT)
+                       if (pcf->pdata->force_shutdown)
+                               pcf->pdata->force_shutdown(pcf);
+       }
+
+       if (pcf_int[2] & PCF50633_INT3_ONKEY1S) {
+               dev_info(pcf->dev, "ONKEY1S held\n");
+               pcf->onkey1s_held = 1 ;
+
+               /* Unmask IRQ_SECOND */
+               pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT1M,
+                                               PCF50633_INT1_SECOND);
+
+               /* Unmask IRQ_ONKEYR */
+               pcf50633_reg_clear_bits(pcf, PCF50633_REG_INT2M,
+                                               PCF50633_INT2_ONKEYR);
+       }
+
+       if ((pcf_int[1] & PCF50633_INT2_ONKEYR) && pcf->onkey1s_held) {
+               pcf->onkey1s_held = 0;
+
+               /* Mask SECOND and ONKEYR interrupts */
+               if (pcf->mask_regs[0] & PCF50633_INT1_SECOND)
+                       pcf50633_reg_set_bit_mask(pcf,
+                                       PCF50633_REG_INT1M,
+                                       PCF50633_INT1_SECOND,
+                                       PCF50633_INT1_SECOND);
+
+               if (pcf->mask_regs[1] & PCF50633_INT2_ONKEYR)
+                       pcf50633_reg_set_bit_mask(pcf,
+                                       PCF50633_REG_INT2M,
+                                       PCF50633_INT2_ONKEYR,
+                                       PCF50633_INT2_ONKEYR);
+       }
+
+       /* Have we just resumed ? */
+       if (pcf->is_suspended) {
+               pcf->is_suspended = 0;
+
+               /* Set the resume reason filtering out non resumers */
+               for (i = 0; i < ARRAY_SIZE(pcf_int); i++)
+                       pcf->resume_reason[i] = pcf_int[i] &
+                                               pcf->pdata->resumers[i];
+
+               /* Make sure we don't pass on any ONKEY events to
+                * userspace now */
+               pcf_int[1] &= ~(PCF50633_INT2_ONKEYR | PCF50633_INT2_ONKEYF);
+       }
+
+       for (i = 0; i < ARRAY_SIZE(pcf_int); i++) {
+               /* Unset masked interrupts */
+               pcf_int[i] &= ~pcf->mask_regs[i];
+
+               for (j = 0; j < 8 ; j++)
+                       if (pcf_int[i] & (1 << j))
+                               pcf50633_irq_call_handler(pcf, (i * 8) + j);
+       }
+
+out:
+       return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+
+int pcf50633_irq_suspend(struct pcf50633 *pcf)
+{
+       int ret;
+       int i;
+       u8 res[5];
+
+
+       /* Make sure our interrupt handlers are not called
+        * henceforth */
+       disable_irq(pcf->irq);
+
+       /* Save the masks */
+       ret = pcf50633_read_block(pcf, PCF50633_REG_INT1M,
+                               ARRAY_SIZE(pcf->suspend_irq_masks),
+                                       pcf->suspend_irq_masks);
+       if (ret < 0) {
+               dev_err(pcf->dev, "error saving irq masks\n");
+               goto out;
+       }
+
+       /* Write wakeup irq masks */
+       for (i = 0; i < ARRAY_SIZE(res); i++)
+               res[i] = ~pcf->pdata->resumers[i];
+
+       ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M,
+                                       ARRAY_SIZE(res), &res[0]);
+       if (ret < 0) {
+               dev_err(pcf->dev, "error writing wakeup irq masks\n");
+               goto out;
+       }
+
+       pcf->is_suspended = 1;
+
+out:
+       return ret;
+}
+
+int pcf50633_irq_resume(struct pcf50633 *pcf)
+{
+       int ret;
+
+       /* Write the saved mask registers */
+       ret = pcf50633_write_block(pcf, PCF50633_REG_INT1M,
+                               ARRAY_SIZE(pcf->suspend_irq_masks),
+                                       pcf->suspend_irq_masks);
+       if (ret < 0)
+               dev_err(pcf->dev, "Error restoring saved suspend masks\n");
+
+       enable_irq(pcf->irq);
+
+       return ret;
+}
+
+#endif
+
+int pcf50633_irq_init(struct pcf50633 *pcf, int irq)
+{
+       int ret;
+
+       pcf->irq = irq;
+
+       /* Enable all interrupts except RTC SECOND */
+       pcf->mask_regs[0] = 0x80;
+       pcf50633_reg_write(pcf, PCF50633_REG_INT1M, pcf->mask_regs[0]);
+       pcf50633_reg_write(pcf, PCF50633_REG_INT2M, 0x00);
+       pcf50633_reg_write(pcf, PCF50633_REG_INT3M, 0x00);
+       pcf50633_reg_write(pcf, PCF50633_REG_INT4M, 0x00);
+       pcf50633_reg_write(pcf, PCF50633_REG_INT5M, 0x00);
+
+       ret = request_threaded_irq(irq, NULL, pcf50633_irq,
+                                       IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+                                       "pcf50633", pcf);
+
+       if (ret)
+               dev_err(pcf->dev, "Failed to request IRQ %d\n", ret);
+
+       if (enable_irq_wake(irq) < 0)
+               dev_err(pcf->dev, "IRQ %u cannot be enabled as wake-up source"
+                       "in this hardware revision", irq);
+
+       return ret;
+}
+
+void pcf50633_irq_free(struct pcf50633 *pcf)
+{
+       free_irq(pcf->irq, pcf);
+}
diff --git a/drivers/mfd/rdc321x-southbridge.c b/drivers/mfd/rdc321x-southbridge.c
new file mode 100644 (file)
index 0000000..5092297
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * RDC321x MFD southbrige driver
+ *
+ * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>
+ * Copyright (C) 2010 Bernhard Loos <bernhardloos@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/pci.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/rdc321x.h>
+
+static struct rdc321x_wdt_pdata rdc321x_wdt_pdata;
+
+static struct resource rdc321x_wdt_resource[] = {
+       {
+               .name   = "wdt-reg",
+               .start  = RDC321X_WDT_CTRL,
+               .end    = RDC321X_WDT_CTRL + 0x3,
+               .flags  = IORESOURCE_IO,
+       }
+};
+
+static struct rdc321x_gpio_pdata rdc321x_gpio_pdata = {
+       .max_gpios      = RDC321X_MAX_GPIO,
+};
+
+static struct resource rdc321x_gpio_resources[] = {
+       {
+               .name   = "gpio-reg1",
+               .start  = RDC321X_GPIO_CTRL_REG1,
+               .end    = RDC321X_GPIO_CTRL_REG1 + 0x7,
+               .flags  = IORESOURCE_IO,
+       }, {
+               .name   = "gpio-reg2",
+               .start  = RDC321X_GPIO_CTRL_REG2,
+               .end    = RDC321X_GPIO_CTRL_REG2 + 0x7,
+               .flags  = IORESOURCE_IO,
+       }
+};
+
+static struct mfd_cell rdc321x_sb_cells[] = {
+       {
+               .name           = "rdc321x-wdt",
+               .resources      = rdc321x_wdt_resource,
+               .num_resources  = ARRAY_SIZE(rdc321x_wdt_resource),
+               .driver_data    = &rdc321x_wdt_pdata,
+       }, {
+               .name           = "rdc321x-gpio",
+               .resources      = rdc321x_gpio_resources,
+               .num_resources  = ARRAY_SIZE(rdc321x_gpio_resources),
+               .driver_data    = &rdc321x_gpio_pdata,
+       },
+};
+
+static int __devinit rdc321x_sb_probe(struct pci_dev *pdev,
+                                       const struct pci_device_id *ent)
+{
+       int err;
+
+       err = pci_enable_device(pdev);
+       if (err) {
+               dev_err(&pdev->dev, "failed to enable device\n");
+               return err;
+       }
+
+       rdc321x_gpio_pdata.sb_pdev = pdev;
+       rdc321x_wdt_pdata.sb_pdev = pdev;
+
+       return mfd_add_devices(&pdev->dev, -1,
+               rdc321x_sb_cells, ARRAY_SIZE(rdc321x_sb_cells), NULL, 0);
+}
+
+static void __devexit rdc321x_sb_remove(struct pci_dev *pdev)
+{
+       mfd_remove_devices(&pdev->dev);
+}
+
+static DEFINE_PCI_DEVICE_TABLE(rdc321x_sb_table) = {
+       { PCI_DEVICE(PCI_VENDOR_ID_RDC, PCI_DEVICE_ID_RDC_R6030) },
+       {}
+};
+
+static struct pci_driver rdc321x_sb_driver = {
+       .name           = "RDC321x Southbridge",
+       .id_table       = rdc321x_sb_table,
+       .probe          = rdc321x_sb_probe,
+       .remove         = __devexit_p(rdc321x_sb_remove),
+};
+
+static int __init rdc321x_sb_init(void)
+{
+       return pci_register_driver(&rdc321x_sb_driver);
+}
+
+static void __exit rdc321x_sb_exit(void)
+{
+       pci_unregister_driver(&rdc321x_sb_driver);
+}
+
+module_init(rdc321x_sb_init);
+module_exit(rdc321x_sb_exit);
+
+MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("RDC R-321x MFD southbridge driver");
index da6383a934ac33ac57a39a010f23f94ba1c9daa1..5041d33adf0babf3db03c2eb34688a2f77883479 100644 (file)
@@ -318,6 +318,9 @@ static int t7l66xb_probe(struct platform_device *dev)
        struct resource *iomem, *rscr;
        int ret;
 
+       if (pdata == NULL)
+               return -EINVAL;
+
        iomem = platform_get_resource(dev, IORESOURCE_MEM, 0);
        if (!iomem)
                return -EINVAL;
diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c
new file mode 100644 (file)
index 0000000..715f095
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License, version 2
+ * Author: Hanumath Prasad <hanumath.prasad@stericsson.com> for ST-Ericsson
+ * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/tc35892.h>
+
+/**
+ * tc35892_reg_read() - read a single TC35892 register
+ * @tc35892:   Device to read from
+ * @reg:       Register to read
+ */
+int tc35892_reg_read(struct tc35892 *tc35892, u8 reg)
+{
+       int ret;
+
+       ret = i2c_smbus_read_byte_data(tc35892->i2c, reg);
+       if (ret < 0)
+               dev_err(tc35892->dev, "failed to read reg %#x: %d\n",
+                       reg, ret);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(tc35892_reg_read);
+
+/**
+ * tc35892_reg_read() - write a single TC35892 register
+ * @tc35892:   Device to write to
+ * @reg:       Register to read
+ * @data:      Value to write
+ */
+int tc35892_reg_write(struct tc35892 *tc35892, u8 reg, u8 data)
+{
+       int ret;
+
+       ret = i2c_smbus_write_byte_data(tc35892->i2c, reg, data);
+       if (ret < 0)
+               dev_err(tc35892->dev, "failed to write reg %#x: %d\n",
+                       reg, ret);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(tc35892_reg_write);
+
+/**
+ * tc35892_block_read() - read multiple TC35892 registers
+ * @tc35892:   Device to read from
+ * @reg:       First register
+ * @length:    Number of registers
+ * @values:    Buffer to write to
+ */
+int tc35892_block_read(struct tc35892 *tc35892, u8 reg, u8 length, u8 *values)
+{
+       int ret;
+
+       ret = i2c_smbus_read_i2c_block_data(tc35892->i2c, reg, length, values);
+       if (ret < 0)
+               dev_err(tc35892->dev, "failed to read regs %#x: %d\n",
+                       reg, ret);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(tc35892_block_read);
+
+/**
+ * tc35892_block_write() - write multiple TC35892 registers
+ * @tc35892:   Device to write to
+ * @reg:       First register
+ * @length:    Number of registers
+ * @values:    Values to write
+ */
+int tc35892_block_write(struct tc35892 *tc35892, u8 reg, u8 length,
+                       const u8 *values)
+{
+       int ret;
+
+       ret = i2c_smbus_write_i2c_block_data(tc35892->i2c, reg, length,
+                                            values);
+       if (ret < 0)
+               dev_err(tc35892->dev, "failed to write regs %#x: %d\n",
+                       reg, ret);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(tc35892_block_write);
+
+/**
+ * tc35892_set_bits() - set the value of a bitfield in a TC35892 register
+ * @tc35892:   Device to write to
+ * @reg:       Register to write
+ * @mask:      Mask of bits to set
+ * @values:    Value to set
+ */
+int tc35892_set_bits(struct tc35892 *tc35892, u8 reg, u8 mask, u8 val)
+{
+       int ret;
+
+       mutex_lock(&tc35892->lock);
+
+       ret = tc35892_reg_read(tc35892, reg);
+       if (ret < 0)
+               goto out;
+
+       ret &= ~mask;
+       ret |= val;
+
+       ret = tc35892_reg_write(tc35892, reg, ret);
+
+out:
+       mutex_unlock(&tc35892->lock);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(tc35892_set_bits);
+
+static struct resource gpio_resources[] = {
+       {
+               .start  = TC35892_INT_GPIIRQ,
+               .end    = TC35892_INT_GPIIRQ,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct mfd_cell tc35892_devs[] = {
+       {
+               .name           = "tc35892-gpio",
+               .num_resources  = ARRAY_SIZE(gpio_resources),
+               .resources      = &gpio_resources[0],
+       },
+};
+
+static irqreturn_t tc35892_irq(int irq, void *data)
+{
+       struct tc35892 *tc35892 = data;
+       int status;
+
+       status = tc35892_reg_read(tc35892, TC35892_IRQST);
+       if (status < 0)
+               return IRQ_NONE;
+
+       while (status) {
+               int bit = __ffs(status);
+
+               handle_nested_irq(tc35892->irq_base + bit);
+               status &= ~(1 << bit);
+       }
+
+       /*
+        * A dummy read or write (to any register) appears to be necessary to
+        * have the last interrupt clear (for example, GPIO IC write) take
+        * effect.
+        */
+       tc35892_reg_read(tc35892, TC35892_IRQST);
+
+       return IRQ_HANDLED;
+}
+
+static void tc35892_irq_dummy(unsigned int irq)
+{
+       /* No mask/unmask at this level */
+}
+
+static struct irq_chip tc35892_irq_chip = {
+       .name   = "tc35892",
+       .mask   = tc35892_irq_dummy,
+       .unmask = tc35892_irq_dummy,
+};
+
+static int tc35892_irq_init(struct tc35892 *tc35892)
+{
+       int base = tc35892->irq_base;
+       int irq;
+
+       for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) {
+               set_irq_chip_data(irq, tc35892);
+               set_irq_chip_and_handler(irq, &tc35892_irq_chip,
+                                        handle_edge_irq);
+               set_irq_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+               set_irq_flags(irq, IRQF_VALID);
+#else
+               set_irq_noprobe(irq);
+#endif
+       }
+
+       return 0;
+}
+
+static void tc35892_irq_remove(struct tc35892 *tc35892)
+{
+       int base = tc35892->irq_base;
+       int irq;
+
+       for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) {
+#ifdef CONFIG_ARM
+               set_irq_flags(irq, 0);
+#endif
+               set_irq_chip_and_handler(irq, NULL, NULL);
+               set_irq_chip_data(irq, NULL);
+       }
+}
+
+static int tc35892_chip_init(struct tc35892 *tc35892)
+{
+       int manf, ver, ret;
+
+       manf = tc35892_reg_read(tc35892, TC35892_MANFCODE);
+       if (manf < 0)
+               return manf;
+
+       ver = tc35892_reg_read(tc35892, TC35892_VERSION);
+       if (ver < 0)
+               return ver;
+
+       if (manf != TC35892_MANFCODE_MAGIC) {
+               dev_err(tc35892->dev, "unknown manufacturer: %#x\n", manf);
+               return -EINVAL;
+       }
+
+       dev_info(tc35892->dev, "manufacturer: %#x, version: %#x\n", manf, ver);
+
+       /* Put everything except the IRQ module into reset */
+       ret = tc35892_reg_write(tc35892, TC35892_RSTCTRL,
+                               TC35892_RSTCTRL_TIMRST
+                               | TC35892_RSTCTRL_ROTRST
+                               | TC35892_RSTCTRL_KBDRST
+                               | TC35892_RSTCTRL_GPIRST);
+       if (ret < 0)
+               return ret;
+
+       /* Clear the reset interrupt. */
+       return tc35892_reg_write(tc35892, TC35892_RSTINTCLR, 0x1);
+}
+
+static int __devinit tc35892_probe(struct i2c_client *i2c,
+                                  const struct i2c_device_id *id)
+{
+       struct tc35892_platform_data *pdata = i2c->dev.platform_data;
+       struct tc35892 *tc35892;
+       int ret;
+
+       if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA
+                                    | I2C_FUNC_SMBUS_I2C_BLOCK))
+               return -EIO;
+
+       tc35892 = kzalloc(sizeof(struct tc35892), GFP_KERNEL);
+       if (!tc35892)
+               return -ENOMEM;
+
+       mutex_init(&tc35892->lock);
+
+       tc35892->dev = &i2c->dev;
+       tc35892->i2c = i2c;
+       tc35892->pdata = pdata;
+       tc35892->irq_base = pdata->irq_base;
+       tc35892->num_gpio = id->driver_data;
+
+       i2c_set_clientdata(i2c, tc35892);
+
+       ret = tc35892_chip_init(tc35892);
+       if (ret)
+               goto out_free;
+
+       ret = tc35892_irq_init(tc35892);
+       if (ret)
+               goto out_free;
+
+       ret = request_threaded_irq(tc35892->i2c->irq, NULL, tc35892_irq,
+                                  IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+                                  "tc35892", tc35892);
+       if (ret) {
+               dev_err(tc35892->dev, "failed to request IRQ: %d\n", ret);
+               goto out_removeirq;
+       }
+
+       ret = mfd_add_devices(tc35892->dev, -1, tc35892_devs,
+                             ARRAY_SIZE(tc35892_devs), NULL,
+                             tc35892->irq_base);
+       if (ret) {
+               dev_err(tc35892->dev, "failed to add children\n");
+               goto out_freeirq;
+       }
+
+       return 0;
+
+out_freeirq:
+       free_irq(tc35892->i2c->irq, tc35892);
+out_removeirq:
+       tc35892_irq_remove(tc35892);
+out_free:
+       i2c_set_clientdata(i2c, NULL);
+       kfree(tc35892);
+       return ret;
+}
+
+static int __devexit tc35892_remove(struct i2c_client *client)
+{
+       struct tc35892 *tc35892 = i2c_get_clientdata(client);
+
+       mfd_remove_devices(tc35892->dev);
+
+       free_irq(tc35892->i2c->irq, tc35892);
+       tc35892_irq_remove(tc35892);
+
+       i2c_set_clientdata(client, NULL);
+       kfree(tc35892);
+
+       return 0;
+}
+
+static const struct i2c_device_id tc35892_id[] = {
+       { "tc35892", 24 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, tc35892_id);
+
+static struct i2c_driver tc35892_driver = {
+       .driver.name    = "tc35892",
+       .driver.owner   = THIS_MODULE,
+       .probe          = tc35892_probe,
+       .remove         = __devexit_p(tc35892_remove),
+       .id_table       = tc35892_id,
+};
+
+static int __init tc35892_init(void)
+{
+       return i2c_add_driver(&tc35892_driver);
+}
+subsys_initcall(tc35892_init);
+
+static void __exit tc35892_exit(void)
+{
+       i2c_del_driver(&tc35892_driver);
+}
+module_exit(tc35892_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("TC35892 MFD core driver");
+MODULE_AUTHOR("Hanumath Prasad, Rabin Vincent");
index 7f478ec4184b47d8aa0f04b54cbeccf2a418e258..ac5995026c8836f392806293a6092c4f1ce30ac7 100644 (file)
@@ -31,6 +31,7 @@
 
 #include <linux/i2c.h>
 #include <linux/i2c-ocores.h>
+#include <linux/i2c-xiic.h>
 #include <linux/i2c/tsc2007.h>
 
 #include <linux/spi/spi.h>
@@ -40,6 +41,8 @@
 
 #include <media/timb_radio.h>
 
+#include <linux/timb_dma.h>
+
 #include "timberdale.h"
 
 #define DRIVER_NAME "timberdale"
@@ -69,6 +72,12 @@ static struct i2c_board_info timberdale_i2c_board_info[] = {
        },
 };
 
+static __devinitdata struct xiic_i2c_platform_data
+timberdale_xiic_platform_data = {
+       .devices = timberdale_i2c_board_info,
+       .num_devices = ARRAY_SIZE(timberdale_i2c_board_info)
+};
+
 static __devinitdata struct ocores_i2c_platform_data
 timberdale_ocores_platform_data = {
        .regstep = 4,
@@ -77,7 +86,20 @@ timberdale_ocores_platform_data = {
        .num_devices = ARRAY_SIZE(timberdale_i2c_board_info)
 };
 
-const static __devinitconst struct resource timberdale_ocores_resources[] = {
+static const __devinitconst struct resource timberdale_xiic_resources[] = {
+       {
+               .start  = XIICOFFSET,
+               .end    = XIICEND,
+               .flags  = IORESOURCE_MEM,
+       },
+       {
+               .start  = IRQ_TIMBERDALE_I2C,
+               .end    = IRQ_TIMBERDALE_I2C,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static const __devinitconst struct resource timberdale_ocores_resources[] = {
        {
                .start  = OCORESOFFSET,
                .end    = OCORESEND,
@@ -126,7 +148,7 @@ static __devinitdata struct xspi_platform_data timberdale_xspi_platform_data = {
         */
 };
 
-const static __devinitconst struct resource timberdale_spi_resources[] = {
+static const __devinitconst struct resource timberdale_spi_resources[] = {
        {
                .start  = SPIOFFSET,
                .end    = SPIEND,
@@ -139,7 +161,7 @@ const static __devinitconst struct resource timberdale_spi_resources[] = {
        },
 };
 
-const static __devinitconst struct resource timberdale_eth_resources[] = {
+static const __devinitconst struct resource timberdale_eth_resources[] = {
        {
                .start  = ETHOFFSET,
                .end    = ETHEND,
@@ -159,7 +181,7 @@ static __devinitdata struct timbgpio_platform_data
        .irq_base = 200,
 };
 
-const static __devinitconst struct resource timberdale_gpio_resources[] = {
+static const __devinitconst struct resource timberdale_gpio_resources[] = {
        {
                .start  = GPIOOFFSET,
                .end    = GPIOEND,
@@ -172,7 +194,7 @@ const static __devinitconst struct resource timberdale_gpio_resources[] = {
        },
 };
 
-const static __devinitconst struct resource timberdale_mlogicore_resources[] = {
+static const __devinitconst struct resource timberdale_mlogicore_resources[] = {
        {
                .start  = MLCOREOFFSET,
                .end    = MLCOREEND,
@@ -190,7 +212,7 @@ const static __devinitconst struct resource timberdale_mlogicore_resources[] = {
        },
 };
 
-const static __devinitconst struct resource timberdale_uart_resources[] = {
+static const __devinitconst struct resource timberdale_uart_resources[] = {
        {
                .start  = UARTOFFSET,
                .end    = UARTEND,
@@ -203,7 +225,7 @@ const static __devinitconst struct resource timberdale_uart_resources[] = {
        },
 };
 
-const static __devinitconst struct resource timberdale_uartlite_resources[] = {
+static const __devinitconst struct resource timberdale_uartlite_resources[] = {
        {
                .start  = UARTLITEOFFSET,
                .end    = UARTLITEEND,
@@ -216,7 +238,7 @@ const static __devinitconst struct resource timberdale_uartlite_resources[] = {
        },
 };
 
-const static __devinitconst struct resource timberdale_radio_resources[] = {
+static const __devinitconst struct resource timberdale_radio_resources[] = {
        {
                .start  = RDSOFFSET,
                .end    = RDSEND,
@@ -250,7 +272,66 @@ static __devinitdata struct timb_radio_platform_data
        }
 };
 
-const static __devinitconst struct resource timberdale_dma_resources[] = {
+static __devinitdata struct timb_dma_platform_data timb_dma_platform_data = {
+       .nr_channels = 10,
+       .channels = {
+               {
+                       /* UART RX */
+                       .rx = true,
+                       .descriptors = 2,
+                       .descriptor_elements = 1
+               },
+               {
+                       /* UART TX */
+                       .rx = false,
+                       .descriptors = 2,
+                       .descriptor_elements = 1
+               },
+               {
+                       /* MLB RX */
+                       .rx = true,
+                       .descriptors = 2,
+                       .descriptor_elements = 1
+               },
+               {
+                       /* MLB TX */
+                       .rx = false,
+                       .descriptors = 2,
+                       .descriptor_elements = 1
+               },
+               {
+                       /* Video RX */
+                       .rx = true,
+                       .bytes_per_line = 1440,
+                       .descriptors = 2,
+                       .descriptor_elements = 16
+               },
+               {
+                       /* Video framedrop */
+               },
+               {
+                       /* SDHCI RX */
+                       .rx = true,
+               },
+               {
+                       /* SDHCI TX */
+               },
+               {
+                       /* ETH RX */
+                       .rx = true,
+                       .descriptors = 2,
+                       .descriptor_elements = 1
+               },
+               {
+                       /* ETH TX */
+                       .rx = false,
+                       .descriptors = 2,
+                       .descriptor_elements = 1
+               },
+       }
+};
+
+static const __devinitconst struct resource timberdale_dma_resources[] = {
        {
                .start  = DMAOFFSET,
                .end    = DMAEND,
@@ -264,11 +345,25 @@ const static __devinitconst struct resource timberdale_dma_resources[] = {
 };
 
 static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = {
+       {
+               .name = "timb-dma",
+               .num_resources = ARRAY_SIZE(timberdale_dma_resources),
+               .resources = timberdale_dma_resources,
+               .platform_data = &timb_dma_platform_data,
+               .data_size = sizeof(timb_dma_platform_data),
+       },
        {
                .name = "timb-uart",
                .num_resources = ARRAY_SIZE(timberdale_uart_resources),
                .resources = timberdale_uart_resources,
        },
+       {
+               .name = "xiic-i2c",
+               .num_resources = ARRAY_SIZE(timberdale_xiic_resources),
+               .resources = timberdale_xiic_resources,
+               .platform_data = &timberdale_xiic_platform_data,
+               .data_size = sizeof(timberdale_xiic_platform_data),
+       },
        {
                .name = "timb-gpio",
                .num_resources = ARRAY_SIZE(timberdale_gpio_resources),
@@ -295,14 +390,16 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg0[] = {
                .num_resources = ARRAY_SIZE(timberdale_eth_resources),
                .resources = timberdale_eth_resources,
        },
+};
+
+static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = {
        {
                .name = "timb-dma",
                .num_resources = ARRAY_SIZE(timberdale_dma_resources),
                .resources = timberdale_dma_resources,
+               .platform_data = &timb_dma_platform_data,
+               .data_size = sizeof(timb_dma_platform_data),
        },
-};
-
-static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = {
        {
                .name = "timb-uart",
                .num_resources = ARRAY_SIZE(timberdale_uart_resources),
@@ -313,6 +410,13 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = {
                .num_resources = ARRAY_SIZE(timberdale_uartlite_resources),
                .resources = timberdale_uartlite_resources,
        },
+       {
+               .name = "xiic-i2c",
+               .num_resources = ARRAY_SIZE(timberdale_xiic_resources),
+               .resources = timberdale_xiic_resources,
+               .platform_data = &timberdale_xiic_platform_data,
+               .data_size = sizeof(timberdale_xiic_platform_data),
+       },
        {
                .name = "timb-gpio",
                .num_resources = ARRAY_SIZE(timberdale_gpio_resources),
@@ -344,19 +448,28 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg1[] = {
                .num_resources = ARRAY_SIZE(timberdale_eth_resources),
                .resources = timberdale_eth_resources,
        },
+};
+
+static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = {
        {
                .name = "timb-dma",
                .num_resources = ARRAY_SIZE(timberdale_dma_resources),
                .resources = timberdale_dma_resources,
+               .platform_data = &timb_dma_platform_data,
+               .data_size = sizeof(timb_dma_platform_data),
        },
-};
-
-static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = {
        {
                .name = "timb-uart",
                .num_resources = ARRAY_SIZE(timberdale_uart_resources),
                .resources = timberdale_uart_resources,
        },
+       {
+               .name = "xiic-i2c",
+               .num_resources = ARRAY_SIZE(timberdale_xiic_resources),
+               .resources = timberdale_xiic_resources,
+               .platform_data = &timberdale_xiic_platform_data,
+               .data_size = sizeof(timberdale_xiic_platform_data),
+       },
        {
                .name = "timb-gpio",
                .num_resources = ARRAY_SIZE(timberdale_gpio_resources),
@@ -378,14 +491,16 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg2[] = {
                .platform_data = &timberdale_xspi_platform_data,
                .data_size = sizeof(timberdale_xspi_platform_data),
        },
+};
+
+static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = {
        {
                .name = "timb-dma",
                .num_resources = ARRAY_SIZE(timberdale_dma_resources),
                .resources = timberdale_dma_resources,
+               .platform_data = &timb_dma_platform_data,
+               .data_size = sizeof(timb_dma_platform_data),
        },
-};
-
-static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = {
        {
                .name = "timb-uart",
                .num_resources = ARRAY_SIZE(timberdale_uart_resources),
@@ -424,11 +539,6 @@ static __devinitdata struct mfd_cell timberdale_cells_bar0_cfg3[] = {
                .num_resources = ARRAY_SIZE(timberdale_eth_resources),
                .resources = timberdale_eth_resources,
        },
-       {
-               .name = "timb-dma",
-               .num_resources = ARRAY_SIZE(timberdale_dma_resources),
-               .resources = timberdale_dma_resources,
-       },
 };
 
 static const __devinitconst struct resource timberdale_sdhc_resources[] = {
index 8d27ffabc25de748a32945a9b916c251ad6beadd..c11bf6ebfe000ca14e7249b25eaf2c24022bba6e 100644 (file)
@@ -23,7 +23,7 @@
 #ifndef MFD_TIMBERDALE_H
 #define MFD_TIMBERDALE_H
 
-#define DRV_VERSION            "0.1"
+#define DRV_VERSION            "0.2"
 
 /* This driver only support versions >= 3.8 and < 4.0  */
 #define TIMB_SUPPORTED_MAJOR   3
@@ -66,7 +66,7 @@
 
 #define CHIPCTLOFFSET  0x800
 #define CHIPCTLEND     0x8ff
-#define CHIPCTLSIZE    (CHIPCTLEND - CHIPCTLOFFSET)
+#define CHIPCTLSIZE    (CHIPCTLEND - CHIPCTLOFFSET + 1)
 
 #define INTCOFFSET     0xc00
 #define INTCEND                0xfff
 #define GPIO_PIN_BT_RST                15
 #define GPIO_NR_PINS           16
 
+/* DMA Channels */
+#define DMA_UART_RX         0
+#define DMA_UART_TX         1
+#define DMA_MLB_RX          2
+#define DMA_MLB_TX          3
+#define DMA_VIDEO_RX        4
+#define DMA_VIDEO_DROP      5
+#define DMA_SDHCI_RX        6
+#define DMA_SDHCI_TX        7
+#define DMA_ETH_RX          8
+#define DMA_ETH_TX          9
+
 #endif
index e5955306c2fa3397d97c5dba149adf1c67213159..9b22a77f70f5dac90aec0395d176d9ad06d4a71f 100644 (file)
@@ -530,8 +530,8 @@ static int __exit tps65010_remove(struct i2c_client *client)
        cancel_delayed_work(&tps->work);
        flush_scheduled_work();
        debugfs_remove(tps->file);
-       kfree(tps);
        i2c_set_clientdata(client, NULL);
+       kfree(tps);
        the_tps = NULL;
        return 0;
 }
diff --git a/drivers/mfd/tps6507x.c b/drivers/mfd/tps6507x.c
new file mode 100644 (file)
index 0000000..d859dff
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * tps6507x.c  --  TPS6507x chip family multi-function driver
+ *
+ *  Copyright (c) 2010 RidgeRun (todd.fischer@ridgerun.com)
+ *
+ * Author: Todd Fischer
+ *         todd.fischer@ridgerun.com
+ *
+ * Credits:
+ *
+ *    Using code from wm831x-*.c, wm8400-core, Wolfson Microelectronics PLC.
+ *
+ * For licencing details see kernel-base/COPYING
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/tps6507x.h>
+
+static struct mfd_cell tps6507x_devs[] = {
+       {
+               .name = "tps6507x-pmic",
+       },
+       {
+               .name = "tps6507x-ts",
+       },
+};
+
+
+static int tps6507x_i2c_read_device(struct tps6507x_dev *tps6507x, char reg,
+                                 int bytes, void *dest)
+{
+       struct i2c_client *i2c = tps6507x->i2c_client;
+       struct i2c_msg xfer[2];
+       int ret;
+
+       /* Write register */
+       xfer[0].addr = i2c->addr;
+       xfer[0].flags = 0;
+       xfer[0].len = 1;
+       xfer[0].buf = &reg;
+
+       /* Read data */
+       xfer[1].addr = i2c->addr;
+       xfer[1].flags = I2C_M_RD;
+       xfer[1].len = bytes;
+       xfer[1].buf = dest;
+
+       ret = i2c_transfer(i2c->adapter, xfer, 2);
+       if (ret == 2)
+               ret = 0;
+       else if (ret >= 0)
+               ret = -EIO;
+
+       return ret;
+}
+
+static int tps6507x_i2c_write_device(struct tps6507x_dev *tps6507x, char reg,
+                                  int bytes, void *src)
+{
+       struct i2c_client *i2c = tps6507x->i2c_client;
+       /* we add 1 byte for device register */
+       u8 msg[TPS6507X_MAX_REGISTER + 1];
+       int ret;
+
+       if (bytes > (TPS6507X_MAX_REGISTER + 1))
+               return -EINVAL;
+
+       msg[0] = reg;
+       memcpy(&msg[1], src, bytes);
+
+       ret = i2c_master_send(i2c, msg, bytes + 1);
+       if (ret < 0)
+               return ret;
+       if (ret != bytes + 1)
+               return -EIO;
+       return 0;
+}
+
+static int tps6507x_i2c_probe(struct i2c_client *i2c,
+                           const struct i2c_device_id *id)
+{
+       struct tps6507x_dev *tps6507x;
+       int ret = 0;
+
+       tps6507x = kzalloc(sizeof(struct tps6507x_dev), GFP_KERNEL);
+       if (tps6507x == NULL) {
+               kfree(i2c);
+               return -ENOMEM;
+       }
+
+       i2c_set_clientdata(i2c, tps6507x);
+       tps6507x->dev = &i2c->dev;
+       tps6507x->i2c_client = i2c;
+       tps6507x->read_dev = tps6507x_i2c_read_device;
+       tps6507x->write_dev = tps6507x_i2c_write_device;
+
+       ret = mfd_add_devices(tps6507x->dev, -1,
+                             tps6507x_devs, ARRAY_SIZE(tps6507x_devs),
+                             NULL, 0);
+
+       if (ret < 0)
+               goto err;
+
+       return ret;
+
+err:
+       mfd_remove_devices(tps6507x->dev);
+       kfree(tps6507x);
+       return ret;
+}
+
+static int tps6507x_i2c_remove(struct i2c_client *i2c)
+{
+       struct tps6507x_dev *tps6507x = i2c_get_clientdata(i2c);
+
+       mfd_remove_devices(tps6507x->dev);
+       kfree(tps6507x);
+
+       return 0;
+}
+
+static const struct i2c_device_id tps6507x_i2c_id[] = {
+       { "tps6507x", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, tps6507x_i2c_id);
+
+
+static struct i2c_driver tps6507x_i2c_driver = {
+       .driver = {
+                  .name = "tps6507x",
+                  .owner = THIS_MODULE,
+       },
+       .probe = tps6507x_i2c_probe,
+       .remove = tps6507x_i2c_remove,
+       .id_table = tps6507x_i2c_id,
+};
+
+static int __init tps6507x_i2c_init(void)
+{
+       return i2c_add_driver(&tps6507x_i2c_driver);
+}
+/* init early so consumer devices can complete system boot */
+subsys_initcall(tps6507x_i2c_init);
+
+static void __exit tps6507x_i2c_exit(void)
+{
+       i2c_del_driver(&tps6507x_i2c_driver);
+}
+module_exit(tps6507x_i2c_exit);
+
+MODULE_DESCRIPTION("TPS6507x chip family multi-function driver");
+MODULE_LICENSE("GPL");
index 202bdd59632d4e90355ea4bf8c733e1ee3719d18..097f24d8bceb9ec2ad0d1440430c302d7f0a4a35 100644 (file)
@@ -232,10 +232,11 @@ static const struct sih sih_modules_twl5031[8] = {
        },
        [6] = {
                /*
-                * ACI doesn't use the same SIH organization.
-                * For example, it supports only one interrupt line
+                * ECI/DBI doesn't use the same SIH organization.
+                * For example, it supports only one interrupt output line.
+                * That is, the interrupts are seen on both INT1 and INT2 lines.
                 */
-               .name           = "aci",
+               .name           = "eci_dbi",
                .module         = TWL5031_MODULE_ACCESSORY,
                .bits           = 9,
                .bytes_ixr      = 2,
@@ -247,8 +248,8 @@ static const struct sih sih_modules_twl5031[8] = {
 
        },
        [7] = {
-               /* Accessory */
-               .name           = "acc",
+               /* Audio accessory */
+               .name           = "audio",
                .module         = TWL5031_MODULE_ACCESSORY,
                .control_offset = TWL5031_ACCSIHCTRL,
                .bits           = 2,
index f2ab025ad97a3cad7532ad95b21684cf4561ff5e..1a968f34d67911d5029e97e1c67bbb15ebafb60a 100644 (file)
@@ -322,7 +322,11 @@ EXPORT_SYMBOL_GPL(wm831x_set_bits);
  */
 int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input)
 {
-       int ret, src;
+       int ret, src, irq_masked, timeout;
+
+       /* Are we using the interrupt? */
+       irq_masked = wm831x_reg_read(wm831x, WM831X_INTERRUPT_STATUS_1_MASK);
+       irq_masked &= WM831X_AUXADC_DATA_EINT;
 
        mutex_lock(&wm831x->auxadc_lock);
 
@@ -342,6 +346,9 @@ int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input)
                goto out;
        }
 
+       /* Clear any notification from a very late arriving interrupt */
+       try_wait_for_completion(&wm831x->auxadc_done);
+
        ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL,
                              WM831X_AUX_CVT_ENA, WM831X_AUX_CVT_ENA);
        if (ret < 0) {
@@ -349,22 +356,46 @@ int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input)
                goto disable;
        }
 
-       /* If an interrupt arrived late clean up after it */
-       try_wait_for_completion(&wm831x->auxadc_done);
-
-       /* Ignore the result to allow us to soldier on without IRQ hookup */
-       wait_for_completion_timeout(&wm831x->auxadc_done, msecs_to_jiffies(5));
-
-       ret = wm831x_reg_read(wm831x, WM831X_AUXADC_CONTROL);
-       if (ret < 0) {
-               dev_err(wm831x->dev, "AUXADC status read failed: %d\n", ret);
-               goto disable;
-       }
-
-       if (ret & WM831X_AUX_CVT_ENA) {
-               dev_err(wm831x->dev, "Timed out reading AUXADC\n");
-               ret = -EBUSY;
-               goto disable;
+       if (irq_masked) {
+               /* If we're not using interrupts then poll the
+                * interrupt status register */
+               timeout = 5;
+               while (timeout) {
+                       msleep(1);
+
+                       ret = wm831x_reg_read(wm831x,
+                                             WM831X_INTERRUPT_STATUS_1);
+                       if (ret < 0) {
+                               dev_err(wm831x->dev,
+                                       "ISR 1 read failed: %d\n", ret);
+                               goto disable;
+                       }
+
+                       /* Did it complete? */
+                       if (ret & WM831X_AUXADC_DATA_EINT) {
+                               wm831x_reg_write(wm831x,
+                                                WM831X_INTERRUPT_STATUS_1,
+                                                WM831X_AUXADC_DATA_EINT);
+                               break;
+                       } else {
+                               dev_err(wm831x->dev,
+                                       "AUXADC conversion timeout\n");
+                               ret = -EBUSY;
+                               goto disable;
+                       }
+               }
+       } else {
+               /* If we are using interrupts then wait for the
+                * interrupt to complete.  Use an extremely long
+                * timeout to handle situations with heavy load where
+                * the notification of the interrupt may be delayed by
+                * threaded IRQ handling. */
+               if (!wait_for_completion_timeout(&wm831x->auxadc_done,
+                                                msecs_to_jiffies(500))) {
+                       dev_err(wm831x->dev, "Timed out waiting for AUXADC\n");
+                       ret = -EBUSY;
+                       goto disable;
+               }
        }
 
        ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA);
@@ -1463,6 +1494,7 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq)
        case WM8310:
                parent = WM8310;
                wm831x->num_gpio = 16;
+               wm831x->charger_irq_wake = 1;
                if (rev > 0) {
                        wm831x->has_gpio_ena = 1;
                        wm831x->has_cs_sts = 1;
@@ -1474,6 +1506,7 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq)
        case WM8311:
                parent = WM8311;
                wm831x->num_gpio = 16;
+               wm831x->charger_irq_wake = 1;
                if (rev > 0) {
                        wm831x->has_gpio_ena = 1;
                        wm831x->has_cs_sts = 1;
@@ -1485,6 +1518,7 @@ static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq)
        case WM8312:
                parent = WM8312;
                wm831x->num_gpio = 16;
+               wm831x->charger_irq_wake = 1;
                if (rev > 0) {
                        wm831x->has_gpio_ena = 1;
                        wm831x->has_cs_sts = 1;
@@ -1623,6 +1657,42 @@ static void wm831x_device_exit(struct wm831x *wm831x)
        kfree(wm831x);
 }
 
+static int wm831x_device_suspend(struct wm831x *wm831x)
+{
+       int reg, mask;
+
+       /* If the charger IRQs are a wake source then make sure we ack
+        * them even if they're not actively being used (eg, no power
+        * driver or no IRQ line wired up) then acknowledge the
+        * interrupts otherwise suspend won't last very long.
+        */
+       if (wm831x->charger_irq_wake) {
+               reg = wm831x_reg_read(wm831x, WM831X_INTERRUPT_STATUS_2_MASK);
+
+               mask = WM831X_CHG_BATT_HOT_EINT |
+                       WM831X_CHG_BATT_COLD_EINT |
+                       WM831X_CHG_BATT_FAIL_EINT |
+                       WM831X_CHG_OV_EINT | WM831X_CHG_END_EINT |
+                       WM831X_CHG_TO_EINT | WM831X_CHG_MODE_EINT |
+                       WM831X_CHG_START_EINT;
+
+               /* If any of the interrupts are masked read the statuses */
+               if (reg & mask)
+                       reg = wm831x_reg_read(wm831x,
+                                             WM831X_INTERRUPT_STATUS_2);
+
+               if (reg & mask) {
+                       dev_info(wm831x->dev,
+                                "Acknowledging masked charger IRQs: %x\n",
+                                reg & mask);
+                       wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_2,
+                                        reg & mask);
+               }
+       }
+
+       return 0;
+}
+
 static int wm831x_i2c_read_device(struct wm831x *wm831x, unsigned short reg,
                                  int bytes, void *dest)
 {
@@ -1697,6 +1767,13 @@ static int wm831x_i2c_remove(struct i2c_client *i2c)
        return 0;
 }
 
+static int wm831x_i2c_suspend(struct i2c_client *i2c, pm_message_t mesg)
+{
+       struct wm831x *wm831x = i2c_get_clientdata(i2c);
+
+       return wm831x_device_suspend(wm831x);
+}
+
 static const struct i2c_device_id wm831x_i2c_id[] = {
        { "wm8310", WM8310 },
        { "wm8311", WM8311 },
@@ -1714,6 +1791,7 @@ static struct i2c_driver wm831x_i2c_driver = {
        },
        .probe = wm831x_i2c_probe,
        .remove = wm831x_i2c_remove,
+       .suspend = wm831x_i2c_suspend,
        .id_table = wm831x_i2c_id,
 };
 
index 4c1122ceb443b376040f4a232719437091f1915a..7dabe4dbd3732e1d75c396b9b1e01bdeafafa57c 100644 (file)
@@ -39,8 +39,6 @@ struct wm831x_irq_data {
        int primary;
        int reg;
        int mask;
-       irq_handler_t handler;
-       void *handler_data;
 };
 
 static struct wm831x_irq_data wm831x_irqs[] = {
@@ -492,6 +490,14 @@ int wm831x_irq_init(struct wm831x *wm831x, int irq)
 
        mutex_init(&wm831x->irq_lock);
 
+       /* Mask the individual interrupt sources */
+       for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks_cur); i++) {
+               wm831x->irq_masks_cur[i] = 0xffff;
+               wm831x->irq_masks_cache[i] = 0xffff;
+               wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i,
+                                0xffff);
+       }
+
        if (!irq) {
                dev_warn(wm831x->dev,
                         "No interrupt specified - functionality limited\n");
@@ -507,14 +513,6 @@ int wm831x_irq_init(struct wm831x *wm831x, int irq)
        wm831x->irq = irq;
        wm831x->irq_base = pdata->irq_base;
 
-       /* Mask the individual interrupt sources */
-       for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks_cur); i++) {
-               wm831x->irq_masks_cur[i] = 0xffff;
-               wm831x->irq_masks_cache[i] = 0xffff;
-               wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i,
-                                0xffff);
-       }
-
        /* Register them with genirq */
        for (cur_irq = wm831x->irq_base;
             cur_irq < ARRAY_SIZE(wm831x_irqs) + wm831x->irq_base;
index 65830f57c093574baace391c8fb8bb78d78c4473..7795af4b1fe15f7ee4690bd1a16eb17188ac492f 100644 (file)
@@ -64,10 +64,8 @@ static int wm8350_i2c_probe(struct i2c_client *i2c,
        int ret = 0;
 
        wm8350 = kzalloc(sizeof(struct wm8350), GFP_KERNEL);
-       if (wm8350 == NULL) {
-               kfree(i2c);
+       if (wm8350 == NULL)
                return -ENOMEM;
-       }
 
        i2c_set_clientdata(i2c, wm8350);
        wm8350->dev = &i2c->dev;
@@ -82,6 +80,7 @@ static int wm8350_i2c_probe(struct i2c_client *i2c,
        return ret;
 
 err:
+       i2c_set_clientdata(i2c, NULL);
        kfree(wm8350);
        return ret;
 }
@@ -91,6 +90,7 @@ static int wm8350_i2c_remove(struct i2c_client *i2c)
        struct wm8350 *wm8350 = i2c_get_clientdata(i2c);
 
        wm8350_device_exit(wm8350);
+       i2c_set_clientdata(i2c, NULL);
        kfree(wm8350);
 
        return 0;
index 865ce013a821fafffd7def52636d45c77900eb8a..e08aafa663dc4a1460bd901fd369fe9ec11a12b9 100644 (file)
@@ -118,7 +118,7 @@ static int wm8400_read(struct wm8400 *wm8400, u8 reg, int num_regs, u16 *dest)
 {
        int i, ret = 0;
 
-       BUG_ON(reg + num_regs - 1 > ARRAY_SIZE(wm8400->reg_cache));
+       BUG_ON(reg + num_regs > ARRAY_SIZE(wm8400->reg_cache));
 
        /* If there are any volatile reads then read back the entire block */
        for (i = reg; i < reg + num_regs; i++)
@@ -144,7 +144,7 @@ static int wm8400_write(struct wm8400 *wm8400, u8 reg, int num_regs,
 {
        int ret, i;
 
-       BUG_ON(reg + num_regs - 1 > ARRAY_SIZE(wm8400->reg_cache));
+       BUG_ON(reg + num_regs > ARRAY_SIZE(wm8400->reg_cache));
 
        for (i = 0; i < num_regs; i++) {
                BUG_ON(!reg_data[reg + i].writable);
index 05b751719bd5dd4634b7a1234291cc764fbd82ec..2c5227c02fa02c2ec00fee49ec550abf042d93f0 100644 (file)
@@ -63,6 +63,16 @@ config CAN_BFIN
          To compile this driver as a module, choose M here: the
          module will be called bfin_can.
 
+config CAN_JANZ_ICAN3
+       tristate "Janz VMOD-ICAN3 Intelligent CAN controller"
+       depends on CAN_DEV && MFD_JANZ_CMODIO
+       ---help---
+         Driver for Janz VMOD-ICAN3 Intelligent CAN controller module, which
+         connects to a MODULbus carrier board.
+
+         This driver can also be built as a module. If so, the module will be
+         called janz-ican3.ko.
+
 source "drivers/net/can/mscan/Kconfig"
 
 source "drivers/net/can/sja1000/Kconfig"
index 7a702f28d01c8e1990b67f8ab9f63e920a02b10c..9047cd066fea2d36b06d624765dc25b9e3bc6f98 100644 (file)
@@ -15,5 +15,6 @@ obj-$(CONFIG_CAN_AT91)                += at91_can.o
 obj-$(CONFIG_CAN_TI_HECC)      += ti_hecc.o
 obj-$(CONFIG_CAN_MCP251X)      += mcp251x.o
 obj-$(CONFIG_CAN_BFIN)         += bfin_can.o
+obj-$(CONFIG_CAN_JANZ_ICAN3)   += janz-ican3.o
 
 ccflags-$(CONFIG_CAN_DEBUG_DEVICES) := -DDEBUG
diff --git a/drivers/net/can/janz-ican3.c b/drivers/net/can/janz-ican3.c
new file mode 100644 (file)
index 0000000..6e533dc
--- /dev/null
@@ -0,0 +1,1830 @@
+/*
+ * Janz MODULbus VMOD-ICAN3 CAN Interface Driver
+ *
+ * Copyright (c) 2010 Ira W. Snyder <iws@ovro.caltech.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+
+#include <linux/netdevice.h>
+#include <linux/can.h>
+#include <linux/can/dev.h>
+#include <linux/can/error.h>
+
+#include <linux/mfd/janz.h>
+
+/* the DPM has 64k of memory, organized into 256x 256 byte pages */
+#define DPM_NUM_PAGES          256
+#define DPM_PAGE_SIZE          256
+#define DPM_PAGE_ADDR(p)       ((p) * DPM_PAGE_SIZE)
+
+/* JANZ ICAN3 "old-style" host interface queue page numbers */
+#define QUEUE_OLD_CONTROL      0
+#define QUEUE_OLD_RB0          1
+#define QUEUE_OLD_RB1          2
+#define QUEUE_OLD_WB0          3
+#define QUEUE_OLD_WB1          4
+
+/* Janz ICAN3 "old-style" host interface control registers */
+#define MSYNC_PEER             0x00            /* ICAN only */
+#define MSYNC_LOCL             0x01            /* host only */
+#define TARGET_RUNNING         0x02
+
+#define MSYNC_RB0              0x01
+#define MSYNC_RB1              0x02
+#define MSYNC_RBLW             0x04
+#define MSYNC_RB_MASK          (MSYNC_RB0 | MSYNC_RB1)
+
+#define MSYNC_WB0              0x10
+#define MSYNC_WB1              0x20
+#define MSYNC_WBLW             0x40
+#define MSYNC_WB_MASK          (MSYNC_WB0 | MSYNC_WB1)
+
+/* Janz ICAN3 "new-style" host interface queue page numbers */
+#define QUEUE_TOHOST           5
+#define QUEUE_FROMHOST_MID     6
+#define QUEUE_FROMHOST_HIGH    7
+#define QUEUE_FROMHOST_LOW     8
+
+/* The first free page in the DPM is #9 */
+#define DPM_FREE_START         9
+
+/* Janz ICAN3 "new-style" and "fast" host interface descriptor flags */
+#define DESC_VALID             0x80
+#define DESC_WRAP              0x40
+#define DESC_INTERRUPT         0x20
+#define DESC_IVALID            0x10
+#define DESC_LEN(len)          (len)
+
+/* Janz ICAN3 Firmware Messages */
+#define MSG_CONNECTI           0x02
+#define MSG_DISCONNECT         0x03
+#define MSG_IDVERS             0x04
+#define MSG_MSGLOST            0x05
+#define MSG_NEWHOSTIF          0x08
+#define MSG_INQUIRY            0x0a
+#define MSG_SETAFILMASK                0x10
+#define MSG_INITFDPMQUEUE      0x11
+#define MSG_HWCONF             0x12
+#define MSG_FMSGLOST           0x15
+#define MSG_CEVTIND            0x37
+#define MSG_CBTRREQ            0x41
+#define MSG_COFFREQ            0x42
+#define MSG_CONREQ             0x43
+#define MSG_CCONFREQ           0x47
+
+/*
+ * Janz ICAN3 CAN Inquiry Message Types
+ *
+ * NOTE: there appears to be a firmware bug here. You must send
+ * NOTE: INQUIRY_STATUS and expect to receive an INQUIRY_EXTENDED
+ * NOTE: response. The controller never responds to a message with
+ * NOTE: the INQUIRY_EXTENDED subspec :(
+ */
+#define INQUIRY_STATUS         0x00
+#define INQUIRY_TERMINATION    0x01
+#define INQUIRY_EXTENDED       0x04
+
+/* Janz ICAN3 CAN Set Acceptance Filter Mask Message Types */
+#define SETAFILMASK_REJECT     0x00
+#define SETAFILMASK_FASTIF     0x02
+
+/* Janz ICAN3 CAN Hardware Configuration Message Types */
+#define HWCONF_TERMINATE_ON    0x01
+#define HWCONF_TERMINATE_OFF   0x00
+
+/* Janz ICAN3 CAN Event Indication Message Types */
+#define CEVTIND_EI             0x01
+#define CEVTIND_DOI            0x02
+#define CEVTIND_LOST           0x04
+#define CEVTIND_FULL           0x08
+#define CEVTIND_BEI            0x10
+
+#define CEVTIND_CHIP_SJA1000   0x02
+
+#define ICAN3_BUSERR_QUOTA_MAX 255
+
+/* Janz ICAN3 CAN Frame Conversion */
+#define ICAN3_ECHO     0x10
+#define ICAN3_EFF_RTR  0x40
+#define ICAN3_SFF_RTR  0x10
+#define ICAN3_EFF      0x80
+
+#define ICAN3_CAN_TYPE_MASK    0x0f
+#define ICAN3_CAN_TYPE_SFF     0x00
+#define ICAN3_CAN_TYPE_EFF     0x01
+
+#define ICAN3_CAN_DLC_MASK     0x0f
+
+/*
+ * SJA1000 Status and Error Register Definitions
+ *
+ * Copied from drivers/net/can/sja1000/sja1000.h
+ */
+
+/* status register content */
+#define SR_BS          0x80
+#define SR_ES          0x40
+#define SR_TS          0x20
+#define SR_RS          0x10
+#define SR_TCS         0x08
+#define SR_TBS         0x04
+#define SR_DOS         0x02
+#define SR_RBS         0x01
+
+#define SR_CRIT (SR_BS|SR_ES)
+
+/* ECC register */
+#define ECC_SEG                0x1F
+#define ECC_DIR                0x20
+#define ECC_ERR                6
+#define ECC_BIT                0x00
+#define ECC_FORM       0x40
+#define ECC_STUFF      0x80
+#define ECC_MASK       0xc0
+
+/* Number of buffers for use in the "new-style" host interface */
+#define ICAN3_NEW_BUFFERS      16
+
+/* Number of buffers for use in the "fast" host interface */
+#define ICAN3_TX_BUFFERS       512
+#define ICAN3_RX_BUFFERS       1024
+
+/* SJA1000 Clock Input */
+#define ICAN3_CAN_CLOCK                8000000
+
+/* Driver Name */
+#define DRV_NAME "janz-ican3"
+
+/* DPM Control Registers -- starts at offset 0x100 in the MODULbus registers */
+struct ican3_dpm_control {
+       /* window address register */
+       u8 window_address;
+       u8 unused1;
+
+       /*
+        * Read access: clear interrupt from microcontroller
+        * Write access: send interrupt to microcontroller
+        */
+       u8 interrupt;
+       u8 unused2;
+
+       /* write-only: reset all hardware on the module */
+       u8 hwreset;
+       u8 unused3;
+
+       /* write-only: generate an interrupt to the TPU */
+       u8 tpuinterrupt;
+};
+
+struct ican3_dev {
+
+       /* must be the first member */
+       struct can_priv can;
+
+       /* CAN network device */
+       struct net_device *ndev;
+       struct napi_struct napi;
+
+       /* Device for printing */
+       struct device *dev;
+
+       /* module number */
+       unsigned int num;
+
+       /* base address of registers and IRQ */
+       struct janz_cmodio_onboard_regs __iomem *ctrl;
+       struct ican3_dpm_control __iomem *dpmctrl;
+       void __iomem *dpm;
+       int irq;
+
+       /* CAN bus termination status */
+       struct completion termination_comp;
+       bool termination_enabled;
+
+       /* CAN bus error status registers */
+       struct completion buserror_comp;
+       struct can_berr_counter bec;
+
+       /* old and new style host interface */
+       unsigned int iftype;
+
+       /*
+        * Any function which changes the current DPM page must hold this
+        * lock while it is performing data accesses. This ensures that the
+        * function will not be preempted and end up reading data from a
+        * different DPM page than it expects.
+        */
+       spinlock_t lock;
+
+       /* new host interface */
+       unsigned int rx_int;
+       unsigned int rx_num;
+       unsigned int tx_num;
+
+       /* fast host interface */
+       unsigned int fastrx_start;
+       unsigned int fastrx_int;
+       unsigned int fastrx_num;
+       unsigned int fasttx_start;
+       unsigned int fasttx_num;
+
+       /* first free DPM page */
+       unsigned int free_page;
+};
+
+struct ican3_msg {
+       u8 control;
+       u8 spec;
+       __le16 len;
+       u8 data[252];
+};
+
+struct ican3_new_desc {
+       u8 control;
+       u8 pointer;
+};
+
+struct ican3_fast_desc {
+       u8 control;
+       u8 command;
+       u8 data[14];
+};
+
+/* write to the window basic address register */
+static inline void ican3_set_page(struct ican3_dev *mod, unsigned int page)
+{
+       BUG_ON(page >= DPM_NUM_PAGES);
+       iowrite8(page, &mod->dpmctrl->window_address);
+}
+
+/*
+ * ICAN3 "old-style" host interface
+ */
+
+/*
+ * Recieve a message from the ICAN3 "old-style" firmware interface
+ *
+ * LOCKING: must hold mod->lock
+ *
+ * returns 0 on success, -ENOMEM when no message exists
+ */
+static int ican3_old_recv_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       unsigned int mbox, mbox_page;
+       u8 locl, peer, xord;
+
+       /* get the MSYNC registers */
+       ican3_set_page(mod, QUEUE_OLD_CONTROL);
+       peer = ioread8(mod->dpm + MSYNC_PEER);
+       locl = ioread8(mod->dpm + MSYNC_LOCL);
+       xord = locl ^ peer;
+
+       if ((xord & MSYNC_RB_MASK) == 0x00) {
+               dev_dbg(mod->dev, "no mbox for reading\n");
+               return -ENOMEM;
+       }
+
+       /* find the first free mbox to read */
+       if ((xord & MSYNC_RB_MASK) == MSYNC_RB_MASK)
+               mbox = (xord & MSYNC_RBLW) ? MSYNC_RB0 : MSYNC_RB1;
+       else
+               mbox = (xord & MSYNC_RB0) ? MSYNC_RB0 : MSYNC_RB1;
+
+       /* copy the message */
+       mbox_page = (mbox == MSYNC_RB0) ? QUEUE_OLD_RB0 : QUEUE_OLD_RB1;
+       ican3_set_page(mod, mbox_page);
+       memcpy_fromio(msg, mod->dpm, sizeof(*msg));
+
+       /*
+        * notify the firmware that the read buffer is available
+        * for it to fill again
+        */
+       locl ^= mbox;
+
+       ican3_set_page(mod, QUEUE_OLD_CONTROL);
+       iowrite8(locl, mod->dpm + MSYNC_LOCL);
+       return 0;
+}
+
+/*
+ * Send a message through the "old-style" firmware interface
+ *
+ * LOCKING: must hold mod->lock
+ *
+ * returns 0 on success, -ENOMEM when no free space exists
+ */
+static int ican3_old_send_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       unsigned int mbox, mbox_page;
+       u8 locl, peer, xord;
+
+       /* get the MSYNC registers */
+       ican3_set_page(mod, QUEUE_OLD_CONTROL);
+       peer = ioread8(mod->dpm + MSYNC_PEER);
+       locl = ioread8(mod->dpm + MSYNC_LOCL);
+       xord = locl ^ peer;
+
+       if ((xord & MSYNC_WB_MASK) == MSYNC_WB_MASK) {
+               dev_err(mod->dev, "no mbox for writing\n");
+               return -ENOMEM;
+       }
+
+       /* calculate a free mbox to use */
+       mbox = (xord & MSYNC_WB0) ? MSYNC_WB1 : MSYNC_WB0;
+
+       /* copy the message to the DPM */
+       mbox_page = (mbox == MSYNC_WB0) ? QUEUE_OLD_WB0 : QUEUE_OLD_WB1;
+       ican3_set_page(mod, mbox_page);
+       memcpy_toio(mod->dpm, msg, sizeof(*msg));
+
+       locl ^= mbox;
+       if (mbox == MSYNC_WB1)
+               locl |= MSYNC_WBLW;
+
+       ican3_set_page(mod, QUEUE_OLD_CONTROL);
+       iowrite8(locl, mod->dpm + MSYNC_LOCL);
+       return 0;
+}
+
+/*
+ * ICAN3 "new-style" Host Interface Setup
+ */
+
+static void __devinit ican3_init_new_host_interface(struct ican3_dev *mod)
+{
+       struct ican3_new_desc desc;
+       unsigned long flags;
+       void __iomem *dst;
+       int i;
+
+       spin_lock_irqsave(&mod->lock, flags);
+
+       /* setup the internal datastructures for RX */
+       mod->rx_num = 0;
+       mod->rx_int = 0;
+
+       /* tohost queue descriptors are in page 5 */
+       ican3_set_page(mod, QUEUE_TOHOST);
+       dst = mod->dpm;
+
+       /* initialize the tohost (rx) queue descriptors: pages 9-24 */
+       for (i = 0; i < ICAN3_NEW_BUFFERS; i++) {
+               desc.control = DESC_INTERRUPT | DESC_LEN(1); /* I L=1 */
+               desc.pointer = mod->free_page;
+
+               /* set wrap flag on last buffer */
+               if (i == ICAN3_NEW_BUFFERS - 1)
+                       desc.control |= DESC_WRAP;
+
+               memcpy_toio(dst, &desc, sizeof(desc));
+               dst += sizeof(desc);
+               mod->free_page++;
+       }
+
+       /* fromhost (tx) mid queue descriptors are in page 6 */
+       ican3_set_page(mod, QUEUE_FROMHOST_MID);
+       dst = mod->dpm;
+
+       /* setup the internal datastructures for TX */
+       mod->tx_num = 0;
+
+       /* initialize the fromhost mid queue descriptors: pages 25-40 */
+       for (i = 0; i < ICAN3_NEW_BUFFERS; i++) {
+               desc.control = DESC_VALID | DESC_LEN(1); /* V L=1 */
+               desc.pointer = mod->free_page;
+
+               /* set wrap flag on last buffer */
+               if (i == ICAN3_NEW_BUFFERS - 1)
+                       desc.control |= DESC_WRAP;
+
+               memcpy_toio(dst, &desc, sizeof(desc));
+               dst += sizeof(desc);
+               mod->free_page++;
+       }
+
+       /* fromhost hi queue descriptors are in page 7 */
+       ican3_set_page(mod, QUEUE_FROMHOST_HIGH);
+       dst = mod->dpm;
+
+       /* initialize only a single buffer in the fromhost hi queue (unused) */
+       desc.control = DESC_VALID | DESC_WRAP | DESC_LEN(1); /* VW L=1 */
+       desc.pointer = mod->free_page;
+       memcpy_toio(dst, &desc, sizeof(desc));
+       mod->free_page++;
+
+       /* fromhost low queue descriptors are in page 8 */
+       ican3_set_page(mod, QUEUE_FROMHOST_LOW);
+       dst = mod->dpm;
+
+       /* initialize only a single buffer in the fromhost low queue (unused) */
+       desc.control = DESC_VALID | DESC_WRAP | DESC_LEN(1); /* VW L=1 */
+       desc.pointer = mod->free_page;
+       memcpy_toio(dst, &desc, sizeof(desc));
+       mod->free_page++;
+
+       spin_unlock_irqrestore(&mod->lock, flags);
+}
+
+/*
+ * ICAN3 Fast Host Interface Setup
+ */
+
+static void __devinit ican3_init_fast_host_interface(struct ican3_dev *mod)
+{
+       struct ican3_fast_desc desc;
+       unsigned long flags;
+       unsigned int addr;
+       void __iomem *dst;
+       int i;
+
+       spin_lock_irqsave(&mod->lock, flags);
+
+       /* save the start recv page */
+       mod->fastrx_start = mod->free_page;
+       mod->fastrx_num = 0;
+       mod->fastrx_int = 0;
+
+       /* build a single fast tohost queue descriptor */
+       memset(&desc, 0, sizeof(desc));
+       desc.control = 0x00;
+       desc.command = 1;
+
+       /* build the tohost queue descriptor ring in memory */
+       addr = 0;
+       for (i = 0; i < ICAN3_RX_BUFFERS; i++) {
+
+               /* set the wrap bit on the last buffer */
+               if (i == ICAN3_RX_BUFFERS - 1)
+                       desc.control |= DESC_WRAP;
+
+               /* switch to the correct page */
+               ican3_set_page(mod, mod->free_page);
+
+               /* copy the descriptor to the DPM */
+               dst = mod->dpm + addr;
+               memcpy_toio(dst, &desc, sizeof(desc));
+               addr += sizeof(desc);
+
+               /* move to the next page if necessary */
+               if (addr >= DPM_PAGE_SIZE) {
+                       addr = 0;
+                       mod->free_page++;
+               }
+       }
+
+       /* make sure we page-align the next queue */
+       if (addr != 0)
+               mod->free_page++;
+
+       /* save the start xmit page */
+       mod->fasttx_start = mod->free_page;
+       mod->fasttx_num = 0;
+
+       /* build a single fast fromhost queue descriptor */
+       memset(&desc, 0, sizeof(desc));
+       desc.control = DESC_VALID;
+       desc.command = 1;
+
+       /* build the fromhost queue descriptor ring in memory */
+       addr = 0;
+       for (i = 0; i < ICAN3_TX_BUFFERS; i++) {
+
+               /* set the wrap bit on the last buffer */
+               if (i == ICAN3_TX_BUFFERS - 1)
+                       desc.control |= DESC_WRAP;
+
+               /* switch to the correct page */
+               ican3_set_page(mod, mod->free_page);
+
+               /* copy the descriptor to the DPM */
+               dst = mod->dpm + addr;
+               memcpy_toio(dst, &desc, sizeof(desc));
+               addr += sizeof(desc);
+
+               /* move to the next page if necessary */
+               if (addr >= DPM_PAGE_SIZE) {
+                       addr = 0;
+                       mod->free_page++;
+               }
+       }
+
+       spin_unlock_irqrestore(&mod->lock, flags);
+}
+
+/*
+ * ICAN3 "new-style" Host Interface Message Helpers
+ */
+
+/*
+ * LOCKING: must hold mod->lock
+ */
+static int ican3_new_send_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       struct ican3_new_desc desc;
+       void __iomem *desc_addr = mod->dpm + (mod->tx_num * sizeof(desc));
+
+       /* switch to the fromhost mid queue, and read the buffer descriptor */
+       ican3_set_page(mod, QUEUE_FROMHOST_MID);
+       memcpy_fromio(&desc, desc_addr, sizeof(desc));
+
+       if (!(desc.control & DESC_VALID)) {
+               dev_dbg(mod->dev, "%s: no free buffers\n", __func__);
+               return -ENOMEM;
+       }
+
+       /* switch to the data page, copy the data */
+       ican3_set_page(mod, desc.pointer);
+       memcpy_toio(mod->dpm, msg, sizeof(*msg));
+
+       /* switch back to the descriptor, set the valid bit, write it back */
+       ican3_set_page(mod, QUEUE_FROMHOST_MID);
+       desc.control ^= DESC_VALID;
+       memcpy_toio(desc_addr, &desc, sizeof(desc));
+
+       /* update the tx number */
+       mod->tx_num = (desc.control & DESC_WRAP) ? 0 : (mod->tx_num + 1);
+       return 0;
+}
+
+/*
+ * LOCKING: must hold mod->lock
+ */
+static int ican3_new_recv_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       struct ican3_new_desc desc;
+       void __iomem *desc_addr = mod->dpm + (mod->rx_num * sizeof(desc));
+
+       /* switch to the tohost queue, and read the buffer descriptor */
+       ican3_set_page(mod, QUEUE_TOHOST);
+       memcpy_fromio(&desc, desc_addr, sizeof(desc));
+
+       if (!(desc.control & DESC_VALID)) {
+               dev_dbg(mod->dev, "%s: no buffers to recv\n", __func__);
+               return -ENOMEM;
+       }
+
+       /* switch to the data page, copy the data */
+       ican3_set_page(mod, desc.pointer);
+       memcpy_fromio(msg, mod->dpm, sizeof(*msg));
+
+       /* switch back to the descriptor, toggle the valid bit, write it back */
+       ican3_set_page(mod, QUEUE_TOHOST);
+       desc.control ^= DESC_VALID;
+       memcpy_toio(desc_addr, &desc, sizeof(desc));
+
+       /* update the rx number */
+       mod->rx_num = (desc.control & DESC_WRAP) ? 0 : (mod->rx_num + 1);
+       return 0;
+}
+
+/*
+ * Message Send / Recv Helpers
+ */
+
+static int ican3_send_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&mod->lock, flags);
+
+       if (mod->iftype == 0)
+               ret = ican3_old_send_msg(mod, msg);
+       else
+               ret = ican3_new_send_msg(mod, msg);
+
+       spin_unlock_irqrestore(&mod->lock, flags);
+       return ret;
+}
+
+static int ican3_recv_msg(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       unsigned long flags;
+       int ret;
+
+       spin_lock_irqsave(&mod->lock, flags);
+
+       if (mod->iftype == 0)
+               ret = ican3_old_recv_msg(mod, msg);
+       else
+               ret = ican3_new_recv_msg(mod, msg);
+
+       spin_unlock_irqrestore(&mod->lock, flags);
+       return ret;
+}
+
+/*
+ * Quick Pre-constructed Messages
+ */
+
+static int __devinit ican3_msg_connect(struct ican3_dev *mod)
+{
+       struct ican3_msg msg;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.spec = MSG_CONNECTI;
+       msg.len = cpu_to_le16(0);
+
+       return ican3_send_msg(mod, &msg);
+}
+
+static int __devexit ican3_msg_disconnect(struct ican3_dev *mod)
+{
+       struct ican3_msg msg;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.spec = MSG_DISCONNECT;
+       msg.len = cpu_to_le16(0);
+
+       return ican3_send_msg(mod, &msg);
+}
+
+static int __devinit ican3_msg_newhostif(struct ican3_dev *mod)
+{
+       struct ican3_msg msg;
+       int ret;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.spec = MSG_NEWHOSTIF;
+       msg.len = cpu_to_le16(0);
+
+       /* If we're not using the old interface, switching seems bogus */
+       WARN_ON(mod->iftype != 0);
+
+       ret = ican3_send_msg(mod, &msg);
+       if (ret)
+               return ret;
+
+       /* mark the module as using the new host interface */
+       mod->iftype = 1;
+       return 0;
+}
+
+static int __devinit ican3_msg_fasthostif(struct ican3_dev *mod)
+{
+       struct ican3_msg msg;
+       unsigned int addr;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.spec = MSG_INITFDPMQUEUE;
+       msg.len = cpu_to_le16(8);
+
+       /* write the tohost queue start address */
+       addr = DPM_PAGE_ADDR(mod->fastrx_start);
+       msg.data[0] = addr & 0xff;
+       msg.data[1] = (addr >> 8) & 0xff;
+       msg.data[2] = (addr >> 16) & 0xff;
+       msg.data[3] = (addr >> 24) & 0xff;
+
+       /* write the fromhost queue start address */
+       addr = DPM_PAGE_ADDR(mod->fasttx_start);
+       msg.data[4] = addr & 0xff;
+       msg.data[5] = (addr >> 8) & 0xff;
+       msg.data[6] = (addr >> 16) & 0xff;
+       msg.data[7] = (addr >> 24) & 0xff;
+
+       /* If we're not using the new interface yet, we cannot do this */
+       WARN_ON(mod->iftype != 1);
+
+       return ican3_send_msg(mod, &msg);
+}
+
+/*
+ * Setup the CAN filter to either accept or reject all
+ * messages from the CAN bus.
+ */
+static int __devinit ican3_set_id_filter(struct ican3_dev *mod, bool accept)
+{
+       struct ican3_msg msg;
+       int ret;
+
+       /* Standard Frame Format */
+       memset(&msg, 0, sizeof(msg));
+       msg.spec = MSG_SETAFILMASK;
+       msg.len = cpu_to_le16(5);
+       msg.data[0] = 0x00; /* IDLo LSB */
+       msg.data[1] = 0x00; /* IDLo MSB */
+       msg.data[2] = 0xff; /* IDHi LSB */
+       msg.data[3] = 0x07; /* IDHi MSB */
+
+       /* accept all frames for fast host if, or reject all frames */
+       msg.data[4] = accept ? SETAFILMASK_FASTIF : SETAFILMASK_REJECT;
+
+       ret = ican3_send_msg(mod, &msg);
+       if (ret)
+               return ret;
+
+       /* Extended Frame Format */
+       memset(&msg, 0, sizeof(msg));
+       msg.spec = MSG_SETAFILMASK;
+       msg.len = cpu_to_le16(13);
+       msg.data[0] = 0;    /* MUX = 0 */
+       msg.data[1] = 0x00; /* IDLo LSB */
+       msg.data[2] = 0x00;
+       msg.data[3] = 0x00;
+       msg.data[4] = 0x20; /* IDLo MSB */
+       msg.data[5] = 0xff; /* IDHi LSB */
+       msg.data[6] = 0xff;
+       msg.data[7] = 0xff;
+       msg.data[8] = 0x3f; /* IDHi MSB */
+
+       /* accept all frames for fast host if, or reject all frames */
+       msg.data[9] = accept ? SETAFILMASK_FASTIF : SETAFILMASK_REJECT;
+
+       return ican3_send_msg(mod, &msg);
+}
+
+/*
+ * Bring the CAN bus online or offline
+ */
+static int ican3_set_bus_state(struct ican3_dev *mod, bool on)
+{
+       struct ican3_msg msg;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.spec = on ? MSG_CONREQ : MSG_COFFREQ;
+       msg.len = cpu_to_le16(0);
+
+       return ican3_send_msg(mod, &msg);
+}
+
+static int ican3_set_termination(struct ican3_dev *mod, bool on)
+{
+       struct ican3_msg msg;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.spec = MSG_HWCONF;
+       msg.len = cpu_to_le16(2);
+       msg.data[0] = 0x00;
+       msg.data[1] = on ? HWCONF_TERMINATE_ON : HWCONF_TERMINATE_OFF;
+
+       return ican3_send_msg(mod, &msg);
+}
+
+static int ican3_send_inquiry(struct ican3_dev *mod, u8 subspec)
+{
+       struct ican3_msg msg;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.spec = MSG_INQUIRY;
+       msg.len = cpu_to_le16(2);
+       msg.data[0] = subspec;
+       msg.data[1] = 0x00;
+
+       return ican3_send_msg(mod, &msg);
+}
+
+static int ican3_set_buserror(struct ican3_dev *mod, u8 quota)
+{
+       struct ican3_msg msg;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.spec = MSG_CCONFREQ;
+       msg.len = cpu_to_le16(2);
+       msg.data[0] = 0x00;
+       msg.data[1] = quota;
+
+       return ican3_send_msg(mod, &msg);
+}
+
+/*
+ * ICAN3 to Linux CAN Frame Conversion
+ */
+
+static void ican3_to_can_frame(struct ican3_dev *mod,
+                              struct ican3_fast_desc *desc,
+                              struct can_frame *cf)
+{
+       if ((desc->command & ICAN3_CAN_TYPE_MASK) == ICAN3_CAN_TYPE_SFF) {
+               if (desc->data[1] & ICAN3_SFF_RTR)
+                       cf->can_id |= CAN_RTR_FLAG;
+
+               cf->can_id |= desc->data[0] << 3;
+               cf->can_id |= (desc->data[1] & 0xe0) >> 5;
+               cf->can_dlc = desc->data[1] & ICAN3_CAN_DLC_MASK;
+               memcpy(cf->data, &desc->data[2], sizeof(cf->data));
+       } else {
+               cf->can_dlc = desc->data[0] & ICAN3_CAN_DLC_MASK;
+               if (desc->data[0] & ICAN3_EFF_RTR)
+                       cf->can_id |= CAN_RTR_FLAG;
+
+               if (desc->data[0] & ICAN3_EFF) {
+                       cf->can_id |= CAN_EFF_FLAG;
+                       cf->can_id |= desc->data[2] << 21; /* 28-21 */
+                       cf->can_id |= desc->data[3] << 13; /* 20-13 */
+                       cf->can_id |= desc->data[4] << 5;  /* 12-5  */
+                       cf->can_id |= (desc->data[5] & 0xf8) >> 3;
+               } else {
+                       cf->can_id |= desc->data[2] << 3;  /* 10-3  */
+                       cf->can_id |= desc->data[3] >> 5;  /* 2-0   */
+               }
+
+               memcpy(cf->data, &desc->data[6], sizeof(cf->data));
+       }
+}
+
+static void can_frame_to_ican3(struct ican3_dev *mod,
+                              struct can_frame *cf,
+                              struct ican3_fast_desc *desc)
+{
+       /* clear out any stale data in the descriptor */
+       memset(desc->data, 0, sizeof(desc->data));
+
+       /* we always use the extended format, with the ECHO flag set */
+       desc->command = ICAN3_CAN_TYPE_EFF;
+       desc->data[0] |= cf->can_dlc;
+       desc->data[1] |= ICAN3_ECHO;
+
+       if (cf->can_id & CAN_RTR_FLAG)
+               desc->data[0] |= ICAN3_EFF_RTR;
+
+       /* pack the id into the correct places */
+       if (cf->can_id & CAN_EFF_FLAG) {
+               desc->data[0] |= ICAN3_EFF;
+               desc->data[2] = (cf->can_id & 0x1fe00000) >> 21; /* 28-21 */
+               desc->data[3] = (cf->can_id & 0x001fe000) >> 13; /* 20-13 */
+               desc->data[4] = (cf->can_id & 0x00001fe0) >> 5;  /* 12-5  */
+               desc->data[5] = (cf->can_id & 0x0000001f) << 3;  /* 4-0   */
+       } else {
+               desc->data[2] = (cf->can_id & 0x7F8) >> 3; /* bits 10-3 */
+               desc->data[3] = (cf->can_id & 0x007) << 5; /* bits 2-0  */
+       }
+
+       /* copy the data bits into the descriptor */
+       memcpy(&desc->data[6], cf->data, sizeof(cf->data));
+}
+
+/*
+ * Interrupt Handling
+ */
+
+/*
+ * Handle an ID + Version message response from the firmware. We never generate
+ * this message in production code, but it is very useful when debugging to be
+ * able to display this message.
+ */
+static void ican3_handle_idvers(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       dev_dbg(mod->dev, "IDVERS response: %s\n", msg->data);
+}
+
+static void ican3_handle_msglost(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       struct net_device *dev = mod->ndev;
+       struct net_device_stats *stats = &dev->stats;
+       struct can_frame *cf;
+       struct sk_buff *skb;
+
+       /*
+        * Report that communication messages with the microcontroller firmware
+        * are being lost. These are never CAN frames, so we do not generate an
+        * error frame for userspace
+        */
+       if (msg->spec == MSG_MSGLOST) {
+               dev_err(mod->dev, "lost %d control messages\n", msg->data[0]);
+               return;
+       }
+
+       /*
+        * Oops, this indicates that we have lost messages in the fast queue,
+        * which are exclusively CAN messages. Our driver isn't reading CAN
+        * frames fast enough.
+        *
+        * We'll pretend that the SJA1000 told us that it ran out of buffer
+        * space, because there is not a better message for this.
+        */
+       skb = alloc_can_err_skb(dev, &cf);
+       if (skb) {
+               cf->can_id |= CAN_ERR_CRTL;
+               cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+               stats->rx_errors++;
+               stats->rx_bytes += cf->can_dlc;
+               netif_rx(skb);
+       }
+}
+
+/*
+ * Handle CAN Event Indication Messages from the firmware
+ *
+ * The ICAN3 firmware provides the values of some SJA1000 registers when it
+ * generates this message. The code below is largely copied from the
+ * drivers/net/can/sja1000/sja1000.c file, and adapted as necessary
+ */
+static int ican3_handle_cevtind(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       struct net_device *dev = mod->ndev;
+       struct net_device_stats *stats = &dev->stats;
+       enum can_state state = mod->can.state;
+       u8 status, isrc, rxerr, txerr;
+       struct can_frame *cf;
+       struct sk_buff *skb;
+
+       /* we can only handle the SJA1000 part */
+       if (msg->data[1] != CEVTIND_CHIP_SJA1000) {
+               dev_err(mod->dev, "unable to handle errors on non-SJA1000\n");
+               return -ENODEV;
+       }
+
+       /* check the message length for sanity */
+       if (le16_to_cpu(msg->len) < 6) {
+               dev_err(mod->dev, "error message too short\n");
+               return -EINVAL;
+       }
+
+       skb = alloc_can_err_skb(dev, &cf);
+       if (skb == NULL)
+               return -ENOMEM;
+
+       isrc = msg->data[0];
+       status = msg->data[3];
+       rxerr = msg->data[4];
+       txerr = msg->data[5];
+
+       /* data overrun interrupt */
+       if (isrc == CEVTIND_DOI || isrc == CEVTIND_LOST) {
+               dev_dbg(mod->dev, "data overrun interrupt\n");
+               cf->can_id |= CAN_ERR_CRTL;
+               cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+               stats->rx_over_errors++;
+               stats->rx_errors++;
+       }
+
+       /* error warning + passive interrupt */
+       if (isrc == CEVTIND_EI) {
+               dev_dbg(mod->dev, "error warning + passive interrupt\n");
+               if (status & SR_BS) {
+                       state = CAN_STATE_BUS_OFF;
+                       cf->can_id |= CAN_ERR_BUSOFF;
+                       can_bus_off(dev);
+               } else if (status & SR_ES) {
+                       if (rxerr >= 128 || txerr >= 128)
+                               state = CAN_STATE_ERROR_PASSIVE;
+                       else
+                               state = CAN_STATE_ERROR_WARNING;
+               } else {
+                       state = CAN_STATE_ERROR_ACTIVE;
+               }
+       }
+
+       /* bus error interrupt */
+       if (isrc == CEVTIND_BEI) {
+               u8 ecc = msg->data[2];
+
+               dev_dbg(mod->dev, "bus error interrupt\n");
+               mod->can.can_stats.bus_error++;
+               stats->rx_errors++;
+               cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+               switch (ecc & ECC_MASK) {
+               case ECC_BIT:
+                       cf->data[2] |= CAN_ERR_PROT_BIT;
+                       break;
+               case ECC_FORM:
+                       cf->data[2] |= CAN_ERR_PROT_FORM;
+                       break;
+               case ECC_STUFF:
+                       cf->data[2] |= CAN_ERR_PROT_STUFF;
+                       break;
+               default:
+                       cf->data[2] |= CAN_ERR_PROT_UNSPEC;
+                       cf->data[3] = ecc & ECC_SEG;
+                       break;
+               }
+
+               if ((ecc & ECC_DIR) == 0)
+                       cf->data[2] |= CAN_ERR_PROT_TX;
+
+               cf->data[6] = txerr;
+               cf->data[7] = rxerr;
+       }
+
+       if (state != mod->can.state && (state == CAN_STATE_ERROR_WARNING ||
+                                       state == CAN_STATE_ERROR_PASSIVE)) {
+               cf->can_id |= CAN_ERR_CRTL;
+               if (state == CAN_STATE_ERROR_WARNING) {
+                       mod->can.can_stats.error_warning++;
+                       cf->data[1] = (txerr > rxerr) ?
+                               CAN_ERR_CRTL_TX_WARNING :
+                               CAN_ERR_CRTL_RX_WARNING;
+               } else {
+                       mod->can.can_stats.error_passive++;
+                       cf->data[1] = (txerr > rxerr) ?
+                               CAN_ERR_CRTL_TX_PASSIVE :
+                               CAN_ERR_CRTL_RX_PASSIVE;
+               }
+
+               cf->data[6] = txerr;
+               cf->data[7] = rxerr;
+       }
+
+       mod->can.state = state;
+       stats->rx_errors++;
+       stats->rx_bytes += cf->can_dlc;
+       netif_rx(skb);
+       return 0;
+}
+
+static void ican3_handle_inquiry(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       switch (msg->data[0]) {
+       case INQUIRY_STATUS:
+       case INQUIRY_EXTENDED:
+               mod->bec.rxerr = msg->data[5];
+               mod->bec.txerr = msg->data[6];
+               complete(&mod->buserror_comp);
+               break;
+       case INQUIRY_TERMINATION:
+               mod->termination_enabled = msg->data[6] & HWCONF_TERMINATE_ON;
+               complete(&mod->termination_comp);
+               break;
+       default:
+               dev_err(mod->dev, "recieved an unknown inquiry response\n");
+               break;
+       }
+}
+
+static void ican3_handle_unknown_message(struct ican3_dev *mod,
+                                       struct ican3_msg *msg)
+{
+       dev_warn(mod->dev, "recieved unknown message: spec 0x%.2x length %d\n",
+                          msg->spec, le16_to_cpu(msg->len));
+}
+
+/*
+ * Handle a control message from the firmware
+ */
+static void ican3_handle_message(struct ican3_dev *mod, struct ican3_msg *msg)
+{
+       dev_dbg(mod->dev, "%s: modno %d spec 0x%.2x len %d bytes\n", __func__,
+                          mod->num, msg->spec, le16_to_cpu(msg->len));
+
+       switch (msg->spec) {
+       case MSG_IDVERS:
+               ican3_handle_idvers(mod, msg);
+               break;
+       case MSG_MSGLOST:
+       case MSG_FMSGLOST:
+               ican3_handle_msglost(mod, msg);
+               break;
+       case MSG_CEVTIND:
+               ican3_handle_cevtind(mod, msg);
+               break;
+       case MSG_INQUIRY:
+               ican3_handle_inquiry(mod, msg);
+               break;
+       default:
+               ican3_handle_unknown_message(mod, msg);
+               break;
+       }
+}
+
+/*
+ * Check that there is room in the TX ring to transmit another skb
+ *
+ * LOCKING: must hold mod->lock
+ */
+static bool ican3_txok(struct ican3_dev *mod)
+{
+       struct ican3_fast_desc __iomem *desc;
+       u8 control;
+
+       /* copy the control bits of the descriptor */
+       ican3_set_page(mod, mod->fasttx_start + (mod->fasttx_num / 16));
+       desc = mod->dpm + ((mod->fasttx_num % 16) * sizeof(*desc));
+       control = ioread8(&desc->control);
+
+       /* if the control bits are not valid, then we have no more space */
+       if (!(control & DESC_VALID))
+               return false;
+
+       return true;
+}
+
+/*
+ * Recieve one CAN frame from the hardware
+ *
+ * This works like the core of a NAPI function, but is intended to be called
+ * from workqueue context instead. This driver already needs a workqueue to
+ * process control messages, so we use the workqueue instead of using NAPI.
+ * This was done to simplify locking.
+ *
+ * CONTEXT: must be called from user context
+ */
+static int ican3_recv_skb(struct ican3_dev *mod)
+{
+       struct net_device *ndev = mod->ndev;
+       struct net_device_stats *stats = &ndev->stats;
+       struct ican3_fast_desc desc;
+       void __iomem *desc_addr;
+       struct can_frame *cf;
+       struct sk_buff *skb;
+       unsigned long flags;
+
+       spin_lock_irqsave(&mod->lock, flags);
+
+       /* copy the whole descriptor */
+       ican3_set_page(mod, mod->fastrx_start + (mod->fastrx_num / 16));
+       desc_addr = mod->dpm + ((mod->fastrx_num % 16) * sizeof(desc));
+       memcpy_fromio(&desc, desc_addr, sizeof(desc));
+
+       spin_unlock_irqrestore(&mod->lock, flags);
+
+       /* check that we actually have a CAN frame */
+       if (!(desc.control & DESC_VALID))
+               return -ENOBUFS;
+
+       /* allocate an skb */
+       skb = alloc_can_skb(ndev, &cf);
+       if (unlikely(skb == NULL)) {
+               stats->rx_dropped++;
+               goto err_noalloc;
+       }
+
+       /* convert the ICAN3 frame into Linux CAN format */
+       ican3_to_can_frame(mod, &desc, cf);
+
+       /* receive the skb, update statistics */
+       netif_receive_skb(skb);
+       stats->rx_packets++;
+       stats->rx_bytes += cf->can_dlc;
+
+err_noalloc:
+       /* toggle the valid bit and return the descriptor to the ring */
+       desc.control ^= DESC_VALID;
+
+       spin_lock_irqsave(&mod->lock, flags);
+
+       ican3_set_page(mod, mod->fastrx_start + (mod->fastrx_num / 16));
+       memcpy_toio(desc_addr, &desc, 1);
+
+       /* update the next buffer pointer */
+       mod->fastrx_num = (desc.control & DESC_WRAP) ? 0
+                                                    : (mod->fastrx_num + 1);
+
+       /* there are still more buffers to process */
+       spin_unlock_irqrestore(&mod->lock, flags);
+       return 0;
+}
+
+static int ican3_napi(struct napi_struct *napi, int budget)
+{
+       struct ican3_dev *mod = container_of(napi, struct ican3_dev, napi);
+       struct ican3_msg msg;
+       unsigned long flags;
+       int received = 0;
+       int ret;
+
+       /* process all communication messages */
+       while (true) {
+               ret = ican3_recv_msg(mod, &msg);
+               if (ret)
+                       break;
+
+               ican3_handle_message(mod, &msg);
+       }
+
+       /* process all CAN frames from the fast interface */
+       while (received < budget) {
+               ret = ican3_recv_skb(mod);
+               if (ret)
+                       break;
+
+               received++;
+       }
+
+       /* We have processed all packets that the adapter had, but it
+        * was less than our budget, stop polling */
+       if (received < budget)
+               napi_complete(napi);
+
+       spin_lock_irqsave(&mod->lock, flags);
+
+       /* Wake up the transmit queue if necessary */
+       if (netif_queue_stopped(mod->ndev) && ican3_txok(mod))
+               netif_wake_queue(mod->ndev);
+
+       spin_unlock_irqrestore(&mod->lock, flags);
+
+       /* re-enable interrupt generation */
+       iowrite8(1 << mod->num, &mod->ctrl->int_enable);
+       return received;
+}
+
+static irqreturn_t ican3_irq(int irq, void *dev_id)
+{
+       struct ican3_dev *mod = dev_id;
+       u8 stat;
+
+       /*
+        * The interrupt status register on this device reports interrupts
+        * as zeroes instead of using ones like most other devices
+        */
+       stat = ioread8(&mod->ctrl->int_disable) & (1 << mod->num);
+       if (stat == (1 << mod->num))
+               return IRQ_NONE;
+
+       /* clear the MODULbus interrupt from the microcontroller */
+       ioread8(&mod->dpmctrl->interrupt);
+
+       /* disable interrupt generation, schedule the NAPI poller */
+       iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+       napi_schedule(&mod->napi);
+       return IRQ_HANDLED;
+}
+
+/*
+ * Firmware reset, startup, and shutdown
+ */
+
+/*
+ * Reset an ICAN module to its power-on state
+ *
+ * CONTEXT: no network device registered
+ * LOCKING: work function disabled
+ */
+static int ican3_reset_module(struct ican3_dev *mod)
+{
+       u8 val = 1 << mod->num;
+       unsigned long start;
+       u8 runold, runnew;
+
+       /* disable interrupts so no more work is scheduled */
+       iowrite8(1 << mod->num, &mod->ctrl->int_disable);
+
+       /* flush any pending work */
+       flush_scheduled_work();
+
+       /* the first unallocated page in the DPM is #9 */
+       mod->free_page = DPM_FREE_START;
+
+       ican3_set_page(mod, QUEUE_OLD_CONTROL);
+       runold = ioread8(mod->dpm + TARGET_RUNNING);
+
+       /* reset the module */
+       iowrite8(val, &mod->ctrl->reset_assert);
+       iowrite8(val, &mod->ctrl->reset_deassert);
+
+       /* wait until the module has finished resetting and is running */
+       start = jiffies;
+       do {
+               ican3_set_page(mod, QUEUE_OLD_CONTROL);
+               runnew = ioread8(mod->dpm + TARGET_RUNNING);
+               if (runnew == (runold ^ 0xff))
+                       return 0;
+
+               msleep(10);
+       } while (time_before(jiffies, start + HZ / 4));
+
+       dev_err(mod->dev, "failed to reset CAN module\n");
+       return -ETIMEDOUT;
+}
+
+static void __devexit ican3_shutdown_module(struct ican3_dev *mod)
+{
+       ican3_msg_disconnect(mod);
+       ican3_reset_module(mod);
+}
+
+/*
+ * Startup an ICAN module, bringing it into fast mode
+ */
+static int __devinit ican3_startup_module(struct ican3_dev *mod)
+{
+       int ret;
+
+       ret = ican3_reset_module(mod);
+       if (ret) {
+               dev_err(mod->dev, "unable to reset module\n");
+               return ret;
+       }
+
+       /* re-enable interrupts so we can send messages */
+       iowrite8(1 << mod->num, &mod->ctrl->int_enable);
+
+       ret = ican3_msg_connect(mod);
+       if (ret) {
+               dev_err(mod->dev, "unable to connect to module\n");
+               return ret;
+       }
+
+       ican3_init_new_host_interface(mod);
+       ret = ican3_msg_newhostif(mod);
+       if (ret) {
+               dev_err(mod->dev, "unable to switch to new-style interface\n");
+               return ret;
+       }
+
+       /* default to "termination on" */
+       ret = ican3_set_termination(mod, true);
+       if (ret) {
+               dev_err(mod->dev, "unable to enable termination\n");
+               return ret;
+       }
+
+       /* default to "bus errors enabled" */
+       ret = ican3_set_buserror(mod, ICAN3_BUSERR_QUOTA_MAX);
+       if (ret) {
+               dev_err(mod->dev, "unable to set bus-error\n");
+               return ret;
+ &nb