diff options
Diffstat (limited to 'drivers/pinctrl/pinctrl-st.c')
-rw-r--r-- | drivers/pinctrl/pinctrl-st.c | 337 |
1 files changed, 335 insertions, 2 deletions
diff --git a/drivers/pinctrl/pinctrl-st.c b/drivers/pinctrl/pinctrl-st.c index 320c27363cc8..9fb66aa796aa 100644 --- a/drivers/pinctrl/pinctrl-st.c +++ b/drivers/pinctrl/pinctrl-st.c @@ -13,7 +13,12 @@ #include <linux/slab.h> #include <linux/err.h> #include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqdesc.h> +#include <linux/irqdomain.h> +#include <linux/irqchip/chained_irq.h> #include <linux/of.h> +#include <linux/of_irq.h> #include <linux/of_gpio.h> #include <linux/of_address.h> #include <linux/regmap.h> @@ -266,11 +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 { @@ -284,6 +337,7 @@ struct st_pinctrl { int ngroups; struct regmap *regmap; const struct st_pctl_data *data; + void __iomem *irqmux_base; }; /* SOC specific data */ @@ -1200,6 +1254,194 @@ static int st_pctl_parse_functions(struct device_node *np, return 0; } +static int st_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct st_gpio_bank *bank = gpio_chip_to_bank(chip); + int irq = -ENXIO; + + if (offset < chip->ngpio) + irq = irq_find_mapping(bank->domain, offset); + + dev_info(chip->dev, "%s: request IRQ for GPIO %d, return %d\n", + chip->label, offset + chip->base, irq); + return irq; +} + +static void st_gpio_irq_mask(struct irq_data *d) +{ + struct st_gpio_bank *bank = irq_data_get_irq_chip_data(d); + + writel(BIT(d->hwirq), bank->base + REG_PIO_CLR_PMASK); +} + +static void st_gpio_irq_unmask(struct irq_data *d) +{ + struct st_gpio_bank *bank = irq_data_get_irq_chip_data(d); + + writel(BIT(d->hwirq), bank->base + REG_PIO_SET_PMASK); +} + +static unsigned int st_gpio_irq_startup(struct irq_data *d) +{ + struct st_gpio_bank *bank = irq_data_get_irq_chip_data(d); + + if (gpio_lock_as_irq(&bank->gpio_chip, d->hwirq)) + dev_err(bank->gpio_chip.dev, + "unable to lock HW IRQ %lu for IRQ\n", + d->hwirq); + + st_gpio_irq_unmask(d); + + return 0; +} + +static void st_gpio_irq_shutdown(struct irq_data *d) +{ + struct st_gpio_bank *bank = irq_data_get_irq_chip_data(d); + + st_gpio_irq_mask(d); + gpio_unlock_as_irq(&bank->gpio_chip, d->hwirq); +} + +static int st_gpio_irq_set_type(struct irq_data *d, unsigned type) +{ + struct st_gpio_bank *bank = irq_data_get_irq_chip_data(d); + 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); + writel(val, bank->base + REG_PIO_PCOMP); + + 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; + 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); + port_comp = readl(bank->base + REG_PIO_PCOMP); + port_mask = readl(bank->base + REG_PIO_PMASK); + + active_irqs = (port_in ^ port_comp) & port_mask; + + if (active_irqs == 0) + 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)); + } + } +} + +static void st_gpio_irq_handler(unsigned irq, struct irq_desc *desc) +{ + /* interrupt dedicated per bank */ + struct irq_chip *chip = irq_get_chip(irq); + struct st_gpio_bank *bank = irq_get_handler_data(irq); + + chained_irq_enter(chip, desc); + __gpio_irq_handler(bank); + chained_irq_exit(chip, desc); +} + +static void st_gpio_irqmux_handler(unsigned irq, struct irq_desc *desc) +{ + struct irq_chip *chip = irq_get_chip(irq); + struct st_pinctrl *info = irq_get_handler_data(irq); + unsigned long status; + int n; + + chained_irq_enter(chip, desc); + + status = readl(info->irqmux_base); + + for_each_set_bit(n, &status, ST_GPIO_PINS_PER_BANK) + __gpio_irq_handler(&info->banks[n]); + + chained_irq_exit(chip, desc); +} + static struct gpio_chip st_gpio_template = { .request = st_gpio_request, .free = st_gpio_free, @@ -1210,6 +1452,34 @@ static struct gpio_chip st_gpio_template = { .ngpio = ST_GPIO_PINS_PER_BANK, .of_gpio_n_cells = 1, .of_xlate = st_gpio_xlate, + .to_irq = st_gpio_to_irq, +}; + +static struct irq_chip st_gpio_irqchip = { + .name = "GPIO", + .irq_mask = st_gpio_irq_mask, + .irq_unmask = st_gpio_irq_unmask, + .irq_set_type = st_gpio_irq_set_type, + .irq_startup = st_gpio_irq_startup, + .irq_shutdown = st_gpio_irq_shutdown, +}; + +static int st_gpio_irq_domain_map(struct irq_domain *h, + unsigned int virq, irq_hw_number_t hw) +{ + struct st_gpio_bank *bank = h->host_data; + + irq_set_chip(virq, &st_gpio_irqchip); + irq_set_handler(virq, handle_simple_irq); + set_irq_flags(virq, IRQF_VALID); + irq_set_chip_data(virq, bank); + + return 0; +} + +static struct irq_domain_ops st_gpio_irq_ops = { + .map = st_gpio_irq_domain_map, + .xlate = irq_domain_xlate_twocell, }; static int st_gpiolib_register_bank(struct st_pinctrl *info, @@ -1219,8 +1489,8 @@ static int st_gpiolib_register_bank(struct st_pinctrl *info, struct pinctrl_gpio_range *range = &bank->range; struct device *dev = info->dev; int bank_num = of_alias_get_id(np, "gpio"); - struct resource res; - int err; + struct resource res, irq_res; + int gpio_irq = 0, err, i; if (of_address_to_resource(np, 0, &res)) return -ENODEV; @@ -1233,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; @@ -1248,6 +1519,51 @@ static int st_gpiolib_register_bank(struct st_pinctrl *info, } dev_info(dev, "%s bank added.\n", range->name); + /** + * GPIO bank can have one of the two possible types of + * interrupt-wirings. + * + * First type is via irqmux, single interrupt is used by multiple + * gpio banks. This reduces number of overall interrupts numbers + * required. All these banks belong to a single pincontroller. + * _________ + * | |----> [gpio-bank (n) ] + * | |----> [gpio-bank (n + 1)] + * [irqN]-- | irq-mux |----> [gpio-bank (n + 2)] + * | |----> [gpio-bank (... )] + * |_________|----> [gpio-bank (n + 7)] + * + * Second type has a dedicated interrupt per each gpio bank. + * + * [irqN]----> [gpio-bank (n)] + */ + + if (!of_irq_to_resource(np, 0, &irq_res)) { + gpio_irq = irq_res.start; + irq_set_chained_handler(gpio_irq, st_gpio_irq_handler); + irq_set_handler_data(gpio_irq, bank); + } + + if (info->irqmux_base > 0 || gpio_irq > 0) { + /* Setup IRQ domain */ + bank->domain = irq_domain_add_linear(np, + ST_GPIO_PINS_PER_BANK, + &st_gpio_irq_ops, bank); + if (!bank->domain) { + dev_err(dev, "Failed to add irq domain for %s\n", + np->full_name); + } else { + for (i = 0; i < ST_GPIO_PINS_PER_BANK; i++) { + if (irq_create_mapping(bank->domain, i) < 0) + dev_err(dev, + "Failed to map IRQ %i\n", i); + } + } + + } else { + dev_info(dev, "No IRQ support for %s bank\n", np->full_name); + } + return 0; } @@ -1276,6 +1592,8 @@ static int st_pctl_probe_dt(struct platform_device *pdev, struct device_node *np = pdev->dev.of_node; struct device_node *child; int grp_index = 0; + int irq = 0; + struct resource *res; st_pctl_dt_child_count(info, np); if (!info->nbanks) { @@ -1306,6 +1624,21 @@ static int st_pctl_probe_dt(struct platform_device *pdev, } info->data = of_match_node(st_pctl_of_match, np)->data; + irq = platform_get_irq(pdev, 0); + + if (irq > 0) { + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "irqmux"); + info->irqmux_base = devm_ioremap_resource(&pdev->dev, res); + + if (IS_ERR(info->irqmux_base)) + return PTR_ERR(info->irqmux_base); + + irq_set_chained_handler(irq, st_gpio_irqmux_handler); + irq_set_handler_data(irq, info); + + } + pctl_desc->npins = info->nbanks * ST_GPIO_PINS_PER_BANK; pdesc = devm_kzalloc(&pdev->dev, sizeof(*pdesc) * pctl_desc->npins, GFP_KERNEL); |