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