return 0;
}
+static int rtpcs_93xx_sds_get_pll_config(struct rtpcs_serdes *sds, enum rtpcs_sds_pll_type pll,
+ enum rtpcs_sds_pll_speed *speed)
+{
+ struct rtpcs_serdes *even_sds = rtpcs_sds_get_even(sds);
+ int sbit, speed_val;
+
+ /*
+ * PLL config is shared between adjacent SerDes in the even lane. Each SerDes defines
+ * what PLL it needs (ring or LC) while the PLL itself stores the current speed.
+ */
+
+ sbit = pll == RTPCS_SDS_PLL_TYPE_LC ? 8 : 12;
+ speed_val = rtpcs_sds_read_bits(even_sds, 0x20, 0x12, sbit + 3, sbit);
+ if (speed_val < 0)
+ return speed_val;
+
+ /* bit 0 is force-bit, bits [3:1] are speed selector */
+ *speed = (enum rtpcs_sds_pll_speed)(speed_val >> 1);
+ return 0;
+}
+
+static int rtpcs_93xx_sds_set_pll_config(struct rtpcs_serdes *sds, enum rtpcs_sds_pll_type pll,
+ enum rtpcs_sds_pll_speed speed)
+{
+ struct rtpcs_serdes *even_sds = rtpcs_sds_get_even(sds);
+ int sbit = pll == RTPCS_SDS_PLL_TYPE_LC ? 8 : 12;
+ int ret;
+
+ if (speed >= RTPCS_SDS_PLL_SPD_END)
+ return -EINVAL;
+
+ if (pll >= RTPCS_SDS_PLL_TYPE_END)
+ return -EINVAL;
+
+ if ((pll == RTPCS_SDS_PLL_TYPE_RING) && (speed == RTPCS_SDS_PLL_SPD_10000))
+ return -EINVAL;
+
+ /*
+ * A SerDes clock can either be taken from the low speed ring PLL or the high speed
+ * LC PLL. As it is unclear if disabling PLLs has any positive or negative effect,
+ * always activate both.
+ */
+
+ ret = rtpcs_sds_write_bits(even_sds, 0x20, 0x12, 3, 0, 0xf);
+ if (ret < 0)
+ return ret;
+
+ /* bit 0 is force-bit, bits [3:1] are speed selector */
+ ret = rtpcs_sds_write_bits(even_sds, 0x20, 0x12, sbit + 3, sbit, (speed << 1) | BIT(0));
+ if (ret < 0)
+ return ret;
+
+ if (sds->ops->reset_cmu)
+ ret = sds->ops->reset_cmu(sds, pll);
+
+ return ret;
+}
+
+static int rtpcs_93xx_sds_config_cmu(struct rtpcs_serdes *sds, enum rtpcs_sds_mode hw_mode)
+{
+ struct rtpcs_serdes *nb_sds = rtpcs_sds_get_neighbor(sds);
+ enum rtpcs_sds_pll_speed speed, neighbor_speed;
+ enum rtpcs_sds_pll_type pll, neighbor_pll;
+ bool speed_changed = true;
+ int ret;
+
+ /*
+ * A SerDes pair on the RTL930x is driven by two PLLs. A low speed ring PLL can generate
+ * signals of 1.25G and 3.125G for link speeds of 1G/2.5G. A high speed LC PLL can
+ * additionally generate a 10.3125G signal for 10G speeds. To drive the pair at different
+ * speeds each SerDes must use its own PLL. But what if the SerDess attached to the ring
+ * PLL suddenly needs 10G but the LC PLL is running at 1G? To avoid reconfiguring the
+ * "partner" SerDes we must choose wisely what assignment serves the current needs. The
+ * logic boils down to the following rules:
+ *
+ * - Use ring PLL for slow 1G speeds
+ * - Use LC PLL for fast 10G speeds
+ * - For 2.5G prefer ring over LC PLL
+ *
+ * However, when we want to configure 10G speed while the other SerDes is already using
+ * the LC PLL for a slower speed, there is no way to avoid reconfiguration. Note that
+ * this can even happen when the other SerDes is not actually in use, because changing
+ * the state of a SerDes back to RTL930X_SDS_OFF is not (yet) implemented.
+ */
+
+ ret = nb_sds->ops->get_pll_select(nb_sds, &neighbor_pll);
+ if (ret < 0)
+ return ret;
+
+ ret = rtpcs_93xx_sds_get_pll_config(nb_sds, neighbor_pll, &neighbor_speed);
+ if (ret < 0)
+ return ret;
+
+ ret = rtpcs_sds_select_pll_speed(hw_mode, &speed);
+ if (ret < 0)
+ return ret;
+
+ if (nb_sds->hw_mode == RTPCS_SDS_MODE_OFF)
+ pll = speed == RTPCS_SDS_PLL_SPD_10000 ? RTPCS_SDS_PLL_TYPE_LC
+ : RTPCS_SDS_PLL_TYPE_RING;
+ else if (speed == neighbor_speed) {
+ speed_changed = false;
+ pll = neighbor_pll;
+ } else if (neighbor_pll == RTPCS_SDS_PLL_TYPE_RING)
+ pll = RTPCS_SDS_PLL_TYPE_LC;
+ else if (speed == RTPCS_SDS_PLL_SPD_10000) {
+ pr_info("%s: SDS %d needs LC PLL, reconfigure SDS %d to use ring PLL\n",
+ __func__, sds->id, nb_sds->id);
+ ret = nb_sds->ops->reconfigure_to_pll(nb_sds, RTPCS_SDS_PLL_TYPE_RING);
+ if (ret < 0)
+ return ret;
+
+ pll = RTPCS_SDS_PLL_TYPE_LC;
+ } else
+ pll = RTPCS_SDS_PLL_TYPE_RING;
+
+ if (speed_changed)
+ ret = rtpcs_93xx_sds_set_pll_config(sds, pll, speed);
+
+ ret = sds->ops->set_pll_select(sds, hw_mode, pll);
+ if (ret < 0)
+ return ret;
+
+ pr_info("%s: SDS %d using %s PLL for mode %d\n", __func__, sds->id,
+ pll == RTPCS_SDS_PLL_TYPE_LC ? "LC" : "ring", hw_mode);
+
+ return ret;
+}
+
/* RTL930X */
/*
return 0;
}
-static int rtpcs_930x_sds_get_pll_config(struct rtpcs_serdes *sds, enum rtpcs_sds_pll_type pll,
- enum rtpcs_sds_pll_speed *speed)
-{
- struct rtpcs_serdes *even_sds = rtpcs_sds_get_even(sds);
- int sbit, speed_val;
-
- /*
- * PLL data is shared between adjacent SerDes in the even lane. Each SerDes defines
- * what PLL it needs (ring or LC) while the PLL itself stores the current speed.
- */
-
- sbit = pll == RTPCS_SDS_PLL_TYPE_LC ? 8 : 12;
- speed_val = rtpcs_sds_read_bits(even_sds, 0x20, 0x12, sbit + 3, sbit);
- if (speed_val < 0)
- return speed_val;
-
- /* bit 0 is force-bit, bits [3:1] are speed selector */
- *speed = (enum rtpcs_sds_pll_speed)(speed_val >> 1);
- return 0;
-}
-
static int rtpcs_930x_sds_set_pll_select(struct rtpcs_serdes *sds, enum rtpcs_sds_mode hw_mode,
enum rtpcs_sds_pll_type pll)
{
return rtpcs_sds_write_bits(even_sds, 0x20, 0x12, pbit + 1, pbit, (pll << 1) | BIT(0));
}
-static int rtpcs_930x_sds_reset_cmu(struct rtpcs_serdes *sds)
+static int rtpcs_930x_sds_reset_cmu(struct rtpcs_serdes *sds, enum rtpcs_sds_pll_type pll)
{
struct rtpcs_serdes *even_sds = rtpcs_sds_get_even(sds);
int reset_sequence[4] = { 3, 2, 3, 1 };
- enum rtpcs_sds_pll_type pll;
int bit, i, ret;
/*
* of the Otto platform have different reset sequences. Luckily it always boils down
* to flipping two bits in a special sequence.
*/
- ret = rtpcs_930x_sds_get_pll_select(sds, &pll);
- if (ret < 0)
- return ret;
bit = pll == RTPCS_SDS_PLL_TYPE_LC ? 2 : 0;
return 0;
}
-static int rtpcs_930x_sds_set_pll_config(struct rtpcs_serdes *sds, enum rtpcs_sds_pll_type pll,
- enum rtpcs_sds_pll_speed speed)
-{
- struct rtpcs_serdes *even_sds = rtpcs_sds_get_even(sds);
- int sbit = pll == RTPCS_SDS_PLL_TYPE_LC ? 8 : 12;
- int ret;
-
- if (speed >= RTPCS_SDS_PLL_SPD_END)
- return -EINVAL;
-
- if (pll >= RTPCS_SDS_PLL_TYPE_END)
- return -EINVAL;
-
- if ((pll == RTPCS_SDS_PLL_TYPE_RING) && (speed == RTPCS_SDS_PLL_SPD_10000))
- return -EINVAL;
-
- /*
- * A SerDes clock can either be taken from the low speed ring PLL or the high speed
- * LC PLL. As it is unclear if disabling PLLs has any positive or negative effect,
- * always activate both.
- */
-
- ret = rtpcs_sds_write_bits(even_sds, 0x20, 0x12, 3, 0, 0xf);
- if (ret < 0)
- return ret;
-
- /* bit 0 is force-bit, bits [3:1] are speed selector */
- ret = rtpcs_sds_write_bits(even_sds, 0x20, 0x12, sbit + 3, sbit, (speed << 1) | BIT(0));
- if (ret < 0)
- return ret;
-
- return rtpcs_930x_sds_reset_cmu(sds);
-}
-
static int rtpcs_930x_sds_wait_clock_ready(struct rtpcs_serdes *sds)
{
struct rtpcs_serdes *even_sds = rtpcs_sds_get_even(sds);
if (ret < 0)
return ret;
- ret = rtpcs_930x_sds_get_pll_config(sds, old_pll, &speed);
+ ret = rtpcs_93xx_sds_get_pll_config(sds, old_pll, &speed);
if (ret < 0)
return ret;
rtpcs_930x_sds_set_power(sds, false);
__rtpcs_930x_sds_set_ip_mode(sds, RTPCS_930X_SDS_OFF);
- ret = rtpcs_930x_sds_set_pll_config(sds, pll, speed);
+ ret = rtpcs_93xx_sds_set_pll_config(sds, pll, speed);
if (ret < 0)
return ret;
return 0;
}
-static int rtpcs_930x_sds_config_cmu(struct rtpcs_serdes *sds,
- enum rtpcs_sds_mode hw_mode)
-{
- struct rtpcs_serdes *nb_sds = rtpcs_sds_get_neighbor(sds);
- enum rtpcs_sds_pll_speed speed, neighbor_speed;
- enum rtpcs_sds_pll_type pll, neighbor_pll;
- bool speed_changed = true;
- int neighbor_mode, ret;
-
- /*
- * A SerDes pair on the RTL930x is driven by two PLLs. A low speed ring PLL can generate
- * signals of 1.25G and 3.125G for link speeds of 1G/2.5G. A high speed LC PLL can
- * additionally generate a 10.3125G signal for 10G speeds. To drive the pair at different
- * speeds each SerDes must use its own PLL. But what if the SerDess attached to the ring
- * PLL suddenly needs 10G but the LC PLL is running at 1G? To avoid reconfiguring the
- * "partner" SerDes we must choose wisely what assignment serves the current needs. The
- * logic boils down to the following rules:
- *
- * - Use ring PLL for slow 1G speeds
- * - Use LC PLL for fast 10G speeds
- * - For 2.5G prefer ring over LC PLL
- *
- * However, when we want to configure 10G speed while the other SerDes is already using
- * the LC PLL for a slower speed, there is no way to avoid reconfiguration. Note that
- * this can even happen when the other SerDes is not actually in use, because changing
- * the state of a SerDes back to RTL930X_SDS_OFF is not (yet) implemented.
- */
-
- neighbor_mode = __rtpcs_930x_sds_get_ip_mode(nb_sds);
-
- ret = rtpcs_930x_sds_get_pll_select(sds, &neighbor_pll);
- if (ret < 0)
- return ret;
-
- ret = rtpcs_930x_sds_get_pll_config(nb_sds, neighbor_pll, &neighbor_speed);
- if (ret < 0)
- return ret;
-
- ret = rtpcs_sds_select_pll_speed(hw_mode, &speed);
- if (ret < 0)
- return ret;
-
- if (!neighbor_mode)
- pll = speed == RTPCS_SDS_PLL_SPD_10000 ? RTPCS_SDS_PLL_TYPE_LC
- : RTPCS_SDS_PLL_TYPE_RING;
- else if (speed == neighbor_speed) {
- speed_changed = false;
- pll = neighbor_pll;
- } else if (neighbor_pll == RTPCS_SDS_PLL_TYPE_RING)
- pll = RTPCS_SDS_PLL_TYPE_LC;
- else if (speed == RTPCS_SDS_PLL_SPD_10000) {
- pr_info("%s: SDS %d needs LC PLL, reconfigure SDS %d to use ring PLL\n",
- __func__, sds->id, nb_sds->id);
- ret = rtpcs_930x_sds_reconfigure_to_pll(nb_sds, RTPCS_SDS_PLL_TYPE_RING);
- if (ret < 0)
- return ret;
-
- pll = RTPCS_SDS_PLL_TYPE_LC;
- } else
- pll = RTPCS_SDS_PLL_TYPE_RING;
-
- if (speed_changed)
- ret = rtpcs_930x_sds_set_pll_config(sds, pll, speed);
-
- ret = rtpcs_930x_sds_set_pll_select(sds, hw_mode, pll);
- if (ret < 0)
- return ret;
-
- pr_info("%s: SDS %d using %s PLL for mode %d\n", __func__, sds->id,
- pll == RTPCS_SDS_PLL_TYPE_LC ? "LC" : "ring", hw_mode);
-
- return ret;
-}
-
static void rtpcs_930x_sds_reset_state_machine(struct rtpcs_serdes *sds)
{
rtpcs_sds_write_bits(sds, 0x06, 0x02, 12, 12, 0x01); /* SM_RESET bit */
if (hw_mode == RTPCS_SDS_MODE_OFF)
return 0;
- ret = rtpcs_930x_sds_config_cmu(sds, hw_mode);
+ ret = rtpcs_93xx_sds_config_cmu(sds, hw_mode);
if (ret < 0)
pr_err("%s: SDS %d could not configure PLL for mode %d: %d\n", __func__,
sds->id, hw_mode, ret);
.xsg_write = rtpcs_930x_sds_op_xsg_write,
.set_autoneg = rtpcs_93xx_sds_set_autoneg,
.restart_autoneg = rtpcs_generic_sds_restart_autoneg,
+ .get_pll_select = rtpcs_930x_sds_get_pll_select,
+ .set_pll_select = rtpcs_930x_sds_set_pll_select,
+ .reset_cmu = rtpcs_930x_sds_reset_cmu,
+ .reconfigure_to_pll = rtpcs_930x_sds_reconfigure_to_pll,
};
static const struct rtpcs_sds_regs rtpcs_930x_sds_regs = {