Skip to content

Commit c297561

Browse files
HoratiuVulturlinusw
authored andcommitted
pinctrl: ocelot: Fix interrupt controller
When an external device generated a level based interrupt then the interrupt controller could miss the interrupt. The reason is that the interrupt controller can detect only link changes. In the following example, if there is a PHY that generates an interrupt then the following would happen. The GPIO detected that the interrupt line changed, and then the 'ocelot_irq_handler' was called. Here it detects which GPIO line saw the change and for that will call the following: 1. irq_mask 2. phy interrupt routine 3. irq_eoi 4. irq_unmask And this works fine for simple cases, but if the PHY generates many interrupts, for example when doing PTP timestamping, then the following could happen. Again the function 'ocelot_irq_handler' will be called and then from here the following could happen: 1. irq_mask 2. phy interrupt routine 3. irq_eoi 4. irq_unmask Right before step 3(irq_eoi), the PHY will generate another interrupt. Now the interrupt controller will acknowledge the change in the interrupt line. So we miss the interrupt. A solution will be to use 'handle_level_irq' instead of 'handle_fasteoi_irq', because for this will change routine order of handling the interrupt. 1. irq_mask 2. irq_ack 3. phy interrupt routine 4. irq_unmask And now if the PHY will generate a new interrupt before irq_unmask, the interrupt controller will detect this because it already acknowledge the change in interrupt line at step 2(irq_ack). But this is not the full solution because there is another issue. In case there are 2 PHYs that share the interrupt line. For example phy1 generates an interrupt, then the following can happen: 1.irq_mask 2.irq_ack 3.phy0 interrupt routine 4.phy1 interrupt routine 5.irq_unmask In case phy0 will generate an interrupt while clearing the interrupt source in phy1, then the interrupt line will be kept down by phy0. So the interrupt controller will not see any changes in the interrupt line. The solution here is to update 'irq_unmask' such that it can detect if the interrupt line is still active or not. And if it is active then call again the procedure to clear the interrupts. But we don't want to do it every time, only if we know that the interrupt controller has not seen already that the interrupt line has changed. While at this, add support also for IRQ_TYPE_LEVEL_LOW. Fixes: be36abb ("pinctrl: ocelot: add support for interrupt controller") Signed-off-by: Horatiu Vultur <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Linus Walleij <[email protected]>
1 parent 76648c8 commit c297561

File tree

1 file changed

+97
-14
lines changed

1 file changed

+97
-14
lines changed

drivers/pinctrl/pinctrl-ocelot.c

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -331,13 +331,19 @@ struct ocelot_pinctrl {
331331
const struct ocelot_pincfg_data *pincfg_data;
332332
struct ocelot_pmx_func func[FUNC_MAX];
333333
u8 stride;
334+
struct workqueue_struct *wq;
334335
};
335336

336337
struct ocelot_match_data {
337338
struct pinctrl_desc desc;
338339
struct ocelot_pincfg_data pincfg_data;
339340
};
340341

342+
struct ocelot_irq_work {
343+
struct work_struct irq_work;
344+
struct irq_desc *irq_desc;
345+
};
346+
341347
#define LUTON_P(p, f0, f1) \
342348
static struct ocelot_pin_caps luton_pin_##p = { \
343349
.pin = p, \
@@ -1813,6 +1819,75 @@ static void ocelot_irq_mask(struct irq_data *data)
18131819
gpiochip_disable_irq(chip, gpio);
18141820
}
18151821

