From nobody Thu Apr 2 09:14:01 2026 Received: from smtpout-02.galae.net (smtpout-02.galae.net [185.246.84.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EAE2E3BA241 for ; Mon, 30 Mar 2026 10:16:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.84.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774865784; cv=none; b=MDnJOwfge5s2DxLEjUrR7jGjzmxyV8fmHX78gtX8eaIZ02O9iqqKBljb+XGb3qj3CnpG3/QItuYK7vuRsF9KQaNWnSSz6YTpwmqYfW3VHjpGkIgnZo/22jvP5zW8a00A1Vds8hPjIdeum56YwcBiRrnHEuyComl4Uxu5kJnImtM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774865784; c=relaxed/simple; bh=7KKTvcZcMV/A1vHfqktVWzypo7vSvF011WDH/qo4T/k=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=AG74nuelfVTgE+RkPeQIirS726mLRG2CAmtgcZZtZtg9WG2Bmqbv5gVDh5wWOMdwiYfK2jJmagU4FDlZ5tpizzdLpFmVWGaQeAAKCyqLO44MZsAIduqSkSEnXOM3wx0ZjPVTD4MpWqijpsB2KGdSC4zY6WkOxEZoS03rgyP6Eik= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=qkEs735Z; arc=none smtp.client-ip=185.246.84.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="qkEs735Z" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-02.galae.net (Postfix) with ESMTPS id 74D181A3091; Mon, 30 Mar 2026 10:16:21 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 3E9AE5FFA8; Mon, 30 Mar 2026 10:16:21 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id CC3F110450F14; Mon, 30 Mar 2026 12:16:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1774865780; h=from:subject:date:message-id:to:cc:mime-version: content-transfer-encoding:in-reply-to:references; bh=jSr+qiZSp3u5YVngTgqtKy54G7uJpnLiCgqcSG9ifqI=; b=qkEs735Z4mx4toJSZEIhr4Cm2syqUpXUZlnjV2Hsd65IKdq9eiPODCLeaESxv1yqrD6tNk 7likJAxIQpACSkhP8uIDabDowbGxD4ZckUs0CM7On9EqIaal7VykrTi0q5xRXebG7OvaJi WVeINT4k/kbtYr0rQ4/Skt8qDpE8y4dBDZsn5uIC0kdYkuGcL/o5MT4pP2lR35SaDXOW5c YuHWlekWm0zRij9AGf3PqtDpmkzVr5UL+tes700qNHUpzCcdk/rnisK06P1dzup5zY3oFm wMyGtMSG2X8Zla4JS/1bkSPOnm1FV5mRy0rMNIqcSCq0vPibUOZAhEAM6lTQuw== From: Herve Codina To: Herve Codina , Liam Girdwood , Mark Brown , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Saravana Kannan , Jaroslav Kysela , Takashi Iwai Cc: linux-sound@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Christophe Leroy , Thomas Petazzoni Subject: [PATCH 1/4] of: Introduce of_property_read_s32_index() Date: Mon, 30 Mar 2026 12:16:05 +0200 Message-ID: <20260330101610.57942-2-herve.codina@bootlin.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260330101610.57942-1-herve.codina@bootlin.com> References: <20260330101610.57942-1-herve.codina@bootlin.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Last-TLS-Session-Version: TLSv1.3 Content-Type: text/plain; charset="utf-8" Signed integers can be read from single value properties using of_property_read_s32() but nothing exist to read signed integers from multi-value properties. Fix this lack adding of_property_read_s32_index(). Signed-off-by: Herve Codina --- include/linux/of.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/linux/of.h b/include/linux/of.h index be6ec4916adf..526c1ee819cd 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -1457,6 +1457,13 @@ static inline int of_property_read_s32(const struct = device_node *np, return of_property_read_u32(np, propname, (u32*) out_value); } =20 +static inline int of_property_read_s32_index(const struct device_node *np, + const char *propname, u32 index, + s32 *out_value) +{ + return of_property_read_u32_index(np, propname, index, (u32 *)out_value); +} + #define of_for_each_phandle(it, err, np, ln, cn, cc) \ for (of_phandle_iterator_init((it), (np), (ln), (cn), (cc)), \ err =3D of_phandle_iterator_next(it); \ --=20 2.53.0 From nobody Thu Apr 2 09:14:01 2026 Received: from smtpout-02.galae.net (smtpout-02.galae.net [185.246.84.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E529E3BED0F; Mon, 30 Mar 2026 10:16:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.84.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774865786; cv=none; b=Llkm94cqrdoOIIUlTR0NdcyRikxiwHE37M/MItk9mV0pFSLRqJo+pDlCfiXrNTBED5K7Wv9fryUf4dEpYDLpKAFfpafyx49z8bJcsUQ5bI+AAhlD4SRCRdpUg0x41cU2wlztSWO6TaL1O71d3YyfHCoS5aE//l5Cj6hAq+H9MnU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774865786; c=relaxed/simple; bh=TCwNmfCeTrlUtEXUeP7bfZ+xD1LwsS5HE+OE+Ze37Js=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=d3rLxFLbZU8HKIk9KAHAMGfTbtLgeRVaj8NsW0oPNKqIcqpBtN9M0zX3p1cGaWeMV8YqQ4nhwlPzP0MUztILjen+y9GPGxDoTD0POCyGL9NM7w9uaxLv79ISW1AufFDkDJXGKTEYAmir8755N96Yx57foVP1ytnIQROcOdS9+yk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=QOxdP1sg; arc=none smtp.client-ip=185.246.84.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="QOxdP1sg" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-02.galae.net (Postfix) with ESMTPS id 990C21A3093; Mon, 30 Mar 2026 10:16:23 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 6F84E5FFA8; Mon, 30 Mar 2026 10:16:23 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id 01F4310450EE3; Mon, 30 Mar 2026 12:16:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1774865782; h=from:subject:date:message-id:to:cc:mime-version: content-transfer-encoding:in-reply-to:references; bh=Rpc/cRF17z80LJZfxbko/BL1lznxbHYgAYR6pqtDvvU=; b=QOxdP1sgmdKs9m2HxApDP8fUtni3pDUHYqx/qjjkKNcagdnyDq3llCsDeZlKQh6ixppMeb hUin5j9EuurdyXKnuiOnRC7grXb43N5SvkLGfXto4zOQOT4ml21orSPmd9/nMSRGEgPKFk zcKgIqmqEBD0jx+9gn7ZL59LJ7w/ZWPPIGlKzRI9u8AZ+oi60yvtNNSG8r+JJMA0jtEBXI BXrpbrnNtUqa6GBwqdBgbZW+TNpSig1t4Au+OjtKjXgdz4t61VXe1pycaw8ZujPjAyKwtG tpjl0qpGU0hW0JhybKb4QfIcgesMGRNmfLQB7vzOaioBNGPo5/d8Pn7CmTwfFA== From: Herve Codina To: Herve Codina , Liam Girdwood , Mark Brown , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Saravana Kannan , Jaroslav Kysela , Takashi Iwai Cc: linux-sound@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Christophe Leroy , Thomas Petazzoni Subject: [PATCH 2/4] ASoC: dt-bindings: Add support for the GPIOs driven amplifier Date: Mon, 30 Mar 2026 12:16:06 +0200 Message-ID: <20260330101610.57942-3-herve.codina@bootlin.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260330101610.57942-1-herve.codina@bootlin.com> References: <20260330101610.57942-1-herve.codina@bootlin.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Last-TLS-Session-Version: TLSv1.3 Content-Type: text/plain; charset="utf-8" Some amplifiers based on analog switches and op-amps can be present in the audio path and can be driven by GPIOs in order to control their gain value, their mute and/or bypass functions. Those components needs to be viewed as audio components in order to be fully integrated in the audio path. audio-gpio-amplifier allows to consider these GPIO driven amplifiers as auxiliary audio devices. Signed-off-by: Herve Codina --- .../bindings/sound/audio-gpio-amp.yaml | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/audio-gpio-amp.= yaml diff --git a/Documentation/devicetree/bindings/sound/audio-gpio-amp.yaml b/= Documentation/devicetree/bindings/sound/audio-gpio-amp.yaml new file mode 100644 index 000000000000..15dc898f8574 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/audio-gpio-amp.yaml @@ -0,0 +1,309 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/audio-gpio-amp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Audio amplifier driven by GPIOs + +maintainers: + - Herve Codina + +description: | + Audio GPIO amplifiers are driven by GPIO in order to control the gain va= lue + of the amplifier, its mute function and/or its bypass function. + + Those amplifiers are based on discrete components (analog switches, op-a= mps + and more) where some of them, mostly analog switches, are controlled by = GPIOs + to adjust the gain value of the whole amplifier and/or to control + the mute and/or bypass function. + + For instance, the following piece of hardware is a GPIO amplifier + + +5VA + ^ + |\ | + | \ + Vin >---------------------------|+ \ + | +-------+-----> Vout + .--\/\/\/--+------------|- / | + | | | / | + v | |/ | | + GND o v | + \ GND | + gpio >-----------> \ | + o o | + | | | + | '--\/\/\/--. | + | +--\/\/\/--' + '---------------' + + A GPIO driven amplifier can work in several mode depending on the electr= onic + design. + - points defined: + The values of GPIOs used to control gain set a specific gain value + without any specific relationship between each value. For instance, + using 2 GPIOS: + 0b00 <-> -10.0 dB + 0b01 <-> +3.0 dB + 0b10 <-> 0 dB + 0b11 <-> +6.0 dB + + This can be described using the gain-points property. + + - range defined: + The values of GPIOs used to control gain set a specific gain value + following a linear dB range from a minimum dB value to a maximum dB + value. For instance, using 2 GPIOS: + 0b00 <-> -3.0 dB + 0b01 <-> 0 db + 0b10 <-> +3.0 dB + 0b11 <-> +6.0 dB + + This can be described using the gain-range property. + + - labels defined: + Some electronic design are not meant to a specific dB gain value. = In + that case it is relevant to use labels to describe them. For insta= nce, + using 2 GPIOS: + 0b00 <-> Low boost + 0b01 <-> Middle boost + 0b10 <-> High boost + 0b11 <-> Max boost + + This can be described using the gain-labels property + +properties: + compatible: + const: audio-gpio-amp + + vdd-supply: + description: Main power supply of the amplifier + + vddio-supply: + description: Power supply related to the control path + + vdda1-supply: + description: Analog power supply + + vdda2-supply: + description: Additional analog power supply + + mute-gpios: + description: GPIO to control the mute function + maxItems: 1 + + bypass-gpios: + description: GPIO to control the bypass function + maxItems: 1 + + gain-gpios: + description: | + GPIOs to control the amplifier gain + + The gain value is computed from GPIOs value from 0 to 2^N-1 with N t= he + number of GPIO described. The first GPIO described is the lsb of the= gain + value. + + For instance assuming 2 gpios + gain-gpios =3D <&gpio1 GPIO_ACTIVE_HIGH> <&gpio2 GPIO_ACTIVE_HIGH= >; + The gain value will be the following: + + gpio1 | gpio2 | gain + ------+-------+----- + 0 | 0 | 0b00 -> 0 + 1 | 0 | 0b01 -> 1 + 0 | 1 | 0b10 -> 2 + 1 | 1 | 0b11 -> 3 + ------+-------+----- + + Note: The gain value, bits set to 1 or 0, indicate the state active = (bit + set) or the state inactive (bit unset) of the related GPIO. The + physical voltage corresponding to this active/inactive state is + given by the GPIO_ACTIVE_HIGH and GPIO_ACTIVE_LOW flags. + + minItems: 1 + maxItems: 32 + + gain-points: + $ref: /schemas/types.yaml#/definitions/int32-matrix + items: + items: + - description: The GPIOs value + - description: The related amplifier gain in 0.01 dB unit + minItems: 2 + description: | + List of the GPIOs value / Gain value in dB pair defining the gain + set on each GPIOs value. + + With 2 GPIOs controlling the gain, GPIOs value can be 0, 1, 2 and 3. + Assuming that GPIOs values set the hardware gains according to the + following table: + + GPIOs | Hardware + value | amplification + ------+-------------- + 0 | -10.0 dB + 1 | +3.0 dB + 2 | 0 dB + 3 | +6.0 dB + ------+-------------- + + The description using gain points can be: + gain-points =3D <0 (-1000)>, <1 300>, <2 0>, <3 600>; + + gain-range: + $ref: /schemas/types.yaml#/definitions/int32-array + items: + - description: Gain in 0.01 dB unit when all GPIOs are inactive + - description: Gain in 0.01 dB unit when all GPIOs are active + description: | + Gains (in 0.01 dB unit) set by the extremum (minimal and maximum) va= lue + of GPIOs. The following formula must be satisfied. + + gain-range[1] - gain-range[0] + Gain =3D ------------------------------- x GPIO_value + gain-range[= 0] + 2^N - 1 + + With N, the number of GPIOs used to control the gain and Gain comput= ed in + 0.01 dB unit. + + With 2 GPIOs controlling the gain, GPIOs value can be 0, 1, 2 and 3. + Assuming that gain value set the hardware according to the following + table: + + GPIOs | Hardware 1 | Hardware 2 + value | amplification | amplification + ------+---------------+--------------- + 0 | -3.0 dB | +10.0 dB + 1 | 0 dB | +5.0 dB + 2 | +3.0 dB | 0 dB + 3 | +6.0 dB | -5.0 dB + ------+---------------+--------------- + + The description for hardware 1 using a gain range can be: + gain-range =3D <(-300) 600>; + + The description for hardware 2 using a gain range can be: + gain-range =3D <1000 (-500)>; + + gain-labels: + $ref: /schemas/types.yaml#/definitions/string-array + description: | + List of the gain labels attached to the combination of GPIOs control= ling + the gain. The first label is related to the gain value 0, the second= label + is related to the gain value 1 and so on. + + With 2 GPIOs controlling the gain, GPIOs value can be 0, 1, 2 and 3. + Assuming that gain value set the hardware according to the following + table: + + GPIOs | Hardware + value | amplification + ------+-------------- + 0 | Low + 1 | Middle + 2 | High + 3 | Max + ------+-------------- + + The description using gain labels can be: + gain-labels =3D "Low", "Middle", "High", "Max"; + +dependencies: + gain-points: [ gain-gpios ] + gain-range: [ gain-gpios ] + gain-labels: [ gain-gpios ] + +required: + - compatible + - vdd-supply + +anyOf: + - required: + - gain-gpios + - required: + - mute-gpios + - required: + - bypass-gpios + +allOf: + - $ref: dai-common.yaml# + - if: + required: + - gain-points + then: + properties: + gain-range: false + gain-labels: false + - if: + required: + - gain-range + then: + properties: + gain-points: false + gain-labels: false + - if: + required: + - gain-labels + then: + properties: + gain-points: false + gain-range: false + +unevaluatedProperties: false + +examples: + - | + #include + + /* Gain controlled by gpios */ + amplifier0 { + compatible =3D "audio-gpio-amp"; + vdd-supply =3D <®ulator>; + gain-gpios =3D <&gpio 0 GPIO_ACTIVE_HIGH>, <&gpio 1 GPIO_ACTIVE_HI= GH>; + }; + + /* Gain controlled by gpio using range */ + amplifier1 { + compatible =3D "audio-gpio-amp"; + vdd-supply =3D <®ulator>; + gain-gpios =3D <&gpio 0 GPIO_ACTIVE_HIGH>, <&gpio 1 GPIO_ACTIVE_HI= GH>; + gain-range =3D <(-300) 600>; + }; + + /* Gain controlled by gpio using points */ + amplifier2 { + compatible =3D "audio-gpio-amp"; + vdd-supply =3D <®ulator>; + gain-gpios =3D <&gpio 0 GPIO_ACTIVE_HIGH>, <&gpio 1 GPIO_ACTIVE_HI= GH>; + gain-points =3D <0 (-1000)>, <1 300>, <2 0>, <3 600>; + }; + + /* Gain controlled by gpio with labels */ + amplifier3 { + compatible =3D "audio-gpio-amp"; + vdd-supply =3D <®ulator>; + gain-gpios =3D <&gpio 0 GPIO_ACTIVE_HIGH>; + gain-labels =3D "Low", "High"; + }; + + /* A mutable amplifier without any gain control */ + amplifier4 { + compatible =3D "audio-gpio-amp"; + vdd-supply =3D <®ulator>; + mute-gpios =3D <&gpio 0 GPIO_ACTIVE_HIGH>; + }; + + /* Several supplies, gain controlled using range, mute and bypass */ + amplifier5 { + compatible =3D "audio-gpio-amp"; + vdd-supply =3D <®ulator>; + vddio-supply =3D <®ulator1>; + vdda1-supply =3D <®ulator2>; + gain-gpios =3D <&gpio 0 GPIO_ACTIVE_HIGH>, <&gpio 1 GPIO_ACTIVE_HI= GH>; + gain-range =3D <(-300) 600>; + mute-gpios =3D <&gpio 2 GPIO_ACTIVE_HIGH>; + bypass-gpios =3D <&gpio 3 GPIO_ACTIVE_HIGH>; + }; +... --=20 2.53.0 From nobody Thu Apr 2 09:14:01 2026 Received: from smtpout-03.galae.net (smtpout-03.galae.net [185.246.85.4]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BA08C3C7DE2 for ; Mon, 30 Mar 2026 10:16:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.85.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774865788; cv=none; b=Y6yLjASENTuThaIxaMWAceCXMayp9AvhRNs2C8yCXRmFDHkaqfIcRf4n5QUXIDLXcEfKUgoQ2NUztWEOCtGzhBf6hIEoH4G/4PBiqeOGaCFt6Y8UxuWaovIY9FioR65zvqJ/tp1WOKLkdNRAxtSRhpuJLNsXTTCyvgmOqBXcA4I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774865788; c=relaxed/simple; bh=xM1akr07Dz446VRZt/CCqwsrByNdgNhW0Xt1dVyYrDs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XT1I7dQSVHm6gZPnuPmM/ny2pt5OHwoyslvY2hqwtyuKvPX2yVl1F08ix9qhGeJ0azpwewOgKvApMZCM7s1G+obUx84rccK5fpTQfEWJo6TrrXhfwoJUMEcE3GRzOy4d4aIPSSFDBvni5bVuPj45Y5Z+wbqaNy6beiVjt0XyCIQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=Lae7BwgU; arc=none smtp.client-ip=185.246.85.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="Lae7BwgU" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id 7DBC24E427F4; Mon, 30 Mar 2026 10:16:25 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 523525FFA8; Mon, 30 Mar 2026 10:16:25 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id DFC7F104507FB; Mon, 30 Mar 2026 12:16:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1774865784; h=from:subject:date:message-id:to:cc:mime-version: content-transfer-encoding:in-reply-to:references; bh=bMYmHuykRe4/eZkZh8e99sq1nJrdSRFnwgcIE8gDrw8=; b=Lae7BwgUsVmFovhELhox/DC2vChZnYSC7kpxKiYll7BzFjFTX/lBov5pAcBKs/mmeA9NRz 18q8KC1UE2ds7m1vlrir4s9keUxzRS+YNRCNpSb6GdKGGHQ2ql1mfQ8XHKJuQsxOV3eAxO x37rItrVbaCmkbXzp5SEJSzpRICZ9l+ANpzqbqAMqpn5K5jdIeh6YRYfImf/JqD3hnHfva EdD9F9uU1XI0wkcUH6BJIFtZn0woiPXdN60gQX8tZilCVabu9MIIuEjussHuF0FXGBSoPA NfOTLsdNVmQNsdd6434699lG0KRGfyRFvmMQWQKipB5SV1dhG7vAKVDYCQ/TIg== From: Herve Codina To: Herve Codina , Liam Girdwood , Mark Brown , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Saravana Kannan , Jaroslav Kysela , Takashi Iwai Cc: linux-sound@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Christophe Leroy , Thomas Petazzoni Subject: [PATCH 3/4] ASoC: codecs: Add support for the GPIOs driven amplifier Date: Mon, 30 Mar 2026 12:16:07 +0200 Message-ID: <20260330101610.57942-4-herve.codina@bootlin.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260330101610.57942-1-herve.codina@bootlin.com> References: <20260330101610.57942-1-herve.codina@bootlin.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Last-TLS-Session-Version: TLSv1.3 Content-Type: text/plain; charset="utf-8" Some amplifiers driven by GPIOs can be present in the audio path. In order to be fully integrated in the audio path and to have audio mixer items available to control those amplifiers, an audio component is needed. This support allows to handle those GPIO driven amplifiers as auxiliary audio devices and so control them using audio mixer controls. Signed-off-by: Herve Codina --- sound/soc/codecs/Kconfig | 12 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/audio-gpio-amp.c | 765 ++++++++++++++++++++++++++++++ 3 files changed, 779 insertions(+) create mode 100644 sound/soc/codecs/audio-gpio-amp.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index adb3fb923be3..9c51519d6eea 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -52,6 +52,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_AK5558 imply SND_SOC_ALC5623 imply SND_SOC_ALC5632 + imply SND_SOC_AUDIO_GPIO_AMP imply SND_SOC_AUDIO_IIO_AUX imply SND_SOC_AW8738 imply SND_SOC_AW87390 @@ -648,6 +649,17 @@ config SND_SOC_ALC5632 tristate depends on I2C =20 +config SND_SOC_AUDIO_GPIO_AMP + tristate "Audio GPIO Amplifier" + select GPIOLIB + help + Enable support for GPIO amplifier. + This allows to have an amplifier driven by GPIOs in the audio path and + controlled using mixer controls. + + To compile this driver as a module, choose M here: the module + will be called snd-soc-audio-gpio-amp. + config SND_SOC_AUDIO_IIO_AUX tristate "Audio IIO Auxiliary device" depends on IIO diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3ddee5298721..e1794a3368ab 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -423,6 +423,7 @@ snd-soc-wsa883x-y :=3D wsa883x.o snd-soc-wsa884x-y :=3D wsa884x.o snd-soc-zl38060-y :=3D zl38060.o # Amp +snd-soc-audio-gpio-amp-y :=3D audio-gpio-amp.o snd-soc-max9877-y :=3D max9877.o snd-soc-max98504-y :=3D max98504.o snd-soc-simple-amplifier-y :=3D simple-amplifier.o @@ -869,6 +870,7 @@ obj-$(CONFIG_SND_SOC_WSA884X) +=3D snd-soc-wsa884x.o obj-$(CONFIG_SND_SOC_ZL38060) +=3D snd-soc-zl38060.o =20 # Amp +obj-$(CONFIG_SND_SOC_AUDIO_GPIO_AMP) +=3D snd-soc-audio-gpio-amp.o obj-$(CONFIG_SND_SOC_MAX9877) +=3D snd-soc-max9877.o obj-$(CONFIG_SND_SOC_MAX98504) +=3D snd-soc-max98504.o obj-$(CONFIG_SND_SOC_SIMPLE_AMPLIFIER) +=3D snd-soc-simple-amplifier.o diff --git a/sound/soc/codecs/audio-gpio-amp.c b/sound/soc/codecs/audio-gpi= o-amp.c new file mode 100644 index 000000000000..053501cfa5d6 --- /dev/null +++ b/sound/soc/codecs/audio-gpio-amp.c @@ -0,0 +1,765 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// GPIOs controlled amplifier ALSA SoC driver +// +// Copyright 2026 CS GROUP France +// +// Author: Herve Codina + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct audio_gpio_single { + struct gpio_desc *gpio; + bool is_inverted; + int kctrl_val; + const char *control_name; +}; + +struct audio_gpio_point { + u32 gpio_val; + int gain_db; +}; + +struct audio_gpio_points { + unsigned int nb_points; + struct audio_gpio_point *tab_points; +}; + +struct audio_gpio_range { + int min_db; + int max_db; +}; + +struct audio_gpio_labels { + unsigned int nb_labels; + const char **tab_labels; +}; + +enum audio_gpio_mode { + AUDIO_GPIO_MODE_NONE, + AUDIO_GPIO_MODE_RANGE, + AUDIO_GPIO_MODE_LABELS, + AUDIO_GPIO_MODE_POINTS, +}; + +struct audio_gpio_multi { + struct gpio_descs *gpios; + bool is_inverted; + u32 kctrl_val; + u32 kctrl_max; + const char *control_name; + unsigned int *tlv_array; + enum audio_gpio_mode mode; + union { + struct audio_gpio_range range; + struct audio_gpio_points points; + struct audio_gpio_labels labels; + }; +}; + +struct audio_gpio_amp { + struct audio_gpio_single mute; + struct audio_gpio_single bypass; + struct audio_gpio_multi gain; +}; + +static const struct snd_soc_dapm_widget audio_gpio_amp_dapm_widgets[] =3D { + SND_SOC_DAPM_INPUT("IN"), + SND_SOC_DAPM_OUTPUT("OUT"), + SND_SOC_DAPM_PGA("PGA", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_REGULATOR_SUPPLY("vdd", 0, 0), +}; + +static const struct snd_soc_dapm_route audio_gpio_amp_dapm_routes[] =3D { + { "PGA", NULL, "IN" }, + { "PGA", NULL, "vdd" }, + { "OUT", NULL, "PGA" }, +}; + +static int audio_gpio_amp_single_kctrl_write_gpio(struct audio_gpio_single= *single, + int kctrl_val) +{ + int gpio_val; + + gpio_val =3D single->is_inverted ? !kctrl_val : kctrl_val; + + return gpiod_set_value_cansleep(single->gpio, gpio_val); +} + +static int audio_gpio_amp_single_kctrl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->count =3D 1; + uinfo->value.integer.min =3D 0; + uinfo->value.integer.max =3D 1; + uinfo->type =3D SNDRV_CTL_ELEM_TYPE_BOOLEAN; + return 0; +} + +static int audio_gpio_amp_single_kctrl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct audio_gpio_single *single =3D (struct audio_gpio_single *)kcontrol= ->private_value; + + ucontrol->value.integer.value[0] =3D single->kctrl_val; + + return 0; +} + +static int audio_gpio_amp_single_kctrl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct audio_gpio_single *single =3D (struct audio_gpio_single *)kcontrol= ->private_value; + int kctrl_val; + int err; + + kctrl_val =3D ucontrol->value.integer.value[0]; + + if (kctrl_val =3D=3D single->kctrl_val) + return 0; + + err =3D audio_gpio_amp_single_kctrl_write_gpio(single, kctrl_val); + if (err) + return err; + + single->kctrl_val =3D kctrl_val; + + return 1; /* The value changed */ +} + +static int audio_gpio_amp_single_add_kcontrol(struct snd_soc_component *co= mponent, + struct audio_gpio_single *single) +{ + struct snd_kcontrol_new control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D single->control_name, + .info =3D audio_gpio_amp_single_kctrl_info, + .get =3D audio_gpio_amp_single_kctrl_get, + .put =3D audio_gpio_amp_single_kctrl_put, + .private_value =3D (unsigned long)single, + }; + int ret; + + /* Be consistent between single->kctrl_val value and the GPIO value */ + ret =3D audio_gpio_amp_single_kctrl_write_gpio(single, single->kctrl_val); + if (ret) + return ret; + + return snd_soc_add_component_controls(component, &control, 1); +} + +static int audio_gpio_amp_multi_kctrl_write_gpios(struct audio_gpio_multi = *multi, + u32 kctrl_val) +{ + DECLARE_BITMAP(bm, 32); + u32 gpio_val; + + if (multi->mode =3D=3D AUDIO_GPIO_MODE_POINTS) { + if (kctrl_val >=3D multi->points.nb_points) + return -EINVAL; + + gpio_val =3D multi->points.tab_points[kctrl_val].gpio_val; + } else { + if (kctrl_val > multi->kctrl_max) + return -EINVAL; + + gpio_val =3D multi->is_inverted ? + multi->kctrl_max - kctrl_val : + kctrl_val; + } + + bitmap_from_arr32(bm, &gpio_val, multi->gpios->ndescs); + + return gpiod_multi_set_value_cansleep(multi->gpios, bm); +} + +static int audio_gpio_amp_multi_kctrl_int_info(struct snd_kcontrol *kcontr= ol, + struct snd_ctl_elem_info *uinfo) +{ + struct audio_gpio_multi *multi =3D (struct audio_gpio_multi *)kcontrol->p= rivate_value; + + uinfo->count =3D 1; + uinfo->value.integer.min =3D 0; + if (multi->mode =3D=3D AUDIO_GPIO_MODE_POINTS) + uinfo->value.integer.max =3D multi->points.nb_points - 1; + else + uinfo->value.integer.max =3D multi->kctrl_max; + uinfo->type =3D SNDRV_CTL_ELEM_TYPE_INTEGER; + return 0; +} + +static int audio_gpio_amp_multi_kctrl_int_get(struct snd_kcontrol *kcontro= l, + struct snd_ctl_elem_value *ucontrol) +{ + struct audio_gpio_multi *multi =3D (struct audio_gpio_multi *)kcontrol->p= rivate_value; + + ucontrol->value.integer.value[0] =3D multi->kctrl_val; + return 0; +} + +static int audio_gpio_amp_multi_kctrl_int_put(struct snd_kcontrol *kcontro= l, + struct snd_ctl_elem_value *ucontrol) +{ + struct audio_gpio_multi *multi =3D (struct audio_gpio_multi *)kcontrol->p= rivate_value; + u32 kctrl_val; + int ret; + + kctrl_val =3D ucontrol->value.integer.value[0]; + + if (kctrl_val =3D=3D multi->kctrl_val) + return 0; + + ret =3D audio_gpio_amp_multi_kctrl_write_gpios(multi, kctrl_val); + if (ret) + return ret; + + multi->kctrl_val =3D kctrl_val; + + return 1; /* The value changed */ +} + +static int audio_gpio_amp_multi_kctrl_enum_info(struct snd_kcontrol *kcont= rol, + struct snd_ctl_elem_info *uinfo) +{ + struct audio_gpio_multi *multi =3D (struct audio_gpio_multi *)kcontrol->p= rivate_value; + + return snd_ctl_enum_info(uinfo, 1, multi->labels.nb_labels, + multi->labels.tab_labels); +} + +static int audio_gpio_amp_multi_kctrl_enum_get(struct snd_kcontrol *kcontr= ol, + struct snd_ctl_elem_value *ucontrol) +{ + struct audio_gpio_multi *multi =3D (struct audio_gpio_multi *)kcontrol->p= rivate_value; + + ucontrol->value.enumerated.item[0] =3D multi->kctrl_val; + return 0; +} + +static int audio_gpio_amp_multi_kctrl_enum_put(struct snd_kcontrol *kcontr= ol, + struct snd_ctl_elem_value *ucontrol) +{ + struct audio_gpio_multi *multi =3D (struct audio_gpio_multi *)kcontrol->p= rivate_value; + u32 kctrl_val; + int ret; + + kctrl_val =3D ucontrol->value.enumerated.item[0]; + + if (kctrl_val =3D=3D multi->kctrl_val) + return 0; + + ret =3D audio_gpio_amp_multi_kctrl_write_gpios(multi, kctrl_val); + if (ret) + return ret; + + multi->kctrl_val =3D kctrl_val; + + return 1; /* The value changed */ +} + +static int *audio_gpio_amp_alloc_tlv_range(const struct audio_gpio_range *= range) +{ + DECLARE_TLV_DB_MINMAX(tmp, range->min_db, range->max_db); + + return kmemdup(tmp, sizeof(tmp), GFP_KERNEL); +} + +static int *audio_gpio_amp_alloc_tlv_points(struct audio_gpio_points *poin= ts) +{ + unsigned int *tlv; + unsigned int *t; + unsigned int i; + + tlv =3D kzalloc_objs(*tlv, 2 + points->nb_points * 6, GFP_KERNEL); + if (!tlv) + return NULL; + + t =3D tlv; + + /* Fill first TLV */ + *t++ =3D SNDRV_CTL_TLVT_DB_RANGE; /* Tag */ + *t++ =3D points->nb_points * 6 * sizeof(*tlv); /* Len */ + /* points are sorted from lower to higher value */ + for (i =3D 0; i < points->nb_points; i++) { + /* Fill item i */ + *t++ =3D i; /* min */ + *t++ =3D i; /* max */ + *t++ =3D SNDRV_CTL_TLVT_DB_MINMAX; /* Tag */ + *t++ =3D 2 * sizeof(*tlv); /* Len */ + *t++ =3D points->tab_points[i].gain_db; /* min_dB */ + *t++ =3D points->tab_points[i].gain_db; /* max_dB */ + } + + return tlv; +} + +static int audio_gpio_amp_multi_add_kcontrol(struct snd_soc_component *com= ponent, + struct audio_gpio_multi *multi) +{ + struct snd_kcontrol_new control =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .name =3D multi->control_name, + .info =3D audio_gpio_amp_multi_kctrl_int_info, + .get =3D audio_gpio_amp_multi_kctrl_int_get, + .put =3D audio_gpio_amp_multi_kctrl_int_put, + .private_value =3D (unsigned long)multi, + }; + int ret; + + switch (multi->mode) { + case AUDIO_GPIO_MODE_RANGE: + multi->tlv_array =3D audio_gpio_amp_alloc_tlv_range(&multi->range); + if (!multi->tlv_array) + return -ENOMEM; + + control.access =3D SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE; + control.tlv.p =3D multi->tlv_array; + break; + + case AUDIO_GPIO_MODE_POINTS: + multi->tlv_array =3D audio_gpio_amp_alloc_tlv_points(&multi->points); + if (!multi->tlv_array) + return -ENOMEM; + + control.access =3D SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE; + control.tlv.p =3D multi->tlv_array; + break; + + case AUDIO_GPIO_MODE_LABELS: + /* Use enumerated values */ + control.info =3D audio_gpio_amp_multi_kctrl_enum_info; + control.get =3D audio_gpio_amp_multi_kctrl_enum_get; + control.put =3D audio_gpio_amp_multi_kctrl_enum_put; + break; + + case AUDIO_GPIO_MODE_NONE: + /* Already set control configuration is enough */ + break; + + default: + return -EINVAL; + } + + /* Be consistent between multi->kctrl_val value and the GPIOs value */ + ret =3D audio_gpio_amp_multi_kctrl_write_gpios(multi, multi->kctrl_val); + if (ret) + return ret; + + return snd_soc_add_component_controls(component, &control, 1); +} + +struct audio_gpio_amp_supply { + const char *prop_name; + const struct snd_soc_dapm_widget dapm_widget; + const struct snd_soc_dapm_route dapm_route; +}; + +static const struct audio_gpio_amp_supply audio_gpio_amp_supplies[] =3D { + { + .prop_name =3D "vddio-supply", + .dapm_widget =3D SND_SOC_DAPM_REGULATOR_SUPPLY("vddio", 0, 0), + .dapm_route =3D { "PGA", NULL, "vddio" }, + }, { + .prop_name =3D "vdda1-supply", + .dapm_widget =3D SND_SOC_DAPM_REGULATOR_SUPPLY("vdda1", 0, 0), + .dapm_route =3D { "PGA", NULL, "vdda1" }, + }, { + .prop_name =3D "vdda2-supply", + .dapm_widget =3D SND_SOC_DAPM_REGULATOR_SUPPLY("vdda2", 0, 0), + .dapm_route =3D { "PGA", NULL, "vdda2" }, + }, + { /* End of list */} +}; + +static int audio_gpio_amp_add_power_supplies(struct snd_soc_component *com= ponent) +{ + struct snd_soc_dapm_context *dapm =3D snd_soc_component_to_dapm(component= ); + const struct audio_gpio_amp_supply *supply; + struct device *dev =3D component->dev; + int ret; + + supply =3D audio_gpio_amp_supplies; + do { + if (!of_property_present(dev->of_node, supply->prop_name)) + continue; + + ret =3D snd_soc_dapm_new_controls(dapm, &supply->dapm_widget, 1); + if (ret) { + dev_err(dev, "Failed to add control for '%s' (%d)\n", + supply->prop_name, ret); + return ret; + } + ret =3D snd_soc_dapm_add_routes(dapm, &supply->dapm_route, 1); + if (ret) { + dev_err(dev, "Failed to add route for '%s' (%d)\n", + supply->prop_name, ret); + return ret; + } + } while ((++supply)->prop_name); + + return 0; +} + +static int audio_gpio_amp_component_probe(struct snd_soc_component *compon= ent) +{ + struct audio_gpio_amp *gpio_amp =3D snd_soc_component_get_drvdata(compone= nt); + int ret; + + ret =3D audio_gpio_amp_add_power_supplies(component); + if (ret) + return ret; + + if (gpio_amp->mute.gpio) { + /* + * The name of the GPIO used is mute. According to this name, 1 + * means muted and 0 means un-muted. + * + * An inversion is expected by ALSA. Indeed from ALSA point of + * view, 1 means 'on' (un-muted) and 0 means 'off' (muted). + */ + gpio_amp->mute.is_inverted =3D true; + gpio_amp->mute.kctrl_val =3D 1; /* Un-muted */ + ret =3D audio_gpio_amp_single_add_kcontrol(component, &gpio_amp->mute); + if (ret) + return ret; + } + + if (gpio_amp->bypass.gpio) { + ret =3D audio_gpio_amp_single_add_kcontrol(component, &gpio_amp->bypass); + if (ret) + return ret; + } + + if (gpio_amp->gain.gpios) { + ret =3D audio_gpio_amp_multi_add_kcontrol(component, &gpio_amp->gain); + if (ret) + return ret; + } + + return 0; +} + +static void audio_gpio_amp_component_remove(struct snd_soc_component *comp= onent) +{ + struct audio_gpio_amp *gpio_amp =3D snd_soc_component_get_drvdata(compone= nt); + + kfree(gpio_amp->gain.tlv_array); + gpio_amp->gain.tlv_array =3D NULL; +} + +static const struct snd_soc_component_driver audio_gpio_amp_component_driv= er =3D { + .dapm_widgets =3D audio_gpio_amp_dapm_widgets, + .num_dapm_widgets =3D ARRAY_SIZE(audio_gpio_amp_dapm_widgets), + .dapm_routes =3D audio_gpio_amp_dapm_routes, + .num_dapm_routes =3D ARRAY_SIZE(audio_gpio_amp_dapm_routes), + .probe =3D audio_gpio_amp_component_probe, + .remove =3D audio_gpio_amp_component_remove, +}; + +static int audio_gpio_amp_parse_labels(struct device *dev, + struct audio_gpio_multi *multi, + const char *labels_property) +{ + struct audio_gpio_labels *labels =3D &multi->labels; + struct device_node *np =3D dev->of_node; + int ret; + + ret =3D of_property_count_strings(np, labels_property); + if (ret <=3D 0) + return ret; + + labels->nb_labels =3D ret; + if (labels->nb_labels > (1 << multi->gpios->ndescs)) + return -EINVAL; + + labels->tab_labels =3D devm_kcalloc(dev, labels->nb_labels, + sizeof(*labels->tab_labels), + GFP_KERNEL); + if (!labels->tab_labels) + return -ENOMEM; + + multi->kctrl_max =3D labels->nb_labels - 1; + multi->kctrl_val =3D 0; + multi->is_inverted =3D false; + + return of_property_read_string_array(np, labels_property, labels->tab_lab= els, + labels->nb_labels); +} + +static int audio_gpio_amp_parse_range(struct device *dev, + struct audio_gpio_multi *multi, + const char *range_property) +{ + struct audio_gpio_range *range =3D &multi->range; + struct device_node *np =3D dev->of_node; + s32 tmp; + int ret; + + ret =3D of_property_read_s32_index(np, range_property, 0, &tmp); + if (ret) + return ret; + range->min_db =3D tmp; + + ret =3D of_property_read_s32_index(np, range_property, 1, &tmp); + if (ret) + return ret; + range->max_db =3D tmp; + + multi->kctrl_max =3D (1 << multi->gpios->ndescs) - 1; + multi->kctrl_val =3D 0; + multi->is_inverted =3D false; + + if (range->min_db > range->max_db) { + /* Invert range */ + swap(range->min_db, range->max_db); + multi->is_inverted =3D 1; + + /* + * When the range is inverted, choose to have the initial + * amplification set to max_db (i.e. all GPIOs inactive). + */ + multi->kctrl_val =3D multi->kctrl_max; + } + + return 0; +} + +static int audio_gpio_amp_cmp_points(const void *a, const void *b) +{ + const struct audio_gpio_point *a_point =3D a; + const struct audio_gpio_point *b_point =3D b; + + return a_point->gain_db - b_point->gain_db; +} + +static int audio_gpio_amp_parse_points(struct device *dev, + struct audio_gpio_multi *multi, + const char *points_property) +{ + struct audio_gpio_points *points =3D &multi->points; + struct device_node *np =3D dev->of_node; + struct audio_gpio_point first_point; + unsigned int max_gpio_val; + unsigned int i; + int ret; + u32 u; + s32 s; + + max_gpio_val =3D (1 << multi->gpios->ndescs) - 1; + + ret =3D of_property_count_u32_elems(np, points_property); + if (ret <=3D 0) + return ret; + + if (ret % 2) + return -EINVAL; + + points->nb_points =3D ret / 2; + if (points->nb_points > max_gpio_val + 1) + return -EINVAL; + + points->tab_points =3D devm_kcalloc(dev, points->nb_points, + sizeof(*points->tab_points), + GFP_KERNEL); + if (!points->tab_points) + return -ENOMEM; + + for (i =3D 0; i < points->nb_points; i++) { + /* Gpio value */ + ret =3D of_property_read_u32_index(np, points_property, + i * 2, &u); + if (ret) + return ret; + if (u > max_gpio_val) + return -EINVAL; + + points->tab_points[i].gpio_val =3D u; + + /* Gain value */ + ret =3D of_property_read_s32_index(np, points_property, + i * 2 + 1, &s); + if (ret) + return ret; + + points->tab_points[i].gain_db =3D s; + } + + first_point =3D points->tab_points[0]; + + /* Sort the tab_point array by gain_db value */ + sort(points->tab_points, points->nb_points, sizeof(*points->tab_points), + audio_gpio_amp_cmp_points, NULL); + + multi->kctrl_max =3D points->nb_points - 1; + multi->is_inverted =3D false; + + /* + * multi->kctrl_val is the index in tab_points. + * + * Choose to have the initial amplification value set to the first item + * available in the tab_points array before sorting. + * + * This first point before sorting has been identified. Search for it in + * the sorted array in order to set the multi->kctrl_val initial value. + */ + for (i =3D 0; i < points->nb_points; i++) { + if (points->tab_points[i].gpio_val =3D=3D first_point.gpio_val && + points->tab_points[i].gain_db =3D=3D first_point.gain_db) { + multi->kctrl_val =3D i; + break; + } + } + + return 0; +} + +static int audio_gpio_amp_parse_multi_gpio(struct device *dev, + struct audio_gpio_multi *multi, + const char *gpios_property, + const char *range_property, + const char *points_property, + const char *labels_property) +{ + struct device_node *np =3D dev->of_node; + int ret; + + /* Start with the value 0 (GPIO inactive). Can be changed later */ + multi->is_inverted =3D false; + multi->kctrl_val =3D 0; + multi->gpios =3D devm_gpiod_get_array_optional(dev, gpios_property, GPIOD= _OUT_LOW); + if (IS_ERR(multi->gpios)) + return dev_err_probe(dev, PTR_ERR(multi->gpios), + "Failed to get '%s' gpios\n", + gpios_property); + if (!multi->gpios) + return 0; + + if (multi->gpios->ndescs > 32) + return dev_err_probe(dev, -EINVAL, + "Number of '%s' gpios limited to 32\n", + labels_property); + + /* Set default value for the kctrl_max. Can be changed later */ + multi->kctrl_max =3D (1 << multi->gpios->ndescs) - 1; + + multi->mode =3D AUDIO_GPIO_MODE_NONE; + if (of_property_present(np, points_property)) { + ret =3D audio_gpio_amp_parse_points(dev, multi, points_property); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to parse '%s'\n", + points_property); + multi->mode =3D AUDIO_GPIO_MODE_POINTS; + + } else if (of_property_present(np, range_property)) { + ret =3D audio_gpio_amp_parse_range(dev, multi, range_property); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to parse '%s'\n", + range_property); + multi->mode =3D AUDIO_GPIO_MODE_RANGE; + + } else if (of_property_present(np, labels_property)) { + ret =3D audio_gpio_amp_parse_labels(dev, multi, labels_property); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to parse '%s'\n", + labels_property); + + multi->mode =3D AUDIO_GPIO_MODE_LABELS; + } + + return 0; +} + +static int audio_gpio_amp_parse_single_gpio(struct device *dev, + struct audio_gpio_single *single, + const char *gpio_property) +{ + /* Start with the inactive value */ + single->is_inverted =3D false; + single->kctrl_val =3D 0; + single->gpio =3D devm_gpiod_get_optional(dev, gpio_property, GPIOD_OUT_LO= W); + if (IS_ERR(single->gpio)) + return dev_err_probe(dev, PTR_ERR(single->gpio), + "Failed to get '%s' gpio\n", + gpio_property); + return 0; +} + +static int audio_gpio_amp_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct audio_gpio_amp *gpio_amp; + int ret; + + gpio_amp =3D devm_kzalloc(dev, sizeof(*gpio_amp), GFP_KERNEL); + if (!gpio_amp) + return -ENOMEM; + + ret =3D audio_gpio_amp_parse_single_gpio(dev, &gpio_amp->mute, "mute"); + if (ret) + return ret; + + ret =3D audio_gpio_amp_parse_single_gpio(dev, &gpio_amp->bypass, "bypass"= ); + if (ret) + return ret; + + ret =3D audio_gpio_amp_parse_multi_gpio(dev, &gpio_amp->gain, "gain", + "gain-range", "gain-points", "gain-labels"); + if (ret) + return ret; + + /* Set controls name */ + gpio_amp->gain.control_name =3D "Volume"; + gpio_amp->mute.control_name =3D "Switch"; + gpio_amp->bypass.control_name =3D "Bypass Switch"; + + if (gpio_amp->gain.mode =3D=3D AUDIO_GPIO_MODE_LABELS) { + /* + * The gain widget control will use enumerated values. + * + * Having just "Voltage" and "Switch" widget names with + * enumerated values and boolean value can confuse ALSA in terms + * of possible values (strings). + * + * Make things clear and avoid the just "Switch" name in that + * case. + */ + gpio_amp->mute.control_name =3D "Out Switch"; + } + + platform_set_drvdata(pdev, gpio_amp); + + return devm_snd_soc_register_component(dev, + &audio_gpio_amp_component_driver, + NULL, 0); +} + +static const struct of_device_id audio_gpio_amp_ids[] =3D { + { .compatible =3D "audio-gpio-amp" }, + { } +}; +MODULE_DEVICE_TABLE(of, audio_gpio_amp_ids); + +static struct platform_driver audio_gpio_amp_driver =3D { + .driver =3D { + .name =3D "audio-gpio-amp", + .of_match_table =3D of_match_ptr(audio_gpio_amp_ids), + }, + .probe =3D audio_gpio_amp_probe, +}; +module_platform_driver(audio_gpio_amp_driver); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("ASoC GPIOs controlled amplifier driver"); +MODULE_LICENSE("GPL"); --=20 2.53.0 From nobody Thu Apr 2 09:14:01 2026 Received: from smtpout-02.galae.net (smtpout-02.galae.net [185.246.84.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5D4E33C7E16 for ; Mon, 30 Mar 2026 10:16:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.84.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774865789; cv=none; b=m/ntdRmwzTL1XH7nB7wjU0lFALvVLjLD72nUsmlZTOXlRngY5dXFMnXbK/VX3bPikeenqpijJ8eoAajFdWosCIOFCBlaoY5bC5jQgBPB1MIfRMWQ37eWKFi2+vkq0Q9magAAazpb7xJ6XucKASvEUYIHZbvFztlWPbUiUFlvLOg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774865789; c=relaxed/simple; bh=Jk8KbZlmKq8ZEAXNtnD15PPIxXzpCD4uHwokEAZ/GLY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KWa+bnDRVWCEsjADU6fM19093KUIQLcPgse1qMEtudNk0Pg5xQmlJwD1tz88R1adBtXv7LuAv0JG/K/S0PfXEtvLdKVka02kfJUQ+F98eBHECs3+mgrXFlvXAggfRVIIE4XpD56agz3nb0cAaFP4+cJUbX6j9fXAqx/LQ7381QM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=i0FV1/RZ; arc=none smtp.client-ip=185.246.84.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="i0FV1/RZ" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-02.galae.net (Postfix) with ESMTPS id 282C41A3091; Mon, 30 Mar 2026 10:16:27 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id F35255FFA8; Mon, 30 Mar 2026 10:16:26 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id C2CA310450EF5; Mon, 30 Mar 2026 12:16:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1774865786; h=from:subject:date:message-id:to:cc:mime-version: content-transfer-encoding:in-reply-to:references; bh=BqD0ex0T8aAZm0wxVLLFoEpGMUaXrtSX0K4TG3luA3Y=; b=i0FV1/RZtqg0RZR5cxypfFm/IVAU03WN3LRg3SNU9/MYuAaq+2oCQGwp3+H0UdPf9NKxAJ IaIMAGKdQOJ40HX1xkVZG2SfE2uWInSFIdWWRS0/+CWt9lv6/49CH5wMCwaF2RahdptYm3 /8e/8O48elxwqZHIAGZeItWHvUj4CwLUFoxxMzIgGRP7+9ANSWDc0kKjQYwOLXI5EYvV73 rkkJnFsDOCViSIHCcPVanC9anZ9Mq6eFVootTozX7L4o16UGaGYrzO02UNTHXUUp4nSlxn /EW8xmskQ+YWAmu1USSu62re05X/OjoOOW/XR/LwfnaGwa37rapv7GuTWrvkOA== From: Herve Codina To: Herve Codina , Liam Girdwood , Mark Brown , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Saravana Kannan , Jaroslav Kysela , Takashi Iwai Cc: linux-sound@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Christophe Leroy , Thomas Petazzoni Subject: [PATCH 4/4] MAINTAINERS: Add the ASoC gpio amplifier entry Date: Mon, 30 Mar 2026 12:16:08 +0200 Message-ID: <20260330101610.57942-5-herve.codina@bootlin.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260330101610.57942-1-herve.codina@bootlin.com> References: <20260330101610.57942-1-herve.codina@bootlin.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Last-TLS-Session-Version: TLSv1.3 Content-Type: text/plain; charset="utf-8" After contributing the component, add myself as the maintainer for the ASoC gpio amplifier component. Signed-off-by: Herve Codina --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 55af015174a5..2546eb81f233 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24765,6 +24765,13 @@ F: include/sound/dmaengine_pcm.h F: sound/core/pcm_dmaengine.c F: sound/soc/soc-generic-dmaengine-pcm.c =20 +SOUND - SOC LAYER / AUDIO GPIO AMPLIFIER +M: Herve Codina +L: linux-sound@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/sound/audio-gpio-amp.yaml +F: sound/soc/codecs/audio-gpio-amp.c + SOUND - SOC LAYER / DYNAMIC AUDIO POWER MANAGEMENT (ASoC) M: Liam Girdwood M: Mark Brown --=20 2.53.0