xref: /linux/sound/soc/sof/intel/hda-mlink.c (revision f3caa0b02455409eec4673ddd8df72d8bcad4e98)
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  */
48 struct 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 
110 static 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(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(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(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 
202 static 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(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 
227 static 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(lctl);
233 	val |= mask;
234 
235 	writel(val, lctl);
236 
237 	return check_sublink_power(lctl, sublink, true);
238 }
239 
240 static int hdaml_link_shutdown(u32 __iomem *lctl, int sublink)
241 {
242 	u32 val;
243 	u32 mask;
244 
245 	val = readl(lctl);
246 	mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT;
247 	val &= ~mask;
248 
249 	writel(val, lctl);
250 
251 	return check_sublink_power(lctl, sublink, false);
252 }
253 
254 static void hdaml_link_enable_interrupt(u32 __iomem *lctl, bool enable)
255 {
256 	u32 val;
257 
258 	val = readl(lctl);
259 	if (enable)
260 		val |= AZX_ML_LCTL_INTEN;
261 	else
262 		val &= ~AZX_ML_LCTL_INTEN;
263 
264 	writel(val, lctl);
265 }
266 
267 static bool hdaml_link_check_interrupt(u32 __iomem *lctl)
268 {
269 	u32 val;
270 
271 	val = readl(lctl);
272 
273 	return val & AZX_ML_LCTL_INTSTS;
274 }
275 
276 static 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(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 
294 static void hdaml_link_set_syncprd(u32 __iomem *lsync, u32 syncprd)
295 {
296 	u32 val;
297 
298 	val = readl(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, lsync);
309 }
310 
311 static int hdaml_link_wait_syncpu(u32 __iomem *lsync)
312 {
313 	return hdaml_wait_bit(lsync, 0, AZX_REG_ML_LSYNC_SYNCPU, 0);
314 }
315 
316 static void hdaml_link_sync_arm(u32 __iomem *lsync, int sublink)
317 {
318 	u32 val;
319 
320 	val = readl(lsync);
321 	val |= (AZX_REG_ML_LSYNC_CMDSYNC << sublink);
322 
323 	writel(val, lsync);
324 }
325 
326 static void hdaml_link_sync_go(u32 __iomem *lsync)
327 {
328 	u32 val;
329 
330 	val = readl(lsync);
331 	val |= AZX_REG_ML_LSYNC_SYNCGO;
332 
333 	writel(val, lsync);
334 }
335 
336 static bool hdaml_link_check_cmdsync(u32 __iomem *lsync, u32 cmdsync_mask)
337 {
338 	u32 val;
339 
340 	val = readl(lsync);
341 
342 	return !!(val & cmdsync_mask);
343 }
344 
345 static u16 hdaml_link_get_lsdiid(u16 __iomem *lsdiid)
346 {
347 	return readw(lsdiid);
348 }
349 
350 static void hdaml_link_set_lsdiid(u16 __iomem *lsdiid, int dev_num)
351 {
352 	u16 val;
353 
354 	val = readw(lsdiid);
355 	val |= BIT(dev_num);
356 
357 	writew(val, lsdiid);
358 }
359 
360 static 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(pcmsycm);
366 
367 	u16p_replace_bits(&val, lchan, GENMASK(3, 0));
368 	u16p_replace_bits(&val, hchan, GENMASK(7, 4));
369 	u16p_replace_bits(&val, stream_id, GENMASK(13, 8));
370 	u16p_replace_bits(&val, dir, BIT(15));
371 
372 	writew(val, pcmsycm);
373 }
374 
375 static void hdaml_lctl_offload_enable(u32 __iomem *lctl, bool enable)
376 {
377 	u32 val = readl(lctl);
378 
379 	if (enable)
380 		val |=  AZX_ML_LCTL_OFLEN;
381 	else
382 		val &=  ~AZX_ML_LCTL_OFLEN;
383 
384 	writel(val, lctl);
385 }
386 
387 /* END HDAML section */
388 
389 static 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_obj(*h2link);
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(bus->dev, h2link, bus->remap_addr, hlink->ml_addr, index);
407 	if (ret < 0) {
408 		kfree(h2link);
409 		return ret;
410 	}
411 
412 	mutex_init(&h2link->eml_lock);
413 
414 	list_add_tail(&hlink->list, &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 
426 int 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 	/* Enumeration is a one time operation, skip if already done */
436 	if (!list_empty(&bus->hlink_list))
437 		return 0;
438 
439 	link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1;
440 
441 	dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count);
442 
443 	for (i = 0; i < link_count; i++) {
444 		ret = hda_ml_alloc_h2link(bus, i);
445 		if (ret < 0) {
446 			hda_bus_ml_free(bus);
447 			return ret;
448 		}
449 	}
450 	return 0;
451 }
452 EXPORT_SYMBOL_NS(hda_bus_ml_init, "SND_SOC_SOF_HDA_MLINK");
453 
454 void hda_bus_ml_free(struct hdac_bus *bus)
455 {
456 	struct hdac_ext_link *hlink, *_h;
457 	struct hdac_ext2_link *h2link;
458 
459 	if (!bus->mlcap)
460 		return;
461 
462 	list_for_each_entry_safe(hlink, _h, &bus->hlink_list, list) {
463 		list_del(&hlink->list);
464 		h2link = hdac_ext_link_to_ext2(hlink);
465 
466 		mutex_destroy(&h2link->eml_lock);
467 		kfree(h2link);
468 	}
469 }
470 EXPORT_SYMBOL_NS(hda_bus_ml_free, "SND_SOC_SOF_HDA_MLINK");
471 
472 static struct hdac_ext2_link *
473 find_ext2_link(struct hdac_bus *bus, bool alt, int elid)
474 {
475 	struct hdac_ext_link *hlink;
476 
477 	list_for_each_entry(hlink, &bus->hlink_list, list) {
478 		struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
479 
480 		if (h2link->alt == alt && h2link->elid == elid)
481 			return h2link;
482 	}
483 
484 	return NULL;
485 }
486 
487 int hdac_bus_eml_get_count(struct hdac_bus *bus, bool alt, int elid)
488 {
489 	struct hdac_ext2_link *h2link;
490 
491 	h2link = find_ext2_link(bus, alt, elid);
492 	if (!h2link)
493 		return 0;
494 
495 	return h2link->slcount;
496 }
497 EXPORT_SYMBOL_NS(hdac_bus_eml_get_count, "SND_SOC_SOF_HDA_MLINK");
498 
499 void hdac_bus_eml_enable_interrupt_unlocked(struct hdac_bus *bus, bool alt, int elid, bool enable)
500 {
501 	struct hdac_ext2_link *h2link;
502 	struct hdac_ext_link *hlink;
503 
504 	h2link = find_ext2_link(bus, alt, elid);
505 	if (!h2link)
506 		return;
507 
508 	if (!h2link->intc)
509 		return;
510 
511 	hlink = &h2link->hext_link;
512 
513 	hdaml_link_enable_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
514 }
515 EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt_unlocked, "SND_SOC_SOF_HDA_MLINK");
516 
517 void hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable)
518 {
519 	struct hdac_ext2_link *h2link;
520 	struct hdac_ext_link *hlink;
521 
522 	h2link = find_ext2_link(bus, alt, elid);
523 	if (!h2link)
524 		return;
525 
526 	if (!h2link->intc)
527 		return;
528 
529 	hlink = &h2link->hext_link;
530 
531 	scoped_guard(mutex, &h2link->eml_lock)
532 		hdaml_link_enable_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
533 }
534 EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt, "SND_SOC_SOF_HDA_MLINK");
535 
536 bool hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid)
537 {
538 	struct hdac_ext2_link *h2link;
539 	struct hdac_ext_link *hlink;
540 
541 	h2link = find_ext2_link(bus, alt, elid);
542 	if (!h2link)
543 		return false;
544 
545 	if (!h2link->intc)
546 		return false;
547 
548 	hlink = &h2link->hext_link;
549 
550 	return hdaml_link_check_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL);
551 }
552 EXPORT_SYMBOL_NS(hdac_bus_eml_check_interrupt, "SND_SOC_SOF_HDA_MLINK");
553 
554 int hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd)
555 {
556 	struct hdac_ext2_link *h2link;
557 	struct hdac_ext_link *hlink;
558 
559 	h2link = find_ext2_link(bus, alt, elid);
560 	if (!h2link)
561 		return 0;
562 
563 	if (!h2link->lss)
564 		return 0;
565 
566 	hlink = &h2link->hext_link;
567 
568 	hdaml_link_set_syncprd(hlink->ml_addr + AZX_REG_ML_LSYNC, syncprd);
569 
570 	return 0;
571 }
572 EXPORT_SYMBOL_NS(hdac_bus_eml_set_syncprd_unlocked, "SND_SOC_SOF_HDA_MLINK");
573 
574 int hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd)
575 {
576 	return hdac_bus_eml_set_syncprd_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, syncprd);
577 }
578 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_syncprd_unlocked, "SND_SOC_SOF_HDA_MLINK");
579 
580 int hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid)
581 {
582 	struct hdac_ext2_link *h2link;
583 	struct hdac_ext_link *hlink;
584 
585 	h2link = find_ext2_link(bus, alt, elid);
586 	if (!h2link)
587 		return 0;
588 
589 	if (!h2link->lss)
590 		return 0;
591 
592 	hlink = &h2link->hext_link;
593 
594 	return hdaml_link_wait_syncpu(hlink->ml_addr + AZX_REG_ML_LSYNC);
595 }
596 EXPORT_SYMBOL_NS(hdac_bus_eml_wait_syncpu_unlocked, "SND_SOC_SOF_HDA_MLINK");
597 
598 int hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus)
599 {
600 	return hdac_bus_eml_wait_syncpu_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
601 }
602 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_wait_syncpu_unlocked, "SND_SOC_SOF_HDA_MLINK");
603 
604 void hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
605 {
606 	struct hdac_ext2_link *h2link;
607 	struct hdac_ext_link *hlink;
608 
609 	h2link = find_ext2_link(bus, alt, elid);
610 	if (!h2link)
611 		return;
612 
613 	if (!h2link->lss)
614 		return;
615 
616 	hlink = &h2link->hext_link;
617 
618 	hdaml_link_sync_arm(hlink->ml_addr + AZX_REG_ML_LSYNC, sublink);
619 }
620 EXPORT_SYMBOL_NS(hdac_bus_eml_sync_arm_unlocked, "SND_SOC_SOF_HDA_MLINK");
621 
622 void hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink)
623 {
624 	hdac_bus_eml_sync_arm_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
625 }
626 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_arm_unlocked, "SND_SOC_SOF_HDA_MLINK");
627 
628 int hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid)
629 {
630 	struct hdac_ext2_link *h2link;
631 	struct hdac_ext_link *hlink;
632 
633 	h2link = find_ext2_link(bus, alt, elid);
634 	if (!h2link)
635 		return 0;
636 
637 	if (!h2link->lss)
638 		return 0;
639 
640 	hlink = &h2link->hext_link;
641 
642 	hdaml_link_sync_go(hlink->ml_addr + AZX_REG_ML_LSYNC);
643 
644 	return 0;
645 }
646 EXPORT_SYMBOL_NS(hdac_bus_eml_sync_go_unlocked, "SND_SOC_SOF_HDA_MLINK");
647 
648 int hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus)
649 {
650 	return hdac_bus_eml_sync_go_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
651 }
652 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_go_unlocked, "SND_SOC_SOF_HDA_MLINK");
653 
654 bool hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid)
655 {
656 	struct hdac_ext2_link *h2link;
657 	struct hdac_ext_link *hlink;
658 	u32 cmdsync_mask;
659 
660 	h2link = find_ext2_link(bus, alt, elid);
661 	if (!h2link)
662 		return 0;
663 
664 	if (!h2link->lss)
665 		return 0;
666 
667 	hlink = &h2link->hext_link;
668 
669 	cmdsync_mask = GENMASK(AZX_REG_ML_LSYNC_CMDSYNC_SHIFT + h2link->slcount - 1,
670 			       AZX_REG_ML_LSYNC_CMDSYNC_SHIFT);
671 
672 	return hdaml_link_check_cmdsync(hlink->ml_addr + AZX_REG_ML_LSYNC,
673 					cmdsync_mask);
674 }
675 EXPORT_SYMBOL_NS(hdac_bus_eml_check_cmdsync_unlocked, "SND_SOC_SOF_HDA_MLINK");
676 
677 bool hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus)
678 {
679 	return hdac_bus_eml_check_cmdsync_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
680 }
681 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_check_cmdsync_unlocked, "SND_SOC_SOF_HDA_MLINK");
682 
683 static int hdac_bus_eml_power_up_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
684 				      bool eml_lock)
685 {
686 	struct hdac_ext2_link *h2link;
687 	struct hdac_ext_link *hlink;
688 	int ret = 0;
689 
690 	h2link = find_ext2_link(bus, alt, elid);
691 	if (!h2link)
692 		return -ENODEV;
693 
694 	if (sublink >= h2link->slcount)
695 		return -EINVAL;
696 
697 	hlink = &h2link->hext_link;
698 
699 	if (eml_lock)
700 		mutex_lock(&h2link->eml_lock);
701 
702 	if (!alt) {
703 		if (++hlink->ref_count > 1)
704 			goto skip_init;
705 	} else {
706 		if (++h2link->sublink_ref_count[sublink] > 1)
707 			goto skip_init;
708 	}
709 
710 	ret = hdaml_link_init(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
711 	if ((h2link->mic_privacy_mask & BIT(sublink)) && !ret) {
712 		u16 __iomem *pvccs = h2link->base_ptr +
713 				     h2link->shim_vs_offset +
714 				     sublink * h2link->instance_offset +
715 				     AZX_REG_INTEL_VS_SHIM_PVCCS;
716 		u16 val = readw(pvccs);
717 
718 		writew(val | AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE, pvccs);
719 
720 		if (val & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS)
721 			dev_dbg(bus->dev,
722 				"sublink %d (%d:%d): Mic privacy is enabled\n",
723 				sublink, alt, elid);
724 	}
725 
726 skip_init:
727 	if (eml_lock)
728 		mutex_unlock(&h2link->eml_lock);
729 
730 	return ret;
731 }
732 
733 int hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink)
734 {
735 	return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, true);
736 }
737 EXPORT_SYMBOL_NS(hdac_bus_eml_power_up, "SND_SOC_SOF_HDA_MLINK");
738 
739 int hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
740 {
741 	return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, false);
742 }
743 EXPORT_SYMBOL_NS(hdac_bus_eml_power_up_unlocked, "SND_SOC_SOF_HDA_MLINK");
744 
745 static int hdac_bus_eml_power_down_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
746 					bool eml_lock)
747 {
748 	struct hdac_ext2_link *h2link;
749 	struct hdac_ext_link *hlink;
750 	int ret = 0;
751 
752 	h2link = find_ext2_link(bus, alt, elid);
753 	if (!h2link)
754 		return -ENODEV;
755 
756 	if (sublink >= h2link->slcount)
757 		return -EINVAL;
758 
759 	hlink = &h2link->hext_link;
760 
761 	if (eml_lock)
762 		mutex_lock(&h2link->eml_lock);
763 
764 	if (!alt) {
765 		if (--hlink->ref_count > 0)
766 			goto skip_shutdown;
767 	} else {
768 		if (--h2link->sublink_ref_count[sublink] > 0)
769 			goto skip_shutdown;
770 	}
771 
772 	if (h2link->mic_privacy_mask & BIT(sublink)) {
773 		u16 __iomem *pvccs = h2link->base_ptr +
774 				     h2link->shim_vs_offset +
775 				     sublink * h2link->instance_offset +
776 				     AZX_REG_INTEL_VS_SHIM_PVCCS;
777 
778 		writew(readw(pvccs) & ~AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE, pvccs);
779 	}
780 
781 	ret = hdaml_link_shutdown(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
782 
783 skip_shutdown:
784 	if (eml_lock)
785 		mutex_unlock(&h2link->eml_lock);
786 
787 	return ret;
788 }
789 
790 int hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink)
791 {
792 	return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, true);
793 }
794 EXPORT_SYMBOL_NS(hdac_bus_eml_power_down, "SND_SOC_SOF_HDA_MLINK");
795 
796 int hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
797 {
798 	return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, false);
799 }
800 EXPORT_SYMBOL_NS(hdac_bus_eml_power_down_unlocked, "SND_SOC_SOF_HDA_MLINK");
801 
802 int hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink)
803 {
804 	return hdac_bus_eml_power_up_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
805 }
806 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_up_unlocked, "SND_SOC_SOF_HDA_MLINK");
807 
808 int hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink)
809 {
810 	return hdac_bus_eml_power_down_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
811 }
812 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_down_unlocked, "SND_SOC_SOF_HDA_MLINK");
813 
814 int hdac_bus_eml_sdw_get_lsdiid_unlocked(struct hdac_bus *bus, int sublink, u16 *lsdiid)
815 {
816 	struct hdac_ext2_link *h2link;
817 	struct hdac_ext_link *hlink;
818 
819 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
820 	if (!h2link)
821 		return -ENODEV;
822 
823 	hlink = &h2link->hext_link;
824 
825 	*lsdiid = hdaml_link_get_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink));
826 
827 	return 0;
828 } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_lsdiid_unlocked, "SND_SOC_SOF_HDA_MLINK");
829 
830 int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num)
831 {
832 	struct hdac_ext2_link *h2link;
833 	struct hdac_ext_link *hlink;
834 
835 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
836 	if (!h2link)
837 		return -ENODEV;
838 
839 	hlink = &h2link->hext_link;
840 
841 	scoped_guard(mutex, &h2link->eml_lock)
842 		hdaml_link_set_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink), dev_num);
843 
844 	return 0;
845 } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_lsdiid, "SND_SOC_SOF_HDA_MLINK");
846 
847 /*
848  * the 'y' parameter comes from the PCMSyCM hardware register naming. 'y' refers to the
849  * PDI index, i.e. the FIFO used for RX or TX
850  */
851 int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y,
852 				   int channel_mask, int stream_id, int dir)
853 {
854 	struct hdac_ext2_link *h2link;
855 	u16 __iomem *pcmsycm;
856 	int hchan;
857 	int lchan;
858 	u16 val;
859 
860 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
861 	if (!h2link)
862 		return -ENODEV;
863 
864 	pcmsycm = h2link->base_ptr + h2link->shim_offset +
865 		h2link->instance_offset * sublink +
866 		AZX_REG_SDW_SHIM_PCMSyCM(y);
867 
868 	if (channel_mask) {
869 		hchan = __fls(channel_mask);
870 		lchan = __ffs(channel_mask);
871 	} else {
872 		hchan = 0;
873 		lchan = 0;
874 	}
875 
876 	scoped_guard(mutex, &h2link->eml_lock)
877 		hdaml_shim_map_stream_ch(pcmsycm, lchan, hchan, stream_id, dir);
878 
879 	val = readw(pcmsycm);
880 
881 	dev_dbg(bus->dev, "sublink %d channel_mask %#x stream_id %d dir %d pcmscm %#x\n",
882 		sublink, channel_mask, stream_id, dir, val);
883 
884 	return 0;
885 } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_map_stream_ch, "SND_SOC_SOF_HDA_MLINK");
886 
887 void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
888 {
889 	struct hdac_ext_link *hlink;
890 
891 	/* Reset stream-to-link mapping */
892 	list_for_each_entry(hlink, &bus->hlink_list, list)
893 		writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV);
894 }
895 EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK");
896 
897 enum hda_bus_ml_link_type hda_bus_ml_link_get_type(struct hdac_ext_link *hlink)
898 {
899 	struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
900 
901 	if (!h2link->alt)
902 		return HDA_BUS_ML_LINK_HDA;
903 
904 	switch (h2link->elid) {
905 	case AZX_REG_ML_LEPTR_ID_SDW:
906 		return HDA_BUS_ML_LINK_SDW;
907 	case AZX_REG_ML_LEPTR_ID_INTEL_UAOL:
908 		return HDA_BUS_ML_LINK_UAOL;
909 	default:
910 		return HDA_BUS_ML_LINK_OTHER;
911 	}
912 }
913 EXPORT_SYMBOL_NS(hda_bus_ml_link_get_type, "SND_SOC_SOF_HDA_MLINK");
914 
915 int hda_bus_ml_resume(struct hdac_bus *bus)
916 {
917 	struct hdac_ext_link *hlink;
918 	int ret;
919 
920 	/* power up links that were active before suspend */
921 	list_for_each_entry(hlink, &bus->hlink_list, list) {
922 		struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
923 
924 		if (!h2link->alt && hlink->ref_count) {
925 			ret = snd_hdac_ext_bus_link_power_up(hlink);
926 			if (ret < 0)
927 				return ret;
928 		}
929 	}
930 	return 0;
931 }
932 EXPORT_SYMBOL_NS(hda_bus_ml_resume, "SND_SOC_SOF_HDA_MLINK");
933 
934 int hda_bus_ml_suspend(struct hdac_bus *bus)
935 {
936 	struct hdac_ext_link *hlink;
937 	int ret;
938 
939 	list_for_each_entry(hlink, &bus->hlink_list, list) {
940 		struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
941 
942 		if (!h2link->alt) {
943 			ret = snd_hdac_ext_bus_link_power_down(hlink);
944 			if (ret < 0)
945 				return ret;
946 		}
947 	}
948 	return 0;
949 }
950 EXPORT_SYMBOL_NS(hda_bus_ml_suspend, "SND_SOC_SOF_HDA_MLINK");
951 
952 struct mutex *hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid)
953 {
954 	struct hdac_ext2_link *h2link;
955 
956 	h2link = find_ext2_link(bus, alt, elid);
957 	if (!h2link)
958 		return NULL;
959 
960 	return &h2link->eml_lock;
961 }
962 EXPORT_SYMBOL_NS(hdac_bus_eml_get_mutex, "SND_SOC_SOF_HDA_MLINK");
963 
964 struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus)
965 {
966 	struct hdac_ext2_link *h2link;
967 
968 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_SSP);
969 	if (!h2link)
970 		return NULL;
971 
972 	return &h2link->hext_link;
973 }
974 EXPORT_SYMBOL_NS(hdac_bus_eml_ssp_get_hlink, "SND_SOC_SOF_HDA_MLINK");
975 
976 struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus)
977 {
978 	struct hdac_ext2_link *h2link;
979 
980 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_DMIC);
981 	if (!h2link)
982 		return NULL;
983 
984 	return &h2link->hext_link;
985 }
986 EXPORT_SYMBOL_NS(hdac_bus_eml_dmic_get_hlink, "SND_SOC_SOF_HDA_MLINK");
987 
988 struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus)
989 {
990 	struct hdac_ext2_link *h2link;
991 
992 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
993 	if (!h2link)
994 		return NULL;
995 
996 	return &h2link->hext_link;
997 }
998 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_hlink, "SND_SOC_SOF_HDA_MLINK");
999 
1000 void hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable)
1001 {
1002 	struct hdac_ext2_link *h2link;
1003 	struct hdac_ext_link *hlink;
1004 
1005 	h2link = find_ext2_link(bus, alt, elid);
1006 	if (!h2link || !h2link->ofls)
1007 		return;
1008 
1009 	hlink = &h2link->hext_link;
1010 
1011 	scoped_guard(mutex, &h2link->eml_lock)
1012 		hdaml_lctl_offload_enable(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
1013 }
1014 EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, "SND_SOC_SOF_HDA_MLINK");
1015 
1016 void hdac_bus_eml_set_mic_privacy_mask(struct hdac_bus *bus, bool alt, int elid,
1017 				       unsigned long mask)
1018 {
1019 	struct hdac_ext2_link *h2link;
1020 
1021 	if (!mask)
1022 		return;
1023 
1024 	h2link = find_ext2_link(bus, alt, elid);
1025 	if (!h2link)
1026 		return;
1027 
1028 	if (__fls(mask) > h2link->slcount) {
1029 		dev_warn(bus->dev,
1030 			 "%s: invalid sublink mask for %d:%d, slcount %d: %#lx\n",
1031 			 __func__, alt, elid, h2link->slcount, mask);
1032 		return;
1033 	}
1034 
1035 	dev_dbg(bus->dev, "sublink mask for %d:%d, slcount %d: %#lx\n", alt,
1036 		elid, h2link->slcount, mask);
1037 
1038 	h2link->mic_privacy_mask = mask;
1039 }
1040 EXPORT_SYMBOL_NS(hdac_bus_eml_set_mic_privacy_mask, "SND_SOC_SOF_HDA_MLINK");
1041 
1042 bool hdac_bus_eml_is_mic_privacy_changed(struct hdac_bus *bus, bool alt, int elid)
1043 {
1044 	struct hdac_ext2_link *h2link;
1045 	bool changed = false;
1046 	u16 __iomem *pvccs;
1047 	int i;
1048 
1049 	h2link = find_ext2_link(bus, alt, elid);
1050 	if (!h2link)
1051 		return false;
1052 
1053 	/* The change in privacy state needs to be acked for each link */
1054 	for_each_set_bit(i, &h2link->mic_privacy_mask, h2link->slcount) {
1055 		u16 val;
1056 
1057 		if (h2link->sublink_ref_count[i] == 0)
1058 			continue;
1059 
1060 		pvccs = h2link->base_ptr +
1061 			h2link->shim_vs_offset +
1062 			i * h2link->instance_offset +
1063 			AZX_REG_INTEL_VS_SHIM_PVCCS;
1064 
1065 		val = readw(pvccs);
1066 		if (val & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHG) {
1067 			writew(val, pvccs);
1068 			changed = true;
1069 		}
1070 	}
1071 
1072 	return changed;
1073 }
1074 EXPORT_SYMBOL_NS(hdac_bus_eml_is_mic_privacy_changed, "SND_SOC_SOF_HDA_MLINK");
1075 
1076 bool hdac_bus_eml_get_mic_privacy_state(struct hdac_bus *bus, bool alt, int elid)
1077 {
1078 	struct hdac_ext2_link *h2link;
1079 	u16 __iomem *pvccs;
1080 	bool state;
1081 	int i;
1082 
1083 	h2link = find_ext2_link(bus, alt, elid);
1084 	if (!h2link)
1085 		return false;
1086 
1087 	for_each_set_bit(i, &h2link->mic_privacy_mask, h2link->slcount) {
1088 		if (h2link->sublink_ref_count[i] == 0)
1089 			continue;
1090 
1091 		/* Return the privacy state from the first active link */
1092 		pvccs = h2link->base_ptr +
1093 			h2link->shim_vs_offset +
1094 			i * h2link->instance_offset +
1095 			AZX_REG_INTEL_VS_SHIM_PVCCS;
1096 
1097 		state = readw(pvccs) & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS;
1098 		dev_dbg(bus->dev, "alt: %d, elid: %d: Mic privacy is %s\n", alt,
1099 			elid, str_enabled_disabled(state));
1100 
1101 		return state;
1102 	}
1103 
1104 	return false;
1105 }
1106 EXPORT_SYMBOL_NS(hdac_bus_eml_get_mic_privacy_state, "SND_SOC_SOF_HDA_MLINK");
1107 
1108 #endif
1109 
1110 MODULE_LICENSE("Dual BSD/GPL");
1111 MODULE_DESCRIPTION("SOF support for HDaudio multi-link");
1112