2014-04-14 21:51:32 -03:00
/*
2014-11-14 18:19:37 -03:00
* Silicon Labs Si2146 / 2147 / 2148 / 2157 / 2158 silicon tuner driver
2014-04-14 21:51:32 -03:00
*
* Copyright ( C ) 2014 Antti Palosaari < crope @ iki . fi >
*
* 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 .
*/
2014-04-10 21:58:10 -03:00
# include "si2157_priv.h"
2014-07-13 10:52:20 -03:00
static const struct dvb_tuner_ops si2157_ops ;
2014-04-10 21:58:10 -03:00
/* execute firmware command */
2014-12-06 14:40:06 -03:00
static int si2157_cmd_execute ( struct i2c_client * client , struct si2157_cmd * cmd )
2014-04-10 21:58:10 -03:00
{
2014-12-06 14:40:06 -03:00
struct si2157_dev * dev = i2c_get_clientdata ( client ) ;
2014-04-10 21:58:10 -03:00
int ret ;
unsigned long timeout ;
2014-12-05 17:46:30 -03:00
mutex_lock ( & dev - > i2c_mutex ) ;
2014-04-10 21:58:10 -03:00
2014-07-10 06:02:53 -03:00
if ( cmd - > wlen ) {
2014-04-10 21:58:10 -03:00
/* write cmd and args for firmware */
2014-12-06 14:40:06 -03:00
ret = i2c_master_send ( client , cmd - > args , cmd - > wlen ) ;
2014-04-10 21:58:10 -03:00
if ( ret < 0 ) {
goto err_mutex_unlock ;
2014-07-10 06:02:53 -03:00
} else if ( ret ! = cmd - > wlen ) {
2014-04-10 21:58:10 -03:00
ret = - EREMOTEIO ;
goto err_mutex_unlock ;
}
}
2014-07-10 06:02:53 -03:00
if ( cmd - > rlen ) {
/* wait cmd execution terminate */
# define TIMEOUT 80
timeout = jiffies + msecs_to_jiffies ( TIMEOUT ) ;
while ( ! time_after ( jiffies , timeout ) ) {
2014-12-06 14:40:06 -03:00
ret = i2c_master_recv ( client , cmd - > args , cmd - > rlen ) ;
2014-07-10 06:02:53 -03:00
if ( ret < 0 ) {
goto err_mutex_unlock ;
} else if ( ret ! = cmd - > rlen ) {
ret = - EREMOTEIO ;
goto err_mutex_unlock ;
}
/* firmware ready? */
if ( ( cmd - > args [ 0 ] > > 7 ) & 0x01 )
break ;
2014-04-10 21:58:10 -03:00
}
2014-12-06 14:40:06 -03:00
dev_dbg ( & client - > dev , " cmd execution took %d ms \n " ,
2014-07-10 06:02:53 -03:00
jiffies_to_msecs ( jiffies ) -
( jiffies_to_msecs ( timeout ) - TIMEOUT ) ) ;
2014-04-10 21:58:10 -03:00
2014-07-10 06:02:53 -03:00
if ( ! ( ( cmd - > args [ 0 ] > > 7 ) & 0x01 ) ) {
ret = - ETIMEDOUT ;
goto err_mutex_unlock ;
}
2014-04-10 21:58:10 -03:00
}
2014-12-06 14:04:05 -03:00
mutex_unlock ( & dev - > i2c_mutex ) ;
return 0 ;
2014-07-10 06:02:53 -03:00
2014-04-10 21:58:10 -03:00
err_mutex_unlock :
2014-12-05 17:46:30 -03:00
mutex_unlock ( & dev - > i2c_mutex ) ;
2014-12-06 14:40:06 -03:00
dev_dbg ( & client - > dev , " failed=%d \n " , ret ) ;
2014-04-10 21:58:10 -03:00
return ret ;
}
static int si2157_init ( struct dvb_frontend * fe )
{
2014-12-06 14:40:06 -03:00
struct i2c_client * client = fe - > tuner_priv ;
struct si2157_dev * dev = i2c_get_clientdata ( client ) ;
2014-07-13 20:05:28 -03:00
int ret , len , remaining ;
2014-07-13 10:52:19 -03:00
struct si2157_cmd cmd ;
2014-12-06 14:51:17 -03:00
const struct firmware * fw ;
2014-07-13 10:52:20 -03:00
u8 * fw_file ;
2014-07-13 20:05:28 -03:00
unsigned int chip_id ;
2014-04-10 21:58:10 -03:00
2014-12-06 14:40:06 -03:00
dev_dbg ( & client - > dev , " \n " ) ;
2014-04-10 21:58:10 -03:00
2014-12-05 17:46:30 -03:00
if ( dev - > fw_loaded )
2014-08-25 15:07:03 -03:00
goto warm ;
/* power up */
2014-12-05 17:46:30 -03:00
if ( dev - > chiptype = = SI2157_CHIPTYPE_SI2146 ) {
2014-11-24 03:57:33 -03:00
memcpy ( cmd . args , " \xc0 \x05 \x01 \x00 \x00 \x0b \x00 \x00 \x01 " , 9 ) ;
cmd . wlen = 9 ;
} else {
memcpy ( cmd . args , " \xc0 \x00 \x0c \x00 \x00 \x01 \x01 \x01 \x01 \x01 \x01 \x02 \x00 \x00 \x01 " , 15 ) ;
cmd . wlen = 15 ;
}
2014-07-13 10:52:19 -03:00
cmd . rlen = 1 ;
2014-12-06 14:40:06 -03:00
ret = si2157_cmd_execute ( client , & cmd ) ;
2014-07-13 10:52:19 -03:00
if ( ret )
goto err ;
/* query chip revision */
memcpy ( cmd . args , " \x02 " , 1 ) ;
cmd . wlen = 1 ;
cmd . rlen = 13 ;
2014-12-06 14:40:06 -03:00
ret = si2157_cmd_execute ( client , & cmd ) ;
2014-07-13 10:52:19 -03:00
if ( ret )
goto err ;
2014-07-13 20:05:28 -03:00
chip_id = cmd . args [ 1 ] < < 24 | cmd . args [ 2 ] < < 16 | cmd . args [ 3 ] < < 8 |
cmd . args [ 4 ] < < 0 ;
# define SI2158_A20 ('A' << 24 | 58 << 16 | '2' << 8 | '0' << 0)
2014-11-14 18:19:37 -03:00
# define SI2148_A20 ('A' << 24 | 48 << 16 | '2' << 8 | '0' << 0)
2014-07-13 20:05:28 -03:00
# define SI2157_A30 ('A' << 24 | 57 << 16 | '3' << 8 | '0' << 0)
2014-09-11 17:01:38 -03:00
# define SI2147_A30 ('A' << 24 | 47 << 16 | '3' << 8 | '0' << 0)
2014-11-24 03:57:33 -03:00
# define SI2146_A10 ('A' << 24 | 46 << 16 | '1' << 8 | '0' << 0)
2014-07-13 20:05:28 -03:00
switch ( chip_id ) {
case SI2158_A20 :
2014-11-14 18:19:37 -03:00
case SI2148_A20 :
2014-07-13 20:05:28 -03:00
fw_file = SI2158_A20_FIRMWARE ;
break ;
case SI2157_A30 :
2014-09-11 17:01:38 -03:00
case SI2147_A30 :
2014-11-24 03:57:33 -03:00
case SI2146_A10 :
2014-12-06 18:12:39 -03:00
fw_file = NULL ;
break ;
2014-07-13 20:05:28 -03:00
default :
2014-12-06 14:40:06 -03:00
dev_err ( & client - > dev , " unknown chip version Si21%d-%c%c%c \n " ,
2014-08-05 09:03:54 -03:00
cmd . args [ 2 ] , cmd . args [ 1 ] ,
2014-07-13 20:05:28 -03:00
cmd . args [ 3 ] , cmd . args [ 4 ] ) ;
ret = - EINVAL ;
goto err ;
}
2014-07-13 10:52:20 -03:00
2014-12-06 18:12:39 -03:00
dev_info ( & client - > dev , " found a 'Silicon Labs Si21%d-%c%c%c' \n " ,
cmd . args [ 2 ] , cmd . args [ 1 ] , cmd . args [ 3 ] , cmd . args [ 4 ] ) ;
if ( fw_file = = NULL )
goto skip_fw_download ;
2014-07-13 10:52:20 -03:00
2014-07-13 20:05:28 -03:00
/* request the firmware, this will block and timeout */
2014-12-06 14:40:06 -03:00
ret = request_firmware ( & fw , fw_file , & client - > dev ) ;
2014-07-13 20:05:28 -03:00
if ( ret ) {
2014-12-06 14:40:06 -03:00
dev_err ( & client - > dev , " firmware file '%s' not found \n " ,
2014-08-05 09:03:54 -03:00
fw_file ) ;
2014-07-13 20:05:28 -03:00
goto err ;
}
2014-07-13 10:52:20 -03:00
2014-07-13 20:05:28 -03:00
/* firmware should be n chunks of 17 bytes */
if ( fw - > size % 17 ! = 0 ) {
2014-12-06 14:40:06 -03:00
dev_err ( & client - > dev , " firmware file '%s' is invalid \n " ,
2014-08-05 09:03:54 -03:00
fw_file ) ;
2014-07-13 20:05:28 -03:00
ret = - EINVAL ;
2014-12-06 14:51:17 -03:00
goto err_release_firmware ;
2014-07-13 20:05:28 -03:00
}
2014-07-13 10:52:20 -03:00
2014-12-06 14:40:06 -03:00
dev_info ( & client - > dev , " downloading firmware from file '%s' \n " ,
2014-08-05 09:03:54 -03:00
fw_file ) ;
2014-07-13 10:52:20 -03:00
2014-07-13 20:05:28 -03:00
for ( remaining = fw - > size ; remaining > 0 ; remaining - = 17 ) {
len = fw - > data [ fw - > size - remaining ] ;
memcpy ( cmd . args , & fw - > data [ ( fw - > size - remaining ) + 1 ] , len ) ;
cmd . wlen = len ;
cmd . rlen = 1 ;
2014-12-06 14:40:06 -03:00
ret = si2157_cmd_execute ( client , & cmd ) ;
2014-07-13 20:05:28 -03:00
if ( ret ) {
2014-12-06 14:40:06 -03:00
dev_err ( & client - > dev , " firmware download failed %d \n " ,
2014-08-05 09:03:54 -03:00
ret ) ;
2014-12-06 14:51:17 -03:00
goto err_release_firmware ;
2014-07-13 10:52:20 -03:00
}
}
2014-07-13 20:05:28 -03:00
release_firmware ( fw ) ;
skip_fw_download :
2014-07-13 10:52:19 -03:00
/* reboot the tuner with new firmware? */
memcpy ( cmd . args , " \x01 \x01 " , 2 ) ;
cmd . wlen = 2 ;
cmd . rlen = 1 ;
2014-12-06 14:40:06 -03:00
ret = si2157_cmd_execute ( client , & cmd ) ;
2014-07-13 10:52:19 -03:00
if ( ret )
goto err ;
2014-12-06 17:25:24 -03:00
/* query firmware version */
memcpy ( cmd . args , " \x11 " , 1 ) ;
cmd . wlen = 1 ;
cmd . rlen = 10 ;
ret = si2157_cmd_execute ( client , & cmd ) ;
if ( ret )
goto err ;
dev_info ( & client - > dev , " firmware version: %c.%c.%d \n " ,
cmd . args [ 6 ] , cmd . args [ 7 ] , cmd . args [ 8 ] ) ;
2014-12-05 17:46:30 -03:00
dev - > fw_loaded = true ;
2014-04-10 21:58:10 -03:00
2014-08-25 15:07:03 -03:00
warm :
2014-12-05 17:46:30 -03:00
dev - > active = true ;
2014-04-10 21:58:10 -03:00
return 0 ;
2014-08-25 15:07:03 -03:00
2014-12-06 14:51:17 -03:00
err_release_firmware :
2014-11-30 15:05:48 -03:00
release_firmware ( fw ) ;
2014-11-30 16:48:24 -03:00
err :
2014-12-06 14:40:06 -03:00
dev_dbg ( & client - > dev , " failed=%d \n " , ret ) ;
2014-07-13 10:52:19 -03:00
return ret ;
2014-04-10 21:58:10 -03:00
}
static int si2157_sleep ( struct dvb_frontend * fe )
{
2014-12-06 14:40:06 -03:00
struct i2c_client * client = fe - > tuner_priv ;
struct si2157_dev * dev = i2c_get_clientdata ( client ) ;
2014-07-09 18:37:30 -03:00
int ret ;
struct si2157_cmd cmd ;
2014-04-10 21:58:10 -03:00
2014-12-06 14:40:06 -03:00
dev_dbg ( & client - > dev , " \n " ) ;
2014-04-10 21:58:10 -03:00
2014-12-05 17:46:30 -03:00
dev - > active = false ;
2014-04-10 21:58:10 -03:00
2014-08-25 15:07:02 -03:00
/* standby */
memcpy ( cmd . args , " \x16 \x00 " , 2 ) ;
cmd . wlen = 2 ;
cmd . rlen = 1 ;
2014-12-06 14:40:06 -03:00
ret = si2157_cmd_execute ( client , & cmd ) ;
2014-07-09 18:37:30 -03:00
if ( ret )
goto err ;
2014-04-10 21:58:10 -03:00
return 0 ;
2014-07-09 18:37:30 -03:00
err :
2014-12-06 14:40:06 -03:00
dev_dbg ( & client - > dev , " failed=%d \n " , ret ) ;
2014-07-09 18:37:30 -03:00
return ret ;
2014-04-10 21:58:10 -03:00
}
static int si2157_set_params ( struct dvb_frontend * fe )
{
2014-12-06 14:40:06 -03:00
struct i2c_client * client = fe - > tuner_priv ;
struct si2157_dev * dev = i2c_get_clientdata ( client ) ;
2014-04-10 21:58:10 -03:00
struct dtv_frontend_properties * c = & fe - > dtv_property_cache ;
int ret ;
struct si2157_cmd cmd ;
2014-07-13 10:52:21 -03:00
u8 bandwidth , delivery_system ;
2014-04-10 21:58:10 -03:00
2014-12-06 14:40:06 -03:00
dev_dbg ( & client - > dev ,
2014-08-05 09:03:54 -03:00
" delivery_system=%d frequency=%u bandwidth_hz=%u \n " ,
2014-12-06 14:40:06 -03:00
c - > delivery_system , c - > frequency , c - > bandwidth_hz ) ;
2014-04-10 21:58:10 -03:00
2014-12-05 17:46:30 -03:00
if ( ! dev - > active ) {
2014-04-10 21:58:10 -03:00
ret = - EAGAIN ;
goto err ;
}
2014-07-13 10:52:21 -03:00
if ( c - > bandwidth_hz < = 6000000 )
bandwidth = 0x06 ;
else if ( c - > bandwidth_hz < = 7000000 )
bandwidth = 0x07 ;
else if ( c - > bandwidth_hz < = 8000000 )
bandwidth = 0x08 ;
else
bandwidth = 0x0f ;
switch ( c - > delivery_system ) {
2014-08-17 02:24:49 -03:00
case SYS_ATSC :
delivery_system = 0x00 ;
break ;
2014-10-30 07:43:16 -03:00
case SYS_DVBC_ANNEX_B :
delivery_system = 0x10 ;
break ;
2014-07-13 10:52:21 -03:00
case SYS_DVBT :
case SYS_DVBT2 : /* it seems DVB-T and DVB-T2 both are 0x20 here */
delivery_system = 0x20 ;
break ;
case SYS_DVBC_ANNEX_A :
delivery_system = 0x30 ;
break ;
default :
ret = - EINVAL ;
goto err ;
}
memcpy ( cmd . args , " \x14 \x00 \x03 \x07 \x00 \x00 " , 6 ) ;
cmd . args [ 4 ] = delivery_system | bandwidth ;
2014-12-05 17:46:30 -03:00
if ( dev - > inversion )
2014-07-15 16:34:36 -03:00
cmd . args [ 5 ] = 0x01 ;
2014-07-13 10:52:21 -03:00
cmd . wlen = 6 ;
2014-09-11 17:01:38 -03:00
cmd . rlen = 4 ;
2014-12-06 14:40:06 -03:00
ret = si2157_cmd_execute ( client , & cmd ) ;
2014-09-11 17:01:38 -03:00
if ( ret )
goto err ;
2014-12-05 17:46:30 -03:00
if ( dev - > chiptype = = SI2157_CHIPTYPE_SI2146 )
2014-11-24 03:57:33 -03:00
memcpy ( cmd . args , " \x14 \x00 \x02 \x07 \x00 \x01 " , 6 ) ;
else
memcpy ( cmd . args , " \x14 \x00 \x02 \x07 \x01 \x00 " , 6 ) ;
2014-09-11 17:01:38 -03:00
cmd . wlen = 6 ;
cmd . rlen = 4 ;
2014-12-06 14:40:06 -03:00
ret = si2157_cmd_execute ( client , & cmd ) ;
2014-07-13 10:52:21 -03:00
if ( ret )
goto err ;
2014-04-10 21:58:10 -03:00
/* set frequency */
2014-07-13 10:52:19 -03:00
memcpy ( cmd . args , " \x41 \x00 \x00 \x00 \x00 \x00 \x00 \x00 " , 8 ) ;
2014-04-10 21:58:10 -03:00
cmd . args [ 4 ] = ( c - > frequency > > 0 ) & 0xff ;
cmd . args [ 5 ] = ( c - > frequency > > 8 ) & 0xff ;
cmd . args [ 6 ] = ( c - > frequency > > 16 ) & 0xff ;
cmd . args [ 7 ] = ( c - > frequency > > 24 ) & 0xff ;
2014-07-10 06:02:53 -03:00
cmd . wlen = 8 ;
cmd . rlen = 1 ;
2014-12-06 14:40:06 -03:00
ret = si2157_cmd_execute ( client , & cmd ) ;
2014-04-10 21:58:10 -03:00
if ( ret )
goto err ;
return 0 ;
err :
2014-12-06 14:40:06 -03:00
dev_dbg ( & client - > dev , " failed=%d \n " , ret ) ;
2014-04-10 21:58:10 -03:00
return ret ;
}
2014-07-15 04:58:40 -03:00
static int si2157_get_if_frequency ( struct dvb_frontend * fe , u32 * frequency )
{
* frequency = 5000000 ; /* default value of property 0x0706 */
return 0 ;
}
2014-07-18 02:41:12 -03:00
static const struct dvb_tuner_ops si2157_ops = {
2014-04-10 21:58:10 -03:00
. info = {
2014-11-14 18:19:37 -03:00
. name = " Silicon Labs Si2146/2147/2148/2157/2158 " ,
2014-04-12 01:53:51 -03:00
. frequency_min = 110000000 ,
2014-04-10 21:58:10 -03:00
. frequency_max = 862000000 ,
} ,
. init = si2157_init ,
. sleep = si2157_sleep ,
. set_params = si2157_set_params ,
2014-07-15 04:58:40 -03:00
. get_if_frequency = si2157_get_if_frequency ,
2014-04-10 21:58:10 -03:00
} ;
static int si2157_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct si2157_config * cfg = client - > dev . platform_data ;
struct dvb_frontend * fe = cfg - > fe ;
2014-12-05 17:46:30 -03:00
struct si2157_dev * dev ;
2014-04-10 21:58:10 -03:00
struct si2157_cmd cmd ;
int ret ;
2014-12-05 17:46:30 -03:00
dev = kzalloc ( sizeof ( * dev ) , GFP_KERNEL ) ;
if ( ! dev ) {
2014-04-10 21:58:10 -03:00
ret = - ENOMEM ;
2014-08-05 09:03:54 -03:00
dev_err ( & client - > dev , " kzalloc() failed \n " ) ;
2014-04-10 21:58:10 -03:00
goto err ;
}
2014-12-06 14:40:06 -03:00
i2c_set_clientdata ( client , dev ) ;
2014-12-05 17:46:30 -03:00
dev - > fe = cfg - > fe ;
dev - > inversion = cfg - > inversion ;
dev - > fw_loaded = false ;
dev - > chiptype = ( u8 ) id - > driver_data ;
mutex_init ( & dev - > i2c_mutex ) ;
2014-04-10 21:58:10 -03:00
/* check if the tuner is there */
2014-07-10 06:02:53 -03:00
cmd . wlen = 0 ;
cmd . rlen = 1 ;
2014-12-06 14:40:06 -03:00
ret = si2157_cmd_execute ( client , & cmd ) ;
2014-04-10 21:58:10 -03:00
if ( ret )
2014-12-06 15:13:31 -03:00
goto err_kfree ;
2014-04-10 21:58:10 -03:00
2014-12-05 17:46:30 -03:00
memcpy ( & fe - > ops . tuner_ops , & si2157_ops , sizeof ( struct dvb_tuner_ops ) ) ;
2014-12-06 14:40:06 -03:00
fe - > tuner_priv = client ;
2014-04-10 21:58:10 -03:00
2014-12-06 14:40:06 -03:00
dev_info ( & client - > dev , " Silicon Labs %s successfully attached \n " ,
2014-12-05 17:46:30 -03:00
dev - > chiptype = = SI2157_CHIPTYPE_SI2146 ?
2014-11-14 18:19:37 -03:00
" Si2146 " : " Si2147/2148/2157/2158 " ) ;
2014-11-24 03:57:33 -03:00
2014-04-10 21:58:10 -03:00
return 0 ;
2014-12-06 15:13:31 -03:00
err_kfree :
kfree ( dev ) ;
2014-04-10 21:58:10 -03:00
err :
2014-08-05 09:03:54 -03:00
dev_dbg ( & client - > dev , " failed=%d \n " , ret ) ;
2014-04-10 21:58:10 -03:00
return ret ;
}
static int si2157_remove ( struct i2c_client * client )
{
2014-12-05 17:46:30 -03:00
struct si2157_dev * dev = i2c_get_clientdata ( client ) ;
struct dvb_frontend * fe = dev - > fe ;
2014-04-10 21:58:10 -03:00
2014-08-05 09:03:54 -03:00
dev_dbg ( & client - > dev , " \n " ) ;
2014-04-10 21:58:10 -03:00
memset ( & fe - > ops . tuner_ops , 0 , sizeof ( struct dvb_tuner_ops ) ) ;
fe - > tuner_priv = NULL ;
2014-12-05 17:46:30 -03:00
kfree ( dev ) ;
2014-04-10 21:58:10 -03:00
return 0 ;
}
2014-12-06 15:07:45 -03:00
static const struct i2c_device_id si2157_id_table [ ] = {
{ " si2157 " , SI2157_CHIPTYPE_SI2157 } ,
{ " si2146 " , SI2157_CHIPTYPE_SI2146 } ,
2014-04-10 21:58:10 -03:00
{ }
} ;
2014-12-06 15:07:45 -03:00
MODULE_DEVICE_TABLE ( i2c , si2157_id_table ) ;
2014-04-10 21:58:10 -03:00
static struct i2c_driver si2157_driver = {
. driver = {
. owner = THIS_MODULE ,
. name = " si2157 " ,
} ,
. probe = si2157_probe ,
. remove = si2157_remove ,
2014-12-06 15:07:45 -03:00
. id_table = si2157_id_table ,
2014-04-10 21:58:10 -03:00
} ;
module_i2c_driver ( si2157_driver ) ;
2014-11-14 18:19:37 -03:00
MODULE_DESCRIPTION ( " Silicon Labs Si2146/2147/2148/2157/2158 silicon tuner driver " ) ;
2014-04-10 21:58:10 -03:00
MODULE_AUTHOR ( " Antti Palosaari <crope@iki.fi> " ) ;
MODULE_LICENSE ( " GPL " ) ;
2014-07-13 16:13:49 -03:00
MODULE_FIRMWARE ( SI2158_A20_FIRMWARE ) ;