/*
* S8900-64XC QSFP CPLD driver
*
* Copyright (C) 2017 Ingrasys, Inc.
*
* 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, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef DEBUG
#define DEBUG_PRINT(fmt, args...) \
printk (KERN_INFO "%s[%d]: " fmt "\r\n", \
__FUNCTION__, __LINE__, ##args)
#else
#define DEBUG_PRINT(fmt, args...)
#endif
#define ERROR_MSG(fmt, args...) \
printk(KERN_ERR "%s[%d]: " fmt "\r\n", \
__FUNCTION__, __LINE__, ##args)
#define SFF_8436_MMAP_SIZE (256)
#define EEPROM_SIZE (5 * 128) /* 640 byte eeprom */
#define EEPROM_PAGE_SIZE (128)
#define EEPROM_DEFAULT_PAGE (0)
#define I2C_RW_RETRY_COUNT (3)
#define I2C_RW_RETRY_INTERVAL (100) /* ms */
#define USE_I2C_BLOCK_READ (1)
#define SFP_EEPROM_A0_I2C_ADDR (0xA0 >> 1)
#define SFF_8436_PAGE_PROV_ADDR (0xC3) /* Memory Page 01/02 Provided */
#define SFF_8436_PAGE_01_PRESENT (1 << 6) /* Memory Page 01 present */
#define SFF_8436_PAGE_02_PRESENT (1 << 7) /* Memory Page 02 present */
#define SFF_8436_PAGE_SELECT_ADDR (0x7F)
#define SFF_8436_STATUS_ADDR (0x02)
#define SFF_8436_STATUS_PAGE_03_PRESENT_L (1 << 2) /* Flat Memory:0-Paging, 1-Page 0 only */
#define S8900_64XC_MUX_BASE_NR 1
#define S8900_64XC_SFP_EEPROM_BASE_NR 2
#define CPLD_DEVICE_NUM 3
#define SFP_CPLD_DEVICE_NUM 2
#define QSFP_EEPROM_DEVICE_NUM 3
#define TOTAL_PORT_NUM 64
#define CPLD_MUX_OFFSET 24
#define SFP_EEPROM_DEV_NUM 3
#define SFP_EEPROM_NAME_LEN 16
/* CPLD registers */
#define CPLD_REG_REV 0x01
#define CPLD_REG_ID 0x02
#define CPLD_MUX_REG 0x4a
/* QSFP signal bit in register */
#define BIT_RST 0
#define BIT_LPM 2
#define BIT_INT 0
#define BIT_ABS 1
#define BIT_ABS_2 5
static ssize_t sfp_eeprom_read(struct i2c_client *, loff_t, u8 *,int);
static ssize_t sfp_eeprom_write(struct i2c_client *, loff_t, const char *,int);
enum port_numbers {
sfp1, sfp2, sfp3, sfp4, sfp5, sfp6, sfp7, sfp8,
sfp9, sfp10, sfp11, sfp12, sfp13, sfp14, sfp15, sfp16,
sfp17, sfp18, sfp19, sfp20, sfp21, sfp22, sfp23, sfp24,
sfp25, sfp26, sfp27, sfp28, sfp29, sfp30, sfp31, sfp32,
sfp33, sfp34, sfp35, sfp36, sfp37, sfp38, sfp39, sfp40,
sfp41, sfp42, sfp43, sfp44, sfp45, sfp46, sfp47, sfp48,
qsfp49, qsfp50, qsfp51, qsfp52, qsfp53, qsfp54, qsfp55, qsfp56,
qsfp57, qsfp58, qsfp59, qsfp60, qsfp61, qsfp62, qsfp63, qsfp64
};
static const struct platform_device_id qsfp_device_id[] = {
{"sfp1", sfp1}, {"sfp2", sfp2}, {"sfp3", sfp3}, {"sfp4", sfp4},
{"sfp5", sfp5}, {"sfp6", sfp6}, {"sfp7", sfp7}, {"sfp8", sfp8},
{"sfp9", sfp9}, {"sfp10", sfp10}, {"sfp11", sfp11}, {"sfp12", sfp12},
{"sfp13", sfp13}, {"sfp14", sfp14}, {"sfp15", sfp15}, {"sfp16", sfp16},
{"sfp17", sfp17}, {"sfp18", sfp18}, {"sfp19", sfp19}, {"sfp20", sfp20},
{"sfp21", sfp21}, {"sfp22", sfp22}, {"sfp23", sfp23}, {"sfp24", sfp24},
{"sfp25", sfp25}, {"sfp26", sfp26}, {"sfp27", sfp27}, {"sfp28", sfp28},
{"sfp29", sfp29}, {"sfp30", sfp30}, {"sfp31", sfp31}, {"sfp32", sfp32},
{"sfp33", sfp33}, {"sfp34", sfp34}, {"sfp35", sfp35}, {"sfp36", sfp36},
{"sfp37", sfp37}, {"sfp38", sfp38}, {"sfp39", sfp39}, {"sfp40", sfp40},
{"sfp41", sfp41}, {"sfp42", sfp42}, {"sfp43", sfp43}, {"sfp44", sfp44},
{"sfp45", sfp45}, {"sfp46", sfp46}, {"sfp47", sfp47}, {"sfp48", sfp48},
{"qsfp49", qsfp49}, {"qsfp50", qsfp50}, {"qsfp51", qsfp51}, {"qsfp52", qsfp52},
{"qsfp53", qsfp53}, {"qsfp54", qsfp54}, {"qsfp55", qsfp55}, {"qsfp56", qsfp56},
{"qsfp57", qsfp57}, {"qsfp58", qsfp58}, {"qsfp59", qsfp59}, {"qsfp60", qsfp60},
{"qsfp61", qsfp61}, {"qsfp62", qsfp62}, {"qsfp63", qsfp63}, {"qsfp64", qsfp64},
{}
};
MODULE_DEVICE_TABLE(platform, qsfp_device_id);
/*
* list of valid port types
* note OOM_PORT_TYPE_NOT_PRESENT to indicate no
* module is present in this port
*/
typedef enum oom_driver_port_type_e {
OOM_DRIVER_PORT_TYPE_INVALID,
OOM_DRIVER_PORT_TYPE_NOT_PRESENT,
OOM_DRIVER_PORT_TYPE_SFP,
OOM_DRIVER_PORT_TYPE_SFP_PLUS,
OOM_DRIVER_PORT_TYPE_QSFP,
OOM_DRIVER_PORT_TYPE_QSFP_PLUS,
OOM_DRIVER_PORT_TYPE_QSFP28
} oom_driver_port_type_t;
enum driver_type_e {
DRIVER_TYPE_SFP_MSA,
DRIVER_TYPE_SFP_DDM,
DRIVER_TYPE_QSFP
};
typedef enum eeprom_operation_e {
EEPROM_READ,
EEPROM_WRITE
} eeprom_operation_t;
/*
* Each client has this additional data
*/
struct eeprom_data {
char valid; /* !=0 if registers are valid */
unsigned long last_updated; /* In jiffies */
struct bin_attribute bin; /* eeprom data */
};
struct qsfp_data {
char valid; /* !=0 if registers are valid */
unsigned long last_updated; /* In jiffies */
u8 status[3]; /* bit0:port0, bit1:port1 and so on */
/* index 0 => tx_fail
1 => tx_disable
2 => rx_loss
*/
u8 device_id;
struct eeprom_data eeprom;
};
struct sfp_port_data {
struct mutex update_lock;
enum driver_type_e driver_type;
int port; /* CPLD port index */
oom_driver_port_type_t port_type;
u64 present; /* present status, bit0:port0, bit1:port1 and so on */
struct qsfp_data *qsfp;
struct i2c_client *client;
};
enum sfp_sysfs_attributes {
PRESENT,
PRESENT_ALL,
PORT_NUMBER,
PORT_TYPE,
DDM_IMPLEMENTED,
TX_FAULT,
TX_FAULT1,
TX_FAULT2,
TX_FAULT3,
TX_FAULT4,
TX_DISABLE,
TX_DISABLE1,
TX_DISABLE2,
TX_DISABLE3,
TX_DISABLE4,
TX_DISABLE_ALL,
RX_LOS,
RX_LOS1,
RX_LOS2,
RX_LOS3,
RX_LOS4,
RX_LOS_ALL,
};
static void device_release(struct device *dev)
{
return;
}
/*
* S8900-64XC CPLD register addresses
*/
static const int int_abs_reg[CPLD_DEVICE_NUM][2]= {
{0x20, 0x31},
{0x20, 0x31},
{0x20, 0x2F},
};
static const int rst_lp_reg[CPLD_DEVICE_NUM][2]= {
{0xFF, 0xFF}, /*dummy*/
{0xFF, 0xFF}, /*dummy*/
{0x30, 0x3F},
};
/*
* S8900-64XC CPLD
*/
enum cpld_type {
cpld_1,
cpld_2,
cpld_3,
};
enum qsfp_signal {
sig_int,
sig_abs,
sig_rst,
sig_lpm
};
struct cpld_platform_data {
int reg_addr;
struct i2c_client *client;
};
struct sfp_platform_data {
int reg_addr;
int parent;
int cpld_reg;
struct i2c_client *client;
};
static struct cpld_platform_data s8900_64xc_cpld_platform_data[] = {
[cpld_1] = {
.reg_addr = 0x33,
},
[cpld_2] = {
.reg_addr = 0x33,
},
[cpld_3] = {
.reg_addr = 0x33,
}
};
static struct sfp_platform_data s8900_64xc_sfp_platform_data[] = {
[sfp1] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x01,
},
[sfp2] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x02,
},
[sfp3] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x03,
},
[sfp4] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x04,
},
[sfp5] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x05,
},
[sfp6] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x06,
},
[sfp7] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x07,
},
[sfp8] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x08,
},
[sfp9] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x09,
},
[sfp10] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x0A,
},
[sfp11] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x0B,
},
[sfp12] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x0C,
},
[sfp13] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x0D,
},
[sfp14] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x0E,
},
[sfp15] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x0F,
},
[sfp16] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x10,
},
[sfp17] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x11,
},
[sfp18] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x12,
},
[sfp19] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x13,
},
[sfp20] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x14,
},
[sfp21] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x15,
},
[sfp22] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x16,
},
[sfp23] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x17,
},
[sfp24] = {
.reg_addr = 0x50,
.parent = 2,
.cpld_reg = 0x18,
},
[sfp25] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x01,
},
[sfp26] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x02,
},
[sfp27] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x03,
},
[sfp28] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x04,
},
[sfp29] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x05,
},
[sfp30] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x06,
},
[sfp31] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x07,
},
[sfp32] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x08,
},
[sfp33] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x09,
},
[sfp34] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x0A,
},
[sfp35] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x0B,
},
[sfp36] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x0C,
},
[sfp37] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x0D,
},
[sfp38] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x0E,
},
[sfp39] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x0F,
},
[sfp40] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x10,
},
[sfp41] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x11,
},
[sfp42] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x12,
},
[sfp43] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x13,
},
[sfp44] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x14,
},
[sfp45] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x15,
},
[sfp46] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x16,
},
[sfp47] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x17,
},
[sfp48] = {
.reg_addr = 0x50,
.parent = 3,
.cpld_reg = 0x18,
},
[qsfp49] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x01,
},
[qsfp50] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x02,
},
[qsfp51] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x03,
},
[qsfp52] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x04,
},
[qsfp53] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x05,
},
[qsfp54] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x06,
},
[qsfp55] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x07,
},
[qsfp56] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x08,
},
[qsfp57] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x09,
},
[qsfp58] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x0A,
},
[qsfp59] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x0B,
},
[qsfp60] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x0C,
},
[qsfp61] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x0D,
},
[qsfp62] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x0E,
},
[qsfp63] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x0F,
},
[qsfp64] = {
.reg_addr = 0x50,
.parent = 4,
.cpld_reg = 0x10,
}
};
static struct platform_device s8900_64xc_cpld = {
.name = "ingrasys-s8900-64xc-cpld",
.id = 0,
.dev = {
.platform_data = s8900_64xc_cpld_platform_data,
.release = device_release
},
};
static struct platform_device s8900_64xc_sfp = {
.name = "ingrasys-s8900-64xc-sfp",
.id = 0,
.dev = {
.platform_data = s8900_64xc_sfp_platform_data,
.release = device_release
},
};
/*
* S8900-64XC I2C DEVICES
*/
struct i2c_device_platform_data {
int parent;
struct i2c_board_info info;
struct i2c_client *client;
};
/* module_platform_driver */
static ssize_t
get_prs_cpld_reg(struct device *dev,
struct device_attribute *devattr,
char *buf, int signal)
{
int ret;
u64 data = 0;
u64 shift = 0;
int i = 0;
int j = 0;
int port = 0;
int bit = 0;
int bit_mask = 0;
int bit_mask_2 = 0;
int (*reg)[CPLD_DEVICE_NUM][2];
struct cpld_platform_data *pdata = NULL;
pdata = dev->platform_data;
switch(signal) {
case sig_int:
bit = BIT_INT;
reg = (typeof(reg)) &int_abs_reg;
break;
case sig_abs:
bit = BIT_ABS;
reg = (typeof(reg)) &int_abs_reg;
break;
default:
return sprintf(buf, "signal/na");
}
bit_mask = 0x1 << bit;
bit_mask_2 = 0x1 << BIT_ABS_2;
for (i=0; i= 0x2a && j <= 0x2f && i < SFP_CPLD_DEVICE_NUM) {
continue;
}
ret = i2c_smbus_read_byte_data(pdata[i].client, j);
if (ret < 0) {
return sprintf((char *)buf, "i2c_smbus_read_byte_data/na");
}
shift = ((u64) ((ret & bit_mask) >> bit)) << port;
data |= shift;
DEBUG_PRINT("port=%d, shift=0x%016llx, ret=%x, bit_mask=%08x, bit=%08x, data=0x%016llx\n", port, shift, ret, bit_mask, bit, data);
/* CPLD2 and CPLD3 have BIT 1 and BIT 5 for present */
if (i < SFP_CPLD_DEVICE_NUM) {
port++;
shift = ((u64) ((ret & bit_mask_2) >> BIT_ABS_2)) << port;
data |= shift;
DEBUG_PRINT("port=%d, shift=0x%016llx, ret=%x, bit_mask_2=0x%x, bit=%x, data=0x%016llx\n", port, shift, ret, bit_mask_2, bit, data);
}
port++;
}
}
return sprintf((char *)buf, "0x%016llx\n", data);
}
/* module_platform_driver */
static ssize_t
get_rst_lp_cpld_reg(struct device *dev,
struct device_attribute *devattr,
char *buf, int signal)
{
int ret;
u64 data = 0;
u64 shift = 0;
int i = 0;
int j = 0;
int port = 0;
int bit = 0;
int bit_mask = 0;
int (*reg)[CPLD_DEVICE_NUM][2];
struct cpld_platform_data *pdata = NULL;
pdata = dev->platform_data;
switch(signal) {
case sig_rst:
bit = BIT_RST;
reg = (typeof(reg)) &rst_lp_reg;
break;
case sig_lpm:
bit = BIT_LPM;
reg = (typeof(reg)) &rst_lp_reg;
break;
default:
return sprintf(buf, "na");
}
bit_mask = 0x1 << bit;
for (i=2; i> bit)) << port;
data |= shift;
port++;
}
}
return sprintf(buf, "0x%04llx\n", data);
}
static ssize_t
set_rst_lp_cpld_reg(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count, int signal)
{
unsigned long data;
int err;
struct cpld_platform_data *pdata = dev->platform_data;
u8 current_reg_val = 0;
u8 new_reg_val = 0;
int value;
int i = 0;
int j = 0;
int port = 0;
int ret = 0;
int bit = 0;
int (*reg)[CPLD_DEVICE_NUM][2];
err = kstrtoul(buf, 16, &data);
if (err)
return err;
switch(signal) {
case sig_rst:
bit = BIT_RST;
reg = (typeof(reg)) &rst_lp_reg;
break;
case sig_lpm:
bit = BIT_LPM;
reg = (typeof(reg)) &rst_lp_reg;
break;
default:
return sprintf((char *)buf, "signal/na");
}
for (i=2; i> port) & 0x1;
//set value on bit N of new_reg_val
if (value > 0) {
new_reg_val = current_reg_val | (u8) (0x1 << bit);
} else {
new_reg_val = current_reg_val & (u8) ~(0x1 << bit);
}
//write reg value if changed
if (current_reg_val != new_reg_val) {
ret = i2c_smbus_write_byte_data(pdata[i].client, j,
(u8)(new_reg_val));
if (ret < 0){
return ret;
}
}
port++;
}
}
return count;
}
static ssize_t
get_lpmode(struct device *dev,
struct device_attribute *devattr, char *buf)
{
return get_rst_lp_cpld_reg(dev, devattr, buf, sig_lpm);
}
static ssize_t
set_lpmode(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
return set_rst_lp_cpld_reg(dev, devattr, buf, count, sig_lpm);
}
static ssize_t
get_reset(struct device *dev,
struct device_attribute *devattr, char *buf)
{
return get_rst_lp_cpld_reg(dev, devattr, buf, sig_rst);
}
static ssize_t
set_reset(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
return set_rst_lp_cpld_reg(dev, devattr, buf, count, sig_rst);
}
static ssize_t
get_modprs(struct device *dev,
struct device_attribute *devattr, char *buf)
{
return get_prs_cpld_reg(dev, devattr, buf, sig_abs);
}
static DEVICE_ATTR(qsfp_modprs, S_IRUGO, get_modprs, NULL);
static DEVICE_ATTR(qsfp_lpmode, S_IRUGO | S_IWUSR, get_lpmode, set_lpmode);
static DEVICE_ATTR(qsfp_reset, S_IRUGO | S_IWUSR, get_reset, set_reset);
static struct attribute *s8900_64xc_cpld_attrs[] = {
&dev_attr_qsfp_lpmode.attr,
&dev_attr_qsfp_reset.attr,
&dev_attr_qsfp_modprs.attr,
NULL,
};
static struct attribute_group s8900_64xc_cpld_attr_grp = {
.attrs = s8900_64xc_cpld_attrs,
};
/*
* Assumes that sanity checks for offset happened at sysfs-layer.
* Offset within Lower Page 00h and Upper Page 00h are not recomputed
*/
static uint8_t
sff_8436_translate_offset(loff_t *offset)
{
unsigned page = 0;
if (*offset < SFF_8436_MMAP_SIZE) {
return 0;
}
page = (*offset >> 7)-1;
if (page > 0 ) {
*offset = 0x80 + (*offset & 0x7f);
} else {
*offset &= 0xff;
}
return page;
}
static ssize_t
eeprom_read(struct i2c_client *client,
u8 command, const char *data,
int data_len)
{
#if USE_I2C_BLOCK_READ
int result, retry = I2C_RW_RETRY_COUNT;
if (data_len > I2C_SMBUS_BLOCK_MAX) {
data_len = I2C_SMBUS_BLOCK_MAX;
}
while (retry) {
result = i2c_smbus_read_i2c_block_data(client, command,
data_len, (u8 *)data);
if (result < 0) {
msleep(I2C_RW_RETRY_INTERVAL);
retry--;
continue;
}
break;
}
if (unlikely(result < 0)) {
goto abort;
}
if (unlikely(result != data_len)) {
result = -EIO;
goto abort;
}
abort:
return result;
#else
int result, retry = I2C_RW_RETRY_COUNT;
while (retry) {
result = i2c_smbus_read_byte_data(client, command);
if (result < 0) {
msleep(I2C_RW_RETRY_INTERVAL);
retry--;
continue;
}
break;
}
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 = 1;
abort:
return result;
#endif
}
static ssize_t
eeprom_write(struct i2c_client *client,
u8 command, const char *data,
int data_len)
{
#if USE_I2C_BLOCK_READ
int result, retry = I2C_RW_RETRY_COUNT;
if (data_len > I2C_SMBUS_BLOCK_MAX) {
data_len = I2C_SMBUS_BLOCK_MAX;
}
while (retry) {
result = i2c_smbus_write_i2c_block_data(client, command, data_len, data);
if (result < 0) {
msleep(I2C_RW_RETRY_INTERVAL);
retry--;
continue;
}
break;
}
if (unlikely(result < 0)) {
return result;
}
return data_len;
#else
int result, retry = I2C_RW_RETRY_COUNT;
while (retry) {
result = i2c_smbus_write_byte_data(client, command, *data);
if (result < 0) {
msleep(I2C_RW_RETRY_INTERVAL);
retry--;
continue;
}
break;
}
if (unlikely(result < 0)) {
return result;
}
return 1;
#endif
}
static ssize_t
sfp_eeprom_read_write(struct i2c_client *client,
eeprom_operation_t op,
loff_t off,
const char *data,
int data_len)
{
u8 page, phy_page;
u8 val, refresh_page = 0;
int ret;
ssize_t retval = 0;
size_t pending_len = 0, page_len = 0;
loff_t page_offset = 0, page_start_offset = 0;
loff_t phy_offset;
if (off > EEPROM_SIZE) {
return 0;
}
if (off + data_len > EEPROM_SIZE) {
data_len = EEPROM_SIZE - off;
}
/*
* Refresh pages which covers the requested data
* from offset to off + len
* Only refresh pages which contain requested bytes
*
*/
pending_len = data_len;
for (page = off >> 7; page <= (off + data_len - 1) >> 7; page++) {
refresh_page = 0;
switch (page) {
case 0:
/* Lower page 00h */
refresh_page = 1;
break;
case 1:
/* Upper page 00h */
refresh_page = 1;
break;
case 2:
/* Upper page 01h */
ret = eeprom_read(client, SFF_8436_PAGE_PROV_ADDR, &val, sizeof(val));
if (ret < 0) {
DEBUG_PRINT("Can't read EEPROM offset %d.\n",
SFF_8436_PAGE_PROV_ADDR);
goto error;
}
if (val & SFF_8436_PAGE_01_PRESENT) {
DEBUG_PRINT("Offset:%d Value:(0x%02x & 0x%02x)",
SFF_8436_PAGE_PROV_ADDR, val,
SFF_8436_PAGE_01_PRESENT);
refresh_page = 1;
}
break;
case 3:
/* Upper page 02h */
ret = eeprom_read(client, SFF_8436_PAGE_PROV_ADDR, &val, sizeof(val));
if (ret < 0) {
ERROR_MSG("Can't read EEPROM offset %d.\n",
SFF_8436_PAGE_PROV_ADDR);
goto error;
}
if (val & SFF_8436_PAGE_02_PRESENT) {
DEBUG_PRINT("Offset:%d Value:(0x%02x & 0x%02x)",
SFF_8436_PAGE_PROV_ADDR, val,
SFF_8436_PAGE_02_PRESENT);
refresh_page = 1;
}
break;
case 4:
/* Upper page 03h */
ret = eeprom_read(client, SFF_8436_STATUS_ADDR, &val, sizeof(val));
if (ret < 0) {
ERROR_MSG("Can't read EEPROM offset %d.\n",
SFF_8436_STATUS_ADDR);
goto error;
}
if (!(val & SFF_8436_STATUS_PAGE_03_PRESENT_L)) {
DEBUG_PRINT("Offset:%d Value:(0x%02x & 0x%02x)",
SFF_8436_STATUS_ADDR, val,
SFF_8436_STATUS_PAGE_03_PRESENT_L);
refresh_page = 1;
}
break;
default:
DEBUG_PRINT("Invalid Page %d\n", page);
ret = retval;
goto error;
break;
}
if (!refresh_page) {
/* if page is not valid or already refreshed */
continue;
}
/*
* Compute the offset and number of bytes to be read/write
* w.r.t requested page
*
* 1. start at offset 0 (within the page), and read/write the entire page
* 2. start at offset 0 (within the page) and read/write less than entire page
* 3. start at an offset not equal to 0 and read/write the rest of the page
* 4. start at an offset not equal to 0 and read/write less than (end of page - offset)
*
*/
page_start_offset = page * EEPROM_PAGE_SIZE;
if (page_start_offset < off) {
page_offset = off;
if (off + pending_len < page_start_offset + EEPROM_PAGE_SIZE) {
page_len = pending_len;
} else {
page_len = EEPROM_PAGE_SIZE - off;
}
} else {
page_offset = page_start_offset;
if (pending_len > EEPROM_PAGE_SIZE) {
page_len = EEPROM_PAGE_SIZE;
} else {
page_len = pending_len;
}
}
pending_len = pending_len - page_len;
/* Change current EEPROM page */
phy_offset = page_offset;
phy_page = sff_8436_translate_offset(&phy_offset);
if (phy_page > 0) {
ret = eeprom_write(client, SFF_8436_PAGE_SELECT_ADDR,
&phy_page, sizeof(phy_page));
if (ret < 0) {
ERROR_MSG("Can't write EEPROM offset %d.\n",
SFF_8436_PAGE_SELECT_ADDR);
goto error;
}
}
/*
* If page_len > 32, I2C client continue read or write EEPROM.
*/
while (page_len) {
if (op == EEPROM_READ) {
ret = eeprom_read(client, phy_offset, data, page_len);
} else if (op == EEPROM_WRITE) {
ret = eeprom_write(client, phy_offset, data, page_len);
} else {
ERROR_MSG("Bad EEPROM operation %d.\n", op);
break;
}
if (ret <= 0) {
if (retval == 0) {
retval = ret;
}
break;
}
phy_offset += ret;
off += ret;
data += ret;
page_len -= ret;
retval += ret;
}
/* Restore EEPROM page to default */
if (phy_page > 0) {
phy_page = EEPROM_DEFAULT_PAGE;
ret = eeprom_write(client, SFF_8436_PAGE_SELECT_ADDR,
&phy_page, sizeof(phy_page));
if (ret < 0) {
ERROR_MSG("Can't write EEPROM offset %d.\n",
SFF_8436_PAGE_SELECT_ADDR);
goto error;
}
}
}
return retval;
error:
return ret;
}
static inline ssize_t
sfp_eeprom_read(struct i2c_client *client,
loff_t off, u8 *data,
int data_len)
{
return sfp_eeprom_read_write(client, EEPROM_READ,
off, data, data_len);
}
static inline ssize_t
sfp_eeprom_write(struct i2c_client *client,
loff_t off, const char *data,
int data_len)
{
return sfp_eeprom_read_write(client, EEPROM_WRITE,
off, data, data_len);
}
static struct i2c_client *
cpld_sfp_port_client(int port,
int *data_reg)
{
*data_reg = s8900_64xc_sfp_platform_data[port].cpld_reg;
if (port >= sfp1 && port <= sfp24) {
return s8900_64xc_cpld_platform_data[cpld_1].client;
} else if (port >= sfp25 && port <= sfp48) {
return s8900_64xc_cpld_platform_data[cpld_2].client;
} else if (port >= qsfp49 && port <= qsfp64) {
return s8900_64xc_cpld_platform_data[cpld_3].client;
} else {
ERROR_MSG("Unknown port: %d", port);
return NULL;
}
}
static ssize_t
sfp_port_read(struct sfp_port_data *data,
char *buf, loff_t off,
size_t count, int port)
{
ssize_t retval = 0;
int data_reg = 0;
struct i2c_client *cpld_client = NULL;
if (unlikely(!count)) {
DEBUG_PRINT("Count = 0, return");
return count;
}
/*
* Read data from chip, protecting against concurrent updates
* from this host, but not from other I2C masters.
*/
mutex_lock(&data->update_lock);
//CPLD MUX select
cpld_client = cpld_sfp_port_client(port, &data_reg);
DEBUG_PRINT("data_reg=%d, port=%d", data_reg, port);
if (!cpld_client) {
ERROR_MSG("Error i2c client for port %d", port);
return 0;
}
i2c_smbus_write_byte_data(cpld_client, CPLD_MUX_REG ,data_reg);
while (count) {
ssize_t status;
status = sfp_eeprom_read(data->client, off, buf, count);
if (status <= 0) {
if (retval == 0) {
retval = status;
}
break;
}
buf += status;
off += status;
count -= status;
retval += status;
}
//CPLD MUX deselect
i2c_smbus_write_byte_data(cpld_client, CPLD_MUX_REG ,0x0);
mutex_unlock(&data->update_lock);
return retval;
}
static ssize_t
sfp_bin_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct sfp_port_data *data;
struct platform_device_id *dev_id = NULL;
dev_id = (struct platform_device_id *)attr->private;
DEBUG_PRINT("offset = (%lld), count = (%ld) dev_port=%d", off, count, (int)dev_id->driver_data);
data = dev_get_drvdata(container_of(kobj, struct device, kobj));
return sfp_port_read(data, buf, off, count, (int)dev_id->driver_data);
}
static ssize_t
sfp_port_write(struct sfp_port_data *data,
const char *buf, loff_t off,
size_t count, int port)
{
ssize_t retval = 0;
int data_reg = 0;
struct i2c_client *cpld_client = NULL;
if (unlikely(!count)) {
return count;
}
/*
* Write data to chip, protecting against concurrent updates
* from this host, but not from other I2C masters.
*/
mutex_lock(&data->update_lock);
//CPLD MUX select
cpld_client = cpld_sfp_port_client(port, &data_reg);
DEBUG_PRINT("data_reg=%d, port=%d", data_reg, port);
if (!cpld_client) {
ERROR_MSG("Error i2c client for port %d", port);
}
i2c_smbus_write_byte_data(cpld_client, CPLD_MUX_REG ,data_reg);
while (count) {
ssize_t status;
status = sfp_eeprom_write(data->client, off, buf, count);
if (status <= 0) {
if (retval == 0) {
retval = status;
}
break;
}
buf += status;
off += status;
count -= status;
retval += status;
}
//CPLD MUX deselect
i2c_smbus_write_byte_data(cpld_client, CPLD_MUX_REG ,0x0);
mutex_unlock(&data->update_lock);
return retval;
}
static ssize_t
sfp_bin_write(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct sfp_port_data *data;
struct platform_device_id *dev_id = NULL;
dev_id = (struct platform_device_id *)attr->private;
DEBUG_PRINT("offset = (%lld), count = (%ld) dev_port=%d",
off, count, (int)dev_id->driver_data);
data = dev_get_drvdata(container_of(kobj, struct device, kobj));
return sfp_port_write(data, buf, off, count, (int)dev_id->driver_data);
}
static int
sfp_sysfs_eeprom_init(struct kobject *kobj,
struct bin_attribute *eeprom,
const struct platform_device_id *dev_id)
{
int err;
sysfs_bin_attr_init(eeprom);
eeprom->attr.name = dev_id->name;
eeprom->attr.mode = S_IWUSR | S_IRUGO;
eeprom->read = sfp_bin_read;
eeprom->write = sfp_bin_write;
eeprom->private = (void *)dev_id;
eeprom->size = EEPROM_SIZE;
/* Create eeprom file */
err = sysfs_create_bin_file(kobj, eeprom);
if (err) {
DEBUG_PRINT("err=%d", err);
return err;
}
return 0;
}
static int
sfp_sysfs_eeprom_cleanup(struct kobject *kobj,
struct bin_attribute *eeprom)
{
sysfs_remove_bin_file(kobj, eeprom);
return 0;
}
static int
sfp_i2c_check_functionality(struct i2c_client *client)
{
#if USE_I2C_BLOCK_READ
return i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK);
#else
return i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA);
#endif
}
static int
qsfp_probe(struct i2c_client *client,
const struct platform_device_id *dev_id,
struct qsfp_data **data)
{
int status;
struct qsfp_data *qsfp;
if (!sfp_i2c_check_functionality(client)) {
status = -EIO;
goto exit;
}
qsfp = kzalloc(sizeof(struct qsfp_data), GFP_KERNEL);
if (!qsfp) {
status = -ENOMEM;
DEBUG_PRINT("No memory.");
goto exit;
}
/* Register sysfs hooks */ //TBD: must remove
/* status = sysfs_create_group(&client->dev.kobj, &qsfp_group);
if (status) {
goto exit_free;
} */
/* init eeprom */
status = sfp_sysfs_eeprom_init(&client->dev.kobj, &qsfp->eeprom.bin, dev_id);
if (status) {
DEBUG_PRINT("sfp_sysfs_eeprom_init error");
goto exit_free;
}
//TBD: Must remove
/*if (s9130_32x_kobj) {
status = sysfs_create_link(s9130_32x_kobj, &client->dev.kobj, client->name);
if (status) {
goto exit_remove;
}
}*/
*data = qsfp;
dev_info(&client->dev, "qsfp '%s'\n", client->name);
return 0;
exit_free:
kfree(qsfp);
exit:
return status;
}
static int
qsfp_device_probe(struct platform_device *pdev)
{
struct sfp_platform_data *pdata;
struct sfp_port_data *data[TOTAL_PORT_NUM];
struct i2c_adapter *parent[SFP_EEPROM_DEV_NUM];
struct i2c_client *sfp_client[SFP_EEPROM_DEV_NUM];
int i;
int ret=0;
DEBUG_PRINT("Start");
pdata = pdev->dev.platform_data;
if (!pdata) {
ERROR_MSG("Missing platform data\n");
return -ENODEV;
}
//New eeprom device
for (i=0; i < SFP_EEPROM_DEV_NUM; i++) {
parent[i] = i2c_get_adapter(i+S8900_64XC_SFP_EEPROM_BASE_NR);
if (!parent[i]) {
ERROR_MSG("Parent adapter (%d) not found\n", i+S8900_64XC_SFP_EEPROM_BASE_NR);
ret=-ENODEV;
goto error;
}
sfp_client[i] = i2c_new_dummy(parent[i], SFP_EEPROM_A0_I2C_ADDR);
if (!sfp_client[i]) {
ERROR_MSG("[%d]: Fail to create dummy i2c client for parent %d addr 0x%x\n", i, i+S8900_64XC_SFP_EEPROM_BASE_NR, SFP_EEPROM_A0_I2C_ADDR);
ret=-ENODEV;
goto error;
}
}
//Assign client to dummy device
for (i = 0; i < TOTAL_PORT_NUM; i++) {
switch (pdata[i].parent) {
case 2:
pdata[i].client = sfp_client[0];
break;
case 3:
pdata[i].client = sfp_client[1];
break;
case 4:
pdata[i].client = sfp_client[2];
break;
default:
ERROR_MSG("Error parent number: %d, ", i);
break;
}
if (!pdata[i].client) {
ERROR_MSG("[%d]: Fail to create dummy i2c client for parent %d addr 0x%x\n", i, pdata[i].parent, pdata[i].reg_addr);
ret=-ENODEV;
goto error;
}
}
for (i = 0; i < TOTAL_PORT_NUM; i++) {
data[i] = kzalloc(sizeof(struct sfp_port_data), GFP_KERNEL);
if (!data[i]) {
ret=-ENOMEM;
ERROR_MSG("No memory");
goto error;
}
i2c_set_clientdata(pdata[i].client, data[i]);
mutex_init(&data[i]->update_lock);
data[i]->port = qsfp_device_id[i].driver_data;
data[i]->client = pdata[i].client;
DEBUG_PRINT("data[%d]->port=%d", i, data[i]->port);
if (pdata[i].client->addr != SFP_EEPROM_A0_I2C_ADDR) {
ret=-ENODEV;
ERROR_MSG("Not approve device address");
goto error;
}
data[i]->driver_type = DRIVER_TYPE_QSFP;
ret |= qsfp_probe(pdata[i].client, &qsfp_device_id[i], &data[i]->qsfp);
}
if (ret) {
ERROR_MSG("qsfp_probe failed someone.");
//goto error;
}
return 0;
error:
DEBUG_PRINT("error start");
i2c_put_adapter(parent[i]);
i--;
for (; i >= 0; i--) {
if (pdata[i].client) {
i2c_unregister_device(pdata[i].client);
i2c_put_adapter(parent[i]);
}
}
return ret;
}
static int
qsfp_remove(struct i2c_client *client,
struct qsfp_data *data)
{
//TBD: Must remove
/*if (s9130_32x_kobj) {
sysfs_remove_link(s9130_32x_kobj, client->name);
}*/
//TBD: Must remove all ports EEPROM BIN
sfp_sysfs_eeprom_cleanup(&client->dev.kobj, &data->eeprom.bin);
//TBD: Must remove sysfs_remove_group(&client->dev.kobj, &qsfp_group);
kfree(data);
return 0;
}
static int __exit
qsfp_device_remove(struct platform_device *pdev)
{
struct sfp_port_data *data = NULL;
struct sfp_platform_data *pdata = pdev->dev.platform_data;
struct i2c_adapter *parent = NULL;
int i;
if (!pdata) {
ERROR_MSG("Missing platform data\n");
return -ENOENT;
}
for (i = 0; i < TOTAL_PORT_NUM; i+=CPLD_MUX_OFFSET) {
data = i2c_get_clientdata(pdata[i].client);
if (!data) {
ERROR_MSG("Empty data. skip. i=%d", i);
continue;
}
qsfp_remove(pdata[i].client, data->qsfp);
if (pdata[i].client) {
parent = (pdata[i].client)->adapter;
i2c_unregister_device(pdata[i].client);
i2c_put_adapter(parent);
}
kfree(data);
}
return 0;
}
static int
cpld_probe(struct platform_device *pdev)
{
struct cpld_platform_data *pdata;
struct i2c_adapter *parent[CPLD_DEVICE_NUM];
int i;
int ret;
pdata = pdev->dev.platform_data;
if (!pdata) {
ERROR_MSG("Missing platform data\n");
return -ENODEV;
}
for (i = 0; i < CPLD_DEVICE_NUM; i++) {
parent[i] = i2c_get_adapter(S8900_64XC_MUX_BASE_NR + i + 1);
if (!parent[i]) {
ERROR_MSG("Parent adapter (%d) not found\n",
S8900_64XC_MUX_BASE_NR + i + 1);
return -ENODEV;
}
pdata[i].client = i2c_new_dummy(parent[i], pdata[i].reg_addr);
if (!pdata[i].client) {
ERROR_MSG("Fail to create dummy i2c client for addr %d\n", pdata[i].reg_addr);
goto error;
}
}
ret = sysfs_create_group(&pdev->dev.kobj, &s8900_64xc_cpld_attr_grp);
if (ret)
goto error;
return 0;
error:
if (i < CPLD_DEVICE_NUM) {
i2c_put_adapter(parent[i]);
}
i--;
for (; i >= 0; i--) {
if (pdata[i].client) {
i2c_unregister_device(pdata[i].client);
i2c_put_adapter(parent[i]);
}
}
return -ENODEV;
}
static int __exit cpld_remove(struct platform_device *pdev)
{
int i;
struct i2c_adapter *parent = NULL;
struct cpld_platform_data *pdata = pdev->dev.platform_data;
sysfs_remove_group(&pdev->dev.kobj, &s8900_64xc_cpld_attr_grp);
if (!pdata) {
ERROR_MSG("Missing platform data\n");
} else {
for (i = 0; i < CPLD_DEVICE_NUM; i++) {
if (pdata[i].client) {
parent = (pdata[i].client)->adapter;
i2c_unregister_device(pdata[i].client);
i2c_put_adapter(parent);
}
}
}
return 0;
}
static struct platform_driver cpld_driver = {
.probe = cpld_probe,
.remove = __exit_p(cpld_remove),
.driver = {
.owner = THIS_MODULE,
.name = "ingrasys-s8900-64xc-cpld",
},
};
static struct platform_driver qsfp_driver = {
.probe = qsfp_device_probe,
.remove = __exit_p(qsfp_device_remove),
//.id_table = qsfp_device_id,
.driver = {
.owner = THIS_MODULE,
.name = "ingrasys-s8900-64xc-sfp",
},
};
static int __init ingrasys_s8900_64xc_platform_init(void)
{
int ret = 0;
DEBUG_PRINT("ingrasysl_s8900_64xc_platform module initialization\n");
//mdelay(10000);
ret = platform_driver_register(&cpld_driver);
if (ret) {
ERROR_MSG("Fail to register cpld driver\n");
goto error_cpld_driver;
}
ret = platform_device_register(&s8900_64xc_cpld);
if (ret) {
ERROR_MSG("Fail to create cpld device\n");
goto error_cpld;
}
ret = platform_driver_register(&qsfp_driver);
if (ret) {
ERROR_MSG("Fail to register sfp driver\n");
goto error_cpld_driver;
}
ret = platform_device_register(&s8900_64xc_sfp);
if (ret) {
ERROR_MSG("Fail to create sfp device\n");
goto error_cpld;
}
return 0;
error_cpld:
platform_driver_unregister(&cpld_driver);
platform_driver_unregister(&qsfp_driver);
error_cpld_driver:
return ret;
}
static void __exit ingrasys_s8900_64xc_platform_exit(void)
{
platform_device_unregister(&s8900_64xc_sfp);
platform_device_unregister(&s8900_64xc_cpld);
platform_driver_unregister(&qsfp_driver);
platform_driver_unregister(&cpld_driver);
}
module_init(ingrasys_s8900_64xc_platform_init);
module_exit(ingrasys_s8900_64xc_platform_exit);
MODULE_DESCRIPTION("Ingrasys S8900-64XC Platform Support");
MODULE_AUTHOR("Wade He ");
MODULE_LICENSE("GPL");