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

ASoC: SOF: Intel: hda-mlink: add structures to parse ALT links



Extend hdac_ext_link to store information needed for ALT
links. Follow-up patches will include more functional patches for
power-up and down.

Note that this patch suggests the use of an 'eml_lock' to serialize
access to shared registers. SoundWire-specific sequence require the
lock to be taken at a higher level, as a result the helpers added in
follow-up patches will provide 'unlocked' versions when needed.

Also note that the low-level sequences with the 'hdaml_' prefix are
taken directly from the hardware specifications - naming conventions
included. The code will be split in two, with locking and linked-list
management handled separately to avoid mixing required hardware setup
and Linux-based resource management.

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-7-peter.ujfalusi@linux.intel.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 18227585
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@ struct hdac_bus;

#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)

int hda_bus_ml_get_capabilities(struct hdac_bus *bus);
int hda_bus_ml_init(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);
@@ -20,7 +20,7 @@ int hda_bus_ml_suspend(struct hdac_bus *bus);
#else

static inline int
hda_bus_ml_get_capabilities(struct hdac_bus *bus) { return 0; }
hda_bus_ml_init(struct hdac_bus *bus) { return 0; }

static inline void hda_bus_ml_free(struct hdac_bus *bus) { }
static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { }
+216 −5
Original line number Diff line number Diff line
@@ -14,28 +14,239 @@
#include <sound/hda_register.h>
#include <sound/hda-mlink.h>

#include <linux/bitfield.h>
#include <linux/module.h>

#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)

int 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)
{
	if (bus->mlcap)
		return snd_hdac_ext_bus_get_ml_capabilities(bus);
	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;
}
EXPORT_SYMBOL_NS(hda_bus_ml_get_capabilities, SND_SOC_SOF_HDA_MLINK);

/* 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;

	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);
+1 −1
Original line number Diff line number Diff line
@@ -918,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)))