18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Texas Instruments' K3 Interrupt Router irqchip driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2018-2019 Texas Instruments Incorporated - https://www.ti.com/ 68c2ecf20Sopenharmony_ci * Lokesh Vutla <lokeshvutla@ti.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/err.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 128c2ecf20Sopenharmony_ci#include <linux/io.h> 138c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 148c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 158c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 168c2ecf20Sopenharmony_ci#include <linux/of_address.h> 178c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 188c2ecf20Sopenharmony_ci#include <linux/soc/ti/ti_sci_protocol.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci/** 218c2ecf20Sopenharmony_ci * struct ti_sci_intr_irq_domain - Structure representing a TISCI based 228c2ecf20Sopenharmony_ci * Interrupt Router IRQ domain. 238c2ecf20Sopenharmony_ci * @sci: Pointer to TISCI handle 248c2ecf20Sopenharmony_ci * @out_irqs: TISCI resource pointer representing INTR irqs. 258c2ecf20Sopenharmony_ci * @dev: Struct device pointer. 268c2ecf20Sopenharmony_ci * @ti_sci_id: TI-SCI device identifier 278c2ecf20Sopenharmony_ci * @type: Specifies the trigger type supported by this Interrupt Router 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_cistruct ti_sci_intr_irq_domain { 308c2ecf20Sopenharmony_ci const struct ti_sci_handle *sci; 318c2ecf20Sopenharmony_ci struct ti_sci_resource *out_irqs; 328c2ecf20Sopenharmony_ci struct device *dev; 338c2ecf20Sopenharmony_ci u32 ti_sci_id; 348c2ecf20Sopenharmony_ci u32 type; 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic struct irq_chip ti_sci_intr_irq_chip = { 388c2ecf20Sopenharmony_ci .name = "INTR", 398c2ecf20Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 408c2ecf20Sopenharmony_ci .irq_mask = irq_chip_mask_parent, 418c2ecf20Sopenharmony_ci .irq_unmask = irq_chip_unmask_parent, 428c2ecf20Sopenharmony_ci .irq_set_type = irq_chip_set_type_parent, 438c2ecf20Sopenharmony_ci .irq_retrigger = irq_chip_retrigger_hierarchy, 448c2ecf20Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 458c2ecf20Sopenharmony_ci}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci/** 488c2ecf20Sopenharmony_ci * ti_sci_intr_irq_domain_translate() - Retrieve hwirq and type from 498c2ecf20Sopenharmony_ci * IRQ firmware specific handler. 508c2ecf20Sopenharmony_ci * @domain: Pointer to IRQ domain 518c2ecf20Sopenharmony_ci * @fwspec: Pointer to IRQ specific firmware structure 528c2ecf20Sopenharmony_ci * @hwirq: IRQ number identified by hardware 538c2ecf20Sopenharmony_ci * @type: IRQ type 548c2ecf20Sopenharmony_ci * 558c2ecf20Sopenharmony_ci * Return 0 if all went ok else appropriate error. 568c2ecf20Sopenharmony_ci */ 578c2ecf20Sopenharmony_cistatic int ti_sci_intr_irq_domain_translate(struct irq_domain *domain, 588c2ecf20Sopenharmony_ci struct irq_fwspec *fwspec, 598c2ecf20Sopenharmony_ci unsigned long *hwirq, 608c2ecf20Sopenharmony_ci unsigned int *type) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci struct ti_sci_intr_irq_domain *intr = domain->host_data; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci if (fwspec->param_count != 1) 658c2ecf20Sopenharmony_ci return -EINVAL; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci *hwirq = fwspec->param[0]; 688c2ecf20Sopenharmony_ci *type = intr->type; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci return 0; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci/** 748c2ecf20Sopenharmony_ci * ti_sci_intr_xlate_irq() - Translate hwirq to parent's hwirq. 758c2ecf20Sopenharmony_ci * @intr: IRQ domain corresponding to Interrupt Router 768c2ecf20Sopenharmony_ci * @irq: Hardware irq corresponding to the above irq domain 778c2ecf20Sopenharmony_ci * 788c2ecf20Sopenharmony_ci * Return parent irq number if translation is available else -ENOENT. 798c2ecf20Sopenharmony_ci */ 808c2ecf20Sopenharmony_cistatic int ti_sci_intr_xlate_irq(struct ti_sci_intr_irq_domain *intr, u32 irq) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci struct device_node *np = dev_of_node(intr->dev); 838c2ecf20Sopenharmony_ci u32 base, pbase, size, len; 848c2ecf20Sopenharmony_ci const __be32 *range; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci range = of_get_property(np, "ti,interrupt-ranges", &len); 878c2ecf20Sopenharmony_ci if (!range) 888c2ecf20Sopenharmony_ci return irq; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci for (len /= sizeof(*range); len >= 3; len -= 3) { 918c2ecf20Sopenharmony_ci base = be32_to_cpu(*range++); 928c2ecf20Sopenharmony_ci pbase = be32_to_cpu(*range++); 938c2ecf20Sopenharmony_ci size = be32_to_cpu(*range++); 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci if (base <= irq && irq < base + size) 968c2ecf20Sopenharmony_ci return irq - base + pbase; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci return -ENOENT; 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci/** 1038c2ecf20Sopenharmony_ci * ti_sci_intr_irq_domain_free() - Free the specified IRQs from the domain. 1048c2ecf20Sopenharmony_ci * @domain: Domain to which the irqs belong 1058c2ecf20Sopenharmony_ci * @virq: Linux virtual IRQ to be freed. 1068c2ecf20Sopenharmony_ci * @nr_irqs: Number of continuous irqs to be freed 1078c2ecf20Sopenharmony_ci */ 1088c2ecf20Sopenharmony_cistatic void ti_sci_intr_irq_domain_free(struct irq_domain *domain, 1098c2ecf20Sopenharmony_ci unsigned int virq, unsigned int nr_irqs) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci struct ti_sci_intr_irq_domain *intr = domain->host_data; 1128c2ecf20Sopenharmony_ci struct irq_data *data; 1138c2ecf20Sopenharmony_ci int out_irq; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci data = irq_domain_get_irq_data(domain, virq); 1168c2ecf20Sopenharmony_ci out_irq = (uintptr_t)data->chip_data; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci intr->sci->ops.rm_irq_ops.free_irq(intr->sci, 1198c2ecf20Sopenharmony_ci intr->ti_sci_id, data->hwirq, 1208c2ecf20Sopenharmony_ci intr->ti_sci_id, out_irq); 1218c2ecf20Sopenharmony_ci ti_sci_release_resource(intr->out_irqs, out_irq); 1228c2ecf20Sopenharmony_ci irq_domain_free_irqs_parent(domain, virq, 1); 1238c2ecf20Sopenharmony_ci irq_domain_reset_irq_data(data); 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci/** 1278c2ecf20Sopenharmony_ci * ti_sci_intr_alloc_parent_irq() - Allocate parent IRQ 1288c2ecf20Sopenharmony_ci * @domain: Pointer to the interrupt router IRQ domain 1298c2ecf20Sopenharmony_ci * @virq: Corresponding Linux virtual IRQ number 1308c2ecf20Sopenharmony_ci * @hwirq: Corresponding hwirq for the IRQ within this IRQ domain 1318c2ecf20Sopenharmony_ci * 1328c2ecf20Sopenharmony_ci * Returns intr output irq if all went well else appropriate error pointer. 1338c2ecf20Sopenharmony_ci */ 1348c2ecf20Sopenharmony_cistatic int ti_sci_intr_alloc_parent_irq(struct irq_domain *domain, 1358c2ecf20Sopenharmony_ci unsigned int virq, u32 hwirq) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci struct ti_sci_intr_irq_domain *intr = domain->host_data; 1388c2ecf20Sopenharmony_ci struct device_node *parent_node; 1398c2ecf20Sopenharmony_ci struct irq_fwspec fwspec; 1408c2ecf20Sopenharmony_ci int p_hwirq, err = 0; 1418c2ecf20Sopenharmony_ci u16 out_irq; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci out_irq = ti_sci_get_free_resource(intr->out_irqs); 1448c2ecf20Sopenharmony_ci if (out_irq == TI_SCI_RESOURCE_NULL) 1458c2ecf20Sopenharmony_ci return -EINVAL; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci p_hwirq = ti_sci_intr_xlate_irq(intr, out_irq); 1488c2ecf20Sopenharmony_ci if (p_hwirq < 0) 1498c2ecf20Sopenharmony_ci goto err_irqs; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci parent_node = of_irq_find_parent(dev_of_node(intr->dev)); 1528c2ecf20Sopenharmony_ci fwspec.fwnode = of_node_to_fwnode(parent_node); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci if (of_device_is_compatible(parent_node, "arm,gic-v3")) { 1558c2ecf20Sopenharmony_ci /* Parent is GIC */ 1568c2ecf20Sopenharmony_ci fwspec.param_count = 3; 1578c2ecf20Sopenharmony_ci fwspec.param[0] = 0; /* SPI */ 1588c2ecf20Sopenharmony_ci fwspec.param[1] = p_hwirq - 32; /* SPI offset */ 1598c2ecf20Sopenharmony_ci fwspec.param[2] = intr->type; 1608c2ecf20Sopenharmony_ci } else { 1618c2ecf20Sopenharmony_ci /* Parent is Interrupt Router */ 1628c2ecf20Sopenharmony_ci fwspec.param_count = 1; 1638c2ecf20Sopenharmony_ci fwspec.param[0] = p_hwirq; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); 1678c2ecf20Sopenharmony_ci if (err) 1688c2ecf20Sopenharmony_ci goto err_irqs; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci err = intr->sci->ops.rm_irq_ops.set_irq(intr->sci, 1718c2ecf20Sopenharmony_ci intr->ti_sci_id, hwirq, 1728c2ecf20Sopenharmony_ci intr->ti_sci_id, out_irq); 1738c2ecf20Sopenharmony_ci if (err) 1748c2ecf20Sopenharmony_ci goto err_msg; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci return out_irq; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cierr_msg: 1798c2ecf20Sopenharmony_ci irq_domain_free_irqs_parent(domain, virq, 1); 1808c2ecf20Sopenharmony_cierr_irqs: 1818c2ecf20Sopenharmony_ci ti_sci_release_resource(intr->out_irqs, out_irq); 1828c2ecf20Sopenharmony_ci return err; 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci/** 1868c2ecf20Sopenharmony_ci * ti_sci_intr_irq_domain_alloc() - Allocate Interrupt router IRQs 1878c2ecf20Sopenharmony_ci * @domain: Point to the interrupt router IRQ domain 1888c2ecf20Sopenharmony_ci * @virq: Corresponding Linux virtual IRQ number 1898c2ecf20Sopenharmony_ci * @nr_irqs: Continuous irqs to be allocated 1908c2ecf20Sopenharmony_ci * @data: Pointer to firmware specifier 1918c2ecf20Sopenharmony_ci * 1928c2ecf20Sopenharmony_ci * Return 0 if all went well else appropriate error value. 1938c2ecf20Sopenharmony_ci */ 1948c2ecf20Sopenharmony_cistatic int ti_sci_intr_irq_domain_alloc(struct irq_domain *domain, 1958c2ecf20Sopenharmony_ci unsigned int virq, unsigned int nr_irqs, 1968c2ecf20Sopenharmony_ci void *data) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci struct irq_fwspec *fwspec = data; 1998c2ecf20Sopenharmony_ci unsigned long hwirq; 2008c2ecf20Sopenharmony_ci unsigned int flags; 2018c2ecf20Sopenharmony_ci int err, out_irq; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci err = ti_sci_intr_irq_domain_translate(domain, fwspec, &hwirq, &flags); 2048c2ecf20Sopenharmony_ci if (err) 2058c2ecf20Sopenharmony_ci return err; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci out_irq = ti_sci_intr_alloc_parent_irq(domain, virq, hwirq); 2088c2ecf20Sopenharmony_ci if (out_irq < 0) 2098c2ecf20Sopenharmony_ci return out_irq; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci irq_domain_set_hwirq_and_chip(domain, virq, hwirq, 2128c2ecf20Sopenharmony_ci &ti_sci_intr_irq_chip, 2138c2ecf20Sopenharmony_ci (void *)(uintptr_t)out_irq); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci return 0; 2168c2ecf20Sopenharmony_ci} 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_cistatic const struct irq_domain_ops ti_sci_intr_irq_domain_ops = { 2198c2ecf20Sopenharmony_ci .free = ti_sci_intr_irq_domain_free, 2208c2ecf20Sopenharmony_ci .alloc = ti_sci_intr_irq_domain_alloc, 2218c2ecf20Sopenharmony_ci .translate = ti_sci_intr_irq_domain_translate, 2228c2ecf20Sopenharmony_ci}; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_cistatic int ti_sci_intr_irq_domain_probe(struct platform_device *pdev) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci struct irq_domain *parent_domain, *domain; 2278c2ecf20Sopenharmony_ci struct ti_sci_intr_irq_domain *intr; 2288c2ecf20Sopenharmony_ci struct device_node *parent_node; 2298c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 2308c2ecf20Sopenharmony_ci int ret; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci parent_node = of_irq_find_parent(dev_of_node(dev)); 2338c2ecf20Sopenharmony_ci if (!parent_node) { 2348c2ecf20Sopenharmony_ci dev_err(dev, "Failed to get IRQ parent node\n"); 2358c2ecf20Sopenharmony_ci return -ENODEV; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci parent_domain = irq_find_host(parent_node); 2398c2ecf20Sopenharmony_ci of_node_put(parent_node); 2408c2ecf20Sopenharmony_ci if (!parent_domain) { 2418c2ecf20Sopenharmony_ci dev_err(dev, "Failed to find IRQ parent domain\n"); 2428c2ecf20Sopenharmony_ci return -ENODEV; 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci intr = devm_kzalloc(dev, sizeof(*intr), GFP_KERNEL); 2468c2ecf20Sopenharmony_ci if (!intr) 2478c2ecf20Sopenharmony_ci return -ENOMEM; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci intr->dev = dev; 2508c2ecf20Sopenharmony_ci ret = of_property_read_u32(dev_of_node(dev), "ti,intr-trigger-type", 2518c2ecf20Sopenharmony_ci &intr->type); 2528c2ecf20Sopenharmony_ci if (ret) { 2538c2ecf20Sopenharmony_ci dev_err(dev, "missing ti,intr-trigger-type property\n"); 2548c2ecf20Sopenharmony_ci return -EINVAL; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci intr->sci = devm_ti_sci_get_by_phandle(dev, "ti,sci"); 2588c2ecf20Sopenharmony_ci if (IS_ERR(intr->sci)) 2598c2ecf20Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(intr->sci), 2608c2ecf20Sopenharmony_ci "ti,sci read fail\n"); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci ret = of_property_read_u32(dev_of_node(dev), "ti,sci-dev-id", 2638c2ecf20Sopenharmony_ci &intr->ti_sci_id); 2648c2ecf20Sopenharmony_ci if (ret) { 2658c2ecf20Sopenharmony_ci dev_err(dev, "missing 'ti,sci-dev-id' property\n"); 2668c2ecf20Sopenharmony_ci return -EINVAL; 2678c2ecf20Sopenharmony_ci } 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci intr->out_irqs = devm_ti_sci_get_resource(intr->sci, dev, 2708c2ecf20Sopenharmony_ci intr->ti_sci_id, 2718c2ecf20Sopenharmony_ci TI_SCI_RESASG_SUBTYPE_IR_OUTPUT); 2728c2ecf20Sopenharmony_ci if (IS_ERR(intr->out_irqs)) { 2738c2ecf20Sopenharmony_ci dev_err(dev, "Destination irq resource allocation failed\n"); 2748c2ecf20Sopenharmony_ci return PTR_ERR(intr->out_irqs); 2758c2ecf20Sopenharmony_ci } 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci domain = irq_domain_add_hierarchy(parent_domain, 0, 0, dev_of_node(dev), 2788c2ecf20Sopenharmony_ci &ti_sci_intr_irq_domain_ops, intr); 2798c2ecf20Sopenharmony_ci if (!domain) { 2808c2ecf20Sopenharmony_ci dev_err(dev, "Failed to allocate IRQ domain\n"); 2818c2ecf20Sopenharmony_ci return -ENOMEM; 2828c2ecf20Sopenharmony_ci } 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci dev_info(dev, "Interrupt Router %d domain created\n", intr->ti_sci_id); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci return 0; 2878c2ecf20Sopenharmony_ci} 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cistatic const struct of_device_id ti_sci_intr_irq_domain_of_match[] = { 2908c2ecf20Sopenharmony_ci { .compatible = "ti,sci-intr", }, 2918c2ecf20Sopenharmony_ci { /* sentinel */ }, 2928c2ecf20Sopenharmony_ci}; 2938c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ti_sci_intr_irq_domain_of_match); 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_cistatic struct platform_driver ti_sci_intr_irq_domain_driver = { 2968c2ecf20Sopenharmony_ci .probe = ti_sci_intr_irq_domain_probe, 2978c2ecf20Sopenharmony_ci .driver = { 2988c2ecf20Sopenharmony_ci .name = "ti-sci-intr", 2998c2ecf20Sopenharmony_ci .of_match_table = ti_sci_intr_irq_domain_of_match, 3008c2ecf20Sopenharmony_ci }, 3018c2ecf20Sopenharmony_ci}; 3028c2ecf20Sopenharmony_cimodule_platform_driver(ti_sci_intr_irq_domain_driver); 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ciMODULE_AUTHOR("Lokesh Vutla <lokeshvutla@ticom>"); 3058c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("K3 Interrupt Router driver over TI SCI protocol"); 3068c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 307