blob: 9af634cd16c4d7009b77712ca9b87a2fb6097911 [file] [log] [blame]
/* Copyright (c) 2010, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/android_pmem.h>
#include <linux/clk.h>
#include <linux/firmware.h>
#include <linux/delay.h>
#include <mach/clk.h>
#include "vcd_ddl_firmware.h"
#include "vcd_api.h"
#include "video_core_init_internal.h"
#include "video_core_init.h"
#if DEBUG
#define DBG(x...) printk(KERN_DEBUG x)
#else
#define DBG(x...)
#endif
#define VID_C_NAME "msm_vidc_reg"
#define ERR(x...) printk(KERN_ERR x)
static struct vid_c_dev *vidc_dev;
static dev_t vidc_dev_num;
static struct class *vidc_class;
static const struct file_operations vid_c_fops = {
.owner = THIS_MODULE,
.open = NULL,
.release = NULL,
.ioctl = NULL,
};
struct workqueue_struct *vid_c_wq;
struct workqueue_struct *vidc_timer_wq;
static irqreturn_t vid_c_isr(int irq, void *dev);
static spinlock_t vidc_spin_lock;
static void vid_c_timer_fn(unsigned long data)
{
unsigned long flag;
struct vid_c_timer *hw_timer = NULL;
DBG("%s: Timer expired\n", __func__);
spin_lock_irqsave(&vidc_spin_lock, flag);
hw_timer = (struct vid_c_timer *)data;
list_add_tail(&hw_timer->list, &vidc_dev->vidc_timer_queue);
spin_unlock_irqrestore(&vidc_spin_lock, flag);
DBG("Queue the work for timer\n");
queue_work(vidc_timer_wq, &vidc_dev->vidc_timer_worker);
}
static void vid_c_timer_handler(struct work_struct *work)
{
unsigned long flag = 0;
u32 islist_empty = 0;
struct vid_c_timer *hw_timer = NULL;
DBG("%s: Timer expired\n", __func__);
do {
spin_lock_irqsave(&vidc_spin_lock, flag);
islist_empty = list_empty(&vidc_dev->vidc_timer_queue);
if (!islist_empty) {
hw_timer = list_first_entry(&vidc_dev->vidc_timer_queue,
struct vid_c_timer, list);
list_del(&hw_timer->list);
}
spin_unlock_irqrestore(&vidc_spin_lock, flag);
if (!islist_empty && hw_timer && hw_timer->cb_func)
hw_timer->cb_func(hw_timer->userdata);
} while (!islist_empty);
}
static void vid_c_work_handler(struct work_struct *work)
{
DBG("vid_c_work_handler()");
vcd_read_and_clear_interrupt();
vcd_response_handler();
enable_irq(vidc_dev->irq);
DBG("vid_c_work_handler() done");
}
static DECLARE_WORK(vid_c_work, vid_c_work_handler);
static int __init vid_c_720p_probe(struct platform_device *pdev)
{
int rc;
struct resource *resource;
DBG("Enter %s\n", __func__);
if (pdev->id) {
ERR("Invalid platform device ID = %d\n", pdev->id);
return -EINVAL;
}
vidc_dev->irq = platform_get_irq(pdev, 0);
if (unlikely(vidc_dev->irq < 0)) {
ERR("%s: Invalid irq = %d\n", __func__, vidc_dev->irq);
return -ENXIO;
}
resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (unlikely(!resource)) {
ERR("%s: Invalid resource\n", __func__);
return -ENXIO;
}
vidc_dev->phys_base = resource->start;
vidc_dev->virt_base = ioremap(resource->start,
resource->end - resource->start + 1);
if (!vidc_dev->virt_base) {
ERR("%s: ioremap failed\n", __func__);
return -ENOMEM;
}
vidc_dev->device = &pdev->dev;
mutex_init(&vidc_dev->lock);
vid_c_wq = create_singlethread_workqueue("vid_c_worker_queue");
if (!vid_c_wq) {
ERR("%s: create workqueue failed\n", __func__);
return -ENOMEM;
}
rc = vcd_fw_init(vidc_dev->device);
if (rc)
ERR("%s: failed to prepare firmware %d\n", __func__, rc);
return rc;
}
static int __devexit vid_c_720p_remove(struct platform_device *pdev)
{
if (pdev->id) {
ERR("Invalid plaform device ID = %d\n", pdev->id);
return -EINVAL;
}
vcd_fw_exit();
return 0;
}
static struct platform_driver msm_vid_c_720p_platform_driver = {
.probe = vid_c_720p_probe,
.remove = vid_c_720p_remove,
.driver = {
.name = "msm_vidc_720p",
},
};
static void __exit vid_c_exit(void)
{
platform_driver_unregister(&msm_vid_c_720p_platform_driver);
}
static irqreturn_t vid_c_isr(int irq, void *dev)
{
DBG("vid_c_isr() %d\n", irq);
disable_irq_nosync(irq);
queue_work(vid_c_wq, &vid_c_work);
return IRQ_HANDLED;
}
static int __init vid_c_init(void)
{
int rc = 0;
struct device *class_devp;
vidc_dev = kzalloc(sizeof(struct vid_c_dev), GFP_KERNEL);
if (!vidc_dev) {
ERR("%s Unable to allocate memory for vid_c_dev\n",
__func__);
return -ENOMEM;
}
rc = alloc_chrdev_region(&vidc_dev_num, 0, 1, VID_C_NAME);
if (rc < 0) {
ERR("%s: alloc_chrdev_region failed %d\n", __func__, rc);
goto error_vid_c_alloc_chrdev_region;
}
vidc_class = class_create(THIS_MODULE, VID_C_NAME);
if (IS_ERR(vidc_class)) {
rc = PTR_ERR(vidc_class);
ERR("%s: couldn't create vid_c_class %d\n", __func__, rc);
goto error_vid_c_class_create;
}
class_devp = device_create(vidc_class, NULL, vidc_dev_num, NULL,
VID_C_NAME);
if (IS_ERR(class_devp)) {
rc = PTR_ERR(class_devp);
ERR("%s: class device_create failed %d\n", __func__, rc);
goto error_vid_c_class_device_create;
}
cdev_init(&vidc_dev->cdev, &vid_c_fops);
vidc_dev->cdev.owner = THIS_MODULE;
rc = cdev_add(&(vidc_dev->cdev), vidc_dev_num, 1);
if (rc < 0) {
ERR("%s: cdev_add failed %d\n", __func__, rc);
goto error_vid_c_cdev_add;
}
rc = platform_driver_register(&msm_vid_c_720p_platform_driver);
if (rc) {
ERR("%s failed to load\n", __func__);
goto error_vid_c_platfom_register;
}
rc = request_irq(vidc_dev->irq, vid_c_isr, IRQF_TRIGGER_HIGH,
"vid_c", vidc_dev->device);
if (unlikely(rc)) {
ERR("%s:request_irq failed\n", __func__);
goto error_vid_c_platfom_register;
}
vidc_timer_wq = create_singlethread_workqueue("vidc_timer_wq");
if (!vidc_timer_wq) {
ERR("%s: create workqueue failed\n", __func__);
rc = -ENOMEM;
goto error_vid_c_platfom_register;
}
DBG("Disabling IRQ in %s\n", __func__);
disable_irq_nosync(vidc_dev->irq);
INIT_WORK(&vidc_dev->vidc_timer_worker, vid_c_timer_handler);
spin_lock_init(&vidc_spin_lock);
INIT_LIST_HEAD(&vidc_dev->vidc_timer_queue);
vidc_dev->clock_enabled = 0;
vidc_dev->ref_count = 0;
vidc_dev->firmware_refcount = 0;
vidc_dev->get_firmware = 0;
return 0;
error_vid_c_platfom_register:
cdev_del(&(vidc_dev->cdev));
error_vid_c_cdev_add:
device_destroy(vidc_class, vidc_dev_num);
error_vid_c_class_device_create:
class_destroy(vidc_class);
error_vid_c_class_create:
unregister_chrdev_region(vidc_dev_num, 1);
error_vid_c_alloc_chrdev_region:
kfree(vidc_dev);
return rc;
}
void __iomem *vid_c_get_ioaddr()
{
return vidc_dev->virt_base;
}
EXPORT_SYMBOL(vid_c_get_ioaddr);
#ifdef USE_RES_TRACKER
u32 vid_c_enable_pwr_rail()
{
int rc;
mutex_lock(&vidc_dev->lock);
if (vidc_dev->rail_enabled) {
mutex_unlock(&vidc_dev->lock);
return true;
}
//TODO: internal_pwr_rail_mode(MFC_CLK_ID, MANUAL)
vidc_dev->pclk = clk_get(vidc_dev->device, "mfc_pclk");
if (IS_ERR(vidc_dev->pclk)) {
ERR("%s: mfc_pclk get failed\n", __func__);
goto err;
}
vidc_dev->hclk = clk_get(vidc_dev->device, "mfc_clk");
if (IS_ERR(vidc_dev->hclk)) {
ERR("%s: mfc_clk get failed\n", __func__);
goto err;
}
vidc_dev->hclk_div2 = clk_get(vidc_dev->device, "mfc_div2_clk");
if (IS_ERR(vidc_dev->hclk_div2)) {
ERR("%s: mfc_div2_clk get failed\n", __func__);
goto err;
}
//TODO: internal_pwr_rail_ctl(MFC_CLK_ID, 1)
//TODO msleep must die
msleep(20);
rc = clk_reset(vidc_dev->pclk, CLK_RESET_DEASSERT);
if (rc) {
ERR("clk_reset failed %d\n", rc);
goto err;
}
//TODO msleep must die
msleep(20);
vidc_dev->rail_enabled = 1;
mutex_unlock(&vidc_dev->lock);
return true;
err:
if (!IS_ERR(vidc_dev->pclk))
clk_put(vidc_dev->pclk);
if (!IS_ERR(vidc_dev->hclk))
clk_put(vidc_dev->hclk);
if (!IS_ERR(vidc_dev->hclk_div2))
clk_put(vidc_dev->hclk_div2);
mutex_unlock(&vidc_dev->lock);
return false;
}
EXPORT_SYMBOL(vid_c_enable_pwr_rail);
u32 vid_c_disable_pwr_rail()
{
int rc = -1;
mutex_lock(&vidc_dev->lock);
if (vidc_dev->clock_enabled) {
mutex_unlock(&vidc_dev->lock);
DBG("Calling CLK disable in power down\n");
vid_c_disable_clk();
mutex_lock(&vidc_dev->lock);
}
if (!vidc_dev->rail_enabled) {
mutex_unlock(&vidc_dev->lock);
return false;
}
vidc_dev->rail_enabled = 0;
rc = clk_reset(vidc_dev->pclk, CLK_RESET_ASSERT);
if (rc) {
ERR("clk_reset failed %d\n", rc);
mutex_unlock(&vidc_dev->lock);
return false;
}
msleep(20);
//TODO: internal_pwr_rail_ctl(MFC_CLK_ID, 0)
clk_put(vidc_dev->hclk_div2);
clk_put(vidc_dev->hclk);
clk_put(vidc_dev->pclk);
mutex_unlock(&vidc_dev->lock);
return true;
}
EXPORT_SYMBOL(vid_c_disable_pwr_rail);
u32 vid_c_enable_clk()
{
mutex_lock(&vidc_dev->lock);
if (!vidc_dev->rail_enabled) {
goto err;
}
if (vidc_dev->clock_enabled) {
mutex_unlock(&vidc_dev->lock);
return true;
}
DBG("Enabling IRQ in %s\n", __func__);
enable_irq(vidc_dev->irq);
DBG("%s: Enabling the clocks ...\n", __func__);
if (clk_enable(vidc_dev->pclk)) {
ERR("vidc pclk enable failed\n");
goto err;
}
if (clk_enable(vidc_dev->hclk)) {
ERR("vidc hclk enable failed\n");
goto err;
}
if (clk_enable(vidc_dev->hclk_div2)) {
ERR("vidc hclk_div2 enable failed\n");
goto err;
}
vidc_dev->clock_enabled = 1;
mutex_unlock(&vidc_dev->lock);
return true;
err:
mutex_unlock(&vidc_dev->lock);
return false;
}
EXPORT_SYMBOL(vid_c_enable_clk);
u32 vid_c_sel_clk_rate(unsigned long hclk_rate)
{
mutex_lock(&vidc_dev->lock);
if (clk_set_rate(vidc_dev->hclk, hclk_rate)) {
ERR("vidc hclk set rate failed\n");
mutex_unlock(&vidc_dev->lock);
return false;
}
vidc_dev->hclk_rate = hclk_rate;
mutex_unlock(&vidc_dev->lock);
return true;
}
EXPORT_SYMBOL(vid_c_sel_clk_rate);
u32 vid_c_get_clk_rate(unsigned long *phclk_rate)
{
if (!phclk_rate) {
ERR("vid_c_get_clk_rate(): phclk_rate is NULL\n");
return false;
}
mutex_lock(&vidc_dev->lock);
*phclk_rate = clk_get_rate(vidc_dev->hclk);
if (!(*phclk_rate)) {
ERR("vidc hclk get rate failed\n");
mutex_unlock(&vidc_dev->lock);
return false;
}
mutex_unlock(&vidc_dev->lock);
return true;
}
EXPORT_SYMBOL(vid_c_get_clk_rate);
u32 vid_c_disable_clk(void)
{
mutex_lock(&vidc_dev->lock);
if (!vidc_dev->clock_enabled) {
mutex_unlock(&vidc_dev->lock);
return false;
}
DBG("Disabling IRQ in %s\n", __func__);
disable_irq_nosync(vidc_dev->irq);
DBG("%s: Disabling the clocks ...\n", __func__);
vidc_dev->clock_enabled = 0;
clk_disable(vidc_dev->hclk);
clk_disable(vidc_dev->hclk_div2);
clk_disable(vidc_dev->pclk);
mutex_unlock(&vidc_dev->lock);
return true;
}
EXPORT_SYMBOL(vid_c_disable_clk);
//TODO: consider deleting USE_RES_TRACKER
#else
u32 vid_c_enable_clk(unsigned long hclk_rate)
{
int rc = -1;
mutex_lock(&vidc_dev->lock);
vidc_dev->ref_count++;
if (!vidc_dev->clock_enabled) {
DBG("Enabling IRQ in %s()\n", __func__);
enable_irq(vidc_dev->irq);
rc = internal_pwr_rail_mode
(PWR_RAIL_MFC_CLK, PWR_RAIL_CTL_MANUAL);
if (rc) {
ERR("%s(): internal_pwr_rail_mode failed %d\n",
__func__, rc);
return false;
}
DBG("%s(): internal_pwr_rail_mode Success %d\n",
__func__, rc);
vidc_dev->pclk =
clk_get(vidc_dev->device, "mfc_pclk");
if (IS_ERR(vidc_dev->pclk)) {
ERR("%s(): mfc_pclk get failed\n", __func__);
mutex_unlock(&vidc_dev->lock);
return false;
}
vidc_dev->hclk =
clk_get(vidc_dev->device, "mfc_clk");
if (IS_ERR(vidc_dev->hclk)) {
ERR("%s(): mfc_clk get failed\n", __func__);
clk_put(vidc_dev->pclk);
mutex_unlock(&vidc_dev->lock);
return false;
}
vidc_dev->hclk_div2 =
clk_get(vidc_dev->device, "mfc_div2_clk");
if (IS_ERR(vidc_dev->pclk)) {
ERR("%s(): mfc_div2_clk get failed\n", __func__);
clk_put(vidc_dev->pclk);
clk_put(vidc_dev->hclk);
mutex_unlock(&vidc_dev->lock);
return false;
}
vidc_dev->hclk_rate = hclk_rate;
if (clk_set_rate(vidc_dev->hclk,
vidc_dev->hclk_rate)) {
ERR("vid_c hclk set rate failed\n");
clk_put(vidc_dev->pclk);
clk_put(vidc_dev->hclk);
clk_put(vidc_dev->hclk_div2);
mutex_unlock(&vidc_dev->lock);
return false;
}
if (clk_enable(vidc_dev->pclk)) {
ERR("vid_c pclk Enable failed\n");
clk_put(vidc_dev->hclk);
clk_put(vidc_dev->hclk_div2);
mutex_unlock(&vidc_dev->lock);
return false;
}
if (clk_enable(vidc_dev->hclk)) {
ERR("vid_c hclk Enable failed\n");
clk_put(vidc_dev->pclk);
clk_put(vidc_dev->hclk_div2);
mutex_unlock(&vidc_dev->lock);
return false;
}
if (clk_enable(vidc_dev->hclk_div2)) {
ERR("vid_c hclk Enable failed\n");
clk_put(vidc_dev->hclk);
clk_put(vidc_dev->pclk);
mutex_unlock(&vidc_dev->lock);
return false;
}
msleep(20);
rc = internal_pwr_rail_ctl(PWR_RAIL_MFC_CLK, 1);
if (rc) {
ERR("\n internal_pwr_rail_ctl failed %d\n", rc);
return false;
}
DBG("%s(): internal_pwr_rail_ctl Success %d\n",
__func__, rc);
msleep(20);
rc = clk_reset(vidc_dev->pclk, CLK_RESET_DEASSERT);
if (rc) {
ERR("\n clk_reset failed %d\n", rc);
return false;
}
msleep(20);
}
vidc_dev->clock_enabled = 1;
mutex_unlock(&vidc_dev->lock);
return true;
}
EXPORT_SYMBOL(vid_c_enable_clk);
u32 vid_c_disable_clk(void)
{
int rc = -1;
mutex_lock(&vidc_dev->lock);
if (!vidc_dev->ref_count ||
!vidc_dev->clock_enabled) {
return false;
}
if (vidc_dev->ref_count > 0)
vidc_dev->ref_count--;
if (!vidc_dev->ref_count) {
DBG("Disabling IRQ in %s()\n", __func__);
disable_irq_nosync(vidc_dev->irq);
rc = clk_reset(vidc_dev->pclk, CLK_RESET_ASSERT);
if (rc) {
ERR("\n clk_reset failed %d\n", rc);
return false;
}
msleep(20);
rc = internal_pwr_rail_ctl(PWR_RAIL_MFC_CLK, 0);
if (rc) {
ERR("\n internal_pwr_rail_ctl failed %d\n", rc);
return false;
}
vidc_dev->clock_enabled = 0;
clk_disable(vidc_dev->hclk);
clk_disable(vidc_dev->hclk_div2);
clk_disable(vidc_dev->pclk);
clk_put(vidc_dev->hclk_div2);
clk_put(vidc_dev->hclk);
clk_put(vidc_dev->pclk);
}
mutex_unlock(&vidc_dev->lock);
return true;
}
EXPORT_SYMBOL(vid_c_disable_clk);
#endif
u32 vid_c_lookup_addr_table(struct video_client_ctx *client_ctx,
enum buffer_dir buffer_type, u32 search_with_user_vaddr,
void __user **user_addr, void **kern_addr, phys_addr_t *phys_addr,
int *pmem_fd, struct file **file, s32 *buffer_index)
{
u32 num_of_buffers;
u32 i;
struct buf_addr_table *buf_addr_table;
u32 found = false;
if (!client_ctx)
return false;
if (buffer_type == BUFFER_TYPE_INPUT) {
buf_addr_table = client_ctx->input_buf_addr_table;
num_of_buffers = client_ctx->num_of_input_buffers;
DBG("%s: buffer_type = INPUT\n", __func__);
} else {
buf_addr_table = client_ctx->output_buf_addr_table;
num_of_buffers = client_ctx->num_of_output_buffers;
DBG("%s: buffer_type = OUTPUT\n", __func__);
}
for (i = 0; i < num_of_buffers; ++i) {
if (search_with_user_vaddr) {
if (*user_addr == buf_addr_table[i].user_addr) {
*kern_addr = buf_addr_table[i].kern_addr;
found = true;
DBG("%s: client_ctx=%p user_addr=%p is found\n",
__func__, client_ctx, *user_addr);
break;
}
} else {
if (*kern_addr == buf_addr_table[i].kern_addr) {
*user_addr = buf_addr_table[i].user_addr;
found = true;
DBG("%s: client_ctx=%p kern_addr=%p is found",
__func__, client_ctx, *kern_addr);
break;
}
}
}
if (!found) {
if (search_with_user_vaddr)
DBG("%s: client_ctx=%p user_addr=%p not found\n",
__func__, client_ctx, *user_addr);
else
DBG("%s: client_ctx=%p kern_addr=%p not found\n",
__func__, client_ctx, *kern_addr);
return false;
}
*phys_addr = buf_addr_table[i].phys_addr;
*pmem_fd = buf_addr_table[i].pmem_fd;
*file = buf_addr_table[i].file;
*buffer_index = i;
if (search_with_user_vaddr)
DBG("kern_addr=%p phys_addr=%X pmem_fd=%d "
"struct *file=%p buffer_index=%d\n", *kern_addr,
*phys_addr, *pmem_fd, *file, *buffer_index);
else
DBG("user_addr=%p phys_addr=%X pmem_fd=%d, "
"struct *file=%p buffer_index=%d\n", *user_addr,
*phys_addr, *pmem_fd, *file, *buffer_index);
return true;
}
EXPORT_SYMBOL(vid_c_lookup_addr_table);
u32 vid_c_timer_create(void (*pf_timer_handler)(void *), void *user_data,
void **pp_timer_handle)
{
struct vid_c_timer *hw_timer = NULL;
if (!pf_timer_handler || !pp_timer_handle) {
DBG("%s: timer creation failed\n", __func__);
return false;
}
hw_timer = kzalloc(sizeof(struct vid_c_timer), GFP_KERNEL);
if (!hw_timer) {
DBG("%s: timer creation failed in allocation\n", __func__);
return false;
}
init_timer(&hw_timer->hw_timeout);
hw_timer->hw_timeout.data = (unsigned long)hw_timer;
hw_timer->hw_timeout.function = vid_c_timer_fn;
hw_timer->cb_func = pf_timer_handler;
hw_timer->userdata = user_data;
*pp_timer_handle = hw_timer;
return true;
}
EXPORT_SYMBOL(vid_c_timer_create);
void vid_c_timer_release(void *timer_handle)
{
kfree(timer_handle);
}
EXPORT_SYMBOL(vid_c_timer_release);
void vid_c_timer_start(void *timer_handle, u32 time_out)
{
struct vid_c_timer *hw_timer = timer_handle;
DBG("%s: start timer\n ", __func__);
if (hw_timer) {
hw_timer->hw_timeout.expires = jiffies + 1 * HZ;
add_timer(&hw_timer->hw_timeout);
}
}
EXPORT_SYMBOL(vid_c_timer_start);
void vid_c_timer_stop(void *timer_handle)
{
struct vid_c_timer *hw_timer = timer_handle;
DBG("%s: stop timer\n ", __func__);
if (hw_timer)
del_timer(&hw_timer->hw_timeout);
}
EXPORT_SYMBOL(vid_c_timer_stop);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Video decoder/encoder driver Init Module");
MODULE_VERSION("1.0");
module_init(vid_c_init);
module_exit(vid_c_exit);