// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * mtk_amp_ad82088d.c -- ESMT AMP ad82088d driver
 *
 * Copyright (c) 2022 MediaTek Inc.
 */

#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/io.h>
#include <linux/i2c.h>
#include <linux/gpio/consumer.h>
#include <sound/soc.h>
#include <sound/control.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
#include <linux/version.h>
#include <linux/kthread.h>

#include "mtk_amp_ad82088d.h"
#include "mtk_ini_parser.h"

// you can read out the register and ram data, and check them.
#define AD82088D_REG_RAM_CHECK

#define SINGLE_REGISTER		1
#define SINGLE_RAM		2
#define MULTIPLE_RAM		3

/* address(1 byte) + value(1 byte) */
#define SINGLE_REGISTER_BYTE		2
/* ram bank index(1 byte) + address (1 byte) + 4 params(4 * 1 byte)*/
#define AD82088D_SINGLE_RAM_BYTE	6
/* ram bank index(1 byte) + address (1 byte) + 5 * 4 params(5 * 4 * 1 byte)*/
#define AD82088D_MULTIPLE_RAM_BYTE	22

static struct task_struct *set_volume_mode_thread;
static DEFINE_MUTEX(set_volume_mode_mutex);

u8 AmpInitTblAD82088D[] = {
//DataLen	Address	DataN	DataN+1...
	/* don't need to do sw reset here, hw reset is applied */
	1,	0x00,	0x04,//##AGC_ALPHA
	1,	0x01,	0x82,//##State_Control_2
	1,	0x03,	0x18,//##Master_volume_control
	1,	0x04,	0x18,//##Channel_1_volume_control
	1,	0x05,	0x18,//##Channel_2_volume_control
	1,	0x06,	0x18,//##Channel_3_volume_control
	1,	0x07,	0x18,//##Channel_4_volume_control
	1,	0x08,	0x18,//##Channel_5_volume_control
	1,	0x09,	0x18,//##Channel_6_volume_control
	1,	0x0a,	0x00,//##Reserve
	1,	0x0b,	0x00,//##Reserve
	1,	0x0c,	0x90,//##State_Control_4
	1,	0x0d,	0xc0,//##Channel_1_configuration_registers
	1,	0x0e,	0xc0,//##Channel_2_configuration_registers
	1,	0x0f,	0xc0,//##Channel_3_configuration_registers
	1,	0x10,	0xc0,//##Channel_4_configuration_registers
	1,	0x11,	0xc0,//##Channel_5_configuration_registers
	1,	0x12,	0xc0,//##Channel_6_configuration_registers
	1,	0x13,	0xc0,//##Channel_7_configuration_registers
	1,	0x14,	0xc0,//##Channel_8_configuration_registers
	1,	0x15,	0x62,//##Q_PWM_DUTY
	1,	0x16,	0x20,//##clock_and_FS_detection
	1,	0x17,	0x10,//##clock_det
	1,	0x18,	0x40,//##DTC
	1,	0x19,	0x06,//##Error_Delay
	1,	0x1a,	0xaa,//##State_Control_5
	1,	0x1b,	0x80,//##State_Control_6
	1,	0x1c,	0x80,//##State_Control_7
	1,	0x33,	0x06,//##State_Control_8
	1,	0x34,	0x00,//##AGL_control
	1,	0x35,	0x00,//##Volume_Fine_tune
	1,	0x36,	0x00,//##Volume_Fine_tune
	1,	0x37,	0x6c,//##Device_ID_register
	0x00,
};

static void mtk_amp_delaytask_us(unsigned int u32Us)
{
	/*
	 * Refer to Documentation/timers/timers-howto.txt
	 * Non-atomic context
	 *		< 10us	:	udelay()
	 *	10us ~ 20ms	:	usleep_range()
	 *		> 10ms	:	msleep() or msleep_interruptible()
	 */

	if (u32Us < 10UL)
		udelay(u32Us);
	else if (u32Us < 20UL * 1000UL)
		usleep_range(u32Us, u32Us + 1);
	else
		msleep_interruptible((unsigned int)(u32Us / 1000UL));
}

static int amp_ad82088d_startup(struct snd_pcm_substream *substream,
				struct snd_soc_dai *dai)
{
	struct amp_ad82088d_priv *priv = snd_soc_dai_get_drvdata(dai);

	if (priv->initialized)
		dev_info(priv->dev, "amp 82088d initialized !!!!!!!!!\n");
	else
		dev_info(priv->dev, "amp 82088d not init !!!!!!!!!!!\n");

	return 0;
}

static const struct snd_soc_dai_ops amp_ad82088d_dai_ops = {
	.startup	= amp_ad82088d_startup,
};

static struct snd_soc_dai_driver amp_ad82088d_dais[] = {
	{
		.name = "AMP_AD82088D",
		.id = 0,
		.playback = {
			.stream_name = "AMP AD82088D",
		},
		.ops = &amp_ad82088d_dai_ops,
	},
};

static struct snd_soc_dai_driver amp_ad82088d_1_dais[] = {
	{
		.name = "AMP_AD82088D_1",
		.id = 1,
		.playback = {
			.stream_name = "AMP AD82088D_1",
		},
	},
};

