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