mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 07:35:36 -05:00
Russell King pointed out that disabling the switch by clearing
GSWIP_MDIO_GLOB_ENABLE before calling dsa_unregister_switch() is
problematic, as it violates a Golden Rule of driver development to
always first unpublish userspace interfaces and then disable the
hardware.
Fix this, and also simplify the probe() function, by introducing a
dsa_switch_ops teardown() operation which takes care of clearing the
GSWIP_MDIO_GLOB_ENABLE bit.
Fixes: 14fceff477 ("net: dsa: Add Lantiq / Intel DSA driver for vrx200")
Suggested-by: "Russell King (Oracle)" <linux@armlinux.org.uk>
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/4ebd72a29edc1e4059b9666a26a0bb5d906a829a.1765241054.git.daniel@makrotopia.org
Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
516 lines
13 KiB
C
516 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Lantiq / Intel GSWIP switch driver for VRX200, xRX300 and xRX330 SoCs
|
|
*
|
|
* Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
|
|
* Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de>
|
|
* Copyright (C) 2012 John Crispin <john@phrozen.org>
|
|
* Copyright (C) 2010 Lantiq Deutschland
|
|
*/
|
|
|
|
#include "lantiq_gswip.h"
|
|
#include "lantiq_pce.h"
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/reset.h>
|
|
#include <dt-bindings/mips/lantiq_rcu_gphy.h>
|
|
|
|
#include <net/dsa.h>
|
|
|
|
struct xway_gphy_match_data {
|
|
char *fe_firmware_name;
|
|
char *ge_firmware_name;
|
|
};
|
|
|
|
static void gswip_xrx200_phylink_get_caps(struct dsa_switch *ds, int port,
|
|
struct phylink_config *config)
|
|
{
|
|
switch (port) {
|
|
case 0:
|
|
case 1:
|
|
phy_interface_set_rgmii(config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_MII,
|
|
config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_REVMII,
|
|
config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_RMII,
|
|
config->supported_interfaces);
|
|
break;
|
|
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 6:
|
|
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
|
|
config->supported_interfaces);
|
|
break;
|
|
|
|
case 5:
|
|
phy_interface_set_rgmii(config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
|
|
config->supported_interfaces);
|
|
break;
|
|
}
|
|
|
|
config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
MAC_10 | MAC_100 | MAC_1000;
|
|
}
|
|
|
|
static void gswip_xrx300_phylink_get_caps(struct dsa_switch *ds, int port,
|
|
struct phylink_config *config)
|
|
{
|
|
switch (port) {
|
|
case 0:
|
|
phy_interface_set_rgmii(config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_GMII,
|
|
config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_RMII,
|
|
config->supported_interfaces);
|
|
break;
|
|
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 6:
|
|
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
|
|
config->supported_interfaces);
|
|
break;
|
|
|
|
case 5:
|
|
phy_interface_set_rgmii(config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
|
|
config->supported_interfaces);
|
|
__set_bit(PHY_INTERFACE_MODE_RMII,
|
|
config->supported_interfaces);
|
|
break;
|
|
}
|
|
|
|
config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
MAC_10 | MAC_100 | MAC_1000;
|
|
}
|
|
|
|
static const struct xway_gphy_match_data xrx200a1x_gphy_data = {
|
|
.fe_firmware_name = "lantiq/xrx200_phy22f_a14.bin",
|
|
.ge_firmware_name = "lantiq/xrx200_phy11g_a14.bin",
|
|
};
|
|
|
|
static const struct xway_gphy_match_data xrx200a2x_gphy_data = {
|
|
.fe_firmware_name = "lantiq/xrx200_phy22f_a22.bin",
|
|
.ge_firmware_name = "lantiq/xrx200_phy11g_a22.bin",
|
|
};
|
|
|
|
static const struct xway_gphy_match_data xrx300_gphy_data = {
|
|
.fe_firmware_name = "lantiq/xrx300_phy22f_a21.bin",
|
|
.ge_firmware_name = "lantiq/xrx300_phy11g_a21.bin",
|
|
};
|
|
|
|
static const struct of_device_id xway_gphy_match[] __maybe_unused = {
|
|
{ .compatible = "lantiq,xrx200-gphy-fw", .data = NULL },
|
|
{ .compatible = "lantiq,xrx200a1x-gphy-fw", .data = &xrx200a1x_gphy_data },
|
|
{ .compatible = "lantiq,xrx200a2x-gphy-fw", .data = &xrx200a2x_gphy_data },
|
|
{ .compatible = "lantiq,xrx300-gphy-fw", .data = &xrx300_gphy_data },
|
|
{ .compatible = "lantiq,xrx330-gphy-fw", .data = &xrx300_gphy_data },
|
|
{},
|
|
};
|
|
|
|
static int gswip_gphy_fw_load(struct gswip_priv *priv, struct gswip_gphy_fw *gphy_fw)
|
|
{
|
|
struct device *dev = priv->dev;
|
|
const struct firmware *fw;
|
|
void *fw_addr;
|
|
dma_addr_t dma_addr;
|
|
dma_addr_t dev_addr;
|
|
size_t size;
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(gphy_fw->clk_gate);
|
|
if (ret)
|
|
return ret;
|
|
|
|
reset_control_assert(gphy_fw->reset);
|
|
|
|
/* The vendor BSP uses a 200ms delay after asserting the reset line.
|
|
* Without this some users are observing that the PHY is not coming up
|
|
* on the MDIO bus.
|
|
*/
|
|
msleep(200);
|
|
|
|
ret = request_firmware(&fw, gphy_fw->fw_name, dev);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "failed to load firmware: %s\n",
|
|
gphy_fw->fw_name);
|
|
|
|
/* GPHY cores need the firmware code in a persistent and contiguous
|
|
* memory area with a 16 kB boundary aligned start address.
|
|
*/
|
|
size = fw->size + XRX200_GPHY_FW_ALIGN;
|
|
|
|
fw_addr = dmam_alloc_coherent(dev, size, &dma_addr, GFP_KERNEL);
|
|
if (fw_addr) {
|
|
fw_addr = PTR_ALIGN(fw_addr, XRX200_GPHY_FW_ALIGN);
|
|
dev_addr = ALIGN(dma_addr, XRX200_GPHY_FW_ALIGN);
|
|
memcpy(fw_addr, fw->data, fw->size);
|
|
} else {
|
|
release_firmware(fw);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
release_firmware(fw);
|
|
|
|
ret = regmap_write(priv->rcu_regmap, gphy_fw->fw_addr_offset, dev_addr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
reset_control_deassert(gphy_fw->reset);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gswip_gphy_fw_probe(struct gswip_priv *priv,
|
|
struct gswip_gphy_fw *gphy_fw,
|
|
struct device_node *gphy_fw_np, int i)
|
|
{
|
|
struct device *dev = priv->dev;
|
|
u32 gphy_mode;
|
|
int ret;
|
|
char gphyname[10];
|
|
|
|
snprintf(gphyname, sizeof(gphyname), "gphy%d", i);
|
|
|
|
gphy_fw->clk_gate = devm_clk_get(dev, gphyname);
|
|
if (IS_ERR(gphy_fw->clk_gate)) {
|
|
return dev_err_probe(dev, PTR_ERR(gphy_fw->clk_gate),
|
|
"Failed to lookup gate clock\n");
|
|
}
|
|
|
|
ret = of_property_read_u32(gphy_fw_np, "reg", &gphy_fw->fw_addr_offset);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = of_property_read_u32(gphy_fw_np, "lantiq,gphy-mode", &gphy_mode);
|
|
/* Default to GE mode */
|
|
if (ret)
|
|
gphy_mode = GPHY_MODE_GE;
|
|
|
|
switch (gphy_mode) {
|
|
case GPHY_MODE_FE:
|
|
gphy_fw->fw_name = priv->gphy_fw_name_cfg->fe_firmware_name;
|
|
break;
|
|
case GPHY_MODE_GE:
|
|
gphy_fw->fw_name = priv->gphy_fw_name_cfg->ge_firmware_name;
|
|
break;
|
|
default:
|
|
return dev_err_probe(dev, -EINVAL, "Unknown GPHY mode %d\n",
|
|
gphy_mode);
|
|
}
|
|
|
|
gphy_fw->reset = of_reset_control_array_get_exclusive(gphy_fw_np);
|
|
if (IS_ERR(gphy_fw->reset))
|
|
return dev_err_probe(dev, PTR_ERR(gphy_fw->reset),
|
|
"Failed to lookup gphy reset\n");
|
|
|
|
return gswip_gphy_fw_load(priv, gphy_fw);
|
|
}
|
|
|
|
static void gswip_gphy_fw_remove(struct gswip_priv *priv,
|
|
struct gswip_gphy_fw *gphy_fw)
|
|
{
|
|
int ret;
|
|
|
|
/* check if the device was fully probed */
|
|
if (!gphy_fw->fw_name)
|
|
return;
|
|
|
|
ret = regmap_write(priv->rcu_regmap, gphy_fw->fw_addr_offset, 0);
|
|
if (ret)
|
|
dev_err(priv->dev, "can not reset GPHY FW pointer\n");
|
|
|
|
clk_disable_unprepare(gphy_fw->clk_gate);
|
|
|
|
reset_control_put(gphy_fw->reset);
|
|
}
|
|
|
|
static int gswip_gphy_fw_list(struct gswip_priv *priv,
|
|
struct device_node *gphy_fw_list_np, u32 version)
|
|
{
|
|
struct device *dev = priv->dev;
|
|
struct device_node *gphy_fw_np;
|
|
const struct of_device_id *match;
|
|
int err;
|
|
int i = 0;
|
|
|
|
/* The VRX200 rev 1.1 uses the GSWIP 2.0 and needs the older
|
|
* GPHY firmware. The VRX200 rev 1.2 uses the GSWIP 2.1 and also
|
|
* needs a different GPHY firmware.
|
|
*/
|
|
if (of_device_is_compatible(gphy_fw_list_np, "lantiq,xrx200-gphy-fw")) {
|
|
switch (version) {
|
|
case GSWIP_VERSION_2_0:
|
|
priv->gphy_fw_name_cfg = &xrx200a1x_gphy_data;
|
|
break;
|
|
case GSWIP_VERSION_2_1:
|
|
priv->gphy_fw_name_cfg = &xrx200a2x_gphy_data;
|
|
break;
|
|
default:
|
|
return dev_err_probe(dev, -ENOENT,
|
|
"unknown GSWIP version: 0x%x\n",
|
|
version);
|
|
}
|
|
}
|
|
|
|
match = of_match_node(xway_gphy_match, gphy_fw_list_np);
|
|
if (match && match->data)
|
|
priv->gphy_fw_name_cfg = match->data;
|
|
|
|
if (!priv->gphy_fw_name_cfg)
|
|
return dev_err_probe(dev, -ENOENT,
|
|
"GPHY compatible type not supported\n");
|
|
|
|
priv->num_gphy_fw = of_get_available_child_count(gphy_fw_list_np);
|
|
if (!priv->num_gphy_fw)
|
|
return -ENOENT;
|
|
|
|
priv->rcu_regmap = syscon_regmap_lookup_by_phandle(gphy_fw_list_np,
|
|
"lantiq,rcu");
|
|
if (IS_ERR(priv->rcu_regmap))
|
|
return PTR_ERR(priv->rcu_regmap);
|
|
|
|
priv->gphy_fw = devm_kmalloc_array(dev, priv->num_gphy_fw,
|
|
sizeof(*priv->gphy_fw),
|
|
GFP_KERNEL | __GFP_ZERO);
|
|
if (!priv->gphy_fw)
|
|
return -ENOMEM;
|
|
|
|
for_each_available_child_of_node(gphy_fw_list_np, gphy_fw_np) {
|
|
err = gswip_gphy_fw_probe(priv, &priv->gphy_fw[i],
|
|
gphy_fw_np, i);
|
|
if (err) {
|
|
of_node_put(gphy_fw_np);
|
|
goto remove_gphy;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
/* The standalone PHY11G requires 300ms to be fully
|
|
* initialized and ready for any MDIO communication after being
|
|
* taken out of reset. For the SoC-internal GPHY variant there
|
|
* is no (known) documentation for the minimum time after a
|
|
* reset. Use the same value as for the standalone variant as
|
|
* some users have reported internal PHYs not being detected
|
|
* without any delay.
|
|
*/
|
|
msleep(300);
|
|
|
|
return 0;
|
|
|
|
remove_gphy:
|
|
for (i = 0; i < priv->num_gphy_fw; i++)
|
|
gswip_gphy_fw_remove(priv, &priv->gphy_fw[i]);
|
|
return err;
|
|
}
|
|
|
|
static const struct regmap_config sw_regmap_config = {
|
|
.name = "switch",
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_shift = REGMAP_UPSHIFT(2),
|
|
.val_format_endian = REGMAP_ENDIAN_NATIVE,
|
|
.max_register = GSWIP_SDMA_PCTRLp(6),
|
|
};
|
|
|
|
static const struct regmap_config mdio_regmap_config = {
|
|
.name = "mdio",
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_shift = REGMAP_UPSHIFT(2),
|
|
.val_format_endian = REGMAP_ENDIAN_NATIVE,
|
|
.max_register = GSWIP_MDIO_PHYp(0),
|
|
};
|
|
|
|
static const struct regmap_config mii_regmap_config = {
|
|
.name = "mii",
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_shift = REGMAP_UPSHIFT(2),
|
|
.val_format_endian = REGMAP_ENDIAN_NATIVE,
|
|
.max_register = GSWIP_MII_CFGp(6),
|
|
};
|
|
|
|
static int gswip_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np, *gphy_fw_np;
|
|
__iomem void *gswip, *mdio, *mii;
|
|
struct device *dev = &pdev->dev;
|
|
struct gswip_priv *priv;
|
|
int err;
|
|
int i;
|
|
u32 version;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
gswip = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(gswip))
|
|
return PTR_ERR(gswip);
|
|
|
|
mdio = devm_platform_ioremap_resource(pdev, 1);
|
|
if (IS_ERR(mdio))
|
|
return PTR_ERR(mdio);
|
|
|
|
mii = devm_platform_ioremap_resource(pdev, 2);
|
|
if (IS_ERR(mii))
|
|
return PTR_ERR(mii);
|
|
|
|
priv->gswip = devm_regmap_init_mmio(dev, gswip, &sw_regmap_config);
|
|
if (IS_ERR(priv->gswip))
|
|
return PTR_ERR(priv->gswip);
|
|
|
|
priv->mdio = devm_regmap_init_mmio(dev, mdio, &mdio_regmap_config);
|
|
if (IS_ERR(priv->mdio))
|
|
return PTR_ERR(priv->mdio);
|
|
|
|
priv->mii = devm_regmap_init_mmio(dev, mii, &mii_regmap_config);
|
|
if (IS_ERR(priv->mii))
|
|
return PTR_ERR(priv->mii);
|
|
|
|
priv->hw_info = of_device_get_match_data(dev);
|
|
if (!priv->hw_info)
|
|
return -EINVAL;
|
|
|
|
priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
|
|
if (!priv->ds)
|
|
return -ENOMEM;
|
|
|
|
priv->dev = dev;
|
|
|
|
regmap_read(priv->gswip, GSWIP_VERSION, &version);
|
|
|
|
np = dev->of_node;
|
|
switch (version) {
|
|
case GSWIP_VERSION_2_0:
|
|
case GSWIP_VERSION_2_1:
|
|
if (!of_device_is_compatible(np, "lantiq,xrx200-gswip"))
|
|
return -EINVAL;
|
|
break;
|
|
case GSWIP_VERSION_2_2:
|
|
case GSWIP_VERSION_2_2_ETC:
|
|
if (!of_device_is_compatible(np, "lantiq,xrx300-gswip") &&
|
|
!of_device_is_compatible(np, "lantiq,xrx330-gswip"))
|
|
return -EINVAL;
|
|
break;
|
|
default:
|
|
return dev_err_probe(dev, -ENOENT,
|
|
"unknown GSWIP version: 0x%x\n", version);
|
|
}
|
|
|
|
/* bring up the mdio bus */
|
|
gphy_fw_np = of_get_compatible_child(dev->of_node, "lantiq,gphy-fw");
|
|
if (gphy_fw_np) {
|
|
err = gswip_gphy_fw_list(priv, gphy_fw_np, version);
|
|
of_node_put(gphy_fw_np);
|
|
if (err)
|
|
return dev_err_probe(dev, err,
|
|
"gphy fw probe failed\n");
|
|
}
|
|
|
|
err = gswip_probe_common(priv, version);
|
|
if (err)
|
|
goto gphy_fw_remove;
|
|
|
|
platform_set_drvdata(pdev, priv);
|
|
|
|
return 0;
|
|
|
|
gphy_fw_remove:
|
|
for (i = 0; i < priv->num_gphy_fw; i++)
|
|
gswip_gphy_fw_remove(priv, &priv->gphy_fw[i]);
|
|
return err;
|
|
}
|
|
|
|
static void gswip_remove(struct platform_device *pdev)
|
|
{
|
|
struct gswip_priv *priv = platform_get_drvdata(pdev);
|
|
int i;
|
|
|
|
if (!priv)
|
|
return;
|
|
|
|
dsa_unregister_switch(priv->ds);
|
|
|
|
for (i = 0; i < priv->num_gphy_fw; i++)
|
|
gswip_gphy_fw_remove(priv, &priv->gphy_fw[i]);
|
|
}
|
|
|
|
static void gswip_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct gswip_priv *priv = platform_get_drvdata(pdev);
|
|
|
|
if (!priv)
|
|
return;
|
|
|
|
dsa_switch_shutdown(priv->ds);
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
}
|
|
|
|
static const struct gswip_hw_info gswip_xrx200 = {
|
|
.max_ports = 7,
|
|
.allowed_cpu_ports = BIT(6),
|
|
.mii_ports = BIT(0) | BIT(1) | BIT(5),
|
|
.mii_port_reg_offset = 0,
|
|
.phylink_get_caps = gswip_xrx200_phylink_get_caps,
|
|
.pce_microcode = &gswip_pce_microcode,
|
|
.pce_microcode_size = ARRAY_SIZE(gswip_pce_microcode),
|
|
.tag_protocol = DSA_TAG_PROTO_GSWIP,
|
|
};
|
|
|
|
static const struct gswip_hw_info gswip_xrx300 = {
|
|
.max_ports = 7,
|
|
.allowed_cpu_ports = BIT(6),
|
|
.mii_ports = BIT(0) | BIT(5),
|
|
.mii_port_reg_offset = 0,
|
|
.phylink_get_caps = gswip_xrx300_phylink_get_caps,
|
|
.pce_microcode = &gswip_pce_microcode,
|
|
.pce_microcode_size = ARRAY_SIZE(gswip_pce_microcode),
|
|
.tag_protocol = DSA_TAG_PROTO_GSWIP,
|
|
};
|
|
|
|
static const struct of_device_id gswip_of_match[] = {
|
|
{ .compatible = "lantiq,xrx200-gswip", .data = &gswip_xrx200 },
|
|
{ .compatible = "lantiq,xrx300-gswip", .data = &gswip_xrx300 },
|
|
{ .compatible = "lantiq,xrx330-gswip", .data = &gswip_xrx300 },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, gswip_of_match);
|
|
|
|
static struct platform_driver gswip_driver = {
|
|
.probe = gswip_probe,
|
|
.remove = gswip_remove,
|
|
.shutdown = gswip_shutdown,
|
|
.driver = {
|
|
.name = "gswip",
|
|
.of_match_table = gswip_of_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(gswip_driver);
|
|
|
|
MODULE_FIRMWARE("lantiq/xrx300_phy11g_a21.bin");
|
|
MODULE_FIRMWARE("lantiq/xrx300_phy22f_a21.bin");
|
|
MODULE_FIRMWARE("lantiq/xrx200_phy11g_a14.bin");
|
|
MODULE_FIRMWARE("lantiq/xrx200_phy11g_a22.bin");
|
|
MODULE_FIRMWARE("lantiq/xrx200_phy22f_a14.bin");
|
|
MODULE_FIRMWARE("lantiq/xrx200_phy22f_a22.bin");
|
|
MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
|
|
MODULE_DESCRIPTION("Lantiq / Intel GSWIP driver");
|
|
MODULE_LICENSE("GPL v2");
|