summaryrefslogtreecommitdiff
path: root/sound/soc/sdca/sdca_interrupts.c
blob: 4539a52a8e32ba4a83929e9790ebbf7b246a4d4a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2025 Cirrus Logic, Inc. and
//                    Cirrus Logic International Semiconductor Ltd.

/*
 * The MIPI SDCA specification is available for public downloads at
 * https://www.mipi.org/mipi-sdca-v1-0-download
 */

#include <linux/bitmap.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/interrupt.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_registers.h>
#include <sound/sdca.h>
#include <sound/sdca_fdl.h>
#include <sound/sdca_function.h>
#include <sound/sdca_hid.h>
#include <sound/sdca_interrupts.h>
#include <sound/sdca_jack.h>
#include <sound/sdca_ump.h>
#include <sound/soc-component.h>
#include <sound/soc.h>

#define IRQ_SDCA(number) REGMAP_IRQ_REG(number, ((number) / BITS_PER_BYTE), \
					SDW_SCP_SDCA_INTMASK_SDCA_##number)

static const struct regmap_irq regmap_irqs[SDCA_MAX_INTERRUPTS] = {
	IRQ_SDCA(0),
	IRQ_SDCA(1),
	IRQ_SDCA(2),
	IRQ_SDCA(3),
	IRQ_SDCA(4),
	IRQ_SDCA(5),
	IRQ_SDCA(6),
	IRQ_SDCA(7),
	IRQ_SDCA(8),
	IRQ_SDCA(9),
	IRQ_SDCA(10),
	IRQ_SDCA(11),
	IRQ_SDCA(12),
	IRQ_SDCA(13),
	IRQ_SDCA(14),
	IRQ_SDCA(15),
	IRQ_SDCA(16),
	IRQ_SDCA(17),
	IRQ_SDCA(18),
	IRQ_SDCA(19),
	IRQ_SDCA(20),
	IRQ_SDCA(21),
	IRQ_SDCA(22),
	IRQ_SDCA(23),
	IRQ_SDCA(24),
	IRQ_SDCA(25),
	IRQ_SDCA(26),
	IRQ_SDCA(27),
	IRQ_SDCA(28),
	IRQ_SDCA(29),
	IRQ_SDCA(30),
};

static const struct regmap_irq_chip sdca_irq_chip = {
	.name = "sdca_irq",

	.status_base = SDW_SCP_SDCA_INT1,
	.unmask_base = SDW_SCP_SDCA_INTMASK1,
	.ack_base = SDW_SCP_SDCA_INT1,
	.num_regs = 4,

	.irqs = regmap_irqs,
	.num_irqs = SDCA_MAX_INTERRUPTS,

	.runtime_pm = true,
};

static irqreturn_t base_handler(int irq, void *data)
{
	struct sdca_interrupt *interrupt = data;
	struct device *dev = interrupt->dev;

	dev_info(dev, "%s irq without full handling\n", interrupt->name);

	return IRQ_HANDLED;
}

static irqreturn_t function_status_handler(int irq, void *data)
{
	struct sdca_interrupt *interrupt = data;
	struct device *dev = interrupt->dev;
	irqreturn_t irqret = IRQ_NONE;
	unsigned int reg, val;
	unsigned long status;
	unsigned int mask;
	int ret;

	ret = pm_runtime_get_sync(dev);
	if (ret < 0) {
		dev_err(dev, "failed to resume for function status: %d\n", ret);
		goto error;
	}

	reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id,
			   interrupt->control->sel, 0);

	ret = regmap_read(interrupt->function_regmap, reg, &val);
	if (ret < 0) {
		dev_err(dev, "failed to read function status: %d\n", ret);
		goto error;
	}

	dev_dbg(dev, "function status: %#x\n", val);

	status = val;
	for_each_set_bit(mask, &status, BITS_PER_BYTE) {
		switch (BIT(mask)) {
		case SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION:
/*
 * FIXME: Should this do init writes?
 *
 * Currently init writes/cache sync are done from the suspend/resume
 * infrastructure. It is unclear in what situations one would receive this
 * IRQ outside of that flow. Presumably it would be something like the chip
 * crashing. In that case however doing the init writes and a cache sync might
 * not be sufficient, for example if the failure was during audio playback
 * there could be ordering constraints on the register writes to restore the
 * state that are not handled by a simple cache sync.
 */
			break;
		case SDCA_CTL_ENTITY_0_FUNCTION_FAULT:
			dev_err(dev, "function fault\n");
			break;
		case SDCA_CTL_ENTITY_0_UMP_SEQUENCE_FAULT:
			dev_err(dev, "ump sequence fault\n");
			break;
		case SDCA_CTL_ENTITY_0_FUNCTION_BUSY:
			dev_info(dev, "unexpected function busy\n");
			break;
		case SDCA_CTL_ENTITY_0_DEVICE_NEWLY_ATTACHED:
		case SDCA_CTL_ENTITY_0_INTS_DISABLED_ABNORMALLY:
		case SDCA_CTL_ENTITY_0_STREAMING_STOPPED_ABNORMALLY:
		case SDCA_CTL_ENTITY_0_FUNCTION_HAS_BEEN_RESET:
			break;
		}
	}

	ret = regmap_write(interrupt->function_regmap, reg, val & 0x7F);
	if (ret < 0) {
		dev_err(dev, "failed to clear function status: %d\n", ret);
		goto error;
	}

	irqret = IRQ_HANDLED;
