mirror of
https://github.com/torvalds/linux.git
synced 2026-04-21 16:23:59 -04:00
The Mediatek LynxI PCS is used from the MT7530 DSA driver (where it does not have an OF presence) and from mtk_eth_soc, where it does (Documentation/devicetree/bindings/net/pcs/mediatek,sgmiisys.yaml informs of a combined clock provider + SGMII PCS "SGMIISYS" syscon block). Currently, mtk_eth_soc parses the SGMIISYS OF node for the "mediatek,pnswap" property and sets a bit in the "flags" argument of mtk_pcs_lynxi_create() if set. I'd like to deprecate "mediatek,pnswap" in favour of a property which takes the current phy-mode into consideration. But this is only known at mtk_pcs_lynxi_config() time, and not known at mtk_pcs_lynxi_create(), when the SGMIISYS OF node is parsed. To achieve that, we must pass the OF node of the PCS, if it exists, to mtk_pcs_lynxi_create(), and let the PCS take a reference on it and handle property parsing whenever it wants. Use the fwnode API which is more general than OF (in case we ever need to describe the PCS using some other format). This API should be NULL tolerant, so add no particular tests for the mt7530 case. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Link: https://patch.msgid.link/20260119091220.1493761-5-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
266 lines
6.3 KiB
C
266 lines
6.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/mdio.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pcs/pcs-mtk-lynxi.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_mdio.h>
|
|
#include <linux/of_net.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <net/dsa.h>
|
|
|
|
#include "mt7530.h"
|
|
|
|
static int
|
|
mt7530_regmap_write(void *context, unsigned int reg, unsigned int val)
|
|
{
|
|
struct mt7530_priv *priv = context;
|
|
struct mii_bus *bus = priv->bus;
|
|
u16 page, r, lo, hi;
|
|
int ret;
|
|
|
|
page = (reg >> 6) & 0x3ff;
|
|
r = (reg >> 2) & 0xf;
|
|
lo = val & 0xffff;
|
|
hi = val >> 16;
|
|
|
|
ret = bus->write(bus, priv->mdiodev->addr, 0x1f, page);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = bus->write(bus, priv->mdiodev->addr, r, lo);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = bus->write(bus, priv->mdiodev->addr, 0x10, hi);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val)
|
|
{
|
|
struct mt7530_priv *priv = context;
|
|
struct mii_bus *bus = priv->bus;
|
|
u16 page, r, lo, hi;
|
|
int ret;
|
|
|
|
page = (reg >> 6) & 0x3ff;
|
|
r = (reg >> 2) & 0xf;
|
|
|
|
ret = bus->write(bus, priv->mdiodev->addr, 0x1f, page);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
lo = bus->read(bus, priv->mdiodev->addr, r);
|
|
hi = bus->read(bus, priv->mdiodev->addr, 0x10);
|
|
|
|
*val = (hi << 16) | (lo & 0xffff);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
mt7530_mdio_regmap_lock(void *mdio_lock)
|
|
{
|
|
mutex_lock_nested(mdio_lock, MDIO_MUTEX_NESTED);
|
|
}
|
|
|
|
static void
|
|
mt7530_mdio_regmap_unlock(void *mdio_lock)
|
|
{
|
|
mutex_unlock(mdio_lock);
|
|
}
|
|
|
|
static const struct regmap_bus mt7530_regmap_bus = {
|
|
.reg_write = mt7530_regmap_write,
|
|
.reg_read = mt7530_regmap_read,
|
|
};
|
|
|
|
static int
|
|
mt7531_create_sgmii(struct mt7530_priv *priv)
|
|
{
|
|
struct regmap_config *mt7531_pcs_config[2] = {};
|
|
struct phylink_pcs *pcs;
|
|
struct regmap *regmap;
|
|
int i, ret = 0;
|
|
|
|
for (i = priv->p5_sgmii ? 0 : 1; i < 2; i++) {
|
|
mt7531_pcs_config[i] = devm_kzalloc(priv->dev,
|
|
sizeof(struct regmap_config),
|
|
GFP_KERNEL);
|
|
if (!mt7531_pcs_config[i]) {
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
|
|
mt7531_pcs_config[i]->name = i ? "port6" : "port5";
|
|
mt7531_pcs_config[i]->reg_bits = 16;
|
|
mt7531_pcs_config[i]->val_bits = 32;
|
|
mt7531_pcs_config[i]->reg_stride = 4;
|
|
mt7531_pcs_config[i]->reg_base = MT7531_SGMII_REG_BASE(5 + i);
|
|
mt7531_pcs_config[i]->max_register = 0x17c;
|
|
mt7531_pcs_config[i]->lock = mt7530_mdio_regmap_lock;
|
|
mt7531_pcs_config[i]->unlock = mt7530_mdio_regmap_unlock;
|
|
mt7531_pcs_config[i]->lock_arg = &priv->bus->mdio_lock;
|
|
|
|
regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus, priv,
|
|
mt7531_pcs_config[i]);
|
|
if (IS_ERR(regmap)) {
|
|
ret = PTR_ERR(regmap);
|
|
break;
|
|
}
|
|
pcs = mtk_pcs_lynxi_create(priv->dev, NULL, regmap,
|
|
MT7531_PHYA_CTRL_SIGNAL3);
|
|
if (!pcs) {
|
|
ret = -ENXIO;
|
|
break;
|
|
}
|
|
priv->ports[5 + i].sgmii_pcs = pcs;
|
|
}
|
|
|
|
if (ret && i)
|
|
mtk_pcs_lynxi_destroy(priv->ports[5].sgmii_pcs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id mt7530_of_match[] = {
|
|
{ .compatible = "mediatek,mt7621", .data = &mt753x_table[ID_MT7621], },
|
|
{ .compatible = "mediatek,mt7530", .data = &mt753x_table[ID_MT7530], },
|
|
{ .compatible = "mediatek,mt7531", .data = &mt753x_table[ID_MT7531], },
|
|
{ /* sentinel */ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mt7530_of_match);
|
|
|
|
static const struct regmap_config regmap_config = {
|
|
.reg_bits = 16,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.max_register = MT7530_CREV,
|
|
.disable_locking = true,
|
|
};
|
|
|
|
static int
|
|
mt7530_probe(struct mdio_device *mdiodev)
|
|
{
|
|
struct mt7530_priv *priv;
|
|
struct device_node *dn;
|
|
int ret;
|
|
|
|
dn = mdiodev->dev.of_node;
|
|
|
|
priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
priv->bus = mdiodev->bus;
|
|
priv->dev = &mdiodev->dev;
|
|
priv->mdiodev = mdiodev;
|
|
|
|
ret = mt7530_probe_common(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Use medatek,mcm property to distinguish hardware type that would
|
|
* cause a little bit differences on power-on sequence.
|
|
* Not MCM that indicates switch works as the remote standalone
|
|
* integrated circuit so the GPIO pin would be used to complete
|
|
* the reset, otherwise memory-mapped register accessing used
|
|
* through syscon provides in the case of MCM.
|
|
*/
|
|
priv->mcm = of_property_read_bool(dn, "mediatek,mcm");
|
|
if (priv->mcm) {
|
|
dev_info(&mdiodev->dev, "MT7530 adapts as multi-chip module\n");
|
|
|
|
priv->rstc = devm_reset_control_get(&mdiodev->dev, "mcm");
|
|
if (IS_ERR(priv->rstc)) {
|
|
dev_err(&mdiodev->dev, "Couldn't get our reset line\n");
|
|
return PTR_ERR(priv->rstc);
|
|
}
|
|
} else {
|
|
priv->reset = devm_gpiod_get_optional(&mdiodev->dev, "reset",
|
|
GPIOD_OUT_LOW);
|
|
if (IS_ERR(priv->reset)) {
|
|
dev_err(&mdiodev->dev, "Couldn't get our reset line\n");
|
|
return PTR_ERR(priv->reset);
|
|
}
|
|
}
|
|
|
|
if (priv->id == ID_MT7530) {
|
|
priv->core_pwr = devm_regulator_get(&mdiodev->dev, "core");
|
|
if (IS_ERR(priv->core_pwr))
|
|
return PTR_ERR(priv->core_pwr);
|
|
|
|
priv->io_pwr = devm_regulator_get(&mdiodev->dev, "io");
|
|
if (IS_ERR(priv->io_pwr))
|
|
return PTR_ERR(priv->io_pwr);
|
|
}
|
|
|
|
priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus, priv,
|
|
®map_config);
|
|
if (IS_ERR(priv->regmap))
|
|
return PTR_ERR(priv->regmap);
|
|
|
|
if (priv->id == ID_MT7531)
|
|
priv->create_sgmii = mt7531_create_sgmii;
|
|
|
|
return dsa_register_switch(priv->ds);
|
|
}
|
|
|
|
static void
|
|
mt7530_remove(struct mdio_device *mdiodev)
|
|
{
|
|
struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev);
|
|
int ret = 0, i;
|
|
|
|
if (!priv)
|
|
return;
|
|
|
|
ret = regulator_disable(priv->core_pwr);
|
|
if (ret < 0)
|
|
dev_err(priv->dev,
|
|
"Failed to disable core power: %d\n", ret);
|
|
|
|
ret = regulator_disable(priv->io_pwr);
|
|
if (ret < 0)
|
|
dev_err(priv->dev, "Failed to disable io pwr: %d\n",
|
|
ret);
|
|
|
|
mt7530_remove_common(priv);
|
|
|
|
for (i = 0; i < 2; ++i)
|
|
mtk_pcs_lynxi_destroy(priv->ports[5 + i].sgmii_pcs);
|
|
}
|
|
|
|
static void mt7530_shutdown(struct mdio_device *mdiodev)
|
|
{
|
|
struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev);
|
|
|
|
if (!priv)
|
|
return;
|
|
|
|
dsa_switch_shutdown(priv->ds);
|
|
|
|
dev_set_drvdata(&mdiodev->dev, NULL);
|
|
}
|
|
|
|
static struct mdio_driver mt7530_mdio_driver = {
|
|
.probe = mt7530_probe,
|
|
.remove = mt7530_remove,
|
|
.shutdown = mt7530_shutdown,
|
|
.mdiodrv.driver = {
|
|
.name = "mt7530-mdio",
|
|
.of_match_table = mt7530_of_match,
|
|
},
|
|
};
|
|
|
|
mdio_module_driver(mt7530_mdio_driver);
|
|
|
|
MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>");
|
|
MODULE_DESCRIPTION("Driver for Mediatek MT7530 Switch (MDIO)");
|
|
MODULE_LICENSE("GPL");
|