[net-next,17/20] net: ethernet: qualcomm: Add PPE UNIPHY support for phylink

Message ID 20240110114033.32575-18-quic_luoj@quicinc.com
State New
Headers
Series net: ethernet: Add qcom PPE driver |

Commit Message

Jie Luo Jan. 10, 2024, 11:40 a.m. UTC
  From: Lei Wei <quic_leiwei@quicinc.com>

This driver adds support for PPE UNIPHY initialization and UNIPHY
PCS operations which used by phylink.

PPE supports maximum 6 GMAC or XGMAC ports which can be connected
with maximum 3 UNIPHYs. The UNIPHY registers and provides raw clock
to feeds NCCSS clocks to provide different clocks to PPE ports in
different link speed.

Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
---
 drivers/net/ethernet/qualcomm/ppe/Makefile    |   2 +-
 drivers/net/ethernet/qualcomm/ppe/ppe.c       |  25 +
 drivers/net/ethernet/qualcomm/ppe/ppe.h       |   2 +
 .../net/ethernet/qualcomm/ppe/ppe_uniphy.c    | 789 ++++++++++++++++++
 .../net/ethernet/qualcomm/ppe/ppe_uniphy.h    | 227 +++++
 include/linux/soc/qcom/ppe.h                  |   1 +
 6 files changed, 1045 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.c
 create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.h
  

Comments

Russell King (Oracle) Jan. 10, 2024, 12:09 p.m. UTC | #1
On Wed, Jan 10, 2024 at 07:40:29PM +0800, Luo Jie wrote:
> +static int clk_uniphy_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	struct clk_uniphy *uniphy = to_clk_uniphy(hw);
> +
> +	if (rate != UNIPHY_CLK_RATE_125M && rate != UNIPHY_CLK_RATE_312P5M)
> +		return -1;

Sigh. I get very annoyed off by stuff like this. It's lazy programming,
and makes me wonder why I should be bothered to spend time reviewing if
the programmer can't be bothered to pay attention to details. It makes
me wonder what else is done lazily in the patch.

-1 is -EPERM "Operation not permitted". This is highly likely not an
appropriate error code for this code.

> +int ppe_uniphy_autoneg_complete_check(struct ppe_uniphy *uniphy, int port)
> +{
> +	u32 reg, val;
> +	int channel, ret;
> +
> +	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII ||
> +	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
> +		/* Only uniphy0 may have multi channels */
> +		channel = (uniphy->index == 0) ? (port - 1) : 0;
> +		reg = (channel == 0) ? VR_MII_AN_INTR_STS_ADDR :
> +		       VR_MII_AN_INTR_STS_CHANNEL_ADDR(channel);
> +
> +		/* Wait auto negotiation complete */
> +		ret = read_poll_timeout(ppe_uniphy_read, val,
> +					(val & CL37_ANCMPLT_INTR),
> +					1000, 100000, true,
> +					uniphy, reg);
> +		if (ret) {
> +			dev_err(uniphy->ppe_dev->dev,
> +				"uniphy %d auto negotiation timeout\n", uniphy->index);
> +			return ret;
> +		}
> +
> +		/* Clear auto negotiation complete interrupt */
> +		ppe_uniphy_mask(uniphy, reg, CL37_ANCMPLT_INTR, 0);
> +	}
> +
> +	return 0;
> +}

Why is this necessary? Why is it callable outside this file? Shouldn't
this be done in the .pcs_get_state method? If negotiation hasn't
completed (and negotiation is being used) then .pcs_get_state should not
report that the link is up.

> +
> +int ppe_uniphy_speed_set(struct ppe_uniphy *uniphy, int port, int speed)
> +{
> +	u32 reg, val;
> +	int channel;
> +
> +	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII ||
> +	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
> +		/* Only uniphy0 may have multiple channels */
> +		channel = (uniphy->index == 0) ? (port - 1) : 0;
> +
> +		reg = (channel == 0) ? SR_MII_CTRL_ADDR :
> +		       SR_MII_CTRL_CHANNEL_ADDR(channel);
> +
> +		switch (speed) {
> +		case SPEED_100:
> +			val = USXGMII_SPEED_100;
> +			break;
> +		case SPEED_1000:
> +			val = USXGMII_SPEED_1000;
> +			break;
> +		case SPEED_2500:
> +			val = USXGMII_SPEED_2500;
> +			break;
> +		case SPEED_5000:
> +			val = USXGMII_SPEED_5000;
> +			break;
> +		case SPEED_10000:
> +			val = USXGMII_SPEED_10000;
> +			break;
> +		case SPEED_10:
> +			val = USXGMII_SPEED_10;
> +			break;
> +		default:
> +			val = 0;
> +			break;
> +		}
> +
> +		ppe_uniphy_mask(uniphy, reg, USXGMII_SPEED_MASK, val);
> +	}
> +
> +	return 0;
> +}
> +
> +int ppe_uniphy_duplex_set(struct ppe_uniphy *uniphy, int port, int duplex)
> +{
> +	u32 reg;
> +	int channel;
> +
> +	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII &&
> +	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
> +		/* Only uniphy0 may have multiple channels */
> +		channel = (uniphy->index == 0) ? (port - 1) : 0;
> +
> +		reg = (channel == 0) ? SR_MII_CTRL_ADDR :
> +		       SR_MII_CTRL_CHANNEL_ADDR(channel);
> +
> +		ppe_uniphy_mask(uniphy, reg, USXGMII_DUPLEX_FULL,
> +				(duplex == DUPLEX_FULL) ? USXGMII_DUPLEX_FULL : 0);
> +	}
> +
> +	return 0;
> +}

What calls the above two functions? Surely this should be called from
the .pcs_link_up method? I would also imagine that you call each of
these consecutively. So why not modify the register in one go rather
than piecemeal like this. I'm not a fan of one-function-to-control-one-
parameter-in-a-register style when it results in more register accesses
than are really necessary.

> +static void ppe_pcs_get_state(struct phylink_pcs *pcs,
> +			      struct phylink_link_state *state)
> +{
> +	struct ppe_uniphy *uniphy = pcs_to_ppe_uniphy(pcs);
> +	u32 val;
> +
> +	switch (state->interface) {
> +	case PHY_INTERFACE_MODE_10GBASER:
> +		val = ppe_uniphy_read(uniphy, SR_XS_PCS_KR_STS1_ADDR);
> +		state->link = (val & SR_XS_PCS_KR_STS1_PLU) ? 1 : 0;

Unnecessary tenary operation.

		state->link = !!(val & SR_XS_PCS_KR_STS1_PLU);

> +		state->duplex = DUPLEX_FULL;
> +		state->speed = SPEED_10000;
> +		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);

Excessive parens.

> +		break;
> +	case PHY_INTERFACE_MODE_2500BASEX:
> +		val = ppe_uniphy_read(uniphy, UNIPHY_CHANNEL0_INPUT_OUTPUT_6_ADDR);
> +		state->link = (val & NEWADDEDFROMHERE_CH0_LINK_MAC) ? 1 : 0;

Ditto.

> +		state->duplex = DUPLEX_FULL;
> +		state->speed = SPEED_2500;
> +		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);

Ditto.

> +		break;
> +	case PHY_INTERFACE_MODE_1000BASEX:
> +	case PHY_INTERFACE_MODE_SGMII:
> +		val = ppe_uniphy_read(uniphy, UNIPHY_CHANNEL0_INPUT_OUTPUT_6_ADDR);
> +		state->link = (val & NEWADDEDFROMHERE_CH0_LINK_MAC) ? 1 : 0;
> +		state->duplex = (val & NEWADDEDFROMHERE_CH0_DUPLEX_MODE_MAC) ?
> +			DUPLEX_FULL : DUPLEX_HALF;
> +		if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_10M)
> +			state->speed = SPEED_10;
> +		else if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_100M)
> +			state->speed = SPEED_100;
> +		else if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_1000M)
> +			state->speed = SPEED_1000;

Looks like a switch(FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val)
would be better here. Also "NEWADDEDFROMHERE" ?

> +		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);

Ditto.

As you make no differentiation between 1000base-X and SGMII, I question
whether your hardware supports 1000base-X. I seem to recall in previous
discussions that it doesn't. So, that means it doesn't support the
inband negotiation word format for 1000base-X. Thus, 1000base-X should
not be included in any of these switch statements, and 1000base-X won't
be usable.

> +/* [register] UNIPHY_MODE_CTRL */
> +#define UNIPHY_MODE_CTRL_ADDR				0x46c
> +#define NEWADDEDFROMHERE_CH0_AUTONEG_MODE		BIT(0)
> +#define NEWADDEDFROMHERE_CH1_CH0_SGMII			BIT(1)
> +#define NEWADDEDFROMHERE_CH4_CH1_0_SGMII		BIT(2)
> +#define NEWADDEDFROMHERE_SGMII_EVEN_LOW			BIT(3)
> +#define NEWADDEDFROMHERE_CH0_MODE_CTRL_25M		GENMASK(6, 4)
> +#define NEWADDEDFROMHERE_CH0_QSGMII_SGMII		BIT(8)
> +#define NEWADDEDFROMHERE_CH0_PSGMII_QSGMII		BIT(9)
> +#define NEWADDEDFROMHERE_SG_MODE			BIT(10)
> +#define NEWADDEDFROMHERE_SGPLUS_MODE			BIT(11)
> +#define NEWADDEDFROMHERE_XPCS_MODE			BIT(12)
> +#define NEWADDEDFROMHERE_USXG_EN			BIT(13)
> +#define NEWADDEDFROMHERE_SW_V17_V18			BIT(15)

Again, why "NEWADDEDFROMHERE" ?

> +/* [register] VR_XS_PCS_EEE_MCTRL0 */
> +#define VR_XS_PCS_EEE_MCTRL0_ADDR			0x38006
> +#define LTX_EN						BIT(0)
> +#define LRX_EN						BIT(1)
> +#define SIGN_BIT					BIT(6)

"SIGN_BIT" is likely too generic a name.

