/*
* Samsung S5P SoC series camera interface (camera capture) driver
*
* Copyright (c) 2010 Samsung Electronics Co., Ltd
* Author: Sylwester Nawrocki, <s.nawrocki@samsung.com>
*
* This program 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.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/bug.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/i2c.h>
#include <linux/videodev2.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mem2mem.h>
#include <media/videobuf2-core.h>
#include <media/videobuf2-dma-contig.h>
#include "fimc-core.h"
static struct v4l2_subdev *fimc_subdev_register(struct fimc_dev *fimc,
struct s5p_fimc_isp_info *isp_info)
{
struct i2c_adapter *i2c_adap;
struct fimc_vid_cap *vid_cap = &fimc->vid_cap;
struct v4l2_subdev *sd = NULL;
i2c_adap = i2c_get_adapter(isp_info->i2c_bus_num);
if (!i2c_adap)
return ERR_PTR(-ENOMEM);
sd = v4l2_i2c_new_subdev_board(&vid_cap->v4l2_dev, i2c_adap,
isp_info->board_info, NULL);
if (!sd) {
v4l2_err(&vid_cap->v4l2_dev, "failed to acquire subdev\n");
return NULL;
}
v4l2_info(&vid_cap->v4l2_dev, "subdevice %s registered successfuly\n",
isp_info->board_info->type);
return sd;
}
static void fimc_subdev_unregister(struct fimc_dev *fimc)
{
struct fimc_vid_cap *vid_cap = &fimc->vid_cap;
struct i2c_client *client;
if (vid_cap->input_index < 0)
return; /* Subdevice already released or not registered. */
if (vid_cap->sd) {
v4l2_device_unregister_subdev(vid_cap->sd);
client = v4l2_get_subdevdata(vid_cap->sd);
i2c_unregister_device(client);
i2c_put_adapter(client->adapter);
vid_cap->sd = NULL;
}
vid_cap->input_index = -1;
}
/**
* fimc_subdev_attach - attach v4l2_subdev to camera host interface
*
* @fimc: FIMC device information
* @index: index to the array of available subdevices,
* -1 for full array search or non negative value
* to select specific subdevice
*/
static int fimc_subdev_attach(struct fimc_dev *fimc, int index)
{
struct fimc_vid_cap *vid_cap = &fimc->vid_cap;
struct s5p_platform_fimc *pdata = fimc->pdata;
struct s5p_fimc_isp_info *isp_info;
struct v4l2_subdev *sd;
int i;
for (i = 0; i < FIMC_MAX_CAMIF_CLIENTS; ++i) {
isp_info = pdata->isp_info[i];
if (!isp_info || (index >= 0 && i != index))
continue;
sd = fimc_subdev_register(fimc, isp_info);
if (!IS_ERR_OR_NULL(sd)) {
vid_cap->sd = sd;
vid_cap->input_index = i;
return 0;
}
}
vid_cap->input_index = -1;
vid_cap->sd = NULL;
v4l2_err(&vid_cap->v4l2_dev, "fimc%d: sensor attach failed\n",
fimc->id);
return -ENODEV;
}
static int fimc_isp_subdev_init(struct fimc_dev *fimc, unsigned int index)
{
struct s5p_fimc_isp_info *isp_info;
int ret;
if (index >= FIMC_MAX_CAMIF_CLIENTS)
return -EINVAL;
isp_info = fimc->pdata->isp_info[index];
if (!isp_info)
return -EINVAL;
if (isp_info->clk_frequency)
clk_set_rate(fimc->clock[CLK_CAM], isp_info->clk_frequency);
ret = clk_enable(fimc->clock[CLK_CAM]);
if (ret)
return ret;
ret = fimc_subdev_attach(fimc, index);
if (ret)
return ret;
ret = fimc_hw_set_camera_polarity(fimc, isp_info);
if (ret)
return ret;
ret = v4l2_subdev_call(fimc->vid_cap.sd, core, s_power, 1);
if (!ret)
return ret;
/* enabling power failed so unregister subdev */
fimc_subdev_unregister(fimc);
v4l2_err(&fimc->vid_cap.v4l2_dev, "ISP initialization failed: %d\n",
ret);
return ret;
}
static int fimc_stop_capture(struct fimc_dev *fimc)
{
unsigned long flags;
struct fimc_vid_cap *cap;
struct fimc_vid_buffer *buf;
cap = &fimc->vid_cap;
if (!fimc_capture_active(fimc))
return 0;
spin_lock_irqsave(&fimc->slock, flags);
set_bit(ST_CAPT_SHUT, &fimc->state);
fimc_deactivate_capture(fimc);
spin_unlock_irqrestore(&fimc->slock, <