Re: [PATCH v3 3/3] net: ethernet: add ag71xx driver

From: Oleksij Rempel
Date: Sun May 19 2019 - 13:37:46 EST


Hi Andrew,

thank you for the review!

On Mon, Apr 22, 2019 at 03:25:33PM +0200, Andrew Lunn wrote:
> On Mon, Apr 22, 2019 at 08:40:46AM +0200, Oleksij Rempel wrote:
> > +static int ag71xx_msg_enable = -1;
> > +
> > +module_param_named(msg_enable, ag71xx_msg_enable, uint,
> > + (S_IRUSR|S_IRGRP|S_IROTH));
> > +MODULE_PARM_DESC(msg_enable, "Debug message level (see linux/netdevice.h for bitmap)");
>
> Hi Oleksij
>
> Module parameters are generally not liked.
>
> Please use .set_msglevel.

Ok, I remove it for now. ethtool ops are currently not supported in this
driver.

> > +static int ag71xx_mdio_mii_read(struct mii_bus *bus, int addr, int reg)
> > +{
> > + struct ag71xx *ag = bus->priv;
> > + struct net_device *ndev = ag->ndev;
> > + int err;
> > + int ret;
> > +
> > + err = ag71xx_mdio_wait_busy(ag);
> > + if (err)
> > + return 0xffff;
> > +
> > + ag71xx_wr(ag, AG71XX_REG_MII_CMD, MII_CMD_WRITE);
>
> It would be good to comment why you need this. Or is it a copy/paste
> error?

copy/paste. fixed.

>
> > + ag71xx_wr(ag, AG71XX_REG_MII_ADDR,
> > + ((addr & 0xff) << MII_ADDR_SHIFT) | (reg & 0xff));
> > + ag71xx_wr(ag, AG71XX_REG_MII_CMD, MII_CMD_READ);
> > +
> > + err = ag71xx_mdio_wait_busy(ag);
> > + if (err)
> > + return 0xffff;
> > +
> > + ret = ag71xx_rr(ag, AG71XX_REG_MII_STATUS);
> > + ret &= 0xffff;
> > + ag71xx_wr(ag, AG71XX_REG_MII_CMD, MII_CMD_WRITE);
>
> This one as well.

done

> > +
> > + netif_dbg(ag, link, ndev, "mii_read: addr=%04x, reg=%04x, value=%04x\n",
> > + addr, reg, ret);
> > +
> > + return ret;
> > +}
> > +
> > +static int ag71xx_mdio_mii_write(struct mii_bus *bus, int addr, int reg,
> > + u16 val)
> > +{
> > + struct ag71xx *ag = bus->priv;
> > + struct net_device *ndev = ag->ndev;
> > +
> > + netif_dbg(ag, link, ndev, "mii_write: addr=%04x, reg=%04x, value=%04x\n",
> > + addr, reg, val);
> > +
> > + ag71xx_wr(ag, AG71XX_REG_MII_ADDR,
> > + ((addr & 0xff) << MII_ADDR_SHIFT) | (reg & 0xff));
> > + ag71xx_wr(ag, AG71XX_REG_MII_CTRL, val);
> > +
> > + ag71xx_mdio_wait_busy(ag);
> > +
> > + return 0;
>
> Return the -ETIMEOUT from ag71xx_mdio_wait_busy() please.

done

> > +static int ag71xx_mdio_get_divider(struct ag71xx *ag, u32 *div)
> > +{
> > + struct device *dev = &ag->pdev->dev;
> > + struct device_node *np = dev->of_node;
> > + struct clk *ref_clk = of_clk_get(np, 0);
> > + unsigned long ref_clock;
> > + const u32 *table;
> > + int ndivs, i;
> > +
> > + if (IS_ERR(ref_clk))
> > + return -EINVAL;
> > +
> > + ref_clock = clk_get_rate(ref_clk);
>
> I _think_ you need to prepare and enable the clock before you can use
> clk_get_rate().

This WiSoC has no advanced clk infrastructure. Almost every thing is
connected to AHB clock and can't be enabled or disabled. Any way, I added
proper clk registration in case there are more modern variants..

>
> > + clk_put(ref_clk);
> > +
> > + if (ag71xx_is(ag, AR9330) || ag71xx_is(ag, AR9340)) {
> > + table = ar933x_mdio_div_table;
> > + ndivs = ARRAY_SIZE(ar933x_mdio_div_table);
> > + } else if (ag71xx_is(ag, AR7240)) {
> > + table = ar7240_mdio_div_table;
> > + ndivs = ARRAY_SIZE(ar7240_mdio_div_table);
> > + } else {
> > + table = ar71xx_mdio_div_table;
> > + ndivs = ARRAY_SIZE(ar71xx_mdio_div_table);
> > + }
> > +
> > + for (i = 0; i < ndivs; i++) {
> > + unsigned long t;
> > +
> > + t = ref_clock / table[i];
> > + if (t <= AG71XX_MDIO_MAX_CLK) {
> > + *div = i;
> > + return 0;
> > + }
> > + }
> > +
> > + return -ENOENT;
> > +}
> > +
> > +static int ag71xx_mdio_reset(struct mii_bus *bus)
> > +{
> > + struct ag71xx *ag = bus->priv;
> > + u32 t;
> > +
> > + if (ag71xx_mdio_get_divider(ag, &t)) {
> > + if (ag71xx_is(ag, AR9340))
> > + t = MII_CFG_CLK_DIV_58;
> > + else
> > + t = MII_CFG_CLK_DIV_10;
> > + }
>
> You should return the -ENOENT from ag71xx_mdio_get_divider().

done.

>
> > +
> > + ag71xx_wr(ag, AG71XX_REG_MII_CFG, t | MII_CFG_RESET);
> > + udelay(100);
> > +
> > + ag71xx_wr(ag, AG71XX_REG_MII_CFG, t);
> > + udelay(100);
> > +
> > + return 0;
> > +}
> > +
> > +static int ag71xx_mdio_probe(struct ag71xx *ag)
> > +{
> > + static struct mii_bus *mii_bus;
> > + struct device *dev = &ag->pdev->dev;
> > + struct device_node *np = dev->of_node;
> > + int err;
> > +
> > + ag->mii_bus = NULL;
> > +
> > + /*
> > + * On most (all?) Atheros/QCA SoCs dual eth interfaces are not equal.
> > + *
> > + * That is to say eth0 can not work independently. It only works
> > + * when eth1 is working.
> > + */
>
> Please could you explain that some more? Is there just one MDIO bus
> shared by two ethernet controllers? If so, it would be better to have
> the MDIO bus controller as a separate driver.

hm... I compared different Atheros/QCA docs and noticed that it is a
wrong statement. All of them have MDIO for each ETH. In case of
AR9331 MDIO0 is not connected to the internal switch/phy. Not sure if it
is connected to any thing at all.

I'll remove this quirk.

> > + if ((ag->dcfg->quirks & AG71XX_ETH0_NO_MDIO) && !ag->mac_idx)
> > + return 0;
> > +
> > + mii_bus = devm_mdiobus_alloc(dev);
> > + if (!mii_bus)
> > + return -ENOMEM;
> > +
> > + ag->mdio_reset = of_reset_control_get_exclusive(np, "mdio");
>
> Can this return -EPROBE_DEFFER? If so, you should return it.

done

> > +
> > + mii_bus->name = "ag71xx_mdio";
> > + mii_bus->read = ag71xx_mdio_mii_read;
> > + mii_bus->write = ag71xx_mdio_mii_write;
> > + mii_bus->reset = ag71xx_mdio_reset;
> > + mii_bus->priv = ag;
> > + mii_bus->parent = dev;
> > + snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s.%d", np->name, ag->mac_idx);
> > +
> > + if (!IS_ERR(ag->mdio_reset)) {
> > + reset_control_assert(ag->mdio_reset);
> > + msleep(100);
> > + reset_control_deassert(ag->mdio_reset);
> > + msleep(200);
> > + }
> > +
> > + err = of_mdiobus_register(mii_bus, np);
> > + if (err)
> > + return err;
> > +
> > + ag->mii_bus = mii_bus;
> > +
> > + return 0;
> > +}
>
>
> > +static void ag71xx_dma_reset(struct ag71xx *ag)
> > +{
> > + u32 val;
> > + int i;
> > +
> > +
> > + /* stop RX and TX */
> > + ag71xx_wr(ag, AG71XX_REG_RX_CTRL, 0);
> > + ag71xx_wr(ag, AG71XX_REG_TX_CTRL, 0);
> > +
> > + /*
> > + * give the hardware some time to really stop all rx/tx activity
> > + * clearing the descriptors too early causes random memory corruption
> > + */
> > + mdelay(1);
>
> This does not sounds too safe. Can you walk the descriptor rings to
> know it has finished?

good point. done.

> > +static unsigned char *ag71xx_speed_str(struct ag71xx *ag)
> > +{
> > + switch (ag->speed) {
> > + case SPEED_1000:
> > + return "1000";
> > + case SPEED_100:
> > + return "100";
> > + case SPEED_10:
> > + return "10";
> > + }
> > +
> > + return "?";
> > +}
>
> phy_speed_to_str()

done

> > +
> > +static void ag71xx_link_adjust(struct ag71xx *ag, bool update)
> > +{
> > + struct net_device *ndev = ag->ndev;
> > + u32 cfg2;
> > + u32 ifctl;
> > + u32 fifo5;
> > +
> > + if (!ag->link && update) {
> > + ag71xx_hw_stop(ag);
> > + netif_carrier_off(ag->ndev);
> > + netif_info(ag, link, ndev, "link down\n");
> > + return;
> > + }
> > +
> > + if (!ag71xx_is(ag, AR7100) && !ag71xx_is(ag, AR9130))
> > + ag71xx_fast_reset(ag);
> > +
> > + cfg2 = ag71xx_rr(ag, AG71XX_REG_MAC_CFG2);
> > + cfg2 &= ~(MAC_CFG2_IF_1000 | MAC_CFG2_IF_10_100 | MAC_CFG2_FDX);
> > + cfg2 |= (ag->duplex) ? MAC_CFG2_FDX : 0;
> > +
> > + ifctl = ag71xx_rr(ag, AG71XX_REG_MAC_IFCTL);
> > + ifctl &= ~(MAC_IFCTL_SPEED);
> > +
> > + fifo5 = ag71xx_rr(ag, AG71XX_REG_FIFO_CFG5);
> > + fifo5 &= ~FIFO_CFG5_BM;
> > +
> > + switch (ag->speed) {
> > + case SPEED_1000:
> > + cfg2 |= MAC_CFG2_IF_1000;
> > + fifo5 |= FIFO_CFG5_BM;
> > + break;
> > + case SPEED_100:
> > + cfg2 |= MAC_CFG2_IF_10_100;
> > + ifctl |= MAC_IFCTL_SPEED;
> > + break;
> > + case SPEED_10:
> > + cfg2 |= MAC_CFG2_IF_10_100;
> > + break;
> > + default:
> > + BUG();
>
> Please don't use BUG(). That kills the machine. WARN().

done

> > + return;
> > + }
> > +
> > + if (ag->tx_ring.desc_split) {
> > + ag->fifodata[2] &= 0xffff;
> > + ag->fifodata[2] |= ((2048 - ag->tx_ring.desc_split) / 4) << 16;
> > + }
> > +
> > + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG3, ag->fifodata[2]);
> > +
> > + ag71xx_wr(ag, AG71XX_REG_MAC_CFG2, cfg2);
> > + ag71xx_wr(ag, AG71XX_REG_FIFO_CFG5, fifo5);
> > + ag71xx_wr(ag, AG71XX_REG_MAC_IFCTL, ifctl);
> > +
> > + ag71xx_hw_start(ag);
> > +
> > + netif_carrier_on(ag->ndev);
>
> You should not need to do anything with the carrier. phylib will do
> all that for you.

done

> > + if (update)
> > + netif_info(ag, link, ndev, "link up (%sMbps/%s duplex)\n",
> > + ag71xx_speed_str(ag),
> > + (DUPLEX_FULL == ag->duplex) ? "Full" : "Half");
>
> phy_print_status() is the standard way to do this.

done

> > +}
> > +
> > +static void ag71xx_phy_link_adjust(struct net_device *ndev)
> > +{
> > + struct ag71xx *ag = netdev_priv(ndev);
> > + struct phy_device *phydev = ag->phy_dev;
> > + unsigned long flags;
> > + int status_change = 0;
> > +
> > + spin_lock_irqsave(&ag->lock, flags);
> > +
> > + if (phydev->link) {
> > + if (ag->duplex != phydev->duplex
> > + || ag->speed != phydev->speed) {
> > + status_change = 1;
> > + }
> > + }
> > +
> > + if (phydev->link != ag->link)
> > + status_change = 1;
> > +
> > + ag->link = phydev->link;
> > + ag->duplex = phydev->duplex;
> > + ag->speed = phydev->speed;
>
> It appears you always have some sort of PHY attached, either a real
> PHY, or a fixed link. So you can probably simply this, remove
> ap->link, ap->dupex, ap->speed, and just get it from phydev when you
> need it.

done

> > +
> > + if (status_change)
> > + ag71xx_link_adjust(ag, true);
> > +
> > + spin_unlock_irqrestore(&ag->lock, flags);
>
> You are doing a lot of stuff while holding this spinlock. What exactly
> are you protecting?

can't identify it. seems to be artifact from old driver.

> > +}
> > +
> > +static int ag71xx_phy_connect(struct ag71xx *ag)
> > +{
> > + struct device_node *np = ag->pdev->dev.of_node;
> > + struct net_device *ndev = ag->ndev;
> > + struct device_node *phy_node;
> > + int ret;
> > +
> > + if (of_phy_is_fixed_link(np)) {
> > + ret = of_phy_register_fixed_link(np);
> > + if (ret < 0) {
> > + netif_err(ag, probe, ndev, "Failed to register fixed PHY link: %d\n",
> > + ret);
> > + return ret;
> > + }
> > +
> > + phy_node = of_node_get(np);
> > + } else {
> > + phy_node = of_parse_phandle(np, "phy-handle", 0);
> > + }
> > +
> > + if (!phy_node) {
> > + netif_err(ag, probe, ndev, "Could not find valid phy node\n");
> > + return -ENODEV;
> > + }
> > +
> > + ag->phy_dev = of_phy_connect(ag->ndev, phy_node, ag71xx_phy_link_adjust,
> > + 0, ag->phy_if_mode);
>
> ndev->phydev. No need to place it in the private structure.

done

> > +
> > + of_node_put(phy_node);
> > +
> > + if (!ag->phy_dev) {
> > + netif_err(ag, probe, ndev, "Could not connect to PHY device\n");
> > + return -ENODEV;
> > + }
> > +
> > + phy_attached_info(ag->phy_dev);
> > +
> > + return 0;
> > +}
> > +
> > +static int ag71xx_open(struct net_device *ndev)
> > +{
> > + struct ag71xx *ag = netdev_priv(ndev);
> > + unsigned int max_frame_len;
> > + int ret;
> > +
> > + netif_carrier_off(ndev);
> > + max_frame_len = ag71xx_max_frame_len(ndev->mtu);
> > + ag->rx_buf_size = SKB_DATA_ALIGN(max_frame_len + NET_SKB_PAD + NET_IP_ALIGN);
> > +
> > + /* setup max frame length */
> > + ag71xx_wr(ag, AG71XX_REG_MAC_MFL, max_frame_len);
> > + ag71xx_hw_set_macaddr(ag, ndev->dev_addr);
> > +
> > + ret = ag71xx_hw_enable(ag);
> > + if (ret)
> > + goto err;
> > +
> > + ret = ag71xx_phy_connect(ag);
> > + if (ret)
> > + goto err;
> > +
> > + phy_start(ag->phy_dev);
> > +
> > + return 0;
> > +
> > +err:
> > + ag71xx_rings_cleanup(ag);
> > + return ret;
> > +}
> > +
> > +static int ag71xx_stop(struct net_device *ndev)
> > +{
> > + struct ag71xx *ag = netdev_priv(ndev);
> > +
> > + phy_stop(ag->phy_dev);
> > + ag71xx_hw_disable(ag);
> > +
>
> open() does the phy_connect, so close should do the phy_disconnect.

done

> > + return 0;
> > +}
> > +
> > +static int ag71xx_do_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd)
> > +{
> > + struct ag71xx *ag = netdev_priv(ndev);
> > +
> > + switch (cmd) {
> > + case SIOCSIFHWADDR:
> > + if (copy_from_user
> > + (ndev->dev_addr, ifr->ifr_data, sizeof(ndev->dev_addr)))
> > + return -EFAULT;
> > + return 0;
> > +
> > + case SIOCGIFHWADDR:
> > + if (copy_to_user
> > + (ifr->ifr_data, ndev->dev_addr, sizeof(ndev->dev_addr)))
> > + return -EFAULT;
> > + return 0;
>
> The core code should handle this for you, dev_ifsioc_locked().

done

> > +
> > + case SIOCGMIIPHY:
> > + case SIOCGMIIREG:
> > + case SIOCSMIIREG:
> > + if (ag->phy_dev == NULL)
> > + break;
>
> It is more normal to just do
>
> if (ndev->phydev)
> return phy_mii_ioctl(ndev->phydev, req, cmd);
>
> Out side of the switch statement.

done

> > +
> > + return phy_mii_ioctl(ag->phy_dev, ifr, cmd);
> > +
> > + default:
> > + break;
> > + }
> > +
> > + return -EOPNOTSUPP;
> > +}
> > +
>
> Andrew

thx!



--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |