2023-04-17 18:08:16

by Daniel Machon

[permalink] [raw]
Subject: [PATCH 0/7] Power down serdes lanes and CMUs initially

This patch series aims to lower the power consumption of the Sparx5
switch chip, by configuring optimal quiet mode for serdes lanes, as well
as powering down unused CMUs (Clock Multiplier Unit). Before this series,
serdes lane quiet mode were not optimally configured, and all the CMUs were
powered on by default. This uses needless power.

Each serdes lane is provided a CMU clock, depending on the serdes mode
and the serdes index. CMUs will now be powered on individually, when
needed. The amount of power saved varies, depending on the port
configuration. As a reference, this change saves about 1W on
sparx5_pcb135 and 2W on sparx5_pcb134.

Patch #1: Adds the required registers.
Patch #2: Configures optimal quiet mode for all serdes lanes.
Patch #3: Reorders CMU functions.
Patch #4: Configures CMUs to be powered down by default.

Patch #5: Adds the serdes mode and index to CMU index map, and configures the
individual CMU when the corresponding serdes lane is configured.

Patch #6: Removes the code that unconditionally powered on all CMUs
Patch #7: Adds a check that serdesmode is not unset when a CMU is configured.

Daniel Machon (7):
phy: sparx5-serdes: add registers required for SD/CMU power down
phy: sparx5-serdes: configure optimal qiet mode for serdes lanes
phy: sparx5-serdes: reorder CMU functions
phy: sparx5-serdes: power down all CMUs by default
phy: sparx5-serdes: power on CMUs individually
phy: sparx5-serdes: remove power up of all CMUs
phy: sparx5-serdes: do not reconfigure serdes if serdesmode is unset

drivers/phy/microchip/sparx5_serdes.c | 426 +++++++++++++--------
drivers/phy/microchip/sparx5_serdes.h | 1 -
drivers/phy/microchip/sparx5_serdes_regs.h | 106 +++++
3 files changed, 370 insertions(+), 163 deletions(-)

--
2.34.1


2023-04-17 18:08:31

by Daniel Machon

[permalink] [raw]
Subject: [PATCH 1/7] phy: sparx5-serdes: add registers required for SD/CMU power down

Add registers required to configure serdeses and CMUs for initial power
down.

Signed-off-by: Daniel Machon <[email protected]>
---
drivers/phy/microchip/sparx5_serdes_regs.h | 106 +++++++++++++++++++++
1 file changed, 106 insertions(+)