static struct snd_soc_dai_driver amp_ad82088d_2_dais[] = {
	{
		.name = "AMP_AD82088D_2",
		.id = 2,
		.playback = {
			.stream_name = "AMP AD82088D_2",
		},
	},
};

static struct snd_soc_dai_driver amp_ad82088d_3_dais[] = {
	{
		.name = "AMP_AD82088D_3",
		.id = 3,
		.playback = {
			.stream_name = "AMP AD82088D_3",
		},
	},
};

static struct snd_soc_dai_driver amp_ad82088d_4_dais[] = {
	{
		.name = "AMP_AD82088D_4",
		.id = 4,
		.playback = {
			.stream_name = "AMP AD82088D_4",
		},
	},
};

static int amp_write(struct amp_ad82088d_priv *priv, unsigned char *InitTable)
{
	u8 *pu8Str = InitTable;
	u8 *buf;
	u8 reg;
	u8 size;
	int ret;

	do {
		size = *pu8Str;
		pu8Str++;

		reg = *pu8Str;
		pu8Str++;

		buf = pu8Str;
		ret = regmap_raw_write(priv->regmap, reg, buf, size);
		if (ret < 0) {
			dev_err(priv->dev, "i2c write fail\n");
			return -1;
		}

		pu8Str = pu8Str + size;
	} while (*pu8Str != 0);

	return 0;
}

static int amp_ad82088d_single_ram_write(struct amp_ad82088d_priv *priv, unsigned char *buf)
{
	u8 bank = 0;
	u8 cmd = 0;
	int ret = 0;

	bank = buf[0];

	ret |= regmap_write(priv->regmap, CFADDR, buf[1]);
	ret |= regmap_write(priv->regmap, A1CF1, buf[2]);
	ret |= regmap_write(priv->regmap, A1CF2, buf[3]);
	ret |= regmap_write(priv->regmap, A1CF3, buf[4]);
	ret |= regmap_write(priv->regmap, A1CF4, buf[5]);
	if (bank == 0)
		cmd = 0x1;
	if (bank == 1)
		cmd = 0x41;

	ret |= regmap_write(priv->regmap, CFUD, cmd);

	return ret;
}

static int amp_ad82088d_multiple_ram_write(struct amp_ad82088d_priv *priv, unsigned char *buf)
{
	u8 bank = 0;
	u8 cmd = 0;
	int ret = 0;

	bank = buf[0];

	ret |= regmap_write(priv->regmap, CFADDR, buf[1]);
	/* write A1 */
	ret |= regmap_write(priv->regmap, A1CF1, buf[2]);
	ret |= regmap_write(priv->regmap, A1CF2, buf[3]);
	ret |= regmap_write(priv->regmap, A1CF3, buf[4]);
	ret |= regmap_write(priv->regmap, A1CF4, buf[5]);
	/* write A2 */
	ret |= regmap_write(priv->regmap, A2CF1, buf[6]);
	ret |= regmap_write(priv->regmap, A2CF2, buf[7]);
	ret |= regmap_write(priv->regmap, A2CF3, buf[8]);
	ret |= regmap_write(priv->regmap, A2CF4, buf[9]);
	/* write B1 */
	ret |= regmap_write(priv->regmap, B1CF1, buf[10]);
	ret |= regmap_write(priv->regmap, B1CF2, buf[11]);
	ret |= regmap_write(priv->regmap, B1CF3, buf[12]);
	ret |= regmap_write(priv->regmap, B1CF4, buf[13]);
	/* write B2 */
	ret |= regmap_write(priv->regmap, B2CF1, buf[14]);
	ret |= regmap_write(priv->regmap, B2CF2, buf[15]);
	ret |= regmap_write(priv->regmap, B2CF3, buf[16]);
	ret |= regmap_write(priv->regmap, B2CF4, buf[17]);
	/* write A0 */
	ret |= regmap_write(priv->regmap, A0CF1, buf[18]);
	ret |= regmap_write(priv->regmap, A0CF2, buf[19]);
	ret |= regmap_write(priv->regmap, A0CF3, buf[20]);
	ret |= regmap_write(priv->regmap, A0CF4, buf[21]);

	if (bank == 0)
		cmd = 0x2;
	if (bank == 1)
		cmd = 0x42;

	ret |= regmap_write(priv->regmap, CFUD, cmd);

	return ret;
}

static int amp_cus_show(struct amp_ad82088d_priv *priv, unsigned char *table)
{
	u8 *buf = table;
	u8 cmd = 0;
	u8 size = 0;

	do {
		cmd = *buf;
		buf++;
		pr_err("CMD %x\n", cmd);

		if (cmd == SINGLE_REGISTER) {
			u8 reg = 0, value = 0;

			reg = *buf;
			value = *(buf + 1);
			pr_err("reg %x value %x\n", reg, value);
			size = SINGLE_REGISTER_BYTE;
		} else if (cmd == SINGLE_RAM) {
			pr_err("single ram bank %x reg %x [%x %x %x %x]\n",
				buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
			size = AD82088D_SINGLE_RAM_BYTE;
		} else if (cmd == MULTIPLE_RAM) {	/* MULTIPLE_RAM */
			pr_err("multi ram bank %x reg %x [%x %x %x %x][%x %x %x %x][%x %x %x %x]",
				buf[0], buf[1],
				buf[2], buf[3], buf[4], buf[5],
				buf[6], buf[7], buf[8], buf[9],
				buf[10], buf[11], buf[12], buf[13]);
			 pr_err("[%x %x %x %x][%x %x %x %x]\n",
				buf[14], buf[15], buf[16], buf[17],
				buf[18], buf[19], buf[20], buf[21]);
			size = AD82088D_MULTIPLE_RAM_BYTE;
		} else
			pr_err("[AMP 82088d]wrong cmd\n");

		buf = buf + size;
	} while (*buf != 0);

	return 0;
}

static int amp_cus_write(struct amp_ad82088d_priv *priv, unsigned char *table)
{
	u8 *buf = table;
	u8 cmd = 0;
	int ret = 0;
	u8 size = 0;

	do {
		cmd = *buf;
		buf++;

		if (cmd == SINGLE_REGISTER) {
			u8 reg = 0, value = 0;

			reg = *buf;
			value = *(buf + 1);
			ret = regmap_write(priv->regmap, reg, value);
			size = SINGLE_REGISTER_BYTE;
		} else if (cmd == SINGLE_RAM) {
			ret = amp_ad82088d_single_ram_write(priv, buf);
			size = AD82088D_SINGLE_RAM_BYTE;
		} else if (cmd == MULTIPLE_RAM) {	/* MULTIPLE_RAM */
			ret = amp_ad82088d_multiple_ram_write(priv, buf);
			size = AD82088D_MULTIPLE_RAM_BYTE;
		} else {
			pr_err("[AMP 82088d]wrong cmd\n");
			return -1;
		}

		if (ret < 0) {
			dev_err(priv->dev, "[AMP 82088d]i2c write fail\n");
			return -1;
		}

		buf = buf + size;
	} while (*buf != 0);

	return 0;
}

static int amp_ad82088d_powerdown(struct snd_soc_component *component, bool bEnable)
{
	struct amp_ad82088d_priv *priv = snd_soc_component_get_drvdata(component);

	gpiod_set_value(priv->amp_powerdown, bEnable);

	return 0;
}

static int amp_ad82088d_mute(struct snd_soc_component *component, bool bEnable)
{
	struct amp_ad82088d_priv *priv = snd_soc_component_get_drvdata(component);

	if (bEnable)
		regmap_update_bits(priv->regmap, 0x02, 0x40, 0x40);
	else
		regmap_update_bits(priv->regmap, 0x02, 0x40, 0x0);

	return 0;
}

static int amp_ad82088d_init(struct amp_ad82088d_priv *priv, unsigned char *InitTable)
{
	int ret = 0;

	ret = amp_write(priv, InitTable);
	if (ret < 0) {
		dev_err(priv->dev, "[AMP 82088d]Init fail\n");
		priv->initialized = 0;
	} else {
		dev_info(priv->dev, "[AMP 82088d]Init success\n");
		priv->initialized = 1;
	}

	return ret;
}

static int amp_ad82088d_cus_init(struct amp_ad82088d_priv *priv, unsigned char *InitTable)
{
	int ret = 0;

	ret = amp_cus_write(priv, InitTable);
	if (ret < 0) {
		dev_err(priv->dev, "[AMP 82088d]Init fail\n");
		priv->initialized = 0;
	} else {
		dev_info(priv->dev, "[AMP 82088d]Init success\n");
		priv->initialized = 1;
	}

	return ret;
}

//[Sysfs] HELP command
static ssize_t help_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "Debug Help:\n"
		       "- amp_status\n"
		       "             <R>: Read amp status.\n"
		       "- amp_mute   <W>: write(1)/Clear(0) amp mute status.\n"
		       "- volume_config\n"
		       "             <R>: Read volume config.\n"
		       "- sound_mode\n"
		       "             <R>: Read sound mode exist or not.\n"
		       "- ini_content\n"
		       "             <R>: Read sound mode X vol X content\n"
		       "             <W>: set sound mode X vol X\n"
		       );
}

static ssize_t amp_status_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct amp_ad82088d_priv *priv = dev_get_drvdata(dev);

	return snprintf(buf, PAGE_SIZE, "%d\n", priv->initialized);
}

static ssize_t amp_mute_store(struct device *dev,
				  struct device_attribute *attr, const char *buf, size_t count)
{
	ssize_t retval;
	int mute_flag = 0;
	struct amp_ad82088d_priv *priv = dev_get_drvdata(dev);
	struct snd_soc_component *component = priv->component;

	retval = kstrtoint(buf, 10, &mute_flag);
	if (retval == 0) {
		if ((mute_flag != 0) && (mute_flag != 1))
			dev_err(dev, "You should enter 0 or 1 !!!\n");
	}

	dev_info(dev, "set amp mute %d\n", mute_flag);

	amp_ad82088d_mute(component, mute_flag);

	return (retval < 0) ? retval : count;
}

static ssize_t volume_config_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct amp_ad82088d_priv *priv = dev_get_drvdata(dev);
	int count = 0;
	int i = 0;

	count += scnprintf(buf+count, PAGE_SIZE, "vol config %d\n", priv->cus_param->volume_config);

	for (i = 0; i < priv->cus_param->volume_config; i++)
		count += scnprintf(buf+count, PAGE_SIZE, "vol index %d : %d\n",
			i, priv->cus_param->vol_interval_range[i]);

	return count;
}

static ssize_t sound_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct amp_ad82088d_priv *priv = dev_get_drvdata(dev);
	int count = 0;
	int i = 0, j = 0;

	for (i = 0; i < SOUND_MODE_MAX; i++) {
		if (priv->cus_param->volume_config) {
			for (j = 0; j < priv->cus_param->volume_config; j++)  {
				if (priv->cus_param->sound_mode[i][j] != NULL)
					count += scnprintf(buf+count, PAGE_SIZE,
						"sound mode %d vol config %d exist\n",
							i, j);
			}
		} else {
			if (priv->cus_param->sound_mode[i][0] != NULL)
				count += scnprintf(buf+count, PAGE_SIZE, "sound mode %d exist\n",
							i);
		}
	}

	return count;
}

static ssize_t ini_content_store(struct device *dev,
				struct device_attribute *attr, const char *buf, size_t count)
{
	struct amp_ad82088d_priv *priv = dev_get_drvdata(dev);
	unsigned int sound_mode;
	unsigned int vol_index;

	if (sscanf(buf, "%u %u", &sound_mode, &vol_index) < 0) {
		dev_err(dev, "[%s]input value wrong\n", __func__);
		return 0;
	}

	if (sound_mode > (SOUND_MODE_MAX - 1)) {
		dev_err(dev, "[%s]input sound mode wrong %d\n", __func__, sound_mode);
		return 0;
	}

	if (vol_index > VOLUME_MAX) {
		dev_err(dev, "[%s]input vol index wrong %d\n", __func__, vol_index);
		return 0;
	}

	priv->sysfs_sound_mode = sound_mode;
	priv->sysfs_vol_index = vol_index;

	return count;
}

static ssize_t ini_content_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct amp_ad82088d_priv *priv = dev_get_drvdata(dev);
	int count = 0;

	dev_info(dev, "show sound mode %d vol index %d\n",
		priv->sysfs_sound_mode, priv->sysfs_vol_index);

	if (priv->cus_param->sound_mode[priv->sysfs_sound_mode][priv->sysfs_vol_index] == NULL)
		return 0;

	amp_cus_show(priv,
		priv->cus_param->sound_mode[priv->sysfs_sound_mode][priv->sysfs_vol_index]);

	return count;
}

static DEVICE_ATTR_RO(help);
static DEVICE_ATTR_RO(amp_status);
static DEVICE_ATTR_WO(amp_mute);
static DEVICE_ATTR_RO(sound_mode);
static DEVICE_ATTR_RO(volume_config);
static DEVICE_ATTR_RW(ini_content);

static struct attribute *amp_attrs[] = {
	&dev_attr_help.attr,
	&dev_attr_amp_status.attr,
	&dev_attr_amp_mute.attr,
	&dev_attr_sound_mode.attr,
	&dev_attr_volume_config.attr,
	&dev_attr_ini_content.attr,
	NULL,
};

static const struct attribute_group amp_attr_group = {
	.name = "mtk_dbg",
	.attrs = amp_attrs,
};

static const struct attribute_group *amp_attr_groups[] = {
	&amp_attr_group,
	NULL,
};

static void do_work(struct work_struct *work)
{
	struct amp_ad82088d_priv *priv = container_of(work, struct amp_ad82088d_priv, work);
	const struct firmware *fw = NULL;
	int ret = 0;

	ret = request_firmware_direct(&fw, priv->cus_ini, priv->dev);
	if (fw == NULL) {
		dev_err(priv->dev, "fw = NULL, request cus ini fw not avail\n");
		return;
	}

	ret = cus_parser(fw, priv->cus_param);
	if (ret < 0) {
		dev_err(priv->dev, "[AMP 82088d] parse cus ini fail\n");
		release_firmware(fw);
		return;
	}

	release_firmware(fw);
}

static void parse_external_ini(struct amp_ad82088d_priv *priv)
{
	const struct firmware *fw = NULL;
	int ret = 0;

	if (priv->mtk_ini && (priv->no_extini == 0)) {
		ret = request_firmware_direct(&fw, priv->mtk_ini, priv->dev);
		if (ret < 0) {
			dev_info(priv->dev, "Err! load ext ini fail,fallback to default\n");
			priv->no_extini = 1;
		} else {
			priv->init_table = mtk_parser(fw);
			if (priv->init_table)
				dev_info(priv->dev, "load ext ini success\n");
		}
		release_firmware(fw);
	}

	if (priv->cus_ini && (priv->no_cusini == 0)) {
		ret = request_firmware_direct(&fw, priv->cus_ini, priv->dev);
		if (ret < 0) {
			dev_info(priv->dev, "Err! load cus ini fail,fallback to default\n");
			priv->no_cusini = 1;
		} else {
			ret = cus_common_parser(fw, priv->cus_param);
			if (ret == 0)
				dev_info(priv->dev, "load cus ini success\n");
		}
		release_firmware(fw);
	}
}

