2017-02-23 09:38:43 +03:00
/*
* 1 - Wire implementation for the ds2438 chip
*
* Copyright ( c ) 2017 Mariusz Bialonczyk < manio @ skyboo . net >
*
* This source code is licensed under the GNU General Public License ,
* Version 2. See the file COPYING for more details .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/device.h>
# include <linux/types.h>
# include <linux/delay.h>
2017-06-05 16:52:08 +03:00
# include <linux/w1.h>
# define W1_FAMILY_DS2438 0x26
2017-02-23 09:38:43 +03:00
# define W1_DS2438_RETRIES 3
/* Memory commands */
# define W1_DS2438_READ_SCRATCH 0xBE
# define W1_DS2438_WRITE_SCRATCH 0x4E
# define W1_DS2438_COPY_SCRATCH 0x48
# define W1_DS2438_RECALL_MEMORY 0xB8
/* Register commands */
# define W1_DS2438_CONVERT_TEMP 0x44
# define W1_DS2438_CONVERT_VOLTAGE 0xB4
# define DS2438_PAGE_SIZE 8
# define DS2438_ADC_INPUT_VAD 0
# define DS2438_ADC_INPUT_VDD 1
# define DS2438_MAX_CONVERSION_TIME 10 /* ms */
/* Page #0 definitions */
# define DS2438_STATUS_REG 0x00 /* Status/Configuration Register */
# define DS2438_STATUS_IAD (1 << 0) /* Current A/D Control Bit */
# define DS2438_STATUS_CA (1 << 1) /* Current Accumulator Configuration */
# define DS2438_STATUS_EE (1 << 2) /* Current Accumulator Shadow Selector bit */
# define DS2438_STATUS_AD (1 << 3) /* Voltage A/D Input Select Bit */
# define DS2438_STATUS_TB (1 << 4) /* Temperature Busy Flag */
# define DS2438_STATUS_NVB (1 << 5) /* Nonvolatile Memory Busy Flag */
# define DS2438_STATUS_ADB (1 << 6) /* A/D Converter Busy Flag */
# define DS2438_TEMP_LSB 0x01
# define DS2438_TEMP_MSB 0x02
# define DS2438_VOLTAGE_LSB 0x03
# define DS2438_VOLTAGE_MSB 0x04
# define DS2438_CURRENT_LSB 0x05
# define DS2438_CURRENT_MSB 0x06
# define DS2438_THRESHOLD 0x07
int w1_ds2438_get_page ( struct w1_slave * sl , int pageno , u8 * buf )
{
unsigned int retries = W1_DS2438_RETRIES ;
u8 w1_buf [ 2 ] ;
u8 crc ;
size_t count ;
while ( retries - - ) {
crc = 0 ;
if ( w1_reset_select_slave ( sl ) )
continue ;
w1_buf [ 0 ] = W1_DS2438_RECALL_MEMORY ;
w1_buf [ 1 ] = 0x00 ;
w1_write_block ( sl - > master , w1_buf , 2 ) ;
if ( w1_reset_select_slave ( sl ) )
continue ;
w1_buf [ 0 ] = W1_DS2438_READ_SCRATCH ;
w1_buf [ 1 ] = 0x00 ;
w1_write_block ( sl - > master , w1_buf , 2 ) ;
count = w1_read_block ( sl - > master , buf , DS2438_PAGE_SIZE + 1 ) ;
if ( count = = DS2438_PAGE_SIZE + 1 ) {
crc = w1_calc_crc8 ( buf , DS2438_PAGE_SIZE ) ;
/* check for correct CRC */
if ( ( u8 ) buf [ DS2438_PAGE_SIZE ] = = crc )
return 0 ;
}
}
return - 1 ;
}
int w1_ds2438_get_temperature ( struct w1_slave * sl , int16_t * temperature )
{
unsigned int retries = W1_DS2438_RETRIES ;
u8 w1_buf [ DS2438_PAGE_SIZE + 1 /*for CRC*/ ] ;
unsigned int tm = DS2438_MAX_CONVERSION_TIME ;
unsigned long sleep_rem ;
int ret ;
mutex_lock ( & sl - > master - > bus_mutex ) ;
while ( retries - - ) {
if ( w1_reset_select_slave ( sl ) )
continue ;
w1_write_8 ( sl - > master , W1_DS2438_CONVERT_TEMP ) ;
mutex_unlock ( & sl - > master - > bus_mutex ) ;
sleep_rem = msleep_interruptible ( tm ) ;
if ( sleep_rem ! = 0 ) {
ret = - 1 ;
goto post_unlock ;
}
if ( mutex_lock_interruptible ( & sl - > master - > bus_mutex ) ! = 0 ) {
ret = - 1 ;
goto post_unlock ;
}
break ;
}
if ( w1_ds2438_get_page ( sl , 0 , w1_buf ) = = 0 ) {
* temperature = ( ( ( int16_t ) w1_buf [ DS2438_TEMP_MSB ] ) < < 8 ) | ( ( uint16_t ) w1_buf [ DS2438_TEMP_LSB ] ) ;
ret = 0 ;
} else
ret = - 1 ;
mutex_unlock ( & sl - > master - > bus_mutex ) ;
post_unlock :
return ret ;
}
int w1_ds2438_change_config_bit ( struct w1_slave * sl , u8 mask , u8 value )
{
unsigned int retries = W1_DS2438_RETRIES ;
u8 w1_buf [ 3 ] ;
u8 status ;
int perform_write = 0 ;
while ( retries - - ) {
if ( w1_reset_select_slave ( sl ) )
continue ;
w1_buf [ 0 ] = W1_DS2438_RECALL_MEMORY ;
w1_buf [ 1 ] = 0x00 ;
w1_write_block ( sl - > master , w1_buf , 2 ) ;
if ( w1_reset_select_slave ( sl ) )
continue ;
w1_buf [ 0 ] = W1_DS2438_READ_SCRATCH ;
w1_buf [ 1 ] = 0x00 ;
w1_write_block ( sl - > master , w1_buf , 2 ) ;
/* reading one byte of result */
status = w1_read_8 ( sl - > master ) ;
/* if bit0=1, set a value to a mask for easy compare */
if ( value )
value = mask ;
if ( ( status & mask ) = = value )
return 0 ; /* already set as requested */
else {
/* changing bit */
status ^ = mask ;
perform_write = 1 ;
}
break ;
}
if ( perform_write ) {
retries = W1_DS2438_RETRIES ;
while ( retries - - ) {
if ( w1_reset_select_slave ( sl ) )
continue ;
w1_buf [ 0 ] = W1_DS2438_WRITE_SCRATCH ;
w1_buf [ 1 ] = 0x00 ;
w1_buf [ 2 ] = status ;
w1_write_block ( sl - > master , w1_buf , 3 ) ;
if ( w1_reset_select_slave ( sl ) )
continue ;
w1_buf [ 0 ] = W1_DS2438_COPY_SCRATCH ;
w1_buf [ 1 ] = 0x00 ;
w1_write_block ( sl - > master , w1_buf , 2 ) ;
return 0 ;
}
}
return - 1 ;
}
uint16_t w1_ds2438_get_voltage ( struct w1_slave * sl , int adc_input , uint16_t * voltage )
{
unsigned int retries = W1_DS2438_RETRIES ;
u8 w1_buf [ DS2438_PAGE_SIZE + 1 /*for CRC*/ ] ;
unsigned int tm = DS2438_MAX_CONVERSION_TIME ;
unsigned long sleep_rem ;
int ret ;
mutex_lock ( & sl - > master - > bus_mutex ) ;
if ( w1_ds2438_change_config_bit ( sl , DS2438_STATUS_AD , adc_input ) ) {
ret = - 1 ;
goto pre_unlock ;
}
while ( retries - - ) {
if ( w1_reset_select_slave ( sl ) )
continue ;
w1_write_8 ( sl - > master , W1_DS2438_CONVERT_VOLTAGE ) ;
mutex_unlock ( & sl - > master - > bus_mutex ) ;
sleep_rem = msleep_interruptible ( tm ) ;
if ( sleep_rem ! = 0 ) {
ret = - 1 ;
goto post_unlock ;
}
if ( mutex_lock_interruptible ( & sl - > master - > bus_mutex ) ! = 0 ) {
ret = - 1 ;
goto post_unlock ;
}
break ;
}
if ( w1_ds2438_get_page ( sl , 0 , w1_buf ) = = 0 ) {
* voltage = ( ( ( uint16_t ) w1_buf [ DS2438_VOLTAGE_MSB ] ) < < 8 ) | ( ( uint16_t ) w1_buf [ DS2438_VOLTAGE_LSB ] ) ;
ret = 0 ;
} else
ret = - 1 ;
pre_unlock :
mutex_unlock ( & sl - > master - > bus_mutex ) ;
post_unlock :
return ret ;
}
static ssize_t iad_write ( struct file * filp , struct kobject * kobj ,
struct bin_attribute * bin_attr , char * buf ,
loff_t off , size_t count )
{
struct w1_slave * sl = kobj_to_w1_slave ( kobj ) ;
int ret ;
if ( count ! = 1 | | off ! = 0 )
return - EFAULT ;
mutex_lock ( & sl - > master - > bus_mutex ) ;
if ( w1_ds2438_change_config_bit ( sl , DS2438_STATUS_IAD , * buf & 0x01 ) = = 0 )
ret = 1 ;
else
ret = - EIO ;
mutex_unlock ( & sl - > master - > bus_mutex ) ;
return ret ;
}
static ssize_t page0_read ( struct file * filp , struct kobject * kobj ,
struct bin_attribute * bin_attr , char * buf ,
loff_t off , size_t count )
{
struct w1_slave * sl = kobj_to_w1_slave ( kobj ) ;
int ret ;
u8 w1_buf [ DS2438_PAGE_SIZE + 1 /*for CRC*/ ] ;
if ( off ! = 0 )
return 0 ;
if ( ! buf )
return - EINVAL ;
mutex_lock ( & sl - > master - > bus_mutex ) ;
if ( w1_ds2438_get_page ( sl , 0 , w1_buf ) = = 0 ) {
memcpy ( buf , & w1_buf , DS2438_PAGE_SIZE ) ;
ret = DS2438_PAGE_SIZE ;
} else
ret = - EIO ;
mutex_unlock ( & sl - > master - > bus_mutex ) ;
return ret ;
}
static ssize_t temperature_read ( struct file * filp , struct kobject * kobj ,
struct bin_attribute * bin_attr , char * buf ,
loff_t off , size_t count )
{
struct w1_slave * sl = kobj_to_w1_slave ( kobj ) ;
int ret ;
ssize_t c = PAGE_SIZE ;
int16_t temp ;
if ( off ! = 0 )
return 0 ;
if ( ! buf )
return - EINVAL ;
if ( w1_ds2438_get_temperature ( sl , & temp ) = = 0 ) {
c - = snprintf ( buf + PAGE_SIZE - c , c , " %d \n " , temp ) ;
ret = PAGE_SIZE - c ;
} else
ret = - EIO ;
return ret ;
}
static ssize_t vad_read ( struct file * filp , struct kobject * kobj ,
struct bin_attribute * bin_attr , char * buf ,
loff_t off , size_t count )
{
struct w1_slave * sl = kobj_to_w1_slave ( kobj ) ;
int ret ;
ssize_t c = PAGE_SIZE ;
uint16_t voltage ;
if ( off ! = 0 )
return 0 ;
if ( ! buf )
return - EINVAL ;
if ( w1_ds2438_get_voltage ( sl , DS2438_ADC_INPUT_VAD , & voltage ) = = 0 ) {
c - = snprintf ( buf + PAGE_SIZE - c , c , " %d \n " , voltage ) ;
ret = PAGE_SIZE - c ;
} else
ret = - EIO ;
return ret ;
}
static ssize_t vdd_read ( struct file * filp , struct kobject * kobj ,
struct bin_attribute * bin_attr , char * buf ,
loff_t off , size_t count )
{
struct w1_slave * sl = kobj_to_w1_slave ( kobj ) ;
int ret ;
ssize_t c = PAGE_SIZE ;
uint16_t voltage ;
if ( off ! = 0 )
return 0 ;
if ( ! buf )
return - EINVAL ;
if ( w1_ds2438_get_voltage ( sl , DS2438_ADC_INPUT_VDD , & voltage ) = = 0 ) {
c - = snprintf ( buf + PAGE_SIZE - c , c , " %d \n " , voltage ) ;
ret = PAGE_SIZE - c ;
} else
ret = - EIO ;
return ret ;
}
static BIN_ATTR ( iad , S_IRUGO | S_IWUSR | S_IWGRP , NULL , iad_write , 1 ) ;
static BIN_ATTR_RO ( page0 , DS2438_PAGE_SIZE ) ;
static BIN_ATTR_RO ( temperature , 0 /* real length varies */ ) ;
static BIN_ATTR_RO ( vad , 0 /* real length varies */ ) ;
static BIN_ATTR_RO ( vdd , 0 /* real length varies */ ) ;
static struct bin_attribute * w1_ds2438_bin_attrs [ ] = {
& bin_attr_iad ,
& bin_attr_page0 ,
& bin_attr_temperature ,
& bin_attr_vad ,
& bin_attr_vdd ,
NULL ,
} ;
static const struct attribute_group w1_ds2438_group = {
. bin_attrs = w1_ds2438_bin_attrs ,
} ;
static const struct attribute_group * w1_ds2438_groups [ ] = {
& w1_ds2438_group ,
NULL ,
} ;
static struct w1_family_ops w1_ds2438_fops = {
. groups = w1_ds2438_groups ,
} ;
static struct w1_family w1_ds2438_family = {
. fid = W1_FAMILY_DS2438 ,
. fops = & w1_ds2438_fops ,
} ;
module_w1_family ( w1_ds2438_family ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Mariusz Bialonczyk <manio@skyboo.net> " ) ;
MODULE_DESCRIPTION ( " 1-wire driver for Maxim/Dallas DS2438 Smart Battery Monitor " ) ;
MODULE_ALIAS ( " w1-family- " __stringify ( W1_FAMILY_DS2438 ) ) ;