All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
From: Kenneth Lee <liguozhu@hisilicon.com>
To: <robh+dt@kernel.org>, <pawel.moll@arm.com>,
	<mark.rutland@arm.com>, <ijc+devicetree@hellion.org.uk>,
	<galak@codeaurora.org>, <catalin.marinas@arm.com>,
	<will.deacon@arm.com>, <liguozhu@hisilicon.com>,
	<Yisen.Zhuang@huawei.com>, <davem@davemloft.net>,
	<paul.gortmaker@windriver.com>, <dingtianhong@huawei.com>,
	<zhangfei.gao@linaro.org>, <devicetree@vger.kernel.org>,
	<linux-kernel@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>, <netdev@vger.kernel.org>,
	<linuxarm@huawei.com>, <salil.mehta@huawei.com>,
	<huangdaode@hisilicon.com>
Cc: Kenneth Lee <liguozhu@huawei.com>
Subject: [PATCH 3/5] net: add Hisilicon Network Subsystem MDIO support
Date: Fri, 14 Aug 2015 18:30:20 +0800	[thread overview]
Message-ID: <1439548222-231611-4-git-send-email-liguozhu@hisilicon.com> (raw)
In-Reply-To: <1439548222-231611-1-git-send-email-liguozhu@hisilicon.com>

The MDIO support for Hisilicon Network Subsystem. It is used in Hislicon
P660 and Hi1610 SoC to control the external PHY

Signed-off-by: Yisen Zhuang <Yisen.Zhuang@huawei.com>
Signed-off-by: Kenneth Lee <liguozhu@huawei.com>
---
 drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c | 597 +++++++++++++++++++++
 1 file changed, 597 insertions(+)
 create mode 100644 drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c

