diff options
| author | Linus Walleij <linusw@kernel.org> | 2026-01-19 00:27:18 +0100 |
|---|---|---|
| committer | Linus Walleij <linusw@kernel.org> | 2026-01-19 00:27:18 +0100 |
| commit | 43519f545757e291cff04f23cc1a0bbc1ca6e2f0 (patch) | |
| tree | b8f8088436899d2d20756455c7e00187cf8de6d4 /drivers | |
| parent | ba7693014d52e709797ae430cfe6ac1c8cadd3e6 (diff) | |
| parent | 829dde3369a91ad637ac15629ea8d73f3db2c562 (diff) | |
Merge tag 'renesas-pinctrl-for-v6.20-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/geert/renesas-drivers into devel
pinctrl: renesas: Updates for v6.20
- Add support for GPIO IRQs on RZ/T2H and RZ/N2H.
Signed-off-by: Linus Walleij <linusw@kernel.org>
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/pinctrl/renesas/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/pinctrl/renesas/pinctrl-rzt2h.c | 248 |
2 files changed, 241 insertions, 9 deletions
diff --git a/drivers/pinctrl/renesas/Kconfig b/drivers/pinctrl/renesas/Kconfig index 8cbd79a13414..d979e25e5088 100644 --- a/drivers/pinctrl/renesas/Kconfig +++ b/drivers/pinctrl/renesas/Kconfig @@ -308,9 +308,11 @@ config PINCTRL_RZT2H bool "pin control support for RZ/N2H and RZ/T2H" if COMPILE_TEST depends on 64BIT && OF select GPIOLIB + select GPIOLIB_IRQCHIP select GENERIC_PINCTRL_GROUPS select GENERIC_PINMUX_FUNCTIONS select GENERIC_PINCONF + select IRQ_DOMAIN_HIERARCHY help This selects GPIO and pinctrl driver for Renesas RZ/T2H platforms. diff --git a/drivers/pinctrl/renesas/pinctrl-rzt2h.c b/drivers/pinctrl/renesas/pinctrl-rzt2h.c index 4826ff91cd90..9949108a35bb 100644 --- a/drivers/pinctrl/renesas/pinctrl-rzt2h.c +++ b/drivers/pinctrl/renesas/pinctrl-rzt2h.c @@ -18,6 +18,7 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/of_device.h> +#include <linux/of_irq.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/spinlock.h> @@ -51,6 +52,7 @@ #define PFC_MASK GENMASK_ULL(5, 0) #define PFC_PIN_MASK(pin) (PFC_MASK << ((pin) * 8)) +#define PFC_FUNC_INTERRUPT 0 /* * Use 16 lower bits [15:0] for pin identifier @@ -64,6 +66,9 @@ #define RZT2H_MAX_SAFETY_PORTS 12 +#define RZT2H_INTERRUPTS_START 16 +#define RZT2H_INTERRUPTS_NUM 17 + struct rzt2h_pinctrl_data { unsigned int n_port_pins; const u8 *port_pin_configs; @@ -79,9 +84,11 @@ struct rzt2h_pinctrl { struct device *dev; struct gpio_chip gpio_chip; struct pinctrl_gpio_range gpio_range; + DECLARE_BITMAP(used_irqs, RZT2H_INTERRUPTS_NUM); spinlock_t lock; /* lock read/write registers */ struct mutex mutex; /* serialize adding groups and functions */ bool safety_port_enabled; + atomic_t wakeup_path; }; #define RZT2H_GET_BASE(pctrl, port) \ @@ -119,6 +126,19 @@ static int rzt2h_validate_pin(struct rzt2h_pinctrl *pctrl, unsigned int offset) return (pincfg & BIT(pin)) ? 0 : -EINVAL; } +static void rzt2h_pinctrl_set_gpio_en(struct rzt2h_pinctrl *pctrl, + u8 port, u8 pin, bool en) +{ + u8 reg = rzt2h_pinctrl_readb(pctrl, port, PMC(port)); + + if (en) + reg &= ~BIT(pin); + else + reg |= BIT(pin); + + rzt2h_pinctrl_writeb(pctrl, port, reg, PMC(port)); +} + static void rzt2h_pinctrl_set_pfc_mode(struct rzt2h_pinctrl *pctrl, u8 port, u8 pin, u8 func) { @@ -133,8 +153,7 @@ static void rzt2h_pinctrl_set_pfc_mode(struct rzt2h_pinctrl *pctrl, rzt2h_pinctrl_writew(pctrl, port, reg16, PM(port)); /* Temporarily switch to GPIO mode with PMC register */ - reg16 = rzt2h_pinctrl_readb(pctrl, port, PMC(port)); - rzt2h_pinctrl_writeb(pctrl, port, reg16 & ~BIT(pin), PMC(port)); + rzt2h_pinctrl_set_gpio_en(pctrl, port, pin, true); /* Select Pin function mode with PFC register */ reg64 = rzt2h_pinctrl_readq(pctrl, port, PFC(port)); @@ -142,8 +161,7 @@ static void rzt2h_pinctrl_set_pfc_mode(struct rzt2h_pinctrl *pctrl, rzt2h_pinctrl_writeq(pctrl, port, reg64 | ((u64)func << (pin * 8)), PFC(port)); /* Switch to Peripheral pin function with PMC register */ - reg16 = rzt2h_pinctrl_readb(pctrl, port, PMC(port)); - rzt2h_pinctrl_writeb(pctrl, port, reg16 | BIT(pin), PMC(port)); + rzt2h_pinctrl_set_gpio_en(pctrl, port, pin, false); } static int rzt2h_pinctrl_set_mux(struct pinctrl_dev *pctldev, @@ -447,7 +465,6 @@ static int rzt2h_gpio_request(struct gpio_chip *chip, unsigned int offset) u8 port = RZT2H_PIN_ID_TO_PORT(offset); u8 bit = RZT2H_PIN_ID_TO_PIN(offset); int ret; - u8 reg; ret = rzt2h_validate_pin(pctrl, offset); if (ret) @@ -460,9 +477,7 @@ static int rzt2h_gpio_request(struct gpio_chip *chip, unsigned int offset) guard(spinlock_irqsave)(&pctrl->lock); /* Select GPIO mode in PMC Register */ - reg = rzt2h_pinctrl_readb(pctrl, port, PMC(port)); - reg &= ~BIT(bit); - rzt2h_pinctrl_writeb(pctrl, port, reg, PMC(port)); + rzt2h_pinctrl_set_gpio_en(pctrl, port, bit, true); return 0; } @@ -486,6 +501,7 @@ static int rzt2h_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) struct rzt2h_pinctrl *pctrl = gpiochip_get_data(chip); u8 port = RZT2H_PIN_ID_TO_PORT(offset); u8 bit = RZT2H_PIN_ID_TO_PIN(offset); + u64 reg64; u16 reg; int ret; @@ -493,8 +509,25 @@ static int rzt2h_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) if (ret) return ret; - if (rzt2h_pinctrl_readb(pctrl, port, PMC(port)) & BIT(bit)) + guard(spinlock_irqsave)(&pctrl->lock); + + if (rzt2h_pinctrl_readb(pctrl, port, PMC(port)) & BIT(bit)) { + /* + * When a GPIO is being requested as an IRQ, the pinctrl + * framework expects to be able to read the GPIO's direction. + * IRQ function is separate from GPIO, and enabling it takes the + * pin out of GPIO mode. + * At this point, .child_to_parent_hwirq() has already been + * called to enable the IRQ function. + * Default to input direction for IRQ function. + */ + reg64 = rzt2h_pinctrl_readq(pctrl, port, PFC(port)); + reg64 = (reg64 >> (bit * 8)) & PFC_MASK; + if (reg64 == PFC_FUNC_INTERRUPT) + return GPIO_LINE_DIRECTION_IN; + return -EINVAL; + } reg = rzt2h_pinctrl_readw(pctrl, port, PM(port)); reg = (reg >> (bit * 2)) & PM_MASK; @@ -617,14 +650,185 @@ static const char * const rzt2h_gpio_names[] = { "P35_0", "P35_1", "P35_2", "P35_3", "P35_4", "P35_5", "P35_6", "P35_7", }; +/* + * Interrupts 0-15 are for INTCPUn, which are not exposed externally. + * Interrupts 16-31 are for IRQn. SEI is 32. + * This table matches the information found in User Manual's Section + * 17.5, Multiplexed Pin Configurations, Tables 17.5 to 17.40, on the + * Interrupt rows. + * RZ/N2H has the same GPIO to IRQ mapping, except for the pins which + * are not present. + */ +static const u8 rzt2h_gpio_irq_map[] = { + 32, 16, 17, 18, 19, 0, 20, 21, + 22, 0, 0, 0, 0, 0, 0, 0, + 23, 24, 25, 26, 27, 0, 0, 0, + 0, 0, 28, 29, 30, 31, 0, 0, + 0, 0, 0, 0, 0, 32, 16, 17, + 18, 19, 20, 21, 22, 0, 0, 0, + 0, 0, 24, 25, 26, 27, 0, 28, + 29, 30, 31, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 24, 32, 16, + 0, 0, 0, 0, 0, 0, 0, 0, + 20, 23, 17, 18, 19, 0, 16, 25, + 29, 20, 21, 22, 23, 0, 0, 0, + 0, 0, 0, 0, 17, 0, 0, 18, + 0, 0, 19, 0, 0, 20, 0, 30, + 21, 0, 0, 22, 0, 0, 24, 25, + 0, 0, 0, 0, 0, 16, 17, 0, + 18, 0, 0, 26, 27, 0, 0, 0, + 28, 29, 30, 31, 0, 0, 0, 0, + 23, 31, 32, 16, 17, 18, 19, 20, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 27, 0, 0, 21, 22, 23, 24, 25, + 26, 0, 0, 0, 0, 0, 0, 0, + 27, 28, 29, 30, 31, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 28, 32, 16, + 17, 18, 19, 0, 0, 0, 0, 20, + 21, 22, 23, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 24, 25, 0, 0, + 0, 0, 26, 27, 0, 0, 0, 30, + 0, 29, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 28, 29, 30, 31, 0, + 0, 0, 0, 0, 0, 0, 0, 30, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static void rzt2h_gpio_irq_disable(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + unsigned int hwirq = irqd_to_hwirq(d); + + irq_chip_disable_parent(d); + gpiochip_disable_irq(gc, hwirq); +} + +static void rzt2h_gpio_irq_enable(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + unsigned int hwirq = irqd_to_hwirq(d); + + gpiochip_enable_irq(gc, hwirq); + irq_chip_enable_parent(d); +} + +static int rzt2h_gpio_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct rzt2h_pinctrl *pctrl = container_of(gc, struct rzt2h_pinctrl, gpio_chip); + int ret; + + ret = irq_chip_set_wake_parent(d, on); + if (ret) + return ret; + + /* + * If any of the IRQs are in use, put the entire pin controller on the + * device wakeup path. + */ + if (on) + atomic_inc(&pctrl->wakeup_path); + else + atomic_dec(&pctrl->wakeup_path); + + return 0; +} + +static const struct irq_chip rzt2h_gpio_irqchip = { + .name = "rzt2h-gpio", + .irq_disable = rzt2h_gpio_irq_disable, + .irq_enable = rzt2h_gpio_irq_enable, + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_set_type = irq_chip_set_type_parent, + .irq_set_wake = rzt2h_gpio_irq_set_wake, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static int rzt2h_gpio_child_to_parent_hwirq(struct gpio_chip *gc, + unsigned int child, + unsigned int child_type, + unsigned int *parent, + unsigned int *parent_type) +{ + struct rzt2h_pinctrl *pctrl = gpiochip_get_data(gc); + u8 port = RZT2H_PIN_ID_TO_PORT(child); + u8 pin = RZT2H_PIN_ID_TO_PIN(child); + u8 parent_irq; + + parent_irq = rzt2h_gpio_irq_map[child]; + if (parent_irq < RZT2H_INTERRUPTS_START) + return -EINVAL; + + if (test_and_set_bit(parent_irq - RZT2H_INTERRUPTS_START, + pctrl->used_irqs)) + return -EBUSY; + + rzt2h_pinctrl_set_pfc_mode(pctrl, port, pin, PFC_FUNC_INTERRUPT); + + *parent = parent_irq; + *parent_type = child_type; + + return 0; +} + +static void rzt2h_gpio_irq_domain_free(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs) +{ + struct irq_data *d = irq_domain_get_irq_data(domain, virq); + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct rzt2h_pinctrl *pctrl = container_of(gc, struct rzt2h_pinctrl, gpio_chip); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + u8 port = RZT2H_PIN_ID_TO_PORT(hwirq); + u8 pin = RZT2H_PIN_ID_TO_PIN(hwirq); + + if (test_and_clear_bit(hwirq - RZT2H_INTERRUPTS_START, pctrl->used_irqs)) + rzt2h_pinctrl_set_gpio_en(pctrl, port, pin, false); + + irq_domain_free_irqs_common(domain, virq, nr_irqs); +} + +static void rzt2h_gpio_init_irq_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct rzt2h_pinctrl *pctrl = gpiochip_get_data(gc); + unsigned int offset; + + for (offset = 0; offset < ngpios; offset++) { + if (!rzt2h_gpio_irq_map[offset] || rzt2h_validate_pin(pctrl, offset)) + clear_bit(offset, valid_mask); + } +} + static int rzt2h_gpio_register(struct rzt2h_pinctrl *pctrl) { struct pinctrl_gpio_range *range = &pctrl->gpio_range; struct gpio_chip *chip = &pctrl->gpio_chip; + struct device_node *np = pctrl->dev->of_node; + struct irq_domain *parent_domain; struct device *dev = pctrl->dev; struct of_phandle_args of_args; + struct device_node *parent_np; + struct gpio_irq_chip *girq; int ret; + parent_np = of_irq_find_parent(np); + if (!parent_np) + return -ENXIO; + + parent_domain = irq_find_host(parent_np); + of_node_put(parent_np); + if (!parent_domain) + return -EPROBE_DEFER; + ret = of_parse_phandle_with_fixed_args(dev->of_node, "gpio-ranges", 3, 0, &of_args); if (ret) return dev_err_probe(dev, ret, "Unable to parse gpio-ranges\n"); @@ -648,6 +852,17 @@ static int rzt2h_gpio_register(struct rzt2h_pinctrl *pctrl) chip->set = rzt2h_gpio_set; chip->label = dev_name(dev); + if (of_property_present(np, "interrupt-controller")) { + girq = &chip->irq; + gpio_irq_chip_set_chip(girq, &rzt2h_gpio_irqchip); + girq->fwnode = dev_fwnode(pctrl->dev); + girq->parent_domain = parent_domain; + girq->child_to_parent_hwirq = rzt2h_gpio_child_to_parent_hwirq; + girq->populate_parent_alloc_arg = gpiochip_populate_parent_fwspec_twocell; + girq->child_irq_domain_ops.free = rzt2h_gpio_irq_domain_free; + girq->init_valid_mask = rzt2h_gpio_init_irq_valid_mask; + } + range->id = 0; range->pin_base = 0; range->base = 0; @@ -792,10 +1007,25 @@ static const struct of_device_id rzt2h_pinctrl_of_table[] = { { /* sentinel */ } }; +static int rzt2h_pinctrl_suspend_noirq(struct device *dev) +{ + struct rzt2h_pinctrl *pctrl = dev_get_drvdata(dev); + + if (atomic_read(&pctrl->wakeup_path)) + device_set_wakeup_path(dev); + + return 0; +} + +static const struct dev_pm_ops rzt2h_pinctrl_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(rzt2h_pinctrl_suspend_noirq, NULL) +}; + static struct platform_driver rzt2h_pinctrl_driver = { .driver = { .name = DRV_NAME, .of_match_table = of_match_ptr(rzt2h_pinctrl_of_table), + .pm = pm_sleep_ptr(&rzt2h_pinctrl_pm_ops), .suppress_bind_attrs = true, }, .probe = rzt2h_pinctrl_probe, |
