diff mbox series

ASoC: tlv320aic31xx: Add support for loading filter coefficients

Message ID 20240906-tlv320-filter-v1-1-6955f53ff435@bootlin.com
State Accepted
Commit 77b696f489d2fd83bbcffeed363baac8f2f6ed4b
Headers show
Series ASoC: tlv320aic31xx: Add support for loading filter coefficients | expand

Commit Message

Romain Gantois Sept. 6, 2024, 12:20 p.m. UTC
The TLV320DAC3100 Audio DAC has 25 built-in digital audio processing blocks
(PRBs). Each of these PRBs has a static filter structure with programmable
coefficients. Once a PRB is selected for use by the DAC, its filter
coefficients can be configured via a dedicated set of registers.

Define a new optional firmware which can be loaded by the TLV320DAC driver.
This firmware describes a full set of filter coefficients for all blocks
used by the various PRBs.

The firmware's binary format is heavily inspired by the one used in the
peb2466 driver. It includes a version marker to allow for potential
evolutions of the format.

Note that adaptive filtering is not supported i.e. filter coefficients are
loaded once before power-on and then cannot be changed while the DAC is
powered. This is why only page A coefficients are modified. Page B
coefficients are only used for adaptive filtering.

Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
---
 sound/soc/codecs/tlv320aic31xx.c | 100 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 100 insertions(+)


---
base-commit: 8400291e289ee6b2bf9779ff1c83a291501f017b
change-id: 20240806-tlv320-filter-189edd66496c

Best regards,

Comments

Mark Brown Sept. 9, 2024, 5:18 p.m. UTC | #1
On Fri, 06 Sep 2024 14:20:58 +0200, Romain Gantois wrote:
> The TLV320DAC3100 Audio DAC has 25 built-in digital audio processing blocks
> (PRBs). Each of these PRBs has a static filter structure with programmable
> coefficients. Once a PRB is selected for use by the DAC, its filter
> coefficients can be configured via a dedicated set of registers.
> 
> Define a new optional firmware which can be loaded by the TLV320DAC driver.
> This firmware describes a full set of filter coefficients for all blocks
> used by the various PRBs.
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next

Thanks!

[1/1] ASoC: tlv320aic31xx: Add support for loading filter coefficients
      commit: 77b696f489d2fd83bbcffeed363baac8f2f6ed4b

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark
diff mbox series

Patch

diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c
index 2f94cfda0e33..7e624c4b77b6 100644
--- a/sound/soc/codecs/tlv320aic31xx.c
+++ b/sound/soc/codecs/tlv320aic31xx.c
@@ -12,6 +12,7 @@ 
  * and mono/stereo Class-D speaker driver.
  */
 
+#include <asm/unaligned.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
 #include <linux/init.h>
@@ -22,6 +23,7 @@ 
 #include <linux/gpio/consumer.h>
 #include <linux/regulator/consumer.h>
 #include <linux/acpi.h>
+#include <linux/firmware.h>
 #include <linux/of.h>
 #include <linux/slab.h>
 #include <sound/core.h>
@@ -1638,6 +1640,98 @@  static const struct i2c_device_id aic31xx_i2c_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, aic31xx_i2c_id);
 
+static int tlv320dac3100_fw_load(struct aic31xx_priv *aic31xx,
+				 const u8 *data, size_t size)
+{
+	int ret, reg;
+	u16 val16;
+
+	/*
+	 * Coefficients firmware binary structure. Multi-byte values are big-endian.
+	 *
+	 * @0, 16bits: Magic (0xB30C)
+	 * @2, 16bits: Version (0x0100 for version 1.0)
+	 * @4, 8bits: DAC Processing Block Selection
+	 * @5, 62 16-bit values: Page 8 buffer A DAC programmable filter coefficients
+	 * @129, 12 16-bit values: Page 9 Buffer A DAC programmable filter coefficients
+	 *
+	 * Filter coefficients are interpreted as two's complement values
+	 * ranging from -32 768 to 32 767. For more details on filter coefficients,
+	 * please refer to the TLV320DAC3100 datasheet, tables 6-120 and 6-123.
+	 */
+
+	if (size != 153) {
+		dev_err(aic31xx->dev, "firmware size is %zu, expected 153 bytes\n", size);
+		return -EINVAL;
+	}
+
+	/* Check magic */
+	val16 = get_unaligned_be16(data);
+	if (val16 != 0xb30c) {
+		dev_err(aic31xx->dev, "fw magic is 0x%04x expected 0xb30c\n", val16);
+		return -EINVAL;
+	}
+	data += 2;
+
+	/* Check version */
+	val16 = get_unaligned_be16(data);
+	if (val16 != 0x0100) {
+		dev_err(aic31xx->dev, "invalid firmware version 0x%04x! expected 1", val16);
+		return -EINVAL;
+	}
+	data += 2;
+
+	ret = regmap_write(aic31xx->regmap, AIC31XX_DACPRB, *data);
+	if (ret) {
+		dev_err(aic31xx->dev, "failed to write PRB index: err %d\n", ret);
+		return ret;
+	}
+	data += 1;
+
+	/* Page 8 Buffer A coefficients */
+	for (reg = 2; reg < 126; reg++) {
+		ret = regmap_write(aic31xx->regmap, AIC31XX_REG(8, reg), *data);
+		if (ret) {
+			dev_err(aic31xx->dev,
+				"failed to write page 8 filter coefficient %d: err %d\n", reg, ret);
+			return ret;
+		}
+		data++;
+	}
+
+	/* Page 9 Buffer A coefficients */
+	for (reg = 2; reg < 26; reg++) {
+		ret = regmap_write(aic31xx->regmap, AIC31XX_REG(9, reg), *data);
+		if (ret) {
+			dev_err(aic31xx->dev,
+				"failed to write page 9 filter coefficient %d: err %d\n", reg, ret);
+			return ret;
+		}
+		data++;
+	}
+
+	dev_info(aic31xx->dev, "done loading DAC filter coefficients\n");
+
+	return ret;
+}
+
+static int tlv320dac3100_load_coeffs(struct aic31xx_priv *aic31xx,
+				     const char *fw_name)
+{
+	const struct firmware *fw;
+	int ret;
+
+	ret = request_firmware(&fw, fw_name, aic31xx->dev);
+	if (ret)
+		return ret;
+
+	ret = tlv320dac3100_fw_load(aic31xx, fw->data, fw->size);
+
+	release_firmware(fw);
+
+	return ret;
+}
+
 static int aic31xx_i2c_probe(struct i2c_client *i2c)
 {
 	struct aic31xx_priv *aic31xx;
@@ -1727,6 +1821,12 @@  static int aic31xx_i2c_probe(struct i2c_client *i2c)
 		}
 	}
 
+	if (aic31xx->codec_type == DAC3100) {
+		ret = tlv320dac3100_load_coeffs(aic31xx, "tlv320dac3100-coeffs.bin");
+		if (ret)
+			dev_warn(aic31xx->dev, "Did not load any filter coefficients\n");
+	}
+
 	if (aic31xx->codec_type & DAC31XX_BIT)
 		return devm_snd_soc_register_component(&i2c->dev,
 				&soc_codec_driver_aic31xx,