diff --git a/drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c b/drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c
new file mode 100644
index 0000000..7113fa8
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2014-2015 Hisilicon Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/of_address.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock_types.h>
+
+#define MDIO_DRV_NAME "hi-mdio"
+#define MDIO_BUS_NAME "Hisilicon MII Bus"
+#define MDIO_DRV_VERSION "1.1.0"
+#define MDIO_COPYRIGHT "Copyright(c) 2015 Huawei Corporation."
+#define MDIO_DRV_STRING MDIO_BUS_NAME
+#define MDIO_DEFAULT_DEVICE_DESCR MDIO_BUS_NAME
+
+#define MDIO_CTL_DEV_ADDR(x)	(x & 0x1f)
+#define MDIO_CTL_PORT_ADDR(x)	((x & 0x1f) << 5)
+
+#define MDIO_BASE_ADDR			0x403C0000
+#define MDIO_REG_ADDR_LEN		0x1000
+#define MDIO_PHY_GRP_LEN		0x100
+#define MDIO_REG_LEN			0x10
+#define MDIO_PHY_ADDR_NUM		5
+#define MDIO_MAX_PHY_ADDR		0x1F
+#define MDIO_MAX_PHY_REG_ADDR		0xFFFF
+
+#define MDIO_TIMEOUT			1000000
+
+struct hns_mdio_device {
+	struct device *dev;
+	void *vbase;		/* mdio reg base address */
+	u8 phy_class[PHY_MAX_ADDR];
+	u8 index;
+	u8 chip_id;
+	u8 gidx;		/* global index */
+};
+
+#define MDIO_COMMAND_REG		0x0
+#define MDIO_ADDR_REG			0x4
+#define MDIO_WDATA_REG			0x8
+#define MDIO_RDATA_REG			0xc
+#define MDIO_STA_REG			0x10
+
+#define MDIO_CMD_DEVAD_M	0x1f
+#define MDIO_CMD_DEVAD_S	0
+#define MDIO_CMD_PRTAD_M	0x1f
+#define MDIO_CMD_PRTAD_S	5
+#define MDIO_CMD_OP_M		0x3
+#define MDIO_CMD_OP_S		10
+#define MDIO_CMD_ST_M		0x3
+#define MDIO_CMD_ST_S		12
+#define MDIO_CMD_START_B	14
+
+#define MDIO_ADDR_DATA_M	0xffff
+#define MDIO_ADDR_DATA_S	0
+
+#define MDIO_WDATA_DATA_M	0xffff
+#define MDIO_WDATA_DATA_S	0
+
+#define MDIO_RDATA_DATA_M	0xffff
+#define MDIO_RDATA_DATA_S	0
+
+#define MDIO_STATE_STA_B	0
+
+enum mdio_st_clause {
+	MDIO_ST_CLAUSE_45 = 0,
+	MDIO_ST_CLAUSE_22
+};
+
+enum mdio_c22_op_seq {
+	MDIO_C22_WRITE = 1,
+	MDIO_C22_READ = 2
+};
+
+enum mdio_c45_op_seq {
+	MDIO_C45_WRITE_ADDR = 0,
+	MDIO_C45_WRITE_DATA,
+	MDIO_C45_READ_INCREMENT,
+	MDIO_C45_READ
+};
+
+static inline void mdio_write_reg(void *base, u32 reg, u32 value)
+{
+	u8 __iomem *reg_addr = ACCESS_ONCE(base);
+
+	writel(value, reg_addr + reg);
+}
+
+#define MDIO_WRITE_REG(a, reg, value) \
+	mdio_write_reg((a)->vbase, (reg), (value))
+
+static inline u32 mdio_read_reg(void *base, u32 reg)
+{
+	u8 __iomem *reg_addr = ACCESS_ONCE(base);
+
+	return readl(reg_addr + reg);
+}
+
+#define MDIO_READ_REG(a, reg) \
+	mdio_read_reg((a)->vbase, (reg))
+
+#define mdio_set_field(origin, mask, shift, val) \
+	do { \
+		(origin) &= (~((mask) << (shift))); \
+		(origin) |= (((val) & (mask)) << (shift)); \
+	} while (0)
+
+#define mdio_get_field(origin, mask, shift) (((origin) >> (shift)) & (mask))
+
+static void mdio_set_reg_field(void *base, u32 reg, u32 mask, u32 shift,
+			       u32 val)
+{
+	u32 origin = mdio_read_reg(base, reg);
+
+	mdio_set_field(origin, mask, shift, val);
+	mdio_write_reg(base, reg, origin);
+}
+
+#define MDIO_SET_REG_FIELD(dev, reg, mask, shift, val) \
+	mdio_set_reg_field((dev)->vbase, (reg), (mask), (shift), (val))
+
+static u32 mdio_get_reg_field(void *base, u32 reg, u32 mask, u32 shift)
+{
+	u32 origin;
+
+	origin = mdio_read_reg(base, reg);
+	return mdio_get_field(origin, mask, shift);
+}
+
+#define MDIO_GET_REG_FIELD(dev, reg, mask, shift) \
+		mdio_get_reg_field((dev)->vbase, (reg), (mask), (shift))
+
+#define MDIO_SET_REG_BIT(dev, reg, bit, val) \
+		mdio_set_reg_field((dev)->vbase, (reg), 0x1ull, (bit), (val))
+
+#define MDIO_GET_REG_BIT(dev, reg, bit) \
+		mdio_get_reg_field((dev)->vbase, (reg), 0x1ull, (bit))
+
+/**
+ *hns_mdio_read_hw - read phy regs
+ *@mdio_dev: mdio device
+ *@phy_addr:phy addr
+ *@is_c45:
+ *@page:
+ *@reg: reg
+ *@data:regs data
+ *return status
+ */
+static int hns_mdio_read_hw(struct hns_mdio_device *mdio_dev, u8 phy_addr,
+			    u8 is_c45, u8 page, u16 reg, u16 *data)
+{
+	u32 cmd_reg_value;
+	u32 sta_reg_value;
+	u32 time_cnt;
+
+	if (phy_addr > MDIO_MAX_PHY_ADDR) {
+		dev_err(mdio_dev->dev, "Wrong phy address: phy_addr(%x)\n",
+			phy_addr);
+		return -EINVAL;
+	}
+
+	dev_dbg(mdio_dev->dev, "mdio(%d) base is %p\n",
+		mdio_dev->gidx, mdio_dev->vbase);
+	dev_dbg(mdio_dev->dev,
+		"phy_addr=%d, is_c45=%d, devad=%d, regnum=%#x!\n",
+		phy_addr, is_c45, page, reg);
+
+	/* Step 1; waiting for MDIO_COMMAND_REG 's mdio_start==0,	*/
+	/*	after that can do read or write*/
+	for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+		cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+						 MDIO_COMMAND_REG,
+						 MDIO_CMD_START_B);
+		if (!cmd_reg_value)
+			break;
+	}
+	if (cmd_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO is busy!\n");
+		return -EBUSY;
+	}
+
+	if (!is_c45) {
+		cmd_reg_value = MDIO_ST_CLAUSE_22 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C22_READ << MDIO_CMD_OP_S;
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+		cmd_reg_value |= (reg & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	} else {
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_ADDR_REG, MDIO_ADDR_DATA_M,
+				   MDIO_ADDR_DATA_S, reg);
+
+		/* Step 2; config the cmd-reg to write addr*/
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_WRITE_ADDR << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+
+		/* Step 3; waiting for MDIO_COMMAND_REG 's mdio_start==0,*/
+		/*check for read or write opt is finished */
+		for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+			cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+							 MDIO_COMMAND_REG,
+							 MDIO_CMD_START_B);
+			if (!cmd_reg_value)
+				break;
+		}
+		if (cmd_reg_value) {
+			dev_err(mdio_dev->dev, "MDIO is busy\n");
+			return -EBUSY;
+		}
+
+		/* Step 4; config cmd-reg, send read opt */
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_READ << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	}
+
+	/* Step 5; waiting for MDIO_COMMAND_REG 's mdio_start==0,*/
+	/* check for read or write opt is finished */
+	for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+		cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+						 MDIO_COMMAND_REG,
+						 MDIO_CMD_START_B);
+		if (!cmd_reg_value)
+			break;
+	}
+	if (cmd_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO is busy\n");
+		return -EBUSY;
+	}
+
+	sta_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+					 MDIO_STA_REG, MDIO_STATE_STA_B);
+	if (sta_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO Read failed!\n");
+		return -EBUSY;
+	}
+
+	/* Step 6; get out data*/
+	*data = (u16)MDIO_GET_REG_FIELD(mdio_dev, MDIO_RDATA_REG,
+					MDIO_RDATA_DATA_M, MDIO_RDATA_DATA_S);
+
+	return 0;
+}
+
+/**
+ *hns_mdio_write_hw - write phy regs
+ *@mdio_dev: mdio device
+ *@phy_addr: phy addr
+ *@is_c45: 0 means the clause 22, none 0 means clause 45
+ *@page: phy page addr
+ *@reg:  phy reg
+ *@data: regs data
+ *return status
+ */
+static int hns_mdio_write_hw(struct hns_mdio_device *mdio_dev, u8 phy_addr,
+			     u8 is_c45, u8 page, u16 reg, u16 data)
+{
+	u32 cmd_reg_value;
+	u32 time_cnt;
+
+	if (phy_addr > MDIO_MAX_PHY_ADDR) {
+		dev_err(mdio_dev->dev, "Wrong phy address: phy_addr(%x)\n",
+			phy_addr);
+		return -EINVAL;
+	}
+
+	dev_dbg(mdio_dev->dev, "mdio(%d) base is %p,write data=%d\n",
+		mdio_dev->gidx, mdio_dev->vbase, data);
+	dev_dbg(mdio_dev->dev, "phy_addr=%d, is_c45=%d, devad=%d, regnum=%#x\n",
+		phy_addr, is_c45, page, reg);
+
+	/* Step 1; waiting for MDIO_COMMAND_REG 's mdio_start==0,*/
+	/*	after that can do read or wtrite*/
+	for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+		cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+						 MDIO_COMMAND_REG,
+						 MDIO_CMD_START_B);
+		if (!cmd_reg_value)
+			break;
+	}
+	if (cmd_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO is busy\n");
+		return -EBUSY;
+	}
+
+	if (!is_c45) {
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_WDATA_REG, MDIO_WDATA_DATA_M,
+				   MDIO_WDATA_DATA_S, data);
+
+		cmd_reg_value = MDIO_ST_CLAUSE_22 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C22_WRITE << MDIO_CMD_OP_S;
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+		cmd_reg_value |= (reg & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	} else {
+		/* Step 2; config the cmd-reg to write addr*/
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_ADDR_REG, MDIO_ADDR_DATA_M,
+				   MDIO_ADDR_DATA_S, reg);
+
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_WRITE_ADDR << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+
+		/* Step 3; waiting for MDIO_COMMAND_REG's mdio_start==0,*/
+		/* check for read or write opt is finished */
+		for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+			cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+							 MDIO_COMMAND_REG,
+							 MDIO_CMD_START_B);
+			if (!cmd_reg_value)
+				break;
+		}
+		if (cmd_reg_value) {
+			dev_err(mdio_dev->dev, "MDIO is busy\n");
+			return -EBUSY;
+		}
+
+		/* Step 4; config the data needed writing */
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_WDATA_REG, MDIO_WDATA_DATA_M,
+				   MDIO_WDATA_DATA_S, data);
+
+		/* Step 5; config the cmd-reg for the write opt*/
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_WRITE_DATA << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	}
+
+	return 0;
+}
+
+/**
+ * hns_mdio_get_chip_id - get parent node's chip-id attribute
+ * @dpev: mdio platform device
+ * return chip id if found the chip-id, or return 0
+ */
+static u32 hns_mdio_get_chip_id(struct platform_device *pdev)
+{
+	struct device_node *np;
+	u32 chip_id;
+	int ret;
+
+	np = of_get_parent(pdev->dev.of_node);
+	if (!np)
+		return 0;
+
+	ret = of_property_read_u32(np, "chip-id", &chip_id);
+	if (ret)
+		return 0;
+
+	return chip_id;
+}
+
+/**
+ * hns_mdio_write - access phy register
+ * @bus: mdio bus
+ * @phy_id: phy id
+ * @regnum: register num
+ * @value: register value
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_write(struct mii_bus *bus,
+			  int phy_id, int regnum, u16 value)
+{
+	int ret;
+	struct hns_mdio_device *mdio_dev = (struct hns_mdio_device *)bus->priv;
+	u8 devad = ((regnum >> 16) & 0x1f);
+	u8 is_c45 = !!(regnum & MII_ADDR_C45);
+
+	ret = hns_mdio_write_hw(mdio_dev, phy_id, is_c45, devad,
+				(u16)(regnum & 0xffff), value);
+	if (ret) {
+		dev_err(&bus->dev, "write_phy_reg fail, phy_id=%d, devad=%d,regnum=%#x, value=%#x!\n",
+			phy_id, devad, regnum, value);
+
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * hns_mdio_read - access phy register
+ * @bus: mdio bus
+ * @phy_id: phy id
+ * @regnum: register num
+ * @value: register value
+ *
+ * Return phy register value
+ */
+static int hns_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+	int ret;
+	u16 reg_val = 0;
+	u8 devad = ((regnum >> 16) & 0x1f);
+	u8 is_c45 = !!(regnum & MII_ADDR_C45);
+	struct hns_mdio_device *mdio_dev = (struct hns_mdio_device *)bus->priv;
+
+	ret = hns_mdio_read_hw(mdio_dev, phy_id, is_c45, devad,
+			       (u16)(regnum & 0xffff), &reg_val);
+	if (ret) {
+		dev_err(&bus->dev, "read_phy_reg fail, mdio_idx=%d, phy_id=%d, devad=%d,regnum=%#x!\n",
+			mdio_dev->gidx,	phy_id, devad, regnum);
+
+		return ret;
+	}
+
+	return reg_val;
+}
+
+/**
+ * hns_mdio_reset - reset mdio bus
+ * @bus: mdio bus
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_reset(struct mii_bus *bus)
+{
+	return 0;
+}
+
+/**
+ * hns_mdio_bus_name - get mdio bus name
+ * @name: mdio bus name
+ * @np: mdio device node pointer
+ */
+static void hns_mdio_bus_name(char *name, struct device_node *np)
+{
+	const u32 *addr;
+	u64 taddr = OF_BAD_ADDR;
+
+	addr = of_get_address(np, 0, NULL, NULL);
+	if (addr)
+		taddr = of_translate_address(np, addr);
+
+	snprintf(name, MII_BUS_ID_SIZE, "%s@%llx", np->name,
+		 (unsigned long long)taddr);
+}
+
+/**
+ * hns_mdio_probe - probe mdio device
+ * @pdev: mdio platform device
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_probe(struct platform_device *pdev)
+{
+	struct device_node *np;
+	struct hns_mdio_device *mdio_dev;
+	struct mii_bus *new_bus;
+	struct resource *res;
+	int ret;
+
+	if (!pdev) {
+		dev_err(NULL, "pdev is NULL!\r\n");
+		return -ENODEV;
+	}
+	np = pdev->dev.of_node;
+	mdio_dev = devm_kzalloc(&pdev->dev, sizeof(*mdio_dev), GFP_KERNEL);
+	if (!mdio_dev)
+		return -ENOMEM;
+
+	new_bus = devm_mdiobus_alloc(&pdev->dev);
+	if (!new_bus) {
+		dev_err(&pdev->dev, "mdiobus_alloc fail!\n");
+		return -ENOMEM;
+	}
+
+	new_bus->name = MDIO_BUS_NAME;
+	new_bus->read = hns_mdio_read;
+	new_bus->write = hns_mdio_write;
+	new_bus->reset = hns_mdio_reset;
+	new_bus->priv = mdio_dev;
+	hns_mdio_bus_name(new_bus->id, np);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	mdio_dev->vbase = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(mdio_dev->vbase)) {
+		ret = PTR_ERR(mdio_dev->vbase);
+		return -EFAULT;
+	}
+	mdio_dev->index = 0;
+	mdio_dev->chip_id = hns_mdio_get_chip_id(pdev);
+	mdio_dev->gidx = mdio_dev->chip_id;
+	mdio_dev->dev = &pdev->dev;
+
+	new_bus->irq = devm_kcalloc(&pdev->dev, PHY_MAX_ADDR,
+				    sizeof(int), GFP_KERNEL);
+	if (!new_bus->irq)
+		return -ENOMEM;
+
+	new_bus->parent = &pdev->dev;
+	platform_set_drvdata(pdev, new_bus);
+
+	ret = of_mdiobus_register(new_bus, np);
+	if (ret) {
+		dev_err(&pdev->dev, "Cannot register as MDIO bus!\n");
+		platform_set_drvdata(pdev, NULL);
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * hns_mdio_remove - remove mdio device
+ * @pdev: mdio platform device
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_remove(struct platform_device *pdev)
+{
+	struct hns_mdio_device *mdio_dev;
+	struct mii_bus *bus;
+
+	bus = platform_get_drvdata(pdev);
+	mdio_dev = (struct hns_mdio_device *)bus->priv;
+
+	mdiobus_unregister(bus);
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static const struct of_device_id hns_mdio_match[] = {
+	{.compatible = "hisilicon,mdio"},
+	{}
+};
+
+static struct platform_driver hns_mdio_driver = {
+	.probe = hns_mdio_probe,
+	.remove = hns_mdio_remove,
+	.driver = {
+		   .name = MDIO_DRV_NAME,
+		   .of_match_table = hns_mdio_match,
+		   },
+};
+
+module_platform_driver(hns_mdio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Huawei Tech. Co., Ltd.");
+MODULE_DESCRIPTION("Hisilicon HNS MDIO driver");
+MODULE_ALIAS("platform:" MDIO_DRV_NAME);
-- 
1.9.1


WARNING: multiple messages have this Message-ID (diff)
From: Kenneth Lee <liguozhu@hisilicon.com>
To: robh+dt@kernel.org, pawel.moll@arm.com, mark.rutland@arm.com,
	ijc+devicetree@hellion.org.uk, galak@codeaurora.org,
	catalin.marinas@arm.com, will.deacon@arm.com,
	liguozhu@hisilicon.com, Yisen.Zhuang@huawei.com,
	davem@davemloft.net, paul.gortmaker@windriver.com,
	dingtianhong@huawei.com, zhangfei.gao@linaro.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org, netdev@vger.kernel.org,
	linuxarm@huawei.com, salil.mehta@huawei.com,
	huangdaode@hisilicon.com
Cc: Kenneth Lee <liguozhu@huawei.com>
Subject: [PATCH 3/5] net: add Hisilicon Network Subsystem MDIO support
Date: Fri, 14 Aug 2015 18:30:20 +0800	[thread overview]
Message-ID: <1439548222-231611-4-git-send-email-liguozhu@hisilicon.com> (raw)
In-Reply-To: <1439548222-231611-1-git-send-email-liguozhu@hisilicon.com>

The MDIO support for Hisilicon Network Subsystem. It is used in Hislicon
P660 and Hi1610 SoC to control the external PHY

Signed-off-by: Yisen Zhuang <Yisen.Zhuang@huawei.com>
Signed-off-by: Kenneth Lee <liguozhu@huawei.com>
---
 drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c | 597 +++++++++++++++++++++
 1 file changed, 597 insertions(+)
 create mode 100644 drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c

diff --git a/drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c b/drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c
new file mode 100644
index 0000000..7113fa8
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2014-2015 Hisilicon Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/of_address.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock_types.h>
+
+#define MDIO_DRV_NAME "hi-mdio"
+#define MDIO_BUS_NAME "Hisilicon MII Bus"
+#define MDIO_DRV_VERSION "1.1.0"
+#define MDIO_COPYRIGHT "Copyright(c) 2015 Huawei Corporation."
+#define MDIO_DRV_STRING MDIO_BUS_NAME
+#define MDIO_DEFAULT_DEVICE_DESCR MDIO_BUS_NAME
+
+#define MDIO_CTL_DEV_ADDR(x)	(x & 0x1f)
+#define MDIO_CTL_PORT_ADDR(x)	((x & 0x1f) << 5)
+
+#define MDIO_BASE_ADDR			0x403C0000
+#define MDIO_REG_ADDR_LEN		0x1000
+#define MDIO_PHY_GRP_LEN		0x100
+#define MDIO_REG_LEN			0x10
+#define MDIO_PHY_ADDR_NUM		5
+#define MDIO_MAX_PHY_ADDR		0x1F
+#define MDIO_MAX_PHY_REG_ADDR		0xFFFF
+
+#define MDIO_TIMEOUT			1000000
+
+struct hns_mdio_device {
+	struct device *dev;
+	void *vbase;		/* mdio reg base address */
+	u8 phy_class[PHY_MAX_ADDR];
+	u8 index;
+	u8 chip_id;
+	u8 gidx;		/* global index */
+};
+
+#define MDIO_COMMAND_REG		0x0
+#define MDIO_ADDR_REG			0x4
+#define MDIO_WDATA_REG			0x8
+#define MDIO_RDATA_REG			0xc
+#define MDIO_STA_REG			0x10
+
+#define MDIO_CMD_DEVAD_M	0x1f
+#define MDIO_CMD_DEVAD_S	0
+#define MDIO_CMD_PRTAD_M	0x1f
+#define MDIO_CMD_PRTAD_S	5
+#define MDIO_CMD_OP_M		0x3
+#define MDIO_CMD_OP_S		10
+#define MDIO_CMD_ST_M		0x3
+#define MDIO_CMD_ST_S		12
+#define MDIO_CMD_START_B	14
+
+#define MDIO_ADDR_DATA_M	0xffff
+#define MDIO_ADDR_DATA_S	0
+
+#define MDIO_WDATA_DATA_M	0xffff
+#define MDIO_WDATA_DATA_S	0
+
+#define MDIO_RDATA_DATA_M	0xffff
+#define MDIO_RDATA_DATA_S	0
+
+#define MDIO_STATE_STA_B	0
+
+enum mdio_st_clause {
+	MDIO_ST_CLAUSE_45 = 0,
+	MDIO_ST_CLAUSE_22
+};
+
+enum mdio_c22_op_seq {
+	MDIO_C22_WRITE = 1,
+	MDIO_C22_READ = 2
+};
+
+enum mdio_c45_op_seq {
+	MDIO_C45_WRITE_ADDR = 0,
+	MDIO_C45_WRITE_DATA,
+	MDIO_C45_READ_INCREMENT,
+	MDIO_C45_READ
+};
+
+static inline void mdio_write_reg(void *base, u32 reg, u32 value)
+{
+	u8 __iomem *reg_addr = ACCESS_ONCE(base);
+
+	writel(value, reg_addr + reg);
+}
+
+#define MDIO_WRITE_REG(a, reg, value) \
+	mdio_write_reg((a)->vbase, (reg), (value))
+
+static inline u32 mdio_read_reg(void *base, u32 reg)
+{
+	u8 __iomem *reg_addr = ACCESS_ONCE(base);
+
+	return readl(reg_addr + reg);
+}
+
+#define MDIO_READ_REG(a, reg) \
+	mdio_read_reg((a)->vbase, (reg))
+
+#define mdio_set_field(origin, mask, shift, val) \
+	do { \
+		(origin) &= (~((mask) << (shift))); \
+		(origin) |= (((val) & (mask)) << (shift)); \
+	} while (0)
+
+#define mdio_get_field(origin, mask, shift) (((origin) >> (shift)) & (mask))
+
+static void mdio_set_reg_field(void *base, u32 reg, u32 mask, u32 shift,
+			       u32 val)
+{
+	u32 origin = mdio_read_reg(base, reg);
+
+	mdio_set_field(origin, mask, shift, val);
+	mdio_write_reg(base, reg, origin);
+}
+
+#define MDIO_SET_REG_FIELD(dev, reg, mask, shift, val) \
+	mdio_set_reg_field((dev)->vbase, (reg), (mask), (shift), (val))
+
+static u32 mdio_get_reg_field(void *base, u32 reg, u32 mask, u32 shift)
+{
+	u32 origin;
+
+	origin = mdio_read_reg(base, reg);
+	return mdio_get_field(origin, mask, shift);
+}
+
+#define MDIO_GET_REG_FIELD(dev, reg, mask, shift) \
+		mdio_get_reg_field((dev)->vbase, (reg), (mask), (shift))
+
+#define MDIO_SET_REG_BIT(dev, reg, bit, val) \
+		mdio_set_reg_field((dev)->vbase, (reg), 0x1ull, (bit), (val))
+
+#define MDIO_GET_REG_BIT(dev, reg, bit) \
+		mdio_get_reg_field((dev)->vbase, (reg), 0x1ull, (bit))
+
+/**
+ *hns_mdio_read_hw - read phy regs
+ *@mdio_dev: mdio device
+ *@phy_addr:phy addr
+ *@is_c45:
+ *@page:
+ *@reg: reg
+ *@data:regs data
+ *return status
+ */
+static int hns_mdio_read_hw(struct hns_mdio_device *mdio_dev, u8 phy_addr,
+			    u8 is_c45, u8 page, u16 reg, u16 *data)
+{
+	u32 cmd_reg_value;
+	u32 sta_reg_value;
+	u32 time_cnt;
+
+	if (phy_addr > MDIO_MAX_PHY_ADDR) {
+		dev_err(mdio_dev->dev, "Wrong phy address: phy_addr(%x)\n",
+			phy_addr);
+		return -EINVAL;
+	}
+
+	dev_dbg(mdio_dev->dev, "mdio(%d) base is %p\n",
+		mdio_dev->gidx, mdio_dev->vbase);
+	dev_dbg(mdio_dev->dev,
+		"phy_addr=%d, is_c45=%d, devad=%d, regnum=%#x!\n",
+		phy_addr, is_c45, page, reg);
+
+	/* Step 1; waiting for MDIO_COMMAND_REG 's mdio_start==0,	*/
+	/*	after that can do read or write*/
+	for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+		cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+						 MDIO_COMMAND_REG,
+						 MDIO_CMD_START_B);
+		if (!cmd_reg_value)
+			break;
+	}
+	if (cmd_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO is busy!\n");
+		return -EBUSY;
+	}
+
+	if (!is_c45) {
+		cmd_reg_value = MDIO_ST_CLAUSE_22 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C22_READ << MDIO_CMD_OP_S;
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+		cmd_reg_value |= (reg & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	} else {
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_ADDR_REG, MDIO_ADDR_DATA_M,
+				   MDIO_ADDR_DATA_S, reg);
+
+		/* Step 2; config the cmd-reg to write addr*/
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_WRITE_ADDR << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+
+		/* Step 3; waiting for MDIO_COMMAND_REG 's mdio_start==0,*/
+		/*check for read or write opt is finished */
+		for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+			cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+							 MDIO_COMMAND_REG,
+							 MDIO_CMD_START_B);
+			if (!cmd_reg_value)
+				break;
+		}
+		if (cmd_reg_value) {
+			dev_err(mdio_dev->dev, "MDIO is busy\n");
+			return -EBUSY;
+		}
+
+		/* Step 4; config cmd-reg, send read opt */
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_READ << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	}
+
+	/* Step 5; waiting for MDIO_COMMAND_REG 's mdio_start==0,*/
+	/* check for read or write opt is finished */
+	for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+		cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+						 MDIO_COMMAND_REG,
+						 MDIO_CMD_START_B);
+		if (!cmd_reg_value)
+			break;
+	}
+	if (cmd_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO is busy\n");
+		return -EBUSY;
+	}
+
+	sta_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+					 MDIO_STA_REG, MDIO_STATE_STA_B);
+	if (sta_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO Read failed!\n");
+		return -EBUSY;
+	}
+
+	/* Step 6; get out data*/
+	*data = (u16)MDIO_GET_REG_FIELD(mdio_dev, MDIO_RDATA_REG,
+					MDIO_RDATA_DATA_M, MDIO_RDATA_DATA_S);
+
+	return 0;
+}
+
+/**
+ *hns_mdio_write_hw - write phy regs
+ *@mdio_dev: mdio device
+ *@phy_addr: phy addr
+ *@is_c45: 0 means the clause 22, none 0 means clause 45
+ *@page: phy page addr
+ *@reg:  phy reg
+ *@data: regs data
+ *return status
+ */
+static int hns_mdio_write_hw(struct hns_mdio_device *mdio_dev, u8 phy_addr,
+			     u8 is_c45, u8 page, u16 reg, u16 data)
+{
+	u32 cmd_reg_value;
+	u32 time_cnt;
+
+	if (phy_addr > MDIO_MAX_PHY_ADDR) {
+		dev_err(mdio_dev->dev, "Wrong phy address: phy_addr(%x)\n",
+			phy_addr);
+		return -EINVAL;
+	}
+
+	dev_dbg(mdio_dev->dev, "mdio(%d) base is %p,write data=%d\n",
+		mdio_dev->gidx, mdio_dev->vbase, data);
+	dev_dbg(mdio_dev->dev, "phy_addr=%d, is_c45=%d, devad=%d, regnum=%#x\n",
+		phy_addr, is_c45, page, reg);
+
+	/* Step 1; waiting for MDIO_COMMAND_REG 's mdio_start==0,*/
+	/*	after that can do read or wtrite*/
+	for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+		cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+						 MDIO_COMMAND_REG,
+						 MDIO_CMD_START_B);
+		if (!cmd_reg_value)
+			break;
+	}
+	if (cmd_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO is busy\n");
+		return -EBUSY;
+	}
+
+	if (!is_c45) {
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_WDATA_REG, MDIO_WDATA_DATA_M,
+				   MDIO_WDATA_DATA_S, data);
+
+		cmd_reg_value = MDIO_ST_CLAUSE_22 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C22_WRITE << MDIO_CMD_OP_S;
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+		cmd_reg_value |= (reg & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	} else {
+		/* Step 2; config the cmd-reg to write addr*/
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_ADDR_REG, MDIO_ADDR_DATA_M,
+				   MDIO_ADDR_DATA_S, reg);
+
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_WRITE_ADDR << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+
+		/* Step 3; waiting for MDIO_COMMAND_REG's mdio_start==0,*/
+		/* check for read or write opt is finished */
+		for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+			cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+							 MDIO_COMMAND_REG,
+							 MDIO_CMD_START_B);
+			if (!cmd_reg_value)
+				break;
+		}
+		if (cmd_reg_value) {
+			dev_err(mdio_dev->dev, "MDIO is busy\n");
+			return -EBUSY;
+		}
+
+		/* Step 4; config the data needed writing */
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_WDATA_REG, MDIO_WDATA_DATA_M,
+				   MDIO_WDATA_DATA_S, data);
+
+		/* Step 5; config the cmd-reg for the write opt*/
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_WRITE_DATA << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	}
+
+	return 0;
+}
+
+/**
+ * hns_mdio_get_chip_id - get parent node's chip-id attribute
+ * @dpev: mdio platform device
+ * return chip id if found the chip-id, or return 0
+ */
+static u32 hns_mdio_get_chip_id(struct platform_device *pdev)
+{
+	struct device_node *np;
+	u32 chip_id;
+	int ret;
+
+	np = of_get_parent(pdev->dev.of_node);
+	if (!np)
+		return 0;
+
+	ret = of_property_read_u32(np, "chip-id", &chip_id);
+	if (ret)
+		return 0;
+
+	return chip_id;
+}
+
+/**
+ * hns_mdio_write - access phy register
+ * @bus: mdio bus
+ * @phy_id: phy id
+ * @regnum: register num
+ * @value: register value
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_write(struct mii_bus *bus,
+			  int phy_id, int regnum, u16 value)
+{
+	int ret;
+	struct hns_mdio_device *mdio_dev = (struct hns_mdio_device *)bus->priv;
+	u8 devad = ((regnum >> 16) & 0x1f);
+	u8 is_c45 = !!(regnum & MII_ADDR_C45);
+
+	ret = hns_mdio_write_hw(mdio_dev, phy_id, is_c45, devad,
+				(u16)(regnum & 0xffff), value);
+	if (ret) {
+		dev_err(&bus->dev, "write_phy_reg fail, phy_id=%d, devad=%d,regnum=%#x, value=%#x!\n",
+			phy_id, devad, regnum, value);
+
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * hns_mdio_read - access phy register
+ * @bus: mdio bus
+ * @phy_id: phy id
+ * @regnum: register num
+ * @value: register value
+ *
+ * Return phy register value
+ */
+static int hns_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+	int ret;
+	u16 reg_val = 0;
+	u8 devad = ((regnum >> 16) & 0x1f);
+	u8 is_c45 = !!(regnum & MII_ADDR_C45);
+	struct hns_mdio_device *mdio_dev = (struct hns_mdio_device *)bus->priv;
+
+	ret = hns_mdio_read_hw(mdio_dev, phy_id, is_c45, devad,
+			       (u16)(regnum & 0xffff), &reg_val);
+	if (ret) {
+		dev_err(&bus->dev, "read_phy_reg fail, mdio_idx=%d, phy_id=%d, devad=%d,regnum=%#x!\n",
+			mdio_dev->gidx,	phy_id, devad, regnum);
+
+		return ret;
+	}
+
+	return reg_val;
+}
+
+/**
+ * hns_mdio_reset - reset mdio bus
+ * @bus: mdio bus
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_reset(struct mii_bus *bus)
+{
+	return 0;
+}
+
+/**
+ * hns_mdio_bus_name - get mdio bus name
+ * @name: mdio bus name
+ * @np: mdio device node pointer
+ */
+static void hns_mdio_bus_name(char *name, struct device_node *np)
+{
+	const u32 *addr;
+	u64 taddr = OF_BAD_ADDR;
+
+	addr = of_get_address(np, 0, NULL, NULL);
+	if (addr)
+		taddr = of_translate_address(np, addr);
+
+	snprintf(name, MII_BUS_ID_SIZE, "%s@%llx", np->name,
+		 (unsigned long long)taddr);
+}
+
+/**
+ * hns_mdio_probe - probe mdio device
+ * @pdev: mdio platform device
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_probe(struct platform_device *pdev)
+{
+	struct device_node *np;
+	struct hns_mdio_device *mdio_dev;
+	struct mii_bus *new_bus;
+	struct resource *res;
+	int ret;
+
+	if (!pdev) {
+		dev_err(NULL, "pdev is NULL!\r\n");
+		return -ENODEV;
+	}
+	np = pdev->dev.of_node;
+	mdio_dev = devm_kzalloc(&pdev->dev, sizeof(*mdio_dev), GFP_KERNEL);
+	if (!mdio_dev)
+		return -ENOMEM;
+
+	new_bus = devm_mdiobus_alloc(&pdev->dev);
+	if (!new_bus) {
+		dev_err(&pdev->dev, "mdiobus_alloc fail!\n");
+		return -ENOMEM;
+	}
+
+	new_bus->name = MDIO_BUS_NAME;
+	new_bus->read = hns_mdio_read;
+	new_bus->write = hns_mdio_write;
+	new_bus->reset = hns_mdio_reset;
+	new_bus->priv = mdio_dev;
+	hns_mdio_bus_name(new_bus->id, np);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	mdio_dev->vbase = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(mdio_dev->vbase)) {
+		ret = PTR_ERR(mdio_dev->vbase);
+		return -EFAULT;
+	}
+	mdio_dev->index = 0;
+	mdio_dev->chip_id = hns_mdio_get_chip_id(pdev);
+	mdio_dev->gidx = mdio_dev->chip_id;
+	mdio_dev->dev = &pdev->dev;
+
+	new_bus->irq = devm_kcalloc(&pdev->dev, PHY_MAX_ADDR,
+				    sizeof(int), GFP_KERNEL);
+	if (!new_bus->irq)
+		return -ENOMEM;
+
+	new_bus->parent = &pdev->dev;
+	platform_set_drvdata(pdev, new_bus);
+
+	ret = of_mdiobus_register(new_bus, np);
+	if (ret) {
+		dev_err(&pdev->dev, "Cannot register as MDIO bus!\n");
+		platform_set_drvdata(pdev, NULL);
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * hns_mdio_remove - remove mdio device
+ * @pdev: mdio platform device
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_remove(struct platform_device *pdev)
+{
+	struct hns_mdio_device *mdio_dev;
+	struct mii_bus *bus;
+
+	bus = platform_get_drvdata(pdev);
+	mdio_dev = (struct hns_mdio_device *)bus->priv;
+
+	mdiobus_unregister(bus);
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static const struct of_device_id hns_mdio_match[] = {
+	{.compatible = "hisilicon,mdio"},
+	{}
+};
+
+static struct platform_driver hns_mdio_driver = {
+	.probe = hns_mdio_probe,
+	.remove = hns_mdio_remove,
+	.driver = {
+		   .name = MDIO_DRV_NAME,
+		   .of_match_table = hns_mdio_match,
+		   },
+};
+
+module_platform_driver(hns_mdio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Huawei Tech. Co., Ltd.");
+MODULE_DESCRIPTION("Hisilicon HNS MDIO driver");
+MODULE_ALIAS("platform:" MDIO_DRV_NAME);
-- 
1.9.1

WARNING: multiple messages have this Message-ID (diff)
From: liguozhu@hisilicon.com (Kenneth Lee)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH 3/5] net: add Hisilicon Network Subsystem MDIO support
Date: Fri, 14 Aug 2015 18:30:20 +0800	[thread overview]
Message-ID: <1439548222-231611-4-git-send-email-liguozhu@hisilicon.com> (raw)
In-Reply-To: <1439548222-231611-1-git-send-email-liguozhu@hisilicon.com>

The MDIO support for Hisilicon Network Subsystem. It is used in Hislicon
P660 and Hi1610 SoC to control the external PHY

Signed-off-by: Yisen Zhuang <Yisen.Zhuang@huawei.com>
Signed-off-by: Kenneth Lee <liguozhu@huawei.com>
---
 drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c | 597 +++++++++++++++++++++
 1 file changed, 597 insertions(+)
 create mode 100644 drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c

diff --git a/drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c b/drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c
new file mode 100644
index 0000000..7113fa8
--- /dev/null
+++ b/drivers/net/ethernet/hisilicon/hns/hns_mdio_main.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2014-2015 Hisilicon Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/of_address.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock_types.h>
+
+#define MDIO_DRV_NAME "hi-mdio"
+#define MDIO_BUS_NAME "Hisilicon MII Bus"
+#define MDIO_DRV_VERSION "1.1.0"
+#define MDIO_COPYRIGHT "Copyright(c) 2015 Huawei Corporation."
+#define MDIO_DRV_STRING MDIO_BUS_NAME
+#define MDIO_DEFAULT_DEVICE_DESCR MDIO_BUS_NAME
+
+#define MDIO_CTL_DEV_ADDR(x)	(x & 0x1f)
+#define MDIO_CTL_PORT_ADDR(x)	((x & 0x1f) << 5)
+
+#define MDIO_BASE_ADDR			0x403C0000
+#define MDIO_REG_ADDR_LEN		0x1000
+#define MDIO_PHY_GRP_LEN		0x100
+#define MDIO_REG_LEN			0x10
+#define MDIO_PHY_ADDR_NUM		5
+#define MDIO_MAX_PHY_ADDR		0x1F
+#define MDIO_MAX_PHY_REG_ADDR		0xFFFF
+
+#define MDIO_TIMEOUT			1000000
+
+struct hns_mdio_device {
+	struct device *dev;
+	void *vbase;		/* mdio reg base address */
+	u8 phy_class[PHY_MAX_ADDR];
+	u8 index;
+	u8 chip_id;
+	u8 gidx;		/* global index */
+};
+
+#define MDIO_COMMAND_REG		0x0
+#define MDIO_ADDR_REG			0x4
+#define MDIO_WDATA_REG			0x8
+#define MDIO_RDATA_REG			0xc
+#define MDIO_STA_REG			0x10
+
+#define MDIO_CMD_DEVAD_M	0x1f
+#define MDIO_CMD_DEVAD_S	0
+#define MDIO_CMD_PRTAD_M	0x1f
+#define MDIO_CMD_PRTAD_S	5
+#define MDIO_CMD_OP_M		0x3
+#define MDIO_CMD_OP_S		10
+#define MDIO_CMD_ST_M		0x3
+#define MDIO_CMD_ST_S		12
+#define MDIO_CMD_START_B	14
+
+#define MDIO_ADDR_DATA_M	0xffff
+#define MDIO_ADDR_DATA_S	0
+
+#define MDIO_WDATA_DATA_M	0xffff
+#define MDIO_WDATA_DATA_S	0
+
+#define MDIO_RDATA_DATA_M	0xffff
+#define MDIO_RDATA_DATA_S	0
+
+#define MDIO_STATE_STA_B	0
+
+enum mdio_st_clause {
+	MDIO_ST_CLAUSE_45 = 0,
+	MDIO_ST_CLAUSE_22
+};
+
+enum mdio_c22_op_seq {
+	MDIO_C22_WRITE = 1,
+	MDIO_C22_READ = 2
+};
+
+enum mdio_c45_op_seq {
+	MDIO_C45_WRITE_ADDR = 0,
+	MDIO_C45_WRITE_DATA,
+	MDIO_C45_READ_INCREMENT,
+	MDIO_C45_READ
+};
+
+static inline void mdio_write_reg(void *base, u32 reg, u32 value)
+{
+	u8 __iomem *reg_addr = ACCESS_ONCE(base);
+
+	writel(value, reg_addr + reg);
+}
+
+#define MDIO_WRITE_REG(a, reg, value) \
+	mdio_write_reg((a)->vbase, (reg), (value))
+
+static inline u32 mdio_read_reg(void *base, u32 reg)
+{
+	u8 __iomem *reg_addr = ACCESS_ONCE(base);
+
+	return readl(reg_addr + reg);
+}
+
+#define MDIO_READ_REG(a, reg) \
+	mdio_read_reg((a)->vbase, (reg))
+
+#define mdio_set_field(origin, mask, shift, val) \
+	do { \
+		(origin) &= (~((mask) << (shift))); \
+		(origin) |= (((val) & (mask)) << (shift)); \
+	} while (0)
+
+#define mdio_get_field(origin, mask, shift) (((origin) >> (shift)) & (mask))
+
+static void mdio_set_reg_field(void *base, u32 reg, u32 mask, u32 shift,
+			       u32 val)
+{
+	u32 origin = mdio_read_reg(base, reg);
+
+	mdio_set_field(origin, mask, shift, val);
+	mdio_write_reg(base, reg, origin);
+}
+
+#define MDIO_SET_REG_FIELD(dev, reg, mask, shift, val) \
+	mdio_set_reg_field((dev)->vbase, (reg), (mask), (shift), (val))
+
+static u32 mdio_get_reg_field(void *base, u32 reg, u32 mask, u32 shift)
+{
+	u32 origin;
+
+	origin = mdio_read_reg(base, reg);
+	return mdio_get_field(origin, mask, shift);
+}
+
+#define MDIO_GET_REG_FIELD(dev, reg, mask, shift) \
+		mdio_get_reg_field((dev)->vbase, (reg), (mask), (shift))
+
+#define MDIO_SET_REG_BIT(dev, reg, bit, val) \
+		mdio_set_reg_field((dev)->vbase, (reg), 0x1ull, (bit), (val))
+
+#define MDIO_GET_REG_BIT(dev, reg, bit) \
+		mdio_get_reg_field((dev)->vbase, (reg), 0x1ull, (bit))
+
+/**
+ *hns_mdio_read_hw - read phy regs
+ *@mdio_dev: mdio device
+ *@phy_addr:phy addr
+ *@is_c45:
+ *@page:
+ *@reg: reg
+ *@data:regs data
+ *return status
+ */
+static int hns_mdio_read_hw(struct hns_mdio_device *mdio_dev, u8 phy_addr,
+			    u8 is_c45, u8 page, u16 reg, u16 *data)
+{
+	u32 cmd_reg_value;
+	u32 sta_reg_value;
+	u32 time_cnt;
+
+	if (phy_addr > MDIO_MAX_PHY_ADDR) {
+		dev_err(mdio_dev->dev, "Wrong phy address: phy_addr(%x)\n",
+			phy_addr);
+		return -EINVAL;
+	}
+
+	dev_dbg(mdio_dev->dev, "mdio(%d) base is %p\n",
+		mdio_dev->gidx, mdio_dev->vbase);
+	dev_dbg(mdio_dev->dev,
+		"phy_addr=%d, is_c45=%d, devad=%d, regnum=%#x!\n",
+		phy_addr, is_c45, page, reg);
+
+	/* Step 1; waiting for MDIO_COMMAND_REG 's mdio_start==0,	*/
+	/*	after that can do read or write*/
+	for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+		cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+						 MDIO_COMMAND_REG,
+						 MDIO_CMD_START_B);
+		if (!cmd_reg_value)
+			break;
+	}
+	if (cmd_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO is busy!\n");
+		return -EBUSY;
+	}
+
+	if (!is_c45) {
+		cmd_reg_value = MDIO_ST_CLAUSE_22 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C22_READ << MDIO_CMD_OP_S;
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+		cmd_reg_value |= (reg & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	} else {
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_ADDR_REG, MDIO_ADDR_DATA_M,
+				   MDIO_ADDR_DATA_S, reg);
+
+		/* Step 2; config the cmd-reg to write addr*/
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_WRITE_ADDR << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+
+		/* Step 3; waiting for MDIO_COMMAND_REG 's mdio_start==0,*/
+		/*check for read or write opt is finished */
+		for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+			cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+							 MDIO_COMMAND_REG,
+							 MDIO_CMD_START_B);
+			if (!cmd_reg_value)
+				break;
+		}
+		if (cmd_reg_value) {
+			dev_err(mdio_dev->dev, "MDIO is busy\n");
+			return -EBUSY;
+		}
+
+		/* Step 4; config cmd-reg, send read opt */
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_READ << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	}
+
+	/* Step 5; waiting for MDIO_COMMAND_REG 's mdio_start==0,*/
+	/* check for read or write opt is finished */
+	for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+		cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+						 MDIO_COMMAND_REG,
+						 MDIO_CMD_START_B);
+		if (!cmd_reg_value)
+			break;
+	}
+	if (cmd_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO is busy\n");
+		return -EBUSY;
+	}
+
+	sta_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+					 MDIO_STA_REG, MDIO_STATE_STA_B);
+	if (sta_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO Read failed!\n");
+		return -EBUSY;
+	}
+
+	/* Step 6; get out data*/
+	*data = (u16)MDIO_GET_REG_FIELD(mdio_dev, MDIO_RDATA_REG,
+					MDIO_RDATA_DATA_M, MDIO_RDATA_DATA_S);
+
+	return 0;
+}
+
+/**
+ *hns_mdio_write_hw - write phy regs
+ *@mdio_dev: mdio device
+ *@phy_addr: phy addr
+ *@is_c45: 0 means the clause 22, none 0 means clause 45
+ *@page: phy page addr
+ *@reg:  phy reg
+ *@data: regs data
+ *return status
+ */
+static int hns_mdio_write_hw(struct hns_mdio_device *mdio_dev, u8 phy_addr,
+			     u8 is_c45, u8 page, u16 reg, u16 data)
+{
+	u32 cmd_reg_value;
+	u32 time_cnt;
+
+	if (phy_addr > MDIO_MAX_PHY_ADDR) {
+		dev_err(mdio_dev->dev, "Wrong phy address: phy_addr(%x)\n",
+			phy_addr);
+		return -EINVAL;
+	}
+
+	dev_dbg(mdio_dev->dev, "mdio(%d) base is %p,write data=%d\n",
+		mdio_dev->gidx, mdio_dev->vbase, data);
+	dev_dbg(mdio_dev->dev, "phy_addr=%d, is_c45=%d, devad=%d, regnum=%#x\n",
+		phy_addr, is_c45, page, reg);
+
+	/* Step 1; waiting for MDIO_COMMAND_REG 's mdio_start==0,*/
+	/*	after that can do read or wtrite*/
+	for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+		cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+						 MDIO_COMMAND_REG,
+						 MDIO_CMD_START_B);
+		if (!cmd_reg_value)
+			break;
+	}
+	if (cmd_reg_value) {
+		dev_err(mdio_dev->dev, "MDIO is busy\n");
+		return -EBUSY;
+	}
+
+	if (!is_c45) {
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_WDATA_REG, MDIO_WDATA_DATA_M,
+				   MDIO_WDATA_DATA_S, data);
+
+		cmd_reg_value = MDIO_ST_CLAUSE_22 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C22_WRITE << MDIO_CMD_OP_S;
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+		cmd_reg_value |= (reg & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	} else {
+		/* Step 2; config the cmd-reg to write addr*/
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_ADDR_REG, MDIO_ADDR_DATA_M,
+				   MDIO_ADDR_DATA_S, reg);
+
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_WRITE_ADDR << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+
+		/* Step 3; waiting for MDIO_COMMAND_REG's mdio_start==0,*/
+		/* check for read or write opt is finished */
+		for (time_cnt = MDIO_TIMEOUT; time_cnt; time_cnt--) {
+			cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev,
+							 MDIO_COMMAND_REG,
+							 MDIO_CMD_START_B);
+			if (!cmd_reg_value)
+				break;
+		}
+		if (cmd_reg_value) {
+			dev_err(mdio_dev->dev, "MDIO is busy\n");
+			return -EBUSY;
+		}
+
+		/* Step 4; config the data needed writing */
+		MDIO_SET_REG_FIELD(mdio_dev, MDIO_WDATA_REG, MDIO_WDATA_DATA_M,
+				   MDIO_WDATA_DATA_S, data);
+
+		/* Step 5; config the cmd-reg for the write opt*/
+		cmd_reg_value = MDIO_ST_CLAUSE_45 << MDIO_CMD_ST_S;
+		cmd_reg_value |= MDIO_C45_WRITE_DATA << MDIO_CMD_OP_S;
+
+		/* mdio_st==2'b00: is configing port addr*/
+		cmd_reg_value |=
+			(phy_addr & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
+
+		/* mdio_st == 2'b00: is configing dev_addr*/
+		cmd_reg_value |= (page & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
+		cmd_reg_value |= 1 << MDIO_CMD_START_B;
+		MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
+	}
+
+	return 0;
+}
+
+/**
+ * hns_mdio_get_chip_id - get parent node's chip-id attribute
+ * @dpev: mdio platform device
+ * return chip id if found the chip-id, or return 0
+ */
+static u32 hns_mdio_get_chip_id(struct platform_device *pdev)
+{
+	struct device_node *np;
+	u32 chip_id;
+	int ret;
+
+	np = of_get_parent(pdev->dev.of_node);
+	if (!np)
+		return 0;
+
+	ret = of_property_read_u32(np, "chip-id", &chip_id);
+	if (ret)
+		return 0;
+
+	return chip_id;
+}
+
+/**
+ * hns_mdio_write - access phy register
+ * @bus: mdio bus
+ * @phy_id: phy id
+ * @regnum: register num
+ * @value: register value
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_write(struct mii_bus *bus,
+			  int phy_id, int regnum, u16 value)
+{
+	int ret;
+	struct hns_mdio_device *mdio_dev = (struct hns_mdio_device *)bus->priv;
+	u8 devad = ((regnum >> 16) & 0x1f);
+	u8 is_c45 = !!(regnum & MII_ADDR_C45);
+
+	ret = hns_mdio_write_hw(mdio_dev, phy_id, is_c45, devad,
+				(u16)(regnum & 0xffff), value);
+	if (ret) {
+		dev_err(&bus->dev, "write_phy_reg fail, phy_id=%d, devad=%d,regnum=%#x, value=%#x!\n",
+			phy_id, devad, regnum, value);
+
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * hns_mdio_read - access phy register
+ * @bus: mdio bus
+ * @phy_id: phy id
+ * @regnum: register num
+ * @value: register value
+ *
+ * Return phy register value
+ */
+static int hns_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
+{
+	int ret;
+	u16 reg_val = 0;
+	u8 devad = ((regnum >> 16) & 0x1f);
+	u8 is_c45 = !!(regnum & MII_ADDR_C45);
+	struct hns_mdio_device *mdio_dev = (struct hns_mdio_device *)bus->priv;
+
+	ret = hns_mdio_read_hw(mdio_dev, phy_id, is_c45, devad,
+			       (u16)(regnum & 0xffff), &reg_val);
+	if (ret) {
+		dev_err(&bus->dev, "read_phy_reg fail, mdio_idx=%d, phy_id=%d, devad=%d,regnum=%#x!\n",
+			mdio_dev->gidx,	phy_id, devad, regnum);
+
+		return ret;
+	}
+
+	return reg_val;
+}
+
+/**
+ * hns_mdio_reset - reset mdio bus
+ * @bus: mdio bus
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_reset(struct mii_bus *bus)
+{
+	return 0;
+}
+
+/**
+ * hns_mdio_bus_name - get mdio bus name
+ * @name: mdio bus name
+ * @np: mdio device node pointer
+ */
+static void hns_mdio_bus_name(char *name, struct device_node *np)
+{
+	const u32 *addr;
+	u64 taddr = OF_BAD_ADDR;
+
+	addr = of_get_address(np, 0, NULL, NULL);
+	if (addr)
+		taddr = of_translate_address(np, addr);
+
+	snprintf(name, MII_BUS_ID_SIZE, "%s@%llx", np->name,
+		 (unsigned long long)taddr);
+}
+
+/**
+ * hns_mdio_probe - probe mdio device
+ * @pdev: mdio platform device
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_probe(struct platform_device *pdev)
+{
+	struct device_node *np;
+	struct hns_mdio_device *mdio_dev;
+	struct mii_bus *new_bus;
+	struct resource *res;
+	int ret;
+
+	if (!pdev) {
+		dev_err(NULL, "pdev is NULL!\r\n");
+		return -ENODEV;
+	}
+	np = pdev->dev.of_node;
+	mdio_dev = devm_kzalloc(&pdev->dev, sizeof(*mdio_dev), GFP_KERNEL);
+	if (!mdio_dev)
+		return -ENOMEM;
+
+	new_bus = devm_mdiobus_alloc(&pdev->dev);
+	if (!new_bus) {
+		dev_err(&pdev->dev, "mdiobus_alloc fail!\n");
+		return -ENOMEM;
+	}
+
+	new_bus->name = MDIO_BUS_NAME;
+	new_bus->read = hns_mdio_read;
+	new_bus->write = hns_mdio_write;
+	new_bus->reset = hns_mdio_reset;
+	new_bus->priv = mdio_dev;
+	hns_mdio_bus_name(new_bus->id, np);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	mdio_dev->vbase = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(mdio_dev->vbase)) {
+		ret = PTR_ERR(mdio_dev->vbase);
+		return -EFAULT;
+	}
+	mdio_dev->index = 0;
+	mdio_dev->chip_id = hns_mdio_get_chip_id(pdev);
+	mdio_dev->gidx = mdio_dev->chip_id;
+	mdio_dev->dev = &pdev->dev;
+
+	new_bus->irq = devm_kcalloc(&pdev->dev, PHY_MAX_ADDR,
+				    sizeof(int), GFP_KERNEL);
+	if (!new_bus->irq)
+		return -ENOMEM;
+
+	new_bus->parent = &pdev->dev;
+	platform_set_drvdata(pdev, new_bus);
+
+	ret = of_mdiobus_register(new_bus, np);
+	if (ret) {
+		dev_err(&pdev->dev, "Cannot register as MDIO bus!\n");
+		platform_set_drvdata(pdev, NULL);
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * hns_mdio_remove - remove mdio device
+ * @pdev: mdio platform device
+ *
+ * Return 0 on success, negative on failure
+ */
+static int hns_mdio_remove(struct platform_device *pdev)
+{
+	struct hns_mdio_device *mdio_dev;
+	struct mii_bus *bus;
+
+	bus = platform_get_drvdata(pdev);
+	mdio_dev = (struct hns_mdio_device *)bus->priv;
+
+	mdiobus_unregister(bus);
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static const struct of_device_id hns_mdio_match[] = {
+	{.compatible = "hisilicon,mdio"},
+	{}
+};
+
+static struct platform_driver hns_mdio_driver = {
+	.probe = hns_mdio_probe,
+	.remove = hns_mdio_remove,
+	.driver = {
+		   .name = MDIO_DRV_NAME,
+		   .of_match_table = hns_mdio_match,
+		   },
+};
+
+module_platform_driver(hns_mdio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Huawei Tech. Co., Ltd.");
+MODULE_DESCRIPTION("Hisilicon HNS MDIO driver");
+MODULE_ALIAS("platform:" MDIO_DRV_NAME);
-- 
1.9.1

  parent reply	other threads:[~2015-08-14 10:27 UTC|newest]

Thread overview: 48+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-08-14 10:30 [PATCH 0/5] net: Hisilicon Network Subsystem support Kenneth Lee
2015-08-14 10:30 ` Kenneth Lee
2015-08-14 10:30 ` Kenneth Lee
2015-08-14 10:30 ` Kenneth Lee
2015-08-14 10:30 ` [PATCH 1/5] net: add Hisilicon Network Subsystem support (config and documents) Kenneth Lee
2015-08-14 10:30   ` Kenneth Lee
2015-08-14 10:30   ` Kenneth Lee
2015-08-14 20:49   ` Arnd Bergmann
2015-08-14 20:49     ` Arnd Bergmann
2015-08-17  1:28     ` 答复: " Liguozhu (Kenneth)
2015-08-17  1:28       ` Liguozhu (Kenneth)
2015-08-17  1:28       ` Liguozhu (Kenneth)
2015-08-21 14:00       ` Arnd Bergmann
2015-08-21 14:00         ` Arnd Bergmann
2015-08-21 14:00         ` Arnd Bergmann
2015-08-21 14:00         ` Arnd Bergmann
2015-08-27  9:50         ` Kenneth Lee
2015-08-27  9:50           ` Kenneth Lee
2015-08-27  9:50           ` Kenneth Lee
2015-08-14 10:30 ` [PATCH 2/5] net: add Hisilicon Network Subsystem hnae framework support Kenneth Lee
2015-08-14 10:30   ` Kenneth Lee
2015-08-14 10:30   ` Kenneth Lee
2015-08-17 19:25   ` David Miller
2015-08-17 19:25     ` David Miller
2015-08-17 19:25     ` David Miller
2015-08-18  0:12   ` Alexey Klimov
2015-08-18  0:12     ` Alexey Klimov
2015-08-18  0:12     ` Alexey Klimov
2015-08-21  6:36     ` Kenneth Lee
2015-08-21  6:36       ` Kenneth Lee
2015-08-21  6:36       ` Kenneth Lee
2015-08-14 10:30 ` Kenneth Lee [this message]
2015-08-14 10:30   ` [PATCH 3/5] net: add Hisilicon Network Subsystem MDIO support Kenneth Lee
2015-08-14 10:30   ` Kenneth Lee
2015-08-14 20:57   ` Arnd Bergmann
2015-08-14 20:57     ` Arnd Bergmann
2015-08-17  9:17     ` Kenneth Lee
2015-08-17  9:17       ` Kenneth Lee
2015-08-17  9:17       ` Kenneth Lee
2015-08-17  9:17       ` Kenneth Lee
2015-08-21 14:01       ` Arnd Bergmann
2015-08-21 14:01         ` Arnd Bergmann
2015-08-21 14:01         ` Arnd Bergmann
2015-08-14 10:30 ` [PATCH 4/5] net: add Hisilicon Network Subsystem DSAF support Kenneth Lee
2015-08-14 10:30   ` Kenneth Lee
2015-08-14 10:30 ` [PATCH 5/5] net: add Hisilicon Network Subsystem basic ethernet support Kenneth Lee
2015-08-14 10:30   ` Kenneth Lee
2015-08-14 10:30   ` Kenneth Lee

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1439548222-231611-4-git-send-email-liguozhu@hisilicon.com \
    --to=liguozhu@hisilicon.com \
    --cc=Yisen.Zhuang@huawei.com \
    --cc=catalin.marinas@arm.com \
    --cc=davem@davemloft.net \
    --cc=devicetree@vger.kernel.org \
    --cc=dingtianhong@huawei.com \
    --cc=galak@codeaurora.org \
    --cc=huangdaode@hisilicon.com \
    --cc=ijc+devicetree@hellion.org.uk \
    --cc=liguozhu@huawei.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linuxarm@huawei.com \
    --cc=mark.rutland@arm.com \
    --cc=netdev@vger.kernel.org \
    --cc=paul.gortmaker@windriver.com \
    --cc=pawel.moll@arm.com \
    --cc=robh+dt@kernel.org \
    --cc=salil.mehta@huawei.com \
    --cc=will.deacon@arm.com \
    --cc=zhangfei.gao@linaro.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.