// 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 5ms */
	mdelay(5);

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

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

	/* Configure GPIO0, GPIO1 and GPIO2 as outputs */
	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);

	/* Configure the chip */
        tas5825m_write_reg(client, 0x02, 0x40); /* # 768kHz, BTL, BD mode */
	tas5825m_write_reg(client, 0x53, 0x60); /* # 175kHz bandwidth */
	tas5825m_write_reg(client, 0x54, 0x04); /* Analog gain of -2.0dB for 24V */
	tas5825m_write_reg(client, 0x4c, 0x30); /* Digital volume 0dB */
	tas5825m_write_reg(client, 0x03, 0x02); /* Hiz, unmute */

	/* Wait for the device to settle down */
	mdelay(5);

	/* Start playing */
	tas5825m_write_reg(client, 0x03, 0x03); /* Play mode */
	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");