810 lines
20 KiB
C
810 lines
20 KiB
C
/*
|
|
* linux-3.10/drivers/media/platform/sunxi-vin/vin-csi/sunxi_csi.c
|
|
*
|
|
* Copyright (c) 2007-2017 Allwinnertech Co., Ltd.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
|
|
/*
|
|
******************************************************************************
|
|
*
|
|
* sunxi_csi.c
|
|
*
|
|
* Hawkview ISP - sunxi_csi.c module
|
|
*
|
|
* Copyright (c) 2015 by Allwinnertech Co., Ltd. http://www.allwinnertech.com
|
|
*
|
|
* Version Author Date Description
|
|
*
|
|
* 3.0 Yang Feng 2015/02/27 ISP Tuning Tools Support
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
#include <linux/platform_device.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-mediabus.h>
|
|
#include <media/v4l2-subdev.h>
|
|
#include <media/sunxi_camera_v2.h>
|
|
|
|
#include "bsp_csi.h"
|
|
#include "parser_reg.h"
|
|
#include "sunxi_csi.h"
|
|
#include "../platform/platform_cfg.h"
|
|
|
|
#define CSI_MODULE_NAME "vin_csi"
|
|
|
|
#define IS_FLAG(x, y) (((x)&(y)) == y)
|
|
|
|
static LIST_HEAD(csi_drv_list);
|
|
|
|
#define CSI_MAX_WIDTH 0xffff
|
|
#define CSI_MAX_HEIGHT 0xffff
|
|
|
|
static struct csi_format sunxi_csi_formats[] = {
|
|
{
|
|
.code = V4L2_MBUS_FMT_YUYV8_2X8,
|
|
.seq = SEQ_YUYV,
|
|
.infmt = FMT_YUV422,
|
|
.data_width = 8,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_YVYU8_2X8,
|
|
.seq = SEQ_YVYU,
|
|
.infmt = FMT_YUV422,
|
|
.data_width = 8,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_UYVY8_2X8,
|
|
.seq = SEQ_UYVY,
|
|
.infmt = FMT_YUV422,
|
|
.data_width = 8,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_VYUY8_2X8,
|
|
.seq = SEQ_VYUY,
|
|
.infmt = FMT_YUV422,
|
|
.data_width = 8,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_YUYV8_1X16,
|
|
.seq = SEQ_YUYV,
|
|
.infmt = FMT_YUV422,
|
|
.data_width = 16,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_YVYU8_1X16,
|
|
.seq = SEQ_YVYU,
|
|
.infmt = FMT_YUV422,
|
|
.data_width = 16,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_UYVY8_1X16,
|
|
.seq = SEQ_UYVY,
|
|
.infmt = FMT_YUV422,
|
|
.data_width = 16,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_VYUY8_1X16,
|
|
.seq = SEQ_VYUY,
|
|
.infmt = FMT_YUV422,
|
|
.data_width = 16,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SBGGR8_1X8,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 8,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SGBRG8_1X8,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 8,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SGRBG8_1X8,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 8,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SRGGB8_1X8,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 8,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SBGGR10_1X10,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 10,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SGBRG10_1X10,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 10,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SGRBG10_1X10,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 10,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SRGGB10_1X10,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 10,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SBGGR12_1X12,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 12,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SGBRG12_1X12,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 12,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SGRBG12_1X12,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 12,
|
|
}, {
|
|
.code = V4L2_MBUS_FMT_SRGGB12_1X12,
|
|
.infmt = FMT_RAW,
|
|
.data_width = 12,
|
|
}
|
|
};
|
|
|
|
static int __csi_pin_config(struct csi_dev *dev, int enable)
|
|
{
|
|
#ifndef FPGA_VER
|
|
char pinctrl_names[10] = "";
|
|
|
|
if (!IS_ERR_OR_NULL(dev->pctrl))
|
|
devm_pinctrl_put(dev->pctrl);
|
|
|
|
if (1 == enable) {
|
|
strcpy(pinctrl_names, "default");
|
|
} else {
|
|
strcpy(pinctrl_names, "sleep");
|
|
}
|
|
dev->pctrl = devm_pinctrl_get_select(&dev->pdev->dev, pinctrl_names);
|
|
if (IS_ERR_OR_NULL(dev->pctrl)) {
|
|
vin_err("csi%d request pinctrl handle failed!\n", dev->id);
|
|
return -EINVAL;
|
|
}
|
|
usleep_range(5000, 6000);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int __csi_pin_release(struct csi_dev *dev)
|
|
{
|
|
#ifndef FPGA_VER
|
|
if (!IS_ERR_OR_NULL(dev->pctrl))
|
|
devm_pinctrl_put(dev->pctrl);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int __csi_set_fmt_hw(struct csi_dev *csi)
|
|
{
|
|
struct v4l2_mbus_framefmt *mf = &csi->mf;
|
|
int i;
|
|
#if defined(CONFIG_ARCH_SUN8IW11P1) || defined(CONFIG_ARCH_SUN50IW1P1)
|
|
struct mbus_framefmt_res *res = (void *)mf->reserved;
|
|
int ret;
|
|
|
|
for (i = 0; i < csi->bus_info.ch_total_num; i++)
|
|
csi->bus_info.bus_ch_fmt[i] = mf->code;
|
|
for (i = 0; i < csi->bus_info.ch_total_num; i++) {
|
|
csi->frame_info.pix_ch_fmt[i] = res->res_pix_fmt;
|
|
csi->frame_info.ch_field[i] = mf->field;
|
|
csi->frame_info.ch_size[i].width = mf->width;
|
|
csi->frame_info.ch_size[i].height = mf->height;
|
|
csi->frame_info.ch_offset[i].hoff = 0;
|
|
csi->frame_info.ch_offset[i].voff = 0;
|
|
}
|
|
csi->frame_info.arrange = csi->arrange;
|
|
|
|
ret = bsp_csi_set_fmt(csi->id, &csi->bus_info, &csi->frame_info);
|
|
if (ret < 0) {
|
|
vin_err("bsp_csi_set_fmt error at %s!\n", __func__);
|
|
return -1;
|
|
}
|
|
ret = bsp_csi_set_size(csi->id, &csi->bus_info, &csi->frame_info);
|
|
if (ret < 0) {
|
|
vin_err("bsp_csi_set_size error at %s!\n", __func__);
|
|
return -1;
|
|
}
|
|
#else
|
|
struct prs_ncsi_bt656_header bt656_header;
|
|
struct prs_mcsi_if_cfg mcsi_if;
|
|
struct prs_cap_mode mode;
|
|
|
|
memset(&bt656_header, 0, sizeof(bt656_header));
|
|
memset(&mcsi_if, 0, sizeof(mcsi_if));
|
|
|
|
csi->ncsi_if.seq = csi->csi_fmt->seq;
|
|
mcsi_if.seq = csi->csi_fmt->seq;
|
|
|
|
switch (csi->csi_fmt->data_width) {
|
|
case 8:
|
|
csi->ncsi_if.dw = DW_8BIT;
|
|
break;
|
|
case 10:
|
|
csi->ncsi_if.dw = DW_10BIT;
|
|
break;
|
|
case 12:
|
|
csi->ncsi_if.dw = DW_12BIT;
|
|
break;
|
|
default:
|
|
csi->ncsi_if.dw = DW_8BIT;
|
|
break;
|
|
}
|
|
|
|
switch (mf->field) {
|
|
case V4L2_FIELD_ANY:
|
|
case V4L2_FIELD_NONE:
|
|
csi->ncsi_if.type = PROGRESSED;
|
|
csi->ncsi_if.mode = FRAME_MODE;
|
|
mcsi_if.mode = FRAME_MODE;
|
|
break;
|
|
case V4L2_FIELD_TOP:
|
|
case V4L2_FIELD_BOTTOM:
|
|
csi->ncsi_if.type = INTERLACE;
|
|
csi->ncsi_if.mode = FIELD_MODE;
|
|
mcsi_if.mode = FIELD_MODE;
|
|
break;
|
|
case V4L2_FIELD_INTERLACED:
|
|
csi->ncsi_if.type = INTERLACE;
|
|
csi->ncsi_if.mode = FRAME_MODE;
|
|
mcsi_if.mode = FRAME_MODE;
|
|
break;
|
|
default:
|
|
csi->ncsi_if.type = PROGRESSED;
|
|
csi->ncsi_if.mode = FRAME_MODE;
|
|
mcsi_if.mode = FRAME_MODE;
|
|
break;
|
|
}
|
|
|
|
switch (csi->bus_info.bus_if) {
|
|
case V4L2_MBUS_PARALLEL:
|
|
if (csi->csi_fmt->data_width == 16)
|
|
csi->ncsi_if.intf = PRS_IF_INTLV_16BIT;
|
|
else
|
|
csi->ncsi_if.intf = PRS_IF_INTLV;
|
|
break;
|
|
case V4L2_MBUS_BT656:
|
|
if (csi->csi_fmt->data_width == 16) {
|
|
if (csi->bus_info.ch_total_num == 1) {
|
|
csi->ncsi_if.intf = PRS_IF_BT1120_1CH;
|
|
} else if (csi->bus_info.ch_total_num == 2) {
|
|
csi->ncsi_if.intf = PRS_IF_BT1120_2CH;
|
|
} else if (csi->bus_info.ch_total_num == 4) {
|
|
csi->ncsi_if.intf = PRS_IF_BT1120_4CH;
|
|
}
|
|
} else {
|
|
if (csi->bus_info.ch_total_num == 1) {
|
|
csi->ncsi_if.intf = PRS_IF_BT656_1CH;
|
|
} else if (csi->bus_info.ch_total_num == 2) {
|
|
csi->ncsi_if.intf = PRS_IF_BT656_2CH;
|
|
} else if (csi->bus_info.ch_total_num == 4) {
|
|
csi->ncsi_if.intf = PRS_IF_BT656_4CH;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
switch (csi->bus_info.bus_if) {
|
|
case V4L2_MBUS_PARALLEL:
|
|
csic_prs_mode(csi->id, PRS_NCSI);
|
|
csic_prs_ncsi_if_cfg(csi->id, &csi->ncsi_if);
|
|
csic_prs_ncsi_en(csi->id, 1);
|
|
break;
|
|
case V4L2_MBUS_BT656:
|
|
csic_prs_mode(csi->id, PRS_NCSI);
|
|
bt656_header.ch0_id = 0;
|
|
bt656_header.ch1_id = 1;
|
|
bt656_header.ch2_id = 2;
|
|
bt656_header.ch3_id = 3;
|
|
csic_prs_ncsi_bt656_header_cfg(csi->id, &bt656_header);
|
|
csic_prs_ncsi_if_cfg(csi->id, &csi->ncsi_if);
|
|
csic_prs_ncsi_en(csi->id, 1);
|
|
break;
|
|
case V4L2_MBUS_CSI2:
|
|
csic_prs_mode(csi->id, PRS_MCSI);
|
|
csic_prs_mcsi_if_cfg(csi->id, &mcsi_if);
|
|
csic_prs_mcsi_en(csi->id, 1);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
if (csi->capture_mode == V4L2_MODE_IMAGE)
|
|
mode.mode = SCAP;
|
|
else
|
|
mode.mode = VCAP;
|
|
|
|
if (csi->out_size.hor_len != mf->width ||
|
|
csi->out_size.ver_len != mf->height) {
|
|
csi->out_size.hor_len = mf->width;
|
|
csi->out_size.ver_len = mf->height;
|
|
csi->out_size.hor_start = 0;
|
|
csi->out_size.ver_start = 0;
|
|
}
|
|
|
|
if (mf->field == V4L2_FIELD_INTERLACED || mf->field == V4L2_FIELD_TOP ||
|
|
mf->field == V4L2_FIELD_BOTTOM)
|
|
csi->out_size.ver_len = csi->out_size.ver_len / 2;
|
|
|
|
for (i = 0; i < csi->bus_info.ch_total_num; i++) {
|
|
csic_prs_input_fmt_cfg(csi->id, i, csi->csi_fmt->infmt);
|
|
csic_prs_output_size_cfg(csi->id, i, &csi->out_size);
|
|
}
|
|
|
|
csic_prs_capture_start(csi->id, csi->bus_info.ch_total_num, &mode);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_csi_subdev_s_power(struct v4l2_subdev *sd, int enable)
|
|
{
|
|
struct csi_dev *csi = v4l2_get_subdevdata(sd);
|
|
|
|
__csi_pin_config(csi, enable);
|
|
return 0;
|
|
}
|
|
static int sunxi_csi_subdev_s_stream(struct v4l2_subdev *sd, int enable)
|
|
{
|
|
struct csi_dev *csi = v4l2_get_subdevdata(sd);
|
|
struct v4l2_mbus_framefmt *mf = &csi->mf;
|
|
|
|
#if defined(CONFIG_ARCH_SUN8IW11P1) || defined(CONFIG_ARCH_SUN50IW1P1)
|
|
if (enable) {
|
|
bsp_csi_enable(csi->id);
|
|
bsp_csi_disable(csi->id);
|
|
bsp_csi_enable(csi->id);
|
|
__csi_set_fmt_hw(csi);
|
|
|
|
vin_log(VIN_LOG_CSI, "enable csi int in s_stream on\n");
|
|
bsp_csi_int_clear_status(csi->id, csi->cur_ch,
|
|
CSI_INT_ALL);
|
|
bsp_csi_int_enable(csi->id, csi->cur_ch,
|
|
CSI_INT_CAPTURE_DONE | CSI_INT_FRAME_DONE |
|
|
CSI_INT_BUF_0_OVERFLOW |
|
|
CSI_INT_BUF_1_OVERFLOW |
|
|
CSI_INT_BUF_2_OVERFLOW |
|
|
CSI_INT_HBLANK_OVERFLOW);
|
|
|
|
if (csi->capture_mode == V4L2_MODE_IMAGE)
|
|
bsp_csi_cap_start(csi->id, csi->bus_info.ch_total_num,
|
|
CSI_SCAP);
|
|
else
|
|
bsp_csi_cap_start(csi->id, csi->bus_info.ch_total_num,
|
|
CSI_VCAP);
|
|
} else {
|
|
vin_log(VIN_LOG_CSI, "disable csi int in s_stream off\n");
|
|
bsp_csi_int_disable(csi->id, csi->cur_ch, CSI_INT_ALL);
|
|
bsp_csi_int_clear_status(csi->id, csi->cur_ch, CSI_INT_ALL);
|
|
|
|
if (csi->capture_mode == V4L2_MODE_IMAGE)
|
|
bsp_csi_cap_stop(csi->id, csi->bus_info.ch_total_num, CSI_SCAP);
|
|
else
|
|
bsp_csi_cap_stop(csi->id, csi->bus_info.ch_total_num, CSI_VCAP);
|
|
bsp_csi_disable(csi->id);
|
|
}
|
|
#else
|
|
csic_prs_pclk_en(csi->id, enable);
|
|
if (enable) {
|
|
csic_prs_enable(csi->id);
|
|
csic_prs_disable(csi->id);
|
|
csic_prs_enable(csi->id);
|
|
__csi_set_fmt_hw(csi);
|
|
} else {
|
|
switch (csi->bus_info.bus_if) {
|
|
case V4L2_MBUS_PARALLEL:
|
|
case V4L2_MBUS_BT656:
|
|
csic_prs_ncsi_en(csi->id, 0);
|
|
break;
|
|
case V4L2_MBUS_CSI2:
|
|
csic_prs_mcsi_en(csi->id, 0);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
csic_prs_capture_stop(csi->id);
|
|
csic_prs_disable(csi->id);
|
|
}
|
|
#endif
|
|
vin_print("%s on = %d, %d*%d hoff = %d, voff = %d, %x %d\n",
|
|
__func__, enable, csi->out_size.hor_len, csi->out_size.ver_len,
|
|
csi->out_size.hor_start, csi->out_size.ver_start,
|
|
mf->code, mf->field);
|
|
|
|
return 0;
|
|
}
|
|
static int sunxi_csi_subdev_s_parm(struct v4l2_subdev *sd,
|
|
struct v4l2_streamparm *param)
|
|
{
|
|
struct csi_dev *csi = v4l2_get_subdevdata(sd);
|
|
csi->capture_mode = param->parm.capture.capturemode;
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_csi_enum_mbus_code(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_fh *fh,
|
|
struct v4l2_subdev_mbus_code_enum *code)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static struct csi_format *__csi_find_format(
|
|
struct v4l2_mbus_framefmt *mf)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sunxi_csi_formats); i++)
|
|
if (mf->code == sunxi_csi_formats[i].code)
|
|
return &sunxi_csi_formats[i];
|
|
return NULL;
|
|
}
|
|
|
|
static struct csi_format *__csi_try_format(
|
|
struct v4l2_mbus_framefmt *mf)
|
|
{
|
|
struct csi_format *csi_fmt;
|
|
|
|
csi_fmt = __csi_find_format(mf);
|
|
if (csi_fmt == NULL)
|
|
csi_fmt = &sunxi_csi_formats[0];
|
|
|
|
mf->code = csi_fmt->code;
|
|
v4l_bound_align_image(&mf->width, 1, CSI_MAX_WIDTH, csi_fmt->wd_align,
|
|
&mf->height, 1, CSI_MAX_HEIGHT, 1, 0);
|
|
return csi_fmt;
|
|
}
|
|
|
|
static struct v4l2_mbus_framefmt *__csi_get_format(
|
|
struct csi_dev *csi, struct v4l2_subdev_fh *fh,
|
|
enum v4l2_subdev_format_whence which)
|
|
{
|
|
if (which == V4L2_SUBDEV_FORMAT_TRY)
|
|
return fh ? v4l2_subdev_get_try_format(fh, 0) : NULL;
|
|
|
|
return &csi->mf;
|
|
}
|
|
|
|
static int sunxi_csi_subdev_set_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_fh *fh,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
struct csi_dev *csi = v4l2_get_subdevdata(sd);
|
|
struct v4l2_mbus_framefmt *mf;
|
|
struct csi_format *csi_fmt;
|
|
|
|
vin_log(VIN_LOG_FMT, "%s %d*%d %x %d\n", __func__,
|
|
fmt->format.width, fmt->format.height,
|
|
fmt->format.code, fmt->format.field);
|
|
|
|
mf = __csi_get_format(csi, fh, fmt->which);
|
|
|
|
if (fmt->pad == CSI_PAD_SOURCE) {
|
|
if (mf) {
|
|
mutex_lock(&csi->subdev_lock);
|
|
fmt->format = *mf;
|
|
mutex_unlock(&csi->subdev_lock);
|
|
}
|
|
return 0;
|
|
}
|
|
csi_fmt = __csi_try_format(&fmt->format);
|
|
if (mf) {
|
|
mutex_lock(&csi->subdev_lock);
|
|
*mf = fmt->format;
|
|
if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
|
|
csi->csi_fmt = csi_fmt;
|
|
mutex_unlock(&csi->subdev_lock);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_csi_subdev_get_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_fh *fh,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
struct csi_dev *csi = v4l2_get_subdevdata(sd);
|
|
struct v4l2_mbus_framefmt *mf;
|
|
|
|
mf = __csi_get_format(csi, fh, fmt->which);
|
|
if (!mf)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&csi->subdev_lock);
|
|
fmt->format = *mf;
|
|
mutex_unlock(&csi->subdev_lock);
|
|
return 0;
|
|
}
|
|
|
|
int sunxi_csi_subdev_init(struct v4l2_subdev *sd, u32 val)
|
|
{
|
|
return 0;
|
|
}
|
|
static int sunxi_csi_subdev_set_crop(struct v4l2_subdev *sd,
|
|
const struct v4l2_crop *crop)
|
|
{
|
|
struct csi_dev *csi = v4l2_get_subdevdata(sd);
|
|
csi->out_size.hor_len = crop->c.width;
|
|
csi->out_size.ver_len = crop->c.height;
|
|
csi->out_size.hor_start = crop->c.left;
|
|
csi->out_size.ver_start = crop->c.top;
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_csi_s_mbus_config(struct v4l2_subdev *sd,
|
|
const struct v4l2_mbus_config *cfg)
|
|
{
|
|
struct csi_dev *csi = v4l2_get_subdevdata(sd);
|
|
|
|
if (cfg->type == V4L2_MBUS_CSI2) {
|
|
csi->bus_info.bus_if = V4L2_MBUS_CSI2;
|
|
csi->bus_info.ch_total_num = 0;
|
|
if (IS_FLAG(cfg->flags, V4L2_MBUS_CSI2_CHANNEL_0)) {
|
|
csi->bus_info.ch_total_num++;
|
|
}
|
|
if (IS_FLAG(cfg->flags, V4L2_MBUS_CSI2_CHANNEL_1)) {
|
|
csi->bus_info.ch_total_num++;
|
|
}
|
|
if (IS_FLAG(cfg->flags, V4L2_MBUS_CSI2_CHANNEL_2)) {
|
|
csi->bus_info.ch_total_num++;
|
|
}
|
|
if (IS_FLAG(cfg->flags, V4L2_MBUS_CSI2_CHANNEL_3)) {
|
|
csi->bus_info.ch_total_num++;
|
|
}
|
|
vin_print("csi->bus_info.ch_total_num = %d\n",
|
|
csi->bus_info.ch_total_num);
|
|
} else if (cfg->type == V4L2_MBUS_PARALLEL) {
|
|
csi->bus_info.bus_if = V4L2_MBUS_PARALLEL;
|
|
if (IS_FLAG(cfg->flags, V4L2_MBUS_MASTER)) {
|
|
if (IS_FLAG(cfg->flags, V4L2_MBUS_HSYNC_ACTIVE_HIGH)) {
|
|
csi->bus_info.bus_tmg.href_pol = ACTIVE_HIGH;
|
|
csi->ncsi_if.href = REF_POSITIVE;
|
|
} else {
|
|
csi->bus_info.bus_tmg.href_pol = ACTIVE_LOW;
|
|
csi->ncsi_if.href = REF_NEGATIVE;
|
|
}
|
|
if (IS_FLAG(cfg->flags, V4L2_MBUS_VSYNC_ACTIVE_HIGH)) {
|
|
csi->bus_info.bus_tmg.vref_pol = ACTIVE_HIGH;
|
|
csi->ncsi_if.vref = REF_POSITIVE;
|
|
} else {
|
|
csi->bus_info.bus_tmg.vref_pol = ACTIVE_LOW;
|
|
csi->ncsi_if.vref = REF_NEGATIVE;
|
|
}
|
|
if (IS_FLAG(cfg->flags, V4L2_MBUS_PCLK_SAMPLE_RISING)) {
|
|
csi->bus_info.bus_tmg.pclk_sample = RISING;
|
|
csi->ncsi_if.clk = CLK_RISING;
|
|
} else {
|
|
csi->bus_info.bus_tmg.pclk_sample = FALLING;
|
|
csi->ncsi_if.clk = CLK_FALLING;
|
|
}
|
|
if (IS_FLAG(cfg->flags, V4L2_MBUS_FIELD_EVEN_HIGH)) {
|
|
csi->bus_info.bus_tmg.field_even_pol = ACTIVE_HIGH;
|
|
csi->ncsi_if.field = FIELD_POS;
|
|
} else {
|
|
csi->bus_info.bus_tmg.field_even_pol = ACTIVE_LOW;
|
|
csi->ncsi_if.field = FIELD_NEG;
|
|
}
|
|
} else {
|
|
vin_err("Do not support V4L2_MBUS_SLAVE!\n");
|
|
return -1;
|
|
}
|
|
} else if (cfg->type == V4L2_MBUS_BT656) {
|
|
csi->bus_info.bus_if = V4L2_MBUS_BT656;
|
|
csi->bus_info.ch_total_num = 0;
|
|
if (IS_FLAG(cfg->flags, CSI_CH_0))
|
|
csi->bus_info.ch_total_num++;
|
|
if (IS_FLAG(cfg->flags, CSI_CH_1))
|
|
csi->bus_info.ch_total_num++;
|
|
if (IS_FLAG(cfg->flags, CSI_CH_2))
|
|
csi->bus_info.ch_total_num++;
|
|
if (IS_FLAG(cfg->flags, CSI_CH_3))
|
|
csi->bus_info.ch_total_num++;
|
|
if (csi->bus_info.ch_total_num == 4) {
|
|
csi->arrange.column = 2;
|
|
csi->arrange.row = 2;
|
|
} else if (csi->bus_info.ch_total_num == 2) {
|
|
csi->arrange.column = 2;
|
|
csi->arrange.row = 1;
|
|
} else {
|
|
csi->bus_info.ch_total_num = 1;
|
|
csi->arrange.column = 1;
|
|
csi->arrange.row = 1;
|
|
}
|
|
vin_print("ch_total_num = %d\n", csi->bus_info.ch_total_num);
|
|
if (IS_FLAG(cfg->flags, V4L2_MBUS_PCLK_SAMPLE_RISING)) {
|
|
csi->bus_info.bus_tmg.pclk_sample = RISING;
|
|
csi->ncsi_if.clk = CLK_RISING;
|
|
} else {
|
|
csi->bus_info.bus_tmg.pclk_sample = FALLING;
|
|
csi->ncsi_if.clk = CLK_FALLING;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long sunxi_csi_subdev_ioctl(struct v4l2_subdev *sd, unsigned int cmd,
|
|
void *arg)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_subdev_core_ops sunxi_csi_core_ops = {
|
|
.s_power = sunxi_csi_subdev_s_power,
|
|
.init = sunxi_csi_subdev_init,
|
|
.ioctl = sunxi_csi_subdev_ioctl,
|
|
};
|
|
|
|
static const struct v4l2_subdev_video_ops sunxi_csi_subdev_video_ops = {
|
|
.s_stream = sunxi_csi_subdev_s_stream,
|
|
.s_crop = sunxi_csi_subdev_set_crop,
|
|
.s_mbus_config = sunxi_csi_s_mbus_config,
|
|
.s_parm = sunxi_csi_subdev_s_parm,
|
|
};
|
|
|
|
static const struct v4l2_subdev_pad_ops sunxi_csi_subdev_pad_ops = {
|
|
.enum_mbus_code = sunxi_csi_enum_mbus_code,
|
|
.get_fmt = sunxi_csi_subdev_get_fmt,
|
|
.set_fmt = sunxi_csi_subdev_set_fmt,
|
|
};
|
|
|
|
static struct v4l2_subdev_ops sunxi_csi_subdev_ops = {
|
|
.core = &sunxi_csi_core_ops,
|
|
.video = &sunxi_csi_subdev_video_ops,
|
|
.pad = &sunxi_csi_subdev_pad_ops,
|
|
};
|
|
|
|
static int __csi_init_subdev(struct csi_dev *csi)
|
|
{
|
|
struct v4l2_subdev *sd = &csi->subdev;
|
|
int ret;
|
|
mutex_init(&csi->subdev_lock);
|
|
csi->arrange.row = 1;
|
|
csi->arrange.column = 1;
|
|
csi->bus_info.ch_total_num = 1;
|
|
v4l2_subdev_init(sd, &sunxi_csi_subdev_ops);
|
|
sd->grp_id = VIN_GRP_ID_CSI;
|
|
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
snprintf(sd->name, sizeof(sd->name), "sunxi_csi.%u", csi->id);
|
|
v4l2_set_subdevdata(sd, csi);
|
|
|
|
csi->csi_pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
|
|
csi->csi_pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
|
|
sd->entity.type = MEDIA_ENT_T_V4L2_SUBDEV;
|
|
|
|
ret = media_entity_init(&sd->entity, CSI_PAD_NUM, csi->csi_pads, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int csi_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct csi_dev *csi = NULL;
|
|
int ret = 0;
|
|
|
|
if (np == NULL) {
|
|
vin_err("CSI failed to get of node\n");
|
|
return -ENODEV;
|
|
}
|
|
csi = kzalloc(sizeof(struct csi_dev), GFP_KERNEL);
|
|
if (!csi) {
|
|
ret = -ENOMEM;
|
|
goto ekzalloc;
|
|
}
|
|
|
|
of_property_read_u32(np, "device_id", &pdev->id);
|
|
if (pdev->id < 0) {
|
|
vin_err("CSI failed to get device id\n");
|
|
ret = -EINVAL;
|
|
goto freedev;
|
|
}
|
|
csi->id = pdev->id;
|
|
csi->pdev = pdev;
|
|
csi->cur_ch = 0;
|
|
|
|
/*just for test because the csi1 is virtual node*/
|
|
csi->base = of_iomap(np, 0);
|
|
if (!csi->base) {
|
|
ret = -EIO;
|
|
goto freedev;
|
|
}
|
|
#if defined(CONFIG_ARCH_SUN8IW11P1) || defined(CONFIG_ARCH_SUN50IW1P1)
|
|
ret = bsp_csi_set_base_addr(csi->id, (unsigned long)csi->base);
|
|
#else
|
|
ret = csic_prs_set_base_addr(csi->id, (unsigned long)csi->base);
|
|
#endif
|
|
if (ret < 0)
|
|
goto unmap;
|
|
|
|
spin_lock_init(&csi->slock);
|
|
init_waitqueue_head(&csi->wait);
|
|
list_add_tail(&csi->csi_list, &csi_drv_list);
|
|
__csi_init_subdev(csi);
|
|
|
|
platform_set_drvdata(pdev, csi);
|
|
vin_print("csi%d probe end!\n", csi->id);
|
|
|
|
return 0;
|
|
|
|
unmap:
|
|
iounmap(csi->base);
|
|
freedev:
|
|
kfree(csi);
|
|
ekzalloc:
|
|
vin_print("csi probe err!\n");
|
|
return ret;
|
|
}
|
|
|
|
static int csi_remove(struct platform_device *pdev)
|
|
{
|
|
struct csi_dev *csi = platform_get_drvdata(pdev);
|
|
struct v4l2_subdev *sd = &csi->subdev;
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
v4l2_set_subdevdata(sd, NULL);
|
|
__csi_pin_release(csi);
|
|
mutex_destroy(&csi->subdev_lock);
|
|
if (csi->base)
|
|
iounmap(csi->base);
|
|
list_del(&csi->csi_list);
|
|
kfree(csi);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id sunxi_csi_match[] = {
|
|
{.compatible = "allwinner,sunxi-csi",},
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver csi_platform_driver = {
|
|
.probe = csi_probe,
|
|
.remove = csi_remove,
|
|
.driver = {
|
|
.name = CSI_MODULE_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = sunxi_csi_match,
|
|
}
|
|
};
|
|
|
|
void sunxi_csi_dump_regs(struct v4l2_subdev *sd)
|
|
{
|
|
struct csi_dev *csi = v4l2_get_subdevdata(sd);
|
|
int i = 0;
|
|
printk("Vfe dump CSI regs :\n");
|
|
for (i = 0; i < 0xb0; i = i + 4) {
|
|
if (i % 0x10 == 0)
|
|
printk("0x%08x: ", i);
|
|
printk("0x%08x, ", readl(csi->base + i));
|
|
if (i % 0x10 == 0xc)
|
|
printk("\n");
|
|
}
|
|
}
|
|
|
|
struct v4l2_subdev *sunxi_csi_get_subdev(int id)
|
|
{
|
|
struct csi_dev *csi;
|
|
list_for_each_entry(csi, &csi_drv_list, csi_list) {
|
|
if (csi->id == id) {
|
|
csi->use_cnt++;
|
|
return &csi->subdev;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int sunxi_csi_platform_register(void)
|
|
{
|
|
return platform_driver_register(&csi_platform_driver);
|
|
}
|
|
|
|
void sunxi_csi_platform_unregister(void)
|
|
{
|
|
platform_driver_unregister(&csi_platform_driver);
|
|
vin_print("csi_exit end\n");
|
|
}
|