443 lines
15 KiB
C
443 lines
15 KiB
C
|
/*
|
||
|
* A hwmon driver for the Accton as5812 54t fan
|
||
|
*
|
||
|
* Copyright (C) 2015 Accton Technology Corporation.
|
||
|
* Brandon Chuang <brandon_chuang@accton.com.tw>
|
||
|
*
|
||
|
* 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 02139, USA.
|
||
|
*/
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/jiffies.h>
|
||
|
#include <linux/hwmon.h>
|
||
|
#include <linux/hwmon-sysfs.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/sysfs.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/syscalls.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
|
||
|
#define FAN_MAX_NUMBER 5
|
||
|
#define FAN_SPEED_CPLD_TO_RPM_STEP 150
|
||
|
#define FAN_SPEED_PRECENT_TO_CPLD_STEP 5
|
||
|
#define FAN_DUTY_CYCLE_MIN 0
|
||
|
#define FAN_DUTY_CYCLE_MAX 100 /* 100% */
|
||
|
|
||
|
#define CPLD_REG_FAN_STATUS_OFFSET 0xC
|
||
|
#define CPLD_REG_FANR_STATUS_OFFSET 0x1F
|
||
|
#define CPLD_REG_FAN_DIRECTION_OFFSET 0x1E
|
||
|
|
||
|
#define CPLD_FAN1_REG_SPEED_OFFSET 0x10
|
||
|
#define CPLD_FAN2_REG_SPEED_OFFSET 0x11
|
||
|
#define CPLD_FAN3_REG_SPEED_OFFSET 0x12
|
||
|
#define CPLD_FAN4_REG_SPEED_OFFSET 0x13
|
||
|
#define CPLD_FAN5_REG_SPEED_OFFSET 0x14
|
||
|
|
||
|
#define CPLD_FANR1_REG_SPEED_OFFSET 0x18
|
||
|
#define CPLD_FANR2_REG_SPEED_OFFSET 0x19
|
||
|
#define CPLD_FANR3_REG_SPEED_OFFSET 0x1A
|
||
|
#define CPLD_FANR4_REG_SPEED_OFFSET 0x1B
|
||
|
#define CPLD_FANR5_REG_SPEED_OFFSET 0x1C
|
||
|
|
||
|
#define CPLD_REG_FAN_PWM_CYCLE_OFFSET 0xD
|
||
|
|
||
|
#define CPLD_FAN1_INFO_BIT_MASK 0x1
|
||
|
#define CPLD_FAN2_INFO_BIT_MASK 0x2
|
||
|
#define CPLD_FAN3_INFO_BIT_MASK 0x4
|
||
|
#define CPLD_FAN4_INFO_BIT_MASK 0x8
|
||
|
#define CPLD_FAN5_INFO_BIT_MASK 0x10
|
||
|
|
||
|
#define PROJECT_NAME
|
||
|
|
||
|
#define LOCAL_DEBUG 0
|
||
|
|
||
|
static struct accton_as5812_54t_fan *fan_data = NULL;
|
||
|
|
||
|
struct accton_as5812_54t_fan {
|
||
|
struct platform_device *pdev;
|
||
|
struct device *hwmon_dev;
|
||
|
struct mutex update_lock;
|
||
|
char valid; /* != 0 if registers are valid */
|
||
|
unsigned long last_updated; /* In jiffies */
|
||
|
u8 status[FAN_MAX_NUMBER]; /* inner first fan status */
|
||
|
u32 speed[FAN_MAX_NUMBER]; /* inner first fan speed */
|
||
|
u8 direction[FAN_MAX_NUMBER]; /* reconrd the direction of inner first and second fans */
|
||
|
u32 duty_cycle[FAN_MAX_NUMBER]; /* control the speed of inner first and second fans */
|
||
|
u8 r_status[FAN_MAX_NUMBER]; /* inner second fan status */
|
||
|
u32 r_speed[FAN_MAX_NUMBER]; /* inner second fan speed */
|
||
|
};
|
||
|
|
||
|
/*******************/
|
||
|
#define MAKE_FAN_MASK_OR_REG(name,type) \
|
||
|
CPLD_FAN##type##1_##name, \
|
||
|
CPLD_FAN##type##2_##name, \
|
||
|
CPLD_FAN##type##3_##name, \
|
||
|
CPLD_FAN##type##4_##name, \
|
||
|
CPLD_FAN##type##5_##name,
|
||
|
|
||
|
/* fan related data
|
||
|
*/
|
||
|
static const u8 fan_info_mask[] = {
|
||
|
MAKE_FAN_MASK_OR_REG(INFO_BIT_MASK,)
|
||
|
};
|
||
|
|
||
|
static const u8 fan_speed_reg[] = {
|
||
|
MAKE_FAN_MASK_OR_REG(REG_SPEED_OFFSET,)
|
||
|
};
|
||
|
|
||
|
static const u8 fanr_speed_reg[] = {
|
||
|
MAKE_FAN_MASK_OR_REG(REG_SPEED_OFFSET,R)
|
||
|
};
|
||
|
|
||
|
/*******************/
|
||
|
#define DEF_FAN_SET(id) \
|
||
|
FAN##id##_FAULT, \
|
||
|
FAN##id##_SPEED, \
|
||
|
FAN##id##_DUTY_CYCLE, \
|
||
|
FAN##id##_DIRECTION, \
|
||
|
FANR##id##_FAULT, \
|
||
|
FANR##id##_SPEED,
|
||
|
|
||
|
enum sysfs_fan_attributes {
|
||
|
DEF_FAN_SET(1)
|
||
|
DEF_FAN_SET(2)
|
||
|
DEF_FAN_SET(3)
|
||
|
DEF_FAN_SET(4)
|
||
|
DEF_FAN_SET(5)
|
||
|
};
|
||
|
/*******************/
|
||
|
static void accton_as5812_54t_fan_update_device(struct device *dev);
|
||
|
static int accton_as5812_54t_fan_read_value(u8 reg);
|
||
|
static int accton_as5812_54t_fan_write_value(u8 reg, u8 value);
|
||
|
|
||
|
static ssize_t fan_set_duty_cycle(struct device *dev,
|
||
|
struct device_attribute *da,const char *buf, size_t count);
|
||
|
static ssize_t fan_show_value(struct device *dev,
|
||
|
struct device_attribute *da, char *buf);
|
||
|
|
||
|
extern int accton_i2c_cpld_read(unsigned short cpld_addr, u8 reg);
|
||
|
extern int accton_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value);
|
||
|
|
||
|
|
||
|
/*******************/
|
||
|
#define _MAKE_SENSOR_DEVICE_ATTR(prj, id) \
|
||
|
static SENSOR_DEVICE_ATTR(prj##fan##id##_fault, S_IRUGO, fan_show_value, NULL, FAN##id##_FAULT); \
|
||
|
static SENSOR_DEVICE_ATTR(prj##fan##id##_speed_rpm, S_IRUGO, fan_show_value, NULL, FAN##id##_SPEED); \
|
||
|
static SENSOR_DEVICE_ATTR(prj##fan##id##_duty_cycle_percentage, S_IWUSR | S_IRUGO, fan_show_value, \
|
||
|
fan_set_duty_cycle, FAN##id##_DUTY_CYCLE); \
|
||
|
static SENSOR_DEVICE_ATTR(prj##fan##id##_direction, S_IRUGO, fan_show_value, NULL, FAN##id##_DIRECTION); \
|
||
|
static SENSOR_DEVICE_ATTR(prj##fanr##id##_fault, S_IRUGO, fan_show_value, NULL, FANR##id##_FAULT); \
|
||
|
static SENSOR_DEVICE_ATTR(prj##fanr##id##_speed_rpm, S_IRUGO, fan_show_value, NULL, FANR##id##_SPEED);
|
||
|
|
||
|
#define MAKE_SENSOR_DEVICE_ATTR(prj,id) _MAKE_SENSOR_DEVICE_ATTR(prj,id)
|
||
|
|
||
|
MAKE_SENSOR_DEVICE_ATTR(PROJECT_NAME, 1)
|
||
|
MAKE_SENSOR_DEVICE_ATTR(PROJECT_NAME, 2)
|
||
|
MAKE_SENSOR_DEVICE_ATTR(PROJECT_NAME, 3)
|
||
|
MAKE_SENSOR_DEVICE_ATTR(PROJECT_NAME, 4)
|
||
|
MAKE_SENSOR_DEVICE_ATTR(PROJECT_NAME, 5)
|
||
|
/*******************/
|
||
|
|
||
|
#define _MAKE_FAN_ATTR(prj, id) \
|
||
|
&sensor_dev_attr_##prj##fan##id##_fault.dev_attr.attr, \
|
||
|
&sensor_dev_attr_##prj##fan##id##_speed_rpm.dev_attr.attr, \
|
||
|
&sensor_dev_attr_##prj##fan##id##_duty_cycle_percentage.dev_attr.attr,\
|
||
|
&sensor_dev_attr_##prj##fan##id##_direction.dev_attr.attr, \
|
||
|
&sensor_dev_attr_##prj##fanr##id##_fault.dev_attr.attr, \
|
||
|
&sensor_dev_attr_##prj##fanr##id##_speed_rpm.dev_attr.attr,
|
||
|
|
||
|
#define MAKE_FAN_ATTR(prj, id) _MAKE_FAN_ATTR(prj, id)
|
||
|
|
||
|
static struct attribute *accton_as5812_54t_fan_attributes[] = {
|
||
|
/* fan related attributes */
|
||
|
MAKE_FAN_ATTR(PROJECT_NAME,1)
|
||
|
MAKE_FAN_ATTR(PROJECT_NAME,2)
|
||
|
MAKE_FAN_ATTR(PROJECT_NAME,3)
|
||
|
MAKE_FAN_ATTR(PROJECT_NAME,4)
|
||
|
MAKE_FAN_ATTR(PROJECT_NAME,5)
|
||
|
NULL
|
||
|
};
|
||
|
/*******************/
|
||
|
|
||
|
/* fan related functions
|
||
|
*/
|
||
|
static ssize_t fan_show_value(struct device *dev, struct device_attribute *da,
|
||
|
char *buf)
|
||
|
{
|
||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||
|
ssize_t ret = 0;
|
||
|
int data_index, type_index;
|
||
|
|
||
|
accton_as5812_54t_fan_update_device(dev);
|
||
|
|
||
|
if (fan_data->valid == 0) {
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
type_index = attr->index%FAN2_FAULT;
|
||
|
data_index = attr->index/FAN2_FAULT;
|
||
|
|
||
|
switch (type_index) {
|
||
|
case FAN1_FAULT:
|
||
|
ret = sprintf(buf, "%d\n", fan_data->status[data_index]);
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||
|
break;
|
||
|
case FAN1_SPEED:
|
||
|
ret = sprintf(buf, "%d\n", fan_data->speed[data_index]);
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||
|
break;
|
||
|
case FAN1_DUTY_CYCLE:
|
||
|
ret = sprintf(buf, "%d\n", fan_data->duty_cycle[data_index]);
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||
|
break;
|
||
|
case FAN1_DIRECTION:
|
||
|
ret = sprintf(buf, "%d\n", fan_data->direction[data_index]); /* presnet, need to modify*/
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||
|
break;
|
||
|
case FANR1_FAULT:
|
||
|
ret = sprintf(buf, "%d\n", fan_data->r_status[data_index]);
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||
|
break;
|
||
|
case FANR1_SPEED:
|
||
|
ret = sprintf(buf, "%d\n", fan_data->r_speed[data_index]);
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||
|
break;
|
||
|
default:
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[Check !!][%s][%d] \n", __FUNCTION__, __LINE__);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
/*******************/
|
||
|
static ssize_t fan_set_duty_cycle(struct device *dev, struct device_attribute *da,
|
||
|
const char *buf, size_t count) {
|
||
|
|
||
|
int error, value;
|
||
|
|
||
|
error = kstrtoint(buf, 10, &value);
|
||
|
if (error)
|
||
|
return error;
|
||
|
|
||
|
if (value < FAN_DUTY_CYCLE_MIN || value > FAN_DUTY_CYCLE_MAX)
|
||
|
return -EINVAL;
|
||
|
|
||
|
accton_as5812_54t_fan_write_value(CPLD_REG_FAN_PWM_CYCLE_OFFSET, value/FAN_SPEED_PRECENT_TO_CPLD_STEP);
|
||
|
|
||
|
fan_data->valid = 0;
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static const struct attribute_group accton_as5812_54t_fan_group = {
|
||
|
.attrs = accton_as5812_54t_fan_attributes,
|
||
|
};
|
||
|
|
||
|
static int accton_as5812_54t_fan_read_value(u8 reg)
|
||
|
{
|
||
|
return accton_i2c_cpld_read(0x60, reg);
|
||
|
}
|
||
|
|
||
|
static int accton_as5812_54t_fan_write_value(u8 reg, u8 value)
|
||
|
{
|
||
|
return accton_i2c_cpld_write(0x60, reg, value);
|
||
|
}
|
||
|
|
||
|
static void accton_as5812_54t_fan_update_device(struct device *dev)
|
||
|
{
|
||
|
int speed, r_speed, fault, r_fault, ctrl_speed, direction;
|
||
|
int i;
|
||
|
|
||
|
mutex_lock(&fan_data->update_lock);
|
||
|
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("Starting accton_as5812_54t_fan update \n");
|
||
|
|
||
|
if (!(time_after(jiffies, fan_data->last_updated + HZ + HZ / 2) || !fan_data->valid)) {
|
||
|
/* do nothing */
|
||
|
goto _exit;
|
||
|
}
|
||
|
|
||
|
fan_data->valid = 0;
|
||
|
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("Starting accton_as5812_54t_fan update 2 \n");
|
||
|
|
||
|
fault = accton_as5812_54t_fan_read_value(CPLD_REG_FAN_STATUS_OFFSET);
|
||
|
r_fault = accton_as5812_54t_fan_read_value(CPLD_REG_FANR_STATUS_OFFSET);
|
||
|
direction = accton_as5812_54t_fan_read_value(CPLD_REG_FAN_DIRECTION_OFFSET);
|
||
|
ctrl_speed = accton_as5812_54t_fan_read_value(CPLD_REG_FAN_PWM_CYCLE_OFFSET);
|
||
|
|
||
|
if ( (fault < 0) || (r_fault < 0) || (direction < 0) || (ctrl_speed < 0) )
|
||
|
{
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[Error!!][%s][%d] \n", __FUNCTION__, __LINE__);
|
||
|
goto _exit; /* error */
|
||
|
}
|
||
|
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[fan:] fault:%d, r_fault=%d, direction=%d, ctrl_speed=%d \n",fault, r_fault, direction, ctrl_speed);
|
||
|
|
||
|
for (i=0; i<FAN_MAX_NUMBER; i++)
|
||
|
{
|
||
|
/* Update fan data
|
||
|
*/
|
||
|
|
||
|
/* fan fault
|
||
|
* 0: normal, 1:abnormal
|
||
|
* Each FAN-tray module has two fans.
|
||
|
*/
|
||
|
fan_data->status[i] = (fault & fan_info_mask[i]) >> i;
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[fan%d:] fail=%d \n",i, fan_data->status[i]);
|
||
|
|
||
|
fan_data->r_status[i] = (r_fault & fan_info_mask[i]) >> i;
|
||
|
fan_data->direction[i] = (direction & fan_info_mask[i]) >> i;
|
||
|
fan_data->duty_cycle[i] = ctrl_speed * FAN_SPEED_PRECENT_TO_CPLD_STEP;
|
||
|
|
||
|
/* fan speed
|
||
|
*/
|
||
|
speed = accton_as5812_54t_fan_read_value(fan_speed_reg[i]);
|
||
|
r_speed = accton_as5812_54t_fan_read_value(fanr_speed_reg[i]);
|
||
|
if ( (speed < 0) || (r_speed < 0) )
|
||
|
{
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[Error!!][%s][%d] \n", __FUNCTION__, __LINE__);
|
||
|
goto _exit; /* error */
|
||
|
}
|
||
|
|
||
|
if (LOCAL_DEBUG)
|
||
|
printk ("[fan%d:] speed:%d, r_speed=%d \n", i, speed, r_speed);
|
||
|
|
||
|
fan_data->speed[i] = speed * FAN_SPEED_CPLD_TO_RPM_STEP;
|
||
|
fan_data->r_speed[i] = r_speed * FAN_SPEED_CPLD_TO_RPM_STEP;
|
||
|
}
|
||
|
|
||
|
/* finish to update */
|
||
|
fan_data->last_updated = jiffies;
|
||
|
fan_data->valid = 1;
|
||
|
|
||
|
_exit:
|
||
|
mutex_unlock(&fan_data->update_lock);
|
||
|
}
|
||
|
|
||
|
static int accton_as5812_54t_fan_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
int status = -1;
|
||
|
|
||
|
/* Register sysfs hooks */
|
||
|
status = sysfs_create_group(&pdev->dev.kobj, &accton_as5812_54t_fan_group);
|
||
|
if (status) {
|
||
|
goto exit;
|
||
|
|
||
|
}
|
||
|
|
||
|
fan_data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||
|
if (IS_ERR(fan_data->hwmon_dev)) {
|
||
|
status = PTR_ERR(fan_data->hwmon_dev);
|
||
|
goto exit_remove;
|
||
|
}
|
||
|
|
||
|
dev_info(&pdev->dev, "accton_as5812_54t_fan\n");
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
exit_remove:
|
||
|
sysfs_remove_group(&pdev->dev.kobj, &accton_as5812_54t_fan_group);
|
||
|
exit:
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
static int accton_as5812_54t_fan_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
hwmon_device_unregister(fan_data->hwmon_dev);
|
||
|
sysfs_remove_group(&fan_data->pdev->dev.kobj, &accton_as5812_54t_fan_group);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#define DRVNAME "as5812_54t_fan"
|
||
|
|
||
|
static struct platform_driver accton_as5812_54t_fan_driver = {
|
||
|
.probe = accton_as5812_54t_fan_probe,
|
||
|
.remove = accton_as5812_54t_fan_remove,
|
||
|
.driver = {
|
||
|
.name = DRVNAME,
|
||
|
.owner = THIS_MODULE,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static int __init accton_as5812_54t_fan_init(void)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
extern int platform_accton_as5812_54t(void);
|
||
|
if (!platform_accton_as5812_54t()) {
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
ret = platform_driver_register(&accton_as5812_54t_fan_driver);
|
||
|
if (ret < 0) {
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
fan_data = kzalloc(sizeof(struct accton_as5812_54t_fan), GFP_KERNEL);
|
||
|
if (!fan_data) {
|
||
|
ret = -ENOMEM;
|
||
|
platform_driver_unregister(&accton_as5812_54t_fan_driver);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
mutex_init(&fan_data->update_lock);
|
||
|
fan_data->valid = 0;
|
||
|
|
||
|
fan_data->pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
|
||
|
if (IS_ERR(fan_data->pdev)) {
|
||
|
ret = PTR_ERR(fan_data->pdev);
|
||
|
platform_driver_unregister(&accton_as5812_54t_fan_driver);
|
||
|
kfree(fan_data);
|
||
|
goto exit;
|
||
|
}
|
||
|
|
||
|
exit:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void __exit accton_as5812_54t_fan_exit(void)
|
||
|
{
|
||
|
platform_device_unregister(fan_data->pdev);
|
||
|
platform_driver_unregister(&accton_as5812_54t_fan_driver);
|
||
|
kfree(fan_data);
|
||
|
}
|
||
|
|
||
|
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
|
||
|
MODULE_DESCRIPTION("accton_as5812_54t_fan driver");
|
||
|
MODULE_LICENSE("GPL");
|
||
|
|
||
|
module_init(accton_as5812_54t_fan_init);
|
||
|
module_exit(accton_as5812_54t_fan_exit);
|
||
|
|