From: NagarajuDeepakX <deepakx.nagar...@intel.com> commit b9927cdeeb591cacbd31206530d54a98ce08207f from https://github.com/altera-opensource/linux-socfpga.git
This patch adds qsfp_phy support to the E-Tile Ethernet controller. Signed-off-by: NagarajuDeepakX <deepakx.nagar...@intel.com> Signed-off-by: Wenlin Kang <wenlin.k...@windriver.com> --- drivers/net/phy/Kconfig | 8 + drivers/net/phy/Makefile | 4 + drivers/net/phy/qsfp.c | 1848 ++++++++++++++++++++++++++++++++++++ drivers/net/phy/qsfp_bus.c | 782 +++++++++++++++ include/linux/qsfp.h | 400 ++++++++ 5 files changed, 3042 insertions(+) create mode 100644 drivers/net/phy/qsfp.c create mode 100644 drivers/net/phy/qsfp_bus.c create mode 100644 include/linux/qsfp.h diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 698bea312adc..4833e817bb98 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -63,6 +63,14 @@ config SFP comment "MII PHY device drivers" +config QSFP + tristate "QSFP cage support" + depends on I2C && PHYLINK + depends on HWMON || HWMON=n + select MDIO_I2C + +comment "MII PHY device drivers" + config AMD_PHY tristate "AMD PHYs" help diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index a13e402074cf..f3dd5cd88451 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -30,6 +30,10 @@ obj-$(CONFIG_SFP) += sfp.o sfp-obj-$(CONFIG_SFP) += sfp-bus.o obj-y += $(sfp-obj-y) $(sfp-obj-m) +obj-$(CONFIG_QSFP) += qsfp.o +qsfp-obj-$(CONFIG_QSFP) += qsfp_bus.o +obj-y += $(qsfp-obj-y) $(qsfp-obj-m) + obj-$(CONFIG_ADIN_PHY) += adin.o obj-$(CONFIG_AMD_PHY) += amd.o aquantia-objs += aquantia_main.o diff --git a/drivers/net/phy/qsfp.c b/drivers/net/phy/qsfp.c new file mode 100644 index 000000000000..c0f1f8953d6c --- /dev/null +++ b/drivers/net/phy/qsfp.c @@ -0,0 +1,1848 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/acpi.h> +#include <linux/ctype.h> +#include <linux/debugfs.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 <linux/qsfp.h> + +#include "swphy.h" + +static u8 previous_state = 1; +static struct qsfp *qsfp; +static void qsfp_sm_event(struct qsfp *qsfp, unsigned int event); +static void get_module_revision(struct qsfp *qsfp); + +#define QSFP_TX_CHANNEL_4 0x4 +#define QSFP_TX_CHANNEL_3 0x3 +#define QSFP_TX_CHANNEL_2 0x2 +#define QSFP_TX_CHANNEL_1 0x1 +#define QSFP_RX_CHANNEL_4 0x4 +#define QSFP_RX_CHANNEL_3 0x3 +#define QSFP_RX_CHANNEL_2 0x2 +#define QSFP_RX_CHANNEL_1 0x1 + +enum { + GPIO_MODULE_PRESENT, + GPIO_MODULE_INTERRUPT, + GPIO_MODULE_INIT_MODE, + GPIO_MODULE_RESET, + GPIO_MODULE_SELECT, + GPIO_MAX, + + QSFP_F_PRESENT = BIT(GPIO_MODULE_PRESENT), + QSFP_INTERRUPT = BIT(GPIO_MODULE_INTERRUPT), + QSFP_INIT = BIT(GPIO_MODULE_INIT_MODE), + QSFP_RESET = BIT(GPIO_MODULE_RESET), + QSFP_SELECT = BIT(GPIO_MODULE_SELECT), + + QSFP_E_INSERT = 0, + QSFP_E_REMOVE, + QSFP_E_DEV_ATTACH, + QSFP_E_DEV_DETACH, + QSFP_E_DEV_DOWN, + QSFP_E_DEV_UP, + QSFP_E_TX_FAULT, + QSFP_E_TX_CLEAR, + QSFP_E_TX_LOS, + QSFP_E_RX_LOS, + QSFP_E_TIMEOUT, + + QSFP_MOD_EMPTY = 0, + QSFP_MOD_ERROR, + QSFP_MOD_PROBE, + QSFP_MOD_WAITDEV, + QSFP_MOD_HPOWER, + QSFP_MOD_WAITPWR, + QSFP_MOD_PRESENT, + + QSFP_DEV_DETACHED = 0, + QSFP_DEV_DOWN, + QSFP_DEV_UP, + + QSFP_S_DOWN = 0, + QSFP_S_FAIL, + QSFP_S_WAIT, + QSFP_S_INIT, + QSFP_S_INIT_PHY, + QSFP_S_INIT_TX_FAULT, + QSFP_S_WAIT_LOS, + QSFP_S_LINK_UP, + QSFP_S_TX_FAULT, + QSFP_S_REINIT, + QSFP_S_TX_DISABLE, +}; + +static const char *const mod_state_strings[] = { + [QSFP_MOD_EMPTY] = "empty", [QSFP_MOD_ERROR] = "error", + [QSFP_MOD_PROBE] = "probe", [QSFP_MOD_WAITDEV] = "waitdev", + [QSFP_MOD_HPOWER] = "hpower", [QSFP_MOD_WAITPWR] = "waitpwr", + [QSFP_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[] = { + [QSFP_DEV_DETACHED] = "detached", + [QSFP_DEV_DOWN] = "down", + [QSFP_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[] = { + [QSFP_E_INSERT] = "insert", [QSFP_E_REMOVE] = "remove", + [QSFP_E_DEV_ATTACH] = "dev_attach", [QSFP_E_DEV_DETACH] = "dev_detach", + [QSFP_E_DEV_DOWN] = "dev_down", [QSFP_E_DEV_UP] = "dev_up", + [QSFP_E_TX_FAULT] = "tx_fault", [QSFP_E_TX_CLEAR] = "tx_clear", + [QSFP_E_TX_LOS] = "tx_los", [QSFP_E_RX_LOS] = "rx_los", + [QSFP_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[] = { + [QSFP_S_DOWN] = "down", + [QSFP_S_FAIL] = "fail", + [QSFP_S_WAIT] = "wait", + [QSFP_S_INIT] = "init", + [QSFP_S_INIT_PHY] = "init_phy", + [QSFP_S_INIT_TX_FAULT] = "init_tx_fault", + [QSFP_S_WAIT_LOS] = "wait_los", + [QSFP_S_LINK_UP] = "link_up", + [QSFP_S_TX_FAULT] = "tx_fault", + [QSFP_S_REINIT] = "reinit", + [QSFP_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 *const gpio_of_names[] = { + "qsfpdd_modprsn", "qsfpdd_intn", "qsfpdd_initmode", + "qsfpdd_resetn", "qsfpdd_modseln", +}; + +static const enum gpiod_flags gpio_flags[] = { + GPIOD_IN, GPIOD_IN, GPIOD_ASIS, GPIOD_ASIS, GPIOD_ASIS, +}; + +/* t_start_up (SFF-8431) or t_init (SFF-8472) is the time required for a + * non-cooled module to initialise its laser safety circuitry. We wait + * an initial T_WAIT period before we check the tx fault to give any PHY + * on board (for a copper qsfp) time to initialise. + */ +#define T_WAIT msecs_to_jiffies(50) +#define T_START_UP msecs_to_jiffies(300) +#define T_START_UP_BAD_GPON msecs_to_jiffies(60000) + +/* t_reset is the time required to assert the TX_DISABLE signal to reset + * an indicated TX_FAULT. + */ +#define T_RESET_US 10 +#define T_FAULT_RECOVER msecs_to_jiffies(1000) + +/* N_FAULT_INIT is the number of recovery attempts at module initialisation + * time. If the TX_FAULT signal is not deasserted after this number of + * attempts at clearing it, we decide that the module is faulty. + * N_FAULT is the same but after the module has initialised. + */ +#define N_FAULT_INIT 5 +#define N_FAULT 5 + +/* T_PHY_RETRY is the time interval between attempts to probe the PHY. + * R_PHY_RETRY is the number of attempts. + */ +#define T_PHY_RETRY msecs_to_jiffies(50) +#define R_PHY_RETRY 12 + +/* qsfp module presence detection is poor: the three MOD DEF signals are + * the same length on the PCB, which means it's possible for MOD DEF 0 to + * connect before the I2C bus on MOD DEF 1/2. + * + * The SFF-8472 specifies t_serial ("Time from power on until module is + * ready for data transmission over the two wire serial bus.") as 300ms. + */ +#define T_SERIAL msecs_to_jiffies(300) +#define T_HPOWER_LEVEL msecs_to_jiffies(300) +#define T_PROBE_RETRY_INIT msecs_to_jiffies(100) +#define R_PROBE_RETRY_INIT 10 +#define T_PROBE_RETRY_SLOW msecs_to_jiffies(5000) +#define R_PROBE_RETRY_SLOW 12 + +/* qsfp modules appear to always have their PHY configured for bus address + * 0x56 (which with mdio-i2c, translates to a PHY address of 22). + */ +#define QSFP_PHY_ADDR 22 + +struct sff_data { + unsigned int gpios; + bool (*module_supported)(const struct qsfp_eeprom_id *id); +}; + +struct qsfp { + struct device *dev; + struct i2c_adapter *i2c; + struct mii_bus *i2c_mii; + struct qsfp_bus *qsfp_bus; + struct phy_device *mod_phy; + const struct sff_data *type; + size_t i2c_block_size; + u32 max_power_mw; + unsigned int module_revision; + unsigned int module_present; + int channel_number; + unsigned char vender_name[16]; + unsigned char sn_number[16]; + unsigned char part_number[16]; + + unsigned int (*get_state)(struct qsfp *qsfp); + void (*set_state)(struct qsfp *qsfp, unsigned int state); + int (*read)(struct qsfp *qsfp, bool a2, u8 dev_addr, void *buf, size_t len); + int (*write)(struct qsfp *qsfp, bool a2, u8 dev_addr, void *buf, size_t len); + + struct gpio_desc *gpio[GPIO_MAX]; + int gpio_irq[GPIO_MAX]; + + bool need_poll; + + struct mutex st_mutex; /* Protects state */ + unsigned int state_soft_mask; + 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_mod_tries_init; + unsigned char sm_mod_tries; + unsigned char sm_dev_state; + unsigned short sm_state; + unsigned char sm_fault_retries; + unsigned char sm_phy_retries; + + struct qsfp_eeprom_id id; + unsigned int module_power_mw; + unsigned int module_t_start_up; +}; + +static bool qsfp_module_supported(const struct qsfp_eeprom_id *id) +{ + if (id->base.etile_qsfp_identifier == SFF8024_ID_QSFP_28) + return true; + + /* qsfp GPON module Ubiquiti U-Fiber Instant has in its EEPROM stored + * phys id SFF instead of qsfp. Therefore mark this module explicitly + * as supported based on vendor name and pn match. + */ + if (id->base.etile_qsfp_identifier == SFF8024_ID_QSFP_DD_INF_8628 && + id->base.etile_qsfp_ext_identifier == QSFP_EXT_IDENTIFIER && + !memcmp(id->base.etile_qsfp_vendor_name, "UBNT ", 16) && + !memcmp(id->base.etile_qsfp_vendor_pn, "UF-INSTANT ", 16)) + return true; + + return false; +} + +static const struct sff_data qsfp_data = { + .gpios = QSFP_F_PRESENT | QSFP_INTERRUPT | QSFP_INIT | QSFP_RESET | + QSFP_SELECT, + .module_supported = qsfp_module_supported, +}; + +static const struct of_device_id qsfp_of_match[] = { + { + .compatible = "sff,qsfp", + .data = &qsfp_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, qsfp_of_match); + +static unsigned long poll_jiffies; + +static unsigned int qsfp_gpio_get_state(struct qsfp *qsfp) +{ + unsigned int i, state, v; + + for (i = state = 0; i < GPIO_MAX; i++) { + if (gpio_flags[i] != GPIOD_IN || !qsfp->gpio[i]) + continue; + + v = gpiod_get_value_cansleep(qsfp->gpio[i]); + if (v) + state |= BIT(i); + } + + return state; +} + +static unsigned int sff_gpio_get_state(struct qsfp *qsfp) +{ + return qsfp_gpio_get_state(qsfp) | QSFP_F_PRESENT; +} + +static void qsfp_gpio_set_state(struct qsfp *qsfp, unsigned int state) +{ + if (state & QSFP_F_PRESENT) { + /* If the module is present, drive the signals */ + gpiod_direction_output(qsfp->gpio[GPIO_MODULE_SELECT], + state & QSFP_SELECT); + + } else { + /* Otherwise, let them float to the pull-ups */ + + gpiod_direction_input(qsfp->gpio[GPIO_MODULE_PRESENT]); + } +} + +static int qsfp_i2c_read(struct qsfp *qsfp, bool a2, u8 dev_addr, void *buf, + size_t len) +{ + struct i2c_msg msgs[2]; + u8 bus_addr = a2 ? 0x51 : 0x50; + + size_t block_size = qsfp->i2c_block_size; + size_t this_len; + int ret; + + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &dev_addr; + msgs[1].addr = bus_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = buf; + + while (len) { + this_len = len; + if (this_len > block_size) + this_len = block_size; + + msgs[1].len = this_len; + + ret = i2c_transfer(qsfp->i2c, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + + if (ret != ARRAY_SIZE(msgs)) + break; + + msgs[1].buf += this_len; + dev_addr += this_len; + len -= this_len; + } + + return msgs[1].buf - (u8 *)buf; +} + +static int qsfp_i2c_write(struct qsfp *qsfp, bool a2, u8 dev_addr, void *buf, + size_t len) +{ + struct i2c_msg msgs[1]; + u8 bus_addr = a2 ? 0x51 : 0x50; + int ret; + + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = 1 + len; + msgs[0].buf = kmalloc(1 + len, GFP_KERNEL); + if (!msgs[0].buf) + return -ENOMEM; + + msgs[0].buf[0] = dev_addr; + memcpy(&msgs[0].buf[1], buf, len); + + ret = i2c_transfer(qsfp->i2c, msgs, ARRAY_SIZE(msgs)); + + kfree(msgs[0].buf); + + if (ret < 0) + return ret; + + return ret == ARRAY_SIZE(msgs) ? len : 0; +} + +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; +} + +void qsfp_reset(struct qsfp *qsfp, unsigned int value) +{ + unsigned int state = qsfp_gpio_get_state(qsfp); + + if (value) + qsfp_gpio_set_state(qsfp, state | QSFP_RESET); + else + qsfp_gpio_set_state(qsfp, state & (~QSFP_RESET)); +} +EXPORT_SYMBOL(qsfp_reset); + +/* Interface */ +static int qsfp_read(struct qsfp *qsfp, bool a2, u8 addr, void *buf, size_t len) +{ + return qsfp->read(qsfp, a2, addr, buf, len); +} + +static int qsfp_write(struct qsfp *qsfp, bool a2, u8 addr, void *buf, + size_t len) +{ + return qsfp->write(qsfp, a2, addr, buf, len); +} + +static void qsfp_soft_start_poll(struct qsfp *qsfp) +{ + u8 status; + //const struct qsfp_eeprom_id *id = &qsfp->id; + qsfp_read(qsfp, false, QSFP_OPTIONS, &status, sizeof(status)); + + qsfp->state_soft_mask = 0; + + if (qsfp->id.base.etile_qsfp_options_3 & QSFP_OPTIONS_TX_DISABLE) + qsfp->state_soft_mask |= QSFP_OPTIONS_TX_DISABLE; + if (qsfp->id.base.etile_qsfp_options_3 & QSFP_OPTIONS_TX_FAULT) + qsfp->state_soft_mask |= QSFP_OPTIONS_TX_FAULT; + if (qsfp->id.base.etile_qsfp_options_3 & QSFP_OPTIONS_TX_LOSS_SIGNAL) + qsfp->state_soft_mask |= QSFP_OPTIONS_TX_LOSS_SIGNAL; + + if (qsfp->state_soft_mask & + (QSFP_OPTIONS_TX_LOSS_SIGNAL | QSFP_OPTIONS_TX_FAULT) && + !qsfp->need_poll) + + mod_delayed_work(system_wq, &qsfp->poll, poll_jiffies); +} + +static void qsfp_soft_stop_poll(struct qsfp *qsfp) +{ + qsfp->state_soft_mask = 0; +} + +static unsigned int qsfp_get_state(struct qsfp *qsfp) +{ + unsigned int state = qsfp->get_state(qsfp); + + return state; +} + +static void qsfp_set_state(struct qsfp *qsfp, unsigned int state) +{ + qsfp->set_state(qsfp, state); +} + +static unsigned int qsfp_check(void *buf, size_t len) +{ + u8 *p, check; + + for (p = buf, check = 0; len; p++, len--) + check += *p; + + return check; +} + +/* Helpers */ +static void qsfp_module_tx_disable(struct qsfp *qsfp) +{ + dev_dbg(qsfp->dev, "tx disable %u -> %u\n", + qsfp->id.base.etile_qsfp_options_3 & QSFP_OPTIONS_TX_DISABLE ? + 1 : + 0, + 1); + qsfp->id.base.etile_qsfp_options_3 |= QSFP_OPTIONS_TX_DISABLE; + qsfp_set_state(qsfp, qsfp->state); +} + +static void qsfp_module_tx_enable(struct qsfp *qsfp) +{ + dev_dbg(qsfp->dev, "tx disable %u -> %u\n", + qsfp->id.base.etile_qsfp_options_3 & QSFP_OPTIONS_TX_DISABLE ? + 1 : + 0, + 0); + qsfp->id.base.etile_qsfp_options_3 &= ~QSFP_OPTIONS_TX_DISABLE; + qsfp_set_state(qsfp, qsfp->state); +} + +static void qsfp_module_tx_fault_reset(struct qsfp *qsfp) +{ + unsigned int state = qsfp->id.base.etile_qsfp_options_3; + + int ret; + + ret = qsfp_read(qsfp, false, QSFP_OPTIONS, &state, sizeof(state)); + + if (state & QSFP_OPTIONS_TX_DISABLE) + return; + + qsfp_set_state(qsfp, state | QSFP_OPTIONS_TX_DISABLE); + + udelay(T_RESET_US); + + qsfp_set_state(qsfp, state); +} + +/* qsfp state machine */ +static void qsfp_sm_set_timer(struct qsfp *qsfp, unsigned int timeout) +{ + if (timeout) { + mod_delayed_work(system_power_efficient_wq, &qsfp->timeout, + timeout); + } else { + cancel_delayed_work(&qsfp->timeout); + } +} + +static void qsfp_sm_next(struct qsfp *qsfp, unsigned int state, + unsigned int timeout) +{ + qsfp->sm_state = state; + qsfp_sm_set_timer(qsfp, timeout); +} + +static void qsfp_sm_mod_next(struct qsfp *qsfp, unsigned int state, + unsigned int timeout) +{ + qsfp->sm_mod_state = state; + qsfp_sm_set_timer(qsfp, timeout); +} + +static void qsfp_sm_phy_detach(struct qsfp *qsfp) +{ + qsfp_remove_phy(qsfp->qsfp_bus); + phy_device_remove(qsfp->mod_phy); + phy_device_free(qsfp->mod_phy); + qsfp->mod_phy = NULL; +} + +static int qsfp_sm_probe_phy(struct qsfp *qsfp, bool is_c45) +{ + struct phy_device *phy; + int err; + + phy = get_phy_device(qsfp->i2c_mii, QSFP_PHY_ADDR, is_c45); + + if (phy == ERR_PTR(-ENODEV)) + return PTR_ERR(phy); + + if (IS_ERR(phy)) { + dev_err(qsfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy)); + return PTR_ERR(phy); + } + + err = phy_device_register(phy); + if (err) { + phy_device_free(phy); + dev_err(qsfp->dev, "phy_device_register failed: %d\n", err); + return err; + } + + err = qsfp_add_phy(qsfp->qsfp_bus, phy); + if (err) { + phy_device_remove(phy); + phy_device_free(phy); + dev_err(qsfp->dev, "qsfp_add_phy failed: %d\n", err); + return err; + } + + qsfp->mod_phy = phy; + + return 0; +} + +static void qsfp_sm_link_up(struct qsfp *qsfp) +{ + qsfp_link_up(qsfp->qsfp_bus); + qsfp_sm_next(qsfp, QSFP_S_LINK_UP, 0); +} + +static void qsfp_sm_link_down(struct qsfp *qsfp) +{ + qsfp_link_down(qsfp->qsfp_bus); +} + +static void qsfp_sm_link_check_los(struct qsfp *qsfp) +{ + bool los = false; + + if (los) + qsfp_sm_next(qsfp, QSFP_S_WAIT_LOS, 0); + else + + qsfp_sm_link_up(qsfp); +} + +static bool qsfp_los_event_active(struct qsfp *qsfp, unsigned int event) +{ + int ret; + u8 buf[16]; + + ret = qsfp_read(qsfp, false, QSFP_PHYS_ID, buf, 1); + + return 0; +} + +static bool qsfp_los_event_inactive(struct qsfp *qsfp, unsigned int event) +{ + return 0; +} + +static void qsfp_sm_fault(struct qsfp *qsfp, unsigned int next_state, bool warn) +{ + if (qsfp->sm_fault_retries && !--qsfp->sm_fault_retries) { + dev_err(qsfp->dev, + "module persistently indicates fault, disabling\n"); + qsfp_sm_next(qsfp, QSFP_S_TX_DISABLE, 0); + } else { + if (warn) + dev_err(qsfp->dev, "module transmit fault indicated\n"); + + qsfp_sm_next(qsfp, next_state, T_FAULT_RECOVER); + } +} + +/* Probe a qsfp for a PHY device if the module supports copper - the PHY + * normally sits at I2C bus address 0x56, and may either be a clause 22 + * or clause 45 PHY. + * + * Clause 22 copper qsfp modules normally operate in Cisco SGMII mode with + * negotiation enabled, but some may be in 1000base-X - which is for the + * PHY driver to determine. + * + * Clause 45 copper qsfp+ modules (10G) appear to switch their interface + * mode according to the negotiated line speed. + */ +static int qsfp_sm_probe_for_phy(struct qsfp *qsfp) +{ + int err = 0; + + switch (qsfp->id.base.etile_qsfp_spec_compliance_1[0]) { + case SFF8636_QSFP_DD_ECC_LAUI2_C2M: + case SFF8636_QSFP_DD_ECC_50GAUI2_C2M: + case SFF8636_QSFP_DD_ECC_50GAUI1_C2M: + case SFF8636_QSFP_DD_ECC_CDAUI8_C2M: + err = qsfp_sm_probe_phy(qsfp, true); + break; + + default: + + err = qsfp_sm_probe_phy(qsfp, false); + break; + } + return err; +} + +static int qsfp_module_parse_power(struct qsfp *qsfp) +{ + u32 power_mw = 1000; + + if (power_mw > qsfp->max_power_mw) { + /* Module power specification exceeds the allowed maximum. */ + if (qsfp->id.base.etile_qsfp_spec_compliance_1[0] == + SFF8636_QSFP_DD_ECC_100GBASE_CR4 && + !(qsfp->id.base.etile_qsfp_diag_monitor & + QSFP_DIAGMON_DDM)) { + /* The module appears not to implement bus address + * 0xa2, so assume that the module powers up in the + * indicated mode. + */ + dev_err(qsfp->dev, + "Host does not support %u.%uW modules, module left in power mode\n", + power_mw / 1000, (power_mw / 100) % 10); + return -EINVAL; + } + } + + /* If the module requires a higher power mode, but also requires + * an address change sequence, warn the user that the module may + * not be functional. + */ + if (qsfp->id.base.etile_qsfp_diag_monitor & QSFP_DIAGMON_ADDRMODE && + power_mw > 1000) { + dev_warn(qsfp->dev, + "Address Change Sequence not supported but module requires %u.%uW, module may not be functional\n", + power_mw / 1000, (power_mw / 100) % 10); + return 0; + } + + qsfp->module_power_mw = power_mw; + + return 0; +} + +static int qsfp_sm_mod_hpower(struct qsfp *qsfp, bool enable) +{ + u8 val; + int err; + + static void *gpio_reg; + u32 gpio_reg_val; + + gpio_reg = ioremap(0x82000020, 4); + gpio_reg_val = readl(gpio_reg); + gpio_reg_val = gpio_reg_val & 0xfffffffe; + writel(gpio_reg_val, gpio_reg); + + qsfp_set_state(qsfp, qsfp->state & QSFP_INIT); + + err = qsfp_read(qsfp, false, QSFP_EXT_STATUS, &val, sizeof(val)); + if (err != sizeof(val)) { + dev_err(qsfp->dev, "Failed to read EEPROM: %d\n", err); + return -EAGAIN; + } + + /* DM7052 reports as a high power module, responds to reads (with + * all bytes 0xff) at 0x51 but does not accept writes. In any case, + * if the bit is already set, we're already in high power mode. + */ + if (!!(val & BIT(0)) == enable) + return 0; + + if (enable) + val |= BIT(0); + else + val &= ~BIT(0); + + err = qsfp_write(qsfp, false, QSFP_EXT_STATUS, &val, sizeof(val)); + if (err != sizeof(val)) { + dev_err(qsfp->dev, "Failed to write EEPROM: %d\n", err); + return -EAGAIN; + } + + if (enable) + dev_info(qsfp->dev, "Module switched to %u.%uW power level\n", + qsfp->module_power_mw / 1000, + (qsfp->module_power_mw / 100) % 10); + + return 0; +} + +/* GPON modules based on Realtek RTL8672 and RTL9601C chips (e.g. V-SOL + * V2801F, CarlitoxxPro CPGOS03-0490, Ubiquiti U-Fiber Instant, ...) do + * not support multibyte reads from the EEPROM. Each multi-byte read + * operation returns just one byte of EEPROM followed by zeros. There is + * no way to identify which modules are using Realtek RTL8672 and RTL9601C + * chips. Moreover every OEM of V-SOL V2801F module puts its own vendor + * name and vendor id into EEPROM, so there is even no way to detect if + * module is V-SOL V2801F. Therefore check for those zeros in the read + * data and then based on check switch to reading EEPROM to one byte + * at a time. + */ +static bool qsfp_id_needs_byte_io(struct qsfp *qsfp, void *buf, size_t len) +{ + size_t i, block_size = qsfp->i2c_block_size; + + /* Already using byte IO */ + if (block_size == 1) + return false; + + for (i = 1; i < len; i += block_size) { + if (memchr_inv(buf + i, '\0', min(block_size - 1, len - i))) + return false; + } + return true; +} + +static void get_module_revision(struct qsfp *old) +{ + int ret; + u8 buf[16]; + char buf_16[16]; + + ret = qsfp_read(qsfp, false, QSFP_VENDOR_NAME, buf_16, 16); + buf_16[15] = '\0'; + strcpy(qsfp->vender_name, buf_16); + ret = qsfp_read(qsfp, false, QSFP_VENDOR_PN, buf_16, 16); + buf_16[15] = '\0'; + strcpy(qsfp->part_number, buf_16); + ret = qsfp_read(qsfp, false, QSFP_VENDOR_SN, buf_16, 16); + buf_16[15] = '\0'; + strcpy(qsfp->sn_number, buf_16); + ret = qsfp_read(qsfp, false, QSFP_STATUS, buf, 1); + qsfp->module_revision = buf[0]; +} + +int get_cable_attach(struct qsfp *old) +{ + return qsfp->module_present; +} +EXPORT_SYMBOL(get_cable_attach); + +int get_channel_info(struct qsfp *old) +{ + return qsfp->channel_number; +} +EXPORT_SYMBOL(get_channel_info); + +static int qsfp_select_eeprom_page(struct qsfp *qsfp) +{ + int err; + u8 buf[16]; + u8 i = 0; + int ret; + + err = qsfp_write(qsfp, false, QSFP_PAGE_SELECT_BYTE, &i, 1); + + ret = qsfp_read(qsfp, false, QSFP_PAGE_SELECT_BYTE, buf, 1); + + return 0; +} + +static int qsfp_status_indicators(struct qsfp *qsfp) +{ + int ret; + u8 buf[16]; + bool flag = false; + static unsigned int prv_buf, prv_buf_rx; + + qsfp->state = qsfp_gpio_get_state(qsfp); + if (qsfp->state & QSFP_F_PRESENT) { + ret = qsfp_read(qsfp, false, QSFP_STATUS, buf, 1); + qsfp->module_revision = buf[0]; + if (qsfp->module_revision == 0x0) { + qsfp->module_present = TRUE; + qsfp->channel_number = -EOPNOTSUPP; + } + + if (qsfp->module_revision == 0x7 || qsfp->module_revision == 0x8) { + ret = qsfp_read(qsfp, false, QSFP_OPTIONS, buf, 1); + + if (buf[0] & QSFP_OPTIONS_TX_LOSS_SIGNAL) { + ret = qsfp_read(qsfp, false, QSFP_RX_TX_LOSS, buf, 1); + + if (prv_buf != buf[0]) { + prv_buf = buf[0]; + flag = true; + } + if (flag) { + qsfp->module_present = TRUE; + qsfp->channel_number = buf[0]; + } else { + qsfp->module_present = TRUE; + qsfp->channel_number = buf[0]; + } + } else { + ret = qsfp_read(qsfp, false, QSFP_RX_TX_LOSS, buf, 1); + buf[0] = buf[0] & 0xf; + if (prv_buf_rx != buf[0]) { + prv_buf_rx = buf[0]; + flag = true; + } + if (flag) { + qsfp->module_present = TRUE; + qsfp->channel_number = buf[0]; + } else { + qsfp->module_present = TRUE; + qsfp->channel_number = buf[0]; + } + } + } + + } else { + qsfp->module_present = -EINVAL; + qsfp->channel_number = -EOPNOTSUPP; + } + + return 0; +} + +static int qsfp_state_indicators(struct qsfp *qsfp) +{ + int ret; + u8 buf[16]; + bool flag = false; + + qsfp->state = qsfp_gpio_get_state(qsfp); + qsfp->state &= QSFP_F_PRESENT; + if (previous_state != qsfp->state) { + previous_state = (qsfp->state); + flag = true; + } + + if (flag) { + if (qsfp->state & QSFP_F_PRESENT) { + ret = qsfp_read(qsfp, false, QSFP_PHYS_ID, buf, 1); + + get_module_revision(qsfp); + pr_info("module: %s pn: %s sn: %s rev: %x\n", qsfp->vender_name, + qsfp->part_number, qsfp->sn_number, qsfp->module_revision); + + } else { + qsfp->module_present = -EINVAL; + qsfp->channel_number = -EOPNOTSUPP; + } + } + + return 0; +} + +static int qsfp_cotsworks_fixup_check(struct qsfp *qsfp, + struct qsfp_eeprom_id *id) +{ + u8 check; + int err; + + err = qsfp_write(qsfp, false, QSFP_PAGE_SELECT_BYTE, (u8 *)0x2, 1); + + if (id->base.etile_qsfp_identifier != SFF8024_ID_QSFP_DD_INF_8628 || + id->base.etile_qsfp_ext_identifier != QSFP_EXT_IDENTIFIER || + id->base.etile_qsfp_connector_type != + SFF8024_QSFP_DD_CONNECTOR_LC) { + dev_warn(qsfp->dev, + "Rewriting fiber module EEPROM with corrected values\n"); + id->base.etile_qsfp_identifier = SFF8024_ID_QSFP_DD_INF_8628; + id->base.etile_qsfp_ext_identifier = QSFP_EXT_IDENTIFIER; + id->base.etile_qsfp_connector_type = + SFF8024_QSFP_DD_CONNECTOR_LC; + err = qsfp_write(qsfp, false, QSFP_PHYS_ID, &id->base, 3); + if (err != 3) { + dev_err(qsfp->dev, + "Failed to rewrite module EEPROM: %d\n", err); + return err; + } + + /* Cotsworks modules have been found to require a delay between write operations. */ + mdelay(50); + + /* Update base structure checksum */ + check = qsfp_check(&id->base, sizeof(id->base) - 1); + err = qsfp_write(qsfp, false, QSFP_CC_BASE, &check, 1); + if (err != 1) { + dev_err(qsfp->dev, + "Failed to update base structure checksum in fiber module EEPROM: %d\n", + err); + return err; + } + } + return 0; +} + +static int qsfp_sm_mod_probe(struct qsfp *qsfp, bool report) +{ + /* qsfp module inserted - read I2C data */ + struct qsfp_eeprom_id id; + bool cotsworks_sfbg; + bool cotsworks; + + int ret; + + /* Some qsfp modules and also some Linux I2C drivers do not like reads + * longer than 16 bytes, so read the EEPROM in chunks of 16 bytes at + * a time. + */ + + qsfp->i2c_block_size = 16; + + qsfp_select_eeprom_page(qsfp); + + ret = qsfp_read(qsfp, false, 0x0, &id.base, sizeof(id.base)); + if (ret < 0) { + if (report) + dev_err(qsfp->dev, "failed to read EEPROM: %d\n", ret); + return -EAGAIN; + } + + if (ret != sizeof(id.base)) { + dev_err(qsfp->dev, "EEPROM short read: %d\n", ret); + + return -EAGAIN; + } + + /* Some qsfp modules (e.g. Nokia 3FE46541AA) lock up if read from + * address 0x51 is just one byte at a time. Also SFF-8472 requires + * that EEPROM supports atomic 16bit read operation for diagnostic + * fields, so do not switch to one byte reading at a time unless it + * is really required and we have no other option. + */ + if (qsfp_id_needs_byte_io(qsfp, &id.base, sizeof(id.base))) { + dev_info(qsfp->dev, + "Detected broken RTL8672/RTL9601C emulated EEPROM\n"); + + dev_info(qsfp->dev, + "Switching to reading EEPROM to one byte at a time\n"); + + qsfp->i2c_block_size = 1; + + ret = qsfp_read(qsfp, false, 0, &id.base, sizeof(id.base)); + if (ret < 0) { + if (report) { + dev_err(qsfp->dev, + "failed to read EEPROM: %d\n", ret); + } + + return -EAGAIN; + } + + if (ret != sizeof(id.base)) { + dev_err(qsfp->dev, "EEPROM short read: %d\n", ret); + + return -EAGAIN; + } + } + + /* Cotsworks do not seem to update the checksums when they + * do the final programming with the final module part number, + * serial number and date code. + */ + cotsworks = + !memcmp(id.base.etile_qsfp_vendor_name, "COTSWORKS ", 16); + cotsworks_sfbg = !memcmp(id.base.etile_qsfp_vendor_pn, "SFBG", 4); + + /* Cotsworks SFF module EEPROM do not always have valid etile_qsfp_identifier, + * etile_qsfp_ext_identifier, and connector bytes. Rewrite SFF EEPROM bytes if + * Cotsworks PN matches and bytes are not correct. + */ + if (cotsworks && cotsworks_sfbg) { + ret = qsfp_cotsworks_fixup_check(qsfp, &id); + + if (ret < 0) + return ret; + } + + qsfp->id = id; + dev_info(qsfp->dev, "module %.*s %.*s rev %.*x sn %.*s dc %.*s\n", + (int)sizeof(id.base.etile_qsfp_vendor_name), + id.base.etile_qsfp_vendor_name, + (int)sizeof(id.base.etile_qsfp_vendor_pn), + id.base.etile_qsfp_vendor_pn, + (int)sizeof(id.base.etile_qsfp_revision), + id.base.etile_qsfp_revision, + (int)sizeof(id.base.etile_qsfp_vendor_serial_number), + id.base.etile_qsfp_vendor_serial_number, + (int)sizeof(id.base.etile_qsfp_vendor_date_code), + id.base.etile_qsfp_vendor_date_code); + + /* Check whether we support this module */ + if (!qsfp->type->module_supported(&id)) { + dev_err(qsfp->dev, + "module is not supported - phys id 0x%02x 0x%02x\n", + qsfp->id.base.etile_qsfp_identifier_1, + qsfp->id.base.etile_qsfp_ext_identifier); + return -EINVAL; + } + + /* Parse the module power requirement */ + ret = qsfp_module_parse_power(qsfp); + if (ret < 0) + return ret; + + if (!memcmp(id.base.etile_qsfp_vendor_name, "YAMAICHI ", 16) && + !memcmp(id.base.etile_qsfp_vendor_pn, "3FE46541AA ", 16)) + qsfp->module_t_start_up = T_START_UP_BAD_GPON; + else + qsfp->module_t_start_up = T_START_UP; + + return 0; +} + +static void qsfp_sm_mod_remove(struct qsfp *qsfp) +{ + if (qsfp->sm_mod_state > QSFP_MOD_WAITDEV) + qsfp_module_remove(qsfp->qsfp_bus); + + memset(&qsfp->id, 0, sizeof(qsfp->id)); + qsfp->module_power_mw = 0; + + dev_info(qsfp->dev, "module removed\n"); +} + +/* This state machine tracks the upstream's state */ +static void qsfp_sm_device(struct qsfp *qsfp, unsigned int event) +{ + switch (qsfp->sm_dev_state) { + default: + if (event == QSFP_E_DEV_ATTACH) + qsfp->sm_dev_state = QSFP_DEV_DOWN; + break; + case QSFP_DEV_DOWN: + + if (event == QSFP_E_DEV_DETACH) + qsfp->sm_dev_state = QSFP_DEV_DETACHED; + + else if (event == QSFP_E_DEV_UP) + qsfp->sm_dev_state = QSFP_DEV_UP; + break; + + case QSFP_DEV_UP: + + if (event == QSFP_E_DEV_DETACH) + qsfp->sm_dev_state = QSFP_DEV_DETACHED; + else if (event == QSFP_E_DEV_DOWN) + qsfp->sm_dev_state = QSFP_DEV_DOWN; + break; + } +} + +/* This state machine tracks the insert/remove state of the module, probes + * the on-board EEPROM, and sets up the power level. + */ +static void qsfp_sm_module(struct qsfp *qsfp, unsigned int event) +{ + int err; + + /* Handle remove event globally, it resets this state machine */ + if (event == QSFP_E_REMOVE) { + if (qsfp->sm_mod_state > QSFP_MOD_PROBE) + qsfp_sm_mod_remove(qsfp); + qsfp_sm_mod_next(qsfp, QSFP_MOD_EMPTY, 0); + return; + } + + /* Handle device detach globally */ + if (qsfp->sm_dev_state < QSFP_DEV_DOWN && + qsfp->sm_mod_state > QSFP_MOD_WAITDEV) { + if (qsfp->module_power_mw > 1000 && + qsfp->sm_mod_state > QSFP_MOD_HPOWER) + qsfp_sm_mod_hpower(qsfp, false); + qsfp_sm_mod_next(qsfp, QSFP_MOD_WAITDEV, 0); + return; + } + + switch (qsfp->sm_mod_state) { + default: + if (event == QSFP_E_INSERT) { + qsfp_sm_mod_next(qsfp, QSFP_MOD_PROBE, T_SERIAL); + qsfp->sm_mod_tries_init = R_PROBE_RETRY_INIT; + qsfp->sm_mod_tries = R_PROBE_RETRY_SLOW; + } + break; + + case QSFP_MOD_PROBE: + /* Wait for T_PROBE_INIT to time out */ + if (event != QSFP_E_TIMEOUT) + break; + + err = qsfp_sm_mod_probe(qsfp, qsfp->sm_mod_tries == 1); + if (err == -EAGAIN) { + if (qsfp->sm_mod_tries_init && + --qsfp->sm_mod_tries_init) { + qsfp_sm_set_timer(qsfp, T_PROBE_RETRY_INIT); + break; + } else if (qsfp->sm_mod_tries && --qsfp->sm_mod_tries) { + if (qsfp->sm_mod_tries == R_PROBE_RETRY_SLOW - 1) + dev_warn(qsfp->dev, "please wait, module slow to respond\n"); + qsfp_sm_set_timer(qsfp, T_PROBE_RETRY_SLOW); + break; + } + } + if (err < 0) { + qsfp_sm_mod_next(qsfp, QSFP_MOD_ERROR, 0); + break; + } + + fallthrough; + case QSFP_MOD_WAITDEV: + + /* Ensure that the device is attached before proceeding */ + if (qsfp->sm_dev_state < QSFP_DEV_DOWN) + break; + + /* Report the module insertion to the upstream device */ + qsfp->id.base.etile_qsfp_identifier = 0x11; // + err = qsfp_module_insert(qsfp->qsfp_bus, &qsfp->id); + + if (err < 0) { + qsfp_sm_mod_next(qsfp, QSFP_MOD_ERROR, 0); + break; + } + + /* If this is a power level 1 module, we are done */ + if (qsfp->module_power_mw <= 1000) + goto insert; + + qsfp_sm_mod_next(qsfp, QSFP_MOD_HPOWER, 0); + fallthrough; + case QSFP_MOD_HPOWER: + /* Enable high power mode */ + err = qsfp_sm_mod_hpower(qsfp, true); + if (err < 0) { + if (err != -EAGAIN) { + qsfp_module_remove(qsfp->qsfp_bus); + qsfp_sm_mod_next(qsfp, QSFP_MOD_ERROR, 0); + } else { + qsfp_sm_set_timer(qsfp, T_PROBE_RETRY_INIT); + } + break; + } + + qsfp_sm_mod_next(qsfp, QSFP_MOD_WAITPWR, T_HPOWER_LEVEL); + break; + + case QSFP_MOD_WAITPWR: + + /* Wait for T_HPOWER_LEVEL to time out */ + if (event != QSFP_E_TIMEOUT) + break; + +insert: + + qsfp_sm_mod_next(qsfp, QSFP_MOD_PRESENT, 0); + break; + + case QSFP_MOD_PRESENT: + case QSFP_MOD_ERROR: + break; + } +} + +static void qsfp_sm_main(struct qsfp *qsfp, unsigned int event) +{ + unsigned long timeout; + int ret; + + /* Some events are global */ + if (qsfp->sm_state != QSFP_S_DOWN && + (qsfp->sm_mod_state != QSFP_MOD_PRESENT || + qsfp->sm_dev_state != QSFP_DEV_UP)) { + if (qsfp->sm_state == QSFP_S_LINK_UP && + qsfp->sm_dev_state == QSFP_DEV_UP) + qsfp_sm_link_down(qsfp); + if (qsfp->sm_state > QSFP_S_INIT) + qsfp_module_stop(qsfp->qsfp_bus); + if (qsfp->mod_phy) + qsfp_sm_phy_detach(qsfp); + qsfp_module_tx_disable(qsfp); + qsfp_soft_stop_poll(qsfp); + qsfp_sm_next(qsfp, QSFP_S_DOWN, 0); + return; + } + + /* The main state machine */ + switch (qsfp->sm_state) { + case QSFP_S_DOWN: + + if (qsfp->sm_mod_state != QSFP_MOD_PRESENT || + qsfp->sm_dev_state != QSFP_DEV_UP) + break; + + if (!(qsfp->id.base.etile_qsfp_diag_monitor & + QSFP_DIAGMON_ADDRMODE)) + qsfp_soft_start_poll(qsfp); + + qsfp_module_tx_enable(qsfp); + + /* Initialise the fault clearance retries */ + qsfp->sm_fault_retries = N_FAULT_INIT; + + /* We need to check the TX_FAULT state, which is not defined + * while TX_DISABLE is asserted. The earliest we want to do + * anything (such as probe for a PHY) is 50ms. + */ + qsfp_sm_next(qsfp, QSFP_S_WAIT, T_WAIT); + break; + + case QSFP_S_WAIT: + + if (event != QSFP_E_TIMEOUT) + + break; + + if (qsfp->id.base.etile_qsfp_options_3 & + QSFP_OPTIONS_TX_FAULT) { + /* Wait up to t_init (SFF-8472) or t_start_up (SFF-8431) + * from the TX_DISABLE deassertion for the module to + * initialise, which is indicated by TX_FAULT + * deasserting. + */ + timeout = qsfp->module_t_start_up; + if (timeout > T_WAIT) + timeout -= T_WAIT; + else + timeout = 1; + + qsfp_sm_next(qsfp, QSFP_S_INIT, timeout); + } else { + /* TX_FAULT is not asserted, assume the module has + * finished initialising. + */ + goto init_done; + } + break; + + case QSFP_S_INIT: + + if (event == QSFP_E_TIMEOUT && + qsfp->id.base.etile_qsfp_options_3 & + QSFP_OPTIONS_TX_FAULT) { + /* TX_FAULT is still asserted after t_init + * or t_start_up, so assume there is a fault. + */ + qsfp_sm_fault(qsfp, QSFP_S_INIT_TX_FAULT, + qsfp->sm_fault_retries == N_FAULT_INIT); + } else if (event == QSFP_E_TIMEOUT || + event == QSFP_E_TX_CLEAR) { +init_done: + + qsfp->sm_phy_retries = R_PHY_RETRY; + goto phy_probe; + } + break; + + case QSFP_S_INIT_PHY: + + if (event != QSFP_E_TIMEOUT) + break; +phy_probe: + /* TX_FAULT deasserted or we timed out with TX_FAULT + * clear. Probe for the PHY and check the LOS state. + */ + ret = qsfp_sm_probe_for_phy(qsfp); + if (ret == -ENODEV) { + if (--qsfp->sm_phy_retries) { + qsfp_sm_next(qsfp, QSFP_S_INIT_PHY, + T_PHY_RETRY); + dev_info(qsfp->dev, "PHY detected\n"); + break; + } + } else if (ret) { + qsfp_sm_next(qsfp, QSFP_S_FAIL, 0); + break; + } + if (qsfp_module_start(qsfp->qsfp_bus)) { + qsfp_sm_next(qsfp, QSFP_S_FAIL, 0); + break; + } + qsfp_sm_link_check_los(qsfp); + + /* Reset the fault retry count */ + qsfp->sm_fault_retries = N_FAULT; + break; + + case QSFP_S_INIT_TX_FAULT: + + if (event == QSFP_E_TIMEOUT) { + qsfp_module_tx_fault_reset(qsfp); + qsfp_sm_next(qsfp, QSFP_S_INIT, + qsfp->module_t_start_up); + } + + break; + + case QSFP_S_WAIT_LOS: + + if (event == QSFP_E_TX_FAULT) + qsfp_sm_fault(qsfp, QSFP_S_TX_FAULT, true); + else if (qsfp_los_event_inactive(qsfp, event)) + qsfp_sm_link_up(qsfp); + + break; + + case QSFP_S_LINK_UP: + + if (event == QSFP_E_TX_FAULT) { + qsfp_sm_link_down(qsfp); + qsfp_sm_fault(qsfp, QSFP_S_TX_FAULT, true); + } else if (qsfp_los_event_active(qsfp, event)) { + qsfp_sm_link_down(qsfp); + qsfp_sm_next(qsfp, QSFP_S_WAIT_LOS, 0); + } + + break; + + case QSFP_S_TX_FAULT: + + if (event == QSFP_E_TIMEOUT) { + qsfp_module_tx_fault_reset(qsfp); + qsfp_sm_next(qsfp, QSFP_S_REINIT, + qsfp->module_t_start_up); + } + + break; + + case QSFP_S_REINIT: + + if (event == QSFP_E_TIMEOUT && + qsfp->id.base.etile_qsfp_options_3 & + QSFP_OPTIONS_TX_FAULT) { + qsfp_sm_fault(qsfp, QSFP_S_TX_FAULT, false); + } else if (event == QSFP_E_TIMEOUT || + event == QSFP_E_TX_CLEAR) { + dev_info(qsfp->dev, + "module transmit fault recovered\n"); + qsfp_sm_link_check_los(qsfp); + }; + + break; + + case QSFP_S_TX_DISABLE: + + break; + } +} + +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); +} + +void qsfp_attach(struct qsfp *old) +{ + qsfp_sm_event(qsfp, QSFP_E_DEV_ATTACH); +} +EXPORT_SYMBOL(qsfp_attach); + +void qsfp_detach(struct qsfp *old) +{ + qsfp_sm_event(qsfp, QSFP_E_DEV_DETACH); +} +EXPORT_SYMBOL(qsfp_detach); + +void qsfp_start(struct qsfp *old) +{ + qsfp_sm_event(qsfp, QSFP_E_DEV_UP); +} +EXPORT_SYMBOL(qsfp_start); + +void qsfp_stop(struct qsfp *old) +{ + qsfp_sm_event(qsfp, QSFP_E_DEV_DOWN); +} +EXPORT_SYMBOL(qsfp_stop); + +static int qsfp_module_info(struct qsfp *qsfp, struct ethtool_modinfo *modinfo) +{ + /* locking... and check module is present */ + + if (qsfp->id.base.etile_qsfp_spec_compliance_1[0] && + !(qsfp->id.base.etile_qsfp_diag_monitor & QSFP_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 qsfp_module_eeprom(struct qsfp *qsfp, 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(qsfp, 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(qsfp, false, first, data, len); + if (ret < 0) + return ret; + } + return 0; +} + +static const struct qsfp_socket_ops qsfp_module_ops = { + .attach = qsfp_attach, + .detach = qsfp_detach, + .start = qsfp_start, + .stop = qsfp_stop, + .module_info = qsfp_module_info, + .module_eeprom = qsfp_module_eeprom, +}; + +static void qsfp_timeout(struct work_struct *work) +{ + struct qsfp *qsfp = container_of(work, struct qsfp, timeout.work); + + rtnl_lock(); + qsfp_sm_event(qsfp, QSFP_E_TIMEOUT); + rtnl_unlock(); +} + +static void qsfp_check_state(struct qsfp *qsfp) +{ + unsigned int state, i, changed; + + mutex_lock(&qsfp->st_mutex); + state = qsfp_get_state(qsfp); + + changed = state ^ qsfp->state; + changed &= QSFP_F_PRESENT | QSFP_OPTIONS_TX_LOSS_SIGNAL | + QSFP_OPTIONS_TX_FAULT; + + for (i = 0; i < GPIO_MAX; i++) + if (changed & BIT(i)) + dev_dbg(qsfp->dev, "%s %u -> %u\n", gpio_of_names[i], + !!(qsfp->state & BIT(i)), !!(state & BIT(i))); + + qsfp->state = state; + + rtnl_lock(); + if (changed & QSFP_F_PRESENT) + qsfp_sm_event(qsfp, state & QSFP_F_PRESENT ? QSFP_E_INSERT : + QSFP_E_REMOVE); + + if (changed & QSFP_OPTIONS_TX_FAULT) + qsfp_sm_event(qsfp, state & QSFP_OPTIONS_TX_FAULT ? + QSFP_E_TX_FAULT : + QSFP_E_TX_CLEAR); + + if (changed & QSFP_OPTIONS_TX_LOSS_SIGNAL) + qsfp_sm_event(qsfp, state & QSFP_OPTIONS_TX_LOSS_SIGNAL ? + QSFP_E_TX_LOS : + QSFP_E_RX_LOS); + + rtnl_unlock(); + mutex_unlock(&qsfp->st_mutex); +} + +static irqreturn_t qsfp_irq(int irq, void *data) +{ + struct qsfp *qsfp = data; + + qsfp_check_state(qsfp); + + return IRQ_HANDLED; +} + +static void qsfp_poll(struct work_struct *work) +{ + struct qsfp *qsfp = container_of(work, struct qsfp, poll.work); + + qsfp_check_state(qsfp); + + qsfp_state_indicators(qsfp); + qsfp_status_indicators(qsfp); + + if (qsfp->state_soft_mask & + (QSFP_OPTIONS_TX_LOSS_SIGNAL | QSFP_OPTIONS_TX_FAULT) || + qsfp->need_poll) + mod_delayed_work(system_wq, &qsfp->poll, poll_jiffies); +} + +static struct qsfp *qsfp_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, qsfp_timeout); + + return qsfp; +} + +static void qsfp_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) +{ + const struct sff_data *sff; + struct i2c_adapter *i2c; + char *qsfp_irq_name; + int err, i; + static void *reg_addr; + u32 reg_value; + + qsfp = qsfp_alloc(&pdev->dev); + + if (IS_ERR(qsfp)) + return PTR_ERR(qsfp); + + platform_set_drvdata(pdev, qsfp); + + err = devm_add_action(qsfp->dev, qsfp_cleanup, qsfp); + if (err < 0) + return err; + + qsfp->type = &qsfp_data; + sff = qsfp->type; + + 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; + + qsfp->type = id->data; + sff = qsfp->type; + + reg_addr = ioremap(0xffd11000, 0x40); + reg_addr += 0x28; + reg_value = readl(reg_addr); + //bring out from reset by writing to bit 8// + reg_value &= 0xfffffeff; + writel(reg_value, reg_addr); + + 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; + } + qsfp_reset(qsfp, 0); + + for (i = 0; i < GPIO_MAX; i++) + if (sff->gpios & BIT(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]); + } + + qsfp->get_state = qsfp_gpio_get_state; + qsfp->set_state = qsfp_gpio_set_state; + + qsfp_reset(qsfp, 1); + + /* Modules that have no detect signal are always present */ + if (!(qsfp->gpio[GPIO_MODULE_PRESENT])) + qsfp->get_state = sff_gpio_get_state; + + device_property_read_u32(&pdev->dev, "maximum-power-milliwatt", + &qsfp->max_power_mw); + if (!qsfp->max_power_mw) + qsfp->max_power_mw = 1000; + + qsfp_set_state(qsfp, qsfp->state | QSFP_SELECT | QSFP_F_PRESENT); + + qsfp->state = qsfp_get_state(qsfp) | QSFP_OPTIONS_TX_DISABLE; + + if (qsfp->state & QSFP_F_PRESENT) { + qsfp->state |= QSFP_SELECT; + + qsfp->state = qsfp_get_state(qsfp) | QSFP_OPTIONS_TX_DISABLE; + + qsfp_set_state(qsfp, qsfp->state); + rtnl_lock(); + qsfp_sm_event(qsfp, QSFP_E_INSERT); + + rtnl_unlock(); + } else { + pr_info("qsfp is not present\n"); + } + + qsfp->gpio_irq[GPIO_MODULE_INTERRUPT] = + gpiod_to_irq(qsfp->gpio[GPIO_MODULE_INTERRUPT]); + if (qsfp->gpio_irq[GPIO_MODULE_INTERRUPT] < 0) { + qsfp->gpio_irq[GPIO_MODULE_INTERRUPT] = 0; + qsfp->need_poll = true; + } + + qsfp_irq_name = devm_kasprintf(qsfp->dev, GFP_KERNEL, "%s-%s", + dev_name(qsfp->dev), + gpio_of_names[GPIO_MODULE_INTERRUPT]); + + if (!qsfp_irq_name) + return -ENOMEM; + + err = devm_request_threaded_irq(qsfp->dev, qsfp->gpio_irq[GPIO_MODULE_INTERRUPT], + NULL, qsfp_irq, + IRQF_ONESHOT | + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + qsfp_irq_name, qsfp); + + if (err) { + qsfp->gpio_irq[GPIO_MODULE_INTERRUPT] = 0; + qsfp->need_poll = true; + } + + if (qsfp->need_poll) + mod_delayed_work(system_wq, &qsfp->poll, poll_jiffies); + + /* We could have an issue in cases no Tx disable pin is available or + * wired as modules using a laser as their light source will continue to + * be active when the fiber is removed. This could be a safety issue and + * we should at least warn the user about that. + */ + if (qsfp->id.base.etile_qsfp_options_3 & QSFP_OPTIONS_TX_DISABLE) + dev_warn(qsfp->dev, "No tx_disable pin: qsfp modules will always be emitting.\n"); + + qsfp->qsfp_bus = + qsfp_register_socket(qsfp->dev, qsfp, &qsfp_module_ops); + if (!qsfp->qsfp_bus) + return -ENOMEM; + + get_module_revision(qsfp); + + return 0; +} + +static int qsfp_remove(struct platform_device *pdev) + +{ + struct qsfp *qsfp = platform_get_drvdata(pdev); + + qsfp_unregister_socket(qsfp->qsfp_bus); + + rtnl_lock(); + qsfp_sm_event(qsfp, QSFP_E_REMOVE); + rtnl_unlock(); + + return 0; +} + +static struct platform_driver qsfp_driver = { + .probe = qsfp_probe, + .remove = qsfp_remove, + .driver = { + .name = "sff,qsfp", + .owner = THIS_MODULE, + .of_match_table = qsfp_of_match, + }, +}; + +static int qsfp_init(void) +{ + poll_jiffies = msecs_to_jiffies(1000); + + 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("Malku Deepak"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/qsfp_bus.c b/drivers/net/phy/qsfp_bus.c new file mode 100644 index 000000000000..cd46c4f7ba13 --- /dev/null +++ b/drivers/net/phy/qsfp_bus.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/export.h> +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/phylink.h> +#include <linux/property.h> +#include <linux/rtnetlink.h> +#include <linux/slab.h> +#include <linux/qsfp.h> + + + +struct qsfp_quirk { + const char *vendor; + const char *part; + void (*modes)(const struct qsfp_eeprom_id *id, unsigned long *modes); +}; + +/** + * struct qsfp_bus - internal representation of a qsfp bus + */ +struct qsfp_bus { + /* private: */ + struct kref kref; + struct list_head node; + struct fwnode_handle *fwnode; + + const struct qsfp_socket_ops *socket_ops; + struct device *qsfp_dev; + struct qsfp *qsfp; + const struct qsfp_quirk *qsfp_quirk; + + const struct qsfp_upstream_ops *upstream_ops; + void *upstream; + struct phy_device *phydev; + + bool registered; + bool started; +}; + +static void qsfp_quirk_2500basex(const struct qsfp_eeprom_id *id, + unsigned long *modes) +{ + phylink_set(modes, 2500baseX_Full); +} + +static void qsfp_quirk_ubnt_uf_instant(const struct qsfp_eeprom_id *id, + unsigned long *modes) +{ + /* Ubiquiti U-Fiber Instant module claims that support all transceiver + * types including 10G Ethernet which is not truth. So clear all claimed + * modes and set only one mode which module supports: 1000baseX_Full. + */ + phylink_zero(modes); + phylink_set(modes, 1000baseX_Full); +} + +static const struct qsfp_quirk qsfp_quirks[] = { + { + // Alcatel Lucent G-010S-P can operate at 2500base-X, but + // incorrectly report 2500MBd NRZ in their EEPROM + .vendor = "ALCATELLUCENT", + .part = "G010SP", + .modes = qsfp_quirk_2500basex, + }, + { + // Alcatel Lucent G-010S-A can operate at 2500base-X, but + // report 3.2GBd NRZ in their EEPROM + .vendor = "ALCATELLUCENT", + .part = "3FE46541AA", + .modes = qsfp_quirk_2500basex, + }, + { + // Huawei MA5671A can operate at 2500base-X, but report 1.2GBd + // NRZ in their EEPROM + .vendor = "HUAWEI", + .part = "MA5671A", + .modes = qsfp_quirk_2500basex, + }, + { + .vendor = "UBNT", + .part = "UF-INSTANT", + .modes = qsfp_quirk_ubnt_uf_instant, + }, +}; + +static size_t qsfp_strlen(const char *str, size_t maxlen) +{ + size_t size, i; + + /* Trailing characters should be filled with space chars */ + for (i = 0, size = 0; i < maxlen; i++) + if (str[i] != ' ') + size = i + 1; + + return size; +} + +static bool qsfp_match(const char *qs, const char *str, size_t len) +{ + if (!qs) + return true; + if (strlen(qs) != len) + return false; + return !strncmp(qs, str, len); +} + +static const struct qsfp_quirk * +qsfp_lookup_quirk(const struct qsfp_eeprom_id *id) +{ + const struct qsfp_quirk *q; + unsigned int i; + size_t vs, ps; + + vs = qsfp_strlen(id->base.etile_qsfp_vendor_name, + ARRAY_SIZE(id->base.etile_qsfp_vendor_name)); + ps = qsfp_strlen(id->base.etile_qsfp_vendor_pn, + ARRAY_SIZE(id->base.etile_qsfp_vendor_pn)); + + for (i = 0, q = qsfp_quirks; i < ARRAY_SIZE(qsfp_quirks); i++, q++) + if (qsfp_match(q->vendor, id->base.etile_qsfp_vendor_name, + vs) && + qsfp_match(q->part, id->base.etile_qsfp_vendor_pn, ps)) + return q; + + return NULL; +} + +/** + * qsfp_parse_port() - Parse the EEPROM base ID, setting the port type + * @bus: a pointer to the &struct qsfp_bus structure for the qsfp module + * @id: a pointer to the module's &struct qsfp_eeprom_id + * @support: optional pointer to an array of unsigned long for the + * ethtool support mask + * + * Parse the EEPROM identification given in @id, and return one of + * %PORT_TP, %PORT_FIBRE or %PORT_OTHER. If @support is non-%NULL, + * also set the ethtool %ETHTOOL_LINK_MODE_xxx_BIT corresponding with + * the connector type. + * + * If the port type is not known, returns %PORT_OTHER. + */ + +int qsfp_parse_port(struct qsfp_bus *bus, const struct qsfp_eeprom_id *id, + unsigned long *support) +{ + int port; + + /* port is the physical connector, set this from the connector field. */ + switch (id->base.etile_qsfp_connector_type) { + case SFF8024_QSFP_DD_CONNECTOR_SC: + case SFF8024_QSFP_DD_CONNECTOR_FIBERJACK: + case SFF8024_QSFP_DD_CONNECTOR_LC: + case SFF8024_QSFP_DD_CONNECTOR_MT_RJ: + case SFF8024_QSFP_DD_CONNECTOR_MU: + case SFF8024_QSFP_DD_CONNECTOR_OPTICAL_PIGTAIL: + case SFF8024_QSFP_DD_CONNECTOR_MPO_1X12: + case SFF8024_QSFP_DD_CONNECTOR_MPO_2X16: + port = PORT_FIBRE; + break; + + case SFF8024_QSFP_DD_CONNECTOR_RJ45: + port = PORT_TP; + break; + + case SFF8024_QSFP_DD_CONNECTOR_COPPER_PIGTAIL: + port = PORT_DA; + break; + + case SFF8024_QSFP_DD_CONNECTOR_UNSPEC: + { + port = PORT_TP; + break; + } + fallthrough; + case SFF8024_QSFP_DD_CONNECTOR_SG: /* guess */ + case SFF8024_QSFP_DD_CONNECTOR_HSSDC_II: + case SFF8024_QSFP_DD_CONNECTOR_NOSEPARATE: + case SFF8024_QSFP_DD_CONNECTOR_MXC_2X16: + port = PORT_OTHER; + break; + default: + dev_warn(bus->qsfp_dev, "qsfp: unknown connector id 0x%02x\n", + id->base.etile_qsfp_connector_type); + port = PORT_OTHER; + break; + } + + if (support) { + switch (port) { + case PORT_FIBRE: + phylink_set(support, FIBRE); + break; + + case PORT_TP: + phylink_set(support, TP); + break; + } + } + + return port; +} +EXPORT_SYMBOL_GPL(qsfp_parse_port); + +/** + * qsfp_may_have_phy() - indicate whether the module may have a PHY + * @bus: a pointer to the &struct qsfp_bus structure for the qsfp module + * @id: a pointer to the module's &struct qsfp_eeprom_id + * + * Parse the EEPROM identification given in @id, and return whether + * this module may have a PHY. + */ + +bool qsfp_may_have_phy(struct qsfp_bus *bus, const struct qsfp_eeprom_id *id) +{ + if (id->base.etile_qsfp_identifier != SFF8024_ID_QSFP_DD_INF_8628) { + switch (id->base.etile_qsfp_spec_compliance_1[0]) { + case SFF8636_QSFP_DD_ECC_100GBASE_CR4: + case SFF8636_QSFP_DD_ECC_CAUI4: + + return true; + } + } + + return false; +} +EXPORT_SYMBOL_GPL(qsfp_may_have_phy); + +/** + * qsfp_parse_support() - Parse the eeprom id for supported link modes + * @bus: a pointer to the &struct qsfp_bus structure for the qsfp module + * @id: a pointer to the module's &struct qsfp_eeprom_id + * @support: pointer to an array of unsigned long for the ethtool support mask + * + * Parse the EEPROM identification information and derive the supported + * ethtool link modes for the module. + */ + +void qsfp_parse_support(struct qsfp_bus *bus, const struct qsfp_eeprom_id *id, + unsigned long *support) +{ + unsigned int etile_qsfp_br_nom, etile_qsfp_br_max, etile_qsfp_br_min; + __ETHTOOL_DECLARE_LINK_MODE_MASK(modes) = { + 0, + }; + + /* Decode the bitrate information to MBd */ + etile_qsfp_br_min = 0; + etile_qsfp_br_nom = 0; + etile_qsfp_br_max = 0; + if (id->base.etile_qsfp_br_nom) { + if (id->base.etile_qsfp_br_nom != 255) { + etile_qsfp_br_nom = id->base.etile_qsfp_br_nom * 100; + etile_qsfp_br_min = etile_qsfp_br_nom - + id->base.etile_qsfp_br_nom * + id->base.etile_qsfp_br_min; + etile_qsfp_br_max = etile_qsfp_br_nom + + id->base.etile_qsfp_br_nom * + id->base.etile_qsfp_br_max; + } else if (id->base.etile_qsfp_br_max) { + etile_qsfp_br_nom = 250 * id->base.etile_qsfp_br_max; + etile_qsfp_br_max = etile_qsfp_br_nom + + etile_qsfp_br_nom * + id->base.etile_qsfp_br_min / + 100; + etile_qsfp_br_min = etile_qsfp_br_nom - + etile_qsfp_br_nom * + id->base.etile_qsfp_br_min / + 100; + } + + /* When using passive cables, in case neither BR,min nor BR,max + * are specified, set etile_qsfp_br_min to 0 as the nominal value is then + * used as the maximum. + */ + } + + /* Set ethtool support from the compliance fields. */ + if (id->base.etile_qsfp_spec_compliance_1) + phylink_set(modes, 10000baseSR_Full); + if (id->base.etile_qsfp_spec_compliance_1) + phylink_set(modes, 10000baseLR_Full); + + switch (id->base.etile_qsfp_spec_compliance_1[0]) { + case SFF8636_QSFP_DD_ECC_100GBASE_CR4: + break; + case SFF8636_QSFP_DD_ECC_CAUI4: + phylink_set(modes, 100000baseSR4_Full); + phylink_set(modes, 25000baseSR_Full); + break; + + default: + dev_warn(bus->qsfp_dev, + "Unknown/unsupported extended compliance code: 0x%02x\n", + id->base.etile_qsfp_spec_compliance_1[0]); + break; + } + + /* If we haven't discovered any modes that this module supports, try + * the bitrate to determine supported modes. Some BiDi modules (eg, + * 1310nm/1550nm) are not 1000BASE-BX compliant due to the differing + * wavelengths, so do not set any transceiver bits. + */ + if (bitmap_empty(modes, __ETHTOOL_LINK_MODE_MASK_NBITS)) { + /* If the bit rate allows 1000baseX */ + if (etile_qsfp_br_nom && etile_qsfp_br_min <= 1300 && + etile_qsfp_br_max >= 1200) + phylink_set(modes, 1000baseX_Full); + } + + if (bus->qsfp_quirk) + bus->qsfp_quirk->modes(id, modes); + + bitmap_or(support, support, modes, __ETHTOOL_LINK_MODE_MASK_NBITS); + + phylink_set(support, Autoneg); + phylink_set(support, Pause); + phylink_set(support, Asym_Pause); +} +EXPORT_SYMBOL_GPL(qsfp_parse_support); + +/** + * qsfp_select_interface() - Select appropriate phy_interface_t mode + * @bus: a pointer to the &struct qsfp_bus structure for the qsfp module + * @link_modes: ethtool link modes mask + * + * Derive the phy_interface_t mode for the qsfp module from the link + * modes mask. + */ +phy_interface_t qsfp_select_interface(struct qsfp_bus *bus, + unsigned long *link_modes) +{ + if (phylink_test(link_modes, 10000baseCR_Full) || + phylink_test(link_modes, 10000baseSR_Full) || + phylink_test(link_modes, 10000baseLR_Full) || + phylink_test(link_modes, 10000baseLRM_Full) || + phylink_test(link_modes, 10000baseER_Full) || + phylink_test(link_modes, 10000baseT_Full)) + return PHY_INTERFACE_MODE_10GBASER; + + if (phylink_test(link_modes, 2500baseX_Full)) + return PHY_INTERFACE_MODE_2500BASEX; + + if (phylink_test(link_modes, 1000baseT_Half) || + phylink_test(link_modes, 1000baseT_Full)) + return PHY_INTERFACE_MODE_SGMII; + + if (phylink_test(link_modes, 1000baseX_Full)) + return PHY_INTERFACE_MODE_1000BASEX; + + dev_warn(bus->qsfp_dev, "Unable to ascertain link mode\n"); + + return PHY_INTERFACE_MODE_NA; +} +EXPORT_SYMBOL_GPL(qsfp_select_interface); + +static LIST_HEAD(qsfp_buses); +static DEFINE_MUTEX(qsfp_mutex); + +static const struct qsfp_upstream_ops * +qsfp_get_upstream_ops(struct qsfp_bus *bus) +{ + return bus->registered ? bus->upstream_ops : NULL; +} + +static struct qsfp_bus *qsfp_bus_get(struct fwnode_handle *fwnode) +{ + struct qsfp_bus *qsfp, *new, *found = NULL; + + new = kzalloc(sizeof(*new), GFP_KERNEL); + + mutex_lock(&qsfp_mutex); + + list_for_each_entry(qsfp, &qsfp_buses, node) { + if (qsfp->fwnode == fwnode) { + kref_get(&qsfp->kref); + found = qsfp; + break; + } + } + + if (!found && new) { + kref_init(&new->kref); + new->fwnode = fwnode; + list_add(&new->node, &qsfp_buses); + found = new; + new = NULL; + } + + mutex_unlock(&qsfp_mutex); + + kfree(new); + + return found; +} + +static void qsfp_bus_release(struct kref *kref) +{ + struct qsfp_bus *bus = container_of(kref, struct qsfp_bus, kref); + + list_del(&bus->node); + mutex_unlock(&qsfp_mutex); + kfree(bus); +} + +/** + * qsfp_bus_put() - put a reference on the &struct qsfp_bus + * @bus: the &struct qsfp_bus found via qsfp_bus_find_fwnode() + * + * Put a reference on the &struct qsfp_bus and free the underlying structure + * if this was the last reference. + */ +void qsfp_bus_put(struct qsfp_bus *bus) +{ + if (bus) + kref_put_mutex(&bus->kref, qsfp_bus_release, &qsfp_mutex); +} +EXPORT_SYMBOL_GPL(qsfp_bus_put); + +static int qsfp_register_bus(struct qsfp_bus *bus) +{ + const struct qsfp_upstream_ops *ops = bus->upstream_ops; + int ret; + + pr_info("qsfp register bus\n"); + + if (ops) { + if (ops->link_down) + ops->link_down(bus->upstream); + if (ops->connect_phy /*&& bus->phydev*/) { + ret = ops->connect_phy(bus->upstream, bus->phydev); + if (ret) + return ret; + } + } + bus->registered = true; + + bus->socket_ops->attach(bus->qsfp); + if (bus->started) + bus->socket_ops->start(bus->qsfp); + bus->upstream_ops->attach(bus->upstream, bus); + return 0; +} + +static void qsfp_unregister_bus(struct qsfp_bus *bus) +{ + const struct qsfp_upstream_ops *ops = bus->upstream_ops; + + if (bus->registered) { + bus->upstream_ops->detach(bus->upstream, bus); + if (bus->started) + bus->socket_ops->stop(bus->qsfp); + bus->socket_ops->detach(bus->qsfp); + if (bus->phydev && ops && ops->disconnect_phy) + ops->disconnect_phy(bus->upstream); + } + bus->registered = false; +} + +/** + * qsfp_get_module_info() - Get the ethtool_modinfo for a qsfp module + * @bus: a pointer to the &struct qsfp_bus structure for the qsfp module + * @modinfo: a &struct ethtool_modinfo + * + * Fill in the type and eeprom_len parameters in @modinfo for a module on + * the qsfp bus specified by @bus. + * + * Returns 0 on success or a negative errno number. + */ +int qsfp_get_module_info(struct qsfp_bus *bus, struct ethtool_modinfo *modinfo) +{ + return bus->socket_ops->module_info(bus->qsfp, modinfo); +} +EXPORT_SYMBOL_GPL(qsfp_get_module_info); + +/** + * qsfp_get_module_eeprom() - Read the qsfp module EEPROM + * @bus: a pointer to the &struct qsfp_bus structure for the qsfp module + * @ee: a &struct ethtool_eeprom + * @data: buffer to contain the EEPROM data (must be at least @ee->len bytes) + * + * Read the EEPROM as specified by the supplied @ee. See the documentation + * for &struct ethtool_eeprom for the region to be read. + * + * Returns 0 on success or a negative errno number. + */ +int qsfp_get_module_eeprom(struct qsfp_bus *bus, struct ethtool_eeprom *ee, + u8 *data) +{ + return bus->socket_ops->module_eeprom(bus->qsfp, ee, data); +} +EXPORT_SYMBOL_GPL(qsfp_get_module_eeprom); + +/** + * qsfp_upstream_start() - Inform the qsfp that the network device is up + * @bus: a pointer to the &struct qsfp_bus structure for the qsfp module + * + * Inform the qsfp socket that the network device is now up, so that the + * module can be enabled by allowing TX_DISABLE to be deasserted. This + * should be called from the network device driver's &struct net_device_ops + * ndo_open() method. + */ +void qsfp_upstream_start(struct qsfp_bus *bus) +{ + if (bus->registered) + bus->socket_ops->start(bus->qsfp); + bus->started = true; +} +EXPORT_SYMBOL_GPL(qsfp_upstream_start); + +/** + * qsfp_upstream_stop() - Inform the qsfp that the network device is down + * @bus: a pointer to the &struct qsfp_bus structure for the qsfp module + * + * Inform the qsfp socket that the network device is now up, so that the + * module can be disabled by asserting TX_DISABLE, disabling the laser + * in optical modules. This should be called from the network device + * driver's &struct net_device_ops ndo_stop() method. + */ +void qsfp_upstream_stop(struct qsfp_bus *bus) +{ + if (bus->registered) + bus->socket_ops->stop(bus->qsfp); + bus->started = false; +} +EXPORT_SYMBOL_GPL(qsfp_upstream_stop); + +static void qsfp_upstream_clear(struct qsfp_bus *bus) +{ + bus->upstream_ops = NULL; + bus->upstream = NULL; +} + +/** + * qsfp_bus_find_fwnode() - parse and locate the qsfp bus from fwnode + * @fwnode: firmware node for the parent device (MAC or PHY) + * + * Parse the parent device's firmware node for a qsfp bus, and locate + * the qsfp_bus structure, incrementing its reference count. This must + * be put via qsfp_bus_put() when done. + * + * Returns: + * - on success, a pointer to the qsfp_bus structure, + * - %NULL if no qsfp is specified, + * - on failure, an error pointer value: + * - corresponding to the errors detailed for + * fwnode_property_get_reference_args(). + * - %-ENOMEM if we failed to allocate the bus. + * - an error from the upstream's connect_phy() method. + */ +struct qsfp_bus *qsfp_bus_find_fwnode(struct fwnode_handle *fwnode) +{ + struct fwnode_reference_args ref; + struct qsfp_bus *bus; + int ret; + + ret = fwnode_property_get_reference_args(fwnode, "qsfp", NULL, 0, 0, + &ref); + if (ret == -ENOENT) + return NULL; + else if (ret < 0) + return ERR_PTR(ret); + + bus = qsfp_bus_get(ref.fwnode); + fwnode_handle_put(ref.fwnode); + if (!bus) + return ERR_PTR(-ENOMEM); + + return bus; +} +EXPORT_SYMBOL_GPL(qsfp_bus_find_fwnode); + +/** + * qsfp_bus_add_upstream() - parse and register the neighbouring device + * @bus: the &struct qsfp_bus found via qsfp_bus_find_fwnode() + * @upstream: the upstream private data + * @ops: the upstream's &struct qsfp_upstream_ops + * + * Add upstream driver for the qsfp bus, and if the bus is complete, register + * the qsfp bus using qsfp_register_upstream(). This takes a reference on the + * bus, so it is safe to put the bus after this call. + * + * Returns: + * - on success, a pointer to the qsfp_bus structure, + * - %NULL if no qsfp is specified, + * - on failure, an error pointer value: + * - corresponding to the errors detailed for + * fwnode_property_get_reference_args(). + * - %-ENOMEM if we failed to allocate the bus. + * - an error from the upstream's connect_phy() method. + */ +int qsfp_bus_add_upstream(struct qsfp_bus *bus, void *upstream, + const struct qsfp_upstream_ops *ops) +{ + int ret; + + /* If no bus, return success */ + if (!bus) + return 0; + + rtnl_lock(); + kref_get(&bus->kref); + bus->upstream_ops = ops; + bus->upstream = upstream; + + if (bus->qsfp) { + ret = qsfp_register_bus(bus); + if (ret) + qsfp_upstream_clear(bus); + } else { + ret = 0; + } + rtnl_unlock(); + + if (ret) + qsfp_bus_put(bus); + + return ret; +} +EXPORT_SYMBOL_GPL(qsfp_bus_add_upstream); + +/** + * qsfp_bus_del_upstream() - Delete a qsfp bus + * @bus: a pointer to the &struct qsfp_bus structure for the qsfp module + * + * Delete a previously registered upstream connection for the qsfp + * module. @bus should have been added by qsfp_bus_add_upstream(). + */ +void qsfp_bus_del_upstream(struct qsfp_bus *bus) +{ + if (bus) { + rtnl_lock(); + if (bus->qsfp) + qsfp_unregister_bus(bus); + qsfp_upstream_clear(bus); + rtnl_unlock(); + + qsfp_bus_put(bus); + } +} +EXPORT_SYMBOL_GPL(qsfp_bus_del_upstream); + +/* Socket driver entry points */ +int qsfp_add_phy(struct qsfp_bus *bus, struct phy_device *phydev) +{ + const struct qsfp_upstream_ops *ops = qsfp_get_upstream_ops(bus); + int ret = 0; + + if (ops && ops->connect_phy) + ret = ops->connect_phy(bus->upstream, phydev); + + if (ret == 0) + bus->phydev = phydev; + + return ret; +} +EXPORT_SYMBOL_GPL(qsfp_add_phy); + +void qsfp_remove_phy(struct qsfp_bus *bus) +{ + const struct qsfp_upstream_ops *ops = qsfp_get_upstream_ops(bus); + + if (ops && ops->disconnect_phy) + ops->disconnect_phy(bus->upstream); + bus->phydev = NULL; +} +EXPORT_SYMBOL_GPL(qsfp_remove_phy); + +void qsfp_link_up(struct qsfp_bus *bus) +{ + const struct qsfp_upstream_ops *ops = qsfp_get_upstream_ops(bus); + + if (ops && ops->link_up) + ops->link_up(bus->upstream); +} +EXPORT_SYMBOL_GPL(qsfp_link_up); + +void qsfp_link_down(struct qsfp_bus *bus) +{ + const struct qsfp_upstream_ops *ops = qsfp_get_upstream_ops(bus); + + if (ops && ops->link_down) + ops->link_down(bus->upstream); +} +EXPORT_SYMBOL_GPL(qsfp_link_down); + +int qsfp_module_insert(struct qsfp_bus *bus, const struct qsfp_eeprom_id *id) +{ + const struct qsfp_upstream_ops *ops = qsfp_get_upstream_ops(bus); + int ret = 0; + + pr_info("Tessolve module insert\n"); + + bus->qsfp_quirk = qsfp_lookup_quirk(id); + + if (ops && ops->module_insert) + ret = ops->module_insert(bus->upstream, id); + + return ret; +} +EXPORT_SYMBOL_GPL(qsfp_module_insert); + +void qsfp_module_remove(struct qsfp_bus *bus) +{ + const struct qsfp_upstream_ops *ops = qsfp_get_upstream_ops(bus); + + if (ops && ops->module_remove) + ops->module_remove(bus->upstream); + + bus->qsfp_quirk = NULL; +} +EXPORT_SYMBOL_GPL(qsfp_module_remove); + +int qsfp_module_start(struct qsfp_bus *bus) +{ + const struct qsfp_upstream_ops *ops = qsfp_get_upstream_ops(bus); + int ret = 0; + + if (ops && ops->module_start) + ret = ops->module_start(bus->upstream); + + return ret; +} +EXPORT_SYMBOL_GPL(qsfp_module_start); + +void qsfp_module_stop(struct qsfp_bus *bus) +{ + const struct qsfp_upstream_ops *ops = qsfp_get_upstream_ops(bus); + + if (ops && ops->module_stop) + ops->module_stop(bus->upstream); +} +EXPORT_SYMBOL_GPL(qsfp_module_stop); + +static void qsfp_socket_clear(struct qsfp_bus *bus) +{ + bus->qsfp_dev = NULL; + bus->qsfp = NULL; + bus->socket_ops = NULL; +} + +struct qsfp_bus *qsfp_register_socket(struct device *dev, struct qsfp *qsfp, + const struct qsfp_socket_ops *ops) +{ + struct qsfp_bus *bus = qsfp_bus_get(dev->fwnode); + int ret = 0; + + if (bus) { + rtnl_lock(); + bus->qsfp_dev = dev; + bus->qsfp = qsfp; + bus->socket_ops = ops; + + if (bus->upstream_ops) { + ret = qsfp_register_bus(bus); + if (ret) + qsfp_socket_clear(bus); + } + rtnl_unlock(); + } + + if (ret) { + qsfp_bus_put(bus); + bus = NULL; + } + + return bus; +} +EXPORT_SYMBOL_GPL(qsfp_register_socket); + +void qsfp_unregister_socket(struct qsfp_bus *bus) +{ + rtnl_lock(); + if (bus->upstream_ops) + qsfp_unregister_bus(bus); + qsfp_socket_clear(bus); + rtnl_unlock(); + + qsfp_bus_put(bus); +} +EXPORT_SYMBOL_GPL(qsfp_unregister_socket); diff --git a/include/linux/qsfp.h b/include/linux/qsfp.h new file mode 100644 index 000000000000..72a819eed32d --- /dev/null +++ b/include/linux/qsfp.h @@ -0,0 +1,400 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Intel QSFP_MODULE_DRIVER + * Copyright (C) 2020-2021 Intel Corporation. All rights reserved. + * + * Contributors: + * Malku + * Deepak + * Original driver contributed by Intel. + */ +#ifndef LINUX_QSFP_H +#define LINUX_QSFP_H + +#include <linux/phy.h> +#include <linux/ethtool.h> + +struct qsfp; +struct qsfp_eeprom_base { + u8 etile_qsfp_identifier; //0x00 00 + u8 etile_qsfp_revision; //0x01 01 + u8 etile_qsfp_status; //0x02 02 + u8 etile_qsfp_interrupt_flags[19]; //0x03 03 + u8 etile_qsfp_device_monitors[12]; //0x16 22 + u8 etile_qsfp_channel_monitors[48]; //0x22 34 + u8 RESERVED_0[4]; //0x52 82 + u8 etile_qsfp_control[13]; //0x56 86 + u8 RESERVED_1; //0x63 99 + u8 etile_qsfp_device_channel_masks[5]; //0x64 100 + u8 etile_qsfp_vendor_specific[2]; //0x69 105 + u8 RESERVED_2; //0x6b 107 + u8 etile_device_properties_1[3]; //0x6c 108 + u8 etile_pci_express[2]; //0x6f 111 + u8 etile_device_properties_2[2]; //0x71 113 + u8 RESERVED_3[4]; //0x74 115 + u8 etile_qsfp_password_change[4]; //0x77 119 + u8 etile_qsfp_password_entry_area[4]; //0x7b 123 + u8 etile_qsfp_page_select_byte; //0x7f 127 + u8 etile_qsfp_identifier_1; //0x80 128 + u8 etile_qsfp_ext_identifier; //0x81 129 + u8 etile_qsfp_connector_type; //0x82 130 + u8 etile_qsfp_spec_compliance_1[8]; //0x83 131 + u8 etile_qsfp_encoding; //0x8b 139 + u8 etile_qsfp_br_nom; //0x8c 140 + u8 etile_qsfp_ext_compliance; //0x8d 141 + u8 etile_qsfp_link_lenghth_1; //0x8e 142 + u8 etile_qsfp_link_lenghth_2; //0x8f 143 + u8 etile_qsfp_link_lenghth_3; //0x90 144 + u8 etile_qsfp_link_lenghth_4; //0x91 145 + u8 etile_qsfp_link_lenghth_5; //0x92 146 + u8 etile_qsfp_device_technology; //0x93 147 + char etile_qsfp_vendor_name[16]; //0x94 148 + u8 etile_qsfp_extended_module; //0xa4 164 + char etile_qsfp_vendor_oui[3]; //0xa5 165 + char etile_qsfp_vendor_pn[16]; //0xa8 168 + char etile_qsfp_vendor_rev[2]; //0xb8 184 + u8 etile_qsfp_wavelength_copper[2]; //0xba 186 + u8 etile_qsfp_wavelength_tolerance[2]; //0xbc 188 + u8 etile_qsfp_max_case_temp[1]; //0xbe 190 + u8 etile_qsfp_cc_base; //0xbf 191 + u8 etile_qsfp_ext_spec_compliance; //0xc0 192 + u8 etile_qsfp_options_1; //0xc1 193 + u8 etile_qsfp_options_2; //0xc2 194 + u8 etile_qsfp_options_3; //0xc3 195 + char etile_qsfp_vendor_serial_number[16]; //0xc4 196 + char etile_qsfp_vendor_date_code[8]; //0xd4 212 + u8 etile_qsfp_diag_monitor; //0xdc 220 + u8 etile_qsfp_enhanced_options; //0xdd 221 + u8 etile_qsfp_br_nom_1; //0xde 222 + u8 etile_qsfp_cc_ext; //0xdf 223 + u8 etile_qsfp_venodor_specific_id[32]; //0xe0 224 + u8 etile_qsfp_br_max; //TBD + u8 etile_qsfp_br_min; //TBD + +} __packed; + + + /** + * struct qsfp_eeprom_id - raw qsfp module identification information + * @base: base qsfp module identification structure + * @ext: extended qsfp module identification structure + * + * See the SFF-8472 specification and related documents for the definition + * of these structure members. This can be obtained from + * https://www.snia.org/technology-communities/sff/specifications + */ +struct qsfp_eeprom_id { + struct qsfp_eeprom_base base; + +} __packed; + + + +enum { + + SFF8024_ID_QSFP_DD_INF_8628 = 0x18, + SFF8024_QSFP_DD_ENCODING_UNSPEC = 0x00, + SFF8024_QSFP_DD_ENCODING_8B10B = 0x01, + SFF8024_QSFP_DD_ENCODING_4B5B = 0x02, + SFF8024_QSFP_DD_ENCODING_NRZ = 0x03, + SFF8024_QSFP_DD_ENCODING_8436_SONET = 0x04, + SFF8024_QSFP_DD_ENCODING_8436_64B66B = 0x05, + SFF8024_QSFP_DD_ENCODING_8436_MANCHESTER = 0x06, + SFF8024_QSFP_DD_ENCODING_256B257B = 0x07, + SFF8024_QSFP_DD_ENCODING_PAM4 = 0x08, + SFF8024_ID_QSFP_28 = 0x11, + + SFF8024_QSFP_DD_CONNECTOR_UNSPEC = 0x00, + SFF8024_QSFP_DD_CONNECTOR_SC = 0x01, + SFF8024_QSFP_DD_CONNECTOR_FIBRE_CHANNEL_STYLE1 = 0x02, + SFF8024_QSFP_DD_CONNECTOR_FIBRE_CHANNEL_STYLE2 = 0x03, + SFF8024_QSFP_DD_CONNECTOR_BNC_TNC = 0x04, + SFF8024_QSFP_DD_CONNECTOR_FIBRE_CHANNEL_COAX_HEADERS = 0x05, + SFF8024_QSFP_DD_CONNECTOR_FIBERJACK = 0x06, + SFF8024_QSFP_DD_CONNECTOR_LC = 0x07, + SFF8024_QSFP_DD_CONNECTOR_MT_RJ = 0x08, + SFF8024_QSFP_DD_CONNECTOR_MU = 0x09, + SFF8024_QSFP_DD_CONNECTOR_SG = 0x0a, + SFF8024_QSFP_DD_CONNECTOR_OPTICAL_PIGTAIL = 0x0b, + SFF8024_QSFP_DD_CONNECTOR_MPO_1X12 = 0x0c, + SFF8024_QSFP_DD_CONNECTOR_MPO_2X16 = 0x0d, + SFF8024_QSFP_DD_CONNECTOR_HSSDC_II = 0x20, + SFF8024_QSFP_DD_CONNECTOR_COPPER_PIGTAIL = 0x21, + SFF8024_QSFP_DD_CONNECTOR_RJ45 = 0x22, + SFF8024_QSFP_DD_CONNECTOR_NOSEPARATE = 0x23, + SFF8024_QSFP_DD_CONNECTOR_MXC_2X16 = 0x24, + SFF8024_QSFP_DD_CONNECTOR_CS_OPTICAL_CONNECTOR = 0x25, + SFF8024_QSFP_DD_CONNECTOR_SN_OPICAL_CONNECTOR = 0x26, + SFF8024_QSFP_DD_CONNECTOR_MPO_2X12 = 0x27, + SFF8024_QSFP_DD_CONNECTOR_MXC_1X16 = 0x28, + + SFF8636_QSFP_DD_ECC_100GBASE_CR4 = 1, + SFF8636_QSFP_DD_ECC_CAUI4 = 0, + + SFF8636_QSFP_DD_ECC_LAUI2_C2M = 3, + SFF8636_QSFP_DD_ECC_50GAUI2_C2M = 2, + SFF8636_QSFP_DD_ECC_50GAUI1_C2M = 1, + SFF8636_QSFP_DD_ECC_CDAUI8_C2M = 0, + +}; + +/* qsfp EEPROM registers */ +enum { + QSFP_PHYS_ID = 0x00, + QSFP_STATUS = 0x01, + QSFP_STATUS_1 = 0x02, + QSFP_RX_TX_LOSS = 0x03, + QSFP_TX_FAULT = 0x04, + QSFP_DEVICE_MONITORS = 0x16, + QSFP_CHANNEL_MONITORS = 0x22, + QSFP_CONTROL = 0x56, + QSFP_DEVICE_CHANNEL_MASKS = 0x64, + QSFP_VENDOR_SPECIFIC = 0x69, + QSFP_DEVICE_PROPERTIES = 0x6C, + QSFP_PCI_EXPRESS = 0x6F, + QSFP_DEVICE_PROPERTIES_1 = 0x71, + QSFP_PASSWORD_CHANGE_AREA = 0x77, + QSFP_PASSWORD_ENTRY_AREA = 0x7B, + QSFP_PAGE_SELECT_BYTE = 0x7F, + QSFP_IDENTIFIER = 0x80, + QSFP_EXT_IDENTIFIER = 0x81, + QSFP_CONNECTOR = 0x82, + QSFP_COMPLIANCE = 0x83, + QSFP_ENCODING = 0x8b, + QSFP_BR_NOMINAL = 0x8C, + QSFP_EXT_RATE_SELECT = 0x8d, + QSFP_LINK_LEN_SMF = 0x8e, + QSFP_LINK_LEN_OM3_50 = 0x8f, + QSFP_LINK_LEN_OM2_50 = 0x90, + QSFP_LINK_LEN_OM2_62_5 = 0x91, + QSFP_LINK_LEN_COPPER_1M = 0x92, + QSFP_DEVICE_TECHNOLOGY = 0x93, + QSFP_VENDOR_NAME = 0x94, + QSFP_VENDOR_OUI = 0xa5, + QSFP_VENDOR_PN = 0xa8, + QSFP_VENDOR_REV = 0xb8, + QSFP_MAX_CASE_TEMP = 0xbe, + QSFP_CC_BASE = 0xbf, + QSFP_OPTIONS = 0xC3, + QSFP_VENDOR_SN = 0xC4, + QSFP_DATECODE = 0xD4, + QSFP_DIAGMON = 0xDc, + QSFP_ENHOPTS = 0xDD, + QSFP_CC_EXT = 0xDf, + + QSFP_TX4_LOS_CHANNEL_4 = BIT(7), + QSFP_TX3_LOS_CHANNEL_3 = BIT(6), + QSFP_TX2_LOS_CHANNEL_2 = BIT(5), + QSFP_TX1_LOS_CHANNEL_1 = BIT(4), + QSFP_RX4_LOS_CHANNEL_4 = BIT(3), + QSFP_RX3_LOS_CHANNEL_3 = BIT(2), + QSFP_RX2_LOS_CHANNEL_2 = BIT(1), + QSFP_RX1_LOS_CHANNEL_1 = BIT(0), + + QSFP_OPTIONS_TX_INPUT_ADAPTIVE = BIT(20), + QSFP_OPTIONS_TX_INPUT_EQUALIZATION = BIT(19), + QSFP_OPTIONS_TX_INPUT_EQUALIZATION_FIXED = BIT(18), + QSFP_OPTIONS_RX_OUTPUT_EMPHASIS = BIT(17), + QSFP_OPTIONS_RX_OUTPUT_APLITUDE = BIT(16), + QSFP_OPTIONS_TX_CDR_CONTROL = BIT(15), + QSFP_OPTIONS_RX_CDR_CONTROL = BIT(14), + QSFP_OPTIONS_TX_CDR_LOSS = BIT(13), + QSFP_OPTIONS_RX_CDR_LOSS = BIT(12), + QSFP_OPTIONS_RX_SQUELCH_DISABLE = BIT(11), + QSFP_OPTIONS_RX_OUTPUT_DISBALE = BIT(10), + QSFP_OPTIONS_TX_SQUELCH_DISABLE = BIT(9), + QSFP_OPTIONS_TX_OUTPUT_DISBALE = BIT(8), + QSFP_OPTIONS_MEMORY_PAGE_2 = BIT(7), + QSFP_OPTIONS_MEMORY_PAGE_1 = BIT(6), + QSFP_OPTIONS_RATE_SELECT = BIT(5), + QSFP_OPTIONS_TX_DISABLE = BIT(4), + QSFP_OPTIONS_TX_FAULT = BIT(3), + QSFP_OPTIONS_TX_SQUELCH_IMPLEMENTED = BIT(2), + QSFP_OPTIONS_TX_LOSS_SIGNAL = BIT(1), + QSFP_OPTIONS_PAGES = BIT(0), + + + + QSFP_DIAGMON_DDM = BIT(6), + QSFP_DIAGMON_INT_CAL = BIT(5), + QSFP_DIAGMON_EXT_CAL = BIT(4), + QSFP_DIAGMON_RXPWR_AVG = BIT(3), + QSFP_DIAGMON_ADDRMODE = BIT(2), + + QSFP_DIAGMON_RX_OPTICAL_POWER_MONITOR = BIT(4), + QSFP_DIAGMON_RX_OPTICAL_POWER_MEASUREMENT_TYPE = BIT(3), + QSFP_DIAGMON_TX_OPTICAL_POWER_MONITOR = BIT(2), + QSFP_DIAGMON_TX_BIAS_MONITOR_IMPLEMENTED = BIT(1), + QSFP_DIAGMON_RESERVED = BIT(0), + + QSFP_ENHOPTS_USER_DEFINED = BIT(7), + QSFP_ENHOPTS_VENDOR_SPECIFIC = BIT(6), + QSFP_ENHOPTS_INTERNAL_3_VOLTS = BIT(5), + QSFP_ENHOPTS_POWER_CHANGE_COMPLETE = BIT(4), + QSFP_ENHOPTS_RX_RATE_SELECT = BIT(3), + QSFP_ENHOPTS_APPLICATION_SELECTED = BIT(2), + + QSFP_SFF8472_COMPLIANCE_NONE = 0x00, + QSFP_SFF8472_COMPLIANCE_REV9_3 = 0x01, + QSFP_SFF8472_COMPLIANCE_REV9_5 = 0x02, + QSFP_SFF8472_COMPLIANCE_REV10_2 = 0x03, + QSFP_SFF8472_COMPLIANCE_REV10_4 = 0x04, + QSFP_SFF8472_COMPLIANCE_REV11_0 = 0x05, + QSFP_SFF8472_COMPLIANCE_REV11_3 = 0x06, + QSFP_SFF8472_COMPLIANCE_REV11_4 = 0x07, + QsSFP_SFF8472_COMPLIANCE_REV12_0 = 0x08, + + QSFP_EXT_STATUS = 0x76, + +}; + +struct fwnode_handle; +struct ethtool_eeprom; +struct ethtool_modinfo; +struct qsfp_bus; + + /** + * struct qsfp_upstream_ops - upstream operations structure + * @attach: called when the qsfp socket driver is bound to the upstream + * (mandatory). + * @detach: called when the qsfp socket driver is unbound from the upstream + * (mandatory). + * @module_insert: called after a module has been detected to determine + * whether the module is supported for the upstream device. + * @module_remove: called after the module has been removed. + * @module_start: called after the PHY probe step + * @module_stop: called before the PHY is removed + * @link_down: called when the link is non-operational for whatever + * reason. + * @link_up: called when the link is operational. + * @connect_phy: called when an I2C accessible PHY has been detected + * on the module. + * @disconnect_phy: called when a module with an I2C accessible PHY has + * been removed. + */ +struct qsfp_upstream_ops { + void (*attach)(void *priv, struct qsfp_bus *bus); + void (*detach)(void *priv, struct qsfp_bus *bus); + int (*module_insert)(void *priv, const struct qsfp_eeprom_id *id); + void (*module_remove)(void *priv); + int (*module_start)(void *priv); + void (*module_stop)(void *priv); + void (*link_down)(void *priv); + void (*link_up)(void *priv); + int (*connect_phy)(void *priv, struct phy_device *phydev); + void (*disconnect_phy)(void *priv); +}; + +struct qsfp_socket_ops { + void (*attach)(struct qsfp *qsfp); + void (*detach)(struct qsfp *qsfp); + void (*start)(struct qsfp *qsfp); + void (*stop)(struct qsfp *qsfp); + int (*module_info)(struct qsfp *qsfp, struct ethtool_modinfo *modinfo); + int (*module_eeprom)(struct qsfp *qsfp, struct ethtool_eeprom *ee, + u8 *data); +}; + +int qsfp_add_phy(struct qsfp_bus *bus, struct phy_device *phydev); +void qsfp_remove_phy(struct qsfp_bus *bus); +void qsfp_link_up(struct qsfp_bus *bus); +void qsfp_link_down(struct qsfp_bus *bus); +int qsfp_module_insert(struct qsfp_bus *bus, const struct qsfp_eeprom_id *id); +void qsfp_module_remove(struct qsfp_bus *bus); +int qsfp_module_start(struct qsfp_bus *bus); +void qsfp_module_stop(struct qsfp_bus *bus); +int qsfp_link_configure(struct qsfp_bus *bus, const struct qsfp_eeprom_id *id); +struct qsfp_bus *qsfp_register_socket(struct device *dev, struct qsfp *qsfp, + const struct qsfp_socket_ops *ops); +void qsfp_unregister_socket(struct qsfp_bus *bus); + +int get_cable_attach(struct qsfp *old); +int get_channel_info(struct qsfp *old); + +#if IS_ENABLED(CONFIG_QSFP) +int qsfp_parse_port(struct qsfp_bus *bus, const struct qsfp_eeprom_id *id, + unsigned long *support); +bool qsfp_may_have_phy(struct qsfp_bus *bus, const struct qsfp_eeprom_id *id); +void qsfp_parse_support(struct qsfp_bus *bus, const struct qsfp_eeprom_id *id, + unsigned long *support); +phy_interface_t qsfp_select_interface(struct qsfp_bus *bus, + unsigned long *link_modes); + +int qsfp_get_module_info(struct qsfp_bus *bus, struct ethtool_modinfo *modinfo); +int qsfp_get_module_eeprom(struct qsfp_bus *bus, struct ethtool_eeprom *ee, + u8 *data); +void qsfp_upstream_start(struct qsfp_bus *bus); +void qsfp_upstream_stop(struct qsfp_bus *bus); +void qsfp_bus_put(struct qsfp_bus *bus); +struct qsfp_bus *qsfp_bus_find_fwnode(struct fwnode_handle *fwnode); +int qsfp_bus_add_upstream(struct qsfp_bus *bus, void *upstream, + const struct qsfp_upstream_ops *ops); +void qsfp_bus_del_upstream(struct qsfp_bus *bus); +#else + +static inline int qsfp_parse_port(struct qsfp_bus *bus, + const struct qsfp_eeprom_id *id, + unsigned long *support) +{ + return PORT_OTHER; +} + +static inline bool qsfp_may_have_phy(struct qsfp_bus *bus, + const struct qsfp_eeprom_id *id) +{ + return false; +} + +static inline void qsfp_parse_support(struct qsfp_bus *bus, + const struct qsfp_eeprom_id *id, + unsigned long *support) +{ +} + +static inline phy_interface_t qsfp_select_interface(struct qsfp_bus *bus, + unsigned long *link_modes) +{ + return PHY_INTERFACE_MODE_NA; +} + +static inline int qsfp_get_module_info(struct qsfp_bus *bus, + struct ethtool_modinfo *modinfo) +{ + return -EOPNOTSUPP; +} + +static inline int qsfp_get_module_eeprom(struct qsfp_bus *bus, + struct ethtool_eeprom *ee, u8 *data) +{ + return -EOPNOTSUPP; +} + +static inline void qsfp_upstream_start(struct qsfp_bus *bus) +{ +} + +static inline void qsfp_upstream_stop(struct qsfp_bus *bus) +{ +} + +static inline void qsfp_bus_put(struct qsfp_bus *bus) +{ +} + +static inline struct qsfp_bus * +qsfp_bus_find_fwnode(struct fwnode_handle *fwnode) +{ + return NULL; +} + +static inline int qsfp_bus_add_upstream(struct qsfp_bus *bus, void *upstream, + const struct qsfp_upstream_ops *ops) +{ + return 0; +} + +static inline void qsfp_bus_del_upstream(struct qsfp_bus *bus) +{ +} + +#endif +#endif /* LINUX_QSFP_H */ -- 2.25.1
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#12592): https://lists.yoctoproject.org/g/linux-yocto/message/12592 Mute This Topic: https://lists.yoctoproject.org/mt/98921515/21656 Group Owner: linux-yocto+ow...@lists.yoctoproject.org Unsubscribe: https://lists.yoctoproject.org/g/linux-yocto/unsub [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-