// SPDX-License-Identifier: GPL-2.0+
/*
 * NXP C45 PHY driver
 *
 * Copyright 2021 NXP
 * Author: Radu Pirea <radu-nicolae.pirea@oss.nxp.com>
 */
#include <common.h>
#include <dm.h>
#include <dm/devres.h>
#include <linux/delay.h>
#include <linux/math64.h>
#include <linux/mdio.h>
#include <phy.h>

#define PHY_ID_TJA_1103			0x001BB010

#define VEND1_DEVICE_CONTROL		0x0040
#define DEVICE_CONTROL_RESET		BIT(15)
#define DEVICE_CONTROL_CONFIG_GLOBAL_EN	BIT(14)
#define DEVICE_CONTROL_CONFIG_ALL_EN	BIT(13)

#define VEND1_PORT_CONTROL		0x8040
#define PORT_CONTROL_EN			BIT(14)

#define VEND1_PHY_CONTROL		0x8100
#define PHY_CONFIG_EN			BIT(14)
#define PHY_START_OP			BIT(0)

#define VEND1_PHY_CONFIG		0x8108
#define PHY_CONFIG_AUTO			BIT(0)

#define VEND1_PORT_INFRA_CONTROL	0xAC00
#define PORT_INFRA_CONTROL_EN		BIT(14)

#define VEND1_RXID			0xAFCC
#define VEND1_TXID			0xAFCD
#define ID_ENABLE			BIT(15)

#define VEND1_ABILITIES			0xAFC4
#define RGMII_ID_ABILITY		BIT(15)
#define RGMII_ABILITY			BIT(14)
#define RMII_ABILITY			BIT(10)
#define REVMII_ABILITY			BIT(9)
#define MII_ABILITY			BIT(8)
#define SGMII_ABILITY			BIT(0)

#define VEND1_MII_BASIC_CONFIG		0xAFC6
#define MII_BASIC_CONFIG_REV		BIT(8)
#define MII_BASIC_CONFIG_SGMII		0x9
#define MII_BASIC_CONFIG_RGMII		0x7
#define MII_BASIC_CONFIG_RMII		0x5
#define MII_BASIC_CONFIG_MII		0x4

#define RGMII_PERIOD_PS			8000U
#define PS_PER_DEGREE			div_u64(RGMII_PERIOD_PS, 360)
#define MIN_ID_PS			1644U
#define MAX_ID_PS			2260U
#define DEFAULT_ID_PS			2000U

#define RESET_DELAY_MS			25
#define CONF_EN_DELAY_US		450

struct nxp_c45_phy {
	u32 tx_delay;
	u32 rx_delay;
};

static int nxp_c45_soft_reset(struct phy_device *phydev)
{
	int tries = 10, ret;

	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_DEVICE_CONTROL,
			    DEVICE_CONTROL_RESET);
	if (ret)
		return ret;

	do {
		ret = phy_read_mmd(phydev, MDIO_MMD_VEND1,
				   VEND1_DEVICE_CONTROL);
		if (!(ret & DEVICE_CONTROL_RESET))
			return 0;
		mdelay(RESET_DELAY_MS);
	} while (tries--);

	return -EIO;
}

static int nxp_c45_start_op(struct phy_device *phydev)
{
	return phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_PHY_CONTROL,
			     PHY_START_OP);
}

static int nxp_c45_config_enable(struct phy_device *phydev)
{
	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_DEVICE_CONTROL,
		      DEVICE_CONTROL_CONFIG_GLOBAL_EN |
		      DEVICE_CONTROL_CONFIG_ALL_EN);
	udelay(CONF_EN_DELAY_US);

	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_PORT_CONTROL,
		      PORT_CONTROL_EN);
	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_PHY_CONTROL,
		      PHY_CONFIG_EN);
	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_PORT_INFRA_CONTROL,
		      PORT_INFRA_CONTROL_EN);

	return 0;
}

static u64 nxp_c45_get_phase_shift(u64 phase_offset_raw)
{
	/* The delay in degree phase is 73.8 + phase_offset_raw * 0.9.
	 * To avoid floating point operations we'll multiply by 10
	 * and get 1 decimal point precision.
	 */
	phase_offset_raw *= 10;
	phase_offset_raw -= 738;
	return div_u64(phase_offset_raw, 9);
}

static void nxp_c45_disable_delays(struct phy_device *phydev)
{
	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_TXID, 0);
	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_RXID, 0);
}

static int nxp_c45_check_delay(struct phy_device *phydev, u32 delay)
{
	if (delay < MIN_ID_PS) {
		pr_err("%s: delay value smaller than %u\n",
		       phydev->drv->name, MIN_ID_PS);
		return -EINVAL;
	}

	if (delay > MAX_ID_PS) {
		pr_err("%s: delay value higher than %u\n",
		       phydev->drv->name, MAX_ID_PS);
		return -EINVAL;
	}

	return 0;
}

