--- 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 +#include +#include +#include +#include +#include +#include +#include + +#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");