diff mbox

[v2,2/4] pinctrl: st: Add software edge trigger interrupt support.

Message ID 1389886651-31990-1-git-send-email-srinivas.kandagatla@st.com (mailing list archive)
State New, archived
Headers show

Commit Message

Srinivas KANDAGATLA Jan. 16, 2014, 3:37 p.m. UTC
From: Srinivas Kandagatla <srinivas.kandagatla@st.com>

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>
---
 drivers/pinctrl/pinctrl-st.c |  116 +++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 114 insertions(+), 2 deletions(-)

Comments

Linus Walleij Jan. 23, 2014, 7:29 a.m. UTC | #1
On Thu, Jan 16, 2014 at 4:37 PM,  <srinivas.kandagatla@st.com> wrote:

> 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.

(...)


> +/*
> + * 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.

(...)

> +/*
> + * As edge triggers are not supported at hardware level, it is supported by
> + * software by exploiting the level trigger support in hardware.

(...)

All this is quite hard to understand. Maybe it's just because
it's hard overall. Edge triggers are not supported by hardware
so we use the hardware edge trigger support?

That is a bit oxymoronic...

> + * 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.

But I do understand this, that's VERY clever and may be something
that can be exploited on other hardware as well some day.

So patch applied.

Yours,
Linus Walleij
Lee Jones Jan. 24, 2014, 8:28 a.m. UTC | #2
> > +/*
> > + * As edge triggers are not supported at hardware level, it is supported by
> > + * software by exploiting the level trigger support in hardware.
> 
> (...)
> 
> All this is quite hard to understand. Maybe it's just because
> it's hard overall. Edge triggers are not supported by hardware
> so we use the hardware edge trigger support?
> 
> That is a bit oxymoronic...

That's not what is says. Read it again. :)
Linus Walleij Jan. 27, 2014, 9:43 a.m. UTC | #3
On Fri, Jan 24, 2014 at 9:28 AM, Lee Jones <lee.jones@linaro.org> wrote:
>> > +/*
>> > + * As edge triggers are not supported at hardware level, it is supported by
>> > + * software by exploiting the level trigger support in hardware.
>>
>> (...)
>>
>> All this is quite hard to understand. Maybe it's just because
>> it's hard overall. Edge triggers are not supported by hardware
>> so we use the hardware edge trigger support?
>>
>> That is a bit oxymoronic...
>
> That's not what is says. Read it again. :)

Argh yeah I was wrong ... it's perfectly consistent with the
implementation. Sorry for being such a slow brain this last
friday.

Yours,
Linus Walleij
diff mbox

Patch

diff --git a/drivers/pinctrl/pinctrl-st.c b/drivers/pinctrl/pinctrl-st.c
index 51e4f3a..9fb66aa 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;