1aac94001SLuiz Angelo Daros de Luca // SPDX-License-Identifier: GPL-2.0+ 2aac94001SLuiz Angelo Daros de Luca /* Realtek MDIO interface driver 3aac94001SLuiz Angelo Daros de Luca * 4aac94001SLuiz Angelo Daros de Luca * ASICs we intend to support with this driver: 5aac94001SLuiz Angelo Daros de Luca * 6aac94001SLuiz Angelo Daros de Luca * RTL8366 - The original version, apparently 7aac94001SLuiz Angelo Daros de Luca * RTL8369 - Similar enough to have the same datsheet as RTL8366 8aac94001SLuiz Angelo Daros de Luca * RTL8366RB - Probably reads out "RTL8366 revision B", has a quite 9aac94001SLuiz Angelo Daros de Luca * different register layout from the other two 10aac94001SLuiz Angelo Daros de Luca * RTL8366S - Is this "RTL8366 super"? 11aac94001SLuiz Angelo Daros de Luca * RTL8367 - Has an OpenWRT driver as well 12aac94001SLuiz Angelo Daros de Luca * RTL8368S - Seems to be an alternative name for RTL8366RB 13aac94001SLuiz Angelo Daros de Luca * RTL8370 - Also uses SMI 14aac94001SLuiz Angelo Daros de Luca * 15aac94001SLuiz Angelo Daros de Luca * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> 16aac94001SLuiz Angelo Daros de Luca * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com> 17aac94001SLuiz Angelo Daros de Luca * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv> 18aac94001SLuiz Angelo Daros de Luca * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com> 19aac94001SLuiz Angelo Daros de Luca * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> 20aac94001SLuiz Angelo Daros de Luca */ 21aac94001SLuiz Angelo Daros de Luca 22aac94001SLuiz Angelo Daros de Luca #include <linux/module.h> 23aac94001SLuiz Angelo Daros de Luca #include <linux/of_device.h> 24aac94001SLuiz Angelo Daros de Luca #include <linux/regmap.h> 25aac94001SLuiz Angelo Daros de Luca 26aac94001SLuiz Angelo Daros de Luca #include "realtek.h" 27aac94001SLuiz Angelo Daros de Luca 28aac94001SLuiz Angelo Daros de Luca /* Read/write via mdiobus */ 29aac94001SLuiz Angelo Daros de Luca #define REALTEK_MDIO_CTRL0_REG 31 30aac94001SLuiz Angelo Daros de Luca #define REALTEK_MDIO_START_REG 29 31aac94001SLuiz Angelo Daros de Luca #define REALTEK_MDIO_CTRL1_REG 21 32aac94001SLuiz Angelo Daros de Luca #define REALTEK_MDIO_ADDRESS_REG 23 33aac94001SLuiz Angelo Daros de Luca #define REALTEK_MDIO_DATA_WRITE_REG 24 34aac94001SLuiz Angelo Daros de Luca #define REALTEK_MDIO_DATA_READ_REG 25 35aac94001SLuiz Angelo Daros de Luca 36aac94001SLuiz Angelo Daros de Luca #define REALTEK_MDIO_START_OP 0xFFFF 37aac94001SLuiz Angelo Daros de Luca #define REALTEK_MDIO_ADDR_OP 0x000E 38aac94001SLuiz Angelo Daros de Luca #define REALTEK_MDIO_READ_OP 0x0001 39aac94001SLuiz Angelo Daros de Luca #define REALTEK_MDIO_WRITE_OP 0x0003 40aac94001SLuiz Angelo Daros de Luca 41aac94001SLuiz Angelo Daros de Luca static int realtek_mdio_write(void *ctx, u32 reg, u32 val) 42aac94001SLuiz Angelo Daros de Luca { 43aac94001SLuiz Angelo Daros de Luca struct realtek_priv *priv = ctx; 44aac94001SLuiz Angelo Daros de Luca struct mii_bus *bus = priv->bus; 45aac94001SLuiz Angelo Daros de Luca int ret; 46aac94001SLuiz Angelo Daros de Luca 47aac94001SLuiz Angelo Daros de Luca mutex_lock(&bus->mdio_lock); 48aac94001SLuiz Angelo Daros de Luca 49aac94001SLuiz Angelo Daros de Luca ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL0_REG, REALTEK_MDIO_ADDR_OP); 50aac94001SLuiz Angelo Daros de Luca if (ret) 51aac94001SLuiz Angelo Daros de Luca goto out_unlock; 52aac94001SLuiz Angelo Daros de Luca 53aac94001SLuiz Angelo Daros de Luca ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_ADDRESS_REG, reg); 54aac94001SLuiz Angelo Daros de Luca if (ret) 55aac94001SLuiz Angelo Daros de Luca goto out_unlock; 56aac94001SLuiz Angelo Daros de Luca 57aac94001SLuiz Angelo Daros de Luca ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_DATA_WRITE_REG, val); 58aac94001SLuiz Angelo Daros de Luca if (ret) 59aac94001SLuiz Angelo Daros de Luca goto out_unlock; 60aac94001SLuiz Angelo Daros de Luca 61aac94001SLuiz Angelo Daros de Luca ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL1_REG, REALTEK_MDIO_WRITE_OP); 62aac94001SLuiz Angelo Daros de Luca 63aac94001SLuiz Angelo Daros de Luca out_unlock: 64aac94001SLuiz Angelo Daros de Luca mutex_unlock(&bus->mdio_lock); 65aac94001SLuiz Angelo Daros de Luca 66aac94001SLuiz Angelo Daros de Luca return ret; 67aac94001SLuiz Angelo Daros de Luca } 68aac94001SLuiz Angelo Daros de Luca 69aac94001SLuiz Angelo Daros de Luca static int realtek_mdio_read(void *ctx, u32 reg, u32 *val) 70aac94001SLuiz Angelo Daros de Luca { 71aac94001SLuiz Angelo Daros de Luca struct realtek_priv *priv = ctx; 72aac94001SLuiz Angelo Daros de Luca struct mii_bus *bus = priv->bus; 73aac94001SLuiz Angelo Daros de Luca int ret; 74aac94001SLuiz Angelo Daros de Luca 75aac94001SLuiz Angelo Daros de Luca mutex_lock(&bus->mdio_lock); 76aac94001SLuiz Angelo Daros de Luca 77aac94001SLuiz Angelo Daros de Luca ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL0_REG, REALTEK_MDIO_ADDR_OP); 78aac94001SLuiz Angelo Daros de Luca if (ret) 79aac94001SLuiz Angelo Daros de Luca goto out_unlock; 80aac94001SLuiz Angelo Daros de Luca 81aac94001SLuiz Angelo Daros de Luca ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_ADDRESS_REG, reg); 82aac94001SLuiz Angelo Daros de Luca if (ret) 83aac94001SLuiz Angelo Daros de Luca goto out_unlock; 84aac94001SLuiz Angelo Daros de Luca 85aac94001SLuiz Angelo Daros de Luca ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL1_REG, REALTEK_MDIO_READ_OP); 86aac94001SLuiz Angelo Daros de Luca if (ret) 87aac94001SLuiz Angelo Daros de Luca goto out_unlock; 88aac94001SLuiz Angelo Daros de Luca 89aac94001SLuiz Angelo Daros de Luca ret = bus->read(bus, priv->mdio_addr, REALTEK_MDIO_DATA_READ_REG); 90aac94001SLuiz Angelo Daros de Luca if (ret >= 0) { 91aac94001SLuiz Angelo Daros de Luca *val = ret; 92aac94001SLuiz Angelo Daros de Luca ret = 0; 93aac94001SLuiz Angelo Daros de Luca } 94aac94001SLuiz Angelo Daros de Luca 95aac94001SLuiz Angelo Daros de Luca out_unlock: 96aac94001SLuiz Angelo Daros de Luca mutex_unlock(&bus->mdio_lock); 97aac94001SLuiz Angelo Daros de Luca 98aac94001SLuiz Angelo Daros de Luca return ret; 99aac94001SLuiz Angelo Daros de Luca } 100aac94001SLuiz Angelo Daros de Luca 101*907e772fSAlvin Šipraga static void realtek_mdio_lock(void *ctx) 102*907e772fSAlvin Šipraga { 103*907e772fSAlvin Šipraga struct realtek_priv *priv = ctx; 104*907e772fSAlvin Šipraga 105*907e772fSAlvin Šipraga mutex_lock(&priv->map_lock); 106*907e772fSAlvin Šipraga } 107*907e772fSAlvin Šipraga 108*907e772fSAlvin Šipraga static void realtek_mdio_unlock(void *ctx) 109*907e772fSAlvin Šipraga { 110*907e772fSAlvin Šipraga struct realtek_priv *priv = ctx; 111*907e772fSAlvin Šipraga 112*907e772fSAlvin Šipraga mutex_unlock(&priv->map_lock); 113*907e772fSAlvin Šipraga } 114*907e772fSAlvin Šipraga 115aac94001SLuiz Angelo Daros de Luca static const struct regmap_config realtek_mdio_regmap_config = { 116aac94001SLuiz Angelo Daros de Luca .reg_bits = 10, /* A4..A0 R4..R0 */ 117aac94001SLuiz Angelo Daros de Luca .val_bits = 16, 118aac94001SLuiz Angelo Daros de Luca .reg_stride = 1, 119aac94001SLuiz Angelo Daros de Luca /* PHY regs are at 0x8000 */ 120aac94001SLuiz Angelo Daros de Luca .max_register = 0xffff, 121aac94001SLuiz Angelo Daros de Luca .reg_format_endian = REGMAP_ENDIAN_BIG, 122aac94001SLuiz Angelo Daros de Luca .reg_read = realtek_mdio_read, 123aac94001SLuiz Angelo Daros de Luca .reg_write = realtek_mdio_write, 124aac94001SLuiz Angelo Daros de Luca .cache_type = REGCACHE_NONE, 125*907e772fSAlvin Šipraga .lock = realtek_mdio_lock, 126*907e772fSAlvin Šipraga .unlock = realtek_mdio_unlock, 127*907e772fSAlvin Šipraga }; 128*907e772fSAlvin Šipraga 129*907e772fSAlvin Šipraga static const struct regmap_config realtek_mdio_nolock_regmap_config = { 130*907e772fSAlvin Šipraga .reg_bits = 10, /* A4..A0 R4..R0 */ 131*907e772fSAlvin Šipraga .val_bits = 16, 132*907e772fSAlvin Šipraga .reg_stride = 1, 133*907e772fSAlvin Šipraga /* PHY regs are at 0x8000 */ 134*907e772fSAlvin Šipraga .max_register = 0xffff, 135*907e772fSAlvin Šipraga .reg_format_endian = REGMAP_ENDIAN_BIG, 136*907e772fSAlvin Šipraga .reg_read = realtek_mdio_read, 137*907e772fSAlvin Šipraga .reg_write = realtek_mdio_write, 138*907e772fSAlvin Šipraga .cache_type = REGCACHE_NONE, 139*907e772fSAlvin Šipraga .disable_locking = true, 140aac94001SLuiz Angelo Daros de Luca }; 141aac94001SLuiz Angelo Daros de Luca 142aac94001SLuiz Angelo Daros de Luca static int realtek_mdio_probe(struct mdio_device *mdiodev) 143aac94001SLuiz Angelo Daros de Luca { 144aac94001SLuiz Angelo Daros de Luca struct realtek_priv *priv; 145aac94001SLuiz Angelo Daros de Luca struct device *dev = &mdiodev->dev; 146aac94001SLuiz Angelo Daros de Luca const struct realtek_variant *var; 147*907e772fSAlvin Šipraga struct regmap_config rc; 148aac94001SLuiz Angelo Daros de Luca struct device_node *np; 149*907e772fSAlvin Šipraga int ret; 150aac94001SLuiz Angelo Daros de Luca 151aac94001SLuiz Angelo Daros de Luca var = of_device_get_match_data(dev); 152aac94001SLuiz Angelo Daros de Luca if (!var) 153aac94001SLuiz Angelo Daros de Luca return -EINVAL; 154aac94001SLuiz Angelo Daros de Luca 155aac94001SLuiz Angelo Daros de Luca priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL); 156aac94001SLuiz Angelo Daros de Luca if (!priv) 157aac94001SLuiz Angelo Daros de Luca return -ENOMEM; 158aac94001SLuiz Angelo Daros de Luca 159*907e772fSAlvin Šipraga mutex_init(&priv->map_lock); 160*907e772fSAlvin Šipraga 161*907e772fSAlvin Šipraga rc = realtek_mdio_regmap_config; 162*907e772fSAlvin Šipraga rc.lock_arg = priv; 163*907e772fSAlvin Šipraga priv->map = devm_regmap_init(dev, NULL, priv, &rc); 164aac94001SLuiz Angelo Daros de Luca if (IS_ERR(priv->map)) { 165aac94001SLuiz Angelo Daros de Luca ret = PTR_ERR(priv->map); 166aac94001SLuiz Angelo Daros de Luca dev_err(dev, "regmap init failed: %d\n", ret); 167aac94001SLuiz Angelo Daros de Luca return ret; 168aac94001SLuiz Angelo Daros de Luca } 169aac94001SLuiz Angelo Daros de Luca 170*907e772fSAlvin Šipraga rc = realtek_mdio_nolock_regmap_config; 171*907e772fSAlvin Šipraga priv->map_nolock = devm_regmap_init(dev, NULL, priv, &rc); 172*907e772fSAlvin Šipraga if (IS_ERR(priv->map_nolock)) { 173*907e772fSAlvin Šipraga ret = PTR_ERR(priv->map_nolock); 174*907e772fSAlvin Šipraga dev_err(dev, "regmap init failed: %d\n", ret); 175*907e772fSAlvin Šipraga return ret; 176*907e772fSAlvin Šipraga } 177*907e772fSAlvin Šipraga 178aac94001SLuiz Angelo Daros de Luca priv->mdio_addr = mdiodev->addr; 179aac94001SLuiz Angelo Daros de Luca priv->bus = mdiodev->bus; 180aac94001SLuiz Angelo Daros de Luca priv->dev = &mdiodev->dev; 181aac94001SLuiz Angelo Daros de Luca priv->chip_data = (void *)priv + sizeof(*priv); 182aac94001SLuiz Angelo Daros de Luca 183aac94001SLuiz Angelo Daros de Luca priv->clk_delay = var->clk_delay; 184aac94001SLuiz Angelo Daros de Luca priv->cmd_read = var->cmd_read; 185aac94001SLuiz Angelo Daros de Luca priv->cmd_write = var->cmd_write; 186aac94001SLuiz Angelo Daros de Luca priv->ops = var->ops; 187aac94001SLuiz Angelo Daros de Luca 188aac94001SLuiz Angelo Daros de Luca priv->write_reg_noack = realtek_mdio_write; 189aac94001SLuiz Angelo Daros de Luca 190aac94001SLuiz Angelo Daros de Luca np = dev->of_node; 191aac94001SLuiz Angelo Daros de Luca 192aac94001SLuiz Angelo Daros de Luca dev_set_drvdata(dev, priv); 193aac94001SLuiz Angelo Daros de Luca 194aac94001SLuiz Angelo Daros de Luca /* TODO: if power is software controlled, set up any regulators here */ 195aac94001SLuiz Angelo Daros de Luca priv->leds_disabled = of_property_read_bool(np, "realtek,disable-leds"); 196aac94001SLuiz Angelo Daros de Luca 19705f7b042SLuiz Angelo Daros de Luca priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); 19805f7b042SLuiz Angelo Daros de Luca if (IS_ERR(priv->reset)) { 19905f7b042SLuiz Angelo Daros de Luca dev_err(dev, "failed to get RESET GPIO\n"); 20005f7b042SLuiz Angelo Daros de Luca return PTR_ERR(priv->reset); 20105f7b042SLuiz Angelo Daros de Luca } 20205f7b042SLuiz Angelo Daros de Luca 20305f7b042SLuiz Angelo Daros de Luca if (priv->reset) { 20405f7b042SLuiz Angelo Daros de Luca gpiod_set_value(priv->reset, 1); 20505f7b042SLuiz Angelo Daros de Luca dev_dbg(dev, "asserted RESET\n"); 20605f7b042SLuiz Angelo Daros de Luca msleep(REALTEK_HW_STOP_DELAY); 20705f7b042SLuiz Angelo Daros de Luca gpiod_set_value(priv->reset, 0); 20805f7b042SLuiz Angelo Daros de Luca msleep(REALTEK_HW_START_DELAY); 20905f7b042SLuiz Angelo Daros de Luca dev_dbg(dev, "deasserted RESET\n"); 21005f7b042SLuiz Angelo Daros de Luca } 21105f7b042SLuiz Angelo Daros de Luca 212aac94001SLuiz Angelo Daros de Luca ret = priv->ops->detect(priv); 213aac94001SLuiz Angelo Daros de Luca if (ret) { 214aac94001SLuiz Angelo Daros de Luca dev_err(dev, "unable to detect switch\n"); 215aac94001SLuiz Angelo Daros de Luca return ret; 216aac94001SLuiz Angelo Daros de Luca } 217aac94001SLuiz Angelo Daros de Luca 218aac94001SLuiz Angelo Daros de Luca priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL); 219aac94001SLuiz Angelo Daros de Luca if (!priv->ds) 220aac94001SLuiz Angelo Daros de Luca return -ENOMEM; 221aac94001SLuiz Angelo Daros de Luca 222aac94001SLuiz Angelo Daros de Luca priv->ds->dev = dev; 223aac94001SLuiz Angelo Daros de Luca priv->ds->num_ports = priv->num_ports; 224aac94001SLuiz Angelo Daros de Luca priv->ds->priv = priv; 225aac94001SLuiz Angelo Daros de Luca priv->ds->ops = var->ds_ops_mdio; 226aac94001SLuiz Angelo Daros de Luca 227aac94001SLuiz Angelo Daros de Luca ret = dsa_register_switch(priv->ds); 228aac94001SLuiz Angelo Daros de Luca if (ret) { 229aac94001SLuiz Angelo Daros de Luca dev_err(priv->dev, "unable to register switch ret = %d\n", ret); 230aac94001SLuiz Angelo Daros de Luca return ret; 231aac94001SLuiz Angelo Daros de Luca } 232aac94001SLuiz Angelo Daros de Luca 233aac94001SLuiz Angelo Daros de Luca return 0; 234aac94001SLuiz Angelo Daros de Luca } 235aac94001SLuiz Angelo Daros de Luca 236aac94001SLuiz Angelo Daros de Luca static void realtek_mdio_remove(struct mdio_device *mdiodev) 237aac94001SLuiz Angelo Daros de Luca { 238aac94001SLuiz Angelo Daros de Luca struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev); 239aac94001SLuiz Angelo Daros de Luca 240aac94001SLuiz Angelo Daros de Luca if (!priv) 241aac94001SLuiz Angelo Daros de Luca return; 242aac94001SLuiz Angelo Daros de Luca 243aac94001SLuiz Angelo Daros de Luca dsa_unregister_switch(priv->ds); 244aac94001SLuiz Angelo Daros de Luca 24505f7b042SLuiz Angelo Daros de Luca /* leave the device reset asserted */ 24605f7b042SLuiz Angelo Daros de Luca if (priv->reset) 24705f7b042SLuiz Angelo Daros de Luca gpiod_set_value(priv->reset, 1); 24805f7b042SLuiz Angelo Daros de Luca 249aac94001SLuiz Angelo Daros de Luca dev_set_drvdata(&mdiodev->dev, NULL); 250aac94001SLuiz Angelo Daros de Luca } 251aac94001SLuiz Angelo Daros de Luca 252aac94001SLuiz Angelo Daros de Luca static void realtek_mdio_shutdown(struct mdio_device *mdiodev) 253aac94001SLuiz Angelo Daros de Luca { 254aac94001SLuiz Angelo Daros de Luca struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev); 255aac94001SLuiz Angelo Daros de Luca 256aac94001SLuiz Angelo Daros de Luca if (!priv) 257aac94001SLuiz Angelo Daros de Luca return; 258aac94001SLuiz Angelo Daros de Luca 259aac94001SLuiz Angelo Daros de Luca dsa_switch_shutdown(priv->ds); 260aac94001SLuiz Angelo Daros de Luca 261aac94001SLuiz Angelo Daros de Luca dev_set_drvdata(&mdiodev->dev, NULL); 262aac94001SLuiz Angelo Daros de Luca } 263aac94001SLuiz Angelo Daros de Luca 264aac94001SLuiz Angelo Daros de Luca static const struct of_device_id realtek_mdio_of_match[] = { 265aac94001SLuiz Angelo Daros de Luca #if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB) 266aac94001SLuiz Angelo Daros de Luca { .compatible = "realtek,rtl8366rb", .data = &rtl8366rb_variant, }, 267aac94001SLuiz Angelo Daros de Luca #endif 268aac94001SLuiz Angelo Daros de Luca #if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8365MB) 269aac94001SLuiz Angelo Daros de Luca { .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, }, 270d40f607cSLuiz Angelo Daros de Luca { .compatible = "realtek,rtl8367s", .data = &rtl8365mb_variant, }, 271aac94001SLuiz Angelo Daros de Luca #endif 272aac94001SLuiz Angelo Daros de Luca { /* sentinel */ }, 273aac94001SLuiz Angelo Daros de Luca }; 274aac94001SLuiz Angelo Daros de Luca MODULE_DEVICE_TABLE(of, realtek_mdio_of_match); 275aac94001SLuiz Angelo Daros de Luca 276aac94001SLuiz Angelo Daros de Luca static struct mdio_driver realtek_mdio_driver = { 277aac94001SLuiz Angelo Daros de Luca .mdiodrv.driver = { 278aac94001SLuiz Angelo Daros de Luca .name = "realtek-mdio", 279aac94001SLuiz Angelo Daros de Luca .of_match_table = of_match_ptr(realtek_mdio_of_match), 280aac94001SLuiz Angelo Daros de Luca }, 281aac94001SLuiz Angelo Daros de Luca .probe = realtek_mdio_probe, 282aac94001SLuiz Angelo Daros de Luca .remove = realtek_mdio_remove, 283aac94001SLuiz Angelo Daros de Luca .shutdown = realtek_mdio_shutdown, 284aac94001SLuiz Angelo Daros de Luca }; 285aac94001SLuiz Angelo Daros de Luca 286aac94001SLuiz Angelo Daros de Luca mdio_module_driver(realtek_mdio_driver); 287aac94001SLuiz Angelo Daros de Luca 288aac94001SLuiz Angelo Daros de Luca MODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>"); 289aac94001SLuiz Angelo Daros de Luca MODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via MDIO interface"); 290aac94001SLuiz Angelo Daros de Luca MODULE_LICENSE("GPL"); 291