/*
 * Copyright (c) 2014, 2016, 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) "msmclock: %s: " fmt, __func__

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/regulator/consumer.h>
#include <linux/of.h>
#include <linux/hashtable.h>

#include <linux/clk/msm-clk-provider.h>
#include <soc/qcom/msm-clock-controller.h>
#include <soc/qcom/clock-rpm.h>

/* Protects list operations */
static DEFINE_MUTEX(msmclk_lock);
static LIST_HEAD(msmclk_parser_list);
static u32 msmclk_debug;

struct hitem {
	struct hlist_node list;
	phandle key;
	void *ptr;
};

int of_property_count_phandles(struct device_node *np, char *propname)
{
	const __be32 *phandle;
	int size;

	phandle = of_get_property(np, propname, &size);
	return phandle ? (size / sizeof(*phandle)) : -EINVAL;
}
EXPORT_SYMBOL(of_property_count_phandles);

int of_property_read_phandle_index(struct device_node *np, char *propname,
					int index, phandle *p)
{
	const __be32 *phandle;
	int size;

	phandle = of_get_property(np, propname, &size);
	if ((!phandle) || (size < sizeof(*phandle) * (index + 1)))
		return -EINVAL;

	*p = be32_to_cpup(phandle + index);
	return 0;
}
EXPORT_SYMBOL(of_property_read_phandle_index);

static int generic_vdd_parse_regulators(struct device *dev,
		struct clk_vdd_class *vdd, struct device_node *np)
{
	int num_regulators, i, rc;
	char *name = "qcom,regulators";

	num_regulators = of_property_count_phandles(np, name);
	if (num_regulators <= 0) {
		dt_prop_err(np, name, "missing dt property\n");
		return -EINVAL;
	}

	vdd->regulator = devm_kzalloc(dev,
				sizeof(*vdd->regulator) * num_regulators,
				GFP_KERNEL);
	if (!vdd->regulator) {
		dt_err(np, "memory alloc failure\n");
		return -ENOMEM;
	}

	for (i = 0; i < num_regulators; i++) {
		phandle p;
		rc = of_property_read_phandle_index(np, name, i, &p);
		if (rc) {
			dt_prop_err(np, name, "unable to read phandle\n");
			return rc;
		}

		vdd->regulator[i] = msmclk_parse_phandle(dev, p);
		if (IS_ERR(vdd->regulator[i])) {
			dt_prop_err(np, name, "hashtable lookup failed\n");
			return PTR_ERR(vdd->regulator[i]);
		}
	}

	vdd->num_regulators = num_regulators;
	return 0;
}

static int generic_vdd_parse_levels(struct device *dev,
		struct clk_vdd_class *vdd, struct device_node *np)
{
	int len, rc;
	char *name = "qcom,uV-levels";

	if (!of_find_property(np, name, &len)) {
		dt_prop_err(np, name, "missing dt property\n");
		return -EINVAL;
	}

	len /= sizeof(u32);
	if (len % vdd->num_regulators) {
		dt_err(np, "mismatch beween qcom,uV-levels and qcom,regulators dt properties\n");
		return -EINVAL;
	}

	vdd->num_levels = len / vdd->num_regulators;
	vdd->vdd_uv = devm_kzalloc(dev, len * sizeof(*vdd->vdd_uv),
						GFP_KERNEL);
	vdd->level_votes = devm_kzalloc(dev,
				vdd->num_levels * sizeof(*vdd->level_votes),
				GFP_KERNEL);

	if (!vdd->vdd_uv || !vdd->level_votes) {
		dt_err(np, "memory alloc failure\n");
		return -ENOMEM;
	}

	rc = of_property_read_u32_array(np, name, vdd->vdd_uv,
					vdd->num_levels * vdd->num_regulators);
	if (rc) {
		dt_prop_err(np, name, "unable to read u32 array\n");
		return -EINVAL;
	}

	/* Optional Property */
	name = "qcom,uA-levels";
	if (!of_find_property(np, name, &len))
		return 0;

	len /= sizeof(u32);
	if (len / vdd->num_regulators != vdd->num_levels) {
		dt_err(np, "size of qcom,uA-levels and qcom,uV-levels must match\n");
		return -EINVAL;
	}

