Merge branch 'dsa-realtek-MDIO'

Luiz Angelo Daros de Luca says:

====================
net: dsa: realtek: MDIO interface and RTL8367S,RTL8367RB-VB

The old realtek-smi driver was linking subdrivers into a single
realtek-smi.ko After this series, each subdriver will be an independent
module required by either realtek-smi (platform driver) or the new
realtek-mdio (mdio driver). Both interface drivers (SMI or MDIO) are
independent, and they might even work side-by-side, although it will be
difficult to find such device. The subdriver can be individually
selected but only at buildtime, saving some storage space for custom
embedded systems.

Existing realtek-smi devices continue to work untouched during the
tests. The realtek-smi was moved into a realtek subdirectory, but it
normally does not break things.

I couldn't identify a fixed relation between port numbers (0..9) and
external interfaces (0..2), and I'm not sure if it is fixed for each
chip version or a device configuration. Until there is more info about
it, there is a new port property "realtek,ext-int" that can inform the
external interface.

The rtl8365mb might now handle multiple CPU ports and extint ports not
used as CPU ports. RTL8367S has an SGMII external interface, but my test
device (TP-Link Archer C5v4) uses only the second RGMII interface. We
need a test device with more external ports to test these features.
The driver still cannot handle SGMII ports.

RTL8367RB-VB support was added using information from Frank Wunderlich
<frank-w@public-files.de> but I didn't test it myself.

The rtl8365mb was tested with a MDIO-connected RTL8367S (TP-Link Acher
C5v4) and a SMI-connected RTL8365MB-VC switch (Asus RT-AC88U)

The rtl8366rb subdriver was not tested with this patch series, but it
was only slightly touched. It would be nice to test it, especially in an
MDIO-connected switch.

Best,

Luiz

Changelog:

v1-v2)
- formatting fixes
- dropped the rtl8365mb->rtl8367c rename
- other suggestions

v2-v3)
* realtek-mdio.c:
  - cleanup realtek-mdio.c (BUG_ON, comments and includes)
  - check devm_regmap_init return code
  - removed realtek,rtl8366s string from realtek-mdio
* realtek-smi.c:
  - removed void* type cast
* rtl8365mb.c:
  - using macros to identify EXT interfaces
  - rename some extra extport->extint cases
  - allow extint as non cpu (not tested)
  - allow multple cpu ports (not tested)
  - dropped cpu info from struct rtl8365mb
* dropped dt-bindings changes (dealing outside this series)
* formatting issues fixed

v3-v4)
* fix cover message numbering 0/13 -> 0/11
* use static for realtek_mdio_read_reg
  - Reported-by: kernel test robot <lkp@intel.com>
* use dsa_switch_for_each_cpu_port
* mention realtek_smi_{variant,ops} to realtek_{variant,ops}
  in commit message

v5) sent again v4 branch. Sorry

v4-v6)
- added support for RTL8367RB-VB
- cleanup mdio_{read,write}, removing misterious START_OP, checking and
  returning errors
- renamed priv->phy_id to priv->mdio_addr
- duplicated priv->ds_ops into ds_ops_{smi,mdio}. ds_ops_smi must not
  set
  phy_read or else both dsa and this driver might free slave_mii.
Dropped
  401fd75c92f37
- Map port to extint using code instead of device-tree property. Added
  comment
  about port number, port description and external interfaces. Dropped
  'realtek,ext-int' device-tree property
- Redacted the non-cpu ext port commit message, not highlighting the
  possibility of using multiple CPU ports as it was just a byproduct.
- In a possible case of multiple cpu ports, use the first one as the
  trap port.
  Dropped 'realtek,trap-port' device-tree property
- Some formatting fixes
- BUG: rtl8365mb_phy_mode_supported was still checking for a cpu port
  and not
  an external interface
- BUG: fix trapdoor masking for port>7. Got a compiler error with a
  bigger
  constant value
- WARN: completed kdoc for rtl8366rb_drop_untagged()
- WARN: removed marks from incomplete kdoc
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2022-01-28 15:02:50 +00:00
commit 7c263e9db3
12 changed files with 1545 additions and 1132 deletions

View File

