|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +// Copyright (C) 2025 Cirrus Logic, Inc. and |
| 3 | +// Cirrus Logic International Semiconductor Ltd. |
| 4 | + |
| 5 | +/* |
| 6 | + * The MIPI SDCA specification is available for public downloads at |
| 7 | + * https://www.mipi.org/mipi-sdca-v1-0-download |
| 8 | + */ |
| 9 | + |
| 10 | +#include <linux/bits.h> |
| 11 | +#include <linux/cleanup.h> |
| 12 | +#include <linux/device.h> |
| 13 | +#include <linux/interrupt.h> |
| 14 | +#include <linux/regmap.h> |
| 15 | +#include <linux/soundwire/sdw.h> |
| 16 | +#include <linux/soundwire/sdw_registers.h> |
| 17 | +#include <sound/sdca.h> |
| 18 | +#include <sound/sdca_function.h> |
| 19 | +#include <sound/sdca_interrupts.h> |
| 20 | +#include <sound/soc-component.h> |
| 21 | + |
| 22 | +#define IRQ_SDCA(number) REGMAP_IRQ_REG(number, ((number) / BITS_PER_BYTE), \ |
| 23 | + SDW_SCP_SDCA_INTMASK_SDCA_##number) |
| 24 | + |
| 25 | +static const struct regmap_irq regmap_irqs[SDCA_MAX_INTERRUPTS] = { |
| 26 | + IRQ_SDCA(0), |
| 27 | + IRQ_SDCA(1), |
| 28 | + IRQ_SDCA(2), |
| 29 | + IRQ_SDCA(3), |
| 30 | + IRQ_SDCA(4), |
| 31 | + IRQ_SDCA(5), |
| 32 | + IRQ_SDCA(6), |
| 33 | + IRQ_SDCA(7), |
| 34 | + IRQ_SDCA(8), |
| 35 | + IRQ_SDCA(9), |
| 36 | + IRQ_SDCA(10), |
| 37 | + IRQ_SDCA(11), |
| 38 | + IRQ_SDCA(12), |
| 39 | + IRQ_SDCA(13), |
| 40 | + IRQ_SDCA(14), |
| 41 | + IRQ_SDCA(15), |
| 42 | + IRQ_SDCA(16), |
| 43 | + IRQ_SDCA(17), |
| 44 | + IRQ_SDCA(18), |
| 45 | + IRQ_SDCA(19), |
| 46 | + IRQ_SDCA(20), |
| 47 | + IRQ_SDCA(21), |
| 48 | + IRQ_SDCA(22), |
| 49 | + IRQ_SDCA(23), |
| 50 | + IRQ_SDCA(24), |
| 51 | + IRQ_SDCA(25), |
| 52 | + IRQ_SDCA(26), |
| 53 | + IRQ_SDCA(27), |
| 54 | + IRQ_SDCA(28), |
| 55 | + IRQ_SDCA(29), |
| 56 | + IRQ_SDCA(30), |
| 57 | +}; |
| 58 | + |
| 59 | +static const struct regmap_irq_chip sdca_irq_chip = { |
| 60 | + .name = "sdca_irq", |
| 61 | + |
| 62 | + .status_base = SDW_SCP_SDCA_INT1, |
| 63 | + .unmask_base = SDW_SCP_SDCA_INTMASK1, |
| 64 | + .ack_base = SDW_SCP_SDCA_INT1, |
| 65 | + .num_regs = 4, |
| 66 | + |
| 67 | + .irqs = regmap_irqs, |
| 68 | + .num_irqs = SDCA_MAX_INTERRUPTS, |
| 69 | + |
| 70 | + .runtime_pm = true, |
| 71 | +}; |
| 72 | + |
| 73 | +static irqreturn_t base_handler(int irq, void *data) |
| 74 | +{ |
| 75 | + struct sdca_interrupt *interrupt = data; |
| 76 | + struct device *dev = interrupt->component->dev; |
| 77 | + |
| 78 | + dev_info(dev, "%s irq without full handling\n", interrupt->name); |
| 79 | + |
| 80 | + return IRQ_HANDLED; |
| 81 | +} |
| 82 | + |
| 83 | +static int sdca_irq_request_locked(struct device *dev, |
| 84 | + struct sdca_interrupt_info *info, |
| 85 | + int sdca_irq, const char *name, |
| 86 | + irq_handler_t handler, void *data) |
| 87 | +{ |
| 88 | + int irq; |
| 89 | + int ret; |
| 90 | + |
| 91 | + irq = regmap_irq_get_virq(info->irq_data, sdca_irq); |
| 92 | + if (irq < 0) |
| 93 | + return irq; |
| 94 | + |
| 95 | + ret = devm_request_threaded_irq(dev, irq, NULL, handler, |
| 96 | + IRQF_ONESHOT, name, data); |
| 97 | + if (ret) |
| 98 | + return ret; |
| 99 | + |
| 100 | + dev_dbg(dev, "requested irq %d for %s\n", irq, name); |
| 101 | + |
| 102 | + return 0; |
| 103 | +} |
| 104 | + |
| 105 | +/** |
| 106 | + * sdca_request_irq - request an individual SDCA interrupt |
| 107 | + * @dev: Pointer to the struct device against which things should be allocated. |
| 108 | + * @interrupt_info: Pointer to the interrupt information structure. |
| 109 | + * @sdca_irq: SDCA interrupt position. |
| 110 | + * @name: Name to be given to the IRQ. |
| 111 | + * @handler: A callback thread function to be called for the IRQ. |
| 112 | + * @data: Private data pointer that will be passed to the handler. |
| 113 | + * |
| 114 | + * Typically this is handled internally by sdca_irq_populate, however if |
| 115 | + * a device requires custom IRQ handling this can be called manually before |
| 116 | + * calling sdca_irq_populate, which will then skip that IRQ whilst processing. |
| 117 | + * |
| 118 | + * Return: Zero on success, and a negative error code on failure. |
| 119 | + */ |
| 120 | +int sdca_irq_request(struct device *dev, struct sdca_interrupt_info *info, |
| 121 | + int sdca_irq, const char *name, irq_handler_t handler, |
| 122 | + void *data) |
| 123 | +{ |
| 124 | + int ret; |
| 125 | + |
| 126 | + if (sdca_irq < 0 || sdca_irq > SDCA_MAX_INTERRUPTS) { |
| 127 | + dev_err(dev, "bad irq request: %d\n", sdca_irq); |
| 128 | + return -EINVAL; |
| 129 | + } |
| 130 | + |
| 131 | + guard(mutex)(&info->irq_lock); |
| 132 | + |
| 133 | + ret = sdca_irq_request_locked(dev, info, sdca_irq, name, handler, data); |
| 134 | + if (ret) { |
| 135 | + dev_err(dev, "failed to request irq %s: %d\n", name, ret); |
| 136 | + return ret; |
| 137 | + } |
| 138 | + |
| 139 | + info->irqs[sdca_irq].externally_requested = true; |
| 140 | + |
| 141 | + return 0; |
| 142 | +} |
| 143 | +EXPORT_SYMBOL_NS_GPL(sdca_irq_request, "SND_SOC_SDCA_IRQ"); |
| 144 | + |
| 145 | +/** |
| 146 | + * sdca_irq_data_populate - Populate common interrupt data |
| 147 | + * @component: Pointer to the ASoC component for the Function. |
| 148 | + * @function: Pointer to the SDCA Function. |
| 149 | + * @entity: Pointer to the SDCA Entity. |
| 150 | + * @control: Pointer to the SDCA Control. |
| 151 | + * @interrupt: Pointer to the SDCA interrupt for this IRQ. |
| 152 | + * |
| 153 | + * Return: Zero on success, and a negative error code on failure. |
| 154 | + */ |
| 155 | +int sdca_irq_data_populate(struct snd_soc_component *component, |
| 156 | + struct sdca_function_data *function, |
| 157 | + struct sdca_entity *entity, |
| 158 | + struct sdca_control *control, |
| 159 | + struct sdca_interrupt *interrupt) |
| 160 | +{ |
| 161 | + struct device *dev = component->dev; |
| 162 | + const char *name; |
| 163 | + |
| 164 | + name = devm_kasprintf(dev, GFP_KERNEL, "%s %s %s", function->desc->name, |
| 165 | + entity->label, control->label); |
| 166 | + if (!name) |
| 167 | + return -ENOMEM; |
| 168 | + |
| 169 | + interrupt->name = name; |
| 170 | + interrupt->component = component; |
| 171 | + interrupt->function = function; |
| 172 | + interrupt->entity = entity; |
| 173 | + interrupt->control = control; |
| 174 | + |
| 175 | + return 0; |
| 176 | +} |
| 177 | +EXPORT_SYMBOL_NS_GPL(sdca_irq_data_populate, "SND_SOC_SDCA_IRQ"); |
| 178 | + |
| 179 | +/** |
| 180 | + * sdca_irq_populate - Request all the individual IRQs for an SDCA Function |
| 181 | + * @function: Pointer to the SDCA Function. |
| 182 | + * @component: Pointer to the ASoC component for the Function. |
| 183 | + * @info: Pointer to the SDCA interrupt info for this device. |
| 184 | + * |
| 185 | + * Typically this would be called from the driver for a single SDCA Function. |
| 186 | + * |
| 187 | + * Return: Zero on success, and a negative error code on failure. |
| 188 | + */ |
| 189 | +int sdca_irq_populate(struct sdca_function_data *function, |
| 190 | + struct snd_soc_component *component, |
| 191 | + struct sdca_interrupt_info *info) |
| 192 | +{ |
| 193 | + struct device *dev = component->dev; |
| 194 | + int i, j; |
| 195 | + |
| 196 | + guard(mutex)(&info->irq_lock); |
| 197 | + |
| 198 | + for (i = 0; i < function->num_entities; i++) { |
| 199 | + struct sdca_entity *entity = &function->entities[i]; |
| 200 | + |
| 201 | + for (j = 0; j < entity->num_controls; j++) { |
| 202 | + struct sdca_control *control = &entity->controls[j]; |
| 203 | + int irq = control->interrupt_position; |
| 204 | + struct sdca_interrupt *interrupt; |
| 205 | + const char *name; |
| 206 | + int ret; |
| 207 | + |
| 208 | + if (irq == SDCA_NO_INTERRUPT) { |
| 209 | + continue; |
| 210 | + } else if (irq < 0 || irq >= SDCA_MAX_INTERRUPTS) { |
| 211 | + dev_err(dev, "bad irq position: %d\n", irq); |
| 212 | + return -EINVAL; |
| 213 | + } |
| 214 | + |
| 215 | + interrupt = &info->irqs[irq]; |
| 216 | + |
| 217 | + if (interrupt->externally_requested) { |
| 218 | + dev_dbg(dev, |
| 219 | + "skipping irq %d, externally requested\n", |
| 220 | + irq); |
| 221 | + continue; |
| 222 | + } |
| 223 | + |
| 224 | + ret = sdca_irq_data_populate(component, function, entity, |
| 225 | + control, interrupt); |
| 226 | + if (ret) |
| 227 | + return ret; |
| 228 | + |
| 229 | + ret = sdca_irq_request_locked(dev, info, irq, interrupt->name, |
| 230 | + base_handler, interrupt); |
| 231 | + if (ret) { |
| 232 | + dev_err(dev, "failed to request irq %s: %d\n", |
| 233 | + name, ret); |
| 234 | + return ret; |
| 235 | + } |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + return 0; |
| 240 | +} |
| 241 | +EXPORT_SYMBOL_NS_GPL(sdca_irq_populate, "SND_SOC_SDCA_IRQ"); |
| 242 | + |
| 243 | +/** |
| 244 | + * sdca_irq_allocate - allocate an SDCA interrupt structure for a device |
| 245 | + * @dev: Device pointer against which things should be allocated. |
| 246 | + * @regmap: regmap to be used for accessing the SDCA IRQ registers. |
| 247 | + * @irq: The interrupt number. |
| 248 | + * |
| 249 | + * Typically this would be called from the top level driver for the whole |
| 250 | + * SDCA device, as only a single instance is required across all Functions |
| 251 | + * on the device. |
| 252 | + * |
| 253 | + * Return: A pointer to the allocated sdca_interrupt_info struct, or an |
| 254 | + * error code. |
| 255 | + */ |
| 256 | +struct sdca_interrupt_info *sdca_irq_allocate(struct device *dev, |
| 257 | + struct regmap *regmap, int irq) |
| 258 | +{ |
| 259 | + struct sdca_interrupt_info *info; |
| 260 | + int ret; |
| 261 | + |
| 262 | + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); |
| 263 | + if (!info) |
| 264 | + return ERR_PTR(-ENOMEM); |
| 265 | + |
| 266 | + info->irq_chip = sdca_irq_chip; |
| 267 | + |
| 268 | + devm_mutex_init(dev, &info->irq_lock); |
| 269 | + |
| 270 | + ret = devm_regmap_add_irq_chip(dev, regmap, irq, IRQF_ONESHOT, 0, |
| 271 | + &info->irq_chip, &info->irq_data); |
| 272 | + if (ret) { |
| 273 | + dev_err(dev, "failed to register irq chip: %d\n", ret); |
| 274 | + return ERR_PTR(ret); |
| 275 | + } |
| 276 | + |
| 277 | + dev_dbg(dev, "registered on irq %d\n", irq); |
| 278 | + |
| 279 | + return info; |
| 280 | +} |
| 281 | +EXPORT_SYMBOL_NS_GPL(sdca_irq_allocate, "SND_SOC_SDCA_IRQ"); |
| 282 | + |
| 283 | +MODULE_LICENSE("GPL"); |
| 284 | +MODULE_DESCRIPTION("SDCA IRQ library"); |
0 commit comments