blob: ca2d3372dd950371cc2b8132c1f5dcd1a402c16a [file] [log] [blame]
/*
* ispstat.c
*
* STAT module for TI's OMAP3 Camera ISP
*
* Copyright (C) 2009 Texas Instruments, Inc.
*
* Contributors:
* Sergio Aguirre <saaguirre@ti.com>
* Troy Laramy
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <linux/dma-mapping.h>
#include <linux/uaccess.h>
#include "isp.h"
inline int greater_overflow(int a, int b, int limit)
{
int limit2 = limit / 2;
if (b - a > limit2)
return 1;
else if (a - b > limit2)
return 0;
else
return a > b;
}
int ispstat_buf_queue(struct ispstat *stat)
{
unsigned long flags;
if (!stat->active_buf)
return -1;
do_gettimeofday(&stat->active_buf->ts);
spin_lock_irqsave(&stat->lock, flags);
stat->active_buf->config_counter = stat->config_counter;
stat->active_buf->frame_number = stat->frame_number;
stat->frame_number++;
if (stat->frame_number == stat->max_frame)
stat->frame_number = 0;
stat->active_buf = NULL;
spin_unlock_irqrestore(&stat->lock, flags);
return 0;
}
/* Get next free buffer to write the statistics to and mark it active. */
struct ispstat_buffer *ispstat_buf_next(struct ispstat *stat)
{
unsigned long flags;
struct ispstat_buffer *found = NULL;
int i;
spin_lock_irqsave(&stat->lock, flags);
if (stat->active_buf)
dev_dbg(stat->dev, "%s: new buffer requested without queuing "
"active one.\n", stat->tag);
for (i = 0; i < stat->nbufs; i++) {
struct ispstat_buffer *curr = &stat->buf[i];
/*
* Don't select the buffer which is being copied to
* userspace.
*/
if (curr == stat->locked_buf)
continue;
/* Uninitialised buffer -- pick that one over anything else. */
if (curr->frame_number == stat->max_frame) {
found = curr;
break;
}
if (!found ||
!greater_overflow(curr->frame_number, found->frame_number,
stat->max_frame))
found = curr;
}
stat->active_buf = found;
spin_unlock_irqrestore(&stat->lock, flags);
return found;
}
/* Get buffer to userspace. */
static struct ispstat_buffer *ispstat_buf_find(
struct ispstat *stat, u32 frame_number)
{
int i;
for (i = 0; i < stat->nbufs; i++) {
struct ispstat_buffer *curr = &stat->buf[i];
/* We cannot deal with the active buffer. */
if (curr == stat->active_buf)
continue;
/* Don't take uninitialised buffers. */
if (curr->frame_number == stat->max_frame)
continue;
/* Found correct number. */
if (curr->frame_number == frame_number)
return curr;
}
return NULL;
}
/**
* ispstat_stats_available - Check for stats available of specified frame.
* @aewbdata: Pointer to return AE AWB statistics data
*
* Returns 0 if successful, or -1 if statistics are unavailable.
**/
struct ispstat_buffer *ispstat_buf_get(struct ispstat *stat,
void __user *ptr,
unsigned int frame_number)
{
int rval = 0;
unsigned long flags;
struct ispstat_buffer *buf;
spin_lock_irqsave(&stat->lock, flags);
buf = ispstat_buf_find(stat, frame_number);
if (!buf) {
spin_unlock_irqrestore(&stat->lock, flags);
dev_dbg(stat->dev, "%s: cannot find requested buffer. "
"frame_number = %d\n", stat->tag, frame_number);
return ERR_PTR(-EBUSY);
}
stat->locked_buf = buf;
spin_unlock_irqrestore(&stat->lock, flags);
rval = copy_to_user((void *)ptr,
buf->virt_addr,
stat->buf_size);
if (rval) {
dev_info(stat->dev,
"%s: failed copying %d bytes of stat data\n",
stat->tag, rval);
buf = ERR_PTR(-EFAULT);
ispstat_buf_release(stat);
}
return buf;
}
void ispstat_buf_release(struct ispstat *stat)
{
unsigned long flags;
spin_lock_irqsave(&stat->lock, flags);
stat->locked_buf = NULL;
spin_unlock_irqrestore(&stat->lock, flags);
}
void ispstat_bufs_free(struct ispstat *stat)
{
struct isp_device *isp = dev_get_drvdata(stat->dev);
int i;
for (i = 0; i < stat->nbufs; i++) {
struct ispstat_buffer *buf = &stat->buf[i];
if (!stat->dma_buf) {
if (!buf->iommu_addr)
continue;
iommu_vfree(isp->iommu, buf->iommu_addr);
} else {
if (!buf->virt_addr)
continue;
dma_free_coherent(stat->dev, stat->buf_alloc_size,
buf->virt_addr, buf->dma_addr);
}
buf->iommu_addr = 0;
buf->dma_addr = 0;
buf->virt_addr = NULL;
}
stat->buf_alloc_size = 0;
}
static int ispstat_bufs_alloc_iommu(struct ispstat *stat, unsigned int size)
{
struct isp_device *isp = dev_get_drvdata(stat->dev);
int i;
stat->buf_alloc_size = size;
for (i = 0; i < stat->nbufs; i++) {
struct ispstat_buffer *buf = &stat->buf[i];
WARN_ON(buf->dma_addr);
buf->iommu_addr = iommu_vmalloc(isp->iommu, 0, size,
IOMMU_FLAG);
if (buf->iommu_addr == 0) {
dev_err(stat->dev,
"%s stat: Can't acquire memory for "
"buffer %d\n", stat->tag, i);
ispstat_bufs_free(stat);
return -ENOMEM;
}
buf->virt_addr = da_to_va(isp->iommu, (u32)buf->iommu_addr);
buf->frame_number = stat->max_frame;
}
stat->dma_buf = 0;
return 0;
}
static int ispstat_bufs_alloc_dma(struct ispstat *stat, unsigned int size)
{
int i;
/* dma_alloc_coherent() size is PAGE_ALIGNED */
size = PAGE_ALIGN(size);
stat->buf_alloc_size = size;
for (i = 0; i < stat->nbufs; i++) {
struct ispstat_buffer *buf = &stat->buf[i];
WARN_ON(buf->iommu_addr);
buf->virt_addr = dma_alloc_coherent(stat->dev, size,
&buf->dma_addr, GFP_KERNEL | GFP_DMA);
if (!buf->virt_addr || !buf->dma_addr) {
dev_info(stat->dev,
"%s stat: Can't acquire memory for "
"DMA buffer %d\n", stat->tag, i);
ispstat_bufs_free(stat);
return -ENOMEM;
}
buf->frame_number = stat->max_frame;
}
stat->dma_buf = 1;
return 0;
}
int ispstat_bufs_alloc(struct ispstat *stat,
unsigned int size, int dma_buf)
{
struct isp_device *isp = dev_get_drvdata(stat->dev);
unsigned long flags;
int ret = 0;
int i;
spin_lock_irqsave(&stat->lock, flags);
BUG_ON(stat->locked_buf != NULL);
dma_buf = dma_buf ? 1 : 0;
/* Are the old buffers big enough? */
if ((stat->buf_alloc_size >= size) && (stat->dma_buf == dma_buf)) {
for (i = 0; i < stat->nbufs; i++)
stat->buf[i].frame_number = stat->max_frame;
spin_unlock_irqrestore(&stat->lock, flags);
goto out;
}
if (isp->running != ISP_STOPPED) {
dev_info(stat->dev,
"%s stat: trying to configure when busy\n",
stat->tag);
spin_unlock_irqrestore(&stat->lock, flags);
return -EBUSY;
}
spin_unlock_irqrestore(&stat->lock, flags);
ispstat_bufs_free(stat);
if (dma_buf)
ret = ispstat_bufs_alloc_dma(stat, size);
else
ret = ispstat_bufs_alloc_iommu(stat, size);
if (ret)
size = 0;
out:
stat->buf_size = size;
stat->active_buf = NULL;
return ret;
}
int ispstat_init(struct device *dev, char *tag, struct ispstat *stat,
unsigned int nbufs, unsigned int max_frame)
{
BUG_ON(nbufs < 2);
BUG_ON(max_frame < 2);
BUG_ON(nbufs >= max_frame);
memset(stat, 0, sizeof(*stat));
stat->buf = kcalloc(nbufs, sizeof(*stat->buf), GFP_KERNEL);
if (!stat->buf)
return -ENOMEM;
spin_lock_init(&stat->lock);
stat->nbufs = nbufs;
stat->dev = dev;
stat->tag = tag;
stat->max_frame = max_frame;
stat->frame_number = 1;
return 0;
}
void ispstat_free(struct ispstat *stat)
{
ispstat_bufs_free(stat);
kfree(stat->buf);
}