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.
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.
>
@@ -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
@@ -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);
@@ -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);
new file mode 100644
@@ -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);
+}
new file mode 100644
@@ -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_ */
@@ -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