From cc5c06ad122ceb42eb1587347503fb0fff9f275f Mon Sep 17 00:00:00 2001 From: chraac 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sunxi-gmac.h" +#include +#include +#include + + +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, ®); + 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, ®, &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 "); +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 + * + * 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 +#include +#include +#include +#include + +/* 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 + * + * 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 +#include +#include +#include +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#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 "); +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 + */ + +#ifndef __LINUX_MFD_AC200_H +#define __LINUX_MFD_AC200_H + +#include +#include + +/* 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 /* FIXME: Shouldn't be here */ #include +/* + * 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 -- GitLab