| // 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"); |