static int amp_ad82088d_probe(struct snd_soc_component *component)
{
	struct amp_ad82088d_priv *priv =	snd_soc_component_get_drvdata(component);
	int ret = 0;

	if ((priv->suspended != true) && (priv->init_table == NULL))
		parse_external_ini(priv);

	if (priv->type == 0) {
		//wait t5 min time = 10ms
		mtk_amp_delaytask_us(10*1000);
		//pull high amp PD pin
		amp_ad82088d_powerdown(component, 0);
		//wait t9 min time = 20ms then we can start I2C command
		mtk_amp_delaytask_us(20*1000);
	}
	//software reset amp, if no have hardware reset pin(24pin's ad82088d)
	regmap_update_bits(priv->regmap, 0x1A, 0x20, 0x0);
	mtk_amp_delaytask_us(5*1000);
	//normal operation
	regmap_update_bits(priv->regmap, 0x1A, 0x20, 0x20);
	mtk_amp_delaytask_us(20*1000);
	//set all channel mute during writing all of registers and RAM
	regmap_update_bits(priv->regmap, 0x02, 0x40, 0x40);

	if (priv->init_table)			/* mtk_ini */
		ret = amp_ad82088d_init(priv, priv->init_table);
	else if (priv->cus_param->init_table)	/* cus_ini */
		ret = amp_ad82088d_cus_init(priv, priv->cus_param->init_table);
	else					/* default init table */
		ret = amp_ad82088d_init(priv, AmpInitTblAD82088D);
	if (ret < 0)
		dev_err(priv->dev, "[AMP 82088d] init fail\n");

	mtk_amp_delaytask_us(2*1000);
	//set all channel un-mute
	regmap_update_bits(priv->regmap, 0x02, 0x40, 0x0);

	priv->component = component;

	/* async parse sound mode & volume config */
	if ((priv->suspended != true) && (priv->cus_param->init_table))
		schedule_work(&priv->work);

	return 0;
}

static void amp_ad82088d_remove(struct snd_soc_component *component)
{
	amp_ad82088d_powerdown(component, 1);
}

static int amp_ad82088d_mute_put(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
	struct amp_ad82088d_priv *priv = snd_soc_component_get_drvdata(component);
	int val = ucontrol->value.integer.value[0];

	if (val < 0)
		return -EINVAL;

	dev_info(priv->dev, "set amp mute %d\n", val);

	amp_ad82088d_mute(component, val);

	return 0;
}

static int amp_ad82088d_mute_get(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
	struct amp_ad82088d_priv *priv = snd_soc_component_get_drvdata(component);
	unsigned int val = 0;
	int ret;

	ret = regmap_read(priv->regmap, AD82088D_STATE_CTRL3_REG, &val);

	dev_info(priv->dev, "get amp mute %d\n", val);
	if (ret < 0)
		dev_err(priv->dev, "fail to read mute register %d\n", ret);

	ucontrol->value.integer.value[0] = val;

	return 0;
}

static int amp_ad82088d_set_sound_mode(struct amp_ad82088d_priv *priv, int sound_mode)
{
	if (priv->cus_param->volume_config) {
		if (priv->volume < 0)
			return 0;

		if (priv->curr_vol_interval < 0)
			return 0;
	} else
		priv->vol_interval = 0;

	if (priv->cus_param->sound_mode[priv->sound_mode][priv->vol_interval] == NULL)
		return 0;

	amp_cus_write(priv, priv->cus_param->sound_mode[priv->sound_mode][priv->vol_interval]);

	return 0;
}

static int set_volume_mode_func(void *data)
{
	struct amp_ad82088d_priv *priv;

	priv = (struct amp_ad82088d_priv *)data;
	if (!priv) {
		pr_err("error: priv is null.\n");
		return 0;
	}

	if (priv->cus_param->sound_mode[priv->sound_mode][priv->vol_interval] == NULL)
		return 0;

	mutex_lock(&set_volume_mode_mutex);

	amp_cus_write(priv,
		priv->cus_param->sound_mode[priv->sound_mode][priv->vol_interval]);

	mutex_unlock(&set_volume_mode_mutex);
	return 0;
}

static int amp_ad82088d_set_volume_mode(struct amp_ad82088d_priv *priv)
{
	int volume_index = priv->cus_param->volume_config - 1;

	if (volume_index < 0)
		return 0;

	/* get volume interval */
	while (volume_index >= 0) {
		if (priv->volume >= priv->cus_param->vol_interval_range[volume_index])
			break;
		volume_index--;
	}

	if (priv->curr_vol_interval == volume_index) {
		pr_err("vol_interval %d not change return\n", priv->curr_vol_interval);
		return 0;
	}

	priv->curr_vol_interval = priv->vol_interval = volume_index;

	if (priv->sound_mode < SOUND_MODE_MAX && priv->vol_interval < VOLUME_MAX) {
		if (priv->cus_param->sound_mode[priv->sound_mode][priv->vol_interval] == NULL)
			return 0;

		set_volume_mode_thread = kthread_create(set_volume_mode_func,
					(void *)priv, "set_volume_mode_thread");
		if (IS_ERR(set_volume_mode_thread))
			pr_err("error: create set_volume_mode_thread failed.\n");
		wake_up_process(set_volume_mode_thread);
	}

	return 0;
}

