From fa2f16cd48feea84fcad4d113112799d99618318 Mon Sep 17 00:00:00 2001 From: Jeffrey Townsend Date: Wed, 20 Jul 2016 23:18:04 +0000 Subject: [PATCH] AS4610 patch. --- ...latform-accton-as4610-device-drivers.patch | 3946 +++++++++++++++++ 1 file changed, 3946 insertions(+) create mode 100644 packages/base/any/kernels/3.2.71-1+deb7/patches/platform-accton-as4610-device-drivers.patch diff --git a/packages/base/any/kernels/3.2.71-1+deb7/patches/platform-accton-as4610-device-drivers.patch b/packages/base/any/kernels/3.2.71-1+deb7/patches/platform-accton-as4610-device-drivers.patch new file mode 100644 index 00000000..027d3693 --- /dev/null +++ b/packages/base/any/kernels/3.2.71-1+deb7/patches/platform-accton-as4610-device-drivers.patch @@ -0,0 +1,3946 @@ +Device driver patches for accton as4610 54 (fan/psu/cpld/led/sfp) + +diff --git a/arch/arm/boot/dts/accton_as4610_54.dts b/arch/arm/boot/dts/accton_as4610_54.dts +index 9276c0a..8848f8c 100644 +--- a/arch/arm/boot/dts/accton_as4610_54.dts ++++ b/arch/arm/boot/dts/accton_as4610_54.dts +@@ -105,46 +105,46 @@ + }; + + i2c0: i2c@18038000 { +- compatible = "iproc-smb"; +- reg = <0x18038000 0x1000>; ++ compatible = "iproc-smb"; ++ reg = <0x18038000 0x1000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ interrupts = < 127 >; ++ clock-frequency = <400000>; ++ cpld@1,0 { + #address-cells = <1>; +- #size-cells = <0>; +- interrupts = < 127 >; +- clock-frequency = <400000>; +- cpld@1,0 { +- #address-cells = <1>; +- #size-cells = <1>; +- compatible = "accton,as4610-54-cpld"; +- label = "cpld"; +- reg = <0x30>; +- }; ++ #size-cells = <1>; ++ compatible = "accton,as4610_54_cpld"; ++ label = "cpld"; ++ reg = <0x30>; ++ }; + }; + + i2c1: i2c@1803b000 { +- compatible = "iproc-smb"; +- reg = <0x1803b000 0x1000>; ++ compatible = "iproc-smb"; ++ reg = <0x1803b000 0x1000>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ interrupts = < 128 >; ++ clock-frequency = <100000>; ++ mux@70 { ++ compatible = "ti,pca9548"; ++ reg = <0x70>; + #address-cells = <1>; + #size-cells = <0>; +- interrupts = < 128 >; +- clock-frequency = <100000>; +- mux@70 { +- compatible = "ti,pca9548"; +- reg = <0x70>; ++ deselect-on-exit; ++ ++ // SFP+ 1 ++ i2c@0 { + #address-cells = <1>; + #size-cells = <0>; +- deselect-on-exit; +- +- // SFP+ 1 +- i2c@0 { +- #address-cells = <1>; +- #size-cells = <0>; +- reg = <0>; +- sfp_eeprom@50 { +- compatible = "at,24c04"; +- reg = <0x50>; +- label = "port49"; +- }; ++ reg = <0>; ++ sfp_eeprom@50 { ++ compatible = "at,as4610_sfp1"; ++ reg = <0x50>; ++ label = "port49"; + }; ++ }; + + // SFP+ 2 + i2c@1 { +@@ -152,7 +152,7 @@ + #size-cells = <0>; + reg = <1>; + sfp_eeprom@50 { +- compatible = "at,24c04"; ++ compatible = "accton,as4610_sfp2"; + reg = <0x50>; + label = "port50"; + }; +@@ -164,7 +164,7 @@ + #size-cells = <0>; + reg = <2>; + sfp_eeprom@50 { +- compatible = "at,24c04"; ++ compatible = "accton,as4610_sfp3"; + reg = <0x50>; + label = "port51"; + }; +@@ -176,7 +176,7 @@ + #size-cells = <0>; + reg = <3>; + sfp_eeprom@50 { +- compatible = "at,24c04"; ++ compatible = "accton,as4610_sfp4"; + reg = <0x50>; + label = "port52"; + }; +@@ -188,7 +188,7 @@ + #size-cells = <0>; + reg = <4>; + sfp_eeprom@50 { +- compatible = "at,24c04"; ++ compatible = "accton,as4610_sfp5"; + reg = <0x50>; + }; + }; +@@ -199,7 +199,7 @@ + #size-cells = <0>; + reg = <5>; + sfp_eeprom@50 { +- compatible = "at,24c04"; ++ compatible = "accton,as4610_sfp6"; + reg = <0x50>; + }; + }; +@@ -209,17 +209,21 @@ + #address-cells = <1>; + #size-cells = <0>; + reg = <6>; +- psu_eeprom@50 { +- compatible = "at,24c02"; ++ psu1_eeprom@50 { ++ compatible = "accton,as4610_psu1"; + reg = <0x50>; +- label = "psu1_eeprom"; +- read-only; + }; +- psu_eeprom@51 { +- compatible = "at,24c02"; ++ psu1_pmbus@58 { ++ compatible = "3y-power,ym1921"; ++ reg = <0x58>; ++ }; ++ psu2_eeprom@51 { ++ compatible = "accton,as4610_psu2"; + reg = <0x51>; +- label = "psu2_eeprom"; +- read-only; ++ }; ++ psu2_pmbus@59 { ++ compatible = "3y-power,ym1921"; ++ reg = <0x59>; + }; + }; + +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +index 5c984a6..df89e25 100644 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -1412,6 +1412,43 @@ config SENSORS_MC13783_ADC + help + Support for the A/D converter on MC13783 PMIC. + ++config SENSORS_ACCTON_I2C_CPLD ++ tristate "Accton i2c cpld" ++ depends on I2C ++ help ++ If you say yes here you get support for Accton i2c cpld. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_i2c_cpld. ++ ++config SENSORS_ACCTON_AS4610_FAN ++ tristate "Accton as4610 fan" ++ depends on I2C && SENSORS_ACCTON_I2C_CPLD ++ help ++ If you say yes here you get support for Accton as4610 fan. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as4610_fan. ++ ++config SENSORS_ACCTON_AS4610_PSU ++ tristate "Accton as4610 psu" ++ depends on I2C && SENSORS_ACCTON_I2C_CPLD ++ help ++ If you say yes here you get support for Accton as4610 psu. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as4610_psu. ++ ++config SENSORS_YM2651Y ++ tristate "3Y Power (YM-2651Y, YM1921Y) Power Module" ++ depends on I2C ++ help ++ If you say yes here you get support for 3Y Power YM-2651Y and YM1921Y ++ Power Module. ++ ++ This driver can also be built as a module. If so, the module will ++ be called ym2651y. ++ + if ACPI + + comment "ACPI drivers" +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +index ff3a18e..39c9888 100644 +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -21,6 +21,9 @@ obj-$(CONFIG_SENSORS_W83791D) += w83791d.o + + obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o + obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o ++obj-$(CONFIG_SENSORS_ACCTON_I2C_CPLD) += accton_i2c_cpld.o ++obj-$(CONFIG_SENSORS_ACCTON_AS4610_FAN) += accton_as4610_fan.o ++obj-$(CONFIG_SENSORS_ACCTON_AS4610_PSU) += accton_as4610_psu.o + obj-$(CONFIG_SENSORS_AD7314) += ad7314.o + obj-$(CONFIG_SENSORS_AD7414) += ad7414.o + obj-$(CONFIG_SENSORS_AD7418) += ad7418.o +@@ -130,6 +133,7 @@ obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o + obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o + obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o + obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o ++obj-$(CONFIG_SENSORS_YM2651Y) += ym2651y.o + + obj-$(CONFIG_PMBUS) += pmbus/ + +diff --git a/drivers/hwmon/accton_as4610_fan.c b/drivers/hwmon/accton_as4610_fan.c +new file mode 100644 +index 0000000..3934bcd +--- /dev/null ++++ b/drivers/hwmon/accton_as4610_fan.c +@@ -0,0 +1,344 @@ ++/* ++ * A hwmon driver for the Accton as4610 fan ++ * ++ * Copyright (C) 2016 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 ++ ++#define DRVNAME "as4610_fan" ++ ++static struct as4610_fan_data *as4610_fan_update_device(struct device *dev); ++static ssize_t fan_show_value(struct device *dev, struct device_attribute *da, char *buf); ++static ssize_t set_duty_cycle(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count); ++ ++/* fan related data, the index should match sysfs_fan_attributes ++ */ ++static const u8 fan_reg[] = { ++ 0x2B, /* fan PWM(for all fan) */ ++ 0x2C, /* fan 1 speed(rpm) */ ++ 0x2D, /* fan 2 speed(rpm) */ ++ 0x11, /* fan1-2 operating status */ ++}; ++ ++static struct as4610_fan_data *fan_data = NULL; ++ ++/* Each client has this additional data */ ++struct as4610_fan_data { ++ 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 reg_val[ARRAY_SIZE(fan_reg)]; /* Register value */ ++}; ++ ++enum fan_id { ++ FAN1_ID, ++ FAN2_ID ++}; ++ ++enum sysfs_fan_attributes { ++ FAN_DUTY_CYCLE_PERCENTAGE, /* Only one CPLD register to control duty cycle for all fans */ ++ FAN1_SPEED_RPM, ++ FAN2_SPEED_RPM, ++ FAN_FAULT, ++ FAN1_FAULT, ++ FAN2_FAULT ++}; ++ ++/* Define attributes ++ */ ++#define DECLARE_FAN_FAULT_SENSOR_DEV_ATTR(index) \ ++ static SENSOR_DEVICE_ATTR(fan##index##_fault, S_IRUGO, fan_show_value, NULL, FAN##index##_FAULT) ++#define DECLARE_FAN_FAULT_ATTR(index) &sensor_dev_attr_fan##index##_fault.dev_attr.attr ++ ++#define DECLARE_FAN_DUTY_CYCLE_SENSOR_DEV_ATTR(index) \ ++ static SENSOR_DEVICE_ATTR(fan##index##_duty_cycle_percentage, S_IWUSR | S_IRUGO, fan_show_value, set_duty_cycle, FAN##index##_DUTY_CYCLE_PERCENTAGE) ++#define DECLARE_FAN_DUTY_CYCLE_ATTR(index) &sensor_dev_attr_fan##index##_duty_cycle_percentage.dev_attr.attr ++ ++#define DECLARE_FAN_SPEED_RPM_SENSOR_DEV_ATTR(index) \ ++ static SENSOR_DEVICE_ATTR(fan##index##_speed_rpm, S_IRUGO, fan_show_value, NULL, FAN##index##_SPEED_RPM) ++#define DECLARE_FAN_SPEED_RPM_ATTR(index) &sensor_dev_attr_fan##index##_speed_rpm.dev_attr.attr ++ ++/* fan fault attributes in this platform */ ++DECLARE_FAN_FAULT_SENSOR_DEV_ATTR(1); ++DECLARE_FAN_FAULT_SENSOR_DEV_ATTR(2); ++/* fan speed(rpm) attributes in this platform */ ++DECLARE_FAN_SPEED_RPM_SENSOR_DEV_ATTR(1); ++DECLARE_FAN_SPEED_RPM_SENSOR_DEV_ATTR(2); ++/* 1 fan duty cycle attribute in this platform */ ++DECLARE_FAN_DUTY_CYCLE_SENSOR_DEV_ATTR(); ++ ++static struct attribute *as4610_fan_attributes[] = { ++ /* fan related attributes */ ++ DECLARE_FAN_FAULT_ATTR(1), ++ DECLARE_FAN_FAULT_ATTR(2), ++ DECLARE_FAN_SPEED_RPM_ATTR(1), ++ DECLARE_FAN_SPEED_RPM_ATTR(2), ++ DECLARE_FAN_DUTY_CYCLE_ATTR(), ++ NULL ++}; ++ ++#define FAN_DUTY_CYCLE_REG_MASK 0xF ++#define FAN_MAX_DUTY_CYCLE 100 ++#define FAN_REG_VAL_TO_SPEED_RPM_STEP 100 ++ ++static int as4610_fan_read_value(u8 reg) ++{ ++ return accton_i2c_cpld_read(AS4610_CPLD_SLAVE_ADDR, reg); ++} ++ ++static int as4610_fan_write_value(u8 reg, u8 value) ++{ ++ return accton_i2c_cpld_write(AS4610_CPLD_SLAVE_ADDR, reg, value); ++} ++ ++/* fan utility functions ++ */ ++static u32 reg_val_to_duty_cycle(u8 reg_val) ++{ ++ reg_val &= FAN_DUTY_CYCLE_REG_MASK; ++ return (u32)((reg_val * 125 + 5)/10); ++} ++ ++static u8 duty_cycle_to_reg_val(u8 duty_cycle) ++{ ++ return ((u32)duty_cycle * 10 / 125); ++} ++ ++static u32 reg_val_to_speed_rpm(u8 reg_val) ++{ ++ /* Count Frequency is 1.515KHz= 0.66ms ++ * Count Period = 400 cycle = 400*0.66ms = 264ms ++ * R.P.M value = read value x3.79*60/2 ++ * 3.79 = 1000ms/264ms ++ * 60 = 1min =60s ++ * 2 = 1 rotation of fan has two pulses. ++ */ ++ return (u32)reg_val * 379 * 60 / 2 / 100; ++} ++ ++static u8 is_fan_fault(struct as4610_fan_data *data, enum fan_id id) ++{ ++ u8 mask = (id == FAN1_ID) ? 0x10 : 0x20; ++ ++ return !(data->reg_val[FAN_FAULT] & mask); ++} ++ ++static ssize_t 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 < 0 || value > FAN_MAX_DUTY_CYCLE) ++ return -EINVAL; ++ ++ as4610_fan_write_value(fan_reg[FAN_DUTY_CYCLE_PERCENTAGE], duty_cycle_to_reg_val(value)); ++ return count; ++} ++ ++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); ++ struct as4610_fan_data *data = as4610_fan_update_device(dev); ++ ssize_t ret = 0; ++ ++ if (data->valid) { ++ switch (attr->index) { ++ case FAN_DUTY_CYCLE_PERCENTAGE: ++ { ++ u32 duty_cycle = reg_val_to_duty_cycle(data->reg_val[attr->index]); ++ ret = sprintf(buf, "%u\n", duty_cycle); ++ break; ++ } ++ case FAN1_SPEED_RPM: ++ case FAN2_SPEED_RPM: ++ ret = sprintf(buf, "%u\n", reg_val_to_speed_rpm(data->reg_val[attr->index])); ++ break; ++ case FAN1_FAULT: ++ case FAN2_FAULT: ++ ret = sprintf(buf, "%d\n", is_fan_fault(data, attr->index - FAN1_FAULT)); ++ break; ++ default: ++ break; ++ } ++ } ++ ++ return ret; ++} ++ ++static const struct attribute_group as4610_fan_group = { ++ .attrs = as4610_fan_attributes, ++}; ++ ++static struct as4610_fan_data *as4610_fan_update_device(struct device *dev) ++{ ++ mutex_lock(&fan_data->update_lock); ++ ++ if (time_after(jiffies, fan_data->last_updated + HZ + HZ / 2) || ++ !fan_data->valid) { ++ int i; ++ ++ dev_dbg(fan_data->hwmon_dev, "Starting as4610_fan update\n"); ++ fan_data->valid = 0; ++ ++ /* Update fan data ++ */ ++ for (i = 0; i < ARRAY_SIZE(fan_data->reg_val); i++) { ++ int status = as4610_fan_read_value(fan_reg[i]); ++ ++ if (status < 0) { ++ fan_data->valid = 0; ++ mutex_unlock(&fan_data->update_lock); ++ dev_dbg(fan_data->hwmon_dev, "reg %d, err %d\n", fan_reg[i], status); ++ return fan_data; ++ } ++ else { ++ fan_data->reg_val[i] = status; ++ } ++ } ++ ++ fan_data->last_updated = jiffies; ++ fan_data->valid = 1; ++ } ++ ++ mutex_unlock(&fan_data->update_lock); ++ ++ return fan_data; ++} ++ ++static int as4610_fan_probe(struct platform_device *pdev) ++{ ++ int status = -1; ++ ++ /* Register sysfs hooks */ ++ status = sysfs_create_group(&pdev->dev.kobj, &as4610_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_as5712_54x_fan\n"); ++ ++ return 0; ++ ++exit_remove: ++ sysfs_remove_group(&pdev->dev.kobj, &as4610_fan_group); ++exit: ++ return status; ++} ++ ++static int as4610_fan_remove(struct platform_device *pdev) ++{ ++ hwmon_device_unregister(fan_data->hwmon_dev); ++ sysfs_remove_group(&pdev->dev.kobj, &as4610_fan_group); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id as4610_fan_id[] = { ++ { "as4610_fan", 0 }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, as4610_fan_id); ++ ++static struct platform_driver as4610_fan_driver = { ++ .probe = as4610_fan_probe, ++ .remove = as4610_fan_remove, ++ .driver = { ++ .name = DRVNAME, ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init as4610_fan_init(void) ++{ ++ int ret; ++ ++ if (as4610_number_of_system_fan() == 0) { ++ return -ENODEV; ++ } ++ ++ ret = platform_driver_register(&as4610_fan_driver); ++ if (ret < 0) { ++ goto exit; ++ } ++ ++ fan_data = kzalloc(sizeof(struct as4610_fan_data), GFP_KERNEL); ++ if (!fan_data) { ++ ret = -ENOMEM; ++ platform_driver_unregister(&as4610_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(&as4610_fan_driver); ++ kfree(fan_data); ++ goto exit; ++ } ++ ++exit: ++ return ret; ++} ++ ++static void __exit as4610_fan_exit(void) ++{ ++ if (!fan_data) { ++ return; ++ } ++ ++ platform_device_unregister(fan_data->pdev); ++ platform_driver_unregister(&as4610_fan_driver); ++ kfree(fan_data); ++} ++ ++late_initcall(as4610_fan_init); ++module_exit(as4610_fan_exit); ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("as4610_fan driver"); ++MODULE_LICENSE("GPL"); ++ +diff --git a/drivers/hwmon/accton_as4610_psu.c b/drivers/hwmon/accton_as4610_psu.c +new file mode 100644 +index 0000000..1f0d79d +--- /dev/null ++++ b/drivers/hwmon/accton_as4610_psu.c +@@ -0,0 +1,286 @@ ++/* ++ * An hwmon driver for accton as4610 Power Module ++ * ++ * Copyright (C) 2016 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 ++#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 as4610_psu_read_data(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[] = { 0x50, 0x53, I2C_CLIENT_END }; ++ ++/* Each client has this additional data ++ */ ++struct as4610_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[9]; /* Model name, read from eeprom */ ++}; ++ ++static struct as4610_psu_data *as4610_psu_update_device(struct device *dev); ++ ++enum as4610_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 *as4610_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 as4610_psu_data *data = as4610_psu_update_device(dev); ++ u8 status = 0; ++ ++ if (attr->index == PSU_PRESENT) { ++ status = (data->status >> (data->index*2) & 0x1); ++ } ++ else { /* PSU_POWER_GOOD */ ++ status = (data->status >> (data->index*2 + 1) & 0x1); ++ } ++ ++ return sprintf(buf, "%d\n", status); ++} ++ ++static ssize_t show_model_name(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct as4610_psu_data *data = as4610_psu_update_device(dev); ++ ++ return sprintf(buf, "%s\n", data->model_name); ++} ++ ++static const struct attribute_group as4610_psu_group = { ++ .attrs = as4610_psu_attributes, ++}; ++ ++static int as4610_psu_probe(struct i2c_client *client, ++ const struct i2c_device_id *dev_id) ++{ ++ struct as4610_psu_data *data; ++ int status; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { ++ status = -EIO; ++ goto exit; ++ } ++ ++ data = kzalloc(sizeof(struct as4610_psu_data), GFP_KERNEL); ++ if (!data) { ++ status = -ENOMEM; ++ goto exit; ++ } ++ ++ i2c_set_clientdata(client, data); ++ data->valid = 0; ++ data->index = dev_id->driver_data; ++ mutex_init(&data->update_lock); ++ ++ dev_info(&client->dev, "chip found\n"); ++ ++ /* Register sysfs hooks */ ++ status = sysfs_create_group(&client->dev.kobj, &as4610_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; ++ } ++ ++ 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, &as4610_psu_group); ++exit_free: ++ kfree(data); ++exit: ++ ++ return status; ++} ++ ++static int as4610_psu_remove(struct i2c_client *client) ++{ ++ struct as4610_psu_data *data = i2c_get_clientdata(client); ++ ++ hwmon_device_unregister(data->hwmon_dev); ++ sysfs_remove_group(&client->dev.kobj, &as4610_psu_group); ++ kfree(data); ++ ++ return 0; ++} ++ ++enum psu_index ++{ ++ as4610_psu1, ++ as4610_psu2 ++}; ++ ++static const struct i2c_device_id as4610_psu_id[] = { ++ { "as4610_psu1", as4610_psu1 }, ++ { "as4610_psu2", as4610_psu2 }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, as4610_psu_id); ++ ++static struct i2c_driver as4610_psu_driver = { ++ .class = I2C_CLASS_HWMON, ++ .driver = { ++ .name = "as4610_psu", ++ }, ++ .probe = as4610_psu_probe, ++ .remove = as4610_psu_remove, ++ .id_table = as4610_psu_id, ++ .address_list = normal_i2c, ++}; ++ ++static int as4610_psu_read_data(struct i2c_client *client, u8 command, u8 *data, ++ int count) ++{ ++ int status = 0; ++ ++ while (count) { ++ status = i2c_smbus_read_byte_data(client, command); ++ if (unlikely(status < 0)) { ++ break; ++ } ++ ++ *data = (u8)status; ++ data += 1; ++ command += 1; ++ count -= 1; ++ } ++ ++ return status; ++} ++ ++static struct as4610_psu_data *as4610_psu_update_device(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct as4610_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; ++ ++ data->valid = 0; ++ data->status = 0; ++ dev_dbg(&client->dev, "Starting as4610 update\n"); ++ ++ /* Read psu status */ ++ status = accton_i2c_cpld_read(0x30, 0x11); ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "cpld reg 0x30 err %d\n", status); ++ goto exit; ++ } ++ else { ++ data->status = status; ++ } ++ ++ /* Read model name */ ++ memset(data->model_name, 0, sizeof(data->model_name)); ++ present = (data->status >> (data->index*2) & 0x1); ++ ++ if (present) { ++ int len = ARRAY_SIZE(data->model_name)-1; ++ ++ status = as4610_psu_read_data(client, 0x20, 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); ++ goto exit; ++ } ++ else { ++ data->model_name[ARRAY_SIZE(data->model_name)-1] = '\0'; ++ } ++ } ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++exit: ++ mutex_unlock(&data->update_lock); ++ ++ return data; ++} ++ ++static int __init as4610_psu_init(void) ++{ ++ return i2c_add_driver(&as4610_psu_driver); ++} ++ ++static void __exit as4610_psu_exit(void) ++{ ++ i2c_del_driver(&as4610_psu_driver); ++} ++ ++module_init(as4610_psu_init); ++module_exit(as4610_psu_exit); ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("as4610_psu driver"); ++MODULE_LICENSE("GPL"); ++ +diff --git a/drivers/hwmon/accton_i2c_cpld.c b/drivers/hwmon/accton_i2c_cpld.c +new file mode 100644 +index 0000000..0b9762e +--- /dev/null ++++ b/drivers/hwmon/accton_i2c_cpld.c +@@ -0,0 +1,285 @@ ++/* ++ * A hwmon driver for the accton_i2c_cpld ++ * ++ * Copyright (C) 2013 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 ++ ++static LIST_HEAD(cpld_client_list); ++static struct mutex list_lock; ++ ++enum cpld_device_id { ++ as4610_30_cpld, ++ as4610_54_cpld ++}; ++ ++struct cpld_data { ++ enum cpld_device_id id; ++}; ++ ++struct cpld_client_node { ++ struct i2c_client *client; ++ struct list_head list; ++}; ++ ++/* Addresses scanned for accton_i2c_cpld ++ */ ++static const unsigned short normal_i2c[] = { 0x31, 0x35, 0x60, 0x61, 0x62, I2C_CLIENT_END }; ++ ++static u8 cpld_product_id_offset(enum cpld_device_id id) ++{ ++ switch (id) { ++ case as4610_30_cpld: ++ case as4610_54_cpld: ++ return 0x1; ++ } ++ ++ return 0; ++} ++ ++static int cpld_has_product_id(const struct i2c_device_id *dev_id) ++{ ++ return (dev_id->driver_data == as4610_30_cpld) || ++ (dev_id->driver_data == as4610_54_cpld); ++} ++ ++static ssize_t show_cpld_product_id(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ int val = 0; ++ struct i2c_client *client = to_i2c_client(dev); ++ struct cpld_data *data = i2c_get_clientdata(client); ++ ++ val = i2c_smbus_read_byte_data(client, cpld_product_id_offset(data->id)); ++ if (val < 0) { ++ dev_dbg(&client->dev, "cpld(0x%x) reg(0x%x) err %d\n", client->addr, cpld_product_id_offset(data->id), val); ++ } ++ ++ return sprintf(buf, "%d\n", (val & 0xF)); ++} ++ ++static u8 cpld_version_offset(enum cpld_device_id id) ++{ ++ switch (id) { ++ case as4610_30_cpld: ++ case as4610_54_cpld: ++ return 0xB; ++ } ++ ++ return 0; ++} ++ ++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); ++ struct cpld_data *data = i2c_get_clientdata(client); ++ ++ val = i2c_smbus_read_byte_data(client, cpld_version_offset(data->id)); ++ if (val < 0) { ++ dev_dbg(&client->dev, "cpld(0x%x) reg(0xB) err %d\n", client->addr, val); ++ } ++ ++ return sprintf(buf, "%d\n", val); ++} ++ ++static void accton_i2c_cpld_add_client(struct i2c_client *client, enum cpld_device_id id) ++{ ++ struct cpld_client_node *node = kzalloc(sizeof(struct cpld_client_node), GFP_KERNEL); ++ struct cpld_data *data = kzalloc(sizeof(struct cpld_data), GFP_KERNEL); ++ ++ if (!node) { ++ dev_dbg(&client->dev, "Can't allocate cpld_client_node (0x%x)\n", client->addr); ++ return; ++ } ++ ++ if (!data) { ++ dev_dbg(&client->dev, "Can't allocate cpld_client_data (0x%x)\n", client->addr); ++ return; ++ } ++ ++ data->id = id; ++ i2c_set_clientdata(client, data); ++ 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 struct device_attribute ver = __ATTR(version, 0600, show_cpld_version, NULL); ++static struct device_attribute pid = __ATTR(product_id, 0600, show_cpld_product_id, NULL); ++ ++static int accton_i2c_cpld_probe(struct i2c_client *client, ++ const struct i2c_device_id *dev_id) ++{ ++ int status; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { ++ dev_dbg(&client->dev, "i2c_check_functionality failed (0x%x)\n", client->addr); ++ status = -EIO; ++ goto exit; ++ } ++ ++ status = sysfs_create_file(&client->dev.kobj, &ver.attr); ++ if (status) { ++ goto exit; ++ } ++ ++ if (cpld_has_product_id(dev_id)) { ++ status = sysfs_create_file(&client->dev.kobj, &pid.attr); ++ if (status) { ++ goto exit; ++ } ++ } ++ ++ dev_info(&client->dev, "chip found\n"); ++ accton_i2c_cpld_add_client(client, (enum cpld_device_id)dev_id->driver_data); ++ ++ return 0; ++ ++exit: ++ return status; ++} ++ ++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; ++} ++ ++static const struct i2c_device_id accton_i2c_cpld_id[] = { ++ { "as4610_30_cpld", as4610_30_cpld }, ++ { "as4610_54_cpld", as4610_54_cpld }, ++ { /* LIST END */} ++}; ++MODULE_DEVICE_TABLE(i2c, accton_i2c_cpld_id); ++ ++static struct i2c_driver accton_i2c_cpld_driver = { ++ .class = I2C_CLASS_HWMON, ++ .driver = { ++ .name = "accton_i2c_cpld", ++ }, ++ .probe = accton_i2c_cpld_probe, ++ .remove = accton_i2c_cpld_remove, ++ .id_table = accton_i2c_cpld_id, ++ .address_list = normal_i2c, ++}; ++ ++int accton_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(accton_i2c_cpld_read); ++ ++int accton_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(accton_i2c_cpld_write); ++ ++static int __init accton_i2c_cpld_init(void) ++{ ++ mutex_init(&list_lock); ++ return i2c_add_driver(&accton_i2c_cpld_driver); ++} ++ ++static void __exit accton_i2c_cpld_exit(void) ++{ ++ i2c_del_driver(&accton_i2c_cpld_driver); ++} ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("accton_i2c_cpld driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(accton_i2c_cpld_init); ++module_exit(accton_i2c_cpld_exit); ++ +diff --git a/drivers/hwmon/lm77.c b/drivers/hwmon/lm77.c +index 8dfc678..b9eafcf 100644 +--- a/drivers/hwmon/lm77.c ++++ b/drivers/hwmon/lm77.c +@@ -36,7 +36,7 @@ + #include + + /* Addresses to scan */ +-static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, ++static const unsigned short normal_i2c[] = { /*0x48,*/ 0x49, 0x4a, 0x4b, + I2C_CLIENT_END }; + + /* The LM77 registers */ +diff --git a/drivers/hwmon/ym2651y.c b/drivers/hwmon/ym2651y.c +new file mode 100644 +index 0000000..e761a74 +--- /dev/null ++++ b/drivers/hwmon/ym2651y.c +@@ -0,0 +1,651 @@ ++/* ++ * An hwmon driver for the 3Y Power YM-2651Y Power Module ++ * ++ * Copyright (C) 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 USE_I2C_BLOCK_READ 0 ++#if USE_I2C_BLOCK_READ ++ #define I2C_FUNCTION (I2C_FUNC_SMBUS_BYTE_DATA | \ ++ I2C_FUNC_SMBUS_WORD_DATA | \ ++ I2C_FUNC_SMBUS_I2C_BLOCK) ++#else ++ #define I2C_FUNCTION (I2C_FUNC_SMBUS_BYTE_DATA | \ ++ I2C_FUNC_SMBUS_WORD_DATA) ++#endif ++ ++#define MAX_FAN_DUTY_CYCLE 100 ++ ++/* Addresses scanned ++ */ ++static const unsigned short normal_i2c[] = { 0x58, 0x5b, I2C_CLIENT_END }; ++ ++/* Each client has this additional data ++ */ ++struct ym2651y_data { ++ struct device *hwmon_dev; ++ struct mutex update_lock; ++ char valid; /* !=0 if registers are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ u8 capability; /* Register value */ ++ u16 status_word; /* Register value */ ++ u8 fan_fault; /* Register value */ ++ u8 over_temp; /* Register value */ ++ u16 v_out; /* Register value */ ++ u16 i_out; /* Register value */ ++ u16 p_out; /* Register value */ ++ u16 temp; /* Register value */ ++ u16 fan_speed; /* Register value */ ++ u16 fan_duty_cycle[2]; /* Register value */ ++ u8 pmbus_revision; /* Register value */ ++ u16 mfr_vin_min; /* Register value */ ++ u16 mfr_vin_max; /* Register value */ ++ u16 mfr_iin_max; /* Register value */ ++ u16 mfr_iout_max; /* Register value */ ++ u16 mfr_pin_max; /* Register value */ ++ u16 mfr_pout_max; /* Register value */ ++ u16 mfr_vout_min; /* Register value */ ++ u16 mfr_vout_max; /* Register value */ ++#if USE_I2C_BLOCK_READ ++ u8 fan_dir[4]; /* Register value */ ++ u8 mfr_id[10]; /* Register value */ ++ u8 mfr_model[10]; /* Register value */ ++ u8 mfr_revsion[3]; /* Register value */ ++#endif ++ ++}; ++ ++static ssize_t show_byte(struct device *dev, struct device_attribute *da, ++ char *buf); ++static ssize_t show_word(struct device *dev, struct device_attribute *da, ++ char *buf); ++static ssize_t show_linear(struct device *dev, struct device_attribute *da, ++ char *buf); ++static ssize_t show_fan_fault(struct device *dev, struct device_attribute *da, ++ char *buf); ++static ssize_t show_over_temp(struct device *dev, struct device_attribute *da, ++ char *buf); ++static ssize_t show_ascii(struct device *dev, struct device_attribute *da, ++ char *buf); ++static struct ym2651y_data *ym2651y_update_device(struct device *dev); ++static ssize_t set_fan_duty_cycle(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count); ++static int ym2651y_write_word(struct i2c_client *client, u8 reg, u16 value); ++ ++enum ym2651y_sysfs_attributes { ++ PSU_POWER_ON = 0, ++ PSU_TEMP_FAULT, ++ PSU_POWER_GOOD, ++ PSU_FAN1_FAULT, ++ PSU_FAN_DIRECTION, ++ PSU_OVER_TEMP, ++ PSU_V_OUT, ++ PSU_I_OUT, ++ PSU_P_OUT, ++ PSU_TEMP1_INPUT, ++ PSU_FAN1_SPEED, ++ PSU_FAN1_DUTY_CYCLE, ++ PSU_PMBUS_REVISION, ++ PSU_MFR_ID, ++ PSU_MFR_MODEL, ++ PSU_MFR_REVISION, ++ PSU_MFR_VIN_MIN, ++ PSU_MFR_VIN_MAX, ++ PSU_MFR_VOUT_MIN, ++ PSU_MFR_VOUT_MAX, ++ PSU_MFR_IIN_MAX, ++ PSU_MFR_IOUT_MAX, ++ PSU_MFR_PIN_MAX, ++ PSU_MFR_POUT_MAX ++}; ++ ++/* sysfs attributes for hwmon ++ */ ++static SENSOR_DEVICE_ATTR(psu_power_on, S_IRUGO, show_word, NULL, PSU_POWER_ON); ++static SENSOR_DEVICE_ATTR(psu_temp_fault, S_IRUGO, show_word, NULL, PSU_TEMP_FAULT); ++static SENSOR_DEVICE_ATTR(psu_power_good, S_IRUGO, show_word, NULL, PSU_POWER_GOOD); ++static SENSOR_DEVICE_ATTR(psu_fan1_fault, S_IRUGO, show_fan_fault, NULL, PSU_FAN1_FAULT); ++static SENSOR_DEVICE_ATTR(psu_over_temp, S_IRUGO, show_over_temp, NULL, PSU_OVER_TEMP); ++static SENSOR_DEVICE_ATTR(psu_v_out, S_IRUGO, show_linear, NULL, PSU_V_OUT); ++static SENSOR_DEVICE_ATTR(psu_i_out, S_IRUGO, show_linear, NULL, PSU_I_OUT); ++static SENSOR_DEVICE_ATTR(psu_p_out, S_IRUGO, show_linear, NULL, PSU_P_OUT); ++static SENSOR_DEVICE_ATTR(psu_temp1_input, S_IRUGO, show_linear, NULL, PSU_TEMP1_INPUT); ++static SENSOR_DEVICE_ATTR(psu_fan1_speed_rpm, S_IRUGO, show_linear, NULL, PSU_FAN1_SPEED); ++static SENSOR_DEVICE_ATTR(psu_fan1_duty_cycle_percentage, S_IWUSR | S_IRUGO, show_linear, set_fan_duty_cycle, PSU_FAN1_DUTY_CYCLE); ++static SENSOR_DEVICE_ATTR(psu_pmbus_revision, S_IRUGO, show_byte, NULL, PSU_PMBUS_REVISION); ++static SENSOR_DEVICE_ATTR(psu_mfr_vin_min, S_IRUGO, show_linear, NULL, PSU_MFR_VIN_MIN); ++static SENSOR_DEVICE_ATTR(psu_mfr_vin_max, S_IRUGO, show_linear, NULL, PSU_MFR_VIN_MAX); ++static SENSOR_DEVICE_ATTR(psu_mfr_vout_min, S_IRUGO, show_linear, NULL, PSU_MFR_VOUT_MIN); ++static SENSOR_DEVICE_ATTR(psu_mfr_vout_max, S_IRUGO, show_linear, NULL, PSU_MFR_VOUT_MAX); ++static SENSOR_DEVICE_ATTR(psu_mfr_iin_max, S_IRUGO, show_linear, NULL, PSU_MFR_IIN_MAX); ++static SENSOR_DEVICE_ATTR(psu_mfr_iout_max, S_IRUGO, show_linear, NULL, PSU_MFR_IOUT_MAX); ++static SENSOR_DEVICE_ATTR(psu_mfr_pin_max, S_IRUGO, show_linear, NULL, PSU_MFR_PIN_MAX); ++static SENSOR_DEVICE_ATTR(psu_mfr_pout_max, S_IRUGO, show_linear, NULL, PSU_MFR_POUT_MAX); ++#if USE_I2C_BLOCK_READ ++static SENSOR_DEVICE_ATTR(psu_fan_dir, S_IRUGO, show_ascii, NULL, PSU_FAN_DIRECTION); ++static SENSOR_DEVICE_ATTR(psu_mfr_id, S_IRUGO, show_ascii, NULL, PSU_MFR_ID); ++static SENSOR_DEVICE_ATTR(psu_mfr_model, S_IRUGO, show_ascii, NULL, PSU_MFR_MODEL); ++static SENSOR_DEVICE_ATTR(psu_mfr_revision, S_IRUGO, show_ascii, NULL, PSU_MFR_REVISION); ++#endif ++ ++ ++static struct attribute *ym2651y_attributes[] = { ++ &sensor_dev_attr_psu_power_on.dev_attr.attr, ++ &sensor_dev_attr_psu_temp_fault.dev_attr.attr, ++ &sensor_dev_attr_psu_power_good.dev_attr.attr, ++ &sensor_dev_attr_psu_fan1_fault.dev_attr.attr, ++ &sensor_dev_attr_psu_over_temp.dev_attr.attr, ++ &sensor_dev_attr_psu_v_out.dev_attr.attr, ++ &sensor_dev_attr_psu_i_out.dev_attr.attr, ++ &sensor_dev_attr_psu_p_out.dev_attr.attr, ++ &sensor_dev_attr_psu_temp1_input.dev_attr.attr, ++ &sensor_dev_attr_psu_fan1_speed_rpm.dev_attr.attr, ++ &sensor_dev_attr_psu_fan1_duty_cycle_percentage.dev_attr.attr, ++ &sensor_dev_attr_psu_pmbus_revision.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_vin_min.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_vin_max.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_pout_max.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_iin_max.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_pin_max.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_vout_min.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_vout_max.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_iout_max.dev_attr.attr, ++#if USE_I2C_BLOCK_READ ++ &sensor_dev_attr_psu_fan_dir.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_id.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_model.dev_attr.attr, ++ &sensor_dev_attr_psu_mfr_revision.dev_attr.attr, ++#endif ++ NULL ++}; ++ ++static ssize_t show_byte(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ struct ym2651y_data *data = ym2651y_update_device(dev); ++ ++ if (!data->valid) { ++ return 0; ++ } ++ ++ return (attr->index == PSU_PMBUS_REVISION) ? sprintf(buf, "%d\n", data->pmbus_revision) : ++ sprintf(buf, "0\n"); ++} ++ ++static ssize_t show_word(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ struct ym2651y_data *data = ym2651y_update_device(dev); ++ u16 status = 0; ++ ++ if (!data->valid) { ++ return 0; ++ } ++ ++ switch (attr->index) { ++ case PSU_POWER_ON: /* psu_power_on, low byte bit 6 of status_word, 0=>ON, 1=>OFF */ ++ status = (data->status_word & 0x40) ? 0 : 1; ++ break; ++ case PSU_TEMP_FAULT: /* psu_temp_fault, low byte bit 2 of status_word, 0=>Normal, 1=>temp fault */ ++ status = (data->status_word & 0x4) >> 2; ++ break; ++ case PSU_POWER_GOOD: /* psu_power_good, high byte bit 3 of status_word, 0=>OK, 1=>FAIL */ ++ status = (data->status_word & 0x800) ? 0 : 1; ++ break; ++ } ++ ++ return sprintf(buf, "%d\n", status); ++} ++ ++static int two_complement_to_int(u16 data, u8 valid_bit, int mask) ++{ ++ u16 valid_data = data & mask; ++ bool is_negative = valid_data >> (valid_bit - 1); ++ ++ return is_negative ? (-(((~valid_data) & mask) + 1)) : valid_data; ++} ++ ++static ssize_t set_fan_duty_cycle(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ struct i2c_client *client = to_i2c_client(dev); ++ struct ym2651y_data *data = i2c_get_clientdata(client); ++ int nr = (attr->index == PSU_FAN1_DUTY_CYCLE) ? 0 : 1; ++ long speed; ++ int error; ++ ++ error = kstrtol(buf, 10, &speed); ++ if (error) ++ return error; ++ ++ if (speed < 0 || speed > MAX_FAN_DUTY_CYCLE) ++ return -EINVAL; ++ ++ mutex_lock(&data->update_lock); ++ data->fan_duty_cycle[nr] = speed; ++ ym2651y_write_word(client, 0x3B + nr, data->fan_duty_cycle[nr]); ++ mutex_unlock(&data->update_lock); ++ ++ return count; ++} ++ ++static ssize_t show_linear(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ struct ym2651y_data *data = ym2651y_update_device(dev); ++ ++ u16 value = 0; ++ int exponent, mantissa; ++ int multiplier = 1000; ++ ++ if (!data->valid) { ++ return 0; ++ } ++ ++ switch (attr->index) { ++ case PSU_V_OUT: ++ value = data->v_out; ++ break; ++ case PSU_I_OUT: ++ value = data->i_out; ++ break; ++ case PSU_P_OUT: ++ value = data->p_out; ++ break; ++ case PSU_TEMP1_INPUT: ++ value = data->temp; ++ break; ++ case PSU_FAN1_SPEED: ++ value = data->fan_speed; ++ multiplier = 1; ++ break; ++ case PSU_FAN1_DUTY_CYCLE: ++ value = data->fan_duty_cycle[0]; ++ multiplier = 1; ++ break; ++ case PSU_MFR_VIN_MIN: ++ value = data->mfr_vin_min; ++ break; ++ case PSU_MFR_VIN_MAX: ++ value = data->mfr_vin_max; ++ break; ++ case PSU_MFR_VOUT_MIN: ++ value = data->mfr_vout_min; ++ break; ++ case PSU_MFR_VOUT_MAX: ++ value = data->mfr_vout_max; ++ break; ++ case PSU_MFR_PIN_MAX: ++ value = data->mfr_pin_max; ++ break; ++ case PSU_MFR_POUT_MAX: ++ value = data->mfr_pout_max; ++ break; ++ case PSU_MFR_IOUT_MAX: ++ value = data->mfr_iout_max; ++ break; ++ case PSU_MFR_IIN_MAX: ++ value = data->mfr_iin_max; ++ break; ++ } ++ ++ exponent = two_complement_to_int(value >> 11, 5, 0x1f); ++ mantissa = two_complement_to_int(value & 0x7ff, 11, 0x7ff); ++ ++ return (exponent >= 0) ? sprintf(buf, "%d\n", (mantissa << exponent) * multiplier) : ++ sprintf(buf, "%d\n", (mantissa * multiplier) / (1 << -exponent)); ++} ++ ++static ssize_t show_fan_fault(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ struct ym2651y_data *data = ym2651y_update_device(dev); ++ ++ u8 shift = (attr->index == PSU_FAN1_FAULT) ? 7 : 6; ++ ++ if (!data->valid) { ++ return 0; ++ } ++ ++ return sprintf(buf, "%d\n", data->fan_fault >> shift); ++} ++ ++static ssize_t show_over_temp(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct ym2651y_data *data = ym2651y_update_device(dev); ++ ++ if (!data->valid) { ++ return 0; ++ } ++ ++ return sprintf(buf, "%d\n", data->over_temp >> 7); ++} ++ ++#if USE_I2C_BLOCK_READ ++static ssize_t show_ascii(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ struct ym2651y_data *data = ym2651y_update_device(dev); ++ u8 *ptr = NULL; ++ ++ if (!data->valid) { ++ return 0; ++ } ++ ++ switch (attr->index) { ++ case PSU_FAN_DIRECTION: /* psu_fan_dir */ ++ ptr = data->fan_dir; ++ break; ++ case PSU_MFR_ID: /* psu_mfr_id */ ++ ptr = data->mfr_id; ++ break; ++ case PSU_MFR_MODEL: /* psu_mfr_model */ ++ ptr = data->mfr_model; ++ break; ++ case PSU_MFR_REVISION: /* psu_mfr_revision */ ++ ptr = data->mfr_revsion; ++ break; ++ default: ++ return 0; ++ } ++ ++ return sprintf(buf, "%s\n", ptr); ++} ++#endif ++ ++static const struct attribute_group ym2651y_group = { ++ .attrs = ym2651y_attributes, ++}; ++ ++static int ym2651y_probe(struct i2c_client *client, ++ const struct i2c_device_id *dev_id) ++{ ++ struct ym2651y_data *data; ++ int status; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNCTION)) { ++ status = -EIO; ++ goto exit; ++ } ++ ++ data = kzalloc(sizeof(struct ym2651y_data), GFP_KERNEL); ++ if (!data) { ++ status = -ENOMEM; ++ goto exit; ++ } ++ ++ i2c_set_clientdata(client, data); ++ mutex_init(&data->update_lock); ++ ++ dev_info(&client->dev, "chip found\n"); ++ ++ /* Register sysfs hooks */ ++ status = sysfs_create_group(&client->dev.kobj, &ym2651y_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: psu '%s'\n", ++ dev_name(data->hwmon_dev), client->name); ++ ++ return 0; ++ ++exit_remove: ++ sysfs_remove_group(&client->dev.kobj, &ym2651y_group); ++exit_free: ++ kfree(data); ++exit: ++ ++ return status; ++} ++ ++static int ym2651y_remove(struct i2c_client *client) ++{ ++ struct ym2651y_data *data = i2c_get_clientdata(client); ++ ++ hwmon_device_unregister(data->hwmon_dev); ++ sysfs_remove_group(&client->dev.kobj, &ym2651y_group); ++ kfree(data); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id ym2651y_id[] = { ++ { "ym2651", 0 }, ++ { "ym1921", 1 }, ++ {} ++}; ++MODULE_DEVICE_TABLE(i2c, ym2651y_id); ++ ++static struct i2c_driver ym2651y_driver = { ++ .class = I2C_CLASS_HWMON, ++ .driver = { ++ .name = "ym2651", ++ }, ++ .probe = ym2651y_probe, ++ .remove = ym2651y_remove, ++ .id_table = ym2651y_id, ++ .address_list = normal_i2c, ++}; ++ ++static int ym2651y_read_byte(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_byte_data(client, reg); ++} ++ ++static int ym2651y_read_word(struct i2c_client *client, u8 reg) ++{ ++ return i2c_smbus_read_word_data(client, reg); ++} ++ ++static int ym2651y_write_word(struct i2c_client *client, u8 reg, u16 value) ++{ ++ return i2c_smbus_write_word_data(client, reg, value); ++} ++ ++#if USE_I2C_BLOCK_READ ++static int ym2651y_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; ++} ++#endif ++ ++struct reg_data_byte { ++ u8 reg; ++ u8 *value; ++}; ++ ++struct reg_data_word { ++ u8 reg; ++ u16 *value; ++}; ++ ++static struct ym2651y_data *ym2651y_update_device(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct ym2651y_data *data = i2c_get_clientdata(client); ++ ++ mutex_lock(&data->update_lock); ++ ++ if (time_after(jiffies, data->last_updated + HZ + HZ / 2) ++ || !data->valid) { ++ int i, status; ++ u8 command; ++ u8 fan_dir[5] = {0}; ++ struct reg_data_byte regs_byte[] = { {0x19, &data->capability}, ++ {0x7d, &data->over_temp}, ++ {0x81, &data->fan_fault}, ++ {0x98, &data->pmbus_revision}}; ++ struct reg_data_word regs_word[] = { {0x79, &data->status_word}, ++ {0x8b, &data->v_out}, ++ {0x8c, &data->i_out}, ++ {0x96, &data->p_out}, ++ {0x8d, &data->temp}, ++ {0x3b, &(data->fan_duty_cycle[0])}, ++ {0x3c, &(data->fan_duty_cycle[1])}, ++ {0x90, &data->fan_speed}, ++ {0xa0, &data->mfr_vin_min}, ++ {0xa1, &data->mfr_vin_max}, ++ {0xa2, &data->mfr_iin_max}, ++ {0xa3, &data->mfr_pin_max}, ++ {0xa4, &data->mfr_vout_min}, ++ {0xa5, &data->mfr_vout_max}, ++ {0xa6, &data->mfr_iout_max}, ++ {0xa7, &data->mfr_pout_max}}; ++ ++ dev_dbg(&client->dev, "Starting ym2651 update\n"); ++ data->valid = 0; ++ ++ /* Read byte data */ ++ for (i = 0; i < ARRAY_SIZE(regs_byte); i++) { ++ status = ym2651y_read_byte(client, regs_byte[i].reg); ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "reg %d, err %d\n", regs_byte[i].reg, status); ++ goto exit; ++ } ++ else { ++ *(regs_byte[i].value) = status; ++ } ++ } ++ ++ /* Read word data */ ++ for (i = 0; i < ARRAY_SIZE(regs_word); i++) { ++ status = ym2651y_read_word(client, regs_word[i].reg); ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "reg %d, err %d\n", regs_word[i].reg, status); ++ goto exit; ++ } ++ else { ++ *(regs_word[i].value) = status; ++ } ++ } ++ ++#if USE_I2C_BLOCK_READ ++ /* Read fan_direction */ ++ command = 0xC3; ++ status = ym2651y_read_block(client, command, fan_dir, ARRAY_SIZE(fan_dir)-1); ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "reg %d, err %d\n", command, status); ++ goto exit; ++ } ++ ++ strncpy(data->fan_dir, fan_dir+1, ARRAY_SIZE(data->fan_dir)-1); ++ data->fan_dir[ARRAY_SIZE(data->fan_dir)-1] = '\0'; ++ ++ /* Read mfr_id */ ++ command = 0x99; ++ status = ym2651y_read_block(client, command, data->mfr_id, ++ ARRAY_SIZE(data->mfr_id)-1); ++ data->mfr_id[ARRAY_SIZE(data->mfr_id)-1] = '\0'; ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "reg %d, err %d\n", command, status); ++ goto exit; ++ } ++ ++ /* Read mfr_model */ ++ command = 0x9a; ++ status = ym2651y_read_block(client, command, data->mfr_model, ++ ARRAY_SIZE(data->mfr_model)-1); ++ data->mfr_model[ARRAY_SIZE(data->mfr_model)-1] = '\0'; ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "reg %d, err %d\n", command, status); ++ goto exit; ++ } ++ ++ /* Read mfr_revsion */ ++ command = 0x9b; ++ status = ym2651y_read_block(client, command, data->mfr_revsion, ++ ARRAY_SIZE(data->mfr_revsion)-1); ++ data->mfr_revsion[ARRAY_SIZE(data->mfr_revsion)-1] = '\0'; ++ ++ if (status < 0) { ++ dev_dbg(&client->dev, "reg %d, err %d\n", command, status); ++ goto exit; ++ } ++#endif ++ ++ data->last_updated = jiffies; ++ data->valid = 1; ++ } ++ ++exit: ++ mutex_unlock(&data->update_lock); ++ ++ return data; ++} ++ ++static int __init ym2651y_init(void) ++{ ++ return i2c_add_driver(&ym2651y_driver); ++} ++ ++static void __exit ym2651y_exit(void) ++{ ++ i2c_del_driver(&ym2651y_driver); ++} ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("3Y Power YM-2651Y driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(ym2651y_init); ++module_exit(ym2651y_exit); ++ +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index ff203a4..97d4d54 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -387,6 +387,13 @@ config LEDS_RENESAS_TPU + pin function. The latter to support brightness control. + Brightness control is supported but hardware blinking is not. + ++config LEDS_ACCTON_AS4610 ++ tristate "LED support for the Accton as4610" ++ depends on LEDS_CLASS && SENSORS_ACCTON_I2C_CPLD ++ help ++ This option enables support for the LEDs on the Accton as4610. ++ Say Y to enable LEDs on the Accton as4610. ++ + config LEDS_TRIGGERS + bool "LED Trigger support" + depends on LEDS_CLASS +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index e4f6bf5..db2dab8 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -43,6 +43,7 @@ obj-$(CONFIG_LEDS_NS2) += leds-ns2.o + obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o + obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o + obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o ++obj-$(CONFIG_LEDS_ACCTON_AS4610) += leds-accton_as4610.o + + # LED SPI Drivers + obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o +diff --git a/drivers/leds/leds-accton_as4610.c b/drivers/leds/leds-accton_as4610.c +new file mode 100644 +index 0000000..6f20735 +--- /dev/null ++++ b/drivers/leds/leds-accton_as4610.c +@@ -0,0 +1,719 @@ ++/* ++ * A LED driver for the accton_as4610_led ++ * ++ * Copyright (C) 2016 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 ++#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 "as4610_led" ++ ++struct as4610_led_data { ++ struct platform_device *pdev; ++ struct mutex update_lock; ++ char valid; /* != 0 if registers are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ u8 num_of_led; ++ u8 reg_val[5]; /* Register value, 0 = (0x1A) Blinking function ++ 1 = (0x30) 7-seg 2 ++ 2 = (0x31) 7-seg 1 ++ 3 = (0x32) SYS/PRI/PSU1-2 LED ++ 4 = (0x33) STK1-2/Fan/PoE LED */ ++}; ++ ++static struct as4610_led_data *ledctl = NULL; ++ ++/* LED related data ++ */ ++#define LED_7SEG_REG_MASK 0x0F ++#define LED_7SEG_POINT_REG_MASK 0x10 ++ ++#define LED_NORMAL_MASK 0x03 ++#define LED_NORMAL_GREEN_VALUE 0x02 ++#define LED_NORMAL_AMBER_VALUE 0x01 ++#define LED_NORMAL_OFF_VALUE 0x00 ++ ++#define LED_TYPE_SYS_REG_MASK 0xC0 ++#define LED_MODE_SYS_GREEN_MASK 0x80 ++#define LED_MODE_SYS_AMBER_MASK 0x40 ++#define LED_MODE_SYS_OFF_MASK 0x00 ++#define LED_MODE_SYS_BLINK_MASK 0x80 ++ ++#define LED_TYPE_PRI_REG_MASK 0x30 ++#define LED_MODE_PRI_GREEN_MASK 0x20 ++#define LED_MODE_PRI_AMBER_MASK 0x10 ++#define LED_MODE_PRI_OFF_MASK 0x00 ++#define LED_MODE_PRI_BLINK_MASK 0x40 ++ ++#define LED_TYPE_PSU1_REG_MASK 0x0C ++#define LED_MODE_PSU1_BLINK_MASK 0x20 ++ ++#define LED_TYPE_PSU2_REG_MASK 0x03 ++#define LED_MODE_PSU2_BLINK_MASK 0x10 ++ ++#define LED_TYPE_STK1_REG_MASK 0xC0 ++#define LED_MODE_STK1_GREEN_MASK 0x80 ++#define LED_MODE_STK1_AMBER_MASK 0x40 ++#define LED_MODE_STK1_OFF_MASK 0x00 ++#define LED_MODE_STK1_BLINK_MASK 0x08 ++ ++#define LED_TYPE_STK2_REG_MASK 0x30 ++#define LED_MODE_STK2_GREEN_MASK 0x20 ++#define LED_MODE_STK2_AMBER_MASK 0x10 ++#define LED_MODE_STK2_OFF_MASK 0x00 ++#define LED_MODE_STK2_BLINK_MASK 0x04 ++ ++#define LED_TYPE_FAN_REG_MASK 0x0C ++#define LED_MODE_FAN_BLINK_MASK 0x02 ++ ++#define LED_TYPE_POE_REG_MASK 0x03 ++#define LED_MODE_POE_GREEN_MASK 0x02 ++#define LED_MODE_POE_AMBER_MASK 0x01 ++#define LED_MODE_POE_OFF_MASK 0x00 ++#define LED_MODE_POE_BLINK_MASK 0x01 ++ ++static const u8 led_reg[] = { ++ 0x1A, /* Blinking function */ ++ 0x30, /* 7-seg 1 */ ++ 0x31, /* 7-seg 2 */ ++ 0x32, /* SYS/PRI/PSU1-2 LED */ ++ 0x33, /* STK1-2/Fan/PoE LED */ ++}; ++ ++enum led_type { ++ LED_TYPE_SYS, ++ LED_TYPE_PRI, ++ LED_TYPE_PSU1, ++ LED_TYPE_PSU2, ++ LED_TYPE_STK1, ++ LED_TYPE_STK2, ++ LED_TYPE_7SEG_TENS, ++ LED_TYPE_7SEG_TENS_POINT, ++ LED_TYPE_7SEG_DIGITS, ++ LED_TYPE_7SEG_DIGITS_POINT, ++ LED_TYPE_FAN, ++ LED_TYPE_POE ++}; ++ ++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_AUTO_BLINKING, ++ LED_MODE_UNKNOWN, ++ LED_MODE_SEVEN_SEGMENT_MAX = 9, ++}; ++ ++static int as4610_led_read_value(u8 reg) ++{ ++ return accton_i2c_cpld_read(0x30, reg); ++} ++ ++static int as4610_led_write_value(u8 reg, u8 value) ++{ ++ return accton_i2c_cpld_write(0x30, reg, value); ++} ++ ++static void as4610_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 as4610_led update\n"); ++ ++ /* Update LED data ++ */ ++ for (i = 0; i < ARRAY_SIZE(ledctl->reg_val); i++) { ++ int status = as4610_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 enum led_brightness seven_segment_get(struct led_classdev *cdev, u8 reg_id) ++{ ++ as4610_led_update(); ++ return (ledctl->reg_val[reg_id] & LED_7SEG_REG_MASK); ++#if 0 ++ as4610_led_update(); ++ ++ return (ledctl->reg_val[2] & LED_7SEG_REG_MASK) * 10 + ++ (ledctl->reg_val[1] & LED_7SEG_REG_MASK); ++#endif ++} ++ ++static void seven_segment_set(struct led_classdev *cdev, enum led_brightness mode, u8 reg_id) ++{ ++ if (mode > cdev->max_brightness) { ++ return; ++ } ++ ++ ledctl->reg_val[reg_id] &= 0xF0; ++ ledctl->reg_val[reg_id] |= mode; ++ as4610_led_write_value(led_reg[reg_id], ledctl->reg_val[reg_id]); ++ ++#if 0 ++ u8 digits, tens; ++ ++ if (mode > LED_MODE_SEVEN_SEGMENT_MAX) { ++ return; ++ } ++ ++ digits = mode % 10; ++ tens = mode / 10; ++ ledctl->reg_val[1] &= 0xF0; ++ ledctl->reg_val[1] |= digits; ++ ledctl->reg_val[2] &= 0xF0; ++ ledctl->reg_val[2] |= tens; ++ as4610_led_write_value(led_reg[1], ledctl->reg_val[1]); ++ as4610_led_write_value(led_reg[2], ledctl->reg_val[2]); ++#endif ++} ++ ++static enum led_brightness seven_segment_digits_get(struct led_classdev *cdev) ++{ ++ return seven_segment_get(cdev, 1); ++} ++ ++static void seven_segment_digits_set(struct led_classdev *cdev, enum led_brightness mode) ++{ ++ seven_segment_set(cdev, mode, 1); ++} ++ ++static enum led_brightness seven_segment_tens_get(struct led_classdev *cdev) ++{ ++ return seven_segment_get(cdev, 2); ++} ++ ++static void seven_segment_tens_set(struct led_classdev *cdev, enum led_brightness mode) ++{ ++ seven_segment_set(cdev, mode, 2); ++} ++ ++static enum led_brightness seven_segment_point_get(struct led_classdev *cdev, u8 reg_id) ++{ ++ as4610_led_update(); ++ return (ledctl->reg_val[reg_id] & LED_7SEG_POINT_REG_MASK) ? LED_MODE_GREEN : LED_MODE_OFF; ++} ++ ++static void seven_segment_point_set(struct led_classdev *cdev, ++ enum led_brightness mode, u8 reg_id) ++{ ++ /* Validate brightness */ ++ if ((int)mode < LED_MODE_OFF || mode > cdev->max_brightness) { ++ return; ++ } ++ ++ if ((int)mode == (int)LED_MODE_OFF) { ++ ledctl->reg_val[reg_id] &= ~LED_7SEG_POINT_REG_MASK; ++ } ++ else { /* LED_MODE_GREEN */ ++ ledctl->reg_val[reg_id] |= LED_7SEG_POINT_REG_MASK; ++ } ++ ++ as4610_led_write_value(led_reg[reg_id], ledctl->reg_val[reg_id]); ++} ++ ++static enum led_brightness seven_segment_tens_point_get(struct led_classdev *cdev) ++{ ++ return seven_segment_point_get(cdev, 2); ++} ++ ++static void seven_segment_tens_point_set(struct led_classdev *cdev, ++ enum led_brightness mode) ++{ ++ seven_segment_point_set(cdev, mode, 2); ++} ++ ++static enum led_brightness seven_segment_digits_point_get(struct led_classdev *cdev) ++{ ++ return seven_segment_point_get(cdev, 1); ++} ++ ++static void seven_segment_digits_point_set(struct led_classdev *cdev, ++ enum led_brightness mode) ++{ ++ seven_segment_point_set(cdev, mode, 1); ++} ++ ++static u8 led_is_blinking_mode(enum led_brightness mode) ++{ ++ return ((int)mode == (int)LED_MODE_GREEN_BLINK || ++ (int)mode == (int)LED_MODE_AMBER_BLINK || ++ (int)mode == (int)LED_MODE_AUTO_BLINKING); ++} ++ ++static enum led_brightness as4610_led_auto_get(u8 blink_mask) ++{ ++ as4610_led_update(); ++ return (ledctl->reg_val[0] & blink_mask) ? LED_MODE_AUTO_BLINKING : LED_MODE_AUTO; ++} ++ ++static void as4610_led_auto_set(struct led_classdev *cdev, ++ enum led_brightness mode, u8 blink_mask) ++{ ++ /* Validate brightness */ ++ if ((int)mode < (int)LED_MODE_AUTO || mode > cdev->max_brightness) { ++ return; ++ } ++ ++ /* Set blinking */ ++ if (led_is_blinking_mode(mode)) { ++ ledctl->reg_val[0] |= blink_mask; ++ } ++ else { ++ ledctl->reg_val[0] &= ~blink_mask; ++ } ++ as4610_led_write_value(led_reg[0], ledctl->reg_val[0]); ++} ++ ++static enum led_brightness as4610_led_psu1_get(struct led_classdev *cdev) ++{ ++ return as4610_led_auto_get(LED_MODE_PSU1_BLINK_MASK); ++} ++ ++static void as4610_led_psu1_set(struct led_classdev *cdev, ++ enum led_brightness mode) ++{ ++ as4610_led_auto_set(cdev, mode, LED_MODE_PSU1_BLINK_MASK); ++} ++ ++static enum led_brightness as4610_led_psu2_get(struct led_classdev *cdev) ++{ ++ return as4610_led_auto_get(LED_MODE_PSU2_BLINK_MASK); ++} ++ ++static void as4610_led_psu2_set(struct led_classdev *led_cdev, ++ enum led_brightness mode) ++{ ++ as4610_led_auto_set(led_cdev, mode, LED_MODE_PSU2_BLINK_MASK); ++} ++ ++static enum led_brightness as4610_led_fan_get(struct led_classdev *cdev) ++{ ++ return as4610_led_auto_get(LED_MODE_FAN_BLINK_MASK); ++} ++ ++static void as4610_led_fan_set(struct led_classdev *cdev, ++ enum led_brightness mode) ++{ ++ as4610_led_auto_set(cdev, mode, LED_MODE_FAN_BLINK_MASK); ++} ++ ++static u8 led_normal_light_mode_to_reg_val(enum led_brightness mode) ++{ ++ if (led_is_blinking_mode(mode)) { ++ mode -= 1; /* convert blinking mode to non-blinking mode */ ++ } ++ ++ if ((int)mode == (int)LED_MODE_GREEN) { ++ return LED_NORMAL_GREEN_VALUE; ++ } ++ else if ((int)mode == (int)LED_MODE_AMBER) { ++ return LED_NORMAL_AMBER_VALUE; ++ } ++ ++ return LED_NORMAL_OFF_VALUE; ++} ++ ++static enum led_brightness led_normal_reg_val_to_light_mode(u8 reg_val) ++{ ++ reg_val &= LED_NORMAL_MASK; ++ ++ if (reg_val & LED_NORMAL_GREEN_VALUE) { ++ return LED_MODE_GREEN; ++ } ++ else if (reg_val & LED_NORMAL_AMBER_VALUE) { ++ return LED_MODE_AMBER; ++ } ++ ++ return LED_MODE_OFF; ++} ++ ++static void as4610_led_normal_set(struct led_classdev *cdev, ++ enum led_brightness mode, u8 blink_mask, u8 reg_id, u8 reg_mask, u8 shift) ++{ ++ /* Validate brightness */ ++ if (mode > cdev->max_brightness) { ++ return; ++ } ++ ++ /* Set blinking */ ++ if (led_is_blinking_mode(mode)) { ++ ledctl->reg_val[0] |= blink_mask; ++ } ++ else { ++ ledctl->reg_val[0] &= ~blink_mask; ++ } ++ as4610_led_write_value(led_reg[0], ledctl->reg_val[0]); ++ ++ /* Set color */ ++ ledctl->reg_val[reg_id] &= ~reg_mask; ++ ledctl->reg_val[reg_id] |= (led_normal_light_mode_to_reg_val(mode) << shift); ++ as4610_led_write_value(led_reg[reg_id], ledctl->reg_val[reg_id]); ++} ++ ++static enum led_brightness as4610_led_normal_get(u8 reg_id, u8 blink_mask, u8 shift) ++{ ++ u8 blinking = 0; ++ enum led_brightness mode; ++ ++ as4610_led_update(); ++ ++ mode = led_normal_reg_val_to_light_mode(ledctl->reg_val[reg_id] >> shift); ++ if ((int)mode == (int)LED_MODE_OFF) { ++ return mode; ++ } ++ ++ /* Checking blinking */ ++ if (ledctl->reg_val[0] & blink_mask) { ++ blinking = 1; ++ } ++ ++ return blinking ? (mode+1) : mode; ++} ++ ++static void as4610_led_sys_set(struct led_classdev *cdev, ++ enum led_brightness mode) ++{ ++ as4610_led_normal_set(cdev, mode, LED_MODE_SYS_BLINK_MASK, ++ 3, LED_TYPE_SYS_REG_MASK, 6); ++} ++ ++static enum led_brightness as4610_led_sys_get(struct led_classdev *cdev) ++{ ++ return as4610_led_normal_get(3, LED_MODE_SYS_BLINK_MASK, 6); ++} ++ ++static void as4610_led_pri_set(struct led_classdev *cdev, ++ enum led_brightness mode) ++{ ++ as4610_led_normal_set(cdev, mode, LED_MODE_PRI_BLINK_MASK, ++ 3, LED_TYPE_PRI_REG_MASK, 4); ++} ++ ++static enum led_brightness as4610_led_pri_get(struct led_classdev *cdev) ++{ ++ return as4610_led_normal_get(3, LED_MODE_PRI_BLINK_MASK, 4); ++} ++ ++static void as4610_led_poe_set(struct led_classdev *cdev, ++ enum led_brightness mode) ++{ ++ as4610_led_normal_set(cdev, mode, LED_MODE_POE_BLINK_MASK, ++ 4, LED_TYPE_POE_REG_MASK, 0); ++} ++ ++static enum led_brightness as4610_led_poe_get(struct led_classdev *cdev) ++{ ++ return as4610_led_normal_get(4, LED_MODE_POE_BLINK_MASK, 0); ++} ++ ++static void as4610_led_stk1_set(struct led_classdev *cdev, ++ enum led_brightness mode) ++{ ++ as4610_led_normal_set(cdev, mode, LED_MODE_STK1_BLINK_MASK, ++ 4, LED_TYPE_STK1_REG_MASK, 6); ++} ++ ++static enum led_brightness as4610_led_stk1_get(struct led_classdev *cdev) ++{ ++ return as4610_led_normal_get(4, LED_MODE_STK1_BLINK_MASK, 6); ++} ++ ++static void as4610_led_stk2_set(struct led_classdev *cdev, ++ enum led_brightness mode) ++{ ++ as4610_led_normal_set(cdev, mode, LED_MODE_STK2_BLINK_MASK, ++ 4, LED_TYPE_STK2_REG_MASK, 4); ++} ++ ++static enum led_brightness as4610_led_stk2_get(struct led_classdev *cdev) ++{ ++ return as4610_led_normal_get(4, LED_MODE_STK2_BLINK_MASK, 4); ++} ++ ++static struct led_classdev as4610_leds[] = { ++ [LED_TYPE_SYS] = { ++ .name = "as4610::sys", ++ .default_trigger = "unused", ++ .brightness_set = as4610_led_sys_set, ++ .brightness_get = as4610_led_sys_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AMBER_BLINK, ++ }, ++ [LED_TYPE_PRI] = { ++ .name = "as4610::pri", ++ .default_trigger = "unused", ++ .brightness_set = as4610_led_pri_set, ++ .brightness_get = as4610_led_pri_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AMBER_BLINK, ++ }, ++ [LED_TYPE_PSU1] = { ++ .name = "as4610::psu1", ++ .default_trigger = "unused", ++ .brightness_set = as4610_led_psu1_set, ++ .brightness_get = as4610_led_psu1_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO_BLINKING, ++ }, ++ [LED_TYPE_PSU2] = { ++ .name = "as4610::psu2", ++ .default_trigger = "unused", ++ .brightness_set = as4610_led_psu2_set, ++ .brightness_get = as4610_led_psu2_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO_BLINKING, ++ }, ++ [LED_TYPE_STK1] = { ++ .name = "as4610::stk1", ++ .default_trigger = "unused", ++ .brightness_set = as4610_led_stk1_set, ++ .brightness_get = as4610_led_stk1_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AMBER_BLINK, ++ }, ++ [LED_TYPE_STK2] = { ++ .name = "as4610::stk2", ++ .default_trigger = "unused", ++ .brightness_set = as4610_led_stk2_set, ++ .brightness_get = as4610_led_stk2_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AMBER_BLINK, ++ }, ++ [LED_TYPE_7SEG_TENS] = { ++ .name = "as4610::7seg_tens", ++ .default_trigger = "unused", ++ .brightness_set = seven_segment_tens_set, ++ .brightness_get = seven_segment_tens_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_SEVEN_SEGMENT_MAX, ++ }, ++ [LED_TYPE_7SEG_TENS_POINT] = { ++ .name = "as4610::7seg_tens_point", ++ .default_trigger = "unused", ++ .brightness_set = seven_segment_tens_point_set, ++ .brightness_get = seven_segment_tens_point_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_GREEN, ++ }, ++ [LED_TYPE_7SEG_DIGITS] = { ++ .name = "as4610::7seg_digits", ++ .default_trigger = "unused", ++ .brightness_set = seven_segment_digits_set, ++ .brightness_get = seven_segment_digits_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_SEVEN_SEGMENT_MAX, ++ }, ++ [LED_TYPE_7SEG_DIGITS_POINT] = { ++ .name = "as4610::7seg_digits_point", ++ .default_trigger = "unused", ++ .brightness_set = seven_segment_digits_point_set, ++ .brightness_get = seven_segment_digits_point_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_GREEN, ++ }, ++ [LED_TYPE_FAN] = { ++ .name = "as4610::fan", ++ .default_trigger = "unused", ++ .brightness_set = as4610_led_fan_set, ++ .brightness_get = as4610_led_fan_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AUTO_BLINKING, ++ }, ++ [LED_TYPE_POE] = { ++ .name = "as4610::poe", ++ .default_trigger = "unused", ++ .brightness_set = as4610_led_poe_set, ++ .brightness_get = as4610_led_poe_get, ++ .flags = LED_CORE_SUSPENDRESUME, ++ .max_brightness = LED_MODE_AMBER_BLINK, ++ }, ++}; ++ ++static int as4610_led_suspend(struct platform_device *dev, ++ pm_message_t state) ++{ ++ int i = 0; ++ ++ for (i = 0; i < ledctl->num_of_led; i++) { ++ led_classdev_suspend(&as4610_leds[i]); ++ } ++ ++ return 0; ++} ++ ++static int as4610_led_resume(struct platform_device *dev) ++{ ++ int i = 0; ++ ++ for (i = 0; i < ledctl->num_of_led; i++) { ++ led_classdev_resume(&as4610_leds[i]); ++ } ++ ++ return 0; ++} ++ ++static int as4610_led_probe(struct platform_device *pdev) ++{ ++ int ret = 0, i; ++ ++ for (i = 0; i < ledctl->num_of_led; i++) { ++ ret = led_classdev_register(&pdev->dev, &as4610_leds[i]); ++ ++ if (ret < 0) ++ break; ++ } ++ ++ /* Check if all LEDs were successfully registered */ ++ if (i != ledctl->num_of_led){ ++ int j; ++ ++ /* only unregister the LEDs that were successfully registered */ ++ for (j = 0; j < i; j++) { ++ led_classdev_unregister(&as4610_leds[i]); ++ } ++ } ++ ++ return ret; ++} ++ ++static int as4610_led_remove(struct platform_device *pdev) ++{ ++ int i; ++ ++ for (i = 0; i < ledctl->num_of_led; i++) { ++ led_classdev_unregister(&as4610_leds[i]); ++ } ++ ++ return 0; ++} ++ ++static struct platform_driver as4610_led_driver = { ++ .probe = as4610_led_probe, ++ .remove = as4610_led_remove, ++ .suspend = as4610_led_suspend, ++ .resume = as4610_led_resume, ++ .driver = { ++ .name = DRVNAME, ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++static int __init as4610_led_init(void) ++{ ++ int ret; ++ ++ if (as4610_product_id() == PID_UNKNOWN) { ++ return -ENODEV; ++ } ++ ++ ret = platform_driver_register(&as4610_led_driver); ++ if (ret < 0) { ++ goto exit; ++ } ++ ++ ledctl = kzalloc(sizeof(struct as4610_led_data), GFP_KERNEL); ++ if (!ledctl) { ++ ret = -ENOMEM; ++ platform_driver_unregister(&as4610_led_driver); ++ goto exit; ++ } ++ ++ if (as4610_is_poe_system()) { ++ ledctl->num_of_led = LED_TYPE_POE - LED_TYPE_SYS + 1; ++ } ++ else { ++ ledctl->num_of_led = LED_TYPE_7SEG_DIGITS_POINT - LED_TYPE_SYS + 1; ++ } ++ ++ 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(&as4610_led_driver); ++ kfree(ledctl); ++ goto exit; ++ } ++ ++exit: ++ return ret; ++} ++ ++static void __exit as4610_led_exit(void) ++{ ++ if (!ledctl) { ++ return; ++ } ++ ++ platform_device_unregister(ledctl->pdev); ++ platform_driver_unregister(&as4610_led_driver); ++ kfree(ledctl); ++} ++ ++late_initcall(as4610_led_init); ++module_exit(as4610_led_exit); ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("as4610_led driver"); ++MODULE_LICENSE("GPL"); ++ +diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig +index 7c7b208..1c62b33 100644 +--- a/drivers/misc/eeprom/Kconfig ++++ b/drivers/misc/eeprom/Kconfig +@@ -117,5 +117,14 @@ config EEPROM_SFF_8436 + + This driver can also be built as a module. If so, the module + will be called sff_8436. ++ ++config EEPROM_ACCTON_AS4610_SFP ++ tristate "Accton as4610 sfp" ++ depends on I2C && SENSORS_ACCTON_I2C_CPLD ++ help ++ If you say yes here you get support for Accton as4610 sfp. ++ ++ This driver can also be built as a module. If so, the module will ++ be called accton_as4610_sfp. + + endmenu +diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile +index 9edd559..12f7cae 100644 +--- a/drivers/misc/eeprom/Makefile ++++ b/drivers/misc/eeprom/Makefile +@@ -7,3 +7,4 @@ obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o + obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o + obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o + obj-$(CONFIG_EEPROM_SFF_8436) += sff_8436_eeprom.o ++obj-$(CONFIG_EEPROM_ACCTON_AS4610_SFP) += accton_as4610_sfp.o +diff --git a/drivers/misc/eeprom/accton_as4610_sfp.c b/drivers/misc/eeprom/accton_as4610_sfp.c +new file mode 100644 +index 0000000..51488b4 +--- /dev/null ++++ b/drivers/misc/eeprom/accton_as4610_sfp.c +@@ -0,0 +1,1265 @@ ++/* ++ * SFP driver for accton as4610 sfp ++ * ++ * Copyright (C) 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 ++ ++#define DRIVER_NAME "as4610_sfp" /* Platform dependent */ ++ ++#define DEBUG_MODE 0 ++ ++#if (DEBUG_MODE == 1) ++ #define DEBUG_PRINT(fmt, args...) \ ++ printk (KERN_INFO "%s:%s[%d]: " fmt "\r\n", __FILE__, __FUNCTION__, __LINE__, ##args) ++#else ++ #define DEBUG_PRINT(fmt, args...) ++#endif ++ ++#define NUM_OF_SFP_PORT 32 ++#define EEPROM_NAME "sfp_eeprom" ++#define EEPROM_SIZE 256 /* 256 byte eeprom */ ++#define BIT_INDEX(i) (1ULL << (i)) ++#define USE_I2C_BLOCK_READ 0 /* Platform dependent */ ++#define I2C_RW_RETRY_COUNT 3 ++#define I2C_RW_RETRY_INTERVAL 100 /* ms */ ++ ++#define SFP_EEPROM_A0_I2C_ADDR (0xA0 >> 1) ++#define SFP_EEPROM_A2_I2C_ADDR (0xA2 >> 1) ++ ++#define SFF8024_PHYSICAL_DEVICE_ID_ADDR 0x0 ++#define SFF8024_DEVICE_ID_SFP 0x3 ++#define SFF8024_DEVICE_ID_QSFP 0xC ++#define SFF8024_DEVICE_ID_QSFP_PLUS 0xD ++#define SFF8024_DEVICE_ID_QSFP28 0x11 ++ ++#define SFF8472_DIAG_MON_TYPE_ADDR 92 ++#define SFF8472_DIAG_MON_TYPE_DDM_MASK 0x40 ++#define SFF8472_10G_ETH_COMPLIANCE_ADDR 0x3 ++#define SFF8472_10G_BASE_MASK 0xF0 ++ ++#define SFF8436_RX_LOS_ADDR 3 ++#define SFF8436_TX_FAULT_ADDR 4 ++#define SFF8436_TX_DISABLE_ADDR 86 ++ ++static ssize_t show_port_number(struct device *dev, struct device_attribute *da, char *buf); ++static ssize_t show_port_type(struct device *dev, struct device_attribute *da, char *buf); ++static ssize_t show_present(struct device *dev, struct device_attribute *da, char *buf); ++static ssize_t sfp_show_tx_rx_status(struct device *dev, struct device_attribute *da, char *buf); ++static ssize_t qsfp_show_tx_rx_status(struct device *dev, struct device_attribute *da, char *buf); ++static ssize_t sfp_set_tx_disable(struct device *dev, struct device_attribute *da, const char *buf, size_t count); ++static ssize_t qsfp_set_tx_disable(struct device *dev, struct device_attribute *da, const char *buf, size_t count);; ++static ssize_t sfp_show_ddm_implemented(struct device *dev, struct device_attribute *da, char *buf); ++static ssize_t sfp_eeprom_read(struct i2c_client *, u8, u8 *,int); ++static ssize_t sfp_eeprom_write(struct i2c_client *, u8 , const char *,int); ++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 sfp_sysfs_attributes { ++ PRESENT, ++ PRESENT_ALL, ++ PORT_NUMBER, ++ PORT_TYPE, ++ DDM_IMPLEMENTED, ++ TX_FAULT, ++ TX_FAULT1, ++ TX_FAULT2, ++ TX_FAULT3, ++ TX_FAULT4, ++ TX_DISABLE, ++ TX_DISABLE1, ++ TX_DISABLE2, ++ TX_DISABLE3, ++ TX_DISABLE4, ++ RX_LOS, ++ RX_LOS1, ++ RX_LOS2, ++ RX_LOS3, ++ RX_LOS4, ++ RX_LOS_ALL ++}; ++ ++/* SFP/QSFP common attributes for sysfs */ ++static SENSOR_DEVICE_ATTR(sfp_port_number, S_IRUGO, show_port_number, NULL, PORT_NUMBER); ++static SENSOR_DEVICE_ATTR(sfp_port_type, S_IRUGO, show_port_type, NULL, PORT_TYPE); ++static SENSOR_DEVICE_ATTR(sfp_is_present, S_IRUGO, show_present, NULL, PRESENT); ++static SENSOR_DEVICE_ATTR(sfp_is_present_all, S_IRUGO, show_present, NULL, PRESENT_ALL); ++static SENSOR_DEVICE_ATTR(sfp_rx_los, S_IRUGO, sfp_show_tx_rx_status, NULL, RX_LOS); ++static SENSOR_DEVICE_ATTR(sfp_tx_disable, S_IWUSR | S_IRUGO, sfp_show_tx_rx_status, sfp_set_tx_disable, TX_DISABLE); ++static SENSOR_DEVICE_ATTR(sfp_tx_fault, S_IRUGO, sfp_show_tx_rx_status, NULL, TX_FAULT); ++ ++/* QSFP attributes for sysfs */ ++static SENSOR_DEVICE_ATTR(sfp_rx_los1, S_IRUGO, qsfp_show_tx_rx_status, NULL, RX_LOS1); ++static SENSOR_DEVICE_ATTR(sfp_rx_los2, S_IRUGO, qsfp_show_tx_rx_status, NULL, RX_LOS2); ++static SENSOR_DEVICE_ATTR(sfp_rx_los3, S_IRUGO, qsfp_show_tx_rx_status, NULL, RX_LOS3); ++static SENSOR_DEVICE_ATTR(sfp_rx_los4, S_IRUGO, qsfp_show_tx_rx_status, NULL, RX_LOS4); ++static SENSOR_DEVICE_ATTR(sfp_tx_disable1, S_IWUSR | S_IRUGO, qsfp_show_tx_rx_status, qsfp_set_tx_disable, TX_DISABLE1); ++static SENSOR_DEVICE_ATTR(sfp_tx_disable2, S_IWUSR | S_IRUGO, qsfp_show_tx_rx_status, qsfp_set_tx_disable, TX_DISABLE2); ++static SENSOR_DEVICE_ATTR(sfp_tx_disable3, S_IWUSR | S_IRUGO, qsfp_show_tx_rx_status, qsfp_set_tx_disable, TX_DISABLE3); ++static SENSOR_DEVICE_ATTR(sfp_tx_disable4, S_IWUSR | S_IRUGO, qsfp_show_tx_rx_status, qsfp_set_tx_disable, TX_DISABLE4); ++static SENSOR_DEVICE_ATTR(sfp_tx_fault1, S_IRUGO, qsfp_show_tx_rx_status, NULL, TX_FAULT1); ++static SENSOR_DEVICE_ATTR(sfp_tx_fault2, S_IRUGO, qsfp_show_tx_rx_status, NULL, TX_FAULT2); ++static SENSOR_DEVICE_ATTR(sfp_tx_fault3, S_IRUGO, qsfp_show_tx_rx_status, NULL, TX_FAULT3); ++static SENSOR_DEVICE_ATTR(sfp_tx_fault4, S_IRUGO, qsfp_show_tx_rx_status, NULL, TX_FAULT4); ++static struct attribute *qsfp_attributes[] = { ++ &sensor_dev_attr_sfp_port_number.dev_attr.attr, ++ &sensor_dev_attr_sfp_port_type.dev_attr.attr, ++ &sensor_dev_attr_sfp_is_present.dev_attr.attr, ++ &sensor_dev_attr_sfp_is_present_all.dev_attr.attr, ++ &sensor_dev_attr_sfp_rx_los.dev_attr.attr, ++ &sensor_dev_attr_sfp_rx_los1.dev_attr.attr, ++ &sensor_dev_attr_sfp_rx_los2.dev_attr.attr, ++ &sensor_dev_attr_sfp_rx_los3.dev_attr.attr, ++ &sensor_dev_attr_sfp_rx_los4.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_disable.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_disable1.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_disable2.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_disable3.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_disable4.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_fault.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_fault1.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_fault2.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_fault3.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_fault4.dev_attr.attr, ++ NULL ++}; ++ ++/* SFP msa attributes for sysfs */ ++static SENSOR_DEVICE_ATTR(sfp_ddm_implemented, S_IRUGO, sfp_show_ddm_implemented, NULL, DDM_IMPLEMENTED); ++static SENSOR_DEVICE_ATTR(sfp_rx_los_all, S_IRUGO, sfp_show_tx_rx_status, NULL, RX_LOS_ALL); ++static struct attribute *sfp_msa_attributes[] = { ++ &sensor_dev_attr_sfp_port_number.dev_attr.attr, ++ &sensor_dev_attr_sfp_port_type.dev_attr.attr, ++ &sensor_dev_attr_sfp_is_present.dev_attr.attr, ++ &sensor_dev_attr_sfp_is_present_all.dev_attr.attr, ++ &sensor_dev_attr_sfp_ddm_implemented.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_fault.dev_attr.attr, ++ &sensor_dev_attr_sfp_rx_los.dev_attr.attr, ++ &sensor_dev_attr_sfp_rx_los_all.dev_attr.attr, ++ &sensor_dev_attr_sfp_tx_disable.dev_attr.attr, ++ NULL ++}; ++ ++/* SFP ddm attributes for sysfs */ ++static struct attribute *sfp_ddm_attributes[] = { ++ NULL ++}; ++ ++/* Platform dependent +++ */ ++enum port_numbers { ++as4610_sfp1, /* Port 25 for as4610_30, Port 49 for as4610_54 */ ++as4610_sfp2, /* Port 26 for as4610_30, Port 50 for as4610_54 */ ++as4610_sfp3, /* Port 27 for as4610_30, Port 51 for as4610_54 */ ++as4610_sfp4, /* Port 28 for as4610_30, Port 52 for as4610_54 */ ++as4610_sfp5, /* Port 29 for as4610_30, Port 53 for as4610_54 */ ++as4610_sfp6, /* Port 30 for as4610_30, Port 54 for as4610_54 */ ++}; ++ ++static const struct i2c_device_id sfp_device_id[] = { ++{"as4610_sfp1", as4610_sfp1}, ++{"as4610_sfp2", as4610_sfp2}, ++{"as4610_sfp3", as4610_sfp3}, ++{"as4610_sfp4", as4610_sfp4}, ++{"as4610_sfp5", as4610_sfp5}, ++{"as4610_sfp6", as4610_sfp6}, ++{ /* LIST END */ } ++}; ++MODULE_DEVICE_TABLE(i2c, sfp_device_id); ++/* Platform dependent --- */ ++ ++/* ++ * list of valid port types ++ * note OOM_PORT_TYPE_NOT_PRESENT to indicate no ++ * module is present in this port ++ */ ++typedef enum oom_driver_port_type_e { ++ OOM_DRIVER_PORT_TYPE_INVALID, ++ OOM_DRIVER_PORT_TYPE_NOT_PRESENT, ++ OOM_DRIVER_PORT_TYPE_SFP, ++ OOM_DRIVER_PORT_TYPE_SFP_PLUS, ++ OOM_DRIVER_PORT_TYPE_QSFP, ++ OOM_DRIVER_PORT_TYPE_QSFP_PLUS, ++ OOM_DRIVER_PORT_TYPE_QSFP28 ++} oom_driver_port_type_t; ++ ++enum driver_type_e { ++ DRIVER_TYPE_SFP_MSA, ++ DRIVER_TYPE_SFP_DDM, ++ DRIVER_TYPE_QSFP ++}; ++ ++/* Each client has this additional data ++ */ ++struct eeprom_data { ++ char valid; /* !=0 if registers are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ struct bin_attribute bin; /* eeprom data */ ++}; ++ ++struct sfp_msa_data { ++ char valid; /* !=0 if registers are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ u64 status[6]; /* bit0:port0, bit1:port1 and so on */ ++ /* index 0 => tx_fail ++ 1 => tx_disable ++ 2 => rx_loss ++ 3 => device id ++ 4 => 10G Ethernet Compliance Codes ++ to distinguish SFP or SFP+ ++ 5 => DIAGNOSTIC MONITORING TYPE */ ++ struct eeprom_data eeprom; ++}; ++ ++struct sfp_ddm_data { ++ struct eeprom_data eeprom; ++}; ++ ++struct qsfp_data { ++ char valid; /* !=0 if registers are valid */ ++ unsigned long last_updated; /* In jiffies */ ++ u8 status[3]; /* bit0:port0, bit1:port1 and so on */ ++ /* index 0 => tx_fail ++ 1 => tx_disable ++ 2 => rx_loss */ ++ ++ u8 device_id; ++ struct eeprom_data eeprom; ++}; ++ ++struct sfp_port_data { ++ struct mutex update_lock; ++ enum driver_type_e driver_type; ++ int port; /* CPLD port index */ ++ oom_driver_port_type_t port_type; ++ u64 present; /* present status, bit0:port0, bit1:port1 and so on */ ++ ++ struct sfp_msa_data *msa; ++ struct sfp_ddm_data *ddm; ++ struct qsfp_data *qsfp; ++ ++ struct i2c_client *client; ++}; ++ ++static ssize_t show_port_number(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ ++ return sprintf(buf, "sfp %d\n", data->port); ++} ++ ++/* Platform dependent +++ */ ++static struct sfp_port_data *sfp_update_present(struct i2c_client *client) ++{ ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ int i = 0; ++ int status = -1; ++ u8 regs[] = {0x2, 0x3, 0x21}; ++ ++ DEBUG_PRINT("Starting sfp present status update"); ++ mutex_lock(&data->update_lock); ++ ++ /* Read present status of port 49~54 */ ++ data->present = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(regs); i++) { ++ status = accton_i2c_cpld_read(0x30, regs[i]); ++ ++ if (status < 0) { ++ DEBUG_PRINT("cpld(0x30) reg(0x%x) err %d", regs[i], status); ++ goto exit; ++ } ++ ++ DEBUG_PRINT("Present status = 0x%lx", data->present); ++ switch (i) { ++ case 0: ++ data->present |= (status & BIT_INDEX(6)) >> 6; /* port 25/49 */ ++ data->present |= (status & BIT_INDEX(2)) >> 1; /* port 26/50 */ ++ break; ++ case 1: ++ data->present |= (status & BIT_INDEX(6)) >> 4; /* port 27/51 */ ++ data->present |= (status & BIT_INDEX(2)) << 1; /* port 28/52 */ ++ break; ++ case 2: ++ data->present |= (status & BIT_INDEX(0)) << 4; /* port 29/53 */ ++ data->present |= (status & BIT_INDEX(4)) << 1; /* port 30/54 */ ++ break; ++ default: ++ break; ++ } ++ } ++ ++ DEBUG_PRINT("Present status = 0x%lx", data->present); ++exit: ++ mutex_unlock(&data->update_lock); ++ return data; ++} ++ ++static struct sfp_port_data *sfp_update_tx_rx_status(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ int i = 0; ++ int status = -1; ++ u8 regs[] = {0x2, 0x3, 0x21}; ++ ++ if (time_before(jiffies, data->msa->last_updated + HZ + HZ / 2) && data->msa->valid) { ++ return data; ++ } ++ ++ DEBUG_PRINT("Starting as4610 sfp tx rx status update"); ++ mutex_lock(&data->update_lock); ++ data->msa->valid = 0; ++ memset(data->msa->status, 0, sizeof(data->msa->status)); ++ ++ /* Read status of port 49~52(SFP port) */ ++ for (i = 0; i < ARRAY_SIZE(regs); i++) { ++ status = accton_i2c_cpld_read(0x30, regs[i]); ++ ++ if (status < 0) { ++ DEBUG_PRINT("cpld(0x30) reg(0x%x) err %d", regs[i], status); ++ goto exit; ++ } ++ ++ DEBUG_PRINT("TX_FAULT = 0x%lx, TX_DISABLE = 0x%lx, TX_FAULT = 0x%lx", ++ data->msa->status[0], data->msa->status[1], data->msa->status[2]); ++ switch (i) { ++ case 0: ++ /* port 25/49 */ ++ data->msa->status[0] |= (status & BIT_INDEX(5)) >> 5; /* tx_fail */ ++ data->msa->status[2] |= (status & BIT_INDEX(4)) >> 4; /* rx_los */ ++ /* port 26/50 */ ++ data->msa->status[0] |= (status & BIT_INDEX(1)) << 0; /* tx_fail */ ++ data->msa->status[2] |= (status & BIT_INDEX(0)) << 1; /* rx_los */ ++ break; ++ case 1: ++ /* port 27/51 */ ++ data->msa->status[0] |= (status & BIT_INDEX(5)) >> 3; /* tx_fail */ ++ data->msa->status[2] |= (status & BIT_INDEX(4)) >> 2; /* rx_los */ ++ /* port 28/52 */ ++ data->msa->status[0] |= (status & BIT_INDEX(1)) << 1; /* tx_fail */ ++ data->msa->status[2] |= (status & BIT_INDEX(0)) << 2; /* rx_los */ ++ break; ++ default: ++ break; ++ } ++ } ++ ++ DEBUG_PRINT("TX_FAULT = 0x%lx, TX_DISABLE = 0x%lx, TX_FAULT = 0x%lx", ++ data->msa->status[0], data->msa->status[1], data->msa->status[2]); ++ data->msa->valid = 1; ++ data->msa->last_updated = jiffies; ++ ++exit: ++ mutex_unlock(&data->update_lock); ++ return data; ++} ++/* Platform dependent --- */ ++ ++static ssize_t sfp_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 sfp_port_data *data = i2c_get_clientdata(client); ++ ++ if (data->driver_type == DRIVER_TYPE_QSFP) { ++ return qsfp_set_tx_disable(dev, da, buf, count); ++ } ++ ++ return 0; ++} ++ ++static int sfp_is_port_present(struct i2c_client *client, int port) ++{ ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ ++ data = sfp_update_present(client); ++ if (IS_ERR(data)) { ++ return PTR_ERR(data); ++ } ++ ++ return (data->present & BIT_INDEX(data->port)) ? 1 : 0; /* Platform dependent */ ++} ++ ++/* Platform dependent +++ */ ++static ssize_t show_present(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ struct i2c_client *client = to_i2c_client(dev); ++ ++ if (PRESENT_ALL == attr->index) { ++ struct sfp_port_data *data = sfp_update_present(client); ++ ++ if (IS_ERR(data)) { ++ return PTR_ERR(data); ++ } ++ ++ /* Return values 25/49 -> 30/54 in order */ ++ return sprintf(buf, "%.2x\n", (u8)(data->present)); ++ } ++ else { ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ int present = sfp_is_port_present(client, data->port); ++ ++ if (IS_ERR_VALUE(present)) { ++ return present; ++ } ++ ++ /* PRESENT */ ++ return sprintf(buf, "%d\n", present); ++ } ++} ++/* Platform dependent --- */ ++ ++static struct sfp_port_data *sfp_update_port_type(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ u8 buf = 0; ++ int status; ++ ++ mutex_lock(&data->update_lock); ++ ++ switch (data->driver_type) { ++ case DRIVER_TYPE_SFP_MSA: ++ { ++ status = sfp_eeprom_read(client, SFF8024_PHYSICAL_DEVICE_ID_ADDR, &buf, sizeof(buf)); ++ if (unlikely(status < 0)) { ++ data->port_type = OOM_DRIVER_PORT_TYPE_INVALID; ++ break; ++ } ++ ++ if (buf != SFF8024_DEVICE_ID_SFP) { ++ data->port_type = OOM_DRIVER_PORT_TYPE_INVALID; ++ break; ++ } ++ ++ status = sfp_eeprom_read(client, SFF8472_10G_ETH_COMPLIANCE_ADDR, &buf, sizeof(buf)); ++ if (unlikely(status < 0)) { ++ data->port_type = OOM_DRIVER_PORT_TYPE_INVALID; ++ break; ++ } ++ ++ DEBUG_PRINT("sfp port type (0x3) data = (0x%x)", buf); ++ data->port_type = buf & SFF8472_10G_BASE_MASK ? OOM_DRIVER_PORT_TYPE_SFP_PLUS : OOM_DRIVER_PORT_TYPE_SFP; ++ break; ++ } ++ case DRIVER_TYPE_QSFP: ++ { ++ status = sfp_eeprom_read(client, SFF8024_PHYSICAL_DEVICE_ID_ADDR, &buf, sizeof(buf)); ++ if (unlikely(status < 0)) { ++ data->port_type = OOM_DRIVER_PORT_TYPE_INVALID; ++ break; ++ } ++ ++ DEBUG_PRINT("qsfp port type (0x0) buf = (0x%x)", buf); ++ switch (buf) { ++ case SFF8024_DEVICE_ID_QSFP: ++ data->port_type = OOM_DRIVER_PORT_TYPE_QSFP; ++ break; ++ case SFF8024_DEVICE_ID_QSFP_PLUS: ++ data->port_type = OOM_DRIVER_PORT_TYPE_QSFP_PLUS; ++ break; ++ case SFF8024_DEVICE_ID_QSFP28: ++ data->port_type = OOM_DRIVER_PORT_TYPE_QSFP_PLUS; ++ break; ++ default: ++ data->port_type = OOM_DRIVER_PORT_TYPE_INVALID; ++ break; ++ } ++ ++ break; ++ } ++ default: ++ break; ++ } ++ ++ mutex_unlock(&data->update_lock); ++ return data; ++} ++ ++static ssize_t show_port_type(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ int present = sfp_is_port_present(client, data->port); ++ ++ if (IS_ERR_VALUE(present)) { ++ return present; ++ } ++ ++ if (!present) { ++ return sprintf(buf, "%d\n", OOM_DRIVER_PORT_TYPE_NOT_PRESENT); ++ } ++ ++ sfp_update_port_type(dev); ++ return sprintf(buf, "%d\n", data->port_type); ++} ++ ++static struct sfp_port_data *qsfp_update_tx_rx_status(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ int i, status = -1; ++ u8 buf = 0; ++ u8 reg[] = {SFF8436_TX_FAULT_ADDR, SFF8436_TX_DISABLE_ADDR, SFF8436_RX_LOS_ADDR}; ++ ++ if (time_before(jiffies, data->qsfp->last_updated + HZ + HZ / 2) && data->qsfp->valid) { ++ return data; ++ } ++ ++ DEBUG_PRINT("Starting sfp tx rx status update"); ++ mutex_lock(&data->update_lock); ++ data->qsfp->valid = 0; ++ memset(data->qsfp->status, 0, sizeof(data->qsfp->status)); ++ ++ /* Notify device to update tx fault/ tx disable/ rx los status */ ++ for (i = 0; i < ARRAY_SIZE(reg); i++) { ++ status = sfp_eeprom_read(client, reg[i], &buf, sizeof(buf)); ++ if (unlikely(status < 0)) { ++ data = ERR_PTR(status); ++ goto exit; ++ } ++ } ++ msleep(200); ++ ++ /* Read actual tx fault/ tx disable/ rx los status */ ++ for (i = 0; i < ARRAY_SIZE(reg); i++) { ++ status = sfp_eeprom_read(client, reg[i], &buf, sizeof(buf)); ++ if (unlikely(status < 0)) { ++ data = ERR_PTR(status); ++ goto exit; ++ } ++ ++ DEBUG_PRINT("qsfp reg(0x%x) status = (0x%x)", reg[i], data->qsfp->status[i]); ++ data->qsfp->status[i] = (buf & 0xF); ++ } ++ ++ data->qsfp->valid = 1; ++ data->qsfp->last_updated = jiffies; ++ ++exit: ++ mutex_unlock(&data->update_lock); ++ return data; ++} ++ ++static ssize_t qsfp_show_tx_rx_status(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ int present; ++ u8 val = 0; ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ struct i2c_client *client = to_i2c_client(dev); ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ ++ present = sfp_is_port_present(client, data->port); ++ if (IS_ERR_VALUE(present)) { ++ return present; ++ } ++ ++ if (present == 0) { ++ /* port is not present */ ++ return -ENODEV; ++ } ++ ++ data = qsfp_update_tx_rx_status(dev); ++ if (IS_ERR(data)) { ++ return PTR_ERR(data); ++ } ++ ++ switch (attr->index) { ++ case TX_FAULT: ++ val = (data->qsfp->status[2] & 0xF) ? 1 : 0; ++ break; ++ case TX_FAULT1: ++ case TX_FAULT2: ++ case TX_FAULT3: ++ case TX_FAULT4: ++ val = (data->qsfp->status[2] & BIT_INDEX(attr->index - TX_FAULT1)) ? 1 : 0; ++ break; ++ case TX_DISABLE: ++ val = (data->qsfp->status[1] & 0xF) ? 1 : 0; ++ break; ++ case TX_DISABLE1: ++ case TX_DISABLE2: ++ case TX_DISABLE3: ++ case TX_DISABLE4: ++ val = (data->qsfp->status[1] & BIT_INDEX(attr->index - TX_DISABLE1)) ? 1 : 0; ++ break; ++ case RX_LOS: ++ val = (data->qsfp->status[0] & 0xF) ? 1 : 0; ++ break; ++ case RX_LOS1: ++ case RX_LOS2: ++ case RX_LOS3: ++ case RX_LOS4: ++ val = (data->qsfp->status[0] & BIT_INDEX(attr->index - RX_LOS1)) ? 1 : 0; ++ break; ++ default: ++ break; ++ } ++ ++ return sprintf(buf, "%d\n", val); ++} ++ ++static ssize_t qsfp_set_tx_disable(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ long disable; ++ int status; ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ struct sfp_port_data *data = NULL; ++ ++ status = kstrtol(buf, 10, &disable); ++ if (status) { ++ return status; ++ } ++ ++ data = qsfp_update_tx_rx_status(dev); ++ if (IS_ERR(data)) { ++ return PTR_ERR(data); ++ } ++ ++ mutex_lock(&data->update_lock); ++ ++ if (attr->index == TX_DISABLE) { ++ if (disable) { ++ data->qsfp->status[1] |= 0xF; ++ } ++ else { ++ data->qsfp->status[1] &= ~0xF; ++ } ++ } ++ else {/* TX_DISABLE1 ~ TX_DISABLE4*/ ++ if (disable) { ++ data->qsfp->status[1] |= (1 << (attr->index - TX_DISABLE1)); ++ } ++ else { ++ data->qsfp->status[1] &= ~(1 << (attr->index - TX_DISABLE1)); ++ } ++ } ++ ++ DEBUG_PRINT("index = (%d), status = (0x%x)", attr->index, data->qsfp->status[1]); ++ status = sfp_eeprom_write(data->client, SFF8436_TX_DISABLE_ADDR, &data->qsfp->status[1], sizeof(data->qsfp->status[1])); ++ if (unlikely(status < 0)) { ++ count = status; ++ } ++ ++ mutex_unlock(&data->update_lock); ++ return count; ++} ++ ++static ssize_t sfp_show_ddm_implemented(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ int status; ++ char ddm; ++ struct i2c_client *client = to_i2c_client(dev); ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ ++ status = sfp_is_port_present(client, data->port); ++ if (IS_ERR_VALUE(status)) { ++ return status; ++ } ++ ++ if (status == 0) { ++ /* port is not present */ ++ return -ENODEV; ++ } ++ ++ status = sfp_eeprom_read(client, SFF8472_DIAG_MON_TYPE_ADDR, &ddm, sizeof(ddm)); ++ if (unlikely(status < 0)) { ++ return status; ++ } ++ ++ return sprintf(buf, "%d\n", (ddm & SFF8472_DIAG_MON_TYPE_DDM_MASK) ? 1 : 0); ++} ++ ++/* Platform dependent +++ */ ++static ssize_t sfp_show_tx_rx_status(struct device *dev, struct device_attribute *da, ++ char *buf) ++{ ++ u8 val = 0, index = 0; ++ struct i2c_client *client = to_i2c_client(dev); ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ struct sensor_device_attribute *attr = to_sensor_dev_attr(da); ++ ++ if (data->driver_type == DRIVER_TYPE_QSFP) { ++ return qsfp_show_tx_rx_status(dev, da, buf); ++ } ++ ++ data = sfp_update_tx_rx_status(dev); ++ if (IS_ERR(data)) { ++ return PTR_ERR(data); ++ } ++ ++ if(attr->index == RX_LOS_ALL) { ++ /** Return values 25/49 -> 28/52 in order */ ++ return sprintf(buf, "%.2x\n", (u8)(data->msa->status[2])); ++ } ++ ++ switch (attr->index) { ++ case TX_FAULT: ++ index = 0; ++ break; ++ case TX_DISABLE: ++ index = 1; ++ break; ++ case RX_LOS: ++ index = 2; ++ break; ++ default: ++ return 0; ++ } ++ ++ val = (data->msa->status[index] & BIT_INDEX(data->port)) ? 1 : 0; ++ return sprintf(buf, "%d\n", val); ++} ++/* Platform dependent --- */ ++static ssize_t sfp_eeprom_write(struct i2c_client *client, u8 command, const char *data, ++ int data_len) ++{ ++#if USE_I2C_BLOCK_READ ++ int status, retry = I2C_RW_RETRY_COUNT; ++ ++ if (data_len > I2C_SMBUS_BLOCK_MAX) { ++ data_len = I2C_SMBUS_BLOCK_MAX; ++ } ++ ++ while (retry) { ++ status = i2c_smbus_write_i2c_block_data(client, command, data_len, data); ++ if (unlikely(status < 0)) { ++ msleep(I2C_RW_RETRY_INTERVAL); ++ retry--; ++ continue; ++ } ++ ++ break; ++ } ++ ++ if (unlikely(status < 0)) { ++ return status; ++ } ++ ++ return data_len; ++#else ++ int status, retry = I2C_RW_RETRY_COUNT; ++ ++ while (retry) { ++ status = i2c_smbus_write_byte_data(client, command, *data); ++ if (unlikely(status < 0)) { ++ msleep(I2C_RW_RETRY_INTERVAL); ++ retry--; ++ continue; ++ } ++ ++ break; ++ } ++ ++ if (unlikely(status < 0)) { ++ return status; ++ } ++ ++ return 1; ++#endif ++ ++ ++} ++ ++static ssize_t sfp_port_write(struct sfp_port_data *data, ++ const char *buf, loff_t off, size_t count) ++{ ++ ssize_t retval = 0; ++ ++ if (unlikely(!count)) { ++ return count; ++ } ++ ++ /* ++ * Write data to chip, protecting against concurrent updates ++ * from this host, but not from other I2C masters. ++ */ ++ mutex_lock(&data->update_lock); ++ ++ while (count) { ++ ssize_t status; ++ ++ status = sfp_eeprom_write(data->client, off, buf, count); ++ if (status <= 0) { ++ if (retval == 0) { ++ retval = status; ++ } ++ break; ++ } ++ buf += status; ++ off += status; ++ count -= status; ++ retval += status; ++ } ++ ++ mutex_unlock(&data->update_lock); ++ return retval; ++} ++ ++ ++static ssize_t sfp_bin_write(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, ++ char *buf, loff_t off, size_t count) ++{ ++ int present; ++ struct sfp_port_data *data; ++ DEBUG_PRINT("%s(%d) offset = (%d), count = (%d)", off, count); ++ data = dev_get_drvdata(container_of(kobj, struct device, kobj)); ++ ++ present = sfp_is_port_present(data->client, data->port); ++ if (IS_ERR_VALUE(present)) { ++ return present; ++ } ++ ++ if (present == 0) { ++ /* port is not present */ ++ return -ENODEV; ++ } ++ ++ return sfp_port_write(data, buf, off, count); ++} ++ ++static ssize_t sfp_eeprom_read(struct i2c_client *client, u8 command, u8 *data, ++ int data_len) ++{ ++#if USE_I2C_BLOCK_READ ++ int status, retry = I2C_RW_RETRY_COUNT; ++ ++ if (data_len > I2C_SMBUS_BLOCK_MAX) { ++ data_len = I2C_SMBUS_BLOCK_MAX; ++ } ++ ++ while (retry) { ++ status = i2c_smbus_read_i2c_block_data(client, command, data_len, data); ++ if (unlikely(status < 0)) { ++ msleep(I2C_RW_RETRY_INTERVAL); ++ retry--; ++ continue; ++ } ++ ++ break; ++ } ++ ++ if (unlikely(status < 0)) { ++ goto abort; ++ } ++ if (unlikely(status != data_len)) { ++ status = -EIO; ++ goto abort; ++ } ++ ++ //result = data_len; ++ ++abort: ++ return status; ++#else ++ int status, retry = I2C_RW_RETRY_COUNT; ++ ++ while (retry) { ++ status = i2c_smbus_read_byte_data(client, command); ++ if (unlikely(status < 0)) { ++ msleep(I2C_RW_RETRY_INTERVAL); ++ retry--; ++ continue; ++ } ++ ++ break; ++ } ++ ++ if (unlikely(status < 0)) { ++ dev_dbg(&client->dev, "sfp read byte data failed, command(0x%2x), data(0x%2x)\r\n", command, status); ++ goto abort; ++ } ++ ++ *data = (u8)status; ++ status = 1; ++ ++abort: ++ return status; ++#endif ++} ++ ++static ssize_t sfp_port_read(struct sfp_port_data *data, ++ char *buf, loff_t off, size_t count) ++{ ++ ssize_t retval = 0; ++ ++ if (unlikely(!count)) { ++ DEBUG_PRINT("Count = 0, return"); ++ return count; ++ } ++ ++ /* ++ * Read data from chip, protecting against concurrent updates ++ * from this host, but not from other I2C masters. ++ */ ++ mutex_lock(&data->update_lock); ++ ++ while (count) { ++ ssize_t status; ++ ++ status = sfp_eeprom_read(data->client, off, buf, count); ++ if (status <= 0) { ++ if (retval == 0) { ++ retval = status; ++ } ++ break; ++ } ++ ++ buf += status; ++ off += status; ++ count -= status; ++ retval += status; ++ } ++ ++ mutex_unlock(&data->update_lock); ++ return retval; ++ ++} ++ ++static ssize_t sfp_bin_read(struct file *filp, struct kobject *kobj, ++ struct bin_attribute *attr, ++ char *buf, loff_t off, size_t count) ++{ ++ int present; ++ struct sfp_port_data *data; ++ DEBUG_PRINT("offset = (%d), count = (%d)", off, count); ++ data = dev_get_drvdata(container_of(kobj, struct device, kobj)); ++ ++ present = sfp_is_port_present(data->client, data->port); ++ if (IS_ERR_VALUE(present)) { ++ return present; ++ } ++ ++ if (present == 0) { ++ /* port is not present */ ++ return -ENODEV; ++ } ++ ++ return sfp_port_read(data, buf, off, count); ++} ++ ++static int sfp_sysfs_eeprom_init(struct kobject *kobj, struct bin_attribute *eeprom) ++{ ++ int err; ++ ++ sysfs_bin_attr_init(eeprom); ++ eeprom->attr.name = EEPROM_NAME; ++ eeprom->attr.mode = S_IWUSR | S_IRUGO; ++ eeprom->read = sfp_bin_read; ++ eeprom->write = sfp_bin_write; ++ eeprom->size = EEPROM_SIZE; ++ ++ /* Create eeprom file */ ++ err = sysfs_create_bin_file(kobj, eeprom); ++ if (err) { ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int sfp_sysfs_eeprom_cleanup(struct kobject *kobj, struct bin_attribute *eeprom) ++{ ++ sysfs_remove_bin_file(kobj, eeprom); ++ return 0; ++} ++ ++static const struct attribute_group sfp_msa_group = { ++ .attrs = sfp_msa_attributes, ++}; ++ ++static int sfp_i2c_check_functionality(struct i2c_client *client) ++{ ++#if USE_I2C_BLOCK_READ ++ return i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK); ++#else ++ return i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA); ++#endif ++} ++ ++static int sfp_msa_probe(struct i2c_client *client, const struct i2c_device_id *dev_id, ++ struct sfp_msa_data **data) ++{ ++ int status; ++ struct sfp_msa_data *msa; ++ ++ if (!sfp_i2c_check_functionality(client)) { ++ status = -EIO; ++ goto exit; ++ } ++ ++ msa = kzalloc(sizeof(struct sfp_msa_data), GFP_KERNEL); ++ if (!msa) { ++ status = -ENOMEM; ++ goto exit; ++ } ++ ++ /* Register sysfs hooks */ ++ status = sysfs_create_group(&client->dev.kobj, &sfp_msa_group); ++ if (status) { ++ goto exit_free; ++ } ++ ++ /* init eeprom */ ++ status = sfp_sysfs_eeprom_init(&client->dev.kobj, &msa->eeprom.bin); ++ if (status) { ++ goto exit_remove; ++ } ++ ++ *data = msa; ++ dev_info(&client->dev, "sfp msa '%s'\n", client->name); ++ ++ return 0; ++ ++exit_remove: ++ sysfs_remove_group(&client->dev.kobj, &sfp_msa_group); ++exit_free: ++ kfree(msa); ++exit: ++ ++ return status; ++} ++ ++static const struct attribute_group sfp_ddm_group = { ++ .attrs = sfp_ddm_attributes, ++}; ++ ++static int sfp_ddm_probe(struct i2c_client *client, const struct i2c_device_id *dev_id, ++ struct sfp_ddm_data **data) ++{ ++ int status; ++ struct sfp_ddm_data *ddm; ++ ++ if (!sfp_i2c_check_functionality(client)) { ++ status = -EIO; ++ goto exit; ++ } ++ ++ ddm = kzalloc(sizeof(struct sfp_ddm_data), GFP_KERNEL); ++ if (!ddm) { ++ status = -ENOMEM; ++ goto exit; ++ } ++ ++ /* Register sysfs hooks */ ++ status = sysfs_create_group(&client->dev.kobj, &sfp_ddm_group); ++ if (status) { ++ goto exit_free; ++ } ++ ++ /* init eeprom */ ++ status = sfp_sysfs_eeprom_init(&client->dev.kobj, &ddm->eeprom.bin); ++ if (status) { ++ goto exit_remove; ++ } ++ ++ *data = ddm; ++ dev_info(&client->dev, "sfp ddm '%s'\n", client->name); ++ ++ return 0; ++ ++exit_remove: ++ sysfs_remove_group(&client->dev.kobj, &sfp_ddm_group); ++exit_free: ++ kfree(ddm); ++exit: ++ ++ return status; ++} ++ ++static const struct attribute_group qsfp_group = { ++ .attrs = qsfp_attributes, ++}; ++ ++static int qsfp_probe(struct i2c_client *client, const struct i2c_device_id *dev_id, ++ struct qsfp_data **data) ++{ ++ int status; ++ struct qsfp_data *qsfp; ++ ++ if (!sfp_i2c_check_functionality(client)) { ++ status = -EIO; ++ goto exit; ++ } ++ ++ qsfp = kzalloc(sizeof(struct qsfp_data), GFP_KERNEL); ++ if (!qsfp) { ++ status = -ENOMEM; ++ goto exit; ++ } ++ ++ /* Register sysfs hooks */ ++ status = sysfs_create_group(&client->dev.kobj, &qsfp_group); ++ if (status) { ++ goto exit_free; ++ } ++ ++ /* init eeprom */ ++ status = sfp_sysfs_eeprom_init(&client->dev.kobj, &qsfp->eeprom.bin); ++ if (status) { ++ goto exit_remove; ++ } ++ ++ *data = qsfp; ++ dev_info(&client->dev, "qsfp '%s'\n", client->name); ++ ++ return 0; ++ ++exit_remove: ++ sysfs_remove_group(&client->dev.kobj, &qsfp_group); ++exit_free: ++ kfree(qsfp); ++exit: ++ ++ return status; ++} ++ ++/* Platform dependent +++ */ ++static int sfp_device_probe(struct i2c_client *client, ++ const struct i2c_device_id *dev_id) ++{ ++ struct sfp_port_data *data = NULL; ++ ++ data = kzalloc(sizeof(struct sfp_port_data), GFP_KERNEL); ++ if (!data) { ++ return -ENOMEM; ++ } ++ ++ i2c_set_clientdata(client, data); ++ mutex_init(&data->update_lock); ++ data->port = dev_id->driver_data; ++ data->client = client; ++ ++ if (client->addr != SFP_EEPROM_A0_I2C_ADDR && ++ client->addr != SFP_EEPROM_A2_I2C_ADDR ) { ++ return -ENODEV; ++ } ++ ++ if (dev_id->driver_data >= as4610_sfp1 && dev_id->driver_data <= as4610_sfp4) { ++ if (client->addr == SFP_EEPROM_A0_I2C_ADDR) { ++ data->driver_type = DRIVER_TYPE_SFP_MSA; ++ return sfp_msa_probe(client, dev_id, &data->msa); ++ } ++ else if (client->addr == SFP_EEPROM_A2_I2C_ADDR) { ++ data->driver_type = DRIVER_TYPE_SFP_DDM; ++ return sfp_ddm_probe(client, dev_id, &data->ddm); ++ } ++ } ++ else if (dev_id->driver_data >= as4610_sfp5 && dev_id->driver_data <= as4610_sfp6) { ++ if (client->addr == SFP_EEPROM_A0_I2C_ADDR) { ++ data->driver_type = DRIVER_TYPE_QSFP; ++ return qsfp_probe(client, dev_id, &data->qsfp); ++ } ++ } ++ ++ return -ENODEV; ++} ++/* Platform dependent --- */ ++ ++static int sfp_msa_remove(struct i2c_client *client, struct sfp_msa_data *data) ++{ ++ sfp_sysfs_eeprom_cleanup(&client->dev.kobj, &data->eeprom.bin); ++ sysfs_remove_group(&client->dev.kobj, &sfp_msa_group); ++ kfree(data); ++ return 0; ++} ++ ++static int sfp_ddm_remove(struct i2c_client *client, struct sfp_ddm_data *data) ++{ ++ sfp_sysfs_eeprom_cleanup(&client->dev.kobj, &data->eeprom.bin); ++ sysfs_remove_group(&client->dev.kobj, &sfp_ddm_group); ++ kfree(data); ++ return 0; ++} ++ ++static int qfp_remove(struct i2c_client *client, struct qsfp_data *data) ++{ ++ sfp_sysfs_eeprom_cleanup(&client->dev.kobj, &data->eeprom.bin); ++ sysfs_remove_group(&client->dev.kobj, &qsfp_group); ++ kfree(data); ++ return 0; ++} ++ ++static int sfp_device_remove(struct i2c_client *client) ++{ ++ struct sfp_port_data *data = i2c_get_clientdata(client); ++ ++ switch (data->driver_type) { ++ case DRIVER_TYPE_SFP_MSA: ++ return sfp_msa_remove(client, data->msa); ++ case DRIVER_TYPE_SFP_DDM: ++ return sfp_ddm_remove(client, data->ddm); ++ case DRIVER_TYPE_QSFP: ++ return qfp_remove(client, data->qsfp); ++ } ++ ++ return 0; ++} ++ ++/* Addresses scanned ++ */ ++static const unsigned short normal_i2c[] = { SFP_EEPROM_A0_I2C_ADDR, SFP_EEPROM_A2_I2C_ADDR, I2C_CLIENT_END }; ++ ++static struct i2c_driver sfp_driver = { ++ .driver = { ++ .name = DRIVER_NAME, ++ }, ++ .probe = sfp_device_probe, ++ .remove = sfp_device_remove, ++ .id_table = sfp_device_id, ++ .address_list = normal_i2c, ++}; ++ ++static int __init sfp_init(void) ++{ ++ return i2c_add_driver(&sfp_driver); ++} ++ ++static void __exit sfp_exit(void) ++{ ++ i2c_del_driver(&sfp_driver); ++} ++ ++MODULE_AUTHOR("Brandon Chuang "); ++MODULE_DESCRIPTION("accton as4610_sfp driver"); ++MODULE_LICENSE("GPL"); ++ ++module_init(sfp_init); ++module_exit(sfp_exit); ++ +diff --git a/include/linux/accton_i2c_cpld.h b/include/linux/accton_i2c_cpld.h +new file mode 100644 +index 0000000..3f13209 +--- /dev/null ++++ b/include/linux/accton_i2c_cpld.h +@@ -0,0 +1,53 @@ ++/* ++ * A hwmon driver for the accton_i2c_cpld ++ * ++ * Copyright (C) 2016 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. ++ */ ++ ++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 AS4610_CPLD_SLAVE_ADDR 0x30 ++#define AS4610_CPLD_PID_OFFSET 0x01 /* Product ID offset */ ++ ++enum as4610_product_id_e { ++ PID_AS4610_30T, ++ PID_AS4610_30P, ++ PID_AS4610_54T, ++ PID_AS4610_54P, ++ PID_UNKNOWN ++}; ++ ++static inline int as4610_product_id(void) ++{ ++ int pid = accton_i2c_cpld_read(AS4610_CPLD_SLAVE_ADDR, AS4610_CPLD_PID_OFFSET); ++ pid &= 0xF; ++ return (pid < PID_AS4610_30T || pid > PID_AS4610_54P) ? PID_UNKNOWN : pid; ++} ++ ++static inline int as4610_is_poe_system(void) ++{ ++ int pid = as4610_product_id(); ++ return (pid == PID_AS4610_30P || pid == PID_AS4610_54P); ++} ++ ++static inline int as4610_number_of_system_fan(void) ++{ ++ return as4610_is_poe_system() ? 1 : 0; ++} ++ +