#include <linux/ethtool.h>
#include <linux/linkmode.h>
#include <linux/phy.h>
#include "phy-caps.h"
static struct link_capabilities link_caps[__LINK_CAPA_MAX] __ro_after_init = {
{ SPEED_10, DUPLEX_HALF, {0} },
{ SPEED_10, DUPLEX_FULL, {0} },
{ SPEED_100, DUPLEX_HALF, {0} },
{ SPEED_100, DUPLEX_FULL, {0} },
{ SPEED_1000, DUPLEX_HALF, {0} },
{ SPEED_1000, DUPLEX_FULL, {0} },
{ SPEED_2500, DUPLEX_FULL, {0} },
{ SPEED_5000, DUPLEX_FULL, {0} },
{ SPEED_10000, DUPLEX_FULL, {0} },
{ SPEED_20000, DUPLEX_FULL, {0} },
{ SPEED_25000, DUPLEX_FULL, {0} },
{ SPEED_40000, DUPLEX_FULL, {0} },
{ SPEED_50000, DUPLEX_FULL, {0} },
{ SPEED_56000, DUPLEX_FULL, {0} },
{ SPEED_80000, DUPLEX_FULL, {0} },
{ SPEED_100000, DUPLEX_FULL, {0} },
{ SPEED_200000, DUPLEX_FULL, {0} },
{ SPEED_400000, DUPLEX_FULL, {0} },
{ SPEED_800000, DUPLEX_FULL, {0} },
{ SPEED_1600000, DUPLEX_FULL, {0} },
};
static int speed_duplex_to_capa(int speed, unsigned int duplex)
{
if (duplex == DUPLEX_UNKNOWN ||
(speed > SPEED_1000 && duplex != DUPLEX_FULL))
return -EINVAL;
switch (speed) {
case SPEED_10: return duplex == DUPLEX_FULL ?
LINK_CAPA_10FD : LINK_CAPA_10HD;
case SPEED_100: return duplex == DUPLEX_FULL ?
LINK_CAPA_100FD : LINK_CAPA_100HD;
case SPEED_1000: return duplex == DUPLEX_FULL ?
LINK_CAPA_1000FD : LINK_CAPA_1000HD;
case SPEED_2500: return LINK_CAPA_2500FD;
case SPEED_5000: return LINK_CAPA_5000FD;
case SPEED_10000: return LINK_CAPA_10000FD;
case SPEED_20000: return LINK_CAPA_20000FD;
case SPEED_25000: return LINK_CAPA_25000FD;
case SPEED_40000: return LINK_CAPA_40000FD;
case SPEED_50000: return LINK_CAPA_50000FD;
case SPEED_56000: return LINK_CAPA_56000FD;
case SPEED_80000: return LINK_CAPA_80000FD;
case SPEED_100000: return LINK_CAPA_100000FD;
case SPEED_200000: return LINK_CAPA_200000FD;
case SPEED_400000: return LINK_CAPA_400000FD;
case SPEED_800000: return LINK_CAPA_800000FD;
case SPEED_1600000: return LINK_CAPA_1600000FD;
}
return -EINVAL;
}
#define for_each_link_caps_asc_speed(cap) \
for (cap = link_caps; cap < &link_caps[__LINK_CAPA_MAX]; cap++)
#define for_each_link_caps_desc_speed(cap) \
for (cap = &link_caps[__LINK_CAPA_MAX - 1]; cap >= link_caps; cap--)
int __init phy_caps_init(void)
{
const struct link_mode_info *linkmode;
int i, capa;
for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) {
linkmode = &link_mode_params[i];
if (linkmode->pairs < linkmode->min_pairs) {
pr_err("Pairs count must not be under min_pairs for linkmode %d\n",
i);
return -EINVAL;
}
capa = speed_duplex_to_capa(linkmode->speed, linkmode->duplex);
if (capa < 0) {
if (linkmode->speed != SPEED_UNKNOWN) {
pr_err("Unknown speed %d, please update LINK_CAPS\n",
linkmode->speed);
return -EINVAL;
}
continue;
}
__set_bit(i, link_caps[capa].linkmodes);
}
return 0;
}
size_t phy_caps_speeds(unsigned int *speeds, size_t size,
unsigned long *linkmodes)
{
struct link_capabilities *lcap;
size_t count = 0;
for_each_link_caps_asc_speed(lcap) {
if (linkmode_intersects(lcap->linkmodes, linkmodes) &&
(count == 0 || speeds[count - 1] != lcap->speed)) {
speeds[count++] = lcap->speed;
if (count >= size)
break;
}
}
return count;
}
const struct link_capabilities *
phy_caps_lookup_by_linkmode(const unsigned long *linkmodes)
{
struct link_capabilities *lcap;
for_each_link_caps_desc_speed(lcap)
if (linkmode_intersects(lcap->linkmodes, linkmodes))
return lcap;
return NULL;
}
const struct link_capabilities *
phy_caps_lookup_by_linkmode_rev(const unsigned long *linkmodes, bool fdx_only)
{
struct link_capabilities *lcap;
for_each_link_caps_asc_speed(lcap) {
if (fdx_only && lcap->duplex != DUPLEX_FULL)
continue;
if (linkmode_intersects(lcap->linkmodes, linkmodes))
return lcap;
}
return NULL;
}
const struct link_capabilities *
phy_caps_lookup(int speed, unsigned int duplex, const unsigned long *supported,
bool exact)
{
const struct link_capabilities *lcap, *match = NULL, *last = NULL;
for_each_link_caps_desc_speed(lcap) {
if (linkmode_intersects(lcap->linkmodes, supported)) {
last = lcap;
if (lcap->speed == speed && lcap->duplex == duplex) {
return lcap;
} else if (!exact) {
if (!match && lcap->speed <= speed)
match = lcap;
if (lcap->speed < speed)
break;
}
}
}
if (!match && !exact)
match = last;
return match;
}
EXPORT_SYMBOL_GPL(phy_caps_lookup);
void phy_caps_linkmode_max_speed(u32 max_speed, unsigned long *linkmodes)
{
struct link_capabilities *lcap;
for_each_link_caps_desc_speed(lcap)
if (lcap->speed > max_speed)
linkmode_andnot(linkmodes, linkmodes, lcap->linkmodes);
else
break;
}
bool phy_caps_valid(int speed, int duplex, const unsigned long *linkmodes)
{
int capa = speed_duplex_to_capa(speed, duplex);
if (capa < 0)
return false;
return linkmode_intersects(link_caps[capa].linkmodes, linkmodes);
}
void phy_caps_linkmodes(unsigned long caps, unsigned long *linkmodes)
{
unsigned long capa;
for_each_set_bit(capa, &caps, __LINK_CAPA_MAX)
linkmode_or(linkmodes, linkmodes, link_caps[capa].linkmodes);
}
EXPORT_SYMBOL_GPL(phy_caps_linkmodes);
unsigned long phy_caps_from_interface(phy_interface_t interface)
{
unsigned long link_caps = 0;
switch (interface) {
case PHY_INTERFACE_MODE_USXGMII:
link_caps |= BIT(LINK_CAPA_10000FD) | BIT(LINK_CAPA_5000FD);
fallthrough;
case PHY_INTERFACE_MODE_10G_QXGMII:
link_caps |= BIT(LINK_CAPA_2500FD);
fallthrough;
case PHY_INTERFACE_MODE_RGMII_TXID:
case PHY_INTERFACE_MODE_RGMII_RXID:
case PHY_INTERFACE_MODE_RGMII_ID:
case PHY_INTERFACE_MODE_RGMII:
case PHY_INTERFACE_MODE_PSGMII:
case PHY_INTERFACE_MODE_QSGMII:
case PHY_INTERFACE_MODE_QUSGMII:
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_GMII:
link_caps |= BIT(LINK_CAPA_1000HD) | BIT(LINK_CAPA_1000FD);
fallthrough;
case PHY_INTERFACE_MODE_REVRMII:
case PHY_INTERFACE_MODE_RMII:
case PHY_INTERFACE_MODE_SMII:
case PHY_INTERFACE_MODE_REVMII:
case PHY_INTERFACE_MODE_MII:
link_caps |= BIT(LINK_CAPA_10HD) | BIT(LINK_CAPA_10FD);
fallthrough;
case PHY_INTERFACE_MODE_100BASEX:
link_caps |= BIT(LINK_CAPA_100HD) | BIT(LINK_CAPA_100FD);
break;
case PHY_INTERFACE_MODE_MIILITE:
link_caps |= BIT(LINK_CAPA_10FD) | BIT(LINK_CAPA_100FD);
break;
case PHY_INTERFACE_MODE_TBI:
case PHY_INTERFACE_MODE_MOCA:
case PHY_INTERFACE_MODE_RTBI:
case PHY_INTERFACE_MODE_1000BASEX:
link_caps |= BIT(LINK_CAPA_1000HD);
fallthrough;
case PHY_INTERFACE_MODE_1000BASEKX:
case PHY_INTERFACE_MODE_TRGMII:
link_caps |= BIT(LINK_CAPA_1000FD);
break;
case PHY_INTERFACE_MODE_2500BASEX:
link_caps |= BIT(LINK_CAPA_2500FD);
break;
case PHY_INTERFACE_MODE_5GBASER:
link_caps |= BIT(LINK_CAPA_5000FD);
break;
case PHY_INTERFACE_MODE_XGMII:
case PHY_INTERFACE_MODE_RXAUI:
case PHY_INTERFACE_MODE_XAUI:
case PHY_INTERFACE_MODE_10GBASER:
case PHY_INTERFACE_MODE_10GKR:
link_caps |= BIT(LINK_CAPA_10000FD);
break;
case PHY_INTERFACE_MODE_25GBASER:
link_caps |= BIT(LINK_CAPA_25000FD);
break;
case PHY_INTERFACE_MODE_XLGMII:
link_caps |= BIT(LINK_CAPA_40000FD);
break;
case PHY_INTERFACE_MODE_50GBASER:
case PHY_INTERFACE_MODE_LAUI:
link_caps |= BIT(LINK_CAPA_50000FD);
break;
case PHY_INTERFACE_MODE_100GBASEP:
link_caps |= BIT(LINK_CAPA_100000FD);
break;
case PHY_INTERFACE_MODE_INTERNAL:
link_caps |= LINK_CAPA_ALL;
break;
case PHY_INTERFACE_MODE_NA:
case PHY_INTERFACE_MODE_MAX:
break;
}
return link_caps;
}
EXPORT_SYMBOL_GPL(phy_caps_from_interface);
void phy_caps_medium_get_supported(unsigned long *supported,
enum ethtool_link_medium medium,
int pairs)
{
int i;
for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) {
if (link_mode_params[i].mediums == BIT(ETHTOOL_LINK_MEDIUM_NONE)) {
linkmode_set_bit(i, supported);
continue;
}
if (link_mode_params[i].mediums & BIT(medium) &&
(!link_mode_params[i].min_pairs ||
(link_mode_params[i].min_pairs <= pairs &&
link_mode_params[i].pairs >= pairs)))
linkmode_set_bit(i, supported);
}
}
EXPORT_SYMBOL_GPL(phy_caps_medium_get_supported);
u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes)
{
const struct link_mode_info *linkmode;
u32 mediums = 0;
int i;
for_each_set_bit(i, linkmodes, __ETHTOOL_LINK_MODE_MASK_NBITS) {
linkmode = &link_mode_params[i];
mediums |= linkmode->mediums;
}
return mediums;
}
EXPORT_SYMBOL_GPL(phy_caps_mediums_from_linkmodes);