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