armbian_build/patch/kernel/archive/sunxi-6.7/patches.armbian/drv-net-gmac-sun50i-h616-gmac.patch

4519 lines
122 KiB
Diff

From cc5c06ad122ceb42eb1587347503fb0fff9f275f Mon Sep 17 00:00:00 2001
From: chraac <chraac@gmail.com>
Date: Wed, 1 May 2024 14:32:00 +0800
Subject: [PATCH] driver: allwinner h618 emac
commit:
97b476246bc79756423cf6c1d4907606c9633d4b
---
drivers/gpio/gpiolib-of.c | 29 +-
drivers/mfd/Kconfig | 10 +
drivers/mfd/Makefile | 1 +
drivers/mfd/sunxi-ac200.c | 288 +++
drivers/net/ethernet/allwinner/Kconfig | 8 +
drivers/net/ethernet/allwinner/Makefile | 2 +
drivers/net/ethernet/allwinner/sunxi-gmac.c | 2219 +++++++++++++++++
drivers/net/ethernet/allwinner/sunxi-gmac.h | 270 ++
.../net/ethernet/allwinner/sunxi_gmac_ops.c | 768 ++++++
drivers/net/phy/Kconfig | 8 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/sunxi-ephy.c | 518 ++++
include/linux/mfd/ac200.h | 213 ++
include/linux/of_gpio.h | 18 +
14 files changed, 4338 insertions(+), 15 deletions(-)
create mode 100644 drivers/mfd/sunxi-ac200.c
create mode 100644 drivers/net/ethernet/allwinner/sunxi-gmac.c
create mode 100644 drivers/net/ethernet/allwinner/sunxi-gmac.h
create mode 100644 drivers/net/ethernet/allwinner/sunxi_gmac_ops.c
create mode 100644 drivers/net/phy/sunxi-ephy.c
create mode 100644 include/linux/mfd/ac200.h
diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
index d9525d95e818..6842f90f9efe 100644
--- a/drivers/gpio/gpiolib-of.c
+++ b/drivers/gpio/gpiolib-of.c
@@ -25,21 +25,6 @@
#include "gpiolib.h"
#include "gpiolib-of.h"
-/*
- * This is Linux-specific flags. By default controllers' and Linux' mapping
- * match, but GPIO controllers are free to translate their own flags to
- * Linux-specific in their .xlate callback. Though, 1:1 mapping is recommended.
- */
-enum of_gpio_flags {
- OF_GPIO_ACTIVE_LOW = 0x1,
- OF_GPIO_SINGLE_ENDED = 0x2,
- OF_GPIO_OPEN_DRAIN = 0x4,
- OF_GPIO_TRANSITORY = 0x8,
- OF_GPIO_PULL_UP = 0x10,
- OF_GPIO_PULL_DOWN = 0x20,
- OF_GPIO_PULL_DISABLE = 0x40,
-};
-
/**
* of_gpio_named_count() - Count GPIOs for a device
* @np: device node to count GPIOs for
@@ -398,6 +383,20 @@ static struct gpio_desc *of_get_named_gpiod_flags(const struct device_node *np,
return desc;
}
+int of_get_named_gpio_flags(const struct device_node *np, const char *list_name,
+ int index, enum of_gpio_flags *flags)
+{
+ struct gpio_desc *desc;
+
+ desc = of_get_named_gpiod_flags(np, list_name, index, flags);
+
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+ else
+ return desc_to_gpio(desc);
+}
+EXPORT_SYMBOL_GPL(of_get_named_gpio_flags);
+
/**
* of_get_named_gpio() - Get a GPIO number to use with GPIO API
* @np: device node to get GPIO from
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index caed5e75d11f..e532334e024f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -203,6 +203,16 @@ config MFD_AC200
This driver include only the core APIs. You have to select individual
components like Ethernet PHY or codec under the corresponding menus.
+config MFD_AC200_SUNXI
+ tristate "X-Powers AC200 (Sunxi)"
+ select MFD_CORE
+ depends on I2C
+ depends on PWM_SUNXI_ENHANCE
+ help
+ If you say Y here you get support for the X-Powers AC200 IC.
+ This driver include only the core APIs. You have to select individual
+ components like Ethernet PHY or RTC under the corresponding menus.
+
config MFD_AXP20X
tristate
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 0c3b4aaf4eb7..bc180cf44009 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -141,6 +141,7 @@ obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o
obj-$(CONFIG_MFD_AC100) += ac100.o
obj-$(CONFIG_MFD_AC200) += ac200.o
+obj-$(CONFIG_MFD_AC200_SUNXI) += sunxi-ac200.o
obj-$(CONFIG_MFD_AXP20X) += axp20x.o
obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o
obj-$(CONFIG_MFD_AXP20X_RSB) += axp20x-rsb.o
diff --git a/drivers/mfd/sunxi-ac200.c b/drivers/mfd/sunxi-ac200.c
new file mode 100644
index 000000000000..7c5b0e0523cf
--- /dev/null
+++ b/drivers/mfd/sunxi-ac200.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MFD core driver for X-Powers' AC200 IC
+ *
+ * The AC200 is a chip which is co-packaged with Allwinner H6 SoC and
+ * includes analog audio codec, analog TV encoder, ethernet PHY, eFuse
+ * and RTC.
+ *
+ * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/ac200.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/nvmem-consumer.h>
+
+#define SUNXI_AC300_KEY (0x1 << 8)
+
+/* Interrupts */
+#define AC200_IRQ_RTC 0
+#define AC200_IRQ_EPHY 1
+#define AC200_IRQ_TVE 2
+
+/* IRQ enable register */
+#define AC200_SYS_IRQ_ENABLE_OUT_EN BIT(15)
+#define AC200_SYS_IRQ_ENABLE_RTC BIT(12)
+#define AC200_SYS_IRQ_ENABLE_EPHY BIT(8)
+#define AC200_SYS_IRQ_ENABLE_TVE BIT(4)
+
+static const struct regmap_range_cfg ac200_range_cfg[] = {
+ {
+ .range_min = AC200_SYS_VERSION,
+ .range_max = AC200_IC_CHARA1,
+ .selector_reg = AC200_TWI_REG_ADDR_H,
+ .selector_mask = 0xff,
+ .selector_shift = 0,
+ .window_start = 0,
+ .window_len = 256,
+ }
+};
+
+static const struct regmap_config ac200_regmap_config = {
+ .name = "ac200",
+ .reg_bits = 8,
+ .val_bits = 16,
+ .ranges = ac200_range_cfg,
+ .num_ranges = ARRAY_SIZE(ac200_range_cfg),
+ .max_register = AC200_IC_CHARA1,
+};
+
+static const struct regmap_irq ac200_regmap_irqs[] = {
+ REGMAP_IRQ_REG(AC200_IRQ_RTC, 0, AC200_SYS_IRQ_ENABLE_RTC),
+ REGMAP_IRQ_REG(AC200_IRQ_EPHY, 0, AC200_SYS_IRQ_ENABLE_EPHY),
+ REGMAP_IRQ_REG(AC200_IRQ_TVE, 0, AC200_SYS_IRQ_ENABLE_TVE),
+};
+
+static const struct regmap_irq_chip ac200_regmap_irq_chip = {
+ .name = "ac200_irq_chip",
+ .status_base = AC200_SYS_IRQ_STATUS,
+ .mask_base = AC200_SYS_IRQ_ENABLE,
+ .irqs = ac200_regmap_irqs,
+ .num_irqs = ARRAY_SIZE(ac200_regmap_irqs),
+ .num_regs = 1,
+};
+
+static const struct resource ephy_resource[] = {
+ DEFINE_RES_IRQ(AC200_IRQ_EPHY),
+};
+
+static const struct mfd_cell ac200_cells[] = {
+ {
+ .name = "ac200-ephy-sunxi",
+ .num_resources = ARRAY_SIZE(ephy_resource),
+ .resources = ephy_resource,
+ .of_compatible = "x-powers,ac200-ephy-sunxi",
+ },
+ {
+ .name = "acx00-codec",
+ .of_compatible = "x-powers,ac200-codec-sunxi",
+ },
+};
+
+atomic_t ac200_en;
+
+int ac200_enable(void)
+{
+ return atomic_read(&ac200_en);
+}
+
+EXPORT_SYMBOL(ac200_enable);
+
+static uint16_t ephy_caldata = 0;
+
+static int sun50i_ephy_get_calibrate(struct device *dev)
+{
+ struct nvmem_cell *calcell;
+ uint16_t *caldata;
+ size_t callen;
+ int ret = 0;
+
+ calcell = devm_nvmem_cell_get(dev, "calibration");
+ if (IS_ERR(calcell)) {
+ dev_err_probe(dev, PTR_ERR(calcell),
+ "Failed to get calibration nvmem cell (%pe)\n",
+ calcell);
+
+ if (PTR_ERR(calcell) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ goto out;
+ }
+
+ caldata = nvmem_cell_read(calcell, &callen);
+ if (IS_ERR(caldata)) {
+ ret = PTR_ERR(caldata);
+ dev_err(dev, "Failed to read calibration data (%pe)\n",
+ caldata);
+ goto out;
+ }
+
+ ephy_caldata = *caldata;
+ kfree(caldata);
+out:
+ return ret;
+}
+
+uint16_t sun50i_ephy_calibrate_value(void)
+{
+ return ephy_caldata;
+}
+
+EXPORT_SYMBOL(sun50i_ephy_calibrate_value);
+
+static int ac200_i2c_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct ac200_dev *ac200;
+ uint32_t ephy_cal;
+ int ret;
+
+ // 24Mhz clock for both ac200 and ac300 devices
+ ac200 = devm_kzalloc(dev, sizeof(*ac200), GFP_KERNEL);
+ if (!ac200)
+ return -ENOMEM;
+
+ ac200->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(ac200->clk)) {
+ dev_err(dev, "Can't obtain the clock!\n");
+ return PTR_ERR(ac200->clk);
+ }
+
+ ret = clk_prepare_enable(ac200->clk);
+ if (ret) {
+ dev_err(dev, "rclk_prepare_enable failed! \n");
+ return ret;
+ }
+
+ ret = sun50i_ephy_get_calibrate(dev);
+ if (ret) {
+ dev_err(dev, "sun50i get ephy id failed\n");
+ return ret;
+ }
+ ephy_cal = sun50i_ephy_calibrate_value();
+
+ if (ephy_cal & SUNXI_AC300_KEY) {
+ pr_warn("it's ac300, skip the ac200 init!\n");
+ return -EINVAL;
+ } else {
+ pr_warn("it's ac200, ac200 init start!\n");
+ }
+
+ i2c_set_clientdata(i2c, ac200);
+
+ ac200->regmap = devm_regmap_init_i2c(i2c, &ac200_regmap_config);
+ if (IS_ERR(ac200->regmap))
+ {
+ ret = PTR_ERR(ac200->regmap);
+ dev_err(dev, "regmap init failed: %d\n", ret);
+ return ret;
+ }
+
+ /* do a reset to put chip in a known state */
+ ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0);
+ if (ret)
+ {
+ dev_err(dev, "AC200_SYS_CONTROL 0 failed! \n");
+ return ret;
+ }
+ atomic_set(&ac200_en, 0);
+
+ ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 1);
+ if (ret)
+ {
+ dev_err(dev, "AC200_SYS_CONTROL 1 failed! \n");
+ return ret;
+ }
+ atomic_set(&ac200_en, 1);
+
+ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ac200_cells,
+ ARRAY_SIZE(ac200_cells), NULL, 0, NULL);
+ if (ret)
+ {
+ dev_err(dev, "failed to add MFD devices: %d\n", ret);
+ return ret;
+ }
+ else
+ {
+ dev_err(dev, "add MFD devices success! \n");
+ }
+
+ return 0;
+}
+
+static void ac200_i2c_remove(struct i2c_client *i2c)
+{
+ struct ac200_dev *ac200 = i2c_get_clientdata(i2c);
+
+ regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0);
+
+ mfd_remove_devices(&i2c->dev);
+}static void ac200_i2c_shutdown(struct i2c_client *i2c)
+{
+ struct ac200_dev *ac200 = i2c_get_clientdata(i2c);
+
+ regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0);
+}
+
+static int ac200_i2c_suspend(struct device *dev)
+{
+ struct ac200_dev *ac200 = dev_get_drvdata(dev);
+
+ if (!IS_ERR_OR_NULL(ac200->clk))
+ clk_disable_unprepare(ac200->clk);
+
+ atomic_set(&ac200_en, 0);
+ return 0;
+}
+
+static int ac200_i2c_resume(struct device *dev)
+{
+ struct ac200_dev *ac200 = dev_get_drvdata(dev);
+
+ if (!IS_ERR_OR_NULL(ac200->clk))
+ clk_prepare_enable(ac200->clk);
+
+ atomic_set(&ac200_en, 0);
+ msleep(40);
+ regmap_write(ac200->regmap, AC200_SYS_CONTROL, 1);
+ atomic_set(&ac200_en, 1);
+ return 0;
+}
+
+static const struct dev_pm_ops ac200_core_pm_ops = {
+ .suspend_late = ac200_i2c_suspend,
+ .resume_early = ac200_i2c_resume,
+};
+
+static const struct i2c_device_id ac200_ids[] = {
+ { "ac200", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, ac200_ids);
+
+static const struct of_device_id ac200_of_match[] = {
+ { .compatible = "x-powers,ac200-sunxi" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ac200_of_match);
+
+static struct i2c_driver ac200_i2c_driver = {
+ .driver = {
+ .name = "ac200-sunxi",
+ .of_match_table = of_match_ptr(ac200_of_match),
+ .pm = &ac200_core_pm_ops,
+ },
+ .probe = ac200_i2c_probe,
+ .remove = ac200_i2c_remove,
+ .shutdown = ac200_i2c_shutdown,
+ .id_table = ac200_ids,
+};
+module_i2c_driver(ac200_i2c_driver);
+
+MODULE_DESCRIPTION("MFD core driver for AC200");
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/allwinner/Kconfig b/drivers/net/ethernet/allwinner/Kconfig
index 3e81059f8693..36d808810ca4 100644
--- a/drivers/net/ethernet/allwinner/Kconfig
+++ b/drivers/net/ethernet/allwinner/Kconfig
@@ -34,4 +34,12 @@ config SUN4I_EMAC
To compile this driver as a module, choose M here. The module
will be called sun4i-emac.
+config SUNXI_GMAC
+ tristate "Allwinner GMAC support"
+ depends on ARCH_SUNXI
+ depends on OF
+ depends on AC200_PHY_SUNXI
+ select CRC32
+ select MII
+
endif # NET_VENDOR_ALLWINNER
diff --git a/drivers/net/ethernet/allwinner/Makefile b/drivers/net/ethernet/allwinner/Makefile
index ddd5a5079e8a..56b9c434a5b8 100644
--- a/drivers/net/ethernet/allwinner/Makefile
+++ b/drivers/net/ethernet/allwinner/Makefile
@@ -4,3 +4,5 @@
#
obj-$(CONFIG_SUN4I_EMAC) += sun4i-emac.o
+sunxi_gmac-objs := sunxi_gmac_ops.o sunxi-gmac.o
+obj-$(CONFIG_SUNXI_GMAC) += sunxi_gmac.o
diff --git a/drivers/net/ethernet/allwinner/sunxi-gmac.c b/drivers/net/ethernet/allwinner/sunxi-gmac.c
new file mode 100644
index 000000000000..d31b86fc1e8c
--- /dev/null
+++ b/drivers/net/ethernet/allwinner/sunxi-gmac.c
@@ -0,0 +1,2219 @@
+/*
+ * linux/drivers/net/ethernet/allwinner/sunxi_gmac.c
+ *
+ * Copyright © 2016-2018, fuzhaoke
+ * Author: fuzhaoke <fuzhaoke@allwinnertech.com>
+ *
+ * This file is provided under a dual BSD/GPL license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/mii.h>
+#include <linux/gpio.h>
+#include <linux/crc32.h>
+#include <linux/skbuff.h>
+#include <linux/module.h>
+#include <linux/kernel.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/crypto.h>
+#include <crypto/algapi.h>
+#include <crypto/hash.h>
+#include <linux/err.h>
+#include <linux/scatterlist.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_net.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include "sunxi-gmac.h"
+#include <linux/timer.h>
+#include <linux/phylink.h>
+#include <linux/reset.h>
+
+
+int sunxi_get_soc_chipid(unsigned char *chipid);
+
+#define DMA_DESC_RX 256
+#define DMA_DESC_TX 256
+#define BUDGET (dma_desc_rx / 4)
+#define TX_THRESH (dma_desc_tx / 4)
+
+#define HASH_TABLE_SIZE 64
+#define MAX_BUF_SZ (SZ_2K - 1)
+
+#define POWER_CHAN_NUM 3
+
+#undef PKT_DEBUG
+#undef DESC_PRINT
+
+#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 GETH_MAC_ADDRESS "01:02:03:04:05:06"
+static char *mac_str = GETH_MAC_ADDRESS;
+module_param(mac_str, charp, 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 dma_desc_rx = DMA_DESC_RX;
+module_param(dma_desc_rx, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(watchdog, "The number of receive's descriptors");
+
+static int dma_desc_tx = DMA_DESC_TX;
+module_param(dma_desc_tx, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(watchdog, "The number of transmit's descriptors");
+
+/* - 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]");
+
+static unsigned long tx_delay;
+module_param(tx_delay, ulong, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(tx_delay, "Adjust transmit clock delay, value: 0~7");
+
+static unsigned long rx_delay;
+module_param(rx_delay, ulong, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(rx_delay, "Adjust receive clock delay, value: 0~31");
+
+/* whether using ephy_clk */
+static int g_use_ephy_clk;
+static int g_phy_addr;
+
+struct geth_priv {
+ struct dma_desc *dma_tx;
+ struct sk_buff **tx_sk;
+ unsigned int tx_clean;
+ unsigned int tx_dirty;
+ dma_addr_t dma_tx_phy;
+
+ struct reset_control *ephy_rst;
+
+ unsigned long buf_sz;
+
+ struct dma_desc *dma_rx;
+ struct sk_buff **rx_sk;
+ unsigned int rx_clean;
+ unsigned int rx_dirty;
+ dma_addr_t dma_rx_phy;
+
+ struct net_device *ndev;
+ struct device *dev;
+ struct napi_struct napi;
+
+ struct geth_extra_stats xstats;
+
+ struct mii_bus *mii;
+ int link;
+ int speed;
+ int duplex;
+#define INT_PHY 0
+#define EXT_PHY 1
+ int phy_ext;
+ int phy_interface;
+
+ void __iomem *base;
+ void __iomem *base_phy;
+ struct clk *geth_clk;
+ struct clk *ephy_clk;
+ struct pinctrl *pinctrl;
+
+ struct regulator *gmac_power[POWER_CHAN_NUM];
+ bool is_suspend;
+ int phyrst;
+ u8 rst_active_low;
+ /* definition spinlock */
+ spinlock_t lock;
+ spinlock_t tx_lock;
+
+ /* resume work */
+ struct work_struct eth_work;
+};
+
+static u64 geth_dma_mask = DMA_BIT_MASK(32);
+
+void sunxi_udelay(int n)
+{
+ udelay(n);
+}
+
+static int geth_stop(struct net_device *ndev);
+static int geth_open(struct net_device *ndev);
+static void geth_tx_complete(struct geth_priv *priv);
+static void geth_rx_refill(struct net_device *ndev);
+
+#ifdef CONFIG_GETH_ATTRS
+static ssize_t adjust_bgs_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int value = 0;
+ u32 efuse_value;
+ struct net_device *ndev = to_net_dev(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ if (priv->phy_ext == INT_PHY) {
+ value = readl(priv->base_phy) >> 28;
+ if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0)
+ pr_err("get PHY efuse fail!\n");
+ else
+#if defined(CONFIG_ARCH_SUN50IW2)
+ value = value - ((efuse_value >> 24) & 0x0F);
+#else
+ pr_warn("miss config come from efuse!\n");
+#endif
+ }
+
+ return sprintf(buf, "bgs: %d\n", value);
+}
+
+static ssize_t adjust_bgs_write(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int out = 0;
+ struct net_device *ndev = to_net_dev(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+ u32 clk_value = readl(priv->base_phy);
+ u32 efuse_value;
+
+ out = simple_strtoul(buf, NULL, 10);
+
+ if (priv->phy_ext == INT_PHY) {
+ clk_value &= ~(0xF << 28);
+ if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0)
+ pr_err("get PHY efuse fail!\n");
+ else
+#if defined(CONFIG_ARCH_SUN50IW2)
+ clk_value |= (((efuse_value >> 24) & 0x0F) + out) << 28;
+#else
+ pr_warn("miss config come from efuse!\n");
+#endif
+ }
+
+ writel(clk_value, priv->base_phy);
+
+ return count;
+}
+
+static struct device_attribute adjust_reg[] = {
+ __ATTR(adjust_bgs, 0664, adjust_bgs_show, adjust_bgs_write),
+};
+
+static int geth_create_attrs(struct net_device *ndev)
+{
+ int j, ret;
+
+ for (j = 0; j < ARRAY_SIZE(adjust_reg); j++) {
+ ret = device_create_file(&ndev->dev, &adjust_reg[j]);
+ if (ret)
+ goto sysfs_failed;
+ }
+ goto succeed;
+
+sysfs_failed:
+ while (j--)
+ device_remove_file(&ndev->dev, &adjust_reg[j]);
+succeed:
+ return ret;
+}
+#endif
+
+#ifdef DEBUG
+static void desc_print(struct dma_desc *desc, int size)
+{
+#ifdef DESC_PRINT
+ 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
+}
+#endif
+
+static ssize_t gphy_test_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+
+ if (!dev) {
+ pr_err("Argment is invalid\n");
+ return 0;
+ }
+
+ if (!ndev) {
+ pr_err("Net device is null\n");
+ return 0;
+ }
+
+ 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 gphy_test_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+ u16 value = 0;
+ int ret = 0;
+ u16 data = 0;
+
+ if (!dev) {
+ pr_err("Argument is invalid\n");
+ return count;
+ }
+
+ if (!ndev) {
+ pr_err("Net device is null\n");
+ return count;
+ }
+
+ data = sunxi_mdio_read(priv->base, g_phy_addr, MII_CTRL1000);
+
+ ret = kstrtou16(buf, 0, &value);
+ if (ret)
+ return ret;
+
+ if (value >= 0 && value <= 4) {
+ data &= ~(0x7 << 13);
+ data |= value << 13;
+ sunxi_mdio_write(priv->base, g_phy_addr, MII_CTRL1000, data);
+ pr_info("Set MII_CTRL1000(0x09) Reg: 0x%x\n", data);
+ } else {
+ pr_info("unknown value (%d)\n", value);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(gphy_test, 0664, gphy_test_show, gphy_test_store);
+
+static struct mii_reg_dump mii_reg;
+
+static ssize_t mii_read_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct net_device *ndev;
+ struct geth_priv *priv;
+
+ if (!dev) {
+ pr_err("Argment is invalid\n");
+ return 0;
+ }
+
+ ndev = dev_get_drvdata(dev);
+ if (!ndev) {
+ pr_err("Net device is null\n");
+ return 0;
+ }
+
+ priv = netdev_priv(ndev);
+ if (!priv) {
+ pr_err("geth_priv is null\n");
+ return 0;
+ }
+
+ if (!netif_running(ndev)) {
+ pr_warn("eth is down!\n");
+ return 0;
+ }
+
+ mii_reg.value = sunxi_mdio_read(priv->base, 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 mii_read_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct net_device *ndev;
+ struct geth_priv *priv;
+ int ret = 0;
+ u16 reg, addr;
+ char *ptr;
+
+ ptr = (char *)buf;
+
+ if (!dev) {
+ pr_err("Argment is invalid\n");
+ return count;
+ }
+
+ ndev = dev_get_drvdata(dev);
+ if (!ndev) {
+ pr_err("Net device is null\n");
+ return count;
+ }
+
+ priv = netdev_priv(ndev);
+ if (!priv) {
+ pr_err("geth_priv is null\n");
+ return count;
+ }
+
+ if (!netif_running(ndev)) {
+ pr_warn("eth is down!\n");
+ return count;
+ }
+
+ ret = sunxi_parse_read_str(ptr, &addr, &reg);
+ if (ret)
+ return ret;
+
+ mii_reg.addr = addr;
+ mii_reg.reg = reg;
+
+ return count;
+}
+
+static DEVICE_ATTR(mii_read, 0664, mii_read_show, mii_read_store);
+
+static ssize_t mii_write_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct net_device *ndev;
+ struct geth_priv *priv;
+ u16 bef_val, aft_val;
+
+ if (!dev) {
+ pr_err("Argment is invalid\n");
+ return 0;
+ }
+
+ ndev = dev_get_drvdata(dev);
+ if (!ndev) {
+ pr_err("Net device is null\n");
+ return 0;
+ }
+
+ priv = netdev_priv(ndev);
+ if (!priv) {
+ pr_err("geth_priv is null\n");
+ return 0;
+ }
+
+ if (!netif_running(ndev)) {
+ pr_warn("eth is down!\n");
+ return 0;
+ }
+
+ bef_val = sunxi_mdio_read(priv->base, mii_reg.addr, mii_reg.reg);
+ sunxi_mdio_write(priv->base, mii_reg.addr, mii_reg.reg, mii_reg.value);
+ aft_val = sunxi_mdio_read(priv->base, 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 mii_write_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct net_device *ndev;
+ struct geth_priv *priv;
+ int ret = 0;
+ u16 reg, addr, val;
+ char *ptr;
+
+ ptr = (char *)buf;
+
+ if (!dev) {
+ pr_err("Argment is invalid\n");
+ return count;
+ }
+
+ ndev = dev_get_drvdata(dev);
+ if (!ndev) {
+ pr_err("Net device is null\n");
+ return count;
+ }
+
+ priv = netdev_priv(ndev);
+ if (!priv) {
+ pr_err("geth_priv is null\n");
+ return count;
+ }
+
+ if (!netif_running(ndev)) {
+ pr_warn("eth is down!\n");
+ return count;
+ }
+
+ ret = sunxi_parse_write_str(ptr, &addr, &reg, &val);
+ if (ret)
+ return ret;
+
+ mii_reg.reg = reg;
+ mii_reg.addr = addr;
+ mii_reg.value = val;
+
+ return count;
+}
+
+static DEVICE_ATTR(mii_write, 0664, mii_write_show, mii_write_store);
+
+static int geth_power_on(struct geth_priv *priv)
+{
+ int value;
+ int i;
+
+ value = readl(priv->base_phy);
+ if (priv->phy_ext == INT_PHY) {
+ value |= (1 << 15);
+ value &= ~(1 << 16);
+ value |= (3 << 17);
+ } else {
+ value &= ~(1 << 15);
+
+ for (i = 0; i < POWER_CHAN_NUM; i++) {
+ if (IS_ERR_OR_NULL(priv->gmac_power[i]))
+ continue;
+ if (regulator_enable(priv->gmac_power[i]) != 0) {
+ pr_err("gmac-power%d enable error\n", i);
+ return -EINVAL;
+ }
+ }
+ }
+
+ writel(value, priv->base_phy);
+
+ return 0;
+}
+
+static void geth_power_off(struct geth_priv *priv)
+{
+ int value;
+ int i;
+
+ if (priv->phy_ext == INT_PHY) {
+ value = readl(priv->base_phy);
+ value |= (1 << 16);
+ writel(value, priv->base_phy);
+ } else {
+ for (i = 0; i < POWER_CHAN_NUM; i++) {
+ if (IS_ERR_OR_NULL(priv->gmac_power[i]))
+ continue;
+ regulator_disable(priv->gmac_power[i]);
+ }
+ }
+}
+
+/* PHY interface operations */
+static int geth_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
+{
+ struct net_device *ndev = bus->priv;
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ return (int)sunxi_mdio_read(priv->base, phyaddr, phyreg);
+}
+
+static int geth_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg, u16 data)
+{
+ struct net_device *ndev = bus->priv;
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ sunxi_mdio_write(priv->base, phyaddr, phyreg, data);
+
+ return 0;
+}
+
+static int geth_mdio_reset(struct mii_bus *bus)
+{
+ struct net_device *ndev = bus->priv;
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ return sunxi_mdio_reset(priv->base);
+}
+
+static void geth_adjust_link(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct phy_device *phydev = ndev->phydev;
+ unsigned long flags;
+ int new_state = 0;
+
+ if (!phydev)
+ return;
+
+ spin_lock_irqsave(&priv->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 != priv->duplex) {
+ new_state = 1;
+ priv->duplex = phydev->duplex;
+ }
+ /* Flow Control operation */
+ if (phydev->pause)
+ sunxi_flow_ctrl(priv->base, phydev->duplex, flow_ctrl, pause);
+
+ if (phydev->speed != priv->speed) {
+ new_state = 1;
+ priv->speed = phydev->speed;
+ }
+
+ if (priv->link == 0) {
+ new_state = 1;
+ priv->link = phydev->link;
+ }
+
+ if (new_state)
+ sunxi_set_link_mode(priv->base, priv->duplex, priv->speed);
+
+#ifdef LOOPBACK_DEBUG
+ phydev->state = PHY_FORCING;
+#endif
+
+ } else if (priv->link != phydev->link) {
+ new_state = 1;
+ priv->link = 0;
+ priv->speed = 0;
+ priv->duplex = -1;
+ }
+
+ if (new_state)
+ phy_print_status(phydev);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int geth_phy_init(struct net_device *ndev)
+{
+ int value;
+ struct mii_bus *new_bus;
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct phy_device *phydev = ndev->phydev;
+ u32 supported = 0, advertising = 0;
+
+ /* Fixup the phy interface type */
+ if (priv->phy_ext == INT_PHY) {
+ priv->phy_interface = PHY_INTERFACE_MODE_MII;
+ } else {
+ /* If config gpio to reset the phy device, we should reset it */
+ if (gpio_is_valid(priv->phyrst)) {
+ gpio_direction_output(priv->phyrst, priv->rst_active_low);
+ msleep(50);
+ gpio_direction_output(priv->phyrst, !priv->rst_active_low);
+ msleep(50);
+ }
+ }
+
+ new_bus = mdiobus_alloc();
+ if (!new_bus) {
+ netdev_err(ndev, "Failed to alloc new mdio bus\n");
+ return -ENOMEM;
+ }
+
+ new_bus->name = dev_name(priv->dev);
+ new_bus->read = &geth_mdio_read;
+ new_bus->write = &geth_mdio_write;
+ new_bus->reset = &geth_mdio_reset;
+ snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x", new_bus->name, 0);
+
+ new_bus->parent = priv->dev;
+ new_bus->priv = ndev;
+
+ if (mdiobus_register(new_bus)) {
+ pr_err("%s: Cannot register as MDIO bus\n", new_bus->name);
+ goto reg_fail;
+ }
+
+ priv->mii = new_bus;
+
+ {
+ int addr;
+ for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
+ struct phy_device *phydev_tmp = mdiobus_get_phy(new_bus, addr);
+
+ if (IS_ERR_OR_NULL(phydev_tmp) || phydev_tmp->phy_id == 0xffff) {
+ if (!IS_ERR_OR_NULL(phydev_tmp))
+ phy_device_remove(phydev_tmp);
+ phydev_tmp = mdiobus_scan_c22(new_bus, addr);
+ }
+
+ if (IS_ERR_OR_NULL(phydev_tmp) || phydev_tmp->phy_id == 0xffff ||
+ phydev_tmp->phy_id == 0x00 || phydev_tmp->phy_id == AC300_ID)
+ continue;
+
+ if (phydev_tmp->phy_id == EPHY_ID || phydev_tmp->phy_id == IP101G_ID) {
+ phydev = phydev_tmp;
+ g_phy_addr = addr;
+ break;
+ }
+ }
+ }
+
+ if (!phydev) {
+ netdev_err(ndev, "No PHY found!\n");
+ goto err;
+ }
+
+ phy_write(phydev, MII_BMCR, BMCR_RESET);
+ while (BMCR_RESET & phy_read(phydev, MII_BMCR))
+ msleep(30);
+
+ value = phy_read(phydev, MII_BMCR);
+ phy_write(phydev, MII_BMCR, (value & ~BMCR_PDOWN));
+
+ phydev->irq = PHY_POLL;
+
+ value = phy_connect_direct(ndev, phydev, &geth_adjust_link, priv->phy_interface);
+ if (value) {
+ netdev_err(ndev, "Could not attach to PHY\n");
+ goto err;
+ } else {
+ netdev_info(ndev, "%s: Type(%d) PHY ID %08x at %d IRQ %s (%s)\n",
+ ndev->name, phydev->interface, phydev->phy_id,
+ phydev->mdio.addr, "poll", dev_name(&phydev->mdio.dev));
+ }
+
+#if 0
+ phydev->supported &= PHY_GBIT_FEATURES; //kandy 000002cf
+ phydev->advertising = phydev->supported;
+#else
+
+ ethtool_convert_link_mode_to_legacy_u32(&supported, phydev->supported);
+
+ advertising = supported &= *PHY_BASIC_FEATURES; // kandy 000002cf
+
+ ethtool_convert_legacy_u32_to_link_mode(phydev->supported, supported);
+ ethtool_convert_legacy_u32_to_link_mode(phydev->advertising, advertising);
+#endif
+
+ if (priv->phy_ext == INT_PHY) {
+ /* EPHY Initial */
+ phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */
+ phy_write(phydev, 0x12, 0x4824); /* Disable APS */
+ phy_write(phydev, 0x1f, 0x0200); /* switchto page 2 */
+ phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */
+ phy_write(phydev, 0x1f, 0x0600); /* switchto page 6 */
+ phy_write(phydev, 0x14, 0x708F); /* PHYAFE TX optimization */
+ phy_write(phydev, 0x19, 0x0000);
+ 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, 0x0100); /* switchto page 1 */
+ /* reg 0x17 bit3,set 0 to disable iEEE */
+ phy_write(phydev, 0x17, phy_read(phydev, 0x17) & (~(1<<3)));
+ phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */
+ }
+
+ return 0;
+
+err:
+ mdiobus_unregister(new_bus);
+reg_fail:
+ mdiobus_free(new_bus);
+
+ return -EINVAL;
+}
+
+static int geth_phy_release(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct phy_device *phydev = ndev->phydev;
+ int value = 0;
+
+ /* Stop and disconnect the PHY */
+ if (phydev)
+ phy_stop(phydev);
+
+ priv->link = PHY_DOWN;
+ priv->speed = 0;
+ priv->duplex = -1;
+
+ if (phydev) {
+ value = phy_read(phydev, MII_BMCR);
+ phy_write(phydev, MII_BMCR, (value | BMCR_PDOWN));
+ phy_disconnect(phydev);
+ ndev->phydev = NULL;
+ }
+
+#if 1 // defined(CONFIG_SUNXI_EPHY)
+ gmac_ephy_shutdown(); /* Turn off the clock of PHY for low power consumption */
+#endif
+
+ if (priv->mii) {
+ mdiobus_unregister(priv->mii);
+ priv->mii->priv = NULL;
+ mdiobus_free(priv->mii);
+ priv->mii = NULL;
+ }
+
+ return 0;
+}
+
+static void geth_rx_refill(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct dma_desc *desc;
+ struct sk_buff *sk = NULL;
+ dma_addr_t paddr;
+
+ while (circ_space(priv->rx_clean, priv->rx_dirty, dma_desc_rx) > 0) {
+ int entry = priv->rx_clean;
+
+ /* Find the dirty's desc and clean it */
+ desc = priv->dma_rx + entry;
+
+ if (priv->rx_sk[entry] == NULL) {
+ sk = netdev_alloc_skb_ip_align(ndev, priv->buf_sz);
+
+ if (unlikely(sk == NULL))
+ break;
+
+ priv->rx_sk[entry] = sk;
+ paddr = dma_map_single(priv->dev, sk->data,
+ priv->buf_sz, DMA_FROM_DEVICE);
+ desc_buf_set(desc, paddr, priv->buf_sz);
+ }
+
+ /* sync memery */
+ wmb();
+ desc_set_own(desc);
+ priv->rx_clean = circ_inc(priv->rx_clean, dma_desc_rx);
+ }
+}
+
+/* geth_dma_desc_init - initialize the RX/TX descriptor list
+ * @ndev: net device structure
+ * Description: initialize the list for dma.
+ */
+static int geth_dma_desc_init(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ unsigned int buf_sz;
+
+ priv->rx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_rx, GFP_KERNEL);
+ if (!priv->rx_sk)
+ return -ENOMEM;
+
+ priv->tx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_tx, GFP_KERNEL);
+ if (!priv->tx_sk)
+ goto tx_sk_err;
+
+ /* Set the size of buffer depend on the MTU & max buf size */
+ buf_sz = MAX_BUF_SZ;
+
+ priv->dma_tx = dma_alloc_coherent(priv->dev,
+ dma_desc_tx * sizeof(struct dma_desc),
+ &priv->dma_tx_phy,
+ GFP_KERNEL);
+ if (!priv->dma_tx)
+ goto dma_tx_err;
+
+ priv->dma_rx = dma_alloc_coherent(priv->dev,
+ dma_desc_rx * sizeof(struct dma_desc),
+ &priv->dma_rx_phy,
+ GFP_KERNEL);
+ if (!priv->dma_rx)
+ goto dma_rx_err;
+
+ priv->buf_sz = buf_sz;
+
+ return 0;
+
+dma_rx_err:
+ dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc), priv->dma_tx, priv->dma_tx_phy);
+dma_tx_err:
+ kfree(priv->tx_sk);
+tx_sk_err:
+ kfree(priv->rx_sk);
+
+ return -ENOMEM;
+}
+
+static void geth_free_rx_sk(struct geth_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < dma_desc_rx; i++) {
+ if (priv->rx_sk[i] != NULL) {
+ struct dma_desc *desc = priv->dma_rx + i;
+
+ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc), desc_buf_get_len(desc), DMA_FROM_DEVICE);
+ dev_kfree_skb_any(priv->rx_sk[i]);
+ priv->rx_sk[i] = NULL;
+ }
+ }
+}
+
+static void geth_free_tx_sk(struct geth_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < dma_desc_tx; i++) {
+ if (priv->tx_sk[i] != NULL) {
+ struct dma_desc *desc = priv->dma_tx + i;
+
+ if (desc_buf_get_addr(desc))
+ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc), desc_buf_get_len(desc), DMA_TO_DEVICE);
+ dev_kfree_skb_any(priv->tx_sk[i]);
+ priv->tx_sk[i] = NULL;
+ }
+ }
+}
+
+static void geth_free_dma_desc(struct geth_priv *priv)
+{
+ /* Free the region of consistent memory previously allocated for the DMA */
+ dma_free_coherent(priv->dev, dma_desc_tx * sizeof(struct dma_desc), priv->dma_tx, priv->dma_tx_phy);
+ dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc), priv->dma_rx, priv->dma_rx_phy);
+
+ kfree(priv->rx_sk);
+ kfree(priv->tx_sk);
+}
+
+#ifdef CONFIG_PM
+static int geth_select_gpio_state(struct pinctrl *pctrl, char *name)
+{
+ int ret = 0;
+ struct pinctrl_state *pctrl_state = NULL;
+
+ 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 geth_suspend(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ cancel_work_sync(&priv->eth_work);
+
+ if (!ndev || !netif_running(ndev))
+ return 0;
+
+ spin_lock(&priv->lock);
+ netif_device_detach(ndev);
+ spin_unlock(&priv->lock);
+
+ geth_stop(ndev);
+
+ if (priv->phy_ext == EXT_PHY)
+ geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_SLEEP);
+
+ return 0;
+}
+
+static void geth_resume_work(struct work_struct *work)
+{
+ struct geth_priv *priv = container_of(work, struct geth_priv, eth_work);
+ struct net_device *ndev = priv->ndev;
+
+ if (!netif_running(ndev))
+ return;
+
+ if (priv->phy_ext == EXT_PHY)
+ geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_DEFAULT);
+
+ geth_open(ndev);
+
+#if 1 //defined(CONFIG_SUNXI_EPHY)
+ if (!ephy_is_enable()) {
+ pr_info("[geth_resume] ephy is not enable, waiting...\n");
+ msleep(2000);
+ if (!ephy_is_enable()) {
+ netdev_err(ndev, "Wait for ephy resume timeout.\n");
+ return;
+ }
+ }
+#endif
+
+ spin_lock(&priv->lock);
+ netif_device_attach(ndev);
+ spin_unlock(&priv->lock);
+
+}
+
+static void geth_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ schedule_work(&priv->eth_work);
+}
+
+
+
+static int ucc_geth_suspend(struct platform_device *ofdev, pm_message_t state)
+{
+ struct net_device *ndev = platform_get_drvdata(ofdev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ cancel_work_sync(&priv->eth_work);
+
+ if (!ndev || !netif_running(ndev))
+ return 0;
+
+ spin_lock(&priv->lock);
+ netif_device_detach(ndev);
+ spin_unlock(&priv->lock);
+
+ geth_stop(ndev);
+
+ if (priv->phy_ext == EXT_PHY)
+ geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_SLEEP);
+
+
+ return 0;
+}
+
+static int ucc_geth_resume(struct platform_device *ofdev)
+{
+ struct net_device *ndev = platform_get_drvdata(ofdev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ schedule_work(&priv->eth_work);
+
+
+ return 0;
+}
+
+static int geth_freeze(struct device *dev)
+{
+ return 0;
+}
+
+static int geth_restore(struct device *dev)
+{
+ return 0;
+}
+
+static const struct dev_pm_ops geth_pm_ops = {
+ .complete = geth_resume,
+ .prepare = geth_suspend,
+ .suspend = NULL,
+ .resume = NULL,
+ .freeze = geth_freeze,
+ .restore = geth_restore,
+};
+#else
+static const struct dev_pm_ops geth_pm_ops;
+#endif /* CONFIG_PM */
+
+/*#define sunxi_get_soc_chipid(x) {}*/
+static void geth_chip_hwaddr(u8 *addr)
+{
+#define MD5_SIZE 16
+#define CHIP_SIZE 16
+
+ struct crypto_ahash *tfm;
+ struct ahash_request *req;
+ struct scatterlist sg;
+ u8 result[MD5_SIZE];
+ u8 chipid[CHIP_SIZE];
+ int i = 0;
+ int ret = -1;
+
+ memset(chipid, 0, sizeof(chipid));
+ memset(result, 0, sizeof(result));
+
+ sunxi_get_soc_chipid((u8 *)chipid);
+
+ tfm = crypto_alloc_ahash("md5", 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(tfm)) {
+ pr_err("Failed to alloc md5\n");
+ return;
+ }
+
+ req = ahash_request_alloc(tfm, GFP_KERNEL);
+ if (!req)
+ goto out;
+
+ ahash_request_set_callback(req, 0, NULL, NULL);
+
+ ret = crypto_ahash_init(req);
+ if (ret) {
+ pr_err("crypto_ahash_init() failed\n");
+ goto out;
+ }
+
+ sg_init_one(&sg, chipid, sizeof(chipid));
+ ahash_request_set_crypt(req, &sg, result, sizeof(chipid));
+ ret = crypto_ahash_update(req);
+ if (ret) {
+ pr_err("crypto_ahash_update() failed for id\n");
+ goto out;
+ }
+
+ ret = crypto_ahash_final(req);
+ if (ret) {
+ pr_err("crypto_ahash_final() failed for result\n");
+ goto out;
+ }
+
+ ahash_request_free(req);
+
+ /* Choose md5 result's [0][2][4][6][8][10] byte as mac address */
+ for (i = 0; i < ETH_ALEN; i++)
+ addr[i] = result[2 * i];
+ addr[0] &= 0xfe; /* clear multicast bit */
+ addr[0] |= 0x02; /* set local assignment bit (IEEE802) */
+
+out:
+ crypto_free_ahash(tfm);
+}
+
+static void geth_check_addr(struct net_device *ndev, unsigned char *mac)
+{
+ int i;
+ char *p = mac;
+ u8 addr[ETH_ALEN];
+
+ if (!is_valid_ether_addr(ndev->dev_addr)) {
+ for (i = 0; i < ETH_ALEN; i++, p++)
+ addr[i] = simple_strtoul(p, &p, 16);
+
+ if (!is_valid_ether_addr(addr))
+ geth_chip_hwaddr(addr);
+
+ if (!is_valid_ether_addr(addr)) {
+ eth_random_addr(addr);
+ pr_warn("%s: Use random mac address\n", ndev->name);
+ }
+ }
+
+ eth_hw_addr_set(ndev, addr);
+}
+
+static void geth_clk_enable(struct geth_priv *priv)
+{
+ int phy_interface = 0;
+ u32 clk_value;
+ u32 efuse_value;
+
+ if (clk_prepare_enable(priv->geth_clk))
+ pr_err("try to enable geth_clk failed!\n");
+
+ if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk) && !IS_ERR_OR_NULL(priv->ephy_clk)) {
+ if (clk_prepare_enable(priv->ephy_clk))
+ pr_err("try to enable ephy_clk failed!\n");
+ }
+
+ phy_interface = priv->phy_interface;
+
+ clk_value = readl(priv->base_phy);
+ if (phy_interface == PHY_INTERFACE_MODE_RGMII)
+ clk_value |= 0x00000004;
+ else
+ clk_value &= (~0x00000004);
+
+ clk_value &= (~0x00002003);
+ if (phy_interface == PHY_INTERFACE_MODE_RGMII || phy_interface == PHY_INTERFACE_MODE_GMII)
+ clk_value |= 0x00000002;
+ else if (phy_interface == PHY_INTERFACE_MODE_RMII)
+ clk_value |= 0x00002001;
+
+ if (priv->phy_ext == INT_PHY) {
+// if (0 != sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value))
+// pr_err("get PHY efuse fail!\n");
+// else
+// #if defined(CONFIG_ARCH_SUN50IW2)
+// clk_value |= (((efuse_value >> 24) & 0x0F) + 3) << 28;
+// #else
+// pr_warn("miss config come from efuse!\n");
+// #endif
+ }
+
+ /* Adjust Tx/Rx clock delay */
+ clk_value &= ~(0x07 << 10);
+ clk_value |= ((tx_delay & 0x07) << 10);
+ clk_value &= ~(0x1F << 5);
+ clk_value |= ((rx_delay & 0x1F) << 5);
+
+ writel(clk_value, priv->base_phy);
+}
+
+static void geth_clk_disable(struct geth_priv *priv)
+{
+ if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk) && !IS_ERR_OR_NULL(priv->ephy_clk))
+ clk_disable_unprepare(priv->ephy_clk);
+
+ clk_disable_unprepare(priv->geth_clk);
+}
+
+static void geth_tx_err(struct geth_priv *priv)
+{
+ netif_stop_queue(priv->ndev);
+
+ sunxi_stop_tx(priv->base);
+
+ geth_free_tx_sk(priv);
+ memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc));
+ desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx);
+ priv->tx_dirty = 0;
+ priv->tx_clean = 0;
+ sunxi_start_tx(priv->base, priv->dma_tx_phy);
+
+ priv->ndev->stats.tx_errors++;
+ netif_wake_queue(priv->ndev);
+}
+
+static inline void geth_schedule(struct geth_priv *priv)
+{
+ if (likely(napi_schedule_prep(&priv->napi))) {
+ sunxi_int_disable(priv->base);
+ __napi_schedule(&priv->napi);
+ }
+}
+
+static irqreturn_t geth_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = (struct net_device *)dev_id;
+ struct geth_priv *priv = netdev_priv(ndev);
+ int status;
+
+ if (unlikely(!ndev)) {
+ pr_err("%s: invalid ndev pointer\n", __func__);
+ return IRQ_NONE;
+ }
+
+ status = sunxi_int_status(priv->base, (void *)(&priv->xstats));
+
+ if (likely(status == handle_tx_rx))
+ geth_schedule(priv);
+ 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))
+ geth_tx_err(priv);
+ else
+ netdev_info(ndev, "Do nothing.....\n");
+
+ return IRQ_HANDLED;
+}
+
+static int geth_open(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ int ret = 0;
+
+ ret = geth_dma_desc_init(ndev);
+ if (ret) {
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = geth_power_on(priv);
+ if (ret) {
+ netdev_err(ndev, "Power on is failed\n");
+ ret = -EINVAL;
+ goto power_err;
+ }
+
+ geth_clk_enable(priv);
+
+ ret = geth_phy_init(ndev);
+ if (ret) {
+ netdev_err(ndev, "phy init again...\n");
+ ret = geth_phy_init(ndev);
+ if (ret) {
+ netdev_err(ndev, "phy init failed\n");
+ ret = -EINVAL;
+ goto phy_err;
+ }
+ }
+
+ ret = sunxi_mac_reset((void *)priv->base, &sunxi_udelay, 10000);
+ if (ret) {
+ netdev_err(ndev, "Initialize hardware error\n");
+ goto mac_err;
+ }
+
+ sunxi_mac_init(priv->base, txmode, rxmode);
+ sunxi_set_umac(priv->base, ndev->dev_addr, 0);
+
+ memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc));
+ memset(priv->dma_rx, 0, dma_desc_rx * sizeof(struct dma_desc));
+
+ desc_init_chain(priv->dma_rx, (unsigned long)priv->dma_rx_phy, dma_desc_rx);
+ desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx);
+
+ priv->rx_clean = 0;
+ priv->rx_dirty = 0;
+ priv->tx_clean = 0;
+ priv->tx_dirty = 0;
+ geth_rx_refill(ndev);
+
+ /* Extra statistics */
+ memset(&priv->xstats, 0, sizeof(struct geth_extra_stats));
+
+ if (ndev->phydev)
+ phy_start(ndev->phydev);
+
+ sunxi_start_rx(priv->base, (unsigned long)((struct dma_desc *)priv->dma_rx_phy + priv->rx_dirty));
+ sunxi_start_tx(priv->base, (unsigned long)((struct dma_desc *)priv->dma_tx_phy + priv->tx_clean));
+
+ /* Enable the Rx/Tx */
+ sunxi_mac_enable(priv->base);
+
+ netif_carrier_on(ndev);
+
+ napi_enable(&priv->napi);
+ netif_start_queue(ndev);
+
+ return 0;
+
+mac_err:
+ geth_phy_release(ndev);
+phy_err:
+ geth_clk_disable(priv);
+
+ geth_power_off(priv);
+
+power_err:
+ geth_free_dma_desc(priv);
+
+ return ret;
+}
+
+static int geth_stop(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ netif_stop_queue(ndev);
+ napi_disable(&priv->napi);
+
+ netif_carrier_off(ndev);
+
+ /* Release PHY resources */
+ geth_phy_release(ndev);
+
+ /* Disable Rx/Tx */
+ sunxi_mac_disable(priv->base);
+
+ geth_clk_disable(priv);
+ geth_power_off(priv);
+
+ netif_tx_lock_bh(ndev);
+ /* Release the DMA TX/RX socket buffers */
+ geth_free_rx_sk(priv);
+ geth_free_tx_sk(priv);
+ netif_tx_unlock_bh(ndev);
+
+ /* Ensure that hareware have been stopped */
+ geth_free_dma_desc(priv);
+
+ return 0;
+}
+
+static void geth_tx_complete(struct geth_priv *priv)
+{
+ unsigned int entry = 0;
+ struct sk_buff *skb = NULL;
+ struct dma_desc *desc = NULL;
+ int tx_stat;
+
+ spin_lock(&priv->tx_lock);
+ while (circ_cnt(priv->tx_dirty, priv->tx_clean, dma_desc_tx) > 0) {
+ entry = priv->tx_clean;
+ desc = priv->dma_tx + entry;
+
+ /* Check if the descriptor is owned by the DMA. */
+ if (desc_get_own(desc))
+ break;
+
+ /* Verify tx error by looking at the last segment */
+ if (desc_get_tx_ls(desc)) {
+ tx_stat = desc_get_tx_status(desc, (void *)(&priv->xstats));
+
+ if (likely(!tx_stat))
+ priv->ndev->stats.tx_packets++;
+ else
+ priv->ndev->stats.tx_errors++;
+ }
+
+ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc), desc_buf_get_len(desc), DMA_TO_DEVICE);
+
+ skb = priv->tx_sk[entry];
+ priv->tx_sk[entry] = NULL;
+ desc_init(desc);
+
+ /* Find next dirty desc */
+ priv->tx_clean = circ_inc(entry, dma_desc_tx);
+
+ if (unlikely(skb == NULL))
+ continue;
+
+ dev_kfree_skb(skb);
+ }
+
+ if (unlikely(netif_queue_stopped(priv->ndev)) && circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) > TX_THRESH) {
+ netif_wake_queue(priv->ndev);
+ }
+ spin_unlock(&priv->tx_lock);
+}
+
+static netdev_tx_t geth_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ unsigned int entry;
+ struct dma_desc *desc, *first;
+ unsigned int len, tmp_len = 0;
+ int i, csum_insert;
+ int nfrags = skb_shinfo(skb)->nr_frags;
+ dma_addr_t paddr;
+
+ spin_lock(&priv->tx_lock);
+ if (unlikely(circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) < (nfrags + 1))) {
+ if (!netif_queue_stopped(ndev)) {
+ netdev_err(ndev, "%s: BUG! Tx Ring full when queue awake\n", __func__);
+ netif_stop_queue(ndev);
+ }
+ spin_unlock(&priv->tx_lock);
+
+ return NETDEV_TX_BUSY;
+ }
+
+ csum_insert = (skb->ip_summed == CHECKSUM_PARTIAL);
+ entry = priv->tx_dirty;
+ first = priv->dma_tx + entry;
+ desc = priv->dma_tx + entry;
+
+ len = skb_headlen(skb);
+ priv->tx_sk[entry] = skb;
+
+#ifdef PKT_DEBUG
+ printk("======TX PKT DATA: ============\n");
+ /* dump the packet */
+ print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE,
+ 16, 1, skb->data, 64, true);
+#endif
+
+ /* Every desc max size is 2K */
+ while (len != 0) {
+ desc = priv->dma_tx + entry;
+ tmp_len = ((len > MAX_BUF_SZ) ? MAX_BUF_SZ : len);
+
+ paddr = dma_map_single(priv->dev, skb->data, tmp_len, DMA_TO_DEVICE);
+ if (dma_mapping_error(priv->dev, paddr)) {
+ dev_kfree_skb(skb);
+ return -EIO;
+ }
+ desc_buf_set(desc, paddr, tmp_len);
+ /* Don't set the first's own bit, here */
+ if (first != desc) {
+ priv->tx_sk[entry] = NULL;
+ desc_set_own(desc);
+ }
+
+ entry = circ_inc(entry, dma_desc_tx);
+ 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 = priv->dma_tx + entry;
+ paddr = skb_frag_dma_map(priv->dev, frag, 0, len, DMA_TO_DEVICE);
+ if (dma_mapping_error(priv->dev, paddr)) {
+ dev_kfree_skb(skb);
+ return -EIO;
+ }
+
+ desc_buf_set(desc, paddr, len);
+ desc_set_own(desc);
+ priv->tx_sk[entry] = NULL;
+ entry = circ_inc(entry, dma_desc_tx);
+ }
+
+ ndev->stats.tx_bytes += skb->len;
+ priv->tx_dirty = entry;
+ desc_tx_close(first, desc, csum_insert);
+
+ desc_set_own(first);
+ spin_unlock(&priv->tx_lock);
+
+ if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) <= (MAX_SKB_FRAGS + 1)) {
+ netif_stop_queue(ndev);
+ if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) > TX_THRESH)
+ netif_wake_queue(ndev);
+ }
+
+#ifdef DEBUG
+ printk("=======TX Descriptor DMA: 0x%08llx\n", priv->dma_tx_phy);
+ printk("Tx pointor: dirty: %d, clean: %d\n", priv->tx_dirty, priv->tx_clean);
+ desc_print(priv->dma_tx, dma_desc_tx);
+#endif
+ sunxi_tx_poll(priv->base);
+ geth_tx_complete(priv);
+
+ return NETDEV_TX_OK;
+}
+
+static int geth_rx(struct geth_priv *priv, int limit)
+{
+ unsigned int rxcount = 0;
+ unsigned int entry;
+ struct dma_desc *desc;
+ struct sk_buff *skb;
+ int status;
+ int frame_len;
+
+ while (rxcount < limit) {
+ entry = priv->rx_dirty;
+ desc = priv->dma_rx + entry;
+
+ if (desc_get_own(desc))
+ break;
+
+ rxcount++;
+ priv->rx_dirty = circ_inc(priv->rx_dirty, dma_desc_rx);
+
+ /* Get length & status from hardware */
+ frame_len = desc_rx_frame_len(desc);
+ status = desc_get_rx_status(desc, (void *)(&priv->xstats));
+
+ netdev_dbg(priv->ndev, "Rx frame size %d, status: %d\n", frame_len, status);
+
+ skb = priv->rx_sk[entry];
+ if (unlikely(!skb)) {
+ netdev_err(priv->ndev, "Skb is null\n");
+ priv->ndev->stats.rx_dropped++;
+ break;
+ }
+
+#ifdef PKT_DEBUG
+ printk("======RX PKT DATA: ============\n");
+ /* dump the packet */
+ print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE,
+ 16, 1, skb->data, 64, true);
+#endif
+
+ if (status == discard_frame) {
+ netdev_dbg(priv->ndev, "Get error pkt\n");
+ priv->ndev->stats.rx_errors++;
+ continue;
+ }
+
+ if (unlikely(status != llc_snap))
+ frame_len -= ETH_FCS_LEN;
+
+ priv->rx_sk[entry] = NULL;
+
+ skb_put(skb, frame_len);
+ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc), desc_buf_get_len(desc), DMA_FROM_DEVICE);
+
+ skb->protocol = eth_type_trans(skb, priv->ndev);
+
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ napi_gro_receive(&priv->napi, skb);
+
+ priv->ndev->stats.rx_packets++;
+ priv->ndev->stats.rx_bytes += frame_len;
+ }
+
+#ifdef DEBUG
+ if (rxcount > 0) {
+ printk("======RX Descriptor DMA: 0x%08llx=\n", priv->dma_rx_phy);
+ printk("RX pointor: dirty: %d, clean: %d\n", priv->rx_dirty, priv->rx_clean);
+ desc_print(priv->dma_rx, dma_desc_rx);
+ }
+#endif
+ geth_rx_refill(priv->ndev);
+
+ return rxcount;
+}
+
+static int geth_poll(struct napi_struct *napi, int budget)
+{
+ struct geth_priv *priv = container_of(napi, struct geth_priv, napi);
+ int work_done = 0;
+
+ geth_tx_complete(priv);
+ work_done = geth_rx(priv, budget);
+
+ if (work_done < budget) {
+ napi_complete(napi);
+ sunxi_int_enable(priv->base);
+ }
+
+ return work_done;
+}
+
+static int geth_change_mtu(struct net_device *ndev, int new_mtu)
+{
+ int max_mtu;
+
+ if (netif_running(ndev)) {
+ pr_err("%s: must be stopped to change its MTU\n", ndev->name);
+ return -EBUSY;
+ }
+
+ max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN);
+
+ if ((new_mtu < 46) || (new_mtu > max_mtu)) {
+ pr_err("%s: invalid MTU, max MTU is: %d\n", ndev->name, max_mtu);
+ return -EINVAL;
+ }
+
+ ndev->mtu = new_mtu;
+ netdev_update_features(ndev);
+
+ return 0;
+}
+
+static netdev_features_t geth_fix_features(struct net_device *ndev, netdev_features_t features)
+{
+ return features;
+}
+
+static void geth_set_rx_mode(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ unsigned int value = 0;
+
+ pr_debug("%s: # mcasts %d, # unicast %d\n",
+ __func__, netdev_mc_count(ndev), netdev_uc_count(ndev));
+
+ spin_lock(&priv->lock);
+ if (ndev->flags & IFF_PROMISC) {
+ value = GETH_FRAME_FILTER_PR;
+ } else if ((netdev_mc_count(ndev) > HASH_TABLE_SIZE) ||
+ (ndev->flags & IFF_ALLMULTI)) {
+ value = GETH_FRAME_FILTER_PM; /* pass all multi */
+ sunxi_hash_filter(priv->base, ~0UL, ~0UL);
+ } else if (!netdev_mc_empty(ndev)) {
+ u32 mc_filter[2];
+ struct netdev_hw_addr *ha;
+
+ /* Hash filter for multicast */
+ value = GETH_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_hash_filter(priv->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 |= GETH_FRAME_FILTER_PR;
+ } else {
+ int reg = 1;
+ struct netdev_hw_addr *ha;
+
+ netdev_for_each_uc_addr(ha, ndev) {
+ sunxi_set_umac(priv->base, ha->addr, reg);
+ reg++;
+ }
+ }
+
+#ifdef FRAME_FILTER_DEBUG
+ /* Enable Receive all mode (to debug filtering_fail errors) */
+ value |= GETH_FRAME_FILTER_RA;
+#endif
+ sunxi_set_filter(priv->base, value);
+ spin_unlock(&priv->lock);
+}
+
+static void geth_tx_timeout(struct net_device *ndev, unsigned int txqueue)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ geth_tx_err(priv);
+}
+
+static int geth_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 geth_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) {
+ pr_warn("%s: can't change I/O address\n", ndev->name);
+ return -EOPNOTSUPP;
+ }
+
+ /* Don't allow changing the IRQ */
+ if (map->irq != ndev->irq) {
+ pr_warn("%s: can't change IRQ number %d\n", ndev->name, ndev->irq);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int geth_set_mac_address(struct net_device *ndev, void *p)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct sockaddr *addr = p;
+
+ if (!is_valid_ether_addr(addr->sa_data))
+ return -EADDRNOTAVAIL;
+
+ eth_hw_addr_set(ndev, addr->sa_data);
+
+ sunxi_set_umac(priv->base, ndev->dev_addr, 0);
+
+ return 0;
+}
+
+int geth_set_features(struct net_device *ndev, netdev_features_t features)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ if (features & NETIF_F_LOOPBACK && netif_running(ndev))
+ sunxi_mac_loopback(priv->base, 1);
+ else
+ sunxi_mac_loopback(priv->base, 0);
+
+ return 0;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+/* Polling receive - used by NETCONSOLE and other diagnostic tools
+ * to allow network I/O with interrupts disabled.
+ */
+static void geth_poll_controller(struct net_device *dev)
+{
+ disable_irq(dev->irq);
+ geth_interrupt(dev->irq, dev);
+ enable_irq(dev->irq);
+}
+#endif
+
+static const struct net_device_ops geth_netdev_ops = {
+ .ndo_init = NULL,
+ .ndo_open = geth_open,
+ .ndo_start_xmit = geth_xmit,
+ .ndo_stop = geth_stop,
+ .ndo_change_mtu = geth_change_mtu,
+ .ndo_fix_features = geth_fix_features,
+ .ndo_set_rx_mode = geth_set_rx_mode,
+ .ndo_tx_timeout = geth_tx_timeout,
+ .ndo_do_ioctl = geth_ioctl,
+ .ndo_set_config = geth_config,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+ .ndo_poll_controller = geth_poll_controller,
+#endif
+ .ndo_set_mac_address = geth_set_mac_address,
+ .ndo_set_features = geth_set_features,
+};
+
+static int geth_check_if_running(struct net_device *ndev)
+{
+ if (!netif_running(ndev))
+ return -EBUSY;
+ return 0;
+}
+
+static int geth_get_sset_count(struct net_device *netdev, int sset)
+{
+ int len;
+
+ switch (sset) {
+ case ETH_SS_STATS:
+ len = 0;
+ return len;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+#if 0
+static int geth_ethtool_getsettings(struct net_device *ndev,
+ struct ethtool_cmd *cmd)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct phy_device *phy = ndev->phydev;
+ int rc;
+
+ if (phy == NULL) {
+ netdev_err(ndev, "%s: %s: PHY is not registered\n",
+ __func__, ndev->name);
+ return -ENODEV;
+ }
+
+ if (!netif_running(ndev)) {
+ pr_err("%s: interface is disabled: we cannot track "
+ "link speed / duplex setting\n", ndev->name);
+ return -EBUSY;
+ }
+
+ cmd->transceiver = XCVR_INTERNAL;
+ spin_lock_irq(&priv->lock);
+ rc = phy_ethtool_gset(phy, cmd);
+ spin_unlock_irq(&priv->lock);
+
+ return rc;
+}
+
+static int geth_ethtool_setsettings(struct net_device *ndev,
+ struct ethtool_cmd *cmd)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct phy_device *phy = ndev->phydev;
+ int rc;
+
+ spin_lock(&priv->lock);
+ rc = phy_ethtool_sset(phy, cmd);
+ spin_unlock(&priv->lock);
+
+ return rc;
+}
+#endif
+
+static void geth_ethtool_getdrvinfo(struct net_device *ndev,
+ struct ethtool_drvinfo *info)
+{
+ strlcpy(info->driver, "sunxi_geth", sizeof(info->driver));
+
+#define DRV_MODULE_VERSION "SUNXI Gbgit driver V1.1"
+
+ strcpy(info->version, DRV_MODULE_VERSION);
+ info->fw_version[0] = '\0';
+}
+
+static const struct ethtool_ops geth_ethtool_ops = {
+ .begin = geth_check_if_running,
+ // .get_settings = geth_ethtool_getsettings,
+ // .set_settings = geth_ethtool_setsettings,
+ .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .set_link_ksettings = phy_ethtool_set_link_ksettings,
+ .get_link = ethtool_op_get_link,
+ .get_pauseparam = NULL,
+ .set_pauseparam = NULL,
+ .get_ethtool_stats = NULL,
+ .get_strings = NULL,
+ .get_wol = NULL,
+ .set_wol = NULL,
+ .get_sset_count = geth_get_sset_count,
+ .get_drvinfo = geth_ethtool_getdrvinfo,
+};
+
+struct gpio_config {
+ u32 gpio;
+ u32 mul_sel;
+ u32 pull;
+ u32 drv_level;
+ u32 data;
+};
+
+/* config hardware resource */
+static int geth_hw_init(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct device_node *np = pdev->dev.of_node;
+ int ret = 0;
+ struct resource *res;
+ u32 value;
+ struct gpio_config cfg;
+ const char *gmac_power;
+ char power[20];
+ int i;
+
+// #ifdef CONFIG_SUNXI_EXT_PHY
+ priv->phy_ext = EXT_PHY;
+// #else
+// priv->phy_ext = INT_PHY;
+// #endif
+
+ /* config memery resource */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (unlikely(!res)) {
+ pr_err("%s: ERROR: get gmac memory failed", __func__);
+ return -ENODEV;
+ }
+
+ priv->base = devm_ioremap_resource(&pdev->dev, res);
+ if (!priv->base) {
+ pr_err("%s: ERROR: gmac memory mapping failed", __func__);
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (unlikely(!res)) {
+ pr_err("%s: ERROR: get phy memory failed", __func__);
+ ret = -ENODEV;
+ goto mem_err;
+ }
+
+ priv->base_phy = devm_ioremap_resource(&pdev->dev, res);
+ if (unlikely(!priv->base_phy)) {
+ pr_err("%s: ERROR: phy memory mapping failed", __func__);
+ ret = -ENOMEM;
+ goto mem_err;
+ }
+
+ /* config IRQ */
+ ndev->irq = platform_get_irq_byname(pdev, "gmacirq");
+ if (ndev->irq == -ENXIO) {
+ pr_err("%s: ERROR: MAC IRQ not found\n", __func__);
+ ret = -ENXIO;
+ goto irq_err;
+ }
+
+ ret = request_irq(ndev->irq, geth_interrupt, IRQF_SHARED, dev_name(&pdev->dev), ndev);
+ if (unlikely(ret < 0)) {
+ pr_err("Could not request irq %d, error: %d\n", ndev->irq, ret);
+ goto irq_err;
+ }
+
+ /* config clock */
+ priv->geth_clk = of_clk_get_by_name(np, "bus-emac1");
+ if (unlikely(!priv->geth_clk || IS_ERR(priv->geth_clk))) {
+ pr_err("Get gmac clock failed!\n");
+ ret = -EINVAL;
+ goto clk_err;
+ }
+
+ if (INT_PHY == priv->phy_ext) {
+ priv->ephy_clk = of_clk_get_by_name(np, "emac-25m");
+ if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
+ pr_err("Get ephy clock failed!\n");
+ ret = -EINVAL;
+ goto clk_err;
+ }
+ }
+#if 1 // defined(CONFIG_ARCH_SUN8IW12) || defined(CONFIG_ARCH_SUN50IW9)
+ else {
+ if (!of_property_read_u32(np, "use_ephy25m", &g_use_ephy_clk)
+ && g_use_ephy_clk) {
+ priv->ephy_clk = of_clk_get_by_name(np, "ephy");
+ if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
+ pr_err("Get ephy clk failed!\n");
+ ret = -EINVAL;
+ goto clk_err;
+ }
+ }
+ }
+#endif
+
+ /* config power regulator */
+ if (EXT_PHY == priv->phy_ext) {
+ for (i = 0; i < POWER_CHAN_NUM; i++) {
+ snprintf(power, 15, "gmac-power%d", i);
+ ret = of_property_read_string(np, power, &gmac_power);
+ if (ret) {
+ priv->gmac_power[i] = NULL;
+ pr_info("gmac-power%d: NULL\n", i);
+ continue;
+ }
+ priv->gmac_power[i] = regulator_get(NULL, gmac_power);
+ if (IS_ERR(priv->gmac_power[i])) {
+ pr_err("gmac-power%d get error!\n", i);
+ ret = -EINVAL;
+ goto clk_err;
+ }
+ }
+ }
+
+ // /* config other parameters */
+ // priv->phy_interface = of_get_phy_mode(np);
+ // if (priv->phy_interface != PHY_INTERFACE_MODE_MII &&
+ // priv->phy_interface != PHY_INTERFACE_MODE_RGMII &&
+ // priv->phy_interface != PHY_INTERFACE_MODE_RMII) {
+ // pr_err("Not support phy type!\n");
+ // priv->phy_interface = PHY_INTERFACE_MODE_MII;
+ // }
+
+ priv->phy_interface = PHY_INTERFACE_MODE_RMII;
+
+ if (!of_property_read_u32(np, "tx-delay", &value))
+ tx_delay = value;
+
+ if (!of_property_read_u32(np, "rx-delay", &value))
+ rx_delay = value;
+
+ /* config pinctrl */
+ if (EXT_PHY == priv->phy_ext) {
+ priv->phyrst = of_get_named_gpio_flags(np, "phy-rst", 0, (enum of_gpio_flags *)&cfg);
+ priv->rst_active_low = (cfg.data == OF_GPIO_ACTIVE_LOW) ? 1 : 0;
+
+ if (gpio_is_valid(priv->phyrst)) {
+ if (gpio_request(priv->phyrst, "phy-rst") < 0) {
+ pr_err("gmac gpio request fail!\n");
+ ret = -EINVAL;
+ goto pin_err;
+ }
+ }
+
+ priv->pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR_OR_NULL(priv->pinctrl)) {
+ pr_err("gmac pinctrl error!\n");
+ priv->pinctrl = NULL;
+ ret = -EINVAL;
+ goto pin_err;
+ }
+ }
+
+ priv->ephy_rst = devm_reset_control_get_optional(&pdev->dev, "stmmaceth");
+ reset_control_assert(priv->ephy_rst);
+ reset_control_deassert(priv->ephy_rst);
+
+ msleep(800);
+
+ return 0;
+
+pin_err:
+ if (EXT_PHY == priv->phy_ext) {
+ for (i = 0; i < POWER_CHAN_NUM; i++) {
+ if (IS_ERR_OR_NULL(priv->gmac_power[i]))
+ continue;
+ regulator_put(priv->gmac_power[i]);
+ }
+ }
+clk_err:
+ free_irq(ndev->irq, ndev);
+irq_err:
+ devm_iounmap(&pdev->dev, priv->base_phy);
+mem_err:
+ devm_iounmap(&pdev->dev, priv->base);
+
+ return ret;
+}
+
+static void geth_hw_release(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct geth_priv *priv = netdev_priv(ndev);
+ int i;
+
+ devm_iounmap(&pdev->dev, (priv->base_phy));
+ devm_iounmap(&pdev->dev, priv->base);
+ free_irq(ndev->irq, ndev);
+ if (priv->geth_clk)
+ clk_put(priv->geth_clk);
+
+ if (EXT_PHY == priv->phy_ext) {
+ for (i = 0; i < POWER_CHAN_NUM; i++) {
+ if (IS_ERR_OR_NULL(priv->gmac_power[i]))
+ continue;
+ regulator_put(priv->gmac_power[i]);
+ }
+
+ if (!IS_ERR_OR_NULL(priv->pinctrl))
+ devm_pinctrl_put(priv->pinctrl);
+
+ if (gpio_is_valid(priv->phyrst))
+ gpio_free(priv->phyrst);
+ }
+
+ if (!IS_ERR_OR_NULL(priv->ephy_clk))
+ clk_put(priv->ephy_clk);
+}
+
+/**
+ * geth_probe
+ * @pdev: platform device pointer
+ * Description: the driver is initialized through platform_device.
+ */
+static int geth_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct net_device *ndev = NULL;
+ struct geth_priv *priv;
+
+#ifdef CONFIG_OF
+ pdev->dev.dma_mask = &geth_dma_mask;
+ pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+#endif
+
+ ndev = alloc_etherdev(sizeof(struct geth_priv));
+ if (!ndev) {
+ dev_err(&pdev->dev, "could not allocate device.\n");
+ return -ENOMEM;
+ }
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+
+ priv = netdev_priv(ndev);
+ platform_set_drvdata(pdev, ndev);
+
+ /* Must set private data to pdev, before call it */
+ ret = geth_hw_init(pdev);
+ if (0 != ret) {
+ pr_err("geth_hw_init fail!\n");
+ goto hw_err;
+ }
+
+ /* setup the netdevice, fill the field of netdevice */
+ ether_setup(ndev);
+ ndev->netdev_ops = &geth_netdev_ops;
+ netdev_set_default_ethtool_ops(ndev, &geth_ethtool_ops);
+ ndev->base_addr = (unsigned long)priv->base;
+
+ priv->ndev = ndev;
+ priv->dev = &pdev->dev;
+
+ /* TODO: support the VLAN frames */
+ ndev->hw_features = NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_IP_CSUM |
+ NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM;
+
+ ndev->features |= ndev->hw_features;
+ ndev->hw_features |= NETIF_F_LOOPBACK;
+ ndev->priv_flags |= IFF_UNICAST_FLT;
+
+ ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
+
+ netif_napi_add(ndev, &priv->napi, geth_poll);
+
+ spin_lock_init(&priv->lock);
+ spin_lock_init(&priv->tx_lock);
+
+ /* The last val is mdc clock ratio */
+ sunxi_geth_register((void *)ndev->base_addr, HW_VERSION, 0x03);
+
+ ret = register_netdev(ndev);
+ if (ret) {
+ netif_napi_del(&priv->napi);
+ pr_err("Error: Register %s failed\n", ndev->name);
+ goto reg_err;
+ }
+
+ /* Before open the device, the mac address should be set */
+ geth_check_addr(ndev, mac_str);
+
+#ifdef CONFIG_GETH_ATTRS
+ geth_create_attrs(ndev);
+#endif
+ device_create_file(&pdev->dev, &dev_attr_gphy_test);
+ device_create_file(&pdev->dev, &dev_attr_mii_read);
+ device_create_file(&pdev->dev, &dev_attr_mii_write);
+
+ device_enable_async_suspend(&pdev->dev);
+
+#ifdef CONFIG_PM
+ INIT_WORK(&priv->eth_work, geth_resume_work);
+#endif
+
+ return 0;
+
+reg_err:
+ geth_hw_release(pdev);
+hw_err:
+ platform_set_drvdata(pdev, NULL);
+ free_netdev(ndev);
+
+ return ret;
+}
+
+static int geth_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ device_remove_file(&pdev->dev, &dev_attr_gphy_test);
+ device_remove_file(&pdev->dev, &dev_attr_mii_read);
+ device_remove_file(&pdev->dev, &dev_attr_mii_write);
+
+ netif_napi_del(&priv->napi);
+ unregister_netdev(ndev);
+ geth_hw_release(pdev);
+ platform_set_drvdata(pdev, NULL);
+ free_netdev(ndev);
+
+ return 0;
+}
+
+static const struct of_device_id geth_of_match[] = {
+ {.compatible = "allwinner,sunxi-gmac",},
+ {},
+};
+MODULE_DEVICE_TABLE(of, geth_of_match);
+
+static struct platform_driver geth_driver = {
+ .probe = geth_probe,
+ .remove = geth_remove,
+ .suspend = ucc_geth_suspend,
+ .resume = ucc_geth_resume,
+ .driver = {
+ .name = "sunxi-gmac",
+ .owner = THIS_MODULE,
+ .pm = &geth_pm_ops,
+ .of_match_table = geth_of_match,
+ },
+};
+module_platform_driver(geth_driver);
+
+#ifndef MODULE
+static int __init set_mac_addr(char *str)
+{
+ char *p = str;
+
+ if (str && strlen(str))
+ memcpy(mac_str, p, 18);
+
+ return 0;
+}
+__setup("mac_addr=", set_mac_addr);
+#endif
+
+MODULE_DESCRIPTION("Allwinner Gigabit Ethernet driver");
+MODULE_AUTHOR("fuzhaoke <fuzhaoke@allwinnertech.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/allwinner/sunxi-gmac.h b/drivers/net/ethernet/allwinner/sunxi-gmac.h
new file mode 100644
index 000000000000..0ba8977d28f4
--- /dev/null
+++ b/drivers/net/ethernet/allwinner/sunxi-gmac.h
@@ -0,0 +1,270 @@
+/*
+ * linux/drivers/net/ethernet/allwinner/sunxi_gmac.h
+ *
+ * Copyright © 2016-2018, fuzhaoke
+ * Author: fuzhaoke <fuzhaoke@allwinnertech.com>
+ *
+ * This file is provided under a dual BSD/GPL license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __SUNXI_GETH_H__
+#define __SUNXI_GETH_H__
+
+#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+/* GETH_FRAME_FILTER register value */
+#define GETH_FRAME_FILTER_PR 0x00000001 /* Promiscuous Mode */
+#define GETH_FRAME_FILTER_HUC 0x00000002 /* Hash Unicast */
+#define GETH_FRAME_FILTER_HMC 0x00000004 /* Hash Multicast */
+#define GETH_FRAME_FILTER_DAIF 0x00000008 /* DA Inverse Filtering */
+#define GETH_FRAME_FILTER_PM 0x00000010 /* Pass all multicast */
+#define GETH_FRAME_FILTER_DBF 0x00000020 /* Disable Broadcast frames */
+#define GETH_FRAME_FILTER_SAIF 0x00000100 /* Inverse Filtering */
+#define GETH_FRAME_FILTER_SAF 0x00000200 /* Source Address Filter */
+#define GETH_FRAME_FILTER_HPF 0x00000400 /* Hash or perfect Filter */
+#define GETH_FRAME_FILTER_RA 0x80000000 /* Receive all mode */
+
+/* Default tx descriptor */
+#define TX_SINGLE_DESC0 0x80000000
+#define TX_SINGLE_DESC1 0x63000000
+
+/* Default rx descriptor */
+#define RX_SINGLE_DESC0 0x80000000
+#define RX_SINGLE_DESC1 0x83000000
+
+#define EPHY_ID 0x00441400
+#define AC300_ID 0xc0000000
+#define EPHY_ID_RTL8211F 0x001CC916
+#define IP101G_ID 0x02430c54
+
+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;
+} 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;
+} desc1_u;
+
+typedef struct dma_desc {
+ desc0_u desc0;
+ desc1_u desc1;
+ /* The address of buffers */
+ unsigned int desc2;
+ /* Next desc's address */
+ unsigned int desc3;
+} __attribute__((packed)) dma_desc_t;
+
+enum rx_frame_status { /* IPC status */
+ good_frame = 0,
+ discard_frame = 1,
+ csum_none = 2,
+ llc_snap = 4,
+};
+
+enum tx_dma_irq_status {
+ tx_hard_error = 1,
+ tx_hard_error_bump_tc = 2,
+ handle_tx_rx = 3,
+};
+
+struct geth_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 mii_reg_dump {
+ u32 addr;
+ u16 reg;
+ u16 value;
+};
+
+int sunxi_mdio_read(void *, int, int);
+int sunxi_mdio_write(void *, int, int, unsigned short);
+int sunxi_mdio_reset(void *);
+void sunxi_set_link_mode(void *iobase, int duplex, int speed);
+void sunxi_int_disable(void *);
+int sunxi_int_status(void *, struct geth_extra_stats *x);
+int sunxi_mac_init(void *, int txmode, int rxmode);
+void sunxi_set_umac(void *, unsigned char *, int);
+void sunxi_mac_enable(void *);
+void sunxi_mac_disable(void *);
+void sunxi_tx_poll(void *);
+void sunxi_int_enable(void *);
+void sunxi_start_rx(void *, unsigned long);
+void sunxi_start_tx(void *, unsigned long);
+void sunxi_stop_tx(void *);
+void sunxi_stop_rx(void *);
+void sunxi_hash_filter(void *iobase, unsigned long low, unsigned long high);
+void sunxi_set_filter(void *iobase, unsigned long flags);
+void sunxi_flow_ctrl(void *iobase, int duplex, int fc, int pause);
+void sunxi_mac_loopback(void *iobase, int enable);
+
+void desc_buf_set(struct dma_desc *p, unsigned long paddr, int size);
+void desc_set_own(struct dma_desc *p);
+void desc_init_chain(struct dma_desc *p, unsigned long paddr, unsigned int size);
+void desc_tx_close(struct dma_desc *first, struct dma_desc *end, int csum_insert);
+void desc_init(struct dma_desc *p);
+int desc_get_tx_status(struct dma_desc *desc, struct geth_extra_stats *x);
+int desc_buf_get_len(struct dma_desc *desc);
+int desc_buf_get_addr(struct dma_desc *desc);
+int desc_get_rx_status(struct dma_desc *desc, struct geth_extra_stats *x);
+int desc_get_own(struct dma_desc *desc);
+int desc_get_tx_ls(struct dma_desc *desc);
+int desc_rx_frame_len(struct dma_desc *desc);
+
+int sunxi_mac_reset(void *iobase, void (*mdelay)(int), int n);
+int sunxi_geth_register(void *iobase, int version, unsigned int div);
+
+int sunxi_parse_read_str(char *str, u16 *addr, u16 *reg);
+int sunxi_parse_write_str(char *str, u16 *addr, u16 *reg, u16 *val);
+extern int ephy_is_enable(void);
+extern int gmac_ephy_shutdown(void);
+
+#if defined(CONFIG_ARCH_SUN8IW3) \
+ || defined(CONFIG_ARCH_SUN9IW1) \
+ || defined(CONFIG_ARCH_SUN7I)
+#define HW_VERSION 0
+#else
+#define HW_VERSION 1
+#endif
+
+#endif
diff --git a/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c b/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c
new file mode 100644
index 000000000000..926516835023
--- /dev/null
+++ b/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c
@@ -0,0 +1,768 @@
+/*
+ * linux/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c
+ *
+ * Copyright © 2016-2018, fuzhaoke
+ * Author: fuzhaoke <fuzhaoke@allwinnertech.com>
+ *
+ * This file is provided under a dual BSD/GPL license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/ctype.h>
+#include <linux/printk.h>
+#include <linux/io.h>
+#include "sunxi-gmac.h"
+
+/******************************************************************************
+ * sun8iw6 operations
+ *****************************************************************************/
+#define GETH_BASIC_CTL0 0x00
+#define GETH_BASIC_CTL1 0x04
+#define GETH_INT_STA 0x08
+#define GETH_INT_EN 0x0C
+#define GETH_TX_CTL0 0x10
+#define GETH_TX_CTL1 0x14
+#define GETH_TX_FLOW_CTL 0x1C
+#define GETH_TX_DESC_LIST 0x20
+#define GETH_RX_CTL0 0x24
+#define GETH_RX_CTL1 0x28
+#define GETH_RX_DESC_LIST 0x34
+#define GETH_RX_FRM_FLT 0x38
+#define GETH_RX_HASH0 0x40
+#define GETH_RX_HASH1 0x44
+#define GETH_MDIO_ADDR 0x48
+#define GETH_MDIO_DATA 0x4C
+#define GETH_ADDR_HI(reg) (0x50 + ((reg) << 3))
+#define GETH_ADDR_LO(reg) (0x54 + ((reg) << 3))
+#define GETH_TX_DMA_STA 0xB0
+#define GETH_TX_CUR_DESC 0xB4
+#define GETH_TX_CUR_BUF 0xB8
+#define GETH_RX_DMA_STA 0xC0
+#define GETH_RX_CUR_DESC 0xC4
+#define GETH_RX_CUR_BUF 0xC8
+#define GETH_RGMII_STA 0xD0
+
+#define RGMII_IRQ 0x00000001
+
+#define CTL0_LM 0x02
+#define CTL0_DM 0x01
+#define CTL0_SPEED 0x04
+
+#define BURST_LEN 0x3F000000
+#define RX_TX_PRI 0x02
+#define SOFT_RST 0x01
+
+#define TX_FLUSH 0x01
+#define TX_MD 0x02
+#define TX_NEXT_FRM 0x04
+#define TX_TH 0x0700
+
+#define RX_FLUSH 0x01
+#define RX_MD 0x02
+#define RX_RUNT_FRM 0x04
+#define RX_TH 0x0030
+
+#define TX_INT 0x00001
+#define TX_STOP_INT 0x00002
+#define TX_UA_INT 0x00004
+#define TX_TOUT_INT 0x00008
+#define TX_UNF_INT 0x00010
+#define TX_EARLY_INT 0x00020
+#define RX_INT 0x00100
+#define RX_UA_INT 0x00200
+#define RX_STOP_INT 0x00400
+#define RX_TOUT_INT 0x00800
+#define RX_OVF_INT 0x01000
+#define RX_EARLY_INT 0x02000
+#define LINK_STA_INT 0x10000
+
+#define DISCARD_FRAME -1
+#define GOOD_FRAME 0
+#define CSUM_NONE 2
+#define LLC_SNAP 4
+
+#define SF_DMA_MODE 1
+
+/* Flow Control defines */
+#define FLOW_OFF 0
+#define FLOW_RX 1
+#define FLOW_TX 2
+#define FLOW_AUTO (FLOW_TX | FLOW_RX)
+
+#define HASH_TABLE_SIZE 64
+#define PAUSE_TIME 0x200
+#define GMAC_MAX_UNICAST_ADDRESSES 8
+
+/* PHY address */
+#define PHY_ADDR 0x01
+#define PHY_DM 0x0010
+#define PHY_AUTO_NEG 0x0020
+#define PHY_POWERDOWN 0x0080
+#define PHY_NEG_EN 0x1000
+
+#define MII_BUSY 0x00000001
+#define MII_WRITE 0x00000002
+#define MII_PHY_MASK 0x0000FFC0
+#define MII_CR_MASK 0x0000001C
+#define MII_CLK 0x00000008
+/* 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 |
+ */
+
+enum csum_insertion {
+ cic_dis = 0, /* Checksum Insertion Control */
+ cic_ip = 1, /* Only IP header */
+ cic_no_pse = 2, /* IP header but not pseudoheader */
+ cic_full = 3, /* IP header and pseudoheader */
+};
+
+struct gethdev {
+ void *iobase;
+ unsigned int ver;
+ unsigned int mdc_div;
+};
+
+static struct gethdev hwdev;
+
+/***************************************************************************
+ * External interface
+ **************************************************************************/
+/* Set a ring desc buffer */
+void desc_init_chain(struct 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 dma_desc *p = desc;
+ unsigned long dma_phy = addr;
+
+ for (i = 0; i < (size - 1); i++) {
+ dma_phy += sizeof(struct dma_desc);
+ p->desc3 = (unsigned int)dma_phy;
+ /* Chain mode */
+ p->desc1.all |= (1 << 24);
+ p++;
+ }
+ p->desc1.all |= (1 << 24);
+ p->desc3 = (unsigned int)addr;
+}
+
+int sunxi_mdio_read(void *iobase, int phyaddr, int phyreg)
+{
+ unsigned int value = 0;
+
+ /* Mask the MDC_DIV_RATIO */
+ value |= ((hwdev.mdc_div & 0x07) << 20);
+ value |= (((phyaddr << 12) & (0x0001F000)) |
+ ((phyreg << 4) & (0x000007F0)) |
+ MII_BUSY);
+
+ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
+ ;
+
+ writel(value, iobase + GETH_MDIO_ADDR);
+ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
+ ;
+
+ return (int)readl(iobase + GETH_MDIO_DATA);
+}
+
+int sunxi_mdio_write(void *iobase, int phyaddr, int phyreg, unsigned short data)
+{
+ unsigned int value;
+
+ value = ((0x07 << 20) & readl(iobase + GETH_MDIO_ADDR)) |
+ (hwdev.mdc_div << 20);
+ value |= (((phyaddr << 12) & (0x0001F000)) |
+ ((phyreg << 4) & (0x000007F0))) |
+ MII_WRITE | MII_BUSY;
+
+ /* Wait until any existing MII operation is complete */
+ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
+ ;
+
+ /* Set the MII address register to write */
+ writel(data, iobase + GETH_MDIO_DATA);
+ writel(value, iobase + GETH_MDIO_ADDR);
+
+ /* Wait until any existing MII operation is complete */
+ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
+ ;
+
+ return 0;
+}
+
+int sunxi_mdio_reset(void *iobase)
+{
+ writel((4 << 2), iobase + GETH_MDIO_ADDR);
+ return 0;
+}
+
+void sunxi_set_link_mode(void *iobase, int duplex, int speed)
+{
+ unsigned int ctrl = readl(iobase + GETH_BASIC_CTL0);
+
+ if (!duplex)
+ ctrl &= ~CTL0_DM;
+ else
+ ctrl |= CTL0_DM;
+
+ switch (speed) {
+ case 1000:
+ ctrl &= ~0x0C;
+ break;
+ case 100:
+ case 10:
+ default:
+ ctrl |= 0x08;
+ if (speed == 100)
+ ctrl |= 0x04;
+ else
+ ctrl &= ~0x04;
+ break;
+ }
+
+ writel(ctrl, iobase + GETH_BASIC_CTL0);
+}
+
+void sunxi_mac_loopback(void *iobase, int enable)
+{
+ int reg;
+
+ reg = readl(iobase + GETH_BASIC_CTL0);
+ if (enable)
+ reg |= 0x02;
+ else
+ reg &= ~0x02;
+ writel(reg, iobase + GETH_BASIC_CTL0);
+}
+
+void sunxi_flow_ctrl(void *iobase, int duplex, int fc, int pause)
+{
+ unsigned int flow = 0;
+
+ if (fc & FLOW_RX) {
+ flow = readl(iobase + GETH_RX_CTL0);
+ flow |= 0x10000;
+ writel(flow, iobase + GETH_RX_CTL0);
+ }
+
+ if (fc & FLOW_TX) {
+ flow = readl(iobase + GETH_TX_FLOW_CTL);
+ flow |= 0x00001;
+ writel(flow, iobase + GETH_TX_FLOW_CTL);
+ }
+
+ if (duplex) {
+ flow = readl(iobase + GETH_TX_FLOW_CTL);
+ flow |= (pause << 4);
+ writel(flow, iobase + GETH_TX_FLOW_CTL);
+ }
+}
+
+int sunxi_int_status(void *iobase, struct geth_extra_stats *x)
+{
+ int ret = 0;
+ /* read the status register (CSR5) */
+ unsigned int intr_status;
+
+ intr_status = readl(iobase + GETH_RGMII_STA);
+ if (intr_status & RGMII_IRQ)
+ readl(iobase + GETH_RGMII_STA);
+
+ intr_status = readl(iobase + GETH_INT_STA);
+
+ /* ABNORMAL interrupts */
+ if (intr_status & TX_UNF_INT) {
+ ret = tx_hard_error_bump_tc;
+ x->tx_undeflow_irq++;
+ }
+ if (intr_status & TX_TOUT_INT) {
+ x->tx_jabber_irq++;
+ }
+ if (intr_status & RX_OVF_INT) {
+ x->rx_overflow_irq++;
+ }
+ if (intr_status & RX_UA_INT) {
+ x->rx_buf_unav_irq++;
+ }
+ if (intr_status & RX_STOP_INT) {
+ x->rx_process_stopped_irq++;
+ }
+ if (intr_status & RX_TOUT_INT) {
+ x->rx_watchdog_irq++;
+ }
+ if (intr_status & TX_EARLY_INT) {
+ x->tx_early_irq++;
+ }
+ if (intr_status & TX_STOP_INT) {
+ x->tx_process_stopped_irq++;
+ ret = tx_hard_error;
+ }
+
+ /* TX/RX NORMAL interrupts */
+ if (intr_status & (TX_INT | RX_INT | RX_EARLY_INT | TX_UA_INT)) {
+ x->normal_irq_n++;
+ if (intr_status & (TX_INT | RX_INT))
+ ret = handle_tx_rx;
+ }
+ /* Clear the interrupt by writing a logic 1 to the CSR5[15-0] */
+ writel(intr_status & 0x3FFF, iobase + GETH_INT_STA);
+
+ return ret;
+}
+
+void sunxi_start_rx(void *iobase, unsigned long rxbase)
+{
+ unsigned int value;
+
+ /* Write the base address of Rx descriptor lists into registers */
+ writel(rxbase, iobase + GETH_RX_DESC_LIST);
+
+ value = readl(iobase + GETH_RX_CTL1);
+ value |= 0x40000000;
+ writel(value, iobase + GETH_RX_CTL1);
+}
+
+void sunxi_stop_rx(void *iobase)
+{
+ unsigned int value;
+
+ value = readl(iobase + GETH_RX_CTL1);
+ value &= ~0x40000000;
+ writel(value, iobase + GETH_RX_CTL1);
+}
+
+void sunxi_start_tx(void *iobase, unsigned long txbase)
+{
+ unsigned int value;
+
+ /* Write the base address of Tx descriptor lists into registers */
+ writel(txbase, iobase + GETH_TX_DESC_LIST);
+
+ value = readl(iobase + GETH_TX_CTL1);
+ value |= 0x40000000;
+ writel(value, iobase + GETH_TX_CTL1);
+}
+
+void sunxi_stop_tx(void *iobase)
+{
+ unsigned int value = readl(iobase + GETH_TX_CTL1);
+
+ value &= ~0x40000000;
+ writel(value, iobase + GETH_TX_CTL1);
+}
+
+static int sunxi_dma_init(void *iobase)
+{
+ unsigned int value;
+
+ /* Burst should be 8 */
+ value = (8 << 24);
+
+#ifdef CONFIG_GMAC_DA
+ value |= RX_TX_PRI; /* Rx has priority over tx */
+#endif
+ writel(value, iobase + GETH_BASIC_CTL1);
+
+ /* Mask interrupts by writing to CSR7 */
+ writel(RX_INT | TX_INT | TX_UNF_INT, iobase + GETH_INT_EN);
+
+ return 0;
+}
+
+int sunxi_mac_init(void *iobase, int txmode, int rxmode)
+{
+ unsigned int value;
+
+ sunxi_dma_init(iobase);
+
+ /* Initialize the core component */
+ value = readl(iobase + GETH_TX_CTL0);
+ value |= (1 << 30); /* Jabber Disable */
+ writel(value, iobase + GETH_TX_CTL0);
+
+ value = readl(iobase + GETH_RX_CTL0);
+ value |= (1 << 27); /* Enable CRC & IPv4 Header Checksum */
+ value |= (1 << 28); /* Automatic Pad/CRC Stripping */
+ value |= (1 << 29); /* Jumbo Frame Enable */
+ writel(value, iobase + GETH_RX_CTL0);
+
+ writel((hwdev.mdc_div << 20), iobase + GETH_MDIO_ADDR); /* MDC_DIV_RATIO */
+
+ /* Set the Rx&Tx mode */
+ value = readl(iobase + GETH_TX_CTL1);
+ if (txmode == SF_DMA_MODE) {
+ /* Transmit COE type 2 cannot be done in cut-through mode. */
+ value |= TX_MD;
+ /* Operating on second frame increase the performance
+ * especially when transmit store-and-forward is used.
+ */
+ value |= TX_NEXT_FRM;
+ } else {
+ value &= ~TX_MD;
+ value &= ~TX_TH;
+ /* Set the transmit threshold */
+ if (txmode <= 64)
+ value |= 0x00000000;
+ else if (txmode <= 128)
+ value |= 0x00000100;
+ else if (txmode <= 192)
+ value |= 0x00000200;
+ else
+ value |= 0x00000300;
+ }
+ writel(value, iobase + GETH_TX_CTL1);
+
+ value = readl(iobase + GETH_RX_CTL1);
+ if (rxmode == SF_DMA_MODE) {
+ value |= RX_MD;
+ } else {
+ value &= ~RX_MD;
+ value &= ~RX_TH;
+ if (rxmode <= 32)
+ value |= 0x10;
+ else if (rxmode <= 64)
+ value |= 0x00;
+ else if (rxmode <= 96)
+ value |= 0x20;
+ else
+ value |= 0x30;
+ }
+ writel(value, iobase + GETH_RX_CTL1);
+
+ return 0;
+}
+
+void sunxi_hash_filter(void *iobase, unsigned long low, unsigned long high)
+{
+ writel(high, iobase + GETH_RX_HASH0);
+ writel(low, iobase + GETH_RX_HASH1);
+}
+
+void sunxi_set_filter(void *iobase, unsigned long flags)
+{
+ int tmp_flags;
+
+ tmp_flags = readl(iobase + GETH_RX_FRM_FLT);
+
+ tmp_flags |= ((flags >> 31) |
+ ((flags >> 9) & 0x00000002) |
+ ((flags << 1) & 0x00000010) |
+ ((flags >> 3) & 0x00000060) |
+ ((flags << 7) & 0x00000300) |
+ ((flags << 6) & 0x00003000) |
+ ((flags << 12) & 0x00030000) |
+ (flags << 31));
+
+ writel(tmp_flags, iobase + GETH_RX_FRM_FLT);
+}
+
+void sunxi_set_umac(void *iobase, unsigned char *addr, int index)
+{
+ unsigned long data;
+
+ data = (addr[5] << 8) | addr[4];
+ writel(data, iobase + GETH_ADDR_HI(index));
+ data = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
+ writel(data, iobase + GETH_ADDR_LO(index));
+}
+
+void sunxi_mac_enable(void *iobase)
+{
+ unsigned long value;
+
+ value = readl(iobase + GETH_TX_CTL0);
+ value |= (1 << 31);
+ writel(value, iobase + GETH_TX_CTL0);
+
+ value = readl(iobase + GETH_RX_CTL0);
+ value |= (1 << 31);
+ writel(value, iobase + GETH_RX_CTL0);
+}
+
+void sunxi_mac_disable(void *iobase)
+{
+ unsigned long value;
+
+ value = readl(iobase + GETH_TX_CTL0);
+ value &= ~(1 << 31);
+ writel(value, iobase + GETH_TX_CTL0);
+
+ value = readl(iobase + GETH_RX_CTL0);
+ value &= ~(1 << 31);
+ writel(value, iobase + GETH_RX_CTL0);
+}
+
+void sunxi_tx_poll(void *iobase)
+{
+ unsigned int value;
+
+ value = readl(iobase + GETH_TX_CTL1);
+ writel(value | 0x80000000, iobase + GETH_TX_CTL1);
+}
+
+void sunxi_rx_poll(void *iobase)
+{
+ unsigned int value;
+
+ value = readl(iobase + GETH_RX_CTL1);
+ writel(value | 0x80000000, iobase + GETH_RX_CTL1);
+}
+
+void sunxi_int_enable(void *iobase)
+{
+ writel(RX_INT | TX_INT | TX_UNF_INT, iobase + GETH_INT_EN);
+}
+
+void sunxi_int_disable(void *iobase)
+{
+ writel(0, iobase + GETH_INT_EN);
+}
+
+void desc_buf_set(struct dma_desc *desc, unsigned long paddr, int size)
+{
+ desc->desc1.all &= (~((1 << 11) - 1));
+ desc->desc1.all |= (size & ((1 << 11) - 1));
+ desc->desc2 = paddr;
+}
+
+void desc_set_own(struct dma_desc *desc)
+{
+ desc->desc0.all |= 0x80000000;
+}
+
+void desc_tx_close(struct dma_desc *first, struct dma_desc *end, int csum_insert)
+{
+ struct 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);
+}
+
+void desc_init(struct dma_desc *desc)
+{
+ desc->desc1.all = 0;
+ desc->desc2 = 0;
+
+ desc->desc1.all |= (1 << 24);
+}
+
+int desc_get_tx_status(struct dma_desc *desc, struct geth_extra_stats *x)
+{
+ int ret = 0;
+
+ if (desc->desc0.tx.under_err) {
+ x->tx_underflow++;
+ ret = -1;
+ }
+ if (desc->desc0.tx.no_carr) {
+ x->tx_carrier++;
+ ret = -1;
+ }
+ if (desc->desc0.tx.loss_carr) {
+ x->tx_losscarrier++;
+ ret = -1;
+ }
+
+#if 0
+ if ((desc->desc0.tx.ex_deferral) ||
+ (desc->desc0.tx.ex_coll) ||
+ (desc->desc0.tx.late_coll))
+ stats->collisions += desc->desc0.tx.coll_cnt;
+#endif
+
+ if (desc->desc0.tx.deferred)
+ x->tx_deferred++;
+
+ return ret;
+}
+
+int desc_buf_get_len(struct dma_desc *desc)
+{
+ return (desc->desc1.all & ((1 << 11) - 1));
+}
+
+int desc_buf_get_addr(struct dma_desc *desc)
+{
+ return desc->desc2;
+}
+
+int desc_rx_frame_len(struct dma_desc *desc)
+{
+ return desc->desc0.rx.frm_len;
+}
+
+int desc_get_rx_status(struct dma_desc *desc, struct geth_extra_stats *x)
+{
+ int ret = good_frame;
+
+ if (desc->desc0.rx.last_desc == 0) {
+ return discard_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;
+ }
+
+ return ret;
+}
+
+int desc_get_own(struct dma_desc *desc)
+{
+ return desc->desc0.all & 0x80000000;
+}
+
+int desc_get_tx_ls(struct dma_desc *desc)
+{
+ return desc->desc1.tx.last_seg;
+}
+
+int sunxi_geth_register(void *iobase, int version, unsigned int div)
+{
+ hwdev.ver = version;
+ hwdev.iobase = iobase;
+ hwdev.mdc_div = div;
+
+ return 0;
+}
+
+int sunxi_mac_reset(void *iobase, void (*delay)(int), int n)
+{
+ unsigned int value;
+
+ /* DMA SW reset */
+ value = readl(iobase + GETH_BASIC_CTL1);
+ value |= SOFT_RST;
+ writel(value, iobase + GETH_BASIC_CTL1);
+
+ delay(n);
+
+ return !!(readl(iobase + GETH_BASIC_CTL1) & SOFT_RST);
+}
+
+/**
+ * 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.
+ */
+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.
+ */
+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)
+ break;
+
+ *ptr2 = ' ';
+
+ if (ret)
+ break;
+
+ ptr = ptr2 + 1;
+ }
+
+ *addr = result_addr[0];
+ *reg = result_addr[1];
+ *val = result_addr[2];
+
+ return ret;
+}
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 43edfd9d61dc..0283b463cad9 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -75,6 +75,14 @@ config AC200_PHY
help
Fast ethernet PHY as found in X-Powers AC200 multi-function device.
+config AC200_PHY_SUNXI
+ tristate "AC200 EPHY(Sunxi)"
+ depends on NVMEM
+ depends on OF
+ depends on MFD_AC200_SUNXI
+ help
+ Fast ethernet PHY as found in X-Powers AC200(Sunxi) multi-function device.
+
config AMD_PHY
tristate "AMD PHYs"
help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index e5a46da678ff..340df0674fcd 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -33,6 +33,7 @@ sfp-obj-$(CONFIG_SFP) += sfp-bus.o
obj-y += $(sfp-obj-y) $(sfp-obj-m)
obj-$(CONFIG_AC200_PHY) += ac200-phy.o
+obj-$(CONFIG_AC200_PHY_SUNXI) += sunxi-ephy.o
obj-$(CONFIG_ADIN_PHY) += adin.o
obj-$(CONFIG_ADIN1100_PHY) += adin1100.o
obj-$(CONFIG_AMD_PHY) += amd.o
diff --git a/drivers/net/phy/sunxi-ephy.c b/drivers/net/phy/sunxi-ephy.c
new file mode 100644
index 000000000000..92f5ba101ced
--- /dev/null
+++ b/drivers/net/phy/sunxi-ephy.c
@@ -0,0 +1,518 @@
+/*
+ * Copyright © 2015-2016, Shuge
+ * Author: Sugar <shugeLinux@gmail.com>
+ *
+ * This file is provided under a dual BSD/GPL license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#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 <asm/io.h>
+#include <asm/irq.h>
+
+#include <linux/mfd/ac200.h>
+
+#define EPHY_AC200 1
+#define EPHY_AC300 2
+
+#define WAIT_MAX_COUNT 200
+
+struct ephy_res {
+ struct phy_device *ac300;
+ struct ac200_dev *ac200;
+ atomic_t ephy_en;
+};
+
+static struct ephy_res ac200_ephy;
+static struct ephy_res ac300_ephy;
+u8 ephy_type;
+
+static int ac200_reg_read(struct ac200_dev *ac200, unsigned short reg)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(ac200->regmap, reg, &val);
+
+ if (ret < 0)
+ return ret;
+ else
+ return val;
+}
+
+static int ac200_reg_write(struct ac200_dev *ac200, unsigned short reg, unsigned short val)
+{
+ return regmap_write(ac200->regmap, reg, val);
+}
+
+int ephy_is_enable(void)
+{
+ if (ephy_type == EPHY_AC200)
+ return atomic_read(&ac200_ephy.ephy_en);
+ else if (ephy_type == EPHY_AC300)
+ return atomic_read(&ac300_ephy.ephy_en);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ephy_is_enable);
+
+static void 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 */
+}
+
+static void 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);
+}
+
+static void ephy_config_default(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, 0x708b); /* 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 */
+}
+
+static void __maybe_unused ephy_config_fixed(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 */
+}
+
+static void __maybe_unused ephy_config_cali(struct phy_device *phydev, u16 ephy_cali)
+{
+ int value;
+ value = phy_read(phydev, 0x06);
+ value &= ~(0x0F << 12);
+ value |= (0x0F & (0x03 + ephy_cali)) << 12;
+ phy_write(phydev, 0x06, value);
+
+ return;
+}
+
+int ephy_config_init(struct phy_device *phydev)
+{
+ int value;
+#if 1 //defined(CONFIG_ARCH_SUN50IW9)
+ if (ephy_type == EPHY_AC300) {
+ int ret;
+ u16 ephy_cali = 0;
+ ephy_cali = sun50i_ephy_calibrate_value();
+ if (ret) {
+ pr_err("ephy cali efuse read fail!\n");
+ return -1;
+ }
+ ephy_config_cali(ac300_ephy.ac300, ephy_cali);
+ /*
+ * BIT9: the flag of calibration value
+ * 0: Normal
+ * 1: Low level of calibration value
+ */
+ if (ephy_cali & BIT(9)) {
+ pr_debug("ac300:ephy cali efuse read: fixed!\n");
+ ephy_config_fixed(phydev);
+ } else {
+ pr_debug("ac300:ephy cali efuse read: default!\n");
+ ephy_config_default(phydev);
+ }
+ } else {
+ pr_debug("ac200:ephy cali efuse read: default!\n");
+ ephy_config_default(phydev);
+ }
+#else
+ ephy_config_default(phydev);
+#endif
+
+#if 0
+ /* Disable Auto Power Saving mode */
+ phy_write(phydev, 0x1f, 0x0100); /* Switch to Page 1 */
+ value = phy_read(phydev, 0x17);
+ value &= ~BIT(13);
+ phy_write(phydev, 0x17, value);
+#endif
+ disable_intelligent_ieee(phydev); /* Disable Intelligent IEEE */
+ disable_802_3az_ieee(phydev); /* Disable 802.3az IEEE */
+ phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */
+
+#if 1 // def CONFIG_MFD_ACX00
+ if (ephy_type == EPHY_AC200) {
+ value = ac200_reg_read(ac200_ephy.ac200, AC200_EPHY_CTL);
+ if (phydev->interface == PHY_INTERFACE_MODE_RMII)
+ value |= (1 << 11);
+ else
+ value &= (~(1 << 11));
+ ac200_reg_write(ac200_ephy.ac200, AC200_EPHY_CTL, value | (1 << 11));
+ } else
+#endif
+ if (ephy_type == EPHY_AC300) {
+ 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 | (1 << 1)); /*LED_POL 1:Low active*/
+ }
+
+#if defined(CONFIG_ARCH_SUN50IW6)
+ value = phy_read(phydev, 0x13);
+ value |= 1 << 12;
+ phy_write(phydev, 0x13, value);
+#endif
+
+ return 0;
+}
+EXPORT_SYMBOL(ephy_config_init);
+
+static int ephy_probe(struct phy_device *phydev)
+{
+ return 0;
+}
+
+#if 0
+static int ephy_ack_interrupt(struct phy_device *phydev)
+{
+ int err = phy_read(phydev, IP101A_G_IRQ_CONF_STATUS);
+
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+#endif
+
+
+static struct phy_driver sunxi_phy_driver = {
+ .phy_id = 0x00441400,
+ .name = "ephy",
+ .phy_id_mask = 0x0ffffff0,
+ .features = PHY_BASIC_FEATURES, //| SUPPORTED_Pause |SUPPORTED_Asym_Pause,
+#if 0
+ .flags = PHY_HAS_INTERRUPT,
+ .ack_interrupt = ephy_ack_interrupt,
+#endif
+ .config_init = &ephy_config_init,
+ .config_aneg = &genphy_config_aneg,
+ .read_status = &genphy_read_status,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .probe = ephy_probe,
+};
+
+static void ac300_ephy_enable(struct ephy_res *priv)
+{
+ phy_write(priv->ac300, 0x00, 0x1f83); /* release reset */
+
+ phy_write(priv->ac300, 0x00, 0x1fb7); /* clk gating (24MHz clock)*/
+
+ //phy_write(priv->ac300, 0x05, 0xa81f);
+ phy_write(priv->ac300, 0x05, 0xa819);
+
+ phy_write(priv->ac300, 0x06, 0x00);
+
+ msleep(1000); /* FIXME: fix some board compatible issues. */
+
+ atomic_set(&ac300_ephy.ephy_en, 1);
+}
+
+static void ac300_ephy_disable(struct ephy_res *priv)
+{
+ phy_write(priv->ac300, 0x00, 0x1f40);
+ phy_write(priv->ac300, 0x05, 0xa800);
+
+ phy_write(priv->ac300, 0x06, 0x01);
+
+ atomic_set(&ac300_ephy.ephy_en, 0);
+}
+
+static int ac300_ephy_probe(struct phy_device *phydev)
+{
+ ac300_ephy.ac300 = phydev;
+
+ atomic_set(&ac300_ephy.ephy_en, 0);
+ ac300_ephy_enable(&ac300_ephy);
+
+ ephy_type = EPHY_AC300;
+
+ return 0;
+}
+
+static void ac300_ephy_shutdown(struct phy_device *phydev)
+{
+ ac300_ephy.ac300 = phydev;
+
+ phy_write(ac300_ephy.ac300, 0x00, 0x1f40);
+ phy_write(ac300_ephy.ac300, 0x05, 0xa800);
+ phy_write(ac300_ephy.ac300, 0x06, 0x01);
+}
+
+int gmac_ephy_shutdown(void)
+{
+ if (ephy_type == EPHY_AC300)
+ ac300_ephy_disable(&ac300_ephy);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gmac_ephy_shutdown);
+
+static int ac300_ephy_suspend(struct phy_device *phydev)
+{
+ ac300_ephy_disable(&ac300_ephy);
+
+ return 0;
+}
+
+static int ac300_ephy_resume(struct phy_device *phydev)
+{
+ ac300_ephy_enable(&ac300_ephy);
+
+ return 0;
+}
+
+static struct phy_driver ac300_ephy_driver = {
+ .phy_id = 0xc0000000,
+ .name = "ac300",
+ .phy_id_mask = 0xffffffff,
+ .suspend = ac300_ephy_suspend,
+ .resume = ac300_ephy_resume,
+ .probe = ac300_ephy_probe,
+ // .shutdown = ac300_ephy_shutdown,
+};
+
+
+static void ac200_ephy_enable(struct ephy_res *priv)
+{
+#if 1 //ifdef CONFIG_MFD_ACX00
+ int value;
+ unsigned char i = 0;
+#if 1 // defined(CONFIG_ARCH_SUN50IW6) || defined(CONFIG_ARCH_SUN50IW9)
+ u16 ephy_cali = 0;
+#endif
+
+ if (!ac200_enable()) {
+ for (i = 0; i < WAIT_MAX_COUNT; i++) {
+ msleep(10);
+ if (ac200_enable())
+ break;
+ }
+ if (i == WAIT_MAX_COUNT) {
+ pr_err("acx00 is no enable, and ac200_ephy_enable is fail\n");
+ return;
+ }
+ }
+
+ value = ac200_reg_read(priv->ac200, AC200_SYS_EPHY_CTL0);
+ value |= 0x03;
+ ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL0, value);
+ value = ac200_reg_read(priv->ac200, AC200_SYS_EPHY_CTL1);
+
+ value |= 0x0f;
+
+ ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL1, value);
+ ac200_reg_write(priv->ac200, AC200_EPHY_CTL, 0x06);
+
+ /*for ephy */
+ value = ac200_reg_read(priv->ac200, AC200_EPHY_CTL);
+ value &= ~(0xf << 12);
+
+#if 1 // defined(CONFIG_ARCH_SUN50IW6) || defined(CONFIG_ARCH_SUN50IW9)
+
+ ephy_cali = sun50i_ephy_calibrate_value();
+ value |= (0x0F & (0x03 + ephy_cali)) << 12;
+#else
+ value |= (0x0F & (0x03 + ac200_reg_read(priv->ac200, AC200_SID))) << 12;
+#endif
+
+ ac200_reg_write(priv->ac200, AC200_EPHY_CTL, value);
+
+ atomic_set(&ac200_ephy.ephy_en, 1);
+#endif
+}
+
+static void ac200_ephy_disable(struct ephy_res *priv)
+{
+ int value;
+
+ /* reset ephy */
+ value = ac200_reg_read(priv->ac200, AC200_SYS_EPHY_CTL0);
+ value &= ~0x01;
+ ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL0, value);
+
+ /* shutdown ephy */
+ value = ac200_reg_read(priv->ac200, AC200_EPHY_CTL);
+ value |= 0x01;
+ ac200_reg_write(priv->ac200, AC200_EPHY_CTL, value);
+
+ atomic_set(&priv->ephy_en, 0);
+}
+
+static const struct platform_device_id ac200_ephy_id[] = {
+ { "ac200-ephy", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(platform, ac200_ephy_id);
+
+static int ac200_ephy_probe(struct platform_device *pdev)
+{
+ struct ac200_dev *ax = dev_get_drvdata(pdev->dev.parent);
+
+ if (!ax)
+ return -ENODEV;
+
+ ac200_ephy.ac200 = ax;
+ ephy_type = EPHY_AC200;
+ platform_set_drvdata(pdev, &ac200_ephy);
+
+ atomic_set(&ac200_ephy.ephy_en, 0);
+ ac200_ephy_enable(&ac200_ephy);
+
+ return 0;
+}
+
+static int ac200_ephy_remove(struct platform_device *pdev)
+{
+ ac200_ephy_disable(&ac200_ephy);
+
+ return 0;
+}
+
+static int ac200_ephy_suspend(struct device *dev)
+{
+ ac200_ephy_disable(&ac200_ephy);
+
+ return 0;
+}
+
+static int ac200_ephy_resume(struct device *dev)
+{
+ ac200_ephy_enable(&ac200_ephy);
+
+ return 0;
+}
+
+/* Suspend hook structures */
+static const struct dev_pm_ops ac200_ephy_pm_ops = {
+ .suspend = ac200_ephy_suspend,
+ .resume = ac200_ephy_resume,
+};
+
+static struct platform_driver ac200_ephy_driver = {
+ .driver = {
+ .name = "ac200-ephy-sunxi",
+ .owner = THIS_MODULE,
+ .pm = &ac200_ephy_pm_ops,
+ },
+ .probe = ac200_ephy_probe,
+ .remove = ac200_ephy_remove,
+ .id_table = ac200_ephy_id,
+};
+
+static int ephy_init(void)
+{
+ int ret = 0;
+
+ ret = platform_driver_register(&ac200_ephy_driver);
+ if (ret)
+ return ret;
+
+ ret = phy_driver_register(&ac300_ephy_driver, THIS_MODULE);
+ if (ret)
+ goto ac300_ephy_error;
+
+ ret = phy_driver_register(&sunxi_phy_driver, THIS_MODULE);
+ if (ret)
+ goto ephy_driver_error;
+
+ return ret;
+
+ephy_driver_error:
+ phy_driver_unregister(&ac300_ephy_driver);
+
+ac300_ephy_error:
+ platform_driver_unregister(&ac200_ephy_driver);
+
+ return ret;
+}
+
+static void ephy_exit(void)
+{
+ phy_driver_unregister(&sunxi_phy_driver);
+ phy_driver_unregister(&ac300_ephy_driver);
+ platform_driver_unregister(&ac200_ephy_driver);
+}
+
+module_init(ephy_init);
+module_exit(ephy_exit);
+
+static struct mdio_device_id __maybe_unused ephy_tbl[] = {
+ { 0x00441400, 0x0ffffff0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, ephy_tbl);
+
+MODULE_DESCRIPTION("Allwinner EPHY drivers");
+MODULE_AUTHOR("Sugar <shugeLinux@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/ac200.h b/include/linux/mfd/ac200.h
new file mode 100644
index 000000000000..84d89cbfbd3d
--- /dev/null
+++ b/include/linux/mfd/ac200.h
@@ -0,0 +1,213 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AC200 register list
+ *
+ * Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#ifndef __LINUX_MFD_AC200_H
+#define __LINUX_MFD_AC200_H
+
+#include <linux/regmap.h>
+#include <linux/clk.h>
+
+/* interface registers (can be accessed from any page) */
+#define AC200_TWI_CHANGE_TO_RSB 0x3E
+#define AC200_TWI_PAD_DELAY 0xC4
+#define AC200_TWI_REG_ADDR_H 0xFE
+
+/* General registers */
+#define AC200_SYS_VERSION 0x0000
+#define AC200_SYS_CONTROL 0x0002
+#define AC200_SYS_IRQ_ENABLE 0x0004
+#define AC200_SYS_IRQ_STATUS 0x0006
+#define AC200_SYS_CLK_CTL 0x0008
+#define AC200_SYS_DLDO_OSC_CTL 0x000A
+#define AC200_SYS_PLL_CTL0 0x000C
+#define AC200_SYS_PLL_CTL1 0x000E
+#define AC200_SYS_AUDIO_CTL0 0x0010
+#define AC200_SYS_AUDIO_CTL1 0x0012
+#define AC200_SYS_EPHY_CTL0 0x0014
+#define AC200_SYS_EPHY_CTL1 0x0016
+#define AC200_SYS_TVE_CTL0 0x0018
+#define AC200_SYS_TVE_CTL1 0x001A
+
+/* Audio Codec registers */
+#define AC200_AC_SYS_CLK_CTL 0x2000
+#define AC200_SYS_MOD_RST 0x2002
+#define AC200_SYS_SAMP_CTL 0x2004
+#define AC200_I2S_CTL 0x2100
+#define AC200_I2S_CLK 0x2102
+#define AC200_I2S_FMT0 0x2104
+#define AC200_I2S_FMT1 0x2108
+#define AC200_I2S_MIX_SRC 0x2114
+#define AC200_I2S_MIX_GAIN 0x2116
+#define AC200_I2S_DACDAT_DVC 0x2118
+#define AC200_I2S_ADCDAT_DVC 0x211A
+#define AC200_AC_DAC_DPC 0x2200
+#define AC200_AC_DAC_MIX_SRC 0x2202
+#define AC200_AC_DAC_MIX_GAIN 0x2204
+#define AC200_DACA_OMIXER_CTRL 0x2220
+#define AC200_OMIXER_SR 0x2222
+#define AC200_LINEOUT_CTRL 0x2224
+#define AC200_AC_ADC_DPC 0x2300
+#define AC200_MBIAS_CTRL 0x2310
+#define AC200_ADC_MIC_CTRL 0x2320
+#define AC200_ADCMIXER_SR 0x2322
+#define AC200_ANALOG_TUNING0 0x232A
+#define AC200_ANALOG_TUNING1 0x232C
+#define AC200_AC_AGC_SEL 0x2480
+#define AC200_ADC_DAPLCTRL 0x2500
+#define AC200_ADC_DAPRCTRL 0x2502
+#define AC200_ADC_DAPLSTA 0x2504
+#define AC200_ADC_DAPRSTA 0x2506
+#define AC200_ADC_DAPLTL 0x2508
+#define AC200_ADC_DAPRTL 0x250A
+#define AC200_ADC_DAPLHAC 0x250C
+#define AC200_ADC_DAPLLAC 0x250E
+#define AC200_ADC_DAPRHAC 0x2510
+#define AC200_ADC_DAPRLAC 0x2512
+#define AC200_ADC_DAPLDT 0x2514
+#define AC200_ADC_DAPLAT 0x2516
+#define AC200_ADC_DAPRDT 0x2518
+#define AC200_ADC_DAPRAT 0x251A
+#define AC200_ADC_DAPNTH 0x251C
+#define AC200_ADC_DAPLHNAC 0x251E
+#define AC200_ADC_DAPLLNAC 0x2520
+#define AC200_ADC_DAPRHNAC 0x2522
+#define AC200_ADC_DAPRLNAC 0x2524
+#define AC200_AC_DAPHHPFC 0x2526
+#define AC200_AC_DAPLHPFC 0x2528
+#define AC200_AC_DAPOPT 0x252A
+#define AC200_AC_DAC_DAPCTRL 0x3000
+#define AC200_AC_DRC_HHPFC 0x3002
+#define AC200_AC_DRC_LHPFC 0x3004
+#define AC200_AC_DRC_CTRL 0x3006
+#define AC200_AC_DRC_LPFHAT 0x3008
+#define AC200_AC_DRC_LPFLAT 0x300A
+#define AC200_AC_DRC_RPFHAT 0x300C
+#define AC200_AC_DRC_RPFLAT 0x300E
+#define AC200_AC_DRC_LPFHRT 0x3010
+#define AC200_AC_DRC_LPFLRT 0x3012
+#define AC200_AC_DRC_RPFHRT 0x3014
+#define AC200_AC_DRC_RPFLRT 0x3016
+#define AC200_AC_DRC_LRMSHAT 0x3018
+#define AC200_AC_DRC_LRMSLAT 0x301A
+#define AC200_AC_DRC_RRMSHAT 0x301C
+#define AC200_AC_DRC_RRMSLAT 0x301E
+#define AC200_AC_DRC_HCT 0x3020
+#define AC200_AC_DRC_LCT 0x3022
+#define AC200_AC_DRC_HKC 0x3024
+#define AC200_AC_DRC_LKC 0x3026
+#define AC200_AC_DRC_HOPC 0x3028
+#define AC200_AC_DRC_LOPC 0x302A
+#define AC200_AC_DRC_HLT 0x302C
+#define AC200_AC_DRC_LLT 0x302E
+#define AC200_AC_DRC_HKI 0x3030
+#define AC200_AC_DRC_LKI 0x3032
+#define AC200_AC_DRC_HOPL 0x3034
+#define AC200_AC_DRC_LOPL 0x3036
+#define AC200_AC_DRC_HET 0x3038
+#define AC200_AC_DRC_LET 0x303A
+#define AC200_AC_DRC_HKE 0x303C
+#define AC200_AC_DRC_LKE 0x303E
+#define AC200_AC_DRC_HOPE 0x3040
+#define AC200_AC_DRC_LOPE 0x3042
+#define AC200_AC_DRC_HKN 0x3044
+#define AC200_AC_DRC_LKN 0x3046
+#define AC200_AC_DRC_SFHAT 0x3048
+#define AC200_AC_DRC_SFLAT 0x304A
+#define AC200_AC_DRC_SFHRT 0x304C
+#define AC200_AC_DRC_SFLRT 0x304E
+#define AC200_AC_DRC_MXGHS 0x3050
+#define AC200_AC_DRC_MXGLS 0x3052
+#define AC200_AC_DRC_MNGHS 0x3054
+#define AC200_AC_DRC_MNGLS 0x3056
+#define AC200_AC_DRC_EPSHC 0x3058
+#define AC200_AC_DRC_EPSLC 0x305A
+#define AC200_AC_DRC_HPFHGAIN 0x305E
+#define AC200_AC_DRC_HPFLGAIN 0x3060
+#define AC200_AC_DRC_BISTCR 0x3100
+#define AC200_AC_DRC_BISTST 0x3102
+
+/* TVE registers */
+#define AC200_TVE_CTL0 0x4000
+#define AC200_TVE_CTL1 0x4002
+#define AC200_TVE_MOD0 0x4004
+#define AC200_TVE_MOD1 0x4006
+#define AC200_TVE_DAC_CFG0 0x4008
+#define AC200_TVE_DAC_CFG1 0x400A
+#define AC200_TVE_YC_DELAY 0x400C
+#define AC200_TVE_YC_FILTER 0x400E
+#define AC200_TVE_BURST_FRQ0 0x4010
+#define AC200_TVE_BURST_FRQ1 0x4012
+#define AC200_TVE_FRONT_PORCH 0x4014
+#define AC200_TVE_BACK_PORCH 0x4016
+#define AC200_TVE_TOTAL_LINE 0x401C
+#define AC200_TVE_FIRST_ACTIVE 0x401E
+#define AC200_TVE_BLACK_LEVEL 0x4020
+#define AC200_TVE_BLANK_LEVEL 0x4022
+#define AC200_TVE_PLUG_EN 0x4030
+#define AC200_TVE_PLUG_IRQ_EN 0x4032
+#define AC200_TVE_PLUG_IRQ_STA 0x4034
+#define AC200_TVE_PLUG_STA 0x4038
+#define AC200_TVE_PLUG_DEBOUNCE 0x4040
+#define AC200_TVE_DAC_TEST 0x4042
+#define AC200_TVE_PLUG_PULSE_LEVEL 0x40F4
+#define AC200_TVE_PLUG_PULSE_START 0x40F8
+#define AC200_TVE_PLUG_PULSE_PERIOD 0x40FA
+#define AC200_TVE_IF_CTL 0x5000
+#define AC200_TVE_IF_TIM0 0x5008
+#define AC200_TVE_IF_TIM1 0x500A
+#define AC200_TVE_IF_TIM2 0x500C
+#define AC200_TVE_IF_TIM3 0x500E
+#define AC200_TVE_IF_SYNC0 0x5010
+#define AC200_TVE_IF_SYNC1 0x5012
+#define AC200_TVE_IF_SYNC2 0x5014
+#define AC200_TVE_IF_TIM4 0x5016
+#define AC200_TVE_IF_STATUS 0x5018
+
+/* EPHY registers */
+#define AC200_EPHY_CTL 0x6000
+#define AC200_EPHY_BIST 0x6002
+
+/* eFuse registers (0x8000 - 0x9FFF, layout unknown) */
+
+/* RTC registers */
+#define AC200_LOSC_CTRL0 0xA000
+#define AC200_LOSC_CTRL1 0xA002
+#define AC200_LOSC_AUTO_SWT_STA 0xA004
+#define AC200_INTOSC_CLK_PRESCAL 0xA008
+#define AC200_RTC_YY_MM_DD0 0xA010
+#define AC200_RTC_YY_MM_DD1 0xA012
+#define AC200_RTC_HH_MM_SS0 0xA014
+#define AC200_RTC_HH_MM_SS1 0xA016
+#define AC200_ALARM0_CUR_VLU0 0xA024
+#define AC200_ALARM0_CUR_VLU1 0xA026
+#define AC200_ALARM0_ENABLE 0xA028
+#define AC200_ALARM0_IRQ_EN 0xA02C
+#define AC200_ALARM0_IRQ_STA 0xA030
+#define AC200_ALARM1_WK_HH_MM_SS0 0xA040
+#define AC200_ALARM1_WK_HH_MM_SS1 0xA042
+#define AC200_ALARM1_ENABLE 0xA044
+#define AC200_ALARM1_IRQ_EN 0xA048
+#define AC200_ALARM1_IRQ_STA 0xA04C
+#define AC200_ALARM_CONFIG 0xA050
+#define AC200_LOSC_OUT_GATING 0xA060
+#define AC200_GP_DATA(x) (0xA100 + (x) * 2)
+#define AC200_RTC_DEB 0xA170
+#define AC200_GPL_HOLD_OUTPUT 0xA180
+#define AC200_VDD_RTC 0xA190
+#define AC200_IC_CHARA0 0xA1F0
+#define AC200_IC_CHARA1 0xA1F2
+
+struct ac200_dev {
+ struct clk *clk;
+ struct regmap *regmap;
+ struct regmap_irq_chip_data *regmap_irqc;
+};
+
+extern int ac200_enable(void);
+extern uint16_t sun50i_ephy_calibrate_value(void);
+
+#endif /* __LINUX_MFD_AC200_H */
diff --git a/include/linux/of_gpio.h b/include/linux/of_gpio.h
index d0f66a5e1b2a..71349cac1a67 100644
--- a/include/linux/of_gpio.h
+++ b/include/linux/of_gpio.h
@@ -15,6 +15,21 @@
#include <linux/gpio.h> /* FIXME: Shouldn't be here */
#include <linux/of.h>
+/*
+ * This is Linux-specific flags. By default controllers' and Linux' mapping
+ * match, but GPIO controllers are free to translate their own flags to
+ * Linux-specific in their .xlate callback. Though, 1:1 mapping is recommended.
+ */
+enum of_gpio_flags {
+ OF_GPIO_ACTIVE_LOW = 0x1,
+ OF_GPIO_SINGLE_ENDED = 0x2,
+ OF_GPIO_OPEN_DRAIN = 0x4,
+ OF_GPIO_TRANSITORY = 0x8,
+ OF_GPIO_PULL_UP = 0x10,
+ OF_GPIO_PULL_DOWN = 0x20,
+ OF_GPIO_PULL_DISABLE = 0x40,
+};
+
struct device_node;
#ifdef CONFIG_OF_GPIO
@@ -22,6 +37,9 @@ struct device_node;
extern int of_get_named_gpio(const struct device_node *np,
const char *list_name, int index);
+extern int of_get_named_gpio_flags(const struct device_node *np,
+ const char *list_name, int index, enum of_gpio_flags *flags);
+
#else /* CONFIG_OF_GPIO */
#include <linux/errno.h>
--
GitLab