Thread (37 messages) 37 messages, 9 authors, 2024-01-22

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

From: "Russell King (Oracle)" <linux@armlinux.org.uk>
Date: 2024-01-10 12:10:42
Also in: linux-arm-msm, linux-devicetree, linux-doc, lkml, netdev

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.
quoted hunk ↗ jump to hunk
+
+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.

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help