// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <asm/cacheflush.h>
#include <linux/amlogic/major.h>
#include <linux/slab.h>
#include <linux/reset.h>
#include <linux/uaccess.h>
#include <linux/amlogic/media/utils/amstream.h>
#include "audiodsp_module.h"
#include <linux/dma-mapping.h>
#include <linux/amlogic/media/frame_sync/ptsserv.h>
#include <linux/amlogic/media/frame_sync/timestamp.h>
#include <linux/amlogic/media/frame_sync/tsync.h>

unsigned int dsp_debug_flag = 1;

MODULE_DESCRIPTION("AMLOGIC APOLLO Audio dsp driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zhou Zhi <zhi.zhou@amlogic.com>");
MODULE_VERSION("1.0.0");
/* static int IEC958_mode_raw_last; */
/* static int IEC958_mode_codec_last; */
static int decopt  = 1;
/* code for DD/DD+ DRC control  */
/* Dynamic range compression mode */
enum DDP_DEC_DRC_MODE {
	GBL_COMP_CUSTOM_0 = 0,	/* custom mode, analog  dialnorm */
	GBL_COMP_CUSTOM_1,	/* custom mode, digital dialnorm */
	GBL_COMP_LINE,		/* line out mode (default)       */
	GBL_COMP_RF		/* RF mode                       */
};

#define DRC_MODE_BIT  0
#define DRC_HIGH_CUT_BIT 3
#define DRC_LOW_BST_BIT 16
static unsigned int ac3_drc_control =
(GBL_COMP_LINE << DRC_MODE_BIT) |
(100 << DRC_HIGH_CUT_BIT) | (100 << DRC_LOW_BST_BIT);
/* code for DTS dial norm/downmix mode control */
enum DTS_DMX_MODE {
	DTS_DMX_LORO = 0,
	DTS_DMX_LTRT,
};

#define DTS_DMX_MODE_BIT  0
#define DTS_DIAL_NORM_BIT  1
#define DTS_DRC_SCALE_BIT  2
static unsigned int dts_dec_control =
(DTS_DMX_LORO << DTS_DMX_MODE_BIT) |
(1 << DTS_DIAL_NORM_BIT) | (0 << DTS_DRC_SCALE_BIT);

static struct audiodsp_priv *audiodsp_p;
#define  DSP_DRIVER_NAME	"audiodsp"
#define  DSP_NAME	"dsp"

#ifdef CONFIG_PM
struct audiodsp_pm_state_t {
	int event;
	/*  */
};

/* static struct audiodsp_pm_state_t pm_state; */

#endif
static ssize_t codec_fmt_show(struct class *cla, struct class_attribute *attr,
			      char *buf)
{
	size_t ret = 0;
	struct audiodsp_priv *priv = audiodsp_privdata();

	ret = sprintf(buf, "The codec Format %d\n", priv->stream_fmt);
	return ret;
}

static const struct file_operations audiodsp_fops = {
.owner = THIS_MODULE,
.open = NULL,
.read = NULL,
.write = NULL,
.release = NULL,
.unlocked_ioctl = NULL,
};

static ssize_t codec_fatal_err_show(struct class *cla,
				    struct class_attribute *attr, char *buf)
{
	struct audiodsp_priv *priv = audiodsp_privdata();

	return sprintf(buf, "%d\n", priv->decode_fatal_err);
}

static ssize_t codec_fatal_err_store(struct class *cla,
				     struct class_attribute *attr,
				     const char *buf, size_t count)
{
	struct audiodsp_priv *priv = audiodsp_privdata();

	if (buf[0] == '0')
		priv->decode_fatal_err = 0;
	else if (buf[0] == '1')
		priv->decode_fatal_err = 1;
	else if (buf[0] == '2')
		priv->decode_fatal_err = 2;

	pr_debug("codec_fatal_err value:%d\n ", priv->decode_fatal_err);
	return count;
}

static ssize_t digital_raw_show(struct class *cla, struct class_attribute *attr,
				char *buf)
{
	char *pbuf = buf;

	pbuf += sprintf(pbuf, "%d\n", IEC958_mode_raw);
	return pbuf - buf;
}

static ssize_t digital_raw_store(struct class *class,
				 struct class_attribute *attr, const char *buf,
				 size_t count)
{
	pr_info("buf=%s\n", buf);
	if (buf[0] == '0')
		IEC958_mode_raw = 0;	/* PCM */
	else if (buf[0] == '1')
		IEC958_mode_raw = 1;	/* RAW without over clock */
	else if (buf[0] == '2')
		IEC958_mode_raw = 2;	/* RAW with over clock */

	pr_info("IEC958_mode_raw=%d\n", IEC958_mode_raw);
	return count;
}

#define SUPPORT_TYPE_NUM  10
static unsigned char *codec_str[SUPPORT_TYPE_NUM] = {
	"2 CH PCM", "DTS RAW Mode", "Dolby Digital", "DTS",
	"DD+", "DTS-HD", "8 CH PCM", "TrueHD", "DTS-HD MA",
	"HIGH_SR_Stereo_PCM"
};

static ssize_t digital_codec_show(struct class *cla,
				  struct class_attribute *attr, char *buf)
{
	char *pbuf = buf;

	pbuf += sprintf(pbuf, "%d\n", IEC958_mode_codec);
	return pbuf - buf;
}

static ssize_t digital_codec_store(struct class *class,
				   struct class_attribute *attr,
				   const char *buf, size_t count)
{
	unsigned int digital_codec = 0;
	unsigned int mode_codec = IEC958_mode_codec;

	if (buf) {
		if (kstrtoint(buf, 10, &digital_codec))
			pr_info("kstrtoint err %s\n", __func__);
		if (digital_codec < SUPPORT_TYPE_NUM) {
			IEC958_mode_codec = digital_codec;
			pr_info("IEC958_mode_codec= %d, IEC958 type %s\n",
				digital_codec, codec_str[digital_codec]);
		} else {
			pr_info("IEC958 type set exceed supported range\n");
		}
	}
	/*
	 * when raw output switch to pcm output,
	 * need trigger pcm hw prepare to re-init hw configuration
	 */
	pr_info("last mode %d,now %d\n", mode_codec, IEC958_mode_codec);

	return count;
}

static ssize_t print_flag_show(struct class *cla,
			       struct class_attribute *attr, char *buf)
{
	static const char * const dec_format[] = {
		"0 - disable arc dsp print",
		"1 - enable arc dsp print",
	};
	char *pbuf = buf;

	pbuf +=
	    sprintf(pbuf, "audiodsp decode option: %s\n",
		    dec_format[(decopt & 0x5) >> 2]);
	return pbuf - buf;
}

static ssize_t print_flag_store(struct class *class,
				struct class_attribute *attr, const char *buf,
				size_t count)
{
	unsigned int dec_opt = 0x1;

	pr_info("buf=%s\n", buf);
	if (buf[0] == '0')
		dec_opt = 0;	/* disable print flag */
	else if (buf[0] == '1')
		dec_opt = 1;	/* enable print flag */

	decopt = (decopt & (~4)) | (dec_opt << 2);
	pr_info("dec option=%d, decopt = %x\n", dec_opt, decopt);
	return count;
}

static ssize_t dec_option_show(struct class *cla, struct class_attribute *attr,
			       char *buf)
{
	static const char * const dec_format[] = {
		"0 - mute dts and ac3 ",
		"1 - mute dts.ac3 with noise ",
		"2 - mute ac3.dts with noise",
		"3 - both ac3 and dts with noise",
	};
	char *pbuf = buf;

	pbuf +=
	    sprintf(pbuf, "audiodsp decode option: %s\n",
		    dec_format[decopt & 0x3]);
	return pbuf - buf;
}

