diff --git a/drivers/platform/msm/Kconfig b/drivers/platform/msm/Kconfig index 872a9b53c18..93949862839 100644 --- a/drivers/platform/msm/Kconfig +++ b/drivers/platform/msm/Kconfig @@ -85,5 +85,11 @@ config IPA for the IPA core. Kernel and user-space processes can call the IPA driver to configure IPA core. +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 0a755d3a115..919c07f77da 100644 --- a/drivers/platform/msm/Makefile +++ b/drivers/platform/msm/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_SPS) += sps/ 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 diff --git a/drivers/platform/msm/avtimer.c b/drivers/platform/msm/avtimer.c new file mode 100644 index 00000000000..f513ceb7cdd --- /dev/null +++ b/drivers/platform/msm/avtimer.c @@ -0,0 +1,369 @@ + +/* Copyright (c) 2012, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 (core_handle) + rc = avcs_core_disable_power_collapse(1); + + pavtimer->avtimer_open_cnt++; + 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 29546b77b32..46ae59fdf1c 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -447,3 +447,4 @@ header-y += msm_audio_amrwb.h header-y += coresight-stm.h header-y += ci-bridge-spi.h header-y += msm_audio_amrwbplus.h +header-y += avtimer.h diff --git a/include/linux/avtimer.h b/include/linux/avtimer.h new file mode 100644 index 00000000000..e68da6a43d0 --- /dev/null +++ b/include/linux/avtimer.h @@ -0,0 +1,21 @@ +#ifndef AVTIMER_H +#define AVTIMER_H + +#include + +#define MAJOR_NUM 100 + +#define IOCTL_GET_AVTIMER_TICK _IOR(MAJOR_NUM, 0, char *) +/* + * This IOCTL is used 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 flase */ + +#endif