diff options
Diffstat (limited to 'drivers/net/dsa/mt7530-mdio.c')
| -rw-r--r-- | drivers/net/dsa/mt7530-mdio.c | 271 | 
1 files changed, 271 insertions, 0 deletions
diff --git a/drivers/net/dsa/mt7530-mdio.c b/drivers/net/dsa/mt7530-mdio.c new file mode 100644 index 000000000000..088533663b83 --- /dev/null +++ b/drivers/net/dsa/mt7530-mdio.c @@ -0,0 +1,271 @@ +// 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 <[email protected]>"); +MODULE_DESCRIPTION("Driver for Mediatek MT7530 Switch (MDIO)"); +MODULE_LICENSE("GPL");  |