2007-05-01 05:10:39

by Paul Sokolovsky

[permalink] [raw]
Subject: [RFC, PATCH 3/4] SoC base drivers: ASIC3 driver

Hello linux-kernel,

Note: This driver depends on ds1wm.h header, recently submitted, and which by now should be in -mm tree.
-----

asic3_base: SoC base driver for ASIC3 chip.

Signed-off-by: Paul Sokolovsky <[email protected]>


drivers/soc/Kconfig | 22 +
drivers/soc/Makefile | 5 +
drivers/soc/asic3_base.c | 1177 ++++++++++++++++++++++++++++++++++++++++
include/linux/soc/asic3_base.h | 100 ++++
include/linux/soc/tmio_mmc.h | 17 +
5 files changed, 1321 insertions(+), 0 deletions(-)

diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
new file mode 100644
index 0000000..2c9d846
--- /dev/null
+++ b/drivers/soc/Kconfig
@@ -0,0 +1,22 @@
+#
+# SoC drivers
+#
+# System-on-Chip are chips that implement a number of virtually unrelated
+# functions but sharing some common circuitry, for example power management
+# or an auxiliary interrupt controller.
+#
+
+menu "SoC drivers"
+
+config SOC_ASIC3
+ tristate "HTC ASIC3 SoC support (iPAQ & HTC PDAs)"
+
+config SOC_ASIC3_DS1WM
+ bool "Support HTC ASIC3 builtin DS1WM block"
+ depends on SOC_ASIC3
+ help
+ Choose Y here if you want to include support for ASIC3's builtin
+ W1 controller. Some devices do not use it, and yet other have
+ separate DS1WM controller. For them, choose N.
+
+endmenu
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
new file mode 100644
index 0000000..13031b1
--- /dev/null
+++ b/drivers/soc/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for SoC drivers.
+#
+
+obj-$(CONFIG_SOC_ASIC3) += asic3_base.o soc-core.o
diff --git a/drivers/soc/asic3_base.c b/drivers/soc/asic3_base.c
new file mode 100644
index 0000000..ca0f862
--- /dev/null
+++ b/drivers/soc/asic3_base.c
@@ -0,0 +1,1177 @@
+/*
+ * Driver interface to HTC "ASIC3"
+ *
+ * Copyright 2001 Compaq Computer Corporation.
+ * Copyright 2004-2005 Phil Blundell
+ *
+ * Use consistent with the GNU GPL is permitted,
+ * provided that this copyright notice is
+ * preserved in its entirety in all copies and derived works.
+ *
+ * COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
+ * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
+ * FITNESS FOR ANY PARTICULAR PURPOSE.
+ *
+ * Author: Andrew Christian
+ * <[email protected]>
+ * October 2001
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/ds1wm.h>
+#ifdef CONFIG_PLAT_S3C24XX
+#include <asm/plat-s3c24xx/clock.h>
+#define clk_register(clk) s3c24xx_register_clock(clk)
+#define clk_unregister(clk)
+#else
+#include <asm/arch/clock.h>
+#endif
+
+#include <asm/hardware.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+
+#include <asm/hardware/ipaq-asic3.h>
+#include <linux/soc/asic3_base.h>
+#include <linux/soc/tmio_mmc.h>
+#include "soc-core.h"
+
+
+struct asic3_data
+{
+ void *mapping;
+ unsigned int bus_shift;
+ int irq_base;
+ int irq_nr;
+
+ u16 irq_bothedge[4];
+ struct device *dev;
+
+ struct platform_device *mmc_dev;
+};
+
+static spinlock_t asic3_gpio_lock;
+
+static int asic3_remove(struct platform_device *dev);
+
+static inline unsigned long asic3_address(struct device *dev,
+ unsigned int reg)
+{
+ struct asic3_data *adata;
+
+ adata = (struct asic3_data *)dev->driver_data;
+
+ return (unsigned long)adata->mapping + (reg >> (2 - adata->bus_shift));
+}
+
+void asic3_write_register(struct device *dev, unsigned int reg, u32 value)
+{
+ __raw_writew(value, asic3_address(dev, reg));
+}
+EXPORT_SYMBOL(asic3_write_register);
+
+u32 asic3_read_register(struct device *dev, unsigned int reg)
+{
+ return __raw_readw(asic3_address(dev, reg));
+}
+EXPORT_SYMBOL(asic3_read_register);
+
+static inline void __asic3_write_register(struct asic3_data *asic,
+ unsigned int reg, u32 value)
+{
+ __raw_writew(value, (unsigned long)asic->mapping
+ + (reg >> (2 - asic->bus_shift)));
+}
+
+static inline u32 __asic3_read_register(struct asic3_data *asic,
+ unsigned int reg)
+{
+ return __raw_readw((unsigned long)asic->mapping
+ + (reg >> (2 - asic->bus_shift)));
+}
+
+#define ASIC3_GPIO_FN(get_fn_name, set_fn_name, REG) \
+u32 get_fn_name(struct device *dev) \
+{ \
+ return asic3_read_register(dev, REG); \
+} \
+EXPORT_SYMBOL(get_fn_name); \
+ \
+void set_fn_name(struct device *dev, u32 bits, u32 val) \
+{ \
+ unsigned long flags; \
+ \
+ spin_lock_irqsave(&asic3_gpio_lock, flags); \
+ val |= (asic3_read_register(dev, REG) & ~bits); \
+ asic3_write_register(dev, REG, val); \
+ spin_unlock_irqrestore(&asic3_gpio_lock, flags); \
+} \
+EXPORT_SYMBOL(set_fn_name);
+
+#define ASIC3_GPIO_REGISTER(ACTION, action, fn, FN) \
+ ASIC3_GPIO_FN (asic3_get_gpio_ ## action ## _ ## fn , \
+ asic3_set_gpio_ ## action ## _ ## fn , \
+ _IPAQ_ASIC3_GPIO_ ## FN ## _Base \
+ + _IPAQ_ASIC3_GPIO_ ## ACTION )
+
+#define ASIC3_GPIO_FUNCTIONS(fn, FN) \
+ ASIC3_GPIO_REGISTER (Direction, dir, fn, FN) \
+ ASIC3_GPIO_REGISTER (Out, out, fn, FN) \
+ ASIC3_GPIO_REGISTER (SleepMask, sleepmask, fn, FN) \
+ ASIC3_GPIO_REGISTER (SleepOut, sleepout, fn, FN) \
+ ASIC3_GPIO_REGISTER (BattFaultOut, battfaultout, fn, FN) \
+ ASIC3_GPIO_REGISTER (AltFunction, alt_fn, fn, FN) \
+ ASIC3_GPIO_REGISTER (SleepConf, sleepconf, fn, FN) \
+ ASIC3_GPIO_REGISTER (Status, status, fn, FN)
+
+ASIC3_GPIO_FUNCTIONS(a, A)
+ASIC3_GPIO_FUNCTIONS(b, B)
+ASIC3_GPIO_FUNCTIONS(c, C)
+ASIC3_GPIO_FUNCTIONS(d, D)
+
+int asic3_gpio_get_value(struct device *dev, unsigned gpio)
+{
+ u32 mask = ASIC3_GPIO_bit(gpio);
+ printk("%s(%d)\n", __FUNCTION__, gpio);
+ switch (gpio >> 4) {
+ case _IPAQ_ASIC3_GPIO_BANK_A:
+ return asic3_get_gpio_status_a(dev) & mask;
+ case _IPAQ_ASIC3_GPIO_BANK_B:
+ return asic3_get_gpio_status_b(dev) & mask;
+ case _IPAQ_ASIC3_GPIO_BANK_C:
+ return asic3_get_gpio_status_c(dev) & mask;
+ case _IPAQ_ASIC3_GPIO_BANK_D:
+ return asic3_get_gpio_status_d(dev) & mask;
+ }
+
+ printk(KERN_ERR "%s: invalid GPIO value 0x%x", __FUNCTION__, gpio);
+ return 0;
+}
+EXPORT_SYMBOL(asic3_gpio_get_value);
+
+void asic3_gpio_set_value(struct device *dev, unsigned gpio, int val)
+{
+ u32 mask = ASIC3_GPIO_bit(gpio);
+ u32 bitval = 0;
+ if (val) bitval = mask;
+ printk("%s(%d, %d)\n", __FUNCTION__, gpio, val);
+
+ switch (gpio >> 4) {
+ case _IPAQ_ASIC3_GPIO_BANK_A:
+ asic3_set_gpio_out_a(dev, mask, bitval);
+ return;
+ case _IPAQ_ASIC3_GPIO_BANK_B:
+ asic3_set_gpio_out_b(dev, mask, bitval);
+ return;
+ case _IPAQ_ASIC3_GPIO_BANK_C:
+ asic3_set_gpio_out_c(dev, mask, bitval);
+ return;
+ case _IPAQ_ASIC3_GPIO_BANK_D:
+ asic3_set_gpio_out_d(dev, mask, bitval);
+ return;
+ }
+
+ printk(KERN_ERR "%s: invalid GPIO value 0x%x", __FUNCTION__, gpio);
+}
+EXPORT_SYMBOL(asic3_gpio_set_value);
+
+int asic3_irq_base(struct device *dev)
+{
+ struct asic3_data *asic = dev->driver_data;
+
+ return asic->irq_base;
+}
+EXPORT_SYMBOL(asic3_irq_base);
+
+void asic3_set_led(struct device *dev, int led_num, int duty_time,
+ int cycle_time, int timebase)
+{
+ struct asic3_data *asic = dev->driver_data;
+ unsigned int led_base;
+
+ /* it's a macro thing: see #define _IPAQ_ASIC_LED_0_Base for why you
+ * can't substitute led_num in the macros below...
+ */
+
+ switch (led_num) {
+ case 0:
+ led_base = _IPAQ_ASIC3_LED_0_Base;
+ break;
+ case 1:
+ led_base = _IPAQ_ASIC3_LED_1_Base;
+ break;
+ case 2:
+ led_base = _IPAQ_ASIC3_LED_2_Base;
+ break;
+ default:
+ printk(KERN_ERR "%s: invalid led number %d", __FUNCTION__,
+ led_num);
+ return;
+ }
+
+ __asic3_write_register(asic, led_base + _IPAQ_ASIC3_LED_TimeBase,
+ timebase | LED_EN);
+ __asic3_write_register(asic, led_base + _IPAQ_ASIC3_LED_PeriodTime,
+ cycle_time);
+ __asic3_write_register(asic, led_base + _IPAQ_ASIC3_LED_DutyTime,
+ 0);
+ udelay(20); /* asic voodoo - possibly need a whole duty cycle? */
+ __asic3_write_register(asic, led_base + _IPAQ_ASIC3_LED_DutyTime,
+ duty_time);
+}
+
+EXPORT_SYMBOL(asic3_set_led);
+
+void asic3_set_clock_sel(struct device *dev, u32 bits, u32 val)
+{
+ struct asic3_data *asic = dev->driver_data;
+ unsigned long flags;
+ u32 v;
+
+ spin_lock_irqsave(&asic3_gpio_lock, flags);
+ v = __asic3_read_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, SEL));
+ v = (v & ~bits) | val;
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, SEL), v);
+ spin_unlock_irqrestore(&asic3_gpio_lock, flags);
+}
+EXPORT_SYMBOL(asic3_set_clock_sel);
+
+void asic3_set_clock_cdex(struct device *dev, u32 bits, u32 val)
+{
+ struct asic3_data *asic = dev->driver_data;
+ unsigned long flags;
+ u32 v;
+
+ spin_lock_irqsave(&asic3_gpio_lock, flags);
+ v = __asic3_read_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, CDEX));
+ v = (v & ~bits) | val;
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, CDEX), v);
+ spin_unlock_irqrestore(&asic3_gpio_lock, flags);
+}
+EXPORT_SYMBOL(asic3_set_clock_cdex);
+
+static int asic3_clock_cdex_enable(struct clk *clk, int enable)
+{
+ struct asic3_data *asic = (struct asic3_data *)clk->parent->ctrlbit;
+ unsigned long flags, val;
+
+ local_irq_save(flags);
+
+ val = __asic3_read_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, CDEX));
+ if (enable)
+ val |= clk->ctrlbit;
+ else
+ val &= ~clk->ctrlbit;
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, CDEX), val);
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+/* base clocks */
+
+static struct clk clk_g = {
+ .name = "gclk",
+ .rate = 0,
+ .parent = NULL,
+};
+
+/* clock definitions */
+
+static struct clk asic3_clocks[] = {
+ {
+ .name = "spi",
+ .id = -1,
+ .parent = &clk_g,
+ .enable = asic3_clock_cdex_enable,
+ .ctrlbit = CLOCK_CDEX_SPI,
+ },
+#ifdef CONFIG_HTC_ASIC3_DS1WM
+ {
+ .name = "ds1wm",
+ .id = -1,
+ .rate = 5000000,
+ .parent = &clk_g,
+ .enable = asic3_clock_cdex_enable,
+ .ctrlbit = CLOCK_CDEX_OWM,
+ },
+#endif
+ {
+ .name = "pwm0",
+ .id = -1,
+ .parent = &clk_g,
+ .enable = asic3_clock_cdex_enable,
+ .ctrlbit = CLOCK_CDEX_PWM0,
+ },
+ {
+ .name = "pwm1",
+ .id = -1,
+ .parent = &clk_g,
+ .enable = asic3_clock_cdex_enable,
+ .ctrlbit = CLOCK_CDEX_PWM1,
+ },
+ {
+ .name = "led0",
+ .id = -1,
+ .parent = &clk_g,
+ .enable = asic3_clock_cdex_enable,
+ .ctrlbit = CLOCK_CDEX_LED0,
+ },
+ {
+ .name = "led1",
+ .id = -1,
+ .parent = &clk_g,
+ .enable = asic3_clock_cdex_enable,
+ .ctrlbit = CLOCK_CDEX_LED1,
+ },
+ {
+ .name = "led2",
+ .id = -1,
+ .parent = &clk_g,
+ .enable = asic3_clock_cdex_enable,
+ .ctrlbit = CLOCK_CDEX_LED2,
+ },
+ {
+ .name = "smbus",
+ .id = -1,
+ .parent = &clk_g,
+ .enable = asic3_clock_cdex_enable,
+ .ctrlbit = CLOCK_CDEX_SMBUS,
+ },
+ {
+ .name = "ex0",
+ .id = -1,
+ .parent = &clk_g,
+ .enable = asic3_clock_cdex_enable,
+ .ctrlbit = CLOCK_CDEX_EX0,
+ },
+ {
+ .name = "ex1",
+ .id = -1,
+ .parent = &clk_g,
+ .enable = asic3_clock_cdex_enable,
+ .ctrlbit = CLOCK_CDEX_EX1,
+ },
+};
+
+void asic3_set_extcf_select(struct device *dev, u32 bits, u32 val)
+{
+ struct asic3_data *asic = dev->driver_data;
+ unsigned long flags;
+ u32 v;
+
+ spin_lock_irqsave(&asic3_gpio_lock, flags);
+ v = __asic3_read_register(asic, IPAQ_ASIC3_OFFSET(EXTCF, Select));
+ v = (v & ~bits) | val;
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(EXTCF, Select), v);
+ spin_unlock_irqrestore(&asic3_gpio_lock, flags);
+}
+EXPORT_SYMBOL(asic3_set_extcf_select);
+
+void asic3_set_extcf_reset(struct device *dev, u32 bits, u32 val)
+{
+ struct asic3_data *asic = dev->driver_data;
+ unsigned long flags;
+ u32 v;
+
+ spin_lock_irqsave(&asic3_gpio_lock, flags);
+ v = __asic3_read_register(asic, IPAQ_ASIC3_OFFSET(EXTCF, Reset));
+ v = (v & ~bits) | val;
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(EXTCF, Reset), v);
+ spin_unlock_irqrestore(&asic3_gpio_lock, flags);
+}
+EXPORT_SYMBOL(asic3_set_extcf_reset);
+
+void asic3_set_sdhwctrl(struct device *dev, u32 bits, u32 val)
+{
+ struct asic3_data *asic = dev->driver_data;
+ unsigned long flags;
+ u32 v;
+
+ spin_lock_irqsave (&asic3_gpio_lock, flags);
+ v = __asic3_read_register(asic, IPAQ_ASIC3_OFFSET(SDHWCTRL, SDConf));
+ v = (v & ~bits) | val;
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(SDHWCTRL, SDConf), v);
+ spin_unlock_irqrestore(&asic3_gpio_lock, flags);
+}
+EXPORT_SYMBOL(asic3_set_sdhwctrl);
+
+
+#define MAX_ASIC_ISR_LOOPS 20
+#define _IPAQ_ASIC3_GPIO_Base_INCR \
+ (_IPAQ_ASIC3_GPIO_B_Base - _IPAQ_ASIC3_GPIO_A_Base)
+
+static inline void asic3_irq_flip_edge(struct asic3_data *asic,
+ u32 base, int bit)
+{
+ u16 edge = __asic3_read_register(asic,
+ base + _IPAQ_ASIC3_GPIO_EdgeTrigger);
+ edge ^= bit;
+ __asic3_write_register(asic,
+ base + _IPAQ_ASIC3_GPIO_EdgeTrigger, edge);
+}
+
+static void asic3_irq_demux(unsigned int irq, struct irq_desc *desc)
+{
+ int iter;
+ struct asic3_data *asic;
+
+ /* Acknowledge the parrent (i.e. CPU's) IRQ */
+ desc->chip->ack(irq);
+
+ asic = desc->handler_data;
+
+ /* printk( KERN_NOTICE "asic3_irq_demux: irq=%d\n", irq ); */
+ for (iter = 0 ; iter < MAX_ASIC_ISR_LOOPS; iter++) {
+ u32 status;
+ int bank;
+
+ status = __asic3_read_register(asic,
+ IPAQ_ASIC3_OFFSET(INTR, PIntStat));
+ /* Check all ten register bits */
+ if ((status & 0x3ff) == 0)
+ break;
+
+ /* Handle GPIO IRQs */
+ for (bank = 0; bank < 4; bank++) {
+ if (status & (1 << bank)) {
+ unsigned long base, i, istat;
+
+ base = _IPAQ_ASIC3_GPIO_A_Base
+ + bank * _IPAQ_ASIC3_GPIO_Base_INCR;
+ istat = __asic3_read_register(asic,
+ base + _IPAQ_ASIC3_GPIO_IntStatus);
+ /* IntStatus is write 0 to clear */
+ /* XXX could miss interrupts! */
+ __asic3_write_register(asic,
+ base + _IPAQ_ASIC3_GPIO_IntStatus, 0);
+
+ for (i = 0; i < 16; i++) {
+ int bit = (1 << i);
+ unsigned int irqnr;
+ if (!(istat & bit))
+ continue;
+
+ irqnr = asic->irq_base
+ + (16 * bank) + i;
+ desc = irq_desc + irqnr;
+ desc->handle_irq(irqnr, desc);
+ if (asic->irq_bothedge[bank] & bit) {
+ asic3_irq_flip_edge(asic, base,
+ bit);
+ }
+ }
+ }
+ }
+
+ /* Handle remaining IRQs in the status register */
+ {
+ int i;
+
+ for (i = ASIC3_LED0_IRQ; i <= ASIC3_OWM_IRQ; i++) {
+ /* They start at bit 4 and go up */
+ if (status & (1 << (i - ASIC3_LED0_IRQ + 4))) {
+ desc = irq_desc + asic->irq_base + i;
+ desc->handle_irq(asic->irq_base + i,
+ desc);
+ }
+ }
+ }
+
+ }
+
+ if (iter >= MAX_ASIC_ISR_LOOPS)
+ printk(KERN_ERR "%s: interrupt processing overrun\n",
+ __FUNCTION__);
+}
+
+static inline int asic3_irq_to_bank(struct asic3_data *asic, int irq)
+{
+ int n;
+
+ n = (irq - asic->irq_base) >> 4;
+
+ return (n * (_IPAQ_ASIC3_GPIO_B_Base - _IPAQ_ASIC3_GPIO_A_Base));
+}
+
+static inline int asic3_irq_to_index(struct asic3_data *asic, int irq)
+{
+ return (irq - asic->irq_base) & 15;
+}
+
+static void asic3_mask_gpio_irq(unsigned int irq)
+{
+ struct asic3_data *asic = get_irq_chip_data(irq);
+ u32 val, bank, index;
+ unsigned long flags;
+
+ bank = asic3_irq_to_bank(asic, irq);
+ index = asic3_irq_to_index(asic, irq);
+
+ spin_lock_irqsave(&asic3_gpio_lock, flags);
+ val = __asic3_read_register(asic, bank + _IPAQ_ASIC3_GPIO_Mask);
+ val |= 1 << index;
+ __asic3_write_register(asic, bank + _IPAQ_ASIC3_GPIO_Mask, val);
+ spin_unlock_irqrestore(&asic3_gpio_lock, flags);
+}
+
+static void asic3_mask_irq(unsigned int irq)
+{
+ struct asic3_data *asic = get_irq_chip_data(irq);
+ int regval;
+
+ if (irq < ASIC3_NR_GPIO_IRQS) {
+ printk(KERN_ERR "asic3_base: gpio mask attempt, irq %d\n",
+ irq);
+ return;
+ }
+
+ regval = __asic3_read_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask);
+
+ switch (irq - asic->irq_base) {
+ case ASIC3_LED0_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval & ~ASIC3_INTMASK_MASK0);
+ break;
+ case ASIC3_LED1_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval & ~ASIC3_INTMASK_MASK1);
+ break;
+ case ASIC3_LED2_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval & ~ASIC3_INTMASK_MASK2);
+ break;
+ case ASIC3_SPI_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval & ~ASIC3_INTMASK_MASK3);
+ break;
+ case ASIC3_SMBUS_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval & ~ASIC3_INTMASK_MASK4);
+ break;
+ case ASIC3_OWM_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval & ~ASIC3_INTMASK_MASK5);
+ break;
+ default:
+ printk(KERN_ERR "asic3_base: bad non-gpio irq %d\n", irq);
+ break;
+ }
+}
+
+static void asic3_unmask_gpio_irq(unsigned int irq)
+{
+ struct asic3_data *asic = get_irq_chip_data(irq);
+ u32 val, bank, index;
+ unsigned long flags;
+
+ bank = asic3_irq_to_bank(asic, irq);
+ index = asic3_irq_to_index(asic, irq);
+
+ spin_lock_irqsave(&asic3_gpio_lock, flags);
+ val = __asic3_read_register(asic, bank + _IPAQ_ASIC3_GPIO_Mask);
+ val &= ~(1 << index);
+ __asic3_write_register(asic, bank + _IPAQ_ASIC3_GPIO_Mask, val);
+ spin_unlock_irqrestore(&asic3_gpio_lock, flags);
+}
+
+static void asic3_unmask_irq(unsigned int irq)
+{
+ struct asic3_data *asic = get_irq_chip_data(irq);
+ int regval;
+
+ if (irq < ASIC3_NR_GPIO_IRQS) {
+ printk(KERN_ERR "asic3_base: gpio unmask attempt, irq %d\n",
+ irq);
+ return;
+ }
+
+ regval = __asic3_read_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask);
+
+ switch (irq - asic->irq_base) {
+ case ASIC3_LED0_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval | ASIC3_INTMASK_MASK0);
+ break;
+ case ASIC3_LED1_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval | ASIC3_INTMASK_MASK1);
+ break;
+ case ASIC3_LED2_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval | ASIC3_INTMASK_MASK2);
+ break;
+ case ASIC3_SPI_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval | ASIC3_INTMASK_MASK3);
+ break;
+ case ASIC3_SMBUS_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval | ASIC3_INTMASK_MASK4);
+ break;
+ case ASIC3_OWM_IRQ:
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
+ regval | ASIC3_INTMASK_MASK5);
+ break;
+ default:
+ printk(KERN_ERR "asic3_base: bad non-gpio irq %d\n", irq);
+ break;
+ }
+}
+
+static int asic3_gpio_irq_type(unsigned int irq, unsigned int type)
+{
+ struct asic3_data *asic = get_irq_chip_data(irq);
+ u32 bank, index;
+ unsigned long flags;
+ u16 trigger, level, edge, bit;
+
+ bank = asic3_irq_to_bank(asic, irq);
+ index = asic3_irq_to_index(asic, irq);
+ bit = 1<<index;
+
+ spin_lock_irqsave(&asic3_gpio_lock, flags);
+ level = __asic3_read_register(asic,
+ bank + _IPAQ_ASIC3_GPIO_LevelTrigger);
+ edge = __asic3_read_register(asic,
+ bank + _IPAQ_ASIC3_GPIO_EdgeTrigger);
+ trigger = __asic3_read_register(asic,
+ bank + _IPAQ_ASIC3_GPIO_TriggerType);
+ asic->irq_bothedge[(irq - asic->irq_base) >> 4] &= ~bit;
+
+ if (type == IRQT_RISING) {
+ trigger |= bit;
+ edge |= bit;
+ } else if (type == IRQT_FALLING) {
+ trigger |= bit;
+ edge &= ~bit;
+ } else if (type == IRQT_BOTHEDGE) {
+ trigger |= bit;
+ if (asic3_gpio_get_value(asic->dev, irq - asic->irq_base))
+ edge &= ~bit;
+ else
+ edge |= bit;
+ asic->irq_bothedge[(irq - asic->irq_base) >> 4] |= bit;
+ } else if (type == IRQT_LOW) {
+ trigger &= ~bit;
+ level &= ~bit;
+ } else if (type == IRQT_HIGH) {
+ trigger &= ~bit;
+ level |= bit;
+ } else {
+ /*
+ * if type == IRQT_NOEDGE, we should mask interrupts, but
+ * be careful to not unmask them if mask was also called.
+ * Probably need internal state for mask.
+ */
+ printk(KERN_NOTICE "asic3: irq type not changed.\n");
+ }
+ __asic3_write_register(asic, bank + _IPAQ_ASIC3_GPIO_LevelTrigger,
+ level);
+ __asic3_write_register(asic, bank + _IPAQ_ASIC3_GPIO_EdgeTrigger,
+ edge);
+ __asic3_write_register(asic, bank + _IPAQ_ASIC3_GPIO_TriggerType,
+ trigger);
+ spin_unlock_irqrestore(&asic3_gpio_lock, flags);
+ return 0;
+}
+
+static struct irq_chip asic3_gpio_irq_chip = {
+ .name = "ASIC3-GPIO",
+ .ack = asic3_mask_gpio_irq,
+ .mask = asic3_mask_gpio_irq,
+ .unmask = asic3_unmask_gpio_irq,
+ .set_type = asic3_gpio_irq_type,
+};
+
+static struct irq_chip asic3_irq_chip = {
+ .name = "ASIC3",
+ .ack = asic3_mask_irq,
+ .mask = asic3_mask_irq,
+ .unmask = asic3_unmask_irq,
+};
+
+static void asic3_release(struct device *dev)
+{
+ struct platform_device *sdev = to_platform_device(dev);
+
+ kfree(sdev->resource);
+ kfree(sdev);
+}
+
+int asic3_register_mmc(struct device *dev)
+{
+ struct platform_device *sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
+ struct tmio_mmc_hwconfig *mmc_config = kmalloc(sizeof(*mmc_config),
+ GFP_KERNEL);
+ struct platform_device *pdev = to_platform_device(dev);
+ struct asic3_data *asic = dev->driver_data;
+ struct asic3_platform_data *asic3_pdata = dev->platform_data;
+ struct resource *res;
+ int rc;
+
+ if (sdev == NULL || mmc_config == NULL)
+ return -ENOMEM;
+
+ if (asic3_pdata->tmio_mmc_hwconfig) {
+ memcpy(mmc_config, asic3_pdata->tmio_mmc_hwconfig,
+ sizeof(*mmc_config));
+ } else {
+ memset(mmc_config, 0, sizeof(*mmc_config));
+ }
+ mmc_config->address_shift = asic->bus_shift;
+
+ sdev->id = -1;
+ sdev->name = "asic3_mmc";
+ sdev->dev.parent = dev;
+ sdev->num_resources = 2;
+ sdev->dev.platform_data = mmc_config;
+ sdev->dev.release = asic3_release;
+
+ res = kzalloc(sdev->num_resources * sizeof(struct resource),
+ GFP_KERNEL);
+ if (res == NULL) {
+ kfree(sdev);
+ kfree(mmc_config);
+ return -ENOMEM;
+ }
+ sdev->resource = res;
+
+ res[0].start = pdev->resource[2].start;
+ res[0].end = pdev->resource[2].end;
+ res[0].flags = IORESOURCE_MEM;
+ res[1].start = res[1].end = pdev->resource[3].start;
+ res[1].flags = IORESOURCE_IRQ;
+
+ rc = platform_device_register(sdev);
+ if (rc) {
+ printk(KERN_ERR "asic3_base: "
+ "Could not register asic3_mmc device\n");
+ kfree(res);
+ kfree(sdev);
+ return rc;
+ }
+
+ asic->mmc_dev = sdev;
+
+ return 0;
+}
+EXPORT_SYMBOL(asic3_register_mmc);
+
+int asic3_unregister_mmc(struct device *dev)
+{
+ struct asic3_data *asic = dev->driver_data;
+ platform_device_unregister(asic->mmc_dev);
+ asic->mmc_dev = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL(asic3_unregister_mmc);
+
+#ifdef CONFIG_HTC_ASIC3_DS1WM
+/*
+ * DS1WM subdevice
+ */
+
+static void asic3_ds1wm_enable(struct platform_device *ds1wm_dev)
+{
+ struct device *dev = ds1wm_dev->dev.parent;
+
+ /* Turn on external clocks and the OWM clock */
+ asic3_set_clock_cdex(dev,
+ CLOCK_CDEX_EX0 | CLOCK_CDEX_EX1 | CLOCK_CDEX_OWM,
+ CLOCK_CDEX_EX0 | CLOCK_CDEX_EX1 | CLOCK_CDEX_OWM);
+
+ mdelay(1);
+
+ asic3_set_extcf_reset(dev, ASIC3_EXTCF_OWM_RESET,
+ ASIC3_EXTCF_OWM_RESET);
+ mdelay(1);
+ asic3_set_extcf_reset(dev, ASIC3_EXTCF_OWM_RESET, 0);
+ mdelay(1);
+
+ /* Clear OWM_SMB, set OWM_EN */
+ asic3_set_extcf_select(dev,
+ ASIC3_EXTCF_OWM_SMB | ASIC3_EXTCF_OWM_EN,
+ 0 | ASIC3_EXTCF_OWM_EN);
+
+ mdelay(1);
+}
+
+static void asic3_ds1wm_disable(struct platform_device *ds1wm_dev)
+{
+ struct device *dev = ds1wm_dev->dev.parent;
+
+ asic3_set_extcf_select(dev,
+ ASIC3_EXTCF_OWM_SMB | ASIC3_EXTCF_OWM_EN,
+ 0 | 0);
+
+ asic3_set_clock_cdex(dev,
+ CLOCK_CDEX_EX0 | CLOCK_CDEX_EX1 | CLOCK_CDEX_OWM,
+ CLOCK_CDEX_EX0 | CLOCK_CDEX_EX1 | 0);
+}
+
+
+static struct resource asic3_ds1wm_resources[] = {
+ {
+ .start = _IPAQ_ASIC3_OWM_Base,
+ .end = _IPAQ_ASIC3_OWM_Base + 0x14 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = ASIC3_OWM_IRQ,
+ .end = ASIC3_OWM_IRQ,
+ .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_SOC_SUBDEVICE,
+ },
+};
+
+static struct ds1wm_platform_data ds1wm_pd = {
+ .active_high = 1,
+ .enable = asic3_ds1wm_enable,
+ .disable = asic3_ds1wm_disable,
+};
+#endif
+
+static struct soc_device_data asic3_blocks[] = {
+#ifdef CONFIG_HTC_ASIC3_DS1WM
+ {
+ .name = "ds1wm",
+ .res = asic3_ds1wm_resources,
+ .num_resources = ARRAY_SIZE(asic3_ds1wm_resources),
+ .hwconfig = &ds1wm_pd,
+ },
+#endif
+};
+
+static int asic3_probe(struct platform_device *pdev)
+{
+ struct asic3_platform_data *pdata = pdev->dev.platform_data;
+ struct asic3_data *asic;
+ struct device *dev = &pdev->dev;
+ unsigned long clksel;
+ int i, rc;
+
+ asic = kzalloc(sizeof(struct asic3_data), GFP_KERNEL);
+ if (!asic)
+ return -ENOMEM;
+
+ spin_lock_init (&asic3_gpio_lock);
+ platform_set_drvdata(pdev, asic);
+ asic->dev = &pdev->dev;
+
+ asic->mapping = ioremap(pdev->resource[0].start, IPAQ_ASIC3_MAP_SIZE);
+ if (!asic->mapping) {
+ printk(KERN_ERR "asic3: couldn't ioremap ASIC3\n");
+ kfree (asic);
+ return -ENOMEM;
+ }
+
+ if (pdata && pdata->bus_shift)
+ asic->bus_shift = pdata->bus_shift;
+ else
+ asic->bus_shift = 2;
+
+ /* XXX: should get correct SD clock values from pdata struct */
+ clksel = 0;
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, SEL), clksel);
+
+ /* Register ASIC3's clocks. */
+ clk_g.ctrlbit = (int)asic;
+
+ if (clk_register(&clk_g) < 0)
+ printk(KERN_ERR "asic3: failed to register ASIC3 gclk\n");
+
+ for (i = 0; i < ARRAY_SIZE(asic3_clocks); i++) {
+ rc = clk_register(&asic3_clocks[i]);
+ if (rc < 0)
+ printk(KERN_ERR "asic3: "
+ "failed to register clock %s (%d)\n",
+ asic3_clocks[i].name, rc);
+ }
+
+ __asic3_write_register(asic, IPAQ_ASIC3_GPIO_OFFSET(A, Mask), 0xffff);
+ __asic3_write_register(asic, IPAQ_ASIC3_GPIO_OFFSET(B, Mask), 0xffff);
+ __asic3_write_register(asic, IPAQ_ASIC3_GPIO_OFFSET(C, Mask), 0xffff);
+ __asic3_write_register(asic, IPAQ_ASIC3_GPIO_OFFSET(D, Mask), 0xffff);
+
+ asic3_set_gpio_sleepmask_a(dev, 0xffff, 0xffff);
+ asic3_set_gpio_sleepmask_b(dev, 0xffff, 0xffff);
+ asic3_set_gpio_sleepmask_c(dev, 0xffff, 0xffff);
+ asic3_set_gpio_sleepmask_d(dev, 0xffff, 0xffff);
+
+ if (pdata) {
+ asic3_set_gpio_out_a(dev, 0xffff, pdata->gpio_a.init);
+ asic3_set_gpio_out_b(dev, 0xffff, pdata->gpio_b.init);
+ asic3_set_gpio_out_c(dev, 0xffff, pdata->gpio_c.init);
+ asic3_set_gpio_out_d(dev, 0xffff, pdata->gpio_d.init);
+
+ asic3_set_gpio_dir_a(dev, 0xffff, pdata->gpio_a.dir);
+ asic3_set_gpio_dir_b(dev, 0xffff, pdata->gpio_b.dir);
+ asic3_set_gpio_dir_c(dev, 0xffff, pdata->gpio_c.dir);
+ asic3_set_gpio_dir_d(dev, 0xffff, pdata->gpio_d.dir);
+
+ asic3_set_gpio_sleepmask_a(dev, 0xffff,
+ pdata->gpio_a.sleep_mask);
+ asic3_set_gpio_sleepmask_b(dev, 0xffff,
+ pdata->gpio_b.sleep_mask);
+ asic3_set_gpio_sleepmask_c(dev, 0xffff,
+ pdata->gpio_c.sleep_mask);
+ asic3_set_gpio_sleepmask_d(dev, 0xffff,
+ pdata->gpio_d.sleep_mask);
+
+ asic3_set_gpio_sleepout_a(dev, 0xffff,
+ pdata->gpio_a.sleep_out);
+ asic3_set_gpio_sleepout_b(dev, 0xffff,
+ pdata->gpio_b.sleep_out);
+ asic3_set_gpio_sleepout_c(dev, 0xffff,
+ pdata->gpio_c.sleep_out);
+ asic3_set_gpio_sleepout_d(dev, 0xffff,
+ pdata->gpio_d.sleep_out);
+
+ asic3_set_gpio_battfaultout_a(dev, 0xffff,
+ pdata->gpio_a.batt_fault_out);
+ asic3_set_gpio_battfaultout_b(dev, 0xffff,
+ pdata->gpio_b.batt_fault_out);
+ asic3_set_gpio_battfaultout_c(dev, 0xffff,
+ pdata->gpio_c.batt_fault_out);
+ asic3_set_gpio_battfaultout_d(dev, 0xffff,
+ pdata->gpio_d.batt_fault_out);
+
+ asic3_set_gpio_sleepconf_a(dev, 0xffff,
+ pdata->gpio_a.sleep_conf);
+ asic3_set_gpio_sleepconf_b(dev, 0xffff,
+ pdata->gpio_b.sleep_conf);
+ asic3_set_gpio_sleepconf_c(dev, 0xffff,
+ pdata->gpio_c.sleep_conf);
+ asic3_set_gpio_sleepconf_d(dev, 0xffff,
+ pdata->gpio_d.sleep_conf);
+
+ asic3_set_gpio_alt_fn_a(dev, 0xffff,
+ pdata->gpio_a.alt_function);
+ asic3_set_gpio_alt_fn_b(dev, 0xffff,
+ pdata->gpio_b.alt_function);
+ asic3_set_gpio_alt_fn_c(dev, 0xffff,
+ pdata->gpio_c.alt_function);
+ asic3_set_gpio_alt_fn_d(dev, 0xffff,
+ pdata->gpio_d.alt_function);
+ }
+
+ asic->irq_nr = -1;
+ asic->irq_base = -1;
+
+ if (pdev->num_resources > 1)
+ asic->irq_nr = pdev->resource[1].start;
+
+ if (asic->irq_nr != -1) {
+ unsigned int i;
+
+ if (!pdata->irq_base) {
+ printk(KERN_ERR "asic3: IRQ base not specified\n");
+ asic3_remove(pdev);
+ return -EINVAL;
+ }
+
+ asic->irq_base = pdata->irq_base;
+
+ /* turn on clock to IRQ controller */
+ clksel |= CLOCK_SEL_CX;
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, SEL),
+ clksel);
+
+ printk(KERN_INFO "asic3: using irq %d-%d on irq %d\n",
+ asic->irq_base, asic->irq_base + ASIC3_NR_IRQS - 1,
+ asic->irq_nr);
+
+ for (i = 0 ; i < ASIC3_NR_IRQS ; i++) {
+ int irq = i + asic->irq_base;
+ if (i < ASIC3_NR_GPIO_IRQS) {
+ set_irq_chip(irq, &asic3_gpio_irq_chip);
+ set_irq_chip_data(irq, asic);
+ set_irq_handler(irq, handle_level_irq);
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+ } else {
+ /* The remaining IRQs are not GPIO */
+ set_irq_chip(irq, &asic3_irq_chip);
+ set_irq_chip_data(irq, asic);
+ set_irq_handler(irq, handle_level_irq);
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+ }
+ }
+
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(INTR, IntMask),
+ ASIC3_INTMASK_GINTMASK);
+
+ set_irq_chained_handler(asic->irq_nr, asic3_irq_demux);
+ set_irq_type(asic->irq_nr, IRQT_RISING);
+ set_irq_data(asic->irq_nr, asic);
+ }
+
+#ifdef CONFIG_HTC_ASIC3_DS1WM
+ ds1wm_pd.bus_shift = asic->bus_shift;
+#endif
+
+ soc_add_devices(pdev, asic3_blocks, ARRAY_SIZE(asic3_blocks),
+ &pdev->resource[0],
+ asic->bus_shift - ASIC3_DEFAULT_ADDR_SHIFT,
+ asic->irq_base);
+
+ if (pdev->num_resources > 2) {
+ int rc;
+ rc = asic3_register_mmc(dev);
+ if (rc) {
+ asic3_remove(pdev);
+ return rc;
+ }
+ }
+
+ if (pdata && pdata->num_child_platform_devs != 0)
+ platform_add_devices(pdata->child_platform_devs,
+ pdata->num_child_platform_devs);
+
+ return 0;
+}
+
+static int asic3_remove(struct platform_device *pdev)
+{
+ struct asic3_platform_data *pdata = pdev->dev.platform_data;
+ struct asic3_data *asic = platform_get_drvdata(pdev);
+ int i;
+
+ if (pdata && pdata->num_child_platform_devs != 0) {
+ for (i = 0; i < pdata->num_child_platform_devs; i++) {
+ platform_device_unregister(
+ pdata->child_platform_devs[i]);
+ }
+ }
+
+ if (asic->irq_nr != -1) {
+ unsigned int i;
+
+ for (i = 0 ; i < ASIC3_NR_IRQS ; i++) {
+ int irq = i + asic->irq_base;
+ set_irq_flags(irq, 0);
+ set_irq_handler (irq, NULL);
+ set_irq_chip (irq, NULL);
+ set_irq_chip_data(irq, NULL);
+ }
+
+ set_irq_chained_handler(asic->irq_nr, NULL);
+ }
+
+ if (asic->mmc_dev)
+ asic3_unregister_mmc(&pdev->dev);
+
+ for (i = 0; i < ARRAY_SIZE(asic3_clocks); i++)
+ clk_unregister(&asic3_clocks[i]);
+ clk_unregister(&clk_g);
+
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, SEL), 0);
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(INTR, IntMask), 0);
+
+ iounmap(asic->mapping);
+
+ kfree(asic);
+
+ return 0;
+}
+
+static void asic3_shutdown(struct platform_device *pdev)
+{
+}
+
+#define ASIC3_SUSPEND_CDEX_MASK \
+ (CLOCK_CDEX_LED0 | CLOCK_CDEX_LED1 | CLOCK_CDEX_LED2)
+static unsigned short suspend_cdex;
+
+static int asic3_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct asic3_data *asic = platform_get_drvdata(pdev);
+ suspend_cdex = __asic3_read_register(asic,
+ _IPAQ_ASIC3_CLOCK_Base + _IPAQ_ASIC3_CLOCK_CDEX);
+ /* The LEDs are still active during suspend */
+ __asic3_write_register(asic,
+ _IPAQ_ASIC3_CLOCK_Base + _IPAQ_ASIC3_CLOCK_CDEX,
+ suspend_cdex & ASIC3_SUSPEND_CDEX_MASK);
+ return 0;
+}
+
+static int asic3_resume(struct platform_device *pdev)
+{
+ struct asic3_data *asic = platform_get_drvdata(pdev);
+ unsigned short intmask;
+
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, CDEX),
+ suspend_cdex);
+
+ if (asic->irq_nr != -1) {
+ /* Toggle the interrupt mask to try to get ASIC3 to show
+ * the CPU an interrupt edge. For more details see the
+ * kernel-discuss thread around 13 June 2005 with the
+ * subject "asic3 suspend / resume". */
+ intmask = __asic3_read_register(asic,
+ IPAQ_ASIC3_OFFSET(INTR, IntMask));
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(INTR, IntMask),
+ intmask & ~ASIC3_INTMASK_GINTMASK);
+ mdelay(1);
+ __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(INTR, IntMask),
+ intmask | ASIC3_INTMASK_GINTMASK);
+ }
+
+ return 0;
+}
+
+static struct platform_driver asic3_device_driver = {
+ .driver = {
+ .name = "asic3",
+ },
+ .probe = asic3_probe,
+ .remove = asic3_remove,
+ .suspend = asic3_suspend,
+ .resume = asic3_resume,
+ .shutdown = asic3_shutdown,
+};
+
+static int __init asic3_base_init(void)
+{
+ int retval = 0;
+ retval = platform_driver_register(&asic3_device_driver);
+ return retval;
+}
+
+static void __exit asic3_base_exit(void)
+{
+ platform_driver_unregister(&asic3_device_driver);
+}
+
+#ifdef MODULE
+module_init(asic3_base_init);
+#else /* start early for dependencies */
+subsys_initcall(asic3_base_init);
+#endif
+module_exit(asic3_base_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Phil Blundell <[email protected]>");
+MODULE_DESCRIPTION("Core driver for HTC ASIC3");
+MODULE_SUPPORTED_DEVICE("asic3");
diff --git a/include/linux/soc/asic3_base.h b/include/linux/soc/asic3_base.h
new file mode 100644
index 0000000..f17acda
--- /dev/null
+++ b/include/linux/soc/asic3_base.h
@@ -0,0 +1,100 @@
+#include <asm/types.h>
+
+/* Private API - for ASIC3 devices internal use only */
+#define HDR_IPAQ_ASIC3_ACTION(ACTION,action,fn,FN) \
+u32 asic3_get_gpio_ ## action ## _ ## fn (struct device *dev); \
+void asic3_set_gpio_ ## action ## _ ## fn (struct device *dev, u32 bits, u32 val);
+
+#define HDR_IPAQ_ASIC3_FN(fn,FN) \
+ HDR_IPAQ_ASIC3_ACTION ( MASK,mask,fn,FN) \
+ HDR_IPAQ_ASIC3_ACTION ( DIR, dir, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( OUT, out, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( LEVELTRI, trigtype, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( RISING, rising, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( LEVEL, triglevel, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( SLEEP_MASK, sleepmask, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( SLEEP_OUT, sleepout, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( BATT_FAULT_OUT, battfaultout, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( INT_STATUS, intstatus, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( ALT_FUNCTION, alt_fn, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( SLEEP_CONF, sleepconf, fn, FN) \
+ HDR_IPAQ_ASIC3_ACTION ( STATUS, status, fn, FN)
+
+/* Public API */
+
+#define ASIC3_GPIOA_IRQ_BASE 0
+#define ASIC3_GPIOB_IRQ_BASE 16
+#define ASIC3_GPIOC_IRQ_BASE 32
+#define ASIC3_GPIOD_IRQ_BASE 48
+#define ASIC3_LED0_IRQ 64
+#define ASIC3_LED1_IRQ 65
+#define ASIC3_LED2_IRQ 66
+#define ASIC3_SPI_IRQ 67
+#define ASIC3_SMBUS_IRQ 68
+#define ASIC3_OWM_IRQ 69
+
+#define ASIC3_NR_GPIO_IRQS 64 /* 16 bits each GPIO A...D banks */
+#define ASIC3_NR_IRQS (ASIC3_OWM_IRQ + 1)
+
+extern int asic3_irq_base(struct device *dev);
+
+extern void asic3_write_register(struct device *dev, unsigned int reg,
+ u32 value);
+extern u32 asic3_read_register(struct device *dev, unsigned int reg);
+
+/* old clock api */
+extern void asic3_set_clock_sel(struct device *dev, u32 bits, u32 val);
+extern u32 asic3_get_clock_cdex(struct device *dev);
+extern void asic3_set_clock_cdex(struct device *dev, u32 bits, u32 val);
+
+extern void asic3_set_extcf_select(struct device *dev, u32 bits, u32 val);
+extern void asic3_set_extcf_reset(struct device *dev, u32 bits, u32 val);
+extern void asic3_set_sdhwctrl(struct device *dev, u32 bits, u32 val);
+
+extern void asic3_set_led(struct device *dev, int led_num, int duty_time,
+ int cycle_time, int timebase);
+
+extern int asic3_register_mmc(struct device *dev);
+extern int asic3_unregister_mmc(struct device *dev);
+
+/* Accessors for GPIO banks */
+HDR_IPAQ_ASIC3_FN(a, A)
+HDR_IPAQ_ASIC3_FN(b, B)
+HDR_IPAQ_ASIC3_FN(c, C)
+HDR_IPAQ_ASIC3_FN(d, D)
+
+#define _IPAQ_ASIC3_GPIO_BANK_A 0
+#define _IPAQ_ASIC3_GPIO_BANK_B 1
+#define _IPAQ_ASIC3_GPIO_BANK_C 2
+#define _IPAQ_ASIC3_GPIO_BANK_D 3
+
+#define ASIC3_GPIO_bit(gpio) (1 << (gpio & 0xf))
+
+extern int asic3_get_gpio_bit(struct device *dev, int gpio);
+extern void asic3_set_gpio_bit(struct device *dev, int gpio, int val);
+extern int asic3_gpio_get_value(struct device *dev, unsigned gpio);
+extern void asic3_gpio_set_value(struct device *dev, unsigned gpio, int val);
+
+
+struct tmio_mmc_hwconfig;
+
+struct asic3_platform_data
+{
+ struct {
+ u32 dir;
+ u32 init;
+ u32 sleep_mask;
+ u32 sleep_out;
+ u32 batt_fault_out;
+ u32 sleep_conf;
+ u32 alt_function;
+ } gpio_a, gpio_b, gpio_c, gpio_d;
+
+ int irq_base;
+ unsigned int bus_shift;
+
+ struct platform_device **child_platform_devs;
+ int num_child_platform_devs;
+
+ struct tmio_mmc_hwconfig *tmio_mmc_hwconfig;
+};
diff --git a/include/linux/soc/tmio_mmc.h b/include/linux/soc/tmio_mmc.h
new file mode 100644
index 0000000..b8c407c
--- /dev/null
+++ b/include/linux/soc/tmio_mmc.h
@@ -0,0 +1,17 @@
+#include <linux/platform_device.h>
+
+#define MMC_CLOCK_DISABLED 0
+#define MMC_CLOCK_ENABLED 1
+
+#define TMIO_WP_ALWAYS_RW ((void*)-1)
+
+struct tmio_mmc_hwconfig {
+ void (*hwinit)(struct platform_device *sdev);
+ void (*set_mmc_clock)(struct platform_device *sdev, int state);
+
+ /* NULL - use ASIC3 signal,
+ TMIO_WP_ALWAYS_RW - assume always R/W (e.g. miniSD)
+ otherwise - machine-specific handler */
+ int (*mmc_get_ro)(struct platform_device *pdev);
+ short address_shift;
+};


--
Best regards,
Paul mailto:[email protected]


2007-05-01 06:58:01

by Andrew Morton

[permalink] [raw]
Subject: Re: [RFC, PATCH 3/4] SoC base drivers: ASIC3 driver

On Tue, 1 May 2007 08:09:48 +0300 Paul Sokolovsky <[email protected]> wrote:

> Hello linux-kernel,
>
> Note: This driver depends on ds1wm.h header, recently submitted, and which by now should be in -mm tree.
> -----
>
> asic3_base: SoC base driver for ASIC3 chip.
>
> Signed-off-by: Paul Sokolovsky <[email protected]>
>
> ...
>
> +
> +struct asic3_data
> +{

struct asic3_data {

> + void *mapping;
> + unsigned int bus_shift;
> + int irq_base;
> + int irq_nr;
> +
> + u16 irq_bothedge[4];
> + struct device *dev;
> +
> + struct platform_device *mmc_dev;
> +};
> +
> +static spinlock_t asic3_gpio_lock;

DEFINE_SPINLOCK(), please - it's better to do it at compile-time.

> +static int asic3_remove(struct platform_device *dev);
> +
> +static inline unsigned long asic3_address(struct device *dev,
> + unsigned int reg)
> +{
> + struct asic3_data *adata;
> +
> + adata = (struct asic3_data *)dev->driver_data;
> +
> + return (unsigned long)adata->mapping + (reg >> (2 - adata->bus_shift));
> +}
> +
> +void asic3_write_register(struct device *dev, unsigned int reg, u32 value)
> +{
> + __raw_writew(value, asic3_address(dev, reg));
> +}
> +EXPORT_SYMBOL(asic3_write_register);
> +
> +u32 asic3_read_register(struct device *dev, unsigned int reg)
> +{
> + return __raw_readw(asic3_address(dev, reg));
> +}
> +EXPORT_SYMBOL(asic3_read_register);
> +
> +static inline void __asic3_write_register(struct asic3_data *asic,
> + unsigned int reg, u32 value)
> +{
> + __raw_writew(value, (unsigned long)asic->mapping
> + + (reg >> (2 - asic->bus_shift)));
> +}
> +
> +static inline u32 __asic3_read_register(struct asic3_data *asic,
> + unsigned int reg)
> +{
> + return __raw_readw((unsigned long)asic->mapping
> + + (reg >> (2 - asic->bus_shift)));
> +}

Why __raw_*() here?

How come we're using the io.h functions here, but [patch 2/4] open-coded it?

> +#define ASIC3_GPIO_FN(get_fn_name, set_fn_name, REG) \
> +u32 get_fn_name(struct device *dev) \
> +{ \
> + return asic3_read_register(dev, REG); \
> +} \
> +EXPORT_SYMBOL(get_fn_name); \
> + \
> +void set_fn_name(struct device *dev, u32 bits, u32 val) \
> +{ \
> + unsigned long flags; \
> + \
> + spin_lock_irqsave(&asic3_gpio_lock, flags); \
> + val |= (asic3_read_register(dev, REG) & ~bits); \
> + asic3_write_register(dev, REG, val); \
> + spin_unlock_irqrestore(&asic3_gpio_lock, flags); \
> +} \
> +EXPORT_SYMBOL(set_fn_name);
> +
> +#define ASIC3_GPIO_REGISTER(ACTION, action, fn, FN) \
> + ASIC3_GPIO_FN (asic3_get_gpio_ ## action ## _ ## fn , \
> + asic3_set_gpio_ ## action ## _ ## fn , \
> + _IPAQ_ASIC3_GPIO_ ## FN ## _Base \
> + + _IPAQ_ASIC3_GPIO_ ## ACTION )
> +
> +#define ASIC3_GPIO_FUNCTIONS(fn, FN) \
> + ASIC3_GPIO_REGISTER (Direction, dir, fn, FN) \
> + ASIC3_GPIO_REGISTER (Out, out, fn, FN) \
> + ASIC3_GPIO_REGISTER (SleepMask, sleepmask, fn, FN) \
> + ASIC3_GPIO_REGISTER (SleepOut, sleepout, fn, FN) \
> + ASIC3_GPIO_REGISTER (BattFaultOut, battfaultout, fn, FN) \
> + ASIC3_GPIO_REGISTER (AltFunction, alt_fn, fn, FN) \
> + ASIC3_GPIO_REGISTER (SleepConf, sleepconf, fn, FN) \
> + ASIC3_GPIO_REGISTER (Status, status, fn, FN)
> +
> +ASIC3_GPIO_FUNCTIONS(a, A)
> +ASIC3_GPIO_FUNCTIONS(b, B)
> +ASIC3_GPIO_FUNCTIONS(c, C)
> +ASIC3_GPIO_FUNCTIONS(d, D)

Ho hum, fair enough.

Was it deliberate that get_fn_name() and set_fn_name() are given global
scope? I guess so, given that they're exported to modules.

Please remove the space between the function or macro name and the "("
(whole patchset).

> +int asic3_gpio_get_value(struct device *dev, unsigned gpio)
> +{
> + u32 mask = ASIC3_GPIO_bit(gpio);
> + printk("%s(%d)\n", __FUNCTION__, gpio);
> + switch (gpio >> 4) {
> + case _IPAQ_ASIC3_GPIO_BANK_A:
> + return asic3_get_gpio_status_a(dev) & mask;
> + case _IPAQ_ASIC3_GPIO_BANK_B:
> + return asic3_get_gpio_status_b(dev) & mask;
> + case _IPAQ_ASIC3_GPIO_BANK_C:
> + return asic3_get_gpio_status_c(dev) & mask;
> + case _IPAQ_ASIC3_GPIO_BANK_D:
> + return asic3_get_gpio_status_d(dev) & mask;
> + }
> +
> + printk(KERN_ERR "%s: invalid GPIO value 0x%x", __FUNCTION__, gpio);
> + return 0;
> +}
> +EXPORT_SYMBOL(asic3_gpio_get_value);
> +
> +void asic3_gpio_set_value(struct device *dev, unsigned gpio, int val)
> +{
> + u32 mask = ASIC3_GPIO_bit(gpio);
> + u32 bitval = 0;
> + if (val) bitval = mask;
> + printk("%s(%d, %d)\n", __FUNCTION__, gpio, val);
> +
> + switch (gpio >> 4) {
> + case _IPAQ_ASIC3_GPIO_BANK_A:
> + asic3_set_gpio_out_a(dev, mask, bitval);
> + return;
> + case _IPAQ_ASIC3_GPIO_BANK_B:
> + asic3_set_gpio_out_b(dev, mask, bitval);
> + return;
> + case _IPAQ_ASIC3_GPIO_BANK_C:
> + asic3_set_gpio_out_c(dev, mask, bitval);
> + return;
> + case _IPAQ_ASIC3_GPIO_BANK_D:
> + asic3_set_gpio_out_d(dev, mask, bitval);
> + return;
> + }
> +
> + printk(KERN_ERR "%s: invalid GPIO value 0x%x", __FUNCTION__, gpio);
> +}
> +EXPORT_SYMBOL(asic3_gpio_set_value);

I assume all these debugging printks won't last long.

> +int asic3_irq_base(struct device *dev)
> +{
> + struct asic3_data *asic = dev->driver_data;
> +
> + return asic->irq_base;
> +}
> +EXPORT_SYMBOL(asic3_irq_base);
> +
> +void asic3_set_led(struct device *dev, int led_num, int duty_time,
> + int cycle_time, int timebase)
> +{
> + struct asic3_data *asic = dev->driver_data;
> + unsigned int led_base;
> +
> + /* it's a macro thing: see #define _IPAQ_ASIC_LED_0_Base for why you
> + * can't substitute led_num in the macros below...
> + */
> +
> + switch (led_num) {
> + case 0:
> + led_base = _IPAQ_ASIC3_LED_0_Base;
> + break;
> + case 1:
> + led_base = _IPAQ_ASIC3_LED_1_Base;
> + break;
> + case 2:
> + led_base = _IPAQ_ASIC3_LED_2_Base;
> + break;
> + default:
> + printk(KERN_ERR "%s: invalid led number %d", __FUNCTION__,
> + led_num);
> + return;
> + }
> +
> + __asic3_write_register(asic, led_base + _IPAQ_ASIC3_LED_TimeBase,
> + timebase | LED_EN);
> + __asic3_write_register(asic, led_base + _IPAQ_ASIC3_LED_PeriodTime,
> + cycle_time);
> + __asic3_write_register(asic, led_base + _IPAQ_ASIC3_LED_DutyTime,
> + 0);
> + udelay(20); /* asic voodoo - possibly need a whole duty cycle? */
> + __asic3_write_register(asic, led_base + _IPAQ_ASIC3_LED_DutyTime,
> + duty_time);
> +}
> +
> +EXPORT_SYMBOL(asic3_set_led);

Remove the line before EXPORT_SYMBOL().

> +void asic3_set_clock_sel(struct device *dev, u32 bits, u32 val)
> +{
> + struct asic3_data *asic = dev->driver_data;
> + unsigned long flags;
> + u32 v;
> +
> + spin_lock_irqsave(&asic3_gpio_lock, flags);
> + v = __asic3_read_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, SEL));
> + v = (v & ~bits) | val;
> + __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, SEL), v);
> + spin_unlock_irqrestore(&asic3_gpio_lock, flags);
> +}
> +EXPORT_SYMBOL(asic3_set_clock_sel);
> +
> +void asic3_set_clock_cdex(struct device *dev, u32 bits, u32 val)
> +{
> + struct asic3_data *asic = dev->driver_data;
> + unsigned long flags;
> + u32 v;
> +
> + spin_lock_irqsave(&asic3_gpio_lock, flags);
> + v = __asic3_read_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, CDEX));
> + v = (v & ~bits) | val;
> + __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, CDEX), v);
> + spin_unlock_irqrestore(&asic3_gpio_lock, flags);
> +}
> +EXPORT_SYMBOL(asic3_set_clock_cdex);
> +
> +static int asic3_clock_cdex_enable(struct clk *clk, int enable)
> +{
> + struct asic3_data *asic = (struct asic3_data *)clk->parent->ctrlbit;
> + unsigned long flags, val;
> +
> + local_irq_save(flags);
> +
> + val = __asic3_read_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, CDEX));
> + if (enable)
> + val |= clk->ctrlbit;
> + else
> + val &= ~clk->ctrlbit;
> + __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, CDEX), val);
> +
> + local_irq_restore(flags);
> +
> + return 0;
> +}

How come asic3_clock_cdex_enable() uses local_irq_save() but the
similar-looking functions above use spin_lock_irqsave()?

> +
> +#define MAX_ASIC_ISR_LOOPS 20
> +#define _IPAQ_ASIC3_GPIO_Base_INCR \
> + (_IPAQ_ASIC3_GPIO_B_Base - _IPAQ_ASIC3_GPIO_A_Base)
> +
> +static inline void asic3_irq_flip_edge(struct asic3_data *asic,
> + u32 base, int bit)
> +{
> + u16 edge = __asic3_read_register(asic,
> + base + _IPAQ_ASIC3_GPIO_EdgeTrigger);
> + edge ^= bit;
> + __asic3_write_register(asic,
> + base + _IPAQ_ASIC3_GPIO_EdgeTrigger, edge);
> +}

This function doesn't need the spinlock?

> +static void asic3_irq_demux(unsigned int irq, struct irq_desc *desc)
> +{
> + int iter;
> + struct asic3_data *asic;
> +
> + /* Acknowledge the parrent (i.e. CPU's) IRQ */
> + desc->chip->ack(irq);
> +
> + asic = desc->handler_data;
> +
> + /* printk( KERN_NOTICE "asic3_irq_demux: irq=%d\n", irq ); */
> + for (iter = 0 ; iter < MAX_ASIC_ISR_LOOPS; iter++) {
> + u32 status;
> + int bank;
> +
> + status = __asic3_read_register(asic,
> + IPAQ_ASIC3_OFFSET(INTR, PIntStat));
> + /* Check all ten register bits */
> + if ((status & 0x3ff) == 0)
> + break;
> +
> + /* Handle GPIO IRQs */
> + for (bank = 0; bank < 4; bank++) {
> + if (status & (1 << bank)) {
> + unsigned long base, i, istat;
> +
> + base = _IPAQ_ASIC3_GPIO_A_Base
> + + bank * _IPAQ_ASIC3_GPIO_Base_INCR;
> + istat = __asic3_read_register(asic,
> + base + _IPAQ_ASIC3_GPIO_IntStatus);
> + /* IntStatus is write 0 to clear */
> + /* XXX could miss interrupts! */
> + __asic3_write_register(asic,
> + base + _IPAQ_ASIC3_GPIO_IntStatus, 0);

And neither does this?

> + for (i = 0; i < 16; i++) {

I hope the magical 16 is meaningful to those who are familiar with the
hardware.

> + int bit = (1 << i);
> + unsigned int irqnr;
> + if (!(istat & bit))
> + continue;
> +
> + irqnr = asic->irq_base
> + + (16 * bank) + i;
> + desc = irq_desc + irqnr;
> + desc->handle_irq(irqnr, desc);
> + if (asic->irq_bothedge[bank] & bit) {
> + asic3_irq_flip_edge(asic, base,
> + bit);
> + }
> + }
> + }
> + }
> +
> + /* Handle remaining IRQs in the status register */
> + {
> + int i;
> +
> + for (i = ASIC3_LED0_IRQ; i <= ASIC3_OWM_IRQ; i++) {
> + /* They start at bit 4 and go up */
> + if (status & (1 << (i - ASIC3_LED0_IRQ + 4))) {
> + desc = irq_desc + asic->irq_base + i;
> + desc->handle_irq(asic->irq_base + i,
> + desc);
> + }
> + }
> + }
> +
> + }
> +
> + if (iter >= MAX_ASIC_ISR_LOOPS)
> + printk(KERN_ERR "%s: interrupt processing overrun\n",
> + __FUNCTION__);
> +}
> +
> +static inline int asic3_irq_to_bank(struct asic3_data *asic, int irq)
> +{
> + int n;
> +
> + n = (irq - asic->irq_base) >> 4;
> +
> + return (n * (_IPAQ_ASIC3_GPIO_B_Base - _IPAQ_ASIC3_GPIO_A_Base));
> +}
> +
> +static inline int asic3_irq_to_index(struct asic3_data *asic, int irq)
> +{
> + return (irq - asic->irq_base) & 15;
> +}
> +
> +static void asic3_mask_gpio_irq(unsigned int irq)
> +{
> + struct asic3_data *asic = get_irq_chip_data(irq);
> + u32 val, bank, index;
> + unsigned long flags;
> +
> + bank = asic3_irq_to_bank(asic, irq);
> + index = asic3_irq_to_index(asic, irq);
> +
> + spin_lock_irqsave(&asic3_gpio_lock, flags);
> + val = __asic3_read_register(asic, bank + _IPAQ_ASIC3_GPIO_Mask);
> + val |= 1 << index;
> + __asic3_write_register(asic, bank + _IPAQ_ASIC3_GPIO_Mask, val);
> + spin_unlock_irqrestore(&asic3_gpio_lock, flags);
> +}

Locked.

> +static void asic3_mask_irq(unsigned int irq)
> +{
> + struct asic3_data *asic = get_irq_chip_data(irq);
> + int regval;
> +
> + if (irq < ASIC3_NR_GPIO_IRQS) {
> + printk(KERN_ERR "asic3_base: gpio mask attempt, irq %d\n",
> + irq);
> + return;
> + }
> +
> + regval = __asic3_read_register(asic,
> + _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask);
> +
> + switch (irq - asic->irq_base) {
> + case ASIC3_LED0_IRQ:
> + __asic3_write_register(asic,
> + _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
> + regval & ~ASIC3_INTMASK_MASK0);
> + break;
> + case ASIC3_LED1_IRQ:
> + __asic3_write_register(asic,
> + _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
> + regval & ~ASIC3_INTMASK_MASK1);
> + break;
> + case ASIC3_LED2_IRQ:
> + __asic3_write_register(asic,
> + _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
> + regval & ~ASIC3_INTMASK_MASK2);
> + break;
> + case ASIC3_SPI_IRQ:
> + __asic3_write_register(asic,
> + _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
> + regval & ~ASIC3_INTMASK_MASK3);
> + break;
> + case ASIC3_SMBUS_IRQ:
> + __asic3_write_register(asic,
> + _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
> + regval & ~ASIC3_INTMASK_MASK4);
> + break;
> + case ASIC3_OWM_IRQ:
> + __asic3_write_register(asic,
> + _IPAQ_ASIC3_INTR_Base + _IPAQ_ASIC3_INTR_IntMask,
> + regval & ~ASIC3_INTMASK_MASK5);
> + break;
> + default:
> + printk(KERN_ERR "asic3_base: bad non-gpio irq %d\n", irq);
> + break;
> + }
> +}

Not locked!

Please add a comment to asic3_gpio_lock identifying what resource(s) it
protects.

> +static void asic3_unmask_gpio_irq(unsigned int irq)

sticky space bar.

> +{
> + struct asic3_data *asic = get_irq_chip_data(irq);
> + u32 val, bank, index;
> + unsigned long flags;
> +
> + bank = asic3_irq_to_bank(asic, irq);
> + index = asic3_irq_to_index(asic, irq);
> +
> + spin_lock_irqsave(&asic3_gpio_lock, flags);
> + val = __asic3_read_register(asic, bank + _IPAQ_ASIC3_GPIO_Mask);
> + val &= ~(1 << index);
> + __asic3_write_register(asic, bank + _IPAQ_ASIC3_GPIO_Mask, val);
> + spin_unlock_irqrestore(&asic3_gpio_lock, flags);
> +}
>
> ...
>
> +static int asic3_gpio_irq_type(unsigned int irq, unsigned int type)
> +{
> + struct asic3_data *asic = get_irq_chip_data(irq);
> + u32 bank, index;
> + unsigned long flags;
> + u16 trigger, level, edge, bit;
> +
> + bank = asic3_irq_to_bank(asic, irq);
> + index = asic3_irq_to_index(asic, irq);
> + bit = 1<<index;
> +
> + spin_lock_irqsave(&asic3_gpio_lock, flags);
> + level = __asic3_read_register(asic,
> + bank + _IPAQ_ASIC3_GPIO_LevelTrigger);
> + edge = __asic3_read_register(asic,
> + bank + _IPAQ_ASIC3_GPIO_EdgeTrigger);
> + trigger = __asic3_read_register(asic,
> + bank + _IPAQ_ASIC3_GPIO_TriggerType);
> + asic->irq_bothedge[(irq - asic->irq_base) >> 4] &= ~bit;
> +
> + if (type == IRQT_RISING) {
> + trigger |= bit;
> + edge |= bit;
> + } else if (type == IRQT_FALLING) {
> + trigger |= bit;
> + edge &= ~bit;
> + } else if (type == IRQT_BOTHEDGE) {
> + trigger |= bit;
> + if (asic3_gpio_get_value(asic->dev, irq - asic->irq_base))
> + edge &= ~bit;
> + else
> + edge |= bit;
> + asic->irq_bothedge[(irq - asic->irq_base) >> 4] |= bit;
> + } else if (type == IRQT_LOW) {
> + trigger &= ~bit;
> + level &= ~bit;
> + } else if (type == IRQT_HIGH) {
> + trigger &= ~bit;
> + level |= bit;
> + } else {
> + /*
> + * if type == IRQT_NOEDGE, we should mask interrupts, but
> + * be careful to not unmask them if mask was also called.
> + * Probably need internal state for mask.
> + */
> + printk(KERN_NOTICE "asic3: irq type not changed.\n");
> + }
> + __asic3_write_register(asic, bank + _IPAQ_ASIC3_GPIO_LevelTrigger,
> + level);
> + __asic3_write_register(asic, bank + _IPAQ_ASIC3_GPIO_EdgeTrigger,
> + edge);
> + __asic3_write_register(asic, bank + _IPAQ_ASIC3_GPIO_TriggerType,
> + trigger);
> + spin_unlock_irqrestore(&asic3_gpio_lock, flags);
> + return 0;
> +}

Locking here looks good.

> +static struct irq_chip asic3_gpio_irq_chip = {
> + .name = "ASIC3-GPIO",
> + .ack = asic3_mask_gpio_irq,
> + .mask = asic3_mask_gpio_irq,
> + .unmask = asic3_unmask_gpio_irq,
> + .set_type = asic3_gpio_irq_type,
> +};
> +
> +static struct irq_chip asic3_irq_chip = {
> + .name = "ASIC3",
> + .ack = asic3_mask_irq,
> + .mask = asic3_mask_irq,
> + .unmask = asic3_unmask_irq,
> +};
> +
> +static void asic3_release(struct device *dev)
> +{
> + struct platform_device *sdev = to_platform_device(dev);
> +
> + kfree(sdev->resource);
> + kfree(sdev);
> +}
> +
> +int asic3_register_mmc(struct device *dev)
> +{
> + struct platform_device *sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
> + struct tmio_mmc_hwconfig *mmc_config = kmalloc(sizeof(*mmc_config),
> + GFP_KERNEL);
> + struct platform_device *pdev = to_platform_device(dev);
> + struct asic3_data *asic = dev->driver_data;
> + struct asic3_platform_data *asic3_pdata = dev->platform_data;
> + struct resource *res;
> + int rc;
> +
> + if (sdev == NULL || mmc_config == NULL)
> + return -ENOMEM;

That'll leak *sdev if *mmc_config==NULL.

> + if (asic3_pdata->tmio_mmc_hwconfig) {
> + memcpy(mmc_config, asic3_pdata->tmio_mmc_hwconfig,
> + sizeof(*mmc_config));
> + } else {
> + memset(mmc_config, 0, sizeof(*mmc_config));
> + }
> + mmc_config->address_shift = asic->bus_shift;
> +
> + sdev->id = -1;
> + sdev->name = "asic3_mmc";
> + sdev->dev.parent = dev;
> + sdev->num_resources = 2;
> + sdev->dev.platform_data = mmc_config;
> + sdev->dev.release = asic3_release;
> +
> + res = kzalloc(sdev->num_resources * sizeof(struct resource),
> + GFP_KERNEL);
> + if (res == NULL) {
> + kfree(sdev);
> + kfree(mmc_config);
> + return -ENOMEM;
> + }
> + sdev->resource = res;
> +
> + res[0].start = pdev->resource[2].start;
> + res[0].end = pdev->resource[2].end;
> + res[0].flags = IORESOURCE_MEM;
> + res[1].start = res[1].end = pdev->resource[3].start;
> + res[1].flags = IORESOURCE_IRQ;
> +
> + rc = platform_device_register(sdev);
> + if (rc) {
> + printk(KERN_ERR "asic3_base: "
> + "Could not register asic3_mmc device\n");
> + kfree(res);
> + kfree(sdev);

kfree(mmc_config); ?

> + return rc;
> + }
> +
> + asic->mmc_dev = sdev;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(asic3_register_mmc);
> +
> +int asic3_unregister_mmc(struct device *dev)
> +{
> + struct asic3_data *asic = dev->driver_data;
> + platform_device_unregister(asic->mmc_dev);
> + asic->mmc_dev = 0;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(asic3_unregister_mmc);
> +
>
> ...
>
> + for (i = 0 ; i < ASIC3_NR_IRQS ; i++) {

Use
for (i = 0; i < ASIC3_NR_IRQS; i++) {

> + for (i = 0 ; i < ASIC3_NR_IRQS ; i++) {

Ditto (check all patches) (soon we'll have a script to do this) (hopefully)

> + int irq = i + asic->irq_base;
> + set_irq_flags(irq, 0);
> + set_irq_handler (irq, NULL);
> + set_irq_chip (irq, NULL);
> + set_irq_chip_data(irq, NULL);
> + }
> +
> + set_irq_chained_handler(asic->irq_nr, NULL);
> + }
> +
> + if (asic->mmc_dev)
> + asic3_unregister_mmc(&pdev->dev);
> +
> + for (i = 0; i < ARRAY_SIZE(asic3_clocks); i++)
> + clk_unregister(&asic3_clocks[i]);
> + clk_unregister(&clk_g);
> +
> + __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, SEL), 0);
> + __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(INTR, IntMask), 0);
> +
> + iounmap(asic->mapping);
> +
> + kfree(asic);
> +
> + return 0;
> +}
>
> ...
>
> +static int asic3_suspend(struct platform_device *pdev, pm_message_t state)
> +{
> + struct asic3_data *asic = platform_get_drvdata(pdev);
> + suspend_cdex = __asic3_read_register(asic,
> + _IPAQ_ASIC3_CLOCK_Base + _IPAQ_ASIC3_CLOCK_CDEX);
> + /* The LEDs are still active during suspend */
> + __asic3_write_register(asic,
> + _IPAQ_ASIC3_CLOCK_Base + _IPAQ_ASIC3_CLOCK_CDEX,
> + suspend_cdex & ASIC3_SUSPEND_CDEX_MASK);
> + return 0;
> +}
> +
> +static int asic3_resume(struct platform_device *pdev)
> +{
> + struct asic3_data *asic = platform_get_drvdata(pdev);
> + unsigned short intmask;
> +
> + __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(CLOCK, CDEX),
> + suspend_cdex);
> +
> + if (asic->irq_nr != -1) {
> + /* Toggle the interrupt mask to try to get ASIC3 to show
> + * the CPU an interrupt edge. For more details see the
> + * kernel-discuss thread around 13 June 2005 with the
> + * subject "asic3 suspend / resume". */
> + intmask = __asic3_read_register(asic,
> + IPAQ_ASIC3_OFFSET(INTR, IntMask));
> + __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(INTR, IntMask),
> + intmask & ~ASIC3_INTMASK_GINTMASK);
> + mdelay(1);
> + __asic3_write_register(asic, IPAQ_ASIC3_OFFSET(INTR, IntMask),
> + intmask | ASIC3_INTMASK_GINTMASK);
> + }
> +
> + return 0;
> +}
> +
> +static struct platform_driver asic3_device_driver = {
> + .driver = {
> + .name = "asic3",
> + },
> + .probe = asic3_probe,
> + .remove = asic3_remove,

Should .remove be __devexit_p()?

> + .suspend = asic3_suspend,
> + .resume = asic3_resume,
> + .shutdown = asic3_shutdown,
> +};

