1510 lines
38 KiB
C
1510 lines
38 KiB
C
|
/* Copyright (c) 2008-2010, 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
#define DEBUG_TRACE_VDEC
|
||
|
#define DEBUG
|
||
|
*/
|
||
|
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/file.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/list.h>
|
||
|
#include <linux/miscdevice.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/wakelock.h>
|
||
|
#include <linux/pm_qos.h>
|
||
|
|
||
|
#include <linux/msm_q6vdec.h>
|
||
|
|
||
|
#include <mach/cpuidle.h>
|
||
|
|
||
|
#include "dal.h"
|
||
|
|
||
|
#define DALDEVICEID_VDEC_DEVICE 0x02000026
|
||
|
#define DALDEVICEID_VDEC_PORTNAME "DAL_AQ_VID"
|
||
|
|
||
|
#define VDEC_INTERFACE_VERSION 0x00020000
|
||
|
|
||
|
#define MAJOR_MASK 0xFFFF0000
|
||
|
#define MINOR_MASK 0x0000FFFF
|
||
|
|
||
|
#define VDEC_GET_MAJOR_VERSION(version) (((version)&MAJOR_MASK)>>16)
|
||
|
|
||
|
#define VDEC_GET_MINOR_VERSION(version) ((version)&MINOR_MASK)
|
||
|
|
||
|
#ifdef DEBUG_TRACE_VDEC
|
||
|
#define TRACE(fmt,x...) \
|
||
|
do { pr_debug("%s:%d " fmt, __func__, __LINE__, ##x); } while (0)
|
||
|
#else
|
||
|
#define TRACE(fmt,x...) do { } while (0)
|
||
|
#endif
|
||
|
|
||
|
#define YAMATO_COLOR_FORMAT 0x02
|
||
|
#define MAX_Q6_LOAD ((720*1280)/256) /* 720p */
|
||
|
#define MAX_Q6_LOAD_YAMATO ((736*1280)/256)
|
||
|
#define MAX_Q6_LOAD_VP6 ((800*480)/256)
|
||
|
|
||
|
#define VDEC_MAX_PORTS 4
|
||
|
|
||
|
/*
|
||
|
*why magic number 300?
|
||
|
|
||
|
*the Maximum size of the DAL payload is 512 bytes according to DAL protocol
|
||
|
*Initialize call to QDSP6 from scorpion need to send sequence header as part of
|
||
|
*the DAL payload. DAL payload to initialize contains the following
|
||
|
|
||
|
*1) configuration data- 52 bytes 2) length field of config data - 4 bytes
|
||
|
*3) sequence header data ( that is from the bit stream)
|
||
|
*4) length field for sequence header - 4 bytes
|
||
|
*5) length field for output structure - 4 bytes
|
||
|
|
||
|
*that left with 512 - 68 = 448 bytes. It is unusual that we get a sequence
|
||
|
*header with such a big length unless the bit stream has multiple sequence
|
||
|
*headers.We estimated 300 is good enough which gives enough room for rest
|
||
|
*of the payload and even reserves some space for future payload.
|
||
|
*/
|
||
|
|
||
|
#define VDEC_MAX_SEQ_HEADER_SIZE 300
|
||
|
|
||
|
char *Q6Portnames[] = {
|
||
|
"DAL_AQ_VID_0",
|
||
|
"DAL_AQ_VID_1",
|
||
|
"DAL_AQ_VID_2",
|
||
|
"DAL_AQ_VID_3"
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
#define DALDEVICEID_VDEC_DEVICE_0 0x020000D2
|
||
|
#define DALDEVICEID_VDEC_DEVICE_1 0x020000D3
|
||
|
#define DALDEVICEID_VDEC_DEVICE_2 0x020000D4
|
||
|
#define DALDEVICEID_VDEC_DEVICE_3 0x020000D5
|
||
|
#define DALDEVICEID_VDEC_DEVICE_4 0x020000D6
|
||
|
#define DALDEVICEID_VDEC_DEVICE_5 0x020000D7
|
||
|
#define DALDEVICEID_VDEC_DEVICE_6 0x020000D8
|
||
|
#define DALDEVICEID_VDEC_DEVICE_7 0x020000D9
|
||
|
#define DALDEVICEID_VDEC_DEVICE_8 0x020000DA
|
||
|
#define DALDEVICEID_VDEC_DEVICE_9 0x020000DB
|
||
|
#define DALDEVICEID_VDEC_DEVICE_10 0x020000DC
|
||
|
#define DALDEVICEID_VDEC_DEVICE_11 0x020000DD
|
||
|
#define DALDEVICEID_VDEC_DEVICE_12 0x020000DE
|
||
|
#define DALDEVICEID_VDEC_DEVICE_13 0x020000DF
|
||
|
#define DALDEVICEID_VDEC_DEVICE_14 0x020000E0
|
||
|
#define DALDEVICEID_VDEC_DEVICE_15 0x020000E1
|
||
|
#define DALDEVICEID_VDEC_DEVICE_16 0x020000E2
|
||
|
#define DALDEVICEID_VDEC_DEVICE_17 0x020000E3
|
||
|
#define DALDEVICEID_VDEC_DEVICE_18 0x020000E4
|
||
|
#define DALDEVICEID_VDEC_DEVICE_19 0x020000E5
|
||
|
#define DALDEVICEID_VDEC_DEVICE_20 0x020000E6
|
||
|
#define DALDEVICEID_VDEC_DEVICE_21 0x020000E7
|
||
|
#define DALDEVICEID_VDEC_DEVICE_22 0x020000E8
|
||
|
#define DALDEVICEID_VDEC_DEVICE_23 0x020000E9
|
||
|
#define DALDEVICEID_VDEC_DEVICE_24 0x020000EA
|
||
|
#define DALDEVICEID_VDEC_DEVICE_25 0x020000EB
|
||
|
#define DALDEVICEID_VDEC_DEVICE_26 0x020000EC
|
||
|
#define DALDEVICEID_VDEC_DEVICE_27 0x020000ED
|
||
|
#define DALDEVICEID_VDEC_DEVICE_28 0x020000EE
|
||
|
#define DALDEVICEID_VDEC_DEVICE_29 0x020000EF
|
||
|
#define DALDEVICEID_VDEC_DEVICE_30 0x020000F0
|
||
|
#define DALDEVICEID_VDEC_DEVICE_31 0x020000F1
|
||
|
|
||
|
#define DALVDEC_MAX_DEVICE_IDS 32
|
||
|
|
||
|
|
||
|
static int numOfPorts;
|
||
|
|
||
|
|
||
|
static char loadOnPorts[VDEC_MAX_PORTS];
|
||
|
|
||
|
static char deviceIdRegistry[DALVDEC_MAX_DEVICE_IDS];
|
||
|
|
||
|
|
||
|
#define VDEC_DEVID_FREE 0
|
||
|
#define VDEC_DEVID_OCCUPIED 1
|
||
|
|
||
|
#define MAX_SUPPORTED_INSTANCES 6
|
||
|
|
||
|
#define MAKEFOURCC(ch0, ch1, ch2, ch3) ((unsigned int)(unsigned char)(ch0) | \
|
||
|
((unsigned int)(unsigned char)(ch1) << 8) | \
|
||
|
((unsigned int)(unsigned char)(ch2) << 16) | \
|
||
|
((unsigned int)(unsigned char)(ch3) << 24))
|
||
|
|
||
|
#define FOURCC_MPEG4 MAKEFOURCC('m', 'p', '4', 'v')
|
||
|
#define FOURCC_H263 MAKEFOURCC('h', '2', '6', '3')
|
||
|
#define FOURCC_H264 MAKEFOURCC('h', '2', '6', '4')
|
||
|
#define FOURCC_VC1 MAKEFOURCC('w', 'm', 'v', '3')
|
||
|
#define FOURCC_DIVX MAKEFOURCC('D', 'I', 'V', 'X')
|
||
|
#define FOURCC_SPARK MAKEFOURCC('F', 'L', 'V', '1')
|
||
|
#define FOURCC_VP6 MAKEFOURCC('V', 'P', '6', '0')
|
||
|
|
||
|
/* static struct vdec_data *multiInstances[MAX_SUPPORTED_INSTANCES];*/
|
||
|
|
||
|
static int totalPlaybackQ6load;
|
||
|
static int totalTnailQ6load;
|
||
|
|
||
|
#define FLAG_THUMBNAIL_MODE 0x8
|
||
|
#define MAX_TNAILS 3
|
||
|
|
||
|
#define TRUE 1
|
||
|
#define FALSE 0
|
||
|
|
||
|
enum {
|
||
|
VDEC_DALRPC_INITIALIZE = DAL_OP_FIRST_DEVICE_API,
|
||
|
VDEC_DALRPC_SETBUFFERS,
|
||
|
VDEC_DALRPC_FREEBUFFERS,
|
||
|
VDEC_DALRPC_QUEUE,
|
||
|
VDEC_DALRPC_SIGEOFSTREAM,
|
||
|
VDEC_DALRPC_FLUSH,
|
||
|
VDEC_DALRPC_REUSEFRAMEBUFFER,
|
||
|
VDEC_DALRPC_GETDECATTRIBUTES,
|
||
|
VDEC_DALRPC_SUSPEND,
|
||
|
VDEC_DALRPC_RESUME,
|
||
|
VDEC_DALRPC_INITIALIZE_00,
|
||
|
VDEC_DALRPC_GETINTERNALBUFFERREQ,
|
||
|
VDEC_DALRPC_SETBUFFERS_00,
|
||
|
VDEC_DALRPC_FREEBUFFERS_00,
|
||
|
VDEC_DALRPC_GETPROPERTY,
|
||
|
VDEC_DALRPC_SETPROPERTY,
|
||
|
VDEC_DALRPC_GETDECATTRIBUTES_00,
|
||
|
VDEC_DALRPC_PERFORMANCE_CHANGE_REQUEST
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
VDEC_ASYNCMSG_DECODE_DONE = 0xdec0de00,
|
||
|
VDEC_ASYNCMSG_REUSE_FRAME,
|
||
|
};
|
||
|
|
||
|
struct vdec_init_cfg {
|
||
|
u32 decode_done_evt;
|
||
|
u32 reuse_frame_evt;
|
||
|
struct vdec_config cfg;
|
||
|
};
|
||
|
|
||
|
struct vdec_buffer_status {
|
||
|
u32 data;
|
||
|
u32 status;
|
||
|
};
|
||
|
|
||
|
#define VDEC_MSG_MAX 128
|
||
|
|
||
|
struct vdec_msg_list {
|
||
|
struct list_head list;
|
||
|
struct vdec_msg vdec_msg;
|
||
|
};
|
||
|
|
||
|
struct vdec_mem_info {
|
||
|
u32 buf_type;
|
||
|
u32 id;
|
||
|
unsigned long phys_addr;
|
||
|
unsigned long len;
|
||
|
struct file *file;
|
||
|
};
|
||
|
|
||
|
struct vdec_mem_list {
|
||
|
struct list_head list;
|
||
|
struct vdec_mem_info mem;
|
||
|
};
|
||
|
|
||
|
struct videoStreamDetails{
|
||
|
int height;
|
||
|
int width;
|
||
|
unsigned int fourcc;
|
||
|
int Q6usage;
|
||
|
bool isThisTnail;
|
||
|
bool isTnailGranted;
|
||
|
};
|
||
|
|
||
|
struct vdec_data {
|
||
|
struct dal_client *vdec_handle;
|
||
|
unsigned int Q6deviceId;
|
||
|
struct videoStreamDetails streamDetails;
|
||
|
struct list_head vdec_msg_list_head;
|
||
|
struct list_head vdec_msg_list_free;
|
||
|
wait_queue_head_t vdec_msg_evt;
|
||
|
spinlock_t vdec_list_lock;
|
||
|
struct list_head vdec_mem_list_head;
|
||
|
spinlock_t vdec_mem_list_lock;
|
||
|
int mem_initialized;
|
||
|
int running;
|
||
|
int close_decode;
|
||
|
};
|
||
|
|
||
|
static struct class *driver_class;
|
||
|
static dev_t vdec_device_no;
|
||
|
static struct cdev vdec_cdev;
|
||
|
static int ref_cnt;
|
||
|
static DEFINE_MUTEX(vdec_ref_lock);
|
||
|
|
||
|
static DEFINE_MUTEX(idlecount_lock);
|
||
|
|
||
|
static DEFINE_MUTEX(vdec_rm_lock);
|
||
|
|
||
|
static int idlecount;
|
||
|
static struct wake_lock wakelock;
|
||
|
static struct pm_qos_request pm_qos_req;
|
||
|
|
||
|
static void prevent_sleep(void)
|
||
|
{
|
||
|
mutex_lock(&idlecount_lock);
|
||
|
if (++idlecount == 1) {
|
||
|
pm_qos_update_request(&pm_qos_req,
|
||
|
msm_cpuidle_get_deep_idle_latency());
|
||
|
wake_lock(&wakelock);
|
||
|
}
|
||
|
mutex_unlock(&idlecount_lock);
|
||
|
}
|
||
|
|
||
|
static void allow_sleep(void)
|
||
|
{
|
||
|
mutex_lock(&idlecount_lock);
|
||
|
if (--idlecount == 0) {
|
||
|
wake_unlock(&wakelock);
|
||
|
pm_qos_update_request(&pm_qos_req, PM_QOS_DEFAULT_VALUE);
|
||
|
}
|
||
|
mutex_unlock(&idlecount_lock);
|
||
|
}
|
||
|
|
||
|
static inline int vdec_check_version(u32 client, u32 server)
|
||
|
{
|
||
|
int ret = -EINVAL;
|
||
|
if ((VDEC_GET_MAJOR_VERSION(client) == VDEC_GET_MAJOR_VERSION(server))
|
||
|
&& (VDEC_GET_MINOR_VERSION(client) <=
|
||
|
VDEC_GET_MINOR_VERSION(server)))
|
||
|
ret = 0;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int vdec_get_msg(struct vdec_data *vd, void *msg)
|
||
|
{
|
||
|
struct vdec_msg_list *l;
|
||
|
unsigned long flags;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!vd->running)
|
||
|
return -EPERM;
|
||
|
|
||
|
spin_lock_irqsave(&vd->vdec_list_lock, flags);
|
||
|
list_for_each_entry_reverse(l, &vd->vdec_msg_list_head, list) {
|
||
|
if (copy_to_user(msg, &l->vdec_msg, sizeof(struct vdec_msg)))
|
||
|
pr_err("vdec_get_msg failed to copy_to_user!\n");
|
||
|
if (l->vdec_msg.id == VDEC_MSG_REUSEINPUTBUFFER)
|
||
|
TRACE("reuse_input_buffer %d\n", l->vdec_msg.buf_id);
|
||
|
else if (l->vdec_msg.id == VDEC_MSG_FRAMEDONE)
|
||
|
TRACE("frame_done (stat=%d)\n",
|
||
|
l->vdec_msg.vfr_info.status);
|
||
|
else
|
||
|
TRACE("unknown msg (msgid=%d)\n", l->vdec_msg.id);
|
||
|
list_del(&l->list);
|
||
|
list_add(&l->list, &vd->vdec_msg_list_free);
|
||
|
ret = 1;
|
||
|
break;
|
||
|
}
|
||
|
spin_unlock_irqrestore(&vd->vdec_list_lock, flags);
|
||
|
|
||
|
if (vd->close_decode)
|
||
|
ret = 1;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void vdec_put_msg(struct vdec_data *vd, struct vdec_msg *msg)
|
||
|
{
|
||
|
struct vdec_msg_list *l;
|
||
|
unsigned long flags;
|
||
|
int found = 0;
|
||
|
|
||
|
spin_lock_irqsave(&vd->vdec_list_lock, flags);
|
||
|
list_for_each_entry(l, &vd->vdec_msg_list_free, list) {
|
||
|
memcpy(&l->vdec_msg, msg, sizeof(struct vdec_msg));
|
||
|
list_del(&l->list);
|
||
|
list_add(&l->list, &vd->vdec_msg_list_head);
|
||
|
found = 1;
|
||
|
break;
|
||
|
}
|
||
|
spin_unlock_irqrestore(&vd->vdec_list_lock, flags);
|
||
|
|
||
|
if (found)
|
||
|
wake_up(&vd->vdec_msg_evt);
|
||
|
else
|
||
|
pr_err("vdec_put_msg can't find free list!\n");
|
||
|
}
|
||
|
|
||
|
static struct vdec_mem_list *vdec_get_mem_from_list(struct vdec_data *vd,
|
||
|
u32 pmem_id, u32 buf_type)
|
||
|
{
|
||
|
struct vdec_mem_list *l;
|
||
|
unsigned long flags;
|
||
|
int found = 0;
|
||
|
|
||
|
spin_lock_irqsave(&vd->vdec_mem_list_lock, flags);
|
||
|
list_for_each_entry(l, &vd->vdec_mem_list_head, list) {
|
||
|
if (l->mem.buf_type == buf_type && l->mem.id == pmem_id) {
|
||
|
found = 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
spin_unlock_irqrestore(&vd->vdec_mem_list_lock, flags);
|
||
|
|
||
|
if (found)
|
||
|
return l;
|
||
|
else
|
||
|
return NULL;
|
||
|
|
||
|
}
|
||
|
static int vdec_setproperty(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
struct vdec_property_info property;
|
||
|
int res;
|
||
|
|
||
|
if (copy_from_user(&property, argp, sizeof(struct vdec_property_info)))
|
||
|
return -1;
|
||
|
|
||
|
res = dal_call_f6(vd->vdec_handle, VDEC_DALRPC_SETPROPERTY,
|
||
|
property.id, &(property.property), sizeof(union vdec_property));
|
||
|
if (res)
|
||
|
TRACE("Set Property failed");
|
||
|
else
|
||
|
TRACE("Set Property succeeded");
|
||
|
return res;
|
||
|
}
|
||
|
static int vdec_getproperty(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
int res;
|
||
|
union vdec_property property = {0};
|
||
|
|
||
|
res = dal_call_f11(vd->vdec_handle, VDEC_DALRPC_GETPROPERTY,
|
||
|
((struct vdec_property_info *)argp)->id, &property,
|
||
|
sizeof(union vdec_property));
|
||
|
|
||
|
if (res)
|
||
|
TRACE("get Property failed");
|
||
|
else
|
||
|
TRACE("get Property succeeded");
|
||
|
|
||
|
res = copy_to_user(
|
||
|
(&((struct vdec_property_info *)argp)->property),
|
||
|
&property, sizeof(property));
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
static int vdec_performance_change_request(struct vdec_data *vd, void* argp)
|
||
|
{
|
||
|
u32 request_type;
|
||
|
int ret;
|
||
|
|
||
|
ret = copy_from_user(&request_type, argp, sizeof(request_type));
|
||
|
if (ret) {
|
||
|
pr_err("%s: copy_from_user failed\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
ret = dal_call_f0(vd->vdec_handle,
|
||
|
VDEC_DALRPC_PERFORMANCE_CHANGE_REQUEST,
|
||
|
request_type);
|
||
|
if (ret) {
|
||
|
pr_err("%s: remote function failed (%d)\n", __func__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#ifdef TRACE_PORTS
|
||
|
static void printportsanddeviceids(void)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
pr_err("\n\n%s:loadOnPorts", __func__);
|
||
|
for (i = 0; i < numOfPorts; i++)
|
||
|
pr_err("\t%d", loadOnPorts[i]);
|
||
|
|
||
|
pr_err("\n\n");
|
||
|
|
||
|
pr_err("\n\n%s:Devids", __func__);
|
||
|
for (i = 0; i < DALVDEC_MAX_DEVICE_IDS; i++)
|
||
|
pr_err("Devid[%d]:%d\n", i, deviceIdRegistry[i]);
|
||
|
|
||
|
|
||
|
pr_err("\n\n");
|
||
|
}
|
||
|
#endif /*TRACE_PORTS*/
|
||
|
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
* This method is used to get the number of ports supported on the Q6
|
||
|
*
|
||
|
*/
|
||
|
static int vdec_get_numberofq6ports(void)
|
||
|
{
|
||
|
struct dal_client *vdec_handle = NULL;
|
||
|
int retval = 0;
|
||
|
union vdec_property property = {0};
|
||
|
|
||
|
vdec_handle = dal_attach(DALDEVICEID_VDEC_DEVICE,
|
||
|
DALDEVICEID_VDEC_PORTNAME, 1, NULL, NULL);
|
||
|
if (!vdec_handle) {
|
||
|
pr_err("%s: failed to attach\n", __func__);
|
||
|
return 1;/* default setting */
|
||
|
}
|
||
|
|
||
|
retval = dal_call_f6(vdec_handle, VDEC_DALRPC_GETPROPERTY,
|
||
|
VDEC_NUM_DAL_PORTS, (void *)&property, sizeof(union vdec_property));
|
||
|
if (retval) {
|
||
|
pr_err("%s: Q6get prperty failed\n", __func__);
|
||
|
return 1;/* default setting */
|
||
|
}
|
||
|
|
||
|
dal_detach(vdec_handle);
|
||
|
return property.num_dal_ports ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* This method is used to get the find the least loaded port and a corresponding
|
||
|
* free device id in that port.
|
||
|
*
|
||
|
* Prerequisite: vdec_open should have been called.
|
||
|
*
|
||
|
* @param[in] deviceid
|
||
|
* device id will be populated here.
|
||
|
*
|
||
|
* @param[in] portname
|
||
|
* portname will be populated here.
|
||
|
*/
|
||
|
static void vdec_get_next_portanddevid(int *deviceid, char **portname)
|
||
|
{
|
||
|
|
||
|
int i = 0;
|
||
|
int leastLoad = 0;
|
||
|
int leastLoadedIndex = 0;
|
||
|
|
||
|
if (0 == numOfPorts) {
|
||
|
numOfPorts = vdec_get_numberofq6ports();
|
||
|
pr_err("%s: Q6get numOfPorts %d\n", __func__, numOfPorts);
|
||
|
numOfPorts = 4;
|
||
|
/*fix: me currently hard coded to 4 as
|
||
|
*the Q6 getproperty is failing
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
if ((NULL == deviceid) || (NULL == portname))
|
||
|
return;
|
||
|
else
|
||
|
*deviceid = 0; /* init value */
|
||
|
|
||
|
if (numOfPorts > 1) {
|
||
|
/* multi ports mode*/
|
||
|
|
||
|
/* find the least loaded port*/
|
||
|
for (i = 1, leastLoad = loadOnPorts[0], leastLoadedIndex = 0;
|
||
|
i < numOfPorts; i++) {
|
||
|
if (leastLoad > loadOnPorts[i]) {
|
||
|
leastLoadedIndex = i;
|
||
|
leastLoad = loadOnPorts[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* register the load */
|
||
|
loadOnPorts[leastLoadedIndex]++;
|
||
|
*portname = Q6Portnames[leastLoadedIndex];
|
||
|
|
||
|
/* find a free device id corresponding to the port*/
|
||
|
for (i = leastLoadedIndex; i < DALVDEC_MAX_DEVICE_IDS;
|
||
|
i += numOfPorts) {
|
||
|
if (VDEC_DEVID_FREE == deviceIdRegistry[i]) {
|
||
|
deviceIdRegistry[i] = VDEC_DEVID_OCCUPIED;
|
||
|
*deviceid = DALDEVICEID_VDEC_DEVICE_0 + i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef TRACE_PORTS
|
||
|
printportsanddeviceids();
|
||
|
#endif /*TRACE_PORTS*/
|
||
|
} else if (1 == numOfPorts) {
|
||
|
/* single port mode */
|
||
|
*deviceid = DALDEVICEID_VDEC_DEVICE;
|
||
|
*portname = DALDEVICEID_VDEC_PORTNAME;
|
||
|
} else if (numOfPorts <= 0) {
|
||
|
pr_err("%s: FATAL error numOfPorts cannot be \
|
||
|
less than or equal to zero\n", __func__);
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* This method frees up the used dev id and decrements the port load.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static void vdec_freeup_portanddevid(int deviceid)
|
||
|
{
|
||
|
|
||
|
if (numOfPorts > 1) {
|
||
|
/* multi ports mode*/
|
||
|
if (VDEC_DEVID_FREE ==
|
||
|
deviceIdRegistry[deviceid - DALDEVICEID_VDEC_DEVICE_0])
|
||
|
pr_err("device id cannot be already free\n");
|
||
|
deviceIdRegistry[deviceid - DALDEVICEID_VDEC_DEVICE_0] =
|
||
|
VDEC_DEVID_FREE;
|
||
|
|
||
|
loadOnPorts[(deviceid - DALDEVICEID_VDEC_DEVICE_0)
|
||
|
% numOfPorts]--;
|
||
|
|
||
|
if (loadOnPorts[(deviceid - DALDEVICEID_VDEC_DEVICE_0)
|
||
|
% numOfPorts] < 0)
|
||
|
pr_err("Warning:load cannot be negative\n");
|
||
|
|
||
|
pr_err("dettaching on deviceid %x portname %s\n", deviceid,
|
||
|
Q6Portnames[(deviceid - DALDEVICEID_VDEC_DEVICE_0)
|
||
|
% numOfPorts]);
|
||
|
|
||
|
#ifdef TRACE_PORTS
|
||
|
printportsanddeviceids();
|
||
|
#endif /*TRACE_PORTS*/
|
||
|
} else {
|
||
|
/*single port mode, nothing to be done here*/
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* This method validates whether a new instance can be houred or not.
|
||
|
*
|
||
|
*/
|
||
|
static int vdec_rm_checkWithRm(struct vdec_data *vdecInstance,
|
||
|
unsigned int color_format)
|
||
|
{
|
||
|
|
||
|
unsigned int maxQ6load = 0;/* in the units of macro blocks per second */
|
||
|
unsigned int currentq6load = 0;
|
||
|
struct videoStreamDetails *streamDetails = &vdecInstance->streamDetails;
|
||
|
|
||
|
|
||
|
|
||
|
if (streamDetails->isThisTnail) {
|
||
|
if (totalTnailQ6load < MAX_TNAILS) {
|
||
|
|
||
|
totalTnailQ6load++;
|
||
|
streamDetails->isTnailGranted = TRUE;
|
||
|
pr_info("%s: thumbnail granted %d\n", __func__,
|
||
|
totalTnailQ6load);
|
||
|
return 0;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
pr_err("%s: thumbnails load max this instance cannot \
|
||
|
be supported\n", __func__);
|
||
|
streamDetails->isTnailGranted = FALSE;
|
||
|
return -ENOSPC;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* calculate the Q6 percentage instance would need */
|
||
|
if ((streamDetails->fourcc == FOURCC_MPEG4) ||
|
||
|
(streamDetails->fourcc == FOURCC_H264) ||
|
||
|
(streamDetails->fourcc == FOURCC_DIVX) ||
|
||
|
(streamDetails->fourcc == FOURCC_VC1) ||
|
||
|
(streamDetails->fourcc == FOURCC_SPARK) ||
|
||
|
(streamDetails->fourcc == FOURCC_H263)
|
||
|
){
|
||
|
|
||
|
/* is yamato color format,
|
||
|
Rounds the H & W --> mutiple of 32 */
|
||
|
if (color_format == YAMATO_COLOR_FORMAT)
|
||
|
maxQ6load = MAX_Q6_LOAD_YAMATO;
|
||
|
else
|
||
|
maxQ6load = MAX_Q6_LOAD; /* 720p */
|
||
|
|
||
|
} else if (streamDetails->fourcc == FOURCC_VP6) {
|
||
|
|
||
|
maxQ6load = MAX_Q6_LOAD_VP6; /* FWVGA */
|
||
|
|
||
|
} else {
|
||
|
|
||
|
pr_err("%s: unknown fourcc %d maxQ6load %u\n", __func__,
|
||
|
streamDetails->fourcc, maxQ6load);
|
||
|
return -EINVAL;
|
||
|
|
||
|
}
|
||
|
|
||
|
currentq6load = ((streamDetails->height)*(streamDetails->width) / 256);
|
||
|
currentq6load = ((currentq6load * 100)/maxQ6load);
|
||
|
if ((currentq6load+totalPlaybackQ6load) > 100) {
|
||
|
/* reject this instance */
|
||
|
pr_err("%s: too much Q6load [cur+tot] = [%d + %d] = %d",
|
||
|
__func__, currentq6load, totalPlaybackQ6load,
|
||
|
(currentq6load+totalPlaybackQ6load));
|
||
|
pr_err("rejecting the instance,[WxH] = [%d x %d],color_fmt=0x%x\n",
|
||
|
streamDetails->width, streamDetails->height, color_format);
|
||
|
pr_err("VDEC_fmt=%s\n", (char *)(&streamDetails->fourcc));
|
||
|
streamDetails->Q6usage = 0;
|
||
|
return -ENOSPC;
|
||
|
}
|
||
|
|
||
|
totalPlaybackQ6load += currentq6load;
|
||
|
streamDetails->Q6usage = currentq6load;
|
||
|
|
||
|
pr_info("%s: adding a load [%d%%] bringing total Q6load to [%d%%]\n",
|
||
|
__func__, currentq6load, totalPlaybackQ6load);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int vdec_initialize(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
struct vdec_config_sps vdec_cfg_sps;
|
||
|
struct vdec_init_cfg vi_cfg;
|
||
|
struct vdec_buf_req vdec_buf_req;
|
||
|
struct u8 *header;
|
||
|
int ret = 0;
|
||
|
|
||
|
ret = copy_from_user(&vdec_cfg_sps,
|
||
|
&((struct vdec_init *)argp)->sps_cfg,
|
||
|
sizeof(vdec_cfg_sps));
|
||
|
|
||
|
if (ret) {
|
||
|
pr_err("%s: copy_from_user failed\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
vi_cfg.decode_done_evt = VDEC_ASYNCMSG_DECODE_DONE;
|
||
|
vi_cfg.reuse_frame_evt = VDEC_ASYNCMSG_REUSE_FRAME;
|
||
|
memcpy(&vi_cfg.cfg, &vdec_cfg_sps.cfg, sizeof(struct vdec_config));
|
||
|
|
||
|
/*
|
||
|
* restricting the max value of the seq header
|
||
|
*/
|
||
|
if (vdec_cfg_sps.seq.len > VDEC_MAX_SEQ_HEADER_SIZE)
|
||
|
vdec_cfg_sps.seq.len = VDEC_MAX_SEQ_HEADER_SIZE;
|
||
|
|
||
|
header = kmalloc(vdec_cfg_sps.seq.len, GFP_KERNEL);
|
||
|
if (!header) {
|
||
|
pr_err("%s: kmalloc failed\n", __func__);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
ret = copy_from_user(header,
|
||
|
((struct vdec_init *)argp)->sps_cfg.seq.header,
|
||
|
vdec_cfg_sps.seq.len);
|
||
|
|
||
|
if (ret) {
|
||
|
pr_err("%s: copy_from_user failed\n", __func__);
|
||
|
kfree(header);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
TRACE("vi_cfg: handle=%p fourcc=0x%x w=%d h=%d order=%d notify_en=%d "
|
||
|
"vc1_rb=%d h264_sd=%d h264_nls=%d pp_flag=%d fruc_en=%d\n",
|
||
|
vd->vdec_handle, vi_cfg.cfg.fourcc, vi_cfg.cfg.width,
|
||
|
vi_cfg.cfg.height, vi_cfg.cfg.order, vi_cfg.cfg.notify_enable,
|
||
|
vi_cfg.cfg.vc1_rowbase, vi_cfg.cfg.h264_startcode_detect,
|
||
|
vi_cfg.cfg.h264_nal_len_size, vi_cfg.cfg.postproc_flag,
|
||
|
vi_cfg.cfg.fruc_enable);
|
||
|
|
||
|
vd->streamDetails.height = vi_cfg.cfg.height;
|
||
|
vd->streamDetails.width = vi_cfg.cfg.width;
|
||
|
vd->streamDetails.fourcc = vi_cfg.cfg.fourcc;
|
||
|
if (FLAG_THUMBNAIL_MODE == vi_cfg.cfg.postproc_flag)
|
||
|
vd->streamDetails.isThisTnail = TRUE;
|
||
|
else
|
||
|
vd->streamDetails.isThisTnail = FALSE;
|
||
|
|
||
|
mutex_lock(&vdec_rm_lock);
|
||
|
ret = vdec_rm_checkWithRm(vd, vi_cfg.cfg.color_format);
|
||
|
mutex_unlock(&vdec_rm_lock);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = dal_call_f13(vd->vdec_handle, VDEC_DALRPC_INITIALIZE,
|
||
|
&vi_cfg, sizeof(vi_cfg),
|
||
|
header, vdec_cfg_sps.seq.len,
|
||
|
&vdec_buf_req, sizeof(vdec_buf_req));
|
||
|
|
||
|
kfree(header);
|
||
|
|
||
|
if (ret)
|
||
|
pr_err("%s: remote function failed (%d)\n", __func__, ret);
|
||
|
else
|
||
|
ret = copy_to_user(((struct vdec_init *)argp)->buf_req,
|
||
|
&vdec_buf_req, sizeof(vdec_buf_req));
|
||
|
|
||
|
vd->close_decode = 0;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void vdec_rm_freeupResources(struct vdec_data *vdecInstance)
|
||
|
{
|
||
|
struct videoStreamDetails *streamDetails = &vdecInstance->streamDetails;
|
||
|
|
||
|
|
||
|
|
||
|
if ((streamDetails->isThisTnail) &&
|
||
|
(streamDetails->isTnailGranted)) {
|
||
|
|
||
|
totalTnailQ6load--;
|
||
|
pr_info("%s: Thumbnail released %d\n", __func__,
|
||
|
totalTnailQ6load);
|
||
|
|
||
|
} else if (streamDetails->Q6usage > 0) {
|
||
|
|
||
|
totalPlaybackQ6load -= streamDetails->Q6usage;
|
||
|
if (totalPlaybackQ6load < 0)
|
||
|
pr_err("Warning:Q6load cannot be negative\n");
|
||
|
|
||
|
pr_info("%s:Releasing [%d%%] of Q6load from a total of [%d%%]\n"
|
||
|
, __func__, streamDetails->Q6usage,
|
||
|
(streamDetails->Q6usage+totalPlaybackQ6load));
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
static int vdec_setbuffers(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
struct vdec_buffer vmem;
|
||
|
struct vdec_mem_list *l;
|
||
|
unsigned long vstart;
|
||
|
unsigned long flags;
|
||
|
struct {
|
||
|
uint32_t size;
|
||
|
struct vdec_buf_info buf;
|
||
|
} rpc;
|
||
|
uint32_t res;
|
||
|
|
||
|
int ret = 0;
|
||
|
|
||
|
vd->mem_initialized = 0;
|
||
|
|
||
|
ret = copy_from_user(&vmem, argp, sizeof(vmem));
|
||
|
if (ret) {
|
||
|
pr_err("%s: copy_from_user failed\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
l = kzalloc(sizeof(struct vdec_mem_list), GFP_KERNEL);
|
||
|
if (!l) {
|
||
|
pr_err("%s: kzalloc failed!\n", __func__);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
l->mem.id = vmem.pmem_id;
|
||
|
l->mem.buf_type = vmem.buf.buf_type;
|
||
|
|
||
|
ret = get_pmem_file(l->mem.id, &l->mem.phys_addr, &vstart,
|
||
|
&l->mem.len, &l->mem.file);
|
||
|
if (ret) {
|
||
|
pr_err("%s: get_pmem_fd failed\n", __func__);
|
||
|
goto err_get_pmem_file;
|
||
|
}
|
||
|
|
||
|
TRACE("pmem_id=%d (phys=0x%08lx len=0x%lx) buftype=%d num_buf=%d "
|
||
|
"islast=%d src_id=%d offset=0x%08x size=0x%x\n",
|
||
|
vmem.pmem_id, l->mem.phys_addr, l->mem.len,
|
||
|
vmem.buf.buf_type, vmem.buf.num_buf, vmem.buf.islast,
|
||
|
vmem.buf.region.src_id, vmem.buf.region.offset,
|
||
|
vmem.buf.region.size);
|
||
|
|
||
|
/* input buffers */
|
||
|
if ((vmem.buf.region.offset + vmem.buf.region.size) > l->mem.len) {
|
||
|
pr_err("%s: invalid input buffer offset!\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto err_bad_offset;
|
||
|
|
||
|
}
|
||
|
vmem.buf.region.offset += l->mem.phys_addr;
|
||
|
|
||
|
rpc.size = sizeof(vmem.buf);
|
||
|
memcpy(&rpc.buf, &vmem.buf, sizeof(struct vdec_buf_info));
|
||
|
|
||
|
|
||
|
ret = dal_call(vd->vdec_handle, VDEC_DALRPC_SETBUFFERS, 5,
|
||
|
&rpc, sizeof(rpc), &res, sizeof(res));
|
||
|
|
||
|
if (ret < 4) {
|
||
|
pr_err("%s: remote function failed (%d)\n", __func__, ret);
|
||
|
ret = -EIO;
|
||
|
goto err_dal_call;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&vd->vdec_mem_list_lock, flags);
|
||
|
list_add(&l->list, &vd->vdec_mem_list_head);
|
||
|
spin_unlock_irqrestore(&vd->vdec_mem_list_lock, flags);
|
||
|
|
||
|
vd->mem_initialized = 1;
|
||
|
return ret;
|
||
|
|
||
|
err_dal_call:
|
||
|
err_bad_offset:
|
||
|
put_pmem_file(l->mem.file);
|
||
|
err_get_pmem_file:
|
||
|
kfree(l);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int vdec_queue(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
struct {
|
||
|
uint32_t size;
|
||
|
struct vdec_input_buf_info buf_info;
|
||
|
uint32_t osize;
|
||
|
} rpc;
|
||
|
struct vdec_mem_list *l;
|
||
|
struct {
|
||
|
uint32_t result;
|
||
|
uint32_t size;
|
||
|
struct vdec_queue_status status;
|
||
|
} rpc_res;
|
||
|
|
||
|
u32 pmem_id;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!vd->mem_initialized) {
|
||
|
pr_err("%s: memory is not being initialized!\n", __func__);
|
||
|
return -EPERM;
|
||
|
}
|
||
|
|
||
|
ret = copy_from_user(&rpc.buf_info,
|
||
|
&((struct vdec_input_buf *)argp)->buffer,
|
||
|
sizeof(rpc.buf_info));
|
||
|
if (ret) {
|
||
|
pr_err("%s: copy_from_user failed\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = copy_from_user(&pmem_id,
|
||
|
&((struct vdec_input_buf *)argp)->pmem_id,
|
||
|
sizeof(u32));
|
||
|
if (ret) {
|
||
|
pr_err("%s: copy_from_user failed\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
l = vdec_get_mem_from_list(vd, pmem_id, VDEC_BUFFER_TYPE_INPUT);
|
||
|
|
||
|
if (NULL == l) {
|
||
|
pr_err("%s: not able to find the buffer from list\n", __func__);
|
||
|
return -EPERM;
|
||
|
}
|
||
|
|
||
|
if ((rpc.buf_info.size + rpc.buf_info.offset) >= l->mem.len) {
|
||
|
pr_err("%s: invalid queue buffer offset!\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
rpc.buf_info.offset += l->mem.phys_addr;
|
||
|
rpc.size = sizeof(struct vdec_input_buf_info);
|
||
|
rpc.osize = sizeof(struct vdec_queue_status);
|
||
|
|
||
|
/* complete the writes to the buffer */
|
||
|
wmb();
|
||
|
ret = dal_call(vd->vdec_handle, VDEC_DALRPC_QUEUE, 8,
|
||
|
&rpc, sizeof(rpc), &rpc_res, sizeof(rpc_res));
|
||
|
if (ret < 4) {
|
||
|
pr_err("%s: remote function failed (%d)\n", __func__, ret);
|
||
|
ret = -EIO;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int vdec_reuse_framebuffer(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
u32 buf_id;
|
||
|
int ret = 0;
|
||
|
|
||
|
ret = copy_from_user(&buf_id, argp, sizeof(buf_id));
|
||
|
if (ret) {
|
||
|
pr_err("%s: copy_from_user failed\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = dal_call_f0(vd->vdec_handle, VDEC_DALRPC_REUSEFRAMEBUFFER,
|
||
|
buf_id);
|
||
|
if (ret)
|
||
|
pr_err("%s: remote function failed (%d)\n", __func__, ret);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int vdec_flush(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
u32 flush_type;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!vd->mem_initialized) {
|
||
|
pr_err("%s: memory is not being initialized!\n", __func__);
|
||
|
return -EPERM;
|
||
|
}
|
||
|
|
||
|
ret = copy_from_user(&flush_type, argp, sizeof(flush_type));
|
||
|
if (ret) {
|
||
|
pr_err("%s: copy_from_user failed\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
TRACE("flush_type=%d\n", flush_type);
|
||
|
ret = dal_call_f0(vd->vdec_handle, VDEC_DALRPC_FLUSH, flush_type);
|
||
|
if (ret) {
|
||
|
pr_err("%s: remote function failed (%d)\n", __func__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int vdec_close(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
struct vdec_mem_list *l;
|
||
|
int ret = 0;
|
||
|
|
||
|
pr_info("q6vdec_close()\n");
|
||
|
vd->close_decode = 1;
|
||
|
wake_up(&vd->vdec_msg_evt);
|
||
|
|
||
|
ret = dal_call_f0(vd->vdec_handle, DAL_OP_CLOSE, 0);
|
||
|
if (ret)
|
||
|
pr_err("%s: failed to close daldevice (%d)\n", __func__, ret);
|
||
|
|
||
|
if (vd->mem_initialized) {
|
||
|
list_for_each_entry(l, &vd->vdec_mem_list_head, list)
|
||
|
put_pmem_file(l->mem.file);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
static int vdec_getdecattributes(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
struct {
|
||
|
uint32_t status;
|
||
|
uint32_t size;
|
||
|
struct vdec_dec_attributes dec_attr;
|
||
|
} rpc;
|
||
|
uint32_t inp;
|
||
|
int ret = 0;
|
||
|
inp = sizeof(struct vdec_dec_attributes);
|
||
|
|
||
|
ret = dal_call(vd->vdec_handle, VDEC_DALRPC_GETDECATTRIBUTES, 9,
|
||
|
&inp, sizeof(inp), &rpc, sizeof(rpc));
|
||
|
if (ret < 4 || rpc.size != sizeof(struct vdec_dec_attributes)) {
|
||
|
pr_err("%s: remote function failed (%d)\n", __func__, ret);
|
||
|
ret = -EIO;
|
||
|
} else
|
||
|
ret =
|
||
|
copy_to_user(((struct vdec_dec_attributes *)argp),
|
||
|
&rpc.dec_attr, sizeof(rpc.dec_attr));
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int vdec_freebuffers(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
struct vdec_buffer vmem;
|
||
|
struct vdec_mem_list *l;
|
||
|
struct {
|
||
|
uint32_t size;
|
||
|
struct vdec_buf_info buf;
|
||
|
} rpc;
|
||
|
uint32_t res;
|
||
|
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!vd->mem_initialized) {
|
||
|
pr_err("%s: memory is not being initialized!\n", __func__);
|
||
|
return -EPERM;
|
||
|
}
|
||
|
|
||
|
ret = copy_from_user(&vmem, argp, sizeof(vmem));
|
||
|
if (ret) {
|
||
|
pr_err("%s: copy_from_user failed\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
l = vdec_get_mem_from_list(vd, vmem.pmem_id, vmem.buf.buf_type);
|
||
|
|
||
|
if (NULL == l) {
|
||
|
pr_err("%s: not able to find the buffer from list\n", __func__);
|
||
|
return -EPERM;
|
||
|
}
|
||
|
|
||
|
/* input buffers */
|
||
|
if ((vmem.buf.region.offset + vmem.buf.region.size) > l->mem.len) {
|
||
|
pr_err("%s: invalid input buffer offset!\n", __func__);
|
||
|
return -EINVAL;
|
||
|
|
||
|
}
|
||
|
vmem.buf.region.offset += l->mem.phys_addr;
|
||
|
|
||
|
rpc.size = sizeof(vmem.buf);
|
||
|
memcpy(&rpc.buf, &vmem.buf, sizeof(struct vdec_buf_info));
|
||
|
|
||
|
ret = dal_call(vd->vdec_handle, VDEC_DALRPC_FREEBUFFERS, 5,
|
||
|
&rpc, sizeof(rpc), &res, sizeof(res));
|
||
|
if (ret < 4) {
|
||
|
pr_err("%s: remote function failed (%d)\n", __func__, ret);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int vdec_getversion(struct vdec_data *vd, void *argp)
|
||
|
{
|
||
|
struct vdec_version ver_info;
|
||
|
int ret = 0;
|
||
|
|
||
|
ver_info.major = VDEC_GET_MAJOR_VERSION(VDEC_INTERFACE_VERSION);
|
||
|
ver_info.minor = VDEC_GET_MINOR_VERSION(VDEC_INTERFACE_VERSION);
|
||
|
|
||
|
ret = copy_to_user(((struct vdec_version *)argp),
|
||
|
&ver_info, sizeof(ver_info));
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
}
|
||
|
|
||
|
static long vdec_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||
|
{
|
||
|
struct vdec_data *vd = file->private_data;
|
||
|
void __user *argp = (void __user *)arg;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!vd->running)
|
||
|
return -EPERM;
|
||
|
|
||
|
switch (cmd) {
|
||
|
case VDEC_IOCTL_INITIALIZE:
|
||
|
ret = vdec_initialize(vd, argp);
|
||
|
break;
|
||
|
|
||
|
case VDEC_IOCTL_SETBUFFERS:
|
||
|
ret = vdec_setbuffers(vd, argp);
|
||
|
break;
|
||
|
|
||
|
case VDEC_IOCTL_QUEUE:
|
||
|
TRACE("VDEC_IOCTL_QUEUE (pid=%d tid=%d)\n",
|
||
|
current->group_leader->pid, current->pid);
|
||
|
ret = vdec_queue(vd, argp);
|
||
|
break;
|
||
|
|
||
|
case VDEC_IOCTL_REUSEFRAMEBUFFER:
|
||
|
TRACE("VDEC_IOCTL_REUSEFRAMEBUFFER (pid=%d tid=%d)\n",
|
||
|
current->group_leader->pid, current->pid);
|
||
|
ret = vdec_reuse_framebuffer(vd, argp);
|
||
|
break;
|
||
|
|
||
|
case VDEC_IOCTL_FLUSH:
|
||
|
TRACE("IOCTL flush\n");
|
||
|
ret = vdec_flush(vd, argp);
|
||
|
break;
|
||
|
|
||
|
case VDEC_IOCTL_EOS:
|
||
|
TRACE("VDEC_IOCTL_EOS (pid=%d tid=%d)\n",
|
||
|
current->group_leader->pid, current->pid);
|
||
|
ret = dal_call_f0(vd->vdec_handle, VDEC_DALRPC_SIGEOFSTREAM, 0);
|
||
|
if (ret)
|
||
|
pr_err("%s: remote function failed (%d)\n",
|
||
|
__func__, ret);
|
||
|
break;
|
||
|
|
||
|
case VDEC_IOCTL_GETMSG:
|
||
|
TRACE("VDEC_IOCTL_GETMSG (pid=%d tid=%d)\n",
|
||
|
current->group_leader->pid, current->pid);
|
||
|
wait_event_interruptible(vd->vdec_msg_evt,
|
||
|
vdec_get_msg(vd, argp));
|
||
|
|
||
|
if (vd->close_decode)
|
||
|
ret = -EINTR;
|
||
|
else
|
||
|
/* order the reads from the buffer */
|
||
|
rmb();
|
||
|
break;
|
||
|
|
||
|
case VDEC_IOCTL_CLOSE:
|
||
|
ret = vdec_close(vd, argp);
|
||
|
break;
|
||
|
|
||
|
case VDEC_IOCTL_GETDECATTRIBUTES:
|
||
|
TRACE("VDEC_IOCTL_GETDECATTRIBUTES (pid=%d tid=%d)\n",
|
||
|
current->group_leader->pid, current->pid);
|
||
|
ret = vdec_getdecattributes(vd, argp);
|
||
|
|
||
|
if (ret)
|
||
|
pr_err("%s: remote function failed (%d)\n",
|
||
|
__func__, ret);
|
||
|
break;
|
||
|
|
||
|
case VDEC_IOCTL_FREEBUFFERS:
|
||
|
TRACE("VDEC_IOCTL_FREEBUFFERS (pid=%d tid=%d)\n",
|
||
|
current->group_leader->pid, current->pid);
|
||
|
ret = vdec_freebuffers(vd, argp);
|
||
|
|
||
|
if (ret)
|
||
|
pr_err("%s: remote function failed (%d)\n",
|
||
|
__func__, ret);
|
||
|
break;
|
||
|
case VDEC_IOCTL_GETVERSION:
|
||
|
TRACE("VDEC_IOCTL_GETVERSION (pid=%d tid=%d)\n",
|
||
|
current->group_leader->pid, current->pid);
|
||
|
ret = vdec_getversion(vd, argp);
|
||
|
|
||
|
if (ret)
|
||
|
pr_err("%s: remote function failed (%d)\n",
|
||
|
__func__, ret);
|
||
|
break;
|
||
|
case VDEC_IOCTL_GETPROPERTY:
|
||
|
TRACE("VDEC_IOCTL_GETPROPERTY (pid=%d tid=%d)\n",
|
||
|
current->group_leader->pid, current->pid);
|
||
|
ret = vdec_getproperty(vd, argp);
|
||
|
break;
|
||
|
case VDEC_IOCTL_SETPROPERTY:
|
||
|
TRACE("VDEC_IOCTL_SETPROPERTY (pid=%d tid=%d)\n",
|
||
|
current->group_leader->pid, current->pid);
|
||
|
ret = vdec_setproperty(vd, argp);
|
||
|
break;
|
||
|
case VDEC_IOCTL_PERFORMANCE_CHANGE_REQ:
|
||
|
ret = vdec_performance_change_request(vd, argp);
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("%s: invalid ioctl!\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
TRACE("ioctl done (pid=%d tid=%d)\n",
|
||
|
current->group_leader->pid, current->pid);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void vdec_dcdone_handler(struct vdec_data *vd, void *frame,
|
||
|
uint32_t frame_size)
|
||
|
{
|
||
|
struct vdec_msg msg;
|
||
|
struct vdec_mem_list *l;
|
||
|
unsigned long flags;
|
||
|
int found = 0;
|
||
|
|
||
|
if (frame_size < sizeof(struct vdec_frame_info)) {
|
||
|
pr_warning("%s: msg size mismatch %d != %d\n", __func__,
|
||
|
frame_size, sizeof(struct vdec_frame_info));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
memcpy(&msg.vfr_info, (struct vdec_frame_info *)frame,
|
||
|
sizeof(struct vdec_frame_info));
|
||
|
|
||
|
if (msg.vfr_info.status == VDEC_FRAME_DECODE_OK) {
|
||
|
spin_lock_irqsave(&vd->vdec_mem_list_lock, flags);
|
||
|
list_for_each_entry(l, &vd->vdec_mem_list_head, list) {
|
||
|
if ((l->mem.buf_type == VDEC_BUFFER_TYPE_OUTPUT) &&
|
||
|
(msg.vfr_info.offset >= l->mem.phys_addr) &&
|
||
|
(msg.vfr_info.offset <
|
||
|
(l->mem.phys_addr + l->mem.len))) {
|
||
|
found = 1;
|
||
|
msg.vfr_info.offset -= l->mem.phys_addr;
|
||
|
msg.vfr_info.data2 = l->mem.id;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
spin_unlock_irqrestore(&vd->vdec_mem_list_lock, flags);
|
||
|
}
|
||
|
|
||
|
if (found || (msg.vfr_info.status != VDEC_FRAME_DECODE_OK)) {
|
||
|
msg.id = VDEC_MSG_FRAMEDONE;
|
||
|
vdec_put_msg(vd, &msg);
|
||
|
} else {
|
||
|
pr_err("%s: invalid phys addr = 0x%x\n",
|
||
|
__func__, msg.vfr_info.offset);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
static void vdec_reuseibuf_handler(struct vdec_data *vd, void *bufstat,
|
||
|
uint32_t bufstat_size)
|
||
|
{
|
||
|
struct vdec_buffer_status *vdec_bufstat;
|
||
|
struct vdec_msg msg;
|
||
|
|
||
|
/* TODO: how do we signal the client? If they are waiting on a
|
||
|
* message in an ioctl, they may block forever */
|
||
|
if (bufstat_size != sizeof(struct vdec_buffer_status)) {
|
||
|
pr_warning("%s: msg size mismatch %d != %d\n", __func__,
|
||
|
bufstat_size, sizeof(struct vdec_buffer_status));
|
||
|
return;
|
||
|
}
|
||
|
vdec_bufstat = (struct vdec_buffer_status *)bufstat;
|
||
|
msg.id = VDEC_MSG_REUSEINPUTBUFFER;
|
||
|
msg.buf_id = vdec_bufstat->data;
|
||
|
vdec_put_msg(vd, &msg);
|
||
|
}
|
||
|
|
||
|
static void callback(void *data, int len, void *cookie)
|
||
|
{
|
||
|
struct vdec_data *vd = (struct vdec_data *)cookie;
|
||
|
uint32_t *tmp = (uint32_t *) data;
|
||
|
|
||
|
if (!vd->mem_initialized) {
|
||
|
pr_err("%s:memory not initialize but callback called!\n",
|
||
|
__func__);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
TRACE("vdec_async: tmp=0x%08x 0x%08x 0x%08x\n", tmp[0], tmp[1], tmp[2]);
|
||
|
switch (tmp[0]) {
|
||
|
case VDEC_ASYNCMSG_DECODE_DONE:
|
||
|
vdec_dcdone_handler(vd, &tmp[3], tmp[2]);
|
||
|
break;
|
||
|
case VDEC_ASYNCMSG_REUSE_FRAME:
|
||
|
vdec_reuseibuf_handler(vd, &tmp[3], tmp[2]);
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("%s: Unknown async message from DSP id=0x%08x sz=%u\n",
|
||
|
__func__, tmp[0], tmp[2]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int vdec_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
int ret;
|
||
|
int i;
|
||
|
struct vdec_msg_list *l;
|
||
|
struct vdec_data *vd;
|
||
|
struct dal_info version_info;
|
||
|
char *portname = NULL;
|
||
|
|
||
|
pr_info("q6vdec_open()\n");
|
||
|
mutex_lock(&vdec_ref_lock);
|
||
|
if (ref_cnt >= MAX_SUPPORTED_INSTANCES) {
|
||
|
pr_err("%s: Max allowed instances exceeded \n", __func__);
|
||
|
mutex_unlock(&vdec_ref_lock);
|
||
|
return -EBUSY;
|
||
|
}
|
||
|
ref_cnt++;
|
||
|
mutex_unlock(&vdec_ref_lock);
|
||
|
|
||
|
vd = kmalloc(sizeof(struct vdec_data), GFP_KERNEL);
|
||
|
if (!vd) {
|
||
|
pr_err("%s: kmalloc failed\n", __func__);
|
||
|
ret = -ENOMEM;
|
||
|
goto vdec_open_err_handle_vd;
|
||
|
}
|
||
|
file->private_data = vd;
|
||
|
|
||
|
vd->mem_initialized = 0;
|
||
|
INIT_LIST_HEAD(&vd->vdec_msg_list_head);
|
||
|
INIT_LIST_HEAD(&vd->vdec_msg_list_free);
|
||
|
INIT_LIST_HEAD(&vd->vdec_mem_list_head);
|
||
|
init_waitqueue_head(&vd->vdec_msg_evt);
|
||
|
|
||
|
spin_lock_init(&vd->vdec_list_lock);
|
||
|
spin_lock_init(&vd->vdec_mem_list_lock);
|
||
|
for (i = 0; i < VDEC_MSG_MAX; i++) {
|
||
|
l = kzalloc(sizeof(struct vdec_msg_list), GFP_KERNEL);
|
||
|
if (!l) {
|
||
|
pr_err("%s: kzalloc failed!\n", __func__);
|
||
|
ret = -ENOMEM;
|
||
|
goto vdec_open_err_handle_list;
|
||
|
}
|
||
|
list_add(&l->list, &vd->vdec_msg_list_free);
|
||
|
}
|
||
|
|
||
|
memset(&vd->streamDetails, 0, sizeof(struct videoStreamDetails));
|
||
|
|
||
|
mutex_lock(&vdec_ref_lock);
|
||
|
vdec_get_next_portanddevid(&vd->Q6deviceId, &portname);
|
||
|
mutex_unlock(&vdec_ref_lock);
|
||
|
|
||
|
if ((0 == vd->Q6deviceId) || (NULL == portname)) {
|
||
|
pr_err("%s: FATAL error portname %s or deviceId %d not picked properly\n",
|
||
|
__func__, portname, vd->Q6deviceId);
|
||
|
ret = -EIO;
|
||
|
goto vdec_open_err_handle_list;
|
||
|
} else {
|
||
|
pr_err("attaching on deviceid %x portname %s\n",
|
||
|
vd->Q6deviceId, portname);
|
||
|
vd->vdec_handle = dal_attach(vd->Q6deviceId,
|
||
|
portname, 1, callback, vd);
|
||
|
}
|
||
|
|
||
|
if (!vd->vdec_handle) {
|
||
|
pr_err("%s: failed to attach\n", __func__);
|
||
|
ret = -EIO;
|
||
|
goto vdec_open_err_handle_list;
|
||
|
}
|
||
|
ret = dal_call_f9(vd->vdec_handle, DAL_OP_INFO,
|
||
|
&version_info, sizeof(struct dal_info));
|
||
|
|
||
|
if (ret) {
|
||
|
pr_err("%s: failed to get version \n", __func__);
|
||
|
goto vdec_open_err_handle_version;
|
||
|
}
|
||
|
|
||
|
TRACE("q6vdec_open() interface version 0x%x\n", version_info.version);
|
||
|
if (vdec_check_version(VDEC_INTERFACE_VERSION,
|
||
|
version_info.version)) {
|
||
|
pr_err("%s: driver version mismatch !\n", __func__);
|
||
|
goto vdec_open_err_handle_version;
|
||
|
}
|
||
|
|
||
|
vd->running = 1;
|
||
|
prevent_sleep();
|
||
|
|
||
|
return 0;
|
||
|
vdec_open_err_handle_version:
|
||
|
dal_detach(vd->vdec_handle);
|
||
|
vdec_open_err_handle_list:
|
||
|
{
|
||
|
struct vdec_msg_list *l, *n;
|
||
|
list_for_each_entry_safe(l, n, &vd->vdec_msg_list_free, list) {
|
||
|
list_del(&l->list);
|
||
|
kfree(l);
|
||
|
}
|
||
|
}
|
||
|
vdec_open_err_handle_vd:
|
||
|
mutex_lock(&vdec_ref_lock);
|
||
|
vdec_freeup_portanddevid(vd->Q6deviceId);
|
||
|
ref_cnt--;
|
||
|
mutex_unlock(&vdec_ref_lock);
|
||
|
kfree(vd);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int vdec_release(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
int ret;
|
||
|
struct vdec_msg_list *l, *n;
|
||
|
struct vdec_mem_list *m, *k;
|
||
|
struct vdec_data *vd = file->private_data;
|
||
|
|
||
|
vd->running = 0;
|
||
|
wake_up_all(&vd->vdec_msg_evt);
|
||
|
|
||
|
if (!vd->close_decode)
|
||
|
vdec_close(vd, NULL);
|
||
|
|
||
|
ret = dal_detach(vd->vdec_handle);
|
||
|
if (ret)
|
||
|
printk(KERN_INFO "%s: failed to detach (%d)\n", __func__, ret);
|
||
|
|
||
|
list_for_each_entry_safe(l, n, &vd->vdec_msg_list_free, list) {
|
||
|
list_del(&l->list);
|
||
|
kfree(l);
|
||
|
}
|
||
|
|
||
|
list_for_each_entry_safe(l, n, &vd->vdec_msg_list_head, list) {
|
||
|
list_del(&l->list);
|
||
|
kfree(l);
|
||
|
}
|
||
|
|
||
|
list_for_each_entry_safe(m, k, &vd->vdec_mem_list_head, list) {
|
||
|
list_del(&m->list);
|
||
|
kfree(m);
|
||
|
}
|
||
|
mutex_lock(&vdec_ref_lock);
|
||
|
BUG_ON(ref_cnt <= 0);
|
||
|
ref_cnt--;
|
||
|
vdec_freeup_portanddevid(vd->Q6deviceId);
|
||
|
mutex_unlock(&vdec_ref_lock);
|
||
|
|
||
|
mutex_lock(&vdec_rm_lock);
|
||
|
vdec_rm_freeupResources(vd);
|
||
|
mutex_unlock(&vdec_rm_lock);
|
||
|
|
||
|
|
||
|
kfree(vd);
|
||
|
allow_sleep();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct file_operations vdec_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = vdec_open,
|
||
|
.release = vdec_release,
|
||
|
.unlocked_ioctl = vdec_ioctl,
|
||
|
};
|
||
|
|
||
|
static int __init vdec_init(void)
|
||
|
{
|
||
|
struct device *class_dev;
|
||
|
int rc = 0;
|
||
|
|
||
|
pm_qos_add_request(&pm_qos_req, PM_QOS_CPU_DMA_LATENCY,
|
||
|
PM_QOS_DEFAULT_VALUE);
|
||
|
wake_lock_init(&wakelock, WAKE_LOCK_SUSPEND, "vdec_suspend");
|
||
|
|
||
|
rc = alloc_chrdev_region(&vdec_device_no, 0, 1, "vdec");
|
||
|
if (rc < 0) {
|
||
|
pr_err("%s: alloc_chrdev_region failed %d\n", __func__, rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
driver_class = class_create(THIS_MODULE, "vdec");
|
||
|
if (IS_ERR(driver_class)) {
|
||
|
rc = -ENOMEM;
|
||
|
pr_err("%s: class_create failed %d\n", __func__, rc);
|
||
|
goto vdec_init_err_unregister_chrdev_region;
|
||
|
}
|
||
|
class_dev = device_create(driver_class, NULL,
|
||
|
vdec_device_no, NULL, "vdec");
|
||
|
if (!class_dev) {
|
||
|
pr_err("%s: class_device_create failed %d\n", __func__, rc);
|
||
|
rc = -ENOMEM;
|
||
|
goto vdec_init_err_class_destroy;
|
||
|
}
|
||
|
|
||
|
cdev_init(&vdec_cdev, &vdec_fops);
|
||
|
vdec_cdev.owner = THIS_MODULE;
|
||
|
rc = cdev_add(&vdec_cdev, MKDEV(MAJOR(vdec_device_no), 0), 1);
|
||
|
|
||
|
if (rc < 0) {
|
||
|
pr_err("%s: cdev_add failed %d\n", __func__, rc);
|
||
|
goto vdec_init_err_class_device_destroy;
|
||
|
}
|
||
|
|
||
|
memset(&deviceIdRegistry, 0, sizeof(deviceIdRegistry));
|
||
|
memset(&loadOnPorts, 0, sizeof(loadOnPorts));
|
||
|
numOfPorts = 0;
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
vdec_init_err_class_device_destroy:
|
||
|
device_destroy(driver_class, vdec_device_no);
|
||
|
vdec_init_err_class_destroy:
|
||
|
class_destroy(driver_class);
|
||
|
vdec_init_err_unregister_chrdev_region:
|
||
|
unregister_chrdev_region(vdec_device_no, 1);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static void __exit vdec_exit(void)
|
||
|
{
|
||
|
device_destroy(driver_class, vdec_device_no);
|
||
|
class_destroy(driver_class);
|
||
|
unregister_chrdev_region(vdec_device_no, 1);
|
||
|
}
|
||
|
|
||
|
MODULE_DESCRIPTION("video decoder driver for QSD platform");
|
||
|
MODULE_VERSION("2.00");
|
||
|
|
||
|
module_init(vdec_init);
|
||
|
module_exit(vdec_exit);
|