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

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.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 4c2d4e44
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
@@ -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; }
+163 −0
Original line number Diff line number Diff line
@@ -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;