162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/* Realtek MDIO interface driver
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * ASICs we intend to support with this driver:
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * RTL8366   - The original version, apparently
762306a36Sopenharmony_ci * RTL8369   - Similar enough to have the same datsheet as RTL8366
862306a36Sopenharmony_ci * RTL8366RB - Probably reads out "RTL8366 revision B", has a quite
962306a36Sopenharmony_ci *             different register layout from the other two
1062306a36Sopenharmony_ci * RTL8366S  - Is this "RTL8366 super"?
1162306a36Sopenharmony_ci * RTL8367   - Has an OpenWRT driver as well
1262306a36Sopenharmony_ci * RTL8368S  - Seems to be an alternative name for RTL8366RB
1362306a36Sopenharmony_ci * RTL8370   - Also uses SMI
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
1662306a36Sopenharmony_ci * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
1762306a36Sopenharmony_ci * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
1862306a36Sopenharmony_ci * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
1962306a36Sopenharmony_ci * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
2062306a36Sopenharmony_ci */
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <linux/module.h>
2362306a36Sopenharmony_ci#include <linux/of.h>
2462306a36Sopenharmony_ci#include <linux/overflow.h>
2562306a36Sopenharmony_ci#include <linux/regmap.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include "realtek.h"
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/* Read/write via mdiobus */
3062306a36Sopenharmony_ci#define REALTEK_MDIO_CTRL0_REG		31
3162306a36Sopenharmony_ci#define REALTEK_MDIO_START_REG		29
3262306a36Sopenharmony_ci#define REALTEK_MDIO_CTRL1_REG		21
3362306a36Sopenharmony_ci#define REALTEK_MDIO_ADDRESS_REG	23
3462306a36Sopenharmony_ci#define REALTEK_MDIO_DATA_WRITE_REG	24
3562306a36Sopenharmony_ci#define REALTEK_MDIO_DATA_READ_REG	25
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define REALTEK_MDIO_START_OP		0xFFFF
3862306a36Sopenharmony_ci#define REALTEK_MDIO_ADDR_OP		0x000E
3962306a36Sopenharmony_ci#define REALTEK_MDIO_READ_OP		0x0001
4062306a36Sopenharmony_ci#define REALTEK_MDIO_WRITE_OP		0x0003
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int realtek_mdio_write(void *ctx, u32 reg, u32 val)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct realtek_priv *priv = ctx;
4562306a36Sopenharmony_ci	struct mii_bus *bus = priv->bus;
4662306a36Sopenharmony_ci	int ret;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	mutex_lock(&bus->mdio_lock);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL0_REG, REALTEK_MDIO_ADDR_OP);
5162306a36Sopenharmony_ci	if (ret)
5262306a36Sopenharmony_ci		goto out_unlock;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_ADDRESS_REG, reg);
5562306a36Sopenharmony_ci	if (ret)
5662306a36Sopenharmony_ci		goto out_unlock;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_DATA_WRITE_REG, val);
5962306a36Sopenharmony_ci	if (ret)
6062306a36Sopenharmony_ci		goto out_unlock;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL1_REG, REALTEK_MDIO_WRITE_OP);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ciout_unlock:
6562306a36Sopenharmony_ci	mutex_unlock(&bus->mdio_lock);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return ret;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic int realtek_mdio_read(void *ctx, u32 reg, u32 *val)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct realtek_priv *priv = ctx;
7362306a36Sopenharmony_ci	struct mii_bus *bus = priv->bus;
7462306a36Sopenharmony_ci	int ret;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	mutex_lock(&bus->mdio_lock);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL0_REG, REALTEK_MDIO_ADDR_OP);
7962306a36Sopenharmony_ci	if (ret)
8062306a36Sopenharmony_ci		goto out_unlock;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_ADDRESS_REG, reg);
8362306a36Sopenharmony_ci	if (ret)
8462306a36Sopenharmony_ci		goto out_unlock;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL1_REG, REALTEK_MDIO_READ_OP);
8762306a36Sopenharmony_ci	if (ret)
8862306a36Sopenharmony_ci		goto out_unlock;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	ret = bus->read(bus, priv->mdio_addr, REALTEK_MDIO_DATA_READ_REG);
9162306a36Sopenharmony_ci	if (ret >= 0) {
9262306a36Sopenharmony_ci		*val = ret;
9362306a36Sopenharmony_ci		ret = 0;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ciout_unlock:
9762306a36Sopenharmony_ci	mutex_unlock(&bus->mdio_lock);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	return ret;
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_cistatic void realtek_mdio_lock(void *ctx)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	struct realtek_priv *priv = ctx;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	mutex_lock(&priv->map_lock);
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic void realtek_mdio_unlock(void *ctx)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	struct realtek_priv *priv = ctx;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	mutex_unlock(&priv->map_lock);
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic const struct regmap_config realtek_mdio_regmap_config = {
11762306a36Sopenharmony_ci	.reg_bits = 10, /* A4..A0 R4..R0 */
11862306a36Sopenharmony_ci	.val_bits = 16,
11962306a36Sopenharmony_ci	.reg_stride = 1,
12062306a36Sopenharmony_ci	/* PHY regs are at 0x8000 */
12162306a36Sopenharmony_ci	.max_register = 0xffff,
12262306a36Sopenharmony_ci	.reg_format_endian = REGMAP_ENDIAN_BIG,
12362306a36Sopenharmony_ci	.reg_read = realtek_mdio_read,
12462306a36Sopenharmony_ci	.reg_write = realtek_mdio_write,
12562306a36Sopenharmony_ci	.cache_type = REGCACHE_NONE,
12662306a36Sopenharmony_ci	.lock = realtek_mdio_lock,
12762306a36Sopenharmony_ci	.unlock = realtek_mdio_unlock,
12862306a36Sopenharmony_ci};
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic const struct regmap_config realtek_mdio_nolock_regmap_config = {
13162306a36Sopenharmony_ci	.reg_bits = 10, /* A4..A0 R4..R0 */
13262306a36Sopenharmony_ci	.val_bits = 16,
13362306a36Sopenharmony_ci	.reg_stride = 1,
13462306a36Sopenharmony_ci	/* PHY regs are at 0x8000 */
13562306a36Sopenharmony_ci	.max_register = 0xffff,
13662306a36Sopenharmony_ci	.reg_format_endian = REGMAP_ENDIAN_BIG,
13762306a36Sopenharmony_ci	.reg_read = realtek_mdio_read,
13862306a36Sopenharmony_ci	.reg_write = realtek_mdio_write,
13962306a36Sopenharmony_ci	.cache_type = REGCACHE_NONE,
14062306a36Sopenharmony_ci	.disable_locking = true,
14162306a36Sopenharmony_ci};
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic int realtek_mdio_probe(struct mdio_device *mdiodev)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	struct realtek_priv *priv;
14662306a36Sopenharmony_ci	struct device *dev = &mdiodev->dev;
14762306a36Sopenharmony_ci	const struct realtek_variant *var;
14862306a36Sopenharmony_ci	struct regmap_config rc;
14962306a36Sopenharmony_ci	struct device_node *np;
15062306a36Sopenharmony_ci	int ret;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	var = of_device_get_match_data(dev);
15362306a36Sopenharmony_ci	if (!var)
15462306a36Sopenharmony_ci		return -EINVAL;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	priv = devm_kzalloc(&mdiodev->dev,
15762306a36Sopenharmony_ci			    size_add(sizeof(*priv), var->chip_data_sz),
15862306a36Sopenharmony_ci			    GFP_KERNEL);
15962306a36Sopenharmony_ci	if (!priv)
16062306a36Sopenharmony_ci		return -ENOMEM;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	mutex_init(&priv->map_lock);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	rc = realtek_mdio_regmap_config;
16562306a36Sopenharmony_ci	rc.lock_arg = priv;
16662306a36Sopenharmony_ci	priv->map = devm_regmap_init(dev, NULL, priv, &rc);
16762306a36Sopenharmony_ci	if (IS_ERR(priv->map)) {
16862306a36Sopenharmony_ci		ret = PTR_ERR(priv->map);
16962306a36Sopenharmony_ci		dev_err(dev, "regmap init failed: %d\n", ret);
17062306a36Sopenharmony_ci		return ret;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	rc = realtek_mdio_nolock_regmap_config;
17462306a36Sopenharmony_ci	priv->map_nolock = devm_regmap_init(dev, NULL, priv, &rc);
17562306a36Sopenharmony_ci	if (IS_ERR(priv->map_nolock)) {
17662306a36Sopenharmony_ci		ret = PTR_ERR(priv->map_nolock);
17762306a36Sopenharmony_ci		dev_err(dev, "regmap init failed: %d\n", ret);
17862306a36Sopenharmony_ci		return ret;
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	priv->mdio_addr = mdiodev->addr;
18262306a36Sopenharmony_ci	priv->bus = mdiodev->bus;
18362306a36Sopenharmony_ci	priv->dev = &mdiodev->dev;
18462306a36Sopenharmony_ci	priv->chip_data = (void *)priv + sizeof(*priv);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	priv->clk_delay = var->clk_delay;
18762306a36Sopenharmony_ci	priv->cmd_read = var->cmd_read;
18862306a36Sopenharmony_ci	priv->cmd_write = var->cmd_write;
18962306a36Sopenharmony_ci	priv->ops = var->ops;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	priv->write_reg_noack = realtek_mdio_write;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	np = dev->of_node;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	dev_set_drvdata(dev, priv);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	/* TODO: if power is software controlled, set up any regulators here */
19862306a36Sopenharmony_ci	priv->leds_disabled = of_property_read_bool(np, "realtek,disable-leds");
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
20162306a36Sopenharmony_ci	if (IS_ERR(priv->reset)) {
20262306a36Sopenharmony_ci		dev_err(dev, "failed to get RESET GPIO\n");
20362306a36Sopenharmony_ci		return PTR_ERR(priv->reset);
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	if (priv->reset) {
20762306a36Sopenharmony_ci		gpiod_set_value(priv->reset, 1);
20862306a36Sopenharmony_ci		dev_dbg(dev, "asserted RESET\n");
20962306a36Sopenharmony_ci		msleep(REALTEK_HW_STOP_DELAY);
21062306a36Sopenharmony_ci		gpiod_set_value(priv->reset, 0);
21162306a36Sopenharmony_ci		msleep(REALTEK_HW_START_DELAY);
21262306a36Sopenharmony_ci		dev_dbg(dev, "deasserted RESET\n");
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	ret = priv->ops->detect(priv);
21662306a36Sopenharmony_ci	if (ret) {
21762306a36Sopenharmony_ci		dev_err(dev, "unable to detect switch\n");
21862306a36Sopenharmony_ci		return ret;
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
22262306a36Sopenharmony_ci	if (!priv->ds)
22362306a36Sopenharmony_ci		return -ENOMEM;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	priv->ds->dev = dev;
22662306a36Sopenharmony_ci	priv->ds->num_ports = priv->num_ports;
22762306a36Sopenharmony_ci	priv->ds->priv = priv;
22862306a36Sopenharmony_ci	priv->ds->ops = var->ds_ops_mdio;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	ret = dsa_register_switch(priv->ds);
23162306a36Sopenharmony_ci	if (ret) {
23262306a36Sopenharmony_ci		dev_err(priv->dev, "unable to register switch ret = %d\n", ret);
23362306a36Sopenharmony_ci		return ret;
23462306a36Sopenharmony_ci	}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	return 0;
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_cistatic void realtek_mdio_remove(struct mdio_device *mdiodev)
24062306a36Sopenharmony_ci{
24162306a36Sopenharmony_ci	struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	if (!priv)
24462306a36Sopenharmony_ci		return;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	dsa_unregister_switch(priv->ds);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	/* leave the device reset asserted */
24962306a36Sopenharmony_ci	if (priv->reset)
25062306a36Sopenharmony_ci		gpiod_set_value(priv->reset, 1);
25162306a36Sopenharmony_ci}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_cistatic void realtek_mdio_shutdown(struct mdio_device *mdiodev)
25462306a36Sopenharmony_ci{
25562306a36Sopenharmony_ci	struct realtek_priv *priv = dev_get_drvdata(&mdiodev->dev);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	if (!priv)
25862306a36Sopenharmony_ci		return;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	dsa_switch_shutdown(priv->ds);
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	dev_set_drvdata(&mdiodev->dev, NULL);
26362306a36Sopenharmony_ci}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_cistatic const struct of_device_id realtek_mdio_of_match[] = {
26662306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB)
26762306a36Sopenharmony_ci	{ .compatible = "realtek,rtl8366rb", .data = &rtl8366rb_variant, },
26862306a36Sopenharmony_ci#endif
26962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8365MB)
27062306a36Sopenharmony_ci	{ .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, },
27162306a36Sopenharmony_ci#endif
27262306a36Sopenharmony_ci	{ /* sentinel */ },
27362306a36Sopenharmony_ci};
27462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, realtek_mdio_of_match);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cistatic struct mdio_driver realtek_mdio_driver = {
27762306a36Sopenharmony_ci	.mdiodrv.driver = {
27862306a36Sopenharmony_ci		.name = "realtek-mdio",
27962306a36Sopenharmony_ci		.of_match_table = realtek_mdio_of_match,
28062306a36Sopenharmony_ci	},
28162306a36Sopenharmony_ci	.probe  = realtek_mdio_probe,
28262306a36Sopenharmony_ci	.remove = realtek_mdio_remove,
28362306a36Sopenharmony_ci	.shutdown = realtek_mdio_shutdown,
28462306a36Sopenharmony_ci};
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_cimdio_module_driver(realtek_mdio_driver);
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ciMODULE_AUTHOR("Luiz Angelo Daros de Luca <luizluca@gmail.com>");
28962306a36Sopenharmony_ciMODULE_DESCRIPTION("Driver for Realtek ethernet switch connected via MDIO interface");
29062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
291