static int amp_ad82088d_sound_mode_get(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
	struct amp_ad82088d_priv *priv = snd_soc_component_get_drvdata(component);

	dev_info(priv->dev, "get amp sound_mode %d\n", priv->sound_mode);

	ucontrol->value.integer.value[0] = priv->sound_mode;

	return 0;
}

static int amp_ad82088d_sound_mode_put(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
	struct amp_ad82088d_priv *priv = snd_soc_component_get_drvdata(component);
	unsigned int val = ucontrol->value.integer.value[0];

	if (val >= SOUND_MODE_MAX)
		return -EINVAL;

	dev_info(priv->dev, "set amp sound_mode %d\n", val);

	if (val == priv->curr_sound_mode)
		return 0;

	priv->curr_sound_mode = priv->sound_mode = val;

	amp_ad82088d_set_sound_mode(priv, val);

	return 0;
}

static int amp_ad82088d_volume_get(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
	struct amp_ad82088d_priv *priv = snd_soc_component_get_drvdata(component);

	dev_info(priv->dev, "get amp volume %d\n", priv->volume);

	ucontrol->value.integer.value[0] = priv->volume;

	return 0;
}

static int amp_ad82088d_volume_put(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
	struct amp_ad82088d_priv *priv = snd_soc_component_get_drvdata(component);
	unsigned int val = ucontrol->value.integer.value[0];
	int i = 0;

	if (val > VOLUME_MAX)
		return -EINVAL;

	dev_info(priv->dev, "set amp volume %d config %d\n", val, priv->cus_param->volume_config);

	for (i = 0; i < priv->cus_param->volume_config; i++)
		pr_err("volume interval index %d %d\n", i, priv->cus_param->vol_interval_range[i]);

	if (val == priv->volume)
		return 0;

	priv->volume = val;
	/* if has volume config, do set sound mode vol config */
	if (priv->cus_param->volume_config)
		amp_ad82088d_set_volume_mode(priv);

	return 0;
}

static int amp_control_info(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 100;

	return 0;
}

#define SOC_INTEGER_AMP(xname, xvalue, xhandler_get, xhandler_put) \
{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
	.name = xname, \
	.info = amp_control_info, \
	.get = xhandler_get, .put = xhandler_put, \
	.private_value = (unsigned short)xvalue \
}

static const struct snd_kcontrol_new amp_ad82088d_controls[] = {
	SOC_SINGLE_BOOL_EXT("AMP Mute", 0, amp_ad82088d_mute_get,
				amp_ad82088d_mute_put),
	SOC_INTEGER_AMP("AMP SOUNDMODE", 0, amp_ad82088d_sound_mode_get,
				amp_ad82088d_sound_mode_put),
	SOC_INTEGER_AMP("AMP VOLUME", 0, amp_ad82088d_volume_get,
				amp_ad82088d_volume_put),
};

static const struct snd_kcontrol_new amp_ad82088d_1_controls[] = {
	SOC_SINGLE_BOOL_EXT("AMP-1 Mute", 0, amp_ad82088d_mute_get,
				amp_ad82088d_mute_put),
	SOC_INTEGER_AMP("AMP-1 SOUNDMODE", 0, amp_ad82088d_sound_mode_get,
				amp_ad82088d_sound_mode_put),
	SOC_INTEGER_AMP("AMP-1 VOLUME", 0, amp_ad82088d_volume_get,
				amp_ad82088d_volume_put),
};

static const struct snd_kcontrol_new amp_ad82088d_2_controls[] = {
	SOC_SINGLE_BOOL_EXT("AMP-2 Mute", 0, amp_ad82088d_mute_get,
				amp_ad82088d_mute_put),
	SOC_INTEGER_AMP("AMP-2 SOUNDMODE", 0, amp_ad82088d_sound_mode_get,
				amp_ad82088d_sound_mode_put),
	SOC_INTEGER_AMP("AMP-2 VOLUME", 0, amp_ad82088d_volume_get,
				amp_ad82088d_volume_put),
};

static const struct snd_kcontrol_new amp_ad82088d_3_controls[] = {
	SOC_SINGLE_BOOL_EXT("AMP-3 Mute", 0, amp_ad82088d_mute_get,
				amp_ad82088d_mute_put),
	SOC_INTEGER_AMP("AMP-3 SOUNDMODE", 0, amp_ad82088d_sound_mode_get,
				amp_ad82088d_sound_mode_put),
	SOC_INTEGER_AMP("AMP-3 VOLUME", 0, amp_ad82088d_volume_get,
				amp_ad82088d_volume_put),
};

