// SPDX-License-Identifier: GPL-2.0-or-later
/*
Samsung S5H1411 VSB/QAM demodulator driver
Copyright (C) 2008 Steven Toth <stoth@linuxtv.org>
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <media/dvb_frontend.h>
#include "s5h1411.h"
struct s5h1411_state {
struct i2c_adapter *i2c;
/* configuration settings */
const struct s5h1411_config *config;
struct dvb_frontend frontend;
enum fe_modulation current_modulation;
unsigned int first_tune:1 ;
u32 current_frequency;
int if_freq;
u8 inversion;
};
static int debug;
#define dprintk(arg...) do { \
if (debug) \
printk(arg); \
} while (0 )
/* Register values to initialise the demod, defaults to VSB */
static struct init_tab {
u8 addr;
u8 reg;
u16 data;
} init_tab[] = {
{ S5H1411_I2C_TOP_ADDR, 0 x00, 0 x0071, },
{ S5H1411_I2C_TOP_ADDR, 0 x08, 0 x0047, },
{ S5H1411_I2C_TOP_ADDR, 0 x1c, 0 x0400, },
{ S5H1411_I2C_TOP_ADDR, 0 x1e, 0 x0370, },
{ S5H1411_I2C_TOP_ADDR, 0 x1f, 0 x342c, },
{ S5H1411_I2C_TOP_ADDR, 0 x24, 0 x0231, },
{ S5H1411_I2C_TOP_ADDR, 0 x25, 0 x1011, },
{ S5H1411_I2C_TOP_ADDR, 0 x26, 0 x0f07, },
{ S5H1411_I2C_TOP_ADDR, 0 x27, 0 x0f04, },
{ S5H1411_I2C_TOP_ADDR, 0 x28, 0 x070f, },
{ S5H1411_I2C_TOP_ADDR, 0 x29, 0 x2820, },
{ S5H1411_I2C_TOP_ADDR, 0 x2a, 0 x102e, },
{ S5H1411_I2C_TOP_ADDR, 0 x2b, 0 x0220, },
{ S5H1411_I2C_TOP_ADDR, 0 x2e, 0 x0d0e, },
{ S5H1411_I2C_TOP_ADDR, 0 x2f, 0 x1013, },
{ S5H1411_I2C_TOP_ADDR, 0 x31, 0 x171b, },
{ S5H1411_I2C_TOP_ADDR, 0 x32, 0 x0e0f, },
{ S5H1411_I2C_TOP_ADDR, 0 x33, 0 x0f10, },
{ S5H1411_I2C_TOP_ADDR, 0 x34, 0 x170e, },
{ S5H1411_I2C_TOP_ADDR, 0 x35, 0 x4b10, },
{ S5H1411_I2C_TOP_ADDR, 0 x36, 0 x0f17, },
{ S5H1411_I2C_TOP_ADDR, 0 x3c, 0 x1577, },
{ S5H1411_I2C_TOP_ADDR, 0 x3d, 0 x081a, },
{ S5H1411_I2C_TOP_ADDR, 0 x3e, 0 x77ee, },
{ S5H1411_I2C_TOP_ADDR, 0 x40, 0 x1e09, },
{ S5H1411_I2C_TOP_ADDR, 0 x41, 0 x0f0c, },
{ S5H1411_I2C_TOP_ADDR, 0 x42, 0 x1f10, },
{ S5H1411_I2C_TOP_ADDR, 0 x4d, 0 x0509, },
{ S5H1411_I2C_TOP_ADDR, 0 x4e, 0 x0a00, },
{ S5H1411_I2C_TOP_ADDR, 0 x50, 0 x0000, },
{ S5H1411_I2C_TOP_ADDR, 0 x5b, 0 x0000, },
{ S5H1411_I2C_TOP_ADDR, 0 x5c, 0 x0008, },
{ S5H1411_I2C_TOP_ADDR, 0 x57, 0 x1101, },
{ S5H1411_I2C_TOP_ADDR, 0 x65, 0 x007c, },
{ S5H1411_I2C_TOP_ADDR, 0 x68, 0 x0512, },
{ S5H1411_I2C_TOP_ADDR, 0 x69, 0 x0258, },
{ S5H1411_I2C_TOP_ADDR, 0 x70, 0 x0004, },
{ S5H1411_I2C_TOP_ADDR, 0 x71, 0 x0007, },
{ S5H1411_I2C_TOP_ADDR, 0 x76, 0 x00a9, },
{ S5H1411_I2C_TOP_ADDR, 0 x78, 0 x3141, },
{ S5H1411_I2C_TOP_ADDR, 0 x7a, 0 x3141, },
{ S5H1411_I2C_TOP_ADDR, 0 xb3, 0 x8003, },
{ S5H1411_I2C_TOP_ADDR, 0 xb5, 0 xa6bb, },
{ S5H1411_I2C_TOP_ADDR, 0 xb6, 0 x0609, },
{ S5H1411_I2C_TOP_ADDR, 0 xb7, 0 x2f06, },
{ S5H1411_I2C_TOP_ADDR, 0 xb8, 0 x003f, },
{ S5H1411_I2C_TOP_ADDR, 0 xb9, 0 x2700, },
{ S5H1411_I2C_TOP_ADDR, 0 xba, 0 xfac8, },
{ S5H1411_I2C_TOP_ADDR, 0 xbe, 0 x1003, },
{ S5H1411_I2C_TOP_ADDR, 0 xbf, 0 x103f, },
{ S5H1411_I2C_TOP_ADDR, 0 xce, 0 x2000, },
{ S5H1411_I2C_TOP_ADDR, 0 xcf, 0 x0800, },
{ S5H1411_I2C_TOP_ADDR, 0 xd0, 0 x0800, },
{ S5H1411_I2C_TOP_ADDR, 0 xd1, 0 x0400, },
{ S5H1411_I2C_TOP_ADDR, 0 xd2, 0 x0800, },
{ S5H1411_I2C_TOP_ADDR, 0 xd3, 0 x2000, },
{ S5H1411_I2C_TOP_ADDR, 0 xd4, 0 x3000, },
{ S5H1411_I2C_TOP_ADDR, 0 xdb, 0 x4a9b, },
{ S5H1411_I2C_TOP_ADDR, 0 xdc, 0 x1000, },
{ S5H1411_I2C_TOP_ADDR, 0 xde, 0 x0001, },
{ S5H1411_I2C_TOP_ADDR, 0 xdf, 0 x0000, },
{ S5H1411_I2C_TOP_ADDR, 0 xe3, 0 x0301, },
{ S5H1411_I2C_QAM_ADDR, 0 xf3, 0 x0000, },
{ S5H1411_I2C_QAM_ADDR, 0 xf3, 0 x0001, },
{ S5H1411_I2C_QAM_ADDR, 0 x08, 0 x0600, },
{ S5H1411_I2C_QAM_ADDR, 0 x18, 0 x4201, },
{ S5H1411_I2C_QAM_ADDR, 0 x1e, 0 x6476, },
{ S5H1411_I2C_QAM_ADDR, 0 x21, 0 x0830, },
{ S5H1411_I2C_QAM_ADDR, 0 x0c, 0 x5679, },
{ S5H1411_I2C_QAM_ADDR, 0 x0d, 0 x579b, },
{ S5H1411_I2C_QAM_ADDR, 0 x24, 0 x0102, },
{ S5H1411_I2C_QAM_ADDR, 0 x31, 0 x7488, },
{ S5H1411_I2C_QAM_ADDR, 0 x32, 0 x0a08, },
{ S5H1411_I2C_QAM_ADDR, 0 x3d, 0 x8689, },
{ S5H1411_I2C_QAM_ADDR, 0 x49, 0 x0048, },
{ S5H1411_I2C_QAM_ADDR, 0 x57, 0 x2012, },
{ S5H1411_I2C_QAM_ADDR, 0 x5d, 0 x7676, },
{ S5H1411_I2C_QAM_ADDR, 0 x04, 0 x0400, },
{ S5H1411_I2C_QAM_ADDR, 0 x58, 0 x00c0, },
{ S5H1411_I2C_QAM_ADDR, 0 x5b, 0 x0100, },
};
/* VSB SNR lookup table */
static struct vsb_snr_tab {
u16 val;
u16 data;
} vsb_snr_tab[] = {
{ 0 x39f, 300 , },
{ 0 x39b, 295 , },
{ 0 x397, 290 , },
{ 0 x394, 285 , },
{ 0 x38f, 280 , },
{ 0 x38b, 275 , },
{ 0 x387, 270 , },
{ 0 x382, 265 , },
{ 0 x37d, 260 , },
{ 0 x377, 255 , },
{ 0 x370, 250 , },
{ 0 x36a, 245 , },
{ 0 x364, 240 , },
{ 0 x35b, 235 , },
{ 0 x353, 230 , },
{ 0 x349, 225 , },
{ 0 x340, 220 , },
{ 0 x337, 215 , },
{ 0 x327, 210 , },
{ 0 x31b, 205 , },
{ 0 x310, 200 , },
{ 0 x302, 195 , },
{ 0 x2f3, 190 , },
{ 0 x2e4, 185 , },
{ 0 x2d7, 180 , },
{ 0 x2cd, 175 , },
{ 0 x2bb, 170 , },
{ 0 x2a9, 165 , },
{ 0 x29e, 160 , },
{ 0 x284, 155 , },
{ 0 x27a, 150 , },
{ 0 x260, 145 , },
{ 0 x23a, 140 , },
{ 0 x224, 135 , },
{ 0 x213, 130 , },
{ 0 x204, 125 , },
{ 0 x1fe, 120 , },
{ 0 , 0 , },
};
/* QAM64 SNR lookup table */
static struct qam64_snr_tab {
u16 val;
u16 data;
} qam64_snr_tab[] = {
{ 0 x0001, 0 , },
{ 0 x0af0, 300 , },
{ 0 x0d80, 290 , },
{ 0 x10a0, 280 , },
{ 0 x14b5, 270 , },
{ 0 x1590, 268 , },
{ 0 x1680, 266 , },
{ 0 x17b0, 264 , },
{ 0 x18c0, 262 , },
{ 0 x19b0, 260 , },
{ 0 x1ad0, 258 , },
{ 0 x1d00, 256 , },
{ 0 x1da0, 254 , },
{ 0 x1ef0, 252 , },
{ 0 x2050, 250 , },
{ 0 x20f0, 249 , },
{ 0 x21d0, 248 , },
{ 0 x22b0, 247 , },
{ 0 x23a0, 246 , },
{ 0 x2470, 245 , },
{ 0 x24f0, 244 , },
{ 0 x25a0, 243 , },
{ 0 x26c0, 242 , },
{ 0 x27b0, 241 , },
{ 0 x28d0, 240 , },
{ 0 x29b0, 239 , },
{ 0 x2ad0, 238 , },
{ 0 x2ba0, 237 , },
{ 0 x2c80, 236 , },
{ 0 x2d20, 235 , },
{ 0 x2e00, 234 , },
{ 0 x2f10, 233 , },
{ 0 x3050, 232 , },
{ 0 x3190, 231 , },
{ 0 x3300, 230 , },
{ 0 x3340, 229 , },
{ 0 x3200, 228 , },
{ 0 x3550, 227 , },
{ 0 x3610, 226 , },
{ 0 x3600, 225 , },
{ 0 x3700, 224 , },
{ 0 x3800, 223 , },
{ 0 x3920, 222 , },
{ 0 x3a20, 221 , },
{ 0 x3b30, 220 , },
{ 0 x3d00, 219 , },
{ 0 x3e00, 218 , },
{ 0 x4000, 217 , },
{ 0 x4100, 216 , },
{ 0 x4300, 215 , },
{ 0 x4400, 214 , },
{ 0 x4600, 213 , },
{ 0 x4700, 212 , },
{ 0 x4800, 211 , },
{ 0 x4a00, 210 , },
{ 0 x4b00, 209 , },
{ 0 x4d00, 208 , },
{ 0 x4f00, 207 , },
{ 0 x5050, 206 , },
{ 0 x5200, 205 , },
{ 0 x53c0, 204 , },
{ 0 x5450, 203 , },
{ 0 x5650, 202 , },
{ 0 x5820, 201 , },
{ 0 x6000, 200 , },
{ 0 xffff, 0 , },
};
/* QAM256 SNR lookup table */
static struct qam256_snr_tab {
u16 val;
u16 data;
} qam256_snr_tab[] = {
{ 0 x0001, 0 , },
{ 0 x0970, 400 , },
{ 0 x0a90, 390 , },
{ 0 x0b90, 380 , },
{ 0 x0d90, 370 , },
{ 0 x0ff0, 360 , },
{ 0 x1240, 350 , },
{ 0 x1345, 348 , },
{ 0 x13c0, 346 , },
{ 0 x14c0, 344 , },
{ 0 x1500, 342 , },
{ 0 x1610, 340 , },
{ 0 x1700, 338 , },
{ 0 x1800, 336 , },
{ 0 x18b0, 334 , },
{ 0 x1900, 332 , },
{ 0 x1ab0, 330 , },
{ 0 x1bc0, 328 , },
{ 0 x1cb0, 326 , },
{ 0 x1db0, 324 , },
{ 0 x1eb0, 322 , },
{ 0 x2030, 320 , },
{ 0 x2200, 318 , },
{ 0 x2280, 316 , },
{ 0 x2410, 314 , },
{ 0 x25b0, 312 , },
{ 0 x27a0, 310 , },
{ 0 x2840, 308 , },
{ 0 x29d0, 306 , },
{ 0 x2b10, 304 , },
{ 0 x2d30, 302 , },
{ 0 x2f20, 300 , },
{ 0 x30c0, 298 , },
{ 0 x3260, 297 , },
{ 0 x32c0, 296 , },
{ 0 x3300, 295 , },
{ 0 x33b0, 294 , },
{ 0 x34b0, 293 , },
{ 0 x35a0, 292 , },
{ 0 x3650, 291 , },
{ 0 x3800, 290 , },
{ 0 x3900, 289 , },
{ 0 x3a50, 288 , },
{ 0 x3b30, 287 , },
{ 0 x3cb0, 286 , },
{ 0 x3e20, 285 , },
{ 0 x3fa0, 284 , },
{ 0 x40a0, 283 , },
{ 0 x41c0, 282 , },
{ 0 x42f0, 281 , },
{ 0 x44a0, 280 , },
{ 0 x4600, 279 , },
{ 0 x47b0, 278 , },
{ 0 x4900, 277 , },
{ 0 x4a00, 276 , },
{ 0 x4ba0, 275 , },
{ 0 x4d00, 274 , },
{ 0 x4f00, 273 , },
{ 0 x5000, 272 , },
{ 0 x51f0, 272 , },
{ 0 x53a0, 270 , },
{ 0 x5520, 269 , },
{ 0 x5700, 268 , },
{ 0 x5800, 267 , },
{ 0 x5a00, 266 , },
{ 0 x5c00, 265 , },
{ 0 x5d00, 264 , },
{ 0 x5f00, 263 , },
{ 0 x6000, 262 , },
{ 0 x6200, 261 , },
{ 0 x6400, 260 , },
{ 0 xffff, 0 , },
};
/* 8 bit registers, 16 bit values */
static int s5h1411_writereg(struct s5h1411_state *state,
u8 addr, u8 reg, u16 data)
{
int ret;
u8 buf[] = { reg, data >> 8 , data & 0 xff };
struct i2c_msg msg = { .addr = addr, .flags = 0 , .buf = buf, .len = 3 };
ret = i2c_transfer(state->i2c, &msg, 1 );
if (ret != 1 )
printk(KERN_ERR "%s: writereg error 0x%02x 0x%02x 0x%04x, ret == %i)\n" ,
__func__, addr, reg, data, ret);
return (ret != 1 ) ? -1 : 0 ;
}
static u16 s5h1411_readreg(struct s5h1411_state *state, u8 addr, u8 reg)
{
int ret;
u8 b0[] = { reg };
u8 b1[] = { 0 , 0 };
struct i2c_msg msg[] = {
{ .addr = addr, .flags = 0 , .buf = b0, .len = 1 },
{ .addr = addr, .flags = I2C_M_RD, .buf = b1, .len = 2 } };
ret = i2c_transfer(state->i2c, msg, 2 );
if (ret != 2 )
printk(KERN_ERR "%s: readreg error (ret == %i)\n" ,
__func__, ret);
return (b1[0 ] << 8 ) | b1[1 ];
}
static int s5h1411_softreset(struct dvb_frontend *fe)
{
struct s5h1411_state *state = fe->demodulator_priv;
dprintk("%s()\n" , __func__);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xf7, 0 );
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xf7, 1 );
return 0 ;
}
static int s5h1411_set_if_freq(struct dvb_frontend *fe, int KHz)
{
struct s5h1411_state *state = fe->demodulator_priv;
dprintk("%s(%d KHz)\n" , __func__, KHz);
switch (KHz) {
case 3250 :
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x38, 0 x10d5);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x39, 0 x5342);
s5h1411_writereg(state, S5H1411_I2C_QAM_ADDR, 0 x2c, 0 x10d9);
break ;
case 3500 :
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x38, 0 x1225);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x39, 0 x1e96);
s5h1411_writereg(state, S5H1411_I2C_QAM_ADDR, 0 x2c, 0 x1225);
break ;
case 4000 :
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x38, 0 x14bc);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x39, 0 xb53e);
s5h1411_writereg(state, S5H1411_I2C_QAM_ADDR, 0 x2c, 0 x14bd);
break ;
default :
dprintk("%s(%d KHz) Invalid, defaulting to 5380\n" ,
__func__, KHz);
fallthrough;
case 5380 :
case 44000 :
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x38, 0 x1be4);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x39, 0 x3655);
s5h1411_writereg(state, S5H1411_I2C_QAM_ADDR, 0 x2c, 0 x1be4);
break ;
}
state->if_freq = KHz;
return 0 ;
}
static int s5h1411_set_mpeg_timing(struct dvb_frontend *fe, int mode)
{
struct s5h1411_state *state = fe->demodulator_priv;
u16 val;
dprintk("%s(%d)\n" , __func__, mode);
val = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 xbe) & 0 xcfff;
switch (mode) {
case S5H1411_MPEGTIMING_CONTINUOUS_INVERTING_CLOCK:
val |= 0 x0000;
break ;
case S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK:
dprintk("%s(%d) Mode1 or Defaulting\n" , __func__, mode);
val |= 0 x1000;
break ;
case S5H1411_MPEGTIMING_NONCONTINUOUS_INVERTING_CLOCK:
val |= 0 x2000;
break ;
case S5H1411_MPEGTIMING_NONCONTINUOUS_NONINVERTING_CLOCK:
val |= 0 x3000;
break ;
default :
return -EINVAL;
}
/* Configure MPEG Signal Timing charactistics */
return s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xbe, val);
}
static int s5h1411_set_spectralinversion(struct dvb_frontend *fe, int inversion)
{
struct s5h1411_state *state = fe->demodulator_priv;
u16 val;
dprintk("%s(%d)\n" , __func__, inversion);
val = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 x24) & ~0 x1000;
if (inversion == 1 )
val |= 0 x1000; /* Inverted */
state->inversion = inversion;
return s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x24, val);
}
static int s5h1411_set_serialmode(struct dvb_frontend *fe, int serial)
{
struct s5h1411_state *state = fe->demodulator_priv;
u16 val;
dprintk("%s(%d)\n" , __func__, serial);
val = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 xbd) & ~0 x100;
if (serial == 1 )
val |= 0 x100;
return s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xbd, val);
}
static int s5h1411_enable_modulation(struct dvb_frontend *fe,
enum fe_modulation m)
{
struct s5h1411_state *state = fe->demodulator_priv;
dprintk("%s(0x%08x)\n" , __func__, m);
if ((state->first_tune == 0 ) && (m == state->current_modulation)) {
dprintk("%s() Already at desired modulation. Skipping...\n" ,
__func__);
return 0 ;
}
switch (m) {
case VSB_8:
dprintk("%s() VSB_8\n" , __func__);
s5h1411_set_if_freq(fe, state->config->vsb_if);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x00, 0 x71);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xf6, 0 x00);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xcd, 0 xf1);
break ;
case QAM_64:
case QAM_256:
case QAM_AUTO:
dprintk("%s() QAM_AUTO (64/256)\n" , __func__);
s5h1411_set_if_freq(fe, state->config->qam_if);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 x00, 0 x0171);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xf6, 0 x0001);
s5h1411_writereg(state, S5H1411_I2C_QAM_ADDR, 0 x16, 0 x1101);
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xcd, 0 x00f0);
break ;
default :
dprintk("%s() Invalid modulation\n" , __func__);
return -EINVAL;
}
state->current_modulation = m;
state->first_tune = 0 ;
s5h1411_softreset(fe);
return 0 ;
}
static int s5h1411_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
{
struct s5h1411_state *state = fe->demodulator_priv;
dprintk("%s(%d)\n" , __func__, enable);
if (enable)
return s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xf5, 1 );
else
return s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xf5, 0 );
}
static int s5h1411_set_gpio(struct dvb_frontend *fe, int enable)
{
struct s5h1411_state *state = fe->demodulator_priv;
u16 val;
dprintk("%s(%d)\n" , __func__, enable);
val = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 xe0) & ~0 x02;
if (enable)
return s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xe0,
val | 0 x02);
else
return s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xe0, val);
}
static int s5h1411_set_powerstate(struct dvb_frontend *fe, int enable)
{
struct s5h1411_state *state = fe->demodulator_priv;
dprintk("%s(%d)\n" , __func__, enable);
if (enable)
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xf4, 1 );
else {
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xf4, 0 );
s5h1411_softreset(fe);
}
return 0 ;
}
static int s5h1411_sleep(struct dvb_frontend *fe)
{
return s5h1411_set_powerstate(fe, 1 );
}
static int s5h1411_register_reset(struct dvb_frontend *fe)
{
struct s5h1411_state *state = fe->demodulator_priv;
dprintk("%s()\n" , __func__);
return s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xf3, 0 );
}
/* Talk to the demod, set the FEC, GUARD, QAM settings etc */
static int s5h1411_set_frontend(struct dvb_frontend *fe)
{
struct dtv_frontend_properties *p = &fe->dtv_property_cache;
struct s5h1411_state *state = fe->demodulator_priv;
dprintk("%s(frequency=%d)\n" , __func__, p->frequency);
s5h1411_softreset(fe);
state->current_frequency = p->frequency;
s5h1411_enable_modulation(fe, p->modulation);
if (fe->ops.tuner_ops.set_params) {
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 1 );
fe->ops.tuner_ops.set_params(fe);
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 0 );
}
/* Issue a reset to the demod so it knows to resync against the
newly tuned frequency */
s5h1411_softreset(fe);
return 0 ;
}
/* Reset the demod hardware and reset all of the configuration registers
to a default state. */
static int s5h1411_init(struct dvb_frontend *fe)
{
struct s5h1411_state *state = fe->demodulator_priv;
int i;
dprintk("%s()\n" , __func__);
s5h1411_set_powerstate(fe, 0 );
s5h1411_register_reset(fe);
for (i = 0 ; i < ARRAY_SIZE(init_tab); i++)
s5h1411_writereg(state, init_tab[i].addr,
init_tab[i].reg,
init_tab[i].data);
/* The datasheet says that after initialisation, VSB is default */
state->current_modulation = VSB_8;
/* Although the datasheet says it's in VSB, empirical evidence
shows problems getting lock on the first tuning request. Make
sure we call enable_modulation the first time around */
state->first_tune = 1 ;
if (state->config->output_mode == S5H1411_SERIAL_OUTPUT)
/* Serial */
s5h1411_set_serialmode(fe, 1 );
else
/* Parallel */
s5h1411_set_serialmode(fe, 0 );
s5h1411_set_spectralinversion(fe, state->config->inversion);
s5h1411_set_if_freq(fe, state->config->vsb_if);
s5h1411_set_gpio(fe, state->config->gpio);
s5h1411_set_mpeg_timing(fe, state->config->mpeg_timing);
s5h1411_softreset(fe);
/* Note: Leaving the I2C gate closed. */
s5h1411_i2c_gate_ctrl(fe, 0 );
return 0 ;
}
static int s5h1411_read_status(struct dvb_frontend *fe, enum fe_status *status)
{
struct s5h1411_state *state = fe->demodulator_priv;
u16 reg;
u32 tuner_status = 0 ;
*status = 0 ;
/* Register F2 bit 15 = Master Lock, removed */
switch (state->current_modulation) {
case QAM_64:
case QAM_256:
reg = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 xf0);
if (reg & 0 x10) /* QAM FEC Lock */
*status |= FE_HAS_SYNC | FE_HAS_LOCK;
if (reg & 0 x100) /* QAM EQ Lock */
*status |= FE_HAS_VITERBI | FE_HAS_CARRIER | FE_HAS_SIGNAL;
break ;
case VSB_8:
reg = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 xf2);
if (reg & 0 x1000) /* FEC Lock */
*status |= FE_HAS_SYNC | FE_HAS_LOCK;
if (reg & 0 x2000) /* EQ Lock */
*status |= FE_HAS_VITERBI | FE_HAS_CARRIER | FE_HAS_SIGNAL;
reg = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 x53);
if (reg & 0 x1) /* AFC Lock */
*status |= FE_HAS_SIGNAL;
break ;
default :
return -EINVAL;
}
switch (state->config->status_mode) {
case S5H1411_DEMODLOCKING:
if (*status & FE_HAS_VITERBI)
*status |= FE_HAS_CARRIER | FE_HAS_SIGNAL;
break ;
case S5H1411_TUNERLOCKING:
/* Get the tuner status */
if (fe->ops.tuner_ops.get_status) {
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 1 );
fe->ops.tuner_ops.get_status(fe, &tuner_status);
if (fe->ops.i2c_gate_ctrl)
fe->ops.i2c_gate_ctrl(fe, 0 );
}
if (tuner_status)
*status |= FE_HAS_CARRIER | FE_HAS_SIGNAL;
break ;
}
dprintk("%s() status 0x%08x\n" , __func__, *status);
return 0 ;
}
static int s5h1411_qam256_lookup_snr(struct dvb_frontend *fe, u16 *snr, u16 v)
{
int i, ret = -EINVAL;
dprintk("%s()\n" , __func__);
for (i = 0 ; i < ARRAY_SIZE(qam256_snr_tab); i++) {
if (v < qam256_snr_tab[i].val) {
*snr = qam256_snr_tab[i].data;
ret = 0 ;
break ;
}
}
return ret;
}
static int s5h1411_qam64_lookup_snr(struct dvb_frontend *fe, u16 *snr, u16 v)
{
int i, ret = -EINVAL;
dprintk("%s()\n" , __func__);
for (i = 0 ; i < ARRAY_SIZE(qam64_snr_tab); i++) {
if (v < qam64_snr_tab[i].val) {
*snr = qam64_snr_tab[i].data;
ret = 0 ;
break ;
}
}
return ret;
}
static int s5h1411_vsb_lookup_snr(struct dvb_frontend *fe, u16 *snr, u16 v)
{
int i, ret = -EINVAL;
dprintk("%s()\n" , __func__);
for (i = 0 ; i < ARRAY_SIZE(vsb_snr_tab); i++) {
if (v > vsb_snr_tab[i].val) {
*snr = vsb_snr_tab[i].data;
ret = 0 ;
break ;
}
}
dprintk("%s() snr=%d\n" , __func__, *snr);
return ret;
}
static int s5h1411_read_snr(struct dvb_frontend *fe, u16 *snr)
{
struct s5h1411_state *state = fe->demodulator_priv;
u16 reg;
dprintk("%s()\n" , __func__);
switch (state->current_modulation) {
case QAM_64:
reg = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 xf1);
return s5h1411_qam64_lookup_snr(fe, snr, reg);
case QAM_256:
reg = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 xf1);
return s5h1411_qam256_lookup_snr(fe, snr, reg);
case VSB_8:
reg = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR,
0 xf2) & 0 x3ff;
return s5h1411_vsb_lookup_snr(fe, snr, reg);
default :
break ;
}
return -EINVAL;
}
static int s5h1411_read_signal_strength(struct dvb_frontend *fe,
u16 *signal_strength)
{
/* borrowed from lgdt330x.c
*
* Calculate strength from SNR up to 35dB
* Even though the SNR can go higher than 35dB,
* there is some comfort factor in having a range of
* strong signals that can show at 100%
*/
u16 snr;
u32 tmp;
int ret = s5h1411_read_snr(fe, &snr);
*signal_strength = 0 ;
if (0 == ret) {
/* The following calculation method was chosen
* purely for the sake of code re-use from the
* other demod drivers that use this method */
/* Convert from SNR in dB * 10 to 8.24 fixed-point */
tmp = (snr * ((1 << 24 ) / 10 ));
/* Convert from 8.24 fixed-point to
* scale the range 0 - 35*2^24 into 0 - 65535*/
if (tmp >= 8960 * 0 x10000)
*signal_strength = 0 xffff;
else
*signal_strength = tmp / 8960 ;
}
return ret;
}
static int s5h1411_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
{
struct s5h1411_state *state = fe->demodulator_priv;
*ucblocks = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 xc9);
return 0 ;
}
static int s5h1411_read_ber(struct dvb_frontend *fe, u32 *ber)
{
return s5h1411_read_ucblocks(fe, ber);
}
static int s5h1411_get_frontend(struct dvb_frontend *fe,
struct dtv_frontend_properties *p)
{
struct s5h1411_state *state = fe->demodulator_priv;
p->frequency = state->current_frequency;
p->modulation = state->current_modulation;
return 0 ;
}
static int s5h1411_get_tune_settings(struct dvb_frontend *fe,
struct dvb_frontend_tune_settings *tune)
{
tune->min_delay_ms = 1000 ;
return 0 ;
}
static void s5h1411_release(struct dvb_frontend *fe)
{
struct s5h1411_state *state = fe->demodulator_priv;
kfree(state);
}
static const struct dvb_frontend_ops s5h1411_ops;
struct dvb_frontend *s5h1411_attach(const struct s5h1411_config *config,
struct i2c_adapter *i2c)
{
struct s5h1411_state *state = NULL;
u16 reg;
/* allocate memory for the internal state */
state = kzalloc(sizeof (struct s5h1411_state), GFP_KERNEL);
if (state == NULL)
goto error;
/* setup the state */
state->config = config;
state->i2c = i2c;
state->current_modulation = VSB_8;
state->inversion = state->config->inversion;
/* check if the demod exists */
reg = s5h1411_readreg(state, S5H1411_I2C_TOP_ADDR, 0 x05);
if (reg != 0 x0066)
goto error;
/* create dvb_frontend */
memcpy(&state->frontend.ops, &s5h1411_ops,
sizeof (struct dvb_frontend_ops));
state->frontend.demodulator_priv = state;
if (s5h1411_init(&state->frontend) != 0 ) {
printk(KERN_ERR "%s: Failed to initialize correctly\n" ,
__func__);
goto error;
}
/* Note: Leaving the I2C gate open here. */
s5h1411_writereg(state, S5H1411_I2C_TOP_ADDR, 0 xf5, 1 );
/* Put the device into low-power mode until first use */
s5h1411_set_powerstate(&state->frontend, 1 );
return &state->frontend;
error:
kfree(state);
return NULL;
}
EXPORT_SYMBOL_GPL(s5h1411_attach);
static const struct dvb_frontend_ops s5h1411_ops = {
.delsys = { SYS_ATSC, SYS_DVBC_ANNEX_B },
.info = {
.name = "Samsung S5H1411 QAM/8VSB Frontend" ,
.frequency_min_hz = 54 * MHz,
.frequency_max_hz = 858 * MHz,
.frequency_stepsize_hz = 62500 ,
.caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB
},
.init = s5h1411_init,
.sleep = s5h1411_sleep,
.i2c_gate_ctrl = s5h1411_i2c_gate_ctrl,
.set_frontend = s5h1411_set_frontend,
.get_frontend = s5h1411_get_frontend,
.get_tune_settings = s5h1411_get_tune_settings,
.read_status = s5h1411_read_status,
.read_ber = s5h1411_read_ber,
.read_signal_strength = s5h1411_read_signal_strength,
.read_snr = s5h1411_read_snr,
.read_ucblocks = s5h1411_read_ucblocks,
.release = s5h1411_release,
};
module_param(debug, int , 0644 );
MODULE_PARM_DESC(debug, "Enable verbose debug messages" );
MODULE_DESCRIPTION("Samsung S5H1411 QAM-B/ATSC Demodulator driver" );
MODULE_AUTHOR("Steven Toth" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=95 H=88 G=91
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland