mirror of
https://github.com/Telecominfraproject/OpenNetworkLinux.git
synced 2025-12-26 17:57:01 +00:00
AS5812-54X Kernel Modules.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
include $(ONL)/make/pkg.mk
|
||||
@@ -0,0 +1 @@
|
||||
!include $ONL_TEMPLATES/platform-modules.yml PLATFORM=x86-64-accton-as5812-54x ARCH=amd64 KERNELS="onl-kernel-3.16-lts-x86-64-all:amd64"
|
||||
1
packages/platforms/accton/x86-64/x86-64-accton-as5812-54x/modules/builds/.gitignore
vendored
Normal file
1
packages/platforms/accton/x86-64/x86-64-accton-as5812-54x/modules/builds/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
lib
|
||||
@@ -0,0 +1,5 @@
|
||||
KERNELS := onl-kernel-3.16-lts-x86-64-all:amd64
|
||||
KMODULES := $(wildcard *.c)
|
||||
PLATFORM := x86-64-accton-as5812-54x
|
||||
ARCH := x86_64
|
||||
include $(ONL)/make/kmodule.mk
|
||||
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* An I2C multiplexer dirver for accton as5812 CPLD
|
||||
*
|
||||
* Copyright (C) 2015 Accton Technology Corporation.
|
||||
* Brandon Chuang <brandon_chuang@accton.com.tw>
|
||||
*
|
||||
* This module supports the accton cpld that hold the channel select
|
||||
* mechanism for other i2c slave devices, such as SFP.
|
||||
* This includes the:
|
||||
* Accton as5812_54x CPLD1/CPLD2/CPLD3
|
||||
*
|
||||
* Based on:
|
||||
* pca954x.c from Kumar Gala <galak@kernel.crashing.org>
|
||||
* Copyright (C) 2006
|
||||
*
|
||||
* Based on:
|
||||
* pca954x.c from Ken Harrenstien
|
||||
* Copyright (C) 2004 Google, Inc. (Ken Harrenstien)
|
||||
*
|
||||
* Based on:
|
||||
* i2c-virtual_cb.c from Brian Kuschak <bkuschak@yahoo.com>
|
||||
* and
|
||||
* pca9540.c from Jean Delvare <khali@linux-fr.org>.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-mux.h>
|
||||
#include <linux/dmi.h>
|
||||
|
||||
static struct dmi_system_id as5812_54x_dmi_table[] = {
|
||||
{
|
||||
.ident = "Accton AS5812-54X",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Accton"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "AS5812-54X"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Accton AS5812-54X",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Accton"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "AS5812-54X"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
int platform_accton_as5812_54x(void)
|
||||
{
|
||||
return dmi_check_system(as5812_54x_dmi_table);
|
||||
}
|
||||
EXPORT_SYMBOL(platform_accton_as5812_54x);
|
||||
|
||||
#define NUM_OF_CPLD1_CHANS 0x0
|
||||
#define NUM_OF_CPLD2_CHANS 0x18
|
||||
#define NUM_OF_CPLD3_CHANS 0x1E
|
||||
#define CPLD_CHANNEL_SELECT_REG 0x2
|
||||
#define CPLD_DESELECT_CHANNEL 0xFF
|
||||
|
||||
#define ACCTON_I2C_CPLD_MUX_MAX_NCHANS NUM_OF_CPLD3_CHANS
|
||||
|
||||
static LIST_HEAD(cpld_client_list);
|
||||
static struct mutex list_lock;
|
||||
|
||||
struct cpld_client_node {
|
||||
struct i2c_client *client;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
enum cpld_mux_type {
|
||||
as5812_54x_cpld2,
|
||||
as5812_54x_cpld3,
|
||||
as5812_54x_cpld1
|
||||
};
|
||||
|
||||
struct accton_i2c_cpld_mux {
|
||||
enum cpld_mux_type type;
|
||||
struct i2c_adapter *virt_adaps[ACCTON_I2C_CPLD_MUX_MAX_NCHANS];
|
||||
u8 last_chan; /* last register value */
|
||||
};
|
||||
|
||||
struct chip_desc {
|
||||
u8 nchans;
|
||||
u8 deselectChan;
|
||||
};
|
||||
|
||||
/* Provide specs for the PCA954x types we know about */
|
||||
static const struct chip_desc chips[] = {
|
||||
[as5812_54x_cpld1] = {
|
||||
.nchans = NUM_OF_CPLD1_CHANS,
|
||||
.deselectChan = CPLD_DESELECT_CHANNEL,
|
||||
},
|
||||
[as5812_54x_cpld2] = {
|
||||
.nchans = NUM_OF_CPLD2_CHANS,
|
||||
.deselectChan = CPLD_DESELECT_CHANNEL,
|
||||
},
|
||||
[as5812_54x_cpld3] = {
|
||||
.nchans = NUM_OF_CPLD3_CHANS,
|
||||
.deselectChan = CPLD_DESELECT_CHANNEL,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct i2c_device_id accton_i2c_cpld_mux_id[] = {
|
||||
{ "as5812_54x_cpld1", as5812_54x_cpld1 },
|
||||
{ "as5812_54x_cpld2", as5812_54x_cpld2 },
|
||||
{ "as5812_54x_cpld3", as5812_54x_cpld3 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, accton_i2c_cpld_mux_id);
|
||||
|
||||
/* Write to mux register. Don't use i2c_transfer()/i2c_smbus_xfer()
|
||||
for this as they will try to lock adapter a second time */
|
||||
static int accton_i2c_cpld_mux_reg_write(struct i2c_adapter *adap,
|
||||
struct i2c_client *client, u8 val)
|
||||
{
|
||||
unsigned long orig_jiffies;
|
||||
unsigned short flags;
|
||||
union i2c_smbus_data data;
|
||||
int try;
|
||||
s32 res = -EIO;
|
||||
|
||||
data.byte = val;
|
||||
flags = client->flags;
|
||||
flags &= I2C_M_TEN | I2C_CLIENT_PEC;
|
||||
|
||||
if (adap->algo->smbus_xfer) {
|
||||
/* Retry automatically on arbitration loss */
|
||||
orig_jiffies = jiffies;
|
||||
for (res = 0, try = 0; try <= adap->retries; try++) {
|
||||
res = adap->algo->smbus_xfer(adap, client->addr, flags,
|
||||
I2C_SMBUS_WRITE, CPLD_CHANNEL_SELECT_REG,
|
||||
I2C_SMBUS_BYTE_DATA, &data);
|
||||
if (res != -EAGAIN)
|
||||
break;
|
||||
if (time_after(jiffies,
|
||||
orig_jiffies + adap->timeout))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int accton_i2c_cpld_mux_select_chan(struct i2c_adapter *adap,
|
||||
void *client, u32 chan)
|
||||
{
|
||||
struct accton_i2c_cpld_mux *data = i2c_get_clientdata(client);
|
||||
u8 regval;
|
||||
int ret = 0;
|
||||
regval = chan;
|
||||
|
||||
/* Only select the channel if its different from the last channel */
|
||||
if (data->last_chan != regval) {
|
||||
ret = accton_i2c_cpld_mux_reg_write(adap, client, regval);
|
||||
data->last_chan = regval;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int accton_i2c_cpld_mux_deselect_mux(struct i2c_adapter *adap,
|
||||
void *client, u32 chan)
|
||||
{
|
||||
struct accton_i2c_cpld_mux *data = i2c_get_clientdata(client);
|
||||
|
||||
/* Deselect active channel */
|
||||
data->last_chan = chips[data->type].deselectChan;
|
||||
|
||||
return accton_i2c_cpld_mux_reg_write(adap, client, data->last_chan);
|
||||
}
|
||||
|
||||
static void accton_i2c_cpld_add_client(struct i2c_client *client)
|
||||
{
|
||||
struct cpld_client_node *node = kzalloc(sizeof(struct cpld_client_node), GFP_KERNEL);
|
||||
|
||||
if (!node) {
|
||||
dev_dbg(&client->dev, "Can't allocate cpld_client_node (0x%x)\n", client->addr);
|
||||
return;
|
||||
}
|
||||
|
||||
node->client = client;
|
||||
|
||||
mutex_lock(&list_lock);
|
||||
list_add(&node->list, &cpld_client_list);
|
||||
mutex_unlock(&list_lock);
|
||||
}
|
||||
|
||||
static void accton_i2c_cpld_remove_client(struct i2c_client *client)
|
||||
{
|
||||
struct list_head *list_node = NULL;
|
||||
struct cpld_client_node *cpld_node = NULL;
|
||||
int found = 0;
|
||||
|
||||
mutex_lock(&list_lock);
|
||||
|
||||
list_for_each(list_node, &cpld_client_list)
|
||||
{
|
||||
cpld_node = list_entry(list_node, struct cpld_client_node, list);
|
||||
|
||||
if (cpld_node->client == client) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
list_del(list_node);
|
||||
kfree(cpld_node);
|
||||
}
|
||||
|
||||
mutex_unlock(&list_lock);
|
||||
}
|
||||
|
||||
static ssize_t show_cpld_version(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
u8 reg = 0x1;
|
||||
struct i2c_client *client;
|
||||
int len;
|
||||
|
||||
client = to_i2c_client(dev);
|
||||
len = sprintf(buf, "%d", i2c_smbus_read_byte_data(client, reg));
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct device_attribute ver = __ATTR(version, 0600, show_cpld_version, NULL);
|
||||
|
||||
/*
|
||||
* I2C init/probing/exit functions
|
||||
*/
|
||||
static int accton_i2c_cpld_mux_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent);
|
||||
int chan=0;
|
||||
struct accton_i2c_cpld_mux *data;
|
||||
int ret = -ENODEV;
|
||||
|
||||
if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE))
|
||||
goto err;
|
||||
|
||||
data = kzalloc(sizeof(struct accton_i2c_cpld_mux), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, data);
|
||||
|
||||
data->type = id->driver_data;
|
||||
|
||||
if (data->type == as5812_54x_cpld2 || data->type == as5812_54x_cpld3) {
|
||||
data->last_chan = chips[data->type].deselectChan; /* force the first selection */
|
||||
|
||||
/* Now create an adapter for each channel */
|
||||
for (chan = 0; chan < chips[data->type].nchans; chan++) {
|
||||
data->virt_adaps[chan] = i2c_add_mux_adapter(adap, &client->dev, client, 0, chan,
|
||||
I2C_CLASS_HWMON | I2C_CLASS_SPD,
|
||||
accton_i2c_cpld_mux_select_chan,
|
||||
accton_i2c_cpld_mux_deselect_mux);
|
||||
|
||||
if (data->virt_adaps[chan] == NULL) {
|
||||
ret = -ENODEV;
|
||||
dev_err(&client->dev, "failed to register multiplexed adapter %d\n", chan);
|
||||
goto virt_reg_failed;
|
||||
}
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "registered %d multiplexed busses for I2C mux %s\n",
|
||||
chan, client->name);
|
||||
}
|
||||
|
||||
accton_i2c_cpld_add_client(client);
|
||||
|
||||
ret = sysfs_create_file(&client->dev.kobj, &ver.attr);
|
||||
if (ret)
|
||||
goto virt_reg_failed;
|
||||
|
||||
return 0;
|
||||
|
||||
virt_reg_failed:
|
||||
for (chan--; chan >= 0; chan--) {
|
||||
i2c_del_mux_adapter(data->virt_adaps[chan]);
|
||||
}
|
||||
|
||||
kfree(data);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int accton_i2c_cpld_mux_remove(struct i2c_client *client)
|
||||
{
|
||||
struct accton_i2c_cpld_mux *data = i2c_get_clientdata(client);
|
||||
const struct chip_desc *chip = &chips[data->type];
|
||||
int chan;
|
||||
|
||||
sysfs_remove_file(&client->dev.kobj, &ver.attr);
|
||||
|
||||
for (chan = 0; chan < chip->nchans; ++chan) {
|
||||
if (data->virt_adaps[chan]) {
|
||||
i2c_del_mux_adapter(data->virt_adaps[chan]);
|
||||
data->virt_adaps[chan] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
kfree(data);
|
||||
accton_i2c_cpld_remove_client(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int as5812_54x_i2c_cpld_read(unsigned short cpld_addr, u8 reg)
|
||||
{
|
||||
struct list_head *list_node = NULL;
|
||||
struct cpld_client_node *cpld_node = NULL;
|
||||
int ret = -EPERM;
|
||||
|
||||
mutex_lock(&list_lock);
|
||||
|
||||
list_for_each(list_node, &cpld_client_list)
|
||||
{
|
||||
cpld_node = list_entry(list_node, struct cpld_client_node, list);
|
||||
|
||||
if (cpld_node->client->addr == cpld_addr) {
|
||||
ret = i2c_smbus_read_byte_data(cpld_node->client, reg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&list_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(as5812_54x_i2c_cpld_read);
|
||||
|
||||
int as5812_54x_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value)
|
||||
{
|
||||
struct list_head *list_node = NULL;
|
||||
struct cpld_client_node *cpld_node = NULL;
|
||||
int ret = -EIO;
|
||||
|
||||
mutex_lock(&list_lock);
|
||||
|
||||
list_for_each(list_node, &cpld_client_list)
|
||||
{
|
||||
cpld_node = list_entry(list_node, struct cpld_client_node, list);
|
||||
|
||||
if (cpld_node->client->addr == cpld_addr) {
|
||||
ret = i2c_smbus_write_byte_data(cpld_node->client, reg, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&list_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(as5812_54x_i2c_cpld_write);
|
||||
|
||||
static struct i2c_driver accton_i2c_cpld_mux_driver = {
|
||||
.driver = {
|
||||
.name = "as5812_54x_cpld",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = accton_i2c_cpld_mux_probe,
|
||||
.remove = accton_i2c_cpld_mux_remove,
|
||||
.id_table = accton_i2c_cpld_mux_id,
|
||||
};
|
||||
|
||||
static int __init accton_i2c_cpld_mux_init(void)
|
||||
{
|
||||
mutex_init(&list_lock);
|
||||
return i2c_add_driver(&accton_i2c_cpld_mux_driver);
|
||||
}
|
||||
|
||||
static void __exit accton_i2c_cpld_mux_exit(void)
|
||||
{
|
||||
i2c_del_driver(&accton_i2c_cpld_mux_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
|
||||
MODULE_DESCRIPTION("Accton I2C CPLD mux driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(accton_i2c_cpld_mux_init);
|
||||
module_exit(accton_i2c_cpld_mux_exit);
|
||||
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
* A hwmon driver for the Accton as5812 54x fan
|
||||
*
|
||||
* Copyright (C) 2015 Accton Technology Corporation.
|
||||
* Brandon Chuang <brandon_chuang@accton.com.tw>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define FAN_MAX_NUMBER 5
|
||||
#define FAN_SPEED_CPLD_TO_RPM_STEP 150
|
||||
#define FAN_SPEED_PRECENT_TO_CPLD_STEP 5
|
||||
#define FAN_DUTY_CYCLE_MIN 0
|
||||
#define FAN_DUTY_CYCLE_MAX 100 /* 100% */
|
||||
|
||||
#define CPLD_REG_FAN_STATUS_OFFSET 0xC
|
||||
#define CPLD_REG_FANR_STATUS_OFFSET 0x1F
|
||||
#define CPLD_REG_FAN_DIRECTION_OFFSET 0x1E
|
||||
|
||||
#define CPLD_FAN1_REG_SPEED_OFFSET 0x10
|
||||
#define CPLD_FAN2_REG_SPEED_OFFSET 0x11
|
||||
#define CPLD_FAN3_REG_SPEED_OFFSET 0x12
|
||||
#define CPLD_FAN4_REG_SPEED_OFFSET 0x13
|
||||
#define CPLD_FAN5_REG_SPEED_OFFSET 0x14
|
||||
|
||||
#define CPLD_FANR1_REG_SPEED_OFFSET 0x18
|
||||
#define CPLD_FANR2_REG_SPEED_OFFSET 0x19
|
||||
#define CPLD_FANR3_REG_SPEED_OFFSET 0x1A
|
||||
#define CPLD_FANR4_REG_SPEED_OFFSET 0x1B
|
||||
#define CPLD_FANR5_REG_SPEED_OFFSET 0x1C
|
||||
|
||||
#define CPLD_REG_FAN_PWM_CYCLE_OFFSET 0xD
|
||||
|
||||
#define CPLD_FAN1_INFO_BIT_MASK 0x1
|
||||
#define CPLD_FAN2_INFO_BIT_MASK 0x2
|
||||
#define CPLD_FAN3_INFO_BIT_MASK 0x4
|
||||
#define CPLD_FAN4_INFO_BIT_MASK 0x8
|
||||
#define CPLD_FAN5_INFO_BIT_MASK 0x10
|
||||
|
||||
#define PROJECT_NAME
|
||||
|
||||
#define LOCAL_DEBUG 0
|
||||
|
||||
static struct accton_as5812_54x_fan *fan_data = NULL;
|
||||
|
||||
struct accton_as5812_54x_fan {
|
||||
struct platform_device *pdev;
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
char valid; /* != 0 if registers are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
u8 status[FAN_MAX_NUMBER]; /* inner first fan status */
|
||||
u32 speed[FAN_MAX_NUMBER]; /* inner first fan speed */
|
||||
u8 direction[FAN_MAX_NUMBER]; /* reconrd the direction of inner first and second fans */
|
||||
u32 duty_cycle[FAN_MAX_NUMBER]; /* control the speed of inner first and second fans */
|
||||
u8 r_status[FAN_MAX_NUMBER]; /* inner second fan status */
|
||||
u32 r_speed[FAN_MAX_NUMBER]; /* inner second fan speed */
|
||||
};
|
||||
|
||||
/*******************/
|
||||
#define MAKE_FAN_MASK_OR_REG(name,type) \
|
||||
CPLD_FAN##type##1_##name, \
|
||||
CPLD_FAN##type##2_##name, \
|
||||
CPLD_FAN##type##3_##name, \
|
||||
CPLD_FAN##type##4_##name, \
|
||||
CPLD_FAN##type##5_##name,
|
||||
|
||||
/* fan related data
|
||||
*/
|
||||
static const u8 fan_info_mask[] = {
|
||||
MAKE_FAN_MASK_OR_REG(INFO_BIT_MASK,)
|
||||
};
|
||||
|
||||
static const u8 fan_speed_reg[] = {
|
||||
MAKE_FAN_MASK_OR_REG(REG_SPEED_OFFSET,)
|
||||
};
|
||||
|
||||
static const u8 fanr_speed_reg[] = {
|
||||
MAKE_FAN_MASK_OR_REG(REG_SPEED_OFFSET,R)
|
||||
};
|
||||
|
||||
/*******************/
|
||||
#define DEF_FAN_SET(id) \
|
||||
FAN##id##_FAULT, \
|
||||
FAN##id##_SPEED, \
|
||||
FAN##id##_DUTY_CYCLE, \
|
||||
FAN##id##_DIRECTION, \
|
||||
FANR##id##_FAULT, \
|
||||
FANR##id##_SPEED,
|
||||
|
||||
enum sysfs_fan_attributes {
|
||||
DEF_FAN_SET(1)
|
||||
DEF_FAN_SET(2)
|
||||
DEF_FAN_SET(3)
|
||||
DEF_FAN_SET(4)
|
||||
DEF_FAN_SET(5)
|
||||
};
|
||||
/*******************/
|
||||
static void accton_as5812_54x_fan_update_device(struct device *dev);
|
||||
static int accton_as5812_54x_fan_read_value(u8 reg);
|
||||
static int accton_as5812_54x_fan_write_value(u8 reg, u8 value);
|
||||
|
||||
static ssize_t fan_set_duty_cycle(struct device *dev,
|
||||
struct device_attribute *da,const char *buf, size_t count);
|
||||
static ssize_t fan_show_value(struct device *dev,
|
||||
struct device_attribute *da, char *buf);
|
||||
|
||||
extern int as5812_54x_i2c_cpld_read(unsigned short cpld_addr, u8 reg);
|
||||
extern int as5812_54x_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value);
|
||||
|
||||
|
||||
/*******************/
|
||||
#define _MAKE_SENSOR_DEVICE_ATTR(prj, id) \
|
||||
static SENSOR_DEVICE_ATTR(prj##fan##id##_fault, S_IRUGO, fan_show_value, NULL, FAN##id##_FAULT); \
|
||||
static SENSOR_DEVICE_ATTR(prj##fan##id##_speed_rpm, S_IRUGO, fan_show_value, NULL, FAN##id##_SPEED); \
|
||||
static SENSOR_DEVICE_ATTR(prj##fan##id##_duty_cycle_percentage, S_IWUSR | S_IRUGO, fan_show_value, \
|
||||
fan_set_duty_cycle, FAN##id##_DUTY_CYCLE); \
|
||||
static SENSOR_DEVICE_ATTR(prj##fan##id##_direction, S_IRUGO, fan_show_value, NULL, FAN##id##_DIRECTION); \
|
||||
static SENSOR_DEVICE_ATTR(prj##fanr##id##_fault, S_IRUGO, fan_show_value, NULL, FANR##id##_FAULT); \
|
||||
static SENSOR_DEVICE_ATTR(prj##fanr##id##_speed_rpm, S_IRUGO, fan_show_value, NULL, FANR##id##_SPEED);
|
||||
|
||||
#define MAKE_SENSOR_DEVICE_ATTR(prj,id) _MAKE_SENSOR_DEVICE_ATTR(prj,id)
|
||||
|
||||
MAKE_SENSOR_DEVICE_ATTR(PROJECT_NAME, 1)
|
||||
MAKE_SENSOR_DEVICE_ATTR(PROJECT_NAME, 2)
|
||||
MAKE_SENSOR_DEVICE_ATTR(PROJECT_NAME, 3)
|
||||
MAKE_SENSOR_DEVICE_ATTR(PROJECT_NAME, 4)
|
||||
MAKE_SENSOR_DEVICE_ATTR(PROJECT_NAME, 5)
|
||||
/*******************/
|
||||
|
||||
#define _MAKE_FAN_ATTR(prj, id) \
|
||||
&sensor_dev_attr_##prj##fan##id##_fault.dev_attr.attr, \
|
||||
&sensor_dev_attr_##prj##fan##id##_speed_rpm.dev_attr.attr, \
|
||||
&sensor_dev_attr_##prj##fan##id##_duty_cycle_percentage.dev_attr.attr,\
|
||||
&sensor_dev_attr_##prj##fan##id##_direction.dev_attr.attr, \
|
||||
&sensor_dev_attr_##prj##fanr##id##_fault.dev_attr.attr, \
|
||||
&sensor_dev_attr_##prj##fanr##id##_speed_rpm.dev_attr.attr,
|
||||
|
||||
#define MAKE_FAN_ATTR(prj, id) _MAKE_FAN_ATTR(prj, id)
|
||||
|
||||
static struct attribute *accton_as5812_54x_fan_attributes[] = {
|
||||
/* fan related attributes */
|
||||
MAKE_FAN_ATTR(PROJECT_NAME,1)
|
||||
MAKE_FAN_ATTR(PROJECT_NAME,2)
|
||||
MAKE_FAN_ATTR(PROJECT_NAME,3)
|
||||
MAKE_FAN_ATTR(PROJECT_NAME,4)
|
||||
MAKE_FAN_ATTR(PROJECT_NAME,5)
|
||||
NULL
|
||||
};
|
||||
/*******************/
|
||||
|
||||
/* fan related functions
|
||||
*/
|
||||
static ssize_t fan_show_value(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
ssize_t ret = 0;
|
||||
int data_index, type_index;
|
||||
|
||||
accton_as5812_54x_fan_update_device(dev);
|
||||
|
||||
if (fan_data->valid == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
type_index = attr->index%FAN2_FAULT;
|
||||
data_index = attr->index/FAN2_FAULT;
|
||||
|
||||
switch (type_index) {
|
||||
case FAN1_FAULT:
|
||||
ret = sprintf(buf, "%d\n", fan_data->status[data_index]);
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||||
break;
|
||||
case FAN1_SPEED:
|
||||
ret = sprintf(buf, "%d\n", fan_data->speed[data_index]);
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||||
break;
|
||||
case FAN1_DUTY_CYCLE:
|
||||
ret = sprintf(buf, "%d\n", fan_data->duty_cycle[data_index]);
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||||
break;
|
||||
case FAN1_DIRECTION:
|
||||
ret = sprintf(buf, "%d\n", fan_data->direction[data_index]); /* presnet, need to modify*/
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||||
break;
|
||||
case FANR1_FAULT:
|
||||
ret = sprintf(buf, "%d\n", fan_data->r_status[data_index]);
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||||
break;
|
||||
case FANR1_SPEED:
|
||||
ret = sprintf(buf, "%d\n", fan_data->r_speed[data_index]);
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[Check !!][%s][%d][type->index=%d][data->index=%d]\n", __FUNCTION__, __LINE__, type_index, data_index);
|
||||
break;
|
||||
default:
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[Check !!][%s][%d] \n", __FUNCTION__, __LINE__);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
/*******************/
|
||||
static ssize_t fan_set_duty_cycle(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count) {
|
||||
|
||||
int error, value;
|
||||
|
||||
error = kstrtoint(buf, 10, &value);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
if (value < FAN_DUTY_CYCLE_MIN || value > FAN_DUTY_CYCLE_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
accton_as5812_54x_fan_write_value(CPLD_REG_FAN_PWM_CYCLE_OFFSET, value/FAN_SPEED_PRECENT_TO_CPLD_STEP);
|
||||
|
||||
fan_data->valid = 0;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct attribute_group accton_as5812_54x_fan_group = {
|
||||
.attrs = accton_as5812_54x_fan_attributes,
|
||||
};
|
||||
|
||||
static int accton_as5812_54x_fan_read_value(u8 reg)
|
||||
{
|
||||
return as5812_54x_i2c_cpld_read(0x60, reg);
|
||||
}
|
||||
|
||||
static int accton_as5812_54x_fan_write_value(u8 reg, u8 value)
|
||||
{
|
||||
return as5812_54x_i2c_cpld_write(0x60, reg, value);
|
||||
}
|
||||
|
||||
static void accton_as5812_54x_fan_update_device(struct device *dev)
|
||||
{
|
||||
int speed, r_speed, fault, r_fault, ctrl_speed, direction;
|
||||
int i;
|
||||
|
||||
mutex_lock(&fan_data->update_lock);
|
||||
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("Starting accton_as5812_54x_fan update \n");
|
||||
|
||||
if (!(time_after(jiffies, fan_data->last_updated + HZ + HZ / 2) || !fan_data->valid)) {
|
||||
/* do nothing */
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
fan_data->valid = 0;
|
||||
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("Starting accton_as5812_54x_fan update 2 \n");
|
||||
|
||||
fault = accton_as5812_54x_fan_read_value(CPLD_REG_FAN_STATUS_OFFSET);
|
||||
r_fault = accton_as5812_54x_fan_read_value(CPLD_REG_FANR_STATUS_OFFSET);
|
||||
direction = accton_as5812_54x_fan_read_value(CPLD_REG_FAN_DIRECTION_OFFSET);
|
||||
ctrl_speed = accton_as5812_54x_fan_read_value(CPLD_REG_FAN_PWM_CYCLE_OFFSET);
|
||||
|
||||
if ( (fault < 0) || (r_fault < 0) || (direction < 0) || (ctrl_speed < 0) )
|
||||
{
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[Error!!][%s][%d] \n", __FUNCTION__, __LINE__);
|
||||
goto _exit; /* error */
|
||||
}
|
||||
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[fan:] fault:%d, r_fault=%d, direction=%d, ctrl_speed=%d \n",fault, r_fault, direction, ctrl_speed);
|
||||
|
||||
for (i=0; i<FAN_MAX_NUMBER; i++)
|
||||
{
|
||||
/* Update fan data
|
||||
*/
|
||||
|
||||
/* fan fault
|
||||
* 0: normal, 1:abnormal
|
||||
* Each FAN-tray module has two fans.
|
||||
*/
|
||||
fan_data->status[i] = (fault & fan_info_mask[i]) >> i;
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[fan%d:] fail=%d \n",i, fan_data->status[i]);
|
||||
|
||||
fan_data->r_status[i] = (r_fault & fan_info_mask[i]) >> i;
|
||||
fan_data->direction[i] = (direction & fan_info_mask[i]) >> i;
|
||||
fan_data->duty_cycle[i] = ctrl_speed * FAN_SPEED_PRECENT_TO_CPLD_STEP;
|
||||
|
||||
/* fan speed
|
||||
*/
|
||||
speed = accton_as5812_54x_fan_read_value(fan_speed_reg[i]);
|
||||
r_speed = accton_as5812_54x_fan_read_value(fanr_speed_reg[i]);
|
||||
if ( (speed < 0) || (r_speed < 0) )
|
||||
{
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[Error!!][%s][%d] \n", __FUNCTION__, __LINE__);
|
||||
goto _exit; /* error */
|
||||
}
|
||||
|
||||
if (LOCAL_DEBUG)
|
||||
printk ("[fan%d:] speed:%d, r_speed=%d \n", i, speed, r_speed);
|
||||
|
||||
fan_data->speed[i] = speed * FAN_SPEED_CPLD_TO_RPM_STEP;
|
||||
fan_data->r_speed[i] = r_speed * FAN_SPEED_CPLD_TO_RPM_STEP;
|
||||
}
|
||||
|
||||
/* finish to update */
|
||||
fan_data->last_updated = jiffies;
|
||||
fan_data->valid = 1;
|
||||
|
||||
_exit:
|
||||
mutex_unlock(&fan_data->update_lock);
|
||||
}
|
||||
|
||||
static int accton_as5812_54x_fan_probe(struct platform_device *pdev)
|
||||
{
|
||||
int status = -1;
|
||||
|
||||
/* Register sysfs hooks */
|
||||
status = sysfs_create_group(&pdev->dev.kobj, &accton_as5812_54x_fan_group);
|
||||
if (status) {
|
||||
goto exit;
|
||||
|
||||
}
|
||||
|
||||
fan_data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(fan_data->hwmon_dev)) {
|
||||
status = PTR_ERR(fan_data->hwmon_dev);
|
||||
goto exit_remove;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "accton_as5812_54x_fan\n");
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &accton_as5812_54x_fan_group);
|
||||
exit:
|
||||
return status;
|
||||
}
|
||||
|
||||
static int accton_as5812_54x_fan_remove(struct platform_device *pdev)
|
||||
{
|
||||
hwmon_device_unregister(fan_data->hwmon_dev);
|
||||
sysfs_remove_group(&fan_data->pdev->dev.kobj, &accton_as5812_54x_fan_group);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DRVNAME "as5812_54x_fan"
|
||||
|
||||
static struct platform_driver accton_as5812_54x_fan_driver = {
|
||||
.probe = accton_as5812_54x_fan_probe,
|
||||
.remove = accton_as5812_54x_fan_remove,
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init accton_as5812_54x_fan_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
extern int platform_accton_as5812_54x(void);
|
||||
if(!platform_accton_as5812_54x()) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&accton_as5812_54x_fan_driver);
|
||||
if (ret < 0) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
fan_data = kzalloc(sizeof(struct accton_as5812_54x_fan), GFP_KERNEL);
|
||||
if (!fan_data) {
|
||||
ret = -ENOMEM;
|
||||
platform_driver_unregister(&accton_as5812_54x_fan_driver);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
mutex_init(&fan_data->update_lock);
|
||||
fan_data->valid = 0;
|
||||
|
||||
fan_data->pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
|
||||
if (IS_ERR(fan_data->pdev)) {
|
||||
ret = PTR_ERR(fan_data->pdev);
|
||||
platform_driver_unregister(&accton_as5812_54x_fan_driver);
|
||||
kfree(fan_data);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit accton_as5812_54x_fan_exit(void)
|
||||
{
|
||||
platform_device_unregister(fan_data->pdev);
|
||||
platform_driver_unregister(&accton_as5812_54x_fan_driver);
|
||||
kfree(fan_data);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
|
||||
MODULE_DESCRIPTION("accton_as5812_54x_fan driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(accton_as5812_54x_fan_init);
|
||||
module_exit(accton_as5812_54x_fan_exit);
|
||||
|
||||
@@ -0,0 +1,597 @@
|
||||
/*
|
||||
* A LED driver for the accton_as5812_54x_led
|
||||
*
|
||||
* Copyright (C) 2015 Accton Technology Corporation.
|
||||
* Brandon Chuang <brandon_chuang@accton.com.tw>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
/*#define DEBUG*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
extern int as5812_54x_i2c_cpld_read (unsigned short cpld_addr, u8 reg);
|
||||
extern int as5812_54x_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value);
|
||||
|
||||
extern void led_classdev_unregister(struct led_classdev *led_cdev);
|
||||
extern int led_classdev_register(struct device *parent, struct led_classdev *led_cdev);
|
||||
extern void led_classdev_resume(struct led_classdev *led_cdev);
|
||||
extern void led_classdev_suspend(struct led_classdev *led_cdev);
|
||||
|
||||
#define DRVNAME "as5812_54x_led"
|
||||
|
||||
struct accton_as5812_54x_led_data {
|
||||
struct platform_device *pdev;
|
||||
struct mutex update_lock;
|
||||
char valid; /* != 0 if registers are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
u8 reg_val[4]; /* Register value, 0 = LOC/DIAG/FAN LED
|
||||
1 = PSU1/PSU2 LED
|
||||
2 = FAN1-4 LED
|
||||
3 = FAN5-6 LED */
|
||||
};
|
||||
|
||||
static struct accton_as5812_54x_led_data *ledctl = NULL;
|
||||
|
||||
/* LED related data
|
||||
*/
|
||||
#define LED_TYPE_PSU1_REG_MASK 0x03
|
||||
#define LED_MODE_PSU1_GREEN_MASK 0x02
|
||||
#define LED_MODE_PSU1_AMBER_MASK 0x01
|
||||
#define LED_MODE_PSU1_OFF_MASK 0x03
|
||||
#define LED_MODE_PSU1_AUTO_MASK 0x00
|
||||
|
||||
#define LED_TYPE_PSU2_REG_MASK 0x0C
|
||||
#define LED_MODE_PSU2_GREEN_MASK 0x08
|
||||
#define LED_MODE_PSU2_AMBER_MASK 0x04
|
||||
#define LED_MODE_PSU2_OFF_MASK 0x0C
|
||||
#define LED_MODE_PSU2_AUTO_MASK 0x00
|
||||
|
||||
#define LED_TYPE_DIAG_REG_MASK 0x0C
|
||||
#define LED_MODE_DIAG_GREEN_MASK 0x08
|
||||
#define LED_MODE_DIAG_AMBER_MASK 0x04
|
||||
#define LED_MODE_DIAG_OFF_MASK 0x0C
|
||||
|
||||
#define LED_TYPE_FAN_REG_MASK 0x03
|
||||
#define LED_MODE_FAN_GREEN_MASK 0x02
|
||||
#define LED_MODE_FAN_AMBER_MASK 0x01
|
||||
#define LED_MODE_FAN_OFF_MASK 0x03
|
||||
#define LED_MODE_FAN_AUTO_MASK 0x00
|
||||
|
||||
#define LED_TYPE_FAN1_REG_MASK 0x03
|
||||
#define LED_TYPE_FAN2_REG_MASK 0x0C
|
||||
#define LED_TYPE_FAN3_REG_MASK 0x30
|
||||
#define LED_TYPE_FAN4_REG_MASK 0xC0
|
||||
#define LED_TYPE_FAN5_REG_MASK 0x03
|
||||
#define LED_TYPE_FAN6_REG_MASK 0x0C
|
||||
|
||||
#define LED_MODE_FANX_GREEN_MASK 0x01
|
||||
#define LED_MODE_FANX_RED_MASK 0x02
|
||||
#define LED_MODE_FANX_OFF_MASK 0x00
|
||||
|
||||
#define LED_TYPE_LOC_REG_MASK 0x30
|
||||
#define LED_MODE_LOC_ON_MASK 0x00
|
||||
#define LED_MODE_LOC_OFF_MASK 0x10
|
||||
#define LED_MODE_LOC_BLINK_MASK 0x20
|
||||
|
||||
static const u8 led_reg[] = {
|
||||
0xA, /* LOC/DIAG/FAN LED*/
|
||||
0xB, /* PSU1/PSU2 LED */
|
||||
0x16, /* FAN1-4 LED */
|
||||
0x17, /* FAN4-6 LED */
|
||||
};
|
||||
|
||||
enum led_type {
|
||||
LED_TYPE_PSU1,
|
||||
LED_TYPE_PSU2,
|
||||
LED_TYPE_DIAG,
|
||||
LED_TYPE_FAN,
|
||||
LED_TYPE_FAN1,
|
||||
LED_TYPE_FAN2,
|
||||
LED_TYPE_FAN3,
|
||||
LED_TYPE_FAN4,
|
||||
LED_TYPE_FAN5,
|
||||
LED_TYPE_LOC
|
||||
};
|
||||
|
||||
enum led_light_mode {
|
||||
LED_MODE_OFF = 0,
|
||||
LED_MODE_GREEN,
|
||||
LED_MODE_AMBER,
|
||||
LED_MODE_RED,
|
||||
LED_MODE_GREEN_BLINK,
|
||||
LED_MODE_AMBER_BLINK,
|
||||
LED_MODE_RED_BLINK,
|
||||
LED_MODE_AUTO,
|
||||
};
|
||||
|
||||
struct led_type_mode {
|
||||
enum led_type type;
|
||||
int type_mask;
|
||||
enum led_light_mode mode;
|
||||
int mode_mask;
|
||||
};
|
||||
|
||||
static struct led_type_mode led_type_mode_data[] = {
|
||||
{LED_TYPE_PSU1, LED_TYPE_PSU1_REG_MASK, LED_MODE_GREEN, LED_MODE_PSU1_GREEN_MASK},
|
||||
{LED_TYPE_PSU1, LED_TYPE_PSU1_REG_MASK, LED_MODE_AMBER, LED_MODE_PSU1_AMBER_MASK},
|
||||
{LED_TYPE_PSU1, LED_TYPE_PSU1_REG_MASK, LED_MODE_AUTO, LED_MODE_PSU1_AUTO_MASK},
|
||||
{LED_TYPE_PSU1, LED_TYPE_PSU1_REG_MASK, LED_MODE_OFF, LED_MODE_PSU1_OFF_MASK},
|
||||
{LED_TYPE_PSU2, LED_TYPE_PSU2_REG_MASK, LED_MODE_GREEN, LED_MODE_PSU2_GREEN_MASK},
|
||||
{LED_TYPE_PSU2, LED_TYPE_PSU2_REG_MASK, LED_MODE_AMBER, LED_MODE_PSU2_AMBER_MASK},
|
||||
{LED_TYPE_PSU2, LED_TYPE_PSU2_REG_MASK, LED_MODE_AUTO, LED_MODE_PSU2_AUTO_MASK},
|
||||
{LED_TYPE_PSU2, LED_TYPE_PSU2_REG_MASK, LED_MODE_OFF, LED_MODE_PSU2_OFF_MASK},
|
||||
{LED_TYPE_FAN, LED_TYPE_FAN_REG_MASK, LED_MODE_GREEN, LED_MODE_FAN_GREEN_MASK},
|
||||
{LED_TYPE_FAN, LED_TYPE_FAN_REG_MASK, LED_MODE_AMBER, LED_MODE_FAN_AMBER_MASK},
|
||||
{LED_TYPE_FAN, LED_TYPE_FAN_REG_MASK, LED_MODE_AUTO, LED_MODE_FAN_AUTO_MASK},
|
||||
{LED_TYPE_FAN, LED_TYPE_FAN_REG_MASK, LED_MODE_OFF, LED_MODE_FAN_OFF_MASK},
|
||||
{LED_TYPE_FAN1, LED_TYPE_FAN1_REG_MASK, LED_MODE_GREEN, LED_MODE_FANX_GREEN_MASK << 0},
|
||||
{LED_TYPE_FAN1, LED_TYPE_FAN1_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 0},
|
||||
{LED_TYPE_FAN1, LED_TYPE_FAN1_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 0},
|
||||
{LED_TYPE_FAN2, LED_TYPE_FAN2_REG_MASK, LED_MODE_GREEN, LED_MODE_FANX_GREEN_MASK << 2},
|
||||
{LED_TYPE_FAN2, LED_TYPE_FAN2_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 2},
|
||||
{LED_TYPE_FAN2, LED_TYPE_FAN2_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 2},
|
||||
{LED_TYPE_FAN3, LED_TYPE_FAN3_REG_MASK, LED_MODE_GREEN, LED_MODE_FANX_GREEN_MASK << 4},
|
||||
{LED_TYPE_FAN3, LED_TYPE_FAN3_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 4},
|
||||
{LED_TYPE_FAN3, LED_TYPE_FAN3_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 4},
|
||||
{LED_TYPE_FAN4, LED_TYPE_FAN4_REG_MASK, LED_MODE_GREEN, LED_MODE_FANX_GREEN_MASK << 6},
|
||||
{LED_TYPE_FAN4, LED_TYPE_FAN4_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 6},
|
||||
{LED_TYPE_FAN4, LED_TYPE_FAN4_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 6},
|
||||
{LED_TYPE_FAN5, LED_TYPE_FAN5_REG_MASK, LED_MODE_GREEN, LED_MODE_FANX_GREEN_MASK << 0},
|
||||
{LED_TYPE_FAN5, LED_TYPE_FAN5_REG_MASK, LED_MODE_RED, LED_MODE_FANX_RED_MASK << 0},
|
||||
{LED_TYPE_FAN5, LED_TYPE_FAN5_REG_MASK, LED_MODE_OFF, LED_MODE_FANX_OFF_MASK << 0},
|
||||
{LED_TYPE_DIAG, LED_TYPE_DIAG_REG_MASK, LED_MODE_GREEN, LED_MODE_DIAG_GREEN_MASK},
|
||||
{LED_TYPE_DIAG, LED_TYPE_DIAG_REG_MASK, LED_MODE_AMBER, LED_MODE_DIAG_AMBER_MASK},
|
||||
{LED_TYPE_DIAG, LED_TYPE_DIAG_REG_MASK, LED_MODE_OFF, LED_MODE_DIAG_OFF_MASK},
|
||||
{LED_TYPE_LOC, LED_TYPE_LOC_REG_MASK, LED_MODE_AMBER, LED_MODE_LOC_ON_MASK},
|
||||
{LED_TYPE_LOC, LED_TYPE_LOC_REG_MASK, LED_MODE_OFF, LED_MODE_LOC_OFF_MASK},
|
||||
{LED_TYPE_LOC, LED_TYPE_LOC_REG_MASK, LED_MODE_AMBER_BLINK, LED_MODE_LOC_BLINK_MASK}
|
||||
};
|
||||
|
||||
|
||||
struct fanx_info_s {
|
||||
u8 cname; /* device name */
|
||||
enum led_type type;
|
||||
u8 reg_id; /* map to led_reg & reg_val */
|
||||
};
|
||||
|
||||
static struct fanx_info_s fanx_info[] = {
|
||||
{'1', LED_TYPE_FAN1, 2},
|
||||
{'2', LED_TYPE_FAN2, 2},
|
||||
{'3', LED_TYPE_FAN3, 2},
|
||||
{'4', LED_TYPE_FAN4, 2},
|
||||
{'5', LED_TYPE_FAN5, 3}
|
||||
};
|
||||
|
||||
static int led_reg_val_to_light_mode(enum led_type type, u8 reg_val) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(led_type_mode_data); i++) {
|
||||
|
||||
if (type != led_type_mode_data[i].type)
|
||||
continue;
|
||||
|
||||
if ((led_type_mode_data[i].type_mask & reg_val) ==
|
||||
led_type_mode_data[i].mode_mask)
|
||||
{
|
||||
return led_type_mode_data[i].mode;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u8 led_light_mode_to_reg_val(enum led_type type,
|
||||
enum led_light_mode mode, u8 reg_val) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(led_type_mode_data); i++) {
|
||||
if (type != led_type_mode_data[i].type)
|
||||
continue;
|
||||
|
||||
if (mode != led_type_mode_data[i].mode)
|
||||
continue;
|
||||
|
||||
reg_val = led_type_mode_data[i].mode_mask |
|
||||
(reg_val & (~led_type_mode_data[i].type_mask));
|
||||
}
|
||||
|
||||
return reg_val;
|
||||
}
|
||||
|
||||
static int accton_as5812_54x_led_read_value(u8 reg)
|
||||
{
|
||||
return as5812_54x_i2c_cpld_read(0x60, reg);
|
||||
}
|
||||
|
||||
static int accton_as5812_54x_led_write_value(u8 reg, u8 value)
|
||||
{
|
||||
return as5812_54x_i2c_cpld_write(0x60, reg, value);
|
||||
}
|
||||
|
||||
static void accton_as5812_54x_led_update(void)
|
||||
{
|
||||
mutex_lock(&ledctl->update_lock);
|
||||
|
||||
if (time_after(jiffies, ledctl->last_updated + HZ + HZ / 2)
|
||||
|| !ledctl->valid) {
|
||||
int i;
|
||||
|
||||
dev_dbg(&ledctl->pdev->dev, "Starting accton_as5812_54x_led update\n");
|
||||
|
||||
/* Update LED data
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(ledctl->reg_val); i++) {
|
||||
int status = accton_as5812_54x_led_read_value(led_reg[i]);
|
||||
|
||||
if (status < 0) {
|
||||
ledctl->valid = 0;
|
||||
dev_dbg(&ledctl->pdev->dev, "reg %d, err %d\n", led_reg[i], status);
|
||||
goto exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
ledctl->reg_val[i] = status;
|
||||
}
|
||||
}
|
||||
|
||||
ledctl->last_updated = jiffies;
|
||||
ledctl->valid = 1;
|
||||
}
|
||||
|
||||
exit:
|
||||
mutex_unlock(&ledctl->update_lock);
|
||||
}
|
||||
|
||||
static void accton_as5812_54x_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_light_mode,
|
||||
u8 reg, enum led_type type)
|
||||
{
|
||||
int reg_val;
|
||||
|
||||
mutex_lock(&ledctl->update_lock);
|
||||
|
||||
reg_val = accton_as5812_54x_led_read_value(reg);
|
||||
|
||||
if (reg_val < 0) {
|
||||
dev_dbg(&ledctl->pdev->dev, "reg %d, err %d\n", reg, reg_val);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
reg_val = led_light_mode_to_reg_val(type, led_light_mode, reg_val);
|
||||
accton_as5812_54x_led_write_value(reg, reg_val);
|
||||
|
||||
/* to prevent the slow-update issue */
|
||||
ledctl->valid = 0;
|
||||
|
||||
exit:
|
||||
mutex_unlock(&ledctl->update_lock);
|
||||
}
|
||||
|
||||
static void accton_as5812_54x_led_psu_1_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_light_mode)
|
||||
{
|
||||
accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[1], LED_TYPE_PSU1);
|
||||
}
|
||||
|
||||
static enum led_brightness accton_as5812_54x_led_psu_1_get(struct led_classdev *cdev)
|
||||
{
|
||||
accton_as5812_54x_led_update();
|
||||
return led_reg_val_to_light_mode(LED_TYPE_PSU1, ledctl->reg_val[1]);
|
||||
}
|
||||
|
||||
static void accton_as5812_54x_led_psu_2_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_light_mode)
|
||||
{
|
||||
accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[1], LED_TYPE_PSU2);
|
||||
}
|
||||
|
||||
static enum led_brightness accton_as5812_54x_led_psu_2_get(struct led_classdev *cdev)
|
||||
{
|
||||
accton_as5812_54x_led_update();
|
||||
return led_reg_val_to_light_mode(LED_TYPE_PSU2, ledctl->reg_val[1]);
|
||||
}
|
||||
|
||||
static void accton_as5812_54x_led_fan_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_light_mode)
|
||||
{
|
||||
accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_FAN);
|
||||
}
|
||||
|
||||
static enum led_brightness accton_as5812_54x_led_fan_get(struct led_classdev *cdev)
|
||||
{
|
||||
accton_as5812_54x_led_update();
|
||||
return led_reg_val_to_light_mode(LED_TYPE_FAN, ledctl->reg_val[0]);
|
||||
}
|
||||
|
||||
|
||||
static void accton_as5812_54x_led_fanx_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_light_mode)
|
||||
{
|
||||
enum led_type led_type1;
|
||||
int reg_id;
|
||||
int i, nsize;
|
||||
int ncount = sizeof(fanx_info)/sizeof(struct fanx_info_s);
|
||||
|
||||
for(i=0;i<ncount;i++)
|
||||
{
|
||||
nsize=strlen(led_cdev->name);
|
||||
|
||||
if (led_cdev->name[nsize-1] == fanx_info[i].cname)
|
||||
{
|
||||
led_type1 = fanx_info[i].type;
|
||||
reg_id = fanx_info[i].reg_id;
|
||||
accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[reg_id], led_type1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static enum led_brightness accton_as5812_54x_led_fanx_get(struct led_classdev *cdev)
|
||||
{
|
||||
enum led_type led_type1;
|
||||
int reg_id;
|
||||
int i, nsize;
|
||||
int ncount = sizeof(fanx_info)/sizeof(struct fanx_info_s);
|
||||
|
||||
for(i=0;i<ncount;i++)
|
||||
{
|
||||
nsize=strlen(cdev->name);
|
||||
|
||||
if (cdev->name[nsize-1] == fanx_info[i].cname)
|
||||
{
|
||||
led_type1 = fanx_info[i].type;
|
||||
reg_id = fanx_info[i].reg_id;
|
||||
accton_as5812_54x_led_update();
|
||||
return led_reg_val_to_light_mode(led_type1, ledctl->reg_val[reg_id]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return led_reg_val_to_light_mode(LED_TYPE_FAN1, ledctl->reg_val[2]);
|
||||
}
|
||||
|
||||
|
||||
static void accton_as5812_54x_led_diag_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_light_mode)
|
||||
{
|
||||
accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_DIAG);
|
||||
}
|
||||
|
||||
static enum led_brightness accton_as5812_54x_led_diag_get(struct led_classdev *cdev)
|
||||
{
|
||||
accton_as5812_54x_led_update();
|
||||
return led_reg_val_to_light_mode(LED_TYPE_DIAG, ledctl->reg_val[0]);
|
||||
}
|
||||
|
||||
static void accton_as5812_54x_led_loc_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_light_mode)
|
||||
{
|
||||
accton_as5812_54x_led_set(led_cdev, led_light_mode, led_reg[0], LED_TYPE_LOC);
|
||||
}
|
||||
|
||||
static enum led_brightness accton_as5812_54x_led_loc_get(struct led_classdev *cdev)
|
||||
{
|
||||
accton_as5812_54x_led_update();
|
||||
return led_reg_val_to_light_mode(LED_TYPE_LOC, ledctl->reg_val[0]);
|
||||
}
|
||||
|
||||
static struct led_classdev accton_as5812_54x_leds[] = {
|
||||
[LED_TYPE_PSU1] = {
|
||||
.name = "accton_as5812_54x_led::psu1",
|
||||
.default_trigger = "unused",
|
||||
.brightness_set = accton_as5812_54x_led_psu_1_set,
|
||||
.brightness_get = accton_as5812_54x_led_psu_1_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
.max_brightness = LED_MODE_AUTO,
|
||||
},
|
||||
[LED_TYPE_PSU2] = {
|
||||
.name = "accton_as5812_54x_led::psu2",
|
||||
.default_trigger = "unused",
|
||||
.brightness_set = accton_as5812_54x_led_psu_2_set,
|
||||
.brightness_get = accton_as5812_54x_led_psu_2_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
.max_brightness = LED_MODE_AUTO,
|
||||
},
|
||||
[LED_TYPE_FAN] = {
|
||||
.name = "accton_as5812_54x_led::fan",
|
||||
.default_trigger = "unused",
|
||||
.brightness_set = accton_as5812_54x_led_fan_set,
|
||||
.brightness_get = accton_as5812_54x_led_fan_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
.max_brightness = LED_MODE_AUTO,
|
||||
},
|
||||
[LED_TYPE_FAN1] = {
|
||||
.name = "accton_as5812_54x_led::fan1",
|
||||
.default_trigger = "unused",
|
||||
.brightness_set = accton_as5812_54x_led_fanx_set,
|
||||
.brightness_get = accton_as5812_54x_led_fanx_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
.max_brightness = LED_MODE_AUTO,
|
||||
},
|
||||
[LED_TYPE_FAN2] = {
|
||||
.name = "accton_as5812_54x_led::fan2",
|
||||
.default_trigger = "unused",
|
||||
.brightness_set = accton_as5812_54x_led_fanx_set,
|
||||
.brightness_get = accton_as5812_54x_led_fanx_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
.max_brightness = LED_MODE_AUTO,
|
||||
},
|
||||
[LED_TYPE_FAN3] = {
|
||||
.name = "accton_as5812_54x_led::fan3",
|
||||
.default_trigger = "unused",
|
||||
.brightness_set = accton_as5812_54x_led_fanx_set,
|
||||
.brightness_get = accton_as5812_54x_led_fanx_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
.max_brightness = LED_MODE_AUTO,
|
||||
},
|
||||
[LED_TYPE_FAN4] = {
|
||||
.name = "accton_as5812_54x_led::fan4",
|
||||
.default_trigger = "unused",
|
||||
.brightness_set = accton_as5812_54x_led_fanx_set,
|
||||
.brightness_get = accton_as5812_54x_led_fanx_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
.max_brightness = LED_MODE_AUTO,
|
||||
},
|
||||
[LED_TYPE_FAN5] = {
|
||||
.name = "accton_as5812_54x_led::fan5",
|
||||
.default_trigger = "unused",
|
||||
.brightness_set = accton_as5812_54x_led_fanx_set,
|
||||
.brightness_get = accton_as5812_54x_led_fanx_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
.max_brightness = LED_MODE_AUTO,
|
||||
},
|
||||
[LED_TYPE_DIAG] = {
|
||||
.name = "accton_as5812_54x_led::diag",
|
||||
.default_trigger = "unused",
|
||||
.brightness_set = accton_as5812_54x_led_diag_set,
|
||||
.brightness_get = accton_as5812_54x_led_diag_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
.max_brightness = LED_MODE_AUTO,
|
||||
},
|
||||
[LED_TYPE_LOC] = {
|
||||
.name = "accton_as5812_54x_led::loc",
|
||||
.default_trigger = "unused",
|
||||
.brightness_set = accton_as5812_54x_led_loc_set,
|
||||
.brightness_get = accton_as5812_54x_led_loc_get,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
.max_brightness = LED_MODE_AUTO,
|
||||
},
|
||||
};
|
||||
|
||||
static int accton_as5812_54x_led_suspend(struct platform_device *dev,
|
||||
pm_message_t state)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(accton_as5812_54x_leds); i++) {
|
||||
led_classdev_suspend(&accton_as5812_54x_leds[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int accton_as5812_54x_led_resume(struct platform_device *dev)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(accton_as5812_54x_leds); i++) {
|
||||
led_classdev_resume(&accton_as5812_54x_leds[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int accton_as5812_54x_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(accton_as5812_54x_leds); i++) {
|
||||
ret = led_classdev_register(&pdev->dev, &accton_as5812_54x_leds[i]);
|
||||
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check if all LEDs were successfully registered */
|
||||
if (i != ARRAY_SIZE(accton_as5812_54x_leds)){
|
||||
int j;
|
||||
|
||||
/* only unregister the LEDs that were successfully registered */
|
||||
for (j = 0; j < i; j++) {
|
||||
led_classdev_unregister(&accton_as5812_54x_leds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int accton_as5812_54x_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(accton_as5812_54x_leds); i++) {
|
||||
led_classdev_unregister(&accton_as5812_54x_leds[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver accton_as5812_54x_led_driver = {
|
||||
.probe = accton_as5812_54x_led_probe,
|
||||
.remove = accton_as5812_54x_led_remove,
|
||||
.suspend = accton_as5812_54x_led_suspend,
|
||||
.resume = accton_as5812_54x_led_resume,
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init accton_as5812_54x_led_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
extern int platform_accton_as5812_54x(void);
|
||||
if(!platform_accton_as5812_54x()) {
|
||||
return -ENODEV;
|
||||
}
|
||||
ret = platform_driver_register(&accton_as5812_54x_led_driver);
|
||||
if (ret < 0) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ledctl = kzalloc(sizeof(struct accton_as5812_54x_led_data), GFP_KERNEL);
|
||||
if (!ledctl) {
|
||||
ret = -ENOMEM;
|
||||
platform_driver_unregister(&accton_as5812_54x_led_driver);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
mutex_init(&ledctl->update_lock);
|
||||
|
||||
ledctl->pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
|
||||
if (IS_ERR(ledctl->pdev)) {
|
||||
ret = PTR_ERR(ledctl->pdev);
|
||||
platform_driver_unregister(&accton_as5812_54x_led_driver);
|
||||
kfree(ledctl);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit accton_as5812_54x_led_exit(void)
|
||||
{
|
||||
platform_device_unregister(ledctl->pdev);
|
||||
platform_driver_unregister(&accton_as5812_54x_led_driver);
|
||||
kfree(ledctl);
|
||||
}
|
||||
|
||||
module_init(accton_as5812_54x_led_init);
|
||||
module_exit(accton_as5812_54x_led_exit);
|
||||
|
||||
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
|
||||
MODULE_DESCRIPTION("accton_as5812_54x_led driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* An hwmon driver for accton as5812_54x Power Module
|
||||
*
|
||||
* Copyright (C) 2015 Accton Technology Corporation.
|
||||
* Brandon Chuang <brandon_chuang@accton.com.tw>
|
||||
*
|
||||
* Based on ad7414.c
|
||||
* Copyright 2006 Stefan Roese <sr at denx.de>, DENX Software Engineering
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
static ssize_t show_index(struct device *dev, struct device_attribute *da, char *buf);
|
||||
static ssize_t show_status(struct device *dev, struct device_attribute *da, char *buf);
|
||||
static ssize_t show_model_name(struct device *dev, struct device_attribute *da, char *buf);
|
||||
static int as5812_54x_psu_read_block(struct i2c_client *client, u8 command, u8 *data,int data_len);
|
||||
extern int as5812_54x_i2c_cpld_read(unsigned short cpld_addr, u8 reg);
|
||||
|
||||
/* Addresses scanned
|
||||
*/
|
||||
static const unsigned short normal_i2c[] = { 0x38, 0x3b, 0x50, 0x53, I2C_CLIENT_END };
|
||||
|
||||
/* Each client has this additional data
|
||||
*/
|
||||
struct as5812_54x_psu_data {
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
char valid; /* !=0 if registers are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
u8 index; /* PSU index */
|
||||
u8 status; /* Status(present/power_good) register read from CPLD */
|
||||
char model_name[14]; /* Model name, read from eeprom */
|
||||
};
|
||||
|
||||
static struct as5812_54x_psu_data *as5812_54x_psu_update_device(struct device *dev);
|
||||
|
||||
enum as5812_54x_psu_sysfs_attributes {
|
||||
PSU_INDEX,
|
||||
PSU_PRESENT,
|
||||
PSU_MODEL_NAME,
|
||||
PSU_POWER_GOOD
|
||||
};
|
||||
|
||||
/* sysfs attributes for hwmon
|
||||
*/
|
||||
static SENSOR_DEVICE_ATTR(psu_index, S_IRUGO, show_index, NULL, PSU_INDEX);
|
||||
static SENSOR_DEVICE_ATTR(psu_present, S_IRUGO, show_status, NULL, PSU_PRESENT);
|
||||
static SENSOR_DEVICE_ATTR(psu_model_name, S_IRUGO, show_model_name,NULL, PSU_MODEL_NAME);
|
||||
static SENSOR_DEVICE_ATTR(psu_power_good, S_IRUGO, show_status, NULL, PSU_POWER_GOOD);
|
||||
|
||||
static struct attribute *as5812_54x_psu_attributes[] = {
|
||||
&sensor_dev_attr_psu_index.dev_attr.attr,
|
||||
&sensor_dev_attr_psu_present.dev_attr.attr,
|
||||
&sensor_dev_attr_psu_model_name.dev_attr.attr,
|
||||
&sensor_dev_attr_psu_power_good.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static ssize_t show_index(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct as5812_54x_psu_data *data = i2c_get_clientdata(client);
|
||||
|
||||
return sprintf(buf, "%d\n", data->index);
|
||||
}
|
||||
|
||||
static ssize_t show_status(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct as5812_54x_psu_data *data = as5812_54x_psu_update_device(dev);
|
||||
u8 status = 0;
|
||||
|
||||
if (attr->index == PSU_PRESENT) {
|
||||
status = !(data->status >> ((data->index - 1) * 4) & 0x1);
|
||||
}
|
||||
else { /* PSU_POWER_GOOD */
|
||||
status = data->status >> ((data->index - 1) * 4 + 1) & 0x1;
|
||||
}
|
||||
|
||||
return sprintf(buf, "%d\n", status);
|
||||
}
|
||||
|
||||
static ssize_t show_model_name(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct as5812_54x_psu_data *data = as5812_54x_psu_update_device(dev);
|
||||
|
||||
return sprintf(buf, "%s", data->model_name);
|
||||
}
|
||||
|
||||
static const struct attribute_group as5812_54x_psu_group = {
|
||||
.attrs = as5812_54x_psu_attributes,
|
||||
};
|
||||
|
||||
static int as5812_54x_psu_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *dev_id)
|
||||
{
|
||||
struct as5812_54x_psu_data *data;
|
||||
int status;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) {
|
||||
status = -EIO;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
data = kzalloc(sizeof(struct as5812_54x_psu_data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
status = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, data);
|
||||
data->valid = 0;
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
dev_info(&client->dev, "chip found\n");
|
||||
|
||||
/* Register sysfs hooks */
|
||||
status = sysfs_create_group(&client->dev.kobj, &as5812_54x_psu_group);
|
||||
if (status) {
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&client->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
status = PTR_ERR(data->hwmon_dev);
|
||||
goto exit_remove;
|
||||
}
|
||||
|
||||
/* Update PSU index */
|
||||
if (client->addr == 0x38 || client->addr == 0x50) {
|
||||
data->index = 1;
|
||||
}
|
||||
else if (client->addr == 0x3b || client->addr == 0x53) {
|
||||
data->index = 2;
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "%s: psu '%s'\n",
|
||||
dev_name(data->hwmon_dev), client->name);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove:
|
||||
sysfs_remove_group(&client->dev.kobj, &as5812_54x_psu_group);
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int as5812_54x_psu_remove(struct i2c_client *client)
|
||||
{
|
||||
struct as5812_54x_psu_data *data = i2c_get_clientdata(client);
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
sysfs_remove_group(&client->dev.kobj, &as5812_54x_psu_group);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id as5812_54x_psu_id[] = {
|
||||
{ "as5812_54x_psu", 0 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, as5812_54x_psu_id);
|
||||
|
||||
static struct i2c_driver as5812_54x_psu_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "as5812_54x_psu",
|
||||
},
|
||||
.probe = as5812_54x_psu_probe,
|
||||
.remove = as5812_54x_psu_remove,
|
||||
.id_table = as5812_54x_psu_id,
|
||||
.address_list = normal_i2c,
|
||||
};
|
||||
|
||||
static int as5812_54x_psu_read_block(struct i2c_client *client, u8 command, u8 *data,
|
||||
int data_len)
|
||||
{
|
||||
int result = i2c_smbus_read_i2c_block_data(client, command, data_len, data);
|
||||
|
||||
if (unlikely(result < 0))
|
||||
goto abort;
|
||||
if (unlikely(result != data_len)) {
|
||||
result = -EIO;
|
||||
goto abort;
|
||||
}
|
||||
|
||||
result = 0;
|
||||
|
||||
abort:
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct as5812_54x_psu_data *as5812_54x_psu_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct as5812_54x_psu_data *data = i2c_get_clientdata(client);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
int status = -1;
|
||||
|
||||
dev_dbg(&client->dev, "Starting as5812_54x update\n");
|
||||
|
||||
/* Read model name */
|
||||
if (client->addr == 0x38 || client->addr == 0x3b) {
|
||||
/* AC power */
|
||||
status = as5812_54x_psu_read_block(client, 0x26, data->model_name,
|
||||
ARRAY_SIZE(data->model_name)-1);
|
||||
}
|
||||
else {
|
||||
/* DC power */
|
||||
status = as5812_54x_psu_read_block(client, 0x50, data->model_name,
|
||||
ARRAY_SIZE(data->model_name)-1);
|
||||
}
|
||||
|
||||
if (status < 0) {
|
||||
data->model_name[0] = '\0';
|
||||
dev_dbg(&client->dev, "unable to read model name from (0x%x)\n", client->addr);
|
||||
}
|
||||
else {
|
||||
data->model_name[ARRAY_SIZE(data->model_name)-1] = '\0';
|
||||
}
|
||||
|
||||
/* Read psu status */
|
||||
status = as5812_54x_i2c_cpld_read(0x60, 0x2);
|
||||
|
||||
if (status < 0) {
|
||||
dev_dbg(&client->dev, "cpld reg 0x60 err %d\n", status);
|
||||
}
|
||||
else {
|
||||
data->status = status;
|
||||
}
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int __init as5812_54x_psu_init(void)
|
||||
{
|
||||
extern int platform_accton_as5812_54x(void);
|
||||
if(!platform_accton_as5812_54x()) {
|
||||
return -ENODEV;
|
||||
}
|
||||
return i2c_add_driver(&as5812_54x_psu_driver);
|
||||
}
|
||||
|
||||
static void __exit as5812_54x_psu_exit(void)
|
||||
{
|
||||
i2c_del_driver(&as5812_54x_psu_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
|
||||
MODULE_DESCRIPTION("accton as5812_54x_psu driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(as5812_54x_psu_init);
|
||||
module_exit(as5812_54x_psu_exit);
|
||||
|
||||
@@ -0,0 +1,508 @@
|
||||
/*
|
||||
* An hwmon driver for accton as5812_54x sfp
|
||||
*
|
||||
* Copyright (C) 2015 Accton Technology Corporation.
|
||||
* Brandon Chuang <brandon_chuang@accton.com.tw>
|
||||
*
|
||||
* Based on ad7414.c
|
||||
* Copyright 2006 Stefan Roese <sr at denx.de>, DENX Software Engineering
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define NUM_OF_SFP_PORT 54
|
||||
#define BIT_INDEX(i) (1ULL << (i))
|
||||
|
||||
/* Addresses scanned
|
||||
*/
|
||||
static const unsigned short normal_i2c[] = { 0x50, I2C_CLIENT_END };
|
||||
|
||||
/* Each client has this additional data
|
||||
*/
|
||||
struct as5812_54x_sfp_data {
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
char valid; /* !=0 if registers are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
int port; /* Front port index */
|
||||
char eeprom[256]; /* eeprom data */
|
||||
u64 status[4]; /* bit0:port0, bit1:port1 and so on */
|
||||
/* index 0 => is_present
|
||||
1 => tx_fail
|
||||
2 => tx_disable
|
||||
3 => rx_loss */
|
||||
};
|
||||
|
||||
/* The table maps active port to cpld port.
|
||||
* Array index 0 is for active port 1,
|
||||
* index 1 for active port 2, and so on.
|
||||
* The array content implies cpld port index.
|
||||
*/
|
||||
static const u8 cpld_to_front_port_table[] =
|
||||
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||
49, 52, 50, 53, 51, 54};
|
||||
|
||||
#define CPLD_PORT_TO_FRONT_PORT(port) (cpld_to_front_port_table[port])
|
||||
|
||||
static struct as5812_54x_sfp_data *as5812_54x_sfp_update_device(struct device *dev, int update_eeprom);
|
||||
static ssize_t show_port_number(struct device *dev, struct device_attribute *da, char *buf);
|
||||
static ssize_t show_status(struct device *dev, struct device_attribute *da, char *buf);
|
||||
static ssize_t show_eeprom(struct device *dev, struct device_attribute *da, char *buf);
|
||||
static ssize_t set_tx_disable(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count);
|
||||
extern int as5812_54x_i2c_cpld_read(unsigned short cpld_addr, u8 reg);
|
||||
extern int as5812_54x_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value);
|
||||
|
||||
enum as5812_54x_sfp_sysfs_attributes {
|
||||
SFP_IS_PRESENT,
|
||||
SFP_TX_FAULT,
|
||||
SFP_TX_DISABLE,
|
||||
SFP_RX_LOSS,
|
||||
SFP_PORT_NUMBER,
|
||||
SFP_EEPROM,
|
||||
SFP_RX_LOS_ALL,
|
||||
SFP_IS_PRESENT_ALL,
|
||||
};
|
||||
|
||||
/* sysfs attributes for hwmon
|
||||
*/
|
||||
static SENSOR_DEVICE_ATTR(sfp_is_present, S_IRUGO, show_status, NULL, SFP_IS_PRESENT);
|
||||
static SENSOR_DEVICE_ATTR(sfp_tx_fault, S_IRUGO, show_status, NULL, SFP_TX_FAULT);
|
||||
static SENSOR_DEVICE_ATTR(sfp_tx_disable, S_IWUSR | S_IRUGO, show_status, set_tx_disable, SFP_TX_DISABLE);
|
||||
static SENSOR_DEVICE_ATTR(sfp_rx_loss, S_IRUGO, show_status,NULL, SFP_RX_LOSS);
|
||||
static SENSOR_DEVICE_ATTR(sfp_port_number, S_IRUGO, show_port_number, NULL, SFP_PORT_NUMBER);
|
||||
static SENSOR_DEVICE_ATTR(sfp_eeprom, S_IRUGO, show_eeprom, NULL, SFP_EEPROM);
|
||||
static SENSOR_DEVICE_ATTR(sfp_rx_los_all, S_IRUGO, show_status,NULL, SFP_RX_LOS_ALL);
|
||||
static SENSOR_DEVICE_ATTR(sfp_is_present_all, S_IRUGO, show_status,NULL, SFP_IS_PRESENT_ALL);
|
||||
|
||||
static struct attribute *as5812_54x_sfp_attributes[] = {
|
||||
&sensor_dev_attr_sfp_is_present.dev_attr.attr,
|
||||
&sensor_dev_attr_sfp_tx_fault.dev_attr.attr,
|
||||
&sensor_dev_attr_sfp_rx_loss.dev_attr.attr,
|
||||
&sensor_dev_attr_sfp_tx_disable.dev_attr.attr,
|
||||
&sensor_dev_attr_sfp_eeprom.dev_attr.attr,
|
||||
&sensor_dev_attr_sfp_port_number.dev_attr.attr,
|
||||
&sensor_dev_attr_sfp_rx_los_all.dev_attr.attr,
|
||||
&sensor_dev_attr_sfp_is_present_all.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static ssize_t show_port_number(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct as5812_54x_sfp_data *data = i2c_get_clientdata(client);
|
||||
|
||||
return sprintf(buf, "%d\n", CPLD_PORT_TO_FRONT_PORT(data->port));
|
||||
}
|
||||
|
||||
static ssize_t show_status(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct as5812_54x_sfp_data *data;
|
||||
u8 val;
|
||||
int values[7];
|
||||
|
||||
/* Error-check the CPLD read results. */
|
||||
#define VALIDATED_READ(_buf, _rv, _read_expr, _invert) \
|
||||
do { \
|
||||
_rv = (_read_expr); \
|
||||
if(_rv < 0) { \
|
||||
return sprintf(_buf, "READ ERROR\n"); \
|
||||
} \
|
||||
if(_invert) { \
|
||||
_rv = ~_rv; \
|
||||
} \
|
||||
_rv &= 0xFF; \
|
||||
} while(0)
|
||||
|
||||
if(attr->index == SFP_RX_LOS_ALL) {
|
||||
/*
|
||||
* Report the RX_LOS status for all ports.
|
||||
* This does not depend on the currently active SFP selector.
|
||||
*/
|
||||
|
||||
/* RX_LOS Ports 1-8 */
|
||||
VALIDATED_READ(buf, values[0], as5812_54x_i2c_cpld_read(0x61, 0x0F), 0);
|
||||
/* RX_LOS Ports 9-16 */
|
||||
VALIDATED_READ(buf, values[1], as5812_54x_i2c_cpld_read(0x61, 0x10), 0);
|
||||
/* RX_LOS Ports 17-24 */
|
||||
VALIDATED_READ(buf, values[2], as5812_54x_i2c_cpld_read(0x61, 0x11), 0);
|
||||
/* RX_LOS Ports 25-32 */
|
||||
VALIDATED_READ(buf, values[3], as5812_54x_i2c_cpld_read(0x62, 0x0F), 0);
|
||||
/* RX_LOS Ports 33-40 */
|
||||
VALIDATED_READ(buf, values[4], as5812_54x_i2c_cpld_read(0x62, 0x10), 0);
|
||||
/* RX_LOS Ports 41-48 */
|
||||
VALIDATED_READ(buf, values[5], as5812_54x_i2c_cpld_read(0x62, 0x11), 0);
|
||||
|
||||
/** Return values 1 -> 48 in order */
|
||||
return sprintf(buf, "%.2x %.2x %.2x %.2x %.2x %.2x\n",
|
||||
values[0], values[1], values[2],
|
||||
values[3], values[4], values[5]);
|
||||
}
|
||||
|
||||
if(attr->index == SFP_IS_PRESENT_ALL) {
|
||||
/*
|
||||
* Report the SFP_PRESENCE status for all ports.
|
||||
* This does not depend on the currently active SFP selector.
|
||||
*/
|
||||
|
||||
/* SFP_PRESENT Ports 1-8 */
|
||||
VALIDATED_READ(buf, values[0], as5812_54x_i2c_cpld_read(0x61, 0x6), 1);
|
||||
/* SFP_PRESENT Ports 9-16 */
|
||||
VALIDATED_READ(buf, values[1], as5812_54x_i2c_cpld_read(0x61, 0x7), 1);
|
||||
/* SFP_PRESENT Ports 17-24 */
|
||||
VALIDATED_READ(buf, values[2], as5812_54x_i2c_cpld_read(0x61, 0x8), 1);
|
||||
/* SFP_PRESENT Ports 25-32 */
|
||||
VALIDATED_READ(buf, values[3], as5812_54x_i2c_cpld_read(0x62, 0x6), 1);
|
||||
/* SFP_PRESENT Ports 33-40 */
|
||||
VALIDATED_READ(buf, values[4], as5812_54x_i2c_cpld_read(0x62, 0x7), 1);
|
||||
/* SFP_PRESENT Ports 41-48 */
|
||||
VALIDATED_READ(buf, values[5], as5812_54x_i2c_cpld_read(0x62, 0x8), 1);
|
||||
/* QSFP_PRESENT Ports 49-54 */
|
||||
VALIDATED_READ(buf, values[6], as5812_54x_i2c_cpld_read(0x62, 0x14), 1);
|
||||
|
||||
/* Return values 1 -> 54 in order */
|
||||
return sprintf(buf, "%.2x %.2x %.2x %.2x %.2x %.2x %.2x\n",
|
||||
values[0], values[1], values[2],
|
||||
values[3], values[4], values[5],
|
||||
values[6] & 0x3F);
|
||||
}
|
||||
/*
|
||||
* The remaining attributes are gathered on a per-selected-sfp basis.
|
||||
*/
|
||||
data = as5812_54x_sfp_update_device(dev, 0);
|
||||
if (attr->index == SFP_IS_PRESENT) {
|
||||
val = (data->status[attr->index] & BIT_INDEX(data->port)) ? 0 : 1;
|
||||
}
|
||||
else {
|
||||
val = (data->status[attr->index] & BIT_INDEX(data->port)) ? 1 : 0;
|
||||
}
|
||||
|
||||
return sprintf(buf, "%d", val);
|
||||
}
|
||||
|
||||
static ssize_t set_tx_disable(struct device *dev, struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct as5812_54x_sfp_data *data = i2c_get_clientdata(client);
|
||||
unsigned short cpld_addr = 0;
|
||||
u8 cpld_reg = 0, cpld_val = 0, cpld_bit = 0;
|
||||
long disable;
|
||||
int error;
|
||||
|
||||
/* Tx disable is not supported for QSFP ports(49-54) */
|
||||
if (data->port >= 48) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
error = kstrtol(buf, 10, &disable);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if(data->port < 24) {
|
||||
cpld_addr = 0x61;
|
||||
cpld_reg = 0xC + data->port / 8;
|
||||
cpld_bit = 1 << (data->port % 8);
|
||||
}
|
||||
else {
|
||||
cpld_addr = 0x62;
|
||||
cpld_reg = 0xC + (data->port - 24) / 8;
|
||||
cpld_bit = 1 << (data->port % 8);
|
||||
}
|
||||
|
||||
cpld_val = as5812_54x_i2c_cpld_read(cpld_addr, cpld_reg);
|
||||
|
||||
/* Update tx_disable status */
|
||||
if (disable) {
|
||||
data->status[SFP_TX_DISABLE] |= BIT_INDEX(data->port);
|
||||
cpld_val |= cpld_bit;
|
||||
}
|
||||
else {
|
||||
data->status[SFP_TX_DISABLE] &= ~BIT_INDEX(data->port);
|
||||
cpld_val &= ~cpld_bit;
|
||||
}
|
||||
|
||||
as5812_54x_i2c_cpld_write(cpld_addr, cpld_reg, cpld_val);
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_eeprom(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
struct as5812_54x_sfp_data *data = as5812_54x_sfp_update_device(dev, 1);
|
||||
|
||||
if (!data->valid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((data->status[SFP_IS_PRESENT] & BIT_INDEX(data->port)) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(buf, data->eeprom, sizeof(data->eeprom));
|
||||
|
||||
return sizeof(data->eeprom);
|
||||
}
|
||||
|
||||
static const struct attribute_group as5812_54x_sfp_group = {
|
||||
.attrs = as5812_54x_sfp_attributes,
|
||||
};
|
||||
|
||||
static int as5812_54x_sfp_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *dev_id)
|
||||
{
|
||||
struct as5812_54x_sfp_data *data;
|
||||
int status;
|
||||
|
||||
extern int platform_accton_as5812_54x(void);
|
||||
if(!platform_accton_as5812_54x()) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
status = -EIO;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
data = kzalloc(sizeof(struct as5812_54x_sfp_data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
status = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
mutex_init(&data->update_lock);
|
||||
data->port = dev_id->driver_data;
|
||||
i2c_set_clientdata(client, data);
|
||||
|
||||
dev_info(&client->dev, "chip found\n");
|
||||
|
||||
/* Register sysfs hooks */
|
||||
status = sysfs_create_group(&client->dev.kobj, &as5812_54x_sfp_group);
|
||||
if (status) {
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&client->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
status = PTR_ERR(data->hwmon_dev);
|
||||
goto exit_remove;
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "%s: sfp '%s'\n",
|
||||
dev_name(data->hwmon_dev), client->name);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove:
|
||||
sysfs_remove_group(&client->dev.kobj, &as5812_54x_sfp_group);
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int as5812_54x_sfp_remove(struct i2c_client *client)
|
||||
{
|
||||
struct as5812_54x_sfp_data *data = i2c_get_clientdata(client);
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
sysfs_remove_group(&client->dev.kobj, &as5812_54x_sfp_group);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum port_numbers {
|
||||
as5812_54x_sfp1, as5812_54x_sfp2, as5812_54x_sfp3, as5812_54x_sfp4,
|
||||
as5812_54x_sfp5, as5812_54x_sfp6, as5812_54x_sfp7, as5812_54x_sfp8,
|
||||
as5812_54x_sfp9, as5812_54x_sfp10, as5812_54x_sfp11,as5812_54x_sfp12,
|
||||
as5812_54x_sfp13, as5812_54x_sfp14, as5812_54x_sfp15,as5812_54x_sfp16,
|
||||
as5812_54x_sfp17, as5812_54x_sfp18, as5812_54x_sfp19,as5812_54x_sfp20,
|
||||
as5812_54x_sfp21, as5812_54x_sfp22, as5812_54x_sfp23,as5812_54x_sfp24,
|
||||
as5812_54x_sfp25, as5812_54x_sfp26, as5812_54x_sfp27,as5812_54x_sfp28,
|
||||
as5812_54x_sfp29, as5812_54x_sfp30, as5812_54x_sfp31,as5812_54x_sfp32,
|
||||
as5812_54x_sfp33, as5812_54x_sfp34, as5812_54x_sfp35,as5812_54x_sfp36,
|
||||
as5812_54x_sfp37, as5812_54x_sfp38, as5812_54x_sfp39,as5812_54x_sfp40,
|
||||
as5812_54x_sfp41, as5812_54x_sfp42, as5812_54x_sfp43,as5812_54x_sfp44,
|
||||
as5812_54x_sfp45, as5812_54x_sfp46, as5812_54x_sfp47,as5812_54x_sfp48,
|
||||
as5812_54x_sfp49, as5812_54x_sfp52, as5812_54x_sfp50,as5812_54x_sfp53,
|
||||
as5812_54x_sfp51, as5812_54x_sfp54
|
||||
};
|
||||
|
||||
static const struct i2c_device_id as5812_54x_sfp_id[] = {
|
||||
{ "as5812_54x_sfp1", as5812_54x_sfp1 }, { "as5812_54x_sfp2", as5812_54x_sfp2 },
|
||||
{ "as5812_54x_sfp3", as5812_54x_sfp3 }, { "as5812_54x_sfp4", as5812_54x_sfp4 },
|
||||
{ "as5812_54x_sfp5", as5812_54x_sfp5 }, { "as5812_54x_sfp6", as5812_54x_sfp6 },
|
||||
{ "as5812_54x_sfp7", as5812_54x_sfp7 }, { "as5812_54x_sfp8", as5812_54x_sfp8 },
|
||||
{ "as5812_54x_sfp9", as5812_54x_sfp9 }, { "as5812_54x_sfp10", as5812_54x_sfp10 },
|
||||
{ "as5812_54x_sfp11", as5812_54x_sfp11 }, { "as5812_54x_sfp12", as5812_54x_sfp12 },
|
||||
{ "as5812_54x_sfp13", as5812_54x_sfp13 }, { "as5812_54x_sfp14", as5812_54x_sfp14 },
|
||||
{ "as5812_54x_sfp15", as5812_54x_sfp15 }, { "as5812_54x_sfp16", as5812_54x_sfp16 },
|
||||
{ "as5812_54x_sfp17", as5812_54x_sfp17 }, { "as5812_54x_sfp18", as5812_54x_sfp18 },
|
||||
{ "as5812_54x_sfp19", as5812_54x_sfp19 }, { "as5812_54x_sfp20", as5812_54x_sfp20 },
|
||||
{ "as5812_54x_sfp21", as5812_54x_sfp21 }, { "as5812_54x_sfp22", as5812_54x_sfp22 },
|
||||
{ "as5812_54x_sfp23", as5812_54x_sfp23 }, { "as5812_54x_sfp24", as5812_54x_sfp24 },
|
||||
{ "as5812_54x_sfp25", as5812_54x_sfp25 }, { "as5812_54x_sfp26", as5812_54x_sfp26 },
|
||||
{ "as5812_54x_sfp27", as5812_54x_sfp27 }, { "as5812_54x_sfp28", as5812_54x_sfp28 },
|
||||
{ "as5812_54x_sfp29", as5812_54x_sfp29 }, { "as5812_54x_sfp30", as5812_54x_sfp30 },
|
||||
{ "as5812_54x_sfp31", as5812_54x_sfp31 }, { "as5812_54x_sfp32", as5812_54x_sfp32 },
|
||||
{ "as5812_54x_sfp33", as5812_54x_sfp33 }, { "as5812_54x_sfp34", as5812_54x_sfp34 },
|
||||
{ "as5812_54x_sfp35", as5812_54x_sfp35 }, { "as5812_54x_sfp36", as5812_54x_sfp36 },
|
||||
{ "as5812_54x_sfp37", as5812_54x_sfp37 }, { "as5812_54x_sfp38", as5812_54x_sfp38 },
|
||||
{ "as5812_54x_sfp39", as5812_54x_sfp39 }, { "as5812_54x_sfp40", as5812_54x_sfp40 },
|
||||
{ "as5812_54x_sfp41", as5812_54x_sfp41 }, { "as5812_54x_sfp42", as5812_54x_sfp42 },
|
||||
{ "as5812_54x_sfp43", as5812_54x_sfp43 }, { "as5812_54x_sfp44", as5812_54x_sfp44 },
|
||||
{ "as5812_54x_sfp45", as5812_54x_sfp45 }, { "as5812_54x_sfp46", as5812_54x_sfp46 },
|
||||
{ "as5812_54x_sfp47", as5812_54x_sfp47 }, { "as5812_54x_sfp48", as5812_54x_sfp48 },
|
||||
{ "as5812_54x_sfp49", as5812_54x_sfp49 }, { "as5812_54x_sfp50", as5812_54x_sfp50 },
|
||||
{ "as5812_54x_sfp51", as5812_54x_sfp51 }, { "as5812_54x_sfp52", as5812_54x_sfp52 },
|
||||
{ "as5812_54x_sfp53", as5812_54x_sfp53 }, { "as5812_54x_sfp54", as5812_54x_sfp54 },
|
||||
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, as5812_54x_sfp_id);
|
||||
|
||||
static struct i2c_driver as5812_54x_sfp_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "as5812_54x_sfp",
|
||||
},
|
||||
.probe = as5812_54x_sfp_probe,
|
||||
.remove = as5812_54x_sfp_remove,
|
||||
.id_table = as5812_54x_sfp_id,
|
||||
.address_list = normal_i2c,
|
||||
};
|
||||
|
||||
static int as5812_54x_sfp_read_byte(struct i2c_client *client, u8 command, u8 *data)
|
||||
{
|
||||
int result = i2c_smbus_read_byte_data(client, command);
|
||||
|
||||
if (unlikely(result < 0)) {
|
||||
dev_dbg(&client->dev, "sfp read byte data failed, command(0x%2x), data(0x%2x)\r\n", command, result);
|
||||
goto abort;
|
||||
}
|
||||
|
||||
*data = (u8)result;
|
||||
result = 0;
|
||||
|
||||
abort:
|
||||
return result;
|
||||
}
|
||||
|
||||
#define ALWAYS_UPDATE_DEVICE 1
|
||||
|
||||
static struct as5812_54x_sfp_data *as5812_54x_sfp_update_device(struct device *dev, int update_eeprom)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct as5812_54x_sfp_data *data = i2c_get_clientdata(client);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (ALWAYS_UPDATE_DEVICE || time_after(jiffies, data->last_updated + HZ + HZ / 2)
|
||||
|| !data->valid) {
|
||||
int status = -1;
|
||||
int i = 0, j = 0;
|
||||
|
||||
data->valid = 0;
|
||||
//dev_dbg(&client->dev, "Starting as5812_54x sfp status update\n");
|
||||
memset(data->status, 0, sizeof(data->status));
|
||||
|
||||
/* Read status of port 1~48(SFP port) */
|
||||
for (i = 0; i < 2; i++) {
|
||||
for (j = 0; j < 12; j++) {
|
||||
status = as5812_54x_i2c_cpld_read(0x61+i, 0x6+j);
|
||||
|
||||
if (status < 0) {
|
||||
dev_dbg(&client->dev, "cpld(0x%x) reg(0x%x) err %d\n", 0x61+i, 0x6+j, status);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
data->status[j/3] |= (u64)status << ((i*24) + (j%3)*8);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Bring QSFPs out of reset,
|
||||
* This is a temporary fix until the QSFP+_MOD_RST register
|
||||
* can be exposed through the driver.
|
||||
*/
|
||||
as5812_54x_i2c_cpld_write(0x62, 0x15, 0x3F);
|
||||
|
||||
/* Read present status of port 49-54(QSFP port) */
|
||||
status = as5812_54x_i2c_cpld_read(0x62, 0x14);
|
||||
|
||||
if (status < 0) {
|
||||
dev_dbg(&client->dev, "cpld(0x%x) reg(0x%x) err %d\n", 0x61+i, 0x6+j, status);
|
||||
}
|
||||
else {
|
||||
data->status[SFP_IS_PRESENT] |= (u64)status << 48;
|
||||
}
|
||||
|
||||
if (update_eeprom) {
|
||||
/* Read eeprom data based on port number */
|
||||
memset(data->eeprom, 0, sizeof(data->eeprom));
|
||||
|
||||
/* Check if the port is present */
|
||||
if ((data->status[SFP_IS_PRESENT] & BIT_INDEX(data->port)) == 0) {
|
||||
/* read eeprom */
|
||||
for (i = 0; i < sizeof(data->eeprom); i++) {
|
||||
status = as5812_54x_sfp_read_byte(client, i, data->eeprom + i);
|
||||
|
||||
if (status < 0) {
|
||||
dev_dbg(&client->dev, "unable to read eeprom from port(%d)\n",
|
||||
CPLD_PORT_TO_FRONT_PORT(data->port));
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->valid = 1;
|
||||
data->last_updated = jiffies;
|
||||
}
|
||||
|
||||
exit:
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
module_i2c_driver(as5812_54x_sfp_driver);
|
||||
|
||||
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
|
||||
MODULE_DESCRIPTION("accton as5812_54x_sfp driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user