diff mbox

[RFC,1/4] power: asv: Add common ASV support for samsung SoCs

Message ID 1378898048-25205-2-git-send-email-yadi.brar@samsung.com (mailing list archive)
State New, archived
Headers show

Commit Message

Yadwinder Singh Brar Sept. 11, 2013, 11:14 a.m. UTC
This patch introduces a common ASV(Adaptive Supply Voltage) basic framework
for samsung SoCs. It provides common APIs (to be called by users to get ASV
values or init opp_table) and an interface for SoC specific drivers to
register ASV members(instances).

Signed-off-by: Yadwinder Singh Brar <yadi.brar@samsung.com>
---

Hopefully asv_get_volt() can go out in future, once all users start using OPP
library.

---
 drivers/power/Kconfig                    |    1 +
 drivers/power/Makefile                   |    1 +
 drivers/power/asv/Kconfig                |   11 ++
 drivers/power/asv/Makefile               |    1 +
 drivers/power/asv/samsung-asv.c          |  175 ++++++++++++++++++++++++++++++
 include/linux/power/samsung-asv-driver.h |   61 +++++++++++
 include/linux/power/samsung-asv.h        |   37 +++++++
 7 files changed, 287 insertions(+), 0 deletions(-)
 create mode 100644 drivers/power/asv/Kconfig
 create mode 100644 drivers/power/asv/Makefile
 create mode 100644 drivers/power/asv/samsung-asv.c
 create mode 100644 include/linux/power/samsung-asv-driver.h
 create mode 100644 include/linux/power/samsung-asv.h
diff mbox

Patch

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 7b8979c..2e6b087 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -367,3 +367,4 @@  source "drivers/power/reset/Kconfig"
 endif # POWER_SUPPLY
 
 source "drivers/power/avs/Kconfig"
+source "drivers/power/asv/Kconfig"
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 653bf6c..da93c46 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -51,6 +51,7 @@  obj-$(CONFIG_CHARGER_MAX8997)	+= max8997_charger.o
 obj-$(CONFIG_CHARGER_MAX8998)	+= max8998_charger.o
 obj-$(CONFIG_CHARGER_BQ2415X)	+= bq2415x_charger.o
 obj-$(CONFIG_POWER_AVS)		+= avs/
+obj-$(CONFIG_POWER_ASV)		+= asv/
 obj-$(CONFIG_CHARGER_SMB347)	+= smb347-charger.o
 obj-$(CONFIG_CHARGER_TPS65090)	+= tps65090-charger.o
 obj-$(CONFIG_POWER_RESET)	+= reset/
