Alsa-devel
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
August 2023
- 168 participants
- 403 discussions
18 Aug '23
These two patches add an ACPI HID and update the way the platform-
specific firmware identifier is extracted from the ACPI.
CHANGES SINCE V1:
- Rebased to apply on v6.4 and v6.5.
- Change kstrdup() to devm_kstrdup()
To apply on v6.6 a 1-line change is needed:
static int cs35l56_get_firmware_uid(struct cs35l56_private *cs35l56)
{
- struct device *dev = cs35l56->dev;
+ struct device *dev = cs35l56->base.dev;
Maciej Strozek (1):
ASoC: cs35l56: Read firmware uuid from a device property instead of
_SUB
Simon Trimmer (1):
ASoC: cs35l56: Add an ACPI match table
sound/soc/codecs/cs35l56-i2c.c | 9 +++++++++
sound/soc/codecs/cs35l56-spi.c | 9 +++++++++
sound/soc/codecs/cs35l56.c | 31 ++++++++++++-------------------
3 files changed, 30 insertions(+), 19 deletions(-)
--
2.30.2
2
3
These two patches add an ACPI HID and update the way the platform-
specific firmware identifier is extracted from the ACPI.
Maciej Strozek (1):
ASoC: cs35l56: Read firmware uuid from a device property instead of
_SUB
Simon Trimmer (1):
ASoC: cs35l56: Add an ACPI match table
sound/soc/codecs/cs35l56-i2c.c | 9 +++++++++
sound/soc/codecs/cs35l56-spi.c | 9 +++++++++
sound/soc/codecs/cs35l56.c | 31 ++++++++++++++-----------------
3 files changed, 32 insertions(+), 17 deletions(-)
--
2.30.2
2
6
[PATCH v2 01/11] ASoC: dt-bindings: Add common sound card properties
by Krzysztof Kozlowski 18 Aug '23
by Krzysztof Kozlowski 18 Aug '23
18 Aug '23
Almost every board machine / sound cards has "audio-routing" and "model"
properties, so move them to common schema to have only one definition of
these properties.
For amlogic,gx-sound-card, drop the minItems: 2 from the
"audio-routing", because any limit here - lower or upper- is rather
meaningless. This will also fix `dtbs_check` warnings like:
meson-gxm-s912-libretech-pc.dtb: sound: audio-routing: ['AU2 INL', 'ACODEC LOLN', 'AU2 INR', 'ACODEC LORN', '7J4-14 LEFT', 'AU2 OUTL', '7J4-11 RIGHT', 'AU2 OUTR'] is too long
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno(a)collabora.com>
Reviewed-by: Rob Herring <robh(a)kernel.org>
Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski(a)linaro.org>
---
Changes in v2:
1. Drop minItems/maxItems from amlogic,gx-sound-card
---
.../sound/amlogic,axg-sound-card.yaml | 15 +++--------
.../bindings/sound/amlogic,gx-sound-card.yaml | 17 +++---------
.../devicetree/bindings/sound/fsl,rpmsg.yaml | 17 +++---------
.../bindings/sound/google,sc7180-trogdor.yaml | 17 +++---------
.../sound/google,sc7280-herobrine.yaml | 17 +++---------
.../bindings/sound/imx-audio-card.yaml | 18 +++----------
.../bindings/sound/sound-card-common.yaml | 27 +++++++++++++++++++
7 files changed, 50 insertions(+), 78 deletions(-)
create mode 100644 Documentation/devicetree/bindings/sound/sound-card-common.yaml
diff --git a/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.yaml b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.yaml
index bf1234550343..5db718e4d0e7 100644
--- a/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.yaml
+++ b/Documentation/devicetree/bindings/sound/amlogic,axg-sound-card.yaml
@@ -9,6 +9,9 @@ title: Amlogic AXG sound card
maintainers:
- Jerome Brunet <jbrunet(a)baylibre.com>
+allOf:
+ - $ref: sound-card-common.yaml#
+
properties:
compatible:
const: amlogic,axg-sound-card
@@ -17,23 +20,12 @@ properties:
$ref: /schemas/types.yaml#/definitions/phandle-array
description: list of auxiliary devices
- audio-routing:
- $ref: /schemas/types.yaml#/definitions/non-unique-string-array
- description:
- A list of the connections between audio components. Each entry is a
- pair of strings, the first being the connection's sink, the second
- being the connection's source.
-
audio-widgets:
$ref: /schemas/types.yaml#/definitions/non-unique-string-array
description:
A list off component DAPM widget. Each entry is a pair of strings,
the first being the widget type, the second being the widget name
- model:
- $ref: /schemas/types.yaml#/definitions/string
- description: User specified audio sound card name
-
patternProperties:
"^dai-link-[0-9]+$":
type: object
@@ -108,7 +100,6 @@ patternProperties:
- sound-dai
required:
- - model
- dai-link-0
unevaluatedProperties: false
diff --git a/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml b/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml
index b358fd601ed3..d4277d342e69 100644
--- a/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml
+++ b/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml
@@ -9,6 +9,9 @@ title: Amlogic GX sound card
maintainers:
- Jerome Brunet <jbrunet(a)baylibre.com>
+allOf:
+ - $ref: sound-card-common.yaml#
+
properties:
compatible:
items:
@@ -18,14 +21,6 @@ properties:
$ref: /schemas/types.yaml#/definitions/phandle-array
description: list of auxiliary devices
- audio-routing:
- $ref: /schemas/types.yaml#/definitions/non-unique-string-array
- minItems: 2
- description: |-
- A list of the connections between audio components. Each entry is a
- pair of strings, the first being the connection's sink, the second
- being the connection's source.
-
audio-widgets:
$ref: /schemas/types.yaml#/definitions/non-unique-string-array
minItems: 2
@@ -33,10 +28,6 @@ properties:
A list off component DAPM widget. Each entry is a pair of strings,
the first being the widget type, the second being the widget name
- model:
- $ref: /schemas/types.yaml#/definitions/string
- description: User specified audio sound card name
-
patternProperties:
"^dai-link-[0-9]+$":
type: object
@@ -86,7 +77,7 @@ required:
- model
- dai-link-0
-additionalProperties: false
+unevaluatedProperties: false
examples:
- |
diff --git a/Documentation/devicetree/bindings/sound/fsl,rpmsg.yaml b/Documentation/devicetree/bindings/sound/fsl,rpmsg.yaml
index 6df0e03a1d4b..188f38baddec 100644
--- a/Documentation/devicetree/bindings/sound/fsl,rpmsg.yaml
+++ b/Documentation/devicetree/bindings/sound/fsl,rpmsg.yaml
@@ -17,6 +17,9 @@ description: |
such as SAI, MICFIL, .etc through building rpmsg channels between
Cortex-A and Cortex-M.
+allOf:
+ - $ref: sound-card-common.yaml#
+
properties:
compatible:
enum:
@@ -27,10 +30,6 @@ properties:
- fsl,imx8ulp-rpmsg-audio
- fsl,imx93-rpmsg-audio
- model:
- $ref: /schemas/types.yaml#/definitions/string
- description: User specified audio sound card name
-
clocks:
items:
- description: Peripheral clock for register access
@@ -66,13 +65,6 @@ properties:
$ref: /schemas/types.yaml#/definitions/phandle
description: The phandle to a node of audio codec
- audio-routing:
- $ref: /schemas/types.yaml#/definitions/non-unique-string-array
- description: |
- A list of the connections between audio components. Each entry is a
- pair of strings, the first being the connection's sink, the second
- being the connection's source.
-
fsl,enable-lpa:
$ref: /schemas/types.yaml#/definitions/flag
description: enable low power audio path.
@@ -101,9 +93,8 @@ properties:
required:
- compatible
- - model
-additionalProperties: false
+unevaluatedProperties: false
examples:
- |
diff --git a/Documentation/devicetree/bindings/sound/google,sc7180-trogdor.yaml b/Documentation/devicetree/bindings/sound/google,sc7180-trogdor.yaml
index ba5b7728cf33..bac940553965 100644
--- a/Documentation/devicetree/bindings/sound/google,sc7180-trogdor.yaml
+++ b/Documentation/devicetree/bindings/sound/google,sc7180-trogdor.yaml
@@ -13,23 +13,15 @@ maintainers:
description:
This binding describes the SC7180 sound card which uses LPASS for audio.
+allOf:
+ - $ref: sound-card-common.yaml#
+
properties:
compatible:
enum:
- google,sc7180-trogdor
- google,sc7180-coachz
- audio-routing:
- $ref: /schemas/types.yaml#/definitions/non-unique-string-array
- description:
- A list of the connections between audio components. Each entry is a
- pair of strings, the first being the connection's sink, the second
- being the connection's source.
-
- model:
- $ref: /schemas/types.yaml#/definitions/string
- description: User specified audio sound card name
-
"#address-cells":
const: 1
@@ -86,11 +78,10 @@ patternProperties:
required:
- compatible
- - model
- "#address-cells"
- "#size-cells"
-additionalProperties: false
+unevaluatedProperties: false
examples:
diff --git a/Documentation/devicetree/bindings/sound/google,sc7280-herobrine.yaml b/Documentation/devicetree/bindings/sound/google,sc7280-herobrine.yaml
index 0b1a01a4c14e..ec4b6e547ca6 100644
--- a/Documentation/devicetree/bindings/sound/google,sc7280-herobrine.yaml
+++ b/Documentation/devicetree/bindings/sound/google,sc7280-herobrine.yaml
@@ -13,22 +13,14 @@ maintainers:
description:
This binding describes the SC7280 sound card which uses LPASS for audio.
+allOf:
+ - $ref: sound-card-common.yaml#
+
properties:
compatible:
enum:
- google,sc7280-herobrine
- audio-routing:
- $ref: /schemas/types.yaml#/definitions/non-unique-string-array
- description:
- A list of the connections between audio components. Each entry is a
- pair of strings, the first being the connection's sink, the second
- being the connection's source.
-
- model:
- $ref: /schemas/types.yaml#/definitions/string
- description: User specified audio sound card name
-
"#address-cells":
const: 1
@@ -97,11 +89,10 @@ patternProperties:
required:
- compatible
- - model
- "#address-cells"
- "#size-cells"
-additionalProperties: false
+unevaluatedProperties: false
examples:
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-card.yaml b/Documentation/devicetree/bindings/sound/imx-audio-card.yaml
index b6f5d486600e..f7ad5ea2491e 100644
--- a/Documentation/devicetree/bindings/sound/imx-audio-card.yaml
+++ b/Documentation/devicetree/bindings/sound/imx-audio-card.yaml
@@ -9,23 +9,14 @@ title: NXP i.MX audio sound card.
maintainers:
- Shengjiu Wang <shengjiu.wang(a)nxp.com>
+allOf:
+ - $ref: sound-card-common.yaml#
+
properties:
compatible:
enum:
- fsl,imx-audio-card
- model:
- $ref: /schemas/types.yaml#/definitions/string
- description: User specified audio sound card name
-
- audio-routing:
- $ref: /schemas/types.yaml#/definitions/non-unique-string-array
- description:
- A list of the connections between audio components. Each entry is a
- pair of strings, the first being the connection's sink, the second
- being the connection's source. Valid names could be power supplies,
- MicBias of codec and the jacks on the board.
-
patternProperties:
".*-dai-link$":
description:
@@ -84,9 +75,8 @@ patternProperties:
required:
- compatible
- - model
-additionalProperties: false
+unevaluatedProperties: false
examples:
- |
diff --git a/Documentation/devicetree/bindings/sound/sound-card-common.yaml b/Documentation/devicetree/bindings/sound/sound-card-common.yaml
new file mode 100644
index 000000000000..3a941177f684
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/sound-card-common.yaml
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/sound-card-common.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Board Sound Card Common Properties
+
+maintainers:
+ - Mark Brown <broonie(a)kernel.org>
+
+properties:
+ audio-routing:
+ $ref: /schemas/types.yaml#/definitions/non-unique-string-array
+ description: |
+ A list of the connections between audio components. Each entry is a
+ pair of strings, the first being the connection's sink, the second
+ being the connection's source.
+
+ model:
+ $ref: /schemas/types.yaml#/definitions/string
+ description: User specified audio sound card name
+
+required:
+ - model
+
+additionalProperties: true
--
2.34.1
4
20
18 Aug '23
Hi,
Almost every board machine / sound cards has "audio-routing" and "model"
properties, so we can make things simpler by introducing one common
binding. It is also expected that given property has only one definition.
If the patchset looks good, then the next steps will be:
1. Convert more vendor audio-routing properties to audio-routing,
2. Add dai-links children to common binding.
Best regards,
Krzysztof
Krzysztof Kozlowski (12):
ASoC: dt-bindings: amlogic,gx-sound-card: correct maxItems constraints
ASoC: dt-bindings: Add common sound card properties
ASoC: dt-bindings: mediatek,mt8188-mt6359: use common sound card
ASoC: dt-bindings: samsung,aries-wm8994: use common sound card
ASoC: dt-bindings: samsung,midas-audio: use common sound card
ASoC: dt-bindings: samsung,odroid: use common sound card
ASoC: dt-bindings: samsung,tm2: use common sound card
ASoC: samsung: odroid: use of_property_present to check for property
ASoC: samsung: aries_wm8994: parse audio-routing
ASoC: samsung: midas_wm1811: parse audio-routing
ASoC: samsung: odroid: parse audio-routing
ASoC: samsung: tm2_wm5110: parse audio-routing
.../sound/amlogic,axg-sound-card.yaml | 15 +++--------
.../bindings/sound/amlogic,gx-sound-card.yaml | 16 +++++------
.../devicetree/bindings/sound/fsl,rpmsg.yaml | 17 +++---------
.../bindings/sound/google,sc7180-trogdor.yaml | 17 +++---------
.../sound/google,sc7280-herobrine.yaml | 17 +++---------
.../bindings/sound/imx-audio-card.yaml | 18 +++----------
.../sound/mediatek,mt8188-mt6359.yaml | 17 +++++-------
.../bindings/sound/samsung,aries-wm8994.yaml | 16 +++++------
.../bindings/sound/samsung,midas-audio.yaml | 16 +++++------
.../bindings/sound/samsung,odroid.yaml | 14 +++++-----
.../bindings/sound/samsung,tm2.yaml | 16 +++++------
.../bindings/sound/sound-card-common.yaml | 27 +++++++++++++++++++
sound/soc/samsung/aries_wm8994.c | 10 ++++---
sound/soc/samsung/midas_wm1811.c | 10 ++++---
sound/soc/samsung/odroid.c | 15 ++++++-----
sound/soc/samsung/tm2_wm5110.c | 10 ++++---
16 files changed, 119 insertions(+), 132 deletions(-)
create mode 100644 Documentation/devicetree/bindings/sound/sound-card-common.yaml
--
2.34.1
5
30
[PATCH v1] ALSA: hda: cs35l41: Support systems with missing _DSD properties
by Stefan Binding 18 Aug '23
by Stefan Binding 18 Aug '23
18 Aug '23
Some systems using CS35L41 with HDA were released without some
required _DSD properties in ACPI. To support these special cases,
add an api to configure the correct properties for systems with
this issue.
This initial commit moves the no _DSD support for Lenovo
Legion Laptops (CLSA0100, CLSA0101) into a new framework which
can be extended to support additional laptops in the future.
Signed-off-by: Stefan Binding <sbinding(a)opensource.cirrus.com>
---
sound/pci/hda/Makefile | 2 +-
sound/pci/hda/cs35l41_hda.c | 65 ++++++-------------------
sound/pci/hda/cs35l41_hda.h | 1 +
sound/pci/hda/cs35l41_hda_property.c | 73 ++++++++++++++++++++++++++++
sound/pci/hda/cs35l41_hda_property.h | 18 +++++++
5 files changed, 108 insertions(+), 51 deletions(-)
create mode 100644 sound/pci/hda/cs35l41_hda_property.c
create mode 100644 sound/pci/hda/cs35l41_hda_property.h
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile
index c6e6509e7b8e..5506255be895 100644
--- a/sound/pci/hda/Makefile
+++ b/sound/pci/hda/Makefile
@@ -28,7 +28,7 @@ snd-hda-codec-via-objs := patch_via.o
snd-hda-codec-hdmi-objs := patch_hdmi.o hda_eld.o
# side codecs
-snd-hda-scodec-cs35l41-objs := cs35l41_hda.o
+snd-hda-scodec-cs35l41-objs := cs35l41_hda.o cs35l41_hda_property.o
snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o
snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o
snd-hda-scodec-cs35l56-objs := cs35l56_hda.o
diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index 825e551be9bb..f9b77353c266 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -19,6 +19,7 @@
#include "hda_component.h"
#include "cs35l41_hda.h"
#include "hda_cs_dsp_ctl.h"
+#include "cs35l41_hda_property.h"
#define CS35L41_FIRMWARE_ROOT "cirrus/"
#define CS35L41_PART "cs35l41"
@@ -1315,8 +1316,7 @@ static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41)
return cs35l41_hda_channel_map(cs35l41->dev, 0, NULL, 1, &hw_cfg->spk_pos);
}
-static int cs35l41_get_speaker_id(struct device *dev, int amp_index,
- int num_amps, int fixed_gpio_id)
+int cs35l41_get_speaker_id(struct device *dev, int amp_index, int num_amps, int fixed_gpio_id)
{
struct gpio_desc *speaker_id_desc;
int speaker_id = -ENODEV;
@@ -1370,49 +1370,6 @@ static int cs35l41_get_speaker_id(struct device *dev, int amp_index,
return speaker_id;
}
-/*
- * Device CLSA010(0/1) doesn't have _DSD so a gpiod_get by the label reset won't work.
- * And devices created by serial-multi-instantiate don't have their device struct
- * pointing to the correct fwnode, so acpi_dev must be used here.
- * And devm functions expect that the device requesting the resource has the correct
- * fwnode.
- */
-static int cs35l41_no_acpi_dsd(struct cs35l41_hda *cs35l41, struct device *physdev, int id,
- const char *hid)
-{
- struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg;
-
- /* check I2C address to assign the index */
- cs35l41->index = id == 0x40 ? 0 : 1;
- cs35l41->channel_index = 0;
- cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH);
- cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, 2);
- hw_cfg->spk_pos = cs35l41->index;
- hw_cfg->gpio2.func = CS35L41_INTERRUPT;
- hw_cfg->gpio2.valid = true;
- hw_cfg->valid = true;
-
- if (strncmp(hid, "CLSA0100", 8) == 0) {
- hw_cfg->bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH;
- } else if (strncmp(hid, "CLSA0101", 8) == 0) {
- hw_cfg->bst_type = CS35L41_EXT_BOOST;
- hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH;
- hw_cfg->gpio1.valid = true;
- } else {
- /*
- * Note: CLSA010(0/1) are special cases which use a slightly different design.
- * All other HIDs e.g. CSC3551 require valid ACPI _DSD properties to be supported.
- */
- dev_err(cs35l41->dev, "Error: ACPI _DSD Properties are missing for HID %s.\n", hid);
- hw_cfg->valid = false;
- hw_cfg->gpio1.valid = false;
- hw_cfg->gpio2.valid = false;
- return -EINVAL;
- }
-
- return 0;
-}
-
static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id)
{
struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg;
@@ -1438,12 +1395,17 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
sub = NULL;
cs35l41->acpi_subsystem_id = sub;
+ ret = cs35l41_add_dsd_properties(cs35l41, physdev, id, hid);
+ if (!ret) {
+ dev_info(cs35l41->dev, "Using extra _DSD properties, bypassing _DSD in ACPI\n");
+ goto put_physdev;
+ }
+
property = "cirrus,dev-index";
ret = device_property_count_u32(physdev, property);
- if (ret <= 0) {
- ret = cs35l41_no_acpi_dsd(cs35l41, physdev, id, hid);
- goto err_put_physdev;
- }
+ if (ret <= 0)
+ goto err;
+
if (ret > ARRAY_SIZE(values)) {
ret = -EINVAL;
goto err;
@@ -1533,7 +1495,10 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
err:
dev_err(cs35l41->dev, "Failed property %s: %d\n", property, ret);
-err_put_physdev:
+ hw_cfg->valid = false;
+ hw_cfg->gpio1.valid = false;
+ hw_cfg->gpio2.valid = false;
+put_physdev:
put_device(physdev);
return ret;
diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h
index bdb35f3be68a..b93bf762976e 100644
--- a/sound/pci/hda/cs35l41_hda.h
+++ b/sound/pci/hda/cs35l41_hda.h
@@ -83,5 +83,6 @@ extern const struct dev_pm_ops cs35l41_hda_pm_ops;
int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq,
struct regmap *regmap);
void cs35l41_hda_remove(struct device *dev);
+int cs35l41_get_speaker_id(struct device *dev, int amp_index, int num_amps, int fixed_gpio_id);
#endif /*__CS35L41_HDA_H__*/
diff --git a/sound/pci/hda/cs35l41_hda_property.c b/sound/pci/hda/cs35l41_hda_property.c
new file mode 100644
index 000000000000..673f23257a09
--- /dev/null
+++ b/sound/pci/hda/cs35l41_hda_property.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// CS35L41 ALSA HDA Property driver
+//
+// Copyright 2023 Cirrus Logic, Inc.
+//
+// Author: Stefan Binding <sbinding(a)opensource.cirrus.com>
+
+#include <linux/gpio/consumer.h>
+#include <linux/string.h>
+#include "cs35l41_hda_property.h"
+
+/*
+ * Device CLSA010(0/1) doesn't have _DSD so a gpiod_get by the label reset won't work.
+ * And devices created by serial-multi-instantiate don't have their device struct
+ * pointing to the correct fwnode, so acpi_dev must be used here.
+ * And devm functions expect that the device requesting the resource has the correct
+ * fwnode.
+ */
+static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, int id,
+ const char *hid)
+{
+ struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg;
+
+ /* check I2C address to assign the index */
+ cs35l41->index = id == 0x40 ? 0 : 1;
+ cs35l41->channel_index = 0;
+ cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH);
+ cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, 2);
+ hw_cfg->spk_pos = cs35l41->index;
+ hw_cfg->gpio2.func = CS35L41_INTERRUPT;
+ hw_cfg->gpio2.valid = true;
+ hw_cfg->valid = true;
+
+ if (strcmp(hid, "CLSA0100") == 0) {
+ hw_cfg->bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH;
+ } else if (strcmp(hid, "CLSA0101") == 0) {
+ hw_cfg->bst_type = CS35L41_EXT_BOOST;
+ hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH;
+ hw_cfg->gpio1.valid = true;
+ }
+
+ return 0;
+}
+
+struct cs35l41_prop_model {
+ const char *hid;
+ const char *ssid;
+ int (*add_prop)(struct cs35l41_hda *cs35l41, struct device *physdev, int id,
+ const char *hid);
+};
+
+const struct cs35l41_prop_model cs35l41_prop_model_table[] = {
+ { "CLSA0100", NULL, lenovo_legion_no_acpi },
+ { "CLSA0101", NULL, lenovo_legion_no_acpi },
+ {}
+};
+
+int cs35l41_add_dsd_properties(struct cs35l41_hda *cs35l41, struct device *physdev, int id,
+ const char *hid)
+{
+ const struct cs35l41_prop_model *model;
+
+ for (model = cs35l41_prop_model_table; model->hid > 0; model++) {
+ if (!strcmp(model->hid, hid) &&
+ (!model->ssid ||
+ (cs35l41->acpi_subsystem_id &&
+ !strcmp(model->ssid, cs35l41->acpi_subsystem_id))))
+ return model->add_prop(cs35l41, physdev, id, hid);
+ }
+
+ return -ENOENT;
+}
diff --git a/sound/pci/hda/cs35l41_hda_property.h b/sound/pci/hda/cs35l41_hda_property.h
new file mode 100644
index 000000000000..fd834042e2fd
--- /dev/null
+++ b/sound/pci/hda/cs35l41_hda_property.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * CS35L41 ALSA HDA Property driver
+ *
+ * Copyright 2023 Cirrus Logic, Inc.
+ *
+ * Author: Stefan Binding <sbinding(a)opensource.cirrus.com>
+ */
+
+#ifndef CS35L41_HDA_PROP_H
+#define CS35L41_HDA_PROP_H
+
+#include <linux/device.h>
+#include "cs35l41_hda.h"
+
+int cs35l41_add_dsd_properties(struct cs35l41_hda *cs35l41, struct device *physdev, int id,
+ const char *hid);
+#endif /* CS35L41_HDA_PROP_H */
--
2.34.1
3
2
Changes in v4:
- fix typo of authors and copyright
- refine with bitfield helper for readability [2/6]
- refine with human readable error message [3/6]
- use "mt7986-wm8960-sound" as compatible string [4/6] [5/6]
- refine properties based on reviewer's suggestions [5/6]
- remove assigned-clocks and assigned-clocks-parents [6/6]
- constrain clocks per variants [6/6]
Changes in v3:
- merge clk api to mt7986-afe-pcm.c, remove [2/7] in v2
- refine based on reviewer's suggestions [1/6] [2/6]
- fix the comment format, move in clk api, and simplify based on reviewer's suggestions [3/6]
- remove redundant prefix in dt-binding [6/6]
Changes in v2:
- v1 title: [PATCH 0/7] ASoC: mediatek: Add support for MT79xx SoC
- add missing maintainers
- rename mt79xx to mt7986 in all files
- use clk bulk api in mt7986-afe-clk.c [2/7]
- refine mt79xx-afe-pcm.c based on reviewer's suggestions [4/7]
- refine dt-binding files based on reviewer's suggestions [6/7] [7/7]
- transpose [3/7] and [4/7] in v1 to fix test build errors
Maso Huang (6):
ASoC: mediatek: mt7986: add common header
ASoC: mediatek: mt7986: support etdm in platform driver
ASoC: mediatek: mt7986: add platform driver
ASoC: mediatek: mt7986: add machine driver with wm8960
ASoC: dt-bindings: mediatek,mt7986-wm8960: add mt7986-wm8960 document
ASoC: dt-bindings: mediatek,mt7986-afe: add audio afe document
.../bindings/sound/mediatek,mt7986-afe.yaml | 160 +++++
.../sound/mediatek,mt7986-wm8960.yaml | 67 ++
sound/soc/mediatek/Kconfig | 20 +
sound/soc/mediatek/Makefile | 1 +
sound/soc/mediatek/mt7986/Makefile | 9 +
sound/soc/mediatek/mt7986/mt7986-afe-common.h | 49 ++
sound/soc/mediatek/mt7986/mt7986-afe-pcm.c | 622 ++++++++++++++++++
sound/soc/mediatek/mt7986/mt7986-dai-etdm.c | 411 ++++++++++++
sound/soc/mediatek/mt7986/mt7986-reg.h | 196 ++++++
sound/soc/mediatek/mt7986/mt7986-wm8960.c | 196 ++++++
10 files changed, 1731 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/mediatek,mt7986-afe.yaml
create mode 100644 Documentation/devicetree/bindings/sound/mediatek,mt7986-wm8960.yaml
create mode 100644 sound/soc/mediatek/mt7986/Makefile
create mode 100644 sound/soc/mediatek/mt7986/mt7986-afe-common.h
create mode 100644 sound/soc/mediatek/mt7986/mt7986-afe-pcm.c
create mode 100644 sound/soc/mediatek/mt7986/mt7986-dai-etdm.c
create mode 100644 sound/soc/mediatek/mt7986/mt7986-reg.h
create mode 100644 sound/soc/mediatek/mt7986/mt7986-wm8960.c
--
2.18.0
4
9
17 Aug '23
Add source file and header file for tas2783 driver.
Update Kconfig and Makefile for tas2783 driver.
Signed-off-by: Baojun Xu <baojun.xu(a)ti.com>
---
Change in v2:
- change all registers access to regmap.
- change header file include order.
- remove tas2783_sdw_read().
- remove regmap_bus and relative function.
- change custom access to regmap.
- return if get calibration data from EFI failed.
- remove mutex protect for null function.
- remove tasdevice_startup() as no actions needed.
- remove sysclk function as no action needed.
- remove tasdevice_mute().
- remove crc8 look up table generate.
- remove first_hw_init flag.
- change some define name to TAS2783_XXX.
- remove SMS_HTONL(), use le_to_cpup().
- remove enum channel define.
- change struct and element name for FW.
- remove global address elements.
- remove irq and gpio in structure.
- remove tasdevice structure.
---
sound/soc/codecs/Kconfig | 12 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/tas2783-sdw.c | 872 +++++++++++++++++++++++++++++++++
sound/soc/codecs/tas2783.h | 113 +++++
4 files changed, 999 insertions(+)
create mode 100644 sound/soc/codecs/tas2783-sdw.c
create mode 100644 sound/soc/codecs/tas2783.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index c2de4ee72183..a4d334faca5e 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -240,6 +240,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_TAS2781_COMLIB
imply SND_SOC_TAS2781_FMWLIB
imply SND_SOC_TAS2781_I2C
+ imply SND_SOC_TAS2783
imply SND_SOC_TAS5086
imply SND_SOC_TAS571X
imply SND_SOC_TAS5720
@@ -1758,6 +1759,17 @@ config SND_SOC_TAS2781_I2C
algo coefficient setting, for one, two or even multiple TAS2781
chips.
+config SND_SOC_TAS2783
+ tristate "Texas Instruments TAS2783 speaker amplifier (sdw)"
+ depends on SOUNDWIRE
+ select REGMAP
+ select CRC32_SARWATE
+ help
+ Enable support for Texas Instruments TAS2783 Smart Amplifier
+ Digital input mono Class-D and DSP-inside audio power amplifiers.
+ Note the TAS2783 driver implements a flexible and configurable
+ algo coff setting, for one, two, even multiple TAS2783 chips.
+
config SND_SOC_TAS5086
tristate "Texas Instruments TAS5086 speaker amplifier"
depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index b48a9a323b84..3298dfa3b9d5 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -276,6 +276,7 @@ snd-soc-tas2770-objs := tas2770.o
snd-soc-tas2781-comlib-objs := tas2781-comlib.o
snd-soc-tas2781-fmwlib-objs := tas2781-fmwlib.o
snd-soc-tas2781-i2c-objs := tas2781-i2c.o
+snd-soc-tas2783-objs := tas2783-sdw.o
snd-soc-tfa9879-objs := tfa9879.o
snd-soc-tfa989x-objs := tfa989x.o
snd-soc-tlv320adc3xxx-objs := tlv320adc3xxx.o
@@ -648,6 +649,7 @@ obj-$(CONFIG_SND_SOC_TAS2780) += snd-soc-tas2780.o
obj-$(CONFIG_SND_SOC_TAS2781_COMLIB) += snd-soc-tas2781-comlib.o
obj-$(CONFIG_SND_SOC_TAS2781_FMWLIB) += snd-soc-tas2781-fmwlib.o
obj-$(CONFIG_SND_SOC_TAS2781_I2C) += snd-soc-tas2781-i2c.o
+obj-$(CONFIG_SND_SOC_TAS2783) += snd-soc-tas2783.o
obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o
obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o
obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o
diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c
new file mode 100644
index 000000000000..f916ac9c5520
--- /dev/null
+++ b/sound/soc/codecs/tas2783-sdw.c
@@ -0,0 +1,871 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// ALSA SoC Texas Instruments TAS2783 Audio Smart Amplifier.
+//
+// Copyright (C) 2023 Texas Instruments Incorporated
+// https://www.ti.com
+//
+// The TAS2783 driver implements a flexible and configurable
+// algo coefficient setting for single TAS2783 chips.
+//
+// Author: Baojun Xu <baojun.xu(a)ti.com>
+// Author: Kevin Lu <kevin-lu(a)ti.com>
+//
+
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/efi.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/pcm_params.h>
+#include <sound/sdw.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/tas2781-tlv.h>
+
+#include "tas2783.h"
+
+static const unsigned int tas2783_calibration_reg[] = {
+ TAS2783_CALIBRATION_RE,
+ TAS2783_CALIBRATION_RE_LOW,
+ TAS2783_CALIBRATION_INV_RE,
+ TAS2783_CALIBRATION_POW,
+ TAS2783_CALIBRATION_TLIMIT,
+ 0,
+};
+
+static const struct reg_default tas2783_reg_defaults[] = {
+ // Default values for ROM mode. Activated.
+ { 0x8002, 0x1a}, // Put AMP in power down mode, B0P0R02.
+ { 0x800e, 0x44}, // VSense from slot 4, B0P0R0E.
+ { 0x800f, 0x40}, // ISense from slot 0, B0P0R0E.
+ { 0x8097, 0xc8}, // SARBurstMask = 0, B0P1R17.
+ { 0x80b5, 0x74},
+ { 0x8099, 0x20},
+ { 0xfe8d, 0x0d},
+ { 0xfebe, 0x4a},
+ { 0x8230, 0x00},
+ { 0x8231, 0x00},
+ { 0x8232, 0x00},
+ { 0x8233, 0x01}, // YMEM[70] = 1. B0P4R30.
+ { 0x8418, 0x00}, // 0 dB in B0P8R18.
+ { 0x8419, 0x00},
+ { 0x841a, 0x00},
+ { 0x841b, 0x00},
+ { 0x8428, 0x40}, // Unmute channel, B0P8R28.
+ { 0x8429, 0x00},
+ { 0x842a, 0x00},
+ { 0x842b, 0x00},
+ { 0x8548, 0x00}, // 0dB in B0PAR48.
+ { 0x8549, 0x00},
+ { 0x854a, 0x00},
+ { 0x854b, 0x00},
+ { 0x8558, 0x40}, // unmute channel, B0PAR58.
+ { 0x8559, 0x00},
+ { 0x855a, 0x00},
+ { 0x855b, 0x00},
+ { 0x800a, 0x3a}, // Enable both channel in B0P0RA.
+ { 0x805c, 0xd9}, // Enable clock detected in B0P0R5C.
+ { 0x8002, 0x00}, // Put AMP in active mode, B0P0R02.
+ /* Below register was used to select function, entity, CS, CN. */
+ { SDW_SDCA_CTL(1, 1, 2, 0), 0}, // 0x40400088.
+ { SDW_SDCA_CTL(1, 1, 1, 0), 0}, // 0x40400090.
+ { SDW_SDCA_CTL(1, 2, 1, 0), 0}, // 0x40400108.
+};
+
+static bool tas2783_readable_register(struct device *dev,
+ unsigned int reg)
+{
+ switch (reg) {
+ case 0x8000 ... 0xc000: // Page 0 ~ 127.
+ case 0xfe80 ... 0xfeff: // Page 253.
+ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU21,
+ TAS2783_SDCA_CTL_UDMPU_CLUSTER, 0):
+ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21,
+ TAS2783_SDCA_CTL_FU_MUTE, TAS2783_DEVICE_CHANNEL_LEFT):
+ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21,
+ TAS2783_SDCA_CTL_FU_MUTE, TAS2783_DEVICE_CHANNEL_RIGHT):
+ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23,
+ TAS2783_SDCA_CTL_REQ_POWER_STATE, 0):
+ case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE22,
+ TAS2783_SDCA_CTL_REQ_POWER_STATE, 0):
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool tas2783_volatile_register(struct device *dev,
+ unsigned int reg)
+{
+ switch (reg) {
+ case 0x8001:
+ // Only reset register was volatiled.
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tasdevice_regmap = {
+ .reg_bits = 32,
+ .val_bits = 8,
+ .readable_reg = tas2783_readable_register,
+ .volatile_reg = tas2783_volatile_register,
+ .max_register = 0x41008000 + TASDEVICE_REG(0xa1, 0x60, 0x7f),
+ .reg_defaults = tas2783_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tas2783_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+static int tasdevice_clamp(int val, int max, unsigned int invert)
+{
+ /* Keep in valid area, out of range value don't care. */
+ if (val > max)
+ val = max;
+ if (invert)
+ val = max - val;
+ if (val < 0)
+ val = 0;
+ return val;
+}
+
+static int tas2783_digital_getvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec
+ = snd_soc_kcontrol_component(kcontrol);
+ struct tasdevice_priv *tas_dev =
+ snd_soc_component_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct regmap *map = tas_dev->regmap;
+ int val = 0, ret;
+
+ if (!map) {
+ ret = -EINVAL;
+ dev_err(tas_dev->dev, "%s, regmap doesn't exist.\n",
+ __func__);
+ goto out;
+ }
+ /* Read the primary device as the whole */
+ ret = regmap_read(map, mc->reg, &val);
+ dev_dbg(tas_dev->dev, "%s, get digital vol %d from %x with %d\n",
+ __func__, val, mc->reg, ret);
+ if (ret) {
+ dev_err(tas_dev->dev, "%s, get digital vol error %x.\n",
+ __func__, ret);
+ goto out;
+ }
+ ucontrol->value.integer.value[0] =
+ tasdevice_clamp(val, mc->max, mc->invert);
+
+out:
+ return ret;
+}
+
+static int tas2783_digital_putvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec
+ = snd_soc_kcontrol_component(kcontrol);
+ struct tasdevice_priv *tas_dev =
+ snd_soc_component_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct regmap *map = tas_dev->regmap;
+ int val, ret;
+
+ if (!map) {
+ dev_err(tas_dev->dev, "%s, regmap doesn't exist.\n",
+ __func__);
+ return -EINVAL;
+ }
+ val = tasdevice_clamp(ucontrol->value.integer.value[0],
+ mc->max, mc->invert);
+
+ ret = regmap_write(map, mc->reg, val);
+ if (ret != 0) {
+ dev_err(tas_dev->dev, "Write @%#x..%#x:%d\n",
+ mc->reg, val, ret);
+ return ret;
+ }
+ dev_dbg(tas_dev->dev, "%s, Put digital vol %d into %x.\n",
+ __func__, val, mc->reg);
+
+ return 1;
+}
+
+static int tas2783_amp_getvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec
+ = snd_soc_kcontrol_component(kcontrol);
+ struct tasdevice_priv *tas_dev =
+ snd_soc_component_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct regmap *map = tas_dev->regmap;
+ unsigned char mask;
+ int ret, val;
+
+ if (!map) {
+ dev_err(tas_dev->dev, "%s, regmap doesn't exist.\n",
+ __func__);
+ return -EINVAL;
+ }
+ /* Read the primary device */
+ ret = regmap_read(map, mc->reg, &val);
+ dev_dbg(tas_dev->dev, "%s, get AMP vol %d from %x with %d\n",
+ __func__, val, mc->reg, ret);
+
+ mask = (1 << fls(mc->max)) - 1;
+ mask <<= mc->shift;
+ val = (val & mask) >> mc->shift;
+ ucontrol->value.integer.value[0] = tasdevice_clamp(val, mc->max,
+ mc->invert);
+
+ return ret;
+}
+
+static int tas2783_amp_putvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *codec
+ = snd_soc_kcontrol_component(kcontrol);
+ struct tasdevice_priv *tas_dev =
+ snd_soc_component_get_drvdata(codec);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct regmap *map = tas_dev->regmap;
+ unsigned char mask;
+ int val, ret;
+
+ if (!map) {
+ dev_err(tas_dev->dev, "%s, regmap doesn't exist.\n",
+ __func__);
+ return -EINVAL;
+ }
+ mask = (1 << fls(mc->max)) - 1;
+ mask <<= mc->shift;
+ val = tasdevice_clamp(ucontrol->value.integer.value[0], mc->max,
+ mc->invert);
+ ret = regmap_update_bits(map, mc->reg, mask, val << mc->shift);
+ if (ret != 0) {
+ dev_err(tas_dev->dev, "Write @%#x..%#x:%d\n",
+ mc->reg, val, ret);
+ return ret;
+ }
+
+ dev_dbg(tas_dev->dev, "wr amp %x into 0x%08x by %x shift %d.\n",
+ val, mc->reg, mask, mc->shift);
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new tas2783_snd_controls[] = {
+ SOC_SINGLE_RANGE_EXT_TLV("Amp Gain Volume", TAS2783_AMP_LEVEL,
+ 1, 0, 20, 0, tas2783_amp_getvol,
+ tas2783_amp_putvol, amp_vol_tlv),
+ SOC_SINGLE_RANGE_EXT_TLV("Digital Volume", TAS2783_DVC_LVL,
+ 0, 0, 200, 1, tas2783_digital_getvol,
+ tas2783_digital_putvol, dvc_tlv),
+};
+
+static void tas2783_apply_calib(
+ struct tasdevice_priv *tas_priv, unsigned int *cali_data)
+{
+ struct regmap *map = tas_priv->regmap;
+ int i = 0, ret;
+ u8 *reg_start;
+
+ if (!map) {
+ dev_err(tas_priv->dev, "%s, regmap doesn't exist.\n",
+ __func__);
+ return;
+ }
+ if (!tas_priv->sdw_peripheral) {
+ dev_err(tas_priv->dev, "%s, slaver doesn't exist.\n",
+ __func__);
+ return;
+ }
+ if ((tas_priv->sdw_peripheral->id.unique_id <
+ TAS2783_DEVICE_ID_START) ||
+ (tas_priv->sdw_peripheral->id.unique_id >
+ TAS2783_DEVICE_ID_END)) {
+ dev_err(tas_priv->dev, "%s, wrong unique id: %x.\n",
+ __func__, tas_priv->sdw_peripheral->id.unique_id);
+ return;
+ }
+ reg_start =
+ (u8 *)(cali_data+(tas_priv->sdw_peripheral->id.unique_id -
+ TAS2783_DEVICE_ID_START)*TAS2783_CALIBRATION_SIZE);
+ while (tas2783_calibration_reg[i] != 0) {
+ ret = regmap_bulk_write(map, tas2783_calibration_reg[i],
+ reg_start + i, 4);
+ if (ret != 0) {
+ dev_err(tas_priv->dev, "Cali failed %x:%d\n",
+ tas2783_calibration_reg[i], ret);
+ break;
+ }
+ i++;
+ }
+}
+
+static int tas2783_calibration(struct tasdevice_priv *tas_priv)
+{
+ efi_guid_t efi_guid = EFI_GUID(0x1f52d2a1, 0xbb3a, 0x457d, 0xbc,
+ 0x09, 0x43, 0xa3, 0xf4, 0x31, 0x0a, 0x92);
+ static efi_char16_t efi_name[] = TAS2783_CALIDATA_NAME;
+ struct tm *tm = &tas_priv->tm;
+ unsigned int attr, crc;
+ unsigned int *tmp_val;
+ efi_status_t status;
+
+ tas_priv->cali_data.total_sz = TAS2783_MAX_CALIDATA_SIZE;
+ /* Get real size of UEFI variable */
+ status = efi.get_variable(efi_name, &efi_guid, &attr,
+ &tas_priv->cali_data.total_sz, tas_priv->cali_data.data);
+ dev_dbg(tas_priv->dev, "cali get %lx bytes with result : %ld\n",
+ tas_priv->cali_data.total_sz, status);
+ if (status == EFI_BUFFER_TOO_SMALL) {
+ status = efi.get_variable(efi_name, &efi_guid, &attr,
+ &tas_priv->cali_data.total_sz,
+ tas_priv->cali_data.data);
+ dev_dbg(tas_priv->dev, "cali get %lx bytes result:%ld\n",
+ tas_priv->cali_data.total_sz, status);
+ }
+ /* Failed got calibration data from EFI. */
+ if (status != 0) {
+ dev_dbg(tas_priv->dev, "cali get %lx error with:%ld\n",
+ tas_priv->cali_data.total_sz, status);
+ return 0;
+ }
+ /* Print all content of calibration data for debug. */
+ for (int i = 0; i < tas_priv->cali_data.total_sz; i += 4) {
+ dev_dbg(tas_priv->dev, "cali get %02x %02x %02x %02x",
+ tas_priv->cali_data.data[i],
+ tas_priv->cali_data.data[i+1],
+ tas_priv->cali_data.data[i+2],
+ tas_priv->cali_data.data[i+3]);
+ }
+
+ tmp_val = (unsigned int *)tas_priv->cali_data.data;
+
+ crc = crc32(~0, tas_priv->cali_data.data, 84) ^ ~0;
+ dev_dbg(tas_priv->dev, "cali crc 0x%08x PK tmp_val 0x%08x\n",
+ crc, tmp_val[21]);
+
+ if (crc == tmp_val[21]) {
+ time64_to_tm(tmp_val[20], 0, tm);
+ dev_dbg(tas_priv->dev, "%4ld-%2d-%2d, %2d:%2d:%2d\n",
+ tm->tm_year, tm->tm_mon, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ tas2783_apply_calib(tas_priv, tmp_val);
+ } else {
+ dev_dbg(tas_priv->dev, "CRC error!\n");
+ tas_priv->cali_data.total_sz = 0;
+ }
+
+ return 0;
+}
+
+static void tasdevice_rca_ready(const struct firmware *fmw, void *context)
+{
+ struct tasdevice_priv *tas_dev =
+ (struct tasdevice_priv *) context;
+ struct regmap *map = tas_dev->regmap;
+ struct tas2783_firmware_node *p;
+ int offset = 0, num_nodes = 0, img_sz, ret;
+ unsigned char *buf;
+
+ mutex_lock(&tas_dev->codec_lock);
+
+ if (!fmw || !fmw->data) {
+ dev_err(tas_dev->dev,
+ "Failed to read %s, no side - effect on driver running\n",
+ tas_dev->rca_binaryname);
+ ret = -EINVAL;
+ goto out;
+ }
+ if (!map) {
+ dev_err(tas_dev->dev, "Failed to load regmap.\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ buf = (unsigned char *)fmw->data;
+
+ img_sz = le32_to_cpup((__le32 *)&buf[offset]);
+ dev_dbg(tas_dev->dev, "Got %x:%lx.\n", img_sz, fmw->size);
+ offset += sizeof(img_sz);
+ if (img_sz != fmw->size) {
+ dev_err(tas_dev->dev,
+ "Size not match, %d %u", (int)fmw->size, img_sz);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ while ((offset < img_sz) && (num_nodes < TAS2783_MAX_NODES)) {
+ /* Store firmware into context of driver. */
+ p = (struct tas2783_firmware_node *)(buf + offset);
+ tas_dev->firmware_node[num_nodes].vendor_id =
+ p->vendor_id;
+ tas_dev->firmware_node[num_nodes].file_id = p->file_id;
+ tas_dev->firmware_node[num_nodes].version_id =
+ p->version_id;
+ tas_dev->firmware_node[num_nodes].length = p->length;
+ tas_dev->firmware_node[num_nodes].download_addr =
+ p->download_addr;
+ tas_dev->firmware_node[num_nodes].start_addr =
+ ((char *)p) + sizeof(unsigned int)*5;
+
+ ret = regmap_bulk_write(map, p->download_addr,
+ p->start_addr, p->length);
+ dev_dbg(tas_dev->dev, "Wr %d :%x:%x:%x:%x:%x:%x %d.\n",
+ num_nodes, p->vendor_id, p->file_id,
+ p->version_id, p->length, p->download_addr,
+ p->start_addr[0], ret);
+
+ offset += sizeof(unsigned int)*5 + p->length;
+ num_nodes++;
+ }
+
+ tas2783_calibration(tas_dev);
+
+out:
+ mutex_unlock(&tas_dev->codec_lock);
+ if (fmw)
+ release_firmware(fmw);
+}
+
+static const struct snd_soc_dapm_widget tasdevice_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("ASI OUT", "ASI Capture", 0, SND_SOC_NOPM,
+ 0, 0),
+ SND_SOC_DAPM_OUTPUT("OUT"),
+ SND_SOC_DAPM_INPUT("DMIC")
+};
+
+static const struct snd_soc_dapm_route tasdevice_audio_map[] = {
+ {"OUT", NULL, "ASI"},
+ {"ASI OUT", NULL, "DMIC"}
+};
+
+static int tasdevice_set_sdw_stream(
+ struct snd_soc_dai *dai, void *sdw_stream, int direction)
+{
+ struct sdw_stream_data *stream;
+
+ if (!sdw_stream)
+ return 0;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return -ENOMEM;
+
+ stream->sdw_stream = sdw_stream;
+
+ /* Use tx_mask or rx_mask to set dma_data */
+ snd_soc_dai_dma_data_set(dai, direction, stream);
+
+ return 0;
+}
+
+static void tasdevice_sdw_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sdw_stream_data *stream;
+
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+ kfree(stream);
+}
+
+static int tasdevice_sdw_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct tasdevice_priv *tasdevice =
+ snd_soc_component_get_drvdata(component);
+ struct sdw_stream_config stream_config = {0};
+ struct sdw_port_config port_config = {0};
+ struct sdw_stream_data *stream;
+ int retval;
+
+ dev_dbg(dai->dev, "%s %s", __func__, dai->name);
+ stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!stream)
+ return -EINVAL;
+
+ if (!tasdevice->sdw_peripheral)
+ return -EINVAL;
+
+ /* SoundWire specific configuration */
+ snd_sdw_params_to_config(substream, params,
+ &stream_config, &port_config);
+
+ /* port 1 for playback */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ port_config.num = 1;
+ else
+ port_config.num = 2;
+
+ retval = sdw_stream_add_slave(tasdevice->sdw_peripheral,
+ &stream_config, &port_config, 1, stream->sdw_stream);
+ if (retval) {
+ dev_err(dai->dev, "Unable to configure port\n");
+ return retval;
+ }
+
+ return 0;
+}
+
+static int tasdevice_sdw_pcm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct tasdevice_priv *tasdevice =
+ snd_soc_component_get_drvdata(component);
+ struct sdw_stream_data *stream =
+ snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!tasdevice->sdw_peripheral)
+ return -EINVAL;
+
+ sdw_stream_remove_slave(tasdevice->sdw_peripheral, stream->sdw_stream);
+ return 0;
+}
+
+static const struct snd_soc_dai_ops tasdevice_dai_ops = {
+ .hw_params = tasdevice_sdw_hw_params,
+ .hw_free = tasdevice_sdw_pcm_hw_free,
+ .set_stream = tasdevice_set_sdw_stream,
+ .shutdown = tasdevice_sdw_shutdown,
+};
+
+static struct snd_soc_dai_driver tasdevice_dai_driver[] = {
+ {
+ .name = "tas2783-codec",
+ .id = 0,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = TAS2783_DEVICE_RATES,
+ .formats = TAS2783_DEVICE_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = TAS2783_DEVICE_RATES,
+ .formats = TAS2783_DEVICE_FORMATS,
+ },
+ .ops = &tasdevice_dai_ops,
+ .symmetric_rate = 1,
+ },
+};
+
+static void tas2783_reset(struct tasdevice_priv *tas_dev)
+{
+ struct regmap *map = tas_dev->regmap;
+ unsigned char value_sdw;
+ int ret;
+
+ if (!map) {
+ dev_err(tas_dev->dev, "Failed to load regmap.\n");
+ return;
+ }
+ value_sdw = TAS2873_REG_SWRESET_RESET;
+ ret = regmap_write(map, TAS2873_REG_SWRESET, value_sdw);
+ dev_dbg(tas_dev->dev, "%s TAS2783 was reseted %d.\n",
+ __func__, ret);
+ usleep_range(1000, 1050);
+}
+
+static int tasdevice_codec_probe(struct snd_soc_component *codec)
+{
+ struct tasdevice_priv *tas_dev =
+ snd_soc_component_get_drvdata(codec);
+ int ret;
+
+ dev_dbg(tas_dev->dev, "%s called for TAS2783 start.\n",
+ __func__);
+ /* Codec Lock Hold */
+ mutex_lock(&tas_dev->codec_lock);
+
+ tas2783_reset(tas_dev);
+
+ tas_dev->codec = codec;
+
+ scnprintf(tas_dev->rca_binaryname, 64, "MY_SWFT_x%01x.bin",
+ tas_dev->sdw_peripheral->id.unique_id);
+
+ ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+ tas_dev->rca_binaryname, tas_dev->dev, GFP_KERNEL,
+ tas_dev, tasdevice_rca_ready);
+ dev_dbg(tas_dev->dev,
+ "%s: request_firmware %x open status: 0x%08x\n",
+ __func__, tas_dev->sdw_peripheral->id.unique_id, ret);
+
+ /* Codec Lock Release*/
+ mutex_unlock(&tas_dev->codec_lock);
+
+ dev_dbg(tas_dev->dev, "%s was called end.\n", __func__);
+ return ret;
+}
+
+static const struct snd_soc_component_driver
+ soc_codec_driver_tasdevice = {
+ .probe = tasdevice_codec_probe,
+ .controls = tas2783_snd_controls,
+ .num_controls = ARRAY_SIZE(tas2783_snd_controls),
+ .dapm_widgets = tasdevice_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tasdevice_dapm_widgets),
+ .dapm_routes = tasdevice_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(tasdevice_audio_map),
+ .idle_bias_on = 1,
+ .endianness = 1,
+};
+
+static int tasdevice_init(struct tasdevice_priv *tas_dev)
+{
+ int ret;
+
+ mutex_init(&tas_dev->dev_lock);
+
+ dev_set_drvdata(tas_dev->dev, tas_dev);
+
+ mutex_init(&tas_dev->codec_lock);
+ ret = devm_snd_soc_register_component(tas_dev->dev,
+ &soc_codec_driver_tasdevice,
+ tasdevice_dai_driver, ARRAY_SIZE(tasdevice_dai_driver));
+ if (ret) {
+ dev_err(tas_dev->dev, "%s: codec register error:0x%x\n",
+ __func__, ret);
+ }
+ dev_dbg(tas_dev->dev, "%s was called for TAS2783.\n", __func__);
+
+ return ret;
+}
+
+static int tasdevice_read_prop(struct sdw_slave *slave)
+{
+ struct sdw_slave_prop *prop = &slave->prop;
+ struct sdw_dpn_prop *dpn;
+ unsigned long addr;
+ int nval, i, j;
+ u32 bit;
+
+ prop->scp_int1_mask =
+ SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY;
+ prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY;
+
+ prop->paging_support = true;
+
+ /* first we need to allocate memory for set bits in port lists */
+ prop->source_ports = 0x04; /* BITMAP: 00000100 */
+ prop->sink_ports = 0x2; /* BITMAP: 00000010 */
+
+ nval = hweight32(prop->source_ports);
+ prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->src_dpn_prop), GFP_KERNEL);
+ if (!prop->src_dpn_prop)
+ return -ENOMEM;
+
+ i = 0;
+ dpn = prop->src_dpn_prop;
+ addr = prop->source_ports;
+ for_each_set_bit(bit, &addr, 32) {
+ dpn[i].num = bit;
+ dpn[i].type = SDW_DPN_FULL;
+ dpn[i].simple_ch_prep_sm = true;
+ dpn[i].ch_prep_timeout = 10;
+ i++;
+ }
+
+ /* do this again for sink now */
+ nval = hweight32(prop->sink_ports);
+ prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->sink_dpn_prop), GFP_KERNEL);
+ if (!prop->sink_dpn_prop)
+ return -ENOMEM;
+
+ j = 0;
+ dpn = prop->sink_dpn_prop;
+ addr = prop->sink_ports;
+ for_each_set_bit(bit, &addr, 32) {
+ dpn[j].num = bit;
+ dpn[j].type = SDW_DPN_FULL;
+ dpn[j].simple_ch_prep_sm = true;
+ dpn[j].ch_prep_timeout = 10;
+ j++;
+ }
+
+ /* set the timeout values */
+ prop->clk_stop_timeout = 20;
+
+ dev_dbg(&slave->dev, "%s was performed.\n", __func__);
+
+ return 0;
+}
+
+static int tasdevice_io_init(struct device *dev, struct sdw_slave *slave)
+{
+ struct tasdevice_priv *tasdevice = dev_get_drvdata(dev);
+
+ if (tasdevice->hw_init)
+ return 0;
+
+ /* PM runtime is only enabled when
+ * a Slave reports as Attached
+ * set autosuspend parameters
+ */
+ pm_runtime_set_autosuspend_delay(&slave->dev, 3000);
+ pm_runtime_use_autosuspend(&slave->dev);
+
+ /* update count of parent 'active' children */
+ pm_runtime_set_active(&slave->dev);
+
+ /* make sure the device does not suspend immediately */
+ pm_runtime_mark_last_busy(&slave->dev);
+
+ pm_runtime_enable(&slave->dev);
+
+ pm_runtime_get_noresume(&slave->dev);
+
+ /* Mark Slave initialization complete */
+ tasdevice->hw_init = true;
+
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put_autosuspend(&slave->dev);
+
+ dev_dbg(&slave->dev, "%s hw_init complete\n", __func__);
+ return 0;
+}
+
+static int tasdevice_update_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ struct tasdevice_priv *tasdevice = dev_get_drvdata(&slave->dev);
+
+ /* Update the status */
+ tasdevice->status = status;
+
+ if (status == SDW_SLAVE_UNATTACHED)
+ tasdevice->hw_init = false;
+
+ /* Perform initialization only if slave status
+ * is present and hw_init flag is false
+ */
+ if (tasdevice->hw_init || tasdevice->status != SDW_SLAVE_ATTACHED)
+ return 0;
+
+ /* perform I/O transfers required for Slave initialization */
+ return tasdevice_io_init(&slave->dev, slave);
+}
+
+/*
+ * slave_ops: callbacks for get_clock_stop_mode, clock_stop and
+ * port_prep are not defined for now
+ */
+static const struct sdw_slave_ops tasdevice_sdw_ops = {
+ .read_prop = tasdevice_read_prop,
+ .update_status = tasdevice_update_status,
+};
+
+static void tasdevice_remove(struct tasdevice_priv *tas_dev)
+{
+ snd_soc_unregister_component(tas_dev->dev);
+
+ mutex_destroy(&tas_dev->dev_lock);
+ mutex_destroy(&tas_dev->codec_lock);
+}
+
+static int tasdevice_sdw_probe(struct sdw_slave *peripheral,
+ const struct sdw_device_id *id)
+{
+ struct device *dev = &peripheral->dev;
+ struct tasdevice_priv *tas_dev;
+ int ret;
+
+ dev_dbg(dev, "%s was called.\n", __func__);
+
+ tas_dev = devm_kzalloc(dev, sizeof(*tas_dev), GFP_KERNEL);
+ if (!tas_dev) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ tas_dev->dev = &peripheral->dev;
+ tas_dev->chip_id = id->driver_data;
+ tas_dev->sdw_peripheral = peripheral;
+ tas_dev->hw_init = false;
+
+ dev_dbg(dev, "%d chip id %x for TAS2783.\n",
+ peripheral->id.unique_id, tas_dev->chip_id);
+
+ dev_set_drvdata(dev, tas_dev);
+
+ tas_dev->regmap = devm_regmap_init_sdw(peripheral,
+ &tasdevice_regmap);
+ if (IS_ERR(tas_dev->regmap)) {
+ ret = PTR_ERR(tas_dev->regmap);
+ dev_err(dev, "Failed devm_regmap_init: %d\n", ret);
+ goto out;
+ }
+ ret = tasdevice_init(tas_dev);
+
+out:
+ if (ret < 0 && tas_dev != NULL)
+ tasdevice_remove(tas_dev);
+
+ return ret;
+
+}
+
+static int tasdevice_sdw_remove(struct sdw_slave *peripheral)
+{
+ struct tasdevice_priv *tas_dev =
+ dev_get_drvdata(&peripheral->dev);
+
+ if (tas_dev)
+ tasdevice_remove(tas_dev);
+
+ return 0;
+}
+
+static const struct sdw_device_id tasdevice_sdw_id[] = {
+ SDW_SLAVE_ENTRY(0x0102, 0x0, 0),
+ {},
+};
+MODULE_DEVICE_TABLE(sdw, tasdevice_sdw_id);
+
+static struct sdw_driver tasdevice_sdw_driver = {
+ .driver = {
+ .name = "slave-tas2783",
+ },
+ .probe = tasdevice_sdw_probe,
+ .remove = tasdevice_sdw_remove,
+ .ops = &tasdevice_sdw_ops,
+ .id_table = tasdevice_sdw_id,
+};
+
+module_sdw_driver(tasdevice_sdw_driver);
+
+MODULE_AUTHOR("Baojun Xu <baojun.xu(a)ti.com>");
+MODULE_DESCRIPTION("ASoC TAS2783 SoundWire Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tas2783.h b/sound/soc/codecs/tas2783.h
new file mode 100644
index 000000000000..09575e741400
--- /dev/null
+++ b/sound/soc/codecs/tas2783.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * ALSA SoC Texas Instruments TAS2783 Audio Smart Amplifier.
+ *
+ * Copyright (C) 2023 Texas Instruments Incorporated
+ * https://www.ti.com
+ *
+ * The TAS2783 driver implements a flexible and configurable
+ * algo coff setting for single TAS2783 chips.
+ *
+ * Author: Baojun Xu <baojun.xu(a)ti.com>
+ */
+
+#ifndef __TAS2783_H__
+#define __TAS2783_H__
+
+#define TAS2783_DEVICE_RATES (SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_88200)
+
+#define TAS2783_DEVICE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+/* BOOK, PAGE Control Register */
+#define TASDEVICE_REG(book, page, reg) ((book * 256 * 256) + 0x8000 +\
+ (page * 128) + reg)
+
+/*Software Reset */
+#define TAS2873_REG_SWRESET TASDEVICE_REG(0x0, 0X0, 0x01)
+#define TAS2873_REG_SWRESET_RESET BIT(0)
+
+/* Volume control */
+#define TAS2783_DVC_LVL TASDEVICE_REG(0x0, 0x00, 0x1A)
+#define TAS2783_AMP_LEVEL TASDEVICE_REG(0x0, 0x00, 0x03)
+#define TAS2783_AMP_LEVEL_MASK GENMASK(5, 1)
+
+/* Calibration data */
+#define TAS2783_CALIBRATION_RE TASDEVICE_REG(0x0, 0x17, 0x74)
+#define TAS2783_CALIBRATION_RE_LOW TASDEVICE_REG(0x0, 0x18, 0x14)
+#define TAS2783_CALIBRATION_INV_RE TASDEVICE_REG(0x0, 0x18, 0x0c)
+#define TAS2783_CALIBRATION_POW TASDEVICE_REG(0x0, 0x0d, 0x3c)
+#define TAS2783_CALIBRATION_TLIMIT TASDEVICE_REG(0x0, 0x18, 0x7c)
+
+#define TAS2783_CALIBRATION_SIZE 5 // int data number.
+
+#define TAS2783_DEVICE_ID_START 0x08 // Unique id start
+#define TAS2783_DEVICE_ID_END 0x0F // Unique id end
+
+/* TAS2783 SDCA Control - function number */
+#define FUNC_NUM_SMART_AMP 0x01
+
+/* TAS2783 SDCA entity */
+#define TAS2783_SDCA_ENT_PDE23 0x0C
+#define TAS2783_SDCA_ENT_PDE22 0x0B
+#define TAS2783_SDCA_ENT_FU21 0x01
+#define TAS2783_SDCA_ENT_UDMPU21 0x10
+
+/* TAS2783 SDCA control */
+#define TAS2783_SDCA_CTL_REQ_POWER_STATE 0x01
+#define TAS2783_SDCA_CTL_FU_MUTE 0x01
+#define TAS2783_SDCA_CTL_UDMPU_CLUSTER 0x10
+
+#define TAS2783_DEVICE_CHANNEL_LEFT 1
+#define TAS2783_DEVICE_CHANNEL_RIGHT 2
+
+#define TAS2783_MAX_NODES 100
+#define TAS2783_MAX_CALIDATA_SIZE 252
+#define TAS2783_CALIDATA_NAME L"CALI_DATA"
+
+struct tas2783_firmware_node {
+ unsigned int vendor_id;
+ unsigned int file_id;
+ unsigned int version_id;
+ unsigned int length;
+ unsigned int download_addr;
+ unsigned char *start_addr;
+};
+
+struct calibration_data {
+ unsigned long total_sz;
+ unsigned char data[TAS2783_MAX_CALIDATA_SIZE];
+};
+
+struct tasdevice_priv {
+ struct tas2783_firmware_node firmware_node[TAS2783_MAX_NODES];
+ struct snd_soc_component *component;
+ struct calibration_data cali_data;
+ struct sdw_slave *sdw_peripheral;
+ struct sdw_bus_params params;
+ struct mutex codec_lock;
+ struct regmap *regmap;
+ struct mutex dev_lock;
+ struct device *dev;
+ struct tm tm;
+
+ enum sdw_slave_status status;
+
+ bool hw_init;
+
+ unsigned int chip_id;
+ unsigned char rca_binaryname[64];
+ unsigned char dev_name[32];
+
+ void *codec;
+};
+
+struct sdw_stream_data {
+ struct sdw_stream_runtime *sdw_stream;
+};
+
+#endif /*__TAS2783_H__ */
--
2.34.1
3
3
Create tas2781 side codec HDA driver for Lenovo Laptops. The quantity
of the speakers has been define in ACPI. All of the tas2781s in the
laptop will be aggregated as one audio speaker. The code supports
realtek codec as the primary codec. Code offers several controls for
digtial/analog gain setting during playback, and other for eq params
setting in case of different audio profiles, such as music, voice,
movie, etc.
Signed-off-by: Shenghao Ding <shenghao-ding(a)ti.com>
---
Changes in v3:
- oprimize the indentation
- fix the ambiguous error message
- The check of the given value to kcontrol's put func is in the sound/tas2781-comlib.c
- In tasdevice_info_programs, corrected max value
- Seperate snd_kcontrol_new tas2781_dsp_controls into two standalone kcontrol.
- All the controls set as const static
- Add static for page_array & rgno_array
- Remove uncessary blank lines
- Add descriptions for tas2781_save_calibration
- remove global addr handling in the code
- checking subid in switch statement in function tas2781_hda_bind
- add force firmware load Kcontrol
- rename the kcontrol name to be more undertandable
- remove Superfluous cast in tasdevice_fw_ready
- correct weird line break in function tas2781_acpi_get_i2c_resource
- correct Referencing adev after acpi_dev_put() in tas2781_hda_read_acpi
- As to checking the given value in tasdevice_set_profile_id, it seems done
by the tasdevice_info_profile
- replace strcpy with strscpy in tas2781_hda_read_acpi
- rewrite the subid judgement
- Add tiwai(a)suse.de into Cc list
- remove the cast in tas2781_acpi_get_i2c_resource
- remove else in tas2781_acpi_get_i2c_resource
- fix the return value in tasdevice_set_profile_id
- remove unneeded NL in tasdevice_config_get
- Unifiy the comment style
- remove ret = 0 in tasdevice_fw_ready
- remove ret in tas2781_save_calibration
- remove unused ret in tas2781_hda_playback
- add force firmware load Kcontrol
---
sound/pci/hda/Kconfig | 15 +
sound/pci/hda/Makefile | 2 +
sound/pci/hda/tas2781_hda_i2c.c | 858 ++++++++++++++++++++++++++++++++
3 files changed, 875 insertions(+)
create mode 100644 sound/pci/hda/tas2781_hda_i2c.c
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig
index 886255a03e8b..e66257277492 100644
--- a/sound/pci/hda/Kconfig
+++ b/sound/pci/hda/Kconfig
@@ -130,6 +130,21 @@ config SND_HDA_SCODEC_CS35L41_SPI
comment "Set to Y if you want auto-loading the side codec driver"
depends on SND_HDA=y && SND_HDA_SCODEC_CS35L41_SPI=m
+config SND_HDA_SCODEC_TAS2781_I2C
+ tristate "Build TAS2781 HD-audio side codec support for I2C Bus"
+ depends on I2C
+ depends on ACPI
+ depends on SND_SOC
+ select SND_SOC_TAS2781_COMLIB
+ select SND_SOC_TAS2781_FMWLIB
+ select CRC32_SARWATE
+ help
+ Say Y or M here to include TAS2781 I2C HD-audio side codec support
+ in snd-hda-intel driver, such as ALC287.
+
+comment "Set to Y if you want auto-loading the side codec driver"
+ depends on SND_HDA=y && SND_HDA_SCODEC_TAS2781_I2C=m
+
config SND_HDA_CODEC_REALTEK
tristate "Build Realtek HD-audio codec support"
select SND_HDA_GENERIC
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile
index 00d306104484..1c76609690d6 100644
--- a/sound/pci/hda/Makefile
+++ b/sound/pci/hda/Makefile
@@ -32,6 +32,7 @@ snd-hda-scodec-cs35l41-objs := cs35l41_hda.o
snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o
snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o
snd-hda-cs-dsp-ctls-objs := hda_cs_dsp_ctl.o
+snd-hda-scodec-tas2781-i2c-objs := tas2781_hda_i2c.o
# common driver
obj-$(CONFIG_SND_HDA) := snd-hda-codec.o
@@ -56,6 +57,7 @@ obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o
obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o
obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o
obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o
+obj-$(CONFIG_SND_HDA_SCODEC_TAS2781_I2C) += snd-hda-scodec-tas2781-i2c.o
# this must be the last entry after codec drivers;
# otherwise the codec patches won't be hooked before the PCI probe
diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c
new file mode 100644
index 000000000000..4ccbe12ad451
--- /dev/null
+++ b/sound/pci/hda/tas2781_hda_i2c.c
@@ -0,0 +1,858 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// TAS2781 HDA I2C driver
+//
+// Copyright 2023 Texas Instruments, Inc.
+//
+// Author: Shenghao Ding <shenghao-ding(a)ti.com>
+
+#include <linux/acpi.h>
+#include <linux/crc8.h>
+#include <linux/crc32.h>
+#include <linux/efi.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <sound/hda_codec.h>
+#include <sound/soc.h>
+#include <sound/tas2781.h>
+#include <sound/tlv.h>
+#include <sound/tas2781-tlv.h>
+
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_component.h"
+#include "hda_jack.h"
+#include "hda_generic.h"
+
+#define TASDEVICE_SPEAKER_CALIBRATION_SIZE 20
+
+/* No standard control callbacks for SNDRV_CTL_ELEM_IFACE_CARD
+ * Define two controls, one is Volume control callbacks, the other is
+ * flag setting control callbacks.
+ */
+
+/* Volume control callbacks for tas2781 */
+#define ACARD_SINGLE_RANGE_EXT_TLV(xname, xreg, xshift, xmin, xmax, xinvert, \
+ xhandler_get, xhandler_put, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = (xname),\
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_range, \
+ .get = xhandler_get, .put = xhandler_put, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) \
+ {.reg = xreg, .rreg = xreg, .shift = xshift, \
+ .rshift = xshift, .min = xmin, .max = xmax, \
+ .invert = xinvert} }
+
+/* Flag control callbacks for tas2781 */
+#define ACARD_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = xname, \
+ .info = snd_ctl_boolean_mono_info, \
+ .get = xhandler_get, .put = xhandler_put, \
+ .private_value = xdata }
+
+enum calib_data {
+ R0_VAL = 0,
+ INV_R0,
+ R0LOW,
+ POWER,
+ TLIM,
+ CALIB_MAX
+};
+
+static int tas2781_get_i2c_res(struct acpi_resource *ares, void *data)
+{
+ struct tasdevice_priv *tas_priv = data;
+ struct acpi_resource_i2c_serialbus *sb;
+
+ if (i2c_acpi_get_i2c_resource(ares, &sb)) {
+ if (tas_priv->ndev < TASDEVICE_MAX_CHANNELS &&
+ sb->slave_address != TAS2781_GLOBAL_ADDR) {
+ tas_priv->tasdevice[tas_priv->ndev].dev_addr =
+ (unsigned int)sb->slave_address;
+ tas_priv->ndev++;
+ }
+ }
+ return 1;
+}
+
+static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid)
+{
+ struct acpi_device *adev;
+ struct device *physdev;
+ LIST_HEAD(resources);
+ const char *sub;
+ int ret;
+
+ adev = acpi_dev_get_first_match_dev(hid, NULL, -1);
+ if (!adev) {
+ dev_err(p->dev,
+ "Failed to find an ACPI device for %s\n", hid);
+ return -ENODEV;
+ }
+
+ ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p);
+ if (ret < 0)
+ goto err;
+
+ acpi_dev_free_resource_list(&resources);
+ strscpy(p->dev_name, hid, sizeof(p->dev_name));
+ physdev = get_device(acpi_get_first_physical_node(adev));
+ acpi_dev_put(adev);
+
+ /* No side-effect to the playback even if subsystem_id is NULL*/
+ sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev));
+ if (IS_ERR(sub))
+ sub = NULL;
+
+ p->acpi_subsystem_id = sub;
+
+ put_device(physdev);
+
+ return 0;
+
+err:
+ dev_err(p->dev, "read acpi error, ret: %d\n", ret);
+ put_device(physdev);
+
+ return ret;
+}
+
+static void tas2781_hda_playback_hook(struct device *dev, int action)
+{
+ struct tasdevice_priv *tas_priv = dev_get_drvdata(dev);
+
+ dev_dbg(tas_priv->dev, "%s: action = %d\n", __func__, action);
+ switch (action) {
+ case HDA_GEN_PCM_ACT_OPEN:
+ pm_runtime_get_sync(dev);
+ mutex_lock(&tas_priv->codec_lock);
+ tasdevice_tuning_switch(tas_priv, 0);
+ mutex_unlock(&tas_priv->codec_lock);
+ break;
+ case HDA_GEN_PCM_ACT_CLOSE:
+ mutex_lock(&tas_priv->codec_lock);
+ tasdevice_tuning_switch(tas_priv, 1);
+ mutex_unlock(&tas_priv->codec_lock);
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ break;
+ default:
+ dev_dbg(tas_priv->dev, "Playback action not supported: %d\n",
+ action);
+ break;
+ }
+}
+
+static int tasdevice_info_profile(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = tas_priv->rcabin.ncfgs - 1;
+
+ return 0;
+}
+
+static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = tas_priv->rcabin.profile_cfg_id;
+
+ return 0;
+}
+
+static int tasdevice_hda_clamp(int val, int max)
+{
+ if (val > max)
+ val = max;
+
+ if (val < 0)
+ val = 0;
+ return val;
+}
+
+static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ int nr_profile = ucontrol->value.integer.value[0];
+ int max = tas_priv->rcabin.ncfgs - 1;
+ int val, ret = 0;
+
+ val = tasdevice_hda_clamp(nr_profile, max);
+
+ if (tas_priv->rcabin.profile_cfg_id != nr_profile) {
+ tas_priv->rcabin.profile_cfg_id = nr_profile;
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static int tasdevice_info_programs(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct tasdevice_fw *tas_fw = tas_priv->fmw;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = tas_fw->nr_programs - 1;
+
+ return 0;
+}
+
+static int tasdevice_info_config(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct tasdevice_fw *tas_fw = tas_priv->fmw;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = tas_fw->nr_configurations - 1;
+
+ return 0;
+}
+
+static int tasdevice_program_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = tas_priv->cur_prog;
+
+ return 0;
+}
+
+static int tasdevice_program_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct tasdevice_fw *tas_fw = tas_priv->fmw;
+ int nr_program = ucontrol->value.integer.value[0];
+ int max = tas_fw->nr_programs - 1;
+ int val, ret = 0;
+
+ val = tasdevice_hda_clamp(nr_program, max);
+
+ if (tas_priv->cur_prog != nr_program) {
+ tas_priv->cur_prog = nr_program;
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static int tasdevice_config_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = tas_priv->cur_conf;
+
+ return 0;
+}
+
+static int tasdevice_config_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct tasdevice_fw *tas_fw = tas_priv->fmw;
+ int nr_config = ucontrol->value.integer.value[0];
+ int max = tas_fw->nr_configurations - 1;
+ int val, ret = 0;
+
+ val = tasdevice_hda_clamp(nr_config, max);
+
+ if (tas_priv->cur_conf != nr_config) {
+ tas_priv->cur_conf = nr_config;
+ ret = 1;
+ }
+
+ return ret;
+}
+
+/*
+ * tas2781_digital_getvol - get the volum control
+ * @kcontrol: control pointer
+ * @ucontrol: User data
+ * Customer Kcontrol for tas2781 is primarily for regmap booking, paging
+ * depends on internal regmap mechanism.
+ * tas2781 contains book and page two-level register map, especially
+ * book switching will set the register BXXP00R7F, after switching to the
+ * correct book, then leverage the mechanism for paging to access the
+ * register.
+ */
+static int tas2781_digital_getvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+
+ return tasdevice_digital_getvol(tas_priv, ucontrol, mc);
+}
+
+static int tas2781_amp_getvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+
+ return tasdevice_amp_getvol(tas_priv, ucontrol, mc);
+}
+
+static int tas2781_digital_putvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+
+ /* The check of the given value is in tasdevice_digital_putvol. */
+ return tasdevice_digital_putvol(tas_priv, ucontrol, mc);
+}
+
+static int tas2781_amp_putvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+
+ /* The check of the given value is in tasdevice_amp_putvol. */
+ return tasdevice_amp_putvol(tas_priv, ucontrol, mc);
+}
+
+static int tas2781_force_fwload_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = (int)tas_priv->force_fwload_status;
+ dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__,
+ tas_priv->force_fwload_status ? "ON" : "OFF");
+
+ return 0;
+}
+
+static int tas2781_force_fwload_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ bool change, val = (bool)ucontrol->value.integer.value[0];
+
+ if (tas_priv->force_fwload_status == val)
+ change = false;
+ else {
+ change = true;
+ tas_priv->force_fwload_status = val;
+ }
+ dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__,
+ tas_priv->force_fwload_status ? "ON" : "OFF");
+
+ return change;
+}
+
+static const struct snd_kcontrol_new tas2781_snd_controls[] = {
+ ACARD_SINGLE_RANGE_EXT_TLV("Speaker Analog Gain", TAS2781_AMP_LEVEL,
+ 1, 0, 20, 0, tas2781_amp_getvol,
+ tas2781_amp_putvol, amp_vol_tlv),
+ ACARD_SINGLE_RANGE_EXT_TLV("Speaker Digital Gain", TAS2781_DVC_LVL,
+ 0, 0, 200, 1, tas2781_digital_getvol,
+ tas2781_digital_putvol, dvc_tlv),
+ ACARD_SINGLE_BOOL_EXT("Speaker Force Firmware Load", 0,
+ tas2781_force_fwload_get, tas2781_force_fwload_put),
+};
+
+static const struct snd_kcontrol_new tas2781_prof_ctrl = {
+ .name = "Speaker Profile Id",
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = tasdevice_info_profile,
+ .get = tasdevice_get_profile_id,
+ .put = tasdevice_set_profile_id,
+};
+
+static const struct snd_kcontrol_new tas2781_dsp_prog_ctrl = {
+ .name = "Speaker Program Id",
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = tasdevice_info_programs,
+ .get = tasdevice_program_get,
+ .put = tasdevice_program_put,
+};
+
+static const struct snd_kcontrol_new tas2781_dsp_conf_ctrl = {
+ .name = "Speaker Config Id",
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = tasdevice_info_config,
+ .get = tasdevice_config_get,
+ .put = tasdevice_config_put,
+};
+
+static void tas2781_apply_calib(struct tasdevice_priv *tas_priv)
+{
+ static const unsigned char page_array[CALIB_MAX] = {
+ 0x17, 0x18, 0x18, 0x0d, 0x18
+ };
+ static const unsigned char rgno_array[CALIB_MAX] = {
+ 0x74, 0x0c, 0x14, 0x3c, 0x7c
+ };
+ unsigned char *data;
+ int i, j, rc;
+
+ for (i = 0; i < tas_priv->ndev; i++) {
+ data = tas_priv->cali_data.data +
+ i * TASDEVICE_SPEAKER_CALIBRATION_SIZE;
+ for (j = 0; j < CALIB_MAX; j++) {
+ rc = tasdevice_dev_bulk_write(tas_priv, i,
+ TASDEVICE_REG(0, page_array[j], rgno_array[j]),
+ &(data[4 * j]), 4);
+ if (rc < 0)
+ dev_err(tas_priv->dev,
+ "chn %d calib %d bulk_wr err = %d\n",
+ i, j, rc);
+ }
+ }
+}
+
+/* Update the calibrate data, including speaker impedance, f0, etc, into algo.
+ * Calibrate data is done by manufacturer in the factory. These data are used
+ * by Algo for calucating the speaker temperature, speaker membrance excursion
+ * and f0 in real time during playback.
+ */
+static int tas2781_save_calibration(struct tasdevice_priv *tas_priv)
+{
+ efi_guid_t efi_guid = EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d,
+ 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3);
+ static efi_char16_t efi_name[] = L"CALI_DATA";
+ struct tm *tm = &tas_priv->tm;
+ unsigned int attr, crc;
+ unsigned int *tmp_val;
+ efi_status_t status;
+
+ /* Lenovo devices */
+ if (tas_priv->catlog_id == LENOVO)
+ efi_guid = EFI_GUID(0x1f52d2a1, 0xbb3a, 0x457d, 0xbc, 0x09,
+ 0x43, 0xa3, 0xf4, 0x31, 0x0a, 0x92);
+
+ tas_priv->cali_data.total_sz = 0;
+ /* Get real size of UEFI variable */
+ status = efi.get_variable(efi_name, &efi_guid, &attr,
+ &tas_priv->cali_data.total_sz, tas_priv->cali_data.data);
+ if (status == EFI_BUFFER_TOO_SMALL) {
+ /* Allocate data buffer of data_size bytes */
+ tas_priv->cali_data.data = devm_kzalloc(tas_priv->dev,
+ tas_priv->cali_data.total_sz, GFP_KERNEL);
+ if (!tas_priv->cali_data.data)
+ return -ENOMEM;
+ /* Get variable contents into buffer */
+ status = efi.get_variable(efi_name, &efi_guid, &attr,
+ &tas_priv->cali_data.total_sz,
+ tas_priv->cali_data.data);
+ if (status != EFI_SUCCESS)
+ return -EINVAL;
+ }
+
+ tmp_val = (unsigned int *)tas_priv->cali_data.data;
+
+ crc = crc32(~0, tas_priv->cali_data.data, 84) ^ ~0;
+ dev_dbg(tas_priv->dev, "cali crc 0x%08x PK tmp_val 0x%08x\n",
+ crc, tmp_val[21]);
+
+ if (crc == tmp_val[21]) {
+ time64_to_tm(tmp_val[20], 0, tm);
+ dev_dbg(tas_priv->dev, "%4ld-%2d-%2d, %2d:%2d:%2d\n",
+ tm->tm_year, tm->tm_mon, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ tas2781_apply_calib(tas_priv);
+ } else
+ tas_priv->cali_data.total_sz = 0;
+
+ return 0;
+}
+
+static void tasdev_fw_ready(const struct firmware *fmw, void *context)
+{
+ struct tasdevice_priv *tas_priv = context;
+ struct hda_codec *codec = tas_priv->codec;
+ int i, ret;
+
+ pm_runtime_get_sync(tas_priv->dev);
+ mutex_lock(&tas_priv->codec_lock);
+
+ ret = tasdevice_rca_parser(tas_priv, fmw);
+ if (ret)
+ goto out;
+
+ ret = snd_ctl_add(codec->card,
+ snd_ctl_new1(&tas2781_prof_ctrl, tas_priv));
+ if (ret) {
+ dev_err(tas_priv->dev,
+ "Failed to add KControl %s = %d\n",
+ tas2781_prof_ctrl.name, ret);
+ goto out;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tas2781_snd_controls); i++) {
+ ret = snd_ctl_add(codec->card,
+ snd_ctl_new1(&tas2781_snd_controls[i], tas_priv));
+ if (ret) {
+ dev_err(tas_priv->dev,
+ "Failed to add KControl %s = %d\n",
+ tas2781_snd_controls[i].name, ret);
+ goto out;
+ }
+ }
+
+ tasdevice_dsp_remove(tas_priv);
+
+ tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING;
+ scnprintf(tas_priv->coef_binaryname, 64, "TAS2XXX%04X.bin",
+ codec->core.subsystem_id & 0xffff);
+ ret = tasdevice_dsp_parser(tas_priv);
+ if (ret) {
+ dev_err(tas_priv->dev, "dspfw load %s error\n",
+ tas_priv->coef_binaryname);
+ tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL;
+ goto out;
+ }
+
+ ret = snd_ctl_add(codec->card,
+ snd_ctl_new1(&tas2781_dsp_prog_ctrl, tas_priv));
+ if (ret) {
+ dev_err(tas_priv->dev,
+ "Failed to add KControl %s = %d\n",
+ tas2781_dsp_prog_ctrl.name, ret);
+ goto out;
+ }
+
+ ret = snd_ctl_add(codec->card,
+ snd_ctl_new1(&tas2781_dsp_conf_ctrl, tas_priv));
+ if (ret) {
+ dev_err(tas_priv->dev,
+ "Failed to add KControl %s = %d\n",
+ tas2781_dsp_conf_ctrl.name, ret);
+ goto out;
+ }
+
+ tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK;
+ tasdevice_prmg_load(tas_priv, 0);
+
+ /* If calibrated data occurs error, dsp will still works with default
+ * calibrated data inside algo.
+ */
+ tas2781_save_calibration(tas_priv);
+
+out:
+ if (tas_priv->fw_state == TASDEVICE_DSP_FW_FAIL) {
+ /*If DSP FW fail, kcontrol won't be created */
+ tasdevice_config_info_remove(tas_priv);
+ tasdevice_dsp_remove(tas_priv);
+ }
+ mutex_unlock(&tas_priv->codec_lock);
+ if (fmw)
+ release_firmware(fmw);
+ pm_runtime_mark_last_busy(tas_priv->dev);
+ pm_runtime_put_autosuspend(tas_priv->dev);
+}
+
+static int tas2781_hda_bind(struct device *dev, struct device *master,
+ void *master_data)
+{
+ struct tasdevice_priv *tas_priv = dev_get_drvdata(dev);
+ struct hda_component *comps = master_data;
+ struct hda_codec *codec;
+ unsigned int subid;
+ int ret;
+
+ if (!comps || tas_priv->index < 0 ||
+ tas_priv->index >= HDA_MAX_COMPONENTS)
+ return -EINVAL;
+
+ comps = &comps[tas_priv->index];
+ if (comps->dev)
+ return -EBUSY;
+
+ codec = comps->codec;
+ subid = codec->core.subsystem_id >> 16;
+
+ switch (subid) {
+ case 0x17aa:
+ tas_priv->catlog_id = LENOVO;
+ break;
+ default:
+ tas_priv->catlog_id = OTHERS;
+ break;
+ }
+
+ pm_runtime_get_sync(dev);
+
+ comps->dev = dev;
+
+ strscpy(comps->name, dev_name(dev), sizeof(comps->name));
+
+ ret = tascodec_init(tas_priv, codec, tasdev_fw_ready);
+ if (ret)
+ return ret;
+
+ comps->playback_hook = tas2781_hda_playback_hook;
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
+static void tas2781_hda_unbind(struct device *dev,
+ struct device *master, void *master_data)
+{
+ struct tasdevice_priv *tas_priv = dev_get_drvdata(dev);
+ struct hda_component *comps = master_data;
+
+ if (comps[tas_priv->index].dev == dev)
+ memset(&comps[tas_priv->index], 0, sizeof(*comps));
+
+ tasdevice_config_info_remove(tas_priv);
+ tasdevice_dsp_remove(tas_priv);
+
+ tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING;
+}
+
+static const struct component_ops tas2781_hda_comp_ops = {
+ .bind = tas2781_hda_bind,
+ .unbind = tas2781_hda_unbind,
+};
+
+static void tas2781_hda_remove(struct device *dev)
+{
+ struct tasdevice_priv *tas_priv = dev_get_drvdata(dev);
+
+ pm_runtime_get_sync(tas_priv->dev);
+ pm_runtime_disable(tas_priv->dev);
+
+ component_del(tas_priv->dev, &tas2781_hda_comp_ops);
+
+ pm_runtime_put_noidle(tas_priv->dev);
+
+ tasdevice_remove(tas_priv);
+}
+
+static int tas2781_hda_i2c_probe(struct i2c_client *clt)
+{
+ struct tasdevice_priv *tas_priv;
+ const char *device_name;
+ int ret;
+
+ if (strstr(dev_name(&clt->dev), "TIAS2781"))
+ device_name = "TIAS2781";
+ else
+ return -ENODEV;
+
+ tas_priv = tasdevice_kzalloc(clt);
+ if (!tas_priv)
+ return -ENOMEM;
+
+ tas_priv->irq_info.irq = clt->irq;
+ ret = tas2781_read_acpi(tas_priv, device_name);
+ if (ret)
+ return dev_err_probe(tas_priv->dev, ret,
+ "Platform not supported\n");
+
+ ret = tasdevice_init(tas_priv);
+ if (ret)
+ goto err;
+
+ pm_runtime_set_autosuspend_delay(tas_priv->dev, 3000);
+ pm_runtime_use_autosuspend(tas_priv->dev);
+ pm_runtime_mark_last_busy(tas_priv->dev);
+ pm_runtime_set_active(tas_priv->dev);
+ pm_runtime_get_noresume(tas_priv->dev);
+ pm_runtime_enable(tas_priv->dev);
+
+ pm_runtime_put_autosuspend(tas_priv->dev);
+
+ ret = component_add(tas_priv->dev, &tas2781_hda_comp_ops);
+ if (ret) {
+ dev_err(tas_priv->dev, "Register component failed: %d\n", ret);
+ pm_runtime_disable(tas_priv->dev);
+ goto err;
+ }
+
+ tas2781_reset(tas_priv);
+err:
+ if (ret)
+ tas2781_hda_remove(&clt->dev);
+ return ret;
+}
+
+static void tas2781_hda_i2c_remove(struct i2c_client *clt)
+{
+ tas2781_hda_remove(&clt->dev);
+}
+
+static int tas2781_runtime_suspend(struct device *dev)
+{
+ struct tasdevice_priv *tas_priv = dev_get_drvdata(dev);
+ int i;
+
+ dev_dbg(tas_priv->dev, "Runtime Suspend\n");
+
+ mutex_lock(&tas_priv->codec_lock);
+
+ if (tas_priv->playback_started) {
+ tasdevice_tuning_switch(tas_priv, 1);
+ tas_priv->playback_started = false;
+ }
+
+ for (i = 0; i < tas_priv->ndev; i++) {
+ tas_priv->tasdevice[i].cur_book = -1;
+ tas_priv->tasdevice[i].cur_prog = -1;
+ tas_priv->tasdevice[i].cur_conf = -1;
+ }
+
+ regcache_cache_only(tas_priv->regmap, true);
+ regcache_mark_dirty(tas_priv->regmap);
+
+ mutex_unlock(&tas_priv->codec_lock);
+
+ return 0;
+}
+
+static int tas2781_runtime_resume(struct device *dev)
+{
+ struct tasdevice_priv *tas_priv = dev_get_drvdata(dev);
+ unsigned long calib_data_sz =
+ tas_priv->ndev * TASDEVICE_SPEAKER_CALIBRATION_SIZE;
+ int ret;
+
+ dev_dbg(tas_priv->dev, "Runtime Resume\n");
+
+ mutex_lock(&tas_priv->codec_lock);
+
+ regcache_cache_only(tas_priv->regmap, false);
+ ret = regcache_sync(tas_priv->regmap);
+ if (ret) {
+ dev_err(tas_priv->dev,
+ "Failed to restore register cache: %d\n", ret);
+ goto out;
+ }
+
+ tasdevice_prmg_load(tas_priv, tas_priv->cur_prog);
+
+ /* If calibrated data occurs error, dsp will still works with default
+ * calibrated data inside algo.
+ */
+ if (tas_priv->cali_data.total_sz > calib_data_sz)
+ tas2781_apply_calib(tas_priv);
+
+out:
+ mutex_unlock(&tas_priv->codec_lock);
+
+ return ret;
+}
+
+static int tas2781_system_suspend(struct device *dev)
+{
+ struct tasdevice_priv *tas_priv = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(tas_priv->dev, "System Suspend\n");
+
+ ret = pm_runtime_force_suspend(dev);
+ if (ret)
+ return ret;
+
+ /* Shutdown chip before system suspend */
+ regcache_cache_only(tas_priv->regmap, false);
+ tasdevice_tuning_switch(tas_priv, 1);
+ regcache_cache_only(tas_priv->regmap, true);
+ regcache_mark_dirty(tas_priv->regmap);
+
+ /*
+ * Reset GPIO may be shared, so cannot reset here.
+ * However beyond this point, amps may be powered down.
+ */
+ return 0;
+}
+
+static int tas2781_system_resume(struct device *dev)
+{
+ struct tasdevice_priv *tas_priv = dev_get_drvdata(dev);
+ unsigned long calib_data_sz =
+ tas_priv->ndev * TASDEVICE_SPEAKER_CALIBRATION_SIZE;
+ int i, ret;
+
+ dev_dbg(tas_priv->dev, "System Resume\n");
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+
+ mutex_lock(&tas_priv->codec_lock);
+
+ for (i = 0; i < tas_priv->ndev; i++) {
+ tas_priv->tasdevice[i].cur_book = -1;
+ tas_priv->tasdevice[i].cur_prog = -1;
+ tas_priv->tasdevice[i].cur_conf = -1;
+ }
+ tas2781_reset(tas_priv);
+ tasdevice_prmg_load(tas_priv, tas_priv->cur_prog);
+
+ /* If calibrated data occurs error, dsp will still work with default
+ * calibrated data inside algo.
+ */
+ if (tas_priv->cali_data.total_sz > calib_data_sz)
+ tas2781_apply_calib(tas_priv);
+ mutex_unlock(&tas_priv->codec_lock);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tas2781_hda_pm_ops = {
+ RUNTIME_PM_OPS(tas2781_runtime_suspend, tas2781_runtime_resume, NULL)
+ SYSTEM_SLEEP_PM_OPS(tas2781_system_suspend, tas2781_system_resume)
+};
+
+static const struct i2c_device_id tas2781_hda_i2c_id[] = {
+ { "tas2781-hda", 0 },
+ {}
+};
+
+static const struct acpi_device_id tas2781_acpi_hda_match[] = {
+ {"TIAS2781", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, tas2781_acpi_hda_match);
+
+static struct i2c_driver tas2781_hda_i2c_driver = {
+ .driver = {
+ .name = "tas2781-hda",
+ .acpi_match_table = tas2781_acpi_hda_match,
+ .pm = &tas2781_hda_pm_ops,
+ },
+ .id_table = tas2781_hda_i2c_id,
+ .probe_new = tas2781_hda_i2c_probe,
+ .remove = tas2781_hda_i2c_remove,
+};
+module_i2c_driver(tas2781_hda_i2c_driver);
+
+MODULE_DESCRIPTION("TAS2781 HDA Driver");
+MODULE_AUTHOR("Shenghao Ding, TI, <shenghao-ding(a)ti.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(SND_SOC_TAS2781_FMWLIB);
--
2.34.1
2
2
Fix 'errno undeclared' compile error with older gcc versions
by GitHub pull_request - opened 17 Aug '23
by GitHub pull_request - opened 17 Aug '23
17 Aug '23
alsa-project/alsa-gobject pull request #83 was opened from michele-perrone:
It looks like errno.h has to be included explicitly when compiling with older
gcc versions. This issue was encountered on the following system:
- Ubuntu 20.04 LTS
- Linux 5.4.0-156
- gcc 10.5.0
- Meson 0.53.2
Request URL : https://github.com/alsa-project/alsa-gobject/pull/83
Patch URL : https://github.com/alsa-project/alsa-gobject/pull/83.patch
Repository URL: https://github.com/alsa-project/alsa-gobject
1
0
17 Aug '23
On Sat, May 13, 2023 at 11:02:30PM +0200, Marek Vasut wrote:
> On 5/13/23 03:51, Marek Vasut wrote:
> > On 5/13/23 03:12, Jeff LaBundy wrote:
> > > Hi Marek,
> >
> > Hi,
> >
> > > On Fri, May 12, 2023 at 08:55:51PM +0200, Marek Vasut wrote:
> > > > The PWM beeper volume can be controlled by adjusting the PWM duty cycle,
> > > > expose volume setting via sysfs, so users can make the beeper quieter.
> > > > This patch adds sysfs attribute 'volume' in range 0..50000, i.e. from 0
> > > > to 50% in 1/1000th of percent steps, this resolution should be
> > > > sufficient.
> > > >
> > > > The reason for 50000 cap on volume or PWM duty cycle is because
> > > > duty cycle
> > > > above 50% again reduces the loudness, the PWM wave form is inverted wave
> > > > form of the one for duty cycle below 50% and the beeper gets quieter the
> > > > closer the setting is to 100% . Hence, 50% cap where the wave
> > > > form yields
> > > > the loudest result.
> > > >
> > > > Signed-off-by: Marek Vasut <marex(a)denx.de>
> > > > ---
> > > > An alternative option would be to extend the userspace input
> > > > ABI, e.g. by
> > > > using SND_TONE top 16bits to encode the duty cycle in 0..50000
> > > > range, and
> > > > bottom 16bit to encode the existing frequency in Hz . Since frequency in
> > > > Hz is likely to be below some 25 kHz for audible bell, this fits
> > > > in 16bits
> > > > just fine. Thoughts ?
> > > > ---
> > >
> > > Thanks for the patch; this seems like a useful feature.
> > >
> > > My first thought is that 50000 seems like an oddly specific limit to
> > > impose
> > > upon user space. Ideally, user space need not even care that the
> > > beeper is
> > > implemented via PWM and why 50000 is significant.
> > >
> > > Instead, what about accepting 0..255 as the LED subsystem does for
> > > brightness,
> > > then map these values to 0..50000 internally? In fact, the leds-pwm
> > > driver
> > > does something similar.
> >
> > The pwm_set_relative_duty_cycle() function can map whatever range to
> > whatever other range of the PWM already, so that's not an issues here.
> > It seems to me the 0..127 or 0..255 range is a bit too limiting . I
> > think even for the LEDs the reason for that limit is legacy design, but
> > here I might be wrong.
> >
> > > I'm also curious as to whether this function should be a rogue sysfs
> > > control
> > > limited to this driver, or a generic operation in input. For
> > > example, input
> > > already allows user space to specify the magnitude of an FF effect;
> > > perhaps
> > > something similar is warranted here?
> >
> > See the "An alternative ..." part above, I was wondering about this too,
> > whether this can be added into the input ABI, but I am somewhat
> > reluctant to fiddle with the ABI.
>
> Thinking about this further, we could try and add some
>
> EV_SND SND_TONE_WITH_VOLUME
>
> to avoid overloading EV_SND SND_TONE , and at the same time allow the user
> to set both frequency and volume for the tone without any race condition
> between the two.
>
> The EV_SND SND_TONE_WITH_VOLUME would still take one 32bit parameter, except
> this time the parameter 16 LSbits would be the frequency and 16 MSbits would
> be the volume.
>
> But again, here I would like input from the maintainers.
Beeper was supposed to be an extremely simple device with minimal
controls. I wonder if there is need for volume controls, etc, etc are we
not better moving it over to the sound subsystem. We already have:
sound/drivers/pcsp/pcsp.c
and
sound/pci/hda/hda_beep.c
there, can we have other "advanced" beepers there as well? Adding sound
maintainers to CC...
Thanks.
--
Dmitry
6
21