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