18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Driver for Analog Devices ADV748X CSI-2 Transmitter
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2017 Renesas Electronics Corp.
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/mutex.h>
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <media/v4l2-ctrls.h>
128c2ecf20Sopenharmony_ci#include <media/v4l2-device.h>
138c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include "adv748x.h"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cistatic int adv748x_csi2_set_virtual_channel(struct adv748x_csi2 *tx,
188c2ecf20Sopenharmony_ci					    unsigned int vc)
198c2ecf20Sopenharmony_ci{
208c2ecf20Sopenharmony_ci	return tx_write(tx, ADV748X_CSI_VC_REF, vc << ADV748X_CSI_VC_REF_SHIFT);
218c2ecf20Sopenharmony_ci}
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci/**
248c2ecf20Sopenharmony_ci * adv748x_csi2_register_link : Register and link internal entities
258c2ecf20Sopenharmony_ci *
268c2ecf20Sopenharmony_ci * @tx: CSI2 private entity
278c2ecf20Sopenharmony_ci * @v4l2_dev: Video registration device
288c2ecf20Sopenharmony_ci * @src: Source subdevice to establish link
298c2ecf20Sopenharmony_ci * @src_pad: Pad number of source to link to this @tx
308c2ecf20Sopenharmony_ci * @enable: Link enabled flag
318c2ecf20Sopenharmony_ci *
328c2ecf20Sopenharmony_ci * Ensure that the subdevice is registered against the v4l2_device, and link the
338c2ecf20Sopenharmony_ci * source pad to the sink pad of the CSI2 bus entity.
348c2ecf20Sopenharmony_ci */
358c2ecf20Sopenharmony_cistatic int adv748x_csi2_register_link(struct adv748x_csi2 *tx,
368c2ecf20Sopenharmony_ci				      struct v4l2_device *v4l2_dev,
378c2ecf20Sopenharmony_ci				      struct v4l2_subdev *src,
388c2ecf20Sopenharmony_ci				      unsigned int src_pad,
398c2ecf20Sopenharmony_ci				      bool enable)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	int ret;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	if (!src->v4l2_dev) {
448c2ecf20Sopenharmony_ci		ret = v4l2_device_register_subdev(v4l2_dev, src);
458c2ecf20Sopenharmony_ci		if (ret)
468c2ecf20Sopenharmony_ci			return ret;
478c2ecf20Sopenharmony_ci	}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	ret = media_create_pad_link(&src->entity, src_pad,
508c2ecf20Sopenharmony_ci				    &tx->sd.entity, ADV748X_CSI2_SINK,
518c2ecf20Sopenharmony_ci				    enable ? MEDIA_LNK_FL_ENABLED : 0);
528c2ecf20Sopenharmony_ci	if (ret)
538c2ecf20Sopenharmony_ci		return ret;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	if (enable)
568c2ecf20Sopenharmony_ci		tx->src = src;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	return 0;
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci/* -----------------------------------------------------------------------------
628c2ecf20Sopenharmony_ci * v4l2_subdev_internal_ops
638c2ecf20Sopenharmony_ci *
648c2ecf20Sopenharmony_ci * We use the internal registered operation to be able to ensure that our
658c2ecf20Sopenharmony_ci * incremental subdevices (not connected in the forward path) can be registered
668c2ecf20Sopenharmony_ci * against the resulting video path and media device.
678c2ecf20Sopenharmony_ci */
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic int adv748x_csi2_registered(struct v4l2_subdev *sd)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
728c2ecf20Sopenharmony_ci	struct adv748x_state *state = tx->state;
738c2ecf20Sopenharmony_ci	int ret;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	adv_dbg(state, "Registered %s (%s)", is_txa(tx) ? "TXA":"TXB",
768c2ecf20Sopenharmony_ci			sd->name);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	/*
798c2ecf20Sopenharmony_ci	 * Link TXA to AFE and HDMI, and TXB to AFE only as TXB cannot output
808c2ecf20Sopenharmony_ci	 * HDMI.
818c2ecf20Sopenharmony_ci	 *
828c2ecf20Sopenharmony_ci	 * The HDMI->TXA link is enabled by default, as is the AFE->TXB one.
838c2ecf20Sopenharmony_ci	 */
848c2ecf20Sopenharmony_ci	if (is_afe_enabled(state)) {
858c2ecf20Sopenharmony_ci		ret = adv748x_csi2_register_link(tx, sd->v4l2_dev,
868c2ecf20Sopenharmony_ci						 &state->afe.sd,
878c2ecf20Sopenharmony_ci						 ADV748X_AFE_SOURCE,
888c2ecf20Sopenharmony_ci						 is_txb(tx));
898c2ecf20Sopenharmony_ci		if (ret)
908c2ecf20Sopenharmony_ci			return ret;
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci		/* TXB can output AFE signals only. */
938c2ecf20Sopenharmony_ci		if (is_txb(tx))
948c2ecf20Sopenharmony_ci			state->afe.tx = tx;
958c2ecf20Sopenharmony_ci	}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	/* Register link to HDMI for TXA only. */
988c2ecf20Sopenharmony_ci	if (is_txb(tx) || !is_hdmi_enabled(state))
998c2ecf20Sopenharmony_ci		return 0;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	ret = adv748x_csi2_register_link(tx, sd->v4l2_dev, &state->hdmi.sd,
1028c2ecf20Sopenharmony_ci					 ADV748X_HDMI_SOURCE, true);
1038c2ecf20Sopenharmony_ci	if (ret)
1048c2ecf20Sopenharmony_ci		return ret;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	/* The default HDMI output is TXA. */
1078c2ecf20Sopenharmony_ci	state->hdmi.tx = tx;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	return 0;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_internal_ops adv748x_csi2_internal_ops = {
1138c2ecf20Sopenharmony_ci	.registered = adv748x_csi2_registered,
1148c2ecf20Sopenharmony_ci};
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci/* -----------------------------------------------------------------------------
1178c2ecf20Sopenharmony_ci * v4l2_subdev_video_ops
1188c2ecf20Sopenharmony_ci */
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic int adv748x_csi2_s_stream(struct v4l2_subdev *sd, int enable)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
1238c2ecf20Sopenharmony_ci	struct v4l2_subdev *src;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	src = adv748x_get_remote_sd(&tx->pads[ADV748X_CSI2_SINK]);
1268c2ecf20Sopenharmony_ci	if (!src)
1278c2ecf20Sopenharmony_ci		return -EPIPE;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	return v4l2_subdev_call(src, video, s_stream, enable);
1308c2ecf20Sopenharmony_ci}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_video_ops adv748x_csi2_video_ops = {
1338c2ecf20Sopenharmony_ci	.s_stream = adv748x_csi2_s_stream,
1348c2ecf20Sopenharmony_ci};
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci/* -----------------------------------------------------------------------------
1378c2ecf20Sopenharmony_ci * v4l2_subdev_pad_ops
1388c2ecf20Sopenharmony_ci *
1398c2ecf20Sopenharmony_ci * The CSI2 bus pads are ignorant to the data sizes or formats.
1408c2ecf20Sopenharmony_ci * But we must support setting the pad formats for format propagation.
1418c2ecf20Sopenharmony_ci */
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic struct v4l2_mbus_framefmt *
1448c2ecf20Sopenharmony_ciadv748x_csi2_get_pad_format(struct v4l2_subdev *sd,
1458c2ecf20Sopenharmony_ci			    struct v4l2_subdev_pad_config *cfg,
1468c2ecf20Sopenharmony_ci			    unsigned int pad, u32 which)
1478c2ecf20Sopenharmony_ci{
1488c2ecf20Sopenharmony_ci	struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	if (which == V4L2_SUBDEV_FORMAT_TRY)
1518c2ecf20Sopenharmony_ci		return v4l2_subdev_get_try_format(sd, cfg, pad);
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	return &tx->format;
1548c2ecf20Sopenharmony_ci}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_cistatic int adv748x_csi2_get_format(struct v4l2_subdev *sd,
1578c2ecf20Sopenharmony_ci				   struct v4l2_subdev_pad_config *cfg,
1588c2ecf20Sopenharmony_ci				   struct v4l2_subdev_format *sdformat)
1598c2ecf20Sopenharmony_ci{
1608c2ecf20Sopenharmony_ci	struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
1618c2ecf20Sopenharmony_ci	struct adv748x_state *state = tx->state;
1628c2ecf20Sopenharmony_ci	struct v4l2_mbus_framefmt *mbusformat;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	mbusformat = adv748x_csi2_get_pad_format(sd, cfg, sdformat->pad,
1658c2ecf20Sopenharmony_ci						 sdformat->which);
1668c2ecf20Sopenharmony_ci	if (!mbusformat)
1678c2ecf20Sopenharmony_ci		return -EINVAL;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	mutex_lock(&state->mutex);
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	sdformat->format = *mbusformat;
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	mutex_unlock(&state->mutex);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	return 0;
1768c2ecf20Sopenharmony_ci}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_cistatic int adv748x_csi2_set_format(struct v4l2_subdev *sd,
1798c2ecf20Sopenharmony_ci				   struct v4l2_subdev_pad_config *cfg,
1808c2ecf20Sopenharmony_ci				   struct v4l2_subdev_format *sdformat)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
1838c2ecf20Sopenharmony_ci	struct adv748x_state *state = tx->state;
1848c2ecf20Sopenharmony_ci	struct v4l2_mbus_framefmt *mbusformat;
1858c2ecf20Sopenharmony_ci	int ret = 0;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	mbusformat = adv748x_csi2_get_pad_format(sd, cfg, sdformat->pad,
1888c2ecf20Sopenharmony_ci						 sdformat->which);
1898c2ecf20Sopenharmony_ci	if (!mbusformat)
1908c2ecf20Sopenharmony_ci		return -EINVAL;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	mutex_lock(&state->mutex);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	if (sdformat->pad == ADV748X_CSI2_SOURCE) {
1958c2ecf20Sopenharmony_ci		const struct v4l2_mbus_framefmt *sink_fmt;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci		sink_fmt = adv748x_csi2_get_pad_format(sd, cfg,
1988c2ecf20Sopenharmony_ci						       ADV748X_CSI2_SINK,
1998c2ecf20Sopenharmony_ci						       sdformat->which);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci		if (!sink_fmt) {
2028c2ecf20Sopenharmony_ci			ret = -EINVAL;
2038c2ecf20Sopenharmony_ci			goto unlock;
2048c2ecf20Sopenharmony_ci		}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci		sdformat->format = *sink_fmt;
2078c2ecf20Sopenharmony_ci	}
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	*mbusformat = sdformat->format;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ciunlock:
2128c2ecf20Sopenharmony_ci	mutex_unlock(&state->mutex);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	return ret;
2158c2ecf20Sopenharmony_ci}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_cistatic int adv748x_csi2_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
2188c2ecf20Sopenharmony_ci					struct v4l2_mbus_config *config)
2198c2ecf20Sopenharmony_ci{
2208c2ecf20Sopenharmony_ci	struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	if (pad != ADV748X_CSI2_SOURCE)
2238c2ecf20Sopenharmony_ci		return -EINVAL;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	config->type = V4L2_MBUS_CSI2_DPHY;
2268c2ecf20Sopenharmony_ci	switch (tx->active_lanes) {
2278c2ecf20Sopenharmony_ci	case 1:
2288c2ecf20Sopenharmony_ci		config->flags = V4L2_MBUS_CSI2_1_LANE;
2298c2ecf20Sopenharmony_ci		break;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	case 2:
2328c2ecf20Sopenharmony_ci		config->flags = V4L2_MBUS_CSI2_2_LANE;
2338c2ecf20Sopenharmony_ci		break;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	case 3:
2368c2ecf20Sopenharmony_ci		config->flags = V4L2_MBUS_CSI2_3_LANE;
2378c2ecf20Sopenharmony_ci		break;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	case 4:
2408c2ecf20Sopenharmony_ci		config->flags = V4L2_MBUS_CSI2_4_LANE;
2418c2ecf20Sopenharmony_ci		break;
2428c2ecf20Sopenharmony_ci	}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	return 0;
2458c2ecf20Sopenharmony_ci}
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_pad_ops adv748x_csi2_pad_ops = {
2488c2ecf20Sopenharmony_ci	.get_fmt = adv748x_csi2_get_format,
2498c2ecf20Sopenharmony_ci	.set_fmt = adv748x_csi2_set_format,
2508c2ecf20Sopenharmony_ci	.get_mbus_config = adv748x_csi2_get_mbus_config,
2518c2ecf20Sopenharmony_ci};
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci/* -----------------------------------------------------------------------------
2548c2ecf20Sopenharmony_ci * v4l2_subdev_ops
2558c2ecf20Sopenharmony_ci */
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_ops adv748x_csi2_ops = {
2588c2ecf20Sopenharmony_ci	.video = &adv748x_csi2_video_ops,
2598c2ecf20Sopenharmony_ci	.pad = &adv748x_csi2_pad_ops,
2608c2ecf20Sopenharmony_ci};
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci/* -----------------------------------------------------------------------------
2638c2ecf20Sopenharmony_ci * Subdev module and controls
2648c2ecf20Sopenharmony_ci */
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ciint adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate)
2678c2ecf20Sopenharmony_ci{
2688c2ecf20Sopenharmony_ci	struct adv748x_csi2 *tx = adv748x_sd_to_csi2(sd);
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	if (!tx->pixel_rate)
2718c2ecf20Sopenharmony_ci		return -EINVAL;
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return v4l2_ctrl_s_ctrl_int64(tx->pixel_rate, rate);
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_cistatic int adv748x_csi2_s_ctrl(struct v4l2_ctrl *ctrl)
2778c2ecf20Sopenharmony_ci{
2788c2ecf20Sopenharmony_ci	switch (ctrl->id) {
2798c2ecf20Sopenharmony_ci	case V4L2_CID_PIXEL_RATE:
2808c2ecf20Sopenharmony_ci		return 0;
2818c2ecf20Sopenharmony_ci	default:
2828c2ecf20Sopenharmony_ci		return -EINVAL;
2838c2ecf20Sopenharmony_ci	}
2848c2ecf20Sopenharmony_ci}
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops adv748x_csi2_ctrl_ops = {
2878c2ecf20Sopenharmony_ci	.s_ctrl = adv748x_csi2_s_ctrl,
2888c2ecf20Sopenharmony_ci};
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_cistatic int adv748x_csi2_init_controls(struct adv748x_csi2 *tx)
2918c2ecf20Sopenharmony_ci{
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_init(&tx->ctrl_hdl, 1);
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	tx->pixel_rate = v4l2_ctrl_new_std(&tx->ctrl_hdl,
2968c2ecf20Sopenharmony_ci					   &adv748x_csi2_ctrl_ops,
2978c2ecf20Sopenharmony_ci					   V4L2_CID_PIXEL_RATE, 1, INT_MAX,
2988c2ecf20Sopenharmony_ci					   1, 1);
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	tx->sd.ctrl_handler = &tx->ctrl_hdl;
3018c2ecf20Sopenharmony_ci	if (tx->ctrl_hdl.error) {
3028c2ecf20Sopenharmony_ci		v4l2_ctrl_handler_free(&tx->ctrl_hdl);
3038c2ecf20Sopenharmony_ci		return tx->ctrl_hdl.error;
3048c2ecf20Sopenharmony_ci	}
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	return v4l2_ctrl_handler_setup(&tx->ctrl_hdl);
3078c2ecf20Sopenharmony_ci}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ciint adv748x_csi2_init(struct adv748x_state *state, struct adv748x_csi2 *tx)
3108c2ecf20Sopenharmony_ci{
3118c2ecf20Sopenharmony_ci	int ret;
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	if (!is_tx_enabled(tx))
3148c2ecf20Sopenharmony_ci		return 0;
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	/* Initialise the virtual channel */
3178c2ecf20Sopenharmony_ci	adv748x_csi2_set_virtual_channel(tx, 0);
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	adv748x_subdev_init(&tx->sd, state, &adv748x_csi2_ops,
3208c2ecf20Sopenharmony_ci			    MEDIA_ENT_F_VID_IF_BRIDGE,
3218c2ecf20Sopenharmony_ci			    is_txa(tx) ? "txa" : "txb");
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	/* Ensure that matching is based upon the endpoint fwnodes */
3248c2ecf20Sopenharmony_ci	tx->sd.fwnode = of_fwnode_handle(state->endpoints[tx->port]);
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	/* Register internal ops for incremental subdev registration */
3278c2ecf20Sopenharmony_ci	tx->sd.internal_ops = &adv748x_csi2_internal_ops;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	tx->pads[ADV748X_CSI2_SINK].flags = MEDIA_PAD_FL_SINK;
3308c2ecf20Sopenharmony_ci	tx->pads[ADV748X_CSI2_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	ret = media_entity_pads_init(&tx->sd.entity, ADV748X_CSI2_NR_PADS,
3338c2ecf20Sopenharmony_ci				     tx->pads);
3348c2ecf20Sopenharmony_ci	if (ret)
3358c2ecf20Sopenharmony_ci		return ret;
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	ret = adv748x_csi2_init_controls(tx);
3388c2ecf20Sopenharmony_ci	if (ret)
3398c2ecf20Sopenharmony_ci		goto err_free_media;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	ret = v4l2_async_register_subdev(&tx->sd);
3428c2ecf20Sopenharmony_ci	if (ret)
3438c2ecf20Sopenharmony_ci		goto err_free_ctrl;
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	return 0;
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_cierr_free_ctrl:
3488c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_free(&tx->ctrl_hdl);
3498c2ecf20Sopenharmony_cierr_free_media:
3508c2ecf20Sopenharmony_ci	media_entity_cleanup(&tx->sd.entity);
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	return ret;
3538c2ecf20Sopenharmony_ci}
3548c2ecf20Sopenharmony_ci
3558c2ecf20Sopenharmony_civoid adv748x_csi2_cleanup(struct adv748x_csi2 *tx)
3568c2ecf20Sopenharmony_ci{
3578c2ecf20Sopenharmony_ci	if (!is_tx_enabled(tx))
3588c2ecf20Sopenharmony_ci		return;
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	v4l2_async_unregister_subdev(&tx->sd);
3618c2ecf20Sopenharmony_ci	media_entity_cleanup(&tx->sd.entity);
3628c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_free(&tx->ctrl_hdl);
3638c2ecf20Sopenharmony_ci}
364