summaryrefslogtreecommitdiff
path: root/drivers/pinctrl/pinctrl-st.c
diff options
context:
space:
mode:
authorSrinivas Kandagatla <srinivas.kandagatla@st.com>2014-01-16 19:37:31 +0400
committerLinus Walleij <linus.walleij@linaro.org>2014-02-10 13:13:07 +0400
commit155795b9d143f4210c19ab50462e19fdeb38bc81 (patch)
tree92b8e61d410a2763f962dc788e7bf9bd76d9ff3a /drivers/pinctrl/pinctrl-st.c
parent727b0f71a56de64c0994e24f2504b1b954fd6f93 (diff)
downloadlinux-155795b9d143f4210c19ab50462e19fdeb38bc81.tar.xz
pinctrl: st: Add software edge trigger interrupt support
ST pin controller does not have hardware support for detecting edge triggered interrupts, It only has level triggering support. This patch attempts to fake up edge triggers from hw level trigger support in software. With this facility now the gpios can be easily used for keypads, otherwise it would be difficult for drivers like keypads to work with level trigger interrupts. Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@st.com> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Diffstat (limited to 'drivers/pinctrl/pinctrl-st.c')
-rw-r--r--drivers/pinctrl/pinctrl-st.c116
1 files changed, 114 insertions, 2 deletions
diff --git a/drivers/pinctrl/pinctrl-st.c b/drivers/pinctrl/pinctrl-st.c
index 51e4f3a1d249..9fb66aa796aa 100644
--- a/drivers/pinctrl/pinctrl-st.c
+++ b/drivers/pinctrl/pinctrl-st.c
@@ -271,12 +271,59 @@ struct st_pctl_group {
struct st_pinconf *pin_conf;
};
+/*
+ * Edge triggers are not supported at hardware level, it is supported by
+ * software by exploiting the level trigger support in hardware.
+ * Software uses a virtual register (EDGE_CONF) for edge trigger configuration
+ * of each gpio pin in a GPIO bank.
+ *
+ * Each bank has a 32 bit EDGE_CONF register which is divided in to 8 parts of
+ * 4-bits. Each 4-bit space is allocated for each pin in a gpio bank.
+ *
+ * bit allocation per pin is:
+ * Bits: [0 - 3] | [4 - 7] [8 - 11] ... ... ... ... [ 28 - 31]
+ * --------------------------------------------------------
+ * | pin-0 | pin-2 | pin-3 | ... ... ... ... | pin -7 |
+ * --------------------------------------------------------
+ *
+ * A pin can have one of following the values in its edge configuration field.
+ *
+ * ------- ----------------------------
+ * [0-3] - Description
+ * ------- ----------------------------
+ * 0000 - No edge IRQ.
+ * 0001 - Falling edge IRQ.
+ * 0010 - Rising edge IRQ.
+ * 0011 - Rising and Falling edge IRQ.
+ * ------- ----------------------------
+ */
+
+#define ST_IRQ_EDGE_CONF_BITS_PER_PIN 4
+#define ST_IRQ_EDGE_MASK 0xf
+#define ST_IRQ_EDGE_FALLING BIT(0)
+#define ST_IRQ_EDGE_RISING BIT(1)
+#define ST_IRQ_EDGE_BOTH (BIT(0) | BIT(1))
+
+#define ST_IRQ_RISING_EDGE_CONF(pin) \
+ (ST_IRQ_EDGE_RISING << (pin * ST_IRQ_EDGE_CONF_BITS_PER_PIN))
+
+#define ST_IRQ_FALLING_EDGE_CONF(pin) \
+ (ST_IRQ_EDGE_FALLING << (pin * ST_IRQ_EDGE_CONF_BITS_PER_PIN))
+
+#define ST_IRQ_BOTH_EDGE_CONF(pin) \
+ (ST_IRQ_EDGE_BOTH << (pin * ST_IRQ_EDGE_CONF_BITS_PER_PIN))
+
+#define ST_IRQ_EDGE_CONF(conf, pin) \
+ (conf >> (pin * ST_IRQ_EDGE_CONF_BITS_PER_PIN) & ST_IRQ_EDGE_MASK)
+
struct st_gpio_bank {
struct gpio_chip gpio_chip;
struct pinctrl_gpio_range range;
void __iomem *base;
struct st_pio_control pc;
struct irq_domain *domain;
+ unsigned long irq_edge_conf;
+ spinlock_t lock;
};
struct st_pinctrl {
@@ -1262,18 +1309,37 @@ static int st_gpio_irq_set_type(struct irq_data *d, unsigned type)
unsigned long flags;
int comp, pin = d->hwirq;
u32 val;
+ u32 pin_edge_conf = 0;
switch (type) {
case IRQ_TYPE_LEVEL_HIGH:
comp = 0;
break;
+ case IRQ_TYPE_EDGE_FALLING:
+ comp = 0;
+ pin_edge_conf = ST_IRQ_FALLING_EDGE_CONF(pin);
+ break;
case IRQ_TYPE_LEVEL_LOW:
comp = 1;
break;
+ case IRQ_TYPE_EDGE_RISING:
+ comp = 1;
+ pin_edge_conf = ST_IRQ_RISING_EDGE_CONF(pin);
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ comp = st_gpio_get(&bank->gpio_chip, pin);
+ pin_edge_conf = ST_IRQ_BOTH_EDGE_CONF(pin);
+ break;
default:
return -EINVAL;
}
+ spin_lock_irqsave(&bank->lock, flags);
+ bank->irq_edge_conf &= ~(ST_IRQ_EDGE_MASK << (
+ pin * ST_IRQ_EDGE_CONF_BITS_PER_PIN));
+ bank->irq_edge_conf |= pin_edge_conf;
+ spin_unlock_irqrestore(&bank->lock, flags);
+
val = readl(bank->base + REG_PIO_PCOMP);
val &= ~BIT(pin);
val |= (comp << pin);
@@ -1282,10 +1348,39 @@ static int st_gpio_irq_set_type(struct irq_data *d, unsigned type)
return 0;
}
+/*
+ * As edge triggers are not supported at hardware level, it is supported by
+ * software by exploiting the level trigger support in hardware.
+ *
+ * Steps for detection raising edge interrupt in software.
+ *
+ * Step 1: CONFIGURE pin to detect level LOW interrupts.
+ *
+ * Step 2: DETECT level LOW interrupt and in irqmux/gpio bank interrupt handler,
+ * if the value of pin is low, then CONFIGURE pin for level HIGH interrupt.
+ * IGNORE calling the actual interrupt handler for the pin at this stage.
+ *
+ * Step 3: DETECT level HIGH interrupt and in irqmux/gpio-bank interrupt handler
+ * if the value of pin is HIGH, CONFIGURE pin for level LOW interrupt and then
+ * DISPATCH the interrupt to the interrupt handler of the pin.
+ *
+ * step-1 ________ __________
+ * | | step - 3
+ * | |
+ * step -2 |_____|
+ *
+ * falling edge is also detected int the same way.
+ *
+ */
static void __gpio_irq_handler(struct st_gpio_bank *bank)
{
unsigned long port_in, port_mask, port_comp, active_irqs;
- int n;
+ unsigned long bank_edge_mask, flags;
+ int n, val, ecfg;
+
+ spin_lock_irqsave(&bank->lock, flags);
+ bank_edge_mask = bank->irq_edge_conf;
+ spin_unlock_irqrestore(&bank->lock, flags);
for (;;) {
port_in = readl(bank->base + REG_PIO_PIN);
@@ -1298,6 +1393,22 @@ static void __gpio_irq_handler(struct st_gpio_bank *bank)
break;
for_each_set_bit(n, &active_irqs, BITS_PER_LONG) {
+ /* check if we are detecting fake edges ... */
+ ecfg = ST_IRQ_EDGE_CONF(bank_edge_mask, n);
+
+ if (ecfg) {
+ /* edge detection. */
+ val = st_gpio_get(&bank->gpio_chip, n);
+
+ writel(BIT(n),
+ val ? bank->base + REG_PIO_SET_PCOMP :
+ bank->base + REG_PIO_CLR_PCOMP);
+
+ if (ecfg != ST_IRQ_EDGE_BOTH &&
+ !((ecfg & ST_IRQ_EDGE_FALLING) ^ val))
+ continue;
+ }
+
generic_handle_irq(irq_find_mapping(bank->domain, n));
}
}
@@ -1359,7 +1470,7 @@ static int st_gpio_irq_domain_map(struct irq_domain *h,
struct st_gpio_bank *bank = h->host_data;
irq_set_chip(virq, &st_gpio_irqchip);
- irq_set_handler(virq, handle_level_irq);
+ irq_set_handler(virq, handle_simple_irq);
set_irq_flags(virq, IRQF_VALID);
irq_set_chip_data(virq, bank);
@@ -1392,6 +1503,7 @@ static int st_gpiolib_register_bank(struct st_pinctrl *info,
bank->gpio_chip.base = bank_num * ST_GPIO_PINS_PER_BANK;
bank->gpio_chip.ngpio = ST_GPIO_PINS_PER_BANK;
bank->gpio_chip.of_node = np;
+ spin_lock_init(&bank->lock);
of_property_read_string(np, "st,bank-name", &range->name);
bank->gpio_chip.label = range->name;