18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2004-2013 Synopsys, Inc. (www.synopsys.com)
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * MDIO implementation for ARC EMAC
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/delay.h>
98c2ecf20Sopenharmony_ci#include <linux/of_mdio.h>
108c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
118c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include "emac.h"
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci/* Number of seconds we wait for "MDIO complete" flag to appear */
168c2ecf20Sopenharmony_ci#define ARC_MDIO_COMPLETE_POLL_COUNT	1
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci/**
198c2ecf20Sopenharmony_ci * arc_mdio_complete_wait - Waits until MDIO transaction is completed.
208c2ecf20Sopenharmony_ci * @priv:	Pointer to ARC EMAC private data structure.
218c2ecf20Sopenharmony_ci *
228c2ecf20Sopenharmony_ci * returns:	0 on success, -ETIMEDOUT on a timeout.
238c2ecf20Sopenharmony_ci */
248c2ecf20Sopenharmony_cistatic int arc_mdio_complete_wait(struct arc_emac_priv *priv)
258c2ecf20Sopenharmony_ci{
268c2ecf20Sopenharmony_ci	unsigned int i;
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci	for (i = 0; i < ARC_MDIO_COMPLETE_POLL_COUNT * 40; i++) {
298c2ecf20Sopenharmony_ci		unsigned int status = arc_reg_get(priv, R_STATUS);
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci		status &= MDIO_MASK;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci		if (status) {
348c2ecf20Sopenharmony_ci			/* Reset "MDIO complete" flag */
358c2ecf20Sopenharmony_ci			arc_reg_set(priv, R_STATUS, status);
368c2ecf20Sopenharmony_ci			return 0;
378c2ecf20Sopenharmony_ci		}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci		msleep(25);
408c2ecf20Sopenharmony_ci	}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	return -ETIMEDOUT;
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/**
468c2ecf20Sopenharmony_ci * arc_mdio_read - MDIO interface read function.
478c2ecf20Sopenharmony_ci * @bus:	Pointer to MII bus structure.
488c2ecf20Sopenharmony_ci * @phy_addr:	Address of the PHY device.
498c2ecf20Sopenharmony_ci * @reg_num:	PHY register to read.
508c2ecf20Sopenharmony_ci *
518c2ecf20Sopenharmony_ci * returns:	The register contents on success, -ETIMEDOUT on a timeout.
528c2ecf20Sopenharmony_ci *
538c2ecf20Sopenharmony_ci * Reads the contents of the requested register from the requested PHY
548c2ecf20Sopenharmony_ci * address.
558c2ecf20Sopenharmony_ci */
568c2ecf20Sopenharmony_cistatic int arc_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	struct arc_emac_priv *priv = bus->priv;
598c2ecf20Sopenharmony_ci	unsigned int value;
608c2ecf20Sopenharmony_ci	int error;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	arc_reg_set(priv, R_MDIO,
638c2ecf20Sopenharmony_ci		    0x60020000 | (phy_addr << 23) | (reg_num << 18));
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	error = arc_mdio_complete_wait(priv);
668c2ecf20Sopenharmony_ci	if (error < 0)
678c2ecf20Sopenharmony_ci		return error;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	value = arc_reg_get(priv, R_MDIO) & 0xffff;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	dev_dbg(priv->dev, "arc_mdio_read(phy_addr=%i, reg_num=%x) = %x\n",
728c2ecf20Sopenharmony_ci		phy_addr, reg_num, value);
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	return value;
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci/**
788c2ecf20Sopenharmony_ci * arc_mdio_write - MDIO interface write function.
798c2ecf20Sopenharmony_ci * @bus:	Pointer to MII bus structure.
808c2ecf20Sopenharmony_ci * @phy_addr:	Address of the PHY device.
818c2ecf20Sopenharmony_ci * @reg_num:	PHY register to write to.
828c2ecf20Sopenharmony_ci * @value:	Value to be written into the register.
838c2ecf20Sopenharmony_ci *
848c2ecf20Sopenharmony_ci * returns:	0 on success, -ETIMEDOUT on a timeout.
858c2ecf20Sopenharmony_ci *
868c2ecf20Sopenharmony_ci * Writes the value to the requested register.
878c2ecf20Sopenharmony_ci */
888c2ecf20Sopenharmony_cistatic int arc_mdio_write(struct mii_bus *bus, int phy_addr,
898c2ecf20Sopenharmony_ci			  int reg_num, u16 value)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	struct arc_emac_priv *priv = bus->priv;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	dev_dbg(priv->dev,
948c2ecf20Sopenharmony_ci		"arc_mdio_write(phy_addr=%i, reg_num=%x, value=%x)\n",
958c2ecf20Sopenharmony_ci		phy_addr, reg_num, value);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	arc_reg_set(priv, R_MDIO,
988c2ecf20Sopenharmony_ci		    0x50020000 | (phy_addr << 23) | (reg_num << 18) | value);
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	return arc_mdio_complete_wait(priv);
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci/**
1048c2ecf20Sopenharmony_ci * arc_mdio_reset
1058c2ecf20Sopenharmony_ci * @bus: points to the mii_bus structure
1068c2ecf20Sopenharmony_ci * Description: reset the MII bus
1078c2ecf20Sopenharmony_ci */
1088c2ecf20Sopenharmony_cistatic int arc_mdio_reset(struct mii_bus *bus)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	struct arc_emac_priv *priv = bus->priv;
1118c2ecf20Sopenharmony_ci	struct arc_emac_mdio_bus_data *data = &priv->bus_data;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	if (data->reset_gpio) {
1148c2ecf20Sopenharmony_ci		gpiod_set_value_cansleep(data->reset_gpio, 1);
1158c2ecf20Sopenharmony_ci		msleep(data->msec);
1168c2ecf20Sopenharmony_ci		gpiod_set_value_cansleep(data->reset_gpio, 0);
1178c2ecf20Sopenharmony_ci	}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	return 0;
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci/**
1238c2ecf20Sopenharmony_ci * arc_mdio_probe - MDIO probe function.
1248c2ecf20Sopenharmony_ci * @priv:	Pointer to ARC EMAC private data structure.
1258c2ecf20Sopenharmony_ci *
1268c2ecf20Sopenharmony_ci * returns:	0 on success, -ENOMEM when mdiobus_alloc
1278c2ecf20Sopenharmony_ci * (to allocate memory for MII bus structure) fails.
1288c2ecf20Sopenharmony_ci *
1298c2ecf20Sopenharmony_ci * Sets up and registers the MDIO interface.
1308c2ecf20Sopenharmony_ci */
1318c2ecf20Sopenharmony_ciint arc_mdio_probe(struct arc_emac_priv *priv)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	struct arc_emac_mdio_bus_data *data = &priv->bus_data;
1348c2ecf20Sopenharmony_ci	struct device_node *np = priv->dev->of_node;
1358c2ecf20Sopenharmony_ci	struct mii_bus *bus;
1368c2ecf20Sopenharmony_ci	int error;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	bus = mdiobus_alloc();
1398c2ecf20Sopenharmony_ci	if (!bus)
1408c2ecf20Sopenharmony_ci		return -ENOMEM;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	priv->bus = bus;
1438c2ecf20Sopenharmony_ci	bus->priv = priv;
1448c2ecf20Sopenharmony_ci	bus->parent = priv->dev;
1458c2ecf20Sopenharmony_ci	bus->name = "Synopsys MII Bus";
1468c2ecf20Sopenharmony_ci	bus->read = &arc_mdio_read;
1478c2ecf20Sopenharmony_ci	bus->write = &arc_mdio_write;
1488c2ecf20Sopenharmony_ci	bus->reset = &arc_mdio_reset;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	/* optional reset-related properties */
1518c2ecf20Sopenharmony_ci	data->reset_gpio = devm_gpiod_get_optional(priv->dev, "phy-reset",
1528c2ecf20Sopenharmony_ci						   GPIOD_OUT_LOW);
1538c2ecf20Sopenharmony_ci	if (IS_ERR(data->reset_gpio)) {
1548c2ecf20Sopenharmony_ci		error = PTR_ERR(data->reset_gpio);
1558c2ecf20Sopenharmony_ci		dev_err(priv->dev, "Failed to request gpio: %d\n", error);
1568c2ecf20Sopenharmony_ci		mdiobus_free(bus);
1578c2ecf20Sopenharmony_ci		return error;
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	of_property_read_u32(np, "phy-reset-duration", &data->msec);
1618c2ecf20Sopenharmony_ci	/* A sane reset duration should not be longer than 1s */
1628c2ecf20Sopenharmony_ci	if (data->msec > 1000)
1638c2ecf20Sopenharmony_ci		data->msec = 1;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	snprintf(bus->id, MII_BUS_ID_SIZE, "%s", bus->name);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	error = of_mdiobus_register(bus, priv->dev->of_node);
1688c2ecf20Sopenharmony_ci	if (error) {
1698c2ecf20Sopenharmony_ci		dev_err(priv->dev, "cannot register MDIO bus %s\n", bus->name);
1708c2ecf20Sopenharmony_ci		mdiobus_free(bus);
1718c2ecf20Sopenharmony_ci		return error;
1728c2ecf20Sopenharmony_ci	}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	return 0;
1758c2ecf20Sopenharmony_ci}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci/**
1788c2ecf20Sopenharmony_ci * arc_mdio_remove - MDIO remove function.
1798c2ecf20Sopenharmony_ci * @priv:	Pointer to ARC EMAC private data structure.
1808c2ecf20Sopenharmony_ci *
1818c2ecf20Sopenharmony_ci * Unregisters the MDIO and frees any associate memory for MII bus.
1828c2ecf20Sopenharmony_ci */
1838c2ecf20Sopenharmony_ciint arc_mdio_remove(struct arc_emac_priv *priv)
1848c2ecf20Sopenharmony_ci{
1858c2ecf20Sopenharmony_ci	mdiobus_unregister(priv->bus);
1868c2ecf20Sopenharmony_ci	mdiobus_free(priv->bus);
1878c2ecf20Sopenharmony_ci	priv->bus = NULL;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	return 0;
1908c2ecf20Sopenharmony_ci}
191