18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci 38c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "irq-ls-extirq: " fmt 48c2ecf20Sopenharmony_ci 58c2ecf20Sopenharmony_ci#include <linux/irq.h> 68c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 78c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 88c2ecf20Sopenharmony_ci#include <linux/of.h> 98c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h> 108c2ecf20Sopenharmony_ci#include <linux/regmap.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <dt-bindings/interrupt-controller/arm-gic.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#define MAXIRQ 12 168c2ecf20Sopenharmony_ci#define LS1021A_SCFGREVCR 0x200 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_cistruct ls_extirq_data { 198c2ecf20Sopenharmony_ci struct regmap *syscon; 208c2ecf20Sopenharmony_ci u32 intpcr; 218c2ecf20Sopenharmony_ci bool bit_reverse; 228c2ecf20Sopenharmony_ci u32 nirq; 238c2ecf20Sopenharmony_ci struct irq_fwspec map[MAXIRQ]; 248c2ecf20Sopenharmony_ci}; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic int 278c2ecf20Sopenharmony_cils_extirq_set_type(struct irq_data *data, unsigned int type) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci struct ls_extirq_data *priv = data->chip_data; 308c2ecf20Sopenharmony_ci irq_hw_number_t hwirq = data->hwirq; 318c2ecf20Sopenharmony_ci u32 value, mask; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci if (priv->bit_reverse) 348c2ecf20Sopenharmony_ci mask = 1U << (31 - hwirq); 358c2ecf20Sopenharmony_ci else 368c2ecf20Sopenharmony_ci mask = 1U << hwirq; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci switch (type) { 398c2ecf20Sopenharmony_ci case IRQ_TYPE_LEVEL_LOW: 408c2ecf20Sopenharmony_ci type = IRQ_TYPE_LEVEL_HIGH; 418c2ecf20Sopenharmony_ci value = mask; 428c2ecf20Sopenharmony_ci break; 438c2ecf20Sopenharmony_ci case IRQ_TYPE_EDGE_FALLING: 448c2ecf20Sopenharmony_ci type = IRQ_TYPE_EDGE_RISING; 458c2ecf20Sopenharmony_ci value = mask; 468c2ecf20Sopenharmony_ci break; 478c2ecf20Sopenharmony_ci case IRQ_TYPE_LEVEL_HIGH: 488c2ecf20Sopenharmony_ci case IRQ_TYPE_EDGE_RISING: 498c2ecf20Sopenharmony_ci value = 0; 508c2ecf20Sopenharmony_ci break; 518c2ecf20Sopenharmony_ci default: 528c2ecf20Sopenharmony_ci return -EINVAL; 538c2ecf20Sopenharmony_ci } 548c2ecf20Sopenharmony_ci regmap_update_bits(priv->syscon, priv->intpcr, mask, value); 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci return irq_chip_set_type_parent(data, type); 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic struct irq_chip ls_extirq_chip = { 608c2ecf20Sopenharmony_ci .name = "ls-extirq", 618c2ecf20Sopenharmony_ci .irq_mask = irq_chip_mask_parent, 628c2ecf20Sopenharmony_ci .irq_unmask = irq_chip_unmask_parent, 638c2ecf20Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 648c2ecf20Sopenharmony_ci .irq_set_type = ls_extirq_set_type, 658c2ecf20Sopenharmony_ci .irq_retrigger = irq_chip_retrigger_hierarchy, 668c2ecf20Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 678c2ecf20Sopenharmony_ci .flags = IRQCHIP_SET_TYPE_MASKED, 688c2ecf20Sopenharmony_ci}; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic int 718c2ecf20Sopenharmony_cils_extirq_domain_alloc(struct irq_domain *domain, unsigned int virq, 728c2ecf20Sopenharmony_ci unsigned int nr_irqs, void *arg) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci struct ls_extirq_data *priv = domain->host_data; 758c2ecf20Sopenharmony_ci struct irq_fwspec *fwspec = arg; 768c2ecf20Sopenharmony_ci irq_hw_number_t hwirq; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci if (fwspec->param_count != 2) 798c2ecf20Sopenharmony_ci return -EINVAL; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci hwirq = fwspec->param[0]; 828c2ecf20Sopenharmony_ci if (hwirq >= priv->nirq) 838c2ecf20Sopenharmony_ci return -EINVAL; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &ls_extirq_chip, 868c2ecf20Sopenharmony_ci priv); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci return irq_domain_alloc_irqs_parent(domain, virq, 1, &priv->map[hwirq]); 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic const struct irq_domain_ops extirq_domain_ops = { 928c2ecf20Sopenharmony_ci .xlate = irq_domain_xlate_twocell, 938c2ecf20Sopenharmony_ci .alloc = ls_extirq_domain_alloc, 948c2ecf20Sopenharmony_ci .free = irq_domain_free_irqs_common, 958c2ecf20Sopenharmony_ci}; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic int 988c2ecf20Sopenharmony_cils_extirq_parse_map(struct ls_extirq_data *priv, struct device_node *node) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci const __be32 *map; 1018c2ecf20Sopenharmony_ci u32 mapsize; 1028c2ecf20Sopenharmony_ci int ret; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci map = of_get_property(node, "interrupt-map", &mapsize); 1058c2ecf20Sopenharmony_ci if (!map) 1068c2ecf20Sopenharmony_ci return -ENOENT; 1078c2ecf20Sopenharmony_ci if (mapsize % sizeof(*map)) 1088c2ecf20Sopenharmony_ci return -EINVAL; 1098c2ecf20Sopenharmony_ci mapsize /= sizeof(*map); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci while (mapsize) { 1128c2ecf20Sopenharmony_ci struct device_node *ipar; 1138c2ecf20Sopenharmony_ci u32 hwirq, intsize, j; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci if (mapsize < 3) 1168c2ecf20Sopenharmony_ci return -EINVAL; 1178c2ecf20Sopenharmony_ci hwirq = be32_to_cpup(map); 1188c2ecf20Sopenharmony_ci if (hwirq >= MAXIRQ) 1198c2ecf20Sopenharmony_ci return -EINVAL; 1208c2ecf20Sopenharmony_ci priv->nirq = max(priv->nirq, hwirq + 1); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci ipar = of_find_node_by_phandle(be32_to_cpup(map + 2)); 1238c2ecf20Sopenharmony_ci map += 3; 1248c2ecf20Sopenharmony_ci mapsize -= 3; 1258c2ecf20Sopenharmony_ci if (!ipar) 1268c2ecf20Sopenharmony_ci return -EINVAL; 1278c2ecf20Sopenharmony_ci priv->map[hwirq].fwnode = &ipar->fwnode; 1288c2ecf20Sopenharmony_ci ret = of_property_read_u32(ipar, "#interrupt-cells", &intsize); 1298c2ecf20Sopenharmony_ci if (ret) 1308c2ecf20Sopenharmony_ci return ret; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci if (intsize > mapsize) 1338c2ecf20Sopenharmony_ci return -EINVAL; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci priv->map[hwirq].param_count = intsize; 1368c2ecf20Sopenharmony_ci for (j = 0; j < intsize; ++j) 1378c2ecf20Sopenharmony_ci priv->map[hwirq].param[j] = be32_to_cpup(map++); 1388c2ecf20Sopenharmony_ci mapsize -= intsize; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci return 0; 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic int __init 1448c2ecf20Sopenharmony_cils_extirq_of_init(struct device_node *node, struct device_node *parent) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci struct irq_domain *domain, *parent_domain; 1488c2ecf20Sopenharmony_ci struct ls_extirq_data *priv; 1498c2ecf20Sopenharmony_ci int ret; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci parent_domain = irq_find_host(parent); 1528c2ecf20Sopenharmony_ci if (!parent_domain) { 1538c2ecf20Sopenharmony_ci pr_err("Cannot find parent domain\n"); 1548c2ecf20Sopenharmony_ci return -ENODEV; 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 1588c2ecf20Sopenharmony_ci if (!priv) 1598c2ecf20Sopenharmony_ci return -ENOMEM; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci priv->syscon = syscon_node_to_regmap(node->parent); 1628c2ecf20Sopenharmony_ci if (IS_ERR(priv->syscon)) { 1638c2ecf20Sopenharmony_ci ret = PTR_ERR(priv->syscon); 1648c2ecf20Sopenharmony_ci pr_err("Failed to lookup parent regmap\n"); 1658c2ecf20Sopenharmony_ci goto out; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci ret = of_property_read_u32(node, "reg", &priv->intpcr); 1688c2ecf20Sopenharmony_ci if (ret) { 1698c2ecf20Sopenharmony_ci pr_err("Missing INTPCR offset value\n"); 1708c2ecf20Sopenharmony_ci goto out; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci ret = ls_extirq_parse_map(priv, node); 1748c2ecf20Sopenharmony_ci if (ret) 1758c2ecf20Sopenharmony_ci goto out; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci if (of_device_is_compatible(node, "fsl,ls1021a-extirq")) { 1788c2ecf20Sopenharmony_ci u32 revcr; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci ret = regmap_read(priv->syscon, LS1021A_SCFGREVCR, &revcr); 1818c2ecf20Sopenharmony_ci if (ret) 1828c2ecf20Sopenharmony_ci goto out; 1838c2ecf20Sopenharmony_ci priv->bit_reverse = (revcr != 0); 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci domain = irq_domain_add_hierarchy(parent_domain, 0, priv->nirq, node, 1878c2ecf20Sopenharmony_ci &extirq_domain_ops, priv); 1888c2ecf20Sopenharmony_ci if (!domain) 1898c2ecf20Sopenharmony_ci ret = -ENOMEM; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ciout: 1928c2ecf20Sopenharmony_ci if (ret) 1938c2ecf20Sopenharmony_ci kfree(priv); 1948c2ecf20Sopenharmony_ci return ret; 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(ls1021a_extirq, "fsl,ls1021a-extirq", ls_extirq_of_init); 198