| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * ENETC ethernet controller driver | |
| * Copyright 2019-2025 NXP | |
| */ | |
| #include <dm.h> | |
| #include <errno.h> | |
| #include <pci.h> | |
| #include <miiphy.h> | |
| #include <asm/io.h> | |
| #include <asm/processor.h> | |
| #include <miiphy.h> | |
| #include <linux/delay.h> | |
| #include <power/regulator.h> | |
| #include "fsl_enetc.h" | |
| static u32 enetc_read(struct enetc_mdio_priv *priv, u32 off) | |
| { | |
| return readl(priv->regs_base + off); | |
| } | |
| static void enetc_write(struct enetc_mdio_priv *priv, u32 off, u32 val) | |
| { | |
| writel(val, priv->regs_base + off); | |
| } | |
| static void enetc_mdio_wait_bsy(struct enetc_mdio_priv *priv) | |
| { | |
| int to = 10000; | |
| while ((enetc_read(priv, ENETC_MDIO_CFG) & ENETC_EMDIO_CFG_BSY) && | |
| --to) | |
| cpu_relax(); | |
| if (!to) | |
| printf("T"); | |
| } | |
| int enetc_mdio_read_priv(struct enetc_mdio_priv *priv, int addr, int devad, | |
| int reg) | |
| { | |
| if (devad == MDIO_DEVAD_NONE) | |
| enetc_write(priv, ENETC_MDIO_CFG, ENETC_EMDIO_CFG_C22); | |
| else | |
| enetc_write(priv, ENETC_MDIO_CFG, ENETC_EMDIO_CFG_C45); | |
| enetc_mdio_wait_bsy(priv); | |
| if (devad == MDIO_DEVAD_NONE) { | |
| enetc_write(priv, ENETC_MDIO_CTL, ENETC_MDIO_CTL_READ | | |
| (addr << 5) | reg); | |
| } else { | |
| enetc_write(priv, ENETC_MDIO_CTL, (addr << 5) + devad); | |
| enetc_mdio_wait_bsy(priv); | |
| enetc_write(priv, ENETC_MDIO_STAT, reg); | |
| enetc_mdio_wait_bsy(priv); | |
| enetc_write(priv, ENETC_MDIO_CTL, ENETC_MDIO_CTL_READ | | |
| (addr << 5) | devad); | |
| } | |
| enetc_mdio_wait_bsy(priv); | |
| if (enetc_read(priv, ENETC_MDIO_CFG) & ENETC_EMDIO_CFG_RD_ER) | |
| return ENETC_MDIO_READ_ERR; | |
| return enetc_read(priv, ENETC_MDIO_DATA); | |
| } | |
| int enetc_mdio_write_priv(struct enetc_mdio_priv *priv, int addr, int devad, | |
| int reg, u16 val) | |
| { | |
| if (devad == MDIO_DEVAD_NONE) | |
| enetc_write(priv, ENETC_MDIO_CFG, ENETC_EMDIO_CFG_C22); | |
| else | |
| enetc_write(priv, ENETC_MDIO_CFG, ENETC_EMDIO_CFG_C45); | |
| enetc_mdio_wait_bsy(priv); | |
| if (devad != MDIO_DEVAD_NONE) { | |
| enetc_write(priv, ENETC_MDIO_CTL, (addr << 5) + devad); | |
| enetc_write(priv, ENETC_MDIO_STAT, reg); | |
| } else { | |
| enetc_write(priv, ENETC_MDIO_CTL, (addr << 5) + reg); | |
| } | |
| enetc_mdio_wait_bsy(priv); | |
| enetc_write(priv, ENETC_MDIO_DATA, val); | |
| enetc_mdio_wait_bsy(priv); | |
| return 0; | |
| } | |
| /* DM wrappers */ | |
| static int dm_enetc_mdio_read(struct udevice *dev, int addr, int devad, int reg) | |
| { | |
| struct enetc_mdio_priv *priv = dev_get_priv(dev); | |
| return enetc_mdio_read_priv(priv, addr, devad, reg); | |
| } | |
| static int dm_enetc_mdio_write(struct udevice *dev, int addr, int devad, | |
| int reg, u16 val) | |
| { | |
| struct enetc_mdio_priv *priv = dev_get_priv(dev); | |
| return enetc_mdio_write_priv(priv, addr, devad, reg, val); | |
| } | |
| static const struct mdio_ops enetc_mdio_ops = { | |
| .read = dm_enetc_mdio_read, | |
| .write = dm_enetc_mdio_write, | |
| }; | |
| static int enetc_mdio_bind(struct udevice *dev) | |
| { | |
| char name[16]; | |
| static int eth_num_devices; | |
| /* | |
| * prefer using PCI function numbers to number interfaces, but these | |
| * are only available if dts nodes are present. For PCI they are | |
| * optional, handle that case too. Just in case some nodes are present | |
| * and some are not, use different naming scheme - enetc-N based on | |
| * PCI function # and enetc#N based on interface count | |
| */ | |
| if (ofnode_valid(dev_ofnode(dev))) | |
| sprintf(name, "emdio-%u", PCI_FUNC(pci_get_devfn(dev))); | |
| else | |
| sprintf(name, "emdio#%u", eth_num_devices++); | |
| device_set_name(dev, name); | |
| return 0; | |
| } | |
| static int enetc_mdio_probe(struct udevice *dev) | |
| { | |
| struct pci_child_plat *pplat = dev_get_parent_plat(dev); | |
| struct enetc_mdio_priv *priv = dev_get_priv(dev); | |
| u16 cmd = PCI_COMMAND_MEMORY; | |
| int ret; | |
| struct udevice *supply = NULL; | |
| priv->regs_base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, 0, 0, PCI_REGION_TYPE, 0); | |
| if (!priv->regs_base) { | |
| enetc_dbg(dev, "failed to map BAR0\n"); | |
| return -EINVAL; | |
| } | |
| priv->regs_base += ENETC_MDIO_BASE; | |
| if (CONFIG_IS_ENABLED(DM_REGULATOR)) { | |
| ret = device_get_supply_regulator(dev, "phy-supply", | |
| &supply); | |
| if (ret && ret != -ENOENT) { | |
| printf("%s: device_get_supply_regulator failed: %d\n", | |
| __func__, ret); | |
| return ret; | |
| } | |
| if (supply) { | |
| regulator_set_enable(supply, false); | |
| mdelay(100); | |
| ret = regulator_set_enable_if_allowed(supply, true); | |
| if (ret) { | |
| printf("%s: Error enabling phy supply\n", dev->name); | |
| return ret; | |
| } | |
| } | |
| } | |
| if (pplat->vendor == PCI_VENDOR_ID_PHILIPS) /* i.MX95 */ | |
| cmd |= PCI_COMMAND_MASTER; | |
| dm_pci_clrset_config16(dev, PCI_COMMAND, 0, cmd); | |
| return 0; | |
| } | |
| U_BOOT_DRIVER(enetc_mdio) = { | |
| .name = "enetc_mdio", | |
| .id = UCLASS_MDIO, | |
| .bind = enetc_mdio_bind, | |
| .probe = enetc_mdio_probe, | |
| .ops = &enetc_mdio_ops, | |
| .priv_auto = sizeof(struct enetc_mdio_priv), | |
| }; | |
| static struct pci_device_id enetc_mdio_ids[] = { | |
| { PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, PCI_DEVICE_ID_ENETC_MDIO) }, | |
| { PCI_DEVICE(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_ENETC4_EMDIO) }, | |
| { } | |
| }; | |
| U_BOOT_PCI_DEVICE(enetc_mdio, enetc_mdio_ids); |