platform/broadcom/sonic-platform-modules-accton/as7712-32x/modules/accton_i2c_cpld.c (690 lines of code) (raw):
/*
* A hwmon driver for the accton_i2c_cpld
*
* Copyright (C) 2013 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/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/printk.h>
#define MAX_PORT_NUM 64
#define I2C_RW_RETRY_COUNT 10
#define I2C_RW_RETRY_INTERVAL 60 /* ms */
#define I2C_ADDR_CPLD1 0x60
#define I2C_ADDR_CPLD2 0x62
#define I2C_ADDR_CPLD3 0x64
#define CPLD_ADDRS {I2C_ADDR_CPLD1, I2C_ADDR_CPLD2, I2C_ADDR_CPLD3}
/*
* Number of additional attribute pointers to allocate
* with each call to krealloc
*/
#define ATTR_ALLOC_SIZE 1 /*For last attribute which is NUll.*/
#define NAME_SIZE 24
#define MAX_RESP_LENGTH 48
typedef ssize_t (*show_func)( struct device *dev,
struct device_attribute *attr,
char *buf);
typedef ssize_t (*store_func)(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
enum models {
AS7712_32X,
AS7716_32X,
AS7816_64X,
AS7312_54X,
PLAIN_CPLD, /*No attribute but add i2c addr to the list.*/
NUM_MODEL
};
enum sfp_func {
HAS_SFP = 1<<0 ,
HAS_QSFP = 1<<1 ,
};
enum common_attrs {
CMN_VERSION,
CMN_ACCESS,
CMN_PRESENT_ALL,
NUM_COMMON_ATTR
};
enum sfp_attrs {
SFP_PRESENT,
SFP_RESET,
SFP_LP_MODE,
NUM_SFP_ATTR
};
struct cpld_sensor {
struct cpld_sensor *next;
char name[NAME_SIZE+1]; /* sysfs sensor name */
struct device_attribute attribute;
bool update; /* runtime sensor update needed */
int data; /* Sensor data. Negative if there was a read error */
u8 reg; /* register */
u8 mask; /* bit mask */
bool invert; /* inverted value*/
};
#define to_cpld_sensor(_attr) \
container_of(_attr, struct cpld_sensor, attribute)
struct cpld_data {
struct device *dev;
struct device *hwmon_dev;
int num_attributes;
struct attribute_group group;
enum models model;
struct cpld_sensor *sensors;
struct mutex update_lock;
bool valid;
unsigned long last_updated; /* in jiffies */
int attr_index;
u16 sfp_num;
u8 sfp_types;
struct model_attrs *cmn_attr;
};
struct cpld_client_node {
struct i2c_client *client;
struct list_head list;
};
struct base_attrs {
const char *name;
umode_t mode;
show_func get;
store_func set;
};
struct attrs {
int reg;
bool invert;
struct base_attrs *base;
};
struct model_attrs {
struct attrs **cmn;
struct attrs **portly;
};
static ssize_t show_bit(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t show_presnet_all(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t set_1bit(struct device *dev, struct device_attribute *da,
const char *buf, size_t count);
static ssize_t set_byte(struct device *dev, struct device_attribute *da,
const char *buf, size_t count);
static ssize_t access(struct device *dev, struct device_attribute *da,
const char *buf, size_t count);
int accton_i2c_cpld_read(u8 cpld_addr, u8 reg);
int accton_i2c_cpld_write(unsigned short cpld_addr, u8 reg, u8 value);
struct base_attrs common_attrs[NUM_COMMON_ATTR] =
{
[CMN_VERSION] = {"version", S_IRUGO, show_bit, NULL},
[CMN_ACCESS] = {"access", S_IWUSR, NULL, set_byte},
[CMN_PRESENT_ALL] = {"module_present_all", S_IRUGO, show_presnet_all, NULL},
};
struct attrs as7712_common[] = {
[CMN_VERSION] = {0x01, false, &common_attrs[CMN_VERSION]},
[CMN_ACCESS] = {0x00, false, &common_attrs[CMN_ACCESS]},
[CMN_PRESENT_ALL] = {0x30, false, &common_attrs[CMN_PRESENT_ALL]},
};
struct attrs as7816_common[] = {
[CMN_VERSION] = {0x01, false, &common_attrs[CMN_VERSION]},
[CMN_ACCESS] = {0x00, false, &common_attrs[CMN_ACCESS]},
[CMN_PRESENT_ALL] = {0x70, false, &common_attrs[CMN_PRESENT_ALL]},
};
struct attrs as7312_common[] = {
[CMN_VERSION] = {0x01, false, &common_attrs[CMN_VERSION]},
[CMN_ACCESS] = {0x00, false, &common_attrs[CMN_ACCESS]},
[CMN_PRESENT_ALL] = {-1, false, &common_attrs[CMN_PRESENT_ALL]},
};
struct attrs plain_common[] = {
[CMN_VERSION] = {0x01, false, &common_attrs[CMN_VERSION]},
};
struct base_attrs portly_attrs[] =
{
[SFP_PRESENT] = {"module_present", S_IRUGO, show_bit, NULL},
[SFP_RESET] = {"module_reset", S_IRUGO|S_IWUSR, show_bit, set_1bit},
};
struct attrs as7712_port[] = {
{0x30, true, &portly_attrs[SFP_PRESENT]},
{0x04, true, &portly_attrs[SFP_RESET]},
};
struct attrs as7816_port[] = {
{0x70, true, &portly_attrs[SFP_PRESENT]},
{0x40, true, &portly_attrs[SFP_RESET]},
};
struct attrs *as7712_cmn_list[] = {
&as7712_common[CMN_VERSION],
&as7712_common[CMN_ACCESS],
&as7712_common[CMN_PRESENT_ALL],
NULL
};
struct attrs *as7816_cmn_list[] = {
&as7816_common[CMN_VERSION],
&as7816_common[CMN_ACCESS],
&as7816_common[CMN_PRESENT_ALL],
NULL
};
struct attrs *as7312_cmn_list[] = {
&as7312_common[CMN_VERSION],
&as7312_common[CMN_ACCESS],
&as7312_common[CMN_PRESENT_ALL],
NULL
};
struct attrs *plain_cmn_list[] = {
&plain_common[CMN_VERSION],
NULL
};
struct attrs *as7712_port_list[] = {
&as7712_port[SFP_PRESENT],
&as7712_port[SFP_RESET],
NULL
};
struct attrs *as7816_port_list[] = {
&as7816_port[SFP_PRESENT],
&as7816_port[SFP_RESET],
NULL
};
struct model_attrs models_attr[NUM_MODEL] = {
{.cmn = as7712_cmn_list, .portly=as7712_port_list},
{.cmn = as7712_cmn_list, .portly=as7712_port_list}, /*7716's as 7712*/
{.cmn = as7816_cmn_list, .portly=as7816_port_list},
{.cmn = as7312_cmn_list, .portly=as7816_port_list},
{.cmn = plain_cmn_list, .portly=NULL},
};
static LIST_HEAD(cpld_client_list);
static struct mutex list_lock;
/* Addresses scanned for accton_i2c_cpld
*/
static const unsigned short normal_i2c[] = { I2C_CLIENT_END };
static int get_sfp_spec(int model, u16 *num, u8 *types)
{
switch (model) {
case AS7712_32X:
case AS7716_32X:
*num = 32;
*types = HAS_QSFP;
break;
case AS7816_64X:
*num = 64;
*types = HAS_QSFP;
break;
case AS7312_54X:
*num = 54;
*types = HAS_QSFP|HAS_SFP;
default:
*types = 0;
*num = 0;
break;
}
return 0;
}
static int get_present_reg_distinct(int model, u8 port, u8 *cpld_addr, u8 *reg, u8 *num)
{
u8 cpld_address[] = CPLD_ADDRS;
switch (model) {
case AS7312_54X:
if (port < 48) {
*cpld_addr = cpld_address[1 + port/24];
*reg = 0x09 + (port%24)/8;
*num = 8;
}
else
{
*reg = 0x18;
*num = 4;
*cpld_addr = ( port < 52)? cpld_address[1]: cpld_address[2];
}
break;
default:
return -EINVAL;
}
}
/*Assume the bits for ports are listed in-a-row.*/
static int get_reg_bit(u8 reg_start, int port,
u8 *reg ,u8 *mask)
{
*reg = reg_start + ((port)/8);
*mask = 1 << ((port)%8);
return 0;
}
static int cpld_write_internal(
struct i2c_client *client, u8 reg, u8 value)
{
int status = 0, retry = I2C_RW_RETRY_COUNT;
while (retry) {
status = i2c_smbus_write_byte_data(client, reg, value);
if (unlikely(status < 0)) {
msleep(I2C_RW_RETRY_INTERVAL);
retry--;
continue;
}
break;
}
return status;
}
static int cpld_read_internal(struct i2c_client *client, u8 reg)
{
int status = 0, retry = I2C_RW_RETRY_COUNT;
while (retry) {
status = i2c_smbus_read_byte_data(client, reg);
if (unlikely(status < 0)) {
msleep(I2C_RW_RETRY_INTERVAL);
retry--;
continue;
}
break;
}
return status;
}
/*Turn a numberic array into string with " " between each element.
* e.g., {0x11, 0x33, 0xff, 0xf1} => "11 33 ff f1"
*/
static ssize_t array_stringify(char *buf, u8 *input, size_t size) {
int i;
char t[MAX_RESP_LENGTH+1];
buf[0] = '\0';
for (i = 0; i < size; i++) {
snprintf(t, MAX_RESP_LENGTH, "%x ", input[i]);
strncat(buf, t, MAX_RESP_LENGTH);
}
if (strlen(buf) > 0)
buf[strlen(buf)-1] = '\0'; /*Remove tailing blank*/
return snprintf(buf, MAX_RESP_LENGTH, "%s\n", buf);
}
static ssize_t show_presnet_all_distinct(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct cpld_data *data = i2c_get_clientdata(client);
u8 i, value, reg;
u8 cpld_addr, num;
u8 _value[8];
u64 *values = (u64 *)_value;
values = 0;
mutex_lock(&data->update_lock);
while(i < data->sfp_num)
{
get_present_reg_distinct(data->model, i, &cpld_addr, ®, &num);
if(cpld_addr == client->addr)
value = cpld_read_internal(client, reg);
else
value = accton_i2c_cpld_read(cpld_addr, reg);
if (unlikely(value < 0)) {
goto exit;
}
*values |= (value&((1<<(num))-1)) << i;
i += num;
}
mutex_unlock(&data->update_lock);
*values = cpu_to_le64(*values);
return array_stringify(buf, _value, i);
exit:
mutex_unlock(&data->update_lock);
return value;
}
static ssize_t show_presnet_all(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct cpld_data *data = i2c_get_clientdata(client);
struct cpld_sensor *sensor = to_cpld_sensor(devattr);
u8 i, values[MAX_PORT_NUM/8];
/*In case, the registers out-of-order*/
if (sensor->reg < 0) {
return show_presnet_all_distinct(dev, devattr, buf);
}
mutex_lock(&data->update_lock);
for (i = 0; i < ((data->sfp_num + 7)/8); i++) {
values[i] = cpld_read_internal(client, sensor->reg + i);
if (unlikely(values[i] < 0)) {
goto exit;
}
}
mutex_unlock(&data->update_lock);
return array_stringify(buf, values, i);
exit:
mutex_unlock(&data->update_lock);
return values[i];
}
static ssize_t show_bit(struct device *dev,
struct device_attribute *devattr, char *buf)
{
int value;
struct i2c_client *client = to_i2c_client(dev);
struct cpld_data *data = i2c_get_clientdata(client);
struct cpld_sensor *sensor = to_cpld_sensor(devattr);
mutex_lock(&data->update_lock);
value = cpld_read_internal(client, sensor->reg);
value = value & sensor->mask;
if (sensor->invert)
value = !value;
mutex_unlock(&data->update_lock);
return snprintf(buf, PAGE_SIZE, "%x\n", value);
}
static ssize_t set_1bit(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
long is_reset;
int value, status;
struct i2c_client *client = to_i2c_client(dev);
struct cpld_data *data = i2c_get_clientdata(client);
struct cpld_sensor *sensor = to_cpld_sensor(devattr);
u8 cpld_bit, reg;
status = kstrtol(buf, 10, &is_reset);
if (status) {
return status;
}
reg = sensor->reg;
cpld_bit = sensor->mask;
mutex_lock(&data->update_lock);
value = cpld_read_internal(client, reg);
if (unlikely(status < 0)) {
goto exit;
}
if (sensor->invert)
is_reset = !is_reset;
if (is_reset) {
value |= cpld_bit;
}
else {
value &= ~cpld_bit;
}
status = cpld_write_internal(client, reg, value);
if (unlikely(status < 0)) {
goto exit;
}
mutex_unlock(&data->update_lock);
return count;
exit:
mutex_unlock(&data->update_lock);
return status;
}
static ssize_t set_byte(struct device *dev, struct device_attribute *da,
const char *buf, size_t count)
{
return access(dev, da, buf, count);
}
static ssize_t access(struct device *dev, struct device_attribute *da,
const char *buf, size_t count)
{
int status;
u32 addr, val;
struct i2c_client *client = to_i2c_client(dev);
struct cpld_data *data = i2c_get_clientdata(client);
if (sscanf(buf, "0x%x 0x%x", &addr, &val) != 2) {
return -EINVAL;
}
if (addr > 0xFF || val > 0xFF) {
return -EINVAL;
}
mutex_lock(&data->update_lock);
status = cpld_write_internal(client, addr, val);
if (unlikely(status < 0)) {
goto exit;
}
mutex_unlock(&data->update_lock);
return count;
exit:
mutex_unlock(&data->update_lock);
return status;
}
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 int cpld_add_attribute(struct cpld_data *data, struct attribute *attr)
{
int new_max_attrs = ++data->num_attributes + ATTR_ALLOC_SIZE;
void *new_attrs = krealloc(data->group.attrs,
new_max_attrs * sizeof(void *),
GFP_KERNEL);
if (!new_attrs)
return -ENOMEM;
data->group.attrs = new_attrs;
data->group.attrs[data->num_attributes-1] = attr;
data->group.attrs[data->num_attributes] = NULL;
return 0;
}
static void cpld_dev_attr_init(struct device_attribute *dev_attr,
const char *name, umode_t mode,
show_func show, store_func store)
{
sysfs_attr_init(&dev_attr->attr);
dev_attr->attr.name = name;
dev_attr->attr.mode = mode;
dev_attr->show = show;
dev_attr->store = store;
}
static struct cpld_sensor * add_sensor(struct cpld_data *data,
const char *name,
u8 reg, u8 mask, bool invert,
bool update, umode_t mode,
show_func get, store_func set)
{
struct cpld_sensor *sensor;
struct device_attribute *a;
sensor = devm_kzalloc(data->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return NULL;
a = &sensor->attribute;
snprintf(sensor->name, sizeof(sensor->name), name);
sensor->reg = reg;
sensor->mask = mask;
sensor->update = update;
sensor->invert = invert;
cpld_dev_attr_init(a, sensor->name,
mode,
get, set);
if (cpld_add_attribute(data, &a->attr))
return NULL;
sensor->next = data->sensors;
data->sensors = sensor;
return sensor;
}
static int add_attributes_cmn(struct cpld_data *data, struct attrs **cmn)
{
u8 reg, i ;
bool invert;
struct attrs *a;
struct base_attrs *b;
if (NULL == cmn)
return -1;
for (i = 0; cmn[i]; i++)
{
a = cmn[i];
reg = a->reg;
invert = a->invert;
b = a->base;
if (NULL == b)
break;
if (add_sensor(data, b->name,
reg, 0xff, invert,
true, b->mode,
b->get, b->set) == NULL)
{
return -ENOMEM;
}
}
return 0;
}
static int add_attributes_portly(struct cpld_data *data, struct attrs **pa)
{
char name[NAME_SIZE+1];
int i, j;
u8 reg, mask, invert;
struct attrs *a;
struct base_attrs *b;
if (NULL == pa)
return -1;
for (i = 0; pa[i]; i++) {
a = pa[i];
b = a->base;
if (b == NULL)
break;
invert = a->invert;
for (j = 0; j < data->sfp_num; j++)
{
snprintf(name, NAME_SIZE, "%s_%d", b->name, j+1);
get_reg_bit(a->reg, j, ®, &mask);
if (add_sensor(data, name, reg, mask, invert,
true, b->mode, b->get, b->set) == NULL)
{
return -ENOMEM;
}
}
}
return 0;
}
static int add_attributes(struct i2c_client *client,
struct cpld_data *data)
{
struct model_attrs *m = data->cmn_attr;
if (m == NULL)
return -EINVAL;
/* Common attributes.*/
add_attributes_cmn(data, m->cmn);
/* Port-wise attributes.*/
add_attributes_portly(data, m->portly);
return 0;
}
static int accton_i2c_cpld_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
int status;
struct cpld_data *data = NULL;
struct device *dev = &client->dev;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_dbg(dev, "i2c_check_functionality failed (0x%x)\n", client->addr);
return -EIO;
}
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data) {
return -ENOMEM;
}
data->model = dev_id->driver_data;
data->cmn_attr = &models_attr[data->model];
get_sfp_spec(data->model, &data->sfp_num, &data->sfp_types);
i2c_set_clientdata(client, data);
mutex_init(&data->update_lock);
data->dev = dev;
dev_info(dev, "chip found\n");
status = add_attributes(client, data);
if (status)
goto out_kfree;
/*
* If there are no attributes, something is wrong.
* Bail out instead of trying to register nothing.
*/
if (!data->num_attributes) {
dev_err(dev, "No attributes found\n");
status = -ENODEV;
goto out_kfree;
}
/* Register sysfs hooks */
status = sysfs_create_group(&client->dev.kobj, &data->group);
if (status) {
goto out_kfree;
}
data->hwmon_dev = hwmon_device_register(&client->dev);
if (IS_ERR(data->hwmon_dev)) {
status = PTR_ERR(data->hwmon_dev);
goto exit_remove;
}
accton_i2c_cpld_add_client(client);
dev_info(dev, "%s: cpld '%s'\n",
dev_name(data->hwmon_dev), client->name);
return 0;
exit_remove:
sysfs_remove_group(&client->dev.kobj, &data->group);
out_kfree:
kfree(data->group.attrs);
return status;
}
static int accton_i2c_cpld_remove(struct i2c_client *client)
{
struct cpld_data *data = i2c_get_clientdata(client);
hwmon_device_unregister(data->hwmon_dev);
sysfs_remove_group(&client->dev.kobj, &data->group);
kfree(data->group.attrs);
accton_i2c_cpld_remove_client(client);
return 0;
}
int accton_i2c_cpld_read(u8 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 const struct i2c_device_id accton_i2c_cpld_id[] = {
{ "cpld_as7712", AS7712_32X},
{ "cpld_as7716", AS7716_32X},
{ "cpld_as7816", AS7816_64X},
{ "cpld_as7312", AS7312_54X},
{ "cpld_plain", PLAIN_CPLD},
{ },
};
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,
};
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_init(accton_i2c_cpld_init);
module_exit(accton_i2c_cpld_exit);
MODULE_AUTHOR("Brandon Chuang <brandon_chuang@accton.com.tw>");
MODULE_DESCRIPTION("accton_i2c_cpld driver");
MODULE_LICENSE("GPL");