// 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 "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;

	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 (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);