	vdd->vdd_ua = devm_kzalloc(dev, len * sizeof(*vdd->vdd_ua),
						GFP_KERNEL);
	if (!vdd->vdd_ua) {
		dt_err(np, "memory alloc failure\n");
		return -ENOMEM;
	}

	rc = of_property_read_u32_array(np, name, vdd->vdd_ua,
					vdd->num_levels * vdd->num_regulators);
	if (rc) {
		dt_prop_err(np, name, "unable to read u32 array\n");
		return -EINVAL;
	}

	return 0;
}

static void *simple_vdd_class_dt_parser(struct device *dev,
			struct device_node *np)
{
	struct clk_vdd_class *vdd;
	int rc = 0;

	vdd = devm_kzalloc(dev, sizeof(*vdd), GFP_KERNEL);
	if (!vdd) {
		dev_err(dev, "memory alloc failure\n");
		return ERR_PTR(-ENOMEM);
	}

	mutex_init(&vdd->lock);
	vdd->class_name = np->name;

	rc = generic_vdd_parse_regulators(dev, vdd, np);
	rc |= generic_vdd_parse_levels(dev, vdd, np);
	if (rc) {
		dt_err(np, "unable to read vdd_class\n");
		return ERR_PTR(rc);
	}

	return vdd;
}
MSMCLK_PARSER(simple_vdd_class_dt_parser, "qcom,simple-vdd-class", 0);

static int generic_clk_parse_parents(struct device *dev, struct clk *c,
					struct device_node *np)
{
	int rc;
	phandle p;
	char *name = "qcom,parent";

	/* This property is optional */
	if (!of_find_property(np, name, NULL))
		return 0;

	rc = of_property_read_phandle_index(np, name, 0, &p);
	if (rc) {
		dt_prop_err(np, name, "unable to read phandle\n");
		return rc;
	}

	c->parent = msmclk_parse_phandle(dev, p);
	if (IS_ERR(c->parent)) {
		dt_prop_err(np, name, "hashtable lookup failed\n");
		return PTR_ERR(c->parent);
	}

	return 0;
}

static int generic_clk_parse_vdd(struct device *dev, struct clk *c,
					struct device_node *np)
{
	phandle p;
	int rc;
	char *name = "qcom,supply-group";

	/* This property is optional */
	if (!of_find_property(np, name, NULL))
		return 0;

	rc = of_property_read_phandle_index(np, name, 0, &p);
	if (rc) {
		dt_prop_err(np, name, "unable to read phandle\n");
		return rc;
	}

	c->vdd_class = msmclk_parse_phandle(dev, p);
	if (IS_ERR(c->vdd_class)) {
		dt_prop_err(np, name, "hashtable lookup failed\n");
		return PTR_ERR(c->vdd_class);
	}

	return 0;
}

static int generic_clk_parse_flags(struct device *dev, struct clk *c,
						struct device_node *np)
{
	int rc;
	char *name = "qcom,clk-flags";

	/* This property is optional */
	if (!of_find_property(np, name, NULL))
		return 0;

	rc = of_property_read_u32(np, name, &c->flags);
	if (rc) {
		dt_prop_err(np, name, "unable to read u32\n");
		return rc;
	}

	return 0;
}

static int generic_clk_parse_fmax(struct device *dev, struct clk *c,
					struct device_node *np)
{
	u32 prop_len, i;
	int rc;
	char *name = "qcom,clk-fmax";

	/* This property is optional */
	if (!of_find_property(np, name, &prop_len))
		return 0;

	if (!c->vdd_class) {
		dt_err(np, "both qcom,clk-fmax and qcom,supply-group must be defined\n");
		return -EINVAL;
	}

	prop_len /= sizeof(u32);
	if (prop_len % 2) {
		dt_prop_err(np, name, "bad length\n");
		return -EINVAL;
	}

	/* Value at proplen - 2 is the index of the  last entry in fmax array */
	rc = of_property_read_u32_index(np, name, prop_len - 2, &c->num_fmax);
	c->num_fmax += 1;
	if (rc) {
		dt_prop_err(np, name, "unable to read u32\n");
		return rc;
	}

	c->fmax = devm_kzalloc(dev, sizeof(*c->fmax) * c->num_fmax, GFP_KERNEL);
	if (!c->fmax) {
		dev_err(dev, "memory alloc failure\n");
		return -ENOMEM;
	}

