From 4e5af16f44166bde274da19a80551dd0812f5c08 Mon Sep 17 00:00:00 2001 From: Jeffrey Townsend Date: Thu, 24 Dec 2015 10:37:37 -0800 Subject: [PATCH] Latest import from opennetworklinux/linux --- .../configs/x86_64-all/x86_64-all.config | 21 +- .../patches/network-virt-ethtool.patch | 6 +- ...orm-accton-as5812_54t-device-drivers.patch | 1868 +++++++++++++ ...orm-accton-as5812_54x-device-drivers.patch | 2402 +++++++++++++++++ ...orm-accton-as6712_32x-device-drivers.patch | 6 +- ...orm-accton-as6812_32x-device-drivers.patch | 2293 ++++++++++++++++ .../kernels/3.2.65-1+deb7u2/patches/series | 3 + 7 files changed, 6593 insertions(+), 6 deletions(-) create mode 100644 packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as5812_54t-device-drivers.patch create mode 100644 packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as5812_54x-device-drivers.patch create mode 100644 packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as6812_32x-device-drivers.patch diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/configs/x86_64-all/x86_64-all.config b/packages/base/any/kernels/3.2.65-1+deb7u2/configs/x86_64-all/x86_64-all.config index 32ad6dee..779308ac 100644 --- a/packages/base/any/kernels/3.2.65-1+deb7u2/configs/x86_64-all/x86_64-all.config +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/configs/x86_64-all/x86_64-all.config @@ -1048,7 +1048,10 @@ CONFIG_EEPROM_AT25=y # CONFIG_EEPROM_LEGACY is not set # CONFIG_EEPROM_MAX6875 is not set CONFIG_EEPROM_ACCTON_AS5712_54x_SFP=y +CONFIG_EEPROM_ACCTON_AS5812_54x_SFP=y +CONFIG_EEPROM_ACCTON_AS5812_54t_SFP=y CONFIG_EEPROM_ACCTON_AS6712_32x_SFP=y +CONFIG_EEPROM_ACCTON_AS6812_32x_SFP=y CONFIG_EEPROM_ACCTON_AS7512_32x_SFP=y CONFIG_EEPROM_ACCTON_AS7712_32x_SFP=y CONFIG_EEPROM_93CX6=y @@ -1401,8 +1404,13 @@ CONFIG_ENC28J60_WRITEVERIFY=y # CONFIG_NET_PACKET_ENGINE is not set # CONFIG_NET_VENDOR_QLOGIC is not set CONFIG_NET_VENDOR_REALTEK=y -# CONFIG_8139CP is not set +CONFIG_ATP=y +CONFIG_8139CP=y # CONFIG_8139TOO is not set +# CONFIG_8139TOO_PIO is not set +CONFIG_8139TOO_TUNE_TWISTER=y +CONFIG_8139TOO_8129=y +# CONFIG_8139_OLD_RX_RESET is not set CONFIG_R8169=y # CONFIG_NET_VENDOR_RDC is not set # CONFIG_NET_VENDOR_SEEQ is not set @@ -1609,7 +1617,9 @@ CONFIG_I2C_MUX=y # Multiplexer I2C Chip support # CONFIG_I2C_MUX_ACCTON_AS5712_54x_CPLD=y +CONFIG_I2C_MUX_ACCTON_AS5812_54x_CPLD=y CONFIG_I2C_MUX_ACCTON_AS6712_32x_CPLD=y +CONFIG_I2C_MUX_ACCTON_AS6812_32x_CPLD=y CONFIG_I2C_MUX_GPIO=y CONFIG_I2C_MUX_PCA9541=y CONFIG_I2C_MUX_PCA954x=y @@ -1903,8 +1913,14 @@ CONFIG_SENSORS_W83781D=y CONFIG_SENSORS_CPR_4011_4MXX=y CONFIG_SENSORS_ACCTON_AS5712_54x_FAN=y CONFIG_SENSORS_ACCTON_AS5712_54x_PSU=y +CONFIG_SENSORS_ACCTON_AS5812_54x_FAN=y +CONFIG_SENSORS_ACCTON_AS5812_54x_PSU=y +CONFIG_SENSORS_ACCTON_AS5812_54t_FAN=y +CONFIG_SENSORS_ACCTON_AS5812_54t_PSU=y CONFIG_SENSORS_ACCTON_AS6712_32x_FAN=y CONFIG_SENSORS_ACCTON_AS6712_32x_PSU=y +CONFIG_SENSORS_ACCTON_AS6812_32x_FAN=y +CONFIG_SENSORS_ACCTON_AS6812_32x_PSU=y CONFIG_SENSORS_ACCTON_I2C_CPLD=y CONFIG_SENSORS_ACCTON_AS7512_32x_FAN=y CONFIG_SENSORS_ACCTON_AS7512_32x_PSU=y @@ -2231,7 +2247,10 @@ CONFIG_LEDS_CLASS=y # LED drivers # CONFIG_LEDS_ACCTON_AS5712_54x=y +CONFIG_LEDS_ACCTON_AS5812_54x=y +CONFIG_LEDS_ACCTON_AS5812_54t=y CONFIG_LEDS_ACCTON_AS6712_32x=y +CONFIG_LEDS_ACCTON_AS6812_32x=y CONFIG_LEDS_ACCTON_AS7512_32x=y CONFIG_LEDS_ACCTON_AS7712_32x=y # CONFIG_LEDS_LM3530 is not set diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/network-virt-ethtool.patch b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/network-virt-ethtool.patch index 002fb18a..b5205893 100644 --- a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/network-virt-ethtool.patch +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/network-virt-ethtool.patch @@ -4,19 +4,21 @@ diff --git a/drivers/net/tun.c b/drivers/net/tun.c index adc6269..078c36c 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c -@@ -350,6 +350,8 @@ static void tun_net_uninit(struct net_device *dev) +@@ -350,6 +350,9 @@ static void tun_net_uninit(struct net_device *dev) struct tun_struct *tun = netdev_priv(dev); struct tun_file *tfile = tun->tfile; ++ void port_uninit_ethtool_stats(struct net_device *dev); + port_uninit_ethtool_stats(dev); + /* Inform the methods they need to stop using the dev. */ if (tfile) { -@@ -473,6 +475,8 @@ static u32 tun_net_fix_features(struct net_device *dev, u32 features) +@@ -473,6 +476,9 @@ static u32 tun_net_fix_features(struct net_device *dev, u32 features) { struct tun_struct *tun = netdev_priv(dev); ++ void port_init_ethtool_stats(struct net_device *dev); + port_init_ethtool_stats(dev); + return (features & tun->set_features) | (features & ~TUN_USER_FEATURES); diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as5812_54t-device-drivers.patch b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as5812_54t-device-drivers.patch new file mode 100644 index 00000000..5281309c --- /dev/null +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as5812_54t-device-drivers.patch @@ -0,0 +1,1868 @@ +Device driver patches for accton as5812_54t (fan/psu/cpld/led/sfp) + +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +index 73ee085..89c619d 100644 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -1555,6 +1555,24 @@ config SENSORS_ACCTON_AS6812_32x_PSU + + This driver can also be built as a module. If so, the module will + be called accton_as6812_32x_psu. ++ ++config SENSORS_ACCTON_AS5812_54t_FAN ++ tristate "Accton as5812 54t fan" ++ depends on I2C && SENSORS_ACCTON_I2C_CPLD ++ help ++ If you say yes here you get support for Accton as5812 54t fan. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as5812_54t_fan. ++ ++config SENSORS_ACCTON_AS5812_54t_PSU ++ tristate "Accton as5812 54t psu" ++ depends on I2C && SENSORS_ACCTON_I2C_CPLD ++ help ++ If you say yes here you get support for Accton as5812 54t psu. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as5812_54t_psu. + + if ACPI + +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +index 7700250..b8cf2ef 100644 +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -34,6 +34,8 @@ obj-$(CONFIG_SENSORS_ACCTON_AS5812_54x_FAN) += accton_as5812_54x_fan.o + obj-$(CONFIG_SENSORS_ACCTON_AS5812_54x_PSU) += accton_as5812_54x_psu.o + obj-$(CONFIG_SENSORS_ACCTON_AS6812_32x_FAN) += accton_as6812_32x_fan.o + obj-$(CONFIG_SENSORS_ACCTON_AS6812_32x_PSU) += accton_as6812_32x_psu.o ++obj-$(CONFIG_SENSORS_ACCTON_AS5812_54t_FAN) += accton_as5812_54t_fan.o ++obj-$(CONFIG_SENSORS_ACCTON_AS5812_54t_PSU) += accton_as5812_54t_psu.o + obj-$(CONFIG_SENSORS_AD7314) += ad7314.o + obj-$(CONFIG_SENSORS_AD7414) += ad7414.o + obj-$(CONFIG_SENSORS_AD7418) += ad7418.o +diff --git a/drivers/hwmon/accton_as5812_54t_fan.c b/drivers/hwmon/accton_as5812_54t_fan.c +new file mode 100644 +index 0000000..bad9245 +--- /dev/null ++++ b/drivers/hwmon/accton_as5812_54t_fan.c +@@ -0,0 +1,442 @@ ++/* ++ * A hwmon driver for the Accton as5812 54t fan ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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; istatus[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 "); ++MODULE_DESCRIPTION("accton_as5812_54t_fan driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(accton_as5812_54t_fan_init); ++module_exit(accton_as5812_54t_fan_exit); ++ +diff --git a/drivers/hwmon/accton_as5812_54t_psu.c b/drivers/hwmon/accton_as5812_54t_psu.c +new file mode 100644 +index 0000000..bf1b79e +--- /dev/null ++++ b/drivers/hwmon/accton_as5812_54t_psu.c +@@ -0,0 +1,295 @@ ++/* ++ * An hwmon driver for accton as5812_54t Power Module ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * Based on ad7414.c ++ * Copyright 2006 Stefan Roese , 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++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 "); ++MODULE_DESCRIPTION("accton as5812_54t_psu driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(as5812_54t_psu_init); ++module_exit(as5812_54t_psu_exit); ++ +diff --git a/drivers/hwmon/accton_i2c_cpld.c b/drivers/hwmon/accton_i2c_cpld.c +index 3aeb08d..acf88c9 100644 +--- a/drivers/hwmon/accton_i2c_cpld.c ++++ b/drivers/hwmon/accton_i2c_cpld.c +@@ -40,6 +40,22 @@ struct cpld_client_node { + */ + static const unsigned short normal_i2c[] = { 0x31, 0x35, 0x60, 0x61, 0x62, I2C_CLIENT_END }; + ++static ssize_t show_cpld_version(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ int val = 0; ++ struct i2c_client *client = to_i2c_client(dev); ++ ++ val = i2c_smbus_read_byte_data(client, 0x1); ++ ++ if (val < 0) { ++ dev_dbg(&client->dev, "cpld(0x%x) reg(0x1) err %d\n", client->addr, val); ++ } ++ ++ return sprintf(buf, "%d", val); ++} ++ ++static struct device_attribute ver = __ATTR(version, 0600, show_cpld_version, NULL); ++ + static void accton_i2c_cpld_add_client(struct i2c_client *client) + { + struct cpld_client_node *node = kzalloc(sizeof(struct cpld_client_node), GFP_KERNEL); +@@ -93,6 +109,11 @@ static int accton_i2c_cpld_probe(struct i2c_client *client, + goto exit; + } + ++ status = sysfs_create_file(&client->dev.kobj, &ver.attr); ++ if (status) { ++ goto exit; ++ } ++ + dev_info(&client->dev, "chip found\n"); + accton_i2c_cpld_add_client(client); + +@@ -104,6 +125,7 @@ exit: + + static int accton_i2c_cpld_remove(struct i2c_client *client) + { ++ sysfs_remove_file(&client->dev.kobj, &ver.attr); + accton_i2c_cpld_remove_client(client); + + return 0; +@@ -217,6 +239,22 @@ int platform_accton_as7712_32x(void) + } + EXPORT_SYMBOL(platform_accton_as7712_32x); + ++static struct dmi_system_id as5812_54t_dmi_table[] = { ++ { ++ .ident = "Accton AS5812 54t", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Accton"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "AS5812-54T"), ++ }, ++ } ++}; ++ ++int platform_accton_as5812_54t(void) ++{ ++ return dmi_check_system(as5812_54t_dmi_table); ++} ++EXPORT_SYMBOL(platform_accton_as5812_54t); ++ + MODULE_AUTHOR("Brandon Chuang "); + MODULE_DESCRIPTION("accton_i2c_cpld driver"); + MODULE_LICENSE("GPL"); +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index cb0c17f..599b97b 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -81,6 +81,13 @@ config LEDS_ACCTON_AS6812_32x + help + This option enables support for the LEDs on the Accton as6812 32x. + Say Y to enable LEDs on the Accton as6812 32x. ++ ++config LEDS_ACCTON_AS5812_54t ++ tristate "LED support for the Accton as5812 54t" ++ depends on LEDS_CLASS && SENSORS_ACCTON_I2C_CPLD ++ help ++ This option enables support for the LEDs on the Accton as5812 54t. ++ Say Y to enable LEDs on the Accton as5812 54t. + + config LEDS_LM3530 + tristate "LCD Backlight driver for LM3530" +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 8db7a43..bd20baa 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -49,6 +49,7 @@ obj-$(CONFIG_LEDS_ACCTON_AS7512_32x) += leds-accton_as7512_32x.o + obj-$(CONFIG_LEDS_ACCTON_AS7712_32x) += leds-accton_as7712_32x.o + obj-$(CONFIG_LEDS_ACCTON_AS5812_54x) += leds-accton_as5812_54x.o + obj-$(CONFIG_LEDS_ACCTON_AS6812_32x) += leds-accton_as6812_32x.o ++obj-$(CONFIG_LEDS_ACCTON_AS5812_54t) += leds-accton_as5812_54t.o + + # LED SPI Drivers + obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o +diff --git a/drivers/leds/leds-accton_as5812_54t.c b/drivers/leds/leds-accton_as5812_54t.c +new file mode 100644 +index 0000000..011f62e +--- /dev/null ++++ b/drivers/leds/leds-accton_as5812_54t.c +@@ -0,0 +1,601 @@ ++/* ++ * A LED driver for the accton_as5812_54t_led ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++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;iname); ++ ++ 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;iname); ++ ++ 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 "); ++MODULE_DESCRIPTION("accton_as5812_54t_led driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig +index ff68df7..c75227b 100644 +--- a/drivers/misc/eeprom/Kconfig ++++ b/drivers/misc/eeprom/Kconfig +@@ -126,6 +126,15 @@ config EEPROM_ACCTON_AS6812_32x_SFP + + This driver can also be built as a module. If so, the module will + be called accton_as6812_32x_sfp. ++ ++config EEPROM_ACCTON_AS5812_54t_SFP ++ tristate "Accton as5812 54t sfp" ++ depends on I2C && SENSORS_ACCTON_I2C_CPLD ++ help ++ If you say yes here you get support for Accton as5812 54t sfp. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as5812_54t_sfp. + + config EEPROM_93CX6 + tristate "EEPROM 93CX6 support" +diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile +index 4b682a1..152a8bc 100644 +--- a/drivers/misc/eeprom/Makefile ++++ b/drivers/misc/eeprom/Makefile +@@ -12,4 +12,5 @@ obj-$(CONFIG_EEPROM_ACCTON_AS7512_32x_SFP) += accton_as7512_32x_sfp.o + obj-$(CONFIG_EEPROM_ACCTON_AS7712_32x_SFP) += accton_as7712_32x_sfp.o + obj-$(CONFIG_EEPROM_ACCTON_AS5812_54x_SFP) += accton_as5812_54x_sfp.o + obj-$(CONFIG_EEPROM_ACCTON_AS6812_32x_SFP) += accton_as6812_32x_sfp.o ++obj-$(CONFIG_EEPROM_ACCTON_AS5812_54t_SFP) += accton_as5812_54t_sfp.o + obj-$(CONFIG_EEPROM_SFF_8436) += sff_8436_eeprom.o +diff --git a/drivers/misc/eeprom/accton_as5812_54t_sfp.c b/drivers/misc/eeprom/accton_as5812_54t_sfp.c +new file mode 100644 +index 0000000..0985c80 +--- /dev/null ++++ b/drivers/misc/eeprom/accton_as5812_54t_sfp.c +@@ -0,0 +1,332 @@ ++/* ++ * An hwmon driver for accton as5812_54t sfp ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * Based on ad7414.c ++ * Copyright 2006 Stefan Roese , 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 int convert_cpld_present_value_in_port_order(int value) ++{ ++ int ret = 0; ++ ++ ret |= (value & BIT_INDEX(0)) << 5; ++ ret |= (value & BIT_INDEX(1)) << 1; ++ ret |= (value & BIT_INDEX(2)) >> 1; ++ ret |= (value & BIT_INDEX(3)) << 1; ++ ret |= (value & BIT_INDEX(4)) >> 4; ++ ret |= (value & BIT_INDEX(5)) >> 2; ++ ++ return ret; ++} ++ ++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 = convert_cpld_present_value_in_port_order(status); /* (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 "); ++MODULE_DESCRIPTION("accton as5812_54t_sfp driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(as5812_54t_sfp_init); ++module_exit(as5812_54t_sfp_exit); ++ diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as5812_54x-device-drivers.patch b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as5812_54x-device-drivers.patch new file mode 100644 index 00000000..7f3e1c32 --- /dev/null +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as5812_54x-device-drivers.patch @@ -0,0 +1,2402 @@ +Device driver patches for accton as5812-54x (fan/psu/cpld/led/sfp) + +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +index 2d4a7fb..4d9fb22 100644 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -1520,6 +1520,24 @@ config SENSORS_ACCTON_AS7712_32x_PSU + This driver can also be built as a module. If so, the module will + be called accton_as7712_32x_psu. + ++config SENSORS_ACCTON_AS5812_54x_FAN ++ tristate "Accton as5812 54x fan" ++ depends on I2C && I2C_MUX_ACCTON_AS5812_54x_CPLD ++ help ++ If you say yes here you get support for Accton as5812 54x fan. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as5812_54x_fan. ++ ++config SENSORS_ACCTON_AS5812_54x_PSU ++ tristate "Accton as5812 54x psu" ++ depends on I2C && I2C_MUX_ACCTON_AS5812_54x_CPLD ++ help ++ If you say yes here you get support for Accton as5812 54x psu. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as5812_54x_psu. ++ + if ACPI + + comment "ACPI drivers" +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +index ea97f4a..818dd01 100644 +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -30,6 +30,8 @@ obj-$(CONFIG_SENSORS_ACCTON_AS7512_32x_PSU) += accton_as7512_32x_psu.o + obj-$(CONFIG_SENSORS_ACCTON_AS7712_32x_FAN) += accton_as7712_32x_fan.o + obj-$(CONFIG_SENSORS_ACCTON_AS7712_32x_PSU) += accton_as7712_32x_psu.o + obj-$(CONFIG_SENSORS_ACCTON_I2C_CPLD) += accton_i2c_cpld.o ++obj-$(CONFIG_SENSORS_ACCTON_AS5812_54x_FAN) += accton_as5812_54x_fan.o ++obj-$(CONFIG_SENSORS_ACCTON_AS5812_54x_PSU) += accton_as5812_54x_psu.o + obj-$(CONFIG_SENSORS_AD7314) += ad7314.o + obj-$(CONFIG_SENSORS_AD7414) += ad7414.o + obj-$(CONFIG_SENSORS_AD7418) += ad7418.o +@@ -141,7 +143,6 @@ obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o + obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o + obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o + obj-$(CONFIG_SENSORS_QUANTA_LY_HWMON) += quanta-ly-hwmon.o +-obj-$(CONFIG_SENSORS_YM2651Y) += ym2651y.o + + obj-$(CONFIG_PMBUS) += pmbus/ + +diff --git a/drivers/hwmon/accton_as5812_54x_fan.c b/drivers/hwmon/accton_as5812_54x_fan.c +new file mode 100644 +index 0000000..3e25db1 +--- /dev/null ++++ b/drivers/hwmon/accton_as5812_54x_fan.c +@@ -0,0 +1,442 @@ ++/* ++ * A hwmon driver for the Accton as5812 54x fan ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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_54x_fan *fan_data = NULL; ++ ++struct accton_as5812_54x_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_54x_fan_update_device(struct device *dev); ++static int accton_as5812_54x_fan_read_value(u8 reg); ++static int accton_as5812_54x_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 as5812_54x_i2c_cpld_read(unsigned short cpld_addr, u8 reg); ++extern int as5812_54x_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_54x_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_54x_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_54x_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_54x_fan_group = { ++ .attrs = accton_as5812_54x_fan_attributes, ++}; ++ ++static int accton_as5812_54x_fan_read_value(u8 reg) ++{ ++ return as5812_54x_i2c_cpld_read(0x60, reg); ++} ++ ++static int accton_as5812_54x_fan_write_value(u8 reg, u8 value) ++{ ++ return as5812_54x_i2c_cpld_write(0x60, reg, value); ++} ++ ++static void accton_as5812_54x_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_54x_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_54x_fan update 2 \n"); ++ ++ fault = accton_as5812_54x_fan_read_value(CPLD_REG_FAN_STATUS_OFFSET); ++ r_fault = accton_as5812_54x_fan_read_value(CPLD_REG_FANR_STATUS_OFFSET); ++ direction = accton_as5812_54x_fan_read_value(CPLD_REG_FAN_DIRECTION_OFFSET); ++ ctrl_speed = accton_as5812_54x_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; istatus[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_54x_fan_read_value(fan_speed_reg[i]); ++ r_speed = accton_as5812_54x_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_54x_fan_probe(struct platform_device *pdev) ++{ ++ int status = -1; ++ ++ /* Register sysfs hooks */ ++ status = sysfs_create_group(&pdev->dev.kobj, &accton_as5812_54x_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_54x_fan\n"); ++ ++ return 0; ++ ++exit_remove: ++ sysfs_remove_group(&pdev->dev.kobj, &accton_as5812_54x_fan_group); ++exit: ++ return status; ++} ++ ++static int accton_as5812_54x_fan_remove(struct platform_device *pdev) ++{ ++ hwmon_device_unregister(fan_data->hwmon_dev); ++ sysfs_remove_group(&fan_data->pdev->dev.kobj, &accton_as5812_54x_fan_group); ++ ++ return 0; ++} ++ ++#define DRVNAME "as5812_54x_fan" ++ ++static struct platform_driver accton_as5812_54x_fan_driver = { ++ .probe = accton_as5812_54x_fan_probe, ++ .remove = accton_as5812_54x_fan_remove, ++ .driver = { ++ .name = DRVNAME, ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init accton_as5812_54x_fan_init(void) ++{ ++ int ret; ++ ++ extern int platform_accton_as5812_54x(void); ++ if(!platform_accton_as5812_54x()) { ++ return -ENODEV; ++ } ++ ++ ret = platform_driver_register(&accton_as5812_54x_fan_driver); ++ if (ret < 0) { ++ goto exit; ++ } ++ ++ fan_data = kzalloc(sizeof(struct accton_as5812_54x_fan), GFP_KERNEL); ++ if (!fan_data) { ++ ret = -ENOMEM; ++ platform_driver_unregister(&accton_as5812_54x_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_54x_fan_driver); ++ kfree(fan_data); ++ goto exit; ++ } ++ ++exit: ++ return ret; ++} ++ ++static void __exit accton_as5812_54x_fan_exit(void) ++{ ++ platform_device_unregister(fan_data->pdev); ++ platform_driver_unregister(&accton_as5812_54x_fan_driver); ++ kfree(fan_data); ++} ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("accton_as5812_54x_fan driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(accton_as5812_54x_fan_init); ++module_exit(accton_as5812_54x_fan_exit); ++ +diff --git a/drivers/hwmon/accton_as5812_54x_psu.c b/drivers/hwmon/accton_as5812_54x_psu.c +new file mode 100644 +index 0000000..0d29980 +--- /dev/null ++++ b/drivers/hwmon/accton_as5812_54x_psu.c +@@ -0,0 +1,294 @@ ++/* ++ * An hwmon driver for accton as5812_54x Power Module ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * Based on ad7414.c ++ * Copyright 2006 Stefan Roese , 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++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_54x_psu_read_block(struct i2c_client *client, u8 command, u8 *data,int data_len); ++extern int as5812_54x_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_54x_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_54x_psu_data *as5812_54x_psu_update_device(struct device *dev); ++ ++enum as5812_54x_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_54x_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_54x_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_54x_psu_data *data = as5812_54x_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_54x_psu_data *data = as5812_54x_psu_update_device(dev); ++ ++ return sprintf(buf, "%s", data->model_name); ++} ++ ++static const struct attribute_group as5812_54x_psu_group = { ++ .attrs = as5812_54x_psu_attributes, ++}; ++ ++static int as5812_54x_psu_probe(struct i2c_client *client, ++ const struct i2c_device_id *dev_id) ++{ ++ struct as5812_54x_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_54x_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_54x_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_54x_psu_group); ++exit_free: ++ kfree(data); ++exit: ++ ++ return status; ++} ++ ++static int as5812_54x_psu_remove(struct i2c_client *client) ++{ ++ struct as5812_54x_psu_data *data = i2c_get_clientdata(client); ++ ++ hwmon_device_unregister(data->hwmon_dev); ++ sysfs_remove_group(&client->dev.kobj, &as5812_54x_psu_group); ++ kfree(data); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id as5812_54x_psu_id[] = { ++ { "as5812_54x_psu", 0 }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, as5812_54x_psu_id); ++ ++static struct i2c_driver as5812_54x_psu_driver = { ++ .class = I2C_CLASS_HWMON, ++ .driver = { ++ .name = "as5812_54x_psu", ++ }, ++ .probe = as5812_54x_psu_probe, ++ .remove = as5812_54x_psu_remove, ++ .id_table = as5812_54x_psu_id, ++ .address_list = normal_i2c, ++}; ++ ++static int as5812_54x_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_54x_psu_data *as5812_54x_psu_update_device(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct as5812_54x_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_54x update\n"); ++ ++ /* Read model name */ ++ if (client->addr == 0x38 || client->addr == 0x3b) { ++ /* AC power */ ++ status = as5812_54x_psu_read_block(client, 0x26, data->model_name, ++ ARRAY_SIZE(data->model_name)-1); ++ } ++ else { ++ /* DC power */ ++ status = as5812_54x_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 = as5812_54x_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_54x_psu_init(void) ++{ ++ extern int platform_accton_as5812_54x(void); ++ if(!platform_accton_as5812_54x()) { ++ return -ENODEV; ++ } ++ return i2c_add_driver(&as5812_54x_psu_driver); ++} ++ ++static void __exit as5812_54x_psu_exit(void) ++{ ++ i2c_del_driver(&as5812_54x_psu_driver); ++} ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("accton as5812_54x_psu driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(as5812_54x_psu_init); ++module_exit(as5812_54x_psu_exit); ++ +diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig +index 4429dd9..8ac67ef 100644 +--- a/drivers/i2c/muxes/Kconfig ++++ b/drivers/i2c/muxes/Kconfig +@@ -24,6 +24,15 @@ config I2C_MUX_ACCTON_AS6712_32x_CPLD + This driver can also be built as a module. If so, the module + will be called i2c-mux-accton_as6712_32x_cpld. + ++config I2C_MUX_ACCTON_AS5812_54x_CPLD ++ tristate "Accton as5812_54x CPLD I2C multiplexer" ++ help ++ If you say yes here you get support for the Accton CPLD ++ I2C mux devices. ++ ++ This driver can also be built as a module. If so, the module ++ will be called i2c-mux-accton_as5812_54x_cpld. ++ + config I2C_MUX_GPIO + tristate "GPIO-based I2C multiplexer" + depends on GENERIC_GPIO +diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile +index cab3174..7769d29 100644 +--- a/drivers/i2c/muxes/Makefile ++++ b/drivers/i2c/muxes/Makefile +@@ -9,5 +9,6 @@ obj-$(CONFIG_I2C_MUX_QUANTA) += quanta-i2cmux.o + obj-$(CONFIG_I2C_MUX_QUANTA_LY2) += quanta-ly2-i2c-mux.o + obj-$(CONFIG_I2C_MUX_ACCTON_AS5712_54x_CPLD) += i2c-mux-accton_as5712_54x_cpld.o + obj-$(CONFIG_I2C_MUX_ACCTON_AS6712_32x_CPLD) += i2c-mux-accton_as6712_32x_cpld.o ++obj-$(CONFIG_I2C_MUX_ACCTON_AS5812_54x_CPLD) += i2c-mux-accton_as5812_54x_cpld.o + + ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG +diff --git a/drivers/i2c/muxes/i2c-mux-accton_as5812_54x_cpld.c b/drivers/i2c/muxes/i2c-mux-accton_as5812_54x_cpld.c +new file mode 100644 +index 0000000..e01e557 +--- /dev/null ++++ b/drivers/i2c/muxes/i2c-mux-accton_as5812_54x_cpld.c +@@ -0,0 +1,387 @@ ++/* ++ * An I2C multiplexer dirver for accton as5812 CPLD ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * This module supports the accton cpld that hold the channel select ++ * mechanism for other i2c slave devices, such as SFP. ++ * This includes the: ++ * Accton as5812_54x CPLD1/CPLD2/CPLD3 ++ * ++ * Based on: ++ * pca954x.c from Kumar Gala ++ * Copyright (C) 2006 ++ * ++ * Based on: ++ * pca954x.c from Ken Harrenstien ++ * Copyright (C) 2004 Google, Inc. (Ken Harrenstien) ++ * ++ * Based on: ++ * i2c-virtual_cb.c from Brian Kuschak ++ * and ++ * pca9540.c from Jean Delvare . ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static struct dmi_system_id as5812_54x_dmi_table[] = { ++ { ++ .ident = "Accton AS5812-54X", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Accton"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "AS5812-54X"), ++ }, ++ } ++}; ++ ++int platform_accton_as5812_54x(void) ++{ ++ return dmi_check_system(as5812_54x_dmi_table); ++} ++EXPORT_SYMBOL(platform_accton_as5812_54x); ++ ++#define NUM_OF_CPLD1_CHANS 0x0 ++#define NUM_OF_CPLD2_CHANS 0x18 ++#define NUM_OF_CPLD3_CHANS 0x1E ++#define CPLD_CHANNEL_SELECT_REG 0x2 ++#define CPLD_DESELECT_CHANNEL 0xFF ++ ++#define ACCTON_I2C_CPLD_MUX_MAX_NCHANS NUM_OF_CPLD3_CHANS ++ ++static LIST_HEAD(cpld_client_list); ++static struct mutex list_lock; ++ ++struct cpld_client_node { ++ struct i2c_client *client; ++ struct list_head list; ++}; ++ ++enum cpld_mux_type { ++ as5812_54x_cpld2, ++ as5812_54x_cpld3, ++ as5812_54x_cpld1 ++}; ++ ++struct accton_i2c_cpld_mux { ++ enum cpld_mux_type type; ++ struct i2c_adapter *virt_adaps[ACCTON_I2C_CPLD_MUX_MAX_NCHANS]; ++ u8 last_chan; /* last register value */ ++}; ++ ++struct chip_desc { ++ u8 nchans; ++ u8 deselectChan; ++}; ++ ++/* Provide specs for the PCA954x types we know about */ ++static const struct chip_desc chips[] = { ++ [as5812_54x_cpld1] = { ++ .nchans = NUM_OF_CPLD1_CHANS, ++ .deselectChan = CPLD_DESELECT_CHANNEL, ++ }, ++ [as5812_54x_cpld2] = { ++ .nchans = NUM_OF_CPLD2_CHANS, ++ .deselectChan = CPLD_DESELECT_CHANNEL, ++ }, ++ [as5812_54x_cpld3] = { ++ .nchans = NUM_OF_CPLD3_CHANS, ++ .deselectChan = CPLD_DESELECT_CHANNEL, ++ } ++}; ++ ++static const struct i2c_device_id accton_i2c_cpld_mux_id[] = { ++ { "as5812_54x_cpld1", as5812_54x_cpld1 }, ++ { "as5812_54x_cpld2", as5812_54x_cpld2 }, ++ { "as5812_54x_cpld3", as5812_54x_cpld3 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, accton_i2c_cpld_mux_id); ++ ++/* Write to mux register. Don't use i2c_transfer()/i2c_smbus_xfer() ++ for this as they will try to lock adapter a second time */ ++static int accton_i2c_cpld_mux_reg_write(struct i2c_adapter *adap, ++ struct i2c_client *client, u8 val) ++{ ++ unsigned long orig_jiffies; ++ unsigned short flags; ++ union i2c_smbus_data data; ++ int try; ++ s32 res = -EIO; ++ ++ data.byte = val; ++ flags = client->flags; ++ flags &= I2C_M_TEN | I2C_CLIENT_PEC; ++ ++ if (adap->algo->smbus_xfer) { ++ /* Retry automatically on arbitration loss */ ++ orig_jiffies = jiffies; ++ for (res = 0, try = 0; try <= adap->retries; try++) { ++ res = adap->algo->smbus_xfer(adap, client->addr, flags, ++ I2C_SMBUS_WRITE, CPLD_CHANNEL_SELECT_REG, ++ I2C_SMBUS_BYTE_DATA, &data); ++ if (res != -EAGAIN) ++ break; ++ if (time_after(jiffies, ++ orig_jiffies + adap->timeout)) ++ break; ++ } ++ } ++ ++ return res; ++} ++ ++static int accton_i2c_cpld_mux_select_chan(struct i2c_adapter *adap, ++ void *client, u32 chan) ++{ ++ struct accton_i2c_cpld_mux *data = i2c_get_clientdata(client); ++ u8 regval; ++ int ret = 0; ++ regval = chan; ++ ++ /* Only select the channel if its different from the last channel */ ++ if (data->last_chan != regval) { ++ ret = accton_i2c_cpld_mux_reg_write(adap, client, regval); ++ data->last_chan = regval; ++ } ++ ++ return ret; ++} ++ ++static int accton_i2c_cpld_mux_deselect_mux(struct i2c_adapter *adap, ++ void *client, u32 chan) ++{ ++ struct accton_i2c_cpld_mux *data = i2c_get_clientdata(client); ++ ++ /* Deselect active channel */ ++ data->last_chan = chips[data->type].deselectChan; ++ ++ return accton_i2c_cpld_mux_reg_write(adap, client, data->last_chan); ++} ++ ++static void accton_i2c_cpld_add_client(struct i2c_client *client) ++{ ++ struct cpld_client_node *node = kzalloc(sizeof(struct cpld_client_node), GFP_KERNEL); ++ ++ if (!node) { ++ dev_dbg(&client->dev, "Can't allocate cpld_client_node (0x%x)\n", client->addr); ++ return; ++ } ++ ++ node->client = client; ++ ++ mutex_lock(&list_lock); ++ list_add(&node->list, &cpld_client_list); ++ mutex_unlock(&list_lock); ++} ++ ++static void accton_i2c_cpld_remove_client(struct i2c_client *client) ++{ ++ struct list_head *list_node = NULL; ++ struct cpld_client_node *cpld_node = NULL; ++ int found = 0; ++ ++ mutex_lock(&list_lock); ++ ++ list_for_each(list_node, &cpld_client_list) ++ { ++ cpld_node = list_entry(list_node, struct cpld_client_node, list); ++ ++ if (cpld_node->client == client) { ++ found = 1; ++ break; ++ } ++ } ++ ++ if (found) { ++ list_del(list_node); ++ kfree(cpld_node); ++ } ++ ++ mutex_unlock(&list_lock); ++} ++ ++static ssize_t show_cpld_version(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ u8 reg = 0x1; ++ struct i2c_client *client; ++ int len; ++ ++ client = to_i2c_client(dev); ++ len = sprintf(buf, "%d", i2c_smbus_read_byte_data(client, reg)); ++ ++ return len; ++} ++ ++static struct device_attribute ver = __ATTR(version, 0600, show_cpld_version, NULL); ++ ++/* ++ * I2C init/probing/exit functions ++ */ ++static int accton_i2c_cpld_mux_probe(struct i2c_client *client, ++ const struct i2c_device_id *id) ++{ ++ struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); ++ int chan=0; ++ struct accton_i2c_cpld_mux *data; ++ int ret = -ENODEV; ++ ++ if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE)) ++ goto err; ++ ++ data = kzalloc(sizeof(struct accton_i2c_cpld_mux), GFP_KERNEL); ++ if (!data) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ ++ i2c_set_clientdata(client, data); ++ ++ data->type = id->driver_data; ++ ++ if (data->type == as5812_54x_cpld2 || data->type == as5812_54x_cpld3) { ++ data->last_chan = chips[data->type].deselectChan; /* force the first selection */ ++ ++ /* Now create an adapter for each channel */ ++ for (chan = 0; chan < chips[data->type].nchans; chan++) { ++ data->virt_adaps[chan] = i2c_add_mux_adapter(adap, &client->dev, client, 0, chan, ++ accton_i2c_cpld_mux_select_chan, ++ accton_i2c_cpld_mux_deselect_mux); ++ ++ if (data->virt_adaps[chan] == NULL) { ++ ret = -ENODEV; ++ dev_err(&client->dev, "failed to register multiplexed adapter %d\n", chan); ++ goto virt_reg_failed; ++ } ++ } ++ ++ dev_info(&client->dev, "registered %d multiplexed busses for I2C mux %s\n", ++ chan, client->name); ++ } ++ ++ accton_i2c_cpld_add_client(client); ++ ++ ret = sysfs_create_file(&client->dev.kobj, &ver.attr); ++ if (ret) ++ goto virt_reg_failed; ++ ++ return 0; ++ ++virt_reg_failed: ++ for (chan--; chan >= 0; chan--) { ++ i2c_del_mux_adapter(data->virt_adaps[chan]); ++ } ++ ++ kfree(data); ++err: ++ return ret; ++} ++ ++static int accton_i2c_cpld_mux_remove(struct i2c_client *client) ++{ ++ struct accton_i2c_cpld_mux *data = i2c_get_clientdata(client); ++ const struct chip_desc *chip = &chips[data->type]; ++ int chan; ++ ++ sysfs_remove_file(&client->dev.kobj, &ver.attr); ++ ++ for (chan = 0; chan < chip->nchans; ++chan) { ++ if (data->virt_adaps[chan]) { ++ i2c_del_mux_adapter(data->virt_adaps[chan]); ++ data->virt_adaps[chan] = NULL; ++ } ++ } ++ ++ kfree(data); ++ accton_i2c_cpld_remove_client(client); ++ ++ return 0; ++} ++ ++int as5812_54x_i2c_cpld_read(unsigned short cpld_addr, u8 reg) ++{ ++ struct list_head *list_node = NULL; ++ struct cpld_client_node *cpld_node = NULL; ++ int ret = -EPERM; ++ ++ mutex_lock(&list_lock); ++ ++ list_for_each(list_node, &cpld_client_list) ++ { ++ cpld_node = list_entry(list_node, struct cpld_client_node, list); ++ ++ if (cpld_node->client->addr == cpld_addr) { ++ ret = i2c_smbus_read_byte_data(cpld_node->client, reg); ++ break; ++ } ++ } ++ ++ mutex_unlock(&list_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL(as5812_54x_i2c_cpld_read); ++ ++int as5812_54x_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value) ++{ ++ struct list_head *list_node = NULL; ++ struct cpld_client_node *cpld_node = NULL; ++ int ret = -EIO; ++ ++ mutex_lock(&list_lock); ++ ++ list_for_each(list_node, &cpld_client_list) ++ { ++ cpld_node = list_entry(list_node, struct cpld_client_node, list); ++ ++ if (cpld_node->client->addr == cpld_addr) { ++ ret = i2c_smbus_write_byte_data(cpld_node->client, reg, value); ++ break; ++ } ++ } ++ ++ mutex_unlock(&list_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL(as5812_54x_i2c_cpld_write); ++ ++static struct i2c_driver accton_i2c_cpld_mux_driver = { ++ .driver = { ++ .name = "as5812_54x_cpld", ++ .owner = THIS_MODULE, ++ }, ++ .probe = accton_i2c_cpld_mux_probe, ++ .remove = accton_i2c_cpld_mux_remove, ++ .id_table = accton_i2c_cpld_mux_id, ++}; ++ ++static int __init accton_i2c_cpld_mux_init(void) ++{ ++ mutex_init(&list_lock); ++ return i2c_add_driver(&accton_i2c_cpld_mux_driver); ++} ++ ++static void __exit accton_i2c_cpld_mux_exit(void) ++{ ++ i2c_del_driver(&accton_i2c_cpld_mux_driver); ++} ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("Accton I2C CPLD mux driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(accton_i2c_cpld_mux_init); ++module_exit(accton_i2c_cpld_mux_exit); ++ ++ +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index 48106f2..514f978 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -68,6 +68,13 @@ config LEDS_ACCTON_AS7712_32x + This option enables support for the LEDs on the Accton as7712 32x. + Say Y to enable LEDs on the Accton as7712 32x. + ++config LEDS_ACCTON_AS5812_54x ++ tristate "LED support for the Accton as5812 54x" ++ depends on LEDS_CLASS && I2C_MUX_ACCTON_AS5812_54x_CPLD ++ help ++ This option enables support for the LEDs on the Accton as5812 54x. ++ Say Y to enable LEDs on the Accton as5812 54x. ++ + config LEDS_LM3530 + tristate "LCD Backlight driver for LM3530" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index c4ea931..379c448 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -47,6 +47,8 @@ obj-$(CONFIG_LEDS_ACCTON_AS5712_54x) += leds-accton_as5712_54x.o + obj-$(CONFIG_LEDS_ACCTON_AS6712_32x) += leds-accton_as6712_32x.o + obj-$(CONFIG_LEDS_ACCTON_AS7512_32x) += leds-accton_as7512_32x.o + obj-$(CONFIG_LEDS_ACCTON_AS7712_32x) += leds-accton_as7712_32x.o ++obj-$(CONFIG_LEDS_ACCTON_AS5812_54x) += leds-accton_as5812_54x.o ++ + # LED SPI Drivers + obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o + +diff --git a/drivers/leds/leds-accton_as5812_54x.c b/drivers/leds/leds-accton_as5812_54x.c +new file mode 100644 +index 0000000..b701868 +--- /dev/null ++++ b/drivers/leds/leds-accton_as5812_54x.c +@@ -0,0 +1,597 @@ ++/* ++ * A LED driver for the accton_as5812_54x_led ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++extern int as5812_54x_i2c_cpld_read (unsigned short cpld_addr, u8 reg); ++extern int as5812_54x_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_54x_led" ++ ++struct accton_as5812_54x_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_54x_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_AMBER, ++ LED_MODE_RED, ++ LED_MODE_GREEN_BLINK, ++ LED_MODE_AMBER_BLINK, ++ LED_MODE_RED_BLINK, ++ LED_MODE_AUTO, ++}; ++ ++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 0; ++} ++ ++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_54x_led_read_value(u8 reg) ++{ ++ return as5812_54x_i2c_cpld_read(0x60, reg); ++} ++ ++static int accton_as5812_54x_led_write_value(u8 reg, u8 value) ++{ ++ return as5812_54x_i2c_cpld_write(0x60, reg, value); ++} ++ ++static void accton_as5812_54x_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_54x_led update\n"); ++ ++ /* Update LED data ++ */ ++ for (i = 0; i < ARRAY_SIZE(ledctl->reg_val); i++) { ++ int status = accton_as5812_54x_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_54x_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_54x_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_54x_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_54x_led_psu_1_set(struct led_classdev *led_cdev, ++ enum led_brightness led_light_mode) ++{ ++ accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[1], LED_TYPE_PSU1); ++} ++ ++static enum led_brightness accton_as5812_54x_led_psu_1_get(struct led_classdev *cdev) ++{ ++ accton_as5812_54x_led_update(); ++ return led_reg_val_to_light_mode(LED_TYPE_PSU1, ledctl->reg_val[1]); ++} ++ ++static void accton_as5812_54x_led_psu_2_set(struct led_classdev *led_cdev, ++ enum led_brightness led_light_mode) ++{ ++ accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[1], LED_TYPE_PSU2); ++} ++ ++static enum led_brightness accton_as5812_54x_led_psu_2_get(struct led_classdev *cdev) ++{ ++ accton_as5812_54x_led_update(); ++ return led_reg_val_to_light_mode(LED_TYPE_PSU2, ledctl->reg_val[1]); ++} ++ ++static void accton_as5812_54x_led_fan_set(struct led_classdev *led_cdev, ++ enum led_brightness led_light_mode) ++{ ++ accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_FAN); ++} ++ ++static enum led_brightness accton_as5812_54x_led_fan_get(struct led_classdev *cdev) ++{ ++ accton_as5812_54x_led_update(); ++ return led_reg_val_to_light_mode(LED_TYPE_FAN, ledctl->reg_val[0]); ++} ++ ++ ++static void accton_as5812_54x_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;iname); ++ ++ 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_54x_led_set(led_cdev, led_light_mode, led_reg[reg_id], led_type1); ++ return; ++ } ++ } ++} ++ ++ ++static enum led_brightness accton_as5812_54x_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;iname); ++ ++ if (cdev->name[nsize-1] == fanx_info[i].cname) ++ { ++ led_type1 = fanx_info[i].type; ++ reg_id = fanx_info[i].reg_id; ++ accton_as5812_54x_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_54x_led_diag_set(struct led_classdev *led_cdev, ++ enum led_brightness led_light_mode) ++{ ++ accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_DIAG); ++} ++ ++static enum led_brightness accton_as5812_54x_led_diag_get(struct led_classdev *cdev) ++{ ++ accton_as5812_54x_led_update(); ++ return led_reg_val_to_light_mode(LED_TYPE_DIAG, ledctl->reg_val[0]); ++} ++ ++static void accton_as5812_54x_led_loc_set(struct led_classdev *led_cdev, ++ enum led_brightness led_light_mode) ++{ ++ accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_LOC); ++} ++ ++static enum led_brightness accton_as5812_54x_led_loc_get(struct led_classdev *cdev) ++{ ++ accton_as5812_54x_led_update(); ++ return led_reg_val_to_light_mode(LED_TYPE_LOC, ledctl->reg_val[0]); ++} ++ ++static struct led_classdev accton_as5812_54x_leds[] = { ++ [LED_TYPE_PSU1] = { ++ .name = "accton_as5812_54x_led::psu1", ++ .default_trigger = "unused", ++ .brightness_set = accton_as5812_54x_led_psu_1_set, ++ .brightness_get = accton_as5812_54x_led_psu_1_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_PSU2] = { ++ .name = "accton_as5812_54x_led::psu2", ++ .default_trigger = "unused", ++ .brightness_set = accton_as5812_54x_led_psu_2_set, ++ .brightness_get = accton_as5812_54x_led_psu_2_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN] = { ++ .name = "accton_as5812_54x_led::fan", ++ .default_trigger = "unused", ++ .brightness_set = accton_as5812_54x_led_fan_set, ++ .brightness_get = accton_as5812_54x_led_fan_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN1] = { ++ .name = "accton_as5812_54x_led::fan1", ++ .default_trigger = "unused", ++ .brightness_set = accton_as5812_54x_led_fanx_set, ++ .brightness_get = accton_as5812_54x_led_fanx_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN2] = { ++ .name = "accton_as5812_54x_led::fan2", ++ .default_trigger = "unused", ++ .brightness_set = accton_as5812_54x_led_fanx_set, ++ .brightness_get = accton_as5812_54x_led_fanx_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN3] = { ++ .name = "accton_as5812_54x_led::fan3", ++ .default_trigger = "unused", ++ .brightness_set = accton_as5812_54x_led_fanx_set, ++ .brightness_get = accton_as5812_54x_led_fanx_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN4] = { ++ .name = "accton_as5812_54x_led::fan4", ++ .default_trigger = "unused", ++ .brightness_set = accton_as5812_54x_led_fanx_set, ++ .brightness_get = accton_as5812_54x_led_fanx_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN5] = { ++ .name = "accton_as5812_54x_led::fan5", ++ .default_trigger = "unused", ++ .brightness_set = accton_as5812_54x_led_fanx_set, ++ .brightness_get = accton_as5812_54x_led_fanx_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_DIAG] = { ++ .name = "accton_as5812_54x_led::diag", ++ .default_trigger = "unused", ++ .brightness_set = accton_as5812_54x_led_diag_set, ++ .brightness_get = accton_as5812_54x_led_diag_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_LOC] = { ++ .name = "accton_as5812_54x_led::loc", ++ .default_trigger = "unused", ++ .brightness_set = accton_as5812_54x_led_loc_set, ++ .brightness_get = accton_as5812_54x_led_loc_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++}; ++ ++static int accton_as5812_54x_led_suspend(struct platform_device *dev, ++ pm_message_t state) ++{ ++ int i = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(accton_as5812_54x_leds); i++) { ++ led_classdev_suspend(&accton_as5812_54x_leds[i]); ++ } ++ ++ return 0; ++} ++ ++static int accton_as5812_54x_led_resume(struct platform_device *dev) ++{ ++ int i = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(accton_as5812_54x_leds); i++) { ++ led_classdev_resume(&accton_as5812_54x_leds[i]); ++ } ++ ++ return 0; ++} ++ ++static int accton_as5812_54x_led_probe(struct platform_device *pdev) ++{ ++ int ret, i; ++ ++ for (i = 0; i < ARRAY_SIZE(accton_as5812_54x_leds); i++) { ++ ret = led_classdev_register(&pdev->dev, &accton_as5812_54x_leds[i]); ++ ++ if (ret < 0) ++ break; ++ } ++ ++ /* Check if all LEDs were successfully registered */ ++ if (i != ARRAY_SIZE(accton_as5812_54x_leds)){ ++ int j; ++ ++ /* only unregister the LEDs that were successfully registered */ ++ for (j = 0; j < i; j++) { ++ led_classdev_unregister(&accton_as5812_54x_leds[i]); ++ } ++ } ++ ++ return ret; ++} ++ ++static int accton_as5812_54x_led_remove(struct platform_device *pdev) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(accton_as5812_54x_leds); i++) { ++ led_classdev_unregister(&accton_as5812_54x_leds[i]); ++ } ++ ++ return 0; ++} ++ ++static struct platform_driver accton_as5812_54x_led_driver = { ++ .probe = accton_as5812_54x_led_probe, ++ .remove = accton_as5812_54x_led_remove, ++ .suspend = accton_as5812_54x_led_suspend, ++ .resume = accton_as5812_54x_led_resume, ++ .driver = { ++ .name = DRVNAME, ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init accton_as5812_54x_led_init(void) ++{ ++ int ret; ++ ++ extern int platform_accton_as5812_54x(void); ++ if(!platform_accton_as5812_54x()) { ++ return -ENODEV; ++ } ++ ret = platform_driver_register(&accton_as5812_54x_led_driver); ++ if (ret < 0) { ++ goto exit; ++ } ++ ++ ledctl = kzalloc(sizeof(struct accton_as5812_54x_led_data), GFP_KERNEL); ++ if (!ledctl) { ++ ret = -ENOMEM; ++ platform_driver_unregister(&accton_as5812_54x_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_54x_led_driver); ++ kfree(ledctl); ++ goto exit; ++ } ++ ++exit: ++ return ret; ++} ++ ++static void __exit accton_as5812_54x_led_exit(void) ++{ ++ platform_device_unregister(ledctl->pdev); ++ platform_driver_unregister(&accton_as5812_54x_led_driver); ++ kfree(ledctl); ++} ++ ++module_init(accton_as5812_54x_led_init); ++module_exit(accton_as5812_54x_led_exit); ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("accton_as5812_54x_led driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig +index bd435a0..7c8d3b8 100644 +--- a/drivers/misc/eeprom/Kconfig ++++ b/drivers/misc/eeprom/Kconfig +@@ -109,6 +109,15 @@ config EEPROM_ACCTON_AS7712_32x_SFP + This driver can also be built as a module. If so, the module will + be called accton_as7712_32x_sfp. + ++config EEPROM_ACCTON_AS5812_54x_SFP ++ tristate "Accton as5812 54x sfp" ++ depends on I2C && I2C_MUX_ACCTON_AS5812_54x_CPLD ++ help ++ If you say yes here you get support for Accton as5812 54x sfp. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as5812_54x_sfp. ++ + config EEPROM_93CX6 + tristate "EEPROM 93CX6 support" + help +diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile +index 4ad6540..e11d273 100644 +--- a/drivers/misc/eeprom/Makefile ++++ b/drivers/misc/eeprom/Makefile +@@ -10,4 +10,5 @@ obj-$(CONFIG_EEPROM_ACCTON_AS5712_54x_SFP) += accton_as5712_54x_sfp.o + obj-$(CONFIG_EEPROM_ACCTON_AS6712_32x_SFP) += accton_as6712_32x_sfp.o + obj-$(CONFIG_EEPROM_ACCTON_AS7512_32x_SFP) += accton_as7512_32x_sfp.o + obj-$(CONFIG_EEPROM_ACCTON_AS7712_32x_SFP) += accton_as7712_32x_sfp.o ++obj-$(CONFIG_EEPROM_ACCTON_AS5812_54x_SFP) += accton_as5812_54x_sfp.o + obj-$(CONFIG_EEPROM_SFF_8436) += sff_8436_eeprom.o +diff --git a/drivers/misc/eeprom/accton_as5812_54x_sfp.c b/drivers/misc/eeprom/accton_as5812_54x_sfp.c +new file mode 100644 +index 0000000..44727e2 +--- /dev/null ++++ b/drivers/misc/eeprom/accton_as5812_54x_sfp.c +@@ -0,0 +1,508 @@ ++/* ++ * An hwmon driver for accton as5812_54x sfp ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * Based on ad7414.c ++ * Copyright 2006 Stefan Roese , 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define NUM_OF_SFP_PORT 54 ++#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_54x_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 */ ++ u64 status[4]; /* bit0:port0, bit1:port1 and so on */ ++ /* index 0 => is_present ++ 1 => tx_fail ++ 2 => tx_disable ++ 3 => rx_loss */ ++}; ++ ++/* The table maps active port to cpld port. ++ * Array index 0 is for active port 1, ++ * index 1 for active port 2, and so on. ++ * The array content implies cpld port index. ++ */ ++static const u8 cpld_to_front_port_table[] = ++{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, ++ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ++ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, ++ 49, 52, 50, 53, 51, 54}; ++ ++#define CPLD_PORT_TO_FRONT_PORT(port) (cpld_to_front_port_table[port]) ++ ++static struct as5812_54x_sfp_data *as5812_54x_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); ++static ssize_t set_tx_disable(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count); ++extern int as5812_54x_i2c_cpld_read(unsigned short cpld_addr, u8 reg); ++extern int as5812_54x_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value); ++ ++enum as5812_54x_sfp_sysfs_attributes { ++ SFP_IS_PRESENT, ++ SFP_TX_FAULT, ++ SFP_TX_DISABLE, ++ SFP_RX_LOSS, ++ SFP_PORT_NUMBER, ++ SFP_EEPROM, ++ SFP_RX_LOS_ALL, ++ 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_tx_fault, S_IRUGO, show_status, NULL, SFP_TX_FAULT); ++static SENSOR_DEVICE_ATTR(sfp_tx_disable, S_IWUSR | S_IRUGO, show_status, set_tx_disable, SFP_TX_DISABLE); ++static SENSOR_DEVICE_ATTR(sfp_rx_loss, S_IRUGO, show_status,NULL, SFP_RX_LOSS); ++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_rx_los_all, S_IRUGO, show_status,NULL, SFP_RX_LOS_ALL); ++static SENSOR_DEVICE_ATTR(sfp_is_present_all, S_IRUGO, show_status,NULL, SFP_IS_PRESENT_ALL); ++ ++static struct attribute *as5812_54x_sfp_attributes[] = { ++ &sensor_dev_attr_sfp_is_present.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_fault.dev_attr.attr, ++ &sensor_dev_attr_sfp_rx_loss.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_disable.dev_attr.attr, ++ &sensor_dev_attr_sfp_eeprom.dev_attr.attr, ++ &sensor_dev_attr_sfp_port_number.dev_attr.attr, ++ &sensor_dev_attr_sfp_rx_los_all.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_54x_sfp_data *data = i2c_get_clientdata(client); ++ ++ return sprintf(buf, "%d\n", CPLD_PORT_TO_FRONT_PORT(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_54x_sfp_data *data; ++ u8 val; ++ int values[7]; ++ ++ /* Error-check the CPLD read results. */ ++#define VALIDATED_READ(_buf, _rv, _read_expr, _invert) \ ++ do { \ ++ _rv = (_read_expr); \ ++ if(_rv < 0) { \ ++ return sprintf(_buf, "READ ERROR\n"); \ ++ } \ ++ if(_invert) { \ ++ _rv = ~_rv; \ ++ } \ ++ _rv &= 0xFF; \ ++ } while(0) ++ ++ if(attr->index == SFP_RX_LOS_ALL) { ++ /* ++ * Report the RX_LOS status for all ports. ++ * This does not depend on the currently active SFP selector. ++ */ ++ ++ /* RX_LOS Ports 1-8 */ ++ VALIDATED_READ(buf, values[0], as5812_54x_i2c_cpld_read(0x61, 0x0F), 0); ++ /* RX_LOS Ports 9-16 */ ++ VALIDATED_READ(buf, values[1], as5812_54x_i2c_cpld_read(0x61, 0x10), 0); ++ /* RX_LOS Ports 17-24 */ ++ VALIDATED_READ(buf, values[2], as5812_54x_i2c_cpld_read(0x61, 0x11), 0); ++ /* RX_LOS Ports 25-32 */ ++ VALIDATED_READ(buf, values[3], as5812_54x_i2c_cpld_read(0x62, 0x0F), 0); ++ /* RX_LOS Ports 33-40 */ ++ VALIDATED_READ(buf, values[4], as5812_54x_i2c_cpld_read(0x62, 0x10), 0); ++ /* RX_LOS Ports 41-48 */ ++ VALIDATED_READ(buf, values[5], as5812_54x_i2c_cpld_read(0x62, 0x11), 0); ++ ++ /** Return values 1 -> 48 in order */ ++ return sprintf(buf, "%.2x %.2x %.2x %.2x %.2x %.2x\n", ++ values[0], values[1], values[2], ++ values[3], values[4], values[5]); ++ } ++ ++ if(attr->index == SFP_IS_PRESENT_ALL) { ++ /* ++ * Report the SFP_PRESENCE status for all ports. ++ * This does not depend on the currently active SFP selector. ++ */ ++ ++ /* SFP_PRESENT Ports 1-8 */ ++ VALIDATED_READ(buf, values[0], as5812_54x_i2c_cpld_read(0x61, 0x6), 1); ++ /* SFP_PRESENT Ports 9-16 */ ++ VALIDATED_READ(buf, values[1], as5812_54x_i2c_cpld_read(0x61, 0x7), 1); ++ /* SFP_PRESENT Ports 17-24 */ ++ VALIDATED_READ(buf, values[2], as5812_54x_i2c_cpld_read(0x61, 0x8), 1); ++ /* SFP_PRESENT Ports 25-32 */ ++ VALIDATED_READ(buf, values[3], as5812_54x_i2c_cpld_read(0x62, 0x6), 1); ++ /* SFP_PRESENT Ports 33-40 */ ++ VALIDATED_READ(buf, values[4], as5812_54x_i2c_cpld_read(0x62, 0x7), 1); ++ /* SFP_PRESENT Ports 41-48 */ ++ VALIDATED_READ(buf, values[5], as5812_54x_i2c_cpld_read(0x62, 0x8), 1); ++ /* QSFP_PRESENT Ports 49-54 */ ++ VALIDATED_READ(buf, values[6], as5812_54x_i2c_cpld_read(0x62, 0x14), 1); ++ ++ /* Return values 1 -> 54 in order */ ++ return sprintf(buf, "%.2x %.2x %.2x %.2x %.2x %.2x %.2x\n", ++ values[0], values[1], values[2], ++ values[3], values[4], values[5], ++ values[6] & 0x3F); ++ } ++ /* ++ * The remaining attributes are gathered on a per-selected-sfp basis. ++ */ ++ data = as5812_54x_sfp_update_device(dev, 0); ++ if (attr->index == SFP_IS_PRESENT) { ++ val = (data->status[attr->index] & BIT_INDEX(data->port)) ? 0 : 1; ++ } ++ else { ++ val = (data->status[attr->index] & BIT_INDEX(data->port)) ? 1 : 0; ++ } ++ ++ return sprintf(buf, "%d", val); ++} ++ ++static ssize_t set_tx_disable(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct as5812_54x_sfp_data *data = i2c_get_clientdata(client); ++ unsigned short cpld_addr = 0; ++ u8 cpld_reg = 0, cpld_val = 0, cpld_bit = 0; ++ long disable; ++ int error; ++ ++ /* Tx disable is not supported for QSFP ports(49-54) */ ++ if (data->port >= 48) { ++ return -EINVAL; ++ } ++ ++ error = kstrtol(buf, 10, &disable); ++ if (error) { ++ return error; ++ } ++ ++ mutex_lock(&data->update_lock); ++ ++ if(data->port < 24) { ++ cpld_addr = 0x61; ++ cpld_reg = 0xC + data->port / 8; ++ cpld_bit = 1 << (data->port % 8); ++ } ++ else { ++ cpld_addr = 0x62; ++ cpld_reg = 0xC + (data->port - 24) / 8; ++ cpld_bit = 1 << (data->port % 8); ++ } ++ ++ cpld_val = as5812_54x_i2c_cpld_read(cpld_addr, cpld_reg); ++ ++ /* Update tx_disable status */ ++ if (disable) { ++ data->status[SFP_TX_DISABLE] |= BIT_INDEX(data->port); ++ cpld_val |= cpld_bit; ++ } ++ else { ++ data->status[SFP_TX_DISABLE] &= ~BIT_INDEX(data->port); ++ cpld_val &= ~cpld_bit; ++ } ++ ++ as5812_54x_i2c_cpld_write(cpld_addr, cpld_reg, cpld_val); ++ ++ mutex_unlock(&data->update_lock); ++ ++ return count; ++} ++ ++static ssize_t show_eeprom(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct as5812_54x_sfp_data *data = as5812_54x_sfp_update_device(dev, 1); ++ ++ if (!data->valid) { ++ return 0; ++ } ++ ++ if ((data->status[SFP_IS_PRESENT] & BIT_INDEX(data->port)) != 0) { ++ return 0; ++ } ++ ++ memcpy(buf, data->eeprom, sizeof(data->eeprom)); ++ ++ return sizeof(data->eeprom); ++} ++ ++static const struct attribute_group as5812_54x_sfp_group = { ++ .attrs = as5812_54x_sfp_attributes, ++}; ++ ++static int as5812_54x_sfp_probe(struct i2c_client *client, ++ const struct i2c_device_id *dev_id) ++{ ++ struct as5812_54x_sfp_data *data; ++ int status; ++ ++ extern int platform_accton_as5812_54x(void); ++ if(!platform_accton_as5812_54x()) { ++ return -ENODEV; ++ } ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { ++ status = -EIO; ++ goto exit; ++ } ++ ++ data = kzalloc(sizeof(struct as5812_54x_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_54x_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_54x_sfp_group); ++exit_free: ++ kfree(data); ++exit: ++ ++ return status; ++} ++ ++static int as5812_54x_sfp_remove(struct i2c_client *client) ++{ ++ struct as5812_54x_sfp_data *data = i2c_get_clientdata(client); ++ ++ hwmon_device_unregister(data->hwmon_dev); ++ sysfs_remove_group(&client->dev.kobj, &as5812_54x_sfp_group); ++ kfree(data); ++ ++ return 0; ++} ++ ++enum port_numbers { ++as5812_54x_sfp1, as5812_54x_sfp2, as5812_54x_sfp3, as5812_54x_sfp4, ++as5812_54x_sfp5, as5812_54x_sfp6, as5812_54x_sfp7, as5812_54x_sfp8, ++as5812_54x_sfp9, as5812_54x_sfp10, as5812_54x_sfp11,as5812_54x_sfp12, ++as5812_54x_sfp13, as5812_54x_sfp14, as5812_54x_sfp15,as5812_54x_sfp16, ++as5812_54x_sfp17, as5812_54x_sfp18, as5812_54x_sfp19,as5812_54x_sfp20, ++as5812_54x_sfp21, as5812_54x_sfp22, as5812_54x_sfp23,as5812_54x_sfp24, ++as5812_54x_sfp25, as5812_54x_sfp26, as5812_54x_sfp27,as5812_54x_sfp28, ++as5812_54x_sfp29, as5812_54x_sfp30, as5812_54x_sfp31,as5812_54x_sfp32, ++as5812_54x_sfp33, as5812_54x_sfp34, as5812_54x_sfp35,as5812_54x_sfp36, ++as5812_54x_sfp37, as5812_54x_sfp38, as5812_54x_sfp39,as5812_54x_sfp40, ++as5812_54x_sfp41, as5812_54x_sfp42, as5812_54x_sfp43,as5812_54x_sfp44, ++as5812_54x_sfp45, as5812_54x_sfp46, as5812_54x_sfp47,as5812_54x_sfp48, ++as5812_54x_sfp49, as5812_54x_sfp52, as5812_54x_sfp50,as5812_54x_sfp53, ++as5812_54x_sfp51, as5812_54x_sfp54 ++}; ++ ++static const struct i2c_device_id as5812_54x_sfp_id[] = { ++{ "as5812_54x_sfp1", as5812_54x_sfp1 }, { "as5812_54x_sfp2", as5812_54x_sfp2 }, ++{ "as5812_54x_sfp3", as5812_54x_sfp3 }, { "as5812_54x_sfp4", as5812_54x_sfp4 }, ++{ "as5812_54x_sfp5", as5812_54x_sfp5 }, { "as5812_54x_sfp6", as5812_54x_sfp6 }, ++{ "as5812_54x_sfp7", as5812_54x_sfp7 }, { "as5812_54x_sfp8", as5812_54x_sfp8 }, ++{ "as5812_54x_sfp9", as5812_54x_sfp9 }, { "as5812_54x_sfp10", as5812_54x_sfp10 }, ++{ "as5812_54x_sfp11", as5812_54x_sfp11 }, { "as5812_54x_sfp12", as5812_54x_sfp12 }, ++{ "as5812_54x_sfp13", as5812_54x_sfp13 }, { "as5812_54x_sfp14", as5812_54x_sfp14 }, ++{ "as5812_54x_sfp15", as5812_54x_sfp15 }, { "as5812_54x_sfp16", as5812_54x_sfp16 }, ++{ "as5812_54x_sfp17", as5812_54x_sfp17 }, { "as5812_54x_sfp18", as5812_54x_sfp18 }, ++{ "as5812_54x_sfp19", as5812_54x_sfp19 }, { "as5812_54x_sfp20", as5812_54x_sfp20 }, ++{ "as5812_54x_sfp21", as5812_54x_sfp21 }, { "as5812_54x_sfp22", as5812_54x_sfp22 }, ++{ "as5812_54x_sfp23", as5812_54x_sfp23 }, { "as5812_54x_sfp24", as5812_54x_sfp24 }, ++{ "as5812_54x_sfp25", as5812_54x_sfp25 }, { "as5812_54x_sfp26", as5812_54x_sfp26 }, ++{ "as5812_54x_sfp27", as5812_54x_sfp27 }, { "as5812_54x_sfp28", as5812_54x_sfp28 }, ++{ "as5812_54x_sfp29", as5812_54x_sfp29 }, { "as5812_54x_sfp30", as5812_54x_sfp30 }, ++{ "as5812_54x_sfp31", as5812_54x_sfp31 }, { "as5812_54x_sfp32", as5812_54x_sfp32 }, ++{ "as5812_54x_sfp33", as5812_54x_sfp33 }, { "as5812_54x_sfp34", as5812_54x_sfp34 }, ++{ "as5812_54x_sfp35", as5812_54x_sfp35 }, { "as5812_54x_sfp36", as5812_54x_sfp36 }, ++{ "as5812_54x_sfp37", as5812_54x_sfp37 }, { "as5812_54x_sfp38", as5812_54x_sfp38 }, ++{ "as5812_54x_sfp39", as5812_54x_sfp39 }, { "as5812_54x_sfp40", as5812_54x_sfp40 }, ++{ "as5812_54x_sfp41", as5812_54x_sfp41 }, { "as5812_54x_sfp42", as5812_54x_sfp42 }, ++{ "as5812_54x_sfp43", as5812_54x_sfp43 }, { "as5812_54x_sfp44", as5812_54x_sfp44 }, ++{ "as5812_54x_sfp45", as5812_54x_sfp45 }, { "as5812_54x_sfp46", as5812_54x_sfp46 }, ++{ "as5812_54x_sfp47", as5812_54x_sfp47 }, { "as5812_54x_sfp48", as5812_54x_sfp48 }, ++{ "as5812_54x_sfp49", as5812_54x_sfp49 }, { "as5812_54x_sfp50", as5812_54x_sfp50 }, ++{ "as5812_54x_sfp51", as5812_54x_sfp51 }, { "as5812_54x_sfp52", as5812_54x_sfp52 }, ++{ "as5812_54x_sfp53", as5812_54x_sfp53 }, { "as5812_54x_sfp54", as5812_54x_sfp54 }, ++ ++{} ++}; ++MODULE_DEVICE_TABLE(i2c, as5812_54x_sfp_id); ++ ++static struct i2c_driver as5812_54x_sfp_driver = { ++ .class = I2C_CLASS_HWMON, ++ .driver = { ++ .name = "as5812_54x_sfp", ++ }, ++ .probe = as5812_54x_sfp_probe, ++ .remove = as5812_54x_sfp_remove, ++ .id_table = as5812_54x_sfp_id, ++ .address_list = normal_i2c, ++}; ++ ++static int as5812_54x_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; ++} ++ ++#define ALWAYS_UPDATE_DEVICE 1 ++ ++static struct as5812_54x_sfp_data *as5812_54x_sfp_update_device(struct device *dev, int update_eeprom) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct as5812_54x_sfp_data *data = i2c_get_clientdata(client); ++ ++ mutex_lock(&data->update_lock); ++ ++ if (ALWAYS_UPDATE_DEVICE || time_after(jiffies, data->last_updated + HZ + HZ / 2) ++ || !data->valid) { ++ int status = -1; ++ int i = 0, j = 0; ++ ++ data->valid = 0; ++ //dev_dbg(&client->dev, "Starting as5812_54x sfp status update\n"); ++ memset(data->status, 0, sizeof(data->status)); ++ ++ /* Read status of port 1~48(SFP port) */ ++ for (i = 0; i < 2; i++) { ++ for (j = 0; j < 12; j++) { ++ status = as5812_54x_i2c_cpld_read(0x61+i, 0x6+j); ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "cpld(0x%x) reg(0x%x) err %d\n", 0x61+i, 0x6+j, status); ++ goto exit; ++ } ++ ++ data->status[j/3] |= (u64)status << ((i*24) + (j%3)*8); ++ } ++ } ++ ++ /* ++ * Bring QSFPs out of reset, ++ * This is a temporary fix until the QSFP+_MOD_RST register ++ * can be exposed through the driver. ++ */ ++ as5812_54x_i2c_cpld_write(0x62, 0x15, 0x3F); ++ ++ /* Read present status of port 49-54(QSFP port) */ ++ status = as5812_54x_i2c_cpld_read(0x62, 0x14); ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "cpld(0x%x) reg(0x%x) err %d\n", 0x61+i, 0x6+j, status); ++ } ++ else { ++ data->status[SFP_IS_PRESENT] |= (u64)status << 48; ++ } ++ ++ 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[SFP_IS_PRESENT] & BIT_INDEX(data->port)) == 0) { ++ /* read eeprom */ ++ for (i = 0; i < sizeof(data->eeprom); i++) { ++ status = as5812_54x_sfp_read_byte(client, i, data->eeprom + i); ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "unable to read eeprom from port(%d)\n", ++ CPLD_PORT_TO_FRONT_PORT(data->port)); ++ goto exit; ++ } ++ } ++ } ++ } ++ ++ data->valid = 1; ++ data->last_updated = jiffies; ++ } ++ ++exit: ++ mutex_unlock(&data->update_lock); ++ ++ return data; ++} ++ ++module_i2c_driver(as5812_54x_sfp_driver); ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("accton as5812_54x_sfp driver"); ++MODULE_LICENSE("GPL"); diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as6712_32x-device-drivers.patch b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as6712_32x-device-drivers.patch index 3421588b..4b1220f0 100644 --- a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as6712_32x-device-drivers.patch +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as6712_32x-device-drivers.patch @@ -753,9 +753,9 @@ index 0000000..ef9fadf + model_name_len = 13; + } + else { /* 0x50 & 0x53 */ -+ /* ym2651 AC power */ -+ command = 0x20; -+ model_name_len = 8; ++ /* um400d01x DC power */ ++ command = 0x50; ++ model_name_len = 13; + } + + status = as6712_32x_psu_read_block(client,command,data->model_name, diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as6812_32x-device-drivers.patch b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as6812_32x-device-drivers.patch new file mode 100644 index 00000000..3501563d --- /dev/null +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/platform-accton-as6812_32x-device-drivers.patch @@ -0,0 +1,2293 @@ +Device driver patches for accton as6812-32x (fan/psu/cpld/led/sfp) + +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +index 4d9fb22..73ee085 100644 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -1538,6 +1538,24 @@ config SENSORS_ACCTON_AS5812_54x_PSU + This driver can also be built as a module. If so, the module will + be called accton_as5812_54x_psu. + ++config SENSORS_ACCTON_AS6812_32x_FAN ++ tristate "Accton as6812 32x fan" ++ depends on I2C && I2C_MUX_ACCTON_AS6812_32x_CPLD ++ help ++ If you say yes here you get support for Accton as6812 32x fan. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as6812_32x_fan. ++ ++config SENSORS_ACCTON_AS6812_32x_PSU ++ tristate "Accton as6812 32x psu" ++ depends on I2C && I2C_MUX_ACCTON_AS6812_32x_CPLD ++ help ++ If you say yes here you get support for Accton as6812 32x psu. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as6812_32x_psu. ++ + if ACPI + + comment "ACPI drivers" +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +index 818dd01..7700250 100644 +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -32,6 +32,8 @@ obj-$(CONFIG_SENSORS_ACCTON_AS7712_32x_PSU) += accton_as7712_32x_psu.o + obj-$(CONFIG_SENSORS_ACCTON_I2C_CPLD) += accton_i2c_cpld.o + obj-$(CONFIG_SENSORS_ACCTON_AS5812_54x_FAN) += accton_as5812_54x_fan.o + obj-$(CONFIG_SENSORS_ACCTON_AS5812_54x_PSU) += accton_as5812_54x_psu.o ++obj-$(CONFIG_SENSORS_ACCTON_AS6812_32x_FAN) += accton_as6812_32x_fan.o ++obj-$(CONFIG_SENSORS_ACCTON_AS6812_32x_PSU) += accton_as6812_32x_psu.o + obj-$(CONFIG_SENSORS_AD7314) += ad7314.o + obj-$(CONFIG_SENSORS_AD7414) += ad7414.o + obj-$(CONFIG_SENSORS_AD7418) += ad7418.o +diff --git a/drivers/hwmon/accton_as6812_32x_fan.c b/drivers/hwmon/accton_as6812_32x_fan.c +new file mode 100644 +index 0000000..f055567 +--- /dev/null ++++ b/drivers/hwmon/accton_as6812_32x_fan.c +@@ -0,0 +1,434 @@ ++/* ++ * A hwmon driver for the Accton as6812 32x fan contrl ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 0x17 ++#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 DEBUG_MODE 0 ++ ++#if (DEBUG_MODE == 1) ++ #define DEBUG_PRINT(format, ...) printk(format, __VA_ARGS__) ++#else ++ #define DEBUG_PRINT(format, ...) ++#endif ++ ++static struct accton_as6812_32x_fan *fan_data = NULL; ++ ++struct accton_as6812_32x_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_as6812_32x_fan_update_device(struct device *dev); ++static int accton_as6812_32x_fan_read_value(u8 reg); ++static int accton_as6812_32x_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 as6812_32x_i2c_cpld_read(unsigned short cpld_addr, u8 reg); ++extern int as6812_32x_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_as6812_32x_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_as6812_32x_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]); ++ DEBUG_PRINT("[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]); ++ DEBUG_PRINT("[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]); ++ DEBUG_PRINT("[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*/ ++ DEBUG_PRINT("[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]); ++ DEBUG_PRINT("[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]); ++ DEBUG_PRINT("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index); ++ break; ++ default: ++ DEBUG_PRINT("[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_as6812_32x_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_as6812_32x_fan_group = { ++ .attrs = accton_as6812_32x_fan_attributes, ++}; ++ ++static int accton_as6812_32x_fan_read_value(u8 reg) ++{ ++ return as6812_32x_i2c_cpld_read(0x60, reg); ++} ++ ++static int accton_as6812_32x_fan_write_value(u8 reg, u8 value) ++{ ++ return as6812_32x_i2c_cpld_write(0x60, reg, value); ++} ++ ++static void accton_as6812_32x_fan_update_device(struct device *dev) ++{ ++ int speed, r_speed, fault, r_fault, direction, ctrl_speed; ++ int i; ++ ++ mutex_lock(&fan_data->update_lock); ++ ++ DEBUG_PRINT("Starting accton_as6812_32x_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; ++ ++ DEBUG_PRINT("Starting accton_as6812_32x_fan update 2 \n"); ++ ++ fault = accton_as6812_32x_fan_read_value(CPLD_REG_FAN_STATUS_OFFSET); ++ r_fault = accton_as6812_32x_fan_read_value(CPLD_REG_FANR_STATUS_OFFSET); ++ direction = accton_as6812_32x_fan_read_value(CPLD_REG_FAN_DIRECTION_OFFSET); ++ ctrl_speed = accton_as6812_32x_fan_read_value(CPLD_REG_FAN_PWM_CYCLE_OFFSET); ++ ++ if ( (fault < 0) || (r_fault < 0) || (ctrl_speed < 0) ) ++ { ++ DEBUG_PRINT("[Error!!][%s][%d] \n", __FUNCTION__, __LINE__); ++ goto _exit; /* error */ ++ } ++ ++ DEBUG_PRINT("[fan:] fault:%d, r_fault=%d, ctrl_speed=%d \n",fault, r_fault, 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; ++ DEBUG_PRINT("[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_as6812_32x_fan_read_value(fan_speed_reg[i]); ++ r_speed = accton_as6812_32x_fan_read_value(fanr_speed_reg[i]); ++ if ( (speed < 0) || (r_speed < 0) ) ++ { ++ DEBUG_PRINT("[Error!!][%s][%d] \n", __FUNCTION__, __LINE__); ++ goto _exit; /* error */ ++ } ++ ++ DEBUG_PRINT("[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_as6812_32x_fan_probe(struct platform_device *pdev) ++{ ++ int status = -1; ++ ++ /* Register sysfs hooks */ ++ status = sysfs_create_group(&pdev->dev.kobj, &accton_as6812_32x_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_as6812_32x_fan\n"); ++ ++ return 0; ++ ++exit_remove: ++ sysfs_remove_group(&pdev->dev.kobj, &accton_as6812_32x_fan_group); ++exit: ++ return status; ++} ++ ++static int accton_as6812_32x_fan_remove(struct platform_device *pdev) ++{ ++ hwmon_device_unregister(fan_data->hwmon_dev); ++ sysfs_remove_group(&fan_data->pdev->dev.kobj, &accton_as6812_32x_fan_group); ++ ++ return 0; ++} ++ ++#define DRVNAME "as6812_32x_fan" ++ ++static struct platform_driver accton_as6812_32x_fan_driver = { ++ .probe = accton_as6812_32x_fan_probe, ++ .remove = accton_as6812_32x_fan_remove, ++ .driver = { ++ .name = DRVNAME, ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init accton_as6812_32x_fan_init(void) ++{ ++ int ret; ++ ++ extern int platform_accton_as6812_32x(void); ++ if(!platform_accton_as6812_32x()) { ++ return -ENODEV; ++ } ++ ++ ret = platform_driver_register(&accton_as6812_32x_fan_driver); ++ if (ret < 0) { ++ goto exit; ++ } ++ ++ fan_data = kzalloc(sizeof(struct accton_as6812_32x_fan), GFP_KERNEL); ++ if (!fan_data) { ++ ret = -ENOMEM; ++ platform_driver_unregister(&accton_as6812_32x_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_as6812_32x_fan_driver); ++ kfree(fan_data); ++ goto exit; ++ } ++ ++exit: ++ return ret; ++} ++ ++static void __exit accton_as6812_32x_fan_exit(void) ++{ ++ platform_device_unregister(fan_data->pdev); ++ platform_driver_unregister(&accton_as6812_32x_fan_driver); ++ kfree(fan_data); ++} ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("accton_as6812_32x_fan driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(accton_as6812_32x_fan_init); ++module_exit(accton_as6812_32x_fan_exit); ++ +diff --git a/drivers/hwmon/accton_as6812_32x_psu.c b/drivers/hwmon/accton_as6812_32x_psu.c +new file mode 100644 +index 0000000..dfee68b +--- /dev/null ++++ b/drivers/hwmon/accton_as6812_32x_psu.c +@@ -0,0 +1,305 @@ ++/* ++ * An hwmon driver for accton as6812_32x Power Module ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * Based on ad7414.c ++ * Copyright 2006 Stefan Roese , 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++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 as6812_32x_psu_read_block(struct i2c_client *client, u8 command, u8 *data,int data_len); ++extern int as6812_32x_i2c_cpld_read(unsigned short cpld_addr, u8 reg); ++ ++/* Addresses scanned ++ */ ++static const unsigned short normal_i2c[] = { 0x50, 0x53, I2C_CLIENT_END }; ++ ++/* Each client has this additional data ++ */ ++struct as6812_32x_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 as6812_32x_psu_data *as6812_32x_psu_update_device(struct device *dev); ++ ++enum as6812_32x_psu_sysfs_attributes { ++ PSU_PRESENT, ++ PSU_MODEL_NAME, ++ PSU_POWER_GOOD ++}; ++ ++/* sysfs attributes for hwmon ++ */ ++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 *as6812_32x_psu_attributes[] = { ++ &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_status(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ struct as6812_32x_psu_data *data = as6812_32x_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 as6812_32x_psu_data *data = as6812_32x_psu_update_device(dev); ++ ++ return sprintf(buf, "%s\n", data->model_name); ++} ++ ++static const struct attribute_group as6812_32x_psu_group = { ++ .attrs = as6812_32x_psu_attributes, ++}; ++ ++static int as6812_32x_psu_probe(struct i2c_client *client, ++ const struct i2c_device_id *dev_id) ++{ ++ struct as6812_32x_psu_data *data; ++ int status; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) { ++ status = -EIO; ++ goto exit; ++ } ++ ++ data = kzalloc(sizeof(struct as6812_32x_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, &as6812_32x_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 == 0x50 || client->addr == 0x38) { ++ data->index = 1; ++ } ++ else if (client->addr == 0x53 || client->addr == 0x3b) { ++ 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, &as6812_32x_psu_group); ++exit_free: ++ kfree(data); ++exit: ++ ++ return status; ++} ++ ++static int as6812_32x_psu_remove(struct i2c_client *client) ++{ ++ struct as6812_32x_psu_data *data = i2c_get_clientdata(client); ++ ++ hwmon_device_unregister(data->hwmon_dev); ++ sysfs_remove_group(&client->dev.kobj, &as6812_32x_psu_group); ++ kfree(data); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id as6812_32x_psu_id[] = { ++ { "as6812_32x_psu", 0 }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, as6812_32x_psu_id); ++ ++static struct i2c_driver as6812_32x_psu_driver = { ++ .class = I2C_CLASS_HWMON, ++ .driver = { ++ .name = "as6812_32x_psu", ++ }, ++ .probe = as6812_32x_psu_probe, ++ .remove = as6812_32x_psu_remove, ++ .id_table = as6812_32x_psu_id, ++ .address_list = normal_i2c, ++}; ++ ++static int as6812_32x_psu_read_block(struct i2c_client *client, u8 command, u8 *data, ++ int data_len) ++{ ++ int result = 0; ++ int retry_count = 5; ++ ++ while (retry_count) { ++ retry_count--; ++ ++ result = i2c_smbus_read_i2c_block_data(client, command, data_len, data); ++ ++ if (unlikely(result < 0)) { ++ msleep(10); ++ continue; ++ } ++ ++ if (unlikely(result != data_len)) { ++ result = -EIO; ++ msleep(10); ++ continue; ++ } ++ ++ result = 0; ++ break; ++ } ++ ++ return result; ++} ++ ++static struct as6812_32x_psu_data *as6812_32x_psu_update_device(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct as6812_32x_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; ++ int present = 0; ++ ++ dev_dbg(&client->dev, "Starting as6812_32x update\n"); ++ ++ /* Read psu status */ ++ status = as6812_32x_i2c_cpld_read(0x60, 0x2); ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "cpld reg 0x60 err %d\n", status); ++ } ++ else { ++ data->status = status; ++ } ++ ++ /* Read model name */ ++ memset(data->model_name, 0, sizeof(data->model_name)); ++ present = !(data->status >> ((data->index-1)*4) & 0x1); ++ ++ if (present) { ++ u8 command; ++ int model_name_len = 0; ++ ++ if (client->addr == 0x38 || client->addr == 0x3b) { ++ /* cpr_4011_4mxx AC power */ ++ command = 0x26; ++ model_name_len = 13; ++ } ++ else { /* 0x50 & 0x53 */ ++ /* ym2651 AC power */ ++ command = 0x20; ++ model_name_len = 8; ++ } ++ ++ status = as6812_32x_psu_read_block(client,command,data->model_name, ++ model_name_len); ++ ++ 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[model_name_len] = '\0'; ++ } ++ } ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++ mutex_unlock(&data->update_lock); ++ ++ return data; ++} ++ ++static int __init as6812_32x_psu_init(void) ++{ ++ extern int platform_accton_as6812_32x(void); ++ if(!platform_accton_as6812_32x()) { ++ return -ENODEV; ++ } ++ ++ return i2c_add_driver(&as6812_32x_psu_driver); ++} ++ ++static void __exit as6812_32x_psu_exit(void) ++{ ++ i2c_del_driver(&as6812_32x_psu_driver); ++} ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("as6812_32x_psu driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(as6812_32x_psu_init); ++module_exit(as6812_32x_psu_exit); +diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig +index 8ac67ef..b378ade 100644 +--- a/drivers/i2c/muxes/Kconfig ++++ b/drivers/i2c/muxes/Kconfig +@@ -33,6 +33,15 @@ config I2C_MUX_ACCTON_AS5812_54x_CPLD + This driver can also be built as a module. If so, the module + will be called i2c-mux-accton_as5812_54x_cpld. + ++config I2C_MUX_ACCTON_AS6812_32x_CPLD ++ tristate "Accton as6812_32x CPLD I2C multiplexer" ++ help ++ If you say yes here you get support for the Accton CPLD ++ I2C mux devices. ++ ++ This driver can also be built as a module. If so, the module ++ will be called i2c-mux-accton_as6812_32x_cpld. ++ + config I2C_MUX_GPIO + tristate "GPIO-based I2C multiplexer" + depends on GENERIC_GPIO +diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile +index 7769d29..840a66c 100644 +--- a/drivers/i2c/muxes/Makefile ++++ b/drivers/i2c/muxes/Makefile +@@ -10,5 +10,6 @@ obj-$(CONFIG_I2C_MUX_QUANTA_LY2) += quanta-ly2-i2c-mux.o + obj-$(CONFIG_I2C_MUX_ACCTON_AS5712_54x_CPLD) += i2c-mux-accton_as5712_54x_cpld.o + obj-$(CONFIG_I2C_MUX_ACCTON_AS6712_32x_CPLD) += i2c-mux-accton_as6712_32x_cpld.o + obj-$(CONFIG_I2C_MUX_ACCTON_AS5812_54x_CPLD) += i2c-mux-accton_as5812_54x_cpld.o ++obj-$(CONFIG_I2C_MUX_ACCTON_AS6812_32x_CPLD) += i2c-mux-accton_as6812_32x_cpld.o + + ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG +diff --git a/drivers/i2c/muxes/i2c-mux-accton_as6812_32x_cpld.c b/drivers/i2c/muxes/i2c-mux-accton_as6812_32x_cpld.c +new file mode 100644 +index 0000000..d668ca4 +--- /dev/null ++++ b/drivers/i2c/muxes/i2c-mux-accton_as6812_32x_cpld.c +@@ -0,0 +1,382 @@ ++/* ++ * I2C multiplexer for accton as6812 CPLD ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * This module supports the accton cpld that hold the channel select ++ * mechanism for other i2c slave devices, such as SFP. ++ * This includes the: ++ * Accton as6812_32x CPLD1/CPLD2/CPLD3 ++ * ++ * Based on: ++ * pca954x.c from Kumar Gala ++ * Copyright (C) 2006 ++ * ++ * Based on: ++ * pca954x.c from Ken Harrenstien ++ * Copyright (C) 2004 Google, Inc. (Ken Harrenstien) ++ * ++ * Based on: ++ * i2c-virtual_cb.c from Brian Kuschak ++ * and ++ * pca9540.c from Jean Delvare . ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static struct dmi_system_id as6812_dmi_table[] = { ++ { ++ .ident = "Accton AS6812", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Accton"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "AS6812"), ++ }, ++ } ++}; ++ ++int platform_accton_as6812_32x(void) ++{ ++ return dmi_check_system(as6812_dmi_table); ++} ++EXPORT_SYMBOL(platform_accton_as6812_32x); ++ ++#define NUM_OF_CPLD1_CHANS 0x0 ++#define NUM_OF_CPLD2_CHANS 0x10 ++#define NUM_OF_CPLD3_CHANS 0x10 ++#define NUM_OF_ALL_CPLD_CHANS (NUM_OF_CPLD2_CHANS + NUM_OF_CPLD3_CHANS) ++#define ACCTON_I2C_CPLD_MUX_MAX_NCHANS NUM_OF_CPLD3_CHANS ++ ++static LIST_HEAD(cpld_client_list); ++static struct mutex list_lock; ++ ++struct cpld_client_node { ++ struct i2c_client *client; ++ struct list_head list; ++}; ++ ++enum cpld_mux_type { ++ as6812_32x_cpld2, ++ as6812_32x_cpld3, ++ as6812_32x_cpld1 ++}; ++ ++struct accton_i2c_cpld_mux { ++ enum cpld_mux_type type; ++ struct i2c_adapter *virt_adaps[ACCTON_I2C_CPLD_MUX_MAX_NCHANS]; ++ u8 last_chan; /* last register value */ ++}; ++ ++struct chip_desc { ++ u8 nchans; ++ u8 deselectChan; ++}; ++ ++/* Provide specs for the PCA954x types we know about */ ++static const struct chip_desc chips[] = { ++ [as6812_32x_cpld1] = { ++ .nchans = NUM_OF_CPLD1_CHANS, ++ .deselectChan = NUM_OF_CPLD1_CHANS, ++ }, ++ [as6812_32x_cpld2] = { ++ .nchans = NUM_OF_CPLD2_CHANS, ++ .deselectChan = NUM_OF_CPLD2_CHANS, ++ }, ++ [as6812_32x_cpld3] = { ++ .nchans = NUM_OF_CPLD3_CHANS, ++ .deselectChan = NUM_OF_CPLD3_CHANS, ++ } ++}; ++ ++static const struct i2c_device_id accton_i2c_cpld_mux_id[] = { ++ { "as6812_32x_cpld1", as6812_32x_cpld1 }, ++ { "as6812_32x_cpld2", as6812_32x_cpld2 }, ++ { "as6812_32x_cpld3", as6812_32x_cpld3 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, accton_i2c_cpld_mux_id); ++ ++/* Write to mux register. Don't use i2c_transfer()/i2c_smbus_xfer() ++ for this as they will try to lock adapter a second time */ ++static int accton_i2c_cpld_mux_reg_write(struct i2c_adapter *adap, ++ struct i2c_client *client, u8 val) ++{ ++ unsigned long orig_jiffies; ++ unsigned short flags; ++ union i2c_smbus_data data; ++ int try; ++ s32 res = -EIO; ++ ++ data.byte = val; ++ flags = client->flags; ++ flags &= I2C_M_TEN | I2C_CLIENT_PEC; ++ ++ if (adap->algo->smbus_xfer) { ++ /* Retry automatically on arbitration loss */ ++ orig_jiffies = jiffies; ++ for (res = 0, try = 0; try <= adap->retries; try++) { ++ res = adap->algo->smbus_xfer(adap, client->addr, flags, ++ I2C_SMBUS_WRITE, 0x2, ++ I2C_SMBUS_BYTE_DATA, &data); ++ if (res != -EAGAIN) ++ break; ++ if (time_after(jiffies, ++ orig_jiffies + adap->timeout)) ++ break; ++ } ++ } ++ ++ return res; ++} ++ ++static int accton_i2c_cpld_mux_select_chan(struct i2c_adapter *adap, ++ void *client, u32 chan) ++{ ++ struct accton_i2c_cpld_mux *data = i2c_get_clientdata(client); ++ u8 regval; ++ int ret = 0; ++ regval = chan; ++ ++ /* Only select the channel if its different from the last channel */ ++ if (data->last_chan != regval) { ++ ret = accton_i2c_cpld_mux_reg_write(adap, client, regval); ++ data->last_chan = regval; ++ } ++ ++ return ret; ++} ++ ++static int accton_i2c_cpld_mux_deselect_mux(struct i2c_adapter *adap, ++ void *client, u32 chan) ++{ ++ struct accton_i2c_cpld_mux *data = i2c_get_clientdata(client); ++ ++ /* Deselect active channel */ ++ data->last_chan = chips[data->type].deselectChan; ++ ++ return accton_i2c_cpld_mux_reg_write(adap, client, data->last_chan); ++} ++ ++static void accton_i2c_cpld_add_client(struct i2c_client *client) ++{ ++ struct cpld_client_node *node = kzalloc(sizeof(struct cpld_client_node), GFP_KERNEL); ++ ++ if (!node) { ++ dev_dbg(&client->dev, "Can't allocate cpld_client_node (0x%x)\n", client->addr); ++ return; ++ } ++ ++ node->client = client; ++ ++ mutex_lock(&list_lock); ++ list_add(&node->list, &cpld_client_list); ++ mutex_unlock(&list_lock); ++} ++ ++static void accton_i2c_cpld_remove_client(struct i2c_client *client) ++{ ++ struct list_head *list_node = NULL; ++ struct cpld_client_node *cpld_node = NULL; ++ int found = 0; ++ ++ mutex_lock(&list_lock); ++ ++ list_for_each(list_node, &cpld_client_list) ++ { ++ cpld_node = list_entry(list_node, struct cpld_client_node, list); ++ ++ if (cpld_node->client == client) { ++ found = 1; ++ break; ++ } ++ } ++ ++ if (found) { ++ list_del(list_node); ++ kfree(cpld_node); ++ } ++ ++ mutex_unlock(&list_lock); ++} ++ ++static ssize_t show_cpld_version(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ u8 reg = 0x1; ++ struct i2c_client *client; ++ int len; ++ ++ client = to_i2c_client(dev); ++ len = sprintf(buf, "%d", i2c_smbus_read_byte_data(client, reg)); ++ ++ return len; ++} ++ ++static struct device_attribute ver = __ATTR(version, 0600, show_cpld_version, NULL); ++ ++/* ++ * I2C init/probing/exit functions ++ */ ++static int accton_i2c_cpld_mux_probe(struct i2c_client *client, ++ const struct i2c_device_id *id) ++{ ++ struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); ++ int chan=0; ++ struct accton_i2c_cpld_mux *data; ++ int ret = -ENODEV; ++ ++ if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE)) ++ goto err; ++ ++ data = kzalloc(sizeof(struct accton_i2c_cpld_mux), GFP_KERNEL); ++ if (!data) { ++ ret = -ENOMEM; ++ goto err; ++ } ++ ++ i2c_set_clientdata(client, data); ++ ++ data->type = id->driver_data; ++ ++ if (data->type == as6812_32x_cpld2 || data->type == as6812_32x_cpld3) { ++ data->last_chan = chips[data->type].deselectChan; /* force the first selection */ ++ ++ /* Now create an adapter for each channel */ ++ for (chan = 0; chan < chips[data->type].nchans; chan++) { ++ data->virt_adaps[chan] = i2c_add_mux_adapter(adap, &client->dev, client, 0, chan, ++ accton_i2c_cpld_mux_select_chan, ++ accton_i2c_cpld_mux_deselect_mux); ++ ++ if (data->virt_adaps[chan] == NULL) { ++ ret = -ENODEV; ++ dev_err(&client->dev, "failed to register multiplexed adapter %d\n", chan); ++ goto virt_reg_failed; ++ } ++ } ++ ++ dev_info(&client->dev, "registered %d multiplexed busses for I2C mux %s\n", ++ chan, client->name); ++ } ++ ++ accton_i2c_cpld_add_client(client); ++ ++ ret = sysfs_create_file(&client->dev.kobj, &ver.attr); ++ if (ret) ++ goto virt_reg_failed; ++ ++ return 0; ++ ++virt_reg_failed: ++ for (chan--; chan >= 0; chan--) { ++ i2c_del_mux_adapter(data->virt_adaps[chan]); ++ } ++ kfree(data); ++err: ++ return ret; ++} ++ ++static int accton_i2c_cpld_mux_remove(struct i2c_client *client) ++{ ++ struct accton_i2c_cpld_mux *data = i2c_get_clientdata(client); ++ const struct chip_desc *chip = &chips[data->type]; ++ int chan; ++ ++ sysfs_remove_file(&client->dev.kobj, &ver.attr); ++ ++ for (chan = 0; chan < chip->nchans; ++chan) { ++ if (data->virt_adaps[chan]) { ++ i2c_del_mux_adapter(data->virt_adaps[chan]); ++ data->virt_adaps[chan] = NULL; ++ } ++ } ++ ++ kfree(data); ++ accton_i2c_cpld_remove_client(client); ++ ++ return 0; ++} ++ ++int as6812_32x_i2c_cpld_read(unsigned short cpld_addr, u8 reg) ++{ ++ struct list_head *list_node = NULL; ++ struct cpld_client_node *cpld_node = NULL; ++ int ret = -EPERM; ++ ++ mutex_lock(&list_lock); ++ ++ list_for_each(list_node, &cpld_client_list) ++ { ++ cpld_node = list_entry(list_node, struct cpld_client_node, list); ++ ++ if (cpld_node->client->addr == cpld_addr) { ++ ret = i2c_smbus_read_byte_data(cpld_node->client, reg); ++ break; ++ } ++ } ++ ++ mutex_unlock(&list_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL(as6812_32x_i2c_cpld_read); ++ ++int as6812_32x_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value) ++{ ++ struct list_head *list_node = NULL; ++ struct cpld_client_node *cpld_node = NULL; ++ int ret = -EIO; ++ ++ mutex_lock(&list_lock); ++ ++ list_for_each(list_node, &cpld_client_list) ++ { ++ cpld_node = list_entry(list_node, struct cpld_client_node, list); ++ ++ if (cpld_node->client->addr == cpld_addr) { ++ ret = i2c_smbus_write_byte_data(cpld_node->client, reg, value); ++ break; ++ } ++ } ++ ++ mutex_unlock(&list_lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL(as6812_32x_i2c_cpld_write); ++ ++static struct i2c_driver accton_i2c_cpld_mux_driver = { ++ .driver = { ++ .name = "as6812_32x_cpld", ++ .owner = THIS_MODULE, ++ }, ++ .probe = accton_i2c_cpld_mux_probe, ++ .remove = accton_i2c_cpld_mux_remove, ++ .id_table = accton_i2c_cpld_mux_id, ++}; ++ ++static int __init accton_i2c_cpld_mux_init(void) ++{ ++ mutex_init(&list_lock); ++ return i2c_add_driver(&accton_i2c_cpld_mux_driver); ++} ++ ++static void __exit accton_i2c_cpld_mux_exit(void) ++{ ++ i2c_del_driver(&accton_i2c_cpld_mux_driver); ++} ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("Accton I2C CPLD mux driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(accton_i2c_cpld_mux_init); ++module_exit(accton_i2c_cpld_mux_exit); +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index 514f978..cb0c17f 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -75,6 +75,13 @@ config LEDS_ACCTON_AS5812_54x + This option enables support for the LEDs on the Accton as5812 54x. + Say Y to enable LEDs on the Accton as5812 54x. + ++config LEDS_ACCTON_AS6812_32x ++ tristate "LED support for the Accton as6812 32x" ++ depends on LEDS_CLASS && I2C_MUX_ACCTON_AS6812_32x_CPLD ++ help ++ This option enables support for the LEDs on the Accton as6812 32x. ++ Say Y to enable LEDs on the Accton as6812 32x. ++ + config LEDS_LM3530 + tristate "LCD Backlight driver for LM3530" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 379c448..8db7a43 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -48,6 +48,7 @@ obj-$(CONFIG_LEDS_ACCTON_AS6712_32x) += leds-accton_as6712_32x.o + obj-$(CONFIG_LEDS_ACCTON_AS7512_32x) += leds-accton_as7512_32x.o + obj-$(CONFIG_LEDS_ACCTON_AS7712_32x) += leds-accton_as7712_32x.o + obj-$(CONFIG_LEDS_ACCTON_AS5812_54x) += leds-accton_as5812_54x.o ++obj-$(CONFIG_LEDS_ACCTON_AS6812_32x) += leds-accton_as6812_32x.o + + # LED SPI Drivers + obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o +diff --git a/drivers/leds/leds-accton_as6812_32x.c b/drivers/leds/leds-accton_as6812_32x.c +new file mode 100644 +index 0000000..59c59cb +--- /dev/null ++++ b/drivers/leds/leds-accton_as6812_32x.c +@@ -0,0 +1,617 @@ ++/* ++ * A LED driver for the accton_as6812_32x_led ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * 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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++extern int as6812_32x_i2c_cpld_read (unsigned short cpld_addr, u8 reg); ++extern int as6812_32x_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 "as6812_32x_led" ++ ++struct accton_as6812_32x_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_as6812_32x_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_MODE_DIAG_BLINK_MASK 0x48 ++ ++#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 0xC0 ++#define LED_TYPE_FAN3_REG_MASK 0x30 ++#define LED_TYPE_FAN4_REG_MASK 0x0C ++#define LED_TYPE_FAN5_REG_MASK 0x03 ++ ++#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 */ ++ 0xE, /* FAN2-5 LED */ ++ 0xF, /* FAN1 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_AMBER, ++ LED_MODE_RED, ++ LED_MODE_GREEN_BLINK, ++ LED_MODE_AMBER_BLINK, ++ LED_MODE_RED_BLINK, ++ LED_MODE_AUTO, ++}; ++ ++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 << 6}, ++{LED_TYPE_FAN2, LED_TYPE_FAN2_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 6}, ++{LED_TYPE_FAN2, LED_TYPE_FAN2_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 6}, ++{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 << 2}, ++{LED_TYPE_FAN4, LED_TYPE_FAN4_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 2}, ++{LED_TYPE_FAN4, LED_TYPE_FAN4_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 2}, ++{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_DIAG, LED_TYPE_DIAG_REG_MASK, LED_MODE_GREEN_BLINK, LED_MODE_DIAG_BLINK_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, 3}, ++ {'2', LED_TYPE_FAN2, 2}, ++ {'3', LED_TYPE_FAN3, 2}, ++ {'4', LED_TYPE_FAN4, 2}, ++ {'5', LED_TYPE_FAN5, 2}, ++}; ++ ++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 (type == LED_TYPE_DIAG) ++ { /* special case : bit 6 - meaning blinking */ ++ if (0x40 & reg_val) ++ return LED_MODE_GREEN_BLINK; ++ } ++ 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 0; ++} ++ ++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; ++ ++ if (type == LED_TYPE_DIAG) ++ { ++ if (mode == LED_MODE_GREEN_BLINK) ++ { /* special case : bit 6 - meaning blinking */ ++ reg_val = 0x48 | (reg_val & ~0x4C); ++ break; ++ } ++ else ++ { /* for diag led, other case must cancel bit 6 first */ ++ reg_val = reg_val & ~0x40; ++ } ++ } ++ reg_val = led_type_mode_data[i].mode_mask | ++ (reg_val & (~led_type_mode_data[i].type_mask)); ++ break; ++ } ++ ++ return reg_val; ++} ++ ++static int accton_as6812_32x_led_read_value(u8 reg) ++{ ++ return as6812_32x_i2c_cpld_read(0x60, reg); ++} ++ ++static int accton_as6812_32x_led_write_value(u8 reg, u8 value) ++{ ++ return as6812_32x_i2c_cpld_write(0x60, reg, value); ++} ++ ++static void accton_as6812_32x_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_as6812_32x_led update\n"); ++ ++ /* Update LED data ++ */ ++ for (i = 0; i < ARRAY_SIZE(ledctl->reg_val); i++) { ++ int status = accton_as6812_32x_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_as6812_32x_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_as6812_32x_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_as6812_32x_led_write_value(reg, reg_val); ++ ++ /* to prevent the slow-update issue */ ++ ledctl->valid = 0; ++ ++exit: ++ mutex_unlock(&ledctl->update_lock); ++} ++ ++static void accton_as6812_32x_led_psu_1_set(struct led_classdev *led_cdev, ++ enum led_brightness led_light_mode) ++{ ++ accton_as6812_32x_led_set(led_cdev, led_light_mode, led_reg[1], LED_TYPE_PSU1); ++} ++ ++static enum led_brightness accton_as6812_32x_led_psu_1_get(struct led_classdev *cdev) ++{ ++ accton_as6812_32x_led_update(); ++ return led_reg_val_to_light_mode(LED_TYPE_PSU1, ledctl->reg_val[1]); ++} ++ ++static void accton_as6812_32x_led_psu_2_set(struct led_classdev *led_cdev, ++ enum led_brightness led_light_mode) ++{ ++ accton_as6812_32x_led_set(led_cdev, led_light_mode, led_reg[1], LED_TYPE_PSU2); ++} ++ ++static enum led_brightness accton_as6812_32x_led_psu_2_get(struct led_classdev *cdev) ++{ ++ accton_as6812_32x_led_update(); ++ return led_reg_val_to_light_mode(LED_TYPE_PSU2, ledctl->reg_val[1]); ++} ++ ++static void accton_as6812_32x_led_fan_set(struct led_classdev *led_cdev, ++ enum led_brightness led_light_mode) ++{ ++ accton_as6812_32x_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_FAN); ++} ++ ++static enum led_brightness accton_as6812_32x_led_fan_get(struct led_classdev *cdev) ++{ ++ accton_as6812_32x_led_update(); ++ return led_reg_val_to_light_mode(LED_TYPE_FAN, ledctl->reg_val[0]); ++} ++ ++ ++static void accton_as6812_32x_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;iname); ++ ++ if (led_cdev->name[nsize-1] == fanx_info[i].cname) ++ { ++ led_type1 = fanx_info[i].type; ++ reg_id = fanx_info[i].reg_id; ++ accton_as6812_32x_led_set(led_cdev, led_light_mode, led_reg[reg_id], led_type1); ++ return; ++ } ++ } ++} ++ ++ ++static enum led_brightness accton_as6812_32x_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;iname); ++ ++ if (cdev->name[nsize-1] == fanx_info[i].cname) ++ { ++ led_type1 = fanx_info[i].type; ++ reg_id = fanx_info[i].reg_id; ++ accton_as6812_32x_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_as6812_32x_led_diag_set(struct led_classdev *led_cdev, ++ enum led_brightness led_light_mode) ++{ ++ accton_as6812_32x_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_DIAG); ++} ++ ++static enum led_brightness accton_as6812_32x_led_diag_get(struct led_classdev *cdev) ++{ ++ accton_as6812_32x_led_update(); ++ return led_reg_val_to_light_mode(LED_TYPE_DIAG, ledctl->reg_val[0]); ++} ++ ++static void accton_as6812_32x_led_loc_set(struct led_classdev *led_cdev, ++ enum led_brightness led_light_mode) ++{ ++ accton_as6812_32x_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_LOC); ++} ++ ++static enum led_brightness accton_as6812_32x_led_loc_get(struct led_classdev *cdev) ++{ ++ accton_as6812_32x_led_update(); ++ return led_reg_val_to_light_mode(LED_TYPE_LOC, ledctl->reg_val[0]); ++} ++ ++static struct led_classdev accton_as6812_32x_leds[] = { ++ [LED_TYPE_PSU1] = { ++ .name = "accton_as6812_32x_led::psu1", ++ .default_trigger = "unused", ++ .brightness_set = accton_as6812_32x_led_psu_1_set, ++ .brightness_get = accton_as6812_32x_led_psu_1_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_PSU2] = { ++ .name = "accton_as6812_32x_led::psu2", ++ .default_trigger = "unused", ++ .brightness_set = accton_as6812_32x_led_psu_2_set, ++ .brightness_get = accton_as6812_32x_led_psu_2_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN] = { ++ .name = "accton_as6812_32x_led::fan", ++ .default_trigger = "unused", ++ .brightness_set = accton_as6812_32x_led_fan_set, ++ .brightness_get = accton_as6812_32x_led_fan_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN1] = { ++ .name = "accton_as6812_32x_led::fan1", ++ .default_trigger = "unused", ++ .brightness_set = accton_as6812_32x_led_fanx_set, ++ .brightness_get = accton_as6812_32x_led_fanx_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN2] = { ++ .name = "accton_as6812_32x_led::fan2", ++ .default_trigger = "unused", ++ .brightness_set = accton_as6812_32x_led_fanx_set, ++ .brightness_get = accton_as6812_32x_led_fanx_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN3] = { ++ .name = "accton_as6812_32x_led::fan3", ++ .default_trigger = "unused", ++ .brightness_set = accton_as6812_32x_led_fanx_set, ++ .brightness_get = accton_as6812_32x_led_fanx_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN4] = { ++ .name = "accton_as6812_32x_led::fan4", ++ .default_trigger = "unused", ++ .brightness_set = accton_as6812_32x_led_fanx_set, ++ .brightness_get = accton_as6812_32x_led_fanx_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_FAN5] = { ++ .name = "accton_as6812_32x_led::fan5", ++ .default_trigger = "unused", ++ .brightness_set = accton_as6812_32x_led_fanx_set, ++ .brightness_get = accton_as6812_32x_led_fanx_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_DIAG] = { ++ .name = "accton_as6812_32x_led::diag", ++ .default_trigger = "unused", ++ .brightness_set = accton_as6812_32x_led_diag_set, ++ .brightness_get = accton_as6812_32x_led_diag_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++ [LED_TYPE_LOC] = { ++ .name = "accton_as6812_32x_led::loc", ++ .default_trigger = "unused", ++ .brightness_set = accton_as6812_32x_led_loc_set, ++ .brightness_get = accton_as6812_32x_led_loc_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO, ++ }, ++}; ++ ++static int accton_as6812_32x_led_suspend(struct platform_device *dev, ++ pm_message_t state) ++{ ++ int i = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(accton_as6812_32x_leds); i++) { ++ led_classdev_suspend(&accton_as6812_32x_leds[i]); ++ } ++ ++ return 0; ++} ++ ++static int accton_as6812_32x_led_resume(struct platform_device *dev) ++{ ++ int i = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(accton_as6812_32x_leds); i++) { ++ led_classdev_resume(&accton_as6812_32x_leds[i]); ++ } ++ ++ return 0; ++} ++ ++static int accton_as6812_32x_led_probe(struct platform_device *pdev) ++{ ++ int ret, i; ++ ++ for (i = 0; i < ARRAY_SIZE(accton_as6812_32x_leds); i++) { ++ ret = led_classdev_register(&pdev->dev, &accton_as6812_32x_leds[i]); ++ ++ if (ret < 0) ++ break; ++ } ++ ++ /* Check if all LEDs were successfully registered */ ++ if (i != ARRAY_SIZE(accton_as6812_32x_leds)){ ++ int j; ++ ++ /* only unregister the LEDs that were successfully registered */ ++ for (j = 0; j < i; j++) { ++ led_classdev_unregister(&accton_as6812_32x_leds[i]); ++ } ++ } ++ ++ return ret; ++} ++ ++static int accton_as6812_32x_led_remove(struct platform_device *pdev) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(accton_as6812_32x_leds); i++) { ++ led_classdev_unregister(&accton_as6812_32x_leds[i]); ++ } ++ ++ return 0; ++} ++ ++static struct platform_driver accton_as6812_32x_led_driver = { ++ .probe = accton_as6812_32x_led_probe, ++ .remove = accton_as6812_32x_led_remove, ++ .suspend = accton_as6812_32x_led_suspend, ++ .resume = accton_as6812_32x_led_resume, ++ .driver = { ++ .name = DRVNAME, ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init accton_as6812_32x_led_init(void) ++{ ++ int ret; ++ ++ extern int platform_accton_as6812_32x(void); ++ if(!platform_accton_as6812_32x()) { ++ return -ENODEV; ++ } ++ ++ ret = platform_driver_register(&accton_as6812_32x_led_driver); ++ if (ret < 0) { ++ goto exit; ++ } ++ ++ ledctl = kzalloc(sizeof(struct accton_as6812_32x_led_data), GFP_KERNEL); ++ if (!ledctl) { ++ ret = -ENOMEM; ++ platform_driver_unregister(&accton_as6812_32x_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_as6812_32x_led_driver); ++ kfree(ledctl); ++ goto exit; ++ } ++ ++exit: ++ return ret; ++} ++ ++static void __exit accton_as6812_32x_led_exit(void) ++{ ++ platform_device_unregister(ledctl->pdev); ++ platform_driver_unregister(&accton_as6812_32x_led_driver); ++ kfree(ledctl); ++} ++ ++module_init(accton_as6812_32x_led_init); ++module_exit(accton_as6812_32x_led_exit); ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("accton_as6812_32x_led driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig +index 7c8d3b8..ff68df7 100644 +--- a/drivers/misc/eeprom/Kconfig ++++ b/drivers/misc/eeprom/Kconfig +@@ -118,6 +118,15 @@ config EEPROM_ACCTON_AS5812_54x_SFP + This driver can also be built as a module. If so, the module will + be called accton_as5812_54x_sfp. + ++config EEPROM_ACCTON_AS6812_32x_SFP ++ tristate "Accton as6812 32x sfp" ++ depends on I2C && I2C_MUX_ACCTON_AS6812_32x_CPLD ++ help ++ If you say yes here you get support for Accton as6812 32x sfp. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as6812_32x_sfp. ++ + config EEPROM_93CX6 + tristate "EEPROM 93CX6 support" + help +diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile +index e11d273..4b682a1 100644 +--- a/drivers/misc/eeprom/Makefile ++++ b/drivers/misc/eeprom/Makefile +@@ -11,4 +11,5 @@ obj-$(CONFIG_EEPROM_ACCTON_AS6712_32x_SFP) += accton_as6712_32x_sfp.o + obj-$(CONFIG_EEPROM_ACCTON_AS7512_32x_SFP) += accton_as7512_32x_sfp.o + obj-$(CONFIG_EEPROM_ACCTON_AS7712_32x_SFP) += accton_as7712_32x_sfp.o + obj-$(CONFIG_EEPROM_ACCTON_AS5812_54x_SFP) += accton_as5812_54x_sfp.o ++obj-$(CONFIG_EEPROM_ACCTON_AS6812_32x_SFP) += accton_as6812_32x_sfp.o + obj-$(CONFIG_EEPROM_SFF_8436) += sff_8436_eeprom.o +diff --git a/drivers/misc/eeprom/accton_as6812_32x_sfp.c b/drivers/misc/eeprom/accton_as6812_32x_sfp.c +new file mode 100644 +index 0000000..1669fb8 +--- /dev/null ++++ b/drivers/misc/eeprom/accton_as6812_32x_sfp.c +@@ -0,0 +1,390 @@ ++/* ++ * An hwmon driver for accton as6812_32x sfp ++ * ++ * Copyright (C) 2015 Accton Technology Corporation. ++ * Brandon Chuang ++ * ++ * Based on ad7414.c ++ * Copyright 2006 Stefan Roese , 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 as6812_32x_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 */ ++ u64 is_present; /* present status */ ++}; ++ ++static struct as6812_32x_sfp_data *as6812_32x_sfp_update_device(struct device *dev, int update_eeprom); ++static ssize_t show_present(struct device *dev, struct device_attribute *da,char *buf); ++static ssize_t show_eeprom(struct device *dev, struct device_attribute *da, char *buf); ++static ssize_t show_port_number(struct device *dev, struct device_attribute *da, ++ char *buf); ++static int as6812_32x_sfp_read_byte(struct i2c_client *client, u8 command, u8 *data); ++extern int as6812_32x_i2c_cpld_read(unsigned short cpld_addr, u8 reg); ++extern int as6812_32x_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value); ++//extern int accton_i2c_cpld_mux_get_index(int adap_index); ++ ++enum as6812_32x_sfp_sysfs_attributes { ++ SFP_IS_PRESENT, ++ SFP_EEPROM, ++ SFP_PORT_NUMBER, ++ SFP_IS_PRESENT_ALL ++}; ++ ++/* sysfs attributes for hwmon ++ */ ++static SENSOR_DEVICE_ATTR(sfp_is_present, S_IRUGO, show_present, NULL, SFP_IS_PRESENT); ++static SENSOR_DEVICE_ATTR(sfp_is_present_all, S_IRUGO, show_present, NULL, SFP_IS_PRESENT_ALL); ++static SENSOR_DEVICE_ATTR(sfp_eeprom, S_IRUGO, show_eeprom, NULL, SFP_EEPROM); ++static SENSOR_DEVICE_ATTR(sfp_port_number, S_IRUGO, show_port_number, NULL, SFP_PORT_NUMBER); ++ ++static struct attribute *as6812_32x_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 as6812_32x_sfp_data *data = i2c_get_clientdata(client); ++ ++ return sprintf(buf, "%d\n", data->port+1); ++} ++ ++/* Error-check the CPLD read results. */ ++#define VALIDATED_READ(_buf, _rv, _read_expr, _invert) \ ++do { \ ++ _rv = (_read_expr); \ ++ if(_rv < 0) { \ ++ return sprintf(_buf, "READ ERROR\n"); \ ++ } \ ++ if(_invert) { \ ++ _rv = ~_rv; \ ++ } \ ++ _rv &= 0xFF; \ ++} while(0) ++ ++static ssize_t show_present(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ ++ if(attr->index == SFP_IS_PRESENT_ALL) { ++ int values[4]; ++ /* ++ * Report the SFP_PRESENCE status for all ports. ++ */ ++ ++ /* SFP_PRESENT Ports 1-8 */ ++ VALIDATED_READ(buf, values[0], as6812_32x_i2c_cpld_read(0x62, 0xA), 1); ++ /* SFP_PRESENT Ports 9-16 */ ++ VALIDATED_READ(buf, values[1], as6812_32x_i2c_cpld_read(0x62, 0xB), 1); ++ /* SFP_PRESENT Ports 17-24 */ ++ VALIDATED_READ(buf, values[2], as6812_32x_i2c_cpld_read(0x64, 0xA), 1); ++ /* SFP_PRESENT Ports 25-32 */ ++ VALIDATED_READ(buf, values[3], as6812_32x_i2c_cpld_read(0x64, 0xB), 1); ++ ++ /* Return values 1 -> 32 in order */ ++ return sprintf(buf, "%.2x %.2x %.2x %.2x\n", ++ values[0], values[1], values[2], values[3]); ++ } ++ else { /* SFP_IS_PRESENT */ ++ u8 val; ++ struct as6812_32x_sfp_data *data = as6812_32x_sfp_update_device(dev, 0); ++ ++ if (!data->valid) { ++ return -EIO; ++ } ++ ++ val = (data->is_present & BIT_INDEX(data->port)) ? 0 : 1; ++ return sprintf(buf, "%d", val); ++ } ++} ++ ++static ssize_t show_eeprom(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct as6812_32x_sfp_data *data = as6812_32x_sfp_update_device(dev, 1); ++ ++ if (!data->valid) { ++ return 0; ++ } ++ ++ if ((data->is_present & BIT_INDEX(data->port)) != 0) { ++ return 0; ++ } ++ ++ memcpy(buf, data->eeprom, sizeof(data->eeprom)); ++ ++ return sizeof(data->eeprom); ++} ++ ++static const struct attribute_group as6812_32x_sfp_group = { ++ .attrs = as6812_32x_sfp_attributes, ++}; ++ ++static int as6812_32x_sfp_probe(struct i2c_client *client, ++ const struct i2c_device_id *dev_id) ++{ ++ struct as6812_32x_sfp_data *data; ++ int status; ++ ++ if (!i2c_check_functionality(client->adapter, /*I2C_FUNC_SMBUS_BYTE_DATA | */I2C_FUNC_SMBUS_WORD_DATA)) { ++ status = -EIO; ++ goto exit; ++ } ++ ++ data = kzalloc(sizeof(struct as6812_32x_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, &as6812_32x_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, &as6812_32x_sfp_group); ++exit_free: ++ kfree(data); ++exit: ++ ++ return status; ++} ++ ++static int as6812_32x_sfp_remove(struct i2c_client *client) ++{ ++ struct as6812_32x_sfp_data *data = i2c_get_clientdata(client); ++ ++ hwmon_device_unregister(data->hwmon_dev); ++ sysfs_remove_group(&client->dev.kobj, &as6812_32x_sfp_group); ++ kfree(data); ++ ++ return 0; ++} ++ ++enum port_numbers { ++as6812_32x_sfp1, as6812_32x_sfp2, as6812_32x_sfp3, as6812_32x_sfp4, ++as6812_32x_sfp5, as6812_32x_sfp6, as6812_32x_sfp7, as6812_32x_sfp8, ++as6812_32x_sfp9, as6812_32x_sfp10, as6812_32x_sfp11,as6812_32x_sfp12, ++as6812_32x_sfp13, as6812_32x_sfp14, as6812_32x_sfp15,as6812_32x_sfp16, ++as6812_32x_sfp17, as6812_32x_sfp18, as6812_32x_sfp19,as6812_32x_sfp20, ++as6812_32x_sfp21, as6812_32x_sfp22, as6812_32x_sfp23,as6812_32x_sfp24, ++as6812_32x_sfp25, as6812_32x_sfp26, as6812_32x_sfp27,as6812_32x_sfp28, ++as6812_32x_sfp29, as6812_32x_sfp30, as6812_32x_sfp31,as6812_32x_sfp32 ++}; ++ ++static const struct i2c_device_id as6812_32x_sfp_id[] = { ++{ "as6812_32x_sfp1", as6812_32x_sfp1 }, { "as6812_32x_sfp2", as6812_32x_sfp2 }, ++{ "as6812_32x_sfp3", as6812_32x_sfp3 }, { "as6812_32x_sfp4", as6812_32x_sfp4 }, ++{ "as6812_32x_sfp5", as6812_32x_sfp5 }, { "as6812_32x_sfp6", as6812_32x_sfp6 }, ++{ "as6812_32x_sfp7", as6812_32x_sfp7 }, { "as6812_32x_sfp8", as6812_32x_sfp8 }, ++{ "as6812_32x_sfp9", as6812_32x_sfp9 }, { "as6812_32x_sfp10", as6812_32x_sfp10 }, ++{ "as6812_32x_sfp11", as6812_32x_sfp11 }, { "as6812_32x_sfp12", as6812_32x_sfp12 }, ++{ "as6812_32x_sfp13", as6812_32x_sfp13 }, { "as6812_32x_sfp14", as6812_32x_sfp14 }, ++{ "as6812_32x_sfp15", as6812_32x_sfp15 }, { "as6812_32x_sfp16", as6812_32x_sfp16 }, ++{ "as6812_32x_sfp17", as6812_32x_sfp17 }, { "as6812_32x_sfp18", as6812_32x_sfp18 }, ++{ "as6812_32x_sfp19", as6812_32x_sfp19 }, { "as6812_32x_sfp20", as6812_32x_sfp20 }, ++{ "as6812_32x_sfp21", as6812_32x_sfp21 }, { "as6812_32x_sfp22", as6812_32x_sfp22 }, ++{ "as6812_32x_sfp23", as6812_32x_sfp23 }, { "as6812_32x_sfp24", as6812_32x_sfp24 }, ++{ "as6812_32x_sfp25", as6812_32x_sfp25 }, { "as6812_32x_sfp26", as6812_32x_sfp26 }, ++{ "as6812_32x_sfp27", as6812_32x_sfp27 }, { "as6812_32x_sfp28", as6812_32x_sfp28 }, ++{ "as6812_32x_sfp29", as6812_32x_sfp29 }, { "as6812_32x_sfp30", as6812_32x_sfp30 }, ++{ "as6812_32x_sfp31", as6812_32x_sfp31 }, { "as6812_32x_sfp32", as6812_32x_sfp32 }, ++{} ++}; ++MODULE_DEVICE_TABLE(i2c, as6812_32x_sfp_id); ++ ++ ++static struct i2c_driver as6812_32x_sfp_driver = { ++ .class = I2C_CLASS_HWMON, ++ .driver = { ++ .name = "as6812_32x_sfp", ++ }, ++ .probe = as6812_32x_sfp_probe, ++ .remove = as6812_32x_sfp_remove, ++ .id_table = as6812_32x_sfp_id, ++ .address_list = normal_i2c, ++}; ++ ++#if 0 ++static int as6812_32x_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; ++} ++#endif ++ ++static int as6812_32x_sfp_read_word(struct i2c_client *client, u8 command, u16 *data) ++{ ++ int result = i2c_smbus_read_word_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 = (u16)result; ++ result = 0; ++ ++abort: ++ return result; ++} ++ ++#define ALWAYS_UPDATE 1 ++ ++static struct as6812_32x_sfp_data *as6812_32x_sfp_update_device(struct device *dev, int update_eeprom) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct as6812_32x_sfp_data *data = i2c_get_clientdata(client); ++ ++ mutex_lock(&data->update_lock); ++ ++ if (ALWAYS_UPDATE || time_after(jiffies, data->last_updated + HZ + HZ / 2) ++ || !data->valid) { ++ int status = -1; ++ int i = 0, j = 0; ++ ++ data->valid = 0; ++ ++ /* Read present status of port 1~32 */ ++ data->is_present = 0; ++ ++ for (i = 0; i < 2; i++) { ++ for (j = 0; j < 2; j++) { ++ status = as6812_32x_i2c_cpld_read(0x62+i*2, 0xA+j); ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "cpld(0x%x) reg(0x%x) err %d\n", 0x62+i*2, 0xA+j, status); ++ goto exit; ++ } ++ ++ data->is_present |= (u64)status << ((i*16) + (j*8)); ++ } ++ } ++ ++ 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->is_present & BIT_INDEX(data->port)) == 0) { ++ /* read eeprom */ ++ u16 eeprom_data; ++ for (i = 0; i < (sizeof(data->eeprom) / 2); i++) { ++ status = as6812_32x_sfp_read_word(client, i*2, &eeprom_data); ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "unable to read eeprom from port(%d)\n", data->port); ++ goto exit; ++ } ++ ++ data->eeprom[i*2] = eeprom_data & 0xff; ++ data->eeprom[i*2 + 1] = eeprom_data >> 8; ++ } ++ } ++ } ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++exit: ++ mutex_unlock(&data->update_lock); ++ ++ return data; ++} ++ ++static int __init as6812_32x_sfp_init(void) ++{ ++ extern int platform_accton_as6812_32x(void); ++ if(!platform_accton_as6812_32x()) { ++ return -ENODEV; ++ } ++ ++ return i2c_add_driver(&as6812_32x_sfp_driver); ++} ++ ++static void __exit as6812_32x_sfp_exit(void) ++{ ++ i2c_del_driver(&as6812_32x_sfp_driver); ++} ++ ++module_i2c_driver(as6812_32x_sfp_driver); ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("accton as6812_32x_sfp driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(as6812_32x_sfp_init); ++module_exit(as6812_32x_sfp_exit); diff --git a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/series b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/series index 9a8d2ec5..f4d87389 100644 --- a/packages/base/any/kernels/3.2.65-1+deb7u2/patches/series +++ b/packages/base/any/kernels/3.2.65-1+deb7u2/patches/series @@ -241,3 +241,6 @@ overlayfs_notify.patch platform-accton-as7512_32x-device-drivers.patch driver-pca954x-i2c-mux-deselect-on-exit-config-option.patch platform-accton-as7712_32x-device-drivers.patch +platform-accton-as5812_54x-device-drivers.patch +platform-accton-as6812_32x-device-drivers.patch +platform-accton-as5812_54t-device-drivers.patch