2016-03-05 18:34:56 +03:00
/*
* drivers / hwmon / nsa320 - hwmon . c
*
* ZyXEL NSA320 Media Servers
* hardware monitoring
*
* Copyright ( C ) 2016 Adam Baker < linux @ baker - net . org . uk >
* based on a board file driver
* Copyright ( C ) 2012 Peter Schildmann < linux @ schildmann . info >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License v2 as published by the
* Free Software Foundation .
*
* 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 .
*/
# include <linux/bitops.h>
# include <linux/delay.h>
# include <linux/err.h>
# include <linux/gpio/consumer.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/jiffies.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/of_platform.h>
# include <linux/platform_device.h>
/* Tests for error return values rely upon this value being < 0x80 */
# define MAGIC_NUMBER 0x55
/*
* The Zyxel hwmon MCU is a Holtek HT46R065 that is factory programmed
* to perform temperature and fan speed monitoring . It is read by taking
* the active pin low . The 32 bit output word is then clocked onto the
* data line . The MSB of the data word is a magic nuber to indicate it
* has been read correctly , the next byte is the fan speed ( in hundreds
* of RPM ) and the last two bytes are the temperature ( in tenths of a
* degree )
*/
struct nsa320_hwmon {
struct mutex update_lock ; /* lock GPIO operations */
unsigned long last_updated ; /* jiffies */
unsigned long mcu_data ;
struct gpio_desc * act ;
struct gpio_desc * clk ;
struct gpio_desc * data ;
} ;
enum nsa320_inputs {
NSA320_TEMP = 0 ,
NSA320_FAN = 1 ,
} ;
static const char * const nsa320_input_names [ ] = {
[ NSA320_TEMP ] = " System Temperature " ,
[ NSA320_FAN ] = " Chassis Fan " ,
} ;
/*
* Although this protocol looks similar to SPI the long delay
* between the active ( aka chip select ) signal and the shorter
* delay between clock pulses are needed for reliable operation .
* The delays provided are taken from the manufacturer kernel ,
* testing suggest they probably incorporate a reasonable safety
* margin . ( The single device tested became unreliable if the
* delay was reduced to 1 / 10 th of this value . )
*/
static s32 nsa320_hwmon_update ( struct device * dev )
{
u32 mcu_data ;
u32 mask ;
struct nsa320_hwmon * hwmon = dev_get_drvdata ( dev ) ;
mutex_lock ( & hwmon - > update_lock ) ;
mcu_data = hwmon - > mcu_data ;
if ( time_after ( jiffies , hwmon - > last_updated + HZ ) | | mcu_data = = 0 ) {
gpiod_set_value ( hwmon - > act , 1 ) ;
msleep ( 100 ) ;
mcu_data = 0 ;
for ( mask = BIT ( 31 ) ; mask ; mask > > = 1 ) {
gpiod_set_value ( hwmon - > clk , 0 ) ;
usleep_range ( 100 , 200 ) ;
gpiod_set_value ( hwmon - > clk , 1 ) ;
usleep_range ( 100 , 200 ) ;
if ( gpiod_get_value ( hwmon - > data ) )
mcu_data | = mask ;
}
gpiod_set_value ( hwmon - > act , 0 ) ;
dev_dbg ( dev , " Read raw MCU data %08x \n " , mcu_data ) ;
if ( ( mcu_data > > 24 ) ! = MAGIC_NUMBER ) {
dev_dbg ( dev , " Read invalid MCU data %08x \n " , mcu_data ) ;
mcu_data = - EIO ;
} else {
hwmon - > mcu_data = mcu_data ;
hwmon - > last_updated = jiffies ;
}
}
mutex_unlock ( & hwmon - > update_lock ) ;
return mcu_data ;
}
static ssize_t show_label ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
int channel = to_sensor_dev_attr ( attr ) - > index ;
return sprintf ( buf , " %s \n " , nsa320_input_names [ channel ] ) ;
}
2016-12-22 15:05:02 +03:00
static ssize_t temp1_input_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
2016-03-05 18:34:56 +03:00
{
s32 mcu_data = nsa320_hwmon_update ( dev ) ;
if ( mcu_data < 0 )
return mcu_data ;
return sprintf ( buf , " %d \n " , ( mcu_data & 0xffff ) * 100 ) ;
}
2016-12-22 15:05:02 +03:00
static ssize_t fan1_input_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
2016-03-05 18:34:56 +03:00
{
s32 mcu_data = nsa320_hwmon_update ( dev ) ;
if ( mcu_data < 0 )
return mcu_data ;
return sprintf ( buf , " %d \n " , ( ( mcu_data & 0xff0000 ) > > 16 ) * 100 ) ;
}
static SENSOR_DEVICE_ATTR ( temp1_label , S_IRUGO , show_label , NULL , NSA320_TEMP ) ;
2016-12-22 15:05:02 +03:00
static DEVICE_ATTR_RO ( temp1_input ) ;
2016-03-05 18:34:56 +03:00
static SENSOR_DEVICE_ATTR ( fan1_label , S_IRUGO , show_label , NULL , NSA320_FAN ) ;
2016-12-22 15:05:02 +03:00
static DEVICE_ATTR_RO ( fan1_input ) ;
2016-03-05 18:34:56 +03:00
static struct attribute * nsa320_attrs [ ] = {
& sensor_dev_attr_temp1_label . dev_attr . attr ,
& dev_attr_temp1_input . attr ,
& sensor_dev_attr_fan1_label . dev_attr . attr ,
& dev_attr_fan1_input . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( nsa320 ) ;
static const struct of_device_id of_nsa320_hwmon_match [ ] = {
{ . compatible = " zyxel,nsa320-mcu " , } ,
{ } ,
} ;
static int nsa320_hwmon_probe ( struct platform_device * pdev )
{
struct nsa320_hwmon * hwmon ;
struct device * classdev ;
hwmon = devm_kzalloc ( & pdev - > dev , sizeof ( * hwmon ) , GFP_KERNEL ) ;
if ( ! hwmon )
return - ENOMEM ;
/* Look up the GPIO pins to use */
hwmon - > act = devm_gpiod_get ( & pdev - > dev , " act " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( hwmon - > act ) )
return PTR_ERR ( hwmon - > act ) ;
hwmon - > clk = devm_gpiod_get ( & pdev - > dev , " clk " , GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( hwmon - > clk ) )
return PTR_ERR ( hwmon - > clk ) ;
hwmon - > data = devm_gpiod_get ( & pdev - > dev , " data " , GPIOD_IN ) ;
if ( IS_ERR ( hwmon - > data ) )
return PTR_ERR ( hwmon - > data ) ;
mutex_init ( & hwmon - > update_lock ) ;
classdev = devm_hwmon_device_register_with_groups ( & pdev - > dev ,
" nsa320 " , hwmon , nsa320_groups ) ;
return PTR_ERR_OR_ZERO ( classdev ) ;
}
/* All allocations use devres so remove() is not needed. */
static struct platform_driver nsa320_hwmon_driver = {
. probe = nsa320_hwmon_probe ,
. driver = {
. name = " nsa320-hwmon " ,
. of_match_table = of_match_ptr ( of_nsa320_hwmon_match ) ,
} ,
} ;
module_platform_driver ( nsa320_hwmon_driver ) ;
MODULE_DEVICE_TABLE ( of , of_nsa320_hwmon_match ) ;
MODULE_AUTHOR ( " Peter Schildmann <linux@schildmann.info> " ) ;
MODULE_AUTHOR ( " Adam Baker <linux@baker-net.org.uk> " ) ;
MODULE_DESCRIPTION ( " NSA320 Hardware Monitoring " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:nsa320-hwmon " ) ;