18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/* 10G controller driver for Samsung SoCs
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2013 Samsung Electronics Co., Ltd.
58c2ecf20Sopenharmony_ci *		http://www.samsung.com
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Author: Siva Reddy Kallam <siva.kallam@samsung.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/io.h>
138c2ecf20Sopenharmony_ci#include <linux/mii.h>
148c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
158c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
168c2ecf20Sopenharmony_ci#include <linux/phy.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci#include <linux/sxgbe_platform.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include "sxgbe_common.h"
218c2ecf20Sopenharmony_ci#include "sxgbe_reg.h"
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define SXGBE_SMA_WRITE_CMD	0x01 /* write command */
248c2ecf20Sopenharmony_ci#define SXGBE_SMA_PREAD_CMD	0x02 /* post read  increament address */
258c2ecf20Sopenharmony_ci#define SXGBE_SMA_READ_CMD	0x03 /* read command */
268c2ecf20Sopenharmony_ci#define SXGBE_SMA_SKIP_ADDRFRM	0x00040000 /* skip the address frame */
278c2ecf20Sopenharmony_ci#define SXGBE_MII_BUSY		0x00400000 /* mii busy */
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic int sxgbe_mdio_busy_wait(void __iomem *ioaddr, unsigned int mii_data)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	unsigned long fin_time = jiffies + 3 * HZ; /* 3 seconds */
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	while (!time_after(jiffies, fin_time)) {
348c2ecf20Sopenharmony_ci		if (!(readl(ioaddr + mii_data) & SXGBE_MII_BUSY))
358c2ecf20Sopenharmony_ci			return 0;
368c2ecf20Sopenharmony_ci		cpu_relax();
378c2ecf20Sopenharmony_ci	}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	return -EBUSY;
408c2ecf20Sopenharmony_ci}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic void sxgbe_mdio_ctrl_data(struct sxgbe_priv_data *sp, u32 cmd,
438c2ecf20Sopenharmony_ci				 u16 phydata)
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	u32 reg = phydata;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	reg |= (cmd << 16) | SXGBE_SMA_SKIP_ADDRFRM |
488c2ecf20Sopenharmony_ci	       ((sp->clk_csr & 0x7) << 19) | SXGBE_MII_BUSY;
498c2ecf20Sopenharmony_ci	writel(reg, sp->ioaddr + sp->hw->mii.data);
508c2ecf20Sopenharmony_ci}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic void sxgbe_mdio_c45(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr,
538c2ecf20Sopenharmony_ci			   int phyreg, u16 phydata)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	u32 reg;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	/* set mdio address register */
588c2ecf20Sopenharmony_ci	reg = ((phyreg >> 16) & 0x1f) << 21;
598c2ecf20Sopenharmony_ci	reg |= (phyaddr << 16) | (phyreg & 0xffff);
608c2ecf20Sopenharmony_ci	writel(reg, sp->ioaddr + sp->hw->mii.addr);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	sxgbe_mdio_ctrl_data(sp, cmd, phydata);
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic void sxgbe_mdio_c22(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr,
668c2ecf20Sopenharmony_ci			   int phyreg, u16 phydata)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	u32 reg;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	writel(1 << phyaddr, sp->ioaddr + SXGBE_MDIO_CLAUSE22_PORT_REG);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	/* set mdio address register */
738c2ecf20Sopenharmony_ci	reg = (phyaddr << 16) | (phyreg & 0x1f);
748c2ecf20Sopenharmony_ci	writel(reg, sp->ioaddr + sp->hw->mii.addr);
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	sxgbe_mdio_ctrl_data(sp, cmd, phydata);
778c2ecf20Sopenharmony_ci}
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_cistatic int sxgbe_mdio_access(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr,
808c2ecf20Sopenharmony_ci			     int phyreg, u16 phydata)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	const struct mii_regs *mii = &sp->hw->mii;
838c2ecf20Sopenharmony_ci	int rc;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	rc = sxgbe_mdio_busy_wait(sp->ioaddr, mii->data);
868c2ecf20Sopenharmony_ci	if (rc < 0)
878c2ecf20Sopenharmony_ci		return rc;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	if (phyreg & MII_ADDR_C45) {
908c2ecf20Sopenharmony_ci		sxgbe_mdio_c45(sp, cmd, phyaddr, phyreg, phydata);
918c2ecf20Sopenharmony_ci	} else {
928c2ecf20Sopenharmony_ci		 /* Ports 0-3 only support C22. */
938c2ecf20Sopenharmony_ci		if (phyaddr >= 4)
948c2ecf20Sopenharmony_ci			return -ENODEV;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci		sxgbe_mdio_c22(sp, cmd, phyaddr, phyreg, phydata);
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	return sxgbe_mdio_busy_wait(sp->ioaddr, mii->data);
1008c2ecf20Sopenharmony_ci}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci/**
1038c2ecf20Sopenharmony_ci * sxgbe_mdio_read
1048c2ecf20Sopenharmony_ci * @bus: points to the mii_bus structure
1058c2ecf20Sopenharmony_ci * @phyaddr: address of phy port
1068c2ecf20Sopenharmony_ci * @phyreg: address of register with in phy register
1078c2ecf20Sopenharmony_ci * Description: this function used for C45 and C22 MDIO Read
1088c2ecf20Sopenharmony_ci */
1098c2ecf20Sopenharmony_cistatic int sxgbe_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
1108c2ecf20Sopenharmony_ci{
1118c2ecf20Sopenharmony_ci	struct net_device *ndev = bus->priv;
1128c2ecf20Sopenharmony_ci	struct sxgbe_priv_data *priv = netdev_priv(ndev);
1138c2ecf20Sopenharmony_ci	int rc;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	rc = sxgbe_mdio_access(priv, SXGBE_SMA_READ_CMD, phyaddr, phyreg, 0);
1168c2ecf20Sopenharmony_ci	if (rc < 0)
1178c2ecf20Sopenharmony_ci		return rc;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	return readl(priv->ioaddr + priv->hw->mii.data) & 0xffff;
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci/**
1238c2ecf20Sopenharmony_ci * sxgbe_mdio_write
1248c2ecf20Sopenharmony_ci * @bus: points to the mii_bus structure
1258c2ecf20Sopenharmony_ci * @phyaddr: address of phy port
1268c2ecf20Sopenharmony_ci * @phyreg: address of phy registers
1278c2ecf20Sopenharmony_ci * @phydata: data to be written into phy register
1288c2ecf20Sopenharmony_ci * Description: this function is used for C45 and C22 MDIO write
1298c2ecf20Sopenharmony_ci */
1308c2ecf20Sopenharmony_cistatic int sxgbe_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg,
1318c2ecf20Sopenharmony_ci			     u16 phydata)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	struct net_device *ndev = bus->priv;
1348c2ecf20Sopenharmony_ci	struct sxgbe_priv_data *priv = netdev_priv(ndev);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	return sxgbe_mdio_access(priv, SXGBE_SMA_WRITE_CMD, phyaddr, phyreg,
1378c2ecf20Sopenharmony_ci				 phydata);
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ciint sxgbe_mdio_register(struct net_device *ndev)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	struct mii_bus *mdio_bus;
1438c2ecf20Sopenharmony_ci	struct sxgbe_priv_data *priv = netdev_priv(ndev);
1448c2ecf20Sopenharmony_ci	struct sxgbe_mdio_bus_data *mdio_data = priv->plat->mdio_bus_data;
1458c2ecf20Sopenharmony_ci	int err, phy_addr;
1468c2ecf20Sopenharmony_ci	int *irqlist;
1478c2ecf20Sopenharmony_ci	bool phy_found = false;
1488c2ecf20Sopenharmony_ci	bool act;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	/* allocate the new mdio bus */
1518c2ecf20Sopenharmony_ci	mdio_bus = mdiobus_alloc();
1528c2ecf20Sopenharmony_ci	if (!mdio_bus) {
1538c2ecf20Sopenharmony_ci		netdev_err(ndev, "%s: mii bus allocation failed\n", __func__);
1548c2ecf20Sopenharmony_ci		return -ENOMEM;
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	if (mdio_data->irqs)
1588c2ecf20Sopenharmony_ci		irqlist = mdio_data->irqs;
1598c2ecf20Sopenharmony_ci	else
1608c2ecf20Sopenharmony_ci		irqlist = priv->mii_irq;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	/* assign mii bus fields */
1638c2ecf20Sopenharmony_ci	mdio_bus->name = "sxgbe";
1648c2ecf20Sopenharmony_ci	mdio_bus->read = &sxgbe_mdio_read;
1658c2ecf20Sopenharmony_ci	mdio_bus->write = &sxgbe_mdio_write;
1668c2ecf20Sopenharmony_ci	snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s-%x",
1678c2ecf20Sopenharmony_ci		 mdio_bus->name, priv->plat->bus_id);
1688c2ecf20Sopenharmony_ci	mdio_bus->priv = ndev;
1698c2ecf20Sopenharmony_ci	mdio_bus->phy_mask = mdio_data->phy_mask;
1708c2ecf20Sopenharmony_ci	mdio_bus->parent = priv->device;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	/* register with kernel subsystem */
1738c2ecf20Sopenharmony_ci	err = mdiobus_register(mdio_bus);
1748c2ecf20Sopenharmony_ci	if (err != 0) {
1758c2ecf20Sopenharmony_ci		netdev_err(ndev, "mdiobus register failed\n");
1768c2ecf20Sopenharmony_ci		goto mdiobus_err;
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) {
1808c2ecf20Sopenharmony_ci		struct phy_device *phy = mdiobus_get_phy(mdio_bus, phy_addr);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci		if (phy) {
1838c2ecf20Sopenharmony_ci			char irq_num[4];
1848c2ecf20Sopenharmony_ci			char *irq_str;
1858c2ecf20Sopenharmony_ci			/* If an IRQ was provided to be assigned after
1868c2ecf20Sopenharmony_ci			 * the bus probe, do it here.
1878c2ecf20Sopenharmony_ci			 */
1888c2ecf20Sopenharmony_ci			if ((mdio_data->irqs == NULL) &&
1898c2ecf20Sopenharmony_ci			    (mdio_data->probed_phy_irq > 0)) {
1908c2ecf20Sopenharmony_ci				irqlist[phy_addr] = mdio_data->probed_phy_irq;
1918c2ecf20Sopenharmony_ci				phy->irq = mdio_data->probed_phy_irq;
1928c2ecf20Sopenharmony_ci			}
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci			/* If we're  going to bind the MAC to this PHY bus,
1958c2ecf20Sopenharmony_ci			 * and no PHY number was provided to the MAC,
1968c2ecf20Sopenharmony_ci			 * use the one probed here.
1978c2ecf20Sopenharmony_ci			 */
1988c2ecf20Sopenharmony_ci			if (priv->plat->phy_addr == -1)
1998c2ecf20Sopenharmony_ci				priv->plat->phy_addr = phy_addr;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci			act = (priv->plat->phy_addr == phy_addr);
2028c2ecf20Sopenharmony_ci			switch (phy->irq) {
2038c2ecf20Sopenharmony_ci			case PHY_POLL:
2048c2ecf20Sopenharmony_ci				irq_str = "POLL";
2058c2ecf20Sopenharmony_ci				break;
2068c2ecf20Sopenharmony_ci			case PHY_IGNORE_INTERRUPT:
2078c2ecf20Sopenharmony_ci				irq_str = "IGNORE";
2088c2ecf20Sopenharmony_ci				break;
2098c2ecf20Sopenharmony_ci			default:
2108c2ecf20Sopenharmony_ci				sprintf(irq_num, "%d", phy->irq);
2118c2ecf20Sopenharmony_ci				irq_str = irq_num;
2128c2ecf20Sopenharmony_ci				break;
2138c2ecf20Sopenharmony_ci			}
2148c2ecf20Sopenharmony_ci			netdev_info(ndev, "PHY ID %08x at %d IRQ %s (%s)%s\n",
2158c2ecf20Sopenharmony_ci				    phy->phy_id, phy_addr, irq_str,
2168c2ecf20Sopenharmony_ci				    phydev_name(phy), act ? " active" : "");
2178c2ecf20Sopenharmony_ci			phy_found = true;
2188c2ecf20Sopenharmony_ci		}
2198c2ecf20Sopenharmony_ci	}
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	if (!phy_found) {
2228c2ecf20Sopenharmony_ci		netdev_err(ndev, "PHY not found\n");
2238c2ecf20Sopenharmony_ci		goto phyfound_err;
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	priv->mii = mdio_bus;
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	return 0;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ciphyfound_err:
2318c2ecf20Sopenharmony_ci	err = -ENODEV;
2328c2ecf20Sopenharmony_ci	mdiobus_unregister(mdio_bus);
2338c2ecf20Sopenharmony_cimdiobus_err:
2348c2ecf20Sopenharmony_ci	mdiobus_free(mdio_bus);
2358c2ecf20Sopenharmony_ci	return err;
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ciint sxgbe_mdio_unregister(struct net_device *ndev)
2398c2ecf20Sopenharmony_ci{
2408c2ecf20Sopenharmony_ci	struct sxgbe_priv_data *priv = netdev_priv(ndev);
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	if (!priv->mii)
2438c2ecf20Sopenharmony_ci		return 0;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	mdiobus_unregister(priv->mii);
2468c2ecf20Sopenharmony_ci	priv->mii->priv = NULL;
2478c2ecf20Sopenharmony_ci	mdiobus_free(priv->mii);
2488c2ecf20Sopenharmony_ci	priv->mii = NULL;
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	return 0;
2518c2ecf20Sopenharmony_ci}
252