avtimer: kernel driver to expose avtimer to userspace

avtimer clock is owned by Q6. avtimer clock tick is used by
mpqplayer, and vcap for implementation of avsync logic. This
character driver gets the Q6 out of power collapsed state and
exposes ioctl control to read avtimer tick.

Change-Id: Ic60dbe6a6bf2a4aed30ef89b63830575efd30083
Signed-off-by: Srikanth Uyyala <suyyala@codeaurora.org>
This commit is contained in:
Srikanth Uyyala
2012-12-05 16:02:57 +05:30
parent ba3a7d804a
commit 9e6f423f21
5 changed files with 398 additions and 0 deletions
+6
View File
@@ -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
+1
View File
@@ -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
+369
View File
@@ -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 <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 (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");
+1
View File
@@ -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
+21
View File
@@ -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 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