* Symbols in this file are called "nmk_gpio" for "nomadik gpio"
*/
-static const u32 backup_regs[] = {
- NMK_GPIO_PDIS,
- NMK_GPIO_DIR,
- NMK_GPIO_AFSLA,
- NMK_GPIO_AFSLB,
- NMK_GPIO_SLPC,
- NMK_GPIO_RIMSC,
- NMK_GPIO_FIMSC,
- NMK_GPIO_RWIMSC,
- NMK_GPIO_FWIMSC,
-};
+#define NMK_GPIO_PER_CHIP 32
struct nmk_gpio_chip {
struct gpio_chip chip;
unsigned int parent_irq;
int secondary_parent_irq;
u32 (*get_secondary_status)(unsigned int bank);
+ void (*set_ioforce)(bool enable);
spinlock_t lock;
/* Keep track of configured edges */
u32 edge_rising;
u32 edge_falling;
- u32 backup[ARRAY_SIZE(backup_regs)];
- /* Bitmap, 1 = pull up, 0 = pull down */
- u32 pull;
+ u32 real_wake;
+ u32 rwimsc;
+ u32 fwimsc;
+ u32 slpm;
};
+static struct nmk_gpio_chip *
+nmk_gpio_chips[DIV_ROUND_UP(ARCH_NR_GPIOS, NMK_GPIO_PER_CHIP)];
+
+static DEFINE_SPINLOCK(nmk_gpio_slpm_lock);
+
+#define NUM_BANKS ARRAY_SIZE(nmk_gpio_chips)
+
static void __nmk_gpio_set_mode(struct nmk_gpio_chip *nmk_chip,
unsigned offset, int gpio_mode)
{
pdis &= ~bit;
writel(pdis, nmk_chip->addr + NMK_GPIO_PDIS);
- if (pull == NMK_GPIO_PULL_UP) {
- nmk_chip->pull |= bit;
+ if (pull == NMK_GPIO_PULL_UP)
writel(bit, nmk_chip->addr + NMK_GPIO_DATS);
- } else if (pull == NMK_GPIO_PULL_DOWN) {
- nmk_chip->pull &= ~bit;
+ else if (pull == NMK_GPIO_PULL_DOWN)
writel(bit, nmk_chip->addr + NMK_GPIO_DATC);
- }
}
static void __nmk_gpio_make_input(struct nmk_gpio_chip *nmk_chip,
__nmk_gpio_set_output(nmk_chip, offset, val);
}
+static void __nmk_gpio_set_mode_safe(struct nmk_gpio_chip *nmk_chip,
+ unsigned offset, int gpio_mode,
+ bool glitch)
+{
+ u32 rwimsc = readl(nmk_chip->addr + NMK_GPIO_RWIMSC);
+ u32 fwimsc = readl(nmk_chip->addr + NMK_GPIO_FWIMSC);
+
+ if (glitch && nmk_chip->set_ioforce) {
+ u32 bit = BIT(offset);
+
+ /* Prevent spurious wakeups */
+ writel(rwimsc & ~bit, nmk_chip->addr + NMK_GPIO_RWIMSC);
+ writel(fwimsc & ~bit, nmk_chip->addr + NMK_GPIO_FWIMSC);
+
+ nmk_chip->set_ioforce(true);
+ }
+
+ __nmk_gpio_set_mode(nmk_chip, offset, gpio_mode);
+
+ if (glitch && nmk_chip->set_ioforce) {
+ nmk_chip->set_ioforce(false);
+
+ writel(rwimsc, nmk_chip->addr + NMK_GPIO_RWIMSC);
+ writel(fwimsc, nmk_chip->addr + NMK_GPIO_FWIMSC);
+ }
+}
+
static void __nmk_config_pin(struct nmk_gpio_chip *nmk_chip, unsigned offset,
- pin_cfg_t cfg, bool sleep)
+ pin_cfg_t cfg, bool sleep, unsigned int *slpmregs)
{
static const char *afnames[] = {
[NMK_GPIO_ALT_GPIO] = "GPIO",
int slpm = PIN_SLPM(cfg);
int output = PIN_DIR(cfg);
int val = PIN_VAL(cfg);
+ bool glitch = af == NMK_GPIO_ALT_C;
dev_dbg(nmk_chip->chip.dev, "pin %d [%#lx]: af %s, pull %s, slpm %s (%s%s)\n",
pin, cfg, afnames[af], pullnames[pull], slpmnames[slpm],
__nmk_gpio_set_pull(nmk_chip, offset, pull);
}
- __nmk_gpio_set_slpm(nmk_chip, offset, slpm);
- __nmk_gpio_set_mode(nmk_chip, offset, af);
+ /*
+ * If we've backed up the SLPM registers (glitch workaround), modify
+ * the backups since they will be restored.
+ */
+ if (slpmregs) {
+ if (slpm == NMK_GPIO_SLPM_NOCHANGE)
+ slpmregs[nmk_chip->bank] |= BIT(offset);
+ else
+ slpmregs[nmk_chip->bank] &= ~BIT(offset);
+ } else
+ __nmk_gpio_set_slpm(nmk_chip, offset, slpm);
+
+ __nmk_gpio_set_mode_safe(nmk_chip, offset, af, glitch);
+}
+
+/*
+ * Safe sequence used to switch IOs between GPIO and Alternate-C mode:
+ * - Save SLPM registers
+ * - Set SLPM=0 for the IOs you want to switch and others to 1
+ * - Configure the GPIO registers for the IOs that are being switched
+ * - Set IOFORCE=1
+ * - Modify the AFLSA/B registers for the IOs that are being switched
+ * - Set IOFORCE=0
+ * - Restore SLPM registers
+ * - Any spurious wake up event during switch sequence to be ignored and
+ * cleared
+ */
+static void nmk_gpio_glitch_slpm_init(unsigned int *slpm)
+{
+ int i;
+
+ for (i = 0; i < NUM_BANKS; i++) {
+ struct nmk_gpio_chip *chip = nmk_gpio_chips[i];
+ unsigned int temp = slpm[i];
+
+ if (!chip)
+ break;
+
+ slpm[i] = readl(chip->addr + NMK_GPIO_SLPC);
+ writel(temp, chip->addr + NMK_GPIO_SLPC);
+ }
+}
+
+static void nmk_gpio_glitch_slpm_restore(unsigned int *slpm)
+{
+ int i;
+
+ for (i = 0; i < NUM_BANKS; i++) {
+ struct nmk_gpio_chip *chip = nmk_gpio_chips[i];
+
+ if (!chip)
+ break;
+
+ writel(slpm[i], chip->addr + NMK_GPIO_SLPC);
+ }
+}
+
+static int __nmk_config_pins(pin_cfg_t *cfgs, int num, bool sleep)
+{
+ static unsigned int slpm[NUM_BANKS];
+ unsigned long flags;
+ bool glitch = false;
+ int ret = 0;
+ int i;
+
+ for (i = 0; i < num; i++) {
+ if (PIN_ALT(cfgs[i]) == NMK_GPIO_ALT_C) {
+ glitch = true;
+ break;
+ }
+ }
+
+ spin_lock_irqsave(&nmk_gpio_slpm_lock, flags);
+
+ if (glitch) {
+ memset(slpm, 0xff, sizeof(slpm));
+
+ for (i = 0; i < num; i++) {
+ int pin = PIN_NUM(cfgs[i]);
+ int offset = pin % NMK_GPIO_PER_CHIP;
+
+ if (PIN_ALT(cfgs[i]) == NMK_GPIO_ALT_C)
+ slpm[pin / NMK_GPIO_PER_CHIP] &= ~BIT(offset);
+ }
+
+ nmk_gpio_glitch_slpm_init(slpm);
+ }
+
+ for (i = 0; i < num; i++) {
+ struct nmk_gpio_chip *nmk_chip;
+ int pin = PIN_NUM(cfgs[i]);
+
+ nmk_chip = get_irq_chip_data(NOMADIK_GPIO_TO_IRQ(pin));
+ if (!nmk_chip) {
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_lock(&nmk_chip->lock);
+ __nmk_config_pin(nmk_chip, pin - nmk_chip->chip.base,
+ cfgs[i], sleep, glitch ? slpm : NULL);
+ spin_unlock(&nmk_chip->lock);
+ }
+
+ if (glitch)
+ nmk_gpio_glitch_slpm_restore(slpm);
+
+ spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags);
+
+ return ret;
}
/**
*/
int nmk_config_pin(pin_cfg_t cfg, bool sleep)
{
- struct nmk_gpio_chip *nmk_chip;
- int gpio = PIN_NUM(cfg);
- unsigned long flags;
-
- nmk_chip = get_irq_chip_data(NOMADIK_GPIO_TO_IRQ(gpio));
- if (!nmk_chip)
- return -EINVAL;
-
- spin_lock_irqsave(&nmk_chip->lock, flags);
- __nmk_config_pin(nmk_chip, gpio - nmk_chip->chip.base, cfg, sleep);
- spin_unlock_irqrestore(&nmk_chip->lock, flags);
-
- return 0;
+ return __nmk_config_pins(&cfg, 1, sleep);
}
EXPORT_SYMBOL(nmk_config_pin);
*/
int nmk_config_pins(pin_cfg_t *cfgs, int num)
{
- int ret = 0;
- int i;
-
- for (i = 0; i < num; i++) {
- ret = nmk_config_pin(cfgs[i], false);
- if (ret)
- break;
- }
-
- return ret;
+ return __nmk_config_pins(cfgs, num, false);
}
EXPORT_SYMBOL(nmk_config_pins);
int nmk_config_pins_sleep(pin_cfg_t *cfgs, int num)
{
- int ret = 0;
- int i;
-
- for (i = 0; i < num; i++) {
- ret = nmk_config_pin(cfgs[i], true);
- if (ret)
- break;
- }
-
- return ret;
+ return __nmk_config_pins(cfgs, num, true);
}
EXPORT_SYMBOL(nmk_config_pins_sleep);
if (!nmk_chip)
return -EINVAL;
- spin_lock_irqsave(&nmk_chip->lock, flags);
+ spin_lock_irqsave(&nmk_gpio_slpm_lock, flags);
+ spin_lock(&nmk_chip->lock);
+
__nmk_gpio_set_slpm(nmk_chip, gpio - nmk_chip->chip.base, mode);
- spin_unlock_irqrestore(&nmk_chip->lock, flags);
+
+ spin_unlock(&nmk_chip->lock);
+ spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags);
return 0;
}
}
}
-static int nmk_gpio_irq_modify(struct irq_data *d, enum nmk_gpio_irq_type which,
- bool enable)
+static void __nmk_gpio_set_wake(struct nmk_gpio_chip *nmk_chip,
+ int gpio, bool on)
+{
+#ifdef CONFIG_ARCH_U8500
+ if (cpu_is_u8500v2()) {
+ __nmk_gpio_set_slpm(nmk_chip, gpio - nmk_chip->chip.base,
+ on ? NMK_GPIO_SLPM_WAKEUP_ENABLE
+ : NMK_GPIO_SLPM_WAKEUP_DISABLE);
+ }
+#endif
+ __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, on);
+}
+
+static int nmk_gpio_irq_maskunmask(struct irq_data *d, bool enable)
{
int gpio;
struct nmk_gpio_chip *nmk_chip;
if (!nmk_chip)
return -EINVAL;
- spin_lock_irqsave(&nmk_chip->lock, flags);
- __nmk_gpio_irq_modify(nmk_chip, gpio, which, enable);
- spin_unlock_irqrestore(&nmk_chip->lock, flags);
+ spin_lock_irqsave(&nmk_gpio_slpm_lock, flags);
+ spin_lock(&nmk_chip->lock);
+
+ __nmk_gpio_irq_modify(nmk_chip, gpio, NORMAL, enable);
+
+ if (!(nmk_chip->real_wake & bitmask))
+ __nmk_gpio_set_wake(nmk_chip, gpio, enable);
+
+ spin_unlock(&nmk_chip->lock);
+ spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags);
return 0;
}
static void nmk_gpio_irq_mask(struct irq_data *d)
{
- nmk_gpio_irq_modify(d, NORMAL, false);
+ nmk_gpio_irq_maskunmask(d, false);
}
static void nmk_gpio_irq_unmask(struct irq_data *d)
{
- nmk_gpio_irq_modify(d, NORMAL, true);
+ nmk_gpio_irq_maskunmask(d, true);
}
static int nmk_gpio_irq_set_wake(struct irq_data *d, unsigned int on)
{
+ struct irq_desc *desc = irq_to_desc(d->irq);
+ bool enabled = !(desc->status & IRQ_DISABLED);
struct nmk_gpio_chip *nmk_chip;
unsigned long flags;
+ u32 bitmask;
int gpio;
gpio = NOMADIK_IRQ_TO_GPIO(d->irq);
nmk_chip = irq_data_get_irq_chip_data(d);
if (!nmk_chip)
return -EINVAL;
+ bitmask = nmk_gpio_get_bitmask(gpio);
- spin_lock_irqsave(&nmk_chip->lock, flags);
-#ifdef CONFIG_ARCH_U8500
- if (cpu_is_u8500v2()) {
- __nmk_gpio_set_slpm(nmk_chip, gpio,
- on ? NMK_GPIO_SLPM_WAKEUP_ENABLE
- : NMK_GPIO_SLPM_WAKEUP_DISABLE);
- }
-#endif
- __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, on);
- spin_unlock_irqrestore(&nmk_chip->lock, flags);
+ spin_lock_irqsave(&nmk_gpio_slpm_lock, flags);
+ spin_lock(&nmk_chip->lock);
+
+ if (!enabled)
+ __nmk_gpio_set_wake(nmk_chip, gpio, on);
+
+ if (on)
+ nmk_chip->real_wake |= bitmask;
+ else
+ nmk_chip->real_wake &= ~bitmask;
+
+ spin_unlock(&nmk_chip->lock);
+ spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags);
return 0;
}
if (enabled)
__nmk_gpio_irq_modify(nmk_chip, gpio, NORMAL, false);
- if (wake)
+ if (enabled || wake)
__nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, false);
nmk_chip->edge_rising &= ~bitmask;
if (enabled)
__nmk_gpio_irq_modify(nmk_chip, gpio, NORMAL, true);
- if (wake)
+ if (enabled || wake)
__nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, true);
spin_unlock_irqrestore(&nmk_chip->lock, flags);
: "? ",
(mode < 0) ? "unknown" : modes[mode],
pull ? "pull" : "none");
-
- if (!is_out) {
- int irq = gpio_to_irq(gpio);
- struct irq_desc *desc = irq_to_desc(irq);
-
- /* This races with request_irq(), set_irq_type(),
- * and set_irq_wake() ... but those are "rare".
- *
- * More significantly, trigger type flags aren't
- * currently maintained by genirq.
- */
- if (irq >= 0 && desc->action) {
- char *trigger;
-
- switch (desc->status & IRQ_TYPE_SENSE_MASK) {
- case IRQ_TYPE_NONE:
- trigger = "(default)";
- break;
- case IRQ_TYPE_EDGE_FALLING:
- trigger = "edge-falling";
- break;
- case IRQ_TYPE_EDGE_RISING:
- trigger = "edge-rising";
- break;
- case IRQ_TYPE_EDGE_BOTH:
- trigger = "edge-both";
- break;
- case IRQ_TYPE_LEVEL_HIGH:
- trigger = "level-high";
- break;
- case IRQ_TYPE_LEVEL_LOW:
- trigger = "level-low";
- break;
- default:
- trigger = "?trigger?";
- break;
- }
-
- seq_printf(s, " irq-%d %s%s",
- irq, trigger,
- (desc->status & IRQ_WAKEUP)
- ? " wakeup" : "");
- }
- }
-
seq_printf(s, "\n");
}
}
.can_sleep = 0,
};
+/*
+ * Called from the suspend/resume path to only keep the real wakeup interrupts
+ * (those that have had set_irq_wake() called on them) as wakeup interrupts,
+ * and not the rest of the interrupts which we needed to have as wakeups for
+ * cpuidle.
+ *
+ * PM ops are not used since this needs to be done at the end, after all the
+ * other drivers are done with their suspend callbacks.
+ */
+void nmk_gpio_wakeups_suspend(void)
+{
+ int i;
+
+ for (i = 0; i < NUM_BANKS; i++) {
+ struct nmk_gpio_chip *chip = nmk_gpio_chips[i];
+
+ if (!chip)
+ break;
+
+ chip->rwimsc = readl(chip->addr + NMK_GPIO_RWIMSC);
+ chip->fwimsc = readl(chip->addr + NMK_GPIO_FWIMSC);
+
+ writel(chip->rwimsc & chip->real_wake,
+ chip->addr + NMK_GPIO_RWIMSC);
+ writel(chip->fwimsc & chip->real_wake,
+ chip->addr + NMK_GPIO_FWIMSC);
+
+ if (cpu_is_u8500v2()) {
+ chip->slpm = readl(chip->addr + NMK_GPIO_SLPC);
+
+ /* 0 -> wakeup enable */
+ writel(~chip->real_wake, chip->addr + NMK_GPIO_SLPC);
+ }
+ }
+}
+
+void nmk_gpio_wakeups_resume(void)
+{
+ int i;
+
+ for (i = 0; i < NUM_BANKS; i++) {
+ struct nmk_gpio_chip *chip = nmk_gpio_chips[i];
+
+ if (!chip)
+ break;
+
+ writel(chip->rwimsc, chip->addr + NMK_GPIO_RWIMSC);
+ writel(chip->fwimsc, chip->addr + NMK_GPIO_FWIMSC);
+
+ if (cpu_is_u8500v2())
+ writel(chip->slpm, chip->addr + NMK_GPIO_SLPC);
+ }
+}
+
static int __devinit nmk_gpio_probe(struct platform_device *dev)
{
struct nmk_gpio_platform_data *pdata = dev->dev.platform_data;
nmk_chip->parent_irq = irq;
nmk_chip->secondary_parent_irq = secondary_irq;
nmk_chip->get_secondary_status = pdata->get_secondary_status;
+ nmk_chip->set_ioforce = pdata->set_ioforce;
spin_lock_init(&nmk_chip->lock);
chip = &nmk_chip->chip;
if (ret)
goto out_free;
+ BUG_ON(nmk_chip->bank >= ARRAY_SIZE(nmk_gpio_chips));
+
+ nmk_gpio_chips[nmk_chip->bank] = nmk_chip;
platform_set_drvdata(dev, nmk_chip);
nmk_gpio_init_irq(nmk_chip);
return ret;
}
-#ifdef CONFIG_NOMADIK_GPIO_PM
-static int nmk_gpio_pm(struct platform_device *dev, bool suspend)
-{
- struct nmk_gpio_chip *nmk_chip = platform_get_drvdata(dev);
- int i;
- u32 dir;
- u32 dat;
-
- for (i = 0; i < ARRAY_SIZE(backup_regs); i++) {
- if (suspend)
- nmk_chip->backup[i] = readl(nmk_chip->addr +
- backup_regs[i]);
- else
- writel(nmk_chip->backup[i],
- nmk_chip->addr + backup_regs[i]);
- }
-
- if (!suspend) {
- /*
- * Restore pull-up and pull-down on inputs and
- * outputs.
- */
- dir = readl(nmk_chip->addr + NMK_GPIO_DIR);
- dat = readl(nmk_chip->addr + NMK_GPIO_DAT);
-
- writel((nmk_chip->pull & ~dir) |
- (dat & dir),
- nmk_chip->addr + NMK_GPIO_DATS);
-
- writel((~nmk_chip->pull & ~dir) |
- (~dat & dir),
- nmk_chip->addr + NMK_GPIO_DATC);
- }
- return 0;
-}
-
-static int nmk_gpio_suspend(struct platform_device *dev, pm_message_t state)
-{
- return nmk_gpio_pm(dev, true);
-}
-
-static int nmk_gpio_resume(struct platform_device *dev)
-{
- return nmk_gpio_pm(dev, false);
-}
-#else
-#define nmk_gpio_suspend NULL
-#define nmk_gpio_resume NULL
-#endif
-
static struct platform_driver nmk_gpio_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "gpio",
- },
+ },
.probe = nmk_gpio_probe,
- .suspend = nmk_gpio_suspend,
- .resume = nmk_gpio_resume,
};
static int __init nmk_gpio_init(void)