2019-05-29 07:17:59 -07:00
// SPDX-License-Identifier: GPL-2.0-only
2014-10-06 21:17:15 -07:00
/*
* axp288_adc . c - X - Powers AXP288 PMIC ADC Driver
*
* Copyright ( C ) 2014 Intel Corporation
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*/
2019-09-15 20:53:42 +02:00
# include <linux/dmi.h>
2014-10-06 21:17:15 -07:00
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/device.h>
# include <linux/regmap.h>
# include <linux/mfd/axp20x.h>
# include <linux/platform_device.h>
# include <linux/iio/iio.h>
# include <linux/iio/machine.h>
# include <linux/iio/driver.h>
2019-01-05 19:36:18 +01:00
/*
* This mask enables all ADCs except for the battery temp - sensor ( TS ) , that is
* left as - is to avoid breaking charging on devices without a temp - sensor .
*/
# define AXP288_ADC_EN_MASK 0xF0
# define AXP288_ADC_TS_ENABLE 0x01
2019-09-15 20:53:42 +02:00
# define AXP288_ADC_TS_BIAS_MASK GENMASK(5, 4)
# define AXP288_ADC_TS_BIAS_20UA (0 << 4)
# define AXP288_ADC_TS_BIAS_40UA (1 << 4)
# define AXP288_ADC_TS_BIAS_60UA (2 << 4)
# define AXP288_ADC_TS_BIAS_80UA (3 << 4)
2019-01-05 19:36:18 +01:00
# define AXP288_ADC_TS_CURRENT_ON_OFF_MASK GENMASK(1, 0)
# define AXP288_ADC_TS_CURRENT_OFF (0 << 0)
# define AXP288_ADC_TS_CURRENT_ON_WHEN_CHARGING (1 << 0)
# define AXP288_ADC_TS_CURRENT_ON_ONDEMAND (2 << 0)
# define AXP288_ADC_TS_CURRENT_ON (3 << 0)
2014-10-06 21:17:15 -07:00
enum axp288_adc_id {
AXP288_ADC_TS ,
AXP288_ADC_PMIC ,
AXP288_ADC_GP ,
AXP288_ADC_BATT_CHRG_I ,
AXP288_ADC_BATT_DISCHRG_I ,
AXP288_ADC_BATT_V ,
AXP288_ADC_NR_CHAN ,
} ;
struct axp288_adc_info {
int irq ;
struct regmap * regmap ;
2019-01-05 19:36:18 +01:00
bool ts_enabled ;
2014-10-06 21:17:15 -07:00
} ;
2016-01-22 17:49:22 +00:00
static const struct iio_chan_spec axp288_adc_channels [ ] = {
2014-10-06 21:17:15 -07:00
{
. indexed = 1 ,
. type = IIO_TEMP ,
. channel = 0 ,
. address = AXP288_TS_ADC_H ,
. datasheet_name = " TS_PIN " ,
2015-04-06 11:38:20 -07:00
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
2014-10-06 21:17:15 -07:00
} , {
. indexed = 1 ,
. type = IIO_TEMP ,
. channel = 1 ,
. address = AXP288_PMIC_ADC_H ,
. datasheet_name = " PMIC_TEMP " ,
2015-04-06 11:38:20 -07:00
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
2014-10-06 21:17:15 -07:00
} , {
. indexed = 1 ,
. type = IIO_TEMP ,
. channel = 2 ,
. address = AXP288_GP_ADC_H ,
. datasheet_name = " GPADC " ,
2015-04-06 11:38:20 -07:00
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
2014-10-06 21:17:15 -07:00
} , {
. indexed = 1 ,
. type = IIO_CURRENT ,
. channel = 3 ,
. address = AXP20X_BATT_CHRG_I_H ,
. datasheet_name = " BATT_CHG_I " ,
2015-04-06 11:38:20 -07:00
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
2014-10-06 21:17:15 -07:00
} , {
. indexed = 1 ,
. type = IIO_CURRENT ,
. channel = 4 ,
. address = AXP20X_BATT_DISCHRG_I_H ,
. datasheet_name = " BATT_DISCHRG_I " ,
2015-04-06 11:38:20 -07:00
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
2014-10-06 21:17:15 -07:00
} , {
. indexed = 1 ,
. type = IIO_VOLTAGE ,
. channel = 5 ,
. address = AXP20X_BATT_V_H ,
. datasheet_name = " BATT_V " ,
2015-04-06 11:38:20 -07:00
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) ,
2014-10-06 21:17:15 -07:00
} ,
} ;
/* for consumer drivers */
static struct iio_map axp288_adc_default_maps [ ] = {
2017-11-25 09:38:17 +01:00
IIO_MAP ( " TS_PIN " , " axp288-batt " , " axp288-batt-temp " ) ,
IIO_MAP ( " PMIC_TEMP " , " axp288-pmic " , " axp288-pmic-temp " ) ,
IIO_MAP ( " GPADC " , " axp288-gpadc " , " axp288-system-temp " ) ,
IIO_MAP ( " BATT_CHG_I " , " axp288-chrg " , " axp288-chrg-curr " ) ,
IIO_MAP ( " BATT_DISCHRG_I " , " axp288-chrg " , " axp288-chrg-d-curr " ) ,
IIO_MAP ( " BATT_V " , " axp288-batt " , " axp288-batt-volt " ) ,
2014-10-06 21:17:15 -07:00
{ } ,
} ;
static int axp288_adc_read_channel ( int * val , unsigned long address ,
struct regmap * regmap )
{
u8 buf [ 2 ] ;
if ( regmap_bulk_read ( regmap , address , buf , 2 ) )
return - EIO ;
* val = ( buf [ 0 ] < < 4 ) + ( ( buf [ 1 ] > > 4 ) & 0x0F ) ;
return IIO_VAL_INT ;
}
2019-01-05 19:36:18 +01:00
/*
* The current - source used for the battery temp - sensor ( TS ) is shared
* with the GPADC . For proper fuel - gauge and charger operation the TS
* current - source needs to be permanently on . But to read the GPADC we
* need to temporary switch the TS current - source to ondemand , so that
* the GPADC can use it , otherwise we will always read an all 0 value .
*/
static int axp288_adc_set_ts ( struct axp288_adc_info * info ,
unsigned int mode , unsigned long address )
2017-06-30 19:42:54 +02:00
{
2017-07-08 15:11:57 +02:00
int ret ;
2019-01-05 19:36:18 +01:00
/* No need to switch the current-source if the TS pin is disabled */
if ( ! info - > ts_enabled )
return 0 ;
/* Channels other than GPADC do not need the current source */
2017-06-30 19:42:54 +02:00
if ( address ! = AXP288_GP_ADC_H )
return 0 ;
2019-01-05 19:36:18 +01:00
ret = regmap_update_bits ( info - > regmap , AXP288_ADC_TS_PIN_CTRL ,
AXP288_ADC_TS_CURRENT_ON_OFF_MASK , mode ) ;
2017-07-08 15:11:57 +02:00
if ( ret )
return ret ;
/* When switching to the GPADC pin give things some time to settle */
2019-01-05 19:36:18 +01:00
if ( mode = = AXP288_ADC_TS_CURRENT_ON_ONDEMAND )
2017-07-08 15:11:57 +02:00
usleep_range ( 6000 , 10000 ) ;
return 0 ;
2017-06-30 19:42:54 +02:00
}
2014-10-06 21:17:15 -07:00
static int axp288_adc_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int * val , int * val2 , long mask )
{
int ret ;
struct axp288_adc_info * info = iio_priv ( indio_dev ) ;
mutex_lock ( & indio_dev - > mlock ) ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
2019-01-05 19:36:18 +01:00
if ( axp288_adc_set_ts ( info , AXP288_ADC_TS_CURRENT_ON_ONDEMAND ,
2017-06-30 19:42:54 +02:00
chan - > address ) ) {
dev_err ( & indio_dev - > dev , " GPADC mode \n " ) ;
ret = - EINVAL ;
break ;
}
2014-10-06 21:17:15 -07:00
ret = axp288_adc_read_channel ( val , chan - > address , info - > regmap ) ;
2019-01-05 19:36:18 +01:00
if ( axp288_adc_set_ts ( info , AXP288_ADC_TS_CURRENT_ON ,
2017-06-30 19:42:54 +02:00
chan - > address ) )
dev_err ( & indio_dev - > dev , " TS pin restore \n " ) ;
2014-10-06 21:17:15 -07:00
break ;
default :
ret = - EINVAL ;
}
mutex_unlock ( & indio_dev - > mlock ) ;
return ret ;
}
2019-09-15 20:53:42 +02:00
/*
* We rely on the machine ' s firmware to correctly setup the TS pin bias current
* at boot . This lists systems with broken fw where we need to set it ourselves .
*/
static const struct dmi_system_id axp288_adc_ts_bias_override [ ] = {
{
/* Lenovo Ideapad 100S (11 inch) */
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " LENOVO " ) ,
DMI_MATCH ( DMI_PRODUCT_VERSION , " Lenovo ideapad 100S-11IBY " ) ,
} ,
. driver_data = ( void * ) ( uintptr_t ) AXP288_ADC_TS_BIAS_80UA ,
} ,
{ }
} ;
2019-01-05 19:36:18 +01:00
static int axp288_adc_initialize ( struct axp288_adc_info * info )
2017-06-30 19:42:54 +02:00
{
2019-09-15 20:53:42 +02:00
const struct dmi_system_id * bias_override ;
2019-01-05 19:36:18 +01:00
int ret , adc_enable_val ;
2019-09-15 20:53:42 +02:00
bias_override = dmi_first_match ( axp288_adc_ts_bias_override ) ;
if ( bias_override ) {
ret = regmap_update_bits ( info - > regmap , AXP288_ADC_TS_PIN_CTRL ,
AXP288_ADC_TS_BIAS_MASK ,
( uintptr_t ) bias_override - > driver_data ) ;
if ( ret )
return ret ;
}
2019-01-05 19:36:18 +01:00
/*
* Determine if the TS pin is enabled and set the TS current - source
* accordingly .
*/
ret = regmap_read ( info - > regmap , AXP20X_ADC_EN1 , & adc_enable_val ) ;
if ( ret )
return ret ;
if ( adc_enable_val & AXP288_ADC_TS_ENABLE ) {
info - > ts_enabled = true ;
ret = regmap_update_bits ( info - > regmap , AXP288_ADC_TS_PIN_CTRL ,
AXP288_ADC_TS_CURRENT_ON_OFF_MASK ,
AXP288_ADC_TS_CURRENT_ON ) ;
} else {
info - > ts_enabled = false ;
ret = regmap_update_bits ( info - > regmap , AXP288_ADC_TS_PIN_CTRL ,
AXP288_ADC_TS_CURRENT_ON_OFF_MASK ,
AXP288_ADC_TS_CURRENT_OFF ) ;
}
if ( ret )
return ret ;
2017-06-30 19:42:54 +02:00
2019-01-05 19:36:18 +01:00
/* Turn on the ADC for all channels except TS, leave TS as is */
return regmap_update_bits ( info - > regmap , AXP20X_ADC_EN1 ,
AXP288_ADC_EN_MASK , AXP288_ADC_EN_MASK ) ;
2017-06-30 19:42:54 +02:00
}
2014-10-06 21:17:15 -07:00
static const struct iio_info axp288_adc_iio_info = {
. read_raw = & axp288_adc_read_raw ,
} ;
static int axp288_adc_probe ( struct platform_device * pdev )
{
int ret ;
struct axp288_adc_info * info ;
struct iio_dev * indio_dev ;
struct axp20x_dev * axp20x = dev_get_drvdata ( pdev - > dev . parent ) ;
indio_dev = devm_iio_device_alloc ( & pdev - > dev , sizeof ( * info ) ) ;
if ( ! indio_dev )
return - ENOMEM ;
info = iio_priv ( indio_dev ) ;
info - > irq = platform_get_irq ( pdev , 0 ) ;
2019-07-30 11:15:19 -07:00
if ( info - > irq < 0 )
2014-10-06 21:17:15 -07:00
return info - > irq ;
platform_set_drvdata ( pdev , indio_dev ) ;
info - > regmap = axp20x - > regmap ;
/*
* Set ADC to enabled state at all time , including system suspend .
* otherwise internal fuel gauge functionality may be affected .
*/
2019-01-05 19:36:18 +01:00
ret = axp288_adc_initialize ( info ) ;
2014-10-06 21:17:15 -07:00
if ( ret ) {
dev_err ( & pdev - > dev , " unable to enable ADC device \n " ) ;
return ret ;
}
indio_dev - > name = pdev - > name ;
indio_dev - > channels = axp288_adc_channels ;
indio_dev - > num_channels = ARRAY_SIZE ( axp288_adc_channels ) ;
indio_dev - > info = & axp288_adc_iio_info ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
ret = iio_map_array_register ( indio_dev , axp288_adc_default_maps ) ;
if ( ret < 0 )
return ret ;
ret = iio_device_register ( indio_dev ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " unable to register iio device \n " ) ;
goto err_array_unregister ;
}
return 0 ;
err_array_unregister :
iio_map_array_unregister ( indio_dev ) ;
return ret ;
}
static int axp288_adc_remove ( struct platform_device * pdev )
{
struct iio_dev * indio_dev = platform_get_drvdata ( pdev ) ;
iio_device_unregister ( indio_dev ) ;
iio_map_array_unregister ( indio_dev ) ;
return 0 ;
}
2015-05-02 00:53:31 +09:00
static const struct platform_device_id axp288_adc_id_table [ ] = {
2014-11-11 11:30:08 -08:00
{ . name = " axp288_adc " } ,
{ } ,
} ;
2014-10-06 21:17:15 -07:00
static struct platform_driver axp288_adc_driver = {
. probe = axp288_adc_probe ,
. remove = axp288_adc_remove ,
2014-11-11 11:30:08 -08:00
. id_table = axp288_adc_id_table ,
2014-10-06 21:17:15 -07:00
. driver = {
. name = " axp288_adc " ,
} ,
} ;
2014-11-11 11:30:08 -08:00
MODULE_DEVICE_TABLE ( platform , axp288_adc_id_table ) ;
2014-10-06 21:17:15 -07:00
module_platform_driver ( axp288_adc_driver ) ;
MODULE_AUTHOR ( " Jacob Pan <jacob.jun.pan@linux.intel.com> " ) ;
MODULE_DESCRIPTION ( " X-Powers AXP288 ADC Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;