Initial Contribution

msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142

Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/arch/arm/mach-msm/footswitch-pcom.c b/arch/arm/mach-msm/footswitch-pcom.c
new file mode 100644
index 0000000..97edf8c
--- /dev/null
+++ b/arch/arm/mach-msm/footswitch-pcom.c
@@ -0,0 +1,338 @@
+/* Copyright (c) 2011, Code Aurora Forum. 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/platform_device.h>
+#include <linux/err.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/clk.h>
+#include "footswitch.h"
+#include "proc_comm.h"
+
+/* PCOM power rail IDs */
+#define PCOM_FS_GRP		8
+#define PCOM_FS_GRP_2D		58
+#define PCOM_FS_MDP		14
+#define PCOM_FS_MFC		68
+#define PCOM_FS_ROTATOR		90
+#define PCOM_FS_VFE		41
+#define PCOM_FS_VPE		76
+
+#define PCOM_RAIL_MODE_AUTO	0
+#define PCOM_RAIL_MODE_MANUAL	1
+
+/**
+ * struct footswitch - Per-footswitch data and state
+ * @rdev: Regulator framework device
+ * @desc: Regulator descriptor
+ * @init_data: Regulator platform data
+ * @pcom_id: Proc-comm ID of the footswitch
+ * @is_enabled: Flag set when footswitch is enabled
+ * @is_manual: Flag set when footswitch is in manual proc-comm mode
+ * @core_clk_name: String name of core clock for footswitch power domain
+ * @set_clk_name: String name of clock used to set the core clocks's rate
+ * @ahb_clk_name: String name of AHB clock for footswitch power domain
+ * @core_clk: Clock with name core_clk_name
+ * @set_clk: Clock with name set_clk_name
+ * @abh_clk: Clock with name ahb_clk_name
+ * @set_clk_init_rate: Rate to use for set_clk to if one has not yet been set
+ * @is_rate_set: Flag set if the core clock's rate has been set
+ */
+struct footswitch {
+	struct regulator_dev			*rdev;
+	struct regulator_desc			desc;
+	struct regulator_init_data		init_data;
+	unsigned				pcom_id;
+	bool					is_enabled;
+	bool					is_manual;
+	const char				*core_clk_name;
+	const char				*set_clk_name;
+	const char				*ahb_clk_name;
+	struct clk				*core_clk;
+	struct clk				*set_clk;
+	struct clk				*ahb_clk;
+	const int				set_clk_init_rate;
+	bool					is_rate_set;
+};
+
+static inline int set_rail_mode(int pcom_id, int mode)
+{
+	int  rc;
+
+	rc = msm_proc_comm(PCOM_CLKCTL_RPC_RAIL_CONTROL, &pcom_id, &mode);
+	if (!rc && pcom_id)
+		rc = -EINVAL;
+
+	return rc;
+}
+
+static inline int set_rail_state(int pcom_id, int state)
+{
+	int  rc;
+
+	rc = msm_proc_comm(state, &pcom_id, NULL);
+	if (!rc && pcom_id)
+		rc = -EINVAL;
+
+	return rc;
+}
+
+static int enable_clocks(struct footswitch *fs)
+{
+	fs->is_rate_set = !!(clk_get_rate(fs->set_clk));
+	if (!fs->is_rate_set)
+		clk_set_rate(fs->set_clk, fs->set_clk_init_rate);
+	clk_enable(fs->core_clk);
+
+	if (fs->ahb_clk)
+		clk_enable(fs->ahb_clk);
+
+	return 0;
+}
+
+static void disable_clocks(struct footswitch *fs)
+{
+	if (fs->ahb_clk)
+		clk_disable(fs->ahb_clk);
+	clk_disable(fs->core_clk);
+}
+
+static int footswitch_is_enabled(struct regulator_dev *rdev)
+{
+	struct footswitch *fs = rdev_get_drvdata(rdev);
+
+	return fs->is_enabled;
+}
+
+static int footswitch_enable(struct regulator_dev *rdev)
+{
+	struct footswitch *fs = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = enable_clocks(fs);
+	if (rc)
+		return rc;
+
+	rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_ENABLE);
+	if (!rc)
+		fs->is_enabled = true;
+
+	disable_clocks(fs);
+
+	return rc;
+}
+
+static int footswitch_disable(struct regulator_dev *rdev)
+{
+	struct footswitch *fs = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = enable_clocks(fs);
+	if (rc)
+		return rc;
+
+	rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_DISABLE);
+	if (!rc)
+		fs->is_enabled = false;
+
+	disable_clocks(fs);
+
+	return rc;
+}
+
+static struct regulator_ops footswitch_ops = {
+	.is_enabled = footswitch_is_enabled,
+	.enable = footswitch_enable,
+	.disable = footswitch_disable,
+};
+
+#define FOOTSWITCH(_id, _pcom_id, _name, _core_clk, _set_clk, _rate, _ahb_clk) \
+	[_id] = { \
+		.desc = { \
+			.id = _id, \
+			.name = _name, \
+			.ops = &footswitch_ops, \
+			.type = REGULATOR_VOLTAGE, \
+			.owner = THIS_MODULE, \
+		}, \
+		.pcom_id = _pcom_id, \
+		.core_clk_name = _core_clk, \
+		.set_clk_name = _set_clk, \
+		.set_clk_init_rate = _rate, \
+		.ahb_clk_name = _ahb_clk, \
+	}
+static struct footswitch footswitches[] = {
+	FOOTSWITCH(FS_GFX3D,  PCOM_FS_GRP,     "fs_gfx3d",
+		   "grp_clk", "grp_src_clk", 24576000, "grp_pclk"),
+	FOOTSWITCH(FS_GFX2D0, PCOM_FS_GRP_2D,  "fs_gfx2d0",
+		   "grp_2d_clk",       NULL, 24576000, "grp_2d_pclk"),
+	FOOTSWITCH(FS_MDP,    PCOM_FS_MDP,     "fs_mdp",
+		   "mdp_clk",          NULL, 24576000, "mdp_pclk"),
+	FOOTSWITCH(FS_MFC,    PCOM_FS_MFC,     "fs_mfc",
+		   "mfc_clk",          NULL, 24576000, "mfc_pclk"),
+	FOOTSWITCH(FS_ROT,    PCOM_FS_ROTATOR, "fs_rot",
+		   "rotator_clk",      NULL,        0, "rotator_pclk"),
+	FOOTSWITCH(FS_VFE,    PCOM_FS_VFE,     "fs_vfe",
+		   "vfe_clk",          NULL, 24576000, "vfe_pclk"),
+	FOOTSWITCH(FS_VPE,    PCOM_FS_VPE,     "fs_vpe",
+		   "vpe_clk",          NULL, 24576000, NULL),
+};
+
+static int get_clocks(struct footswitch *fs)
+{
+	int rc;
+
+	/*
+	 * Some SoCs may not have a separate rate-settable clock.
+	 * If one can't be found, try to use the core clock for
+	 * rate-setting instead.
+	 */
+	if (fs->set_clk_name) {
+		fs->set_clk = clk_get(NULL, fs->set_clk_name);
+		if (IS_ERR(fs->set_clk)) {
+			fs->set_clk = clk_get(NULL, fs->core_clk_name);
+			fs->set_clk_name = fs->core_clk_name;
+		}
+	} else {
+		fs->set_clk = clk_get(NULL, fs->core_clk_name);
+		fs->set_clk_name = fs->core_clk_name;
+	}
+	if (IS_ERR(fs->set_clk)) {
+		pr_err("clk_get(%s) failed\n", fs->set_clk_name);
+		rc = PTR_ERR(fs->set_clk);
+		goto err_set_clk;
+	}
+
+	fs->core_clk = clk_get(NULL, fs->core_clk_name);
+	if (IS_ERR(fs->core_clk)) {
+		pr_err("clk_get(%s) failed\n", fs->core_clk_name);
+		rc = PTR_ERR(fs->core_clk);
+		goto err_core_clk;
+	}
+
+	if (fs->ahb_clk_name) {
+		fs->ahb_clk = clk_get(NULL, fs->ahb_clk_name);
+		if (IS_ERR(fs->ahb_clk)) {
+			pr_err("clk_get(%s) failed\n", fs->ahb_clk_name);
+			rc = PTR_ERR(fs->ahb_clk);
+			goto err_ahb_clk;
+		}
+	}
+
+	return 0;
+
+err_ahb_clk:
+	clk_put(fs->core_clk);
+err_core_clk:
+	clk_put(fs->set_clk);
+err_set_clk:
+	return rc;
+}
+
+static void put_clocks(struct footswitch *fs)
+{
+	clk_put(fs->set_clk);
+	clk_put(fs->core_clk);
+	clk_put(fs->ahb_clk);
+}
+
+static int footswitch_probe(struct platform_device *pdev)
+{
+	struct footswitch *fs;
+	struct regulator_init_data *init_data;
+	int rc;
+
+	if (pdev == NULL)
+		return -EINVAL;
+
+	if (pdev->id >= MAX_FS)
+		return -ENODEV;
+
+	fs = &footswitches[pdev->id];
+	if (!fs->is_manual) {
+		pr_err("%s is not in manual mode\n", fs->desc.name);
+		return -EINVAL;
+	}
+	init_data = pdev->dev.platform_data;
+
+	rc = get_clocks(fs);
+	if (rc)
+		return rc;
+
+	fs->rdev = regulator_register(&fs->desc, &pdev->dev, init_data, fs);
+	if (IS_ERR(fs->rdev)) {
+		pr_err("regulator_register(%s) failed\n", fs->desc.name);
+		rc = PTR_ERR(fs->rdev);
+		goto err_register;
+	}
+
+	return 0;
+
+err_register:
+	put_clocks(fs);
+
+	return rc;
+}
+
+static int __devexit footswitch_remove(struct platform_device *pdev)
+{
+	struct footswitch *fs = &footswitches[pdev->id];
+
+	regulator_unregister(fs->rdev);
+	set_rail_mode(fs->pcom_id, PCOM_RAIL_MODE_AUTO);
+	put_clocks(fs);
+
+	return 0;
+}
+
+static struct platform_driver footswitch_driver = {
+	.probe		= footswitch_probe,
+	.remove		= __devexit_p(footswitch_remove),
+	.driver		= {
+		.name		= "footswitch-pcom",
+		.owner		= THIS_MODULE,
+	},
+};
+
+static int __init footswitch_init(void)
+{
+	struct footswitch *fs;
+	int ret;
+
+	/*
+	 * Enable all footswitches in manual mode (ie. not controlled along
+	 * with pcom clocks).
+	 */
+	for (fs = footswitches; fs < footswitches + ARRAY_SIZE(footswitches);
+	     fs++) {
+		set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_ENABLE);
+		ret = set_rail_mode(fs->pcom_id, PCOM_RAIL_MODE_MANUAL);
+		if (!ret)
+			fs->is_manual = 1;
+	}
+
+	return platform_driver_register(&footswitch_driver);
+}
+subsys_initcall(footswitch_init);
+
+static void __exit footswitch_exit(void)
+{
+	platform_driver_unregister(&footswitch_driver);
+}
+module_exit(footswitch_exit);
+
+MODULE_LICENSE("GPLv2");
+MODULE_DESCRIPTION("proc_comm rail footswitch");
+MODULE_ALIAS("platform:footswitch-pcom");