mirror of https://github.com/OpenIPC/firmware.git
449 lines
11 KiB
Diff
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");
|