mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
b7181587ad
PD#SWPL-119805 Problem: dependent on demux driver. Solution: dependent on dvb core. Verify: no verify. Change-Id: Idf14192e65db6ee026c5b41fc6e599a1590e99ea Signed-off-by: hongyu.chen <hongyu.chen@amlogic.com>
500 lines
11 KiB
C
500 lines
11 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/version.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/of.h>
|
|
#include <dvbdev.h>
|
|
|
|
#include "aml_ci.h"
|
|
|
|
#include "aml_ci_bus.h"
|
|
|
|
MODULE_PARM_DESC(aml_ci_debug, "\n\t\t dvb ci debug");
|
|
static int aml_ci_debug = 1;
|
|
module_param(aml_ci_debug, int, 0444);
|
|
|
|
#define pr_dbg(args...)\
|
|
do {\
|
|
if (aml_ci_debug)\
|
|
pr_err("aml_ci: " args);\
|
|
} while (0)
|
|
|
|
#define pr_error(fmt, args...) pr_err("DVBCI: " fmt, ## args)
|
|
|
|
/**\brief aml_ci_mem_read:mem read from cam
|
|
* \param en50221: en50221 obj,used this data to get dvb_ci obj
|
|
* \param slot: slot index
|
|
* \param addr: read addr
|
|
* \return
|
|
* - read value:ok
|
|
* - -EINVAL : error
|
|
*/
|
|
static int aml_ci_mem_read(struct dvb_ca_en50221_cimcu *en50221, int slot, int addr)
|
|
{
|
|
struct aml_ci *ci = en50221->data;
|
|
|
|
if (slot != 0) {
|
|
pr_error("slot !=0 %s :%d\r\n", __func__, slot);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ci->ci_mem_read)
|
|
return ci->ci_mem_read(ci, slot, addr);
|
|
|
|
pr_error("ci_mem_read is null %s\r\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**\brief aml_ci_mem_write:mem write to cam
|
|
* \param en50221: en50221 obj,used this data to get dvb_ci obj
|
|
* \param slot: slot index
|
|
* \param addr: write addr
|
|
* \param addr: write value
|
|
* \return
|
|
* - 0:ok
|
|
* - -EINVAL : error
|
|
*/
|
|
static int aml_ci_mem_write(struct dvb_ca_en50221_cimcu *en50221,
|
|
int slot, int addr, u8 data)
|
|
{
|
|
struct aml_ci *ci = en50221->data;
|
|
|
|
if (slot != 0) {
|
|
pr_error("slot not 0 %s :%d\r\n", __func__, slot);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ci->ci_mem_write)
|
|
return ci->ci_mem_write(ci, slot, addr, data);
|
|
pr_error("ci_mem_write is null %s\r\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**\brief aml_ci_io_read:io read from cam
|
|
* \param en50221: en50221 obj,used this data to get dvb_ci obj
|
|
* \param slot: slot index
|
|
* \param addr: read addr
|
|
* \return
|
|
* - read value:ok
|
|
* - -EINVAL : error
|
|
*/
|
|
static int aml_ci_io_read(struct dvb_ca_en50221_cimcu *en50221, int slot, u8 addr)
|
|
{
|
|
struct aml_ci *ci = en50221->data;
|
|
|
|
if (slot != 0) {
|
|
pr_error("slot !=0 %s :%d\r\n", __func__, slot);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ci->ci_io_read)
|
|
return ci->ci_io_read(ci, slot, addr);
|
|
|
|
pr_error("ci_io_read is null %s\r\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**\brief aml_ci_io_write:io write to cam
|
|
* \param en50221: en50221 obj,used this data to get dvb_ci obj
|
|
* \param slot: slot index
|
|
* \param addr: write addr
|
|
* \param addr: write value
|
|
* \return
|
|
* - 0:ok
|
|
* - -EINVAL : error
|
|
*/
|
|
static int aml_ci_io_write(struct dvb_ca_en50221_cimcu *en50221,
|
|
int slot, u8 addr, u8 data)
|
|
{
|
|
struct aml_ci *ci = en50221->data;
|
|
|
|
if (slot != 0) {
|
|
pr_error("slot !=0 %s :%d\r\n", __func__, slot);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ci->ci_mem_write)
|
|
return ci->ci_io_write(ci, slot, addr, data);
|
|
|
|
pr_error("ci_io_write is null %s\r\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**\brief aml_ci_slot_reset:reset slot
|
|
* \param en50221: en50221 obj,used this data to get dvb_ci obj
|
|
* \param slot: slot index
|
|
* \return
|
|
* - 0:ok
|
|
* - -EINVAL : error
|
|
*/
|
|
static int aml_ci_slot_reset(struct dvb_ca_en50221_cimcu *en50221, int slot)
|
|
{
|
|
struct aml_ci *ci = en50221->data;
|
|
|
|
pr_dbg("Slot(%d): Slot RESET\n", slot);
|
|
if (ci->ci_slot_reset) {
|
|
ci->ci_slot_reset(ci, slot);
|
|
} else {
|
|
pr_error("ci_slot_reset is null %s\r\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**\brief aml_ci_slot_shutdown:show slot
|
|
* \param en50221: en50221 obj,used this data to get dvb_ci obj
|
|
* \param slot: slot index
|
|
* \return
|
|
* - 0:ok
|
|
* - -EINVAL : error
|
|
*/
|
|
static int aml_ci_slot_shutdown(struct dvb_ca_en50221_cimcu *en50221, int slot)
|
|
{
|
|
struct aml_ci *ci = en50221->data;
|
|
|
|
pr_dbg("Slot(%d): Slot shutdown\n", slot);
|
|
if (ci->ci_slot_shutdown) {
|
|
ci->ci_slot_shutdown(ci, slot);
|
|
} else {
|
|
pr_error("ci_slot_shutdown is null %s\r\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**\brief aml_ci_ts_control:control slot ts
|
|
* \param en50221: en50221 obj,used this data to get dvb_ci obj
|
|
* \param slot: slot index
|
|
* \return
|
|
* - 0:ok
|
|
* - -EINVAL : error
|
|
*/
|
|
static int aml_ci_ts_control(struct dvb_ca_en50221_cimcu *en50221, int slot)
|
|
{
|
|
struct aml_ci *ci = en50221->data;
|
|
|
|
pr_dbg("Slot(%d): TS control\n", slot);
|
|
if (ci->ci_slot_ts_enable) {
|
|
ci->ci_slot_ts_enable(ci, slot);
|
|
} else {
|
|
pr_error("%s is null\r\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**\brief aml_ci_slot_status:get slot status
|
|
* \param en50221: en50221 obj,used this data to get dvb_ci obj
|
|
* \param slot: slot index
|
|
* \param open: no used
|
|
* \return
|
|
* - cam status
|
|
* - -EINVAL : error
|
|
*/
|
|
static int aml_ci_slot_status(struct dvb_ca_en50221_cimcu *en50221,
|
|
int slot, int open)
|
|
{
|
|
struct aml_ci *ci = en50221->data;
|
|
|
|
//pr_dbg("Slot(%d): Poll Slot status\n", slot);
|
|
|
|
if (ci->ci_poll_slot_status)
|
|
return ci->ci_poll_slot_status(ci, slot, open);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**\brief aml_ci_slot_status:get slot status
|
|
* \param en50221: en50221 obj,used this data to get dvb_ci obj
|
|
* \param slot: slot index
|
|
* \param open: no used
|
|
* \return
|
|
* - cam status
|
|
* - -EINVAL : error
|
|
*/
|
|
static int aml_ci_slot_wakeup(struct dvb_ca_en50221_cimcu *en50221,
|
|
int slot)
|
|
{
|
|
struct aml_ci *ci = en50221->data;
|
|
|
|
if (ci->ci_get_slot_wakeup)
|
|
return ci->ci_get_slot_wakeup(ci, slot);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**\brief get ci config from dts
|
|
* \param np: device node
|
|
* \return
|
|
* - 0 成功
|
|
* - 其他值 :
|
|
*/
|
|
static int aml_ci_get_config_from_dts(struct platform_device *pdev,
|
|
struct aml_ci *ci)
|
|
{
|
|
char buf[32];
|
|
int ret = 0;
|
|
int value;
|
|
|
|
snprintf(buf, sizeof(buf), "%s", "io_type");
|
|
ret = of_property_read_u32(pdev->dev.of_node, buf, &value);
|
|
if (!ret) {
|
|
pr_dbg("%s: 0x%x\n", buf, value);
|
|
ci->io_type = value;
|
|
}
|
|
snprintf(buf, sizeof(buf), "%s", "raw_mode");
|
|
ret = of_property_read_u32(pdev->dev.of_node, buf, &value);
|
|
if (!ret) {
|
|
pr_dbg("%s: 0x%x\n", buf, value);
|
|
ci->raw_mode = value;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**\brief aml_ci_init:ci dev init
|
|
* \param pdev: platform_device device node,used to get dts info
|
|
* \param dvb: aml_dvb obj,used to get dvb_adapter for en0211 to use
|
|
* \param cip: ci_dev pp
|
|
* \return
|
|
* - 0 成功
|
|
* - 其他值 :
|
|
*/
|
|
int aml_ci_init(struct platform_device *pdev,
|
|
struct dvb_adapter *dvb_adapter, struct aml_ci **cip)
|
|
{
|
|
struct aml_ci *ci = NULL;
|
|
int ca_flags = 0, result;
|
|
|
|
ci = kzalloc(sizeof(*ci), GFP_KERNEL);
|
|
if (!ci) {
|
|
pr_error("Out of memory!, exiting ..\n");
|
|
result = -ENOMEM;
|
|
goto err;
|
|
}
|
|
ci->id = 0;
|
|
aml_ci_get_config_from_dts(pdev, ci);
|
|
|
|
//ci->priv = dvb;
|
|
/* register CA interface */
|
|
|
|
{
|
|
ca_flags = ~DVB_CA_EN50221_FLAG_IRQ_CAMCHANGE;
|
|
/* register CA interface */
|
|
ci->en50221_cimcu.owner = THIS_MODULE;
|
|
ci->en50221_cimcu.read_attribute_mem = aml_ci_mem_read;
|
|
ci->en50221_cimcu.write_attribute_mem = aml_ci_mem_write;
|
|
ci->en50221_cimcu.read_cam_control = aml_ci_io_read;
|
|
ci->en50221_cimcu.write_cam_control = aml_ci_io_write;
|
|
ci->en50221_cimcu.slot_reset = aml_ci_slot_reset;
|
|
ci->en50221_cimcu.slot_shutdown = aml_ci_slot_shutdown;
|
|
ci->en50221_cimcu.slot_ts_enable = aml_ci_ts_control;
|
|
ci->en50221_cimcu.poll_slot_status = aml_ci_slot_status;
|
|
ci->en50221_cimcu.get_slot_wakeup = aml_ci_slot_wakeup;
|
|
|
|
ci->en50221_cimcu.data = ci;
|
|
|
|
if (ci->raw_mode == 0) {
|
|
pr_dbg("Registering EN50221 device\n");
|
|
result = dvb_ca_en50221_cimcu_init(dvb_adapter,
|
|
&ci->en50221_cimcu, ca_flags, 1);
|
|
if (result != 0) {
|
|
pr_error("EN50221_cimcu: Initialization failed <%d>\n",
|
|
result);
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
*cip = ci;
|
|
pr_dbg("Registered EN50221 device\n");
|
|
|
|
if (ci->io_type == AML_DVB_IO_TYPE_CIBUS) {
|
|
ci->ci_init = aml_ci_bus_init;
|
|
ci->ci_exit = aml_ci_bus_exit;
|
|
} else {
|
|
/* no io dev init,is error */
|
|
pr_dbg("unknown io type, please check io_type in dts file\r\n");
|
|
}
|
|
|
|
if (ci->ci_init)
|
|
result = ci->ci_init(pdev, ci);
|
|
|
|
return result;
|
|
err:
|
|
kfree(ci);
|
|
return result;
|
|
}
|
|
|
|
void aml_ci_exit(struct aml_ci *ci)
|
|
{
|
|
pr_dbg("Unregistering EN50221 device\n");
|
|
if (ci) {
|
|
if (ci->raw_mode == 0)
|
|
dvb_ca_en50221_cimcu_release(&ci->en50221_cimcu);
|
|
if (ci->ci_exit)
|
|
ci->ci_exit(ci);
|
|
kfree(ci);
|
|
}
|
|
}
|
|
|
|
static struct aml_ci *ci_dev;
|
|
|
|
static ssize_t ts_show(struct class *class,
|
|
struct class_attribute *attr, char *buf)
|
|
{
|
|
int ret;
|
|
|
|
ret = sprintf(buf, "ts%d\n", 1);
|
|
return ret;
|
|
}
|
|
static CLASS_ATTR_RO(ts);
|
|
|
|
static struct attribute *aml_ci_attrs[] = {
|
|
&class_attr_ts.attr,
|
|
NULL
|
|
};
|
|
|
|
ATTRIBUTE_GROUPS(aml_ci);
|
|
|
|
static int aml_ci_register_class(struct aml_ci *ci)
|
|
{
|
|
#define CLASS_NAME_LEN 48
|
|
int ret;
|
|
struct class *clp;
|
|
|
|
clp = &ci->class;
|
|
|
|
clp->name = kzalloc(CLASS_NAME_LEN, GFP_KERNEL);
|
|
if (!clp->name)
|
|
return -ENOMEM;
|
|
|
|
snprintf((char *)clp->name, CLASS_NAME_LEN, "amlci-%d", ci->id);
|
|
clp->owner = THIS_MODULE;
|
|
clp->class_groups = aml_ci_groups;
|
|
ret = class_register(clp);
|
|
if (ret)
|
|
kfree(clp->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_ci_unregister_class(struct aml_ci *ci)
|
|
{
|
|
class_unregister(&ci->class);
|
|
kfree(ci->class.name);
|
|
return 0;
|
|
}
|
|
|
|
extern struct dvb_adapter *aml_dvb_get_adapter(struct device *dev);
|
|
|
|
static int aml_ci_probe(struct platform_device *pdev)
|
|
{
|
|
struct dvb_adapter *dvb_adapter = NULL;
|
|
int err = 0;
|
|
|
|
dvb_adapter = aml_dvb_get_adapter(&pdev->dev);
|
|
|
|
pr_dbg("---Amlogic CI Init---[%p]\n", dvb_adapter);
|
|
|
|
err = aml_ci_init(pdev, dvb_adapter, &ci_dev);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (ci_dev->io_type == AML_DVB_IO_TYPE_CIBUS) {
|
|
pr_dbg("*********ci bus aml_ci_bus_mod_init---\n");
|
|
aml_ci_bus_mod_init();
|
|
}
|
|
|
|
platform_set_drvdata(pdev, ci_dev);
|
|
aml_ci_register_class(ci_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_ci_remove(struct platform_device *pdev)
|
|
{
|
|
aml_ci_unregister_class(ci_dev);
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
if (ci_dev->io_type == AML_DVB_IO_TYPE_CIBUS) {
|
|
aml_ci_bus_mod_exit();
|
|
aml_ci_bus_exit(ci_dev);
|
|
} else {
|
|
pr_dbg("---Amlogic CI remove unknown io type---\n");
|
|
}
|
|
|
|
aml_ci_exit(ci_dev);
|
|
return 0;
|
|
}
|
|
|
|
static int aml_ci_suspend(struct platform_device *pdev, pm_message_t state)
|
|
{
|
|
pr_dbg("Amlogic CI Suspend!\n");
|
|
|
|
if (ci_dev->io_type == AML_DVB_IO_TYPE_CIBUS)
|
|
aml_ci_bus_exit(ci_dev);
|
|
else
|
|
pr_dbg("---Amlogic CI remove unknown io type---\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aml_ci_resume(struct platform_device *pdev)
|
|
{
|
|
int err = 0;
|
|
|
|
pr_dbg("Amlogic CI Resume!\n");
|
|
|
|
if (ci_dev->io_type == AML_DVB_IO_TYPE_CIBUS)
|
|
aml_ci_bus_init(pdev, ci_dev);
|
|
else
|
|
pr_dbg("---Amlogic CI remove unknown io type---\n");
|
|
return err;
|
|
}
|
|
|
|
static const struct of_device_id dvbci_dev_dt_match[] = {
|
|
{
|
|
.compatible = "amlogic, dvbci",
|
|
},
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver aml_ci_driver = {
|
|
.probe = aml_ci_probe,
|
|
.remove = aml_ci_remove,
|
|
.suspend = aml_ci_suspend,
|
|
.resume = aml_ci_resume,
|
|
.driver = {
|
|
.name = "dvbci",
|
|
.of_match_table = dvbci_dev_dt_match,
|
|
.owner = THIS_MODULE,
|
|
}
|
|
};
|
|
|
|
static int aml_ci_mod_init(void)
|
|
{
|
|
pr_dbg("Amlogic CI mode init\n");
|
|
return platform_driver_register(&aml_ci_driver);
|
|
}
|
|
|
|
static void aml_ci_mod_exit(void)
|
|
{
|
|
pr_dbg("Amlogic CI mode Exit\n");
|
|
platform_driver_unregister(&aml_ci_driver);
|
|
}
|
|
|
|
module_init(aml_ci_mod_init);
|
|
module_exit(aml_ci_mod_exit);
|
|
MODULE_LICENSE("GPL");
|
|
|