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