13d8a97eaSSowjanya Komatineni // SPDX-License-Identifier: GPL-2.0-only
23d8a97eaSSowjanya Komatineni /*
33d8a97eaSSowjanya Komatineni  * Copyright (C) 2020 NVIDIA CORPORATION.  All rights reserved.
43d8a97eaSSowjanya Komatineni  */
53d8a97eaSSowjanya Komatineni 
63d8a97eaSSowjanya Komatineni #include <linux/clk.h>
73d8a97eaSSowjanya Komatineni #include <linux/clk/tegra.h>
83d8a97eaSSowjanya Komatineni #include <linux/device.h>
93d8a97eaSSowjanya Komatineni #include <linux/host1x.h>
103d8a97eaSSowjanya Komatineni #include <linux/module.h>
113d8a97eaSSowjanya Komatineni #include <linux/of.h>
123d8a97eaSSowjanya Komatineni #include <linux/of_device.h>
133d8a97eaSSowjanya Komatineni #include <linux/platform_device.h>
143d8a97eaSSowjanya Komatineni #include <linux/pm_runtime.h>
153d8a97eaSSowjanya Komatineni 
163d8a97eaSSowjanya Komatineni #include "csi.h"
173d8a97eaSSowjanya Komatineni #include "video.h"
183d8a97eaSSowjanya Komatineni 
193d8a97eaSSowjanya Komatineni static inline struct tegra_csi *
203d8a97eaSSowjanya Komatineni host1x_client_to_csi(struct host1x_client *client)
213d8a97eaSSowjanya Komatineni {
223d8a97eaSSowjanya Komatineni 	return container_of(client, struct tegra_csi, client);
233d8a97eaSSowjanya Komatineni }
243d8a97eaSSowjanya Komatineni 
253d8a97eaSSowjanya Komatineni static inline struct tegra_csi_channel *to_csi_chan(struct v4l2_subdev *subdev)
263d8a97eaSSowjanya Komatineni {
273d8a97eaSSowjanya Komatineni 	return container_of(subdev, struct tegra_csi_channel, subdev);
283d8a97eaSSowjanya Komatineni }
293d8a97eaSSowjanya Komatineni 
303d8a97eaSSowjanya Komatineni /*
313d8a97eaSSowjanya Komatineni  * CSI is a separate subdevice which has 6 source pads to generate
323d8a97eaSSowjanya Komatineni  * test pattern. CSI subdevice pad ops are used only for TPG and
333d8a97eaSSowjanya Komatineni  * allows below TPG formats.
343d8a97eaSSowjanya Komatineni  */
353d8a97eaSSowjanya Komatineni static const struct v4l2_mbus_framefmt tegra_csi_tpg_fmts[] = {
363d8a97eaSSowjanya Komatineni 	{
373d8a97eaSSowjanya Komatineni 		TEGRA_DEF_WIDTH,
383d8a97eaSSowjanya Komatineni 		TEGRA_DEF_HEIGHT,
393d8a97eaSSowjanya Komatineni 		MEDIA_BUS_FMT_SRGGB10_1X10,
403d8a97eaSSowjanya Komatineni 		V4L2_FIELD_NONE,
413d8a97eaSSowjanya Komatineni 		V4L2_COLORSPACE_SRGB
423d8a97eaSSowjanya Komatineni 	},
433d8a97eaSSowjanya Komatineni 	{
443d8a97eaSSowjanya Komatineni 		TEGRA_DEF_WIDTH,
453d8a97eaSSowjanya Komatineni 		TEGRA_DEF_HEIGHT,
463d8a97eaSSowjanya Komatineni 		MEDIA_BUS_FMT_RGB888_1X32_PADHI,
473d8a97eaSSowjanya Komatineni 		V4L2_FIELD_NONE,
483d8a97eaSSowjanya Komatineni 		V4L2_COLORSPACE_SRGB
493d8a97eaSSowjanya Komatineni 	},
503d8a97eaSSowjanya Komatineni };
513d8a97eaSSowjanya Komatineni 
523d8a97eaSSowjanya Komatineni static const struct v4l2_frmsize_discrete tegra_csi_tpg_sizes[] = {
533d8a97eaSSowjanya Komatineni 	{ 1280, 720 },
543d8a97eaSSowjanya Komatineni 	{ 1920, 1080 },
553d8a97eaSSowjanya Komatineni 	{ 3840, 2160 },
563d8a97eaSSowjanya Komatineni };
573d8a97eaSSowjanya Komatineni 
583d8a97eaSSowjanya Komatineni /*
593d8a97eaSSowjanya Komatineni  * V4L2 Subdevice Pad Operations
603d8a97eaSSowjanya Komatineni  */
613d8a97eaSSowjanya Komatineni static int csi_enum_bus_code(struct v4l2_subdev *subdev,
623d8a97eaSSowjanya Komatineni 			     struct v4l2_subdev_pad_config *cfg,
633d8a97eaSSowjanya Komatineni 			     struct v4l2_subdev_mbus_code_enum *code)
643d8a97eaSSowjanya Komatineni {
65341187bfSSowjanya Komatineni 	if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
66341187bfSSowjanya Komatineni 		return -ENOIOCTLCMD;
67341187bfSSowjanya Komatineni 
683d8a97eaSSowjanya Komatineni 	if (code->index >= ARRAY_SIZE(tegra_csi_tpg_fmts))
693d8a97eaSSowjanya Komatineni 		return -EINVAL;
703d8a97eaSSowjanya Komatineni 
713d8a97eaSSowjanya Komatineni 	code->code = tegra_csi_tpg_fmts[code->index].code;
723d8a97eaSSowjanya Komatineni 
733d8a97eaSSowjanya Komatineni 	return 0;
743d8a97eaSSowjanya Komatineni }
753d8a97eaSSowjanya Komatineni 
763d8a97eaSSowjanya Komatineni static int csi_get_format(struct v4l2_subdev *subdev,
773d8a97eaSSowjanya Komatineni 			  struct v4l2_subdev_pad_config *cfg,
783d8a97eaSSowjanya Komatineni 			  struct v4l2_subdev_format *fmt)
793d8a97eaSSowjanya Komatineni {
803d8a97eaSSowjanya Komatineni 	struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
813d8a97eaSSowjanya Komatineni 
82341187bfSSowjanya Komatineni 	if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
83341187bfSSowjanya Komatineni 		return -ENOIOCTLCMD;
84341187bfSSowjanya Komatineni 
853d8a97eaSSowjanya Komatineni 	fmt->format = csi_chan->format;
863d8a97eaSSowjanya Komatineni 
873d8a97eaSSowjanya Komatineni 	return 0;
883d8a97eaSSowjanya Komatineni }
893d8a97eaSSowjanya Komatineni 
903d8a97eaSSowjanya Komatineni static int csi_get_frmrate_table_index(struct tegra_csi *csi, u32 code,
913d8a97eaSSowjanya Komatineni 				       u32 width, u32 height)
923d8a97eaSSowjanya Komatineni {
933d8a97eaSSowjanya Komatineni 	const struct tpg_framerate *frmrate;
943d8a97eaSSowjanya Komatineni 	unsigned int i;
953d8a97eaSSowjanya Komatineni 
963d8a97eaSSowjanya Komatineni 	frmrate = csi->soc->tpg_frmrate_table;
973d8a97eaSSowjanya Komatineni 	for (i = 0; i < csi->soc->tpg_frmrate_table_size; i++) {
983d8a97eaSSowjanya Komatineni 		if (frmrate[i].code == code &&
993d8a97eaSSowjanya Komatineni 		    frmrate[i].frmsize.width == width &&
1003d8a97eaSSowjanya Komatineni 		    frmrate[i].frmsize.height == height) {
1013d8a97eaSSowjanya Komatineni 			return i;
1023d8a97eaSSowjanya Komatineni 		}
1033d8a97eaSSowjanya Komatineni 	}
1043d8a97eaSSowjanya Komatineni 
1053d8a97eaSSowjanya Komatineni 	return -EINVAL;
1063d8a97eaSSowjanya Komatineni }
1073d8a97eaSSowjanya Komatineni 
1083d8a97eaSSowjanya Komatineni static void csi_chan_update_blank_intervals(struct tegra_csi_channel *csi_chan,
1093d8a97eaSSowjanya Komatineni 					    u32 code, u32 width, u32 height)
1103d8a97eaSSowjanya Komatineni {
1113d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi = csi_chan->csi;
1123d8a97eaSSowjanya Komatineni 	const struct tpg_framerate *frmrate = csi->soc->tpg_frmrate_table;
1133d8a97eaSSowjanya Komatineni 	int index;
1143d8a97eaSSowjanya Komatineni 
1153d8a97eaSSowjanya Komatineni 	index = csi_get_frmrate_table_index(csi_chan->csi, code,
1163d8a97eaSSowjanya Komatineni 					    width, height);
1173d8a97eaSSowjanya Komatineni 	if (index >= 0) {
1183d8a97eaSSowjanya Komatineni 		csi_chan->h_blank = frmrate[index].h_blank;
1193d8a97eaSSowjanya Komatineni 		csi_chan->v_blank = frmrate[index].v_blank;
1203d8a97eaSSowjanya Komatineni 		csi_chan->framerate = frmrate[index].framerate;
1213d8a97eaSSowjanya Komatineni 	}
1223d8a97eaSSowjanya Komatineni }
1233d8a97eaSSowjanya Komatineni 
1243d8a97eaSSowjanya Komatineni static int csi_enum_framesizes(struct v4l2_subdev *subdev,
1253d8a97eaSSowjanya Komatineni 			       struct v4l2_subdev_pad_config *cfg,
1263d8a97eaSSowjanya Komatineni 			       struct v4l2_subdev_frame_size_enum *fse)
1273d8a97eaSSowjanya Komatineni {
1283d8a97eaSSowjanya Komatineni 	unsigned int i;
1293d8a97eaSSowjanya Komatineni 
130341187bfSSowjanya Komatineni 	if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
131341187bfSSowjanya Komatineni 		return -ENOIOCTLCMD;
132341187bfSSowjanya Komatineni 
1333d8a97eaSSowjanya Komatineni 	if (fse->index >= ARRAY_SIZE(tegra_csi_tpg_sizes))
1343d8a97eaSSowjanya Komatineni 		return -EINVAL;
1353d8a97eaSSowjanya Komatineni 
1363d8a97eaSSowjanya Komatineni 	for (i = 0; i < ARRAY_SIZE(tegra_csi_tpg_fmts); i++)
1373d8a97eaSSowjanya Komatineni 		if (fse->code == tegra_csi_tpg_fmts[i].code)
1383d8a97eaSSowjanya Komatineni 			break;
1393d8a97eaSSowjanya Komatineni 
1403d8a97eaSSowjanya Komatineni 	if (i == ARRAY_SIZE(tegra_csi_tpg_fmts))
1413d8a97eaSSowjanya Komatineni 		return -EINVAL;
1423d8a97eaSSowjanya Komatineni 
1433d8a97eaSSowjanya Komatineni 	fse->min_width = tegra_csi_tpg_sizes[fse->index].width;
1443d8a97eaSSowjanya Komatineni 	fse->max_width = tegra_csi_tpg_sizes[fse->index].width;
1453d8a97eaSSowjanya Komatineni 	fse->min_height = tegra_csi_tpg_sizes[fse->index].height;
1463d8a97eaSSowjanya Komatineni 	fse->max_height = tegra_csi_tpg_sizes[fse->index].height;
1473d8a97eaSSowjanya Komatineni 
1483d8a97eaSSowjanya Komatineni 	return 0;
1493d8a97eaSSowjanya Komatineni }
1503d8a97eaSSowjanya Komatineni 
1513d8a97eaSSowjanya Komatineni static int csi_enum_frameintervals(struct v4l2_subdev *subdev,
1523d8a97eaSSowjanya Komatineni 				   struct v4l2_subdev_pad_config *cfg,
1533d8a97eaSSowjanya Komatineni 				   struct v4l2_subdev_frame_interval_enum *fie)
1543d8a97eaSSowjanya Komatineni {
1553d8a97eaSSowjanya Komatineni 	struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
1563d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi = csi_chan->csi;
1573d8a97eaSSowjanya Komatineni 	const struct tpg_framerate *frmrate = csi->soc->tpg_frmrate_table;
1583d8a97eaSSowjanya Komatineni 	int index;
1593d8a97eaSSowjanya Komatineni 
160341187bfSSowjanya Komatineni 	if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
161341187bfSSowjanya Komatineni 		return -ENOIOCTLCMD;
162341187bfSSowjanya Komatineni 
1633d8a97eaSSowjanya Komatineni 	/* one framerate per format and resolution */
1643d8a97eaSSowjanya Komatineni 	if (fie->index > 0)
1653d8a97eaSSowjanya Komatineni 		return -EINVAL;
1663d8a97eaSSowjanya Komatineni 
1673d8a97eaSSowjanya Komatineni 	index = csi_get_frmrate_table_index(csi_chan->csi, fie->code,
1683d8a97eaSSowjanya Komatineni 					    fie->width, fie->height);
1693d8a97eaSSowjanya Komatineni 	if (index < 0)
1703d8a97eaSSowjanya Komatineni 		return -EINVAL;
1713d8a97eaSSowjanya Komatineni 
1723d8a97eaSSowjanya Komatineni 	fie->interval.numerator = 1;
1733d8a97eaSSowjanya Komatineni 	fie->interval.denominator = frmrate[index].framerate;
1743d8a97eaSSowjanya Komatineni 
1753d8a97eaSSowjanya Komatineni 	return 0;
1763d8a97eaSSowjanya Komatineni }
1773d8a97eaSSowjanya Komatineni 
1783d8a97eaSSowjanya Komatineni static int csi_set_format(struct v4l2_subdev *subdev,
1793d8a97eaSSowjanya Komatineni 			  struct v4l2_subdev_pad_config *cfg,
1803d8a97eaSSowjanya Komatineni 			  struct v4l2_subdev_format *fmt)
1813d8a97eaSSowjanya Komatineni {
1823d8a97eaSSowjanya Komatineni 	struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
1833d8a97eaSSowjanya Komatineni 	struct v4l2_mbus_framefmt *format = &fmt->format;
1843d8a97eaSSowjanya Komatineni 	const struct v4l2_frmsize_discrete *sizes;
1853d8a97eaSSowjanya Komatineni 	unsigned int i;
1863d8a97eaSSowjanya Komatineni 
187341187bfSSowjanya Komatineni 	if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
188341187bfSSowjanya Komatineni 		return -ENOIOCTLCMD;
189341187bfSSowjanya Komatineni 
1903d8a97eaSSowjanya Komatineni 	sizes = v4l2_find_nearest_size(tegra_csi_tpg_sizes,
1913d8a97eaSSowjanya Komatineni 				       ARRAY_SIZE(tegra_csi_tpg_sizes),
1923d8a97eaSSowjanya Komatineni 				       width, height,
1933d8a97eaSSowjanya Komatineni 				       format->width, format->width);
1943d8a97eaSSowjanya Komatineni 	format->width = sizes->width;
1953d8a97eaSSowjanya Komatineni 	format->height = sizes->height;
1963d8a97eaSSowjanya Komatineni 
1973d8a97eaSSowjanya Komatineni 	for (i = 0; i < ARRAY_SIZE(tegra_csi_tpg_fmts); i++)
1983d8a97eaSSowjanya Komatineni 		if (format->code == tegra_csi_tpg_fmts[i].code)
1993d8a97eaSSowjanya Komatineni 			break;
2003d8a97eaSSowjanya Komatineni 
2013d8a97eaSSowjanya Komatineni 	if (i == ARRAY_SIZE(tegra_csi_tpg_fmts))
2023d8a97eaSSowjanya Komatineni 		i = 0;
2033d8a97eaSSowjanya Komatineni 
2043d8a97eaSSowjanya Komatineni 	format->code = tegra_csi_tpg_fmts[i].code;
2053d8a97eaSSowjanya Komatineni 	format->field = V4L2_FIELD_NONE;
2063d8a97eaSSowjanya Komatineni 
2073d8a97eaSSowjanya Komatineni 	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
2083d8a97eaSSowjanya Komatineni 		return 0;
2093d8a97eaSSowjanya Komatineni 
2103d8a97eaSSowjanya Komatineni 	/* update blanking intervals from frame rate table and format */
2113d8a97eaSSowjanya Komatineni 	csi_chan_update_blank_intervals(csi_chan, format->code,
2123d8a97eaSSowjanya Komatineni 					format->width, format->height);
2133d8a97eaSSowjanya Komatineni 	csi_chan->format = *format;
2143d8a97eaSSowjanya Komatineni 
2153d8a97eaSSowjanya Komatineni 	return 0;
2163d8a97eaSSowjanya Komatineni }
2173d8a97eaSSowjanya Komatineni 
2183d8a97eaSSowjanya Komatineni /*
2193d8a97eaSSowjanya Komatineni  * V4L2 Subdevice Video Operations
2203d8a97eaSSowjanya Komatineni  */
2213d8a97eaSSowjanya Komatineni static int tegra_csi_g_frame_interval(struct v4l2_subdev *subdev,
2223d8a97eaSSowjanya Komatineni 				      struct v4l2_subdev_frame_interval *vfi)
2233d8a97eaSSowjanya Komatineni {
2243d8a97eaSSowjanya Komatineni 	struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
2253d8a97eaSSowjanya Komatineni 
226341187bfSSowjanya Komatineni 	if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
227341187bfSSowjanya Komatineni 		return -ENOIOCTLCMD;
228341187bfSSowjanya Komatineni 
2293d8a97eaSSowjanya Komatineni 	vfi->interval.numerator = 1;
2303d8a97eaSSowjanya Komatineni 	vfi->interval.denominator = csi_chan->framerate;
2313d8a97eaSSowjanya Komatineni 
2323d8a97eaSSowjanya Komatineni 	return 0;
2333d8a97eaSSowjanya Komatineni }
2343d8a97eaSSowjanya Komatineni 
235654c433bSSowjanya Komatineni static int tegra_csi_enable_stream(struct v4l2_subdev *subdev)
2363d8a97eaSSowjanya Komatineni {
2373d8a97eaSSowjanya Komatineni 	struct tegra_vi_channel *chan = v4l2_get_subdev_hostdata(subdev);
2383d8a97eaSSowjanya Komatineni 	struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
2393d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi = csi_chan->csi;
240654c433bSSowjanya Komatineni 	int ret;
2413d8a97eaSSowjanya Komatineni 
2423d8a97eaSSowjanya Komatineni 	ret = pm_runtime_get_sync(csi->dev);
2433d8a97eaSSowjanya Komatineni 	if (ret < 0) {
244654c433bSSowjanya Komatineni 		dev_err(csi->dev, "failed to get runtime PM: %d\n", ret);
2453d8a97eaSSowjanya Komatineni 		pm_runtime_put_noidle(csi->dev);
2463d8a97eaSSowjanya Komatineni 		return ret;
2473d8a97eaSSowjanya Komatineni 	}
2483d8a97eaSSowjanya Komatineni 
249654c433bSSowjanya Komatineni 	csi_chan->pg_mode = chan->pg_mode;
2503d8a97eaSSowjanya Komatineni 	ret = csi->ops->csi_start_streaming(csi_chan);
2513d8a97eaSSowjanya Komatineni 	if (ret < 0)
2523d8a97eaSSowjanya Komatineni 		goto rpm_put;
2533d8a97eaSSowjanya Komatineni 
2543d8a97eaSSowjanya Komatineni 	return 0;
2553d8a97eaSSowjanya Komatineni 
2563d8a97eaSSowjanya Komatineni rpm_put:
2573d8a97eaSSowjanya Komatineni 	pm_runtime_put(csi->dev);
2583d8a97eaSSowjanya Komatineni 	return ret;
2593d8a97eaSSowjanya Komatineni }
2603d8a97eaSSowjanya Komatineni 
261654c433bSSowjanya Komatineni static int tegra_csi_disable_stream(struct v4l2_subdev *subdev)
262654c433bSSowjanya Komatineni {
263654c433bSSowjanya Komatineni 	struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
264654c433bSSowjanya Komatineni 	struct tegra_csi *csi = csi_chan->csi;
265654c433bSSowjanya Komatineni 
266654c433bSSowjanya Komatineni 	csi->ops->csi_stop_streaming(csi_chan);
267654c433bSSowjanya Komatineni 
268654c433bSSowjanya Komatineni 	pm_runtime_put(csi->dev);
269654c433bSSowjanya Komatineni 
270654c433bSSowjanya Komatineni 	return 0;
271654c433bSSowjanya Komatineni }
272654c433bSSowjanya Komatineni 
273654c433bSSowjanya Komatineni static int tegra_csi_s_stream(struct v4l2_subdev *subdev, int enable)
274654c433bSSowjanya Komatineni {
275654c433bSSowjanya Komatineni 	int ret;
276654c433bSSowjanya Komatineni 
277654c433bSSowjanya Komatineni 	if (enable)
278654c433bSSowjanya Komatineni 		ret = tegra_csi_enable_stream(subdev);
279654c433bSSowjanya Komatineni 	else
280654c433bSSowjanya Komatineni 		ret = tegra_csi_disable_stream(subdev);
281654c433bSSowjanya Komatineni 
282654c433bSSowjanya Komatineni 	return ret;
283654c433bSSowjanya Komatineni }
284654c433bSSowjanya Komatineni 
2853d8a97eaSSowjanya Komatineni /*
2863d8a97eaSSowjanya Komatineni  * V4L2 Subdevice Operations
2873d8a97eaSSowjanya Komatineni  */
2883d8a97eaSSowjanya Komatineni static const struct v4l2_subdev_video_ops tegra_csi_video_ops = {
2893d8a97eaSSowjanya Komatineni 	.s_stream = tegra_csi_s_stream,
2903d8a97eaSSowjanya Komatineni 	.g_frame_interval = tegra_csi_g_frame_interval,
2913d8a97eaSSowjanya Komatineni 	.s_frame_interval = tegra_csi_g_frame_interval,
2923d8a97eaSSowjanya Komatineni };
2933d8a97eaSSowjanya Komatineni 
2943d8a97eaSSowjanya Komatineni static const struct v4l2_subdev_pad_ops tegra_csi_pad_ops = {
2953d8a97eaSSowjanya Komatineni 	.enum_mbus_code		= csi_enum_bus_code,
2963d8a97eaSSowjanya Komatineni 	.enum_frame_size	= csi_enum_framesizes,
2973d8a97eaSSowjanya Komatineni 	.enum_frame_interval	= csi_enum_frameintervals,
2983d8a97eaSSowjanya Komatineni 	.get_fmt		= csi_get_format,
2993d8a97eaSSowjanya Komatineni 	.set_fmt		= csi_set_format,
3003d8a97eaSSowjanya Komatineni };
3013d8a97eaSSowjanya Komatineni 
3023d8a97eaSSowjanya Komatineni static const struct v4l2_subdev_ops tegra_csi_ops = {
3033d8a97eaSSowjanya Komatineni 	.video  = &tegra_csi_video_ops,
3043d8a97eaSSowjanya Komatineni 	.pad    = &tegra_csi_pad_ops,
3053d8a97eaSSowjanya Komatineni };
3063d8a97eaSSowjanya Komatineni 
3073d8a97eaSSowjanya Komatineni static int tegra_csi_tpg_channels_alloc(struct tegra_csi *csi)
3083d8a97eaSSowjanya Komatineni {
3093d8a97eaSSowjanya Komatineni 	struct device_node *node = csi->dev->of_node;
3103d8a97eaSSowjanya Komatineni 	unsigned int port_num;
3113d8a97eaSSowjanya Komatineni 	struct tegra_csi_channel *chan;
3123d8a97eaSSowjanya Komatineni 	unsigned int tpg_channels = csi->soc->csi_max_channels;
3133d8a97eaSSowjanya Komatineni 
3143d8a97eaSSowjanya Komatineni 	/* allocate CSI channel for each CSI x2 ports */
3153d8a97eaSSowjanya Komatineni 	for (port_num = 0; port_num < tpg_channels; port_num++) {
3163d8a97eaSSowjanya Komatineni 		chan = kzalloc(sizeof(*chan), GFP_KERNEL);
3173d8a97eaSSowjanya Komatineni 		if (!chan)
3183d8a97eaSSowjanya Komatineni 			return -ENOMEM;
3193d8a97eaSSowjanya Komatineni 
3203d8a97eaSSowjanya Komatineni 		list_add_tail(&chan->list, &csi->csi_chans);
3213d8a97eaSSowjanya Komatineni 		chan->csi = csi;
3223d8a97eaSSowjanya Komatineni 		chan->csi_port_num = port_num;
3233d8a97eaSSowjanya Komatineni 		chan->numlanes = 2;
3243d8a97eaSSowjanya Komatineni 		chan->of_node = node;
3253d8a97eaSSowjanya Komatineni 		chan->numpads = 1;
3263d8a97eaSSowjanya Komatineni 		chan->pads[0].flags = MEDIA_PAD_FL_SOURCE;
3273d8a97eaSSowjanya Komatineni 	}
3283d8a97eaSSowjanya Komatineni 
3293d8a97eaSSowjanya Komatineni 	return 0;
3303d8a97eaSSowjanya Komatineni }
3313d8a97eaSSowjanya Komatineni 
3323d8a97eaSSowjanya Komatineni static int tegra_csi_channel_init(struct tegra_csi_channel *chan)
3333d8a97eaSSowjanya Komatineni {
3343d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi = chan->csi;
3353d8a97eaSSowjanya Komatineni 	struct v4l2_subdev *subdev;
3363d8a97eaSSowjanya Komatineni 	int ret;
3373d8a97eaSSowjanya Komatineni 
3383d8a97eaSSowjanya Komatineni 	/* initialize the default format */
3393d8a97eaSSowjanya Komatineni 	chan->format.code = MEDIA_BUS_FMT_SRGGB10_1X10;
3403d8a97eaSSowjanya Komatineni 	chan->format.field = V4L2_FIELD_NONE;
3413d8a97eaSSowjanya Komatineni 	chan->format.colorspace = V4L2_COLORSPACE_SRGB;
3423d8a97eaSSowjanya Komatineni 	chan->format.width = TEGRA_DEF_WIDTH;
3433d8a97eaSSowjanya Komatineni 	chan->format.height = TEGRA_DEF_HEIGHT;
3443d8a97eaSSowjanya Komatineni 	csi_chan_update_blank_intervals(chan, chan->format.code,
3453d8a97eaSSowjanya Komatineni 					chan->format.width,
3463d8a97eaSSowjanya Komatineni 					chan->format.height);
3473d8a97eaSSowjanya Komatineni 	/* initialize V4L2 subdevice and media entity */
3483d8a97eaSSowjanya Komatineni 	subdev = &chan->subdev;
3493d8a97eaSSowjanya Komatineni 	v4l2_subdev_init(subdev, &tegra_csi_ops);
3503d8a97eaSSowjanya Komatineni 	subdev->dev = csi->dev;
351341187bfSSowjanya Komatineni 	if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
3523d8a97eaSSowjanya Komatineni 		snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s-%d", "tpg",
3533d8a97eaSSowjanya Komatineni 			 chan->csi_port_num);
354341187bfSSowjanya Komatineni 	else
355341187bfSSowjanya Komatineni 		snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s",
356341187bfSSowjanya Komatineni 			 kbasename(chan->of_node->full_name));
3573d8a97eaSSowjanya Komatineni 
3583d8a97eaSSowjanya Komatineni 	v4l2_set_subdevdata(subdev, chan);
3593d8a97eaSSowjanya Komatineni 	subdev->fwnode = of_fwnode_handle(chan->of_node);
3603d8a97eaSSowjanya Komatineni 	subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
3613d8a97eaSSowjanya Komatineni 
3623d8a97eaSSowjanya Komatineni 	/* initialize media entity pads */
3633d8a97eaSSowjanya Komatineni 	ret = media_entity_pads_init(&subdev->entity, chan->numpads,
3643d8a97eaSSowjanya Komatineni 				     chan->pads);
3653d8a97eaSSowjanya Komatineni 	if (ret < 0) {
3663d8a97eaSSowjanya Komatineni 		dev_err(csi->dev,
3673d8a97eaSSowjanya Komatineni 			"failed to initialize media entity: %d\n", ret);
3683d8a97eaSSowjanya Komatineni 		subdev->dev = NULL;
3693d8a97eaSSowjanya Komatineni 		return ret;
3703d8a97eaSSowjanya Komatineni 	}
3713d8a97eaSSowjanya Komatineni 
3723d8a97eaSSowjanya Komatineni 	return 0;
3733d8a97eaSSowjanya Komatineni }
3743d8a97eaSSowjanya Komatineni 
3753d8a97eaSSowjanya Komatineni void tegra_csi_error_recover(struct v4l2_subdev *sd)
3763d8a97eaSSowjanya Komatineni {
3773d8a97eaSSowjanya Komatineni 	struct tegra_csi_channel *csi_chan = to_csi_chan(sd);
3783d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi = csi_chan->csi;
3793d8a97eaSSowjanya Komatineni 
3803d8a97eaSSowjanya Komatineni 	/* stop streaming during error recovery */
3813d8a97eaSSowjanya Komatineni 	csi->ops->csi_stop_streaming(csi_chan);
3823d8a97eaSSowjanya Komatineni 	csi->ops->csi_err_recover(csi_chan);
3833d8a97eaSSowjanya Komatineni 	csi->ops->csi_start_streaming(csi_chan);
3843d8a97eaSSowjanya Komatineni }
3853d8a97eaSSowjanya Komatineni 
3863d8a97eaSSowjanya Komatineni static int tegra_csi_channels_init(struct tegra_csi *csi)
3873d8a97eaSSowjanya Komatineni {
3883d8a97eaSSowjanya Komatineni 	struct tegra_csi_channel *chan;
3893d8a97eaSSowjanya Komatineni 	int ret;
3903d8a97eaSSowjanya Komatineni 
3913d8a97eaSSowjanya Komatineni 	list_for_each_entry(chan, &csi->csi_chans, list) {
3923d8a97eaSSowjanya Komatineni 		ret = tegra_csi_channel_init(chan);
3933d8a97eaSSowjanya Komatineni 		if (ret) {
3943d8a97eaSSowjanya Komatineni 			dev_err(csi->dev,
3953d8a97eaSSowjanya Komatineni 				"failed to initialize channel-%d: %d\n",
3963d8a97eaSSowjanya Komatineni 				chan->csi_port_num, ret);
3973d8a97eaSSowjanya Komatineni 			return ret;
3983d8a97eaSSowjanya Komatineni 		}
3993d8a97eaSSowjanya Komatineni 	}
4003d8a97eaSSowjanya Komatineni 
4013d8a97eaSSowjanya Komatineni 	return 0;
4023d8a97eaSSowjanya Komatineni }
4033d8a97eaSSowjanya Komatineni 
4043d8a97eaSSowjanya Komatineni static void tegra_csi_channels_cleanup(struct tegra_csi *csi)
4053d8a97eaSSowjanya Komatineni {
4063d8a97eaSSowjanya Komatineni 	struct v4l2_subdev *subdev;
4073d8a97eaSSowjanya Komatineni 	struct tegra_csi_channel *chan, *tmp;
4083d8a97eaSSowjanya Komatineni 
4093d8a97eaSSowjanya Komatineni 	list_for_each_entry_safe(chan, tmp, &csi->csi_chans, list) {
4103d8a97eaSSowjanya Komatineni 		subdev = &chan->subdev;
4113d8a97eaSSowjanya Komatineni 		if (subdev->dev)
4123d8a97eaSSowjanya Komatineni 			media_entity_cleanup(&subdev->entity);
4133d8a97eaSSowjanya Komatineni 		list_del(&chan->list);
4143d8a97eaSSowjanya Komatineni 		kfree(chan);
4153d8a97eaSSowjanya Komatineni 	}
4163d8a97eaSSowjanya Komatineni }
4173d8a97eaSSowjanya Komatineni 
4183d8a97eaSSowjanya Komatineni static int __maybe_unused csi_runtime_suspend(struct device *dev)
4193d8a97eaSSowjanya Komatineni {
4203d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi = dev_get_drvdata(dev);
4213d8a97eaSSowjanya Komatineni 
4223d8a97eaSSowjanya Komatineni 	clk_bulk_disable_unprepare(csi->soc->num_clks, csi->clks);
4233d8a97eaSSowjanya Komatineni 
4243d8a97eaSSowjanya Komatineni 	return 0;
4253d8a97eaSSowjanya Komatineni }
4263d8a97eaSSowjanya Komatineni 
4273d8a97eaSSowjanya Komatineni static int __maybe_unused csi_runtime_resume(struct device *dev)
4283d8a97eaSSowjanya Komatineni {
4293d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi = dev_get_drvdata(dev);
4303d8a97eaSSowjanya Komatineni 	int ret;
4313d8a97eaSSowjanya Komatineni 
4323d8a97eaSSowjanya Komatineni 	ret = clk_bulk_prepare_enable(csi->soc->num_clks, csi->clks);
4333d8a97eaSSowjanya Komatineni 	if (ret < 0) {
4343d8a97eaSSowjanya Komatineni 		dev_err(csi->dev, "failed to enable clocks: %d\n", ret);
4353d8a97eaSSowjanya Komatineni 		return ret;
4363d8a97eaSSowjanya Komatineni 	}
4373d8a97eaSSowjanya Komatineni 
4383d8a97eaSSowjanya Komatineni 	return 0;
4393d8a97eaSSowjanya Komatineni }
4403d8a97eaSSowjanya Komatineni 
4413d8a97eaSSowjanya Komatineni static int tegra_csi_init(struct host1x_client *client)
4423d8a97eaSSowjanya Komatineni {
4433d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi = host1x_client_to_csi(client);
4443d8a97eaSSowjanya Komatineni 	struct tegra_video_device *vid = dev_get_drvdata(client->host);
4453d8a97eaSSowjanya Komatineni 	int ret;
4463d8a97eaSSowjanya Komatineni 
4473d8a97eaSSowjanya Komatineni 	INIT_LIST_HEAD(&csi->csi_chans);
4483d8a97eaSSowjanya Komatineni 
449341187bfSSowjanya Komatineni 	if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) {
4503d8a97eaSSowjanya Komatineni 		ret = tegra_csi_tpg_channels_alloc(csi);
4513d8a97eaSSowjanya Komatineni 		if (ret < 0) {
4523d8a97eaSSowjanya Komatineni 			dev_err(csi->dev,
4533d8a97eaSSowjanya Komatineni 				"failed to allocate tpg channels: %d\n", ret);
4543d8a97eaSSowjanya Komatineni 			goto cleanup;
4553d8a97eaSSowjanya Komatineni 		}
456341187bfSSowjanya Komatineni 	}
4573d8a97eaSSowjanya Komatineni 
4583d8a97eaSSowjanya Komatineni 	ret = tegra_csi_channels_init(csi);
4593d8a97eaSSowjanya Komatineni 	if (ret < 0)
4603d8a97eaSSowjanya Komatineni 		goto cleanup;
4613d8a97eaSSowjanya Komatineni 
4623d8a97eaSSowjanya Komatineni 	vid->csi = csi;
4633d8a97eaSSowjanya Komatineni 
4643d8a97eaSSowjanya Komatineni 	return 0;
4653d8a97eaSSowjanya Komatineni 
4663d8a97eaSSowjanya Komatineni cleanup:
4673d8a97eaSSowjanya Komatineni 	tegra_csi_channels_cleanup(csi);
4683d8a97eaSSowjanya Komatineni 	return ret;
4693d8a97eaSSowjanya Komatineni }
4703d8a97eaSSowjanya Komatineni 
4713d8a97eaSSowjanya Komatineni static int tegra_csi_exit(struct host1x_client *client)
4723d8a97eaSSowjanya Komatineni {
4733d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi = host1x_client_to_csi(client);
4743d8a97eaSSowjanya Komatineni 
4753d8a97eaSSowjanya Komatineni 	tegra_csi_channels_cleanup(csi);
4763d8a97eaSSowjanya Komatineni 
4773d8a97eaSSowjanya Komatineni 	return 0;
4783d8a97eaSSowjanya Komatineni }
4793d8a97eaSSowjanya Komatineni 
4803d8a97eaSSowjanya Komatineni static const struct host1x_client_ops csi_client_ops = {
4813d8a97eaSSowjanya Komatineni 	.init = tegra_csi_init,
4823d8a97eaSSowjanya Komatineni 	.exit = tegra_csi_exit,
4833d8a97eaSSowjanya Komatineni };
4843d8a97eaSSowjanya Komatineni 
4853d8a97eaSSowjanya Komatineni static int tegra_csi_probe(struct platform_device *pdev)
4863d8a97eaSSowjanya Komatineni {
4873d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi;
4883d8a97eaSSowjanya Komatineni 	unsigned int i;
4893d8a97eaSSowjanya Komatineni 	int ret;
4903d8a97eaSSowjanya Komatineni 
4913d8a97eaSSowjanya Komatineni 	csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL);
4923d8a97eaSSowjanya Komatineni 	if (!csi)
4933d8a97eaSSowjanya Komatineni 		return -ENOMEM;
4943d8a97eaSSowjanya Komatineni 
4953d8a97eaSSowjanya Komatineni 	csi->iomem = devm_platform_ioremap_resource(pdev, 0);
4963d8a97eaSSowjanya Komatineni 	if (IS_ERR(csi->iomem))
4973d8a97eaSSowjanya Komatineni 		return PTR_ERR(csi->iomem);
4983d8a97eaSSowjanya Komatineni 
4993d8a97eaSSowjanya Komatineni 	csi->soc = of_device_get_match_data(&pdev->dev);
5003d8a97eaSSowjanya Komatineni 
5013d8a97eaSSowjanya Komatineni 	csi->clks = devm_kcalloc(&pdev->dev, csi->soc->num_clks,
5023d8a97eaSSowjanya Komatineni 				 sizeof(*csi->clks), GFP_KERNEL);
5033d8a97eaSSowjanya Komatineni 	if (!csi->clks)
5043d8a97eaSSowjanya Komatineni 		return -ENOMEM;
5053d8a97eaSSowjanya Komatineni 
5063d8a97eaSSowjanya Komatineni 	for (i = 0; i < csi->soc->num_clks; i++)
5073d8a97eaSSowjanya Komatineni 		csi->clks[i].id = csi->soc->clk_names[i];
5083d8a97eaSSowjanya Komatineni 
5093d8a97eaSSowjanya Komatineni 	ret = devm_clk_bulk_get(&pdev->dev, csi->soc->num_clks, csi->clks);
5103d8a97eaSSowjanya Komatineni 	if (ret) {
5113d8a97eaSSowjanya Komatineni 		dev_err(&pdev->dev, "failed to get the clocks: %d\n", ret);
5123d8a97eaSSowjanya Komatineni 		return ret;
5133d8a97eaSSowjanya Komatineni 	}
5143d8a97eaSSowjanya Komatineni 
5153d8a97eaSSowjanya Komatineni 	if (!pdev->dev.pm_domain) {
5163d8a97eaSSowjanya Komatineni 		ret = -ENOENT;
5173d8a97eaSSowjanya Komatineni 		dev_warn(&pdev->dev, "PM domain is not attached: %d\n", ret);
5183d8a97eaSSowjanya Komatineni 		return ret;
5193d8a97eaSSowjanya Komatineni 	}
5203d8a97eaSSowjanya Komatineni 
5213d8a97eaSSowjanya Komatineni 	csi->dev = &pdev->dev;
5223d8a97eaSSowjanya Komatineni 	csi->ops = csi->soc->ops;
5233d8a97eaSSowjanya Komatineni 	platform_set_drvdata(pdev, csi);
5243d8a97eaSSowjanya Komatineni 	pm_runtime_enable(&pdev->dev);
5253d8a97eaSSowjanya Komatineni 
5263d8a97eaSSowjanya Komatineni 	/* initialize host1x interface */
5273d8a97eaSSowjanya Komatineni 	INIT_LIST_HEAD(&csi->client.list);
5283d8a97eaSSowjanya Komatineni 	csi->client.ops = &csi_client_ops;
5293d8a97eaSSowjanya Komatineni 	csi->client.dev = &pdev->dev;
5303d8a97eaSSowjanya Komatineni 
5313d8a97eaSSowjanya Komatineni 	ret = host1x_client_register(&csi->client);
5323d8a97eaSSowjanya Komatineni 	if (ret < 0) {
5333d8a97eaSSowjanya Komatineni 		dev_err(&pdev->dev,
5343d8a97eaSSowjanya Komatineni 			"failed to register host1x client: %d\n", ret);
5353d8a97eaSSowjanya Komatineni 		goto rpm_disable;
5363d8a97eaSSowjanya Komatineni 	}
5373d8a97eaSSowjanya Komatineni 
5383d8a97eaSSowjanya Komatineni 	return 0;
5393d8a97eaSSowjanya Komatineni 
5403d8a97eaSSowjanya Komatineni rpm_disable:
5413d8a97eaSSowjanya Komatineni 	pm_runtime_disable(&pdev->dev);
5423d8a97eaSSowjanya Komatineni 	return ret;
5433d8a97eaSSowjanya Komatineni }
5443d8a97eaSSowjanya Komatineni 
5453d8a97eaSSowjanya Komatineni static int tegra_csi_remove(struct platform_device *pdev)
5463d8a97eaSSowjanya Komatineni {
5473d8a97eaSSowjanya Komatineni 	struct tegra_csi *csi = platform_get_drvdata(pdev);
5483d8a97eaSSowjanya Komatineni 	int err;
5493d8a97eaSSowjanya Komatineni 
5503d8a97eaSSowjanya Komatineni 	err = host1x_client_unregister(&csi->client);
5513d8a97eaSSowjanya Komatineni 	if (err < 0) {
5523d8a97eaSSowjanya Komatineni 		dev_err(&pdev->dev,
5533d8a97eaSSowjanya Komatineni 			"failed to unregister host1x client: %d\n", err);
5543d8a97eaSSowjanya Komatineni 		return err;
5553d8a97eaSSowjanya Komatineni 	}
5563d8a97eaSSowjanya Komatineni 
5573d8a97eaSSowjanya Komatineni 	pm_runtime_disable(&pdev->dev);
5583d8a97eaSSowjanya Komatineni 
5593d8a97eaSSowjanya Komatineni 	return 0;
5603d8a97eaSSowjanya Komatineni }
5613d8a97eaSSowjanya Komatineni 
5623d8a97eaSSowjanya Komatineni static const struct of_device_id tegra_csi_of_id_table[] = {
5633d8a97eaSSowjanya Komatineni #if defined(CONFIG_ARCH_TEGRA_210_SOC)
5643d8a97eaSSowjanya Komatineni 	{ .compatible = "nvidia,tegra210-csi", .data = &tegra210_csi_soc },
5653d8a97eaSSowjanya Komatineni #endif
5663d8a97eaSSowjanya Komatineni 	{ }
5673d8a97eaSSowjanya Komatineni };
5683d8a97eaSSowjanya Komatineni MODULE_DEVICE_TABLE(of, tegra_csi_of_id_table);
5693d8a97eaSSowjanya Komatineni 
5703d8a97eaSSowjanya Komatineni static const struct dev_pm_ops tegra_csi_pm_ops = {
5713d8a97eaSSowjanya Komatineni 	SET_RUNTIME_PM_OPS(csi_runtime_suspend, csi_runtime_resume, NULL)
5723d8a97eaSSowjanya Komatineni };
5733d8a97eaSSowjanya Komatineni 
5743d8a97eaSSowjanya Komatineni struct platform_driver tegra_csi_driver = {
5753d8a97eaSSowjanya Komatineni 	.driver = {
5763d8a97eaSSowjanya Komatineni 		.name		= "tegra-csi",
5773d8a97eaSSowjanya Komatineni 		.of_match_table	= tegra_csi_of_id_table,
5783d8a97eaSSowjanya Komatineni 		.pm		= &tegra_csi_pm_ops,
5793d8a97eaSSowjanya Komatineni 	},
5803d8a97eaSSowjanya Komatineni 	.probe			= tegra_csi_probe,
5813d8a97eaSSowjanya Komatineni 	.remove			= tegra_csi_remove,
5823d8a97eaSSowjanya Komatineni };
583