/*
 * Copyright (c) 2015, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#define pr_fmt(fmt) "%s: " fmt, __func__

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/ctype.h>
#include <linux/bitops.h>
#include <linux/io.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/clk/msm-clk-provider.h>
#include <linux/clk/msm-clock-generic.h>
#include <linux/debugfs.h>

#define CMD_RCGR_REG		0x0
#define CMD_UPDATE_EN		BIT(0)
/* Async_clk_en */
#define CMD_ROOT_EN		BIT(1)

struct rcgwr {
	void __iomem *base;
	void __iomem *rcg_base;
	int *dfs_sid_offset;
	int *dfs_sid_value;
	int  dfs_sid_len;
	int *link_sid_offset;
	int *link_sid_value;
	int  link_sid_len;
	int *lmh_sid_offset;
	int *lmh_sid_value;
	int  lmh_sid_len;
	bool inited;
};

static struct rcgwr **rcgwr;
static struct platform_device *cpu_clock_dev;
static u32 num_clusters;

#define DFS_SID_1_2		0x10
#define DFS_SID_3_4		0x14
#define DFS_SID_5_6		0x18
#define DFS_SID_7_8		0x1C
#define DFS_SID_9_10		0x20
#define DFS_SID_11_12		0x24
#define DFS_SID_13_14		0x28
#define DFS_SID_15		0x2C
#define LMH_SID_1_2		0x30
#define LMH_SID_3_4		0x34
#define LMH_SID_5		0x38
#define DCVS_CFG_CTL		0x50
#define LMH_CFG_CTL		0x54
#define RC_CFG_CTL		0x58
#define RC_CFG_DBG		0x5C
#define RC_CFG_UPDATE		0x60

#define RC_CFG_UPDATE_EN_BIT	8
#define RC_CFG_ACK_BIT		16

#define UPDATE_CHECK_MAX_LOOPS  500

#define DFS_SID_START		0xE
#define LMH_SID_START		0x6
#define DCVS_CONFIG		0x2
#define LINK_SID		0x3

/* Sequence for enable */
static int ramp_en[] = { 0x800, 0xC00, 0x400};

static int check_rcg_config(void __iomem *base)
{
	u32 cmd_rcgr_regval, count;

	cmd_rcgr_regval = readl_relaxed(base + CMD_RCGR_REG);
	cmd_rcgr_regval |= CMD_ROOT_EN;
	writel_relaxed(cmd_rcgr_regval, (base + CMD_RCGR_REG));

	for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) {
		cmd_rcgr_regval = readl_relaxed(base + CMD_RCGR_REG);
		cmd_rcgr_regval &= CMD_UPDATE_EN;
		if (!(cmd_rcgr_regval)) {
			pr_debug("cmd_rcgr state on update bit cleared 0x%x, cmd 0x%x\n",
					readl_relaxed(base + CMD_RCGR_REG),
					cmd_rcgr_regval);
			return 0;
		}
		udelay(1);
	}

	BUG_ON(count == 0);

	return -EINVAL;
}

static int rc_config_update(void __iomem *base, u32 rc_value, u32 rc_ack_bit)
{
	u32 count, ret = 0, regval;

	regval = readl_relaxed(base + RC_CFG_UPDATE);
	regval |= rc_value;
	writel_relaxed(regval, base + RC_CFG_UPDATE);
	regval |= BIT(RC_CFG_UPDATE_EN_BIT);
	writel_relaxed(regval, base + RC_CFG_UPDATE);

	/* Poll for update ack */
	for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) {
		regval = readl_relaxed((base + RC_CFG_UPDATE))
						  >> RC_CFG_ACK_BIT;
		if (regval == BIT(rc_ack_bit)) {
			ret = 0;
			break;
		}
		udelay(1);
	}
	BUG_ON(count == 0);

	/* Clear RC_CFG_UPDATE_EN */
	writel_relaxed(0 << RC_CFG_UPDATE_EN_BIT, (base + RC_CFG_UPDATE));
	/* Poll for update ack */
	for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) {
		regval = readl_relaxed((base + RC_CFG_UPDATE))
						>> RC_CFG_ACK_BIT;
		if (!regval)
			return ret;
		udelay(1);
	}
	BUG_ON(count == 0);

	return -EINVAL;
}


static int ramp_control_enable(struct platform_device *pdev,
		struct rcgwr *rcgwr)
{
	int i = 0, ret = 0;

