All of lore.kernel.org
 help / color / mirror / Atom feed
From: Lubomir Rintel <lkundrak@v3.sk>
To: u-boot@lists.denx.de
Subject: [PATCH RFC 16/20] net: Add Ingenic JZ4730 Ethernet driver
Date: Tue, 17 Nov 2020 22:00:14 +0100	[thread overview]
Message-ID: <20201117210018.751469-17-lkundrak@v3.sk> (raw)
In-Reply-To: <20201117210018.751469-1-lkundrak@v3.sk>

This adds support for Ethernet MAC block on Ingenic JZ4730 SoC.

Based on old Ingenic GPL code dump, but significantly cleaned up and
reworked (e.g. to plug into the MAC framework).

Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
---
 drivers/net/Kconfig      |  11 +
 drivers/net/Makefile     |   1 +
 drivers/net/jz4730_eth.c | 447 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 459 insertions(+)
 create mode 100644 drivers/net/jz4730_eth.c

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 3a5e0368805..41dd5951981 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -342,6 +342,17 @@ config FSLDMAFEC
 	  This driver supports the network interface units in the
 	  ColdFire family.
 
+
+config JZ4730_ETH
+        bool "Ingenic JZ4730 Ethernet Support"
+	depends on DM_ETH && SOC_JZ4730
+	select PHYLIB
+	select MII
+	default y
+	help
+	  This driver supports the network interface units in the
+	  Ingenic JZ4730 SoC.
+
 config KS8851_MLL
 	bool "Microchip KS8851-MLL controller driver"
 	help
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index e3bdda359dc..67297066bc6 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_FTGMAC100) += ftgmac100.o
 obj-$(CONFIG_FTMAC110) += ftmac110.o
 obj-$(CONFIG_FTMAC100) += ftmac100.o
 obj-$(CONFIG_GMAC_ROCKCHIP) += gmac_rockchip.o
+obj-$(CONFIG_JZ4730_ETH) += jz4730_eth.o
 obj-$(CONFIG_KS8851_MLL) += ks8851_mll.o
 obj-$(CONFIG_LAN91C96) += lan91c96.o
 obj-$(CONFIG_LPC32XX_ETH) += lpc32xx_eth.o
diff --git a/drivers/net/jz4730_eth.c b/drivers/net/jz4730_eth.c
new file mode 100644
index 00000000000..99b9b68a5a0
--- /dev/null
+++ b/drivers/net/jz4730_eth.c
@@ -0,0 +1,447 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * JZ4730 Ethernet MAC driver.
+ *
+ * Copyright (C) 2005 Ingenic Semiconductor <jlwei@ingenic.cn>
+ * Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <malloc.h>
+#include <net.h>
+#include <phy.h>
+#include <eth_phy.h>
+#include <miiphy.h>
+#include <command.h>
+#include <cpu_func.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+
+#define ETH_BMR		0x1000
+#define ETH_TPDR	0x1004
+#define ETH_RAR		0x100C
+#define ETH_TAR		0x1010
+#define ETH_SR		0x1014
+#define ETH_OMR		0x1018
+#define ETH_IER		0x101C
+#define ETH_MFCR	0x1020
+#define ETH_MCR		0x0000
+#define ETH_MAHR	0x0004
+#define ETH_MALR	0x0008
+#define ETH_HTHR	0x000C
+#define ETH_HTLR	0x0010
+#define ETH_MIAR	0x0014
+#define ETH_MIDR	0x0018
+
+/* Bus Mode Register (DMA_BMR) */
+#define BMR_SWR		0x00000001	/* Software Reset */
+
+#define DMA_BURST	4
+
+/* Operation Mode Register (DMA_OMR) */
+#define OMR_TTM		0x00400000	/* Transmit Threshold Mode */
+#define OMR_SF		0x00200000	/* Store and Forward */
+#define OMR_ST		0x00002000	/* Start/Stop Transmission Command */
+#define OMR_SR		0x00000002	/* Start/Stop Receive */
+
+/* Mac control	Register (MAC_MCR) */
+#define MCR_FDX		0x00100000	/* Full Duplex Mode */
+#define MCR_LCC		0x00001000	/* Late Collision control */
+#define MCR_DBF		0x00000800	/* Broadcast frame Disable */
+#define MCR_TE		0x00000008	/* Transmitter enable */
+#define MCR_RE		0x00000004	/* Receiver enable */
+
+/* Constants for the intr mask and intr status registers. (DMA_SIS and DMA_IER) */
+#define DMA_INT_FB	0x00002000	/* Fatal bus error */
+#define DMA_INT_RW	0x00000200	/* Receive watchdog timeout */
+#define DMA_INT_RS	0x00000100	/* Receive stop */
+#define DMA_INT_RU	0x00000080	/* Receive buffer unavailble */
+#define DMA_INT_RI	0x00000040	/* Receive interrupt */
+#define DMA_INT_TJ	0x00000008	/* Transmit jabber timeout */
+#define DMA_INT_TU	0x00000004	/* Transmit buffer unavailble */
+#define DMA_INT_TS	0x00000002	/* Transmit stop */
+#define DMA_INT_TI	0x00000001	/* Transmit interrupt */
+
+/* Receive Descriptor Bit Summary */
+#define R_OWN		0x80000000	/* Own Bit */
+#define RD_FL		0x3fff0000	/* Frame Length */
+#define RD_ES		0x00008000	/* Error Summary */
+
+#define RD_RER		0x02000000	/* Receive End Of Ring */
+#define RD_RCH		0x01000000	/* Second Address Chained */
+
+/* Transmit Descriptor Bit Summary */
+#define T_OWN		0x80000000	/* Own Bit */
+
+#define TD_LS		0x40000000	/* Last Segment */
+#define TD_FS		0x20000000	/* First Segment */
+#define TD_TER		0x02000000	/* Transmit End Of Ring */
+#define TD_TCH		0x01000000	/* Second Address Chained */
+
+#define MAX_WAIT 1000
+
+/* Tx and Rx Descriptor */
+struct jz4730_eth_desc {
+	u32 status;
+	u32 ctrl;
+	u32 addr;
+	u32 next;
+};
+
+#define NUM_TX_DESCS 4
+
+struct jz4730_eth_priv {
+	void __iomem *base;
+	struct mii_dev *bus;
+	struct phy_device *phy;
+
+	int next_rx;
+	int next_tx;
+
+	struct jz4730_eth_desc rx_desc[PKTBUFSRX];
+	struct jz4730_eth_desc tx_desc[NUM_TX_DESCS];
+};
+
+#define MII_CMD_ADDR(id, offset) (((id) << 11) | ((offset) << 6))
+#define MII_CMD_READ(id, offset) (MII_CMD_ADDR(id, offset))
+#define MII_CMD_WRITE(id, offset) (MII_CMD_ADDR(id, offset) | 0x2)
+
+static int jz4730_eth_mdio_read(struct mii_dev *bus, int addr,
+				int devad, int reg)
+{
+	struct jz4730_eth_priv *priv = bus->priv;
+	u32 mii_cmd = MII_CMD_READ(addr, reg);
+	int i;
+
+	writel(mii_cmd, priv->base + ETH_MIAR);
+
+	/* wait for completion */
+	for (i = 0; i < MAX_WAIT; i++) {
+		if (!(readl(priv->base + ETH_MIAR) & 0x1))
+			break;
+		udelay(1);
+	}
+
+	if (i == MAX_WAIT) {
+		printf("MII wait timeout\n");
+		return -EIO;
+	}
+
+	return readl(priv->base + ETH_MIDR) & 0x0000ffff;
+}
+
+static int jz4730_eth_mdio_write(struct mii_dev *bus, int addr,
+				 int devad, int reg, u16 value)
+{
+	struct jz4730_eth_priv *priv = bus->priv;
+	u32 mii_cmd = MII_CMD_WRITE(addr, reg);
+	int i;
+
+	writel(value, priv->base + ETH_MIDR);
+	writel(mii_cmd, priv->base + ETH_MIAR);
+
+	/* wait for completion */
+	for (i = 0; i < MAX_WAIT; i++) {
+		if (!(readl(priv->base + ETH_MIAR) & 0x1))
+			break;
+		udelay(1);
+	}
+
+	if (i == MAX_WAIT) {
+		printf("MII wait timeout\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int jz4730_eth_probe(struct udevice *dev)
+{
+	struct eth_pdata *pdata = dev_get_platdata(dev);
+	struct jz4730_eth_priv *priv = dev_get_priv(dev);
+	int addr = eth_phy_get_addr(dev);
+	int ret;
+
+	priv->base = dev_remap_addr(dev);
+	if (!priv->base)
+		return -EINVAL;
+	pdata->iobase = (phys_addr_t)priv->base;
+
+	priv->bus = mdio_alloc();
+	if (!priv->bus)
+		return -ENOMEM;
+
+	priv->bus->read = jz4730_eth_mdio_read;
+	priv->bus->write = jz4730_eth_mdio_write;
+	priv->bus->priv = priv;
+	strcpy(priv->bus->name, "jz4730_eth");
+
+	ret = mdio_register(priv->bus);
+	if (ret)
+		goto free_mdio;
+
+	priv->phy = phy_connect(priv->bus, addr, dev, PHY_INTERFACE_MODE_MII);
+	if (!priv->phy) {
+		ret = -EIO;
+		goto unregister_mdio;
+	}
+
+	phy_config(priv->phy);
+
+	return 0;
+
+unregister_mdio:
+	mdio_unregister(priv->bus);
+free_mdio:
+	mdio_free(priv->bus);
+	return ret;
+}
+
+static int jz4730_eth_remove(struct udevice *dev)
+{
+	struct jz4730_eth_priv *priv = dev_get_priv(dev);
+
+	phy_shutdown(priv->phy);
+	free(priv->phy);
+	mdio_unregister(priv->bus);
+	mdio_free(priv->bus);
+
+	return 0;
+}
+
+static void config_mac(struct udevice *dev)
+{
+	struct eth_pdata *pdata = dev_get_platdata(dev);
+	struct jz4730_eth_priv *priv = dev_get_priv(dev);
+	u32 omr, mcr;
+
+	/* Set MAC address */
+#define ea pdata->enetaddr
+	writel((ea[3] << 24) | (ea[2] << 16) | (ea[1] << 8) | ea[0], priv->base + ETH_MALR);
+	writel((ea[5] << 8) | ea[4], priv->base + ETH_MAHR);
+
+	writel(0, priv->base + ETH_HTLR);
+	writel(0, priv->base + ETH_HTHR);
+
+	/* Assert the MCR_PS bit in CSR */
+	if (priv->phy->speed == SPEED_100)
+		omr = OMR_SF;
+	else
+		omr = OMR_TTM | OMR_SF;
+
+	mcr = MCR_TE | MCR_RE | MCR_DBF | MCR_LCC;
+
+	if (priv->phy->duplex == DUPLEX_FULL)
+		mcr |= MCR_FDX;
+
+	/* Set the Operation Mode (OMR) and Mac Control (MCR) registers */
+	writel(omr, priv->base + ETH_OMR);
+	writel(mcr, priv->base + ETH_MCR);
+
+	/* Set the Programmable Burst Length (BMR.PBL, value 1 or 4 is validate) */
+	writel(DMA_BURST << 8, priv->base + ETH_BMR);
+
+	/* Reset csr8 */
+	readl(priv->base + ETH_MFCR); /* missed frams counter */
+}
+
+static int jz4730_eth_start(struct udevice *dev)
+{
+	struct jz4730_eth_priv *priv = dev_get_priv(dev);
+	int i;
+
+	/* Reset ethernet unit */
+	writel(readl(priv->base + ETH_BMR) | BMR_SWR, priv->base + ETH_BMR);
+
+	for (i = 0; i < MAX_WAIT; i++) {
+		if (!(readl(priv->base + ETH_BMR) & BMR_SWR))
+			break;
+		udelay(1);
+	}
+
+	if (i == MAX_WAIT) {
+		printf("Reset eth timeout\n");
+		return -EIO;
+	}
+
+	/* Disable interrupts: we don't use ethernet interrupts */
+	writel(0, priv->base + ETH_IER);
+
+	/* Configure PHY */
+	phy_startup(priv->phy);
+
+	/* Configure MAC */
+	config_mac(dev);
+
+	/* Setup the Rx&Tx descriptors */
+	for (i = 0; i < PKTBUFSRX; i++) {
+		priv->rx_desc[i].status = R_OWN;
+		priv->rx_desc[i].ctrl = PKTSIZE_ALIGN | RD_RCH;
+		priv->rx_desc[i].addr = virt_to_phys(net_rx_packets[i]);
+		priv->rx_desc[i].next = virt_to_phys(priv->rx_desc + i + 1);
+
+		flush_dcache_range((ulong)net_rx_packets[i],
+				   (ulong)net_rx_packets[i] + PKTSIZE_ALIGN);
+	}
+	priv->rx_desc[PKTBUFSRX - 1].next = virt_to_phys(priv->rx_desc);
+	priv->rx_desc[PKTBUFSRX - 1].ctrl |= RD_RER;
+
+	flush_dcache_range((ulong)priv->rx_desc,
+			   (ulong)priv->rx_desc + sizeof(priv->rx_desc));
+
+	for (i = 0; i < NUM_TX_DESCS; i++) {
+		priv->tx_desc[i].status = 0;
+		priv->tx_desc[i].ctrl  = TD_TCH;
+		priv->tx_desc[i].addr = 0;
+		priv->tx_desc[i].next = virt_to_phys(priv->tx_desc + i + 1);
+	}
+	priv->tx_desc[NUM_TX_DESCS - 1].next = virt_to_phys(priv->tx_desc);
+	priv->tx_desc[NUM_TX_DESCS - 1].ctrl |= TD_TER;
+
+	flush_dcache_range((ulong)priv->tx_desc,
+			   (ulong)priv->tx_desc + sizeof(priv->tx_desc));
+
+	writel(virt_to_phys(priv->rx_desc), priv->base + ETH_RAR);
+	writel(virt_to_phys(priv->tx_desc), priv->base + ETH_TAR);
+
+	priv->next_rx = 0;
+	priv->next_tx = 0;
+
+	/* Enable ETH */
+	writel(readl(priv->base + ETH_OMR) | OMR_ST | OMR_SR, priv->base + ETH_OMR);
+
+	return 0;
+}
+
+static int jz4730_eth_send(struct udevice *dev, void *packet, int length)
+{
+	struct jz4730_eth_priv *priv = dev_get_priv(dev);
+	struct jz4730_eth_desc *desc = &priv->tx_desc[priv->next_tx];
+	int i;
+
+	/* tx fifo should always be idle */
+	desc->addr = virt_to_phys(packet);
+	desc->ctrl |= TD_LS | TD_FS | length;
+	desc->status = T_OWN;
+
+	flush_dcache_range((ulong)packet, (ulong)packet + length);
+	flush_dcache_range((ulong)desc, (ulong)desc + sizeof(*desc));
+
+	/* Start the tx */
+	writel(1, priv->base + ETH_TPDR);
+
+	i = 0;
+	while (desc->status & T_OWN) {
+		if (i > MAX_WAIT) {
+			printf("ETH TX timeout\n");
+			break;
+		}
+		udelay(1);
+		i++;
+
+		flush_dcache_range((ulong)desc, (ulong)desc + sizeof(*desc));
+	}
+
+	writel(DMA_INT_TI | DMA_INT_TS | DMA_INT_TU |
+	       DMA_INT_TJ | DMA_INT_FB, priv->base + ETH_SR);
+
+	desc->status = 0;
+	desc->addr = 0;
+	desc->ctrl &= ~(TD_LS | TD_FS);
+
+	flush_dcache_range((ulong)desc, (ulong)desc + sizeof(*desc));
+
+	priv->next_tx++;
+	if (priv->next_tx >= NUM_TX_DESCS)
+		priv->next_tx = 0;
+
+	return 0;
+}
+
+static int jz4730_eth_recv(struct udevice *dev, int flags, uchar **packetp)
+{
+	struct jz4730_eth_priv *priv = dev_get_priv(dev);
+	struct jz4730_eth_desc *desc = &priv->rx_desc[priv->next_rx];
+	int length;
+	u32 status;
+
+	flush_dcache_range((ulong)desc, (ulong)desc + sizeof(*desc));
+
+	status = desc->status;
+
+	if (status & R_OWN) {
+		/* Nothing has been received */
+		return -EINVAL;
+	}
+
+	length = ((status & RD_FL) >> 16); /* with 4-byte CRC value */
+
+	if (status & RD_ES) {
+		printf("ETH RX error 0x%x\n", status);
+		return 0;
+	}
+
+	if (length < 4) {
+		printf("ETH RX buffer too short\n");
+		return 0;
+	}
+
+	flush_dcache_range((ulong)net_rx_packets[priv->next_rx],
+			   (ulong)net_rx_packets[priv->next_rx] + PKTSIZE_ALIGN);
+	*packetp = net_rx_packets[priv->next_rx];
+
+	return length - 4;
+}
+
+static int jz4730_eth_free_pkt(struct udevice *dev, uchar *packet, int length)
+{
+	struct jz4730_eth_priv *priv = dev_get_priv(dev);
+	struct jz4730_eth_desc *desc = &priv->rx_desc[priv->next_rx];
+
+	/* Clear done bits */
+	writel(DMA_INT_RI | DMA_INT_RS | DMA_INT_RU |
+	       DMA_INT_RW | DMA_INT_FB, priv->base + ETH_SR);
+
+	desc->status = R_OWN;
+
+	flush_dcache_range((ulong)desc, (ulong)desc + sizeof(*desc));
+
+	priv->next_rx++;
+	if (priv->next_rx >= PKTBUFSRX)
+		priv->next_rx = 0;
+
+	return 0;
+}
+
+static void jz4730_eth_stop(struct udevice *dev)
+{
+	struct jz4730_eth_priv *priv = dev_get_priv(dev);
+
+	phy_shutdown(priv->phy);
+	writel(readl(priv->base + ETH_OMR) & ~(OMR_ST | OMR_SR), priv->base + ETH_OMR);
+}
+
+static const struct eth_ops jz4730_eth_ops = {
+	.start		= jz4730_eth_start,
+	.send		= jz4730_eth_send,
+	.recv		= jz4730_eth_recv,
+	.free_pkt	= jz4730_eth_free_pkt,
+	.stop		= jz4730_eth_stop,
+};
+
+static const struct udevice_id jz4730_eth_ids[] = {
+	{ .compatible = "ingenic,jz4730-ethernet" },
+	{ }
+};
+
+U_BOOT_DRIVER(jz4730_eth) = {
+	.name		= "jz4730_eth",
+	.id		= UCLASS_ETH,
+	.of_match	= jz4730_eth_ids,
+	.probe		= jz4730_eth_probe,
+	.remove		= jz4730_eth_remove,
+	.ops		= &jz4730_eth_ops,
+	.priv_auto_alloc_size = sizeof(struct jz4730_eth_priv),
+	.platdata_auto_alloc_size = sizeof(struct eth_pdata),
+};
-- 
2.28.0

  parent reply	other threads:[~2020-11-17 21:00 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-11-17 20:59 [PATCH RFC 00/20] MIPS: Add support for JZ4730 and Skytone Alpha 400 Lubomir Rintel
2020-11-17 20:59 ` [PATCH RFC 01/20] config: Remove CONFIG_SYS_ID_EEPROM Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 02/20] mtd: Allow building nand_spl_simple w/o SPL_NAND_ECC Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 03/20] cmd/mac: Don't build unless CONFIG_CMD_MAC is enabled Lubomir Rintel
2020-11-17 22:17   ` Daniel Schwierzeck
2020-11-17 21:00 ` [PATCH RFC 04/20] mips: Don't access CP0_EBASE on JZ47XX Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 05/20] ns16550: Turn on the UME bit if on ARCH_JZ47XX Lubomir Rintel
2020-11-17 22:29   ` Daniel Schwierzeck
2020-11-18  6:55     ` Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 06/20] clk: Add driver for Ingenic JZ4730 CGU Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 07/20] timer-uclass: Tolerate failure to get clock rate in pre_probe Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 08/20] timer: Add Ingenic JZ4730 timer driver Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 09/20] mmc: Default to JZ47XX_MMC=y on ARCH_JZ47XX Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 10/20] mmc/jz_mmc: Add a JZ4740 compatible string Lubomir Rintel
2020-11-18 13:56   ` Ezequiel Garcia
2020-11-17 21:00 ` [PATCH RFC 11/20] mmc/jz_mmc: Support wp-gpio/cd-gpio Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 12/20] pinctrl: Add Ingenic JZ4730 pin control and GPIO driver Lubomir Rintel
2020-11-17 22:39   ` Daniel Schwierzeck
2020-11-17 21:00 ` [PATCH RFC 13/20] nand: Use correct prototype of board_nand_init() with SPL_NAND_SIMPLE Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 14/20] nand/raw: Add Ingenic JZ4730 NAND flash driver Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 15/20] watchdog: Add Ingenic JZ4730 watchdog timer driver Lubomir Rintel
2020-11-17 21:00 ` Lubomir Rintel [this message]
2020-11-17 21:00 ` [PATCH RFC 17/20] mips: dts: Add Ingenic JZ4730 Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 18/20] mips/mach-jz47xx: Add Ingenic JZ4730 support Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 19/20] mips: dts: Add Skytone Alpha 400 Lubomir Rintel
2020-11-17 21:00 ` [PATCH RFC 20/20] board: " Lubomir Rintel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201117210018.751469-17-lkundrak@v3.sk \
    --to=lkundrak@v3.sk \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.