	for (i = 0; i < prop_len; i += 2) {
		u32 level, value;
		rc = of_property_read_u32_index(np, name, i, &level);
		if (rc) {
			dt_prop_err(np, name, "unable to read u32\n");
			return rc;
		}

		rc = of_property_read_u32_index(np, name, i + 1, &value);
		if (rc) {
			dt_prop_err(np, name, "unable to read u32\n");
			return rc;
		}

		if (level >= c->num_fmax) {
			dt_prop_err(np, name, "must be sorted\n");
			return -EINVAL;
		}
		c->fmax[level] = value;
	}

	return 0;
}

static int generic_clk_add_lookup_tbl_entry(struct device *dev, struct clk *c)
{
	struct msmclk_data *drv = dev_get_drvdata(dev);
	struct clk_lookup *cl;

	if (drv->clk_tbl_size >= drv->max_clk_tbl_size) {
		dev_err(dev, "child node count should be > clock_count?\n");
		return -EINVAL;
	}

	cl = drv->clk_tbl + drv->clk_tbl_size;
	cl->clk = c;
	drv->clk_tbl_size++;
	return 0;
}

static int generic_clk_parse_depends(struct device *dev, struct clk *c,
						struct device_node *np)
{
	phandle p;
	int rc;
	char *name = "qcom,depends";

	/* This property is optional */
	if (!of_find_property(np, name, NULL))
		return 0;

	rc = of_property_read_phandle_index(np, name, 0, &p);
	if (rc) {
		dt_prop_err(np, name, "unable to read phandle\n");
		return rc;
	}

	c->depends = msmclk_parse_phandle(dev, p);
	if (IS_ERR(c->depends)) {
		dt_prop_err(np, name, "hashtable lookup failed\n");
		return PTR_ERR(c->depends);
	}

	return 0;
}

static int generic_clk_parse_init_config(struct device *dev, struct clk *c,
						struct device_node *np)
{
	int rc;
	u32 temp;
	char *name = "qcom,always-on";

	c->always_on = of_property_read_bool(np, name);

	name = "qcom,config-rate";
	/* This property is optional */
	if (!of_find_property(np, name, NULL))
		return 0;

	rc = of_property_read_u32(np, name, &temp);
	if (rc) {
		dt_prop_err(np, name, "unable to read u32\n");
		return rc;
	}
	c->init_rate = temp;

	return rc;
}

void *msmclk_generic_clk_init(struct device *dev, struct device_node *np,
				struct clk *c)
{
	int rc;

	/* CLK_INIT macro */
	spin_lock_init(&c->lock);
	mutex_init(&c->prepare_lock);
	INIT_LIST_HEAD(&c->children);
	INIT_LIST_HEAD(&c->siblings);
	INIT_LIST_HEAD(&c->list);
	c->dbg_name = np->name;

	rc = generic_clk_add_lookup_tbl_entry(dev, c);
	rc |= generic_clk_parse_flags(dev, c, np);
	rc |= generic_clk_parse_parents(dev, c, np);
	rc |= generic_clk_parse_vdd(dev, c, np);
	rc |= generic_clk_parse_fmax(dev, c, np);
	rc |= generic_clk_parse_depends(dev, c, np);
	rc |= generic_clk_parse_init_config(dev, c, np);

	if (rc) {
		dt_err(np, "unable to read clk\n");
		return ERR_PTR(-EINVAL);
	}

	return c;
}

static struct msmclk_parser *msmclk_parser_lookup(struct device_node *np)
{
	struct msmclk_parser *item;
	list_for_each_entry(item, &msmclk_parser_list, list) {
		if (of_device_is_compatible(np, item->compatible))
			return item;
	}
	return NULL;
}
void msmclk_parser_register(struct msmclk_parser *item)
{
	mutex_lock(&msmclk_lock);
	list_add(&item->list, &msmclk_parser_list);
	mutex_unlock(&msmclk_lock);
}

static int msmclk_htable_add(struct device *dev, void *result, phandle key);