static ssize_t dec_option_store(struct class *class,
				struct class_attribute *attr, const char *buf,
				size_t count)
{
	unsigned int dec_opt = 0x3;

	pr_info("buf=%s\n", buf);
	if (buf[0] == '0') {
		dec_opt = 0;	/* mute ac3/dts */
	} else if (buf[0] == '1') {
		dec_opt = 1;	/* mute dts */
	} else if (buf[0] == '2') {
		dec_opt = 2;	/* mute ac3 */
	} else if (buf[0] == '3') {
		dec_opt = 3;	/* with noise */
	} else if (buf[0] == '4') {
		pr_info("digital mode :PCM-raw\n");
		decopt |= (1 << 5);
	} else if (buf[0] == '5') {
		pr_info("digital mode :raw\n");
		decopt &= ~(1 << 5);
	}
	decopt = (decopt & (~3)) | dec_opt;
	pr_info("dec option=%d\n", dec_opt);
	return count;
}

static ssize_t ac3_drc_control_show(struct class *cla,
				    struct class_attribute *attr, char *buf)
{
	static const char * const drcmode[] = {
		"CUSTOM_0", "CUSTOM_1", "LINE", "RF" };
	char *pbuf = buf;

	pr_info("\tdd+ drc mode : %s\n", drcmode[ac3_drc_control & 0x3]);
	pr_info("\tdd+ drc high cut scale : %d%%\n",
		(ac3_drc_control >> DRC_HIGH_CUT_BIT) & 0xff);
	pr_info("\tdd+ drc low boost scale : %d%%\n",
		(ac3_drc_control >> DRC_LOW_BST_BIT) & 0xff);
	pbuf += sprintf(pbuf, "%d\n", ac3_drc_control);
	return pbuf - buf;
}

static ssize_t ac3_drc_control_store(struct class *class,
				     struct class_attribute *attr,
				     const char *buf, size_t count)
{
	char tmpbuf[128];
	static const char * const drcmode[] = {
		"CUSTOM_0", "CUSTOM_1", "LINE", "RF" };
	int i = 0;
	unsigned int val;

	while ((buf[i]) && (buf[i] != ',') && (buf[i] != ' ')) {
		tmpbuf[i] = buf[i];
		i++;
	}
	tmpbuf[i] = 0;
	if (strncmp(tmpbuf, "drcmode", 7) == 0) {
		if (kstrtoint(buf + i + 1, 16, &val))
			pr_info("kstrtoint err %s\n", __func__);
		val = val & 0x3;
		pr_info("drc mode set to %s\n", drcmode[val]);
		ac3_drc_control = (ac3_drc_control & (~3)) | val;
	} else if (strncmp(tmpbuf, "drchighcutscale", 15) == 0) {
		if (kstrtoint(buf + i + 1, 16, &val))
			pr_info("kstrtoint err %s\n", __func__);
		val = val & 0xff;
		pr_info("drc high cut scale set to %d%%\n", val);
		ac3_drc_control =
		    (ac3_drc_control & (~(0xff << DRC_HIGH_CUT_BIT))) |
		    (val << DRC_HIGH_CUT_BIT);
	} else if (strncmp(tmpbuf, "drclowboostscale", 16) == 0) {
		if (kstrtoint(buf + i + 1, 16, &val))
			pr_info("kstrtoint err %s\n", __func__);
		val = val & 0xff;
		pr_info("drc low boost scale set to %d%%\n", val);
		ac3_drc_control =
		    (ac3_drc_control & (~(0xff << DRC_LOW_BST_BIT))) |
		    (val << DRC_LOW_BST_BIT);
	} else {
		pr_info("invalid args\n");
	}
	return count;
}

static ssize_t dts_dec_control_show(struct class *cla,
				    struct class_attribute *attr, char *buf)
{
	/* char *dmxmode[] = {"Lo/Ro","Lt/Rt"}; */
	/* char *dialnorm[] = {"disable","enable"}; */
	char *pbuf = buf;

	pbuf += sprintf(pbuf, "%d\n", dts_dec_control);
	return pbuf - buf;
}

