mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-06 05:02:31 +00:00
868ff5f494
Read the PHY address the switch listens on from the reg property of the switch node on the device tree. This change brings support for MT7530 switches on boards with such bootstrapping configuration where the switch listens on a different PHY address than the hardcoded PHY address on the driver, 31. As described on the "MT7621 Programming Guide v0.4" document, the MT7530 switch and its PHYs can be configured to listen on the range of 7-12, 15-20, 23-28, and 31 and 0-4 PHY addresses. There are operations where the switch PHY registers are used. For the PHY address of the control PHY, transform the MT753X_CTRL_PHY_ADDR constant into a macro and use it. The PHY address for the control PHY is 0 when the switch listens on 31. In any other case, it is one greater than the PHY address the switch listens on. Reviewed-by: Daniel Golle <daniel@makrotopia.org> Tested-by: Daniel Golle <daniel@makrotopia.org> Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com> Signed-off-by: Arınç ÜNAL <arinc.unal@arinc9.com> Signed-off-by: Paolo Abeni <pabeni@redhat.com>
269 lines
6.5 KiB
C
269 lines
6.5 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, regmap,
|
|
MT7531_PHYA_CTRL_SIGNAL3, 0);
|
|
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 int
|
|
mt7530_probe(struct mdio_device *mdiodev)
|
|
{
|
|
static struct regmap_config *regmap_config;
|
|
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);
|
|
}
|
|
|
|
regmap_config = devm_kzalloc(&mdiodev->dev, sizeof(*regmap_config),
|
|
GFP_KERNEL);
|
|
if (!regmap_config)
|
|
return -ENOMEM;
|
|
|
|
regmap_config->reg_bits = 16;
|
|
regmap_config->val_bits = 32;
|
|
regmap_config->reg_stride = 4;
|
|
regmap_config->max_register = MT7530_CREV;
|
|
regmap_config->disable_locking = true;
|
|
priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus, priv,
|
|
regmap_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");
|