summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@armlinux.org.uk>2019-10-04 17:45:58 +0100
committerRussell King (Oracle) <rmk+kernel@armlinux.org.uk>2022-01-18 10:19:16 +0000
commit298f6bbcfef04a3d952c785ae331e80d2c1ee6d0 (patch)
tree93486018b00eb4d757b0d6f1c98705dd08ac2f5f
parent08650d8b10b8a69f356808b90aa2e5f8ac692898 (diff)
net: add qsfp support [*experimental*]
Add experimental QSFP+ support for the SolidRun Clearfog-CX platform. Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
-rw-r--r--drivers/net/phy/Kconfig6
-rw-r--r--drivers/net/phy/Makefile1
-rw-r--r--drivers/net/phy/qsfp.c1835
-rw-r--r--include/linux/sfp.h112
4 files changed, 1953 insertions, 1 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index dbd5ccbd3fbd..007783618cbd 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -65,6 +65,12 @@ config SFP
select MDIO_I2C
select SFP_BUS
+config QSFP
+ tristate "QSFP cage support"
+ depends on I2C
+ select MDIO_I2C
+ select SFP_BUS
+
comment "MII PHY device drivers"
config AMD_PHY
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 08ba7c745f7f..f152b27cef46 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_PHYLIB) += libphy.o
obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += mii_timestamper.o
obj-$(CONFIG_SFP) += sff.o sfp.o
+obj-$(CONFIG_QSFP) += sff.o qsfp.o
obj-$(CONFIG_SFP_BUS) += sfp-bus.o
obj-$(CONFIG_ADIN_PHY) += adin.o
diff --git a/drivers/net/phy/qsfp.c b/drivers/net/phy/qsfp.c
new file mode 100644
index 000000000000..6f332f3a14ec
--- /dev/null
+++ b/drivers/net/phy/qsfp.c
@@ -0,0 +1,1835 @@
+// SPDX-License-Identifier: GPL-2.0
+#define DEBUG
+#include <linux/acpi.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/mdio/mdio-i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/rtnetlink.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include "sff.h"
+#include "sfp.h"
+#include "swphy.h"
+
+enum {
+ GPIO_MODPRS,
+ GPIO_INTL,
+ GPIO_MAX,
+
+ SFP_F_PRESENT = BIT(GPIO_MODPRS),
+ SFP_F_INTL = BIT(GPIO_INTL),
+ SFP_F_LOS = BIT(GPIO_MAX + 0),
+ SFP_F_TX_FAULT = BIT(GPIO_MAX + 1),
+ SFP_F_TX_DISABLE = BIT(GPIO_MAX + 2),
+
+ SFP_E_ATTACH = 0,
+ SFP_E_DETACH,
+ SFP_E_INSERT,
+ SFP_E_REMOVE,
+ SFP_E_DEV_DOWN,
+ SFP_E_DEV_UP,
+ SFP_E_TX_FAULT,
+ SFP_E_TX_CLEAR,
+ SFP_E_LOS_HIGH,
+ SFP_E_LOS_LOW,
+ SFP_E_TIMEOUT,
+
+ SFP_MOD_EMPTY = 0,
+ SFP_MOD_ERROR,
+ SFP_MOD_PROBE,
+ SFP_MOD_WATTACH,
+ SFP_MOD_PRESENT,
+
+ SFP_DEV_DETACHED = 0,
+ SFP_DEV_DOWN,
+ SFP_DEV_UP,
+
+ SFP_S_DOWN = 0,
+ SFP_S_ERROR,
+ SFP_S_WPOWER,
+ SFP_S_WTXEN,
+ SFP_S_INIT,
+ SFP_S_WAIT_LOS,
+ SFP_S_LINK_UP,
+ SFP_S_TX_FAULT,
+ SFP_S_REINIT,
+ SFP_S_TX_DISABLE,
+};
+
+static const char * const mod_state_strings[] = {
+ [SFP_MOD_EMPTY] = "empty",
+ [SFP_MOD_ERROR] = "error",
+ [SFP_MOD_PROBE] = "probe",
+ [SFP_MOD_WATTACH] = "wattach",
+ [SFP_MOD_PRESENT] = "present",
+};
+
+static const char *mod_state_to_str(unsigned short mod_state)
+{
+ if (mod_state >= ARRAY_SIZE(mod_state_strings))
+ return "Unknown module state";
+ return mod_state_strings[mod_state];
+}
+
+static const char * const dev_state_strings[] = {
+ [SFP_DEV_DETACHED] = "unattached",
+ [SFP_DEV_DOWN] = "down",
+ [SFP_DEV_UP] = "up",
+};
+
+static const char *dev_state_to_str(unsigned short dev_state)
+{
+ if (dev_state >= ARRAY_SIZE(dev_state_strings))
+ return "Unknown device state";
+ return dev_state_strings[dev_state];
+}
+
+static const char * const event_strings[] = {
+ [SFP_E_ATTACH] = "attach",
+ [SFP_E_DETACH] = "detach",
+ [SFP_E_INSERT] = "insert",
+ [SFP_E_REMOVE] = "remove",
+ [SFP_E_DEV_DOWN] = "dev_down",
+ [SFP_E_DEV_UP] = "dev_up",
+ [SFP_E_TX_FAULT] = "tx_fault",
+ [SFP_E_TX_CLEAR] = "tx_clear",
+ [SFP_E_LOS_HIGH] = "los_high",
+ [SFP_E_LOS_LOW] = "los_low",
+ [SFP_E_TIMEOUT] = "timeout",
+};
+
+static const char *event_to_str(unsigned short event)
+{
+ if (event >= ARRAY_SIZE(event_strings))
+ return "Unknown event";
+ return event_strings[event];
+}
+
+static const char * const sm_state_strings[] = {
+ [SFP_S_DOWN] = "down",
+ [SFP_S_ERROR] = "error",
+ [SFP_S_WPOWER] = "wpower",
+ [SFP_S_WTXEN] = "wtxen",
+ [SFP_S_INIT] = "init",
+ [SFP_S_WAIT_LOS] = "wait_los",
+ [SFP_S_LINK_UP] = "link_up",
+ [SFP_S_TX_FAULT] = "tx_fault",
+ [SFP_S_REINIT] = "reinit",
+ [SFP_S_TX_DISABLE] = "rx_disable",
+};
+
+static const char *sm_state_to_str(unsigned short sm_state)
+{
+ if (sm_state >= ARRAY_SIZE(sm_state_strings))
+ return "Unknown state";
+ return sm_state_strings[sm_state];
+}
+
+static const char *gpio_of_names[] = {
+ "mod-prs",
+ "intl",
+};
+
+static const enum gpiod_flags gpio_flags[] = {
+ GPIOD_IN,
+ GPIOD_IN,
+};
+
+#define T_INIT_JIFFIES msecs_to_jiffies(300)
+#define T_RESET_US 10
+#define T_FAULT_RECOVER msecs_to_jiffies(1000)
+
+/*
+ * SFF8436 defines the time from power on until the module responds to data
+ * transmission over the serial bus as t_serial, 2 seconds.
+ */
+#define T_SERIAL msecs_to_jiffies(2000)
+#define T_OFF_PDOWN msecs_to_jiffies(300)
+#define T_OFF_TXDIS msecs_to_jiffies(400)
+#define T_PROBE_RETRY msecs_to_jiffies(100)
+
+/* SFP modules appear to always have their PHY configured for bus address
+ * 0x56 (which with mdio-i2c, translates to a PHY address of 22).
+ */
+#define SFP_PHY_ADDR 22
+
+struct sff_data {
+ unsigned int gpios;
+ bool (*module_supported)(const struct sfp_eeprom_id *id);
+};
+
+struct qsfp {
+ struct device *dev;
+ struct i2c_adapter *i2c;
+ struct mii_bus *i2c_mii;
+// struct sfp_bus *sfp_bus;
+ const struct sff_data *type;
+ u32 max_power_mW;
+
+ int (*read)(struct qsfp *, u8, u8, void *, size_t);
+ int (*write)(struct qsfp *, u8, u8, void *, size_t);
+
+ struct gpio_desc *gpio[GPIO_MAX];
+ int gpio_irq[GPIO_MAX];
+
+ bool need_poll;
+
+ struct mutex st_mutex; /* Protects state */
+ unsigned int state;
+ struct delayed_work poll;
+ struct delayed_work timeout;
+ struct mutex sm_mutex; /* Protects state machine */
+ unsigned char sm_mod_state;
+ unsigned char sm_dev_state;
+ unsigned short sm_state;
+ unsigned int sm_retries;
+
+ // Module data
+ struct qsfp_sff8x36_id id;
+ unsigned int module_power_mW;
+ bool module_flat_mem;
+ u8 module_power_class;
+ u8 module_revision;
+ u8 module_irq_flags[19];
+ u8 request_tx_disable;
+ u8 current_tx_disable;
+
+#if IS_ENABLED(CONFIG_HWMON)
+ struct sfp_diag diag;
+ struct device *hwmon_dev;
+ char *hwmon_name;
+ __be16 thresholds[48];
+#endif
+
+};
+
+static const struct of_device_id qsfp_of_match[] = {
+ { .compatible = "sff,qsfp", },
+ { .compatible = "sff,qsfp+", },
+ { .compatible = "sff,qsfp28", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qsfp_of_match);
+
+static unsigned long poll_jiffies;
+
+static int qsfp_i2c_read(struct qsfp *qsfp, u8 page, u8 dev_addr, void *buf,
+ size_t len)
+{
+ struct i2c_adapter *i2c = qsfp->i2c;
+ struct i2c_msg msgs[2];
+ size_t this_len;
+ u8 page_buf[2];
+ int ret;
+
+ page_buf[0] = 0x7f;
+ page_buf[1] = page;
+
+ msgs[0].addr = 0x50;
+ msgs[0].flags = 0;
+ msgs[0].len = sizeof(page_buf);
+ msgs[0].buf = page_buf;
+ msgs[1].addr = 0x50;
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].len = len;
+ msgs[1].buf = buf;
+
+ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
+
+ if (dev_addr >= 128 && !qsfp->module_flat_mem)
+ ret = __i2c_transfer(i2c, msgs, 1);
+ else
+ ret = 1;
+ if (ret == 1) {
+ msgs[0].len = 1;
+ msgs[0].buf = &dev_addr;
+
+ while (len) {
+ this_len = len;
+ if (this_len > 16)
+ this_len = 16;
+
+ msgs[1].len = this_len;
+
+ ret = __i2c_transfer(qsfp->i2c, msgs, ARRAY_SIZE(msgs));
+
+ if (ret != ARRAY_SIZE(msgs))
+ break;
+
+ msgs[1].buf += this_len;
+ dev_addr += this_len;
+ len -= this_len;
+ }
+ }
+ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
+
+ if (ret < 0)
+ return ret;
+
+ return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
+}
+
+static int qsfp_i2c_write(struct qsfp *qsfp, u8 page, u8 dev_addr, void *buf,
+ size_t len)
+{
+ struct i2c_adapter *i2c = qsfp->i2c;
+ struct i2c_msg msgs[1];
+ u8 page_buf[2];
+ u8 *p;
+ int ret;
+
+ p = kmalloc(1 + len, GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ p[0] = dev_addr;
+ memcpy(&p[1], buf, len);
+
+ page_buf[0] = 0x7f;
+ page_buf[1] = page;
+
+ msgs[0].addr = 0x50;
+ msgs[0].flags = 0;
+ msgs[0].len = sizeof(page_buf);
+ msgs[0].buf = page_buf;
+
+ i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
+ if (dev_addr >= 128 && !qsfp->module_flat_mem)
+ ret = __i2c_transfer(i2c, msgs, 1);
+ else
+ ret = 1;
+ if (ret == 1) {
+ msgs[0].addr = 0x50;
+ msgs[0].flags = 0;
+ msgs[0].len = 1 + len;
+ msgs[0].buf = p;
+
+ ret = __i2c_transfer(qsfp->i2c, msgs, ARRAY_SIZE(msgs));
+ }
+ i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
+
+ kfree(p);
+
+ if (ret < 0)
+ return ret;
+
+ return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
+}
+
+static int qsfp_i2c_configure(struct qsfp *qsfp, struct i2c_adapter *i2c)
+{
+ struct mii_bus *i2c_mii;
+ int ret;
+
+ if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+ return -EINVAL;
+
+ qsfp->i2c = i2c;
+ qsfp->read = qsfp_i2c_read;
+ qsfp->write = qsfp_i2c_write;
+
+ i2c_mii = mdio_i2c_alloc(qsfp->dev, i2c);
+ if (IS_ERR(i2c_mii))
+ return PTR_ERR(i2c_mii);
+
+ i2c_mii->name = "QSFP I2C Bus";
+ i2c_mii->phy_mask = ~0;
+
+ ret = mdiobus_register(i2c_mii);
+ if (ret < 0) {
+ mdiobus_free(i2c_mii);
+ return ret;
+ }
+
+ qsfp->i2c_mii = i2c_mii;
+
+ return 0;
+}
+
+/* Interface */
+static int qsfp_read(struct qsfp *qsfp, u16 addr, void *buf, size_t len)
+{
+ return qsfp->read(qsfp, addr >> 8, addr, buf, len);
+}
+
+static int qsfp_write(struct qsfp *qsfp, u16 addr, void *buf, size_t len)
+{
+ return qsfp->write(qsfp, addr >> 8, addr, buf, len);
+}
+
+static int qsfp_modb(struct qsfp *qsfp, u16 addr, u8 mask, u8 set)
+{
+ int ret;
+ u8 val;
+
+ ret = qsfp_read(qsfp, addr, &val, sizeof(val));
+ if (ret < 0)
+ return ret;
+
+ val &= ~mask;
+ val |= set & mask;
+
+ return qsfp_write(qsfp, addr, &val, sizeof(val));
+}
+
+static unsigned int sfp_check(void *buf, size_t len)
+{
+ u8 *p, check;
+
+ for (p = buf, check = 0; len; p++, len--)
+ check += *p;
+
+ return check;
+}
+
+#if IS_ENABLED(CONFIG_HWMON)
+static umode_t qsfp_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct qsfp *qsfp = data;
+
+ switch (type) {
+ case hwmon_temp:
+ /* Temperature monitoring is optional, but this bit only
+ * exists in SFF8636 revision 2.8+ */
+ if (qsfp->module_revision >= SFF8X36_REV_8636_2_8 &&
+ !(qsfp->id.ext.sff8636.diagmon & SFF8636_DIAGMON_TEMP))
+ break;
+ switch (attr) {
+ case hwmon_temp_input:
+ return 0444;
+ case hwmon_temp_lcrit:
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ return !qsfp->module_flat_mem ? 0444 : 0;
+ }
+ break;
+ case hwmon_in:
+ /* Supply monitoring is optional, but this bit only
+ * exists in SFF8636 revision 2.8+ */
+ if (qsfp->module_revision >= SFF8X36_REV_8636_2_8 &&
+ !(qsfp->id.ext.sff8636.diagmon & SFF8636_DIAGMON_VCC))
+ break;
+ switch (attr) {
+ case hwmon_in_input:
+ return 0444;
+ case hwmon_in_lcrit:
+ case hwmon_in_min:
+ case hwmon_in_max:
+ case hwmon_in_crit:
+ return !qsfp->module_flat_mem ? 0444 : 0;
+ }
+ break;
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_input:
+ case hwmon_curr_label:
+ return 0444;
+ case hwmon_curr_lcrit:
+ case hwmon_curr_min:
+ case hwmon_curr_max:
+ case hwmon_curr_crit:
+ return !qsfp->module_flat_mem ? 0444 : 0;
+ }
+ break;
+ case hwmon_power:
+ /* TX power monitoring is optional, but this bit only
+ * exists in SFF8636 revision 1.9+ - we only know 2.0+ */
+ if (channel >= 4 &&
+ qsfp->module_revision >= SFF8X36_REV_8636_2_0 &&
+ !(qsfp->id.ext.sff8636.diagmon & SFF8636_DIAGMON_TXPWR))
+ break;
+ switch (attr) {
+ case hwmon_power_input:
+ case hwmon_power_label:
+ return 0444;
+ case hwmon_power_lcrit:
+ case hwmon_power_min:
+ case hwmon_power_max:
+ case hwmon_power_crit:
+ return !qsfp->module_flat_mem ? 0444 : 0;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int qsfp_hwmon_temp(struct qsfp *qsfp, u32 attr, long *value)
+{
+ __be16 temp;
+ int ret;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ ret = qsfp_read(qsfp, SFF8X36_TEMPERATURE, &temp, sizeof(temp));
+ if (ret < 0)
+ return ret;
+ break;
+ case hwmon_temp_lcrit:
+ temp = qsfp->thresholds[1];
+ break;
+ case hwmon_temp_min:
+ temp = qsfp->thresholds[3];
+ break;
+ case hwmon_temp_max:
+ temp = qsfp->thresholds[2];
+ break;
+ case hwmon_temp_crit:
+ temp = qsfp->thresholds[0];
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ *value = DIV_ROUND_CLOSEST(((s16)be16_to_cpu(temp)) * 1000, 256);
+
+ return 0;
+}
+
+static int qsfp_hwmon_vcc(struct qsfp *qsfp, u32 attr, long *value)
+{
+ __be16 volt;
+ int ret;
+
+ switch (attr) {
+ case hwmon_in_input:
+ ret = qsfp_read(qsfp, SFF8X36_SUPPLY_VOLTAGE,
+ &volt, sizeof(volt));
+ if (ret < 0)
+ return ret < 0;
+ break;
+ case hwmon_in_lcrit:
+ volt = qsfp->thresholds[9];
+ break;
+ case hwmon_in_min:
+ volt = qsfp->thresholds[11];
+ break;
+ case hwmon_in_max:
+ volt = qsfp->thresholds[10];
+ break;
+ case hwmon_in_crit:
+ volt = qsfp->thresholds[8];
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ *value = DIV_ROUND_CLOSEST(be16_to_cpu(volt), 10);
+
+ return 0;
+}
+
+static int qsfp_hwmon_bias(struct qsfp *qsfp, u32 attr, int chan, long *value)
+{
+ __be16 bias;
+ u8 addr;
+ int ret;
+
+ switch (attr) {
+ case hwmon_curr_input:
+ addr = SFF8X36_TX_BIAS + 2 * chan;
+
+ ret = qsfp_read(qsfp, addr, &bias, sizeof(bias));
+ if (ret < 0)
+ return ret < 0;
+ break;
+ case hwmon_curr_lcrit:
+ bias = qsfp->thresholds[29];
+ break;
+ case hwmon_curr_min:
+ bias = qsfp->thresholds[31];
+ break;
+ case hwmon_curr_max:
+ bias = qsfp->thresholds[30];
+ break;
+ case hwmon_curr_crit:
+ bias = qsfp->thresholds[28];
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ *value = DIV_ROUND_CLOSEST(be16_to_cpu(bias), 10);
+
+ return 0;
+}
+
+static int qsfp_hwmon_power(struct qsfp *qsfp, u32 attr, int chan, long *value)
+{
+ __be16 pwr;
+ u8 addr;
+ int ret;
+
+ switch (attr) {
+ case hwmon_power_input:
+ addr = 2 * (chan & 3) +
+ (chan & 4 ? SFF8X36_TX_POWER : SFF8X36_RX_POWER);
+
+ ret = qsfp_read(qsfp, addr, &pwr, sizeof(pwr));
+ if (ret < 0)
+ return ret;
+ break;
+ case hwmon_power_lcrit:
+ pwr = qsfp->thresholds[chan & 4 ? 33 : 25];
+ break;
+ case hwmon_power_min:
+ pwr = qsfp->thresholds[chan & 4 ? 35 : 27];
+ break;
+ case hwmon_power_max:
+ pwr = qsfp->thresholds[chan & 4 ? 34 : 26];
+ break;
+ case hwmon_power_crit:
+ pwr = qsfp->thresholds[chan & 4 ? 32 : 24];
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ *value = DIV_ROUND_CLOSEST(be16_to_cpu(pwr), 10);
+
+ return 0;
+}
+
+static int qsfp_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *value)
+{
+ struct qsfp *qsfp = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ return qsfp_hwmon_temp(qsfp, attr, value);
+ case hwmon_in:
+ return qsfp_hwmon_vcc(qsfp, attr, value);
+ case hwmon_curr:
+ return qsfp_hwmon_bias(qsfp, attr, channel, value);
+ case hwmon_power:
+ return qsfp_hwmon_power(qsfp, attr, channel, value);
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+static const char *qsfp_hwmon_power_labels[] = {
+ "power_rx_1",
+ "power_rx_2",
+ "power_rx_3",
+ "power_rx_4",
+ "power_tx_1",
+ "power_tx_2",
+ "power_tx_3",
+ "power_tx_4",
+};
+
+static const char *qsfp_hwmon_curr_labels[] = {
+ "bias_tx_1",
+ "bias_tx_2",
+ "bias_tx_3",
+ "bias_tx_4",
+};
+
+static int qsfp_hwmon_read_str(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ switch (type) {
+ case hwmon_curr:
+ if (attr != hwmon_curr_label || channel < 0 ||
+ channel > ARRAY_SIZE(qsfp_hwmon_curr_labels))
+ break;
+ *str = qsfp_hwmon_curr_labels[channel];
+ return 0;
+ case hwmon_power:
+ if (attr != hwmon_power_label || channel < 0 ||
+ channel > ARRAY_SIZE(qsfp_hwmon_power_labels))
+ break;
+ *str = qsfp_hwmon_power_labels[channel];
+ return 0;
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_ops qsfp_hwmon_ops = {
+ .is_visible = qsfp_hwmon_is_visible,
+ .read = qsfp_hwmon_read,
+ .read_string = qsfp_hwmon_read_str,
+};
+
+static const u32 qsfp_hwmon_chip_config[] = {
+ HWMON_C_REGISTER_TZ,
+ 0,
+};
+
+static const struct hwmon_channel_info qsfp_hwmon_chip = {
+ .type = hwmon_chip,
+ .config = qsfp_hwmon_chip_config,
+};
+
+static const u32 qsfp_hwmon_temp_config[] = {
+ HWMON_T_INPUT |
+ HWMON_T_LCRIT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_CRIT,
+ 0,
+};
+
+static const struct hwmon_channel_info qsfp_hwmon_temp_channel_info = {
+ .type = hwmon_temp,
+ .config = qsfp_hwmon_temp_config,
+};
+
+static const u32 qsfp_hwmon_vcc_config[] = {
+ HWMON_I_INPUT |
+ HWMON_I_LCRIT | HWMON_I_MIN | HWMON_I_MAX | HWMON_I_CRIT,
+ 0,
+};
+
+static const struct hwmon_channel_info qsfp_hwmon_vcc_channel_info = {
+ .type = hwmon_in,
+ .config = qsfp_hwmon_vcc_config,
+};
+
+static u32 qsfp_hwmon_bias_config[] = {
+ HWMON_C_INPUT | HWMON_C_LABEL |
+ HWMON_C_LCRIT | HWMON_C_MIN | HWMON_C_MAX | HWMON_C_CRIT,
+ HWMON_C_INPUT | HWMON_C_LABEL |
+ HWMON_C_LCRIT | HWMON_C_MIN | HWMON_C_MAX | HWMON_C_CRIT,
+ HWMON_C_INPUT | HWMON_C_LABEL |
+ HWMON_C_LCRIT | HWMON_C_MIN | HWMON_C_MAX | HWMON_C_CRIT,
+ HWMON_C_INPUT | HWMON_C_LABEL |
+ HWMON_C_LCRIT | HWMON_C_MIN | HWMON_C_MAX | HWMON_C_CRIT,
+ 0,
+};
+
+static const struct hwmon_channel_info qsfp_hwmon_bias_channel_info = {
+ .type = hwmon_curr,
+ .config = qsfp_hwmon_bias_config,
+};
+
+static const u32 qsfp_hwmon_power_config[] = {
+ /* Receive power */
+ HWMON_P_INPUT | HWMON_P_LABEL |
+ HWMON_P_LCRIT | HWMON_P_MIN | HWMON_P_MAX | HWMON_P_CRIT,
+ HWMON_P_INPUT | HWMON_P_LABEL |
+ HWMON_P_LCRIT | HWMON_P_MIN | HWMON_P_MAX | HWMON_P_CRIT,
+ HWMON_P_INPUT | HWMON_P_LABEL |
+ HWMON_P_LCRIT | HWMON_P_MIN | HWMON_P_MAX | HWMON_P_CRIT,
+ HWMON_P_INPUT | HWMON_P_LABEL |
+ HWMON_P_LCRIT | HWMON_P_MIN | HWMON_P_MAX | HWMON_P_CRIT,
+ /* Transmit power */
+ HWMON_P_INPUT | HWMON_P_LABEL |
+ HWMON_P_LCRIT | HWMON_P_MIN | HWMON_P_MAX | HWMON_P_CRIT,
+ HWMON_P_INPUT | HWMON_P_LABEL |
+ HWMON_P_LCRIT | HWMON_P_MIN | HWMON_P_MAX | HWMON_P_CRIT,
+ HWMON_P_INPUT | HWMON_P_LABEL |
+ HWMON_P_LCRIT | HWMON_P_MIN | HWMON_P_MAX | HWMON_P_CRIT,
+ HWMON_P_INPUT | HWMON_P_LABEL |
+ HWMON_P_LCRIT | HWMON_P_MIN | HWMON_P_MAX | HWMON_P_CRIT,
+ 0,
+};
+
+static const struct hwmon_channel_info qsfp_hwmon_power_channel_info = {
+ .type = hwmon_power,
+ .config = qsfp_hwmon_power_config,
+};
+
+static const struct hwmon_channel_info *qsfp_hwmon_info[] = {
+ &qsfp_hwmon_chip,
+ &qsfp_hwmon_vcc_channel_info,
+ &qsfp_hwmon_temp_channel_info,
+ &qsfp_hwmon_bias_channel_info,
+ &qsfp_hwmon_power_channel_info,
+ NULL,
+};
+
+static const struct hwmon_chip_info qsfp_hwmon_chip_info = {
+ .ops = &qsfp_hwmon_ops,
+ .info = qsfp_hwmon_info,
+};
+
+static int qsfp_hwmon_insert(struct qsfp *qsfp)
+{
+ int i, ret;
+
+ if (!qsfp->module_flat_mem) {
+ ret = qsfp_read(qsfp, SFF8X36_THRESHOLDS, qsfp->thresholds,
+ sizeof(qsfp->thresholds));
+ if (ret < 0)
+ return ret < 0;
+ }
+
+ qsfp->hwmon_name = kstrdup(dev_name(qsfp->dev), GFP_KERNEL);
+ if (!qsfp->hwmon_name)
+ return -ENOMEM;
+
+ for (i = 0; qsfp->hwmon_name[i]; i++)
+ if (hwmon_is_bad_char(qsfp->hwmon_name[i]))
+ qsfp->hwmon_name[i] = '_';
+
+ qsfp->hwmon_dev = hwmon_device_register_with_info(qsfp->dev,
+ qsfp->hwmon_name,
+ qsfp,
+ &qsfp_hwmon_chip_info,
+ NULL);
+
+ return PTR_ERR_OR_ZERO(qsfp->hwmon_dev);
+}
+
+static void qsfp_hwmon_remove(struct qsfp *qsfp)
+{
+ if (!IS_ERR_OR_NULL(qsfp->hwmon_dev)) {
+ hwmon_device_unregister(qsfp->hwmon_dev);
+ qsfp->hwmon_dev = NULL;
+ kfree(qsfp->hwmon_name);
+ }
+}
+#else
+static int qsfp_hwmon_insert(struct qsfp *qsfp)
+{
+ return 0;
+}
+
+static void qsfp_hwmon_remove(struct qsfp *qsfp)
+{
+}
+#endif
+
+static const char *sff8436_encoding(unsigned int encoding)
+{
+ switch (encoding) {
+ case SFF8024_ENCODING_UNSPEC:
+ return "unspecified";
+ case SFF8024_ENCODING_8436_64B66B:
+ return "64b66b";
+ case SFF8024_ENCODING_8B10B:
+ return "8b10b";
+ case SFF8024_ENCODING_4B5B:
+ return "4b5b";
+ case SFF8024_ENCODING_NRZ:
+ return "NRZ";
+ case SFF8024_ENCODING_8436_MANCHESTER:
+ return "MANCHESTER";
+ default:
+ return "unknown";
+ }
+}
+
+/* SFP state machine */
+static void qsfp_sm_set_timer(struct qsfp *sfp, unsigned int timeout)
+{
+ if (timeout)
+ mod_delayed_work(system_power_efficient_wq, &sfp->timeout,
+ timeout);
+ else
+ cancel_delayed_work(&sfp->timeout);
+}
+
+static void qsfp_sm_next(struct qsfp *sfp, unsigned int state,
+ unsigned int timeout)
+{
+ sfp->sm_state = state;
+ qsfp_sm_set_timer(sfp, timeout);
+}
+
+static void qsfp_sm_ins_next(struct qsfp *sfp, unsigned int state,
+ unsigned int timeout)
+{
+ sfp->sm_mod_state = state;
+ qsfp_sm_set_timer(sfp, timeout);
+}
+
+static void sfp_sm_link_up(struct qsfp *sfp)
+{
+// sfp_link_up(sfp->sfp_bus);
+ qsfp_sm_next(sfp, SFP_S_LINK_UP, 0);
+}
+
+static void sfp_sm_link_down(struct qsfp *sfp)
+{
+// sfp_link_down(sfp->sfp_bus);
+}
+
+static void sfp_sm_link_check_los(struct qsfp *sfp)
+{
+ sfp_sm_link_up(sfp);
+}
+
+static bool sfp_los_event_active(struct qsfp *sfp, unsigned int event)
+{
+ return 0;
+}
+
+static bool sfp_los_event_inactive(struct qsfp *sfp, unsigned int event)
+{
+ return 0;
+}
+
+static int qsfp_set_tx_disable(struct qsfp *qsfp, unsigned int val)
+{
+ return qsfp_modb(qsfp, SFF8X36_TX_DISABLE,
+ SFF8X36_TX_DISABLE_TX4 | SFF8X36_TX_DISABLE_TX3 |
+ SFF8X36_TX_DISABLE_TX2 | SFF8X36_TX_DISABLE_TX1, val);
+}
+
+static int qsfp_set_power(struct qsfp *qsfp, unsigned int val)
+{
+ return qsfp_modb(qsfp, SFF8X36_CTRL_93,
+ SFF8X36_CTRL_93_POWER_ORIDE |
+ SFF8X36_CTRL_93_POWER_SET |
+ SFF8636_CTRL_93_POWER_CLS5_7 |
+ SFF8636_CTRL_93_POWER_CLS8, val);
+}
+
+static void sfp_sm_fault(struct qsfp *sfp, bool warn)
+{
+ if (sfp->sm_retries && !--sfp->sm_retries) {
+ dev_err(sfp->dev,
+ "module persistently indicates fault, disabling\n");
+ qsfp_sm_next(sfp, SFP_S_TX_DISABLE, 0);
+ } else {
+ if (warn)
+ dev_err(sfp->dev, "module transmit fault indicated\n");
+
+ qsfp_sm_next(sfp, SFP_S_TX_FAULT, T_FAULT_RECOVER);
+ }
+}
+
+/* Device/upstream state machine - tracks whether we are attached to an
+ * upstream, and whether the upstream is ready to pass data (i.o.w. up).
+ */
+static void qsfp_sm_device(struct qsfp *qsfp, unsigned int event)
+{
+ if (event == SFP_E_DETACH)
+ qsfp->sm_dev_state = SFP_DEV_DETACHED;
+
+ switch (qsfp->sm_dev_state) {
+ default:
+ if (event == SFP_E_ATTACH)
+ qsfp->sm_dev_state = SFP_DEV_DOWN;
+ break;
+
+ case SFP_DEV_DOWN:
+ if (event == SFP_E_DEV_UP)
+ qsfp->sm_dev_state = SFP_DEV_UP;
+ break;
+
+ case SFP_DEV_UP:
+ if (event == SFP_E_DEV_DOWN)
+ qsfp->sm_dev_state = SFP_DEV_DOWN;
+ break;
+ }
+}
+
+static int qsfp_parse_power(struct qsfp *qsfp)
+{
+ unsigned int power_mW, power_class;
+ u8 pwr, mask;
+ int ret;
+
+ if (qsfp->module_revision >= SFF8X36_REV_8636_2_8 &&
+ qsfp->id.base.ext_identifier & BIT(5)) {
+ ret = qsfp_read(qsfp, SFF8X36_CLS8_MAX_POWER, &pwr,
+ sizeof(pwr));
+ if (ret < 0)
+ return ret;
+
+ power_class = 8;
+ power_mW = pwr * 100;
+ } else {
+ if (qsfp->id.base.identifier == SFF8024_ID_QSFP_8438)
+ mask = 0xc0;
+ else
+ mask = 0xc3;
+
+ switch (qsfp->id.base.ext_identifier & mask) {
+ default:
+ power_mW = 1500;
+ power_class = 1;
+ break;
+ case 0x40:
+ power_mW = 2000;
+ power_class = 2;
+ break;
+ case 0x80:
+ power_mW = 2500;
+ power_class = 3;
+ break;
+ case 0xc0:
+ power_mW = 3500;
+ power_class = 4;
+ break;
+ case 0xc1:
+ power_mW = 4000;
+ power_class = 5;
+ break;
+ case 0xc2:
+ power_mW = 4500;
+ power_class = 6;
+ break;
+ case 0xc3:
+ power_mW = 5000;
+ power_class = 7;
+ break;
+ }
+ }
+
+ if (power_mW > qsfp->max_power_mW) {
+ if (power_mW > 3500u) {
+ dev_warn(qsfp->dev,
+ "Host does not support %u.%uW modules, limited to 3.5W\n",
+ power_mW / 1000, (power_mW / 100) % 10);
+ return 0;
+ } else {
+ dev_err(qsfp->dev,
+ "Host does not support %u.%uW modules\n",
+ power_mW / 1000, (power_mW / 100) % 10);
+ return -EINVAL;
+ }
+ }
+
+ qsfp->module_power_mW = power_mW;
+ qsfp->module_power_class = power_class;
+
+ dev_info(qsfp->dev, "module power %u.%uW\n",
+ qsfp->module_power_mW / 1000,
+ (qsfp->module_power_mW / 100) % 10);
+
+ return 0;
+}
+
+static void qsfp_mod_clear(struct qsfp *qsfp)
+{
+ qsfp->module_power_class = 0;
+ qsfp->module_power_mW = 0;
+ qsfp->module_flat_mem = false;
+ qsfp->module_revision = 0;
+ memset(&qsfp->id, 0, sizeof(qsfp->id));
+ memset(&qsfp->module_irq_flags, 0, sizeof(qsfp->module_irq_flags));
+}
+
+static void qsfp_sm_mod_print(struct qsfp *qsfp)
+{
+ const struct qsfp_sff8x36_id *id = &qsfp->id;
+ unsigned int br_nom, wavelen;
+ bool copper;
+ char length[16];
+ char date[9];
+
+ date[0] = id->ext.sff8436.datecode[4];
+ date[1] = id->ext.sff8436.datecode[5];
+ date[2] = '-';
+ date[3] = id->ext.sff8436.datecode[2];
+ date[4] = id->ext.sff8436.datecode[3];
+ date[5] = '-';
+ date[6] = id->ext.sff8436.datecode[0];
+ date[7] = id->ext.sff8436.datecode[1];
+ date[8] = '\0';
+
+ if (id->base.br_nominal == 0) {
+ br_nom = 0;
+ } else if (id->base.br_nominal == 255 &&
+ qsfp->module_revision >= SFF8X36_REV_8636_1_3) {
+ br_nom = 250 * id->ext.sff8636.baud_rate_nominal;
+ } else {
+ br_nom = 100 * id->base.br_nominal;
+ }
+
+ dev_info(qsfp->dev, "module %.*s %.*s rev %.*s sn %.*s dc %s\n",
+ (int)sizeof(id->base.vendor_name), id->base.vendor_name,
+ (int)sizeof(id->base.vendor_pn), id->base.vendor_pn,
+ (int)sizeof(id->base.vendor_rev), id->base.vendor_rev,
+ (int)sizeof(id->ext.sff8436.vendor_sn), id->ext.sff8436.vendor_sn,
+ date);
+
+ dev_info(qsfp->dev, " 0x%02x %s, 0x%02x %s\n",
+ id->base.identifier,
+ id->base.identifier == 0x0c ? "QSFP (INF-8438)" :
+ id->base.identifier == 0x0d ? "QSFP+ (SFF-8636 or SFF-8436)" :
+ id->base.identifier == 0x11 ? "QSFP28 (SFF-8636)" : "unknown",
+ qsfp->module_revision,
+ qsfp->module_revision == 0x00 ? "unspecified" :
+ qsfp->module_revision == 0x01 ? "SFF-8436 4.8 or earlier" :
+ qsfp->module_revision == 0x02 ? "SFF-8436 4.8 or earlier+" :
+ qsfp->module_revision == 0x03 ? "SFF-8636 1.3 or earlier" :
+ qsfp->module_revision == 0x04 ? "SFF-8636 1.4" :
+ qsfp->module_revision == 0x05 ? "SFF-8636 1.5" :
+ qsfp->module_revision == 0x06 ? "SFF-8636 2.0" :
+ qsfp->module_revision == 0x07 ? "SFF-8636 2.5..2.7" :
+ qsfp->module_revision == 0x08 ? "SFF-8636 2.8..2.10" : "unknown");
+
+ dev_info(qsfp->dev, " %s connector, encoding %s, bitrate %u.%03u GBd\n",
+ sff_connector(id->base.connector),
+ sff8436_encoding(id->base.encoding),
+ br_nom / 1000, br_nom % 1000);
+
+ dev_info(qsfp->dev, " Compliance bytes: %*ph %02x\n",
+ (int)sizeof(id->base.compliance), id->base.compliance,
+ id->ext.sff8636.link_codes);
+
+ copper = id->base.compliance[0] & BIT(0) ||
+ id->base.compliance[3] & (BIT(2) | BIT(3));
+
+ if (copper) {
+ dev_info(qsfp->dev, " Copper Attenuation @ 2.5 GHz: %udB\n",
+ id->base.copper_atten[0]);
+ dev_info(qsfp->dev, " Copper Attenuation @ 5 GHz : %udB\n",
+ id->base.copper_atten[1]);
+ dev_info(qsfp->dev, " Copper length : %s\n",
+ sff_link_len(length, sizeof(length),
+ id->base.length[4], 1));
+ } else {
+ wavelen = be16_to_cpup(&id->base.wavelength) * 5;
+ dev_info(qsfp->dev, " Wavelength %u.%02unm, fiber lengths:\n",
+ wavelen / 100, wavelen % 100);
+ dev_info(qsfp->dev, " 9µm SM : %s\n",
+ sff_link_len(length, sizeof(length),
+ id->base.length[0], 1));
+ dev_info(qsfp->dev, " 62.5µm MM OM1: %s\n",
+ sff_link_len(length, sizeof(length),
+ id->base.length[3], 1));
+ dev_info(qsfp->dev, " 50µm MM OM2: %s\n",
+ sff_link_len(length, sizeof(length),
+ id->base.length[2], 1));
+ dev_info(qsfp->dev, " 50µm MM OM3: %s\n",
+ sff_link_len(length, sizeof(length),
+ id->base.length[1], 2));
+ dev_info(qsfp->dev, " 50µm MM OM4: %s\n",
+ sff_link_len(length, sizeof(length),
+ id->base.length[4], 2));
+ }
+}
+
+static int qsfp_sm_mod_probe(struct qsfp *sfp)
+{
+ struct qsfp_sff8x36_id_stat id_stat;
+ struct qsfp_sff8x36_id id;
+ //char options[80];
+ u8 check;
+ int ret;
+
+ ret = qsfp_read(sfp, 0, &id_stat, sizeof(id_stat));
+ if (ret < 0) {
+ dev_err(sfp->dev, "failed to read EEPROM: %d\n", ret);
+ return -EAGAIN;
+ }
+
+ // FIXME: check id_stat.identifier values
+
+ // Wait for the module to report ready
+ if (id_stat.status & SFF8X36_STAT_DATA_NOT_READY)
+ return -EAGAIN;
+
+ // Early setup - we need to know if this module has a page register
+ sfp->module_flat_mem = id_stat.status & SFF8X36_STAT_FLAT_MEM;
+ sfp->module_revision = id_stat.rev_compliance;
+
+ ret = qsfp_read(sfp, SFF8X36_ID, &id, sizeof(id));
+ if (ret < 0) {
+ qsfp_mod_clear(sfp);
+ dev_err(sfp->dev, "failed to read EEPROM: %d\n", ret);
+ return -EAGAIN;
+ }
+
+ if (id.base.identifier != id_stat.identifier) {
+ qsfp_mod_clear(sfp);
+ dev_err(sfp->dev, "QSFP identifier mismatch: 0x%02x != 0x%02x\n",
+ id_stat.identifier, id.base.identifier);
+ return -EINVAL;
+ }
+
+ // Validate the checksum over the base structure
+ check = sfp_check(&id.base, sizeof(id.base) - 1);
+ if (check != id.base.cc_base) {
+ qsfp_mod_clear(sfp);
+ dev_err(sfp->dev,
+ "EEPROM base structure checksum failure: 0x%02x != 0x%02x\n",
+ check, id.base.cc_base);
+ print_hex_dump(KERN_ERR, "sfp EE: ", DUMP_PREFIX_OFFSET,
+ 16, 1, &id, sizeof(id), true);
+ return -EINVAL;
+ }
+
+ // Validate the checksum over the extended structure
+ check = sfp_check(&id.ext.sff8436, sizeof(id.ext.sff8436) - 1);
+ if (check != id.ext.sff8436.cc_ext) {
+ dev_err(sfp->dev,
+ "EEPROM extended structure checksum failure: 0x%02x != 0x%02x\n",
+ check, id.ext.sff8436.cc_ext);
+ print_hex_dump(KERN_ERR, "sfp EE: ", DUMP_PREFIX_OFFSET,
+ 16, 1, &id, sizeof(id), true);
+ memset(&id.ext, 0, sizeof(id.ext));
+ }
+
+ sfp->id = id;
+
+ if (id.ext.sff8436.options[3] & SFF8X36_OPTIONS195_TX_DISABLE) {
+ // Active tx disable
+ sfp->request_tx_disable = SFF8X36_TX_DISABLE_TX4 |
+ SFF8X36_TX_DISABLE_TX3 |
+ SFF8X36_TX_DISABLE_TX2 |
+ SFF8X36_TX_DISABLE_TX1;
+ sfp->current_tx_disable = sfp->request_tx_disable;
+ ret = qsfp_set_tx_disable(sfp, sfp->current_tx_disable);
+ if (ret < 0) {
+ qsfp_mod_clear(sfp);
+ return ret;
+ }
+ }
+
+ // Parse the module power requirements
+ ret = qsfp_parse_power(sfp);
+ if (ret) {
+ qsfp_mod_clear(sfp);
+ return ret;
+ }
+
+ qsfp_sm_mod_print(sfp);
+
+ /* Check whether we support this module */
+// if (!sfp->type->module_supported(&id)) {
+// dev_err(sfp->dev,
+// "module is not supported - phys id 0x%02x 0x%02x\n",
+// id.base.phys_id, id.base.phys_ext_id);
+// return -EINVAL;
+// }
+
+ return qsfp_hwmon_insert(sfp);
+}
+
+static void qsfp_sm_mod_present(struct qsfp *qsfp)
+{
+// int ret;
+
+ if (qsfp->sm_dev_state == SFP_DEV_DETACHED) {
+ qsfp_sm_ins_next(qsfp, SFP_MOD_WATTACH, 0);
+ return;
+ }
+
+ // Start the poller if there is no interrupt support if not running
+ if (!qsfp->gpio_irq[GPIO_INTL])
+ queue_delayed_work(system_wq, &qsfp->poll, poll_jiffies);
+
+// ret = sfp_module_insert(qsfp->sfp_bus, &id);
+// if (ret < 0)
+// qsfp_sm_ins_next(qsfp, SFP_MOD_ERROR, 0);
+// else
+ qsfp_sm_ins_next(qsfp, SFP_MOD_PRESENT, 0);
+}
+
+static void qsfp_sm_mod_remove(struct qsfp *qsfp)
+{
+// if (qsfp->sm_mod_state >= SFP_MOD_PRESENT)
+// sfp_module_remove(qsfp->sfp_bus);
+
+ qsfp_hwmon_remove(qsfp);
+ qsfp_mod_clear(qsfp);
+
+ dev_info(qsfp->dev, "module removed\n");
+}
+
+/* Module state machine - this tracks the insertion state of the module,
+ * probes the EEPROM to identify the type of module and the power level.
+ *
+ * We handle remove events outside of the main switch() as what
+ * is required is fairly universal between the various states.
+ */
+static void qsfp_sm_module(struct qsfp *qsfp, unsigned int event)
+{
+ int ret;
+
+ if (event == SFP_E_REMOVE && qsfp->sm_mod_state != SFP_MOD_EMPTY) {
+ if (qsfp->sm_mod_state >= SFP_MOD_WATTACH)
+ qsfp_sm_mod_remove(qsfp);
+ qsfp_sm_ins_next(qsfp, SFP_MOD_EMPTY, 0);
+ }
+
+ switch (qsfp->sm_mod_state) {
+ default:
+ if (event == SFP_E_INSERT) {
+ qsfp_sm_ins_next(qsfp, SFP_MOD_PROBE, T_SERIAL);
+ qsfp->sm_retries = 10;
+ }
+ break;
+
+ case SFP_MOD_PROBE:
+ if (event == SFP_E_TIMEOUT) {
+ ret = qsfp_sm_mod_probe(qsfp);
+ if (ret == 0)
+ qsfp_sm_mod_present(qsfp);
+ else if (ret != -EAGAIN)
+ qsfp_sm_ins_next(qsfp, SFP_MOD_ERROR, 0);
+ else if (--qsfp->sm_retries)
+ qsfp_sm_set_timer(qsfp, T_PROBE_RETRY);
+ else
+ qsfp_sm_mod_present(qsfp);
+ }
+ break;
+
+ case SFP_MOD_WATTACH:
+ if (event == SFP_E_ATTACH)
+ qsfp_sm_mod_present(qsfp);
+ break;
+
+ case SFP_MOD_PRESENT:
+ if (event == SFP_E_DETACH)
+ qsfp_sm_ins_next(qsfp, SFP_MOD_WATTACH, 0);
+ break;
+ }
+}
+
+static int qsfp_sm_power_up(struct qsfp *qsfp)
+{
+ int ret;
+ u8 val;
+
+ val = SFF8X36_CTRL_93_POWER_ORIDE;
+ if (qsfp->module_power_class >= 8)
+ val |= SFF8636_CTRL_93_POWER_CLS8;
+ if (qsfp->module_power_mW >= 1500u)
+ val |= SFF8636_CTRL_93_POWER_CLS5_7;
+
+ ret = qsfp_set_power(qsfp, val);
+ if (ret < 0)
+ qsfp_sm_next(qsfp, SFP_S_ERROR, 0);
+ else
+ qsfp_sm_next(qsfp, SFP_S_WPOWER, T_OFF_PDOWN);
+
+ return 1;
+}
+
+static void qsfp_sm_power_down(struct qsfp *qsfp)
+{
+ qsfp_set_power(qsfp, SFF8X36_CTRL_93_POWER_ORIDE |
+ SFF8X36_CTRL_93_POWER_SET);
+}
+
+static int sfp_sm_tx_enable(struct qsfp *qsfp)
+{
+ if (qsfp->id.ext.sff8436.options[3] & SFF8X36_OPTIONS195_TX_DISABLE) {
+ if (qsfp_set_tx_disable(qsfp, 0) < 0)
+ qsfp_sm_next(qsfp, SFP_S_ERROR, 0);
+ else
+ qsfp_sm_next(qsfp, SFP_S_WTXEN, T_OFF_TXDIS);
+ return 1;
+ }
+ return 0;
+}
+
+static void sfp_sm_init(struct qsfp *qsfp)
+{
+ qsfp_sm_next(qsfp, SFP_S_INIT, T_OFF_PDOWN);
+}
+
+static void qsfp_sm_main(struct qsfp *qsfp, unsigned int event)
+{
+ if (qsfp->sm_mod_state != SFP_MOD_PRESENT ||
+ qsfp->sm_dev_state != SFP_DEV_UP) {
+ if (qsfp->sm_state != SFP_S_DOWN) {
+ /* If we reported link up, and the upstream is
+ * still up, report link down.
+ */
+ if (qsfp->sm_state == SFP_S_LINK_UP &&
+ qsfp->sm_dev_state == SFP_DEV_UP)
+ sfp_sm_link_down(qsfp);
+ /* If the module is still present, and we've
+ * powered it up, power it back down.
+ */
+ if (qsfp->sm_mod_state == SFP_MOD_PRESENT &&
+ qsfp->sm_state >= SFP_S_WPOWER)
+ qsfp_sm_power_down(qsfp);
+ qsfp_sm_next(qsfp, SFP_S_DOWN, 0);
+ }
+ return;
+ }
+
+ /* The main state machine - only entered when the upstream is up
+ * and the module is present.
+ */
+ switch (qsfp->sm_state) {
+ case SFP_S_ERROR:
+ break;
+
+ case SFP_S_DOWN:
+ if (qsfp_sm_power_up(qsfp))
+ break;
+ fallthrough;
+ case SFP_S_WPOWER:
+ if (qsfp->sm_state != SFP_S_WPOWER || event == SFP_E_TIMEOUT)
+ if (sfp_sm_tx_enable(qsfp))
+ break;
+ fallthrough;
+ case SFP_S_WTXEN:
+ if (qsfp->sm_state != SFP_S_WTXEN || event == SFP_E_TIMEOUT)
+ sfp_sm_init(qsfp);
+ break;
+
+ case SFP_S_INIT:
+ if (event == SFP_E_TIMEOUT && qsfp->state & SFP_F_TX_FAULT)
+ sfp_sm_fault(qsfp, true);
+ else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR)
+ sfp_sm_link_check_los(qsfp);
+ break;
+
+ case SFP_S_WAIT_LOS:
+ if (event == SFP_E_TX_FAULT)
+ sfp_sm_fault(qsfp, true);
+ else if (sfp_los_event_inactive(qsfp, event))
+ sfp_sm_link_up(qsfp);
+ break;
+
+ case SFP_S_LINK_UP:
+ if (event == SFP_E_TX_FAULT) {
+ sfp_sm_link_down(qsfp);
+ sfp_sm_fault(qsfp, true);
+ } else if (sfp_los_event_active(qsfp, event)) {
+ sfp_sm_link_down(qsfp);
+ qsfp_sm_next(qsfp, SFP_S_WAIT_LOS, 0);
+ }
+ break;
+
+ case SFP_S_TX_FAULT:
+ if (event == SFP_E_TIMEOUT) {
+ qsfp_sm_next(qsfp, SFP_S_REINIT, T_INIT_JIFFIES);
+ }
+ break;
+
+ case SFP_S_REINIT:
+ if (event == SFP_E_TIMEOUT && qsfp->state & SFP_F_TX_FAULT) {
+ sfp_sm_fault(qsfp, false);
+ } else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
+ dev_info(qsfp->dev, "module transmit fault recovered\n");
+ sfp_sm_link_check_los(qsfp);
+ }
+ break;
+
+ case SFP_S_TX_DISABLE:
+ break;
+ }
+
+ if (qsfp->current_tx_disable != qsfp->request_tx_disable &&
+ qsfp->id.ext.sff8436.options[3] & SFF8X36_OPTIONS195_TX_DISABLE) {
+ qsfp->current_tx_disable = qsfp->request_tx_disable;
+
+ if (qsfp_set_tx_disable(qsfp, qsfp->current_tx_disable) < 0)
+ qsfp_sm_next(qsfp, SFP_S_ERROR, 0);
+ }
+}
+
+static void qsfp_sm_event(struct qsfp *qsfp, unsigned int event)
+{
+ mutex_lock(&qsfp->sm_mutex);
+
+ dev_dbg(qsfp->dev, "SM: enter %s:%s:%s event %s\n",
+ mod_state_to_str(qsfp->sm_mod_state),
+ dev_state_to_str(qsfp->sm_dev_state),
+ sm_state_to_str(qsfp->sm_state),
+ event_to_str(event));
+
+ qsfp_sm_device(qsfp, event);
+ qsfp_sm_module(qsfp, event);
+ qsfp_sm_main(qsfp, event);
+
+ dev_dbg(qsfp->dev, "SM: exit %s:%s:%s\n",
+ mod_state_to_str(qsfp->sm_mod_state),
+ dev_state_to_str(qsfp->sm_dev_state),
+ sm_state_to_str(qsfp->sm_state));
+
+ mutex_unlock(&qsfp->sm_mutex);
+}
+
+static void sfp_attach(struct qsfp *qsfp)
+{
+ qsfp_sm_event(qsfp, SFP_E_ATTACH);
+}
+
+static void sfp_detach(struct qsfp *qsfp)
+{
+ qsfp_sm_event(qsfp, SFP_E_DETACH);
+}
+
+static void sfp_start(struct qsfp *qsfp)
+{
+ qsfp_sm_event(qsfp, SFP_E_DEV_UP);
+}
+
+static void sfp_stop(struct qsfp *qsfp)
+{
+ qsfp_sm_event(qsfp, SFP_E_DEV_DOWN);
+}
+
+#if 0
+static int sfp_module_info(struct qsfp *sfp, struct ethtool_modinfo *modinfo)
+{
+ /* locking... and check module is present */
+
+ if (sfp->id.ext.sff8472_compliance &&
+ !(sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE)) {
+ modinfo->type = ETH_MODULE_SFF_8472;
+ modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
+ } else {
+ modinfo->type = ETH_MODULE_SFF_8079;
+ modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
+ }
+ return 0;
+}
+
+static int sfp_module_eeprom(struct qsfp *sfp, struct ethtool_eeprom *ee,
+ u8 *data)
+{
+ unsigned int first, last, len;
+ int ret;
+
+ if (ee->len == 0)
+ return -EINVAL;
+
+ first = ee->offset;
+ last = ee->offset + ee->len;
+ if (first < ETH_MODULE_SFF_8079_LEN) {
+ len = min_t(unsigned int, last, ETH_MODULE_SFF_8079_LEN);
+ len -= first;
+
+ ret = qsfp_read(sfp, false, first, data, len);
+ if (ret < 0)
+ return ret;
+
+ first += len;
+ data += len;
+ }
+ if (first < ETH_MODULE_SFF_8472_LEN && last > ETH_MODULE_SFF_8079_LEN) {
+ len = min_t(unsigned int, last, ETH_MODULE_SFF_8472_LEN);
+ len -= first;
+ first -= ETH_MODULE_SFF_8079_LEN;
+
+ ret = qsfp_read(sfp, true, first, data, len);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static const struct sfp_socket_ops sfp_module_ops = {
+ .attach = sfp_attach,
+ .detach = sfp_detach,
+ .start = sfp_start,
+ .stop = sfp_stop,
+ .module_info = sfp_module_info,
+ .module_eeprom = sfp_module_eeprom,
+};
+#endif
+
+static void sfp_timeout(struct work_struct *work)
+{
+ struct qsfp *sfp = container_of(work, struct qsfp, timeout.work);
+
+ rtnl_lock();
+ qsfp_sm_event(sfp, SFP_E_TIMEOUT);
+ rtnl_unlock();
+}
+
+static void qsfp_check_modprs(struct qsfp *qsfp)
+{
+ unsigned int v, state = 0;
+
+ v = gpiod_get_value_cansleep(qsfp->gpio[GPIO_MODPRS]);
+ if (v)
+ state |= SFP_F_PRESENT;
+
+ if (state != qsfp->state) {
+ rtnl_lock();
+ qsfp_sm_event(qsfp, state & SFP_F_PRESENT ?
+ SFP_E_INSERT : SFP_E_REMOVE);
+ rtnl_unlock();
+ qsfp->state = state;
+ }
+}
+
+static void qsfp_check_intl(struct qsfp *qsfp, bool poll)
+{
+ u8 irqs[19];
+ int ret;
+
+ // Read interrupt flags
+ ret = qsfp_read(qsfp, SFF8X36_IRQ_FLAGS, irqs, sizeof(irqs));
+ if (ret < 0)
+ return;
+
+ if (!memcmp(qsfp->module_irq_flags, irqs, sizeof(irqs)))
+ return;
+
+ dev_info(qsfp->dev, "irqs: %*ph\n", (int)sizeof(irqs), irqs);
+
+ memcpy(qsfp->module_irq_flags, irqs, sizeof(irqs));
+}
+
+static irqreturn_t qsfp_irq_modprs(int irq, void *data)
+{
+ struct qsfp *qsfp = data;
+
+ mutex_lock(&qsfp->st_mutex);
+ qsfp_check_modprs(qsfp);
+ mutex_unlock(&qsfp->st_mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qsfp_irq_intl(int irq, void *data)
+{
+ struct qsfp *qsfp = data;
+
+ mutex_lock(&qsfp->st_mutex);
+ qsfp_check_intl(qsfp, false);
+ mutex_unlock(&qsfp->st_mutex);
+
+ return IRQ_HANDLED;
+}
+
+static void qsfp_poll(struct work_struct *work)
+{
+ struct qsfp *qsfp = container_of(work, struct qsfp, poll.work);
+ bool need_poll = false;
+
+ mutex_lock(&qsfp->st_mutex);
+ if (!qsfp->gpio_irq[GPIO_MODPRS]) {
+ qsfp_check_modprs(qsfp);
+ need_poll = true;
+ }
+ if (!qsfp->gpio_irq[GPIO_INTL] && qsfp->sm_mod_state > SFP_MOD_PROBE) {
+ qsfp_check_intl(qsfp, true);
+ need_poll = true;
+ }
+ mutex_unlock(&qsfp->st_mutex);
+
+ if (need_poll)
+ mod_delayed_work(system_wq, &qsfp->poll, poll_jiffies);
+}
+
+static struct qsfp *sfp_alloc(struct device *dev)
+{
+ struct qsfp *qsfp;
+
+ qsfp = kzalloc(sizeof(*qsfp), GFP_KERNEL);
+ if (!qsfp)
+ return ERR_PTR(-ENOMEM);
+
+ qsfp->dev = dev;
+
+ mutex_init(&qsfp->sm_mutex);
+ mutex_init(&qsfp->st_mutex);
+ INIT_DELAYED_WORK(&qsfp->poll, qsfp_poll);
+ INIT_DELAYED_WORK(&qsfp->timeout, sfp_timeout);
+
+ return qsfp;
+}
+
+static void sfp_cleanup(void *data)
+{
+ struct qsfp *qsfp = data;
+
+ cancel_delayed_work_sync(&qsfp->poll);
+ cancel_delayed_work_sync(&qsfp->timeout);
+ if (qsfp->i2c_mii) {
+ mdiobus_unregister(qsfp->i2c_mii);
+ mdiobus_free(qsfp->i2c_mii);
+ }
+ if (qsfp->i2c)
+ i2c_put_adapter(qsfp->i2c);
+ kfree(qsfp);
+}
+
+static int qsfp_probe(struct platform_device *pdev)
+{
+ struct i2c_adapter *i2c;
+ struct qsfp *qsfp;
+ int err, i;
+
+ qsfp = sfp_alloc(&pdev->dev);
+ if (IS_ERR(qsfp))
+ return PTR_ERR(qsfp);
+
+ platform_set_drvdata(pdev, qsfp);
+
+ err = devm_add_action(qsfp->dev, sfp_cleanup, qsfp);
+ if (err < 0)
+ return err;
+
+ if (pdev->dev.of_node) {
+ struct device_node *node = pdev->dev.of_node;
+ const struct of_device_id *id;
+ struct device_node *np;
+
+ id = of_match_node(qsfp_of_match, node);
+ if (WARN_ON(!id))
+ return -EINVAL;
+
+ np = of_parse_phandle(node, "i2c-bus", 0);
+ if (!np) {
+ dev_err(qsfp->dev, "missing 'i2c-bus' property\n");
+ return -ENODEV;
+ }
+
+ i2c = of_find_i2c_adapter_by_node(np);
+ of_node_put(np);
+ } else if (has_acpi_companion(&pdev->dev)) {
+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
+ struct fwnode_handle *fw = acpi_fwnode_handle(adev);
+ struct fwnode_reference_args args;
+ struct acpi_handle *acpi_handle;
+ int ret;
+
+ ret = acpi_node_get_property_reference(fw, "i2c-bus", 0, &args);
+ if (ret || !is_acpi_device_node(args.fwnode)) {
+ dev_err(&pdev->dev, "missing 'i2c-bus' property\n");
+ return -ENODEV;
+ }
+
+ acpi_handle = ACPI_HANDLE_FWNODE(args.fwnode);
+ i2c = i2c_acpi_find_adapter_by_handle(acpi_handle);
+ } else {
+ return -EINVAL;
+ }
+
+ if (!i2c)
+ return -EPROBE_DEFER;
+
+ err = qsfp_i2c_configure(qsfp, i2c);
+ if (err < 0) {
+ i2c_put_adapter(i2c);
+ return err;
+ }
+
+ for (i = 0; i < GPIO_MAX; i++) {
+ qsfp->gpio[i] = devm_gpiod_get_optional(qsfp->dev,
+ gpio_of_names[i], gpio_flags[i]);
+ if (IS_ERR(qsfp->gpio[i]))
+ return PTR_ERR(qsfp->gpio[i]);
+ }
+
+ device_property_read_u32(&pdev->dev, "maximum-power-milliwatt",
+ &qsfp->max_power_mW);
+ if (!qsfp->max_power_mW)
+ qsfp->max_power_mW = 1500;
+
+ dev_info(qsfp->dev, "Host maximum power %u.%uW\n",
+ qsfp->max_power_mW / 1000, (qsfp->max_power_mW / 100) % 10);
+
+ for (i = 0; i < GPIO_MAX; i++) {
+ unsigned long irq_flags;
+ irq_handler_t irq_handler;
+
+ if (gpio_flags[i] != GPIOD_IN || !qsfp->gpio[i])
+ continue;
+
+ qsfp->gpio_irq[i] = gpiod_to_irq(qsfp->gpio[i]);
+ if (!qsfp->gpio_irq[i]) {
+ qsfp->need_poll = true;
+ continue;
+ }
+
+ irq_flags = IRQF_ONESHOT;
+ if (i == GPIO_MODPRS) {
+ irq_flags |= IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING;
+ irq_handler = qsfp_irq_modprs;
+ } else {
+ irq_handler = qsfp_irq_intl;
+ }
+
+ err = devm_request_threaded_irq(qsfp->dev, qsfp->gpio_irq[i],
+ NULL, irq_handler, irq_flags,
+ dev_name(qsfp->dev), qsfp);
+ if (err) {
+ qsfp->gpio_irq[i] = 0;
+ qsfp->need_poll = true;
+ }
+ }
+
+ if (qsfp->need_poll)
+ mod_delayed_work(system_wq, &qsfp->poll, poll_jiffies);
+
+ // Get the initial state
+ mutex_lock(&qsfp->st_mutex);
+ qsfp_check_modprs(qsfp);
+ mutex_unlock(&qsfp->st_mutex);
+
+// qsfp->sfp_bus = sfp_register_socket(qsfp->dev, qsfp, &sfp_module_ops);
+// if (!qsfp->sfp_bus)
+// return -ENOMEM;
+
+ rtnl_lock();
+ sfp_attach(qsfp);
+ sfp_start(qsfp);
+ rtnl_unlock();
+
+ return 0;
+}
+
+static int qsfp_remove(struct platform_device *pdev)
+{
+ struct qsfp *qsfp = platform_get_drvdata(pdev);
+
+// sfp_unregister_socket(sfp->sfp_bus);
+ rtnl_lock();
+ sfp_stop(qsfp);
+ sfp_detach(qsfp);
+ qsfp_sm_event(qsfp, SFP_E_REMOVE);
+ rtnl_unlock();
+
+ return 0;
+}
+
+static void qsfp_shutdown(struct platform_device *pdev)
+{
+ struct qsfp *qsfp = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < GPIO_MAX; i++) {
+ if (!qsfp->gpio_irq[i])
+ continue;
+
+ devm_free_irq(qsfp->dev, qsfp->gpio_irq[i], qsfp);
+ }
+
+ cancel_delayed_work_sync(&qsfp->poll);
+ cancel_delayed_work_sync(&qsfp->timeout);
+}
+
+static struct platform_driver qsfp_driver = {
+ .probe = qsfp_probe,
+ .remove = qsfp_remove,
+ .shutdown = qsfp_shutdown,
+ .driver = {
+ .name = "qsfp",
+ .of_match_table = qsfp_of_match,
+ },
+};
+
+static int qsfp_init(void)
+{
+ poll_jiffies = msecs_to_jiffies(100);
+
+ return platform_driver_register(&qsfp_driver);
+}
+module_init(qsfp_init);
+
+static void qsfp_exit(void)
+{
+ platform_driver_unregister(&qsfp_driver);
+}
+module_exit(qsfp_exit);
+
+MODULE_ALIAS("platform:qsfp");
+MODULE_AUTHOR("Russell King");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/sfp.h b/include/linux/sfp.h
index 26eacf6fa46f..b42f75c3cc50 100644
--- a/include/linux/sfp.h
+++ b/include/linux/sfp.h
@@ -275,6 +275,101 @@ struct sfp_diag {
__be16 cal_v_offset;
} __packed;
+struct qsfp_sff8x36_id_stat {
+ u8 identifier;
+ u8 rev_compliance;
+ u8 status;
+} __packed;
+
+struct qsfp_sff8x36_id_base {
+ u8 identifier;
+ u8 ext_identifier;
+ u8 connector;
+ u8 compliance[8];
+ u8 encoding;
+ u8 br_nominal;
+ u8 ext_ratesel_compliance;
+ u8 length[5];
+ u8 device_tech;
+ char vendor_name[16];
+ u8 ext_module;
+ u8 oui[3];
+ char vendor_pn[16];
+ char vendor_rev[2];
+ union {
+ __be16 wavelength;
+ u8 copper_atten[2];
+ } __packed;
+ __be16 wavelength_tolerance;
+ u8 max_case_temp;
+ u8 cc_base;
+} __packed;
+
+struct qsfp_sff8436_id_ext {
+ u8 options[4];
+ char vendor_sn[16];
+ u8 datecode[8];
+ u8 diagmon;
+ u8 enh_options;
+ u8 reserved;
+ u8 cc_ext;
+} __packed;
+
+struct qsfp_sff8636_id_ext {
+ u8 link_codes;
+ u8 options[3];
+ char vendor_sn[16];
+ u8 datecode[8];
+ u8 diagmon;
+ u8 enh_options;
+ u8 baud_rate_nominal;
+ u8 cc_ext;
+} __packed;
+
+struct qsfp_sff8x36_id {
+ struct qsfp_sff8x36_id_base base;
+ union {
+ struct qsfp_sff8436_id_ext sff8436;
+ struct qsfp_sff8636_id_ext sff8636;
+ } __packed ext;
+} __packed;
+
+enum {
+#define SFF8X36_ADDR(page, addr) ((page) << 8 | (addr))
+ SFF8X36_STAT = SFF8X36_ADDR(0, 2),
+ SFF8X36_STAT_FLAT_MEM = BIT(2),
+ SFF8X36_STAT_INTL = BIT(1),
+ SFF8X36_STAT_DATA_NOT_READY = BIT(0),
+ SFF8X36_IRQ_FLAGS = SFF8X36_ADDR(0, 3),
+ SFF8X36_TEMPERATURE = SFF8X36_ADDR(0, 22),
+ SFF8X36_SUPPLY_VOLTAGE = SFF8X36_ADDR(0, 26),
+ SFF8X36_RX_POWER = SFF8X36_ADDR(0, 34),
+ SFF8X36_TX_BIAS = SFF8X36_ADDR(0, 42),
+ SFF8X36_TX_POWER = SFF8X36_ADDR(0, 50),
+ SFF8X36_TX_DISABLE = SFF8X36_ADDR(0, 86),
+ SFF8X36_TX_DISABLE_TX4 = BIT(3),
+ SFF8X36_TX_DISABLE_TX3 = BIT(2),
+ SFF8X36_TX_DISABLE_TX2 = BIT(1),
+ SFF8X36_TX_DISABLE_TX1 = BIT(0),
+ SFF8X36_RX_RATE_SELECT = SFF8X36_ADDR(0, 87),
+ SFF8X36_TX_RATE_SELECT = SFF8X36_ADDR(0, 88),
+ SFF8X36_CTRL_93 = SFF8X36_ADDR(0, 93),
+ SFF8636_CTRL_93_SW_RESET = BIT(7),
+ SFF8636_CTRL_93_POWER_CLS8 = BIT(3),
+ SFF8636_CTRL_93_POWER_CLS5_7 = BIT(2),
+ SFF8X36_CTRL_93_POWER_SET = BIT(1),
+ SFF8X36_CTRL_93_POWER_ORIDE = BIT(0),
+ SFF8X36_CLS8_MAX_POWER = SFF8X36_ADDR(0, 107),
+ SFF8X36_ID = SFF8X36_ADDR(0, 128),
+ SFF8X36_OPTIONS195_TX_DISABLE = BIT(4),
+ SFF8X36_DIAGMON = SFF8X36_ADDR(0, 220),
+ SFF8636_DIAGMON_TEMP = BIT(5),
+ SFF8636_DIAGMON_VCC = BIT(4),
+ SFF8X36_DIAGMON_RXPWR_AVG = BIT(3),
+ SFF8636_DIAGMON_TXPWR = BIT(2),
+ SFF8X36_THRESHOLDS = SFF8X36_ADDR(3, 128),
+};
+
/* SFF8024 defined constants */
enum {
SFF8024_ID_UNK = 0x00,
@@ -299,7 +394,7 @@ enum {
SFF8024_ENCODING_PAM4 = 0x08,
SFF8024_CONNECTOR_UNSPEC = 0x00,
- /* codes 01-05 not supportable on SFP, but some modules have single SC */
+ // codes 01-05 not supportable on SFP, but some modules have single SC
SFF8024_CONNECTOR_SC = 0x01,
SFF8024_CONNECTOR_FIBERJACK = 0x06,
SFF8024_CONNECTOR_LC = 0x07,
@@ -330,6 +425,21 @@ enum {
SFF8024_ECC_2_5GBASE_T = 0x1e,
};
+enum {
+ // rev_compliance
+ // SFF8X36_REV_UNSPEC can be used up to SFF-8636 rev 2.5 exclusive.
+ SFF8X36_REV_UNSPEC = 0,
+ SFF8X36_REV_8436_4_8 = 1,
+ SFF8X36_REV_8436_4_8P = 2,
+ SFF8X36_REV_8636_1_3 = 3,
+ SFF8X36_REV_8636_1_4 = 4,
+ SFF8X36_REV_8636_1_5 = 5,
+ SFF8X36_REV_8636_2_0 = 6,
+ SFF8X36_REV_8636_2_5 = 7,
+ SFF8X36_REV_8636_2_8 = 8,
+
+};
+
/* SFP EEPROM registers */
enum {
SFP_PHYS_ID = 0x00,