mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
d16f3ece36
PD#SWPL-125117 Problem: Can not find frontend0 node. Solution: fix tuner attach. Verify: verified by aq2432. Change-Id: I9882154faccb324f6a5bdea8f2a0caa749da830c Signed-off-by: min.yang <min.yang@amlogic.com>
806 lines
18 KiB
C
806 lines
18 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/amlogic/aml_tuner.h>
|
|
#include <linux/amlogic/aml_dvb_extern.h>
|
|
|
|
static int tuner_attach(struct dvb_tuner *tuner, bool attach)
|
|
{
|
|
void *fe = NULL;
|
|
struct tuner_ops *ops = NULL;
|
|
bool user_cfg = false;
|
|
|
|
if (IS_ERR_OR_NULL(tuner))
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&tuner->mutex);
|
|
|
|
list_for_each_entry(ops, &tuner->list, list) {
|
|
if (ops->dts_cfg)
|
|
continue;
|
|
user_cfg = true;
|
|
break;
|
|
}
|
|
|
|
ops = NULL;
|
|
list_for_each_entry(ops, &tuner->list, list) {
|
|
if ((ops->attached && attach) ||
|
|
(!ops->attached && !attach)) {
|
|
pr_err("Tuner: tuner%d [id %d] had %s.\n",
|
|
ops->index, ops->cfg.id,
|
|
attach ? "attached" : "detached");
|
|
continue;
|
|
}
|
|
|
|
if (attach) {
|
|
if (!user_cfg || (user_cfg && !ops->dts_cfg)) {
|
|
fe = ops->module->attach(ops->module,
|
|
&ops->fe, &ops->cfg);
|
|
if (!IS_ERR_OR_NULL(fe))
|
|
ops->attached = true;
|
|
else
|
|
ops->attached = false;
|
|
|
|
pr_err("Tuner: attach tuner%d [id %d] %s.\n",
|
|
ops->index, ops->cfg.id,
|
|
ops->attached ? "done" : "fail");
|
|
}
|
|
} else {
|
|
if (tuner->used == ops)
|
|
tuner->used = NULL;
|
|
|
|
ops->module->detach(ops->module);
|
|
|
|
ops->attached = false;
|
|
ops->delivery_system = SYS_UNDEFINED;
|
|
ops->type = AML_FE_UNDEFINED;
|
|
|
|
memset(&ops->fe, 0, sizeof(struct dvb_frontend));
|
|
|
|
pr_err("Tuner: detach tuner%d [id %d] done.\n",
|
|
ops->index, ops->cfg.id);
|
|
}
|
|
}
|
|
mutex_unlock(&tuner->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct tuner_ops *tuner_match(struct dvb_tuner *tuner, int std)
|
|
{
|
|
int ret = 0;
|
|
struct tuner_ops *ops = NULL;
|
|
|
|
if (IS_ERR_OR_NULL(tuner))
|
|
return NULL;
|
|
|
|
mutex_lock(&tuner->mutex);
|
|
|
|
if (tuner->used) {
|
|
if (tuner->used->type == std) {
|
|
mutex_unlock(&tuner->mutex);
|
|
|
|
return tuner->used;
|
|
}
|
|
|
|
ret = tuner->used->module->match(tuner->used->module, std);
|
|
if (!ret) {
|
|
tuner->used->type = std;
|
|
|
|
mutex_unlock(&tuner->mutex);
|
|
|
|
return tuner->used;
|
|
}
|
|
}
|
|
|
|
tuner->used = NULL;
|
|
list_for_each_entry(ops, &tuner->list, list) {
|
|
if (!ops->attached || (ops->cfg.detect && !ops->valid))
|
|
continue;
|
|
|
|
ret = ops->module->match(ops->module, std);
|
|
if (!ret) {
|
|
ops->type = std;
|
|
tuner->used = ops;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&tuner->mutex);
|
|
|
|
return tuner->used;
|
|
}
|
|
|
|
static int tuner_detect(struct dvb_tuner *tuner)
|
|
{
|
|
int ret = 0;
|
|
struct tuner_ops *ops = NULL;
|
|
|
|
if (IS_ERR_OR_NULL(tuner))
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&tuner->mutex);
|
|
|
|
list_for_each_entry(ops, &tuner->list, list) {
|
|
ret = 0;
|
|
|
|
if (!ops->attached || !ops->cfg.detect || ops->valid)
|
|
continue;
|
|
|
|
if (ops->fe.ops.tuner_ops.set_config) {
|
|
ret = ops->fe.ops.tuner_ops.set_config(&ops->fe, NULL);
|
|
} else {
|
|
pr_err("Tuner: tuner%d [id %d] set_config() is NULL.\n",
|
|
ops->index, ops->cfg.id);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!ret) {
|
|
ret = ops->module->detect(&ops->cfg);
|
|
if (!ret)
|
|
ops->valid = true;
|
|
else
|
|
ops->valid = false;
|
|
|
|
pr_err("Tuner: detect tuner%d [id %d] %s.\n",
|
|
ops->index, ops->cfg.id,
|
|
ops->valid ? "done" : "fail");
|
|
|
|
if (ops->fe.ops.tuner_ops.release)
|
|
ops->fe.ops.tuner_ops.release(&ops->fe);
|
|
} else {
|
|
pr_err("Tuner: tuner%d [id %d] set_config() error, ret %d.\n",
|
|
ops->index, ops->cfg.id, ret);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&tuner->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_pre_init(struct dvb_tuner *tuner)
|
|
{
|
|
int ret = 0;
|
|
struct tuner_ops *ops = NULL;
|
|
|
|
if (IS_ERR_OR_NULL(tuner))
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&tuner->mutex);
|
|
|
|
list_for_each_entry(ops, &tuner->list, list) {
|
|
if (!ops->attached || ops->pre_inited)
|
|
continue;
|
|
|
|
/* In some cases, pre-init is required. */
|
|
/* 1. Loop through is enabled. */
|
|
if (ops->cfg.lt_out && ops->fe.ops.tuner_ops.set_config) {
|
|
ret = ops->fe.ops.tuner_ops.set_config(&ops->fe, NULL);
|
|
|
|
ops->pre_inited = ret ? false : true;
|
|
|
|
pr_err("Tuner: pre_init tuner%d [id %d] %s.\n",
|
|
ops->index, ops->cfg.id,
|
|
ret ? "fail" : "done");
|
|
|
|
if (ops->pre_inited && ops->fe.ops.tuner_ops.release)
|
|
ops->fe.ops.tuner_ops.release(&ops->fe);
|
|
} else {
|
|
ops->pre_inited = true;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&tuner->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static DEFINE_MUTEX(dvb_tuners_mutex);
|
|
|
|
static struct dvb_tuner tuners = {
|
|
.used = NULL,
|
|
.list = LIST_HEAD_INIT(tuners.list),
|
|
.mutex = __MUTEX_INITIALIZER(tuners.mutex),
|
|
.refcount = 0,
|
|
.attach = tuner_attach,
|
|
.match = tuner_match,
|
|
.detect = tuner_detect,
|
|
.pre_init = tuner_pre_init
|
|
};
|
|
|
|
static DEFINE_MUTEX(tuner_fe_type_match_mutex);
|
|
|
|
static bool tuner_is_same_cfg(struct tuner_config *cfg1,
|
|
struct tuner_config *cfg2)
|
|
{
|
|
return (cfg1->id == cfg2->id &&
|
|
cfg1->i2c_addr == cfg2->i2c_addr &&
|
|
cfg1->i2c_adap == cfg2->i2c_adap);
|
|
}
|
|
|
|
static bool tuner_is_valid_cfg(struct tuner_config *cfg)
|
|
{
|
|
return !cfg ? false : cfg->id != AM_TUNER_NONE;
|
|
}
|
|
|
|
static bool tuner_is_valid_ops(struct tuner_ops *ops)
|
|
{
|
|
return !ops ? false :
|
|
!(!ops->attached || (ops->cfg.detect && !ops->valid));
|
|
}
|
|
|
|
static struct tuner_ops *tuner_fe_type_match(struct dvb_frontend *fe)
|
|
{
|
|
char *name = NULL;
|
|
struct tuner_ops *match = NULL, *tops = NULL;
|
|
struct demod_ops *dops = NULL, *find = NULL;
|
|
struct dvb_tuner *tuner = get_dvb_tuners();
|
|
struct dvb_demod *demod = get_dvb_demods();
|
|
|
|
mutex_lock(&tuner_fe_type_match_mutex);
|
|
|
|
/* First, lookup in the previous match. */
|
|
if (tuner->used && tuner->used->user == fe &&
|
|
tuner->used->type == fe->ops.info.type) {
|
|
/* name = tuner->used->fe.ops.tuner_ops.info.name;
|
|
* pr_err("Tuner: return current match fe type [%d] tuner%d (%s).\n",
|
|
* fe->ops.info.type, ops->index, name ? name : "");
|
|
*/
|
|
|
|
mutex_unlock(&tuner_fe_type_match_mutex);
|
|
|
|
return tuner->used;
|
|
}
|
|
|
|
/* Then, check if the specified tuner configuration is present. */
|
|
list_for_each_entry(dops, &demod->list, list) {
|
|
if (dops->fe == fe) {
|
|
/* External demod doesn't need to match tuner. */
|
|
if (dops->external) {
|
|
mutex_unlock(&tuner_fe_type_match_mutex);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
find = dops;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (find) {
|
|
if (tuner_is_valid_cfg(&find->cfg.tuner0) ||
|
|
tuner_is_valid_cfg(&find->cfg.tuner1)) {
|
|
list_for_each_entry(tops, &tuner->list, list) {
|
|
if (tuner_is_same_cfg(&tops->cfg,
|
|
&find->cfg.tuner0) ||
|
|
tuner_is_same_cfg(&tops->cfg,
|
|
&find->cfg.tuner1)) {
|
|
if (!tuner_is_valid_ops(tops))
|
|
continue;
|
|
|
|
if (!tops->module->match(tops->module,
|
|
fe->ops.info.type)) {
|
|
match = tops;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Finally, lookup all the configurations, not include specified. */
|
|
if (!match)
|
|
match = tuner->match(tuner, fe->ops.info.type);
|
|
|
|
if (!match) {
|
|
pr_err("Tuner: can't get match fe type [%d] tuner.\n",
|
|
fe->ops.info.type);
|
|
|
|
} else {
|
|
/* Get the actual tuner information and private data. */
|
|
memcpy(&fe->ops.tuner_ops.info, &match->fe.ops.tuner_ops.info,
|
|
sizeof(struct dvb_tuner_info));
|
|
fe->tuner_priv = match->fe.tuner_priv;
|
|
|
|
match->type = fe->ops.info.type;
|
|
match->delivery_system = fe->dtv_property_cache.delivery_system;
|
|
|
|
match->user = fe;
|
|
tuner->used = match;
|
|
|
|
name = match->fe.ops.tuner_ops.info.name;
|
|
pr_err("Tuner: get match fe type [%d] tuner%d (%s).\n",
|
|
fe->ops.info.type, match->index,
|
|
name ? name : "");
|
|
}
|
|
|
|
mutex_unlock(&tuner_fe_type_match_mutex);
|
|
|
|
return match;
|
|
}
|
|
|
|
/* In general, this interface must be called when it is no longer used. */
|
|
static void tuner_release(struct dvb_frontend *fe)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.release)
|
|
match->fe.ops.tuner_ops.release(fe);
|
|
}
|
|
|
|
static int tuner_init(struct dvb_frontend *fe)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.init)
|
|
return match->fe.ops.tuner_ops.init(fe);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_sleep(struct dvb_frontend *fe)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.sleep)
|
|
return match->fe.ops.tuner_ops.sleep(fe);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_suspend(struct dvb_frontend *fe)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.suspend)
|
|
return match->fe.ops.tuner_ops.suspend(fe);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_resume(struct dvb_frontend *fe)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.resume)
|
|
return match->fe.ops.tuner_ops.resume(fe);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* This is the recommended way to set the tuner */
|
|
static int tuner_set_params(struct dvb_frontend *fe)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.set_params)
|
|
return match->fe.ops.tuner_ops.set_params(fe);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_set_analog_params(struct dvb_frontend *fe,
|
|
struct analog_parameters *p)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.set_analog_params)
|
|
return match->fe.ops.tuner_ops.set_analog_params(fe, p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* In general, this interface must be called first,
|
|
* and the value of 'fe->ops.info.type' is valid and clear.
|
|
*/
|
|
static int tuner_set_config(struct dvb_frontend *fe, void *priv_cfg)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.set_config)
|
|
return match->fe.ops.tuner_ops.set_config(fe, priv_cfg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_get_frequency(struct dvb_frontend *fe, u32 *frequency)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.get_frequency)
|
|
return match->fe.ops.tuner_ops.get_frequency(fe, frequency);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_get_bandwidth(struct dvb_frontend *fe, u32 *frequency)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.get_bandwidth)
|
|
return match->fe.ops.tuner_ops.get_bandwidth(fe, frequency);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_get_if_frequency(struct dvb_frontend *fe, u32 *frequency)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.get_if_frequency)
|
|
return match->fe.ops.tuner_ops.get_if_frequency(fe, frequency);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_get_status(struct dvb_frontend *fe, u32 *status)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.get_status)
|
|
return match->fe.ops.tuner_ops.get_status(fe, status);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_get_rf_strength(struct dvb_frontend *fe, u16 *strength)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.get_rf_strength)
|
|
return match->fe.ops.tuner_ops.get_rf_strength(fe, strength);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_AMLOGIC_DVB_COMPAT
|
|
static int tuner_get_strength(struct dvb_frontend *fe, s16 *strength)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.get_strength)
|
|
return match->fe.ops.tuner_ops.get_strength(fe, strength);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int tuner_get_afc(struct dvb_frontend *fe, s32 *afc)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.get_afc)
|
|
return match->fe.ops.tuner_ops.get_afc(fe, afc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This is support for demods like the mt352 - fills out the supplied
|
|
* buffer with what to write.
|
|
*
|
|
* Don't use on newer drivers.
|
|
*/
|
|
static int tuner_calc_regs(struct dvb_frontend *fe, u8 *buf, int buf_len)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.calc_regs)
|
|
return match->fe.ops.tuner_ops.calc_regs(fe, buf, buf_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* These are provided separately from set_params in order to
|
|
* facilitate silicon tuners which require sophisticated tuning loops,
|
|
* controlling each parameter separately.
|
|
*
|
|
* Don't use on newer drivers.
|
|
*/
|
|
static int tuner_set_frequency(struct dvb_frontend *fe, u32 frequency)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.set_frequency)
|
|
return match->fe.ops.tuner_ops.set_frequency(fe, frequency);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tuner_set_bandwidth(struct dvb_frontend *fe, u32 bandwidth)
|
|
{
|
|
struct tuner_ops *match = tuner_fe_type_match(fe);
|
|
|
|
if (match && match->fe.ops.tuner_ops.set_bandwidth)
|
|
return match->fe.ops.tuner_ops.set_bandwidth(fe, bandwidth);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Generic interface, which needs to be attached to the actual tuner.
|
|
* The actual tuner information and private data must be obtained.
|
|
*/
|
|
static struct dvb_tuner_ops tuner_ops = {
|
|
.info = {
|
|
.name = "tuner-useless",
|
|
.frequency_min_hz = 42000000,
|
|
.frequency_max_hz = 2300000000UL,
|
|
.frequency_step_hz = 0,
|
|
.bandwidth_min = 0,
|
|
.bandwidth_max = 0,
|
|
.bandwidth_step = 0,
|
|
},
|
|
.release = tuner_release,
|
|
.init = tuner_init,
|
|
.sleep = tuner_sleep,
|
|
.suspend = tuner_suspend,
|
|
.resume = tuner_resume,
|
|
.set_params = tuner_set_params,
|
|
.set_analog_params = tuner_set_analog_params,
|
|
.calc_regs = tuner_calc_regs,
|
|
.set_config = tuner_set_config,
|
|
.get_frequency = tuner_get_frequency,
|
|
.get_bandwidth = tuner_get_bandwidth,
|
|
.get_if_frequency = tuner_get_if_frequency,
|
|
.get_status = tuner_get_status,
|
|
.get_rf_strength = tuner_get_rf_strength,
|
|
#ifdef CONFIG_AMLOGIC_DVB_COMPAT
|
|
.get_strength = tuner_get_strength,
|
|
#endif
|
|
.get_afc = tuner_get_afc,
|
|
.set_frequency = tuner_set_frequency,
|
|
.set_bandwidth = tuner_set_bandwidth
|
|
};
|
|
|
|
struct dvb_frontend *dvb_tuner_attach(struct dvb_frontend *fe)
|
|
{
|
|
struct dvb_tuner *tuner = get_dvb_tuners();
|
|
|
|
if (IS_ERR_OR_NULL(fe)) {
|
|
pr_err("Tuner: %s: NULL or error pointer of fe.\n", __func__);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
mutex_lock(&dvb_tuners_mutex);
|
|
|
|
/* try attach */
|
|
tuner->attach(tuner, true);
|
|
|
|
/* try detect */
|
|
tuner->detect(tuner);
|
|
|
|
tuner->pre_init(tuner);
|
|
|
|
memcpy(&fe->ops.tuner_ops, &tuner_ops, sizeof(struct dvb_tuner_ops));
|
|
|
|
/* try match */
|
|
if (fe->ops.info.type != AML_FE_UNDEFINED)
|
|
tuner_fe_type_match(fe);
|
|
|
|
tuner->refcount++;
|
|
|
|
mutex_unlock(&dvb_tuners_mutex);
|
|
|
|
return fe;
|
|
}
|
|
EXPORT_SYMBOL(dvb_tuner_attach);
|
|
|
|
int dvb_tuner_detach(void)
|
|
{
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(dvb_tuner_detach);
|
|
|
|
struct tuner_ops *dvb_tuner_ops_create(void)
|
|
{
|
|
struct tuner_ops *ops = NULL;
|
|
|
|
ops = kzalloc(sizeof(*ops), GFP_KERNEL);
|
|
if (ops) {
|
|
ops->attached = false;
|
|
ops->dts_cfg = true;
|
|
ops->index = -1;
|
|
ops->pre_inited = false;
|
|
ops->delivery_system = SYS_UNDEFINED;
|
|
ops->type = AML_FE_UNDEFINED;
|
|
ops->module = NULL;
|
|
INIT_LIST_HEAD(&ops->list);
|
|
}
|
|
|
|
return ops;
|
|
}
|
|
EXPORT_SYMBOL(dvb_tuner_ops_create);
|
|
|
|
void dvb_tuner_ops_destroy(struct tuner_ops *ops)
|
|
{
|
|
if (IS_ERR_OR_NULL(ops))
|
|
return;
|
|
|
|
dvb_tuner_ops_remove(ops);
|
|
|
|
kfree(ops);
|
|
}
|
|
EXPORT_SYMBOL(dvb_tuner_ops_destroy);
|
|
|
|
void dvb_tuner_ops_destroy_all(void)
|
|
{
|
|
struct dvb_tuner *tuner = NULL;
|
|
struct tuner_ops *ops = NULL, *temp = NULL;
|
|
|
|
mutex_lock(&dvb_tuners_mutex);
|
|
|
|
tuner = get_dvb_tuners();
|
|
|
|
list_for_each_entry_safe(ops, temp, &tuner->list, list) {
|
|
dvb_tuner_ops_destroy(ops);
|
|
}
|
|
|
|
list_del_init(&tuner->list);
|
|
|
|
mutex_unlock(&dvb_tuners_mutex);
|
|
}
|
|
EXPORT_SYMBOL(dvb_tuner_ops_destroy_all);
|
|
|
|
int dvb_tuner_ops_add(struct tuner_ops *ops)
|
|
{
|
|
struct tuner_ops *p = NULL;
|
|
struct dvb_tuner *tuner = NULL;
|
|
|
|
if (IS_ERR_OR_NULL(ops))
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&dvb_tuners_mutex);
|
|
|
|
tuner = get_dvb_tuners();
|
|
|
|
list_for_each_entry(p, &tuner->list, list) {
|
|
if (p == ops) {
|
|
mutex_unlock(&dvb_tuners_mutex);
|
|
|
|
pr_err("Tuner: tuner%d ops [0x%p] exist.\n",
|
|
ops->index, ops);
|
|
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
|
|
ops->module = aml_get_tuner_module(ops->cfg.id);
|
|
if (IS_ERR_OR_NULL(ops->module)) {
|
|
mutex_unlock(&dvb_tuners_mutex);
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
ops->cfg.name = ops->module->name;
|
|
|
|
list_add_tail(&ops->list, &tuner->list);
|
|
|
|
mutex_unlock(&dvb_tuners_mutex);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(dvb_tuner_ops_add);
|
|
|
|
void dvb_tuner_ops_remove(struct tuner_ops *ops)
|
|
{
|
|
if (IS_ERR_OR_NULL(ops))
|
|
return;
|
|
|
|
if (ops->attached) {
|
|
if (ops->module && ops->module->detach)
|
|
ops->module->detach(ops->module);
|
|
|
|
ops->attached = false;
|
|
ops->valid = false;
|
|
ops->pre_inited = false;
|
|
ops->index = -1;
|
|
ops->delivery_system = SYS_UNDEFINED;
|
|
ops->type = AML_FE_UNDEFINED;
|
|
ops->module = NULL;
|
|
}
|
|
|
|
list_del_init(&ops->list);
|
|
}
|
|
EXPORT_SYMBOL(dvb_tuner_ops_remove);
|
|
|
|
struct tuner_ops *dvb_tuner_ops_get_byindex(int index)
|
|
{
|
|
struct dvb_tuner *tuner = NULL;
|
|
struct tuner_ops *ops = NULL;
|
|
|
|
mutex_lock(&dvb_tuners_mutex);
|
|
|
|
tuner = get_dvb_tuners();
|
|
|
|
list_for_each_entry(ops, &tuner->list, list) {
|
|
if (ops->index == index) {
|
|
mutex_unlock(&dvb_tuners_mutex);
|
|
|
|
return ops;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&dvb_tuners_mutex);
|
|
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(dvb_tuner_ops_get_byindex);
|
|
|
|
int dvb_tuner_ops_get_index(void)
|
|
{
|
|
struct dvb_tuner *tuner = NULL;
|
|
struct tuner_ops *ops = NULL;
|
|
int index = 0;
|
|
|
|
mutex_lock(&dvb_tuners_mutex);
|
|
|
|
tuner = get_dvb_tuners();
|
|
|
|
list_for_each_entry(ops, &tuner->list, list) {
|
|
index = ops->index;
|
|
}
|
|
mutex_unlock(&dvb_tuners_mutex);
|
|
if (index != 0)
|
|
index++;
|
|
return index;
|
|
}
|
|
EXPORT_SYMBOL(dvb_tuner_ops_get_index);
|
|
|
|
int tuner_attach_register_cb(const enum tuner_type type, tn_attach_cb funcb)
|
|
{
|
|
struct dvb_tuner *tuner = NULL;
|
|
struct tuner_ops *ops = NULL;
|
|
enum tuner_type tuner_id[AM_TUNER_MAX] = {0};
|
|
int mod_num = 0;
|
|
bool found = false;
|
|
|
|
if (type > AM_TUNER_NONE && type < AM_TUNER_MAX)
|
|
aml_set_tuner_attach_cb(type, funcb);
|
|
else
|
|
return -1;
|
|
|
|
mutex_lock(&dvb_tuners_mutex);
|
|
tuner = get_dvb_tuners();
|
|
|
|
/* statistics needed tuner from all insmod tuner modules */
|
|
list_for_each_entry(ops, &tuner->list, list) {
|
|
if (tuner_id[ops->cfg.id] == 0) {
|
|
tuner_id[ops->cfg.id] = ops->cfg.id;
|
|
mod_num++;
|
|
}
|
|
|
|
if (ops->cfg.id == type)
|
|
found = true;
|
|
}
|
|
|
|
if (found)
|
|
tuner->cb_num++;
|
|
|
|
pr_info("%s: register type %d, current num %d, modnum %d\n",
|
|
__func__, type, tuner->cb_num, mod_num);
|
|
|
|
if (tuner->cb_num == mod_num) {
|
|
mutex_unlock(&dvb_tuners_mutex);
|
|
aml_dvb_extern_attach();
|
|
} else {
|
|
mutex_unlock(&dvb_tuners_mutex);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tuner_attach_register_cb);
|
|
|
|
struct dvb_tuner *get_dvb_tuners(void)
|
|
{
|
|
return &tuners;
|
|
}
|