xref: /linux/sound/soc/sof/intel/hda-mlink.c (revision cdd30ebb1b9f36159d66f088b61aee264e649d7a)
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 
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 
hdaml_lnk_enum(struct device * dev,struct hdac_ext2_link * h2link,void __iomem * remap_addr,void __iomem * ml_addr,int link_idx)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 
check_sublink_power(u32 __iomem * lctl,int sublink,bool enabled)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 
hdaml_link_init(u32 __iomem * lctl,int sublink)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 
hdaml_link_shutdown(u32 __iomem * lctl,int sublink)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 
hdaml_link_enable_interrupt(u32 __iomem * lctl,bool enable)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 
hdaml_link_check_interrupt(u32 __iomem * lctl)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 
hdaml_wait_bit(void __iomem * base,int offset,u32 mask,u32 target)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 
hdaml_link_set_syncprd(u32 __iomem * lsync,u32 syncprd)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 
hdaml_link_wait_syncpu(u32 __iomem * lsync)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 
hdaml_link_sync_arm(u32 __iomem * lsync,int sublink)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 
hdaml_link_sync_go(u32 __iomem * lsync)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 
hdaml_link_check_cmdsync(u32 __iomem * lsync,u32 cmdsync_mask)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 
hdaml_link_get_lsdiid(u16 __iomem * lsdiid)334 static u16 hdaml_link_get_lsdiid(u16 __iomem *lsdiid)
335 {
336 	return readw(lsdiid);
337 }
338 
hdaml_link_set_lsdiid(u16 __iomem * lsdiid,int dev_num)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 
hdaml_shim_map_stream_ch(u16 __iomem * pcmsycm,int lchan,int hchan,int stream_id,int dir)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 
hdaml_lctl_offload_enable(u32 __iomem * lctl,bool enable)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 
hda_ml_alloc_h2link(struct hdac_bus * bus,int index)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 
hda_bus_ml_init(struct hdac_bus * bus)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 
hda_bus_ml_free(struct hdac_bus * bus)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 *
find_ext2_link(struct hdac_bus * bus,bool alt,int elid)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 
hdac_bus_eml_get_count(struct hdac_bus * bus,bool alt,int elid)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 
hdac_bus_eml_enable_interrupt_unlocked(struct hdac_bus * bus,bool alt,int elid,bool enable)484 void hdac_bus_eml_enable_interrupt_unlocked(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 	hdaml_link_enable_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
499 }
500 EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt_unlocked, "SND_SOC_SOF_HDA_MLINK");
501 
hdac_bus_eml_enable_interrupt(struct hdac_bus * bus,bool alt,int elid,bool enable)502 void hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable)
503 {
504 	struct hdac_ext2_link *h2link;
505 	struct hdac_ext_link *hlink;
506 
507 	h2link = find_ext2_link(bus, alt, elid);
508 	if (!h2link)
509 		return;
510 
511 	if (!h2link->intc)
512 		return;
513 
514 	hlink = &h2link->hext_link;
515 
516 	mutex_lock(&h2link->eml_lock);
517 
518 	hdaml_link_enable_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
519 
520 	mutex_unlock(&h2link->eml_lock);
521 }
522 EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt, "SND_SOC_SOF_HDA_MLINK");
523 
hdac_bus_eml_check_interrupt(struct hdac_bus * bus,bool alt,int elid)524 bool hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid)
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 false;
532 
533 	if (!h2link->intc)
534 		return false;
535 
536 	hlink = &h2link->hext_link;
537 
538 	return hdaml_link_check_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL);
539 }
540 EXPORT_SYMBOL_NS(hdac_bus_eml_check_interrupt, "SND_SOC_SOF_HDA_MLINK");
541 
hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus * bus,bool alt,int elid,u32 syncprd)542 int hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd)
543 {
544 	struct hdac_ext2_link *h2link;
545 	struct hdac_ext_link *hlink;
546 
547 	h2link = find_ext2_link(bus, alt, elid);
548 	if (!h2link)
549 		return 0;
550 
551 	if (!h2link->lss)
552 		return 0;
553 
554 	hlink = &h2link->hext_link;
555 
556 	hdaml_link_set_syncprd(hlink->ml_addr + AZX_REG_ML_LSYNC, syncprd);
557 
558 	return 0;
559 }
560 EXPORT_SYMBOL_NS(hdac_bus_eml_set_syncprd_unlocked, "SND_SOC_SOF_HDA_MLINK");
561 
hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus * bus,u32 syncprd)562 int hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd)
563 {
564 	return hdac_bus_eml_set_syncprd_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, syncprd);
565 }
566 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_syncprd_unlocked, "SND_SOC_SOF_HDA_MLINK");
567 
hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus * bus,bool alt,int elid)568 int hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid)
569 {
570 	struct hdac_ext2_link *h2link;
571 	struct hdac_ext_link *hlink;
572 
573 	h2link = find_ext2_link(bus, alt, elid);
574 	if (!h2link)
575 		return 0;
576 
577 	if (!h2link->lss)
578 		return 0;
579 
580 	hlink = &h2link->hext_link;
581 
582 	return hdaml_link_wait_syncpu(hlink->ml_addr + AZX_REG_ML_LSYNC);
583 }
584 EXPORT_SYMBOL_NS(hdac_bus_eml_wait_syncpu_unlocked, "SND_SOC_SOF_HDA_MLINK");
585 
hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus * bus)586 int hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus)
587 {
588 	return hdac_bus_eml_wait_syncpu_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
589 }
590 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_wait_syncpu_unlocked, "SND_SOC_SOF_HDA_MLINK");
591 
hdac_bus_eml_sync_arm_unlocked(struct hdac_bus * bus,bool alt,int elid,int sublink)592 void hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
593 {
594 	struct hdac_ext2_link *h2link;
595 	struct hdac_ext_link *hlink;
596 
597 	h2link = find_ext2_link(bus, alt, elid);
598 	if (!h2link)
599 		return;
600 
601 	if (!h2link->lss)
602 		return;
603 
604 	hlink = &h2link->hext_link;
605 
606 	hdaml_link_sync_arm(hlink->ml_addr + AZX_REG_ML_LSYNC, sublink);
607 }
608 EXPORT_SYMBOL_NS(hdac_bus_eml_sync_arm_unlocked, "SND_SOC_SOF_HDA_MLINK");
609 
hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus * bus,int sublink)610 void hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink)
611 {
612 	hdac_bus_eml_sync_arm_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
613 }
614 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_arm_unlocked, "SND_SOC_SOF_HDA_MLINK");
615 
hdac_bus_eml_sync_go_unlocked(struct hdac_bus * bus,bool alt,int elid)616 int hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid)
617 {
618 	struct hdac_ext2_link *h2link;
619 	struct hdac_ext_link *hlink;
620 
621 	h2link = find_ext2_link(bus, alt, elid);
622 	if (!h2link)
623 		return 0;
624 
625 	if (!h2link->lss)
626 		return 0;
627 
628 	hlink = &h2link->hext_link;
629 
630 	hdaml_link_sync_go(hlink->ml_addr + AZX_REG_ML_LSYNC);
631 
632 	return 0;
633 }
634 EXPORT_SYMBOL_NS(hdac_bus_eml_sync_go_unlocked, "SND_SOC_SOF_HDA_MLINK");
635 
hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus * bus)636 int hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus)
637 {
638 	return hdac_bus_eml_sync_go_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
639 }
640 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_go_unlocked, "SND_SOC_SOF_HDA_MLINK");
641 
hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus * bus,bool alt,int elid)642 bool hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid)
643 {
644 	struct hdac_ext2_link *h2link;
645 	struct hdac_ext_link *hlink;
646 	u32 cmdsync_mask;
647 
648 	h2link = find_ext2_link(bus, alt, elid);
649 	if (!h2link)
650 		return 0;
651 
652 	if (!h2link->lss)
653 		return 0;
654 
655 	hlink = &h2link->hext_link;
656 
657 	cmdsync_mask = GENMASK(AZX_REG_ML_LSYNC_CMDSYNC_SHIFT + h2link->slcount - 1,
658 			       AZX_REG_ML_LSYNC_CMDSYNC_SHIFT);
659 
660 	return hdaml_link_check_cmdsync(hlink->ml_addr + AZX_REG_ML_LSYNC,
661 					cmdsync_mask);
662 }
663 EXPORT_SYMBOL_NS(hdac_bus_eml_check_cmdsync_unlocked, "SND_SOC_SOF_HDA_MLINK");
664 
hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus * bus)665 bool hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus)
666 {
667 	return hdac_bus_eml_check_cmdsync_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
668 }
669 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_check_cmdsync_unlocked, "SND_SOC_SOF_HDA_MLINK");
670 
hdac_bus_eml_power_up_base(struct hdac_bus * bus,bool alt,int elid,int sublink,bool eml_lock)671 static int hdac_bus_eml_power_up_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
672 				      bool eml_lock)
673 {
674 	struct hdac_ext2_link *h2link;
675 	struct hdac_ext_link *hlink;
676 	int ret = 0;
677 
678 	h2link = find_ext2_link(bus, alt, elid);
679 	if (!h2link)
680 		return -ENODEV;
681 
682 	if (sublink >= h2link->slcount)
683 		return -EINVAL;
684 
685 	hlink = &h2link->hext_link;
686 
687 	if (eml_lock)
688 		mutex_lock(&h2link->eml_lock);
689 
690 	if (!alt) {
691 		if (++hlink->ref_count > 1)
692 			goto skip_init;
693 	} else {
694 		if (++h2link->sublink_ref_count[sublink] > 1)
695 			goto skip_init;
696 	}
697 
698 	ret = hdaml_link_init(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
699 
700 skip_init:
701 	if (eml_lock)
702 		mutex_unlock(&h2link->eml_lock);
703 
704 	return ret;
705 }
706 
hdac_bus_eml_power_up(struct hdac_bus * bus,bool alt,int elid,int sublink)707 int hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink)
708 {
709 	return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, true);
710 }
711 EXPORT_SYMBOL_NS(hdac_bus_eml_power_up, "SND_SOC_SOF_HDA_MLINK");
712 
hdac_bus_eml_power_up_unlocked(struct hdac_bus * bus,bool alt,int elid,int sublink)713 int hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
714 {
715 	return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, false);
716 }
717 EXPORT_SYMBOL_NS(hdac_bus_eml_power_up_unlocked, "SND_SOC_SOF_HDA_MLINK");
718 
hdac_bus_eml_power_down_base(struct hdac_bus * bus,bool alt,int elid,int sublink,bool eml_lock)719 static int hdac_bus_eml_power_down_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
720 					bool eml_lock)
721 {
722 	struct hdac_ext2_link *h2link;
723 	struct hdac_ext_link *hlink;
724 	int ret = 0;
725 
726 	h2link = find_ext2_link(bus, alt, elid);
727 	if (!h2link)
728 		return -ENODEV;
729 
730 	if (sublink >= h2link->slcount)
731 		return -EINVAL;
732 
733 	hlink = &h2link->hext_link;
734 
735 	if (eml_lock)
736 		mutex_lock(&h2link->eml_lock);
737 
738 	if (!alt) {
739 		if (--hlink->ref_count > 0)
740 			goto skip_shutdown;
741 	} else {
742 		if (--h2link->sublink_ref_count[sublink] > 0)
743 			goto skip_shutdown;
744 	}
745 	ret = hdaml_link_shutdown(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
746 
747 skip_shutdown:
748 	if (eml_lock)
749 		mutex_unlock(&h2link->eml_lock);
750 
751 	return ret;
752 }
753 
hdac_bus_eml_power_down(struct hdac_bus * bus,bool alt,int elid,int sublink)754 int hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink)
755 {
756 	return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, true);
757 }
758 EXPORT_SYMBOL_NS(hdac_bus_eml_power_down, "SND_SOC_SOF_HDA_MLINK");
759 
hdac_bus_eml_power_down_unlocked(struct hdac_bus * bus,bool alt,int elid,int sublink)760 int hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
761 {
762 	return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, false);
763 }
764 EXPORT_SYMBOL_NS(hdac_bus_eml_power_down_unlocked, "SND_SOC_SOF_HDA_MLINK");
765 
hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus * bus,int sublink)766 int hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink)
767 {
768 	return hdac_bus_eml_power_up_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
769 }
770 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_up_unlocked, "SND_SOC_SOF_HDA_MLINK");
771 
hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus * bus,int sublink)772 int hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink)
773 {
774 	return hdac_bus_eml_power_down_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
775 }
776 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_down_unlocked, "SND_SOC_SOF_HDA_MLINK");
777 
hdac_bus_eml_sdw_get_lsdiid_unlocked(struct hdac_bus * bus,int sublink,u16 * lsdiid)778 int hdac_bus_eml_sdw_get_lsdiid_unlocked(struct hdac_bus *bus, int sublink, u16 *lsdiid)
779 {
780 	struct hdac_ext2_link *h2link;
781 	struct hdac_ext_link *hlink;
782 
783 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
784 	if (!h2link)
785 		return -ENODEV;
786 
787 	hlink = &h2link->hext_link;
788 
789 	*lsdiid = hdaml_link_get_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink));
790 
791 	return 0;
792 } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_lsdiid_unlocked, "SND_SOC_SOF_HDA_MLINK");
793 
hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus * bus,int sublink,int dev_num)794 int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num)
795 {
796 	struct hdac_ext2_link *h2link;
797 	struct hdac_ext_link *hlink;
798 
799 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
800 	if (!h2link)
801 		return -ENODEV;
802 
803 	hlink = &h2link->hext_link;
804 
805 	mutex_lock(&h2link->eml_lock);
806 
807 	hdaml_link_set_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink), dev_num);
808 
809 	mutex_unlock(&h2link->eml_lock);
810 
811 	return 0;
812 } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_lsdiid, "SND_SOC_SOF_HDA_MLINK");
813 
814 /*
815  * the 'y' parameter comes from the PCMSyCM hardware register naming. 'y' refers to the
816  * PDI index, i.e. the FIFO used for RX or TX
817  */
hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus * bus,int sublink,int y,int channel_mask,int stream_id,int dir)818 int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y,
819 				   int channel_mask, int stream_id, int dir)
820 {
821 	struct hdac_ext2_link *h2link;
822 	u16 __iomem *pcmsycm;
823 	int hchan;
824 	int lchan;
825 	u16 val;
826 
827 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
828 	if (!h2link)
829 		return -ENODEV;
830 
831 	pcmsycm = h2link->base_ptr + h2link->shim_offset +
832 		h2link->instance_offset * sublink +
833 		AZX_REG_SDW_SHIM_PCMSyCM(y);
834 
835 	if (channel_mask) {
836 		hchan = __fls(channel_mask);
837 		lchan = __ffs(channel_mask);
838 	} else {
839 		hchan = 0;
840 		lchan = 0;
841 	}
842 
843 	mutex_lock(&h2link->eml_lock);
844 
845 	hdaml_shim_map_stream_ch(pcmsycm, lchan, hchan,
846 				 stream_id, dir);
847 
848 	mutex_unlock(&h2link->eml_lock);
849 
850 	val = readw(pcmsycm);
851 
852 	dev_dbg(bus->dev, "sublink %d channel_mask %#x stream_id %d dir %d pcmscm %#x\n",
853 		sublink, channel_mask, stream_id, dir, val);
854 
855 	return 0;
856 } EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_map_stream_ch, "SND_SOC_SOF_HDA_MLINK");
857 
hda_bus_ml_put_all(struct hdac_bus * bus)858 void hda_bus_ml_put_all(struct hdac_bus *bus)
859 {
860 	struct hdac_ext_link *hlink;
861 
862 	list_for_each_entry(hlink, &bus->hlink_list, list) {
863 		struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
864 
865 		if (!h2link->alt)
866 			snd_hdac_ext_bus_link_put(bus, hlink);
867 	}
868 }
869 EXPORT_SYMBOL_NS(hda_bus_ml_put_all, "SND_SOC_SOF_HDA_MLINK");
870 
hda_bus_ml_reset_losidv(struct hdac_bus * bus)871 void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
872 {
873 	struct hdac_ext_link *hlink;
874 
875 	/* Reset stream-to-link mapping */
876 	list_for_each_entry(hlink, &bus->hlink_list, list)
877 		writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV);
878 }
879 EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, "SND_SOC_SOF_HDA_MLINK");
880 
hda_bus_ml_resume(struct hdac_bus * bus)881 int hda_bus_ml_resume(struct hdac_bus *bus)
882 {
883 	struct hdac_ext_link *hlink;
884 	int ret;
885 
886 	/* power up links that were active before suspend */
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 && hlink->ref_count) {
891 			ret = snd_hdac_ext_bus_link_power_up(hlink);
892 			if (ret < 0)
893 				return ret;
894 		}
895 	}
896 	return 0;
897 }
898 EXPORT_SYMBOL_NS(hda_bus_ml_resume, "SND_SOC_SOF_HDA_MLINK");
899 
hda_bus_ml_suspend(struct hdac_bus * bus)900 int hda_bus_ml_suspend(struct hdac_bus *bus)
901 {
902 	struct hdac_ext_link *hlink;
903 	int ret;
904 
905 	list_for_each_entry(hlink, &bus->hlink_list, list) {
906 		struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
907 
908 		if (!h2link->alt) {
909 			ret = snd_hdac_ext_bus_link_power_down(hlink);
910 			if (ret < 0)
911 				return ret;
912 		}
913 	}
914 	return 0;
915 }
916 EXPORT_SYMBOL_NS(hda_bus_ml_suspend, "SND_SOC_SOF_HDA_MLINK");
917 
hdac_bus_eml_get_mutex(struct hdac_bus * bus,bool alt,int elid)918 struct mutex *hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid)
919 {
920 	struct hdac_ext2_link *h2link;
921 
922 	h2link = find_ext2_link(bus, alt, elid);
923 	if (!h2link)
924 		return NULL;
925 
926 	return &h2link->eml_lock;
927 }
928 EXPORT_SYMBOL_NS(hdac_bus_eml_get_mutex, "SND_SOC_SOF_HDA_MLINK");
929 
hdac_bus_eml_ssp_get_hlink(struct hdac_bus * bus)930 struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus)
931 {
932 	struct hdac_ext2_link *h2link;
933 
934 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_SSP);
935 	if (!h2link)
936 		return NULL;
937 
938 	return &h2link->hext_link;
939 }
940 EXPORT_SYMBOL_NS(hdac_bus_eml_ssp_get_hlink, "SND_SOC_SOF_HDA_MLINK");
941 
hdac_bus_eml_dmic_get_hlink(struct hdac_bus * bus)942 struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus)
943 {
944 	struct hdac_ext2_link *h2link;
945 
946 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_DMIC);
947 	if (!h2link)
948 		return NULL;
949 
950 	return &h2link->hext_link;
951 }
952 EXPORT_SYMBOL_NS(hdac_bus_eml_dmic_get_hlink, "SND_SOC_SOF_HDA_MLINK");
953 
hdac_bus_eml_sdw_get_hlink(struct hdac_bus * bus)954 struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus)
955 {
956 	struct hdac_ext2_link *h2link;
957 
958 	h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
959 	if (!h2link)
960 		return NULL;
961 
962 	return &h2link->hext_link;
963 }
964 EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_hlink, "SND_SOC_SOF_HDA_MLINK");
965 
hdac_bus_eml_enable_offload(struct hdac_bus * bus,bool alt,int elid,bool enable)966 int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable)
967 {
968 	struct hdac_ext2_link *h2link;
969 	struct hdac_ext_link *hlink;
970 
971 	h2link = find_ext2_link(bus, alt, elid);
972 	if (!h2link)
973 		return -ENODEV;
974 
975 	if (!h2link->ofls)
976 		return 0;
977 
978 	hlink = &h2link->hext_link;
979 
980 	mutex_lock(&h2link->eml_lock);
981 
982 	hdaml_lctl_offload_enable(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
983 
984 	mutex_unlock(&h2link->eml_lock);
985 
986 	return 0;
987 }
988 EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, "SND_SOC_SOF_HDA_MLINK");
989 
990 #endif
991 
992 MODULE_LICENSE("Dual BSD/GPL");
993 MODULE_DESCRIPTION("SOF support for HDaudio multi-link");
994