// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/of.h>
#include <linux/acpi.h>
#include <linux/pinctrl/consumer.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/of.h>
#include <linux/usb/otg.h>
#include <linux/amlogic/usbtype.h>
#include <linux/clk.h>
#include <linux/phy/phy.h>
#include <linux/amlogic/aml_gpio_consumer.h>

#include <linux/kthread.h>

#include "xhci.h"
#include "xhci-plat.h"
#include "crg_xhci.h"

static const struct xhci_plat_priv crg_xhci_plat_priv = {
	.quirks = XHCI_NO_64BIT_SUPPORT | XHCI_RESET_ON_RESUME,
};

#define CRG_DEFAULT_AUTOSUSPEND_DELAY	5000 /* ms */
#define CRG_XHCI_RESOURCES_NUM	2
#define CRG_GCTL_PRTCAP_HOST	1
#define CRG_GCTL_PRTCAP_DEVICE	2
#define CRG_GCTL_PRTCAP_OTG	3
#define CRG_XHCI_REGS_END		0x7fff
#define	CRG_U3DC_CFG0_MAXSPEED_SS		(0x4L << 0)

struct crg_drd {
	struct device		*dev;

	struct platform_device	*xhci;
	struct resource		xhci_resources[CRG_XHCI_RESOURCES_NUM];

	void __iomem		*regs;
	size_t			regs_size;
	enum usb_dr_mode	dr_mode;
	u32			maximum_speed;
	void			*mem;
	struct usb_phy		*usb2_phy;
	struct usb_phy		*usb3_phy;

	unsigned		super_speed_support:1;
	bool		usb_phy_init;
	bool		usb_suspend;
	struct clk		*general_clk;
};

/* -------------------------------------------------------------------------- */
static void crg_set_mode(struct crg_drd *crg, u32 mode)
{
	u64 tmp;

	if (mode == USB_DR_MODE_HOST) {
		/* set controller host role*/
		tmp = readl(crg->regs + 0x20FC) & ~0x1;
		writel(tmp, crg->regs + 0x20FC);
	}
}

static int crg_core_soft_reset(struct crg_drd *crg)
{
	if (crg->usb2_phy)
		usb_phy_init(crg->usb2_phy);

	if (crg->usb3_phy)
		usb_phy_init(crg->usb3_phy);

	return 0;
}

static void crg_core_exit(struct crg_drd	*crg)
{
	if (crg->usb2_phy)
		usb_phy_shutdown(crg->usb2_phy);
	if (crg->usb3_phy)
		usb_phy_shutdown(crg->usb3_phy);

	if (crg->usb2_phy)
		usb_phy_set_suspend(crg->usb2_phy, 1);
	if (crg->usb3_phy)
		usb_phy_set_suspend(crg->usb3_phy, 1);
}

static int crg_core_init(struct crg_drd *crg)
{
	int			ret;

	ret = crg_core_soft_reset(crg);
	if (ret)
		return ret;

	usb_phy_set_suspend(crg->usb2_phy, 0);
	usb_phy_set_suspend(crg->usb3_phy, 0);

	switch (crg->dr_mode) {
	case USB_DR_MODE_PERIPHERAL:
		crg_set_mode(crg, CRG_GCTL_PRTCAP_DEVICE);
		break;
	case USB_DR_MODE_HOST:
		crg_set_mode(crg, CRG_GCTL_PRTCAP_HOST);
		break;
	case USB_DR_MODE_OTG:
		crg_set_mode(crg, CRG_GCTL_PRTCAP_OTG);
		break;
	default:
		dev_warn(crg->dev, "Unsupported mode %d\n", crg->dr_mode);
		break;
	}

	return 0;
}

static int crg_core_resume(struct crg_drd *crg)
{
	int			ret;

	ret = crg_core_soft_reset(crg);
	if (ret)
		return ret;

	usb_phy_set_suspend(crg->usb2_phy, 0);
	usb_phy_set_suspend(crg->usb3_phy, 0);

	switch (crg->dr_mode) {
	case USB_DR_MODE_PERIPHERAL:
		crg_set_mode(crg, CRG_GCTL_PRTCAP_DEVICE);
		break;
	case USB_DR_MODE_HOST:
		crg_set_mode(crg, CRG_GCTL_PRTCAP_HOST);
		break;
	case USB_DR_MODE_OTG:
		crg_set_mode(crg, CRG_GCTL_PRTCAP_OTG);
		break;
	default:
		dev_warn(crg->dev, "Unsupported mode %d\n", crg->dr_mode);
		break;
	}

	return 0;
}

static int crg_core_get_phy(struct crg_drd *crg)
{
	struct device *dev = crg->dev;

	crg->super_speed_support = 0;

	crg->usb2_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 0);

	crg->usb3_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 1);

	if (crg->usb3_phy)
		if (crg->usb3_phy->flags == AML_USB3_PHY_ENABLE)
			crg->super_speed_support = 1;

	return 0;
}

static void crg_host_exit(struct crg_drd *crg)
{
	/*crg_reset_thread_stop(crg->xhci);*/
	platform_device_unregister(crg->xhci);
}

static int crg_host_init(struct crg_drd *crg)
{
	struct property_entry	props[4];
	struct platform_device	*xhci;
	int			ret, irq;
	struct resource		*res;
	struct platform_device	*crg_pdev = to_platform_device(crg->dev);
	int			prop_idx = 0;

	irq = platform_get_irq_byname(crg_pdev, "host");
	if (irq == -EPROBE_DEFER)
		return irq;

	if (irq <= 0) {
		irq = platform_get_irq_byname(crg_pdev, "crg_usb3");
		if (irq == -EPROBE_DEFER)
			return irq;

		if (irq <= 0) {
			irq = platform_get_irq(crg_pdev, 0);
			if (irq <= 0) {
				if (irq != -EPROBE_DEFER) {
					dev_err(crg->dev,
						"missing host IRQ\n");
				}
				if (!irq)
					irq = -EINVAL;
				return irq;
			}
			res = platform_get_resource(crg_pdev,
							IORESOURCE_IRQ, 0);

		} else {
			res = platform_get_resource_byname(crg_pdev,
							   IORESOURCE_IRQ,
							   "crg_usb3");
		}

	} else {
		res = platform_get_resource_byname(crg_pdev, IORESOURCE_IRQ,
						   "host");
	}

	crg->xhci_resources[1].start = irq;
	crg->xhci_resources[1].end = irq;
	crg->xhci_resources[1].flags = res->flags;
	crg->xhci_resources[1].name = res->name;

	xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
	if (!xhci) {
		dev_err(crg->dev, "couldn't allocate xHCI device\n");
		return -ENOMEM;
	}

	dma_set_coherent_mask(&xhci->dev, crg->dev->coherent_dma_mask);

	xhci->dev.parent	= crg->dev;
	xhci->dev.dma_mask	= crg->dev->dma_mask;
	xhci->dev.dma_parms	= crg->dev->dma_parms;

	crg->xhci = xhci;

	ret = platform_device_add_resources(xhci, crg->xhci_resources,
						CRG_XHCI_RESOURCES_NUM);
	if (ret) {
		dev_err(crg->dev, "couldn't add resources to xHCI device\n");
		goto err1;
	}

	memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props));

	if (crg->super_speed_support)
		props[prop_idx++] = PROPERTY_ENTRY_BOOL("super_speed_support");

	props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-crg-host");
	props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb2-lpm-disable");

	if (prop_idx) {
		ret = device_create_managed_software_node(&xhci->dev, props, NULL);
		if (ret) {
			dev_err(crg->dev, "failed to add properties to xHCI\n");
			goto err1;
		}
	}
	crg_xhci_init();
	ret = platform_device_add_data(xhci, &crg_xhci_plat_priv,
								sizeof(crg_xhci_plat_priv));
	if (ret)
		goto err1;

	ret = platform_device_add(xhci);
	if (ret) {
		dev_err(crg->dev, "failed to register xHCI device\n");
		goto err1;
	}

	return 0;

err1:
	platform_device_put(xhci);
	return ret;
}

static int crg_core_init_mode(struct crg_drd *crg)
{
	struct device *dev = crg->dev;
	int ret;

	switch (crg->dr_mode) {
	case USB_DR_MODE_PERIPHERAL:
	case USB_DR_MODE_OTG:
		break;
	case USB_DR_MODE_HOST:
		ret = crg_host_init(crg);
		if (ret) {
			if (ret != -EPROBE_DEFER)
				dev_err(dev, "failed to initialize host\n");
			return ret;
		}
		break;
	default:
		dev_err(dev, "Unsupported mode of operation %d\n", crg->dr_mode);
		return -EINVAL;
	}

	return 0;
}

#define CRG_ALIGN_MASK		(16 - 1)

static int crg_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct resource	*res;
	struct crg_drd *crg;
	int	ret;
	void *mem;
	u32 tmp;

	mem = devm_kzalloc(dev, sizeof(*crg) + CRG_ALIGN_MASK, GFP_KERNEL);
	if (!mem)
		return -ENOMEM;

	crg = PTR_ALIGN(mem, CRG_ALIGN_MASK + 1);
	crg->mem = mem;
	crg->dev = dev;
	crg->dr_mode = USB_DR_MODE_HOST;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(dev, "missing memory resource\n");
		return -ENODEV;
	}

	crg->xhci_resources[0].start = res->start;
	crg->xhci_resources[0].end = crg->xhci_resources[0].start +
					CRG_XHCI_REGS_END;
	crg->xhci_resources[0].flags = res->flags;
	crg->xhci_resources[0].name = res->name;

	crg->regs = ioremap(res->start, resource_size(res));
	if (!crg->regs) {
		dev_err(dev, "ioremap failed\n");
		return -ENODEV;
	}

	crg->general_clk = devm_clk_get(dev, "crg_general");
	if (IS_ERR(crg->general_clk)) {
		ret = PTR_ERR(crg->general_clk);
		goto err0;
	} else {
		clk_prepare_enable(crg->general_clk);
	}

	platform_set_drvdata(pdev, crg);

	ret = crg_core_get_phy(crg);
	if (ret)
		goto err0;

	if (!dev->dma_mask) {
		dev->dma_mask = dev->parent->dma_mask;
		dev->dma_parms = dev->parent->dma_parms;
		dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask);
	}

	pm_runtime_set_active(dev);
	pm_runtime_use_autosuspend(dev);
	pm_runtime_set_autosuspend_delay(dev, CRG_DEFAULT_AUTOSUSPEND_DELAY);
	pm_runtime_enable(dev);
	ret = pm_runtime_get_sync(dev);
	if (ret < 0)
		goto err0;

	pm_runtime_forbid(dev);

	ret = crg_core_init(crg);
	if (ret) {
		dev_err(dev, "failed to initialize core\n");
		goto err0;
	}

	if (crg->super_speed_support)
		crg->maximum_speed = USB_SPEED_SUPER;
	else
		crg->maximum_speed = USB_SPEED_HIGH;

	/* Check the maximum_speed parameter */
	switch (crg->maximum_speed) {
	case USB_SPEED_LOW:
	case USB_SPEED_FULL:
	case USB_SPEED_HIGH:
		break;
	case USB_SPEED_SUPER:
		tmp = CRG_U3DC_CFG0_MAXSPEED_SS;
		writel(tmp, crg->regs + 0x2410);
	case USB_SPEED_SUPER_PLUS:
		break;
	default:
		dev_err(dev, "invalid maximum_speed parameter %d\n",
			crg->maximum_speed);
		break;
	}

	ret = crg_core_init_mode(crg);
	if (ret)
		goto err0;

	pm_runtime_put(dev);

	return 0;

err0:
	iounmap(crg->regs);
	return ret;
}

static void crg_shutdown(struct platform_device *pdev)
{
	struct crg_drd     *crg = platform_get_drvdata(pdev);

	pm_runtime_get_sync(&pdev->dev);

	crg_core_exit(crg);
	crg_host_exit(crg);

	pm_runtime_put_sync(&pdev->dev);
	pm_runtime_allow(&pdev->dev);
	pm_runtime_disable(&pdev->dev);

	clk_disable_unprepare(crg->general_clk);
}

static int crg_remove(struct platform_device *pdev)
{
	struct crg_drd	   *crg = platform_get_drvdata(pdev);

	pm_runtime_get_sync(&pdev->dev);

	crg_host_exit(crg);

	pm_runtime_put_sync(&pdev->dev);
	pm_runtime_allow(&pdev->dev);
	pm_runtime_disable(&pdev->dev);

	clk_disable_unprepare(crg->general_clk);

	return 0;
}

#ifdef CONFIG_PM
static int crg_suspend_common(struct crg_drd *crg)
{
	switch (crg->dr_mode) {
	case USB_DR_MODE_PERIPHERAL:
	case USB_DR_MODE_OTG:
		break;
	case USB_DR_MODE_HOST:
	default:
		/* do nothing */
		break;
	}

	crg_core_exit(crg);

	return 0;
}

static int crg_resume_common(struct crg_drd *crg)
{
	int		ret;

	ret = crg_core_resume(crg);
	if (ret)
		return ret;

	crg_set_mode(crg, CRG_GCTL_PRTCAP_HOST);

	return 0;
}

static int crg_runtime_checks(struct crg_drd *crg)
{
	switch (crg->dr_mode) {
	case USB_DR_MODE_PERIPHERAL:
	case USB_DR_MODE_OTG:
	case USB_DR_MODE_HOST:
	default:
		/* do nothing */
		break;
	}

	return 0;
}

