868 lines
24 KiB
C
868 lines
24 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* V4L2 Capture ISI subdev driver for i.MX8QXP/QM platform
|
||
|
*
|
||
|
* ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
|
||
|
* used to process image from camera sensor to memory or DC
|
||
|
*
|
||
|
* Copyright (c) 2019 NXP Semiconductor
|
||
|
*/
|
||
|
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/minmax.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/videodev2.h>
|
||
|
|
||
|
#include <media/media-entity.h>
|
||
|
#include <media/v4l2-subdev.h>
|
||
|
#include <media/videobuf2-v4l2.h>
|
||
|
|
||
|
#include "imx8-isi-core.h"
|
||
|
#include "imx8-isi-regs.h"
|
||
|
|
||
|
/*
|
||
|
* While the ISI receives data from the gasket on a 3x12-bit bus, the pipeline
|
||
|
* subdev conceptually includes the gasket in order to avoid exposing an extra
|
||
|
* subdev between the CSIS and the ISI. We thus need to expose media bus codes
|
||
|
* corresponding to the CSIS output, which is narrower.
|
||
|
*/
|
||
|
static const struct mxc_isi_bus_format_info mxc_isi_bus_formats[] = {
|
||
|
/* YUV formats */
|
||
|
{
|
||
|
.mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,
|
||
|
.output = MEDIA_BUS_FMT_YUV8_1X24,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK),
|
||
|
.encoding = MXC_ISI_ENC_YUV,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
|
||
|
.output = MEDIA_BUS_FMT_YUV8_1X24,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_YUV,
|
||
|
},
|
||
|
/* RGB formats */
|
||
|
{
|
||
|
.mbus_code = MEDIA_BUS_FMT_RGB565_1X16,
|
||
|
.output = MEDIA_BUS_FMT_RGB888_1X24,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK),
|
||
|
.encoding = MXC_ISI_ENC_RGB,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
|
||
|
.output = MEDIA_BUS_FMT_RGB888_1X24,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RGB,
|
||
|
},
|
||
|
/* RAW formats */
|
||
|
{
|
||
|
.mbus_code = MEDIA_BUS_FMT_Y8_1X8,
|
||
|
.output = MEDIA_BUS_FMT_Y8_1X8,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_Y10_1X10,
|
||
|
.output = MEDIA_BUS_FMT_Y10_1X10,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_Y12_1X12,
|
||
|
.output = MEDIA_BUS_FMT_Y12_1X12,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_Y14_1X14,
|
||
|
.output = MEDIA_BUS_FMT_Y14_1X14,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
|
||
|
.output = MEDIA_BUS_FMT_SBGGR8_1X8,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
|
||
|
.output = MEDIA_BUS_FMT_SGBRG8_1X8,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
|
||
|
.output = MEDIA_BUS_FMT_SGRBG8_1X8,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
|
||
|
.output = MEDIA_BUS_FMT_SRGGB8_1X8,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
|
||
|
.output = MEDIA_BUS_FMT_SBGGR10_1X10,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
|
||
|
.output = MEDIA_BUS_FMT_SGBRG10_1X10,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
|
||
|
.output = MEDIA_BUS_FMT_SGRBG10_1X10,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
|
||
|
.output = MEDIA_BUS_FMT_SRGGB10_1X10,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
|
||
|
.output = MEDIA_BUS_FMT_SBGGR12_1X12,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
|
||
|
.output = MEDIA_BUS_FMT_SGBRG12_1X12,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
|
||
|
.output = MEDIA_BUS_FMT_SGRBG12_1X12,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
|
||
|
.output = MEDIA_BUS_FMT_SRGGB12_1X12,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14,
|
||
|
.output = MEDIA_BUS_FMT_SBGGR14_1X14,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SGBRG14_1X14,
|
||
|
.output = MEDIA_BUS_FMT_SGBRG14_1X14,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SGRBG14_1X14,
|
||
|
.output = MEDIA_BUS_FMT_SGRBG14_1X14,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}, {
|
||
|
.mbus_code = MEDIA_BUS_FMT_SRGGB14_1X14,
|
||
|
.output = MEDIA_BUS_FMT_SRGGB14_1X14,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
},
|
||
|
/* JPEG */
|
||
|
{
|
||
|
.mbus_code = MEDIA_BUS_FMT_JPEG_1X8,
|
||
|
.output = MEDIA_BUS_FMT_JPEG_1X8,
|
||
|
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||
|
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||
|
.encoding = MXC_ISI_ENC_RAW,
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const struct mxc_isi_bus_format_info *
|
||
|
mxc_isi_bus_format_by_code(u32 code, unsigned int pad)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); i++) {
|
||
|
const struct mxc_isi_bus_format_info *info =
|
||
|
&mxc_isi_bus_formats[i];
|
||
|
|
||
|
if (info->mbus_code == code && info->pads & BIT(pad))
|
||
|
return info;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
const struct mxc_isi_bus_format_info *
|
||
|
mxc_isi_bus_format_by_index(unsigned int index, unsigned int pad)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); i++) {
|
||
|
const struct mxc_isi_bus_format_info *info =
|
||
|
&mxc_isi_bus_formats[i];
|
||
|
|
||
|
if (!(info->pads & BIT(pad)))
|
||
|
continue;
|
||
|
|
||
|
if (!index)
|
||
|
return info;
|
||
|
|
||
|
index--;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static inline struct mxc_isi_pipe *to_isi_pipe(struct v4l2_subdev *sd)
|
||
|
{
|
||
|
return container_of(sd, struct mxc_isi_pipe, sd);
|
||
|
}
|
||
|
|
||
|
int mxc_isi_pipe_enable(struct mxc_isi_pipe *pipe)
|
||
|
{
|
||
|
struct mxc_isi_crossbar *xbar = &pipe->isi->crossbar;
|
||
|
const struct mxc_isi_bus_format_info *sink_info;
|
||
|
const struct mxc_isi_bus_format_info *src_info;
|
||
|
const struct v4l2_mbus_framefmt *sink_fmt;
|
||
|
const struct v4l2_mbus_framefmt *src_fmt;
|
||
|
const struct v4l2_rect *compose;
|
||
|
struct v4l2_subdev_state *state;
|
||
|
struct v4l2_subdev *sd = &pipe->sd;
|
||
|
struct v4l2_area in_size, scale;
|
||
|
struct v4l2_rect crop;
|
||
|
u32 input;
|
||
|
int ret;
|
||
|
|
||
|
/*
|
||
|
* Find the connected input by inspecting the crossbar switch routing
|
||
|
* table.
|
||
|
*/
|
||
|
state = v4l2_subdev_lock_and_get_active_state(&xbar->sd);
|
||
|
ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
|
||
|
xbar->num_sinks + pipe->id,
|
||
|
0, &input, NULL);
|
||
|
v4l2_subdev_unlock_state(state);
|
||
|
|
||
|
if (ret)
|
||
|
return -EPIPE;
|
||
|
|
||
|
/* Configure the pipeline. */
|
||
|
state = v4l2_subdev_lock_and_get_active_state(sd);
|
||
|
|
||
|
sink_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SINK);
|
||
|
src_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
compose = v4l2_subdev_get_try_compose(sd, state, MXC_ISI_PIPE_PAD_SINK);
|
||
|
crop = *v4l2_subdev_get_try_crop(sd, state, MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
|
||
|
sink_info = mxc_isi_bus_format_by_code(sink_fmt->code,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
src_info = mxc_isi_bus_format_by_code(src_fmt->code,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
|
||
|
in_size.width = sink_fmt->width;
|
||
|
in_size.height = sink_fmt->height;
|
||
|
scale.width = compose->width;
|
||
|
scale.height = compose->height;
|
||
|
|
||
|
v4l2_subdev_unlock_state(state);
|
||
|
|
||
|
/* Configure the ISI channel. */
|
||
|
mxc_isi_channel_config(pipe, input, &in_size, &scale, &crop,
|
||
|
sink_info->encoding, src_info->encoding);
|
||
|
|
||
|
mxc_isi_channel_enable(pipe);
|
||
|
|
||
|
/* Enable streams on the crossbar switch. */
|
||
|
ret = v4l2_subdev_enable_streams(&xbar->sd, xbar->num_sinks + pipe->id,
|
||
|
BIT(0));
|
||
|
if (ret) {
|
||
|
mxc_isi_channel_disable(pipe);
|
||
|
dev_err(pipe->isi->dev, "Failed to enable pipe %u\n",
|
||
|
pipe->id);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void mxc_isi_pipe_disable(struct mxc_isi_pipe *pipe)
|
||
|
{
|
||
|
struct mxc_isi_crossbar *xbar = &pipe->isi->crossbar;
|
||
|
int ret;
|
||
|
|
||
|
ret = v4l2_subdev_disable_streams(&xbar->sd, xbar->num_sinks + pipe->id,
|
||
|
BIT(0));
|
||
|
if (ret)
|
||
|
dev_err(pipe->isi->dev, "Failed to disable pipe %u\n",
|
||
|
pipe->id);
|
||
|
|
||
|
mxc_isi_channel_disable(pipe);
|
||
|
}
|
||
|
|
||
|
/* -----------------------------------------------------------------------------
|
||
|
* V4L2 subdev operations
|
||
|
*/
|
||
|
|
||
|
static struct v4l2_mbus_framefmt *
|
||
|
mxc_isi_pipe_get_pad_format(struct mxc_isi_pipe *pipe,
|
||
|
struct v4l2_subdev_state *state,
|
||
|
unsigned int pad)
|
||
|
{
|
||
|
return v4l2_subdev_get_try_format(&pipe->sd, state, pad);
|
||
|
}
|
||
|
|
||
|
static struct v4l2_rect *
|
||
|
mxc_isi_pipe_get_pad_crop(struct mxc_isi_pipe *pipe,
|
||
|
struct v4l2_subdev_state *state,
|
||
|
unsigned int pad)
|
||
|
{
|
||
|
return v4l2_subdev_get_try_crop(&pipe->sd, state, pad);
|
||
|
}
|
||
|
|
||
|
static struct v4l2_rect *
|
||
|
mxc_isi_pipe_get_pad_compose(struct mxc_isi_pipe *pipe,
|
||
|
struct v4l2_subdev_state *state,
|
||
|
unsigned int pad)
|
||
|
{
|
||
|
return v4l2_subdev_get_try_compose(&pipe->sd, state, pad);
|
||
|
}
|
||
|
|
||
|
static int mxc_isi_pipe_init_cfg(struct v4l2_subdev *sd,
|
||
|
struct v4l2_subdev_state *state)
|
||
|
{
|
||
|
struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
|
||
|
struct v4l2_mbus_framefmt *fmt_source;
|
||
|
struct v4l2_mbus_framefmt *fmt_sink;
|
||
|
struct v4l2_rect *compose;
|
||
|
struct v4l2_rect *crop;
|
||
|
|
||
|
fmt_sink = mxc_isi_pipe_get_pad_format(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
fmt_source = mxc_isi_pipe_get_pad_format(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
|
||
|
fmt_sink->width = MXC_ISI_DEF_WIDTH;
|
||
|
fmt_sink->height = MXC_ISI_DEF_HEIGHT;
|
||
|
fmt_sink->code = MXC_ISI_DEF_MBUS_CODE_SINK;
|
||
|
fmt_sink->field = V4L2_FIELD_NONE;
|
||
|
fmt_sink->colorspace = V4L2_COLORSPACE_JPEG;
|
||
|
fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace);
|
||
|
fmt_sink->quantization =
|
||
|
V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace,
|
||
|
fmt_sink->ycbcr_enc);
|
||
|
fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace);
|
||
|
|
||
|
*fmt_source = *fmt_sink;
|
||
|
fmt_source->code = MXC_ISI_DEF_MBUS_CODE_SOURCE;
|
||
|
|
||
|
compose = mxc_isi_pipe_get_pad_compose(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
crop = mxc_isi_pipe_get_pad_crop(pipe, state, MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
|
||
|
compose->left = 0;
|
||
|
compose->top = 0;
|
||
|
compose->width = MXC_ISI_DEF_WIDTH;
|
||
|
compose->height = MXC_ISI_DEF_HEIGHT;
|
||
|
|
||
|
*crop = *compose;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int mxc_isi_pipe_enum_mbus_code(struct v4l2_subdev *sd,
|
||
|
struct v4l2_subdev_state *state,
|
||
|
struct v4l2_subdev_mbus_code_enum *code)
|
||
|
{
|
||
|
static const u32 output_codes[] = {
|
||
|
MEDIA_BUS_FMT_YUV8_1X24,
|
||
|
MEDIA_BUS_FMT_RGB888_1X24,
|
||
|
};
|
||
|
struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
|
||
|
const struct mxc_isi_bus_format_info *info;
|
||
|
unsigned int index;
|
||
|
unsigned int i;
|
||
|
|
||
|
if (code->pad == MXC_ISI_PIPE_PAD_SOURCE) {
|
||
|
const struct v4l2_mbus_framefmt *format;
|
||
|
|
||
|
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
info = mxc_isi_bus_format_by_code(format->code,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
|
||
|
if (info->encoding == MXC_ISI_ENC_RAW) {
|
||
|
/*
|
||
|
* For RAW formats, the sink and source media bus codes
|
||
|
* must match.
|
||
|
*/
|
||
|
if (code->index)
|
||
|
return -EINVAL;
|
||
|
|
||
|
code->code = info->output;
|
||
|
} else {
|
||
|
/*
|
||
|
* For RGB or YUV formats, the ISI supports format
|
||
|
* conversion. Either of the two output formats can be
|
||
|
* used regardless of the input.
|
||
|
*/
|
||
|
if (code->index > 1)
|
||
|
return -EINVAL;
|
||
|
|
||
|
code->code = output_codes[code->index];
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
index = code->index;
|
||
|
|
||
|
for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); ++i) {
|
||
|
info = &mxc_isi_bus_formats[i];
|
||
|
|
||
|
if (!(info->pads & BIT(MXC_ISI_PIPE_PAD_SINK)))
|
||
|
continue;
|
||
|
|
||
|
if (index == 0) {
|
||
|
code->code = info->mbus_code;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
index--;
|
||
|
}
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
static int mxc_isi_pipe_set_fmt(struct v4l2_subdev *sd,
|
||
|
struct v4l2_subdev_state *state,
|
||
|
struct v4l2_subdev_format *fmt)
|
||
|
{
|
||
|
struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
|
||
|
struct v4l2_mbus_framefmt *mf = &fmt->format;
|
||
|
const struct mxc_isi_bus_format_info *info;
|
||
|
struct v4l2_mbus_framefmt *format;
|
||
|
struct v4l2_rect *rect;
|
||
|
|
||
|
if (vb2_is_busy(&pipe->video.vb2_q))
|
||
|
return -EBUSY;
|
||
|
|
||
|
if (fmt->pad == MXC_ISI_PIPE_PAD_SINK) {
|
||
|
unsigned int max_width;
|
||
|
|
||
|
info = mxc_isi_bus_format_by_code(mf->code,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
if (!info)
|
||
|
info = mxc_isi_bus_format_by_code(MXC_ISI_DEF_MBUS_CODE_SINK,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
|
||
|
/*
|
||
|
* Limit the max line length if there's no adjacent pipe to
|
||
|
* chain with.
|
||
|
*/
|
||
|
max_width = pipe->id == pipe->isi->pdata->num_channels - 1
|
||
|
? MXC_ISI_MAX_WIDTH_UNCHAINED
|
||
|
: MXC_ISI_MAX_WIDTH_CHAINED;
|
||
|
|
||
|
mf->code = info->mbus_code;
|
||
|
mf->width = clamp(mf->width, MXC_ISI_MIN_WIDTH, max_width);
|
||
|
mf->height = clamp(mf->height, MXC_ISI_MIN_HEIGHT,
|
||
|
MXC_ISI_MAX_HEIGHT);
|
||
|
|
||
|
/* Propagate the format to the source pad. */
|
||
|
rect = mxc_isi_pipe_get_pad_compose(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
rect->width = mf->width;
|
||
|
rect->height = mf->height;
|
||
|
|
||
|
rect = mxc_isi_pipe_get_pad_crop(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
rect->left = 0;
|
||
|
rect->top = 0;
|
||
|
rect->width = mf->width;
|
||
|
rect->height = mf->height;
|
||
|
|
||
|
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
format->code = info->output;
|
||
|
format->width = mf->width;
|
||
|
format->height = mf->height;
|
||
|
} else {
|
||
|
/*
|
||
|
* For RGB or YUV formats, the ISI supports RGB <-> YUV format
|
||
|
* conversion. For RAW formats, the sink and source media bus
|
||
|
* codes must match.
|
||
|
*/
|
||
|
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
info = mxc_isi_bus_format_by_code(format->code,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
|
||
|
if (info->encoding != MXC_ISI_ENC_RAW) {
|
||
|
if (mf->code != MEDIA_BUS_FMT_YUV8_1X24 &&
|
||
|
mf->code != MEDIA_BUS_FMT_RGB888_1X24)
|
||
|
mf->code = info->output;
|
||
|
|
||
|
info = mxc_isi_bus_format_by_code(mf->code,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
}
|
||
|
|
||
|
mf->code = info->output;
|
||
|
|
||
|
/*
|
||
|
* The width and height on the source can't be changed, they
|
||
|
* must match the crop rectangle size.
|
||
|
*/
|
||
|
rect = mxc_isi_pipe_get_pad_crop(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
|
||
|
mf->width = rect->width;
|
||
|
mf->height = rect->height;
|
||
|
}
|
||
|
|
||
|
format = mxc_isi_pipe_get_pad_format(pipe, state, fmt->pad);
|
||
|
*format = *mf;
|
||
|
|
||
|
dev_dbg(pipe->isi->dev, "pad%u: code: 0x%04x, %ux%u",
|
||
|
fmt->pad, mf->code, mf->width, mf->height);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int mxc_isi_pipe_get_selection(struct v4l2_subdev *sd,
|
||
|
struct v4l2_subdev_state *state,
|
||
|
struct v4l2_subdev_selection *sel)
|
||
|
{
|
||
|
struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
|
||
|
const struct v4l2_mbus_framefmt *format;
|
||
|
const struct v4l2_rect *rect;
|
||
|
|
||
|
switch (sel->target) {
|
||
|
case V4L2_SEL_TGT_COMPOSE_BOUNDS:
|
||
|
if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
|
||
|
/* No compose rectangle on source pad. */
|
||
|
return -EINVAL;
|
||
|
|
||
|
/* The sink compose is bound by the sink format. */
|
||
|
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
sel->r.left = 0;
|
||
|
sel->r.top = 0;
|
||
|
sel->r.width = format->width;
|
||
|
sel->r.height = format->height;
|
||
|
break;
|
||
|
|
||
|
case V4L2_SEL_TGT_CROP_BOUNDS:
|
||
|
if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
|
||
|
/* No crop rectangle on sink pad. */
|
||
|
return -EINVAL;
|
||
|
|
||
|
/* The source crop is bound by the sink compose. */
|
||
|
rect = mxc_isi_pipe_get_pad_compose(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
sel->r = *rect;
|
||
|
break;
|
||
|
|
||
|
case V4L2_SEL_TGT_CROP:
|
||
|
if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
|
||
|
/* No crop rectangle on sink pad. */
|
||
|
return -EINVAL;
|
||
|
|
||
|
rect = mxc_isi_pipe_get_pad_crop(pipe, state, sel->pad);
|
||
|
sel->r = *rect;
|
||
|
break;
|
||
|
|
||
|
case V4L2_SEL_TGT_COMPOSE:
|
||
|
if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
|
||
|
/* No compose rectangle on source pad. */
|
||
|
return -EINVAL;
|
||
|
|
||
|
rect = mxc_isi_pipe_get_pad_compose(pipe, state, sel->pad);
|
||
|
sel->r = *rect;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int mxc_isi_pipe_set_selection(struct v4l2_subdev *sd,
|
||
|
struct v4l2_subdev_state *state,
|
||
|
struct v4l2_subdev_selection *sel)
|
||
|
{
|
||
|
struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
|
||
|
struct v4l2_mbus_framefmt *format;
|
||
|
struct v4l2_rect *rect;
|
||
|
|
||
|
switch (sel->target) {
|
||
|
case V4L2_SEL_TGT_CROP:
|
||
|
if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
|
||
|
/* The pipeline support cropping on the source only. */
|
||
|
return -EINVAL;
|
||
|
|
||
|
/* The source crop is bound by the sink compose. */
|
||
|
rect = mxc_isi_pipe_get_pad_compose(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
sel->r.left = clamp_t(s32, sel->r.left, 0, rect->width - 1);
|
||
|
sel->r.top = clamp_t(s32, sel->r.top, 0, rect->height - 1);
|
||
|
sel->r.width = clamp(sel->r.width, MXC_ISI_MIN_WIDTH,
|
||
|
rect->width - sel->r.left);
|
||
|
sel->r.height = clamp(sel->r.height, MXC_ISI_MIN_HEIGHT,
|
||
|
rect->height - sel->r.top);
|
||
|
|
||
|
rect = mxc_isi_pipe_get_pad_crop(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
*rect = sel->r;
|
||
|
|
||
|
/* Propagate the crop rectangle to the source pad. */
|
||
|
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
format->width = sel->r.width;
|
||
|
format->height = sel->r.height;
|
||
|
break;
|
||
|
|
||
|
case V4L2_SEL_TGT_COMPOSE:
|
||
|
if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
|
||
|
/* Composing is supported on the sink only. */
|
||
|
return -EINVAL;
|
||
|
|
||
|
/* The sink crop is bound by the sink format downscaling only). */
|
||
|
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
|
||
|
sel->r.left = 0;
|
||
|
sel->r.top = 0;
|
||
|
sel->r.width = clamp(sel->r.width, MXC_ISI_MIN_WIDTH,
|
||
|
format->width);
|
||
|
sel->r.height = clamp(sel->r.height, MXC_ISI_MIN_HEIGHT,
|
||
|
format->height);
|
||
|
|
||
|
rect = mxc_isi_pipe_get_pad_compose(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
*rect = sel->r;
|
||
|
|
||
|
/* Propagate the compose rectangle to the source pad. */
|
||
|
rect = mxc_isi_pipe_get_pad_crop(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
rect->left = 0;
|
||
|
rect->top = 0;
|
||
|
rect->width = sel->r.width;
|
||
|
rect->height = sel->r.height;
|
||
|
|
||
|
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
format->width = sel->r.width;
|
||
|
format->height = sel->r.height;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dev_dbg(pipe->isi->dev, "%s, target %#x: (%d,%d)/%dx%d", __func__,
|
||
|
sel->target, sel->r.left, sel->r.top, sel->r.width,
|
||
|
sel->r.height);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct v4l2_subdev_pad_ops mxc_isi_pipe_subdev_pad_ops = {
|
||
|
.init_cfg = mxc_isi_pipe_init_cfg,
|
||
|
.enum_mbus_code = mxc_isi_pipe_enum_mbus_code,
|
||
|
.get_fmt = v4l2_subdev_get_fmt,
|
||
|
.set_fmt = mxc_isi_pipe_set_fmt,
|
||
|
.get_selection = mxc_isi_pipe_get_selection,
|
||
|
.set_selection = mxc_isi_pipe_set_selection,
|
||
|
};
|
||
|
|
||
|
static const struct v4l2_subdev_ops mxc_isi_pipe_subdev_ops = {
|
||
|
.pad = &mxc_isi_pipe_subdev_pad_ops,
|
||
|
};
|
||
|
|
||
|
/* -----------------------------------------------------------------------------
|
||
|
* IRQ handling
|
||
|
*/
|
||
|
|
||
|
static irqreturn_t mxc_isi_pipe_irq_handler(int irq, void *priv)
|
||
|
{
|
||
|
struct mxc_isi_pipe *pipe = priv;
|
||
|
const struct mxc_isi_ier_reg *ier_reg = pipe->isi->pdata->ier_reg;
|
||
|
u32 status;
|
||
|
|
||
|
status = mxc_isi_channel_irq_status(pipe, true);
|
||
|
|
||
|
if (status & CHNL_STS_FRM_STRD) {
|
||
|
if (!WARN_ON(!pipe->irq_handler))
|
||
|
pipe->irq_handler(pipe, status);
|
||
|
}
|
||
|
|
||
|
if (status & (CHNL_STS_AXI_WR_ERR_Y |
|
||
|
CHNL_STS_AXI_WR_ERR_U |
|
||
|
CHNL_STS_AXI_WR_ERR_V))
|
||
|
dev_dbg(pipe->isi->dev, "%s: IRQ AXI Error stat=0x%X\n",
|
||
|
__func__, status);
|
||
|
|
||
|
if (status & (ier_reg->panic_y_buf_en.mask |
|
||
|
ier_reg->panic_u_buf_en.mask |
|
||
|
ier_reg->panic_v_buf_en.mask))
|
||
|
dev_dbg(pipe->isi->dev, "%s: IRQ Panic OFLW Error stat=0x%X\n",
|
||
|
__func__, status);
|
||
|
|
||
|
if (status & (ier_reg->oflw_y_buf_en.mask |
|
||
|
ier_reg->oflw_u_buf_en.mask |
|
||
|
ier_reg->oflw_v_buf_en.mask))
|
||
|
dev_dbg(pipe->isi->dev, "%s: IRQ OFLW Error stat=0x%X\n",
|
||
|
__func__, status);
|
||
|
|
||
|
if (status & (ier_reg->excs_oflw_y_buf_en.mask |
|
||
|
ier_reg->excs_oflw_u_buf_en.mask |
|
||
|
ier_reg->excs_oflw_v_buf_en.mask))
|
||
|
dev_dbg(pipe->isi->dev, "%s: IRQ EXCS OFLW Error stat=0x%X\n",
|
||
|
__func__, status);
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
/* -----------------------------------------------------------------------------
|
||
|
* Init & cleanup
|
||
|
*/
|
||
|
|
||
|
static const struct media_entity_operations mxc_isi_pipe_entity_ops = {
|
||
|
.link_validate = v4l2_subdev_link_validate,
|
||
|
};
|
||
|
|
||
|
int mxc_isi_pipe_init(struct mxc_isi_dev *isi, unsigned int id)
|
||
|
{
|
||
|
struct mxc_isi_pipe *pipe = &isi->pipes[id];
|
||
|
struct v4l2_subdev *sd;
|
||
|
int irq;
|
||
|
int ret;
|
||
|
|
||
|
pipe->id = id;
|
||
|
pipe->isi = isi;
|
||
|
pipe->regs = isi->regs + id * isi->pdata->reg_offset;
|
||
|
|
||
|
mutex_init(&pipe->lock);
|
||
|
|
||
|
pipe->available_res = MXC_ISI_CHANNEL_RES_LINE_BUF
|
||
|
| MXC_ISI_CHANNEL_RES_OUTPUT_BUF;
|
||
|
pipe->acquired_res = 0;
|
||
|
pipe->chained_res = 0;
|
||
|
pipe->chained = false;
|
||
|
|
||
|
sd = &pipe->sd;
|
||
|
v4l2_subdev_init(sd, &mxc_isi_pipe_subdev_ops);
|
||
|
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
||
|
snprintf(sd->name, sizeof(sd->name), "mxc_isi.%d", pipe->id);
|
||
|
sd->dev = isi->dev;
|
||
|
|
||
|
sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
|
||
|
sd->entity.ops = &mxc_isi_pipe_entity_ops;
|
||
|
|
||
|
pipe->pads[MXC_ISI_PIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
|
||
|
pipe->pads[MXC_ISI_PIPE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
|
||
|
|
||
|
ret = media_entity_pads_init(&sd->entity, MXC_ISI_PIPE_PADS_NUM,
|
||
|
pipe->pads);
|
||
|
if (ret)
|
||
|
goto error;
|
||
|
|
||
|
ret = v4l2_subdev_init_finalize(sd);
|
||
|
if (ret < 0)
|
||
|
goto error;
|
||
|
|
||
|
/* Register IRQ handler. */
|
||
|
mxc_isi_channel_irq_clear(pipe);
|
||
|
|
||
|
irq = platform_get_irq(to_platform_device(isi->dev), id);
|
||
|
if (irq < 0) {
|
||
|
dev_err(pipe->isi->dev, "Failed to get IRQ (%d)\n", irq);
|
||
|
ret = irq;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
ret = devm_request_irq(isi->dev, irq, mxc_isi_pipe_irq_handler,
|
||
|
0, dev_name(isi->dev), pipe);
|
||
|
if (ret < 0) {
|
||
|
dev_err(isi->dev, "failed to request IRQ (%d)\n", ret);
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
error:
|
||
|
media_entity_cleanup(&sd->entity);
|
||
|
mutex_destroy(&pipe->lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void mxc_isi_pipe_cleanup(struct mxc_isi_pipe *pipe)
|
||
|
{
|
||
|
struct v4l2_subdev *sd = &pipe->sd;
|
||
|
|
||
|
media_entity_cleanup(&sd->entity);
|
||
|
mutex_destroy(&pipe->lock);
|
||
|
}
|
||
|
|
||
|
int mxc_isi_pipe_acquire(struct mxc_isi_pipe *pipe,
|
||
|
mxc_isi_pipe_irq_t irq_handler)
|
||
|
{
|
||
|
const struct mxc_isi_bus_format_info *sink_info;
|
||
|
const struct mxc_isi_bus_format_info *src_info;
|
||
|
struct v4l2_mbus_framefmt *sink_fmt;
|
||
|
const struct v4l2_mbus_framefmt *src_fmt;
|
||
|
struct v4l2_subdev *sd = &pipe->sd;
|
||
|
struct v4l2_subdev_state *state;
|
||
|
bool bypass;
|
||
|
int ret;
|
||
|
|
||
|
state = v4l2_subdev_lock_and_get_active_state(sd);
|
||
|
sink_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SINK);
|
||
|
src_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
v4l2_subdev_unlock_state(state);
|
||
|
|
||
|
sink_info = mxc_isi_bus_format_by_code(sink_fmt->code,
|
||
|
MXC_ISI_PIPE_PAD_SINK);
|
||
|
src_info = mxc_isi_bus_format_by_code(src_fmt->code,
|
||
|
MXC_ISI_PIPE_PAD_SOURCE);
|
||
|
|
||
|
bypass = sink_fmt->width == src_fmt->width &&
|
||
|
sink_fmt->height == src_fmt->height &&
|
||
|
sink_info->encoding == src_info->encoding;
|
||
|
|
||
|
ret = mxc_isi_channel_acquire(pipe, irq_handler, bypass);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
/* Chain the channel if needed for wide resolutions. */
|
||
|
if (sink_fmt->width > MXC_ISI_MAX_WIDTH_UNCHAINED) {
|
||
|
ret = mxc_isi_channel_chain(pipe, bypass);
|
||
|
if (ret)
|
||
|
mxc_isi_channel_release(pipe);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void mxc_isi_pipe_release(struct mxc_isi_pipe *pipe)
|
||
|
{
|
||
|
mxc_isi_channel_release(pipe);
|
||
|
mxc_isi_channel_unchain(pipe);
|
||
|
}
|