	for (i = 0; i < ARRAY_SIZE(ramp_en); i++) {
		ret = check_rcg_config(rcgwr->rcg_base);
		if (ret) {
			dev_err(&pdev->dev, "Failed to update config!!!\n");
			return ret;
		}
		writel_relaxed(ramp_en[i], rcgwr->base + DCVS_CFG_CTL);
		ret = rc_config_update(rcgwr->base, DCVS_CONFIG, DCVS_CONFIG);
		if (ret) {
			dev_err(&pdev->dev,
				"Failed to config update for 0x2 and ACK 0x4\n");
			break;
		}
	}

	return ret;
}

static int ramp_down_disable(struct platform_device *pdev,
		struct rcgwr *rcgwr)
{
	int ret = 0;

	ret = check_rcg_config(rcgwr->rcg_base);
	if (ret) {
		dev_err(&pdev->dev, "Failed to update config!!!\n");
		return ret;
	}

	writel_relaxed(0x200, rcgwr->base + DCVS_CFG_CTL);
	ret = rc_config_update(rcgwr->base, DCVS_CONFIG, DCVS_CONFIG);
	if (ret)
		dev_err(&pdev->dev,
			"Failed to config update for 0x2 and ACK 0x4\n");

	return ret;
}

static int ramp_control_disable(struct platform_device *pdev,
		struct rcgwr *rcgwr)
{
	int ret = 0;

	if (!rcgwr->inited)
		return 0;

	ret = check_rcg_config(rcgwr->rcg_base);
	if  (ret) {
		dev_err(&pdev->dev, "Failed to update config!!!\n");
		return ret;
	}

	writel_relaxed(0x0, rcgwr->base + DCVS_CFG_CTL);

	ret = rc_config_update(rcgwr->base, DCVS_CONFIG, DCVS_CONFIG);
	if (ret)
		dev_err(&pdev->dev,
			"Failed to config update for 0x2 and ACK 0x4\n");

	rcgwr->inited = false;

	return ret;
}

static int ramp_link_sid(struct platform_device *pdev, struct rcgwr *rcgwr)
{
	int ret = 0, i;

	if (!rcgwr->link_sid_len) {
		pr_err("Use Default Link SID\n");
		return 0;
	}

	ret = check_rcg_config(rcgwr->rcg_base);
	if  (ret) {
		dev_err(&pdev->dev, "Failed to update config!!!\n");
		return ret;
	}

	for (i = 0; i < rcgwr->link_sid_len; i++)
		writel_relaxed(rcgwr->link_sid_value[i],
				rcgwr->base + rcgwr->link_sid_offset[i]);

	ret = rc_config_update(rcgwr->base, LINK_SID, LINK_SID);
	if (ret)
		dev_err(&pdev->dev,
			"Failed to config update for 0x3 and ACK 0x8\n");

	return ret;
}

static int ramp_lmh_sid(struct platform_device *pdev, struct rcgwr *rcgwr)
{
	int ret = 0, i, j;

	if (!rcgwr->lmh_sid_len) {
		pr_err("Use Default LMH SID\n");
		return 0;
	}

	ret = check_rcg_config(rcgwr->rcg_base);
	if  (ret) {
		dev_err(&pdev->dev, "Failed to update config!!!\n");
		return ret;
	}

	for (i = 0; i < rcgwr->lmh_sid_len; i++)
		writel_relaxed(rcgwr->lmh_sid_value[i],
				rcgwr->base + rcgwr->lmh_sid_offset[i]);

	for (i = LMH_SID_START, j = 0; j < rcgwr->lmh_sid_len; i--, j++) {
		ret = rc_config_update(rcgwr->base, i, i);
		if (ret) {
			dev_err(&pdev->dev,
			"Failed to update config for DFSSID-0x%x and ack 0x%lx\n",
					i, BIT(i));
			break;
		}
	}

	return ret;
}

static int ramp_dfs_sid(struct platform_device *pdev, struct rcgwr *rcgwr)
{
	int ret = 0, i, j;

	if (!rcgwr->dfs_sid_len) {
		pr_err("Use Default DFS SID\n");
		return 0;
	}

	ret = check_rcg_config(rcgwr->rcg_base);
	if  (ret) {
		dev_err(&pdev->dev, "Failed to update config!!!\n");
		return ret;
	}

	for (i = 0; i < rcgwr->dfs_sid_len; i++)
		writel_relaxed(rcgwr->dfs_sid_value[i],
				rcgwr->base + rcgwr->dfs_sid_offset[i]);

	for (i = DFS_SID_START, j = 0; j < rcgwr->dfs_sid_len; i--, j++) {
		ret = rc_config_update(rcgwr->base, i, i);
		if (ret) {
			dev_err(&pdev->dev,
				"Failed to update config for DFSSID-0x%x and ack 0x%lx\n",
					i, BIT(i));
			break;
		}
	}

	return ret;
}

