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