> +#define MULT_FACT_100NS					GENMASK(11, 8)
> +
> +/* [register] VR_XS_PCS_KR_CTRL */
> +#define VR_XS_PCS_KR_CTRL_ADDR	0x38007
> +#define USXG_MODE					GENMASK(12, 10)
> +#define QUXGMII_MODE					(FIELD_PREP(USXG_MODE, 0x5))
> +
> +/* [register] VR_XS_PCS_EEE_TXTIMER */
> +#define VR_XS_PCS_EEE_TXTIMER_ADDR			0x38008
> +#define TSL_RES						GENMASK(5, 0)
> +#define T1U_RES						GENMASK(7, 6)
> +#define TWL_RES						GENMASK(12, 8)
> +#define UNIPHY_XPCS_TSL_TIMER				(FIELD_PREP(TSL_RES, 0xa))
> +#define UNIPHY_XPCS_T1U_TIMER				(FIELD_PREP(TSL_RES, 0x3))
> +#define UNIPHY_XPCS_TWL_TIMER				(FIELD_PREP(TSL_RES, 0x16))
> +
> +/* [register] VR_XS_PCS_EEE_RXTIMER  */
> +#define VR_XS_PCS_EEE_RXTIMER_ADDR			0x38009
> +#define RES_100U					GENMASK(7, 0)
> +#define TWR_RES						GENMASK(13, 8)
> +#define UNIPHY_XPCS_100US_TIMER				(FIELD_PREP(RES_100U, 0xc8))
> +#define UNIPHY_XPCS_TWR_TIMER				(FIELD_PREP(RES_100U, 0x1c))
> +
> +/* [register] VR_XS_PCS_DIG_STS */
> +#define VR_XS_PCS_DIG_STS_ADDR				0x3800a
> +#define AM_COUNT					GENMASK(14, 0)
> +#define QUXGMII_AM_COUNT				(FIELD_PREP(AM_COUNT, 0x6018))
> +
> +/* [register] VR_XS_PCS_EEE_MCTRL1 */
> +#define VR_XS_PCS_EEE_MCTRL1_ADDR			0x3800b
> +#define TRN_LPI						BIT(0)
> +#define TRN_RXLPI					BIT(8)
> +
> +/* [register] VR_MII_1_DIG_CTRL1 */
> +#define VR_MII_DIG_CTRL1_CHANNEL1_ADDR			0x1a8000
> +#define VR_MII_DIG_CTRL1_CHANNEL2_ADDR			0x1b8000
> +#define VR_MII_DIG_CTRL1_CHANNEL3_ADDR			0x1c8000
> +#define VR_MII_DIG_CTRL1_CHANNEL_ADDR(x)		(0x1a8000 + 0x10000 * ((x) - 1))
> +#define CHANNEL_USRA_RST				BIT(5)
> +
> +/* [register] VR_MII_AN_CTRL */
> +#define VR_MII_AN_CTRL_ADDR				0x1f8001
> +#define VR_MII_AN_CTRL_CHANNEL1_ADDR			0x1a8001
> +#define VR_MII_AN_CTRL_CHANNEL2_ADDR			0x1b8001
> +#define VR_MII_AN_CTRL_CHANNEL3_ADDR			0x1c8001
> +#define VR_MII_AN_CTRL_CHANNEL_ADDR(x)			(0x1a8001 + 0x10000 * ((x) - 1))
> +#define MII_AN_INTR_EN					BIT(0)
> +#define MII_CTRL					BIT(8)

Too generic a name.

> +
> +/* [register] VR_MII_AN_INTR_STS */
> +#define VR_MII_AN_INTR_STS_ADDR				0x1f8002
> +#define VR_MII_AN_INTR_STS_CHANNEL1_ADDR		0x1a8002
> +#define VR_MII_AN_INTR_STS_CHANNEL2_ADDR		0x1b8002
> +#define VR_MII_AN_INTR_STS_CHANNEL3_ADDR		0x1c8002
> +#define VR_MII_AN_INTR_STS_CHANNEL_ADDR(x)		(0x1a8002 + 0x10000 * ((x) - 1))
> +#define CL37_ANCMPLT_INTR				BIT(0)
> +
> +/* [register] VR_XAUI_MODE_CTRL */
> +#define VR_XAUI_MODE_CTRL_ADDR				0x1f8004
> +#define VR_XAUI_MODE_CTRL_CHANNEL1_ADDR			0x1a8004
> +#define VR_XAUI_MODE_CTRL_CHANNEL2_ADDR			0x1b8004
> +#define VR_XAUI_MODE_CTRL_CHANNEL3_ADDR			0x1c8004
> +#define VR_XAUI_MODE_CTRL_CHANNEL_ADDR(x)		(0x1a8004 + 0x10000 * ((x) - 1))
> +#define IPG_CHECK					BIT(0)
> +
> +/* [register] SR_MII_CTRL */
> +#define SR_MII_CTRL_ADDR				0x1f0000
> +#define SR_MII_CTRL_CHANNEL1_ADDR			0x1a0000
> +#define SR_MII_CTRL_CHANNEL2_ADDR			0x1b0000
> +#define SR_MII_CTRL_CHANNEL3_ADDR			0x1c0000
> +#define SR_MII_CTRL_CHANNEL_ADDR(x)			(0x1a0000 + 0x10000 * ((x) - 1))


> +#define AN_ENABLE					BIT(12)

Looks like MDIO_AN_CTRL1_ENABLE

> +#define USXGMII_DUPLEX_FULL				BIT(8)
> +#define USXGMII_SPEED_MASK				(BIT(13) | BIT(6) | BIT(5))
> +#define USXGMII_SPEED_10000				(BIT(13) | BIT(6))
> +#define USXGMII_SPEED_5000				(BIT(13) | BIT(5))
> +#define USXGMII_SPEED_2500				BIT(5)
> +#define USXGMII_SPEED_1000				BIT(6)
> +#define USXGMII_SPEED_100				BIT(13)
> +#define USXGMII_SPEED_10				0

Looks rather like the standard IEEE 802.3 definitions except for the
2.5G and 5G speeds. Probably worth a comment stating that they're
slightly different.

> +
> +/* PPE UNIPHY data type */
> +struct ppe_uniphy {
> +	void __iomem *base;
> +	struct ppe_device *ppe_dev;
> +	unsigned int index;
> +	phy_interface_t interface;
> +	struct phylink_pcs pcs;
> +};
> +
> +#define pcs_to_ppe_uniphy(_pcs)				container_of(_pcs, struct ppe_uniphy, pcs)

As this should only be used in the .c file, I suggest making this a
static function in the .c file. There should be no requirement to use
it outside of the .c file.

> +
> +struct ppe_uniphy *ppe_uniphy_setup(struct platform_device *pdev);
> +
> +int ppe_uniphy_speed_set(struct ppe_uniphy *uniphy,
> +			 int port, int speed);
> +
> +int ppe_uniphy_duplex_set(struct ppe_uniphy *uniphy,
> +			  int port, int duplex);
> +
> +int ppe_uniphy_adapter_reset(struct ppe_uniphy *uniphy,
> +			     int port);
> +
> +int ppe_uniphy_autoneg_complete_check(struct ppe_uniphy *uniphy,
> +				      int port);
> +
> +int ppe_uniphy_port_gcc_clock_en_set(struct ppe_uniphy *uniphy,
> +				     int port, bool enable);
> +
> +#endif /* _PPE_UNIPHY_H_ */
> diff --git a/include/linux/soc/qcom/ppe.h b/include/linux/soc/qcom/ppe.h
> index 268109c823ad..d3cb18df33fa 100644
> --- a/include/linux/soc/qcom/ppe.h
> +++ b/include/linux/soc/qcom/ppe.h
> @@ -20,6 +20,7 @@ struct ppe_device {
>  	struct dentry *debugfs_root;
>  	bool is_ppe_probed;
>  	void *ppe_priv;
> +	void *uniphy;

Not struct ppe_uniphy *uniphy? You can declare the struct before use
via:

struct ppe_uniphy;

so you don't need to include ppe_uniphy.h in this header.

Thanks.
  
Lei Wei Jan. 22, 2024, 2:37 p.m. UTC | #2
On 1/10/2024 8:09 PM, Russell King (Oracle) wrote:
> On Wed, Jan 10, 2024 at 07:40:29PM +0800, Luo Jie wrote:
>> +static int clk_uniphy_set_rate(struct clk_hw *hw, unsigned long rate,
>> +			       unsigned long parent_rate)
>> +{
>> +	struct clk_uniphy *uniphy = to_clk_uniphy(hw);
>> +
>> +	if (rate != UNIPHY_CLK_RATE_125M && rate != UNIPHY_CLK_RATE_312P5M)
>> +		return -1;
> 
> Sigh. I get very annoyed off by stuff like this. It's lazy programming,
> and makes me wonder why I should be bothered to spend time reviewing if
> the programmer can't be bothered to pay attention to details. It makes
> me wonder what else is done lazily in the patch.
> 
> -1 is -EPERM "Operation not permitted". This is highly likely not an
> appropriate error code for this code.
> 
Sorry for this. I will update the driver to have appropriate error codes 
where required.

>> +int ppe_uniphy_autoneg_complete_check(struct ppe_uniphy *uniphy, int port)
>> +{
>> +	u32 reg, val;
>> +	int channel, ret;
>> +
>> +	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII ||
>> +	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
>> +		/* Only uniphy0 may have multi channels */
>> +		channel = (uniphy->index == 0) ? (port - 1) : 0;
>> +		reg = (channel == 0) ? VR_MII_AN_INTR_STS_ADDR :
>> +		       VR_MII_AN_INTR_STS_CHANNEL_ADDR(channel);
>> +
>> +		/* Wait auto negotiation complete */
>> +		ret = read_poll_timeout(ppe_uniphy_read, val,
>> +					(val & CL37_ANCMPLT_INTR),
>> +					1000, 100000, true,
>> +					uniphy, reg);
>> +		if (ret) {
>> +			dev_err(uniphy->ppe_dev->dev,
>> +				"uniphy %d auto negotiation timeout\n", uniphy->index);
>> +			return ret;
>> +		}
>> +
>> +		/* Clear auto negotiation complete interrupt */
>> +		ppe_uniphy_mask(uniphy, reg, CL37_ANCMPLT_INTR, 0);
>> +	}
>> +
>> +	return 0;
>> +}
> 
> Why is this necessary? Why is it callable outside this file? Shouldn't
> this be done in the .pcs_get_state method? If negotiation hasn't
> completed (and negotiation is being used) then .pcs_get_state should not
> report that the link is up.
> 
Currently it is called outside this file in the following patch: 
https://lore.kernel.org/netdev/20240110114033.32575-19-quic_luoj@quicinc.com/

Yes, if inband autoneg is used, .pcs_get_state should report the link 
status.  Then this function should not be needed and should be removed. 
I will update the .pcs_get_state method for USXGMII if using inband autoneg.

>> +
>> +int ppe_uniphy_speed_set(struct ppe_uniphy *uniphy, int port, int speed)
>> +{
>> +	u32 reg, val;
>> +	int channel;
>> +
>> +	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII ||
>> +	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
>> +		/* Only uniphy0 may have multiple channels */
>> +		channel = (uniphy->index == 0) ? (port - 1) : 0;
>> +
>> +		reg = (channel == 0) ? SR_MII_CTRL_ADDR :
>> +		       SR_MII_CTRL_CHANNEL_ADDR(channel);
>> +
>> +		switch (speed) {
>> +		case SPEED_100:
>> +			val = USXGMII_SPEED_100;
>> +			break;
>> +		case SPEED_1000:
>> +			val = USXGMII_SPEED_1000;
>> +			break;
>> +		case SPEED_2500:
>> +			val = USXGMII_SPEED_2500;
>> +			break;
>> +		case SPEED_5000:
>> +			val = USXGMII_SPEED_5000;
>> +			break;
>> +		case SPEED_10000:
>> +			val = USXGMII_SPEED_10000;
>> +			break;
>> +		case SPEED_10:
>> +			val = USXGMII_SPEED_10;
>> +			break;
>> +		default:
>> +			val = 0;
>> +			break;
>> +		}
>> +
>> +		ppe_uniphy_mask(uniphy, reg, USXGMII_SPEED_MASK, val);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +int ppe_uniphy_duplex_set(struct ppe_uniphy *uniphy, int port, int duplex)
>> +{
>> +	u32 reg;
>> +	int channel;
>> +
>> +	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII &&
>> +	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
>> +		/* Only uniphy0 may have multiple channels */
>> +		channel = (uniphy->index == 0) ? (port - 1) : 0;
>> +
>> +		reg = (channel == 0) ? SR_MII_CTRL_ADDR :
>> +		       SR_MII_CTRL_CHANNEL_ADDR(channel);
>> +
>> +		ppe_uniphy_mask(uniphy, reg, USXGMII_DUPLEX_FULL,
>> +				(duplex == DUPLEX_FULL) ? USXGMII_DUPLEX_FULL : 0);
>> +	}
>> +
>> +	return 0;
>> +}
> 
> What calls the above two functions? Surely this should be called from
> the .pcs_link_up method? I would also imagine that you call each of
> these consecutively. So why not modify the register in one go rather
> than piecemeal like this. I'm not a fan of one-function-to-control-one-
> parameter-in-a-register style when it results in more register accesses
> than are really necessary.
> 

When we consider the sequence of operations expected from the driver by 
the hardware, the MACand PCSoperations are interleaved. So we were not 
able to clearly separate the MACand PCS operations during link up into
pcs_link_up() and .mac_link_up() ops. So we have avoided using 
pcs_link_up() and included the entire sequence in mac_link_up() op. 
This function is called by the PPE MAC support patch below.

https://lore.kernel.org/netdev/20240110114033.32575-19-quic_luoj@quicinc.com/

The sequence expected by PPE HW from driver for link up is as below:
1. disable uniphy interface clock. (PCS operation)
2. configure the PPE port speed clock. (MAC operation)
3. configure the uniphy pcs speed for usxgmii (PCS operation).
4. configure PPE MAC speed (MAC operation).
5. enable uniphy interface clock (PCS operation).
6. reset uniphy pcs adapter (PCS operation).
7. enable mac (MAC operation).

I will also check whole patch to rework the 
one-function-to-control-one-parameter-in-a-register style being used 
here, thanks for the suggestion.

>> +static void ppe_pcs_get_state(struct phylink_pcs *pcs,
>> +			      struct phylink_link_state *state)
>> +{
>> +	struct ppe_uniphy *uniphy = pcs_to_ppe_uniphy(pcs);
>> +	u32 val;
>> +
>> +	switch (state->interface) {
>> +	case PHY_INTERFACE_MODE_10GBASER:
>> +		val = ppe_uniphy_read(uniphy, SR_XS_PCS_KR_STS1_ADDR);
>> +		state->link = (val & SR_XS_PCS_KR_STS1_PLU) ? 1 : 0;
> 
> Unnecessary tenary operation.
> 
> 		state->link = !!(val & SR_XS_PCS_KR_STS1_PLU);
> 

Sure, Thanks for the suggestion, I will update it.

>> +		state->duplex = DUPLEX_FULL;
>> +		state->speed = SPEED_10000;
>> +		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);
> 
> Excessive parens.
> 
Will update it.

>> +		break;
>> +	case PHY_INTERFACE_MODE_2500BASEX:
>> +		val = ppe_uniphy_read(uniphy, UNIPHY_CHANNEL0_INPUT_OUTPUT_6_ADDR);
>> +		state->link = (val & NEWADDEDFROMHERE_CH0_LINK_MAC) ? 1 : 0;
> 
> Ditto.
> 
Will update it.

>> +		state->duplex = DUPLEX_FULL;
>> +		state->speed = SPEED_2500;
>> +		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);
> 
> Ditto.
> 
Will update it.

>> +		break;
>> +	case PHY_INTERFACE_MODE_1000BASEX:
>> +	case PHY_INTERFACE_MODE_SGMII:
>> +		val = ppe_uniphy_read(uniphy, UNIPHY_CHANNEL0_INPUT_OUTPUT_6_ADDR);
>> +		state->link = (val & NEWADDEDFROMHERE_CH0_LINK_MAC) ? 1 : 0;
>> +		state->duplex = (val & NEWADDEDFROMHERE_CH0_DUPLEX_MODE_MAC) ?
>> +			DUPLEX_FULL : DUPLEX_HALF;
>> +		if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_10M)
>> +			state->speed = SPEED_10;
>> +		else if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_100M)
>> +			state->speed = SPEED_100;
>> +		else if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_1000M)
>> +			state->speed = SPEED_1000;
> 
> Looks like a switch(FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val)
> would be better here. Also "NEWADDEDFROMHERE" ?
> 
Sorry for the confusion, I will translate the register to meaningful name.

