Commit a1d68507 authored by Mark Brown's avatar Mark Brown

ASoC: SOF: Intel: hda-mlink: HDaudio multi-link

Merge series from Peter Ujfalusi <peter.ujfalusi@linux.intel.com>:

The following series adds the core support to handle the recently updated
HDaudio multi-link support to hanlde non HDA links, like SoundWire/DMIC/SSP on
Intel platform.

For details, please see the first patch which documents the current mlink
support (introduced at Skylake) and the new extensions, arriving with LNL.

There is no change in functionality for existing HDA support, the extension is
backwards compatible with existing implementations.
parents 3e5f7972 681f27f3
......@@ -9,3 +9,4 @@ HD-Audio
controls
dp-mst
realtek-pc-beep
intel-multi-link
.. SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
.. include:: <isonum.txt>
================================================
HDAudio multi-link extensions on Intel platforms
================================================
:Copyright: |copy| 2023 Intel Corporation
This file documents the 'multi-link structure' introduced in 2015 with
the Skylake processor and recently extended in newer Intel platforms
HDaudio existing link mapping (2015 addition in SkyLake)
========================================================
External HDAudio codecs are handled with link #0, while iDISP codec
for HDMI/DisplayPort is handled with link #1.
The only change to the 2015 definitions is the declaration of the
LCAP.ALT=0x0 - since the ALT bit was previously reserved, this is a
backwards-compatible change.
LCTL.SPA and LCTL.CPA are automatically set when exiting reset. They
are only used in existing drivers when the SCF value needs to be
corrected.
Basic structure for HDaudio codecs
----------------------------------
::
+-----------+
| ML cap #0 |
+-----------+
| ML cap #1 |---+
+-----------+ |
|
+--> 0x0 +---------------+ LCAP
| ALT=0 |
+---------------+
| S192 |
+---------------+
| S96 |
+---------------+
| S48 |
+---------------+
| S24 |
+---------------+
| S12 |
+---------------+
| S6 |
+---------------+
0x4 +---------------+ LCTL
| INTSTS |
+---------------+
| CPA |
+---------------+
| SPA |
+---------------+
| SCF |
+---------------+
0x8 +---------------+ LOSIDV
| L1OSIVD15 |
+---------------+
| L1OSIDV.. |
+---------------+
| L1OSIDV1 |
+---------------+
0xC +---------------+ LSDIID
| SDIID14 |
+---------------+
| SDIID... |
+---------------+
| SDIID0 |
+---------------+
SoundWire HDaudio extended link mapping
=======================================
A SoundWire extended link is identified when LCAP.ALT=1 and
LEPTR.ID=0.
DMA control uses the existing LOSIDV register.
Changes include additional descriptions for enumeration that were not
present in earlier generations.
- multi-link synchronization: capabilities in LCAP.LSS and control in LSYNC
- number of sublinks (manager IP) in LCAP.LSCOUNT
- power management moved from SHIM to LCTL.SPA bits
- hand-over to the DSP for access to multi-link registers, SHIM/IP with LCTL.OFLEN
- mapping of SoundWire codecs to SDI ID bits
- move of SHIM and Cadence registers to different offsets, with no
change in functionality. The LEPTR.PTR value is an offset from the
ML address, with a default value of 0x30000.
Extended structure for SoundWire (assuming 4 Manager IP)
--------------------------------------------------------
::
+-----------+
| ML cap #0 |
+-----------+
| ML cap #1 |
+-----------+
| ML cap #2 |---+
+-----------+ |
|
+--> 0x0 +---------------+ LCAP
| ALT=1 |
+---------------+
| INTC |
+---------------+
| OFLS |
+---------------+
| LSS |
+---------------+
| SLCOUNT=4 |-----------+
+---------------+ |
|
0x4 +---------------+ LCTL |
| INTSTS | |
+---------------+ |
| CPA (x bits) | |
+---------------+ |
| SPA (x bits) | |
+---------------+ for each sublink x
| INTEN | |
+---------------+ |
| OFLEN | |
+---------------+ |
|
0x8 +---------------+ LOSIDV |
| L1OSIVD15 | |
+---------------+ |
| L1OSIDV.. | |
+---------------+ |
| L1OSIDV1 | +---+----------------------------------------------------------+
+---------------+ | |
v |
0xC + 0x2 * x +---------------+ LSDIIDx +---> 0x30000 +-----------------+ 0x00030000 |
| SDIID14 | | | SoundWire SHIM | |
+---------------+ | | generic | |
| SDIID... | | +-----------------+ 0x00030100 |
+---------------+ | | SoundWire IP | |
| SDIID0 | | +-----------------+ 0x00036000 |
+---------------+ | | SoundWire SHIM | |
| | vendor-specific | |
0x1C +---------------+ LSYNC | +-----------------+ |
| CMDSYNC | | v
+---------------+ | +-----------------+ 0x00030000 + 0x8000 * x
| SYNCGO | | | SoundWire SHIM |
+---------------+ | | generic |
| SYNCPU | | +-----------------+ 0x00030100 + 0x8000 * x
+---------------+ | | SoundWire IP |
| SYNPRD | | +-----------------+ 0x00036000 + 0x8000 * x
+---------------+ | | SoundWire SHIM |
| | vendor-specific |
0x20 +---------------+ LEPTR | +-----------------+
| ID = 0 | |
+---------------+ |
| VER | |
+---------------+ |
| PTR |------------+
+---------------+
DMIC HDaudio extended link mapping
==================================
A DMIC extended link is identified when LCAP.ALT=1 and
LEPTR.ID=0xC1 are set.
DMA control uses the existing LOSIDV register
Changes include additional descriptions for enumeration that were not
present in earlier generations.
- multi-link synchronization: capabilities in LCAP.LSS and control in LSYNC
- power management with LCTL.SPA bits
- hand-over to the DSP for access to multi-link registers, SHIM/IP with LCTL.OFLEN
- move of DMIC registers to different offsets, with no change in
functionality. The LEPTR.PTR value is an offset from the ML
address, with a default value of 0x10000.
Extended structure for DMIC
---------------------------
::
+-----------+
| ML cap #0 |
+-----------+
| ML cap #1 |
+-----------+
| ML cap #2 |---+
+-----------+ |
|
+--> 0x0 +---------------+ LCAP
| ALT=1 |
+---------------+
| INTC |
+---------------+
| OFLS |
+---------------+
| SLCOUNT=1 |
+---------------+
0x4 +---------------+ LCTL
| INTSTS |
+---------------+
| CPA |
+---------------+
| SPA |
+---------------+
| INTEN |
+---------------+
| OFLEN |
+---------------+ +---> 0x10000 +-----------------+ 0x00010000
| | DMIC SHIM |
0x8 +---------------+ LOSIDV | | generic |
| L1OSIVD15 | | +-----------------+ 0x00010100
+---------------+ | | DMIC IP |
| L1OSIDV.. | | +-----------------+ 0x00016000
+---------------+ | | DMIC SHIM |
| L1OSIDV1 | | | vendor-specific |
+---------------+ | +-----------------+
|
0x20 +---------------+ LEPTR |
| ID = 0xC1 | |
+---------------+ |
| VER | |
+---------------+ |
| PTR |-----------+
+---------------+
SSP HDaudio extended link mapping
=================================
A DMIC extended link is identified when LCAP.ALT=1 and
LEPTR.ID=0xC0 are set.
DMA control uses the existing LOSIDV register
Changes include additional descriptions for enumeration and control that were not
present in earlier generations:
- number of sublinks (SSP IP instances) in LCAP.LSCOUNT
- power management moved from SHIM to LCTL.SPA bits
- hand-over to the DSP for access to multi-link registers, SHIM/IP
with LCTL.OFLEN
- move of SHIM and SSP IP registers to different offsets, with no
change in functionality. The LEPTR.PTR value is an offset from the ML
address, with a default value of 0x28000.
Extended structure for SSP (assuming 3 instances of the IP)
-----------------------------------------------------------
::
+-----------+
| ML cap #0 |
+-----------+
| ML cap #1 |
+-----------+
| ML cap #2 |---+
+-----------+ |
|
+--> 0x0 +---------------+ LCAP
| ALT=1 |
+---------------+
| INTC |
+---------------+
| OFLS |
+---------------+
| SLCOUNT=3 |-------------------------for each sublink x -------------------------+
+---------------+ |
|
0x4 +---------------+ LCTL |
| INTSTS | |
+---------------+ |
| CPA (x bits) | |
+---------------+ |
| SPA (x bits) | |
+---------------+ |
| INTEN | |
+---------------+ |
| OFLEN | |
+---------------+ +---> 0x28000 +-----------------+ 0x00028000 |
| | SSP SHIM | |
0x8 +---------------+ LOSIDV | | generic | |
| L1OSIVD15 | | +-----------------+ 0x00028100 |
+---------------+ | | SSP IP | |
| L1OSIDV.. | | +-----------------+ 0x00028C00 |
+---------------+ | | SSP SHIM | |
| L1OSIDV1 | | | vendor-specific | |
+---------------+ | +-----------------+ |
| v
0x20 +---------------+ LEPTR | +-----------------+ 0x00028000 + 0x1000 * x
| ID = 0xC0 | | | SSP SHIM |
+---------------+ | | generic |
| VER | | +-----------------+ 0x00028100 + 0x1000 * x
+---------------+ | | SSP IP |
| PTR |-----------+ +-----------------+ 0x00028C00 + 0x1000 * x
+---------------+ | SSP SHIM |
| vendor-specific |
+-----------------+
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* Copyright(c) 2022-2023 Intel Corporation. All rights reserved.
*/
struct hdac_bus;
struct hdac_ext_link;
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)
int hda_bus_ml_init(struct hdac_bus *bus);
void hda_bus_ml_free(struct hdac_bus *bus);
int hdac_bus_eml_get_count(struct hdac_bus *bus, bool alt, int elid);
void hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable);
bool hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid);
int hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd);
int hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd);
int hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid);
int hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus);
void hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink);
void hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink);
int hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid);
int hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus);
bool hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid);
bool hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus);
int hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink);
int hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink);
int hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink);
int hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink);
int hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink);
int hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink);
int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num);
void hda_bus_ml_put_all(struct hdac_bus *bus);
void hda_bus_ml_reset_losidv(struct hdac_bus *bus);
int hda_bus_ml_resume(struct hdac_bus *bus);
int hda_bus_ml_suspend(struct hdac_bus *bus);
struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus);
struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus);
struct mutex *hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid);
int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable);
#else
static inline int
hda_bus_ml_init(struct hdac_bus *bus) { return 0; }
static inline void hda_bus_ml_free(struct hdac_bus *bus) { }
static inline int
hdac_bus_eml_get_count(struct hdac_bus *bus, bool alt, int elid) { return 0; }
static inline void
hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable) { }
static inline bool
hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid) { return false; }
static inline int
hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd)
{
return 0;
}
static inline int
hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd)
{
return 0;
}
static inline int
hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid)
{
return 0;
}
static inline int
hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus) { return 0; }
static inline void
hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink) { }
static inline void
hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink) { }
static inline int
hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid) { return 0; }
static inline int
hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus) { return 0; }
static inline bool
hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid) { return false; }
static inline bool
hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus) { return false; }
static inline int
hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink)
{
return 0;
}
static inline int
hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
{
return 0;
}
static inline int
hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink)
{
return 0;
}
static inline int
hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
{
return 0;
}
static inline int
hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink) { return 0; }
static inline int
hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink) { return 0; }
static inline int
hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num) { return 0; }
static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { }
static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { }
static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; }
static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; }
static inline struct hdac_ext_link *
hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) { return NULL; }
static inline struct hdac_ext_link *
hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus) { return NULL; }
static inline struct mutex *
hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid) { return NULL; }
static inline int
hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable)
{
return 0;
}
#endif /* CONFIG_SND_SOC_SOF_HDA */
......@@ -258,14 +258,27 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
#define AZX_ML_BASE 0x40
#define AZX_ML_INTERVAL 0x40
/* HDaudio registers valid for HDaudio and HDaudio extended links */
#define AZX_REG_ML_LCAP 0x00
#define AZX_REG_ML_LCTL 0x04
#define AZX_ML_HDA_LCAP_ALT BIT(28)
#define AZX_ML_HDA_LCAP_ALT_HDA 0x0
#define AZX_ML_HDA_LCAP_ALT_HDA_EXT 0x1
#define AZX_ML_HDA_LCAP_INTC BIT(27) /* only used if ALT == 1 */
#define AZX_ML_HDA_LCAP_OFLS BIT(26) /* only used if ALT == 1 */
#define AZX_ML_HDA_LCAP_LSS BIT(23) /* only used if ALT == 1 */
#define AZX_ML_HDA_LCAP_SLCOUNT GENMASK(22, 20) /* only used if ALT == 1 */
#define AZX_REG_ML_LCTL 0x04
#define AZX_ML_LCTL_INTSTS BIT(31) /* only used if ALT == 1 */
#define AZX_ML_LCTL_CPA BIT(23)
#define AZX_ML_LCTL_CPA_SHIFT 23
#define AZX_ML_LCTL_SPA BIT(16)
#define AZX_ML_LCTL_SPA_SHIFT 16
#define AZX_ML_LCTL_SCF GENMASK(3, 0)
#define AZX_ML_LCTL_INTEN BIT(5) /* only used if ALT == 1 */
#define AZX_ML_LCTL_OFLEN BIT(4) /* only used if ALT == 1 */
#define AZX_ML_LCTL_SCF GENMASK(3, 0) /* only used if ALT == 0 */
#define AZX_REG_ML_LOSIDV 0x08
......@@ -273,12 +286,35 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
#define AZX_ML_LOSIDV_STREAM_MASK 0xFFFE
#define AZX_REG_ML_LSDIID 0x0C
#define AZX_REG_ML_LSDIID_OFFSET(x) (0x0C + (x) * 0x02) /* only used if ALT == 1 */
/* HDaudio registers only valid if LCAP.ALT == 0 */
#define AZX_REG_ML_LPSOO 0x10
#define AZX_REG_ML_LPSIO 0x12
#define AZX_REG_ML_LWALFC 0x18
#define AZX_REG_ML_LOUTPAY 0x20
#define AZX_REG_ML_LINPAY 0x30
/* HDaudio Extended link registers only valid if LCAP.ALT == 1 */
#define AZX_REG_ML_LSYNC 0x1C
#define AZX_REG_ML_LSYNC_CMDSYNC BIT(24)
#define AZX_REG_ML_LSYNC_CMDSYNC_SHIFT 24
#define AZX_REG_ML_LSYNC_SYNCGO BIT(23)
#define AZX_REG_ML_LSYNC_SYNCPU BIT(20)
#define AZX_REG_ML_LSYNC_SYNCPRD GENMASK(19, 0)
#define AZX_REG_ML_LEPTR 0x20
#define AZX_REG_ML_LEPTR_ID GENMASK(31, 24)
#define AZX_REG_ML_LEPTR_ID_SHIFT 24
#define AZX_REG_ML_LEPTR_ID_SDW 0x00
#define AZX_REG_ML_LEPTR_ID_INTEL_SSP 0xC0
#define AZX_REG_ML_LEPTR_ID_INTEL_DMIC 0xC1
#define AZX_REG_ML_LEPTR_ID_INTEL_UAOL 0xC2
#define AZX_REG_ML_LEPTR_VER GENMASK(23, 20)
#define AZX_REG_ML_LEPTR_PTR GENMASK(19, 0)
/* registers for DMA Resume Capability Structure */
#define AZX_DRSM_CAP_ID 0x5
#define AZX_REG_DRSM_CTL 0x4
......
......@@ -269,6 +269,13 @@ config SND_SOC_SOF_HDA_COMMON
select SND_INTEL_DSP_CONFIG
select SND_SOC_SOF_HDA_LINK_BASELINE
select SND_SOC_SOF_HDA_PROBES
select SND_SOC_SOF_HDA_MLINK if SND_SOC_SOF_HDA_LINK
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level.
config SND_SOC_SOF_HDA_MLINK
tristate
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level.
......
......@@ -5,10 +5,12 @@ snd-sof-acpi-intel-bdw-objs := bdw.o
snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \
hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \
hda-dai.o hda-dai-ops.o hda-bus.o hda-mlink.o \
hda-dai.o hda-dai-ops.o hda-bus.o \
skl.o hda-loader-skl.o \
apl.o cnl.o tgl.o icl.o mtl.o hda-common-ops.o
snd-sof-intel-hda-mlink-objs := hda-mlink.o
snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-probes.o
snd-sof-intel-hda-objs := hda-codec.o
......@@ -19,6 +21,7 @@ obj-$(CONFIG_SND_SOC_SOF_INTEL_ATOM_HIFI_EP) += snd-sof-intel-atom.o
obj-$(CONFIG_SND_SOC_SOF_BAYTRAIL) += snd-sof-acpi-intel-byt.o
obj-$(CONFIG_SND_SOC_SOF_BROADWELL) += snd-sof-acpi-intel-bdw.o
obj-$(CONFIG_SND_SOC_SOF_HDA_COMMON) += snd-sof-intel-hda-common.o
obj-$(CONFIG_SND_SOC_SOF_HDA_MLINK) += snd-sof-intel-hda-mlink.o
obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o
snd-sof-pci-intel-tng-objs := pci-tng.o
......
......@@ -19,6 +19,7 @@
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/hda_component.h>
#include <sound/hda-mlink.h>
#include "../ops.h"
#include "hda.h"
......
......@@ -18,6 +18,7 @@
#include <linux/module.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/hda-mlink.h>
#include <trace/events/sof_intel.h>
#include "../sof-audio.h"
#include "../ops.h"
......
......@@ -12,49 +12,746 @@
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/hda-mlink.h>
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_intel.h>
#include <sound/intel-dsp-config.h>
#include <sound/intel-nhlt.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
#include "../sof-audio.h"
#include "../sof-pci-dev.h"
#include "../ops.h"
#include "hda.h"
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)
void hda_bus_ml_get_capabilities(struct hdac_bus *bus)
/**
* struct hdac_ext2_link - HDAudio extended+alternate link
*
* @hext_link: hdac_ext_link
* @alt: flag set for alternate extended links
* @intc: boolean for interrupt capable
* @ofls: boolean for offload support
* @lss: boolean for link synchronization capabilities
* @slcount: sublink count
* @elid: extended link ID (AZX_REG_ML_LEPTR_ID_ defines)
* @elver: extended link version
* @leptr: extended link pointer
* @eml_lock: mutual exclusion to access shared registers e.g. CPA/SPA bits
* in LCTL register
* @base_ptr: pointer to shim/ip/shim_vs space
* @instance_offset: offset between each of @slcount instances managed by link
* @shim_offset: offset to SHIM register base
* @ip_offset: offset to IP register base
* @shim_vs_offset: offset to vendor-specific (VS) SHIM base
*/
struct hdac_ext2_link {
struct hdac_ext_link hext_link;
/* read directly from LCAP register */
bool alt;
bool intc;
bool ofls;
bool lss;
int slcount;
int elid;
int elver;
u32 leptr;
struct mutex eml_lock; /* prevent concurrent access to e.g. CPA/SPA */
/* internal values computed from LCAP contents */
void __iomem *base_ptr;
u32 instance_offset;
u32 shim_offset;
u32 ip_offset;
u32 shim_vs_offset;
};
#define hdac_ext_link_to_ext2(h) container_of(h, struct hdac_ext2_link, hext_link)
#define AZX_REG_SDW_INSTANCE_OFFSET 0x8000
#define AZX_REG_SDW_SHIM_OFFSET 0x0
#define AZX_REG_SDW_IP_OFFSET 0x100
#define AZX_REG_SDW_VS_SHIM_OFFSET 0x6000
/* only one instance supported */
#define AZX_REG_INTEL_DMIC_SHIM_OFFSET 0x0
#define AZX_REG_INTEL_DMIC_IP_OFFSET 0x100
#define AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET 0x6000
#define AZX_REG_INTEL_SSP_INSTANCE_OFFSET 0x1000
#define AZX_REG_INTEL_SSP_SHIM_OFFSET 0x0
#define AZX_REG_INTEL_SSP_IP_OFFSET 0x100
#define AZX_REG_INTEL_SSP_VS_SHIM_OFFSET 0xC00
/* only one instance supported */
#define AZX_REG_INTEL_UAOL_SHIM_OFFSET 0x0
#define AZX_REG_INTEL_UAOL_IP_OFFSET 0x100
#define AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET 0xC00
/* HDAML section - this part follows sequences in the hardware specification,
* including naming conventions and the use of the hdaml_ prefix.
* The code is intentionally minimal with limited dependencies on frameworks or
* helpers. Locking and scanning lists is handled at a higher level
*/
static int hdaml_lnk_enum(struct device *dev, struct hdac_ext2_link *h2link,
void __iomem *ml_addr, int link_idx)
{
struct hdac_ext_link *hlink = &h2link->hext_link;
u32 base_offset;
hlink->lcaps = readl(ml_addr + AZX_REG_ML_LCAP);
h2link->alt = FIELD_GET(AZX_ML_HDA_LCAP_ALT, hlink->lcaps);
/* handle alternate extensions */
if (!h2link->alt) {
h2link->slcount = 1;
/*
* LSDIID is initialized by hardware for HDaudio link,
* it needs to be setup by software for alternate links
*/
hlink->lsdiid = readw(ml_addr + AZX_REG_ML_LSDIID);
dev_dbg(dev, "Link %d: HDAudio - lsdiid=%d\n",
link_idx, hlink->lsdiid);
return 0;
}
h2link->intc = FIELD_GET(AZX_ML_HDA_LCAP_INTC, hlink->lcaps);
h2link->ofls = FIELD_GET(AZX_ML_HDA_LCAP_OFLS, hlink->lcaps);
h2link->lss = FIELD_GET(AZX_ML_HDA_LCAP_LSS, hlink->lcaps);
/* read slcount (increment due to zero-based hardware representation */
h2link->slcount = FIELD_GET(AZX_ML_HDA_LCAP_SLCOUNT, hlink->lcaps) + 1;
dev_dbg(dev, "Link %d: HDAudio extended - sublink count %d\n",
link_idx, h2link->slcount);
/* find IP ID and offsets */
h2link->leptr = readl(hlink->ml_addr + AZX_REG_ML_LEPTR);
h2link->elid = FIELD_GET(AZX_REG_ML_LEPTR_ID, h2link->leptr);
base_offset = FIELD_GET(AZX_REG_ML_LEPTR_PTR, h2link->leptr);
h2link->base_ptr = hlink->ml_addr + base_offset;
switch (h2link->elid) {
case AZX_REG_ML_LEPTR_ID_SDW:
h2link->shim_offset = AZX_REG_SDW_SHIM_OFFSET;
h2link->ip_offset = AZX_REG_SDW_IP_OFFSET;
h2link->shim_vs_offset = AZX_REG_SDW_VS_SHIM_OFFSET;
dev_dbg(dev, "Link %d: HDAudio extended - SoundWire alternate link, leptr.ptr %#x\n",
link_idx, base_offset);
break;
case AZX_REG_ML_LEPTR_ID_INTEL_DMIC:
h2link->shim_offset = AZX_REG_INTEL_DMIC_SHIM_OFFSET;
h2link->ip_offset = AZX_REG_INTEL_DMIC_IP_OFFSET;
h2link->shim_vs_offset = AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET;
dev_dbg(dev, "Link %d: HDAudio extended - INTEL DMIC alternate link, leptr.ptr %#x\n",
link_idx, base_offset);
break;
case AZX_REG_ML_LEPTR_ID_INTEL_SSP:
h2link->shim_offset = AZX_REG_INTEL_SSP_SHIM_OFFSET;
h2link->ip_offset = AZX_REG_INTEL_SSP_IP_OFFSET;
h2link->shim_vs_offset = AZX_REG_INTEL_SSP_VS_SHIM_OFFSET;
dev_dbg(dev, "Link %d: HDAudio extended - INTEL SSP alternate link, leptr.ptr %#x\n",
link_idx, base_offset);
break;
case AZX_REG_ML_LEPTR_ID_INTEL_UAOL:
h2link->shim_offset = AZX_REG_INTEL_UAOL_SHIM_OFFSET;
h2link->ip_offset = AZX_REG_INTEL_UAOL_IP_OFFSET;
h2link->shim_vs_offset = AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET;
dev_dbg(dev, "Link %d: HDAudio extended - INTEL UAOL alternate link, leptr.ptr %#x\n",
link_idx, base_offset);
break;
default:
dev_err(dev, "Link %d: HDAudio extended - Unsupported alternate link, leptr.id=%#02x value\n",
link_idx, h2link->elid);
return -EINVAL;
}
return 0;
}
/*
* Hardware recommendations are to wait ~10us before checking any hardware transition
* reported by bits changing status.
* This value does not need to be super-precise, a slack of 5us is perfectly acceptable.
* The worst-case is about 1ms before reporting an issue
*/
#define HDAML_POLL_DELAY_MIN_US 10
#define HDAML_POLL_DELAY_SLACK_US 5
#define HDAML_POLL_DELAY_RETRY 100
static int check_sublink_power(u32 __iomem *lctl, int sublink, bool enabled)
{
if (bus->mlcap)
snd_hdac_ext_bus_get_ml_capabilities(bus);
int mask = BIT(sublink) << AZX_ML_LCTL_CPA_SHIFT;
int retry = HDAML_POLL_DELAY_RETRY;
u32 val;
usleep_range(HDAML_POLL_DELAY_MIN_US,
HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
do {
val = readl(lctl);
if (enabled) {
if (val & mask)
return 0;
} else {
if (!(val & mask))
return 0;
}
usleep_range(HDAML_POLL_DELAY_MIN_US,
HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
} while (--retry);
return -EIO;
}
void hda_bus_ml_free(struct hdac_bus *bus)
static int hdaml_link_init(u32 __iomem *lctl, int sublink)
{
u32 val;
u32 mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT;
val = readl(lctl);
val |= mask;
writel(val, lctl);
return check_sublink_power(lctl, sublink, true);
}
static int hdaml_link_shutdown(u32 __iomem *lctl, int sublink)
{
u32 val;
u32 mask;
val = readl(lctl);
mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT;
val &= ~mask;
writel(val, lctl);
return check_sublink_power(lctl, sublink, false);
}
static void hdaml_link_enable_interrupt(u32 __iomem *lctl, bool enable)
{
u32 val;
val = readl(lctl);
if (enable)
val |= AZX_ML_LCTL_INTEN;
else
val &= ~AZX_ML_LCTL_INTEN;
writel(val, lctl);
}
static bool hdaml_link_check_interrupt(u32 __iomem *lctl)
{
u32 val;
val = readl(lctl);
return val & AZX_ML_LCTL_INTSTS;
}
static int hdaml_wait_bit(void __iomem *base, int offset, u32 mask, u32 target)
{
int timeout = HDAML_POLL_DELAY_RETRY;
u32 reg_read;
do {
reg_read = readl(base + offset);
if ((reg_read & mask) == target)
return 0;
timeout--;
usleep_range(HDAML_POLL_DELAY_MIN_US,
HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
} while (timeout != 0);
return -EAGAIN;
}
static void hdaml_link_set_syncprd(u32 __iomem *lsync, u32 syncprd)
{
u32 val;
val = readl(lsync);
val &= ~AZX_REG_ML_LSYNC_SYNCPRD;
val |= (syncprd & AZX_REG_ML_LSYNC_SYNCPRD);
/*
* set SYNCPU but do not wait. The bit is cleared by hardware when
* the link becomes active.
*/
val |= AZX_REG_ML_LSYNC_SYNCPU;
writel(val, lsync);
}
static int hdaml_link_wait_syncpu(u32 __iomem *lsync)
{
return hdaml_wait_bit(lsync, 0, AZX_REG_ML_LSYNC_SYNCPU, 0);
}
static void hdaml_link_sync_arm(u32 __iomem *lsync, int sublink)
{
u32 val;
val = readl(lsync);
val |= (AZX_REG_ML_LSYNC_CMDSYNC << sublink);
writel(val, lsync);
}
static void hdaml_link_sync_go(u32 __iomem *lsync)
{
u32 val;
val = readl(lsync);
val |= AZX_REG_ML_LSYNC_SYNCGO;
writel(val, lsync);
}
static bool hdaml_link_check_cmdsync(u32 __iomem *lsync, u32 cmdsync_mask)
{
u32 val;
val = readl(lsync);
return !!(val & cmdsync_mask);
}
static void hdaml_link_set_lsdiid(u32 __iomem *lsdiid, int dev_num)
{
u32 val;
val = readl(lsdiid);
val |= BIT(dev_num);
writel(val, lsdiid);
}
static void hdaml_lctl_offload_enable(u32 __iomem *lctl, bool enable)
{
u32 val = readl(lctl);
if (enable)
val |= AZX_ML_LCTL_OFLEN;
else
val &= ~AZX_ML_LCTL_OFLEN;
writel(val, lctl);
}
/* END HDAML section */
static int hda_ml_alloc_h2link(struct hdac_bus *bus, int index)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
int ret;
h2link = kzalloc(sizeof(*h2link), GFP_KERNEL);
if (!h2link)
return -ENOMEM;
/* basic initialization */
hlink = &h2link->hext_link;
hlink->index = index;
hlink->bus = bus;
hlink->ml_addr = bus->mlcap + AZX_ML_BASE + (AZX_ML_INTERVAL * index);
ret = hdaml_lnk_enum(bus->dev, h2link, hlink->ml_addr, index);
if (ret < 0) {
kfree(h2link);
return ret;
}
mutex_init(&h2link->eml_lock);
list_add_tail(&hlink->list, &bus->hlink_list);
/*
* HDaudio regular links are powered-on by default, the
* refcount needs to be initialized.
*/
if (!h2link->alt)
hlink->ref_count = 1;
return 0;
}
int hda_bus_ml_init(struct hdac_bus *bus)
{
u32 link_count;
int ret;
int i;
if (!bus->mlcap)
return 0;
link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1;
dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count);
for (i = 0; i < link_count; i++) {
ret = hda_ml_alloc_h2link(bus, i);
if (ret < 0) {
hda_bus_ml_free(bus);
return ret;
}
}
return 0;
}
EXPORT_SYMBOL_NS(hda_bus_ml_init, SND_SOC_SOF_HDA_MLINK);
void hda_bus_ml_free(struct hdac_bus *bus)
{
struct hdac_ext_link *hlink, *_h;
struct hdac_ext2_link *h2link;
if (!bus->mlcap)
return;
while (!list_empty(&bus->hlink_list)) {
hlink = list_first_entry(&bus->hlink_list, struct hdac_ext_link, list);
list_for_each_entry_safe(hlink, _h, &bus->hlink_list, list) {
list_del(&hlink->list);
kfree(hlink);
h2link = hdac_ext_link_to_ext2(hlink);
mutex_destroy(&h2link->eml_lock);
kfree(h2link);
}
}
EXPORT_SYMBOL_NS(hda_bus_ml_free, SND_SOC_SOF_HDA_MLINK);
static struct hdac_ext2_link *
find_ext2_link(struct hdac_bus *bus, bool alt, int elid)
{
struct hdac_ext_link *hlink;
list_for_each_entry(hlink, &bus->hlink_list, list) {
struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
if (h2link->alt == alt && h2link->elid == elid)
return h2link;
}
return NULL;
}
int hdac_bus_eml_get_count(struct hdac_bus *bus, bool alt, int elid)
{
struct hdac_ext2_link *h2link;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return 0;
return h2link->slcount;
}
EXPORT_SYMBOL_NS(hdac_bus_eml_get_count, SND_SOC_SOF_HDA_MLINK);
void hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return;
if (!h2link->intc)
return;
hlink = &h2link->hext_link;
mutex_lock(&h2link->eml_lock);
hdaml_link_enable_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
mutex_unlock(&h2link->eml_lock);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt, SND_SOC_SOF_HDA_MLINK);
bool hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return false;
if (!h2link->intc)
return false;
hlink = &h2link->hext_link;
return hdaml_link_check_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_check_interrupt, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return 0;
if (!h2link->lss)
return 0;
hlink = &h2link->hext_link;
hdaml_link_set_syncprd(hlink->ml_addr + AZX_REG_ML_LSYNC, syncprd);
return 0;
}
EXPORT_SYMBOL_NS(hdac_bus_eml_set_syncprd_unlocked, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd)
{
return hdac_bus_eml_set_syncprd_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, syncprd);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_syncprd_unlocked, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return 0;
if (!h2link->lss)
return 0;
hlink = &h2link->hext_link;
return hdaml_link_wait_syncpu(hlink->ml_addr + AZX_REG_ML_LSYNC);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_wait_syncpu_unlocked, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus)
{
return hdac_bus_eml_wait_syncpu_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_wait_syncpu_unlocked, SND_SOC_SOF_HDA_MLINK);
void hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return;
if (!h2link->lss)
return;
hlink = &h2link->hext_link;
hdaml_link_sync_arm(hlink->ml_addr + AZX_REG_ML_LSYNC, sublink);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_sync_arm_unlocked, SND_SOC_SOF_HDA_MLINK);
void hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink)
{
hdac_bus_eml_sync_arm_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_arm_unlocked, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return 0;
if (!h2link->lss)
return 0;
hlink = &h2link->hext_link;
hdaml_link_sync_go(hlink->ml_addr + AZX_REG_ML_LSYNC);
return 0;
}
EXPORT_SYMBOL_NS(hdac_bus_eml_sync_go_unlocked, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus)
{
return hdac_bus_eml_sync_go_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_go_unlocked, SND_SOC_SOF_HDA_MLINK);
bool hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
u32 cmdsync_mask;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return 0;
if (!h2link->lss)
return 0;
hlink = &h2link->hext_link;
cmdsync_mask = GENMASK(AZX_REG_ML_LSYNC_CMDSYNC_SHIFT + h2link->slcount - 1,
AZX_REG_ML_LSYNC_CMDSYNC_SHIFT);
return hdaml_link_check_cmdsync(hlink->ml_addr + AZX_REG_ML_LSYNC,
cmdsync_mask);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_check_cmdsync_unlocked, SND_SOC_SOF_HDA_MLINK);
bool hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus)
{
return hdac_bus_eml_check_cmdsync_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_check_cmdsync_unlocked, SND_SOC_SOF_HDA_MLINK);
static int hdac_bus_eml_power_up_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
bool eml_lock)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
int ret = 0;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return -ENODEV;
if (sublink >= h2link->slcount)
return -EINVAL;
hlink = &h2link->hext_link;
if (eml_lock)
mutex_lock(&h2link->eml_lock);
if (++hlink->ref_count > 1)
goto skip_init;
ret = hdaml_link_init(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
skip_init:
if (eml_lock)
mutex_unlock(&h2link->eml_lock);
return ret;
}
int hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink)
{
return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, true);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_power_up, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
{
return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, false);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_power_up_unlocked, SND_SOC_SOF_HDA_MLINK);
static int hdac_bus_eml_power_down_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
bool eml_lock)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
int ret = 0;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return -ENODEV;
if (sublink >= h2link->slcount)
return -EINVAL;
hlink = &h2link->hext_link;
if (eml_lock)
mutex_lock(&h2link->eml_lock);
if (--hlink->ref_count > 0)
goto skip_shutdown;
ret = hdaml_link_shutdown(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
skip_shutdown:
if (eml_lock)
mutex_unlock(&h2link->eml_lock);
return ret;
}
int hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink)
{
return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, true);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_power_down, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
{
return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, false);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_power_down_unlocked, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink)
{
return hdac_bus_eml_power_up_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_up_unlocked, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink)
{
return hdac_bus_eml_power_down_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
}
EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_down_unlocked, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
if (!h2link)
return -ENODEV;
hlink = &h2link->hext_link;
mutex_lock(&h2link->eml_lock);
hdaml_link_set_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink), dev_num);
mutex_unlock(&h2link->eml_lock);
return 0;
} EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_lsdiid, SND_SOC_SOF_HDA_MLINK);
void hda_bus_ml_put_all(struct hdac_bus *bus)
{
struct hdac_ext_link *hlink;
list_for_each_entry(hlink, &bus->hlink_list, list)
snd_hdac_ext_bus_link_put(bus, hlink);
list_for_each_entry(hlink, &bus->hlink_list, list) {
struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
if (!h2link->alt)
snd_hdac_ext_bus_link_put(bus, hlink);
}
}
EXPORT_SYMBOL_NS(hda_bus_ml_put_all, SND_SOC_SOF_HDA_MLINK);
void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
{
......@@ -64,6 +761,7 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
list_for_each_entry(hlink, &bus->hlink_list, list)
writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV);
}
EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, SND_SOC_SOF_HDA_MLINK);
int hda_bus_ml_resume(struct hdac_bus *bus)
{
......@@ -72,7 +770,9 @@ int hda_bus_ml_resume(struct hdac_bus *bus)
/* power up links that were active before suspend */
list_for_each_entry(hlink, &bus->hlink_list, list) {
if (hlink->ref_count) {
struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
if (!h2link->alt && hlink->ref_count) {
ret = snd_hdac_ext_bus_link_power_up(hlink);
if (ret < 0)
return ret;
......@@ -80,10 +780,86 @@ int hda_bus_ml_resume(struct hdac_bus *bus)
}
return 0;
}
EXPORT_SYMBOL_NS(hda_bus_ml_resume, SND_SOC_SOF_HDA_MLINK);
int hda_bus_ml_suspend(struct hdac_bus *bus)
{
return snd_hdac_ext_bus_link_power_down_all(bus);
struct hdac_ext_link *hlink;
int ret;
list_for_each_entry(hlink, &bus->hlink_list, list) {
struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
if (!h2link->alt) {
ret = snd_hdac_ext_bus_link_power_down(hlink);
if (ret < 0)
return ret;
}
}
return 0;
}
EXPORT_SYMBOL_NS(hda_bus_ml_suspend, SND_SOC_SOF_HDA_MLINK);
struct mutex *hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid)
{
struct hdac_ext2_link *h2link;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return NULL;
return &h2link->eml_lock;
}
EXPORT_SYMBOL_NS(hdac_bus_eml_get_mutex, SND_SOC_SOF_HDA_MLINK);
struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus)
{
struct hdac_ext2_link *h2link;
h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_SSP);
if (!h2link)
return NULL;
return &h2link->hext_link;
}
EXPORT_SYMBOL_NS(hdac_bus_eml_ssp_get_hlink, SND_SOC_SOF_HDA_MLINK);
struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus)
{
struct hdac_ext2_link *h2link;
h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_DMIC);
if (!h2link)
return NULL;
return &h2link->hext_link;
}
EXPORT_SYMBOL_NS(hdac_bus_eml_dmic_get_hlink, SND_SOC_SOF_HDA_MLINK);
int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable)
{
struct hdac_ext2_link *h2link;
struct hdac_ext_link *hlink;
h2link = find_ext2_link(bus, alt, elid);
if (!h2link)
return -ENODEV;
if (!h2link->ofls)
return 0;
hlink = &h2link->hext_link;
mutex_lock(&h2link->eml_lock);
hdaml_lctl_offload_enable(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
mutex_unlock(&h2link->eml_lock);
return 0;
}
EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, SND_SOC_SOF_HDA_MLINK);
#endif
MODULE_LICENSE("Dual BSD/GPL");
......@@ -26,6 +26,7 @@
#include <sound/intel-nhlt.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
#include <sound/hda-mlink.h>
#include "../sof-audio.h"
#include "../sof-pci-dev.h"
#include "../ops.h"
......@@ -917,7 +918,7 @@ static int hda_init_caps(struct snd_sof_dev *sdev)
return ret;
}
hda_bus_ml_get_capabilities(bus);
hda_bus_ml_init(bus);
/* Skip SoundWire if it is not supported */
if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
......@@ -1730,3 +1731,4 @@ MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
MODULE_IMPORT_NS(SND_INTEL_SOUNDWIRE_ACPI);
MODULE_IMPORT_NS(SOUNDWIRE_INTEL_INIT);
MODULE_IMPORT_NS(SOUNDWIRE_INTEL);
MODULE_IMPORT_NS(SND_SOC_SOF_HDA_MLINK);
......@@ -763,26 +763,6 @@ static inline int hda_codec_i915_exit(struct snd_sof_dev *sdev) { return 0; }
#endif
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
void hda_bus_ml_get_capabilities(struct hdac_bus *bus);
void hda_bus_ml_free(struct hdac_bus *bus);
void hda_bus_ml_put_all(struct hdac_bus *bus);
void hda_bus_ml_reset_losidv(struct hdac_bus *bus);
int hda_bus_ml_resume(struct hdac_bus *bus);
int hda_bus_ml_suspend(struct hdac_bus *bus);
#else
static inline void hda_bus_ml_get_capabilities(struct hdac_bus *bus) { }
static inline void hda_bus_ml_free(struct hdac_bus *bus) { }
static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { }
static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { }
static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; }
static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; }
#endif /* CONFIG_SND_SOC_SOF_HDA */
/*
* Trace Control.
*/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment