1// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
2//
3// This file is provided under a dual BSD/GPLv2 license. When using or
4// redistributing this file, you may do so under either license.
5//
6// Copyright(c) 2022 Intel Corporation
7//
8
9/*
10 * Management of HDaudio multi-link (capabilities, power, coupling)
11 */
12
13#include <sound/hdaudio_ext.h>
14#include <sound/hda_register.h>
15#include <sound/hda-mlink.h>
16
17#include <linux/bitfield.h>
18#include <linux/module.h>
19#include <linux/string_choices.h>
20
21#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)
22
23/* worst-case number of sublinks is used for sublink refcount array allocation only */
24#define HDAML_MAX_SUBLINKS (AZX_ML_LCTL_CPA_SHIFT - AZX_ML_LCTL_SPA_SHIFT)
25
26/**
27 * struct hdac_ext2_link - HDAudio extended+alternate link
28 *
29 * @hext_link: hdac_ext_link
30 * @alt: flag set for alternate extended links
31 * @intc: boolean for interrupt capable
32 * @ofls: boolean for offload support
33 * @lss: boolean for link synchronization capabilities
34 * @slcount: sublink count
35 * @elid: extended link ID (AZX_REG_ML_LEPTR_ID_ defines)
36 * @elver: extended link version
37 * @leptr: extended link pointer
38 * @eml_lock: mutual exclusion to access shared registers e.g. CPA/SPA bits
39 * in LCTL register
40 * @sublink_ref_count: array of refcounts, required to power-manage sublinks independently
41 * @base_ptr: pointer to shim/ip/shim_vs space
42 * @instance_offset: offset between each of @slcount instances managed by link
43 * @shim_offset: offset to SHIM register base
44 * @ip_offset: offset to IP register base
45 * @shim_vs_offset: offset to vendor-specific (VS) SHIM base
46 * @mic_privacy_mask: bitmask of sublinks where mic privacy is applied
47 */
48struct hdac_ext2_link {
49 struct hdac_ext_link hext_link;
50
51 /* read directly from LCAP register */
52 bool alt;
53 bool intc;
54 bool ofls;
55 bool lss;
56 int slcount;
57 int elid;
58 int elver;
59 u32 leptr;
60
61 struct mutex eml_lock; /* prevent concurrent access to e.g. CPA/SPA */
62 int sublink_ref_count[HDAML_MAX_SUBLINKS];
63
64 /* internal values computed from LCAP contents */
65 void __iomem *base_ptr;
66 u32 instance_offset;
67 u32 shim_offset;
68 u32 ip_offset;
69 u32 shim_vs_offset;
70
71 unsigned long mic_privacy_mask;
72};
73
74#define hdac_ext_link_to_ext2(h) container_of(h, struct hdac_ext2_link, hext_link)
75
76#define AZX_REG_SDW_INSTANCE_OFFSET 0x8000
77#define AZX_REG_SDW_SHIM_OFFSET 0x0
78#define AZX_REG_SDW_IP_OFFSET 0x100
79#define AZX_REG_SDW_VS_SHIM_OFFSET 0x6000
80#define AZX_REG_SDW_SHIM_PCMSyCM(y) (0x16 + 0x4 * (y))
81
82/* only one instance supported */
83#define AZX_REG_INTEL_DMIC_SHIM_OFFSET 0x0
84#define AZX_REG_INTEL_DMIC_IP_OFFSET 0x100
85#define AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET 0x6000
86
87#define AZX_REG_INTEL_SSP_INSTANCE_OFFSET 0x1000
88#define AZX_REG_INTEL_SSP_SHIM_OFFSET 0x0
89#define AZX_REG_INTEL_SSP_IP_OFFSET 0x100
90#define AZX_REG_INTEL_SSP_VS_SHIM_OFFSET 0xC00
91
92/* only one instance supported */
93#define AZX_REG_INTEL_UAOL_SHIM_OFFSET 0x0
94#define AZX_REG_INTEL_UAOL_IP_OFFSET 0x100
95#define AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET 0xC00
96
97/* Microphone privacy */
98#define AZX_REG_INTEL_VS_SHIM_PVCCS 0x10
99#define AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE BIT(0)
100#define AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHG BIT(8)
101#define AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS BIT(9)
102#define AZX_REG_INTEL_VS_SHIM_PVCCS_FMDIS BIT(10)
103
104/* HDAML section - this part follows sequences in the hardware specification,
105 * including naming conventions and the use of the hdaml_ prefix.
106 * The code is intentionally minimal with limited dependencies on frameworks or
107 * helpers. Locking and scanning lists is handled at a higher level
108 */
109
110static int hdaml_lnk_enum(struct device *dev, struct hdac_ext2_link *h2link,
111 void __iomem *remap_addr, void __iomem *ml_addr, int link_idx)
112{
113 struct hdac_ext_link *hlink = &h2link->hext_link;
114 u32 base_offset;
115
116 hlink->lcaps = readl(addr: ml_addr + AZX_REG_ML_LCAP);
117
118 h2link->alt = FIELD_GET(AZX_ML_HDA_LCAP_ALT, hlink->lcaps);
119
120 /* handle alternate extensions */
121 if (!h2link->alt) {
122 h2link->slcount = 1;
123
124 /*
125 * LSDIID is initialized by hardware for HDaudio link,
126 * it needs to be setup by software for alternate links
127 */
128 hlink->lsdiid = readw(addr: ml_addr + AZX_REG_ML_LSDIID);
129
130 dev_dbg(dev, "Link %d: HDAudio - lsdiid=%d\n",
131 link_idx, hlink->lsdiid);
132
133 return 0;
134 }
135
136 h2link->intc = FIELD_GET(AZX_ML_HDA_LCAP_INTC, hlink->lcaps);
137 h2link->ofls = FIELD_GET(AZX_ML_HDA_LCAP_OFLS, hlink->lcaps);
138 h2link->lss = FIELD_GET(AZX_ML_HDA_LCAP_LSS, hlink->lcaps);
139
140 /* read slcount (increment due to zero-based hardware representation */
141 h2link->slcount = FIELD_GET(AZX_ML_HDA_LCAP_SLCOUNT, hlink->lcaps) + 1;
142 dev_dbg(dev, "Link %d: HDAudio extended - sublink count %d\n",
143 link_idx, h2link->slcount);
144
145 /* find IP ID and offsets */
146 h2link->leptr = readl(addr: ml_addr + AZX_REG_ML_LEPTR);
147
148 h2link->elid = FIELD_GET(AZX_REG_ML_LEPTR_ID, h2link->leptr);
149
150 base_offset = FIELD_GET(AZX_REG_ML_LEPTR_PTR, h2link->leptr);
151 h2link->base_ptr = remap_addr + base_offset;
152
153 switch (h2link->elid) {
154 case AZX_REG_ML_LEPTR_ID_SDW:
155 h2link->instance_offset = AZX_REG_SDW_INSTANCE_OFFSET;
156 h2link->shim_offset = AZX_REG_SDW_SHIM_OFFSET;
157 h2link->ip_offset = AZX_REG_SDW_IP_OFFSET;
158 h2link->shim_vs_offset = AZX_REG_SDW_VS_SHIM_OFFSET;
159 dev_dbg(dev, "Link %d: HDAudio extended - SoundWire alternate link, leptr.ptr %#x\n",
160 link_idx, base_offset);
161 break;
162 case AZX_REG_ML_LEPTR_ID_INTEL_DMIC:
163 h2link->shim_offset = AZX_REG_INTEL_DMIC_SHIM_OFFSET;
164 h2link->ip_offset = AZX_REG_INTEL_DMIC_IP_OFFSET;
165 h2link->shim_vs_offset = AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET;
166 dev_dbg(dev, "Link %d: HDAudio extended - INTEL DMIC alternate link, leptr.ptr %#x\n",
167 link_idx, base_offset);
168 break;
169 case AZX_REG_ML_LEPTR_ID_INTEL_SSP:
170 h2link->instance_offset = AZX_REG_INTEL_SSP_INSTANCE_OFFSET;
171 h2link->shim_offset = AZX_REG_INTEL_SSP_SHIM_OFFSET;
172 h2link->ip_offset = AZX_REG_INTEL_SSP_IP_OFFSET;
173 h2link->shim_vs_offset = AZX_REG_INTEL_SSP_VS_SHIM_OFFSET;
174 dev_dbg(dev, "Link %d: HDAudio extended - INTEL SSP alternate link, leptr.ptr %#x\n",
175 link_idx, base_offset);
176 break;
177 case AZX_REG_ML_LEPTR_ID_INTEL_UAOL:
178 h2link->shim_offset = AZX_REG_INTEL_UAOL_SHIM_OFFSET;
179 h2link->ip_offset = AZX_REG_INTEL_UAOL_IP_OFFSET;
180 h2link->shim_vs_offset = AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET;
181 dev_dbg(dev, "Link %d: HDAudio extended - INTEL UAOL alternate link, leptr.ptr %#x\n",
182 link_idx, base_offset);
183 break;
184 default:
185 dev_err(dev, "Link %d: HDAudio extended - Unsupported alternate link, leptr.id=%#02x value\n",
186 link_idx, h2link->elid);
187 return -EINVAL;
188 }
189 return 0;
190}
191
192/*
193 * Hardware recommendations are to wait ~10us before checking any hardware transition
194 * reported by bits changing status.
195 * This value does not need to be super-precise, a slack of 5us is perfectly acceptable.
196 * The worst-case is about 1ms before reporting an issue
197 */
198#define HDAML_POLL_DELAY_MIN_US 10
199#define HDAML_POLL_DELAY_SLACK_US 5
200#define HDAML_POLL_DELAY_RETRY 100
201
202static int check_sublink_power(u32 __iomem *lctl, int sublink, bool enabled)
203{
204 int mask = BIT(sublink) << AZX_ML_LCTL_CPA_SHIFT;
205 int retry = HDAML_POLL_DELAY_RETRY;
206 u32 val;
207
208 usleep_range(HDAML_POLL_DELAY_MIN_US,
209 HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
210 do {
211 val = readl(addr: lctl);
212 if (enabled) {
213 if (val & mask)
214 return 0;
215 } else {
216 if (!(val & mask))
217 return 0;
218 }
219 usleep_range(HDAML_POLL_DELAY_MIN_US,
220 HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
221
222 } while (--retry);
223
224 return -EIO;
225}
226
227static int hdaml_link_init(u32 __iomem *lctl, int sublink)
228{
229 u32 val;
230 u32 mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT;
231
232 val = readl(addr: lctl);
233 val |= mask;
234
235 writel(val, addr: lctl);
236
237 return check_sublink_power(lctl, sublink, enabled: true);
238}
239
240static int hdaml_link_shutdown(u32 __iomem *lctl, int sublink)
241{
242 u32 val;
243 u32 mask;
244
245 val = readl(addr: lctl);
246 mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT;
247 val &= ~mask;
248
249 writel(val, addr: lctl);
250
251 return check_sublink_power(lctl, sublink, enabled: false);
252}
253
254static void hdaml_link_enable_interrupt(u32 __iomem *lctl, bool enable)
255{
256 u32 val;
257
258 val = readl(addr: lctl);
259 if (enable)
260 val |= AZX_ML_LCTL_INTEN;
261 else
262 val &= ~AZX_ML_LCTL_INTEN;
263
264 writel(val, addr: lctl);
265}
266
267static bool hdaml_link_check_interrupt(u32 __iomem *lctl)
268{
269 u32 val;
270
271 val = readl(addr: lctl);
272
273 return val & AZX_ML_LCTL_INTSTS;
274}
275
276static int hdaml_wait_bit(void __iomem *base, int offset, u32 mask, u32 target)
277{
278 int timeout = HDAML_POLL_DELAY_RETRY;
279 u32 reg_read;
280
281 do {
282 reg_read = readl(addr: base + offset);
283 if ((reg_read & mask) == target)
284 return 0;
285
286 timeout--;
287 usleep_range(HDAML_POLL_DELAY_MIN_US,
288 HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
289 } while (timeout != 0);
290
291 return -EAGAIN;
292}
293
294static void hdaml_link_set_syncprd(u32 __iomem *lsync, u32 syncprd)
295{
296 u32 val;
297
298 val = readl(addr: lsync);
299 val &= ~AZX_REG_ML_LSYNC_SYNCPRD;
300 val |= (syncprd & AZX_REG_ML_LSYNC_SYNCPRD);
301
302 /*
303 * set SYNCPU but do not wait. The bit is cleared by hardware when
304 * the link becomes active.
305 */
306 val |= AZX_REG_ML_LSYNC_SYNCPU;
307
308 writel(val, addr: lsync);
309}
310
311static int hdaml_link_wait_syncpu(u32 __iomem *lsync)
312{
313 return hdaml_wait_bit(base: lsync, offset: 0, AZX_REG_ML_LSYNC_SYNCPU, target: 0);
314}
315
316static void hdaml_link_sync_arm(u32 __iomem *lsync, int sublink)
317{
318 u32 val;
319
320 val = readl(addr: lsync);
321 val |= (AZX_REG_ML_LSYNC_CMDSYNC << sublink);
322
323 writel(val, addr: lsync);
324}
325
326static void hdaml_link_sync_go(u32 __iomem *lsync)
327{
328 u32 val;
329
330 val = readl(addr: lsync);
331 val |= AZX_REG_ML_LSYNC_SYNCGO;
332
333 writel(val, addr: lsync);
334}
335
336static bool hdaml_link_check_cmdsync(u32 __iomem *lsync, u32 cmdsync_mask)
337{
338 u32 val;
339
340 val = readl(addr: lsync);
341
342 return !!(val & cmdsync_mask);
343}
344
345static u16 hdaml_link_get_lsdiid(u16 __iomem *lsdiid)
346{
347 return readw(addr: lsdiid);
348}
349
350static void hdaml_link_set_lsdiid(u16 __iomem *lsdiid, int dev_num)
351{
352 u16 val;
353
354 val = readw(addr: lsdiid);
355 val |= BIT(dev_num);
356
357 writew(val, addr: lsdiid);
358}
359
360static void hdaml_shim_map_stream_ch(u16 __iomem *pcmsycm, int lchan, int hchan,
361 int stream_id, int dir)
362{
363 u16 val;
364
365 val = readw(addr: pcmsycm);
366
367 u16p_replace_bits(p: &val, val: lchan, GENMASK(3, 0));
368 u16p_replace_bits(p: &val, val: hchan, GENMASK(7, 4));
369 u16p_replace_bits(p: &val, val: stream_id, GENMASK(13, 8));
370 u16p_replace_bits(p: &val, val: dir, BIT(15));
371
372 writew(val, addr: pcmsycm);
373}
374
375static void hdaml_lctl_offload_enable(u32 __iomem *lctl, bool enable)
376{
377 u32 val = readl(addr: lctl);
378
379 if (enable)
380 val |= AZX_ML_LCTL_OFLEN;
381 else
382 val &= ~AZX_ML_LCTL_OFLEN;
383
384 writel(val, addr: lctl);
385}
386
387/* END HDAML section */
388
389static int hda_ml_alloc_h2link(struct hdac_bus *bus, int index)
390{
391 struct hdac_ext2_link *h2link;
392 struct hdac_ext_link *hlink;
393 int ret;
394
395 h2link = kzalloc(sizeof(*h2link), GFP_KERNEL);
396 if (!h2link)
397 return -ENOMEM;
398
399 /* basic initialization */
400 hlink = &h2link->hext_link;
401
402 hlink->index = index;
403 hlink->bus = bus;
404 hlink->ml_addr = bus->mlcap + AZX_ML_BASE + (AZX_ML_INTERVAL * index);
405
406 ret = hdaml_lnk_enum(dev: bus->dev, h2link, remap_addr: bus->remap_addr, ml_addr: hlink->ml_addr, link_idx: index);
407 if (ret < 0) {
408 kfree(objp: h2link);
409 return ret;
410 }
411
412 mutex_init(&h2link->eml_lock);
413
414 list_add_tail(new: &hlink->list, head: &bus->hlink_list);
415
416 /*
417 * HDaudio regular links are powered-on by default, the
418 * refcount needs to be initialized.
419 */
420 if (!h2link->alt)
421 hlink->ref_count = 1;
422
423 return 0;
424}
425
426int hda_bus_ml_init(struct hdac_bus *bus)
427{
428 u32 link_count;
429 int ret;
430 int i;
431
432 if (!bus->mlcap)
433 return 0;
434
435 link_count = readl(addr: bus->mlcap + AZX_REG_ML_MLCD) + 1;
436
437 dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count);
438
439 for (i = 0; i < link_count; i++) {
440 ret = hda_ml_alloc_h2link(bus, index: i);
441 if (ret < 0) {
442 hda_bus_ml_free(bus);
443 return ret;
444 }
445 }
446 return 0;
447}
448EXPORT_SYMBOL_NS(hda_bus_ml_init, "SND_SOC_SOF_HDA_MLINK");
449
450void hda_bus_ml_free(struct hdac_bus *bus)
451{
452 struct hdac_ext_link *hlink, *_h;
453 struct hdac_ext2_link *h2link;
454
455 if (!bus->mlcap)
456 return;
457
458 list_for_each_entry_safe(hlink, _h, &bus->hlink_list, list) {
459 list_del(entry: &hlink->list);
460 h2link = hdac_ext_link_to_ext2(hlink);
461
462 mutex_destroy(lock: &h2link->eml_lock);
463 kfree(objp: h2link);
464 }
465}
466EXPORT_SYMBOL_NS(hda_bus_ml_free, "SND_SOC_SOF_HDA_MLINK");
467
468static struct hdac_ext2_link *
469find_ext2_link(struct hdac_bus *bus, bool alt, int elid)
470{
471 struct hdac_ext_link *hlink;
472
473 list_for_each_entry(hlink, &bus->hlink_list, list) {
474 struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
475
476 if (h2link->alt == alt && h2link->elid == elid)
477 return h2link;
478 }
479
480 return NULL;
481}
482
483int hdac_bus_eml_get_count(struct hdac_bus *bus, bool alt, int elid)
484{
485 struct hdac_ext2_link *h2link;
486
487 h2link = find_ext2_link(bus, alt, elid);
488 if (!h2link)
489 return 0;
490
491 return h2link->slcount;
492}
493EXPORT_SYMBOL_NS(hdac_bus_eml_get_count, "SND_SOC_SOF_HDA_MLINK");
494
495void hdac_bus_eml_enable_interrupt_unlocked(struct hdac_bus *bus, bool alt, int elid, bool enable)
496{
497 struct hdac_ext2_link *h2link;
498 struct hdac_ext_link *hlink;
499
500 h2link = find_ext2_link(bus, alt, elid);
501 if (!h2link)
502 return;
503
504 if (!h2link->intc)
505 return;
506
507 hlink = &h2link->hext_link;
508
509 hdaml_link_enable_interrupt(lctl: hlink->ml_addr + AZX_REG_ML_LCTL, enable);
510}
511EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt_unlocked, "SND_SOC_SOF_HDA_MLINK");
512
513void hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable)
514{
515 struct hdac_ext2_link *h2link;
516 struct hdac_ext_link *hlink;
517
518 h2link = find_ext2_link(bus, alt, elid);
519 if (!h2link)
520 return;
521
522 if (!h2link->intc)
523 return;
524
525 hlink = &h2link->hext_link;
526
527 mutex_lock(&h2link->eml_lock);
528
529 hdaml_link_enable_interrupt(lctl: hlink->ml_addr + AZX_REG_ML_LCTL, enable);
530
531 mutex_unlock(lock: &h2link->eml_lock);
532}
533EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt, "SND_SOC_SOF_HDA_MLINK");
534
535bool hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid)
536{
537 struct hdac_ext2_link *h2link;
538 struct hdac_ext_link *hlink;
539
540 h2link = find_ext2_link(bus, alt, elid);
541 if (!h2link)
542 return false;
543
544 if (!h2link->intc)
545 return false;
546
547 hlink = &h2link->hext_link;
548
549 return hdaml_link_check_interrupt(lctl: hlink->ml_addr + AZX_REG_ML_LCTL);
550}
551EXPORT_SYMBOL_NS(hdac_bus_eml_check_interrupt, "SND_SOC_SOF_HDA_MLINK");
552
553int hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd)
554{
555 struct hdac_ext2_link *h2link;
556 struct hdac_ext_link *hlink;
557
558 h2link = find_ext2_link(bus, alt, elid);
559 if (!h2link)
560 return 0;
561
562 if (!h2link->lss)
563 return 0;
564
565 hlink = &h2link->hext_link;
566
567 hdaml_link_set_syncprd(lsync: hlink->ml_addr + AZX_REG_ML_LSYNC, syncprd);
568
569 return 0;
570}
571EXPORT_SYMBOL_NS(hdac_bus_eml_set_syncprd_unlocked, "SND_SOC_SOF_HDA_MLINK");
572
573int hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd)
574{
575 return hdac_bus_eml_set_syncprd_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, syncprd);
576}
577EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_syncprd_unlocked, "SND_SOC_SOF_HDA_MLINK");
578
579int hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid)
580{
581 struct hdac_ext2_link *h2link;
582 struct hdac_ext_link *hlink;
583
584 h2link = find_ext2_link(bus, alt, elid);
585 if (!h2link)
586 return 0;
587
588 if (!h2link->lss)
589 return 0;
590
591 hlink = &h2link->hext_link;
592
593 return hdaml_link_wait_syncpu(lsync: hlink->ml_addr + AZX_REG_ML_LSYNC);
594}
595EXPORT_SYMBOL_NS(hdac_bus_eml_wait_syncpu_unlocked, "SND_SOC_SOF_HDA_MLINK");
596
597int hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus)
598{
599 return hdac_bus_eml_wait_syncpu_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
600}
601EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_wait_syncpu_unlocked, "SND_SOC_SOF_HDA_MLINK");
602
603void hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
604{
605 struct hdac_ext2_link *h2link;
606 struct hdac_ext_link *hlink;
607
608 h2link = find_ext2_link(bus, alt, elid);
609 if (!h2link)
610 return;
611
612 if (!h2link->lss)
613 return;
614
615 hlink = &h2link->hext_link;
616
617 hdaml_link_sync_arm(lsync: hlink->ml_addr + AZX_REG_ML_LSYNC, sublink);
618}
619EXPORT_SYMBOL_NS(hdac_bus_eml_sync_arm_unlocked, "SND_SOC_SOF_HDA_MLINK");
620
621void hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink)
622{
623 hdac_bus_eml_sync_arm_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
624}
625EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_arm_unlocked, "SND_SOC_SOF_HDA_MLINK");
626
627int hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid)
628{
629 struct hdac_ext2_link *h2link;
630 struct hdac_ext_link *hlink;
631
632 h2link = find_ext2_link(bus, alt, elid);
633 if (!h2link)
634 return 0;
635
636 if (!h2link->lss)
637 return 0;
638
639 hlink = &h2link->hext_link;
640
641 hdaml_link_sync_go(lsync: hlink->ml_addr + AZX_REG_ML_LSYNC);
642
643 return 0;
644}
645EXPORT_SYMBOL_NS(hdac_bus_eml_sync_go_unlocked, "SND_SOC_SOF_HDA_MLINK");
646
647int hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus)
648{
649 return hdac_bus_eml_sync_go_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
650}
651EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_go_unlocked, "SND_SOC_SOF_HDA_MLINK");
652
653bool hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid)
654{
655 struct hdac_ext2_link *h2link;
656 struct hdac_ext_link *hlink;
657 u32 cmdsync_mask;
658
659 h2link = find_ext2_link(bus, alt, elid);
660 if (!h2link)
661 return 0;
662
663 if (!h2link->lss)
664 return 0;
665
666 hlink = &h2link->hext_link;
667
668 cmdsync_mask = GENMASK(AZX_REG_ML_LSYNC_CMDSYNC_SHIFT + h2link->slcount - 1,
669 AZX_REG_ML_LSYNC_CMDSYNC_SHIFT);
670
671 return hdaml_link_check_cmdsync(lsync: hlink->ml_addr + AZX_REG_ML_LSYNC,
672 cmdsync_mask);
673}
674EXPORT_SYMBOL_NS(hdac_bus_eml_check_cmdsync_unlocked, "SND_SOC_SOF_HDA_MLINK");
675
676bool hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus)
677{
678 return hdac_bus_eml_check_cmdsync_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
679}
680EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_check_cmdsync_unlocked, "SND_SOC_SOF_HDA_MLINK");
681
682static int hdac_bus_eml_power_up_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
683 bool eml_lock)
684{
685 struct hdac_ext2_link *h2link;
686 struct hdac_ext_link *hlink;
687 int ret = 0;
688
689 h2link = find_ext2_link(bus, alt, elid);
690 if (!h2link)
691 return -ENODEV;
692
693 if (sublink >= h2link->slcount)
694 return -EINVAL;
695
696 hlink = &h2link->hext_link;
697
698 if (eml_lock)
699 mutex_lock(&h2link->eml_lock);
700
701 if (!alt) {
702 if (++hlink->ref_count > 1)
703 goto skip_init;
704 } else {
705 if (++h2link->sublink_ref_count[sublink] > 1)
706 goto skip_init;
707 }
708
709 ret = hdaml_link_init(lctl: hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
710 if ((h2link->mic_privacy_mask & BIT(sublink)) && !ret) {
711 u16 __iomem *pvccs = h2link->base_ptr +
712 h2link->shim_vs_offset +
713 sublink * h2link->instance_offset +
714 AZX_REG_INTEL_VS_SHIM_PVCCS;
715 u16 val = readw(addr: pvccs);
716
717 writew(val: val | AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE, addr: pvccs);
718
719 if (val & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS)
720 dev_dbg(bus->dev,
721 "sublink %d (%d:%d): Mic privacy is enabled\n",
722 sublink, alt, elid);
723 }
724
725skip_init:
726 if (eml_lock)
727 mutex_unlock(lock: &h2link->eml_lock);
728
729 return ret;
730}
731
732int hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink)
733{
734 return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, eml_lock: true);
735}
736EXPORT_SYMBOL_NS(hdac_bus_eml_power_up, "SND_SOC_SOF_HDA_MLINK");
737
738int hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
739{
740 return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, eml_lock: false);
741}
742EXPORT_SYMBOL_NS(hdac_bus_eml_power_up_unlocked, "SND_SOC_SOF_HDA_MLINK");
743
744static int hdac_bus_eml_power_down_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
745 bool eml_lock)
746{
747 struct hdac_ext2_link *h2link;
748 struct hdac_ext_link *hlink;
749 int ret = 0;
750
751 h2link = find_ext2_link(bus, alt, elid);
752 if (!h2link)
753 return -ENODEV;
754
755 if (sublink >= h2link->slcount)
756 return -EINVAL;
757
758 hlink = &h2link->hext_link;
759
760 if (eml_lock)
761 mutex_lock(&h2link->eml_lock);
762
763 if (!alt) {
764 if (--hlink->ref_count > 0)
765 goto skip_shutdown;
766 } else {
767 if (--h2link->sublink_ref_count[sublink] > 0)
768 goto skip_shutdown;
769 }
770
771 if (h2link->mic_privacy_mask & BIT(sublink)) {
772 u16 __iomem *pvccs = h2link->base_ptr +
773 h2link->shim_vs_offset +
774 sublink * h2link->instance_offset +
775 AZX_REG_INTEL_VS_SHIM_PVCCS;
776
777 writew(readw(addr: pvccs) & ~AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE, addr: pvccs);
778 }
779
780 ret = hdaml_link_shutdown(lctl: hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
781
782skip_shutdown:
783 if (eml_lock)
784 mutex_unlock(lock: &h2link->eml_lock);
785
786 return ret;
787}
788
789int hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink)
790{
791 return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, eml_lock: true);
792}
793EXPORT_SYMBOL_NS(hdac_bus_eml_power_down, "SND_SOC_SOF_HDA_MLINK");
794
795int hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
796{
797 return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, eml_lock: false);
798}
799EXPORT_SYMBOL_NS(hdac_bus_eml_power_down_unlocked, "SND_SOC_SOF_HDA_MLINK");
800
801int hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink)
802{
803 return hdac_bus_eml_power_up_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
804}
805EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_up_unlocked, "SND_SOC_SOF_HDA_MLINK");
806
807int hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink)
808{
809 return hdac_bus_eml_power_down_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
810}
811EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_down_unlocked, "SND_SOC_SOF_HDA_MLINK");
812
813int hdac_bus_eml_sdw_get_lsdiid_unlocked(struct hdac_bus *bus, int sublink, u16 *lsdiid)
814{
815 struct hdac_ext2_link *h2link;
816 struct hdac_ext_link *hlink;
817
818 h2link = find_ext2_link(bus, alt: true, AZX_REG_ML_LEPTR_ID_SDW);
819 if (!h2link)
820 return -ENODEV;
821
822 hlink = &h2link->hext_link;
823
824 *lsdiid = hdaml_link_get_lsdiid(lsdiid: hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink));
825
826 return 0;
827} EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_lsdiid_unlocked, "SND_SOC_SOF_HDA_MLINK");
828
829int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num)
830{
831 struct hdac_ext2_link *h2link;
832 struct hdac_ext_link *hlink;
833
834 h2link = find_ext2_link(bus, alt: true, AZX_REG_ML_LEPTR_ID_SDW);
835 if (!h2link)
836 return -ENODEV;
837
838 hlink = &h2link->hext_link;
839
840 mutex_lock(&h2link->eml_lock);
841
842 hdaml_link_set_lsdiid(lsdiid: hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink), dev_num);
843
844 mutex_unlock(lock: &h2link->eml_lock);
845
846 return 0;
847} EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_lsdiid, "SND_SOC_SOF_HDA_MLINK");
848
849/*
850 * the 'y' parameter comes from the PCMSyCM hardware register naming. 'y' refers to the
851 * PDI index, i.e. the FIFO used for RX or TX
852 */
853int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y,
854 int channel_mask, int stream_id, int dir)
855{
856 struct hdac_ext2_link *h2link;
857 u16 __iomem *pcmsycm;
858 int hchan;
859 int lchan;
860 u16 val;
861
862 h2link = find_ext2_link(bus, alt: true, AZX_REG_ML_LEPTR_ID_SDW);
863 if (!h2link)
864 return -ENODEV;
865
866 pcmsycm = h2link->base_ptr + h2link->shim_offset +
867 h2link->instance_offset * sublink +
868 AZX_REG_SDW_SHIM_PCMSyCM(y);
869
870 if (channel_mask) {
871 hchan = __fls(word: channel_mask);
872 lchan = __ffs(channel_mask);
873 } else {
874 hchan = 0;
875 lchan = 0;
876 }
877
878 mutex_lock(&h2link->eml_lock);
879
880 hdaml_shim_map_stream_ch(pcmsycm, lchan, hchan,
881 stream_id, dir);
882
883 mutex_unlock(lock: &h2link->eml_lock);
884
885 val = readw(addr: pcmsycm);
886
887 dev_dbg(bus->dev, "sublink %d channel_mask %#x stream_id %d dir %d pcmscm %#x\n",
888 sublink, channel_mask, stream_id, dir, val);
889
890 return 0;
891} EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_map_stream_ch, "SND_SOC_SOF_HDA_MLINK");
892
893void hda_bus_ml_put_all(struct hdac_bus *bus)
894{
895 struct hdac_ext_link *hlink;
896
897 list_for_each_entry(hlink, &bus->hlink_list, list) {
898 struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
899
900 if (!h2link->alt)
901 snd_hdac_ext_bus_link_put(bus, hlink);
902 }
903}
904EXPORT_SYMBOL_NS(hda_bus_ml_put_all, "SND_SOC_SOF_HDA_MLINK");
905
906void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
907{
908 struct hdac_ext_link *hlink;
909
910 /* Reset stream-to-link mapping */
911 list_for_each_entry(hlink, &bus->hlink_list, list)
912 writel(val: 0, addr: hlink->ml_addr + AZX_REG_ML_LOSIDV);
913}
914EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK");
915
916int hda_bus_ml_resume(struct hdac_bus *bus)
917{
918 struct hdac_ext_link *hlink;
919 int ret;
920
921 /* power up links that were active before suspend */
922 list_for_each_entry(hlink, &bus->hlink_list, list) {
923 struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
924
925 if (!h2link->alt && hlink->ref_count) {
926 ret = snd_hdac_ext_bus_link_power_up(hlink);
927 if (ret < 0)
928 return ret;
929 }
930 }
931 return 0;
932}
933EXPORT_SYMBOL_NS(hda_bus_ml_resume, "SND_SOC_SOF_HDA_MLINK");
934
935int hda_bus_ml_suspend(struct hdac_bus *bus)
936{
937 struct hdac_ext_link *hlink;
938 int ret;
939
940 list_for_each_entry(hlink, &bus->hlink_list, list) {
941 struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
942
943 if (!h2link->alt) {
944 ret = snd_hdac_ext_bus_link_power_down(hlink);
945 if (ret < 0)
946 return ret;
947 }
948 }
949 return 0;
950}
951EXPORT_SYMBOL_NS(hda_bus_ml_suspend, "SND_SOC_SOF_HDA_MLINK");
952
953struct mutex *hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid)
954{
955 struct hdac_ext2_link *h2link;
956
957 h2link = find_ext2_link(bus, alt, elid);
958 if (!h2link)
959 return NULL;
960
961 return &h2link->eml_lock;
962}
963EXPORT_SYMBOL_NS(hdac_bus_eml_get_mutex, "SND_SOC_SOF_HDA_MLINK");
964
965struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus)
966{
967 struct hdac_ext2_link *h2link;
968
969 h2link = find_ext2_link(bus, alt: true, AZX_REG_ML_LEPTR_ID_INTEL_SSP);
970 if (!h2link)
971 return NULL;
972
973 return &h2link->hext_link;
974}
975EXPORT_SYMBOL_NS(hdac_bus_eml_ssp_get_hlink, "SND_SOC_SOF_HDA_MLINK");
976
977struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus)
978{
979 struct hdac_ext2_link *h2link;
980
981 h2link = find_ext2_link(bus, alt: true, AZX_REG_ML_LEPTR_ID_INTEL_DMIC);
982 if (!h2link)
983 return NULL;
984
985 return &h2link->hext_link;
986}
987EXPORT_SYMBOL_NS(hdac_bus_eml_dmic_get_hlink, "SND_SOC_SOF_HDA_MLINK");
988
989struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus)
990{
991 struct hdac_ext2_link *h2link;
992
993 h2link = find_ext2_link(bus, alt: true, AZX_REG_ML_LEPTR_ID_SDW);
994 if (!h2link)
995 return NULL;
996
997 return &h2link->hext_link;
998}
999EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_hlink, "SND_SOC_SOF_HDA_MLINK");
1000
1001int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable)
1002{
1003 struct hdac_ext2_link *h2link;
1004 struct hdac_ext_link *hlink;
1005
1006 h2link = find_ext2_link(bus, alt, elid);
1007 if (!h2link)
1008 return -ENODEV;
1009
1010 if (!h2link->ofls)
1011 return 0;
1012
1013 hlink = &h2link->hext_link;
1014
1015 mutex_lock(&h2link->eml_lock);
1016
1017 hdaml_lctl_offload_enable(lctl: hlink->ml_addr + AZX_REG_ML_LCTL, enable);
1018
1019 mutex_unlock(lock: &h2link->eml_lock);
1020
1021 return 0;
1022}
1023EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, "SND_SOC_SOF_HDA_MLINK");
1024
1025void hdac_bus_eml_set_mic_privacy_mask(struct hdac_bus *bus, bool alt, int elid,
1026 unsigned long mask)
1027{
1028 struct hdac_ext2_link *h2link;
1029
1030 if (!mask)
1031 return;
1032
1033 h2link = find_ext2_link(bus, alt, elid);
1034 if (!h2link)
1035 return;
1036
1037 if (__fls(word: mask) > h2link->slcount) {
1038 dev_warn(bus->dev,
1039 "%s: invalid sublink mask for %d:%d, slcount %d: %#lx\n",
1040 __func__, alt, elid, h2link->slcount, mask);
1041 return;
1042 }
1043
1044 dev_dbg(bus->dev, "sublink mask for %d:%d, slcount %d: %#lx\n", alt,
1045 elid, h2link->slcount, mask);
1046
1047 h2link->mic_privacy_mask = mask;
1048}
1049EXPORT_SYMBOL_NS(hdac_bus_eml_set_mic_privacy_mask, "SND_SOC_SOF_HDA_MLINK");
1050
1051bool hdac_bus_eml_is_mic_privacy_changed(struct hdac_bus *bus, bool alt, int elid)
1052{
1053 struct hdac_ext2_link *h2link;
1054 bool changed = false;
1055 u16 __iomem *pvccs;
1056 int i;
1057
1058 h2link = find_ext2_link(bus, alt, elid);
1059 if (!h2link)
1060 return false;
1061
1062 /* The change in privacy state needs to be acked for each link */
1063 for_each_set_bit(i, &h2link->mic_privacy_mask, h2link->slcount) {
1064 u16 val;
1065
1066 if (h2link->sublink_ref_count[i] == 0)
1067 continue;
1068
1069 pvccs = h2link->base_ptr +
1070 h2link->shim_vs_offset +
1071 i * h2link->instance_offset +
1072 AZX_REG_INTEL_VS_SHIM_PVCCS;
1073
1074 val = readw(addr: pvccs);
1075 if (val & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHG) {
1076 writew(val, addr: pvccs);
1077 changed = true;
1078 }
1079 }
1080
1081 return changed;
1082}
1083EXPORT_SYMBOL_NS(hdac_bus_eml_is_mic_privacy_changed, "SND_SOC_SOF_HDA_MLINK");
1084
1085bool hdac_bus_eml_get_mic_privacy_state(struct hdac_bus *bus, bool alt, int elid)
1086{
1087 struct hdac_ext2_link *h2link;
1088 u16 __iomem *pvccs;
1089 bool state;
1090 int i;
1091
1092 h2link = find_ext2_link(bus, alt, elid);
1093 if (!h2link)
1094 return false;
1095
1096 for_each_set_bit(i, &h2link->mic_privacy_mask, h2link->slcount) {
1097 if (h2link->sublink_ref_count[i] == 0)
1098 continue;
1099
1100 /* Return the privacy state from the first active link */
1101 pvccs = h2link->base_ptr +
1102 h2link->shim_vs_offset +
1103 i * h2link->instance_offset +
1104 AZX_REG_INTEL_VS_SHIM_PVCCS;
1105
1106 state = readw(addr: pvccs) & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS;
1107 dev_dbg(bus->dev, "alt: %d, elid: %d: Mic privacy is %s\n", alt,
1108 elid, str_enabled_disabled(state));
1109
1110 return state;
1111 }
1112
1113 return false;
1114}
1115EXPORT_SYMBOL_NS(hdac_bus_eml_get_mic_privacy_state, "SND_SOC_SOF_HDA_MLINK");
1116
1117#endif
1118
1119MODULE_LICENSE("Dual BSD/GPL");
1120MODULE_DESCRIPTION("SOF support for HDaudio multi-link");
1121

source code of linux/sound/soc/sof/intel/hda-mlink.c