blob: 100173fa67e5e2b2a4b10494a0f9c2feb8ed6db7 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/fcntl.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/of.h>
#include <linux/dvb/dmx.h>
#include <linux/amlogic/tee.h>
#include <linux/amlogic/aml_key.h>
#include <linux/amlogic/tee_demux.h>
#include "aml_dvb.h"
#include "am_key.h"
#include "dmx_log.h"
#include "sc2_demux/ts_output.h"
#include "sc2_demux/frontend.h"
#include "sc2_demux/dvb_reg.h"
#define dprint_i(fmt, args...) \
dprintk(LOG_ERROR, debug_dvb, fmt, ## args)
#define dprint(fmt, args...) \
dprintk(LOG_ERROR, debug_dvb, fmt, ## args)
#define pr_dbg(fmt, args...) \
dprintk(LOG_DBG, debug_dvb, "dvb:" fmt, ## args)
MODULE_PARM_DESC(debug_dvb, "\n\t\t Enable demux debug information");
static int debug_dvb = 1;
module_param(debug_dvb, int, 0644);
#define CARD_NAME "amlogic-dvb"
#define DVB_VERSION "V2.02"
DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
static struct aml_dvb aml_dvb_device;
static int dmx_dev_num;
static int tsn_in;
static int tsn_out;
static int print_stc;
static int print_dmx;
#define MAX_DMX_DEV_NUM 32
static int sid_info[MAX_DMX_DEV_NUM];
#define DEFAULT_DMX_DEV_NUM 3
int is_security_dmx;
ssize_t get_pcr_show(struct class *class,
struct class_attribute *attr, char *buf)
{
struct aml_dvb *dvb = aml_get_dvb_device();
int r, total = 0;
int i;
u64 value;
unsigned int base;
int ret = 0;
for (i = 0; i < 4; i++) {
value = 0;
base = 0;
if (print_stc) {
ret = dmx_get_stc(&dvb->dmx[print_dmx].dmx, i,
&value, &base);
if (ret != 0)
continue;
r = sprintf(buf, "dmx:%d num%d stc:0x%llx base:%d\n",
print_dmx, i, value, base);
} else {
ret = dmx_get_pcr(&dvb->dmx[print_dmx].dmx, i, &value);
if (ret != 0)
continue;
r = sprintf(buf, "dmx:%d num%d pcr:0x%llx\n",
print_dmx, i, value);
}
buf += r;
total += r;
}
return total;
}
ssize_t get_pcr_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
if (!strncmp(buf, "dmx", 3))
print_dmx = buf[4] - 0x30;
if (!strncmp(&buf[6], "stc", 3))
print_stc = buf[10] - 0x30;
dprint("dmx=%d stc=%d\n", print_dmx, print_stc);
return count;
}
ssize_t dmx_setting_show(struct class *class, struct class_attribute *attr,
char *buf)
{
int r, total = 0;
int i;
struct aml_dvb *dvb = aml_get_dvb_device();
for (i = 0; i < dmx_dev_num; i++) {
r = sprintf(buf, "dmx%d source:%s ", i,
dvb->dmx[i].source == INPUT_DEMOD ? "input_demod" :
(dvb->dmx[i].source == INPUT_LOCAL ?
"input_local" : "input_local_sec"));
buf += r;
total += r;
r = sprintf(buf, "demod_sid:0x%0x local_sid:0x%0x\n",
dvb->dmx[i].demod_sid, dvb->dmx[i].local_sid);
buf += r;
total += r;
}
return total;
}
ssize_t dsc_setting_show(struct class *class, struct class_attribute *attr,
char *buf)
{
int total = 0;
total = dsc_dump_info(buf);
return total;
}
int demux_get_stc(int demux_device_index, int index,
u64 *stc, unsigned int *base)
{
struct aml_dvb *dvb = aml_get_dvb_device();
if (demux_device_index >= DMX_DEV_COUNT)
return -1;
dmx_get_stc(&dvb->dmx[demux_device_index].dmx, index, stc, base);
return 0;
}
EXPORT_SYMBOL(demux_get_stc);
int demux_get_pcr(int demux_device_index, int index, u64 *pcr)
{
struct aml_dvb *dvb = aml_get_dvb_device();
if (demux_device_index >= DMX_DEV_COUNT)
return -1;
return dmx_get_pcr(&dvb->dmx[demux_device_index].dmx, index, pcr);
}
EXPORT_SYMBOL(demux_get_pcr);
ssize_t tsn_source_show(struct class *class,
struct class_attribute *attr, char *buf)
{
int r, total = 0;
u32 value = 0;
int ret = 0;
if (tsn_in == INPUT_DEMOD)
r = sprintf(buf, "tsn_source:demod\n");
else if (tsn_in == INPUT_LOCAL)
r = sprintf(buf, "tsn_source:local\n");
else if (tsn_in == INPUT_LOCAL_SEC)
r = sprintf(buf, "tsn_source:local_sec\n");
else
return 0;
buf += r;
total += r;
ret = tee_read_reg_bits(0xff610320, &value, 0, 2);
dprint("tee_read_reg_bits value:%d, ret:%d\n", value, ret);
return total;
}
ssize_t tsn_source_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
struct aml_dvb *advb = aml_get_dvb_device();
int tsn_in_reg = 0;
if (!strncmp(buf, "demod", 5))
tsn_in = INPUT_DEMOD;
else if (!strncmp(buf, "local", 5))
tsn_in = INPUT_LOCAL;
else if (!strncmp(buf, "local_sec", 9))
tsn_in = INPUT_LOCAL_SEC;
else
return count;
if (mutex_lock_interruptible(&advb->mutex))
return -ERESTARTSYS;
if (tsn_in == INPUT_DEMOD)
tsn_in_reg = 1;
// pr_dbg("tsn_in:%d, tsn_out:%d\n", tsn_in_reg, tsn_out);
advb->dsc_pipeline = tsn_in_reg;
//set demod/local
if (get_chip_type() == 1) {
u32 value = 0;
int ret = 0;
value = tsn_in_reg;
value += tsn_out << 1;
ret = tee_write_reg_bits(0xff610320, value, 0, 2);
dprint("tee_write_reg_bits value:%d, ret:%d\n", value, ret);
} else {
tee_demux_config_pipeline(tsn_in_reg, tsn_out);
}
mutex_unlock(&advb->mutex);
return count;
}
int tsn_set_double_out(int flag)
{
int tsn_in_reg = 0;
if (tsn_out == flag)
return 0;
tsn_out = flag;
if (tsn_in == INPUT_DEMOD)
tsn_in_reg = 1;
tee_demux_config_pipeline(tsn_in_reg, tsn_out);
return 0;
}
ssize_t tso_source_show(struct class *class,
struct class_attribute *attr, char *buf)
{
int r, total = 0;
r = sprintf(buf, "tso_source:ts1\n");
buf += r;
total += r;
return total;
}
ssize_t tso_source_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
struct aml_dvb *advb = aml_get_dvb_device();
int src = -1;
if (mutex_lock_interruptible(&advb->mutex))
return -ERESTARTSYS;
if (!strncmp("ts0", buf, 3))
src = 0;
else if (!strncmp("ts1", buf, 3))
src = 1;
else if (!strncmp("ts2", buf, 3))
src = 2;
else if (!strncmp("ts3", buf, 3))
src = 3;
tso_set(src);
mutex_unlock(&advb->mutex);
return count;
}
ssize_t tsn_loop_show(struct class *class,
struct class_attribute *attr, char *buf)
{
struct aml_dvb *advb = aml_get_dvb_device();
int r, total = 0;
u32 val = 0;
int ret = 0;
ret = tee_read_reg_bits(0xfe440320, &val, 2, 1);
r = sprintf(buf, "loop:%d, reg val:%d, ret:%d\n", advb->loop_tsn,
val, ret);
buf += r;
total += r;
return total;
}
ssize_t tsn_loop_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
struct aml_dvb *advb = aml_get_dvb_device();
long val = 0;
int loop = 0;
if (kstrtol(buf, 0, &val) == 0) {
loop = (int)val;
if (loop != 1 && loop != 0) {
dprint("tsn err loop:%d\n", loop);
return count;
}
dprint("tsn loop :0x%0x\n", loop);
} else {
dprint("%s parameter fail\n", buf);
return count;
}
if (mutex_lock_interruptible(&advb->mutex))
return -ERESTARTSYS;
set_dvb_loop_tsn(loop);
mutex_unlock(&advb->mutex);
return count;
}
ssize_t dmc_mem_show(struct class *class,
struct class_attribute *attr, char *buf)
{
return dmc_mem_dump_info(buf);
}
ssize_t dmc_mem_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
struct aml_dvb *advb = aml_get_dvb_device();
int sec_level = 0;
unsigned int size = 0;
dprint("input: %s\n", buf);
if (sscanf(buf, "%d %d\n", &sec_level, &size) == 2) {
dprint("sec_level :0x%0x\n", sec_level);
dprint("size :0x%0x\n", size);
} else {
dprint("%s parameter fail, like sec_level(1~7) mem_size\n",
buf);
return count;
}
if (mutex_lock_interruptible(&advb->mutex))
return -ERESTARTSYS;
dmc_mem_set_size(sec_level, size);
mutex_unlock(&advb->mutex);
return count;
}
static CLASS_ATTR_RW(ts_setting);
static CLASS_ATTR_RW(get_pcr);
static CLASS_ATTR_RO(dmx_setting);
static CLASS_ATTR_RO(dsc_setting);
static CLASS_ATTR_RW(tsn_source);
static CLASS_ATTR_RW(tso_source);
static CLASS_ATTR_RW(tsn_loop);
static CLASS_ATTR_RW(dmc_mem);
static struct attribute *aml_stb_class_attrs[] = {
&class_attr_ts_setting.attr,
&class_attr_get_pcr.attr,
&class_attr_dmx_setting.attr,
&class_attr_dsc_setting.attr,
&class_attr_tsn_source.attr,
&class_attr_tso_source.attr,
&class_attr_tsn_loop.attr,
&class_attr_dmc_mem.attr,
NULL
};
ATTRIBUTE_GROUPS(aml_stb_class);
static struct class aml_stb_class = {
.name = "stb",
.class_groups = aml_stb_class_groups,
};
int dmx_get_dev_num(struct platform_device *pdev)
{
char buf[32];
u32 dmxdev = 0;
int ret = 0;
memset(buf, 0, 32);
snprintf(buf, sizeof(buf), "dmxdev_num");
ret = of_property_read_u32(pdev->dev.of_node, buf, &dmxdev);
if (!ret)
dprint("%s: 0x%x\n", buf, dmxdev);
else
dmxdev = DEFAULT_DMX_DEV_NUM;
return dmxdev;
}
int dmx_get_tsn_flag(struct platform_device *pdev, int *tsn_in, int *tsn_out)
{
char buf[32];
u32 source = 0;
int ret = 0;
const char *str;
*tsn_out = 0;
source = 0;
memset(buf, 0, 32);
snprintf(buf, sizeof(buf), "tsn_from");
ret = of_property_read_string(pdev->dev.of_node, buf, &str);
if (!ret) {
dprint("%s:%s\n", buf, str);
if (!strcmp(str, "demod"))
source = INPUT_DEMOD;
else if (!strcmp(str, "local"))
source = INPUT_LOCAL;
else
source = INPUT_LOCAL_SEC;
} else {
source = INPUT_DEMOD;
}
*tsn_in = source;
return 0;
}
struct aml_dvb *aml_get_dvb_device(void)
{
return &aml_dvb_device;
}
EXPORT_SYMBOL(aml_get_dvb_device);
struct dvb_adapter *aml_get_dvb_adapter(void)
{
return &aml_dvb_device.dvb_adapter;
}
EXPORT_SYMBOL(aml_get_dvb_adapter);
struct device *aml_get_device(void)
{
return aml_dvb_device.dev;
}
static int aml_dvb_remove(struct platform_device *pdev)
{
struct aml_dvb *advb;
int i;
advb = &aml_dvb_device;
for (i = 0; i < dmx_dev_num; i++) {
if (advb->tsp[i])
swdmx_ts_parser_free(advb->tsp[i]);
if (advb->swdmx[i])
swdmx_demux_free(advb->swdmx[i]);
}
for (i = 0; i < dmx_dev_num; i++) {
if (advb->dmx[i].init) {
dmx_destroy(&advb->dmx[i]);
advb->dmx[i].id = -1;
}
}
for (i = 0; i < dmx_dev_num; i++) {
dsc_release(&advb->dsc[i]);
advb->dsc[i].id = -1;
}
dmx_key_exit();
ts_output_destroy();
mutex_destroy(&advb->mutex);
dvb_unregister_adapter(&advb->dvb_adapter);
class_unregister(&aml_stb_class);
dmx_unregist_dmx_class();
return 0;
}
static int find_same_sid(int sid_num, int sid)
{
int i = 0;
for (i = 0; i < sid_num; i++) {
if (sid_info[i] == sid)
return 1;
}
return 0;
}
static int get_all_sid_info(int dmx_dev_num, struct aml_dvb *advb)
{
int i = 0;
int j = 0;
int count = 0;
int same = 0;
for (i = 0; i < dmx_dev_num; i++) {
sid_info[i] = i;
sid_info[dmx_dev_num + i] = i + 32;
}
count = dmx_dev_num * 2;
for (j = 0; j < FE_DEV_COUNT; j++) {
if (advb->ts[j].ts_sid != -1) {
same = find_same_sid(dmx_dev_num * 2, advb->ts[j].ts_sid);
if (same == 0) {
sid_info[count] = advb->ts[j].ts_sid;
count++;
}
if (advb->ts[j].ts_sid < 32) {
same = find_same_sid(dmx_dev_num * 2, advb->ts[j].ts_sid);
if (same == 0) {
sid_info[count] =
advb->ts[j].ts_sid + 32;
count++;
}
}
}
}
return count;
}
static int get_first_valid_ts(struct aml_dvb *advb)
{
int i = 0;
for (i = 0; i < FE_DEV_COUNT; i++) {
if (advb->ts[i].ts_sid != -1)
return i;
}
return 0;
}
static int aml_dvb_probe(struct platform_device *pdev)
{
struct aml_dvb *advb;
int i, ret = 0;
int tsn_in_reg = 0;
int sid_num = 0;
int valid_ts = 0;
dprint("probe amlogic dvb driver [%s].\n", DVB_VERSION);
advb = &aml_dvb_device;
memset(advb, 0, sizeof(aml_dvb_device));
advb->dev = &pdev->dev;
advb->pdev = pdev;
ret = dvb_register_adapter(&advb->dvb_adapter, CARD_NAME, THIS_MODULE,
advb->dev, adapter_nr);
if (ret < 0)
return ret;
mutex_init(&advb->mutex);
spin_lock_init(&advb->slock);
ret = init_demux_addr(pdev);
if (ret != 0)
return ret;
frontend_probe(pdev);
dmx_dev_num = dmx_get_dev_num(pdev);
dmx_get_tsn_flag(pdev, &tsn_in, &tsn_out);
if (tsn_in == INPUT_DEMOD)
tsn_in_reg = 1;
pr_dbg("tsn_in:%d, tsn_out:%d\n", tsn_in_reg, tsn_out);
advb->dsc_pipeline = tsn_in_reg;
//set demod/local
if (get_chip_type() == 1) {
u32 value = 0;
int ret = 0;
value = tsn_in_reg;
value += tsn_out << 1;
ret = tee_write_reg_bits(0xff610320, value, 0, 2);
dprint("tee_write_reg_bits value:%d, ret:%d\n", value, ret);
} else {
tee_demux_config_pipeline(tsn_in_reg, tsn_out);
}
sid_num = get_all_sid_info(dmx_dev_num, advb);
dmx_init_hw(sid_num, (int *)&sid_info);
valid_ts = get_first_valid_ts(advb);
//create dmx dev
for (i = 0; i < dmx_dev_num; i++) {
advb->swdmx[i] = swdmx_demux_new();
if (!advb->swdmx[i])
goto INIT_ERR;
advb->tsp[i] = swdmx_ts_parser_new();
if (!advb->tsp[i])
goto INIT_ERR;
swdmx_ts_parser_add_ts_packet_cb(advb->tsp[i],
swdmx_demux_ts_packet_cb,
advb->swdmx[i]);
advb->dmx[i].id = i;
advb->dmx[i].pmutex = &advb->mutex;
advb->dmx[i].pslock = &advb->slock;
advb->dmx[i].swdmx = advb->swdmx[i];
advb->dmx[i].tsp = advb->tsp[i];
advb->dmx[i].source = tsn_in;
advb->dmx[i].ts_index = valid_ts;
advb->dmx[i].demod_sid = advb->ts[advb->dmx[i].ts_index].ts_sid;
advb->dmx[i].local_sid = i;
ret = dmx_init(&advb->dmx[i], &advb->dvb_adapter);
if (ret)
goto INIT_ERR;
advb->dsc[i].mutex = advb->mutex;
// advb->dsc[i].slock = advb->slock;
advb->dsc[i].id = i;
advb->dsc[i].source = tsn_in;
advb->dsc[i].demod_sid = advb->dmx[i].demod_sid;
advb->dsc[i].local_sid = i;
ret = dsc_init(&advb->dsc[i], &advb->dvb_adapter);
if (ret)
goto INIT_ERR;
}
frontend_config_ts_sid();
dmx_key_init();
class_register(&aml_stb_class);
dmx_regist_dmx_class();
ret = tee_demux_get(TEE_DMX_GET_SECURITY_ENABLE,
NULL, 0, &is_security_dmx, sizeof(is_security_dmx));
dprint("probe dvb done, ret:%d, is_security_dmx:%d\n",
ret, is_security_dmx);
return 0;
INIT_ERR:
aml_dvb_remove(pdev);
return -1;
}
void set_dvb_loop_tsn(int flag)
{
struct aml_dvb *advb;
int ret = 0;
advb = &aml_dvb_device;
if (advb->loop_tsn == flag)
return;
// set loop_tsn in tee
advb->loop_tsn = flag;
ret = tee_write_reg_bits(0xfe440320, (u32)flag, 2, 1);
}
int get_dvb_loop_tsn(void)
{
struct aml_dvb *advb;
advb = &aml_dvb_device;
return advb->loop_tsn;
}
#ifdef CONFIG_OF
static const struct of_device_id aml_dvb_dt_match[] = {
{
.compatible = "amlogic sc2, dvb-demux",
},
{}
};
#endif /*CONFIG_OF */
struct platform_driver aml_dvb_driver = {
.probe = aml_dvb_probe,
.remove = aml_dvb_remove,
.suspend = NULL,
.resume = NULL,
.driver = {
.name = "amlogic-dvb-demux",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = aml_dvb_dt_match,
#endif
}
};
static int __init aml_dvb_init(void)
{
dprint("aml dvb init\n");
return platform_driver_register(&aml_dvb_driver);
}
static void __exit aml_dvb_exit(void)
{
dprint("aml dvb exit\n");
platform_driver_unregister(&aml_dvb_driver);
}
module_init(aml_dvb_init);
module_exit(aml_dvb_exit);
MODULE_DESCRIPTION("driver for the AMLogic DVB card");
MODULE_AUTHOR("AMLOGIC");
MODULE_LICENSE("GPL");