// SPDX-License-Identifier: GPL-2.0-only

#include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/gfp_types.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#include <linux/slab.h>
#include <linux/types.h>

#define EQ5_PHY_COUNT	2

#define EQ5_PHY0_GP	0x128
#define EQ5_PHY1_GP	0x12c
#define EQ5_PHY0_SGMII	0x134
#define EQ5_PHY1_SGMII	0x138

#define EQ5_GP_TX_SWRST_DIS	BIT(0)		// Tx SW reset
#define EQ5_GP_TX_M_CLKE	BIT(1)		// Tx M clock enable
#define EQ5_GP_SYS_SWRST_DIS	BIT(2)		// Sys SW reset
#define EQ5_GP_SYS_M_CLKE	BIT(3)		// Sys clock enable
#define EQ5_GP_SGMII_MODE	BIT(4)		// SGMII mode
#define EQ5_GP_RGMII_DRV	GENMASK(8, 5)	// RGMII drive strength

#define EQ5_SGMII_PWR_EN	BIT(0)
#define EQ5_SGMII_RST_DIS	BIT(1)
#define EQ5_SGMII_PLL_EN	BIT(2)
#define EQ5_SGMII_SIG_DET_SW	BIT(3)
#define EQ5_SGMII_PWR_STATE	BIT(4)
#define EQ5_SGMII_PLL_ACK	BIT(18)
#define EQ5_SGMII_PWR_STATE_ACK	GENMASK(24, 20)

/*
 * Instead of storing a phy_interface_t, we store this enum.
 *
 * We do not deal with RGMII timings in this generic PHY driver,
 * it is all handled inside the net PHY.
 */
enum eq5_phy_submode {
	EQ5_PHY_SUBMODE_SGMII,
	EQ5_PHY_SUBMODE_RGMII,
};

struct eq5_phy_inst {
	struct device		*dev;
	struct phy		*phy;
	void __iomem		*gp, *sgmii;
	enum eq5_phy_submode	submode;
	bool			sgmii_support;
};

struct eq5_phy_private {
	struct eq5_phy_inst	phys[EQ5_PHY_COUNT];
};

static int eq5_phy_exit(struct phy *phy)
{
	struct eq5_phy_inst *inst = phy_get_drvdata(phy);

	writel(0, inst->gp);
	writel(0, inst->sgmii);
	udelay(5); /* settling time */
	return 0;
}

static int eq5_phy_init(struct phy *phy)
{
	struct eq5_phy_inst *inst = phy_get_drvdata(phy);
	u32 reg;

	/*
	 * Hardware stops listening to our instructions once it is started.
	 * It must be reset to reconfigure it.
	 */
	eq5_phy_exit(phy);

	reg = EQ5_GP_TX_SWRST_DIS | EQ5_GP_TX_M_CLKE |
	      EQ5_GP_SYS_SWRST_DIS | EQ5_GP_SYS_M_CLKE |
	      FIELD_PREP(EQ5_GP_RGMII_DRV, 0x9);
	writel(reg, inst->gp);

	return 0;
}

static int eq5_phy_power_on(struct phy *phy)
{
	struct eq5_phy_inst *inst = phy_get_drvdata(phy);
	u32 reg;

	if (inst->submode == EQ5_PHY_SUBMODE_SGMII) {
		writel(readl(inst->gp) | EQ5_GP_SGMII_MODE, inst->gp);

		reg = EQ5_SGMII_PWR_EN | EQ5_SGMII_RST_DIS | EQ5_SGMII_PLL_EN;
		writel(reg, inst->sgmii);

		if (readl_poll_timeout(inst->sgmii, reg,
				       reg & EQ5_SGMII_PLL_ACK, 1, 100)) {
			dev_err(inst->dev, "PLL timeout\n");
			return -ETIMEDOUT;
		}

		reg = readl(inst->sgmii);
		reg |= EQ5_SGMII_PWR_STATE | EQ5_SGMII_SIG_DET_SW;
		writel(reg, inst->sgmii);
	} else {
		writel(readl(inst->gp) & ~EQ5_GP_SGMII_MODE, inst->gp);
		writel(0, inst->sgmii);
	}

	return 0;
}

static int eq5_phy_power_off(struct phy *phy)
{
	struct eq5_phy_inst *inst = phy_get_drvdata(phy);

	writel(readl(inst->gp) & ~EQ5_GP_SGMII_MODE, inst->gp);
	writel(0, inst->sgmii);

	return 0;
}

static int eq5_phy_validate(struct phy *phy, enum phy_mode mode, int submode,
			    union phy_configure_opts *opts)
{
	struct eq5_phy_inst *inst = phy_get_drvdata(phy);