>> +		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);
> 
> Ditto.
> 
Will update it.

> As you make no differentiation between 1000base-X and SGMII, I question
> whether your hardware supports 1000base-X. I seem to recall in previous
> discussions that it doesn't. So, that means it doesn't support the
> inband negotiation word format for 1000base-X. Thus, 1000base-X should
> not be included in any of these switch statements, and 1000base-X won't
> be usable.
> 
Our hardware supports both 1000base-x and SGMII auto-neg, the hardware 
can resolve and decode the autoneg result of 1000base-x C37 word format 
and SGMII auto-neg word format. This information after autoneg 
resolution is stored in the same register exported to software. This is 
the reason the same code works for both cases.

>> +/* [register] UNIPHY_MODE_CTRL */
>> +#define UNIPHY_MODE_CTRL_ADDR				0x46c
>> +#define NEWADDEDFROMHERE_CH0_AUTONEG_MODE		BIT(0)
>> +#define NEWADDEDFROMHERE_CH1_CH0_SGMII			BIT(1)
>> +#define NEWADDEDFROMHERE_CH4_CH1_0_SGMII		BIT(2)
>> +#define NEWADDEDFROMHERE_SGMII_EVEN_LOW			BIT(3)
>> +#define NEWADDEDFROMHERE_CH0_MODE_CTRL_25M		GENMASK(6, 4)
>> +#define NEWADDEDFROMHERE_CH0_QSGMII_SGMII		BIT(8)
>> +#define NEWADDEDFROMHERE_CH0_PSGMII_QSGMII		BIT(9)
>> +#define NEWADDEDFROMHERE_SG_MODE			BIT(10)
>> +#define NEWADDEDFROMHERE_SGPLUS_MODE			BIT(11)
>> +#define NEWADDEDFROMHERE_XPCS_MODE			BIT(12)
>> +#define NEWADDEDFROMHERE_USXG_EN			BIT(13)
>> +#define NEWADDEDFROMHERE_SW_V17_V18			BIT(15)
> 
> Again, why "NEWADDEDFROMHERE" ?
> 
Will rename to use proper name.

>> +/* [register] VR_XS_PCS_EEE_MCTRL0 */
>> +#define VR_XS_PCS_EEE_MCTRL0_ADDR			0x38006
>> +#define LTX_EN						BIT(0)
>> +#define LRX_EN						BIT(1)
>> +#define SIGN_BIT					BIT(6)
> 
> "SIGN_BIT" is likely too generic a name.
> 
As above, will rename the register to proper name.

>> +#define MULT_FACT_100NS					GENMASK(11, 8)
>> +
>> +/* [register] VR_XS_PCS_KR_CTRL */
>> +#define VR_XS_PCS_KR_CTRL_ADDR	0x38007
>> +#define USXG_MODE					GENMASK(12, 10)
>> +#define QUXGMII_MODE					(FIELD_PREP(USXG_MODE, 0x5))
>> +
>> +/* [register] VR_XS_PCS_EEE_TXTIMER */
>> +#define VR_XS_PCS_EEE_TXTIMER_ADDR			0x38008
>> +#define TSL_RES						GENMASK(5, 0)
>> +#define T1U_RES						GENMASK(7, 6)
>> +#define TWL_RES						GENMASK(12, 8)
>> +#define UNIPHY_XPCS_TSL_TIMER				(FIELD_PREP(TSL_RES, 0xa))
>> +#define UNIPHY_XPCS_T1U_TIMER				(FIELD_PREP(TSL_RES, 0x3))
>> +#define UNIPHY_XPCS_TWL_TIMER				(FIELD_PREP(TSL_RES, 0x16))
>> +
>> +/* [register] VR_XS_PCS_EEE_RXTIMER  */
>> +#define VR_XS_PCS_EEE_RXTIMER_ADDR			0x38009
>> +#define RES_100U					GENMASK(7, 0)
>> +#define TWR_RES						GENMASK(13, 8)
>> +#define UNIPHY_XPCS_100US_TIMER				(FIELD_PREP(RES_100U, 0xc8))
>> +#define UNIPHY_XPCS_TWR_TIMER				(FIELD_PREP(RES_100U, 0x1c))
>> +
>> +/* [register] VR_XS_PCS_DIG_STS */
>> +#define VR_XS_PCS_DIG_STS_ADDR				0x3800a
>> +#define AM_COUNT					GENMASK(14, 0)
>> +#define QUXGMII_AM_COUNT				(FIELD_PREP(AM_COUNT, 0x6018))
>> +
>> +/* [register] VR_XS_PCS_EEE_MCTRL1 */
>> +#define VR_XS_PCS_EEE_MCTRL1_ADDR			0x3800b
>> +#define TRN_LPI						BIT(0)
>> +#define TRN_RXLPI					BIT(8)
>> +
>> +/* [register] VR_MII_1_DIG_CTRL1 */
>> +#define VR_MII_DIG_CTRL1_CHANNEL1_ADDR			0x1a8000
>> +#define VR_MII_DIG_CTRL1_CHANNEL2_ADDR			0x1b8000
>> +#define VR_MII_DIG_CTRL1_CHANNEL3_ADDR			0x1c8000
>> +#define VR_MII_DIG_CTRL1_CHANNEL_ADDR(x)		(0x1a8000 + 0x10000 * ((x) - 1))
>> +#define CHANNEL_USRA_RST				BIT(5)
>> +
>> +/* [register] VR_MII_AN_CTRL */
>> +#define VR_MII_AN_CTRL_ADDR				0x1f8001
>> +#define VR_MII_AN_CTRL_CHANNEL1_ADDR			0x1a8001
>> +#define VR_MII_AN_CTRL_CHANNEL2_ADDR			0x1b8001
>> +#define VR_MII_AN_CTRL_CHANNEL3_ADDR			0x1c8001
>> +#define VR_MII_AN_CTRL_CHANNEL_ADDR(x)			(0x1a8001 + 0x10000 * ((x) - 1))
>> +#define MII_AN_INTR_EN					BIT(0)
>> +#define MII_CTRL					BIT(8)
> 
> Too generic a name.
> 
Will update and rename it.