void *msmclk_parse_dt_node(struct device *dev, struct device_node *np)
{
	struct msmclk_parser *parser;
	phandle key;
	void *result;
	int rc;

	key = np->phandle;
	result = msmclk_lookup_phandle(dev, key);
	if (!result)
		return ERR_PTR(-EINVAL);

	if (!of_device_is_available(np)) {
		dt_err(np, "node is disabled\n");
		return ERR_PTR(-EINVAL);
	}

	parser = msmclk_parser_lookup(np);
	if (IS_ERR_OR_NULL(parser)) {
		dt_err(np, "no parser found\n");
		return ERR_PTR(-EINVAL);
	}

	/* This may return -EPROBE_DEFER */
	result = parser->parsedt(dev, np);
	if (IS_ERR(result)) {
		dt_err(np, "parsedt failed");
		return result;
	}

	rc = msmclk_htable_add(dev, result, key);
	if (rc)
		return ERR_PTR(rc);

	return result;
}

void *msmclk_parse_phandle(struct device *dev, phandle key)
{
	struct hitem *item;
	struct device_node *np;
	struct msmclk_data *drv = dev_get_drvdata(dev);

	/*
	 * the default phandle value is 0. Since hashtable keys must
	 * be unique, reject the default value.
	 */
	if (!key)
		return ERR_PTR(-EINVAL);

	hash_for_each_possible(drv->htable, item, list, key) {
		if (item->key == key)
			return item->ptr;
	}

	np = of_find_node_by_phandle(key);
	if (!np)
		return ERR_PTR(-EINVAL);

	return msmclk_parse_dt_node(dev, np);
}
EXPORT_SYMBOL(msmclk_parse_phandle);

void *msmclk_lookup_phandle(struct device *dev, phandle key)
{
	struct hitem *item;
	struct msmclk_data *drv = dev_get_drvdata(dev);

	hash_for_each_possible(drv->htable, item, list, key) {
		if (item->key == key)
			return item->ptr;
	}

	return ERR_PTR(-EINVAL);
}
EXPORT_SYMBOL(msmclk_lookup_phandle);

static int msmclk_htable_add(struct device *dev, void *data, phandle key)
{
	struct hitem *item;
	struct msmclk_data *drv = dev_get_drvdata(dev);

	/*
	 * If there are no phandle references to a node, key == 0. However, if
	 * there is a second node like this, both will have key == 0. This
	 * violates the requirement that hashtable keys be unique. Skip it.
	 */
	if (!key)
		return 0;

	if (!IS_ERR(msmclk_lookup_phandle(dev, key))) {
		struct device_node *np = of_find_node_by_phandle(key);
		dev_err(dev, "attempt to add duplicate entry for %s\n",
				np ? np->name : "NULL");
		return -EINVAL;
	}

	item = devm_kzalloc(dev, sizeof(*item), GFP_KERNEL);
	if (!item) {
		dev_err(dev, "memory alloc failure\n");
		return -ENOMEM;
	}

	INIT_HLIST_NODE(&item->list);
	item->key = key;
	item->ptr = data;

	hash_add(drv->htable, &item->list, key);
	return 0;
}

/*
 * Currently, regulators are the only elements capable of probe deferral.
 * Check them first to handle probe deferal efficiently.
*/
static int get_ext_regulators(struct device *dev)
{
	int num_strings, i, rc;
	struct device_node *np;
	void *item;
	char *name = "qcom,regulator-names";

	np = dev->of_node;
	/* This property is optional */
	num_strings = of_property_count_strings(np, name);
	if (num_strings <= 0)
		return 0;

	for (i = 0; i < num_strings; i++) {
		const char *str;
		char buf[50];
		phandle key;

		rc = of_property_read_string_index(np, name, i, &str);
		if (rc) {
			dt_prop_err(np, name, "unable to read string\n");
			return rc;
		}

		item = devm_regulator_get(dev, str);
		if (IS_ERR(item)) {
			dev_err(dev, "Failed to get regulator: %s\n", str);
			return PTR_ERR(item);
		}

		snprintf(buf, ARRAY_SIZE(buf), "%s-supply", str);
		rc = of_property_read_phandle_index(np, buf, 0, &key);
		if (rc) {
			dt_prop_err(np, buf, "unable to read phandle\n");
			return rc;
		}

		rc = msmclk_htable_add(dev, item, key);
		if (rc)
			return rc;
	}
	return 0;
}

static struct clk *msmclk_clk_get(struct of_phandle_args *clkspec, void *data)
{
	phandle key;
	struct clk *c = ERR_PTR(-ENOENT);

	key = clkspec->args[0];
	c = msmclk_lookup_phandle(data, key);

