2019-01-16 19:48:55 +03:00
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2019 Nuvoton Technology corporation.
# include <linux/clk.h>
# include <linux/device.h>
# include <linux/mfd/syscon.h>
# include <linux/io.h>
# include <linux/iio/iio.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
2022-06-10 11:45:20 +03:00
# include <linux/mod_devicetable.h>
2019-01-16 19:48:55 +03:00
# include <linux/module.h>
# include <linux/platform_device.h>
2022-07-13 16:26:40 +03:00
# include <linux/property.h>
2019-01-16 19:48:55 +03:00
# include <linux/regmap.h>
# include <linux/regulator/consumer.h>
# include <linux/spinlock.h>
# include <linux/uaccess.h>
2020-02-03 18:09:17 +03:00
# include <linux/reset.h>
2019-01-16 19:48:55 +03:00
2022-07-13 16:26:40 +03:00
struct npcm_adc_info {
u32 data_mask ;
u32 internal_vref ;
u32 res_bits ;
} ;
2019-01-16 19:48:55 +03:00
struct npcm_adc {
bool int_status ;
u32 adc_sample_hz ;
struct device * dev ;
void __iomem * regs ;
struct clk * adc_clk ;
wait_queue_head_t wq ;
struct regulator * vref ;
2020-02-03 18:09:17 +03:00
struct reset_control * reset ;
2020-09-28 16:13:31 +03:00
/*
* Lock to protect the device state during a potential concurrent
* read access from userspace . Reading a raw value requires a sequence
* of register writes , then a wait for a event and finally a register
* read , during which userspace could issue another read request .
* This lock protects a read access from ocurring before another one
* has finished .
*/
struct mutex lock ;
2022-07-13 16:26:40 +03:00
const struct npcm_adc_info * data ;
2019-01-16 19:48:55 +03:00
} ;
/* ADC registers */
# define NPCM_ADCCON 0x00
# define NPCM_ADCDATA 0x04
/* ADCCON Register Bits */
# define NPCM_ADCCON_ADC_INT_EN BIT(21)
# define NPCM_ADCCON_REFSEL BIT(19)
# define NPCM_ADCCON_ADC_INT_ST BIT(18)
# define NPCM_ADCCON_ADC_EN BIT(17)
# define NPCM_ADCCON_ADC_RST BIT(16)
# define NPCM_ADCCON_ADC_CONV BIT(13)
# define NPCM_ADCCON_CH_MASK GENMASK(27, 24)
# define NPCM_ADCCON_CH(x) ((x) << 24)
# define NPCM_ADCCON_DIV_SHIFT 1
# define NPCM_ADCCON_DIV_MASK GENMASK(8, 1)
# define NPCM_ADC_ENABLE (NPCM_ADCCON_ADC_EN | NPCM_ADCCON_ADC_INT_EN)
/* ADC General Definition */
2022-07-13 16:26:40 +03:00
static const struct npcm_adc_info npxm7xx_adc_info = {
. data_mask = GENMASK ( 9 , 0 ) ,
. internal_vref = 2048 ,
. res_bits = 10 ,
} ;
static const struct npcm_adc_info npxm8xx_adc_info = {
. data_mask = GENMASK ( 11 , 0 ) ,
. internal_vref = 1229 ,
. res_bits = 12 ,
} ;
2019-01-16 19:48:55 +03:00
# define NPCM_ADC_CHAN(ch) { \
. type = IIO_VOLTAGE , \
. indexed = 1 , \
. channel = ch , \
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) , \
. info_mask_shared_by_type = BIT ( IIO_CHAN_INFO_SCALE ) | \
BIT ( IIO_CHAN_INFO_SAMP_FREQ ) , \
}
static const struct iio_chan_spec npcm_adc_iio_channels [ ] = {
NPCM_ADC_CHAN ( 0 ) ,
NPCM_ADC_CHAN ( 1 ) ,
NPCM_ADC_CHAN ( 2 ) ,
NPCM_ADC_CHAN ( 3 ) ,
NPCM_ADC_CHAN ( 4 ) ,
NPCM_ADC_CHAN ( 5 ) ,
NPCM_ADC_CHAN ( 6 ) ,
NPCM_ADC_CHAN ( 7 ) ,
} ;
static irqreturn_t npcm_adc_isr ( int irq , void * data )
{
u32 regtemp ;
struct iio_dev * indio_dev = data ;
struct npcm_adc * info = iio_priv ( indio_dev ) ;
regtemp = ioread32 ( info - > regs + NPCM_ADCCON ) ;
if ( regtemp & NPCM_ADCCON_ADC_INT_ST ) {
iowrite32 ( regtemp , info - > regs + NPCM_ADCCON ) ;
wake_up_interruptible ( & info - > wq ) ;
info - > int_status = true ;
}
return IRQ_HANDLED ;
}
static int npcm_adc_read ( struct npcm_adc * info , int * val , u8 channel )
{
int ret ;
u32 regtemp ;
/* Select ADC channel */
regtemp = ioread32 ( info - > regs + NPCM_ADCCON ) ;
regtemp & = ~ NPCM_ADCCON_CH_MASK ;
info - > int_status = false ;
iowrite32 ( regtemp | NPCM_ADCCON_CH ( channel ) |
NPCM_ADCCON_ADC_CONV , info - > regs + NPCM_ADCCON ) ;
ret = wait_event_interruptible_timeout ( info - > wq , info - > int_status ,
msecs_to_jiffies ( 10 ) ) ;
if ( ret = = 0 ) {
regtemp = ioread32 ( info - > regs + NPCM_ADCCON ) ;
2020-02-03 18:09:17 +03:00
if ( regtemp & NPCM_ADCCON_ADC_CONV ) {
2019-01-16 19:48:55 +03:00
/* if conversion failed - reset ADC module */
2020-02-03 18:09:17 +03:00
reset_control_assert ( info - > reset ) ;
2019-01-16 19:48:55 +03:00
msleep ( 100 ) ;
2020-02-03 18:09:17 +03:00
reset_control_deassert ( info - > reset ) ;
2019-01-16 19:48:55 +03:00
msleep ( 100 ) ;
/* Enable ADC and start conversion module */
iowrite32 ( NPCM_ADC_ENABLE | NPCM_ADCCON_ADC_CONV ,
info - > regs + NPCM_ADCCON ) ;
dev_err ( info - > dev , " RESET ADC Complete \n " ) ;
}
return - ETIMEDOUT ;
}
if ( ret < 0 )
return ret ;
2022-07-13 16:26:40 +03:00
* val = ioread32 ( info - > regs + NPCM_ADCDATA ) ;
* val & = info - > data - > data_mask ;
2019-01-16 19:48:55 +03:00
return 0 ;
}
static int npcm_adc_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan , int * val ,
int * val2 , long mask )
{
int ret ;
int vref_uv ;
struct npcm_adc * info = iio_priv ( indio_dev ) ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
2020-09-28 16:13:31 +03:00
mutex_lock ( & info - > lock ) ;
2019-01-16 19:48:55 +03:00
ret = npcm_adc_read ( info , val , chan - > channel ) ;
2020-09-28 16:13:31 +03:00
mutex_unlock ( & info - > lock ) ;
2019-01-16 19:48:55 +03:00
if ( ret ) {
dev_err ( info - > dev , " NPCM ADC read failed \n " ) ;
return ret ;
}
return IIO_VAL_INT ;
case IIO_CHAN_INFO_SCALE :
2019-04-07 11:19:28 +03:00
if ( ! IS_ERR ( info - > vref ) ) {
2019-01-16 19:48:55 +03:00
vref_uv = regulator_get_voltage ( info - > vref ) ;
* val = vref_uv / 1000 ;
} else {
2022-07-13 16:26:40 +03:00
* val = info - > data - > internal_vref ;
2019-01-16 19:48:55 +03:00
}
2022-07-13 16:26:40 +03:00
* val2 = info - > data - > res_bits ;
2019-01-16 19:48:55 +03:00
return IIO_VAL_FRACTIONAL_LOG2 ;
case IIO_CHAN_INFO_SAMP_FREQ :
* val = info - > adc_sample_hz ;
return IIO_VAL_INT ;
default :
return - EINVAL ;
}
return 0 ;
}
static const struct iio_info npcm_adc_iio_info = {
. read_raw = & npcm_adc_read_raw ,
} ;
static const struct of_device_id npcm_adc_match [ ] = {
2022-07-13 16:26:40 +03:00
{ . compatible = " nuvoton,npcm750-adc " , . data = & npxm7xx_adc_info } ,
{ . compatible = " nuvoton,npcm845-adc " , . data = & npxm8xx_adc_info } ,
2019-01-16 19:48:55 +03:00
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , npcm_adc_match ) ;
static int npcm_adc_probe ( struct platform_device * pdev )
{
int ret ;
int irq ;
u32 div ;
u32 reg_con ;
struct npcm_adc * info ;
struct iio_dev * indio_dev ;
struct device * dev = & pdev - > dev ;
indio_dev = devm_iio_device_alloc ( & pdev - > dev , sizeof ( * info ) ) ;
if ( ! indio_dev )
return - ENOMEM ;
info = iio_priv ( indio_dev ) ;
2022-07-13 16:26:40 +03:00
info - > data = device_get_match_data ( dev ) ;
if ( ! info - > data )
return - EINVAL ;
2020-09-28 16:13:31 +03:00
mutex_init ( & info - > lock ) ;
2019-01-16 19:48:55 +03:00
info - > dev = & pdev - > dev ;
2019-10-13 18:44:27 +03:00
info - > regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
2019-01-16 19:48:55 +03:00
if ( IS_ERR ( info - > regs ) )
return PTR_ERR ( info - > regs ) ;
2020-02-03 18:09:17 +03:00
info - > reset = devm_reset_control_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( info - > reset ) )
return PTR_ERR ( info - > reset ) ;
2019-01-16 19:48:55 +03:00
info - > adc_clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( info - > adc_clk ) ) {
dev_warn ( & pdev - > dev , " ADC clock failed: can't read clk \n " ) ;
return PTR_ERR ( info - > adc_clk ) ;
}
/* calculate ADC clock sample rate */
reg_con = ioread32 ( info - > regs + NPCM_ADCCON ) ;
div = reg_con & NPCM_ADCCON_DIV_MASK ;
div = div > > NPCM_ADCCON_DIV_SHIFT ;
info - > adc_sample_hz = clk_get_rate ( info - > adc_clk ) / ( ( div + 1 ) * 2 ) ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < = 0 ) {
ret = - EINVAL ;
goto err_disable_clk ;
}
ret = devm_request_irq ( & pdev - > dev , irq , npcm_adc_isr , 0 ,
" NPCM_ADC " , indio_dev ) ;
if ( ret < 0 ) {
dev_err ( dev , " failed requesting interrupt \n " ) ;
goto err_disable_clk ;
}
reg_con = ioread32 ( info - > regs + NPCM_ADCCON ) ;
info - > vref = devm_regulator_get_optional ( & pdev - > dev , " vref " ) ;
if ( ! IS_ERR ( info - > vref ) ) {
ret = regulator_enable ( info - > vref ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Can't enable ADC reference voltage \n " ) ;
goto err_disable_clk ;
}
iowrite32 ( reg_con & ~ NPCM_ADCCON_REFSEL ,
info - > regs + NPCM_ADCCON ) ;
} else {
/*
* Any error which is not ENODEV indicates the regulator
* has been specified and so is a failure case .
*/
if ( PTR_ERR ( info - > vref ) ! = - ENODEV ) {
ret = PTR_ERR ( info - > vref ) ;
goto err_disable_clk ;
}
/* Use internal reference */
iowrite32 ( reg_con | NPCM_ADCCON_REFSEL ,
info - > regs + NPCM_ADCCON ) ;
}
init_waitqueue_head ( & info - > wq ) ;
reg_con = ioread32 ( info - > regs + NPCM_ADCCON ) ;
reg_con | = NPCM_ADC_ENABLE ;
/* Enable the ADC Module */
iowrite32 ( reg_con , info - > regs + NPCM_ADCCON ) ;
/* Start ADC conversion */
iowrite32 ( reg_con | NPCM_ADCCON_ADC_CONV , info - > regs + NPCM_ADCCON ) ;
platform_set_drvdata ( pdev , indio_dev ) ;
indio_dev - > name = dev_name ( & pdev - > dev ) ;
indio_dev - > info = & npcm_adc_iio_info ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
indio_dev - > channels = npcm_adc_iio_channels ;
indio_dev - > num_channels = ARRAY_SIZE ( npcm_adc_iio_channels ) ;
ret = iio_device_register ( indio_dev ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Couldn't register the device. \n " ) ;
goto err_iio_register ;
}
pr_info ( " NPCM ADC driver probed \n " ) ;
return 0 ;
err_iio_register :
iowrite32 ( reg_con & ~ NPCM_ADCCON_ADC_EN , info - > regs + NPCM_ADCCON ) ;
if ( ! IS_ERR ( info - > vref ) )
regulator_disable ( info - > vref ) ;
err_disable_clk :
clk_disable_unprepare ( info - > adc_clk ) ;
return ret ;
}
static int npcm_adc_remove ( struct platform_device * pdev )
{
struct iio_dev * indio_dev = platform_get_drvdata ( pdev ) ;
struct npcm_adc * info = iio_priv ( indio_dev ) ;
u32 regtemp ;
iio_device_unregister ( indio_dev ) ;
regtemp = ioread32 ( info - > regs + NPCM_ADCCON ) ;
iowrite32 ( regtemp & ~ NPCM_ADCCON_ADC_EN , info - > regs + NPCM_ADCCON ) ;
if ( ! IS_ERR ( info - > vref ) )
regulator_disable ( info - > vref ) ;
clk_disable_unprepare ( info - > adc_clk ) ;
return 0 ;
}
static struct platform_driver npcm_adc_driver = {
. probe = npcm_adc_probe ,
. remove = npcm_adc_remove ,
. driver = {
. name = " npcm_adc " ,
. of_match_table = npcm_adc_match ,
} ,
} ;
module_platform_driver ( npcm_adc_driver ) ;
MODULE_DESCRIPTION ( " Nuvoton NPCM ADC Driver " ) ;
MODULE_AUTHOR ( " Tomer Maimon <tomer.maimon@nuvoton.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;