aboutsummaryrefslogtreecommitdiff
path: root/tas5825m-module/tas5825m.c
diff options
context:
space:
mode:
Diffstat (limited to 'tas5825m-module/tas5825m.c')
-rw-r--r--tas5825m-module/tas5825m.c324
1 files changed, 324 insertions, 0 deletions
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");