diff --git a/drivers/power/asv/Kconfig b/drivers/power/asv/Kconfig
new file mode 100644
index 0000000..7cd84bd
--- /dev/null
+++ b/drivers/power/asv/Kconfig
@@ -0,0 +1,11 @@ 
+menuconfig POWER_ASV
+	bool "Adaptive Supply Voltage support"
+	help
+	  ASV is a technique used on samsung SoCs, which provides the
+	  recommended supply voltage for some specific parts(like arm, mif etc)
+	  of SoCs which supports dvfs. For a given operating frequency, the
+	  voltage is recommended based on SoCs ASV group.
+	  ASV group info is provided in the chip id info which depends on chip
+	  manufacturing process.
+
+	  Say Y here to enable Adaptive Supply voltage support.
diff --git a/drivers/power/asv/Makefile b/drivers/power/asv/Makefile
new file mode 100644
index 0000000..62921da
--- /dev/null
+++ b/drivers/power/asv/Makefile
@@ -0,0 +1 @@ 
+obj-$(CONFIG_POWER_ASV)			+= asv.o
diff --git a/drivers/power/asv/asv.c b/drivers/power/asv/asv.c
new file mode 100644
index 0000000..61f4a83
--- /dev/null
+++ b/drivers/power/asv/asv.c
@@ -0,0 +1,175 @@ 
+/*
+ * ASV(Adaptive Supply Voltage) common core
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/io.h>
+#include <linux/opp.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/power/asv-driver.h>
+
+static LIST_HEAD(asv_list);
+static DEFINE_MUTEX(asv_mutex);
+
+struct asv_member {
+	struct list_head		node;
+	struct asv_info *asv_info;
+};
+
+static void add_asv_member(struct asv_member *asv_mem)
+{
+	mutex_lock(&asv_mutex);
+	list_add_tail(&asv_mem->node, &asv_list);
+	mutex_unlock(&asv_mutex);
+}
+
+static struct asv_member *asv_get_mem(enum asv_type_id asv_type)
+{
+	struct asv_member *asv_mem;
+	struct asv_info *asv_info;
+
+	list_for_each_entry(asv_mem, &asv_list, node) {
+		asv_info = asv_mem->asv_info;
+		if (asv_type == asv_info->type)
+			return asv_mem;
+	}
+
+	return NULL;
+}
+
+unsigned int asv_get_volt(enum asv_type_id target_type,
+						unsigned int target_freq)
+{
+	struct asv_member *asv_mem = asv_get_mem(target_type);
+	struct asv_freq_table *dvfs_table;
+	struct asv_info *asv_info;
+	unsigned int i;
+
+	if (!asv_mem)
+		return 0;
+
+	asv_info = asv_mem->asv_info;
+	dvfs_table = asv_info->dvfs_table;
+
+	for (i = 0; i < asv_info->nr_dvfs_level; i++) {
+		if (dvfs_table[i].freq == target_freq)
+			return dvfs_table[i].volt;
+	}
+
+	return 0;
+}
+
+int asv_init_opp_table(struct device *dev, enum asv_type_id target_type)
+{
+	struct asv_member *asv_mem = asv_get_mem(target_type);
+	struct asv_info *asv_info;
+	struct asv_freq_table *dvfs_table;
+	unsigned int i;
+
+	if (!asv_mem)
+		return -EINVAL;
+
+	asv_info = asv_mem->asv_info;
+	dvfs_table = asv_info->dvfs_table;
+
+	for (i = 0; i < asv_info->nr_dvfs_level; i++) {
+		if (opp_add(dev, dvfs_table[i].freq * 1000,
+			dvfs_table[i].volt)) {
+			dev_warn(dev, "%s: Failed to add OPP %d\n",
+				 __func__, dvfs_table[i].freq);
+			continue;
+		}
+	}
+
+	return 0;
+}
+
+static struct asv_member *asv_init_member(struct asv_info *asv_info)
+{
+	struct asv_member *asv_mem;
+	int ret = 0;
+
+	if (!asv_info) {
+		pr_err("%s: No ASV info provided\n", __func__);
+		return NULL;
+	}
+
+	asv_mem = kzalloc(sizeof(struct asv_member), GFP_KERNEL);
+	if (!asv_mem) {
+		pr_err("%s: Allocation failed for member: %s\n", __func__,
+			asv_info->name);
+		return NULL;
+	}
+
+	asv_mem->asv_info = kmemdup(asv_info, sizeof(*asv_info), GFP_KERNEL);
+	if (!asv_mem->asv_info) {
+		pr_err("%s: Copying asv_info failed for member: %s\n",
+			__func__, asv_info->name);
+		return NULL;
+	}
+	asv_info = asv_mem->asv_info;
+
+	if (asv_info->ops->get_asv_group) {
+		ret = asv_info->ops->get_asv_group(asv_info);
+		if (ret) {
+			pr_err("%s: get_asv_group failed for %s : %d\n",
+				__func__, asv_info->name, ret);
+			goto err;
+		}
+	}
+
+	if (asv_info->ops->init_asv)
+		ret = asv_info->ops->init_asv(asv_info);
+		if (ret) {
+			pr_err("%s: asv_init failed for %s : %d\n", __func__,
+				asv_info->name, ret);
+			goto err;
+		}
+
+	/* In case of parsing table from DT, we may need to add flag to identify
+	DT supporting members and call init_asv_table from asv_init_opp_table(
+	after getting dev_node from dev,if required), instead of calling here.
+	*/
+
+	if (asv_info->ops->init_asv_table) {
+		ret = asv_info->ops->init_asv_table(asv_info);
+		if (ret) {
+			pr_err("%s: init_asv_table failed for %s : %d\n",
+				__func__, asv_info->name, ret);
+			goto err;
+		}
+	}
+
+	if (!asv_info->nr_dvfs_level || !asv_info->dvfs_table) {
+		pr_err("%s: No dvfs_table for %s\n", __func__, asv_info->name);
+		goto err;
+	}
+
+	pr_info("%s: Registered asv member: %s with group: %d", __func__,
+		asv_info->name, asv_info->asv_grp);
+
+	return asv_mem;
+err:
+	kfree(asv_mem->asv_info);
+	kfree(asv_mem);
+	return NULL;
+}
+
+void register_asv_member(struct asv_info *list, unsigned int nr_member)
+{
+	struct asv_member *asv_mem;
+	int cnt;
+	for (cnt = 0; cnt < nr_member; cnt++) {
+		asv_mem = asv_init_member(&list[cnt]);
+
+		if (asv_mem)
+			add_asv_member(asv_mem);
+	}
+}
diff --git a/include/linux/power/asv-driver.h b/include/linux/power/asv-driver.h
new file mode 100644
index 0000000..faab388
--- /dev/null
+++ b/include/linux/power/asv-driver.h
@@ -0,0 +1,61 @@ 
+/*
+ * Adaptive Supply Voltage Driver Header File
+ *
+ * copyright (c) 2013 samsung electronics co., ltd.
+ *		http://www.samsung.com/
+ *
+ * this program is free software; you can redistribute it and/or modify
+ * it under the terms of the gnu general public license version 2 as
+ * published by the free software foundation.
+*/
+
+#ifndef __ASV_D_H
+#define __ASV_D_H __FILE__
+
+#include <linux/power/asv.h>
+
+struct asv_freq_table {
+	unsigned int	freq;	/* KHz */
+	unsigned int	volt;	/* uV */
+};
+
+/* struct asv_info - information of ASV member for intialisation
+ *
+ * Each member to be registered should be described using this struct
+ * intialised with all required information for that member.
+ *
+ * @asv_type: Type to identify particular member.
+ * @name: Name to used for member.
+ * @asv_ops: Callbacks which can be used for SoC specific operations.
+ * @nr_dvfs_level: Number of dvfs levels supported by member.
+ * @dvfs_table: Table containing supported ASV freqs and corresponding volts.
+ * @asv_grp: ASV group of member.
+ */
+struct asv_info {
+	const char		*name;
+	enum asv_type_id	type;
+	struct asv_ops		*ops;
+	unsigned int		nr_dvfs_level;
+	struct asv_freq_table	*dvfs_table;
+	unsigned int		asv_grp;
+	unsigned int		flags;
+};
+
+/* struct asv_ops - SoC specific operation for ASV members
+ * @get_asv_group - Calcuates and intializes asv_grp of asv_info.
+ * @init_asv - SoC specific intilisation(if anything required)based on asv_grp.
+ * @init_asv_table - Intializes linear array(dvfs_table) for corresponding
+ *		asv_grp.
+ *
+ * All ops should return 0 on sucess.
+ */
+struct asv_ops {
+	int (*init_asv)(struct asv_info *);
+	int (*get_asv_group)(struct asv_info *);
+	int (*init_asv_table)(struct asv_info *);
+};
+
+/* function for registering ASV members */
+void register_asv_member(struct asv_info *list, unsigned int nr_member);
+
+#endif /* __ASV_D_H */
diff --git a/include/linux/power/asv.h b/include/linux/power/asv.h
new file mode 100644
index 0000000..9b187f7
--- /dev/null
+++ b/include/linux/power/asv.h
@@ -0,0 +1,37 @@ 
+/*
+ * Adaptive Supply Voltage Header File
+ *
+ * copyright (c) 2013 samsung electronics co., ltd.
+ *		http://www.samsung.com/
+ *
+ * this program is free software; you can redistribute it and/or modify
+ * it under the terms of the gnu general public license version 2 as
+ * published by the free software foundation.
+*/
+
+#ifndef __ASV_H
+#define __ASV_H __FILE__
+
+enum asv_type_id {
+	ASV_ARM,
+	ASV_INT,
+	ASV_MIF,
+	ASV_G3D,
+};
+
+#ifdef CONFIG_POWER_ASV
+/* asv_get_volt - get the ASV for target_freq for particular target_type.
+ *	returns 0 if target_freq is not supported
+ */
+extern unsigned int asv_get_volt(enum asv_type_id target_type,
+					unsigned int target_freq);
+extern int asv_init_opp_table(struct device *dev,
+					enum asv_type_id target_type);
+#else
+static inline unsigned int asv_get_volt(enum asv_type_id target_type,
+				unsigned int target_freq) { return 0; }
+static int asv_init_opp_table(struct device *dev, enum asv_type_id target_type)
+	{ return 0; }
+
+#endif /* CONFIG_POWER_EXYNOS_AVS */
+#endif /* __ASV_H */