msm: xo-fsm9xxx: xo driver for fsm9xxx
This driver provides access to the regulators from userspace
in FSM9XXX. It provides an ioctl interface to do so.
Acked-by: Kaushik Sikdar <ksikdar@qualcomm.com>
Signed-off-by: Rohit Vaswani <rvaswani@codeaurora.org>
diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index e265f87..9aa3bfa 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -163,7 +163,7 @@
obj-$(CONFIG_ARCH_FSM9XXX) += devices-fsm9xxx.o
obj-$(CONFIG_ARCH_FSM9XXX) += clock-fsm9xxx.o clock-local.o acpuclock-fsm9xxx.o
obj-$(CONFIG_ARCH_FSM9XXX) += dfe-fsm9xxx.o rfic-fsm9xxx.o
-obj-$(CONFIG_ARCH_FSM9XXX) += restart-fsm9xxx.o
+obj-$(CONFIG_ARCH_FSM9XXX) += restart-fsm9xxx.o xo-fsm9xxx.o
obj-$(CONFIG_MSM_WATCHDOG) += msm_watchdog.o
obj-$(CONFIG_MACH_MSM8X60_RUMI3) += board-msm8x60.o
diff --git a/arch/arm/mach-msm/xo-fsm9xxx.c b/arch/arm/mach-msm/xo-fsm9xxx.c
new file mode 100644
index 0000000..a00fb2f
--- /dev/null
+++ b/arch/arm/mach-msm/xo-fsm9xxx.c
@@ -0,0 +1,285 @@
+/* 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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/pm8058-xo.h>
+#include <linux/platform_device.h>
+
+#define FSM_XO_IOC_MAGIC 0x93
+#define FSM_XO_IOC_CLKBUF _IO(FSM_XO_IOC_MAGIC, 1)
+
+#define FSM_XO_DEVICE_READY 0x01
+#define FSM_XO_DEVICE_OFF 0x00
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+/* enum for TCXO clock output buffer definition */
+enum clk_buffer_type {
+ XO_BUFFER_A0 = 0,
+ XO_BUFFER_A1 = 1,
+ XO_BUFFER_LAST
+};
+
+/*
+ * This user request structure is used to exchange the pmic device data
+ * requested to user space applications. The pointer to this structure is
+ * passed to the the ioctl function.
+*/
+struct fsm_xo_req {
+ enum clk_buffer_type clkBuffer;
+ u8 clkBufEnable;
+};
+
+struct fsm_xo_priv_t {
+ struct mutex lock;
+ struct regulator *a0;
+ struct regulator *a1;
+ u8 a0_enabled;
+ u8 a1_enabled;
+};
+
+static struct fsm_xo_priv_t *fsm_xo_priv;
+
+static int fsm_xo_open(struct inode *inode, struct file *filp)
+{
+ if (fsm_xo_priv == NULL)
+ return -ENODEV;
+
+ filp->private_data = fsm_xo_priv;
+
+ return 0;
+}
+
+static int fsm_xo_release(struct inode *inode, struct file *filp)
+{
+ filp->private_data = NULL;
+
+ return 0;
+}
+
+static inline int fsm_xo_enable_a0(void)
+{
+ int err = 0;
+
+ if (!fsm_xo_priv->a0_enabled) {
+ err = regulator_enable(fsm_xo_priv->a0);
+ if (err != 0)
+ pr_err("Error = %d enabling xo buffer a0\n", err);
+ else
+ fsm_xo_priv->a0_enabled = 1;
+ }
+ return err;
+}
+
+static inline int fsm_xo_disable_a0(void)
+{
+ int err = 0;
+
+ if (fsm_xo_priv->a0_enabled) {
+ err = regulator_disable(fsm_xo_priv->a0);
+ if (err != 0)
+ pr_err("Error = %d disabling xo buffer a0\n", err);
+ else
+ fsm_xo_priv->a0_enabled = 0;
+ }
+ return err;
+}
+
+static inline int fsm_xo_enable_a1(void)
+{
+ int err = 0;
+
+ if (!fsm_xo_priv->a1_enabled) {
+ err = regulator_enable(fsm_xo_priv->a1);
+ if (err != 0)
+ pr_err("Error = %d enabling xo buffer a1\n", err);
+ else
+ fsm_xo_priv->a1_enabled = 1;
+ }
+ return err;
+}
+
+static inline int fsm_xo_disable_a1(void)
+{
+ int err = 0;
+
+ if (fsm_xo_priv->a1_enabled) {
+ err = regulator_disable(fsm_xo_priv->a1);
+ if (err != 0)
+ pr_err("Error = %d disabling xo buffer a1\n", err);
+ else
+ fsm_xo_priv->a1_enabled = 0;
+ }
+ return err;
+}
+static long
+fsm_xo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ struct fsm_xo_req req;
+
+ /* Verify user arguments. */
+ if (_IOC_TYPE(cmd) != FSM_XO_IOC_MAGIC)
+ return -ENOTTY;
+
+ /* Lock for access */
+ if (mutex_lock_interruptible(&fsm_xo_priv->lock))
+ return -ERESTARTSYS;
+
+ switch (cmd) {
+ case FSM_XO_IOC_CLKBUF:
+ if (arg == 0) {
+ pr_err("user space arg not supplied\n");
+ err = -EFAULT;
+ break;
+ }
+
+ if (copy_from_user(&req, (void __user *)arg,
+ sizeof(req))) {
+ pr_err("Error copying from user space\n");
+ err = -EFAULT;
+ break;
+ }
+
+ if (req.clkBuffer == XO_BUFFER_A0) {
+ if (req.clkBufEnable)
+ err = fsm_xo_enable_a0();
+ else
+ err = fsm_xo_disable_a0();
+ } else if (req.clkBuffer == XO_BUFFER_A1) {
+ if (req.clkBufEnable)
+ err = fsm_xo_enable_a1();
+ else
+ err = fsm_xo_disable_a1();
+ } else {
+ pr_err("Invalid ioctl argument.\n");
+ err = -ENOTTY;
+ }
+ break;
+ default:
+ pr_err("Invalid ioctl command.\n");
+ err = -ENOTTY;
+ break;
+ }
+
+ mutex_unlock(&fsm_xo_priv->lock);
+ return err;
+}
+
+static const struct file_operations fsm_xo_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = fsm_xo_ioctl,
+ .open = fsm_xo_open,
+ .release = fsm_xo_release
+};
+
+static struct miscdevice fsm_xo_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "fsm_xo",
+ .fops = &fsm_xo_fops
+};
+
+static int fsm_xo_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ /* Initialize */
+ fsm_xo_priv = kzalloc(sizeof(struct fsm_xo_priv_t), GFP_KERNEL);
+
+ if (fsm_xo_priv == NULL) {
+ pr_alert("Not enough memory to initialize device\n");
+ return -ENOMEM;
+ }
+
+ fsm_xo_priv->a0 = regulator_get(&pdev->dev, "a0_clk_buffer");
+ if (IS_ERR(fsm_xo_priv->a0)) {
+ pr_err("Error getting a0_clk_buffer\n");
+ ret = PTR_ERR(fsm_xo_priv->a0);
+ fsm_xo_priv->a0 = NULL;
+ goto err;
+ }
+ fsm_xo_priv->a1 = regulator_get(&pdev->dev, "a1_clk_buffer");
+ if (IS_ERR(fsm_xo_priv->a1)) {
+ pr_err("Error getting a1_clk_buffer\n");
+ ret = PTR_ERR(fsm_xo_priv->a1);
+ fsm_xo_priv->a1 = NULL;
+ goto err;
+ }
+
+ fsm_xo_priv->a0_enabled = 0;
+ fsm_xo_priv->a1_enabled = 0;
+
+ mutex_init(&fsm_xo_priv->lock);
+
+ ret = misc_register(&fsm_xo_dev);
+ if (ret < 0)
+ goto err;
+
+ return 0;
+
+err:
+ if (fsm_xo_priv->a0)
+ regulator_put(fsm_xo_priv->a0);
+ if (fsm_xo_priv->a1)
+ regulator_put(fsm_xo_priv->a1);
+
+ kfree(fsm_xo_priv);
+ fsm_xo_priv = NULL;
+
+ return ret;
+}
+
+static int __devexit fsm_xo_remove(struct platform_device *pdev)
+{
+ if (fsm_xo_priv && fsm_xo_priv->a0)
+ regulator_put(fsm_xo_priv->a0);
+ if (fsm_xo_priv && fsm_xo_priv->a1)
+ regulator_put(fsm_xo_priv->a1);
+
+ kfree(fsm_xo_priv);
+ fsm_xo_priv = NULL;
+
+ misc_deregister(&fsm_xo_dev);
+ return 0;
+}
+
+static struct platform_driver fsm_xo_driver = {
+ .probe = fsm_xo_probe,
+ .remove = fsm_xo_remove,
+ .driver = {
+ .name = "fsm_xo_driver",
+ }
+};
+
+static int __init fsm_xo_init(void)
+{
+ return platform_driver_register(&fsm_xo_driver);
+}
+
+static void __exit fsm_xo_exit(void)
+{
+ platform_driver_unregister(&fsm_xo_driver);
+}
+
+module_init(fsm_xo_init);
+module_exit(fsm_xo_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Provide userspace access to XO buffers in PMIC8058.");
+MODULE_VERSION("1.00");