pinctrl: mediatek: emulate GPIO interrupt on both-edges
MTK EINT does not support generating interrupt on both edges.
Emulate this by changing edge polarity while enable irq,
set types and interrupt handling. This follows an example of
drivers/gpio/gpio-mxc.c.
Signed-off-by: Yingjoe Chen <yingjoe.chen@mediatek.com>
Signed-off-by: Chaotian Jing <chaotian.jing@mediatek.com>
Acked-by: Hongzhou Yang <hongzhou.yang@mediatek.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
diff --git a/drivers/pinctrl/mediatek/pinctrl-mtk-common.c b/drivers/pinctrl/mediatek/pinctrl-mtk-common.c
index 2864fe3..a82ae1a 100644
--- a/drivers/pinctrl/mediatek/pinctrl-mtk-common.c
+++ b/drivers/pinctrl/mediatek/pinctrl-mtk-common.c
@@ -792,6 +792,32 @@
return !!(readl(reg) & bit);
}
+static int mtk_eint_flip_edge(struct mtk_pinctrl *pctl, int hwirq)
+{
+ int start_level, curr_level;
+ unsigned int reg_offset;
+ const struct mtk_eint_offsets *eint_offsets = &(pctl->devdata->eint_offsets);
+ u32 mask = 1 << (hwirq & 0x1f);
+ u32 port = (hwirq >> 5) & eint_offsets->port_mask;
+ void __iomem *reg = pctl->eint_reg_base + (port << 2);
+ const struct mtk_desc_pin *pin;
+
+ pin = mtk_find_pin_by_eint_num(pctl, hwirq);
+ curr_level = mtk_gpio_get(pctl->chip, pin->pin.number);
+ do {
+ start_level = curr_level;
+ if (start_level)
+ reg_offset = eint_offsets->pol_clr;
+ else
+ reg_offset = eint_offsets->pol_set;
+ writel(mask, reg + reg_offset);
+
+ curr_level = mtk_gpio_get(pctl->chip, pin->pin.number);
+ } while (start_level != curr_level);
+
+ return start_level;
+}
+
static void mtk_eint_mask(struct irq_data *d)
{
struct mtk_pinctrl *pctl = irq_data_get_irq_chip_data(d);
@@ -814,6 +840,9 @@
eint_offsets->mask_clr);
writel(mask, reg);
+
+ if (pctl->eint_dual_edges[d->hwirq])
+ mtk_eint_flip_edge(pctl, d->hwirq);
}
static int mtk_gpio_set_debounce(struct gpio_chip *chip, unsigned offset,
@@ -893,13 +922,17 @@
void __iomem *reg;
if (((type & IRQ_TYPE_EDGE_BOTH) && (type & IRQ_TYPE_LEVEL_MASK)) ||
- ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) ||
((type & IRQ_TYPE_LEVEL_MASK) == IRQ_TYPE_LEVEL_MASK)) {
dev_err(pctl->dev, "Can't configure IRQ%d (EINT%lu) for type 0x%X\n",
d->irq, d->hwirq, type);
return -EINVAL;
}
+ if ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH)
+ pctl->eint_dual_edges[d->hwirq] = 1;
+ else
+ pctl->eint_dual_edges[d->hwirq] = 0;
+
if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING)) {
reg = mtk_eint_get_offset(pctl, d->hwirq,
eint_offsets->pol_clr);
@@ -920,6 +953,9 @@
writel(mask, reg);
}
+ if (pctl->eint_dual_edges[d->hwirq])
+ mtk_eint_flip_edge(pctl, d->hwirq);
+
return 0;
}
@@ -986,6 +1022,8 @@
const struct mtk_eint_offsets *eint_offsets =
&pctl->devdata->eint_offsets;
void __iomem *reg = mtk_eint_get_offset(pctl, 0, eint_offsets->stat);
+ int dual_edges, start_level, curr_level;
+ const struct mtk_desc_pin *pin;
chained_irq_enter(chip, desc);
for (eint_num = 0; eint_num < pctl->devdata->ap_num; eint_num += 32) {
@@ -997,8 +1035,31 @@
virq = irq_find_mapping(pctl->domain, index);
status &= ~BIT(offset);
+ dual_edges = pctl->eint_dual_edges[index];
+ if (dual_edges) {
+ /* Clear soft-irq in case we raised it
+ last time */
+ writel(BIT(offset), reg - eint_offsets->stat +
+ eint_offsets->soft_clr);
+
+ pin = mtk_find_pin_by_eint_num(pctl, index);
+ start_level = mtk_gpio_get(pctl->chip,
+ pin->pin.number);
+ }
+
generic_handle_irq(virq);
+ if (dual_edges) {
+ curr_level = mtk_eint_flip_edge(pctl, index);
+
+ /* If level changed, we might lost one edge
+ interrupt, raised it through soft-irq */
+ if (start_level != curr_level)
+ writel(BIT(offset), reg -
+ eint_offsets->stat +
+ eint_offsets->soft_set);
+ }
+
if (index < pctl->devdata->db_cnt)
mtk_eint_debounce_process(pctl , index);
}
@@ -1149,11 +1210,18 @@
goto chip_error;
}
+ pctl->eint_dual_edges = devm_kzalloc(&pdev->dev,
+ sizeof(int) * pctl->devdata->ap_num, GFP_KERNEL);
+ if (!pctl->eint_dual_edges) {
+ ret = -ENOMEM;
+ goto chip_error;
+ }
+
irq = irq_of_parse_and_map(np, 0);
if (!irq) {
dev_err(&pdev->dev, "couldn't parse and map irq\n");
ret = -EINVAL;
- goto chip_error;
+ goto free_edges;
}
pctl->domain = irq_domain_add_linear(np,
@@ -1161,7 +1229,7 @@
if (!pctl->domain) {
dev_err(&pdev->dev, "Couldn't register IRQ domain\n");
ret = -ENOMEM;
- goto chip_error;
+ goto free_edges;
}
mtk_eint_init(pctl);
@@ -1179,6 +1247,8 @@
set_irq_flags(irq, IRQF_VALID);
return 0;
+free_edges:
+ kfree(pctl->eint_dual_edges);
chip_error:
gpiochip_remove(pctl->chip);
pctrl_error: