2019-05-29 17:17:59 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2013-04-18 20:58:28 +04:00
/*
* drivers / mfd / si476x - i2c . c - - Core device driver for si476x MFD
* device
*
* Copyright ( C ) 2012 Innovative Converged Devices ( ICD )
* Copyright ( C ) 2013 Andrey Smirnov
*
* Author : Andrey Smirnov < andrew . smirnov @ gmail . com >
*/
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/gpio.h>
# include <linux/regulator/consumer.h>
# include <linux/i2c.h>
# include <linux/err.h>
# include <linux/mfd/si476x-core.h>
# define SI476X_MAX_IO_ERRORS 10
# define SI476X_DRIVER_RDS_FIFO_DEPTH 128
/**
* si476x_core_config_pinmux ( ) - pin function configuration function
*
* @ core : Core device structure
*
* Configure the functions of the pins of the radio chip .
*
* The function returns zero in case of succes or negative error code
* otherwise .
*/
static int si476x_core_config_pinmux ( struct si476x_core * core )
{
int err ;
dev_dbg ( & core - > client - > dev , " Configuring pinmux \n " ) ;
err = si476x_core_cmd_dig_audio_pin_cfg ( core ,
core - > pinmux . dclk ,
core - > pinmux . dfs ,
core - > pinmux . dout ,
core - > pinmux . xout ) ;
if ( err < 0 ) {
dev_err ( & core - > client - > dev ,
" Failed to configure digital audio pins(err = %d) \n " ,
err ) ;
return err ;
}
err = si476x_core_cmd_zif_pin_cfg ( core ,
core - > pinmux . iqclk ,
core - > pinmux . iqfs ,
core - > pinmux . iout ,
core - > pinmux . qout ) ;
if ( err < 0 ) {
dev_err ( & core - > client - > dev ,
" Failed to configure ZIF pins(err = %d) \n " ,
err ) ;
return err ;
}
err = si476x_core_cmd_ic_link_gpo_ctl_pin_cfg ( core ,
core - > pinmux . icin ,
core - > pinmux . icip ,
core - > pinmux . icon ,
core - > pinmux . icop ) ;
if ( err < 0 ) {
dev_err ( & core - > client - > dev ,
" Failed to configure IC-Link/GPO pins(err = %d) \n " ,
err ) ;
return err ;
}
err = si476x_core_cmd_ana_audio_pin_cfg ( core ,
core - > pinmux . lrout ) ;
if ( err < 0 ) {
dev_err ( & core - > client - > dev ,
" Failed to configure analog audio pins(err = %d) \n " ,
err ) ;
return err ;
}
err = si476x_core_cmd_intb_pin_cfg ( core ,
core - > pinmux . intb ,
core - > pinmux . a1 ) ;
if ( err < 0 ) {
dev_err ( & core - > client - > dev ,
" Failed to configure interrupt pins(err = %d) \n " ,
err ) ;
return err ;
}
return 0 ;
}
static inline void si476x_core_schedule_polling_work ( struct si476x_core * core )
{
schedule_delayed_work ( & core - > status_monitor ,
usecs_to_jiffies ( SI476X_STATUS_POLL_US ) ) ;
}
/**
* si476x_core_start ( ) - early chip startup function
* @ core : Core device structure
* @ soft : When set , this flag forces " soft " startup , where " soft "
* power down is the one done by sending appropriate command instead
* of using reset pin of the tuner
*
* Perform required startup sequence to correctly power
* up the chip and perform initial configuration . It does the
* following sequence of actions :
* 1. Claims and enables the power supplies VD and VIO1 required
* for I2C interface of the chip operation .
* 2. Waits for 100u s , pulls the reset line up , enables irq ,
* waits for another 100u s as it is specified by the
* datasheet .
* 3. Sends ' POWER_UP ' command to the device with all provided
* information about power - up parameters .
* 4. Configures , pin multiplexor , disables digital audio and
* configures interrupt sources .
*
* The function returns zero in case of succes or negative error code
* otherwise .
*/
int si476x_core_start ( struct si476x_core * core , bool soft )
{
struct i2c_client * client = core - > client ;
int err ;
if ( ! soft ) {
if ( gpio_is_valid ( core - > gpio_reset ) )
gpio_set_value_cansleep ( core - > gpio_reset , 1 ) ;
if ( client - > irq )
enable_irq ( client - > irq ) ;
udelay ( 100 ) ;
if ( ! client - > irq ) {
atomic_set ( & core - > is_alive , 1 ) ;
si476x_core_schedule_polling_work ( core ) ;
}
} else {
if ( client - > irq )
enable_irq ( client - > irq ) ;
else {
atomic_set ( & core - > is_alive , 1 ) ;
si476x_core_schedule_polling_work ( core ) ;
}
}
err = si476x_core_cmd_power_up ( core ,
& core - > power_up_parameters ) ;
if ( err < 0 ) {
dev_err ( & core - > client - > dev ,
" Power up failure(err = %d) \n " ,
err ) ;
goto disable_irq ;
}
if ( client - > irq )
atomic_set ( & core - > is_alive , 1 ) ;
err = si476x_core_config_pinmux ( core ) ;
if ( err < 0 ) {
dev_err ( & core - > client - > dev ,
" Failed to configure pinmux(err = %d) \n " ,
err ) ;
goto disable_irq ;
}
if ( client - > irq ) {
err = regmap_write ( core - > regmap ,
SI476X_PROP_INT_CTL_ENABLE ,
SI476X_RDSIEN |
SI476X_STCIEN |
SI476X_CTSIEN ) ;
if ( err < 0 ) {
dev_err ( & core - > client - > dev ,
" Failed to configure interrupt sources "
" (err = %d) \n " , err ) ;
goto disable_irq ;
}
}
return 0 ;
disable_irq :
if ( err = = - ENODEV )
atomic_set ( & core - > is_alive , 0 ) ;
if ( client - > irq )
disable_irq ( client - > irq ) ;
else
cancel_delayed_work_sync ( & core - > status_monitor ) ;
if ( gpio_is_valid ( core - > gpio_reset ) )
gpio_set_value_cansleep ( core - > gpio_reset , 0 ) ;
return err ;
}
EXPORT_SYMBOL_GPL ( si476x_core_start ) ;
/**
* si476x_core_stop ( ) - chip power - down function
* @ core : Core device structure
* @ soft : When set , function sends a POWER_DOWN command instead of
* bringing reset line low
*
* Power down the chip by performing following actions :
* 1. Disable IRQ or stop the polling worker
* 2. Send the POWER_DOWN command if the power down is soft or bring
* reset line low if not .
*
* The function returns zero in case of succes or negative error code
* otherwise .
*/
int si476x_core_stop ( struct si476x_core * core , bool soft )
{
int err = 0 ;
atomic_set ( & core - > is_alive , 0 ) ;
if ( soft ) {
/* TODO: This probably shoud be a configurable option,
* so it is possible to have the chips keep their
* oscillators running
*/
struct si476x_power_down_args args = {
. xosc = false ,
} ;
err = si476x_core_cmd_power_down ( core , & args ) ;
}
/* We couldn't disable those before
* ' si476x_core_cmd_power_down ' since we expect to get CTS
* interrupt */
if ( core - > client - > irq )
disable_irq ( core - > client - > irq ) ;
else
cancel_delayed_work_sync ( & core - > status_monitor ) ;
if ( ! soft ) {
if ( gpio_is_valid ( core - > gpio_reset ) )
gpio_set_value_cansleep ( core - > gpio_reset , 0 ) ;
}
return err ;
}
EXPORT_SYMBOL_GPL ( si476x_core_stop ) ;
/**
* si476x_core_set_power_state ( ) - set the level at which the power is
* supplied for the chip .
* @ core : Core device structure
* @ next_state : enum si476x_power_state describing power state to
* switch to .
*
* Switch on all the required power supplies
*
* This function returns 0 in case of suvccess and negative error code
* otherwise .
*/
int si476x_core_set_power_state ( struct si476x_core * core ,
enum si476x_power_state next_state )
{
/*
It is not clear form the datasheet if it is possible to
work with device if not all power domains are operational .
So for now the power - up policy is " power-up all the things! "
*/
int err = 0 ;
if ( core - > power_state = = SI476X_POWER_INCONSISTENT ) {
dev_err ( & core - > client - > dev ,
" The device in inconsistent power state \n " ) ;
return - EINVAL ;
}
if ( next_state ! = core - > power_state ) {
switch ( next_state ) {
case SI476X_POWER_UP_FULL :
err = regulator_bulk_enable ( ARRAY_SIZE ( core - > supplies ) ,
core - > supplies ) ;
if ( err < 0 ) {
core - > power_state = SI476X_POWER_INCONSISTENT ;
break ;
}
/*
* Startup timing diagram recommends to have a
* 100 us delay between enabling of the power
* supplies and turning the tuner on .
*/
udelay ( 100 ) ;
err = si476x_core_start ( core , false ) ;
if ( err < 0 )
goto disable_regulators ;
core - > power_state = next_state ;
break ;
case SI476X_POWER_DOWN :
core - > power_state = next_state ;
err = si476x_core_stop ( core , false ) ;
if ( err < 0 )
core - > power_state = SI476X_POWER_INCONSISTENT ;
disable_regulators :
err = regulator_bulk_disable ( ARRAY_SIZE ( core - > supplies ) ,
core - > supplies ) ;
if ( err < 0 )
core - > power_state = SI476X_POWER_INCONSISTENT ;
break ;
default :
BUG ( ) ;
}
}
return err ;
}
EXPORT_SYMBOL_GPL ( si476x_core_set_power_state ) ;
/**
* si476x_core_report_drainer_stop ( ) - mark the completion of the RDS
* buffer drain porcess by the worker .
*
* @ core : Core device structure
*/
static inline void si476x_core_report_drainer_stop ( struct si476x_core * core )
{
mutex_lock ( & core - > rds_drainer_status_lock ) ;
core - > rds_drainer_is_working = false ;
mutex_unlock ( & core - > rds_drainer_status_lock ) ;
}
/**
* si476x_core_start_rds_drainer_once ( ) - start RDS drainer worker if
* ther is none working , do nothing otherwise
*
* @ core : Datastructure corresponding to the chip .
*/
static inline void si476x_core_start_rds_drainer_once ( struct si476x_core * core )
{
mutex_lock ( & core - > rds_drainer_status_lock ) ;
if ( ! core - > rds_drainer_is_working ) {
core - > rds_drainer_is_working = true ;
schedule_work ( & core - > rds_fifo_drainer ) ;
}
mutex_unlock ( & core - > rds_drainer_status_lock ) ;
}
/**
2021-03-31 11:15:36 +03:00
* si476x_core_drain_rds_fifo ( ) - RDS buffer drainer .
2013-04-18 20:58:28 +04:00
* @ work : struct work_struct being ppassed to the function by the
* kernel .
*
* Drain the contents of the RDS FIFO of
*/
static void si476x_core_drain_rds_fifo ( struct work_struct * work )
{
int err ;
struct si476x_core * core = container_of ( work , struct si476x_core ,
rds_fifo_drainer ) ;
struct si476x_rds_status_report report ;
si476x_core_lock ( core ) ;
err = si476x_core_cmd_fm_rds_status ( core , true , false , false , & report ) ;
if ( ! err ) {
int i = report . rdsfifoused ;
dev_dbg ( & core - > client - > dev ,
" %d elements in RDS FIFO. Draining. \n " , i ) ;
for ( ; i > 0 ; - - i ) {
err = si476x_core_cmd_fm_rds_status ( core , false , false ,
( i = = 1 ) , & report ) ;
if ( err < 0 )
goto unlock ;
kfifo_in ( & core - > rds_fifo , report . rds ,
sizeof ( report . rds ) ) ;
dev_dbg ( & core - > client - > dev , " RDS data: \n %*ph \n " ,
2013-04-19 20:40:05 +04:00
( int ) sizeof ( report . rds ) , report . rds ) ;
2013-04-18 20:58:28 +04:00
}
dev_dbg ( & core - > client - > dev , " Drrrrained! \n " ) ;
wake_up_interruptible ( & core - > rds_read_queue ) ;
}
unlock :
si476x_core_unlock ( core ) ;
si476x_core_report_drainer_stop ( core ) ;
}
/**
* si476x_core_pronounce_dead ( )
*
* @ core : Core device structure
*
* Mark the device as being dead and wake up all potentially waiting
* threads of execution .
*
*/
static void si476x_core_pronounce_dead ( struct si476x_core * core )
{
dev_info ( & core - > client - > dev , " Core device is dead. \n " ) ;
atomic_set ( & core - > is_alive , 0 ) ;
/* Wake up al possible waiting processes */
wake_up_interruptible ( & core - > rds_read_queue ) ;
atomic_set ( & core - > cts , 1 ) ;
wake_up ( & core - > command ) ;
atomic_set ( & core - > stc , 1 ) ;
wake_up ( & core - > tuning ) ;
}
/**
* si476x_core_i2c_xfer ( )
*
* @ core : Core device structure
* @ type : Transfer type
* @ buf : Transfer buffer for / with data
* @ count : Transfer buffer size
*
* Perfrom and I2C transfer ( either read or write ) and keep a counter
* of I / O errors . If the error counter rises above the threshold
* pronounce device dead .
*
* The function returns zero on succes or negative error code on
* failure .
*/
int si476x_core_i2c_xfer ( struct si476x_core * core ,
enum si476x_i2c_type type ,
char * buf , int count )
{
static int io_errors_count ;
int err ;
if ( type = = SI476X_I2C_SEND )
err = i2c_master_send ( core - > client , buf , count ) ;
else
err = i2c_master_recv ( core - > client , buf , count ) ;
if ( err < 0 ) {
if ( io_errors_count + + > SI476X_MAX_IO_ERRORS )
si476x_core_pronounce_dead ( core ) ;
} else {
io_errors_count = 0 ;
}
return err ;
}
EXPORT_SYMBOL_GPL ( si476x_core_i2c_xfer ) ;
/**
2021-03-31 11:15:36 +03:00
* si476x_core_get_status ( )
2013-04-18 20:58:28 +04:00
* @ core : Core device structure
*
* Get the status byte of the core device by berforming one byte I2C
* read .
*
* The function returns a status value or a negative error code on
* error .
*/
static int si476x_core_get_status ( struct si476x_core * core )
{
u8 response ;
int err = si476x_core_i2c_xfer ( core , SI476X_I2C_RECV ,
& response , sizeof ( response ) ) ;
return ( err < 0 ) ? err : response ;
}
/**
2021-03-31 11:15:36 +03:00
* si476x_core_get_and_signal_status ( ) - IRQ dispatcher
2013-04-18 20:58:28 +04:00
* @ core : Core device structure
*
* Dispatch the arrived interrupt request based on the value of the
* status byte reported by the tuner .
*
*/
static void si476x_core_get_and_signal_status ( struct si476x_core * core )
{
int status = si476x_core_get_status ( core ) ;
if ( status < 0 ) {
dev_err ( & core - > client - > dev , " Failed to get status \n " ) ;
return ;
}
if ( status & SI476X_CTS ) {
/* Unfortunately completions could not be used for
* signalling CTS since this flag cannot be cleared
* in status byte , and therefore once it becomes true
* multiple calls to ' complete ' would cause the
* commands following the current one to be completed
* before they actually are */
dev_dbg ( & core - > client - > dev , " [interrupt] CTSINT \n " ) ;
atomic_set ( & core - > cts , 1 ) ;
wake_up ( & core - > command ) ;
}
if ( status & SI476X_FM_RDS_INT ) {
dev_dbg ( & core - > client - > dev , " [interrupt] RDSINT \n " ) ;
si476x_core_start_rds_drainer_once ( core ) ;
}
if ( status & SI476X_STC_INT ) {
dev_dbg ( & core - > client - > dev , " [interrupt] STCINT \n " ) ;
atomic_set ( & core - > stc , 1 ) ;
wake_up ( & core - > tuning ) ;
}
}
static void si476x_core_poll_loop ( struct work_struct * work )
{
struct si476x_core * core = SI476X_WORK_TO_CORE ( work ) ;
si476x_core_get_and_signal_status ( core ) ;
if ( atomic_read ( & core - > is_alive ) )
si476x_core_schedule_polling_work ( core ) ;
}
static irqreturn_t si476x_core_interrupt ( int irq , void * dev )
{
struct si476x_core * core = dev ;
si476x_core_get_and_signal_status ( core ) ;
return IRQ_HANDLED ;
}
/**
2021-03-31 11:15:36 +03:00
* si476x_core_fwver_to_revision ( )
2013-04-18 20:58:28 +04:00
* @ core : Core device structure
2020-06-24 16:32:06 +03:00
* @ func : Selects the boot function of the device :
* * _BOOTLOADER - Boot loader
* * _FM_RECEIVER - FM receiver
* * _AM_RECEIVER - AM receiver
* * _WB_RECEIVER - Weatherband receiver
2013-04-18 20:58:28 +04:00
* @ major : Firmware major number
* @ minor1 : Firmware first minor number
* @ minor2 : Firmware second minor number
*
* Convert a chip ' s firmware version number into an offset that later
* will be used to as offset in " vtable " of tuner functions
*
* This function returns a positive offset in case of success and a - 1
* in case of failure .
*/
static int si476x_core_fwver_to_revision ( struct si476x_core * core ,
int func , int major ,
int minor1 , int minor2 )
{
switch ( func ) {
case SI476X_FUNC_FM_RECEIVER :
switch ( major ) {
case 5 :
return SI476X_REVISION_A10 ;
case 8 :
return SI476X_REVISION_A20 ;
case 10 :
return SI476X_REVISION_A30 ;
default :
goto unknown_revision ;
}
case SI476X_FUNC_AM_RECEIVER :
switch ( major ) {
case 5 :
return SI476X_REVISION_A10 ;
case 7 :
return SI476X_REVISION_A20 ;
case 9 :
return SI476X_REVISION_A30 ;
default :
goto unknown_revision ;
}
case SI476X_FUNC_WB_RECEIVER :
switch ( major ) {
case 3 :
return SI476X_REVISION_A10 ;
case 5 :
return SI476X_REVISION_A20 ;
case 7 :
return SI476X_REVISION_A30 ;
default :
goto unknown_revision ;
}
case SI476X_FUNC_BOOTLOADER :
2020-06-24 16:32:33 +03:00
default : /* FALLTHROUGH */
2013-04-18 20:58:28 +04:00
BUG ( ) ;
return - 1 ;
}
unknown_revision :
dev_err ( & core - > client - > dev ,
" Unsupported version of the firmware: %d.%d.%d, "
2016-06-04 22:19:58 +03:00
" reverting to A10 compatible functions \n " ,
2013-04-18 20:58:28 +04:00
major , minor1 , minor2 ) ;
return SI476X_REVISION_A10 ;
}
/**
2021-03-31 11:15:36 +03:00
* si476x_core_get_revision_info ( )
2013-04-18 20:58:28 +04:00
* @ core : Core device structure
*
* Get the firmware version number of the device . It is done in
* following three steps :
* 1. Power - up the device
* 2. Send the ' FUNC_INFO ' command
* 3. Powering the device down .
*
* The function return zero on success and a negative error code on
* failure .
*/
static int si476x_core_get_revision_info ( struct si476x_core * core )
{
int rval ;
struct si476x_func_info info ;
si476x_core_lock ( core ) ;
rval = si476x_core_set_power_state ( core , SI476X_POWER_UP_FULL ) ;
if ( rval < 0 )
goto exit ;
rval = si476x_core_cmd_func_info ( core , & info ) ;
if ( rval < 0 )
goto power_down ;
core - > revision = si476x_core_fwver_to_revision ( core , info . func ,
info . firmware . major ,
info . firmware . minor [ 0 ] ,
info . firmware . minor [ 1 ] ) ;
power_down :
si476x_core_set_power_state ( core , SI476X_POWER_DOWN ) ;
exit :
si476x_core_unlock ( core ) ;
return rval ;
}
bool si476x_core_has_am ( struct si476x_core * core )
{
return core - > chip_id = = SI476X_CHIP_SI4761 | |
core - > chip_id = = SI476X_CHIP_SI4764 ;
}
EXPORT_SYMBOL_GPL ( si476x_core_has_am ) ;
bool si476x_core_has_diversity ( struct si476x_core * core )
{
return core - > chip_id = = SI476X_CHIP_SI4764 ;
}
EXPORT_SYMBOL_GPL ( si476x_core_has_diversity ) ;
bool si476x_core_is_a_secondary_tuner ( struct si476x_core * core )
{
return si476x_core_has_diversity ( core ) & &
( core - > diversity_mode = = SI476X_PHDIV_SECONDARY_ANTENNA | |
core - > diversity_mode = = SI476X_PHDIV_SECONDARY_COMBINING ) ;
}
EXPORT_SYMBOL_GPL ( si476x_core_is_a_secondary_tuner ) ;
bool si476x_core_is_a_primary_tuner ( struct si476x_core * core )
{
return si476x_core_has_diversity ( core ) & &
( core - > diversity_mode = = SI476X_PHDIV_PRIMARY_ANTENNA | |
core - > diversity_mode = = SI476X_PHDIV_PRIMARY_COMBINING ) ;
}
EXPORT_SYMBOL_GPL ( si476x_core_is_a_primary_tuner ) ;
bool si476x_core_is_in_am_receiver_mode ( struct si476x_core * core )
{
return si476x_core_has_am ( core ) & &
( core - > power_up_parameters . func = = SI476X_FUNC_AM_RECEIVER ) ;
}
EXPORT_SYMBOL_GPL ( si476x_core_is_in_am_receiver_mode ) ;
bool si476x_core_is_powered_up ( struct si476x_core * core )
{
return core - > power_state = = SI476X_POWER_UP_FULL ;
}
EXPORT_SYMBOL_GPL ( si476x_core_is_powered_up ) ;
static int si476x_core_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
int rval ;
struct si476x_core * core ;
struct si476x_platform_data * pdata ;
struct mfd_cell * cell ;
int cell_num ;
core = devm_kzalloc ( & client - > dev , sizeof ( * core ) , GFP_KERNEL ) ;
2018-03-08 15:45:31 +03:00
if ( ! core )
2013-04-18 20:58:28 +04:00
return - ENOMEM ;
2018-03-08 15:45:31 +03:00
2013-04-18 20:58:28 +04:00
core - > client = client ;
core - > regmap = devm_regmap_init_si476x ( core ) ;
if ( IS_ERR ( core - > regmap ) ) {
rval = PTR_ERR ( core - > regmap ) ;
dev_err ( & client - > dev ,
" Failed to allocate register map: %d \n " ,
rval ) ;
return rval ;
}
i2c_set_clientdata ( client , core ) ;
atomic_set ( & core - > is_alive , 0 ) ;
core - > power_state = SI476X_POWER_DOWN ;
2013-07-30 12:10:05 +04:00
pdata = dev_get_platdata ( & client - > dev ) ;
2013-04-18 20:58:28 +04:00
if ( pdata ) {
memcpy ( & core - > power_up_parameters ,
& pdata - > power_up_parameters ,
sizeof ( core - > power_up_parameters ) ) ;
core - > gpio_reset = - 1 ;
if ( gpio_is_valid ( pdata - > gpio_reset ) ) {
rval = gpio_request ( pdata - > gpio_reset , " si476x reset " ) ;
if ( rval ) {
dev_err ( & client - > dev ,
" Failed to request gpio: %d \n " , rval ) ;
return rval ;
}
core - > gpio_reset = pdata - > gpio_reset ;
gpio_direction_output ( core - > gpio_reset , 0 ) ;
}
core - > diversity_mode = pdata - > diversity_mode ;
memcpy ( & core - > pinmux , & pdata - > pinmux ,
sizeof ( struct si476x_pinmux ) ) ;
} else {
dev_err ( & client - > dev , " No platform data provided \n " ) ;
return - EINVAL ;
}
core - > supplies [ 0 ] . supply = " vd " ;
core - > supplies [ 1 ] . supply = " va " ;
core - > supplies [ 2 ] . supply = " vio1 " ;
core - > supplies [ 3 ] . supply = " vio2 " ;
rval = devm_regulator_bulk_get ( & client - > dev ,
ARRAY_SIZE ( core - > supplies ) ,
core - > supplies ) ;
if ( rval ) {
2016-10-28 21:35:48 +03:00
dev_err ( & client - > dev , " Failed to get all of the regulators \n " ) ;
2013-04-18 20:58:28 +04:00
goto free_gpio ;
}
mutex_init ( & core - > cmd_lock ) ;
init_waitqueue_head ( & core - > command ) ;
init_waitqueue_head ( & core - > tuning ) ;
rval = kfifo_alloc ( & core - > rds_fifo ,
SI476X_DRIVER_RDS_FIFO_DEPTH *
sizeof ( struct v4l2_rds_data ) ,
GFP_KERNEL ) ;
if ( rval ) {
2015-02-24 17:11:26 +03:00
dev_err ( & client - > dev , " Could not allocate the FIFO \n " ) ;
2013-04-18 20:58:28 +04:00
goto free_gpio ;
}
mutex_init ( & core - > rds_drainer_status_lock ) ;
init_waitqueue_head ( & core - > rds_read_queue ) ;
INIT_WORK ( & core - > rds_fifo_drainer , si476x_core_drain_rds_fifo ) ;
if ( client - > irq ) {
rval = devm_request_threaded_irq ( & client - > dev ,
client - > irq , NULL ,
si476x_core_interrupt ,
2015-05-16 21:42:17 +03:00
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT ,
2013-04-18 20:58:28 +04:00
client - > name , core ) ;
if ( rval < 0 ) {
dev_err ( & client - > dev , " Could not request IRQ %d \n " ,
client - > irq ) ;
goto free_kfifo ;
}
disable_irq ( client - > irq ) ;
dev_dbg ( & client - > dev , " IRQ requested. \n " ) ;
core - > rds_fifo_depth = 20 ;
} else {
INIT_DELAYED_WORK ( & core - > status_monitor ,
si476x_core_poll_loop ) ;
dev_info ( & client - > dev ,
" No IRQ number specified, will use polling \n " ) ;
core - > rds_fifo_depth = 5 ;
}
core - > chip_id = id - > driver_data ;
rval = si476x_core_get_revision_info ( core ) ;
if ( rval < 0 ) {
rval = - ENODEV ;
goto free_kfifo ;
}
cell_num = 0 ;
cell = & core - > cells [ SI476X_RADIO_CELL ] ;
cell - > name = " si476x-radio " ;
cell_num + + ;
# ifdef CONFIG_SND_SOC_SI476X
if ( ( core - > chip_id = = SI476X_CHIP_SI4761 | |
core - > chip_id = = SI476X_CHIP_SI4764 ) & &
core - > pinmux . dclk = = SI476X_DCLK_DAUDIO & &
core - > pinmux . dfs = = SI476X_DFS_DAUDIO & &
core - > pinmux . dout = = SI476X_DOUT_I2S_OUTPUT & &
core - > pinmux . xout = = SI476X_XOUT_TRISTATE ) {
cell = & core - > cells [ SI476X_CODEC_CELL ] ;
cell - > name = " si476x-codec " ;
cell_num + + ;
}
# endif
rval = mfd_add_devices ( & client - > dev ,
( client - > adapter - > nr < < 8 ) + client - > addr ,
core - > cells , cell_num ,
NULL , 0 , NULL ) ;
if ( ! rval )
return 0 ;
free_kfifo :
kfifo_free ( & core - > rds_fifo ) ;
free_gpio :
if ( gpio_is_valid ( core - > gpio_reset ) )
gpio_free ( core - > gpio_reset ) ;
return rval ;
}
static int si476x_core_remove ( struct i2c_client * client )
{
struct si476x_core * core = i2c_get_clientdata ( client ) ;
si476x_core_pronounce_dead ( core ) ;
mfd_remove_devices ( & client - > dev ) ;
if ( client - > irq )
disable_irq ( client - > irq ) ;
else
cancel_delayed_work_sync ( & core - > status_monitor ) ;
kfifo_free ( & core - > rds_fifo ) ;
if ( gpio_is_valid ( core - > gpio_reset ) )
gpio_free ( core - > gpio_reset ) ;
return 0 ;
}
static const struct i2c_device_id si476x_id [ ] = {
{ " si4761 " , SI476X_CHIP_SI4761 } ,
{ " si4764 " , SI476X_CHIP_SI4764 } ,
{ " si4768 " , SI476X_CHIP_SI4768 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( i2c , si476x_id ) ;
static struct i2c_driver si476x_core_driver = {
. driver = {
. name = " si476x-core " ,
} ,
. probe = si476x_core_probe ,
. remove = si476x_core_remove ,
. id_table = si476x_id ,
} ;
module_i2c_driver ( si476x_core_driver ) ;
MODULE_AUTHOR ( " Andrey Smirnov <andrew.smirnov@gmail.com> " ) ;
MODULE_DESCRIPTION ( " Si4761/64/68 AM/FM MFD core device driver " ) ;
MODULE_LICENSE ( " GPL " ) ;