error:
	pm_runtime_put(dev);
	return irqret;
}

static irqreturn_t detected_mode_handler(int irq, void *data)
{
	struct sdca_interrupt *interrupt = data;
	struct device *dev = interrupt->dev;
	irqreturn_t irqret = IRQ_NONE;
	int ret;

	ret = pm_runtime_get_sync(dev);
	if (ret < 0) {
		dev_err(dev, "failed to resume for detected mode: %d\n", ret);
		goto error;
	}

	ret = sdca_jack_process(interrupt);
	if (ret)
		goto error;

	irqret = IRQ_HANDLED;
error:
	pm_runtime_put(dev);
	return irqret;
}

static irqreturn_t hid_handler(int irq, void *data)
{
	struct sdca_interrupt *interrupt = data;
	struct device *dev = interrupt->dev;
	irqreturn_t irqret = IRQ_NONE;
	int ret;

	ret = pm_runtime_get_sync(dev);
	if (ret < 0) {
		dev_err(dev, "failed to resume for hid: %d\n", ret);
		goto error;
	}

	ret = sdca_hid_process_report(interrupt);
	if (ret)
		goto error;

	irqret = IRQ_HANDLED;
error:
	pm_runtime_put(dev);
	return irqret;
}

#ifdef CONFIG_PM_SLEEP
static bool no_pm_in_progress(struct device *dev)
{
	return completion_done(&dev->power.completion);
}
#else
static bool no_pm_in_progress(struct device *dev)
{
	return true;
}
#endif

static irqreturn_t fdl_owner_handler(int irq, void *data)
{
	struct sdca_interrupt *interrupt = data;
	struct device *dev = interrupt->dev;
	irqreturn_t irqret = IRQ_NONE;
	int ret;

	/*
	 * FDL has to run from the system resume handler, at which point
	 * we can't wait for the pm runtime.
	 */
	if (no_pm_in_progress(dev)) {
		ret = pm_runtime_get_sync(dev);
		if (ret < 0) {
			dev_err(dev, "failed to resume for fdl: %d\n", ret);
			goto error;
		}
	}

	ret = sdca_fdl_process(interrupt);
	if (ret)
		goto error;

	irqret = IRQ_HANDLED;
error:
	if (no_pm_in_progress(dev))
		pm_runtime_put(dev);
	return irqret;
}

static int sdca_irq_request_locked(struct device *dev,
				   struct sdca_interrupt_info *info,
				   int sdca_irq, const char *name,
				   irq_handler_t handler, void *data)
{
	int irq;
	int ret;

	irq = regmap_irq_get_virq(info->irq_data, sdca_irq);
	if (irq < 0)
		return irq;

	ret = request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, name, data);
	if (ret)
		return ret;

	info->irqs[sdca_irq].irq = irq;

	dev_dbg(dev, "requested irq %d for %s\n", irq, name);

	return 0;
}

static void sdca_irq_free_locked(struct device *dev, struct sdca_interrupt_info *info,
				 int sdca_irq, const char *name, void *data)
{
	int irq;

	irq = regmap_irq_get_virq(info->irq_data, sdca_irq);
	if (irq < 0)
		return;

	free_irq(irq, data);

	info->irqs[sdca_irq].irq = 0;

	dev_dbg(dev, "freed irq %d for %s\n", irq, name);
}

