blob: 0812bf58cef9a55f5823804681d1d94dae9a8e7b [file] [log] [blame]
/* 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.
*
*/
#include <linux/msm_adsp.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/android_pmem.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/clk.h>
#include <mach/msm_adsp.h>
#include "msm_vfe7x.h"
#define QDSP_CMDQUEUE QDSP_vfeCommandQueue
#define VFE_RESET_CMD 0
#define VFE_START_CMD 1
#define VFE_STOP_CMD 2
#define VFE_FRAME_ACK 20
#define STATS_AF_ACK 21
#define STATS_WE_ACK 22
#define MSG_STOP_ACK 1
#define MSG_SNAPSHOT 2
#define MSG_OUTPUT1 6
#define MSG_OUTPUT2 7
#define MSG_STATS_AF 8
#define MSG_STATS_WE 9
static struct msm_adsp_module *qcam_mod;
static struct msm_adsp_module *vfe_mod;
static struct msm_vfe_callback *resp;
static struct vfe_frame_extra *extdata;
struct mutex vfe_lock;
static void *vfe_syncdata;
static uint8_t vfestopped;
static struct stop_event stopevent;
static struct clk *ebi1_clk;
static const char *const ebi1_clk_name = "ebi1_clk";
static void vfe_7x_convert(struct msm_vfe_phy_info *pinfo,
enum vfe_resp_msg type,
void *data, void **ext, int *elen)
{
switch (type) {
case VFE_MSG_OUTPUT1:
case VFE_MSG_OUTPUT2:{
pinfo->y_phy = ((struct vfe_endframe *)data)->y_address;
pinfo->cbcr_phy =
((struct vfe_endframe *)data)->cbcr_address;
CDBG("vfe_7x_convert, y_phy = 0x%x, cbcr_phy = 0x%x\n",
pinfo->y_phy, pinfo->cbcr_phy);
((struct vfe_frame_extra *)extdata)->bl_evencol =
((struct vfe_endframe *)data)->blacklevelevencolumn;
((struct vfe_frame_extra *)extdata)->bl_oddcol =
((struct vfe_endframe *)data)->blackleveloddcolumn;
((struct vfe_frame_extra *)extdata)->g_def_p_cnt =
((struct vfe_endframe *)data)->
greendefectpixelcount;
((struct vfe_frame_extra *)extdata)->r_b_def_p_cnt =
((struct vfe_endframe *)data)->
redbluedefectpixelcount;
*ext = extdata;
*elen = sizeof(*extdata);
}
break;
case VFE_MSG_STATS_AF:
case VFE_MSG_STATS_WE:
pinfo->sbuf_phy = *(uint32_t *) data;
break;
default:
break;
}
}
static void vfe_7x_ops(void *driver_data, unsigned id, size_t len,
void (*getevent) (void *ptr, size_t len))
{
uint32_t evt_buf[3];
struct msm_vfe_resp *rp;
void *data;
len = (id == (uint16_t)-1) ? 0 : len;
data = resp->vfe_alloc(sizeof(struct msm_vfe_resp) + len,
vfe_syncdata,
GFP_ATOMIC);
if (!data) {
pr_err("rp: cannot allocate buffer\n");
return;
}
rp = (struct msm_vfe_resp *)data;
rp->evt_msg.len = len;
if (id == ((uint16_t)-1)) {
/* event */
rp->type = VFE_EVENT;
rp->evt_msg.type = MSM_CAMERA_EVT;
getevent(evt_buf, sizeof(evt_buf));
rp->evt_msg.msg_id = evt_buf[0];
resp->vfe_resp(rp, MSM_CAM_Q_VFE_EVT,
vfe_syncdata,
GFP_ATOMIC);
} else {
/* messages */
rp->evt_msg.type = MSM_CAMERA_MSG;
rp->evt_msg.msg_id = id;
rp->evt_msg.data = rp + 1;
getevent(rp->evt_msg.data, len);
switch (rp->evt_msg.msg_id) {
case MSG_SNAPSHOT:
rp->type = VFE_MSG_SNAPSHOT;
break;
case MSG_OUTPUT1:
rp->type = VFE_MSG_OUTPUT1;
vfe_7x_convert(&(rp->phy), VFE_MSG_OUTPUT1,
rp->evt_msg.data, &(rp->extdata),
&(rp->extlen));
break;
case MSG_OUTPUT2:
rp->type = VFE_MSG_OUTPUT2;
vfe_7x_convert(&(rp->phy), VFE_MSG_OUTPUT2,
rp->evt_msg.data, &(rp->extdata),
&(rp->extlen));
break;
case MSG_STATS_AF:
rp->type = VFE_MSG_STATS_AF;
vfe_7x_convert(&(rp->phy), VFE_MSG_STATS_AF,
rp->evt_msg.data, NULL, NULL);
break;
case MSG_STATS_WE:
rp->type = VFE_MSG_STATS_WE;
vfe_7x_convert(&(rp->phy), VFE_MSG_STATS_WE,
rp->evt_msg.data, NULL, NULL);
CDBG("MSG_STATS_WE: phy = 0x%x\n", rp->phy.sbuf_phy);
break;
case MSG_STOP_ACK:
rp->type = VFE_MSG_GENERAL;
stopevent.state = 1;
wake_up(&stopevent.wait);
break;
default:
rp->type = VFE_MSG_GENERAL;
break;
}
resp->vfe_resp(rp, MSM_CAM_Q_VFE_MSG, vfe_syncdata, GFP_ATOMIC);
}
}
static struct msm_adsp_ops vfe_7x_sync = {
.event = vfe_7x_ops,
};
static int vfe_7x_enable(struct camera_enable_cmd *enable)
{
int rc = -EFAULT;
if (!strcmp(enable->name, "QCAMTASK"))
rc = msm_adsp_enable(qcam_mod);
else if (!strcmp(enable->name, "VFETASK"))
rc = msm_adsp_enable(vfe_mod);
return rc;
}
static int vfe_7x_disable(struct camera_enable_cmd *enable,
struct platform_device *dev __attribute__ ((unused)))
{
int rc = -EFAULT;
if (!strcmp(enable->name, "QCAMTASK"))
rc = msm_adsp_disable(qcam_mod);
else if (!strcmp(enable->name, "VFETASK"))
rc = msm_adsp_disable(vfe_mod);
return rc;
}
static int vfe_7x_stop(void)
{
int rc = 0;
uint32_t stopcmd = VFE_STOP_CMD;
rc = msm_adsp_write(vfe_mod, QDSP_CMDQUEUE, &stopcmd, sizeof(uint32_t));
if (rc < 0) {
CDBG("%s:%d: failed rc = %d \n", __func__, __LINE__, rc);
return rc;
}
stopevent.state = 0;
rc = wait_event_timeout(stopevent.wait,
stopevent.state != 0,
msecs_to_jiffies(stopevent.timeout));
return rc;
}
static void vfe_7x_release(struct platform_device *pdev)
{
struct msm_sensor_ctrl *sctrl =
&((struct msm_sync *)vfe_syncdata)->sctrl;
mutex_lock(&vfe_lock);
vfe_syncdata = NULL;
mutex_unlock(&vfe_lock);
if (!vfestopped) {
CDBG("%s:%d:Calling vfe_7x_stop()\n", __func__, __LINE__);
vfe_7x_stop();
} else
vfestopped = 0;
msm_adsp_disable(qcam_mod);
msm_adsp_disable(vfe_mod);
if (sctrl)
sctrl->s_release();
msm_adsp_put(qcam_mod);
msm_adsp_put(vfe_mod);
msm_camio_disable(pdev);
if (ebi1_clk) {
clk_set_rate(ebi1_clk, 0);
clk_put(ebi1_clk);
ebi1_clk = 0;
}
kfree(extdata);
extdata = 0;
}
static int vfe_7x_init(struct msm_vfe_callback *presp,
struct platform_device *dev)
{
int rc = 0;
init_waitqueue_head(&stopevent.wait);
stopevent.timeout = 200;
stopevent.state = 0;
if (presp && presp->vfe_resp)
resp = presp;
else
return -EIO;
ebi1_clk = clk_get(NULL, ebi1_clk_name);
if (!ebi1_clk) {
pr_err("%s: could not get %s\n", __func__, ebi1_clk_name);
return -EIO;
}
rc = clk_set_rate(ebi1_clk, 128000000);
if (rc < 0) {
pr_err("%s: clk_set_rate(%s) failed: %d\n", __func__,
ebi1_clk_name, rc);
return rc;
}
/* Bring up all the required GPIOs and Clocks */
rc = msm_camio_enable(dev);
if (rc < 0)
return rc;
msm_camio_camif_pad_reg_reset();
extdata = kmalloc(sizeof(struct vfe_frame_extra), GFP_ATOMIC);
if (!extdata) {
rc = -ENOMEM;
goto init_fail;
}
rc = msm_adsp_get("QCAMTASK", &qcam_mod, &vfe_7x_sync, NULL);
if (rc) {
rc = -EBUSY;
goto get_qcam_fail;
}
rc = msm_adsp_get("VFETASK", &vfe_mod, &vfe_7x_sync, NULL);
if (rc) {
rc = -EBUSY;
goto get_vfe_fail;
}
return 0;
get_vfe_fail:
msm_adsp_put(qcam_mod);
get_qcam_fail:
kfree(extdata);
init_fail:
return rc;
}
static int vfe_7x_config_axi(int mode, struct axidata *ad, struct axiout *ao)
{
struct msm_pmem_region *regptr;
unsigned long *bptr;
int cnt;
int rc = 0;
if (mode == OUTPUT_1 || mode == OUTPUT_1_AND_2) {
regptr = ad->region;
CDBG("bufnum1 = %d\n", ad->bufnum1);
CDBG("config_axi1: O1, phy = 0x%lx, y_off = %d, cbcr_off =%d\n",
regptr->paddr, regptr->info.y_off, regptr->info.cbcr_off);
bptr = &ao->output1buffer1_y_phy;
for (cnt = 0; cnt < ad->bufnum1; cnt++) {
*bptr = regptr->paddr + regptr->info.y_off;
bptr++;
*bptr = regptr->paddr + regptr->info.cbcr_off;
bptr++;
regptr++;
}
regptr--;
for (cnt = 0; cnt < (8 - ad->bufnum1); cnt++) {
*bptr = regptr->paddr + regptr->info.y_off;
bptr++;
*bptr = regptr->paddr + regptr->info.cbcr_off;
bptr++;
}
}
/* if OUTPUT1 or Both */
if (mode == OUTPUT_2 || mode == OUTPUT_1_AND_2) {
regptr = &(ad->region[ad->bufnum1]);
CDBG("bufnum2 = %d\n", ad->bufnum2);
CDBG("config_axi2: O2, phy = 0x%lx, y_off = %d, cbcr_off =%d\n",
regptr->paddr, regptr->info.y_off, regptr->info.cbcr_off);
bptr = &ao->output2buffer1_y_phy;
for (cnt = 0; cnt < ad->bufnum2; cnt++) {
*bptr = regptr->paddr + regptr->info.y_off;
bptr++;
*bptr = regptr->paddr + regptr->info.cbcr_off;
bptr++;
regptr++;
}
regptr--;
for (cnt = 0; cnt < (8 - ad->bufnum2); cnt++) {
*bptr = regptr->paddr + regptr->info.y_off;
bptr++;
*bptr = regptr->paddr + regptr->info.cbcr_off;
bptr++;
}
}
return rc;
}
static int vfe_7x_config(struct msm_vfe_cfg_cmd *cmd, void *data)
{
int rc = 0;
int i;
struct msm_pmem_region *regptr = NULL;
struct vfe_stats_ack sack;
struct axidata *axid = NULL;
struct axiout axio;
void *cmd_data_alloc = NULL;
struct msm_vfe_command_7k vfecmd;
if (cmd->cmd_type != CMD_FRAME_BUF_RELEASE &&
cmd->cmd_type != CMD_STATS_BUF_RELEASE &&
cmd->cmd_type != CMD_STATS_AF_BUF_RELEASE) {
if (copy_from_user(&vfecmd,
(void __user *)(cmd->value),
sizeof(struct msm_vfe_command_7k))) {
rc = -EFAULT;
goto config_error;
}
}
switch (cmd->cmd_type) {
case CMD_STATS_AEC_AWB_ENABLE:
case CMD_STATS_AXI_CFG:{
struct vfe_stats_we_cfg scfg;
axid = data;
if (!axid) {
rc = -EFAULT;
goto config_error;
}
if (vfecmd.length != sizeof(typeof(scfg))) {
rc = -EIO;
pr_err
("msm_camera: %s: cmd %d: "\
"user-space data size %d "\
"!= kernel data size %d\n", __func__,
cmd->cmd_type, vfecmd.length,
sizeof(typeof(scfg)));
goto config_error;
}
if (copy_from_user(&scfg,
(void __user *)(vfecmd.value),
vfecmd.length)) {
rc = -EFAULT;
goto config_error;
}
CDBG("STATS_ENABLE: bufnum = %d, enabling = %d\n",
axid->bufnum1, scfg.wb_expstatsenable);
if (axid->bufnum1 > 0) {
regptr = axid->region;
for (i = 0; i < axid->bufnum1; i++) {
CDBG("STATS_ENABLE, phy = 0x%lx\n",
regptr->paddr);
scfg.wb_expstatoutputbuffer[i] =
(void *)regptr->paddr;
regptr++;
}
vfecmd.value = &scfg;
} else {
rc = -EINVAL;
goto config_error;
}
}
break;
case CMD_STATS_AF_ENABLE:
case CMD_STATS_AF_AXI_CFG:{
struct vfe_stats_af_cfg sfcfg;
axid = data;
if (!axid) {
rc = -EFAULT;
goto config_error;
}
if (vfecmd.length > sizeof(typeof(sfcfg))) {
pr_err
("msm_camera: %s: cmd %d: user-space "\
"data %d exceeds kernel buffer %d\n",
__func__, cmd->cmd_type, vfecmd.length,
sizeof(typeof(sfcfg)));
rc = -EIO;
goto config_error;
}
if (copy_from_user(&sfcfg,
(void __user *)(vfecmd.value),
vfecmd.length)) {
rc = -EFAULT;
goto config_error;
}
CDBG("AF_ENABLE: bufnum = %d, enabling = %d\n",
axid->bufnum1, sfcfg.af_enable);
if (axid->bufnum1 > 0) {
regptr = axid->region;
for (i = 0; i < axid->bufnum1; i++) {
CDBG("STATS_ENABLE, phy = 0x%lx\n",
regptr->paddr);
sfcfg.af_outbuf[i] =
(void *)regptr->paddr;
regptr++;
}
vfecmd.value = &sfcfg;
} else {
rc = -EINVAL;
goto config_error;
}
}
break;
case CMD_FRAME_BUF_RELEASE:{
struct msm_frame *b;
unsigned long p;
struct vfe_outputack fack;
if (!data) {
rc = -EFAULT;
goto config_error;
}
b = (struct msm_frame *)(cmd->value);
p = *(unsigned long *)data;
fack.header = VFE_FRAME_ACK;
fack.output2newybufferaddress = (void *)(p + b->y_off);
fack.output2newcbcrbufferaddress =
(void *)(p + b->cbcr_off);
vfecmd.queue = QDSP_CMDQUEUE;
vfecmd.length = sizeof(struct vfe_outputack);
vfecmd.value = &fack;
}
break;
case CMD_SNAP_BUF_RELEASE:
break;
case CMD_STATS_BUF_RELEASE:{
CDBG("vfe_7x_config: CMD_STATS_BUF_RELEASE\n");
if (!data) {
rc = -EFAULT;
goto config_error;
}
sack.header = STATS_WE_ACK;
sack.bufaddr = (void *)*(uint32_t *) data;
vfecmd.queue = QDSP_CMDQUEUE;
vfecmd.length = sizeof(struct vfe_stats_ack);
vfecmd.value = &sack;
}
break;
case CMD_STATS_AF_BUF_RELEASE:{
CDBG("vfe_7x_config: CMD_STATS_AF_BUF_RELEASE\n");
if (!data) {
rc = -EFAULT;
goto config_error;
}
sack.header = STATS_AF_ACK;
sack.bufaddr = (void *)*(uint32_t *) data;
vfecmd.queue = QDSP_CMDQUEUE;
vfecmd.length = sizeof(struct vfe_stats_ack);
vfecmd.value = &sack;
}
break;
case CMD_GENERAL:
case CMD_STATS_DISABLE:{
uint8_t buf[256];
void *tmp = buf;
if (vfecmd.length > sizeof(buf)) {
cmd_data_alloc = tmp =
kmalloc(vfecmd.length, GFP_ATOMIC);
if (!cmd_data_alloc) {
rc = -ENOMEM;
goto config_error;
}
}
if (copy_from_user(tmp,
(void __user *)(vfecmd.value),
vfecmd.length)) {
rc = -EFAULT;
goto config_error;
}
vfecmd.value = tmp;
if (vfecmd.queue == QDSP_CMDQUEUE) {
switch (*(uint32_t *) vfecmd.value) {
case VFE_RESET_CMD:
msm_camio_vfe_blk_reset();
msm_camio_camif_pad_reg_reset_2();
vfestopped = 0;
break;
case VFE_START_CMD:
msm_camio_camif_pad_reg_reset_2();
vfestopped = 0;
break;
case VFE_STOP_CMD:
vfestopped = 1;
goto config_send;
default:
break;
}
} /* QDSP_CMDQUEUE */
}
break;
case CMD_AXI_CFG_OUT1:{
axid = data;
if (!axid) {
rc = -EFAULT;
goto config_error;
}
if (copy_from_user(&axio, (void *)(vfecmd.value),
sizeof(axio))) {
rc = -EFAULT;
goto config_error;
}
vfe_7x_config_axi(OUTPUT_1, axid, &axio);
vfecmd.value = &axio;
}
break;
case CMD_AXI_CFG_OUT2:
case CMD_RAW_PICT_AXI_CFG:{
axid = data;
if (!axid) {
rc = -EFAULT;
goto config_error;
}
if (copy_from_user(&axio, (void __user *)(vfecmd.value),
sizeof(axio))) {
rc = -EFAULT;
goto config_error;
}
vfe_7x_config_axi(OUTPUT_2, axid, &axio);
vfecmd.value = &axio;
}
break;
case CMD_AXI_CFG_SNAP_O1_AND_O2:{
axid = data;
if (!axid) {
rc = -EFAULT;
goto config_error;
}
if (copy_from_user(&axio, (void __user *)(vfecmd.value),
sizeof(axio))) {
rc = -EFAULT;
goto config_error;
}
vfe_7x_config_axi(OUTPUT_1_AND_2, axid, &axio);
vfecmd.value = &axio;
}
break;
default:
break;
} /* switch */
if (vfestopped)
goto config_error;
config_send:
CDBG("send adsp command = %d\n", *(uint32_t *)vfecmd.value);
rc = msm_adsp_write(vfe_mod, vfecmd.queue, vfecmd.value, vfecmd.length);
config_error:
kfree(cmd_data_alloc);
return rc;
}
void msm_camvfe_fn_init(struct msm_camvfe_fn *fptr, void *data)
{
mutex_init(&vfe_lock);
fptr->vfe_init = vfe_7x_init;
fptr->vfe_enable = vfe_7x_enable;
fptr->vfe_config = vfe_7x_config;
fptr->vfe_disable = vfe_7x_disable;
fptr->vfe_release = vfe_7x_release;
vfe_syncdata = data;
}