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

From: Lei Wei
Date: Mon Jan 22 2024 - 09:38:29 EST




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@xxxxxxxxxxx/

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@xxxxxxxxxxx/

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@xxxxxxxxxxx/

+
+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.