static const struct snd_kcontrol_new amp_ad82088d_4_controls[] = {
	SOC_SINGLE_BOOL_EXT("AMP-4 Mute", 0, amp_ad82088d_mute_get,
				amp_ad82088d_mute_put),
	SOC_INTEGER_AMP("AMP-4 SOUNDMODE", 0, amp_ad82088d_sound_mode_get,
				amp_ad82088d_sound_mode_put),
	SOC_INTEGER_AMP("AMP-4 VOLUME", 0, amp_ad82088d_volume_get,
				amp_ad82088d_volume_put),
};

const struct snd_soc_component_driver amp_ad82088d_component = {
	.name		= "amp-ad82088d-0",
	.probe		= amp_ad82088d_probe,
	.remove		= amp_ad82088d_remove,
	.controls	= amp_ad82088d_controls,
	.num_controls	= ARRAY_SIZE(amp_ad82088d_controls),
};

const struct snd_soc_component_driver amp_ad82088d_1_component = {
	.name		= "amp-ad82088d-1",
	.probe		= amp_ad82088d_probe,
	.remove		= amp_ad82088d_remove,
	.controls	= amp_ad82088d_1_controls,
	.num_controls	= ARRAY_SIZE(amp_ad82088d_1_controls),
};

const struct snd_soc_component_driver amp_ad82088d_2_component = {
	.name		= "amp-ad82088d-2",
	.probe		= amp_ad82088d_probe,
	.remove		= amp_ad82088d_remove,
	.controls	= amp_ad82088d_2_controls,
	.num_controls	= ARRAY_SIZE(amp_ad82088d_2_controls),
};

const struct snd_soc_component_driver amp_ad82088d_3_component = {
	.name		= "amp-ad82088d-3",
	.probe		= amp_ad82088d_probe,
	.remove		= amp_ad82088d_remove,
	.controls	= amp_ad82088d_3_controls,
	.num_controls	= ARRAY_SIZE(amp_ad82088d_3_controls),
};

const struct snd_soc_component_driver amp_ad82088d_4_component = {
	.name		= "amp-ad82088d-4",
	.probe		= amp_ad82088d_probe,
	.remove		= amp_ad82088d_remove,
	.controls	= amp_ad82088d_4_controls,
	.num_controls	= ARRAY_SIZE(amp_ad82088d_4_controls),
};

static bool ad82088d_is_volatile_reg(struct device *dev, unsigned int reg)
{
#ifdef	AD82088_REG_RAM_CHECK
	if (reg < AD82088D_MAX_REG)
		return true;
	else
		return false;
#else
	switch (reg) {
	case AD82088D_FAULT_REG:
	case AD82088D_STATE_CTRL1_REG:
	case AD82088D_STATE_CTRL2_REG:
	case AD82088D_STATE_CTRL3_REG:
	case AD82088D_STATE_CTRL5_REG:
		return true;
	default:
		return false;
	}
#endif
}

static const struct regmap_config ad82088d_regmap = {
	.reg_bits = 8,
	.val_bits = 8,
	.max_register = AD82088D_MAX_REG,
	.cache_type = REGCACHE_RBTREE,
	.volatile_reg = ad82088d_is_volatile_reg,
};

