irq-ti-sci-intr.c (8528B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Texas Instruments' K3 Interrupt Router irqchip driver 4 * 5 * Copyright (C) 2018-2019 Texas Instruments Incorporated - https://www.ti.com/ 6 * Lokesh Vutla <lokeshvutla@ti.com> 7 */ 8 9#include <linux/err.h> 10#include <linux/module.h> 11#include <linux/moduleparam.h> 12#include <linux/io.h> 13#include <linux/irqchip.h> 14#include <linux/irqdomain.h> 15#include <linux/of_platform.h> 16#include <linux/of_address.h> 17#include <linux/of_irq.h> 18#include <linux/soc/ti/ti_sci_protocol.h> 19 20/** 21 * struct ti_sci_intr_irq_domain - Structure representing a TISCI based 22 * Interrupt Router IRQ domain. 23 * @sci: Pointer to TISCI handle 24 * @out_irqs: TISCI resource pointer representing INTR irqs. 25 * @dev: Struct device pointer. 26 * @ti_sci_id: TI-SCI device identifier 27 * @type: Specifies the trigger type supported by this Interrupt Router 28 */ 29struct ti_sci_intr_irq_domain { 30 const struct ti_sci_handle *sci; 31 struct ti_sci_resource *out_irqs; 32 struct device *dev; 33 u32 ti_sci_id; 34 u32 type; 35}; 36 37static struct irq_chip ti_sci_intr_irq_chip = { 38 .name = "INTR", 39 .irq_eoi = irq_chip_eoi_parent, 40 .irq_mask = irq_chip_mask_parent, 41 .irq_unmask = irq_chip_unmask_parent, 42 .irq_set_type = irq_chip_set_type_parent, 43 .irq_retrigger = irq_chip_retrigger_hierarchy, 44 .irq_set_affinity = irq_chip_set_affinity_parent, 45}; 46 47/** 48 * ti_sci_intr_irq_domain_translate() - Retrieve hwirq and type from 49 * IRQ firmware specific handler. 50 * @domain: Pointer to IRQ domain 51 * @fwspec: Pointer to IRQ specific firmware structure 52 * @hwirq: IRQ number identified by hardware 53 * @type: IRQ type 54 * 55 * Return 0 if all went ok else appropriate error. 56 */ 57static int ti_sci_intr_irq_domain_translate(struct irq_domain *domain, 58 struct irq_fwspec *fwspec, 59 unsigned long *hwirq, 60 unsigned int *type) 61{ 62 struct ti_sci_intr_irq_domain *intr = domain->host_data; 63 64 if (fwspec->param_count != 1) 65 return -EINVAL; 66 67 *hwirq = fwspec->param[0]; 68 *type = intr->type; 69 70 return 0; 71} 72 73/** 74 * ti_sci_intr_xlate_irq() - Translate hwirq to parent's hwirq. 75 * @intr: IRQ domain corresponding to Interrupt Router 76 * @irq: Hardware irq corresponding to the above irq domain 77 * 78 * Return parent irq number if translation is available else -ENOENT. 79 */ 80static int ti_sci_intr_xlate_irq(struct ti_sci_intr_irq_domain *intr, u32 irq) 81{ 82 struct device_node *np = dev_of_node(intr->dev); 83 u32 base, pbase, size, len; 84 const __be32 *range; 85 86 range = of_get_property(np, "ti,interrupt-ranges", &len); 87 if (!range) 88 return irq; 89 90 for (len /= sizeof(*range); len >= 3; len -= 3) { 91 base = be32_to_cpu(*range++); 92 pbase = be32_to_cpu(*range++); 93 size = be32_to_cpu(*range++); 94 95 if (base <= irq && irq < base + size) 96 return irq - base + pbase; 97 } 98 99 return -ENOENT; 100} 101 102/** 103 * ti_sci_intr_irq_domain_free() - Free the specified IRQs from the domain. 104 * @domain: Domain to which the irqs belong 105 * @virq: Linux virtual IRQ to be freed. 106 * @nr_irqs: Number of continuous irqs to be freed 107 */ 108static void ti_sci_intr_irq_domain_free(struct irq_domain *domain, 109 unsigned int virq, unsigned int nr_irqs) 110{ 111 struct ti_sci_intr_irq_domain *intr = domain->host_data; 112 struct irq_data *data; 113 int out_irq; 114 115 data = irq_domain_get_irq_data(domain, virq); 116 out_irq = (uintptr_t)data->chip_data; 117 118 intr->sci->ops.rm_irq_ops.free_irq(intr->sci, 119 intr->ti_sci_id, data->hwirq, 120 intr->ti_sci_id, out_irq); 121 ti_sci_release_resource(intr->out_irqs, out_irq); 122 irq_domain_free_irqs_parent(domain, virq, 1); 123 irq_domain_reset_irq_data(data); 124} 125 126/** 127 * ti_sci_intr_alloc_parent_irq() - Allocate parent IRQ 128 * @domain: Pointer to the interrupt router IRQ domain 129 * @virq: Corresponding Linux virtual IRQ number 130 * @hwirq: Corresponding hwirq for the IRQ within this IRQ domain 131 * 132 * Returns intr output irq if all went well else appropriate error pointer. 133 */ 134static int ti_sci_intr_alloc_parent_irq(struct irq_domain *domain, 135 unsigned int virq, u32 hwirq) 136{ 137 struct ti_sci_intr_irq_domain *intr = domain->host_data; 138 struct device_node *parent_node; 139 struct irq_fwspec fwspec; 140 int p_hwirq, err = 0; 141 u16 out_irq; 142 143 out_irq = ti_sci_get_free_resource(intr->out_irqs); 144 if (out_irq == TI_SCI_RESOURCE_NULL) 145 return -EINVAL; 146 147 p_hwirq = ti_sci_intr_xlate_irq(intr, out_irq); 148 if (p_hwirq < 0) 149 goto err_irqs; 150 151 parent_node = of_irq_find_parent(dev_of_node(intr->dev)); 152 fwspec.fwnode = of_node_to_fwnode(parent_node); 153 154 if (of_device_is_compatible(parent_node, "arm,gic-v3")) { 155 /* Parent is GIC */ 156 fwspec.param_count = 3; 157 fwspec.param[0] = 0; /* SPI */ 158 fwspec.param[1] = p_hwirq - 32; /* SPI offset */ 159 fwspec.param[2] = intr->type; 160 } else { 161 /* Parent is Interrupt Router */ 162 fwspec.param_count = 1; 163 fwspec.param[0] = p_hwirq; 164 } 165 166 err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); 167 if (err) 168 goto err_irqs; 169 170 err = intr->sci->ops.rm_irq_ops.set_irq(intr->sci, 171 intr->ti_sci_id, hwirq, 172 intr->ti_sci_id, out_irq); 173 if (err) 174 goto err_msg; 175 176 return out_irq; 177 178err_msg: 179 irq_domain_free_irqs_parent(domain, virq, 1); 180err_irqs: 181 ti_sci_release_resource(intr->out_irqs, out_irq); 182 return err; 183} 184 185/** 186 * ti_sci_intr_irq_domain_alloc() - Allocate Interrupt router IRQs 187 * @domain: Point to the interrupt router IRQ domain 188 * @virq: Corresponding Linux virtual IRQ number 189 * @nr_irqs: Continuous irqs to be allocated 190 * @data: Pointer to firmware specifier 191 * 192 * Return 0 if all went well else appropriate error value. 193 */ 194static int ti_sci_intr_irq_domain_alloc(struct irq_domain *domain, 195 unsigned int virq, unsigned int nr_irqs, 196 void *data) 197{ 198 struct irq_fwspec *fwspec = data; 199 unsigned long hwirq; 200 unsigned int flags; 201 int err, out_irq; 202 203 err = ti_sci_intr_irq_domain_translate(domain, fwspec, &hwirq, &flags); 204 if (err) 205 return err; 206 207 out_irq = ti_sci_intr_alloc_parent_irq(domain, virq, hwirq); 208 if (out_irq < 0) 209 return out_irq; 210 211 irq_domain_set_hwirq_and_chip(domain, virq, hwirq, 212 &ti_sci_intr_irq_chip, 213 (void *)(uintptr_t)out_irq); 214 215 return 0; 216} 217 218static const struct irq_domain_ops ti_sci_intr_irq_domain_ops = { 219 .free = ti_sci_intr_irq_domain_free, 220 .alloc = ti_sci_intr_irq_domain_alloc, 221 .translate = ti_sci_intr_irq_domain_translate, 222}; 223 224static int ti_sci_intr_irq_domain_probe(struct platform_device *pdev) 225{ 226 struct irq_domain *parent_domain, *domain; 227 struct ti_sci_intr_irq_domain *intr; 228 struct device_node *parent_node; 229 struct device *dev = &pdev->dev; 230 int ret; 231 232 parent_node = of_irq_find_parent(dev_of_node(dev)); 233 if (!parent_node) { 234 dev_err(dev, "Failed to get IRQ parent node\n"); 235 return -ENODEV; 236 } 237 238 parent_domain = irq_find_host(parent_node); 239 if (!parent_domain) { 240 dev_err(dev, "Failed to find IRQ parent domain\n"); 241 return -ENODEV; 242 } 243 244 intr = devm_kzalloc(dev, sizeof(*intr), GFP_KERNEL); 245 if (!intr) 246 return -ENOMEM; 247 248 intr->dev = dev; 249 ret = of_property_read_u32(dev_of_node(dev), "ti,intr-trigger-type", 250 &intr->type); 251 if (ret) { 252 dev_err(dev, "missing ti,intr-trigger-type property\n"); 253 return -EINVAL; 254 } 255 256 intr->sci = devm_ti_sci_get_by_phandle(dev, "ti,sci"); 257 if (IS_ERR(intr->sci)) 258 return dev_err_probe(dev, PTR_ERR(intr->sci), 259 "ti,sci read fail\n"); 260 261 ret = of_property_read_u32(dev_of_node(dev), "ti,sci-dev-id", 262 &intr->ti_sci_id); 263 if (ret) { 264 dev_err(dev, "missing 'ti,sci-dev-id' property\n"); 265 return -EINVAL; 266 } 267 268 intr->out_irqs = devm_ti_sci_get_resource(intr->sci, dev, 269 intr->ti_sci_id, 270 TI_SCI_RESASG_SUBTYPE_IR_OUTPUT); 271 if (IS_ERR(intr->out_irqs)) { 272 dev_err(dev, "Destination irq resource allocation failed\n"); 273 return PTR_ERR(intr->out_irqs); 274 } 275 276 domain = irq_domain_add_hierarchy(parent_domain, 0, 0, dev_of_node(dev), 277 &ti_sci_intr_irq_domain_ops, intr); 278 if (!domain) { 279 dev_err(dev, "Failed to allocate IRQ domain\n"); 280 return -ENOMEM; 281 } 282 283 dev_info(dev, "Interrupt Router %d domain created\n", intr->ti_sci_id); 284 285 return 0; 286} 287 288static const struct of_device_id ti_sci_intr_irq_domain_of_match[] = { 289 { .compatible = "ti,sci-intr", }, 290 { /* sentinel */ }, 291}; 292MODULE_DEVICE_TABLE(of, ti_sci_intr_irq_domain_of_match); 293 294static struct platform_driver ti_sci_intr_irq_domain_driver = { 295 .probe = ti_sci_intr_irq_domain_probe, 296 .driver = { 297 .name = "ti-sci-intr", 298 .of_match_table = ti_sci_intr_irq_domain_of_match, 299 }, 300}; 301module_platform_driver(ti_sci_intr_irq_domain_driver); 302 303MODULE_AUTHOR("Lokesh Vutla <lokeshvutla@ticom>"); 304MODULE_DESCRIPTION("K3 Interrupt Router driver over TI SCI protocol"); 305MODULE_LICENSE("GPL v2");