AS5812-54T Kernel Modules.

This commit is contained in:
Jeffrey Townsend
2017-01-02 02:35:52 +00:00
parent fc7bd1dd77
commit 1d730ef504
8 changed files with 1664 additions and 0 deletions

View File

@@ -0,0 +1 @@
include $(ONL)/make/pkg.mk

View File

@@ -0,0 +1 @@
!include $ONL_TEMPLATES/platform-modules.yml PLATFORM=x86-64-accton-as5812-54t ARCH=amd64 KERNELS="onl-kernel-3.16-lts-x86-64-all:amd64"

View File

@@ -0,0 +1,5 @@
KERNELS := onl-kernel-3.16-lts-x86-64-all:amd64
KMODULES := $(wildcard *.c)
PLATFORM := x86-64-accton-as5812-54t
ARCH := x86_64
include $(ONL)/make/kmodule.mk

View File

@@ -0,0 +1,442 @@
/*
* 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);

View File

@@ -0,0 +1,601 @@
/*
* A LED driver for the accton_as5812_54t_led
*
* 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.
*/
/*#define DEBUG*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/leds.h>
#include <linux/slab.h>
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);
extern void led_classdev_unregister(struct led_classdev *led_cdev);
extern int led_classdev_register(struct device *parent, struct led_classdev *led_cdev);
extern void led_classdev_resume(struct led_classdev *led_cdev);
extern void led_classdev_suspend(struct led_classdev *led_cdev);
#define DRVNAME "as5812_54t_led"
struct accton_as5812_54t_led_data {
struct platform_device *pdev;
struct mutex update_lock;
char valid; /* != 0 if registers are valid */
unsigned long last_updated; /* In jiffies */
u8 reg_val[4]; /* Register value, 0 = LOC/DIAG/FAN LED
1 = PSU1/PSU2 LED
2 = FAN1-4 LED
3 = FAN5-6 LED */
};
static struct accton_as5812_54t_led_data *ledctl = NULL;
/* LED related data
*/
#define LED_TYPE_PSU1_REG_MASK 0x03
#define LED_MODE_PSU1_GREEN_MASK 0x02
#define LED_MODE_PSU1_AMBER_MASK 0x01
#define LED_MODE_PSU1_OFF_MASK 0x03
#define LED_MODE_PSU1_AUTO_MASK 0x00
#define LED_TYPE_PSU2_REG_MASK 0x0C
#define LED_MODE_PSU2_GREEN_MASK 0x08
#define LED_MODE_PSU2_AMBER_MASK 0x04
#define LED_MODE_PSU2_OFF_MASK 0x0C
#define LED_MODE_PSU2_AUTO_MASK 0x00
#define LED_TYPE_DIAG_REG_MASK 0x0C
#define LED_MODE_DIAG_GREEN_MASK 0x08
#define LED_MODE_DIAG_AMBER_MASK 0x04
#define LED_MODE_DIAG_OFF_MASK 0x0C
#define LED_TYPE_FAN_REG_MASK 0x03
#define LED_MODE_FAN_GREEN_MASK 0x02
#define LED_MODE_FAN_AMBER_MASK 0x01
#define LED_MODE_FAN_OFF_MASK 0x03
#define LED_MODE_FAN_AUTO_MASK 0x00
#define LED_TYPE_FAN1_REG_MASK 0x03
#define LED_TYPE_FAN2_REG_MASK 0x0C
#define LED_TYPE_FAN3_REG_MASK 0x30
#define LED_TYPE_FAN4_REG_MASK 0xC0
#define LED_TYPE_FAN5_REG_MASK 0x03
#define LED_TYPE_FAN6_REG_MASK 0x0C
#define LED_MODE_FANX_GREEN_MASK 0x01
#define LED_MODE_FANX_RED_MASK 0x02
#define LED_MODE_FANX_OFF_MASK 0x00
#define LED_TYPE_LOC_REG_MASK 0x30
#define LED_MODE_LOC_ON_MASK 0x00
#define LED_MODE_LOC_OFF_MASK 0x10
#define LED_MODE_LOC_BLINK_MASK 0x20
static const u8 led_reg[] = {
0xA, /* LOC/DIAG/FAN LED*/
0xB, /* PSU1/PSU2 LED */
0x16, /* FAN1-4 LED */
0x17, /* FAN4-6 LED */
};
enum led_type {
LED_TYPE_PSU1,
LED_TYPE_PSU2,
LED_TYPE_DIAG,
LED_TYPE_FAN,
LED_TYPE_FAN1,
LED_TYPE_FAN2,
LED_TYPE_FAN3,
LED_TYPE_FAN4,
LED_TYPE_FAN5,
LED_TYPE_LOC
};
enum led_light_mode {
LED_MODE_OFF = 0,
LED_MODE_GREEN,
LED_MODE_GREEN_BLINK,
LED_MODE_AMBER,
LED_MODE_AMBER_BLINK,
LED_MODE_RED,
LED_MODE_RED_BLINK,
LED_MODE_BLUE,
LED_MODE_BLUE_BLINK,
LED_MODE_AUTO,
LED_MODE_UNKNOWN
};
struct led_type_mode {
enum led_type type;
int type_mask;
enum led_light_mode mode;
int mode_mask;
};
static struct led_type_mode led_type_mode_data[] = {
{LED_TYPE_PSU1, LED_TYPE_PSU1_REG_MASK, LED_MODE_GREEN, LED_MODE_PSU1_GREEN_MASK},
{LED_TYPE_PSU1, LED_TYPE_PSU1_REG_MASK, LED_MODE_AMBER, LED_MODE_PSU1_AMBER_MASK},
{LED_TYPE_PSU1, LED_TYPE_PSU1_REG_MASK, LED_MODE_AUTO, LED_MODE_PSU1_AUTO_MASK},
{LED_TYPE_PSU1, LED_TYPE_PSU1_REG_MASK, LED_MODE_OFF, LED_MODE_PSU1_OFF_MASK},
{LED_TYPE_PSU2, LED_TYPE_PSU2_REG_MASK, LED_MODE_GREEN, LED_MODE_PSU2_GREEN_MASK},
{LED_TYPE_PSU2, LED_TYPE_PSU2_REG_MASK, LED_MODE_AMBER, LED_MODE_PSU2_AMBER_MASK},
{LED_TYPE_PSU2, LED_TYPE_PSU2_REG_MASK, LED_MODE_AUTO, LED_MODE_PSU2_AUTO_MASK},
{LED_TYPE_PSU2, LED_TYPE_PSU2_REG_MASK, LED_MODE_OFF, LED_MODE_PSU2_OFF_MASK},
{LED_TYPE_FAN, LED_TYPE_FAN_REG_MASK, LED_MODE_GREEN, LED_MODE_FAN_GREEN_MASK},
{LED_TYPE_FAN, LED_TYPE_FAN_REG_MASK, LED_MODE_AMBER, LED_MODE_FAN_AMBER_MASK},
{LED_TYPE_FAN, LED_TYPE_FAN_REG_MASK, LED_MODE_AUTO, LED_MODE_FAN_AUTO_MASK},
{LED_TYPE_FAN, LED_TYPE_FAN_REG_MASK, LED_MODE_OFF, LED_MODE_FAN_OFF_MASK},
{LED_TYPE_FAN1, LED_TYPE_FAN1_REG_MASK, LED_MODE_GREEN, LED_MODE_FANX_GREEN_MASK << 0},
{LED_TYPE_FAN1, LED_TYPE_FAN1_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 0},
{LED_TYPE_FAN1, LED_TYPE_FAN1_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 0},
{LED_TYPE_FAN2, LED_TYPE_FAN2_REG_MASK, LED_MODE_GREEN, LED_MODE_FANX_GREEN_MASK << 2},
{LED_TYPE_FAN2, LED_TYPE_FAN2_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 2},
{LED_TYPE_FAN2, LED_TYPE_FAN2_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 2},
{LED_TYPE_FAN3, LED_TYPE_FAN3_REG_MASK, LED_MODE_GREEN, LED_MODE_FANX_GREEN_MASK << 4},
{LED_TYPE_FAN3, LED_TYPE_FAN3_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 4},
{LED_TYPE_FAN3, LED_TYPE_FAN3_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 4},
{LED_TYPE_FAN4, LED_TYPE_FAN4_REG_MASK, LED_MODE_GREEN, LED_MODE_FANX_GREEN_MASK << 6},
{LED_TYPE_FAN4, LED_TYPE_FAN4_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 6},
{LED_TYPE_FAN4, LED_TYPE_FAN4_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 6},
{LED_TYPE_FAN5, LED_TYPE_FAN5_REG_MASK, LED_MODE_GREEN, LED_MODE_FANX_GREEN_MASK << 0},
{LED_TYPE_FAN5, LED_TYPE_FAN5_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 0},
{LED_TYPE_FAN5, LED_TYPE_FAN5_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 0},
{LED_TYPE_DIAG, LED_TYPE_DIAG_REG_MASK, LED_MODE_GREEN, LED_MODE_DIAG_GREEN_MASK},
{LED_TYPE_DIAG, LED_TYPE_DIAG_REG_MASK, LED_MODE_AMBER, LED_MODE_DIAG_AMBER_MASK},
{LED_TYPE_DIAG, LED_TYPE_DIAG_REG_MASK, LED_MODE_OFF, LED_MODE_DIAG_OFF_MASK},
{LED_TYPE_LOC, LED_TYPE_LOC_REG_MASK, LED_MODE_AMBER, LED_MODE_LOC_ON_MASK},
{LED_TYPE_LOC, LED_TYPE_LOC_REG_MASK, LED_MODE_OFF, LED_MODE_LOC_OFF_MASK},
{LED_TYPE_LOC, LED_TYPE_LOC_REG_MASK, LED_MODE_AMBER_BLINK, LED_MODE_LOC_BLINK_MASK}
};
struct fanx_info_s {
u8 cname; /* device name */
enum led_type type;
u8 reg_id; /* map to led_reg & reg_val */
};
static struct fanx_info_s fanx_info[] = {
{'1', LED_TYPE_FAN1, 2},
{'2', LED_TYPE_FAN2, 2},
{'3', LED_TYPE_FAN3, 2},
{'4', LED_TYPE_FAN4, 2},
{'5', LED_TYPE_FAN5, 3}
};
static int led_reg_val_to_light_mode(enum led_type type, u8 reg_val) {
int i;
for (i = 0; i < ARRAY_SIZE(led_type_mode_data); i++) {
if (type != led_type_mode_data[i].type)
continue;
if ((led_type_mode_data[i].type_mask & reg_val) ==
led_type_mode_data[i].mode_mask)
{
return led_type_mode_data[i].mode;
}
}
return LED_MODE_UNKNOWN;
}
static u8 led_light_mode_to_reg_val(enum led_type type,
enum led_light_mode mode, u8 reg_val) {
int i;
for (i = 0; i < ARRAY_SIZE(led_type_mode_data); i++) {
if (type != led_type_mode_data[i].type)
continue;
if (mode != led_type_mode_data[i].mode)
continue;
reg_val = led_type_mode_data[i].mode_mask |
(reg_val & (~led_type_mode_data[i].type_mask));
}
return reg_val;
}
static int accton_as5812_54t_led_read_value(u8 reg)
{
return accton_i2c_cpld_read(0x60, reg);
}
static int accton_as5812_54t_led_write_value(u8 reg, u8 value)
{
return accton_i2c_cpld_write(0x60, reg, value);
}
static void accton_as5812_54t_led_update(void)
{
mutex_lock(&ledctl->update_lock);
if (time_after(jiffies, ledctl->last_updated + HZ + HZ / 2)
|| !ledctl->valid) {
int i;
dev_dbg(&ledctl->pdev->dev, "Starting accton_as5812_54t_led update\n");
/* Update LED data
*/
for (i = 0; i < ARRAY_SIZE(ledctl->reg_val); i++) {
int status = accton_as5812_54t_led_read_value(led_reg[i]);
if (status < 0) {
ledctl->valid = 0;
dev_dbg(&ledctl->pdev->dev, "reg %d, err %d\n", led_reg[i], status);
goto exit;
}
else
{
ledctl->reg_val[i] = status;
}
}
ledctl->last_updated = jiffies;
ledctl->valid = 1;
}
exit:
mutex_unlock(&ledctl->update_lock);
}
static void accton_as5812_54t_led_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode,
u8 reg, enum led_type type)
{
int reg_val;
mutex_lock(&ledctl->update_lock);
reg_val = accton_as5812_54t_led_read_value(reg);
if (reg_val < 0) {
dev_dbg(&ledctl->pdev->dev, "reg %d, err %d\n", reg, reg_val);
goto exit;
}
reg_val = led_light_mode_to_reg_val(type, led_light_mode, reg_val);
accton_as5812_54t_led_write_value(reg, reg_val);
/* to prevent the slow-update issue */
ledctl->valid = 0;
exit:
mutex_unlock(&ledctl->update_lock);
}
static void accton_as5812_54t_led_psu_1_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode)
{
accton_as5812_54t_led_set(led_cdev, led_light_mode, led_reg[1], LED_TYPE_PSU1);
}
static enum led_brightness accton_as5812_54t_led_psu_1_get(struct led_classdev *cdev)
{
accton_as5812_54t_led_update();
return led_reg_val_to_light_mode(LED_TYPE_PSU1, ledctl->reg_val[1]);
}
static void accton_as5812_54t_led_psu_2_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode)
{
accton_as5812_54t_led_set(led_cdev, led_light_mode, led_reg[1], LED_TYPE_PSU2);
}
static enum led_brightness accton_as5812_54t_led_psu_2_get(struct led_classdev *cdev)
{
accton_as5812_54t_led_update();
return led_reg_val_to_light_mode(LED_TYPE_PSU2, ledctl->reg_val[1]);
}
static void accton_as5812_54t_led_fan_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode)
{
accton_as5812_54t_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_FAN);
}
static enum led_brightness accton_as5812_54t_led_fan_get(struct led_classdev *cdev)
{
accton_as5812_54t_led_update();
return led_reg_val_to_light_mode(LED_TYPE_FAN, ledctl->reg_val[0]);
}
static void accton_as5812_54t_led_fanx_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode)
{
enum led_type led_type1;
int reg_id;
int i, nsize;
int ncount = sizeof(fanx_info)/sizeof(struct fanx_info_s);
for(i=0;i<ncount;i++)
{
nsize=strlen(led_cdev->name);
if (led_cdev->name[nsize-1] == fanx_info[i].cname)
{
led_type1 = fanx_info[i].type;
reg_id = fanx_info[i].reg_id;
accton_as5812_54t_led_set(led_cdev, led_light_mode, led_reg[reg_id], led_type1);
return;
}
}
}
static enum led_brightness accton_as5812_54t_led_fanx_get(struct led_classdev *cdev)
{
enum led_type led_type1;
int reg_id;
int i, nsize;
int ncount = sizeof(fanx_info)/sizeof(struct fanx_info_s);
for(i=0;i<ncount;i++)
{
nsize=strlen(cdev->name);
if (cdev->name[nsize-1] == fanx_info[i].cname)
{
led_type1 = fanx_info[i].type;
reg_id = fanx_info[i].reg_id;
accton_as5812_54t_led_update();
return led_reg_val_to_light_mode(led_type1, ledctl->reg_val[reg_id]);
}
}
return led_reg_val_to_light_mode(LED_TYPE_FAN1, ledctl->reg_val[2]);
}
static void accton_as5812_54t_led_diag_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode)
{
accton_as5812_54t_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_DIAG);
}
static enum led_brightness accton_as5812_54t_led_diag_get(struct led_classdev *cdev)
{
accton_as5812_54t_led_update();
return led_reg_val_to_light_mode(LED_TYPE_DIAG, ledctl->reg_val[0]);
}
static void accton_as5812_54t_led_loc_set(struct led_classdev *led_cdev,
enum led_brightness led_light_mode)
{
accton_as5812_54t_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_LOC);
}
static enum led_brightness accton_as5812_54t_led_loc_get(struct led_classdev *cdev)
{
accton_as5812_54t_led_update();
return led_reg_val_to_light_mode(LED_TYPE_LOC, ledctl->reg_val[0]);
}
static struct led_classdev accton_as5812_54t_leds[] = {
[LED_TYPE_PSU1] = {
.name = "accton_as5812_54t_led::psu1",
.default_trigger = "unused",
.brightness_set = accton_as5812_54t_led_psu_1_set,
.brightness_get = accton_as5812_54t_led_psu_1_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_PSU2] = {
.name = "accton_as5812_54t_led::psu2",
.default_trigger = "unused",
.brightness_set = accton_as5812_54t_led_psu_2_set,
.brightness_get = accton_as5812_54t_led_psu_2_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_FAN] = {
.name = "accton_as5812_54t_led::fan",
.default_trigger = "unused",
.brightness_set = accton_as5812_54t_led_fan_set,
.brightness_get = accton_as5812_54t_led_fan_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_FAN1] = {
.name = "accton_as5812_54t_led::fan1",
.default_trigger = "unused",
.brightness_set = accton_as5812_54t_led_fanx_set,
.brightness_get = accton_as5812_54t_led_fanx_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_FAN2] = {
.name = "accton_as5812_54t_led::fan2",
.default_trigger = "unused",
.brightness_set = accton_as5812_54t_led_fanx_set,
.brightness_get = accton_as5812_54t_led_fanx_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_FAN3] = {
.name = "accton_as5812_54t_led::fan3",
.default_trigger = "unused",
.brightness_set = accton_as5812_54t_led_fanx_set,
.brightness_get = accton_as5812_54t_led_fanx_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_FAN4] = {
.name = "accton_as5812_54t_led::fan4",
.default_trigger = "unused",
.brightness_set = accton_as5812_54t_led_fanx_set,
.brightness_get = accton_as5812_54t_led_fanx_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_FAN5] = {
.name = "accton_as5812_54t_led::fan5",
.default_trigger = "unused",
.brightness_set = accton_as5812_54t_led_fanx_set,
.brightness_get = accton_as5812_54t_led_fanx_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_DIAG] = {
.name = "accton_as5812_54t_led::diag",
.default_trigger = "unused",
.brightness_set = accton_as5812_54t_led_diag_set,
.brightness_get = accton_as5812_54t_led_diag_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
[LED_TYPE_LOC] = {
.name = "accton_as5812_54t_led::loc",
.default_trigger = "unused",
.brightness_set = accton_as5812_54t_led_loc_set,
.brightness_get = accton_as5812_54t_led_loc_get,
.flags = LED_CORE_SUSPENDRESUME,
.max_brightness = LED_MODE_AUTO,
},
};
static int accton_as5812_54t_led_suspend(struct platform_device *dev,
pm_message_t state)
{
int i = 0;
for (i = 0; i < ARRAY_SIZE(accton_as5812_54t_leds); i++) {
led_classdev_suspend(&accton_as5812_54t_leds[i]);
}
return 0;
}
static int accton_as5812_54t_led_resume(struct platform_device *dev)
{
int i = 0;
for (i = 0; i < ARRAY_SIZE(accton_as5812_54t_leds); i++) {
led_classdev_resume(&accton_as5812_54t_leds[i]);
}
return 0;
}
static int accton_as5812_54t_led_probe(struct platform_device *pdev)
{
int ret, i;
for (i = 0; i < ARRAY_SIZE(accton_as5812_54t_leds); i++) {
ret = led_classdev_register(&pdev->dev, &accton_as5812_54t_leds[i]);
if (ret < 0)
break;
}
/* Check if all LEDs were successfully registered */
if (i != ARRAY_SIZE(accton_as5812_54t_leds)){
int j;
/* only unregister the LEDs that were successfully registered */
for (j = 0; j < i; j++) {
led_classdev_unregister(&accton_as5812_54t_leds[i]);
}
}
return ret;
}
static int accton_as5812_54t_led_remove(struct platform_device *pdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(accton_as5812_54t_leds); i++) {
led_classdev_unregister(&accton_as5812_54t_leds[i]);
}
return 0;
}
static struct platform_driver accton_as5812_54t_led_driver = {
.probe = accton_as5812_54t_led_probe,
.remove = accton_as5812_54t_led_remove,
.suspend = accton_as5812_54t_led_suspend,
.resume = accton_as5812_54t_led_resume,
.driver = {
.name = DRVNAME,
.owner = THIS_MODULE,
},
};
static int __init accton_as5812_54t_led_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_led_driver);
if (ret < 0) {
goto exit;
}
ledctl = kzalloc(sizeof(struct accton_as5812_54t_led_data), GFP_KERNEL);
if (!ledctl) {
ret = -ENOMEM;
platform_driver_unregister(&accton_as5812_54t_led_driver);
goto exit;
}
mutex_init(&ledctl->update_lock);
ledctl->pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
if (IS_ERR(ledctl->pdev)) {
ret = PTR_ERR(ledctl->pdev);
platform_driver_unregister(&accton_as5812_54t_led_driver);
kfree(ledctl);
goto exit;
}
exit:
return ret;
}
static void __exit accton_as5812_54t_led_exit(void)
{
platform_device_unregister(ledctl->pdev);
platform_driver_unregister(&accton_as5812_54t_led_driver);
kfree(ledctl);
}
module_init(accton_as5812_54t_led_init);
module_exit(accton_as5812_54t_led_exit);
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
MODULE_DESCRIPTION("accton_as5812_54t_led driver");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,295 @@
/*
* An hwmon driver for accton as5812_54t Power Module
*
* Copyright (C) 2015 Accton Technology Corporation.
* Brandon Chuang <brandon_chuang@accton.com.tw>
*
* Based on ad7414.c
* Copyright 2006 Stefan Roese <sr at denx.de>, DENX Software Engineering
*
* 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/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
static ssize_t show_index(struct device *dev, struct device_attribute *da, char *buf);
static ssize_t show_status(struct device *dev, struct device_attribute *da, char *buf);
static ssize_t show_model_name(struct device *dev, struct device_attribute *da, char *buf);
static int as5812_54t_psu_read_block(struct i2c_client *client, u8 command, u8 *data,int data_len);
extern int accton_i2c_cpld_read(unsigned short cpld_addr, u8 reg);
/* Addresses scanned
*/
static const unsigned short normal_i2c[] = { 0x38, 0x3b, 0x50, 0x53, I2C_CLIENT_END };
/* Each client has this additional data
*/
struct as5812_54t_psu_data {
struct device *hwmon_dev;
struct mutex update_lock;
char valid; /* !=0 if registers are valid */
unsigned long last_updated; /* In jiffies */
u8 index; /* PSU index */
u8 status; /* Status(present/power_good) register read from CPLD */
char model_name[14]; /* Model name, read from eeprom */
};
static struct as5812_54t_psu_data *as5812_54t_psu_update_device(struct device *dev);
enum as5812_54t_psu_sysfs_attributes {
PSU_INDEX,
PSU_PRESENT,
PSU_MODEL_NAME,
PSU_POWER_GOOD
};
/* sysfs attributes for hwmon
*/
static SENSOR_DEVICE_ATTR(psu_index, S_IRUGO, show_index, NULL, PSU_INDEX);
static SENSOR_DEVICE_ATTR(psu_present, S_IRUGO, show_status, NULL, PSU_PRESENT);
static SENSOR_DEVICE_ATTR(psu_model_name, S_IRUGO, show_model_name,NULL, PSU_MODEL_NAME);
static SENSOR_DEVICE_ATTR(psu_power_good, S_IRUGO, show_status, NULL, PSU_POWER_GOOD);
static struct attribute *as5812_54t_psu_attributes[] = {
&sensor_dev_attr_psu_index.dev_attr.attr,
&sensor_dev_attr_psu_present.dev_attr.attr,
&sensor_dev_attr_psu_model_name.dev_attr.attr,
&sensor_dev_attr_psu_power_good.dev_attr.attr,
NULL
};
static ssize_t show_index(struct device *dev, struct device_attribute *da,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct as5812_54t_psu_data *data = i2c_get_clientdata(client);
return sprintf(buf, "%d\n", data->index);
}
static ssize_t show_status(struct device *dev, struct device_attribute *da,
char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct as5812_54t_psu_data *data = as5812_54t_psu_update_device(dev);
u8 status = 0;
if (attr->index == PSU_PRESENT) {
status = !(data->status >> ((data->index - 1) * 4) & 0x1);
}
else { /* PSU_POWER_GOOD */
status = data->status >> ((data->index - 1) * 4 + 1) & 0x1;
}
return sprintf(buf, "%d\n", status);
}
static ssize_t show_model_name(struct device *dev, struct device_attribute *da,
char *buf)
{
struct as5812_54t_psu_data *data = as5812_54t_psu_update_device(dev);
return sprintf(buf, "%s", data->model_name);
}
static const struct attribute_group as5812_54t_psu_group = {
.attrs = as5812_54t_psu_attributes,
};
static int as5812_54t_psu_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
struct as5812_54t_psu_data *data;
int status;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) {
status = -EIO;
goto exit;
}
data = kzalloc(sizeof(struct as5812_54t_psu_data), GFP_KERNEL);
if (!data) {
status = -ENOMEM;
goto exit;
}
i2c_set_clientdata(client, data);
data->valid = 0;
mutex_init(&data->update_lock);
dev_info(&client->dev, "chip found\n");
/* Register sysfs hooks */
status = sysfs_create_group(&client->dev.kobj, &as5812_54t_psu_group);
if (status) {
goto exit_free;
}
data->hwmon_dev = hwmon_device_register(&client->dev);
if (IS_ERR(data->hwmon_dev)) {
status = PTR_ERR(data->hwmon_dev);
goto exit_remove;
}
/* Update PSU index */
if (client->addr == 0x38 || client->addr == 0x50) {
data->index = 1;
}
else if (client->addr == 0x3b || client->addr == 0x53) {
data->index = 2;
}
dev_info(&client->dev, "%s: psu '%s'\n",
dev_name(data->hwmon_dev), client->name);
return 0;
exit_remove:
sysfs_remove_group(&client->dev.kobj, &as5812_54t_psu_group);
exit_free:
kfree(data);
exit:
return status;
}
static int as5812_54t_psu_remove(struct i2c_client *client)
{
struct as5812_54t_psu_data *data = i2c_get_clientdata(client);
hwmon_device_unregister(data->hwmon_dev);
sysfs_remove_group(&client->dev.kobj, &as5812_54t_psu_group);
kfree(data);
return 0;
}
static const struct i2c_device_id as5812_54t_psu_id[] = {
{ "as5812_54t_psu", 0 },
{}
};
MODULE_DEVICE_TABLE(i2c, as5812_54t_psu_id);
static struct i2c_driver as5812_54t_psu_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "as5812_54t_psu",
},
.probe = as5812_54t_psu_probe,
.remove = as5812_54t_psu_remove,
.id_table = as5812_54t_psu_id,
.address_list = normal_i2c,
};
static int as5812_54t_psu_read_block(struct i2c_client *client, u8 command, u8 *data,
int data_len)
{
int result = i2c_smbus_read_i2c_block_data(client, command, data_len, data);
if (unlikely(result < 0))
goto abort;
if (unlikely(result != data_len)) {
result = -EIO;
goto abort;
}
result = 0;
abort:
return result;
}
static struct as5812_54t_psu_data *as5812_54t_psu_update_device(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct as5812_54t_psu_data *data = i2c_get_clientdata(client);
mutex_lock(&data->update_lock);
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|| !data->valid) {
int status = -1;
dev_dbg(&client->dev, "Starting as5812_54t update\n");
/* Read model name */
if (client->addr == 0x38 || client->addr == 0x3b) {
/* AC power */
status = as5812_54t_psu_read_block(client, 0x26, data->model_name,
ARRAY_SIZE(data->model_name)-1);
}
else {
/* DC power */
status = as5812_54t_psu_read_block(client, 0x50, data->model_name,
ARRAY_SIZE(data->model_name)-1);
}
if (status < 0) {
data->model_name[0] = '\0';
dev_dbg(&client->dev, "unable to read model name from (0x%x)\n", client->addr);
}
else {
data->model_name[ARRAY_SIZE(data->model_name)-1] = '\0';
}
/* Read psu status */
status = accton_i2c_cpld_read(0x60, 0x2);
if (status < 0) {
dev_dbg(&client->dev, "cpld reg 0x60 err %d\n", status);
}
else {
data->status = status;
}
data->last_updated = jiffies;
data->valid = 1;
}
mutex_unlock(&data->update_lock);
return data;
}
static int __init as5812_54t_psu_init(void)
{
extern int platform_accton_as5812_54t(void);
if (!platform_accton_as5812_54t()) {
return -ENODEV;
}
return i2c_add_driver(&as5812_54t_psu_driver);
}
static void __exit as5812_54t_psu_exit(void)
{
i2c_del_driver(&as5812_54t_psu_driver);
}
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
MODULE_DESCRIPTION("accton as5812_54t_psu driver");
MODULE_LICENSE("GPL");
module_init(as5812_54t_psu_init);
module_exit(as5812_54t_psu_exit);

