mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 11:18:45 +07:00
18eb8aea7f
The cpsw-phy-sel device is not a child of the cpsw interconnect target module. It lives in the system control module. Let's fix this issue by trying to use cpsw-phy-sel phandle first if it exists and if not fall back to current usage of trying to find the cpsw-phy-sel child. That way the phy sel driver can be a child of the system control module where it belongs in the device tree. Without this fix, we cannot have a proper interconnect target module hierarchy in device tree for things like genpd. Note that deferred probe is mostly not supported by cpsw and this patch does not attempt to fix that. In case deferred probe support is needed, this could be added to cpsw_slave_open() and phy_connect() so they start handling and returning errors. For documenting it, looks like the cpsw-phy-sel is used for all cpsw device tree nodes. It's missing the related binding documentation, so let's also update the binding documentation accordingly. Cc: devicetree@vger.kernel.org Cc: Andrew Lunn <andrew@lunn.ch> Cc: Grygorii Strashko <grygorii.strashko@ti.com> Cc: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Murali Karicheri <m-karicheri2@ti.com> Cc: Rob Herring <robh+dt@kernel.org> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: David S. Miller <davem@davemloft.net>
254 lines
5.8 KiB
C
254 lines
5.8 KiB
C
/* Texas Instruments Ethernet Switch Driver
|
|
*
|
|
* Copyright (C) 2013 Texas Instruments
|
|
*
|
|
* Module Author: Mugunthan V N <mugunthanvnm@ti.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
|
* kind, whether express or implied; without even the implied warranty
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/init.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
|
|
#include "cpsw.h"
|
|
|
|
/* AM33xx SoC specific definitions for the CONTROL port */
|
|
#define AM33XX_GMII_SEL_MODE_MII 0
|
|
#define AM33XX_GMII_SEL_MODE_RMII 1
|
|
#define AM33XX_GMII_SEL_MODE_RGMII 2
|
|
|
|
#define AM33XX_GMII_SEL_RMII2_IO_CLK_EN BIT(7)
|
|
#define AM33XX_GMII_SEL_RMII1_IO_CLK_EN BIT(6)
|
|
#define AM33XX_GMII_SEL_RGMII2_IDMODE BIT(5)
|
|
#define AM33XX_GMII_SEL_RGMII1_IDMODE BIT(4)
|
|
|
|
#define GMII_SEL_MODE_MASK 0x3
|
|
|
|
struct cpsw_phy_sel_priv {
|
|
struct device *dev;
|
|
u32 __iomem *gmii_sel;
|
|
bool rmii_clock_external;
|
|
void (*cpsw_phy_sel)(struct cpsw_phy_sel_priv *priv,
|
|
phy_interface_t phy_mode, int slave);
|
|
};
|
|
|
|
|
|
static void cpsw_gmii_sel_am3352(struct cpsw_phy_sel_priv *priv,
|
|
phy_interface_t phy_mode, int slave)
|
|
{
|
|
u32 reg;
|
|
u32 mask;
|
|
u32 mode = 0;
|
|
bool rgmii_id = false;
|
|
|
|
reg = readl(priv->gmii_sel);
|
|
|
|
switch (phy_mode) {
|
|
case PHY_INTERFACE_MODE_RMII:
|
|
mode = AM33XX_GMII_SEL_MODE_RMII;
|
|
break;
|
|
|
|
case PHY_INTERFACE_MODE_RGMII:
|
|
mode = AM33XX_GMII_SEL_MODE_RGMII;
|
|
break;
|
|
|
|
case PHY_INTERFACE_MODE_RGMII_ID:
|
|
case PHY_INTERFACE_MODE_RGMII_RXID:
|
|
case PHY_INTERFACE_MODE_RGMII_TXID:
|
|
mode = AM33XX_GMII_SEL_MODE_RGMII;
|
|
rgmii_id = true;
|
|
break;
|
|
|
|
default:
|
|
dev_warn(priv->dev,
|
|
"Unsupported PHY mode: \"%s\". Defaulting to MII.\n",
|
|
phy_modes(phy_mode));
|
|
/* fallthrough */
|
|
case PHY_INTERFACE_MODE_MII:
|
|
mode = AM33XX_GMII_SEL_MODE_MII;
|
|
break;
|
|
};
|
|
|
|
mask = GMII_SEL_MODE_MASK << (slave * 2) | BIT(slave + 6);
|
|
mask |= BIT(slave + 4);
|
|
mode <<= slave * 2;
|
|
|
|
if (priv->rmii_clock_external) {
|
|
if (slave == 0)
|
|
mode |= AM33XX_GMII_SEL_RMII1_IO_CLK_EN;
|
|
else
|
|
mode |= AM33XX_GMII_SEL_RMII2_IO_CLK_EN;
|
|
}
|
|
|
|
if (rgmii_id) {
|
|
if (slave == 0)
|
|
mode |= AM33XX_GMII_SEL_RGMII1_IDMODE;
|
|
else
|
|
mode |= AM33XX_GMII_SEL_RGMII2_IDMODE;
|
|
}
|
|
|
|
reg &= ~mask;
|
|
reg |= mode;
|
|
|
|
writel(reg, priv->gmii_sel);
|
|
}
|
|
|
|
static void cpsw_gmii_sel_dra7xx(struct cpsw_phy_sel_priv *priv,
|
|
phy_interface_t phy_mode, int slave)
|
|
{
|
|
u32 reg;
|
|
u32 mask;
|
|
u32 mode = 0;
|
|
|
|
reg = readl(priv->gmii_sel);
|
|
|
|
switch (phy_mode) {
|
|
case PHY_INTERFACE_MODE_RMII:
|
|
mode = AM33XX_GMII_SEL_MODE_RMII;
|
|
break;
|
|
|
|
case PHY_INTERFACE_MODE_RGMII:
|
|
case PHY_INTERFACE_MODE_RGMII_ID:
|
|
case PHY_INTERFACE_MODE_RGMII_RXID:
|
|
case PHY_INTERFACE_MODE_RGMII_TXID:
|
|
mode = AM33XX_GMII_SEL_MODE_RGMII;
|
|
break;
|
|
|
|
default:
|
|
dev_warn(priv->dev,
|
|
"Unsupported PHY mode: \"%s\". Defaulting to MII.\n",
|
|
phy_modes(phy_mode));
|
|
/* fallthrough */
|
|
case PHY_INTERFACE_MODE_MII:
|
|
mode = AM33XX_GMII_SEL_MODE_MII;
|
|
break;
|
|
};
|
|
|
|
switch (slave) {
|
|
case 0:
|
|
mask = GMII_SEL_MODE_MASK;
|
|
break;
|
|
case 1:
|
|
mask = GMII_SEL_MODE_MASK << 4;
|
|
mode <<= 4;
|
|
break;
|
|
default:
|
|
dev_err(priv->dev, "invalid slave number...\n");
|
|
return;
|
|
}
|
|
|
|
if (priv->rmii_clock_external)
|
|
dev_err(priv->dev, "RMII External clock is not supported\n");
|
|
|
|
reg &= ~mask;
|
|
reg |= mode;
|
|
|
|
writel(reg, priv->gmii_sel);
|
|
}
|
|
|
|
static struct platform_driver cpsw_phy_sel_driver;
|
|
static int match(struct device *dev, void *data)
|
|
{
|
|
struct device_node *node = (struct device_node *)data;
|
|
return dev->of_node == node &&
|
|
dev->driver == &cpsw_phy_sel_driver.driver;
|
|
}
|
|
|
|
void cpsw_phy_sel(struct device *dev, phy_interface_t phy_mode, int slave)
|
|
{
|
|
struct device_node *node;
|
|
struct cpsw_phy_sel_priv *priv;
|
|
|
|
node = of_parse_phandle(dev->of_node, "cpsw-phy-sel", 0);
|
|
if (!node) {
|
|
node = of_get_child_by_name(dev->of_node, "cpsw-phy-sel");
|
|
if (!node) {
|
|
dev_err(dev, "Phy mode driver DT not found\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
dev = bus_find_device(&platform_bus_type, NULL, node, match);
|
|
if (!dev) {
|
|
dev_err(dev, "unable to find platform device for %pOF\n", node);
|
|
goto out;
|
|
}
|
|
|
|
priv = dev_get_drvdata(dev);
|
|
|
|
priv->cpsw_phy_sel(priv, phy_mode, slave);
|
|
|
|
put_device(dev);
|
|
out:
|
|
of_node_put(node);
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_phy_sel);
|
|
|
|
static const struct of_device_id cpsw_phy_sel_id_table[] = {
|
|
{
|
|
.compatible = "ti,am3352-cpsw-phy-sel",
|
|
.data = &cpsw_gmii_sel_am3352,
|
|
},
|
|
{
|
|
.compatible = "ti,dra7xx-cpsw-phy-sel",
|
|
.data = &cpsw_gmii_sel_dra7xx,
|
|
},
|
|
{
|
|
.compatible = "ti,am43xx-cpsw-phy-sel",
|
|
.data = &cpsw_gmii_sel_am3352,
|
|
},
|
|
{}
|
|
};
|
|
|
|
static int cpsw_phy_sel_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *res;
|
|
const struct of_device_id *of_id;
|
|
struct cpsw_phy_sel_priv *priv;
|
|
|
|
of_id = of_match_node(cpsw_phy_sel_id_table, pdev->dev.of_node);
|
|
if (!of_id)
|
|
return -EINVAL;
|
|
|
|
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv) {
|
|
dev_err(&pdev->dev, "unable to alloc memory for cpsw phy sel\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->dev = &pdev->dev;
|
|
priv->cpsw_phy_sel = of_id->data;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gmii-sel");
|
|
priv->gmii_sel = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(priv->gmii_sel))
|
|
return PTR_ERR(priv->gmii_sel);
|
|
|
|
if (of_find_property(pdev->dev.of_node, "rmii-clock-ext", NULL))
|
|
priv->rmii_clock_external = true;
|
|
|
|
dev_set_drvdata(&pdev->dev, priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver cpsw_phy_sel_driver = {
|
|
.probe = cpsw_phy_sel_probe,
|
|
.driver = {
|
|
.name = "cpsw-phy-sel",
|
|
.of_match_table = cpsw_phy_sel_id_table,
|
|
},
|
|
};
|
|
builtin_platform_driver(cpsw_phy_sel_driver);
|