[PATCH 1/5] net: Add EMAC ethernet driver found on Allwinner A10 SoC's
Florian Fainelli
florian at openwrt.org
Sun Mar 24 15:03:52 EDT 2013
Hello,
Your phylib implementation looks good now, just some minor comments below:
Le samedi 23 mars 2013 23:30:10, Maxime Ripard a écrit :
> From: Stefan Roese <sr at denx.de>
>
> The Allwinner A10 has an ethernet controller that seem to be developped
> internally by them.
>
> The exact feature set of this controller is unknown, since there is no
> public documentation for this IP, and this driver is mostly the one
> published by Allwinner that has been heavily cleaned up.
>
> Signed-off-by: Stefan Roese <sr at denx.de>
> Signed-off-by: Maxime Ripard <maxime.ripard at free-electrons.com>
> ---
> .../bindings/net/allwinner,sun4i-emac.txt | 19 +
> drivers/net/ethernet/Kconfig | 1 +
> drivers/net/ethernet/Makefile | 1 +
> drivers/net/ethernet/allwinner/Kconfig | 36 +
> drivers/net/ethernet/allwinner/Makefile | 5 +
> drivers/net/ethernet/allwinner/sun4i-emac.c | 1033
> ++++++++++++++++++++ drivers/net/ethernet/allwinner/sun4i-emac.h |
> 114 +++
> 7 files changed, 1209 insertions(+)
> create mode 100644
> Documentation/devicetree/bindings/net/allwinner,sun4i-emac.txt create mode
> 100644 drivers/net/ethernet/allwinner/Kconfig
> create mode 100644 drivers/net/ethernet/allwinner/Makefile
> create mode 100644 drivers/net/ethernet/allwinner/sun4i-emac.c
> create mode 100644 drivers/net/ethernet/allwinner/sun4i-emac.h
>
> diff --git a/Documentation/devicetree/bindings/net/allwinner,sun4i-emac.txt
> b/Documentation/devicetree/bindings/net/allwinner,sun4i-emac.txt new file
> mode 100644
> index 0000000..aaf5013
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/allwinner,sun4i-emac.txt
> @@ -0,0 +1,19 @@
> +* Allwinner EMAC ethernet controller
> +
> +Required properties:
> +- compatible: should be "allwinner,sun4i-emac".
> +- reg: address and length of the register set for the device.
> +- interrupts: interrupt for the device
> +
> +Optional properties:
> +- phy-supply: phandle to a regulator if the PHY needs one
> +- (local-)mac-address: mac address to be used by this driver
> +
> +Example:
> +
> +emac: ethernet at 01c0b000 {
> + compatible = "allwinner,sun4i-emac";
> + reg = <0x01c0b000 0x1000>;
> + phy-supply = <®_emac_3v3>;
> + interrupts = <55>;
Also include a standard PHY device tree binding and use the device tree
helpers to find and connect to your PHY device.
[snip]
> +if NET_VENDOR_ALLWINNER
> +
> +config SUN4I_EMAC
> + tristate "Allwinner A10 EMAC support"
> + depends on ARCH_SUNXI
> + depends on OF
> + select CRC32
> + select NET_CORE
> + select MII
You should select PHYLIB now that you implement it.
[snip]
> +static int emac_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
> +{
> + struct emac_board_info *db = bus->priv;
> + int value;
> +
> + /* issue the phy address and reg */
> + writel((mii_id << 8) | regnum, db->membase + EMAC_MAC_MADR_REG);
> + /* pull up the phy io line */
> + writel(0x1, db->membase + EMAC_MAC_MCMD_REG);
> +
> + /* Wait read complete */
> + while (readl(db->membase + EMAC_MAC_MIND_REG) & 0x1)
> + cpu_relax();
This needs proper timeout handling.
> +
> + /* push down the phy io line */
> + writel(0x0, db->membase + EMAC_MAC_MCMD_REG);
> + /* and read data */
> + value = readl(db->membase + EMAC_MAC_MRDD_REG);
> +
> + return value;
> +}
> +
> +static int emac_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
> + u16 value)
> +{
> + struct emac_board_info *db = bus->priv;
> +
> + /* issue the phy address and reg */
> + writel((mii_id << 8) | regnum, db->membase + EMAC_MAC_MADR_REG);
> + /* pull up the phy io line */
> + writel(0x1, db->membase + EMAC_MAC_MCMD_REG);
> +
> + /* Wait read complete */
> + while (readl(db->membase + EMAC_MAC_MIND_REG) & 0x1)
> + cpu_relax();
Same here.
[snip]
> +static void emac_init_emac(struct net_device *dev);
> +static void emac_handle_link_change(struct net_device *dev)
> +{
> + struct emac_board_info *db = netdev_priv(dev);
> + struct phy_device *phydev = db->phy_dev;
> + unsigned long flags;
> + int status_change = 0;
> +
> + spin_lock_irqsave(&db->lock, flags);
a phylib adjust_link callback is already called with a mutex hold, so your
spinlock could be moved down to emac_init_emac() where the RMW sequence is
performed.
> +
> + if (phydev->link) {
> + if ((db->speed != phydev->speed) ||
> + (db->duplex != phydev->duplex)) {
> + /* Re-init EMAC with new settings */
> + emac_init_emac(dev);
emac_init_emac() (the name could be change BTW) is not just changing the
duplex setting of the MAC but also touches the transmitter/receiver enable
bits, is that what you want as well?
[snip]
> +static int emac_mii_probe(struct net_device *dev)
> +{
> + struct emac_board_info *db = netdev_priv(dev);
> + struct phy_device *phydev;
> + int ret;
> +
> + phydev = phy_find_first(db->mii_bus);
> + if (!phydev) {
> + netdev_err(dev, "no PHY found\n");
> + return -1;
> + }
> +
> + /* to-do: PHY interrupts are currently not supported */
> +
> + /* attach the mac to the phy */
> + ret = phy_connect_direct(dev, phydev, &emac_handle_link_change,
> + db->phy_interface);
> + if (ret) {
> + netdev_err(dev, "Could not attach to PHY\n");
> + return ret;
> + }
You could use of_phy_connect() here to eliminate some boilerplate code.
[snip]
> +unsigned int emac_powerup(struct net_device *ndev)
> +{
> + struct emac_board_info *db = netdev_priv(ndev);
> + unsigned int reg_val;
> +
> + /* initial EMAC */
> + /* flush RX FIFO */
> + reg_val = readl(db->membase + EMAC_RX_CTL_REG);
> + reg_val |= 0x8;
> + writel(reg_val, db->membase + EMAC_RX_CTL_REG);
> + udelay(1);
> +
> + /* initial MAC */
> + /* soft reset MAC */
> + reg_val = readl(db->membase + EMAC_MAC_CTL0_REG);
> + reg_val &= ~EMAC_MAC_CTL0_SOFT_RESET;
> + writel(reg_val, db->membase + EMAC_MAC_CTL0_REG);
> +
> + /* set MII clock */
> + reg_val = readl(db->membase + EMAC_MAC_MCFG_REG);
> + reg_val &= (~(0xf << 2));
> + reg_val |= (0xD << 2);
> + writel(reg_val, db->membase + EMAC_MAC_MCFG_REG);
> +
> + /* clear RX counter */
> + writel(0x0, db->membase + EMAC_RX_FBC_REG);
> +
> + /* disable all interrupt and clear interrupt status */
> + writel(0, db->membase + EMAC_INT_CTL_REG);
> + reg_val = readl(db->membase + EMAC_INT_STA_REG);
> + writel(reg_val, db->membase + EMAC_INT_STA_REG);
> +
> + udelay(1);
> +
> + /* set up EMAC */
> + emac_setup(ndev);
> +
> + /* set mac_address to chip */
> + writel(ndev->dev_addr[0] << 16 | ndev->dev_addr[1] << 8 | ndev->
> + dev_addr[2], db->membase + EMAC_MAC_A1_REG);
> + writel(ndev->dev_addr[3] << 16 | ndev->dev_addr[4] << 8 | ndev->
> + dev_addr[5], db->membase + EMAC_MAC_A0_REG);
> +
> + mdelay(1);
> +
> + return 1;
Either return 0 to be consistent, or do not return anything
[snip]
> +static int emac_start_xmit(struct sk_buff *skb, struct net_device *dev)
> +{
> + struct emac_board_info *db = netdev_priv(dev);
> + unsigned long channel;
> + unsigned long flags;
> +
> + channel = db->tx_fifo_stat & 3;
> + if (channel == 3)
> + return NETDEV_TX_BUSY;
NETDEV_TX_BUSY really is a hard condition, your initial submission had
something like netif_stop_queue(dev) and return 1 and this was actually
better.
[snip]
> +
> +/* Received a packet and pass to upper layer
> + */
> +static void emac_rx(struct net_device *dev)
> +{
> + struct emac_board_info *db = netdev_priv(dev);
> + struct sk_buff *skb;
> + u8 *rdptr;
> + bool good_packet;
> + static int rxlen_last;
> + unsigned int reg_val;
> + u32 rxhdr, rxstatus, rxcount, rxlen;
> +
> + /* Check packet ready or not */
> + while (1) {
> + /* race warning: the first packet might arrive with
> + * the interrupts disabled, but the second will fix
> + * it
> + */
> + rxcount = readl(db->membase + EMAC_RX_FBC_REG);
> +
> + if (netif_msg_rx_status(db))
> + dev_dbg(db->dev, "RXCount: %x\n", rxcount);
> +
> + if ((db->skb_last != NULL) && (rxlen_last > 0)) {
> + dev->stats.rx_bytes += rxlen_last;
> +
> + /* Pass to upper layer */
> + db->skb_last->protocol = eth_type_trans(db->skb_last,
> + dev);
> + netif_rx(db->skb_last);
> + dev->stats.rx_packets++;
> + db->skb_last = NULL;
> + rxlen_last = 0;
> +
> + reg_val = readl(db->membase + EMAC_RX_CTL_REG);
> + reg_val &= ~EMAC_RX_CTL_DMA_EN;
> + writel(reg_val, db->membase + EMAC_RX_CTL_REG);
> + }
> +
> + if (!rxcount) {
> + db->emacrx_completed_flag = 1;
> + reg_val = readl(db->membase + EMAC_INT_CTL_REG);
> + reg_val |= (0xf << 0) | (0x01 << 8);
> + writel(reg_val, db->membase + EMAC_INT_CTL_REG);
> +
> + /* had one stuck? */
> + rxcount = readl(db->membase + EMAC_RX_FBC_REG);
> + if (!rxcount)
> + return;
> + }
> +
> + reg_val = readl(db->membase + EMAC_RX_IO_DATA_REG);
> + if (netif_msg_rx_status(db))
> + dev_dbg(db->dev, "receive header: %x\n", reg_val);
> + if (reg_val != EMAC_UNDOCUMENTED_MAGIC) {
> + /* disable RX */
> + reg_val = readl(db->membase + EMAC_CTL_REG);
> + writel(reg_val & ~EMAC_CTL_RX_EN,
> + db->membase + EMAC_CTL_REG);
> +
> + /* Flush RX FIFO */
> + reg_val = readl(db->membase + EMAC_RX_CTL_REG);
> + writel(reg_val | (1 << 3),
> + db->membase + EMAC_RX_CTL_REG);
> +
> + do {
> + reg_val = readl(db->membase + EMAC_RX_CTL_REG);
> + } while (reg_val & (1 << 3));
> +
> + /* enable RX */
> + reg_val = readl(db->membase + EMAC_CTL_REG);
> + writel(reg_val | EMAC_CTL_RX_EN,
> + db->membase + EMAC_CTL_REG);
> + reg_val = readl(db->membase + EMAC_INT_CTL_REG);
> + reg_val |= (0xf << 0) | (0x01 << 8);
> + writel(reg_val, db->membase + EMAC_INT_CTL_REG);
> +
> + db->emacrx_completed_flag = 1;
> +
> + return;
> + }
> +
> + /* A packet ready now & Get status/length */
> + good_packet = true;
> +
> + emac_inblk_32bit(db->membase + EMAC_RX_IO_DATA_REG,
> + &rxhdr, sizeof(rxhdr));
> +
> + if (netif_msg_rx_status(db))
> + dev_dbg(db->dev, "rxhdr: %x\n", *((int *)(&rxhdr)));
> +
> + rxlen = EMAC_RX_IO_DATA_LEN(rxhdr);
> + rxstatus = EMAC_RX_IO_DATA_STATUS(rxhdr);
> +
> + if (netif_msg_rx_status(db))
> + dev_dbg(db->dev, "RX: status %02x, length %04x\n",
> + rxstatus, rxlen);
> +
> + /* Packet Status check */
> + if (rxlen < 0x40) {
> + good_packet = false;
> + if (netif_msg_rx_err(db))
> + dev_dbg(db->dev, "RX: Bad Packet (runt)\n");
> + }
> +
> + if (unlikely(!(rxstatus & EMAC_RX_IO_DATA_STATUS_OK))) {
> + good_packet = false;
> +
> + if (rxstatus & EMAC_RX_IO_DATA_STATUS_CRC_ERR) {
> + if (netif_msg_rx_err(db))
> + dev_dbg(db->dev, "crc error\n");
> + dev->stats.rx_crc_errors++;
> + }
> +
> + if (rxstatus & EMAC_RX_IO_DATA_STATUS_LEN_ERR) {
> + if (netif_msg_rx_err(db))
> + dev_dbg(db->dev, "length error\n");
> + dev->stats.rx_length_errors++;
> + }
> + }
> +
> + /* Move data from EMAC */
> + skb = dev_alloc_skb(rxlen + 4);
> + if (good_packet && skb) {
> + skb_reserve(skb, 2);
> + rdptr = (u8 *) skb_put(skb, rxlen - 4);
> +
> + /* Read received packet from RX SRAM */
> + if (netif_msg_rx_status(db))
> + dev_dbg(db->dev, "RxLen %x\n", rxlen);
> +
> + emac_inblk_32bit(db->membase + EMAC_RX_IO_DATA_REG,
> + rdptr, rxlen);
> + dev->stats.rx_bytes += rxlen;
> +
> + /* Pass to upper layer */
> + skb->protocol = eth_type_trans(skb, dev);
> + netif_rx(skb);
> + dev->stats.rx_packets++;
> + }
> + }
> +}
> +
> +static irqreturn_t emac_interrupt(int irq, void *dev_id)
> +{
You should implement NAPI, you do not have much to change to actually be able
to do it.
> + netif_stop_queue(ndev);
> + netif_carrier_off(ndev);
phy_stop() is missing here.
--
Florian
More information about the linux-arm-kernel
mailing list