diff --git a/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/Makefile b/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/Makefile index c43c47745229..55bc0efe4040 100755 --- a/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/Makefile +++ b/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/Makefile @@ -1,6 +1,7 @@ obj-m += inv_cpld.o inv_psoc.o obj-m += inv_platform.o obj-m += inv_eeprom.o +obj-m += optoe.o obj-m += swps.o swps-objs := inv_swps.o io_expander.o transceiver.o diff --git a/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/io_expander.c b/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/io_expander.c index 7c9f8a66aec7..847557bede20 100644 --- a/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/io_expander.c +++ b/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/io_expander.c @@ -364,10 +364,7 @@ common_ioexp_init(struct ioexp_obj_s *self) { int chip_id, offset, err_code; struct ioexp_addr_s *addr_p; - - if (self->mode == IOEXP_MODE_DIRECT) { ///important - goto update_common_ioexp_init; - } + /* Setup default value to each physical IO Expander */ for (chip_id=0; chip_id<(self->ioexp_map_p->chip_amount); chip_id++){ /* Get address mapping */ @@ -390,7 +387,6 @@ common_ioexp_init(struct ioexp_obj_s *self) { } } -update_common_ioexp_init: /* Check and update info to object */ err_code = self->update_all(self, 1, "common_ioexp_init"); if (err_code < 0) { diff --git a/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/optoe.c b/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/optoe.c new file mode 120000 index 000000000000..7767daa8fbd9 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/optoe.c @@ -0,0 +1 @@ +../../d7054q28b/modules/optoe.c \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/optoe.h b/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/optoe.h new file mode 120000 index 000000000000..8a05c62a6c87 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/modules/optoe.h @@ -0,0 +1 @@ +../../d7054q28b/modules/optoe.h \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/utils/inventec_d7032_util.py b/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/utils/inventec_d7032_util.py index f6f5e46c5536..a3b2ca7c1d94 100755 --- a/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/utils/inventec_d7032_util.py +++ b/platform/broadcom/sonic-platform-modules-inventec/d7032q28b/utils/inventec_d7032_util.py @@ -124,6 +124,7 @@ def exec_cmd(cmd, show): 'inv_platform', 'inv_psoc', 'inv_cpld', +'optoe', 'swps'] @@ -152,29 +153,44 @@ def system_install(): return status for i in range(22,30): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-4/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe1 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-4/i2c-"+str(i)+"/new_device", 1) if status: print output if FORCE == 0: return status for i in range(30,38): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-5/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe1 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-5/i2c-"+str(i)+"/new_device", 1) if status: print output if FORCE == 0: return status for i in range(6,14): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-2/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe1 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-2/i2c-"+str(i)+"/new_device", 1) if status: print output if FORCE == 0: return status for i in range(14,22): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-3/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe1 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-3/i2c-"+str(i)+"/new_device", 1) if status: print output if FORCE == 0: - return status + return status + #Initialize the port_name + for i in range(22,38): + port=i-22 + status, output =exec_cmd("echo port"+str(port)+"> /sys/bus/i2c/devices/"+str(i)+"-0050/port_name", 1) + if status: + print output + if FORCE == 0: + return status + for i in range(6,22): + port=i+10 + status, output =exec_cmd("echo port"+str(port)+"> /sys/bus/i2c/devices/"+str(i)+"-0050/port_name", 1) + if status: + print output + if FORCE == 0: + return status return diff --git a/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/Makefile b/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/Makefile index aeffa4376588..88a943230344 100755 --- a/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/Makefile +++ b/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/Makefile @@ -1,6 +1,7 @@ obj-m += inv_cpld.o inv_psoc.o obj-m += inv_platform.o obj-m += inv_eeprom.o +obj-m += optoe.o obj-m += swps.o swps-objs := inv_swps.o io_expander.o transceiver.o diff --git a/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/io_expander.c b/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/io_expander.c index 057995e4551a..64cab3cb3092 100644 --- a/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/io_expander.c +++ b/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/io_expander.c @@ -407,9 +407,6 @@ common_ioexp_init(struct ioexp_obj_s *self) { int chip_id, offset, err_code; struct ioexp_addr_s *addr_p; - if (self->mode == IOEXP_MODE_DIRECT) { ///important - goto update_common_ioexp_init; - } /* Setup default value to each physical IO Expander */ for (chip_id=0; chip_id<(self->ioexp_map_p->chip_amount); chip_id++){ /* Get address mapping */ @@ -432,7 +429,6 @@ common_ioexp_init(struct ioexp_obj_s *self) { } } -update_common_ioexp_init: /* Check and update info to object */ err_code = self->update_all(self, 1, "common_ioexp_init"); if (err_code < 0) { diff --git a/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/optoe.c b/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/optoe.c new file mode 100644 index 000000000000..46244b3c70de --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/optoe.c @@ -0,0 +1,1136 @@ +/* + * optoe.c - A driver to read and write the EEPROM on optical transceivers + * (SFP, QSFP and similar I2C based devices) + * + * Copyright (C) 2014 Cumulus networks Inc. + * Copyright (C) 2017 Finisar Corp. + * + * 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 Freeoftware Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * Description: + * a) Optical transceiver EEPROM read/write transactions are just like + * the at24 eeproms managed by the at24.c i2c driver + * b) The register/memory layout is up to 256 128 byte pages defined by + * a "pages valid" register and switched via a "page select" + * register as explained in below diagram. + * c) 256 bytes are mapped at a time. 'Lower page 00h' is the first 128 + * bytes of address space, and always references the same + * location, independent of the page select register. + * All mapped pages are mapped into the upper 128 bytes + * (offset 128-255) of the i2c address. + * d) Devices with one I2C address (eg QSFP) use I2C address 0x50 + * (A0h in the spec), and map all pages in the upper 128 bytes + * of that address. + * e) Devices with two I2C addresses (eg SFP) have 256 bytes of data + * at I2C address 0x50, and 256 bytes of data at I2C address + * 0x51 (A2h in the spec). Page selection and paged access + * only apply to this second I2C address (0x51). + * e) The address space is presented, by the driver, as a linear + * address space. For devices with one I2C client at address + * 0x50 (eg QSFP), offset 0-127 are in the lower + * half of address 50/A0h/client[0]. Offset 128-255 are in + * page 0, 256-383 are page 1, etc. More generally, offset + * 'n' resides in page (n/128)-1. ('page -1' is the lower + * half, offset 0-127). + * f) For devices with two I2C clients at address 0x50 and 0x51 (eg SFP), + * the address space places offset 0-127 in the lower + * half of 50/A0/client[0], offset 128-255 in the upper + * half. Offset 256-383 is in the lower half of 51/A2/client[1]. + * Offset 384-511 is in page 0, in the upper half of 51/A2/... + * Offset 512-639 is in page 1, in the upper half of 51/A2/... + * Offset 'n' is in page (n/128)-3 (for n > 383) + * + * One I2c addressed (eg QSFP) Memory Map + * + * 2-Wire Serial Address: 1010000x + * + * Lower Page 00h (128 bytes) + * ===================== + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * |Page Select Byte(127)| + * ===================== + * | + * | + * | + * | + * V + * ------------------------------------------------------------ + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * V V V V + * ------------ -------------- --------------- -------------- + * | | | | | | | | + * | Upper | | Upper | | Upper | | Upper | + * | Page 00h | | Page 01h | | Page 02h | | Page 03h | + * | | | (Optional) | | (Optional) | | (Optional | + * | | | | | | | for Cable | + * | | | | | | | Assemblies) | + * | ID | | AST | | User | | | + * | Fields | | Table | | EEPROM Data | | | + * | | | | | | | | + * | | | | | | | | + * | | | | | | | | + * ------------ -------------- --------------- -------------- + * + * The SFF 8436 (QSFP) spec only defines the 4 pages described above. + * In anticipation of future applications and devices, this driver + * supports access to the full architected range, 256 pages. + * + **/ + +/* #define DEBUG 1 */ +#define DEBUG 1 + +#undef EEPROM_CLASS +#ifdef CONFIG_EEPROM_CLASS +#define EEPROM_CLASS +#endif +#ifdef CONFIG_EEPROM_CLASS_MODULE +#define EEPROM_CLASS +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "optoe.h" + +#ifdef EEPROM_CLASS +#include +#endif + +#include + +/* fundamental unit of addressing for EEPROM */ +#define OPTOE_PAGE_SIZE 128 +/* + * Single address devices (eg QSFP) have 256 pages, plus the unpaged + * low 128 bytes. If the device does not support paging, it is + * only 2 'pages' long. + */ +#define OPTOE_ARCH_PAGES 256 +#define ONE_ADDR_EEPROM_SIZE ((1 + OPTOE_ARCH_PAGES) * OPTOE_PAGE_SIZE) +#define ONE_ADDR_EEPROM_UNPAGED_SIZE (2 * OPTOE_PAGE_SIZE) +/* + * Dual address devices (eg SFP) have 256 pages, plus the unpaged + * low 128 bytes, plus 256 bytes at 0x50. If the device does not + * support paging, it is 4 'pages' long. + */ +#define TWO_ADDR_EEPROM_SIZE ((3 + OPTOE_ARCH_PAGES) * OPTOE_PAGE_SIZE) +#define TWO_ADDR_EEPROM_UNPAGED_SIZE (4 * OPTOE_PAGE_SIZE) + +/* a few constants to find our way around the EEPROM */ +#define OPTOE_PAGE_SELECT_REG 0x7F +#define ONE_ADDR_PAGEABLE_REG 0x02 +#define ONE_ADDR_NOT_PAGEABLE (1<<2) +#define TWO_ADDR_PAGEABLE_REG 0x40 +#define TWO_ADDR_PAGEABLE (1<<4) +#define OPTOE_ID_REG 0 +#define OPTOE_READ_OP 0 +#define OPTOE_WRITE_OP 1 + +/* The maximum length of a port name */ +#define MAX_PORT_NAME_LEN 20 +struct optoe_data { + struct optoe_platform_data chip; + struct memory_accessor macc; + int use_smbus; + char port_name[MAX_PORT_NAME_LEN]; + + /* + * Lock protects against activities from other Linux tasks, + * but not from changes by other I2C masters. + */ + struct mutex lock; + struct bin_attribute bin; + struct attribute_group attr_group; + + u8 *writebuf; + unsigned int write_max; + + unsigned int num_addresses; + +#ifdef EEPROM_CLASS + struct eeprom_device *eeprom_dev; +#endif + + /* dev_class: ONE_ADDR (QSFP) or TWO_ADDR (SFP) */ + int dev_class; + + struct i2c_client *client[]; +}; + + +/* + * This parameter is to help this driver avoid blocking other drivers out + * of I2C for potentially troublesome amounts of time. With a 100 kHz I2C + * clock, one 256 byte read takes about 1/43 second which is excessive; + * but the 1/170 second it takes at 400 kHz may be quite reasonable; and + * at 1 MHz (Fm+) a 1/430 second delay could easily be invisible. + * + * This value is forced to be a power of two so that writes align on pages. + */ +static unsigned int io_limit = OPTOE_PAGE_SIZE; + +/* + * specs often allow 5 msec for a page write, sometimes 20 msec; + * it's important to recover from write timeouts. + */ +static unsigned int write_timeout = 25; + +/* + * flags to distinguish one-address (QSFP family) from two-address (SFP family) + * If the family is not known, figure it out when the device is accessed + */ +#define ONE_ADDR 1 +#define TWO_ADDR 2 + +static const struct i2c_device_id optoe_ids[] = { + { "optoe1", ONE_ADDR }, + { "optoe2", TWO_ADDR }, + { "sff8436", ONE_ADDR }, + { "24c04", TWO_ADDR }, + { /* END OF LIST */ } +}; +MODULE_DEVICE_TABLE(i2c, optoe_ids); + +/*-------------------------------------------------------------------------*/ +/* + * This routine computes the addressing information to be used for + * a given r/w request. + * + * Task is to calculate the client (0 = i2c addr 50, 1 = i2c addr 51), + * the page, and the offset. + * + * Handles both single address (eg QSFP) and two address (eg SFP). + * For SFP, offset 0-255 are on client[0], >255 is on client[1] + * Offset 256-383 are on the lower half of client[1] + * Pages are accessible on the upper half of client[1]. + * Offset >383 are in 128 byte pages mapped into the upper half + * + * For QSFP, all offsets are on client[0] + * offset 0-127 are on the lower half of client[0] (no paging) + * Pages are accessible on the upper half of client[1]. + * Offset >127 are in 128 byte pages mapped into the upper half + * + * Callers must not read/write beyond the end of a client or a page + * without recomputing the client/page. Hence offset (within page) + * plus length must be less than or equal to 128. (Note that this + * routine does not have access to the length of the call, hence + * cannot do the validity check.) + * + * Offset within Lower Page 00h and Upper Page 00h are not recomputed + */ + +static uint8_t optoe_translate_offset(struct optoe_data *optoe, + loff_t *offset, struct i2c_client **client) +{ + unsigned int page = 0; + + *client = optoe->client[0]; + + /* if SFP style, offset > 255, shift to i2c addr 0x51 */ + if (optoe->dev_class == TWO_ADDR) { + if (*offset > 255) { + /* like QSFP, but shifted to client[1] */ + *client = optoe->client[1]; + *offset -= 256; + } + } + + /* + * if offset is in the range 0-128... + * page doesn't matter (using lower half), return 0. + * offset is already correct (don't add 128 to get to paged area) + */ + if (*offset < OPTOE_PAGE_SIZE) + return page; + + /* note, page will always be positive since *offset >= 128 */ + page = (*offset >> 7)-1; + /* 0x80 places the offset in the top half, offset is last 7 bits */ + *offset = OPTOE_PAGE_SIZE + (*offset & 0x7f); + + return page; /* note also returning client and offset */ +} + +static ssize_t optoe_eeprom_read(struct optoe_data *optoe, + struct i2c_client *client, + char *buf, unsigned int offset, size_t count) +{ + struct i2c_msg msg[2]; + u8 msgbuf[2]; + unsigned long timeout, read_time; + int status, i; + + memset(msg, 0, sizeof(msg)); + + switch (optoe->use_smbus) { + case I2C_SMBUS_I2C_BLOCK_DATA: + /*smaller eeproms can work given some SMBus extension calls */ + if (count > I2C_SMBUS_BLOCK_MAX) + count = I2C_SMBUS_BLOCK_MAX; + break; + case I2C_SMBUS_WORD_DATA: + /* Check for odd length transaction */ + count = (count == 1) ? 1 : 2; + break; + case I2C_SMBUS_BYTE_DATA: + count = 1; + break; + default: + /* + * When we have a better choice than SMBus calls, use a + * combined I2C message. Write address; then read up to + * io_limit data bytes. msgbuf is u8 and will cast to our + * needs. + */ + i = 0; + msgbuf[i++] = offset; + + msg[0].addr = client->addr; + msg[0].buf = msgbuf; + msg[0].len = i; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = buf; + msg[1].len = count; + } + + /* + * Reads fail if the previous write didn't complete yet. We may + * loop a few times until this one succeeds, waiting at least + * long enough for one entire page write to work. + */ + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + read_time = jiffies; + + switch (optoe->use_smbus) { + case I2C_SMBUS_I2C_BLOCK_DATA: + status = i2c_smbus_read_i2c_block_data(client, offset, + count, buf); + break; + case I2C_SMBUS_WORD_DATA: + status = i2c_smbus_read_word_data(client, offset); + if (status >= 0) { + buf[0] = status & 0xff; + if (count == 2) + buf[1] = status >> 8; + status = count; + } + break; + case I2C_SMBUS_BYTE_DATA: + status = i2c_smbus_read_byte_data(client, offset); + if (status >= 0) { + buf[0] = status; + status = count; + } + break; + default: + status = i2c_transfer(client->adapter, msg, 2); + if (status == 2) + status = count; + } + + dev_dbg(&client->dev, "eeprom read %zu@%d --> %d (%ld)\n", + count, offset, status, jiffies); + + if (status == count) /* happy path */ + return count; + + if (status == -ENXIO) /* no module present */ + return status; + + /* REVISIT: at HZ=100, this is sloooow */ + usleep_range(1000, 2000); + } while (time_before(read_time, timeout)); + + return -ETIMEDOUT; +} + +static ssize_t optoe_eeprom_write(struct optoe_data *optoe, + struct i2c_client *client, + const char *buf, + unsigned int offset, size_t count) +{ + struct i2c_msg msg; + ssize_t status; + unsigned long timeout, write_time; + unsigned int next_page_start; + int i = 0; + + /* write max is at most a page + * (In this driver, write_max is actually one byte!) + */ + if (count > optoe->write_max) + count = optoe->write_max; + + /* shorten count if necessary to avoid crossing page boundary */ + next_page_start = roundup(offset + 1, OPTOE_PAGE_SIZE); + if (offset + count > next_page_start) + count = next_page_start - offset; + + switch (optoe->use_smbus) { + case I2C_SMBUS_I2C_BLOCK_DATA: + /*smaller eeproms can work given some SMBus extension calls */ + if (count > I2C_SMBUS_BLOCK_MAX) + count = I2C_SMBUS_BLOCK_MAX; + break; + case I2C_SMBUS_WORD_DATA: + /* Check for odd length transaction */ + count = (count == 1) ? 1 : 2; + break; + case I2C_SMBUS_BYTE_DATA: + count = 1; + break; + default: + /* If we'll use I2C calls for I/O, set up the message */ + msg.addr = client->addr; + msg.flags = 0; + + /* msg.buf is u8 and casts will mask the values */ + msg.buf = optoe->writebuf; + + msg.buf[i++] = offset; + memcpy(&msg.buf[i], buf, count); + msg.len = i + count; + break; + } + + /* + * Reads fail if the previous write didn't complete yet. We may + * loop a few times until this one succeeds, waiting at least + * long enough for one entire page write to work. + */ + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + write_time = jiffies; + + switch (optoe->use_smbus) { + case I2C_SMBUS_I2C_BLOCK_DATA: + status = i2c_smbus_write_i2c_block_data(client, + offset, count, buf); + if (status == 0) + status = count; + break; + case I2C_SMBUS_WORD_DATA: + if (count == 2) { + status = i2c_smbus_write_word_data(client, + offset, (u16)((buf[0])|(buf[1] << 8))); + } else { + /* count = 1 */ + status = i2c_smbus_write_byte_data(client, + offset, buf[0]); + } + if (status == 0) + status = count; + break; + case I2C_SMBUS_BYTE_DATA: + status = i2c_smbus_write_byte_data(client, offset, + buf[0]); + if (status == 0) + status = count; + break; + default: + status = i2c_transfer(client->adapter, &msg, 1); + if (status == 1) + status = count; + break; + } + + dev_dbg(&client->dev, "eeprom write %zu@%d --> %ld (%lu)\n", + count, offset, (long int) status, jiffies); + + if (status == count) + return count; + + /* REVISIT: at HZ=100, this is sloooow */ + usleep_range(1000, 2000); + } while (time_before(write_time, timeout)); + + return -ETIMEDOUT; +} + + +static ssize_t optoe_eeprom_update_client(struct optoe_data *optoe, + char *buf, loff_t off, + size_t count, int opcode) +{ + struct i2c_client *client; + ssize_t retval = 0; + uint8_t page = 0; + loff_t phy_offset = off; + int ret = 0; + + page = optoe_translate_offset(optoe, &phy_offset, &client); + dev_dbg(&client->dev, + "__func__ off %lld page:%d phy_offset:%lld, count:%ld, opcode:%d\n", + off, page, phy_offset, (long int) count, opcode); + if (page > 0) { + ret = optoe_eeprom_write(optoe, client, &page, + OPTOE_PAGE_SELECT_REG, 1); + if (ret < 0) { + dev_dbg(&client->dev, + "Write page register for page %d failed ret:%d!\n", + page, ret); + return ret; + } + } + + while (count) { + ssize_t status; + + if (opcode == OPTOE_READ_OP) { + status = optoe_eeprom_read(optoe, client, + buf, phy_offset, count); + } else { + status = optoe_eeprom_write(optoe, client, + buf, phy_offset, count); + } + if (status <= 0) { + if (retval == 0) + retval = status; + break; + } + buf += status; + phy_offset += status; + count -= status; + retval += status; + } + + + if (page > 0) { + /* return the page register to page 0 (why?) */ + page = 0; + ret = optoe_eeprom_write(optoe, client, &page, + OPTOE_PAGE_SELECT_REG, 1); + if (ret < 0) { + dev_err(&client->dev, + "Restore page register to 0 failed:%d!\n", ret); + /* error only if nothing has been transferred */ + if (retval == 0) + retval = ret; + } + } + return retval; +} + +/* + * Figure out if this access is within the range of supported pages. + * Note this is called on every access because we don't know if the + * module has been replaced since the last call. + * If/when modules support more pages, this is the routine to update + * to validate and allow access to additional pages. + * + * Returns updated len for this access: + * - entire access is legal, original len is returned. + * - access begins legal but is too long, len is truncated to fit. + * - initial offset exceeds supported pages, return -EINVAL + */ +static ssize_t optoe_page_legal(struct optoe_data *optoe, + loff_t off, size_t len) +{ + struct i2c_client *client = optoe->client[0]; + u8 regval; + int status; + size_t maxlen; + + if (off < 0) + return -EINVAL; + if (optoe->dev_class == TWO_ADDR) { + /* SFP case */ + /* if no pages needed, we're good */ + if ((off + len) <= TWO_ADDR_EEPROM_UNPAGED_SIZE) + return len; + /* if offset exceeds possible pages, we're not good */ + if (off >= TWO_ADDR_EEPROM_SIZE) + return -EINVAL; + /* in between, are pages supported? */ + status = optoe_eeprom_read(optoe, client, ®val, + TWO_ADDR_PAGEABLE_REG, 1); + if (status < 0) + return status; /* error out (no module?) */ + if (regval & TWO_ADDR_PAGEABLE) { + /* Pages supported, trim len to the end of pages */ + maxlen = TWO_ADDR_EEPROM_SIZE - off; + } else { + /* pages not supported, trim len to unpaged size */ + if (off >= TWO_ADDR_EEPROM_UNPAGED_SIZE) + return -EINVAL; + maxlen = TWO_ADDR_EEPROM_UNPAGED_SIZE - off; + } + len = (len > maxlen) ? maxlen : len; + dev_dbg(&client->dev, + "page_legal, SFP, off %lld len %ld\n", + off, (long int) len); + } else { + /* QSFP case */ + /* if no pages needed, we're good */ + if ((off + len) <= ONE_ADDR_EEPROM_UNPAGED_SIZE) + return len; + /* if offset exceeds possible pages, we're not good */ + if (off >= ONE_ADDR_EEPROM_SIZE) + return -EINVAL; + /* in between, are pages supported? */ + status = optoe_eeprom_read(optoe, client, ®val, + ONE_ADDR_PAGEABLE_REG, 1); + if (status < 0) + return status; /* error out (no module?) */ + if (regval & ONE_ADDR_NOT_PAGEABLE) { + /* pages not supported, trim len to unpaged size */ + if (off >= ONE_ADDR_EEPROM_UNPAGED_SIZE) + return -EINVAL; + maxlen = ONE_ADDR_EEPROM_UNPAGED_SIZE - off; + } else { + /* Pages supported, trim len to the end of pages */ + maxlen = ONE_ADDR_EEPROM_SIZE - off; + } + len = (len > maxlen) ? maxlen : len; + dev_dbg(&client->dev, + "page_legal, QSFP, off %lld len %ld\n", + off, (long int) len); + } + return len; +} + +static ssize_t optoe_read_write(struct optoe_data *optoe, + char *buf, loff_t off, size_t len, int opcode) +{ + struct i2c_client *client = optoe->client[0]; + int chunk; + int status = 0; + ssize_t retval; + size_t pending_len = 0, chunk_len = 0; + loff_t chunk_offset = 0, chunk_start_offset = 0; + + dev_dbg(&client->dev, + "__func__: off %lld len:%ld, opcode:%s\n", + off, (long int) len, (opcode == OPTOE_READ_OP) ? "r" : "w"); + if (unlikely(!len)) + return len; + + /* + * Read data from chip, protecting against concurrent updates + * from this host, but not from other I2C masters. + */ + mutex_lock(&optoe->lock); + + /* + * Confirm this access fits within the device suppored addr range + */ + status = optoe_page_legal(optoe, off, len); + if (status < 0) + goto err; + len = status; + + /* + * For each (128 byte) chunk involved in this request, issue a + * separate call to sff_eeprom_update_client(), to + * ensure that each access recalculates the client/page + * and writes the page register as needed. + * Note that chunk to page mapping is confusing, is different for + * QSFP and SFP, and never needs to be done. Don't try! + */ + pending_len = len; /* amount remaining to transfer */ + retval = 0; /* amount transferred */ + for (chunk = off >> 7; chunk <= (off + len - 1) >> 7; chunk++) { + + /* + * Compute the offset and number of bytes to be read/write + * + * 1. start at offset 0 (within the chunk), and read/write + * the entire chunk + * 2. start at offset 0 (within the chunk) and read/write less + * than entire chunk + * 3. start at an offset not equal to 0 and read/write the rest + * of the chunk + * 4. start at an offset not equal to 0 and read/write less than + * (end of chunk - offset) + */ + chunk_start_offset = chunk * OPTOE_PAGE_SIZE; + + if (chunk_start_offset < off) { + chunk_offset = off; + if ((off + pending_len) < (chunk_start_offset + + OPTOE_PAGE_SIZE)) + chunk_len = pending_len; + else + chunk_len = OPTOE_PAGE_SIZE - off; + } else { + chunk_offset = chunk_start_offset; + if (pending_len > OPTOE_PAGE_SIZE) + chunk_len = OPTOE_PAGE_SIZE; + else + chunk_len = pending_len; + } + + dev_dbg(&client->dev, + "sff_r/w: off %lld, len %ld, chunk_start_offset %lld, chunk_offset %lld, chunk_len %ld, pending_len %ld\n", + off, (long int) len, chunk_start_offset, chunk_offset, + (long int) chunk_len, (long int) pending_len); + + /* + * note: chunk_offset is from the start of the EEPROM, + * not the start of the chunk + */ + status = optoe_eeprom_update_client(optoe, buf, + chunk_offset, chunk_len, opcode); + if (status != chunk_len) { + /* This is another 'no device present' path */ + dev_dbg(&client->dev, + "o_u_c: chunk %d c_offset %lld c_len %ld failed %d!\n", + chunk, chunk_offset, (long int) chunk_len, status); + goto err; + } + buf += status; + pending_len -= status; + retval += status; + } + mutex_unlock(&optoe->lock); + + return retval; + +err: + mutex_unlock(&optoe->lock); + + return status; +} + +static ssize_t optoe_bin_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = to_i2c_client(container_of(kobj, + struct device, kobj)); + struct optoe_data *optoe = i2c_get_clientdata(client); + + return optoe_read_write(optoe, buf, off, count, OPTOE_READ_OP); +} + + +static ssize_t optoe_bin_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = to_i2c_client(container_of(kobj, + struct device, kobj)); + struct optoe_data *optoe = i2c_get_clientdata(client); + + return optoe_read_write(optoe, buf, off, count, OPTOE_WRITE_OP); +} +/*-------------------------------------------------------------------------*/ + +/* + * This lets other kernel code access the eeprom data. For example, it + * might hold a board's Ethernet address, or board-specific calibration + * data generated on the manufacturing floor. + */ + +static ssize_t optoe_macc_read(struct memory_accessor *macc, + char *buf, off_t offset, size_t count) +{ + struct optoe_data *optoe = container_of(macc, + struct optoe_data, macc); + + return optoe_read_write(optoe, buf, offset, count, OPTOE_READ_OP); +} + +static ssize_t optoe_macc_write(struct memory_accessor *macc, + const char *buf, off_t offset, size_t count) +{ + struct optoe_data *optoe = container_of(macc, + struct optoe_data, macc); + + return optoe_read_write(optoe, (char *) buf, offset, + count, OPTOE_WRITE_OP); +} + +/*-------------------------------------------------------------------------*/ + +static int optoe_remove(struct i2c_client *client) +{ + struct optoe_data *optoe; + int i; + + optoe = i2c_get_clientdata(client); + sysfs_remove_group(&client->dev.kobj, &optoe->attr_group); + sysfs_remove_bin_file(&client->dev.kobj, &optoe->bin); + + for (i = 1; i < optoe->num_addresses; i++) + i2c_unregister_device(optoe->client[i]); + +#ifdef EEPROM_CLASS + eeprom_device_unregister(optoe->eeprom_dev); +#endif + + kfree(optoe->writebuf); + kfree(optoe); + return 0; +} + +static ssize_t show_port_name(struct device *dev, + struct device_attribute *dattr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct optoe_data *optoe = i2c_get_clientdata(client); + ssize_t count; + + mutex_lock(&optoe->lock); + count = sprintf(buf, "%s\n", optoe->port_name); + mutex_unlock(&optoe->lock); + + return count; +} + +static ssize_t set_port_name(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct optoe_data *optoe = i2c_get_clientdata(client); + char port_name[MAX_PORT_NAME_LEN]; + + /* no checking, this value is not used except by show_port_name */ + + if (sscanf(buf, "%19s", port_name) != 1) + return -EINVAL; + + mutex_lock(&optoe->lock); + strcpy(optoe->port_name, port_name); + mutex_unlock(&optoe->lock); + + return count; +} + +static DEVICE_ATTR(port_name, 0644, show_port_name, set_port_name); + +static ssize_t show_dev_class(struct device *dev, + struct device_attribute *dattr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct optoe_data *optoe = i2c_get_clientdata(client); + ssize_t count; + + mutex_lock(&optoe->lock); + count = sprintf(buf, "%d\n", optoe->dev_class); + mutex_unlock(&optoe->lock); + + return count; +} + +static ssize_t set_dev_class(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct optoe_data *optoe = i2c_get_clientdata(client); + int dev_class; + + /* + * dev_class is actually the number of i2c addresses used, thus + * legal values are "1" (QSFP class) and "2" (SFP class) + */ + + if (kstrtoint(buf, 0, &dev_class) != 0 || + dev_class < 1 || dev_class > 2) + return -EINVAL; + + mutex_lock(&optoe->lock); + optoe->dev_class = dev_class; + mutex_unlock(&optoe->lock); + + return count; +} + +static DEVICE_ATTR(dev_class, 0644, show_dev_class, set_dev_class); + +static struct attribute *optoe_attrs[] = { + &dev_attr_port_name.attr, + &dev_attr_dev_class.attr, + NULL, +}; + +static struct attribute_group optoe_attr_group = { + .attrs = optoe_attrs, +}; + +static int optoe_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + int use_smbus = 0; + struct optoe_platform_data chip; + struct optoe_data *optoe; + int num_addresses = 0; + int i = 0; + + if (client->addr != 0x50) { + dev_dbg(&client->dev, "probe, bad i2c addr: 0x%x\n", + client->addr); + err = -EINVAL; + goto exit; + } + + if (client->dev.platform_data) { + chip = *(struct optoe_platform_data *)client->dev.platform_data; + dev_dbg(&client->dev, + "probe, chip provided, flags:0x%x; name: %s\n", + chip.flags, client->name); + } else { + if (!id->driver_data) { + err = -ENODEV; + goto exit; + } + dev_dbg(&client->dev, "probe, building chip\n"); + chip.flags = 0; + chip.setup = NULL; + chip.context = NULL; +#ifdef EEPROM_CLASS + chip.eeprom_data = NULL; +#endif + } + + /* Use I2C operations unless we're stuck with SMBus extensions. */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + use_smbus = I2C_SMBUS_I2C_BLOCK_DATA; + } else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) { + use_smbus = I2C_SMBUS_WORD_DATA; + } else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA)) { + use_smbus = I2C_SMBUS_BYTE_DATA; + } else { + err = -EPFNOSUPPORT; + goto exit; + } + } + + + /* + * Make room for two i2c clients + */ + num_addresses = 2; + + optoe = kzalloc(sizeof(struct optoe_data) + + num_addresses * sizeof(struct i2c_client *), + GFP_KERNEL); + if (!optoe) { + err = -ENOMEM; + goto exit; + } + + mutex_init(&optoe->lock); + + /* determine whether this is a one-address or two-address module */ + if ((strcmp(client->name, "optoe1") == 0) || + (strcmp(client->name, "sff8436") == 0)) { + /* one-address (eg QSFP) family */ + optoe->dev_class = ONE_ADDR; + chip.byte_len = ONE_ADDR_EEPROM_SIZE; + num_addresses = 1; + } else if ((strcmp(client->name, "optoe2") == 0) || + (strcmp(client->name, "24c04") == 0)) { + /* SFP family */ + optoe->dev_class = TWO_ADDR; + chip.byte_len = TWO_ADDR_EEPROM_SIZE; + } else { /* those were the only two choices */ + err = -EINVAL; + goto exit; + } + + dev_dbg(&client->dev, "dev_class: %d\n", optoe->dev_class); + optoe->use_smbus = use_smbus; + optoe->chip = chip; + optoe->num_addresses = num_addresses; + strcpy(optoe->port_name, "unitialized"); + + /* + * Export the EEPROM bytes through sysfs, since that's convenient. + * By default, only root should see the data (maybe passwords etc) + */ + sysfs_bin_attr_init(&optoe->bin); + optoe->bin.attr.name = "eeprom"; + optoe->bin.attr.mode = 0444; + optoe->bin.read = optoe_bin_read; + optoe->bin.size = chip.byte_len; + + optoe->macc.read = optoe_macc_read; + + if (!use_smbus || + (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) || + i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_WORD_DATA) || + i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) { + /* + * NOTE: AN-2079 + * Finisar recommends that the host implement 1 byte writes + * only since this module only supports 32 byte page boundaries. + * 2 byte writes are acceptable for PE and Vout changes per + * Application Note AN-2071. + */ + unsigned int write_max = 1; + + optoe->macc.write = optoe_macc_write; + + optoe->bin.write = optoe_bin_write; + optoe->bin.attr.mode |= 0200; + + if (write_max > io_limit) + write_max = io_limit; + if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX) + write_max = I2C_SMBUS_BLOCK_MAX; + optoe->write_max = write_max; + + /* buffer (data + address at the beginning) */ + optoe->writebuf = kmalloc(write_max + 2, GFP_KERNEL); + if (!optoe->writebuf) { + err = -ENOMEM; + goto exit_kfree; + } + } else { + dev_warn(&client->dev, + "cannot write due to controller restrictions."); + } + + optoe->client[0] = client; + + /* use a dummy I2C device for two-address chips */ + for (i = 1; i < num_addresses; i++) { + optoe->client[i] = i2c_new_dummy(client->adapter, + client->addr + i); + if (!optoe->client[i]) { + dev_err(&client->dev, "address 0x%02x unavailable\n", + client->addr + i); + err = -EADDRINUSE; + goto err_struct; + } + } + + /* create the sysfs eeprom file */ + err = sysfs_create_bin_file(&client->dev.kobj, &optoe->bin); + if (err) + goto err_struct; + + optoe->attr_group = optoe_attr_group; + + err = sysfs_create_group(&client->dev.kobj, &optoe->attr_group); + if (err) { + dev_err(&client->dev, "failed to create sysfs attribute group.\n"); + goto err_struct; + } +#ifdef EEPROM_CLASS + optoe->eeprom_dev = eeprom_device_register(&client->dev, + chip.eeprom_data); + if (IS_ERR(optoe->eeprom_dev)) { + dev_err(&client->dev, "error registering eeprom device.\n"); + err = PTR_ERR(optoe->eeprom_dev); + goto err_sysfs_cleanup; + } +#endif + + i2c_set_clientdata(client, optoe); + + dev_info(&client->dev, "%zu byte %s EEPROM, %s\n", + optoe->bin.size, client->name, + optoe->bin.write ? "read/write" : "read-only"); + + if (use_smbus == I2C_SMBUS_WORD_DATA || + use_smbus == I2C_SMBUS_BYTE_DATA) { + dev_notice(&client->dev, + "Falling back to %s reads, performance will suffer\n", + use_smbus == I2C_SMBUS_WORD_DATA ? "word" : "byte"); + } + + if (chip.setup) + chip.setup(&optoe->macc, chip.context); + + return 0; + +#ifdef EEPROM_CLASS +err_sysfs_cleanup: + sysfs_remove_group(&client->dev.kobj, &optoe->attr_group); + sysfs_remove_bin_file(&client->dev.kobj, &optoe->bin); +#endif + +err_struct: + for (i = 1; i < num_addresses; i++) { + if (optoe->client[i]) + i2c_unregister_device(optoe->client[i]); + } + + kfree(optoe->writebuf); +exit_kfree: + kfree(optoe); +exit: + dev_dbg(&client->dev, "probe error %d\n", err); + + return err; +} + +/*-------------------------------------------------------------------------*/ + +static struct i2c_driver optoe_driver = { + .driver = { + .name = "optoe", + .owner = THIS_MODULE, + }, + .probe = optoe_probe, + .remove = optoe_remove, + .id_table = optoe_ids, +}; + +static int __init optoe_init(void) +{ + + if (!io_limit) { + pr_err("optoe: io_limit must not be 0!\n"); + return -EINVAL; + } + + io_limit = rounddown_pow_of_two(io_limit); + return i2c_add_driver(&optoe_driver); +} +module_init(optoe_init); + +static void __exit optoe_exit(void) +{ + i2c_del_driver(&optoe_driver); +} +module_exit(optoe_exit); + +MODULE_DESCRIPTION("Driver for optical transceiver (SFP, QSFP, ...) EEPROMs"); +MODULE_AUTHOR("DON BOLLINGER "); +MODULE_LICENSE("GPL"); + diff --git a/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/optoe.h b/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/optoe.h new file mode 100644 index 000000000000..45eff59cf95f --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/modules/optoe.h @@ -0,0 +1,34 @@ + +#ifndef _LINUX_OPTOE_H +#define _LINUX_OPTOE_H + +#include +#include + +#ifdef EEPROM_CLASS +#include +#endif + +/* + * The optoe driver is for read/write access to the EEPROM on standard + * I2C based optical transceivers (SFP, QSFP, etc) + * + * While based on the at24 driver, it eliminates code that supports other + * types of I2C EEPROMs, and adds support for pages accessed through the + * page-select register at offset 127. + */ + +struct optoe_platform_data { + u32 byte_len; /* size (sum of all addr) */ + u16 page_size; /* for writes */ + u8 flags; + + void (*setup)(struct memory_accessor *, void *context); + void *context; +#ifdef EEPROM_CLASS + struct eeprom_platform_data *eeprom_data; /* extra data for the eeprom_class */ +#endif +}; + +#endif /* _LINUX_OPTOE_H */ + diff --git a/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/utils/inventec_d7054_util.py b/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/utils/inventec_d7054_util.py index 7fbb707f4187..d48e4002f0c9 100755 --- a/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/utils/inventec_d7054_util.py +++ b/platform/broadcom/sonic-platform-modules-inventec/d7054q28b/utils/inventec_d7054_util.py @@ -127,6 +127,7 @@ def exec_cmd(cmd, show): 'inv_platform', 'inv_psoc', 'inv_cpld', +'optoe', 'swps'] @@ -152,43 +153,55 @@ def system_install(): return status for i in range(10,18): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-2/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe2 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-2/i2c-"+str(i)+"/new_device", 1) if status: print output if FORCE == 0: return status for i in range(18,26): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-3/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe2 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-3/i2c-"+str(i)+"/new_device", 1) if status: print output if FORCE == 0: return status for i in range(26,34): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-4/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe2 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-4/i2c-"+str(i)+"/new_device", 1) if status: print output if FORCE == 0: return status for i in range(34,42): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-5/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe2 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-5/i2c-"+str(i)+"/new_device", 1) if status: print output if FORCE == 0: return status for i in range(42,50): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-6/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe2 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-6/i2c-"+str(i)+"/new_device", 1) if status: print output if FORCE == 0: return status for i in range(50,58): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-7/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe2 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-7/i2c-"+str(i)+"/new_device", 1) if status: print output if FORCE == 0: return status for i in range(58,64): - status, output =exec_cmd("echo sff8436 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-8/i2c-"+str(i)+"/new_device", 1) + status, output =exec_cmd("echo optoe1 0x50 > /sys/bus/i2c/devices/i2c-0/i2c-8/i2c-"+str(i)+"/new_device", 1) + if status: + print output + if FORCE == 0: + return status + #Initialize the port_name + for i in range(10,64): + port=i-10 + if(port%2 == 0): + port =port + 1 + else: + port =port - 1 + status, output =exec_cmd("echo port"+str(port)+"> /sys/bus/i2c/devices/"+str(i)+"-0050/port_name", 1) if status: print output if FORCE == 0: