2019-03-26 16:51:38 +02:00
// SPDX-License-Identifier: GPL-2.0
/*
* ADC driver for Basin Cove PMIC
*
* Copyright ( C ) 2012 Intel Corporation
* Author : Bin Yang < bin . yang @ intel . com >
*
* Rewritten for upstream by :
* Vincent Pelletier < plr . vincent @ gmail . com >
* Andy Shevchenko < andriy . shevchenko @ linux . intel . com >
*/
# include <linux/bitops.h>
# include <linux/completion.h>
# include <linux/interrupt.h>
# include <linux/mfd/intel_soc_pmic.h>
# include <linux/mfd/intel_soc_pmic_mrfld.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/iio/driver.h>
# include <linux/iio/iio.h>
# include <linux/iio/machine.h>
# include <asm/unaligned.h>
# define BCOVE_GPADCREQ 0xDC
# define BCOVE_GPADCREQ_BUSY BIT(0)
# define BCOVE_GPADCREQ_IRQEN BIT(1)
# define BCOVE_ADCIRQ_ALL ( \
BCOVE_ADCIRQ_BATTEMP | \
BCOVE_ADCIRQ_SYSTEMP | \
BCOVE_ADCIRQ_BATTID | \
BCOVE_ADCIRQ_VIBATT | \
BCOVE_ADCIRQ_CCTICK )
# define BCOVE_ADC_TIMEOUT msecs_to_jiffies(1000)
static const u8 mrfld_adc_requests [ ] = {
BCOVE_ADCIRQ_VIBATT ,
BCOVE_ADCIRQ_BATTID ,
BCOVE_ADCIRQ_VIBATT ,
BCOVE_ADCIRQ_SYSTEMP ,
BCOVE_ADCIRQ_BATTEMP ,
BCOVE_ADCIRQ_BATTEMP ,
BCOVE_ADCIRQ_SYSTEMP ,
BCOVE_ADCIRQ_SYSTEMP ,
BCOVE_ADCIRQ_SYSTEMP ,
} ;
struct mrfld_adc {
struct regmap * regmap ;
struct completion completion ;
/* Lock to protect the IPC transfers */
struct mutex lock ;
} ;
static irqreturn_t mrfld_adc_thread_isr ( int irq , void * data )
{
struct iio_dev * indio_dev = data ;
struct mrfld_adc * adc = iio_priv ( indio_dev ) ;
complete ( & adc - > completion ) ;
return IRQ_HANDLED ;
}
static int mrfld_adc_single_conv ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int * result )
{
struct mrfld_adc * adc = iio_priv ( indio_dev ) ;
struct regmap * regmap = adc - > regmap ;
unsigned int req ;
long timeout ;
2020-03-11 11:22:23 +02:00
__be16 value ;
2019-03-26 16:51:38 +02:00
int ret ;
reinit_completion ( & adc - > completion ) ;
regmap_update_bits ( regmap , BCOVE_MADCIRQ , BCOVE_ADCIRQ_ALL , 0 ) ;
regmap_update_bits ( regmap , BCOVE_MIRQLVL1 , BCOVE_LVL1_ADC , 0 ) ;
ret = regmap_read_poll_timeout ( regmap , BCOVE_GPADCREQ , req ,
! ( req & BCOVE_GPADCREQ_BUSY ) ,
2000 , 1000000 ) ;
if ( ret )
goto done ;
req = mrfld_adc_requests [ chan - > channel ] ;
ret = regmap_write ( regmap , BCOVE_GPADCREQ , BCOVE_GPADCREQ_IRQEN | req ) ;
if ( ret )
goto done ;
timeout = wait_for_completion_interruptible_timeout ( & adc - > completion ,
BCOVE_ADC_TIMEOUT ) ;
if ( timeout < 0 ) {
ret = timeout ;
goto done ;
}
if ( timeout = = 0 ) {
ret = - ETIMEDOUT ;
goto done ;
}
2020-03-11 11:22:23 +02:00
ret = regmap_bulk_read ( regmap , chan - > address , & value , sizeof ( value ) ) ;
2019-03-26 16:51:38 +02:00
if ( ret )
goto done ;
2020-03-11 11:22:23 +02:00
* result = be16_to_cpu ( value ) ;
2019-03-26 16:51:38 +02:00
ret = IIO_VAL_INT ;
done :
regmap_update_bits ( regmap , BCOVE_MIRQLVL1 , BCOVE_LVL1_ADC , 0xff ) ;
regmap_update_bits ( regmap , BCOVE_MADCIRQ , BCOVE_ADCIRQ_ALL , 0xff ) ;
return ret ;
}
static int mrfld_adc_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int * val , int * val2 , long mask )
{
struct mrfld_adc * adc = iio_priv ( indio_dev ) ;
int ret ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
mutex_lock ( & adc - > lock ) ;
ret = mrfld_adc_single_conv ( indio_dev , chan , val ) ;
mutex_unlock ( & adc - > lock ) ;
return ret ;
default :
return - EINVAL ;
}
}
static const struct iio_info mrfld_adc_iio_info = {
. read_raw = & mrfld_adc_read_raw ,
} ;
# define BCOVE_ADC_CHANNEL(_type, _channel, _datasheet_name, _address) \
{ \
. indexed = 1 , \
. type = _type , \
. channel = _channel , \
. address = _address , \
. datasheet_name = _datasheet_name , \
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) , \
}
static const struct iio_chan_spec mrfld_adc_channels [ ] = {
BCOVE_ADC_CHANNEL ( IIO_VOLTAGE , 0 , " CH0 " , 0xE9 ) ,
BCOVE_ADC_CHANNEL ( IIO_RESISTANCE , 1 , " CH1 " , 0xEB ) ,
BCOVE_ADC_CHANNEL ( IIO_CURRENT , 2 , " CH2 " , 0xED ) ,
BCOVE_ADC_CHANNEL ( IIO_TEMP , 3 , " CH3 " , 0xCC ) ,
BCOVE_ADC_CHANNEL ( IIO_TEMP , 4 , " CH4 " , 0xC8 ) ,
BCOVE_ADC_CHANNEL ( IIO_TEMP , 5 , " CH5 " , 0xCA ) ,
BCOVE_ADC_CHANNEL ( IIO_TEMP , 6 , " CH6 " , 0xC2 ) ,
BCOVE_ADC_CHANNEL ( IIO_TEMP , 7 , " CH7 " , 0xC4 ) ,
BCOVE_ADC_CHANNEL ( IIO_TEMP , 8 , " CH8 " , 0xC6 ) ,
} ;
static struct iio_map iio_maps [ ] = {
IIO_MAP ( " CH0 " , " bcove-battery " , " VBATRSLT " ) ,
IIO_MAP ( " CH1 " , " bcove-battery " , " BATTID " ) ,
IIO_MAP ( " CH2 " , " bcove-battery " , " IBATRSLT " ) ,
IIO_MAP ( " CH3 " , " bcove-temp " , " PMICTEMP " ) ,
IIO_MAP ( " CH4 " , " bcove-temp " , " BATTEMP0 " ) ,
IIO_MAP ( " CH5 " , " bcove-temp " , " BATTEMP1 " ) ,
IIO_MAP ( " CH6 " , " bcove-temp " , " SYSTEMP0 " ) ,
IIO_MAP ( " CH7 " , " bcove-temp " , " SYSTEMP1 " ) ,
IIO_MAP ( " CH8 " , " bcove-temp " , " SYSTEMP2 " ) ,
{ }
} ;
static int mrfld_adc_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct intel_soc_pmic * pmic = dev_get_drvdata ( dev - > parent ) ;
struct iio_dev * indio_dev ;
struct mrfld_adc * adc ;
int irq ;
int ret ;
2019-11-19 09:21:24 +03:00
indio_dev = devm_iio_device_alloc ( dev , sizeof ( struct mrfld_adc ) ) ;
2019-03-26 16:51:38 +02:00
if ( ! indio_dev )
return - ENOMEM ;
adc = iio_priv ( indio_dev ) ;
mutex_init ( & adc - > lock ) ;
init_completion ( & adc - > completion ) ;
adc - > regmap = pmic - > regmap ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 )
return irq ;
ret = devm_request_threaded_irq ( dev , irq , NULL , mrfld_adc_thread_isr ,
IRQF_ONESHOT | IRQF_SHARED , pdev - > name ,
indio_dev ) ;
if ( ret )
return ret ;
platform_set_drvdata ( pdev , indio_dev ) ;
indio_dev - > name = pdev - > name ;
indio_dev - > channels = mrfld_adc_channels ;
indio_dev - > num_channels = ARRAY_SIZE ( mrfld_adc_channels ) ;
indio_dev - > info = & mrfld_adc_iio_info ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
ret = iio_map_array_register ( indio_dev , iio_maps ) ;
if ( ret )
return ret ;
ret = devm_iio_device_register ( dev , indio_dev ) ;
if ( ret < 0 )
goto err_array_unregister ;
return 0 ;
err_array_unregister :
iio_map_array_unregister ( indio_dev ) ;
return ret ;
}
static int mrfld_adc_remove ( struct platform_device * pdev )
{
struct iio_dev * indio_dev = platform_get_drvdata ( pdev ) ;
iio_map_array_unregister ( indio_dev ) ;
return 0 ;
}
static const struct platform_device_id mrfld_adc_id_table [ ] = {
{ . name = " mrfld_bcove_adc " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( platform , mrfld_adc_id_table ) ;
static struct platform_driver mrfld_adc_driver = {
. driver = {
. name = " mrfld_bcove_adc " ,
} ,
. probe = mrfld_adc_probe ,
. remove = mrfld_adc_remove ,
. id_table = mrfld_adc_id_table ,
} ;
module_platform_driver ( mrfld_adc_driver ) ;
MODULE_AUTHOR ( " Bin Yang <bin.yang@intel.com> " ) ;
MODULE_AUTHOR ( " Vincent Pelletier <plr.vincent@gmail.com> " ) ;
MODULE_AUTHOR ( " Andy Shevchenko <andriy.shevchenko@linux.intel.com> " ) ;
MODULE_DESCRIPTION ( " ADC driver for Basin Cove PMIC " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;