162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* MOXA ART Ethernet (RTL8201CP) MDIO interface driver
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2013 Jonas Jensen <jonas.jensen@gmail.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/delay.h>
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/mutex.h>
1162306a36Sopenharmony_ci#include <linux/of_address.h>
1262306a36Sopenharmony_ci#include <linux/of_mdio.h>
1362306a36Sopenharmony_ci#include <linux/phy.h>
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define REG_PHY_CTRL            0
1762306a36Sopenharmony_ci#define REG_PHY_WRITE_DATA      4
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/* REG_PHY_CTRL */
2062306a36Sopenharmony_ci#define MIIWR                   BIT(27) /* init write sequence (auto cleared)*/
2162306a36Sopenharmony_ci#define MIIRD                   BIT(26)
2262306a36Sopenharmony_ci#define REGAD_MASK              0x3e00000
2362306a36Sopenharmony_ci#define PHYAD_MASK              0x1f0000
2462306a36Sopenharmony_ci#define MIIRDATA_MASK           0xffff
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* REG_PHY_WRITE_DATA */
2762306a36Sopenharmony_ci#define MIIWDATA_MASK           0xffff
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistruct moxart_mdio_data {
3062306a36Sopenharmony_ci	void __iomem		*base;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic int moxart_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	struct moxart_mdio_data *data = bus->priv;
3662306a36Sopenharmony_ci	u32 ctrl = 0;
3762306a36Sopenharmony_ci	unsigned int count = 5;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	dev_dbg(&bus->dev, "%s\n", __func__);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	ctrl |= MIIRD | ((mii_id << 16) & PHYAD_MASK) |
4262306a36Sopenharmony_ci		((regnum << 21) & REGAD_MASK);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	writel(ctrl, data->base + REG_PHY_CTRL);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	do {
4762306a36Sopenharmony_ci		ctrl = readl(data->base + REG_PHY_CTRL);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci		if (!(ctrl & MIIRD))
5062306a36Sopenharmony_ci			return ctrl & MIIRDATA_MASK;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci		mdelay(10);
5362306a36Sopenharmony_ci		count--;
5462306a36Sopenharmony_ci	} while (count > 0);
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	dev_dbg(&bus->dev, "%s timed out\n", __func__);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	return -ETIMEDOUT;
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic int moxart_mdio_write(struct mii_bus *bus, int mii_id,
6262306a36Sopenharmony_ci			     int regnum, u16 value)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct moxart_mdio_data *data = bus->priv;
6562306a36Sopenharmony_ci	u32 ctrl = 0;
6662306a36Sopenharmony_ci	unsigned int count = 5;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	dev_dbg(&bus->dev, "%s\n", __func__);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	ctrl |= MIIWR | ((mii_id << 16) & PHYAD_MASK) |
7162306a36Sopenharmony_ci		((regnum << 21) & REGAD_MASK);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	value &= MIIWDATA_MASK;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	writel(value, data->base + REG_PHY_WRITE_DATA);
7662306a36Sopenharmony_ci	writel(ctrl, data->base + REG_PHY_CTRL);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	do {
7962306a36Sopenharmony_ci		ctrl = readl(data->base + REG_PHY_CTRL);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci		if (!(ctrl & MIIWR))
8262306a36Sopenharmony_ci			return 0;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci		mdelay(10);
8562306a36Sopenharmony_ci		count--;
8662306a36Sopenharmony_ci	} while (count > 0);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	dev_dbg(&bus->dev, "%s timed out\n", __func__);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	return -ETIMEDOUT;
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic int moxart_mdio_reset(struct mii_bus *bus)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	int data, i;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	for (i = 0; i < PHY_MAX_ADDR; i++) {
9862306a36Sopenharmony_ci		data = moxart_mdio_read(bus, i, MII_BMCR);
9962306a36Sopenharmony_ci		if (data < 0)
10062306a36Sopenharmony_ci			continue;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		data |= BMCR_RESET;
10362306a36Sopenharmony_ci		if (moxart_mdio_write(bus, i, MII_BMCR, data) < 0)
10462306a36Sopenharmony_ci			continue;
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	return 0;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic int moxart_mdio_probe(struct platform_device *pdev)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
11362306a36Sopenharmony_ci	struct mii_bus *bus;
11462306a36Sopenharmony_ci	struct moxart_mdio_data *data;
11562306a36Sopenharmony_ci	int ret, i;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	bus = mdiobus_alloc_size(sizeof(*data));
11862306a36Sopenharmony_ci	if (!bus)
11962306a36Sopenharmony_ci		return -ENOMEM;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	bus->name = "MOXA ART Ethernet MII";
12262306a36Sopenharmony_ci	bus->read = &moxart_mdio_read;
12362306a36Sopenharmony_ci	bus->write = &moxart_mdio_write;
12462306a36Sopenharmony_ci	bus->reset = &moxart_mdio_reset;
12562306a36Sopenharmony_ci	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d-mii", pdev->name, pdev->id);
12662306a36Sopenharmony_ci	bus->parent = &pdev->dev;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* Setting PHY_MAC_INTERRUPT here even if it has no effect,
12962306a36Sopenharmony_ci	 * of_mdiobus_register() sets these PHY_POLL.
13062306a36Sopenharmony_ci	 * Ideally, the interrupt from MAC controller could be used to
13162306a36Sopenharmony_ci	 * detect link state changes, not polling, i.e. if there was
13262306a36Sopenharmony_ci	 * a way phy_driver could set PHY_HAS_INTERRUPT but have that
13362306a36Sopenharmony_ci	 * interrupt handled in ethernet drivercode.
13462306a36Sopenharmony_ci	 */
13562306a36Sopenharmony_ci	for (i = 0; i < PHY_MAX_ADDR; i++)
13662306a36Sopenharmony_ci		bus->irq[i] = PHY_MAC_INTERRUPT;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	data = bus->priv;
13962306a36Sopenharmony_ci	data->base = devm_platform_ioremap_resource(pdev, 0);
14062306a36Sopenharmony_ci	if (IS_ERR(data->base)) {
14162306a36Sopenharmony_ci		ret = PTR_ERR(data->base);
14262306a36Sopenharmony_ci		goto err_out_free_mdiobus;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	ret = of_mdiobus_register(bus, np);
14662306a36Sopenharmony_ci	if (ret < 0)
14762306a36Sopenharmony_ci		goto err_out_free_mdiobus;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	platform_set_drvdata(pdev, bus);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return 0;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cierr_out_free_mdiobus:
15462306a36Sopenharmony_ci	mdiobus_free(bus);
15562306a36Sopenharmony_ci	return ret;
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic int moxart_mdio_remove(struct platform_device *pdev)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	struct mii_bus *bus = platform_get_drvdata(pdev);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	mdiobus_unregister(bus);
16362306a36Sopenharmony_ci	mdiobus_free(bus);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	return 0;
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic const struct of_device_id moxart_mdio_dt_ids[] = {
16962306a36Sopenharmony_ci	{ .compatible = "moxa,moxart-mdio" },
17062306a36Sopenharmony_ci	{ }
17162306a36Sopenharmony_ci};
17262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, moxart_mdio_dt_ids);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic struct platform_driver moxart_mdio_driver = {
17562306a36Sopenharmony_ci	.probe = moxart_mdio_probe,
17662306a36Sopenharmony_ci	.remove = moxart_mdio_remove,
17762306a36Sopenharmony_ci	.driver = {
17862306a36Sopenharmony_ci		.name = "moxart-mdio",
17962306a36Sopenharmony_ci		.of_match_table = moxart_mdio_dt_ids,
18062306a36Sopenharmony_ci	},
18162306a36Sopenharmony_ci};
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cimodule_platform_driver(moxart_mdio_driver);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ciMODULE_DESCRIPTION("MOXA ART MDIO interface driver");
18662306a36Sopenharmony_ciMODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
18762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
188