Unverified Commit 8a7e7a03 authored by Mark Brown's avatar Mark Brown
Browse files

ASoC: SOF: Intel: Add support for ACE3+ mic privacy

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

ACE3 (Panther Lake) introduced support for microphone privacy feature which
can - in hardware - mute incoming audio data based on a state of a physical
switch.
The change in the privacy state is delivered through interface IP blocks
and can only be handled by the link owner.
In Intel platforms Soundwire is for example host owned, so the interrupt
can only be handled by the host.

Since the input stream is going to be muted by hardware, the host needs to
send a message to firmware about the change in privacy so it can execute a
fade out/in to enhance user experience.

The support for microphone privacy can be queried from the HW_CONFIG data
under the INTEL_MIC_PRIVACY_CAP tuple. This is Intel specific data, the
core will pass it to platform code if the intel_configure_mic_privacy()
callback is provided.

Platform code can call sof_ipc4_mic_privacy_state_change() to send the IPC
message to the firmware on state change.
parents 1ec3f1dc 4a43c324
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -62,6 +62,12 @@ 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);

/* microphone privacy specific function supported by ACE3+ architecture */
void hdac_bus_eml_set_mic_privacy_mask(struct hdac_bus *bus, bool alt, int elid,
				       unsigned long mask);
bool hdac_bus_eml_is_mic_privacy_changed(struct hdac_bus *bus, bool alt, int elid);
bool hdac_bus_eml_get_mic_privacy_state(struct hdac_bus *bus, bool alt, int elid);

#else

static inline int
@@ -185,4 +191,23 @@ hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enabl
{
	return 0;
}

static inline void
hdac_bus_eml_set_mic_privacy_mask(struct hdac_bus *bus, bool alt, int elid,
				  unsigned long mask)
{
}

static inline bool
hdac_bus_eml_is_mic_privacy_changed(struct hdac_bus *bus, bool alt, int elid)
{
	return false;
}

static inline bool
hdac_bus_eml_get_mic_privacy_state(struct hdac_bus *bus, bool alt, int elid)
{
	return false;
}

#endif /* CONFIG_SND_SOC_SOF_HDA_MLINK */
+13 −0
Original line number Diff line number Diff line
@@ -396,6 +396,7 @@ enum sof_ipc4_base_fw_params {
	SOF_IPC4_FW_PARAM_MODULES_INFO_GET,
	SOF_IPC4_FW_PARAM_LIBRARIES_INFO_GET = 16,
	SOF_IPC4_FW_PARAM_SYSTEM_TIME = 20,
	SOF_IPC4_FW_PARAM_MIC_PRIVACY_STATE_CHANGE = 35,
};

