avtimer: kernel driver to expose avtimer to userspace

This driver expose the API to get LPASS subsystem
out of power collapsed state and exposes ioctl control to
read avtimer tick.

Change-Id: I4b50fbfca5741449a8e5f0d0514f5ca7bccec04d
Signed-off-by: Srikanth Uyyala <suyyala@codeaurora.org>
diff --git a/drivers/platform/msm/Kconfig b/drivers/platform/msm/Kconfig
index 34e1d40..204ac55 100644
--- a/drivers/platform/msm/Kconfig
+++ b/drivers/platform/msm/Kconfig
@@ -76,4 +76,10 @@
 	  PNP PMIC. It configures the frequency of clkdiv outputs on the
 	  PMIC. These clocks are typically wired through alternate functions
 	  on gpio pins.
+config MSM_AVTIMER
+	tristate "Avtimer Driver"
+	depends on ARCH_MSM8960
+	help
+	  This driver gets the Q6 out of power collapsed state and
+          exposes ioctl control to read avtimer tick.
 endmenu
diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile
index 35efd91..2a87419 100644
--- a/drivers/platform/msm/Makefile
+++ b/drivers/platform/msm/Makefile
@@ -7,3 +7,4 @@
 obj-$(CONFIG_QPNP_PWM) += qpnp-pwm.o
 obj-$(CONFIG_QPNP_POWER_ON) += qpnp-power-on.o
 obj-$(CONFIG_QPNP_CLKDIV) += qpnp-clkdiv.o