static void amp_register_component(struct amp_ad82088d_priv *priv)
{
	int ret;

	if (priv->type == 0) {				/* master */
		ret = devm_snd_soc_register_component(&priv->client->dev,
						&amp_ad82088d_component,
						amp_ad82088d_dais,
						ARRAY_SIZE(amp_ad82088d_dais));
		if (ret)
			dev_err(priv->dev, "AMP soc_register_component fail %d\n", ret);
	} else if (priv->type == 1) {			/* slave */
		ret = devm_snd_soc_register_component(&priv->client->dev,
						&amp_ad82088d_1_component,
						amp_ad82088d_1_dais,
						ARRAY_SIZE(amp_ad82088d_1_dais));
		if (ret)
			dev_err(priv->dev, "AMP soc_register_component 1 fail %d\n", ret);
	} else if (priv->type == 2) {			/* slave */
		ret = devm_snd_soc_register_component(&priv->client->dev,
						&amp_ad82088d_2_component,
						amp_ad82088d_2_dais,
						ARRAY_SIZE(amp_ad82088d_2_dais));
		if (ret)
			dev_err(priv->dev, "AMP soc_register_component 2 fail %d\n", ret);
	} else if (priv->type == 3) {			/* slave */
		ret = devm_snd_soc_register_component(&priv->client->dev,
						&amp_ad82088d_3_component,
						amp_ad82088d_3_dais,
						ARRAY_SIZE(amp_ad82088d_3_dais));
		if (ret)
			dev_err(priv->dev, "AMP soc_register_component 3 fail %d\n", ret);
	} else if (priv->type == 4) {			/* slave */
		ret = devm_snd_soc_register_component(&priv->client->dev,
						&amp_ad82088d_4_component,
						amp_ad82088d_4_dais,
						ARRAY_SIZE(amp_ad82088d_4_dais));
		if (ret)
			dev_err(priv->dev, "AMP soc_register_component 4 fail %d\n", ret);
	}
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
static int amp_ad82088d_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
#else
static int amp_ad82088d_i2c_probe(struct i2c_client *client)
#endif
{
	struct amp_ad82088d_priv *priv;
	struct device *dev = &client->dev;
	struct device_node *node;
	int ret;
	const char *name = NULL;

	node = dev->of_node;
	if (!node)
		return -ENODEV;

	if (of_device_is_available(node) == 0)
		return 0;

	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->dev = &client->dev;
	priv->client = client;

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
		return -ENODEV;

	i2c_set_clientdata(client, priv);

	/*
	 *	get type :
	 *	type = 0 : master
	 *	type = 1/2/3/4 : slave
	 *	if can't get type : means master
	 */
	ret = of_property_read_u32(node, "type", &priv->type);
	if (ret) {
		dev_info(&client->dev, "[AMP 82088d]can't get type\n");
		priv->type = 0;
	}

	/* GPIO : master get gpio handler */
	if (priv->type == 0) {
		priv->amp_powerdown = devm_gpiod_get(&client->dev, "amppowerdown", GPIOD_ASIS);
		if (IS_ERR(priv->amp_powerdown)) {
			dev_err(&client->dev, "Unable to retrieve gpio amp powerdown\n");
			return PTR_ERR(priv->amp_powerdown);
		}
		gpiod_direction_output(priv->amp_powerdown, 1);
	}

	priv->regmap = devm_regmap_init_i2c(client, &ad82088d_regmap);
	if (IS_ERR(priv->regmap))
		return PTR_ERR(priv->regmap);

	priv->cus_param = devm_kzalloc(dev, sizeof(struct cus_param), GFP_KERNEL);
	if (!priv->cus_param)
		return -ENOMEM;

	INIT_WORK(&priv->work, do_work);

	amp_register_component(priv);

	/* parse dts */
	ret = of_property_read_string(node, "mtk_ini", &name);
	if (ret)
		dev_err(&client->dev, "[AMP 82088d]can't get mtk ini path\n");

	priv->mtk_ini = name;
	name = NULL;
	ret = of_property_read_string(node, "cus_ini", &name);
	if (ret)
		dev_err(&client->dev, "[AMP 82088d]can't get cus ini path\n");

	priv->cus_ini = name;

	priv->volume = -1;
	priv->curr_vol_interval = -1;
	priv->curr_sound_mode = -1;

	/* Create device attribute files */
	ret = sysfs_create_groups(&priv->dev->kobj, amp_attr_groups);
	if (ret)
		dev_err(priv->dev, "Audio amp Sysfs init fail\n");

	return 0;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0)
static int amp_ad82088d_i2c_remove(struct i2c_client *client)
#else
static void amp_ad82088d_i2c_remove(struct i2c_client *client)
#endif
{
	struct amp_ad82088d_priv *priv = i2c_get_clientdata(client);

	if (priv->init_table)
		vfree(priv->init_table);
	cancel_work_sync(&priv->work);
	sysfs_remove_groups(&priv->dev->kobj, amp_attr_groups);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0)
	return 0;
#endif
}

void amp_ad82088d_i2c_shutdown(struct i2c_client *client)
{
	struct amp_ad82088d_priv *priv = i2c_get_clientdata(client);
	struct snd_soc_component *component = priv->component;

	amp_ad82088d_powerdown(component, 1);
	mdelay(35);
}

#ifdef CONFIG_PM
int amp_ad82088d_pm_suspend(struct device *dev)
{
	struct amp_ad82088d_priv *priv = dev_get_drvdata(dev);
	struct snd_soc_component *component = priv->component;

	if (priv->suspended)
		return 0;

	priv->curr_sound_mode = -1;

	//pull low amp PD pin
	amp_ad82088d_powerdown(component, 1);
	cancel_work_sync(&priv->work);
	priv->suspended = true;

	return 0;
}

int amp_ad82088d_pm_resume(struct device *dev)
{
	struct amp_ad82088d_priv *priv = dev_get_drvdata(dev);
	struct snd_soc_component *component = priv->component;

	if (!priv->suspended)
		return 0;

	amp_ad82088d_probe(component);

	priv->suspended = false;

	return 0;
}

static const struct dev_pm_ops amp_ad82088d_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(amp_ad82088d_pm_suspend,
			   amp_ad82088d_pm_resume)
};
#endif

static const struct of_device_id mtk_amp_dt_match[] = {
	{ .compatible = "ESMT, ad82088d", },
	{ }
};
MODULE_DEVICE_TABLE(of, mtk_amp_dt_match);

static const struct i2c_device_id ad82088d_i2c_id[] = {
	{ "esmt,amp", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, ad82088d_i2c_id);

static struct i2c_driver amp_ad82088d_i2c_driver = {
	.driver = {
		.owner	= THIS_MODULE,
		.name	= "esmt-amp82088d",
		.of_match_table	= of_match_ptr(mtk_amp_dt_match),
#ifdef CONFIG_PM
		.pm	= &amp_ad82088d_pm_ops,
#endif
	},
	.probe		= amp_ad82088d_i2c_probe,
	.shutdown	= amp_ad82088d_i2c_shutdown,
	.remove		= amp_ad82088d_i2c_remove,
	.id_table	= ad82088d_i2c_id,
};

module_i2c_driver(amp_ad82088d_i2c_driver);

MODULE_DESCRIPTION("ALSA SoC AMP ad82088d platform driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("AMP ad82088d driver");