View File

@@ -0,0 +1,318 @@
/*
* An hwmon driver for accton as5812_54t sfp
*
* Copyright (C) 2015 Accton Technology Corporation.
* Brandon Chuang <brandon_chuang@accton.com.tw>
*
* Based on ad7414.c
* Copyright 2006 Stefan Roese <sr at denx.de>, DENX Software Engineering
*
* 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/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#define QSFP_PORT_START_INDEX 49
#define BIT_INDEX(i) (1ULL << (i))
/* Addresses scanned
*/
static const unsigned short normal_i2c[] = { 0x50, I2C_CLIENT_END };
/* Each client has this additional data
*/
struct as5812_54t_sfp_data {
struct device *hwmon_dev;
struct mutex update_lock;
char valid; /* !=0 if registers are valid */
unsigned long last_updated; /* In jiffies */
int port; /* Front port index */
char eeprom[256]; /* eeprom data */
u8 status; /* bit0:port49, bit1:port50 and so on */
};
static struct as5812_54t_sfp_data *as5812_54t_sfp_update_device(struct device *dev, int update_eeprom);
static ssize_t show_port_number(struct device *dev, struct device_attribute *da, char *buf);
static ssize_t show_status(struct device *dev, struct device_attribute *da, char *buf);
static ssize_t show_eeprom(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);
enum as5812_54t_sfp_sysfs_attributes {
SFP_IS_PRESENT,
SFP_PORT_NUMBER,
SFP_EEPROM,
SFP_IS_PRESENT_ALL,
};
/* sysfs attributes for hwmon
*/
static SENSOR_DEVICE_ATTR(sfp_is_present, S_IRUGO, show_status, NULL, SFP_IS_PRESENT);
static SENSOR_DEVICE_ATTR(sfp_port_number, S_IRUGO, show_port_number, NULL, SFP_PORT_NUMBER);
static SENSOR_DEVICE_ATTR(sfp_eeprom, S_IRUGO, show_eeprom, NULL, SFP_EEPROM);
static SENSOR_DEVICE_ATTR(sfp_is_present_all, S_IRUGO, show_status,NULL, SFP_IS_PRESENT_ALL);
static struct attribute *as5812_54t_sfp_attributes[] = {
&sensor_dev_attr_sfp_is_present.dev_attr.attr,
&sensor_dev_attr_sfp_eeprom.dev_attr.attr,
&sensor_dev_attr_sfp_port_number.dev_attr.attr,
&sensor_dev_attr_sfp_is_present_all.dev_attr.attr,
NULL
};
static ssize_t show_port_number(struct device *dev, struct device_attribute *da,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct as5812_54t_sfp_data *data = i2c_get_clientdata(client);
return sprintf(buf, "%d\n",data->port);
}
static ssize_t show_status(struct device *dev, struct device_attribute *da,
char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct as5812_54t_sfp_data *data = as5812_54t_sfp_update_device(dev, 0);
if (attr->index == SFP_IS_PRESENT) {
u8 val;
val = (data->status & BIT_INDEX(data->port - QSFP_PORT_START_INDEX)) ? 0 : 1;
return sprintf(buf, "%d", val);
}
else { /* SFP_IS_PRESENT_ALL */
return sprintf(buf, "%.2x\n", ~data->status);
}
}
static ssize_t show_eeprom(struct device *dev, struct device_attribute *da,
char *buf)
{
struct as5812_54t_sfp_data *data = as5812_54t_sfp_update_device(dev, 1);
if (!data->valid) {
return 0;
}
if ((data->status & BIT_INDEX(data->port - QSFP_PORT_START_INDEX)) != 0) {
return 0;
}
memcpy(buf, data->eeprom, sizeof(data->eeprom));
return sizeof(data->eeprom);
}
static const struct attribute_group as5812_54t_sfp_group = {
.attrs = as5812_54t_sfp_attributes,
};
static int as5812_54t_sfp_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
struct as5812_54t_sfp_data *data;
int status;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
status = -EIO;
goto exit;
}
data = kzalloc(sizeof(struct as5812_54t_sfp_data), GFP_KERNEL);
if (!data) {
status = -ENOMEM;
goto exit;
}
mutex_init(&data->update_lock);
data->port = dev_id->driver_data;
i2c_set_clientdata(client, data);
dev_info(&client->dev, "chip found\n");
/* Register sysfs hooks */
status = sysfs_create_group(&client->dev.kobj, &as5812_54t_sfp_group);
if (status) {
goto exit_free;
}
data->hwmon_dev = hwmon_device_register(&client->dev);
if (IS_ERR(data->hwmon_dev)) {
status = PTR_ERR(data->hwmon_dev);
goto exit_remove;
}
dev_info(&client->dev, "%s: sfp '%s'\n",
dev_name(data->hwmon_dev), client->name);
return 0;
exit_remove:
sysfs_remove_group(&client->dev.kobj, &as5812_54t_sfp_group);
exit_free:
kfree(data);
exit:
return status;
}
static int as5812_54t_sfp_remove(struct i2c_client *client)
{
struct as5812_54t_sfp_data *data = i2c_get_clientdata(client);
hwmon_device_unregister(data->hwmon_dev);
sysfs_remove_group(&client->dev.kobj, &as5812_54t_sfp_group);
kfree(data);
return 0;
}
enum port_numbers {
as5812_54t_qsfp49 = 49,
as5812_54t_qsfp50,
as5812_54t_qsfp51,
as5812_54t_qsfp52,
as5812_54t_qsfp53,
as5812_54t_qsfp54
};
static const struct i2c_device_id as5812_54t_sfp_id[] = {
{ "as5812_54t_qsfp49", as5812_54t_qsfp49 }, { "as5812_54t_qsfp50", as5812_54t_qsfp50 },
{ "as5812_54t_qsfp51", as5812_54t_qsfp51 }, { "as5812_54t_qsfp52", as5812_54t_qsfp52 },
{ "as5812_54t_qsfp53", as5812_54t_qsfp53 }, { "as5812_54t_qsfp54", as5812_54t_qsfp54 },
{}
};
MODULE_DEVICE_TABLE(i2c, as5812_54t_sfp_id);
static struct i2c_driver as5812_54t_sfp_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "as5812_54t_sfp",
},
.probe = as5812_54t_sfp_probe,
.remove = as5812_54t_sfp_remove,
.id_table = as5812_54t_sfp_id,
.address_list = normal_i2c,
};
static int as5812_54t_sfp_read_byte(struct i2c_client *client, u8 command, u8 *data)
{
int result = i2c_smbus_read_byte_data(client, command);
if (unlikely(result < 0)) {
dev_dbg(&client->dev, "sfp read byte data failed, command(0x%2x), data(0x%2x)\r\n", command, result);
goto abort;
}
*data = (u8)result;
result = 0;
abort:
return result;
}
static struct as5812_54t_sfp_data *as5812_54t_sfp_update_device(struct device *dev, int update_eeprom)
{
struct i2c_client *client = to_i2c_client(dev);
struct as5812_54t_sfp_data *data = i2c_get_clientdata(client);
mutex_lock(&data->update_lock);
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|| !data->valid || update_eeprom) {
int status = -1;
int i = 0;
data->valid = 0;
//dev_dbg(&client->dev, "Starting as5812_54t sfp status update\n");
data->status = 0xFF;
/*
* Bring QSFPs out of reset,
* This is a temporary fix until the QSFP+_MOD_RST register
* can be exposed through the driver.
*/
accton_i2c_cpld_write(0x60, 0x23, 0x3F);
/* Read present status of port 49-54(QSFP port) */
status = accton_i2c_cpld_read(0x60, 0x22);
if (status < 0) {
dev_dbg(&client->dev, "cpld(0x60) reg(0x22) err %d\n", status);
}
else {
data->status = status & 0x3F; /* (u32)status */
}
if (update_eeprom) {
/* Read eeprom data based on port number */
memset(data->eeprom, 0, sizeof(data->eeprom));
/* Check if the port is present */
if ((data->status & BIT_INDEX(data->port - QSFP_PORT_START_INDEX)) == 0) {
/* read eeprom */
for (i = 0; i < sizeof(data->eeprom); i++) {
status = as5812_54t_sfp_read_byte(client, i, data->eeprom + i);
if (status < 0) {
dev_dbg(&client->dev, "unable to read eeprom from port(%d)\n",
data->port);
goto exit;
}
}
}
}
data->valid = 1;
data->last_updated = jiffies;
}
exit:
mutex_unlock(&data->update_lock);
return data;
}
static int __init as5812_54t_sfp_init(void)
{
extern int platform_accton_as5812_54t(void);
if (!platform_accton_as5812_54t()) {
return -ENODEV;
}
return i2c_add_driver(&as5812_54t_sfp_driver);
}
static void __exit as5812_54t_sfp_exit(void)
{
i2c_del_driver(&as5812_54t_sfp_driver);
}
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
MODULE_DESCRIPTION("accton as5812_54t_sfp driver");
MODULE_LICENSE("GPL");
module_init(as5812_54t_sfp_init);
module_exit(as5812_54t_sfp_exit);