/* 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");