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");
 |