static int nxp_c45_get_delays(struct phy_device *phydev)
{
	struct nxp_c45_phy *priv = phydev->priv;
	int ret;

	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
		ret = dev_read_u32(phydev->dev, "tx-internal-delay-ps",
				   &priv->tx_delay);
		if (ret)
			priv->tx_delay = DEFAULT_ID_PS;

		ret = nxp_c45_check_delay(phydev, priv->tx_delay);
		if (ret) {
			pr_err("%s: tx-internal-delay-ps invalid value\n",
			       phydev->drv->name);
			return ret;
		}
	}

	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
	    phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
		ret = dev_read_u32(phydev->dev, "rx-internal-delay-ps",
				   &priv->rx_delay);
		if (ret)
			priv->rx_delay = DEFAULT_ID_PS;

		ret = nxp_c45_check_delay(phydev, priv->rx_delay);
		if (ret) {
			pr_err("%s: rx-internal-delay-ps invalid value\n",
			       phydev->drv->name);
			return ret;
		}
	}

	return 0;
}

static void nxp_c45_set_delays(struct phy_device *phydev)
{
	struct nxp_c45_phy *priv = phydev->priv;
	u64 tx_delay = priv->tx_delay;
	u64 rx_delay = priv->rx_delay;
	u64 degree;

	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
		degree = div_u64(tx_delay, PS_PER_DEGREE);
		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_TXID,
			      ID_ENABLE | nxp_c45_get_phase_shift(degree));
	} else {
		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_TXID, 0);
	}

	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
	    phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
		degree = div_u64(rx_delay, PS_PER_DEGREE);
		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_RXID,
			      ID_ENABLE | nxp_c45_get_phase_shift(degree));
	} else {
		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_RXID, 0);
	}
}

static int nxp_c45_set_phy_mode(struct phy_device *phydev)
{
	int ret;

	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_ABILITIES);
	pr_debug("%s: Clause 45 managed PHY abilities 0x%x\n",
		 phydev->drv->name, ret);

	switch (phydev->interface) {
	case PHY_INTERFACE_MODE_RGMII:
		if (!(ret & RGMII_ABILITY)) {
			pr_err("%s: rgmii mode not supported\n",
			       phydev->drv->name);
			return -EINVAL;
		}
		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
			      MII_BASIC_CONFIG_RGMII);
		nxp_c45_disable_delays(phydev);
		break;
	case PHY_INTERFACE_MODE_RGMII_ID:
	case PHY_INTERFACE_MODE_RGMII_TXID:
	case PHY_INTERFACE_MODE_RGMII_RXID:
		if (!(ret & RGMII_ID_ABILITY)) {
			pr_err("%s: rgmii-id, rgmii-txid, rgmii-rxid modes are not supported\n",
			       phydev->drv->name);
			return -EINVAL;
		}
		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
			      MII_BASIC_CONFIG_RGMII);
		ret = nxp_c45_get_delays(phydev);
		if (ret)
			return ret;

		nxp_c45_set_delays(phydev);
		break;
	case PHY_INTERFACE_MODE_MII:
		if (!(ret & MII_ABILITY)) {
			pr_err("%s: mii mode not supported\n",
			       phydev->drv->name);
			return -EINVAL;
		}
		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
			      MII_BASIC_CONFIG_MII);
		break;
	case PHY_INTERFACE_MODE_RMII:
		if (!(ret & RMII_ABILITY)) {
			pr_err("%s: rmii mode not supported\n",
			       phydev->drv->name);
			return -EINVAL;
		}
		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
			      MII_BASIC_CONFIG_RMII);
		break;
	case PHY_INTERFACE_MODE_SGMII:
		if (!(ret & SGMII_ABILITY)) {
			pr_err("%s: sgmii mode not supported\n",
			       phydev->drv->name);
			return -EINVAL;
		}
		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_MII_BASIC_CONFIG,
			      MII_BASIC_CONFIG_SGMII);
		break;
	case PHY_INTERFACE_MODE_INTERNAL:
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int nxp_c45_config(struct phy_device *phydev)
{
	int ret;

	ret = nxp_c45_soft_reset(phydev);
	if (ret)
		return ret;

	ret = nxp_c45_config_enable(phydev);
	if (ret) {
		pr_err("%s: Failed to enable config\n", phydev->drv->name);
		return ret;
	}

	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_PHY_CONFIG,
		      PHY_CONFIG_AUTO);

	ret = nxp_c45_set_phy_mode(phydev);
	if (ret) {
		pr_err("%s: Failed to set phy mode\n", phydev->drv->name);
		return ret;
	}

	phydev->autoneg = AUTONEG_DISABLE;

	return nxp_c45_start_op(phydev);
}

static int nxp_c45_startup(struct phy_device *phydev)
{
	u32 reg;

	reg = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_STAT1);
	phydev->link = !!(reg & MDIO_STAT1_LSTATUS);
	phydev->speed = SPEED_100;
	phydev->duplex = DUPLEX_FULL;
	return 0;
}

static int nxp_c45_probe(struct phy_device *phydev)
{
	struct nxp_c45_phy *priv;

	priv = devm_kzalloc(phydev->priv, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	phydev->priv = priv;

	return 0;
}

static struct phy_driver nxp_tja11xx = {
	.name = "NXP C45 TJA1103",
	.uid  = PHY_ID_TJA_1103,
	.mask = 0xfffff0,
	.features = PHY_100BT1_FEATURES,
	.probe = &nxp_c45_probe,
	.config = &nxp_c45_config,
	.startup = &nxp_c45_startup,
	.shutdown = &genphy_shutdown,
};

int phy_nxp_tja11xx_init(void)
{
	phy_register(&nxp_tja11xx);
	return 0;
}
