// SPDX-License-Identifier: GPL-2.0
/*
 * mtk_amp_acm8625.c -- amp acm8625 driver
 *
 * Copyright (c) 2020 MediaTek Inc.
 */

#include <linux/module.h>
#include <linux/version.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 "mtk_amp_acm8625.h"
#include "mtk_ini_parser.h"

#define ACM8625_VOLUME_0DB	110

u8 AmpInitTblACM8625[] = {
//DataLen	Address	DataN	DataN+1...
	1,	0x00,	0x00,
	1,	0x04,	0x00,
	1,	0xfc,	0x86,
	1,	0xfd,	0x25,
	1,	0xfe,	0x15,
	1,	0x00,	0x01,
	1,	0x02,	0x20,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	0x00,
};

u8 AmpInitTblDSPcfg[] = {
//DataLen	Address	DataN	DataN+1...
	1,	0x00,	0x00,
	1,	0x04,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x0b,
	1,	0x5c,	0x80,
	1,	0x5d,	0x9e,
	1,	0x5e,	0x02,
	1,	0x5f,	0x9e,
	1,	0x60,	0x80,
	1,	0x61,	0x9e,
	1,	0x62,	0x03,
	1,	0x63,	0x9e,
	1,	0x00,	0x04,
	1,	0x88,	0x00,
	1,	0x89,	0xe2,
	1,	0x8a,	0xc4,
	1,	0x8b,	0x6b,
	1,	0x00,	0x01,
	1,	0x01,	0x00,
	1,	0x00,	0x00,
	1,	0x11,	0x03,
	1,	0x02,	0x00,
	1,	0x06,	0x30,
	1,	0x28,	0x00,
	1,	0x05,	0xc6,
	1,	0x03,	0x02,
	1,	0x01,	0x84,
	1,	0x00,	0x00,
	1,	0x04,	0x02,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x00,
	1,	0x00,	0x03,
	1,	0x13,	0x1d,
	1,	0x00,	0x00,
	1,	0x04,	0x03,
	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_acm8625_startup(struct snd_pcm_substream *substream,
				struct snd_soc_dai *dai)
{
	struct amp_acm8625_priv *priv = snd_soc_dai_get_drvdata(dai);

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

	return 0;
}

static const struct snd_soc_dai_ops amp_acm8625_dai_ops = {
	.startup	= amp_acm8625_startup,
};

static struct snd_soc_dai_driver amp_acm8625_dais[] = {
	{
		.name = "AMP_ACM8625",
		.id = 0,
		.playback = {
			.stream_name = "AMP ACM8625",
		},
		.ops = &amp_acm8625_dai_ops,
	},
};

static struct snd_soc_dai_driver amp_acm8625_1_dais[] = {
	{
		.name = "AMP_ACM8625_1",
		.id = 1,
		.playback = {
			.stream_name = "AMP ACM8625_1",
		},
	},
};

static struct snd_soc_dai_driver amp_acm8625_2_dais[] = {
	{
		.name = "AMP_ACM8625_2",
		.id = 2,
		.playback = {
			.stream_name = "AMP ACM8625_2",
		},
	},
};

static struct snd_soc_dai_driver amp_acm8625_3_dais[] = {
	{
		.name = "AMP_ACM8625_3",
		.id = 3,
		.playback = {
			.stream_name = "AMP ACM8625_3",
		},
	},
};

static struct snd_soc_dai_driver amp_acm8625_4_dais[] = {
	{
		.name = "AMP_ACM8625_4",
		.id = 4,
		.playback = {
			.stream_name = "AMP ACM8625_4",
		},
	},
};

static int amp_write(struct amp_acm8625_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_acm8625_powerdown(struct snd_soc_component *component, bool bEnable)
{
	struct amp_acm8625_priv *priv = snd_soc_component_get_drvdata(component);

	if (priv->amp_powerdown)
		gpiod_set_value(priv->amp_powerdown, bEnable);

	return 0;
}

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

	if (bEnable)
		regmap_write(priv->regmap, 0x4, 0x0c);
	else
		regmap_write(priv->regmap, 0x4, 0x03);

	return 0;
}

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

	ret = amp_write(priv, InitTable);
	if (ret < 0) {
		dev_err(priv->dev, "[AMP acm8625]Init fail\n");
		priv->initialized = 0;
	} else {
		dev_info(priv->dev, "[AMP acm8625]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"
		       );
}

static ssize_t amp_status_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct amp_acm8625_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_acm8625_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_acm8625_mute(component, mute_flag);

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

static DEVICE_ATTR_RO(help);
static DEVICE_ATTR_RO(amp_status);
static DEVICE_ATTR_WO(amp_mute);

static struct attribute *amp_attrs[] = {
	&dev_attr_help.attr,
	&dev_attr_amp_status.attr,
	&dev_attr_amp_mute.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 acm8625_refresh(struct amp_acm8625_priv *priv)
{
	/* clear digital soft-mute */
	regmap_write(priv->regmap, 0x4, 0x3);
}

static int amp_acm8625_probe(struct snd_soc_component *component)
{
	struct amp_acm8625_priv *priv =
				snd_soc_component_get_drvdata(component);
	int ret = 0;
	int retry_time = 5;
	const struct firmware *fw = NULL;

	if (priv->suspended != true) {
		if (priv->mtk_ini && (priv->init_table == NULL) && (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->type == 0) {
		amp_acm8625_powerdown(component, 0);
		mtk_amp_delaytask_us(2*1000);
	}

	do {
		ret = amp_acm8625_init(priv, AmpInitTblACM8625);
		usleep_range(5000, 15000);
		if (priv->init_table)
			ret = amp_acm8625_init(priv, priv->init_table);
		else
			ret = amp_acm8625_init(priv, AmpInitTblDSPcfg);
		retry_time -= 1;
	} while ((ret < 0) && retry_time);

	acm8625_refresh(priv);

	priv->component = component;

	return 0;
}

static void amp_acm8625_remove(struct snd_soc_component *component)
{
	amp_acm8625_powerdown(component, 1);
}

static int amp_acm8625_mute_put(struct snd_kcontrol *kcontrol,
				struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component =
				snd_soc_kcontrol_component(kcontrol);
	struct amp_acm8625_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_acm8625_mute(component, val);

	return 0;
}

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

	ret = regmap_read(priv->regmap, 0x04, &val);
	if (ret < 0)
		dev_err(priv->dev, "read amp vol fail\n");

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

	if (val & 0x0c)
		val = 1;
	else
		val = 0;

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

	return 0;
}

static const struct snd_kcontrol_new amp_acm8625_controls[] = {
	SOC_SINGLE_BOOL_EXT("AMP Mute", 0, amp_acm8625_mute_get,
				amp_acm8625_mute_put),
};

const struct snd_soc_component_driver amp_acm8625_component = {
	.name		= "amp-acm8625-platform",
	.probe		= amp_acm8625_probe,
	.remove		= amp_acm8625_remove,
	.controls	= amp_acm8625_controls,
	.num_controls	= ARRAY_SIZE(amp_acm8625_controls),
};

const struct snd_soc_component_driver amp_acm8625_1_component = {
	.name		= "amp-acm8625-1",
	.probe		= amp_acm8625_probe,
	.remove		= amp_acm8625_remove,
};

const struct snd_soc_component_driver amp_acm8625_2_component = {
	.name		= "amp-acm8625-2",
	.probe		= amp_acm8625_probe,
	.remove		= amp_acm8625_remove,
};

const struct snd_soc_component_driver amp_acm8625_3_component = {
	.name		= "amp-acm8625-3",
	.probe		= amp_acm8625_probe,
	.remove		= amp_acm8625_remove,
};

const struct snd_soc_component_driver amp_acm8625_4_component = {
	.name		= "amp-acm8625-4",
	.probe		= amp_acm8625_probe,
	.remove		= amp_acm8625_remove,
};

static const struct regmap_config acm8625_regmap = {
	.reg_bits = 8,
	.val_bits = 8,
	.max_register = 0x7f,
};

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

	if (priv->type == 0) {				/* master */
		ret = devm_snd_soc_register_component(&priv->client->dev,
						&amp_acm8625_component,
						amp_acm8625_dais,
						ARRAY_SIZE(amp_acm8625_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_acm8625_1_component,
						amp_acm8625_1_dais,
						ARRAY_SIZE(amp_acm8625_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_acm8625_2_component,
						amp_acm8625_2_dais,
						ARRAY_SIZE(amp_acm8625_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_acm8625_3_component,
						amp_acm8625_3_dais,
						ARRAY_SIZE(amp_acm8625_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_acm8625_4_component,
						amp_acm8625_4_dais,
						ARRAY_SIZE(amp_acm8625_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_acm8625_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
#else
static int amp_acm8625_i2c_probe(struct i2c_client *client)
#endif
{
	struct amp_acm8625_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 acm8625]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_OUT_LOW);
		if (IS_ERR(priv->amp_powerdown)) {
			dev_err(&client->dev, "Unable to retrieve gpio amp powerdown\n");
			return PTR_ERR(priv->amp_powerdown);
		}
	}

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

	amp_register_component(priv);

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

	priv->mtk_ini = name;

	ret = sysfs_create_groups(&priv->dev->kobj, amp_attr_groups);
	if (ret)
		dev_err(&client->dev, "Audio amp Sysfs init fail\n");

	return 0;
}

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

	if (priv->init_table)
		vfree(priv->init_table);

	sysfs_remove_groups(&priv->dev->kobj, amp_attr_groups);

#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0)
	return 0;
#endif
}

void amp_acm8625_i2c_shutdown(struct i2c_client *client)
{
	struct amp_acm8625_priv *priv = i2c_get_clientdata(client);

	if (priv->amp_powerdown)
		gpiod_set_value(priv->amp_powerdown, 1);
}

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

	if (priv->suspended)
		return 0;

	amp_acm8625_mute(component, 1);
	amp_acm8625_powerdown(component, 1);

	priv->suspended = true;

	return 0;
}

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

	if (!priv->suspended)
		return 0;

	amp_acm8625_probe(component);

	priv->suspended = false;

	return 0;
}

static const struct dev_pm_ops amp_acm8625_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(amp_acm8625_pm_suspend,
			   amp_acm8625_pm_resume)
};
#endif

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

static const struct i2c_device_id acm8625_i2c_id[] = {
	{ "acme, acm8625", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, acm8625_i2c_id);

static struct i2c_driver amp_acm8625_i2c_driver = {
	.driver = {
		.owner	= THIS_MODULE,
		.name	= "acme-acm8625",
		.of_match_table	= of_match_ptr(mtk_amp_dt_match),
#ifdef CONFIG_PM
		.pm	= &amp_acm8625_pm_ops,
#endif
	},
	.probe		= amp_acm8625_i2c_probe,
	.shutdown	= amp_acm8625_i2c_shutdown,
	.remove		= amp_acm8625_i2c_remove,
	.id_table	= acm8625_i2c_id,
};

module_i2c_driver(amp_acm8625_i2c_driver);

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