realtek: migrate PCS to fwnode_pcs provider/consumer API
PCS driver registers each SerDes as an fwnode_pcs provider in probe; the resolver returns the cached or freshly-allocated rtpcs_link for the requested (sds, link_idx) cell. DSA glue stops calling rtpcs_create directly, drops .mac_select_pcs, and instead populates phylink_config.num_available_pcs / fill_available_pcs from each port's pcs-handle in phylink_get_caps. The rtl838x_port.pcs pointer becomes a has_pcs bool populated at port probe via fwnode_property_ present, since nothing assigns the actual phylink_pcs anymore but the "does this port use a PCS?" checks elsewhere still need a presence flag. Without .mac_select_pcs, phylink_major_config only searches the pcs_list when state->interface is set in phylink_config.pcs_interfaces (drivers/net/phy/phylink.c:1378). Populate it per port whenever the port has a pcs-handle, listing the SerDes-routable interface modes for each SoC variant -- without this, pcs_config / pcs_link_up are never called and the SerDes is left unconfigured. pcs_get_state still needs the MAC port number to index per-port link status registers. Recover it at probe via rtpcs_map_links: walk the sibling switch's ethernet-ports subtree (same backwards topology lookup the sibling MDIO driver does for phy-handle), and for every port whose pcs-handle resolves to one of our SerDes, store the port's reg in sds->link_port[]. The resolver consults link_port[] when allocating rtpcs_link and fails with -ENODEV if a consumer requested a link the map step didn't record. Avoids a driver-side port_base table that would have to encode per-SoC SerDes-to-port wiring (and would silently break on non-contiguous variants); the DT is the single source of truth. Kconfig selects FWNODE_PCS. Assisted-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Link: https://github.com/openwrt/openwrt/pull/23539 Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
This commit is contained in:
parent
c4f5c34fa4
commit
a4965dbf48
@ -255,8 +255,6 @@ static bool rtldsa_phys_load_deferred(void)
|
|||||||
static int rtl83xx_mdio_probe(struct rtl838x_switch_priv *priv)
|
static int rtl83xx_mdio_probe(struct rtl838x_switch_priv *priv)
|
||||||
{
|
{
|
||||||
struct device_node *dn, *phy_node, *led_node;
|
struct device_node *dn, *phy_node, *led_node;
|
||||||
struct of_phandle_args pcs_args;
|
|
||||||
bool has_pcs;
|
|
||||||
u32 pn;
|
u32 pn;
|
||||||
|
|
||||||
/* Check if all busses of Realtek mdio controller are registered */
|
/* Check if all busses of Realtek mdio controller are registered */
|
||||||
@ -294,26 +292,14 @@ static int rtl83xx_mdio_probe(struct rtl838x_switch_priv *priv)
|
|||||||
if (of_property_read_u32(dn, "reg", &pn))
|
if (of_property_read_u32(dn, "reg", &pn))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
has_pcs = !of_parse_phandle_with_args(dn, "pcs-handle", "#pcs-cells",
|
|
||||||
0, &pcs_args);
|
|
||||||
phy_node = of_parse_phandle(dn, "phy-handle", 0);
|
phy_node = of_parse_phandle(dn, "phy-handle", 0);
|
||||||
if (pn != priv->r->cpu_port && !phy_node && !has_pcs) {
|
priv->ports[pn].has_pcs = fwnode_property_present(of_fwnode_handle(dn),
|
||||||
|
"pcs-handle");
|
||||||
|
if (pn != priv->r->cpu_port && !phy_node && !priv->ports[pn].has_pcs) {
|
||||||
dev_err(priv->dev, "Port node %d has neither pcs-handle nor phy-handle\n", pn);
|
dev_err(priv->dev, "Port node %d has neither pcs-handle nor phy-handle\n", pn);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_pcs) {
|
|
||||||
priv->ports[pn].pcs = rtpcs_create(priv->dev, pcs_args.np,
|
|
||||||
pcs_args.args[0], pn);
|
|
||||||
of_node_put(pcs_args.np);
|
|
||||||
if (IS_ERR(priv->ports[pn].pcs)) {
|
|
||||||
dev_err(priv->dev, "port %u failed to create PCS instance: %ld\n",
|
|
||||||
pn, PTR_ERR(priv->ports[pn].pcs));
|
|
||||||
priv->ports[pn].pcs = NULL;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
priv->ports[pn].leds_on_this_port = 0;
|
priv->ports[pn].leds_on_this_port = 0;
|
||||||
if (led_node) {
|
if (led_node) {
|
||||||
if (of_property_read_u32(dn, "led-set", &led_set))
|
if (of_property_read_u32(dn, "led-set", &led_set))
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <net/dsa.h>
|
#include <net/dsa.h>
|
||||||
#include <linux/etherdevice.h>
|
#include <linux/etherdevice.h>
|
||||||
#include <linux/if_bridge.h>
|
#include <linux/if_bridge.h>
|
||||||
|
#include <linux/pcs/pcs.h>
|
||||||
#include <asm/mach-rtl-otto/mach-rtl-otto.h>
|
#include <asm/mach-rtl-otto/mach-rtl-otto.h>
|
||||||
|
|
||||||
#include "rtl-otto.h"
|
#include "rtl-otto.h"
|
||||||
@ -50,7 +51,7 @@ static void rtldsa_enable_phy_polling(struct rtl838x_switch_priv *priv)
|
|||||||
msleep(1000);
|
msleep(1000);
|
||||||
/* Enable all ports with a PHY, including the SFP-ports */
|
/* Enable all ports with a PHY, including the SFP-ports */
|
||||||
for (int i = 0; i < priv->r->cpu_port; i++) {
|
for (int i = 0; i < priv->r->cpu_port; i++) {
|
||||||
if (priv->ports[i].phy || priv->ports[i].pcs)
|
if (priv->ports[i].phy || priv->ports[i].has_pcs)
|
||||||
v |= BIT_ULL(i);
|
v |= BIT_ULL(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +181,7 @@ static int rtldsa_83xx_setup(struct dsa_switch *ds)
|
|||||||
* they will work in isolated mode (only traffic between port and CPU).
|
* they will work in isolated mode (only traffic between port and CPU).
|
||||||
*/
|
*/
|
||||||
for (int i = 0; i < priv->r->cpu_port; i++) {
|
for (int i = 0; i < priv->r->cpu_port; i++) {
|
||||||
if (priv->ports[i].phy || priv->ports[i].pcs) {
|
if (priv->ports[i].phy || priv->ports[i].has_pcs) {
|
||||||
priv->ports[i].pm = BIT_ULL(priv->r->cpu_port);
|
priv->ports[i].pm = BIT_ULL(priv->r->cpu_port);
|
||||||
priv->r->traffic_set(i, BIT_ULL(i));
|
priv->r->traffic_set(i, BIT_ULL(i));
|
||||||
}
|
}
|
||||||
@ -253,7 +254,7 @@ static int rtldsa_93xx_setup(struct dsa_switch *ds)
|
|||||||
* they will work in isolated mode (only traffic between port and CPU).
|
* they will work in isolated mode (only traffic between port and CPU).
|
||||||
*/
|
*/
|
||||||
for (int i = 0; i < priv->r->cpu_port; i++) {
|
for (int i = 0; i < priv->r->cpu_port; i++) {
|
||||||
if (priv->ports[i].phy || priv->ports[i].pcs) {
|
if (priv->ports[i].phy || priv->ports[i].has_pcs) {
|
||||||
priv->ports[i].pm = BIT_ULL(priv->r->cpu_port);
|
priv->ports[i].pm = BIT_ULL(priv->r->cpu_port);
|
||||||
priv->r->traffic_set(i, BIT_ULL(i));
|
priv->r->traffic_set(i, BIT_ULL(i));
|
||||||
}
|
}
|
||||||
@ -285,18 +286,21 @@ static int rtldsa_93xx_setup(struct dsa_switch *ds)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct phylink_pcs *rtldsa_phylink_mac_select_pcs(struct phylink_config *config,
|
static int rtldsa_phylink_fill_available_pcs(struct phylink_config *config,
|
||||||
phy_interface_t interface)
|
struct phylink_pcs **available_pcs,
|
||||||
|
unsigned int num_available_pcs)
|
||||||
{
|
{
|
||||||
struct dsa_port *dp = dsa_phylink_to_port(config);
|
struct dsa_port *dp = dsa_phylink_to_port(config);
|
||||||
struct rtl838x_switch_priv *priv = dp->ds->priv;
|
|
||||||
|
|
||||||
return priv->ports[dp->index].pcs;
|
return fwnode_phylink_pcs_parse(of_fwnode_handle(dp->dn),
|
||||||
|
available_pcs, &num_available_pcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rtldsa_83xx_phylink_get_caps(struct dsa_switch *ds, int port,
|
static void rtldsa_83xx_phylink_get_caps(struct dsa_switch *ds, int port,
|
||||||
struct phylink_config *config)
|
struct phylink_config *config)
|
||||||
{
|
{
|
||||||
|
struct dsa_port *dp = dsa_to_port(ds, port);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: This needs to take into account the MAC to SERDES mapping and the
|
* TODO: This needs to take into account the MAC to SERDES mapping and the
|
||||||
* specific SoC capabilities. Right now we just assume all RTL83xx ports
|
* specific SoC capabilities. Right now we just assume all RTL83xx ports
|
||||||
@ -311,11 +315,21 @@ static void rtldsa_83xx_phylink_get_caps(struct dsa_switch *ds, int port,
|
|||||||
__set_bit(PHY_INTERFACE_MODE_INTERNAL, config->supported_interfaces);
|
__set_bit(PHY_INTERFACE_MODE_INTERNAL, config->supported_interfaces);
|
||||||
__set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces);
|
__set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces);
|
||||||
__set_bit(PHY_INTERFACE_MODE_QSGMII, config->supported_interfaces);
|
__set_bit(PHY_INTERFACE_MODE_QSGMII, config->supported_interfaces);
|
||||||
|
|
||||||
|
if (!fwnode_phylink_pcs_parse(of_fwnode_handle(dp->dn), NULL,
|
||||||
|
&config->num_available_pcs)) {
|
||||||
|
config->fill_available_pcs = rtldsa_phylink_fill_available_pcs;
|
||||||
|
__set_bit(PHY_INTERFACE_MODE_SGMII, config->pcs_interfaces);
|
||||||
|
__set_bit(PHY_INTERFACE_MODE_QSGMII, config->pcs_interfaces);
|
||||||
|
__set_bit(PHY_INTERFACE_MODE_1000BASEX, config->pcs_interfaces);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rtldsa_93xx_phylink_get_caps(struct dsa_switch *ds, int port,
|
static void rtldsa_93xx_phylink_get_caps(struct dsa_switch *ds, int port,
|
||||||
struct phylink_config *config)
|
struct phylink_config *config)
|
||||||
{
|
{
|
||||||
|
struct dsa_port *dp = dsa_to_port(ds, port);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: This needs to take into account the MAC to SERDES mapping and the
|
* TODO: This needs to take into account the MAC to SERDES mapping and the
|
||||||
* specific SoC capabilities. Right now we just assume all RTL93xx ports
|
* specific SoC capabilities. Right now we just assume all RTL93xx ports
|
||||||
@ -334,6 +348,18 @@ static void rtldsa_93xx_phylink_get_caps(struct dsa_switch *ds, int port,
|
|||||||
__set_bit(PHY_INTERFACE_MODE_2500BASEX, config->supported_interfaces);
|
__set_bit(PHY_INTERFACE_MODE_2500BASEX, config->supported_interfaces);
|
||||||
__set_bit(PHY_INTERFACE_MODE_USXGMII, config->supported_interfaces);
|
__set_bit(PHY_INTERFACE_MODE_USXGMII, config->supported_interfaces);
|
||||||
__set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config->supported_interfaces);
|
__set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config->supported_interfaces);
|
||||||
|
|
||||||
|
if (!fwnode_phylink_pcs_parse(of_fwnode_handle(dp->dn), NULL,
|
||||||
|
&config->num_available_pcs)) {
|
||||||
|
config->fill_available_pcs = rtldsa_phylink_fill_available_pcs;
|
||||||
|
__set_bit(PHY_INTERFACE_MODE_SGMII, config->pcs_interfaces);
|
||||||
|
__set_bit(PHY_INTERFACE_MODE_QSGMII, config->pcs_interfaces);
|
||||||
|
__set_bit(PHY_INTERFACE_MODE_1000BASEX, config->pcs_interfaces);
|
||||||
|
__set_bit(PHY_INTERFACE_MODE_2500BASEX, config->pcs_interfaces);
|
||||||
|
__set_bit(PHY_INTERFACE_MODE_USXGMII, config->pcs_interfaces);
|
||||||
|
__set_bit(PHY_INTERFACE_MODE_10GBASER, config->pcs_interfaces);
|
||||||
|
__set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config->pcs_interfaces);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rtldsa_83xx_phylink_mac_config(struct phylink_config *config,
|
static void rtldsa_83xx_phylink_mac_config(struct phylink_config *config,
|
||||||
@ -776,7 +802,7 @@ static void rtldsa_poll_counters(struct work_struct *work)
|
|||||||
counters_work);
|
counters_work);
|
||||||
|
|
||||||
for (int port = 0; port < priv->r->cpu_port; port++) {
|
for (int port = 0; port < priv->r->cpu_port; port++) {
|
||||||
if (!priv->ports[port].phy && !priv->ports[port].pcs)
|
if (!priv->ports[port].phy && !priv->ports[port].has_pcs)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
rtldsa_counters_lock(priv, port);
|
rtldsa_counters_lock(priv, port);
|
||||||
@ -793,7 +819,7 @@ static void rtldsa_init_counters(struct rtl838x_switch_priv *priv)
|
|||||||
struct rtldsa_counter_state *counters;
|
struct rtldsa_counter_state *counters;
|
||||||
|
|
||||||
for (int port = 0; port < priv->r->cpu_port; port++) {
|
for (int port = 0; port < priv->r->cpu_port; port++) {
|
||||||
if (!priv->ports[port].phy && !priv->ports[port].pcs)
|
if (!priv->ports[port].phy && !priv->ports[port].has_pcs)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
counters = &priv->ports[port].counters;
|
counters = &priv->ports[port].counters;
|
||||||
@ -2593,7 +2619,6 @@ unlock:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const struct phylink_mac_ops rtldsa_83xx_phylink_mac_ops = {
|
const struct phylink_mac_ops rtldsa_83xx_phylink_mac_ops = {
|
||||||
.mac_select_pcs = rtldsa_phylink_mac_select_pcs,
|
|
||||||
.mac_config = rtldsa_83xx_phylink_mac_config,
|
.mac_config = rtldsa_83xx_phylink_mac_config,
|
||||||
.mac_link_down = rtldsa_83xx_phylink_mac_link_down,
|
.mac_link_down = rtldsa_83xx_phylink_mac_link_down,
|
||||||
.mac_link_up = rtldsa_83xx_phylink_mac_link_up,
|
.mac_link_up = rtldsa_83xx_phylink_mac_link_up,
|
||||||
@ -2653,7 +2678,6 @@ const struct dsa_switch_ops rtldsa_83xx_switch_ops = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const struct phylink_mac_ops rtldsa_93xx_phylink_mac_ops = {
|
const struct phylink_mac_ops rtldsa_93xx_phylink_mac_ops = {
|
||||||
.mac_select_pcs = rtldsa_phylink_mac_select_pcs,
|
|
||||||
.mac_config = rtldsa_93xx_phylink_mac_config,
|
.mac_config = rtldsa_93xx_phylink_mac_config,
|
||||||
.mac_link_down = rtldsa_93xx_phylink_mac_link_down,
|
.mac_link_down = rtldsa_93xx_phylink_mac_link_down,
|
||||||
.mac_link_up = rtldsa_93xx_phylink_mac_link_up,
|
.mac_link_up = rtldsa_93xx_phylink_mac_link_up,
|
||||||
|
|||||||
@ -1003,7 +1003,7 @@ struct rtldsa_port {
|
|||||||
u64 pm;
|
u64 pm;
|
||||||
u16 pvid;
|
u16 pvid;
|
||||||
bool eee_enabled;
|
bool eee_enabled;
|
||||||
struct phylink_pcs *pcs;
|
bool has_pcs;
|
||||||
int led_set;
|
int led_set;
|
||||||
int leds_on_this_port;
|
int leds_on_this_port;
|
||||||
struct rtldsa_counter_state counters;
|
struct rtldsa_counter_state counters;
|
||||||
|
|||||||
@ -2668,7 +2668,7 @@ static void rtl930x_led_init(struct rtl838x_switch_priv *priv)
|
|||||||
sw_w32_mask(0x3 << pos, 0, RTL930X_LED_PORT_FIB_SET_SEL_CTRL(i));
|
sw_w32_mask(0x3 << pos, 0, RTL930X_LED_PORT_FIB_SET_SEL_CTRL(i));
|
||||||
sw_w32_mask(0x3 << pos, 0, RTL930X_LED_PORT_COPR_SET_SEL_CTRL(i));
|
sw_w32_mask(0x3 << pos, 0, RTL930X_LED_PORT_COPR_SET_SEL_CTRL(i));
|
||||||
|
|
||||||
if (!priv->ports[i].phy && !priv->ports[i].pcs && !(forced_leds_per_port[i]))
|
if (!priv->ports[i].phy && !priv->ports[i].has_pcs && !(forced_leds_per_port[i]))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (forced_leds_per_port[i] > 0)
|
if (forced_leds_per_port[i] > 0)
|
||||||
|
|||||||
@ -1660,7 +1660,7 @@ static void rtldsa_931x_led_init(struct rtl838x_switch_priv *priv)
|
|||||||
sw_w32_mask(0x3 << pos, 0, RTL931X_LED_PORT_COPR_SET_SEL_CTRL(i));
|
sw_w32_mask(0x3 << pos, 0, RTL931X_LED_PORT_COPR_SET_SEL_CTRL(i));
|
||||||
|
|
||||||
/* Skip port if not present (auto-detect) or not in forced mask */
|
/* Skip port if not present (auto-detect) or not in forced mask */
|
||||||
if (!priv->ports[i].phy && !priv->ports[i].pcs && !(forced_leds_per_port[i]))
|
if (!priv->ports[i].phy && !priv->ports[i].has_pcs && !(forced_leds_per_port[i]))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (forced_leds_per_port[i] > 0)
|
if (forced_leds_per_port[i] > 0)
|
||||||
|
|||||||
@ -5,11 +5,14 @@
|
|||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/of_mdio.h>
|
#include <linux/of_mdio.h>
|
||||||
#include <linux/of_platform.h>
|
#include <linux/of_platform.h>
|
||||||
|
#include <linux/pcs/pcs-provider.h>
|
||||||
#include <linux/phy.h>
|
#include <linux/phy.h>
|
||||||
#include <linux/phy/phy-common-props.h>
|
#include <linux/phy/phy-common-props.h>
|
||||||
#include <linux/phylink.h>
|
#include <linux/phylink.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/property.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/rtnetlink.h>
|
||||||
|
|
||||||
#define RTPCS_SDS_CNT 14
|
#define RTPCS_SDS_CNT 14
|
||||||
#define RTPCS_MAX_LINKS_PER_SDS 8
|
#define RTPCS_MAX_LINKS_PER_SDS 8
|
||||||
@ -4241,39 +4244,133 @@ static void rtpcs_sds_put_fwnode(void *data)
|
|||||||
fwnode_handle_put(sds->fwnode);
|
fwnode_handle_put(sds->fwnode);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rtpcs_count_links(struct rtpcs_ctrl *ctrl)
|
static void rtpcs_del_provider_action(void *data)
|
||||||
{
|
{
|
||||||
struct device_node *consumer __free(device_node) = NULL;
|
struct rtpcs_serdes *sds = data;
|
||||||
struct of_phandle_args args;
|
|
||||||
|
|
||||||
for_each_node_with_property(consumer, "pcs-handle") {
|
fwnode_pcs_del_provider(sds->fwnode);
|
||||||
int idx = 0;
|
|
||||||
|
|
||||||
if (!of_device_is_available(consumer))
|
rtnl_lock();
|
||||||
|
for (int i = 0; i < RTPCS_MAX_LINKS_PER_SDS; i++) {
|
||||||
|
if (!sds->link[i])
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
while (!of_parse_phandle_with_args(consumer, "pcs-handle",
|
phylink_release_pcs(&sds->link[i]->pcs);
|
||||||
"#pcs-cells", idx++, &args)) {
|
|
||||||
struct device_node *arg_np __free(device_node) = args.np;
|
|
||||||
|
|
||||||
for (int s = 0; s < ctrl->cfg->serdes_count; s++) {
|
|
||||||
struct rtpcs_serdes *sds = &ctrl->serdes[s];
|
|
||||||
|
|
||||||
if (of_fwnode_handle(arg_np) != sds->fwnode)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (sds->num_of_links >= RTPCS_MAX_LINKS_PER_SDS) {
|
|
||||||
dev_warn(ctrl->dev,
|
|
||||||
"%pOF: pcs-handle to sds%u exceeds max %u, clamping\n",
|
|
||||||
consumer, sds->id, RTPCS_MAX_LINKS_PER_SDS);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
sds->num_of_links++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
rtnl_unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct rtpcs_serdes *rtpcs_find_serdes(struct rtpcs_ctrl *ctrl,
|
||||||
|
struct fwnode_handle *fwnode)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ctrl->cfg->serdes_count; i++) {
|
||||||
|
if (ctrl->serdes[i].fwnode == fwnode)
|
||||||
|
return &ctrl->serdes[i];
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Walk the sibling switch's ethernet-ports subtree to learn which MAC port
|
||||||
|
* each (SerDes, link_idx) pair serves. Same "backwards" topology lookup the
|
||||||
|
* sibling MDIO driver does for phy-handle: the DT already encodes the
|
||||||
|
* mapping via per-port pcs-handle properties, so the driver doesn't need a
|
||||||
|
* parallel per-SoC table. pcs_get_state still needs the port number to
|
||||||
|
* index MAC-side link status registers; it reads link_port[] populated
|
||||||
|
* here.
|
||||||
|
*/
|
||||||
|
static int rtpcs_map_links(struct device *dev, struct rtpcs_ctrl *ctrl)
|
||||||
|
{
|
||||||
|
struct fwnode_handle *fw_dev = dev_fwnode(dev);
|
||||||
|
struct fwnode_handle *fw_parent __free(fwnode_handle) = fwnode_get_parent(fw_dev);
|
||||||
|
if (!fw_parent)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
struct fwnode_handle *fw_switch __free(fwnode_handle) =
|
||||||
|
fwnode_get_named_child_node(fw_parent, "ethernet-switch");
|
||||||
|
if (!fw_switch)
|
||||||
|
return dev_err_probe(dev, -ENODEV, "%pfwP missing ethernet-switch\n", fw_parent);
|
||||||
|
|
||||||
|
struct fwnode_handle *fw_ports __free(fwnode_handle) =
|
||||||
|
fwnode_get_named_child_node(fw_switch, "ethernet-ports");
|
||||||
|
if (!fw_ports)
|
||||||
|
return dev_err_probe(dev, -ENODEV, "%pfwP missing ethernet-ports\n", fw_switch);
|
||||||
|
|
||||||
|
fwnode_for_each_child_node_scoped(fw_ports, fw_port) {
|
||||||
|
struct fwnode_reference_args args;
|
||||||
|
struct rtpcs_serdes *sds;
|
||||||
|
int link_idx, ret;
|
||||||
|
u32 pn;
|
||||||
|
|
||||||
|
if (fwnode_property_read_u32(fw_port, "reg", &pn))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ret = fwnode_property_get_reference_args(fw_port, "pcs-handle", "#pcs-cells",
|
||||||
|
-1, 0, &args);
|
||||||
|
if (ret)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
struct fwnode_handle *fw_pcs __free(fwnode_handle) = args.fwnode;
|
||||||
|
link_idx = args.args[0];
|
||||||
|
|
||||||
|
if (link_idx >= RTPCS_MAX_LINKS_PER_SDS)
|
||||||
|
return dev_err_probe(dev, -ERANGE,
|
||||||
|
"%pfwP: pcs-handle link %d exceeds max %u\n",
|
||||||
|
fw_port, link_idx, RTPCS_MAX_LINKS_PER_SDS);
|
||||||
|
|
||||||
|
sds = rtpcs_find_serdes(ctrl, fw_pcs);
|
||||||
|
if (!sds)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (sds->link_port[link_idx] >= 0)
|
||||||
|
return dev_err_probe(dev, -EEXIST,
|
||||||
|
"%pfwP: sds%u link %d already assigned to port %d\n",
|
||||||
|
fw_port, sds->id, link_idx, sds->link_port[link_idx]);
|
||||||
|
|
||||||
|
sds->link_port[link_idx] = pn;
|
||||||
|
sds->num_of_links++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct phylink_pcs *rtpcs_pcs_get(struct fwnode_reference_args *pcsspec, void *data)
|
||||||
|
{
|
||||||
|
struct rtpcs_serdes *sds = data;
|
||||||
|
struct rtpcs_link *link;
|
||||||
|
unsigned int link_idx;
|
||||||
|
struct device *dev;
|
||||||
|
|
||||||
|
dev = sds->ctrl->dev;
|
||||||
|
if (!pcsspec->nargs) {
|
||||||
|
dev_err(dev, "invalid number of cells in 'pcs' property\n");
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
link_idx = pcsspec->args[0];
|
||||||
|
if (link_idx >= RTPCS_MAX_LINKS_PER_SDS)
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
|
||||||
|
if (sds->link_port[link_idx] < 0) {
|
||||||
|
dev_err(dev, "sds %u link %d not associated with any port\n",
|
||||||
|
sds->id, link_idx);
|
||||||
|
return ERR_PTR(-ENODEV);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sds->link[link_idx]) {
|
||||||
|
link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL);
|
||||||
|
if (!link)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
link->ctrl = sds->ctrl;
|
||||||
|
link->port = sds->link_port[link_idx];
|
||||||
|
link->sds = sds;
|
||||||
|
link->pcs.ops = sds->ctrl->cfg->pcs_ops;
|
||||||
|
|
||||||
|
sds->link[link_idx] = link;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sds->link[link_idx]->pcs;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rtpcs_probe(struct platform_device *pdev)
|
static int rtpcs_probe(struct platform_device *pdev)
|
||||||
@ -4332,7 +4429,9 @@ static int rtpcs_probe(struct platform_device *pdev)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
rtpcs_count_links(ctrl);
|
ret = rtpcs_map_links(dev, ctrl);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
if (ctrl->cfg->init) {
|
if (ctrl->cfg->init) {
|
||||||
ret = ctrl->cfg->init(ctrl);
|
ret = ctrl->cfg->init(ctrl);
|
||||||
@ -4346,8 +4445,21 @@ static int rtpcs_probe(struct platform_device *pdev)
|
|||||||
*/
|
*/
|
||||||
platform_set_drvdata(pdev, ctrl);
|
platform_set_drvdata(pdev, ctrl);
|
||||||
|
|
||||||
dev_info(dev, "Realtek PCS driver initialized\n");
|
for (i = 0; i < ctrl->cfg->serdes_count; i++) {
|
||||||
|
sds = &ctrl->serdes[i];
|
||||||
|
if (!sds->fwnode)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ret = fwnode_pcs_add_provider(sds->fwnode, rtpcs_pcs_get, sds);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
ret = devm_add_action_or_reset(dev, rtpcs_del_provider_action,
|
||||||
|
sds);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(dev, "Realtek PCS driver initialized\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,13 +11,14 @@ Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
|
|||||||
---
|
---
|
||||||
--- a/drivers/net/pcs/Kconfig
|
--- a/drivers/net/pcs/Kconfig
|
||||||
+++ b/drivers/net/pcs/Kconfig
|
+++ b/drivers/net/pcs/Kconfig
|
||||||
@@ -46,6 +46,15 @@ config PCS_MTK_USXGMII
|
@@ -46,6 +46,16 @@ config PCS_MTK_USXGMII
|
||||||
1000Base-X, 2500Base-X and Cisco SGMII are supported on the same
|
1000Base-X, 2500Base-X and Cisco SGMII are supported on the same
|
||||||
differential pairs via an embedded LynxI PCS.
|
differential pairs via an embedded LynxI PCS.
|
||||||
|
|
||||||
+config PCS_RTL_OTTO
|
+config PCS_RTL_OTTO
|
||||||
+ tristate "Realtek Otto SerDes PCS"
|
+ tristate "Realtek Otto SerDes PCS"
|
||||||
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
|
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
|
||||||
|
+ select FWNODE_PCS
|
||||||
+ select PHY_COMMON_PROPS
|
+ select PHY_COMMON_PROPS
|
||||||
+ select PHYLINK
|
+ select PHYLINK
|
||||||
+ select REGMAP
|
+ select REGMAP
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user