// 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 "sfp.h"
/**
* struct sfp_bus - internal representation of a sfp bus
*/
struct sfp_bus {
/* private: */
struct kref kref;
struct list_head node;
const struct fwnode_handle *fwnode;
const struct sfp_socket_ops *socket_ops;
struct device *sfp_dev;
struct sfp *sfp;
const struct sfp_upstream_ops *upstream_ops;
void *upstream;
struct phy_device *phydev;
bool registered;
bool started;
struct sfp_module_caps caps;
};
const struct sfp_module_caps *sfp_get_module_caps(struct sfp_bus *bus)
{
return &bus->caps;
}
EXPORT_SYMBOL_GPL(sfp_get_module_caps);
static void sfp_module_parse_port(struct sfp_bus *bus,
const struct sfp_eeprom_id *id)
{
int port;
/* port is the physical connector, set this from the connector field. */
switch (id->base.connector) {
case SFF8024_CONNECTOR_SC:
case SFF8024_CONNECTOR_FIBERJACK:
case SFF8024_CONNECTOR_LC:
case SFF8024_CONNECTOR_MT_RJ:
case SFF8024_CONNECTOR_MU:
case SFF8024_CONNECTOR_OPTICAL_PIGTAIL:
case SFF8024_CONNECTOR_MPO_1X12:
case SFF8024_CONNECTOR_MPO_2X16:
port = PORT_FIBRE;
break;
case SFF8024_CONNECTOR_RJ45:
port = PORT_TP;
break;
case SFF8024_CONNECTOR_COPPER_PIGTAIL:
port = PORT_DA;
break;
case SFF8024_CONNECTOR_UNSPEC:
if (id->base.e1000_base_t) {
port = PORT_TP;
break;
}
fallthrough;
case SFF8024_CONNECTOR_SG: /* guess */
case SFF8024_CONNECTOR_HSSDC_II:
case SFF8024_CONNECTOR_NOSEPARATE:
case SFF8024_CONNECTOR_MXC_2X16:
port = PORT_OTHER;
break;
default:
dev_warn(bus->sfp_dev, "SFP: unknown connector id 0x%02x\n",
id->base.connector);
port = PORT_OTHER;
break;
}
switch (port) {
case PORT_FIBRE:
phylink_set(bus->caps.link_modes, FIBRE);
break;
case PORT_TP:
phylink_set(bus->caps.link_modes, TP);
break;
}
bus->caps.port = port;
}
static void sfp_module_parse_may_have_phy(struct sfp_bus *bus,
const struct sfp_eeprom_id *id)
{
if (id->base.e1000_base_t) {
bus->caps.may_have_phy = true;
return;
}
if (id->base.phys_id != SFF8024_ID_DWDM_SFP) {
switch (id->base.extended_cc) {
case SFF8024_ECC_10GBASE_T_SFI:
case SFF8024_ECC_10GBASE_T_SR:
case SFF8024_ECC_5GBASE_T:
case SFF8024_ECC_2_5GBASE_T:
bus->caps.may_have_phy = true;
return;
}
}
bus->caps.may_have_phy = false;
}
static void sfp_module_parse_support(struct sfp_bus *bus,
const struct sfp_eeprom_id *id)
{
unsigned long *interfaces = bus->caps.interfaces;
unsigned long *modes = bus->caps.link_modes;
unsigned int br_min, br_nom, br_max;
/* Decode the bitrate information to MBd */
br_min = br_nom = br_max = 0;
if (id->base.br_nominal) {
if (id->base.br_nominal != 255) {
br_nom = id->base.br_nominal * 100;
br_min = br_nom - id->base.br_nominal * id->ext.br_min;
br_max = br_nom + id->base.br_nominal * id->ext.br_max;
} else if (id->ext.br_max) {
br_nom = 250 * id->ext.br_max;
br_max = br_nom + br_nom * id->ext.br_min / 100;
br_min = br_nom - br_nom * id->ext.br_min / 100;
}
/* When using passive cables, in case neither BR,min nor BR,max
* are specified, set br_min to 0 as the nominal value is then
* used as the maximum.
*/
if (br_min == br_max && id->base.sfp_ct_passive)
br_min = 0;
}
/* Set ethtool support from the compliance fields. */
if (id->base.e10g_base_sr) {
phylink_set(modes, 10000baseSR_Full);
__set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
}
if (id->base.e10g_base_lr) {
phylink_set(modes, 10000baseLR_Full);
__set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
}
if (id->base.e10g_base_lrm) {
phylink_set(modes, 10000baseLRM_Full);
__set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);
}
if (id->base.e10g_base_er) {
phylink_set(modes, 10000baseER_Full);
__set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces);