qcom-irq-combiner.c (7083B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. 3 */ 4 5/* 6 * Driver for interrupt combiners in the Top-level Control and Status 7 * Registers (TCSR) hardware block in Qualcomm Technologies chips. 8 * An interrupt combiner in this block combines a set of interrupts by 9 * OR'ing the individual interrupt signals into a summary interrupt 10 * signal routed to a parent interrupt controller, and provides read- 11 * only, 32-bit registers to query the status of individual interrupts. 12 * The status bit for IRQ n is bit (n % 32) within register (n / 32) 13 * of the given combiner. Thus, each combiner can be described as a set 14 * of register offsets and the number of IRQs managed. 15 */ 16 17#define pr_fmt(fmt) "QCOM80B1:" fmt 18 19#include <linux/acpi.h> 20#include <linux/irqchip/chained_irq.h> 21#include <linux/irqdomain.h> 22#include <linux/platform_device.h> 23 24#define REG_SIZE 32 25 26struct combiner_reg { 27 void __iomem *addr; 28 unsigned long enabled; 29}; 30 31struct combiner { 32 struct irq_domain *domain; 33 int parent_irq; 34 u32 nirqs; 35 u32 nregs; 36 struct combiner_reg regs[]; 37}; 38 39static inline int irq_nr(u32 reg, u32 bit) 40{ 41 return reg * REG_SIZE + bit; 42} 43 44/* 45 * Handler for the cascaded IRQ. 46 */ 47static void combiner_handle_irq(struct irq_desc *desc) 48{ 49 struct combiner *combiner = irq_desc_get_handler_data(desc); 50 struct irq_chip *chip = irq_desc_get_chip(desc); 51 u32 reg; 52 53 chained_irq_enter(chip, desc); 54 55 for (reg = 0; reg < combiner->nregs; reg++) { 56 int hwirq; 57 u32 bit; 58 u32 status; 59 60 bit = readl_relaxed(combiner->regs[reg].addr); 61 status = bit & combiner->regs[reg].enabled; 62 if (bit && !status) 63 pr_warn_ratelimited("Unexpected IRQ on CPU%d: (%08x %08lx %p)\n", 64 smp_processor_id(), bit, 65 combiner->regs[reg].enabled, 66 combiner->regs[reg].addr); 67 68 while (status) { 69 bit = __ffs(status); 70 status &= ~(1 << bit); 71 hwirq = irq_nr(reg, bit); 72 generic_handle_domain_irq(combiner->domain, hwirq); 73 } 74 } 75 76 chained_irq_exit(chip, desc); 77} 78 79static void combiner_irq_chip_mask_irq(struct irq_data *data) 80{ 81 struct combiner *combiner = irq_data_get_irq_chip_data(data); 82 struct combiner_reg *reg = combiner->regs + data->hwirq / REG_SIZE; 83 84 clear_bit(data->hwirq % REG_SIZE, ®->enabled); 85} 86 87static void combiner_irq_chip_unmask_irq(struct irq_data *data) 88{ 89 struct combiner *combiner = irq_data_get_irq_chip_data(data); 90 struct combiner_reg *reg = combiner->regs + data->hwirq / REG_SIZE; 91 92 set_bit(data->hwirq % REG_SIZE, ®->enabled); 93} 94 95static struct irq_chip irq_chip = { 96 .irq_mask = combiner_irq_chip_mask_irq, 97 .irq_unmask = combiner_irq_chip_unmask_irq, 98 .name = "qcom-irq-combiner" 99}; 100 101static int combiner_irq_map(struct irq_domain *domain, unsigned int irq, 102 irq_hw_number_t hwirq) 103{ 104 irq_set_chip_and_handler(irq, &irq_chip, handle_level_irq); 105 irq_set_chip_data(irq, domain->host_data); 106 irq_set_noprobe(irq); 107 return 0; 108} 109 110static void combiner_irq_unmap(struct irq_domain *domain, unsigned int irq) 111{ 112 irq_domain_reset_irq_data(irq_get_irq_data(irq)); 113} 114 115static int combiner_irq_translate(struct irq_domain *d, struct irq_fwspec *fws, 116 unsigned long *hwirq, unsigned int *type) 117{ 118 struct combiner *combiner = d->host_data; 119 120 if (is_acpi_node(fws->fwnode)) { 121 if (WARN_ON((fws->param_count != 2) || 122 (fws->param[0] >= combiner->nirqs) || 123 (fws->param[1] & IORESOURCE_IRQ_LOWEDGE) || 124 (fws->param[1] & IORESOURCE_IRQ_HIGHEDGE))) 125 return -EINVAL; 126 127 *hwirq = fws->param[0]; 128 *type = fws->param[1]; 129 return 0; 130 } 131 132 return -EINVAL; 133} 134 135static const struct irq_domain_ops domain_ops = { 136 .map = combiner_irq_map, 137 .unmap = combiner_irq_unmap, 138 .translate = combiner_irq_translate 139}; 140 141static acpi_status count_registers_cb(struct acpi_resource *ares, void *context) 142{ 143 int *count = context; 144 145 if (ares->type == ACPI_RESOURCE_TYPE_GENERIC_REGISTER) 146 ++(*count); 147 return AE_OK; 148} 149 150static int count_registers(struct platform_device *pdev) 151{ 152 acpi_handle ahandle = ACPI_HANDLE(&pdev->dev); 153 acpi_status status; 154 int count = 0; 155 156 if (!acpi_has_method(ahandle, METHOD_NAME__CRS)) 157 return -EINVAL; 158 159 status = acpi_walk_resources(ahandle, METHOD_NAME__CRS, 160 count_registers_cb, &count); 161 if (ACPI_FAILURE(status)) 162 return -EINVAL; 163 return count; 164} 165 166struct get_registers_context { 167 struct device *dev; 168 struct combiner *combiner; 169 int err; 170}; 171 172static acpi_status get_registers_cb(struct acpi_resource *ares, void *context) 173{ 174 struct get_registers_context *ctx = context; 175 struct acpi_resource_generic_register *reg; 176 phys_addr_t paddr; 177 void __iomem *vaddr; 178 179 if (ares->type != ACPI_RESOURCE_TYPE_GENERIC_REGISTER) 180 return AE_OK; 181 182 reg = &ares->data.generic_reg; 183 paddr = reg->address; 184 if ((reg->space_id != ACPI_SPACE_MEM) || 185 (reg->bit_offset != 0) || 186 (reg->bit_width > REG_SIZE)) { 187 dev_err(ctx->dev, "Bad register resource @%pa\n", &paddr); 188 ctx->err = -EINVAL; 189 return AE_ERROR; 190 } 191 192 vaddr = devm_ioremap(ctx->dev, reg->address, REG_SIZE); 193 if (!vaddr) { 194 dev_err(ctx->dev, "Can't map register @%pa\n", &paddr); 195 ctx->err = -ENOMEM; 196 return AE_ERROR; 197 } 198 199 ctx->combiner->regs[ctx->combiner->nregs].addr = vaddr; 200 ctx->combiner->nirqs += reg->bit_width; 201 ctx->combiner->nregs++; 202 return AE_OK; 203} 204 205static int get_registers(struct platform_device *pdev, struct combiner *comb) 206{ 207 acpi_handle ahandle = ACPI_HANDLE(&pdev->dev); 208 acpi_status status; 209 struct get_registers_context ctx; 210 211 if (!acpi_has_method(ahandle, METHOD_NAME__CRS)) 212 return -EINVAL; 213 214 ctx.dev = &pdev->dev; 215 ctx.combiner = comb; 216 ctx.err = 0; 217 218 status = acpi_walk_resources(ahandle, METHOD_NAME__CRS, 219 get_registers_cb, &ctx); 220 if (ACPI_FAILURE(status)) 221 return ctx.err; 222 return 0; 223} 224 225static int __init combiner_probe(struct platform_device *pdev) 226{ 227 struct combiner *combiner; 228 int nregs; 229 int err; 230 231 nregs = count_registers(pdev); 232 if (nregs <= 0) { 233 dev_err(&pdev->dev, "Error reading register resources\n"); 234 return -EINVAL; 235 } 236 237 combiner = devm_kzalloc(&pdev->dev, struct_size(combiner, regs, nregs), 238 GFP_KERNEL); 239 if (!combiner) 240 return -ENOMEM; 241 242 err = get_registers(pdev, combiner); 243 if (err < 0) 244 return err; 245 246 combiner->parent_irq = platform_get_irq(pdev, 0); 247 if (combiner->parent_irq <= 0) 248 return -EPROBE_DEFER; 249 250 combiner->domain = irq_domain_create_linear(pdev->dev.fwnode, combiner->nirqs, 251 &domain_ops, combiner); 252 if (!combiner->domain) 253 /* Errors printed by irq_domain_create_linear */ 254 return -ENODEV; 255 256 irq_set_chained_handler_and_data(combiner->parent_irq, 257 combiner_handle_irq, combiner); 258 259 dev_info(&pdev->dev, "Initialized with [p=%d,n=%d,r=%p]\n", 260 combiner->parent_irq, combiner->nirqs, combiner->regs[0].addr); 261 return 0; 262} 263 264static const struct acpi_device_id qcom_irq_combiner_ids[] = { 265 { "QCOM80B1", }, 266 { } 267}; 268 269static struct platform_driver qcom_irq_combiner_probe = { 270 .driver = { 271 .name = "qcom-irq-combiner", 272 .acpi_match_table = ACPI_PTR(qcom_irq_combiner_ids), 273 }, 274 .probe = combiner_probe, 275}; 276builtin_platform_driver(qcom_irq_combiner_probe);