Commit 1ebaeb09 authored by Sowjanya Komatineni's avatar Sowjanya Komatineni Committed by Mauro Carvalho Chehab

media: tegra-video: Add support for external sensor capture

This patch adds support to capture from the external sensor
based on device graph in the device tree.

Driver walks through the device graph to create media links
between the entities and registers and unregisters video devices
when the corresponding sub-devices are bound and unbound.

Channel formats are enumerated based on available formats from
the sensor and the corresponding matched formats from the Tegra
supported video formats list.

Each Tegra CSI instance can be configured as 4-lane or 2-lane
based on supported lane configuration from the sensor through
the device tree.

Currently this driver supports V4L2 video node centric only.

[hverkuil: changed video_unregister_device to vb2_video_unregister_device]
Signed-off-by: default avatarSowjanya Komatineni <skomatineni@nvidia.com>
Signed-off-by: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab+huawei@kernel.org>
parent 654c433b
...@@ -5,6 +5,7 @@ config VIDEO_TEGRA ...@@ -5,6 +5,7 @@ config VIDEO_TEGRA
depends on VIDEO_V4L2 depends on VIDEO_V4L2
select MEDIA_CONTROLLER select MEDIA_CONTROLLER
select VIDEOBUF2_DMA_CONTIG select VIDEOBUF2_DMA_CONTIG
select V4L2_FWNODE
help help
Choose this option if you have an NVIDIA Tegra SoC. Choose this option if you have an NVIDIA Tegra SoC.
......
TODO list TODO list
* Currently driver supports Tegra build-in TPG only with direct media links
from CSI to VI. Add kernel config CONFIG_VIDEO_TEGRA_TPG and update the
driver to do TPG Vs Sensor media links based on CONFIG_VIDEO_TEGRA_TPG.
* Add real camera sensor capture support.
* Add Tegra CSI MIPI pads calibration. * Add Tegra CSI MIPI pads calibration.
* Add MIPI clock Settle time computation based on the data rate. * Add MIPI clock Settle time computation based on the data rate.
* Add support for Ganged mode. * Add support for Ganged mode.
......
...@@ -9,10 +9,13 @@ ...@@ -9,10 +9,13 @@
#include <linux/host1x.h> #include <linux/host1x.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <media/v4l2-fwnode.h>
#include "csi.h" #include "csi.h"
#include "video.h" #include "video.h"
...@@ -304,15 +307,13 @@ static const struct v4l2_subdev_ops tegra_csi_ops = { ...@@ -304,15 +307,13 @@ static const struct v4l2_subdev_ops tegra_csi_ops = {
.pad = &tegra_csi_pad_ops, .pad = &tegra_csi_pad_ops,
}; };
static int tegra_csi_tpg_channels_alloc(struct tegra_csi *csi) 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 device_node *node = csi->dev->of_node;
unsigned int port_num;
struct tegra_csi_channel *chan; struct tegra_csi_channel *chan;
unsigned int tpg_channels = csi->soc->csi_max_channels;
/* allocate CSI channel for each CSI x2 ports */
for (port_num = 0; port_num < tpg_channels; port_num++) {
chan = kzalloc(sizeof(*chan), GFP_KERNEL); chan = kzalloc(sizeof(*chan), GFP_KERNEL);
if (!chan) if (!chan)
return -ENOMEM; return -ENOMEM;
...@@ -320,15 +321,101 @@ static int tegra_csi_tpg_channels_alloc(struct tegra_csi *csi) ...@@ -320,15 +321,101 @@ static int tegra_csi_tpg_channels_alloc(struct tegra_csi *csi)
list_add_tail(&chan->list, &csi->csi_chans); list_add_tail(&chan->list, &csi->csi_chans);
chan->csi = csi; chan->csi = csi;
chan->csi_port_num = port_num; chan->csi_port_num = port_num;
chan->numlanes = 2; chan->numlanes = lanes;
chan->of_node = node; chan->of_node = node;
chan->numpads = 1; 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; chan->pads[0].flags = MEDIA_PAD_FL_SOURCE;
} }
return 0; return 0;
} }
static int tegra_csi_tpg_channels_alloc(struct tegra_csi *csi)
{
struct device_node *node = csi->dev->of_node;
unsigned int port_num;
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++) {
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) static int tegra_csi_channel_init(struct tegra_csi_channel *chan)
{ {
struct tegra_csi *csi = chan->csi; struct tegra_csi *csi = chan->csi;
...@@ -369,6 +456,15 @@ static int tegra_csi_channel_init(struct tegra_csi_channel *chan) ...@@ -369,6 +456,15 @@ static int tegra_csi_channel_init(struct tegra_csi_channel *chan)
return ret; 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; return 0;
} }
...@@ -408,8 +504,12 @@ static void tegra_csi_channels_cleanup(struct tegra_csi *csi) ...@@ -408,8 +504,12 @@ static void tegra_csi_channels_cleanup(struct tegra_csi *csi)
list_for_each_entry_safe(chan, tmp, &csi->csi_chans, list) { list_for_each_entry_safe(chan, tmp, &csi->csi_chans, list) {
subdev = &chan->subdev; 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); media_entity_cleanup(&subdev->entity);
}
list_del(&chan->list); list_del(&chan->list);
kfree(chan); kfree(chan);
} }
...@@ -446,14 +546,15 @@ static int tegra_csi_init(struct host1x_client *client) ...@@ -446,14 +546,15 @@ static int tegra_csi_init(struct host1x_client *client)
INIT_LIST_HEAD(&csi->csi_chans); INIT_LIST_HEAD(&csi->csi_chans);
if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) { if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
ret = tegra_csi_tpg_channels_alloc(csi); ret = tegra_csi_tpg_channels_alloc(csi);
else
ret = tegra_csi_channels_alloc(csi);
if (ret < 0) { if (ret < 0) {
dev_err(csi->dev, dev_err(csi->dev,
"failed to allocate tpg channels: %d\n", ret); "failed to allocate channels: %d\n", ret);
goto cleanup; goto cleanup;
} }
}
ret = tegra_csi_channels_init(csi); ret = tegra_csi_channels_init(csi);
if (ret < 0) if (ret < 0)
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#define __TEGRA_CSI_H__ #define __TEGRA_CSI_H__
#include <media/media-entity.h> #include <media/media-entity.h>
#include <media/v4l2-async.h>
#include <media/v4l2-subdev.h> #include <media/v4l2-subdev.h>
/* /*
......
...@@ -230,7 +230,7 @@ static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan) ...@@ -230,7 +230,7 @@ static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan)
tegra_channel_capture_setup(chan); tegra_channel_capture_setup(chan);
/* recover CSI block */ /* recover CSI block */
subdev = tegra_channel_get_remote_subdev(chan); subdev = tegra_channel_get_remote_csi_subdev(chan);
tegra_csi_error_recover(subdev); tegra_csi_error_recover(subdev);
} }
......
This diff is collapsed.
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <linux/wait.h> #include <linux/wait.h>
#include <media/media-entity.h> #include <media/media-entity.h>
#include <media/v4l2-async.h>
#include <media/v4l2-ctrls.h> #include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h> #include <media/v4l2-device.h>
#include <media/v4l2-dev.h> #include <media/v4l2-dev.h>
...@@ -92,6 +93,19 @@ struct tegra_vi { ...@@ -92,6 +93,19 @@ struct tegra_vi {
struct list_head vi_chans; struct list_head vi_chans;
}; };
/**
* struct tegra_vi_graph_entity - Entity in the video graph
*
* @asd: subdev asynchronous registration information
* @entity: media entity from the corresponding V4L2 subdev
* @subdev: V4L2 subdev
*/
struct tegra_vi_graph_entity {
struct v4l2_async_subdev asd;
struct media_entity *entity;
struct v4l2_subdev *subdev;
};
/** /**
* struct tegra_vi_channel - Tegra video channel * struct tegra_vi_channel - Tegra video channel
* *
...@@ -138,10 +152,13 @@ struct tegra_vi { ...@@ -138,10 +152,13 @@ struct tegra_vi {
* @done_lock: protects the capture done queue list * @done_lock: protects the capture done queue list
* *
* @portno: VI channel port number * @portno: VI channel port number
* @of_node: device node of VI channel
* *
* @ctrl_handler: V4L2 control handler of this video channel * @ctrl_handler: V4L2 control handler of this video channel
* @fmts_bitmap: a bitmap for supported formats matching v4l2 subdev formats
* @tpg_fmts_bitmap: a bitmap for supported TPG formats * @tpg_fmts_bitmap: a bitmap for supported TPG formats
* @pg_mode: test pattern generator mode (disabled/direct/patch) * @pg_mode: test pattern generator mode (disabled/direct/patch)
* @notifier: V4L2 asynchronous subdevs notifier
*/ */
struct tegra_vi_channel { struct tegra_vi_channel {
struct list_head list; struct list_head list;
...@@ -174,10 +191,14 @@ struct tegra_vi_channel { ...@@ -174,10 +191,14 @@ struct tegra_vi_channel {
spinlock_t done_lock; spinlock_t done_lock;
unsigned char portno; unsigned char portno;
struct device_node *of_node;
struct v4l2_ctrl_handler ctrl_handler; struct v4l2_ctrl_handler ctrl_handler;
DECLARE_BITMAP(fmts_bitmap, MAX_FORMAT_NUM);
DECLARE_BITMAP(tpg_fmts_bitmap, MAX_FORMAT_NUM); DECLARE_BITMAP(tpg_fmts_bitmap, MAX_FORMAT_NUM);
enum tegra_vi_pg_mode pg_mode; enum tegra_vi_pg_mode pg_mode;
struct v4l2_async_notifier notifier;
}; };
/** /**
...@@ -249,7 +270,9 @@ extern const struct tegra_vi_soc tegra210_vi_soc; ...@@ -249,7 +270,9 @@ extern const struct tegra_vi_soc tegra210_vi_soc;
#endif #endif
struct v4l2_subdev * struct v4l2_subdev *
tegra_channel_get_remote_subdev(struct tegra_vi_channel *chan); tegra_channel_get_remote_csi_subdev(struct tegra_vi_channel *chan);
struct v4l2_subdev *
tegra_channel_get_remote_source_subdev(struct tegra_vi_channel *chan);
int tegra_channel_set_stream(struct tegra_vi_channel *chan, bool on); int tegra_channel_set_stream(struct tegra_vi_channel *chan, bool on);
void tegra_channel_release_buffers(struct tegra_vi_channel *chan, void tegra_channel_release_buffers(struct tegra_vi_channel *chan,
enum vb2_buffer_state state); enum vb2_buffer_state state);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment