mirror of
https://github.com/torvalds/linux.git
synced 2024-12-26 04:42:12 +00:00
a3f90c75b8
Right now, satellite tuner drivers specify frequencies in kHz, while terrestrial/cable ones specify in Hz. That's confusing for developers. However, the main problem is that universal tuners capable of handling both satellite and non-satelite delivery systems are appearing. We end by needing to hack the drivers in order to support such hybrid tuners. So, convert everything to specify tuner frequencies in Hz. Plese notice that a similar patch is also needed for frontends. Tested-by: Katsuhiro Suzuki <suzuki.katsuhiro@socionext.com> Acked-by: Michael Büsch <m@bues.ch> Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
456 lines
10 KiB
C
456 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Sharp QM1D1C0042 8PSK tuner driver
|
|
*
|
|
* Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com>
|
|
*/
|
|
|
|
/*
|
|
* NOTICE:
|
|
* As the disclosed information on the chip is very limited,
|
|
* this driver lacks some features, including chip config like IF freq.
|
|
* It assumes that users of this driver (such as a PCI bridge of
|
|
* DTV receiver cards) know the relevant info and
|
|
* configure the chip via I2C if necessary.
|
|
*
|
|
* Currently, PT3 driver is the only one that uses this driver,
|
|
* and contains init/config code in its firmware.
|
|
* Thus some part of the code might be dependent on PT3 specific config.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/math64.h>
|
|
#include "qm1d1c0042.h"
|
|
|
|
#define QM1D1C0042_NUM_REGS 0x20
|
|
#define QM1D1C0042_NUM_REG_ROWS 2
|
|
|
|
static const u8
|
|
reg_initval[QM1D1C0042_NUM_REG_ROWS][QM1D1C0042_NUM_REGS] = { {
|
|
0x48, 0x1c, 0xa0, 0x10, 0xbc, 0xc5, 0x20, 0x33,
|
|
0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
|
0x00, 0xff, 0xf3, 0x00, 0x2a, 0x64, 0xa6, 0x86,
|
|
0x8c, 0xcf, 0xb8, 0xf1, 0xa8, 0xf2, 0x89, 0x00
|
|
}, {
|
|
0x68, 0x1c, 0xc0, 0x10, 0xbc, 0xc1, 0x11, 0x33,
|
|
0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
|
0x00, 0xff, 0xf3, 0x00, 0x3f, 0x25, 0x5c, 0xd6,
|
|
0x55, 0xcf, 0x95, 0xf6, 0x36, 0xf2, 0x09, 0x00
|
|
}
|
|
};
|
|
|
|
static int reg_index;
|
|
|
|
static const struct qm1d1c0042_config default_cfg = {
|
|
.xtal_freq = 16000,
|
|
.lpf = 1,
|
|
.fast_srch = 0,
|
|
.lpf_wait = 20,
|
|
.fast_srch_wait = 4,
|
|
.normal_srch_wait = 15,
|
|
};
|
|
|
|
struct qm1d1c0042_state {
|
|
struct qm1d1c0042_config cfg;
|
|
struct i2c_client *i2c;
|
|
u8 regs[QM1D1C0042_NUM_REGS];
|
|
};
|
|
|
|
static struct qm1d1c0042_state *cfg_to_state(struct qm1d1c0042_config *c)
|
|
{
|
|
return container_of(c, struct qm1d1c0042_state, cfg);
|
|
}
|
|
|
|
static int reg_write(struct qm1d1c0042_state *state, u8 reg, u8 val)
|
|
{
|
|
u8 wbuf[2] = { reg, val };
|
|
int ret;
|
|
|
|
ret = i2c_master_send(state->i2c, wbuf, sizeof(wbuf));
|
|
if (ret >= 0 && ret < sizeof(wbuf))
|
|
ret = -EIO;
|
|
return (ret == sizeof(wbuf)) ? 0 : ret;
|
|
}
|
|
|
|
static int reg_read(struct qm1d1c0042_state *state, u8 reg, u8 *val)
|
|
{
|
|
struct i2c_msg msgs[2] = {
|
|
{
|
|
.addr = state->i2c->addr,
|
|
.flags = 0,
|
|
.buf = ®,
|
|
.len = 1,
|
|
},
|
|
{
|
|
.addr = state->i2c->addr,
|
|
.flags = I2C_M_RD,
|
|
.buf = val,
|
|
.len = 1,
|
|
},
|
|
};
|
|
int ret;
|
|
|
|
ret = i2c_transfer(state->i2c->adapter, msgs, ARRAY_SIZE(msgs));
|
|
if (ret >= 0 && ret < ARRAY_SIZE(msgs))
|
|
ret = -EIO;
|
|
return (ret == ARRAY_SIZE(msgs)) ? 0 : ret;
|
|
}
|
|
|
|
|
|
static int qm1d1c0042_set_srch_mode(struct qm1d1c0042_state *state, bool fast)
|
|
{
|
|
if (fast)
|
|
state->regs[0x03] |= 0x01; /* set fast search mode */
|
|
else
|
|
state->regs[0x03] &= ~0x01 & 0xff;
|
|
|
|
return reg_write(state, 0x03, state->regs[0x03]);
|
|
}
|
|
|
|
static int qm1d1c0042_wakeup(struct qm1d1c0042_state *state)
|
|
{
|
|
int ret;
|
|
|
|
state->regs[0x01] |= 1 << 3; /* BB_Reg_enable */
|
|
state->regs[0x01] &= (~(1 << 0)) & 0xff; /* NORMAL (wake-up) */
|
|
state->regs[0x05] &= (~(1 << 3)) & 0xff; /* pfd_rst NORMAL */
|
|
ret = reg_write(state, 0x01, state->regs[0x01]);
|
|
if (ret == 0)
|
|
ret = reg_write(state, 0x05, state->regs[0x05]);
|
|
|
|
if (ret < 0)
|
|
dev_warn(&state->i2c->dev, "(%s) failed. [adap%d-fe%d]\n",
|
|
__func__, state->cfg.fe->dvb->num, state->cfg.fe->id);
|
|
return ret;
|
|
}
|
|
|
|
/* tuner_ops */
|
|
|
|
static int qm1d1c0042_set_config(struct dvb_frontend *fe, void *priv_cfg)
|
|
{
|
|
struct qm1d1c0042_state *state;
|
|
struct qm1d1c0042_config *cfg;
|
|
|
|
state = fe->tuner_priv;
|
|
cfg = priv_cfg;
|
|
|
|
if (cfg->fe)
|
|
state->cfg.fe = cfg->fe;
|
|
|
|
if (cfg->xtal_freq != QM1D1C0042_CFG_XTAL_DFLT)
|
|
dev_warn(&state->i2c->dev,
|
|
"(%s) changing xtal_freq not supported. ", __func__);
|
|
state->cfg.xtal_freq = default_cfg.xtal_freq;
|
|
|
|
state->cfg.lpf = cfg->lpf;
|
|
state->cfg.fast_srch = cfg->fast_srch;
|
|
|
|
if (cfg->lpf_wait != QM1D1C0042_CFG_WAIT_DFLT)
|
|
state->cfg.lpf_wait = cfg->lpf_wait;
|
|
else
|
|
state->cfg.lpf_wait = default_cfg.lpf_wait;
|
|
|
|
if (cfg->fast_srch_wait != QM1D1C0042_CFG_WAIT_DFLT)
|
|
state->cfg.fast_srch_wait = cfg->fast_srch_wait;
|
|
else
|
|
state->cfg.fast_srch_wait = default_cfg.fast_srch_wait;
|
|
|
|
if (cfg->normal_srch_wait != QM1D1C0042_CFG_WAIT_DFLT)
|
|
state->cfg.normal_srch_wait = cfg->normal_srch_wait;
|
|
else
|
|
state->cfg.normal_srch_wait = default_cfg.normal_srch_wait;
|
|
return 0;
|
|
}
|
|
|
|
/* divisor, vco_band parameters */
|
|
/* {maxfreq, param1(band?), param2(div?) */
|
|
static const u32 conv_table[9][3] = {
|
|
{ 2151000, 1, 7 },
|
|
{ 1950000, 1, 6 },
|
|
{ 1800000, 1, 5 },
|
|
{ 1600000, 1, 4 },
|
|
{ 1450000, 1, 3 },
|
|
{ 1250000, 1, 2 },
|
|
{ 1200000, 0, 7 },
|
|
{ 975000, 0, 6 },
|
|
{ 950000, 0, 0 }
|
|
};
|
|
|
|
static int qm1d1c0042_set_params(struct dvb_frontend *fe)
|
|
{
|
|
struct qm1d1c0042_state *state;
|
|
u32 freq;
|
|
int i, ret;
|
|
u8 val, mask;
|
|
u32 a, sd;
|
|
s32 b;
|
|
|
|
state = fe->tuner_priv;
|
|
freq = fe->dtv_property_cache.frequency;
|
|
|
|
state->regs[0x08] &= 0xf0;
|
|
state->regs[0x08] |= 0x09;
|
|
|
|
state->regs[0x13] &= 0x9f;
|
|
state->regs[0x13] |= 0x20;
|
|
|
|
/* div2/vco_band */
|
|
val = state->regs[0x02] & 0x0f;
|
|
for (i = 0; i < 8; i++)
|
|
if (freq < conv_table[i][0] && freq >= conv_table[i + 1][0]) {
|
|
val |= conv_table[i][1] << 7;
|
|
val |= conv_table[i][2] << 4;
|
|
break;
|
|
}
|
|
ret = reg_write(state, 0x02, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
a = (freq + state->cfg.xtal_freq / 2) / state->cfg.xtal_freq;
|
|
|
|
state->regs[0x06] &= 0x40;
|
|
state->regs[0x06] |= (a - 12) / 4;
|
|
ret = reg_write(state, 0x06, state->regs[0x06]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
state->regs[0x07] &= 0xf0;
|
|
state->regs[0x07] |= (a - 4 * ((a - 12) / 4 + 1) - 5) & 0x0f;
|
|
ret = reg_write(state, 0x07, state->regs[0x07]);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* LPF */
|
|
val = state->regs[0x08];
|
|
if (state->cfg.lpf) {
|
|
/* LPF_CLK, LPF_FC */
|
|
val &= 0xf0;
|
|
val |= 0x02;
|
|
}
|
|
ret = reg_write(state, 0x08, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/*
|
|
* b = (freq / state->cfg.xtal_freq - a) << 20;
|
|
* sd = b (b >= 0)
|
|
* 1<<22 + b (b < 0)
|
|
*/
|
|
b = (s32)div64_s64(((s64) freq) << 20, state->cfg.xtal_freq)
|
|
- (((s64) a) << 20);
|
|
|
|
if (b >= 0)
|
|
sd = b;
|
|
else
|
|
sd = (1 << 22) + b;
|
|
|
|
state->regs[0x09] &= 0xc0;
|
|
state->regs[0x09] |= (sd >> 16) & 0x3f;
|
|
state->regs[0x0a] = (sd >> 8) & 0xff;
|
|
state->regs[0x0b] = sd & 0xff;
|
|
ret = reg_write(state, 0x09, state->regs[0x09]);
|
|
if (ret == 0)
|
|
ret = reg_write(state, 0x0a, state->regs[0x0a]);
|
|
if (ret == 0)
|
|
ret = reg_write(state, 0x0b, state->regs[0x0b]);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
if (!state->cfg.lpf) {
|
|
/* CSEL_Offset */
|
|
ret = reg_write(state, 0x13, state->regs[0x13]);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
/* VCO_TM, LPF_TM */
|
|
mask = state->cfg.lpf ? 0x3f : 0x7f;
|
|
val = state->regs[0x0c] & mask;
|
|
ret = reg_write(state, 0x0c, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
usleep_range(2000, 3000);
|
|
val = state->regs[0x0c] | ~mask;
|
|
ret = reg_write(state, 0x0c, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (state->cfg.lpf)
|
|
msleep(state->cfg.lpf_wait);
|
|
else if (state->regs[0x03] & 0x01)
|
|
msleep(state->cfg.fast_srch_wait);
|
|
else
|
|
msleep(state->cfg.normal_srch_wait);
|
|
|
|
if (state->cfg.lpf) {
|
|
/* LPF_FC */
|
|
ret = reg_write(state, 0x08, 0x09);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* CSEL_Offset */
|
|
ret = reg_write(state, 0x13, state->regs[0x13]);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int qm1d1c0042_sleep(struct dvb_frontend *fe)
|
|
{
|
|
struct qm1d1c0042_state *state;
|
|
int ret;
|
|
|
|
state = fe->tuner_priv;
|
|
state->regs[0x01] &= (~(1 << 3)) & 0xff; /* BB_Reg_disable */
|
|
state->regs[0x01] |= 1 << 0; /* STDBY */
|
|
state->regs[0x05] |= 1 << 3; /* pfd_rst STANDBY */
|
|
ret = reg_write(state, 0x05, state->regs[0x05]);
|
|
if (ret == 0)
|
|
ret = reg_write(state, 0x01, state->regs[0x01]);
|
|
if (ret < 0)
|
|
dev_warn(&state->i2c->dev, "(%s) failed. [adap%d-fe%d]\n",
|
|
__func__, fe->dvb->num, fe->id);
|
|
return ret;
|
|
}
|
|
|
|
static int qm1d1c0042_init(struct dvb_frontend *fe)
|
|
{
|
|
struct qm1d1c0042_state *state;
|
|
u8 val;
|
|
int i, ret;
|
|
|
|
state = fe->tuner_priv;
|
|
|
|
reg_write(state, 0x01, 0x0c);
|
|
reg_write(state, 0x01, 0x0c);
|
|
|
|
ret = reg_write(state, 0x01, 0x0c); /* soft reset on */
|
|
if (ret < 0)
|
|
goto failed;
|
|
usleep_range(2000, 3000);
|
|
|
|
ret = reg_write(state, 0x01, 0x1c); /* soft reset off */
|
|
if (ret < 0)
|
|
goto failed;
|
|
|
|
/* check ID and choose initial registers corresponding ID */
|
|
ret = reg_read(state, 0x00, &val);
|
|
if (ret < 0)
|
|
goto failed;
|
|
for (reg_index = 0; reg_index < QM1D1C0042_NUM_REG_ROWS;
|
|
reg_index++) {
|
|
if (val == reg_initval[reg_index][0x00])
|
|
break;
|
|
}
|
|
if (reg_index >= QM1D1C0042_NUM_REG_ROWS)
|
|
goto failed;
|
|
memcpy(state->regs, reg_initval[reg_index], QM1D1C0042_NUM_REGS);
|
|
usleep_range(2000, 3000);
|
|
|
|
state->regs[0x0c] |= 0x40;
|
|
ret = reg_write(state, 0x0c, state->regs[0x0c]);
|
|
if (ret < 0)
|
|
goto failed;
|
|
msleep(state->cfg.lpf_wait);
|
|
|
|
/* set all writable registers */
|
|
for (i = 1; i <= 0x0c ; i++) {
|
|
ret = reg_write(state, i, state->regs[i]);
|
|
if (ret < 0)
|
|
goto failed;
|
|
}
|
|
for (i = 0x11; i < QM1D1C0042_NUM_REGS; i++) {
|
|
ret = reg_write(state, i, state->regs[i]);
|
|
if (ret < 0)
|
|
goto failed;
|
|
}
|
|
|
|
ret = qm1d1c0042_wakeup(state);
|
|
if (ret < 0)
|
|
goto failed;
|
|
|
|
ret = qm1d1c0042_set_srch_mode(state, state->cfg.fast_srch);
|
|
if (ret < 0)
|
|
goto failed;
|
|
|
|
return ret;
|
|
|
|
failed:
|
|
dev_warn(&state->i2c->dev, "(%s) failed. [adap%d-fe%d]\n",
|
|
__func__, fe->dvb->num, fe->id);
|
|
return ret;
|
|
}
|
|
|
|
/* I2C driver functions */
|
|
|
|
static const struct dvb_tuner_ops qm1d1c0042_ops = {
|
|
.info = {
|
|
.name = "Sharp QM1D1C0042",
|
|
|
|
.frequency_min_hz = 950 * MHz,
|
|
.frequency_max_hz = 2150 * MHz,
|
|
},
|
|
|
|
.init = qm1d1c0042_init,
|
|
.sleep = qm1d1c0042_sleep,
|
|
.set_config = qm1d1c0042_set_config,
|
|
.set_params = qm1d1c0042_set_params,
|
|
};
|
|
|
|
|
|
static int qm1d1c0042_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct qm1d1c0042_state *state;
|
|
struct qm1d1c0042_config *cfg;
|
|
struct dvb_frontend *fe;
|
|
|
|
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
|
if (!state)
|
|
return -ENOMEM;
|
|
state->i2c = client;
|
|
|
|
cfg = client->dev.platform_data;
|
|
fe = cfg->fe;
|
|
fe->tuner_priv = state;
|
|
qm1d1c0042_set_config(fe, cfg);
|
|
memcpy(&fe->ops.tuner_ops, &qm1d1c0042_ops, sizeof(qm1d1c0042_ops));
|
|
|
|
i2c_set_clientdata(client, &state->cfg);
|
|
dev_info(&client->dev, "Sharp QM1D1C0042 attached.\n");
|
|
return 0;
|
|
}
|
|
|
|
static int qm1d1c0042_remove(struct i2c_client *client)
|
|
{
|
|
struct qm1d1c0042_state *state;
|
|
|
|
state = cfg_to_state(i2c_get_clientdata(client));
|
|
state->cfg.fe->tuner_priv = NULL;
|
|
kfree(state);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct i2c_device_id qm1d1c0042_id[] = {
|
|
{"qm1d1c0042", 0},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, qm1d1c0042_id);
|
|
|
|
static struct i2c_driver qm1d1c0042_driver = {
|
|
.driver = {
|
|
.name = "qm1d1c0042",
|
|
},
|
|
.probe = qm1d1c0042_probe,
|
|
.remove = qm1d1c0042_remove,
|
|
.id_table = qm1d1c0042_id,
|
|
};
|
|
|
|
module_i2c_driver(qm1d1c0042_driver);
|
|
|
|
MODULE_DESCRIPTION("Sharp QM1D1C0042 tuner");
|
|
MODULE_AUTHOR("Akihiro TSUKADA");
|
|
MODULE_LICENSE("GPL");
|