[PATCH 1/2] net: dsa: fix phydev->speed being uninitialized for the CPU port fixed PHY

Vladimir Oltean vladimir.oltean at nxp.com
Tue Mar 29 02:03:31 CEST 2022


On Mon, Mar 28, 2022 at 03:23:02PM -0700, Tim Harvey wrote:
> On Mon, Mar 28, 2022 at 2:26 AM Vladimir Oltean <vladimir.oltean at nxp.com> wrote:
> >
> > On Fri, Mar 25, 2022 at 02:03:56PM -0700, Tim Harvey wrote:
> > > On Fri, Mar 25, 2022 at 11:07 AM Vladimir Oltean
> > > <vladimir.oltean at nxp.com> wrote:
> > > >
> > > > Hi Tim,
> > > >
> > > > On Fri, Mar 25, 2022 at 09:53:20AM -0700, Tim Harvey wrote:
> > > > > Vladimir,
> > > > >
> > > > > I came across this while looking for the best place to configure cpu
> > > > > port interface mode (ie rgmii id) for the mv88e61xx dsa driver I'm
> > > > > working on. Note that this patch causes port_probe to be called on the
> > > > > cpu port 'before' the master device has been probed. I'm not sure if
> > > > > this is intended or not?
> > > > >
> > > > > In my case I was looking to configure the cpu port interface mode when
> > > > > the port was probed but I can't do that because it happens before the
> > > > > switch is probed because of some things that need to happen there.
> > > > > Best Regards,
> > > > >
> > > > > Tim
> > > >
> > > > You're past the DM_MDIO probing issues now? Glad to hear that.
> > > > Probing the DSA CPU port before the master wasn't necessarily the
> > > > intention, but then again, I'm not sure that there's a strict ordering
> > > > guarantee between the two that needs to be satisfied?
> > > >
> > > > What do you need exactly? We could probably always reverse the
> > > > device_probe(master) call with the probing of the CPU port if the
> > > > ordering turns out to be a real problem. I can regression-test the
> > > > change on my boards, but I'd like to understand the need you have, maybe
> > > > even document it so that future changes are aware of it.
> > >
> > > Yes, I've got the mdio probing working now. The mv88e61xx driver
> > > supports several chips that have different register offsets that need
> > > to be known before indirect read/write and port read/write can be
> > > used. That detection happens early on so I have it in the dsa_probe. I
> > > would prefer to configure the cpu port interface mode (mac mode, link
> > > speed/duplex etc) once instead of doing it every time the cpu port
> > > gets enabled so I want to put that in the dsa_probe but at that time I
> > > don't have the phy_device to determine interface mode (I suppose I
> > > could get it from dt?). I noticed dsa_class calls ops->port_probe for
> > > the cpu port early so that seemed like a great place to do all that,
> > > but then I found my switch dsa_probe hadn't been called yet so I
> > > haven't identified the switch and set the register offsets yet.
> > >
> > > I have worked around the fact that the port_probe is called for the
> > > cpu port before the switch is probed I just wasn't sure if something
> > > in this patch should be changed in case others fall into this trap in
> > > the future. dsa_port_probe probes the master before calling
> > > ops->port_probe with the comment 'We depend on the master device for
> > > proper operation' so I just figured the same should be done for the
> > > pre_probe.
> >
> > Sorry, that was quite a blunder, we should definitely ensure that
> > U_BOOT_DRIVER :: probe gets called before dsa_ops :: port_probe.
> > I've made a change moving the port_probe call for the CPU port to
> > dsa_post_probe(), tested it in the sandbox, and it appears to work.
> >
> 
> Ok, sounds good - did you submit this someplace? I haven't seen it.

No, I didn't submit it anywhere. My thinking is that since the existing
DSA drivers aren't critically affected by the calling order, you could
include a change along those lines in your work for Marvell switches.

> > > I hope to send a series in the next few days but I do have a few items
> > > I still need to fix:
> > >
> > > 1. my board currently uses the mv88e61xx_hw_reset weak override to
> > > configure LEDs, GPIO's using direct register writes as I need one of
> > > the GPIO's to be configured as a 125MHz RGMII_RCLK and configure MAC
> > > interface mode(rgmii delays). I've moved the mac interface config into
> > > the driver based on the dt props (phy-mode and fixed-link subnode) but
> > > am still not sure how to go about dealing with the 'very custom' LED
> > > and GPIO config without the hassle of defining new dt bindings. I was
> > > hoping to use board_phy_config() but when that is called for the
> > > switch the phy_id is a generic PHY_FIXED_ID and when called for the
> > > ports the phydev->bus uses indirect register writes which can't be
> > > used to configure the gpios.
> >
> > How are these configs handled in Linux?
> 
> Each of the 14 GPIO's for MV88E61xx can be configured as GPIO,
> PTP_TRIG (PTP output trigger), PTP_EVREQ (PTP event request input),
> PTP_EXTCLK (PTP ext clock input), SE_RCLK0 (SyncE RX clock 0 output),
> SE_RCLK1 (SyncE RX clock 1 output), or CLK125 (125 MHz clock output).
> Currently the only config handled in Linux is PTP_EVREQ for PTP
> support.
> 
> Each port has 2 LED's which can be configured for 16 different
> functions. The LED config is not handled at all the the Linux driver
> (PHY LED config is spotty in general).
> 
> I think it makes sense to handle this in board_config_phy() and I can
> do and avoid the issue of direct register access with something like:
> 
> int board_phy_config(struct phy_device *phydev)
> {
>         unsigned short val;
> 
>         /* Fixed PHY: for GW5904/GW5909 this is Marvell 88E6176 GbE Switch */
>         if (phydev->phy_id == 0xa5a55a5a &&
>                 ((board_type == GW5904) || (board_type == GW5909))) {
>                /* get the mdio parent bus so we have direct register access */
>                struct mii_dev *bus = miiphy_get_dev_by_name("mdio");
> 
>                 puts("MV88E61XX ");
> 
>                 /* GPIO[0] output CLK125 for RGMII_REFCLK:
>                  * GPIO[7:0] Direction register is 0x62 of Scratch registers: 1=input 0=output
>                  * GPIO[0] Pin Control register is 0x68 of Scratch registers: 7=CLK125 (Free running 125MHz clock output)
>                  * Scratch register access is at 0x1a of GLOBAL2 register (0x1c)
>                  */
>                 bus->write(bus, 0x1c, 0, 0x1a, (1 << 15) | (0x62 << 8) | 0xfe);
>                 bus->write(bus, 0x1c, 0, 0x1a, (1 << 15) | (0x68 << 8) | 7);
> 
>                 /* Port 0-3 LED configuration: Table 80/82
>                  * LED configuration: 7:4-green (8=Activity)  3:0 amber (8=Link)
>                  *  LED Control is register 0x16 of port registers
>                  *  port registers are at 0x10 + port
>                  */
>                 bus->write(bus, 0x10, 0, 0x16, 0x8088);
>                 bus->write(bus, 0x11, 0, 0x16, 0x8088);
>                 bus->write(bus, 0x12, 0, 0x16, 0x8088);
>                 bus->write(bus, 0x13, 0, 0x16, 0x8088);
> 
>         }
> }
> 
> >
> > > 2. While my switch mdio bus is probing now as well as my fec_dm_mdio
> > > bus I'm not clear how to properly get the struct mii_dev * associated
> > > with the fec_dm_mdio from the dsa switch. Currently I'm using
> > > miiphy_get_dev_by_name("mdio") which is horrible. How do I get the
> > > mii_bus or even the udevice of an dm_mdio bus associated with a dm_eth
> > > device?
> >
> > Hmm, isn't the MDIO bus udevice the dev->parent of the switch?
> 
> My dt is:
> 
> &fec {
>         pinctrl-names = "default";
>         pinctrl-0 = <&pinctrl_enet>;
>         phy-mode = "rgmii-id";
>         status = "okay";
> 
>         fixed-link {
>                 speed = <1000>;
>                 full-duplex;
>         };
> 
>         mdio {
>                 #address-cells = <1>;
>                 #size-cells = <0>;
> 
>                 switch at 0 {
>                         compatible = "marvell,mv88e6085";
>                         reg = <0>;
> 
>                         mdios {
>                                 #address-cells = <1>;
>                                 #size-cells = <0>;
> 
>                                 sw_phy0: ethernet-phy at 0 {
>                                         reg = <0x0>;
>                                 };
> 
>                                 sw_phy1: ethernet-phy at 1 {
>                                         reg = <0x1>;
>                                 };
> 
>                                 sw_phy2: ethernet-phy at 2 {
>                                         reg = <0x2>;
>                                 };
> 
>                                 sw_phy3: ethernet-phy at 3 {
>                                         reg = <0x3>;
>                                 };
>                         };
> 
>                         ports {
>                                 #address-cells = <1>;
>                                 #size-cells = <0>;
> 
>                                 port at 0 {
>                                         reg = <0>;
>                                         label = "lan4";
>                                         phy-mode = "internal";
>                                         phy-handle = <&sw_phy0>;
>                                 };
> 
>                                 port at 1 {
>                                         reg = <1>;
>                                         label = "lan3";
>                                         phy-mode = "internal";
>                                         phy-handle = <&sw_phy1>;
>                                 };
> 
>                                 port at 2 {
>                                         reg = <2>;
>                                         label = "lan2";
>                                         phy-mode = "internal";
>                                         phy-handle = <&sw_phy2>;
>                                 };
> 
>                                 port at 3 {
>                                         reg = <3>;
>                                         label = "lan1";
>                                         phy-mode = "internal";
>                                         phy-handle = <&sw_phy3>;
>                                 };
> 
>                                 port at 5 {
>                                         reg = <5>;
>                                         label = "cpu";
>                                         ethernet = <&fec>;
>                                         phy-mode = "rgmii-id";
> 
>                                         fixed-link {
>                                                 speed = <1000>;
>                                                 full-duplex;
>                                         };
>                                 };
>                         };
>                 };
>         };
> };
> 
> Assuming this looks correct to you the dm_fec_mdio UCLASS_MDIO gets
> bound to the 'mdio' node and its parent is the fec eth device. So in
> my switch probe the parent of the switch is the dm_fec_mdio device but
> I'm not clear how to get to that udevice's strict mii_dev*.

My confusion comes from the fact that I don't really understand why you
want to get to the struct mii_dev. In DM world, that structure would be
at dev_get_uclass_priv(dev->parent)->mii_bus from the perspective of the
DM_DSA driver, but see below.

> > Assuming that the reason you need this is for MDIO input/output towards
> > the switch. Although my suggestion would be to wrap this I/O behind
> > dm_mdio_read(struct udevice *dev /* MDIO child device */) and
> > dm_mdio_write(struct udevice *dev), rather than poking inside struct
> > udevice from the mv88e61xx driver.
> 
> I'm not clear what you mean by the above?

What I meant by the above was that I was assuming you wouldn't attempt
to configure the switch in any way outside of its DM_DSA driver, that is
kind of the point of the device model. Which is also the point of me
pointing out that the MDIO bus udevice is the dev->parent of the DM_DSA
udevice, with no implications whatsoever to what you should do to access
the switch "cleaner" from board/ files.

I know of no clean way to mix DM code with board code, maybe others
reading this could chime in.

For LED control, perhaps you could come up with some "one size fits all"
configuration which is done from the DM_DSA driver rather than the board
files. Although you're probably doing this in the first place so that
you don't have to do it in Linux, case in which...

Generally the stance in the ARM DT world has been that Linux takes care
of initializing the hardware up to the state it needs, assuming a blank
slate, and the bootloader minimally initializes the peripherals it needs
for a successful boot process.

For the 125 MHz clock, "qca,clk-out-frequency" from drivers/net/phy/atheros.c
seems to me like a close enough precedent that's supported in U-Boot as
well. I would propose some DT bindings and certainly make sure to run
them by the Linux community first, even as an RFC, then do the same here
once there is basic agreement.

> My current pass at adding DSA support to the mv88e61xx driver is to
> change the struct phy_device *phydev argument to struct udevice *dev
> throughout (which actually isn't as messy as it seems) and I store the
> struct mii_dev* in the switch priv structure to be used by the low
> level functions needing mii.

That is quite interesting, I would have expected quite the contrary.
Since DSA uses the device model, and your switch is the first driver to
probe on an MDIO bus (=> which also uses the device model, otherwise it
wouldn't probe), the expectation would be to not make use of
struct mii_dev, but rather go through the struct mdio_ops :: read and
write methods of the DM_MDIO udevice (for which I see no existing
wrapper functions, hence the prior reference to dm_mdio_read and
dm_mdio_write as naming suggestions for these wrapper functions).
The lack of these wrappers is probably explained due to the traditional
lack of DM devices sitting on MDIO buses. This is probably why the code
conversion doesn't seem messy to you.


More information about the U-Boot mailing list