2007-07-29 02:34:52 +04:00
/*
Samsung S5H1409 VSB / QAM demodulator driver
2008-09-04 00:12:12 +04:00
Copyright ( C ) 2006 Steven Toth < stoth @ linuxtv . org >
2007-07-29 02:34:52 +04:00
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# 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 "dvb_frontend.h"
# include "s5h1409.h"
struct s5h1409_state {
2008-10-17 03:27:11 +04:00
struct i2c_adapter * i2c ;
2007-07-29 02:34:52 +04:00
/* configuration settings */
2008-10-17 03:27:11 +04:00
const struct s5h1409_config * config ;
2007-07-29 02:34:52 +04:00
struct dvb_frontend frontend ;
/* previous uncorrected block counter */
fe_modulation_t current_modulation ;
u32 current_frequency ;
2007-12-13 16:04:10 +03:00
int if_freq ;
2007-10-25 04:05:51 +04:00
u32 is_qam_locked ;
2009-10-28 07:26:05 +03:00
/* QAM tuning state goes through the following state transitions */
# define QAM_STATE_UNTUNED 0
# define QAM_STATE_TUNING_STARTED 1
# define QAM_STATE_INTERLEAVE_SET 2
# define QAM_STATE_QAM_OPTIMIZED_L1 3
# define QAM_STATE_QAM_OPTIMIZED_L2 4
# define QAM_STATE_QAM_OPTIMIZED_L3 5
u8 qam_state ;
2007-07-29 02:34:52 +04:00
} ;
2008-04-22 21:41:48 +04:00
static int debug ;
2008-10-17 03:27:11 +04:00
module_param ( debug , int , 0644 ) ;
MODULE_PARM_DESC ( debug , " Enable verbose debug messages " ) ;
2007-07-29 02:34:52 +04:00
# define dprintk if (debug) printk
/* Register values to initialise the demod, this will set VSB by default */
static struct init_tab {
u8 reg ;
u16 data ;
} init_tab [ ] = {
{ 0x00 , 0x0071 , } ,
{ 0x01 , 0x3213 , } ,
{ 0x09 , 0x0025 , } ,
{ 0x1c , 0x001d , } ,
{ 0x1f , 0x002d , } ,
{ 0x20 , 0x001d , } ,
{ 0x22 , 0x0022 , } ,
{ 0x23 , 0x0020 , } ,
{ 0x29 , 0x110f , } ,
{ 0x2a , 0x10b4 , } ,
{ 0x2b , 0x10ae , } ,
{ 0x2c , 0x0031 , } ,
{ 0x31 , 0x010d , } ,
{ 0x32 , 0x0100 , } ,
{ 0x44 , 0x0510 , } ,
{ 0x54 , 0x0104 , } ,
{ 0x58 , 0x2222 , } ,
{ 0x59 , 0x1162 , } ,
{ 0x5a , 0x3211 , } ,
{ 0x5d , 0x0370 , } ,
{ 0x5e , 0x0296 , } ,
{ 0x61 , 0x0010 , } ,
{ 0x63 , 0x4a00 , } ,
{ 0x65 , 0x0800 , } ,
{ 0x71 , 0x0003 , } ,
{ 0x72 , 0x0470 , } ,
{ 0x81 , 0x0002 , } ,
{ 0x82 , 0x0600 , } ,
{ 0x86 , 0x0002 , } ,
{ 0x8a , 0x2c38 , } ,
{ 0x8b , 0x2a37 , } ,
{ 0x92 , 0x302f , } ,
{ 0x93 , 0x3332 , } ,
{ 0x96 , 0x000c , } ,
{ 0x99 , 0x0101 , } ,
{ 0x9c , 0x2e37 , } ,
{ 0x9d , 0x2c37 , } ,
{ 0x9e , 0x2c37 , } ,
{ 0xab , 0x0100 , } ,
{ 0xac , 0x1003 , } ,
{ 0xad , 0x103f , } ,
{ 0xe2 , 0x0100 , } ,
2008-01-16 03:35:22 +03:00
{ 0xe3 , 0x1000 , } ,
2007-07-29 02:34:52 +04:00
{ 0x28 , 0x1010 , } ,
{ 0xb1 , 0x000e , } ,
} ;
/* VSB SNR lookup table */
static struct vsb_snr_tab {
u16 val ;
u16 data ;
} vsb_snr_tab [ ] = {
2007-12-13 04:14:00 +03:00
{ 924 , 300 , } ,
2007-07-29 02:34:52 +04:00
{ 923 , 300 , } ,
{ 918 , 295 , } ,
{ 915 , 290 , } ,
{ 911 , 285 , } ,
{ 906 , 280 , } ,
{ 901 , 275 , } ,
{ 896 , 270 , } ,
{ 891 , 265 , } ,
{ 885 , 260 , } ,
{ 879 , 255 , } ,
{ 873 , 250 , } ,
{ 864 , 245 , } ,
{ 858 , 240 , } ,
{ 850 , 235 , } ,
{ 841 , 230 , } ,
{ 832 , 225 , } ,
{ 823 , 220 , } ,
{ 812 , 215 , } ,
{ 802 , 210 , } ,
{ 788 , 205 , } ,
{ 778 , 200 , } ,
{ 767 , 195 , } ,
{ 753 , 190 , } ,
{ 740 , 185 , } ,
{ 725 , 180 , } ,
{ 707 , 175 , } ,
{ 689 , 170 , } ,
{ 671 , 165 , } ,
{ 656 , 160 , } ,
{ 637 , 155 , } ,
{ 616 , 150 , } ,
{ 542 , 145 , } ,
{ 519 , 140 , } ,
{ 507 , 135 , } ,
{ 497 , 130 , } ,
{ 492 , 125 , } ,
{ 474 , 120 , } ,
{ 300 , 111 , } ,
{ 0 , 0 , } ,
} ;
/* QAM64 SNR lookup table */
static struct qam64_snr_tab {
u16 val ;
u16 data ;
} qam64_snr_tab [ ] = {
2007-12-13 04:14:00 +03:00
{ 1 , 0 , } ,
2007-07-29 02:34:52 +04:00
{ 12 , 300 , } ,
{ 15 , 290 , } ,
{ 18 , 280 , } ,
{ 22 , 270 , } ,
{ 23 , 268 , } ,
{ 24 , 266 , } ,
{ 25 , 264 , } ,
{ 27 , 262 , } ,
{ 28 , 260 , } ,
{ 29 , 258 , } ,
{ 30 , 256 , } ,
{ 32 , 254 , } ,
{ 33 , 252 , } ,
{ 34 , 250 , } ,
{ 35 , 249 , } ,
{ 36 , 248 , } ,
{ 37 , 247 , } ,
{ 38 , 246 , } ,
{ 39 , 245 , } ,
{ 40 , 244 , } ,
{ 41 , 243 , } ,
{ 42 , 241 , } ,
{ 43 , 240 , } ,
{ 44 , 239 , } ,
{ 45 , 238 , } ,
{ 46 , 237 , } ,
{ 47 , 236 , } ,
{ 48 , 235 , } ,
{ 49 , 234 , } ,
{ 50 , 233 , } ,
{ 51 , 232 , } ,
{ 52 , 231 , } ,
{ 53 , 230 , } ,
{ 55 , 229 , } ,
{ 56 , 228 , } ,
{ 57 , 227 , } ,
{ 58 , 226 , } ,
{ 59 , 225 , } ,
{ 60 , 224 , } ,
{ 62 , 223 , } ,
{ 63 , 222 , } ,
{ 65 , 221 , } ,
{ 66 , 220 , } ,
{ 68 , 219 , } ,
{ 69 , 218 , } ,
{ 70 , 217 , } ,
{ 72 , 216 , } ,
{ 73 , 215 , } ,
{ 75 , 214 , } ,
{ 76 , 213 , } ,
{ 78 , 212 , } ,
{ 80 , 211 , } ,
{ 81 , 210 , } ,
{ 83 , 209 , } ,
{ 84 , 208 , } ,
{ 85 , 207 , } ,
{ 87 , 206 , } ,
{ 89 , 205 , } ,
{ 91 , 204 , } ,
{ 93 , 203 , } ,
{ 95 , 202 , } ,
{ 96 , 201 , } ,
{ 104 , 200 , } ,
2007-12-13 04:14:00 +03:00
{ 255 , 0 , } ,
2007-07-29 02:34:52 +04:00
} ;
/* QAM256 SNR lookup table */
static struct qam256_snr_tab {
u16 val ;
u16 data ;
} qam256_snr_tab [ ] = {
2007-12-13 04:14:00 +03:00
{ 1 , 0 , } ,
2007-07-29 02:34:52 +04:00
{ 12 , 400 , } ,
{ 13 , 390 , } ,
{ 15 , 380 , } ,
{ 17 , 360 , } ,
{ 19 , 350 , } ,
{ 22 , 348 , } ,
{ 23 , 346 , } ,
{ 24 , 344 , } ,
{ 25 , 342 , } ,
{ 26 , 340 , } ,
{ 27 , 336 , } ,
{ 28 , 334 , } ,
{ 29 , 332 , } ,
{ 30 , 330 , } ,
{ 31 , 328 , } ,
{ 32 , 326 , } ,
{ 33 , 325 , } ,
{ 34 , 322 , } ,
{ 35 , 320 , } ,
{ 37 , 318 , } ,
{ 39 , 316 , } ,
{ 40 , 314 , } ,
{ 41 , 312 , } ,
{ 42 , 310 , } ,
{ 43 , 308 , } ,
{ 46 , 306 , } ,
{ 47 , 304 , } ,
{ 49 , 302 , } ,
{ 51 , 300 , } ,
{ 53 , 298 , } ,
{ 54 , 297 , } ,
{ 55 , 296 , } ,
{ 56 , 295 , } ,
{ 57 , 294 , } ,
{ 59 , 293 , } ,
{ 60 , 292 , } ,
{ 61 , 291 , } ,
{ 63 , 290 , } ,
{ 64 , 289 , } ,
{ 65 , 288 , } ,
{ 66 , 287 , } ,
{ 68 , 286 , } ,
{ 69 , 285 , } ,
{ 71 , 284 , } ,
{ 72 , 283 , } ,
{ 74 , 282 , } ,
{ 75 , 281 , } ,
{ 76 , 280 , } ,
{ 77 , 279 , } ,
{ 78 , 278 , } ,
{ 81 , 277 , } ,
{ 83 , 276 , } ,
{ 84 , 275 , } ,
{ 86 , 274 , } ,
{ 87 , 273 , } ,
{ 89 , 272 , } ,
{ 90 , 271 , } ,
{ 92 , 270 , } ,
{ 93 , 269 , } ,
{ 95 , 268 , } ,
{ 96 , 267 , } ,
{ 98 , 266 , } ,
{ 100 , 265 , } ,
{ 102 , 264 , } ,
{ 104 , 263 , } ,
{ 105 , 262 , } ,
{ 106 , 261 , } ,
{ 110 , 260 , } ,
2007-12-13 04:14:00 +03:00
{ 255 , 0 , } ,
2007-07-29 02:34:52 +04:00
} ;
/* 8 bit registers, 16 bit values */
2008-10-17 03:27:11 +04:00
static int s5h1409_writereg ( struct s5h1409_state * state , u8 reg , u16 data )
2007-07-29 02:34:52 +04:00
{
int ret ;
2008-10-17 03:27:11 +04:00
u8 buf [ ] = { reg , data > > 8 , data & 0xff } ;
2007-07-29 02:34:52 +04:00
2007-07-29 03:02:55 +04:00
struct i2c_msg msg = { . addr = state - > config - > demod_address ,
. flags = 0 , . buf = buf , . len = 3 } ;
2007-07-29 02:34:52 +04:00
ret = i2c_transfer ( state - > i2c , & msg , 1 ) ;
if ( ret ! = 1 )
2008-10-17 03:27:11 +04:00
printk ( KERN_ERR " %s: error (reg == 0x%02x, val == 0x%04x, "
2008-04-09 06:20:00 +04:00
" ret == %i) \n " , __func__ , reg , data , ret ) ;
2007-07-29 02:34:52 +04:00
return ( ret ! = 1 ) ? - 1 : 0 ;
}
2008-10-17 03:27:11 +04:00
static u16 s5h1409_readreg ( struct s5h1409_state * state , u8 reg )
2007-07-29 02:34:52 +04:00
{
int ret ;
2008-10-17 03:27:11 +04:00
u8 b0 [ ] = { reg } ;
u8 b1 [ ] = { 0 , 0 } ;
2007-07-29 02:34:52 +04:00
2008-10-17 03:27:11 +04:00
struct i2c_msg msg [ ] = {
2007-07-29 03:02:55 +04:00
{ . addr = state - > config - > demod_address , . flags = 0 ,
. buf = b0 , . len = 1 } ,
{ . addr = state - > config - > demod_address , . flags = I2C_M_RD ,
. buf = b1 , . len = 2 } } ;
2007-07-29 02:34:52 +04:00
ret = i2c_transfer ( state - > i2c , msg , 2 ) ;
if ( ret ! = 2 )
2008-04-09 06:20:00 +04:00
printk ( " %s: readreg error (ret == %i) \n " , __func__ , ret ) ;
2007-07-29 02:34:52 +04:00
return ( b1 [ 0 ] < < 8 ) | b1 [ 1 ] ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_softreset ( struct dvb_frontend * fe )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
2008-04-09 06:20:00 +04:00
dprintk ( " %s() \n " , __func__ ) ;
2007-07-29 02:34:52 +04:00
s5h1409_writereg ( state , 0xf5 , 0 ) ;
s5h1409_writereg ( state , 0xf5 , 1 ) ;
2007-10-25 04:05:51 +04:00
state - > is_qam_locked = 0 ;
2009-10-28 07:26:05 +03:00
state - > qam_state = QAM_STATE_UNTUNED ;
2007-07-29 02:34:52 +04:00
return 0 ;
}
2007-12-13 16:04:10 +03:00
# define S5H1409_VSB_IF_FREQ 5380
2008-10-17 03:27:11 +04:00
# define S5H1409_QAM_IF_FREQ (state->config->qam_if)
2007-12-13 16:04:10 +03:00
2008-10-17 03:27:11 +04:00
static int s5h1409_set_if_freq ( struct dvb_frontend * fe , int KHz )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
2008-04-09 06:20:00 +04:00
dprintk ( " %s(%d KHz) \n " , __func__ , KHz ) ;
2007-07-29 02:34:52 +04:00
2007-12-13 16:11:49 +03:00
switch ( KHz ) {
case 4000 :
2007-10-25 04:05:51 +04:00
s5h1409_writereg ( state , 0x87 , 0x014b ) ;
s5h1409_writereg ( state , 0x88 , 0x0cb5 ) ;
s5h1409_writereg ( state , 0x89 , 0x03e2 ) ;
2007-12-13 16:11:49 +03:00
break ;
case 5380 :
case 44000 :
default :
s5h1409_writereg ( state , 0x87 , 0x01be ) ;
s5h1409_writereg ( state , 0x88 , 0x0436 ) ;
s5h1409_writereg ( state , 0x89 , 0x054d ) ;
break ;
2007-07-29 02:34:52 +04:00
}
2007-12-13 16:11:49 +03:00
state - > if_freq = KHz ;
2007-07-29 02:34:52 +04:00
2007-12-13 16:11:49 +03:00
return 0 ;
2007-07-29 02:34:52 +04:00
}
2008-10-17 03:27:11 +04:00
static int s5h1409_set_spectralinversion ( struct dvb_frontend * fe , int inverted )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
2008-04-09 06:20:00 +04:00
dprintk ( " %s(%d) \n " , __func__ , inverted ) ;
2007-07-29 02:34:52 +04:00
2008-10-17 03:27:11 +04:00
if ( inverted = = 1 )
2007-07-29 02:34:52 +04:00
return s5h1409_writereg ( state , 0x1b , 0x1101 ) ; /* Inverted */
else
return s5h1409_writereg ( state , 0x1b , 0x0110 ) ; /* Normal */
}
2008-10-17 03:27:11 +04:00
static int s5h1409_enable_modulation ( struct dvb_frontend * fe ,
2007-07-29 03:02:55 +04:00
fe_modulation_t m )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
2008-04-09 06:20:00 +04:00
dprintk ( " %s(0x%08x) \n " , __func__ , m ) ;
2007-07-29 02:34:52 +04:00
2008-10-17 03:27:11 +04:00
switch ( m ) {
2007-07-29 02:34:52 +04:00
case VSB_8 :
2008-04-09 06:20:00 +04:00
dprintk ( " %s() VSB_8 \n " , __func__ ) ;
2007-12-13 16:04:10 +03:00
if ( state - > if_freq ! = S5H1409_VSB_IF_FREQ )
s5h1409_set_if_freq ( fe , S5H1409_VSB_IF_FREQ ) ;
2007-07-29 02:34:52 +04:00
s5h1409_writereg ( state , 0xf4 , 0 ) ;
break ;
case QAM_64 :
case QAM_256 :
2008-09-04 00:12:12 +04:00
case QAM_AUTO :
2008-04-09 06:20:00 +04:00
dprintk ( " %s() QAM_AUTO (64/256) \n " , __func__ ) ;
2007-12-13 16:04:10 +03:00
if ( state - > if_freq ! = S5H1409_QAM_IF_FREQ )
s5h1409_set_if_freq ( fe , S5H1409_QAM_IF_FREQ ) ;
2007-07-29 02:34:52 +04:00
s5h1409_writereg ( state , 0xf4 , 1 ) ;
2007-10-25 04:05:51 +04:00
s5h1409_writereg ( state , 0x85 , 0x110 ) ;
2007-07-29 02:34:52 +04:00
break ;
default :
2008-04-09 06:20:00 +04:00
dprintk ( " %s() Invalid modulation \n " , __func__ ) ;
2007-07-29 02:34:52 +04:00
return - EINVAL ;
}
state - > current_modulation = m ;
s5h1409_softreset ( fe ) ;
return 0 ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_i2c_gate_ctrl ( struct dvb_frontend * fe , int enable )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
2008-04-09 06:20:00 +04:00
dprintk ( " %s(%d) \n " , __func__ , enable ) ;
2007-07-29 02:34:52 +04:00
if ( enable )
return s5h1409_writereg ( state , 0xf3 , 1 ) ;
else
return s5h1409_writereg ( state , 0xf3 , 0 ) ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_set_gpio ( struct dvb_frontend * fe , int enable )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
2008-04-09 06:20:00 +04:00
dprintk ( " %s(%d) \n " , __func__ , enable ) ;
2007-07-29 02:34:52 +04:00
if ( enable )
2008-01-16 03:35:22 +03:00
return s5h1409_writereg ( state , 0xe3 ,
s5h1409_readreg ( state , 0xe3 ) | 0x1100 ) ;
2007-07-29 02:34:52 +04:00
else
2008-01-16 03:35:22 +03:00
return s5h1409_writereg ( state , 0xe3 ,
2008-04-03 05:14:41 +04:00
s5h1409_readreg ( state , 0xe3 ) & 0xfeff ) ;
2007-07-29 02:34:52 +04:00
}
2008-10-17 03:27:11 +04:00
static int s5h1409_sleep ( struct dvb_frontend * fe , int enable )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
2008-04-09 06:20:00 +04:00
dprintk ( " %s(%d) \n " , __func__ , enable ) ;
2007-07-29 02:34:52 +04:00
return s5h1409_writereg ( state , 0xf2 , enable ) ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_register_reset ( struct dvb_frontend * fe )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
2008-04-09 06:20:00 +04:00
dprintk ( " %s() \n " , __func__ ) ;
2007-07-29 02:34:52 +04:00
return s5h1409_writereg ( state , 0xfa , 0 ) ;
}
2007-10-25 04:05:51 +04:00
static void s5h1409_set_qam_amhum_mode ( struct dvb_frontend * fe )
{
struct s5h1409_state * state = fe - > demodulator_priv ;
u16 reg ;
2009-10-28 07:26:05 +03:00
if ( state - > qam_state < QAM_STATE_INTERLEAVE_SET ) {
/* We should not perform amhum optimization until
the interleave mode has been configured */
return ;
}
if ( state - > qam_state = = QAM_STATE_QAM_OPTIMIZED_L3 ) {
/* We've already reached the maximum optimization level, so
dont bother banging on the status registers */
return ;
}
/* QAM EQ lock check */
reg = s5h1409_readreg ( state , 0xf0 ) ;
if ( ( reg > > 13 ) & 0x1 ) {
reg & = 0xff ;
s5h1409_writereg ( state , 0x96 , 0x000c ) ;
if ( reg < 0x68 ) {
if ( state - > qam_state < QAM_STATE_QAM_OPTIMIZED_L3 ) {
dprintk ( " %s() setting QAM state to OPT_L3 \n " ,
__func__ ) ;
s5h1409_writereg ( state , 0x93 , 0x3130 ) ;
s5h1409_writereg ( state , 0x9e , 0x2836 ) ;
state - > qam_state = QAM_STATE_QAM_OPTIMIZED_L3 ;
}
} else {
if ( state - > qam_state < QAM_STATE_QAM_OPTIMIZED_L2 ) {
dprintk ( " %s() setting QAM state to OPT_L2 \n " ,
__func__ ) ;
s5h1409_writereg ( state , 0x93 , 0x3332 ) ;
s5h1409_writereg ( state , 0x9e , 0x2c37 ) ;
state - > qam_state = QAM_STATE_QAM_OPTIMIZED_L2 ;
}
}
} else {
if ( state - > qam_state < QAM_STATE_QAM_OPTIMIZED_L1 ) {
dprintk ( " %s() setting QAM state to OPT_L1 \n " , __func__ ) ;
s5h1409_writereg ( state , 0x96 , 0x0008 ) ;
s5h1409_writereg ( state , 0x93 , 0x3332 ) ;
s5h1409_writereg ( state , 0x9e , 0x2c37 ) ;
state - > qam_state = QAM_STATE_QAM_OPTIMIZED_L1 ;
}
}
}
static void s5h1409_set_qam_amhum_mode_legacy ( struct dvb_frontend * fe )
{
struct s5h1409_state * state = fe - > demodulator_priv ;
u16 reg ;
2007-10-25 04:05:51 +04:00
if ( state - > is_qam_locked )
return ;
/* QAM EQ lock check */
reg = s5h1409_readreg ( state , 0xf0 ) ;
if ( ( reg > > 13 ) & 0x1 ) {
state - > is_qam_locked = 1 ;
reg & = 0xff ;
s5h1409_writereg ( state , 0x96 , 0x00c ) ;
2008-10-17 03:27:11 +04:00
if ( ( reg < 0x38 ) | | ( reg > 0x68 ) ) {
2007-10-25 04:05:51 +04:00
s5h1409_writereg ( state , 0x93 , 0x3332 ) ;
s5h1409_writereg ( state , 0x9e , 0x2c37 ) ;
} else {
s5h1409_writereg ( state , 0x93 , 0x3130 ) ;
s5h1409_writereg ( state , 0x9e , 0x2836 ) ;
}
} else {
s5h1409_writereg ( state , 0x96 , 0x0008 ) ;
s5h1409_writereg ( state , 0x93 , 0x3332 ) ;
s5h1409_writereg ( state , 0x9e , 0x2c37 ) ;
}
}
static void s5h1409_set_qam_interleave_mode ( struct dvb_frontend * fe )
{
struct s5h1409_state * state = fe - > demodulator_priv ;
u16 reg , reg1 , reg2 ;
2009-10-28 07:26:05 +03:00
if ( state - > qam_state > = QAM_STATE_INTERLEAVE_SET ) {
/* We've done the optimization already */
return ;
}
reg = s5h1409_readreg ( state , 0xf1 ) ;
/* Master lock */
if ( ( reg > > 15 ) & 0x1 ) {
if ( state - > qam_state = = QAM_STATE_UNTUNED | |
state - > qam_state = = QAM_STATE_TUNING_STARTED ) {
dprintk ( " %s() setting QAM state to INTERLEAVE_SET \n " ,
__func__ ) ;
reg1 = s5h1409_readreg ( state , 0xb2 ) ;
reg2 = s5h1409_readreg ( state , 0xad ) ;
s5h1409_writereg ( state , 0x96 , 0x0020 ) ;
s5h1409_writereg ( state , 0xad ,
( ( ( reg1 & 0xf000 ) > > 4 ) | ( reg2 & 0xf0ff ) ) ) ;
state - > qam_state = QAM_STATE_INTERLEAVE_SET ;
}
} else {
if ( state - > qam_state = = QAM_STATE_UNTUNED ) {
dprintk ( " %s() setting QAM state to TUNING_STARTED \n " ,
__func__ ) ;
s5h1409_writereg ( state , 0x96 , 0x08 ) ;
s5h1409_writereg ( state , 0xab ,
s5h1409_readreg ( state , 0xab ) | 0x1001 ) ;
state - > qam_state = QAM_STATE_TUNING_STARTED ;
}
}
}
static void s5h1409_set_qam_interleave_mode_legacy ( struct dvb_frontend * fe )
{
struct s5h1409_state * state = fe - > demodulator_priv ;
u16 reg , reg1 , reg2 ;
2007-10-25 04:05:51 +04:00
reg = s5h1409_readreg ( state , 0xf1 ) ;
/* Master lock */
if ( ( reg > > 15 ) & 0x1 ) {
if ( state - > qam_state ! = 2 ) {
state - > qam_state = 2 ;
reg1 = s5h1409_readreg ( state , 0xb2 ) ;
reg2 = s5h1409_readreg ( state , 0xad ) ;
s5h1409_writereg ( state , 0x96 , 0x20 ) ;
s5h1409_writereg ( state , 0xad ,
2008-10-17 03:27:11 +04:00
( ( ( reg1 & 0xf000 ) > > 4 ) | ( reg2 & 0xf0ff ) ) ) ;
2008-01-16 03:35:22 +03:00
s5h1409_writereg ( state , 0xab ,
s5h1409_readreg ( state , 0xab ) & 0xeffe ) ;
2007-10-25 04:05:51 +04:00
}
} else {
if ( state - > qam_state ! = 1 ) {
state - > qam_state = 1 ;
s5h1409_writereg ( state , 0x96 , 0x08 ) ;
2008-01-16 03:35:22 +03:00
s5h1409_writereg ( state , 0xab ,
s5h1409_readreg ( state , 0xab ) | 0x1001 ) ;
2007-10-25 04:05:51 +04:00
}
}
}
2007-07-29 02:34:52 +04:00
/* Talk to the demod, set the FEC, GUARD, QAM settings etc */
2008-10-17 03:27:11 +04:00
static int s5h1409_set_frontend ( struct dvb_frontend * fe ,
2007-07-29 03:02:55 +04:00
struct dvb_frontend_parameters * p )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
2008-04-09 06:20:00 +04:00
dprintk ( " %s(frequency=%d) \n " , __func__ , p - > frequency ) ;
2007-07-29 02:34:52 +04:00
s5h1409_softreset ( fe ) ;
state - > current_frequency = p - > frequency ;
s5h1409_enable_modulation ( fe , p - > u . vsb . modulation ) ;
if ( fe - > ops . tuner_ops . set_params ) {
2008-10-17 03:27:11 +04:00
if ( fe - > ops . i2c_gate_ctrl )
fe - > ops . i2c_gate_ctrl ( fe , 1 ) ;
2007-07-29 02:34:52 +04:00
fe - > ops . tuner_ops . set_params ( fe , p ) ;
2008-10-17 03:27:11 +04:00
if ( fe - > ops . i2c_gate_ctrl )
fe - > ops . i2c_gate_ctrl ( fe , 0 ) ;
2007-07-29 02:34:52 +04:00
}
2009-01-26 09:07:59 +03:00
/* Issue a reset to the demod so it knows to resync against the
newly tuned frequency */
s5h1409_softreset ( fe ) ;
2009-10-28 07:26:05 +03:00
/* Optimize the demod for QAM */
if ( state - > current_modulation ! = VSB_8 ) {
/* This almost certainly applies to all boards, but for now
only do it for the HVR - 1600. Once the other boards are
tested , the " legacy " versions can just go away */
if ( state - > config - > hvr1600_opt = = S5H1409_HVR1600_OPTIMIZE ) {
s5h1409_set_qam_interleave_mode ( fe ) ;
2009-11-02 07:30:40 +03:00
s5h1409_set_qam_amhum_mode ( fe ) ;
2009-10-28 07:26:05 +03:00
} else {
s5h1409_set_qam_amhum_mode_legacy ( fe ) ;
s5h1409_set_qam_interleave_mode_legacy ( fe ) ;
}
}
2007-07-29 02:34:52 +04:00
return 0 ;
}
2008-01-16 03:35:22 +03:00
static int s5h1409_set_mpeg_timing ( struct dvb_frontend * fe , int mode )
{
struct s5h1409_state * state = fe - > demodulator_priv ;
u16 val ;
2008-04-09 06:20:00 +04:00
dprintk ( " %s(%d) \n " , __func__ , mode ) ;
2008-01-16 03:35:22 +03:00
val = s5h1409_readreg ( state , 0xac ) & 0xcfff ;
switch ( mode ) {
case S5H1409_MPEGTIMING_CONTINOUS_INVERTING_CLOCK :
val | = 0x0000 ;
break ;
case S5H1409_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK :
2008-04-09 06:20:00 +04:00
dprintk ( " %s(%d) Mode1 or Defaulting \n " , __func__ , mode ) ;
2008-01-16 03:35:22 +03:00
val | = 0x1000 ;
break ;
case S5H1409_MPEGTIMING_NONCONTINOUS_INVERTING_CLOCK :
val | = 0x2000 ;
break ;
case S5H1409_MPEGTIMING_NONCONTINOUS_NONINVERTING_CLOCK :
val | = 0x3000 ;
break ;
default :
return - EINVAL ;
}
/* Configure MPEG Signal Timing charactistics */
return s5h1409_writereg ( state , 0xac , val ) ;
}
2007-07-29 02:34:52 +04:00
/* Reset the demod hardware and reset all of the configuration registers
to a default state . */
2008-10-17 03:27:11 +04:00
static int s5h1409_init ( struct dvb_frontend * fe )
2007-07-29 02:34:52 +04:00
{
int i ;
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2008-04-09 06:20:00 +04:00
dprintk ( " %s() \n " , __func__ ) ;
2007-07-29 02:34:52 +04:00
s5h1409_sleep ( fe , 0 ) ;
s5h1409_register_reset ( fe ) ;
2008-10-17 03:27:11 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( init_tab ) ; i + + )
2007-07-29 02:34:52 +04:00
s5h1409_writereg ( state , init_tab [ i ] . reg , init_tab [ i ] . data ) ;
/* The datasheet says that after initialisation, VSB is default */
state - > current_modulation = VSB_8 ;
2009-10-28 05:33:21 +03:00
/* Optimize for the HVR-1600 if appropriate. Note that some of these
may get folded into the generic case after testing with other
devices */
if ( state - > config - > hvr1600_opt = = S5H1409_HVR1600_OPTIMIZE ) {
/* VSB AGC REF */
s5h1409_writereg ( state , 0x09 , 0x0050 ) ;
/* Unknown but Windows driver does it... */
s5h1409_writereg ( state , 0x21 , 0x0001 ) ;
s5h1409_writereg ( state , 0x50 , 0x030e ) ;
/* QAM AGC REF */
s5h1409_writereg ( state , 0x82 , 0x0800 ) ;
}
2007-07-29 02:34:52 +04:00
if ( state - > config - > output_mode = = S5H1409_SERIAL_OUTPUT )
2008-01-16 03:35:22 +03:00
s5h1409_writereg ( state , 0xab ,
s5h1409_readreg ( state , 0xab ) | 0x100 ) ; /* Serial */
2007-07-29 02:34:52 +04:00
else
2008-01-16 03:35:22 +03:00
s5h1409_writereg ( state , 0xab ,
s5h1409_readreg ( state , 0xab ) & 0xfeff ) ; /* Parallel */
2007-07-29 02:34:52 +04:00
s5h1409_set_spectralinversion ( fe , state - > config - > inversion ) ;
2007-12-13 16:04:10 +03:00
s5h1409_set_if_freq ( fe , state - > if_freq ) ;
2007-07-29 02:34:52 +04:00
s5h1409_set_gpio ( fe , state - > config - > gpio ) ;
2008-01-16 03:35:22 +03:00
s5h1409_set_mpeg_timing ( fe , state - > config - > mpeg_timing ) ;
2007-07-29 02:34:52 +04:00
s5h1409_softreset ( fe ) ;
2007-10-25 04:05:51 +04:00
/* Note: Leaving the I2C gate closed. */
s5h1409_i2c_gate_ctrl ( fe , 0 ) ;
2007-07-29 02:34:52 +04:00
return 0 ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_read_status ( struct dvb_frontend * fe , fe_status_t * status )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
u16 reg ;
u32 tuner_status = 0 ;
* status = 0 ;
2009-10-28 07:26:05 +03:00
/* Optimize the demod for QAM */
if ( state - > current_modulation ! = VSB_8 ) {
/* This almost certainly applies to all boards, but for now
only do it for the HVR - 1600. Once the other boards are
tested , the " legacy " versions can just go away */
if ( state - > config - > hvr1600_opt = = S5H1409_HVR1600_OPTIMIZE ) {
s5h1409_set_qam_interleave_mode ( fe ) ;
2009-11-02 07:30:40 +03:00
s5h1409_set_qam_amhum_mode ( fe ) ;
2009-10-28 07:26:05 +03:00
}
}
2007-07-29 02:34:52 +04:00
/* Get the demodulator status */
reg = s5h1409_readreg ( state , 0xf1 ) ;
2008-10-17 03:27:11 +04:00
if ( reg & 0x1000 )
2007-07-29 02:34:52 +04:00
* status | = FE_HAS_VITERBI ;
2008-10-17 03:27:11 +04:00
if ( reg & 0x8000 )
2007-07-29 02:34:52 +04:00
* status | = FE_HAS_LOCK | FE_HAS_SYNC ;
2008-10-17 03:27:11 +04:00
switch ( state - > config - > status_mode ) {
2007-07-29 02:34:52 +04:00
case S5H1409_DEMODLOCKING :
if ( * status & FE_HAS_VITERBI )
* status | = FE_HAS_CARRIER | FE_HAS_SIGNAL ;
break ;
case S5H1409_TUNERLOCKING :
/* Get the tuner status */
if ( fe - > ops . tuner_ops . get_status ) {
2007-07-29 03:02:55 +04:00
if ( fe - > ops . i2c_gate_ctrl )
fe - > ops . i2c_gate_ctrl ( fe , 1 ) ;
2007-07-29 02:34:52 +04:00
fe - > ops . tuner_ops . get_status ( fe , & tuner_status ) ;
2007-07-29 03:02:55 +04:00
if ( fe - > ops . i2c_gate_ctrl )
fe - > ops . i2c_gate_ctrl ( fe , 0 ) ;
2007-07-29 02:34:52 +04:00
}
if ( tuner_status )
* status | = FE_HAS_CARRIER | FE_HAS_SIGNAL ;
break ;
}
2008-04-09 06:20:00 +04:00
dprintk ( " %s() status 0x%08x \n " , __func__ , * status ) ;
2007-07-29 02:34:52 +04:00
return 0 ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_qam256_lookup_snr ( struct dvb_frontend * fe , u16 * snr , u16 v )
2007-07-29 02:34:52 +04:00
{
int i , ret = - EINVAL ;
2008-04-09 06:20:00 +04:00
dprintk ( " %s() \n " , __func__ ) ;
2007-07-29 02:34:52 +04:00
2008-10-17 03:27:11 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( qam256_snr_tab ) ; i + + ) {
2007-07-29 02:34:52 +04:00
if ( v < qam256_snr_tab [ i ] . val ) {
* snr = qam256_snr_tab [ i ] . data ;
ret = 0 ;
break ;
}
}
return ret ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_qam64_lookup_snr ( struct dvb_frontend * fe , u16 * snr , u16 v )
2007-07-29 02:34:52 +04:00
{
int i , ret = - EINVAL ;
2008-04-09 06:20:00 +04:00
dprintk ( " %s() \n " , __func__ ) ;
2007-07-29 02:34:52 +04:00
2008-10-17 03:27:11 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( qam64_snr_tab ) ; i + + ) {
2007-07-29 02:34:52 +04:00
if ( v < qam64_snr_tab [ i ] . val ) {
* snr = qam64_snr_tab [ i ] . data ;
ret = 0 ;
break ;
}
}
return ret ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_vsb_lookup_snr ( struct dvb_frontend * fe , u16 * snr , u16 v )
2007-07-29 02:34:52 +04:00
{
int i , ret = - EINVAL ;
2008-04-09 06:20:00 +04:00
dprintk ( " %s() \n " , __func__ ) ;
2007-07-29 02:34:52 +04:00
2008-10-17 03:27:11 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( vsb_snr_tab ) ; i + + ) {
2007-07-29 02:34:52 +04:00
if ( v > vsb_snr_tab [ i ] . val ) {
* snr = vsb_snr_tab [ i ] . data ;
ret = 0 ;
break ;
}
}
2008-04-09 06:20:00 +04:00
dprintk ( " %s() snr=%d \n " , __func__ , * snr ) ;
2007-07-29 02:34:52 +04:00
return ret ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_read_snr ( struct dvb_frontend * fe , u16 * snr )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
u16 reg ;
2008-04-09 06:20:00 +04:00
dprintk ( " %s() \n " , __func__ ) ;
2007-07-29 02:34:52 +04:00
2008-10-17 03:27:11 +04:00
switch ( state - > current_modulation ) {
2007-07-29 02:34:52 +04:00
case QAM_64 :
2007-12-13 04:14:00 +03:00
reg = s5h1409_readreg ( state , 0xf0 ) & 0xff ;
2007-07-29 02:34:52 +04:00
return s5h1409_qam64_lookup_snr ( fe , snr , reg ) ;
case QAM_256 :
2007-12-13 04:14:00 +03:00
reg = s5h1409_readreg ( state , 0xf0 ) & 0xff ;
2007-07-29 02:34:52 +04:00
return s5h1409_qam256_lookup_snr ( fe , snr , reg ) ;
case VSB_8 :
2007-12-13 04:14:00 +03:00
reg = s5h1409_readreg ( state , 0xf1 ) & 0x3ff ;
2007-07-29 02:34:52 +04:00
return s5h1409_vsb_lookup_snr ( fe , snr , reg ) ;
default :
break ;
}
return - EINVAL ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_read_signal_strength ( struct dvb_frontend * fe ,
u16 * signal_strength )
2007-07-29 02:34:52 +04:00
{
return s5h1409_read_snr ( fe , signal_strength ) ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_read_ucblocks ( struct dvb_frontend * fe , u32 * ucblocks )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
* ucblocks = s5h1409_readreg ( state , 0xb5 ) ;
return 0 ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_read_ber ( struct dvb_frontend * fe , u32 * ber )
2007-07-29 02:34:52 +04:00
{
return s5h1409_read_ucblocks ( fe , ber ) ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_get_frontend ( struct dvb_frontend * fe ,
2007-07-29 03:02:55 +04:00
struct dvb_frontend_parameters * p )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
p - > frequency = state - > current_frequency ;
p - > u . vsb . modulation = state - > current_modulation ;
return 0 ;
}
2008-10-17 03:27:11 +04:00
static int s5h1409_get_tune_settings ( struct dvb_frontend * fe ,
2007-07-29 03:02:55 +04:00
struct dvb_frontend_tune_settings * tune )
2007-07-29 02:34:52 +04:00
{
tune - > min_delay_ms = 1000 ;
return 0 ;
}
2008-10-17 03:27:11 +04:00
static void s5h1409_release ( struct dvb_frontend * fe )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = fe - > demodulator_priv ;
2007-07-29 02:34:52 +04:00
kfree ( state ) ;
}
static struct dvb_frontend_ops s5h1409_ops ;
2008-10-17 03:27:11 +04:00
struct dvb_frontend * s5h1409_attach ( const struct s5h1409_config * config ,
struct i2c_adapter * i2c )
2007-07-29 02:34:52 +04:00
{
2008-10-17 03:27:11 +04:00
struct s5h1409_state * state = NULL ;
2008-01-10 09:43:11 +03:00
u16 reg ;
2007-07-29 02:34:52 +04:00
/* allocate memory for the internal state */
2009-08-11 05:51:01 +04:00
state = kzalloc ( sizeof ( struct s5h1409_state ) , GFP_KERNEL ) ;
2007-07-29 02:34:52 +04:00
if ( state = = NULL )
goto error ;
/* setup the state */
state - > config = config ;
state - > i2c = i2c ;
state - > current_modulation = 0 ;
2007-12-13 16:04:10 +03:00
state - > if_freq = S5H1409_VSB_IF_FREQ ;
2007-07-29 02:34:52 +04:00
/* check if the demod exists */
2008-01-10 09:43:11 +03:00
reg = s5h1409_readreg ( state , 0x04 ) ;
if ( ( reg ! = 0x0066 ) & & ( reg ! = 0x007f ) )
2007-07-29 02:34:52 +04:00
goto error ;
/* create dvb_frontend */
2007-07-29 03:02:55 +04:00
memcpy ( & state - > frontend . ops , & s5h1409_ops ,
sizeof ( struct dvb_frontend_ops ) ) ;
2007-07-29 02:34:52 +04:00
state - > frontend . demodulator_priv = state ;
2008-01-10 09:43:11 +03:00
if ( s5h1409_init ( & state - > frontend ) ! = 0 ) {
printk ( KERN_ERR " %s: Failed to initialize correctly \n " ,
2008-04-09 06:20:00 +04:00
__func__ ) ;
2008-01-10 09:43:11 +03:00
goto error ;
}
2007-07-29 02:34:52 +04:00
/* Note: Leaving the I2C gate open here. */
2008-01-10 09:43:11 +03:00
s5h1409_i2c_gate_ctrl ( & state - > frontend , 1 ) ;
2007-07-29 02:34:52 +04:00
return & state - > frontend ;
error :
kfree ( state ) ;
return NULL ;
}
2008-10-17 03:27:11 +04:00
EXPORT_SYMBOL ( s5h1409_attach ) ;
2007-07-29 02:34:52 +04:00
static struct dvb_frontend_ops s5h1409_ops = {
. info = {
. name = " Samsung S5H1409 QAM/8VSB Frontend " ,
. type = FE_ATSC ,
. frequency_min = 54000000 ,
. frequency_max = 858000000 ,
. frequency_stepsize = 62500 ,
. caps = FE_CAN_QAM_64 | FE_CAN_QAM_256 | FE_CAN_8VSB
} ,
. init = s5h1409_init ,
. i2c_gate_ctrl = s5h1409_i2c_gate_ctrl ,
. set_frontend = s5h1409_set_frontend ,
. get_frontend = s5h1409_get_frontend ,
. get_tune_settings = s5h1409_get_tune_settings ,
. read_status = s5h1409_read_status ,
. read_ber = s5h1409_read_ber ,
. read_signal_strength = s5h1409_read_signal_strength ,
. read_snr = s5h1409_read_snr ,
. read_ucblocks = s5h1409_read_ucblocks ,
. release = s5h1409_release ,
} ;
MODULE_DESCRIPTION ( " Samsung S5H1409 QAM-B/ATSC Demodulator driver " ) ;
MODULE_AUTHOR ( " Steven Toth " ) ;
MODULE_LICENSE ( " GPL " ) ;
2007-07-29 03:02:55 +04:00
/*
* Local variables :
* c - basic - offset : 8
*/