162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) 262306a36Sopenharmony_ci/* Copyright 2020 NXP 362306a36Sopenharmony_ci * Lynx PCS MDIO helpers 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/mdio.h> 762306a36Sopenharmony_ci#include <linux/phylink.h> 862306a36Sopenharmony_ci#include <linux/pcs-lynx.h> 962306a36Sopenharmony_ci#include <linux/property.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#define SGMII_CLOCK_PERIOD_NS 8 /* PCS is clocked at 125 MHz */ 1262306a36Sopenharmony_ci#define LINK_TIMER_VAL(ns) ((u32)((ns) / SGMII_CLOCK_PERIOD_NS)) 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#define LINK_TIMER_LO 0x12 1562306a36Sopenharmony_ci#define LINK_TIMER_HI 0x13 1662306a36Sopenharmony_ci#define IF_MODE 0x14 1762306a36Sopenharmony_ci#define IF_MODE_SGMII_EN BIT(0) 1862306a36Sopenharmony_ci#define IF_MODE_USE_SGMII_AN BIT(1) 1962306a36Sopenharmony_ci#define IF_MODE_SPEED(x) (((x) << 2) & GENMASK(3, 2)) 2062306a36Sopenharmony_ci#define IF_MODE_SPEED_MSK GENMASK(3, 2) 2162306a36Sopenharmony_ci#define IF_MODE_HALF_DUPLEX BIT(4) 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistruct lynx_pcs { 2462306a36Sopenharmony_ci struct phylink_pcs pcs; 2562306a36Sopenharmony_ci struct mdio_device *mdio; 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cienum sgmii_speed { 2962306a36Sopenharmony_ci SGMII_SPEED_10 = 0, 3062306a36Sopenharmony_ci SGMII_SPEED_100 = 1, 3162306a36Sopenharmony_ci SGMII_SPEED_1000 = 2, 3262306a36Sopenharmony_ci SGMII_SPEED_2500 = 2, 3362306a36Sopenharmony_ci}; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define phylink_pcs_to_lynx(pl_pcs) container_of((pl_pcs), struct lynx_pcs, pcs) 3662306a36Sopenharmony_ci#define lynx_to_phylink_pcs(lynx) (&(lynx)->pcs) 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic void lynx_pcs_get_state_usxgmii(struct mdio_device *pcs, 3962306a36Sopenharmony_ci struct phylink_link_state *state) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci struct mii_bus *bus = pcs->bus; 4262306a36Sopenharmony_ci int addr = pcs->addr; 4362306a36Sopenharmony_ci int status, lpa; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci status = mdiobus_c45_read(bus, addr, MDIO_MMD_VEND2, MII_BMSR); 4662306a36Sopenharmony_ci if (status < 0) 4762306a36Sopenharmony_ci return; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci state->link = !!(status & MDIO_STAT1_LSTATUS); 5062306a36Sopenharmony_ci state->an_complete = !!(status & MDIO_AN_STAT1_COMPLETE); 5162306a36Sopenharmony_ci if (!state->link || !state->an_complete) 5262306a36Sopenharmony_ci return; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci lpa = mdiobus_c45_read(bus, addr, MDIO_MMD_VEND2, MII_LPA); 5562306a36Sopenharmony_ci if (lpa < 0) 5662306a36Sopenharmony_ci return; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci phylink_decode_usxgmii_word(state, lpa); 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic void lynx_pcs_get_state_2500basex(struct mdio_device *pcs, 6262306a36Sopenharmony_ci struct phylink_link_state *state) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci int bmsr, lpa; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci bmsr = mdiodev_read(pcs, MII_BMSR); 6762306a36Sopenharmony_ci lpa = mdiodev_read(pcs, MII_LPA); 6862306a36Sopenharmony_ci if (bmsr < 0 || lpa < 0) { 6962306a36Sopenharmony_ci state->link = false; 7062306a36Sopenharmony_ci return; 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci state->link = !!(bmsr & BMSR_LSTATUS); 7462306a36Sopenharmony_ci state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE); 7562306a36Sopenharmony_ci if (!state->link) 7662306a36Sopenharmony_ci return; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci state->speed = SPEED_2500; 7962306a36Sopenharmony_ci state->pause |= MLO_PAUSE_TX | MLO_PAUSE_RX; 8062306a36Sopenharmony_ci state->duplex = DUPLEX_FULL; 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic void lynx_pcs_get_state(struct phylink_pcs *pcs, 8462306a36Sopenharmony_ci struct phylink_link_state *state) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci switch (state->interface) { 8962306a36Sopenharmony_ci case PHY_INTERFACE_MODE_1000BASEX: 9062306a36Sopenharmony_ci case PHY_INTERFACE_MODE_SGMII: 9162306a36Sopenharmony_ci case PHY_INTERFACE_MODE_QSGMII: 9262306a36Sopenharmony_ci phylink_mii_c22_pcs_get_state(lynx->mdio, state); 9362306a36Sopenharmony_ci break; 9462306a36Sopenharmony_ci case PHY_INTERFACE_MODE_2500BASEX: 9562306a36Sopenharmony_ci lynx_pcs_get_state_2500basex(lynx->mdio, state); 9662306a36Sopenharmony_ci break; 9762306a36Sopenharmony_ci case PHY_INTERFACE_MODE_USXGMII: 9862306a36Sopenharmony_ci lynx_pcs_get_state_usxgmii(lynx->mdio, state); 9962306a36Sopenharmony_ci break; 10062306a36Sopenharmony_ci case PHY_INTERFACE_MODE_10GBASER: 10162306a36Sopenharmony_ci phylink_mii_c45_pcs_get_state(lynx->mdio, state); 10262306a36Sopenharmony_ci break; 10362306a36Sopenharmony_ci default: 10462306a36Sopenharmony_ci break; 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci dev_dbg(&lynx->mdio->dev, 10862306a36Sopenharmony_ci "mode=%s/%s/%s link=%u an_complete=%u\n", 10962306a36Sopenharmony_ci phy_modes(state->interface), 11062306a36Sopenharmony_ci phy_speed_to_str(state->speed), 11162306a36Sopenharmony_ci phy_duplex_to_str(state->duplex), 11262306a36Sopenharmony_ci state->link, state->an_complete); 11362306a36Sopenharmony_ci} 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_cistatic int lynx_pcs_config_giga(struct mdio_device *pcs, 11662306a36Sopenharmony_ci phy_interface_t interface, 11762306a36Sopenharmony_ci const unsigned long *advertising, 11862306a36Sopenharmony_ci unsigned int neg_mode) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci int link_timer_ns; 12162306a36Sopenharmony_ci u32 link_timer; 12262306a36Sopenharmony_ci u16 if_mode; 12362306a36Sopenharmony_ci int err; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci link_timer_ns = phylink_get_link_timer_ns(interface); 12662306a36Sopenharmony_ci if (link_timer_ns > 0) { 12762306a36Sopenharmony_ci link_timer = LINK_TIMER_VAL(link_timer_ns); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci mdiodev_write(pcs, LINK_TIMER_LO, link_timer & 0xffff); 13062306a36Sopenharmony_ci mdiodev_write(pcs, LINK_TIMER_HI, link_timer >> 16); 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (interface == PHY_INTERFACE_MODE_1000BASEX) { 13462306a36Sopenharmony_ci if_mode = 0; 13562306a36Sopenharmony_ci } else { 13662306a36Sopenharmony_ci /* SGMII and QSGMII */ 13762306a36Sopenharmony_ci if_mode = IF_MODE_SGMII_EN; 13862306a36Sopenharmony_ci if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) 13962306a36Sopenharmony_ci if_mode |= IF_MODE_USE_SGMII_AN; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci err = mdiodev_modify(pcs, IF_MODE, 14362306a36Sopenharmony_ci IF_MODE_SGMII_EN | IF_MODE_USE_SGMII_AN, 14462306a36Sopenharmony_ci if_mode); 14562306a36Sopenharmony_ci if (err) 14662306a36Sopenharmony_ci return err; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci return phylink_mii_c22_pcs_config(pcs, interface, advertising, 14962306a36Sopenharmony_ci neg_mode); 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic int lynx_pcs_config_usxgmii(struct mdio_device *pcs, 15362306a36Sopenharmony_ci const unsigned long *advertising, 15462306a36Sopenharmony_ci unsigned int neg_mode) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci struct mii_bus *bus = pcs->bus; 15762306a36Sopenharmony_ci int addr = pcs->addr; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) { 16062306a36Sopenharmony_ci dev_err(&pcs->dev, "USXGMII only supports in-band AN for now\n"); 16162306a36Sopenharmony_ci return -EOPNOTSUPP; 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* Configure device ability for the USXGMII Replicator */ 16562306a36Sopenharmony_ci return mdiobus_c45_write(bus, addr, MDIO_MMD_VEND2, MII_ADVERTISE, 16662306a36Sopenharmony_ci MDIO_USXGMII_10G | MDIO_USXGMII_LINK | 16762306a36Sopenharmony_ci MDIO_USXGMII_FULL_DUPLEX | 16862306a36Sopenharmony_ci ADVERTISE_SGMII | ADVERTISE_LPACK); 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic int lynx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, 17262306a36Sopenharmony_ci phy_interface_t ifmode, 17362306a36Sopenharmony_ci const unsigned long *advertising, bool permit) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci switch (ifmode) { 17862306a36Sopenharmony_ci case PHY_INTERFACE_MODE_1000BASEX: 17962306a36Sopenharmony_ci case PHY_INTERFACE_MODE_SGMII: 18062306a36Sopenharmony_ci case PHY_INTERFACE_MODE_QSGMII: 18162306a36Sopenharmony_ci return lynx_pcs_config_giga(lynx->mdio, ifmode, advertising, 18262306a36Sopenharmony_ci neg_mode); 18362306a36Sopenharmony_ci case PHY_INTERFACE_MODE_2500BASEX: 18462306a36Sopenharmony_ci if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { 18562306a36Sopenharmony_ci dev_err(&lynx->mdio->dev, 18662306a36Sopenharmony_ci "AN not supported on 3.125GHz SerDes lane\n"); 18762306a36Sopenharmony_ci return -EOPNOTSUPP; 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci break; 19062306a36Sopenharmony_ci case PHY_INTERFACE_MODE_USXGMII: 19162306a36Sopenharmony_ci return lynx_pcs_config_usxgmii(lynx->mdio, advertising, 19262306a36Sopenharmony_ci neg_mode); 19362306a36Sopenharmony_ci case PHY_INTERFACE_MODE_10GBASER: 19462306a36Sopenharmony_ci /* Nothing to do here for 10GBASER */ 19562306a36Sopenharmony_ci break; 19662306a36Sopenharmony_ci default: 19762306a36Sopenharmony_ci return -EOPNOTSUPP; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci return 0; 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic void lynx_pcs_an_restart(struct phylink_pcs *pcs) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci phylink_mii_c22_pcs_an_restart(lynx->mdio); 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_cistatic void lynx_pcs_link_up_sgmii(struct mdio_device *pcs, 21162306a36Sopenharmony_ci unsigned int neg_mode, 21262306a36Sopenharmony_ci int speed, int duplex) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci u16 if_mode = 0, sgmii_speed; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci /* The PCS needs to be configured manually only 21762306a36Sopenharmony_ci * when not operating on in-band mode 21862306a36Sopenharmony_ci */ 21962306a36Sopenharmony_ci if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) 22062306a36Sopenharmony_ci return; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci if (duplex == DUPLEX_HALF) 22362306a36Sopenharmony_ci if_mode |= IF_MODE_HALF_DUPLEX; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci switch (speed) { 22662306a36Sopenharmony_ci case SPEED_1000: 22762306a36Sopenharmony_ci sgmii_speed = SGMII_SPEED_1000; 22862306a36Sopenharmony_ci break; 22962306a36Sopenharmony_ci case SPEED_100: 23062306a36Sopenharmony_ci sgmii_speed = SGMII_SPEED_100; 23162306a36Sopenharmony_ci break; 23262306a36Sopenharmony_ci case SPEED_10: 23362306a36Sopenharmony_ci sgmii_speed = SGMII_SPEED_10; 23462306a36Sopenharmony_ci break; 23562306a36Sopenharmony_ci case SPEED_UNKNOWN: 23662306a36Sopenharmony_ci /* Silently don't do anything */ 23762306a36Sopenharmony_ci return; 23862306a36Sopenharmony_ci default: 23962306a36Sopenharmony_ci dev_err(&pcs->dev, "Invalid PCS speed %d\n", speed); 24062306a36Sopenharmony_ci return; 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci if_mode |= IF_MODE_SPEED(sgmii_speed); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci mdiodev_modify(pcs, IF_MODE, 24562306a36Sopenharmony_ci IF_MODE_HALF_DUPLEX | IF_MODE_SPEED_MSK, 24662306a36Sopenharmony_ci if_mode); 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci/* 2500Base-X is SerDes protocol 7 on Felix and 6 on ENETC. It is a SerDes lane 25062306a36Sopenharmony_ci * clocked at 3.125 GHz which encodes symbols with 8b/10b and does not have 25162306a36Sopenharmony_ci * auto-negotiation of any link parameters. Electrically it is compatible with 25262306a36Sopenharmony_ci * a single lane of XAUI. 25362306a36Sopenharmony_ci * The hardware reference manual wants to call this mode SGMII, but it isn't 25462306a36Sopenharmony_ci * really, since the fundamental features of SGMII: 25562306a36Sopenharmony_ci * - Downgrading the link speed by duplicating symbols 25662306a36Sopenharmony_ci * - Auto-negotiation 25762306a36Sopenharmony_ci * are not there. 25862306a36Sopenharmony_ci * The speed is configured at 1000 in the IF_MODE because the clock frequency 25962306a36Sopenharmony_ci * is actually given by a PLL configured in the Reset Configuration Word (RCW). 26062306a36Sopenharmony_ci * Since there is no difference between fixed speed SGMII w/o AN and 802.3z w/o 26162306a36Sopenharmony_ci * AN, we call this PHY interface type 2500Base-X. In case a PHY negotiates a 26262306a36Sopenharmony_ci * lower link speed on line side, the system-side interface remains fixed at 26362306a36Sopenharmony_ci * 2500 Mbps and we do rate adaptation through pause frames. 26462306a36Sopenharmony_ci */ 26562306a36Sopenharmony_cistatic void lynx_pcs_link_up_2500basex(struct mdio_device *pcs, 26662306a36Sopenharmony_ci unsigned int neg_mode, 26762306a36Sopenharmony_ci int speed, int duplex) 26862306a36Sopenharmony_ci{ 26962306a36Sopenharmony_ci u16 if_mode = 0; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { 27262306a36Sopenharmony_ci dev_err(&pcs->dev, "AN not supported for 2500BaseX\n"); 27362306a36Sopenharmony_ci return; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci if (duplex == DUPLEX_HALF) 27762306a36Sopenharmony_ci if_mode |= IF_MODE_HALF_DUPLEX; 27862306a36Sopenharmony_ci if_mode |= IF_MODE_SPEED(SGMII_SPEED_2500); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci mdiodev_modify(pcs, IF_MODE, 28162306a36Sopenharmony_ci IF_MODE_HALF_DUPLEX | IF_MODE_SPEED_MSK, 28262306a36Sopenharmony_ci if_mode); 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic void lynx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, 28662306a36Sopenharmony_ci phy_interface_t interface, 28762306a36Sopenharmony_ci int speed, int duplex) 28862306a36Sopenharmony_ci{ 28962306a36Sopenharmony_ci struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci switch (interface) { 29262306a36Sopenharmony_ci case PHY_INTERFACE_MODE_SGMII: 29362306a36Sopenharmony_ci case PHY_INTERFACE_MODE_QSGMII: 29462306a36Sopenharmony_ci lynx_pcs_link_up_sgmii(lynx->mdio, neg_mode, speed, duplex); 29562306a36Sopenharmony_ci break; 29662306a36Sopenharmony_ci case PHY_INTERFACE_MODE_2500BASEX: 29762306a36Sopenharmony_ci lynx_pcs_link_up_2500basex(lynx->mdio, neg_mode, speed, duplex); 29862306a36Sopenharmony_ci break; 29962306a36Sopenharmony_ci case PHY_INTERFACE_MODE_USXGMII: 30062306a36Sopenharmony_ci /* At the moment, only in-band AN is supported for USXGMII 30162306a36Sopenharmony_ci * so nothing to do in link_up 30262306a36Sopenharmony_ci */ 30362306a36Sopenharmony_ci break; 30462306a36Sopenharmony_ci default: 30562306a36Sopenharmony_ci break; 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci} 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_cistatic const struct phylink_pcs_ops lynx_pcs_phylink_ops = { 31062306a36Sopenharmony_ci .pcs_get_state = lynx_pcs_get_state, 31162306a36Sopenharmony_ci .pcs_config = lynx_pcs_config, 31262306a36Sopenharmony_ci .pcs_an_restart = lynx_pcs_an_restart, 31362306a36Sopenharmony_ci .pcs_link_up = lynx_pcs_link_up, 31462306a36Sopenharmony_ci}; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_cistatic struct phylink_pcs *lynx_pcs_create(struct mdio_device *mdio) 31762306a36Sopenharmony_ci{ 31862306a36Sopenharmony_ci struct lynx_pcs *lynx; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci lynx = kzalloc(sizeof(*lynx), GFP_KERNEL); 32162306a36Sopenharmony_ci if (!lynx) 32262306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci mdio_device_get(mdio); 32562306a36Sopenharmony_ci lynx->mdio = mdio; 32662306a36Sopenharmony_ci lynx->pcs.ops = &lynx_pcs_phylink_ops; 32762306a36Sopenharmony_ci lynx->pcs.neg_mode = true; 32862306a36Sopenharmony_ci lynx->pcs.poll = true; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci return lynx_to_phylink_pcs(lynx); 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_cistruct phylink_pcs *lynx_pcs_create_mdiodev(struct mii_bus *bus, int addr) 33462306a36Sopenharmony_ci{ 33562306a36Sopenharmony_ci struct mdio_device *mdio; 33662306a36Sopenharmony_ci struct phylink_pcs *pcs; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci mdio = mdio_device_create(bus, addr); 33962306a36Sopenharmony_ci if (IS_ERR(mdio)) 34062306a36Sopenharmony_ci return ERR_CAST(mdio); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci pcs = lynx_pcs_create(mdio); 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci /* lynx_create() has taken a refcount on the mdiodev if it was 34562306a36Sopenharmony_ci * successful. If lynx_create() fails, this will free the mdio 34662306a36Sopenharmony_ci * device here. In any case, we don't need to hold our reference 34762306a36Sopenharmony_ci * anymore, and putting it here will allow mdio_device_put() in 34862306a36Sopenharmony_ci * lynx_destroy() to automatically free the mdio device. 34962306a36Sopenharmony_ci */ 35062306a36Sopenharmony_ci mdio_device_put(mdio); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci return pcs; 35362306a36Sopenharmony_ci} 35462306a36Sopenharmony_ciEXPORT_SYMBOL(lynx_pcs_create_mdiodev); 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci/* 35762306a36Sopenharmony_ci * lynx_pcs_create_fwnode() creates a lynx PCS instance from the fwnode 35862306a36Sopenharmony_ci * device indicated by node. 35962306a36Sopenharmony_ci * 36062306a36Sopenharmony_ci * Returns: 36162306a36Sopenharmony_ci * -ENODEV if the fwnode is marked unavailable 36262306a36Sopenharmony_ci * -EPROBE_DEFER if we fail to find the device 36362306a36Sopenharmony_ci * -ENOMEM if we fail to allocate memory 36462306a36Sopenharmony_ci * pointer to a phylink_pcs on success 36562306a36Sopenharmony_ci */ 36662306a36Sopenharmony_cistruct phylink_pcs *lynx_pcs_create_fwnode(struct fwnode_handle *node) 36762306a36Sopenharmony_ci{ 36862306a36Sopenharmony_ci struct mdio_device *mdio; 36962306a36Sopenharmony_ci struct phylink_pcs *pcs; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci if (!fwnode_device_is_available(node)) 37262306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci mdio = fwnode_mdio_find_device(node); 37562306a36Sopenharmony_ci if (!mdio) 37662306a36Sopenharmony_ci return ERR_PTR(-EPROBE_DEFER); 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci pcs = lynx_pcs_create(mdio); 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci /* lynx_create() has taken a refcount on the mdiodev if it was 38162306a36Sopenharmony_ci * successful. If lynx_create() fails, this will free the mdio 38262306a36Sopenharmony_ci * device here. In any case, we don't need to hold our reference 38362306a36Sopenharmony_ci * anymore, and putting it here will allow mdio_device_put() in 38462306a36Sopenharmony_ci * lynx_destroy() to automatically free the mdio device. 38562306a36Sopenharmony_ci */ 38662306a36Sopenharmony_ci mdio_device_put(mdio); 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci return pcs; 38962306a36Sopenharmony_ci} 39062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(lynx_pcs_create_fwnode); 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_civoid lynx_pcs_destroy(struct phylink_pcs *pcs) 39362306a36Sopenharmony_ci{ 39462306a36Sopenharmony_ci struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci mdio_device_put(lynx->mdio); 39762306a36Sopenharmony_ci kfree(lynx); 39862306a36Sopenharmony_ci} 39962306a36Sopenharmony_ciEXPORT_SYMBOL(lynx_pcs_destroy); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 402