/**
 * sdca_irq_request - request an individual SDCA interrupt
 * @dev: Pointer to the struct device against which things should be allocated.
 * @info: Pointer to the interrupt information structure.
 * @sdca_irq: SDCA interrupt position.
 * @name: Name to be given to the IRQ.
 * @handler: A callback thread function to be called for the IRQ.
 * @data: Private data pointer that will be passed to the handler.
 *
 * Typically this is handled internally by sdca_irq_populate, however if
 * a device requires custom IRQ handling this can be called manually before
 * calling sdca_irq_populate, which will then skip that IRQ whilst processing.
 *
 * Return: Zero on success, and a negative error code on failure.
 */
int sdca_irq_request(struct device *dev, struct sdca_interrupt_info *info,
		     int sdca_irq, const char *name, irq_handler_t handler,
		     void *data)
{
	int ret;

	if (sdca_irq < 0 || sdca_irq >= SDCA_MAX_INTERRUPTS) {
		dev_err(dev, "bad irq request: %d\n", sdca_irq);
		return -EINVAL;
	}

	guard(mutex)(&info->irq_lock);

	ret = sdca_irq_request_locked(dev, info, sdca_irq, name, handler, data);
	if (ret) {
		dev_err(dev, "failed to request irq %s: %d\n", name, ret);
		return ret;
	}

	return 0;
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_request, "SND_SOC_SDCA");

/**
 * sdca_irq_free - free an individual SDCA interrupt
 * @dev: Pointer to the struct device.
 * @info: Pointer to the interrupt information structure.
 * @sdca_irq: SDCA interrupt position.
 * @name: Name to be given to the IRQ.
 * @data: Private data pointer that will be passed to the handler.
 *
 * Typically this is handled internally by sdca_irq_cleanup, however if
 * a device requires custom IRQ handling this can be called manually before
 * calling sdca_irq_cleanup, which will then skip that IRQ whilst processing.
 */
void sdca_irq_free(struct device *dev, struct sdca_interrupt_info *info,
		   int sdca_irq, const char *name, void *data)
{
	if (sdca_irq < 0 || sdca_irq >= SDCA_MAX_INTERRUPTS)
		return;

	guard(mutex)(&info->irq_lock);

	sdca_irq_free_locked(dev, info, sdca_irq, name, data);
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_free, "SND_SOC_SDCA");

/**
 * sdca_irq_data_populate - Populate common interrupt data
 * @dev: Pointer to the Function device.
 * @regmap: Pointer to the Function regmap.
 * @component: Pointer to the ASoC component for the Function.
 * @function: Pointer to the SDCA Function.
 * @entity: Pointer to the SDCA Entity.
 * @control: Pointer to the SDCA Control.
 * @interrupt: Pointer to the SDCA interrupt for this IRQ.
 *
 * Return: Zero on success, and a negative error code on failure.
 */
int sdca_irq_data_populate(struct device *dev, struct regmap *regmap,
			   struct snd_soc_component *component,
			   struct sdca_function_data *function,
			   struct sdca_entity *entity,
			   struct sdca_control *control,
			   struct sdca_interrupt *interrupt)
{
	const char *name;

	if (!dev && component)
		dev = component->dev;
	if (!dev)
		return -ENODEV;

	name = kasprintf(GFP_KERNEL, "%s %s", entity->label, control->label);
	if (!name)
		return -ENOMEM;

	interrupt->name = name;
	interrupt->dev = dev;
	if (!regmap && component)
		interrupt->function_regmap = component->regmap;
	else
		interrupt->function_regmap = regmap;
	interrupt->component = component;
	interrupt->function = function;
	interrupt->entity = entity;
	interrupt->control = control;

	return 0;
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_data_populate, "SND_SOC_SDCA");

static struct sdca_interrupt *get_interrupt_data(struct device *dev, int irq,
						 struct sdca_interrupt_info *info)
{
	if (irq == SDCA_NO_INTERRUPT) {
		return NULL;
	} else if (irq < 0 || irq >= SDCA_MAX_INTERRUPTS) {
		dev_err(dev, "bad irq position: %d\n", irq);
		return ERR_PTR(-EINVAL);
	}

	if (info->irqs[irq].irq) {
		dev_dbg(dev, "skipping irq %d, already requested\n", irq);
		return NULL;
	}

	return &info->irqs[irq];
}

/**
 * sdca_irq_populate_early - process pre-audio card IRQ registrations
 * @dev: Device pointer for SDCA Function.
 * @regmap: Regmap pointer for the SDCA Function.
 * @function: Pointer to the SDCA Function.
 * @info: Pointer to the SDCA interrupt info for this device.
 *
 * This is intended to be used as part of the Function boot process. It
 * can be called before the soundcard is registered (ie. doesn't depend
 * on component) and will register the FDL interrupts.
 *
 * Return: Zero on success, and a negative error code on failure.
 */
int sdca_irq_populate_early(struct device *dev, struct regmap *regmap,
			    struct sdca_function_data *function,
			    struct sdca_interrupt_info *info)
{
	int i, j;

	guard(mutex)(&info->irq_lock);

	for (i = 0; i < function->num_entities; i++) {
		struct sdca_entity *entity = &function->entities[i];

		for (j = 0; j < entity->num_controls; j++) {
			struct sdca_control *control = &entity->controls[j];
			int irq = control->interrupt_position;
			struct sdca_interrupt *interrupt;
			int ret;

			interrupt = get_interrupt_data(dev, irq, info);
			if (IS_ERR(interrupt))
				return PTR_ERR(interrupt);
			else if (!interrupt)
				continue;

			switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
			case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER):
				ret = sdca_irq_data_populate(dev, regmap, NULL,
							     function, entity,
							     control, interrupt);
				if (ret)
					return ret;

				ret = sdca_fdl_alloc_state(interrupt);
				if (ret)
					return ret;

				ret = sdca_irq_request_locked(dev, info, irq,
							      interrupt->name,
							      fdl_owner_handler,
							      interrupt);
				if (ret) {
					dev_err(dev, "failed to request irq %s: %d\n",
						interrupt->name, ret);
					return ret;
				}
				break;
			default:
				break;
			}
		}
	}

	return 0;
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_populate_early, "SND_SOC_SDCA");

/**
 * sdca_irq_populate - Request all the individual IRQs for an SDCA Function
 * @function: Pointer to the SDCA Function.
 * @component: Pointer to the ASoC component for the Function.
 * @info: Pointer to the SDCA interrupt info for this device.
 *
 * Typically this would be called from the driver for a single SDCA Function.
 *
 * Return: Zero on success, and a negative error code on failure.
 */
int sdca_irq_populate(struct sdca_function_data *function,
		      struct snd_soc_component *component,
		      struct sdca_interrupt_info *info)
{
	struct device *dev = component->dev;
	int i, j;

	guard(mutex)(&info->irq_lock);

	for (i = 0; i < function->num_entities; i++) {
		struct sdca_entity *entity = &function->entities[i];

		for (j = 0; j < entity->num_controls; j++) {
			struct sdca_control *control = &entity->controls[j];
			int irq = control->interrupt_position;
			struct sdca_interrupt *interrupt;
			irq_handler_t handler;
			int ret;

			interrupt = get_interrupt_data(dev, irq, info);
			if (IS_ERR(interrupt))
				return PTR_ERR(interrupt);
			else if (!interrupt)
				continue;

			ret = sdca_irq_data_populate(dev, NULL, component,
						     function, entity, control,
						     interrupt);
			if (ret)
				return ret;

			handler = base_handler;

			switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
			case SDCA_CTL_TYPE_S(ENTITY_0, FUNCTION_STATUS):
				handler = function_status_handler;
				break;
			case SDCA_CTL_TYPE_S(GE, DETECTED_MODE):
				ret = sdca_jack_alloc_state(interrupt);
				if (ret)
					return ret;

				handler = detected_mode_handler;
				break;
			case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER):
				ret = sdca_fdl_alloc_state(interrupt);
				if (ret)
					return ret;

				handler = fdl_owner_handler;
				break;
			case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER):
				handler = hid_handler;
				break;
			default:
				break;
			}

			ret = sdca_irq_request_locked(dev, info, irq, interrupt->name,
						      handler, interrupt);
			if (ret) {
				dev_err(dev, "failed to request irq %s: %d\n",
					interrupt->name, ret);
				return ret;
			}
		}
	}

	return 0;
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_populate, "SND_SOC_SDCA");

/**
 * sdca_irq_cleanup - Free all the individual IRQs for an SDCA Function
 * @dev: Device pointer against which the sdca_interrupt_info was allocated.
 * @function: Pointer to the SDCA Function.
 * @info: Pointer to the SDCA interrupt info for this device.
 *
 * Typically this would be called from the driver for a single SDCA Function.
 */
void sdca_irq_cleanup(struct device *dev,
		      struct sdca_function_data *function,
		      struct sdca_interrupt_info *info)
{
	int i;

	guard(mutex)(&info->irq_lock);

	for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) {
		struct sdca_interrupt *interrupt = &info->irqs[i];

		if (interrupt->function != function || !interrupt->irq)
			continue;

		sdca_irq_free_locked(dev, info, i, interrupt->name, interrupt);

		kfree(interrupt->name);
	}
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_cleanup, "SND_SOC_SDCA");

/**
 * sdca_irq_allocate - allocate an SDCA interrupt structure for a device
 * @sdev: Device pointer against which things should be allocated.
 * @regmap: regmap to be used for accessing the SDCA IRQ registers.
 * @irq: The interrupt number.
 *
 * Typically this would be called from the top level driver for the whole
 * SDCA device, as only a single instance is required across all Functions
 * on the device.
 *
 * Return: A pointer to the allocated sdca_interrupt_info struct, or an
 * error code.
 */
struct sdca_interrupt_info *sdca_irq_allocate(struct device *sdev,
					      struct regmap *regmap, int irq)
{
	struct sdca_interrupt_info *info;
	int ret, i;

	info = devm_kzalloc(sdev, sizeof(*info), GFP_KERNEL);
	if (!info)
		return ERR_PTR(-ENOMEM);

	info->irq_chip = sdca_irq_chip;

	for (i = 0; i < ARRAY_SIZE(info->irqs); i++)
		info->irqs[i].device_regmap = regmap;

	ret = devm_mutex_init(sdev, &info->irq_lock);
	if (ret)
		return ERR_PTR(ret);

	ret = devm_regmap_add_irq_chip(sdev, regmap, irq, IRQF_ONESHOT, 0,
				       &info->irq_chip, &info->irq_data);
	if (ret) {
		dev_err(sdev, "failed to register irq chip: %d\n", ret);
		return ERR_PTR(ret);
	}

	dev_dbg(sdev, "registered on irq %d\n", irq);

	return info;
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_allocate, "SND_SOC_SDCA");

static void irq_enable_flags(struct sdca_function_data *function,
			     struct sdca_interrupt_info *info, bool early)
{
	int i;

	for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) {
		struct sdca_interrupt *interrupt = &info->irqs[i];

		if (!interrupt->irq || interrupt->function != function)
			continue;

		switch (SDCA_CTL_TYPE(interrupt->entity->type,
				      interrupt->control->sel)) {
		case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER):
			if (early)
				enable_irq(interrupt->irq);
			break;
		default:
			if (!early)
				enable_irq(interrupt->irq);
			break;
		}
	}
}

/**
 * sdca_irq_enable_early - Re-enable early SDCA IRQs for a given function
 * @function: Pointer to the SDCA Function.
 * @info: Pointer to the SDCA interrupt info for this device.
 *
 * The early version of the IRQ enable allows enabling IRQs which may be
 * necessary to bootstrap functionality for other IRQs, such as the FDL
 * process.
 */
void sdca_irq_enable_early(struct sdca_function_data *function,
			   struct sdca_interrupt_info *info)
{
	irq_enable_flags(function, info, true);
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_enable_early, "SND_SOC_SDCA");

/**
 * sdca_irq_enable - Re-enable SDCA IRQs for a given function
 * @function: Pointer to the SDCA Function.
 * @info: Pointer to the SDCA interrupt info for this device.
 */
void sdca_irq_enable(struct sdca_function_data *function,
		     struct sdca_interrupt_info *info)
{
	irq_enable_flags(function, info, false);
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_enable, "SND_SOC_SDCA");

/**
 * sdca_irq_disable - Disable SDCA IRQs for a given function
 * @function: Pointer to the SDCA Function.
 * @info: Pointer to the SDCA interrupt info for this device.
 */
void sdca_irq_disable(struct sdca_function_data *function,
		      struct sdca_interrupt_info *info)
{
	int i;

	for (i = 0; i < SDCA_MAX_INTERRUPTS; i++) {
		struct sdca_interrupt *interrupt = &info->irqs[i];

		if (!interrupt->irq || interrupt->function != function)
			continue;

		disable_irq(interrupt->irq);
	}
}
EXPORT_SYMBOL_NS_GPL(sdca_irq_disable, "SND_SOC_SDCA");