xref: /linux/sound/soc/sof/intel/hda-mlink.c (revision af0bc3ac9a9e830cb52b718ecb237c4e76a466be)
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(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(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 	link_count = readl(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, i);
441 		if (ret < 0) {
442 			hda_bus_ml_free(bus);
443 			return ret;
444 		}
445 	}
446 	return 0;
447 }
448 EXPORT_SYMBOL_NS(hda_bus_ml_init, "SND_SOC_SOF_HDA_MLINK");
449 
450 void 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(&hlink->list);
460 		h2link = hdac_ext_link_to_ext2(hlink);
461 
462 		mutex_destroy(&h2link->eml_lock);
463 		kfree(h2link);
464 	}
465 }
466 EXPORT_SYMBOL_NS(hda_bus_ml_free, "SND_SOC_SOF_HDA_MLINK");
467 
468 static struct hdac_ext2_link *
469 find_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 
483 int 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 }
493 EXPORT_SYMBOL_NS(hdac_bus_eml_get_count, "SND_SOC_SOF_HDA_MLINK");
494 
495 void 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(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
510 }
511 EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt_unlocked, "SND_SOC_SOF_HDA_MLINK");
512 
513 void 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 	scoped_guard(mutex, &h2link->eml_lock)
528 		hdaml_link_enable_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
529 }
530 EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt, "SND_SOC_SOF_HDA_MLINK");
531 
532 bool hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid)
533 {
534 	struct hdac_ext2_link *h2link;
535 	struct hdac_ext_link *hlink;
536 
537 	h2link = find_ext2_link(bus, alt, elid);
538 	if (!h2link)
539 		return false;
540 
541 	if (!h2link->intc)
542 		return false;
543 
544 	hlink = &h2link->hext_link;
545 
546 	return hdaml_link_check_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL);
547 }
548 EXPORT_SYMBOL_NS(hdac_bus_eml_check_interrupt, "SND_SOC_SOF_HDA_MLINK");
549 
550 int hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd)
551 {
552 	struct hdac_ext2_link *h2link;
553 	struct hdac_ext_link *hlink;
554 
555 	h2link = find_ext2_link(bus, alt, elid);
556 	if (!h2link)
557 		return 0;
558 
559 	if (!h2link->lss)
560 		return 0;
561 
562 	hlink = &h2link->hext_link;
563 
564 	hdaml_link_set_syncprd(hlink->ml_addr + AZX_REG_ML_LSYNC, syncprd);
565 
566 	return 0;
567 }
568 EXPORT_SYMBOL_NS(hdac_bus_eml_set_syncprd_unlocked, "SND_SOC_SOF_HDA_MLINK");
569 
570 int hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd)
571 {
572 	return hdac_bus_eml_set_syncprd_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, syncprd);
573 }
574 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_syncprd_unlocked, "SND_SOC_SOF_HDA_MLINK");
575 
576 int hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid)
577 {
578 	struct hdac_ext2_link *h2link;
579 	struct hdac_ext_link *hlink;
580 
581 	h2link = find_ext2_link(bus, alt, elid);
582 	if (!h2link)
583 		return 0;
584 
585 	if (!h2link->lss)
586 		return 0;
587 
588 	hlink = &h2link->hext_link;
589 
590 	return hdaml_link_wait_syncpu(hlink->ml_addr + AZX_REG_ML_LSYNC);
591 }
592 EXPORT_SYMBOL_NS(hdac_bus_eml_wait_syncpu_unlocked, "SND_SOC_SOF_HDA_MLINK");
593 
594 int hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus)
595 {
596 	return hdac_bus_eml_wait_syncpu_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
597 }
598 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_wait_syncpu_unlocked, "SND_SOC_SOF_HDA_MLINK");
599 
600 void hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
601 {
602 	struct hdac_ext2_link *h2link;
603 	struct hdac_ext_link *hlink;
604 
605 	h2link = find_ext2_link(bus, alt, elid);
606 	if (!h2link)
607 		return;
608 
609 	if (!h2link->lss)
610 		return;
611 
612 	hlink = &h2link->hext_link;
613 
614 	hdaml_link_sync_arm(hlink->ml_addr + AZX_REG_ML_LSYNC, sublink);
615 }
616 EXPORT_SYMBOL_NS(hdac_bus_eml_sync_arm_unlocked, "SND_SOC_SOF_HDA_MLINK");
617 
618 void hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink)
619 {
620 	hdac_bus_eml_sync_arm_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
621 }
622 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_arm_unlocked, "SND_SOC_SOF_HDA_MLINK");
623 
624 int hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid)
625 {
626 	struct hdac_ext2_link *h2link;
627 	struct hdac_ext_link *hlink;
628 
629 	h2link = find_ext2_link(bus, alt, elid);
630 	if (!h2link)
631 		return 0;
632 
633 	if (!h2link->lss)
634 		return 0;
635 
636 	hlink = &h2link->hext_link;
637 
638 	hdaml_link_sync_go(hlink->ml_addr + AZX_REG_ML_LSYNC);
639 
640 	return 0;
641 }
642 EXPORT_SYMBOL_NS(hdac_bus_eml_sync_go_unlocked, "SND_SOC_SOF_HDA_MLINK");
643 
644 int hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus)
645 {
646 	return hdac_bus_eml_sync_go_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
647 }
648 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_go_unlocked, "SND_SOC_SOF_HDA_MLINK");
649 
650 bool hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid)
651 {
652 	struct hdac_ext2_link *h2link;
653 	struct hdac_ext_link *hlink;
654 	u32 cmdsync_mask;
655 
656 	h2link = find_ext2_link(bus, alt, elid);
657 	if (!h2link)
658 		return 0;
659 
660 	if (!h2link->lss)
661 		return 0;
662 
663 	hlink = &h2link->hext_link;
664 
665 	cmdsync_mask = GENMASK(AZX_REG_ML_LSYNC_CMDSYNC_SHIFT + h2link->slcount - 1,
666 			       AZX_REG_ML_LSYNC_CMDSYNC_SHIFT);
667 
668 	return hdaml_link_check_cmdsync(hlink->ml_addr + AZX_REG_ML_LSYNC,
669 					cmdsync_mask);
670 }
671 EXPORT_SYMBOL_NS(hdac_bus_eml_check_cmdsync_unlocked, "SND_SOC_SOF_HDA_MLINK");
672 
673 bool hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus)
674 {
675 	return hdac_bus_eml_check_cmdsync_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
676 }
677 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_check_cmdsync_unlocked, "SND_SOC_SOF_HDA_MLINK");
678 
679 static int hdac_bus_eml_power_up_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
680 				      bool eml_lock)
681 {
682 	struct hdac_ext2_link *h2link;
683 	struct hdac_ext_link *hlink;
684 	int ret = 0;
685 
686 	h2link = find_ext2_link(bus, alt, elid);
687 	if (!h2link)
688 		return -ENODEV;
689 
690 	if (sublink >= h2link->slcount)
691 		return -EINVAL;
692 
693 	hlink = &h2link->hext_link;
694 
695 	if (eml_lock)
696 		mutex_lock(&h2link->eml_lock);
697 
698 	if (!alt) {
699 		if (++hlink->ref_count > 1)
700 			goto skip_init;
701 	} else {
702 		if (++h2link->sublink_ref_count[sublink] > 1)
703 			goto skip_init;
704 	}
705 
706 	ret = hdaml_link_init(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
707 	if ((h2link->mic_privacy_mask & BIT(sublink)) && !ret) {
708 		u16 __iomem *pvccs = h2link->base_ptr +
709 				     h2link->shim_vs_offset +
710 				     sublink * h2link->instance_offset +
711 				     AZX_REG_INTEL_VS_SHIM_PVCCS;
712 		u16 val = readw(pvccs);
713 
714 		writew(val | AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE, pvccs);
715 
716 		if (val & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS)
717 			dev_dbg(bus->dev,
718 				"sublink %d (%d:%d): Mic privacy is enabled\n",
719 				sublink, alt, elid);
720 	}
721 
722 skip_init:
723 	if (eml_lock)
724 		mutex_unlock(&h2link->eml_lock);
725 
726 	return ret;
727 }
728 
729 int hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink)
730 {
731 	return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, true);
732 }
733 EXPORT_SYMBOL_NS(hdac_bus_eml_power_up, "SND_SOC_SOF_HDA_MLINK");
734 
735 int hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
736 {
737 	return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, false);
738 }
739 EXPORT_SYMBOL_NS(hdac_bus_eml_power_up_unlocked, "SND_SOC_SOF_HDA_MLINK");
740 
741 static int hdac_bus_eml_power_down_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
742 					bool eml_lock)
743 {
744 	struct hdac_ext2_link *h2link;
745 	struct hdac_ext_link *hlink;
746 	int ret = 0;
747 
748 	h2link = find_ext2_link(bus, alt, elid);
749 	if (!h2link)
750 		return -ENODEV;
751 
752 	if (sublink >= h2link->slcount)
753 		return -EINVAL;
754 
755 	hlink = &h2link->hext_link;
756 
757 	if (eml_lock)
758 		mutex_lock(&h2link->eml_lock);
759 
760 	if (!alt) {
761 		if (--hlink->ref_count > 0)
762 			goto skip_shutdown;
763 	} else {
764 		if (--h2link->sublink_ref_count[sublink] > 0)
765 			goto skip_shutdown;
766 	}
767 
768 	if (h2link->mic_privacy_mask & BIT(sublink)) {
769 		u16 __iomem *pvccs = h2link->base_ptr +
770 				     h2link->shim_vs_offset +
771 				     sublink * h2link->instance_offset +
772 				     AZX_REG_INTEL_VS_SHIM_PVCCS;
773 
774 		writew(readw(pvccs) & ~AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE, pvccs);
775 	}
776 
777 	ret = hdaml_link_shutdown(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
778 
779 skip_shutdown:
780 	if (eml_lock)
781 		mutex_unlock(&h2link->eml_lock);
782 
783 	return ret;
784 }
785 
786 int hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink)
787 {
788 	return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, true);
789 }
790 EXPORT_SYMBOL_NS(hdac_bus_eml_power_down, "SND_SOC_SOF_HDA_MLINK");
791 
792 int hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
793 {
794 	return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, false);
795 }
796 EXPORT_SYMBOL_NS(hdac_bus_eml_power_down_unlocked, "SND_SOC_SOF_HDA_MLINK");
797 
798 int hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink)
799 {
800 	return hdac_bus_eml_power_up_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
801 }
802 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_up_unlocked, "SND_SOC_SOF_HDA_MLINK");
803 
804 int hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink)
805 {
806 	return hdac_bus_eml_power_down_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
807 }
808 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_down_unlocked, "SND_SOC_SOF_HDA_MLINK");
809 
810 int hdac_bus_eml_sdw_get_lsdiid_unlocked(struct hdac_bus *bus, int sublink, u16 *lsdiid)
811 {
812 	struct hdac_ext2_link *h2link;
813 	struct hdac_ext_link *hlink;
814 
815 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
816 	if (!h2link)
817 		return -ENODEV;
818 
819 	hlink = &h2link->hext_link;
820 
821 	*lsdiid = hdaml_link_get_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink));
822 
823 	return 0;
824 } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_lsdiid_unlocked, "SND_SOC_SOF_HDA_MLINK");
825 
826 int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num)
827 {
828 	struct hdac_ext2_link *h2link;
829 	struct hdac_ext_link *hlink;
830 
831 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
832 	if (!h2link)
833 		return -ENODEV;
834 
835 	hlink = &h2link->hext_link;
836 
837 	scoped_guard(mutex, &h2link->eml_lock)
838 		hdaml_link_set_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink), dev_num);
839 
840 	return 0;
841 } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_lsdiid, "SND_SOC_SOF_HDA_MLINK");
842 
843 /*
844  * the 'y' parameter comes from the PCMSyCM hardware register naming. 'y' refers to the
845  * PDI index, i.e. the FIFO used for RX or TX
846  */
847 int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y,
848 				   int channel_mask, int stream_id, int dir)
849 {
850 	struct hdac_ext2_link *h2link;
851 	u16 __iomem *pcmsycm;
852 	int hchan;
853 	int lchan;
854 	u16 val;
855 
856 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
857 	if (!h2link)
858 		return -ENODEV;
859 
860 	pcmsycm = h2link->base_ptr + h2link->shim_offset +
861 		h2link->instance_offset * sublink +
862 		AZX_REG_SDW_SHIM_PCMSyCM(y);
863 
864 	if (channel_mask) {
865 		hchan = __fls(channel_mask);
866 		lchan = __ffs(channel_mask);
867 	} else {
868 		hchan = 0;
869 		lchan = 0;
870 	}
871 
872 	scoped_guard(mutex, &h2link->eml_lock)
873 		hdaml_shim_map_stream_ch(pcmsycm, lchan, hchan, stream_id, dir);
874 
875 	val = readw(pcmsycm);
876 
877 	dev_dbg(bus->dev, "sublink %d channel_mask %#x stream_id %d dir %d pcmscm %#x\n",
878 		sublink, channel_mask, stream_id, dir, val);
879 
880 	return 0;
881 } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_map_stream_ch, "SND_SOC_SOF_HDA_MLINK");
882 
883 void hda_bus_ml_put_all(struct hdac_bus *bus)
884 {
885 	struct hdac_ext_link *hlink;
886 
887 	list_for_each_entry(hlink, &bus->hlink_list, list) {
888 		struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
889 
890 		if (!h2link->alt)
891 			snd_hdac_ext_bus_link_put(bus, hlink);
892 	}
893 }
894 EXPORT_SYMBOL_NS(hda_bus_ml_put_all, "SND_SOC_SOF_HDA_MLINK");
895 
896 void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
897 {
898 	struct hdac_ext_link *hlink;
899 
900 	/* Reset stream-to-link mapping */
901 	list_for_each_entry(hlink, &bus->hlink_list, list)
902 		writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV);
903 }
904 EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK");
905 
906 int hda_bus_ml_resume(struct hdac_bus *bus)
907 {
908 	struct hdac_ext_link *hlink;
909 	int ret;
910 
911 	/* power up links that were active before suspend */
912 	list_for_each_entry(hlink, &bus->hlink_list, list) {
913 		struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
914 
915 		if (!h2link->alt && hlink->ref_count) {
916 			ret = snd_hdac_ext_bus_link_power_up(hlink);
917 			if (ret < 0)
918 				return ret;
919 		}
920 	}
921 	return 0;
922 }
923 EXPORT_SYMBOL_NS(hda_bus_ml_resume, "SND_SOC_SOF_HDA_MLINK");
924 
925 int hda_bus_ml_suspend(struct hdac_bus *bus)
926 {
927 	struct hdac_ext_link *hlink;
928 	int ret;
929 
930 	list_for_each_entry(hlink, &bus->hlink_list, list) {
931 		struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
932 
933 		if (!h2link->alt) {
934 			ret = snd_hdac_ext_bus_link_power_down(hlink);
935 			if (ret < 0)
936 				return ret;
937 		}
938 	}
939 	return 0;
940 }
941 EXPORT_SYMBOL_NS(hda_bus_ml_suspend, "SND_SOC_SOF_HDA_MLINK");
942 
943 struct mutex *hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid)
944 {
945 	struct hdac_ext2_link *h2link;
946 
947 	h2link = find_ext2_link(bus, alt, elid);
948 	if (!h2link)
949 		return NULL;
950 
951 	return &h2link->eml_lock;
952 }
953 EXPORT_SYMBOL_NS(hdac_bus_eml_get_mutex, "SND_SOC_SOF_HDA_MLINK");
954 
955 struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus)
956 {
957 	struct hdac_ext2_link *h2link;
958 
959 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_SSP);
960 	if (!h2link)
961 		return NULL;
962 
963 	return &h2link->hext_link;
964 }
965 EXPORT_SYMBOL_NS(hdac_bus_eml_ssp_get_hlink, "SND_SOC_SOF_HDA_MLINK");
966 
967 struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus)
968 {
969 	struct hdac_ext2_link *h2link;
970 
971 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_DMIC);
972 	if (!h2link)
973 		return NULL;
974 
975 	return &h2link->hext_link;
976 }
977 EXPORT_SYMBOL_NS(hdac_bus_eml_dmic_get_hlink, "SND_SOC_SOF_HDA_MLINK");
978 
979 struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus)
980 {
981 	struct hdac_ext2_link *h2link;
982 
983 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
984 	if (!h2link)
985 		return NULL;
986 
987 	return &h2link->hext_link;
988 }
989 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_hlink, "SND_SOC_SOF_HDA_MLINK");
990 
991 int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable)
992 {
993 	struct hdac_ext2_link *h2link;
994 	struct hdac_ext_link *hlink;
995 
996 	h2link = find_ext2_link(bus, alt, elid);
997 	if (!h2link)
998 		return -ENODEV;
999 
1000 	if (!h2link->ofls)
1001 		return 0;
1002 
1003 	hlink = &h2link->hext_link;
1004 
1005 	scoped_guard(mutex, &h2link->eml_lock)
1006 		hdaml_lctl_offload_enable(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
1007 
1008 	return 0;
1009 }
1010 EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, "SND_SOC_SOF_HDA_MLINK");
1011 
1012 void hdac_bus_eml_set_mic_privacy_mask(struct hdac_bus *bus, bool alt, int elid,
1013 				       unsigned long mask)
1014 {
1015 	struct hdac_ext2_link *h2link;
1016 
1017 	if (!mask)
1018 		return;
1019 
1020 	h2link = find_ext2_link(bus, alt, elid);
1021 	if (!h2link)
1022 		return;
1023 
1024 	if (__fls(mask) > h2link->slcount) {
1025 		dev_warn(bus->dev,
1026 			 "%s: invalid sublink mask for %d:%d, slcount %d: %#lx\n",
1027 			 __func__, alt, elid, h2link->slcount, mask);
1028 		return;
1029 	}
1030 
1031 	dev_dbg(bus->dev, "sublink mask for %d:%d, slcount %d: %#lx\n", alt,
1032 		elid, h2link->slcount, mask);
1033 
1034 	h2link->mic_privacy_mask = mask;
1035 }
1036 EXPORT_SYMBOL_NS(hdac_bus_eml_set_mic_privacy_mask, "SND_SOC_SOF_HDA_MLINK");
1037 
1038 bool hdac_bus_eml_is_mic_privacy_changed(struct hdac_bus *bus, bool alt, int elid)
1039 {
1040 	struct hdac_ext2_link *h2link;
1041 	bool changed = false;
1042 	u16 __iomem *pvccs;
1043 	int i;
1044 
1045 	h2link = find_ext2_link(bus, alt, elid);
1046 	if (!h2link)
1047 		return false;
1048 
1049 	/* The change in privacy state needs to be acked for each link */
1050 	for_each_set_bit(i, &h2link->mic_privacy_mask, h2link->slcount) {
1051 		u16 val;
1052 
1053 		if (h2link->sublink_ref_count[i] == 0)
1054 			continue;
1055 
1056 		pvccs = h2link->base_ptr +
1057 			h2link->shim_vs_offset +
1058 			i * h2link->instance_offset +
1059 			AZX_REG_INTEL_VS_SHIM_PVCCS;
1060 
1061 		val = readw(pvccs);
1062 		if (val & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHG) {
1063 			writew(val, pvccs);
1064 			changed = true;
1065 		}
1066 	}
1067 
1068 	return changed;
1069 }
1070 EXPORT_SYMBOL_NS(hdac_bus_eml_is_mic_privacy_changed, "SND_SOC_SOF_HDA_MLINK");
1071 
1072 bool hdac_bus_eml_get_mic_privacy_state(struct hdac_bus *bus, bool alt, int elid)
1073 {
1074 	struct hdac_ext2_link *h2link;
1075 	u16 __iomem *pvccs;
1076 	bool state;
1077 	int i;
1078 
1079 	h2link = find_ext2_link(bus, alt, elid);
1080 	if (!h2link)
1081 		return false;
1082 
1083 	for_each_set_bit(i, &h2link->mic_privacy_mask, h2link->slcount) {
1084 		if (h2link->sublink_ref_count[i] == 0)
1085 			continue;
1086 
1087 		/* Return the privacy state from the first active link */
1088 		pvccs = h2link->base_ptr +
1089 			h2link->shim_vs_offset +
1090 			i * h2link->instance_offset +
1091 			AZX_REG_INTEL_VS_SHIM_PVCCS;
1092 
1093 		state = readw(pvccs) & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS;
1094 		dev_dbg(bus->dev, "alt: %d, elid: %d: Mic privacy is %s\n", alt,
1095 			elid, str_enabled_disabled(state));
1096 
1097 		return state;
1098 	}
1099 
1100 	return false;
1101 }
1102 EXPORT_SYMBOL_NS(hdac_bus_eml_get_mic_privacy_state, "SND_SOC_SOF_HDA_MLINK");
1103 
1104 #endif
1105 
1106 MODULE_LICENSE("Dual BSD/GPL");
1107 MODULE_DESCRIPTION("SOF support for HDaudio multi-link");
1108