aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAurelien Jarno <aurelien@aurel32.net>2020-04-13 18:54:23 +0200
committerAurelien Jarno <aurelien@aurel32.net>2020-04-13 21:51:32 +0200
commit5f76c571ec23cad3c9f79d634141293eae080643 (patch)
tree941af0cc25836dc3063631855ca7776ea21cae31
parente7101511824d54e836f4b6ccee630aef118a1ade (diff)
downloadrpi-amp-5f76c571ec23cad3c9f79d634141293eae080643.tar.gz
Add preliminary kernel module
-rw-r--r--README.md23
-rw-r--r--tas5825m-module/Makefile21
-rw-r--r--tas5825m-module/dts.patch69
-rw-r--r--tas5825m-module/tas5825m.c324
-rw-r--r--tas5825m-module/tas5825m.h24
5 files changed, 459 insertions, 2 deletions
diff --git a/README.md b/README.md
index 7fa2427..66db318 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,25 @@ control, audio processor, etc). It is also not good quality enough to be
upstreamable. It has been sufficient for my usage, so I didn't take the time
to improve it.
+The Linux kernel module should build fine on a system with a recent kernel
+(so far I only tested 5.4 and 5.5) and with the kernel headers available. It
+could be built and installed with the following command:
+
+```
+ cd tas5825m-module
+ make
+ make modules_install
+```
+
+The tricky part is to modify the device tree to get the board recognized by
+the kernel. In the long term the 24L32 EEPROM should be used for that, but I
+haven't look at how it works yet. The patch I use on my system is
+[available](tas5825m-module/dts.patch), it should probably be adapted
+depending on the system and the kernel version. I used the `dtc` tool to dump
+the original device tree binary file into a source format, and later to
+rebuild the binary version. It should be easier to use device tree overlays
+but again I haven't looked at that yet.
+
## Sources
@@ -68,5 +87,5 @@ All the sources of the repository are available
## License
-The contents of this repository is released under the
-[Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0)](LICENSE).
+The contents of this repository, excluding the `tas5825m-module` directory, is
+released under the [Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0)](LICENSE).
diff --git a/tas5825m-module/Makefile b/tas5825m-module/Makefile
new file mode 100644
index 0000000..2147d32
--- /dev/null
+++ b/tas5825m-module/Makefile
@@ -0,0 +1,21 @@
+ifneq ($(KERNELRELEASE),)
+# kbuild part of makefile
+obj-m := tas5825m.o
+else
+
+# Normal Makefile
+
+KERNELDIR := /lib/modules/`uname -r`/build
+all:
+ $(MAKE) -C $(KERNELDIR) M=`pwd` modules
+
+debug:
+ $(MAKE) -C $(KERNELDIR) EXTRA_CFLAGS=-DDEBUG M=`pwd` modules
+
+modules_install:
+ $(MAKE) -C $(KERNELDIR) M=`pwd` modules_install
+
+clean:
+ rm -rf *.o *.ko *.mod.c *.mod .*.cmd Module.symvers modules.order .tmp_versions
+
+endif
diff --git a/tas5825m-module/dts.patch b/tas5825m-module/dts.patch
new file mode 100644
index 0000000..6bfddcb
--- /dev/null
+++ b/tas5825m-module/dts.patch
@@ -0,0 +1,69 @@
+--- bcm2837-rpi-3-b-plus.orig.dts
++++ bcm2837-rpi-3-b-plus.rpi-amp.dts
+@@ -368,6 +368,12 @@
+ brcm,pins = < 0x04 0x05 0x07 0x08 0x09 0x0a 0x0b >;
+ brcm,function = < 0x04 >;
+ };
++
++ i2s_alt0: i2s_alt0 {
++ brcm,pins = <18 19 20 21>;
++ brcm,function = < 0x04 >;
++ };
++
+ };
+
+ serial@7e201000 {
+@@ -401,13 +407,16 @@
+ bus-width = < 0x04 >;
+ };
+
+- i2s@7e203000 {
++ i2s0: i2s@7e203000 {
++ #sound-dai-cells= <0>;
+ compatible = "brcm,bcm2835-i2s";
+ reg = < 0x7e203000 0x24 >;
+ clocks = < 0x04 0x1f >;
+ dmas = < 0x0c 0x02 0x0c 0x03 >;
+ dma-names = "tx\0rx";
+- status = "disabled";
++ status = "okay";
++ pinctrl-names = "default";
++ pinctrl-0 = <&i2s_alt0>;
+ };
+
+ spi@7e204000 {
+@@ -582,6 +591,13 @@
+ pinctrl-names = "default";
+ pinctrl-0 = < 0x16 >;
+ clock-frequency = < 0x186a0 >;
++
++ tas5825m: tas5825m@4c {
++ compatible = "ti,tas5825m";
++ reg = < 0x4c >;
++ pdn-gpios = < 0x18 0x1b 0x01 >;
++ #sound-dai-cells = < 0x00 >;
++ };
+ };
+
+ i2c@7e805000 {
+@@ -823,4 +839,20 @@
+ reset-gpios = < 0x0b 0x01 0x01 >;
+ phandle = < 0x15 >;
+ };
++
++ sound {
++ compatible = "simple-audio-card";
++ simple-audio-card,name = "tas5825m";
++ simple-audio-card,format = "i2s";
++ simple-audio-card,bitclock-master = <&dailink0_master>;
++ simple-audio-card,frame-master = <&dailink0_master>;
++
++ dailink0_master: simple-audio-card,cpu {
++ sound-dai = <&i2s0>;
++ };
++
++ simple-audio-card,codec {
++ sound-dai = <&tas5825m>;
++ };
++ };
+ };
diff --git a/tas5825m-module/tas5825m.c b/tas5825m-module/tas5825m.c
new file mode 100644
index 0000000..fdca808
--- /dev/null
+++ b/tas5825m-module/tas5825m.c
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * tas5825.c - ALSA SoC Texas Instruments TAS5825M Audio Amplifier
+ *
+ * Copyright (C) 2020 Aurelien Jarno <aurelien@aurel32.net>
+ *
+ * Author: Aurelien Jarno <aurelien@aurel32.net>
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include "tas5825m.h"
+
+struct tas5825m_data {
+ struct gpio_desc *pdn_gpio;
+};
+
+static const struct snd_soc_dapm_widget tas5825m_widgets[] = {
+ SND_SOC_DAPM_OUTPUT("OUT"),
+};
+
+static const struct snd_soc_dapm_route tas5825m_routes[] = {
+ { "OUT", NULL, "Playback" },
+};
+
+static struct snd_soc_component_driver soc_component_dev_tas5825m = {
+ .dapm_widgets = tas5825m_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tas5825m_widgets),
+ .dapm_routes = tas5825m_routes,
+ .num_dapm_routes = ARRAY_SIZE(tas5825m_routes),
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+ .non_legacy_dai_naming = 1,
+};
+
+static struct snd_soc_dai_driver tas5825m_dai = {
+ .name = "tas5825m-amplifier",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = TAS5825M_RATES,
+ .formats = TAS5825M_FORMATS,
+ },
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id tas5825m_of_ids[] = {
+ { .compatible = "ti,tas5825m", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tas5825m_of_ids);
+#endif
+
+static void tas5825m_write_reg(struct i2c_client *client, uint8_t reg, uint8_t val)
+{
+ uint8_t buf[2];
+ int ret;
+
+ buf[0] = reg;
+ buf[1] = val;
+
+ ret = i2c_master_send(client, buf, sizeof(buf));
+ if (ret < 0)
+ dev_err(&client->dev, "unable to write reg 0x%02x, value 0x%02x\n", reg, val);
+}
+
+static int tas5825m_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct tas5825m_data *tas5825m;
+ int ret;
+
+ tas5825m = devm_kzalloc(dev, sizeof(*tas5825m), GFP_KERNEL);
+ if (!tas5825m)
+ return -ENOMEM;
+ dev_set_drvdata(dev, tas5825m);
+
+ /*
+ * Get control of the pdn pin and set it LOW to take the codec
+ * out of the power-down mode.
+ * Note: The actual pin polarity is taken care of in the GPIO lib
+ * according the polarity specified in the DTS.
+ */
+ tas5825m->pdn_gpio = devm_gpiod_get_optional(dev, "pdn", GPIOD_OUT_LOW);
+
+ if (IS_ERR(tas5825m->pdn_gpio)) {
+ if (PTR_ERR(tas5825m->pdn_gpio) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_info(dev, "failed to get pdn GPIO: %ld\n",
+ PTR_ERR(tas5825m->pdn_gpio));
+ tas5825m->pdn_gpio = NULL;
+ }
+
+ /* power-up time may be as long as 1ms */
+ mdelay(1);
+
+ /* PAGE 0x00, BOOK 0x00 */
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x7f, 0x00);
+
+ /* Reset register and digital core */
+ tas5825m_write_reg(client, 0x01, 0x11);
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x00, 0x00);
+
+ /* PAGE 0x00, BOOK 0x00 */
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x7f, 0x00);
+
+ /* Switch from deep sleep to Hi-z mode, keep mute enabled */
+ tas5825m_write_reg(client, 0x03, 0x12);
+
+ /* GPIO0, GPIO1 and GPIO2 as output */
+ tas5825m_write_reg(client, 0x60, 0x07);
+ tas5825m_write_reg(client, 0x61, 0x02);
+ tas5825m_write_reg(client, 0x62, 0x02);
+ tas5825m_write_reg(client, 0x63, 0x02);
+
+ /* GPIO0 = 1, GPIO1 = 0, GPIO2 = 1 */
+// tas5825m_write_reg(client, 0x65, 0x05);
+ tas5825m_write_reg(client, 0x65, 0x00);
+
+ /* ??? */
+ tas5825m_write_reg(client, 0x7f, 0x00);
+ tas5825m_write_reg(client, 0x7d, 0x11); /* ???? */
+ tas5825m_write_reg(client, 0x7e, 0xff); /* ???? */
+ tas5825m_write_reg(client, 0x00, 0x01); /* Page 0x01 */
+ tas5825m_write_reg(client, 0x51, 0x05);
+ tas5825m_write_reg(client, 0x00, 0x02); /* Page 0x02 */
+ tas5825m_write_reg(client, 0x19, 0xa0);
+ tas5825m_write_reg(client, 0x1d, 0x01);
+ tas5825m_write_reg(client, 0x00, 0x00); /* Page 0x00 */
+ tas5825m_write_reg(client, 0x7f, 0x00); /* Book 0x00 */
+ tas5825m_write_reg(client, 0x46, 0x01); /* DSP_CTRL */
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x02, 0x42); /* # 768kHz, BTL, Hybrid mode */
+ tas5825m_write_reg(client, 0x53, 0x60); /* # 175kHz bandwidth */
+ tas5825m_write_reg(client, 0x54, 0x04); /* Analog gain of -2.0dB (originally 0) */
+ tas5825m_write_reg(client, 0x00, 0x00); /* Page 0x00 */
+ tas5825m_write_reg(client, 0x7f, 0x00); /* Book 0x00 */
+ tas5825m_write_reg(client, 0x03, 0x02); /* Hiz, unmute */
+
+ mdelay(5);
+
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x00, 0x00);
+ tas5825m_write_reg(client, 0x7f, 0x8c); /* Book 0x8c */
+ tas5825m_write_reg(client, 0x00, 0x06); /* Page 0x06 */
+ tas5825m_write_reg(client, 0x38, 0x00);
+ tas5825m_write_reg(client, 0x39, 0x00);
+ tas5825m_write_reg(client, 0x3a, 0x04);
+ tas5825m_write_reg(client, 0x3b, 0x00);
+
+ /* Tuning coeffs */
+ tas5825m_write_reg(client, 0x00, 0x0b); /* Page 0x0b */
+ tas5825m_write_reg(client, 0x50, 0x00);
+ tas5825m_write_reg(client, 0x51, 0x20);
+ tas5825m_write_reg(client, 0x52, 0xc4);
+ tas5825m_write_reg(client, 0x53, 0x9c);
+ tas5825m_write_reg(client, 0x5c, 0x7f);
+ tas5825m_write_reg(client, 0x5d, 0xff);
+ tas5825m_write_reg(client, 0x5e, 0xff);
+ tas5825m_write_reg(client, 0x5f, 0xff);
+
+ /* Book 0x8c */
+ tas5825m_write_reg(client, 0x00, 0x00); /* Page 0x00 */
+ tas5825m_write_reg(client, 0x7f, 0x8c); /* Book 0x8c */
+
+ tas5825m_write_reg(client, 0x00, 0x01); /* Page 0x01 */
+ tas5825m_write_reg(client, 0x28, 0x40); /* => enable AGL */
+ tas5825m_write_reg(client, 0x29, 0x00);
+ tas5825m_write_reg(client, 0x2a, 0x00);
+ tas5825m_write_reg(client, 0x2b, 0x00);
+
+ tas5825m_write_reg(client, 0x00, 0x0a); /* Page 0x0a */
+ tas5825m_write_reg(client, 0x64, 0x00); /* Digital left from left = 0x00800000 */
+ tas5825m_write_reg(client, 0x65, 0x80);
+ tas5825m_write_reg(client, 0x66, 0x00);
+ tas5825m_write_reg(client, 0x67, 0x00);
+ tas5825m_write_reg(client, 0x68, 0x00); /* Digital left from right = 0x00000000 */
+ tas5825m_write_reg(client, 0x69, 0x00);
+ tas5825m_write_reg(client, 0x6a, 0x00);
+ tas5825m_write_reg(client, 0x6b, 0x00);
+ tas5825m_write_reg(client, 0x6c, 0x00); /* Digital right from left = 0x00000000 */
+ tas5825m_write_reg(client, 0x6d, 0x00);
+ tas5825m_write_reg(client, 0x6e, 0x00);
+ tas5825m_write_reg(client, 0x6f, 0x00);
+ tas5825m_write_reg(client, 0x70, 0x00); /* Digital right from right = 0x008000000 */
+ tas5825m_write_reg(client, 0x71, 0x80);
+ tas5825m_write_reg(client, 0x72, 0x00);
+ tas5825m_write_reg(client, 0x73, 0x00);
+ tas5825m_write_reg(client, 0x74, 0x00); /* Analog left from left = 0x008000000 */
+ tas5825m_write_reg(client, 0x75, 0x80);
+ tas5825m_write_reg(client, 0x76, 0x00);
+ tas5825m_write_reg(client, 0x77, 0x00);
+ tas5825m_write_reg(client, 0x78, 0x00); /* Analog left from right = 0x00000000 */
+ tas5825m_write_reg(client, 0x79, 0x00);
+ tas5825m_write_reg(client, 0x7a, 0x00);
+ tas5825m_write_reg(client, 0x7b, 0x00);
+ tas5825m_write_reg(client, 0x7c, 0x00); /* Analog right from left = 0x00000000 */
+ tas5825m_write_reg(client, 0x7d, 0x00);
+ tas5825m_write_reg(client, 0x7e, 0x00);
+ tas5825m_write_reg(client, 0x7f, 0x00);
+
+ tas5825m_write_reg(client, 0x00, 0x0b); /* Page 0x0b */
+ tas5825m_write_reg(client, 0x08, 0x00); /* Analog right from right = 0x008000000 */
+ tas5825m_write_reg(client, 0x09, 0x80);
+ tas5825m_write_reg(client, 0x0a, 0x00);
+ tas5825m_write_reg(client, 0x0b, 0x00);
+
+ tas5825m_write_reg(client, 0x38, 0x02); /* AGL PVDD alpha */
+ tas5825m_write_reg(client, 0x39, 0xa3);
+ tas5825m_write_reg(client, 0x3a, 0x9a);
+ tas5825m_write_reg(client, 0x3b, 0xcc);
+ tas5825m_write_reg(client, 0x3c, 0x00); /* AGL temp alpha */
+ tas5825m_write_reg(client, 0x3d, 0x06);
+ tas5825m_write_reg(client, 0x3e, 0xd3);
+ tas5825m_write_reg(client, 0x3f, 0x72);
+
+ tas5825m_write_reg(client, 0x48, 0x04); /* AGL softening filter */
+ tas5825m_write_reg(client, 0x49, 0xc1);
+ tas5825m_write_reg(client, 0x4a, 0xff);
+ tas5825m_write_reg(client, 0x4b, 0x93);
+ tas5825m_write_reg(client, 0x4c, 0x01); /* AGL attack rate */
+ tas5825m_write_reg(client, 0x4d, 0x12);
+ tas5825m_write_reg(client, 0x4e, 0x6e);
+ tas5825m_write_reg(client, 0x4f, 0x98);
+
+ tas5825m_write_reg(client, 0x54, 0x7b); /* AGL softening filter omega */
+ tas5825m_write_reg(client, 0x55, 0x3e);
+ tas5825m_write_reg(client, 0x56, 0x00);
+ tas5825m_write_reg(client, 0x57, 0x6d);
+ tas5825m_write_reg(client, 0x58, 0x00); /* AGL release rate */
+ tas5825m_write_reg(client, 0x59, 0x00);
+ tas5825m_write_reg(client, 0x5a, 0xae);
+ tas5825m_write_reg(client, 0x5b, 0xc3);
+
+ tas5825m_write_reg(client, 0x60, 0x08); /* AGL PVDD threshold*/
+ tas5825m_write_reg(client, 0x61, 0x13);
+ tas5825m_write_reg(client, 0x62, 0x85);
+ tas5825m_write_reg(client, 0x63, 0x62);
+ tas5825m_write_reg(client, 0x64, 0x00); /* AGL temp scale */
+ tas5825m_write_reg(client, 0x65, 0x80);
+ tas5825m_write_reg(client, 0x66, 0x00);
+ tas5825m_write_reg(client, 0x67, 0x00);
+ tas5825m_write_reg(client, 0x6c, 0x00); /* AGL volt scale */
+ tas5825m_write_reg(client, 0x6d, 0x78);
+ tas5825m_write_reg(client, 0x6e, 0xd6);
+ tas5825m_write_reg(client, 0x6f, 0xfd);
+ tas5825m_write_reg(client, 0x70, 0x00); /* Volt/temp scale */
+ tas5825m_write_reg(client, 0x71, 0x08);
+ tas5825m_write_reg(client, 0x72, 0x13);
+ tas5825m_write_reg(client, 0x73, 0x85);
+
+ /* Register Tuning */
+ tas5825m_write_reg(client, 0x00, 0x00); /* Page 0x00 */
+ tas5825m_write_reg(client, 0x7f, 0x00); /* Book 0x00 */
+ tas5825m_write_reg(client, 0x30, 0x00); /* SDOUT is the DSP output */
+ tas5825m_write_reg(client, 0x4c, 0x30); /* Digital volume 0dB */
+ tas5825m_write_reg(client, 0x03, 0x03); /* Play mode */
+ tas5825m_write_reg(client, 0x00, 0x00); /* Page 0x00 */
+ tas5825m_write_reg(client, 0x7f, 0x00); /* Book 0x00 */
+ tas5825m_write_reg(client, 0x78, 0x80); /* Clear analog fault */
+
+ ret = devm_snd_soc_register_component(dev,
+ &soc_component_dev_tas5825m,
+ &tas5825m_dai, 1);
+ if (ret < 0) {
+ dev_err(dev, "unable to register codec: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tas5825m_i2c_remove(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct tas5825m_data *tas5825m = dev_get_drvdata(dev);
+
+ /* put the codec in power-down */
+ if (tas5825m->pdn_gpio)
+ gpiod_set_value_cansleep(tas5825m->pdn_gpio, 1);
+
+ return 0;
+}
+
+static const struct i2c_device_id tas5825m_i2c_ids[] = {
+ { "tas5825m", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tas5825m_i2c_ids);
+
+static struct i2c_driver tas5825m_i2c_driver = {
+ .driver = {
+ .name = "tas5825m",
+ .of_match_table = of_match_ptr(tas5825m_of_ids),
+ },
+ .probe = tas5825m_i2c_probe,
+ .remove = tas5825m_i2c_remove,
+ .id_table = tas5825m_i2c_ids,
+};
+
+module_i2c_driver(tas5825m_i2c_driver);
+
+MODULE_AUTHOR("Aurelien Jarno <aurelien@aurel32.net>");
+MODULE_DESCRIPTION("TAS5825M audio amplifier driver");
+MODULE_LICENSE("GPL");
diff --git a/tas5825m-module/tas5825m.h b/tas5825m-module/tas5825m.h
new file mode 100644
index 0000000..11605d0
--- /dev/null
+++ b/tas5825m-module/tas5825m.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * tas5825.h - ALSA SoC Texas Instruments TAS5825M Audio Amplifier
+ *
+ * Copyright (C) 2020 Aurelien Jarno <aurelien@aurel32.net>
+ *
+ * Author: Aurelien Jarno <aurelien@aurel32.net>
+ */
+
+#ifndef __TAS5825M_H__
+#define __TAS5825M_H__
+
+#define TAS5825M_RATES (SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_192000)
+#define TAS5825M_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+/* Register Address Map */
+#define TAS5825M_RESET_CTRL 0x00
+
+#endif /* __TAS5825M_H__ */
+