+obj-$(CONFIG_MSM_AVTIMER) += avtimer.o
\ No newline at end of file
diff --git a/drivers/platform/msm/avtimer.c b/drivers/platform/msm/avtimer.c
new file mode 100644
index 0000000..8b81c84
--- /dev/null
+++ b/drivers/platform/msm/avtimer.c
@@ -0,0 +1,372 @@
+/* Copyright (c) 2013, 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.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/uaccess.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/avtimer.h>
+#include <mach/qdsp6v2/apr.h>
+
+#define DEVICE_NAME "avtimer"
+
+
+#define ADSP_CMD_SET_POWER_COLLAPSE_STATE 0x0001115C
+
+static int major;	/* Major number assigned to our device driver */
+struct avtimer_t {
+	struct cdev myc;
+	struct class *avtimer_class;
+	struct mutex avtimer_lock;
+	int avtimer_open_cnt;
+	struct dev_avtimer_data *avtimer_pdata;
+};
+static struct avtimer_t avtimer;
+
+static struct apr_svc *core_handle;
+
+struct adsp_power_collapse {
+	struct apr_hdr hdr;
+	uint32_t power_collapse;
+};
+
+static int32_t avcs_core_callback(struct apr_client_data *data, void *priv)
+{
+	uint32_t *payload;
+
+	pr_debug("core msg: payload len = %u, apr resp opcode = 0x%X\n",
+		data->payload_size, data->opcode);
+
+	switch (data->opcode) {
+
+	case APR_BASIC_RSP_RESULT:{
+
+		if (data->payload_size == 0) {
+			pr_err("%s: APR_BASIC_RSP_RESULT No Payload ",
+					__func__);
+			return 0;
+		}
+
+		payload = data->payload;
+
+		switch (payload[0]) {
+
+		case ADSP_CMD_SET_POWER_COLLAPSE_STATE:
+			pr_debug("CMD_SET_POWER_COLLAPSE_STATE status[0x%x]\n",
+					payload[1]);
+			break;
+		default:
+			pr_err("Invalid cmd rsp[0x%x][0x%x]\n",
+					payload[0], payload[1]);
+			break;
+		}
+		break;
+	}
+	case RESET_EVENTS:{
+		pr_debug("Reset event received in Core service");
+		apr_reset(core_handle);
+		core_handle = NULL;
+		break;
+	}
+
+	default:
+		pr_err("Message id from adsp core svc: %d\n", data->opcode);
+		break;
+	}
+
+	return 0;
+}
+
+int avcs_core_open(void)
+{
+	if (core_handle == NULL)
+		core_handle = apr_register("ADSP", "CORE",
+					avcs_core_callback, 0xFFFFFFFF, NULL);
+
+	pr_debug("Open_q %p\n", core_handle);
+	if (core_handle == NULL) {
+		pr_err("%s: Unable to register CORE\n", __func__);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+int avcs_core_disable_power_collapse(int disable)
+{
+	struct adsp_power_collapse pc;
+	int rc = 0;
+
+	if (core_handle) {
+		pc.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_EVENT,
+			APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
+		pc.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE,
+					sizeof(uint32_t));
+		pc.hdr.src_port = 0;
+		pc.hdr.dest_port = 0;
+		pc.hdr.token = 0;
+		pc.hdr.opcode = ADSP_CMD_SET_POWER_COLLAPSE_STATE;
+		/*
+		* When power_collapse set to 1 -- If the aDSP is in the power
+		* collapsed state when this command is received, it is awakened
+		* from this state. The aDSP does not power collapse again until
+		* the client revokes this	command
+		* When power_collapse set to 0 -- This indicates to the aDSP
+		* that the remote client does not need it to be out of power
+		* collapse any longer. This may not always put the aDSP into
+		* power collapse; the aDSP must honor an internal client's
+		* power requirements as well.
+		*/
+		pc.power_collapse = disable;
+		rc = apr_send_pkt(core_handle, (uint32_t *)&pc);
+		if (rc < 0) {
+			pr_debug("disable power collapse = %d failed\n",
+				disable);
+			return rc;
+		}
+		pr_debug("disable power collapse = %d\n", disable);
+	}
+	return 0;
+}
+
+static int avtimer_open(struct inode *inode, struct file *file)
+{
+	int rc = 0;
+	struct avtimer_t *pavtimer = &avtimer;
+
+	pr_debug("avtimer_open\n");
+	mutex_lock(&pavtimer->avtimer_lock);
+
+	if (pavtimer->avtimer_open_cnt != 0) {
+		pavtimer->avtimer_open_cnt++;
+		pr_debug("%s: opened avtimer open count=%d\n",
+			__func__, pavtimer->avtimer_open_cnt);
+		mutex_unlock(&pavtimer->avtimer_lock);
+		return 0;
+	}
+	try_module_get(THIS_MODULE);
+
+	rc = avcs_core_open();
+	if (rc)
+		goto leave;
+	if (core_handle)
+		rc = avcs_core_disable_power_collapse(1);
+	if (!rc)
+		pavtimer->avtimer_open_cnt++;
+
+leave:
+	pr_debug("%s: opened avtimer open count=%d\n",
+		__func__, pavtimer->avtimer_open_cnt);
+	mutex_unlock(&pavtimer->avtimer_lock);
+	pr_debug("avtimer_open leave rc=%d\n", rc);
+
+	return rc;
+}
+
+static int avtimer_release(struct inode *inode, struct file *file)
+{
+	int rc = 0;
+	struct avtimer_t *pavtimer = &avtimer;
+
+	mutex_lock(&pavtimer->avtimer_lock);
+	pavtimer->avtimer_open_cnt--;
+
+	if (core_handle && pavtimer->avtimer_open_cnt == 0)
+		rc = avcs_core_disable_power_collapse(0);
+
+	pr_debug("device_release(%p,%p) open count=%d\n",
+		inode, file, pavtimer->avtimer_open_cnt);
+
+	module_put(THIS_MODULE);
+
+	mutex_unlock(&pavtimer->avtimer_lock);
+
+	return rc;
+}
+
+/*
+ * ioctl call provides GET_AVTIMER
+ */
+static long avtimer_ioctl(struct file *file, unsigned int ioctl_num,
+				unsigned long ioctl_param)
+{
+	struct avtimer_t *pavtimer = &avtimer;
+	pr_debug("avtimer_ioctl: ioctlnum=%d,param=%lx\n",
+				ioctl_num, ioctl_param);
+
+	switch (ioctl_num) {
+	case IOCTL_GET_AVTIMER_TICK:
+	{
+		void __iomem *p_avtimer_msw = NULL, *p_avtimer_lsw = NULL;
+		uint32_t avtimer_msw_1st = 0, avtimer_lsw = 0;
+		uint32_t avtimer_msw_2nd = 0;
+		uint64_t avtimer_tick;
+
+		if (pavtimer->avtimer_pdata) {
+			p_avtimer_lsw = ioremap(
+			pavtimer->avtimer_pdata->avtimer_lsw_phy_addr, 4);
+			p_avtimer_msw = ioremap(
+			pavtimer->avtimer_pdata->avtimer_msw_phy_addr, 4);
+		}
+		if (!p_avtimer_lsw || !p_avtimer_msw) {
+			pr_err("ioremap failed\n");
+			return -EIO;
+		}
+		do {
+			avtimer_msw_1st = ioread32(p_avtimer_msw);
+			avtimer_lsw = ioread32(p_avtimer_lsw);
+			avtimer_msw_2nd = ioread32(p_avtimer_msw);
+		} while (avtimer_msw_1st != avtimer_msw_2nd);
+
+		avtimer_tick =
+		((uint64_t) avtimer_msw_1st << 32) | avtimer_lsw;
+
+		pr_debug("AV Timer tick: msw: %d, lsw: %d\n", avtimer_msw_1st,
+				avtimer_lsw);
+		if (copy_to_user((void *) ioctl_param, &avtimer_tick,
+				sizeof(avtimer_tick))) {
+					pr_err("copy_to_user failed\n");
+					iounmap(p_avtimer_lsw);
+					iounmap(p_avtimer_msw);
+					return -EFAULT;
+			}
+		iounmap(p_avtimer_lsw);
+		iounmap(p_avtimer_msw);
+		}
+		break;
+
+	default:
+		pr_err("invalid cmd\n");
+		break;
+	}
+
+	return 0;
+}
+
+static const struct file_operations avtimer_fops = {
+	.unlocked_ioctl = avtimer_ioctl,
+	.open = avtimer_open,
+	.release = avtimer_release
+};
+
+static int dev_avtimer_probe(struct platform_device *pdev)
+{
+	int result;
+	dev_t dev = MKDEV(major, 0);
+	struct device *device_handle;
+	struct avtimer_t *pavtimer = &avtimer;
+
+	/* get the device number */
+	if (major)
+		result = register_chrdev_region(dev, 1, DEVICE_NAME);
+	else {
+		result = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
+		major = MAJOR(dev);
+	}
+
+	if (result < 0) {
+		pr_err("Registering avtimer device failed\n");
+		return result;
+	}
+
+	pavtimer->avtimer_class = class_create(THIS_MODULE, "avtimer");
+	if (IS_ERR(pavtimer->avtimer_class)) {
+		result = PTR_ERR(pavtimer->avtimer_class);
+		pr_err("Error creating avtimer class: %d\n", result);
+		goto unregister_chrdev_region;
+	}
+	pavtimer->avtimer_pdata = pdev->dev.platform_data;
+
+	cdev_init(&pavtimer->myc, &avtimer_fops);
+	result = cdev_add(&pavtimer->myc, dev, 1);
+
+	if (result < 0) {
+		pr_err("Registering file operations failed\n");
+		goto class_destroy;
+	}
+
+	device_handle = device_create(pavtimer->avtimer_class,
+			NULL, pavtimer->myc.dev, NULL, "avtimer");
+	if (IS_ERR(device_handle)) {
+		result = PTR_ERR(device_handle);
+		pr_err("device_create failed: %d\n", result);
+		goto class_destroy;
+	}
+
+	mutex_init(&pavtimer->avtimer_lock);
+	core_handle = NULL;
+	pavtimer->avtimer_open_cnt = 0;
+
+	pr_debug("Device create done for avtimer major=%d\n", major);
+
+	return 0;
+
+class_destroy:
+	class_destroy(pavtimer->avtimer_class);
+unregister_chrdev_region:
+	unregister_chrdev_region(MKDEV(major, 0), 1);
+	return result;
+
+}
+
+static int __devexit dev_avtimer_remove(struct platform_device *pdev)
+{
+	struct avtimer_t *pavtimer = &avtimer;
+
+	pr_debug("dev_avtimer_remove\n");
+
+	device_destroy(pavtimer->avtimer_class, pavtimer->myc.dev);
+	cdev_del(&pavtimer->myc);
+	class_destroy(pavtimer->avtimer_class);
+	unregister_chrdev_region(MKDEV(major, 0), 1);
+
+	return 0;
+}
+
+static struct platform_driver dev_avtimer_driver = {
+	.probe = dev_avtimer_probe,
+	.remove = __exit_p(dev_avtimer_remove),
+	.driver = {.name = "dev_avtimer"}
+};
+
+static int  __init avtimer_init(void)
+{
+	s32 rc;
+	rc = platform_driver_register(&dev_avtimer_driver);
+	if (IS_ERR_VALUE(rc)) {
+		pr_err("platform_driver_register failed.\n");
+		goto error_platform_driver;
+	}
+	pr_debug("dev_avtimer_init : done\n");
+
+	return 0;
+error_platform_driver:
+
+	pr_err("encounterd error\n");
+	return -ENODEV;
+}
+
+static void __exit avtimer_exit(void)
+{
+	pr_debug("avtimer_exit\n");
+	platform_driver_unregister(&dev_avtimer_driver);
+}
+
+module_init(avtimer_init);
+module_exit(avtimer_exit);
+
+MODULE_DESCRIPTION("avtimer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index 3e681aa..39855d3 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -447,3 +447,4 @@
 header-y += msm_audio_amrwb.h
 header-y += msm_audio_amrwbplus.h
 header-y += coresight-stm.h
+header-y += avtimer.h
diff --git a/include/linux/avtimer.h b/include/linux/avtimer.h
new file mode 100644
index 0000000..c6dabba
--- /dev/null
+++ b/include/linux/avtimer.h
@@ -0,0 +1,21 @@
+#ifndef AVTIMER_H
+#define AVTIMER_H
+
+#include <linux/ioctl.h>
+
+#define MAJOR_NUM 100
+
+#define IOCTL_GET_AVTIMER_TICK _IOR(MAJOR_NUM, 0, char *)
+/*
+ * This IOCTL is used to read the avtimer tick value.
+ * Avtimer is a 64 bit timer tick, hence the expected
+ * argument is of type uint64_t
+ */
+struct dev_avtimer_data {
+	uint32_t avtimer_msw_phy_addr;
+	uint32_t avtimer_lsw_phy_addr;
+};
+int avcs_core_open(void);
+int avcs_core_disable_power_collapse(int disable);/* true or false */
+
+#endif