// SPDX-License-Identifier: GPL-2.0 /* * Renesas RZ/V2H(P) ICU Driver * * Based on irq-renesas-rzg2l.c * * Copyright (C) 2024 Renesas Electronics Corporation. * * Author: Fabrizio Castro */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* DT "interrupts" indexes */ #define ICU_IRQ_START 1 #define ICU_IRQ_COUNT 16 #define ICU_TINT_START (ICU_IRQ_START + ICU_IRQ_COUNT) #define ICU_TINT_COUNT 32 #define ICU_NUM_IRQ (ICU_TINT_START + ICU_TINT_COUNT) /* Registers */ #define ICU_NSCNT 0x00 #define ICU_NSCLR 0x04 #define ICU_NITSR 0x08 #define ICU_ISCTR 0x10 #define ICU_ISCLR 0x14 #define ICU_IITSR 0x18 #define ICU_TSCTR 0x20 #define ICU_TSCLR 0x24 #define ICU_TITSR(k) (0x28 + (k) * 4) #define ICU_TSSR(k) (0x30 + (k) * 4) /* NMI */ #define ICU_NMI_EDGE_FALLING 0 #define ICU_NMI_EDGE_RISING 1 #define ICU_NSCLR_NCLR BIT(0) /* IRQ */ #define ICU_IRQ_LEVEL_LOW 0 #define ICU_IRQ_EDGE_FALLING 1 #define ICU_IRQ_EDGE_RISING 2 #define ICU_IRQ_EDGE_BOTH 3 #define ICU_IITSR_IITSEL_PREP(iitsel, n) ((iitsel) << ((n) * 2)) #define ICU_IITSR_IITSEL_GET(iitsr, n) (((iitsr) >> ((n) * 2)) & 0x03) #define ICU_IITSR_IITSEL_MASK(n) ICU_IITSR_IITSEL_PREP(0x03, n) /* TINT */ #define ICU_TINT_EDGE_RISING 0 #define ICU_TINT_EDGE_FALLING 1 #define ICU_TINT_LEVEL_HIGH 2 #define ICU_TINT_LEVEL_LOW 3 #define ICU_TSSR_TSSEL_PREP(tssel, n, field_width) ((tssel) << ((n) * (field_width))) #define ICU_TSSR_TSSEL_MASK(n, field_width) \ ({\ typeof(field_width) (_field_width) = (field_width); \ ICU_TSSR_TSSEL_PREP((GENMASK(((_field_width) - 2), 0)), (n), _field_width); \ }) #define ICU_TSSR_TIEN(n, field_width) \ ({\ typeof(field_width) (_field_width) = (field_width); \ BIT((_field_width) - 1) << ((n) * (_field_width)); \ }) #define ICU_TITSR_K(tint_nr) ((tint_nr) / 16) #define ICU_TITSR_TITSEL_N(tint_nr) ((tint_nr) % 16) #define ICU_TITSR_TITSEL_PREP(titsel, n) ICU_IITSR_IITSEL_PREP(titsel, n) #define ICU_TITSR_TITSEL_MASK(n) ICU_IITSR_IITSEL_MASK(n) #define ICU_TITSR_TITSEL_GET(titsr, n) ICU_IITSR_IITSEL_GET(titsr, n) #define ICU_TINT_EXTRACT_HWIRQ(x) FIELD_GET(GENMASK(15, 0), (x)) #define ICU_TINT_EXTRACT_GPIOINT(x) FIELD_GET(GENMASK(31, 16), (x)) #define ICU_RZG3E_TINT_OFFSET 0x800 #define ICU_RZG3E_TSSEL_MAX_VAL 0x8c #define ICU_RZV2H_TSSEL_MAX_VAL 0x55 /** * struct rzv2h_hw_info - Interrupt Control Unit controller hardware info structure. * @tssel_lut: TINT lookup table * @t_offs: TINT offset * @max_tssel: TSSEL max value * @field_width: TSSR field width */ struct rzv2h_hw_info { const u8 *tssel_lut; u16 t_offs; u8 max_tssel; u8 field_width; }; /** * struct rzv2h_icu_priv - Interrupt Control Unit controller private data structure. * @base: Controller's base address * @fwspec: IRQ firmware specific data * @lock: Lock to serialize access to hardware registers * @info: Pointer to struct rzv2h_hw_info */ struct rzv2h_icu_priv { void __iomem *base; struct irq_fwspec fwspec[ICU_NUM_IRQ]; raw_spinlock_t lock; const struct rzv2h_hw_info *info; }; static inline struct rzv2h_icu_priv *irq_data_to_priv(struct irq_data *data) { return data->domain->host_data; } static void rzv2h_icu_eoi(struct irq_data *d) { struct rzv2h_icu_priv *priv = irq_data_to_priv(d); unsigned int hw_irq = irqd_to_hwirq(d); unsigned int tintirq_nr; u32 bit; scoped_guard(raw_spinlock, &priv->lock) { if (hw_irq >= ICU_TINT_START) { tintirq_nr = hw_irq - ICU_TINT_START; bit = BIT(tintirq_nr); if (!irqd_is_level_type(d)) writel_relaxed(bit, priv->base + priv->info->t_offs + ICU_TSCLR); } else if (hw_irq >= ICU_IRQ_START) { tintirq_nr = hw_irq - ICU_IRQ_START; bit = BIT(tintirq_nr); if (!irqd_is_level_type(d)) writel_relaxed(bit, priv->base + ICU_ISCLR); } else { writel_relaxed(ICU_NSCLR_NCLR, priv->base + ICU_NSCLR); } } irq_chip_eoi_parent(d); } static void rzv2h_tint_irq_endisable(struct irq_data *d, bool enable) { struct rzv2h_icu_priv *priv = irq_data_to_priv(d); unsigned int hw_irq = irqd_to_hwirq(d); u32 tint_nr, tssel_n, k, tssr; u8 nr_tint; if (hw_irq < ICU_TINT_START) return; tint_nr = hw_irq - ICU_TINT_START; nr_tint = 32 / priv->info->field_width; k = tint_nr / nr_tint; tssel_n = tint_nr % nr_tint; guard(raw_spinlock)(&priv->lock); tssr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TSSR(k)); if (enable) tssr |= ICU_TSSR_TIEN(tssel_n, priv->info->field_width); else tssr &= ~ICU_TSSR_TIEN(tssel_n, priv->info->field_width); writel_relaxed(tssr, priv->base + priv->info->t_offs + ICU_TSSR(k)); } static void rzv2h_icu_irq_disable(struct irq_data *d) { irq_chip_disable_parent(d); rzv2h_tint_irq_endisable(d, false); } static void rzv2h_icu_irq_enable(struct irq_data *d) { rzv2h_tint_irq_endisable(d, true); irq_chip_enable_parent(d); } static int rzv2h_nmi_set_type(struct irq_data *d, unsigned int type) { struct rzv2h_icu_priv *priv = irq_data_to_priv(d); u32 sense; switch (type & IRQ_TYPE_SENSE_MASK) { case IRQ_TYPE_EDGE_FALLING: sense = ICU_NMI_EDGE_FALLING; break; case IRQ_TYPE_EDGE_RISING: sense = ICU_NMI_EDGE_RISING; break; default: return -EINVAL; } writel_relaxed(sense, priv->base + ICU_NITSR); return 0; } static void rzv2h_clear_irq_int(struct rzv2h_icu_priv *priv, unsigned int hwirq) { unsigned int irq_nr = hwirq - ICU_IRQ_START; u32 isctr, iitsr, iitsel; u32 bit = BIT(irq_nr); isctr = readl_relaxed(priv->base + ICU_ISCTR); iitsr = readl_relaxed(priv->base + ICU_IITSR); iitsel = ICU_IITSR_IITSEL_GET(iitsr, irq_nr); /* * When level sensing is used, the interrupt flag gets automatically cleared when the * interrupt signal is de-asserted by the source of the interrupt request, therefore clear * the interrupt only for edge triggered interrupts. */ if ((isctr & bit) && (iitsel != ICU_IRQ_LEVEL_LOW)) writel_relaxed(bit, priv->base + ICU_ISCLR); } static int rzv2h_irq_set_type(struct irq_data *d, unsigned int type) { struct rzv2h_icu_priv *priv = irq_data_to_priv(d); unsigned int hwirq = irqd_to_hwirq(d); u32 irq_nr = hwirq - ICU_IRQ_START; u32 iitsr, sense; switch (type & IRQ_TYPE_SENSE_MASK) { case IRQ_TYPE_LEVEL_LOW: sense = ICU_IRQ_LEVEL_LOW; break; case IRQ_TYPE_EDGE_FALLING: sense = ICU_IRQ_EDGE_FALLING; break; case IRQ_TYPE_EDGE_RISING: sense = ICU_IRQ_EDGE_RISING; break; case IRQ_TYPE_EDGE_BOTH: sense = ICU_IRQ_EDGE_BOTH; break; default: return -EINVAL; } guard(raw_spinlock)(&priv->lock); iitsr = readl_relaxed(priv->base + ICU_IITSR); iitsr &= ~ICU_IITSR_IITSEL_MASK(irq_nr); iitsr |= ICU_IITSR_IITSEL_PREP(sense, irq_nr); rzv2h_clear_irq_int(priv, hwirq); writel_relaxed(iitsr, priv->base + ICU_IITSR); return 0; } static void rzv2h_clear_tint_int(struct rzv2h_icu_priv *priv, unsigned int hwirq) { unsigned int tint_nr = hwirq - ICU_TINT_START; int titsel_n = ICU_TITSR_TITSEL_N(tint_nr); u32 tsctr, titsr, titsel; u32 bit = BIT(tint_nr); int k = tint_nr / 16; tsctr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TSCTR); titsr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TITSR(k)); titsel = ICU_TITSR_TITSEL_GET(titsr, titsel_n); /* * Writing 1 to the corresponding flag from register ICU_TSCTR only has effect if * TSTATn = 1b and if it's a rising edge or a falling edge interrupt. */ if ((tsctr & bit) && ((titsel == ICU_TINT_EDGE_RISING) || (titsel == ICU_TINT_EDGE_FALLING))) writel_relaxed(bit, priv->base + priv->info->t_offs + ICU_TSCLR); } static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type) { u32 titsr, titsr_k, titsel_n, tien; struct rzv2h_icu_priv *priv; u32 tssr, tssr_k, tssel_n; unsigned int hwirq; u32 tint, sense; int tint_nr; u8 nr_tint; switch (type & IRQ_TYPE_SENSE_MASK) { case IRQ_TYPE_LEVEL_LOW: sense = ICU_TINT_LEVEL_LOW; break; case IRQ_TYPE_LEVEL_HIGH: sense = ICU_TINT_LEVEL_HIGH; break; case IRQ_TYPE_EDGE_RISING: sense = ICU_TINT_EDGE_RISING; break; case IRQ_TYPE_EDGE_FALLING: sense = ICU_TINT_EDGE_FALLING; break; default: return -EINVAL; } priv = irq_data_to_priv(d); tint = (u32)(uintptr_t)irq_data_get_irq_chip_data(d); if (tint > priv->info->max_tssel) return -EINVAL; if (priv->info->tssel_lut) tint = priv->info->tssel_lut[tint]; hwirq = irqd_to_hwirq(d); tint_nr = hwirq - ICU_TINT_START; nr_tint = 32 / priv->info->field_width; tssr_k = tint_nr / nr_tint; tssel_n = tint_nr % nr_tint; tien = ICU_TSSR_TIEN(tssel_n, priv->info->field_width); titsr_k = ICU_TITSR_K(tint_nr); titsel_n = ICU_TITSR_TITSEL_N(tint_nr); guard(raw_spinlock)(&priv->lock); tssr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TSSR(tssr_k)); tssr &= ~(ICU_TSSR_TSSEL_MASK(tssel_n, priv->info->field_width) | tien); tssr |= ICU_TSSR_TSSEL_PREP(tint, tssel_n, priv->info->field_width); writel_relaxed(tssr, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k)); titsr = readl_relaxed(priv->base + priv->info->t_offs + ICU_TITSR(titsr_k)); titsr &= ~ICU_TITSR_TITSEL_MASK(titsel_n); titsr |= ICU_TITSR_TITSEL_PREP(sense, titsel_n); writel_relaxed(titsr, priv->base + priv->info->t_offs + ICU_TITSR(titsr_k)); rzv2h_clear_tint_int(priv, hwirq); writel_relaxed(tssr | tien, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k)); return 0; } static int rzv2h_icu_set_type(struct irq_data *d, unsigned int type) { unsigned int hw_irq = irqd_to_hwirq(d); int ret; if (hw_irq >= ICU_TINT_START) ret = rzv2h_tint_set_type(d, type); else if (hw_irq >= ICU_IRQ_START) ret = rzv2h_irq_set_type(d, type); else ret = rzv2h_nmi_set_type(d, type); if (ret) return ret; return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH); } static const struct irq_chip rzv2h_icu_chip = { .name = "rzv2h-icu", .irq_eoi = rzv2h_icu_eoi, .irq_mask = irq_chip_mask_parent, .irq_unmask = irq_chip_unmask_parent, .irq_disable = rzv2h_icu_irq_disable, .irq_enable = rzv2h_icu_irq_enable, .irq_get_irqchip_state = irq_chip_get_parent_state, .irq_set_irqchip_state = irq_chip_set_parent_state, .irq_retrigger = irq_chip_retrigger_hierarchy, .irq_set_type = rzv2h_icu_set_type, .irq_set_affinity = irq_chip_set_affinity_parent, .flags = IRQCHIP_SET_TYPE_MASKED, }; static int rzv2h_icu_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *arg) { struct rzv2h_icu_priv *priv = domain->host_data; unsigned long tint = 0; irq_hw_number_t hwirq; unsigned int type; int ret; ret = irq_domain_translate_twocell(domain, arg, &hwirq, &type); if (ret) return ret; /* * For TINT interrupts the hwirq and TINT are encoded in * fwspec->param[0]. * hwirq is embedded in bits 0-15. * TINT is embedded in bits 16-31. */ if (hwirq >= ICU_TINT_START) { tint = ICU_TINT_EXTRACT_GPIOINT(hwirq); hwirq = ICU_TINT_EXTRACT_HWIRQ(hwirq); if (hwirq < ICU_TINT_START) return -EINVAL; } if (hwirq > (ICU_NUM_IRQ - 1)) return -EINVAL; ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &rzv2h_icu_chip, (void *)(uintptr_t)tint); if (ret) return ret; return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &priv->fwspec[hwirq]); } static const struct irq_domain_ops rzv2h_icu_domain_ops = { .alloc = rzv2h_icu_alloc, .free = irq_domain_free_irqs_common, .translate = irq_domain_translate_twocell, }; static int rzv2h_icu_parse_interrupts(struct rzv2h_icu_priv *priv, struct device_node *np) { struct of_phandle_args map; unsigned int i; int ret; for (i = 0; i < ICU_NUM_IRQ; i++) { ret = of_irq_parse_one(np, i, &map); if (ret) return ret; of_phandle_args_to_fwspec(np, map.args, map.args_count, &priv->fwspec[i]); } return 0; } static void rzv2h_icu_put_device(void *data) { put_device(data); } static int rzv2h_icu_init_common(struct device_node *node, struct device_node *parent, const struct rzv2h_hw_info *hw_info) { struct irq_domain *irq_domain, *parent_domain; struct rzv2h_icu_priv *rzv2h_icu_data; struct platform_device *pdev; struct reset_control *resetn; int ret; pdev = of_find_device_by_node(node); if (!pdev) return -ENODEV; ret = devm_add_action_or_reset(&pdev->dev, rzv2h_icu_put_device, &pdev->dev); if (ret < 0) return ret; parent_domain = irq_find_host(parent); if (!parent_domain) { dev_err(&pdev->dev, "cannot find parent domain\n"); return -ENODEV; } rzv2h_icu_data = devm_kzalloc(&pdev->dev, sizeof(*rzv2h_icu_data), GFP_KERNEL); if (!rzv2h_icu_data) return -ENOMEM; rzv2h_icu_data->base = devm_of_iomap(&pdev->dev, pdev->dev.of_node, 0, NULL); if (IS_ERR(rzv2h_icu_data->base)) return PTR_ERR(rzv2h_icu_data->base); ret = rzv2h_icu_parse_interrupts(rzv2h_icu_data, node); if (ret) { dev_err(&pdev->dev, "cannot parse interrupts: %d\n", ret); return ret; } resetn = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); if (IS_ERR(resetn)) { ret = PTR_ERR(resetn); dev_err(&pdev->dev, "failed to acquire deasserted reset: %d\n", ret); return ret; } ret = devm_pm_runtime_enable(&pdev->dev); if (ret < 0) { dev_err(&pdev->dev, "devm_pm_runtime_enable failed, %d\n", ret); return ret; } ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) { dev_err(&pdev->dev, "pm_runtime_resume_and_get failed: %d\n", ret); return ret; } raw_spin_lock_init(&rzv2h_icu_data->lock); irq_domain = irq_domain_add_hierarchy(parent_domain, 0, ICU_NUM_IRQ, node, &rzv2h_icu_domain_ops, rzv2h_icu_data); if (!irq_domain) { dev_err(&pdev->dev, "failed to add irq domain\n"); ret = -ENOMEM; goto pm_put; } rzv2h_icu_data->info = hw_info; /* * coccicheck complains about a missing put_device call before returning, but it's a false * positive. We still need &pdev->dev after successfully returning from this function. */ return 0; pm_put: pm_runtime_put(&pdev->dev); return ret; } /* Mapping based on port index on Table 4.2-6 and TSSEL bits on Table 4.6-4 */ static const u8 rzg3e_tssel_lut[] = { 81, 82, 83, 84, 85, 86, 87, 88, /* P00-P07 */ 89, 90, 91, 92, 93, 94, 95, 96, /* P10-P17 */ 111, 112, /* P20-P21 */ 97, 98, 99, 100, 101, 102, 103, 104, /* P30-P37 */ 105, 106, 107, 108, 109, 110, /* P40-P45 */ 113, 114, 115, 116, 117, 118, 119, /* P50-P56 */ 120, 121, 122, 123, 124, 125, 126, /* P60-P66 */ 127, 128, 129, 130, 131, 132, 133, 134, /* P70-P77 */ 135, 136, 137, 138, 139, 140, /* P80-P85 */ 43, 44, 45, 46, 47, 48, 49, 50, /* PA0-PA7 */ 51, 52, 53, 54, 55, 56, 57, 58, /* PB0-PB7 */ 59, 60, 61, /* PC0-PC2 */ 62, 63, 64, 65, 66, 67, 68, 69, /* PD0-PD7 */ 70, 71, 72, 73, 74, 75, 76, 77, /* PE0-PE7 */ 78, 79, 80, /* PF0-PF2 */ 25, 26, 27, 28, 29, 30, 31, 32, /* PG0-PG7 */ 33, 34, 35, 36, 37, 38, /* PH0-PH5 */ 4, 5, 6, 7, 8, /* PJ0-PJ4 */ 39, 40, 41, 42, /* PK0-PK3 */ 9, 10, 11, 12, 21, 22, 23, 24, /* PL0-PL7 */ 13, 14, 15, 16, 17, 18, 19, 20, /* PM0-PM7 */ 0, 1, 2, 3 /* PS0-PS3 */ }; static const struct rzv2h_hw_info rzg3e_hw_params = { .tssel_lut = rzg3e_tssel_lut, .t_offs = ICU_RZG3E_TINT_OFFSET, .max_tssel = ICU_RZG3E_TSSEL_MAX_VAL, .field_width = 16, }; static const struct rzv2h_hw_info rzv2h_hw_params = { .t_offs = 0, .max_tssel = ICU_RZV2H_TSSEL_MAX_VAL, .field_width = 8, }; static int rzg3e_icu_init(struct device_node *node, struct device_node *parent) { return rzv2h_icu_init_common(node, parent, &rzg3e_hw_params); } static int rzv2h_icu_init(struct device_node *node, struct device_node *parent) { return rzv2h_icu_init_common(node, parent, &rzv2h_hw_params); } IRQCHIP_PLATFORM_DRIVER_BEGIN(rzv2h_icu) IRQCHIP_MATCH("renesas,r9a09g047-icu", rzg3e_icu_init) IRQCHIP_MATCH("renesas,r9a09g057-icu", rzv2h_icu_init) IRQCHIP_PLATFORM_DRIVER_END(rzv2h_icu) MODULE_AUTHOR("Fabrizio Castro "); MODULE_DESCRIPTION("Renesas RZ/V2H(P) ICU Driver");