enum sof_ipc4_fw_config_params {
@@ -446,6 +447,18 @@ struct sof_ipc4_dx_state_info {
	uint32_t dx_mask;
} __packed __aligned(4);

enum sof_ipc4_hw_config_params {
	SOF_IPC4_HW_CFG_INTEL_MIC_PRIVACY_CAPS = 11,
};

#define SOF_IPC_INTEL_MIC_PRIVACY_VERSION_PTL	1

struct sof_ipc4_intel_mic_privacy_cap {
	uint32_t version;
	uint32_t capabilities_length;
	uint32_t capabilities[];
} __packed;

/* Reply messages */

/*
+1 −1
Original line number Diff line number Diff line
@@ -34,7 +34,7 @@ snd-sof-pci-intel-icl-y := pci-icl.o icl.o
snd-sof-pci-intel-tgl-y := pci-tgl.o tgl.o
snd-sof-pci-intel-mtl-y := pci-mtl.o mtl.o
snd-sof-pci-intel-lnl-y := pci-lnl.o lnl.o
snd-sof-pci-intel-ptl-y := pci-ptl.o
snd-sof-pci-intel-ptl-y := pci-ptl.o ptl.o

obj-$(CONFIG_SND_SOC_SOF_MERRIFIELD) += snd-sof-pci-intel-tng.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_SKL) += snd-sof-pci-intel-skl.o
+127 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

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

#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)

@@ -42,6 +43,7 @@
 * @shim_offset:	offset to SHIM register base
 * @ip_offset:		offset to IP register base
 * @shim_vs_offset:	offset to vendor-specific (VS) SHIM base
 * @mic_privacy_mask:	bitmask of sublinks where mic privacy is applied
 */
struct hdac_ext2_link {
	struct hdac_ext_link hext_link;
@@ -65,6 +67,8 @@ struct hdac_ext2_link {
	u32 shim_offset;
	u32 ip_offset;
	u32 shim_vs_offset;

	unsigned long mic_privacy_mask;
};

#define hdac_ext_link_to_ext2(h) container_of(h, struct hdac_ext2_link, hext_link)
@@ -90,6 +94,13 @@ struct hdac_ext2_link {
#define AZX_REG_INTEL_UAOL_IP_OFFSET			0x100
#define AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET		0xC00

/* Microphone privacy */
#define AZX_REG_INTEL_VS_SHIM_PVCCS			0x10
#define AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE		BIT(0)
#define AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHG		BIT(8)
#define AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS		BIT(9)
#define AZX_REG_INTEL_VS_SHIM_PVCCS_FMDIS		BIT(10)

/* 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
@@ -696,6 +707,20 @@ static int hdac_bus_eml_power_up_base(struct hdac_bus *bus, bool alt, int elid,
	}

	ret = hdaml_link_init(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
	if ((h2link->mic_privacy_mask & BIT(sublink)) && !ret) {
		u16 __iomem *pvccs = h2link->base_ptr +
				     h2link->shim_vs_offset +
				     sublink * h2link->instance_offset +
				     AZX_REG_INTEL_VS_SHIM_PVCCS;
		u16 val = readw(pvccs);

		writew(val | AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE, pvccs);

		if (val & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS)
			dev_dbg(bus->dev,
				"sublink %d (%d:%d): Mic privacy is enabled\n",
				sublink, alt, elid);
	}

skip_init:
	if (eml_lock)
@@ -742,6 +767,16 @@ static int hdac_bus_eml_power_down_base(struct hdac_bus *bus, bool alt, int elid
		if (--h2link->sublink_ref_count[sublink] > 0)
			goto skip_shutdown;
	}

	if (h2link->mic_privacy_mask & BIT(sublink)) {
		u16 __iomem *pvccs = h2link->base_ptr +
				     h2link->shim_vs_offset +
				     sublink * h2link->instance_offset +
				     AZX_REG_INTEL_VS_SHIM_PVCCS;

		writew(readw(pvccs) & ~AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE, pvccs);
	}

	ret = hdaml_link_shutdown(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);

skip_shutdown:
@@ -987,6 +1022,98 @@ int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool e
}
EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, "SND_SOC_SOF_HDA_MLINK");

void hdac_bus_eml_set_mic_privacy_mask(struct hdac_bus *bus, bool alt, int elid,
				       unsigned long mask)
{
	struct hdac_ext2_link *h2link;

	if (!mask)
		return;

	h2link = find_ext2_link(bus, alt, elid);
	if (!h2link)
		return;

	if (__fls(mask) > h2link->slcount) {
		dev_warn(bus->dev,
			 "%s: invalid sublink mask for %d:%d, slcount %d: %#lx\n",
			 __func__, alt, elid, h2link->slcount, mask);
		return;
	}

	dev_dbg(bus->dev, "sublink mask for %d:%d, slcount %d: %#lx\n", alt,
		elid, h2link->slcount, mask);

	h2link->mic_privacy_mask = mask;
}
EXPORT_SYMBOL_NS(hdac_bus_eml_set_mic_privacy_mask, "SND_SOC_SOF_HDA_MLINK");

bool hdac_bus_eml_is_mic_privacy_changed(struct hdac_bus *bus, bool alt, int elid)
{
	struct hdac_ext2_link *h2link;
	bool changed = false;
	u16 __iomem *pvccs;
	int i;

	h2link = find_ext2_link(bus, alt, elid);
	if (!h2link)
		return false;

	/* The change in privacy state needs to be acked for each link */
	for_each_set_bit(i, &h2link->mic_privacy_mask, h2link->slcount) {
		u16 val;

		if (h2link->sublink_ref_count[i] == 0)
			continue;

		pvccs = h2link->base_ptr +
			h2link->shim_vs_offset +
			i * h2link->instance_offset +
			AZX_REG_INTEL_VS_SHIM_PVCCS;

		val = readw(pvccs);
		if (val & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHG) {
			writew(val, pvccs);
			changed = true;
		}
	}

	return changed;
}
EXPORT_SYMBOL_NS(hdac_bus_eml_is_mic_privacy_changed, "SND_SOC_SOF_HDA_MLINK");

bool hdac_bus_eml_get_mic_privacy_state(struct hdac_bus *bus, bool alt, int elid)
{
	struct hdac_ext2_link *h2link;
	u16 __iomem *pvccs;
	bool state;
	int i;

	h2link = find_ext2_link(bus, alt, elid);
	if (!h2link)
		return false;

	for_each_set_bit(i, &h2link->mic_privacy_mask, h2link->slcount) {
		if (h2link->sublink_ref_count[i] == 0)
			continue;

		/* Return the privacy state from the first active link */
		pvccs = h2link->base_ptr +
			h2link->shim_vs_offset +
			i * h2link->instance_offset +
			AZX_REG_INTEL_VS_SHIM_PVCCS;

		state = readw(pvccs) & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS;
		dev_dbg(bus->dev, "alt: %d, elid: %d: Mic privacy is %s\n", alt,
			elid, str_enabled_disabled(state));

		return state;
	}

	return false;
}
EXPORT_SYMBOL_NS(hdac_bus_eml_get_mic_privacy_state, "SND_SOC_SOF_HDA_MLINK");

#endif

MODULE_LICENSE("Dual BSD/GPL");
+34 −0
Original line number Diff line number Diff line
@@ -352,6 +352,27 @@ void hda_sdw_process_wakeen_common(struct snd_sof_dev *sdev)
}
EXPORT_SYMBOL_NS(hda_sdw_process_wakeen_common, "SND_SOC_SOF_INTEL_HDA_GENERIC");

static bool hda_dsp_sdw_check_mic_privacy_irq(struct snd_sof_dev *sdev)
{
	const struct sof_intel_dsp_desc *chip;

	chip = get_chip_info(sdev->pdata);
	if (chip && chip->check_mic_privacy_irq)
		return chip->check_mic_privacy_irq(sdev, true,
						   AZX_REG_ML_LEPTR_ID_SDW);

	return false;
}

static void hda_dsp_sdw_process_mic_privacy(struct snd_sof_dev *sdev)
{
	const struct sof_intel_dsp_desc *chip;

	chip = get_chip_info(sdev->pdata);
	if (chip && chip->process_mic_privacy)
		chip->process_mic_privacy(sdev, true, AZX_REG_ML_LEPTR_ID_SDW);
}

#else /* IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) */
static inline int hda_sdw_acpi_scan(struct snd_sof_dev *sdev)
{
@@ -383,6 +404,13 @@ static inline bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev)
	return false;
}

static inline bool hda_dsp_sdw_check_mic_privacy_irq(struct snd_sof_dev *sdev)
{
	return false;
}

static inline void hda_dsp_sdw_process_mic_privacy(struct snd_sof_dev *sdev) { }

#endif /* IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) */

/* pre fw run operations */
@@ -678,7 +706,13 @@ static irqreturn_t hda_dsp_interrupt_thread(int irq, void *context)

	if (hda_dsp_check_sdw_irq(sdev)) {
		trace_sof_intel_hda_irq(sdev, "sdw");

		hda_dsp_sdw_thread(irq, hdev->sdw);

		if (hda_dsp_sdw_check_mic_privacy_irq(sdev)) {
			trace_sof_intel_hda_irq(sdev, "mic privacy");
			hda_dsp_sdw_process_mic_privacy(sdev);
		}
	}

	if (hda_sdw_check_wakeen_irq(sdev)) {
Loading