firmware/br-ext-chip-goke/board/gk7205v200/kernel/patches/00_drivers-net-phy-mdio-gok...

449 lines
11 KiB
Diff

--- linux-4.9.37/drivers/net/phy/mdio-goke-femac.c 1970-01-01 03:00:00.000000000 +0300
+++ linux-4.9.y/drivers/net/phy/mdio-goke-femac.c 2021-06-07 13:01:34.000000000 +0300
@@ -0,0 +1,445 @@
+/*
+ * Copyright (c) Hunan Goke,Chengdu Goke,Shandong Goke. 2021. All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_mdio.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#define MDIO_RWCTRL 0x00
+#define MDIO_RO_DATA 0x04
+#define MDIO_WRITE BIT(13)
+#define MDIO_RW_FINISH BIT(15)
+#define BIT_PHY_ADDR_OFFSET 8
+#define BIT_WR_DATA_OFFSET 16
+
+#define BIT_MASK_FEPHY_ADDR GENMASK(4, 0)
+#define BIT_FEPHY_SEL BIT(5)
+
+#define BIT_OFFSET_LD_SET 25
+#define BIT_OFFSET_LDO_SET 22
+#define BIT_OFFSET_R_TUNING 16
+
+#define MII_EXPMD 0x1d
+#define MII_EXPMA 0x1e
+
+#define REG_LD_AM 0x3050
+#define BIT_MASK_LD_SET GENMASK(4, 0)
+#define REG_LDO_AM 0x3051
+#define BIT_MASK_LDO_SET GENMASK(2, 0)
+#define REG_R_TUNING 0x3052
+#define BIT_MASK_R_TUNING GENMASK(5, 0)
+#define REG_WR_DONE 0x3053
+#define BIT_CFG_DONE BIT(0)
+#define BIT_CFG_ACK BIT(1)
+#define REG_DEF_ATE 0x3057
+#define BIT_AUTOTRIM_DONE BIT(0)
+
+#define PHY_RESET_DELAYS_PROPERTY "goke,phy-reset-delays-us"
+
+enum phy_reset_delays {
+ PRE_DELAY,
+ PULSE,
+ POST_DELAY,
+ DELAYS_NUM,
+};
+
+struct femac_mdio_data {
+ struct clk *clk;
+ struct clk *fephy_clk;
+ struct reset_control *phy_rst;
+ struct reset_control *fephy_rst;
+ u32 phy_reset_delays[DELAYS_NUM];
+ void __iomem *membase;
+ void __iomem *fephy_iobase;
+ void __iomem *fephy_trim_iobase;
+ struct mii_bus *bus;
+ u32 phy_addr;
+};
+
+static int femac_mdio_wait_ready(struct femac_mdio_data *data)
+{
+ u32 val;
+
+ return readl_poll_timeout_atomic(data->membase + MDIO_RWCTRL,
+ val, val & MDIO_RW_FINISH, 20, 10000);
+}
+
+static int femac_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
+{
+ struct femac_mdio_data *data = bus->priv;
+ int ret;
+
+ ret = femac_mdio_wait_ready(data);
+ if (ret)
+ return ret;
+
+ writel(((u32)mii_id << BIT_PHY_ADDR_OFFSET) | ((u32)regnum),
+ data->membase + MDIO_RWCTRL);
+
+ ret = femac_mdio_wait_ready(data);
+ if (ret)
+ return ret;
+
+ return readl(data->membase + MDIO_RO_DATA) & 0xFFFF;
+}
+
+static int femac_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
+ u16 value)
+{
+ struct femac_mdio_data *data = bus->priv;
+ int ret;
+
+ ret = femac_mdio_wait_ready(data);
+ if (ret)
+ return ret;
+
+ writel(MDIO_WRITE | (value << BIT_WR_DATA_OFFSET) |
+ ((u32)mii_id << BIT_PHY_ADDR_OFFSET) | ((u32)regnum),
+ data->membase + MDIO_RWCTRL);
+
+ return femac_mdio_wait_ready(data);
+}
+
+static void femac_sleep_us(u32 time_us)
+{
+ u32 time_ms;
+
+ if (!time_us)
+ return;
+
+ time_ms = DIV_ROUND_UP(time_us, 1000);
+ if (time_ms < 20)
+ usleep_range(time_us, time_us + 500);
+ else
+ msleep(time_ms);
+}
+
+static void femac_phy_reset(const struct femac_mdio_data *data)
+{
+ /* To make sure PHY hardware reset success,
+ * we must keep PHY in deassert state first and
+ * then complete the hardware reset operation
+ */
+ reset_control_deassert(data->phy_rst);
+ femac_sleep_us(data->phy_reset_delays[PRE_DELAY]);
+
+ reset_control_assert(data->phy_rst);
+ /* delay some time to ensure reset ok,
+ * this depends on PHY hardware feature
+ */
+ femac_sleep_us(data->phy_reset_delays[PULSE]);
+ reset_control_deassert(data->phy_rst);
+ /* delay some time to ensure later MDIO access */
+ femac_sleep_us(data->phy_reset_delays[POST_DELAY]);
+}
+
+static void femac_get_phy_addr(struct femac_mdio_data *data,
+ struct device_node *np)
+{
+ struct device_node *child = NULL;
+ int addr;
+
+ child = of_get_next_available_child(np, NULL);
+ if (!child) {
+ pr_err("%s: No valid PHY device node!\n", __func__);
+ return;
+ }
+
+ addr = of_mdio_parse_addr(&data->bus->dev, child);
+ if (addr < 0) {
+ pr_err("%s: get PHY address failed!\n", __func__);
+ return;
+ }
+
+ data->phy_addr = addr;
+}
+
+static inline bool femac_use_fephy(struct femac_mdio_data *data)
+{
+ /*return false;*/
+ return (data->fephy_iobase ?
+ !(readl(data->fephy_iobase) & BIT_FEPHY_SEL) : false);
+}
+
+static void femac_fephy_reset(struct femac_mdio_data *data)
+{
+ u32 val;
+
+ /* disable MDCK clock to make sure FEPHY reset success */
+ clk_disable_unprepare(data->clk);
+
+ val = readl(data->fephy_iobase);
+ val &= ~BIT_MASK_FEPHY_ADDR;
+ val |= data->phy_addr;
+ writel(val, data->fephy_iobase);
+
+ clk_prepare_enable(data->fephy_clk);
+ udelay(10);
+
+ reset_control_assert(data->fephy_rst);
+ udelay(10);
+ reset_control_deassert(data->fephy_rst);
+ /* delay at least 15ms for MDIO operation */
+ msleep(20);
+
+ clk_prepare_enable(data->clk);
+ /* delay 5ms after enable MDCK to make sure FEPHY trim safe */
+ mdelay(5);
+}
+
+static inline int fephy_expanded_read(struct mii_bus *bus, int phy_addr,
+ u32 reg_addr)
+{
+ int ret;
+
+ femac_mdio_write(bus, phy_addr, MII_EXPMA, reg_addr);
+ ret = femac_mdio_read(bus, phy_addr, MII_EXPMD);
+
+ return ret;
+}
+
+static inline int fephy_expanded_write(struct mii_bus *bus, int phy_addr,
+ u32 reg_addr, u16 val)
+{
+ int ret;
+
+ femac_mdio_write(bus, phy_addr, MII_EXPMA, reg_addr);
+ ret = femac_mdio_write(bus, phy_addr, MII_EXPMD, val);
+
+ return ret;
+}
+
+void femac_fephy_use_default_trim(struct femac_mdio_data *data)
+{
+ unsigned short val;
+ int timeout = 3;
+
+ pr_info("No OTP data, internal PHY use default ATE parameters!\n");
+
+ do {
+ msleep(250);
+ val = fephy_expanded_read(data->bus, data->phy_addr,
+ REG_DEF_ATE);
+ val &= BIT_AUTOTRIM_DONE;
+ } while (!val && --timeout);
+
+ if (!timeout)
+ pr_err("femac PHY wait autotrim done timeout!\n");
+
+ mdelay(5);
+}
+
+static void femac_fephy_trim(struct femac_mdio_data *data)
+{
+ struct mii_bus *bus = data->bus;
+ u32 phy_addr = data->phy_addr;
+ int timeout = 3000;
+ u32 val;
+ u8 ld_set;
+ u8 ldo_set;
+ u8 r_tuning;
+
+ val = readl(data->fephy_iobase);
+ ld_set = (val >> BIT_OFFSET_LD_SET) & BIT_MASK_LD_SET;
+ ldo_set = (val >> BIT_OFFSET_LDO_SET) & BIT_MASK_LDO_SET;
+ r_tuning = (val >> BIT_OFFSET_R_TUNING) & BIT_MASK_R_TUNING;
+
+ if (!ld_set && !ldo_set && !r_tuning) {
+ femac_fephy_use_default_trim(data);
+ return;
+ }
+
+ val = fephy_expanded_read(bus, phy_addr, REG_LD_AM);
+ val = (val & ~BIT_MASK_LD_SET) | (ld_set & BIT_MASK_LD_SET);
+ fephy_expanded_write(bus, phy_addr, REG_LD_AM, val);
+
+ val = fephy_expanded_read(bus, phy_addr, REG_LDO_AM);
+ val = (val & ~BIT_MASK_LDO_SET) | (ldo_set & BIT_MASK_LDO_SET);
+ fephy_expanded_write(bus, phy_addr, REG_LDO_AM, val);
+
+ val = fephy_expanded_read(bus, phy_addr, REG_R_TUNING);
+ val = (val & ~BIT_MASK_R_TUNING) | (r_tuning & BIT_MASK_R_TUNING);
+ fephy_expanded_write(bus, phy_addr, REG_R_TUNING, val);
+
+ val = fephy_expanded_read(bus, phy_addr, REG_WR_DONE);
+ if (val & BIT_CFG_ACK)
+ pr_err("femac PHY 0x3053 bit CFG_ACK value: 1\n");
+ val = val | BIT_CFG_DONE;
+ fephy_expanded_write(bus, phy_addr, REG_WR_DONE, val);
+
+ do {
+ usleep_range(100, 150);
+ val = fephy_expanded_read(bus, phy_addr, REG_WR_DONE);
+ val &= BIT_CFG_ACK;
+ } while (!val && --timeout);
+ if (!timeout)
+ pr_err("femac PHY 0x3053 wait bit CFG_ACK timeout!\n");
+
+ mdelay(5);
+
+ pr_info("FEPHY:addr=%d, la_am=0x%x, ldo_am=0x%x, r_tuning=0x%x\n",
+ phy_addr,
+ fephy_expanded_read(bus, phy_addr, REG_LD_AM),
+ fephy_expanded_read(bus, phy_addr, REG_LDO_AM),
+ fephy_expanded_read(bus, phy_addr, REG_R_TUNING));
+}
+
+static void femac_fephy_reset_and_trim(struct femac_mdio_data *data)
+{
+ femac_fephy_reset(data);
+ femac_fephy_trim(data);
+}
+
+static void femac_fephy_set_phy_addr(struct femac_mdio_data *data)
+{
+ u32 val;
+
+ if (!data->fephy_iobase)
+ return;
+
+ val = readl(data->fephy_iobase);
+ val &= ~BIT_MASK_FEPHY_ADDR;
+ val |= (data->phy_addr + 1);
+ writel(val, data->fephy_iobase);
+}
+
+static int femac_mdio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct mii_bus *bus;
+ struct femac_mdio_data *data;
+ struct resource *res;
+ int ret;
+
+ bus = mdiobus_alloc_size(sizeof(*data));
+ if (!bus)
+ return -ENOMEM;
+
+ bus->name = "femac_mii_bus";
+ bus->read = &femac_mdio_read;
+ bus->write = &femac_mdio_write;
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
+ bus->parent = &pdev->dev;
+
+ data = bus->priv;
+ data->bus = bus;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ data->membase = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(data->membase)) {
+ ret = PTR_ERR(data->membase);
+ goto err_out_free_mdiobus;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res) {
+ data->fephy_iobase = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(data->fephy_iobase)) {
+ ret = PTR_ERR(data->fephy_iobase);
+ goto err_out_free_mdiobus;
+ }
+ } else {
+ data->fephy_iobase = NULL;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (res) {
+ data->fephy_trim_iobase = devm_ioremap_resource(&pdev->dev,
+ res);
+ if (IS_ERR(data->fephy_trim_iobase)) {
+ ret = PTR_ERR(data->fephy_trim_iobase);
+ goto err_out_free_mdiobus;
+ }
+ } else {
+ data->fephy_trim_iobase = NULL;
+ }
+
+ data->clk = devm_clk_get(&pdev->dev, "mdio");
+ if (IS_ERR(data->clk)) {
+ ret = PTR_ERR(data->clk);
+ goto err_out_free_mdiobus;
+ }
+
+ data->fephy_clk = devm_clk_get(&pdev->dev, "phy");
+ if (IS_ERR(data->fephy_clk))
+ data->fephy_clk = NULL;
+
+ ret = clk_prepare_enable(data->clk);
+ if (ret)
+ goto err_out_free_mdiobus;
+
+ data->phy_rst = devm_reset_control_get(&pdev->dev, "external-phy");
+ if (IS_ERR(data->phy_rst)) {
+ data->phy_rst = NULL;
+ } else {
+ ret = of_property_read_u32_array(np,
+ PHY_RESET_DELAYS_PROPERTY,
+ data->phy_reset_delays,
+ DELAYS_NUM);
+ if (ret)
+ goto err_out_disable_clk;
+ femac_phy_reset(data);
+ }
+
+ data->fephy_rst = devm_reset_control_get(&pdev->dev, "internal-phy");
+ if (IS_ERR(data->fephy_rst))
+ data->fephy_rst = NULL;
+
+ femac_get_phy_addr(data, np);
+ if (femac_use_fephy(data))
+ femac_fephy_reset_and_trim(data);
+ else
+ femac_fephy_set_phy_addr(data);
+
+ ret = of_mdiobus_register(bus, np);
+ if (ret)
+ goto err_out_disable_clk;
+
+ platform_set_drvdata(pdev, bus);
+
+ return 0;
+
+err_out_disable_clk:
+ clk_disable_unprepare(data->fephy_clk);
+ clk_disable_unprepare(data->clk);
+err_out_free_mdiobus:
+ mdiobus_free(bus);
+ return ret;
+}
+
+static int femac_mdio_remove(struct platform_device *pdev)
+{
+ struct mii_bus *bus = platform_get_drvdata(pdev);
+ struct femac_mdio_data *data = bus->priv;
+
+ mdiobus_unregister(bus);
+ clk_disable_unprepare(data->clk);
+ mdiobus_free(bus);
+
+ return 0;
+}
+
+static const struct of_device_id femac_mdio_dt_ids[] = {
+ { .compatible = "goke,femac-mdio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, femac_mdio_dt_ids);
+
+static struct platform_driver femac_mdio_driver = {
+ .probe = femac_mdio_probe,
+ .remove = femac_mdio_remove,
+ .driver = {
+ .name = "femac-mdio",
+ .of_match_table = femac_mdio_dt_ids,
+ },
+};
+
+module_platform_driver(femac_mdio_driver);
+
+MODULE_DESCRIPTION("Goke Fast Ethernet MAC MDIO interface driver");
+MODULE_LICENSE("GPL v2");