18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * Driver for the MDIO interface of Marvell network interfaces.
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Since the MDIO interface of Marvell network interfaces is shared
58c2ecf20Sopenharmony_ci * between all network interfaces, having a single driver allows to
68c2ecf20Sopenharmony_ci * handle concurrent accesses properly (you may have four Ethernet
78c2ecf20Sopenharmony_ci * ports, but they in fact share the same SMI interface to access
88c2ecf20Sopenharmony_ci * the MDIO bus). This driver is currently used by the mvneta and
98c2ecf20Sopenharmony_ci * mv643xx_eth drivers.
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * Copyright (C) 2012 Marvell
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public
168c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any
178c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied.
188c2ecf20Sopenharmony_ci */
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include <linux/clk.h>
218c2ecf20Sopenharmony_ci#include <linux/delay.h>
228c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
238c2ecf20Sopenharmony_ci#include <linux/io.h>
248c2ecf20Sopenharmony_ci#include <linux/kernel.h>
258c2ecf20Sopenharmony_ci#include <linux/module.h>
268c2ecf20Sopenharmony_ci#include <linux/of_device.h>
278c2ecf20Sopenharmony_ci#include <linux/of_mdio.h>
288c2ecf20Sopenharmony_ci#include <linux/phy.h>
298c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
308c2ecf20Sopenharmony_ci#include <linux/sched.h>
318c2ecf20Sopenharmony_ci#include <linux/wait.h>
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define MVMDIO_SMI_DATA_SHIFT		0
348c2ecf20Sopenharmony_ci#define MVMDIO_SMI_PHY_ADDR_SHIFT	16
358c2ecf20Sopenharmony_ci#define MVMDIO_SMI_PHY_REG_SHIFT	21
368c2ecf20Sopenharmony_ci#define MVMDIO_SMI_READ_OPERATION	BIT(26)
378c2ecf20Sopenharmony_ci#define MVMDIO_SMI_WRITE_OPERATION	0
388c2ecf20Sopenharmony_ci#define MVMDIO_SMI_READ_VALID		BIT(27)
398c2ecf20Sopenharmony_ci#define MVMDIO_SMI_BUSY			BIT(28)
408c2ecf20Sopenharmony_ci#define MVMDIO_ERR_INT_CAUSE		0x007C
418c2ecf20Sopenharmony_ci#define  MVMDIO_ERR_INT_SMI_DONE	0x00000010
428c2ecf20Sopenharmony_ci#define MVMDIO_ERR_INT_MASK		0x0080
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci#define MVMDIO_XSMI_MGNT_REG		0x0
458c2ecf20Sopenharmony_ci#define  MVMDIO_XSMI_PHYADDR_SHIFT	16
468c2ecf20Sopenharmony_ci#define  MVMDIO_XSMI_DEVADDR_SHIFT	21
478c2ecf20Sopenharmony_ci#define  MVMDIO_XSMI_WRITE_OPERATION	(0x5 << 26)
488c2ecf20Sopenharmony_ci#define  MVMDIO_XSMI_READ_OPERATION	(0x7 << 26)
498c2ecf20Sopenharmony_ci#define  MVMDIO_XSMI_READ_VALID		BIT(29)
508c2ecf20Sopenharmony_ci#define  MVMDIO_XSMI_BUSY		BIT(30)
518c2ecf20Sopenharmony_ci#define MVMDIO_XSMI_ADDR_REG		0x8
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci/*
548c2ecf20Sopenharmony_ci * SMI Timeout measurements:
558c2ecf20Sopenharmony_ci * - Kirkwood 88F6281 (Globalscale Dreamplug): 45us to 95us (Interrupt)
568c2ecf20Sopenharmony_ci * - Armada 370       (Globalscale Mirabox):   41us to 43us (Polled)
578c2ecf20Sopenharmony_ci */
588c2ecf20Sopenharmony_ci#define MVMDIO_SMI_TIMEOUT		1000 /* 1000us = 1ms */
598c2ecf20Sopenharmony_ci#define MVMDIO_SMI_POLL_INTERVAL_MIN	45
608c2ecf20Sopenharmony_ci#define MVMDIO_SMI_POLL_INTERVAL_MAX	55
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci#define MVMDIO_XSMI_POLL_INTERVAL_MIN	150
638c2ecf20Sopenharmony_ci#define MVMDIO_XSMI_POLL_INTERVAL_MAX	160
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistruct orion_mdio_dev {
668c2ecf20Sopenharmony_ci	void __iomem *regs;
678c2ecf20Sopenharmony_ci	struct clk *clk[4];
688c2ecf20Sopenharmony_ci	/*
698c2ecf20Sopenharmony_ci	 * If we have access to the error interrupt pin (which is
708c2ecf20Sopenharmony_ci	 * somewhat misnamed as it not only reflects internal errors
718c2ecf20Sopenharmony_ci	 * but also reflects SMI completion), use that to wait for
728c2ecf20Sopenharmony_ci	 * SMI access completion instead of polling the SMI busy bit.
738c2ecf20Sopenharmony_ci	 */
748c2ecf20Sopenharmony_ci	int err_interrupt;
758c2ecf20Sopenharmony_ci	wait_queue_head_t smi_busy_wait;
768c2ecf20Sopenharmony_ci};
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cienum orion_mdio_bus_type {
798c2ecf20Sopenharmony_ci	BUS_TYPE_SMI,
808c2ecf20Sopenharmony_ci	BUS_TYPE_XSMI
818c2ecf20Sopenharmony_ci};
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistruct orion_mdio_ops {
848c2ecf20Sopenharmony_ci	int (*is_done)(struct orion_mdio_dev *);
858c2ecf20Sopenharmony_ci	unsigned int poll_interval_min;
868c2ecf20Sopenharmony_ci	unsigned int poll_interval_max;
878c2ecf20Sopenharmony_ci};
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci/* Wait for the SMI unit to be ready for another operation
908c2ecf20Sopenharmony_ci */
918c2ecf20Sopenharmony_cistatic int orion_mdio_wait_ready(const struct orion_mdio_ops *ops,
928c2ecf20Sopenharmony_ci				 struct mii_bus *bus)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	struct orion_mdio_dev *dev = bus->priv;
958c2ecf20Sopenharmony_ci	unsigned long timeout = usecs_to_jiffies(MVMDIO_SMI_TIMEOUT);
968c2ecf20Sopenharmony_ci	unsigned long end = jiffies + timeout;
978c2ecf20Sopenharmony_ci	int timedout = 0;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	while (1) {
1008c2ecf20Sopenharmony_ci	        if (ops->is_done(dev))
1018c2ecf20Sopenharmony_ci			return 0;
1028c2ecf20Sopenharmony_ci	        else if (timedout)
1038c2ecf20Sopenharmony_ci			break;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	        if (dev->err_interrupt <= 0) {
1068c2ecf20Sopenharmony_ci			usleep_range(ops->poll_interval_min,
1078c2ecf20Sopenharmony_ci				     ops->poll_interval_max);
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci			if (time_is_before_jiffies(end))
1108c2ecf20Sopenharmony_ci				++timedout;
1118c2ecf20Sopenharmony_ci	        } else {
1128c2ecf20Sopenharmony_ci			/* wait_event_timeout does not guarantee a delay of at
1138c2ecf20Sopenharmony_ci			 * least one whole jiffie, so timeout must be no less
1148c2ecf20Sopenharmony_ci			 * than two.
1158c2ecf20Sopenharmony_ci			 */
1168c2ecf20Sopenharmony_ci			if (timeout < 2)
1178c2ecf20Sopenharmony_ci				timeout = 2;
1188c2ecf20Sopenharmony_ci			wait_event_timeout(dev->smi_busy_wait,
1198c2ecf20Sopenharmony_ci				           ops->is_done(dev), timeout);
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci			++timedout;
1228c2ecf20Sopenharmony_ci	        }
1238c2ecf20Sopenharmony_ci	}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	dev_err(bus->parent, "Timeout: SMI busy for too long\n");
1268c2ecf20Sopenharmony_ci	return  -ETIMEDOUT;
1278c2ecf20Sopenharmony_ci}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_cistatic int orion_mdio_smi_is_done(struct orion_mdio_dev *dev)
1308c2ecf20Sopenharmony_ci{
1318c2ecf20Sopenharmony_ci	return !(readl(dev->regs) & MVMDIO_SMI_BUSY);
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_cistatic const struct orion_mdio_ops orion_mdio_smi_ops = {
1358c2ecf20Sopenharmony_ci	.is_done = orion_mdio_smi_is_done,
1368c2ecf20Sopenharmony_ci	.poll_interval_min = MVMDIO_SMI_POLL_INTERVAL_MIN,
1378c2ecf20Sopenharmony_ci	.poll_interval_max = MVMDIO_SMI_POLL_INTERVAL_MAX,
1388c2ecf20Sopenharmony_ci};
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_cistatic int orion_mdio_smi_read(struct mii_bus *bus, int mii_id,
1418c2ecf20Sopenharmony_ci			       int regnum)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	struct orion_mdio_dev *dev = bus->priv;
1448c2ecf20Sopenharmony_ci	u32 val;
1458c2ecf20Sopenharmony_ci	int ret;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	if (regnum & MII_ADDR_C45)
1488c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	ret = orion_mdio_wait_ready(&orion_mdio_smi_ops, bus);
1518c2ecf20Sopenharmony_ci	if (ret < 0)
1528c2ecf20Sopenharmony_ci		return ret;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	writel(((mii_id << MVMDIO_SMI_PHY_ADDR_SHIFT) |
1558c2ecf20Sopenharmony_ci		(regnum << MVMDIO_SMI_PHY_REG_SHIFT)  |
1568c2ecf20Sopenharmony_ci		MVMDIO_SMI_READ_OPERATION),
1578c2ecf20Sopenharmony_ci	       dev->regs);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	ret = orion_mdio_wait_ready(&orion_mdio_smi_ops, bus);
1608c2ecf20Sopenharmony_ci	if (ret < 0)
1618c2ecf20Sopenharmony_ci		return ret;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	val = readl(dev->regs);
1648c2ecf20Sopenharmony_ci	if (!(val & MVMDIO_SMI_READ_VALID)) {
1658c2ecf20Sopenharmony_ci		dev_err(bus->parent, "SMI bus read not valid\n");
1668c2ecf20Sopenharmony_ci		return -ENODEV;
1678c2ecf20Sopenharmony_ci	}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	return val & GENMASK(15, 0);
1708c2ecf20Sopenharmony_ci}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_cistatic int orion_mdio_smi_write(struct mii_bus *bus, int mii_id,
1738c2ecf20Sopenharmony_ci				int regnum, u16 value)
1748c2ecf20Sopenharmony_ci{
1758c2ecf20Sopenharmony_ci	struct orion_mdio_dev *dev = bus->priv;
1768c2ecf20Sopenharmony_ci	int ret;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	if (regnum & MII_ADDR_C45)
1798c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	ret = orion_mdio_wait_ready(&orion_mdio_smi_ops, bus);
1828c2ecf20Sopenharmony_ci	if (ret < 0)
1838c2ecf20Sopenharmony_ci		return ret;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	writel(((mii_id << MVMDIO_SMI_PHY_ADDR_SHIFT) |
1868c2ecf20Sopenharmony_ci		(regnum << MVMDIO_SMI_PHY_REG_SHIFT)  |
1878c2ecf20Sopenharmony_ci		MVMDIO_SMI_WRITE_OPERATION            |
1888c2ecf20Sopenharmony_ci		(value << MVMDIO_SMI_DATA_SHIFT)),
1898c2ecf20Sopenharmony_ci	       dev->regs);
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	return 0;
1928c2ecf20Sopenharmony_ci}
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_cistatic int orion_mdio_xsmi_is_done(struct orion_mdio_dev *dev)
1958c2ecf20Sopenharmony_ci{
1968c2ecf20Sopenharmony_ci	return !(readl(dev->regs + MVMDIO_XSMI_MGNT_REG) & MVMDIO_XSMI_BUSY);
1978c2ecf20Sopenharmony_ci}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_cistatic const struct orion_mdio_ops orion_mdio_xsmi_ops = {
2008c2ecf20Sopenharmony_ci	.is_done = orion_mdio_xsmi_is_done,
2018c2ecf20Sopenharmony_ci	.poll_interval_min = MVMDIO_XSMI_POLL_INTERVAL_MIN,
2028c2ecf20Sopenharmony_ci	.poll_interval_max = MVMDIO_XSMI_POLL_INTERVAL_MAX,
2038c2ecf20Sopenharmony_ci};
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic int orion_mdio_xsmi_read(struct mii_bus *bus, int mii_id,
2068c2ecf20Sopenharmony_ci				int regnum)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	struct orion_mdio_dev *dev = bus->priv;
2098c2ecf20Sopenharmony_ci	u16 dev_addr = (regnum >> 16) & GENMASK(4, 0);
2108c2ecf20Sopenharmony_ci	int ret;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	if (!(regnum & MII_ADDR_C45))
2138c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	ret = orion_mdio_wait_ready(&orion_mdio_xsmi_ops, bus);
2168c2ecf20Sopenharmony_ci	if (ret < 0)
2178c2ecf20Sopenharmony_ci		return ret;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	writel(regnum & GENMASK(15, 0), dev->regs + MVMDIO_XSMI_ADDR_REG);
2208c2ecf20Sopenharmony_ci	writel((mii_id << MVMDIO_XSMI_PHYADDR_SHIFT) |
2218c2ecf20Sopenharmony_ci	       (dev_addr << MVMDIO_XSMI_DEVADDR_SHIFT) |
2228c2ecf20Sopenharmony_ci	       MVMDIO_XSMI_READ_OPERATION,
2238c2ecf20Sopenharmony_ci	       dev->regs + MVMDIO_XSMI_MGNT_REG);
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	ret = orion_mdio_wait_ready(&orion_mdio_xsmi_ops, bus);
2268c2ecf20Sopenharmony_ci	if (ret < 0)
2278c2ecf20Sopenharmony_ci		return ret;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	if (!(readl(dev->regs + MVMDIO_XSMI_MGNT_REG) &
2308c2ecf20Sopenharmony_ci	      MVMDIO_XSMI_READ_VALID)) {
2318c2ecf20Sopenharmony_ci		dev_err(bus->parent, "XSMI bus read not valid\n");
2328c2ecf20Sopenharmony_ci		return -ENODEV;
2338c2ecf20Sopenharmony_ci	}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	return readl(dev->regs + MVMDIO_XSMI_MGNT_REG) & GENMASK(15, 0);
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_cistatic int orion_mdio_xsmi_write(struct mii_bus *bus, int mii_id,
2398c2ecf20Sopenharmony_ci				int regnum, u16 value)
2408c2ecf20Sopenharmony_ci{
2418c2ecf20Sopenharmony_ci	struct orion_mdio_dev *dev = bus->priv;
2428c2ecf20Sopenharmony_ci	u16 dev_addr = (regnum >> 16) & GENMASK(4, 0);
2438c2ecf20Sopenharmony_ci	int ret;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	if (!(regnum & MII_ADDR_C45))
2468c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	ret = orion_mdio_wait_ready(&orion_mdio_xsmi_ops, bus);
2498c2ecf20Sopenharmony_ci	if (ret < 0)
2508c2ecf20Sopenharmony_ci		return ret;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	writel(regnum & GENMASK(15, 0), dev->regs + MVMDIO_XSMI_ADDR_REG);
2538c2ecf20Sopenharmony_ci	writel((mii_id << MVMDIO_XSMI_PHYADDR_SHIFT) |
2548c2ecf20Sopenharmony_ci	       (dev_addr << MVMDIO_XSMI_DEVADDR_SHIFT) |
2558c2ecf20Sopenharmony_ci	       MVMDIO_XSMI_WRITE_OPERATION | value,
2568c2ecf20Sopenharmony_ci	       dev->regs + MVMDIO_XSMI_MGNT_REG);
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	return 0;
2598c2ecf20Sopenharmony_ci}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cistatic irqreturn_t orion_mdio_err_irq(int irq, void *dev_id)
2628c2ecf20Sopenharmony_ci{
2638c2ecf20Sopenharmony_ci	struct orion_mdio_dev *dev = dev_id;
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	if (readl(dev->regs + MVMDIO_ERR_INT_CAUSE) &
2668c2ecf20Sopenharmony_ci			MVMDIO_ERR_INT_SMI_DONE) {
2678c2ecf20Sopenharmony_ci		writel(~MVMDIO_ERR_INT_SMI_DONE,
2688c2ecf20Sopenharmony_ci				dev->regs + MVMDIO_ERR_INT_CAUSE);
2698c2ecf20Sopenharmony_ci		wake_up(&dev->smi_busy_wait);
2708c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
2718c2ecf20Sopenharmony_ci	}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return IRQ_NONE;
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_cistatic int orion_mdio_probe(struct platform_device *pdev)
2778c2ecf20Sopenharmony_ci{
2788c2ecf20Sopenharmony_ci	enum orion_mdio_bus_type type;
2798c2ecf20Sopenharmony_ci	struct resource *r;
2808c2ecf20Sopenharmony_ci	struct mii_bus *bus;
2818c2ecf20Sopenharmony_ci	struct orion_mdio_dev *dev;
2828c2ecf20Sopenharmony_ci	int i, ret;
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	type = (enum orion_mdio_bus_type)of_device_get_match_data(&pdev->dev);
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
2878c2ecf20Sopenharmony_ci	if (!r) {
2888c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "No SMI register address given\n");
2898c2ecf20Sopenharmony_ci		return -ENODEV;
2908c2ecf20Sopenharmony_ci	}
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	bus = devm_mdiobus_alloc_size(&pdev->dev,
2938c2ecf20Sopenharmony_ci				      sizeof(struct orion_mdio_dev));
2948c2ecf20Sopenharmony_ci	if (!bus)
2958c2ecf20Sopenharmony_ci		return -ENOMEM;
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	switch (type) {
2988c2ecf20Sopenharmony_ci	case BUS_TYPE_SMI:
2998c2ecf20Sopenharmony_ci		bus->read = orion_mdio_smi_read;
3008c2ecf20Sopenharmony_ci		bus->write = orion_mdio_smi_write;
3018c2ecf20Sopenharmony_ci		break;
3028c2ecf20Sopenharmony_ci	case BUS_TYPE_XSMI:
3038c2ecf20Sopenharmony_ci		bus->read = orion_mdio_xsmi_read;
3048c2ecf20Sopenharmony_ci		bus->write = orion_mdio_xsmi_write;
3058c2ecf20Sopenharmony_ci		break;
3068c2ecf20Sopenharmony_ci	}
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	bus->name = "orion_mdio_bus";
3098c2ecf20Sopenharmony_ci	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii",
3108c2ecf20Sopenharmony_ci		 dev_name(&pdev->dev));
3118c2ecf20Sopenharmony_ci	bus->parent = &pdev->dev;
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	dev = bus->priv;
3148c2ecf20Sopenharmony_ci	dev->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
3158c2ecf20Sopenharmony_ci	if (!dev->regs) {
3168c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Unable to remap SMI register\n");
3178c2ecf20Sopenharmony_ci		return -ENODEV;
3188c2ecf20Sopenharmony_ci	}
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	init_waitqueue_head(&dev->smi_busy_wait);
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ci	if (pdev->dev.of_node) {
3238c2ecf20Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(dev->clk); i++) {
3248c2ecf20Sopenharmony_ci			dev->clk[i] = of_clk_get(pdev->dev.of_node, i);
3258c2ecf20Sopenharmony_ci			if (PTR_ERR(dev->clk[i]) == -EPROBE_DEFER) {
3268c2ecf20Sopenharmony_ci				ret = -EPROBE_DEFER;
3278c2ecf20Sopenharmony_ci				goto out_clk;
3288c2ecf20Sopenharmony_ci			}
3298c2ecf20Sopenharmony_ci			if (IS_ERR(dev->clk[i]))
3308c2ecf20Sopenharmony_ci				break;
3318c2ecf20Sopenharmony_ci			clk_prepare_enable(dev->clk[i]);
3328c2ecf20Sopenharmony_ci		}
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci		if (!IS_ERR(of_clk_get(pdev->dev.of_node,
3358c2ecf20Sopenharmony_ci				       ARRAY_SIZE(dev->clk))))
3368c2ecf20Sopenharmony_ci			dev_warn(&pdev->dev,
3378c2ecf20Sopenharmony_ci				 "unsupported number of clocks, limiting to the first "
3388c2ecf20Sopenharmony_ci				 __stringify(ARRAY_SIZE(dev->clk)) "\n");
3398c2ecf20Sopenharmony_ci	} else {
3408c2ecf20Sopenharmony_ci		dev->clk[0] = clk_get(&pdev->dev, NULL);
3418c2ecf20Sopenharmony_ci		if (PTR_ERR(dev->clk[0]) == -EPROBE_DEFER) {
3428c2ecf20Sopenharmony_ci			ret = -EPROBE_DEFER;
3438c2ecf20Sopenharmony_ci			goto out_clk;
3448c2ecf20Sopenharmony_ci		}
3458c2ecf20Sopenharmony_ci		if (!IS_ERR(dev->clk[0]))
3468c2ecf20Sopenharmony_ci			clk_prepare_enable(dev->clk[0]);
3478c2ecf20Sopenharmony_ci	}
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	dev->err_interrupt = platform_get_irq_optional(pdev, 0);
3518c2ecf20Sopenharmony_ci	if (dev->err_interrupt > 0 &&
3528c2ecf20Sopenharmony_ci	    resource_size(r) < MVMDIO_ERR_INT_MASK + 4) {
3538c2ecf20Sopenharmony_ci		dev_err(&pdev->dev,
3548c2ecf20Sopenharmony_ci			"disabling interrupt, resource size is too small\n");
3558c2ecf20Sopenharmony_ci		dev->err_interrupt = 0;
3568c2ecf20Sopenharmony_ci	}
3578c2ecf20Sopenharmony_ci	if (dev->err_interrupt > 0) {
3588c2ecf20Sopenharmony_ci		ret = devm_request_irq(&pdev->dev, dev->err_interrupt,
3598c2ecf20Sopenharmony_ci					orion_mdio_err_irq,
3608c2ecf20Sopenharmony_ci					IRQF_SHARED, pdev->name, dev);
3618c2ecf20Sopenharmony_ci		if (ret)
3628c2ecf20Sopenharmony_ci			goto out_mdio;
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci		writel(MVMDIO_ERR_INT_SMI_DONE,
3658c2ecf20Sopenharmony_ci			dev->regs + MVMDIO_ERR_INT_MASK);
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_ci	} else if (dev->err_interrupt == -EPROBE_DEFER) {
3688c2ecf20Sopenharmony_ci		ret = -EPROBE_DEFER;
3698c2ecf20Sopenharmony_ci		goto out_mdio;
3708c2ecf20Sopenharmony_ci	}
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci	ret = of_mdiobus_register(bus, pdev->dev.of_node);
3738c2ecf20Sopenharmony_ci	if (ret < 0) {
3748c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret);
3758c2ecf20Sopenharmony_ci		goto out_mdio;
3768c2ecf20Sopenharmony_ci	}
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, bus);
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	return 0;
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ciout_mdio:
3838c2ecf20Sopenharmony_ci	if (dev->err_interrupt > 0)
3848c2ecf20Sopenharmony_ci		writel(0, dev->regs + MVMDIO_ERR_INT_MASK);
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ciout_clk:
3878c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(dev->clk); i++) {
3888c2ecf20Sopenharmony_ci		if (IS_ERR(dev->clk[i]))
3898c2ecf20Sopenharmony_ci			break;
3908c2ecf20Sopenharmony_ci		clk_disable_unprepare(dev->clk[i]);
3918c2ecf20Sopenharmony_ci		clk_put(dev->clk[i]);
3928c2ecf20Sopenharmony_ci	}
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci	return ret;
3958c2ecf20Sopenharmony_ci}
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_cistatic int orion_mdio_remove(struct platform_device *pdev)
3988c2ecf20Sopenharmony_ci{
3998c2ecf20Sopenharmony_ci	struct mii_bus *bus = platform_get_drvdata(pdev);
4008c2ecf20Sopenharmony_ci	struct orion_mdio_dev *dev = bus->priv;
4018c2ecf20Sopenharmony_ci	int i;
4028c2ecf20Sopenharmony_ci
4038c2ecf20Sopenharmony_ci	if (dev->err_interrupt > 0)
4048c2ecf20Sopenharmony_ci		writel(0, dev->regs + MVMDIO_ERR_INT_MASK);
4058c2ecf20Sopenharmony_ci	mdiobus_unregister(bus);
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(dev->clk); i++) {
4088c2ecf20Sopenharmony_ci		if (IS_ERR(dev->clk[i]))
4098c2ecf20Sopenharmony_ci			break;
4108c2ecf20Sopenharmony_ci		clk_disable_unprepare(dev->clk[i]);
4118c2ecf20Sopenharmony_ci		clk_put(dev->clk[i]);
4128c2ecf20Sopenharmony_ci	}
4138c2ecf20Sopenharmony_ci
4148c2ecf20Sopenharmony_ci	return 0;
4158c2ecf20Sopenharmony_ci}
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_cistatic const struct of_device_id orion_mdio_match[] = {
4188c2ecf20Sopenharmony_ci	{ .compatible = "marvell,orion-mdio", .data = (void *)BUS_TYPE_SMI },
4198c2ecf20Sopenharmony_ci	{ .compatible = "marvell,xmdio", .data = (void *)BUS_TYPE_XSMI },
4208c2ecf20Sopenharmony_ci	{ }
4218c2ecf20Sopenharmony_ci};
4228c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, orion_mdio_match);
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_cistatic struct platform_driver orion_mdio_driver = {
4258c2ecf20Sopenharmony_ci	.probe = orion_mdio_probe,
4268c2ecf20Sopenharmony_ci	.remove = orion_mdio_remove,
4278c2ecf20Sopenharmony_ci	.driver = {
4288c2ecf20Sopenharmony_ci		.name = "orion-mdio",
4298c2ecf20Sopenharmony_ci		.of_match_table = orion_mdio_match,
4308c2ecf20Sopenharmony_ci	},
4318c2ecf20Sopenharmony_ci};
4328c2ecf20Sopenharmony_ci
4338c2ecf20Sopenharmony_cimodule_platform_driver(orion_mdio_driver);
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Marvell MDIO interface driver");
4368c2ecf20Sopenharmony_ciMODULE_AUTHOR("Thomas Petazzoni <thomas.petazzoni@free-electrons.com>");
4378c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
4388c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:orion-mdio");
439