static ssize_t dts_dec_control_store(struct class *class,
				     struct class_attribute *attr,
				     const char *buf, size_t count)
{
	char tmpbuf[128];
	static const char * const dmxmode[] = { "Lo/Ro", "Lt/Rt" };
	static const char * const dialnorm[] = { "disable", "enable" };
	int i = 0;
	unsigned int val;

	while ((buf[i]) && (buf[i] != ',') && (buf[i] != ' ')) {
		tmpbuf[i] = buf[i];
		i++;
	}
	tmpbuf[i] = 0;
	if (strncmp(tmpbuf, "dtsdmxmode", 10) == 0) {
		if (kstrtoint(buf + i + 1, 16, &val))
			pr_info("kstrtoint err %s\n", __func__);
		val = val & 0x1;
		pr_info("dts dmx mode set to %s\n", dmxmode[val]);
		dts_dec_control = (dts_dec_control & (~1)) | val;
	} else if (strncmp(tmpbuf, "dtsdrcscale", 11) == 0) {
		if (kstrtoint(buf + i + 1, 16, &val))
			pr_info("kstrtoint err %s\n", __func__);
		val = val & 0xff;
		pr_info("dts drc  scale set to %d\n", val);
		dts_dec_control =
		    (dts_dec_control & (~(0xff << DTS_DRC_SCALE_BIT))) |
		    (val << DTS_DRC_SCALE_BIT);
	} else if (strncmp(tmpbuf, "dtsdialnorm", 11) == 0) {
		if (kstrtoint(buf + i + 1, 16, &val))
			pr_info("kstrtoint err %s\n", __func__);
		val = val & 0x1;
		pr_info("dts  dial norm : set to %s\n", dialnorm[val]);
		dts_dec_control =
		    (dts_dec_control & (~(0x1 << DTS_DIAL_NORM_BIT))) |
		    (val << DTS_DIAL_NORM_BIT);
	} else {
		if (kstrtoint(tmpbuf, 16, &dts_dec_control))
			pr_info("kstrtoint err %s\n", __func__);
		pr_info("dts_dec_control/0x%x\n", dts_dec_control);
	}
	return count;
}

static ssize_t dsp_debug_show(struct class *cla, struct class_attribute *attr,
			      char *buf)
{
	char *pbuf = buf;

	pbuf +=
	    sprintf(pbuf, "DSP Debug Flag: %s\n",
		    (dsp_debug_flag == 0) ? "0 - OFF" : "1 - ON");
	return pbuf - buf;
}

static ssize_t dsp_debug_store(struct class *class,
			       struct class_attribute *attr, const char *buf,
			       size_t count)
{
	if (buf[0] == '0')
		dsp_debug_flag = 0;
	else if (buf[0] == '1')
		dsp_debug_flag = 1;

	pr_info("DSP Debug Flag: %d\n", dsp_debug_flag);
	return count;
}

static struct class_attribute audiodsp_attrs[] = {
	__ATTR_RO(codec_fmt),
	__ATTR(codec_fatal_err, 0664,
	       codec_fatal_err_show, codec_fatal_err_store),
	/* __ATTR_RO(swap_buf_ptr), */
	/* __ATTR_RO(dsp_working_status), */
	__ATTR(digital_raw, 0664, digital_raw_show,
	       digital_raw_store),
	__ATTR(digital_codec, 0664, digital_codec_show,
	       digital_codec_store),
	__ATTR(dec_option, 0644, dec_option_show,
	       dec_option_store),
	__ATTR(print_flag, 0644, print_flag_show,
	       print_flag_store),
	__ATTR(ac3_drc_control, 0664,
	       ac3_drc_control_show, ac3_drc_control_store),
	__ATTR(dsp_debug, 0644, dsp_debug_show, dsp_debug_store),
	__ATTR(dts_dec_control, 0644, dts_dec_control_show,
	       dts_dec_control_store),
	/* __ATTR(skip_rawbytes, 0644, skip_rawbytes_show, */
	/*       skip_rawbytes_store), */
	/* __ATTR_RO(pcm_left_len),   */
	__ATTR_NULL
};