static int parse_dt_rcgwr(struct platform_device *pdev, char *prop_name,
				int **off, int **val, int *len)
{
	struct device_node *node = pdev->dev.of_node;
	int prop_len, i;
	u32 *array;

	if (!of_find_property(node, prop_name, &prop_len)) {
		dev_err(&pdev->dev, "missing %s\n", prop_name);
		return -EINVAL;
	}

	prop_len /= sizeof(u32);
	if (prop_len % 2) {
		dev_err(&pdev->dev, "bad length %d\n", prop_len);
		return -EINVAL;
	}

	prop_len /= 2;

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

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

	array = devm_kzalloc(&pdev->dev,
			prop_len * sizeof(u32) * 2, GFP_KERNEL);
	if (!array)
		return -ENOMEM;

	of_property_read_u32_array(node, prop_name, array, prop_len * 2);
	for (i = 0; i < prop_len; i++) {
		*(*off + i) = array[i * 2];
		*(*val + i) = array[2 * i + 1];
	}

	*len = prop_len;

	return 0;
}

static int rcgwr_init_bases(struct platform_device *pdev, struct rcgwr *rcgwr,
		const char *name)
{
	struct resource *res;
	char rcg_name[] = "rcgwr-xxx-base";
	char rcg_mux[] = "xxx-mux";

	snprintf(rcg_name, ARRAY_SIZE(rcg_name), "rcgwr-%s-base", name);
	res = platform_get_resource_byname(pdev,
					IORESOURCE_MEM, rcg_name);
	if (!res) {
		dev_err(&pdev->dev, "missing %s\n", rcg_name);
		return -EINVAL;
	}

	rcgwr->base = devm_ioremap(&pdev->dev, res->start,
						resource_size(res));
	if (!rcgwr->base) {
		dev_err(&pdev->dev, "ioremap failed for %s\n",
					rcg_name);
		return -ENOMEM;
	}

	snprintf(rcg_mux, ARRAY_SIZE(rcg_mux), "%s-mux", name);
	res = platform_get_resource_byname(pdev,
					IORESOURCE_MEM, rcg_mux);
	if (!res) {
		dev_err(&pdev->dev, "missing %s\n", rcg_mux);
		return -EINVAL;
	}

	rcgwr->rcg_base = devm_ioremap(&pdev->dev, res->start,
						resource_size(res));
	if (!rcgwr->rcg_base) {
		dev_err(&pdev->dev, "ioremap failed for %s\n",
					rcg_name);
		return -ENOMEM;
	}

	return 0;
}

/*
 * Disable the RCG ramp controller.
 */
int clock_rcgwr_disable(struct platform_device *pdev)
{
	int i, ret = 0;

	for (i = 0; i < num_clusters; i++) {
		if (!rcgwr[i])
			return -ENOMEM;
		ret = ramp_control_disable(pdev, rcgwr[i]);
		if (ret)
			dev_err(&pdev->dev,
			"Ramp controller disable failed for Cluster-%d\n", i);
	}

	return ret;
}

static int clock_rcgwr_disable_set(void *data, u64 val)
{
	if (val) {
		pr_err("Enabling not supported!!\n");
		return -EINVAL;
	} else
		return clock_rcgwr_disable(cpu_clock_dev);
}

DEFINE_SIMPLE_ATTRIBUTE(rcgwr_enable_fops, NULL,
			clock_rcgwr_disable_set, "%lld\n");

static int clock_debug_enable_show(struct seq_file *m, void *v)
{
	int i = 0;

	seq_puts(m, "Cluster\t\tEnable\n");

	for (i = 0; i < num_clusters; i++)
		seq_printf(m, "%d\t\t%d\n", i, rcgwr[i]->inited);

	return 0;
}

static int clock_debug_open(struct inode *inode, struct file *file)
{
	return single_open(file, clock_debug_enable_show, inode->i_private);
}

static const struct file_operations rcgwr_enable_show = {
	.owner		= THIS_MODULE,
	.open		= clock_debug_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
};

/*
 * Program the DFS Sequence ID.
 * Program the Link Sequence ID.
 * Enable RCG with ramp controller.
 */
