// SPDX-License-Identifier: GPL-2.0
/*
 * Amlogic G12A DWC3 Glue layer
 *
 * Copyright (C) 2019 BayLibre, SAS
 * Author: Neil Armstrong <narmstrong@baylibre.com>
 */

#include <common.h>
#include <log.h>
#include <dm.h>
#include <dm/device-internal.h>
#include <dm/lists.h>
#include <dwc3-uboot.h>
#include <generic-phy.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/printk.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <malloc.h>
#include <regmap.h>
#include <usb.h>
#include "core.h"
#include "gadget.h"
#include <reset.h>
#include <clk.h>
#include <power/regulator.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/compat.h>
#include <dt-bindings/clock/amlogic,a1-peripherals-clkc.h>

/* USB2 Ports Control Registers */

#define U2P_REG_SIZE						0x20

#define U2P_R0							0x0
	#define U2P_R0_HOST_DEVICE				BIT(0)
	#define U2P_R0_POWER_OK					BIT(1)
	#define U2P_R0_HAST_MODE				BIT(2)
	#define U2P_R0_POWER_ON_RESET				BIT(3)
	#define U2P_R0_ID_PULLUP				BIT(4)
	#define U2P_R0_DRV_VBUS					BIT(5)

#define U2P_R1							0x4
	#define U2P_R1_PHY_READY				BIT(0)
	#define U2P_R1_ID_DIG					BIT(1)
	#define U2P_R1_OTG_SESSION_VALID			BIT(2)
	#define U2P_R1_VBUS_VALID				BIT(3)

/* USB Glue Control Registers */

#define USB_R0							0x80
	#define USB_R0_P30_LANE0_TX2RX_LOOPBACK			BIT(17)
	#define USB_R0_P30_LANE0_EXT_PCLK_REQ			BIT(18)
	#define USB_R0_P30_PCS_RX_LOS_MASK_VAL_MASK		GENMASK(28, 19)
	#define USB_R0_U2D_SS_SCALEDOWN_MODE_MASK		GENMASK(30, 29)
	#define USB_R0_U2D_ACT					BIT(31)

#define USB_R1							0x84
	#define USB_R1_U3H_BIGENDIAN_GS				BIT(0)
	#define USB_R1_U3H_PME_ENABLE				BIT(1)
	#define USB_R1_U3H_HUB_PORT_OVERCURRENT_MASK		GENMASK(4, 2)
	#define USB_R1_U3H_HUB_PORT_PERM_ATTACH_MASK		GENMASK(9, 7)
	#define USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK		GENMASK(13, 12)
	#define USB_R1_U3H_HOST_U3_PORT_DISABLE			BIT(16)
	#define USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT	BIT(17)
	#define USB_R1_U3H_HOST_MSI_ENABLE			BIT(18)
	#define USB_R1_U3H_FLADJ_30MHZ_REG_MASK			GENMASK(24, 19)
	#define USB_R1_P30_PCS_TX_SWING_FULL_MASK		GENMASK(31, 25)

#define USB_R2							0x88
	#define USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK		GENMASK(25, 20)
	#define USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK		GENMASK(31, 26)

#define USB_R3							0x8c
	#define USB_R3_P30_SSC_ENABLE				BIT(0)
	#define USB_R3_P30_SSC_RANGE_MASK			GENMASK(3, 1)
	#define USB_R3_P30_SSC_REF_CLK_SEL_MASK			GENMASK(12, 4)
	#define USB_R3_P30_REF_SSP_EN				BIT(13)

#define USB_R4							0x90
	#define USB_R4_P21_PORT_RESET_0				BIT(0)
	#define USB_R4_P21_SLEEP_M0				BIT(1)
	#define USB_R4_MEM_PD_MASK				GENMASK(3, 2)
	#define USB_R4_P21_ONLY					BIT(4)

#define USB_R5							0x94
	#define USB_R5_ID_DIG_SYNC				BIT(0)
	#define USB_R5_ID_DIG_REG				BIT(1)
	#define USB_R5_ID_DIG_CFG_MASK				GENMASK(3, 2)
	#define USB_R5_ID_DIG_EN_0				BIT(4)
	#define USB_R5_ID_DIG_EN_1				BIT(5)
	#define USB_R5_ID_DIG_CURR				BIT(6)
	#define USB_R5_ID_DIG_IRQ				BIT(7)
	#define USB_R5_ID_DIG_TH_MASK				GENMASK(15, 8)
	#define USB_R5_ID_DIG_CNT_MASK				GENMASK(23, 16)

enum {
	USB2_HOST_PHY = 0,
	USB2_OTG_PHY,
	USB3_HOST_PHY,
	PHY_COUNT,
};

static const char *const dwc3_meson_g12a_phy_names[] = {
	"usb2-phy0", "usb2-phy1", "usb3-phy0",
};

static const char *const dwc3_meson_a1_phy_names[] = {
	"usb2-phy0", "usb2-phy1"
};

struct dwc3_meson_g12a;

struct dwc3_meson_g12a_drvdata {
	const char *const *phy_names;
	unsigned int phy_cnt;
	int (*clk_init)(struct dwc3_meson_g12a *priv);
};

struct dwc3_meson_g12a {
	struct udevice		*dev;
	struct regmap           *regmap;
	struct clk		clk;
	struct reset_ctl	reset;
	struct phy		phys[PHY_COUNT];
	enum usb_dr_mode	otg_mode;
	enum usb_dr_mode	otg_phy_mode;
	unsigned int		usb2_ports;
	unsigned int		usb3_ports;
#if CONFIG_IS_ENABLED(DM_REGULATOR)
	struct udevice		*vbus_supply;
#endif
	struct dwc3_meson_g12a_drvdata *drvdata;
};

#define U2P_REG_SIZE						0x20
#define USB_REG_OFFSET						0x80

static void dwc3_meson_g12a_usb2_set_mode(struct dwc3_meson_g12a *priv,
					  int i, enum usb_dr_mode mode)
{
	switch (mode) {
	case USB_DR_MODE_HOST:
	case USB_DR_MODE_OTG:
	case USB_DR_MODE_UNKNOWN:
		regmap_update_bits(priv->regmap, U2P_R0 + (U2P_REG_SIZE * i),
				U2P_R0_HOST_DEVICE,
				U2P_R0_HOST_DEVICE);
		break;

	case USB_DR_MODE_PERIPHERAL:
		regmap_update_bits(priv->regmap, U2P_R0 + (U2P_REG_SIZE * i),
				U2P_R0_HOST_DEVICE, 0);
		break;
	}
}

static int dwc3_meson_g12a_usb2_init(struct dwc3_meson_g12a *priv)
{
	int i;

	if (priv->otg_mode == USB_DR_MODE_PERIPHERAL)
		priv->otg_phy_mode = USB_DR_MODE_PERIPHERAL;
	else
		priv->otg_phy_mode = USB_DR_MODE_HOST;

	for (i = 0 ; i < USB3_HOST_PHY ; ++i) {
		if (!priv->phys[i].dev)
			continue;

		regmap_update_bits(priv->regmap, U2P_R0 + (U2P_REG_SIZE * i),
				   U2P_R0_POWER_ON_RESET,
				   U2P_R0_POWER_ON_RESET);

		if (i == USB2_OTG_PHY) {
			regmap_update_bits(priv->regmap,
					   U2P_R0 + (U2P_REG_SIZE * i),
					   U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS,
					   U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS);

			dwc3_meson_g12a_usb2_set_mode(priv, i,
						      priv->otg_phy_mode);
		} else
			dwc3_meson_g12a_usb2_set_mode(priv, i,
						      USB_DR_MODE_HOST);

		regmap_update_bits(priv->regmap, U2P_R0 + (U2P_REG_SIZE * i),
				   U2P_R0_POWER_ON_RESET, 0);
	}

	return 0;
}

