mirror of
https://github.com/armbian/build.git
synced 2025-08-08 04:06:58 +02:00
4471 lines
125 KiB
Diff
4471 lines
125 KiB
Diff
|
|
Code is backport from BSP kernel
|
|
|
|
Signed-off-by: Piotr Oniszczuk <piotr.oniszczuk@gmail.com>
|
|
|
|
diff --speed-large-files --no-dereference --minimal -Naur linux-6.12.10/drivers/net/ethernet/allwinner/gmac/Kconfig linux-6.12.10/drivers/net/ethernet/allwinner/gmac/Kconfig
|
|
--- linux-6.12.10/drivers/net/ethernet/allwinner/gmac/Kconfig 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-6.12.10/drivers/net/ethernet/allwinner/gmac/Kconfig 2025-01-23 10:14:54.944035323 +0100
|
|
@@ -0,0 +1,37 @@
|
|
+# SPDX-License-Identifier: GPL-2.0-only
|
|
+menu "Gmac Drivers"
|
|
+
|
|
+config SUNXI55I_GMAC
|
|
+ tristate "Allwinner A523 GMAC support"
|
|
+ depends on OF
|
|
+ select SUNXI55I_GMAC_MDIO
|
|
+ select CRC32
|
|
+ help
|
|
+ Support for Allwinner A523 GMAC ethernet driver.
|
|
+
|
|
+ To compile this driver as a module, choose M here. The module
|
|
+ will be called sunxi-gmac.ko.
|
|
+
|
|
+config SUNXI55I_GMAC_MDIO
|
|
+ tristate "Allwinner A523 GMAC MDIO support"
|
|
+ select MDIO_BUS
|
|
+ select MDIO_DEVICE
|
|
+ select PHYLIB
|
|
+ select MII
|
|
+ help
|
|
+ This driver supports the A523 GMAC MDIO interface in the network
|
|
+
|
|
+config SUNXI55I_GMAC_METADATA
|
|
+ bool "Allwinner A523 GMAC metadata support"
|
|
+ depends on SUNXI55I_GMAC
|
|
+ help
|
|
+ Support Allwinner A523 GMAC to transmit and receive metadata
|
|
+
|
|
+# todo (not backported from bsp yet)
|
|
+#config SUNXI55I_EPHY
|
|
+# tristate "Drivers for A523 Allwinner EPHY"
|
|
+# depends on SUNXI55I_GMAC
|
|
+# help
|
|
+# Support Allwinner A523 EPHY
|
|
+
|
|
+endmenu
|
|
diff --speed-large-files --no-dereference --minimal -Naur linux-6.12.10/drivers/net/ethernet/allwinner/gmac/Makefile linux-6.12.10/drivers/net/ethernet/allwinner/gmac/Makefile
|
|
--- linux-6.12.10/drivers/net/ethernet/allwinner/gmac/Makefile 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-6.12.10/drivers/net/ethernet/allwinner/gmac/Makefile 2025-01-23 10:14:29.307367960 +0100
|
|
@@ -0,0 +1,7 @@
|
|
+# SPDX-License-Identifier: GPL-2.0-only
|
|
+
|
|
+ccflags-y += -I $(srctree)/drivers/net/ethernet/allwinner/gmac
|
|
+
|
|
+obj-$(CONFIG_SUNXI55I_GMAC) += sunxi-gmac.o
|
|
+obj-$(CONFIG_SUNXI55I_GMAC_MDIO) += sunxi-mdio.o
|
|
+obj-$(CONFIG_SUNXI55I_EPHY) += sunxi-ephy.o
|
|
diff --speed-large-files --no-dereference --minimal -Naur linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-ephy.c linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-ephy.c
|
|
--- linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-ephy.c 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-ephy.c 2024-12-11 14:45:52.380098296 +0100
|
|
@@ -0,0 +1,315 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+/*
|
|
+* Allwinner ephy driver.
|
|
+*
|
|
+* Copyright(c) 2022-2027 Allwinnertech Co., Ltd.
|
|
+*
|
|
+* This file is licensed under the terms of the GNU General Public
|
|
+* License version 2. This program is licensed "as is" without any
|
|
+* warranty of any kind, whether express or implied.
|
|
+*/
|
|
+
|
|
+/* #define DEBUG */
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/string.h>
|
|
+#include <linux/errno.h>
|
|
+#include <linux/unistd.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/etherdevice.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/spinlock.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mii.h>
|
|
+#include <linux/ethtool.h>
|
|
+#include <linux/phy.h>
|
|
+#include <linux/platform_device.h>
|
|
+//#include <sunxi-sid.h>
|
|
+#include <asm/io.h>
|
|
+#include <asm/irq.h>
|
|
+
|
|
+#define AC300_EPHY "ac300-ephy"
|
|
+#define AC300_DEV "ac300"
|
|
+
|
|
+#define EPHY_CALI_BASE 0
|
|
+#define EPHY_CALI_BIT BIT(29)
|
|
+#define EPHY_BGS_MASK 0x0f000000
|
|
+#define EPHY_BGS_OFFSET 24
|
|
+/*
|
|
+ * Ephy diagram test
|
|
+ * This macro will cause all cpu stuck
|
|
+ * Use it carefully
|
|
+ */
|
|
+/* #define EPHY_100M_ED_TEST */
|
|
+
|
|
+struct ephy_res {
|
|
+ struct phy_device *ac300;
|
|
+ spinlock_t lock;
|
|
+ atomic_t ephy_en;
|
|
+};
|
|
+
|
|
+static struct ephy_res ac300_ephy;
|
|
+
|
|
+static int sunxi_ephy_read_sid(u32 *buf)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ if (!buf)
|
|
+ return -EINVAL;
|
|
+
|
|
+ //todo ret = sunxi_efuse_readn(EFUSE_FTCP_NAME, buf, 4);
|
|
+ //if (ret)
|
|
+ // return ret;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void sunxi_ephy_config_new_init(struct phy_device *phydev)
|
|
+{
|
|
+ phy_write(phydev, 0x1f, 0x0100); /* switch to Page 1 */
|
|
+ phy_write(phydev, 0x12, 0x4824); /* Disable APS */
|
|
+
|
|
+ phy_write(phydev, 0x1f, 0x0200); /* switch to Page 2 */
|
|
+ phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */
|
|
+
|
|
+ phy_write(phydev, 0x1f, 0x0600); /* switch to Page 6 */
|
|
+ phy_write(phydev, 0x14, 0x7809); /* PHYAFE TX optimization */
|
|
+ phy_write(phydev, 0x13, 0xf000); /* PHYAFE RX optimization */
|
|
+ phy_write(phydev, 0x10, 0x5523);
|
|
+ phy_write(phydev, 0x15, 0x3533);
|
|
+
|
|
+ phy_write(phydev, 0x1f, 0x0800); /* switch to Page 8 */
|
|
+ phy_write(phydev, 0x1d, 0x0844); /* disable auto offset */
|
|
+ phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */
|
|
+
|
|
+ phy_write(phydev, 0x1f, 0x0000); /* switch to Page 0 */
|
|
+}
|
|
+
|
|
+void sunxi_ephy_config_old_init(struct phy_device *phydev)
|
|
+{
|
|
+ phy_write(phydev, 0x1f, 0x0100); /* switch to Page 1 */
|
|
+ phy_write(phydev, 0x12, 0x4824); /* Disable APS */
|
|
+
|
|
+ phy_write(phydev, 0x1f, 0x0200); /* switch to Page 2 */
|
|
+ phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */
|
|
+
|
|
+ phy_write(phydev, 0x1f, 0x0600); /* switch to Page 6 */
|
|
+ phy_write(phydev, 0x14, 0x780b); /* PHYAFE TX optimization */
|
|
+ phy_write(phydev, 0x13, 0xf000); /* PHYAFE RX optimization */
|
|
+ phy_write(phydev, 0x15, 0x1530);
|
|
+ phy_write(phydev, 0x1f, 0x0800); /* switch to Page 8 */
|
|
+ phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */
|
|
+
|
|
+ phy_write(phydev, 0x1f, 0x0000); /* switch to Page 0 */
|
|
+}
|
|
+
|
|
+void sunxi_ephy_config_cali(struct phy_device *phydev, u32 ephy_cali)
|
|
+{
|
|
+ int value, bgs_adjust;
|
|
+
|
|
+ /* Adjust BGS value of 0x06 reg */
|
|
+ value = phy_read(phydev, 0x06);
|
|
+ value &= ~(0x0F << 12);
|
|
+ bgs_adjust = (ephy_cali & EPHY_BGS_MASK) >> EPHY_BGS_OFFSET;
|
|
+ value |= (0xF & (EPHY_CALI_BASE + bgs_adjust)) << 12;
|
|
+ phy_write(phydev, 0x06, value);
|
|
+}
|
|
+
|
|
+void sunxi_ephy_disable_intelligent_ieee(struct phy_device *phydev)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */
|
|
+ value = phy_read(phydev, 0x17); /* read address 0 0x17 register */
|
|
+ value &= ~(1 << 3); /* reg 0x17 bit 3, set 0 to disable IEEE */
|
|
+ phy_write(phydev, 0x17, value);
|
|
+ phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */
|
|
+}
|
|
+
|
|
+void sunxi_ephy_disable_802_3az_ieee(struct phy_device *phydev)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ phy_write(phydev, 0xd, 0x7);
|
|
+ phy_write(phydev, 0xe, 0x3c);
|
|
+ phy_write(phydev, 0xd, 0x1 << 14 | 0x7);
|
|
+ value = phy_read(phydev, 0xe);
|
|
+ value &= ~(0x1 << 1);
|
|
+ phy_write(phydev, 0xd, 0x7);
|
|
+ phy_write(phydev, 0xe, 0x3c);
|
|
+ phy_write(phydev, 0xd, 0x1 << 14 | 0x7);
|
|
+ phy_write(phydev, 0xe, value);
|
|
+
|
|
+ phy_write(phydev, 0x1f, 0x0200); /* switch to page 2 */
|
|
+ phy_write(phydev, 0x18, 0x0000);
|
|
+}
|
|
+
|
|
+#ifdef EPHY_100M_ED_TEST
|
|
+static void ephy_debug_test(void *info)
|
|
+{
|
|
+ while (1)
|
|
+ ;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int ephy_config_init(struct phy_device *phydev)
|
|
+{
|
|
+ int value;
|
|
+ int ret;
|
|
+ u32 ephy_cali = 0;
|
|
+
|
|
+ ret = sunxi_ephy_read_sid(&ephy_cali);
|
|
+ if (ret) {
|
|
+ pr_err("ephy cali efuse read fail, use default 0\n");
|
|
+ }
|
|
+
|
|
+ sunxi_ephy_config_cali(ac300_ephy.ac300, ephy_cali);
|
|
+
|
|
+ /*
|
|
+ * EPHY_CALI_BIT: the flag of calibration value
|
|
+ * 0: Normal
|
|
+ * 1: Low level of calibration value
|
|
+ */
|
|
+ if (ephy_cali & EPHY_CALI_BIT) {
|
|
+ pr_debug("Low level ephy, use new init\n");
|
|
+ sunxi_ephy_config_new_init(phydev);
|
|
+ } else {
|
|
+ pr_debug("Normal ephy, use old init\n");
|
|
+ sunxi_ephy_config_old_init(phydev);
|
|
+ }
|
|
+
|
|
+ sunxi_ephy_disable_intelligent_ieee(phydev); /* Disable Intelligent IEEE */
|
|
+ sunxi_ephy_disable_802_3az_ieee(phydev); /* Disable 802.3az IEEE */
|
|
+ phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */
|
|
+
|
|
+#ifdef EPHY_100M_ED_TEST
|
|
+ phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */
|
|
+ phy_write(phydev, 0x00, 0x2100); /* Force 100M Mode */
|
|
+ phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */
|
|
+ phy_write(phydev, 0x13, 0x0100); /* Force TX output@TXP/TXN */
|
|
+ on_each_cpu(ephy_debug_test, NULL, 1); /* Stuck all cpu for ephy eye diagram test */
|
|
+#endif
|
|
+
|
|
+ value = phy_read(ac300_ephy.ac300, 0x06);
|
|
+ if (phydev->interface == PHY_INTERFACE_MODE_RMII)
|
|
+ value |= (1 << 11);
|
|
+ else
|
|
+ value &= (~(1 << 11));
|
|
+
|
|
+ phy_write(ac300_ephy.ac300, 0x06, value);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ephy_probe(struct phy_device *phydev)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ephy_suspend(struct phy_device *phydev)
|
|
+{
|
|
+ return genphy_suspend(phydev);
|
|
+}
|
|
+
|
|
+static int ephy_resume(struct phy_device *phydev)
|
|
+{
|
|
+ return genphy_resume(phydev);
|
|
+}
|
|
+
|
|
+static void ac300_enable(struct phy_device *phydev)
|
|
+{
|
|
+ /* release reset */
|
|
+ phy_write(phydev, 0x00, 0x1f40); /* reset ephy */
|
|
+ phy_write(phydev, 0x00, 0x1f43); /* de-reset ephy */
|
|
+
|
|
+ /* clk gating */
|
|
+ phy_write(phydev, 0x00, 0x1fb7);
|
|
+
|
|
+ /* io enable */
|
|
+ phy_write(phydev, 0x05, 0xa81f);
|
|
+
|
|
+ mdelay(10);
|
|
+ phy_write(phydev, 0x06, 0x0811);
|
|
+
|
|
+ mdelay(10);
|
|
+ phy_write(phydev, 0x06, 0x0810);
|
|
+}
|
|
+
|
|
+static void ac300_disable(struct phy_device *phydev)
|
|
+{
|
|
+ phy_write(phydev, 0x00, 0x1f40);
|
|
+ phy_write(phydev, 0x05, 0xa800);
|
|
+
|
|
+ phy_write(phydev, 0x06, 0x01);
|
|
+}
|
|
+
|
|
+static int ac300_suspend(struct phy_device *phydev)
|
|
+{
|
|
+ ac300_disable(phydev);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ac300_resume(struct phy_device *phydev)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ac300_probe(struct phy_device *phydev)
|
|
+{
|
|
+ ac300_enable(phydev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ac300_init(struct phy_device *phydev)
|
|
+{
|
|
+ /* save ac300 message */
|
|
+ ac300_ephy.ac300 = phydev;
|
|
+
|
|
+ /* ac300 enable */
|
|
+ ac300_enable(phydev);
|
|
+
|
|
+ /* FIXME: delay may be required after AC300 reset*/
|
|
+ msleep(50);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct phy_driver ac300_driver[] = {
|
|
+{
|
|
+ .phy_id = 0xc0000000,
|
|
+ .name = AC300_DEV,
|
|
+ .phy_id_mask = 0xffffffff,
|
|
+ .config_init = ac300_init,
|
|
+ .suspend = ac300_suspend,
|
|
+ .resume = ac300_resume,
|
|
+ .probe = ac300_probe,
|
|
+},
|
|
+{ .phy_id = 0x00441400,
|
|
+ .name = AC300_EPHY,
|
|
+ .phy_id_mask = 0x0ffffff0,
|
|
+ .config_init = ephy_config_init,
|
|
+ .config_aneg = &genphy_config_aneg,
|
|
+ .read_status = &genphy_read_status,
|
|
+ .suspend = ephy_suspend,
|
|
+ .resume = ephy_resume,
|
|
+ .probe = ephy_probe,
|
|
+
|
|
+} };
|
|
+
|
|
+module_phy_driver(ac300_driver);
|
|
+
|
|
+static struct mdio_device_id __maybe_unused ac300_tbl[] = {
|
|
+ { 0xc0000000, 0x0fffffff },
|
|
+ { 0x00441400, 0x0ffffff0 },
|
|
+ { }
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(mdio, ac300_tbl);
|
|
+MODULE_DESCRIPTION("Allwinner phy drivers");
|
|
+MODULE_AUTHOR("xuminghui <xuminghui@allwinnertech.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_VERSION("1.1.1");
|
|
diff --speed-large-files --no-dereference --minimal -Naur linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-gmac.c linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-gmac.c
|
|
--- linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-gmac.c 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-gmac.c 2025-01-23 10:25:09.037385167 +0100
|
|
@@ -0,0 +1,3634 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */
|
|
+/*
|
|
+* Allwinner GMAC driver.
|
|
+*
|
|
+* Copyright(c) 2022-2027 Allwinnertech Co., Ltd.
|
|
+*
|
|
+* This file is licensed under the terms of the GNU General Public
|
|
+* License version 2. This program is licensed "as is" without any
|
|
+* warranty of any kind, whether express or implied.
|
|
+*/
|
|
+
|
|
+/* #define DEBUG */
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/ctype.h>
|
|
+#include <linux/printk.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/clk-provider.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/mii.h>
|
|
+#include <linux/crc32.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/dma-mapping.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
+#include <linux/pinctrl/pinctrl.h>
|
|
+#include <linux/etherdevice.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/phy.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/scatterlist.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <linux/of_net.h>
|
|
+#include <linux/of_mdio.h>
|
|
+#include <linux/version.h>
|
|
+//todo #include <sunxi-sid.h>
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+#include <linux/pwm.h>
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA)
|
|
+#include <linux/miscdevice.h>
|
|
+#endif /* CONFIG_SUNXI55I_GMAC_METADATA */
|
|
+#include <linux/vmalloc.h>
|
|
+
|
|
+/* All sunxi-gmac tracepoints are defined by the include below, which
|
|
+ * must be included exactly once across the whole kernel with
|
|
+ * CREATE_TRACE_POINTS defined
|
|
+ */
|
|
+//#define CREATE_TRACE_POINTS
|
|
+//#include "sunxi-gmac-trace.h"
|
|
+
|
|
+#define SUNXI_GMAC_MODULE_VERSION "2.4.3"
|
|
+
|
|
+#define SUNXI_GMAC_DMA_DESC_RX 256
|
|
+#define SUNXI_GMAC_DMA_DESC_TX 256
|
|
+#define SUNXI_GMAC_BUDGET (sunxi_gmac_dma_desc_rx / 4)
|
|
+#define SUNXI_GMAC_TX_THRESH (sunxi_gmac_dma_desc_tx / 4)
|
|
+
|
|
+#define SUNXI_GMAC_HASH_TABLE_SIZE 64
|
|
+#define SUNXI_GMAC_MAX_BUF_SZ (SZ_2K - 1)
|
|
+/* Under the premise that each descriptor currently transmits 2k data, jumbo frame max is 8100 */
|
|
+#define SUNXI_GMAC_MAX_MTU_SZ 8100
|
|
+
|
|
+/* SUNXI_GMAC_FRAME_FILTER register value */
|
|
+#define SUNXI_GMAC_FRAME_FILTER_PR 0x80000000 /* Promiscuous Mode */
|
|
+#define SUNXI_GMAC_FRAME_FILTER_HUC 0x00000100 /* Hash Unicast */
|
|
+#define SUNXI_GMAC_FRAME_FILTER_HMC 0x00000200 /* Hash Multicast */
|
|
+#define SUNXI_GMAC_FRAME_FILTER_DAIF 0x00000010 /* DA Inverse Filtering */
|
|
+#define SUNXI_GMAC_FRAME_FILTER_PM 0x00010000 /* Pass all multicast */
|
|
+#define SUNXI_GMAC_FRAME_FILTER_DBF 0x00020000 /* Disable Broadcast frames */
|
|
+#define SUNXI_GMAC_FRAME_FILTER_SAIF 0x00000020 /* Inverse Filtering */
|
|
+#define SUNXI_GMAC_FRAME_FILTER_SAF 0x00000040 /* Source Address Filter */
|
|
+#define SUNXI_GMAC_FRAME_FILTER_RA 0x00000001 /* Receive all mode */
|
|
+
|
|
+/* Default tx descriptor */
|
|
+#define SUNXI_GMAC_TX_SINGLE_DESC0 0x80000000
|
|
+#define SUNXI_GMAC_TX_SINGLE_DESC1 0x63000000
|
|
+
|
|
+/* Default rx descriptor */
|
|
+#define SUNXI_GMAC_RX_SINGLE_DESC0 0x80000000
|
|
+#define SUNXI_GMAC_RX_SINGLE_DESC1 0x83000000
|
|
+
|
|
+/******************************************************************************
|
|
+ * sunxi gmac reg offset
|
|
+ *****************************************************************************/
|
|
+#define SUNXI_GMAC_BASIC_CTL0 0x00
|
|
+#define SUNXI_GMAC_BASIC_CTL1 0x04
|
|
+#define SUNXI_GMAC_INT_STA 0x08
|
|
+#define SUNXI_GMAC_INT_EN 0x0C
|
|
+#define SUNXI_GMAC_TX_CTL0 0x10
|
|
+#define SUNXI_GMAC_TX_CTL1 0x14
|
|
+#define SUNXI_GMAC_TX_FLOW_CTL 0x1C
|
|
+#define SUNXI_GMAC_TX_DESC_LIST 0x20
|
|
+#define SUNXI_GMAC_RX_CTL0 0x24
|
|
+#define SUNXI_GMAC_RX_CTL1 0x28
|
|
+#define SUNXI_GMAC_RX_DESC_LIST 0x34
|
|
+#define SUNXI_GMAC_RX_FRM_FLT 0x38
|
|
+#define SUNXI_GMAC_RX_HASH0 0x40
|
|
+#define SUNXI_GMAC_RX_HASH1 0x44
|
|
+#define SUNXI_GMAC_MDIO_ADDR 0x48
|
|
+#define SUNXI_GMAC_MDIO_DATA 0x4C
|
|
+#define SUNXI_GMAC_ADDR_HI(reg) (0x50 + ((reg) << 3))
|
|
+#define SUNXI_GMAC_ADDR_LO(reg) (0x54 + ((reg) << 3))
|
|
+#define SUNXI_GMAC_TX_DMA_STA 0xB0
|
|
+#define SUNXI_GMAC_TX_CUR_DESC 0xB4
|
|
+#define SUNXI_GMAC_TX_CUR_BUF 0xB8
|
|
+#define SUNXI_GMAC_RX_DMA_STA 0xC0
|
|
+#define SUNXI_GMAC_RX_CUR_DESC 0xC4
|
|
+#define SUNXI_GMAC_RX_CUR_BUF 0xC8
|
|
+#define SUNXI_GMAC_RGMII_STA 0xD0
|
|
+
|
|
+#define SUNXI_GMAC_RGMII_IRQ 0x00000001
|
|
+
|
|
+#define SUNXI_GMAC_CTL0_LM 0x02
|
|
+#define SUNXI_GMAC_CTL0_DM 0x01
|
|
+#define SUNXI_GMAC_CTL0_SPEED 0x04
|
|
+
|
|
+#define SUNXI_GMAC_BURST_LEN 0x3F000000
|
|
+#define SUNXI_GMAC_RX_TX_PRI 0x02
|
|
+#define SUNXI_GMAC_SOFT_RST 0x01
|
|
+
|
|
+#define SUNXI_GMAC_TX_FLUSH 0x01
|
|
+#define SUNXI_GMAC_TX_MD 0x02
|
|
+#define SUNXI_GMAC_TX_NEXT_FRM 0x04
|
|
+#define SUNXI_GMAC_TX_TH 0x0700
|
|
+#define SUNXI_GMAC_TX_FLOW_CTL_BIT 0x01
|
|
+
|
|
+#define SUNXI_GMAC_RX_FLUSH 0x01
|
|
+#define SUNXI_GMAC_RX_MD 0x02
|
|
+#define SUNXI_GMAC_RX_RUNT_FRM 0x04
|
|
+#define SUNXI_GMAC_RX_ERR_FRM 0x08
|
|
+#define SUNXI_GMAC_RX_TH 0x0030
|
|
+#define SUNXI_GMAC_RX_FLOW_CTL 0x1000000
|
|
+
|
|
+#define SUNXI_GMAC_TX_INT 0x00001
|
|
+#define SUNXI_GMAC_TX_STOP_INT 0x00002
|
|
+#define SUNXI_GMAC_TX_UA_INT 0x00004
|
|
+#define SUNXI_GMAC_TX_TOUT_INT 0x00008
|
|
+#define SUNXI_GMAC_TX_UNF_INT 0x00010
|
|
+#define SUNXI_GMAC_TX_EARLY_INT 0x00020
|
|
+#define SUNXI_GMAC_RX_INT 0x00100
|
|
+#define SUNXI_GMAC_RX_UA_INT 0x00200
|
|
+#define SUNXI_GMAC_RX_STOP_INT 0x00400
|
|
+#define SUNXI_GMAC_RX_TOUT_INT 0x00800
|
|
+#define SUNXI_GMAC_RX_OVF_INT 0x01000
|
|
+#define SUNXI_GMAC_RX_EARLY_INT 0x02000
|
|
+#define SUNXI_GMAC_LINK_STA_INT 0x10000
|
|
+
|
|
+#define SUNXI_GMAC_CHAIN_MODE_OFFSET 24
|
|
+#define SUNXI_GMAC_LOOPBACK_OFFSET 2
|
|
+#define SUNXI_GMAC_LOOPBACK 0x00000002
|
|
+#define SUNXI_GMAC_CLEAR_SPEED 0x03
|
|
+#define SUNXI_GMAC_1000M_SPEED ~0x0c
|
|
+#define SUNXI_GMAC_100M_SPEED 0x0c
|
|
+#define SUNXI_GMAC_10M_SPEED 0x08
|
|
+#define SUNXI_GMAC_RX_FLOW_EN 0x10000
|
|
+#define SUNXI_GMAC_TX_FLOW_EN 0x00001
|
|
+#define SUNXI_GMAC_PAUSE_OFFSET 4
|
|
+#define SUNXI_GMAC_INT_OFFSET 0x3fff
|
|
+#define SUNXI_GMAC_RX_DMA_EN 0x40000000
|
|
+#define SUNXI_GMAC_TX_DMA_EN 0x40000000
|
|
+#define SUNXI_GMAC_BURST_VALUE 8
|
|
+#define SUNXI_GMAC_BURST_OFFSET 24
|
|
+#define SUNXI_GMAC_SF_DMA_MODE 1
|
|
+#define SUNXI_GMAC_TX_FRM_LEN_OFFSET 30
|
|
+#define SUNXI_GMAC_CRC_OFFSET 27
|
|
+#define SUNXI_GMAC_STRIP_FCS_OFFSET 28
|
|
+#define SUNXI_GMAC_JUMBO_EN_OFFSET 29
|
|
+#define SUNXI_GMAC_MDC_DIV_RATIO_M 0x03
|
|
+#define SUNXI_GMAC_MDC_DIV_OFFSET 20
|
|
+#define SUNXI_GMAC_TX_DMA_TH64 64
|
|
+#define SUNXI_GMAC_TX_DMA_TH128 128
|
|
+#define SUNXI_GMAC_TX_DMA_TH192 192
|
|
+#define SUNXI_GMAC_TX_DMA_TH256 256
|
|
+#define SUNXI_GMAC_TX_DMA_TH64_VAL 0x00000000
|
|
+#define SUNXI_GMAC_TX_DMA_TH128_VAL 0X00000100
|
|
+#define SUNXI_GMAC_TX_DMA_TH192_VAL 0x00000200
|
|
+#define SUNXI_GMAC_TX_DMA_TH256_VAL 0x00000300
|
|
+#define SUNXI_GMAC_RX_DMA_TH32 32
|
|
+#define SUNXI_GMAC_RX_DMA_TH64 64
|
|
+#define SUNXI_GMAC_RX_DMA_TH96 96
|
|
+#define SUNXI_GMAC_RX_DMA_TH128 128
|
|
+#define SUNXI_GMAC_RX_DMA_TH32_VAL 0x10
|
|
+#define SUNXI_GMAC_RX_DMA_TH64_VAL 0x00
|
|
+#define SUNXI_GMAC_RX_DMA_TH96_VAL 0x20
|
|
+#define SUNXI_GMAC_RX_DMA_TH128_VAL 0x30
|
|
+#define SUNXI_GMAC_TX_DMA_START 31
|
|
+#define SUNXI_GMAC_RX_DMA_START 31
|
|
+#define SUNXI_GMAC_DMA_DESC_BUFSIZE 11
|
|
+#define SUNXI_GMAC_LOOPBACK_OFF 0
|
|
+#define SUNXI_GMAC_MAC_LOOPBACK_ON 1
|
|
+#define SUNXI_GMAC_PHY_LOOPBACK_ON 2
|
|
+#define SUNXI_GMAC_OWN_DMA 0x80000000
|
|
+#define SUNXI_GMAC_GPHY_TEST_OFFSET 13
|
|
+#define SUNXI_GMAC_GPHY_TEST_MASK 0x07
|
|
+#define SUNXI_GMAC_PHY_RGMII_MASK 0x00000004
|
|
+#define SUNXI_GMAC_ETCS_RMII_MASK 0x00002003
|
|
+#define SUNXI_GMAC_RGMII_INTCLK_MASK 0x00000002
|
|
+#define SUNXI_GMAC_RMII_MASK 0x00002000
|
|
+#define SUNXI_GMAC_TX_DELAY_MASK 0x07
|
|
+#define SUNXI_GMAC_TX_DELAY_OFFSET 10
|
|
+#define SUNXI_GMAC_RX_DELAY_MASK 0x1F
|
|
+#define SUNXI_GMAC_RX_DELAY_OFFSET 5
|
|
+/* Flow Control defines */
|
|
+#define SUNXI_GMAC_FLOW_OFF 0
|
|
+#define SUNXI_GMAC_FLOW_RX 1
|
|
+#define SUNXI_GMAC_FLOW_TX 2
|
|
+#define SUNXI_GMAC_FLOW_AUTO (SUNXI_GMAC_FLOW_TX | SUNXI_GMAC_FLOW_RX)
|
|
+
|
|
+/* Ring buffer caculate method */
|
|
+#define circ_cnt(head, tail, size) (((head) > (tail)) ? \
|
|
+ ((head) - (tail)) : \
|
|
+ ((head) - (tail)) & ((size) - 1))
|
|
+
|
|
+#define circ_space(head, tail, size) circ_cnt((tail), ((head) + 1), (size))
|
|
+
|
|
+#define circ_inc(n, s) (((n) + 1) % (s))
|
|
+
|
|
+#define MAC_ADDR_LEN 18
|
|
+#define SUNXI_GMAC_MAC_ADDRESS "80:3f:5d:09:8b:26"
|
|
+
|
|
+/* loopback test */
|
|
+#define LOOPBACK_PKT_CNT 64
|
|
+#define LOOPBACK_PKT_LEN 1514
|
|
+#define LOOPBACK_DEFAULT_TIME 5
|
|
+enum self_test_index {
|
|
+ INTERNAL_LOOPBACK_TEST = 0,
|
|
+ EXTERNAL_LOOPBACK_TEST = 1,
|
|
+ SELF_TEST_MAX = 2,
|
|
+};
|
|
+
|
|
+static char sunxi_gmac_test_strings[][ETH_GSTRING_LEN] = {
|
|
+ "Internal lb test (mac loopback)",
|
|
+ "External lb test (phy loopback)",
|
|
+};
|
|
+
|
|
+//todo #ifdef MODULE
|
|
+//extern int get_custom_mac_address(int fmt, char *name, char *addr);
|
|
+//#endif
|
|
+
|
|
+static char mac_str[MAC_ADDR_LEN] = SUNXI_GMAC_MAC_ADDRESS;
|
|
+module_param_string(mac_str, mac_str, MAC_ADDR_LEN, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(mac_str, "MAC Address String.(xx:xx:xx:xx:xx:xx)");
|
|
+
|
|
+static int rxmode = 1;
|
|
+module_param(rxmode, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(rxmode, "DMA threshold control value");
|
|
+
|
|
+static int txmode = 1;
|
|
+module_param(txmode, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(txmode, "DMA threshold control value");
|
|
+
|
|
+static int pause = 0x400;
|
|
+module_param(pause, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(pause, "Flow Control Pause Time");
|
|
+
|
|
+#define TX_TIMEO 5000
|
|
+static int watchdog = TX_TIMEO;
|
|
+module_param(watchdog, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(watchdog, "Transmit timeout in milliseconds");
|
|
+
|
|
+static int sunxi_gmac_dma_desc_rx = SUNXI_GMAC_DMA_DESC_RX;
|
|
+module_param(sunxi_gmac_dma_desc_rx, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(sunxi_gmac_dma_desc_rx, "The number of receive's descriptors");
|
|
+
|
|
+static int sunxi_gmac_dma_desc_tx = SUNXI_GMAC_DMA_DESC_TX;
|
|
+module_param(sunxi_gmac_dma_desc_tx, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(sunxi_gmac_dma_desc_tx, "The number of transmit's descriptors");
|
|
+
|
|
+static int user_tx_delay = -1;
|
|
+module_param(user_tx_delay, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(user_tx_delay, "RGMII tx-delay parameter");
|
|
+
|
|
+static int user_rx_delay = -1;
|
|
+module_param(user_rx_delay, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(user_rx_delay, "RGMII rx-delay parameter");
|
|
+
|
|
+/* - 0: Flow Off
|
|
+ * - 1: Rx Flow
|
|
+ * - 2: Tx Flow
|
|
+ * - 3: Rx & Tx Flow
|
|
+ */
|
|
+static int flow_ctrl;
|
|
+module_param(flow_ctrl, int, S_IRUGO | S_IWUSR);
|
|
+MODULE_PARM_DESC(flow_ctrl, "Flow control [0: off, 1: rx, 2: tx, 3: both]");
|
|
+
|
|
+typedef union {
|
|
+ struct {
|
|
+ /* TDES0 */
|
|
+ unsigned int deferred:1; /* Deferred bit (only half-duplex) */
|
|
+ unsigned int under_err:1; /* Underflow error */
|
|
+ unsigned int ex_deferral:1; /* Excessive deferral */
|
|
+ unsigned int coll_cnt:4; /* Collision count */
|
|
+ unsigned int vlan_tag:1; /* VLAN Frame */
|
|
+ unsigned int ex_coll:1; /* Excessive collision */
|
|
+ unsigned int late_coll:1; /* Late collision */
|
|
+ unsigned int no_carr:1; /* No carrier */
|
|
+ unsigned int loss_carr:1; /* Loss of collision */
|
|
+ unsigned int ipdat_err:1; /* IP payload error */
|
|
+ unsigned int frm_flu:1; /* Frame flushed */
|
|
+ unsigned int jab_timeout:1; /* Jabber timeout */
|
|
+ unsigned int err_sum:1; /* Error summary */
|
|
+ unsigned int iphead_err:1; /* IP header error */
|
|
+ unsigned int ttss:1; /* Transmit time stamp status */
|
|
+ unsigned int reserved0:13;
|
|
+ unsigned int own:1; /* Own bit. CPU:0, DMA:1 */
|
|
+ } tx;
|
|
+
|
|
+ /* bits 5 7 0 | Frame status
|
|
+ * ----------------------------------------------------------
|
|
+ * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octects)
|
|
+ * 1 0 0 | IPv4/6 No CSUM errorS.
|
|
+ * 1 0 1 | IPv4/6 CSUM PAYLOAD error
|
|
+ * 1 1 0 | IPv4/6 CSUM IP HR error
|
|
+ * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS
|
|
+ * 0 0 1 | IPv4/6 unsupported IP PAYLOAD
|
|
+ * 0 1 1 | COE bypassed.. no IPv4/6 frame
|
|
+ * 0 1 0 | Reserved.
|
|
+ */
|
|
+ struct {
|
|
+ /* RDES0 */
|
|
+ unsigned int chsum_err:1; /* Payload checksum error */
|
|
+ unsigned int crc_err:1; /* CRC error */
|
|
+ unsigned int dribbling:1; /* Dribble bit error */
|
|
+ unsigned int mii_err:1; /* Received error (bit3) */
|
|
+ unsigned int recv_wt:1; /* Received watchdog timeout */
|
|
+ unsigned int frm_type:1; /* Frame type */
|
|
+ unsigned int late_coll:1; /* Late Collision */
|
|
+ unsigned int ipch_err:1; /* IPv header checksum error (bit7) */
|
|
+ unsigned int last_desc:1; /* Laset descriptor */
|
|
+ unsigned int first_desc:1; /* First descriptor */
|
|
+ unsigned int vlan_tag:1; /* VLAN Tag */
|
|
+ unsigned int over_err:1; /* Overflow error (bit11) */
|
|
+ unsigned int len_err:1; /* Length error */
|
|
+ unsigned int sou_filter:1; /* Source address filter fail */
|
|
+ unsigned int desc_err:1; /* Descriptor error */
|
|
+ unsigned int err_sum:1; /* Error summary (bit15) */
|
|
+ unsigned int frm_len:14; /* Frame length */
|
|
+ unsigned int des_filter:1; /* Destination address filter fail */
|
|
+ unsigned int own:1; /* Own bit. CPU:0, DMA:1 */
|
|
+ #define RX_PKT_OK 0x7FFFB77C
|
|
+ #define RX_LEN 0x3FFF0000
|
|
+ } rx;
|
|
+
|
|
+ unsigned int all;
|
|
+} sunxi_gmac_desc0_u;
|
|
+
|
|
+typedef union {
|
|
+ struct {
|
|
+ /* TDES1 */
|
|
+ unsigned int buf1_size:11; /* Transmit buffer1 size */
|
|
+ unsigned int buf2_size:11; /* Transmit buffer2 size */
|
|
+ unsigned int ttse:1; /* Transmit time stamp enable */
|
|
+ unsigned int dis_pad:1; /* Disable pad (bit23) */
|
|
+ unsigned int adr_chain:1; /* Second address chained */
|
|
+ unsigned int end_ring:1; /* Transmit end of ring */
|
|
+ unsigned int crc_dis:1; /* Disable CRC */
|
|
+ unsigned int cic:2; /* Checksum insertion control (bit27:28) */
|
|
+ unsigned int first_sg:1; /* First Segment */
|
|
+ unsigned int last_seg:1; /* Last Segment */
|
|
+ unsigned int interrupt:1; /* Interrupt on completion */
|
|
+ } tx;
|
|
+
|
|
+ struct {
|
|
+ /* RDES1 */
|
|
+ unsigned int buf1_size:11; /* Received buffer1 size */
|
|
+ unsigned int buf2_size:11; /* Received buffer2 size */
|
|
+ unsigned int reserved1:2;
|
|
+ unsigned int adr_chain:1; /* Second address chained */
|
|
+ unsigned int end_ring:1; /* Received end of ring */
|
|
+ unsigned int reserved2:5;
|
|
+ unsigned int dis_ic:1; /* Disable interrupt on completion */
|
|
+ } rx;
|
|
+
|
|
+ unsigned int all;
|
|
+} sunxi_gmac_desc1_u;
|
|
+
|
|
+typedef struct sunxi_gmac_dma_desc {
|
|
+ sunxi_gmac_desc0_u desc0;
|
|
+ sunxi_gmac_desc1_u desc1;
|
|
+ /* The address of buffers */
|
|
+ unsigned int desc2;
|
|
+ /* Next desc's address */
|
|
+ unsigned int desc3;
|
|
+} __attribute__((packed)) sunxi_gmac_dma_desc_t;
|
|
+
|
|
+enum rx_frame_status { /* IPC status */
|
|
+ good_frame = 0,
|
|
+ discard_frame = 1,
|
|
+ csum_none = 2,
|
|
+ incomplete_frame = 3, /* use for jumbo frame */
|
|
+ llc_snap = 4,
|
|
+};
|
|
+
|
|
+enum tx_dma_irq_status {
|
|
+ tx_hard_error = 1,
|
|
+ tx_hard_error_bump_tc = 2,
|
|
+ handle_tx_rx = 3,
|
|
+};
|
|
+
|
|
+struct sunxi_gmac_extra_stats {
|
|
+ /* Transmit errors */
|
|
+ unsigned long tx_underflow;
|
|
+ unsigned long tx_carrier;
|
|
+ unsigned long tx_losscarrier;
|
|
+ unsigned long vlan_tag;
|
|
+ unsigned long tx_deferred;
|
|
+ unsigned long tx_vlan;
|
|
+ unsigned long tx_jabber;
|
|
+ unsigned long tx_frame_flushed;
|
|
+ unsigned long tx_payload_error;
|
|
+ unsigned long tx_ip_header_error;
|
|
+
|
|
+ /* Receive errors */
|
|
+ unsigned long rx_desc;
|
|
+ unsigned long sa_filter_fail;
|
|
+ unsigned long overflow_error;
|
|
+ unsigned long ipc_csum_error;
|
|
+ unsigned long rx_collision;
|
|
+ unsigned long rx_crc;
|
|
+ unsigned long dribbling_bit;
|
|
+ unsigned long rx_length;
|
|
+ unsigned long rx_mii;
|
|
+ unsigned long rx_multicast;
|
|
+ unsigned long rx_gmac_overflow;
|
|
+ unsigned long rx_watchdog;
|
|
+ unsigned long da_rx_filter_fail;
|
|
+ unsigned long sa_rx_filter_fail;
|
|
+ unsigned long rx_missed_cntr;
|
|
+ unsigned long rx_overflow_cntr;
|
|
+ unsigned long rx_vlan;
|
|
+
|
|
+ /* Tx/Rx IRQ errors */
|
|
+ unsigned long tx_undeflow_irq;
|
|
+ unsigned long tx_process_stopped_irq;
|
|
+ unsigned long tx_jabber_irq;
|
|
+ unsigned long rx_overflow_irq;
|
|
+ unsigned long rx_buf_unav_irq;
|
|
+ unsigned long rx_process_stopped_irq;
|
|
+ unsigned long rx_watchdog_irq;
|
|
+ unsigned long tx_early_irq;
|
|
+ unsigned long fatal_bus_error_irq;
|
|
+
|
|
+ /* Extra info */
|
|
+ unsigned long threshold;
|
|
+ unsigned long tx_pkt_n;
|
|
+ unsigned long rx_pkt_n;
|
|
+ unsigned long poll_n;
|
|
+ unsigned long sched_timer_n;
|
|
+ unsigned long normal_irq_n;
|
|
+};
|
|
+
|
|
+struct sunxi_gmac;
|
|
+
|
|
+struct sunxi_gmac_ephy_ops {
|
|
+ int (*resource_get)(struct platform_device *pdev);
|
|
+ void (*resource_put)(struct platform_device *pdev);
|
|
+ int (*hardware_init)(struct sunxi_gmac *chip);
|
|
+ void (*hardware_deinit)(struct sunxi_gmac *chip);
|
|
+};
|
|
+
|
|
+struct sunxi_gmac {
|
|
+ struct sunxi_gmac_dma_desc *dma_tx; /* Tx dma descriptor */
|
|
+ struct sk_buff **tx_skb; /* Tx socket buffer array */
|
|
+ unsigned int tx_clean; /* Tx ring buffer data consumer */
|
|
+ unsigned int tx_dirty; /* Tx ring buffer data provider */
|
|
+ dma_addr_t dma_tx_phy; /* Tx dma physical address */
|
|
+
|
|
+ unsigned long buf_sz; /* Size of buffer specified by current descriptor */
|
|
+
|
|
+ struct sunxi_gmac_dma_desc *dma_rx; /* Rx dma descriptor */
|
|
+ struct sk_buff **rx_skb; /* Rx socket buffer array */
|
|
+ unsigned int rx_clean; /* Rx ring buffer data consumer */
|
|
+ unsigned int rx_dirty; /* Rx ring buffer data provider */
|
|
+ dma_addr_t dma_rx_phy; /* Rx dma physical address */
|
|
+
|
|
+ struct net_device *ndev;
|
|
+ struct device *dev;
|
|
+ struct napi_struct napi;
|
|
+
|
|
+ struct sunxi_gmac_extra_stats xstats; /* Additional network statistics */
|
|
+
|
|
+ bool link; /* Phy link status */
|
|
+ int speed; /* NIC network speed */
|
|
+ int duplex; /* NIC network duplex capability */
|
|
+
|
|
+ /* suspend error workaround */
|
|
+ unsigned int gmac_uevent_suppress; /* control kobject_uevent_env */
|
|
+
|
|
+#define SUNXI_EXTERNAL_PHY 1
|
|
+#define SUNXI_INTERNAL_PHY 0
|
|
+ u32 phy_type; /* 1: External phy, 0: Internal phy */
|
|
+
|
|
+#define SUNXI_PHY_USE_CLK25M 0 /* External phy use phy25m clk provided by Soc */
|
|
+#define SUNXI_PHY_USE_EXT_OSC 1 /* External phy use extern osc 25m */
|
|
+ u32 phy_clk_type;
|
|
+
|
|
+ phy_interface_t phy_interface;
|
|
+ void __iomem *base;
|
|
+ void __iomem *syscfg_base;
|
|
+ struct clk *gmac_clk;
|
|
+ struct clk *phy25m_clk;
|
|
+ struct reset_control *reset;
|
|
+ struct pinctrl *pinctrl;
|
|
+ struct regulator *gmac_supply;
|
|
+
|
|
+ /* definition spinlock */
|
|
+ spinlock_t universal_lock; /* universal spinlock */
|
|
+ spinlock_t tx_lock; /* tx tramsmit spinlock */
|
|
+
|
|
+ /* adjust transmit clock delay, value: 0~7 */
|
|
+ /* adjust receive clock delay, value: 0~31 */
|
|
+ u32 tx_delay;
|
|
+ u32 rx_delay;
|
|
+
|
|
+ struct device_node *phy_node;
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+ struct device_node *ac300_np;
|
|
+ struct phy_device *ac300_dev;
|
|
+ struct pwm_device *ac300_pwm;
|
|
+ u32 pwm_channel;
|
|
+#define PWM_DUTY_NS 205
|
|
+#define PWM_PERIOD_NS 410
|
|
+ struct sunxi_gmac_ephy_ops *ephy_ops;
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+
|
|
+ /* loopback test */
|
|
+ int loopback_pkt_len;
|
|
+ int loopback_test_rx_idx;
|
|
+ bool is_loopback_test;
|
|
+ u8 *loopback_test_rx_buf;
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA)
|
|
+ u8 *metadata_buff;
|
|
+ struct miscdevice mdev;
|
|
+ struct completion metadata_done;
|
|
+ u32 metadata_len;
|
|
+#endif
|
|
+
|
|
+ struct sk_buff *skb; /* for jumbo frame */
|
|
+
|
|
+ u32 irq_affinity;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_desc_init_chain - GMAC dma descriptor chain table initialization
|
|
+ *
|
|
+ * @desc: Dma descriptor
|
|
+ * @addr: Dma descriptor physical address
|
|
+ * @size: Dma descriptor numsa
|
|
+ *
|
|
+ * Called when the NIC is up. We init Tx/Rx dma descriptor table.
|
|
+ */
|
|
+static void sunxi_gmac_desc_init_chain(struct sunxi_gmac_dma_desc *desc, unsigned long addr, unsigned int size)
|
|
+{
|
|
+ /* In chained mode the desc3 points to the next element in the ring.
|
|
+ * The latest element has to point to the head.
|
|
+ */
|
|
+ int i;
|
|
+ struct sunxi_gmac_dma_desc *p = desc;
|
|
+ unsigned long dma_phy = addr;
|
|
+
|
|
+ for (i = 0; i < (size - 1); i++) {
|
|
+ dma_phy += sizeof(*p);
|
|
+ p->desc3 = (unsigned int)dma_phy;
|
|
+ /* Chain mode */
|
|
+ p->desc1.all |= (1 << SUNXI_GMAC_CHAIN_MODE_OFFSET);
|
|
+ p++;
|
|
+ }
|
|
+ p->desc1.all |= (1 << SUNXI_GMAC_CHAIN_MODE_OFFSET);
|
|
+ p->desc3 = (unsigned int)addr;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_set_link_mode - GMAC speed/duplex set func
|
|
+ *
|
|
+ * @iobase: Gmac membase
|
|
+ * @duplex: Duplex capability:half/full
|
|
+ * @speed: Speed:10M/100M/1000M
|
|
+ *
|
|
+ * Updates phy status and takes action for network queue if required
|
|
+ * based upon link status.
|
|
+ */
|
|
+static void sunxi_gmac_set_link_mode(void *iobase, int duplex, int speed)
|
|
+{
|
|
+ unsigned int ctrl = readl(iobase + SUNXI_GMAC_BASIC_CTL0);
|
|
+
|
|
+ if (!duplex)
|
|
+ ctrl &= ~SUNXI_GMAC_CTL0_DM;
|
|
+ else
|
|
+ ctrl |= SUNXI_GMAC_CTL0_DM;
|
|
+
|
|
+ /* clear ctrl speed */
|
|
+ ctrl &= SUNXI_GMAC_CLEAR_SPEED;
|
|
+
|
|
+ switch (speed) {
|
|
+ case 1000:
|
|
+ ctrl &= SUNXI_GMAC_1000M_SPEED;
|
|
+ break;
|
|
+ case 100:
|
|
+ ctrl |= SUNXI_GMAC_100M_SPEED;
|
|
+ break;
|
|
+ case 10:
|
|
+ ctrl |= SUNXI_GMAC_10M_SPEED;
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ writel(ctrl, iobase + SUNXI_GMAC_BASIC_CTL0);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_loop - GMAC loopback mode set func
|
|
+ *
|
|
+ * @iobase: Gmac membase
|
|
+ * @loopback_enable: Loopback status
|
|
+ */
|
|
+static void sunxi_gmac_loopback(void *iobase, int loopback_enable)
|
|
+{
|
|
+ int reg;
|
|
+
|
|
+ reg = readl(iobase + SUNXI_GMAC_BASIC_CTL0);
|
|
+ if (loopback_enable)
|
|
+ reg |= SUNXI_GMAC_LOOPBACK_OFFSET;
|
|
+ else
|
|
+ reg &= ~SUNXI_GMAC_LOOPBACK_OFFSET;
|
|
+ writel(reg, iobase + SUNXI_GMAC_BASIC_CTL0);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_crc(void *iobase, bool enable)
|
|
+{
|
|
+ int reg;
|
|
+
|
|
+ reg = readl(iobase + SUNXI_GMAC_RX_CTL0);
|
|
+ if (enable)
|
|
+ reg |= (1 << SUNXI_GMAC_CRC_OFFSET);
|
|
+ else
|
|
+ reg &= ~(1 << SUNXI_GMAC_CRC_OFFSET);
|
|
+
|
|
+ writel(reg, iobase + SUNXI_GMAC_RX_CTL0);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_flow_ctrl - GMAC flow ctrl set func
|
|
+ *
|
|
+ * @iobase: Gmac membase
|
|
+ * @duolex: Duplex capability
|
|
+ * @fc: Flow control option
|
|
+ * @pause: Flow control pause time
|
|
+ */
|
|
+static void sunxi_gmac_flow_ctrl(void *iobase, int duplex, int fc, int pause)
|
|
+{
|
|
+ unsigned int flow;
|
|
+
|
|
+ if (fc & SUNXI_GMAC_FLOW_RX) {
|
|
+ flow = readl(iobase + SUNXI_GMAC_RX_CTL0);
|
|
+ flow |= SUNXI_GMAC_RX_FLOW_EN;
|
|
+ writel(flow, iobase + SUNXI_GMAC_RX_CTL0);
|
|
+ }
|
|
+
|
|
+ if (fc & SUNXI_GMAC_FLOW_TX) {
|
|
+ flow = readl(iobase + SUNXI_GMAC_TX_FLOW_CTL);
|
|
+ flow |= SUNXI_GMAC_TX_FLOW_EN;
|
|
+ writel(flow, iobase + SUNXI_GMAC_TX_FLOW_CTL);
|
|
+ }
|
|
+
|
|
+ if (duplex) {
|
|
+ flow = readl(iobase + SUNXI_GMAC_TX_FLOW_CTL);
|
|
+ flow |= (pause << SUNXI_GMAC_PAUSE_OFFSET);
|
|
+ writel(flow, iobase + SUNXI_GMAC_TX_FLOW_CTL);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_int_status - GMAC get int status func
|
|
+ *
|
|
+ * @iobase: Gmac membase
|
|
+ * @x: Extra statistics
|
|
+ */
|
|
+static int sunxi_gmac_int_status(void *iobase, struct sunxi_gmac_extra_stats *x)
|
|
+{
|
|
+ int ret;
|
|
+ /* read the status register (CSR5) */
|
|
+ unsigned int intr_status;
|
|
+
|
|
+ intr_status = readl(iobase + SUNXI_GMAC_RGMII_STA);
|
|
+ if (intr_status & SUNXI_GMAC_RGMII_IRQ)
|
|
+ readl(iobase + SUNXI_GMAC_RGMII_STA);
|
|
+
|
|
+ intr_status = readl(iobase + SUNXI_GMAC_INT_STA);
|
|
+
|
|
+ /* ABNORMAL interrupts */
|
|
+ if (intr_status & SUNXI_GMAC_TX_UNF_INT) {
|
|
+ ret = tx_hard_error_bump_tc;
|
|
+ x->tx_undeflow_irq++;
|
|
+ }
|
|
+ if (intr_status & SUNXI_GMAC_TX_TOUT_INT)
|
|
+ x->tx_jabber_irq++;
|
|
+
|
|
+ if (intr_status & SUNXI_GMAC_RX_OVF_INT)
|
|
+ x->rx_overflow_irq++;
|
|
+
|
|
+ if (intr_status & SUNXI_GMAC_RX_UA_INT)
|
|
+ x->rx_buf_unav_irq++;
|
|
+
|
|
+ if (intr_status & SUNXI_GMAC_RX_STOP_INT)
|
|
+ x->rx_process_stopped_irq++;
|
|
+
|
|
+ if (intr_status & SUNXI_GMAC_RX_TOUT_INT)
|
|
+ x->rx_watchdog_irq++;
|
|
+
|
|
+ if (intr_status & SUNXI_GMAC_TX_EARLY_INT)
|
|
+ x->tx_early_irq++;
|
|
+
|
|
+ if (intr_status & SUNXI_GMAC_TX_STOP_INT) {
|
|
+ x->tx_process_stopped_irq++;
|
|
+ ret = tx_hard_error;
|
|
+ }
|
|
+
|
|
+ /* TX/RX NORMAL interrupts */
|
|
+ if (intr_status & (SUNXI_GMAC_TX_INT | SUNXI_GMAC_RX_INT | SUNXI_GMAC_RX_EARLY_INT | SUNXI_GMAC_TX_UA_INT)) {
|
|
+ x->normal_irq_n++;
|
|
+ if (intr_status & (SUNXI_GMAC_TX_INT | SUNXI_GMAC_RX_INT))
|
|
+ ret = handle_tx_rx;
|
|
+ }
|
|
+ /* Clear the interrupt by writing a logic 1 to the CSR5[15-0] */
|
|
+ writel(intr_status & SUNXI_GMAC_INT_OFFSET, iobase + SUNXI_GMAC_INT_STA);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_enable_rx - enable gmac rx dma
|
|
+ *
|
|
+ * @iobase: Gmac membase
|
|
+ * @rxbase: Base address of Rx descriptor
|
|
+ */
|
|
+static void sunxi_gmac_enable_rx(void *iobase, unsigned long rxbase)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ /* Write the base address of Rx descriptor lists into registers */
|
|
+ writel(rxbase, iobase + SUNXI_GMAC_RX_DESC_LIST);
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_RX_CTL1);
|
|
+ value |= SUNXI_GMAC_RX_DMA_EN;
|
|
+ writel(value, iobase + SUNXI_GMAC_RX_CTL1);
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_read_rx_flowctl(void *iobase)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_RX_CTL1);
|
|
+
|
|
+ return value & SUNXI_GMAC_RX_FLOW_CTL;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_read_tx_flowctl(void *iobase)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_TX_FLOW_CTL);
|
|
+
|
|
+ return value & SUNXI_GMAC_TX_FLOW_CTL_BIT;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_write_rx_flowctl(void *iobase, bool flag)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_RX_CTL1);
|
|
+
|
|
+ if (flag)
|
|
+ value |= SUNXI_GMAC_RX_FLOW_CTL;
|
|
+ else
|
|
+ value &= ~SUNXI_GMAC_RX_FLOW_CTL;
|
|
+
|
|
+ writel(value, iobase + SUNXI_GMAC_RX_CTL1);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_write_tx_flowctl(void *iobase, bool flag)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_TX_FLOW_CTL);
|
|
+
|
|
+ if (flag)
|
|
+ value |= SUNXI_GMAC_TX_FLOW_CTL_BIT;
|
|
+ else
|
|
+ value &= ~SUNXI_GMAC_TX_FLOW_CTL_BIT;
|
|
+
|
|
+ writel(value, iobase + SUNXI_GMAC_TX_FLOW_CTL);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_enable_tx - enable gmac tx dma
|
|
+ *
|
|
+ * @iobase: Gmac membase
|
|
+ * @rxbase: Base address of Tx descriptor
|
|
+ */
|
|
+static void sunxi_gmac_enable_tx(void *iobase, unsigned long txbase)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ /* Write the base address of Tx descriptor lists into registers */
|
|
+ writel(txbase, iobase + SUNXI_GMAC_TX_DESC_LIST);
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_TX_CTL1);
|
|
+ value |= SUNXI_GMAC_TX_DMA_EN;
|
|
+ writel(value, iobase + SUNXI_GMAC_TX_CTL1);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_disable_tx - disable gmac tx dma
|
|
+ *
|
|
+ * @iobase: Gmac membase
|
|
+ * @rxbase: Base address of Tx descriptor
|
|
+ */
|
|
+static void sunxi_gmac_disable_tx(void *iobase)
|
|
+{
|
|
+ unsigned int value = readl(iobase + SUNXI_GMAC_TX_CTL1);
|
|
+
|
|
+ value &= ~SUNXI_GMAC_TX_DMA_EN;
|
|
+ writel(value, iobase + SUNXI_GMAC_TX_CTL1);
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_dma_init(void *iobase)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ /* Burst should be 8 */
|
|
+ value = (SUNXI_GMAC_BURST_VALUE << SUNXI_GMAC_BURST_OFFSET);
|
|
+
|
|
+#ifdef CONFIG_SUNXI_GMAC_DA
|
|
+ value |= SUNXI_GMAC_RX_TX_PRI; /* Rx has priority over tx */
|
|
+#endif
|
|
+ writel(value, iobase + SUNXI_GMAC_BASIC_CTL1);
|
|
+
|
|
+ /* Mask interrupts by writing to CSR7 */
|
|
+ writel(SUNXI_GMAC_RX_INT | SUNXI_GMAC_TX_UNF_INT, iobase + SUNXI_GMAC_INT_EN);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_init - init gmac config
|
|
+ *
|
|
+ * @iobase: Gmac membase
|
|
+ * @txmode: tx flow control mode
|
|
+ * @rxmode: rx flow control mode
|
|
+ */
|
|
+static int sunxi_gmac_init(void *iobase, int txmode, int rxmode)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ sunxi_gmac_dma_init(iobase);
|
|
+
|
|
+ /* Initialize the core component */
|
|
+ value = readl(iobase + SUNXI_GMAC_TX_CTL0);
|
|
+ value |= (1 << SUNXI_GMAC_TX_FRM_LEN_OFFSET);
|
|
+ writel(value, iobase + SUNXI_GMAC_TX_CTL0);
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_RX_CTL0);
|
|
+ value |= (1 << SUNXI_GMAC_CRC_OFFSET); /* Enable CRC & IPv4 Header Checksum */
|
|
+ value |= (1 << SUNXI_GMAC_STRIP_FCS_OFFSET); /* Automatic Pad/CRC Stripping */
|
|
+ value |= (1 << SUNXI_GMAC_JUMBO_EN_OFFSET); /* Jumbo Frame Enable */
|
|
+ writel(value, iobase + SUNXI_GMAC_RX_CTL0);
|
|
+
|
|
+ writel((SUNXI_GMAC_MDC_DIV_RATIO_M << SUNXI_GMAC_MDC_DIV_OFFSET),
|
|
+ iobase + SUNXI_GMAC_MDIO_ADDR); /* MDC_DIV_RATIO */
|
|
+
|
|
+ /* Set the Rx&Tx mode */
|
|
+ value = readl(iobase + SUNXI_GMAC_TX_CTL1);
|
|
+ if (txmode == SUNXI_GMAC_SF_DMA_MODE) {
|
|
+ /* Transmit COE type 2 cannot be done in cut-through mode. */
|
|
+ value |= SUNXI_GMAC_TX_MD;
|
|
+ /* Operating on second frame increase the performance
|
|
+ * especially when transmit store-and-forward is used.
|
|
+ */
|
|
+ value |= SUNXI_GMAC_TX_NEXT_FRM;
|
|
+ } else {
|
|
+ value &= ~SUNXI_GMAC_TX_MD;
|
|
+ value &= ~SUNXI_GMAC_TX_TH;
|
|
+ /* Set the transmit threshold */
|
|
+ if (txmode <= SUNXI_GMAC_TX_DMA_TH64)
|
|
+ value |= SUNXI_GMAC_TX_DMA_TH64_VAL;
|
|
+ else if (txmode <= SUNXI_GMAC_TX_DMA_TH128)
|
|
+ value |= SUNXI_GMAC_TX_DMA_TH128_VAL;
|
|
+ else if (txmode <= SUNXI_GMAC_TX_DMA_TH192)
|
|
+ value |= SUNXI_GMAC_TX_DMA_TH192_VAL;
|
|
+ else
|
|
+ value |= SUNXI_GMAC_TX_DMA_TH256_VAL;
|
|
+ }
|
|
+ writel(value, iobase + SUNXI_GMAC_TX_CTL1);
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_RX_CTL1);
|
|
+ if (rxmode == SUNXI_GMAC_SF_DMA_MODE) {
|
|
+ value |= SUNXI_GMAC_RX_MD;
|
|
+ } else {
|
|
+ value &= ~SUNXI_GMAC_RX_MD;
|
|
+ value &= ~SUNXI_GMAC_RX_TH;
|
|
+ if (rxmode <= SUNXI_GMAC_RX_DMA_TH32)
|
|
+ value |= SUNXI_GMAC_RX_DMA_TH32_VAL;
|
|
+ else if (rxmode <= SUNXI_GMAC_RX_DMA_TH64)
|
|
+ value |= SUNXI_GMAC_RX_DMA_TH64_VAL;
|
|
+ else if (rxmode <= SUNXI_GMAC_RX_DMA_TH96)
|
|
+ value |= SUNXI_GMAC_RX_DMA_TH96_VAL;
|
|
+ else
|
|
+ value |= SUNXI_GMAC_RX_DMA_TH128_VAL;
|
|
+ }
|
|
+
|
|
+ /* Forward frames with error and undersized good frame. */
|
|
+ value |= (SUNXI_GMAC_RX_ERR_FRM | SUNXI_GMAC_RX_RUNT_FRM);
|
|
+
|
|
+ writel(value, iobase + SUNXI_GMAC_RX_CTL1);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_hash_filter(void *iobase, unsigned long low, unsigned long high)
|
|
+{
|
|
+ writel(high, iobase + SUNXI_GMAC_RX_HASH0);
|
|
+ writel(low, iobase + SUNXI_GMAC_RX_HASH1);
|
|
+}
|
|
+
|
|
+/* write macaddr into MAC register */
|
|
+static void sunxi_gmac_set_mac_addr_to_reg(void *iobase, unsigned char *addr, int index)
|
|
+{
|
|
+ unsigned long data;
|
|
+
|
|
+ /* one char is 8bit, so splice mac address in steps of 8 */
|
|
+ data = (addr[5] << 8) | addr[4];
|
|
+ writel(data, iobase + SUNXI_GMAC_ADDR_HI(index));
|
|
+ data = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
|
|
+ writel(data, iobase + SUNXI_GMAC_ADDR_LO(index));
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_dma_start(void *iobase)
|
|
+{
|
|
+ unsigned long value;
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_TX_CTL0);
|
|
+ value |= (1 << SUNXI_GMAC_TX_DMA_START);
|
|
+ writel(value, iobase + SUNXI_GMAC_TX_CTL0);
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_RX_CTL0);
|
|
+ value |= (1 << SUNXI_GMAC_RX_DMA_START);
|
|
+ writel(value, iobase + SUNXI_GMAC_RX_CTL0);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_dma_stop(void *iobase)
|
|
+{
|
|
+ unsigned long value;
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_TX_CTL0);
|
|
+ value &= ~(1 << SUNXI_GMAC_TX_DMA_START);
|
|
+ writel(value, iobase + SUNXI_GMAC_TX_CTL0);
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_RX_CTL0);
|
|
+ value &= ~(1 << SUNXI_GMAC_RX_DMA_START);
|
|
+ writel(value, iobase + SUNXI_GMAC_RX_CTL0);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_tx_poll(void *iobase)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ value = readl(iobase + SUNXI_GMAC_TX_CTL1);
|
|
+ writel(value | (1 << SUNXI_GMAC_TX_DMA_START), iobase + SUNXI_GMAC_TX_CTL1);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_irq_enable(void *iobase)
|
|
+{
|
|
+ writel(SUNXI_GMAC_RX_INT | SUNXI_GMAC_TX_UNF_INT, iobase + SUNXI_GMAC_INT_EN);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_irq_disable(void *iobase)
|
|
+{
|
|
+ writel(0, iobase + SUNXI_GMAC_INT_EN);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_desc_buf_set(struct sunxi_gmac_dma_desc *desc, unsigned long paddr, int size)
|
|
+{
|
|
+ desc->desc1.all &= (~((1 << SUNXI_GMAC_DMA_DESC_BUFSIZE) - 1));
|
|
+ desc->desc1.all |= (size & ((1 << SUNXI_GMAC_DMA_DESC_BUFSIZE) - 1));
|
|
+ desc->desc2 = paddr;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_desc_set_own(struct sunxi_gmac_dma_desc *desc)
|
|
+{
|
|
+ desc->desc0.all |= SUNXI_GMAC_OWN_DMA;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_desc_tx_close(struct sunxi_gmac_dma_desc *first, struct sunxi_gmac_dma_desc *end, int csum_insert)
|
|
+{
|
|
+ struct sunxi_gmac_dma_desc *desc = first;
|
|
+
|
|
+ first->desc1.tx.first_sg = 1;
|
|
+ end->desc1.tx.last_seg = 1;
|
|
+ end->desc1.tx.interrupt = 1;
|
|
+
|
|
+ if (csum_insert)
|
|
+ do {
|
|
+ desc->desc1.tx.cic = 3;
|
|
+ desc++;
|
|
+ } while (desc <= end);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_desc_init(struct sunxi_gmac_dma_desc *desc)
|
|
+{
|
|
+ desc->desc1.all = 0;
|
|
+ desc->desc2 = 0;
|
|
+ desc->desc1.all |= (1 << SUNXI_GMAC_CHAIN_MODE_OFFSET);
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_desc_get_tx_status(struct sunxi_gmac_dma_desc *desc, struct sunxi_gmac_extra_stats *x)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ if (desc->desc0.tx.under_err) {
|
|
+ x->tx_underflow++;
|
|
+ ret = -EIO;
|
|
+ }
|
|
+
|
|
+ if (desc->desc0.tx.no_carr) {
|
|
+ x->tx_carrier++;
|
|
+ ret = -EIO;
|
|
+ }
|
|
+
|
|
+ if (desc->desc0.tx.loss_carr) {
|
|
+ x->tx_losscarrier++;
|
|
+ ret = -EIO;
|
|
+ }
|
|
+
|
|
+ if (desc->desc0.tx.deferred) {
|
|
+ x->tx_deferred++;
|
|
+ ret = -EIO;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_desc_buf_get_len(struct sunxi_gmac_dma_desc *desc)
|
|
+{
|
|
+ return (desc->desc1.all & ((1 << SUNXI_GMAC_DMA_DESC_BUFSIZE) - 1));
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_desc_buf_get_addr(struct sunxi_gmac_dma_desc *desc)
|
|
+{
|
|
+ return desc->desc2;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_desc_rx_frame_len(struct sunxi_gmac_dma_desc *desc)
|
|
+{
|
|
+ return desc->desc0.rx.frm_len;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_desc_llc_snap(struct sunxi_gmac_dma_desc *desc)
|
|
+{
|
|
+ /* Splice flags as follow:
|
|
+ * bits 5 7 0 | Frame status
|
|
+ * ----------------------------------------------------------
|
|
+ * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octects)
|
|
+ * 1 0 0 | IPv4/6 No CSUM errorS.
|
|
+ * 1 0 1 | IPv4/6 CSUM PAYLOAD error
|
|
+ * 1 1 0 | IPv4/6 CSUM IP HR error
|
|
+ * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS
|
|
+ * 0 0 1 | IPv4/6 unsupported IP PAYLOAD
|
|
+ * 0 1 1 | COE bypassed.. no IPv4/6 frame
|
|
+ * 0 1 0 | Reserved.
|
|
+ */
|
|
+ return ((desc->desc0.rx.frm_type << 2 |
|
|
+ desc->desc0.rx.ipch_err << 1 |
|
|
+ desc->desc0.rx.chsum_err) & 0x7);
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_desc_get_rx_status(struct sunxi_gmac_dma_desc *desc, struct sunxi_gmac_extra_stats *x)
|
|
+{
|
|
+ int ret = good_frame;
|
|
+
|
|
+ if (desc->desc0.rx.last_desc == 0)
|
|
+ return incomplete_frame;
|
|
+
|
|
+ if (desc->desc0.rx.err_sum) {
|
|
+ if (desc->desc0.rx.desc_err)
|
|
+ x->rx_desc++;
|
|
+
|
|
+ if (desc->desc0.rx.sou_filter)
|
|
+ x->sa_filter_fail++;
|
|
+
|
|
+ if (desc->desc0.rx.over_err)
|
|
+ x->overflow_error++;
|
|
+
|
|
+ if (desc->desc0.rx.ipch_err)
|
|
+ x->ipc_csum_error++;
|
|
+
|
|
+ if (desc->desc0.rx.late_coll)
|
|
+ x->rx_collision++;
|
|
+
|
|
+ if (desc->desc0.rx.crc_err)
|
|
+ x->rx_crc++;
|
|
+
|
|
+ ret = discard_frame;
|
|
+ }
|
|
+
|
|
+ if (desc->desc0.rx.len_err)
|
|
+ ret = discard_frame;
|
|
+
|
|
+ if (desc->desc0.rx.mii_err)
|
|
+ ret = discard_frame;
|
|
+
|
|
+ if (ret == good_frame) {
|
|
+ if (sunxi_gmac_desc_llc_snap(desc) == 0)
|
|
+ ret = llc_snap;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_desc_get_own(struct sunxi_gmac_dma_desc *desc)
|
|
+{
|
|
+ return desc->desc0.all & SUNXI_GMAC_OWN_DMA;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_desc_get_tx_last_seg(struct sunxi_gmac_dma_desc *desc)
|
|
+{
|
|
+ return desc->desc1.tx.last_seg;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_reset(void *iobase, int n)
|
|
+{
|
|
+ unsigned int value;
|
|
+
|
|
+ /* gmac software reset */
|
|
+ value = readl(iobase + SUNXI_GMAC_BASIC_CTL1);
|
|
+ value |= SUNXI_GMAC_SOFT_RST;
|
|
+ writel(value, iobase + SUNXI_GMAC_BASIC_CTL1);
|
|
+
|
|
+ udelay(n);
|
|
+
|
|
+ return !!(readl(iobase + SUNXI_GMAC_BASIC_CTL1) & SUNXI_GMAC_SOFT_RST);
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_stop(struct net_device *ndev);
|
|
+
|
|
+static void sunxi_gmac_dump_dma_desc(struct sunxi_gmac_dma_desc *desc, int size)
|
|
+{
|
|
+#ifdef DEBUG
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < size; i++) {
|
|
+ u32 *x = (u32 *)(desc + i);
|
|
+
|
|
+ pr_info("\t%d [0x%08lx]: %08x %08x %08x %08x\n",
|
|
+ i, (unsigned long)(&desc[i]),
|
|
+ x[0], x[1], x[2], x[3]);
|
|
+ }
|
|
+ pr_info("\n");
|
|
+#endif
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_gmac_extra_tx_stats_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ return sprintf(buf, "tx_underflow: %lu\ntx_carrier: %lu\n"
|
|
+ "tx_losscarrier: %lu\nvlan_tag: %lu\n"
|
|
+ "tx_deferred: %lu\ntx_vlan: %lu\n"
|
|
+ "tx_jabber: %lu\ntx_frame_flushed: %lu\n"
|
|
+ "tx_payload_error: %lu\ntx_ip_header_error: %lu\n\n",
|
|
+ chip->xstats.tx_underflow, chip->xstats.tx_carrier,
|
|
+ chip->xstats.tx_losscarrier, chip->xstats.vlan_tag,
|
|
+ chip->xstats.tx_deferred, chip->xstats.tx_vlan,
|
|
+ chip->xstats.tx_jabber, chip->xstats.tx_frame_flushed,
|
|
+ chip->xstats.tx_payload_error, chip->xstats.tx_ip_header_error);
|
|
+}
|
|
+/* eg: cat extra_tx_stats */
|
|
+static DEVICE_ATTR(extra_tx_stats, 0444, sunxi_gmac_extra_tx_stats_show, NULL);
|
|
+
|
|
+static ssize_t sunxi_gmac_extra_rx_stats_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ return sprintf(buf, "rx_desc: %lu\nsa_filter_fail: %lu\n"
|
|
+ "overflow_error: %lu\nipc_csum_error: %lu\n"
|
|
+ "rx_collision: %lu\nrx_crc: %lu\n"
|
|
+ "dribbling_bit: %lu\nrx_length: %lu\n"
|
|
+ "rx_mii: %lu\nrx_multicast: %lu\n"
|
|
+ "rx_gmac_overflow: %lu\nrx_watchdog: %lu\n"
|
|
+ "da_rx_filter_fail: %lu\nsa_rx_filter_fail: %lu\n"
|
|
+ "rx_missed_cntr: %lu\nrx_overflow_cntr: %lu\n"
|
|
+ "rx_vlan: %lu\n\n",
|
|
+ chip->xstats.rx_desc, chip->xstats.sa_filter_fail,
|
|
+ chip->xstats.overflow_error, chip->xstats.ipc_csum_error,
|
|
+ chip->xstats.rx_collision, chip->xstats.rx_crc,
|
|
+ chip->xstats.dribbling_bit, chip->xstats.rx_length,
|
|
+ chip->xstats.rx_mii, chip->xstats.rx_multicast,
|
|
+ chip->xstats.rx_gmac_overflow, chip->xstats.rx_length,
|
|
+ chip->xstats.da_rx_filter_fail, chip->xstats.sa_rx_filter_fail,
|
|
+ chip->xstats.rx_missed_cntr, chip->xstats.rx_overflow_cntr,
|
|
+ chip->xstats.rx_vlan);
|
|
+}
|
|
+/* eg: cat extra_rx_stats */
|
|
+static DEVICE_ATTR(extra_rx_stats, 0444, sunxi_gmac_extra_rx_stats_show, NULL);
|
|
+
|
|
+static ssize_t sunxi_gmac_gphy_test_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return sprintf(buf, "Usage:\necho [0/1/2/3/4] > gphy_test\n"
|
|
+ "0 - Normal Mode\n"
|
|
+ "1 - Transmit Jitter Test\n"
|
|
+ "2 - Transmit Jitter Test(MASTER mode)\n"
|
|
+ "3 - Transmit Jitter Test(SLAVE mode)\n"
|
|
+ "4 - Transmit Distortion Test\n\n");
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_gmac_gphy_test_store(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ u16 value, phyreg_val;
|
|
+ int ret;
|
|
+
|
|
+ phyreg_val = phy_read(ndev->phydev, MII_CTRL1000);
|
|
+
|
|
+ ret = kstrtou16(buf, 0, &value);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (value >= 0 && value <= 4) {
|
|
+ phyreg_val &= ~(SUNXI_GMAC_GPHY_TEST_MASK <<
|
|
+ SUNXI_GMAC_GPHY_TEST_OFFSET);
|
|
+ phyreg_val |= value << SUNXI_GMAC_GPHY_TEST_OFFSET;
|
|
+ phy_write(ndev->phydev, MII_CTRL1000, phyreg_val);
|
|
+ netdev_info(ndev, "Set MII_CTRL1000(0x09) Reg: 0x%x\n", phyreg_val);
|
|
+ } else {
|
|
+ netdev_err(ndev, "Error: Unknown value (%d)\n", value);
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+/* eg: echo 0 > gphy_test */
|
|
+static DEVICE_ATTR(gphy_test, 0664, sunxi_gmac_gphy_test_show, sunxi_gmac_gphy_test_store);
|
|
+
|
|
+static ssize_t sunxi_gmac_mii_read_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return sprintf(buf, "Usage:\necho PHYREG > mii_read\n");
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_gmac_mii_read_store(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ u16 phyreg, phyreg_val;
|
|
+ int ret;
|
|
+
|
|
+ if (!netif_running(ndev)) {
|
|
+ netdev_err(ndev, "Error: Nic is down\n");
|
|
+ return count;
|
|
+ }
|
|
+
|
|
+ ret = kstrtou16(buf, 0, &phyreg);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ phyreg_val = phy_read(ndev->phydev, phyreg);
|
|
+ netdev_info(ndev, "PHYREG[0x%02x] = 0x%04x\n", phyreg, phyreg_val);
|
|
+ return count;
|
|
+}
|
|
+/* eg: echo 0x00 > mii_read; cat mii_read */
|
|
+static DEVICE_ATTR(mii_read, 0664, sunxi_gmac_mii_read_show, sunxi_gmac_mii_read_store);
|
|
+
|
|
+static ssize_t sunxi_gmac_mii_write_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ return sprintf(buf, "Usage:\necho PHYREG PHYVAL > mii_write\n");
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_gmac_mii_write_store(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ u16 phyreg_val_before, phyreg_val_after;
|
|
+ int i, ret;
|
|
+ /* userspace_cmd[0]: phyreg
|
|
+ * userspace_cmd[1]: phyval
|
|
+ */
|
|
+ u16 userspace_cmd[2] = {0};
|
|
+ char *ptr1 = (char *)buf;
|
|
+ char *ptr2;
|
|
+
|
|
+ if (!netif_running(ndev)) {
|
|
+ netdev_err(ndev, "Error: Nic is down\n");
|
|
+ return count;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(userspace_cmd); i++) {
|
|
+ ptr1 = skip_spaces(ptr1);
|
|
+ ptr2 = strchr(ptr1, ' ');
|
|
+ if (ptr2)
|
|
+ *ptr2 = '\0';
|
|
+
|
|
+ ret = kstrtou16(ptr1, 16, &userspace_cmd[i]);
|
|
+ if (!ptr2 || ret)
|
|
+ break;
|
|
+
|
|
+ ptr1 = ptr2 + 1;
|
|
+ }
|
|
+
|
|
+ phyreg_val_before = phy_read(ndev->phydev, userspace_cmd[0]);
|
|
+ phy_write(ndev->phydev, userspace_cmd[0], userspace_cmd[1]);
|
|
+ phyreg_val_after = phy_read(ndev->phydev, userspace_cmd[0]);
|
|
+ netdev_info(ndev, "before PHYREG[0x%02x] = 0x%04x, after PHYREG[0x%02x] = 0x%04x\n",
|
|
+ userspace_cmd[0], phyreg_val_before, userspace_cmd[0], phyreg_val_after);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+/* eg: echo 0x00 0x1234 > mii_write; cat mii_write */
|
|
+static DEVICE_ATTR(mii_write, 0664, sunxi_gmac_mii_write_show, sunxi_gmac_mii_write_store);
|
|
+
|
|
+static ssize_t sunxi_gmac_loopback_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ int macreg_val;
|
|
+ u16 phyreg_val;
|
|
+
|
|
+ phyreg_val = phy_read(ndev->phydev, MII_BMCR);
|
|
+ if (phyreg_val & BMCR_LOOPBACK)
|
|
+ netdev_dbg(ndev, "Phy loopback enabled\n");
|
|
+ else
|
|
+ netdev_dbg(ndev, "Phy loopback disabled\n");
|
|
+
|
|
+ macreg_val = readl(chip->base);
|
|
+ if (macreg_val & SUNXI_GMAC_LOOPBACK)
|
|
+ netdev_dbg(ndev, "Mac loopback enabled\n");
|
|
+ else
|
|
+ netdev_dbg(ndev, "Mac loopback disabled\n");
|
|
+
|
|
+ return sprintf(buf, "Usage:\necho [0/1/2] > loopback\n"
|
|
+ "0 - Loopback off\n"
|
|
+ "1 - Mac loopback mode\n"
|
|
+ "2 - Phy loopback mode\n");
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_gmac_loopback_store(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ int phyreg_val, ret;
|
|
+ u16 mode;
|
|
+
|
|
+ if (!netif_running(ndev)) {
|
|
+ netdev_err(ndev, "Error: eth is down\n");
|
|
+ return count;
|
|
+ }
|
|
+
|
|
+ ret = kstrtou16(buf, 0, &mode);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ switch (mode) {
|
|
+ case SUNXI_GMAC_LOOPBACK_OFF:
|
|
+ sunxi_gmac_loopback(chip->base, 0);
|
|
+ phyreg_val = phy_read(ndev->phydev, MII_BMCR);
|
|
+ phy_write(ndev->phydev, MII_BMCR, phyreg_val & ~BMCR_LOOPBACK);
|
|
+ break;
|
|
+ case SUNXI_GMAC_MAC_LOOPBACK_ON:
|
|
+ phyreg_val = phy_read(ndev->phydev, MII_BMCR);
|
|
+ phy_write(ndev->phydev, MII_BMCR, phyreg_val & ~BMCR_LOOPBACK);
|
|
+ sunxi_gmac_loopback(chip->base, 1);
|
|
+ break;
|
|
+ case SUNXI_GMAC_PHY_LOOPBACK_ON:
|
|
+ sunxi_gmac_loopback(chip->base, 0);
|
|
+ phyreg_val = phy_read(ndev->phydev, MII_BMCR);
|
|
+ phy_write(ndev->phydev, MII_BMCR, phyreg_val | BMCR_LOOPBACK);
|
|
+ break;
|
|
+ default:
|
|
+ netdev_err(ndev, "Error: Please echo right value\n");
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+/* eg: echo 1 > loopback */
|
|
+static DEVICE_ATTR(loopback, 0664, sunxi_gmac_loopback_show, sunxi_gmac_loopback_store);
|
|
+
|
|
+static ssize_t sunxi_gmac_tx_delay_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ u32 reg_val;
|
|
+ u16 tx_delay;
|
|
+
|
|
+ reg_val = readl(chip->syscfg_base);
|
|
+ tx_delay = (reg_val >> SUNXI_GMAC_TX_DELAY_OFFSET) & SUNXI_GMAC_TX_DELAY_MASK;
|
|
+
|
|
+ return sprintf(buf, "Usage:\necho [0~7] > tx_delay\n"
|
|
+ "\nnow tx_delay: %d\n", tx_delay);
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_gmac_tx_delay_store(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ int ret;
|
|
+ u32 reg_val;
|
|
+ u16 tx_delay;
|
|
+
|
|
+ if (!netif_running(ndev)) {
|
|
+ netdev_err(ndev, "Error: eth is down\n");
|
|
+ return count;
|
|
+ }
|
|
+
|
|
+ ret = kstrtou16(buf, 0, &tx_delay);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (tx_delay > 7)
|
|
+ return -EINVAL;
|
|
+
|
|
+ reg_val = readl(chip->syscfg_base);
|
|
+ reg_val &= ~(SUNXI_GMAC_TX_DELAY_MASK << SUNXI_GMAC_TX_DELAY_OFFSET);
|
|
+ reg_val |= ((tx_delay & SUNXI_GMAC_TX_DELAY_MASK) << SUNXI_GMAC_TX_DELAY_OFFSET);
|
|
+ writel(reg_val, chip->syscfg_base);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+/* eg: echo 1 > tx_delay */
|
|
+static DEVICE_ATTR(tx_delay, 0664, sunxi_gmac_tx_delay_show, sunxi_gmac_tx_delay_store);
|
|
+
|
|
+static ssize_t sunxi_gmac_rx_delay_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ u32 reg_val;
|
|
+ u16 rx_delay;
|
|
+
|
|
+ reg_val = readl(chip->syscfg_base);
|
|
+ rx_delay = (reg_val >> SUNXI_GMAC_RX_DELAY_OFFSET) & SUNXI_GMAC_RX_DELAY_MASK;
|
|
+
|
|
+ return sprintf(buf, "Usage:\necho [0~31] > rx_delay\n"
|
|
+ "\nnow rx_delay: %d\n", rx_delay);
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_gmac_rx_delay_store(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ int ret;
|
|
+ u32 reg_val;
|
|
+ u16 rx_delay;
|
|
+
|
|
+ if (!netif_running(ndev)) {
|
|
+ netdev_err(ndev, "Error: eth is down\n");
|
|
+ return count;
|
|
+ }
|
|
+
|
|
+ ret = kstrtou16(buf, 0, &rx_delay);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ if (rx_delay > 31)
|
|
+ return -EINVAL;
|
|
+
|
|
+ reg_val = readl(chip->syscfg_base);
|
|
+ reg_val &= ~(SUNXI_GMAC_RX_DELAY_MASK << SUNXI_GMAC_RX_DELAY_OFFSET);
|
|
+ reg_val |= ((rx_delay & SUNXI_GMAC_RX_DELAY_MASK) << SUNXI_GMAC_RX_DELAY_OFFSET);
|
|
+ writel(reg_val, chip->syscfg_base);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+/* eg: echo 1 > rx_delay */
|
|
+static DEVICE_ATTR(rx_delay, 0664, sunxi_gmac_rx_delay_show, sunxi_gmac_rx_delay_store);
|
|
+
|
|
+/* In phy state machine, we use this func to change link status */
|
|
+static void sunxi_gmac_adjust_link(struct net_device *ndev)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ struct phy_device *phydev = ndev->phydev;
|
|
+ unsigned long flags;
|
|
+ int new_state = 0;
|
|
+
|
|
+ if (!phydev)
|
|
+ return;
|
|
+
|
|
+ spin_lock_irqsave(&chip->universal_lock, flags);
|
|
+ if (phydev->link) {
|
|
+ /* Now we make sure that we can be in full duplex mode.
|
|
+ * If not, we operate in half-duplex mode.
|
|
+ */
|
|
+ if (phydev->duplex != chip->duplex) {
|
|
+ new_state = 1;
|
|
+ chip->duplex = phydev->duplex;
|
|
+ }
|
|
+ /* Flow Control operation */
|
|
+ if (phydev->pause)
|
|
+ sunxi_gmac_flow_ctrl(chip->base, phydev->duplex,
|
|
+ flow_ctrl, pause);
|
|
+
|
|
+ if (phydev->speed != chip->speed) {
|
|
+ new_state = 1;
|
|
+ chip->speed = phydev->speed;
|
|
+ }
|
|
+
|
|
+ if (chip->link == 0) {
|
|
+ new_state = 1;
|
|
+ chip->link = phydev->link;
|
|
+ }
|
|
+
|
|
+ if (new_state)
|
|
+ sunxi_gmac_set_link_mode(chip->base, chip->duplex, chip->speed);
|
|
+
|
|
+ } else if (chip->link != phydev->link) {
|
|
+ new_state = 1;
|
|
+ chip->link = 0;
|
|
+ chip->speed = 0;
|
|
+ chip->duplex = -1;
|
|
+ }
|
|
+ spin_unlock_irqrestore(&chip->universal_lock, flags);
|
|
+
|
|
+ if (new_state)
|
|
+ phy_print_status(phydev);
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_phy_release(struct net_device *ndev)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ struct phy_device *phydev = ndev->phydev;
|
|
+ int value;
|
|
+
|
|
+ /* Stop and disconnect the PHY */
|
|
+ if (phydev)
|
|
+ phy_stop(phydev);
|
|
+
|
|
+ chip->link = PHY_DOWN;
|
|
+ chip->speed = 0;
|
|
+ chip->duplex = -1;
|
|
+
|
|
+ if (phydev) {
|
|
+ value = phy_read(phydev, MII_BMCR);
|
|
+ phy_write(phydev, MII_BMCR, (value | BMCR_PDOWN));
|
|
+ }
|
|
+
|
|
+ if (phydev) {
|
|
+ phy_disconnect(phydev);
|
|
+ ndev->phydev = NULL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Refill rx dma descriptor after using */
|
|
+static void sunxi_gmac_rx_refill(struct net_device *ndev)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ struct sunxi_gmac_dma_desc *desc;
|
|
+ struct sk_buff *skb = NULL;
|
|
+ dma_addr_t dma_addr;
|
|
+
|
|
+ while (circ_space(chip->rx_clean, chip->rx_dirty, sunxi_gmac_dma_desc_rx) > 0) {
|
|
+ int entry = chip->rx_clean;
|
|
+
|
|
+ /* Find the dirty's desc and clean it */
|
|
+ desc = chip->dma_rx + entry;
|
|
+
|
|
+ if (chip->rx_skb[entry] == NULL) {
|
|
+ skb = netdev_alloc_skb_ip_align(ndev, chip->buf_sz);
|
|
+
|
|
+ if (unlikely(skb == NULL))
|
|
+ break;
|
|
+
|
|
+ chip->rx_skb[entry] = skb;
|
|
+ dma_addr = dma_map_single(chip->dev, skb->data,
|
|
+ chip->buf_sz, DMA_FROM_DEVICE);
|
|
+
|
|
+ sunxi_gmac_desc_buf_set(desc, dma_addr, chip->buf_sz);
|
|
+ }
|
|
+
|
|
+ dma_wmb();
|
|
+ sunxi_gmac_desc_set_own(desc);
|
|
+ chip->rx_clean = circ_inc(chip->rx_clean, sunxi_gmac_dma_desc_rx);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * sunxi_gmac_dma_desc_init - initialize the RX/TX descriptor list
|
|
+ *
|
|
+ * @ndev: net device structure
|
|
+ * Description: initialize the list for dma.
|
|
+ */
|
|
+static int sunxi_gmac_dma_desc_init(struct net_device *ndev)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ struct device *dev = &ndev->dev;
|
|
+
|
|
+ chip->rx_skb = devm_kzalloc(dev, sizeof(chip->rx_skb[0]) * sunxi_gmac_dma_desc_rx,
|
|
+ GFP_KERNEL);
|
|
+ if (!chip->rx_skb) {
|
|
+ netdev_err(ndev, "Error: Alloc rx_skb failed\n");
|
|
+ goto rx_skb_err;
|
|
+ }
|
|
+ chip->tx_skb = devm_kzalloc(dev, sizeof(chip->tx_skb[0]) * sunxi_gmac_dma_desc_tx,
|
|
+ GFP_KERNEL);
|
|
+ if (!chip->tx_skb) {
|
|
+ netdev_err(ndev, "Error: Alloc tx_skb failed\n");
|
|
+ goto tx_skb_err;
|
|
+ }
|
|
+
|
|
+ chip->dma_tx = dma_alloc_coherent(chip->dev,
|
|
+ sunxi_gmac_dma_desc_tx *
|
|
+ sizeof(struct sunxi_gmac_dma_desc),
|
|
+ &chip->dma_tx_phy,
|
|
+ GFP_KERNEL);
|
|
+ if (!chip->dma_tx) {
|
|
+ netdev_err(ndev, "Error: Alloc dma_tx failed\n");
|
|
+ goto dma_tx_err;
|
|
+ }
|
|
+
|
|
+ chip->dma_rx = dma_alloc_coherent(chip->dev,
|
|
+ sunxi_gmac_dma_desc_rx *
|
|
+ sizeof(struct sunxi_gmac_dma_desc),
|
|
+ &chip->dma_rx_phy,
|
|
+ GFP_KERNEL);
|
|
+ if (!chip->dma_rx) {
|
|
+ netdev_err(ndev, "Error: Alloc dma_rx failed\n");
|
|
+ goto dma_rx_err;
|
|
+ }
|
|
+
|
|
+ /* Set the size of buffer depend on the MTU & max buf size */
|
|
+ chip->buf_sz = SUNXI_GMAC_MAX_BUF_SZ;
|
|
+ return 0;
|
|
+
|
|
+dma_rx_err:
|
|
+ dma_free_coherent(chip->dev, sunxi_gmac_dma_desc_rx * sizeof(struct sunxi_gmac_dma_desc),
|
|
+ chip->dma_tx, chip->dma_tx_phy);
|
|
+dma_tx_err:
|
|
+ kfree(chip->tx_skb);
|
|
+tx_skb_err:
|
|
+ kfree(chip->rx_skb);
|
|
+rx_skb_err:
|
|
+ return -ENOMEM;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_free_rx_skb(struct sunxi_gmac *chip)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < sunxi_gmac_dma_desc_rx; i++) {
|
|
+ if (chip->rx_skb[i] != NULL) {
|
|
+ struct sunxi_gmac_dma_desc *desc = chip->dma_rx + i;
|
|
+
|
|
+ dma_unmap_single(chip->dev, (u32)sunxi_gmac_desc_buf_get_addr(desc),
|
|
+ sunxi_gmac_desc_buf_get_len(desc),
|
|
+ DMA_FROM_DEVICE);
|
|
+ dev_kfree_skb_any(chip->rx_skb[i]);
|
|
+ chip->rx_skb[i] = NULL;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_free_tx_skb(struct sunxi_gmac *chip)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < sunxi_gmac_dma_desc_tx; i++) {
|
|
+ if (chip->tx_skb[i] != NULL) {
|
|
+ struct sunxi_gmac_dma_desc *desc = chip->dma_tx + i;
|
|
+
|
|
+ if (sunxi_gmac_desc_buf_get_addr(desc))
|
|
+ dma_unmap_single(chip->dev, (u32)sunxi_gmac_desc_buf_get_addr(desc),
|
|
+ sunxi_gmac_desc_buf_get_len(desc),
|
|
+ DMA_TO_DEVICE);
|
|
+ dev_kfree_skb_any(chip->tx_skb[i]);
|
|
+ chip->tx_skb[i] = NULL;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_dma_desc_deinit(struct sunxi_gmac *chip)
|
|
+{
|
|
+ /* Free the region of consistent memory previously allocated for the DMA */
|
|
+ dma_free_coherent(chip->dev, sunxi_gmac_dma_desc_tx * sizeof(struct sunxi_gmac_dma_desc),
|
|
+ chip->dma_tx, chip->dma_tx_phy);
|
|
+ dma_free_coherent(chip->dev, sunxi_gmac_dma_desc_rx * sizeof(struct sunxi_gmac_dma_desc),
|
|
+ chip->dma_rx, chip->dma_rx_phy);
|
|
+
|
|
+ kfree(chip->rx_skb);
|
|
+ kfree(chip->tx_skb);
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_stop(struct net_device *ndev)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ netif_stop_queue(ndev);
|
|
+ napi_disable(&chip->napi);
|
|
+
|
|
+ netif_carrier_off(ndev);
|
|
+
|
|
+ sunxi_gmac_phy_release(ndev);
|
|
+
|
|
+ sunxi_gmac_dma_stop(chip->base);
|
|
+
|
|
+ netif_tx_lock_bh(ndev);
|
|
+ /* Release the DMA TX/RX socket buffers */
|
|
+ sunxi_gmac_free_rx_skb(chip);
|
|
+ sunxi_gmac_free_tx_skb(chip);
|
|
+ netif_tx_unlock_bh(ndev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_power_on(struct sunxi_gmac *chip)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ if (IS_ERR_OR_NULL(chip->gmac_supply))
|
|
+ return 0;
|
|
+
|
|
+ ret = regulator_set_voltage(chip->gmac_supply, 3300000, 3300000);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "gmac-power set voltage error\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = regulator_enable(chip->gmac_supply);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "Error: enable gmac-power failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_power_off(struct sunxi_gmac *chip)
|
|
+{
|
|
+ if (IS_ERR_OR_NULL(chip->gmac_supply))
|
|
+ return;
|
|
+
|
|
+ regulator_disable(chip->gmac_supply);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_open - GMAC device open
|
|
+ * @ndev: The Allwinner GMAC network adapter
|
|
+ *
|
|
+ * Called when system wants to start the interface. We init TX/RX channels
|
|
+ * and enable the hardware for packet reception/transmission and start the
|
|
+ * network queue.
|
|
+ *
|
|
+ * Returns 0 for a successful open, or appropriate error code
|
|
+ */
|
|
+static int sunxi_gmac_open(struct net_device *ndev)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * When changing the configuration of GMAC and PHY,
|
|
+ * it is necessary to turn off the carrier on the link.
|
|
+ */
|
|
+ netif_carrier_off(ndev);
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+ if (chip->ac300_np) {
|
|
+ chip->ac300_dev = of_phy_find_device(chip->ac300_np);
|
|
+ if (!chip->ac300_dev) {
|
|
+ netdev_err(ndev, "Error: Could not find ac300 %s\n",
|
|
+ chip->ac300_np->full_name);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ phy_init_hw(chip->ac300_dev);
|
|
+ }
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+
|
|
+ if (chip->phy_node) {
|
|
+ ndev->phydev = of_phy_connect(ndev, chip->phy_node,
|
|
+ &sunxi_gmac_adjust_link, 0, chip->phy_interface);
|
|
+ if (!ndev->phydev) {
|
|
+ netdev_err(ndev, "Error: Could not connect to phy %s\n",
|
|
+ chip->phy_node->full_name);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ netdev_info(ndev, "%s: Type(%d) PHY ID %08x at %d IRQ %s (%s)\n",
|
|
+ ndev->name, ndev->phydev->interface, ndev->phydev->phy_id,
|
|
+ ndev->phydev->mdio.addr, "poll", dev_name(&ndev->phydev->mdio.dev));
|
|
+ }
|
|
+ ret = sunxi_gmac_reset((void *)chip->base, 1000);
|
|
+ if (ret) {
|
|
+ netdev_err(ndev, "Error: Mac reset failed, please check phy and mac clk\n");
|
|
+ goto mac_reset_err;
|
|
+ }
|
|
+
|
|
+ sunxi_gmac_init(chip->base, txmode, rxmode);
|
|
+ sunxi_gmac_set_mac_addr_to_reg(chip->base, (unsigned char *)ndev->dev_addr, 0);
|
|
+
|
|
+ memset(chip->dma_tx, 0, sunxi_gmac_dma_desc_tx * sizeof(struct sunxi_gmac_dma_desc));
|
|
+ memset(chip->dma_rx, 0, sunxi_gmac_dma_desc_rx * sizeof(struct sunxi_gmac_dma_desc));
|
|
+
|
|
+ sunxi_gmac_desc_init_chain(chip->dma_rx, (unsigned long)chip->dma_rx_phy, sunxi_gmac_dma_desc_rx);
|
|
+ sunxi_gmac_desc_init_chain(chip->dma_tx, (unsigned long)chip->dma_tx_phy, sunxi_gmac_dma_desc_tx);
|
|
+
|
|
+ chip->rx_clean = 0;
|
|
+ chip->rx_dirty = 0;
|
|
+ chip->tx_clean = 0;
|
|
+ chip->tx_dirty = 0;
|
|
+ sunxi_gmac_rx_refill(ndev);
|
|
+
|
|
+ /* Extra statistics */
|
|
+ memset(&chip->xstats, 0, sizeof(struct sunxi_gmac_extra_stats));
|
|
+
|
|
+ if (ndev->phydev)
|
|
+ phy_start(ndev->phydev);
|
|
+
|
|
+ sunxi_gmac_enable_rx(chip->base, (unsigned long)((struct sunxi_gmac_dma_desc *)
|
|
+ chip->dma_rx_phy + chip->rx_dirty));
|
|
+ sunxi_gmac_enable_tx(chip->base, (unsigned long)((struct sunxi_gmac_dma_desc *)
|
|
+ chip->dma_tx_phy + chip->tx_clean));
|
|
+
|
|
+ napi_enable(&chip->napi);
|
|
+ netif_start_queue(ndev);
|
|
+
|
|
+ /* Start the Rx/Tx */
|
|
+ sunxi_gmac_dma_start(chip->base);
|
|
+
|
|
+ /* Indicate that the MAC is responsible for managing PHY PM */
|
|
+ ndev->phydev->mac_managed_pm = true;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+mac_reset_err:
|
|
+ phy_disconnect(ndev->phydev);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_clk_enable(struct sunxi_gmac *chip);
|
|
+static void sunxi_gmac_clk_disable(struct sunxi_gmac *chip);
|
|
+
|
|
+#if IS_ENABLED(CONFIG_PM)
|
|
+static int sunxi_gmac_select_gpio_state(struct pinctrl *pctrl, char *name)
|
|
+{
|
|
+ int ret;
|
|
+ struct pinctrl_state *pctrl_state;
|
|
+
|
|
+ pctrl_state = pinctrl_lookup_state(pctrl, name);
|
|
+ if (IS_ERR(pctrl_state)) {
|
|
+ pr_err("gmac pinctrl_lookup_state(%s) failed! return %p\n",
|
|
+ name, pctrl_state);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = pinctrl_select_state(pctrl, pctrl_state);
|
|
+ if (ret < 0)
|
|
+ pr_err("gmac pinctrl_select_state(%s) failed! return %d\n",
|
|
+ name, ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_resume(struct device *dev)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ if (!netif_running(ndev))
|
|
+ return 0;
|
|
+
|
|
+ sunxi_gmac_power_on(chip);
|
|
+
|
|
+ sunxi_gmac_clk_enable(chip);
|
|
+
|
|
+ sunxi_gmac_select_gpio_state(chip->pinctrl, PINCTRL_STATE_DEFAULT);
|
|
+
|
|
+ netif_device_attach(ndev);
|
|
+
|
|
+ sunxi_gmac_open(ndev);
|
|
+
|
|
+ /* suspend error workaround */
|
|
+ if (ndev->phydev)
|
|
+ dev_set_uevent_suppress(&ndev->phydev->mdio.dev, chip->gmac_uevent_suppress);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_suspend(struct device *dev)
|
|
+{
|
|
+ struct net_device *ndev = dev_get_drvdata(dev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ if (!ndev || !netif_running(ndev))
|
|
+ return 0;
|
|
+
|
|
+ /* suspend error workaround */
|
|
+ if (ndev->phydev) {
|
|
+ chip->gmac_uevent_suppress = dev_get_uevent_suppress(&ndev->phydev->mdio.dev);
|
|
+ dev_set_uevent_suppress(&ndev->phydev->mdio.dev, true);
|
|
+ }
|
|
+
|
|
+ netif_device_detach(ndev);
|
|
+
|
|
+ sunxi_gmac_stop(ndev);
|
|
+
|
|
+ sunxi_gmac_select_gpio_state(chip->pinctrl, PINCTRL_STATE_SLEEP);
|
|
+
|
|
+ sunxi_gmac_power_off(chip);
|
|
+
|
|
+ sunxi_gmac_clk_disable(chip);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct dev_pm_ops sunxi_gmac_pm_ops = {
|
|
+ .suspend = sunxi_gmac_suspend,
|
|
+ .resume = sunxi_gmac_resume,
|
|
+};
|
|
+#else
|
|
+static const struct dev_pm_ops sunxi_gmac_pm_ops;
|
|
+#endif /* CONFIG_PM */
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+static void sunxi_gmac_shutdown(struct platform_device *pdev)
|
|
+{
|
|
+ struct net_device *ndev = platform_get_drvdata(pdev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ if (chip->ac300_dev)
|
|
+ phy_detach(chip->ac300_dev);
|
|
+}
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+
|
|
+static void sunxi_gmac_check_addr(struct net_device *ndev, unsigned char *mac)
|
|
+{
|
|
+ int i;
|
|
+ char *p = mac;
|
|
+ unsigned char tmp_addr[6];
|
|
+
|
|
+ if (!is_valid_ether_addr(ndev->dev_addr)) {
|
|
+ for (i = 0; i < ETH_ALEN; i++, p++) {
|
|
+ tmp_addr[i] = simple_strtoul(p, &p, 16);
|
|
+ dev_addr_mod(ndev, i, &tmp_addr[i], sizeof(char));
|
|
+ }
|
|
+
|
|
+ if (!is_valid_ether_addr(ndev->dev_addr)) {
|
|
+ eth_random_addr((u8 *)ndev->dev_addr);
|
|
+ netdev_info(ndev, "Info: Use random mac address\n");
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_clk_enable(struct sunxi_gmac *chip)
|
|
+{
|
|
+ struct net_device *ndev = chip->ndev;
|
|
+ int ret;
|
|
+ u32 clk_value;
|
|
+
|
|
+ ret = reset_control_deassert(chip->reset);
|
|
+ if (ret) {
|
|
+ netdev_err(ndev, "Error: Try to de-assert gmac rst failed\n");
|
|
+ goto gmac_reset_err;
|
|
+ }
|
|
+
|
|
+ ret = clk_prepare_enable(chip->gmac_clk);
|
|
+ if (ret) {
|
|
+ netdev_err(ndev, "Error: Try to enable gmac_clk failed\n");
|
|
+ goto gmac_clk_err;
|
|
+ }
|
|
+
|
|
+ if (chip->phy_clk_type == SUNXI_PHY_USE_CLK25M) {
|
|
+ ret = clk_prepare_enable(chip->phy25m_clk);
|
|
+ if (ret) {
|
|
+ netdev_err(ndev, "Error: Try to enable phy25m_clk failed\n");
|
|
+ goto phy25m_clk_err;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ clk_value = readl(chip->syscfg_base);
|
|
+ /* Only support RGMII/RMII/MII */
|
|
+ if (chip->phy_interface == PHY_INTERFACE_MODE_RGMII)
|
|
+ clk_value |= SUNXI_GMAC_PHY_RGMII_MASK;
|
|
+ else
|
|
+ clk_value &= (~SUNXI_GMAC_PHY_RGMII_MASK);
|
|
+
|
|
+ clk_value &= (~SUNXI_GMAC_ETCS_RMII_MASK);
|
|
+ if (chip->phy_interface == PHY_INTERFACE_MODE_RGMII
|
|
+ || chip->phy_interface == PHY_INTERFACE_MODE_GMII)
|
|
+ clk_value |= SUNXI_GMAC_RGMII_INTCLK_MASK;
|
|
+ else if (chip->phy_interface == PHY_INTERFACE_MODE_RMII)
|
|
+ clk_value |= SUNXI_GMAC_RMII_MASK;
|
|
+
|
|
+ /*
|
|
+ * Adjust Tx/Rx clock delay
|
|
+ * Tx clock delay: 0~7
|
|
+ * Rx clock delay: 0~31
|
|
+ */
|
|
+ clk_value &= ~(SUNXI_GMAC_TX_DELAY_MASK << SUNXI_GMAC_TX_DELAY_OFFSET);
|
|
+ clk_value |= ((chip->tx_delay & SUNXI_GMAC_TX_DELAY_MASK) << SUNXI_GMAC_TX_DELAY_OFFSET);
|
|
+ clk_value &= ~(SUNXI_GMAC_RX_DELAY_MASK << SUNXI_GMAC_RX_DELAY_OFFSET);
|
|
+ clk_value |= ((chip->rx_delay & SUNXI_GMAC_RX_DELAY_MASK) << SUNXI_GMAC_RX_DELAY_OFFSET);
|
|
+
|
|
+ if (chip->phy_type == SUNXI_EXTERNAL_PHY)
|
|
+ clk_value &= ~(1 << 15);
|
|
+ else
|
|
+ clk_value |= (1 << 15);
|
|
+
|
|
+ writel(clk_value, chip->syscfg_base);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+phy25m_clk_err:
|
|
+ clk_disable(chip->gmac_clk);
|
|
+gmac_clk_err:
|
|
+ reset_control_assert(chip->reset);
|
|
+gmac_reset_err:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_clk_disable(struct sunxi_gmac *chip)
|
|
+{
|
|
+ writel(0, chip->syscfg_base);
|
|
+
|
|
+ if (chip->phy25m_clk)
|
|
+ clk_disable_unprepare(chip->phy25m_clk);
|
|
+
|
|
+ if (chip->gmac_clk)
|
|
+ clk_disable_unprepare(chip->gmac_clk);
|
|
+
|
|
+ if (chip->reset)
|
|
+ reset_control_assert(chip->reset);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_tx_err(struct sunxi_gmac *chip)
|
|
+{
|
|
+ netif_stop_queue(chip->ndev);
|
|
+
|
|
+ sunxi_gmac_disable_tx(chip->base);
|
|
+
|
|
+ sunxi_gmac_free_tx_skb(chip);
|
|
+ memset(chip->dma_tx, 0, sunxi_gmac_dma_desc_tx * sizeof(struct sunxi_gmac_dma_desc));
|
|
+ sunxi_gmac_desc_init_chain(chip->dma_tx, (unsigned long)chip->dma_tx_phy, sunxi_gmac_dma_desc_tx);
|
|
+ chip->tx_dirty = 0;
|
|
+ chip->tx_clean = 0;
|
|
+ sunxi_gmac_enable_tx(chip->base, chip->dma_tx_phy);
|
|
+
|
|
+ chip->ndev->stats.tx_errors++;
|
|
+ netif_wake_queue(chip->ndev);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_schedule(struct sunxi_gmac *chip)
|
|
+{
|
|
+ if (likely(napi_schedule_prep(&chip->napi))) {
|
|
+ sunxi_gmac_irq_disable(chip->base);
|
|
+ __napi_schedule(&chip->napi);
|
|
+ }
|
|
+}
|
|
+
|
|
+static irqreturn_t sunxi_gmac_interrupt(int irq, void *dev_id)
|
|
+{
|
|
+ struct net_device *ndev = (struct net_device *)dev_id;
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ int status;
|
|
+
|
|
+ status = sunxi_gmac_int_status(chip->base, (void *)(&chip->xstats));
|
|
+
|
|
+ if (likely(status == handle_tx_rx))
|
|
+ sunxi_gmac_schedule(chip);
|
|
+ else if (unlikely(status == tx_hard_error_bump_tc))
|
|
+ netdev_info(ndev, "Do nothing for bump tc\n");
|
|
+ else if (unlikely(status == tx_hard_error))
|
|
+ sunxi_gmac_tx_err(chip);
|
|
+ else
|
|
+ netdev_info(ndev, "Do nothing.....\n");
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_tx_complete(struct sunxi_gmac *chip)
|
|
+{
|
|
+ unsigned int entry = 0;
|
|
+ struct sk_buff *skb = NULL;
|
|
+ struct sunxi_gmac_dma_desc *desc = NULL;
|
|
+ int tx_stat;
|
|
+
|
|
+ spin_lock_bh(&chip->tx_lock);
|
|
+ while (circ_cnt(chip->tx_dirty, chip->tx_clean, sunxi_gmac_dma_desc_tx) > 0) {
|
|
+ entry = chip->tx_clean;
|
|
+ desc = chip->dma_tx + entry;
|
|
+
|
|
+ /* Check if the descriptor is owned by the DMA. */
|
|
+ if (sunxi_gmac_desc_get_own(desc))
|
|
+ break;
|
|
+
|
|
+ /* Verify tx error by looking at the last segment */
|
|
+ if (sunxi_gmac_desc_get_tx_last_seg(desc)) {
|
|
+ tx_stat = sunxi_gmac_desc_get_tx_status(desc, (void *)(&chip->xstats));
|
|
+
|
|
+ /*
|
|
+ * These stats will be parsed by net framework layer
|
|
+ * use ifconfig -a in linux cmdline to view
|
|
+ */
|
|
+ if (likely(!tx_stat))
|
|
+ chip->ndev->stats.tx_packets++;
|
|
+ else
|
|
+ chip->ndev->stats.tx_errors++;
|
|
+ }
|
|
+
|
|
+ dma_unmap_single(chip->dev, (u32)sunxi_gmac_desc_buf_get_addr(desc),
|
|
+ sunxi_gmac_desc_buf_get_len(desc), DMA_TO_DEVICE);
|
|
+
|
|
+ skb = chip->tx_skb[entry];
|
|
+ chip->tx_skb[entry] = NULL;
|
|
+ sunxi_gmac_desc_init(desc);
|
|
+
|
|
+ /* Find next dirty desc */
|
|
+ chip->tx_clean = circ_inc(entry, sunxi_gmac_dma_desc_tx);
|
|
+
|
|
+ if (unlikely(skb == NULL))
|
|
+ continue;
|
|
+
|
|
+ dev_kfree_skb(skb);
|
|
+ }
|
|
+
|
|
+ if (unlikely(netif_queue_stopped(chip->ndev)) &&
|
|
+ circ_space(chip->tx_dirty, chip->tx_clean, sunxi_gmac_dma_desc_tx) >
|
|
+ SUNXI_GMAC_TX_THRESH) {
|
|
+ netif_wake_queue(chip->ndev);
|
|
+ }
|
|
+ spin_unlock_bh(&chip->tx_lock);
|
|
+}
|
|
+
|
|
+static netdev_tx_t sunxi_gmac_xmit(struct sk_buff *skb, struct net_device *ndev)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ struct sunxi_gmac_dma_desc *desc, *first;
|
|
+ unsigned int entry, len, tmp_len = 0;
|
|
+ unsigned char *data_addr = skb->data;
|
|
+ int i, csum_insert;
|
|
+ int nfrags = skb_shinfo(skb)->nr_frags;
|
|
+ dma_addr_t dma_addr;
|
|
+
|
|
+ spin_lock_bh(&chip->tx_lock);
|
|
+ if (unlikely(circ_space(chip->tx_dirty, chip->tx_clean,
|
|
+ sunxi_gmac_dma_desc_tx) < (nfrags + 1))) {
|
|
+ if (!netif_queue_stopped(ndev)) {
|
|
+ netdev_err(ndev, "Error: Tx Ring full when queue awake\n");
|
|
+ netif_stop_queue(ndev);
|
|
+ }
|
|
+ spin_unlock_bh(&chip->tx_lock);
|
|
+
|
|
+ return NETDEV_TX_BUSY;
|
|
+ }
|
|
+
|
|
+ csum_insert = (skb->ip_summed == CHECKSUM_PARTIAL);
|
|
+ entry = chip->tx_dirty;
|
|
+ first = chip->dma_tx + entry;
|
|
+ desc = chip->dma_tx + entry;
|
|
+
|
|
+ len = skb_headlen(skb);
|
|
+ chip->tx_skb[entry] = skb;
|
|
+
|
|
+ /* Every desc max size is 2K */
|
|
+ while (len != 0) {
|
|
+ desc = chip->dma_tx + entry;
|
|
+ tmp_len = ((len > SUNXI_GMAC_MAX_BUF_SZ) ? SUNXI_GMAC_MAX_BUF_SZ : len);
|
|
+
|
|
+ dma_addr = dma_map_single(chip->dev, data_addr, tmp_len, DMA_TO_DEVICE);
|
|
+ if (dma_mapping_error(chip->dev, dma_addr)) {
|
|
+ ndev->stats.tx_dropped++;
|
|
+ dev_kfree_skb(skb);
|
|
+ spin_unlock_bh(&chip->tx_lock);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ sunxi_gmac_desc_buf_set(desc, dma_addr, tmp_len);
|
|
+ /* Don't set the first's own bit, here */
|
|
+ if (first != desc) {
|
|
+ chip->tx_skb[entry] = NULL;
|
|
+ sunxi_gmac_desc_set_own(desc);
|
|
+ }
|
|
+
|
|
+ entry = circ_inc(entry, sunxi_gmac_dma_desc_tx);
|
|
+ data_addr += tmp_len;
|
|
+ len -= tmp_len;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < nfrags; i++) {
|
|
+ const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
|
|
+
|
|
+ len = skb_frag_size(frag);
|
|
+ desc = chip->dma_tx + entry;
|
|
+ dma_addr = skb_frag_dma_map(chip->dev, frag, 0, len, DMA_TO_DEVICE);
|
|
+ if (dma_mapping_error(chip->dev, dma_addr)) {
|
|
+ ndev->stats.tx_dropped++;
|
|
+ dev_kfree_skb(skb);
|
|
+ spin_unlock_bh(&chip->tx_lock);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ sunxi_gmac_desc_buf_set(desc, dma_addr, len);
|
|
+ sunxi_gmac_desc_set_own(desc);
|
|
+ chip->tx_skb[entry] = NULL;
|
|
+ entry = circ_inc(entry, sunxi_gmac_dma_desc_tx);
|
|
+ }
|
|
+
|
|
+ ndev->stats.tx_bytes += skb->len;
|
|
+ chip->tx_dirty = entry;
|
|
+ sunxi_gmac_desc_tx_close(first, desc, csum_insert);
|
|
+
|
|
+ /*
|
|
+ * When the own bit, for the first frame, has to be set, all
|
|
+ * descriptors for the same frame has to be set before, to
|
|
+ * avoid race condition.
|
|
+ */
|
|
+ dma_wmb();
|
|
+
|
|
+ sunxi_gmac_desc_set_own(first);
|
|
+ spin_unlock_bh(&chip->tx_lock);
|
|
+
|
|
+ if (circ_space(chip->tx_dirty, chip->tx_clean, sunxi_gmac_dma_desc_tx) <=
|
|
+ (MAX_SKB_FRAGS + 1)) {
|
|
+ netif_stop_queue(ndev);
|
|
+ if (circ_space(chip->tx_dirty, chip->tx_clean, sunxi_gmac_dma_desc_tx) >
|
|
+ SUNXI_GMAC_TX_THRESH)
|
|
+ netif_wake_queue(ndev);
|
|
+ }
|
|
+
|
|
+ netdev_dbg(ndev, "TX descripotor DMA: 0x%08x, dirty: %d, clean: %d\n",
|
|
+ (unsigned int)chip->dma_tx_phy, chip->tx_dirty, chip->tx_clean);
|
|
+ sunxi_gmac_dump_dma_desc(chip->dma_tx, sunxi_gmac_dma_desc_tx);
|
|
+
|
|
+ sunxi_gmac_tx_poll(chip->base);
|
|
+ sunxi_gmac_tx_complete(chip);
|
|
+
|
|
+ return NETDEV_TX_OK;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_copy_loopback_data(struct sunxi_gmac *chip,
|
|
+ struct sk_buff *skb)
|
|
+{
|
|
+ struct net_device *ndev = chip->ndev;
|
|
+ int loopback_len = chip->loopback_pkt_len;
|
|
+ int pkt_offset, frag_len, i;
|
|
+ void *frag_data = NULL;
|
|
+ u8 *loopback_buf = chip->loopback_test_rx_buf;
|
|
+
|
|
+ if (chip->loopback_test_rx_idx == LOOPBACK_PKT_CNT) {
|
|
+ chip->loopback_test_rx_idx = 0;
|
|
+ netdev_warn(ndev, "Warning: Loopback test receive too more test pkts\n");
|
|
+ }
|
|
+
|
|
+ if (skb->len != chip->loopback_pkt_len) {
|
|
+ netdev_warn(ndev, "Warning: Wrong pkt length\n");
|
|
+ chip->loopback_test_rx_idx++;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ pkt_offset = chip->loopback_test_rx_idx * loopback_len;
|
|
+ frag_len = (int)skb_headlen(skb);
|
|
+ memcpy(loopback_buf + pkt_offset, skb->data, frag_len);
|
|
+ pkt_offset += frag_len;
|
|
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
|
|
+ frag_data = skb_frag_address(&skb_shinfo(skb)->frags[i]);
|
|
+ frag_len = (int)skb_frag_size(&skb_shinfo(skb)->frags[i]);
|
|
+ memcpy((loopback_buf + pkt_offset), frag_data, frag_len);
|
|
+ pkt_offset += frag_len;
|
|
+ }
|
|
+
|
|
+ chip->loopback_test_rx_idx++;
|
|
+}
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA)
|
|
+static int sunxi_gmac_rx_metadata_cmp(struct sk_buff *skb)
|
|
+{
|
|
+ const u8 tmp[4] = {0xAA, 0xAA, 0xAA, 0xAA};
|
|
+ u8 *data = skb->data;
|
|
+
|
|
+ data = data + (2 * ETH_ALEN + 2);
|
|
+ return memcmp(data, tmp, 4);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int sunxi_gmac_rx(struct sunxi_gmac *chip, int limit)
|
|
+{
|
|
+ unsigned int rxcount = 0, offset = 0;
|
|
+ unsigned int entry;
|
|
+ struct sunxi_gmac_dma_desc *desc = NULL;
|
|
+ int status;
|
|
+ u32 frame_len;
|
|
+
|
|
+ while (rxcount < limit) {
|
|
+ entry = chip->rx_dirty;
|
|
+ desc = chip->dma_rx + entry;
|
|
+
|
|
+ if (sunxi_gmac_desc_get_own(desc))
|
|
+ break;
|
|
+
|
|
+ rxcount++;
|
|
+ chip->rx_dirty = circ_inc(chip->rx_dirty, sunxi_gmac_dma_desc_rx);
|
|
+
|
|
+ /* Get length & status from hardware */
|
|
+ frame_len = sunxi_gmac_desc_rx_frame_len(desc);
|
|
+ status = sunxi_gmac_desc_get_rx_status(desc, (void *)(&chip->xstats));
|
|
+
|
|
+ netdev_dbg(chip->ndev, "Rx frame size %d, status: %d\n",
|
|
+ frame_len, status);
|
|
+
|
|
+ if (unlikely(!chip->rx_skb[entry])) {
|
|
+ netdev_err(chip->ndev, "Skb is null\n");
|
|
+ chip->ndev->stats.rx_dropped++;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (status == discard_frame || frame_len > SUNXI_GMAC_MAX_MTU_SZ) {
|
|
+ netdev_err(chip->ndev, "Get error pkt\n");
|
|
+ chip->ndev->stats.rx_errors++;
|
|
+ if (chip->rx_skb[entry]) {
|
|
+ dev_kfree_skb_any(chip->rx_skb[entry]);
|
|
+ chip->rx_skb[entry] = NULL;
|
|
+ }
|
|
+
|
|
+ if (chip->skb) {
|
|
+ dev_kfree_skb_any(chip->skb);
|
|
+ chip->skb = NULL;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ dma_unmap_single(chip->dev, (u32)sunxi_gmac_desc_buf_get_addr(desc),
|
|
+ sunxi_gmac_desc_buf_get_len(desc), DMA_FROM_DEVICE);
|
|
+
|
|
+ /* jumbo frame */
|
|
+ if (status == incomplete_frame) {
|
|
+ if (!chip->skb)
|
|
+ chip->skb = netdev_alloc_skb_ip_align(chip->ndev, SUNXI_GMAC_MAX_MTU_SZ);
|
|
+
|
|
+ if (!chip->skb) {
|
|
+ netdev_err(chip->ndev, "Failed to alloc skb\n");
|
|
+ if (chip->rx_skb[entry]) {
|
|
+ dev_kfree_skb_any(chip->rx_skb[entry]);
|
|
+ chip->rx_skb[entry] = NULL;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ skb_copy_to_linear_data_offset(chip->skb, offset, chip->rx_skb[entry]->data, frame_len - offset);
|
|
+ /* after copy, release tmp skb */
|
|
+ dev_kfree_skb_any(chip->rx_skb[entry]);
|
|
+ chip->rx_skb[entry] = NULL;
|
|
+ offset = frame_len;
|
|
+ continue;
|
|
+ } else {
|
|
+ if (!chip->skb) {
|
|
+ /*
|
|
+ * no need to copy skb,
|
|
+ * pass it to protocol stack,
|
|
+ * and protocol stack will release skb
|
|
+ */
|
|
+ chip->skb = chip->rx_skb[entry];
|
|
+ chip->rx_skb[entry] = NULL;
|
|
+ } else {
|
|
+ skb_copy_to_linear_data_offset(chip->skb, offset, chip->rx_skb[entry]->data, frame_len - offset);
|
|
+ /* after copy, release tmp skb */
|
|
+ dev_kfree_skb_any(chip->rx_skb[entry]);
|
|
+ chip->rx_skb[entry] = NULL;
|
|
+ }
|
|
+ offset = 0;
|
|
+ }
|
|
+
|
|
+ if (likely(status != llc_snap))
|
|
+ frame_len -= ETH_FCS_LEN;
|
|
+
|
|
+ skb_put(chip->skb, frame_len);
|
|
+
|
|
+ if (unlikely(chip->is_loopback_test))
|
|
+ sunxi_gmac_copy_loopback_data(chip, chip->skb);
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA)
|
|
+ if (unlikely(sunxi_gmac_rx_metadata_cmp(chip->skb) == 0)) {
|
|
+ frame_len = min(frame_len, chip->metadata_len);
|
|
+ memcpy(chip->metadata_buff, chip->skb->data + (2 * ETH_ALEN + 6), frame_len);
|
|
+ complete(&chip->metadata_done);
|
|
+ dev_kfree_skb_any(chip->skb);
|
|
+ continue;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ chip->skb->protocol = eth_type_trans(chip->skb, chip->ndev);
|
|
+ chip->skb->ip_summed = CHECKSUM_UNNECESSARY;
|
|
+
|
|
+ napi_gro_receive(&chip->napi, chip->skb);
|
|
+
|
|
+ chip->ndev->stats.rx_packets++;
|
|
+ chip->ndev->stats.rx_bytes += frame_len;
|
|
+ chip->skb = NULL;
|
|
+ }
|
|
+
|
|
+ if (rxcount > 0) {
|
|
+ netdev_dbg(chip->ndev, "RX descriptor DMA: 0x%08x, dirty: %d, clean: %d\n",
|
|
+ (unsigned int)chip->dma_rx_phy, chip->rx_dirty, chip->rx_clean);
|
|
+ sunxi_gmac_dump_dma_desc(chip->dma_rx, sunxi_gmac_dma_desc_rx);
|
|
+ }
|
|
+
|
|
+ sunxi_gmac_rx_refill(chip->ndev);
|
|
+
|
|
+ return rxcount;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_poll(struct napi_struct *napi, int budget)
|
|
+{
|
|
+ struct sunxi_gmac *chip = container_of(napi, struct sunxi_gmac, napi);
|
|
+ int work_done = 0;
|
|
+
|
|
+ sunxi_gmac_tx_complete(chip);
|
|
+ work_done = sunxi_gmac_rx(chip, budget);
|
|
+
|
|
+ if (work_done < budget) {
|
|
+ napi_complete(napi);
|
|
+ sunxi_gmac_irq_enable(chip->base);
|
|
+ }
|
|
+
|
|
+ return work_done;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_change_mtu(struct net_device *ndev, int new_mtu)
|
|
+{
|
|
+ if (netif_running(ndev)) {
|
|
+ netdev_err(ndev, "Error: Nic must be stopped to change its MTU\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ if (new_mtu < 46) {
|
|
+ netdev_err(ndev, "Error: Invalid MTU\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ndev->mtu = new_mtu;
|
|
+ netdev_update_features(ndev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static netdev_features_t sunxi_gmac_fix_features(struct net_device *ndev,
|
|
+ netdev_features_t features)
|
|
+{
|
|
+ return features;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_set_rx_mode(struct net_device *ndev)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ unsigned int value = 0;
|
|
+
|
|
+ netdev_dbg(ndev, "%s: # mcasts %d, # unicast %d\n",
|
|
+ __func__, netdev_mc_count(ndev), netdev_uc_count(ndev));
|
|
+
|
|
+ spin_lock_bh(&chip->universal_lock);
|
|
+ if (ndev->flags & IFF_PROMISC) {
|
|
+ value = SUNXI_GMAC_FRAME_FILTER_PR;
|
|
+ } else if ((netdev_mc_count(ndev) > SUNXI_GMAC_HASH_TABLE_SIZE) ||
|
|
+ (ndev->flags & IFF_ALLMULTI)) {
|
|
+ value = SUNXI_GMAC_FRAME_FILTER_PM; /* pass all multi */
|
|
+ sunxi_gmac_hash_filter(chip->base, ~0UL, ~0UL);
|
|
+ } else if (!netdev_mc_empty(ndev)) {
|
|
+ u32 mc_filter[2];
|
|
+ struct netdev_hw_addr *ha;
|
|
+
|
|
+ /* Hash filter for multicast */
|
|
+ value = SUNXI_GMAC_FRAME_FILTER_HMC;
|
|
+
|
|
+ memset(mc_filter, 0, sizeof(mc_filter));
|
|
+ netdev_for_each_mc_addr(ha, ndev) {
|
|
+ /* The upper 6 bits of the calculated CRC are used to
|
|
+ * index the contens of the hash table
|
|
+ */
|
|
+ int bit_nr = bitrev32(~crc32_le(~0, ha->addr, 6)) >> 26;
|
|
+ /* The most significant bit determines the register to
|
|
+ * use (H/L) while the other 5 bits determine the bit
|
|
+ * within the register.
|
|
+ */
|
|
+ mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
|
|
+ }
|
|
+ sunxi_gmac_hash_filter(chip->base, mc_filter[0], mc_filter[1]);
|
|
+ }
|
|
+
|
|
+ /* Handle multiple unicast addresses (perfect filtering)*/
|
|
+ if (netdev_uc_count(ndev) > 16) {
|
|
+ /* Switch to promiscuous mode is more than 8 addrs are required */
|
|
+ value |= SUNXI_GMAC_FRAME_FILTER_PR;
|
|
+ } else {
|
|
+ int reg = 1;
|
|
+ struct netdev_hw_addr *ha;
|
|
+
|
|
+ netdev_for_each_uc_addr(ha, ndev) {
|
|
+ sunxi_gmac_set_mac_addr_to_reg(chip->base, ha->addr, reg);
|
|
+ reg++;
|
|
+ }
|
|
+ }
|
|
+
|
|
+#ifdef FRAME_FILTER_DEBUG
|
|
+ /* Enable Receive all mode (to debug filtering_fail errors) */
|
|
+ value |= SUNXI_GMAC_FRAME_FILTER_RA;
|
|
+#endif
|
|
+ writel(value, chip->base + SUNXI_GMAC_RX_FRM_FLT);
|
|
+ spin_unlock_bh(&chip->universal_lock);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_tx_timeout(struct net_device *ndev, unsigned int txqueue)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ sunxi_gmac_tx_err(chip);
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
|
|
+{
|
|
+ if (!netif_running(ndev))
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (!ndev->phydev)
|
|
+ return -EINVAL;
|
|
+
|
|
+ return phy_mii_ioctl(ndev->phydev, rq, cmd);
|
|
+}
|
|
+
|
|
+/* Configuration changes (passed on by ifconfig) */
|
|
+static int sunxi_gmac_config(struct net_device *ndev, struct ifmap *map)
|
|
+{
|
|
+ if (ndev->flags & IFF_UP) /* can't act on a running interface */
|
|
+ return -EBUSY;
|
|
+
|
|
+ /* Don't allow changing the I/O address */
|
|
+ if (map->base_addr != ndev->base_addr) {
|
|
+ netdev_err(ndev, "Error: Can't change I/O address\n");
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+
|
|
+ /* Don't allow changing the IRQ */
|
|
+ if (map->irq != ndev->irq) {
|
|
+ netdev_err(ndev, "Error: Can't change IRQ number %d\n", ndev->irq);
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_set_mac_address(struct net_device *ndev, void *p)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ struct sockaddr *addr = p;
|
|
+
|
|
+ if (!is_valid_ether_addr(addr->sa_data)) {
|
|
+ netdev_err(ndev, "Error: Set error mac address\n");
|
|
+ return -EADDRNOTAVAIL;
|
|
+ }
|
|
+
|
|
+ eth_hw_addr_set(ndev, addr->sa_data);
|
|
+ sunxi_gmac_set_mac_addr_to_reg(chip->base, (unsigned char *)ndev->dev_addr, 0);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_set_features(struct net_device *ndev, netdev_features_t features)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ if (features & NETIF_F_LOOPBACK && netif_running(ndev))
|
|
+ sunxi_gmac_loopback(chip->base, 1);
|
|
+ else
|
|
+ sunxi_gmac_loopback(chip->base, 0);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#if IS_ENABLED(CONFIG_NET_POLL_CONTROLLER)
|
|
+/* Polling receive - used by NETCONSOLE and other diagnostic tools
|
|
+ * to allow network I/O with interrupts disabled.
|
|
+ */
|
|
+static void sunxi_gmac_poll_controller(struct net_device *dev)
|
|
+{
|
|
+ disable_irq(dev->irq);
|
|
+ sunxi_gmac_interrupt(dev->irq, dev);
|
|
+ enable_irq(dev->irq);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static const struct net_device_ops sunxi_gmac_netdev_ops = {
|
|
+ .ndo_init = NULL,
|
|
+ .ndo_open = sunxi_gmac_open,
|
|
+ .ndo_start_xmit = sunxi_gmac_xmit,
|
|
+ .ndo_stop = sunxi_gmac_stop,
|
|
+ .ndo_change_mtu = sunxi_gmac_change_mtu,
|
|
+ .ndo_fix_features = sunxi_gmac_fix_features,
|
|
+ .ndo_set_rx_mode = sunxi_gmac_set_rx_mode,
|
|
+ .ndo_tx_timeout = sunxi_gmac_tx_timeout,
|
|
+ .ndo_do_ioctl = sunxi_gmac_ioctl,
|
|
+ .ndo_set_config = sunxi_gmac_config,
|
|
+#if IS_ENABLED(CONFIG_NET_POLL_CONTROLLER)
|
|
+ .ndo_poll_controller = sunxi_gmac_poll_controller,
|
|
+#endif
|
|
+ .ndo_set_mac_address = sunxi_gmac_set_mac_address,
|
|
+ .ndo_set_features = sunxi_gmac_set_features,
|
|
+};
|
|
+
|
|
+static int sunxi_gmac_check_if_running(struct net_device *ndev)
|
|
+{
|
|
+ if (!netif_running(ndev))
|
|
+ return -EBUSY;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_ethtool_get_sset_count(struct net_device *netdev, int sset)
|
|
+{
|
|
+ int len;
|
|
+
|
|
+ switch (sset) {
|
|
+ case ETH_SS_STATS:
|
|
+ len = 0;
|
|
+ return len;
|
|
+ default:
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_ethtool_getdrvinfo - Get various SUNXI GMAC driver information.
|
|
+ * @ndev: Pointer to net_device structure
|
|
+ * @ed: Pointer to ethtool_drvinfo structure
|
|
+ *
|
|
+ * This implements ethtool command for getting the driver information.
|
|
+ * Issue "ethtool -i ethX" under linux prompt to execute this function.
|
|
+ */
|
|
+static void sunxi_gmac_ethtool_getdrvinfo(struct net_device *ndev,
|
|
+ struct ethtool_drvinfo *info)
|
|
+{
|
|
+ strscpy(info->driver, "sunxi_gmac", sizeof(info->driver));
|
|
+
|
|
+ strcpy(info->version, SUNXI_GMAC_MODULE_VERSION);
|
|
+ info->fw_version[0] = '\0';
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_ethool_get_pauseparam - Get the pause parameter setting for Tx/Rx.
|
|
+ *
|
|
+ * @ndev: Pointer to net_device structure
|
|
+ * @epause: Pointer to ethtool_pauseparam structure
|
|
+ *
|
|
+ * This implements ethtool command for getting sunxi_gmac ethernet pause frame
|
|
+ * setting. Issue "ethtool -a ethx" to execute this function.
|
|
+ */
|
|
+static void sunxi_gmac_ethtool_get_pauseparam(struct net_device *ndev,
|
|
+ struct ethtool_pauseparam *epause)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ /* TODO: need to support autoneg */
|
|
+ epause->tx_pause = sunxi_gmac_read_tx_flowctl(chip->base);
|
|
+ epause->rx_pause = sunxi_gmac_read_rx_flowctl(chip->base);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_ethtool_set_pauseparam - Set device pause paramter(flow contrl)
|
|
+ * settings.
|
|
+ * @ndev: Pointer to net_device structure
|
|
+ * @epause: Pointer to ethtool_pauseparam structure
|
|
+ *
|
|
+ * This implements ethtool command for enabling flow control on Rx and Tx.
|
|
+ * Issue "ethtool -A ethx tx on|off" under linux prompt to execute this
|
|
+ * function.
|
|
+ *
|
|
+ */
|
|
+static int sunxi_gmac_ethtool_set_pauseparam(struct net_device *ndev,
|
|
+ struct ethtool_pauseparam *epause)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ sunxi_gmac_write_tx_flowctl(chip->base, !!epause->tx_pause);
|
|
+ netdev_info(ndev, "Tx flowctrl %s\n", epause->tx_pause ? "ON" : "OFF");
|
|
+
|
|
+ sunxi_gmac_write_rx_flowctl(chip->base, !!epause->rx_pause);
|
|
+ netdev_info(ndev, "Rx flowctrl %s\n", epause->rx_pause ? "ON" : "OFF");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_ethtool_get_wol - Get device wake-on-lan settings.
|
|
+ *
|
|
+ * @ndev: Pointer to net_device structure
|
|
+ * @wol: Pointer to ethtool_wolinfo structure
|
|
+ *
|
|
+ * This implements ethtool command for get wake-on-lan settings.
|
|
+ * Issue "ethtool -s ethx wol p|u|m|b|a|g|s|d" under linux prompt to execute
|
|
+ * this function.
|
|
+ */
|
|
+static void sunxi_gmac_ethtool_get_wol(struct net_device *ndev,
|
|
+ struct ethtool_wolinfo *wol)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ spin_lock_irq(&chip->universal_lock);
|
|
+ /* TODO: need to support wol */
|
|
+ spin_unlock_irq(&chip->universal_lock);
|
|
+
|
|
+ netdev_err(ndev, "Error: wakeup-on-lan func is not supported yet\n");
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_ethtool_set_wol - set device wake-on-lan settings.
|
|
+ *
|
|
+ * @ndev: Pointer to net_device structure
|
|
+ * @wol: Pointer to ethtool_wolinfo structure
|
|
+ *
|
|
+ * This implements ethtool command for set wake-on-lan settings.
|
|
+ * Issue "ethtool -s ethx wol p|u|n|b|a|g|s|d" under linux prompt to execute
|
|
+ * this function.
|
|
+ */
|
|
+static int sunxi_gmac_ethtool_set_wol(struct net_device *ndev,
|
|
+ struct ethtool_wolinfo *wol)
|
|
+{
|
|
+ /*
|
|
+ * TODO: Wake-on-lane function need to be supported.
|
|
+ */
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __sunxi_gmac_loopback_test(struct net_device *ndev)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ struct sk_buff *skb_tmp = NULL, *skb = NULL;
|
|
+ u8 *test_data = NULL;
|
|
+ u8 *loopback_test_rx_buf = chip->loopback_test_rx_buf;
|
|
+ u32 i, j;
|
|
+
|
|
+ skb_tmp = alloc_skb(LOOPBACK_PKT_LEN, GFP_ATOMIC);
|
|
+ if (!skb_tmp)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ test_data = __skb_put(skb_tmp, LOOPBACK_PKT_LEN);
|
|
+
|
|
+ memset(test_data, 0xFF, 2 * ETH_ALEN);
|
|
+ test_data[ETH_ALEN] = 0xFE;
|
|
+ test_data[2 * ETH_ALEN] = 0x08;
|
|
+ test_data[2 * ETH_ALEN + 1] = 0x0;
|
|
+
|
|
+ for (i = ETH_HLEN; i < LOOPBACK_PKT_LEN; i++)
|
|
+ test_data[i] = i & 0xFF;
|
|
+
|
|
+ skb_tmp->queue_mapping = 0;
|
|
+ skb_tmp->ip_summed = CHECKSUM_COMPLETE;
|
|
+ skb_tmp->dev = ndev;
|
|
+
|
|
+ for (i = 0; i < LOOPBACK_DEFAULT_TIME; i++) {
|
|
+ chip->loopback_test_rx_idx = 0;
|
|
+ memset(loopback_test_rx_buf, 0, LOOPBACK_PKT_CNT * LOOPBACK_PKT_LEN);
|
|
+
|
|
+ for (j = 0; j < LOOPBACK_PKT_CNT; j++) {
|
|
+ skb = pskb_copy(skb_tmp, GFP_ATOMIC);
|
|
+ if (!skb) {
|
|
+ dev_kfree_skb_any(skb_tmp);
|
|
+ netdev_err(ndev, "Error: Copy skb failed for loopback test\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ /* mark index for every pkt */
|
|
+ skb->data[LOOPBACK_PKT_LEN - 1] = j;
|
|
+
|
|
+ /* xmit loopback skb */
|
|
+ if (sunxi_gmac_xmit(skb, ndev)) {
|
|
+ dev_kfree_skb_any(skb);
|
|
+ dev_kfree_skb_any(skb_tmp);
|
|
+ netdev_err(ndev, "Error: Xmit pkt failed for loopback test\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* wait till all pkts received to RX buffer */
|
|
+ msleep(200);
|
|
+
|
|
+ for (j = 0; j < LOOPBACK_PKT_CNT; j++) {
|
|
+ /* compare loopback data */
|
|
+ if (memcmp(loopback_test_rx_buf + j * LOOPBACK_PKT_LEN,
|
|
+ skb_tmp->data, LOOPBACK_PKT_LEN - 1) ||
|
|
+ (*(loopback_test_rx_buf + j * LOOPBACK_PKT_LEN +
|
|
+ LOOPBACK_PKT_LEN - 1) != j)) {
|
|
+ dev_kfree_skb_any(skb_tmp);
|
|
+ netdev_err(ndev, "Error: Compare pkt failed in loopback test (index=0x%02x, data[%d]=0x%02x)\n",
|
|
+ j + i * LOOPBACK_PKT_CNT,
|
|
+ LOOPBACK_PKT_LEN - 1,
|
|
+ *(loopback_test_rx_buf + j * LOOPBACK_PKT_LEN +
|
|
+ LOOPBACK_PKT_LEN - 1));
|
|
+ return -EIO;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dev_kfree_skb_any(skb_tmp);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_loopback_test(struct net_device *ndev, u32 flags,
|
|
+ enum self_test_index *test_index)
|
|
+{
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ u8 *loopback_test_rx_buf = NULL;
|
|
+ int err = 0;
|
|
+
|
|
+ /* set loopback */
|
|
+ if (!(flags & ETH_TEST_FL_EXTERNAL_LB)) {
|
|
+ *test_index = INTERNAL_LOOPBACK_TEST;
|
|
+ sunxi_gmac_loopback(chip->base, true);
|
|
+ } else {
|
|
+ *test_index = EXTERNAL_LOOPBACK_TEST;
|
|
+ err |= phy_loopback(ndev->phydev, true);
|
|
+ if (err)
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /* only focus data part, so turn off the crc check */
|
|
+ sunxi_gmac_crc(chip->base, false);
|
|
+
|
|
+ loopback_test_rx_buf = vmalloc(LOOPBACK_PKT_CNT * LOOPBACK_PKT_LEN);
|
|
+ if (!loopback_test_rx_buf) {
|
|
+ err |= -ENOMEM;
|
|
+ } else {
|
|
+ chip->loopback_test_rx_buf = loopback_test_rx_buf;
|
|
+ chip->loopback_pkt_len = LOOPBACK_PKT_LEN;
|
|
+ chip->is_loopback_test = true;
|
|
+ err |= __sunxi_gmac_loopback_test(ndev);
|
|
+ chip->is_loopback_test = false;
|
|
+ msleep(100);
|
|
+ vfree(loopback_test_rx_buf);
|
|
+ chip->loopback_test_rx_buf = NULL;
|
|
+ }
|
|
+
|
|
+ sunxi_gmac_crc(chip->base, true);
|
|
+out:
|
|
+ if (!(flags & ETH_TEST_FL_EXTERNAL_LB))
|
|
+ sunxi_gmac_loopback(chip->base, false);
|
|
+ else
|
|
+ err |= phy_loopback(ndev->phydev, false);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_self_test(struct net_device *ndev,
|
|
+ struct ethtool_test *eth_test, u64 *data)
|
|
+{
|
|
+ enum self_test_index test_index = 0;
|
|
+ int err;
|
|
+
|
|
+ memset(data, 0, SELF_TEST_MAX * sizeof(u64));
|
|
+
|
|
+ if (!netif_running(ndev)) {
|
|
+ netdev_err(ndev, "Error: Do not support selftest when ndev is closed\n");
|
|
+ eth_test->flags |= ETH_TEST_FL_FAILED;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ netif_carrier_off(ndev);
|
|
+ netif_tx_disable(ndev);
|
|
+
|
|
+ err = sunxi_gmac_loopback_test(ndev, eth_test->flags, &test_index);
|
|
+ if (err) {
|
|
+ eth_test->flags |= ETH_TEST_FL_FAILED;
|
|
+ data[test_index] = 1; /* 0:success, 1:fail */
|
|
+ netdev_err(ndev, "Error: Loopback test failed\n");
|
|
+ }
|
|
+
|
|
+ netif_tx_wake_all_queues(ndev);
|
|
+ netif_carrier_on(ndev);
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_get_sset_count(struct net_device *ndev, int sset)
|
|
+{
|
|
+ switch (sset) {
|
|
+ case ETH_SS_TEST:
|
|
+ return ARRAY_SIZE(sunxi_gmac_test_strings);
|
|
+ case ETH_SS_STATS:
|
|
+ return -EOPNOTSUPP;
|
|
+ default:
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_get_strings(struct net_device *netdev,
|
|
+ u32 stringset, u8 *data)
|
|
+{
|
|
+ switch (stringset) {
|
|
+ case ETH_SS_TEST:
|
|
+ memcpy(data, *sunxi_gmac_test_strings, sizeof(sunxi_gmac_test_strings));
|
|
+ return;
|
|
+ case ETH_SS_STATS:
|
|
+ return;
|
|
+ default:
|
|
+ return;
|
|
+ }
|
|
+}
|
|
+
|
|
+static const struct ethtool_ops sunxi_gmac_ethtool_ops = {
|
|
+ .begin = sunxi_gmac_check_if_running,
|
|
+ .get_link = ethtool_op_get_link,
|
|
+ .get_pauseparam = sunxi_gmac_ethtool_get_pauseparam,
|
|
+ .set_pauseparam = sunxi_gmac_ethtool_set_pauseparam,
|
|
+ .get_wol = sunxi_gmac_ethtool_get_wol,
|
|
+ .set_wol = sunxi_gmac_ethtool_set_wol,
|
|
+ .get_sset_count = sunxi_gmac_ethtool_get_sset_count,
|
|
+ .get_drvinfo = sunxi_gmac_ethtool_getdrvinfo,
|
|
+ .get_link_ksettings = phy_ethtool_get_link_ksettings,
|
|
+ .set_link_ksettings = phy_ethtool_set_link_ksettings,
|
|
+ .get_sset_count = sunxi_gmac_get_sset_count,
|
|
+ .get_strings = sunxi_gmac_get_strings,
|
|
+ .self_test = sunxi_gmac_self_test,
|
|
+};
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+static int sunxi_gmac_ephy_v1_hardware_init(struct sunxi_gmac *chip)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = pwm_config(chip->ac300_pwm, PWM_DUTY_NS, PWM_PERIOD_NS);
|
|
+ if (ret) {
|
|
+ netdev_err(chip->ndev, "Error: Config ac300 pwm failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = pwm_enable(chip->ac300_pwm);
|
|
+ if (ret) {
|
|
+ netdev_err(chip->ndev, "Error: Enable ac300 pwm failed\n");
|
|
+ ret = -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+
|
|
+static int sunxi_gmac_hardware_init(struct platform_device *pdev)
|
|
+{
|
|
+ struct net_device *ndev = platform_get_drvdata(pdev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ int ret;
|
|
+
|
|
+ ret = sunxi_gmac_power_on(chip);
|
|
+ if (ret) {
|
|
+ netdev_err(ndev, "Error: Gmac power on failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto power_on_err;
|
|
+ }
|
|
+
|
|
+ ret = sunxi_gmac_clk_enable(chip);
|
|
+ if (ret) {
|
|
+ netdev_err(ndev, "Error: Clk enable is failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto clk_enable_err;
|
|
+ }
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+ ret = chip->ephy_ops->hardware_init(chip);
|
|
+ if (ret) {
|
|
+ netdev_err(ndev, "Error: ephy init failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto ephy_init_err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+ephy_init_err:
|
|
+ sunxi_gmac_clk_disable(chip);
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+clk_enable_err:
|
|
+ sunxi_gmac_power_off(chip);
|
|
+power_on_err:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+static void sunxi_gmac_ephy_v1_hardware_deinit(struct sunxi_gmac *chip)
|
|
+{
|
|
+ pwm_disable(chip->ac300_pwm);
|
|
+}
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+
|
|
+static void sunxi_gmac_hardware_deinit(struct platform_device *pdev)
|
|
+{
|
|
+ struct net_device *ndev = platform_get_drvdata(pdev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ sunxi_gmac_power_off(chip);
|
|
+
|
|
+ sunxi_gmac_clk_disable(chip);
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+ chip->ephy_ops->hardware_deinit(chip);
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_parse_delay_maps(struct sunxi_gmac *chip)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(chip->dev);
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ int ret, maps_cnt;
|
|
+ u32 *maps;
|
|
+
|
|
+ maps_cnt = of_property_count_elems_of_size(np, "delay-maps", sizeof(u32));
|
|
+ if (maps_cnt <= 0) {
|
|
+ dev_info(&pdev->dev, "Info: not found delay-maps in dts\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ maps = devm_kcalloc(&pdev->dev, maps_cnt, sizeof(u32), GFP_KERNEL);
|
|
+ if (!maps)
|
|
+ return;
|
|
+
|
|
+ ret = of_property_read_u32_array(np, "delay-maps", maps, maps_cnt);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Error: failed to parse delay-maps\n");
|
|
+ goto err_parse_maps;
|
|
+ }
|
|
+/* todo
|
|
+ const u8 array_size = 3;
|
|
+ u16 soc_ver;
|
|
+ int i;
|
|
+
|
|
+ soc_ver = (u16)sunxi_get_soc_ver();
|
|
+ for (i = 0; i < (maps_cnt / array_size); i++) {
|
|
+ if (soc_ver == maps[i * array_size]) {
|
|
+ chip->rx_delay = maps[i * array_size + 1];
|
|
+ chip->tx_delay = maps[i * array_size + 2];
|
|
+ dev_info(&pdev->dev, "Info: delay-maps overwrite delay parameters, rx-delay:%d, tx-delay:%d\n",
|
|
+ chip->rx_delay, chip->tx_delay);
|
|
+ }
|
|
+ }
|
|
+*/
|
|
+err_parse_maps:
|
|
+ devm_kfree(&pdev->dev, maps);
|
|
+}
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+static int sunxi_gmac_ephy_v1_resource_get(struct platform_device *pdev)
|
|
+{
|
|
+ struct net_device *ndev = platform_get_drvdata(pdev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ int ret;
|
|
+
|
|
+ ret = of_property_read_u32(np, "sunxi,pwm-channel", &chip->pwm_channel);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Error: Get ac300 pwm failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ chip->ac300_pwm = pwm_request(chip->pwm_channel, NULL);
|
|
+ if (IS_ERR_OR_NULL(chip->ac300_pwm)) {
|
|
+ dev_err(&pdev->dev, "Error: Get ac300 pwm failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+
|
|
+static int sunxi_gmac_resource_get(struct platform_device *pdev)
|
|
+{
|
|
+ struct net_device *ndev = platform_get_drvdata(pdev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ struct resource *res;
|
|
+ struct cpumask mask;
|
|
+ int cpu;
|
|
+ phy_interface_t phy_mode;
|
|
+
|
|
+ int ret;
|
|
+
|
|
+ /* External phy is selected by default */
|
|
+ chip->phy_type = SUNXI_EXTERNAL_PHY;
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (!res) {
|
|
+ dev_err(&pdev->dev, "Error: Get gmac memory failed\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ chip->base = devm_ioremap_resource(&pdev->dev, res);
|
|
+ if (!chip->base) {
|
|
+ dev_err(&pdev->dev, "Error: Gmac memory mapping failed\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
+ if (!res) {
|
|
+ dev_err(&pdev->dev, "Error: Get phy memory failed\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ chip->syscfg_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
|
|
+ if (!chip->syscfg_base) {
|
|
+ dev_err(&pdev->dev, "Error: Phy memory mapping failed\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ ndev->irq = platform_get_irq_byname(pdev, "gmacirq");
|
|
+ if (ndev->irq < 0) {
|
|
+ dev_err(&pdev->dev, "Error: Gmac irq not found\n");
|
|
+ return -ENXIO;
|
|
+ }
|
|
+
|
|
+ ret = devm_request_irq(&pdev->dev, ndev->irq, sunxi_gmac_interrupt, IRQF_SHARED, dev_name(&pdev->dev), ndev);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Error: Could not request irq %d\n", ndev->irq);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32(np, "irq-affinity", &chip->irq_affinity);
|
|
+ if (ret) {
|
|
+ dev_dbg(&pdev->dev, "Info: Get irq-affinity failed, use default\n");
|
|
+ } else {
|
|
+ for_each_online_cpu(cpu) {
|
|
+ if (chip->irq_affinity & BIT(cpu))
|
|
+ cpumask_set_cpu(cpu, &mask);
|
|
+ }
|
|
+ irq_set_affinity(ndev->irq, &mask);
|
|
+ dev_info(&pdev->dev, "Info: Set irq affinity to cpu%d\n", cpumask_first(&mask));
|
|
+ }
|
|
+
|
|
+ chip->reset = devm_reset_control_get(&pdev->dev, NULL);
|
|
+ if (IS_ERR(chip->reset)) {
|
|
+ dev_err(&pdev->dev, "Error: Get gmac rst failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ chip->pinctrl = devm_pinctrl_get(&pdev->dev);
|
|
+ if (IS_ERR(chip->pinctrl)) {
|
|
+ dev_err(&pdev->dev, "Error: Get Pin failed\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ chip->gmac_clk = devm_clk_get(&pdev->dev, "gmac");
|
|
+ if (!chip->gmac_clk) {
|
|
+ dev_err(&pdev->dev, "Error: Get gmac clock failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = of_get_phy_mode(np, &phy_mode);
|
|
+ if (!ret) {
|
|
+ chip->phy_interface = phy_mode;
|
|
+ if (chip->phy_interface != PHY_INTERFACE_MODE_RGMII &&
|
|
+ chip->phy_interface != PHY_INTERFACE_MODE_RMII &&
|
|
+ chip->phy_interface != PHY_INTERFACE_MODE_MII) {
|
|
+ dev_err(&pdev->dev, "Error: Get gmac phy interface failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32(np, "tx-delay", &chip->tx_delay);
|
|
+ if (ret) {
|
|
+ dev_warn(&pdev->dev, "Warning: Get gmac tx-delay failed, use default 0\n");
|
|
+ chip->tx_delay = 0;
|
|
+ }
|
|
+
|
|
+ if (user_tx_delay >= 0 && user_tx_delay <= 7) {
|
|
+ chip->tx_delay = user_tx_delay;
|
|
+ dev_info(&pdev->dev, "Info: user tx-delay: %d\n", chip->tx_delay);
|
|
+ } else {
|
|
+ dev_info(&pdev->dev, "Info: dts tx-delay: %d\n", chip->tx_delay);
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32(np, "rx-delay", &chip->rx_delay);
|
|
+ if (ret) {
|
|
+ dev_warn(&pdev->dev, "Warning: Get gmac rx-delay failed, use default 0\n");
|
|
+ chip->rx_delay = 0;
|
|
+ }
|
|
+
|
|
+ if (user_rx_delay >= 0 && user_rx_delay <= 31) {
|
|
+ chip->rx_delay = user_rx_delay;
|
|
+ dev_info(&pdev->dev, "Info: user rx-delay: %d\n", chip->rx_delay);
|
|
+ } else {
|
|
+ dev_info(&pdev->dev, "Info: dts rx-delay: %d\n", chip->rx_delay);
|
|
+ }
|
|
+
|
|
+ sunxi_gmac_parse_delay_maps(chip);
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+ chip->ac300_np = of_parse_phandle(np, "ac300-phy-handle", 0);
|
|
+ if (!chip->ac300_np) {
|
|
+ dev_err(&pdev->dev, "Error: Get gmac ac300-phy-handle failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = chip->ephy_ops->resource_get(pdev);
|
|
+ if (ret)
|
|
+ return -EINVAL;
|
|
+
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+
|
|
+ chip->phy_node = of_parse_phandle(np, "phy-handle", 0);
|
|
+ if (!chip->phy_node) {
|
|
+ dev_err(&pdev->dev, "Error: Get gmac phy-handle failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ret = of_property_read_u32(np, "sunxi,phy-clk-type", &chip->phy_clk_type);
|
|
+ if (ret) {
|
|
+ dev_warn(&pdev->dev, "Warning: Get gmac phy-clk-type failed, use default OSC or pwm\n");
|
|
+ chip->phy_clk_type = SUNXI_PHY_USE_EXT_OSC;
|
|
+ };
|
|
+
|
|
+ if (chip->phy_clk_type == SUNXI_PHY_USE_CLK25M) {
|
|
+ chip->phy25m_clk = devm_clk_get(&pdev->dev, "phy25m");
|
|
+ if (IS_ERR_OR_NULL(chip->phy25m_clk)) {
|
|
+ dev_err(&pdev->dev, "Error: Get phy25m clk failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ chip->gmac_supply = devm_regulator_get_optional(&pdev->dev, "gmac3v3");
|
|
+ if (IS_ERR(chip->gmac_supply))
|
|
+ netdev_err(ndev, "Error: Not found gmac3v3-supply\n");
|
|
+
|
|
+ /*
|
|
+ * Read mac-address from dts,
|
|
+ * it doesn't matter if it's missing
|
|
+ */
|
|
+ of_get_ethdev_address(np, ndev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+static void sunxi_gmac_ephy_v1_resource_put(struct platform_device *pdev)
|
|
+{
|
|
+ struct net_device *ndev = platform_get_drvdata(pdev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ pwm_free(chip->ac300_pwm);
|
|
+}
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+
|
|
+static void sunxi_gmac_resource_put(struct platform_device *pdev)
|
|
+{
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+ struct net_device *ndev = platform_get_drvdata(pdev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+ chip->ephy_ops->resource_put(pdev);
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_sysfs_create(struct device *dev)
|
|
+{
|
|
+ device_create_file(dev, &dev_attr_gphy_test);
|
|
+ device_create_file(dev, &dev_attr_mii_read);
|
|
+ device_create_file(dev, &dev_attr_mii_write);
|
|
+ device_create_file(dev, &dev_attr_loopback);
|
|
+ device_create_file(dev, &dev_attr_tx_delay);
|
|
+ device_create_file(dev, &dev_attr_rx_delay);
|
|
+ device_create_file(dev, &dev_attr_extra_tx_stats);
|
|
+ device_create_file(dev, &dev_attr_extra_rx_stats);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_sysfs_destroy(struct device *dev)
|
|
+{
|
|
+ device_remove_file(dev, &dev_attr_gphy_test);
|
|
+ device_remove_file(dev, &dev_attr_mii_read);
|
|
+ device_remove_file(dev, &dev_attr_mii_write);
|
|
+ device_remove_file(dev, &dev_attr_loopback);
|
|
+ device_remove_file(dev, &dev_attr_tx_delay);
|
|
+ device_remove_file(dev, &dev_attr_rx_delay);
|
|
+ device_remove_file(dev, &dev_attr_extra_tx_stats);
|
|
+ device_remove_file(dev, &dev_attr_extra_rx_stats);
|
|
+}
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA)
|
|
+
|
|
+#define GMAC_WRITE _IOWR('X', 1, unsigned int)
|
|
+#define GMAC_READ _IOWR('X', 2, unsigned int)
|
|
+
|
|
+static int sunxi_gmac_fops_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct miscdevice *mdev = file->private_data;
|
|
+ struct sunxi_gmac *chip = container_of(mdev, struct sunxi_gmac, mdev);
|
|
+
|
|
+ file->private_data = chip;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_gmac_fops_release(struct inode *inode, struct file *file)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct sk_buff *sunxi_gmac_skb_compose(struct sunxi_gmac *chip)
|
|
+{
|
|
+ struct sk_buff *skb;
|
|
+ u8 *skb_data, *data = chip->metadata_buff;
|
|
+ u32 len = chip->ndev->mtu;
|
|
+ const u8 markbits = 4;
|
|
+
|
|
+ skb = alloc_skb(len, GFP_KERNEL);
|
|
+ if (!skb)
|
|
+ return NULL;
|
|
+
|
|
+ skb_data = __skb_put(skb, len);
|
|
+
|
|
+ /*
|
|
+ * compose broadcast skb
|
|
+ * dest mac : FF-FF-FF-FF-FF-FF
|
|
+ * src mac : FE-FF-FF-FF-FF-FF
|
|
+ * protocal : 08-00
|
|
+ * metadata mark: AA-AA-AA-AA
|
|
+ * */
|
|
+ memset(skb_data, 0, len);
|
|
+ memset(skb_data, 0xFF, 2 * ETH_ALEN);
|
|
+ skb_data[ETH_ALEN] = 0xFE;
|
|
+ skb_data[2 * ETH_ALEN] = 0x08;
|
|
+ skb_data[2 * ETH_ALEN + 1] = 0x0;
|
|
+ skb_data = skb_data + (2 * ETH_ALEN + 2);
|
|
+ memset(skb_data, 0xAA, markbits);
|
|
+
|
|
+ skb_data = skb_data + markbits;
|
|
+ memcpy(skb_data, data, len);
|
|
+
|
|
+ skb->queue_mapping = 0;
|
|
+ skb->ip_summed = CHECKSUM_NONE;
|
|
+ skb->dev = chip->ndev;
|
|
+
|
|
+ return skb;
|
|
+}
|
|
+
|
|
+static long sunxi_gmac_fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ struct sunxi_gmac *chip = file->private_data;
|
|
+ struct sk_buff *skb;
|
|
+ int ret;
|
|
+
|
|
+ memset(chip->metadata_buff, 0, chip->metadata_len);
|
|
+ switch (cmd) {
|
|
+ case GMAC_WRITE:
|
|
+ ret = copy_from_user(chip->metadata_buff, (void __user *)arg, chip->metadata_len);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "metadata copy from user err\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+ skb = sunxi_gmac_skb_compose(chip);
|
|
+ if (!skb)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ if (sunxi_gmac_xmit(skb, skb->dev))
|
|
+ return -EBUSY;
|
|
+
|
|
+ break;
|
|
+ case GMAC_READ:
|
|
+ wait_for_completion(&chip->metadata_done);
|
|
+
|
|
+ ret = copy_to_user((void __user *)arg, chip->metadata_buff, chip->metadata_len);
|
|
+ if (ret) {
|
|
+ dev_err(chip->dev, "metadata copy to user err\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ ret = -EFAULT;
|
|
+ dev_err(chip->dev, "Unspported cmd\n");
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+struct file_operations sunxi_gmac_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = sunxi_gmac_fops_open,
|
|
+ .release = sunxi_gmac_fops_release,
|
|
+ .unlocked_ioctl = sunxi_gmac_fops_ioctl,
|
|
+};
|
|
+
|
|
+static int sunxi_gmac_fops_init(struct sunxi_gmac *chip)
|
|
+{
|
|
+ chip->mdev.parent = chip->dev;
|
|
+ chip->mdev.minor = MISC_DYNAMIC_MINOR;
|
|
+ chip->mdev.name = "gmac";
|
|
+ chip->mdev.fops = &sunxi_gmac_fops;
|
|
+
|
|
+ /* MTU includes 4 bytes of mark, so the maximum metadata is MTU - 4 bytes in length */
|
|
+ chip->metadata_len = chip->ndev->mtu - 4;
|
|
+
|
|
+ chip->metadata_buff = devm_kzalloc(chip->dev, (sizeof(char) * chip->metadata_len), GFP_KERNEL);
|
|
+ if (!chip->metadata_buff)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ init_completion(&chip->metadata_done);
|
|
+
|
|
+ return misc_register(&chip->mdev);
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_fops_exit(struct sunxi_gmac *chip)
|
|
+{
|
|
+ kfree(chip->metadata_buff);
|
|
+ misc_deregister(&chip->mdev);
|
|
+}
|
|
+
|
|
+#endif /* CONFIG_SUNXI55I_GMAC_METADATA */
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+static struct sunxi_gmac_ephy_ops sunxi_gmac_ephy_ops_v1 = {
|
|
+ .resource_get = sunxi_gmac_ephy_v1_resource_get,
|
|
+ .resource_put = sunxi_gmac_ephy_v1_resource_put,
|
|
+ .hardware_init = sunxi_gmac_ephy_v1_hardware_init,
|
|
+ .hardware_deinit = sunxi_gmac_ephy_v1_hardware_deinit,
|
|
+};
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+
|
|
+static const struct of_device_id sunxi_gmac_of_match[] = {
|
|
+ {.compatible = "allwinner,sunxi-gmac",},
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+ {.compatible = "allwinner,sunxi-gmac-ephy-v1", .data = &sunxi_gmac_ephy_ops_v1, },
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, sunxi_gmac_of_match);
|
|
+
|
|
+/**
|
|
+ * sunxi_gmac_probe - GMAC device probe
|
|
+ * @pdev: The SUNXI GMAC device that we are removing
|
|
+ *
|
|
+ * Called when probing for GMAC device. We get details of instances and
|
|
+ * resource information from platform init and register a network device
|
|
+ * and allocate resources necessary for driver to perform
|
|
+ *
|
|
+ */
|
|
+static int sunxi_gmac_probe(struct platform_device *pdev)
|
|
+{
|
|
+ int ret;
|
|
+ struct net_device *ndev;
|
|
+ struct sunxi_gmac *chip;
|
|
+ const struct of_device_id *match;
|
|
+
|
|
+ dev_dbg(&pdev->dev, "%s() BEGIN\n", __func__);
|
|
+
|
|
+ match = of_match_device(sunxi_gmac_of_match, &pdev->dev);
|
|
+ if (!match) {
|
|
+ dev_err(&pdev->dev, "gmac probe match device failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
|
|
+ pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
|
|
+
|
|
+ ndev = alloc_etherdev(sizeof(*chip));
|
|
+ if (!ndev) {
|
|
+ dev_err(&pdev->dev, "Error: Allocate network device failed\n");
|
|
+ ret = -ENOMEM;
|
|
+ goto alloc_etherdev_err;
|
|
+ }
|
|
+ SET_NETDEV_DEV(ndev, &pdev->dev);
|
|
+
|
|
+ chip = netdev_priv(ndev);
|
|
+ platform_set_drvdata(pdev, ndev);
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+ chip->ephy_ops = (struct sunxi_gmac_ephy_ops *)match->data;
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+ chip->ndev = ndev;
|
|
+ chip->dev = &pdev->dev;
|
|
+ ret = sunxi_gmac_resource_get(pdev);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Error: Get gmac hardware resource failed\n");
|
|
+ goto resource_get_err;
|
|
+ }
|
|
+
|
|
+ ret = sunxi_gmac_hardware_init(pdev);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Error: Init gmac hardware resource failed\n");
|
|
+ goto hardware_init_err;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * setup the netdevice
|
|
+ * fillup netdevice base memory and ops
|
|
+ * fillup netdevice ethtool ops
|
|
+ */
|
|
+ ether_setup(ndev);
|
|
+ ndev->netdev_ops = &sunxi_gmac_netdev_ops;
|
|
+ netdev_set_default_ethtool_ops(ndev, &sunxi_gmac_ethtool_ops);
|
|
+ ndev->base_addr = (unsigned long)chip->base;
|
|
+
|
|
+ /* fillup netdevice features and flags */
|
|
+ ndev->hw_features = NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_IP_CSUM |
|
|
+ NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM | NETIF_F_GRO;
|
|
+ ndev->features |= ndev->hw_features;
|
|
+ ndev->hw_features |= NETIF_F_LOOPBACK;
|
|
+ ndev->priv_flags |= IFF_UNICAST_FLT;
|
|
+ ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
|
|
+ ndev->max_mtu = SUNXI_GMAC_MAX_MTU_SZ;
|
|
+
|
|
+ /* add napi poll method */
|
|
+ netif_napi_add(ndev, &chip->napi, sunxi_gmac_poll);
|
|
+
|
|
+ spin_lock_init(&chip->universal_lock);
|
|
+ spin_lock_init(&chip->tx_lock);
|
|
+
|
|
+ ret = register_netdev(ndev);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Error: Register %s failed\n", ndev->name);
|
|
+ goto register_err;
|
|
+ }
|
|
+
|
|
+//todo #ifdef MODULE
|
|
+// get_custom_mac_address(0, "eth", mac_str);
|
|
+//#endif
|
|
+ /* Before open the device, the mac address should be set */
|
|
+ sunxi_gmac_check_addr(ndev, mac_str);
|
|
+
|
|
+ memcpy(ndev->dev_addr_shadow, ndev->dev_addr, 18);
|
|
+
|
|
+ ret = sunxi_gmac_dma_desc_init(ndev);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Error: Init dma descriptor failed\n");
|
|
+ goto init_dma_desc_err;
|
|
+ }
|
|
+
|
|
+ sunxi_gmac_sysfs_create(&pdev->dev);
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA)
|
|
+ ret = sunxi_gmac_fops_init(chip);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "Error: Init gmac class failed\n");
|
|
+ goto fops_init_err;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ dev_dbg(&pdev->dev, "%s() SUCCESS\n", __func__);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA)
|
|
+fops_init_err:
|
|
+ sunxi_gmac_sysfs_destroy(&pdev->dev);
|
|
+#endif
|
|
+init_dma_desc_err:
|
|
+ unregister_netdev(ndev);
|
|
+register_err:
|
|
+ netif_napi_del(&chip->napi);
|
|
+ sunxi_gmac_hardware_deinit(pdev);
|
|
+hardware_init_err:
|
|
+ sunxi_gmac_resource_put(pdev);
|
|
+resource_get_err:
|
|
+ platform_set_drvdata(pdev, NULL);
|
|
+ free_netdev(ndev);
|
|
+alloc_etherdev_err:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void sunxi_gmac_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct net_device *ndev = platform_get_drvdata(pdev);
|
|
+ struct sunxi_gmac *chip = netdev_priv(ndev);
|
|
+
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_GMAC_METADATA)
|
|
+ sunxi_gmac_fops_exit(chip);
|
|
+#endif
|
|
+ sunxi_gmac_sysfs_destroy(&pdev->dev);
|
|
+ sunxi_gmac_dma_desc_deinit(chip);
|
|
+ unregister_netdev(ndev);
|
|
+ netif_napi_del(&chip->napi);
|
|
+ sunxi_gmac_hardware_deinit(pdev);
|
|
+ sunxi_gmac_resource_put(pdev);
|
|
+ platform_set_drvdata(pdev, NULL);
|
|
+ free_netdev(ndev);
|
|
+}
|
|
+
|
|
+static struct platform_driver sunxi_gmac_driver = {
|
|
+ .probe = sunxi_gmac_probe,
|
|
+ .remove = sunxi_gmac_remove,
|
|
+#if IS_ENABLED(CONFIG_SUNXI55I_EPHY)
|
|
+ .shutdown = sunxi_gmac_shutdown,
|
|
+#endif /* CONFIG_SUNXI55I_EPHY */
|
|
+ .driver = {
|
|
+ .name = "sunxi-gmac",
|
|
+ .owner = THIS_MODULE,
|
|
+ .pm = &sunxi_gmac_pm_ops,
|
|
+ .of_match_table = sunxi_gmac_of_match,
|
|
+ },
|
|
+};
|
|
+module_platform_driver(sunxi_gmac_driver);
|
|
+
|
|
+#ifndef MODULE
|
|
+static int __init sunxi_gmac_set_mac_addr(char *str)
|
|
+{
|
|
+ char *p = str;
|
|
+
|
|
+ /**
|
|
+ * mac address: xx:xx:xx:xx:xx:xx
|
|
+ * The reason why memcpy 18 bytes is
|
|
+ * the `/0`.
|
|
+ */
|
|
+ if (str && strlen(str))
|
|
+ memcpy(mac_str, p, MAC_ADDR_LEN);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+/* TODO: When used more than one mac,
|
|
+ * parsing the mac address becomes a problem.
|
|
+ * Maybe use this way: mac0_addr=, mac1_addr=
|
|
+ */
|
|
+__setup("mac0_addr=", sunxi_gmac_set_mac_addr);
|
|
+#endif /* MODULE */
|
|
+
|
|
+MODULE_DESCRIPTION("Allwinner GMAC driver");
|
|
+MODULE_AUTHOR("xuminghui <xuminghui@allwinnertech.com>");
|
|
+MODULE_AUTHOR("Piotr Oniszczuk <piotr.oniszczuk@gmail.com>");
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
+MODULE_VERSION(SUNXI_GMAC_MODULE_VERSION);
|
|
diff --speed-large-files --no-dereference --minimal -Naur linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-mdio.c linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-mdio.c
|
|
--- linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-mdio.c 1970-01-01 01:00:00.000000000 +0100
|
|
+++ linux-6.12.10/drivers/net/ethernet/allwinner/gmac/sunxi-mdio.c 2024-12-11 14:37:13.080084379 +0100
|
|
@@ -0,0 +1,434 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
+/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */
|
|
+/*
|
|
+ * Allwinner GMAC MDIO interface driver
|
|
+ *
|
|
+ * Copyright 2022 Allwinnertech
|
|
+ *
|
|
+ * This file is licensed under the terms of the GNU General Public
|
|
+ * License version 2. This program is licensed "as is" without any
|
|
+ * warranty of any kind, whether express or implied.
|
|
+ */
|
|
+
|
|
+/* #define DEBUG */
|
|
+#include <linux/delay.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/of_mdio.h>
|
|
+#include <linux/phy.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/iopoll.h>
|
|
+
|
|
+#define SUNXI_MDIO_CONFIG 0x0
|
|
+#define SUNXI_MDIO_DATA 0x4
|
|
+
|
|
+#define SUNXI_MDIO_BUSY 0x00000001
|
|
+#define SUNXI_MDIO_WRITE 0x00000002
|
|
+#define SUNXI_MDIO_PHY_MASK 0x0000FFC0
|
|
+#define SUNXI_MDIO_CR_MASK 0x0000001
|
|
+#define SUNXI_MDIO_CLK 0x00000008
|
|
+#define SUNXI_MDIO_MDC_DIV 0x3
|
|
+
|
|
+/* bits 4 3 2 | AHB1 Clock | MDC Clock
|
|
+ * -------------------------------------------------------
|
|
+ * 0 0 0 | 60 ~ 100 MHz | div-42
|
|
+ * 0 0 1 | 100 ~ 150 MHz | div-62
|
|
+ * 0 1 0 | 20 ~ 35 MHz | div-16
|
|
+ * 0 1 1 | 35 ~ 60 MHz | div-26
|
|
+ * 1 0 0 | 150 ~ 250 MHz | div-102
|
|
+ * 1 0 1 | 250 ~ 300 MHz | div-124
|
|
+ * 1 1 x | Reserved |
|
|
+ */
|
|
+#define SUNXI_MDIO_MDC_DIV_RATIO_M 0x07
|
|
+#define SUNXI_MDIO_MDC_DIV_RATIO_M_BIT 20
|
|
+#define SUNXI_MDIO_PHY_ADDR 0x0001F000
|
|
+#define SUNXI_MDIO_PHY_ADDR_OFFSET 12
|
|
+#define SUNXI_MDIO_PHY_REG 0x000007F0
|
|
+#define SUNXI_MDIO_PHY_REG_OFFSET 4
|
|
+#define SUNXI_MDIO_RESET 0x4
|
|
+#define SUNXI_MDIO_RESET_OFFSET 2
|
|
+
|
|
+#define SUNXI_MDIO_WR_TIMEOUT 10 /* ms */
|
|
+
|
|
+struct mii_reg_dump {
|
|
+ u32 addr;
|
|
+ u16 reg;
|
|
+ u16 value;
|
|
+};
|
|
+
|
|
+struct sunxi_mdio {
|
|
+ struct device *dev;
|
|
+ void __iomem *base;
|
|
+};
|
|
+
|
|
+struct mii_reg_dump mii_reg;
|
|
+/**
|
|
+ * sunxi_parse_read_str - parse the input string for write attri.
|
|
+ * @str: string to be parsed, eg: "0x00 0x01".
|
|
+ * @addr: store the reg address. eg: 0x00.
|
|
+ * @reg: store the expect value. eg: 0x01.
|
|
+ *
|
|
+ * return 0 if success, otherwise failed.
|
|
+ */
|
|
+static int sunxi_parse_read_str(char *str, u16 *addr, u16 *reg)
|
|
+{
|
|
+ char *ptr = str;
|
|
+ char *tstr = NULL;
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * Skip the leading whitespace, find the true split symbol.
|
|
+ * And it must be 'address value'.
|
|
+ */
|
|
+ tstr = strim(str);
|
|
+ ptr = strchr(tstr, ' ');
|
|
+ if (!ptr)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /*
|
|
+ * Replaced split symbol with a %NUL-terminator temporary.
|
|
+ * Will be fixed at end.
|
|
+ */
|
|
+ *ptr = '\0';
|
|
+ ret = kstrtos16(tstr, 16, addr);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ ret = kstrtos16(skip_spaces(ptr + 1), 16, reg);
|
|
+
|
|
+out:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_parse_write_str - parse the input string for compare attri.
|
|
+ * @str: string to be parsed, eg: "0x00 0x11 0x11".
|
|
+ * @addr: store the address. eg: 0x00.
|
|
+ * @reg: store the reg. eg: 0x11.
|
|
+ * @val: store the value. eg: 0x11.
|
|
+ *
|
|
+ * return 0 if success, otherwise failed.
|
|
+ */
|
|
+static int sunxi_parse_write_str(char *str, u16 *addr,
|
|
+ u16 *reg, u16 *val)
|
|
+{
|
|
+ u16 result_addr[3] = { 0 };
|
|
+ char *ptr = str;
|
|
+ char *ptr2 = NULL;
|
|
+ int i, ret = 0;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(result_addr); i++) {
|
|
+ ptr = skip_spaces(ptr);
|
|
+ ptr2 = strchr(ptr, ' ');
|
|
+ if (ptr2)
|
|
+ *ptr2 = '\0';
|
|
+
|
|
+ ret = kstrtou16(ptr, 16, &result_addr[i]);
|
|
+
|
|
+ if (!ptr2 || ret)
|
|
+ break;
|
|
+
|
|
+ ptr = ptr2 + 1;
|
|
+ }
|
|
+
|
|
+ *addr = result_addr[0];
|
|
+ *reg = result_addr[1];
|
|
+ *val = result_addr[2];
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Wait until any existing MII operation is complete
|
|
+ * Read 0 indicate finish in read or write operation
|
|
+ * Read 1 indicate busy
|
|
+ * */
|
|
+static void sunxi_mdio_busy_wait(struct sunxi_mdio *chip)
|
|
+{
|
|
+ unsigned long timeout = jiffies + msecs_to_jiffies(SUNXI_MDIO_WR_TIMEOUT);
|
|
+ u32 reg;
|
|
+
|
|
+ do {
|
|
+ reg = readl(chip->base + SUNXI_MDIO_CONFIG);
|
|
+
|
|
+ if ((reg & SUNXI_MDIO_BUSY) != 1)
|
|
+ break;
|
|
+
|
|
+ } while (time_before(jiffies, timeout));
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_mdio_read - GMAC MII bus read func
|
|
+ * @bus: mii bus struct
|
|
+ * @phyaddr: phy address
|
|
+ * @phyreg: phy register
|
|
+ *
|
|
+ * Called when phy_write is used.
|
|
+ *
|
|
+ * Returns reg value for specific phy register.
|
|
+ */
|
|
+static int sunxi_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
|
|
+{
|
|
+ unsigned int value = 0;
|
|
+ struct sunxi_mdio *chip = bus->priv;
|
|
+
|
|
+ /* Mask the MDC_DIV_RATIO */
|
|
+ value |= ((SUNXI_MDIO_MDC_DIV & SUNXI_MDIO_MDC_DIV_RATIO_M) << SUNXI_MDIO_MDC_DIV_RATIO_M_BIT);
|
|
+ value |= (((phyaddr << SUNXI_MDIO_PHY_ADDR_OFFSET) & (SUNXI_MDIO_PHY_ADDR)) |
|
|
+ ((phyreg << SUNXI_MDIO_PHY_REG_OFFSET) & (SUNXI_MDIO_PHY_REG)) |
|
|
+ SUNXI_MDIO_BUSY);
|
|
+
|
|
+ writel(value, chip->base + SUNXI_MDIO_CONFIG);
|
|
+
|
|
+ sunxi_mdio_busy_wait(chip);
|
|
+
|
|
+ return (int)readl(chip->base + SUNXI_MDIO_DATA);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * sunxi_mdio_write - GMAC MII bus write func
|
|
+ * @bus: mii bus struct
|
|
+ * @phyaddr: phy address
|
|
+ * @phyreg: phy register
|
|
+ * @data: the value to be written to the register
|
|
+ *
|
|
+ * Called when phy_wirte is used.
|
|
+ *
|
|
+ * Returns 0 for a successful open, or appropriate error code
|
|
+ */
|
|
+static int sunxi_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg, unsigned short data)
|
|
+{
|
|
+ unsigned int value;
|
|
+ struct sunxi_mdio *chip = bus->priv;
|
|
+
|
|
+ value = ((SUNXI_MDIO_MDC_DIV_RATIO_M << SUNXI_MDIO_MDC_DIV_RATIO_M_BIT) & readl(chip->base+ SUNXI_MDIO_CONFIG)) |
|
|
+ (SUNXI_MDIO_MDC_DIV << SUNXI_MDIO_MDC_DIV_RATIO_M_BIT);
|
|
+ value |= (((phyaddr << SUNXI_MDIO_PHY_ADDR_OFFSET) & (SUNXI_MDIO_PHY_ADDR)) |
|
|
+ ((phyreg << SUNXI_MDIO_PHY_REG_OFFSET) & (SUNXI_MDIO_PHY_REG))) |
|
|
+ SUNXI_MDIO_WRITE | SUNXI_MDIO_BUSY;
|
|
+
|
|
+ sunxi_mdio_busy_wait(chip);
|
|
+
|
|
+ /* Set the MII address register to write */
|
|
+ writel(data, chip->base + SUNXI_MDIO_DATA);
|
|
+ writel(value, chip->base + SUNXI_MDIO_CONFIG);
|
|
+
|
|
+ sunxi_mdio_busy_wait(chip);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_mdio_reset(struct mii_bus *bus)
|
|
+{
|
|
+ struct sunxi_mdio *chip = bus->priv;
|
|
+
|
|
+ writel((SUNXI_MDIO_RESET << SUNXI_MDIO_RESET_OFFSET), chip->base + SUNXI_MDIO_CONFIG);
|
|
+
|
|
+ sunxi_mdio_busy_wait(chip);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_mdio_read_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct mii_bus *bus = platform_get_drvdata(pdev);
|
|
+
|
|
+ mii_reg.value = sunxi_mdio_read(bus, mii_reg.addr, mii_reg.reg);
|
|
+ return sprintf(buf, "ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n",
|
|
+ mii_reg.addr, mii_reg.reg, mii_reg.value);
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_mdio_read_store(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ int ret = 0;
|
|
+ u16 reg, addr;
|
|
+ char *ptr;
|
|
+
|
|
+ ptr = (char *)buf;
|
|
+
|
|
+ if (!dev) {
|
|
+ pr_err("Argment is invalid\n");
|
|
+ return count;
|
|
+ }
|
|
+
|
|
+ ret = sunxi_parse_read_str(ptr, &addr, ®);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mii_reg.addr = addr;
|
|
+ mii_reg.reg = reg;
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_mdio_write_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct platform_device *pdev = to_platform_device(dev);
|
|
+ struct mii_bus *bus = platform_get_drvdata(pdev);
|
|
+ u16 bef_val, aft_val;
|
|
+
|
|
+ bef_val = sunxi_mdio_read(bus, mii_reg.addr, mii_reg.reg);
|
|
+ sunxi_mdio_write(bus, mii_reg.addr, mii_reg.reg, mii_reg.value);
|
|
+ aft_val = sunxi_mdio_read(bus, mii_reg.addr, mii_reg.reg);
|
|
+ return sprintf(buf, "before ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n"
|
|
+ "after ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n",
|
|
+ mii_reg.addr, mii_reg.reg, bef_val,
|
|
+ mii_reg.addr, mii_reg.reg, aft_val);
|
|
+}
|
|
+
|
|
+static ssize_t sunxi_mdio_write_store(struct device *dev,
|
|
+ struct device_attribute *attr, const char *buf, size_t count)
|
|
+{
|
|
+ int ret = 0;
|
|
+ u16 reg, addr, val;
|
|
+ char *ptr;
|
|
+
|
|
+ ptr = (char *)buf;
|
|
+
|
|
+ ret = sunxi_parse_write_str(ptr, &addr, ®, &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mii_reg.reg = reg;
|
|
+ mii_reg.addr = addr;
|
|
+ mii_reg.value = val;
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(mii_read, 0664, sunxi_mdio_read_show, sunxi_mdio_read_store);
|
|
+static DEVICE_ATTR(mii_write, 0664, sunxi_mdio_write_show, sunxi_mdio_write_store);
|
|
+
|
|
+static void sunxi_mdio_sysfs_create(struct device *dev)
|
|
+{
|
|
+ device_create_file(dev, &dev_attr_mii_read);
|
|
+ device_create_file(dev, &dev_attr_mii_write);
|
|
+}
|
|
+
|
|
+static void sunxi_mdio_sysfs_destroy(struct device *dev)
|
|
+{
|
|
+ device_remove_file(dev, &dev_attr_mii_read);
|
|
+ device_remove_file(dev, &dev_attr_mii_write);
|
|
+}
|
|
+/**
|
|
+ * sunxi_mdio_probe - GMAC MII bus probe func
|
|
+ *
|
|
+ * sunxi mdio probe must run after sunxi emac probe,
|
|
+ * because mdio clk was enabled in emac driver.
|
|
+ */
|
|
+static int sunxi_mdio_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct mii_bus *bus;
|
|
+ struct sunxi_mdio *chip;
|
|
+ int ret;
|
|
+#ifdef DEBUG
|
|
+ struct phy_device *phy;
|
|
+ int addr;
|
|
+#endif
|
|
+
|
|
+ dev_dbg(dev, "%s() BEGIN\n", __func__);
|
|
+
|
|
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
|
|
+ if (!chip)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ bus = mdiobus_alloc_size(sizeof(*bus));
|
|
+ if (!bus) {
|
|
+ dev_err(dev, "Error: alloc mii bus failed\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ bus->name = dev_name(dev);
|
|
+ bus->read = sunxi_mdio_read;
|
|
+ bus->write = sunxi_mdio_write;
|
|
+ bus->reset = sunxi_mdio_reset;
|
|
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
|
|
+ bus->parent = &pdev->dev;
|
|
+ bus->priv = chip;
|
|
+
|
|
+ chip->dev = dev;
|
|
+ chip->base = of_iomap(np, 0);
|
|
+ if (IS_ERR(chip->base)) {
|
|
+ ret = PTR_ERR(chip->base);
|
|
+ goto iomap_err;
|
|
+ }
|
|
+
|
|
+ ret = of_mdiobus_register(bus, np);
|
|
+ if (ret < 0)
|
|
+ goto mdio_register_err;
|
|
+
|
|
+ platform_set_drvdata(pdev, bus);
|
|
+
|
|
+ sunxi_mdio_sysfs_create(dev);
|
|
+
|
|
+#ifdef DEBUG
|
|
+ /* scan and dump the bus */
|
|
+ for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
|
|
+ phy = mdiobus_get_phy(bus, addr);
|
|
+ if (phy)
|
|
+ dev_info(dev, "PHY ID: 0x%08x, ADDR: 0x%x, DEVICE: %s, DRIVER: %s\n",
|
|
+ phy->phy_id, addr, phydev_name(phy),
|
|
+ phy->drv ? phy->drv->name : "Generic PHY");
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ dev_dbg(dev, "%s() SUCCESS\n", __func__);
|
|
+ return 0;
|
|
+
|
|
+mdio_register_err:
|
|
+ iounmap(chip->base);
|
|
+iomap_err:
|
|
+ mdiobus_free(bus);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void sunxi_mdio_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct mii_bus *bus = platform_get_drvdata(pdev);
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct sunxi_mdio *chip = bus->priv;
|
|
+
|
|
+ sunxi_mdio_sysfs_destroy(dev);
|
|
+ mdiobus_unregister(bus);
|
|
+ iounmap(chip->base);
|
|
+ mdiobus_free(bus);
|
|
+}
|
|
+
|
|
+static const struct of_device_id sunxi_mdio_dt_ids[] = {
|
|
+ { .compatible = "allwinner,sunxi-mdio" },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, sunxi_mdio_dt_ids);
|
|
+
|
|
+static struct platform_driver sunxi_mdio_driver = {
|
|
+ .probe = sunxi_mdio_probe,
|
|
+ .remove = sunxi_mdio_remove,
|
|
+ .driver = {
|
|
+ .name = "sunxi-mdio",
|
|
+ .of_match_table = sunxi_mdio_dt_ids,
|
|
+ },
|
|
+};
|
|
+
|
|
+static int __init sunxi_mdio_init(void)
|
|
+{
|
|
+ return platform_driver_register(&sunxi_mdio_driver);
|
|
+}
|
|
+late_initcall(sunxi_mdio_init);
|
|
+
|
|
+static void __exit sunxi_mdio_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&sunxi_mdio_driver);
|
|
+}
|
|
+module_exit(sunxi_mdio_exit);
|
|
+
|
|
+MODULE_DESCRIPTION("Allwinner GMAC MDIO interface driver");
|
|
+MODULE_AUTHOR("xuminghui <xuminghui@allwinnertech.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_VERSION("1.0.1");
|
|
diff --speed-large-files --no-dereference --minimal -Naur linux-6.12.10/drivers/net/ethernet/allwinner/Kconfig linux-6.12.10/drivers/net/ethernet/allwinner/Kconfig
|
|
--- linux-6.12.10/drivers/net/ethernet/allwinner/Kconfig 2025-01-23 10:27:54.404056276 +0100
|
|
+++ linux-6.12.10/drivers/net/ethernet/allwinner/Kconfig 2025-01-23 09:26:21.877290265 +0100
|
|
@@ -35,5 +35,6 @@
|
|
will be called sun4i-emac.
|
|
|
|
source "drivers/net/ethernet/allwinner/gmac-200/Kconfig"
|
|
+source "drivers/net/ethernet/allwinner/gmac/Kconfig"
|
|
|
|
endif # NET_VENDOR_ALLWINNER
|
|
diff --speed-large-files --no-dereference --minimal -Naur linux-6.12.10/drivers/net/ethernet/allwinner/Makefile linux-6.12.10/drivers/net/ethernet/allwinner/Makefile
|
|
--- linux-6.12.10/drivers/net/ethernet/allwinner/Makefile 2025-01-23 10:27:54.404056276 +0100
|
|
+++ linux-6.12.10/drivers/net/ethernet/allwinner/Makefile 2025-01-23 09:28:49.753960900 +0100
|
|
@@ -5,3 +5,4 @@
|
|
|
|
obj-$(CONFIG_SUN4I_EMAC) += sun4i-emac.o
|
|
obj-$(CONFIG_NET_VENDOR_ALLWINNER) += gmac-200/
|
|
+obj-$(CONFIG_NET_VENDOR_ALLWINNER) += gmac/
|