blob: 091d4a0bc8681367cbd9903f0fdfb42b197ae9de [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "audio_dsp: " fmt
#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_info("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 devie for " DSP_NAME "\n");
goto error1;
} else {
DSP_PRNT("register " DSP_NAME " to char divece(%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;
}
static int __init audiodsp_init_module(void)
{
return audiodsp_probe();
}
static 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;
}
module_init(audiodsp_init_module);
module_exit(audiodsp_exit_module);