Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: SOF: Intel: hda-mlink: Add support for mic privacy in VS SHIM registers

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>

authored by

Peter Ujfalusi and committed by
Mark Brown
0978e820 eea84a7f

+152
+25
include/sound/hda-mlink.h
··· 62 62 63 63 int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable); 64 64 65 + /* microphone privacy specific function supported by ACE3+ architecture */ 66 + void hdac_bus_eml_set_mic_privacy_mask(struct hdac_bus *bus, bool alt, int elid, 67 + unsigned long mask); 68 + bool hdac_bus_eml_is_mic_privacy_changed(struct hdac_bus *bus, bool alt, int elid); 69 + bool hdac_bus_eml_get_mic_privacy_state(struct hdac_bus *bus, bool alt, int elid); 70 + 65 71 #else 66 72 67 73 static inline int ··· 191 185 { 192 186 return 0; 193 187 } 188 + 189 + static inline void 190 + hdac_bus_eml_set_mic_privacy_mask(struct hdac_bus *bus, bool alt, int elid, 191 + unsigned long mask) 192 + { 193 + } 194 + 195 + static inline bool 196 + hdac_bus_eml_is_mic_privacy_changed(struct hdac_bus *bus, bool alt, int elid) 197 + { 198 + return false; 199 + } 200 + 201 + static inline bool 202 + hdac_bus_eml_get_mic_privacy_state(struct hdac_bus *bus, bool alt, int elid) 203 + { 204 + return false; 205 + } 206 + 194 207 #endif /* CONFIG_SND_SOC_SOF_HDA_MLINK */
+127
sound/soc/sof/intel/hda-mlink.c
··· 16 16 17 17 #include <linux/bitfield.h> 18 18 #include <linux/module.h> 19 + #include <linux/string_choices.h> 19 20 20 21 #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK) 21 22 ··· 43 42 * @shim_offset: offset to SHIM register base 44 43 * @ip_offset: offset to IP register base 45 44 * @shim_vs_offset: offset to vendor-specific (VS) SHIM base 45 + * @mic_privacy_mask: bitmask of sublinks where mic privacy is applied 46 46 */ 47 47 struct hdac_ext2_link { 48 48 struct hdac_ext_link hext_link; ··· 67 65 u32 shim_offset; 68 66 u32 ip_offset; 69 67 u32 shim_vs_offset; 68 + 69 + unsigned long mic_privacy_mask; 70 70 }; 71 71 72 72 #define hdac_ext_link_to_ext2(h) container_of(h, struct hdac_ext2_link, hext_link) ··· 93 89 #define AZX_REG_INTEL_UAOL_SHIM_OFFSET 0x0 94 90 #define AZX_REG_INTEL_UAOL_IP_OFFSET 0x100 95 91 #define AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET 0xC00 92 + 93 + /* Microphone privacy */ 94 + #define AZX_REG_INTEL_VS_SHIM_PVCCS 0x10 95 + #define AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE BIT(0) 96 + #define AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHG BIT(8) 97 + #define AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS BIT(9) 98 + #define AZX_REG_INTEL_VS_SHIM_PVCCS_FMDIS BIT(10) 96 99 97 100 /* HDAML section - this part follows sequences in the hardware specification, 98 101 * including naming conventions and the use of the hdaml_ prefix. ··· 707 696 } 708 697 709 698 ret = hdaml_link_init(hlink->ml_addr + AZX_REG_ML_LCTL, sublink); 699 + if ((h2link->mic_privacy_mask & BIT(sublink)) && !ret) { 700 + u16 __iomem *pvccs = h2link->base_ptr + 701 + h2link->shim_vs_offset + 702 + sublink * h2link->instance_offset + 703 + AZX_REG_INTEL_VS_SHIM_PVCCS; 704 + u16 val = readw(pvccs); 705 + 706 + writew(val | AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE, pvccs); 707 + 708 + if (val & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS) 709 + dev_dbg(bus->dev, 710 + "sublink %d (%d:%d): Mic privacy is enabled\n", 711 + sublink, alt, elid); 712 + } 710 713 711 714 skip_init: 712 715 if (eml_lock) ··· 767 742 if (--h2link->sublink_ref_count[sublink] > 0) 768 743 goto skip_shutdown; 769 744 } 745 + 746 + if (h2link->mic_privacy_mask & BIT(sublink)) { 747 + u16 __iomem *pvccs = h2link->base_ptr + 748 + h2link->shim_vs_offset + 749 + sublink * h2link->instance_offset + 750 + AZX_REG_INTEL_VS_SHIM_PVCCS; 751 + 752 + writew(readw(pvccs) & ~AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHGIE, pvccs); 753 + } 754 + 770 755 ret = hdaml_link_shutdown(hlink->ml_addr + AZX_REG_ML_LCTL, sublink); 771 756 772 757 skip_shutdown: ··· 1021 986 return 0; 1022 987 } 1023 988 EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, "SND_SOC_SOF_HDA_MLINK"); 989 + 990 + void hdac_bus_eml_set_mic_privacy_mask(struct hdac_bus *bus, bool alt, int elid, 991 + unsigned long mask) 992 + { 993 + struct hdac_ext2_link *h2link; 994 + 995 + if (!mask) 996 + return; 997 + 998 + h2link = find_ext2_link(bus, alt, elid); 999 + if (!h2link) 1000 + return; 1001 + 1002 + if (__fls(mask) > h2link->slcount) { 1003 + dev_warn(bus->dev, 1004 + "%s: invalid sublink mask for %d:%d, slcount %d: %#lx\n", 1005 + __func__, alt, elid, h2link->slcount, mask); 1006 + return; 1007 + } 1008 + 1009 + dev_dbg(bus->dev, "sublink mask for %d:%d, slcount %d: %#lx\n", alt, 1010 + elid, h2link->slcount, mask); 1011 + 1012 + h2link->mic_privacy_mask = mask; 1013 + } 1014 + EXPORT_SYMBOL_NS(hdac_bus_eml_set_mic_privacy_mask, "SND_SOC_SOF_HDA_MLINK"); 1015 + 1016 + bool hdac_bus_eml_is_mic_privacy_changed(struct hdac_bus *bus, bool alt, int elid) 1017 + { 1018 + struct hdac_ext2_link *h2link; 1019 + bool changed = false; 1020 + u16 __iomem *pvccs; 1021 + int i; 1022 + 1023 + h2link = find_ext2_link(bus, alt, elid); 1024 + if (!h2link) 1025 + return false; 1026 + 1027 + /* The change in privacy state needs to be acked for each link */ 1028 + for_each_set_bit(i, &h2link->mic_privacy_mask, h2link->slcount) { 1029 + u16 val; 1030 + 1031 + if (h2link->sublink_ref_count[i] == 0) 1032 + continue; 1033 + 1034 + pvccs = h2link->base_ptr + 1035 + h2link->shim_vs_offset + 1036 + i * h2link->instance_offset + 1037 + AZX_REG_INTEL_VS_SHIM_PVCCS; 1038 + 1039 + val = readw(pvccs); 1040 + if (val & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTSCHG) { 1041 + writew(val, pvccs); 1042 + changed = true; 1043 + } 1044 + } 1045 + 1046 + return changed; 1047 + } 1048 + EXPORT_SYMBOL_NS(hdac_bus_eml_is_mic_privacy_changed, "SND_SOC_SOF_HDA_MLINK"); 1049 + 1050 + bool hdac_bus_eml_get_mic_privacy_state(struct hdac_bus *bus, bool alt, int elid) 1051 + { 1052 + struct hdac_ext2_link *h2link; 1053 + u16 __iomem *pvccs; 1054 + bool state; 1055 + int i; 1056 + 1057 + h2link = find_ext2_link(bus, alt, elid); 1058 + if (!h2link) 1059 + return false; 1060 + 1061 + for_each_set_bit(i, &h2link->mic_privacy_mask, h2link->slcount) { 1062 + if (h2link->sublink_ref_count[i] == 0) 1063 + continue; 1064 + 1065 + /* Return the privacy state from the first active link */ 1066 + pvccs = h2link->base_ptr + 1067 + h2link->shim_vs_offset + 1068 + i * h2link->instance_offset + 1069 + AZX_REG_INTEL_VS_SHIM_PVCCS; 1070 + 1071 + state = readw(pvccs) & AZX_REG_INTEL_VS_SHIM_PVCCS_MDSTS; 1072 + dev_dbg(bus->dev, "alt: %d, elid: %d: Mic privacy is %s\n", alt, 1073 + elid, str_enabled_disabled(state)); 1074 + 1075 + return state; 1076 + } 1077 + 1078 + return false; 1079 + } 1080 + EXPORT_SYMBOL_NS(hdac_bus_eml_get_mic_privacy_state, "SND_SOC_SOF_HDA_MLINK"); 1024 1081 1025 1082 #endif 1026 1083