@ -16352,8 +16352,7 @@ REALTEK RTL83xx SMI DSA ROUTER CHIPS
M: Linus Walleij <linus.walleij@linaro.org>
S: Maintained
F: Documentation/devicetree/bindings/net/dsa/realtek-smi.txt
F: drivers/net/dsa/realtek-smi*
F: drivers/net/dsa/rtl83*
F: drivers/net/dsa/realtek/*
REALTEK WIRELESS DRIVER (rtlwifi family)
M: Ping-Ke Shih <pkshih@realtek.com>

View File

@ -67,17 +67,7 @@ config NET_DSA_QCA8K
This enables support for the Qualcomm Atheros QCA8K Ethernet
switch chips.
config NET_DSA_REALTEK_SMI
tristate "Realtek SMI Ethernet switch family support"
select NET_DSA_TAG_RTL4_A
select NET_DSA_TAG_RTL8_4
select FIXED_PHY
select IRQ_DOMAIN
select REALTEK_PHY
select REGMAP
help
This enables support for the Realtek SMI-based switch
chips, currently only RTL8366RB.
source "drivers/net/dsa/realtek/Kconfig"
config NET_DSA_SMSC_LAN9303
tristate

View File

@ -9,8 +9,6 @@ obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o
obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o
obj-$(CONFIG_NET_DSA_MV88E6060) += mv88e6060.o
obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o
obj-$(CONFIG_NET_DSA_REALTEK_SMI) += realtek-smi.o
realtek-smi-objs := realtek-smi-core.o rtl8366.o rtl8366rb.o rtl8365mb.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303_I2C) += lan9303_i2c.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
@ -23,5 +21,6 @@ obj-y += microchip/
obj-y += mv88e6xxx/
obj-y += ocelot/
obj-y += qca/
obj-y += realtek/
obj-y += sja1105/
obj-y += xrs700x/

View File

@ -1,523 +0,0 @@
// SPDX-License-Identifier: GPL-2.0+
/* Realtek Simple Management Interface (SMI) driver
* It can be discussed how "simple" this interface is.
*
* The SMI protocol piggy-backs the MDIO MDC and MDIO signals levels
* but the protocol is not MDIO at all. Instead it is a Realtek
* pecularity that need to bit-bang the lines in a special way to
* communicate with the switch.
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/skbuff.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/bitops.h>
#include <linux/if_bridge.h>
#include "realtek-smi-core.h"
#define REALTEK_SMI_ACK_RETRY_COUNT 5
#define REALTEK_SMI_HW_STOP_DELAY 25 /* msecs */
#define REALTEK_SMI_HW_START_DELAY 100 /* msecs */
static inline void realtek_smi_clk_delay(struct realtek_smi *smi)
{
ndelay(smi->clk_delay);
}
static void realtek_smi_start(struct realtek_smi *smi)
{
/* Set GPIO pins to output mode, with initial state:
* SCK = 0, SDA = 1
*/
gpiod_direction_output(smi->mdc, 0);
gpiod_direction_output(smi->mdio, 1);
realtek_smi_clk_delay(smi);
/* CLK 1: 0 -> 1, 1 -> 0 */
gpiod_set_value(smi->mdc, 1);
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdc, 0);
realtek_smi_clk_delay(smi);
/* CLK 2: */
gpiod_set_value(smi->mdc, 1);
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdio, 0);
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdc, 0);
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdio, 1);
}
static void realtek_smi_stop(struct realtek_smi *smi)
{
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdio, 0);
gpiod_set_value(smi->mdc, 1);
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdio, 1);
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdc, 1);
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdc, 0);
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdc, 1);
/* Add a click */
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdc, 0);
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdc, 1);
/* Set GPIO pins to input mode */
gpiod_direction_input(smi->mdio);
gpiod_direction_input(smi->mdc);
}
static void realtek_smi_write_bits(struct realtek_smi *smi, u32 data, u32 len)
{
for (; len > 0; len--) {
realtek_smi_clk_delay(smi);
/* Prepare data */
gpiod_set_value(smi->mdio, !!(data & (1 << (len - 1))));
realtek_smi_clk_delay(smi);
/* Clocking */
gpiod_set_value(smi->mdc, 1);
realtek_smi_clk_delay(smi);
gpiod_set_value(smi->mdc, 0);
}
}
static void realtek_smi_read_bits(struct realtek_smi *smi, u32 len, u32 *data)
{
gpiod_direction_input(smi->mdio);
for (*data = 0; len > 0; len--) {
u32 u;
realtek_smi_clk_delay(smi);
/* Clocking */
gpiod_set_value(smi->mdc, 1);
realtek_smi_clk_delay(smi);
u = !!gpiod_get_value(smi->mdio);
gpiod_set_value(smi->mdc, 0);
*data |= (u << (len - 1));
}
gpiod_direction_output(smi->mdio, 0);
}
static int realtek_smi_wait_for_ack(struct realtek_smi *smi)
{
int retry_cnt;
retry_cnt = 0;
do {
u32 ack;
realtek_smi_read_bits(smi, 1, &ack);
if (ack == 0)
break;
if (++retry_cnt > REALTEK_SMI_ACK_RETRY_COUNT) {
dev_err(smi->dev, "ACK timeout\n");
return -ETIMEDOUT;
}
} while (1);
return 0;
}
static int realtek_smi_write_byte(struct realtek_smi *smi, u8 data)
{
realtek_smi_write_bits(smi, data, 8);
return realtek_smi_wait_for_ack(smi);
}
static int realtek_smi_write_byte_noack(struct realtek_smi *smi, u8 data)
{
realtek_smi_write_bits(smi, data, 8);
return 0;
}
static int realtek_smi_read_byte0(struct realtek_smi *smi, u8 *data)
{
u32 t;
/* Read data */
realtek_smi_read_bits(smi, 8, &t);
*data = (t & 0xff);
/* Send an ACK */
realtek_smi_write_bits(smi, 0x00, 1);
return 0;
}
static int realtek_smi_read_byte1(struct realtek_smi *smi, u8 *data)
{
u32 t;
/* Read data */
realtek_smi_read_bits(smi, 8, &t);
*data = (t & 0xff);
/* Send an ACK */
realtek_smi_write_bits(smi, 0x01, 1);
return 0;
}
static int realtek_smi_read_reg(struct realtek_smi *smi, u32 addr, u32 *data)
{
unsigned long flags;
u8 lo = 0;
u8 hi = 0;
int ret;
spin_lock_irqsave(&smi->lock, flags);
realtek_smi_start(smi);
/* Send READ command */
ret = realtek_smi_write_byte(smi, smi->cmd_read);
if (ret)
goto out;
/* Set ADDR[7:0] */
ret = realtek_smi_write_byte(smi, addr & 0xff);
if (ret)
goto out;
/* Set ADDR[15:8] */
ret = realtek_smi_write_byte(smi, addr >> 8);
if (ret)
goto out;
/* Read DATA[7:0] */
realtek_smi_read_byte0(smi, &lo);
/* Read DATA[15:8] */
realtek_smi_read_byte1(smi, &hi);
*data = ((u32)lo) | (((u32)hi) << 8);
ret = 0;
out:
realtek_smi_stop(smi);
spin_unlock_irqrestore(&smi->lock, flags);
return ret;
}
static int realtek_smi_write_reg(struct realtek_smi *smi,
u32 addr, u32 data, bool ack)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&smi->lock, flags);
realtek_smi_start(smi);
/* Send WRITE command */
ret = realtek_smi_write_byte(smi, smi->cmd_write);
if (ret)
goto out;
/* Set ADDR[7:0] */
ret = realtek_smi_write_byte(smi, addr & 0xff);
if (ret)
goto out;
/* Set ADDR[15:8] */
ret = realtek_smi_write_byte(smi, addr >> 8);
if (ret)
goto out;
/* Write DATA[7:0] */
ret = realtek_smi_write_byte(smi, data & 0xff);
if (ret)
goto out;
/* Write DATA[15:8] */
if (ack)
ret = realtek_smi_write_byte(smi, data >> 8);
else
ret = realtek_smi_write_byte_noack(smi, data >> 8);
if (ret)
goto out;
ret = 0;
out:
realtek_smi_stop(smi);
spin_unlock_irqrestore(&smi->lock, flags);
return ret;
}
/* There is one single case when we need to use this accessor and that
* is when issueing soft reset. Since the device reset as soon as we write
* that bit, no ACK will come back for natural reasons.
*/
int realtek_smi_write_reg_noack(struct realtek_smi *smi, u32 addr,
u32 data)
{
return realtek_smi_write_reg(smi, addr, data, false);
}
EXPORT_SYMBOL_GPL(realtek_smi_write_reg_noack);
/* Regmap accessors */
static int realtek_smi_write(void *ctx, u32 reg, u32 val)
{
struct realtek_smi *smi = ctx;
return realtek_smi_write_reg(smi, reg, val, true);
}
static int realtek_smi_read(void *ctx, u32 reg, u32 *val)
{
struct realtek_smi *smi = ctx;
return realtek_smi_read_reg(smi, reg, val);
}
static const struct regmap_config realtek_smi_mdio_regmap_config = {
.reg_bits = 10, /* A4..A0 R4..R0 */
.val_bits = 16,
.reg_stride = 1,
/* PHY regs are at 0x8000 */
.max_register = 0xffff,
.reg_format_endian = REGMAP_ENDIAN_BIG,
.reg_read = realtek_smi_read,
.reg_write = realtek_smi_write,
.cache_type = REGCACHE_NONE,
};
static int realtek_smi_mdio_read(struct mii_bus *bus, int addr, int regnum)
{
struct realtek_smi *smi = bus->priv;
return smi->ops->phy_read(smi, addr, regnum);
}
static int realtek_smi_mdio_write(struct mii_bus *bus, int addr, int regnum,
u16 val)
{
struct realtek_smi *smi = bus->priv;
return smi->ops->phy_write(smi, addr, regnum, val);
}
int realtek_smi_setup_mdio(struct realtek_smi *smi)
{
struct device_node *mdio_np;
int ret;
mdio_np = of_get_compatible_child(smi->dev->of_node, "realtek,smi-mdio");
if (!mdio_np) {
dev_err(smi->dev, "no MDIO bus node\n");
return -ENODEV;
}
smi->slave_mii_bus = devm_mdiobus_alloc(smi->dev);
if (!smi->slave_mii_bus) {
ret = -ENOMEM;
goto err_put_node;
}
smi->slave_mii_bus->priv = smi;
smi->slave_mii_bus->name = "SMI slave MII";
smi->slave_mii_bus->read = realtek_smi_mdio_read;
smi->slave_mii_bus->write = realtek_smi_mdio_write;
snprintf(smi->slave_mii_bus->id, MII_BUS_ID_SIZE, "SMI-%d",
smi->ds->index);
smi->slave_mii_bus->dev.of_node = mdio_np;
smi->slave_mii_bus->parent = smi->dev;
smi->ds->slave_mii_bus = smi->slave_mii_bus;
ret = devm_of_mdiobus_register(smi->dev, smi->slave_mii_bus, mdio_np);
if (ret) {
dev_err(smi->dev, "unable to register MDIO bus %s\n",
smi->slave_mii_bus->id);
goto err_put_node;
}
return 0;
err_put_node:
of_node_put(mdio_np);
return ret;
}
static int realtek_smi_probe(struct platform_device *pdev)
{
const struct realtek_smi_variant *var;
struct device *dev = &pdev->dev;
struct realtek_smi *smi;
struct device_node *np;
int ret;
var = of_device_get_match_data(dev);
np = dev->of_node;
smi = devm_kzalloc(dev, sizeof(*smi) + var->chip_data_sz, GFP_KERNEL);
if (!smi)
return -ENOMEM;
smi->chip_data = (void *)smi + sizeof(*smi);
smi->map = devm_regmap_init(dev, NULL, smi,
&realtek_smi_mdio_regmap_config);
if (IS_ERR(smi->map)) {
ret = PTR_ERR(smi->map);
dev_err(dev, "regmap init failed: %d\n", ret);
return ret;
}
/* Link forward and backward */
smi->dev = dev;
smi->clk_delay = var->clk_delay;
smi->cmd_read = var->cmd_read;
smi->cmd_write = var->cmd_write;
smi->ops = var->ops;
dev_set_drvdata(dev, smi);
spin_lock_init(&smi->lock);
/* TODO: if power is software controlled, set up any regulators here */
/* Assert then deassert RESET */
smi->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(smi->reset)) {
dev_err(dev, "failed to get RESET GPIO\n");
return PTR_ERR(smi->reset);
}
msleep(REALTEK_SMI_HW_STOP_DELAY);
gpiod_set_value(smi->reset, 0);
msleep(REALTEK_SMI_HW_START_DELAY);
dev_info(dev, "deasserted RESET\n");
/* Fetch MDIO pins */
smi->mdc = devm_gpiod_get_optional(dev, "mdc", GPIOD_OUT_LOW);
if (IS_ERR(smi->mdc))
return PTR_ERR(smi->mdc);
smi->mdio = devm_gpiod_get_optional(dev, "mdio", GPIOD_OUT_LOW);
if (IS_ERR(smi->mdio))
return PTR_ERR(smi->mdio);
smi->leds_disabled = of_property_read_bool(np, "realtek,disable-leds");
ret = smi->ops->detect(smi);
if (ret) {
dev_err(dev, "unable to detect switch\n");
return ret;
}
smi->ds = devm_kzalloc(dev, sizeof(*smi->ds), GFP_KERNEL);
if (!smi->ds)
return -ENOMEM;
smi->ds->dev = dev;
smi->ds->num_ports = smi->num_ports;
smi->ds->priv = smi;
smi->ds->ops = var->ds_ops;
ret = dsa_register_switch(smi->ds);
if (ret) {
dev_err_probe(dev, ret, "unable to register switch\n");
return ret;
}
return 0;
}
static int realtek_smi_remove(struct platform_device *pdev)
{
struct realtek_smi *smi = platform_get_drvdata(pdev);
if (!smi)
return 0;
dsa_unregister_switch(smi->ds);
if (smi->slave_mii_bus)
of_node_put(smi->slave_mii_bus->dev.of_node);
gpiod_set_value(smi->reset, 1);
platform_set_drvdata(pdev, NULL);
return 0;
}
static void realtek_smi_shutdown(struct platform_device *pdev)
{
struct realtek_smi *smi = platform_get_drvdata(pdev);
if (!smi)
return;
dsa_switch_shutdown(smi->ds);
platform_set_drvdata(pdev, NULL);
}
static const struct of_device_id realtek_smi_of_match[] = {
{
.compatible = "realtek,rtl8366rb",
.data = &rtl8366rb_variant,
},
{
/* FIXME: add support for RTL8366S and more */
.compatible = "realtek,rtl8366s",
.data = NULL,
},
{
.compatible = "realtek,rtl8365mb",
.data = &rtl8365mb_variant,
},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, realtek_smi_of_match);
static struct platform_driver realtek_smi_driver = {
.driver = {
.name = "realtek-smi",
.of_match_table = of_match_ptr(realtek_smi_of_match),
},
.probe = realtek_smi_probe,
.remove = realtek_smi_remove,
.shutdown = realtek_smi_shutdown,
};
module_platform_driver(realtek_smi_driver);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,44 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig NET_DSA_REALTEK
tristate "Realtek Ethernet switch family support"
depends on NET_DSA
select FIXED_PHY
select IRQ_DOMAIN
select REALTEK_PHY
select REGMAP
help
Select to enable support for Realtek Ethernet switch chips.
config NET_DSA_REALTEK_MDIO
tristate "Realtek MDIO connected switch driver"
depends on NET_DSA_REALTEK
default y
help
Select to enable support for registering switches configured
through MDIO.
config NET_DSA_REALTEK_SMI
tristate "Realtek SMI connected switch driver"
depends on NET_DSA_REALTEK
default y
help
Select to enable support for registering switches connected
through SMI.
config NET_DSA_REALTEK_RTL8365MB
tristate "Realtek RTL8365MB switch subdriver"
default y
depends on NET_DSA_REALTEK
depends on NET_DSA_REALTEK_SMI || NET_DSA_REALTEK_MDIO
select NET_DSA_TAG_RTL8_4
help
Select to enable support for Realtek RTL8365MB-VC and RTL8367S.
config NET_DSA_REALTEK_RTL8366RB
tristate "Realtek RTL8366RB switch subdriver"
default y
depends on NET_DSA_REALTEK
depends on NET_DSA_REALTEK_SMI || NET_DSA_REALTEK_MDIO
select NET_DSA_TAG_RTL4_A
help
Select to enable support for Realtek RTL8366RB

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_REALTEK_MDIO) += realtek-mdio.o
obj-$(CONFIG_NET_DSA_REALTEK_SMI) += realtek-smi.o
obj-$(CONFIG_NET_DSA_REALTEK_RTL8366RB) += rtl8366.o
rtl8366-objs := rtl8366-core.o rtl8366rb.o
obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o

View File

@ -0,0 +1,229 @@
// 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_device.h>
#include <linux/regmap.h>
#include "realtek.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 regmap_config realtek_mdio_regmap_config = {
.reg_bits = 10, /* A4..A0 R4..R0 */
.val_bits = 16,
.reg_stride = 1,
/* PHY regs are at 0x8000 */
.max_register = 0xffff,
.reg_format_endian = REGMAP_ENDIAN_BIG,
.reg_read = realtek_mdio_read,
.reg_write = realtek_mdio_write,
.cache_type = REGCACHE_NONE,
};
static int realtek_mdio_probe(struct mdio_device *mdiodev)
{
struct realtek_priv *priv;
struct device *dev = &mdiodev->dev;
const struct realtek_variant *var;
int ret;
struct device_node *np;
var = of_device_get_match_data(dev);
if (!var)
return -EINVAL;
priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->map = devm_regmap_init(dev, NULL, priv, &realtek_mdio_regmap_config);
if (IS_ERR(priv->map)) {
ret = PTR_ERR(priv->map);
dev_err(dev, "regmap init failed: %d\n", ret);
return ret;
}
priv->mdio_addr = mdiodev->addr;
priv->bus = mdiodev->bus;
priv->dev = &mdiodev->dev;
priv->chip_data = (void *)priv + sizeof(*priv);
priv->clk_delay = var->clk_delay;
priv->cmd_read = var->cmd_read;
priv->cmd_write = var->cmd_write;
priv->ops = var->ops;
priv->write_reg_noack = realtek_mdio_write;
np = dev->of_node;
dev_set_drvdata(dev, priv);
/* TODO: if power is software controlled, set up any regulators here */
priv->leds_disabled = of_property_read_bool(np, "realtek,disable-leds");
ret = priv->ops->detect(priv);
if (ret) {
dev_err(dev, "unable to detect switch\n");
return ret;
}
priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
if (!priv->ds)
return -ENOMEM;
priv->ds->dev = dev;
priv->ds->num_ports = priv->num_ports;
priv->ds->priv = priv;
priv->ds->ops = var->ds_ops_mdio;
ret = dsa_register_switch(priv->ds);
if (ret) {
dev_err(priv->dev, "unable to register switch ret = %d\n", ret);
return ret;
}
return 0;
}
static void realtek_mdio_remove(struct mdio_device *mdiodev)
{
struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);
if (!priv)
return;
dsa_unregister_switch(priv->ds);
dev_set_drvdata(&mdiodev->dev, NULL);
}
static void realtek_mdio_shutdown(struct mdio_device *mdiodev)
{
struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);
if (!priv)
return;
dsa_switch_shutdown(priv->ds);
dev_set_drvdata(&mdiodev->dev, NULL);
}
static const struct of_device_id realtek_mdio_of_match[] = {
#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB)
{ .compatible = "realtek,rtl8366rb", .data = &rtl8366rb_variant, },
#endif
#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8365MB)
{ .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, },
{ .compatible = "realtek,rtl8367s", .data = &rtl8365mb_variant, },
#endif
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, realtek_mdio_of_match);
static struct mdio_driver realtek_mdio_driver = {
.mdiodrv.driver = {
.name = "realtek-mdio",
.of_match_table = of_match_ptr(realtek_mdio_of_match),
},
.probe = realtek_mdio_probe,
.remove = realtek_mdio_remove,
.shutdown = realtek_mdio_shutdown,
};
mdio_module_driver(realtek_mdio_driver);
MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via MDIO interface");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,535 @@
// SPDX-License-Identifier: GPL-2.0+
/* Realtek Simple Management Interface (SMI) driver
* It can be discussed how "simple" this interface is.
*
* The SMI protocol piggy-backs the MDIO MDC and MDIO signals levels
* but the protocol is not MDIO at all. Instead it is a Realtek
* pecularity that need to bit-bang the lines in a special way to
* communicate with the switch.
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/skbuff.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/bitops.h>
#include <linux/if_bridge.h>
#include "realtek.h"
#define REALTEK_SMI_ACK_RETRY_COUNT 5
#define REALTEK_SMI_HW_STOP_DELAY 25 /* msecs */
#define REALTEK_SMI_HW_START_DELAY 100 /* msecs */
static inline void realtek_smi_clk_delay(struct realtek_priv *priv)
{
ndelay(priv->clk_delay);
}
static void realtek_smi_start(struct realtek_priv *priv)
{
/* Set GPIO pins to output mode, with initial state:
* SCK = 0, SDA = 1
*/
gpiod_direction_output(priv->mdc, 0);
gpiod_direction_output(priv->mdio, 1);
realtek_smi_clk_delay(priv);
/* CLK 1: 0 -> 1, 1 -> 0 */
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
/* CLK 2: */
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 1);
}
static void realtek_smi_stop(struct realtek_priv *priv)
{
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 0);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdio, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
/* Add a click */
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 1);
/* Set GPIO pins to input mode */
gpiod_direction_input(priv->mdio);
gpiod_direction_input(priv->mdc);
}
static void realtek_smi_write_bits(struct realtek_priv *priv, u32 data, u32 len)
{
for (; len > 0; len--) {
realtek_smi_clk_delay(priv);
/* Prepare data */
gpiod_set_value(priv->mdio, !!(data & (1 << (len - 1))));
realtek_smi_clk_delay(priv);
/* Clocking */
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
gpiod_set_value(priv->mdc, 0);
}
}
static void realtek_smi_read_bits(struct realtek_priv *priv, u32 len, u32 *data)
{
gpiod_direction_input(priv->mdio);
for (*data = 0; len > 0; len--) {
u32 u;
realtek_smi_clk_delay(priv);
/* Clocking */
gpiod_set_value(priv->mdc, 1);
realtek_smi_clk_delay(priv);
u = !!gpiod_get_value(priv->mdio);
gpiod_set_value(priv->mdc, 0);
*data |= (u << (len - 1));
}
gpiod_direction_output(priv->mdio, 0);
}
static int realtek_smi_wait_for_ack(struct realtek_priv *priv)
{
int retry_cnt;
retry_cnt = 0;
do {
u32 ack;
realtek_smi_read_bits(priv, 1, &ack);
if (ack == 0)
break;
if (++retry_cnt > REALTEK_SMI_ACK_RETRY_COUNT) {
dev_err(priv->dev, "ACK timeout\n");
return -ETIMEDOUT;
}
} while (1);
return 0;
}
static int realtek_smi_write_byte(struct realtek_priv *priv, u8 data)
{
realtek_smi_write_bits(priv, data, 8);
return realtek_smi_wait_for_ack(priv);
}
static int realtek_smi_write_byte_noack(struct realtek_priv *priv, u8 data)
{
realtek_smi_write_bits(priv, data, 8);
return 0;
}
static int realtek_smi_read_byte0(struct realtek_priv *priv, u8 *data)
{
u32 t;
/* Read data */
realtek_smi_read_bits(priv, 8, &t);
*data = (t & 0xff);
/* Send an ACK */
realtek_smi_write_bits(priv, 0x00, 1);
return 0;
}
static int realtek_smi_read_byte1(struct realtek_priv *priv, u8 *data)
{
u32 t;
/* Read data */
realtek_smi_read_bits(priv, 8, &t);
*data = (t & 0xff);
/* Send an ACK */
realtek_smi_write_bits(priv, 0x01, 1);
return 0;
}
static int realtek_smi_read_reg(struct realtek_priv *priv, u32 addr, u32 *data)
{
unsigned long flags;
u8 lo = 0;
u8 hi = 0;
int ret;
spin_lock_irqsave(&priv->lock, flags);
realtek_smi_start(priv);
/* Send READ command */
ret = realtek_smi_write_byte(priv, priv->cmd_read);
if (ret)
goto out;
/* Set ADDR[7:0] */
ret = realtek_smi_write_byte(priv, addr & 0xff);
if (ret)
goto out;
/* Set ADDR[15:8] */
ret = realtek_smi_write_byte(priv, addr >> 8);
if (ret)
goto out;
/* Read DATA[7:0] */
realtek_smi_read_byte0(priv, &lo);
/* Read DATA[15:8] */
realtek_smi_read_byte1(priv, &hi);
*data = ((u32)lo) | (((u32)hi) << 8);
ret = 0;
out:
realtek_smi_stop(priv);
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
static int realtek_smi_write_reg(struct realtek_priv *priv,
u32 addr, u32 data, bool ack)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&priv->lock, flags);
realtek_smi_start(priv);
/* Send WRITE command */
ret = realtek_smi_write_byte(priv, priv->cmd_write);
if (ret)
goto out;
/* Set ADDR[7:0] */
ret = realtek_smi_write_byte(priv, addr & 0xff);
if (ret)
goto out;
/* Set ADDR[15:8] */
ret = realtek_smi_write_byte(priv, addr >> 8);
if (ret)
goto out;
/* Write DATA[7:0] */
ret = realtek_smi_write_byte(priv, data & 0xff);
if (ret)
goto out;
/* Write DATA[15:8] */
if (ack)
ret = realtek_smi_write_byte(priv, data >> 8);
else
ret = realtek_smi_write_byte_noack(priv, data >> 8);
if (ret)
goto out;
ret = 0;
out:
realtek_smi_stop(priv);
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
/* There is one single case when we need to use this accessor and that
* is when issueing soft reset. Since the device reset as soon as we write
* that bit, no ACK will come back for natural reasons.
*/
static int realtek_smi_write_reg_noack(void *ctx, u32 reg, u32 val)
{
return realtek_smi_write_reg(ctx, reg, val, false);
}
/* Regmap accessors */
static int realtek_smi_write(void *ctx, u32 reg, u32 val)
{
struct realtek_priv *priv = ctx;
return realtek_smi_write_reg(priv, reg, val, true);
}
static int realtek_smi_read(void *ctx, u32 reg, u32 *val)
{
struct realtek_priv *priv = ctx;
return realtek_smi_read_reg(priv, reg, val);
}
static const struct regmap_config realtek_smi_mdio_regmap_config = {
.reg_bits = 10, /* A4..A0 R4..R0 */
.val_bits = 16,
.reg_stride = 1,
/* PHY regs are at 0x8000 */
.max_register = 0xffff,
.reg_format_endian = REGMAP_ENDIAN_BIG,
.reg_read = realtek_smi_read,
.reg_write = realtek_smi_write,
.cache_type = REGCACHE_NONE,
};
static int realtek_smi_mdio_read(struct mii_bus *bus, int addr, int regnum)
{
struct realtek_priv *priv = bus->priv;
return priv->ops->phy_read(priv, addr, regnum);
}
static int realtek_smi_mdio_write(struct mii_bus *bus, int addr, int regnum,
u16 val)
{
struct realtek_priv *priv = bus->priv;
return priv->ops->phy_write(priv, addr, regnum, val);
}
static int realtek_smi_setup_mdio(struct dsa_switch *ds)
{
struct realtek_priv *priv = ds->priv;
struct device_node *mdio_np;
int ret;
mdio_np = of_get_compatible_child(priv->dev->of_node, "realtek,smi-mdio");
if (!mdio_np) {
dev_err(priv->dev, "no MDIO bus node\n");
return -ENODEV;
}
priv->slave_mii_bus = devm_mdiobus_alloc(priv->dev);
if (!priv->slave_mii_bus) {
ret = -ENOMEM;
goto err_put_node;
}
priv->slave_mii_bus->priv = priv;
priv->slave_mii_bus->name = "SMI slave MII";
priv->slave_mii_bus->read = realtek_smi_mdio_read;
priv->slave_mii_bus->write = realtek_smi_mdio_write;
snprintf(priv->slave_mii_bus->id, MII_BUS_ID_SIZE, "SMI-%d",
ds->index);
priv->slave_mii_bus->dev.of_node = mdio_np;
priv->slave_mii_bus->parent = priv->dev;
ds->slave_mii_bus = priv->slave_mii_bus;
ret = devm_of_mdiobus_register(priv->dev, priv->slave_mii_bus, mdio_np);
if (ret) {
dev_err(priv->dev, "unable to register MDIO bus %s\n",
priv->slave_mii_bus->id);
goto err_put_node;
}
return 0;
err_put_node:
of_node_put(mdio_np);
return ret;
}
static int realtek_smi_probe(struct platform_device *pdev)
{
const struct realtek_variant *var;
struct device *dev = &pdev->dev;
struct realtek_priv *priv;
struct device_node *np;
int ret;
var = of_device_get_match_data(dev);
np = dev->of_node;
priv = devm_kzalloc(dev, sizeof(*priv) + var->chip_data_sz, GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->chip_data = (void *)priv + sizeof(*priv);
priv->map = devm_regmap_init(dev, NULL, priv,
&realtek_smi_mdio_regmap_config);
if (IS_ERR(priv->map)) {
ret = PTR_ERR(priv->map);
dev_err(dev, "regmap init failed: %d\n", ret);
return ret;
}
/* Link forward and backward */
priv->dev = dev;
priv->clk_delay = var->clk_delay;
priv->cmd_read = var->cmd_read;
priv->cmd_write = var->cmd_write;
priv->ops = var->ops;
priv->setup_interface = realtek_smi_setup_mdio;
priv->write_reg_noack = realtek_smi_write_reg_noack;
dev_set_drvdata(dev, priv);
spin_lock_init(&priv->lock);
/* TODO: if power is software controlled, set up any regulators here */
/* Assert then deassert RESET */
priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(priv->reset)) {
dev_err(dev, "failed to get RESET GPIO\n");
return PTR_ERR(priv->reset);
}
msleep(REALTEK_SMI_HW_STOP_DELAY);
gpiod_set_value(priv->reset, 0);
msleep(REALTEK_SMI_HW_START_DELAY);
dev_info(dev, "deasserted RESET\n");
/* Fetch MDIO pins */
priv->mdc = devm_gpiod_get_optional(dev, "mdc", GPIOD_OUT_LOW);
if (IS_ERR(priv->mdc))
return PTR_ERR(priv->mdc);
priv->mdio = devm_gpiod_get_optional(dev, "mdio", GPIOD_OUT_LOW);
if (IS_ERR(priv->mdio))
return PTR_ERR(priv->mdio);
priv->leds_disabled = of_property_read_bool(np, "realtek,disable-leds");
ret = priv->ops->detect(priv);
if (ret) {
dev_err(dev, "unable to detect switch\n");
return ret;
}
priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
if (!priv->ds)
return -ENOMEM;
priv->ds->dev = dev;
priv->ds->num_ports = priv->num_ports;
priv->ds->priv = priv;
priv->ds->ops = var->ds_ops_smi;
ret = dsa_register_switch(priv->ds);
if (ret) {
dev_err_probe(dev, ret, "unable to register switch\n");
return ret;
}
return 0;
}
static int realtek_smi_remove(struct platform_device *pdev)
{
struct realtek_priv *priv = platform_get_drvdata(pdev);
if (!priv)
return 0;
dsa_unregister_switch(priv->ds);
if (priv->slave_mii_bus)
of_node_put(priv->slave_mii_bus->dev.of_node);
gpiod_set_value(priv->reset, 1);
platform_set_drvdata(pdev, NULL);
return 0;
}
static void realtek_smi_shutdown(struct platform_device *pdev)
{
struct realtek_priv *priv = platform_get_drvdata(pdev);
if (!priv)
return;
dsa_switch_shutdown(priv->ds);
platform_set_drvdata(pdev, NULL);
}
static const struct of_device_id realtek_smi_of_match[] = {
#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB)
{
.compatible = "realtek,rtl8366rb",
.data = &rtl8366rb_variant,
},
#endif
{
/* FIXME: add support for RTL8366S and more */
.compatible = "realtek,rtl8366s",
.data = NULL,
},
#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8365MB)
{
.compatible = "realtek,rtl8365mb",
.data = &rtl8365mb_variant,
},
{
.compatible = "realtek,rtl8367s",
.data = &rtl8365mb_variant,
},
#endif
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, realtek_smi_of_match);
static struct platform_driver realtek_smi_driver = {
.driver = {
.name = "realtek-smi",
.of_match_table = of_match_ptr(realtek_smi_of_match),
},
.probe = realtek_smi_probe,
.remove = realtek_smi_remove,
.shutdown = realtek_smi_shutdown,
};
module_platform_driver(realtek_smi_driver);
MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via SMI interface");
MODULE_LICENSE("GPL");

View File

@ -13,7 +13,7 @@
#include <linux/gpio/consumer.h>
#include <net/dsa.h>
struct realtek_smi_ops;
struct realtek_ops;
struct dentry;
struct inode;
struct file;
@ -25,7 +25,7 @@ struct rtl8366_mib_counter {
const char *name;
};
/**
/*
* struct rtl8366_vlan_mc - Virtual LAN member configuration
*/
struct rtl8366_vlan_mc {
@ -43,13 +43,15 @@ struct rtl8366_vlan_4k {
u8 fid;
};
struct realtek_smi {
struct realtek_priv {
struct device *dev;
struct gpio_desc *reset;
struct gpio_desc *mdc;
struct gpio_desc *mdio;
struct regmap *map;
struct mii_bus *slave_mii_bus;
struct mii_bus *bus;
int mdio_addr;
unsigned int clk_delay;
u8 cmd_read;
@ -65,7 +67,9 @@ struct realtek_smi {
unsigned int num_mib_counters;
struct rtl8366_mib_counter *mib_counters;
const struct realtek_smi_ops *ops;
const struct realtek_ops *ops;
int (*setup_interface)(struct dsa_switch *ds);
int (*write_reg_noack)(void *ctx, u32 addr, u32 data);
int vlan_enabled;
int vlan4k_enabled;
@ -74,61 +78,57 @@ struct realtek_smi {
void *chip_data; /* Per-chip extra variant data */
};
/**
* struct realtek_smi_ops - vtable for the per-SMI-chiptype operations
/*
* struct realtek_ops - vtable for the per-SMI-chiptype operations
* @detect: detects the chiptype
*/
struct realtek_smi_ops {
int (*detect)(struct realtek_smi *smi);
int (*reset_chip)(struct realtek_smi *smi);
int (*setup)(struct realtek_smi *smi);
void (*cleanup)(struct realtek_smi *smi);
int (*get_mib_counter)(struct realtek_smi *smi,
struct realtek_ops {
int (*detect)(struct realtek_priv *priv);
int (*reset_chip)(struct realtek_priv *priv);
int (*setup)(struct realtek_priv *priv);
void (*cleanup)(struct realtek_priv *priv);
int (*get_mib_counter)(struct realtek_priv *priv,
int port,
struct rtl8366_mib_counter *mib,
u64 *mibvalue);
int (*get_vlan_mc)(struct realtek_smi *smi, u32 index,
int (*get_vlan_mc)(struct realtek_priv *priv, u32 index,
struct rtl8366_vlan_mc *vlanmc);
int (*set_vlan_mc)(struct realtek_smi *smi, u32 index,
int (*set_vlan_mc)(struct realtek_priv *priv, u32 index,
const struct rtl8366_vlan_mc *vlanmc);
int (*get_vlan_4k)(struct realtek_smi *smi, u32 vid,
int (*get_vlan_4k)(struct realtek_priv *priv, u32 vid,
struct rtl8366_vlan_4k *vlan4k);
int (*set_vlan_4k)(struct realtek_smi *smi,
int (*set_vlan_4k)(struct realtek_priv *priv,
const struct rtl8366_vlan_4k *vlan4k);
int (*get_mc_index)(struct realtek_smi *smi, int port, int *val);
int (*set_mc_index)(struct realtek_smi *smi, int port, int index);
bool (*is_vlan_valid)(struct realtek_smi *smi, unsigned int vlan);
int (*enable_vlan)(struct realtek_smi *smi, bool enable);
int (*enable_vlan4k)(struct realtek_smi *smi, bool enable);
int (*enable_port)(struct realtek_smi *smi, int port, bool enable);
int (*phy_read)(struct realtek_smi *smi, int phy, int regnum);
int (*phy_write)(struct realtek_smi *smi, int phy, int regnum,
int (*get_mc_index)(struct realtek_priv *priv, int port, int *val);
int (*set_mc_index)(struct realtek_priv *priv, int port, int index);
bool (*is_vlan_valid)(struct realtek_priv *priv, unsigned int vlan);
int (*enable_vlan)(struct realtek_priv *priv, bool enable);
int (*enable_vlan4k)(struct realtek_priv *priv, bool enable);
int (*enable_port)(struct realtek_priv *priv, int port, bool enable);
int (*phy_read)(struct realtek_priv *priv, int phy, int regnum);
int (*phy_write)(struct realtek_priv *priv, int phy, int regnum,
u16 val);
};
struct realtek_smi_variant {
const struct dsa_switch_ops *ds_ops;
const struct realtek_smi_ops *ops;
struct realtek_variant {
const struct dsa_switch_ops *ds_ops_smi;
const struct dsa_switch_ops *ds_ops_mdio;
const struct realtek_ops *ops;
unsigned int clk_delay;
u8 cmd_read;
u8 cmd_write;
size_t chip_data_sz;
};
/* SMI core calls */
int realtek_smi_write_reg_noack(struct realtek_smi *smi, u32 addr,
u32 data);
int realtek_smi_setup_mdio(struct realtek_smi *smi);
/* RTL8366 library helpers */
int rtl8366_mc_is_used(struct realtek_smi *smi, int mc_index, int *used);
int rtl8366_set_vlan(struct realtek_smi *smi, int vid, u32 member,
int rtl8366_mc_is_used(struct realtek_priv *priv, int mc_index, int *used);
int rtl8366_set_vlan(struct realtek_priv *priv, int vid, u32 member,
u32 untag, u32 fid);
int rtl8366_set_pvid(struct realtek_smi *smi, unsigned int port,
int rtl8366_set_pvid(struct realtek_priv *priv, unsigned int port,
unsigned int vid);
int rtl8366_enable_vlan4k(struct realtek_smi *smi, bool enable);
int rtl8366_enable_vlan(struct realtek_smi *smi, bool enable);
int rtl8366_reset_vlan(struct realtek_smi *smi);
int rtl8366_enable_vlan4k(struct realtek_priv *priv, bool enable);
int rtl8366_enable_vlan(struct realtek_priv *priv, bool enable);
int rtl8366_reset_vlan(struct realtek_priv *priv);
int rtl8366_vlan_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack);
@ -139,7 +139,7 @@ void rtl8366_get_strings(struct dsa_switch *ds, int port, u32 stringset,
int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset);
void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data);
extern const struct realtek_smi_variant rtl8366rb_variant;
extern const struct realtek_smi_variant rtl8365mb_variant;
extern const struct realtek_variant rtl8366rb_variant;
extern const struct realtek_variant rtl8365mb_variant;
#endif /* _REALTEK_SMI_H */

View File

@ -11,18 +11,18 @@
#include <linux/if_bridge.h>
#include <net/dsa.h>
#include "realtek-smi-core.h"
#include "realtek.h"
int rtl8366_mc_is_used(struct realtek_smi *smi, int mc_index, int *used)
int rtl8366_mc_is_used(struct realtek_priv *priv, int mc_index, int *used)
{
int ret;
int i;
*used = 0;
for (i = 0; i < smi->num_ports; i++) {
for (i = 0; i < priv->num_ports; i++) {
int index = 0;
ret = smi->ops->get_mc_index(smi, i, &index);
ret = priv->ops->get_mc_index(priv, i, &index);
if (ret)
return ret;
@ -38,13 +38,13 @@ EXPORT_SYMBOL_GPL(rtl8366_mc_is_used);
/**
* rtl8366_obtain_mc() - retrieve or allocate a VLAN member configuration
* @smi: the Realtek SMI device instance
* @priv: the Realtek SMI device instance
* @vid: the VLAN ID to look up or allocate
* @vlanmc: the pointer will be assigned to a pointer to a valid member config
* if successful
* @return: index of a new member config or negative error number
*/
static int rtl8366_obtain_mc(struct realtek_smi *smi, int vid,
static int rtl8366_obtain_mc(struct realtek_priv *priv, int vid,
struct rtl8366_vlan_mc *vlanmc)
{
struct rtl8366_vlan_4k vlan4k;
@ -52,10 +52,10 @@ static int rtl8366_obtain_mc(struct realtek_smi *smi, int vid,
int i;
/* Try to find an existing member config entry for this VID */
for (i = 0; i < smi->num_vlan_mc; i++) {
ret = smi->ops->get_vlan_mc(smi, i, vlanmc);
for (i = 0; i < priv->num_vlan_mc; i++) {
ret = priv->ops->get_vlan_mc(priv, i, vlanmc);
if (ret) {
dev_err(smi->dev, "error searching for VLAN MC %d for VID %d\n",
dev_err(priv->dev, "error searching for VLAN MC %d for VID %d\n",
i, vid);
return ret;
}
@ -65,19 +65,19 @@ static int rtl8366_obtain_mc(struct realtek_smi *smi, int vid,
}
/* We have no MC entry for this VID, try to find an empty one */
for (i = 0; i < smi->num_vlan_mc; i++) {
ret = smi->ops->get_vlan_mc(smi, i, vlanmc);
for (i = 0; i < priv->num_vlan_mc; i++) {
ret = priv->ops->get_vlan_mc(priv, i, vlanmc);
if (ret) {
dev_err(smi->dev, "error searching for VLAN MC %d for VID %d\n",
dev_err(priv->dev, "error searching for VLAN MC %d for VID %d\n",
i, vid);
return ret;
}
if (vlanmc->vid == 0 && vlanmc->member == 0) {
/* Update the entry from the 4K table */
ret = smi->ops->get_vlan_4k(smi, vid, &vlan4k);
ret = priv->ops->get_vlan_4k(priv, vid, &vlan4k);
if (ret) {
dev_err(smi->dev, "error looking for 4K VLAN MC %d for VID %d\n",
dev_err(priv->dev, "error looking for 4K VLAN MC %d for VID %d\n",
i, vid);
return ret;
}
@ -86,30 +86,30 @@ static int rtl8366_obtain_mc(struct realtek_smi *smi, int vid,
vlanmc->member = vlan4k.member;
vlanmc->untag = vlan4k.untag;
vlanmc->fid = vlan4k.fid;
ret = smi->ops->set_vlan_mc(smi, i, vlanmc);
ret = priv->ops->set_vlan_mc(priv, i, vlanmc);
if (ret) {
dev_err(smi->dev, "unable to set/update VLAN MC %d for VID %d\n",
dev_err(priv->dev, "unable to set/update VLAN MC %d for VID %d\n",
i, vid);
return ret;
}
dev_dbg(smi->dev, "created new MC at index %d for VID %d\n",
dev_dbg(priv->dev, "created new MC at index %d for VID %d\n",
i, vid);
return i;
}
}
/* MC table is full, try to find an unused entry and replace it */
for (i = 0; i < smi->num_vlan_mc; i++) {
for (i = 0; i < priv->num_vlan_mc; i++) {
int used;
ret = rtl8366_mc_is_used(smi, i, &used);
ret = rtl8366_mc_is_used(priv, i, &used);
if (ret)
return ret;
if (!used) {
/* Update the entry from the 4K table */
ret = smi->ops->get_vlan_4k(smi, vid, &vlan4k);
ret = priv->ops->get_vlan_4k(priv, vid, &vlan4k);
if (ret)
return ret;
@ -117,23 +117,23 @@ static int rtl8366_obtain_mc(struct realtek_smi *smi, int vid,
vlanmc->member = vlan4k.member;
vlanmc->untag = vlan4k.untag;
vlanmc->fid = vlan4k.fid;
ret = smi->ops->set_vlan_mc(smi, i, vlanmc);
ret = priv->ops->set_vlan_mc(priv, i, vlanmc);
if (ret) {
dev_err(smi->dev, "unable to set/update VLAN MC %d for VID %d\n",
dev_err(priv->dev, "unable to set/update VLAN MC %d for VID %d\n",
i, vid);
return ret;
}
dev_dbg(smi->dev, "recycled MC at index %i for VID %d\n",
dev_dbg(priv->dev, "recycled MC at index %i for VID %d\n",
i, vid);
return i;
}
}
dev_err(smi->dev, "all VLAN member configurations are in use\n");
dev_err(priv->dev, "all VLAN member configurations are in use\n");
return -ENOSPC;
}
int rtl8366_set_vlan(struct realtek_smi *smi, int vid, u32 member,
int rtl8366_set_vlan(struct realtek_priv *priv, int vid, u32 member,
u32 untag, u32 fid)
{
struct rtl8366_vlan_mc vlanmc;
@ -141,31 +141,31 @@ int rtl8366_set_vlan(struct realtek_smi *smi, int vid, u32 member,
int mc;
int ret;
if (!smi->ops->is_vlan_valid(smi, vid))
if (!priv->ops->is_vlan_valid(priv, vid))
return -EINVAL;
dev_dbg(smi->dev,
dev_dbg(priv->dev,
"setting VLAN%d 4k members: 0x%02x, untagged: 0x%02x\n",
vid, member, untag);
/* Update the 4K table */
ret = smi->ops->get_vlan_4k(smi, vid, &vlan4k);
ret = priv->ops->get_vlan_4k(priv, vid, &vlan4k);
if (ret)
return ret;
vlan4k.member |= member;
vlan4k.untag |= untag;
vlan4k.fid = fid;
ret = smi->ops->set_vlan_4k(smi, &vlan4k);
ret = priv->ops->set_vlan_4k(priv, &vlan4k);
if (ret)
return ret;
dev_dbg(smi->dev,
dev_dbg(priv->dev,
"resulting VLAN%d 4k members: 0x%02x, untagged: 0x%02x\n",
vid, vlan4k.member, vlan4k.untag);
/* Find or allocate a member config for this VID */
ret = rtl8366_obtain_mc(smi, vid, &vlanmc);
ret = rtl8366_obtain_mc(priv, vid, &vlanmc);
if (ret < 0)
return ret;
mc = ret;
@ -176,12 +176,12 @@ int rtl8366_set_vlan(struct realtek_smi *smi, int vid, u32 member,
vlanmc.fid = fid;
/* Commit updates to the MC entry */
ret = smi->ops->set_vlan_mc(smi, mc, &vlanmc);
ret = priv->ops->set_vlan_mc(priv, mc, &vlanmc);
if (ret)
dev_err(smi->dev, "failed to commit changes to VLAN MC index %d for VID %d\n",
dev_err(priv->dev, "failed to commit changes to VLAN MC index %d for VID %d\n",
mc, vid);
else
dev_dbg(smi->dev,
dev_dbg(priv->dev,
"resulting VLAN%d MC members: 0x%02x, untagged: 0x%02x\n",
vid, vlanmc.member, vlanmc.untag);
@ -189,37 +189,37 @@ int rtl8366_set_vlan(struct realtek_smi *smi, int vid, u32 member,
}
EXPORT_SYMBOL_GPL(rtl8366_set_vlan);
int rtl8366_set_pvid(struct realtek_smi *smi, unsigned int port,
int rtl8366_set_pvid(struct realtek_priv *priv, unsigned int port,
unsigned int vid)
{
struct rtl8366_vlan_mc vlanmc;
int mc;
int ret;
if (!smi->ops->is_vlan_valid(smi, vid))
if (!priv->ops->is_vlan_valid(priv, vid))
return -EINVAL;
/* Find or allocate a member config for this VID */
ret = rtl8366_obtain_mc(smi, vid, &vlanmc);
ret = rtl8366_obtain_mc(priv, vid, &vlanmc);
if (ret < 0)
return ret;
mc = ret;
ret = smi->ops->set_mc_index(smi, port, mc);
ret = priv->ops->set_mc_index(priv, port, mc);
if (ret) {
dev_err(smi->dev, "set PVID: failed to set MC index %d for port %d\n",
dev_err(priv->dev, "set PVID: failed to set MC index %d for port %d\n",
mc, port);
return ret;
}
dev_dbg(smi->dev, "set PVID: the PVID for port %d set to %d using existing MC index %d\n",
dev_dbg(priv->dev, "set PVID: the PVID for port %d set to %d using existing MC index %d\n",
port, vid, mc);
return 0;
}
EXPORT_SYMBOL_GPL(rtl8366_set_pvid);
int rtl8366_enable_vlan4k(struct realtek_smi *smi, bool enable)
int rtl8366_enable_vlan4k(struct realtek_priv *priv, bool enable)
{
int ret;
@ -229,52 +229,52 @@ int rtl8366_enable_vlan4k(struct realtek_smi *smi, bool enable)
*/
if (enable) {
/* Make sure VLAN is ON */
ret = smi->ops->enable_vlan(smi, true);
ret = priv->ops->enable_vlan(priv, true);
if (ret)
return ret;
smi->vlan_enabled = true;
priv->vlan_enabled = true;
}
ret = smi->ops->enable_vlan4k(smi, enable);
ret = priv->ops->enable_vlan4k(priv, enable);
if (ret)
return ret;
smi->vlan4k_enabled = enable;
priv->vlan4k_enabled = enable;
return 0;
}
EXPORT_SYMBOL_GPL(rtl8366_enable_vlan4k);
int rtl8366_enable_vlan(struct realtek_smi *smi, bool enable)
int rtl8366_enable_vlan(struct realtek_priv *priv, bool enable)
{
int ret;
ret = smi->ops->enable_vlan(smi, enable);
ret = priv->ops->enable_vlan(priv, enable);
if (ret)
return ret;
smi->vlan_enabled = enable;
priv->vlan_enabled = enable;
/* If we turn VLAN off, make sure that we turn off
* 4k VLAN as well, if that happened to be on.
*/
if (!enable) {
smi->vlan4k_enabled = false;
ret = smi->ops->enable_vlan4k(smi, false);
priv->vlan4k_enabled = false;
ret = priv->ops->enable_vlan4k(priv, false);
}
return ret;
}
EXPORT_SYMBOL_GPL(rtl8366_enable_vlan);
int rtl8366_reset_vlan(struct realtek_smi *smi)
int rtl8366_reset_vlan(struct realtek_priv *priv)
{
struct rtl8366_vlan_mc vlanmc;
int ret;
int i;
rtl8366_enable_vlan(smi, false);
rtl8366_enable_vlan4k(smi, false);
rtl8366_enable_vlan(priv, false);
rtl8366_enable_vlan4k(priv, false);
/* Clear the 16 VLAN member configurations */
vlanmc.vid = 0;
@ -282,8 +282,8 @@ int rtl8366_reset_vlan(struct realtek_smi *smi)
vlanmc.member = 0;
vlanmc.untag = 0;
vlanmc.fid = 0;
for (i = 0; i < smi->num_vlan_mc; i++) {
ret = smi->ops->set_vlan_mc(smi, i, &vlanmc);
for (i = 0; i < priv->num_vlan_mc; i++) {
ret = priv->ops->set_vlan_mc(priv, i, &vlanmc);
if (ret)
return ret;
}
@ -298,12 +298,12 @@ int rtl8366_vlan_add(struct dsa_switch *ds, int port,
{
bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID);
struct realtek_smi *smi = ds->priv;
struct realtek_priv *priv = ds->priv;
u32 member = 0;
u32 untag = 0;
int ret;
if (!smi->ops->is_vlan_valid(smi, vlan->vid)) {
if (!priv->ops->is_vlan_valid(priv, vlan->vid)) {
NL_SET_ERR_MSG_MOD(extack, "VLAN ID not valid");
return -EINVAL;
}
@ -312,13 +312,13 @@ int rtl8366_vlan_add(struct dsa_switch *ds, int port,
* FIXME: what's with this 4k business?
* Just rtl8366_enable_vlan() seems inconclusive.
*/
ret = rtl8366_enable_vlan4k(smi, true);
ret = rtl8366_enable_vlan4k(priv, true);
if (ret) {
NL_SET_ERR_MSG_MOD(extack, "Failed to enable VLAN 4K");
return ret;
}
dev_dbg(smi->dev, "add VLAN %d on port %d, %s, %s\n",
dev_dbg(priv->dev, "add VLAN %d on port %d, %s, %s\n",
vlan->vid, port, untagged ? "untagged" : "tagged",
pvid ? "PVID" : "no PVID");
@ -327,18 +327,18 @@ int rtl8366_vlan_add(struct dsa_switch *ds, int port,
if (untagged)
untag |= BIT(port);
ret = rtl8366_set_vlan(smi, vlan->vid, member, untag, 0);
ret = rtl8366_set_vlan(priv, vlan->vid, member, untag, 0);
if (ret) {
dev_err(smi->dev, "failed to set up VLAN %04x", vlan->vid);
dev_err(priv->dev, "failed to set up VLAN %04x", vlan->vid);
return ret;
}
if (!pvid)
return 0;
ret = rtl8366_set_pvid(smi, port, vlan->vid);
ret = rtl8366_set_pvid(priv, port, vlan->vid);
if (ret) {
dev_err(smi->dev, "failed to set PVID on port %d to VLAN %04x",
dev_err(priv->dev, "failed to set PVID on port %d to VLAN %04x",
port, vlan->vid);
return ret;
}
@ -350,15 +350,15 @@ EXPORT_SYMBOL_GPL(rtl8366_vlan_add);
int rtl8366_vlan_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
struct realtek_smi *smi = ds->priv;
struct realtek_priv *priv = ds->priv;
int ret, i;
dev_dbg(smi->dev, "del VLAN %d on port %d\n", vlan->vid, port);
dev_dbg(priv->dev, "del VLAN %d on port %d\n", vlan->vid, port);
for (i = 0; i < smi->num_vlan_mc; i++) {
for (i = 0; i < priv->num_vlan_mc; i++) {
struct rtl8366_vlan_mc vlanmc;
ret = smi->ops->get_vlan_mc(smi, i, &vlanmc);
ret = priv->ops->get_vlan_mc(priv, i, &vlanmc);
if (ret)
return ret;
@ -376,9 +376,9 @@ int rtl8366_vlan_del(struct dsa_switch *ds, int port,
vlanmc.priority = 0;
vlanmc.fid = 0;
}
ret = smi->ops->set_vlan_mc(smi, i, &vlanmc);
ret = priv->ops->set_vlan_mc(priv, i, &vlanmc);
if (ret) {
dev_err(smi->dev,
dev_err(priv->dev,
"failed to remove VLAN %04x\n",
vlan->vid);
return ret;
@ -394,15 +394,15 @@ EXPORT_SYMBOL_GPL(rtl8366_vlan_del);
void rtl8366_get_strings(struct dsa_switch *ds, int port, u32 stringset,
uint8_t *data)
{
struct realtek_smi *smi = ds->priv;
struct realtek_priv *priv = ds->priv;
struct rtl8366_mib_counter *mib;
int i;
if (port >= smi->num_ports)
if (port >= priv->num_ports)
return;
for (i = 0; i < smi->num_mib_counters; i++) {
mib = &smi->mib_counters[i];
for (i = 0; i < priv->num_mib_counters; i++) {
mib = &priv->mib_counters[i];
strncpy(data + i * ETH_GSTRING_LEN,
mib->name, ETH_GSTRING_LEN);
}
@ -411,35 +411,35 @@ EXPORT_SYMBOL_GPL(rtl8366_get_strings);
int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
struct realtek_smi *smi = ds->priv;
struct realtek_priv *priv = ds->priv;
/* We only support SS_STATS */
if (sset != ETH_SS_STATS)
return 0;
if (port >= smi->num_ports)
if (port >= priv->num_ports)
return -EINVAL;
return smi->num_mib_counters;
return priv->num_mib_counters;
}
EXPORT_SYMBOL_GPL(rtl8366_get_sset_count);
void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
{
struct realtek_smi *smi = ds->priv;
struct realtek_priv *priv = ds->priv;
int i;
int ret;
if (port >= smi->num_ports)
if (port >= priv->num_ports)
return;
for (i = 0; i < smi->num_mib_counters; i++) {
for (i = 0; i < priv->num_mib_counters; i++) {
struct rtl8366_mib_counter *mib;
u64 mibvalue = 0;
mib = &smi->mib_counters[i];
ret = smi->ops->get_mib_counter(smi, port, mib, &mibvalue);
mib = &priv->mib_counters[i];
ret = priv->ops->get_mib_counter(priv, port, mib, &mibvalue);
if (ret) {
dev_err(smi->dev, "error reading MIB counter %s\n",
dev_err(priv->dev, "error reading MIB counter %s\n",
mib->name);
}
data[i] = mibvalue;