// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
 *
 * Synopsys DesignWare MIPI D-PHY controller driver.
 * Platform driver
 *
 * Author: Luis Oliveira <luis.oliveira@synopsys.com>
 */

#include <dw-dphy-data.h>
#include <dw-csi-data.h>
#include "dw-dphy-rx.h"
#include "bm_csi_hw.h"

/* Global variable for compatibility mode, this could be override later */
static int phy_type = 1; //Changed to fit single phy 0 - single | 1 - 8 lanes phy

module_param(phy_type, int, 0664);
MODULE_PARM_DESC(phy_type, "Disable compatibility mode for D-PHY G128");

static struct phy_ops dw_dphy_ops = {
	.init = dw_dphy_init,
	.reset = dw_dphy_reset,
	.power_on = dw_dphy_power_on,
	.power_off = dw_dphy_power_off,
	.owner = THIS_MODULE,
};

static int get_resources(struct device *dev, struct dw_dphy_rx *dphy)
{
	int ret = 0;

    struct platform_device *pdev = to_platform_device(dev);
    struct bm_csi_drvdata *drvdata = platform_get_drvdata(pdev);
	struct dw_phy_pdata *pdata = &drvdata->dphy_pdata;

	dphy->dphy_freq = pdata->dphy_frequency;
	dphy->dphy_te_len = pdata->dphy_te_len;
	dphy->dphy_gen = pdata->dphy_gen;
    drvdata->dphy = dphy;

	return ret;
}

static int phy_register(struct device *dev)
{
	int ret = 0;
    struct platform_device *pdev = to_platform_device(dev);
    struct bm_csi_drvdata *drvdata = platform_get_drvdata(pdev);
    struct dw_dphy_rx *dphy = drvdata->dphy;
	struct dw_phy_pdata *pdata = &drvdata->dphy_pdata;

	ret = phy_create_lookup(dphy->phy,
					phys[pdata->id].name,
					csis[pdata->id].name);
	if (ret)
		dev_err(dev, "Failed to create dphy lookup\n");
	else
		dev_warn(dev, "Created dphy lookup [%s] --> [%s]\n",
		    phys[pdata->id].name, csis[pdata->id].name);


	return ret;
}

static void phy_unregister(struct device *dev)
{
	if (!dev->of_node) {

        struct platform_device *pdev = to_platform_device(dev);
        struct bm_csi_drvdata *drvdata = platform_get_drvdata(pdev);
        struct dw_dphy_rx *dphy = drvdata->dphy;
	    struct dw_phy_pdata *pdata = &drvdata->dphy_pdata;

		phy_remove_lookup(dphy->phy,
				  phys[pdata->id].name, csis[pdata->id].name);
		dev_warn(dev, "Removed dphy lookup [%s] --> [%s]\n",
			 phys[pdata->id].name, csis[pdata->id].name);
	}
}

#define REG_DPHY_OFFSET 0x40
int dw_dphy_rx_probe(struct platform_device *pdev, unsigned int dphyglueiftester, unsigned int sysreg_mipi_csi_ctrl)
{
	struct device *dev = &pdev->dev;
	struct dw_dphy_rx *dphy;
    struct bm_csi_drvdata *drvdata;

	dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
	if (!dphy)
		return -ENOMEM;

	drvdata = platform_get_drvdata(pdev);
    drvdata->dphy= dphy;

    dphy->phy_id = drvdata->phy_id;
	dphy->base_address = drvdata->base + REG_DPHY_OFFSET;
	dphy->dphyglueiftester = dphyglueiftester;
	dphy->sysreg_mipi_csi_ctrl = sysreg_mipi_csi_ctrl;

    if (IS_ERR(dphy->base_address)) {
		dev_err(&pdev->dev, "error requesting base address\n");
		return PTR_ERR(dphy->base_address);
	}

	if (get_resources(dev, dphy)) {
		dev_err(dev, "failed to parse PHY resources\n");
		return -EINVAL;
	}

	dphy->phy = devm_phy_create(dev, NULL, &dw_dphy_ops);
	if (IS_ERR(dphy->phy)) {
		dev_err(dev, "failed to create PHY\n");
		return PTR_ERR(dphy->phy);
	}

	phy_set_drvdata(dphy->phy, dphy);

	if (phy_register(dev)) {
		dev_err(dev, "failed to register PHY\n");
		return -EINVAL;
	}

	dphy->lp_time = 1000; /* 1000 ns */
	dphy->lanes_config = dw_dphy_setup_config(dphy);

	dev_vdbg(&dphy->phy->dev, "Probing dphy finished\n");

	dw_dphy_create_capabilities_sysfs(pdev);

	return 0;
}

int dw_dphy_rx_remove(struct platform_device *pdev)
{
    struct bm_csi_drvdata *drvdata = platform_get_drvdata(pdev);
    struct dw_dphy_rx *dphy = drvdata->dphy;

	dev_vdbg(&dphy->phy->dev, "phy removed\n");

	phy_reset(dphy->phy);
	phy_unregister(&pdev->dev);
    dw_dphy_remove_capabilities_sysfs(pdev);

	return 0;
}

MODULE_DESCRIPTION("Synopsys DesignWare MIPI DPHY Rx driver");
MODULE_AUTHOR("Luis Oliveira <lolivei@synopsys.com>");
MODULE_LICENSE("GPL v2");
