2018-01-18 11:46:10 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* cxd2880 - spi . c
* Sony CXD2880 DVB - T2 / T tuner + demodulator driver
* SPI adapter
*
* Copyright ( C ) 2016 , 2017 , 2018 Sony Semiconductor Solutions Corporation
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
# include <linux/spi/spi.h>
2018-11-08 15:50:09 +03:00
# include <linux/regulator/consumer.h>
2018-01-18 11:46:10 +03:00
# include <linux/ktime.h>
2018-03-07 13:02:37 +03:00
# include <media/dvb_demux.h>
# include <media/dmxdev.h>
# include <media/dvb_frontend.h>
2018-01-18 11:46:10 +03:00
# include "cxd2880.h"
# define CXD2880_MAX_FILTER_SIZE 32
# define BURST_WRITE_MAX 128
# define MAX_TRANS_PKT 300
struct cxd2880_ts_buf_info {
u8 read_ready : 1 ;
u8 almost_full : 1 ;
u8 almost_empty : 1 ;
u8 overflow : 1 ;
u8 underflow : 1 ;
u16 pkt_num ;
} ;
struct cxd2880_pid_config {
u8 is_enable ;
u16 pid ;
} ;
struct cxd2880_pid_filter_config {
u8 is_negative ;
struct cxd2880_pid_config pid_config [ CXD2880_MAX_FILTER_SIZE ] ;
} ;
struct cxd2880_dvb_spi {
struct dvb_frontend dvb_fe ;
struct dvb_adapter adapter ;
struct dvb_demux demux ;
struct dmxdev dmxdev ;
struct dmx_frontend dmx_fe ;
struct task_struct * cxd2880_ts_read_thread ;
struct spi_device * spi ;
struct mutex spi_mutex ; /* For SPI access exclusive control */
int feed_count ;
int all_pid_feed_count ;
2018-11-08 15:50:09 +03:00
struct regulator * vcc_supply ;
2018-01-18 11:46:10 +03:00
u8 * ts_buf ;
struct cxd2880_pid_filter_config filter_config ;
} ;
DVB_DEFINE_MOD_OPT_ADAPTER_NR ( adapter_nr ) ;
static int cxd2880_write_spi ( struct spi_device * spi , u8 * data , u32 size )
{
struct spi_message msg ;
2018-04-26 09:39:17 +03:00
struct spi_transfer tx = { } ;
2018-01-18 11:46:10 +03:00
if ( ! spi | | ! data ) {
pr_err ( " invalid arg \n " ) ;
return - EINVAL ;
}
tx . tx_buf = data ;
tx . len = size ;
spi_message_init ( & msg ) ;
spi_message_add_tail ( & tx , & msg ) ;
return spi_sync ( spi , & msg ) ;
}
static int cxd2880_write_reg ( struct spi_device * spi ,
u8 sub_address , const u8 * data , u32 size )
{
u8 send_data [ BURST_WRITE_MAX + 4 ] ;
const u8 * write_data_top = NULL ;
int ret = 0 ;
if ( ! spi | | ! data ) {
pr_err ( " invalid arg \n " ) ;
return - EINVAL ;
}
2018-03-13 15:09:11 +03:00
if ( size > BURST_WRITE_MAX | | size > U8_MAX ) {
2018-01-18 11:46:10 +03:00
pr_err ( " data size > WRITE_MAX \n " ) ;
return - EINVAL ;
}
if ( sub_address + size > 0x100 ) {
pr_err ( " out of range \n " ) ;
return - EINVAL ;
}
send_data [ 0 ] = 0x0e ;
write_data_top = data ;
2018-03-13 15:09:11 +03:00
send_data [ 1 ] = sub_address ;
send_data [ 2 ] = ( u8 ) size ;
memcpy ( & send_data [ 3 ] , write_data_top , send_data [ 2 ] ) ;
ret = cxd2880_write_spi ( spi , send_data , send_data [ 2 ] + 3 ) ;
if ( ret )
pr_err ( " write spi failed %d \n " , ret ) ;
2018-01-18 11:46:10 +03:00
return ret ;
}
static int cxd2880_spi_read_ts ( struct spi_device * spi ,
u8 * read_data ,
u32 packet_num )
{
int ret ;
u8 data [ 3 ] ;
struct spi_message message ;
2018-04-26 09:39:17 +03:00
struct spi_transfer transfer [ 2 ] = { } ;
2018-01-18 11:46:10 +03:00
if ( ! spi | | ! read_data | | ! packet_num ) {
pr_err ( " invalid arg \n " ) ;
return - EINVAL ;
}
if ( packet_num > 0xffff ) {
pr_err ( " packet num > 0xffff \n " ) ;
return - EINVAL ;
}
data [ 0 ] = 0x10 ;
data [ 1 ] = packet_num > > 8 ;
data [ 2 ] = packet_num ;
spi_message_init ( & message ) ;
transfer [ 0 ] . len = 3 ;
transfer [ 0 ] . tx_buf = data ;
spi_message_add_tail ( & transfer [ 0 ] , & message ) ;
transfer [ 1 ] . len = packet_num * 188 ;
transfer [ 1 ] . rx_buf = read_data ;
spi_message_add_tail ( & transfer [ 1 ] , & message ) ;
ret = spi_sync ( spi , & message ) ;
if ( ret )
pr_err ( " spi_write_then_read failed \n " ) ;
return ret ;
}
static int cxd2880_spi_read_ts_buffer_info ( struct spi_device * spi ,
struct cxd2880_ts_buf_info * info )
{
u8 send_data = 0x20 ;
u8 recv_data [ 2 ] ;
int ret ;
if ( ! spi | | ! info ) {
pr_err ( " invalid arg \n " ) ;
return - EINVAL ;
}
ret = spi_write_then_read ( spi , & send_data , 1 ,
recv_data , sizeof ( recv_data ) ) ;
if ( ret )
pr_err ( " spi_write_then_read failed \n " ) ;
info - > read_ready = ( recv_data [ 0 ] & 0x80 ) ? 1 : 0 ;
info - > almost_full = ( recv_data [ 0 ] & 0x40 ) ? 1 : 0 ;
info - > almost_empty = ( recv_data [ 0 ] & 0x20 ) ? 1 : 0 ;
info - > overflow = ( recv_data [ 0 ] & 0x10 ) ? 1 : 0 ;
info - > underflow = ( recv_data [ 0 ] & 0x08 ) ? 1 : 0 ;
info - > pkt_num = ( ( recv_data [ 0 ] & 0x07 ) < < 8 ) | recv_data [ 1 ] ;
return ret ;
}
static int cxd2880_spi_clear_ts_buffer ( struct spi_device * spi )
{
u8 data = 0x03 ;
int ret ;
ret = cxd2880_write_spi ( spi , & data , 1 ) ;
if ( ret )
pr_err ( " write spi failed \n " ) ;
return ret ;
}
static int cxd2880_set_pid_filter ( struct spi_device * spi ,
struct cxd2880_pid_filter_config * cfg )
{
u8 data [ 65 ] ;
int i ;
u16 pid = 0 ;
int ret ;
if ( ! spi ) {
pr_err ( " invalid arg \n " ) ;
return - EINVAL ;
}
data [ 0 ] = 0x00 ;
ret = cxd2880_write_reg ( spi , 0x00 , & data [ 0 ] , 1 ) ;
if ( ret )
return ret ;
if ( ! cfg ) {
data [ 0 ] = 0x02 ;
ret = cxd2880_write_reg ( spi , 0x50 , & data [ 0 ] , 1 ) ;
} else {
data [ 0 ] = cfg - > is_negative ? 0x01 : 0x00 ;
for ( i = 0 ; i < CXD2880_MAX_FILTER_SIZE ; i + + ) {
pid = cfg - > pid_config [ i ] . pid ;
if ( cfg - > pid_config [ i ] . is_enable ) {
data [ 1 + ( i * 2 ) ] = ( pid > > 8 ) | 0x20 ;
data [ 2 + ( i * 2 ) ] = pid & 0xff ;
} else {
data [ 1 + ( i * 2 ) ] = 0x00 ;
data [ 2 + ( i * 2 ) ] = 0x00 ;
}
}
ret = cxd2880_write_reg ( spi , 0x50 , data , 65 ) ;
}
return ret ;
}
static int cxd2880_update_pid_filter ( struct cxd2880_dvb_spi * dvb_spi ,
struct cxd2880_pid_filter_config * cfg ,
bool is_all_pid_filter )
{
int ret ;
if ( ! dvb_spi | | ! cfg ) {
pr_err ( " invalid arg. \n " ) ;
return - EINVAL ;
}
mutex_lock ( & dvb_spi - > spi_mutex ) ;
if ( is_all_pid_filter ) {
struct cxd2880_pid_filter_config tmpcfg ;
memset ( & tmpcfg , 0 , sizeof ( tmpcfg ) ) ;
tmpcfg . is_negative = 1 ;
tmpcfg . pid_config [ 0 ] . is_enable = 1 ;
tmpcfg . pid_config [ 0 ] . pid = 0x1fff ;
ret = cxd2880_set_pid_filter ( dvb_spi - > spi , & tmpcfg ) ;
} else {
ret = cxd2880_set_pid_filter ( dvb_spi - > spi , cfg ) ;
}
mutex_unlock ( & dvb_spi - > spi_mutex ) ;
if ( ret )
pr_err ( " set_pid_filter failed \n " ) ;
return ret ;
}
static int cxd2880_ts_read ( void * arg )
{
struct cxd2880_dvb_spi * dvb_spi = NULL ;
struct cxd2880_ts_buf_info info ;
ktime_t start ;
u32 i ;
int ret ;
dvb_spi = arg ;
if ( ! dvb_spi ) {
pr_err ( " invalid arg \n " ) ;
return - EINVAL ;
}
ret = cxd2880_spi_clear_ts_buffer ( dvb_spi - > spi ) ;
if ( ret ) {
pr_err ( " set_clear_ts_buffer failed \n " ) ;
return ret ;
}
start = ktime_get ( ) ;
while ( ! kthread_should_stop ( ) ) {
ret = cxd2880_spi_read_ts_buffer_info ( dvb_spi - > spi ,
& info ) ;
if ( ret ) {
pr_err ( " spi_read_ts_buffer_info error \n " ) ;
return ret ;
}
if ( info . pkt_num > MAX_TRANS_PKT ) {
for ( i = 0 ; i < info . pkt_num / MAX_TRANS_PKT ; i + + ) {
cxd2880_spi_read_ts ( dvb_spi - > spi ,
dvb_spi - > ts_buf ,
MAX_TRANS_PKT ) ;
dvb_dmx_swfilter ( & dvb_spi - > demux ,
dvb_spi - > ts_buf ,
MAX_TRANS_PKT * 188 ) ;
}
start = ktime_get ( ) ;
} else if ( ( info . pkt_num > 0 ) & &
( ktime_to_ms ( ktime_sub ( ktime_get ( ) , start ) ) > = 500 ) ) {
cxd2880_spi_read_ts ( dvb_spi - > spi ,
dvb_spi - > ts_buf ,
info . pkt_num ) ;
dvb_dmx_swfilter ( & dvb_spi - > demux ,
dvb_spi - > ts_buf ,
info . pkt_num * 188 ) ;
start = ktime_get ( ) ;
} else {
usleep_range ( 10000 , 11000 ) ;
}
}
return 0 ;
}
static int cxd2880_start_feed ( struct dvb_demux_feed * feed )
{
int ret = 0 ;
int i = 0 ;
struct dvb_demux * demux = NULL ;
struct cxd2880_dvb_spi * dvb_spi = NULL ;
if ( ! feed ) {
pr_err ( " invalid arg \n " ) ;
return - EINVAL ;
}
demux = feed - > demux ;
if ( ! demux ) {
pr_err ( " feed->demux is NULL \n " ) ;
return - EINVAL ;
}
dvb_spi = demux - > priv ;
if ( dvb_spi - > feed_count = = CXD2880_MAX_FILTER_SIZE ) {
pr_err ( " Exceeded maximum PID count (32). " ) ;
pr_err ( " Selected PID cannot be enabled. \n " ) ;
return - EINVAL ;
}
if ( feed - > pid = = 0x2000 ) {
if ( dvb_spi - > all_pid_feed_count = = 0 ) {
ret = cxd2880_update_pid_filter ( dvb_spi ,
& dvb_spi - > filter_config ,
true ) ;
if ( ret ) {
pr_err ( " update pid filter failed \n " ) ;
return ret ;
}
}
dvb_spi - > all_pid_feed_count + + ;
pr_debug ( " all PID feed (count = %d) \n " ,
dvb_spi - > all_pid_feed_count ) ;
} else {
struct cxd2880_pid_filter_config cfgtmp ;
cfgtmp = dvb_spi - > filter_config ;
for ( i = 0 ; i < CXD2880_MAX_FILTER_SIZE ; i + + ) {
if ( cfgtmp . pid_config [ i ] . is_enable = = 0 ) {
cfgtmp . pid_config [ i ] . is_enable = 1 ;
cfgtmp . pid_config [ i ] . pid = feed - > pid ;
pr_debug ( " store PID %d to #%d \n " ,
feed - > pid , i ) ;
break ;
}
}
if ( i = = CXD2880_MAX_FILTER_SIZE ) {
2018-04-26 09:39:17 +03:00
pr_err ( " PID filter is full. \n " ) ;
2018-01-18 11:46:10 +03:00
return - EINVAL ;
}
if ( ! dvb_spi - > all_pid_feed_count )
ret = cxd2880_update_pid_filter ( dvb_spi ,
& cfgtmp ,
false ) ;
if ( ret )
return ret ;
dvb_spi - > filter_config = cfgtmp ;
}
if ( dvb_spi - > feed_count = = 0 ) {
dvb_spi - > ts_buf =
kmalloc ( MAX_TRANS_PKT * 188 ,
GFP_KERNEL | GFP_DMA ) ;
if ( ! dvb_spi - > ts_buf ) {
pr_err ( " ts buffer allocate failed \n " ) ;
memset ( & dvb_spi - > filter_config , 0 ,
sizeof ( dvb_spi - > filter_config ) ) ;
dvb_spi - > all_pid_feed_count = 0 ;
return - ENOMEM ;
}
dvb_spi - > cxd2880_ts_read_thread = kthread_run ( cxd2880_ts_read ,
dvb_spi ,
" cxd2880_ts_read " ) ;
if ( IS_ERR ( dvb_spi - > cxd2880_ts_read_thread ) ) {
pr_err ( " kthread_run failed/ \n " ) ;
kfree ( dvb_spi - > ts_buf ) ;
dvb_spi - > ts_buf = NULL ;
memset ( & dvb_spi - > filter_config , 0 ,
sizeof ( dvb_spi - > filter_config ) ) ;
dvb_spi - > all_pid_feed_count = 0 ;
return PTR_ERR ( dvb_spi - > cxd2880_ts_read_thread ) ;
}
}
dvb_spi - > feed_count + + ;
pr_debug ( " start feed (count %d) \n " , dvb_spi - > feed_count ) ;
return 0 ;
}
static int cxd2880_stop_feed ( struct dvb_demux_feed * feed )
{
int i = 0 ;
int ret ;
struct dvb_demux * demux = NULL ;
struct cxd2880_dvb_spi * dvb_spi = NULL ;
if ( ! feed ) {
pr_err ( " invalid arg \n " ) ;
return - EINVAL ;
}
demux = feed - > demux ;
if ( ! demux ) {
pr_err ( " feed->demux is NULL \n " ) ;
return - EINVAL ;
}
dvb_spi = demux - > priv ;
if ( ! dvb_spi - > feed_count ) {
pr_err ( " no feed is started \n " ) ;
return - EINVAL ;
}
if ( feed - > pid = = 0x2000 ) {
/*
* Special PID case .
* Number of 0x2000 feed request was stored
* in dvb_spi - > all_pid_feed_count .
*/
if ( dvb_spi - > all_pid_feed_count < = 0 ) {
pr_err ( " PID %d not found. \n " , feed - > pid ) ;
return - EINVAL ;
}
dvb_spi - > all_pid_feed_count - - ;
} else {
struct cxd2880_pid_filter_config cfgtmp ;
cfgtmp = dvb_spi - > filter_config ;
for ( i = 0 ; i < CXD2880_MAX_FILTER_SIZE ; i + + ) {
if ( feed - > pid = = cfgtmp . pid_config [ i ] . pid & &
cfgtmp . pid_config [ i ] . is_enable ! = 0 ) {
cfgtmp . pid_config [ i ] . is_enable = 0 ;
cfgtmp . pid_config [ i ] . pid = 0 ;
pr_debug ( " removed PID %d from #%d \n " ,
feed - > pid , i ) ;
break ;
}
}
dvb_spi - > filter_config = cfgtmp ;
if ( i = = CXD2880_MAX_FILTER_SIZE ) {
pr_err ( " PID %d not found \n " , feed - > pid ) ;
return - EINVAL ;
}
}
ret = cxd2880_update_pid_filter ( dvb_spi ,
& dvb_spi - > filter_config ,
dvb_spi - > all_pid_feed_count > 0 ) ;
dvb_spi - > feed_count - - ;
if ( dvb_spi - > feed_count = = 0 ) {
int ret_stop = 0 ;
ret_stop = kthread_stop ( dvb_spi - > cxd2880_ts_read_thread ) ;
if ( ret_stop ) {
pr_err ( " 'kthread_stop failed. (%d) \n " , ret_stop ) ;
ret = ret_stop ;
}
kfree ( dvb_spi - > ts_buf ) ;
dvb_spi - > ts_buf = NULL ;
}
pr_debug ( " stop feed ok.(count %d) \n " , dvb_spi - > feed_count ) ;
return ret ;
}
static const struct of_device_id cxd2880_spi_of_match [ ] = {
{ . compatible = " sony,cxd2880 " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , cxd2880_spi_of_match ) ;
static int
cxd2880_spi_probe ( struct spi_device * spi )
{
int ret ;
struct cxd2880_dvb_spi * dvb_spi = NULL ;
struct cxd2880_config config ;
if ( ! spi ) {
pr_err ( " invalid arg. \n " ) ;
return - EINVAL ;
}
dvb_spi = kzalloc ( sizeof ( struct cxd2880_dvb_spi ) , GFP_KERNEL ) ;
if ( ! dvb_spi )
return - ENOMEM ;
2018-11-08 15:50:09 +03:00
dvb_spi - > vcc_supply = devm_regulator_get_optional ( & spi - > dev , " vcc " ) ;
if ( IS_ERR ( dvb_spi - > vcc_supply ) ) {
2018-12-22 17:12:26 +03:00
if ( PTR_ERR ( dvb_spi - > vcc_supply ) = = - EPROBE_DEFER ) {
ret = - EPROBE_DEFER ;
goto fail_adapter ;
}
2018-11-08 15:50:09 +03:00
dvb_spi - > vcc_supply = NULL ;
} else {
ret = regulator_enable ( dvb_spi - > vcc_supply ) ;
if ( ret )
2018-12-22 17:12:26 +03:00
goto fail_adapter ;
2018-11-08 15:50:09 +03:00
}
2018-01-18 11:46:10 +03:00
dvb_spi - > spi = spi ;
mutex_init ( & dvb_spi - > spi_mutex ) ;
dev_set_drvdata ( & spi - > dev , dvb_spi ) ;
config . spi = spi ;
config . spi_mutex = & dvb_spi - > spi_mutex ;
ret = dvb_register_adapter ( & dvb_spi - > adapter ,
" CXD2880 " ,
THIS_MODULE ,
& spi - > dev ,
adapter_nr ) ;
if ( ret < 0 ) {
pr_err ( " dvb_register_adapter() failed \n " ) ;
goto fail_adapter ;
}
if ( ! dvb_attach ( cxd2880_attach , & dvb_spi - > dvb_fe , & config ) ) {
pr_err ( " cxd2880_attach failed \n " ) ;
2018-11-08 15:44:48 +03:00
ret = - ENODEV ;
2018-01-18 11:46:10 +03:00
goto fail_attach ;
}
ret = dvb_register_frontend ( & dvb_spi - > adapter ,
& dvb_spi - > dvb_fe ) ;
if ( ret < 0 ) {
pr_err ( " dvb_register_frontend() failed \n " ) ;
goto fail_frontend ;
}
dvb_spi - > demux . dmx . capabilities = DMX_TS_FILTERING ;
dvb_spi - > demux . priv = dvb_spi ;
dvb_spi - > demux . filternum = CXD2880_MAX_FILTER_SIZE ;
dvb_spi - > demux . feednum = CXD2880_MAX_FILTER_SIZE ;
dvb_spi - > demux . start_feed = cxd2880_start_feed ;
dvb_spi - > demux . stop_feed = cxd2880_stop_feed ;
ret = dvb_dmx_init ( & dvb_spi - > demux ) ;
if ( ret < 0 ) {
pr_err ( " dvb_dmx_init() failed \n " ) ;
goto fail_dmx ;
}
dvb_spi - > dmxdev . filternum = CXD2880_MAX_FILTER_SIZE ;
dvb_spi - > dmxdev . demux = & dvb_spi - > demux . dmx ;
dvb_spi - > dmxdev . capabilities = 0 ;
ret = dvb_dmxdev_init ( & dvb_spi - > dmxdev ,
& dvb_spi - > adapter ) ;
if ( ret < 0 ) {
pr_err ( " dvb_dmxdev_init() failed \n " ) ;
goto fail_dmxdev ;
}
dvb_spi - > dmx_fe . source = DMX_FRONTEND_0 ;
ret = dvb_spi - > demux . dmx . add_frontend ( & dvb_spi - > demux . dmx ,
& dvb_spi - > dmx_fe ) ;
if ( ret < 0 ) {
pr_err ( " add_frontend() failed \n " ) ;
goto fail_dmx_fe ;
}
ret = dvb_spi - > demux . dmx . connect_frontend ( & dvb_spi - > demux . dmx ,
& dvb_spi - > dmx_fe ) ;
if ( ret < 0 ) {
pr_err ( " dvb_register_frontend() failed \n " ) ;
goto fail_fe_conn ;
}
pr_info ( " Sony CXD2880 has successfully attached. \n " ) ;
return 0 ;
fail_fe_conn :
dvb_spi - > demux . dmx . remove_frontend ( & dvb_spi - > demux . dmx ,
& dvb_spi - > dmx_fe ) ;
fail_dmx_fe :
dvb_dmxdev_release ( & dvb_spi - > dmxdev ) ;
fail_dmxdev :
dvb_dmx_release ( & dvb_spi - > demux ) ;
fail_dmx :
dvb_unregister_frontend ( & dvb_spi - > dvb_fe ) ;
fail_frontend :
dvb_frontend_detach ( & dvb_spi - > dvb_fe ) ;
fail_attach :
dvb_unregister_adapter ( & dvb_spi - > adapter ) ;
fail_adapter :
kfree ( dvb_spi ) ;
return ret ;
}
static int
cxd2880_spi_remove ( struct spi_device * spi )
{
struct cxd2880_dvb_spi * dvb_spi ;
if ( ! spi ) {
pr_err ( " invalid arg \n " ) ;
return - EINVAL ;
}
dvb_spi = dev_get_drvdata ( & spi - > dev ) ;
if ( ! dvb_spi ) {
pr_err ( " failed \n " ) ;
return - EINVAL ;
}
dvb_spi - > demux . dmx . remove_frontend ( & dvb_spi - > demux . dmx ,
& dvb_spi - > dmx_fe ) ;
dvb_dmxdev_release ( & dvb_spi - > dmxdev ) ;
dvb_dmx_release ( & dvb_spi - > demux ) ;
dvb_unregister_frontend ( & dvb_spi - > dvb_fe ) ;
dvb_frontend_detach ( & dvb_spi - > dvb_fe ) ;
dvb_unregister_adapter ( & dvb_spi - > adapter ) ;
2018-11-08 15:50:09 +03:00
if ( dvb_spi - > vcc_supply )
regulator_disable ( dvb_spi - > vcc_supply ) ;
2018-01-18 11:46:10 +03:00
kfree ( dvb_spi ) ;
pr_info ( " cxd2880_spi remove ok. \n " ) ;
return 0 ;
}
static const struct spi_device_id cxd2880_spi_id [ ] = {
{ " cxd2880 " , 0 } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( spi , cxd2880_spi_id ) ;
static struct spi_driver cxd2880_spi_driver = {
. driver = {
. name = " cxd2880 " ,
. of_match_table = cxd2880_spi_of_match ,
} ,
. id_table = cxd2880_spi_id ,
. probe = cxd2880_spi_probe ,
. remove = cxd2880_spi_remove ,
} ;
module_spi_driver ( cxd2880_spi_driver ) ;
MODULE_DESCRIPTION ( " Sony CXD2880 DVB-T2/T tuner + demod driver SPI adapter " ) ;
MODULE_AUTHOR ( " Sony Semiconductor Solutions Corporation " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;