2019-02-04 03:15:14 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* ADC driver for the Ingenic JZ47xx SoCs
* Copyright ( c ) 2019 Artur Rojek < contact @ artur - rojek . eu >
*
* based on drivers / mfd / jz4740 - adc . c
*/
# include <dt-bindings/iio/adc/ingenic,adc.h>
# include <linux/clk.h>
# include <linux/iio/iio.h>
# include <linux/io.h>
# include <linux/iopoll.h>
2019-07-04 20:36:56 +03:00
# include <linux/kernel.h>
2019-02-04 03:15:14 +03:00
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/platform_device.h>
# define JZ_ADC_REG_ENABLE 0x00
# define JZ_ADC_REG_CFG 0x04
# define JZ_ADC_REG_CTRL 0x08
# define JZ_ADC_REG_STATUS 0x0c
# define JZ_ADC_REG_ADTCH 0x18
# define JZ_ADC_REG_ADBDAT 0x1c
# define JZ_ADC_REG_ADSDAT 0x20
2019-07-04 20:36:56 +03:00
# define JZ_ADC_REG_ADCLK 0x28
2019-02-04 03:15:14 +03:00
2019-07-27 22:59:40 +03:00
# define JZ_ADC_REG_ENABLE_PD BIT(7)
# define JZ_ADC_REG_CFG_AUX_MD (BIT(0) | BIT(1))
2019-02-04 03:15:14 +03:00
# define JZ_ADC_REG_CFG_BAT_MD BIT(4)
2019-07-04 20:36:56 +03:00
# define JZ_ADC_REG_ADCLK_CLKDIV_LSB 0
2019-07-27 22:59:40 +03:00
# define JZ4725B_ADC_REG_ADCLK_CLKDIV10US_LSB 16
# define JZ4770_ADC_REG_ADCLK_CLKDIV10US_LSB 8
# define JZ4770_ADC_REG_ADCLK_CLKDIVMS_LSB 16
2019-02-04 03:15:14 +03:00
# define JZ_ADC_AUX_VREF 3300
# define JZ_ADC_AUX_VREF_BITS 12
# define JZ_ADC_BATTERY_LOW_VREF 2500
# define JZ_ADC_BATTERY_LOW_VREF_BITS 12
# define JZ4725B_ADC_BATTERY_HIGH_VREF 7500
# define JZ4725B_ADC_BATTERY_HIGH_VREF_BITS 10
# define JZ4740_ADC_BATTERY_HIGH_VREF (7500 * 0.986)
# define JZ4740_ADC_BATTERY_HIGH_VREF_BITS 12
2019-07-27 22:59:40 +03:00
# define JZ4770_ADC_BATTERY_VREF 6600
# define JZ4770_ADC_BATTERY_VREF_BITS 12
2019-02-04 03:15:14 +03:00
2019-07-04 20:36:56 +03:00
struct ingenic_adc ;
2019-02-04 03:15:14 +03:00
struct ingenic_adc_soc_data {
unsigned int battery_high_vref ;
unsigned int battery_high_vref_bits ;
const int * battery_raw_avail ;
size_t battery_raw_avail_size ;
const int * battery_scale_avail ;
size_t battery_scale_avail_size ;
2019-07-27 22:59:40 +03:00
unsigned int battery_vref_mode : 1 ;
unsigned int has_aux2 : 1 ;
2019-07-04 20:36:56 +03:00
int ( * init_clk_div ) ( struct device * dev , struct ingenic_adc * adc ) ;
2019-02-04 03:15:14 +03:00
} ;
struct ingenic_adc {
void __iomem * base ;
struct clk * clk ;
struct mutex lock ;
2019-07-27 22:59:40 +03:00
struct mutex aux_lock ;
2019-02-04 03:15:14 +03:00
const struct ingenic_adc_soc_data * soc_data ;
bool low_vref_mode ;
} ;
static void ingenic_adc_set_config ( struct ingenic_adc * adc ,
uint32_t mask ,
uint32_t val )
{
uint32_t cfg ;
clk_enable ( adc - > clk ) ;
mutex_lock ( & adc - > lock ) ;
cfg = readl ( adc - > base + JZ_ADC_REG_CFG ) & ~ mask ;
cfg | = val ;
writel ( cfg , adc - > base + JZ_ADC_REG_CFG ) ;
mutex_unlock ( & adc - > lock ) ;
clk_disable ( adc - > clk ) ;
}
static void ingenic_adc_enable ( struct ingenic_adc * adc ,
int engine ,
bool enabled )
{
u8 val ;
mutex_lock ( & adc - > lock ) ;
val = readb ( adc - > base + JZ_ADC_REG_ENABLE ) ;
if ( enabled )
val | = BIT ( engine ) ;
else
val & = ~ BIT ( engine ) ;
writeb ( val , adc - > base + JZ_ADC_REG_ENABLE ) ;
mutex_unlock ( & adc - > lock ) ;
}
static int ingenic_adc_capture ( struct ingenic_adc * adc ,
int engine )
{
u8 val ;
int ret ;
ingenic_adc_enable ( adc , engine , true ) ;
ret = readb_poll_timeout ( adc - > base + JZ_ADC_REG_ENABLE , val ,
! ( val & BIT ( engine ) ) , 250 , 1000 ) ;
if ( ret )
ingenic_adc_enable ( adc , engine , false ) ;
return ret ;
}
static int ingenic_adc_write_raw ( struct iio_dev * iio_dev ,
struct iio_chan_spec const * chan ,
int val ,
int val2 ,
long m )
{
struct ingenic_adc * adc = iio_priv ( iio_dev ) ;
switch ( m ) {
case IIO_CHAN_INFO_SCALE :
switch ( chan - > channel ) {
case INGENIC_ADC_BATTERY :
2019-07-27 22:59:40 +03:00
if ( ! adc - > soc_data - > battery_vref_mode )
return - EINVAL ;
2019-02-04 03:15:14 +03:00
if ( val > JZ_ADC_BATTERY_LOW_VREF ) {
ingenic_adc_set_config ( adc ,
JZ_ADC_REG_CFG_BAT_MD ,
0 ) ;
adc - > low_vref_mode = false ;
} else {
ingenic_adc_set_config ( adc ,
JZ_ADC_REG_CFG_BAT_MD ,
JZ_ADC_REG_CFG_BAT_MD ) ;
adc - > low_vref_mode = true ;
}
return 0 ;
default :
return - EINVAL ;
}
default :
return - EINVAL ;
}
}
static const int jz4725b_adc_battery_raw_avail [ ] = {
0 , 1 , ( 1 < < JZ_ADC_BATTERY_LOW_VREF_BITS ) - 1 ,
} ;
static const int jz4725b_adc_battery_scale_avail [ ] = {
JZ4725B_ADC_BATTERY_HIGH_VREF , JZ4725B_ADC_BATTERY_HIGH_VREF_BITS ,
JZ_ADC_BATTERY_LOW_VREF , JZ_ADC_BATTERY_LOW_VREF_BITS ,
} ;
static const int jz4740_adc_battery_raw_avail [ ] = {
0 , 1 , ( 1 < < JZ_ADC_BATTERY_LOW_VREF_BITS ) - 1 ,
} ;
static const int jz4740_adc_battery_scale_avail [ ] = {
JZ4740_ADC_BATTERY_HIGH_VREF , JZ4740_ADC_BATTERY_HIGH_VREF_BITS ,
JZ_ADC_BATTERY_LOW_VREF , JZ_ADC_BATTERY_LOW_VREF_BITS ,
} ;
2019-07-27 22:59:40 +03:00
static const int jz4770_adc_battery_raw_avail [ ] = {
0 , 1 , ( 1 < < JZ4770_ADC_BATTERY_VREF_BITS ) - 1 ,
} ;
static const int jz4770_adc_battery_scale_avail [ ] = {
JZ4770_ADC_BATTERY_VREF , JZ4770_ADC_BATTERY_VREF_BITS ,
} ;
2019-07-04 20:36:56 +03:00
static int jz4725b_adc_init_clk_div ( struct device * dev , struct ingenic_adc * adc )
{
struct clk * parent_clk ;
unsigned long parent_rate , rate ;
unsigned int div_main , div_10us ;
parent_clk = clk_get_parent ( adc - > clk ) ;
if ( ! parent_clk ) {
dev_err ( dev , " ADC clock has no parent \n " ) ;
return - ENODEV ;
}
parent_rate = clk_get_rate ( parent_clk ) ;
/*
* The JZ4725B ADC works at 500 kHz to 8 MHz .
* We pick the highest rate possible .
* In practice we typically get 6 MHz , half of the 12 MHz EXT clock .
*/
div_main = DIV_ROUND_UP ( parent_rate , 8000000 ) ;
div_main = clamp ( div_main , 1u , 64u ) ;
rate = parent_rate / div_main ;
if ( rate < 500000 | | rate > 8000000 ) {
dev_err ( dev , " No valid divider for ADC main clock \n " ) ;
return - EINVAL ;
}
/* We also need a divider that produces a 10us clock. */
div_10us = DIV_ROUND_UP ( rate , 100000 ) ;
2019-07-27 22:59:40 +03:00
writel ( ( ( div_10us - 1 ) < < JZ4725B_ADC_REG_ADCLK_CLKDIV10US_LSB ) |
( div_main - 1 ) < < JZ_ADC_REG_ADCLK_CLKDIV_LSB ,
adc - > base + JZ_ADC_REG_ADCLK ) ;
return 0 ;
}
static int jz4770_adc_init_clk_div ( struct device * dev , struct ingenic_adc * adc )
{
struct clk * parent_clk ;
unsigned long parent_rate , rate ;
unsigned int div_main , div_ms , div_10us ;
parent_clk = clk_get_parent ( adc - > clk ) ;
if ( ! parent_clk ) {
dev_err ( dev , " ADC clock has no parent \n " ) ;
return - ENODEV ;
}
parent_rate = clk_get_rate ( parent_clk ) ;
/*
* The JZ4770 ADC works at 20 kHz to 200 kHz .
* We pick the highest rate possible .
*/
div_main = DIV_ROUND_UP ( parent_rate , 200000 ) ;
div_main = clamp ( div_main , 1u , 256u ) ;
rate = parent_rate / div_main ;
if ( rate < 20000 | | rate > 200000 ) {
dev_err ( dev , " No valid divider for ADC main clock \n " ) ;
return - EINVAL ;
}
/* We also need a divider that produces a 10us clock. */
div_10us = DIV_ROUND_UP ( rate , 10000 ) ;
/* And another, which produces a 1ms clock. */
div_ms = DIV_ROUND_UP ( rate , 1000 ) ;
writel ( ( ( div_ms - 1 ) < < JZ4770_ADC_REG_ADCLK_CLKDIVMS_LSB ) |
( ( div_10us - 1 ) < < JZ4770_ADC_REG_ADCLK_CLKDIV10US_LSB ) |
2019-07-04 20:36:56 +03:00
( div_main - 1 ) < < JZ_ADC_REG_ADCLK_CLKDIV_LSB ,
adc - > base + JZ_ADC_REG_ADCLK ) ;
return 0 ;
}
2019-02-04 03:15:14 +03:00
static const struct ingenic_adc_soc_data jz4725b_adc_soc_data = {
. battery_high_vref = JZ4725B_ADC_BATTERY_HIGH_VREF ,
. battery_high_vref_bits = JZ4725B_ADC_BATTERY_HIGH_VREF_BITS ,
. battery_raw_avail = jz4725b_adc_battery_raw_avail ,
. battery_raw_avail_size = ARRAY_SIZE ( jz4725b_adc_battery_raw_avail ) ,
. battery_scale_avail = jz4725b_adc_battery_scale_avail ,
. battery_scale_avail_size = ARRAY_SIZE ( jz4725b_adc_battery_scale_avail ) ,
2019-07-27 22:59:40 +03:00
. battery_vref_mode = true ,
. has_aux2 = false ,
2019-07-04 20:36:56 +03:00
. init_clk_div = jz4725b_adc_init_clk_div ,
2019-02-04 03:15:14 +03:00
} ;
static const struct ingenic_adc_soc_data jz4740_adc_soc_data = {
. battery_high_vref = JZ4740_ADC_BATTERY_HIGH_VREF ,
. battery_high_vref_bits = JZ4740_ADC_BATTERY_HIGH_VREF_BITS ,
. battery_raw_avail = jz4740_adc_battery_raw_avail ,
. battery_raw_avail_size = ARRAY_SIZE ( jz4740_adc_battery_raw_avail ) ,
. battery_scale_avail = jz4740_adc_battery_scale_avail ,
. battery_scale_avail_size = ARRAY_SIZE ( jz4740_adc_battery_scale_avail ) ,
2019-07-27 22:59:40 +03:00
. battery_vref_mode = true ,
. has_aux2 = false ,
2019-07-04 20:36:56 +03:00
. init_clk_div = NULL , /* no ADCLK register on JZ4740 */
2019-02-04 03:15:14 +03:00
} ;
2019-07-27 22:59:40 +03:00
static const struct ingenic_adc_soc_data jz4770_adc_soc_data = {
. battery_high_vref = JZ4770_ADC_BATTERY_VREF ,
. battery_high_vref_bits = JZ4770_ADC_BATTERY_VREF_BITS ,
. battery_raw_avail = jz4770_adc_battery_raw_avail ,
. battery_raw_avail_size = ARRAY_SIZE ( jz4770_adc_battery_raw_avail ) ,
. battery_scale_avail = jz4770_adc_battery_scale_avail ,
. battery_scale_avail_size = ARRAY_SIZE ( jz4770_adc_battery_scale_avail ) ,
. battery_vref_mode = false ,
. has_aux2 = true ,
. init_clk_div = jz4770_adc_init_clk_div ,
} ;
2019-02-04 03:15:14 +03:00
static int ingenic_adc_read_avail ( struct iio_dev * iio_dev ,
struct iio_chan_spec const * chan ,
const int * * vals ,
int * type ,
int * length ,
long m )
{
struct ingenic_adc * adc = iio_priv ( iio_dev ) ;
switch ( m ) {
case IIO_CHAN_INFO_RAW :
* type = IIO_VAL_INT ;
* length = adc - > soc_data - > battery_raw_avail_size ;
* vals = adc - > soc_data - > battery_raw_avail ;
return IIO_AVAIL_RANGE ;
case IIO_CHAN_INFO_SCALE :
* type = IIO_VAL_FRACTIONAL_LOG2 ;
* length = adc - > soc_data - > battery_scale_avail_size ;
* vals = adc - > soc_data - > battery_scale_avail ;
return IIO_AVAIL_LIST ;
default :
return - EINVAL ;
} ;
}
2019-07-27 22:59:40 +03:00
static int ingenic_adc_read_chan_info_raw ( struct ingenic_adc * adc ,
struct iio_chan_spec const * chan ,
int * val )
{
int bit , ret , engine = ( chan - > channel = = INGENIC_ADC_BATTERY ) ;
/* We cannot sample AUX/AUX2 in parallel. */
mutex_lock ( & adc - > aux_lock ) ;
if ( adc - > soc_data - > has_aux2 & & engine = = 0 ) {
bit = BIT ( chan - > channel = = INGENIC_ADC_AUX2 ) ;
ingenic_adc_set_config ( adc , JZ_ADC_REG_CFG_AUX_MD , bit ) ;
}
clk_enable ( adc - > clk ) ;
ret = ingenic_adc_capture ( adc , engine ) ;
if ( ret )
goto out ;
switch ( chan - > channel ) {
case INGENIC_ADC_AUX :
case INGENIC_ADC_AUX2 :
* val = readw ( adc - > base + JZ_ADC_REG_ADSDAT ) ;
break ;
case INGENIC_ADC_BATTERY :
* val = readw ( adc - > base + JZ_ADC_REG_ADBDAT ) ;
break ;
}
ret = IIO_VAL_INT ;
out :
clk_disable ( adc - > clk ) ;
mutex_unlock ( & adc - > aux_lock ) ;
return ret ;
}
2019-02-04 03:15:14 +03:00
static int ingenic_adc_read_raw ( struct iio_dev * iio_dev ,
struct iio_chan_spec const * chan ,
int * val ,
int * val2 ,
long m )
{
struct ingenic_adc * adc = iio_priv ( iio_dev ) ;
switch ( m ) {
case IIO_CHAN_INFO_RAW :
2019-07-27 22:59:40 +03:00
return ingenic_adc_read_chan_info_raw ( adc , chan , val ) ;
2019-02-04 03:15:14 +03:00
case IIO_CHAN_INFO_SCALE :
switch ( chan - > channel ) {
case INGENIC_ADC_AUX :
2019-07-27 22:59:40 +03:00
case INGENIC_ADC_AUX2 :
2019-02-04 03:15:14 +03:00
* val = JZ_ADC_AUX_VREF ;
* val2 = JZ_ADC_AUX_VREF_BITS ;
break ;
case INGENIC_ADC_BATTERY :
if ( adc - > low_vref_mode ) {
* val = JZ_ADC_BATTERY_LOW_VREF ;
* val2 = JZ_ADC_BATTERY_LOW_VREF_BITS ;
} else {
* val = adc - > soc_data - > battery_high_vref ;
* val2 = adc - > soc_data - > battery_high_vref_bits ;
}
break ;
}
return IIO_VAL_FRACTIONAL_LOG2 ;
default :
return - EINVAL ;
}
}
static void ingenic_adc_clk_cleanup ( void * data )
{
clk_unprepare ( data ) ;
}
static const struct iio_info ingenic_adc_info = {
. write_raw = ingenic_adc_write_raw ,
. read_raw = ingenic_adc_read_raw ,
. read_avail = ingenic_adc_read_avail ,
} ;
static const struct iio_chan_spec ingenic_channels [ ] = {
{
. extend_name = " aux " ,
. type = IIO_VOLTAGE ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ,
. indexed = 1 ,
. channel = INGENIC_ADC_AUX ,
} ,
{
. extend_name = " battery " ,
. type = IIO_VOLTAGE ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ,
. info_mask_separate_available = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ,
. indexed = 1 ,
. channel = INGENIC_ADC_BATTERY ,
} ,
2019-07-27 22:59:40 +03:00
{ /* Must always be last in the array. */
. extend_name = " aux2 " ,
. type = IIO_VOLTAGE ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ,
. indexed = 1 ,
. channel = INGENIC_ADC_AUX2 ,
} ,
2019-02-04 03:15:14 +03:00
} ;
static int ingenic_adc_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct iio_dev * iio_dev ;
struct ingenic_adc * adc ;
const struct ingenic_adc_soc_data * soc_data ;
int ret ;
soc_data = device_get_match_data ( dev ) ;
if ( ! soc_data )
return - EINVAL ;
iio_dev = devm_iio_device_alloc ( dev , sizeof ( * adc ) ) ;
if ( ! iio_dev )
return - ENOMEM ;
adc = iio_priv ( iio_dev ) ;
mutex_init ( & adc - > lock ) ;
2019-07-27 22:59:40 +03:00
mutex_init ( & adc - > aux_lock ) ;
2019-02-04 03:15:14 +03:00
adc - > soc_data = soc_data ;
2019-10-13 18:27:16 +03:00
adc - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2019-02-18 09:57:35 +03:00
if ( IS_ERR ( adc - > base ) )
2019-02-04 03:15:14 +03:00
return PTR_ERR ( adc - > base ) ;
adc - > clk = devm_clk_get ( dev , " adc " ) ;
if ( IS_ERR ( adc - > clk ) ) {
dev_err ( dev , " Unable to get clock \n " ) ;
return PTR_ERR ( adc - > clk ) ;
}
ret = clk_prepare_enable ( adc - > clk ) ;
if ( ret ) {
dev_err ( dev , " Failed to enable clock \n " ) ;
return ret ;
}
2019-07-04 20:36:56 +03:00
/* Set clock dividers. */
if ( soc_data - > init_clk_div ) {
ret = soc_data - > init_clk_div ( dev , adc ) ;
if ( ret ) {
clk_disable_unprepare ( adc - > clk ) ;
return ret ;
}
}
2019-02-04 03:15:14 +03:00
/* Put hardware in a known passive state. */
writeb ( 0x00 , adc - > base + JZ_ADC_REG_ENABLE ) ;
writeb ( 0xff , adc - > base + JZ_ADC_REG_CTRL ) ;
2019-07-27 22:59:40 +03:00
usleep_range ( 2000 , 3000 ) ; /* Must wait at least 2ms. */
2019-02-04 03:15:14 +03:00
clk_disable ( adc - > clk ) ;
ret = devm_add_action_or_reset ( dev , ingenic_adc_clk_cleanup , adc - > clk ) ;
if ( ret ) {
dev_err ( dev , " Unable to add action \n " ) ;
return ret ;
}
iio_dev - > dev . parent = dev ;
iio_dev - > name = " jz-adc " ;
iio_dev - > modes = INDIO_DIRECT_MODE ;
iio_dev - > channels = ingenic_channels ;
iio_dev - > num_channels = ARRAY_SIZE ( ingenic_channels ) ;
2019-07-27 22:59:40 +03:00
/* Remove AUX2 from the list of supported channels. */
if ( ! adc - > soc_data - > has_aux2 )
iio_dev - > num_channels - = 1 ;
2019-02-04 03:15:14 +03:00
iio_dev - > info = & ingenic_adc_info ;
ret = devm_iio_device_register ( dev , iio_dev ) ;
if ( ret )
dev_err ( dev , " Unable to register IIO device \n " ) ;
return ret ;
}
# ifdef CONFIG_OF
static const struct of_device_id ingenic_adc_of_match [ ] = {
{ . compatible = " ingenic,jz4725b-adc " , . data = & jz4725b_adc_soc_data , } ,
{ . compatible = " ingenic,jz4740-adc " , . data = & jz4740_adc_soc_data , } ,
2019-07-27 22:59:40 +03:00
{ . compatible = " ingenic,jz4770-adc " , . data = & jz4770_adc_soc_data , } ,
2019-02-04 03:15:14 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ingenic_adc_of_match ) ;
# endif
static struct platform_driver ingenic_adc_driver = {
. driver = {
. name = " ingenic-adc " ,
. of_match_table = of_match_ptr ( ingenic_adc_of_match ) ,
} ,
. probe = ingenic_adc_probe ,
} ;
module_platform_driver ( ingenic_adc_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;