int clock_rcgwr_init(struct platform_device *pdev)
{
	int ret = 0, i;
	char link_sid[] = "qcom,link-sid-xxx";
	char dfs_sid[]  = "qcom,dfs-sid-xxx";
	char lmh_sid[]  = "qcom,lmh-sid-xxx";
	char ramp_dis[] = "qcom,ramp-dis-xxx";
	char names[] = "cxxx";
	struct dentry *debugfs_base;

	ret = of_property_read_u32(pdev->dev.of_node, "qcom,num-clusters",
						&num_clusters);
	if (ret)
		panic("Cannot read num-clusters from dt (ret:%d)\n", ret);

	rcgwr = devm_kzalloc(&pdev->dev, sizeof(struct rcgwr) * num_clusters,
				GFP_KERNEL);
	if (!rcgwr)
		BUG();

	for (i = 0; i < num_clusters; i++) {
		rcgwr[i] = devm_kzalloc(&pdev->dev, sizeof(struct rcgwr),
				GFP_KERNEL);
		if (!rcgwr[i])
			goto fail_mem;

		snprintf(names, ARRAY_SIZE(names), "c%d", i);

		ret = rcgwr_init_bases(pdev, rcgwr[i], names);
		if (ret) {
			dev_err(&pdev->dev, "Failed to init_bases for RCGwR\n");
			goto fail_mem;
		}

		snprintf(dfs_sid, ARRAY_SIZE(dfs_sid),
					"qcom,dfs-sid-%s", names);
		ret = parse_dt_rcgwr(pdev, dfs_sid, &(rcgwr[i]->dfs_sid_offset),
			&(rcgwr[i]->dfs_sid_value), &(rcgwr[i]->dfs_sid_len));
		if (ret)
			dev_err(&pdev->dev,
				"No DFS SID tables found for Cluster-%d\n", i);

		snprintf(link_sid, ARRAY_SIZE(link_sid),
					"qcom,link-sid-%s", names);
		ret = parse_dt_rcgwr(pdev, link_sid,
			&(rcgwr[i]->link_sid_offset),
			&(rcgwr[i]->link_sid_value), &(rcgwr[i]->link_sid_len));
		if (ret)
			dev_err(&pdev->dev,
				"No Link SID tables found for Cluster-%d\n", i);

		snprintf(lmh_sid, ARRAY_SIZE(lmh_sid),
					"qcom,lmh-sid-%s", names);
		ret = parse_dt_rcgwr(pdev, lmh_sid,
			&(rcgwr[i]->lmh_sid_offset),
			&(rcgwr[i]->lmh_sid_value), &(rcgwr[i]->lmh_sid_len));
		if (ret)
			dev_err(&pdev->dev,
				"No LMH SID tables found for Cluster-%d\n", i);

		ret = ramp_lmh_sid(pdev, rcgwr[i]);
		if (ret)
			goto fail_mem;

		ret = ramp_dfs_sid(pdev, rcgwr[i]);
		if (ret)
			goto fail_mem;

		ret = ramp_link_sid(pdev, rcgwr[i]);
		if (ret)
			goto fail_mem;

		ret = ramp_control_enable(pdev, rcgwr[i]);
		if (ret)
			goto fail_mem;

		snprintf(ramp_dis, ARRAY_SIZE(ramp_dis),
					"qcom,ramp-dis-%s", names);
		if (of_property_read_bool(pdev->dev.of_node, ramp_dis)) {
			ret = ramp_down_disable(pdev, rcgwr[i]);
			if (ret)
				goto fail_mem;
		}

		rcgwr[i]->inited = true;
	}

	cpu_clock_dev = pdev;

	debugfs_base = debugfs_create_dir("rcgwr", NULL);
	if (debugfs_base) {
		if (!debugfs_create_file("enable", S_IRUGO, debugfs_base, NULL,
				&rcgwr_enable_fops)) {
			pr_err("Unable to create `enable` debugfs entry\n");
			debugfs_remove(debugfs_base);
		}

		if (!debugfs_create_file("status", 0444, debugfs_base, NULL,
					&rcgwr_enable_show)) {
			pr_err("Unable to create `status` debugfs entry\n");
			debugfs_remove_recursive(debugfs_base);
		}
	} else
		pr_err("Unable to create debugfs dir\n");

	pr_info("RCGwR  Init Completed\n");

	return ret;

fail_mem:
	--i;
	for (; i >= 0 ; i--) {
		devm_kfree(&pdev->dev, rcgwr[i]);
		rcgwr[i] = NULL;
	}
	devm_kfree(&pdev->dev, rcgwr);
	panic("RCGwR failed to Initialize\n");
}
