/* arch/arm/mach-rk29/vpu.c
*
* Copyright (C) 2010 ROCKCHIP, Inc.
+ * author: chenhengming chm@rock-chips.com
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
-#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
#include <asm/uaccess.h>
struct list_head done;
wait_queue_head_t wait;
pid_t pid;
+ atomic_t task_running;
} vpu_session;
/**
typedef struct vpu_service_info {
spinlock_t lock;
- struct workqueue_struct *workqueue;
+ struct timer_list timer; /* timer for power off */
struct list_head waiting; /* link to link_reg in struct vpu_reg */
struct list_head running; /* link to link_reg in struct vpu_reg */
struct list_head done; /* link to link_reg in struct vpu_reg */
struct list_head session; /* link to list_session in struct vpu_session */
- atomic_t task_running;
+ atomic_t total_running;
bool enabled;
vpu_reg *reg_codec;
vpu_reg *reg_pproc;
unsigned long size;
} vpu_request;
+static struct clk *clk_vpu; /* for power on notify */
static struct clk *aclk_vepu;
static struct clk *hclk_vepu;
static struct clk *aclk_ddr_vepu;
static vpu_device dec_dev;
static vpu_device enc_dev;
-static void vpu_service_power_off_work_func(struct work_struct *work);
-static DECLARE_DELAYED_WORK(vpu_service_power_off_work, vpu_service_power_off_work_func);
-#define POWER_OFF_DELAY 3*HZ /* 3s */
+#define POWER_OFF_DELAY 4*HZ /* 4s */
+#define TIMEOUT_DELAY 2*HZ /* 2s */
static void vpu_get_clk(void)
{
+ clk_vpu = clk_get(NULL, "vpu");
aclk_vepu = clk_get(NULL, "aclk_vepu");
hclk_vepu = clk_get(NULL, "hclk_vepu");
aclk_ddr_vepu = clk_get(NULL, "aclk_ddr_vepu");
static void vpu_put_clk(void)
{
+ clk_put(clk_vpu);
clk_put(aclk_vepu);
clk_put(hclk_vepu);
clk_put(aclk_ddr_vepu);
clk_put(hclk_cpu_vcodec);
}
-static void vpu_service_power_on(void)
+static void vpu_reset(void)
{
- if (service.enabled)
- return;
-
- printk("vpu: power on\n");
- clk_enable(aclk_vepu);
- clk_enable(hclk_vepu);
- clk_enable(hclk_cpu_vcodec);
- udelay(10);
- pmu_set_power_domain(PD_VCODEC, true);
- udelay(10);
+ clk_disable(aclk_ddr_vepu);
+ cru_set_soft_reset(SOFT_RST_CPU_VODEC_A2A_AHB, true);
+ cru_set_soft_reset(SOFT_RST_DDR_VCODEC_PORT, true);
+ cru_set_soft_reset(SOFT_RST_VCODEC_AHB_BUS, true);
+ cru_set_soft_reset(SOFT_RST_VCODEC_AXI_BUS, true);
+ mdelay(10);
+ cru_set_soft_reset(SOFT_RST_VCODEC_AXI_BUS, false);
+ cru_set_soft_reset(SOFT_RST_VCODEC_AHB_BUS, false);
+ cru_set_soft_reset(SOFT_RST_DDR_VCODEC_PORT, false);
+ cru_set_soft_reset(SOFT_RST_CPU_VODEC_A2A_AHB, false);
clk_enable(aclk_ddr_vepu);
- service.enabled = true;
+ service.reg_codec = NULL;
+ service.reg_pproc = NULL;
+ service.reg_resev = NULL;
+}
+
+static void reg_deinit(vpu_reg *reg);
+static void vpu_service_session_clear(vpu_session *session)
+{
+ vpu_reg *reg, *n;
+ list_for_each_entry_safe(reg, n, &session->waiting, session_link) {
+ reg_deinit(reg);
+ }
+ list_for_each_entry_safe(reg, n, &session->running, session_link) {
+ reg_deinit(reg);
+ }
+ list_for_each_entry_safe(reg, n, &session->done, session_link) {
+ reg_deinit(reg);
+ }
+}
+
+static void vpu_service_dump(void)
+{
+ int running;
+ vpu_reg *reg, *reg_tmp;
+ vpu_session *session, *session_tmp;
+
+ running = atomic_read(&service.total_running);
+ printk("total_running %d\n", running);
+
+ printk("reg_codec 0x%.8x\n", (unsigned int)service.reg_codec);
+ printk("reg_pproc 0x%.8x\n", (unsigned int)service.reg_pproc);
+ printk("reg_resev 0x%.8x\n", (unsigned int)service.reg_resev);
+
+ list_for_each_entry_safe(session, session_tmp, &service.session, list_session) {
+ printk("session pid %d type %d:\n", session->pid, session->type);
+ running = atomic_read(&session->task_running);
+ printk("task_running %d\n", running);
+ list_for_each_entry_safe(reg, reg_tmp, &session->waiting, session_link) {
+ printk("waiting register set 0x%.8x\n", (unsigned int)reg);
+ }
+ list_for_each_entry_safe(reg, reg_tmp, &session->running, session_link) {
+ printk("running register set 0x%.8x\n", (unsigned int)reg);
+ }
+ list_for_each_entry_safe(reg, reg_tmp, &session->done, session_link) {
+ printk("done register set 0x%.8x\n", (unsigned int)reg);
+ }
+ }
}
static void vpu_service_power_off(void)
{
+ int total_running;
+
if (!service.enabled)
return;
- while(atomic_read(&service.task_running))
- udelay(10);
+ service.enabled = false;
+ total_running = atomic_read(&service.total_running);
+ if (total_running) {
+ pr_alert("power off when %d task running!!\n", total_running);
+ mdelay(50);
+ pr_alert("delay 50 ms for running task\n");
+ vpu_service_dump();
+ }
- printk("vpu: power off\n");
+ printk("vpu: power off...");
pmu_set_power_domain(PD_VCODEC, false);
udelay(10);
clk_disable(hclk_cpu_vcodec);
clk_disable(aclk_ddr_vepu);
clk_disable(hclk_vepu);
clk_disable(aclk_vepu);
-
- service.enabled = false;
+ clk_disable(clk_vpu);
+ printk("done\n");
}
-static void vpu_service_power_off_work_func(struct work_struct *work)
+static void vpu_service_power_off_work_func(unsigned long data)
{
- pr_debug("work\n");
+ printk("delayed ");
vpu_service_power_off();
}
+static void vpu_service_power_maintain(void)
+{
+ if (service.enabled) {
+ mod_timer(&service.timer, jiffies + POWER_OFF_DELAY);
+ } else {
+ pr_err("maintain power when power is off!\n");
+ }
+}
+
+static void vpu_service_power_on(void)
+{
+ clk_enable(clk_vpu); /* notify vpu on without lock. */
+
+ spin_lock_bh(&service.lock);
+ if (!service.enabled) {
+ service.enabled = true;
+ printk("vpu: power on\n");
+
+ clk_enable(clk_vpu);
+ clk_enable(aclk_vepu);
+ clk_enable(hclk_vepu);
+ clk_enable(hclk_cpu_vcodec);
+ udelay(10);
+ pmu_set_power_domain(PD_VCODEC, true);
+ udelay(10);
+ clk_enable(aclk_ddr_vepu);
+ init_timer(&service.timer);
+ service.timer.expires = jiffies + POWER_OFF_DELAY;
+ service.timer.function = vpu_service_power_off_work_func;
+ add_timer(&service.timer);
+ spin_unlock_bh(&service.lock);
+ } else {
+ spin_unlock_bh(&service.lock);
+ vpu_service_power_maintain();
+ }
+
+ clk_disable(clk_vpu);
+}
+
static vpu_reg *reg_init(vpu_session *session, void __user *src, unsigned long size)
{
unsigned long flag;
list_del_init(®->session_link);
list_add_tail(®->session_link, ®->session->running);
-
- atomic_add(1, &service.task_running);
}
static void reg_copy_from_hw(vpu_reg *reg, volatile u32 *src, u32 count)
break;
}
}
- atomic_sub(1, &service.task_running);
+ atomic_sub(1, ®->session->task_running);
+ atomic_sub(1, &service.total_running);
wake_up_interruptible_sync(®->session->wait);
spin_unlock(&service.lock);
}
{
int i;
u32 *src = (u32 *)®->reg[0];
-
+ atomic_add(1, &service.total_running);
+ atomic_add(1, ®->session->task_running);
switch (reg->type) {
case VPU_ENC : {
u32 *dst = (u32 *)enc_dev.hwregs;
} break;
default : {
pr_err("unsupport session type %d", reg->type);
+ atomic_sub(1, &service.total_running);
+ atomic_sub(1, ®->session->task_running);
break;
}
}
if (!list_empty(&service.waiting)) {
vpu_reg *reg = list_entry(service.waiting.next, vpu_reg, status_link);
+ vpu_service_power_maintain();
if (((VPU_DEC_PP == reg->type) && (NULL == service.reg_codec) && (NULL == service.reg_pproc)) ||
- ((VPU_DEC == reg->type) && (NULL == service.reg_codec)) ||
- ((VPU_PP == reg->type) && (NULL == service.reg_pproc)) ||
- ((VPU_ENC == reg->type) && (NULL == service.reg_codec))) {
+ ((VPU_DEC == reg->type) && (NULL == service.reg_codec)) ||
+ ((VPU_PP == reg->type) && (NULL == service.reg_pproc)) ||
+ ((VPU_ENC == reg->type) && (NULL == service.reg_codec))) {
reg_from_wait_to_run(reg);
- if (!cancel_delayed_work(&vpu_service_power_off_work)) {
- if (!in_interrupt()) {
- flush_delayed_work(&vpu_service_power_off_work);
- } else {
- pr_err("try_set_reg in inturrpt but cancel power off failed\n");
- }
- }
- vpu_service_power_on();
reg_copy_to_hw(reg);
}
- spin_unlock_irqrestore(&service.lock, flag);
- } else {
- spin_unlock_irqrestore(&service.lock, flag);
- queue_delayed_work(service.workqueue, &vpu_service_power_off_work, POWER_OFF_DELAY);
}
+ spin_unlock_irqrestore(&service.lock, flag);
}
static int return_reg(vpu_reg *reg, u32 __user *dst)
if (NULL == reg) {
return -EFAULT;
} else {
+ vpu_service_power_on();
try_set_reg();
}
case VPU_IOC_GET_REG : {
vpu_request req;
vpu_reg *reg;
+ unsigned long flag;
if (copy_from_user(&req, (void __user *)arg, sizeof(vpu_request))) {
pr_err("VPU_IOC_GET_REG copy_from_user failed\n");
return -EFAULT;
} else {
- int ret = wait_event_interruptible_timeout(session->wait, !list_empty(&session->done), HZ);
+ int ret = wait_event_interruptible_timeout(session->wait, !list_empty(&session->done), TIMEOUT_DELAY);
if (unlikely(ret < 0)) {
pr_err("pid %d wait task ret %d\n", session->pid, ret);
- return ret;
} else if (0 == ret) {
- pr_err("pid %d wait task done timeout\n", session->pid);
- return -ETIMEDOUT;
+ pr_err("pid %d wait %d task done timeout\n", session->pid, atomic_read(&session->task_running));
+ ret = -ETIMEDOUT;
}
- }
- {
- unsigned long flag;
spin_lock_irqsave(&service.lock, flag);
- reg = list_entry(session->done.next, vpu_reg, session_link);
- return_reg(reg, (u32 __user *)req.req);
+ if (ret < 0) {
+ int task_running = atomic_read(&session->task_running);
+ vpu_service_dump();
+ if (task_running) {
+ atomic_set(&session->task_running, 0);
+ atomic_sub(task_running, &service.total_running);
+ printk("%d task is running but not return, reset hardware...", task_running);
+ vpu_reset();
+ printk("done\n");
+ }
+ vpu_service_session_clear(session);
+ spin_unlock_irqrestore(&service.lock, flag);
+ return ret;
+ }
spin_unlock_irqrestore(&service.lock, flag);
}
+ spin_lock_irqsave(&service.lock, flag);
+ reg = list_entry(session->done.next, vpu_reg, session_link);
+ return_reg(reg, (u32 __user *)req.req);
+ spin_unlock_irqrestore(&service.lock, flag);
break;
}
default : {
init_waitqueue_head(&session->wait);
/* no need to protect */
list_add_tail(&session->list_session, &service.session);
+ atomic_set(&session->task_running, 0);
filp->private_data = (void *)session;
pr_debug("dev opened\n");
static int vpu_service_release(struct inode *inode, struct file *filp)
{
+ int task_running;
unsigned long flag;
vpu_session *session = (vpu_session *)filp->private_data;
if (NULL == session)
return -EINVAL;
+ task_running = atomic_read(&session->task_running);
+ if (task_running) {
+ pr_err("vpu_service session %d still has %d task running when closing\n", session->pid, task_running);
+ msleep(50);
+ }
wake_up_interruptible_sync(&session->wait);
- msleep(50);
+ spin_lock_irqsave(&service.lock, flag);
/* remove this filp from the asynchronusly notified filp's */
//vpu_service_fasync(-1, filp, 0);
list_del(&session->list_session);
- spin_lock_irqsave(&service.lock, flag);
- {
- vpu_reg *reg, *n;
- list_for_each_entry_safe(reg, n, &session->waiting, session_link) {
- reg_deinit(reg);
- }
- list_for_each_entry_safe(reg, n, &session->running, session_link) {
- reg_deinit(reg);
- }
- list_for_each_entry_safe(reg, n, &session->done, session_link) {
- reg_deinit(reg);
- }
- }
- spin_unlock_irqrestore(&service.lock, flag);
+ vpu_service_session_clear(session);
kfree(session);
+ spin_unlock_irqrestore(&service.lock, flag);
pr_debug("dev closed\n");
return 0;
static void vpu_service_shutdown(struct platform_device *pdev)
{
pr_cont("shutdown...");
- cancel_delayed_work(&vpu_service_power_off_work);
+ del_timer(&service.timer);
vpu_service_power_off();
pr_cont("done\n");
}
{
bool enabled;
pr_info("suspend...");
- cancel_delayed_work(&vpu_service_power_off_work);
+ del_timer(&service.timer);
enabled = service.enabled;
vpu_service_power_off();
service.enabled = enabled;
if (service.enabled) {
service.enabled = false;
vpu_service_power_on();
+ try_set_reg();
}
return 0;
}
/* clear pp IRQ */
writel(irq_status_pp & (~DEC_INTERRUPT_BIT), dev->hwregs + PP_INTERRUPT_REGISTER);
pr_debug("PP IRQ received!\n");
-
if (NULL == service.reg_pproc) {
pr_err("pp isr with no task waiting\n");
} else {
/* clear enc IRQ */
writel(irq_status & (~ENC_INTERRUPT_BIT), dev->hwregs + ENC_INTERRUPT_REGISTER);
pr_debug("ENC IRQ received!\n");
-
if (NULL == service.reg_codec) {
pr_err("enc isr with no task waiting\n");
} else {
spin_lock_init(&service.lock);
service.reg_codec = NULL;
service.reg_pproc = NULL;
- atomic_set(&service.task_running, 0);
+ atomic_set(&service.total_running, 0);
service.enabled = false;
- service.workqueue = create_singlethread_workqueue("vpu_service");
- if (!service.workqueue) {
- pr_err("create_singlethread_workqueue failed\n");
- return -ENOMEM;
- }
vpu_get_clk();
vpu_service_power_on();
platform_device_register(&vpu_service_device);
platform_driver_probe(&vpu_service_driver, NULL);
get_hw_info();
+ del_timer(&service.timer);
vpu_service_power_off();
pr_info("init success\n");
err_req_vdpu_irq:
pr_info("init failed\n");
err_reserve_io:
+ del_timer(&service.timer);
vpu_service_power_off();
vpu_service_release_io();
vpu_put_clk();
- destroy_workqueue(service.workqueue);
pr_info("init failed\n");
return ret;
}
static void __exit vpu_service_exit(void)
{
- cancel_delayed_work(&vpu_service_power_off_work);
+ del_timer(&service.timer);
vpu_service_power_off();
- destroy_workqueue(service.workqueue);
platform_device_unregister(&vpu_service_device);
platform_driver_unregister(&vpu_service_driver);
misc_deregister(&vpu_service_misc_device);
vpu_reg *reg, *reg_tmp;
vpu_session *session, *session_tmp;
- cancel_delayed_work_sync(&vpu_service_power_off_work);
vpu_service_power_on();
seq_printf(s, "\nENC Registers:\n");
n = enc_dev.iosize >> 2;
}
}
spin_unlock_irqrestore(&service.lock, flag);
- queue_delayed_work(service.workqueue, &vpu_service_power_off_work, POWER_OFF_DELAY);
return 0;
}