Commit fc7dab8e authored by Pierre-Louis Bossart's avatar Pierre-Louis Bossart Committed by Mark Brown

ASoC: SOF: Intel: hda-mlink: introduce helpers for 'extended links' PM

Add helpers to program SPA/CPA bits, using a mutex to access the
shared LCTL register if required.

All links are managed with the same LCTLx.SPA bits. However there are
quite a few implementation details to be aware of:

Legacy HDaudio multi-links are powered-up when exiting reset, which
requires the ref_count to be manually set to one when initializing the
link.

Alternate links for SoundWire/DMIC/SSP need to be explicitly
powered-up before accessing the SHIM/IP/Vendor-Specific SHIM space for
each sublink. DMIC/SSP/SoundWire are all different cases with a
different device/dai/hlink relationship.

SoundWire will handle power management with the auxiliary device
resume/suspend routine. The ref_count is not necessary in this case.

The DMIC/SSP will by contrast handle the power management from DAI
.startup and .shutdown callbacks.

The SSP has a 1:1 mapping between sublink and DAI, but it's
bidirectional so the ref_count will help avoid turning off the sublink
when one of the two directions is still in use.

The DMIC has a single link but two DAIs for data generated at
different sampling frequencies, again the ref_count will make sure the
two DAIs can be used concurrently.

And last the SoundWire Intel require power-up/down and bank switch to
be handled with a lock already taken, so the 'eml_lock' is made
optional with the _unlocked versions of the helpers.

Note that the _check_power_active() implementation is similar to
previous helpers in sound/hda/ext, with sleep duration and timeout
aligned with hardware recommendations. If desired, this helper could
be modified in a second step with .e.g. readl_poll_timeout()
Signed-off-by: default avatarPierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: default avatarRander Wang <rander.wang@intel.com>
Reviewed-by: default avatarPéter Ujfalusi <peter.ujfalusi@linux.intel.com>
Reviewed-by: default avatarRanjani Sridharan <ranjani.sridharan@linux.intel.com>
Signed-off-by: default avatarPeter Ujfalusi <peter.ujfalusi@linux.intel.com>
Reviewed-by: default avatarTakashi Iwai <tiwai@suse.de>
Link: https://lore.kernel.org/r/20230404104127.5629-9-peter.ujfalusi@linux.intel.comSigned-off-by: default avatarMark Brown <broonie@kernel.org>
parent 4c2d4e44
......@@ -12,6 +12,13 @@ struct hdac_bus;
int hda_bus_ml_init(struct hdac_bus *bus);
void hda_bus_ml_free(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);
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);
......@@ -23,6 +30,31 @@ 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_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 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; }
......
......@@ -170,6 +170,68 @@ static int hdaml_lnk_enum(struct device *dev, struct hdac_ext2_link *h2link,
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)
{
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;
}
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);
}
/* END HDAML section */
static int hda_ml_alloc_h2link(struct hdac_bus *bus, int index)
......@@ -251,6 +313,107 @@ void hda_bus_ml_free(struct hdac_bus *bus)
}
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;
}
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);
void hda_bus_ml_put_all(struct hdac_bus *bus)
{
struct hdac_ext_link *hlink;
......
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