From 19fc5b90be085ebfe061d01011c433af6cc38c10 Mon Sep 17 00:00:00 2001
From: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
Date: Mon, 10 Mar 2025 15:46:04 +0200
Subject: [PATCH] ucm2: HDA: HiFi-analog/mic: Refactor the analog mic discovery

The current mic device creation works on certain machines and fails on
others. There are several places of conflicts and setups which can only
just fail, but this is mostly not an issue if the user never uses the mic,
only the speaker/headset - which, to be honest is what most of us do ;)

As an example:
The mic selection in most codecs are via enum and it is assumed to be
named 'Input Source', which is not always the case as some device uses
'Capture Source' for the control's name.
There is also different sets of mics that one can select from:
Exhibit A
numid=6,iface=MIXER,name='Input Source'
  ; type=ENUMERATED,access=rw------,values=1,items=2
  ; Item #0 'Headset Mic'
  ; Item #1 'Headphone Mic'
  : values=1

Exhibit B
numid=6,iface=MIXER,name='Capture Source'
  ; type=ENUMERATED,access=rw------,values=1,items=2
  ; Item #0 'Internal Mic'
  ; Item #1 'Headset Mic'
  : values=0

Exhibit C
numid=6,iface=MIXER,name='Capture Source'
  ; type=ENUMERATED,access=rw------,values=1,items=3
  ; Item #0 'Internal Mic'
  ; Item #1 'Headset Mic'
  ; Item #2 'Headphone Mic'
  : values=0

Exhibit D (this pushes the limits... The patch will ignore item 1)
numid=6,iface=MIXER,name='Capture Source'
  ; type=ENUMERATED,access=rw------,values=1,items=3
  ; Item #0 'Internal Mic'
  ; Item #1 'Internal Mic 1'
  ; Item #2 'Mic'
  : values=2

Other issue is that we have this 'Headphone Mic', which turned out to be
a 'Stereo Microphone in Headphone Jack', so if it is selected then the
Headphone cannot work, they conflict, they use the same rings for different
direction and purpose.

This patch aims to make the mic discovery a bit more deterministic and
pragmatic.

But even if the UCM creates the use case profiles correctly, it is still
up to UIs (KDE/GNOME/etc) to misunderstand how UCM presents the profiles,
what they mean and most of all what 'Mic1', `Mic2', etc is.
KDE presents the profiles as they are and user can selct between them to
pick the right combination of output and input.
GNOME goes further with simplification (and fails with it) and presents
'random' Configuration profiles for Output and Input, plus a device
selection and they do work in an interesting way. GNOME also have popup
for specifying the type of the plugged accessory, which does not worl at
all with UCM profiles.

But, this patch is meant for a small step to have clear rules based mic
presentation for HDA.

The expectation is that what have worked will work as it used to and what
did not worked should be detected and presented correctly.

Closes: https://github.com/alsa-project/alsa-ucm-conf/pull/526
Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
---
 ucm2/HDA/HiFi-analog.conf |  145 ---------------
 ucm2/HDA/HiFi-mic.conf    |  432 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 441 insertions(+), 136 deletions(-)
 create mode 100644 ucm2/HDA/HiFi-mic.conf

--- a/ucm2/HDA/HiFi-analog.conf
+++ b/ucm2/HDA/HiFi-analog.conf
@@ -1,55 +1,23 @@
 # Generic HDA devices for analog I/O
 
-Define.FrontMicName ""
-Define.FrontMicJack ""
-Define.DeviceFrontMic ""
 Define.LineDevice ""
 Define.hpvol "Headphone"
 Define.hpjack "Headphone Jack"
 Define.loctl "Line"
 Define.lovol "Line"
 
-If.imicname {
-	Condition {
-		Type ControlExists
-		Control "name='Internal Mic Boost Volume'"
-	}
-	True.Define {
-		FrontMicName "Internal"
-	}
-}
-
-If.fmicname {
+If.hpjack {
 	Condition {
 		Type ControlExists
-		Control "name='Front Mic Playback Switch'"
-	}
-	True.Define {
-		FrontMicName "Front"
-		FrontMicJack "Front Mic Jack"
-	}
-}
-
-If.fmic {
-	Condition {
-		Type String
-		Empty "${var:FrontMicName}"
+		Control "iface=CARD,name='Headphone Mic Jack'"
 	}
-	False {
-		If.mic2 {
-			Condition {
-				Type String
-				String1 "${var:DeviceMic}"
-				String2 "Mic2"
-			}
-			True.Define {
-				DeviceFrontMic "Mic3"
-			}
-			False.Define {
-				DeviceMic "Mic1"
-				DeviceFrontMic "Mic2"
-			}
+	True.Define.hpjack "Headphone Mic Jack"
+	False.If.front {
+		Condition {
+			Type ControlExists
+			Control "iface=CARD,name='Front Headphone Jack'"
 		}
+		True.Define.hpjack "Front Headphone Jack"
 	}
 }
 
@@ -85,21 +53,6 @@ If.hplo {
 	}
 }
 
-If.hpjack {
-	Condition {
-		Type ControlExists
-		Control "iface=CARD,name='Headphone Mic Jack'"
-	}
-	True.Define.hpjack "Headphone Mic Jack"
-	False.If.front {
-		Condition {
-			Type ControlExists
-			Control "iface=CARD,name='Front Headphone Jack'"
-		}
-		True.Define.hpjack "Front Headphone Jack"
-	}
-}
-
 SectionDevice."Headphones" {
 	Comment "Headphones"
 
@@ -175,87 +128,7 @@ If.spk {
 	}
 }
 
-If.monomic {
-	Condition {
-		Type ControlExists
-		Control "name='Input Source'"
-		ControlEnum "Headphone Mic"
-	}
-	True {
-		SectionDevice."${var:DeviceMic}" {
-			Comment "Headphones Stereo Microphone"
-
-			ConflictingDevice [
-				"Headset"
-			]
-
-			EnableSequence [
-				cset "name='Input Source' 'Headphone Mic'"
-			]
-
-			Value {
-				CapturePriority 200
-				Include.value.File "/HDA/HDA-Capture-value.conf"
-				CaptureMasterElem "Headphone Mic Boost"
-				JackControl "Headphone Mic Jack"
-			}
-		}
-
-		SectionDevice."Headset" {
-			Comment "Headset Mono Microphone"
-
-			EnableSequence [
-				cset "name='Input Source' 'Headset Mic'"
-			]
-
-			Value {
-				CapturePriority 300
-				Include.value.File "/HDA/HDA-Capture-value.conf"
-				CaptureMasterElem "Headset Mic Boost"
-				JackControl "Headphone Mic Jack"
-			}
-		}
-	}
-	False {
-		SectionDevice."${var:DeviceMic}" {
-			Comment "Headphones Stereo Microphone"
-
-			Value {
-				CapturePriority 200
-				Include.value.File "/HDA/HDA-Capture-value.conf"
-				CaptureMasterElem "Mic Boost"
-				JackControl "Mic Jack"
-			}
-		}
-	}
-}
-
-If.frontmic {
-	Condition {
-		Type String
-		Empty "${var:DeviceFrontMic}"
-	}
-	False.SectionDevice."${var:DeviceFrontMic}" {
-		Comment "${var:FrontMicName} Stereo Microphone"
-
-		ConflictingDevice [
-			"${var:DeviceMic}"
-		]
-
-		Value {
-			CapturePriority 400
-			Include.value.File "/HDA/HDA-Capture-value.conf"
-			CaptureMasterElem "${var:FrontMicName} Mic Boost"
-		}
-		If.jack {
-			Condition {
-				Type String
-				Empty "${var:FrontMicJack}"
-			}
-			False.Value.JackControl "${var:FrontMicJack}"
-		}
-	}
-}
+Include.hifi-mic.File "/HDA/HiFi-mic.conf"
 
 If.line {
 	Condition {
--- /dev/null
+++ b/ucm2/HDA/HiFi-mic.conf
@@ -0,0 +1,432 @@
+# Generic handling of analog microphones on HDA devices
+
+# Microphone related variables on entry
+# If DMIC is present:
+#   (DeviceDmic == "Mic1")
+#   DeviceMic == "Mic2"
+#
+# If DMIC is not present:
+#   DeviceMic == "Mic"
+
+# Microphone related variables as used internally
+# HDA codecs can offer input source selection (mux) via "Input Source" or
+# "Capture Source" with varying options:
+#  'Internal Mic'
+#  'Headset Mic'
+#  'Headphone Mic'
+#  'Mic'
+#
+# or there can be 'Mic' and optional 'Front Mic' available
+#
+# This leads to up to maximum of four possible input devices.
+
+Define.SourceControl ""
+Define.MicJackControl ""
+
+# DeviceMic is pre-configured
+Define.DeviceMicName ""
+Define.DeviceMicComment ""
+Define.DeviceMicPriority ""
+Define.DeviceMicJack ""
+Define.DeviceMic2 ""
+Define.DeviceMic2Name ""
+Define.DeviceMic2Comment ""
+Define.DeviceMic2Priority ""
+Define.DeviceMic2Jack ""
+Define.DeviceMic3 ""
+Define.DeviceMic3Name ""
+Define.DeviceMic3Comment ""
+Define.DeviceMic3Priority ""
+Define.DeviceMic3Jack ""
+Define.DeviceMic4 ""
+Define.DeviceMic4Name ""
+Define.DeviceMic4Comment ""
+Define.DeviceMic4Priority ""
+Define.DeviceMic4Jack ""
+
+# evaluate the microphone jack name
+If.hsmicjack {
+	Condition {
+		Type ControlExists
+		Control "iface=CARD,name='Headset Mic Jack'"
+	}
+	True.Define.MicJackControl "Headset Mic Jack"
+	False.If.hpmicjack {
+		Condition {
+			Type ControlExists
+			Control "iface=CARD,name='Headphone Mic Jack'"
+		}
+		True.Define.MicJackControl "Headphone Mic Jack"
+		False.If.hpjack {
+			Condition {
+				Type ControlExists
+				Control "iface=CARD,name='Headphone Jack'"
+			}
+			True.Define.MicJackControl "Headphone Jack"
+			False.If.micjack {
+				Condition {
+					Type ControlExists
+					Control "iface=CARD,name='Mic Jack'"
+				}
+				True.Define.MicJackControl "Mic Jack"
+			}
+		}
+	}
+}
+
+# determine the name of the source selection enum, if present
+If.inputsourcectl {
+	Condition {
+		Type ControlExists
+		Control "name='Input Source'"
+	}
+	True.Define.SourceControl "Input Source"
+	False.If.capturesourcectl {
+		Condition {
+			Type ControlExists
+			Control "name='Capture Source'"
+		}
+		True.Define.SourceControl "Capture Source"
+	}
+}
+
+# Set initial names and priorities for the SectionDevices based on the presence
+# of DMIC (DMIC is named as 'Mic1')
+If.micdevs {
+	# DeviceMic == "Mic2" indicates DMIC presence
+	Condition {
+		Type String
+		String1 "${var:DeviceMic}"
+		String2 "Mic2"
+	}
+	True.Define {
+		# DeviceMic "Mic2" - already set (Mic1 is DMIC)
+		DeviceMicPriority 200
+		DeviceMic2 "Mic3"
+		DeviceMic2Priority 300
+		DeviceMic3 "Mic4"
+		DeviceMic3Priority 400
+		DeviceMic4 "Mic5"
+		DeviceMic4Priority 500
+	}
+	False.Define {
+		DeviceMic "Mic1"
+		DeviceMicPriority 100
+		DeviceMic2 "Mic2"
+		DeviceMic2Priority 200
+		DeviceMic3 "Mic3"
+		DeviceMic3Priority 300
+		DeviceMic4 "Mic4"
+		DeviceMic4Priority 400
+	}
+}
+
+# Scan the Microphone configuration in the system and set the definitions for
+# the SectionDevice creations for them.
+If.micsetup {
+	Condition {
+		Type String
+		Empty "${var:SourceControl}"
+	}
+	False {
+		# the input selection is via ENUM, the possible types are
+		# Internal, Headset, Headphone and just 'Mic'
+
+		# Internal Mic
+		If.internalmic {
+			Condition {
+				Type ControlExists
+				Control "name='${var:SourceControl}'"
+				ControlEnum "Internal Mic"
+			}
+			True.Define {
+				DeviceMicName "Internal Mic"
+				DeviceMicComment "Internal Stereo Microphone"
+			}
+		}
+
+		# Headphone Mic (Stereo Microphone in Headphone Jack)
+		If.hpmic {
+			Condition {
+				Type ControlExists
+				Control "name='${var:SourceControl}'"
+				ControlEnum "Headphone Mic"
+			}
+			True.If.whichdev {
+				Condition {
+					Type String
+					Empty "${var:DeviceMicName}"
+				}
+				True.Define {
+					DeviceMicName "Headphone Mic"
+					DeviceMicComment "Headphones Stereo Microphone"
+					DeviceMicJack "${var:MicJackControl}"
+				}
+				False.Define {
+					DeviceMic2Name "Headphone Mic"
+					DeviceMic2Comment "Headphones Stereo Microphone"
+					DeviceMic2Jack "${var:MicJackControl}"
+				}
+			}
+		}
+
+		# Just 'Mic'
+		If.justmic {
+			Condition {
+				Type ControlExists
+				Control "name='${var:SourceControl}'"
+				ControlEnum "Mic"
+			}
+			True.If.whichdev {
+				Condition {
+					Type String
+					Empty "${var:DeviceMicName}"
+				}
+				True.Define {
+					DeviceMicName "Mic"
+					DeviceMicComment "Stereo Microphone"
+					DeviceMicJack "${var:MicJackControl}"
+				}
+				False.If.whichdev2 {
+					Condition {
+						Type String
+						Empty "${var:DeviceMic2Name}"
+					}
+					True.Define {
+						DeviceMic2Name "Mic"
+						DeviceMic2Comment "Stereo Microphone"
+						DeviceMic2Jack "${var:MicJackControl}"
+					}
+					False.Define {
+						DeviceMic3Name "Mic"
+						DeviceMic3Comment "Stereo Microphone"
+						DeviceMic3Jack "${var:MicJackControl}"
+					}
+				}
+			}
+		}
+
+		# Headset Mono Mic
+		If.hsmic {
+			Condition {
+				Type ControlExists
+				Control "name='${var:SourceControl}'"
+				ControlEnum "Headset Mic"
+			}
+			True.If.whichdev {
+				# Override the DeviceMicX (which is used as
+				# SectionDevice name) for headset mic as per
+				# UCM naming convention, reference:
+				# https://github.com/alsa-project/alsa-lib/blob/master/include/use-case.h
+				Condition {
+					Type String
+					Empty "${var:DeviceMicName}"
+				}
+				True.Define {
+					DeviceMic "Headset"
+					DeviceMicName "Headset Mic"
+					DeviceMicComment "Headset Mono Microphone"
+					DeviceMicJack "${var:MicJackControl}"
+				}
+				False.If.whichdev2 {
+					Condition {
+						Type String
+						Empty "${var:DeviceMic2Name}"
+					}
+					True.Define {
+						DeviceMic2 "Headset"
+						DeviceMic2Name "Headset Mic"
+						DeviceMic2Comment "Headset Mono Microphone"
+						DeviceMic2Jack "${var:MicJackControl}"
+					}
+					False.If.whichdev3 {
+						Condition {
+							Type String
+							Empty "${var:DeviceMic2Name}"
+						}
+						True.Define {
+							DeviceMic3 "Headset"
+							DeviceMic3Name "Headset Mic"
+							DeviceMic3Comment "Headset Mono Microphone"
+							DeviceMic3Jack "${var:MicJackControl}"
+						}
+						False.Define {
+							DeviceMic4 "Headset"
+							DeviceMic4Name "Headset Mic"
+							DeviceMic4Comment "Headset Mono Microphone"
+							DeviceMic4Jack "${var:MicJackControl}"
+						}
+					}
+
+				}
+			}
+		}
+	}
+	True {
+		# the input selection is via switches, the possible types are
+		# Mic and Front Mic
+		Define {
+			DeviceMicName "Mic"
+			DeviceMicComment "Stereo Microphone"
+			DeviceMicJack "Mic Jack"
+			DeviceMicPriority 200
+		}
+
+		If.fmic {
+			Condition {
+				Type ControlExists
+				Control "name='Front Mic Playback Switch'"
+			}
+			True.Define {
+				DeviceMic2Name "Front Mic"
+				DeviceMic2Comment "Front Stereo Microphone"
+				DeviceMic2Jack "Front Mic Jack"
+				DeviceMic2Priority 300
+			}
+		}
+	}
+}
+
+# Macro HDACaptureDevice - Create the SectionDevice for HDA inputs
+#
+# Arguments:
+# DevName - Name of the device (used as SectionDevice."${var:__DevName}"
+# MicName - Name of the Microphone
+# DevComment - Long name of the SectionDevice
+# JackName - Jack control if available
+# Priority - CapturePriority
+# ConflictCondition1 - (string) if not empty, set ConflictingDevice 1
+# ConflictDev1 - Name of the ConflictingDevice 1
+# ConflictCondition2 - (string) if not empty, set ConflictingDevice 2
+# ConflictDev2 - Name of the ConflictingDevice 2
+# ConflictCondition3 - (string) if not empty, set ConflictingDevice 3
+# ConflictDev3 - Name of the ConflictingDevice 3
+DefineMacro.HDACaptureDevice.SectionDevice."${var:__DevName}" {
+	Comment "${var:__DevComment}"
+
+	If.micsetup {
+		Condition {
+			Type String
+			Empty "${var:SourceControl}"
+		}
+		False.EnableSequence [
+			cset "name='${var:SourceControl}' '${var:__MicName}'"
+		]
+	}
+
+	Value {
+		CapturePriority "${var:__Priority}"
+		Include.value.File "/HDA/HDA-Capture-value.conf"
+		CaptureMasterElem "${var:__MicName} Boost"
+	}
+	If.jack {
+		Condition {
+			Type String
+			Empty "${var:__JackName}"
+		}
+		False.Value.JackControl "${var:__JackName}"
+	}
+
+	If.conflict1 {
+		Condition {
+			Type String
+			Empty "${var:-__ConflictCondition1}"
+		}
+		False.ConflictingDevice [
+			"${var:-__ConflictDev1}"
+		]
+	}
+	If.conflict2 {
+		Condition {
+			Type String
+			Empty "${var:-__ConflictCondition2}"
+		}
+		False.ConflictingDevice [
+			"${var:-__ConflictDev2}"
+		]
+	}
+	If.conflict3 {
+		Condition {
+			Type String
+			Empty "${var:-__ConflictCondition3}"
+		}
+		False.ConflictingDevice [
+			"${var:-__ConflictDev3}"
+		]
+	}
+
+	# Special case: The stereo mic in Heapdhone jack is in conflict with the
+	# Headphones
+	If.hpconflict {
+		Condition {
+			Type String
+			String1 "${var:__MicName}"
+			String2 "Headphone Mic"
+		}
+		True.ConflictingDevice [
+			"Headphones"
+		]
+	}
+}
+
+If.mic1 {
+	Condition {
+		Type String
+		Empty "${var:DeviceMicName}"
+	}
+	False.Macro.0.HDACaptureDevice {
+		DevName "${var:DeviceMic}"
+		MicName "${var:DeviceMicName}"
+		DevComment "${var:DeviceMicComment}"
+		JackName "${var:DeviceMicJack}"
+		Priority "${var:DeviceMicPriority}"
+		ConflictCondition1 "${var:DeviceMic2Name}" ConflictDev1 "${var:DeviceMic2}"
+		ConflictCondition2 "${var:DeviceMic3Name}" ConflictDev2 "${var:DeviceMic3}"
+		ConflictCondition3 "${var:DeviceMic4Name}" ConflictDev3 "${var:DeviceMic4}"
+	}
+}
+
+If.mic2 {
+	Condition {
+		Type String
+		Empty "${var:DeviceMic2Name}"
+	}
+	False.Macro.1.HDACaptureDevice {
+		DevName "${var:DeviceMic2}"
+		MicName "${var:DeviceMic2Name}"
+		DevComment "${var:DeviceMic2Comment}"
+		JackName "${var:DeviceMic2Jack}"
+		Priority "${var:DeviceMic2Priority}"
+		ConflictCondition1 "${var:DeviceMic3Name}" ConflictDev1 "${var:DeviceMic3}"
+		ConflictCondition2 "${var:DeviceMic4Name}" ConflictDev2 "${var:DeviceMic4}"
+	}
+}
+
+If.mic3 {
+	Condition {
+		Type String
+		Empty "${var:DeviceMic3Name}"
+	}
+	False.Macro.1.HDACaptureDevice {
+		DevName "${var:DeviceMic3}"
+		MicName "${var:DeviceMic3Name}"
+		DevComment "${var:DeviceMic3Comment}"
+		JackName "${var:DeviceMic3Jack}"
+		Priority "${var:DeviceMic3Priority}"
+		ConflictCondition1 "${var:DeviceMic4Name}" ConflictDev1 "${var:DeviceMic4}"
+	}
+}
+
+If.mic4 {
+	Condition {
+		Type String
+		Empty "${var:DeviceMic4Name}"
+	}
+	False.Macro.1.HDACaptureDevice {
+		DevName "${var:DeviceMic4}"
+		MicName "${var:DeviceMic4Name}"
+		DevComment "${var:DeviceMic4Comment}"
+		JackName "${var:DeviceMic4Jack}"
+		Priority "${var:DeviceMic4Priority}"
+	}
+}