	if (!IS_ERR(c) && !(c->flags & CLKFLAG_INIT_DONE))
		return ERR_PTR(-EPROBE_DEFER);

	return c;
}

static void *regulator_dt_parser(struct device *dev, struct device_node *np)
{
	dt_err(np, "regulators should be handled in probe()");
	return ERR_PTR(-EINVAL);
}
MSMCLK_PARSER(regulator_dt_parser, "qcom,rpm-smd-regulator", 0);

static void *msmclk_dt_parser(struct device *dev, struct device_node *np)
{
	dt_err(np, "calling into other clock controllers isn't allowed");
	return ERR_PTR(-EINVAL);
}
MSMCLK_PARSER(msmclk_dt_parser, "qcom,msm-clock-controller", 0);

static struct msmclk_data *msmclk_drv_init(struct device *dev)
{
	struct msmclk_data *drv;
	size_t size;

	drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
	if (!drv) {
		dev_err(dev, "memory alloc failure\n");
		return ERR_PTR(-ENOMEM);
	}
	dev_set_drvdata(dev, drv);

	drv->dev = dev;
	INIT_LIST_HEAD(&drv->list);

	/* This overestimates size */
	drv->max_clk_tbl_size = of_get_child_count(dev->of_node);
	size = sizeof(*drv->clk_tbl) * drv->max_clk_tbl_size;
	drv->clk_tbl = devm_kzalloc(dev, size, GFP_KERNEL);
	if (!drv->clk_tbl) {
		dev_err(dev, "memory alloc failure clock table size %zu\n",
				size);
		return ERR_PTR(-ENOMEM);
	}

	hash_init(drv->htable);
	return drv;
}

static int msmclk_probe(struct platform_device *pdev)
{
	struct resource *res;
	struct device *dev;
	struct msmclk_data *drv;
	struct device_node *child;
	void *result;
	int rc = 0;

	dev = &pdev->dev;
	drv = msmclk_drv_init(dev);
	if (IS_ERR(drv))
		return PTR_ERR(drv);

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cc-base");
	if (!res) {
		dt_err(dev->of_node, "missing cc-base\n");
		return -EINVAL;
	}
	drv->base = devm_ioremap(dev, res->start, resource_size(res));
	if (!drv->base) {
		dev_err(dev, "ioremap failed for drv->base\n");
		return -ENOMEM;
	}
	rc = msmclk_htable_add(dev, drv, dev->of_node->phandle);
	if (rc)
		return rc;

	rc = enable_rpm_scaling();
	if (rc)
		return rc;

	rc = get_ext_regulators(dev);
	if (rc)
		return rc;

	/*
	 * Returning -EPROBE_DEFER here is inefficient due to
	 * destroying work 'unnecessarily'
	 */
	for_each_available_child_of_node(dev->of_node, child) {
		result = msmclk_parse_dt_node(dev, child);
		if (!IS_ERR(result))
			continue;
		if (!msmclk_debug)
			return PTR_ERR(result);
		/*
		 * Parse and report all errors instead of immediately
		 * exiting. Return the first error code.
		 */
		if (!rc)
			rc = PTR_ERR(result);
	}
	if (rc)
		return rc;

	rc = of_clk_add_provider(dev->of_node, msmclk_clk_get, dev);
	if (rc) {
		dev_err(dev, "of_clk_add_provider failed\n");
		return rc;
	}

	/*
	 * can't fail after registering clocks, because users may have
	 * gotten clock references. Failing would delete the memory.
	 */
	WARN_ON(msm_clock_register(drv->clk_tbl, drv->clk_tbl_size));
	dev_info(dev, "registered clocks\n");

	return 0;
}

static struct of_device_id msmclk_match_table[] = {
	{.compatible = "qcom,msm-clock-controller"},
	{}
};

static struct platform_driver msmclk_driver = {
	.probe = msmclk_probe,
	.driver = {
		.name =  "msm-clock-controller",
		.of_match_table = msmclk_match_table,
		.owner = THIS_MODULE,
	},
};

static bool initialized;
int __init msmclk_init(void)
{
	int rc;
	if (initialized)
		return 0;

	rc = platform_driver_register(&msmclk_driver);
	if (rc)
		return rc;
	initialized = true;
	return rc;
}
arch_initcall(msmclk_init);
