Skip to content

Commit

Permalink
ASoC: SOF: Intel: hda-mlink: Add support for mic privacy in VS SHIM r…
Browse files Browse the repository at this point in the history
…egisters

New register has been introduced with PTL in the vendor specific SHIM
registers, outside of the IPs itself for microphone privacy status handling.

Via the PVCCS register the current microphone privacy status can be checked
and the interrupt generation on status change can be enabled/disabled.
The status change interrupt is routed to the owner of the interface
(DSP/host).

The PVCCS is provided for each sublink under the IP to make it possible to
control the interrupt generation per sublink.
On status change the MDSTSCHG bit needs to be cleared for all sublink of
the interface to be able to detect future changes in privacy.

The status bit (MDSTS) is volatile in all PVCCS register, it reflects the
current state of the GPIO signal.

Microphone privacy is a hardware feature (if enabled and configured that
way), the host has only passive, monitoring role.

The added functions are generic to be future proof if the mic privacy
support is extended beyond Soundwire and DMIC links.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Reviewed-by: Liam Girdwood <liam.r.girdwood@intel.com>
Reviewed-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Link: https://patch.msgid.link/20250307112816.1495-7-peter.ujfalusi@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
  • Loading branch information
Peter Ujfalusi authored and Mark Brown committed Mar 8, 2025
1 parent eea84a7 commit 0978e82
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 0 deletions.
25 changes: 25 additions & 0 deletions include/sound/hda-mlink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 */
127 changes: 127 additions & 0 deletions sound/soc/sof/intel/hda-mlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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;
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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");
Expand Down

0 comments on commit 0978e82

Please sign in to comment.