* [PATCH v1 0/2] ASoC: codecs: nau8310: Add NAU83G10 amplifier
@ 2026-10-22 8:40 Neo Chang
2026-10-22 8:40 ` [PATCH v1 1/2] ASoC: dt-bindings: nuvoton,nau8310: Add NAU83G10 Neo Chang
2026-10-22 8:40 ` [PATCH v1 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier Neo Chang
0 siblings, 2 replies; 6+ messages in thread
From: Neo Chang @ 2026-10-22 8:40 UTC (permalink / raw)
To: broonie
Cc: lgirdwood, perex, robh, krzk+dt, linux-sound, devicetree,
alsa-devel, neo.chang70, kchsu0, sjlin0, Neo Chang
This patch series adds support for the Nuvoton NAU83G10 audio amplifier.
The NAU83G10 is a mono boost Class-D audio amplifier with
an integrated DSP and I/V-sense support. The driver supports
I2C register access through regmap.
DAPM widgets and routes, TDM interface configuration, and DSP firmware
loading.
The series is structured asd follows:
- Patch 1: Adds the YAML device tree bindings documentation.
- Patch 2: Adds the ASoC audio amplifier driver implementation.
Neo Chang (2):
ASoC: dt-bindings: nuvoton,nau8310: Add NAU83G10
ASoC: codecs: nau8310: Add support for NAU83G10 amplifier
.../bindings/sound/nuvoton,nau8310.yaml | 200 ++
sound/soc/codecs/Kconfig | 13 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/nau8310-dsp.c | 690 ++++++
sound/soc/codecs/nau8310-dsp.h | 111 +
sound/soc/codecs/nau8310.c | 1845 +++++++++++++++++
sound/soc/codecs/nau8310.h | 629 ++++++
7 files changed, 3490 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml
create mode 100644 sound/soc/codecs/nau8310-dsp.c
create mode 100644 sound/soc/codecs/nau8310-dsp.h
create mode 100644 sound/soc/codecs/nau8310.c
create mode 100644 sound/soc/codecs/nau8310.h
--
2.25.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v1 1/2] ASoC: dt-bindings: nuvoton,nau8310: Add NAU83G10
2026-10-22 8:40 [PATCH v1 0/2] ASoC: codecs: nau8310: Add NAU83G10 amplifier Neo Chang
@ 2026-10-22 8:40 ` Neo Chang
2026-10-22 8:51 ` sashiko-bot
2026-10-22 8:40 ` [PATCH v1 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier Neo Chang
1 sibling, 1 reply; 6+ messages in thread
From: Neo Chang @ 2026-10-22 8:40 UTC (permalink / raw)
To: broonie
Cc: lgirdwood, perex, robh, krzk+dt, linux-sound, devicetree,
alsa-devel, neo.chang70, kchsu0, sjlin0, Neo Chang
Add device tree bindings documentation for the Nuvoton NAU83G10
audio amplifier.
Signed-off-by: Neo Chang <YLCHANG2@nuvoton.com>
---
.../bindings/sound/nuvoton,nau8310.yaml | 200 ++++++++++++++++++
1 file changed, 200 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml
diff --git a/Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml b/Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml
new file mode 100644
index 000000000000..5a5142a583ed
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml
@@ -0,0 +1,200 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/nuvoton,nau8310.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nuvoton NAU83G10/20 Mono Class-D Amplifier with DSP
+
+description:
+ Mono Class-D Amplifier with DSP and I/V-sense.
+ This device supports I2C.
+
+maintainers:
+ - Neo Chang <YLCHANG2@nuvoton.com>
+
+properties:
+ compatible:
+ enum:
+ - nuvoton,nau8310
+ - nuvoton,nau8320
+
+ reg:
+ description: The I2C address is determined by the hardware pins.
+ enum: [0x10, 0x11, 0x40, 0x41]
+
+ "#sound-dai-cells":
+ const: 0
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ const: mclk
+
+ firmware-name:
+ items:
+ - description:
+ DSP core firmware.
+
+ nuvoton,vref-impedance:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ VREF Impedance Selection.
+ enum:
+ - 0 # Open
+ - 1 # 25 kOhm
+ - 2 # 125 kOhm
+ - 3 # 2.5 kOhm
+ default: 0
+
+ nuvoton,dac-vref:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ DAC Reference Voltage Setting.
+ enum:
+ - 0 # Voltage source directly from VDDA pin
+ - 1 # VDDA * 1.5/1.8V
+ - 2 # VDDA * 1.6/1.8V
+ - 3 # VDDA * 1.7/1.8V
+ default: 1
+
+ nuvoton,sar-voltage:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ SAR Gain Tracking Selection.
+ enum:
+ - 0 # VDDA
+ - 1 # VDDA * 1.0
+ - 2 # VDDA * 1.1
+ - 3 # VDDA * 1.2
+ - 4 # VDDA * 1.3
+ - 5 # VDDA * 1.4
+ - 6 # VDDA * 1.54
+ - 7 # VDDA * 3.12
+ default: 0
+
+ nuvoton,sar-compare-time:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ SAR compare time.
+ enum:
+ - 0 # 500 ns
+ - 1 # 1 us
+ - 2 # 2 us
+ - 3 # 4 us
+ default: 1
+
+ nuvoton,sar-sampling-time:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ SAR sampling time.
+ enum:
+ - 0 # 2 us
+ - 1 # 4 us
+ - 2 # 8 us
+ - 3 # 16 us
+ default: 1
+
+ nuvoton,clock-det-disable:
+ type: boolean
+ description:
+ Enable Clock Detection Module.
+
+ nuvoton,clock-det-data:
+ type: boolean
+ description:
+ Clock detection non-zero samples configuration for power up signal.
+
+ nuvoton,boost-delay:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Boost Control Hold Timer in decrement (msec).
+ enum:
+ - 0 # 0 msec
+ - 1 # 1 msec
+ - 2 # 2 msec
+ - 3 # 4 msec
+ - 4 # 8 msec
+ - 5 # 16 msec
+ - 6 # 32 msec
+ - 7 # 64 msec
+ - 8 # 128 msec
+ default: 0
+
+ nuvoton,boost-convert-enable:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Boost Convert Enable.
+ enum:
+ - 0 # BOOST CONVERTER disabled
+ - 1 # BOOST CONVERTER enable gated by POWERUP signal
+ - 2 # BOOST CONVERTER disabled
+ - 3 # BOOST CONVERTER enabled
+ default: 0
+
+ nuvoton,boost-target-limit:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Boost Control Target Value Limit.
+ The value ranges from 0 to 63, representing 0V to 12V with a 0.19V step.
+ minimum: 0
+ maximum: 63
+ default: 63
+
+ nuvoton,boost-target-margin:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Boost Margin Value between Boost Target Voltage and peak output level.
+ The value ranges from 0 to 63, representing 0V to 12V with a 0.19V step.
+ minimum: 0
+ maximum: 63
+ default: 0
+
+ nuvoton,temp-compensation:
+ type: boolean
+ description:
+ Enable Temperature Compensation.
+
+ nuvoton,normal-iis-data:
+ type: boolean
+ description:
+ Enable Normal I2S Audio Data mode. The I2S interface
+ transmits only audio data without SAR ADC data.
+
+required:
+ - compatible
+ - reg
+ - firmware-name
+
+allOf:
+ - $ref: dai-common.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ codec@10 {
+ compatible = "nuvoton,nau8310";
+ reg = <0x10>;
+ #sound-dai-cells = <0>;
+ firmware-name = "NAU83G10.kcs.bin";
+ nuvoton,vref-impedance = <2>;
+ nuvoton,dac-vref = <1>;
+ nuvoton,sar-voltage = <0>;
+ nuvoton,sar-compare-time = <1>;
+ nuvoton,sar-sampling-time = <1>;
+ nuvoton,clock-det-disable;
+ nuvoton,clock-det-data;
+ nuvoton,boost-delay = <0x8>;
+ nuvoton,boost-convert-enable = <1>;
+ nuvoton,boost-target-limit = <0x32>;
+ nuvoton,boost-target-margin = <0x2>;
+ nuvoton,temp-compensation;
+ nuvoton,normal-iis-data;
+ };
+ };
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v1 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier
2026-10-22 8:40 [PATCH v1 0/2] ASoC: codecs: nau8310: Add NAU83G10 amplifier Neo Chang
2026-10-22 8:40 ` [PATCH v1 1/2] ASoC: dt-bindings: nuvoton,nau8310: Add NAU83G10 Neo Chang
@ 2026-10-22 8:40 ` Neo Chang
2026-10-22 10:09 ` sashiko-bot
2026-10-23 9:35 ` kernel test robot
1 sibling, 2 replies; 6+ messages in thread
From: Neo Chang @ 2026-10-22 8:40 UTC (permalink / raw)
To: broonie
Cc: lgirdwood, perex, robh, krzk+dt, linux-sound, devicetree,
alsa-devel, neo.chang70, kchsu0, sjlin0, Neo Chang
Add support for the Nuvoton NAU83G10 audio amplifier.
The NAU83G10 is a mono boost Class-D audio amplifier with
integrated DSP and I/V-sense support.
Signed-off-by: Neo Chang <YLCHANG2@nuvoton.com>
---
sound/soc/codecs/Kconfig | 13 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/nau8310-dsp.c | 690 ++++++++++++
sound/soc/codecs/nau8310-dsp.h | 111 ++
sound/soc/codecs/nau8310.c | 1845 ++++++++++++++++++++++++++++++++
sound/soc/codecs/nau8310.h | 629 +++++++++++
6 files changed, 3290 insertions(+)
create mode 100644 sound/soc/codecs/nau8310-dsp.c
create mode 100644 sound/soc/codecs/nau8310-dsp.h
create mode 100644 sound/soc/codecs/nau8310.c
create mode 100644 sound/soc/codecs/nau8310.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index ba59dabeeccd..d5a639c4d581 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -174,6 +174,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_MT6660
imply SND_SOC_NAU8315
imply SND_SOC_NAU8325
+ imply SND_SOC_NAU8310
imply SND_SOC_NAU8360
imply SND_SOC_NAU8540
imply SND_SOC_NAU8810
@@ -2940,6 +2941,18 @@ config SND_SOC_NAU8825
tristate
depends on I2C
+config SND_SOC_NAU8310
+ tristate "Nuvoton Technology Corporation NAU83G10 Mono Boosted Amplifier"
+ depends on I2C
+ select FW_LOADER
+ help
+ The NAU83G10 is a mono boosted Class-D amplifier with an integrated
+ DSP and I/V sense. The DSP is programmed with a speaker excursion
+ control algorithm that uses real-time voltage and current feedback
+ to prevent the speaker membrane from exceeding its rated limit.
+ It has a high-efficiency Class-H boost converter to provide
+ ample headroom for overall loudness and sound quality.
+
config SND_SOC_NAU8360
tristate "Nuvoton Technology Corporation NAU83G60 Stereo Smart Amplifier"
depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 3826f9e4fce8..81efb10e7db5 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -199,6 +199,7 @@ snd-soc-mt6359-accdet-y := mt6359-accdet.o
snd-soc-mt6660-y := mt6660.o
snd-soc-nau8315-y := nau8315.o
snd-soc-nau8325-y := nau8325.o
+snd-soc-nau8310-y := nau8310.o nau8310-dsp.o
snd-soc-nau8360-y := nau8360.o nau8360-dsp.o
snd-soc-nau8540-y := nau8540.o
snd-soc-nau8810-y := nau8810.o
@@ -642,6 +643,7 @@ obj-$(CONFIG_SND_SOC_MT6359_ACCDET) += mt6359-accdet.o
obj-$(CONFIG_SND_SOC_MT6660) += snd-soc-mt6660.o
obj-$(CONFIG_SND_SOC_NAU8315) += snd-soc-nau8315.o
obj-$(CONFIG_SND_SOC_NAU8325) += snd-soc-nau8325.o
+obj-$(CONFIG_SND_SOC_NAU8310) += snd-soc-nau8310.o
obj-$(CONFIG_SND_SOC_NAU8360) += snd-soc-nau8360.o
obj-$(CONFIG_SND_SOC_NAU8540) += snd-soc-nau8540.o
obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o
diff --git a/sound/soc/codecs/nau8310-dsp.c b/sound/soc/codecs/nau8310-dsp.c
new file mode 100644
index 000000000000..7e8606beb208
--- /dev/null
+++ b/sound/soc/codecs/nau8310-dsp.c
@@ -0,0 +1,690 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// The NAU83G10/20 Boosted Mono Class-D Amplifier with DSP and I/V-sense driver.
+//
+// Copyright 2026 Nuvoton Technology Crop.
+// Author: John Hsu <KCHSU0@nuvoton.com>
+// David Lin <ctlin0@nuvoton.com>
+// Neo Chang <ylchang2@nuvoton.com>
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/bitfield.h>
+#include <sound/soc.h>
+
+#include "nau8310-dsp.h"
+#include "nau8310.h"
+
+#define NAU8310_DSP_IDLE_RETRY 10
+
+static int nau8310_dsp_set_kcs_setup(struct snd_soc_component *component);
+
+#define NAU_DSP_CMD(_id, _msg, _setup, _reply) \
+ [_id] = { \
+ .cmd_id = _id, \
+ .msg_param = _msg, \
+ .setup_data = _setup, \
+ .reply_data = _reply, \
+ }
+
+#define NAU_DSP_CMD_ID(_id) \
+ [_id] = { \
+ .cmd_id = _id, \
+ }
+
+/*
+ * Preamble Fragment Masks:
+ * FIELD | BITS | SHIFT | MASK | DETAILS
+ * ----------------|--------|-------|------|---------------------------------
+ * length high | [31:24]| 24 | 0xff | Upper 8 bits of total length (LEN[9:2])
+ * cmd_id | [23:18]| 18 | 0x3f | Command identifier (6 bits)
+ * length low | [17:16]| 16 | 0x03 | Lower 2 bits of total length (LEN[1:0])
+ * preamble magic | [15:0] | 0 |0xffff| Preamble signature (0xB2A1)
+ */
+#define NAU8310_HOST_LEN_HIGH_MASK GENMASK(31, 24)
+#define NAU8310_HOST_CMD_ID_MASK GENMASK(23, 18)
+#define NAU8310_HOST_LEN_LOW_MASK GENMASK(17, 16)
+#define NAU8310_HOST_PREAMBLE_MASK GENMASK(15, 0)
+
+/*
+ * Payload Fragment Masks:
+ * FIELD | BITS | SHIFT | MASK | DETAILS
+ * ----------------|--------|-------|------|---------------------------------
+ * param_size | [31:16]| 16 |0xffff| Size of parameter data (16 bits)
+ * param_offset | [15:0] | 0 |0xffff| Starting offset (16 bits)
+ */
+#define NAU8310_HOST_PARAM_SIZE_MASK GENMASK(31, 16)
+#define NAU8310_HOST_PARAM_OFFSET_MASK GENMASK(15, 0)
+
+/*
+ * Trailing Fragment Masks:
+ * FIELD | BITS | SHIFT | MASK | DETAILS
+ * ----------------|--------|-------|------|---------------------------------
+ * length high | [15:14]| 14 | 0x03 | Upper 2 bits of total length
+ * padding | [13:12]| 12 | 0x03 | Padding bytes count
+ * length low | [7:0] | 0 | 0xff | Lower 8 bits of total length
+ * Note: Reassembled length = (length_high << 8) | length_low (Total 10 bits)
+ */
+#define NAU8310_TRAIL_LEN_LOW_MASK GENMASK(7, 0)
+#define NAU8310_TRAIL_PAD_MASK GENMASK(13, 12)
+#define NAU8310_TRAIL_LEN_HIGH_MASK GENMASK(15, 14)
+
+/*
+ * Reply Preamble Masks:
+ * FIELD | BITS | SHIFT | MASK | DETAILS
+ * ----------------|--------|-------|------|---------------------------------
+ * length high | [31:24]| 24 | 0xff | Upper 8 bits of total length
+ * reply_id | [23:18]| 18 | 0x3f | Reply identifier (6 bits)
+ * length low | [17:16]| 16 | 0x03 | Lower 2 bits of total length
+ * Note: Reassembled length = (length_high << 2) | length_low (Total 10 bits)
+ */
+#define NAU8310_REPLY_LEN_HIGH_MASK GENMASK(31, 24)
+#define NAU8310_REPLY_ID_MASK GENMASK(23, 18)
+#define NAU8310_REPLY_LEN_LOW_MASK GENMASK(17, 16)
+
+static const struct nau8310_cmd_info nau8310_dsp_cmd_table[] = {
+ /* { cmd_id, msg_param, setup_data, reply_data} */
+ NAU_DSP_CMD(NAU8310_DSP_CMD_GET_COUNTER, 0, 0, 1),
+ NAU_DSP_CMD(NAU8310_DSP_CMD_GET_FRAME_STATUS, 0, 0, 1),
+ NAU_DSP_CMD(NAU8310_DSP_CMD_GET_REVISION, 0, 0, 1),
+ NAU_DSP_CMD(NAU8310_DSP_CMD_GET_KCS_RSLTS, 1, 0, 1),
+ NAU_DSP_CMD(NAU8310_DSP_CMD_GET_KCS_SETUP, 1, 0, 1),
+ NAU_DSP_CMD(NAU8310_DSP_CMD_SET_KCS_SETUP, 1, 1, 0),
+ NAU_DSP_CMD_ID(NAU8310_DSP_CMD_CLK_STOP),
+ NAU_DSP_CMD_ID(NAU8310_DSP_CMD_CLK_RESTART),
+};
+
+static const char *const dsp_cmd_table[] = {
+ [NAU8310_DSP_CMD_GET_COUNTER] = "GET_COUNTER",
+ [NAU8310_DSP_CMD_GET_FRAME_STATUS] = "GET_FRAME_STATUS",
+ [NAU8310_DSP_CMD_GET_REVISION] = "GET_REVISION",
+ [NAU8310_DSP_CMD_GET_KCS_RSLTS] = "GET_KCS_RSLTS",
+ [NAU8310_DSP_CMD_GET_KCS_SETUP] = "GET_KCS_SETUP",
+ [NAU8310_DSP_CMD_SET_KCS_SETUP] = "SET_KCS_SETUP",
+ [NAU8310_DSP_CMD_CLK_STOP] = "CLK_STOP",
+ [NAU8310_DSP_CMD_CLK_RESTART] = "CLK_RESTART",
+};
+
+static void nau8310_sw_reset_chip(struct regmap *regmap)
+{
+ regmap_write(regmap, NAU8310_R01_SOFTWARE_RST, 0x00);
+ regmap_write(regmap, NAU8310_R01_SOFTWARE_RST, 0x00);
+}
+
+static int nau8310_dsp_idle(struct snd_soc_component *cp)
+{
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
+ struct regmap *regmap = nau8310->regmap;
+ unsigned int idle_pattern, timeout = NAU8310_DSP_IDLE_RETRY * USEC_PER_MSEC;
+ int ret = 0;
+
+ ret = regmap_read_poll_timeout(regmap,
+ NAU8310_RF000_DSP_COMM, idle_pattern,
+ idle_pattern == NAU8310_DSP_COMM_IDLE_WORD, USEC_PER_MSEC, timeout);
+ if (ret) {
+ dev_err(nau8310->dev, "Timeout waiting for DSP idle state: %d", ret);
+ nau8310_sw_reset_chip(regmap);
+ }
+
+ return ret;
+}
+
+/*
+ * nau8310_pack_preamble - Pack DSP preamble fragment
+ * @cmd_id: Command ID for the DSP message
+ * @frag_len: Total length of the message fragments
+ *
+ * Return: 32-bit packed payload in Little Endian format.
+ */
+static inline u32 nau8310_pack_preamble(u8 cmd_id, u16 frag_len)
+{
+ return FIELD_PREP(NAU8310_HOST_PREAMBLE_MASK, NAU8310_DSP_COMM_PREAMBLE) |
+ FIELD_PREP(NAU8310_HOST_CMD_ID_MASK, cmd_id) |
+ FIELD_PREP(NAU8310_HOST_LEN_LOW_MASK, frag_len) |
+ FIELD_PREP(NAU8310_HOST_LEN_HIGH_MASK, frag_len >> 2);
+}
+
+/*
+ * nau8310_pack_param - Pack DSP parameter fragment
+ * @param_offset: Starting offset of the parameter data
+ * @param_size: Size of the parameter data in bytes
+ *
+ * Return: 32-bit packed payload in Little Endian format.
+ */
+static inline u32 nau8310_pack_param(u16 param_offset, u16 param_size)
+{
+ return FIELD_PREP(NAU8310_HOST_PARAM_OFFSET_MASK, param_offset) |
+ FIELD_PREP(NAU8310_HOST_PARAM_SIZE_MASK, param_size);
+}
+
+/*
+ * nau8310_pack_trailing - Pack DSP trailing fragment
+ * @frag_cnt: Current fragment count
+ * @padding: Number of padding bytes added to the final data fragment
+ *
+ * Return: 32-bit packed payload in Little Endian format.
+ */
+static inline u32 nau8310_pack_trailing(u16 frag_cnt, u8 padding)
+{
+ return FIELD_PREP(NAU8310_TRAIL_LEN_LOW_MASK, frag_cnt) |
+ FIELD_PREP(NAU8310_TRAIL_PAD_MASK, padding) |
+ FIELD_PREP(NAU8310_TRAIL_LEN_HIGH_MASK, frag_cnt >> 8);
+}
+
+static void nau8310_send_data_payload(struct snd_soc_component *cp,
+ unsigned short dsp_addr, const void *param_data, int param_size,
+ int *frag_cnt, int *padding)
+{
+ const u8 *data = (const u8 *)param_data;
+ u32 payload = 0;
+ int i, data_size = 0;
+
+ for (i = 0; i < param_size; i++) {
+ payload |= data[i] << (data_size * 8);
+ data_size++;
+
+ if (data_size == NAU8310_DSP_DATA_BYTE) {
+ snd_soc_component_write(cp, dsp_addr, payload);
+
+ data_size = 0;
+ payload = 0;
+ (*frag_cnt)++;
+ }
+ }
+
+ if (data_size > 0) {
+ *padding = NAU8310_DSP_DATA_BYTE - data_size;
+ snd_soc_component_write(cp, dsp_addr, payload);
+ (*frag_cnt)++;
+ }
+}
+
+static int nau8310_message_to_dsp(struct snd_soc_component *cp,
+ const struct nau8310_cmd_info *cmd_info, int frag_len,
+ int param_offset, int param_size, void *param_data)
+{
+ unsigned int payload;
+ int ret, padding = 0, frag_cnt = 0;
+
+ ret = nau8310_dsp_idle(cp);
+ if (ret)
+ return ret;
+
+ /* sending preamble fragment */
+ payload = nau8310_pack_preamble(cmd_info->cmd_id, frag_len);
+ snd_soc_component_write(cp, NAU8310_RF000_DSP_COMM, payload);
+
+ if (!cmd_info->msg_param)
+ return 0;
+
+ /* sending payload + padding */
+ payload = nau8310_pack_param(param_offset, param_size);
+ snd_soc_component_write(cp, NAU8310_RF000_DSP_COMM, payload);
+ frag_cnt++;
+
+ if (cmd_info->setup_data)
+ nau8310_send_data_payload(cp, NAU8310_RF000_DSP_COMM, param_data,
+ param_size, &frag_cnt, &padding);
+
+ /* sending trailing fragment */
+ frag_cnt++;
+ payload = nau8310_pack_trailing(frag_cnt, padding);
+ snd_soc_component_write(cp, NAU8310_RF000_DSP_COMM, payload);
+
+ if (frag_cnt != frag_len) {
+ dev_err(cp->dev, "message error (CMD_ID 0x%x, LEN 0x%x) !!!",
+ cmd_info->cmd_id, frag_cnt);
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+static int nau8310_dsp_replied(struct nau8310 *nau8310, int *length)
+{
+ unsigned int reply_preamble;
+ int ret, reply_id;
+
+ ret = regmap_read_poll_timeout(nau8310->regmap,
+ NAU8310_RF000_DSP_COMM, reply_preamble,
+ (reply_preamble & 0xffff) == NAU8310_DSP_COMM_PREAMBLE,
+ USEC_PER_MSEC, NAU8310_DSP_IDLE_RETRY * USEC_PER_MSEC);
+ if (ret) {
+ dev_err(nau8310->dev, "timeout for reply preamble: %d", ret);
+ return ret;
+ }
+
+ *length = FIELD_GET(NAU8310_REPLY_LEN_LOW_MASK, reply_preamble);
+ *length |= FIELD_GET(NAU8310_REPLY_LEN_HIGH_MASK, reply_preamble) << 2;
+ reply_id = FIELD_GET(NAU8310_REPLY_ID_MASK, reply_preamble);
+
+ return (reply_id != NAU8310_DSP_REPLY_OK) ? -reply_id : 0;
+}
+
+static int nau8310_validate_trailing(struct snd_soc_component *cp,
+ int frag_len, int pad_len_exp)
+{
+ int ret, len_pos, pad_len;
+ unsigned int payload;
+ struct device *dev = cp->dev;
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
+
+ /* reading trailing fragment */
+ ret = regmap_read(nau8310->regmap, NAU8310_RF000_DSP_COMM, &payload);
+ if (ret) {
+ dev_err(dev, "failed to read trailing fragment");
+ return ret;
+ }
+
+ len_pos = FIELD_GET(NAU8310_TRAIL_LEN_LOW_MASK, payload);
+ len_pos |= FIELD_GET(NAU8310_TRAIL_LEN_HIGH_MASK, payload) << 8;
+
+ if (len_pos != frag_len) {
+ dev_err(dev, "LEN_POST %02X, expect %02X", len_pos, frag_len);
+ return -EPROTO;
+ }
+
+ pad_len = FIELD_GET(NAU8310_TRAIL_PAD_MASK, payload);
+ if (pad_len != pad_len_exp) {
+ dev_err(dev, "PAD_LEN %02X, expect %02X", pad_len, pad_len_exp);
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+static int nau8310_read_data_payload(struct snd_soc_component *cp,
+ int frag_len, bool msg_param,
+ void *data, int data_size, int *data_count)
+{
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
+ struct device *dev = cp->dev;
+ unsigned int payload;
+ u32 *data_buf = data;
+ int i, j, ret;
+
+ *data_count = (msg_param) ? data_size : 0;
+ for (i = 0; i < frag_len - 1; i++) {
+ ret = regmap_read(nau8310->regmap, NAU8310_RF000_DSP_COMM, &payload);
+ if (ret) {
+ dev_err(dev, "failed to read payload of dsp");
+ return ret;
+ }
+
+ if (!msg_param) {
+ *data_buf++ = payload;
+ break;
+ }
+
+ if (*data_count >= NAU8310_DSP_DATA_BYTE) {
+ *data_buf++ = payload;
+ *data_count -= NAU8310_DSP_DATA_BYTE;
+ } else if (*data_count > 0) {
+ for (j = 0; j < *data_count; j++)
+ ((u8 *)data_buf)[j] = (payload >> (j * 8)) & 0xff;
+
+ *data_count = 0;
+ }
+
+ if (*data_count == 0)
+ break;
+ }
+
+ return 0;
+}
+
+static int nau8310_reply_from_dsp(struct snd_soc_component *cp,
+ const struct nau8310_cmd_info *cmd_info, int data_size,
+ void *data)
+{
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
+ struct device *dev = cp->dev;
+ int ret, frag_len, pad_len_exp, data_count = 0;
+
+ ret = nau8310_dsp_replied(nau8310, &frag_len);
+ if (ret)
+ return ret;
+ if (!frag_len || !cmd_info->reply_data)
+ return 0;
+ if (!data || frag_len == 1)
+ return -EINVAL;
+
+ ret = nau8310_read_data_payload(cp, frag_len,
+ cmd_info->msg_param, data, data_size, &data_count);
+ if (ret)
+ return ret;
+
+ /* check the reply length same as request */
+ if (data_count && (cmd_info->cmd_id == NAU8310_DSP_CMD_GET_KCS_RSLTS ||
+ cmd_info->cmd_id == NAU8310_DSP_CMD_GET_KCS_SETUP))
+ dev_warn(dev, "payload_len %d, expected %d",
+ data_size - data_count, data_size);
+
+ pad_len_exp = cmd_info->msg_param ?
+ (frag_len - 1) * NAU8310_DSP_DATA_BYTE - (data_size - data_count) : 0;
+ return nau8310_validate_trailing(cp, frag_len, pad_len_exp);
+}
+
+/*
+ * nau8310_send_dsp_command - Send command to DSP
+ *
+ * @cp: component to register
+ * @cmd_id: DSP supported command ID
+ * @kcs_setup: KCS setup structure
+ *
+ * The communication protocol is a Master-Slave type protocol
+ * where the host processor is the master and DSP is the slave.
+ * The Master initiates the communication and can either write or
+ * read back from the slave.
+ * Transactions from the Master are called "Messages",
+ * and read-back data from the Slave is called a "Reply".
+ *
+ * The function sends command to DSP according to the command ID.
+ * These commands include getting the information of DSP,
+ * getting or setting KCS configuration, or making DSP control.
+ */
+static int nau8310_send_dsp_command(struct snd_soc_component *cp, int cmd_id,
+ struct nau8310_kcs_setup *kcs_setup)
+{
+ const struct nau8310_cmd_info *cmd_info;
+ int ret = 0, frag_len = 0;
+
+ cmd_info = &nau8310_dsp_cmd_table[cmd_id];
+ if ((cmd_info->msg_param && !kcs_setup->set_len) ||
+ (cmd_info->setup_data && !kcs_setup->set_kcs_data) ||
+ (cmd_info->reply_data && !kcs_setup->get_data))
+ return -EFAULT;
+
+ /* Read up to 1kB data because the LEN field to request data is 10-bits
+ * long; and not beyond 3kB offset.
+ */
+ if (cmd_id == NAU8310_DSP_CMD_GET_KCS_SETUP &&
+ (kcs_setup->set_len > NAU8310_DSP_KCS_DAT_LEN_MAX ||
+ kcs_setup->set_kcs_offset > NAU8310_DSP_KCS_OFFSET_MAX))
+ return -ERANGE;
+
+ /* one fragment for offset and size parameters
+ * one fragment for a postamble fragment
+ */
+ if (cmd_info->msg_param)
+ frag_len += 2;
+
+ /* fragments for KCS setup writen */
+ if (cmd_info->setup_data)
+ frag_len += DIV_ROUND_UP(kcs_setup->set_len, NAU8310_DSP_DATA_BYTE);
+
+ ret = nau8310_message_to_dsp(cp, cmd_info, frag_len,
+ kcs_setup->set_kcs_offset, kcs_setup->set_len,
+ kcs_setup->set_kcs_data);
+ if (ret)
+ return ret;
+
+ return nau8310_reply_from_dsp(cp, cmd_info, kcs_setup->get_len,
+ kcs_setup->get_data);
+}
+
+static inline int nau8310_dsp_exec_command(struct snd_soc_component *cp, int cmd_id,
+ int offset, int set_len, void *set_data, int get_len, void *get_data)
+{
+ struct nau8310_kcs_setup kcs_setup = {
+ .set_kcs_offset = offset,
+ .set_len = set_len,
+ .set_kcs_data = set_data,
+ .get_len = get_len,
+ .get_data = get_data,
+ };
+
+ return nau8310_send_dsp_command(cp, cmd_id, &kcs_setup);
+}
+
+/*
+ * nau8310_dsp_kcs_setup - Send KCS setup command to DSP
+ *
+ * @cp: component to register
+ * @offset: address offset relative to KCS start
+ * @size: size of data writen to KCS
+ * @data: data writen to KCS setup
+ *
+ * The function sends KCS setup command to DSP for
+ * setting KCS configuration. The maximum size that you can transfer into
+ * the DSP is 96 bytes. Therefore, the driver has to split the data into
+ * 96 bytes chucks, if the setup configuration over the threshold.
+ */
+static int nau8310_dsp_kcs_setup(struct snd_soc_component *cp, int offset, int size,
+ const void *data)
+{
+ u8 *data_buf = (u8 *)data;
+ unsigned int kcs_rst;
+ int retries = 0, ret, data_len, data_rem, addr_offset;
+
+ /* Limit full load of KCS_SETUP data and not beyond 3kB offset. */
+ if (!data || size > NAU8310_DSP_KCS_DAT_LEN_MAX ||
+ offset > NAU8310_DSP_KCS_OFFSET_MAX)
+ return -EINVAL;
+
+ /* sending fragments for KCS setup */
+ addr_offset = offset;
+ data_rem = size;
+
+ while (data_rem) {
+ data_len = min(data_rem, NAU8310_DSP_KCS_TX_MAX);
+
+ ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_SET_KCS_SETUP,
+ addr_offset, data_len, (void *)data_buf, 0, &kcs_rst);
+ if (ret) {
+ if (retries++ < NAU8310_DSP_RETRY_MAX)
+ continue;
+ return ret;
+ }
+
+ data_buf += (u8)data_len;
+ addr_offset += data_len;
+ data_rem -= data_len;
+
+ /* checking KCS result */
+ ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_GET_KCS_RSLTS,
+ 0, NAU8310_DSP_DATA_BYTE, NULL, NAU8310_DSP_DATA_BYTE, &kcs_rst);
+ if (ret)
+ return ret;
+ if (kcs_rst != NAU8310_DSP_KCS_RSLTS_SUCCESS)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nau8310_dsp_snd_soc_dapm_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int mask = (1 << fls(mc->max)) - 1;
+ unsigned int val;
+ int ret;
+
+ if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) {
+ dev_warn_ratelimited(nau8310->dev, "changing path is not allowed during playback");
+ return -EBUSY;
+ }
+
+ val = (ucontrol->value.integer.value[0] & mask);
+
+ mutex_lock(&nau8310->lock);
+ if (val == NAU8310_DAC_SRC_DSP && !nau8310->load_fw_done) {
+ dev_warn_ratelimited(nau8310->dev, "Cannot enable DSP: Firmware not ready or disabled");
+ mutex_unlock(&nau8310->lock);
+ return -EBUSY;
+ }
+
+ ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+ if (val == NAU8310_DAC_SRC_BYPASS) {
+ /* Switch the clock source of DSP to MCLK. */
+ regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
+ NAU8310_DSP_SEL_OSC, 0);
+ }
+ mutex_unlock(&nau8310->lock);
+
+ return ret;
+}
+
+#define NAU8310_DSP_SOC_DAPM_SINGLE(xname, reg, shift, mask, invert) \
+ SOC_SINGLE_EXT(xname, reg, shift, mask, invert, \
+ snd_soc_dapm_get_volsw, nau8310_dsp_snd_soc_dapm_put)
+
+static const struct snd_kcontrol_new nau8310_dacdat_select_dsp =
+ NAU8310_DSP_SOC_DAPM_SINGLE("Switch", NAU8310_R1A_DSP_CORE_CTRL2,
+ NAU8310_DAC_SEL_DSP_SFT, 1, 0);
+
+static int nau8310_dsp_clock_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm);
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
+ int ret = 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_CLK_RESTART,
+ 0, 0, NULL, 0, NULL);
+ if (ret)
+ dev_err(cp->dev, "Send DSP command %s fail (%d)",
+ dsp_cmd_table[NAU8310_DSP_CMD_CLK_RESTART], ret);
+
+ /* Switch the clock source of DSP to MCLK. */
+ regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
+ NAU8310_DSP_SEL_OSC, 0);
+ } else if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_CLK_STOP,
+ 0, 0, NULL, 0, NULL);
+ if (ret)
+ dev_err(cp->dev, "Send DSP command %s fail (%d)",
+ dsp_cmd_table[NAU8310_DSP_CMD_CLK_STOP], ret);
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget nau8310_dsp_dapm_widgets[] = {
+ SND_SOC_DAPM_PGA("DSP", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_SWITCH("DAC data from DSP", SND_SOC_NOPM,
+ 0, 0, &nau8310_dacdat_select_dsp),
+ SND_SOC_DAPM_SUPPLY("DSP Clock", SND_SOC_NOPM, 0, 0,
+ nau8310_dsp_clock_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route nau8310_dsp_dapm_routes[] = {
+ { "DSP", NULL, "Capture Sense" },
+ { "DAC data from DSP", "Switch", "DSP" },
+ { "DAC", NULL, "DAC data from DSP" },
+ { "DSP", NULL, "DSP Clock" },
+};
+
+static int nau8310_dsp_set_kcs_setup(struct snd_soc_component *cp)
+{
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
+ struct regmap *regmap = nau8310->regmap;
+ struct nau8310_kcs_setup kcs_setup_comp, *kcs_setup = &kcs_setup_comp;
+ struct device *dev = cp->dev;
+ const struct firmware *fw;
+ int ret, status = 0, buf_off, buf_len;
+
+ dev_dbg(dev, "Send DSP command %s",
+ dsp_cmd_table[NAU8310_DSP_CMD_GET_FRAME_STATUS]);
+
+ kcs_setup->get_len = kcs_setup->set_len = sizeof(status);
+ kcs_setup->get_data = (void *)&status;
+
+ ret = nau8310_send_dsp_command(cp, NAU8310_DSP_CMD_GET_FRAME_STATUS, kcs_setup);
+ if (ret) {
+ dev_err(dev, "Send DSP command %s fail (%d)",
+ dsp_cmd_table[NAU8310_DSP_CMD_GET_FRAME_STATUS], ret);
+ goto err;
+ }
+
+ if (!(status & NAU8310_DSP_ALGO_OK)) {
+ dev_err(dev, "Algorithm of DSP is not ready, status %x", status);
+ ret = -EIO;
+ goto err;
+ }
+
+ dev_info(dev, "Algorithm of DSP is ready, status %x", status);
+
+ dev_dbg(dev, "Request firmware and wait until finish.");
+ ret = request_firmware(&fw, NAU8310_DSP_FIRMWARE, dev);
+ if (ret) {
+ dev_err(dev, "Failed to load firmware (%d)", ret);
+ goto err;
+ }
+
+ buf_off = 0;
+ buf_len = nau8310->kcs_setup_size = fw->size;
+ dev_dbg(dev, "Send DSP command %s (OFF %d, LEN %d)",
+ dsp_cmd_table[NAU8310_DSP_CMD_SET_KCS_SETUP], buf_off, buf_len);
+ ret = nau8310_dsp_kcs_setup(cp, buf_off, buf_len, fw->data);
+ if (ret) {
+ dev_err(dev, "Send DSP command %s fail (%d)",
+ dsp_cmd_table[NAU8310_DSP_CMD_SET_KCS_SETUP], ret);
+ goto err_loaded;
+ }
+
+ release_firmware(fw);
+ nau8310->load_fw_done = true;
+
+ return 0;
+
+err_loaded:
+ regmap_update_bits(regmap, NAU8310_R13_MUTE_CTRL, NAU8310_SOFT_MUTE, 0);
+ if (fw)
+ release_firmware(fw);
+err:
+ nau8310->load_fw_done = false;
+ return ret;
+}
+
+int nau8310_dsp_init(struct snd_soc_component *cp)
+{
+ struct regmap *regmap = cp->regmap;
+
+ nau8310_sw_reset_chip(regmap);
+
+ /* wait for the power ready */
+ msleep(120);
+
+ regmap_update_bits(regmap, NAU8310_R1A_DSP_CORE_CTRL2,
+ NAU8310_DSP_RUNSTALL | NAU8310_DAC_SEL_DSP_OUT, NAU8310_DAC_SEL_DSP_OUT);
+
+ return nau8310_dsp_set_kcs_setup(cp);
+}
+
+int nau8310_dsp_setup_controls(struct snd_soc_component *cp)
+{
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
+ struct snd_soc_dapm_context *dapm = nau8310->dapm;
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, nau8310_dsp_dapm_widgets,
+ ARRAY_SIZE(nau8310_dsp_dapm_widgets));
+ if (ret) {
+ dev_err(cp->dev, "Add DSP widget fail (%d)", ret);
+ goto err;
+ }
+
+ ret = snd_soc_dapm_add_routes(dapm, nau8310_dsp_dapm_routes,
+ ARRAY_SIZE(nau8310_dsp_dapm_routes));
+ if (ret) {
+ dev_err(cp->dev, "Add DSP route fail (%d)", ret);
+ goto err;
+ }
+
+ return 0;
+err:
+ snd_soc_dapm_del_routes(dapm, nau8310_dsp_dapm_routes,
+ ARRAY_SIZE(nau8310_dsp_dapm_routes));
+ return ret;
+}
diff --git a/sound/soc/codecs/nau8310-dsp.h b/sound/soc/codecs/nau8310-dsp.h
new file mode 100644
index 000000000000..a8301b9df8cb
--- /dev/null
+++ b/sound/soc/codecs/nau8310-dsp.h
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// The NAU83G10/20 Boosted Mono Class-D Amplifier with DSP and I/V-sense driver.
+//
+// Copyright 2026 Nuvoton Technology Crop.
+// Author: John Hsu <KCHSU0@nuvoton.com>
+// David Lin <ctlin0@nuvoton.com>
+// Neo Chang <ylchang2@nuvoton.com>
+
+#ifndef __NAU8310_DSP_H__
+#define __NAU8310_DSP_H__
+
+#define NAU8310_DSP_COMM_IDLE_WORD 0xf4f3f2f1
+#define NAU8310_DSP_COMM_PREAMBLE 0xb2a1
+#define NAU8310_DSP_DATA_BYTE 4
+#define NAU8310_DSP_DATA_LEN (NAU8310_DSP_DATA_BYTE << 3)
+/* max bytes of data to transfer into DSP each time during the KCS setup */
+#define NAU8310_DSP_KCS_TX_MAX 96
+#define NAU8310_DSP_RETRY_MAX 3
+#define NAU8310_DSP_KCS_DAT_LEN_BITS 10
+#define NAU8310_DSP_KCS_DAT_LEN_MAX ((1 << NAU8310_DSP_KCS_DAT_LEN_BITS) - 1)
+#define NAU8310_DSP_KCS_OFFSET_MAX 3072
+#define NAU8310_DSP_KCS_RSLTS_SUCCESS 0x10
+
+/* FRAME_STATUS (0x9) */
+#define NAU8310_DSP_SNS_OVF_SFT 31
+#define NAU8310_DSP_SNS_OVF (0x1 << NAU8310_DSP_SNS_OVF_SFT)
+#define NAU8310_DSP_AUD_UVF_SFT 30
+#define NAU8310_DSP_AUD_UVF (0x1 << NAU8310_DSP_AUD_UVF_SFT)
+#define NAU8310_DSP_AUD_OVF_SFT 29
+#define NAU8310_DSP_AUD_OVF (0x1 << NAU8310_DSP_AUD_OVF_SFT)
+#define NAU8310_DSP_ALC_STS_SFT 28
+#define NAU8310_DSP_ALC_STS (0x1 << NAU8310_DSP_ALC_STS_SFT)
+#define NAU8310_DSP_CLK_STOP_SFT 25
+#define NAU8310_DSP_CLK_STOP (0x1 << NAU8310_DSP_CLK_STOP_SFT)
+#define NAU8310_DSP_OCP_OTP_SFT 24
+#define NAU8310_DSP_OCP_OTP (0x1 << NAU8310_DSP_OCP_OTP_SFT)
+#define NAU8310_DSP_UVLO_SFT 22
+#define NAU8310_DSP_UVLO (0x1 << NAU8310_DSP_UVLO_SFT)
+#define NAU8310_DSP_OVP_SFT 21
+#define NAU8310_DSP_OVP (0x1 << NAU8310_DSP_OVP_SFT)
+#define NAU8310_DSP_APWR_DWN_SFT 20
+#define NAU8310_DSP_APWR_DWN (0x1 << NAU8310_DSP_APWR_DWN_SFT)
+#define NAU8310_DSP_SNSR_RATE_SFT 12
+#define NAU8310_DSP_SNSR_RATE_MASK (0xff << NAU8310_DSP_SNSR_RATE_SFT)
+#define NAU8310_DSP_AUD_RATE_SFT 4
+#define NAU8310_DSP_AUD_RATE_MASK (0xff << NAU8310_DSP_AUD_RATE_SFT)
+#define NAU8310_DSP_FEED_TRU_SFT 1
+#define NAU8310_DSP_FEED_TRU (0x1 << NAU8310_DSP_FEED_TRU_SFT)
+#define NAU8310_DSP_ALGO_OK 0x1
+
+#define NAU8310_DSP_FIRMDIR "Nuvoton/"
+#define NAU8310_DSP_FIRMWARE NAU8310_DSP_FIRMDIR"NAU83G10.kcs.bin"
+#define NAU8310_DSP_FWHDR_SIZE 8
+#define NAU8310_DSP_FWHDR_SIREV 4
+#define NAU8310_DSP_FW_KCS_DATA NAU8310_DSP_FWHDR_SIZE
+
+enum {
+ NAU8310_DSP_REPLY_OK,
+ NAU8310_DSP_REPLY_MSG_INTEGRETY_ERR,
+ NAU8310_DSP_REPLY_EXECUTION_ERR,
+ NAU8310_DSP_REPLY_COMMAND_DOESNT_EXISTS_ERR,
+ NAU8310_DSP_REPLY_UNKNOWN_ERR,
+ NAU8310_DSP_REPLY_MSG_TOO_LONG,
+};
+
+enum {
+ NAU8310_DSP_CMD_GET_COUNTER = 0x1,
+ NAU8310_DSP_CMD_GET_FRAME_STATUS = 0x9,
+ NAU8310_DSP_CMD_GET_REVISION = 0xa,
+ NAU8310_DSP_CMD_GET_KCS_RSLTS = 0x4,
+ NAU8310_DSP_CMD_GET_KCS_SETUP = 0x6,
+ NAU8310_DSP_CMD_SET_KCS_SETUP = 0x7,
+ NAU8310_DSP_CMD_CLK_STOP = 0xb,
+ NAU8310_DSP_CMD_CLK_RESTART = 0xc,
+};
+
+/**
+ * struct nau8310_cmd_info - DSP command information structure
+ * @cmd_id: The command identification number
+ * @msg_param: Message parameters including offset, size, and data
+ * @setup_data: Setup data for write-only operations
+ * @reply_data: Reply data received from the DSP
+ */
+struct nau8310_cmd_info {
+ int cmd_id;
+ bool msg_param;
+ bool setup_data;
+ bool reply_data;
+};
+
+/**
+ * struct nau8310_kcs_setup - KCS setup configuration structure
+ * @set_len: Length of data written in bytes
+ * @set_kcs_offset: Address offset relative to the start of KCS
+ * @set_kcs_data: Pointer to the data written to KCS
+ * @get_len: Length of data from the reply in bytes
+ * @get_data: Pointer to the buffer for reply data
+ */
+struct nau8310_kcs_setup {
+ int set_len;
+ int set_kcs_offset;
+ void *set_kcs_data;
+ int get_len;
+ void *get_data;
+};
+
+int nau8310_dsp_init(struct snd_soc_component *component);
+int nau8310_dsp_setup_controls(struct snd_soc_component *component);
+
+#endif /* __NAU8310_DSP_H__ */
diff --git a/sound/soc/codecs/nau8310.c b/sound/soc/codecs/nau8310.c
new file mode 100644
index 000000000000..b4de2de33768
--- /dev/null
+++ b/sound/soc/codecs/nau8310.c
@@ -0,0 +1,1845 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// The NAU83G10/20 Boosted Mono Class-D Amplifier with DSP and I/V-sense driver.
+//
+// Copyright 2026 Nuvoton Technology Crop.
+// Author: John Hsu <KCHSU0@nuvoton.com>
+// David Lin <ctlin0@nuvoton.com>
+// Neo Chang <ylchang2@nuvoton.com>
+
+#include <linux/unaligned.h>
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "nau8310-dsp.h"
+#include "nau8310.h"
+
+/* Range of Master Clock MCLK (Hz) */
+#define MASTER_CLK_MIN 2048000
+#define MASTER_CLK_MAX 49152000
+/* DSP Clock (Hz) */
+#define DSP_CLK_MIN_MULTI_VALUE 2000
+#define DSP_CLK_MAX 102400000
+#define DSP_MCLK_HI_SPEED 12288000
+
+#define NAU8310_DEFAULT_SAMPLE_RATE 48000
+#define NAU8310_SYSCLK_MULT 256
+
+static const unsigned int nau8310_tx_slot_map[NAU8310_TDM_MAX_SLOTS] = {
+ NAU8310_ADC_V_SEL_SLOT0,
+ NAU8310_ADC_I_SEL_SLOT1,
+ NAU8310_ADC_V_SEL_SLOT2,
+ NAU8310_ADC_I_SEL_SLOT3,
+ NAU8310_ADC_V_SEL_SLOT4,
+ NAU8310_ADC_I_SEL_SLOT5,
+ NAU8310_ADC_V_SEL_SLOT6,
+ NAU8310_ADC_I_SEL_SLOT7
+};
+
+static const unsigned int nau8310_rx_slot_map[NAU8310_TDM_MAX_SLOTS] = {
+ NAU8310_DAC_SEL_SLOT0,
+ NAU8310_DAC_SEL_SLOT1,
+ NAU8310_DAC_SEL_SLOT2,
+ NAU8310_DAC_SEL_SLOT3,
+ NAU8310_DAC_SEL_SLOT4,
+ NAU8310_DAC_SEL_SLOT5,
+ NAU8310_DAC_SEL_SLOT6,
+ NAU8310_DAC_SEL_SLOT7,
+};
+
+static const struct nau8310_src_attr dsp_src_mult[] = {
+ /* param : power of 2 */
+ { 0, 0x3 },
+ { 1, 0x2 },
+ { 2, 0x1 },
+ { 3, 0x0 },
+ { 4, 0x4 },
+};
+
+/* scaling for MCLK source */
+#define CLK_PROC_BYPASS (-1)
+
+static const struct nau8310_src_attr mclk_n1_div[] = {
+ { 3, 0x2 },
+ { 1, 0x0 },
+ { 2, 0x1 },
+};
+
+static const struct nau8310_src_attr mclk_n2_div[] = {
+ /* param : power of 2 */
+ { 0, 0x0 },
+ { 1, 0x1 },
+ { 2, 0x2 },
+ { 3, 0x3 },
+};
+
+static const struct nau8310_src_attr mclk_src_mult[] = {
+ /* param : power of 2 */
+ { 0, 0x1 },
+ { 1, 0x2 },
+ { 2, 0x3 },
+ { 3, 0x4 },
+ { 4, 0x5 },
+};
+
+/* Sample Rate and MCLK_SRC selections */
+static const struct nau8310_srate_attr target_srate_table[] = {
+ /* { FS, range, max, { MCLK source }, adc_div} */
+ { 48000, 2, true, { 12288000, 19200000, 24000000 }, 0x1 },
+ { 16000, 1, false, { 4096000, 6400000, 8000000 }, 0x0 },
+ { 8000, 0, false, { 2048000, 3200000, 4000000 }, 0x0 },
+ { 44100, 2, true, { 11289600, 17640000, 22050000 }, 0x1 },
+ { 64000, 3, false, { 16384000, 25600000, 32000000 }, 0x1 },
+ { 96000, 3, true, { 24576000, 38400000, 48000000 }, (-1) },
+ { 12000, 0, true, { 3072000, 4800000, 6000000 }, 0x0 },
+ { 24000, 1, true, { 6144000, 9600000, 12000000 }, 0x2 },
+ { 32000, 2, false, { 8192000, 12800000, 16000000 }, 0x2 },
+};
+
+/* the maximum frequency of CLK_ADC and CLK_DAC */
+#define CLK_DA_AD_MAX 6144000
+
+/* over sampling rate */
+static const struct nau8310_osr_attr osr_dac_sel[] = {
+ { 64, 2 }, /* OSR 64, SRC 1/4 */
+ { 256, 0 }, /* OSR 256, SRC 1 */
+ { 128, 1 }, /* OSR 128, SRC 1/2 */
+ { 0, 0 },
+ { 32, 3 }, /* OSR 32, SRC 1/8 */
+};
+
+static const struct nau8310_osr_attr osr_adc_sel[] = {
+ { 32, 2 }, /* OSR 32, SRC 1/8 */
+ { 64, 1 }, /* OSR 64, SRC 1/4 */
+ { 128, 0 }, /* OSR 128, SRC 1/2 */
+};
+
+/* For a typical 48kHz frame sync I2S signal on DSP case, the ADC sampling rate
+ * must be 12kHz. So the specfied OSR to SRC divider is need to divide by 4.
+ */
+static const struct nau8310_osr_attr osr_adc_dsp_sel[] = {
+ { 0, 0 }, /* no such case */
+ { 64, 3 }, /* OSR 64, SRC 1/16 */
+ { 128, 2 }, /* OSR 128, SRC 1/8 */
+};
+
+static const struct reg_default nau8310_reg_defaults[] = {
+ { NAU8310_R02_I2C_ADDR, 0x0000 },
+ { NAU8310_R03_CLK_CTRL, 0x0000 },
+ { NAU8310_R04_ENA_CTRL, 0x0000 },
+ { NAU8310_R05_INTERRUPT_CTRL, 0x007f },
+ { NAU8310_R06_INT_CLR_STATUS, 0x0000 },
+ { NAU8310_R07_SAR_CTRL1, 0x00b4 },
+ { NAU8310_R08_GPIO124_CTRL, 0x0102 },
+ { NAU8310_R09_GPIOOUT, 0x0000 },
+ { NAU8310_R0A_IO_CTRL, 0x0000 },
+ { NAU8310_R0B_I2S_PCM_CTRL0, 0x0000 },
+ { NAU8310_R0C_TDM_CTRL, 0x0c00 },
+ { NAU8310_R0D_I2S_PCM_CTRL1, 0x000a },
+ { NAU8310_R0E_I2S_PCM_CTRL2, 0x8010 },
+ { NAU8310_R0F_LEFT_TIME_SLOT, 0x0000 },
+ { NAU8310_R10_RIGHT_TIME_SLOT, 0x0000 },
+ { NAU8310_R12_HPF_CTRL, 0x0010 },
+ { NAU8310_R13_MUTE_CTRL, 0x00cf },
+ { NAU8310_R14_ADC_VOL_CTRL, 0x0000 },
+ { NAU8310_R15_INT_COE, 0xa41f },
+ { NAU8310_R16_DEC_COE, 0xb7cd },
+ { NAU8310_R17_BOOST_CTRL1, 0x3f00 },
+ { NAU8310_R18_BOOST_CTRL2, 0x0000 },
+ { NAU8310_R19_DSP_CORE_CTRL1, 0x0000 },
+ { NAU8310_R1A_DSP_CORE_CTRL2, 0x0000 },
+ { NAU8310_R28_ADC_RATE, 0x0002 },
+ { NAU8310_R29_DAC_CTRL1, 0x0081 },
+ { NAU8310_R2A_DAC_CTRL2, 0x0000 },
+ { NAU8310_R2C_ALC_CTRL1, 0x2000 },
+ { NAU8310_R2D_ALC_CTRL2, 0x8400 },
+ { NAU8310_R2E_ALC_CTRL3, 0x0000 },
+ { NAU8310_R2F_ALC_CTRL4, 0x00bf },
+ { NAU8310_R30_TEMP_COMP_CTRL, 0x7df4 },
+ { NAU8310_R31_UVLO_CTRL0, 0x0000 },
+ { NAU8310_R32_UVLO_CTRL1, 0x0000 },
+ { NAU8310_R33_LPF_CTRL, 0x7380 },
+ { NAU8310_R40_CLK_DET_CTRL, 0xa801 },
+ { NAU8310_R55_MISC_CTRL, 0x0000 },
+ { NAU8310_R60_BIAS_ADJ, 0x0000 },
+ { NAU8310_R61_ANALOG_CONTROL_1, 0x0000 },
+ { NAU8310_R62_ANALOG_CONTROL_2, 0x0000 },
+ { NAU8310_R63_ANALOG_CONTROL_3, 0x0000 },
+ { NAU8310_R64_ANALOG_CONTROL_4, 0x0000 },
+ { NAU8310_R65_ANALOG_CONTROL_5, 0x0000 },
+ { NAU8310_R66_ANALOG_CONTROL_6, 0x0000 },
+ { NAU8310_R68_ANALOG_CONTROL_7, 0x0000 },
+ { NAU8310_R69_CLIP_CTRL, 0x0000 },
+ { NAU8310_R6B_ANALOG_CONTROL_8, 0x0000 },
+ { NAU8310_R6C_ANALOG_CONTROL_9, 0x0000 },
+ { NAU8310_R71_ANALOG_ADC_1, 0x0011 },
+ { NAU8310_R72_ANALOG_ADC_2, 0x0020 },
+ { NAU8310_R73_RDAC, 0x0008 },
+ { NAU8310_R76_BOOST, 0x0000 },
+ { NAU8310_R77_FEPGA, 0x0000 },
+ { NAU8310_R7F_POWER_UP_CONTROL, 0x0000 },
+ { NAU8310_R80_BIQ0_COE_1, 0x0800 },
+ { NAU8310_R81_BIQ0_COE_2, 0x0000 },
+ { NAU8310_R82_BIQ0_COE_3, 0x0000 },
+ { NAU8310_R83_BIQ0_COE_4, 0x0000 },
+ { NAU8310_R84_BIQ0_COE_5, 0x0000 },
+ { NAU8310_R85_BIQ0_COE_6, 0x0000 },
+ { NAU8310_R86_BIQ0_COE_7, 0x0000 },
+ { NAU8310_R87_BIQ0_COE_8, 0x0000 },
+ { NAU8310_R88_BIQ0_COE_9, 0x0000 },
+ { NAU8310_R89_BIQ0_COE_10, 0x0000 },
+ { NAU8310_R8A_BIQ1_COE_1, 0x0000 },
+ { NAU8310_R8B_BIQ1_COE_2, 0x0000 },
+ { NAU8310_R8C_BIQ1_COE_3, 0x0000 },
+ { NAU8310_R8D_BIQ1_COE_4, 0x0000 },
+ { NAU8310_R8E_BIQ1_COE_5, 0x0000 },
+ { NAU8310_R8F_BIQ1_COE_6, 0x0000 },
+ { NAU8310_R90_BIQ1_COE_7, 0x0000 },
+ { NAU8310_R91_BIQ1_COE_8, 0x0000 },
+ { NAU8310_R92_BIQ1_COE_9, 0x0000 },
+ { NAU8310_R93_BIQ1_COE_10, 0x0000 },
+ { NAU8310_R94_BIQ2_COE_1, 0x0000 },
+ { NAU8310_R95_BIQ2_COE_2, 0x0000 },
+ { NAU8310_R96_BIQ2_COE_3, 0x0000 },
+ { NAU8310_R97_BIQ2_COE_4, 0x0000 },
+ { NAU8310_R98_BIQ2_COE_5, 0x0000 },
+ { NAU8310_R99_BIQ2_COE_6, 0x0000 },
+ { NAU8310_R9A_BIQ2_COE_7, 0x0000 },
+ { NAU8310_R9B_BIQ2_COE_8, 0x0000 },
+ { NAU8310_R9C_BIQ2_COE_9, 0x0000 },
+ { NAU8310_R9D_BIQ2_COE_10, 0x0000 },
+ { NAU8310_RF000_DSP_COMM, 0x0000 },
+};
+
+static bool nau8310_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case NAU8310_R03_CLK_CTRL ... NAU8310_R10_RIGHT_TIME_SLOT:
+ case NAU8310_R12_HPF_CTRL ... NAU8310_R2A_DAC_CTRL2:
+ case NAU8310_R2C_ALC_CTRL1 ... NAU8310_R33_LPF_CTRL:
+ case NAU8310_R40_CLK_DET_CTRL:
+ case NAU8310_R46_I2C_DEVICE_ID:
+ case NAU8310_R4A_ANALOG_READ:
+ case NAU8310_R55_MISC_CTRL:
+ case NAU8310_R60_BIAS_ADJ ... NAU8310_R66_ANALOG_CONTROL_6:
+ case NAU8310_R68_ANALOG_CONTROL_7 ... NAU8310_R69_CLIP_CTRL:
+ case NAU8310_R6B_ANALOG_CONTROL_8 ... NAU8310_R6C_ANALOG_CONTROL_9:
+ case NAU8310_R71_ANALOG_ADC_1 ... NAU8310_R73_RDAC:
+ case NAU8310_R76_BOOST ... NAU8310_R77_FEPGA:
+ case NAU8310_R7F_POWER_UP_CONTROL ... NAU8310_R9D_BIQ2_COE_10:
+ case NAU8310_RF000_DSP_COMM:
+ return true;
+ default:
+ return false;
+ }
+
+}
+
+static bool nau8310_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case NAU8310_R00_HARDWARE_RST ... NAU8310_R10_RIGHT_TIME_SLOT:
+ case NAU8310_R12_HPF_CTRL ... NAU8310_R1A_DSP_CORE_CTRL2:
+ case NAU8310_R28_ADC_RATE ... NAU8310_R2A_DAC_CTRL2:
+ case NAU8310_R2C_ALC_CTRL1 ... NAU8310_R33_LPF_CTRL:
+ case NAU8310_R40_CLK_DET_CTRL:
+ case NAU8310_R55_MISC_CTRL:
+ case NAU8310_R60_BIAS_ADJ ... NAU8310_R66_ANALOG_CONTROL_6:
+ case NAU8310_R68_ANALOG_CONTROL_7 ... NAU8310_R69_CLIP_CTRL:
+ case NAU8310_R6B_ANALOG_CONTROL_8 ... NAU8310_R6C_ANALOG_CONTROL_9:
+ case NAU8310_R71_ANALOG_ADC_1 ... NAU8310_R73_RDAC:
+ case NAU8310_R76_BOOST ... NAU8310_R77_FEPGA:
+ case NAU8310_R7F_POWER_UP_CONTROL ... NAU8310_R9D_BIQ2_COE_10:
+ case NAU8310_RF000_DSP_COMM:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool nau8310_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case NAU8310_R00_HARDWARE_RST ... NAU8310_R01_SOFTWARE_RST:
+ case NAU8310_R06_INT_CLR_STATUS:
+ case NAU8310_R1B_CLK_DOUBLER_O ... NAU8310_R27_DSP_STATUS_2:
+ case NAU8310_R46_I2C_DEVICE_ID:
+ case NAU8310_R4A_ANALOG_READ:
+ case NAU8310_RF000_DSP_COMM:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int nau8310_clkdet_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct regmap *regmap = nau8310->regmap;
+ unsigned int max = mc->max, min = mc->min, val;
+ unsigned int mask = (1 << fls(max)) - 1;
+
+ val = ((ucontrol->value.integer.value[0] + min) & mask);
+
+ if (nau8310->clock_detection == val)
+ return 0;
+
+ nau8310->clock_detection = val;
+
+ if (nau8310->clock_detection)
+ regmap_update_bits(regmap, NAU8310_R40_CLK_DET_CTRL,
+ NAU8310_CLKPWRUP_DIS, 0);
+ else
+ regmap_update_bits(regmap, NAU8310_R40_CLK_DET_CTRL,
+ NAU8310_CLKPWRUP_DIS, NAU8310_CLKPWRUP_DIS);
+
+ return 1;
+}
+
+static int nau8310_clkdet_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.integer.value[0] = nau8310->clock_detection;
+
+ return 0;
+}
+
+static int nau8310_biq_coeff_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct soc_bytes_ext *params = (void *)kcontrol->private_value;
+ int i, reg, reg_val;
+ u16 *val;
+
+ val = (u16 *)ucontrol->value.bytes.data;
+ reg = NAU8310_R80_BIQ0_COE_1;
+ for (i = 0; i < params->max / sizeof(u16); i++) {
+ regmap_read(nau8310->regmap, reg + i, ®_val);
+ /* conversion of 16-bit integers between native CPU format
+ * and big endian format
+ */
+ reg_val = cpu_to_be16(reg_val);
+ *(val + i) = reg_val;
+ }
+
+ return 0;
+}
+
+static int nau8310_biq_coeff_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
+ struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct soc_bytes_ext *params = (void *)kcontrol->private_value;
+ void *data;
+ u16 *val, value;
+ int i, reg, ret;
+
+ data = kmemdup(ucontrol->value.bytes.data, params->max, GFP_KERNEL | GFP_DMA);
+ if (!data)
+ return -ENOMEM;
+
+ val = (u16 *)data;
+ reg = NAU8310_R80_BIQ0_COE_1;
+ for (i = 0; i < params->max / sizeof(u16); i++) {
+ /* conversion of 16-bit integers between native CPU format
+ * and big endian format
+ */
+ value = be16_to_cpu(*(val + i));
+ ret = regmap_write(nau8310->regmap, reg + i, value);
+ if (ret) {
+ dev_err(nau8310->dev, "BIQ configuration fail, register: %x ret: %d",
+ reg + i, ret);
+ kfree(data);
+ return ret;
+ }
+ }
+ kfree(data);
+
+ return 0;
+}
+
+static const char * const nau8310_adc_decimation[] = { "32", "64", "128" };
+
+static const struct soc_enum nau8310_adc_decimation_enum =
+ SOC_ENUM_SINGLE(NAU8310_R28_ADC_RATE, NAU8310_ADC_SYNC_DOWN_SFT,
+ ARRAY_SIZE(nau8310_adc_decimation), nau8310_adc_decimation);
+
+static const char * const nau8310_dac_oversampl[] = {
+ "64", "256", "128", "", "32" };
+
+static const struct soc_enum nau8310_dac_oversampl_enum =
+ SOC_ENUM_SINGLE(NAU8310_R29_DAC_CTRL1, NAU8310_DAC_OVERSAMPLE_SFT,
+ ARRAY_SIZE(nau8310_dac_oversampl), nau8310_dac_oversampl);
+
+static const char * const nau8310_i2s_channel[] = { "Right", "Left"};
+
+static const struct soc_enum nau8310_aec_channel_sel_enum =
+ SOC_ENUM_SINGLE(NAU8310_R0B_I2S_PCM_CTRL0, NAU8310_AEC_CH_SEL_SFT,
+ ARRAY_SIZE(nau8310_i2s_channel), nau8310_i2s_channel);
+
+static const char * const nau8310_source[] = { "DSP", "Raw Data"};
+
+static const struct soc_enum nau8310_aec_source_sel_enum =
+ SOC_ENUM_SINGLE(NAU8310_R0B_I2S_PCM_CTRL0, NAU8310_AEC_SRC_SEL_SFT,
+ ARRAY_SIZE(nau8310_source), nau8310_source);
+
+static const char * const nau8310_adc_alcgain_slot[] = { "Disable", "Slot 0",
+ "Slot 1", "Slot 2", "Slot 3", "Slot 4", "Slot 5", "Slot 6", "Slot 7" };
+
+static const unsigned int nau8310_adc_alcgain_val[] = {
+ 0x0, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
+
+static const struct soc_enum nau8310_adc_alcgain_sel_enum =
+ SOC_VALUE_ENUM_SINGLE(NAU8310_R0C_TDM_CTRL, NAU8310_ADC_ALC_SEL_SFT,
+ 0xf, ARRAY_SIZE(nau8310_adc_alcgain_slot),
+ nau8310_adc_alcgain_slot, nau8310_adc_alcgain_val);
+
+static const char * const nau8310_ivsense_format[] = { "signed", "unsigned" };
+
+static const struct soc_enum nau8310_ivsense_format_enum =
+ SOC_ENUM_SINGLE(NAU8310_R28_ADC_RATE, NAU8310_UNSIGN_IV_SFT,
+ ARRAY_SIZE(nau8310_ivsense_format), nau8310_ivsense_format);
+
+/* LB/LM is referred to low battery/limiter mode. */
+static const char * const nau8310_limiter_mode[] = {
+ "LM with Clip Detection",
+ "LBLM with Clip Detection",
+ "LM with Pre-Programmed Limit Level",
+ "LBLM with Pre-Programmed Limit Level",
+ "LBLM with Pre-Programmed VBAT Ratio Limit Level" };
+
+static const struct soc_enum nau8310_limiter_mode_enum =
+ SOC_ENUM_SINGLE(NAU8310_R2E_ALC_CTRL3, NAU8310_LIM_MDE_SFT,
+ ARRAY_SIZE(nau8310_limiter_mode), nau8310_limiter_mode);
+
+static const DECLARE_TLV_DB_MINMAX(adc_vol_tlv, 0, 24125);
+static const DECLARE_TLV_DB_MINMAX(pga_gain_tlv, 0, 1600);
+
+static const struct snd_kcontrol_new nau8310_snd_controls[] = {
+ SOC_ENUM("ADC Decimation Rate", nau8310_adc_decimation_enum),
+ SOC_ENUM("DAC Oversampling Rate", nau8310_dac_oversampl_enum),
+ SND_SOC_BYTES_EXT("BIQ Coefficients", 60,
+ nau8310_biq_coeff_get, nau8310_biq_coeff_put),
+
+ SOC_SINGLE_TLV("ADC Left Channel Volume",
+ NAU8310_R14_ADC_VOL_CTRL, NAU8310_ADC_GAIN_L_SFT,
+ NAU8310_ADC_GAIN_L_MAX, 0, adc_vol_tlv),
+ SOC_SINGLE_TLV("ADC Right Channel Volume",
+ NAU8310_R14_ADC_VOL_CTRL, NAU8310_ADC_GAIN_R_SFT,
+ NAU8310_ADC_GAIN_R_MAX, 0, adc_vol_tlv),
+ SOC_SINGLE_TLV("PGA Volume", NAU8310_R7F_POWER_UP_CONTROL,
+ NAU8310_PGA_GAIN_SFT, NAU8310_PGA_GAIN_MAX, 0, pga_gain_tlv),
+
+ /* Boost Control Target Value Limit. 0.16 V per step. */
+ SOC_SINGLE("Boost Target Limit", NAU8310_R17_BOOST_CTRL1,
+ NAU8310_BSTLIMIT_SFT, NAU8310_BSTLIMIT_MAX, 0),
+ /* Boost Margin Value between Boost Target Voltage and
+ * Peak Output Level. 0.16 V per step.
+ */
+ SOC_SINGLE("Boost Margin", NAU8310_R17_BOOST_CTRL1,
+ NAU8310_BSTMARGIN_SFT, NAU8310_BSTMARGIN_MAX, 0),
+
+ SOC_ENUM("AEC Channel", nau8310_aec_channel_sel_enum),
+ SOC_ENUM("AEC Source", nau8310_aec_source_sel_enum),
+ SOC_ENUM("ADC ALC Gain Channel Source", nau8310_adc_alcgain_sel_enum),
+ SOC_SINGLE("PingPong Mode Switch", NAU8310_R0C_TDM_CTRL,
+ NAU8310_PINGPONG_SFT, 1, 0),
+ SOC_ENUM("IV Sense Format", nau8310_ivsense_format_enum),
+
+ SOC_SINGLE_EXT("Clock Detection Switch", SND_SOC_NOPM, 0, 1, 0,
+ nau8310_clkdet_get, nau8310_clkdet_put),
+
+ SOC_SINGLE("AEC Enable Switch", NAU8310_R0B_I2S_PCM_CTRL0,
+ NAU8310_AEC_MODE_SFT, 1, 0),
+ SOC_SINGLE("ALC Max Gain", NAU8310_R2C_ALC_CTRL1,
+ NAU8310_ALC_MAXGAIN_SFT, NAU8310_ALC_MAXGAIN_MAX, 0),
+ SOC_SINGLE("ALC Min Gain", NAU8310_R2C_ALC_CTRL1,
+ NAU8310_ALC_MINGAIN_SFT, NAU8310_ALC_MINGAIN_MAX, 0),
+ SOC_SINGLE("ALC Decay Timer", NAU8310_R2D_ALC_CTRL2,
+ NAU8310_ALC_DCY_SFT, NAU8310_ALC_DCY_MAX, 0),
+ SOC_SINGLE("ALC Attack Timer", NAU8310_R2D_ALC_CTRL2,
+ NAU8310_ALC_ATK_SFT, NAU8310_ALC_ATK_MAX, 0),
+ SOC_SINGLE("ALC Hold Time", NAU8310_R2D_ALC_CTRL2,
+ NAU8310_ALC_HLD_SFT, NAU8310_ALC_HLD_MAX, 0),
+ SOC_SINGLE("ALC Target Level", NAU8310_R2D_ALC_CTRL2,
+ NAU8310_ALC_LVL_SFT, NAU8310_ALC_LVL_MAX, 0),
+ SOC_SINGLE("ALC Enable Switch", NAU8310_R2E_ALC_CTRL3,
+ NAU8310_ALC_EN_SFT, 1, 0),
+ SOC_ENUM("Limiter Mode Selection", nau8310_limiter_mode_enum),
+ SOC_SINGLE("Low Battery Limiter Threshold", NAU8310_R2E_ALC_CTRL3,
+ NAU8310_VBAT_THLD_SFT, NAU8310_VBAT_THLD_MAX, 0),
+ SOC_SINGLE("Auto Attenuation Enable Switch", NAU8310_R2E_ALC_CTRL3,
+ NAU8310_AUTOATT_EN_SFT, 1, 0),
+ SOC_SINGLE("BCLK IO Driver Stronger Switch", NAU8310_R0A_IO_CTRL,
+ NAU8310_BCLK_DS_SFT, 1, 0),
+ SOC_SINGLE("LRC IO Driver Stronger Switch", NAU8310_R0A_IO_CTRL,
+ NAU8310_LRC_DS_SFT, 1, 0),
+ SOC_SINGLE("ADCDAT IO Driver Stronger Switch", NAU8310_R0A_IO_CTRL,
+ NAU8310_ADCDAT_DS_SFT, 1, 0),
+
+ SOC_SINGLE("Low Output Noise in Receiver Mode Switch",
+ NAU8310_R64_ANALOG_CONTROL_4, NAU8310_RECV_MODE_SFT, 1, 0),
+
+ SOC_SINGLE("VBAT Peak Current Limit",
+ NAU8310_R6B_ANALOG_CONTROL_8,
+ NAU8310_VBAT_PCL_SFT, NAU8310_VBAT_PCL_MAX, 0),
+ SOC_SINGLE("VBAT Comparator Threshold",
+ NAU8310_R6B_ANALOG_CONTROL_8,
+ NAU8310_VBAT_THD_SFT, NAU8310_VBAT_THD_MAX, 0),
+
+ SOC_SINGLE("ISense PGA Supply Current Trim",
+ NAU8310_R77_FEPGA, NAU8310_CURR_TRIM_SFT, NAU8310_CURR_TRIM_MAX, 0),
+};
+
+static const struct snd_kcontrol_new nau8310_alc_gain_select =
+ SOC_DAPM_SINGLE("Switch", NAU8310_R2C_ALC_CTRL1,
+ NAU8310_ALC_GAIN_SEL_SFT, 1, 0);
+
+static int nau8310_clock_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ /* Switch the clock source of DSP to MCLK. */
+ regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
+ NAU8310_DSP_SEL_OSC, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nau8310_powerup_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct regmap *regmap = nau8310->regmap;
+
+ if (nau8310->clock_detection)
+ return 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ regmap_update_bits(regmap, NAU8310_R40_CLK_DET_CTRL,
+ NAU8310_PWRUP_DFT, NAU8310_PWRUP_DFT);
+ else if (SND_SOC_DAPM_EVENT_OFF(event))
+ regmap_update_bits(regmap, NAU8310_R40_CLK_DET_CTRL,
+ NAU8310_PWRUP_DFT, 0);
+
+ return 0;
+}
+
+static int nau8310_adc_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct regmap *regmap = nau8310->regmap;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_update_bits(regmap, NAU8310_R0E_I2S_PCM_CTRL2,
+ NAU8310_I2S_ADCDAT_OE_MASK, NAU8310_I2S_ADCDAT_OE_EN);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ regmap_update_bits(regmap, NAU8310_R0E_I2S_PCM_CTRL2,
+ NAU8310_I2S_ADCDAT_OE_MASK, NAU8310_I2S_ADCDAT_OE_DIS);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nau8310_dac_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct regmap *regmap = nau8310->regmap;
+
+ /* Soft mute to prevent the pop noise */
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_update_bits(regmap, NAU8310_R13_MUTE_CTRL,
+ NAU8310_SOFT_MUTE, 0);
+ msleep(30);
+ regmap_update_bits(regmap, NAU8310_R0E_I2S_PCM_CTRL2,
+ NAU8310_I2S_TRISTATE, 0);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ regmap_update_bits(regmap, NAU8310_R0E_I2S_PCM_CTRL2,
+ NAU8310_I2S_TRISTATE, NAU8310_I2S_TRISTATE);
+ regmap_update_bits(regmap, NAU8310_R13_MUTE_CTRL,
+ NAU8310_SOFT_MUTE, NAU8310_SOFT_MUTE);
+ msleep(30);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static inline bool nau8310_dsp_active(struct snd_soc_component *cp)
+{
+ return (snd_soc_component_read(cp, NAU8310_R1A_DSP_CORE_CTRL2) &
+ NAU8310_DAC_SEL_DSP_OUT);
+}
+
+static int check_dsp_enabled(struct snd_soc_dapm_widget *source,
+ struct snd_soc_dapm_widget *sink)
+{
+ struct snd_soc_component *cp = snd_soc_dapm_to_component(source->dapm);
+
+ return nau8310_dsp_active(cp);
+}
+
+static const struct snd_soc_dapm_widget nau8310_dapm_widgets[] = {
+ SND_SOC_DAPM_SUPPLY("Clock", SND_SOC_NOPM, 0, 0,
+ nau8310_clock_event, SND_SOC_DAPM_PRE_PMU),
+ SND_SOC_DAPM_SUPPLY("PowerUp", SND_SOC_NOPM, 0, 0,
+ nau8310_powerup_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_SWITCH_E("ALC gain selection mode", SND_SOC_NOPM,
+ 0, 0, &nau8310_alc_gain_select, nau8310_adc_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_SIGGEN("Sense"),
+
+ SND_SOC_DAPM_PGA("SAR", NAU8310_R07_SAR_CTRL1, NAU8310_SAR_ENA_SFT, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("ADC_I", NAU8310_R04_ENA_CTRL, NAU8310_ADCEN_I_SFT, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("ADC_V", NAU8310_R04_ENA_CTRL, NAU8310_ADCEN_V_SFT, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Capture Sense", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_ADC_E("ADC_OUT", "Capture", SND_SOC_NOPM, 0, 0,
+ nau8310_adc_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_AIF_IN("AIFRX", "Playback", 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_DAC_E("DAC", NULL, NAU8310_R04_ENA_CTRL, NAU8310_DAC_CH_EN_SFT, 0,
+ nau8310_dac_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_OUTPUT("Speaker"),
+};
+
+static const struct snd_soc_dapm_route nau8310_dapm_routes[] = {
+ { "SAR", NULL, "Sense" },
+ { "ADC_I", NULL, "Sense" },
+ { "ADC_V", NULL, "Sense" },
+ { "Capture Sense", NULL, "SAR" },
+ { "Capture Sense", NULL, "ADC_I" },
+ { "Capture Sense", NULL, "ADC_V" },
+
+ { "ALC gain selection mode", "Switch", "Capture Sense" },
+ { "ADC_OUT", NULL, "Capture Sense" },
+
+ { "AIFRX", NULL, "Clock", check_dsp_enabled },
+ { "DAC", NULL, "PowerUp" },
+ { "DAC", NULL, "ALC gain selection mode" },
+ { "DAC", NULL, "AIFRX" },
+ { "Speaker", NULL, "DAC"},
+};
+
+static int nau8310_srate_clk_apply(struct nau8310 *nau8310,
+ const struct nau8310_srate_attr *srate_table,
+ int n1_sel, int mclk_mult_sel, int n2_sel, int dsp_mult_sel)
+{
+ struct regmap *regmap = nau8310->regmap;
+ int dsp_clk, mult_sel;
+ unsigned int ctrl_val = 0;
+
+ static const unsigned int mclkx_val[] = {
+ /* multiplier 4x, i.e. 2^2 */
+ [2] = NAU8310_MCLK4XEN,
+ /* multiplier 8x, i.e. 2^3 */
+ [3] = NAU8310_MCLK8XEN | NAU8310_MCLK4XEN,
+ /* multiplier 16x, i.e. 2^4 */
+ [4] = NAU8310_MCLK16XEN | NAU8310_MCLK8XEN | NAU8310_MCLK4XEN,
+ };
+
+ if (!srate_table || srate_table->adc_div < 0 ||
+ dsp_mult_sel < 0 || dsp_mult_sel >= ARRAY_SIZE(dsp_src_mult) ||
+ n2_sel < 0 || n2_sel >= ARRAY_SIZE(mclk_n2_div) ||
+ n1_sel < 0 || n1_sel >= ARRAY_SIZE(mclk_n1_div)) {
+ dev_err(nau8310->dev, "The fs %d isn't support in DSP.",
+ nau8310->fs);
+ return -EINVAL;
+ }
+ regmap_update_bits(regmap, NAU8310_R03_CLK_CTRL,
+ NAU8310_CLK_ADC_DIV2 | NAU8310_CLK_ADC_DIV4,
+ srate_table->adc_div << NAU8310_CLK_ADC_DIV4_SFT);
+ regmap_update_bits(regmap, NAU8310_R40_CLK_DET_CTRL,
+ NAU8310_SRATE_MASK | NAU8310_DIV_MAX | NAU8310_ALT_SRATE_EN,
+ (srate_table->range << NAU8310_SRATE_SFT) |
+ (srate_table->max ? NAU8310_DIV_MAX : 0) |
+ (srate_table->fs == 44100 ? NAU8310_ALT_SRATE_EN : 0));
+
+ dsp_clk = nau8310->mclk << dsp_src_mult[dsp_mult_sel].param;
+ dsp_clk = dsp_clk / mclk_n1_div[n1_sel].param;
+
+ dev_dbg(nau8310->dev, "FS %dHz, DSP_CLK %uHz, n1_sel (%d), mclk_mult_sel (%d), n2_sel (%d), dsp_mult_sel (%d).",
+ nau8310->fs, dsp_clk, n1_sel, mclk_mult_sel, n2_sel, dsp_mult_sel);
+
+ regmap_update_bits(regmap, NAU8310_R03_CLK_CTRL,
+ NAU8310_MCLK_SRC_MASK, mclk_n2_div[n2_sel].val);
+ ctrl_val = (dsp_src_mult[dsp_mult_sel].val << NAU8310_CLK_DSP_SRC_SFT) |
+ (mclk_n1_div[n1_sel].val << NAU8310_CLK_MUL_SRC_SFT);
+
+ if (mclk_mult_sel != CLK_PROC_BYPASS) {
+ ctrl_val |= mclk_src_mult[mclk_mult_sel].val << NAU8310_MCLK_SEL_SFT;
+ mult_sel = (mclk_mult_sel > dsp_mult_sel ? mclk_mult_sel : dsp_mult_sel);
+ } else
+ mult_sel = dsp_mult_sel;
+
+ regmap_update_bits(regmap, NAU8310_R04_ENA_CTRL,
+ NAU8310_CLK_DSP_SRC_MASK | NAU8310_CLK_MUL_SRC_MASK |
+ NAU8310_MCLK_SEL_MASK, ctrl_val);
+
+ if (mult_sel < 2 || mult_sel > 4)
+ return -EINVAL;
+
+ regmap_update_bits(regmap, NAU8310_R68_ANALOG_CONTROL_7,
+ NAU8310_MCLKX_MASK, mclkx_val[mult_sel]);
+
+ return 0;
+}
+
+static int nau8310_calc_dsp_clk(int mclk, int fs, int mult_param, int n1_param)
+{
+ int dsp_clk = (mclk << mult_param) / n1_param;
+
+ if (dsp_clk >= (fs * DSP_CLK_MIN_MULTI_VALUE) && dsp_clk <= DSP_CLK_MAX)
+ return dsp_clk;
+
+ return 0;
+}
+
+static int nau8310_clkdsp_choose(struct nau8310 *nau8310, int *n1_sel, bool n1_try,
+ int *dsp_n1_sel, int *dsp_mult_sel)
+{
+ int i, j, dsp_n1, mult_sel, dsp_clk, dsp_clk_max = 0;
+
+ if (!nau8310->fs || !nau8310->mclk)
+ goto proc_err;
+
+ if (n1_sel) {
+ dsp_n1 = *n1_sel;
+ for (j = 0; j < ARRAY_SIZE(dsp_src_mult); j++) {
+ dsp_clk = nau8310_calc_dsp_clk(nau8310->mclk, nau8310->fs,
+ dsp_src_mult[j].param, mclk_n1_div[dsp_n1].param);
+ if (!dsp_clk)
+ continue;
+ if (dsp_clk_max < dsp_clk) {
+ dsp_clk_max = dsp_clk;
+ mult_sel = j;
+ }
+ }
+ if (!dsp_clk_max)
+ goto proc_err;
+ if (!n1_try) {
+ *dsp_n1_sel = dsp_n1;
+ *dsp_mult_sel = mult_sel;
+ }
+ } else {
+ dsp_n1 = -1;
+ if (nau8310->mclk >= DSP_MCLK_HI_SPEED) {
+ /* get max N1 when MCLK over 12.288MHz */
+ for (i = 0; i < ARRAY_SIZE(mclk_n1_div); i++)
+ for (j = 0; j < ARRAY_SIZE(dsp_src_mult); j++) {
+ dsp_clk = nau8310_calc_dsp_clk(nau8310->mclk, nau8310->fs,
+ dsp_src_mult[j].param, mclk_n1_div[i].param);
+ if (!dsp_clk)
+ continue;
+ if ((dsp_clk_max < dsp_clk || i > dsp_n1)) {
+ dsp_clk_max = dsp_clk;
+ dsp_n1 = i;
+ mult_sel = j;
+ }
+ }
+ if (!dsp_clk_max)
+ goto proc_err;
+ } else {
+ for (i = 0; i < ARRAY_SIZE(mclk_n1_div); i++) {
+ for (j = 0; j < ARRAY_SIZE(dsp_src_mult); j++) {
+ dsp_clk = nau8310_calc_dsp_clk(nau8310->mclk, nau8310->fs,
+ dsp_src_mult[j].param, mclk_n1_div[i].param);
+ if (dsp_clk) {
+ dsp_n1 = i;
+ mult_sel = j;
+ break;
+ }
+ }
+ if (dsp_n1 >= 0)
+ break;
+ }
+ if (dsp_n1 < 0)
+ goto proc_err;
+ }
+ *dsp_n1_sel = dsp_n1;
+ *dsp_mult_sel = mult_sel;
+ }
+
+ return 0;
+
+proc_err:
+ return -EINVAL;
+}
+
+static inline int get_osrate_val(const struct nau8310_osr_attr *arr, size_t arr_size, int osr)
+{
+ if (osr >= arr_size)
+ return -EINVAL;
+
+ return arr[osr].osr;
+}
+
+static int nau8310_clock_check(struct nau8310 *nau8310, int stream, int rate, int osr)
+{
+ struct snd_soc_component *cp = snd_soc_dapm_to_component(nau8310->dapm);
+ int osrate = 0;
+
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ osrate = get_osrate_val(osr_dac_sel, ARRAY_SIZE(osr_dac_sel), osr);
+ else {
+ if (nau8310_dsp_active(cp))
+ osrate = get_osrate_val(osr_adc_dsp_sel,
+ ARRAY_SIZE(osr_adc_dsp_sel), osr);
+ else
+ osrate = get_osrate_val(osr_adc_sel,
+ ARRAY_SIZE(osr_adc_sel), osr);
+ }
+
+ if (osrate <= 0)
+ return -EINVAL;
+
+ if (rate * osrate > CLK_DA_AD_MAX) {
+ dev_err(nau8310->dev, "exceed the maximum frequency of CLK_ADC or CLK_DAC");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int nau8310_osr_apply(struct nau8310 *nau8310)
+{
+ struct snd_soc_component *cp = snd_soc_dapm_to_component(nau8310->dapm);
+ struct regmap *regmap = nau8310->regmap;
+ unsigned int dac_osr, adc_osr;
+ unsigned int dac_src, adc_src;
+
+ /* CLK_DAC or CLK_ADC = OSR * FS
+ * DAC or ADC clock frequency is defined as Over Sampling Rate (OSR)
+ * multiplied by the audio sample rate (Fs). Note that the OSR and Fs
+ * values must be selected such that the maximum frequency is less
+ * than 6.144 MHz.
+ */
+ regmap_read(regmap, NAU8310_R29_DAC_CTRL1, &dac_osr);
+ dac_osr &= NAU8310_DAC_OVERSAMPLE_MASK;
+ if (nau8310_clock_check(nau8310, SNDRV_PCM_STREAM_PLAYBACK, nau8310->fs, dac_osr))
+ return -EINVAL;
+
+ regmap_read(regmap, NAU8310_R28_ADC_RATE, &adc_osr);
+ adc_osr &= NAU8310_ADC_SYNC_DOWN_MASK;
+ if (nau8310_clock_check(nau8310, SNDRV_PCM_STREAM_CAPTURE, nau8310->fs, adc_osr))
+ return -EINVAL;
+
+ dac_src = osr_dac_sel[dac_osr].clk_src << NAU8310_CLK_DAC_SRC_SFT;
+ adc_src = (nau8310_dsp_active(cp)) ?
+ (osr_adc_dsp_sel[adc_osr].clk_src << NAU8310_CLK_ADC_SRC_SFT) :
+ (osr_adc_sel[adc_osr].clk_src << NAU8310_CLK_ADC_SRC_SFT);
+ regmap_update_bits(regmap, NAU8310_R03_CLK_CTRL,
+ NAU8310_CLK_DAC_SRC_MASK | NAU8310_CLK_ADC_SRC_MASK, dac_src | adc_src);
+
+ return 0;
+}
+
+static int nau8310_match_mclk_ratio(const struct nau8310_srate_attr *srate_table,
+ int mclk_src)
+{
+ if (srate_table->mclk_src[NAU8310_MCLK_FS_RATIO_256] == mclk_src)
+ return NAU8310_MCLK_FS_RATIO_256;
+
+ if (srate_table->mclk_src[NAU8310_MCLK_FS_RATIO_400] == mclk_src)
+ return NAU8310_MCLK_FS_RATIO_400;
+
+ if (srate_table->mclk_src[NAU8310_MCLK_FS_RATIO_500] == mclk_src)
+ return NAU8310_MCLK_FS_RATIO_500;
+
+ return NAU8310_MCLK_FS_RATIO_NUM;
+}
+
+static int nau8310_clksrc_n2(struct nau8310 *nau8310,
+ const struct nau8310_srate_attr *srate_table, int mclk, int *n2_sel)
+{
+ int i, mclk_src, ratio = NAU8310_MCLK_FS_RATIO_NUM;
+
+ for (i = 0; i < ARRAY_SIZE(mclk_n2_div); i++) {
+ mclk_src = mclk >> mclk_n2_div[i].param;
+ ratio = nau8310_match_mclk_ratio(srate_table, mclk_src);
+ if (ratio != NAU8310_MCLK_FS_RATIO_NUM) {
+ *n2_sel = i;
+ break;
+ }
+ }
+
+ return ratio;
+}
+
+static const struct nau8310_srate_attr *target_srate_attribute(int srate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(target_srate_table); i++)
+ if (target_srate_table[i].fs == srate)
+ break;
+
+ if (i == ARRAY_SIZE(target_srate_table))
+ return NULL;
+
+ return &target_srate_table[i];
+}
+
+static int nau8310_clksrc_choose(struct nau8310 *nau8310,
+ const struct nau8310_srate_attr **srate_table,
+ int *n1_sel, int *mult_sel, int *n2_sel, int *dsp_mult_sel)
+{
+ int i, j, mclk, mclk_max, ratio, ratio_sel, n2_max, ret;
+
+ if (!nau8310->mclk || !nau8310->fs)
+ goto proc_err;
+
+ /* select sampling rate and MCLK_SRC */
+ *srate_table = target_srate_attribute(nau8310->fs);
+ if (*srate_table == NULL)
+ goto proc_err;
+
+ /* First check clock from MCLK directly, decide N2 for MCLK_SRC.
+ * If not good, consider 1/N1 and Multiplier.
+ */
+ ratio = nau8310_clksrc_n2(nau8310, *srate_table, nau8310->mclk, n2_sel);
+ if (ratio != NAU8310_MCLK_FS_RATIO_NUM) {
+ *n1_sel = CLK_PROC_BYPASS;
+ *mult_sel = CLK_PROC_BYPASS;
+ ret = nau8310_clkdsp_choose(nau8310, NULL, false,
+ n1_sel, dsp_mult_sel);
+ if (ret)
+ goto proc_err;
+ goto proc_done;
+ }
+ /* Get MCLK_SRC through 1/N, Multiplier, and then 1/N2. */
+ mclk_max = 0;
+ for (i = 0; i < ARRAY_SIZE(mclk_n1_div); i++) {
+ for (j = 0; j < ARRAY_SIZE(mclk_src_mult); j++) {
+ mclk = nau8310->mclk << mclk_src_mult[j].param;
+ mclk = mclk / mclk_n1_div[i].param;
+ ratio = nau8310_clksrc_n2(nau8310, *srate_table, mclk, n2_sel);
+ if (ratio != NAU8310_MCLK_FS_RATIO_NUM &&
+ (mclk_max < mclk || i > *n1_sel)) {
+ ret = nau8310_clkdsp_choose(nau8310, &i, true,
+ n1_sel, dsp_mult_sel);
+ if (ret)
+ break;
+ mclk_max = mclk;
+ n2_max = *n2_sel;
+ *n1_sel = i;
+ *mult_sel = j;
+ ratio_sel = ratio;
+ }
+ }
+ }
+ if (mclk_max) {
+ *n2_sel = n2_max;
+ ratio = ratio_sel;
+ nau8310_clkdsp_choose(nau8310, n1_sel, false, n1_sel, dsp_mult_sel);
+ goto proc_done;
+ }
+
+proc_err:
+ dev_err(nau8310->dev, "The MCLK %d is invalid. It can't get MCLK_SRC of 256/400/500 FS (%d).",
+ nau8310->mclk, nau8310->fs);
+ return -EINVAL;
+proc_done:
+ dev_dbg(nau8310->dev, "FS %dHz, range 0x%x, %s, (n1,mu,n2,dmu):(%d,%d,%d,%d), MCLK_SRC %uHz (%d)",
+ nau8310->fs, (*srate_table)->range,
+ (*srate_table)->max ? "MAX" : "MIN",
+ *n1_sel == CLK_PROC_BYPASS ?
+ CLK_PROC_BYPASS : mclk_n1_div[*n1_sel].param,
+ *mult_sel == CLK_PROC_BYPASS ?
+ CLK_PROC_BYPASS : 1 << mclk_src_mult[*mult_sel].param,
+ 1 << mclk_n2_div[*n2_sel].param,
+ 1 << dsp_src_mult[*dsp_mult_sel].param,
+ (*srate_table)->mclk_src[ratio],
+ (*srate_table)->mclk_src[ratio] / nau8310->fs);
+ return 0;
+}
+
+static int nau8310_clock_config(struct nau8310 *nau8310)
+{
+ const struct nau8310_srate_attr *srate_table;
+ int ret, n1_sel, mult_sel, n2_sel, dsp_mult_sel;
+
+ ret = nau8310_clksrc_choose(nau8310, &srate_table, &n1_sel, &mult_sel,
+ &n2_sel, &dsp_mult_sel);
+ if (ret)
+ return ret;
+
+ ret = nau8310_srate_clk_apply(nau8310, srate_table, n1_sel, mult_sel,
+ n2_sel, dsp_mult_sel);
+ if (ret)
+ return ret;
+
+ ret = nau8310_osr_apply(nau8310);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int nau8310_set_sysclk(struct snd_soc_component *component,
+ int clk_id, int source, unsigned int freq, int dir)
+{
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+
+ if (freq < MASTER_CLK_MIN || freq > MASTER_CLK_MAX) {
+ dev_err(nau8310->dev,
+ "Exceed the range of input clocks, MCLK %dHz", freq);
+ return -EINVAL;
+ }
+ nau8310->mclk = freq;
+ dev_dbg(nau8310->dev, "%s, MCLK %dHz", __func__, nau8310->mclk);
+
+ return 0;
+}
+
+static int nau8310_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *cp = dai->component;
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
+ unsigned int val_len = 0;
+ int ret;
+
+ nau8310->fs = params_rate(params);
+ if (nau8310_dsp_active(cp) && nau8310->fs != NAU8310_DEFAULT_SAMPLE_RATE) {
+ dev_err(nau8310->dev,
+ "For ROM Code revision 00, DSP only run at 48kHz.");
+ return -EINVAL;
+ }
+ ret = nau8310_clock_config(nau8310);
+ if (ret)
+ return -EINVAL;
+
+ switch (params_width(params)) {
+ case 16:
+ val_len |= NAU8310_I2S_DL_16;
+ break;
+ case 20:
+ val_len |= NAU8310_I2S_DL_20;
+ break;
+ case 24:
+ val_len |= NAU8310_I2S_DL_24;
+ break;
+ case 32:
+ val_len |= NAU8310_I2S_DL_32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(nau8310->regmap, NAU8310_R0D_I2S_PCM_CTRL1,
+ NAU8310_I2S_DL_MASK, val_len);
+
+ return 0;
+}
+
+static int nau8310_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct regmap *regmap = nau8310->regmap;
+ unsigned int ctrl1_val = 0, ctrl2_val = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBP_CFP:
+ ctrl2_val |= NAU8310_I2S_MS_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBC_CFC:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ ctrl1_val |= NAU8310_I2S_BP_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ ctrl1_val |= NAU8310_I2S_DF_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ctrl1_val |= NAU8310_I2S_DF_LEFT;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ ctrl1_val |= NAU8310_I2S_DF_RIGHT;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ ctrl1_val |= NAU8310_I2S_DF_PCM_AB;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ ctrl1_val |= NAU8310_I2S_DF_PCM_AB;
+ ctrl1_val |= NAU8310_I2S_PCMB_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(regmap, NAU8310_R0D_I2S_PCM_CTRL1,
+ NAU8310_I2S_DF_MASK | NAU8310_I2S_BP_MASK |
+ NAU8310_I2S_PCMB_EN, ctrl1_val);
+ regmap_update_bits(regmap, NAU8310_R0E_I2S_PCM_CTRL2,
+ NAU8310_I2S_MS_MASK, ctrl2_val);
+ return 0;
+}
+
+static int nau8310_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width)
+{
+ struct snd_soc_component *cp = dai->component;
+ struct device *dev = cp->dev;
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
+ struct regmap *regmap = nau8310->regmap;
+ unsigned int ctrl0_val = 0, ctrl_val = 0;
+ int i;
+
+ if (!slots || !slot_width) {
+ regmap_update_bits(regmap, NAU8310_R0C_TDM_CTRL,
+ NAU8310_TDM_EN, 0);
+ return 0;
+ }
+
+ if (slots > NAU8310_TDM_MAX_SLOTS)
+ return -EINVAL;
+
+ if (slot_width != 16 && slot_width != 24 && slot_width != 32) {
+ dev_err(dev, "Invalid TDM channel length: %d", slot_width);
+ return -EINVAL;
+ }
+
+ if (tx_mask || rx_mask)
+ ctrl_val |= NAU8310_TDM_EN;
+
+ for (i = 0; i < NAU8310_TDM_MAX_SLOTS; i++)
+ if (tx_mask & BIT(i)) {
+ ctrl_val |= nau8310_tx_slot_map[i];
+ dev_dbg(dev, "NAU8310_ADC_%c_SEL_SLOT%d",
+ (i % 2 == 0) ? 'V' : 'I', i);
+ }
+
+ for (i = 0; i < NAU8310_TDM_MAX_SLOTS; i++)
+ if (rx_mask & BIT(i)) {
+ ctrl0_val |= nau8310_rx_slot_map[i];
+ dev_dbg(dev, "NAU8310_DAC_SEL_SLOT%d", i);
+ break;
+ }
+
+ dev_dbg(dev, "%s: tx_mask: 0x%08X, rx_mask: 0x%08X, ADC slots: 0x%08X, DAC slots: 0x%08X",
+ __func__, tx_mask, rx_mask, ctrl_val, ctrl0_val);
+
+ regmap_update_bits(regmap, NAU8310_R0C_TDM_CTRL,
+ NAU8310_TDM_EN | NAU8310_TDM_OFFSET_EN |
+ NAU8310_ADC_I_SEL_MASK | NAU8310_ADC_V_SEL_MASK, ctrl_val);
+ regmap_update_bits(regmap, NAU8310_R0B_I2S_PCM_CTRL0,
+ NAU8310_DAC_SEL_MASK, ctrl0_val);
+
+ return 0;
+}
+
+static int nau8310_switch_internal_osc(struct snd_soc_component *cp)
+{
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
+ int ret;
+
+ /* For internal Ring OSC, the default fs apply to 48kHz */
+ nau8310->fs = NAU8310_DEFAULT_SAMPLE_RATE;
+ ret = nau8310_set_sysclk(cp, 0, 0,
+ nau8310->fs * NAU8310_SYSCLK_MULT, SND_SOC_CLOCK_IN);
+ if (ret)
+ return ret;
+
+ ret = nau8310_clock_config(nau8310);
+ if (ret)
+ return ret;
+
+ /* Switch clock source to OSC before MCLK off if DSP is enabled. */
+ regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
+ NAU8310_DSP_OSC_EN | NAU8310_DSP_SEL_OSC,
+ NAU8310_DSP_OSC_EN | NAU8310_DSP_SEL_OSC);
+
+ return 0;
+}
+
+static int nau8310_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+
+ flush_work(&nau8310->load_fw_work);
+
+ if (nau8310_dsp_active(component) && !nau8310->load_fw_done) {
+ dev_warn(nau8310->dev, "DSP firmware is not ready yet!");
+ return -EBUSY;
+ }
+
+ if (nau8310_dsp_active(component))
+ snd_soc_dapm_enable_pin(nau8310->dapm, "Sense");
+
+ return 0;
+}
+
+static void nau8310_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *cp = dai->component;
+ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(cp);
+ int ret;
+
+ if (!nau8310_dsp_active(cp))
+ return;
+
+ snd_soc_dapm_disable_pin(dapm, "Sense");
+
+ ret = nau8310_switch_internal_osc(cp);
+ if (ret)
+ return;
+}
+
+static void nau8310_reset_chip(struct regmap *regmap)
+{
+ regmap_write(regmap, NAU8310_R00_HARDWARE_RST, 0x0001);
+ regmap_write(regmap, NAU8310_R00_HARDWARE_RST, 0x0000);
+}
+
+static void nau8310_software_reset(struct regmap *regmap)
+{
+ regmap_write(regmap, NAU8310_R01_SOFTWARE_RST, 0x0000);
+ regmap_write(regmap, NAU8310_R01_SOFTWARE_RST, 0x0000);
+}
+
+static int nau8310_dsp_fw_load(struct nau8310 *nau8310)
+{
+ int ret = 0;
+ struct snd_soc_component *cp = snd_soc_dapm_to_component(nau8310->dapm);
+
+ ret = nau8310_switch_internal_osc(cp);
+ if (ret)
+ return ret;
+
+ regmap_update_bits(nau8310->regmap, NAU8310_R68_ANALOG_CONTROL_7,
+ NAU8310_MU_HALF_RANGE_EN, NAU8310_MU_HALF_RANGE_EN);
+
+ nau8310->load_fw_done = false;
+ schedule_work(&nau8310->load_fw_work);
+
+ return ret;
+}
+
+static void nau8310_load_fw_work(struct work_struct *work)
+{
+ struct nau8310 *nau8310 = container_of(work, struct nau8310, load_fw_work);
+ struct regmap *regmap = nau8310->regmap;
+ struct snd_soc_component *cp = snd_soc_dapm_to_component(nau8310->dapm);
+ int ret;
+
+ mutex_lock(&nau8310->lock);
+ ret = nau8310_dsp_init(cp);
+ mutex_unlock(&nau8310->lock);
+ if (ret) {
+ dev_err(nau8310->dev, "Failed to initialize DSP: %d", ret);
+ goto err;
+ }
+
+ return;
+
+err:
+
+ regmap_update_bits(regmap,
+ NAU8310_R1A_DSP_CORE_CTRL2,
+ NAU8310_DSP_RUNSTALL | NAU8310_DAC_SEL_DSP_OUT, NAU8310_DSP_RUNSTALL);
+
+ /* Switch the clock source of DSP to MCLK. */
+ regmap_update_bits(regmap, NAU8310_R04_ENA_CTRL, NAU8310_DSP_SEL_OSC, 0);
+}
+
+static int nau8310_codec_probe(struct snd_soc_component *component)
+{
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
+ int ret = 0;
+
+ nau8310->dapm = dapm;
+
+ /* Loading DSP firmware */
+ ret = nau8310_dsp_fw_load(nau8310);
+ if (ret) {
+ dev_err(nau8310->dev, "Loading DSP firmware failed(%d)", ret);
+ goto err;
+ }
+
+ ret = nau8310_dsp_setup_controls(component);
+ if (ret) {
+ dev_err(nau8310->dev, "DSP setup controls failed(%d)", ret);
+ goto err;
+ }
+
+err:
+ return ret;
+}
+
+static int __maybe_unused nau8310_suspend(struct snd_soc_component *component)
+{
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct regmap *regmap = nau8310->regmap;
+
+ cancel_work_sync(&nau8310->load_fw_work);
+
+ if (nau8310_dsp_active(component))
+ regmap_update_bits(regmap, NAU8310_R04_ENA_CTRL,
+ NAU8310_DSP_SEL_OSC, NAU8310_DSP_SEL_OSC);
+
+ regcache_cache_only(regmap, true);
+ regcache_mark_dirty(regmap);
+
+ return 0;
+}
+
+static int __maybe_unused nau8310_resume(struct snd_soc_component *component)
+{
+ struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
+ struct regmap *regmap = nau8310->regmap;
+ int ret = 0;
+
+ regcache_cache_only(regmap, false);
+ ret = regcache_sync(regmap);
+ if (ret)
+ return ret;
+
+ if (nau8310_dsp_active(component)) {
+ ret = nau8310_dsp_fw_load(nau8310);
+ if (ret)
+ dev_err(nau8310->dev, "Loading DSP firmware failed(%d)", ret);
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_component_driver soc_comp_dev_nau8310 = {
+ .probe = nau8310_codec_probe,
+ .set_sysclk = nau8310_set_sysclk,
+ .suspend = nau8310_suspend,
+ .resume = nau8310_resume,
+ .controls = nau8310_snd_controls,
+ .num_controls = ARRAY_SIZE(nau8310_snd_controls),
+ .dapm_widgets = nau8310_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(nau8310_dapm_widgets),
+ .dapm_routes = nau8310_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(nau8310_dapm_routes),
+ .suspend_bias_off = 1,
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+};
+
+static const struct snd_soc_dai_ops nau8310_dai_ops = {
+ .startup = nau8310_startup,
+ .hw_params = nau8310_hw_params,
+ .shutdown = nau8310_shutdown,
+ .set_fmt = nau8310_set_fmt,
+ .set_tdm_slot = nau8310_set_tdm_slot,
+};
+
+#define NAU8310_RATES SNDRV_PCM_RATE_8000_192000
+#define NAU8310_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \
+ | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver nau8310_dai = {
+ .name = NAU8310_CODEC_DAI,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = NAU8310_RATES,
+ .formats = NAU8310_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = NAU8310_RATES,
+ .formats = NAU8310_FORMATS,
+ },
+ .ops = &nau8310_dai_ops,
+ .symmetric_rate = 1,
+};
+
+static int nau8310_reg_write(void *context, unsigned int reg, unsigned int value)
+{
+ struct i2c_client *client = context;
+ struct nau8310 *nau8310 = i2c_get_clientdata(client);
+ int ret, count = 0;
+
+ put_unaligned_be16(reg, &nau8310->i2c_write_buf[count]);
+ count += 2;
+ if (reg == NAU8310_RF000_DSP_COMM) {
+ /* format for DSP, 4 bytes value and native */
+ put_unaligned_le32(value, &nau8310->i2c_write_buf[count]);
+ count += 4;
+ } else {
+ /* format for Codec, 2 bytes value and endian big */
+ put_unaligned_be16(value, &nau8310->i2c_write_buf[count]);
+ count += 2;
+ }
+
+ ret = i2c_master_send(client, nau8310->i2c_write_buf, count);
+
+ if (ret == count)
+ return 0;
+ if (ret < 0)
+ return ret;
+
+ return -EIO;
+}
+
+static int nau8310_reg_read(void *context, unsigned int reg, unsigned int *value)
+{
+ struct i2c_client *client = context;
+ struct nau8310 *nau8310 = i2c_get_clientdata(client);
+ struct i2c_msg xfer[2];
+ int ret;
+
+ put_unaligned_be16(reg, &nau8310->i2c_read_reg);
+ xfer[0].addr = client->addr;
+ xfer[0].len = 2;
+ xfer[0].buf = nau8310->i2c_read_reg;
+ xfer[0].flags = 0;
+
+ xfer[1].addr = client->addr;
+ xfer[1].len = (reg == NAU8310_RF000_DSP_COMM ? 4 : 2);
+ xfer[1].buf = nau8310->i2c_read_buf;
+ xfer[1].flags = I2C_M_RD;
+
+ ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer));
+ if (ret < 0)
+ return ret;
+ if (ret != ARRAY_SIZE(xfer))
+ return -EIO;
+
+ if (reg == NAU8310_RF000_DSP_COMM)
+ *value = get_unaligned_le32(nau8310->i2c_read_buf);
+ else
+ *value = get_unaligned_be16(nau8310->i2c_read_buf);
+
+ return 0;
+}
+
+static const struct regmap_config nau8310_regmap_config = {
+ .reg_bits = NAU8310_REG_ADDR_LEN,
+ .val_bits = NAU8310_REG_DATA_LEN,
+
+ .max_register = NAU8310_REG_MAX,
+ .readable_reg = nau8310_readable_reg,
+ .writeable_reg = nau8310_writeable_reg,
+ .volatile_reg = nau8310_volatile_reg,
+ .reg_read = nau8310_reg_read,
+ .reg_write = nau8310_reg_write,
+
+ .cache_type = REGCACHE_MAPLE,
+ .reg_defaults = nau8310_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(nau8310_reg_defaults),
+};
+
+static void nau8310_init_regs(struct nau8310 *nau8310)
+{
+ struct regmap *regmap = nau8310->regmap;
+ unsigned int clk_det = 0, ana_ctrl = 0;
+
+ /* Latch IIC LSB value */
+ regmap_write(regmap, NAU8310_R02_I2C_ADDR, 0x0001);
+ /* Block MCLK */
+ regmap_update_bits(regmap, NAU8310_R04_ENA_CTRL,
+ NAU8310_MCLK_SEL_MASK, 0x7 << NAU8310_MCLK_SEL_SFT);
+ /* stall DSP */
+ regmap_update_bits(regmap, NAU8310_R1A_DSP_CORE_CTRL2,
+ NAU8310_DSP_RUNSTALL, NAU8310_DSP_RUNSTALL);
+ /* Default ADCI from slot1, ADCV from slot 0. */
+ regmap_update_bits(regmap, NAU8310_R0C_TDM_CTRL,
+ NAU8310_ADC_I_SEL_MASK | NAU8310_ADC_V_SEL_MASK,
+ (0x4 << NAU8310_ADC_I_SEL_SFT) | 0x4);
+ /* Set I-sense digital gain to +10.25dB */
+ regmap_update_bits(regmap, NAU8310_R14_ADC_VOL_CTRL,
+ NAU8310_ADC_GAIN_L_MASK | NAU8310_ADC_GAIN_R_MASK,
+ (0x1 << NAU8310_ADC_GAIN_L_SFT) | 0x62);
+ /* PGA in Class A Mode */
+ regmap_update_bits(regmap, NAU8310_R76_BOOST,
+ NAU8310_STG2_SEL_CLASSA, NAU8310_STG2_SEL_CLASSA);
+ /* Set I-sense PGA Bias Current to minimum,
+ * Disable PGA Common Mode Lock.
+ */
+ regmap_update_bits(regmap, NAU8310_R77_FEPGA,
+ NAU8310_CURR_TRIM_MASK | NAU8310_CMLCK_ENB,
+ (0x7 << NAU8310_CURR_TRIM_SFT) | NAU8310_CMLCK_ENB);
+ /* Set I-sense PGA to +12dB gain */
+ regmap_update_bits(regmap, NAU8310_R7F_POWER_UP_CONTROL,
+ NAU8310_PGA_GAIN_MASK, 0x3 << NAU8310_PGA_GAIN_SFT);
+ /* Normal I2S Audio data w/o SAR ADC data.
+ * Default oversampling/decimations settings are unusable
+ * (audible hiss). Set it to something better.
+ */
+ regmap_update_bits(regmap, NAU8310_R28_ADC_RATE,
+ NAU8310_I2S_MODE | NAU8310_ADC_SYNC_DOWN_MASK,
+ (nau8310->normal_iis_data << NAU8310_I2S_MODE_SFT) |
+ NAU8310_ADC_SYNC_DOWN_64);
+ regmap_update_bits(regmap, NAU8310_R29_DAC_CTRL1,
+ NAU8310_DAC_OVERSAMPLE_MASK, NAU8310_DAC_OVERSAMPLE_64);
+ /* Enable auto attenuation for better output signal */
+ regmap_update_bits(regmap, NAU8310_R2E_ALC_CTRL3,
+ NAU8310_AUTOATT_EN, NAU8310_AUTOATT_EN);
+ /* Set ALC parameters */
+ regmap_update_bits(regmap, NAU8310_R2C_ALC_CTRL1,
+ NAU8310_ALC_MAXGAIN_MASK, 0x7 << NAU8310_ALC_MAXGAIN_SFT);
+ regmap_update_bits(regmap, NAU8310_R2D_ALC_CTRL2,
+ NAU8310_ALC_DCY_MASK | NAU8310_ALC_ATK_MASK |
+ NAU8310_ALC_HLD_MASK, (0x5 << NAU8310_ALC_DCY_SFT) |
+ (0x3 << NAU8310_ALC_ATK_SFT) | (0x5 << NAU8310_ALC_HLD_SFT));
+ regmap_update_bits(regmap, NAU8310_R2E_ALC_CTRL3,
+ NAU8310_LIM_MDE_MASK | NAU8310_VBAT_THLD_MASK,
+ (0x3 << NAU8310_LIM_MDE_SFT) | (0x18 << NAU8310_VBAT_THLD_SFT));
+
+ if (!nau8310->clock_detection)
+ clk_det |= NAU8310_CLKPWRUP_DIS;
+ if (nau8310->clock_det_data)
+ clk_det |= NAU8310_APWRUP_EN;
+ regmap_update_bits(regmap, NAU8310_R40_CLK_DET_CTRL,
+ NAU8310_CLKPWRUP_DIS | NAU8310_PWRUP_DFT | NAU8310_APWRUP_EN, clk_det);
+
+ /* Boost Target Limit and Margin */
+ regmap_update_bits(regmap, NAU8310_R17_BOOST_CTRL1,
+ NAU8310_BSTLIMIT_MASK | NAU8310_BSTMARGIN_MASK,
+ (nau8310->boost_target_limit << NAU8310_BSTLIMIT_SFT) |
+ nau8310->boost_target_margin);
+ /* Temperature compensation */
+ regmap_update_bits(regmap, NAU8310_R30_TEMP_COMP_CTRL,
+ NAU8310_TEMP_COMP_ACT2_MASK, 0);
+ /* Low pass filter control */
+ regmap_update_bits(regmap, NAU8310_R33_LPF_CTRL,
+ NAU8310_LPF_IN1_EN | NAU8310_LPF_IN1_TC_MASK |
+ NAU8310_LPF_IN2_EN | NAU8310_LPF_IN2_TC_MASK,
+ NAU8310_LPF_IN1_EN | (0x7 << NAU8310_LPF_IN1_TC_SFT) |
+ NAU8310_LPF_IN2_EN | (0x7 << NAU8310_LPF_IN2_TC_SFT));
+ /* Set Boost Attack/Decay, Hold Time and TC EN */
+ regmap_update_bits(regmap, NAU8310_R18_BOOST_CTRL2, NAU8310_TC_EN |
+ NAU8310_BSTHOLD_MASK | NAU8310_BSTSTEPTIME_MASK |
+ NAU8310_BSTDELAY_MASK, (nau8310->temp_compensation << NAU8310_TC_SFT) |
+ (0xf << NAU8310_BSTHOLD_SFT) |
+ (0x7 << NAU8310_BSTSTEPTIME_SFT) | nau8310->boost_delay);
+ /* DAC Reference Voltage Setting */
+ regmap_update_bits(regmap, NAU8310_R73_RDAC,
+ NAU8310_DACVREFSEL_MASK, nau8310->dac_vref << NAU8310_DACVREFSEL_SFT);
+ /* DAC Reference Voltage Decoupling Capacitors. */
+ regmap_update_bits(regmap, NAU8310_R63_ANALOG_CONTROL_3,
+ NAU8310_DACREFCAP_MASK, 0x3 << NAU8310_DACREFCAP_SFT);
+ /* Auto-Att Min Gain 12dB, Class-D P&N Driver Slew Rate +75%. */
+ regmap_update_bits(regmap, NAU8310_R64_ANALOG_CONTROL_4,
+ NAU8310_AUTOATTMIN_MASK | NAU8310_CLASSD_SLEWN_MASK |
+ NAU8310_CLASSD_SLEWP_MASK | NAU8310_CLASSD_SHORTP_MASK |
+ NAU8310_CLASSD_SHORTN_MASK,
+ NAU8310_CLASSD_SHORTP_100UP | NAU8310_AUTOATTMIN_12DB |
+ NAU8310_CLASSD_SLEWP_75UP | NAU8310_CLASSD_SLEWN_75UP);
+ /* Bias Current, Monitor Vboost drops below VBAT,
+ * Non-Overlapping-Time Control.
+ */
+ regmap_update_bits(regmap, NAU8310_R65_ANALOG_CONTROL_5,
+ NAU8310_BSTINDR_MASK | NAU8310_BSTCURGEN_MASK |
+ NAU8310_BSTMONEN | NAU8310_BSTCLKPULSE_MASK,
+ (0x4 << NAU8310_BSTINDR_SFT) | NAU8310_BSTMONEN |
+ (0xd << NAU8310_BSTCURGEN_SFT) | 0x2);
+ /* Slew Rate Adjust, Bias Current. */
+ regmap_update_bits(regmap, NAU8310_R66_ANALOG_CONTROL_6,
+ NAU8310_BSTSLEWPOFF_MASK | NAU8310_BSTSLEWPON_MASK |
+ NAU8310_BSTSLEWNON_MASK | NAU8310_BSTSLEWNOFF_MASK |
+ NAU8310_BSTIPDR_MASK, (0x3 << NAU8310_BSTSLEWPOFF_SFT) |
+ (0x2 << NAU8310_BSTSLEWPON_SFT) | (0x2 << NAU8310_BSTSLEWNON_SFT) |
+ (0x1 << NAU8310_BSTSLEWNOFF_SFT) | 0x4);
+
+ if (nau8310->silicon_id == NAU8310_REG_SI_REV_G10)
+ /* Peak Current 4A, Low Voltage 2.8V */
+ ana_ctrl = (0x11 << NAU8310_VBAT_PCL_SFT) | (0xf << NAU8310_VBAT_THD_SFT);
+ else if (nau8310->silicon_id == NAU8310_REG_SI_REV_G20)
+ /* Peak Current 1A, Low Voltage 3.6V */
+ ana_ctrl = (0x7 << NAU8310_VBAT_THD_SFT);
+
+ regmap_update_bits(regmap, NAU8310_R6B_ANALOG_CONTROL_8,
+ NAU8310_VBAT_PCL_MASK | NAU8310_VBAT_THD_MASK, ana_ctrl);
+
+ /* current limiting for low VBAT level */
+ regmap_update_bits(regmap, NAU8310_R6C_ANALOG_CONTROL_9,
+ NAU8310_VBAT_CURLMT_MASK, 0xf);
+ /* VMID Tieoff (VMID Resistor Selection) */
+ regmap_update_bits(regmap, NAU8310_R60_BIAS_ADJ,
+ NAU8310_BIAS_VMID_SEL_MASK,
+ nau8310->vref_impedance << NAU8310_BIAS_VMID_SEL_SFT);
+ /* VREF Bandgap buffer ON, I/V Sense Ref Buffer Setting -1.5dB. */
+ regmap_update_bits(regmap, NAU8310_R68_ANALOG_CONTROL_7,
+ NAU8310_ADCGAIN_MASK | NAU8310_VREFBG_EN,
+ (0x3 << NAU8310_ADCGAIN_SFT) | NAU8310_VREFBG_EN);
+ nau8310_software_reset(regmap);
+ /* Enable VMID, BIAS, DAC, DCA CLOCK, ADC, Voltage/Current Amps,
+ * ADC Resetb gated by 'PowerUp' Signal.
+ */
+ regmap_update_bits(regmap, NAU8310_R61_ANALOG_CONTROL_1,
+ NAU8310_ISEN_MASK | NAU8310_ADCRSTEN_MASK |
+ NAU8310_VSEN_MASK | NAU8310_ADCEN_MASK |
+ NAU8310_DACCLKEN_MASK | NAU8310_DACEN_MASK |
+ NAU8310_BIASEN_MASK | NAU8310_VMIDEN_MASK,
+ (0x1 << NAU8310_ISEN_SFT) | (0x1 << NAU8310_ADCRSTEN_SFT) |
+ (0x1 << NAU8310_VSEN_SFT) | (0x1 << NAU8310_ADCEN_SFT) |
+ (0x1 << NAU8310_DACCLKEN_SFT) | (0x1 << NAU8310_DACEN_SFT) |
+ (0x1 << NAU8310_BIASEN_SFT) | 0x1);
+ /* Enable Boost Converter, VMID Fast, Class-D gated by
+ * 'PowerUp' Signal
+ */
+ regmap_update_bits(regmap, NAU8310_R62_ANALOG_CONTROL_2,
+ NAU8310_CLASSDEN_MASK | NAU8310_PDVMDFST_MASK |
+ NAU8310_BSTEN_MASK, (0x1 << NAU8310_CLASSDEN_SFT) |
+ (0x1 << NAU8310_PDVMDFST_SFT) | nau8310->boost_convert_enable);
+ regmap_update_bits(regmap, NAU8310_R07_SAR_CTRL1,
+ NAU8310_SAR_TRACKING_GAIN_MASK | NAU8310_SAR_COMPARE_TIME_MASK |
+ NAU8310_SAR_SAMPLING_TIME_MASK,
+ nau8310->sar_voltage << NAU8310_SAR_TRACKING_GAIN_SFT |
+ nau8310->sar_compare_time << NAU8310_SAR_COMPARE_TIME_SFT |
+ nau8310->sar_sampling_time << NAU8310_SAR_SAMPLING_TIME_SFT);
+}
+
+static void nau8310_print_device_properties(struct nau8310 *nau8310)
+{
+ struct device *dev = nau8310->dev;
+
+ dev_dbg(dev, "vref-impedance: %d", nau8310->vref_impedance);
+ dev_dbg(dev, "dac-vref: %d", nau8310->dac_vref);
+ dev_dbg(dev, "sar-voltage: %d", nau8310->sar_voltage);
+ dev_dbg(dev, "sar-compare-time: %d", nau8310->sar_compare_time);
+ dev_dbg(dev, "sar-sampling-time: %d", nau8310->sar_sampling_time);
+ dev_dbg(dev, "clock-det-disable: %d", nau8310->clock_detection);
+ dev_dbg(dev, "clock-det-data: %d", nau8310->clock_det_data);
+ dev_dbg(dev, "temp-compensation: %d", nau8310->temp_compensation);
+ dev_dbg(dev, "boost-delay: %d", nau8310->boost_delay);
+ dev_dbg(dev, "boost-convert-enable: %d", nau8310->boost_convert_enable);
+ dev_dbg(dev, "boost-target-limit: %x", nau8310->boost_target_limit);
+ dev_dbg(dev, "boost-target-margin: %x", nau8310->boost_target_margin);
+ dev_dbg(dev, "normal-iis-data: %d", nau8310->normal_iis_data);
+ dev_dbg(dev, "dsp-firmware: %s", nau8310->dsp_firmware);
+}
+
+static int nau8310_read_device_properties(struct nau8310 *nau8310)
+{
+ const char *def_fws = NAU8310_DSP_FIRMWARE;
+ struct device *dev = nau8310->dev;
+ const char *firmware_names = NULL;
+ int ret;
+
+ ret = device_property_read_u32(dev, "nuvoton,vref-impedance",
+ &nau8310->vref_impedance);
+ if (ret)
+ nau8310->vref_impedance = 2;
+
+ ret = device_property_read_u32(dev, "nuvoton,dac-vref",
+ &nau8310->dac_vref);
+ if (ret)
+ nau8310->dac_vref = 1;
+
+ ret = device_property_read_u32(dev, "nuvoton,sar-voltage",
+ &nau8310->sar_voltage);
+ if (ret)
+ nau8310->sar_voltage = 0;
+
+ ret = device_property_read_u32(dev, "nuvoton,sar-compare-time",
+ &nau8310->sar_compare_time);
+ if (ret)
+ nau8310->sar_compare_time = 1;
+
+ ret = device_property_read_u32(dev, "nuvoton,sar-sampling-time",
+ &nau8310->sar_sampling_time);
+ if (ret)
+ nau8310->sar_sampling_time = 1;
+
+ nau8310->clock_detection =
+ !device_property_read_bool(dev, "nuvoton,clock-det-disable");
+ nau8310->clock_det_data =
+ device_property_read_bool(dev, "nuvoton,clock-det-data");
+ nau8310->temp_compensation =
+ device_property_read_bool(dev, "nuvoton,temp-compensation");
+ if (nau8310->silicon_id == NAU8310_REG_SI_REV_G10) {
+ ret = device_property_read_u32(dev, "nuvoton,boost-delay",
+ &nau8310->boost_delay);
+ if (ret)
+ nau8310->boost_delay = 0x8;
+
+ ret = device_property_read_u32(dev, "nuvoton,boost-convert-enable",
+ &nau8310->boost_convert_enable);
+ if (ret)
+ nau8310->boost_convert_enable = 0x0;
+
+ ret = device_property_read_u32(dev, "nuvoton,boost-target-limit",
+ &nau8310->boost_target_limit);
+ if (ret)
+ nau8310->boost_target_limit = 0x32;
+
+ ret = device_property_read_u32(dev, "nuvoton,boost-target-margin",
+ &nau8310->boost_target_margin);
+ if (ret)
+ nau8310->boost_target_margin = 0x2;
+ }
+ nau8310->normal_iis_data =
+ device_property_read_bool(dev, "nuvoton,normal-iis-data");
+
+ ret = device_property_read_string(dev, "firmware-name",
+ &firmware_names);
+
+ if (ret) {
+ nau8310->dsp_firmware = devm_kstrdup(dev, def_fws, GFP_KERNEL);
+
+ if (!nau8310->dsp_firmware)
+ return -ENOMEM;
+ } else {
+ nau8310->dsp_firmware = devm_kasprintf(dev, GFP_KERNEL,
+ NAU8310_DSP_FIRMDIR "%s", firmware_names);
+
+ if (!nau8310->dsp_firmware)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int nau8310_i2c_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct nau8310 *nau8310;
+ int ret, value;
+
+ nau8310 = devm_kzalloc(dev, sizeof(*nau8310), GFP_KERNEL);
+ if (!nau8310)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, nau8310);
+ mutex_init(&nau8310->lock);
+ INIT_WORK(&nau8310->load_fw_work, nau8310_load_fw_work);
+
+ nau8310->regmap = devm_regmap_init(dev, NULL, i2c, &nau8310_regmap_config);
+ if (IS_ERR(nau8310->regmap))
+ return PTR_ERR(nau8310->regmap);
+
+ nau8310->dev = dev;
+
+ nau8310_reset_chip(nau8310->regmap);
+ ret = regmap_read(nau8310->regmap, NAU8310_R46_I2C_DEVICE_ID, &value);
+ if (ret) {
+ dev_err(dev, "Failed to read NAU8310/20 device id: %d", ret);
+ return ret;
+ }
+
+ nau8310->silicon_id = value & NAU8310_REG_SI_REV_MASK;
+ ret = nau8310_read_device_properties(nau8310);
+ if (ret)
+ return ret;
+
+ nau8310_print_device_properties(nau8310);
+ nau8310_init_regs(nau8310);
+
+ return devm_snd_soc_register_component(dev, &soc_comp_dev_nau8310,
+ &nau8310_dai, 1);
+}
+
+static void nau8310_i2c_remove(struct i2c_client *client)
+{
+ struct nau8310 *nau8310 = i2c_get_clientdata(client);
+
+ cancel_work_sync(&nau8310->load_fw_work);
+}
+
+static const struct i2c_device_id nau8310_i2c_ids[] = {
+ { .name = "nau8310" },
+ { .name = "nau8320" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, nau8310_i2c_ids);
+
+static const struct of_device_id nau8310_of_ids[] = {
+ { .compatible = "nuvoton,nau8310" },
+ { .compatible = "nuvoton,nau8320" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, nau8310_of_ids);
+
+static const struct acpi_device_id nau8310_acpi_match[] = {
+ { .id = "NVTN2000" },
+ { .id = "NVTN2001" },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, nau8310_acpi_match);
+
+static struct i2c_driver nau8310_i2c_driver = {
+ .driver = {
+ .name = "nau8310",
+ .of_match_table = of_match_ptr(nau8310_of_ids),
+ .acpi_match_table = ACPI_PTR(nau8310_acpi_match),
+ },
+ .probe = nau8310_i2c_probe,
+ .remove = nau8310_i2c_remove,
+ .id_table = nau8310_i2c_ids,
+};
+module_i2c_driver(nau8310_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC NAU83G10/20 Boosted Mono Class-D Amplifier with DSP and I/V-sense driver");
+MODULE_AUTHOR("John Hsu <kchsu0@nuvoton.com>");
+MODULE_AUTHOR("David Lin <ctlin0@nuvoton.com>");
+MODULE_AUTHOR("Neo Chang <ylchang2@nuvoton.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/nau8310.h b/sound/soc/codecs/nau8310.h
new file mode 100644
index 000000000000..f0c32b3d09b9
--- /dev/null
+++ b/sound/soc/codecs/nau8310.h
@@ -0,0 +1,629 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// The NAU83G10/20 Boosted Mono Class-D Amplifier with DSP and I/V-sense driver.
+//
+// Copyright 2026 Nuvoton Technology Crop.
+// Author: John Hsu <KCHSU0@nuvoton.com>
+// David Lin <ctlin0@nuvoton.com>
+// Neo Chang <ylchang2@nuvoton.com>
+
+#ifndef __NAU8310_H__
+#define __NAU8310_H__
+
+#define NAU8310_R00_HARDWARE_RST 0x00
+#define NAU8310_R01_SOFTWARE_RST 0x01
+#define NAU8310_R02_I2C_ADDR 0x02
+#define NAU8310_R03_CLK_CTRL 0x03
+#define NAU8310_R04_ENA_CTRL 0x04
+#define NAU8310_R05_INTERRUPT_CTRL 0x05
+#define NAU8310_R06_INT_CLR_STATUS 0x06
+#define NAU8310_R07_SAR_CTRL1 0x07
+#define NAU8310_R08_GPIO124_CTRL 0x08
+#define NAU8310_R09_GPIOOUT 0x09
+#define NAU8310_R0A_IO_CTRL 0x0a
+#define NAU8310_R0B_I2S_PCM_CTRL0 0x0b
+#define NAU8310_R0C_TDM_CTRL 0x0c
+#define NAU8310_R0D_I2S_PCM_CTRL1 0x0d
+#define NAU8310_R0E_I2S_PCM_CTRL2 0x0e
+#define NAU8310_R0F_LEFT_TIME_SLOT 0x0f
+#define NAU8310_R10_RIGHT_TIME_SLOT 0x10
+#define NAU8310_R12_HPF_CTRL 0x12
+#define NAU8310_R13_MUTE_CTRL 0x13
+#define NAU8310_R14_ADC_VOL_CTRL 0x14
+#define NAU8310_R15_INT_COE 0x15
+#define NAU8310_R16_DEC_COE 0x16
+#define NAU8310_R17_BOOST_CTRL1 0x17
+#define NAU8310_R18_BOOST_CTRL2 0x18
+#define NAU8310_R19_DSP_CORE_CTRL1 0x19
+#define NAU8310_R1A_DSP_CORE_CTRL2 0x1a
+#define NAU8310_R1B_CLK_DOUBLER_O 0x1b
+#define NAU8310_R1C_BOOST_CTRL_O 0x1c
+#define NAU8310_R1D_GENERAL_STATUS0 0x1d
+#define NAU8310_R1E_GENERAL_STATUS1 0x1e
+#define NAU8310_R1F_GENERAL_STATUS2 0x1f
+#define NAU8310_R20_SAR_ADC_OUT_01 0x20
+#define NAU8310_R21_SAR_ADC_OUT_23 0x21
+#define NAU8310_R22_ALC_READOUT1 0x22
+#define NAU8310_R23_ALC_READOUT2 0x23
+#define NAU8310_R24_ALC_READOUT3 0x24
+#define NAU8310_R25_DSP_STATUS_0 0x25
+#define NAU8310_R26_DSP_STATUS_1 0x26
+#define NAU8310_R27_DSP_STATUS_2 0x27
+#define NAU8310_R28_ADC_RATE 0x28
+#define NAU8310_R29_DAC_CTRL1 0x29
+#define NAU8310_R2A_DAC_CTRL2 0x2a
+#define NAU8310_R2C_ALC_CTRL1 0x2c
+#define NAU8310_R2D_ALC_CTRL2 0x2d
+#define NAU8310_R2E_ALC_CTRL3 0x2e
+#define NAU8310_R2F_ALC_CTRL4 0x2f
+#define NAU8310_R30_TEMP_COMP_CTRL 0x30
+#define NAU8310_R31_UVLO_CTRL0 0x31
+#define NAU8310_R32_UVLO_CTRL1 0x32
+#define NAU8310_R33_LPF_CTRL 0x33
+#define NAU8310_R40_CLK_DET_CTRL 0x40
+#define NAU8310_R46_I2C_DEVICE_ID 0x46
+#define NAU8310_R4A_ANALOG_READ 0x4a
+#define NAU8310_R55_MISC_CTRL 0x55
+#define NAU8310_R60_BIAS_ADJ 0x60
+#define NAU8310_R61_ANALOG_CONTROL_1 0x61
+#define NAU8310_R62_ANALOG_CONTROL_2 0x62
+#define NAU8310_R63_ANALOG_CONTROL_3 0x63
+#define NAU8310_R64_ANALOG_CONTROL_4 0x64
+#define NAU8310_R65_ANALOG_CONTROL_5 0x65
+#define NAU8310_R66_ANALOG_CONTROL_6 0x66
+#define NAU8310_R68_ANALOG_CONTROL_7 0x68
+#define NAU8310_R69_CLIP_CTRL 0x69
+#define NAU8310_R6B_ANALOG_CONTROL_8 0x6b
+#define NAU8310_R6C_ANALOG_CONTROL_9 0x6c
+#define NAU8310_R71_ANALOG_ADC_1 0x71
+#define NAU8310_R72_ANALOG_ADC_2 0x72
+#define NAU8310_R73_RDAC 0x73
+#define NAU8310_R76_BOOST 0x76
+#define NAU8310_R77_FEPGA 0x77
+#define NAU8310_R7F_POWER_UP_CONTROL 0x7f
+#define NAU8310_R80_BIQ0_COE_1 0x80
+#define NAU8310_R81_BIQ0_COE_2 0x81
+#define NAU8310_R82_BIQ0_COE_3 0x82
+#define NAU8310_R83_BIQ0_COE_4 0x83
+#define NAU8310_R84_BIQ0_COE_5 0x84
+#define NAU8310_R85_BIQ0_COE_6 0x85
+#define NAU8310_R86_BIQ0_COE_7 0x86
+#define NAU8310_R87_BIQ0_COE_8 0x87
+#define NAU8310_R88_BIQ0_COE_9 0x88
+#define NAU8310_R89_BIQ0_COE_10 0x89
+#define NAU8310_R8A_BIQ1_COE_1 0x8a
+#define NAU8310_R8B_BIQ1_COE_2 0x8b
+#define NAU8310_R8C_BIQ1_COE_3 0x8c
+#define NAU8310_R8D_BIQ1_COE_4 0x8d
+#define NAU8310_R8E_BIQ1_COE_5 0x8e
+#define NAU8310_R8F_BIQ1_COE_6 0x8f
+#define NAU8310_R90_BIQ1_COE_7 0x90
+#define NAU8310_R91_BIQ1_COE_8 0x91
+#define NAU8310_R92_BIQ1_COE_9 0x92
+#define NAU8310_R93_BIQ1_COE_10 0x93
+#define NAU8310_R94_BIQ2_COE_1 0x94
+#define NAU8310_R95_BIQ2_COE_2 0x95
+#define NAU8310_R96_BIQ2_COE_3 0x96
+#define NAU8310_R97_BIQ2_COE_4 0x97
+#define NAU8310_R98_BIQ2_COE_5 0x98
+#define NAU8310_R99_BIQ2_COE_6 0x99
+#define NAU8310_R9A_BIQ2_COE_7 0x9a
+#define NAU8310_R9B_BIQ2_COE_8 0x9b
+#define NAU8310_R9C_BIQ2_COE_9 0x9c
+#define NAU8310_R9D_BIQ2_COE_10 0x9d
+#define NAU8310_RF000_DSP_COMM 0xf000
+#define NAU8310_REG_MAX NAU8310_RF000_DSP_COMM
+/* 16-bit control register address, and 16-bits control register data */
+#define NAU8310_REG_ADDR_LEN 16
+#define NAU8310_REG_DATA_LEN 16
+
+
+/* CLK_CTRL (0x03) */
+#define NAU8310_CLK_ADC_DIV2_SFT 12
+#define NAU8310_CLK_ADC_DIV2 (0x1 << NAU8310_CLK_ADC_DIV2_SFT)
+#define NAU8310_CLK_ADC_DIV4_SFT 11
+#define NAU8310_CLK_ADC_DIV4 (0x1 << NAU8310_CLK_ADC_DIV4_SFT)
+#define NAU8310_CLK_ADC_SRC_SFT 6
+#define NAU8310_CLK_ADC_SRC_MASK (0x3 << NAU8310_CLK_ADC_SRC_SFT)
+#define NAU8310_CLK_DAC_SRC_SFT 4
+#define NAU8310_CLK_DAC_SRC_MASK (0x3 << NAU8310_CLK_DAC_SRC_SFT)
+#define NAU8310_MCLK_SRC_MASK 0x7
+
+/* ENA_CTRL (0x04) */
+#define NAU8310_DSP_OSC_SFT 14
+#define NAU8310_DSP_OSC_EN (0x1 << NAU8310_DSP_OSC_SFT)
+#define NAU8310_DSP_SEL_OSC_SFT 13
+#define NAU8310_DSP_SEL_OSC (0x1 << NAU8310_DSP_SEL_OSC_SFT)
+#define NAU8310_MCLK_SEL_SFT 8
+#define NAU8310_MCLK_SEL_MASK (0x7 << NAU8310_MCLK_SEL_SFT)
+#define NAU8310_CLK_MUL_SRC_SFT 6
+#define NAU8310_CLK_MUL_SRC_MASK (0x3 << NAU8310_CLK_MUL_SRC_SFT)
+#define NAU8310_CLK_DSP_SRC_SFT 3
+#define NAU8310_CLK_DSP_SRC_MASK (0x7 << NAU8310_CLK_DSP_SRC_SFT)
+#define NAU8310_DAC_CH_EN_SFT 2
+#define NAU8310_DAC_CH_EN (0x1 << NAU8310_DAC_CH_EN_SFT)
+#define NAU8310_ADCEN_I_SFT 1
+#define NAU8310_ADCEN_I (0x1 << NAU8310_ADCEN_I_SFT)
+#define NAU8310_ADCEN_V_SFT 0
+#define NAU8310_ADCEN_V (0x1 << NAU8310_ADCEN_V_SFT)
+
+/* INTERRUPT_CTRL (0x05) */
+#define NAU8310_WD_INT_MASK_SFT 14
+#define NAU8310_WD_INT_MASK (0x1 << NAU8310_WD_INT_MASK_SFT)
+#define NAU8310_DSP2I2C_INT_MASK_SFT 13
+#define NAU8310_DSP2I2C_INT_MASK (0x1 << NAU8310_DSP2I2C_INT_MASK_SFT)
+#define NAU8310_ARP_DWN_INT_MASK_SFT 12
+#define NAU8310_ARP_DWN_INT_MASK (0x1 << NAU8310_ARP_DWN_INT_MASK_SFT)
+#define NAU8310_CLIP_INT_MASK_SFT 11
+#define NAU8310_CLIP_INT_MASK (0x1 << NAU8310_CLIP_INT_MASK_SFT)
+#define NAU8310_LVD_INT_MASK_SFT 10
+#define NAU8310_LVD_INT_MASK (0x1 << NAU8310_LVD_INT_MASK_SFT)
+#define NAU8310_OVP_INT_MASK_SFT 9
+#define NAU8310_OVP_INT_MASK (0x1 << NAU8310_OVP_INT_MASK_SFT)
+#define NAU8310_PWR_INT_DIS_SFT 8
+#define NAU8310_PWR_INT_DIS (0x1 << NAU8310_PWR_INT_DIS_SFT)
+#define NAU8310_WD_INT_DIS_SFT 6
+#define NAU8310_WD_INT_DIS (0x1 << NAU8310_WD_INT_DIS_SFT)
+#define NAU8310_DSP2I2C_INT_DIS_SFT 5
+#define NAU8310_DSP2I2C_INT_DIS (0x1 << NAU8310_DSP2I2C_INT_DIS_SFT)
+#define NAU8310_ARP_DWN_INT_DIS_SFT 4
+#define NAU8310_ARP_DWN_INT_DIS (0x1 << NAU8310_ARP_DWN_INT_DIS_SFT)
+#define NAU8310_CLIP_INT_DIS_SFT 3
+#define NAU8310_CLIP_INT_DIS (0x1 << NAU8310_CLIP_INT_DIS_SFT)
+#define NAU8310_LVD_INT_DIS_SFT 2
+#define NAU8310_LVD_INT_DIS (0x1 << NAU8310_LVD_INT_DIS_SFT)
+#define NAU8310_OVP_INT_DIS_SFT 1
+#define NAU8310_OVP_INT_DIS (0x1 << NAU8310_OVP_INT_DIS_SFT)
+#define NAU8310_PWR_INT_MASK 0x1
+
+/* INT_CLR_STATUS (0x06) */
+#define NAU8310_INT_STATUS_MASK 0x7ff
+#define NAU8310_INT_STATUS_WD (0x1 << 6)
+#define NAU8310_INT_STATUS_DSP2I2C (0x1 << 5)
+#define NAU8310_INT_STATUS_ARP_DWN (0x1 << 4)
+#define NAU8310_INT_STATUS_CLIP (0x1 << 3)
+#define NAU8310_INT_STATUS_LVD (0x1 << 2)
+#define NAU8310_INT_STATUS_OVP (0x1 << 1)
+#define NAU8310_INT_STATUS_PWR 0x1
+
+/* SAR_CTRL1 (0x07) */
+#define NAU8310_SAR_TRACKING_GAIN_SFT 11
+#define NAU8310_SAR_TRACKING_GAIN_MASK (0x7 << NAU8310_SAR_TRACKING_GAIN_SFT)
+#define NAU8310_SAR_COMPARE_TIME_SFT 5
+#define NAU8310_SAR_COMPARE_TIME_MASK (0x3 << NAU8310_SAR_COMPARE_TIME_SFT)
+#define NAU8310_SAR_SAMPLING_TIME_SFT 2
+#define NAU8310_SAR_SAMPLING_TIME_MASK (0x3 << NAU8310_SAR_SAMPLING_TIME_SFT)
+#define NAU8310_SAR_ENA_SFT 0
+#define NAU8310_SAR_ENA_EN (0x1 << NAU8310_SAR_ENA_SFT)
+
+/* IO_CTRL (0x0a) */
+#define NAU8310_IRQ_PL_SFT 15
+#define NAU8310_IRQ_PL_ACT_HIGH (0x1 << NAU8310_IRQ_PL_SFT)
+#define NAU8310_IRQ_DS_SFT 12
+#define NAU8310_IRQ_DS_HIGH (0x1 << NAU8310_IRQ_DS_SFT)
+#define NAU8310_IRQ_OUTPUT_SFT 11
+#define NAU8310_IRQ_OUTPUT_EN (0x1 << NAU8310_IRQ_OUTPUT_SFT)
+#define NAU8310_BCLK_DS_SFT 2
+#define NAU8310_BCLK_DS_EN (0x1 << NAU8310_BCLK_DS_SFT)
+#define NAU8310_LRC_DS_SFT 1
+#define NAU8310_LRC_DS_EN (0x1 << NAU8310_LRC_DS_SFT)
+#define NAU8310_ADCDAT_DS_SFT 0
+#define NAU8310_ADCDAT_DS_EN 0x1
+
+/* I2S_PCM_CTRL0 (0x0b) */
+#define NAU8310_ALC_MODE_SFT 15
+#define NAU8310_ALC_MODE (0x1 << NAU8310_ALC_MODE_SFT)
+#define NAU8310_ALC_SEND_ON_LOC_SFT 14
+#define NAU8310_ALC_SEND_ON_LOC (0x1 << NAU8310_ALC_SEND_ON_LOC_SFT)
+#define NAU8310_DAC_SEL_SFT 10
+#define NAU8310_DAC_SEL_MASK (0x7 << NAU8310_DAC_SEL_SFT)
+#define NAU8310_DAC_SEL_SLOT0 0
+#define NAU8310_DAC_SEL_SLOT1 (0x1 << NAU8310_DAC_SEL_SFT)
+#define NAU8310_DAC_SEL_SLOT2 (0x2 << NAU8310_DAC_SEL_SFT)
+#define NAU8310_DAC_SEL_SLOT3 (0x3 << NAU8310_DAC_SEL_SFT)
+#define NAU8310_DAC_SEL_SLOT4 (0x4 << NAU8310_DAC_SEL_SFT)
+#define NAU8310_DAC_SEL_SLOT5 (0x5 << NAU8310_DAC_SEL_SFT)
+#define NAU8310_DAC_SEL_SLOT6 (0x6 << NAU8310_DAC_SEL_SFT)
+#define NAU8310_DAC_SEL_SLOT7 (0x7 << NAU8310_DAC_SEL_SFT)
+#define NAU8310_AEC_CH_SEL_SFT 2
+#define NAU8310_AEC_CH_SEL_RIGHT (0x0 << NAU8310_AEC_CH_SEL_SFT)
+#define NAU8310_AEC_CH_SEL_LEFT (0x1 << NAU8310_AEC_CH_SEL_SFT)
+#define NAU8310_AEC_SRC_SEL_SFT 1
+#define NAU8310_AEC_SRC_SEL_DSP (0x0 << NAU8310_AEC_SRC_SEL_SFT)
+#define NAU8310_AEC_SRC_SEL_DAC (0x1 << NAU8310_AEC_SRC_SEL_SFT)
+#define NAU8310_AEC_MODE_SFT 0
+#define NAU8310_AEC_MODE_IV (0x0 << NAU8310_AEC_MODE_SFT)
+#define NAU8310_AEC_MODE_AEC (0x1 << NAU8310_AEC_MODE_SFT)
+
+/* TDM_CTRL (0x0c) */
+#define NAU8310_TDM_SFT 15
+#define NAU8310_TDM_EN (0x1 << NAU8310_TDM_SFT)
+#define NAU8310_TDM_OFFSET_SFT 14
+#define NAU8310_TDM_OFFSET_EN (0x1 << NAU8310_TDM_OFFSET_SFT)
+#define NAU8310_PINGPONG_SFT 13
+#define NAU8310_PINGPONG_EN (0x1 << NAU8310_PINGPONG_SFT)
+#define NAU8310_ADC_ALC_SEL_SFT 6
+#define NAU8310_ADC_ALC_SEL_MASK (0xf << NAU8310_ADC_ALC_SEL_SFT)
+#define NAU8310_ADC_I_SEL_SFT 3
+#define NAU8310_ADC_I_SEL_MASK (0x7 << NAU8310_ADC_I_SEL_SFT)
+#define NAU8310_ADC_I_SEL_DIS 0
+#define NAU8310_ADC_I_SEL_SLOT1 (0x4 << NAU8310_ADC_I_SEL_SFT)
+#define NAU8310_ADC_I_SEL_SLOT3 (0x5 << NAU8310_ADC_I_SEL_SFT)
+#define NAU8310_ADC_I_SEL_SLOT5 (0x6 << NAU8310_ADC_I_SEL_SFT)
+#define NAU8310_ADC_I_SEL_SLOT7 (0x7 << NAU8310_ADC_I_SEL_SFT)
+#define NAU8310_ADC_V_SEL_SFT 0
+#define NAU8310_ADC_V_SEL_MASK 0x7
+#define NAU8310_ADC_V_SEL_DIS 0
+#define NAU8310_ADC_V_SEL_SLOT0 0x4
+#define NAU8310_ADC_V_SEL_SLOT2 0x5
+#define NAU8310_ADC_V_SEL_SLOT4 0x6
+#define NAU8310_ADC_V_SEL_SLOT6 0x7
+
+/* I2S_PCM_CTRL1 (0x0d) */
+#define NAU8310_I2S_BP_SFT 7
+#define NAU8310_I2S_BP_MASK (0x1 << NAU8310_I2S_BP_SFT)
+#define NAU8310_I2S_BP_INV (0x1 << NAU8310_I2S_BP_SFT)
+#define NAU8310_I2S_PCMB_SFT 6
+#define NAU8310_I2S_PCMB_EN (0x1 << NAU8310_I2S_PCMB_SFT)
+#define NAU8310_I2S_DL_SFT 2
+#define NAU8310_I2S_DL_MASK (0x3 << NAU8310_I2S_DL_SFT)
+#define NAU8310_I2S_DL_16 (0x0 << NAU8310_I2S_DL_SFT)
+#define NAU8310_I2S_DL_20 (0x1 << NAU8310_I2S_DL_SFT)
+#define NAU8310_I2S_DL_24 (0x2 << NAU8310_I2S_DL_SFT)
+#define NAU8310_I2S_DL_32 (0x3 << NAU8310_I2S_DL_SFT)
+#define NAU8310_I2S_DF_MASK 0x3
+#define NAU8310_I2S_DF_RIGHT 0x0
+#define NAU8310_I2S_DF_LEFT 0x1
+#define NAU8310_I2S_DF_I2S 0x2
+#define NAU8310_I2S_DF_PCM_AB 0x3
+
+/* I2S_PCM_CTRL2 (0x0e) */
+#define NAU8310_I2S_TRISTATE_SFT 15
+#define NAU8310_I2S_TRISTATE (1 << NAU8310_I2S_TRISTATE_SFT)
+#define NAU8310_I2S_LRC_DIV_SFT 12
+#define NAU8310_I2S_LRC_DIV_MASK (0x3 << NAU8310_I2S_LRC_DIV_SFT)
+#define NAU8310_I2S_ADCDAT_OE_SFT 4
+#define NAU8310_I2S_ADCDAT_OE_MASK (1 << NAU8310_I2S_ADCDAT_OE_SFT)
+#define NAU8310_I2S_ADCDAT_OE_EN (0 << NAU8310_I2S_ADCDAT_OE_SFT)
+#define NAU8310_I2S_ADCDAT_OE_DIS (1 << NAU8310_I2S_ADCDAT_OE_SFT)
+#define NAU8310_I2S_MS_SFT 3
+#define NAU8310_I2S_MS_MASK (0x1 << NAU8310_I2S_MS_SFT)
+#define NAU8310_I2S_MS_MASTER (0x1 << NAU8310_I2S_MS_SFT)
+#define NAU8310_I2S_BLK_DIV_MASK 0x7
+
+/* MUTE_CTRL (0x13) */
+#define NAU8310_SOFT_MUTE_SFT 15
+#define NAU8310_SOFT_MUTE (0x1 << NAU8310_SOFT_MUTE_SFT)
+#define NAU8310_DAC_ZE_SFT 8
+#define NAU8310_DAC_ZE_EN (0x1 << NAU8310_DAC_ZE_SFT)
+#define NAU8310_DAC_VOL_SFT 0
+#define NAU8310_DAC_VOL_MAX 0xff
+
+/* ADC_VOL_CTRL (0x14) */
+#define NAU8310_ADC_GAIN_L_SFT 8
+#define NAU8310_ADC_GAIN_L_MAX 0xc1
+#define NAU8310_ADC_GAIN_L_MASK (0xff << NAU8310_ADC_GAIN_L_SFT)
+#define NAU8310_ADC_GAIN_R_SFT 0
+#define NAU8310_ADC_GAIN_R_MAX 0xc1
+#define NAU8310_ADC_GAIN_R_MASK 0xff
+
+/* BOOST_CTRL1 (0x17) */
+#define NAU8310_BSTLIMIT_SFT 8
+#define NAU8310_BSTLIMIT_MAX 0x3f
+#define NAU8310_BSTLIMIT_MASK (0x3f << NAU8310_BSTLIMIT_SFT)
+#define NAU8310_BSTMARGIN_SFT 0
+#define NAU8310_BSTMARGIN_MAX 0x3f
+#define NAU8310_BSTMARGIN_MASK 0x3f
+
+/* BOOST_CTRL2 (0x18) */
+#define NAU8310_TC_SFT 15
+#define NAU8310_TC_EN (0x1 << NAU8310_TC_SFT)
+#define NAU8310_BSTHOLD_SFT 7
+#define NAU8310_BSTHOLD_MASK (0xf << NAU8310_BSTHOLD_SFT)
+#define NAU8310_BSTSTEPTIME_SFT 4
+#define NAU8310_BSTSTEPTIME_MASK (0x7 << NAU8310_BSTSTEPTIME_SFT)
+#define NAU8310_BSTDELAY_MASK 0xf
+
+/* DSP_CORE_CTRL2 (0x1a) */
+#define NAU8310_DAC_SEL_DSP_SFT 5
+#define NAU8310_DAC_SEL_DSP_OUT (0x1 << NAU8310_DAC_SEL_DSP_SFT)
+#define NAU8310_DSP_RUNSTALL_SFT 4
+#define NAU8310_DSP_RUNSTALL (0x1 << NAU8310_DSP_RUNSTALL_SFT)
+
+/* ADC_RATE (0x28) */
+#define NAU8310_I2S_MODE_SFT 15
+#define NAU8310_I2S_MODE (0x1 << NAU8310_I2S_MODE_SFT)
+#define NAU8310_UNSIGN_IV_SFT 8
+#define NAU8310_UNSIGN_IV (0x1 << NAU8310_UNSIGN_IV_SFT)
+#define NAU8310_ADC_SYNC_DOWN_SFT 0
+#define NAU8310_ADC_SYNC_DOWN_MASK 0x3
+#define NAU8310_ADC_SYNC_DOWN_32 0
+#define NAU8310_ADC_SYNC_DOWN_64 1
+#define NAU8310_ADC_SYNC_DOWN_128 2
+
+/* DAC_CTRL1 (0x29) */
+#define NAU8310_DAC_OVERSAMPLE_SFT 0
+#define NAU8310_DAC_OVERSAMPLE_MASK 0x7
+#define NAU8310_DAC_OVERSAMPLE_64 0
+#define NAU8310_DAC_OVERSAMPLE_256 1
+#define NAU8310_DAC_OVERSAMPLE_128 2
+#define NAU8310_DAC_OVERSAMPLE_32 4
+
+/* ALC_CTRL1 (0x2c) */
+#define NAU8310_ALC_MAXGAIN_SFT 5
+#define NAU8310_ALC_MAXGAIN_MAX 0x7
+#define NAU8310_ALC_MAXGAIN_MASK (0x7 << NAU8310_ALC_MAXGAIN_SFT)
+#define NAU8310_ALC_MINGAIN_SFT 1
+#define NAU8310_ALC_MINGAIN_MAX 4
+#define NAU8310_ALC_MINGAIN_MASK (0x7 << NAU8310_ALC_MINGAIN_SFT)
+#define NAU8310_ALC_GAIN_SEL_SFT 0
+#define NAU8310_ALC_GAIN_SEL_MODE 1
+
+/* ALC_CTRL2 (0x2d) */
+#define NAU8310_ALC_DCY_SFT 12
+#define NAU8310_ALC_DCY_MAX 0xb
+#define NAU8310_ALC_DCY_MASK (0xf << NAU8310_ALC_DCY_SFT)
+#define NAU8310_ALC_ATK_SFT 8
+#define NAU8310_ALC_ATK_MAX 0xb
+#define NAU8310_ALC_ATK_MASK (0xf << NAU8310_ALC_ATK_SFT)
+#define NAU8310_ALC_HLD_SFT 4
+#define NAU8310_ALC_HLD_MAX 0xa
+#define NAU8310_ALC_HLD_MASK (0xf << NAU8310_ALC_HLD_SFT)
+#define NAU8310_ALC_LVL_SFT 0
+#define NAU8310_ALC_LVL_MAX 0xf
+#define NAU8310_ALC_LVL_MASK 0xf
+
+/* ALC_CTRL3 (0x2e) */
+#define NAU8310_ALC_EN_SFT 15
+#define NAU8310_ALC_EN (0x1 << NAU8310_ALC_EN_SFT)
+#define NAU8310_LIM_MDE_SFT 12
+#define NAU8310_LIM_MDE_MASK (0x7 << NAU8310_LIM_MDE_SFT)
+#define NAU8310_VBAT_THLD_SFT 5
+#define NAU8310_VBAT_THLD_MAX 0x1f
+#define NAU8310_VBAT_THLD_MASK (0x1f << NAU8310_VBAT_THLD_SFT)
+#define NAU8310_AUTOATT_EN_SFT 4
+#define NAU8310_AUTOATT_EN (0x1 << NAU8310_AUTOATT_EN_SFT)
+
+/* TEMP_COMP_CTRL (0x30) */
+#define NAU8310_TEMP_COMP_ACT2_MASK 0xff
+
+/* LPF_CTRL (0x33) */
+#define NAU8310_LPF_IN1_EN_SFT 15
+#define NAU8310_LPF_IN1_EN (0x1 << NAU8310_LPF_IN1_EN_SFT)
+#define NAU8310_LPF_IN1_TC_SFT 11
+#define NAU8310_LPF_IN1_TC_MASK (0xf << NAU8310_LPF_IN1_TC_SFT)
+#define NAU8310_LPF_IN2_EN_SFT 10
+#define NAU8310_LPF_IN2_EN (0x1 << NAU8310_LPF_IN2_EN_SFT)
+#define NAU8310_LPF_IN2_TC_SFT 6
+#define NAU8310_LPF_IN2_TC_MASK (0xf << NAU8310_LPF_IN2_TC_SFT)
+
+/* CLK_DET_CTRL (0x40) */
+#define NAU8310_APWRUP_SFT 15
+#define NAU8310_APWRUP_EN (0x1 << NAU8310_APWRUP_SFT)
+#define NAU8310_CLKPWRUP_SFT 14
+#define NAU8310_CLKPWRUP_DIS (0x1 << NAU8310_CLKPWRUP_SFT)
+#define NAU8310_PWRUP_DFT_SFT 13
+#define NAU8310_PWRUP_DFT (0x1 << NAU8310_PWRUP_DFT_SFT)
+#define NAU8310_SRATE_SFT 10
+#define NAU8310_SRATE_MASK (0x7 << NAU8310_SRATE_SFT)
+#define NAU8310_ALT_SRATE_SFT 9
+#define NAU8310_ALT_SRATE_EN (0x1 << NAU8310_ALT_SRATE_SFT)
+#define NAU8310_DIV_MAX 0x1
+
+/* I2C_DEVICE_ID (0x46) */
+#define NAU8310_I2C_DEVICE_ID_SFT 8
+#define NAU8310_I2C_DEVICE_ID_MASK (0x7f << NAU8310_I2C_DEVICE_ID_SFT)
+#define NAU8310_REG_SI_REV_MASK 0xff
+#define NAU8310_REG_SI_REV_G10 0xe1
+#define NAU8310_REG_SI_REV_G20 0x61
+
+/* BIAS_ADJ (0x60) */
+#define NAU8310_BIAS_VMID_SEL_SFT 4
+#define NAU8310_BIAS_VMID_SEL_MASK (0x3 << NAU8310_BIAS_VMID_SEL_SFT)
+
+/* ANALOG_CONTROL_1 (0x61) */
+#define NAU8310_ISEN_SFT 14
+#define NAU8310_ISEN_MASK (0x3 << NAU8310_ISEN_SFT)
+#define NAU8310_ADCRSTEN_SFT 12
+#define NAU8310_ADCRSTEN_MASK (0x3 << NAU8310_ADCRSTEN_SFT)
+#define NAU8310_VSEN_SFT 10
+#define NAU8310_VSEN_MASK (0x3 << NAU8310_VSEN_SFT)
+#define NAU8310_ADCEN_SFT 8
+#define NAU8310_ADCEN_MASK (0x3 << NAU8310_ADCEN_SFT)
+#define NAU8310_DACCLKEN_SFT 6
+#define NAU8310_DACCLKEN_MASK (0x3 << NAU8310_DACCLKEN_SFT)
+#define NAU8310_DACEN_SFT 4
+#define NAU8310_DACEN_MASK (0x3 << NAU8310_DACEN_SFT)
+#define NAU8310_BIASEN_SFT 2
+#define NAU8310_BIASEN_MASK (0x3 << NAU8310_BIASEN_SFT)
+#define NAU8310_VMIDEN_MASK 0x3
+
+/* ANALOG_CONTROL_2 (0x62) */
+#define NAU8310_CLASSDEN_SFT 4
+#define NAU8310_CLASSDEN_MASK (0x3 << NAU8310_CLASSDEN_SFT)
+#define NAU8310_PDVMDFST_SFT 2
+#define NAU8310_PDVMDFST_MASK (0x3 << NAU8310_PDVMDFST_SFT)
+#define NAU8310_BSTEN_MASK 0x3
+
+/* ANALOG_CONTROL_3 (0x63) */
+#define NAU8310_DACREFCAP_SFT 4
+#define NAU8310_DACREFCAP_MASK (0x3 << NAU8310_DACREFCAP_SFT)
+
+/* ANALOG_CONTROL_4 (0x64) */
+#define NAU8310_RECV_MODE_SFT 15
+#define NAU8310_RECV_MODE (0x1 << NAU8310_RECV_MODE_SFT)
+#define NAU8310_AUTOATTMIN_SFT 14
+#define NAU8310_AUTOATTMIN_MASK (0x1 << NAU8310_AUTOATTMIN_SFT)
+#define NAU8310_AUTOATTMIN_12DB (0x0 << NAU8310_AUTOATTMIN_SFT)
+#define NAU8310_AUTOATTMIN_0DB (0x1 << NAU8310_AUTOATTMIN_SFT)
+#define NAU8310_CLASSD_SHORTP_SFT 10
+#define NAU8310_CLASSD_SHORTP_MASK (0xf << NAU8310_CLASSD_SHORTP_SFT)
+#define NAU8310_CLASSD_SHORTP_NOMINAL (0x0 << NAU8310_CLASSD_SHORTP_SFT)
+#define NAU8310_CLASSD_SHORTP_33UP (0x1 << NAU8310_CLASSD_SHORTP_SFT)
+#define NAU8310_CLASSD_SHORTP_100UP (0x3 << NAU8310_CLASSD_SHORTP_SFT)
+#define NAU8310_CLASSD_SHORTP_22DOWN (0x8 << NAU8310_CLASSD_SHORTP_SFT)
+#define NAU8310_CLASSD_SHORTP_33DOWN (0xc << NAU8310_CLASSD_SHORTP_SFT)
+#define NAU8310_CLASSD_SHORTN_SFT 6
+#define NAU8310_CLASSD_SHORTN_MASK (0xf << NAU8310_CLASSD_SHORTN_SFT)
+#define NAU8310_CLASSD_SHORTN_NOMINAL (0x0 << NAU8310_CLASSD_SHORTN_SFT)
+#define NAU8310_CLASSD_SHORTN_33UP (0x1 << NAU8310_CLASSD_SHORTN_SFT)
+#define NAU8310_CLASSD_SHORTN_100UP (0x3 << NAU8310_CLASSD_SHORTN_SFT)
+#define NAU8310_CLASSD_SHORTN_22DOWN (0x8 << NAU8310_CLASSD_SHORTN_SFT)
+#define NAU8310_CLASSD_SHORTN_33DOWN (0xc << NAU8310_CLASSD_SHORTN_SFT)
+#define NAU8310_CLASSD_SLEWP_SFT 3
+#define NAU8310_CLASSD_SLEWP_MASK (0x7 << NAU8310_CLASSD_SLEWP_SFT)
+#define NAU8310_CLASSD_SLEWP_NOMINAL (0x0 << NAU8310_CLASSD_SLEWP_SFT)
+#define NAU8310_CLASSD_SLEWP_25UP (0x1 << NAU8310_CLASSD_SLEWP_SFT)
+#define NAU8310_CLASSD_SLEWP_50UP (0x2 << NAU8310_CLASSD_SLEWP_SFT)
+#define NAU8310_CLASSD_SLEWP_75UP (0x3 << NAU8310_CLASSD_SLEWP_SFT)
+#define NAU8310_CLASSD_SLEWP_OFF (0x4 << NAU8310_CLASSD_SLEWP_SFT)
+#define NAU8310_CLASSD_SLEWP_75DOWN (0x5 << NAU8310_CLASSD_SLEWP_SFT)
+#define NAU8310_CLASSD_SLEWP_50DOWN (0x6 << NAU8310_CLASSD_SLEWP_SFT)
+#define NAU8310_CLASSD_SLEWP_25DOWN (0x7 << NAU8310_CLASSD_SLEWP_SFT)
+#define NAU8310_CLASSD_SLEWN_SFT 0
+#define NAU8310_CLASSD_SLEWN_MASK 0x7
+#define NAU8310_CLASSD_SLEWN_NOMINAL 0x0
+#define NAU8310_CLASSD_SLEWN_25UP 0x1
+#define NAU8310_CLASSD_SLEWN_50UP 0x2
+#define NAU8310_CLASSD_SLEWN_75UP 0x3
+#define NAU8310_CLASSD_SLEWN_OFF 0x4
+#define NAU8310_CLASSD_SLEWN_75DOWN 0x5
+#define NAU8310_CLASSD_SLEWN_50DOWN 0x6
+#define NAU8310_CLASSD_SLEWN_25DOWN 0x7
+
+/* ANALOG_CONTROL_5 (0x65) */
+#define NAU8310_BSTINDR_SFT 12
+#define NAU8310_BSTINDR_MASK (0x7 << NAU8310_BSTINDR_SFT)
+#define NAU8310_BSTCURGEN_SFT 8
+#define NAU8310_BSTCURGEN_MASK (0xf << NAU8310_BSTCURGEN_SFT)
+#define NAU8310_BSTMONEN_SFT 7
+#define NAU8310_BSTMONEN (0x1 << NAU8310_BSTMONEN_SFT)
+#define NAU8310_BSTNOVPN_SFT 3
+#define NAU8310_BSTNOVPN_MASK (0x3 << NAU8310_BSTNOVPN_SFT)
+#define NAU8310_BSTCLKPULSE_MASK 0x3
+
+/* ANALOG_CONTROL_6 (0x66) */
+#define NAU8310_BSTRADJ_SFT 12
+#define NAU8310_BSTRADJ_MASK (0x7 << NAU8310_BSTRADJ_SFT)
+#define NAU8310_BSTSLEWPOFF_SFT 10
+#define NAU8310_BSTSLEWPOFF_MASK (0x3 << NAU8310_BSTSLEWPOFF_SFT)
+#define NAU8310_BSTSLEWPON_SFT 8
+#define NAU8310_BSTSLEWPON_MASK (0x3 << NAU8310_BSTSLEWPON_SFT)
+#define NAU8310_BSTSLEWNON_SFT 6
+#define NAU8310_BSTSLEWNON_MASK (0x3 << NAU8310_BSTSLEWNON_SFT)
+#define NAU8310_BSTSLEWNOFF_SFT 4
+#define NAU8310_BSTSLEWNOFF_MASK (0x3 << NAU8310_BSTSLEWNOFF_SFT)
+#define NAU8310_BSTIPDR_MASK 0x7
+
+/* ANALOG_CONTROL_7 (0x68) */
+#define NAU8310_ADCGAIN_SFT 9
+#define NAU8310_ADCGAIN_MASK (0x3 << NAU8310_ADCGAIN_SFT)
+#define NAU8310_VREFBG_SFT 6
+#define NAU8310_VREFBG_EN (0x1 << NAU8310_VREFBG_SFT)
+#define NAU8310_MU_HALF_RANGE_SFT 3
+#define NAU8310_MU_HALF_RANGE_EN (0x1 << NAU8310_MU_HALF_RANGE_SFT)
+#define NAU8310_MCLKX_MASK 0x7
+#define NAU8310_MCLK16XEN 0x4
+#define NAU8310_MCLK8XEN 0x2
+#define NAU8310_MCLK4XEN 0x1
+
+/* ANALOG_CONTROL_8 (0x6b) */
+#define NAU8310_VBAT_PCL_SFT 11
+#define NAU8310_VBAT_PCL_MAX 0x1f
+#define NAU8310_VBAT_PCL_MASK (0x1f << NAU8310_VBAT_PCL_SFT)
+#define NAU8310_VBAT_THD_SFT 4
+#define NAU8310_VBAT_THD_MAX 0xf
+#define NAU8310_VBAT_THD_MASK (0xf << NAU8310_VBAT_THD_SFT)
+
+/* ANALOG_CONTROL_9 (0x6c) */
+#define NAU8310_VBAT_CURLMT_MASK 0x1f
+
+/* RDAC (0x73) */
+#define NAU8310_DACVREFSEL_SFT 2
+#define NAU8310_DACVREFSEL_MASK (0x3 << NAU8310_DACVREFSEL_SFT)
+
+/* BOOST (0x76) */
+#define NAU8310_STG2_SEL_SFT 14
+#define NAU8310_STG2_SEL_CLASSA (0x1 << NAU8310_STG2_SEL_SFT)
+
+/* FEPGA (0x77) */
+#define NAU8310_CURR_TRIM_SFT 8
+#define NAU8310_CURR_TRIM_MAX 0x7
+#define NAU8310_CURR_TRIM_MASK (0x7 << NAU8310_CURR_TRIM_SFT)
+#define NAU8310_CMLCK_ENB_SFT 7
+#define NAU8310_CMLCK_ENB (0x1 << NAU8310_CMLCK_ENB_SFT)
+
+/* POWER_UP_CONTROL (0x7f) */
+#define NAU8310_PGA_GAIN_SFT 4
+#define NAU8310_PGA_GAIN_MAX 0x4
+#define NAU8310_PGA_GAIN_MASK (0x7 << NAU8310_PGA_GAIN_SFT)
+
+#define NAU8310_TDM_MAX_SLOTS 8
+
+#define NAU8310_I2C_REG_LEN 2
+#define NAU8310_I2C_RXBUF_LEN 4
+#define NAU8310_I2C_TXBUF_LEN 6
+#define NAU8310_CODEC_DAI "nau8310-hifi"
+
+/* DAC Source Path*/
+enum {
+ NAU8310_DAC_SRC_BYPASS = 0,
+ NAU8310_DAC_SRC_DSP,
+};
+
+struct nau8310 {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock;
+ struct snd_soc_dapm_context *dapm;
+ char silicon_id;
+ int mclk;
+ int fs;
+ int vref_impedance;
+ int dac_vref;
+ int sar_voltage;
+ int sar_compare_time;
+ int sar_sampling_time;
+ int clock_detection;
+ int clock_det_data;
+ int temp_compensation;
+ int boost_delay;
+ int boost_convert_enable;
+ int boost_target_limit;
+ int boost_target_margin;
+ int normal_iis_data;
+ int kcs_setup_size;
+ bool load_fw_done;
+ u8 i2c_read_reg[NAU8310_I2C_REG_LEN] ____cacheline_aligned;
+ u8 i2c_read_buf[NAU8310_I2C_RXBUF_LEN] ____cacheline_aligned;
+ u8 i2c_write_buf[NAU8310_I2C_TXBUF_LEN] ____cacheline_aligned;
+ struct work_struct load_fw_work;
+ const char *dsp_firmware;
+};
+
+struct nau8310_src_attr {
+ int param;
+ unsigned int val;
+};
+
+enum {
+ NAU8310_MCLK_FS_RATIO_256,
+ NAU8310_MCLK_FS_RATIO_400,
+ NAU8310_MCLK_FS_RATIO_500,
+ NAU8310_MCLK_FS_RATIO_NUM,
+};
+
+struct nau8310_srate_attr {
+ int fs;
+ int range;
+ bool max;
+ unsigned int mclk_src[NAU8310_MCLK_FS_RATIO_NUM];
+ int adc_div;
+};
+
+struct nau8310_osr_attr {
+ unsigned int osr;
+ unsigned int clk_src;
+};
+
+#endif /* __NAU8310_H__ */
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v1 1/2] ASoC: dt-bindings: nuvoton,nau8310: Add NAU83G10
2026-10-22 8:40 ` [PATCH v1 1/2] ASoC: dt-bindings: nuvoton,nau8310: Add NAU83G10 Neo Chang
@ 2026-10-22 8:51 ` sashiko-bot
0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-10-22 8:51 UTC (permalink / raw)
To: Neo Chang; +Cc: linux-sound, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] ASoC: dt-bindings: nuvoton,nau8310: inverted clock-det-disable description
- [Medium] ASoC: dt-bindings: nuvoton,nau8310: raw register values instead of units
- [Medium] ASoC: dt-bindings: nuvoton,nau8310: unconstrained nau8320 variant properties
--
commit 01b8fdb80988a85278fec4d71b7b4a47411ed6ee
Author: Neo Chang <YLCHANG2@nuvoton.com>
ASoC: dt-bindings: nuvoton,nau8310: Add NAU83G10
This commit adds the device tree binding schema in
Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml for the
Nuvoton mono class-d audio amplifier family.
> diff --git a/Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml b/Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml
> new file mode 100644
> index 0000000000000..5a5142a583ed1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml
> @@ -0,0 +1,200 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/sound/nuvoton,nau8310.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Nuvoton NAU83G10/20 Mono Class-D Amplifier with DSP
> +
> +description:
> + Mono Class-D Amplifier with DSP and I/V-sense.
> + This device supports I2C.
> +
> +maintainers:
> + - Neo Chang <YLCHANG2@nuvoton.com>
> +
> +properties:
> + compatible:
> + enum:
> + - nuvoton,nau8310
> + - nuvoton,nau8320
[Severity: Medium]
Should Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml restrict
the boost converter properties when compatible is set to nuvoton,nau8320?
In sound/soc/codecs/nau8310.c, nau8310_read_device_properties() only reads
nuvoton,boost-delay, nuvoton,boost-convert-enable,
nuvoton,boost-target-limit, and nuvoton,boost-target-margin for
nuvoton,nau8310 and ignores them on nuvoton,nau8320:
if (nau8310->silicon_id == NAU8310_REG_SI_REV_G10) {
ret = device_property_read_u32(dev, "nuvoton,boost-delay",
&nau8310->boost_delay);
...
}
Without an allOf if/then block setting those four properties to false for
nuvoton,nau8320, devicetree nodes for nuvoton,nau8320 with boost converter
properties will pass schema validation even though the driver ignores them.
Should the commit message also mention nuvoton,nau8320 alongside
nuvoton,nau8310?
[ ... ]
> + nuvoton,vref-impedance:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description:
> + VREF Impedance Selection.
> + enum:
> + - 0 # Open
> + - 1 # 25 kOhm
> + - 2 # 125 kOhm
> + - 3 # 2.5 kOhm
> + default: 0
> +
> + nuvoton,dac-vref:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description:
> + DAC Reference Voltage Setting.
> + enum:
> + - 0 # Voltage source directly from VDDA pin
> + - 1 # VDDA * 1.5/1.8V
> + - 2 # VDDA * 1.6/1.8V
> + - 3 # VDDA * 1.7/1.8V
> + default: 1
> +
> + nuvoton,sar-voltage:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description:
> + SAR Gain Tracking Selection.
> + enum:
> + - 0 # VDDA
> + - 1 # VDDA * 1.0
> + - 2 # VDDA * 1.1
> + - 3 # VDDA * 1.2
> + - 4 # VDDA * 1.3
> + - 5 # VDDA * 1.4
> + - 6 # VDDA * 1.54
> + - 7 # VDDA * 3.12
> + default: 0
> +
> + nuvoton,sar-compare-time:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description:
> + SAR compare time.
> + enum:
> + - 0 # 500 ns
> + - 1 # 1 us
> + - 2 # 2 us
> + - 3 # 4 us
> + default: 1
> +
> + nuvoton,sar-sampling-time:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description:
> + SAR sampling time.
> + enum:
> + - 0 # 2 us
> + - 1 # 4 us
> + - 2 # 8 us
> + - 3 # 16 us
> + default: 1
> +
> + nuvoton,clock-det-disable:
> + type: boolean
> + description:
> + Enable Clock Detection Module.
[Severity: Medium]
Is the description for nuvoton,clock-det-disable in
Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml inverted
relative to the property name and driver behavior?
In sound/soc/codecs/nau8310.c, nau8310_read_device_properties() and
nau8310_init_regs() disable clock detection when nuvoton,clock-det-disable
is present in the devicetree node:
nau8310->clock_detection =
!device_property_read_bool(dev, "nuvoton,clock-det-disable");
if (!nau8310->clock_detection)
regmap_update_bits(nau8310->regmap, NAU8310_REG_CLK_DET,
NAU8310_CLKPWRUP_DIS, NAU8310_CLKPWRUP_DIS);
Also, Documentation/devicetree/bindings/sound/nuvoton,nau8325.yaml already
defines nuvoton,clock-detection-disable for this feature. Could this binding
reuse nuvoton,clock-detection-disable and state that it disables the clock
detection module?
> +
> + nuvoton,clock-det-data:
> + type: boolean
> + description:
> + Clock detection non-zero samples configuration for power up signal.
> +
> + nuvoton,boost-delay:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description:
> + Boost Control Hold Timer in decrement (msec).
> + enum:
> + - 0 # 0 msec
> + - 1 # 1 msec
> + - 2 # 2 msec
> + - 3 # 4 msec
> + - 4 # 8 msec
> + - 5 # 16 msec
> + - 6 # 32 msec
> + - 7 # 64 msec
> + - 8 # 128 msec
> + default: 0
> +
> + nuvoton,boost-convert-enable:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description:
> + Boost Convert Enable.
> + enum:
> + - 0 # BOOST CONVERTER disabled
> + - 1 # BOOST CONVERTER enable gated by POWERUP signal
> + - 2 # BOOST CONVERTER disabled
> + - 3 # BOOST CONVERTER enabled
> + default: 0
> +
> + nuvoton,boost-target-limit:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description:
> + Boost Control Target Value Limit.
> + The value ranges from 0 to 63, representing 0V to 12V with a 0.19V step.
> + minimum: 0
> + maximum: 63
> + default: 63
> +
> + nuvoton,boost-target-margin:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description:
> + Boost Margin Value between Boost Target Voltage and peak output level.
> + The value ranges from 0 to 63, representing 0V to 12V with a 0.19V step.
> + minimum: 0
> + maximum: 63
> + default: 0
[Severity: Medium]
Could these vendor properties in
Documentation/devicetree/bindings/sound/nuvoton,nau8310.yaml use standard
unit suffixes and physical values instead of raw register encodings?
Specifically:
- nuvoton,vref-impedance and nuvoton,dac-vref use raw register indices 0..3,
whereas Documentation/devicetree/bindings/sound/nuvoton,nau8325.yaml
already defines nuvoton,vref-impedance-ohms and nuvoton,dac-vref-microvolt.
- nuvoton,sar-compare-time, nuvoton,sar-sampling-time, nuvoton,boost-delay,
nuvoton,boost-target-limit, and nuvoton,boost-target-margin expose raw
register indices or step counts (0..3, 0..8, 0..63) rather than physical
values with -ns, -us, -ms, and -microvolt suffixes.
- The schema default values for nuvoton,vref-impedance (0),
nuvoton,boost-delay (0), nuvoton,boost-target-limit (63), and
nuvoton,boost-target-margin (0) do not match the defaults applied in
nau8310_read_device_properties() (2, 0x8, 0x32, and 0x2).
- nuvoton,boost-convert-enable has a boolean-style -enable suffix for a
uint32 register field where both 0 and 2 mean disabled, and
nuvoton,sar-voltage exposes duplicate register values 0 and 1 for the same
1.0 gain setting.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022084037.259983-1-YLCHANG2@nuvoton.com?part=1
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v1 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier
2026-10-22 8:40 ` [PATCH v1 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier Neo Chang
@ 2026-10-22 10:09 ` sashiko-bot
2026-10-23 9:35 ` kernel test robot
1 sibling, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-10-22 10:09 UTC (permalink / raw)
To: Neo Chang; +Cc: linux-sound, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 15 potential issue(s) to consider:
- [High] ASoC: nau8310: type confusion and crash in component kcontrol callbacks
- [High] ASoC: nau8310: nau8310_dsp_init() desynchronizes DAPM switch state
- [High] ASoC: nau8310: missing cancel_work_sync() on probe error and component remove
- [High] ASoC: nau8310: lock inversion and bias_level TOCTOU in nau8310_dsp_snd_soc_dapm_put()
- [Medium] ASoC: nau8310: nau8310_dsp_set_kcs_setup() ignores nau8310->dsp_firmware
- [Medium] ASoC: nau8310: broken clock config for 64kHz/96kHz and 1x/2x multipliers
- [Medium] ASoC: nau8310: uninitialized *n1_sel read in nau8310_clksrc_choose()
- [Medium] ASoC: nau8310: nau8310_shutdown() clobbers active stream and mclk state
- [Medium] ASoC: nau8310: nau8310_dac_oversampl_enum exposes reserved register value
- [Medium] ASoC: nau8310: volatile registers in nau8310_reg_defaults synced on resume
- [Medium] ASoC: nau8310: nau8310_set_tdm_slot() bitwise-ORs encoded TX slot values
- [Medium] ASoC: nau8310: nau8310_reply_from_dsp() leaves reply buffer uninitialized
- [Medium] ASoC: nau8310: race between load_fw_work and DAPM clock events
- [Low] ASoC: nau8310: broken snd_soc_dapm_del_routes() error cleanup
- [Low] ASoC: nau8310: uninitialized kcs_setup_comp fields in nau8310_dsp_set_kcs_setup()
--
commit 781c4e28ad8239f56461795282d48ebf131f9a1b
Author: Neo Chang <YLCHANG2@nuvoton.com>
ASoC: codecs: nau8310: Add support for NAU83G10 amplifier
This patch adds an ASoC codec driver for the Nuvoton NAU83G10 mono boost
Class-D audio amplifier with integrated dsp and I/V-sense support.
> diff --git a/sound/soc/codecs/nau8310-dsp.c b/sound/soc/codecs/nau8310-dsp.c
> new file mode 100644
> index 0000000000000..7e8606beb2084
> --- /dev/null
> +++ b/sound/soc/codecs/nau8310-dsp.c
[ ... ]
> +static int nau8310_reply_from_dsp(struct snd_soc_component *cp,
> + const struct nau8310_cmd_info *cmd_info, int data_size,
> + void *data)
> +{
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> + struct device *dev = cp->dev;
> + int ret, frag_len, pad_len_exp, data_count = 0;
> +
> + ret = nau8310_dsp_replied(nau8310, &frag_len);
> + if (ret)
> + return ret;
> + if (!frag_len || !cmd_info->reply_data)
> + return 0;
> + if (!data || frag_len == 1)
> + return -EINVAL;
[Severity: Medium]
In sound/soc/codecs/nau8310-dsp.c, if nau8310_dsp_replied() returns a
reply preamble with frag_len == 0 for a command where cmd_info->reply_data
is true, nau8310_reply_from_dsp() returns 0 without writing anything to
the data buffer.
In nau8310_dsp_kcs_setup(), kcs_rst is declared uninitialized on the stack
and passed to nau8310_dsp_exec_command() for NAU8310_DSP_CMD_GET_KCS_RSLTS:
unsigned int kcs_rst;
...
ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_GET_KCS_RSLTS,
0, NAU8310_DSP_DATA_BYTE, NULL, NAU8310_DSP_DATA_BYTE, &kcs_rst);
if (ret)
return ret;
if (kcs_rst != NAU8310_DSP_KCS_RSLTS_SUCCESS)
return -EINVAL;
Could a zero-length reply cause nau8310_dsp_kcs_setup() to evaluate an
uninitialized kcs_rst value? Should nau8310_reply_from_dsp() return an
error when cmd_info->reply_data is set but frag_len == 0?
[ ... ]
> +static int nau8310_dsp_snd_soc_dapm_put(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
> + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
> + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
> + struct soc_mixer_control *mc =
> + (struct soc_mixer_control *)kcontrol->private_value;
> + unsigned int mask = (1 << fls(mc->max)) - 1;
> + unsigned int val;
> + int ret;
> +
> + if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) {
> + dev_warn_ratelimited(nau8310->dev, "changing path is not allowed during playback");
> + return -EBUSY;
> + }
> +
> + val = (ucontrol->value.integer.value[0] & mask);
> +
> + mutex_lock(&nau8310->lock);
> + if (val == NAU8310_DAC_SRC_DSP && !nau8310->load_fw_done) {
> + dev_warn_ratelimited(nau8310->dev, "Cannot enable DSP: Firmware not ready or disabled");
> + mutex_unlock(&nau8310->lock);
> + return -EBUSY;
> + }
> +
> + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
> +
> + if (val == NAU8310_DAC_SRC_BYPASS) {
> + /* Switch the clock source of DSP to MCLK. */
> + regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
> + NAU8310_DSP_SEL_OSC, 0);
> + }
> + mutex_unlock(&nau8310->lock);
> +
> + return ret;
> +}
[Severity: High]
Can nau8310_dsp_snd_soc_dapm_put() in sound/soc/codecs/nau8310-dsp.c cause
a lock inversion with nau8310_startup() and nau8310_load_fw_work()?
Here, nau8310_dsp_snd_soc_dapm_put() acquires nau8310->lock and then calls
snd_soc_dapm_put_volsw(), which acquires card->dapm_mutex and
card->pcm_mutex (via snd_soc_dpcm_runtime_update()).
However, when opening a pcm stream, soc_pcm_open() holds card->pcm_mutex
and calls nau8310_startup(), which calls flush_work(&nau8310->load_fw_work)
while nau8310_load_fw_work() acquires nau8310->lock:
Thread 1 (control write):
nau8310_dsp_snd_soc_dapm_put()
mutex_lock(&nau8310->lock)
snd_soc_dapm_put_volsw()
snd_soc_dpcm_runtime_update()
snd_soc_dpcm_mutex_lock(card) -> locks card->pcm_mutex
Thread 2 (pcm open):
soc_pcm_open() -> locks card->pcm_mutex
__soc_pcm_open()
snd_soc_dai_startup()
nau8310_startup()
flush_work(&nau8310->load_fw_work)
waits for nau8310_load_fw_work() -> mutex_lock(&nau8310->lock)
Also, snd_soc_dapm_get_bias_level(dapm) is checked before acquiring
nau8310->lock or card->dapm_mutex, so a stream can start while waiting for
nau8310->lock and bypass the playback check. And when val ==
NAU8310_DAC_SRC_BYPASS, clearing NAU8310_DSP_SEL_OSC does not check if
load_fw_work is still pending, which can switch the clock away from the
internal oscillator before nau8310_load_fw_work() runs.
> +
> +#define NAU8310_DSP_SOC_DAPM_SINGLE(xname, reg, shift, mask, invert) \
> + SOC_SINGLE_EXT(xname, reg, shift, mask, invert, \
> + snd_soc_dapm_get_volsw, nau8310_dsp_snd_soc_dapm_put)
> +
> +static const struct snd_kcontrol_new nau8310_dacdat_select_dsp =
> + NAU8310_DSP_SOC_DAPM_SINGLE("Switch", NAU8310_R1A_DSP_CORE_CTRL2,
> + NAU8310_DAC_SEL_DSP_SFT, 1, 0);
> +
> +static int nau8310_dsp_clock_event(struct snd_soc_dapm_widget *w,
> + struct snd_kcontrol *kcontrol, int event)
> +{
> + struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm);
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> + int ret = 0;
> +
> + if (SND_SOC_DAPM_EVENT_ON(event)) {
> + ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_CLK_RESTART,
> + 0, 0, NULL, 0, NULL);
> + if (ret)
> + dev_err(cp->dev, "Send DSP command %s fail (%d)",
> + dsp_cmd_table[NAU8310_DSP_CMD_CLK_RESTART], ret);
> +
> + /* Switch the clock source of DSP to MCLK. */
> + regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
> + NAU8310_DSP_SEL_OSC, 0);
> + } else if (SND_SOC_DAPM_EVENT_OFF(event)) {
> + ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_CLK_STOP,
> + 0, 0, NULL, 0, NULL);
> + if (ret)
> + dev_err(cp->dev, "Send DSP command %s fail (%d)",
> + dsp_cmd_table[NAU8310_DSP_CMD_CLK_STOP], ret);
> + }
> +
> + return ret;
> +}
[Severity: Medium]
Can nau8310_dsp_clock_event() in sound/soc/codecs/nau8310-dsp.c race with
nau8310_load_fw_work() during system resume?
In nau8310_resume(), nau8310_dsp_fw_load() switches the clock source to
the internal oscillator (NAU8310_DSP_SEL_OSC) and schedules
nau8310->load_fw_work asynchronously. Immediately afterwards,
soc_resume_deferred() powers up dapm widgets and invokes
nau8310_dsp_clock_event() while nau8310_load_fw_work() is still in
nau8310_dsp_init() (which sleeps in msleep(120) and then transfers
firmware over NAU8310_RF000_DSP_COMM under nau8310->lock).
Because nau8310_dsp_clock_event() and nau8310_clock_event() do not
synchronize with load_fw_work or acquire nau8310->lock, won't they
interleave commands on NAU8310_RF000_DSP_COMM and clear
NAU8310_DSP_SEL_OSC while firmware download is still in progress?
[ ... ]
> +static int nau8310_dsp_set_kcs_setup(struct snd_soc_component *cp)
> +{
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> + struct regmap *regmap = nau8310->regmap;
> + struct nau8310_kcs_setup kcs_setup_comp, *kcs_setup = &kcs_setup_comp;
> + struct device *dev = cp->dev;
> + const struct firmware *fw;
> + int ret, status = 0, buf_off, buf_len;
> +
> + dev_dbg(dev, "Send DSP command %s",
> + dsp_cmd_table[NAU8310_DSP_CMD_GET_FRAME_STATUS]);
> +
> + kcs_setup->get_len = kcs_setup->set_len = sizeof(status);
> + kcs_setup->get_data = (void *)&status;
> +
> + ret = nau8310_send_dsp_command(cp, NAU8310_DSP_CMD_GET_FRAME_STATUS, kcs_setup);
[Severity: Low]
In nau8310_dsp_set_kcs_setup() in sound/soc/codecs/nau8310-dsp.c,
kcs_setup_comp is declared on the stack without zero-initializing
set_kcs_offset and set_kcs_data, which are then read in
nau8310_send_dsp_command() and passed to nau8310_message_to_dsp(). Should
kcs_setup_comp be zero-initialized (or should this call
nau8310_dsp_exec_command())?
> + if (ret) {
> + dev_err(dev, "Send DSP command %s fail (%d)",
> + dsp_cmd_table[NAU8310_DSP_CMD_GET_FRAME_STATUS], ret);
> + goto err;
> + }
> +
> + if (!(status & NAU8310_DSP_ALGO_OK)) {
> + dev_err(dev, "Algorithm of DSP is not ready, status %x", status);
> + ret = -EIO;
> + goto err;
> + }
> +
> + dev_info(dev, "Algorithm of DSP is ready, status %x", status);
> +
> + dev_dbg(dev, "Request firmware and wait until finish.");
> + ret = request_firmware(&fw, NAU8310_DSP_FIRMWARE, dev);
[Severity: Medium]
In nau8310_read_device_properties() in sound/soc/codecs/nau8310.c, the
driver reads the "firmware-name" device property and allocates
nau8310->dsp_firmware. However, nau8310_dsp_set_kcs_setup() passes the
hardcoded NAU8310_DSP_FIRMWARE macro to request_firmware() here. Should
this pass nau8310->dsp_firmware instead?
> + if (ret) {
> + dev_err(dev, "Failed to load firmware (%d)", ret);
> + goto err;
> + }
[ ... ]
> +int nau8310_dsp_init(struct snd_soc_component *cp)
> +{
> + struct regmap *regmap = cp->regmap;
> +
> + nau8310_sw_reset_chip(regmap);
> +
> + /* wait for the power ready */
> + msleep(120);
> +
> + regmap_update_bits(regmap, NAU8310_R1A_DSP_CORE_CTRL2,
> + NAU8310_DSP_RUNSTALL | NAU8310_DAC_SEL_DSP_OUT, NAU8310_DAC_SEL_DSP_OUT);
> +
> + return nau8310_dsp_set_kcs_setup(cp);
> +}
[Severity: High]
Does setting NAU8310_DAC_SEL_DSP_OUT directly via regmap_update_bits() in
nau8310_dsp_init() desynchronize the "DAC data from DSP" dapm switch
state?
In nau8310_codec_probe(), nau8310_dsp_fw_load() schedules load_fw_work and
nau8310_dsp_setup_controls() immediately registers the "DAC data from DSP"
switch widget (which is backed by NAU8310_R1A_DSP_CORE_CTRL2 bit
NAU8310_DAC_SEL_DSP_SFT) while load_fw_work is sleeping in msleep(120).
When snd_soc_dapm_add_routes() runs, NAU8310_DAC_SEL_DSP_OUT is still 0,
so dapm initializes the switch path as disconnected. When
nau8310_dsp_init() subsequently sets NAU8310_DAC_SEL_DSP_OUT in the
register without going through dapm, nau8310_dsp_active() becomes true and
the hardware mux selects the dsp output, but dapm still treats "DAC data
from DSP" as disconnected and leaves the "DSP", "DSP Clock", and "Capture
Sense" widgets unpowered during playback.
> +
> +int nau8310_dsp_setup_controls(struct snd_soc_component *cp)
> +{
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> + struct snd_soc_dapm_context *dapm = nau8310->dapm;
> + int ret;
> +
> + ret = snd_soc_dapm_new_controls(dapm, nau8310_dsp_dapm_widgets,
> + ARRAY_SIZE(nau8310_dsp_dapm_widgets));
> + if (ret) {
> + dev_err(cp->dev, "Add DSP widget fail (%d)", ret);
> + goto err;
> + }
> +
> + ret = snd_soc_dapm_add_routes(dapm, nau8310_dsp_dapm_routes,
> + ARRAY_SIZE(nau8310_dsp_dapm_routes));
> + if (ret) {
> + dev_err(cp->dev, "Add DSP route fail (%d)", ret);
> + goto err;
> + }
> +
> + return 0;
> +err:
> + snd_soc_dapm_del_routes(dapm, nau8310_dsp_dapm_routes,
> + ARRAY_SIZE(nau8310_dsp_dapm_routes));
> + return ret;
> +}
[Severity: Low]
In nau8310_dsp_setup_controls() in sound/soc/codecs/nau8310-dsp.c, if
snd_soc_dapm_new_controls() fails, jumping to err calls
snd_soc_dapm_del_routes() on routes that were never added. In addition,
nau8310_dsp_dapm_routes contains a route with a control ("Switch"), which
snd_soc_dapm_del_route() does not support removing. Since component probe
failure already frees dapm widgets and routes via snd_soc_dapm_free(), can
the snd_soc_dapm_del_routes() call in the error path be dropped?
> diff --git a/sound/soc/codecs/nau8310.c b/sound/soc/codecs/nau8310.c
> new file mode 100644
> index 0000000000000..b4de2de337683
> --- /dev/null
> +++ b/sound/soc/codecs/nau8310.c
[ ... ]
> +static const struct reg_default nau8310_reg_defaults[] = {
> + { NAU8310_R02_I2C_ADDR, 0x0000 },
> + { NAU8310_R03_CLK_CTRL, 0x0000 },
> + { NAU8310_R04_ENA_CTRL, 0x0000 },
> + { NAU8310_R05_INTERRUPT_CTRL, 0x007f },
> + { NAU8310_R06_INT_CLR_STATUS, 0x0000 },
[ ... ]
> + { NAU8310_R9C_BIQ2_COE_9, 0x0000 },
> + { NAU8310_R9D_BIQ2_COE_10, 0x0000 },
> + { NAU8310_RF000_DSP_COMM, 0x0000 },
> +};
[Severity: Medium]
NAU8310_R06_INT_CLR_STATUS and NAU8310_RF000_DSP_COMM are marked as both
volatile and writeable, and are also included in nau8310_reg_defaults[] in
sound/soc/codecs/nau8310.c.
Because regcache_maple_populate() seeds the maple cache from
reg_defaults[], when nau8310_suspend() calls regcache_mark_dirty() and
nau8310_resume() calls regcache_sync(), regcache will write 0x0000 to
NAU8310_R06_INT_CLR_STATUS and 0x00000000 to the 32-bit mailbox register
NAU8310_RF000_DSP_COMM. Should volatile registers be omitted from
nau8310_reg_defaults[]?
[ ... ]
> +static int nau8310_clkdet_put(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
> + struct soc_mixer_control *mc =
> + (struct soc_mixer_control *)kcontrol->private_value;
> + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
> + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
[ ... ]
> +static int nau8310_clkdet_get(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
> + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
> + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
> +
> + ucontrol->value.integer.value[0] = nau8310->clock_detection;
> +
> + return 0;
> +}
> +
> +static int nau8310_biq_coeff_get(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
> + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
> + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
> + struct soc_bytes_ext *params = (void *)kcontrol->private_value;
> + int i, reg, reg_val;
> + u16 *val;
> +
> + val = (u16 *)ucontrol->value.bytes.data;
> + reg = NAU8310_R80_BIQ0_COE_1;
> + for (i = 0; i < params->max / sizeof(u16); i++) {
> + regmap_read(nau8310->regmap, reg + i, ®_val);
> + /* conversion of 16-bit integers between native CPU format
> + * and big endian format
> + */
> + reg_val = cpu_to_be16(reg_val);
> + *(val + i) = reg_val;
> + }
> +
> + return 0;
> +}
> +
> +static int nau8310_biq_coeff_put(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
> + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
> + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
[Severity: High]
Won't calling snd_soc_dapm_kcontrol_to_dapm(kcontrol) in
nau8310_clkdet_put(), nau8310_clkdet_get(), nau8310_biq_coeff_get(), and
nau8310_biq_coeff_put() crash when userspace accesses the "Clock Detection
Switch" or "BIQ Coefficients" controls?
Both controls are registered in nau8310_snd_controls[] via
soc_comp_dev_nau8310.controls, so kcontrol->private_data points to struct
snd_soc_component, not struct dapm_kcontrol_data. Calling
snd_soc_dapm_kcontrol_to_dapm() reinterprets the component pointer as
struct dapm_kcontrol_data and dereferences data->wlist->widgets[0]->dapm.
Should these four functions use snd_soc_kcontrol_component(kcontrol)
instead?
Also, in nau8310_biq_coeff_get(), if regmap_read() fails, reg_val is
uninitialized on the stack and gets copied into
ucontrol->value.bytes.data. Should the return value of regmap_read() be
checked?
[ ... ]
> +static const char * const nau8310_dac_oversampl[] = {
> + "64", "256", "128", "", "32" };
> +
> +static const struct soc_enum nau8310_dac_oversampl_enum =
> + SOC_ENUM_SINGLE(NAU8310_R29_DAC_CTRL1, NAU8310_DAC_OVERSAMPLE_SFT,
> + ARRAY_SIZE(nau8310_dac_oversampl), nau8310_dac_oversampl);
[Severity: Medium]
In sound/soc/codecs/nau8310.c, nau8310_dac_oversampl[] includes an empty
string "" at index 3 with SOC_ENUM_SINGLE, exposing reserved register
value 3 to userspace. If userspace selects index 3, osr_dac_sel[3] has osr
== 0 and nau8310_osr_apply() fails with -EINVAL during hw_params. Should
this use SOC_VALUE_ENUM_SINGLE to skip the reserved value?
[ ... ]
> +static int nau8310_srate_clk_apply(struct nau8310 *nau8310,
> + const struct nau8310_srate_attr *srate_table,
> + int n1_sel, int mclk_mult_sel, int n2_sel, int dsp_mult_sel)
> +{
> + struct regmap *regmap = nau8310->regmap;
> + int dsp_clk, mult_sel;
> + unsigned int ctrl_val = 0;
> +
> + static const unsigned int mclkx_val[] = {
> + /* multiplier 4x, i.e. 2^2 */
> + [2] = NAU8310_MCLK4XEN,
> + /* multiplier 8x, i.e. 2^3 */
> + [3] = NAU8310_MCLK8XEN | NAU8310_MCLK4XEN,
> + /* multiplier 16x, i.e. 2^4 */
> + [4] = NAU8310_MCLK16XEN | NAU8310_MCLK8XEN | NAU8310_MCLK4XEN,
> + };
> +
> + if (!srate_table || srate_table->adc_div < 0 ||
> + dsp_mult_sel < 0 || dsp_mult_sel >= ARRAY_SIZE(dsp_src_mult) ||
> + n2_sel < 0 || n2_sel >= ARRAY_SIZE(mclk_n2_div) ||
> + n1_sel < 0 || n1_sel >= ARRAY_SIZE(mclk_n1_div)) {
> + dev_err(nau8310->dev, "The fs %d isn't support in DSP.",
> + nau8310->fs);
> + return -EINVAL;
> + }
[ ... ]
> + regmap_update_bits(regmap, NAU8310_R04_ENA_CTRL,
> + NAU8310_CLK_DSP_SRC_MASK | NAU8310_CLK_MUL_SRC_MASK |
> + NAU8310_MCLK_SEL_MASK, ctrl_val);
> +
> + if (mult_sel < 2 || mult_sel > 4)
> + return -EINVAL;
> +
> + regmap_update_bits(regmap, NAU8310_R68_ANALOG_CONTROL_7,
> + NAU8310_MCLKX_MASK, mclkx_val[mult_sel]);
> +
> + return 0;
> +}
> +
> +static int nau8310_calc_dsp_clk(int mclk, int fs, int mult_param, int n1_param)
> +{
> + int dsp_clk = (mclk << mult_param) / n1_param;
> +
> + if (dsp_clk >= (fs * DSP_CLK_MIN_MULTI_VALUE) && dsp_clk <= DSP_CLK_MAX)
> + return dsp_clk;
> +
> + return 0;
> +}
[Severity: Medium]
Can 64 kHz or 96 kHz ever succeed in nau8310_clock_config() when the dsp
is bypassed, or can 1x/2x clock multipliers ever be applied?
In nau8310_calc_dsp_clk(), dsp_clk must be between fs * 2000 and
102400000. For fs = 64000 (64000 * 2000 = 128000000) and fs = 96000
(96000 * 2000 = 192000000), fs * DSP_CLK_MIN_MULTI_VALUE exceeds
DSP_CLK_MAX, so nau8310_clkdsp_choose() always fails even when the dsp is
bypassed. In addition, target_srate_table[] sets adc_div = -1 for 96000,
which nau8310_srate_clk_apply() rejects unconditionally, and
NAU8310_RATES advertises SNDRV_PCM_RATE_8000_192000 including rates
(11025, 22050, 88200, 176400, 192000) not present in
target_srate_table[].
Furthermore, in nau8310_srate_clk_apply(), mult_sel is an index into
mclk_src_mult[] / dsp_src_mult[] where 0 and 1 represent 1x and 2x
multipliers, but the check:
if (mult_sel < 2 || mult_sel > 4)
return -EINVAL;
rejects mult_sel == 0 and mult_sel == 1 with -EINVAL after already
updating NAU8310_R03_CLK_CTRL, NAU8310_R40_CLK_DET_CTRL, and
NAU8310_R04_ENA_CTRL. Should mclkx_val[] allow 0 for 1x and 2x multipliers
instead of returning -EINVAL?
[ ... ]
> +static int nau8310_clksrc_choose(struct nau8310 *nau8310,
> + const struct nau8310_srate_attr **srate_table,
> + int *n1_sel, int *mult_sel, int *n2_sel, int *dsp_mult_sel)
> +{
> + int i, j, mclk, mclk_max, ratio, ratio_sel, n2_max, ret;
[ ... ]
> + /* Get MCLK_SRC through 1/N, Multiplier, and then 1/N2. */
> + mclk_max = 0;
> + for (i = 0; i < ARRAY_SIZE(mclk_n1_div); i++) {
> + for (j = 0; j < ARRAY_SIZE(mclk_src_mult); j++) {
> + mclk = nau8310->mclk << mclk_src_mult[j].param;
> + mclk = mclk / mclk_n1_div[i].param;
> + ratio = nau8310_clksrc_n2(nau8310, *srate_table, mclk, n2_sel);
> + if (ratio != NAU8310_MCLK_FS_RATIO_NUM &&
> + (mclk_max < mclk || i > *n1_sel)) {
[Severity: Medium]
In nau8310_clock_config() in sound/soc/codecs/nau8310.c, n1_sel is
declared uninitialized on the stack and passed to nau8310_clksrc_choose().
When the initial direct mclk check does not match, *n1_sel is never
initialized before entering this loop, so evaluating i > *n1_sel when
mclk_max >= mclk reads an uninitialized stack value. Should *n1_sel be
initialized (e.g. to -1) before the loop?
[ ... ]
> +static int nau8310_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
> + unsigned int rx_mask, int slots, int slot_width)
> +{
[ ... ]
> + for (i = 0; i < NAU8310_TDM_MAX_SLOTS; i++)
> + if (tx_mask & BIT(i)) {
> + ctrl_val |= nau8310_tx_slot_map[i];
> + dev_dbg(dev, "NAU8310_ADC_%c_SEL_SLOT%d",
> + (i % 2 == 0) ? 'V' : 'I', i);
> + }
[Severity: Medium]
In nau8310_set_tdm_slot() in sound/soc/codecs/nau8310.c,
nau8310_tx_slot_map[] contains binary-encoded slot values (0x4, 0x5, 0x6,
0x7 for even V slots and 0x4<<3, 0x5<<3, 0x6<<3, 0x7<<3 for odd I slots),
not one-hot bitmasks. If tx_mask has more than one even bit or more than
one odd bit set (for example, BIT(2) | BIT(4)), bitwise-ORing them
(0x5 | 0x6 = 0x7) corrupts the slot selection to slot 6. Should this
validate or select at most one even slot and one odd slot?
[ ... ]
> +static int nau8310_switch_internal_osc(struct snd_soc_component *cp)
> +{
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
> + int ret;
> +
> + /* For internal Ring OSC, the default fs apply to 48kHz */
> + nau8310->fs = NAU8310_DEFAULT_SAMPLE_RATE;
> + ret = nau8310_set_sysclk(cp, 0, 0,
> + nau8310->fs * NAU8310_SYSCLK_MULT, SND_SOC_CLOCK_IN);
> + if (ret)
> + return ret;
> +
> + ret = nau8310_clock_config(nau8310);
> + if (ret)
> + return ret;
> +
> + /* Switch clock source to OSC before MCLK off if DSP is enabled. */
> + regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
> + NAU8310_DSP_OSC_EN | NAU8310_DSP_SEL_OSC,
> + NAU8310_DSP_OSC_EN | NAU8310_DSP_SEL_OSC);
> +
> + return 0;
> +}
[ ... ]
> +static void nau8310_shutdown(struct snd_pcm_substream *substream,
> + struct snd_soc_dai *dai)
> +{
> + struct snd_soc_component *cp = dai->component;
> + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(cp);
> + int ret;
> +
> + if (!nau8310_dsp_active(cp))
> + return;
> +
> + snd_soc_dapm_disable_pin(dapm, "Sense");
> +
> + ret = nau8310_switch_internal_osc(cp);
> + if (ret)
> + return;
> +}
[Severity: Medium]
In sound/soc/codecs/nau8310.c, what happens in nau8310_shutdown() when
both playback and capture substreams are active and one of them closes?
Because nau8310_shutdown() does not check snd_soc_component_active(cp) (or
dai->active), closing one substream while the other is still running will
disable the "Sense" pin and call nau8310_switch_internal_osc(cp),
switching the clocking to the internal oscillator mid-stream.
Also, nau8310_switch_internal_osc() calls nau8310_set_sysclk() with
48000 * 256 (12.288 MHz), overwriting nau8310->mclk during probe, resume,
and shutdown. If a machine driver sets the sysclk frequency once at
initialization rather than on every hw_params, won't nau8310->mclk be
clobbered?
[ ... ]
> +static int nau8310_codec_probe(struct snd_soc_component *component)
> +{
> + struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
> + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
> + int ret = 0;
> +
> + nau8310->dapm = dapm;
> +
> + /* Loading DSP firmware */
> + ret = nau8310_dsp_fw_load(nau8310);
> + if (ret) {
> + dev_err(nau8310->dev, "Loading DSP firmware failed(%d)", ret);
> + goto err;
> + }
> +
> + ret = nau8310_dsp_setup_controls(component);
> + if (ret) {
> + dev_err(nau8310->dev, "DSP setup controls failed(%d)", ret);
> + goto err;
> + }
> +
> +err:
> + return ret;
> +}
[ ... ]
> +static const struct snd_soc_component_driver soc_comp_dev_nau8310 = {
> + .probe = nau8310_codec_probe,
> + .set_sysclk = nau8310_set_sysclk,
> + .suspend = nau8310_suspend,
> + .resume = nau8310_resume,
[Severity: High]
In nau8310_codec_probe() in sound/soc/codecs/nau8310.c,
nau8310_dsp_fw_load() schedules nau8310->load_fw_work. If
nau8310_dsp_setup_controls() fails (or if card binding later fails or the
component is unbound from the card), load_fw_work is not cancelled because
the error path in nau8310_codec_probe() does not call
cancel_work_sync(&nau8310->load_fw_work) and soc_comp_dev_nau8310 has no
.remove callback.
Could nau8310_load_fw_work() continue running after component teardown or
race with a subsequent component re-probe?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022084037.259983-1-YLCHANG2@nuvoton.com?part=2
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v1 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier
2026-10-22 8:40 ` [PATCH v1 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier Neo Chang
2026-10-22 10:09 ` sashiko-bot
@ 2026-10-23 9:35 ` kernel test robot
1 sibling, 0 replies; 6+ messages in thread
From: kernel test robot @ 2026-10-23 9:35 UTC (permalink / raw)
To: Neo Chang, broonie
Cc: oe-kbuild-all, lgirdwood, perex, robh, krzk+dt, linux-sound,
devicetree, alsa-devel, neo.chang70, kchsu0, sjlin0, Neo Chang
Hi Neo,
kernel test robot noticed the following build warnings:
[auto build test WARNING on broonie-sound/for-next]
[also build test WARNING on next-20261022]
[cannot apply to linus/master v7.3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Neo-Chang/ASoC-dt-bindings-nuvoton-nau8310-Add-NAU83G10/20261022-164036
base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
patch link: https://lore.kernel.org/r/20261022084037.259983-3-YLCHANG2%40nuvoton.com
patch subject: [PATCH v1 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier
config: powerpc-randconfig-1000-20261023 (https://download.01.org/0day-ci/archive/20261023/202610231724.YNiXxf9w-lkp@intel.com/config)
compiler: powerpc-linux-gcc (GCC) 15.3.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261023/202610231724.YNiXxf9w-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202610231724.YNiXxf9w-lkp@intel.com/
All warnings (new ones prefixed by >>):
In function 'nau8310_send_dsp_command',
inlined from 'nau8310_dsp_set_kcs_setup' at sound/soc/codecs/nau8310-dsp.c:603:8:
>> sound/soc/codecs/nau8310-dsp.c:419:15: warning: 'kcs_setup_comp.set_kcs_offset' is used uninitialized [-Wuninitialized]
419 | ret = nau8310_message_to_dsp(cp, cmd_info, frag_len,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
420 | kcs_setup->set_kcs_offset, kcs_setup->set_len,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
421 | kcs_setup->set_kcs_data);
| ~~~~~~~~~~~~~~~~~~~~~~~~
sound/soc/codecs/nau8310-dsp.c: In function 'nau8310_dsp_set_kcs_setup':
sound/soc/codecs/nau8310-dsp.c:592:34: note: 'kcs_setup_comp' declared here
592 | struct nau8310_kcs_setup kcs_setup_comp, *kcs_setup = &kcs_setup_comp;
| ^~~~~~~~~~~~~~
In function 'nau8310_send_dsp_command',
inlined from 'nau8310_dsp_set_kcs_setup' at sound/soc/codecs/nau8310-dsp.c:603:8:
>> sound/soc/codecs/nau8310-dsp.c:419:15: warning: 'kcs_setup_comp.set_kcs_data' is used uninitialized [-Wuninitialized]
419 | ret = nau8310_message_to_dsp(cp, cmd_info, frag_len,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
420 | kcs_setup->set_kcs_offset, kcs_setup->set_len,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
421 | kcs_setup->set_kcs_data);
| ~~~~~~~~~~~~~~~~~~~~~~~~
sound/soc/codecs/nau8310-dsp.c: In function 'nau8310_dsp_set_kcs_setup':
sound/soc/codecs/nau8310-dsp.c:592:34: note: 'kcs_setup_comp' declared here
592 | struct nau8310_kcs_setup kcs_setup_comp, *kcs_setup = &kcs_setup_comp;
| ^~~~~~~~~~~~~~
vim +419 sound/soc/codecs/nau8310-dsp.c
370
371 /*
372 * nau8310_send_dsp_command - Send command to DSP
373 *
374 * @cp: component to register
375 * @cmd_id: DSP supported command ID
376 * @kcs_setup: KCS setup structure
377 *
378 * The communication protocol is a Master-Slave type protocol
379 * where the host processor is the master and DSP is the slave.
380 * The Master initiates the communication and can either write or
381 * read back from the slave.
382 * Transactions from the Master are called "Messages",
383 * and read-back data from the Slave is called a "Reply".
384 *
385 * The function sends command to DSP according to the command ID.
386 * These commands include getting the information of DSP,
387 * getting or setting KCS configuration, or making DSP control.
388 */
389 static int nau8310_send_dsp_command(struct snd_soc_component *cp, int cmd_id,
390 struct nau8310_kcs_setup *kcs_setup)
391 {
392 const struct nau8310_cmd_info *cmd_info;
393 int ret = 0, frag_len = 0;
394
395 cmd_info = &nau8310_dsp_cmd_table[cmd_id];
396 if ((cmd_info->msg_param && !kcs_setup->set_len) ||
397 (cmd_info->setup_data && !kcs_setup->set_kcs_data) ||
398 (cmd_info->reply_data && !kcs_setup->get_data))
399 return -EFAULT;
400
401 /* Read up to 1kB data because the LEN field to request data is 10-bits
402 * long; and not beyond 3kB offset.
403 */
404 if (cmd_id == NAU8310_DSP_CMD_GET_KCS_SETUP &&
405 (kcs_setup->set_len > NAU8310_DSP_KCS_DAT_LEN_MAX ||
406 kcs_setup->set_kcs_offset > NAU8310_DSP_KCS_OFFSET_MAX))
407 return -ERANGE;
408
409 /* one fragment for offset and size parameters
410 * one fragment for a postamble fragment
411 */
412 if (cmd_info->msg_param)
413 frag_len += 2;
414
415 /* fragments for KCS setup writen */
416 if (cmd_info->setup_data)
417 frag_len += DIV_ROUND_UP(kcs_setup->set_len, NAU8310_DSP_DATA_BYTE);
418
> 419 ret = nau8310_message_to_dsp(cp, cmd_info, frag_len,
420 kcs_setup->set_kcs_offset, kcs_setup->set_len,
421 kcs_setup->set_kcs_data);
422 if (ret)
423 return ret;
424
425 return nau8310_reply_from_dsp(cp, cmd_info, kcs_setup->get_len,
426 kcs_setup->get_data);
427 }
428
429 static inline int nau8310_dsp_exec_command(struct snd_soc_component *cp, int cmd_id,
430 int offset, int set_len, void *set_data, int get_len, void *get_data)
431 {
432 struct nau8310_kcs_setup kcs_setup = {
433 .set_kcs_offset = offset,
434 .set_len = set_len,
435 .set_kcs_data = set_data,
436 .get_len = get_len,
437 .get_data = get_data,
438 };
439
440 return nau8310_send_dsp_command(cp, cmd_id, &kcs_setup);
441 }
442
443 /*
444 * nau8310_dsp_kcs_setup - Send KCS setup command to DSP
445 *
446 * @cp: component to register
447 * @offset: address offset relative to KCS start
448 * @size: size of data writen to KCS
449 * @data: data writen to KCS setup
450 *
451 * The function sends KCS setup command to DSP for
452 * setting KCS configuration. The maximum size that you can transfer into
453 * the DSP is 96 bytes. Therefore, the driver has to split the data into
454 * 96 bytes chucks, if the setup configuration over the threshold.
455 */
456 static int nau8310_dsp_kcs_setup(struct snd_soc_component *cp, int offset, int size,
457 const void *data)
458 {
459 u8 *data_buf = (u8 *)data;
460 unsigned int kcs_rst;
461 int retries = 0, ret, data_len, data_rem, addr_offset;
462
463 /* Limit full load of KCS_SETUP data and not beyond 3kB offset. */
464 if (!data || size > NAU8310_DSP_KCS_DAT_LEN_MAX ||
465 offset > NAU8310_DSP_KCS_OFFSET_MAX)
466 return -EINVAL;
467
468 /* sending fragments for KCS setup */
469 addr_offset = offset;
470 data_rem = size;
471
472 while (data_rem) {
473 data_len = min(data_rem, NAU8310_DSP_KCS_TX_MAX);
474
475 ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_SET_KCS_SETUP,
476 addr_offset, data_len, (void *)data_buf, 0, &kcs_rst);
477 if (ret) {
478 if (retries++ < NAU8310_DSP_RETRY_MAX)
479 continue;
480 return ret;
481 }
482
483 data_buf += (u8)data_len;
484 addr_offset += data_len;
485 data_rem -= data_len;
486
487 /* checking KCS result */
488 ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_GET_KCS_RSLTS,
489 0, NAU8310_DSP_DATA_BYTE, NULL, NAU8310_DSP_DATA_BYTE, &kcs_rst);
490 if (ret)
491 return ret;
492 if (kcs_rst != NAU8310_DSP_KCS_RSLTS_SUCCESS)
493 return -EINVAL;
494 }
495
496 return 0;
497 }
498
499 static int nau8310_dsp_snd_soc_dapm_put(struct snd_kcontrol *kcontrol,
500 struct snd_ctl_elem_value *ucontrol)
501 {
502 struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol);
503 struct snd_soc_component *component = snd_soc_dapm_to_component(dapm);
504 struct nau8310 *nau8310 = snd_soc_component_get_drvdata(component);
505 struct soc_mixer_control *mc =
506 (struct soc_mixer_control *)kcontrol->private_value;
507 unsigned int mask = (1 << fls(mc->max)) - 1;
508 unsigned int val;
509 int ret;
510
511 if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) {
512 dev_warn_ratelimited(nau8310->dev, "changing path is not allowed during playback");
513 return -EBUSY;
514 }
515
516 val = (ucontrol->value.integer.value[0] & mask);
517
518 mutex_lock(&nau8310->lock);
519 if (val == NAU8310_DAC_SRC_DSP && !nau8310->load_fw_done) {
520 dev_warn_ratelimited(nau8310->dev, "Cannot enable DSP: Firmware not ready or disabled");
521 mutex_unlock(&nau8310->lock);
522 return -EBUSY;
523 }
524
525 ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
526
527 if (val == NAU8310_DAC_SRC_BYPASS) {
528 /* Switch the clock source of DSP to MCLK. */
529 regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
530 NAU8310_DSP_SEL_OSC, 0);
531 }
532 mutex_unlock(&nau8310->lock);
533
534 return ret;
535 }
536
537 #define NAU8310_DSP_SOC_DAPM_SINGLE(xname, reg, shift, mask, invert) \
538 SOC_SINGLE_EXT(xname, reg, shift, mask, invert, \
539 snd_soc_dapm_get_volsw, nau8310_dsp_snd_soc_dapm_put)
540
541 static const struct snd_kcontrol_new nau8310_dacdat_select_dsp =
542 NAU8310_DSP_SOC_DAPM_SINGLE("Switch", NAU8310_R1A_DSP_CORE_CTRL2,
543 NAU8310_DAC_SEL_DSP_SFT, 1, 0);
544
545 static int nau8310_dsp_clock_event(struct snd_soc_dapm_widget *w,
546 struct snd_kcontrol *kcontrol, int event)
547 {
548 struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm);
549 struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
550 int ret = 0;
551
552 if (SND_SOC_DAPM_EVENT_ON(event)) {
553 ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_CLK_RESTART,
554 0, 0, NULL, 0, NULL);
555 if (ret)
556 dev_err(cp->dev, "Send DSP command %s fail (%d)",
557 dsp_cmd_table[NAU8310_DSP_CMD_CLK_RESTART], ret);
558
559 /* Switch the clock source of DSP to MCLK. */
560 regmap_update_bits(nau8310->regmap, NAU8310_R04_ENA_CTRL,
561 NAU8310_DSP_SEL_OSC, 0);
562 } else if (SND_SOC_DAPM_EVENT_OFF(event)) {
563 ret = nau8310_dsp_exec_command(cp, NAU8310_DSP_CMD_CLK_STOP,
564 0, 0, NULL, 0, NULL);
565 if (ret)
566 dev_err(cp->dev, "Send DSP command %s fail (%d)",
567 dsp_cmd_table[NAU8310_DSP_CMD_CLK_STOP], ret);
568 }
569
570 return ret;
571 }
572
573 static const struct snd_soc_dapm_widget nau8310_dsp_dapm_widgets[] = {
574 SND_SOC_DAPM_PGA("DSP", SND_SOC_NOPM, 0, 0, NULL, 0),
575 SND_SOC_DAPM_SWITCH("DAC data from DSP", SND_SOC_NOPM,
576 0, 0, &nau8310_dacdat_select_dsp),
577 SND_SOC_DAPM_SUPPLY("DSP Clock", SND_SOC_NOPM, 0, 0,
578 nau8310_dsp_clock_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
579 };
580
581 static const struct snd_soc_dapm_route nau8310_dsp_dapm_routes[] = {
582 { "DSP", NULL, "Capture Sense" },
583 { "DAC data from DSP", "Switch", "DSP" },
584 { "DAC", NULL, "DAC data from DSP" },
585 { "DSP", NULL, "DSP Clock" },
586 };
587
588 static int nau8310_dsp_set_kcs_setup(struct snd_soc_component *cp)
589 {
590 struct nau8310 *nau8310 = snd_soc_component_get_drvdata(cp);
591 struct regmap *regmap = nau8310->regmap;
> 592 struct nau8310_kcs_setup kcs_setup_comp, *kcs_setup = &kcs_setup_comp;
593 struct device *dev = cp->dev;
594 const struct firmware *fw;
595 int ret, status = 0, buf_off, buf_len;
596
597 dev_dbg(dev, "Send DSP command %s",
598 dsp_cmd_table[NAU8310_DSP_CMD_GET_FRAME_STATUS]);
599
600 kcs_setup->get_len = kcs_setup->set_len = sizeof(status);
601 kcs_setup->get_data = (void *)&status;
602
> 603 ret = nau8310_send_dsp_command(cp, NAU8310_DSP_CMD_GET_FRAME_STATUS, kcs_setup);
604 if (ret) {
605 dev_err(dev, "Send DSP command %s fail (%d)",
606 dsp_cmd_table[NAU8310_DSP_CMD_GET_FRAME_STATUS], ret);
607 goto err;
608 }
609
610 if (!(status & NAU8310_DSP_ALGO_OK)) {
611 dev_err(dev, "Algorithm of DSP is not ready, status %x", status);
612 ret = -EIO;
613 goto err;
614 }
615
616 dev_info(dev, "Algorithm of DSP is ready, status %x", status);
617
618 dev_dbg(dev, "Request firmware and wait until finish.");
619 ret = request_firmware(&fw, NAU8310_DSP_FIRMWARE, dev);
620 if (ret) {
621 dev_err(dev, "Failed to load firmware (%d)", ret);
622 goto err;
623 }
624
625 buf_off = 0;
626 buf_len = nau8310->kcs_setup_size = fw->size;
627 dev_dbg(dev, "Send DSP command %s (OFF %d, LEN %d)",
628 dsp_cmd_table[NAU8310_DSP_CMD_SET_KCS_SETUP], buf_off, buf_len);
629 ret = nau8310_dsp_kcs_setup(cp, buf_off, buf_len, fw->data);
630 if (ret) {
631 dev_err(dev, "Send DSP command %s fail (%d)",
632 dsp_cmd_table[NAU8310_DSP_CMD_SET_KCS_SETUP], ret);
633 goto err_loaded;
634 }
635
636 release_firmware(fw);
637 nau8310->load_fw_done = true;
638
639 return 0;
640
641 err_loaded:
642 regmap_update_bits(regmap, NAU8310_R13_MUTE_CTRL, NAU8310_SOFT_MUTE, 0);
643 if (fw)
644 release_firmware(fw);
645 err:
646 nau8310->load_fw_done = false;
647 return ret;
648 }
649
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-23 9:36 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-22 8:40 [PATCH v1 0/2] ASoC: codecs: nau8310: Add NAU83G10 amplifier Neo Chang
2026-10-22 8:40 ` [PATCH v1 1/2] ASoC: dt-bindings: nuvoton,nau8310: Add NAU83G10 Neo Chang
2026-10-22 8:51 ` sashiko-bot
2026-10-22 8:40 ` [PATCH v1 2/2] ASoC: codecs: nau8310: Add support for NAU83G10 amplifier Neo Chang
2026-10-22 10:09 ` sashiko-bot
2026-10-23 9:35 ` kernel test robot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox