2882 lines
66 KiB
C
2882 lines
66 KiB
C
/* Copyright (c) 2009, 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.
|
|
*
|
|
*/
|
|
|
|
/* FIXME: management of mutexes */
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <mach/board.h>
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/list.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/android_pmem.h>
|
|
#include <linux/poll.h>
|
|
#include <media/msm_camera.h>
|
|
#include <mach/camera.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <linux/rtc.h>
|
|
DEFINE_MUTEX(hlist_mut);
|
|
|
|
#define MSM_MAX_CAMERA_SENSORS 5
|
|
|
|
#define ERR_USER_COPY(to) pr_err("%s(%d): copy %s user\n", \
|
|
__func__, __LINE__, ((to) ? "to" : "from"))
|
|
#define ERR_COPY_FROM_USER() ERR_USER_COPY(0)
|
|
#define ERR_COPY_TO_USER() ERR_USER_COPY(1)
|
|
|
|
static struct class *msm_class;
|
|
static dev_t msm_devno;
|
|
static LIST_HEAD(msm_sensors);
|
|
|
|
#define __CONTAINS(r, v, l, field) ({ \
|
|
typeof(r) __r = r; \
|
|
typeof(v) __v = v; \
|
|
typeof(v) __e = __v + l; \
|
|
int res = __v >= __r->field && \
|
|
__e <= __r->field + __r->len; \
|
|
res; \
|
|
})
|
|
|
|
#define CONTAINS(r1, r2, field) ({ \
|
|
typeof(r2) __r2 = r2; \
|
|
__CONTAINS(r1, __r2->field, __r2->len, field); \
|
|
})
|
|
|
|
#define IN_RANGE(r, v, field) ({ \
|
|
typeof(r) __r = r; \
|
|
typeof(v) __vv = v; \
|
|
int res = ((__vv >= __r->field) && \
|
|
(__vv < (__r->field + __r->len))); \
|
|
res; \
|
|
})
|
|
|
|
#define OVERLAPS(r1, r2, field) ({ \
|
|
typeof(r1) __r1 = r1; \
|
|
typeof(r2) __r2 = r2; \
|
|
typeof(__r2->field) __v = __r2->field; \
|
|
typeof(__v) __e = __v + __r2->len - 1; \
|
|
int res = (IN_RANGE(__r1, __v, field) || \
|
|
IN_RANGE(__r1, __e, field)); \
|
|
res; \
|
|
})
|
|
|
|
static inline void free_qcmd(struct msm_queue_cmd *qcmd)
|
|
{
|
|
if (!qcmd || !qcmd->on_heap)
|
|
return;
|
|
CDBG("%s qcmd->on_heap:%d\n",__func__,qcmd->on_heap);
|
|
if (!--qcmd->on_heap)
|
|
kfree(qcmd);
|
|
}
|
|
|
|
static void msm_queue_init(struct msm_device_queue *queue, const char *name)
|
|
{
|
|
spin_lock_init(&queue->lock);
|
|
queue->len = 0;
|
|
queue->max = 0;
|
|
queue->name = name;
|
|
INIT_LIST_HEAD(&queue->list);
|
|
init_waitqueue_head(&queue->wait);
|
|
}
|
|
|
|
static void msm_enqueue(struct msm_device_queue *queue,
|
|
struct list_head *entry)
|
|
{
|
|
unsigned long flags;
|
|
spin_lock_irqsave(&queue->lock, flags);
|
|
queue->len++;
|
|
|
|
if (queue->len > queue->max) {
|
|
queue->max = queue->len;
|
|
#if 0
|
|
pr_info("%s: queue %s new max is %d\n", __func__,
|
|
queue->name, queue->max);
|
|
#else
|
|
if(queue->max < 1024)
|
|
pr_info("%s: queue %s new max is %d\n", __func__,
|
|
queue->name, queue->max);
|
|
#endif
|
|
}
|
|
list_add_tail(entry, &queue->list);
|
|
wake_up(&queue->wait);
|
|
CDBG("%s: woke up %s\n", __func__, queue->name);
|
|
spin_unlock_irqrestore(&queue->lock, flags);
|
|
}
|
|
|
|
#define msm_dequeue(queue, member) ({ \
|
|
unsigned long flags; \
|
|
struct msm_device_queue *__q = (queue); \
|
|
struct msm_queue_cmd *qcmd = 0; \
|
|
spin_lock_irqsave(&__q->lock, flags); \
|
|
if (!list_empty(&__q->list)) { \
|
|
__q->len--; \
|
|
qcmd = list_first_entry(&__q->list, \
|
|
struct msm_queue_cmd, member); \
|
|
if (qcmd) { \
|
|
list_del_init(&qcmd->member); \
|
|
} \
|
|
} \
|
|
spin_unlock_irqrestore(&__q->lock, flags); \
|
|
qcmd; \
|
|
})
|
|
|
|
|
|
#define msm_queue_drain(queue, member) do { \
|
|
unsigned long flags; \
|
|
struct msm_device_queue *__q = (queue); \
|
|
struct msm_queue_cmd *qcmd; \
|
|
spin_lock_irqsave(&__q->lock, flags); \
|
|
CDBG("%s: draining queue %s\n", __func__, __q->name); \
|
|
while (!list_empty(&__q->list)) { \
|
|
qcmd = list_first_entry(&__q->list, \
|
|
struct msm_queue_cmd, member); \
|
|
list_del_init(&qcmd->member); \
|
|
free_qcmd(qcmd); \
|
|
}; \
|
|
__q->len = 0; \
|
|
spin_unlock_irqrestore(&__q->lock, flags); \
|
|
} while(0)
|
|
|
|
static int check_overlap(struct hlist_head *ptype,
|
|
unsigned long paddr,
|
|
unsigned long len)
|
|
{
|
|
struct msm_pmem_region *region;
|
|
struct msm_pmem_region t = { .paddr = paddr, .len = len };
|
|
struct hlist_node *node;
|
|
|
|
hlist_for_each_entry(region, node, ptype, list) {
|
|
if (CONTAINS(region, &t, paddr) ||
|
|
CONTAINS(&t, region, paddr) ||
|
|
OVERLAPS(region, &t, paddr)) {
|
|
printk(KERN_ERR
|
|
" region (PHYS %p len %ld)"
|
|
" clashes with registered region"
|
|
" (paddr %p len %ld)\n",
|
|
(void *)t.paddr, t.len,
|
|
(void *)region->paddr, region->len);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_pmem_info(struct msm_pmem_info *info, int len)
|
|
{
|
|
if (info->offset & (PAGE_SIZE - 1)) {
|
|
pr_err("%s: pmem offset is not page-aligned\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
if (info->offset < len &&
|
|
info->offset + info->len <= len &&
|
|
info->y_off < len &&
|
|
info->cbcr_off < len)
|
|
return 0;
|
|
|
|
error:
|
|
pr_err("%s: check failed: off %d len %d y %d cbcr %d (total len %d)\n",
|
|
__func__,
|
|
info->offset,
|
|
info->len,
|
|
info->y_off,
|
|
info->cbcr_off,
|
|
len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int msm_pmem_table_add(struct hlist_head *ptype,
|
|
struct msm_pmem_info *info)
|
|
{
|
|
struct file *file;
|
|
unsigned long paddr;
|
|
unsigned long kvstart;
|
|
unsigned long len;
|
|
int rc;
|
|
struct msm_pmem_region *region;
|
|
|
|
rc = get_pmem_file(info->fd, &paddr, &kvstart, &len, &file);
|
|
if (rc < 0) {
|
|
pr_err("%s: get_pmem_file fd %d error %d\n",
|
|
__func__,
|
|
info->fd, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (!info->len)
|
|
info->len = len;
|
|
|
|
rc = check_pmem_info(info, len);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
paddr += info->offset;
|
|
kvstart += info->offset;
|
|
len = info->len;
|
|
|
|
if (check_overlap(ptype, paddr, len) < 0)
|
|
return -EINVAL;
|
|
|
|
CDBG("%s: type %d, paddr 0x%lx, vaddr 0x%lx\n",
|
|
__func__,
|
|
info->type, paddr, (unsigned long)info->vaddr);
|
|
|
|
region = kzalloc(sizeof(struct msm_pmem_region), GFP_KERNEL);
|
|
if (!region)
|
|
return -ENOMEM;
|
|
|
|
INIT_HLIST_NODE(®ion->list);
|
|
|
|
region->paddr = paddr;
|
|
region->kvaddr = kvstart;
|
|
region->len = len;
|
|
region->file = file;
|
|
memcpy(®ion->info, info, sizeof(region->info));
|
|
|
|
hlist_add_head(&(region->list), ptype);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* return of 0 means failure */
|
|
static uint8_t msm_pmem_region_lookup(struct hlist_head *ptype,
|
|
int pmem_type, struct msm_pmem_region *reg, uint8_t maxcount)
|
|
{
|
|
struct msm_pmem_region *region;
|
|
struct msm_pmem_region *regptr;
|
|
struct hlist_node *node, *n;
|
|
|
|
uint8_t rc = 0;
|
|
|
|
regptr = reg;
|
|
mutex_lock(&hlist_mut);
|
|
hlist_for_each_entry_safe(region, node, n, ptype, list) {
|
|
if (region->info.type == pmem_type &&
|
|
region->info.vfe_can_write) {
|
|
*regptr = *region;
|
|
rc += 1;
|
|
if (rc >= maxcount)
|
|
break;
|
|
regptr++;
|
|
}
|
|
}
|
|
mutex_unlock(&hlist_mut);
|
|
return rc;
|
|
}
|
|
|
|
static int msm_pmem_frame_ptov_lookup(struct msm_sync *sync,
|
|
unsigned long pyaddr, unsigned long pcbcraddr,
|
|
struct msm_pmem_region **pmem_region,
|
|
int take_from_vfe)
|
|
{
|
|
struct hlist_node *node, *n;
|
|
struct msm_pmem_region *region;
|
|
|
|
hlist_for_each_entry_safe(region, node, n, &sync->pmem_frames, list) {
|
|
if (pyaddr == (region->paddr + region->info.y_off) &&
|
|
#ifndef CONFIG_ARCH_MSM7225
|
|
pcbcraddr == (region->paddr +
|
|
region->info.cbcr_off) &&
|
|
#endif
|
|
region->info.vfe_can_write) {
|
|
*pmem_region = region;
|
|
region->info.vfe_can_write = !take_from_vfe;
|
|
#ifdef CONFIG_ARCH_MSM7225
|
|
if (pcbcraddr != (region->paddr + region->info.cbcr_off)) {
|
|
pr_err("%s cbcr addr = %lx, NOT EQUAL to region->paddr + region->info.cbcr_off = %lx\n",
|
|
__func__, pcbcraddr, region->paddr + region->info.cbcr_off);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static unsigned long msm_pmem_stats_ptov_lookup(struct msm_sync *sync,
|
|
unsigned long addr, int *fd)
|
|
{
|
|
struct msm_pmem_region *region;
|
|
struct hlist_node *node, *n;
|
|
|
|
hlist_for_each_entry_safe(region, node, n, &sync->pmem_stats, list) {
|
|
if (addr == region->paddr && region->info.vfe_can_write) {
|
|
/* offset since we could pass vaddr inside a
|
|
* registered pmem buffer */
|
|
*fd = region->info.fd;
|
|
region->info.vfe_can_write = 0;
|
|
return (unsigned long)(region->info.vaddr);
|
|
}
|
|
}
|
|
#if 1
|
|
printk("msm_pmem_stats_ptov_lookup: lookup vaddr..\n");
|
|
hlist_for_each_entry_safe(region, node, n, &sync->pmem_stats, list) {
|
|
if (addr == (unsigned long)(region->info.vaddr)) {
|
|
/* offset since we could pass vaddr inside a
|
|
* registered pmem buffer */
|
|
*fd = region->info.fd;
|
|
region->info.vfe_can_write = 0;
|
|
return (unsigned long)(region->info.vaddr);
|
|
}
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long msm_pmem_frame_vtop_lookup(struct msm_sync *sync,
|
|
unsigned long buffer,
|
|
uint32_t yoff, uint32_t cbcroff, int fd)
|
|
{
|
|
struct msm_pmem_region *region;
|
|
struct hlist_node *node, *n;
|
|
|
|
hlist_for_each_entry_safe(region,
|
|
node, n, &sync->pmem_frames, list) {
|
|
if (((unsigned long)(region->info.vaddr) == buffer) &&
|
|
(region->info.y_off == yoff) &&
|
|
(region->info.cbcr_off == cbcroff) &&
|
|
(region->info.fd == fd) &&
|
|
(region->info.vfe_can_write == 0)) {
|
|
region->info.vfe_can_write = 1;
|
|
return region->paddr;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long msm_pmem_stats_vtop_lookup(
|
|
struct msm_sync *sync,
|
|
unsigned long buffer,
|
|
int fd)
|
|
{
|
|
struct msm_pmem_region *region;
|
|
struct hlist_node *node, *n;
|
|
|
|
hlist_for_each_entry_safe(region, node, n, &sync->pmem_stats, list) {
|
|
if (((unsigned long)(region->info.vaddr) == buffer) &&
|
|
(region->info.fd == fd) &&
|
|
region->info.vfe_can_write == 0) {
|
|
region->info.vfe_can_write = 1;
|
|
return region->paddr;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __msm_pmem_table_del(struct msm_sync *sync,
|
|
struct msm_pmem_info *pinfo)
|
|
{
|
|
int rc = 0;
|
|
struct msm_pmem_region *region;
|
|
struct hlist_node *node, *n;
|
|
|
|
switch (pinfo->type) {
|
|
#ifndef CONFIG_720P_CAMERA
|
|
case MSM_PMEM_OUTPUT1:
|
|
case MSM_PMEM_OUTPUT2:
|
|
#else
|
|
case MSM_PMEM_VIDEO:
|
|
case MSM_PMEM_PREVIEW:
|
|
#endif
|
|
case MSM_PMEM_THUMBNAIL:
|
|
case MSM_PMEM_MAINIMG:
|
|
case MSM_PMEM_RAW_MAINIMG:
|
|
hlist_for_each_entry_safe(region, node, n,
|
|
&sync->pmem_frames, list) {
|
|
|
|
if (pinfo->type == region->info.type &&
|
|
pinfo->vaddr == region->info.vaddr &&
|
|
pinfo->fd == region->info.fd) {
|
|
hlist_del(node);
|
|
put_pmem_file(region->file);
|
|
kfree(region);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MSM_PMEM_AEC_AWB:
|
|
case MSM_PMEM_AF:
|
|
hlist_for_each_entry_safe(region, node, n,
|
|
&sync->pmem_stats, list) {
|
|
|
|
if (pinfo->type == region->info.type &&
|
|
pinfo->vaddr == region->info.vaddr &&
|
|
pinfo->fd == region->info.fd) {
|
|
hlist_del(node);
|
|
put_pmem_file(region->file);
|
|
kfree(region);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int msm_pmem_table_del(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
struct msm_pmem_info info;
|
|
|
|
if (copy_from_user(&info, arg, sizeof(info))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
return __msm_pmem_table_del(sync, &info);
|
|
}
|
|
|
|
static int __msm_get_frame(struct msm_sync *sync,
|
|
struct msm_frame *frame)
|
|
{
|
|
int rc = 0;
|
|
|
|
struct msm_pmem_region *region;
|
|
struct msm_queue_cmd *qcmd = NULL;
|
|
struct msm_vfe_resp *vdata;
|
|
struct msm_vfe_phy_info *pphy;
|
|
|
|
if (&sync->frame_q) {
|
|
qcmd = msm_dequeue(&sync->frame_q, list_frame);
|
|
}
|
|
|
|
if (!qcmd) {
|
|
pr_err("%s: no preview frame.\n", __func__);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
vdata = (struct msm_vfe_resp *)(qcmd->command);
|
|
pphy = &vdata->phy;
|
|
|
|
rc = msm_pmem_frame_ptov_lookup(sync,
|
|
pphy->y_phy,
|
|
pphy->cbcr_phy,
|
|
®ion,
|
|
1); /* give frame to user space */
|
|
|
|
if (rc < 0) {
|
|
pr_err("%s: cannot get frame, invalid lookup address "
|
|
"y %x cbcr %x\n",
|
|
__func__,
|
|
pphy->y_phy,
|
|
pphy->cbcr_phy);
|
|
goto err;
|
|
}
|
|
|
|
frame->buffer = (unsigned long)region->info.vaddr;
|
|
frame->y_off = region->info.y_off;
|
|
frame->cbcr_off = region->info.cbcr_off;
|
|
frame->fd = region->info.fd;
|
|
frame->path = vdata->phy.output_id;
|
|
CDBG("%s: y %x, cbcr %x, qcmd %x, virt_addr %x\n",
|
|
__func__,
|
|
pphy->y_phy, pphy->cbcr_phy, (int) qcmd, (int) frame->buffer);
|
|
|
|
err:
|
|
free_qcmd(qcmd);
|
|
return rc;
|
|
}
|
|
|
|
static int msm_get_frame(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
int rc = 0;
|
|
struct msm_frame frame;
|
|
|
|
if (copy_from_user(&frame,
|
|
arg,
|
|
sizeof(struct msm_frame))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
rc = __msm_get_frame(sync, &frame);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (sync->croplen) {
|
|
if (frame.croplen != sync->croplen) {
|
|
pr_err("%s: invalid frame croplen %d,"
|
|
"expecting %d\n",
|
|
__func__,
|
|
frame.croplen,
|
|
sync->croplen);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (copy_to_user((void *)frame.cropinfo,
|
|
sync->cropinfo,
|
|
sync->croplen)) {
|
|
ERR_COPY_TO_USER();
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
if (copy_to_user((void *)arg,
|
|
&frame, sizeof(struct msm_frame))) {
|
|
ERR_COPY_TO_USER();
|
|
rc = -EFAULT;
|
|
}
|
|
|
|
CDBG("%s: got frame\n", __func__);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int msm_enable_vfe(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
int rc = -EIO;
|
|
struct camera_enable_cmd cfg;
|
|
|
|
if (copy_from_user(&cfg,
|
|
arg,
|
|
sizeof(struct camera_enable_cmd))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (sync->vfefn.vfe_enable)
|
|
rc = sync->vfefn.vfe_enable(&cfg);
|
|
|
|
CDBG("%s: rc %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
static int msm_disable_vfe(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
int rc = -EIO;
|
|
struct camera_enable_cmd cfg;
|
|
|
|
if (copy_from_user(&cfg,
|
|
arg,
|
|
sizeof(struct camera_enable_cmd))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (sync->vfefn.vfe_disable)
|
|
rc = sync->vfefn.vfe_disable(&cfg, NULL);
|
|
|
|
CDBG("%s: rc %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
static struct msm_queue_cmd *__msm_control(struct msm_sync *sync,
|
|
struct msm_device_queue *queue,
|
|
struct msm_queue_cmd *qcmd,
|
|
int timeout)
|
|
{
|
|
int rc;
|
|
|
|
msm_enqueue(&sync->event_q, &qcmd->list_config);
|
|
|
|
if (!queue)
|
|
return NULL;
|
|
|
|
/* wait for config status */
|
|
rc = wait_event_interruptible_timeout(
|
|
queue->wait,
|
|
!list_empty_careful(&queue->list),
|
|
timeout);
|
|
if (list_empty_careful(&queue->list)) {
|
|
if (!rc)
|
|
rc = -ETIMEDOUT;
|
|
if (rc < 0) {
|
|
pr_err("%s: wait_event error %d\n", __func__, rc);
|
|
/* qcmd may be still on the event_q, in which case we
|
|
* need to remove it. Alternatively, qcmd may have
|
|
* been dequeued and processed already, in which case
|
|
* the list removal will be a no-op.
|
|
*/
|
|
list_del_init(&qcmd->list_config);
|
|
return ERR_PTR(rc);
|
|
}
|
|
}
|
|
|
|
qcmd = msm_dequeue(queue, list_control);
|
|
BUG_ON(!qcmd);
|
|
|
|
return qcmd;
|
|
}
|
|
|
|
static struct msm_queue_cmd *__msm_control_nb(struct msm_sync *sync,
|
|
struct msm_queue_cmd *qcmd_to_copy)
|
|
{
|
|
/* Since this is a non-blocking command, we cannot use qcmd_to_copy and
|
|
* its data, since they are on the stack. We replicate them on the heap
|
|
* and mark them on_heap so that they get freed when the config thread
|
|
* dequeues them.
|
|
*/
|
|
|
|
struct msm_ctrl_cmd *udata;
|
|
struct msm_ctrl_cmd *udata_to_copy = qcmd_to_copy->command;
|
|
|
|
struct msm_queue_cmd *qcmd =
|
|
kzalloc(sizeof(*qcmd_to_copy) +
|
|
sizeof(*udata_to_copy) +
|
|
udata_to_copy->length,
|
|
GFP_KERNEL);
|
|
if (!qcmd) {
|
|
pr_err("%s: out of memory\n", __func__);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
*qcmd = *qcmd_to_copy;
|
|
udata = qcmd->command = qcmd + 1;
|
|
memcpy(udata, udata_to_copy, sizeof(*udata));
|
|
udata->value = udata + 1;
|
|
memcpy(udata->value, udata_to_copy->value, udata_to_copy->length);
|
|
|
|
qcmd->on_heap = 1;
|
|
|
|
/* qcmd_resp will be set to NULL */
|
|
return __msm_control(sync, NULL, qcmd, 0);
|
|
}
|
|
|
|
static int msm_control(struct msm_control_device *ctrl_pmsm,
|
|
int block,
|
|
void __user *arg)
|
|
{
|
|
int rc = 0;
|
|
|
|
struct msm_sync *sync = ctrl_pmsm->pmsm->sync;
|
|
void __user *uptr;
|
|
struct msm_ctrl_cmd udata;
|
|
struct msm_queue_cmd qcmd;
|
|
struct msm_queue_cmd *qcmd_resp = NULL;
|
|
uint8_t data[50];
|
|
|
|
if (copy_from_user(&udata, arg, sizeof(struct msm_ctrl_cmd))) {
|
|
ERR_COPY_FROM_USER();
|
|
rc = -EFAULT;
|
|
goto end;
|
|
}
|
|
|
|
uptr = udata.value;
|
|
udata.value = data;
|
|
|
|
qcmd.on_heap = 0;
|
|
qcmd.type = MSM_CAM_Q_CTRL;
|
|
qcmd.command = &udata;
|
|
|
|
if (udata.length) {
|
|
if (udata.length > sizeof(data)) {
|
|
pr_err("%s: user data too large (%d, max is %d)\n",
|
|
__func__,
|
|
udata.length,
|
|
sizeof(data));
|
|
rc = -EIO;
|
|
goto end;
|
|
}
|
|
if (copy_from_user(udata.value, uptr, udata.length)) {
|
|
ERR_COPY_FROM_USER();
|
|
rc = -EFAULT;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (unlikely(!block)) {
|
|
qcmd_resp = __msm_control_nb(sync, &qcmd);
|
|
goto end;
|
|
}
|
|
|
|
qcmd_resp = __msm_control(sync,
|
|
&ctrl_pmsm->ctrl_q,
|
|
&qcmd, MAX_SCHEDULE_TIMEOUT);
|
|
|
|
if (!qcmd_resp || IS_ERR(qcmd_resp)) {
|
|
/* Do not free qcmd_resp here. If the config thread read it,
|
|
* then it has already been freed, and we timed out because
|
|
* we did not receive a MSM_CAM_IOCTL_CTRL_CMD_DONE. If the
|
|
* config thread itself is blocked and not dequeueing commands,
|
|
* then it will either eventually unblock and process them,
|
|
* or when it is killed, qcmd will be freed in
|
|
* msm_release_config.
|
|
*/
|
|
rc = PTR_ERR(qcmd_resp);
|
|
qcmd_resp = NULL;
|
|
goto end;
|
|
}
|
|
|
|
if (qcmd_resp->command) {
|
|
udata = *(struct msm_ctrl_cmd *)qcmd_resp->command;
|
|
if (udata.length > 0) {
|
|
if (copy_to_user(uptr,
|
|
udata.value,
|
|
udata.length)) {
|
|
ERR_COPY_TO_USER();
|
|
rc = -EFAULT;
|
|
goto end;
|
|
}
|
|
}
|
|
udata.value = uptr;
|
|
|
|
if (copy_to_user((void *)arg, &udata,
|
|
sizeof(struct msm_ctrl_cmd))) {
|
|
ERR_COPY_TO_USER();
|
|
rc = -EFAULT;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
end:
|
|
free_qcmd(qcmd_resp);
|
|
CDBG("%s: rc %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Divert frames for post-processing by delivering them to the config thread;
|
|
* when post-processing is done, it will return the frame to the frame thread.
|
|
*/
|
|
static int msm_divert_frame(struct msm_sync *sync,
|
|
struct msm_vfe_resp *data,
|
|
struct msm_stats_event_ctrl *se)
|
|
{
|
|
struct msm_pmem_region *region;
|
|
struct msm_postproc buf;
|
|
int rc;
|
|
|
|
pr_info("%s: preview PP sync->pp_mask %d\n", __func__, sync->pp_mask);
|
|
|
|
if (!(sync->pp_mask & PP_PREV)) {
|
|
pr_err("%s: diverting preview frame but not in PP_PREV!\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = msm_pmem_frame_ptov_lookup(sync,
|
|
data->phy.y_phy,
|
|
data->phy.cbcr_phy,
|
|
®ion,
|
|
0); /* vfe can still write to frame */
|
|
if (rc < 0) {
|
|
CDBG("%s: msm_pmem_frame_ptov_lookup failed\n", __func__);
|
|
return rc;
|
|
}
|
|
|
|
buf.fmain.buffer = (unsigned long)region->info.vaddr;
|
|
buf.fmain.y_off = region->info.y_off;
|
|
buf.fmain.cbcr_off = region->info.cbcr_off;
|
|
buf.fmain.fd = region->info.fd;
|
|
|
|
CDBG("%s: buf %ld fd %d\n",
|
|
__func__, buf.fmain.buffer,
|
|
buf.fmain.fd);
|
|
if (copy_to_user((void *)(se->stats_event.data),
|
|
&(buf.fmain),
|
|
sizeof(struct msm_frame))) {
|
|
ERR_COPY_TO_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_divert_snapshot(struct msm_sync *sync,
|
|
struct msm_vfe_resp *data,
|
|
struct msm_stats_event_ctrl *se)
|
|
{
|
|
struct msm_postproc buf;
|
|
struct msm_pmem_region region;
|
|
|
|
CDBG("%s: preview PP sync->pp_mask %d\n", __func__, sync->pp_mask);
|
|
|
|
if (!(sync->pp_mask & (PP_SNAP|PP_RAW_SNAP))) {
|
|
pr_err("%s: diverting snapshot but not in PP_SNAP!\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(®ion, 0, sizeof(region));
|
|
buf.fmnum = msm_pmem_region_lookup(&sync->pmem_frames,
|
|
MSM_PMEM_MAINIMG,
|
|
®ion, 1);
|
|
if (buf.fmnum == 1) {
|
|
buf.fmain.buffer = (uint32_t)region.info.vaddr;
|
|
buf.fmain.y_off = region.info.y_off;
|
|
buf.fmain.cbcr_off = region.info.cbcr_off;
|
|
buf.fmain.fd = region.info.fd;
|
|
} else {
|
|
if (buf.fmnum > 1)
|
|
pr_err("%s: MSM_PMEM_MAINIMG lookup found %d\n",
|
|
__func__, buf.fmnum);
|
|
buf.fmnum = msm_pmem_region_lookup(&sync->pmem_frames,
|
|
MSM_PMEM_RAW_MAINIMG,
|
|
®ion, 1);
|
|
if (buf.fmnum == 1) {
|
|
buf.fmain.path = MSM_FRAME_PREV_2;
|
|
buf.fmain.buffer = (uint32_t)region.info.vaddr;
|
|
buf.fmain.fd = region.info.fd;
|
|
} else {
|
|
pr_err("%s: pmem lookup fail (found %d)\n",
|
|
__func__, buf.fmnum);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
CDBG("%s: snapshot copy_to_user!\n", __func__);
|
|
if (copy_to_user((void *)(se->stats_event.data), &buf, sizeof(buf))) {
|
|
ERR_COPY_TO_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_get_stats(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
int timeout;
|
|
int rc = 0;
|
|
|
|
struct msm_stats_event_ctrl se;
|
|
|
|
struct msm_queue_cmd *qcmd = NULL;
|
|
struct msm_ctrl_cmd *ctrl = NULL;
|
|
struct msm_vfe_resp *data = NULL;
|
|
struct msm_stats_buf stats;
|
|
|
|
if (copy_from_user(&se, arg,
|
|
sizeof(struct msm_stats_event_ctrl))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
timeout = (int)se.timeout_ms;
|
|
|
|
CDBG("%s: timeout %d\n", __func__, timeout);
|
|
rc = wait_event_interruptible_timeout(
|
|
sync->event_q.wait,
|
|
!list_empty_careful(&sync->event_q.list),
|
|
msecs_to_jiffies(timeout));
|
|
if (list_empty_careful(&sync->event_q.list)) {
|
|
if (rc == 0)
|
|
rc = -ETIMEDOUT;
|
|
if (rc < 0) {
|
|
pr_err("\n%s: error %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
CDBG("%s: returned from wait: %d\n", __func__, rc);
|
|
|
|
rc = 0;
|
|
|
|
qcmd = msm_dequeue(&sync->event_q, list_config);
|
|
BUG_ON(!qcmd);
|
|
|
|
/* HTC: check qcmd */
|
|
if (!qcmd) {
|
|
rc = -EFAULT;
|
|
pr_err("%s: qcmd is NULL, rc %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
CDBG("%s: received from DSP %d\n", __func__, qcmd->type);
|
|
|
|
switch (qcmd->type) {
|
|
case MSM_CAM_Q_VFE_EVT:
|
|
case MSM_CAM_Q_VFE_MSG:
|
|
data = (struct msm_vfe_resp *)(qcmd->command);
|
|
|
|
/* adsp event and message */
|
|
se.resptype = MSM_CAM_RESP_STAT_EVT_MSG;
|
|
|
|
/* 0 - msg from aDSP, 1 - event from mARM */
|
|
se.stats_event.type = data->evt_msg.type;
|
|
se.stats_event.msg_id = data->evt_msg.msg_id;
|
|
se.stats_event.len = data->evt_msg.len;
|
|
|
|
CDBG("%s: qcmd->type %d length %d msd_id %d\n", __func__,
|
|
qcmd->type,
|
|
se.stats_event.len,
|
|
se.stats_event.msg_id);
|
|
|
|
if ((data->type == VFE_MSG_STATS_AF) ||
|
|
(data->type == VFE_MSG_STATS_WE)) {
|
|
|
|
stats.buffer =
|
|
msm_pmem_stats_ptov_lookup(sync,
|
|
data->phy.sbuf_phy,
|
|
&(stats.fd));
|
|
if (!stats.buffer) {
|
|
pr_err("%s: msm_pmem_stats_ptov_lookup error, addr = %x\n",
|
|
__func__,data->phy.sbuf_phy);
|
|
#if 1
|
|
se.resptype = MSM_CAM_RESP_MAX;
|
|
#else
|
|
rc = -EINVAL;
|
|
goto failure;
|
|
#endif
|
|
}
|
|
|
|
if (copy_to_user((void *)(se.stats_event.data),
|
|
&stats,
|
|
sizeof(struct msm_stats_buf))) {
|
|
ERR_COPY_TO_USER();
|
|
rc = -EFAULT;
|
|
goto failure;
|
|
}
|
|
} else if ((data->evt_msg.len > 0) &&
|
|
(data->type == VFE_MSG_GENERAL)) {
|
|
if (copy_to_user((void *)(se.stats_event.data),
|
|
data->evt_msg.data,
|
|
data->evt_msg.len)) {
|
|
ERR_COPY_TO_USER();
|
|
rc = -EFAULT;
|
|
goto failure;
|
|
}
|
|
} else {
|
|
#ifndef CONFIG_720P_CAMERA
|
|
if ((sync->pp_mask & PP_PREV) &&
|
|
(data->type == VFE_MSG_OUTPUT1 ||
|
|
data->type == VFE_MSG_OUTPUT2))
|
|
rc = msm_divert_frame(sync, data, &se);
|
|
else if ((sync->pp_mask & (PP_SNAP|PP_RAW_SNAP)) &&
|
|
data->type == VFE_MSG_SNAPSHOT)
|
|
rc = msm_divert_snapshot(sync,
|
|
data, &se);
|
|
#else
|
|
if ((sync->pp_mask & PP_PREV) &&
|
|
(data->type == VFE_MSG_OUTPUT_P))
|
|
rc = msm_divert_frame(sync, data, &se);
|
|
else if ((sync->pp_mask & (PP_SNAP|PP_RAW_SNAP)) &&
|
|
(data->type == VFE_MSG_SNAPSHOT ||
|
|
data->type == VFE_MSG_OUTPUT_T ||
|
|
data->type == VFE_MSG_OUTPUT_S))
|
|
rc = msm_divert_snapshot(sync,
|
|
data, &se);
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case MSM_CAM_Q_CTRL:
|
|
/* control command from control thread */
|
|
ctrl = (struct msm_ctrl_cmd *)(qcmd->command);
|
|
|
|
CDBG("%s: qcmd->type %d length %d\n", __func__,
|
|
qcmd->type, ctrl->length);
|
|
|
|
if (ctrl->length > 0) {
|
|
if (copy_to_user((void *)(se.ctrl_cmd.value),
|
|
ctrl->value,
|
|
ctrl->length)) {
|
|
ERR_COPY_TO_USER();
|
|
rc = -EFAULT;
|
|
goto failure;
|
|
}
|
|
}
|
|
|
|
se.resptype = MSM_CAM_RESP_CTRL;
|
|
|
|
/* what to control */
|
|
se.ctrl_cmd.type = ctrl->type;
|
|
se.ctrl_cmd.length = ctrl->length;
|
|
se.ctrl_cmd.resp_fd = ctrl->resp_fd;
|
|
break;
|
|
|
|
case MSM_CAM_Q_V4L2_REQ:
|
|
/* control command from v4l2 client */
|
|
ctrl = (struct msm_ctrl_cmd *)(qcmd->command);
|
|
|
|
CDBG("%s: qcmd->type %d len %d\n", __func__, qcmd->type, ctrl->length);
|
|
|
|
if (ctrl->length > 0) {
|
|
if (copy_to_user((void *)(se.ctrl_cmd.value),
|
|
ctrl->value, ctrl->length)) {
|
|
ERR_COPY_TO_USER();
|
|
rc = -EFAULT;
|
|
goto failure;
|
|
}
|
|
}
|
|
|
|
/* 2 tells config thread this is v4l2 request */
|
|
se.resptype = MSM_CAM_RESP_V4L2;
|
|
|
|
/* what to control */
|
|
se.ctrl_cmd.type = ctrl->type;
|
|
se.ctrl_cmd.length = ctrl->length;
|
|
break;
|
|
|
|
default:
|
|
rc = -EFAULT;
|
|
goto failure;
|
|
} /* switch qcmd->type */
|
|
|
|
if (copy_to_user((void *)arg, &se, sizeof(se))) {
|
|
ERR_COPY_TO_USER();
|
|
rc = -EFAULT;
|
|
goto failure;
|
|
}
|
|
|
|
failure:
|
|
free_qcmd(qcmd);
|
|
|
|
CDBG("%s: %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
static int msm_ctrl_cmd_done(struct msm_control_device *ctrl_pmsm,
|
|
void __user *arg)
|
|
{
|
|
void __user *uptr;
|
|
struct msm_queue_cmd *qcmd = &ctrl_pmsm->qcmd;
|
|
struct msm_ctrl_cmd *command = &ctrl_pmsm->ctrl;
|
|
|
|
if (copy_from_user(command, arg, sizeof(*command))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
qcmd->on_heap = 0;
|
|
qcmd->command = command;
|
|
uptr = command->value;
|
|
|
|
if (command->length > 0) {
|
|
command->value = ctrl_pmsm->ctrl_data;
|
|
if (command->length > sizeof(ctrl_pmsm->ctrl_data)) {
|
|
pr_err("%s: user data %d is too big (max %d)\n",
|
|
__func__, command->length,
|
|
sizeof(ctrl_pmsm->ctrl_data));
|
|
return -EINVAL;
|
|
}
|
|
if (copy_from_user(command->value,
|
|
uptr,
|
|
command->length)) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
} else
|
|
command->value = NULL;
|
|
|
|
CDBG("%s: end\n", __func__);
|
|
|
|
/* wake up control thread */
|
|
msm_enqueue(&ctrl_pmsm->ctrl_q, &qcmd->list_control);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_config_vfe(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
struct msm_vfe_cfg_cmd cfgcmd;
|
|
struct msm_pmem_region region[8];
|
|
struct axidata axi_data;
|
|
|
|
if (!sync->vfefn.vfe_config) {
|
|
pr_err("%s: no vfe_config!\n", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
if (copy_from_user(&cfgcmd, arg, sizeof(cfgcmd))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
memset(&axi_data, 0, sizeof(axi_data));
|
|
|
|
CDBG("%s: cmd_type %d\n", __func__, cfgcmd.cmd_type);
|
|
|
|
switch (cfgcmd.cmd_type) {
|
|
case CMD_STATS_ENABLE:
|
|
axi_data.bufnum1 =
|
|
msm_pmem_region_lookup(&sync->pmem_stats,
|
|
MSM_PMEM_AEC_AWB, ®ion[0],
|
|
NUM_WB_EXP_STAT_OUTPUT_BUFFERS);
|
|
|
|
/* HTC: check axi_data.bufnum1 if out of bound of "region" array */
|
|
if (!axi_data.bufnum1 || axi_data.bufnum1 >=
|
|
(sizeof(region)/sizeof(struct msm_pmem_region))) {
|
|
pr_err("%s %d: pmem region lookup error or out of bound\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
axi_data.bufnum2 =
|
|
msm_pmem_region_lookup(&sync->pmem_stats,
|
|
MSM_PMEM_AF, ®ion[axi_data.bufnum1],
|
|
NUM_AF_STAT_OUTPUT_BUFFERS);
|
|
if (!axi_data.bufnum1 || !axi_data.bufnum2) {
|
|
pr_err("%s: pmem region lookup error\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
axi_data.region = ®ion[0];
|
|
return sync->vfefn.vfe_config(&cfgcmd, &axi_data);
|
|
case CMD_STATS_AF_ENABLE:
|
|
axi_data.bufnum1 =
|
|
msm_pmem_region_lookup(&sync->pmem_stats,
|
|
MSM_PMEM_AF, ®ion[0],
|
|
NUM_AF_STAT_OUTPUT_BUFFERS);
|
|
if (!axi_data.bufnum1) {
|
|
pr_err("%s %d: pmem region lookup error\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
axi_data.region = ®ion[0];
|
|
return sync->vfefn.vfe_config(&cfgcmd, &axi_data);
|
|
|
|
case CMD_STATS_AEC_AWB_ENABLE:
|
|
axi_data.bufnum1 =
|
|
msm_pmem_region_lookup(&sync->pmem_stats,
|
|
MSM_PMEM_AEC_AWB, ®ion[0],
|
|
NUM_WB_EXP_STAT_OUTPUT_BUFFERS);
|
|
if (!axi_data.bufnum1) {
|
|
pr_err("%s %d: pmem region lookup error\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
axi_data.region = ®ion[0];
|
|
return sync->vfefn.vfe_config(&cfgcmd, &axi_data);
|
|
case CMD_GENERAL:
|
|
case CMD_STATS_DISABLE:
|
|
return sync->vfefn.vfe_config(&cfgcmd, NULL);
|
|
default:
|
|
pr_err("%s: unknown command type %d\n",
|
|
__func__, cfgcmd.cmd_type);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int msm_frame_axi_cfg(struct msm_sync *sync,
|
|
struct msm_vfe_cfg_cmd *cfgcmd)
|
|
{
|
|
int rc = -EIO;
|
|
struct axidata axi_data;
|
|
void *data = &axi_data;
|
|
struct msm_pmem_region region[8];
|
|
int pmem_type;
|
|
|
|
memset(&axi_data, 0, sizeof(axi_data));
|
|
|
|
switch (cfgcmd->cmd_type) {
|
|
|
|
#ifndef CONFIG_720P_CAMERA
|
|
case CMD_AXI_CFG_OUT1:
|
|
pmem_type = MSM_PMEM_OUTPUT1;
|
|
axi_data.bufnum1 =
|
|
msm_pmem_region_lookup(&sync->pmem_frames, pmem_type,
|
|
®ion[0], 8);
|
|
if (!axi_data.bufnum1) {
|
|
pr_err("%s %d: pmem region lookup error\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
|
|
case CMD_AXI_CFG_OUT2:
|
|
pmem_type = MSM_PMEM_OUTPUT2;
|
|
axi_data.bufnum2 =
|
|
msm_pmem_region_lookup(&sync->pmem_frames, pmem_type,
|
|
®ion[0], 8);
|
|
if (!axi_data.bufnum2) {
|
|
pr_err("%s %d: pmem region lookup error (empty %d)\n",
|
|
__func__, __LINE__,
|
|
hlist_empty(&sync->pmem_frames));
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
|
|
case CMD_AXI_CFG_SNAP_O1_AND_O2:
|
|
pmem_type = MSM_PMEM_THUMBNAIL;
|
|
axi_data.bufnum1 =
|
|
msm_pmem_region_lookup(&sync->pmem_frames, pmem_type,
|
|
®ion[0], 8);
|
|
|
|
/* HTC: check axi_data.bufnum1 if out of bound of "region" array */
|
|
if (!axi_data.bufnum1 || axi_data.bufnum1 >=
|
|
(sizeof(region)/sizeof(struct msm_pmem_region))) {
|
|
pr_err("%s %d: pmem region lookup error or out of bound\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pmem_type = MSM_PMEM_MAINIMG;
|
|
axi_data.bufnum2 =
|
|
msm_pmem_region_lookup(&sync->pmem_frames, pmem_type,
|
|
®ion[axi_data.bufnum1], 8);
|
|
if (!axi_data.bufnum2) {
|
|
pr_err("%s %d: pmem region lookup error\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
#else
|
|
case CMD_AXI_CFG_PREVIEW:
|
|
pmem_type = MSM_PMEM_PREVIEW;
|
|
axi_data.bufnum2 =
|
|
msm_pmem_region_lookup(&sync->pmem_frames, pmem_type,
|
|
®ion[0], 8);
|
|
if (!axi_data.bufnum2) {
|
|
pr_err("%s %d: pmem region lookup error (empty %d)\n",
|
|
__func__, __LINE__,
|
|
hlist_empty(&sync->pmem_frames));
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
|
|
case CMD_AXI_CFG_VIDEO:
|
|
pmem_type = MSM_PMEM_PREVIEW;
|
|
axi_data.bufnum1 =
|
|
msm_pmem_region_lookup(&sync->pmem_frames, pmem_type,
|
|
®ion[0], 8);
|
|
if (!axi_data.bufnum1) {
|
|
pr_err("%s %d: pmem region lookup error\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pmem_type = MSM_PMEM_VIDEO;
|
|
axi_data.bufnum2 =
|
|
msm_pmem_region_lookup(&sync->pmem_frames, pmem_type,
|
|
®ion[axi_data.bufnum1],
|
|
(8-(axi_data.bufnum1)));
|
|
if (!axi_data.bufnum2) {
|
|
pr_err("%s %d: pmem region lookup error\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
|
|
|
|
case CMD_AXI_CFG_SNAP:
|
|
pmem_type = MSM_PMEM_THUMBNAIL;
|
|
axi_data.bufnum1 =
|
|
msm_pmem_region_lookup(&sync->pmem_frames, pmem_type,
|
|
®ion[0], 8);
|
|
if (!axi_data.bufnum1) {
|
|
pr_err("%s %d: pmem region lookup error\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pmem_type = MSM_PMEM_MAINIMG;
|
|
axi_data.bufnum2 =
|
|
msm_pmem_region_lookup(&sync->pmem_frames, pmem_type,
|
|
®ion[axi_data.bufnum1],
|
|
(8-(axi_data.bufnum1)));
|
|
if (!axi_data.bufnum2) {
|
|
pr_err("%s %d: pmem region lookup error\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
#endif
|
|
case CMD_RAW_PICT_AXI_CFG:
|
|
pmem_type = MSM_PMEM_RAW_MAINIMG;
|
|
axi_data.bufnum2 =
|
|
msm_pmem_region_lookup(&sync->pmem_frames, pmem_type,
|
|
®ion[0], 8);
|
|
if (!axi_data.bufnum2) {
|
|
pr_err("%s %d: pmem region lookup error\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
|
|
case CMD_GENERAL:
|
|
data = NULL;
|
|
break;
|
|
|
|
default:
|
|
pr_err("%s: unknown command type %d\n",
|
|
__func__, cfgcmd->cmd_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
axi_data.region = ®ion[0];
|
|
|
|
/* send the AXI configuration command to driver */
|
|
|
|
if (sync->vfefn.vfe_config)
|
|
rc = sync->vfefn.vfe_config(cfgcmd, data);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int msm_get_sensor_info(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
int rc = 0;
|
|
struct msm_camsensor_info info;
|
|
struct msm_camera_sensor_info *sdata;
|
|
|
|
if (copy_from_user(&info,
|
|
arg,
|
|
sizeof(struct msm_camsensor_info))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
sdata = sync->pdev->dev.platform_data;
|
|
CDBG("%s: sensor_name %s\n", __func__, sdata->sensor_name);
|
|
|
|
memcpy(&info.name[0],
|
|
sdata->sensor_name,
|
|
MAX_SENSOR_NAME);
|
|
info.flash_enabled = !!sdata->flash_cfg;
|
|
|
|
/* copy back to user space */
|
|
if (copy_to_user((void *)arg,
|
|
&info,
|
|
sizeof(struct msm_camsensor_info))) {
|
|
ERR_COPY_TO_USER();
|
|
rc = -EFAULT;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int __msm_put_frame_buf(struct msm_sync *sync,
|
|
struct msm_frame *pb)
|
|
{
|
|
unsigned long pphy;
|
|
struct msm_vfe_cfg_cmd cfgcmd;
|
|
|
|
int rc = -EIO;
|
|
|
|
pphy = msm_pmem_frame_vtop_lookup(sync,
|
|
pb->buffer,
|
|
pb->y_off, pb->cbcr_off, pb->fd);
|
|
|
|
if (pphy != 0) {
|
|
CDBG("%s: rel: vaddr %lx, paddr %lx\n",
|
|
__func__,
|
|
pb->buffer, pphy);
|
|
cfgcmd.cmd_type = CMD_FRAME_BUF_RELEASE;
|
|
cfgcmd.value = (void *)pb;
|
|
if (sync->vfefn.vfe_config) {
|
|
rc = sync->vfefn.vfe_config(&cfgcmd, &pphy);
|
|
}
|
|
} else {
|
|
pr_err("%s: msm_pmem_frame_vtop_lookup failed\n",
|
|
__func__);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int msm_put_frame_buffer(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
struct msm_frame buf;
|
|
|
|
if (copy_from_user(&buf,
|
|
arg,
|
|
sizeof(struct msm_frame))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
return __msm_put_frame_buf(sync, &buf);
|
|
}
|
|
|
|
static int __msm_register_pmem(struct msm_sync *sync,
|
|
struct msm_pmem_info *pinfo)
|
|
{
|
|
int rc = 0;
|
|
|
|
switch (pinfo->type) {
|
|
#ifndef CONFIG_720P_CAMERA
|
|
case MSM_PMEM_OUTPUT1:
|
|
case MSM_PMEM_OUTPUT2:
|
|
#else
|
|
case MSM_PMEM_VIDEO:
|
|
case MSM_PMEM_PREVIEW:
|
|
#endif
|
|
case MSM_PMEM_THUMBNAIL:
|
|
case MSM_PMEM_MAINIMG:
|
|
case MSM_PMEM_RAW_MAINIMG:
|
|
rc = msm_pmem_table_add(&sync->pmem_frames, pinfo);
|
|
break;
|
|
|
|
case MSM_PMEM_AEC_AWB:
|
|
case MSM_PMEM_AF:
|
|
rc = msm_pmem_table_add(&sync->pmem_stats, pinfo);
|
|
break;
|
|
|
|
default:
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int msm_register_pmem(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
struct msm_pmem_info info;
|
|
|
|
if (copy_from_user(&info, arg, sizeof(info))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
return __msm_register_pmem(sync, &info);
|
|
}
|
|
|
|
static int msm_stats_axi_cfg(struct msm_sync *sync,
|
|
struct msm_vfe_cfg_cmd *cfgcmd)
|
|
{
|
|
int rc = -EIO;
|
|
struct axidata axi_data;
|
|
void *data = &axi_data;
|
|
|
|
struct msm_pmem_region region[3];
|
|
int pmem_type = MSM_PMEM_MAX;
|
|
|
|
memset(&axi_data, 0, sizeof(axi_data));
|
|
|
|
switch (cfgcmd->cmd_type) {
|
|
case CMD_STATS_AXI_CFG:
|
|
pmem_type = MSM_PMEM_AEC_AWB;
|
|
break;
|
|
case CMD_STATS_AF_AXI_CFG:
|
|
pmem_type = MSM_PMEM_AF;
|
|
break;
|
|
case CMD_GENERAL:
|
|
data = NULL;
|
|
break;
|
|
default:
|
|
pr_err("%s: unknown command type %d\n",
|
|
__func__, cfgcmd->cmd_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cfgcmd->cmd_type != CMD_GENERAL) {
|
|
axi_data.bufnum1 =
|
|
msm_pmem_region_lookup(&sync->pmem_stats, pmem_type,
|
|
®ion[0], NUM_WB_EXP_STAT_OUTPUT_BUFFERS);
|
|
if (!axi_data.bufnum1) {
|
|
pr_err("%s %d: pmem region lookup error\n",
|
|
__func__, __LINE__);
|
|
return -EINVAL;
|
|
}
|
|
axi_data.region = ®ion[0];
|
|
}
|
|
|
|
/* send the AEC/AWB STATS configuration command to driver */
|
|
|
|
if (sync->vfefn.vfe_config)
|
|
rc = sync->vfefn.vfe_config(cfgcmd, &axi_data);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int msm_put_stats_buffer(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
int rc = -EIO;
|
|
|
|
struct msm_stats_buf buf;
|
|
unsigned long pphy;
|
|
struct msm_vfe_cfg_cmd cfgcmd;
|
|
|
|
if (copy_from_user(&buf, arg,
|
|
sizeof(struct msm_stats_buf))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
CDBG("%s\n", __func__);
|
|
pphy = msm_pmem_stats_vtop_lookup(sync, buf.buffer, buf.fd);
|
|
|
|
if (pphy != 0) {
|
|
if (buf.type == STAT_AEAW)
|
|
cfgcmd.cmd_type = CMD_STATS_BUF_RELEASE;
|
|
else if (buf.type == STAT_AF)
|
|
cfgcmd.cmd_type = CMD_STATS_AF_BUF_RELEASE;
|
|
else {
|
|
pr_err("%s: invalid buf type %d\n",
|
|
__func__,
|
|
buf.type);
|
|
rc = -EINVAL;
|
|
goto put_done;
|
|
}
|
|
|
|
cfgcmd.value = (void *)&buf;
|
|
if (sync->vfefn.vfe_config) {
|
|
rc = sync->vfefn.vfe_config(&cfgcmd, &pphy);
|
|
if (rc < 0)
|
|
pr_err("%s: vfe_config error %d\n",
|
|
__func__, rc);
|
|
} else
|
|
pr_err("%s: vfe_config is NULL\n", __func__);
|
|
} else {
|
|
pr_err("%s: NULL physical address\n", __func__);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
put_done:
|
|
return rc;
|
|
}
|
|
|
|
static int msm_axi_config(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
struct msm_vfe_cfg_cmd cfgcmd;
|
|
|
|
if (copy_from_user(&cfgcmd, arg, sizeof(cfgcmd))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
switch (cfgcmd.cmd_type) {
|
|
#ifndef CONFIG_720P_CAMERA
|
|
case CMD_AXI_CFG_OUT1:
|
|
case CMD_AXI_CFG_OUT2:
|
|
case CMD_AXI_CFG_SNAP_O1_AND_O2:
|
|
#else
|
|
case CMD_AXI_CFG_VIDEO:
|
|
case CMD_AXI_CFG_PREVIEW:
|
|
case CMD_AXI_CFG_SNAP:
|
|
#endif
|
|
case CMD_RAW_PICT_AXI_CFG:
|
|
return msm_frame_axi_cfg(sync, &cfgcmd);
|
|
|
|
case CMD_STATS_AXI_CFG:
|
|
case CMD_STATS_AF_AXI_CFG:
|
|
return msm_stats_axi_cfg(sync, &cfgcmd);
|
|
|
|
default:
|
|
pr_err("%s: unknown command type %d\n",
|
|
__func__,
|
|
cfgcmd.cmd_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __msm_get_pic(struct msm_sync *sync, struct msm_ctrl_cmd *ctrl)
|
|
{
|
|
int rc = 0;
|
|
int tm;
|
|
|
|
struct msm_queue_cmd *qcmd = NULL;
|
|
|
|
tm = (int)ctrl->timeout_ms;
|
|
|
|
rc = wait_event_interruptible_timeout(
|
|
sync->pict_q.wait,
|
|
!list_empty_careful(&sync->pict_q.list),
|
|
msecs_to_jiffies(tm));
|
|
if (list_empty_careful(&sync->pict_q.list)) {
|
|
if (rc == 0)
|
|
return -ETIMEDOUT;
|
|
if (rc < 0) {
|
|
pr_err("%s: rc %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
qcmd = msm_dequeue(&sync->pict_q, list_pict);
|
|
BUG_ON(!qcmd);
|
|
|
|
/* HTC: check qcmd */
|
|
if (!qcmd) {
|
|
rc = -EFAULT;
|
|
pr_err("%s: qcmd is NULL, rc %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (qcmd->command != NULL) {
|
|
struct msm_ctrl_cmd *q =
|
|
(struct msm_ctrl_cmd *)qcmd->command;
|
|
ctrl->type = q->type;
|
|
ctrl->status = q->status;
|
|
} else {
|
|
ctrl->type = -1;
|
|
ctrl->status = -1;
|
|
}
|
|
|
|
free_qcmd(qcmd);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int msm_get_pic(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
struct msm_ctrl_cmd ctrlcmd;
|
|
struct msm_pmem_region pic_pmem_region;
|
|
int rc;
|
|
unsigned long end;
|
|
int cline_mask;
|
|
|
|
if (copy_from_user(&ctrlcmd,
|
|
arg,
|
|
sizeof(struct msm_ctrl_cmd))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
rc = __msm_get_pic(sync, &ctrlcmd);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (sync->croplen) {
|
|
if (ctrlcmd.length != sync->croplen) {
|
|
pr_err("%s: invalid len %d < %d\n",
|
|
__func__,
|
|
ctrlcmd.length,
|
|
sync->croplen);
|
|
return -EINVAL;
|
|
}
|
|
if (copy_to_user(ctrlcmd.value,
|
|
sync->cropinfo,
|
|
sync->croplen)) {
|
|
ERR_COPY_TO_USER();
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
if (msm_pmem_region_lookup(&sync->pmem_frames,
|
|
MSM_PMEM_MAINIMG,
|
|
&pic_pmem_region, 1) == 0) {
|
|
pr_err("%s pmem region lookup error\n", __func__);
|
|
pr_info("%s probably getting RAW\n", __func__);
|
|
if (msm_pmem_region_lookup(&sync->pmem_frames,
|
|
MSM_PMEM_RAW_MAINIMG,
|
|
&pic_pmem_region, 1) == 0) {
|
|
pr_err("%s RAW pmem region lookup error\n", __func__);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
cline_mask = cache_line_size() - 1;
|
|
end = pic_pmem_region.kvaddr + pic_pmem_region.len;
|
|
end = (end + cline_mask) & ~cline_mask;
|
|
|
|
pr_info("%s: flushing cache for [%08lx, %08lx)\n",
|
|
__func__,
|
|
pic_pmem_region.kvaddr, end);
|
|
|
|
dmac_inv_range((const void *)pic_pmem_region.kvaddr,
|
|
(const void *)end);
|
|
|
|
CDBG("%s: copy snapshot frame to user\n", __func__);
|
|
if (copy_to_user((void *)arg,
|
|
&ctrlcmd,
|
|
sizeof(struct msm_ctrl_cmd))) {
|
|
ERR_COPY_TO_USER();
|
|
return -EFAULT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int msm_set_crop(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
struct crop_info crop;
|
|
|
|
if (copy_from_user(&crop,
|
|
arg,
|
|
sizeof(struct crop_info))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (!sync->croplen) {
|
|
sync->cropinfo = kzalloc(crop.len, GFP_KERNEL);
|
|
if (!sync->cropinfo)
|
|
return -ENOMEM;
|
|
} else if (sync->croplen < crop.len)
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user(sync->cropinfo,
|
|
crop.info,
|
|
crop.len)) {
|
|
ERR_COPY_FROM_USER();
|
|
kfree(sync->cropinfo);
|
|
return -EFAULT;
|
|
}
|
|
|
|
sync->croplen = crop.len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_pp_grab(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
uint32_t enable;
|
|
if (copy_from_user(&enable, arg, sizeof(enable))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
} else {
|
|
enable &= PP_MASK;
|
|
if (enable & (enable - 1)) {
|
|
pr_err("%s: error: more than one PP request!\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
if (sync->pp_mask) {
|
|
pr_err("%s: postproc %x is already enabled\n",
|
|
__func__, sync->pp_mask & enable);
|
|
return -EINVAL;
|
|
}
|
|
|
|
CDBG("%s: sync->pp_mask %d enable %d\n", __func__,
|
|
sync->pp_mask, enable);
|
|
sync->pp_mask |= enable;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_pp_release(struct msm_sync *sync, void __user *arg)
|
|
{
|
|
uint32_t mask;
|
|
if (copy_from_user(&mask, arg, sizeof(uint32_t))) {
|
|
ERR_COPY_FROM_USER();
|
|
return -EFAULT;
|
|
}
|
|
|
|
mask &= PP_MASK;
|
|
if (!(sync->pp_mask & mask)) {
|
|
pr_warning("%s: pp not in progress for %x\n", __func__,
|
|
mask);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((mask & PP_PREV) && (sync->pp_mask & PP_PREV)) {
|
|
if (!sync->pp_prev) {
|
|
pr_err("%s: no preview frame to deliver!\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
pr_info("%s: delivering pp_prev\n", __func__);
|
|
msm_enqueue(&sync->frame_q, &sync->pp_prev->list_frame);
|
|
sync->pp_prev = NULL;
|
|
goto done;
|
|
}
|
|
|
|
if (((mask & PP_SNAP) && (sync->pp_mask & PP_SNAP)) ||
|
|
((mask & PP_RAW_SNAP) &&
|
|
(sync->pp_mask & PP_RAW_SNAP))) {
|
|
if (!sync->pp_snap) {
|
|
pr_err("%s: no snapshot to deliver!\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
pr_info("%s: delivering pp_snap\n", __func__);
|
|
msm_enqueue(&sync->pict_q, &sync->pp_snap->list_pict);
|
|
sync->pp_snap = NULL;
|
|
}
|
|
|
|
done:
|
|
sync->pp_mask = 0;
|
|
return 0;
|
|
}
|
|
|
|
static long msm_ioctl_common(struct msm_device *pmsm,
|
|
unsigned int cmd,
|
|
void __user *argp)
|
|
{
|
|
CDBG("%s\n", __func__);
|
|
switch (cmd) {
|
|
case MSM_CAM_IOCTL_REGISTER_PMEM:
|
|
return msm_register_pmem(pmsm->sync, argp);
|
|
case MSM_CAM_IOCTL_UNREGISTER_PMEM:
|
|
return msm_pmem_table_del(pmsm->sync, argp);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
int msm_camera_flash(struct msm_sync *sync, int level)
|
|
{
|
|
int flash_level = 0;
|
|
uint8_t phy_flash = 0;
|
|
int ret = 0;
|
|
|
|
if (!sync->sdata->flash_cfg) {
|
|
pr_err("%s: camera flash is not supported.\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!sync->sdata->flash_cfg->num_flash_levels) {
|
|
pr_err("%s: no flash levels.\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
sync->sdata->flash_cfg->postpone_led_mode = MSM_CAMERA_LED_OFF;
|
|
|
|
switch (level) {
|
|
case MSM_CAMERA_LED_LOW_FOR_SNAPSHOT:
|
|
/* postpone set led low*/
|
|
sync->sdata->flash_cfg->postpone_led_mode = MSM_CAMERA_LED_LOW;
|
|
phy_flash = 0;
|
|
break;
|
|
case MSM_CAMERA_LED_HIGH:
|
|
/* postpone set led high*/
|
|
sync->sdata->flash_cfg->postpone_led_mode = MSM_CAMERA_LED_HIGH;
|
|
phy_flash = 0;
|
|
break;
|
|
case MSM_CAMERA_LED_LOW:
|
|
flash_level = sync->sdata->flash_cfg->num_flash_levels / 2;
|
|
phy_flash = 1;
|
|
break;
|
|
case MSM_CAMERA_LED_OFF:
|
|
flash_level = 0;
|
|
phy_flash = 1;
|
|
break;
|
|
default:
|
|
pr_err("%s: invalid flash level %d.\n", __func__, level);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (phy_flash)
|
|
ret = sync->sdata->flash_cfg->camera_flash(flash_level);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static long msm_ioctl_config(struct file *filep, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int rc = -EINVAL;
|
|
void __user *argp = (void __user *)arg;
|
|
struct msm_device *pmsm = filep->private_data;
|
|
|
|
CDBG("%s: cmd %d\n", __func__, _IOC_NR(cmd));
|
|
|
|
switch (cmd) {
|
|
case MSM_CAM_IOCTL_GET_SENSOR_INFO:
|
|
rc = msm_get_sensor_info(pmsm->sync, argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_CONFIG_VFE:
|
|
/* Coming from config thread for update */
|
|
rc = msm_config_vfe(pmsm->sync, argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_GET_STATS:
|
|
/* Coming from config thread wait
|
|
* for vfe statistics and control requests */
|
|
rc = msm_get_stats(pmsm->sync, argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_ENABLE_VFE:
|
|
/* This request comes from control thread:
|
|
* enable either QCAMTASK or VFETASK */
|
|
rc = msm_enable_vfe(pmsm->sync, argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_DISABLE_VFE:
|
|
/* This request comes from control thread:
|
|
* disable either QCAMTASK or VFETASK */
|
|
rc = msm_disable_vfe(pmsm->sync, argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_VFE_APPS_RESET:
|
|
msm_camio_vfe_blk_reset();
|
|
rc = 0;
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_RELEASE_STATS_BUFFER:
|
|
rc = msm_put_stats_buffer(pmsm->sync, argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_AXI_CONFIG:
|
|
rc = msm_axi_config(pmsm->sync, argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_SET_CROP:
|
|
rc = msm_set_crop(pmsm->sync, argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_PP:
|
|
/* Grab one preview frame or one snapshot
|
|
* frame.
|
|
*/
|
|
rc = msm_pp_grab(pmsm->sync, argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_PP_DONE:
|
|
/* Release the preview of snapshot frame
|
|
* that was grabbed.
|
|
*/
|
|
rc = msm_pp_release(pmsm->sync, argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_SENSOR_IO_CFG:
|
|
rc = pmsm->sync->sctrl.s_config(argp);
|
|
break;
|
|
|
|
case MSM_CAM_IOCTL_FLASH_LED_CFG: {
|
|
uint32_t led_state;
|
|
if (copy_from_user(&led_state, argp, sizeof(led_state))) {
|
|
ERR_COPY_FROM_USER();
|
|
rc = -EFAULT;
|
|
} else
|
|
rc = msm_camera_flash(pmsm->sync, led_state);
|
|
break;
|
|
}
|
|
case MSM_CAM_IOCTL_ENABLE_OUTPUT_IND: {
|
|
uint32_t enable;
|
|
if (copy_from_user(&enable, argp, sizeof(enable))) {
|
|
ERR_COPY_FROM_USER();
|
|
rc = -EFAULT;
|
|
break;
|
|
}
|
|
pmsm->sync->report_preview_to_config = enable;
|
|
}
|
|
|
|
default:
|
|
rc = msm_ioctl_common(pmsm, cmd, argp);
|
|
break;
|
|
}
|
|
|
|
CDBG("%s: cmd %d DONE\n", __func__, _IOC_NR(cmd));
|
|
return rc;
|
|
}
|
|
|
|
static int msm_unblock_poll_frame(struct msm_sync *);
|
|
|
|
static long msm_ioctl_frame(struct file *filep, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int rc = -EINVAL;
|
|
void __user *argp = (void __user *)arg;
|
|
struct msm_device *pmsm = filep->private_data;
|
|
|
|
switch (cmd) {
|
|
case MSM_CAM_IOCTL_GETFRAME:
|
|
/* Coming from frame thread to get frame
|
|
* after SELECT is done */
|
|
rc = msm_get_frame(pmsm->sync, argp);
|
|
break;
|
|
case MSM_CAM_IOCTL_RELEASE_FRAME_BUFFER:
|
|
rc = msm_put_frame_buffer(pmsm->sync, argp);
|
|
break;
|
|
case MSM_CAM_IOCTL_UNBLOCK_POLL_FRAME:
|
|
rc = msm_unblock_poll_frame(pmsm->sync);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static long msm_ioctl_control(struct file *filep, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int rc = -EINVAL;
|
|
void __user *argp = (void __user *)arg;
|
|
struct msm_control_device *ctrl_pmsm = filep->private_data;
|
|
struct msm_device *pmsm = ctrl_pmsm->pmsm;
|
|
|
|
switch (cmd) {
|
|
case MSM_CAM_IOCTL_CTRL_COMMAND:
|
|
/* Coming from control thread, may need to wait for
|
|
* command status */
|
|
rc = msm_control(ctrl_pmsm, 1, argp);
|
|
break;
|
|
case MSM_CAM_IOCTL_CTRL_COMMAND_2:
|
|
/* Sends a message, returns immediately */
|
|
rc = msm_control(ctrl_pmsm, 0, argp);
|
|
break;
|
|
case MSM_CAM_IOCTL_CTRL_CMD_DONE:
|
|
/* Config thread calls the control thread to notify it
|
|
* of the result of a MSM_CAM_IOCTL_CTRL_COMMAND.
|
|
*/
|
|
rc = msm_ctrl_cmd_done(ctrl_pmsm, argp);
|
|
break;
|
|
case MSM_CAM_IOCTL_GET_PICTURE:
|
|
rc = msm_get_pic(pmsm->sync, argp);
|
|
break;
|
|
case MSM_CAM_IOCTL_GET_SENSOR_INFO:
|
|
rc = msm_get_sensor_info(pmsm->sync, argp);
|
|
break;
|
|
default:
|
|
rc = msm_ioctl_common(pmsm, cmd, argp);
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static void msm_show_time(void){
|
|
struct timespec ts;
|
|
struct rtc_time tm;
|
|
getnstimeofday(&ts);
|
|
rtc_time_to_tm(ts.tv_sec, &tm);
|
|
pr_info(">>>>>>>>(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)<<<<<<<\n",
|
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
|
tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
|
|
return;
|
|
}
|
|
|
|
|
|
static int __msm_release(struct msm_sync *sync)
|
|
{
|
|
struct msm_pmem_region *region;
|
|
struct hlist_node *hnode;
|
|
struct hlist_node *n;
|
|
pr_info("%s:sync->opencnt:%d \n", __func__, sync->opencnt);
|
|
mutex_lock(&sync->lock);
|
|
if (sync->opencnt)
|
|
sync->opencnt--;
|
|
|
|
if (!sync->opencnt) {
|
|
|
|
msm_show_time();
|
|
|
|
/* need to clean up system resource */
|
|
if (sync->vfefn.vfe_release)
|
|
sync->vfefn.vfe_release(sync->pdev);
|
|
|
|
kfree(sync->cropinfo);
|
|
sync->cropinfo = NULL;
|
|
sync->croplen = 0;
|
|
|
|
/*sensor release moved to vfe_release*/
|
|
|
|
hlist_for_each_entry_safe(region, hnode, n,
|
|
&sync->pmem_frames, list) {
|
|
hlist_del(hnode);
|
|
put_pmem_file(region->file);
|
|
kfree(region);
|
|
}
|
|
|
|
hlist_for_each_entry_safe(region, hnode, n,
|
|
&sync->pmem_stats, list) {
|
|
hlist_del(hnode);
|
|
put_pmem_file(region->file);
|
|
kfree(region);
|
|
}
|
|
|
|
msm_queue_drain(&sync->event_q, list_config);
|
|
msm_queue_drain(&sync->frame_q, list_frame);
|
|
msm_queue_drain(&sync->pict_q, list_pict);
|
|
|
|
wake_unlock(&sync->wake_suspend_lock);
|
|
wake_unlock(&sync->wake_lock);
|
|
|
|
sync->apps_id = NULL;
|
|
CDBG("%s: completed\n", __func__);
|
|
}
|
|
mutex_unlock(&sync->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_release_config(struct inode *node, struct file *filep)
|
|
{
|
|
int rc;
|
|
struct msm_device *pmsm = filep->private_data;
|
|
pr_info("%s: %s\n", __func__, filep->f_path.dentry->d_name.name);
|
|
rc = __msm_release(pmsm->sync);
|
|
if (!rc) {
|
|
pr_info("release config atomic_set pmsm->opened as 0\n");
|
|
atomic_set(&pmsm->opened, 0);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int msm_release_control(struct inode *node, struct file *filep)
|
|
{
|
|
int rc;
|
|
struct msm_control_device *ctrl_pmsm = filep->private_data;
|
|
struct msm_device *pmsm = ctrl_pmsm->pmsm;
|
|
pr_info("%s: %s\n", __func__, filep->f_path.dentry->d_name.name);
|
|
rc = __msm_release(pmsm->sync);
|
|
if (!rc) {
|
|
msm_queue_drain(&ctrl_pmsm->ctrl_q, list_control);
|
|
kfree(ctrl_pmsm);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int msm_release_frame(struct inode *node, struct file *filep)
|
|
{
|
|
int rc;
|
|
struct msm_device *pmsm = filep->private_data;
|
|
pr_info("%s: %s\n", __func__, filep->f_path.dentry->d_name.name);
|
|
rc = __msm_release(pmsm->sync);
|
|
if (!rc) {
|
|
pr_info("release frame atomic_set pmsm->opened as 0\n");
|
|
atomic_set(&pmsm->opened, 0);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int msm_unblock_poll_frame(struct msm_sync *sync)
|
|
{
|
|
unsigned long flags;
|
|
CDBG("%s\n", __func__);
|
|
spin_lock_irqsave(&sync->frame_q.lock, flags);
|
|
sync->unblock_poll_frame = 1;
|
|
wake_up(&sync->frame_q.wait);
|
|
spin_unlock_irqrestore(&sync->frame_q.lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int __msm_poll_frame(struct msm_sync *sync,
|
|
struct file *filep,
|
|
struct poll_table_struct *pll_table)
|
|
{
|
|
int rc = 0;
|
|
unsigned long flags;
|
|
|
|
poll_wait(filep, &sync->frame_q.wait, pll_table);
|
|
|
|
spin_lock_irqsave(&sync->frame_q.lock, flags);
|
|
if (!list_empty_careful(&sync->frame_q.list))
|
|
/* frame ready */
|
|
rc = POLLIN | POLLRDNORM;
|
|
if (sync->unblock_poll_frame) {
|
|
CDBG("%s: sync->unblock_poll_frame is true\n", __func__);
|
|
rc |= POLLPRI;
|
|
sync->unblock_poll_frame = 0;
|
|
}
|
|
spin_unlock_irqrestore(&sync->frame_q.lock, flags);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static unsigned int msm_poll_frame(struct file *filep,
|
|
struct poll_table_struct *pll_table)
|
|
{
|
|
struct msm_device *pmsm = filep->private_data;
|
|
return __msm_poll_frame(pmsm->sync, filep, pll_table);
|
|
}
|
|
|
|
/*
|
|
* This function executes in interrupt context.
|
|
*/
|
|
|
|
static void *msm_vfe_sync_alloc(int size,
|
|
void *syncdata __attribute__((unused)),
|
|
gfp_t gfp)
|
|
{
|
|
struct msm_queue_cmd *qcmd =
|
|
kzalloc(sizeof(struct msm_queue_cmd) + size, gfp);
|
|
if (qcmd) {
|
|
/* Becker and kant */
|
|
memset(qcmd, 0x0, sizeof(struct msm_queue_cmd) + size);
|
|
|
|
qcmd->on_heap = 1;
|
|
return qcmd + 1;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void msm_vfe_sync_free(void *ptr)
|
|
{
|
|
if (ptr) {
|
|
struct msm_queue_cmd *qcmd =
|
|
(struct msm_queue_cmd *)ptr;
|
|
qcmd--;
|
|
if (qcmd->on_heap)
|
|
kfree(qcmd);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function may execute in interrupt context.
|
|
*/
|
|
|
|
static void msm_vfe_sync(struct msm_vfe_resp *vdata,
|
|
enum msm_queue qtype, void *syncdata,
|
|
gfp_t gfp)
|
|
{
|
|
struct msm_queue_cmd *qcmd = NULL;
|
|
struct msm_sync *sync = (struct msm_sync *)syncdata;
|
|
|
|
if (!sync) {
|
|
pr_err("%s: no context in dsp callback.\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (!sync->opencnt) {
|
|
pr_err("%s: SPURIOUS INTERRUPT\n", __func__);
|
|
return;
|
|
}
|
|
|
|
qcmd = ((struct msm_queue_cmd *)vdata) - 1;
|
|
qcmd->type = qtype;
|
|
qcmd->command = vdata;
|
|
|
|
CDBG("%s: qtype %d \n", __func__, qtype);
|
|
CDBG("%s: evt_msg.msg_id %d\n", __func__, vdata->evt_msg.msg_id);
|
|
CDBG("%s: evt_msg.exttype %d\n", __func__, vdata->evt_msg.exttype);
|
|
if (sync->sdata->flash_cfg) {
|
|
if (qtype == MSM_CAM_Q_VFE_MSG &&
|
|
vdata->evt_msg.exttype == VFE_MSG_SNAPSHOT) {
|
|
#if defined(CONFIG_ARCH_MSM_ARM11)
|
|
if (vdata->evt_msg.msg_id == 4)
|
|
/* QDSP_VFETASK_MSG_VFE_START_ACK */
|
|
#elif defined(CONFIG_ARCH_QSD8X50)
|
|
if (vdata->evt_msg.msg_id == 1)
|
|
/* VFE_MSG_ID_START_ACK */
|
|
#endif
|
|
{
|
|
pr_info("flashlight: postpone_led_mode %d\n",
|
|
sync->sdata->flash_cfg->postpone_led_mode);
|
|
sync->sdata->flash_cfg->camera_flash(
|
|
sync->sdata->flash_cfg->postpone_led_mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (qtype != MSM_CAM_Q_VFE_MSG)
|
|
goto for_config;
|
|
|
|
CDBG("%s: vdata->type %d\n", __func__, vdata->type);
|
|
switch (vdata->type) {
|
|
#ifndef CONFIG_720P_CAMERA
|
|
case VFE_MSG_OUTPUT1:
|
|
case VFE_MSG_OUTPUT2:
|
|
#else
|
|
case VFE_MSG_OUTPUT_P:
|
|
#endif
|
|
|
|
if (sync->pp_mask & PP_PREV) {
|
|
CDBG("%s: PP_PREV in progress: phy_y %x phy_cbcr %x\n",
|
|
__func__,
|
|
vdata->phy.y_phy,
|
|
vdata->phy.cbcr_phy);
|
|
if (sync->pp_prev)
|
|
pr_warning("%s: overwriting pp_prev!\n",
|
|
__func__);
|
|
pr_info("%s: sending preview to config\n", __func__);
|
|
sync->pp_prev = qcmd;
|
|
if (qcmd->on_heap)
|
|
qcmd->on_heap++;
|
|
break;
|
|
}
|
|
CDBG("%s: msm_enqueue frame_q\n", __func__);
|
|
if (qcmd->on_heap)
|
|
qcmd->on_heap++;
|
|
msm_enqueue(&sync->frame_q, &qcmd->list_frame);
|
|
break;
|
|
|
|
#ifdef CONFIG_720P_CAMERA
|
|
case VFE_MSG_OUTPUT_V:
|
|
if (qcmd->on_heap)
|
|
qcmd->on_heap++;
|
|
CDBG("%s: msm_enqueue video frame_q\n", __func__);
|
|
msm_enqueue(&sync->frame_q, &qcmd->list_frame);
|
|
break;
|
|
#endif
|
|
|
|
case VFE_MSG_SNAPSHOT:
|
|
if (sync->pp_mask & (PP_SNAP | PP_RAW_SNAP)) {
|
|
CDBG("%s: PP_SNAP in progress: pp_mask %x\n",
|
|
__func__, sync->pp_mask);
|
|
if (sync->pp_snap)
|
|
pr_warning("%s: overwriting pp_snap!\n",
|
|
__func__);
|
|
pr_info("%s: sending snapshot to config\n", __func__);
|
|
sync->pp_snap = qcmd;
|
|
if (qcmd->on_heap)
|
|
qcmd->on_heap++;
|
|
break;
|
|
}
|
|
if (qcmd->on_heap)
|
|
qcmd->on_heap++;
|
|
msm_enqueue(&sync->pict_q, &qcmd->list_pict);
|
|
break;
|
|
|
|
default:
|
|
CDBG("%s: qtype %d not handled\n", __func__, vdata->type);
|
|
/* fall through, send to config. */
|
|
}
|
|
|
|
for_config:
|
|
msm_enqueue(&sync->event_q, &qcmd->list_config);
|
|
}
|
|
|
|
static struct msm_vfe_callback msm_vfe_s = {
|
|
.vfe_resp = msm_vfe_sync,
|
|
.vfe_alloc = msm_vfe_sync_alloc,
|
|
.vfe_free = msm_vfe_sync_free,
|
|
};
|
|
|
|
static int __msm_open(struct msm_sync *sync, const char *const apps_id)
|
|
{
|
|
int rc = 0;
|
|
|
|
mutex_lock(&sync->lock);
|
|
if (sync->apps_id && strcmp(sync->apps_id, apps_id)) {
|
|
pr_err("%s(%s): sensor %s is already opened for %s\n",
|
|
__func__,
|
|
apps_id,
|
|
sync->sdata->sensor_name,
|
|
sync->apps_id);
|
|
rc = -EBUSY;
|
|
goto msm_open_done;
|
|
}
|
|
|
|
sync->apps_id = apps_id;
|
|
|
|
if (!sync->opencnt) {
|
|
wake_lock(&sync->wake_suspend_lock);
|
|
wake_lock(&sync->wake_lock);
|
|
|
|
msm_camvfe_fn_init(&sync->vfefn, sync);
|
|
if (sync->vfefn.vfe_init) {
|
|
rc = sync->vfefn.vfe_init(&msm_vfe_s,
|
|
sync->pdev);
|
|
if (rc < 0) {
|
|
pr_err("%s: vfe_init failed at %d\n",
|
|
__func__, rc);
|
|
goto msm_open_done;
|
|
}
|
|
rc = sync->sctrl.s_init(sync->sdata);
|
|
if (rc < 0) {
|
|
pr_err("%s: sensor init failed: %d\n",
|
|
__func__, rc);
|
|
sync->vfefn.vfe_release(sync->pdev);
|
|
goto msm_open_done;
|
|
}
|
|
} else {
|
|
pr_err("%s: no sensor init func\n", __func__);
|
|
rc = -ENODEV;
|
|
goto msm_open_done;
|
|
}
|
|
|
|
if (rc >= 0) {
|
|
INIT_HLIST_HEAD(&sync->pmem_frames);
|
|
INIT_HLIST_HEAD(&sync->pmem_stats);
|
|
sync->unblock_poll_frame = 0;
|
|
}
|
|
}
|
|
sync->opencnt++;
|
|
|
|
msm_open_done:
|
|
mutex_unlock(&sync->lock);
|
|
return rc;
|
|
}
|
|
|
|
static int msm_open_common(struct inode *inode, struct file *filep,
|
|
int once)
|
|
{
|
|
int rc;
|
|
struct msm_device *pmsm =
|
|
container_of(inode->i_cdev, struct msm_device, cdev);
|
|
|
|
pr_info("%s: open %s\n", __func__, filep->f_path.dentry->d_name.name);
|
|
|
|
if (atomic_cmpxchg(&pmsm->opened, 0, 1) && once) {
|
|
pr_err("%s: %s is already opened.\n",
|
|
__func__,
|
|
filep->f_path.dentry->d_name.name);
|
|
return -EBUSY;
|
|
}
|
|
|
|
rc = nonseekable_open(inode, filep);
|
|
if (rc < 0) {
|
|
pr_err("%s: nonseekable_open error %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = __msm_open(pmsm->sync, MSM_APPS_ID_PROP);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
filep->private_data = pmsm;
|
|
|
|
CDBG("%s: rc %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
|
|
|
|
static int msm_open(struct inode *inode, struct file *filep)
|
|
{
|
|
msm_show_time();
|
|
return msm_open_common(inode, filep, 1);
|
|
}
|
|
|
|
static int msm_open_control(struct inode *inode, struct file *filep)
|
|
{
|
|
int rc;
|
|
|
|
struct msm_control_device *ctrl_pmsm =
|
|
kzalloc(sizeof(struct msm_control_device), GFP_KERNEL);
|
|
if (!ctrl_pmsm)
|
|
return -ENOMEM;
|
|
|
|
rc = msm_open_common(inode, filep, 0);
|
|
if (rc < 0) {
|
|
kfree(ctrl_pmsm);
|
|
return rc;
|
|
}
|
|
|
|
ctrl_pmsm->pmsm = filep->private_data;
|
|
filep->private_data = ctrl_pmsm;
|
|
msm_queue_init(&ctrl_pmsm->ctrl_q, "control");
|
|
|
|
CDBG("%s: rc %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
static int __msm_v4l2_control(struct msm_sync *sync,
|
|
struct msm_ctrl_cmd *out)
|
|
{
|
|
int rc = 0;
|
|
|
|
struct msm_queue_cmd *qcmd = NULL, *rcmd = NULL;
|
|
struct msm_ctrl_cmd *ctrl;
|
|
struct msm_device_queue FIXME;
|
|
|
|
/* wake up config thread, 4 is for V4L2 application */
|
|
qcmd = kzalloc(sizeof(struct msm_queue_cmd), GFP_KERNEL);
|
|
if (!qcmd) {
|
|
pr_err("%s: cannot allocate buffer\n", __func__);
|
|
rc = -ENOMEM;
|
|
goto end;
|
|
}
|
|
qcmd->type = MSM_CAM_Q_V4L2_REQ;
|
|
qcmd->command = out;
|
|
qcmd->on_heap = 1;
|
|
|
|
rcmd = __msm_control(sync, &FIXME, qcmd, out->timeout_ms);
|
|
if (IS_ERR(rcmd)) {
|
|
rc = PTR_ERR(rcmd);
|
|
goto end;
|
|
}
|
|
|
|
ctrl = (struct msm_ctrl_cmd *)(rcmd->command);
|
|
/* FIXME: we should just set out->length = ctrl->length; */
|
|
BUG_ON(out->length < ctrl->length);
|
|
memcpy(out->value, ctrl->value, ctrl->length);
|
|
|
|
end:
|
|
free_qcmd(rcmd);
|
|
CDBG("%s: rc %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
static const struct file_operations msm_fops_config = {
|
|
.owner = THIS_MODULE,
|
|
.open = msm_open,
|
|
.unlocked_ioctl = msm_ioctl_config,
|
|
.release = msm_release_config,
|
|
};
|
|
|
|
static const struct file_operations msm_fops_control = {
|
|
.owner = THIS_MODULE,
|
|
.open = msm_open_control,
|
|
.unlocked_ioctl = msm_ioctl_control,
|
|
.release = msm_release_control,
|
|
};
|
|
|
|
static const struct file_operations msm_fops_frame = {
|
|
.owner = THIS_MODULE,
|
|
.open = msm_open,
|
|
.unlocked_ioctl = msm_ioctl_frame,
|
|
.release = msm_release_frame,
|
|
.poll = msm_poll_frame,
|
|
};
|
|
|
|
static int msm_setup_cdev(struct msm_device *msm,
|
|
int node,
|
|
dev_t devno,
|
|
const char *suffix,
|
|
const struct file_operations *fops)
|
|
{
|
|
int rc = -ENODEV;
|
|
|
|
struct device *device =
|
|
device_create(msm_class, NULL,
|
|
devno, NULL,
|
|
"%s%d", suffix, node);
|
|
|
|
if (IS_ERR(device)) {
|
|
rc = PTR_ERR(device);
|
|
pr_err("%s: error creating device: %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
cdev_init(&msm->cdev, fops);
|
|
msm->cdev.owner = THIS_MODULE;
|
|
|
|
rc = cdev_add(&msm->cdev, devno, 1);
|
|
if (rc < 0) {
|
|
pr_err("%s: error adding cdev: %d\n", __func__, rc);
|
|
device_destroy(msm_class, devno);
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int msm_tear_down_cdev(struct msm_device *msm, dev_t devno)
|
|
{
|
|
cdev_del(&msm->cdev);
|
|
device_destroy(msm_class, devno);
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t led_ril_status_value;
|
|
static uint32_t led_wimax_status_value;
|
|
static uint32_t led_hotspot_status_value;
|
|
static uint16_t led_low_temp_limit;
|
|
static uint16_t led_low_cap_limit;
|
|
static struct kobject *led_status_obj;
|
|
|
|
static ssize_t led_ril_status_get(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ssize_t length;
|
|
length = sprintf(buf, "%d\n", led_ril_status_value);
|
|
return length;
|
|
}
|
|
|
|
static ssize_t led_ril_status_set(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
uint32_t tmp = 0;
|
|
|
|
tmp = buf[0] - 0x30; /* only get the first char */
|
|
|
|
led_ril_status_value = tmp;
|
|
pr_info("led_ril_status_value = %d\n", led_ril_status_value);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t led_wimax_status_get(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ssize_t length;
|
|
length = sprintf(buf, "%d\n", led_wimax_status_value);
|
|
return length;
|
|
}
|
|
|
|
static ssize_t led_wimax_status_set(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
uint32_t tmp = 0;
|
|
|
|
tmp = buf[0] - 0x30; /* only get the first char */
|
|
|
|
led_wimax_status_value = tmp;
|
|
pr_info("led_wimax_status_value = %d\n", led_wimax_status_value);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t led_hotspot_status_get(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ssize_t length;
|
|
length = sprintf(buf, "%d\n", led_hotspot_status_value);
|
|
return length;
|
|
}
|
|
|
|
static ssize_t led_hotspot_status_set(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
uint32_t tmp = 0;
|
|
|
|
tmp = buf[0] - 0x30; /* only get the first char */
|
|
|
|
led_hotspot_status_value = tmp;
|
|
pr_info("led_hotspot_status_value = %d\n", led_hotspot_status_value);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t low_temp_limit_get(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ssize_t length;
|
|
length = sprintf(buf, "%d\n", led_low_temp_limit);
|
|
return length;
|
|
}
|
|
|
|
static ssize_t low_cap_limit_get(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ssize_t length;
|
|
length = sprintf(buf, "%d\n", led_low_cap_limit);
|
|
return length;
|
|
}
|
|
|
|
static DEVICE_ATTR(led_ril_status, 0644,
|
|
led_ril_status_get,
|
|
led_ril_status_set);
|
|
|
|
static DEVICE_ATTR(led_wimax_status, 0644,
|
|
led_wimax_status_get,
|
|
led_wimax_status_set);
|
|
|
|
static DEVICE_ATTR(led_hotspot_status, 0644,
|
|
led_hotspot_status_get,
|
|
led_hotspot_status_set);
|
|
|
|
static DEVICE_ATTR(low_temp_limit, 0444,
|
|
low_temp_limit_get,
|
|
NULL);
|
|
|
|
static DEVICE_ATTR(low_cap_limit, 0444,
|
|
low_cap_limit_get,
|
|
NULL);
|
|
|
|
static int msm_camera_sysfs_init(struct msm_sync* sync)
|
|
{
|
|
int ret = 0;
|
|
CDBG("msm_camera:kobject creat and add\n");
|
|
led_status_obj = kobject_create_and_add("camera_led_status", NULL);
|
|
if (led_status_obj == NULL) {
|
|
pr_info("msm_camera: subsystem_register failed\n");
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
ret = sysfs_create_file(led_status_obj,
|
|
&dev_attr_led_ril_status.attr);
|
|
if (ret) {
|
|
pr_info("msm_camera: sysfs_create_file ril failed\n");
|
|
ret = -EFAULT;
|
|
goto error;
|
|
}
|
|
|
|
ret = sysfs_create_file(led_status_obj,
|
|
&dev_attr_led_wimax_status.attr);
|
|
if (ret) {
|
|
pr_info("msm_camera: sysfs_create_file wimax failed\n");
|
|
ret = -EFAULT;
|
|
goto error;
|
|
}
|
|
|
|
ret = sysfs_create_file(led_status_obj,
|
|
&dev_attr_led_hotspot_status.attr);
|
|
if (ret) {
|
|
pr_info("msm_camera: sysfs_create_file hotspot failed\n");
|
|
ret = -EFAULT;
|
|
goto error;
|
|
}
|
|
|
|
ret = sysfs_create_file(led_status_obj,
|
|
&dev_attr_low_temp_limit.attr);
|
|
if (ret) {
|
|
pr_info("msm_camera: sysfs_create_file low_temp_limit failed\n");
|
|
ret = -EFAULT;
|
|
goto error;
|
|
}
|
|
|
|
ret = sysfs_create_file(led_status_obj,
|
|
&dev_attr_low_cap_limit.attr);
|
|
if (ret) {
|
|
pr_info("msm_camera: sysfs_create_file low_cap_limit failed\n");
|
|
ret = -EFAULT;
|
|
goto error;
|
|
}
|
|
|
|
led_low_temp_limit = sync->sdata->flash_cfg->low_temp_limit;
|
|
led_low_cap_limit = sync->sdata->flash_cfg->low_cap_limit;
|
|
|
|
return ret;
|
|
error:
|
|
kobject_del(led_status_obj);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int msm_v4l2_register(struct msm_v4l2_driver *drv)
|
|
{
|
|
/* FIXME: support multiple sensors */
|
|
if (list_empty(&msm_sensors))
|
|
return -ENODEV;
|
|
|
|
drv->sync = list_first_entry(&msm_sensors, struct msm_sync, list);
|
|
drv->open = __msm_open;
|
|
drv->release = __msm_release;
|
|
drv->ctrl = __msm_v4l2_control;
|
|
drv->reg_pmem = __msm_register_pmem;
|
|
drv->get_frame = __msm_get_frame;
|
|
drv->put_frame = __msm_put_frame_buf;
|
|
drv->get_pict = __msm_get_pic;
|
|
drv->drv_poll = __msm_poll_frame;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_v4l2_register);
|
|
|
|
int msm_v4l2_unregister(struct msm_v4l2_driver *drv)
|
|
{
|
|
drv->sync = NULL;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_v4l2_unregister);
|
|
|
|
static int msm_sync_init(struct msm_sync *sync,
|
|
struct platform_device *pdev,
|
|
int (*sensor_probe)(struct msm_camera_sensor_info *,
|
|
struct msm_sensor_ctrl *), int camera_node)
|
|
{
|
|
int rc = 0;
|
|
struct msm_sensor_ctrl sctrl;
|
|
sync->sdata = pdev->dev.platform_data;
|
|
|
|
msm_queue_init(&sync->event_q, "event");
|
|
msm_queue_init(&sync->frame_q, "frame");
|
|
msm_queue_init(&sync->pict_q, "pict");
|
|
|
|
wake_lock_init(&sync->wake_suspend_lock, WAKE_LOCK_SUSPEND, "msm_camera_wake");
|
|
wake_lock_init(&sync->wake_lock, WAKE_LOCK_IDLE, "msm_camera");
|
|
|
|
rc = msm_camio_probe_on(pdev);
|
|
if (rc < 0)
|
|
return rc;
|
|
sctrl.node = camera_node;
|
|
pr_info("sctrl.node %d\n", sctrl.node);
|
|
rc = sensor_probe(sync->sdata, &sctrl);
|
|
if (rc >= 0) {
|
|
sync->pdev = pdev;
|
|
sync->sctrl = sctrl;
|
|
}
|
|
msm_camio_probe_off(pdev);
|
|
if (rc < 0) {
|
|
pr_err("%s: failed to initialize %s\n",
|
|
__func__,
|
|
sync->sdata->sensor_name);
|
|
wake_lock_destroy(&sync->wake_suspend_lock);
|
|
wake_lock_destroy(&sync->wake_lock);
|
|
return rc;
|
|
}
|
|
|
|
sync->opencnt = 0;
|
|
mutex_init(&sync->lock);
|
|
CDBG("%s: initialized %s\n", __func__, sync->sdata->sensor_name);
|
|
return rc;
|
|
}
|
|
|
|
static int msm_sync_destroy(struct msm_sync *sync)
|
|
{
|
|
wake_lock_destroy(&sync->wake_suspend_lock);
|
|
wake_lock_destroy(&sync->wake_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int msm_device_init(struct msm_device *pmsm,
|
|
struct msm_sync *sync,
|
|
int node)
|
|
{
|
|
int dev_num = 3 * node;
|
|
int rc = msm_setup_cdev(pmsm, node,
|
|
MKDEV(MAJOR(msm_devno), dev_num),
|
|
"control", &msm_fops_control);
|
|
if (rc < 0) {
|
|
pr_err("%s: error creating control node: %d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = msm_setup_cdev(pmsm + 1, node,
|
|
MKDEV(MAJOR(msm_devno), dev_num + 1),
|
|
"config", &msm_fops_config);
|
|
if (rc < 0) {
|
|
pr_err("%s: error creating config node: %d\n", __func__, rc);
|
|
msm_tear_down_cdev(pmsm, MKDEV(MAJOR(msm_devno),
|
|
dev_num));
|
|
return rc;
|
|
}
|
|
|
|
rc = msm_setup_cdev(pmsm + 2, node,
|
|
MKDEV(MAJOR(msm_devno), dev_num + 2),
|
|
"frame", &msm_fops_frame);
|
|
if (rc < 0) {
|
|
pr_err("%s: error creating frame node: %d\n", __func__, rc);
|
|
msm_tear_down_cdev(pmsm,
|
|
MKDEV(MAJOR(msm_devno), dev_num));
|
|
msm_tear_down_cdev(pmsm + 1,
|
|
MKDEV(MAJOR(msm_devno), dev_num + 1));
|
|
return rc;
|
|
}
|
|
|
|
atomic_set(&pmsm[0].opened, 0);
|
|
atomic_set(&pmsm[1].opened, 0);
|
|
atomic_set(&pmsm[2].opened, 0);
|
|
|
|
pmsm[0].sync = sync;
|
|
pmsm[1].sync = sync;
|
|
pmsm[2].sync = sync;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int msm_camera_drv_start(struct platform_device *dev,
|
|
int (*sensor_probe)(struct msm_camera_sensor_info *,
|
|
struct msm_sensor_ctrl *))
|
|
{
|
|
struct msm_device *pmsm = NULL;
|
|
struct msm_sync *sync;
|
|
int rc = -ENODEV;
|
|
static int camera_node = 0;
|
|
|
|
if (camera_node >= MSM_MAX_CAMERA_SENSORS) {
|
|
pr_err("%s: too many camera sensors\n", __func__);
|
|
return rc;
|
|
}
|
|
|
|
if (!msm_class) {
|
|
/* There are three device nodes per sensor */
|
|
rc = alloc_chrdev_region(&msm_devno, 0,
|
|
3 * MSM_MAX_CAMERA_SENSORS,
|
|
"msm_camera");
|
|
if (rc < 0) {
|
|
pr_err("%s: failed to allocate chrdev: %d\n", __func__,
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
msm_class = class_create(THIS_MODULE, "msm_camera");
|
|
if (IS_ERR(msm_class)) {
|
|
rc = PTR_ERR(msm_class);
|
|
pr_err("%s: create device class failed: %d\n",
|
|
__func__, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
pmsm = kzalloc(sizeof(struct msm_device) * 3 +
|
|
sizeof(struct msm_sync), GFP_ATOMIC);
|
|
if (!pmsm)
|
|
return -ENOMEM;
|
|
sync = (struct msm_sync *)(pmsm + 3);
|
|
|
|
rc = msm_sync_init(sync, dev, sensor_probe, camera_node);
|
|
if (rc < 0) {
|
|
kfree(pmsm);
|
|
return rc;
|
|
}
|
|
|
|
CDBG("%s: setting camera node %d\n", __func__, camera_node);
|
|
rc = msm_device_init(pmsm, sync, camera_node);
|
|
if (rc < 0) {
|
|
msm_sync_destroy(sync);
|
|
kfree(pmsm);
|
|
return rc;
|
|
}
|
|
|
|
if (!!sync->sdata->flash_cfg)
|
|
msm_camera_sysfs_init(sync);
|
|
|
|
camera_node++;
|
|
list_add(&sync->list, &msm_sensors);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(msm_camera_drv_start);
|