Does this driver have a Kconfig dependency upon CONFIG_PM?

If not, you should support CONFIG_PM=n. The typical way of doing that is

#ifdef CONFIG_PM
static int asic3_suspend(struct platform_device *pdev, pm_message_t state)
{
...
}

static int asic3_resume(struct platform_device *pdev)
{
...
}
#else
#define asic3_suspend NULL
#define asic3_resume NULL
#endif

> +static int __init asic3_base_init(void)
> +{
> + int retval = 0;
> + retval = platform_driver_register(&asic3_device_driver);
> + return retval;
> +}
> +
> +static void __exit asic3_base_exit(void)
> +{
> + platform_driver_unregister(&asic3_device_driver);
> +}
> +
> +#ifdef MODULE
> +module_init(asic3_base_init);
> +#else /* start early for dependencies */
> +subsys_initcall(asic3_base_init);
> +#endif

hm, I'd expect that subsys_initcall() from within a module will do the
right thing, in which case the ifdef isn't needed.

I certainly hope that's the case.

> +module_exit(asic3_base_exit);
>
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Phil Blundell <[email protected]>");
> +MODULE_DESCRIPTION("Core driver for HTC ASIC3");
> +MODULE_SUPPORTED_DEVICE("asic3");
> diff --git a/include/linux/soc/asic3_base.h b/include/linux/soc/asic3_base.h
> new file mode 100644
> index 0000000..f17acda
> --- /dev/null
> +++ b/include/linux/soc/asic3_base.h
> @@ -0,0 +1,100 @@
> +#include <asm/types.h>
> +
> +/* Private API - for ASIC3 devices internal use only */
> +#define HDR_IPAQ_ASIC3_ACTION(ACTION,action,fn,FN) \
> +u32 asic3_get_gpio_ ## action ## _ ## fn (struct device *dev); \
> +void asic3_set_gpio_ ## action ## _ ## fn (struct device *dev, u32 bits, u32 val);
> +
> +#define HDR_IPAQ_ASIC3_FN(fn,FN) \
> + HDR_IPAQ_ASIC3_ACTION ( MASK,mask,fn,FN) \
> + HDR_IPAQ_ASIC3_ACTION ( DIR, dir, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( OUT, out, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( LEVELTRI, trigtype, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( RISING, rising, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( LEVEL, triglevel, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( SLEEP_MASK, sleepmask, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( SLEEP_OUT, sleepout, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( BATT_FAULT_OUT, battfaultout, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( INT_STATUS, intstatus, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( ALT_FUNCTION, alt_fn, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( SLEEP_CONF, sleepconf, fn, FN) \
> + HDR_IPAQ_ASIC3_ACTION ( STATUS, status, fn, FN)

s/ (/(/g

> +struct tmio_mmc_hwconfig;
> +
> +struct asic3_platform_data
> +{

struct asic3_platform_data {

(review whole patchset)

> + struct {
> + u32 dir;
> + u32 init;
> + u32 sleep_mask;
> + u32 sleep_out;
> + u32 batt_fault_out;
> + u32 sleep_conf;
> + u32 alt_function;
> + } gpio_a, gpio_b, gpio_c, gpio_d;
> +
> + int irq_base;
> + unsigned int bus_shift;
> +
> + struct platform_device **child_platform_devs;
> + int num_child_platform_devs;
> +
> + struct tmio_mmc_hwconfig *tmio_mmc_hwconfig;
> +};
> diff --git a/include/linux/soc/tmio_mmc.h b/include/linux/soc/tmio_mmc.h
> new file mode 100644
> index 0000000..b8c407c
> --- /dev/null
> +++ b/include/linux/soc/tmio_mmc.h
> @@ -0,0 +1,17 @@
> +#include <linux/platform_device.h>
> +
> +#define MMC_CLOCK_DISABLED 0
> +#define MMC_CLOCK_ENABLED 1
> +
> +#define TMIO_WP_ALWAYS_RW ((void*)-1)
> +
> +struct tmio_mmc_hwconfig {
> + void (*hwinit)(struct platform_device *sdev);
> + void (*set_mmc_clock)(struct platform_device *sdev, int state);
> +
> + /* NULL - use ASIC3 signal,
> + TMIO_WP_ALWAYS_RW - assume always R/W (e.g. miniSD)
> + otherwise - machine-specific handler */
> + int (*mmc_get_ro)(struct platform_device *pdev);
> + short address_shift;
> +};

2007-05-01 21:31:15

by Russell King

[permalink] [raw]
Subject: Re: [RFC, PATCH 3/4] SoC base drivers: ASIC3 driver

On Tue, May 01, 2007 at 08:09:48AM +0300, Paul Sokolovsky wrote:
> +static void asic3_release(struct device *dev)
> +{
> + struct platform_device *sdev = to_platform_device(dev);
> +
> + kfree(sdev->resource);
> + kfree(sdev);
> +}
> +
> +int asic3_register_mmc(struct device *dev)
> +{
> + struct platform_device *sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
> + struct tmio_mmc_hwconfig *mmc_config = kmalloc(sizeof(*mmc_config),
> + GFP_KERNEL);
> + struct platform_device *pdev = to_platform_device(dev);
> + struct asic3_data *asic = dev->driver_data;
> + struct asic3_platform_data *asic3_pdata = dev->platform_data;
> + struct resource *res;
> + int rc;
> +
> + if (sdev == NULL || mmc_config == NULL)
> + return -ENOMEM;
> +
> + if (asic3_pdata->tmio_mmc_hwconfig) {
> + memcpy(mmc_config, asic3_pdata->tmio_mmc_hwconfig,
> + sizeof(*mmc_config));
> + } else {
> + memset(mmc_config, 0, sizeof(*mmc_config));
> + }
> + mmc_config->address_shift = asic->bus_shift;
> +
> + sdev->id = -1;
> + sdev->name = "asic3_mmc";
> + sdev->dev.parent = dev;
> + sdev->num_resources = 2;
> + sdev->dev.platform_data = mmc_config;
> + sdev->dev.release = asic3_release;
> +
> + res = kzalloc(sdev->num_resources * sizeof(struct resource),
> + GFP_KERNEL);
> + if (res == NULL) {
> + kfree(sdev);
> + kfree(mmc_config);
> + return -ENOMEM;
> + }
> + sdev->resource = res;
> +
> + res[0].start = pdev->resource[2].start;
> + res[0].end = pdev->resource[2].end;
> + res[0].flags = IORESOURCE_MEM;
> + res[1].start = res[1].end = pdev->resource[3].start;
> + res[1].flags = IORESOURCE_IRQ;
> +
> + rc = platform_device_register(sdev);
> + if (rc) {
> + printk(KERN_ERR "asic3_base: "
> + "Could not register asic3_mmc device\n");
> + kfree(res);
> + kfree(sdev);
> + return rc;
> + }
> +
> + asic->mmc_dev = sdev;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(asic3_register_mmc);
> +
> +int asic3_unregister_mmc(struct device *dev)
> +{
> + struct asic3_data *asic = dev->driver_data;
> + platform_device_unregister(asic->mmc_dev);
> + asic->mmc_dev = 0;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(asic3_unregister_mmc);
...
> +static int asic3_probe(struct platform_device *pdev)
> +{
...
> + if (pdev->num_resources > 2) {
> + int rc;
> + rc = asic3_register_mmc(dev);
...
> + return 0;
> +}
> +
> +static int asic3_remove(struct platform_device *pdev)
> +{
> + struct asic3_platform_data *pdata = pdev->dev.platform_data;
> + struct asic3_data *asic = platform_get_drvdata(pdev);
> + int i;
...
> + if (asic->mmc_dev)
> + asic3_unregister_mmc(&pdev->dev);
...
> + return 0;
> +}
...
> +static struct platform_driver asic3_device_driver = {
> + .driver = {
> + .name = "asic3",
> + },
> + .probe = asic3_probe,
> + .remove = asic3_remove,
> + .suspend = asic3_suspend,
> + .resume = asic3_resume,
> + .shutdown = asic3_shutdown,
> +};
...
> +static void __exit asic3_base_exit(void)
> +{
> + platform_driver_unregister(&asic3_device_driver);
> +}
...
> +module_exit(asic3_base_exit);

What stops the remove function from being called after the module
has been removed from the system?

Again, with platform devices you _really_ want to use the already
provided dynamic allocation methods precisely because they don't
suffer from these problems.

--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: