// 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");