Commit e740d199 authored by Luca Ceresoli's avatar Luca Ceresoli Committed by Hans Verkuil

staging: media: tegra-video: add support for Tegra20 parallel input

The VI peripheral of Tegra supports capturing from MIPI CSI-2 or parallel
video (called VIP in the docs).

The staging tegra-video driver currently implements MIPI CSI-2 video
capture for Tegra210. Add support for parallel video capture (VIP) on
Tegra20. With the generalizations added to the VI driver in previous
commits, this is only a matter of adding the vip.c and tegra20.c
implementations and registering them.

Unfortunately there was no documentation available for the VI or VIP
peripherals of Tegra20 (or any other Tegra chips). This implementation has
been based entirely on the code from a vendor kernel based on Linux 3.1 and
massively adapted to fit into the tegra-video driver. Parts of this code is
definitely non-optimal to say the least (especially tegra20_vi_enable() and
the single-frame capture logic), but it was impossible to improve it.
Signed-off-by: default avatarLuca Ceresoli <luca.ceresoli@bootlin.com>
Reviewed-by: default avatarDmitry Osipenko <digetx@gmail.com>
Signed-off-by: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
[hverkuil: fix host1x_client_unregister usage: it's now a void func]
parent eeb036ab
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
tegra-video-objs := \ tegra-video-objs := \
video.o \ video.o \
vi.o \ vi.o \
vip.o \
csi.o csi.o
tegra-video-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20.o
tegra-video-$(CONFIG_ARCH_TEGRA_210_SOC) += tegra210.o tegra-video-$(CONFIG_ARCH_TEGRA_210_SOC) += tegra210.o
obj-$(CONFIG_VIDEO_TEGRA) += tegra-video.o obj-$(CONFIG_VIDEO_TEGRA) += tegra-video.o
This diff is collapsed.
...@@ -1955,6 +1955,9 @@ static int tegra_vi_remove(struct platform_device *pdev) ...@@ -1955,6 +1955,9 @@ static int tegra_vi_remove(struct platform_device *pdev)
} }
static const struct of_device_id tegra_vi_of_id_table[] = { static const struct of_device_id tegra_vi_of_id_table[] = {
#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
{ .compatible = "nvidia,tegra20-vi", .data = &tegra20_vi_soc },
#endif
#if defined(CONFIG_ARCH_TEGRA_210_SOC) #if defined(CONFIG_ARCH_TEGRA_210_SOC)
{ .compatible = "nvidia,tegra210-vi", .data = &tegra210_vi_soc }, { .compatible = "nvidia,tegra210-vi", .data = &tegra210_vi_soc },
#endif #endif
......
...@@ -296,6 +296,9 @@ struct tegra_video_format { ...@@ -296,6 +296,9 @@ struct tegra_video_format {
u32 fourcc; u32 fourcc;
}; };
#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
extern const struct tegra_vi_soc tegra20_vi_soc;
#endif
#if defined(CONFIG_ARCH_TEGRA_210_SOC) #if defined(CONFIG_ARCH_TEGRA_210_SOC)
extern const struct tegra_vi_soc tegra210_vi_soc; extern const struct tegra_vi_soc tegra210_vi_soc;
#endif #endif
......
...@@ -123,6 +123,10 @@ static int host1x_video_remove(struct host1x_device *dev) ...@@ -123,6 +123,10 @@ static int host1x_video_remove(struct host1x_device *dev)
} }
static const struct of_device_id host1x_video_subdevs[] = { static const struct of_device_id host1x_video_subdevs[] = {
#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
{ .compatible = "nvidia,tegra20-vip", },
{ .compatible = "nvidia,tegra20-vi", },
#endif
#if defined(CONFIG_ARCH_TEGRA_210_SOC) #if defined(CONFIG_ARCH_TEGRA_210_SOC)
{ .compatible = "nvidia,tegra210-csi", }, { .compatible = "nvidia,tegra210-csi", },
{ .compatible = "nvidia,tegra210-vi", }, { .compatible = "nvidia,tegra210-vi", },
...@@ -141,6 +145,7 @@ static struct host1x_driver host1x_video_driver = { ...@@ -141,6 +145,7 @@ static struct host1x_driver host1x_video_driver = {
static struct platform_driver * const drivers[] = { static struct platform_driver * const drivers[] = {
&tegra_csi_driver, &tegra_csi_driver,
&tegra_vip_driver,
&tegra_vi_driver, &tegra_vi_driver,
}; };
......
...@@ -24,5 +24,6 @@ int tegra_v4l2_nodes_setup_tpg(struct tegra_video_device *vid); ...@@ -24,5 +24,6 @@ int tegra_v4l2_nodes_setup_tpg(struct tegra_video_device *vid);
void tegra_v4l2_nodes_cleanup_tpg(struct tegra_video_device *vid); void tegra_v4l2_nodes_cleanup_tpg(struct tegra_video_device *vid);
extern struct platform_driver tegra_vi_driver; extern struct platform_driver tegra_vi_driver;
extern struct platform_driver tegra_vip_driver;
extern struct platform_driver tegra_csi_driver; extern struct platform_driver tegra_csi_driver;
#endif #endif
// SPDX-License-Identifier: GPL-2.0-only
/*
* Parallel video capture module (VIP) for the Tegra VI.
*
* This file implements the VIP-specific infrastructure.
*
* Copyright (C) 2023 SKIDATA GmbH
* Author: Luca Ceresoli <luca.ceresoli@bootlin.com>
*/
#include <linux/device.h>
#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 "vip.h"
static inline struct tegra_vip *host1x_client_to_vip(struct host1x_client *client)
{
return container_of(client, struct tegra_vip, client);
}
static inline struct tegra_vip_channel *subdev_to_vip_channel(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct tegra_vip_channel, subdev);
}
static inline struct tegra_vip *vip_channel_to_vip(struct tegra_vip_channel *chan)
{
return container_of(chan, struct tegra_vip, chan);
}
/* Find the previous subdev in the pipeline (i.e. the one connected to our sink pad) */
static struct v4l2_subdev *tegra_vip_channel_get_prev_subdev(struct tegra_vip_channel *chan)
{
struct media_pad *remote_pad;
remote_pad = media_pad_remote_pad_first(&chan->pads[TEGRA_VIP_PAD_SINK]);
if (!remote_pad)
return NULL;
return media_entity_to_v4l2_subdev(remote_pad->entity);
}
static int tegra_vip_enable_stream(struct v4l2_subdev *subdev)
{
struct tegra_vip_channel *vip_chan = subdev_to_vip_channel(subdev);
struct tegra_vip *vip = vip_channel_to_vip(vip_chan);
struct v4l2_subdev *prev_subdev = tegra_vip_channel_get_prev_subdev(vip_chan);
int err;
err = pm_runtime_resume_and_get(vip->dev);
if (err)
return dev_err_probe(vip->dev, err, "failed to get runtime PM\n");
err = vip->soc->ops->vip_start_streaming(vip_chan);
if (err < 0)
goto err_start_streaming;
err = v4l2_subdev_call(prev_subdev, video, s_stream, true);
if (err < 0 && err != -ENOIOCTLCMD)
goto err_prev_subdev_start_stream;
return 0;
err_prev_subdev_start_stream:
err_start_streaming:
pm_runtime_put(vip->dev);
return err;
}
static int tegra_vip_disable_stream(struct v4l2_subdev *subdev)
{
struct tegra_vip_channel *vip_chan = subdev_to_vip_channel(subdev);
struct tegra_vip *vip = vip_channel_to_vip(vip_chan);
struct v4l2_subdev *prev_subdev = tegra_vip_channel_get_prev_subdev(vip_chan);
v4l2_subdev_call(prev_subdev, video, s_stream, false);
pm_runtime_put(vip->dev);
return 0;
}
static int tegra_vip_s_stream(struct v4l2_subdev *subdev, int enable)
{
int err;
if (enable)
err = tegra_vip_enable_stream(subdev);
else
err = tegra_vip_disable_stream(subdev);
return err;
}
static const struct v4l2_subdev_video_ops tegra_vip_video_ops = {
.s_stream = tegra_vip_s_stream,
};
static const struct v4l2_subdev_ops tegra_vip_ops = {
.video = &tegra_vip_video_ops,
};
static int tegra_vip_channel_of_parse(struct tegra_vip *vip)
{
struct device *dev = vip->dev;
struct device_node *np = dev->of_node;
struct v4l2_fwnode_endpoint v4l2_ep = {
.bus_type = V4L2_MBUS_PARALLEL
};
struct fwnode_handle *fwh;
struct device_node *ep;
unsigned int num_pads;
int err;
dev_dbg(dev, "Parsing %pOF", np);
ep = of_graph_get_endpoint_by_regs(np, 0, 0);
if (!ep) {
err = -EINVAL;
dev_err_probe(dev, err, "%pOF: error getting endpoint node\n", np);
goto err_node_put;
}
fwh = of_fwnode_handle(ep);
err = v4l2_fwnode_endpoint_parse(fwh, &v4l2_ep);
of_node_put(ep);
if (err) {
dev_err_probe(dev, err, "%pOF: failed to parse v4l2 endpoint\n", np);
goto err_node_put;
}
num_pads = of_graph_get_endpoint_count(np);
if (num_pads != TEGRA_VIP_PADS_NUM) {
err = -EINVAL;
dev_err_probe(dev, err, "%pOF: need 2 pads, got %d\n", np, num_pads);
goto err_node_put;
}
vip->chan.of_node = of_node_get(np);
vip->chan.pads[TEGRA_VIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
vip->chan.pads[TEGRA_VIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
return 0;
err_node_put:
of_node_put(np);
return err;
}
static int tegra_vip_channel_init(struct tegra_vip *vip)
{
struct v4l2_subdev *subdev;
int err;
subdev = &vip->chan.subdev;
v4l2_subdev_init(subdev, &tegra_vip_ops);
subdev->dev = vip->dev;
snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s",
kbasename(vip->chan.of_node->full_name));
v4l2_set_subdevdata(subdev, &vip->chan);
subdev->fwnode = of_fwnode_handle(vip->chan.of_node);
subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
err = media_entity_pads_init(&subdev->entity, TEGRA_VIP_PADS_NUM, vip->chan.pads);
if (err)
return dev_err_probe(vip->dev, err, "failed to initialize media entity\n");
err = v4l2_async_register_subdev(subdev);
if (err) {
dev_err_probe(vip->dev, err, "failed to register subdev\n");
goto err_register_subdev;
}
return 0;
err_register_subdev:
media_entity_cleanup(&subdev->entity);
return err;
}
static int tegra_vip_init(struct host1x_client *client)
{
struct tegra_vip *vip = host1x_client_to_vip(client);
int err;
err = tegra_vip_channel_of_parse(vip);
if (err)
return err;
err = tegra_vip_channel_init(vip);
if (err)
goto err_init;
return 0;
err_init:
of_node_put(vip->chan.of_node);
return err;
}
static int tegra_vip_exit(struct host1x_client *client)
{
struct tegra_vip *vip = host1x_client_to_vip(client);
struct v4l2_subdev *subdev = &vip->chan.subdev;
v4l2_async_unregister_subdev(subdev);
media_entity_cleanup(&subdev->entity);
of_node_put(vip->chan.of_node);
return 0;
}
static const struct host1x_client_ops vip_client_ops = {
.init = tegra_vip_init,
.exit = tegra_vip_exit,
};
static int tegra_vip_probe(struct platform_device *pdev)
{
struct tegra_vip *vip;
int err;
dev_dbg(&pdev->dev, "Probing VIP \"%s\" from %pOF\n", pdev->name, pdev->dev.of_node);
vip = devm_kzalloc(&pdev->dev, sizeof(*vip), GFP_KERNEL);
if (!vip)
return -ENOMEM;
vip->soc = of_device_get_match_data(&pdev->dev);
vip->dev = &pdev->dev;
platform_set_drvdata(pdev, vip);
/* initialize host1x interface */
INIT_LIST_HEAD(&vip->client.list);
vip->client.ops = &vip_client_ops;
vip->client.dev = &pdev->dev;
err = host1x_client_register(&vip->client);
if (err)
return dev_err_probe(&pdev->dev, err, "failed to register host1x client\n");
pm_runtime_enable(&pdev->dev);
return 0;
}
static int tegra_vip_remove(struct platform_device *pdev)
{
struct tegra_vip *vip = platform_get_drvdata(pdev);
host1x_client_unregister(&vip->client);
pm_runtime_disable(&pdev->dev);
return 0;
}
#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
extern const struct tegra_vip_soc tegra20_vip_soc;
#endif
static const struct of_device_id tegra_vip_of_id_table[] = {
#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
{ .compatible = "nvidia,tegra20-vip", .data = &tegra20_vip_soc },
#endif
{ }
};
MODULE_DEVICE_TABLE(of, tegra_vip_of_id_table);
struct platform_driver tegra_vip_driver = {
.driver = {
.name = "tegra-vip",
.of_match_table = tegra_vip_of_id_table,
},
.probe = tegra_vip_probe,
.remove = tegra_vip_remove,
};
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2023 SKIDATA GmbH
* Author: Luca Ceresoli <luca.ceresoli@bootlin.com>
*/
#ifndef __TEGRA_VIP_H__
#define __TEGRA_VIP_H__
#include <media/media-entity.h>
#include <media/v4l2-async.h>
#include <media/v4l2-subdev.h>
enum {
TEGRA_VIP_PAD_SINK,
TEGRA_VIP_PAD_SOURCE,
TEGRA_VIP_PADS_NUM,
};
struct tegra_vip;
/**
* struct tegra_vip_channel - Tegra VIP (parallel video capture) channel
*
* @subdev: V4L2 subdevice associated with this channel
* @pads: media pads for the subdevice entity
* @of_node: vip device tree node
*/
struct tegra_vip_channel {
struct v4l2_subdev subdev;
struct media_pad pads[TEGRA_VIP_PADS_NUM];
struct device_node *of_node;
};
/**
* struct tegra_vip_ops - Tegra VIP operations
*
* @vip_start_streaming: programs vip hardware to enable streaming.
*/
struct tegra_vip_ops {
int (*vip_start_streaming)(struct tegra_vip_channel *vip_chan);
};
/**
* struct tegra_vip_soc - NVIDIA Tegra VIP SoC structure
*
* @ops: vip hardware operations
*/
struct tegra_vip_soc {
const struct tegra_vip_ops *ops;
};
/**
* struct tegra_vip - NVIDIA Tegra VIP device structure
*
* @dev: device struct
* @client: host1x_client struct
* @soc: pointer to SoC data structure
* @chan: the VIP channel
*/
struct tegra_vip {
struct device *dev;
struct host1x_client client;
const struct tegra_vip_soc *soc;
struct tegra_vip_channel chan;
};
#endif
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