1822+
static void ocelot_irq_work(struct work_struct *work)
1823+
{
1824+
struct ocelot_irq_work *w = container_of(work, struct ocelot_irq_work, irq_work);
1825+
struct irq_chip *parent_chip = irq_desc_get_chip(w->irq_desc);
1826+
struct gpio_chip *chip = irq_desc_get_chip_data(w->irq_desc);
1827+
struct irq_data *data = irq_desc_get_irq_data(w->irq_desc);
1828+
unsigned int gpio = irqd_to_hwirq(data);
1829+
1830+
local_irq_disable();
1831+
chained_irq_enter(parent_chip, w->irq_desc);
1832+
generic_handle_domain_irq(chip->irq.domain, gpio);
1833+
chained_irq_exit(parent_chip, w->irq_desc);
1834+
local_irq_enable();
1835+
1836+
kfree(w);
1837+
}
1838+
1839+
static void ocelot_irq_unmask_level(struct irq_data *data)
1840+
{
1841+
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
1842+
struct ocelot_pinctrl *info = gpiochip_get_data(chip);
1843+
struct irq_desc *desc = irq_data_to_desc(data);
1844+
unsigned int gpio = irqd_to_hwirq(data);
1845+
unsigned int bit = BIT(gpio % 32);
1846+
bool ack = false, active = false;
1847+
u8 trigger_level;
1848+
int val;
1849+
1850+
trigger_level = irqd_get_trigger_type(data);
1851+
1852+
/* Check if the interrupt line is still active. */
1853+
regmap_read(info->map, REG(OCELOT_GPIO_IN, info, gpio), &val);
1854+
if ((!(val & bit) && trigger_level == IRQ_TYPE_LEVEL_LOW) ||
1855+
(val & bit && trigger_level == IRQ_TYPE_LEVEL_HIGH))
1856+
active = true;
1857+
1858+
/*
1859+
* Check if the interrupt controller has seen any changes in the
1860+
* interrupt line.
1861+
*/
1862+
regmap_read(info->map, REG(OCELOT_GPIO_INTR, info, gpio), &val);
1863+
if (val & bit)
1864+
ack = true;
1865+
1866+
/* Enable the interrupt now */
1867+
gpiochip_enable_irq(chip, gpio);
1868+
regmap_update_bits(info->map, REG(OCELOT_GPIO_INTR_ENA, info, gpio),
1869+
bit, bit);
1870+
1871+
/*
1872+
* In case the interrupt line is still active and the interrupt
1873+
* controller has not seen any changes in the interrupt line, then it
1874+
* means that there happen another interrupt while the line was active.
1875+
* So we missed that one, so we need to kick the interrupt again
1876+
* handler.
1877+
*/
1878+
if (active && !ack) {
1879+
struct ocelot_irq_work *work;
1880+
1881+
work = kmalloc(sizeof(*work), GFP_ATOMIC);
1882+
if (!work)
1883+
return;
1884+
1885+
work->irq_desc = desc;
1886+
INIT_WORK(&work->irq_work, ocelot_irq_work);
1887+
queue_work(info->wq, &work->irq_work);
1888+
}
1889+
}
1890+
18161891
static void ocelot_irq_unmask(struct irq_data *data)
18171892
{
18181893
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
@@ -1836,13 +1911,12 @@ static void ocelot_irq_ack(struct irq_data *data)
18361911

18371912
static int ocelot_irq_set_type(struct irq_data *data, unsigned int type);
18381913

1839-
static struct irq_chip ocelot_eoi_irqchip = {
1914+
static struct irq_chip ocelot_level_irqchip = {
18401915
.name = "gpio",
18411916
.irq_mask = ocelot_irq_mask,
1842-
.irq_eoi = ocelot_irq_ack,
1843-
.irq_unmask = ocelot_irq_unmask,
1844-
.flags = IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED |
1845-
IRQCHIP_IMMUTABLE,
1917+
.irq_ack = ocelot_irq_ack,
1918+
.irq_unmask = ocelot_irq_unmask_level,
1919+
.flags = IRQCHIP_IMMUTABLE,
18461920
.irq_set_type = ocelot_irq_set_type,
18471921
GPIOCHIP_IRQ_RESOURCE_HELPERS
18481922
};
@@ -1859,14 +1933,9 @@ static struct irq_chip ocelot_irqchip = {
18591933

18601934
static int ocelot_irq_set_type(struct irq_data *data, unsigned int type)
18611935
{
1862-
type &= IRQ_TYPE_SENSE_MASK;
1863-
1864-
if (!(type & (IRQ_TYPE_EDGE_BOTH | IRQ_TYPE_LEVEL_HIGH)))
1865-
return -EINVAL;
1866-
1867-
if (type & IRQ_TYPE_LEVEL_HIGH)
1868-
irq_set_chip_handler_name_locked(data, &ocelot_eoi_irqchip,
1869-
handle_fasteoi_irq, NULL);
1936+
if (type & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW))
1937+
irq_set_chip_handler_name_locked(data, &ocelot_level_irqchip,
1938+
handle_level_irq, NULL);
18701939
if (type & IRQ_TYPE_EDGE_BOTH)
18711940
irq_set_chip_handler_name_locked(data, &ocelot_irqchip,
18721941
handle_edge_irq, NULL);
@@ -1996,6 +2065,10 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev)
19962065
if (!info->desc)
19972066
return -ENOMEM;
19982067

2068+
info->wq = alloc_ordered_workqueue("ocelot_ordered", 0);
2069+
if (!info->wq)
2070+
return -ENOMEM;
2071+
19992072
info->pincfg_data = &data->pincfg_data;
20002073

20012074
reset = devm_reset_control_get_optional_shared(dev, "switch");
@@ -2018,7 +2091,7 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev)
20182091
dev_err(dev, "Failed to create regmap\n");
20192092
return PTR_ERR(info->map);
20202093
}
2021-
dev_set_drvdata(dev, info->map);
2094+
dev_set_drvdata(dev, info);
20222095
info->dev = dev;
20232096

20242097
/* Pinconf registers */
@@ -2043,13 +2116,23 @@ static int ocelot_pinctrl_probe(struct platform_device *pdev)
20432116
return 0;
20442117
}
20452118

2119+
static int ocelot_pinctrl_remove(struct platform_device *pdev)
2120+
{
2121+
struct ocelot_pinctrl *info = platform_get_drvdata(pdev);
2122+
2123+
destroy_workqueue(info->wq);
2124+
2125+
return 0;
2126+
}
2127+
20462128
static struct platform_driver ocelot_pinctrl_driver = {
20472129
.driver = {
20482130
.name = "pinctrl-ocelot",
20492131
.of_match_table = of_match_ptr(ocelot_pinctrl_of_match),
20502132
.suppress_bind_attrs = true,
20512133
},
20522134
.probe = ocelot_pinctrl_probe,
2135+
.remove = ocelot_pinctrl_remove,
20532136
};
20542137
module_platform_driver(ocelot_pinctrl_driver);
20552138
MODULE_LICENSE("Dual MIT/GPL");

0 commit comments

Comments
 (0)