976 lines · c
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 or4// redistributing this file, you may do so under either license.5//6// Copyright(c) 2022 Intel Corporation7//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 link27 *28 * @hext_link: hdac_ext_link29 * @alt: flag set for alternate extended links30 * @intc: boolean for interrupt capable31 * @ofls: boolean for offload support32 * @lss: boolean for link synchronization capabilities33 * @slcount: sublink count34 * @elid: extended link ID (AZX_REG_ML_LEPTR_ID_ defines)35 * @elver: extended link version36 * @leptr: extended link pointer37 * @eml_lock: mutual exclusion to access shared registers e.g. CPA/SPA bits38 * in LCTL register39 * @sublink_ref_count: array of refcounts, required to power-manage sublinks independently40 * @base_ptr: pointer to shim/ip/shim_vs space41 * @instance_offset: offset between each of @slcount instances managed by link42 * @shim_offset: offset to SHIM register base43 * @ip_offset: offset to IP register base44 * @shim_vs_offset: offset to vendor-specific (VS) SHIM base45 */46struct 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 0x800073#define AZX_REG_SDW_SHIM_OFFSET 0x074#define AZX_REG_SDW_IP_OFFSET 0x10075#define AZX_REG_SDW_VS_SHIM_OFFSET 0x600076#define AZX_REG_SDW_SHIM_PCMSyCM(y) (0x16 + 0x4 * (y))77 78/* only one instance supported */79#define AZX_REG_INTEL_DMIC_SHIM_OFFSET 0x080#define AZX_REG_INTEL_DMIC_IP_OFFSET 0x10081#define AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET 0x600082 83#define AZX_REG_INTEL_SSP_INSTANCE_OFFSET 0x100084#define AZX_REG_INTEL_SSP_SHIM_OFFSET 0x085#define AZX_REG_INTEL_SSP_IP_OFFSET 0x10086#define AZX_REG_INTEL_SSP_VS_SHIM_OFFSET 0xC0087 88/* only one instance supported */89#define AZX_REG_INTEL_UAOL_SHIM_OFFSET 0x090#define AZX_REG_INTEL_UAOL_IP_OFFSET 0x10091#define AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET 0xC0092 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 or96 * helpers. Locking and scanning lists is handled at a higher level97 */98 99static 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 links116 */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 transition183 * 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 issue186 */187#define HDAML_POLL_DELAY_MIN_US 10188#define HDAML_POLL_DELAY_SLACK_US 5189#define HDAML_POLL_DELAY_RETRY 100190 191static 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 216static 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 229static 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 243static 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 else251 val &= ~AZX_ML_LCTL_INTEN;252 253 writel(val, lctl);254}255 256static 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 265static 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 283static 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 when293 * the link becomes active.294 */295 val |= AZX_REG_ML_LSYNC_SYNCPU;296 297 writel(val, lsync);298}299 300static int hdaml_link_wait_syncpu(u32 __iomem *lsync)301{302 return hdaml_wait_bit(lsync, 0, AZX_REG_ML_LSYNC_SYNCPU, 0);303}304 305static 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 315static 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 325static 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 334static u16 hdaml_link_get_lsdiid(u16 __iomem *lsdiid)335{336 return readw(lsdiid);337}338 339static 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 349static 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 364static 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 else371 val &= ~AZX_ML_LCTL_OFLEN;372 373 writel(val, lctl);374}375 376/* END HDAML section */377 378static 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, the407 * refcount needs to be initialized.408 */409 if (!h2link->alt)410 hlink->ref_count = 1;411 412 return 0;413}414 415int 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}437EXPORT_SYMBOL_NS(hda_bus_ml_init, SND_SOC_SOF_HDA_MLINK);438 439void 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}455EXPORT_SYMBOL_NS(hda_bus_ml_free, SND_SOC_SOF_HDA_MLINK);456 457static struct hdac_ext2_link *458find_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 472int 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}482EXPORT_SYMBOL_NS(hdac_bus_eml_get_count, SND_SOC_SOF_HDA_MLINK);483 484void 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}504EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt, SND_SOC_SOF_HDA_MLINK);505 506bool 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}522EXPORT_SYMBOL_NS(hdac_bus_eml_check_interrupt, SND_SOC_SOF_HDA_MLINK);523 524int 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}542EXPORT_SYMBOL_NS(hdac_bus_eml_set_syncprd_unlocked, SND_SOC_SOF_HDA_MLINK);543 544int 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}548EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_syncprd_unlocked, SND_SOC_SOF_HDA_MLINK);549 550int 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}566EXPORT_SYMBOL_NS(hdac_bus_eml_wait_syncpu_unlocked, SND_SOC_SOF_HDA_MLINK);567 568int 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}572EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_wait_syncpu_unlocked, SND_SOC_SOF_HDA_MLINK);573 574void 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}590EXPORT_SYMBOL_NS(hdac_bus_eml_sync_arm_unlocked, SND_SOC_SOF_HDA_MLINK);591 592void 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}596EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_arm_unlocked, SND_SOC_SOF_HDA_MLINK);597 598int 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}616EXPORT_SYMBOL_NS(hdac_bus_eml_sync_go_unlocked, SND_SOC_SOF_HDA_MLINK);617 618int 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}622EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_go_unlocked, SND_SOC_SOF_HDA_MLINK);623 624bool 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}645EXPORT_SYMBOL_NS(hdac_bus_eml_check_cmdsync_unlocked, SND_SOC_SOF_HDA_MLINK);646 647bool 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}651EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_check_cmdsync_unlocked, SND_SOC_SOF_HDA_MLINK);652 653static 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 682skip_init:683 if (eml_lock)684 mutex_unlock(&h2link->eml_lock);685 686 return ret;687}688 689int 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}693EXPORT_SYMBOL_NS(hdac_bus_eml_power_up, SND_SOC_SOF_HDA_MLINK);694 695int 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}699EXPORT_SYMBOL_NS(hdac_bus_eml_power_up_unlocked, SND_SOC_SOF_HDA_MLINK);700 701static 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 729skip_shutdown:730 if (eml_lock)731 mutex_unlock(&h2link->eml_lock);732 733 return ret;734}735 736int 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}740EXPORT_SYMBOL_NS(hdac_bus_eml_power_down, SND_SOC_SOF_HDA_MLINK);741 742int 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}746EXPORT_SYMBOL_NS(hdac_bus_eml_power_down_unlocked, SND_SOC_SOF_HDA_MLINK);747 748int 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}752EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_up_unlocked, SND_SOC_SOF_HDA_MLINK);753 754int 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}758EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_down_unlocked, SND_SOC_SOF_HDA_MLINK);759 760int 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 776int 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 the798 * PDI index, i.e. the FIFO used for RX or TX799 */800int 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 840void 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}851EXPORT_SYMBOL_NS(hda_bus_ml_put_all, SND_SOC_SOF_HDA_MLINK);852 853void 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}861EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, SND_SOC_SOF_HDA_MLINK);862 863int 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}880EXPORT_SYMBOL_NS(hda_bus_ml_resume, SND_SOC_SOF_HDA_MLINK);881 882int 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}898EXPORT_SYMBOL_NS(hda_bus_ml_suspend, SND_SOC_SOF_HDA_MLINK);899 900struct 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}910EXPORT_SYMBOL_NS(hdac_bus_eml_get_mutex, SND_SOC_SOF_HDA_MLINK);911 912struct 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}922EXPORT_SYMBOL_NS(hdac_bus_eml_ssp_get_hlink, SND_SOC_SOF_HDA_MLINK);923 924struct 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}934EXPORT_SYMBOL_NS(hdac_bus_eml_dmic_get_hlink, SND_SOC_SOF_HDA_MLINK);935 936struct 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}946EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_hlink, SND_SOC_SOF_HDA_MLINK);947 948int 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}970EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, SND_SOC_SOF_HDA_MLINK);971 972#endif973 974MODULE_LICENSE("Dual BSD/GPL");975MODULE_DESCRIPTION("SOF support for HDaudio multi-link");976