2009-10-26 07:08:17 -03:00
/*
* Support for AltoBeam GB20600 ( a . k . a DMB - TH ) demodulator
* ATBM8830 , ATBM8831
*
* Copyright ( C ) 2009 David T . L . Wong < davidtlwong @ gmail . com >
*
* 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 .
*/
2009-12-03 10:57:02 -03:00
# include <asm/div64.h>
2009-10-26 07:08:17 -03:00
# include "dvb_frontend.h"
# include "atbm8830.h"
# include "atbm8830_priv.h"
# define dprintk(args...) \
do { \
if ( debug ) \
printk ( KERN_DEBUG " atbm8830: " args ) ; \
} while ( 0 )
static int debug ;
module_param ( debug , int , 0644 ) ;
MODULE_PARM_DESC ( debug , " Turn on/off frontend debugging (default:off). " ) ;
static int atbm8830_write_reg ( struct atbm_state * priv , u16 reg , u8 data )
{
int ret = 0 ;
u8 dev_addr ;
u8 buf1 [ ] = { reg > > 8 , reg & 0xFF } ;
u8 buf2 [ ] = { data } ;
struct i2c_msg msg1 = { . flags = 0 , . buf = buf1 , . len = 2 } ;
struct i2c_msg msg2 = { . flags = 0 , . buf = buf2 , . len = 1 } ;
dev_addr = priv - > config - > demod_address ;
msg1 . addr = dev_addr ;
msg2 . addr = dev_addr ;
if ( debug > = 2 )
2010-10-25 23:44:25 -03:00
dprintk ( " %s: reg=0x%04X, data=0x%02X \n " , __func__ , reg , data ) ;
2009-10-26 07:08:17 -03:00
ret = i2c_transfer ( priv - > i2c , & msg1 , 1 ) ;
if ( ret ! = 1 )
return - EIO ;
ret = i2c_transfer ( priv - > i2c , & msg2 , 1 ) ;
return ( ret ! = 1 ) ? - EIO : 0 ;
}
static int atbm8830_read_reg ( struct atbm_state * priv , u16 reg , u8 * p_data )
{
int ret ;
u8 dev_addr ;
u8 buf1 [ ] = { reg > > 8 , reg & 0xFF } ;
u8 buf2 [ ] = { 0 } ;
struct i2c_msg msg1 = { . flags = 0 , . buf = buf1 , . len = 2 } ;
struct i2c_msg msg2 = { . flags = I2C_M_RD , . buf = buf2 , . len = 1 } ;
dev_addr = priv - > config - > demod_address ;
msg1 . addr = dev_addr ;
msg2 . addr = dev_addr ;
ret = i2c_transfer ( priv - > i2c , & msg1 , 1 ) ;
if ( ret ! = 1 ) {
2010-10-25 23:44:25 -03:00
dprintk ( " %s: error reg=0x%04x, ret=%i \n " , __func__ , reg , ret ) ;
2009-10-26 07:08:17 -03:00
return - EIO ;
}
ret = i2c_transfer ( priv - > i2c , & msg2 , 1 ) ;
if ( ret ! = 1 )
return - EIO ;
* p_data = buf2 [ 0 ] ;
if ( debug > = 2 )
2010-10-25 23:44:25 -03:00
dprintk ( " %s: reg=0x%04X, data=0x%02X \n " ,
2009-10-26 07:08:17 -03:00
__func__ , reg , buf2 [ 0 ] ) ;
return 0 ;
}
/* Lock register latch so that multi-register read is atomic */
static inline int atbm8830_reglatch_lock ( struct atbm_state * priv , int lock )
{
return atbm8830_write_reg ( priv , REG_READ_LATCH , lock ? 1 : 0 ) ;
}
static int set_osc_freq ( struct atbm_state * priv , u32 freq /*in kHz*/ )
{
u32 val ;
2009-12-03 10:57:02 -03:00
u64 t ;
2009-10-26 07:08:17 -03:00
2009-12-03 10:57:02 -03:00
/* 0x100000 * freq / 30.4MHz */
t = ( u64 ) 0x100000 * freq ;
do_div ( t , 30400 ) ;
val = t ;
2009-10-26 07:08:17 -03:00
atbm8830_write_reg ( priv , REG_OSC_CLK , val ) ;
atbm8830_write_reg ( priv , REG_OSC_CLK + 1 , val > > 8 ) ;
atbm8830_write_reg ( priv , REG_OSC_CLK + 2 , val > > 16 ) ;
return 0 ;
}
static int set_if_freq ( struct atbm_state * priv , u32 freq /*in kHz*/ )
{
2009-11-25 14:30:53 -03:00
2009-10-26 07:08:17 -03:00
u32 fs = priv - > config - > osc_clk_freq ;
2009-12-03 10:57:02 -03:00
u64 t ;
2009-10-26 07:08:17 -03:00
u32 val ;
u8 dat ;
if ( freq ! = 0 ) {
2009-12-03 10:57:02 -03:00
/* 2 * PI * (freq - fs) / fs * (2 ^ 22) */
t = ( u64 ) 2 * 31416 * ( freq - fs ) ;
t < < = 22 ;
do_div ( t , fs ) ;
do_div ( t , 1000 ) ;
val = t ;
2009-10-26 07:08:17 -03:00
atbm8830_write_reg ( priv , REG_TUNER_BASEBAND , 1 ) ;
atbm8830_write_reg ( priv , REG_IF_FREQ , val ) ;
atbm8830_write_reg ( priv , REG_IF_FREQ + 1 , val > > 8 ) ;
atbm8830_write_reg ( priv , REG_IF_FREQ + 2 , val > > 16 ) ;
atbm8830_read_reg ( priv , REG_ADC_CONFIG , & dat ) ;
dat & = 0xFC ;
atbm8830_write_reg ( priv , REG_ADC_CONFIG , dat ) ;
} else {
/* Zero IF */
atbm8830_write_reg ( priv , REG_TUNER_BASEBAND , 0 ) ;
atbm8830_read_reg ( priv , REG_ADC_CONFIG , & dat ) ;
dat & = 0xFC ;
dat | = 0x02 ;
atbm8830_write_reg ( priv , REG_ADC_CONFIG , dat ) ;
if ( priv - > config - > zif_swap_iq )
atbm8830_write_reg ( priv , REG_SWAP_I_Q , 0x03 ) ;
else
atbm8830_write_reg ( priv , REG_SWAP_I_Q , 0x01 ) ;
}
return 0 ;
}
static int is_locked ( struct atbm_state * priv , u8 * locked )
{
u8 status ;
atbm8830_read_reg ( priv , REG_LOCK_STATUS , & status ) ;
if ( locked ! = NULL )
* locked = ( status = = 1 ) ;
return 0 ;
}
2009-11-28 08:36:31 -03:00
static int set_agc_config ( struct atbm_state * priv ,
u8 min , u8 max , u8 hold_loop )
{
/* no effect if both min and max are zero */
if ( ! min & & ! max )
return 0 ;
atbm8830_write_reg ( priv , REG_AGC_MIN , min ) ;
atbm8830_write_reg ( priv , REG_AGC_MAX , max ) ;
atbm8830_write_reg ( priv , REG_AGC_HOLD_LOOP , hold_loop ) ;
return 0 ;
}
2009-10-26 07:08:17 -03:00
static int set_static_channel_mode ( struct atbm_state * priv )
{
int i ;
for ( i = 0 ; i < 5 ; i + + )
atbm8830_write_reg ( priv , 0x099B + i , 0x08 ) ;
atbm8830_write_reg ( priv , 0x095B , 0x7F ) ;
atbm8830_write_reg ( priv , 0x09CB , 0x01 ) ;
atbm8830_write_reg ( priv , 0x09CC , 0x7F ) ;
atbm8830_write_reg ( priv , 0x09CD , 0x7F ) ;
atbm8830_write_reg ( priv , 0x0E01 , 0x20 ) ;
/* For single carrier */
atbm8830_write_reg ( priv , 0x0B03 , 0x0A ) ;
atbm8830_write_reg ( priv , 0x0935 , 0x10 ) ;
atbm8830_write_reg ( priv , 0x0936 , 0x08 ) ;
atbm8830_write_reg ( priv , 0x093E , 0x08 ) ;
atbm8830_write_reg ( priv , 0x096E , 0x06 ) ;
/* frame_count_max0 */
atbm8830_write_reg ( priv , 0x0B09 , 0x00 ) ;
/* frame_count_max1 */
atbm8830_write_reg ( priv , 0x0B0A , 0x08 ) ;
return 0 ;
}
static int set_ts_config ( struct atbm_state * priv )
{
const struct atbm8830_config * cfg = priv - > config ;
/*Set parallel/serial ts mode*/
atbm8830_write_reg ( priv , REG_TS_SERIAL , cfg - > serial_ts ? 1 : 0 ) ;
atbm8830_write_reg ( priv , REG_TS_CLK_MODE , cfg - > serial_ts ? 1 : 0 ) ;
/*Set ts sampling edge*/
atbm8830_write_reg ( priv , REG_TS_SAMPLE_EDGE ,
cfg - > ts_sampling_edge ? 1 : 0 ) ;
/*Set ts clock freerun*/
atbm8830_write_reg ( priv , REG_TS_CLK_FREERUN ,
cfg - > ts_clk_gated ? 0 : 1 ) ;
return 0 ;
}
static int atbm8830_init ( struct dvb_frontend * fe )
{
struct atbm_state * priv = fe - > demodulator_priv ;
const struct atbm8830_config * cfg = priv - > config ;
/*Set oscillator frequency*/
set_osc_freq ( priv , cfg - > osc_clk_freq ) ;
/*Set IF frequency*/
set_if_freq ( priv , cfg - > if_freq ) ;
2009-11-28 08:36:31 -03:00
/*Set AGC Config*/
set_agc_config ( priv , cfg - > agc_min , cfg - > agc_max ,
cfg - > agc_hold_loop ) ;
2009-10-26 07:08:17 -03:00
/*Set static channel mode*/
set_static_channel_mode ( priv ) ;
set_ts_config ( priv ) ;
/*Turn off DSP reset*/
atbm8830_write_reg ( priv , 0x000A , 0 ) ;
/*SW version test*/
atbm8830_write_reg ( priv , 0x020C , 11 ) ;
/* Run */
atbm8830_write_reg ( priv , REG_DEMOD_RUN , 1 ) ;
return 0 ;
}
static void atbm8830_release ( struct dvb_frontend * fe )
{
struct atbm_state * state = fe - > demodulator_priv ;
dprintk ( " %s \n " , __func__ ) ;
kfree ( state ) ;
}
2011-12-22 09:42:32 -03:00
static int atbm8830_set_fe ( struct dvb_frontend * fe )
2009-10-26 07:08:17 -03:00
{
struct atbm_state * priv = fe - > demodulator_priv ;
int i ;
u8 locked = 0 ;
dprintk ( " %s \n " , __func__ ) ;
/* set frequency */
if ( fe - > ops . tuner_ops . set_params ) {
if ( fe - > ops . i2c_gate_ctrl )
fe - > ops . i2c_gate_ctrl ( fe , 1 ) ;
2011-12-24 12:24:33 -03:00
fe - > ops . tuner_ops . set_params ( fe ) ;
2009-10-26 07:08:17 -03:00
if ( fe - > ops . i2c_gate_ctrl )
fe - > ops . i2c_gate_ctrl ( fe , 0 ) ;
}
/* start auto lock */
for ( i = 0 ; i < 10 ; i + + ) {
mdelay ( 100 ) ;
dprintk ( " Try %d \n " , i ) ;
is_locked ( priv , & locked ) ;
if ( locked ! = 0 ) {
dprintk ( " ATBM8830 locked! \n " ) ;
break ;
}
}
return 0 ;
}
[media] dvb: don't require a parameter for get_frontend
Just like set_frontend, use the dvb cache properties for get_frontend.
This is more consistent, as both functions are now symetric. Also,
at the places get_frontend is called, it makes sense to update the
cache.
Most of this patch were generated by this small perl script:
while (<>) { $file .= $_; }
if ($file =~ m/\.get_frontend\s*=\s*([\d\w_]+)/) {
my $get = $1;
$file =~ s/($get)(\s*\([^\,\)]+)\,\s*struct\s+dtv_frontend_properties\s*\*\s*([_\d\w]+)\)\s*\{/\1\2)\n{\n\tstruct dtv_frontend_properties *\3 = &fe->dtv_property_cache;/g;
}
print $file;
Of course, the changes at dvb_frontend.[ch] were made by hand,
as well as the changes on a few other places, where get_frontend()
is called internally inside the driver.
On some places, get_frontend() were just a void function. Those
occurrences were removed, as the DVB core handles such cases.
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
2011-12-30 11:30:21 -03:00
static int atbm8830_get_fe ( struct dvb_frontend * fe )
2009-10-26 07:08:17 -03:00
{
[media] dvb: don't require a parameter for get_frontend
Just like set_frontend, use the dvb cache properties for get_frontend.
This is more consistent, as both functions are now symetric. Also,
at the places get_frontend is called, it makes sense to update the
cache.
Most of this patch were generated by this small perl script:
while (<>) { $file .= $_; }
if ($file =~ m/\.get_frontend\s*=\s*([\d\w_]+)/) {
my $get = $1;
$file =~ s/($get)(\s*\([^\,\)]+)\,\s*struct\s+dtv_frontend_properties\s*\*\s*([_\d\w]+)\)\s*\{/\1\2)\n{\n\tstruct dtv_frontend_properties *\3 = &fe->dtv_property_cache;/g;
}
print $file;
Of course, the changes at dvb_frontend.[ch] were made by hand,
as well as the changes on a few other places, where get_frontend()
is called internally inside the driver.
On some places, get_frontend() were just a void function. Those
occurrences were removed, as the DVB core handles such cases.
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
2011-12-30 11:30:21 -03:00
struct dtv_frontend_properties * c = & fe - > dtv_property_cache ;
2009-10-26 07:08:17 -03:00
dprintk ( " %s \n " , __func__ ) ;
/* TODO: get real readings from device */
/* inversion status */
2011-12-22 09:42:32 -03:00
c - > inversion = INVERSION_OFF ;
2009-10-26 07:08:17 -03:00
/* bandwidth */
2011-12-22 09:42:32 -03:00
c - > bandwidth_hz = 8000000 ;
2009-10-26 07:08:17 -03:00
2011-12-22 09:42:32 -03:00
c - > code_rate_HP = FEC_AUTO ;
c - > code_rate_LP = FEC_AUTO ;
2009-10-26 07:08:17 -03:00
2011-12-22 09:42:32 -03:00
c - > modulation = QAM_AUTO ;
2009-10-26 07:08:17 -03:00
/* transmission mode */
2011-12-22 09:42:32 -03:00
c - > transmission_mode = TRANSMISSION_MODE_AUTO ;
2009-10-26 07:08:17 -03:00
/* guard interval */
2011-12-22 09:42:32 -03:00
c - > guard_interval = GUARD_INTERVAL_AUTO ;
2009-10-26 07:08:17 -03:00
/* hierarchy */
2011-12-22 09:42:32 -03:00
c - > hierarchy = HIERARCHY_NONE ;
2009-10-26 07:08:17 -03:00
return 0 ;
}
static int atbm8830_get_tune_settings ( struct dvb_frontend * fe ,
struct dvb_frontend_tune_settings * fesettings )
{
fesettings - > min_delay_ms = 0 ;
fesettings - > step_size = 0 ;
fesettings - > max_drift = 0 ;
return 0 ;
}
static int atbm8830_read_status ( struct dvb_frontend * fe , fe_status_t * fe_status )
{
struct atbm_state * priv = fe - > demodulator_priv ;
u8 locked = 0 ;
u8 agc_locked = 0 ;
dprintk ( " %s \n " , __func__ ) ;
* fe_status = 0 ;
is_locked ( priv , & locked ) ;
if ( locked ) {
* fe_status | = FE_HAS_SIGNAL | FE_HAS_CARRIER |
FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK ;
}
dprintk ( " %s: fe_status=0x%x \n " , __func__ , * fe_status ) ;
atbm8830_read_reg ( priv , REG_AGC_LOCK , & agc_locked ) ;
dprintk ( " AGC Lock: %d \n " , agc_locked ) ;
return 0 ;
}
static int atbm8830_read_ber ( struct dvb_frontend * fe , u32 * ber )
{
struct atbm_state * priv = fe - > demodulator_priv ;
u32 frame_err ;
u8 t ;
dprintk ( " %s \n " , __func__ ) ;
atbm8830_reglatch_lock ( priv , 1 ) ;
atbm8830_read_reg ( priv , REG_FRAME_ERR_CNT + 1 , & t ) ;
frame_err = t & 0x7F ;
frame_err < < = 8 ;
atbm8830_read_reg ( priv , REG_FRAME_ERR_CNT , & t ) ;
frame_err | = t ;
atbm8830_reglatch_lock ( priv , 0 ) ;
* ber = frame_err * 100 / 32767 ;
dprintk ( " %s: ber=0x%x \n " , __func__ , * ber ) ;
return 0 ;
}
static int atbm8830_read_signal_strength ( struct dvb_frontend * fe , u16 * signal )
{
struct atbm_state * priv = fe - > demodulator_priv ;
u32 pwm ;
u8 t ;
dprintk ( " %s \n " , __func__ ) ;
atbm8830_reglatch_lock ( priv , 1 ) ;
atbm8830_read_reg ( priv , REG_AGC_PWM_VAL + 1 , & t ) ;
pwm = t & 0x03 ;
pwm < < = 8 ;
atbm8830_read_reg ( priv , REG_AGC_PWM_VAL , & t ) ;
pwm | = t ;
atbm8830_reglatch_lock ( priv , 0 ) ;
dprintk ( " AGC PWM = 0x%02X \n " , pwm ) ;
pwm = 0x400 - pwm ;
* signal = pwm * 0x10000 / 0x400 ;
return 0 ;
}
static int atbm8830_read_snr ( struct dvb_frontend * fe , u16 * snr )
{
dprintk ( " %s \n " , __func__ ) ;
* snr = 0 ;
return 0 ;
}
static int atbm8830_read_ucblocks ( struct dvb_frontend * fe , u32 * ucblocks )
{
dprintk ( " %s \n " , __func__ ) ;
* ucblocks = 0 ;
return 0 ;
}
static int atbm8830_i2c_gate_ctrl ( struct dvb_frontend * fe , int enable )
{
struct atbm_state * priv = fe - > demodulator_priv ;
return atbm8830_write_reg ( priv , REG_I2C_GATE , enable ? 1 : 0 ) ;
}
static struct dvb_frontend_ops atbm8830_ops = {
2012-08-12 22:33:21 -03:00
. delsys = { SYS_DTMB } ,
2009-10-26 07:08:17 -03:00
. info = {
. name = " AltoBeam ATBM8830/8831 DMB-TH " ,
. frequency_min = 474000000 ,
. frequency_max = 858000000 ,
. frequency_stepsize = 10000 ,
. caps =
FE_CAN_FEC_AUTO |
FE_CAN_QAM_AUTO |
FE_CAN_TRANSMISSION_MODE_AUTO |
FE_CAN_GUARD_INTERVAL_AUTO
} ,
. release = atbm8830_release ,
. init = atbm8830_init ,
. sleep = NULL ,
. write = NULL ,
. i2c_gate_ctrl = atbm8830_i2c_gate_ctrl ,
2011-12-22 09:42:32 -03:00
. set_frontend = atbm8830_set_fe ,
. get_frontend = atbm8830_get_fe ,
2009-10-26 07:08:17 -03:00
. get_tune_settings = atbm8830_get_tune_settings ,
. read_status = atbm8830_read_status ,
. read_ber = atbm8830_read_ber ,
. read_signal_strength = atbm8830_read_signal_strength ,
. read_snr = atbm8830_read_snr ,
. read_ucblocks = atbm8830_read_ucblocks ,
} ;
struct dvb_frontend * atbm8830_attach ( const struct atbm8830_config * config ,
struct i2c_adapter * i2c )
{
struct atbm_state * priv = NULL ;
u8 data = 0 ;
dprintk ( " %s() \n " , __func__ ) ;
if ( config = = NULL | | i2c = = NULL )
return NULL ;
priv = kzalloc ( sizeof ( struct atbm_state ) , GFP_KERNEL ) ;
if ( priv = = NULL )
goto error_out ;
priv - > config = config ;
priv - > i2c = i2c ;
/* check if the demod is there */
if ( atbm8830_read_reg ( priv , REG_CHIP_ID , & data ) ! = 0 ) {
dprintk ( " %s atbm8830/8831 not found at i2c addr 0x%02X \n " ,
__func__ , priv - > config - > demod_address ) ;
goto error_out ;
}
dprintk ( " atbm8830 chip id: 0x%02X \n " , data ) ;
memcpy ( & priv - > frontend . ops , & atbm8830_ops ,
sizeof ( struct dvb_frontend_ops ) ) ;
priv - > frontend . demodulator_priv = priv ;
atbm8830_init ( & priv - > frontend ) ;
atbm8830_i2c_gate_ctrl ( & priv - > frontend , 1 ) ;
return & priv - > frontend ;
error_out :
dprintk ( " %s() error_out \n " , __func__ ) ;
kfree ( priv ) ;
return NULL ;
}
EXPORT_SYMBOL ( atbm8830_attach ) ;
MODULE_DESCRIPTION ( " AltoBeam ATBM8830/8831 GB20600 demodulator driver " ) ;
MODULE_AUTHOR ( " David T. L. Wong <davidtlwong@gmail.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;