static int crg_runtime_suspend(struct device *dev)
{
	struct crg_drd     *crg = dev_get_drvdata(dev);
	int		ret;

	if (crg_runtime_checks(crg))
		return -EBUSY;

	ret = crg_suspend_common(crg);
	if (ret)
		return ret;

	device_init_wakeup(dev, true);

	return 0;
}

static int crg_runtime_resume(struct device *dev)
{
	struct crg_drd     *crg = dev_get_drvdata(dev);
	int		ret;

	device_init_wakeup(dev, false);

	ret = crg_resume_common(crg);
	if (ret)
		return ret;

	switch (crg->dr_mode) {
	case USB_DR_MODE_PERIPHERAL:
	case USB_DR_MODE_OTG:
	case USB_DR_MODE_HOST:
	default:
		/* do nothing */
		break;
	}

	pm_runtime_mark_last_busy(dev);
	pm_runtime_put(dev);

	return 0;
}

static int crg_runtime_idle(struct device *dev)
{
	struct crg_drd     *crg = dev_get_drvdata(dev);

	switch (crg->dr_mode) {
	case USB_DR_MODE_PERIPHERAL:
	case USB_DR_MODE_OTG:
		if (crg_runtime_checks(crg))
			return -EBUSY;
		break;
	case USB_DR_MODE_HOST:
	default:
		/* do nothing */
		break;
	}

	pm_runtime_mark_last_busy(dev);
	pm_runtime_autosuspend(dev);

	return 0;
}
#endif /* CONFIG_PM */

#ifdef CONFIG_PM_SLEEP
static int crg_suspend(struct device *dev)
{
	struct crg_drd	*crg = dev_get_drvdata(dev);
	int		ret;

	crg->usb_suspend = 1;
	ret = crg_suspend_common(crg);
	if (ret)
		return ret;

	clk_disable_unprepare(crg->general_clk);

	pinctrl_pm_select_sleep_state(dev);

	return 0;
}

static int crg_resume(struct device *dev)
{
	struct crg_drd	*crg = dev_get_drvdata(dev);
	int		ret;

	crg->usb_suspend = 0;

	pinctrl_pm_select_default_state(dev);

	clk_prepare_enable(crg->general_clk);

	ret = crg_resume_common(crg);
	if (ret)
		return ret;

	pm_runtime_disable(dev);
	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);

	return 0;
}
#endif /* CONFIG_PM_SLEEP */

static const struct dev_pm_ops crg_dev_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(crg_suspend, crg_resume)
	SET_RUNTIME_PM_OPS(crg_runtime_suspend, crg_runtime_resume,
			crg_runtime_idle)
};

#ifdef CONFIG_OF
static const struct of_device_id of_crg_drd_match[] = {
	{
		.compatible = "amlogic, crg-drd"
	},
	{ },
};
MODULE_DEVICE_TABLE(of, of_crg_drd_match);
#endif

static struct platform_driver crg_driver = {
	.probe		= crg_probe,
	.remove		= crg_remove,
	.shutdown	= crg_shutdown,
	.driver		= {
		.name	= "crg_drd_otg",
		.of_match_table	= of_match_ptr(of_crg_drd_match),
		.pm	= &crg_dev_pm_ops,
	},
};

#ifdef CONFIG_OF
static const struct of_device_id of_crg_host_match[] = {
	{
		.compatible = "amlogic, crg-host-drd"
	},
	{ },
};
MODULE_DEVICE_TABLE(of, of_crg_host_match);
#endif

static struct platform_driver crg_host_driver = {
	.remove		= crg_remove,
	.shutdown	= crg_shutdown,
	.driver		= {
		.name	= "crg_drd",
		.of_match_table	= of_match_ptr(of_crg_host_match),
		.pm	= &crg_dev_pm_ops,
	},
};

void crg_exit(void)
{
	platform_driver_unregister(&crg_driver);
}
EXPORT_SYMBOL_GPL(crg_exit);

int crg_init(void)
{
	return platform_driver_register(&crg_driver);
}
EXPORT_SYMBOL_GPL(crg_init);

/* AMLOGIC corigine driver does not allow module unload */
int __init amlogic_crg_host_driver_init(void)
{
	platform_driver_probe(&crg_host_driver, crg_probe);

	return 0;
}

#if 0
late_initcall(amlogic_crg_init);

MODULE_ALIAS("platform:crg");
MODULE_AUTHOR("yue wang <yue.wang@amlogic.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("corigine USB3 DRD Controller Driver");
#endif
