2013-02-15 10:56:00 +04:00
/*
* exynos_adc . c - Support for ADC in EXYNOS SoCs
*
* 8 ~ 10 channel , 10 / 12 - bit ADC
*
* Copyright ( C ) 2013 Naveen Krishna Chatradhi < ch . naveen @ samsung . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
2014-07-22 06:04:00 +04:00
# include <linux/errno.h>
2013-02-15 10:56:00 +04:00
# include <linux/kernel.h>
# include <linux/slab.h>
# include <linux/io.h>
# include <linux/clk.h>
# include <linux/completion.h>
# include <linux/of.h>
# include <linux/of_irq.h>
# include <linux/regulator/consumer.h>
# include <linux/of_platform.h>
2013-07-22 15:02:00 +04:00
# include <linux/err.h>
2013-02-15 10:56:00 +04:00
# include <linux/iio/iio.h>
# include <linux/iio/machine.h>
# include <linux/iio/driver.h>
2014-09-16 12:58:00 +04:00
# include <linux/mfd/syscon.h>
# include <linux/regmap.h>
2013-02-15 10:56:00 +04:00
2014-07-28 16:44:00 +04:00
/* S3C/EXYNOS4412/5250 ADC_V1 registers definitions */
2013-02-15 10:56:00 +04:00
# define ADC_V1_CON(x) ((x) + 0x00)
# define ADC_V1_DLY(x) ((x) + 0x08)
# define ADC_V1_DATX(x) ((x) + 0x0C)
# define ADC_V1_INTCLR(x) ((x) + 0x18)
# define ADC_V1_MUX(x) ((x) + 0x1c)
2014-07-28 16:44:00 +04:00
/* S3C2410 ADC registers definitions */
# define ADC_S3C2410_MUX(x) ((x) + 0x18)
2013-02-15 10:56:00 +04:00
/* Future ADC_V2 registers definitions */
# define ADC_V2_CON1(x) ((x) + 0x00)
# define ADC_V2_CON2(x) ((x) + 0x04)
# define ADC_V2_STAT(x) ((x) + 0x08)
# define ADC_V2_INT_EN(x) ((x) + 0x10)
# define ADC_V2_INT_ST(x) ((x) + 0x14)
# define ADC_V2_VER(x) ((x) + 0x20)
/* Bit definitions for ADC_V1 */
# define ADC_V1_CON_RES (1u << 16)
# define ADC_V1_CON_PRSCEN (1u << 14)
# define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6)
# define ADC_V1_CON_STANDBY (1u << 2)
2014-07-28 16:44:00 +04:00
/* Bit definitions for S3C2410 ADC */
# define ADC_S3C2410_CON_SELMUX(x) (((x) & 7) << 3)
2014-07-28 16:44:00 +04:00
# define ADC_S3C2410_DATX_MASK 0x3FF
# define ADC_S3C2416_CON_RES_SEL (1u << 3)
2014-07-28 16:44:00 +04:00
2013-02-15 10:56:00 +04:00
/* Bit definitions for ADC_V2 */
# define ADC_V2_CON1_SOFT_RESET (1u << 2)
# define ADC_V2_CON2_OSEL (1u << 10)
# define ADC_V2_CON2_ESEL (1u << 9)
# define ADC_V2_CON2_HIGHF (1u << 8)
# define ADC_V2_CON2_C_TIME(x) (((x) & 7) << 4)
# define ADC_V2_CON2_ACH_SEL(x) (((x) & 0xF) << 0)
# define ADC_V2_CON2_ACH_MASK 0xF
2014-07-22 06:04:00 +04:00
# define MAX_ADC_V2_CHANNELS 10
# define MAX_ADC_V1_CHANNELS 8
# define MAX_EXYNOS3250_ADC_CHANNELS 2
2013-02-15 10:56:00 +04:00
/* Bit definitions common for ADC_V1 and ADC_V2 */
# define ADC_CON_EN_START (1u << 0)
2014-07-28 16:44:00 +04:00
# define ADC_CON_EN_START_MASK (0x3 << 0)
2013-02-15 10:56:00 +04:00
# define ADC_DATX_MASK 0xFFF
2014-04-30 13:26:00 +04:00
# define EXYNOS_ADC_TIMEOUT (msecs_to_jiffies(100))
2013-02-15 10:56:00 +04:00
2014-09-16 12:58:00 +04:00
# define EXYNOS_ADCV1_PHY_OFFSET 0x0718
# define EXYNOS_ADCV2_PHY_OFFSET 0x0720
2013-02-15 10:56:00 +04:00
struct exynos_adc {
2014-07-22 06:04:00 +04:00
struct exynos_adc_data * data ;
2014-07-22 06:04:00 +04:00
struct device * dev ;
2013-02-15 10:56:00 +04:00
void __iomem * regs ;
2014-09-16 12:58:00 +04:00
struct regmap * pmu_map ;
2013-02-15 10:56:00 +04:00
struct clk * clk ;
2014-07-22 06:04:00 +04:00
struct clk * sclk ;
2013-02-15 10:56:00 +04:00
unsigned int irq ;
struct regulator * vdd ;
struct completion completion ;
u32 value ;
unsigned int version ;
} ;
2014-07-22 06:04:00 +04:00
struct exynos_adc_data {
int num_channels ;
2014-07-22 06:04:00 +04:00
bool needs_sclk ;
2014-07-28 16:44:00 +04:00
bool needs_adc_phy ;
2014-09-16 12:58:00 +04:00
int phy_offset ;
2014-07-28 16:44:00 +04:00
u32 mask ;
2014-07-22 06:04:00 +04:00
void ( * init_hw ) ( struct exynos_adc * info ) ;
void ( * exit_hw ) ( struct exynos_adc * info ) ;
void ( * clear_irq ) ( struct exynos_adc * info ) ;
void ( * start_conv ) ( struct exynos_adc * info , unsigned long addr ) ;
2013-02-15 10:56:00 +04:00
} ;
2014-07-22 06:04:00 +04:00
static void exynos_adc_unprepare_clk ( struct exynos_adc * info )
{
if ( info - > data - > needs_sclk )
clk_unprepare ( info - > sclk ) ;
clk_unprepare ( info - > clk ) ;
}
static int exynos_adc_prepare_clk ( struct exynos_adc * info )
{
int ret ;
ret = clk_prepare ( info - > clk ) ;
if ( ret ) {
dev_err ( info - > dev , " failed preparing adc clock: %d \n " , ret ) ;
return ret ;
}
if ( info - > data - > needs_sclk ) {
ret = clk_prepare ( info - > sclk ) ;
if ( ret ) {
clk_unprepare ( info - > clk ) ;
dev_err ( info - > dev ,
" failed preparing sclk_adc clock: %d \n " , ret ) ;
return ret ;
}
}
return 0 ;
}
static void exynos_adc_disable_clk ( struct exynos_adc * info )
{
if ( info - > data - > needs_sclk )
clk_disable ( info - > sclk ) ;
clk_disable ( info - > clk ) ;
}
static int exynos_adc_enable_clk ( struct exynos_adc * info )
{
int ret ;
ret = clk_enable ( info - > clk ) ;
if ( ret ) {
dev_err ( info - > dev , " failed enabling adc clock: %d \n " , ret ) ;
return ret ;
}
if ( info - > data - > needs_sclk ) {
ret = clk_enable ( info - > sclk ) ;
if ( ret ) {
clk_disable ( info - > clk ) ;
dev_err ( info - > dev ,
" failed enabling sclk_adc clock: %d \n " , ret ) ;
return ret ;
}
}
return 0 ;
}
2014-07-22 06:04:00 +04:00
static void exynos_adc_v1_init_hw ( struct exynos_adc * info )
2013-02-15 10:56:00 +04:00
{
2014-07-22 06:04:00 +04:00
u32 con1 ;
2013-02-15 10:56:00 +04:00
2014-07-28 16:44:00 +04:00
if ( info - > data - > needs_adc_phy )
2014-09-16 12:58:00 +04:00
regmap_write ( info - > pmu_map , info - > data - > phy_offset , 1 ) ;
2014-07-22 06:04:00 +04:00
/* set default prescaler values and Enable prescaler */
con1 = ADC_V1_CON_PRSCLV ( 49 ) | ADC_V1_CON_PRSCEN ;
/* Enable 12-bit ADC resolution */
con1 | = ADC_V1_CON_RES ;
writel ( con1 , ADC_V1_CON ( info - > regs ) ) ;
}
static void exynos_adc_v1_exit_hw ( struct exynos_adc * info )
{
u32 con ;
2014-07-28 16:44:00 +04:00
if ( info - > data - > needs_adc_phy )
2014-09-16 12:58:00 +04:00
regmap_write ( info - > pmu_map , info - > data - > phy_offset , 0 ) ;
2014-07-22 06:04:00 +04:00
con = readl ( ADC_V1_CON ( info - > regs ) ) ;
con | = ADC_V1_CON_STANDBY ;
writel ( con , ADC_V1_CON ( info - > regs ) ) ;
}
static void exynos_adc_v1_clear_irq ( struct exynos_adc * info )
{
writel ( 1 , ADC_V1_INTCLR ( info - > regs ) ) ;
2013-02-15 10:56:00 +04:00
}
2014-07-22 06:04:00 +04:00
static void exynos_adc_v1_start_conv ( struct exynos_adc * info ,
unsigned long addr )
{
u32 con1 ;
writel ( addr , ADC_V1_MUX ( info - > regs ) ) ;
con1 = readl ( ADC_V1_CON ( info - > regs ) ) ;
writel ( con1 | ADC_CON_EN_START , ADC_V1_CON ( info - > regs ) ) ;
}
static const struct exynos_adc_data exynos_adc_v1_data = {
. num_channels = MAX_ADC_V1_CHANNELS ,
2014-07-28 16:44:00 +04:00
. mask = ADC_DATX_MASK , /* 12 bit ADC resolution */
. needs_adc_phy = true ,
2014-09-16 12:58:00 +04:00
. phy_offset = EXYNOS_ADCV1_PHY_OFFSET ,
2014-07-22 06:04:00 +04:00
. init_hw = exynos_adc_v1_init_hw ,
. exit_hw = exynos_adc_v1_exit_hw ,
. clear_irq = exynos_adc_v1_clear_irq ,
. start_conv = exynos_adc_v1_start_conv ,
} ;
2014-07-28 16:44:00 +04:00
static void exynos_adc_s3c2416_start_conv ( struct exynos_adc * info ,
unsigned long addr )
{
u32 con1 ;
/* Enable 12 bit ADC resolution */
con1 = readl ( ADC_V1_CON ( info - > regs ) ) ;
con1 | = ADC_S3C2416_CON_RES_SEL ;
writel ( con1 , ADC_V1_CON ( info - > regs ) ) ;
/* Select channel for S3C2416 */
writel ( addr , ADC_S3C2410_MUX ( info - > regs ) ) ;
con1 = readl ( ADC_V1_CON ( info - > regs ) ) ;
writel ( con1 | ADC_CON_EN_START , ADC_V1_CON ( info - > regs ) ) ;
}
static struct exynos_adc_data const exynos_adc_s3c2416_data = {
. num_channels = MAX_ADC_V1_CHANNELS ,
. mask = ADC_DATX_MASK , /* 12 bit ADC resolution */
. init_hw = exynos_adc_v1_init_hw ,
. exit_hw = exynos_adc_v1_exit_hw ,
. start_conv = exynos_adc_s3c2416_start_conv ,
} ;
static void exynos_adc_s3c2443_start_conv ( struct exynos_adc * info ,
unsigned long addr )
{
u32 con1 ;
/* Select channel for S3C2433 */
writel ( addr , ADC_S3C2410_MUX ( info - > regs ) ) ;
con1 = readl ( ADC_V1_CON ( info - > regs ) ) ;
writel ( con1 | ADC_CON_EN_START , ADC_V1_CON ( info - > regs ) ) ;
}
static struct exynos_adc_data const exynos_adc_s3c2443_data = {
. num_channels = MAX_ADC_V1_CHANNELS ,
. mask = ADC_S3C2410_DATX_MASK , /* 10 bit ADC resolution */
. init_hw = exynos_adc_v1_init_hw ,
. exit_hw = exynos_adc_v1_exit_hw ,
. start_conv = exynos_adc_s3c2443_start_conv ,
} ;
2014-07-28 16:44:00 +04:00
static void exynos_adc_s3c64xx_start_conv ( struct exynos_adc * info ,
unsigned long addr )
{
u32 con1 ;
con1 = readl ( ADC_V1_CON ( info - > regs ) ) ;
con1 & = ~ ADC_S3C2410_CON_SELMUX ( 0x7 ) ;
con1 | = ADC_S3C2410_CON_SELMUX ( addr ) ;
writel ( con1 | ADC_CON_EN_START , ADC_V1_CON ( info - > regs ) ) ;
}
2014-07-28 16:44:00 +04:00
static struct exynos_adc_data const exynos_adc_s3c24xx_data = {
. num_channels = MAX_ADC_V1_CHANNELS ,
. mask = ADC_S3C2410_DATX_MASK , /* 10 bit ADC resolution */
. init_hw = exynos_adc_v1_init_hw ,
. exit_hw = exynos_adc_v1_exit_hw ,
. start_conv = exynos_adc_s3c64xx_start_conv ,
} ;
2014-07-28 16:44:00 +04:00
static struct exynos_adc_data const exynos_adc_s3c64xx_data = {
. num_channels = MAX_ADC_V1_CHANNELS ,
2014-07-28 16:44:00 +04:00
. mask = ADC_DATX_MASK , /* 12 bit ADC resolution */
2014-07-28 16:44:00 +04:00
. init_hw = exynos_adc_v1_init_hw ,
. exit_hw = exynos_adc_v1_exit_hw ,
. clear_irq = exynos_adc_v1_clear_irq ,
. start_conv = exynos_adc_s3c64xx_start_conv ,
} ;
2014-07-22 06:04:00 +04:00
static void exynos_adc_v2_init_hw ( struct exynos_adc * info )
2014-04-30 13:26:00 +04:00
{
u32 con1 , con2 ;
2014-07-28 16:44:00 +04:00
if ( info - > data - > needs_adc_phy )
2014-09-16 12:58:00 +04:00
regmap_write ( info - > pmu_map , info - > data - > phy_offset , 1 ) ;
2014-04-30 13:26:00 +04:00
2014-07-22 06:04:00 +04:00
con1 = ADC_V2_CON1_SOFT_RESET ;
writel ( con1 , ADC_V2_CON1 ( info - > regs ) ) ;
2014-04-30 13:26:00 +04:00
2014-07-22 06:04:00 +04:00
con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL |
ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME ( 0 ) ;
writel ( con2 , ADC_V2_CON2 ( info - > regs ) ) ;
2014-04-30 13:26:00 +04:00
2014-07-22 06:04:00 +04:00
/* Enable interrupts */
writel ( 1 , ADC_V2_INT_EN ( info - > regs ) ) ;
}
static void exynos_adc_v2_exit_hw ( struct exynos_adc * info )
{
u32 con ;
2014-07-28 16:44:00 +04:00
if ( info - > data - > needs_adc_phy )
2014-09-16 12:58:00 +04:00
regmap_write ( info - > pmu_map , info - > data - > phy_offset , 0 ) ;
2014-07-22 06:04:00 +04:00
con = readl ( ADC_V2_CON1 ( info - > regs ) ) ;
con & = ~ ADC_CON_EN_START ;
writel ( con , ADC_V2_CON1 ( info - > regs ) ) ;
}
static void exynos_adc_v2_clear_irq ( struct exynos_adc * info )
{
writel ( 1 , ADC_V2_INT_ST ( info - > regs ) ) ;
}
static void exynos_adc_v2_start_conv ( struct exynos_adc * info ,
unsigned long addr )
{
u32 con1 , con2 ;
con2 = readl ( ADC_V2_CON2 ( info - > regs ) ) ;
con2 & = ~ ADC_V2_CON2_ACH_MASK ;
con2 | = ADC_V2_CON2_ACH_SEL ( addr ) ;
writel ( con2 , ADC_V2_CON2 ( info - > regs ) ) ;
con1 = readl ( ADC_V2_CON1 ( info - > regs ) ) ;
writel ( con1 | ADC_CON_EN_START , ADC_V2_CON1 ( info - > regs ) ) ;
}
static const struct exynos_adc_data exynos_adc_v2_data = {
. num_channels = MAX_ADC_V2_CHANNELS ,
2014-07-28 16:44:00 +04:00
. mask = ADC_DATX_MASK , /* 12 bit ADC resolution */
. needs_adc_phy = true ,
2014-09-16 12:58:00 +04:00
. phy_offset = EXYNOS_ADCV2_PHY_OFFSET ,
2014-07-22 06:04:00 +04:00
. init_hw = exynos_adc_v2_init_hw ,
. exit_hw = exynos_adc_v2_exit_hw ,
. clear_irq = exynos_adc_v2_clear_irq ,
. start_conv = exynos_adc_v2_start_conv ,
} ;
2014-07-22 06:04:00 +04:00
static const struct exynos_adc_data exynos3250_adc_data = {
. num_channels = MAX_EXYNOS3250_ADC_CHANNELS ,
2014-07-28 16:44:00 +04:00
. mask = ADC_DATX_MASK , /* 12 bit ADC resolution */
2014-07-22 06:04:00 +04:00
. needs_sclk = true ,
2014-07-28 16:44:00 +04:00
. needs_adc_phy = true ,
2014-09-16 12:58:00 +04:00
. phy_offset = EXYNOS_ADCV1_PHY_OFFSET ,
2014-07-22 06:04:00 +04:00
. init_hw = exynos_adc_v2_init_hw ,
. exit_hw = exynos_adc_v2_exit_hw ,
. clear_irq = exynos_adc_v2_clear_irq ,
. start_conv = exynos_adc_v2_start_conv ,
} ;
2014-11-01 07:00:43 +03:00
static void exynos_adc_exynos7_init_hw ( struct exynos_adc * info )
{
u32 con1 , con2 ;
if ( info - > data - > needs_adc_phy )
regmap_write ( info - > pmu_map , info - > data - > phy_offset , 1 ) ;
con1 = ADC_V2_CON1_SOFT_RESET ;
writel ( con1 , ADC_V2_CON1 ( info - > regs ) ) ;
con2 = readl ( ADC_V2_CON2 ( info - > regs ) ) ;
con2 & = ~ ADC_V2_CON2_C_TIME ( 7 ) ;
con2 | = ADC_V2_CON2_C_TIME ( 0 ) ;
writel ( con2 , ADC_V2_CON2 ( info - > regs ) ) ;
/* Enable interrupts */
writel ( 1 , ADC_V2_INT_EN ( info - > regs ) ) ;
}
static const struct exynos_adc_data exynos7_adc_data = {
. num_channels = MAX_ADC_V1_CHANNELS ,
. mask = ADC_DATX_MASK , /* 12 bit ADC resolution */
. init_hw = exynos_adc_exynos7_init_hw ,
. exit_hw = exynos_adc_v2_exit_hw ,
. clear_irq = exynos_adc_v2_clear_irq ,
. start_conv = exynos_adc_v2_start_conv ,
} ;
2014-07-22 06:04:00 +04:00
static const struct of_device_id exynos_adc_match [ ] = {
{
2014-07-28 16:44:00 +04:00
. compatible = " samsung,s3c2410-adc " ,
. data = & exynos_adc_s3c24xx_data ,
} , {
. compatible = " samsung,s3c2416-adc " ,
. data = & exynos_adc_s3c2416_data ,
} , {
. compatible = " samsung,s3c2440-adc " ,
. data = & exynos_adc_s3c24xx_data ,
} , {
. compatible = " samsung,s3c2443-adc " ,
. data = & exynos_adc_s3c2443_data ,
} , {
2014-07-28 16:44:00 +04:00
. compatible = " samsung,s3c6410-adc " ,
. data = & exynos_adc_s3c64xx_data ,
} , {
2014-07-22 06:04:00 +04:00
. compatible = " samsung,exynos-adc-v1 " ,
. data = & exynos_adc_v1_data ,
} , {
. compatible = " samsung,exynos-adc-v2 " ,
. data = & exynos_adc_v2_data ,
2014-07-22 06:04:00 +04:00
} , {
. compatible = " samsung,exynos3250-adc " ,
. data = & exynos3250_adc_data ,
2014-11-01 07:00:43 +03:00
} , {
. compatible = " samsung,exynos7-adc " ,
. data = & exynos7_adc_data ,
2014-07-22 06:04:00 +04:00
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , exynos_adc_match ) ;
static struct exynos_adc_data * exynos_adc_get_data ( struct platform_device * pdev )
{
const struct of_device_id * match ;
match = of_match_node ( exynos_adc_match , pdev - > dev . of_node ) ;
return ( struct exynos_adc_data * ) match - > data ;
2014-04-30 13:26:00 +04:00
}
2013-02-15 10:56:00 +04:00
static int exynos_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int * val ,
int * val2 ,
long mask )
{
struct exynos_adc * info = iio_priv ( indio_dev ) ;
unsigned long timeout ;
2014-04-30 13:26:00 +04:00
int ret ;
2013-02-15 10:56:00 +04:00
if ( mask ! = IIO_CHAN_INFO_RAW )
return - EINVAL ;
mutex_lock ( & indio_dev - > mlock ) ;
2014-04-30 13:26:00 +04:00
reinit_completion ( & info - > completion ) ;
2013-02-15 10:56:00 +04:00
/* Select the channel to be used and Trigger conversion */
2014-07-22 06:04:00 +04:00
if ( info - > data - > start_conv )
info - > data - > start_conv ( info , chan - > address ) ;
2013-02-15 10:56:00 +04:00
2014-04-30 13:26:00 +04:00
timeout = wait_for_completion_timeout
2013-02-15 10:56:00 +04:00
( & info - > completion , EXYNOS_ADC_TIMEOUT ) ;
2014-04-30 13:26:00 +04:00
if ( timeout = = 0 ) {
2014-04-30 13:26:00 +04:00
dev_warn ( & indio_dev - > dev , " Conversion timed out! Resetting \n " ) ;
2014-07-22 06:04:00 +04:00
if ( info - > data - > init_hw )
info - > data - > init_hw ( info ) ;
2014-04-30 13:26:00 +04:00
ret = - ETIMEDOUT ;
} else {
* val = info - > value ;
* val2 = 0 ;
ret = IIO_VAL_INT ;
}
2013-02-15 10:56:00 +04:00
mutex_unlock ( & indio_dev - > mlock ) ;
2014-04-30 13:26:00 +04:00
return ret ;
2013-02-15 10:56:00 +04:00
}
static irqreturn_t exynos_adc_isr ( int irq , void * dev_id )
{
struct exynos_adc * info = ( struct exynos_adc * ) dev_id ;
2014-07-28 16:44:00 +04:00
u32 mask = info - > data - > mask ;
2013-02-15 10:56:00 +04:00
/* Read value */
2014-07-28 16:44:00 +04:00
info - > value = readl ( ADC_V1_DATX ( info - > regs ) ) & mask ;
2014-07-22 06:04:00 +04:00
2013-02-15 10:56:00 +04:00
/* clear irq */
2014-07-22 06:04:00 +04:00
if ( info - > data - > clear_irq )
info - > data - > clear_irq ( info ) ;
2013-02-15 10:56:00 +04:00
complete ( & info - > completion ) ;
return IRQ_HANDLED ;
}
static int exynos_adc_reg_access ( struct iio_dev * indio_dev ,
unsigned reg , unsigned writeval ,
unsigned * readval )
{
struct exynos_adc * info = iio_priv ( indio_dev ) ;
if ( readval = = NULL )
return - EINVAL ;
* readval = readl ( info - > regs + reg ) ;
return 0 ;
}
static const struct iio_info exynos_adc_iio_info = {
. read_raw = & exynos_read_raw ,
. debugfs_reg_access = & exynos_adc_reg_access ,
. driver_module = THIS_MODULE ,
} ;
# define ADC_CHANNEL(_index, _id) { \
. type = IIO_VOLTAGE , \
. indexed = 1 , \
. channel = _index , \
. address = _index , \
2013-03-03 16:25:30 +04:00
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) , \
2013-02-15 10:56:00 +04:00
. datasheet_name = _id , \
}
static const struct iio_chan_spec exynos_adc_iio_channels [ ] = {
ADC_CHANNEL ( 0 , " adc0 " ) ,
ADC_CHANNEL ( 1 , " adc1 " ) ,
ADC_CHANNEL ( 2 , " adc2 " ) ,
ADC_CHANNEL ( 3 , " adc3 " ) ,
ADC_CHANNEL ( 4 , " adc4 " ) ,
ADC_CHANNEL ( 5 , " adc5 " ) ,
ADC_CHANNEL ( 6 , " adc6 " ) ,
ADC_CHANNEL ( 7 , " adc7 " ) ,
ADC_CHANNEL ( 8 , " adc8 " ) ,
ADC_CHANNEL ( 9 , " adc9 " ) ,
} ;
static int exynos_adc_remove_devices ( struct device * dev , void * c )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
platform_device_unregister ( pdev ) ;
return 0 ;
}
static int exynos_adc_probe ( struct platform_device * pdev )
{
struct exynos_adc * info = NULL ;
struct device_node * np = pdev - > dev . of_node ;
struct iio_dev * indio_dev = NULL ;
struct resource * mem ;
int ret = - ENODEV ;
int irq ;
if ( ! np )
return ret ;
2013-07-22 15:02:00 +04:00
indio_dev = devm_iio_device_alloc ( & pdev - > dev , sizeof ( struct exynos_adc ) ) ;
2013-02-15 10:56:00 +04:00
if ( ! indio_dev ) {
dev_err ( & pdev - > dev , " failed allocating iio device \n " ) ;
return - ENOMEM ;
}
info = iio_priv ( indio_dev ) ;
2014-07-22 06:04:00 +04:00
info - > data = exynos_adc_get_data ( pdev ) ;
if ( ! info - > data ) {
dev_err ( & pdev - > dev , " failed getting exynos_adc_data \n " ) ;
return - EINVAL ;
}
2013-02-15 10:56:00 +04:00
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-04-03 10:23:00 +04:00
info - > regs = devm_ioremap_resource ( & pdev - > dev , mem ) ;
2013-07-22 15:02:00 +04:00
if ( IS_ERR ( info - > regs ) )
return PTR_ERR ( info - > regs ) ;
2013-02-15 10:56:00 +04:00
2014-07-28 16:44:00 +04:00
if ( info - > data - > needs_adc_phy ) {
2014-09-16 12:58:00 +04:00
info - > pmu_map = syscon_regmap_lookup_by_phandle (
pdev - > dev . of_node ,
" samsung,syscon-phandle " ) ;
if ( IS_ERR ( info - > pmu_map ) ) {
dev_err ( & pdev - > dev , " syscon regmap lookup failed. \n " ) ;
return PTR_ERR ( info - > pmu_map ) ;
}
2014-07-28 16:44:00 +04:00
}
2013-03-14 00:40:00 +04:00
2013-02-15 10:56:00 +04:00
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 ) {
dev_err ( & pdev - > dev , " no irq resource? \n " ) ;
2013-07-22 15:02:00 +04:00
return irq ;
2013-02-15 10:56:00 +04:00
}
info - > irq = irq ;
2014-07-22 06:04:00 +04:00
info - > dev = & pdev - > dev ;
2013-02-15 10:56:00 +04:00
init_completion ( & info - > completion ) ;
info - > clk = devm_clk_get ( & pdev - > dev , " adc " ) ;
if ( IS_ERR ( info - > clk ) ) {
dev_err ( & pdev - > dev , " failed getting clock, err = %ld \n " ,
PTR_ERR ( info - > clk ) ) ;
2014-04-30 13:26:00 +04:00
return PTR_ERR ( info - > clk ) ;
2013-02-15 10:56:00 +04:00
}
2014-07-22 06:04:00 +04:00
if ( info - > data - > needs_sclk ) {
info - > sclk = devm_clk_get ( & pdev - > dev , " sclk " ) ;
if ( IS_ERR ( info - > sclk ) ) {
dev_err ( & pdev - > dev ,
" failed getting sclk clock, err = %ld \n " ,
PTR_ERR ( info - > sclk ) ) ;
return PTR_ERR ( info - > sclk ) ;
}
}
2013-02-15 10:56:00 +04:00
info - > vdd = devm_regulator_get ( & pdev - > dev , " vdd " ) ;
if ( IS_ERR ( info - > vdd ) ) {
dev_err ( & pdev - > dev , " failed getting regulator, err = %ld \n " ,
PTR_ERR ( info - > vdd ) ) ;
2014-04-30 13:26:00 +04:00
return PTR_ERR ( info - > vdd ) ;
2013-02-15 10:56:00 +04:00
}
2014-04-30 13:26:00 +04:00
ret = regulator_enable ( info - > vdd ) ;
if ( ret )
return ret ;
2014-07-22 06:04:00 +04:00
ret = exynos_adc_prepare_clk ( info ) ;
2014-04-30 13:26:00 +04:00
if ( ret )
goto err_disable_reg ;
2014-07-22 06:04:00 +04:00
ret = exynos_adc_enable_clk ( info ) ;
if ( ret )
goto err_unprepare_clk ;
2013-02-15 10:56:00 +04:00
platform_set_drvdata ( pdev , indio_dev ) ;
indio_dev - > name = dev_name ( & pdev - > dev ) ;
indio_dev - > dev . parent = & pdev - > dev ;
indio_dev - > dev . of_node = pdev - > dev . of_node ;
indio_dev - > info = & exynos_adc_iio_info ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
indio_dev - > channels = exynos_adc_iio_channels ;
2014-07-22 06:04:00 +04:00
indio_dev - > num_channels = info - > data - > num_channels ;
2013-02-15 10:56:00 +04:00
2014-04-30 13:26:00 +04:00
ret = request_irq ( info - > irq , exynos_adc_isr ,
0 , dev_name ( & pdev - > dev ) , info ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed requesting irq, irq = %d \n " ,
info - > irq ) ;
goto err_disable_clk ;
}
2013-02-15 10:56:00 +04:00
ret = iio_device_register ( indio_dev ) ;
if ( ret )
goto err_irq ;
2014-07-22 06:04:00 +04:00
if ( info - > data - > init_hw )
info - > data - > init_hw ( info ) ;
2013-02-15 10:56:00 +04:00
2014-04-25 14:14:00 +04:00
ret = of_platform_populate ( np , exynos_adc_match , NULL , & indio_dev - > dev ) ;
2013-02-15 10:56:00 +04:00
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed adding child nodes \n " ) ;
goto err_of_populate ;
}
return 0 ;
err_of_populate :
2014-04-25 14:14:00 +04:00
device_for_each_child ( & indio_dev - > dev , NULL ,
2013-02-15 10:56:00 +04:00
exynos_adc_remove_devices ) ;
iio_device_unregister ( indio_dev ) ;
err_irq :
free_irq ( info - > irq , info ) ;
2014-04-30 13:26:00 +04:00
err_disable_clk :
2014-07-22 06:04:00 +04:00
if ( info - > data - > exit_hw )
info - > data - > exit_hw ( info ) ;
2014-07-22 06:04:00 +04:00
exynos_adc_disable_clk ( info ) ;
err_unprepare_clk :
exynos_adc_unprepare_clk ( info ) ;
2014-04-30 13:26:00 +04:00
err_disable_reg :
regulator_disable ( info - > vdd ) ;
2013-02-15 10:56:00 +04:00
return ret ;
}
static int exynos_adc_remove ( struct platform_device * pdev )
{
struct iio_dev * indio_dev = platform_get_drvdata ( pdev ) ;
struct exynos_adc * info = iio_priv ( indio_dev ) ;
2014-04-25 14:14:00 +04:00
device_for_each_child ( & indio_dev - > dev , NULL ,
2013-02-15 10:56:00 +04:00
exynos_adc_remove_devices ) ;
iio_device_unregister ( indio_dev ) ;
free_irq ( info - > irq , info ) ;
2014-07-22 06:04:00 +04:00
if ( info - > data - > exit_hw )
info - > data - > exit_hw ( info ) ;
2014-07-22 06:04:00 +04:00
exynos_adc_disable_clk ( info ) ;
exynos_adc_unprepare_clk ( info ) ;
2014-04-30 13:26:00 +04:00
regulator_disable ( info - > vdd ) ;
2013-02-15 10:56:00 +04:00
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
static int exynos_adc_suspend ( struct device * dev )
{
2013-05-20 10:34:00 +04:00
struct iio_dev * indio_dev = dev_get_drvdata ( dev ) ;
struct exynos_adc * info = iio_priv ( indio_dev ) ;
2013-02-15 10:56:00 +04:00
2014-07-22 06:04:00 +04:00
if ( info - > data - > exit_hw )
info - > data - > exit_hw ( info ) ;
2014-07-22 06:04:00 +04:00
exynos_adc_disable_clk ( info ) ;
2013-02-15 10:56:00 +04:00
regulator_disable ( info - > vdd ) ;
return 0 ;
}
static int exynos_adc_resume ( struct device * dev )
{
2013-05-20 10:34:00 +04:00
struct iio_dev * indio_dev = dev_get_drvdata ( dev ) ;
struct exynos_adc * info = iio_priv ( indio_dev ) ;
2013-02-15 10:56:00 +04:00
int ret ;
ret = regulator_enable ( info - > vdd ) ;
if ( ret )
return ret ;
2014-07-22 06:04:00 +04:00
ret = exynos_adc_enable_clk ( info ) ;
2014-04-30 13:26:00 +04:00
if ( ret )
return ret ;
2013-02-15 10:56:00 +04:00
2014-07-22 06:04:00 +04:00
if ( info - > data - > init_hw )
info - > data - > init_hw ( info ) ;
2013-02-15 10:56:00 +04:00
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( exynos_adc_pm_ops ,
exynos_adc_suspend ,
exynos_adc_resume ) ;
static struct platform_driver exynos_adc_driver = {
. probe = exynos_adc_probe ,
. remove = exynos_adc_remove ,
. driver = {
. name = " exynos-adc " ,
2013-03-26 13:42:00 +04:00
. of_match_table = exynos_adc_match ,
2013-02-15 10:56:00 +04:00
. pm = & exynos_adc_pm_ops ,
} ,
} ;
module_platform_driver ( exynos_adc_driver ) ;
MODULE_AUTHOR ( " Naveen Krishna Chatradhi <ch.naveen@samsung.com> " ) ;
MODULE_DESCRIPTION ( " Samsung EXYNOS5 ADC driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;