|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * SDCA IRQ handler library |
| 4 | + * |
| 5 | + * Copyright (C) 2022-2023 Cirrus Logic, Inc. and |
| 6 | + * Cirrus Logic International Semiconductor Ltd. |
| 7 | + * |
| 8 | + * The MIPI SDCA specification is available for public downloads at |
| 9 | + * https://www.mipi.org/mipi-sdca-v1-0-download |
| 10 | + */ |
| 11 | +#include <linux/device.h> |
| 12 | +#include <linux/soundwire/sdw.h> |
| 13 | +#include <linux/soundwire/sdw_registers.h> |
| 14 | +#include <sound/sdca.h> |
| 15 | +#include <linux/regmap.h> |
| 16 | +#include <linux/interrupt.h> |
| 17 | +#include <linux/workqueue.h> |
| 18 | +#include <sound/sdca_interrupts.h> |
| 19 | +#include <sound/sdca_function.h> |
| 20 | + |
| 21 | +static irqreturn_t sdca_base_irq_handler(int irq, void *data) |
| 22 | +{ |
| 23 | + struct sdca_interrupt *priv = data; |
| 24 | + |
| 25 | + pr_err("%s IRQ handled\n", priv->name); |
| 26 | + return IRQ_HANDLED; |
| 27 | +} |
| 28 | + |
| 29 | +#define IRQ_SDCA_OFFSET(reg_number) ((SDW_SCP_SDCA_INT##reg_number) - SDW_SCP_SDCA_INT1) |
| 30 | +#define IRQ_SDCA(number, reg_number) REGMAP_IRQ_REG(SDW_SCP_SDCA_INT_SDCA_##number, \ |
| 31 | + IRQ_SDCA_OFFSET(reg_number), \ |
| 32 | + SDW_SCP_SDCA_INTMASK_SDCA_##number) |
| 33 | + |
| 34 | +static const struct regmap_irq regmap_irqs[] = { |
| 35 | + IRQ_SDCA(0, 1), |
| 36 | + IRQ_SDCA(1, 1), |
| 37 | + IRQ_SDCA(2, 1), |
| 38 | + IRQ_SDCA(3, 1), |
| 39 | + IRQ_SDCA(4, 1), |
| 40 | + IRQ_SDCA(5, 1), |
| 41 | + IRQ_SDCA(6, 1), |
| 42 | + IRQ_SDCA(7, 1), |
| 43 | + IRQ_SDCA(8, 2), |
| 44 | + IRQ_SDCA(9, 2), |
| 45 | + IRQ_SDCA(10, 2), |
| 46 | + IRQ_SDCA(11, 2), |
| 47 | + IRQ_SDCA(12, 2), |
| 48 | + IRQ_SDCA(13, 2), |
| 49 | + IRQ_SDCA(14, 2), |
| 50 | + IRQ_SDCA(15, 2), |
| 51 | + IRQ_SDCA(16, 3), |
| 52 | + IRQ_SDCA(17, 3), |
| 53 | + IRQ_SDCA(18, 3), |
| 54 | + IRQ_SDCA(19, 3), |
| 55 | + IRQ_SDCA(20, 3), |
| 56 | + IRQ_SDCA(21, 3), |
| 57 | + IRQ_SDCA(22, 3), |
| 58 | + IRQ_SDCA(23, 3), |
| 59 | + IRQ_SDCA(24, 4), |
| 60 | + IRQ_SDCA(25, 4), |
| 61 | + IRQ_SDCA(26, 4), |
| 62 | + IRQ_SDCA(27, 4), |
| 63 | + IRQ_SDCA(28, 4), |
| 64 | + IRQ_SDCA(29, 4), |
| 65 | +}; |
| 66 | + |
| 67 | +static const struct regmap_irq_chip sdca_irq_chip = { |
| 68 | + .name = "sdca_irq", |
| 69 | + |
| 70 | + .status_base = SDW_SCP_SDCA_INT1, |
| 71 | + .mask_base = SDW_SCP_SDCA_INTMASK1, |
| 72 | + .num_regs = 4, |
| 73 | + |
| 74 | + .irqs = regmap_irqs, |
| 75 | + .num_irqs = SDCA_MAX_INTERRUPTS, |
| 76 | + |
| 77 | + .runtime_pm = true, |
| 78 | +}; |
| 79 | + |
| 80 | +int sdca_request_irq(struct sdca_interrupt_info *interrupt_info, struct sdca_interrupt *irq) |
| 81 | +{ |
| 82 | + char *name = irq->name; |
| 83 | + irq_handler_t thread_fn = irq->callback; |
| 84 | + int ret; |
| 85 | + |
| 86 | + |
| 87 | + ret = irq_create_mapping(interrupt_info->irq_dom, irq->num); |
| 88 | + |
| 89 | + if (ret < 0) { |
| 90 | + dev_err(interrupt_info->dev, "Failed to map IRQ %s\n", name); |
| 91 | + return 1; |
| 92 | + } |
| 93 | + |
| 94 | + dev_dbg(interrupt_info->dev, "Request IRQ %d for %s\n", ret, name); |
| 95 | + |
| 96 | + ret = devm_request_threaded_irq(interrupt_info->dev, ret, NULL, thread_fn, |
| 97 | + IRQF_ONESHOT, name, irq); |
| 98 | + if (ret) { |
| 99 | + dev_err(interrupt_info->dev, "Failed to request IRQ %s\n", name); |
| 100 | + return 1; |
| 101 | + } |
| 102 | + |
| 103 | + return 0; |
| 104 | +} |
| 105 | +EXPORT_SYMBOL_GPL(sdca_request_irq); |
| 106 | + |
| 107 | +static int sdca_process_controls_with_interrupts(struct sdca_interrupt_info *interrupt_info, |
| 108 | + struct sdca_function_data *functions, int num_functions) |
| 109 | +{ |
| 110 | + int i, j, k; |
| 111 | + struct sdca_function_data *function; |
| 112 | + struct sdca_control *control; |
| 113 | + struct sdca_interrupt *interrupt; |
| 114 | + int total_controls = 0; |
| 115 | + int local_controls = 0; |
| 116 | + |
| 117 | + for (i = 0; i < num_functions; i++) { |
| 118 | + function = &functions[i]; |
| 119 | + for (j = 0; j < function->num_entities; j++) { |
| 120 | + for (k = 0; k < function->entities[j].num_controls; k++) { |
| 121 | + control = &function->entities[j].controls[k]; |
| 122 | + if (control->interrupt_position > 0) { |
| 123 | + if (total_controls >= SDCA_MAX_INTERRUPTS) |
| 124 | + return -1; |
| 125 | + |
| 126 | + interrupt = &function->interrupt[local_controls++]; |
| 127 | + interrupt->num = control->interrupt_position; |
| 128 | + interrupt->name = devm_kasprintf(interrupt_info->dev, GFP_KERNEL, |
| 129 | + "%s: %s", function->desc->name, control->label); |
| 130 | + interrupt->callback = &sdca_base_irq_handler; |
| 131 | + interrupt->regmap = interrupt_info->regmap; |
| 132 | + interrupt->control = control; |
| 133 | + interrupt->control_reg = SDW_SDCA_CTL(function->desc->adr, |
| 134 | + function->entities[j].id, |
| 135 | + control->sel, 0); |
| 136 | + |
| 137 | + total_controls++; |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | + function->interrupt_num = local_controls; |
| 142 | + local_controls = 0; |
| 143 | + } |
| 144 | + |
| 145 | + return total_controls; |
| 146 | +} |
| 147 | + |
| 148 | +int sdca_irq_config(struct sdca_interrupt_info *interrupt_info, unsigned int irq, |
| 149 | + struct sdca_function_data *functions, int num_functions) |
| 150 | +{ |
| 151 | + struct irq_data *irq_data; |
| 152 | + unsigned long irq_flags; |
| 153 | + struct irq_domain *dom; |
| 154 | + int ret; |
| 155 | + int control_count; |
| 156 | + |
| 157 | + control_count = sdca_process_controls_with_interrupts(interrupt_info, functions, num_functions); |
| 158 | + if (control_count < 0) { |
| 159 | + dev_info(interrupt_info->dev, "No interrupts to act on\n"); |
| 160 | + return 0; |
| 161 | + } |
| 162 | + |
| 163 | + interrupt_info->irq_chip = sdca_irq_chip; |
| 164 | + interrupt_info->irq_chip.irq_drv_data = interrupt_info->parent; |
| 165 | + |
| 166 | + irq_data = irq_get_irq_data(irq); |
| 167 | + if (!irq_data) { |
| 168 | + dev_err(interrupt_info->dev, "Invalid IRQ: %d\n", irq); |
| 169 | + return -EINVAL; |
| 170 | + } |
| 171 | + |
| 172 | + irq_flags = irqd_get_trigger_type(irq_data); |
| 173 | + switch (irq_flags) { |
| 174 | + case IRQF_TRIGGER_LOW: |
| 175 | + case IRQF_TRIGGER_HIGH: |
| 176 | + case IRQF_TRIGGER_RISING: |
| 177 | + case IRQF_TRIGGER_FALLING: |
| 178 | + break; |
| 179 | + case IRQ_TYPE_NONE: |
| 180 | + default: |
| 181 | + irq_flags = IRQF_TRIGGER_LOW; |
| 182 | + break; |
| 183 | + } |
| 184 | + |
| 185 | + irq_flags |= IRQF_ONESHOT; |
| 186 | + |
| 187 | + ret = devm_regmap_add_irq_chip(interrupt_info->dev, interrupt_info->regmap, |
| 188 | + irq, irq_flags, 0, |
| 189 | + &interrupt_info->irq_chip, &interrupt_info->irq_data); |
| 190 | + if (ret) { |
| 191 | + dev_err(interrupt_info->dev, "Failed to add IRQ chip: %d\n", ret); |
| 192 | + return ret; |
| 193 | + } |
| 194 | + dev_dbg(interrupt_info->dev, "Configured IRQ %d with flags 0x%lx\n", irq, irq_flags); |
| 195 | + |
| 196 | + interrupt_info->irq_dom = regmap_irq_get_domain(interrupt_info->irq_data); |
| 197 | + if (!dom) |
| 198 | + return -EINVAL; |
| 199 | + |
| 200 | + return 0; |
| 201 | +} |
| 202 | +EXPORT_SYMBOL_NS(sdca_irq_config, "SND_SOC_SDCA_IRQ_HANDLER"); |
| 203 | + |
| 204 | +MODULE_LICENSE("GPL"); |
| 205 | +MODULE_DESCRIPTION("SDCA IRQ handler library"); |
0 commit comments