Re: [PATCH net-next v6 3/9] net: dsa: realtek: rtl8365mb: set speed for family D
From: Luiz Angelo Daros de Luca
Date: Sun Oct 18 2026 - 18:41:21 EST
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index 840db5a..2dabc4d 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
> @@ -344,6 +344,7 @@
> #define RTL8365MB_PORT_SPEED_10M 0
> #define RTL8365MB_PORT_SPEED_100M 1
> #define RTL8365MB_PORT_SPEED_1000M 2
> +#define RTL8365MB_D_PORT_SPEED_2500M 5
I also used the same _<FAMILY>_ tag trick previously, but it gets
confusing quickly. Does RTL8365MB_D_xxx mean something exclusive to
family D, or family D and later? I hope a more seasoned reviewer can
weigh in on this.
When I implemented support for the RTL8367R (pre-family B), I ran into
this exact problem of macro definitions (and code conditions) turning
into unmaintainable spaghetti.
In this particular case, as 5 does not exist in Family C and the
semantics of the existing values still hold, you can omit the _D_. A
simple comment will be enough if the semantics change for a future
family.
> /* External interface force configuration registers 0~2 */
> #define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0 0x1310 /* EXT0 */
> @@ -361,6 +362,28 @@
> #define RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK BIT(4)
> #define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK BIT(2)
> #define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK GENMASK(1, 0)
> +/* Width of the FORCE_SPEED field, bits[1:0] */
> +#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_WIDTH 2
This is what bitfield masks/macros are for; there is no need for an
explicit WIDTH define. More comments below on the code using it.
> +/* bits[13:12]; bit 13 reserved, no defined speed value uses it yet */
> +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK GENMASK(13, 12)
> +
> +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE 0x12c0
> +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG(_port) \
> + (RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_BASE + (_port))
> +
> +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE 0x12c8
> +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_REG_EN(_port) \
> + (RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_BASE + (_port))
> +
> +/* Per-port force-mode enable. The vendor RTL8367D API
> + * (dal_rtl8367d_port_macForceLink_set()) treats it as an on/off switch for
> + * the whole forced configuration: all bits set when the MAC is forced, zero
> + * when it is not. The meaning of the individual bits is not documented.
> + * This driver always forces the MAC, as on family C, so all bits are set
> + * on every call, including link down.
> + */
> +#define RTL8365MB_D_DIGITAL_INTERFACE_FORCE_EN_ALL_MASK GENMASK(15, 0)
>
> /* CPU port mask register - controls which ports are treated as CPU ports */
> #define RTL8365MB_CPU_PORT_MASK_REG 0x1219
> @@ -1646,23 +1669,32 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
> u32 r_duplex;
> u32 r_speed;
> u32 r_link;
> + bool is_d;
Thinking about future families like E, F, G..., a boolean flag like
is_d might not scale well.
You can still keep the family id in a variable to keep lines short and clean:
enum rtl8365mb_family family;
> int val;
> int ret;
[...]
> FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK, r_link) |
> FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK,
> r_duplex) |
> - FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK, r_speed);
> - ret = regmap_write(priv->map,
> - RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(extint->id),
> - val);
> - if (ret)
> - return ret;
> + FIELD_PREP(RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK,
> + r_speed &
> + RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK);
Isn't the point of FIELD_GET/FIELD_PREP to avoid bitwise operations
like this? For fields split between bit ranges, you can use the same
trick as in RTL8365MB_CVLAN_MBR_LO_MASK, RTL8365MB_CVLAN_MBR_HI_MASK.
> +
> + if (is_d) {
For multiple/bigger differences, we will need to have different
functions for each family. In this case, for small differences, I
think is_d, is_e, is_f... are better handled by a switch statement
with the same indentation cost as an if:
switch (family) {
case RTL8365MB_FAMILY_D:
...
break;
case RTL8365MB_FAMILY_C:
...
}
And, by omitting the default clause, when we add a future FAMILY_E and
forget to add a case for it, the compiler will warn us of the missing
check. You can even use a (commented) fallthrough when a family
incrementally expands a feature.
> + /* Speed is 3 bits on family D: bits[1:0] go into FORCE_SPEED,
> + * bit[2] goes into FORCE_SPEED2 (bit 12); only 2500M sets it,
> + * bit 13 of the field is unused.
> + */
> + val |= FIELD_PREP(RTL8365MB_D_DIGITAL_INTERFACE_FORCE_SPEED2_MASK,
> + r_speed >>
> + RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_WIDTH);
Here is where FIELD_GET() with a HI/LO mask avoids the manual shift
and makes the WIDTH definition unnecessary.
Best regards,
Luiz