|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* Xilinx GMII2RGMII Converter driver | 
|  | * | 
|  | * Copyright (C) 2016 Xilinx, Inc. | 
|  | * Copyright (C) 2016 Andrew Lunn <andrew@lunn.ch> | 
|  | * | 
|  | * Author: Andrew Lunn <andrew@lunn.ch> | 
|  | * Author: Kedareswara rao Appana <appanad@xilinx.com> | 
|  | * | 
|  | * Description: | 
|  | * This driver is developed for Xilinx GMII2RGMII Converter | 
|  | */ | 
|  | #include <linux/module.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/mii.h> | 
|  | #include <linux/mdio.h> | 
|  | #include <linux/phy.h> | 
|  | #include <linux/clk.h> | 
|  | #include <linux/of_mdio.h> | 
|  |  | 
|  | #define XILINX_GMII2RGMII_REG		0x10 | 
|  | #define XILINX_GMII2RGMII_SPEED_MASK	(BMCR_SPEED1000 | BMCR_SPEED100) | 
|  |  | 
|  | struct gmii2rgmii { | 
|  | struct phy_device *phy_dev; | 
|  | const struct phy_driver *phy_drv; | 
|  | struct phy_driver conv_phy_drv; | 
|  | struct mdio_device *mdio; | 
|  | }; | 
|  |  | 
|  | static void xgmiitorgmii_configure(struct gmii2rgmii *priv, int speed) | 
|  | { | 
|  | struct mii_bus *bus = priv->mdio->bus; | 
|  | int addr = priv->mdio->addr; | 
|  | u16 val; | 
|  |  | 
|  | val = mdiobus_read(bus, addr, XILINX_GMII2RGMII_REG); | 
|  | val &= ~XILINX_GMII2RGMII_SPEED_MASK; | 
|  |  | 
|  | if (speed == SPEED_1000) | 
|  | val |= BMCR_SPEED1000; | 
|  | else if (speed == SPEED_100) | 
|  | val |= BMCR_SPEED100; | 
|  | else | 
|  | val |= BMCR_SPEED10; | 
|  |  | 
|  | mdiobus_write(bus, addr, XILINX_GMII2RGMII_REG, val); | 
|  | } | 
|  |  | 
|  | static int xgmiitorgmii_read_status(struct phy_device *phydev) | 
|  | { | 
|  | struct gmii2rgmii *priv = mdiodev_get_drvdata(&phydev->mdio); | 
|  | int err; | 
|  |  | 
|  | if (priv->phy_drv->read_status) | 
|  | err = priv->phy_drv->read_status(phydev); | 
|  | else | 
|  | err = genphy_read_status(phydev); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | xgmiitorgmii_configure(priv, phydev->speed); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int xgmiitorgmii_set_loopback(struct phy_device *phydev, bool enable) | 
|  | { | 
|  | struct gmii2rgmii *priv = mdiodev_get_drvdata(&phydev->mdio); | 
|  | int err; | 
|  |  | 
|  | if (priv->phy_drv->set_loopback) | 
|  | err = priv->phy_drv->set_loopback(phydev, enable); | 
|  | else | 
|  | err = genphy_loopback(phydev, enable); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | xgmiitorgmii_configure(priv, phydev->speed); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int xgmiitorgmii_probe(struct mdio_device *mdiodev) | 
|  | { | 
|  | struct device *dev = &mdiodev->dev; | 
|  | struct device_node *np = dev->of_node, *phy_node; | 
|  | struct gmii2rgmii *priv; | 
|  | struct clk *clkin; | 
|  |  | 
|  | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | 
|  | if (!priv) | 
|  | return -ENOMEM; | 
|  |  | 
|  | clkin = devm_clk_get_optional_enabled(dev, NULL); | 
|  | if (IS_ERR(clkin)) | 
|  | return dev_err_probe(dev, PTR_ERR(clkin), | 
|  | "Failed to get and enable clock from Device Tree\n"); | 
|  |  | 
|  | phy_node = of_parse_phandle(np, "phy-handle", 0); | 
|  | if (!phy_node) { | 
|  | dev_err(dev, "Couldn't parse phy-handle\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | priv->phy_dev = of_phy_find_device(phy_node); | 
|  | of_node_put(phy_node); | 
|  | if (!priv->phy_dev) { | 
|  | dev_info(dev, "Couldn't find phydev\n"); | 
|  | return -EPROBE_DEFER; | 
|  | } | 
|  |  | 
|  | if (!priv->phy_dev->drv) { | 
|  | dev_info(dev, "Attached phy not ready\n"); | 
|  | put_device(&priv->phy_dev->mdio.dev); | 
|  | return -EPROBE_DEFER; | 
|  | } | 
|  |  | 
|  | priv->mdio = mdiodev; | 
|  | priv->phy_drv = priv->phy_dev->drv; | 
|  | memcpy(&priv->conv_phy_drv, priv->phy_dev->drv, | 
|  | sizeof(struct phy_driver)); | 
|  | priv->conv_phy_drv.read_status = xgmiitorgmii_read_status; | 
|  | priv->conv_phy_drv.set_loopback = xgmiitorgmii_set_loopback; | 
|  | mdiodev_set_drvdata(&priv->phy_dev->mdio, priv); | 
|  | priv->phy_dev->drv = &priv->conv_phy_drv; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct of_device_id xgmiitorgmii_of_match[] = { | 
|  | { .compatible = "xlnx,gmii-to-rgmii-1.0" }, | 
|  | {}, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, xgmiitorgmii_of_match); | 
|  |  | 
|  | static struct mdio_driver xgmiitorgmii_driver = { | 
|  | .probe	= xgmiitorgmii_probe, | 
|  | .mdiodrv.driver = { | 
|  | .name = "xgmiitorgmii", | 
|  | .of_match_table = xgmiitorgmii_of_match, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | mdio_module_driver(xgmiitorgmii_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("Xilinx GMII2RGMII converter driver"); | 
|  | MODULE_LICENSE("GPL"); |