regulator: mem-acc-regulator: Add a driver to control the MEM ACC

The memory accelerator (MEM ACC) controls the delays applied during
memory access. These delays vary based on the power-mode (corner)
on which the memory is connected. This driver adds a regulator
interface to configure the power-mode of the memory accelerator
based on the APC (application processor) corner.

CRs-fixed: 638439
Change-Id: I830ff000f8a16c4d3de5bdebf48d424354ddb086
Signed-off-by: Anirudh Ghayal <aghayal@codeaurora.org>
Signed-off-by: Ke Liu <keliu@codeaurora.org>
diff --git a/drivers/regulator/mem-acc-regulator.c b/drivers/regulator/mem-acc-regulator.c
new file mode 100644
index 0000000..b66285a
--- /dev/null
+++ b/drivers/regulator/mem-acc-regulator.c
@@ -0,0 +1,461 @@
+/* Copyright (c) 2014, 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)	"ACC: %s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+
+#define MEM_ACC_SEL_MASK	0x3
+
+enum {
+	MEMORY_L1,
+	MEMORY_L2,
+	MEMORY_MAX,
+};
+
+struct mem_acc_regulator {
+	struct device		*dev;
+	struct regulator_desc	rdesc;
+	struct regulator_dev	*rdev;
+
+	int			corner;
+	bool			mem_acc_supported[MEMORY_MAX];
+
+	u32			acc_sel_reg[MEMORY_MAX];
+	u32			*acc_sel_mask[MEMORY_MAX];
+	u32			*acc_sel_bit_pos[MEMORY_MAX];
+	u32			num_acc_sel[MEMORY_MAX];
+	u32			*acc_en_bit_pos;
+	u32			num_acc_en;
+	u32			*corner_acc_map;
+	u32			num_corners;
+
+	void __iomem		*acc_sel_base[MEMORY_MAX];
+	void __iomem		*acc_en_base;
+	phys_addr_t		acc_sel_addr[MEMORY_MAX];
+	phys_addr_t		acc_en_addr;
+};
+
+static inline u32 apc_to_acc_corner(struct mem_acc_regulator *mem_acc_vreg,
+								int corner)
+{
+	/*
+	 * corner_acc_map maps the corner from index 0 and  APC corner value
+	 * starts from the value 1
+	 */
+	return mem_acc_vreg->corner_acc_map[corner - 1];
+}
+
+static void __update_acc_sel(struct mem_acc_regulator *mem_acc_vreg,
+						int corner, int mem_type)
+{
+	u32 acc_data, i, bit, acc_corner;
+
+	acc_data = mem_acc_vreg->acc_sel_reg[mem_type];
+	for (i = 0; i < mem_acc_vreg->num_acc_sel[mem_type]; i++) {
+		bit = mem_acc_vreg->acc_sel_bit_pos[mem_type][i];
+		acc_data &= ~mem_acc_vreg->acc_sel_mask[mem_type][i];
+		acc_corner = apc_to_acc_corner(mem_acc_vreg, corner);
+		acc_data |= (acc_corner << bit) &
+			mem_acc_vreg->acc_sel_mask[mem_type][i];
+	}
+	pr_debug("corner=%d old_acc_sel=0x%02x new_acc_sel=0x%02x mem_type=%d\n",
+			corner, mem_acc_vreg->acc_sel_reg[mem_type],
+						acc_data, mem_type);
+	writel_relaxed(acc_data, mem_acc_vreg->acc_sel_base[mem_type]);
+	mem_acc_vreg->acc_sel_reg[mem_type] = acc_data;
+}
+
+static void update_acc_sel(struct mem_acc_regulator *mem_acc_vreg, int corner)
+{
+	int i;
+
+	for (i = 0; i < MEMORY_MAX; i++) {
+		if (mem_acc_vreg->mem_acc_supported[i])
+			__update_acc_sel(mem_acc_vreg, corner, i);
+	}
+}
+
+static int mem_acc_regulator_set_voltage(struct regulator_dev *rdev,
+		int corner, int corner_max, unsigned *selector)
+{
+	struct mem_acc_regulator *mem_acc_vreg = rdev_get_drvdata(rdev);
+	int i;
+
+	if (corner > mem_acc_vreg->num_corners) {
+		pr_err("Invalid corner=%d requested\n", corner);
+		return -EINVAL;
+	}
+
+	pr_debug("old corner=%d, new corner=%d\n",
+			mem_acc_vreg->corner, corner);
+
+	if (corner == mem_acc_vreg->corner)
+		return 0;
+
+	/* go up or down one level at a time */
+	if (corner > mem_acc_vreg->corner) {
+		for (i = mem_acc_vreg->corner + 1; i <= corner; i++) {
+			pr_debug("UP: to corner %d\n", i);
+			update_acc_sel(mem_acc_vreg, i);
+		}
+	} else {
+		for (i = mem_acc_vreg->corner - 1; i >= corner; i--) {
+			pr_debug("DOWN: to corner %d\n", i);
+			update_acc_sel(mem_acc_vreg, i);
+		}
+	}
+
+	pr_debug("new voltage corner set %d\n", corner);
+
+	mem_acc_vreg->corner = corner;
+
+	return 0;
+}
+
+static int mem_acc_regulator_get_voltage(struct regulator_dev *rdev)
+{
+	struct mem_acc_regulator *mem_acc_vreg = rdev_get_drvdata(rdev);
+
+	return mem_acc_vreg->corner;
+}
+
+static struct regulator_ops mem_acc_corner_ops = {
+	.set_voltage		= mem_acc_regulator_set_voltage,
+	.get_voltage		= mem_acc_regulator_get_voltage,
+};
+
+static int __mem_acc_sel_init(struct mem_acc_regulator *mem_acc_vreg,
+							int mem_type)
+{
+	int i;
+	u32 bit;
+
+	mem_acc_vreg->acc_sel_mask[mem_type] = devm_kzalloc(mem_acc_vreg->dev,
+		mem_acc_vreg->num_acc_sel[mem_type] * sizeof(u32), GFP_KERNEL);
+	if (!mem_acc_vreg->acc_sel_mask[mem_type]) {
+		pr_err("Unable to allocate memory for mem_type=%d\n", mem_type);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < mem_acc_vreg->num_acc_sel[mem_type]; i++) {
+		bit = mem_acc_vreg->acc_sel_bit_pos[mem_type][i];
+		mem_acc_vreg->acc_sel_mask[mem_type][i] =
+					MEM_ACC_SEL_MASK << bit;
+	}
+
+	mem_acc_vreg->acc_sel_reg[mem_type] =
+		readl_relaxed(mem_acc_vreg->acc_sel_base[mem_type]);
+
+	return 0;
+}
+
+static int mem_acc_sel_init(struct mem_acc_regulator *mem_acc_vreg)
+{
+	int i, rc;
+
+	for (i = 0; i < MEMORY_MAX; i++) {
+		if (mem_acc_vreg->mem_acc_supported[i]) {
+			rc = __mem_acc_sel_init(mem_acc_vreg, i);
+			if (rc) {
+				pr_err("Unable to intialize mem_type=%d rc=%d\n",
+								i, rc);
+				return rc;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static void mem_acc_en_init(struct mem_acc_regulator *mem_acc_vreg)
+{
+	int i, bit;
+	u32 acc_data;
+
+	acc_data = readl_relaxed(mem_acc_vreg->acc_en_base);
+	pr_debug("init: acc_en_register=%x\n", acc_data);
+	for (i = 0; i < mem_acc_vreg->num_acc_en; i++) {
+		bit = mem_acc_vreg->acc_en_bit_pos[i];
+		acc_data |= BIT(bit);
+	}
+	pr_debug("final: acc_en_register=%x\n", acc_data);
+	writel_relaxed(acc_data, mem_acc_vreg->acc_en_base);
+}
+
+static int populate_acc_data(struct mem_acc_regulator *mem_acc_vreg,
+			const char *prop_name, u32 **value, u32 *len)
+{
+	int rc;
+
+	if (!of_get_property(mem_acc_vreg->dev->of_node, prop_name, len)) {
+		pr_err("Unable to find %s property\n", prop_name);
+		return -EINVAL;
+	}
+	*len /= sizeof(u32);
+	if (!(*len)) {
+		pr_err("Incorrect entries in %s\n", prop_name);
+		return -EINVAL;
+	}
+
+	*value = devm_kzalloc(mem_acc_vreg->dev, (*len) * sizeof(u32),
+							GFP_KERNEL);
+	if (!(*value)) {
+		pr_err("Unable to allocate memory for %s\n", prop_name);
+		return -ENOMEM;
+	}
+
+	pr_debug("Found %s, data-length = %d\n", prop_name, *len);
+
+	rc = of_property_read_u32_array(mem_acc_vreg->dev->of_node,
+					prop_name, *value, *len);
+	if (rc) {
+		pr_err("Unable to populate %s rc=%d\n", prop_name, rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int mem_acc_sel_setup(struct mem_acc_regulator *mem_acc_vreg,
+			struct resource *res, int mem_type)
+{
+	int len, rc;
+	char *mem_select_str;
+
+	mem_acc_vreg->acc_sel_addr[mem_type] = res->start;
+	len = res->end - res->start + 1;
+	pr_debug("'acc_sel_addr' = %pa mem_type=%d (len=%d)\n",
+					&res->start, mem_type, len);
+
+	mem_acc_vreg->acc_sel_base[mem_type] = devm_ioremap(mem_acc_vreg->dev,
+			mem_acc_vreg->acc_sel_addr[mem_type], len);
+	if (!mem_acc_vreg->acc_sel_base[mem_type]) {
+		pr_err("Unable to map 'acc_sel_addr' %pa for mem_type=%d\n",
+			&mem_acc_vreg->acc_sel_addr[mem_type], mem_type);
+		return -EINVAL;
+	}
+
+	switch (mem_type) {
+	case MEMORY_L1:
+		mem_select_str = "qcom,acc-sel-l1-bit-pos";
+		break;
+	case MEMORY_L2:
+		mem_select_str = "qcom,acc-sel-l2-bit-pos";
+		break;
+	}
+
+	rc = populate_acc_data(mem_acc_vreg, mem_select_str,
+			&mem_acc_vreg->acc_sel_bit_pos[mem_type],
+			&mem_acc_vreg->num_acc_sel[mem_type]);
+	if (rc)
+		pr_err("Unable to populate '%s' rc=%d\n", mem_select_str, rc);
+
+	return rc;
+}
+
+static int mem_acc_init(struct platform_device *pdev,
+		struct mem_acc_regulator *mem_acc_vreg)
+{
+	struct resource *res;
+	int len, rc, i;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acc-en");
+	if (!res || !res->start) {
+		pr_debug("'acc-en' resource missing or not used.\n");
+	} else {
+		mem_acc_vreg->acc_en_addr = res->start;
+		len = res->end - res->start + 1;
+		pr_debug("'acc_en_addr' = %pa (len=0x%x)\n", &res->start, len);
+
+		mem_acc_vreg->acc_en_base = devm_ioremap(mem_acc_vreg->dev,
+				mem_acc_vreg->acc_en_addr, len);
+		if (!mem_acc_vreg->acc_en_base) {
+			pr_err("Unable to map 'acc_en_addr' %pa\n",
+					&mem_acc_vreg->acc_en_addr);
+			return -EINVAL;
+		}
+
+		rc = populate_acc_data(mem_acc_vreg, "qcom,acc-en-bit-pos",
+				&mem_acc_vreg->acc_en_bit_pos,
+				&mem_acc_vreg->num_acc_en);
+		if (rc) {
+			pr_err("Unable to populate 'qcom,acc-en-bit-pos' rc=%d\n",
+					rc);
+			return rc;
+		}
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acc-sel-l1");
+	if (!res || !res->start) {
+		pr_debug("'acc-sel-l1' resource missing or not used.\n");
+	} else {
+		rc = mem_acc_sel_setup(mem_acc_vreg, res, MEMORY_L1);
+		if (rc) {
+			pr_err("Unable to setup mem-acc for mem_type=%d rc=%d\n",
+					MEMORY_L1, rc);
+			return rc;
+		}
+		mem_acc_vreg->mem_acc_supported[MEMORY_L1] = true;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acc-sel-l2");
+	if (!res || !res->start) {
+		pr_debug("'acc-sel-l2' resource missing or not used.\n");
+	} else {
+		rc = mem_acc_sel_setup(mem_acc_vreg, res, MEMORY_L2);
+		if (rc) {
+			pr_err("Unable to setup mem-acc for mem_type=%d rc=%d\n",
+					MEMORY_L2, rc);
+			return rc;
+		}
+		mem_acc_vreg->mem_acc_supported[MEMORY_L2] = true;
+	}
+
+	rc = populate_acc_data(mem_acc_vreg, "qcom,corner-acc-map",
+			&mem_acc_vreg->corner_acc_map,
+			&mem_acc_vreg->num_corners);
+	if (rc) {
+		pr_err("Unable to find 'qcom,corner-acc-map' rc=%d\n", rc);
+		return rc;
+	}
+
+	pr_debug("num_corners = %d\n", mem_acc_vreg->num_corners);
+
+	/* Check if at least one valid mem-acc config. is specified */
+	for (i = 0; i < MEMORY_MAX; i++) {
+		if (mem_acc_vreg->mem_acc_supported[i])
+			break;
+	}
+	if (i == MEMORY_MAX) {
+		pr_err("No mem-acc configuration specified\n");
+		return -EINVAL;
+	}
+
+	if (mem_acc_vreg->num_acc_en)
+		mem_acc_en_init(mem_acc_vreg);
+
+	rc = mem_acc_sel_init(mem_acc_vreg);
+	if (rc) {
+		pr_err("Unable to intialize mem_acc_sel reg rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int mem_acc_regulator_probe(struct platform_device *pdev)
+{
+	struct mem_acc_regulator *mem_acc_vreg;
+	struct regulator_desc *rdesc;
+	struct regulator_init_data *init_data;
+	int rc;
+
+	if (!pdev->dev.of_node) {
+		pr_err("Device tree node is missing\n");
+		return -EINVAL;
+	}
+
+	init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node);
+	if (!init_data) {
+		pr_err("regulator init data is missing\n");
+		return -EINVAL;
+	} else {
+		init_data->constraints.input_uV
+			= init_data->constraints.max_uV;
+		init_data->constraints.valid_ops_mask
+			|= REGULATOR_CHANGE_VOLTAGE;
+	}
+
+	mem_acc_vreg = devm_kzalloc(&pdev->dev, sizeof(*mem_acc_vreg),
+			GFP_KERNEL);
+	if (!mem_acc_vreg) {
+		pr_err("Can't allocate mem_acc_vreg memory\n");
+		return -ENOMEM;
+	}
+	mem_acc_vreg->dev = &pdev->dev;
+
+	rc = mem_acc_init(pdev, mem_acc_vreg);
+	if (rc) {
+		pr_err("Unable to initialize mem_acc configuration rc=%d\n",
+				rc);
+		return rc;
+	}
+
+	rdesc			= &mem_acc_vreg->rdesc;
+	rdesc->owner		= THIS_MODULE;
+	rdesc->type		= REGULATOR_VOLTAGE;
+	rdesc->ops		= &mem_acc_corner_ops;
+	rdesc->name		= init_data->constraints.name;
+
+	mem_acc_vreg->rdev = regulator_register(rdesc, &pdev->dev,
+				init_data, mem_acc_vreg, pdev->dev.of_node);
+	if (IS_ERR(mem_acc_vreg->rdev)) {
+		rc = PTR_ERR(mem_acc_vreg->rdev);
+		if (rc != -EPROBE_DEFER)
+			pr_err("regulator_register failed: rc=%d\n", rc);
+		return rc;
+	}
+
+	platform_set_drvdata(pdev, mem_acc_vreg);
+
+	return 0;
+}
+
+static int mem_acc_regulator_remove(struct platform_device *pdev)
+{
+	struct mem_acc_regulator *mem_acc_vreg = platform_get_drvdata(pdev);
+
+	regulator_unregister(mem_acc_vreg->rdev);
+
+	return 0;
+}
+
+static struct of_device_id mem_acc_regulator_match_table[] = {
+	{ .compatible = "qcom,mem-acc-regulator", },
+	{}
+};
+
+static struct platform_driver mem_acc_regulator_driver = {
+	.probe		= mem_acc_regulator_probe,
+	.remove		= mem_acc_regulator_remove,
+	.driver		= {
+		.name		= "qcom,mem-acc-regulator",
+		.of_match_table = mem_acc_regulator_match_table,
+		.owner		= THIS_MODULE,
+	},
+};
+
+int __init mem_acc_regulator_init(void)
+{
+	return platform_driver_register(&mem_acc_regulator_driver);
+}
+postcore_initcall(mem_acc_regulator_init);
+
+static void __exit mem_acc_regulator_exit(void)
+{
+	platform_driver_unregister(&mem_acc_regulator_driver);
+}
+module_exit(mem_acc_regulator_exit);
+
+MODULE_DESCRIPTION("MEM-ACC-SEL regulator driver");
+MODULE_LICENSE("GPL v2");