// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2018 Marvell International Ltd.
 * Author: Ken Ma<make@marvell.com>
 */

#include <common.h>
#include <dm.h>
#include <dm/device-internal.h>
#include <dm/lists.h>
#include <miiphy.h>
#include <phy.h>
#include <asm/io.h>
#include <wait_bit.h>
#include <linux/bitops.h>

#define MVMDIO_SMI_DATA_SHIFT		0
#define MVMDIO_SMI_PHY_ADDR_SHIFT	16
#define MVMDIO_SMI_PHY_REG_SHIFT	21
#define MVMDIO_SMI_READ_OPERATION	BIT(26)
#define MVMDIO_SMI_WRITE_OPERATION	0
#define MVMDIO_SMI_READ_VALID		BIT(27)
#define MVMDIO_SMI_BUSY			BIT(28)

#define MVMDIO_XSMI_MGNT_REG		0x0
#define MVMDIO_XSMI_PHYADDR_SHIFT	16
#define MVMDIO_XSMI_DEVADDR_SHIFT	21
#define MVMDIO_XSMI_WRITE_OPERATION	(0x5 << 26)
#define MVMDIO_XSMI_READ_OPERATION	(0x7 << 26)
#define MVMDIO_XSMI_READ_VALID		BIT(29)
#define MVMDIO_XSMI_BUSY		BIT(30)
#define MVMDIO_XSMI_ADDR_REG		0x8

enum mvmdio_bus_type {
	BUS_TYPE_SMI,
	BUS_TYPE_XSMI
};

struct mvmdio_priv {
	void *mdio_base;
	enum mvmdio_bus_type type;
};

static int mvmdio_smi_read(struct udevice *dev, int addr,
			   int devad, int reg)
{
	struct mvmdio_priv *priv = dev_get_priv(dev);
	u32 val;
	int ret;

	if (devad != MDIO_DEVAD_NONE)
		return -EOPNOTSUPP;

	ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_SMI_BUSY,
				false, CONFIG_SYS_HZ, false);
	if (ret < 0)
		return ret;

	writel(((addr << MVMDIO_SMI_PHY_ADDR_SHIFT) |
		(reg << MVMDIO_SMI_PHY_REG_SHIFT)  |
		MVMDIO_SMI_READ_OPERATION),
	       priv->mdio_base);

	ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_SMI_BUSY,
				false, CONFIG_SYS_HZ, false);
	if (ret < 0)
		return ret;

	val = readl(priv->mdio_base);
	if (!(val & MVMDIO_SMI_READ_VALID)) {
		pr_err("SMI bus read not valid\n");
		return -ENODEV;
	}

	return val & GENMASK(15, 0);
}

static int mvmdio_smi_write(struct udevice *dev, int addr, int devad,
			    int reg, u16 value)
{
	struct mvmdio_priv *priv = dev_get_priv(dev);
	int ret;

	if (devad != MDIO_DEVAD_NONE)
		return -EOPNOTSUPP;

	ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_SMI_BUSY,
				false, CONFIG_SYS_HZ, false);
	if (ret < 0)
		return ret;

	writel(((addr << MVMDIO_SMI_PHY_ADDR_SHIFT) |
		(reg << MVMDIO_SMI_PHY_REG_SHIFT)  |
		MVMDIO_SMI_WRITE_OPERATION            |
		(value << MVMDIO_SMI_DATA_SHIFT)),
	       priv->mdio_base);

	return 0;
}

