// SPDX-License-Identifier: GPL-2.0+ /* * Driver for Analog Devices ADV748X CSI-2 Transmitter * * Copyright (C) 2017 Renesas Electronics Corp. */ #include #include #include #include #include "adv748x.h" static const unsigned int adv748x_csi2_txa_fmts[] = { MEDIA_BUS_FMT_UYVY8_1X16, MEDIA_BUS_FMT_RGB888_1X24, }; static const unsigned int adv748x_csi2_txb_fmts[] = { MEDIA_BUS_FMT_UYVY8_1X16, }; int adv748x_csi2_set_virtual_channel(struct adv748x_csi2 *tx, unsigned int vc) { return tx_write(tx, ADV748X_CSI_VC_REF, vc << ADV748X_CSI_VC_REF_SHIFT); } /** * adv748x_csi2_register_link : Register and link internal entities * * @tx: CSI2 private entity * @v4l2_dev: Video registration device * @src: Source subdevice to establish link * @src_pad: Pad number of source to link to this @tx * @enable: Link enabled flag * * Ensure that the subdevice is registered against the v4l2_device, and link the * source pad to the sink pad of the CSI2 bus entity. */ static int adv748x_csi2_register_link(struct adv748x_csi2 *tx, struct v4l2_device *v4l2_dev, struct v4l2_subdev *src, unsigned int src_pad, bool enable) { int ret; if (!src->v4l2_dev) { ret = v4l2_device_register_subdev(v4l2_dev, src); if (ret) return ret; } ret = media_create_pad_link(&src->entity, src_pad, &tx->sd.entity, ADV748X_CSI2_SINK, enable ? MEDIA_LNK_FL_ENABLED : 0); if (ret) return ret; if (enable) tx->src = src; return 0; } /* ----------------------------------------------------------------------------- * v4l2_subdev_internal_ops */ static int adv748x_csi2_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) { static const struct v4l2_mbus_framefmt adv748x_csi2_default_fmt = { .width = 1280, .height = 720, .code = MEDIA_BUS_FMT_UYVY8_1X16, .colorspace = V4L2_COLORSPACE_SRGB, .field = V4L2_FIELD_NONE, .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, .quantization = V4L2_QUANTIZATION_DEFAULT, .xfer_func = V4L2_XFER_FUNC_DEFAULT, }; struct v4l2_mbus_framefmt *fmt; fmt = v4l2_subdev_state_get_format(state, ADV748X_CSI2_SINK); *fmt = adv748x_csi2_default_fmt; fmt = v4l2_subdev_state_get_format(state, ADV748X_CSI2_SOURCE); *fmt = adv748x_csi2_default_fmt; return 0; } /* * We use the internal registered operation to be able to ensure that our * incremental subdevices (not connected in the forward path) can be registered * against the resulting video path and media device. */ static int adv748x_csi2_registered(struct v4l2_subdev *sd) { struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); struct adv748x_state *state = tx->state; int ret; adv_dbg(state, "Registered %s (%s)", is_txa(tx) ? "TXA":"TXB", sd->name); /* * Link TXA to AFE and HDMI, and TXB to AFE only as TXB cannot output * HDMI. * * The HDMI->TXA link is enabled by default, as is the AFE->TXB one. */ if (is_afe_enabled(state)) { ret = adv748x_csi2_register_link(tx, sd->v4l2_dev, &state->afe.sd, ADV748X_AFE_SOURCE, is_txb(tx)); if (ret) return ret; /* TXB can output AFE signals only. */ if (is_txb(tx)) state->afe.tx = tx; } /* Register link to HDMI for TXA only. */ if (is_txb(tx) || !is_hdmi_enabled(state)) return 0; ret = adv748x_csi2_register_link(tx, sd->v4l2_dev, &state->hdmi.sd, ADV748X_HDMI_SOURCE, true); if (ret) return ret; /* The default HDMI output is TXA. */ state->hdmi.tx = tx; return 0; } static const struct v4l2_subdev_internal_ops adv748x_csi2_internal_ops = { .init_state = adv748x_csi2_init_state, .registered = adv748x_csi2_registered, }; /* ----------------------------------------------------------------------------- * v4l2_subdev_video_ops */ static int adv748x_csi2_s_stream(struct v4l2_subdev *sd, int enable) { struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); struct v4l2_subdev *src; src = adv748x_get_remote_sd(&tx->pads[ADV748X_CSI2_SINK]); if (!src) return -EPIPE; return v4l2_subdev_call(src, video, s_stream, enable); } static const struct v4l2_subdev_video_ops adv748x_csi2_video_ops = { .s_stream = adv748x_csi2_s_stream, }; /* ----------------------------------------------------------------------------- * v4l2_subdev_pad_ops * * The CSI2 bus pads are ignorant to the data sizes or formats. * But we must support setting the pad formats for format propagation. */ static int adv748x_csi2_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) { struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); const unsigned int *codes = is_txa(tx) ? adv748x_csi2_txa_fmts : adv748x_csi2_txb_fmts; size_t num_fmts = is_txa(tx) ? ARRAY_SIZE(adv748x_csi2_txa_fmts) : ARRAY_SIZE(adv748x_csi2_txb_fmts); /* * The format available on the source pad is the one applied on the sink * pad. */ if (code->pad == ADV748X_CSI2_SOURCE) { struct v4l2_mbus_framefmt *fmt; if (code->index) return -EINVAL; fmt = v4l2_subdev_state_get_format(sd_state, ADV748X_CSI2_SINK); code->code = fmt->code; return 0; } if (code->index >= num_fmts) return -EINVAL; code->code = codes[code->index]; return 0; } static bool adv748x_csi2_is_fmt_supported(struct adv748x_csi2 *tx, u32 code) { const unsigned int *codes = is_txa(tx) ? adv748x_csi2_txa_fmts : adv748x_csi2_txb_fmts; size_t num_fmts = is_txa(tx) ? ARRAY_SIZE(adv748x_csi2_txa_fmts) : ARRAY_SIZE(adv748x_csi2_txb_fmts); for (unsigned int i = 0; i < num_fmts; i++) { if (codes[i] == code) return true; } return false; } static int adv748x_csi2_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *sdformat) { struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); struct v4l2_mbus_framefmt *mbusformat; if (sdformat->pad == ADV748X_CSI2_SOURCE) return v4l2_subdev_get_fmt(sd, sd_state, sdformat); /* * Make sure the format is supported, if not default it to * UYVY8 as it's supported by both TXes. */ if (!adv748x_csi2_is_fmt_supported(tx, sdformat->format.code)) sdformat->format.code = MEDIA_BUS_FMT_UYVY8_1X16; mbusformat = v4l2_subdev_state_get_format(sd_state, sdformat->pad); *mbusformat = sdformat->format; /* Propagate format to the source pad. */ mbusformat = v4l2_subdev_state_get_format(sd_state, ADV748X_CSI2_SOURCE); *mbusformat = sdformat->format; return 0; } static int adv748x_csi2_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, struct v4l2_mbus_config *config) { struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); if (pad != ADV748X_CSI2_SOURCE) return -EINVAL; config->type = V4L2_MBUS_CSI2_DPHY; config->bus.mipi_csi2.num_data_lanes = tx->active_lanes; return 0; } static const struct v4l2_subdev_pad_ops adv748x_csi2_pad_ops = { .enum_mbus_code = adv748x_csi2_enum_mbus_code, .get_fmt = v4l2_subdev_get_fmt, .set_fmt = adv748x_csi2_set_format, .get_mbus_config = adv748x_csi2_get_mbus_config, }; /* ----------------------------------------------------------------------------- * v4l2_subdev_ops */ static const struct v4l2_subdev_ops adv748x_csi2_ops = { .video = &adv748x_csi2_video_ops, .pad = &adv748x_csi2_pad_ops, }; /* ----------------------------------------------------------------------------- * Subdev module and controls */ int adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate) { struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd); if (!tx->pixel_rate) return -EINVAL; return v4l2_ctrl_s_ctrl_int64(tx->pixel_rate, rate); } static int adv748x_csi2_s_ctrl(struct v4l2_ctrl *ctrl) { switch (ctrl->id) { case V4L2_CID_PIXEL_RATE: return 0; default: return -EINVAL; } } static const struct v4l2_ctrl_ops adv748x_csi2_ctrl_ops = { .s_ctrl = adv748x_csi2_s_ctrl, }; static int adv748x_csi2_init_controls(struct adv748x_csi2 *tx) { v4l2_ctrl_handler_init(&tx->ctrl_hdl, 1); tx->pixel_rate = v4l2_ctrl_new_std(&tx->ctrl_hdl, &adv748x_csi2_ctrl_ops, V4L2_CID_PIXEL_RATE, 1, INT_MAX, 1, 1); tx->sd.ctrl_handler = &tx->ctrl_hdl; if (tx->ctrl_hdl.error) { v4l2_ctrl_handler_free(&tx->ctrl_hdl); return tx->ctrl_hdl.error; } return v4l2_ctrl_handler_setup(&tx->ctrl_hdl); } int adv748x_csi2_init(struct adv748x_state *state, struct adv748x_csi2 *tx) { int ret; if (!is_tx_enabled(tx)) return 0; adv748x_subdev_init(&tx->sd, state, &adv748x_csi2_ops, MEDIA_ENT_F_VID_IF_BRIDGE, is_txa(tx) ? "txa" : "txb"); /* Register internal ops for incremental subdev registration */ tx->sd.internal_ops = &adv748x_csi2_internal_ops; tx->pads[ADV748X_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; tx->pads[ADV748X_CSI2_SOURCE].flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&tx->sd.entity, ADV748X_CSI2_NR_PADS, tx->pads); if (ret) return ret; ret = v4l2_async_subdev_endpoint_add(&tx->sd, of_fwnode_handle(state->endpoints[tx->port])); if (ret) goto err_free_media; ret = adv748x_csi2_init_controls(tx); if (ret) goto err_cleanup_subdev; tx->sd.state_lock = &state->mutex; ret = v4l2_subdev_init_finalize(&tx->sd); if (ret) goto err_free_ctrl; ret = v4l2_async_register_subdev(&tx->sd); if (ret) goto err_free_ctrl; return 0; err_free_ctrl: v4l2_ctrl_handler_free(&tx->ctrl_hdl); err_cleanup_subdev: v4l2_subdev_cleanup(&tx->sd); err_free_media: media_entity_cleanup(&tx->sd.entity); return ret; } void adv748x_csi2_cleanup(struct adv748x_csi2 *tx) { if (!is_tx_enabled(tx)) return; v4l2_async_unregister_subdev(&tx->sd); media_entity_cleanup(&tx->sd.entity); v4l2_ctrl_handler_free(&tx->ctrl_hdl); v4l2_subdev_cleanup(&tx->sd); }