static struct attribute *audiodsp_class_attrs[] = {
	&audiodsp_attrs[0].attr,
	&audiodsp_attrs[1].attr,
	&audiodsp_attrs[2].attr,
	&audiodsp_attrs[3].attr,
	&audiodsp_attrs[4].attr,
	&audiodsp_attrs[5].attr,
	&audiodsp_attrs[6].attr,
	&audiodsp_attrs[7].attr,
	&audiodsp_attrs[8].attr,
	NULL
};
ATTRIBUTE_GROUPS(audiodsp_class);

static struct class audiodsp_class = {
	.name = DSP_DRIVER_NAME,
	.owner = THIS_MODULE,
	.class_groups = audiodsp_class_groups,
//	.class_attrs = audiodsp_attrs,
};

int audiodsp_probe(void)
{
	int res = 0;
	struct audiodsp_priv *priv;

	dsp_debug_flag = 1;
	priv = kmalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		DSP_PRNT("Out of memory for audiodsp register\n");
		return -1;
	}
	memset(priv, 0, sizeof(struct audiodsp_priv));
	priv->dsp_is_started = 0;
	/*
	 *  priv->p = ioremap_nocache(AUDIO_DSP_START_PHY_ADDR, S_1M);
	 *  if(priv->p)
	 *  DSP_PRNT("DSP IOREMAP to addr 0x%x\n",(unsigned)priv->p);
	 *  else{
	 *  DSP_PRNT("DSP IOREMAP error\n");
	 *  goto error1;
	 *  }
	 */
	audiodsp_p = priv;
	if (priv->dsp_heap_size) {
		if (priv->dsp_heap_start == 0)
			priv->dsp_heap_start =
			    (unsigned long)kmalloc(priv->dsp_heap_size,
						   GFP_KERNEL);
		if (priv->dsp_heap_start == 0) {
			DSP_PRNT("no memory for audio dsp dsp_set_heap\n");
			kfree(priv);
			return -ENOMEM;
		}
	}
	res = register_chrdev(AUDIODSP_MAJOR, DSP_NAME, &audiodsp_fops);
	if (res < 0) {
		DSP_PRNT("Can't register  char device for " DSP_NAME "\n");
		goto error1;
	} else {
		DSP_PRNT("register " DSP_NAME " to char device(%d)\n",
			 AUDIODSP_MAJOR);
	}

	res = class_register(&audiodsp_class);
	if (res < 0) {
		DSP_PRNT("Create audiodsp class failed\n");
		res = -EEXIST;
		goto error2;
	}

	priv->class = &audiodsp_class;
	priv->dev = device_create(priv->class,
				  NULL, MKDEV(AUDIODSP_MAJOR, 0),
				  NULL, "audiodsp0");
	if (!priv->dev) {
		res = -EEXIST;
		goto error3;
	}
#ifdef CONFIG_AM_STREAMING
	/* set_adec_func(audiodsp_get_status); */
#endif
	return res;

	device_destroy(priv->class, MKDEV(AUDIODSP_MAJOR, 0));
 error3:
	class_destroy(priv->class);
 error2:
	unregister_chrdev(AUDIODSP_MAJOR, DSP_NAME);
 error1:
	kfree(priv);
	return res;
}

struct audiodsp_priv *audiodsp_privdata(void)
{
	return audiodsp_p;
}

int __init audiodsp_init_module(void)
{
	return audiodsp_probe();
}

void __exit audiodsp_exit_module(void)
{
	struct audiodsp_priv *priv;

	priv = audiodsp_privdata();
#ifdef CONFIG_AM_STREAMING
	set_adec_func(NULL);
#endif
	/*
	 *  iounmap(priv->p);
	 */
	device_destroy(priv->class, MKDEV(AUDIODSP_MAJOR, 0));
	class_destroy(priv->class);
	unregister_chrdev(AUDIODSP_MAJOR, DSP_NAME);
	kfree(priv);
	priv = NULL;
}
