188 lines
4.9 KiB
C
188 lines
4.9 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/* Realtek MDIO interface driver
|
|
*
|
|
* ASICs we intend to support with this driver:
|
|
*
|
|
* RTL8366 - The original version, apparently
|
|
* RTL8369 - Similar enough to have the same datsheet as RTL8366
|
|
* RTL8366RB - Probably reads out "RTL8366 revision B", has a quite
|
|
* different register layout from the other two
|
|
* RTL8366S - Is this "RTL8366 super"?
|
|
* RTL8367 - Has an OpenWRT driver as well
|
|
* RTL8368S - Seems to be an alternative name for RTL8366RB
|
|
* RTL8370 - Also uses SMI
|
|
*
|
|
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
|
|
* Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
|
|
* Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
|
|
* Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
|
|
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/overflow.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include "realtek.h"
|
|
#include "realtek-mdio.h"
|
|
#include "rtl83xx.h"
|
|
|
|
/* Read/write via mdiobus */
|
|
#define REALTEK_MDIO_CTRL0_REG 31
|
|
#define REALTEK_MDIO_START_REG 29
|
|
#define REALTEK_MDIO_CTRL1_REG 21
|
|
#define REALTEK_MDIO_ADDRESS_REG 23
|
|
#define REALTEK_MDIO_DATA_WRITE_REG 24
|
|
#define REALTEK_MDIO_DATA_READ_REG 25
|
|
|
|
#define REALTEK_MDIO_START_OP 0xFFFF
|
|
#define REALTEK_MDIO_ADDR_OP 0x000E
|
|
#define REALTEK_MDIO_READ_OP 0x0001
|
|
#define REALTEK_MDIO_WRITE_OP 0x0003
|
|
|
|
static int realtek_mdio_write(void *ctx, u32 reg, u32 val)
|
|
{
|
|
struct realtek_priv *priv = ctx;
|
|
struct mii_bus *bus = priv->bus;
|
|
int ret;
|
|
|
|
mutex_lock(&bus->mdio_lock);
|
|
|
|
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL0_REG, REALTEK_MDIO_ADDR_OP);
|
|
if (ret)
|
|
goto out_unlock;
|
|
|
|
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_ADDRESS_REG, reg);
|
|
if (ret)
|
|
goto out_unlock;
|
|
|
|
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_DATA_WRITE_REG, val);
|
|
if (ret)
|
|
goto out_unlock;
|
|
|
|
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL1_REG, REALTEK_MDIO_WRITE_OP);
|
|
|
|
out_unlock:
|
|
mutex_unlock(&bus->mdio_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int realtek_mdio_read(void *ctx, u32 reg, u32 *val)
|
|
{
|
|
struct realtek_priv *priv = ctx;
|
|
struct mii_bus *bus = priv->bus;
|
|
int ret;
|
|
|
|
mutex_lock(&bus->mdio_lock);
|
|
|
|
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL0_REG, REALTEK_MDIO_ADDR_OP);
|
|
if (ret)
|
|
goto out_unlock;
|
|
|
|
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_ADDRESS_REG, reg);
|
|
if (ret)
|
|
goto out_unlock;
|
|
|
|
ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL1_REG, REALTEK_MDIO_READ_OP);
|
|
if (ret)
|
|
goto out_unlock;
|
|
|
|
ret = bus->read(bus, priv->mdio_addr, REALTEK_MDIO_DATA_READ_REG);
|
|
if (ret >= 0) {
|
|
*val = ret;
|
|
ret = 0;
|
|
}
|
|
|
|
out_unlock:
|
|
mutex_unlock(&bus->mdio_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct realtek_interface_info realtek_mdio_info = {
|
|
.reg_read = realtek_mdio_read,
|
|
.reg_write = realtek_mdio_write,
|
|
};
|
|
|
|
/**
|
|
* realtek_mdio_probe() - Probe a platform device for an MDIO-connected switch
|
|
* @mdiodev: mdio_device to probe on.
|
|
*
|
|
* This function should be used as the .probe in an mdio_driver. After
|
|
* calling the common probe function for both interfaces, it initializes the
|
|
* values specific for MDIO-connected devices. Finally, it calls a common
|
|
* function to register the DSA switch.
|
|
*
|
|
* Context: Can sleep. Takes and releases priv->map_lock.
|
|
* Return: Returns 0 on success, a negative error on failure.
|
|
*/
|
|
int realtek_mdio_probe(struct mdio_device *mdiodev)
|
|
{
|
|
struct device *dev = &mdiodev->dev;
|
|
struct realtek_priv *priv;
|
|
int ret;
|
|
|
|
priv = rtl83xx_probe(dev, &realtek_mdio_info);
|
|
if (IS_ERR(priv))
|
|
return PTR_ERR(priv);
|
|
|
|
priv->bus = mdiodev->bus;
|
|
priv->mdio_addr = mdiodev->addr;
|
|
priv->write_reg_noack = realtek_mdio_write;
|
|
|
|
ret = rtl83xx_register_switch(priv);
|
|
if (ret) {
|
|
rtl83xx_remove(priv);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(realtek_mdio_probe, "REALTEK_DSA");
|
|
|
|
/**
|
|
* realtek_mdio_remove() - Remove the driver of an MDIO-connected switch
|
|
* @mdiodev: mdio_device to be removed.
|
|
*
|
|
* This function should be used as the .remove in an mdio_driver. First
|
|
* it unregisters the DSA switch and then it calls the common remove function.
|
|
*
|
|
* Context: Can sleep.
|
|
* Return: Nothing.
|
|
*/
|
|
void realtek_mdio_remove(struct mdio_device *mdiodev)
|
|
{
|
|
struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);
|
|
|
|
if (!priv)
|
|
return;
|
|
|
|
rtl83xx_unregister_switch(priv);
|
|
|
|
rtl83xx_remove(priv);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(realtek_mdio_remove, "REALTEK_DSA");
|
|
|
|
/**
|
|
* realtek_mdio_shutdown() - Shutdown the driver of a MDIO-connected switch
|
|
* @mdiodev: mdio_device shutting down.
|
|
*
|
|
* This function should be used as the .shutdown in a platform_driver. It calls
|
|
* the common shutdown function.
|
|
*
|
|
* Context: Can sleep.
|
|
* Return: Nothing.
|
|
*/
|
|
void realtek_mdio_shutdown(struct mdio_device *mdiodev)
|
|
{
|
|
struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);
|
|
|
|
if (!priv)
|
|
return;
|
|
|
|
rtl83xx_shutdown(priv);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(realtek_mdio_shutdown, "REALTEK_DSA");
|