static int mvmdio_xsmi_read(struct udevice *dev, int addr,
			    int devad, int reg)
{
	struct mvmdio_priv *priv = dev_get_priv(dev);
	int ret;

	if (devad == MDIO_DEVAD_NONE)
		return -EOPNOTSUPP;

	ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_XSMI_BUSY,
				false, CONFIG_SYS_HZ, false);
	if (ret < 0)
		return ret;

	writel(reg & GENMASK(15, 0), priv->mdio_base + MVMDIO_XSMI_ADDR_REG);
	writel(((addr << MVMDIO_XSMI_PHYADDR_SHIFT) |
		(devad << MVMDIO_XSMI_DEVADDR_SHIFT) |
		MVMDIO_XSMI_READ_OPERATION),
	       priv->mdio_base + MVMDIO_XSMI_MGNT_REG);

	ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_XSMI_BUSY,
				false, CONFIG_SYS_HZ, false);
	if (ret < 0)
		return ret;

	if (!(readl(priv->mdio_base + MVMDIO_XSMI_MGNT_REG) &
	      MVMDIO_XSMI_READ_VALID)) {
		pr_err("XSMI bus read not valid\n");
		return -ENODEV;
	}

	return readl(priv->mdio_base + MVMDIO_XSMI_MGNT_REG) & GENMASK(15, 0);
}

static int mvmdio_xsmi_write(struct udevice *dev, int addr, int devad,
			     int reg, u16 value)
{
	struct mvmdio_priv *priv = dev_get_priv(dev);
	int ret;

	if (devad == MDIO_DEVAD_NONE)
		return -EOPNOTSUPP;

	ret = wait_for_bit_le32(priv->mdio_base, MVMDIO_XSMI_BUSY,
				false, CONFIG_SYS_HZ, false);
	if (ret < 0)
		return ret;

	writel(reg & GENMASK(15, 0), priv->mdio_base + MVMDIO_XSMI_ADDR_REG);
	writel(((addr << MVMDIO_XSMI_PHYADDR_SHIFT) |
		(devad << MVMDIO_XSMI_DEVADDR_SHIFT) |
		MVMDIO_XSMI_WRITE_OPERATION | value),
	       priv->mdio_base + MVMDIO_XSMI_MGNT_REG);

	return 0;
}

static int mvmdio_read(struct udevice *dev, int addr, int devad, int reg)
{
	struct mvmdio_priv *priv = dev_get_priv(dev);
	int err = -ENOTSUPP;

	switch (priv->type) {
	case BUS_TYPE_SMI:
		err = mvmdio_smi_read(dev, addr, devad, reg);
		break;
	case BUS_TYPE_XSMI:
		err = mvmdio_xsmi_read(dev, addr, devad, reg);
		break;
	}

	return err;
}

static int mvmdio_write(struct udevice *dev, int addr, int devad, int reg,
			u16 value)
{
	struct mvmdio_priv *priv = dev_get_priv(dev);
	int err = -ENOTSUPP;

	switch (priv->type) {
	case BUS_TYPE_SMI:
		err = mvmdio_smi_write(dev, addr, devad, reg, value);
		break;
	case BUS_TYPE_XSMI:
		err = mvmdio_xsmi_write(dev, addr, devad, reg, value);
		break;
	}

	return err;
}

/*
 * Name the device, we use the device tree node name.
 * This can be overwritten by MDIO class code if device-name property is
 * present.
 */
static int mvmdio_bind(struct udevice *dev)
{
	if (ofnode_valid(dev_ofnode(dev)))
		device_set_name(dev, ofnode_get_name(dev_ofnode(dev)));

	return 0;
}

/* Get device base address and type, either C22 SMII or C45 XSMI */
static int mvmdio_probe(struct udevice *dev)
{
	struct mvmdio_priv *priv = dev_get_priv(dev);

	priv->mdio_base = (void *)dev_read_addr(dev);
	priv->type = (enum mvmdio_bus_type)dev_get_driver_data(dev);

	return 0;
}

static const struct mdio_ops mvmdio_ops = {
	.read = mvmdio_read,
	.write = mvmdio_write,
};

static const struct udevice_id mvmdio_ids[] = {
	{ .compatible = "marvell,orion-mdio", .data = BUS_TYPE_SMI },
	{ .compatible = "marvell,xmdio", .data = BUS_TYPE_XSMI },
	{ }
};

U_BOOT_DRIVER(mvmdio) = {
	.name			= "mvmdio",
	.id			= UCLASS_MDIO,
	.of_match		= mvmdio_ids,
	.bind			= mvmdio_bind,
	.probe			= mvmdio_probe,
	.ops			= &mvmdio_ops,
	.priv_auto	= sizeof(struct mvmdio_priv),
};
