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