diff options
Diffstat (limited to 'drivers/staging/media/tegra-video/csi.c')
-rw-r--r-- | drivers/staging/media/tegra-video/csi.c | 314 |
1 files changed, 284 insertions, 30 deletions
diff --git a/drivers/staging/media/tegra-video/csi.c b/drivers/staging/media/tegra-video/csi.c index 40ea195d141d..a19c85c57fca 100644 --- a/drivers/staging/media/tegra-video/csi.c +++ b/drivers/staging/media/tegra-video/csi.c @@ -9,13 +9,18 @@ #include <linux/host1x.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_graph.h> #include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <media/v4l2-fwnode.h> + #include "csi.h" #include "video.h" +#define MHZ 1000000 + static inline struct tegra_csi * host1x_client_to_csi(struct host1x_client *client) { @@ -62,6 +67,9 @@ static int csi_enum_bus_code(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_mbus_code_enum *code) { + if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) + return -ENOIOCTLCMD; + if (code->index >= ARRAY_SIZE(tegra_csi_tpg_fmts)) return -EINVAL; @@ -76,6 +84,9 @@ static int csi_get_format(struct v4l2_subdev *subdev, { struct tegra_csi_channel *csi_chan = to_csi_chan(subdev); + if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) + return -ENOIOCTLCMD; + fmt->format = csi_chan->format; return 0; @@ -121,6 +132,9 @@ static int csi_enum_framesizes(struct v4l2_subdev *subdev, { unsigned int i; + if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) + return -ENOIOCTLCMD; + if (fse->index >= ARRAY_SIZE(tegra_csi_tpg_sizes)) return -EINVAL; @@ -148,6 +162,9 @@ static int csi_enum_frameintervals(struct v4l2_subdev *subdev, const struct tpg_framerate *frmrate = csi->soc->tpg_frmrate_table; int index; + if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) + return -ENOIOCTLCMD; + /* one framerate per format and resolution */ if (fie->index > 0) return -EINVAL; @@ -172,6 +189,9 @@ static int csi_set_format(struct v4l2_subdev *subdev, const struct v4l2_frmsize_discrete *sizes; unsigned int i; + if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) + return -ENOIOCTLCMD; + sizes = v4l2_find_nearest_size(tegra_csi_tpg_sizes, ARRAY_SIZE(tegra_csi_tpg_sizes), width, height, @@ -208,40 +228,157 @@ static int tegra_csi_g_frame_interval(struct v4l2_subdev *subdev, { struct tegra_csi_channel *csi_chan = to_csi_chan(subdev); + if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) + return -ENOIOCTLCMD; + vfi->interval.numerator = 1; vfi->interval.denominator = csi_chan->framerate; return 0; } -static int tegra_csi_s_stream(struct v4l2_subdev *subdev, int enable) +static unsigned int csi_get_pixel_rate(struct tegra_csi_channel *csi_chan) +{ + struct tegra_vi_channel *chan; + struct v4l2_subdev *src_subdev; + struct v4l2_ctrl *ctrl; + + chan = v4l2_get_subdev_hostdata(&csi_chan->subdev); + src_subdev = tegra_channel_get_remote_source_subdev(chan); + ctrl = v4l2_ctrl_find(src_subdev->ctrl_handler, V4L2_CID_PIXEL_RATE); + if (ctrl) + return v4l2_ctrl_g_ctrl_int64(ctrl); + + return 0; +} + +void tegra_csi_calc_settle_time(struct tegra_csi_channel *csi_chan, + u8 *clk_settle_time, + u8 *ths_settle_time) +{ + struct tegra_csi *csi = csi_chan->csi; + unsigned int cil_clk_mhz; + unsigned int pix_clk_mhz; + int clk_idx = (csi_chan->csi_port_num >> 1) + 1; + + cil_clk_mhz = clk_get_rate(csi->clks[clk_idx].clk) / MHZ; + pix_clk_mhz = csi_get_pixel_rate(csi_chan) / MHZ; + + /* + * CLK Settle time is the interval during which HS receiver should + * ignore any clock lane HS transitions, starting from the beginning + * of T-CLK-PREPARE. + * Per DPHY specification, T-CLK-SETTLE should be between 95ns ~ 300ns + * + * 95ns < (clk-settle-programmed + 7) * lp clk period < 300ns + * midpoint = 197.5 ns + */ + *clk_settle_time = ((95 + 300) * cil_clk_mhz - 14000) / 2000; + + /* + * THS Settle time is the interval during which HS receiver should + * ignore any data lane HS transitions, starting from the beginning + * of THS-PREPARE. + * + * Per DPHY specification, T-HS-SETTLE should be between 85ns + 6UI + * and 145ns+10UI. + * 85ns + 6UI < (Ths-settle-prog + 5) * lp_clk_period < 145ns + 10UI + * midpoint = 115ns + 8UI + */ + if (pix_clk_mhz) + *ths_settle_time = (115 * cil_clk_mhz + 8000 * cil_clk_mhz + / (2 * pix_clk_mhz) - 5000) / 1000; +} + +static int tegra_csi_enable_stream(struct v4l2_subdev *subdev) { struct tegra_vi_channel *chan = v4l2_get_subdev_hostdata(subdev); struct tegra_csi_channel *csi_chan = to_csi_chan(subdev); struct tegra_csi *csi = csi_chan->csi; - int ret = 0; + int ret, err; - csi_chan->pg_mode = chan->pg_mode; - if (enable) { - ret = pm_runtime_get_sync(csi->dev); + ret = pm_runtime_get_sync(csi->dev); + if (ret < 0) { + dev_err(csi->dev, "failed to get runtime PM: %d\n", ret); + pm_runtime_put_noidle(csi->dev); + return ret; + } + + if (csi_chan->mipi) { + ret = tegra_mipi_enable(csi_chan->mipi); if (ret < 0) { dev_err(csi->dev, - "failed to get runtime PM: %d\n", ret); - pm_runtime_put_noidle(csi->dev); - return ret; + "failed to enable MIPI pads: %d\n", ret); + goto rpm_put; } - ret = csi->ops->csi_start_streaming(csi_chan); - if (ret < 0) - goto rpm_put; + /* + * CSI MIPI pads PULLUP, PULLDN and TERM impedances need to + * be calibrated after power on. + * So, trigger the calibration start here and results will + * be latched and applied to the pads when link is in LP11 + * state during start of sensor streaming. + */ + ret = tegra_mipi_start_calibration(csi_chan->mipi); + if (ret < 0) { + dev_err(csi->dev, + "failed to start MIPI calibration: %d\n", ret); + goto disable_mipi; + } + } - return 0; + csi_chan->pg_mode = chan->pg_mode; + ret = csi->ops->csi_start_streaming(csi_chan); + if (ret < 0) + goto finish_calibration; + + return 0; + +finish_calibration: + if (csi_chan->mipi) + tegra_mipi_finish_calibration(csi_chan->mipi); +disable_mipi: + if (csi_chan->mipi) { + err = tegra_mipi_disable(csi_chan->mipi); + if (err < 0) + dev_err(csi->dev, + "failed to disable MIPI pads: %d\n", err); } +rpm_put: + pm_runtime_put(csi->dev); + return ret; +} + +static int tegra_csi_disable_stream(struct v4l2_subdev *subdev) +{ + struct tegra_csi_channel *csi_chan = to_csi_chan(subdev); + struct tegra_csi *csi = csi_chan->csi; + int err; + csi->ops->csi_stop_streaming(csi_chan); -rpm_put: + if (csi_chan->mipi) { + err = tegra_mipi_disable(csi_chan->mipi); + if (err < 0) + dev_err(csi->dev, + "failed to disable MIPI pads: %d\n", err); + } + pm_runtime_put(csi->dev); + + return 0; +} + +static int tegra_csi_s_stream(struct v4l2_subdev *subdev, int enable) +{ + int ret; + + if (enable) + ret = tegra_csi_enable_stream(subdev); + else + ret = tegra_csi_disable_stream(subdev); + return ret; } @@ -267,29 +404,123 @@ static const struct v4l2_subdev_ops tegra_csi_ops = { .pad = &tegra_csi_pad_ops, }; +static int tegra_csi_channel_alloc(struct tegra_csi *csi, + struct device_node *node, + unsigned int port_num, unsigned int lanes, + unsigned int num_pads) +{ + struct tegra_csi_channel *chan; + int ret = 0; + + chan = kzalloc(sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + list_add_tail(&chan->list, &csi->csi_chans); + chan->csi = csi; + chan->csi_port_num = port_num; + chan->numlanes = lanes; + chan->of_node = node; + chan->numpads = num_pads; + if (num_pads & 0x2) { + chan->pads[0].flags = MEDIA_PAD_FL_SINK; + chan->pads[1].flags = MEDIA_PAD_FL_SOURCE; + } else { + chan->pads[0].flags = MEDIA_PAD_FL_SOURCE; + } + + if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) + return 0; + + chan->mipi = tegra_mipi_request(csi->dev, node); + if (IS_ERR(chan->mipi)) { + ret = PTR_ERR(chan->mipi); + dev_err(csi->dev, "failed to get mipi device: %d\n", ret); + } + + return ret; +} + static int tegra_csi_tpg_channels_alloc(struct tegra_csi *csi) { struct device_node *node = csi->dev->of_node; unsigned int port_num; - struct tegra_csi_channel *chan; unsigned int tpg_channels = csi->soc->csi_max_channels; + int ret; /* allocate CSI channel for each CSI x2 ports */ for (port_num = 0; port_num < tpg_channels; port_num++) { - chan = kzalloc(sizeof(*chan), GFP_KERNEL); - if (!chan) - return -ENOMEM; - - list_add_tail(&chan->list, &csi->csi_chans); - chan->csi = csi; - chan->csi_port_num = port_num; - chan->numlanes = 2; - chan->of_node = node; - chan->numpads = 1; - chan->pads[0].flags = MEDIA_PAD_FL_SOURCE; + ret = tegra_csi_channel_alloc(csi, node, port_num, 2, 1); + if (ret < 0) + return ret; + } + + return 0; +} + +static int tegra_csi_channels_alloc(struct tegra_csi *csi) +{ + struct device_node *node = csi->dev->of_node; + struct v4l2_fwnode_endpoint v4l2_ep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *fwh; + struct device_node *channel; + struct device_node *ep; + unsigned int lanes, portno, num_pads; + int ret; + + for_each_child_of_node(node, channel) { + if (!of_node_name_eq(channel, "channel")) + continue; + + ret = of_property_read_u32(channel, "reg", &portno); + if (ret < 0) + continue; + + if (portno >= csi->soc->csi_max_channels) { + dev_err(csi->dev, "invalid port num %d for %pOF\n", + portno, channel); + ret = -EINVAL; + goto err_node_put; + } + + ep = of_graph_get_endpoint_by_regs(channel, 0, 0); + if (!ep) + continue; + + fwh = of_fwnode_handle(ep); + ret = v4l2_fwnode_endpoint_parse(fwh, &v4l2_ep); + of_node_put(ep); + if (ret) { + dev_err(csi->dev, + "failed to parse v4l2 endpoint for %pOF: %d\n", + channel, ret); + goto err_node_put; + } + + lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; + if (!lanes || ((lanes & (lanes - 1)) != 0)) { + dev_err(csi->dev, "invalid data-lanes %d for %pOF\n", + lanes, channel); + ret = -EINVAL; + goto err_node_put; + } + + num_pads = of_graph_get_endpoint_count(channel); + if (num_pads == TEGRA_CSI_PADS_NUM) { + ret = tegra_csi_channel_alloc(csi, channel, portno, + lanes, num_pads); + if (ret < 0) + goto err_node_put; + } } return 0; + +err_node_put: + of_node_put(channel); + return ret; } static int tegra_csi_channel_init(struct tegra_csi_channel *chan) @@ -311,8 +542,12 @@ static int tegra_csi_channel_init(struct tegra_csi_channel *chan) subdev = &chan->subdev; v4l2_subdev_init(subdev, &tegra_csi_ops); subdev->dev = csi->dev; - snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s-%d", "tpg", - chan->csi_port_num); + if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) + snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s-%d", "tpg", + chan->csi_port_num); + else + snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s", + kbasename(chan->of_node->full_name)); v4l2_set_subdevdata(subdev, chan); subdev->fwnode = of_fwnode_handle(chan->of_node); @@ -328,6 +563,15 @@ static int tegra_csi_channel_init(struct tegra_csi_channel *chan) return ret; } + if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) { + ret = v4l2_async_register_subdev(subdev); + if (ret < 0) { + dev_err(csi->dev, + "failed to register subdev: %d\n", ret); + return ret; + } + } + return 0; } @@ -366,9 +610,16 @@ static void tegra_csi_channels_cleanup(struct tegra_csi *csi) struct tegra_csi_channel *chan, *tmp; list_for_each_entry_safe(chan, tmp, &csi->csi_chans, list) { + if (chan->mipi) + tegra_mipi_free(chan->mipi); + subdev = &chan->subdev; - if (subdev->dev) + if (subdev->dev) { + if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) + v4l2_async_unregister_subdev(subdev); media_entity_cleanup(&subdev->entity); + } + list_del(&chan->list); kfree(chan); } @@ -405,10 +656,13 @@ static int tegra_csi_init(struct host1x_client *client) INIT_LIST_HEAD(&csi->csi_chans); - ret = tegra_csi_tpg_channels_alloc(csi); + if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) + ret = tegra_csi_tpg_channels_alloc(csi); + else + ret = tegra_csi_channels_alloc(csi); if (ret < 0) { dev_err(csi->dev, - "failed to allocate tpg channels: %d\n", ret); + "failed to allocate channels: %d\n", ret); goto cleanup; } |