// SPDX-License-Identifier: GPL-2.0-or-later
/*
* stv6110.c
*
* Driver for ST STV6110 satellite tuner IC.
*
* Copyright (C) 2009 NetUP Inc.
* Copyright (C) 2009 Igor M. Liplianin <liplianin@netup.ru>
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/dvb/frontend.h>
#include <linux/types.h>
#include "stv6110.h"
/* Max transfer size done by I2C transfer functions */
#define MAX_XFER_SIZE 64
static int debug;
struct stv6110_priv {
int i2c_address;
struct i2c_adapter *i2c;
u32 mclk;
u8 clk_div;
u8 gain;
u8 regs[8 ];
};
#define dprintk(args...) \
do { \
if (debug) \
printk(KERN_DEBUG args); \
} while (0 )
static s32 abssub(s32 a, s32 b)
{
if (a > b)
return a - b;
else
return b - a;
};
static void stv6110_release(struct dvb_frontend *fe)
{
kfree(fe->tuner_priv);
fe->tuner_priv = NULL;
}
static int stv6110_write_regs(struct dvb_frontend *fe, u8 buf[],
int start, int len)
{
struct stv6110_priv *priv = fe->tuner_priv;
int rc;
u8 cmdbuf[MAX_XFER_SIZE];
struct i2c_msg msg = {
.addr = priv->i2c_address,
.flags = 0 ,
.buf = cmdbuf,
.len = len + 1
};
dprintk("%s\n" , __func__);
if (1 + len > sizeof (cmdbuf)) {
printk(KERN_WARNING
"%s: i2c wr: len=%d is too big!\n" ,
KBUILD_MODNAME, len);
return -EINVAL;
}
if (start + len > 8 )
return -EINVAL;
memcpy(&cmdbuf[1 ], buf, len);
cmdbuf[0 ] = start;
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 1 );
rc = i2c_transfer(priv->i2c, &msg, 1 );
if (rc != 1 )
dprintk("%s: i2c error\n" , __func__);
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 0 );
return 0 ;
}
static int stv6110_read_regs(struct dvb_frontend *fe, u8 regs[],
int start, int len)
{
struct stv6110_priv *priv = fe->tuner_priv;
int rc;
u8 reg[] = { start };
struct i2c_msg msg[] = {
{
.addr = priv->i2c_address,
.flags = 0 ,
.buf = reg,
.len = 1 ,
}, {
.addr = priv->i2c_address,
.flags = I2C_M_RD,
.buf = regs,
.len = len,
},
};
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 1 );
rc = i2c_transfer(priv->i2c, msg, 2 );
if (rc != 2 )
dprintk("%s: i2c error\n" , __func__);
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 0 );
memcpy(&priv->regs[start], regs, len);
return 0 ;
}
static int stv6110_read_reg(struct dvb_frontend *fe, int start)
{
u8 buf[] = { 0 };
stv6110_read_regs(fe, buf, start, 1 );
return buf[0 ];
}
static int stv6110_sleep(struct dvb_frontend *fe)
{
u8 reg[] = { 0 };
stv6110_write_regs(fe, reg, 0 , 1 );
return 0 ;
}
static u32 carrier_width(u32 symbol_rate, enum fe_rolloff rolloff)
{
u32 rlf;
switch (rolloff) {
case ROLLOFF_20:
rlf = 20 ;
break ;
case ROLLOFF_25:
rlf = 25 ;
break ;
default :
rlf = 35 ;
break ;
}
return symbol_rate + ((symbol_rate * rlf) / 100 );
}
static int stv6110_set_bandwidth(struct dvb_frontend *fe, u32 bandwidth)
{
struct stv6110_priv *priv = fe->tuner_priv;
u8 r8, ret = 0 x04;
int i;
if ((bandwidth / 2 ) > 36000000 ) /*BW/2 max=31+5=36 mhz for r8=31*/
r8 = 31 ;
else if ((bandwidth / 2 ) < 5000000 ) /* BW/2 min=5Mhz for F=0 */
r8 = 0 ;
else /*if 5 < BW/2 < 36*/
r8 = (bandwidth / 2 ) / 1000000 - 5 ;
/* ctrl3, RCCLKOFF = 0 Activate the calibration Clock */
/* ctrl3, CF = r8 Set the LPF value */
priv->regs[RSTV6110_CTRL3] &= ~((1 << 6 ) | 0 x1f);
priv->regs[RSTV6110_CTRL3] |= (r8 & 0 x1f);
stv6110_write_regs(fe, &priv->regs[RSTV6110_CTRL3], RSTV6110_CTRL3, 1 );
/* stat1, CALRCSTRT = 1 Start LPF auto calibration*/
priv->regs[RSTV6110_STAT1] |= 0 x02;
stv6110_write_regs(fe, &priv->regs[RSTV6110_STAT1], RSTV6110_STAT1, 1 );
i = 0 ;
/* Wait for CALRCSTRT == 0 */
while ((i < 10 ) && (ret != 0 )) {
ret = ((stv6110_read_reg(fe, RSTV6110_STAT1)) & 0 x02);
mdelay(1 ); /* wait for LPF auto calibration */
i++;
}
/* RCCLKOFF = 1 calibration done, deactivate the calibration Clock */
priv->regs[RSTV6110_CTRL3] |= (1 << 6 );
stv6110_write_regs(fe, &priv->regs[RSTV6110_CTRL3], RSTV6110_CTRL3, 1 );
return 0 ;
}
static int stv6110_init(struct dvb_frontend *fe)
{
struct stv6110_priv *priv = fe->tuner_priv;
u8 buf0[] = { 0 x07, 0 x11, 0 xdc, 0 x85, 0 x17, 0 x01, 0 xe6, 0 x1e };
memcpy(priv->regs, buf0, 8 );
/* K = (Reference / 1000000) - 16 */
priv->regs[RSTV6110_CTRL1] &= ~(0 x1f << 3 );
priv->regs[RSTV6110_CTRL1] |=
((((priv->mclk / 1000000 ) - 16 ) & 0 x1f) << 3 );
/* divisor value for the output clock */
priv->regs[RSTV6110_CTRL2] &= ~0 xc0;
priv->regs[RSTV6110_CTRL2] |= (priv->clk_div << 6 );
stv6110_write_regs(fe, &priv->regs[RSTV6110_CTRL1], RSTV6110_CTRL1, 8 );
msleep(1 );
stv6110_set_bandwidth(fe, 72000000 );
return 0 ;
}
static int stv6110_get_frequency(struct dvb_frontend *fe, u32 *frequency)
{
struct stv6110_priv *priv = fe->tuner_priv;
u32 nbsteps, divider, psd2, freq;
u8 regs[] = { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 };
stv6110_read_regs(fe, regs, 0 , 8 );
/*N*/
divider = (priv->regs[RSTV6110_TUNING2] & 0 x0f) << 8 ;
divider += priv->regs[RSTV6110_TUNING1];
/*R*/
nbsteps = (priv->regs[RSTV6110_TUNING2] >> 6 ) & 3 ;
/*p*/
psd2 = (priv->regs[RSTV6110_TUNING2] >> 4 ) & 1 ;
freq = divider * (priv->mclk / 1000 );
freq /= (1 << (nbsteps + psd2));
freq /= 4 ;
*frequency = freq;
return 0 ;
}
static int stv6110_set_frequency(struct dvb_frontend *fe, u32 frequency)
{
struct stv6110_priv *priv = fe->tuner_priv;
u8 ret = 0 x04;
u32 divider, ref, p, presc, i, result_freq, vco_freq;
s32 p_calc, p_calc_opt = 1000 , r_div, r_div_opt = 0 , p_val;
dprintk("%s, freq=%d kHz, mclk=%d Hz\n" , __func__,
frequency, priv->mclk);
/* K = (Reference / 1000000) - 16 */
priv->regs[RSTV6110_CTRL1] &= ~(0 x1f << 3 );
priv->regs[RSTV6110_CTRL1] |=
((((priv->mclk / 1000000 ) - 16 ) & 0 x1f) << 3 );
/* BB_GAIN = db/2 */
priv->regs[RSTV6110_CTRL2] &= ~0 x0f;
priv->regs[RSTV6110_CTRL2] |= (priv->gain & 0 x0f);
if (frequency <= 1023000 ) {
p = 1 ;
presc = 0 ;
} else if (frequency <= 1300000 ) {
p = 1 ;
presc = 1 ;
} else if (frequency <= 2046000 ) {
p = 0 ;
presc = 0 ;
} else {
p = 0 ;
presc = 1 ;
}
/* DIV4SEL = p*/
priv->regs[RSTV6110_TUNING2] &= ~(1 << 4 );
priv->regs[RSTV6110_TUNING2] |= (p << 4 );
/* PRESC32ON = presc */
priv->regs[RSTV6110_TUNING2] &= ~(1 << 5 );
priv->regs[RSTV6110_TUNING2] |= (presc << 5 );
p_val = (int )(1 << (p + 1 )) * 10 ;/* P = 2 or P = 4 */
for (r_div = 0 ; r_div <= 3 ; r_div++) {
p_calc = (priv->mclk / 100000 );
p_calc /= (1 << (r_div + 1 ));
if ((abssub(p_calc, p_val)) < (abssub(p_calc_opt, p_val)))
r_div_opt = r_div;
p_calc_opt = (priv->mclk / 100000 );
p_calc_opt /= (1 << (r_div_opt + 1 ));
}
ref = priv->mclk / ((1 << (r_div_opt + 1 )) * (1 << (p + 1 )));
divider = (((frequency * 1000 ) + (ref >> 1 )) / ref);
/* RDIV = r_div_opt */
priv->regs[RSTV6110_TUNING2] &= ~(3 << 6 );
priv->regs[RSTV6110_TUNING2] |= (((r_div_opt) & 3 ) << 6 );
/* NDIV_MSB = MSB(divider) */
priv->regs[RSTV6110_TUNING2] &= ~0 x0f;
priv->regs[RSTV6110_TUNING2] |= (((divider) >> 8 ) & 0 x0f);
/* NDIV_LSB, LSB(divider) */
priv->regs[RSTV6110_TUNING1] = (divider & 0 xff);
/* CALVCOSTRT = 1 VCO Auto Calibration */
priv->regs[RSTV6110_STAT1] |= 0 x04;
stv6110_write_regs(fe, &priv->regs[RSTV6110_CTRL1],
RSTV6110_CTRL1, 8 );
i = 0 ;
/* Wait for CALVCOSTRT == 0 */
while ((i < 10 ) && (ret != 0 )) {
ret = ((stv6110_read_reg(fe, RSTV6110_STAT1)) & 0 x04);
msleep(1 ); /* wait for VCO auto calibration */
i++;
}
ret = stv6110_read_reg(fe, RSTV6110_STAT1);
stv6110_get_frequency(fe, &result_freq);
vco_freq = divider * ((priv->mclk / 1000 ) / ((1 << (r_div_opt + 1 ))));
dprintk("%s, stat1=%x, lo_freq=%d kHz, vco_frec=%d kHz\n" , __func__,
ret, result_freq, vco_freq);
return 0 ;
}
static int stv6110_set_params(struct dvb_frontend *fe)
{
struct dtv_frontend_properties *c = &fe->dtv_property_cache;
u32 bandwidth = carrier_width(c->symbol_rate, c->rolloff);
stv6110_set_frequency(fe, c->frequency);
stv6110_set_bandwidth(fe, bandwidth);
return 0 ;
}
static int stv6110_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
{
struct stv6110_priv *priv = fe->tuner_priv;
u8 r8 = 0 ;
u8 regs[] = { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 };
stv6110_read_regs(fe, regs, 0 , 8 );
/* CF */
r8 = priv->regs[RSTV6110_CTRL3] & 0 x1f;
*bandwidth = (r8 + 5 ) * 2000000 ;/* x2 for ZIF tuner BW/2 = F+5 Mhz */
return 0 ;
}
static const struct dvb_tuner_ops stv6110_tuner_ops = {
.info = {
.name = "ST STV6110" ,
.frequency_min_hz = 950 * MHz,
.frequency_max_hz = 2150 * MHz,
.frequency_step_hz = 1 * MHz,
},
.init = stv6110_init,
.release = stv6110_release,
.sleep = stv6110_sleep,
.set_params = stv6110_set_params,
.get_frequency = stv6110_get_frequency,
.set_frequency = stv6110_set_frequency,
.get_bandwidth = stv6110_get_bandwidth,
.set_bandwidth = stv6110_set_bandwidth,
};
struct dvb_frontend *stv6110_attach(struct dvb_frontend *fe,
const struct stv6110_config *config,
struct i2c_adapter *i2c)
{
struct stv6110_priv *priv = NULL;
u8 reg0[] = { 0 x00, 0 x07, 0 x11, 0 xdc, 0 x85, 0 x17, 0 x01, 0 xe6, 0 x1e };
struct i2c_msg msg[] = {
{
.addr = config->i2c_address,
.flags = 0 ,
.buf = reg0,
.len = 9
}
};
int ret;
/* divisor value for the output clock */
reg0[2 ] &= ~0 xc0;
reg0[2 ] |= (config->clk_div << 6 );
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 1 );
ret = i2c_transfer(i2c, msg, 1 );
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 0 );
if (ret != 1 )
return NULL;
priv = kzalloc(sizeof (struct stv6110_priv), GFP_KERNEL);
if (priv == NULL)
return NULL;
priv->i2c_address = config->i2c_address;
priv->i2c = i2c;
priv->mclk = config->mclk;
priv->clk_div = config->clk_div;
priv->gain = config->gain;
memcpy(&priv->regs, ®0[1 ], 8 );
memcpy(&fe->ops.tuner_ops, &stv6110_tuner_ops,
sizeof (struct dvb_tuner_ops));
fe->tuner_priv = priv;
printk(KERN_INFO "STV6110 attached on addr=%x!\n" , priv->i2c_address);
return fe;
}
EXPORT_SYMBOL_GPL(stv6110_attach);
module_param(debug, int , 0644 );
MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off)." );
MODULE_DESCRIPTION("ST STV6110 driver" );
MODULE_AUTHOR("Igor M. Liplianin" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=94 H=88 G=90
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-08)
¤
*© Formatika GbR, Deutschland