blob: 8c94345e407ffa1b281dca857ad0f5760ea13dad [file] [log] [blame]
// 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;
if (IS_ERR_OR_NULL(tuner))
return -EFAULT;
mutex_lock(&tuner->mutex);
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) {
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 thorugh 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 recomended 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 = 0,
.frequency_max_hz = 0,
.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->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);
struct dvb_tuner *get_dvb_tuners(void)
{
return &tuners;
}