2020-05-03 14:41:14 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
/*
|
|
|
|
* Copyright 2020 NXP
|
|
|
|
*/
|
|
|
|
|
2021-07-20 18:15:28 +00:00
|
|
|
#define LOG_CATEGORY UCLASS_ETH_PHY
|
|
|
|
|
2020-05-03 14:41:14 +00:00
|
|
|
#include <common.h>
|
|
|
|
#include <dm.h>
|
2021-07-20 18:09:52 +00:00
|
|
|
#include <log.h>
|
2020-05-03 14:41:14 +00:00
|
|
|
#include <net.h>
|
2021-07-20 18:09:51 +00:00
|
|
|
#include <asm-generic/gpio.h>
|
2021-07-20 18:09:52 +00:00
|
|
|
#include <dm/device_compat.h>
|
2020-05-03 14:41:14 +00:00
|
|
|
#include <dm/device-internal.h>
|
|
|
|
#include <dm/uclass-internal.h>
|
|
|
|
#include <dm/lists.h>
|
2021-07-20 18:09:51 +00:00
|
|
|
#include <linux/delay.h>
|
2020-05-03 14:41:14 +00:00
|
|
|
|
|
|
|
struct eth_phy_device_priv {
|
|
|
|
struct mii_dev *mdio_bus;
|
2021-07-20 18:09:51 +00:00
|
|
|
struct gpio_desc reset_gpio;
|
|
|
|
u32 reset_assert_delay;
|
|
|
|
u32 reset_deassert_delay;
|
2020-05-03 14:41:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
int eth_phy_binds_nodes(struct udevice *eth_dev)
|
|
|
|
{
|
|
|
|
ofnode mdio_node, phy_node;
|
|
|
|
const char *node_name;
|
|
|
|
int ret;
|
|
|
|
|
2021-07-20 18:09:53 +00:00
|
|
|
/* search a subnode named "mdio.*" */
|
|
|
|
dev_for_each_subnode(mdio_node, eth_dev) {
|
|
|
|
node_name = ofnode_get_name(mdio_node);
|
|
|
|
if (!strncmp(node_name, "mdio", 4))
|
|
|
|
break;
|
|
|
|
}
|
2020-05-03 14:41:14 +00:00
|
|
|
if (!ofnode_valid(mdio_node)) {
|
2021-07-20 18:09:53 +00:00
|
|
|
dev_dbg(eth_dev, "%s: %s mdio subnode not found!\n", __func__,
|
2021-07-20 18:09:52 +00:00
|
|
|
eth_dev->name);
|
2020-05-03 14:41:14 +00:00
|
|
|
return -ENXIO;
|
|
|
|
}
|
2021-07-20 18:09:53 +00:00
|
|
|
dev_dbg(eth_dev, "%s: %s subnode found!\n", __func__, node_name);
|
2020-05-03 14:41:14 +00:00
|
|
|
|
|
|
|
ofnode_for_each_subnode(phy_node, mdio_node) {
|
|
|
|
node_name = ofnode_get_name(phy_node);
|
|
|
|
|
2021-07-20 18:09:52 +00:00
|
|
|
dev_dbg(eth_dev, "* Found child node: '%s'\n", node_name);
|
2020-05-03 14:41:14 +00:00
|
|
|
|
|
|
|
ret = device_bind_driver_to_node(eth_dev,
|
|
|
|
"eth_phy_generic_drv",
|
|
|
|
node_name, phy_node, NULL);
|
|
|
|
if (ret) {
|
2021-07-20 18:09:52 +00:00
|
|
|
dev_dbg(eth_dev, " - Eth phy binding error: %d\n", ret);
|
2020-05-03 14:41:14 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-07-20 18:09:52 +00:00
|
|
|
dev_dbg(eth_dev, " - bound phy device: '%s'\n", node_name);
|
2020-05-03 14:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int eth_phy_set_mdio_bus(struct udevice *eth_dev, struct mii_dev *mdio_bus)
|
|
|
|
{
|
|
|
|
struct udevice *dev;
|
|
|
|
struct eth_phy_device_priv *uc_priv;
|
|
|
|
|
|
|
|
for (uclass_first_device(UCLASS_ETH_PHY, &dev); dev;
|
|
|
|
uclass_next_device(&dev)) {
|
|
|
|
if (dev->parent == eth_dev) {
|
2020-12-23 02:30:28 +00:00
|
|
|
uc_priv = (struct eth_phy_device_priv *)(dev_get_uclass_priv(dev));
|
2020-05-03 14:41:14 +00:00
|
|
|
|
|
|
|
if (!uc_priv->mdio_bus)
|
|
|
|
uc_priv->mdio_bus = mdio_bus;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct mii_dev *eth_phy_get_mdio_bus(struct udevice *eth_dev)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct udevice *phy_dev;
|
|
|
|
struct eth_phy_device_priv *uc_priv;
|
|
|
|
|
|
|
|
/* Will probe the parent of phy device, then phy device */
|
|
|
|
ret = uclass_get_device_by_phandle(UCLASS_ETH_PHY, eth_dev,
|
|
|
|
"phy-handle", &phy_dev);
|
|
|
|
if (!ret) {
|
|
|
|
if (eth_dev != phy_dev->parent) {
|
|
|
|
/*
|
|
|
|
* phy_dev is shared and controlled by
|
|
|
|
* other eth controller
|
|
|
|
*/
|
2020-12-23 02:30:28 +00:00
|
|
|
uc_priv = (struct eth_phy_device_priv *)(dev_get_uclass_priv(phy_dev));
|
2020-05-03 14:41:14 +00:00
|
|
|
if (uc_priv->mdio_bus)
|
2021-07-20 18:09:52 +00:00
|
|
|
log_notice("Get shared mii bus on %s\n", eth_dev->name);
|
2020-05-03 14:41:14 +00:00
|
|
|
else
|
2021-07-20 18:09:52 +00:00
|
|
|
log_notice("Can't get shared mii bus on %s\n", eth_dev->name);
|
2020-05-03 14:41:14 +00:00
|
|
|
|
|
|
|
return uc_priv->mdio_bus;
|
|
|
|
}
|
|
|
|
} else {
|
2022-01-01 19:12:23 +00:00
|
|
|
log_debug("Can't find phy-handle for %s\n", eth_dev->name);
|
2020-05-03 14:41:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int eth_phy_get_addr(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct ofnode_phandle_args phandle_args;
|
|
|
|
int reg;
|
|
|
|
|
|
|
|
if (dev_read_phandle_with_args(dev, "phy-handle", NULL, 0, 0,
|
|
|
|
&phandle_args)) {
|
2021-07-20 18:09:52 +00:00
|
|
|
dev_dbg(dev, "Failed to find phy-handle");
|
2020-05-03 14:41:14 +00:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
reg = ofnode_read_u32_default(phandle_args.node, "reg", 0);
|
|
|
|
|
|
|
|
return reg;
|
|
|
|
}
|
|
|
|
|
2021-07-20 18:09:51 +00:00
|
|
|
/* parsing generic properties of devicetree/bindings/net/ethernet-phy.yaml */
|
|
|
|
static int eth_phy_of_to_plat(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct eth_phy_device_priv *uc_priv = dev_get_uclass_priv(dev);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!CONFIG_IS_ENABLED(DM_GPIO))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* search "reset-gpios" in phy node */
|
|
|
|
ret = gpio_request_by_name(dev, "reset-gpios", 0,
|
|
|
|
&uc_priv->reset_gpio,
|
2022-03-01 20:15:02 +00:00
|
|
|
GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
|
2021-11-13 02:23:11 +00:00
|
|
|
if (ret && ret != -ENOENT)
|
2021-07-20 18:09:51 +00:00
|
|
|
return ret;
|
|
|
|
|
|
|
|
uc_priv->reset_assert_delay = dev_read_u32_default(dev, "reset-assert-us", 0);
|
|
|
|
uc_priv->reset_deassert_delay = dev_read_u32_default(dev, "reset-deassert-us", 0);
|
|
|
|
|
2023-05-30 22:51:27 +00:00
|
|
|
/* These are used by some DTs, try these as a fallback. */
|
|
|
|
if (!uc_priv->reset_assert_delay && !uc_priv->reset_deassert_delay) {
|
|
|
|
uc_priv->reset_assert_delay =
|
|
|
|
dev_read_u32_default(dev, "reset-delay-us", 0);
|
|
|
|
uc_priv->reset_deassert_delay =
|
|
|
|
dev_read_u32_default(dev, "reset-post-delay-us", 0);
|
|
|
|
}
|
|
|
|
|
2021-07-20 18:09:51 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-05-30 22:51:18 +00:00
|
|
|
static void eth_phy_reset(struct udevice *dev, int value)
|
2021-07-20 18:09:51 +00:00
|
|
|
{
|
|
|
|
struct eth_phy_device_priv *uc_priv = dev_get_uclass_priv(dev);
|
|
|
|
u32 delay;
|
|
|
|
|
|
|
|
if (!CONFIG_IS_ENABLED(DM_GPIO))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!dm_gpio_is_valid(&uc_priv->reset_gpio))
|
|
|
|
return;
|
|
|
|
|
|
|
|
dm_gpio_set_value(&uc_priv->reset_gpio, value);
|
|
|
|
|
|
|
|
delay = value ? uc_priv->reset_assert_delay : uc_priv->reset_deassert_delay;
|
|
|
|
if (delay)
|
|
|
|
udelay(delay);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int eth_phy_pre_probe(struct udevice *dev)
|
|
|
|
{
|
|
|
|
/* Assert and deassert the reset signal */
|
|
|
|
eth_phy_reset(dev, 1);
|
|
|
|
eth_phy_reset(dev, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-05-03 14:41:14 +00:00
|
|
|
UCLASS_DRIVER(eth_phy_generic) = {
|
|
|
|
.id = UCLASS_ETH_PHY,
|
|
|
|
.name = "eth_phy_generic",
|
2020-12-03 23:55:17 +00:00
|
|
|
.per_device_auto = sizeof(struct eth_phy_device_priv),
|
2021-07-20 18:09:51 +00:00
|
|
|
.pre_probe = eth_phy_pre_probe,
|
2020-05-03 14:41:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
U_BOOT_DRIVER(eth_phy_generic_drv) = {
|
|
|
|
.name = "eth_phy_generic_drv",
|
|
|
|
.id = UCLASS_ETH_PHY,
|
2021-07-20 18:09:51 +00:00
|
|
|
.of_to_plat = eth_phy_of_to_plat,
|
2020-05-03 14:41:14 +00:00
|
|
|
};
|