static void dwc3_meson_g12a_usb3_init(struct dwc3_meson_g12a *priv)
{
	regmap_update_bits(priv->regmap, USB_R3,
			USB_R3_P30_SSC_RANGE_MASK |
			USB_R3_P30_REF_SSP_EN,
			USB_R3_P30_SSC_ENABLE |
			FIELD_PREP(USB_R3_P30_SSC_RANGE_MASK, 2) |
			USB_R3_P30_REF_SSP_EN);
	udelay(2);

	regmap_update_bits(priv->regmap, USB_R2,
			USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK,
			FIELD_PREP(USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK, 0x15));

	regmap_update_bits(priv->regmap, USB_R2,
			USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK,
			FIELD_PREP(USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK, 0x20));

	udelay(2);

	regmap_update_bits(priv->regmap, USB_R1,
			USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT,
			USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT);

	regmap_update_bits(priv->regmap, USB_R1,
			USB_R1_P30_PCS_TX_SWING_FULL_MASK,
			FIELD_PREP(USB_R1_P30_PCS_TX_SWING_FULL_MASK, 127));
}

static void dwc3_meson_g12a_usb_init_mode(struct dwc3_meson_g12a *priv)
{
	if (priv->otg_phy_mode == USB_DR_MODE_PERIPHERAL) {
		regmap_update_bits(priv->regmap, USB_R0,
				USB_R0_U2D_ACT, USB_R0_U2D_ACT);
		regmap_update_bits(priv->regmap, USB_R0,
				USB_R0_U2D_SS_SCALEDOWN_MODE_MASK, 0);
		regmap_update_bits(priv->regmap, USB_R4,
				USB_R4_P21_SLEEP_M0, USB_R4_P21_SLEEP_M0);
	} else {
		regmap_update_bits(priv->regmap, USB_R0,
				USB_R0_U2D_ACT, 0);
		regmap_update_bits(priv->regmap, USB_R4,
				USB_R4_P21_SLEEP_M0, 0);
	}
}

static int dwc3_meson_g12a_usb_init(struct dwc3_meson_g12a *priv)
{
	int ret;

	ret = dwc3_meson_g12a_usb2_init(priv);
	if (ret)
		return ret;

	regmap_update_bits(priv->regmap, USB_R1,
			USB_R1_U3H_FLADJ_30MHZ_REG_MASK,
			FIELD_PREP(USB_R1_U3H_FLADJ_30MHZ_REG_MASK, 0x20));

	regmap_update_bits(priv->regmap, USB_R5,
			USB_R5_ID_DIG_EN_0,
			USB_R5_ID_DIG_EN_0);
	regmap_update_bits(priv->regmap, USB_R5,
			USB_R5_ID_DIG_EN_1,
			USB_R5_ID_DIG_EN_1);
	regmap_update_bits(priv->regmap, USB_R5,
			USB_R5_ID_DIG_TH_MASK,
			FIELD_PREP(USB_R5_ID_DIG_TH_MASK, 0xff));

	/* If we have an actual SuperSpeed port, initialize it */
	if (priv->usb3_ports)
		dwc3_meson_g12a_usb3_init(priv);

	dwc3_meson_g12a_usb_init_mode(priv);

	return 0;
}

int dwc3_meson_g12a_force_mode(struct udevice *dev, enum usb_dr_mode mode)
{
	struct dwc3_meson_g12a *priv = dev_get_plat(dev);

	if (!priv)
		return -EINVAL;

	if (mode != USB_DR_MODE_HOST && mode != USB_DR_MODE_PERIPHERAL)
		return -EINVAL;

	if (!priv->phys[USB2_OTG_PHY].dev)
		return -EINVAL;

	if (mode == USB_DR_MODE_HOST)
		debug("%s: switching to Host Mode\n", __func__);
	else
		debug("%s: switching to Device Mode\n", __func__);

#if CONFIG_IS_ENABLED(DM_REGULATOR)
	if (priv->vbus_supply) {
		int ret = regulator_set_enable(priv->vbus_supply,
					(mode == USB_DR_MODE_PERIPHERAL));
		if (ret)
			return ret;
	}
#endif
	priv->otg_phy_mode = mode;

	dwc3_meson_g12a_usb2_set_mode(priv, USB2_OTG_PHY, mode);

	dwc3_meson_g12a_usb_init_mode(priv);

	return 0;
}

static int dwc3_meson_g12a_get_phys(struct dwc3_meson_g12a *priv)
{
	struct dwc3_meson_g12a_drvdata *data = priv->drvdata;
	int i, ret;

	for (i = 0 ; i < data->phy_cnt; ++i) {
		ret = generic_phy_get_by_name(priv->dev, data->phy_names[i],
					      &priv->phys[i]);
		if (ret == -ENOENT || ret == -ENODATA)
			continue;

		if (ret)
			return ret;

		if (i == USB3_HOST_PHY)
			priv->usb3_ports++;
		else
			priv->usb2_ports++;
	}

	debug("%s: usb2 ports: %d\n", __func__, priv->usb2_ports);
	debug("%s: usb3 ports: %d\n", __func__, priv->usb3_ports);

	return 0;
}

static int dwc3_meson_g12a_reset_init(struct dwc3_meson_g12a *priv)
{
	int ret;

	ret = reset_get_by_index(priv->dev, 0, &priv->reset);
	if (ret)
		return ret;

	ret = reset_assert(&priv->reset);
	udelay(1);
	ret |= reset_deassert(&priv->reset);
	if (ret) {
		reset_free(&priv->reset);
		return ret;
	}

	return 0;
}

static int dwc3_meson_g12a_clk_init(struct dwc3_meson_g12a *priv)
{
	int ret;

	ret = clk_get_by_index(priv->dev, 0, &priv->clk);
	if (ret)
		return ret;

#if CONFIG_IS_ENABLED(CLK)
	ret = clk_enable(&priv->clk);
	if (ret)
		return ret;
#endif

	return 0;
}

static int dwc3_meson_a1_clk_init(struct dwc3_meson_g12a *priv)
{
	int ret;

	ret = clk_get_by_name(priv->dev, "usb_bus", &priv->clk);
	if (ret)
		return ret;

	ret = clk_enable(&priv->clk);
	if (ret)
		return ret;

	return 0;
}

static int dwc3_meson_g12a_probe(struct udevice *dev)
{
	struct dwc3_meson_g12a *priv = dev_get_plat(dev);
	struct dwc3_meson_g12a_drvdata *data =
		(struct dwc3_meson_g12a_drvdata *)dev_get_driver_data(dev);
	int ret, i;

	priv->drvdata = data;
	priv->dev = dev;

	ret = regmap_init_mem(dev_ofnode(dev), &priv->regmap);
	if (ret)
		return ret;

	ret = data->clk_init(priv);
	if (ret)
		return ret;

	ret = dwc3_meson_g12a_reset_init(priv);
	if (ret)
		return ret;

	ret = dwc3_meson_g12a_get_phys(priv);
	if (ret)
		return ret;

#if CONFIG_IS_ENABLED(DM_REGULATOR)
	ret = device_get_supply_regulator(dev, "vbus-supply",
					  &priv->vbus_supply);
	if (ret && ret != -ENOENT) {
		pr_err("Failed to get PHY regulator\n");
		return ret;
	}

	if (priv->vbus_supply) {
		ret = regulator_set_enable(priv->vbus_supply, true);
		if (ret)
			return ret;
	}
#endif

	priv->otg_mode = usb_get_dr_mode(dev_ofnode(dev));

	ret = dwc3_meson_g12a_usb_init(priv);
	if (ret)
		return ret;

	for (i = 0 ; i < data->phy_cnt; ++i) {
		if (!priv->phys[i].dev)
			continue;

		ret = generic_phy_init(&priv->phys[i]);
		if (ret)
			goto err_phy_init;
	}

	for (i = 0; i < data->phy_cnt; ++i) {
		if (!priv->phys[i].dev)
			continue;

		ret = generic_phy_power_on(&priv->phys[i]);
		if (ret)
			goto err_phy_init;
	}

	return 0;

err_phy_init:
	for (i = 0 ; i < data->phy_cnt ; ++i) {
		if (!priv->phys[i].dev)
			continue;

		 generic_phy_exit(&priv->phys[i]);
	}

	return ret;
}

static int dwc3_meson_g12a_remove(struct udevice *dev)
{
	struct dwc3_meson_g12a *priv = dev_get_plat(dev);
	struct dwc3_meson_g12a_drvdata *data = priv->drvdata;
	int i;

	reset_release_all(&priv->reset, 1);

	clk_release_all(&priv->clk, 1);

	for (i = 0; i < data->phy_cnt; ++i) {
		if (!priv->phys[i].dev)
			continue;

		 generic_phy_power_off(&priv->phys[i]);
	}

	for (i = 0 ; i < data->phy_cnt; ++i) {
		if (!priv->phys[i].dev)
			continue;

		 generic_phy_exit(&priv->phys[i]);
	}

	return dm_scan_fdt_dev(dev);
}

static const struct dwc3_meson_g12a_drvdata meson_g12a_drvdata = {
	.phy_names = dwc3_meson_g12a_phy_names,
	.phy_cnt = ARRAY_SIZE(dwc3_meson_g12a_phy_names),
	.clk_init = dwc3_meson_g12a_clk_init,
};

static const struct dwc3_meson_g12a_drvdata meson_a1_drvdata = {
	.phy_names = dwc3_meson_a1_phy_names,
	.phy_cnt = ARRAY_SIZE(dwc3_meson_a1_phy_names),
	.clk_init = dwc3_meson_a1_clk_init,
};

static int dwc3_meson_g12a_child_pre_probe(struct udevice *dev)
{
	if (ofnode_device_is_compatible(dev_ofnode(dev), "amlogic,meson-g12a-usb"))
		return dwc3_meson_g12a_force_mode(dev->parent, USB_DR_MODE_PERIPHERAL);

	if (ofnode_device_is_compatible(dev_ofnode(dev), "amlogic,meson-a1-usb"))
		return dwc3_meson_g12a_force_mode(dev->parent, USB_DR_MODE_PERIPHERAL);

	return 0;
}

static int dwc3_meson_g12a_child_post_remove(struct udevice *dev)
{
	if (ofnode_device_is_compatible(dev_ofnode(dev), "amlogic,meson-g12a-usb"))
		return dwc3_meson_g12a_force_mode(dev->parent, USB_DR_MODE_HOST);

	if (ofnode_device_is_compatible(dev_ofnode(dev), "amlogic,meson-a1-usb"))
		return dwc3_meson_g12a_force_mode(dev->parent, USB_DR_MODE_HOST);

	return 0;
}

static const struct udevice_id dwc3_meson_g12a_ids[] = {
	{
		.compatible = "amlogic,meson-g12a-usb-ctrl",
		.data = (ulong)&meson_g12a_drvdata,
	},
	{
		.compatible = "amlogic,meson-a1-usb-ctrl",
		.data = (ulong)&meson_a1_drvdata,
	},
	{ }
};

U_BOOT_DRIVER(dwc3_generic_wrapper) = {
	.name	= "dwc3-meson-g12a",
	.id	= UCLASS_SIMPLE_BUS,
	.of_match = dwc3_meson_g12a_ids,
	.probe = dwc3_meson_g12a_probe,
	.remove = dwc3_meson_g12a_remove,
	.child_pre_probe = dwc3_meson_g12a_child_pre_probe,
	.child_post_remove = dwc3_meson_g12a_child_post_remove,
	.plat_auto	= sizeof(struct dwc3_meson_g12a),

};