diff --git a/drivers/phy/microchip/sparx5_serdes_regs.h b/drivers/phy/microchip/sparx5_serdes_regs.h
index b96386a4df5a..d0543fd3dc94 100644
--- a/drivers/phy/microchip/sparx5_serdes_regs.h
+++ b/drivers/phy/microchip/sparx5_serdes_regs.h
@@ -2149,6 +2149,92 @@ enum sparx5_serdes_target {
#define SD_CMU_CMU_05_CFG_BIAS_TP_SEL_1_0_GET(x)\
FIELD_GET(SD_CMU_CMU_05_CFG_BIAS_TP_SEL_1_0, x)

+/* SD10G_CMU_TARGET:CMU_GRP_1:CMU_06 */
+#define SD_CMU_CMU_06(t) \
+ __REG(TARGET_SD_CMU, t, 14, 20, 0, 1, 72, 4, 0, 1, 4)
+
+#define SD_CMU_CMU_06_CFG_DISLOS BIT(0)
+#define SD_CMU_CMU_06_CFG_DISLOS_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_06_CFG_DISLOS, x)
+#define SD_CMU_CMU_06_CFG_DISLOS_GET(x)\
+ FIELD_GET(SD_CMU_CMU_06_CFG_DISLOS, x)
+
+#define SD_CMU_CMU_06_CFG_DISLOL BIT(1)
+#define SD_CMU_CMU_06_CFG_DISLOL_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_06_CFG_DISLOL, x)
+#define SD_CMU_CMU_06_CFG_DISLOL_GET(x)\
+ FIELD_GET(SD_CMU_CMU_06_CFG_DISLOL, x)
+
+#define SD_CMU_CMU_06_CFG_DCLOL BIT(2)
+#define SD_CMU_CMU_06_CFG_DCLOL_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_06_CFG_DCLOL, x)
+#define SD_CMU_CMU_06_CFG_DCLOL_GET(x)\
+ FIELD_GET(SD_CMU_CMU_06_CFG_DCLOL, x)
+
+#define SD_CMU_CMU_06_CFG_FORCE_RX_FILT BIT(3)
+#define SD_CMU_CMU_06_CFG_FORCE_RX_FILT_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_06_CFG_FORCE_RX_FILT, x)
+#define SD_CMU_CMU_06_CFG_FORCE_RX_FILT_GET(x)\
+ FIELD_GET(SD_CMU_CMU_06_CFG_FORCE_RX_FILT, x)
+
+#define SD_CMU_CMU_06_CFG_CTRL_LOGIC_PD BIT(4)
+#define SD_CMU_CMU_06_CFG_CTRL_LOGIC_PD_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_06_CFG_CTRL_LOGIC_PD, x)
+#define SD_CMU_CMU_06_CFG_CTRL_LOGIC_PD_GET(x)\
+ FIELD_GET(SD_CMU_CMU_06_CFG_CTRL_LOGIC_PD, x)
+
+#define SD_CMU_CMU_06_CFG_VCO_PD BIT(5)
+#define SD_CMU_CMU_06_CFG_VCO_PD_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_06_CFG_VCO_PD, x)
+#define SD_CMU_CMU_06_CFG_VCO_PD_GET(x)\
+ FIELD_GET(SD_CMU_CMU_06_CFG_VCO_PD, x)
+
+#define SD_CMU_CMU_06_CFG_VCO_CAL_RESETN BIT(6)
+#define SD_CMU_CMU_06_CFG_VCO_CAL_RESETN_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_06_CFG_VCO_CAL_RESETN, x)
+#define SD_CMU_CMU_06_CFG_VCO_CAL_RESETN_GET(x)\
+ FIELD_GET(SD_CMU_CMU_06_CFG_VCO_CAL_RESETN, x)
+
+#define SD_CMU_CMU_06_CFG_VCO_CAL_BYP BIT(7)
+#define SD_CMU_CMU_06_CFG_VCO_CAL_BYP_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_06_CFG_VCO_CAL_BYP, x)
+#define SD_CMU_CMU_06_CFG_VCO_CAL_BYP_GET(x)\
+ FIELD_GET(SD_CMU_CMU_06_CFG_VCO_CAL_BYP, x)
+
+/* SD10G_CMU_TARGET:CMU_GRP_1:CMU_08 */
+#define SD_CMU_CMU_08(t) \
+ __REG(TARGET_SD_CMU, t, 14, 20, 0, 1, 72, 12, 0, 1, 4)
+
+#define SD_CMU_CMU_08_CFG_VFILT2PAD BIT(0)
+#define SD_CMU_CMU_08_CFG_VFILT2PAD_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_08_CFG_VFILT2PAD, x)
+#define SD_CMU_CMU_08_CFG_VFILT2PAD_GET(x)\
+ FIELD_GET(SD_CMU_CMU_08_CFG_VFILT2PAD, x)
+
+#define SD_CMU_CMU_08_CFG_EN_DUMMY BIT(1)
+#define SD_CMU_CMU_08_CFG_EN_DUMMY_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_08_CFG_EN_DUMMY, x)
+#define SD_CMU_CMU_08_CFG_EN_DUMMY_GET(x)\
+ FIELD_GET(SD_CMU_CMU_08_CFG_EN_DUMMY, x)
+
+#define SD_CMU_CMU_08_CFG_CK_TREE_PD BIT(2)
+#define SD_CMU_CMU_08_CFG_CK_TREE_PD_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_08_CFG_CK_TREE_PD, x)
+#define SD_CMU_CMU_08_CFG_CK_TREE_PD_GET(x)\
+ FIELD_GET(SD_CMU_CMU_08_CFG_CK_TREE_PD, x)
+
+#define SD_CMU_CMU_08_CFG_RST_TREE_PD_MAN BIT(3)
+#define SD_CMU_CMU_08_CFG_RST_TREE_PD_MAN_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_08_CFG_RST_TREE_PD_MAN, x)
+#define SD_CMU_CMU_08_CFG_RST_TREE_PD_MAN_GET(x)\
+ FIELD_GET(SD_CMU_CMU_08_CFG_RST_TREE_PD_MAN, x)
+
+#define SD_CMU_CMU_08_CFG_RST_TREE_PD_MAN_EN BIT(4)
+#define SD_CMU_CMU_08_CFG_RST_TREE_PD_MAN_EN_SET(x)\
+ FIELD_PREP(SD_CMU_CMU_08_CFG_RST_TREE_PD_MAN_EN, x)
+#define SD_CMU_CMU_08_CFG_RST_TREE_PD_MAN_EN_GET(x)\
+ FIELD_GET(SD_CMU_CMU_08_CFG_RST_TREE_PD_MAN_EN, x)
+
/* SD10G_CMU_TARGET:CMU_GRP_1:CMU_09 */
#define SD_CMU_CMU_09(t) __REG(TARGET_SD_CMU, t, 14, 20, 0, 1, 72, 16, 0, 1, 4)

@@ -2443,6 +2529,16 @@ enum sparx5_serdes_target {
#define SD_LANE_SD_LANE_STAT_DBG_OBS_GET(x)\
FIELD_GET(SD_LANE_SD_LANE_STAT_DBG_OBS, x)

+/* SD_LANE_TARGET:SD_PWR_CFG:QUIET_MODE_6G */
+#define SD_LANE_QUIET_MODE_6G(t) \
+ __REG(TARGET_SD_LANE, t, 25, 24, 0, 1, 8, 4, 0, 1, 4)
+
+#define SD_LANE_QUIET_MODE_6G_QUIET_MODE GENMASK(24, 0)
+#define SD_LANE_QUIET_MODE_6G_QUIET_MODE_SET(x)\
+ FIELD_PREP(SD_LANE_QUIET_MODE_6G_QUIET_MODE, x)
+#define SD_LANE_QUIET_MODE_6G_QUIET_MODE_GET(x)\
+ FIELD_GET(SD_LANE_QUIET_MODE_6G_QUIET_MODE, x)
+
/* SD_LANE_TARGET:CFG_STAT_FX100:MISC */
#define SD_LANE_MISC(t) __REG(TARGET_SD_LANE, t, 25, 56, 0, 1, 56, 0, 0, 1, 4)

@@ -2692,4 +2788,14 @@ enum sparx5_serdes_target {
#define SD_LANE_25G_SD_LANE_STAT_DBG_OBS_GET(x)\
FIELD_GET(SD_LANE_25G_SD_LANE_STAT_DBG_OBS, x)

+/* SD25G_CFG_TARGET:SD_PWR_CFG:QUIET_MODE_6G */
+#define SD_LANE_25G_QUIET_MODE_6G(t) \
+ __REG(TARGET_SD_LANE_25G, t, 8, 28, 0, 1, 8, 4, 0, 1, 4)
+
+#define SD_LANE_25G_QUIET_MODE_6G_QUIET_MODE GENMASK(24, 0)
+#define SD_LANE_25G_QUIET_MODE_6G_QUIET_MODE_SET(x)\
+ FIELD_PREP(SD_LANE_25G_QUIET_MODE_6G_QUIET_MODE, x)
+#define SD_LANE_25G_QUIET_MODE_6G_QUIET_MODE_GET(x)\
+ FIELD_GET(SD_LANE_25G_QUIET_MODE_6G_QUIET_MODE, x)
+
#endif /* _SPARX5_SERDES_REGS_H_ */
--
2.34.1

2023-04-17 18:08:34

by Daniel Machon

[permalink] [raw]
Subject: [PATCH 3/7] phy: sparx5-serdes: reorder CMU functions

Reorder CMU functions, as some of them are now required by the serdes
functions. No functional changes.

Signed-off-by: Daniel Machon <[email protected]>
---
drivers/phy/microchip/sparx5_serdes.c | 306 +++++++++++++-------------
1 file changed, 153 insertions(+), 153 deletions(-)

diff --git a/drivers/phy/microchip/sparx5_serdes.c b/drivers/phy/microchip/sparx5_serdes.c
index 6ba058b2482f..d8620e0fae7b 100644
--- a/drivers/phy/microchip/sparx5_serdes.c
+++ b/drivers/phy/microchip/sparx5_serdes.c
@@ -925,6 +925,159 @@ static void sparx5_sd10g28_get_params(struct sparx5_serdes_macro *macro,
*params = init;
}

+static int sparx5_cmu_apply_cfg(struct sparx5_serdes_private *priv,
+ u32 cmu_idx,
+ void __iomem *cmu_tgt,
+ void __iomem *cmu_cfg_tgt,
+ u32 spd10g)
+{
+ void __iomem **regs = priv->regs;
+ struct device *dev = priv->dev;
+ int value;
+
+ cmu_tgt = sdx5_inst_get(priv, TARGET_SD_CMU, cmu_idx);
+ cmu_cfg_tgt = sdx5_inst_get(priv, TARGET_SD_CMU_CFG, cmu_idx);
+
+ if (cmu_idx == 1 || cmu_idx == 4 || cmu_idx == 7 ||
+ cmu_idx == 10 || cmu_idx == 13) {
+ spd10g = 0;
+ }
+
+ sdx5_inst_rmw(SD_CMU_CFG_SD_CMU_CFG_EXT_CFG_RST_SET(1),
+ SD_CMU_CFG_SD_CMU_CFG_EXT_CFG_RST,
+ cmu_cfg_tgt,
+ SD_CMU_CFG_SD_CMU_CFG(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CFG_SD_CMU_CFG_EXT_CFG_RST_SET(0),
+ SD_CMU_CFG_SD_CMU_CFG_EXT_CFG_RST,
+ cmu_cfg_tgt,
+ SD_CMU_CFG_SD_CMU_CFG(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CFG_SD_CMU_CFG_CMU_RST_SET(1),
+ SD_CMU_CFG_SD_CMU_CFG_CMU_RST,
+ cmu_cfg_tgt,
+ SD_CMU_CFG_SD_CMU_CFG(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CMU_45_R_DWIDTHCTRL_FROM_HWT_SET(0x1) |
+ SD_CMU_CMU_45_R_REFCK_SSC_EN_FROM_HWT_SET(0x1) |
+ SD_CMU_CMU_45_R_LINK_BUF_EN_FROM_HWT_SET(0x1) |
+ SD_CMU_CMU_45_R_BIAS_EN_FROM_HWT_SET(0x1) |
+ SD_CMU_CMU_45_R_EN_RATECHG_CTRL_SET(0x0),
+ SD_CMU_CMU_45_R_DWIDTHCTRL_FROM_HWT |
+ SD_CMU_CMU_45_R_REFCK_SSC_EN_FROM_HWT |
+ SD_CMU_CMU_45_R_LINK_BUF_EN_FROM_HWT |
+ SD_CMU_CMU_45_R_BIAS_EN_FROM_HWT |
+ SD_CMU_CMU_45_R_EN_RATECHG_CTRL,
+ cmu_tgt,
+ SD_CMU_CMU_45(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CMU_47_R_PCS2PMA_PHYMODE_4_0_SET(0),
+ SD_CMU_CMU_47_R_PCS2PMA_PHYMODE_4_0,
+ cmu_tgt,
+ SD_CMU_CMU_47(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CMU_1B_CFG_RESERVE_7_0_SET(0),
+ SD_CMU_CMU_1B_CFG_RESERVE_7_0,
+ cmu_tgt,
+ SD_CMU_CMU_1B(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CMU_0D_CFG_JC_BYP_SET(0x1),
+ SD_CMU_CMU_0D_CFG_JC_BYP,
+ cmu_tgt,
+ SD_CMU_CMU_0D(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CMU_1F_CFG_VTUNE_SEL_SET(1),
+ SD_CMU_CMU_1F_CFG_VTUNE_SEL,
+ cmu_tgt,
+ SD_CMU_CMU_1F(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CMU_00_CFG_PLL_TP_SEL_1_0_SET(3),
+ SD_CMU_CMU_00_CFG_PLL_TP_SEL_1_0,
+ cmu_tgt,
+ SD_CMU_CMU_00(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CMU_05_CFG_BIAS_TP_SEL_1_0_SET(3),
+ SD_CMU_CMU_05_CFG_BIAS_TP_SEL_1_0,
+ cmu_tgt,
+ SD_CMU_CMU_05(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CMU_30_R_PLL_DLOL_EN_SET(1),
+ SD_CMU_CMU_30_R_PLL_DLOL_EN,
+ cmu_tgt,
+ SD_CMU_CMU_30(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CMU_09_CFG_SW_10G_SET(spd10g),
+ SD_CMU_CMU_09_CFG_SW_10G,
+ cmu_tgt,
+ SD_CMU_CMU_09(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CFG_SD_CMU_CFG_CMU_RST_SET(0),
+ SD_CMU_CFG_SD_CMU_CFG_CMU_RST,
+ cmu_cfg_tgt,
+ SD_CMU_CFG_SD_CMU_CFG(cmu_idx));
+
+ msleep(20);
+
+ sdx5_inst_rmw(SD_CMU_CMU_44_R_PLL_RSTN_SET(0),
+ SD_CMU_CMU_44_R_PLL_RSTN,
+ cmu_tgt,
+ SD_CMU_CMU_44(cmu_idx));
+
+ sdx5_inst_rmw(SD_CMU_CMU_44_R_PLL_RSTN_SET(1),
+ SD_CMU_CMU_44_R_PLL_RSTN,
+ cmu_tgt,
+ SD_CMU_CMU_44(cmu_idx));
+
+ msleep(20);
+
+ value = readl(sdx5_addr(regs, SD_CMU_CMU_E0(cmu_idx)));
+ value = SD_CMU_CMU_E0_PLL_LOL_UDL_GET(value);
+
+ if (value) {
+ dev_err(dev, "CMU PLL Loss of Lock: 0x%x\n", value);
+ return -EINVAL;
+ }
+ sdx5_inst_rmw(SD_CMU_CMU_0D_CFG_PMA_TX_CK_PD_SET(0),
+ SD_CMU_CMU_0D_CFG_PMA_TX_CK_PD,
+ cmu_tgt,
+ SD_CMU_CMU_0D(cmu_idx));
+ return 0;
+}
+
+static int sparx5_cmu_cfg(struct sparx5_serdes_private *priv, u32 cmu_idx)
+{
+ void __iomem *cmu_tgt, *cmu_cfg_tgt;
+ u32 spd10g = 1;
+
+ if (cmu_idx == 1 || cmu_idx == 4 || cmu_idx == 7 ||
+ cmu_idx == 10 || cmu_idx == 13) {
+ spd10g = 0;
+ }
+
+ cmu_tgt = sdx5_inst_get(priv, TARGET_SD_CMU, cmu_idx);
+ cmu_cfg_tgt = sdx5_inst_get(priv, TARGET_SD_CMU_CFG, cmu_idx);
+
+ return sparx5_cmu_apply_cfg(priv, cmu_idx, cmu_tgt, cmu_cfg_tgt, spd10g);
+}
+
+static int sparx5_serdes_cmu_enable(struct sparx5_serdes_private *priv)
+{
+ int idx, err = 0;
+
+ if (!priv->cmu_enabled) {
+ for (idx = 0; idx < SPX5_CMU_MAX; idx++) {
+ err = sparx5_cmu_cfg(priv, idx);
+ if (err) {
+ dev_err(priv->dev, "CMU %u, error: %d\n", idx, err);
+ goto leave;
+ }
+ }
+ priv->cmu_enabled = true;
+ }
+leave:
+ return err;
+}
+
static void sparx5_sd25g28_reset(void __iomem *regs[],
struct sparx5_sd25g28_params *params,
u32 sd_index)
@@ -1966,159 +2119,6 @@ static int sparx5_serdes_clock_config(struct sparx5_serdes_macro *macro)
return 0;
}

-static int sparx5_cmu_apply_cfg(struct sparx5_serdes_private *priv,
- u32 cmu_idx,
- void __iomem *cmu_tgt,
- void __iomem *cmu_cfg_tgt,
- u32 spd10g)
-{
- void __iomem **regs = priv->regs;
- struct device *dev = priv->dev;
- int value;
-
- cmu_tgt = sdx5_inst_get(priv, TARGET_SD_CMU, cmu_idx);
- cmu_cfg_tgt = sdx5_inst_get(priv, TARGET_SD_CMU_CFG, cmu_idx);
-
- if (cmu_idx == 1 || cmu_idx == 4 || cmu_idx == 7 ||
- cmu_idx == 10 || cmu_idx == 13) {
- spd10g = 0;
- }
-
- sdx5_inst_rmw(SD_CMU_CFG_SD_CMU_CFG_EXT_CFG_RST_SET(1),
- SD_CMU_CFG_SD_CMU_CFG_EXT_CFG_RST,
- cmu_cfg_tgt,
- SD_CMU_CFG_SD_CMU_CFG(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CFG_SD_CMU_CFG_EXT_CFG_RST_SET(0),
- SD_CMU_CFG_SD_CMU_CFG_EXT_CFG_RST,
- cmu_cfg_tgt,
- SD_CMU_CFG_SD_CMU_CFG(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CFG_SD_CMU_CFG_CMU_RST_SET(1),
- SD_CMU_CFG_SD_CMU_CFG_CMU_RST,
- cmu_cfg_tgt,
- SD_CMU_CFG_SD_CMU_CFG(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CMU_45_R_DWIDTHCTRL_FROM_HWT_SET(0x1) |
- SD_CMU_CMU_45_R_REFCK_SSC_EN_FROM_HWT_SET(0x1) |
- SD_CMU_CMU_45_R_LINK_BUF_EN_FROM_HWT_SET(0x1) |
- SD_CMU_CMU_45_R_BIAS_EN_FROM_HWT_SET(0x1) |
- SD_CMU_CMU_45_R_EN_RATECHG_CTRL_SET(0x0),
- SD_CMU_CMU_45_R_DWIDTHCTRL_FROM_HWT |
- SD_CMU_CMU_45_R_REFCK_SSC_EN_FROM_HWT |
- SD_CMU_CMU_45_R_LINK_BUF_EN_FROM_HWT |
- SD_CMU_CMU_45_R_BIAS_EN_FROM_HWT |
- SD_CMU_CMU_45_R_EN_RATECHG_CTRL,
- cmu_tgt,
- SD_CMU_CMU_45(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CMU_47_R_PCS2PMA_PHYMODE_4_0_SET(0),
- SD_CMU_CMU_47_R_PCS2PMA_PHYMODE_4_0,
- cmu_tgt,
- SD_CMU_CMU_47(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CMU_1B_CFG_RESERVE_7_0_SET(0),
- SD_CMU_CMU_1B_CFG_RESERVE_7_0,
- cmu_tgt,
- SD_CMU_CMU_1B(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CMU_0D_CFG_JC_BYP_SET(0x1),
- SD_CMU_CMU_0D_CFG_JC_BYP,
- cmu_tgt,
- SD_CMU_CMU_0D(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CMU_1F_CFG_VTUNE_SEL_SET(1),
- SD_CMU_CMU_1F_CFG_VTUNE_SEL,
- cmu_tgt,
- SD_CMU_CMU_1F(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CMU_00_CFG_PLL_TP_SEL_1_0_SET(3),
- SD_CMU_CMU_00_CFG_PLL_TP_SEL_1_0,
- cmu_tgt,
- SD_CMU_CMU_00(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CMU_05_CFG_BIAS_TP_SEL_1_0_SET(3),
- SD_CMU_CMU_05_CFG_BIAS_TP_SEL_1_0,
- cmu_tgt,
- SD_CMU_CMU_05(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CMU_30_R_PLL_DLOL_EN_SET(1),
- SD_CMU_CMU_30_R_PLL_DLOL_EN,
- cmu_tgt,
- SD_CMU_CMU_30(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CMU_09_CFG_SW_10G_SET(spd10g),
- SD_CMU_CMU_09_CFG_SW_10G,
- cmu_tgt,
- SD_CMU_CMU_09(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CFG_SD_CMU_CFG_CMU_RST_SET(0),
- SD_CMU_CFG_SD_CMU_CFG_CMU_RST,
- cmu_cfg_tgt,
- SD_CMU_CFG_SD_CMU_CFG(cmu_idx));
-
- msleep(20);
-
- sdx5_inst_rmw(SD_CMU_CMU_44_R_PLL_RSTN_SET(0),
- SD_CMU_CMU_44_R_PLL_RSTN,
- cmu_tgt,
- SD_CMU_CMU_44(cmu_idx));
-
- sdx5_inst_rmw(SD_CMU_CMU_44_R_PLL_RSTN_SET(1),
- SD_CMU_CMU_44_R_PLL_RSTN,
- cmu_tgt,
- SD_CMU_CMU_44(cmu_idx));
-
- msleep(20);
-
- value = readl(sdx5_addr(regs, SD_CMU_CMU_E0(cmu_idx)));
- value = SD_CMU_CMU_E0_PLL_LOL_UDL_GET(value);
-
- if (value) {
- dev_err(dev, "CMU PLL Loss of Lock: 0x%x\n", value);
- return -EINVAL;
- }
- sdx5_inst_rmw(SD_CMU_CMU_0D_CFG_PMA_TX_CK_PD_SET(0),
- SD_CMU_CMU_0D_CFG_PMA_TX_CK_PD,
- cmu_tgt,
- SD_CMU_CMU_0D(cmu_idx));
- return 0;
-}
-
-static int sparx5_cmu_cfg(struct sparx5_serdes_private *priv, u32 cmu_idx)
-{
- void __iomem *cmu_tgt, *cmu_cfg_tgt;
- u32 spd10g = 1;
-
- if (cmu_idx == 1 || cmu_idx == 4 || cmu_idx == 7 ||
- cmu_idx == 10 || cmu_idx == 13) {
- spd10g = 0;
- }
-
- cmu_tgt = sdx5_inst_get(priv, TARGET_SD_CMU, cmu_idx);
- cmu_cfg_tgt = sdx5_inst_get(priv, TARGET_SD_CMU_CFG, cmu_idx);
-
- return sparx5_cmu_apply_cfg(priv, cmu_idx, cmu_tgt, cmu_cfg_tgt, spd10g);
-}
-
-static int sparx5_serdes_cmu_enable(struct sparx5_serdes_private *priv)
-{
- int idx, err = 0;
-
- if (!priv->cmu_enabled) {
- for (idx = 0; idx < SPX5_CMU_MAX; idx++) {
- err = sparx5_cmu_cfg(priv, idx);
- if (err) {
- dev_err(priv->dev, "CMU %u, error: %d\n", idx, err);
- goto leave;
- }
- }
- priv->cmu_enabled = true;
- }
-leave:
- return err;
-}
-
static int sparx5_serdes_get_serdesmode(phy_interface_t portmode, int speed)
{
switch (portmode) {
--
2.34.1

2023-04-17 18:08:40

by Daniel Machon

[permalink] [raw]
Subject: [PATCH 4/7] phy: sparx5-serdes: power down all CMUs by default

All CMUs are powered up initially. This uses needless power. This patch
makes sure all CMUs are powered down by default. This involves
configuring a number reference clock and power-down registers of the
CMU.

Individual CMUs are later powered up, when the serdes lanes are
configured.

Signed-off-by: Daniel Machon <[email protected]>
---
drivers/phy/microchip/sparx5_serdes.c | 51 +++++++++++++++++++++++++++
1 file changed, 51 insertions(+)

diff --git a/drivers/phy/microchip/sparx5_serdes.c b/drivers/phy/microchip/sparx5_serdes.c
index d8620e0fae7b..0e9db7b36b60 100644
--- a/drivers/phy/microchip/sparx5_serdes.c
+++ b/drivers/phy/microchip/sparx5_serdes.c
@@ -1078,6 +1078,54 @@ static int sparx5_serdes_cmu_enable(struct sparx5_serdes_private *priv)
return err;
}

+static void sparx5_serdes_cmu_power_off(struct sparx5_serdes_private *priv)
+{
+ void __iomem *cmu_inst, *cmu_cfg_inst;
+ int i;
+
+ /* Power down each CMU */
+ for (i = 0; i < SPX5_CMU_MAX; i++) {
+ cmu_inst = sdx5_inst_get(priv, TARGET_SD_CMU, i);
+ cmu_cfg_inst = sdx5_inst_get(priv, TARGET_SD_CMU_CFG, i);
+
+ sdx5_inst_rmw(SD_CMU_CFG_SD_CMU_CFG_EXT_CFG_RST_SET(0),
+ SD_CMU_CFG_SD_CMU_CFG_EXT_CFG_RST, cmu_cfg_inst,
+ SD_CMU_CFG_SD_CMU_CFG(0));
+
+ sdx5_inst_rmw(SD_CMU_CMU_05_CFG_REFCK_TERM_EN_SET(0),
+ SD_CMU_CMU_05_CFG_REFCK_TERM_EN, cmu_inst,
+ SD_CMU_CMU_05(0));
+
+ sdx5_inst_rmw(SD_CMU_CMU_09_CFG_EN_TX_CK_DN_SET(0),
+ SD_CMU_CMU_09_CFG_EN_TX_CK_DN, cmu_inst,
+ SD_CMU_CMU_09(0));
+
+ sdx5_inst_rmw(SD_CMU_CMU_06_CFG_VCO_PD_SET(1),
+ SD_CMU_CMU_06_CFG_VCO_PD, cmu_inst,
+ SD_CMU_CMU_06(0));
+
+ sdx5_inst_rmw(SD_CMU_CMU_09_CFG_EN_TX_CK_UP_SET(0),
+ SD_CMU_CMU_09_CFG_EN_TX_CK_UP, cmu_inst,
+ SD_CMU_CMU_09(0));
+
+ sdx5_inst_rmw(SD_CMU_CMU_08_CFG_CK_TREE_PD_SET(1),
+ SD_CMU_CMU_08_CFG_CK_TREE_PD, cmu_inst,
+ SD_CMU_CMU_08(0));
+
+ sdx5_inst_rmw(SD_CMU_CMU_0D_CFG_REFCK_PD_SET(1) |
+ SD_CMU_CMU_0D_CFG_PD_DIV64_SET(1) |
+ SD_CMU_CMU_0D_CFG_PD_DIV66_SET(1),
+ SD_CMU_CMU_0D_CFG_REFCK_PD |
+ SD_CMU_CMU_0D_CFG_PD_DIV64 |
+ SD_CMU_CMU_0D_CFG_PD_DIV66, cmu_inst,
+ SD_CMU_CMU_0D(0));
+
+ sdx5_inst_rmw(SD_CMU_CMU_06_CFG_CTRL_LOGIC_PD_SET(1),
+ SD_CMU_CMU_06_CFG_CTRL_LOGIC_PD, cmu_inst,
+ SD_CMU_CMU_06(0));
+ }
+}
+
static void sparx5_sd25g28_reset(void __iomem *regs[],
struct sparx5_sd25g28_params *params,
u32 sd_index)
@@ -2521,6 +2569,9 @@ static int sparx5_serdes_probe(struct platform_device *pdev)
return err;
}

+ /* Power down all CMUs by default */
+ sparx5_serdes_cmu_power_off(priv);
+
provider = devm_of_phy_provider_register(priv->dev, sparx5_serdes_xlate);

return PTR_ERR_OR_ZERO(provider);
--
2.34.1

2023-04-17 18:08:43

by Daniel Machon

[permalink] [raw]
Subject: [PATCH 2/7] phy: sparx5-serdes: configure optimal quiet mode for serdes lanes

All the serdes lanes of the sparx5 will transition between normal mode
and quiet mode, depending on activity. Make sure that the quiet mode is
configured optimally for all lanes initially. Although not much, this
will save a small amount of power.

Signed-off-by: Daniel Machon <[email protected]>
---
drivers/phy/microchip/sparx5_serdes.c | 32 ++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)

diff --git a/drivers/phy/microchip/sparx5_serdes.c b/drivers/phy/microchip/sparx5_serdes.c
index ab1b0986aa67..6ba058b2482f 100644
--- a/drivers/phy/microchip/sparx5_serdes.c
+++ b/drivers/phy/microchip/sparx5_serdes.c
@@ -26,6 +26,9 @@
#define SPX5_SERDES_10G_START 13
#define SPX5_SERDES_25G_START 25

+/* Optimal power settings from GUC */
+#define SPX5_SERDES_QUIET_MODE_VAL 0x01ef4e0c
+
enum sparx5_10g28cmu_mode {
SPX5_SD10G28_CMU_MAIN = 0,
SPX5_SD10G28_CMU_AUX1 = 1,
@@ -1899,7 +1902,7 @@ static int sparx5_sd10g28_config(struct sparx5_serdes_macro *macro, bool reset)
static int sparx5_serdes_power_save(struct sparx5_serdes_macro *macro, u32 pwdn)
{
struct sparx5_serdes_private *priv = macro->priv;
- void __iomem *sd_inst;
+ void __iomem *sd_inst, *sd_lane_inst;

if (macro->serdestype == SPX5_SDT_6G)
sd_inst = sdx5_inst_get(priv, TARGET_SD6G_LANE, macro->stpidx);
@@ -1909,12 +1912,36 @@ static int sparx5_serdes_power_save(struct sparx5_serdes_macro *macro, u32 pwdn)
sd_inst = sdx5_inst_get(priv, TARGET_SD25G_LANE, macro->stpidx);

if (macro->serdestype == SPX5_SDT_25G) {
+ sd_lane_inst = sdx5_inst_get(priv, TARGET_SD_LANE_25G,
+ macro->stpidx);
+ /* Take serdes out of reset */
+ sdx5_inst_rmw(SD_LANE_25G_SD_LANE_CFG_EXT_CFG_RST_SET(0),
+ SD_LANE_25G_SD_LANE_CFG_EXT_CFG_RST, sd_lane_inst,
+ SD_LANE_25G_SD_LANE_CFG(0));
+
+ /* Configure optimal settings for quiet mode */
+ sdx5_inst_rmw(SD_LANE_25G_QUIET_MODE_6G_QUIET_MODE_SET(SPX5_SERDES_QUIET_MODE_VAL),
+ SD_LANE_25G_QUIET_MODE_6G_QUIET_MODE,
+ sd_lane_inst, SD_LANE_25G_QUIET_MODE_6G(0));
+
sdx5_inst_rmw(SD25G_LANE_LANE_04_LN_CFG_PD_DRIVER_SET(pwdn),
SD25G_LANE_LANE_04_LN_CFG_PD_DRIVER,
sd_inst,
SD25G_LANE_LANE_04(0));
} else {
/* 6G and 10G */
+ sd_lane_inst = sdx5_inst_get(priv, TARGET_SD_LANE, macro->sidx);
+
+ /* Take serdes out of reset */
+ sdx5_inst_rmw(SD_LANE_SD_LANE_CFG_EXT_CFG_RST_SET(0),
+ SD_LANE_SD_LANE_CFG_EXT_CFG_RST, sd_lane_inst,
+ SD_LANE_SD_LANE_CFG(0));
+
+ /* Configure optimal settings for quiet mode */
+ sdx5_inst_rmw(SD_LANE_QUIET_MODE_6G_QUIET_MODE_SET(SPX5_SERDES_QUIET_MODE_VAL),
+ SD_LANE_QUIET_MODE_6G_QUIET_MODE, sd_lane_inst,
+ SD_LANE_QUIET_MODE_6G(0));
+
sdx5_inst_rmw(SD10G_LANE_LANE_06_CFG_PD_DRIVER_SET(pwdn),
SD10G_LANE_LANE_06_CFG_PD_DRIVER,
sd_inst,
@@ -2308,6 +2335,9 @@ static int sparx5_phy_create(struct sparx5_serdes_private *priv,

phy_set_drvdata(*phy, macro);

+ /* Power off serdes by default */
+ sparx5_serdes_power_off(*phy);
+
return 0;
}

--
2.34.1

2023-04-17 18:09:03

by Daniel Machon

[permalink] [raw]
Subject: [PATCH 5/7] phy: sparx5-serdes: power on CMUs individually

Power on the CMU instance, that provides the clock for the serdes, given the
specified serdes mode and index. The CMU instance is looked up, using a
preset map of serdes mode and index to CMU index.

Signed-off-by: Daniel Machon <[email protected]>
---
drivers/phy/microchip/sparx5_serdes.c | 43 ++++++++++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)

diff --git a/drivers/phy/microchip/sparx5_serdes.c b/drivers/phy/microchip/sparx5_serdes.c
index 0e9db7b36b60..a6638d783a01 100644
--- a/drivers/phy/microchip/sparx5_serdes.c
+++ b/drivers/phy/microchip/sparx5_serdes.c
@@ -25,6 +25,7 @@

#define SPX5_SERDES_10G_START 13
#define SPX5_SERDES_25G_START 25
+#define SPX5_SERDES_6G10G_CNT SPX5_SERDES_25G_START

/* Optimal power settings from GUC */
#define SPX5_SERDES_QUIET_MODE_VAL 0x01ef4e0c
@@ -34,6 +35,7 @@ enum sparx5_10g28cmu_mode {
SPX5_SD10G28_CMU_AUX1 = 1,
SPX5_SD10G28_CMU_AUX2 = 3,
SPX5_SD10G28_CMU_NONE = 4,
+ SPX5_SD10G28_CMU_MAX,
};

enum sparx5_sd25g28_mode_preset_type {
@@ -1078,6 +1080,39 @@ static int sparx5_serdes_cmu_enable(struct sparx5_serdes_private *priv)
return err;
}

+/* Map of 6G/10G serdes mode and index to CMU index. */
+static const int
+sparx5_serdes_cmu_map[SPX5_SD10G28_CMU_MAX][SPX5_SERDES_6G10G_CNT] = {
+ [SPX5_SD10G28_CMU_MAIN] = { 2, 2, 2, 2, 2,
+ 2, 2, 2, 5, 5,
+ 5, 5, 5, 5, 5,
+ 5, 8, 11, 11, 11,
+ 11, 11, 11, 11, 11 },
+ [SPX5_SD10G28_CMU_AUX1] = { 0, 0, 3, 3, 3,
+ 3, 3, 3, 3, 3,
+ 6, 6, 6, 6, 6,
+ 6, 6, 9, 9, 12,
+ 12, 12, 12, 12, 12 },
+ [SPX5_SD10G28_CMU_AUX2] = { 1, 1, 1, 1, 4,
+ 4, 4, 4, 4, 4,
+ 4, 4, 7, 7, 7,
+ 7, 7, 10, 10, 10,
+ 10, 13, 13, 13, 13 },
+ [SPX5_SD10G28_CMU_NONE] = { 1, 1, 1, 1, 4,
+ 4, 4, 4, 4, 4,
+ 4, 4, 7, 7, 7,
+ 7, 7, 10, 10, 10,
+ 10, 13, 13, 13, 13 },
+};
+
+/* Get the index of the CMU which provides the clock for the specified serdes
+ * mode and index.
+ */
+static int sparx5_serdes_cmu_get(enum sparx5_10g28cmu_mode mode, int sd_index)
+{
+ return sparx5_serdes_cmu_map[mode][sd_index];
+}
+
static void sparx5_serdes_cmu_power_off(struct sparx5_serdes_private *priv)
{
void __iomem *cmu_inst, *cmu_cfg_inst;
@@ -1626,7 +1661,13 @@ static int sparx5_sd10g28_apply_params(struct sparx5_serdes_macro *macro,
u32 lane_index = macro->sidx;
u32 sd_index = macro->stpidx;
void __iomem *sd_inst;
- u32 value;
+ u32 value, cmu_idx;
+ int err;
+
+ cmu_idx = sparx5_serdes_cmu_get(params->cmu_sel, lane_index);
+ err = sparx5_cmu_cfg(priv, cmu_idx);
+ if (err)
+ return err;

if (params->is_6g)
sd_inst = sdx5_inst_get(priv, TARGET_SD6G_LANE, sd_index);
--
2.34.1

2023-04-17 18:09:22

by Daniel Machon

[permalink] [raw]
Subject: [PATCH 6/7] phy: sparx5-serdes: remove power up of all CMUs

CMUs should not be powered up by default anymore, so remove responsible
code.

Signed-off-by: Daniel Machon <[email protected]>
---
drivers/phy/microchip/sparx5_serdes.c | 25 -------------------------
drivers/phy/microchip/sparx5_serdes.h | 1 -
2 files changed, 26 deletions(-)

diff --git a/drivers/phy/microchip/sparx5_serdes.c b/drivers/phy/microchip/sparx5_serdes.c
index a6638d783a01..eb9352d1de7e 100644
--- a/drivers/phy/microchip/sparx5_serdes.c
+++ b/drivers/phy/microchip/sparx5_serdes.c
@@ -1062,24 +1062,6 @@ static int sparx5_cmu_cfg(struct sparx5_serdes_private *priv, u32 cmu_idx)
return sparx5_cmu_apply_cfg(priv, cmu_idx, cmu_tgt, cmu_cfg_tgt, spd10g);
}

-static int sparx5_serdes_cmu_enable(struct sparx5_serdes_private *priv)
-{
- int idx, err = 0;
-
- if (!priv->cmu_enabled) {
- for (idx = 0; idx < SPX5_CMU_MAX; idx++) {
- err = sparx5_cmu_cfg(priv, idx);
- if (err) {
- dev_err(priv->dev, "CMU %u, error: %d\n", idx, err);
- goto leave;
- }
- }
- priv->cmu_enabled = true;
- }
-leave:
- return err;
-}
-
/* Map of 6G/10G serdes mode and index to CMU index. */
static const int
sparx5_serdes_cmu_map[SPX5_SD10G28_CMU_MAX][SPX5_SERDES_6G10G_CNT] = {
@@ -2236,10 +2218,6 @@ static int sparx5_serdes_config(struct sparx5_serdes_macro *macro)
int serdesmode;
int err;

- err = sparx5_serdes_cmu_enable(macro->priv);
- if (err)
- return err;
-
serdesmode = sparx5_serdes_get_serdesmode(macro->portmode, macro->speed);
if (serdesmode < 0) {
dev_err(dev, "SerDes %u, interface not supported: %s\n",
@@ -2331,9 +2309,6 @@ static int sparx5_serdes_reset(struct phy *phy)
struct sparx5_serdes_macro *macro = phy_get_drvdata(phy);
int err;

- err = sparx5_serdes_cmu_enable(macro->priv);
- if (err)
- return err;
if (macro->serdestype == SPX5_SDT_25G)
err = sparx5_sd25g28_config(macro, true);
else
diff --git a/drivers/phy/microchip/sparx5_serdes.h b/drivers/phy/microchip/sparx5_serdes.h
index 0a3e496e6210..13f94a29225a 100644
--- a/drivers/phy/microchip/sparx5_serdes.h
+++ b/drivers/phy/microchip/sparx5_serdes.h
@@ -30,7 +30,6 @@ struct sparx5_serdes_private {
struct device *dev;
void __iomem *regs[NUM_TARGETS];
struct phy *phys[SPX5_SERDES_MAX];
- bool cmu_enabled;
unsigned long coreclock;
};

--
2.34.1

2023-04-17 18:10:17

by Daniel Machon

[permalink] [raw]
Subject: [PATCH 7/7] phy: sparx5-serdes: add skip_cmu_cfg check when configuring lanes

Add a check for skip_cmu_cfg when configuring the serdes lane. All
individual serdeses are reset upon first configuration. Resetting the
serdes involves reconfiguring it with preset values. The serdesmode is
required to determine the clock-providing CMU, therefore make sure the
serdes is not reconfigured if the serdesmode is not set.

Signed-off-by: Daniel Machon <[email protected]>
---
drivers/phy/microchip/sparx5_serdes.c | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/drivers/phy/microchip/sparx5_serdes.c b/drivers/phy/microchip/sparx5_serdes.c
index eb9352d1de7e..01bd5ea620c5 100644
--- a/drivers/phy/microchip/sparx5_serdes.c
+++ b/drivers/phy/microchip/sparx5_serdes.c
@@ -1646,6 +1646,10 @@ static int sparx5_sd10g28_apply_params(struct sparx5_serdes_macro *macro,
u32 value, cmu_idx;
int err;

+ /* Do not configure serdes if CMU is not to be configured too */
+ if (params->skip_cmu_cfg)
+ return 0;
+
cmu_idx = sparx5_serdes_cmu_get(params->cmu_sel, lane_index);
err = sparx5_cmu_cfg(priv, cmu_idx);
if (err)
@@ -2111,6 +2115,7 @@ static int sparx5_sd10g28_config(struct sparx5_serdes_macro *macro, bool reset)
.rxinvert = 1,
.txswing = 240,
.reg_rst = reset,
+ .skip_cmu_cfg = reset,
};
int err;

--
2.34.1

2023-05-08 12:03:49

by Vinod Koul

[permalink] [raw]
Subject: Re: [PATCH 0/7] Power down serdes lanes and CMUs initially

On 17-04-23, 20:03, Daniel Machon wrote:
> This patch series aims to lower the power consumption of the Sparx5
> switch chip, by configuring optimal quiet mode for serdes lanes, as well
> as powering down unused CMUs (Clock Multiplier Unit). Before this series,
> serdes lane quiet mode were not optimally configured, and all the CMUs were
> powered on by default. This uses needless power.
>
> Each serdes lane is provided a CMU clock, depending on the serdes mode
> and the serdes index. CMUs will now be powered on individually, when
> needed. The amount of power saved varies, depending on the port
> configuration. As a reference, this change saves about 1W on
> sparx5_pcb135 and 2W on sparx5_pcb134.

Applied, thanks

--
~Vinod