2018-10-28 17:24:01 +01:00
// SPDX-License-Identifier: GPL-2.0
/* ti-dac7311.c - Texas Instruments 8/10/12-bit 1-channel DAC driver
*
* Copyright ( C ) 2018 CMC NV
*
2020-07-04 21:27:43 +02:00
* https : //www.ti.com/lit/ds/symlink/dac7311.pdf
2018-10-28 17:24:01 +01:00
*/
# include <linux/iio/iio.h>
# include <linux/module.h>
# include <linux/regulator/consumer.h>
# include <linux/spi/spi.h>
enum {
ID_DAC5311 = 0 ,
ID_DAC6311 ,
ID_DAC7311 ,
} ;
enum {
POWER_1KOHM_TO_GND = 0 ,
POWER_100KOHM_TO_GND ,
POWER_TRI_STATE ,
} ;
struct ti_dac_spec {
u8 resolution ;
} ;
static const struct ti_dac_spec ti_dac_spec [ ] = {
[ ID_DAC5311 ] = { . resolution = 8 } ,
[ ID_DAC6311 ] = { . resolution = 10 } ,
[ ID_DAC7311 ] = { . resolution = 12 } ,
} ;
/**
* struct ti_dac_chip - TI DAC chip
* @ lock : protects write sequences
* @ vref : regulator generating Vref
* @ spi : SPI device to send data to the device
* @ val : cached value
* @ powerdown : whether the chip is powered down
* @ powerdown_mode : selected by the user
* @ resolution : resolution of the chip
* @ buf : buffer for transfer data
*/
struct ti_dac_chip {
struct mutex lock ;
struct regulator * vref ;
struct spi_device * spi ;
u16 val ;
bool powerdown ;
u8 powerdown_mode ;
u8 resolution ;
2022-05-08 18:56:43 +01:00
u8 buf [ 2 ] __aligned ( IIO_DMA_MINALIGN ) ;
2018-10-28 17:24:01 +01:00
} ;
static u8 ti_dac_get_power ( struct ti_dac_chip * ti_dac , bool powerdown )
{
if ( powerdown )
return ti_dac - > powerdown_mode + 1 ;
return 0 ;
}
static int ti_dac_cmd ( struct ti_dac_chip * ti_dac , u8 power , u16 val )
{
u8 shift = 14 - ti_dac - > resolution ;
ti_dac - > buf [ 0 ] = ( val < < shift ) & 0xFF ;
ti_dac - > buf [ 1 ] = ( power < < 6 ) | ( val > > ( 8 - shift ) ) ;
return spi_write ( ti_dac - > spi , ti_dac - > buf , 2 ) ;
}
static const char * const ti_dac_powerdown_modes [ ] = {
" 1kohm_to_gnd " ,
" 100kohm_to_gnd " ,
" three_state " ,
} ;
static int ti_dac_get_powerdown_mode ( struct iio_dev * indio_dev ,
const struct iio_chan_spec * chan )
{
struct ti_dac_chip * ti_dac = iio_priv ( indio_dev ) ;
return ti_dac - > powerdown_mode ;
}
static int ti_dac_set_powerdown_mode ( struct iio_dev * indio_dev ,
const struct iio_chan_spec * chan ,
unsigned int mode )
{
struct ti_dac_chip * ti_dac = iio_priv ( indio_dev ) ;
ti_dac - > powerdown_mode = mode ;
return 0 ;
}
static const struct iio_enum ti_dac_powerdown_mode = {
. items = ti_dac_powerdown_modes ,
. num_items = ARRAY_SIZE ( ti_dac_powerdown_modes ) ,
. get = ti_dac_get_powerdown_mode ,
. set = ti_dac_set_powerdown_mode ,
} ;
static ssize_t ti_dac_read_powerdown ( struct iio_dev * indio_dev ,
uintptr_t private ,
const struct iio_chan_spec * chan ,
char * buf )
{
struct ti_dac_chip * ti_dac = iio_priv ( indio_dev ) ;
2021-03-20 08:14:05 +01:00
return sysfs_emit ( buf , " %d \n " , ti_dac - > powerdown ) ;
2018-10-28 17:24:01 +01:00
}
static ssize_t ti_dac_write_powerdown ( struct iio_dev * indio_dev ,
uintptr_t private ,
const struct iio_chan_spec * chan ,
const char * buf , size_t len )
{
struct ti_dac_chip * ti_dac = iio_priv ( indio_dev ) ;
bool powerdown ;
u8 power ;
int ret ;
2022-04-09 12:58:12 +02:00
ret = kstrtobool ( buf , & powerdown ) ;
2018-10-28 17:24:01 +01:00
if ( ret )
return ret ;
power = ti_dac_get_power ( ti_dac , powerdown ) ;
mutex_lock ( & ti_dac - > lock ) ;
ret = ti_dac_cmd ( ti_dac , power , 0 ) ;
if ( ! ret )
ti_dac - > powerdown = powerdown ;
mutex_unlock ( & ti_dac - > lock ) ;
return ret ? ret : len ;
}
static const struct iio_chan_spec_ext_info ti_dac_ext_info [ ] = {
{
. name = " powerdown " ,
. read = ti_dac_read_powerdown ,
. write = ti_dac_write_powerdown ,
. shared = IIO_SHARED_BY_TYPE ,
} ,
IIO_ENUM ( " powerdown_mode " , IIO_SHARED_BY_TYPE , & ti_dac_powerdown_mode ) ,
2021-11-19 10:56:27 +02:00
IIO_ENUM_AVAILABLE ( " powerdown_mode " , IIO_SHARED_BY_TYPE , & ti_dac_powerdown_mode ) ,
2018-10-28 17:24:01 +01:00
{ } ,
} ;
# define TI_DAC_CHANNEL(chan) { \
. type = IIO_VOLTAGE , \
. channel = ( chan ) , \
. output = true , \
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) , \
. info_mask_shared_by_type = BIT ( IIO_CHAN_INFO_SCALE ) , \
. ext_info = ti_dac_ext_info , \
}
static const struct iio_chan_spec ti_dac_channels [ ] = {
TI_DAC_CHANNEL ( 0 ) ,
} ;
static int ti_dac_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int * val , int * val2 , long mask )
{
struct ti_dac_chip * ti_dac = iio_priv ( indio_dev ) ;
int ret ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
* val = ti_dac - > val ;
return IIO_VAL_INT ;
case IIO_CHAN_INFO_SCALE :
ret = regulator_get_voltage ( ti_dac - > vref ) ;
if ( ret < 0 )
return ret ;
* val = ret / 1000 ;
* val2 = ti_dac - > resolution ;
return IIO_VAL_FRACTIONAL_LOG2 ;
}
return - EINVAL ;
}
static int ti_dac_write_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int val , int val2 , long mask )
{
struct ti_dac_chip * ti_dac = iio_priv ( indio_dev ) ;
u8 power = ti_dac_get_power ( ti_dac , ti_dac - > powerdown ) ;
int ret ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
if ( ti_dac - > val = = val )
return 0 ;
if ( val > = ( 1 < < ti_dac - > resolution ) | | val < 0 )
return - EINVAL ;
if ( ti_dac - > powerdown )
return - EBUSY ;
mutex_lock ( & ti_dac - > lock ) ;
ret = ti_dac_cmd ( ti_dac , power , val ) ;
if ( ! ret )
ti_dac - > val = val ;
mutex_unlock ( & ti_dac - > lock ) ;
break ;
default :
ret = - EINVAL ;
}
return ret ;
}
static int ti_dac_write_raw_get_fmt ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan , long mask )
{
return IIO_VAL_INT ;
}
static const struct iio_info ti_dac_info = {
. read_raw = ti_dac_read_raw ,
. write_raw = ti_dac_write_raw ,
. write_raw_get_fmt = ti_dac_write_raw_get_fmt ,
} ;
static int ti_dac_probe ( struct spi_device * spi )
{
struct device * dev = & spi - > dev ;
const struct ti_dac_spec * spec ;
struct ti_dac_chip * ti_dac ;
struct iio_dev * indio_dev ;
int ret ;
indio_dev = devm_iio_device_alloc ( dev , sizeof ( * ti_dac ) ) ;
if ( ! indio_dev ) {
dev_err ( dev , " can not allocate iio device \n " ) ;
return - ENOMEM ;
}
spi - > mode = SPI_MODE_1 ;
spi - > bits_per_word = 16 ;
spi_setup ( spi ) ;
indio_dev - > info = & ti_dac_info ;
indio_dev - > name = spi_get_device_id ( spi ) - > name ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
indio_dev - > channels = ti_dac_channels ;
spi_set_drvdata ( spi , indio_dev ) ;
ti_dac = iio_priv ( indio_dev ) ;
ti_dac - > powerdown = false ;
ti_dac - > spi = spi ;
spec = & ti_dac_spec [ spi_get_device_id ( spi ) - > driver_data ] ;
indio_dev - > num_channels = 1 ;
ti_dac - > resolution = spec - > resolution ;
ti_dac - > vref = devm_regulator_get ( dev , " vref " ) ;
2021-09-28 09:39:01 +08:00
if ( IS_ERR ( ti_dac - > vref ) )
return dev_err_probe ( dev , PTR_ERR ( ti_dac - > vref ) ,
" error to get regulator \n " ) ;
2018-10-28 17:24:01 +01:00
ret = regulator_enable ( ti_dac - > vref ) ;
if ( ret < 0 ) {
dev_err ( dev , " can not enable regulator \n " ) ;
return ret ;
}
mutex_init ( & ti_dac - > lock ) ;
ret = iio_device_register ( indio_dev ) ;
if ( ret ) {
dev_err ( dev , " fail to register iio device: %d \n " , ret ) ;
goto err ;
}
return 0 ;
err :
mutex_destroy ( & ti_dac - > lock ) ;
regulator_disable ( ti_dac - > vref ) ;
return ret ;
}
2022-01-23 18:52:01 +01:00
static void ti_dac_remove ( struct spi_device * spi )
2018-10-28 17:24:01 +01:00
{
struct iio_dev * indio_dev = spi_get_drvdata ( spi ) ;
struct ti_dac_chip * ti_dac = iio_priv ( indio_dev ) ;
iio_device_unregister ( indio_dev ) ;
mutex_destroy ( & ti_dac - > lock ) ;
regulator_disable ( ti_dac - > vref ) ;
}
static const struct of_device_id ti_dac_of_id [ ] = {
{ . compatible = " ti,dac5311 " } ,
{ . compatible = " ti,dac6311 " } ,
{ . compatible = " ti,dac7311 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , ti_dac_of_id ) ;
static const struct spi_device_id ti_dac_spi_id [ ] = {
{ " dac5311 " , ID_DAC5311 } ,
{ " dac6311 " , ID_DAC6311 } ,
{ " dac7311 " , ID_DAC7311 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( spi , ti_dac_spi_id ) ;
static struct spi_driver ti_dac_driver = {
. driver = {
. name = " ti-dac7311 " ,
. of_match_table = ti_dac_of_id ,
} ,
. probe = ti_dac_probe ,
. remove = ti_dac_remove ,
. id_table = ti_dac_spi_id ,
} ;
module_spi_driver ( ti_dac_driver ) ;
MODULE_AUTHOR ( " Charles-Antoine Couret <charles-antoine.couret@essensium.com> " ) ;
MODULE_DESCRIPTION ( " Texas Instruments 8/10/12-bit 1-channel DAC driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;