irq-ls-extirq.c (4552B)
1// SPDX-License-Identifier: GPL-2.0 2 3#define pr_fmt(fmt) "irq-ls-extirq: " fmt 4 5#include <linux/irq.h> 6#include <linux/irqchip.h> 7#include <linux/irqdomain.h> 8#include <linux/of.h> 9#include <linux/mfd/syscon.h> 10#include <linux/regmap.h> 11#include <linux/slab.h> 12 13#include <dt-bindings/interrupt-controller/arm-gic.h> 14 15#define MAXIRQ 12 16#define LS1021A_SCFGREVCR 0x200 17 18struct ls_extirq_data { 19 struct regmap *syscon; 20 u32 intpcr; 21 bool is_ls1021a_or_ls1043a; 22 u32 nirq; 23 struct irq_fwspec map[MAXIRQ]; 24}; 25 26static int 27ls_extirq_set_type(struct irq_data *data, unsigned int type) 28{ 29 struct ls_extirq_data *priv = data->chip_data; 30 irq_hw_number_t hwirq = data->hwirq; 31 u32 value, mask; 32 33 if (priv->is_ls1021a_or_ls1043a) 34 mask = 1U << (31 - hwirq); 35 else 36 mask = 1U << hwirq; 37 38 switch (type) { 39 case IRQ_TYPE_LEVEL_LOW: 40 type = IRQ_TYPE_LEVEL_HIGH; 41 value = mask; 42 break; 43 case IRQ_TYPE_EDGE_FALLING: 44 type = IRQ_TYPE_EDGE_RISING; 45 value = mask; 46 break; 47 case IRQ_TYPE_LEVEL_HIGH: 48 case IRQ_TYPE_EDGE_RISING: 49 value = 0; 50 break; 51 default: 52 return -EINVAL; 53 } 54 regmap_update_bits(priv->syscon, priv->intpcr, mask, value); 55 56 return irq_chip_set_type_parent(data, type); 57} 58 59static struct irq_chip ls_extirq_chip = { 60 .name = "ls-extirq", 61 .irq_mask = irq_chip_mask_parent, 62 .irq_unmask = irq_chip_unmask_parent, 63 .irq_eoi = irq_chip_eoi_parent, 64 .irq_set_type = ls_extirq_set_type, 65 .irq_retrigger = irq_chip_retrigger_hierarchy, 66 .irq_set_affinity = irq_chip_set_affinity_parent, 67 .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE, 68}; 69 70static int 71ls_extirq_domain_alloc(struct irq_domain *domain, unsigned int virq, 72 unsigned int nr_irqs, void *arg) 73{ 74 struct ls_extirq_data *priv = domain->host_data; 75 struct irq_fwspec *fwspec = arg; 76 irq_hw_number_t hwirq; 77 78 if (fwspec->param_count != 2) 79 return -EINVAL; 80 81 hwirq = fwspec->param[0]; 82 if (hwirq >= priv->nirq) 83 return -EINVAL; 84 85 irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &ls_extirq_chip, 86 priv); 87 88 return irq_domain_alloc_irqs_parent(domain, virq, 1, &priv->map[hwirq]); 89} 90 91static const struct irq_domain_ops extirq_domain_ops = { 92 .xlate = irq_domain_xlate_twocell, 93 .alloc = ls_extirq_domain_alloc, 94 .free = irq_domain_free_irqs_common, 95}; 96 97static int 98ls_extirq_parse_map(struct ls_extirq_data *priv, struct device_node *node) 99{ 100 const __be32 *map; 101 u32 mapsize; 102 int ret; 103 104 map = of_get_property(node, "interrupt-map", &mapsize); 105 if (!map) 106 return -ENOENT; 107 if (mapsize % sizeof(*map)) 108 return -EINVAL; 109 mapsize /= sizeof(*map); 110 111 while (mapsize) { 112 struct device_node *ipar; 113 u32 hwirq, intsize, j; 114 115 if (mapsize < 3) 116 return -EINVAL; 117 hwirq = be32_to_cpup(map); 118 if (hwirq >= MAXIRQ) 119 return -EINVAL; 120 priv->nirq = max(priv->nirq, hwirq + 1); 121 122 ipar = of_find_node_by_phandle(be32_to_cpup(map + 2)); 123 map += 3; 124 mapsize -= 3; 125 if (!ipar) 126 return -EINVAL; 127 priv->map[hwirq].fwnode = &ipar->fwnode; 128 ret = of_property_read_u32(ipar, "#interrupt-cells", &intsize); 129 if (ret) 130 return ret; 131 132 if (intsize > mapsize) 133 return -EINVAL; 134 135 priv->map[hwirq].param_count = intsize; 136 for (j = 0; j < intsize; ++j) 137 priv->map[hwirq].param[j] = be32_to_cpup(map++); 138 mapsize -= intsize; 139 } 140 return 0; 141} 142 143static int __init 144ls_extirq_of_init(struct device_node *node, struct device_node *parent) 145{ 146 147 struct irq_domain *domain, *parent_domain; 148 struct ls_extirq_data *priv; 149 int ret; 150 151 parent_domain = irq_find_host(parent); 152 if (!parent_domain) { 153 pr_err("Cannot find parent domain\n"); 154 return -ENODEV; 155 } 156 157 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 158 if (!priv) 159 return -ENOMEM; 160 161 priv->syscon = syscon_node_to_regmap(node->parent); 162 if (IS_ERR(priv->syscon)) { 163 ret = PTR_ERR(priv->syscon); 164 pr_err("Failed to lookup parent regmap\n"); 165 goto out; 166 } 167 ret = of_property_read_u32(node, "reg", &priv->intpcr); 168 if (ret) { 169 pr_err("Missing INTPCR offset value\n"); 170 goto out; 171 } 172 173 ret = ls_extirq_parse_map(priv, node); 174 if (ret) 175 goto out; 176 177 priv->is_ls1021a_or_ls1043a = of_device_is_compatible(node, "fsl,ls1021a-extirq") || 178 of_device_is_compatible(node, "fsl,ls1043a-extirq"); 179 180 domain = irq_domain_add_hierarchy(parent_domain, 0, priv->nirq, node, 181 &extirq_domain_ops, priv); 182 if (!domain) 183 ret = -ENOMEM; 184 185out: 186 if (ret) 187 kfree(priv); 188 return ret; 189} 190 191IRQCHIP_DECLARE(ls1021a_extirq, "fsl,ls1021a-extirq", ls_extirq_of_init); 192IRQCHIP_DECLARE(ls1043a_extirq, "fsl,ls1043a-extirq", ls_extirq_of_init); 193IRQCHIP_DECLARE(ls1088a_extirq, "fsl,ls1088a-extirq", ls_extirq_of_init);