	if (mode != PHY_MODE_ETHERNET)
		return -EINVAL;

	if (phy_interface_mode_is_rgmii(submode))
		return 0;

	if (inst->sgmii_support && submode == PHY_INTERFACE_MODE_SGMII)
		return 0;

	return -EINVAL;
}

static int eq5_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
{
	struct eq5_phy_inst *inst = phy_get_drvdata(phy);
	enum eq5_phy_submode target_submode;
	int ret;

	ret = eq5_phy_validate(phy, mode, submode, NULL);
	if (ret)
		return ret;

	if (submode == PHY_INTERFACE_MODE_SGMII)
		target_submode = EQ5_PHY_SUBMODE_SGMII;
	else
		target_submode = EQ5_PHY_SUBMODE_RGMII;

	if (target_submode == inst->submode)
		return 0;

	inst->submode = target_submode;

	if (phy->power_count) {
		eq5_phy_init(phy);
		return eq5_phy_power_on(phy);
	}

	return 0;
}

static const struct phy_ops eq5_phy_ops = {
	.init		= eq5_phy_init,
	.exit		= eq5_phy_exit,
	.power_on	= eq5_phy_power_on,
	.power_off	= eq5_phy_power_off,
	.set_mode	= eq5_phy_set_mode,
	.validate	= eq5_phy_validate,
};

static struct phy *eq5_phy_xlate(struct device *dev,
				 const struct of_phandle_args *args)
{
	struct eq5_phy_private *priv = dev_get_drvdata(dev);

	if (args->args_count != 1 || args->args[0] >= EQ5_PHY_COUNT)
		return ERR_PTR(-EINVAL);

	return priv->phys[args->args[0]].phy;
}

static int eq5_phy_probe_phy(struct device *dev, struct eq5_phy_private *priv,
			     unsigned int index, void __iomem *base,
			     unsigned int gp, unsigned int sgmii,
			     bool sgmii_support)
{
	struct eq5_phy_inst *inst = &priv->phys[index];
	struct phy *phy;

	phy = devm_phy_create(dev, dev->of_node, &eq5_phy_ops);
	if (IS_ERR(phy))
		return dev_err_probe(dev, PTR_ERR(phy),
				     "failed to create PHY %u\n", index);

	inst->dev = dev;
	inst->phy = phy;
	inst->gp = base + gp;
	inst->sgmii = base + sgmii;
	inst->sgmii_support = sgmii_support;
	phy_set_drvdata(phy, inst);

	/*
	 * Init inst->submode based on probe hardware state, allowing
	 * consumers to power us on without first setting the mode.
	 */
	if (sgmii_support && (readl(inst->gp) & EQ5_GP_SGMII_MODE))
		inst->submode = EQ5_PHY_SUBMODE_SGMII;
	else
		inst->submode = EQ5_PHY_SUBMODE_RGMII;

	return 0;
}

static int eq5_phy_probe(struct auxiliary_device *adev,
			 const struct auxiliary_device_id *id)
{
	struct device *dev = &adev->dev;
	struct phy_provider *provider;
	struct eq5_phy_private *priv;
	void __iomem *base;
	int ret;

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

	dev_set_drvdata(dev, priv);

	base = (void __iomem *)dev_get_platdata(dev);

	ret = eq5_phy_probe_phy(dev, priv, 0, base, EQ5_PHY0_GP,
				EQ5_PHY0_SGMII, true);
	if (ret)
		return ret;

	ret = eq5_phy_probe_phy(dev, priv, 1, base, EQ5_PHY1_GP,
				EQ5_PHY1_SGMII, false);
	if (ret)
		return ret;

	provider = devm_of_phy_provider_register(dev, eq5_phy_xlate);
	if (IS_ERR(provider))
		return dev_err_probe(dev, PTR_ERR(provider),
				     "registering provider failed\n");

	return 0;
}

static const struct auxiliary_device_id eq5_phy_id_table[] = {
	{ .name = "clk_eyeq.phy" },
	{}
};
MODULE_DEVICE_TABLE(auxiliary, eq5_phy_id_table);

static struct auxiliary_driver eq5_phy_driver = {
	.probe = eq5_phy_probe,
	.id_table = eq5_phy_id_table,
};
module_auxiliary_driver(eq5_phy_driver);

MODULE_DESCRIPTION("EyeQ5 Ethernet PHY driver");
MODULE_AUTHOR("Théo Lebrun <theo.lebrun@bootlin.com>");
MODULE_LICENSE("GPL");
