diff options
Diffstat (limited to 'drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c')
-rw-r--r-- | drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c | 867 |
1 files changed, 867 insertions, 0 deletions
diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c new file mode 100644 index 000000000000..c4454aa1cb34 --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c @@ -0,0 +1,867 @@ +// 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); +} |