>> +
>> +/* [register] VR_MII_AN_INTR_STS */
>> +#define VR_MII_AN_INTR_STS_ADDR				0x1f8002
>> +#define VR_MII_AN_INTR_STS_CHANNEL1_ADDR		0x1a8002
>> +#define VR_MII_AN_INTR_STS_CHANNEL2_ADDR		0x1b8002
>> +#define VR_MII_AN_INTR_STS_CHANNEL3_ADDR		0x1c8002
>> +#define VR_MII_AN_INTR_STS_CHANNEL_ADDR(x)		(0x1a8002 + 0x10000 * ((x) - 1))
>> +#define CL37_ANCMPLT_INTR				BIT(0)
>> +
>> +/* [register] VR_XAUI_MODE_CTRL */
>> +#define VR_XAUI_MODE_CTRL_ADDR				0x1f8004
>> +#define VR_XAUI_MODE_CTRL_CHANNEL1_ADDR			0x1a8004
>> +#define VR_XAUI_MODE_CTRL_CHANNEL2_ADDR			0x1b8004
>> +#define VR_XAUI_MODE_CTRL_CHANNEL3_ADDR			0x1c8004
>> +#define VR_XAUI_MODE_CTRL_CHANNEL_ADDR(x)		(0x1a8004 + 0x10000 * ((x) - 1))
>> +#define IPG_CHECK					BIT(0)
>> +
>> +/* [register] SR_MII_CTRL */
>> +#define SR_MII_CTRL_ADDR				0x1f0000
>> +#define SR_MII_CTRL_CHANNEL1_ADDR			0x1a0000
>> +#define SR_MII_CTRL_CHANNEL2_ADDR			0x1b0000
>> +#define SR_MII_CTRL_CHANNEL3_ADDR			0x1c0000
>> +#define SR_MII_CTRL_CHANNEL_ADDR(x)			(0x1a0000 + 0x10000 * ((x) - 1))
> 
> 
>> +#define AN_ENABLE					BIT(12)
> 
> Looks like MDIO_AN_CTRL1_ENABLE
> 

This is the uniphy xpcs autoneg enable control bit, our uniphy is not
MDIO accessed, I will rename it to a meaningful name.

>> +#define USXGMII_DUPLEX_FULL				BIT(8)
>> +#define USXGMII_SPEED_MASK				(BIT(13) | BIT(6) | BIT(5))
>> +#define USXGMII_SPEED_10000				(BIT(13) | BIT(6))
>> +#define USXGMII_SPEED_5000				(BIT(13) | BIT(5))
>> +#define USXGMII_SPEED_2500				BIT(5)
>> +#define USXGMII_SPEED_1000				BIT(6)
>> +#define USXGMII_SPEED_100				BIT(13)
>> +#define USXGMII_SPEED_10				0
> 
> Looks rather like the standard IEEE 802.3 definitions except for the
> 2.5G and 5G speeds. Probably worth a comment stating that they're
> slightly different.
>

Sure, will add comment for it in code and documentation files, thanks.

>> +
>> +/* PPE UNIPHY data type */
>> +struct ppe_uniphy {
>> +	void __iomem *base;
>> +	struct ppe_device *ppe_dev;
>> +	unsigned int index;
>> +	phy_interface_t interface;
>> +	struct phylink_pcs pcs;
>> +};
>> +
>> +#define pcs_to_ppe_uniphy(_pcs)				container_of(_pcs, struct ppe_uniphy, pcs)
> 
> As this should only be used in the .c file, I suggest making this a
> static function in the .c file. There should be no requirement to use
> it outside of the .c file.
> 

This is used in the following patch as I explained above for the MAC/PCS 
related comment:
https://lore.kernel.org/netdev/20240110114033.32575-19-quic_luoj@quicinc.com/

>> +
>> +struct ppe_uniphy *ppe_uniphy_setup(struct platform_device *pdev);
>> +
>> +int ppe_uniphy_speed_set(struct ppe_uniphy *uniphy,
>> +			 int port, int speed);
>> +
>> +int ppe_uniphy_duplex_set(struct ppe_uniphy *uniphy,
>> +			  int port, int duplex);
>> +
>> +int ppe_uniphy_adapter_reset(struct ppe_uniphy *uniphy,
>> +			     int port);
>> +
>> +int ppe_uniphy_autoneg_complete_check(struct ppe_uniphy *uniphy,
>> +				      int port);
>> +
>> +int ppe_uniphy_port_gcc_clock_en_set(struct ppe_uniphy *uniphy,
>> +				     int port, bool enable);
>> +
>> +#endif /* _PPE_UNIPHY_H_ */
>> diff --git a/include/linux/soc/qcom/ppe.h b/include/linux/soc/qcom/ppe.h
>> index 268109c823ad..d3cb18df33fa 100644
>> --- a/include/linux/soc/qcom/ppe.h
>> +++ b/include/linux/soc/qcom/ppe.h
>> @@ -20,6 +20,7 @@ struct ppe_device {
>>   	struct dentry *debugfs_root;
>>   	bool is_ppe_probed;
>>   	void *ppe_priv;
>> +	void *uniphy;
> 
> Not struct ppe_uniphy *uniphy? You can declare the struct before use
> via:
> 
> struct ppe_uniphy;
> 
> so you don't need to include ppe_uniphy.h in this header.
> 

Thanks for the good suggestion, will follow this.

> Thanks.
>
  

Patch

diff --git a/drivers/net/ethernet/qualcomm/ppe/Makefile b/drivers/net/ethernet/qualcomm/ppe/Makefile
index 516ea23443ab..487f62d5e38c 100644
--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
+++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
@@ -4,4 +4,4 @@ 
 #
 
 obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
-qcom-ppe-objs := ppe.o ppe_ops.o ppe_debugfs.o
+qcom-ppe-objs := ppe.o ppe_ops.o ppe_debugfs.o ppe_uniphy.o
diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe.c b/drivers/net/ethernet/qualcomm/ppe/ppe.c
index 04f80589c05b..21040efe71fc 100644
--- a/drivers/net/ethernet/qualcomm/ppe/ppe.c
+++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
@@ -18,6 +18,7 @@ 
 #include "ppe_regs.h"
 #include "ppe_ops.h"
 #include "ppe_debugfs.h"
+#include "ppe_uniphy.h"
 
 #define PPE_SCHEDULER_PORT_NUM		8
 #define MPPE_SCHEDULER_PORT_NUM		3
@@ -176,6 +177,26 @@  int ppe_type_get(struct ppe_device *ppe_dev)
 	return ppe_dev_priv->ppe_type;
 }
 
+struct clk **ppe_clock_get(struct ppe_device *ppe_dev)
+{
+	struct ppe_data *ppe_dev_priv = ppe_dev->ppe_priv;
+
+	if (!ppe_dev_priv)
+		return NULL;
+
+	return ppe_dev_priv->clk;
+}
+
+struct reset_control **ppe_reset_get(struct ppe_device *ppe_dev)
+{
+	struct ppe_data *ppe_dev_priv = ppe_dev->ppe_priv;
+
+	if (!ppe_dev_priv)
+		return NULL;
+
+	return ppe_dev_priv->rst;
+}
+
 static int ppe_clock_set_enable(struct ppe_device *ppe_dev,
 				enum ppe_clk_id clk_id, unsigned long rate)
 {
@@ -1405,6 +1426,10 @@  static int qcom_ppe_probe(struct platform_device *pdev)
 				     ret,
 				     "ppe device hw init failed\n");
 
+	ppe_dev->uniphy = ppe_uniphy_setup(pdev);
+	if (IS_ERR(ppe_dev->uniphy))
+		return dev_err_probe(&pdev->dev, ret, "ppe uniphy initialization failed\n");
+
 	ppe_dev->ppe_ops = &qcom_ppe_ops;
 	ppe_dev->is_ppe_probed = true;
 	ppe_debugfs_setup(ppe_dev);
diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe.h b/drivers/net/ethernet/qualcomm/ppe/ppe.h
index 828d467540c9..45b70f47cd21 100644
--- a/drivers/net/ethernet/qualcomm/ppe/ppe.h
+++ b/drivers/net/ethernet/qualcomm/ppe/ppe.h
@@ -173,6 +173,8 @@  struct ppe_scheduler_port_resource {
 };
 
 int ppe_type_get(struct ppe_device *ppe_dev);
+struct clk **ppe_clock_get(struct ppe_device *ppe_dev);
+struct reset_control **ppe_reset_get(struct ppe_device *ppe_dev);
 
 int ppe_write(struct ppe_device *ppe_dev, u32 reg, unsigned int val);
 int ppe_read(struct ppe_device *ppe_dev, u32 reg, unsigned int *val);
diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.c b/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.c
new file mode 100644
index 000000000000..3a2b6fc77a9c
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.c
@@ -0,0 +1,789 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+/* PPE UNIPHY clock register and UNIPHY PCS operations for phylink.
+ *
+ * The PPE UNIPHY block is specifically used by PPE to connect the PPE MAC
+ * with the external PHYs or SFPs or Switches (fixed link). The PPE UNIPHY
+ * block includes serdes, PCS or XPCS and the control logic to support PPE
+ * ports to work in different interface mode and different link speed.
+ *
+ * The PPE UNIPHY block provides raw clock as the parent clock to NSSCC
+ * clocks and the NSSCC clocks can be configured to generate different
+ * port Tx and Rx clocks to PPE ports in different port link speed.
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/clk-provider.h>
+#include <linux/soc/qcom/ppe.h>
+#include "ppe.h"
+#include "ppe_uniphy.h"
+
+/* UNIPHY clock direction */
+enum {
+	UNIPHY_RX = 0,
+	UNIPHY_TX,
+};
+
+/* UNIPHY clock data type */
+struct clk_uniphy {
+	struct clk_hw hw;
+	u8 index;
+	u8 dir;
+	unsigned long rate;
+};
+
+#define to_clk_uniphy(_hw)		container_of(_hw, struct clk_uniphy, hw)
+/* UNIPHY clock rate */
+#define UNIPHY_CLK_RATE_125M		125000000
+#define UNIPHY_CLK_RATE_312P5M		312500000
+
+static void ppe_uniphy_write(struct ppe_uniphy *uniphy, u32 val, u32 reg)
+{
+	if (reg >= UNIPHY_INDIRECT_ADDR_START) {
+		writel(FIELD_GET(UNIPHY_INDIRECT_ADDR_HIGH, reg),
+		       uniphy->base + UNIPHY_INDIRECT_AHB_ADDR);
+		writel(val, uniphy->base + UNIPHY_INDIRECT_DATA_ADDR(reg));
+	} else {
+		writel(val, uniphy->base + reg);
+	}
+}
+
+static u32 ppe_uniphy_read(struct ppe_uniphy *uniphy, u32 reg)
+{
+	if (reg >= UNIPHY_INDIRECT_ADDR_START) {
+		writel(FIELD_GET(UNIPHY_INDIRECT_ADDR_HIGH, reg),
+		       uniphy->base + UNIPHY_INDIRECT_AHB_ADDR);
+		return readl(uniphy->base + UNIPHY_INDIRECT_DATA_ADDR(reg));
+	} else {
+		return readl(uniphy->base + reg);
+	}
+}
+
+static int ppe_uniphy_mask(struct ppe_uniphy *uniphy, u32 reg, u32 mask, u32 set)
+{
+	u32 val;
+
+	val = ppe_uniphy_read(uniphy, reg);
+	val &= ~mask;
+	val |= set;
+	ppe_uniphy_write(uniphy, val, reg);
+
+	return 0;
+}
+
+static unsigned long clk_uniphy_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct clk_uniphy *uniphy = to_clk_uniphy(hw);
+
+	return uniphy->rate;
+}
+
+static int clk_uniphy_determine_rate(struct clk_hw *hw,
+				     struct clk_rate_request *req)
+{
+	if (req->rate <= UNIPHY_CLK_RATE_125M)
+		req->rate = UNIPHY_CLK_RATE_125M;
+	else
+		req->rate = UNIPHY_CLK_RATE_312P5M;
+
+	return 0;
+}
+
+static int clk_uniphy_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct clk_uniphy *uniphy = to_clk_uniphy(hw);
+
+	if (rate != UNIPHY_CLK_RATE_125M && rate != UNIPHY_CLK_RATE_312P5M)
+		return -1;
+
+	uniphy->rate = rate;
+
+	return 0;
+}
+
+static const struct clk_ops clk_uniphy_ops = {
+	.recalc_rate = clk_uniphy_recalc_rate,
+	.determine_rate = clk_uniphy_determine_rate,
+	.set_rate = clk_uniphy_set_rate,
+};
+
+static struct clk_uniphy uniphy0_gcc_rx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy0_gcc_rx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 0,
+	.dir = UNIPHY_RX,
+	.rate = UNIPHY_CLK_RATE_125M,
+};
+
+static struct clk_uniphy uniphy0_gcc_tx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy0_gcc_tx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 0,
+	.dir = UNIPHY_TX,
+	.rate = UNIPHY_CLK_RATE_125M,
+};
+
+static struct clk_uniphy uniphy1_gcc_rx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy1_gcc_rx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 1,
+	.dir = UNIPHY_RX,
+	.rate = UNIPHY_CLK_RATE_312P5M,
+};
+
+static struct clk_uniphy uniphy1_gcc_tx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy1_gcc_tx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 1,
+	.dir = UNIPHY_TX,
+	.rate = UNIPHY_CLK_RATE_312P5M,
+};
+
+static struct clk_uniphy uniphy2_gcc_rx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy2_gcc_rx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 2,
+	.dir = UNIPHY_RX,
+	.rate = UNIPHY_CLK_RATE_312P5M,
+};
+
+static struct clk_uniphy uniphy2_gcc_tx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy2_gcc_tx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 2,
+	.dir = UNIPHY_TX,
+	.rate = UNIPHY_CLK_RATE_312P5M,
+};
+
+static struct clk_hw *uniphy_raw_clks[] = {
+	&uniphy0_gcc_rx_clk.hw, &uniphy0_gcc_tx_clk.hw,
+	&uniphy1_gcc_rx_clk.hw, &uniphy1_gcc_tx_clk.hw,
+	&uniphy2_gcc_rx_clk.hw, &uniphy2_gcc_tx_clk.hw,
+};
+
+int ppe_uniphy_port_gcc_clock_en_set(struct ppe_uniphy *uniphy, int port, bool enable)
+{
+	struct clk **clock = ppe_clock_get(uniphy->ppe_dev);
+	enum ppe_clk_id rx_id, tx_id;
+	int err = 0;
+
+	rx_id = PPE_UNIPHY_PORT1_RX_CLK + ((port - 1) << 1);
+	tx_id = PPE_UNIPHY_PORT1_TX_CLK + ((port - 1) << 1);
+
+	if (enable) {
+		if (!IS_ERR(clock[rx_id])) {
+			err = clk_prepare_enable(clock[rx_id]);
+			if (err) {
+				dev_err(uniphy->ppe_dev->dev,
+					"Failed to enable uniphy port %d rx_clk(%d)\n",
+					port, rx_id);
+				return err;
+			}
+		}
+
+		if (!IS_ERR(clock[tx_id])) {
+			err = clk_prepare_enable(clock[tx_id]);
+			if (err) {
+				dev_err(uniphy->ppe_dev->dev,
+					"Failed to enable uniphy port %d tx_clk(%d)\n",
+					port, tx_id);
+				return err;
+			}
+		}
+	} else {
+		clk_disable_unprepare(clock[rx_id]);
+		clk_disable_unprepare(clock[tx_id]);
+	}
+
+	return 0;
+}
+
+static int ppe_uniphy_interface_gcc_clock_en_set(struct ppe_uniphy *uniphy, bool enable)
+{
+	int ppe_type = ppe_type_get(uniphy->ppe_dev);
+	int port = 0;
+
+	switch (uniphy->index) {
+	case 2:
+		ppe_uniphy_port_gcc_clock_en_set(uniphy, PPE_PORT6, enable);
+		break;
+	case 1:
+		if (ppe_type == PPE_TYPE_APPE)
+			ppe_uniphy_port_gcc_clock_en_set(uniphy, PPE_PORT5, enable);
+		else if (ppe_type == PPE_TYPE_MPPE)
+			ppe_uniphy_port_gcc_clock_en_set(uniphy, PPE_PORT2, enable);
+		break;
+	case 0:
+		if (ppe_type == PPE_TYPE_APPE) {
+			for (port = PPE_PORT1; port <= PPE_PORT4; port++)
+				ppe_uniphy_port_gcc_clock_en_set(uniphy, port, enable);
+		} else if (ppe_type == PPE_TYPE_MPPE) {
+			ppe_uniphy_port_gcc_clock_en_set(uniphy, PPE_PORT1, enable);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int ppe_uniphy_gcc_xpcs_reset(struct ppe_uniphy *uniphy, bool enable)
+{
+	struct reset_control **reset = ppe_reset_get(uniphy->ppe_dev);
+	enum ppe_rst_id id = PPE_UNIPHY0_XPCS_RST + uniphy->index;
+
+	if (IS_ERR(reset[id]))
+		return PTR_ERR(reset[id]);
+
+	if (enable)
+		return reset_control_assert(reset[id]);
+	else
+		return reset_control_deassert(reset[id]);
+}
+
+static int ppe_uniphy_gcc_software_reset(struct ppe_uniphy *uniphy)
+{
+	struct reset_control **reset = ppe_reset_get(uniphy->ppe_dev);
+	int ppe_type = ppe_type_get(uniphy->ppe_dev);
+	unsigned int index = uniphy->index;
+	int err = 0, port = 0;
+
+	/* Assert uniphy sys reset control */
+	if (!IS_ERR(reset[PPE_UNIPHY0_SYS_RST + index])) {
+		err = reset_control_assert(reset[PPE_UNIPHY0_SYS_RST + index]);
+		if (err)
+			return err;
+	}
+
+	/* Assert uniphy port reset control */
+	switch (ppe_type) {
+	case PPE_TYPE_APPE:
+		if (index == 0) {
+			for (port = PPE_PORT1; port <= PPE_PORT4; port++) {
+				if (!IS_ERR(reset[PPE_UNIPHY_PORT1_DIS + port - 1])) {
+					err = reset_control_assert(reset[PPE_UNIPHY_PORT1_DIS +
+								   port - 1]);
+					if (err)
+						return err;
+				}
+			}
+		} else {
+			if (!IS_ERR(reset[PPE_UNIPHY0_SOFT_RST + index])) {
+				err = reset_control_assert(reset[PPE_UNIPHY0_SOFT_RST + index]);
+				if (err)
+					return err;
+			}
+		}
+		break;
+	case PPE_TYPE_MPPE:
+		if (!IS_ERR(reset[PPE_UNIPHY_PORT1_RX_RST + (index << 1)])) {
+			err = reset_control_assert(reset[PPE_UNIPHY_PORT1_RX_RST + (index << 1)]);
+			if (err)
+				return err;
+		}
+
+		if (!IS_ERR(reset[PPE_UNIPHY_PORT1_TX_RST + (index << 1)])) {
+			err = reset_control_assert(reset[PPE_UNIPHY_PORT1_TX_RST + (index << 1)]);
+			if (err)
+				return err;
+		}
+		break;
+	default:
+		break;
+	}
+	fsleep(100000);
+
+	/* Deassert uniphy sys reset control */
+	if (!IS_ERR(reset[PPE_UNIPHY0_SYS_RST + index])) {
+		err = reset_control_deassert(reset[PPE_UNIPHY0_SYS_RST + index]);
+		if (err)
+			return err;
+	}
+
+	/* Deassert uniphy port reset control */
+	switch (ppe_type) {
+	case PPE_TYPE_APPE:
+		if (index == 0) {
+			for (port = PPE_PORT1; port <= PPE_PORT4; port++) {
+				if (!IS_ERR(reset[PPE_UNIPHY_PORT1_DIS + port - 1])) {
+					err = reset_control_deassert(reset[PPE_UNIPHY_PORT1_DIS +
+								     port - 1]);
+					if (err)
+						return err;
+				}
+			}
+		} else {
+			if (!IS_ERR(reset[PPE_UNIPHY0_SOFT_RST + index])) {
+				err = reset_control_deassert(reset[PPE_UNIPHY0_SOFT_RST + index]);
+				if (err)
+					return err;
+			}
+		}
+		break;
+	case PPE_TYPE_MPPE:
+		if (!IS_ERR(reset[PPE_UNIPHY_PORT1_RX_RST + (index << 1)])) {
+			err = reset_control_deassert(reset[PPE_UNIPHY_PORT1_RX_RST + (index << 1)]);
+			if (err)
+				return err;
+		}
+
+		if (!IS_ERR(reset[PPE_UNIPHY_PORT1_TX_RST + (index << 1)])) {
+			err = reset_control_deassert(reset[PPE_UNIPHY_PORT1_TX_RST + (index << 1)]);
+			if (err)
+				return err;
+		}
+		break;
+	default:
+		break;
+	}
+	fsleep(100000);
+
+	return err;
+}
+
+int ppe_uniphy_autoneg_complete_check(struct ppe_uniphy *uniphy, int port)
+{
+	u32 reg, val;
+	int channel, ret;
+
+	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII ||
+	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
+		/* Only uniphy0 may have multi channels */
+		channel = (uniphy->index == 0) ? (port - 1) : 0;
+		reg = (channel == 0) ? VR_MII_AN_INTR_STS_ADDR :
+		       VR_MII_AN_INTR_STS_CHANNEL_ADDR(channel);
+
+		/* Wait auto negotiation complete */
+		ret = read_poll_timeout(ppe_uniphy_read, val,
+					(val & CL37_ANCMPLT_INTR),
+					1000, 100000, true,
+					uniphy, reg);
+		if (ret) {
+			dev_err(uniphy->ppe_dev->dev,
+				"uniphy %d auto negotiation timeout\n", uniphy->index);
+			return ret;
+		}
+
+		/* Clear auto negotiation complete interrupt */
+		ppe_uniphy_mask(uniphy, reg, CL37_ANCMPLT_INTR, 0);
+	}
+
+	return 0;
+}
+
+int ppe_uniphy_speed_set(struct ppe_uniphy *uniphy, int port, int speed)
+{
+	u32 reg, val;
+	int channel;
+
+	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII ||
+	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
+		/* Only uniphy0 may have multiple channels */
+		channel = (uniphy->index == 0) ? (port - 1) : 0;
+
+		reg = (channel == 0) ? SR_MII_CTRL_ADDR :
+		       SR_MII_CTRL_CHANNEL_ADDR(channel);
+
+		switch (speed) {
+		case SPEED_100:
+			val = USXGMII_SPEED_100;
+			break;
+		case SPEED_1000:
+			val = USXGMII_SPEED_1000;
+			break;
+		case SPEED_2500:
+			val = USXGMII_SPEED_2500;
+			break;
+		case SPEED_5000:
+			val = USXGMII_SPEED_5000;
+			break;
+		case SPEED_10000:
+			val = USXGMII_SPEED_10000;
+			break;
+		case SPEED_10:
+			val = USXGMII_SPEED_10;
+			break;
+		default:
+			val = 0;
+			break;
+		}
+
+		ppe_uniphy_mask(uniphy, reg, USXGMII_SPEED_MASK, val);
+	}
+
+	return 0;
+}
+
+int ppe_uniphy_duplex_set(struct ppe_uniphy *uniphy, int port, int duplex)
+{
+	u32 reg;
+	int channel;
+
+	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII &&
+	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
+		/* Only uniphy0 may have multiple channels */
+		channel = (uniphy->index == 0) ? (port - 1) : 0;
+
+		reg = (channel == 0) ? SR_MII_CTRL_ADDR :
+		       SR_MII_CTRL_CHANNEL_ADDR(channel);
+
+		ppe_uniphy_mask(uniphy, reg, USXGMII_DUPLEX_FULL,
+				(duplex == DUPLEX_FULL) ? USXGMII_DUPLEX_FULL : 0);
+	}
+
+	return 0;
+}
+
+int ppe_uniphy_adapter_reset(struct ppe_uniphy *uniphy, int port)
+{
+	int channel;
+
+	/* Only uniphy0 may have multiple channels */
+	channel = (uniphy->index == 0) ? (port - 1) : 0;
+
+	switch (uniphy->interface) {
+	case PHY_INTERFACE_MODE_USXGMII:
+	case PHY_INTERFACE_MODE_QUSGMII:
+		if (channel == 0)
+			ppe_uniphy_mask(uniphy,
+					VR_XS_PCS_DIG_CTRL1_ADDR,
+					USRA_RST, USRA_RST);
+		else
+			ppe_uniphy_mask(uniphy,
+					VR_MII_DIG_CTRL1_CHANNEL_ADDR(channel),
+					CHANNEL_USRA_RST, CHANNEL_USRA_RST);
+		break;
+	case PHY_INTERFACE_MODE_SGMII:
+	case PHY_INTERFACE_MODE_1000BASEX:
+	case PHY_INTERFACE_MODE_2500BASEX:
+	case PHY_INTERFACE_MODE_QSGMII:
+		ppe_uniphy_mask(uniphy,
+				UNIPHY_CHANNEL_INPUT_OUTPUT_4_ADDR(channel),
+				NEWADDEDFROMHERE_CH_ADP_SW_RSTN, 0);
+		ppe_uniphy_mask(uniphy,
+				UNIPHY_CHANNEL_INPUT_OUTPUT_4_ADDR(channel),
+				NEWADDEDFROMHERE_CH_ADP_SW_RSTN,
+				NEWADDEDFROMHERE_CH_ADP_SW_RSTN);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int ppe_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+			  phy_interface_t interface,
+			  const unsigned long *advertising,
+			  bool permit_pause_to_mac)
+{
+	struct ppe_uniphy *uniphy = pcs_to_ppe_uniphy(pcs);
+	unsigned long rate = 0;
+	int ret, channel = 0;
+	u32 val = 0;
+
+	if (uniphy->interface == interface)
+		return 0;
+
+	uniphy->interface = interface;
+
+	/* Disable gcc uniphy interface clock */
+	ppe_uniphy_interface_gcc_clock_en_set(uniphy, false);
+
+	/* Assert gcc uniphy xpcs reset control */
+	ppe_uniphy_gcc_xpcs_reset(uniphy, true);
+
+	/* Configure uniphy mode */
+	switch (interface) {
+	case PHY_INTERFACE_MODE_USXGMII:
+	case PHY_INTERFACE_MODE_10GBASER:
+	case PHY_INTERFACE_MODE_QUSGMII:
+		rate = UNIPHY_CLK_RATE_312P5M;
+		ppe_uniphy_mask(uniphy, UNIPHY_MODE_CTRL_ADDR,
+				USXGMII_MODE_CTRL_MASK, USXGMII_MODE_CTRL);
+		break;
+	case PHY_INTERFACE_MODE_2500BASEX:
+		rate = UNIPHY_CLK_RATE_312P5M;
+		ppe_uniphy_mask(uniphy, UNIPHY_MODE_CTRL_ADDR,
+				SGMIIPLUS_MODE_CTRL_MASK, SGMIIPLUS_MODE_CTRL);
+		break;
+	case PHY_INTERFACE_MODE_SGMII:
+	case PHY_INTERFACE_MODE_1000BASEX:
+		rate = UNIPHY_CLK_RATE_125M;
+		ppe_uniphy_mask(uniphy, UNIPHY_MODE_CTRL_ADDR,
+				SGMII_MODE_CTRL_MASK, SGMII_MODE_CTRL);
+		break;
+	case PHY_INTERFACE_MODE_QSGMII:
+		rate = UNIPHY_CLK_RATE_125M;
+		ppe_uniphy_mask(uniphy, UNIPHY_MODE_CTRL_ADDR,
+				QSGMII_MODE_CTRL_MASK, QSGMII_MODE_CTRL);
+		break;
+	default:
+		break;
+	}
+
+	if (interface == PHY_INTERFACE_MODE_QUSGMII)
+		ppe_uniphy_mask(uniphy, UNIPHY_QP_USXG_OPITON1_ADDR,
+				GMII_SRC_SEL, GMII_SRC_SEL);
+
+	if (interface == PHY_INTERFACE_MODE_10GBASER)
+		ppe_uniphy_mask(uniphy, UNIPHY_LINK_DETECT_ADDR,
+				DETECT_LOS_FROM_SFP, UNIPHY_10GR_LINK_LOSS);
+
+	/* Reset uniphy gcc software reset control */
+	ppe_uniphy_gcc_software_reset(uniphy);
+
+	/* Wait uniphy calibration completion */
+	ret = read_poll_timeout(ppe_uniphy_read, val,
+				(val & MMD1_REG_CALIBRATION_DONE_REG),
+				1000, 100000, true,
+				uniphy, UNIPHY_OFFSET_CALIB_4_ADDR);
+	if (ret) {
+		dev_err(uniphy->ppe_dev->dev,
+			"uniphy %d calibration timeout\n", uniphy->index);
+		return ret;
+	}
+
+	/* Enable gcc uniphy interface clk */
+	ppe_uniphy_interface_gcc_clock_en_set(uniphy, true);
+
+	/* Deassert gcc uniphy xpcs reset control */
+	if (interface == PHY_INTERFACE_MODE_USXGMII ||
+	    interface == PHY_INTERFACE_MODE_10GBASER ||
+		interface == PHY_INTERFACE_MODE_QUSGMII)
+		ppe_uniphy_gcc_xpcs_reset(uniphy, false);
+
+	if (interface == PHY_INTERFACE_MODE_USXGMII ||
+	    interface == PHY_INTERFACE_MODE_QUSGMII) {
+		/* Wait 10gr link up */
+		ret = read_poll_timeout(ppe_uniphy_read, val,
+					(val & SR_XS_PCS_KR_STS1_PLU),
+					1000, 100000, true,
+					uniphy, SR_XS_PCS_KR_STS1_ADDR);
+		if (ret)
+			dev_warn(uniphy->ppe_dev->dev,
+				 "uniphy %d 10gr linkup timeout\n", uniphy->index);
+
+		/* Enable usxgmii */
+		ppe_uniphy_mask(uniphy, VR_XS_PCS_DIG_CTRL1_ADDR, USXGMII_EN, USXGMII_EN);
+
+		if (interface == PHY_INTERFACE_MODE_QUSGMII) {
+			/* XPCS set quxgmii mode */
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_DIG_STS_ADDR, AM_COUNT, QUXGMII_AM_COUNT);
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_KR_CTRL_ADDR, USXG_MODE, QUXGMII_MODE);
+			/* XPCS software reset */
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_DIG_CTRL1_ADDR, VR_RST, VR_RST);
+		}
+
+		/* Enable autoneg complete interrupt and 10M/100M 8bit mii width */
+		ppe_uniphy_mask(uniphy, VR_MII_AN_CTRL_ADDR,
+				MII_AN_INTR_EN | MII_CTRL, MII_AN_INTR_EN | MII_CTRL);
+
+		if (interface == PHY_INTERFACE_MODE_QUSGMII) {
+			for (channel = 1; channel <= 3; channel++)
+				ppe_uniphy_mask(uniphy, VR_MII_AN_CTRL_CHANNEL_ADDR(channel),
+						MII_AN_INTR_EN | MII_CTRL,
+						MII_AN_INTR_EN | MII_CTRL);
+			/* Disable TICD */
+			ppe_uniphy_mask(uniphy, VR_XAUI_MODE_CTRL_ADDR, IPG_CHECK, IPG_CHECK);
+			for (channel = 1; channel <= 3; channel++)
+				ppe_uniphy_mask(uniphy, VR_XAUI_MODE_CTRL_CHANNEL_ADDR(channel),
+						IPG_CHECK, IPG_CHECK);
+		}
+
+		/* Enable autoneg ability and usxgmii 10g speed and full duplex */
+		ppe_uniphy_mask(uniphy, SR_MII_CTRL_ADDR,
+				USXGMII_SPEED_MASK | AN_ENABLE | USXGMII_DUPLEX_FULL,
+				USXGMII_SPEED_10000 | AN_ENABLE | USXGMII_DUPLEX_FULL);
+		if (interface == PHY_INTERFACE_MODE_QUSGMII) {
+			for (channel = 1; channel <= 3; channel++)
+				ppe_uniphy_mask(uniphy, SR_MII_CTRL_CHANNEL_ADDR(channel),
+						USXGMII_SPEED_MASK | AN_ENABLE |
+						USXGMII_DUPLEX_FULL,
+						USXGMII_SPEED_10000 | AN_ENABLE |
+						USXGMII_DUPLEX_FULL);
+
+			/* Enable eee transparent mode */
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_EEE_MCTRL0_ADDR,
+					MULT_FACT_100NS | SIGN_BIT,
+					FIELD_PREP(MULT_FACT_100NS, 0x1) | SIGN_BIT);
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_EEE_TXTIMER_ADDR,
+					TSL_RES | T1U_RES | TWL_RES,
+					UNIPHY_XPCS_TSL_TIMER |
+					UNIPHY_XPCS_T1U_TIMER | UNIPHY_XPCS_TWL_TIMER);
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_EEE_RXTIMER_ADDR,
+					RES_100U | TWR_RES,
+					UNIPHY_XPCS_100US_TIMER | UNIPHY_XPCS_TWR_TIMER);
+
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_EEE_MCTRL1_ADDR,
+					TRN_LPI | TRN_RXLPI, TRN_LPI | TRN_RXLPI);
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_EEE_MCTRL0_ADDR,
+					LTX_EN | LRX_EN, LTX_EN | LRX_EN);
+		}
+	}
+
+	/* Set uniphy raw clk rate */
+	clk_set_rate(uniphy_raw_clks[(uniphy->index << 1) + UNIPHY_RX]->clk,
+		     rate);
+	clk_set_rate(uniphy_raw_clks[(uniphy->index << 1) + UNIPHY_TX]->clk,
+		     rate);
+
+	dev_info(uniphy->ppe_dev->dev,
+		 "ppe pcs config uniphy index %d, interface %s\n",
+		 uniphy->index, phy_modes(interface));
+
+	return 0;
+}
+
+static void ppe_pcs_get_state(struct phylink_pcs *pcs,
+			      struct phylink_link_state *state)
+{
+	struct ppe_uniphy *uniphy = pcs_to_ppe_uniphy(pcs);
+	u32 val;
+
+	switch (state->interface) {
+	case PHY_INTERFACE_MODE_10GBASER:
+		val = ppe_uniphy_read(uniphy, SR_XS_PCS_KR_STS1_ADDR);
+		state->link = (val & SR_XS_PCS_KR_STS1_PLU) ? 1 : 0;
+		state->duplex = DUPLEX_FULL;
+		state->speed = SPEED_10000;
+		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);
+		break;
+	case PHY_INTERFACE_MODE_2500BASEX:
+		val = ppe_uniphy_read(uniphy, UNIPHY_CHANNEL0_INPUT_OUTPUT_6_ADDR);
+		state->link = (val & NEWADDEDFROMHERE_CH0_LINK_MAC) ? 1 : 0;
+		state->duplex = DUPLEX_FULL;
+		state->speed = SPEED_2500;
+		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);
+		break;
+	case PHY_INTERFACE_MODE_1000BASEX:
+	case PHY_INTERFACE_MODE_SGMII:
+		val = ppe_uniphy_read(uniphy, UNIPHY_CHANNEL0_INPUT_OUTPUT_6_ADDR);
+		state->link = (val & NEWADDEDFROMHERE_CH0_LINK_MAC) ? 1 : 0;
+		state->duplex = (val & NEWADDEDFROMHERE_CH0_DUPLEX_MODE_MAC) ?
+			DUPLEX_FULL : DUPLEX_HALF;
+		if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_10M)
+			state->speed = SPEED_10;
+		else if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_100M)
+			state->speed = SPEED_100;
+		else if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_1000M)
+			state->speed = SPEED_1000;
+		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);
+		break;
+	default:
+		break;
+	}
+}
+
+static void ppe_pcs_an_restart(struct phylink_pcs *pcs)
+{
+}
+
+static const struct phylink_pcs_ops ppe_pcs_ops = {
+	.pcs_get_state = ppe_pcs_get_state,
+	.pcs_config = ppe_pcs_config,
+	.pcs_an_restart = ppe_pcs_an_restart,
+};
+
+static void uniphy_clk_release_provider(void *res)
+{
+	of_clk_del_provider(res);
+}
+
+struct ppe_uniphy *ppe_uniphy_setup(struct platform_device *pdev)
+{
+	struct clk_hw_onecell_data *uniphy_clk_data = NULL;
+	struct device_node *np;
+	struct ppe_device *ppe_dev = platform_get_drvdata(pdev);
+	struct ppe_uniphy *uniphy;
+	int i, ret, clk_num = 0;
+
+	np = of_get_child_by_name(pdev->dev.of_node, "qcom-uniphy");
+	if (!np) {
+		dev_err(&pdev->dev, "Failed to find uniphy node\n");
+		return ERR_PTR(-ENODEV);
+	}
+
+	/* Register uniphy raw clock */
+	clk_num = of_property_count_strings(np, "clock-output-names");
+	if (clk_num < 0) {
+		dev_err(&pdev->dev, "%pOFn: invalid clock output count\n", np);
+		goto err_node_put;
+	}
+
+	uniphy_clk_data = devm_kzalloc(&pdev->dev,
+				       struct_size(uniphy_clk_data, hws, clk_num),
+				       GFP_KERNEL);
+	if (!uniphy_clk_data) {
+		ret = -ENOMEM;
+		goto err_node_put;
+	}
+
+	uniphy_clk_data->num = clk_num;
+	for (i = 0; i < clk_num; i++) {
+		ret = of_property_read_string_index(np, "clock-output-names", i,
+						    (const char **)&uniphy_raw_clks[i]->init->name);
+		if (ret) {
+			dev_err(&pdev->dev, "invalid clock name @ %pOFn\n", np);
+			goto err_node_put;
+		}
+
+		ret = devm_clk_hw_register(&pdev->dev, uniphy_raw_clks[i]);
+		if (ret)
+			goto err_node_put;
+		uniphy_clk_data->hws[i] = uniphy_raw_clks[i];
+	}
+
+	ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, uniphy_clk_data);
+	if (ret)
+		goto err_node_put;
+
+	ret = devm_add_action_or_reset(&pdev->dev, uniphy_clk_release_provider, np);
+	if (ret)
+		goto err_node_put;
+
+	/* Initialize each uniphy structure */
+	uniphy = devm_kzalloc(&pdev->dev, sizeof(*uniphy) * (clk_num >> 1), GFP_KERNEL);
+	if (!uniphy) {
+		ret = -ENOMEM;
+		goto err_node_put;
+	}
+
+	for (i = 0; i < (clk_num >> 1); i++) {
+		uniphy[i].base = devm_of_iomap(&pdev->dev, np, i, NULL);
+		if (IS_ERR(uniphy[i].base)) {
+			ret = PTR_ERR(uniphy[i].base);
+			goto err_node_put;
+		}
+		uniphy[i].index = i;
+		uniphy[i].interface = PHY_INTERFACE_MODE_NA;
+		uniphy[i].ppe_dev = ppe_dev;
+		uniphy[i].pcs.ops = &ppe_pcs_ops;
+		uniphy[i].pcs.poll = true;
+	}
+	of_node_put(np);
+	return uniphy;
+
+err_node_put:
+	of_node_put(np);
+	return ERR_PTR(ret);
+}
diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.h b/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.h
new file mode 100644
index 000000000000..ec547e520937
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.h
@@ -0,0 +1,227 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+/* PPE UNIPHY functions and UNIPHY hardware registers declarations. */
+
+#ifndef _PPE_UNIPHY_H_
+#define _PPE_UNIPHY_H_
+
+#include <linux/phylink.h>
+
+#define UNIPHY_INDIRECT_ADDR_START			0x8000
+#define UNIPHY_INDIRECT_AHB_ADDR			0x83fc
+#define UNIPHY_INDIRECT_ADDR_HIGH			GENMASK(20, 8)
+#define UNIPHY_INDIRECT_ADDR_LOW			GENMASK(7, 0)
+#define UNIPHY_INDIRECT_DATA_ADDR(reg)			(FIELD_PREP(GENMASK(15, 10), 0x20) | \
+							FIELD_PREP(GENMASK(9, 2), \
+							FIELD_GET(UNIPHY_INDIRECT_ADDR_LOW, reg)))
+
+/* [register] UNIPHY_MISC2 */
+#define UNIPHY_MISC2_ADDR				0x218
+#define PHY_MODE					GENMASK(6, 4)
+#define USXGMII_PHY_MODE				(FIELD_PREP(PHY_MODE, 0x7))
+#define SGMII_PLUS_PHY_MODE				(FIELD_PREP(PHY_MODE, 0x5))
+#define SGMII_PHY_MODE					(FIELD_PREP(PHY_MODE, 0x3))
+
+/* [register] UNIPHY_MODE_CTRL */
+#define UNIPHY_MODE_CTRL_ADDR				0x46c
+#define NEWADDEDFROMHERE_CH0_AUTONEG_MODE		BIT(0)
+#define NEWADDEDFROMHERE_CH1_CH0_SGMII			BIT(1)
+#define NEWADDEDFROMHERE_CH4_CH1_0_SGMII		BIT(2)
+#define NEWADDEDFROMHERE_SGMII_EVEN_LOW			BIT(3)
+#define NEWADDEDFROMHERE_CH0_MODE_CTRL_25M		GENMASK(6, 4)
+#define NEWADDEDFROMHERE_CH0_QSGMII_SGMII		BIT(8)
+#define NEWADDEDFROMHERE_CH0_PSGMII_QSGMII		BIT(9)
+#define NEWADDEDFROMHERE_SG_MODE			BIT(10)
+#define NEWADDEDFROMHERE_SGPLUS_MODE			BIT(11)
+#define NEWADDEDFROMHERE_XPCS_MODE			BIT(12)
+#define NEWADDEDFROMHERE_USXG_EN			BIT(13)
+#define NEWADDEDFROMHERE_SW_V17_V18			BIT(15)
+#define USXGMII_MODE_CTRL_MASK				GENMASK(12, 8)
+#define USXGMII_MODE_CTRL				NEWADDEDFROMHERE_XPCS_MODE
+#define TEN_GR_MODE_CTRL_MASK				GENMASK(12, 8)
+#define TEN_GR_MODE_CTRL				NEWADDEDFROMHERE_XPCS_MODE
+#define QUSGMII_MODE_CTRL_MASK				GENMASK(12, 8)
+#define QUSGMII_MODE_CTRL				NEWADDEDFROMHERE_XPCS_MODE
+#define SGMIIPLUS_MODE_CTRL_MASK			(NEWADDEDFROMHERE_CH0_AUTONEG_MODE | \
+								GENMASK(12, 8))
+#define SGMIIPLUS_MODE_CTRL				NEWADDEDFROMHERE_SGPLUS_MODE
+#define QSGMII_MODE_CTRL_MASK				(NEWADDEDFROMHERE_CH0_AUTONEG_MODE | \
+								GENMASK(12, 8))
+#define QSGMII_MODE_CTRL				NEWADDEDFROMHERE_CH0_PSGMII_QSGMII
+#define SGMII_MODE_CTRL_MASK				(NEWADDEDFROMHERE_CH0_AUTONEG_MODE | \
+								GENMASK(12, 8))
+#define SGMII_MODE_CTRL					NEWADDEDFROMHERE_SG_MODE
+
+/* [register] UNIPHY_CHANNEL_INPUT_OUTPUT_4 */
+#define UNIPHY_CHANNEL0_INPUT_OUTPUT_4_ADDR		0x480
+#define NEWADDEDFROMHERE_CH0_ADP_SW_RSTN		BIT(11)
+#define UNIPHY_CHANNEL1_INPUT_OUTPUT_4_ADDR		0x498
+#define NEWADDEDFROMHERE_CH1_ADP_SW_RSTN		BIT(11)
+#define UNIPHY_CHANNEL2_INPUT_OUTPUT_4_ADDR		0x4b0
+#define NEWADDEDFROMHERE_CH2_ADP_SW_RSTN		BIT(11)
+#define UNIPHY_CHANNEL3_INPUT_OUTPUT_4_ADDR		0x4c8
+#define NEWADDEDFROMHERE_CH3_ADP_SW_RSTN		BIT(11)
+#define UNIPHY_CHANNEL4_INPUT_OUTPUT_4_ADDR		0x4e0
+#define NEWADDEDFROMHERE_CH4_ADP_SW_RSTN		BIT(11)
+#define UNIPHY_CHANNEL_INPUT_OUTPUT_4_ADDR(x)		(0x480 + 0x18 * (x))
+#define NEWADDEDFROMHERE_CH_ADP_SW_RSTN			BIT(11)
+
+/* [register] UNIPHY_CHANNEL_INPUT_OUTPUT_6 */
+#define UNIPHY_CHANNEL0_INPUT_OUTPUT_6_ADDR		0x488
+#define NEWADDEDFROMHERE_CH0_LINK_MAC			BIT(7)
+#define NEWADDEDFROMHERE_CH0_DUPLEX_MODE_MAC		BIT(6)
+#define NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC		GENMASK(5, 4)
+#define NEWADDEDFROMHERE_CH0_PAUSE_MAC			BIT(3)
+#define NEWADDEDFROMHERE_CH0_ASYM_PAUSE_MAC		BIT(2)
+#define NEWADDEDFROMHERE_CH0_TX_PAUSE_EN_MAC		BIT(1)
+#define NEWADDEDFROMHERE_CH0_RX_PAUSE_EN_MAC		BIT(0)
+#define UNIPHY_SPEED_10M				0
+#define UNIPHY_SPEED_100M				1
+#define UNIPHY_SPEED_1000M				2
+
+/* [register] UNIPHY_INSTANCE_LINK_DETECT */
+#define UNIPHY_LINK_DETECT_ADDR				0x570
+#define DETECT_LOS_FROM_SFP				GENMASK(8, 6)
+#define UNIPHY_10GR_LINK_LOSS				(FIELD_PREP(DETECT_LOS_FROM_SFP, 0x7))
+
+/* [register] UNIPHY_QP_USXG_OPITON1 */
+#define UNIPHY_QP_USXG_OPITON1_ADDR			0x584
+#define GMII_SRC_SEL					BIT(0)
+
+/* [register] UNIPHY_OFFSET_CALIB_4 */
+#define UNIPHY_OFFSET_CALIB_4_ADDR			0x1e0
+#define MMD1_REG_CALIBRATION_DONE_REG			BIT(7)
+#define UNIPHY_CALIBRATION_DONE				0x1
+
+/* [register] UNIPHY_PLL_RESET */
+#define UNIPHY_PLL_RESET_ADDR				0x780
+#define UPHY_ANA_EN_SW_RSTN				BIT(6)
+
+/* [register] SR_XS_PCS_KR_STS1 */
+#define SR_XS_PCS_KR_STS1_ADDR				0x30020
+#define SR_XS_PCS_KR_STS1_PLU				BIT(12)
+
+/* [register] VR_XS_PCS_DIG_CTRL1 */
+#define VR_XS_PCS_DIG_CTRL1_ADDR			0x38000
+#define USXGMII_EN					BIT(9)
+#define USRA_RST					BIT(10)
+#define VR_RST						BIT(15)
+
+/* [register] VR_XS_PCS_EEE_MCTRL0 */
+#define VR_XS_PCS_EEE_MCTRL0_ADDR			0x38006
+#define LTX_EN						BIT(0)
+#define LRX_EN						BIT(1)
+#define SIGN_BIT					BIT(6)
+#define MULT_FACT_100NS					GENMASK(11, 8)
+
+/* [register] VR_XS_PCS_KR_CTRL */
+#define VR_XS_PCS_KR_CTRL_ADDR	0x38007
+#define USXG_MODE					GENMASK(12, 10)
+#define QUXGMII_MODE					(FIELD_PREP(USXG_MODE, 0x5))
+
+/* [register] VR_XS_PCS_EEE_TXTIMER */
+#define VR_XS_PCS_EEE_TXTIMER_ADDR			0x38008
+#define TSL_RES						GENMASK(5, 0)
+#define T1U_RES						GENMASK(7, 6)
+#define TWL_RES						GENMASK(12, 8)
+#define UNIPHY_XPCS_TSL_TIMER				(FIELD_PREP(TSL_RES, 0xa))
+#define UNIPHY_XPCS_T1U_TIMER				(FIELD_PREP(TSL_RES, 0x3))
+#define UNIPHY_XPCS_TWL_TIMER				(FIELD_PREP(TSL_RES, 0x16))
+
+/* [register] VR_XS_PCS_EEE_RXTIMER  */
+#define VR_XS_PCS_EEE_RXTIMER_ADDR			0x38009
+#define RES_100U					GENMASK(7, 0)
+#define TWR_RES						GENMASK(13, 8)
+#define UNIPHY_XPCS_100US_TIMER				(FIELD_PREP(RES_100U, 0xc8))
+#define UNIPHY_XPCS_TWR_TIMER				(FIELD_PREP(RES_100U, 0x1c))
+
+/* [register] VR_XS_PCS_DIG_STS */
+#define VR_XS_PCS_DIG_STS_ADDR				0x3800a
+#define AM_COUNT					GENMASK(14, 0)
+#define QUXGMII_AM_COUNT				(FIELD_PREP(AM_COUNT, 0x6018))
+
+/* [register] VR_XS_PCS_EEE_MCTRL1 */
+#define VR_XS_PCS_EEE_MCTRL1_ADDR			0x3800b
+#define TRN_LPI						BIT(0)
+#define TRN_RXLPI					BIT(8)
+
+/* [register] VR_MII_1_DIG_CTRL1 */
+#define VR_MII_DIG_CTRL1_CHANNEL1_ADDR			0x1a8000
+#define VR_MII_DIG_CTRL1_CHANNEL2_ADDR			0x1b8000
+#define VR_MII_DIG_CTRL1_CHANNEL3_ADDR			0x1c8000
+#define VR_MII_DIG_CTRL1_CHANNEL_ADDR(x)		(0x1a8000 + 0x10000 * ((x) - 1))
+#define CHANNEL_USRA_RST				BIT(5)
+
+/* [register] VR_MII_AN_CTRL */
+#define VR_MII_AN_CTRL_ADDR				0x1f8001
+#define VR_MII_AN_CTRL_CHANNEL1_ADDR			0x1a8001
+#define VR_MII_AN_CTRL_CHANNEL2_ADDR			0x1b8001
+#define VR_MII_AN_CTRL_CHANNEL3_ADDR			0x1c8001
+#define VR_MII_AN_CTRL_CHANNEL_ADDR(x)			(0x1a8001 + 0x10000 * ((x) - 1))
+#define MII_AN_INTR_EN					BIT(0)
+#define MII_CTRL					BIT(8)
+
+/* [register] VR_MII_AN_INTR_STS */
+#define VR_MII_AN_INTR_STS_ADDR				0x1f8002
+#define VR_MII_AN_INTR_STS_CHANNEL1_ADDR		0x1a8002
+#define VR_MII_AN_INTR_STS_CHANNEL2_ADDR		0x1b8002
+#define VR_MII_AN_INTR_STS_CHANNEL3_ADDR		0x1c8002
+#define VR_MII_AN_INTR_STS_CHANNEL_ADDR(x)		(0x1a8002 + 0x10000 * ((x) - 1))
+#define CL37_ANCMPLT_INTR				BIT(0)
+
+/* [register] VR_XAUI_MODE_CTRL */
+#define VR_XAUI_MODE_CTRL_ADDR				0x1f8004
+#define VR_XAUI_MODE_CTRL_CHANNEL1_ADDR			0x1a8004
+#define VR_XAUI_MODE_CTRL_CHANNEL2_ADDR			0x1b8004
+#define VR_XAUI_MODE_CTRL_CHANNEL3_ADDR			0x1c8004
+#define VR_XAUI_MODE_CTRL_CHANNEL_ADDR(x)		(0x1a8004 + 0x10000 * ((x) - 1))
+#define IPG_CHECK					BIT(0)
+
+/* [register] SR_MII_CTRL */
+#define SR_MII_CTRL_ADDR				0x1f0000
+#define SR_MII_CTRL_CHANNEL1_ADDR			0x1a0000
+#define SR_MII_CTRL_CHANNEL2_ADDR			0x1b0000
+#define SR_MII_CTRL_CHANNEL3_ADDR			0x1c0000
+#define SR_MII_CTRL_CHANNEL_ADDR(x)			(0x1a0000 + 0x10000 * ((x) - 1))
+#define AN_ENABLE					BIT(12)
+#define USXGMII_DUPLEX_FULL				BIT(8)
+#define USXGMII_SPEED_MASK				(BIT(13) | BIT(6) | BIT(5))
+#define USXGMII_SPEED_10000				(BIT(13) | BIT(6))
+#define USXGMII_SPEED_5000				(BIT(13) | BIT(5))
+#define USXGMII_SPEED_2500				BIT(5)
+#define USXGMII_SPEED_1000				BIT(6)
+#define USXGMII_SPEED_100				BIT(13)
+#define USXGMII_SPEED_10				0
+
+/* PPE UNIPHY data type */
+struct ppe_uniphy {
+	void __iomem *base;
+	struct ppe_device *ppe_dev;
+	unsigned int index;
+	phy_interface_t interface;
+	struct phylink_pcs pcs;
+};
+
+#define pcs_to_ppe_uniphy(_pcs)				container_of(_pcs, struct ppe_uniphy, pcs)
+
+struct ppe_uniphy *ppe_uniphy_setup(struct platform_device *pdev);
+
+int ppe_uniphy_speed_set(struct ppe_uniphy *uniphy,
+			 int port, int speed);
+
+int ppe_uniphy_duplex_set(struct ppe_uniphy *uniphy,
+			  int port, int duplex);
+
+int ppe_uniphy_adapter_reset(struct ppe_uniphy *uniphy,
+			     int port);
+
+int ppe_uniphy_autoneg_complete_check(struct ppe_uniphy *uniphy,
+				      int port);
+
+int ppe_uniphy_port_gcc_clock_en_set(struct ppe_uniphy *uniphy,
+				     int port, bool enable);
+
+#endif /* _PPE_UNIPHY_H_ */
diff --git a/include/linux/soc/qcom/ppe.h b/include/linux/soc/qcom/ppe.h
index 268109c823ad..d3cb18df33fa 100644
--- a/include/linux/soc/qcom/ppe.h
+++ b/include/linux/soc/qcom/ppe.h
@@ -20,6 +20,7 @@  struct ppe_device {
 	struct dentry *debugfs_root;
 	bool is_ppe_probed;
 	void *ppe_priv;
+	void *uniphy;
 };
 
 /* PPE operations, which is used by the external driver like Ethernet