mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-01 06:33:07 +00:00
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: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Rander Wang <rander.wang@intel.com> Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com> Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com> Reviewed-by: Takashi Iwai <tiwai@suse.de> Link: https://lore.kernel.org/r/20230404104127.5629-9-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
parent
4c2d4e446d
commit
fc7dab8ec0
2 changed files with 195 additions and 0 deletions
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue