272 lines
6.5 KiB
C
272 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 mii_bus *bus = context;
|
|
u16 page, r, lo, hi;
|
|
int ret;
|
|
|
|
page = (reg >> 6) & 0x3ff;
|
|
r = (reg >> 2) & 0xf;
|
|
lo = val & 0xffff;
|
|
hi = val >> 16;
|
|
|
|
/* MT7530 uses 31 as the pseudo port */
|
|
ret = bus->write(bus, 0x1f, 0x1f, page);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = bus->write(bus, 0x1f, r, lo);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = bus->write(bus, 0x1f, 0x10, hi);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val)
|
|
{
|
|
struct mii_bus *bus = context;
|
|
u16 page, r, lo, hi;
|
|
int ret;
|
|
|
|
page = (reg >> 6) & 0x3ff;
|
|
r = (reg >> 2) & 0xf;
|
|
|
|
/* MT7530 uses 31 as the pseudo port */
|
|
ret = bus->write(bus, 0x1f, 0x1f, page);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
lo = bus->read(bus, 0x1f, r);
|
|
hi = bus->read(bus, 0x1f, 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, bool dual_sgmii)
|
|
{
|
|
struct regmap_config *mt7531_pcs_config[2] = {};
|
|
struct phylink_pcs *pcs;
|
|
struct regmap *regmap;
|
|
int i, ret = 0;
|
|
|
|
/* MT7531AE has two SGMII units for port 5 and port 6
|
|
* MT7531BE has only one SGMII unit for port 6
|
|
*/
|
|
for (i = dual_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->bus,
|
|
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;
|
|
|
|
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->bus, 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");
|