Commit fc082b39 authored by Russell King (Oracle)'s avatar Russell King (Oracle) Committed by Jakub Kicinski

net: sfp: add support for rate selection

Add support for parsing the rate select thresholds and switching of the
RS0 and RS1 signals to the transceiver. This is complicated by various
revisions of SFF-8472 and interaction of SFF-8431, SFF-8079 and
INF-8074.
Reviewed-by: default avatarSimon Horman <simon.horman@corigine.com>
Signed-off-by: default avatarRussell King (Oracle) <rmk+kernel@armlinux.org.uk>
Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent dc185822
......@@ -24,14 +24,18 @@ enum {
GPIO_LOS,
GPIO_TX_FAULT,
GPIO_TX_DISABLE,
GPIO_RATE_SELECT,
GPIO_RS0,
GPIO_RS1,
GPIO_MAX,
SFP_F_PRESENT = BIT(GPIO_MODDEF0),
SFP_F_LOS = BIT(GPIO_LOS),
SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT),
SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE),
SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT),
SFP_F_RS0 = BIT(GPIO_RS0),
SFP_F_RS1 = BIT(GPIO_RS1),
SFP_F_OUTPUTS = SFP_F_TX_DISABLE | SFP_F_RS0 | SFP_F_RS1,
SFP_E_INSERT = 0,
SFP_E_REMOVE,
......@@ -148,6 +152,7 @@ static const char *gpio_names[] = {
"tx-fault",
"tx-disable",
"rate-select0",
"rate-select1",
};
static const enum gpiod_flags gpio_flags[] = {
......@@ -156,6 +161,7 @@ static const enum gpiod_flags gpio_flags[] = {
GPIOD_IN,
GPIOD_ASIS,
GPIOD_ASIS,
GPIOD_ASIS,
};
/* t_start_up (SFF-8431) or t_init (SFF-8472) is the time required for a
......@@ -249,6 +255,7 @@ struct sfp {
* state: st_mutex held unless reading input bits
*/
struct mutex st_mutex; /* Protects state */
unsigned int state_hw_drive;
unsigned int state_hw_mask;
unsigned int state_soft_mask;
unsigned int state;
......@@ -269,6 +276,10 @@ struct sfp {
unsigned int module_t_start_up;
unsigned int module_t_wait;
unsigned int rate_kbd;
unsigned int rs_threshold_kbd;
unsigned int rs_state_mask;
bool have_a2;
bool tx_fault_ignore;
......@@ -319,7 +330,7 @@ static bool sfp_module_supported(const struct sfp_eeprom_id *id)
static const struct sff_data sfp_data = {
.gpios = SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT |
SFP_F_TX_DISABLE | SFP_F_RATE_SELECT,
SFP_F_TX_DISABLE | SFP_F_RS0 | SFP_F_RS1,
.module_supported = sfp_module_supported,
};
......@@ -507,20 +518,37 @@ static unsigned int sff_gpio_get_state(struct sfp *sfp)
static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state)
{
if (state & SFP_F_PRESENT) {
/* If the module is present, drive the signals */
if (sfp->gpio[GPIO_TX_DISABLE])
unsigned int drive;
if (state & SFP_F_PRESENT)
/* If the module is present, drive the requested signals */
drive = sfp->state_hw_drive;
else
/* Otherwise, let them float to the pull-ups */
drive = 0;
if (sfp->gpio[GPIO_TX_DISABLE]) {
if (drive & SFP_F_TX_DISABLE)
gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE],
state & SFP_F_TX_DISABLE);
if (state & SFP_F_RATE_SELECT)
gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT],
state & SFP_F_RATE_SELECT);
} else {
/* Otherwise, let them float to the pull-ups */
if (sfp->gpio[GPIO_TX_DISABLE])
else
gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]);
if (state & SFP_F_RATE_SELECT)
gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]);
}
if (sfp->gpio[GPIO_RS0]) {
if (drive & SFP_F_RS0)
gpiod_direction_output(sfp->gpio[GPIO_RS0],
state & SFP_F_RS0);
else
gpiod_direction_input(sfp->gpio[GPIO_RS0]);
}
if (sfp->gpio[GPIO_RS1]) {
if (drive & SFP_F_RS1)
gpiod_direction_output(sfp->gpio[GPIO_RS1],
state & SFP_F_RS1);
else
gpiod_direction_input(sfp->gpio[GPIO_RS1]);
}
}
......@@ -682,16 +710,33 @@ static unsigned int sfp_soft_get_state(struct sfp *sfp)
return state & sfp->state_soft_mask;
}
static void sfp_soft_set_state(struct sfp *sfp, unsigned int state)
static void sfp_soft_set_state(struct sfp *sfp, unsigned int state,
unsigned int soft)
{
u8 mask = SFP_STATUS_TX_DISABLE_FORCE;
u8 mask = 0;
u8 val = 0;
if (soft & SFP_F_TX_DISABLE)
mask |= SFP_STATUS_TX_DISABLE_FORCE;
if (state & SFP_F_TX_DISABLE)
val |= SFP_STATUS_TX_DISABLE_FORCE;
if (soft & SFP_F_RS0)
mask |= SFP_STATUS_RS0_SELECT;
if (state & SFP_F_RS0)
val |= SFP_STATUS_RS0_SELECT;
if (mask)
sfp_modify_u8(sfp, true, SFP_STATUS, mask, val);
sfp_modify_u8(sfp, true, SFP_STATUS, mask, val);
val = mask = 0;
if (soft & SFP_F_RS1)
mask |= SFP_EXT_STATUS_RS1_SELECT;
if (state & SFP_F_RS1)
val |= SFP_EXT_STATUS_RS1_SELECT;
if (mask)
sfp_modify_u8(sfp, true, SFP_EXT_STATUS, mask, val);
}
static void sfp_soft_start_poll(struct sfp *sfp)
......@@ -705,6 +750,8 @@ static void sfp_soft_start_poll(struct sfp *sfp)
mask |= SFP_F_TX_FAULT;
if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS)
mask |= SFP_F_LOS;
if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RATE_SELECT)
mask |= sfp->rs_state_mask;
mutex_lock(&sfp->st_mutex);
// Poll the soft state for hardware pins we want to ignore
......@@ -743,11 +790,13 @@ static unsigned int sfp_get_state(struct sfp *sfp)
*/
static void sfp_set_state(struct sfp *sfp, unsigned int state)
{
unsigned int soft;
sfp->set_state(sfp, state);
if (state & SFP_F_PRESENT &&
sfp->state_soft_mask & SFP_F_TX_DISABLE)
sfp_soft_set_state(sfp, state);
soft = sfp->state_soft_mask & SFP_F_OUTPUTS;
if (state & SFP_F_PRESENT && soft)
sfp_soft_set_state(sfp, state, soft);
}
static void sfp_mod_state(struct sfp *sfp, unsigned int mask, unsigned int set)
......@@ -1589,10 +1638,15 @@ static int sfp_debug_state_show(struct seq_file *s, void *data)
sfp->sm_fault_retries);
seq_printf(s, "PHY probe remaining retries: %d\n",
sfp->sm_phy_retries);
seq_printf(s, "Signalling rate: %u kBd\n", sfp->rate_kbd);
seq_printf(s, "Rate select threshold: %u kBd\n",
sfp->rs_threshold_kbd);
seq_printf(s, "moddef0: %d\n", !!(sfp->state & SFP_F_PRESENT));
seq_printf(s, "rx_los: %d\n", !!(sfp->state & SFP_F_LOS));
seq_printf(s, "tx_fault: %d\n", !!(sfp->state & SFP_F_TX_FAULT));
seq_printf(s, "tx_disable: %d\n", !!(sfp->state & SFP_F_TX_DISABLE));
seq_printf(s, "rs0: %d\n", !!(sfp->state & SFP_F_RS0));
seq_printf(s, "rs1: %d\n", !!(sfp->state & SFP_F_RS1));
return 0;
}
DEFINE_SHOW_ATTRIBUTE(sfp_debug_state);
......@@ -1898,6 +1952,95 @@ static int sfp_sm_mod_hpower(struct sfp *sfp, bool enable)
return 0;
}
static void sfp_module_parse_rate_select(struct sfp *sfp)
{
u8 rate_id;
sfp->rs_threshold_kbd = 0;
sfp->rs_state_mask = 0;
if (!(sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_RATE_SELECT)))
/* No support for RateSelect */
return;
/* Default to INF-8074 RateSelect operation. The signalling threshold
* rate is not well specified, so always select "Full Bandwidth", but
* SFF-8079 reveals that it is understood that RS0 will be low for
* 1.0625Gb/s and high for 2.125Gb/s. Choose a value half-way between.
* This method exists prior to SFF-8472.
*/
sfp->rs_state_mask = SFP_F_RS0;
sfp->rs_threshold_kbd = 1594;
/* Parse the rate identifier, which is complicated due to history:
* SFF-8472 rev 9.5 marks this field as reserved.
* SFF-8079 references SFF-8472 rev 9.5 and defines bit 0. SFF-8472
* compliance is not required.
* SFF-8472 rev 10.2 defines this field using values 0..4
* SFF-8472 rev 11.0 redefines this field with bit 0 for SFF-8079
* and even values.
*/
rate_id = sfp->id.base.rate_id;
if (rate_id == 0)
/* Unspecified */
return;
/* SFF-8472 rev 10.0..10.4 did not account for SFF-8079 using bit 0,
* and allocated value 3 to SFF-8431 independent tx/rx rate select.
* Convert this to a SFF-8472 rev 11.0 rate identifier.
*/
if (sfp->id.ext.sff8472_compliance >= SFP_SFF8472_COMPLIANCE_REV10_2 &&
sfp->id.ext.sff8472_compliance < SFP_SFF8472_COMPLIANCE_REV11_0 &&
rate_id == 3)
rate_id = SFF_RID_8431;
if (rate_id & SFF_RID_8079) {
/* SFF-8079 RateSelect / Application Select in conjunction with
* SFF-8472 rev 9.5. SFF-8079 defines rate_id as a bitfield
* with only bit 0 used, which takes precedence over SFF-8472.
*/
if (!(sfp->id.ext.enhopts & SFP_ENHOPTS_APP_SELECT_SFF8079)) {
/* SFF-8079 Part 1 - rate selection between Fibre
* Channel 1.0625/2.125/4.25 Gbd modes. Note that RS0
* is high for 2125, so we have to subtract 1 to
* include it.
*/
sfp->rs_threshold_kbd = 2125 - 1;
sfp->rs_state_mask = SFP_F_RS0;
}
return;
}
/* SFF-8472 rev 9.5 does not define the rate identifier */
if (sfp->id.ext.sff8472_compliance <= SFP_SFF8472_COMPLIANCE_REV9_5)
return;
/* SFF-8472 rev 11.0 defines rate_id as a numerical value which will
* always have bit 0 clear due to SFF-8079's bitfield usage of rate_id.
*/
switch (rate_id) {
case SFF_RID_8431_RX_ONLY:
sfp->rs_threshold_kbd = 4250;
sfp->rs_state_mask = SFP_F_RS0;
break;
case SFF_RID_8431_TX_ONLY:
sfp->rs_threshold_kbd = 4250;
sfp->rs_state_mask = SFP_F_RS1;
break;
case SFF_RID_8431:
sfp->rs_threshold_kbd = 4250;
sfp->rs_state_mask = SFP_F_RS0 | SFP_F_RS1;
break;
case SFF_RID_10G8G:
sfp->rs_threshold_kbd = 9000;
sfp->rs_state_mask = SFP_F_RS0 | SFP_F_RS1;
break;
}
}
/* 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
......@@ -2117,6 +2260,8 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
if (ret < 0)
return ret;
sfp_module_parse_rate_select(sfp);
mask = SFP_F_PRESENT;
if (sfp->gpio[GPIO_TX_DISABLE])
mask |= SFP_F_TX_DISABLE;
......@@ -2124,6 +2269,10 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
mask |= SFP_F_TX_FAULT;
if (sfp->gpio[GPIO_LOS])
mask |= SFP_F_LOS;
if (sfp->gpio[GPIO_RS0])
mask |= SFP_F_RS0;
if (sfp->gpio[GPIO_RS1])
mask |= SFP_F_RS1;
sfp->module_t_start_up = T_START_UP;
sfp->module_t_wait = T_WAIT;
......@@ -2146,6 +2295,9 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
/* Initialise state bits to use from hardware */
sfp->state_hw_mask = mask;
/* We want to drive the rate select pins that the module is using */
sfp->state_hw_drive |= sfp->rs_state_mask;
if (sfp->quirk && sfp->quirk->fixup)
sfp->quirk->fixup(sfp);
mutex_unlock(&sfp->st_mutex);
......@@ -2162,6 +2314,7 @@ static void sfp_sm_mod_remove(struct sfp *sfp)
memset(&sfp->id, 0, sizeof(sfp->id));
sfp->module_power_mW = 0;
sfp->state_hw_drive = SFP_F_TX_DISABLE;
sfp->have_a2 = false;
dev_info(sfp->dev, "module removed\n");
......@@ -2529,6 +2682,16 @@ static void sfp_stop(struct sfp *sfp)
static void sfp_set_signal_rate(struct sfp *sfp, unsigned int rate_kbd)
{
unsigned int set;
sfp->rate_kbd = rate_kbd;
if (rate_kbd > sfp->rs_threshold_kbd)
set = sfp->rs_state_mask;
else
set = 0;
sfp_mod_state(sfp, SFP_F_RS0 | SFP_F_RS1, set);
}
static int sfp_module_info(struct sfp *sfp, struct ethtool_modinfo *modinfo)
......@@ -2648,7 +2811,7 @@ static void sfp_check_state(struct sfp *sfp)
dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_names[i],
!!(sfp->state & BIT(i)), !!(state & BIT(i)));
state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT);
state |= sfp->state & SFP_F_OUTPUTS;
sfp->state = state;
mutex_unlock(&sfp->st_mutex);
......@@ -2790,6 +2953,7 @@ static int sfp_probe(struct platform_device *pdev)
}
sfp->state_hw_mask = SFP_F_PRESENT;
sfp->state_hw_drive = SFP_F_TX_DISABLE;
sfp->get_state = sfp_gpio_get_state;
sfp->set_state = sfp_gpio_set_state;
......@@ -2815,9 +2979,9 @@ static int sfp_probe(struct platform_device *pdev)
*/
sfp->state = sfp_get_state(sfp) | SFP_F_TX_DISABLE;
if (sfp->gpio[GPIO_RATE_SELECT] &&
gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT]))
sfp->state |= SFP_F_RATE_SELECT;
if (sfp->gpio[GPIO_RS0] &&
gpiod_get_value_cansleep(sfp->gpio[GPIO_RS0]))
sfp->state |= SFP_F_RS0;
sfp_set_state(sfp, sfp->state);
sfp_module_tx_disable(sfp);
if (sfp->state & SFP_F_PRESENT) {
......
......@@ -342,6 +342,12 @@ enum {
SFP_ENCODING = 11,
SFP_BR_NOMINAL = 12,
SFP_RATE_ID = 13,
SFF_RID_8079 = 0x01,
SFF_RID_8431_RX_ONLY = 0x02,
SFF_RID_8431_TX_ONLY = 0x04,
SFF_RID_8431 = 0x06,
SFF_RID_10G8G = 0x0e,
SFP_LINK_LEN_SM_KM = 14,
SFP_LINK_LEN_SM_100M = 15,
SFP_LINK_LEN_50UM_OM2_10M = 16,
......@@ -465,6 +471,7 @@ enum {
SFP_STATUS = 110,
SFP_STATUS_TX_DISABLE = BIT(7),
SFP_STATUS_TX_DISABLE_FORCE = BIT(6),
SFP_STATUS_RS0_SELECT = BIT(3),
SFP_STATUS_TX_FAULT = BIT(2),
SFP_STATUS_RX_LOS = BIT(1),
SFP_ALARM0 = 112,
......@@ -496,6 +503,7 @@ enum {
SFP_WARN1_RXPWR_LOW = BIT(6),
SFP_EXT_STATUS = 118,
SFP_EXT_STATUS_RS1_SELECT = BIT(3),
SFP_EXT_STATUS_PWRLVL_SELECT = BIT(0),
SFP_VSL = 120,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment