Commit 0d2b746b authored by Paul Kocialkowski's avatar Paul Kocialkowski Committed by Mauro Carvalho Chehab

media: sun6i-csi: Add bridge v4l2 subdev with port management

Introduce a bridge v4l2 subdev to prepare for separation between the
processing part (bridge) and the dma engine, which is required to
properly support ths isp workflow later on.

Currently the bridge just manages fwnode mapping to media pads,
using an async notifier (which was previously in the main code).
The s_stream video op just forwards to the connected v4l2 subdev
(sensor or MIPI CSI-2 bridge).

The video capture device is now registered after the bridge and
attaches to it with a media link.
Signed-off-by: default avatarPaul Kocialkowski <paul.kocialkowski@bootlin.com>
Acked-by: default avatarJernej Skrabec <jernej.skrabec@gmail.com>
Signed-off-by: default avatarSakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@kernel.org>
parent 34ec7248
# SPDX-License-Identifier: GPL-2.0-only
sun6i-csi-y += sun6i_video.o sun6i_csi.o
sun6i-csi-y += sun6i_video.o sun6i_csi.o sun6i_csi_bridge.o
obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o
......@@ -34,16 +34,17 @@
bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev,
u32 pixformat, u32 mbus_code)
{
struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2;
struct v4l2_fwnode_endpoint *endpoint =
&csi_dev->bridge.source_parallel.endpoint;
/*
* Some video receivers have the ability to be compatible with
* 8bit and 16bit bus width.
* Identify the media bus format from device tree.
*/
if ((v4l2->v4l2_ep.bus_type == V4L2_MBUS_PARALLEL
|| v4l2->v4l2_ep.bus_type == V4L2_MBUS_BT656)
&& v4l2->v4l2_ep.bus.parallel.bus_width == 16) {
if ((endpoint->bus_type == V4L2_MBUS_PARALLEL
|| endpoint->bus_type == V4L2_MBUS_BT656)
&& endpoint->bus.parallel.bus_width == 16) {
switch (pixformat) {
case V4L2_PIX_FMT_NV12_16L16:
case V4L2_PIX_FMT_NV12:
......@@ -328,7 +329,8 @@ static enum csi_input_seq get_csi_input_seq(struct sun6i_csi_device *csi_dev,
static void sun6i_csi_setup_bus(struct sun6i_csi_device *csi_dev)
{
struct v4l2_fwnode_endpoint *endpoint = &csi_dev->v4l2.v4l2_ep;
struct v4l2_fwnode_endpoint *endpoint =
&csi_dev->bridge.source_parallel.endpoint;
struct sun6i_csi_config *config = &csi_dev->config;
unsigned char bus_width;
u32 flags;
......@@ -583,103 +585,11 @@ static const struct media_device_ops sun6i_csi_media_ops = {
/* V4L2 */
static int sun6i_csi_link_entity(struct sun6i_csi_device *csi_dev,
struct media_entity *entity,
struct fwnode_handle *fwnode)
{
struct media_entity *sink;
struct media_pad *sink_pad;
int src_pad_index;
int ret;
ret = media_entity_get_fwnode_pad(entity, fwnode, MEDIA_PAD_FL_SOURCE);
if (ret < 0) {
dev_err(csi_dev->dev,
"%s: no source pad in external entity %s\n", __func__,
entity->name);
return -EINVAL;
}
src_pad_index = ret;
sink = &csi_dev->video.video_dev.entity;
sink_pad = &csi_dev->video.pad;
dev_dbg(csi_dev->dev, "creating %s:%u -> %s:%u link\n",
entity->name, src_pad_index, sink->name, sink_pad->index);
ret = media_create_pad_link(entity, src_pad_index, sink,
sink_pad->index,
MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
if (ret < 0) {
dev_err(csi_dev->dev, "failed to create %s:%u -> %s:%u link\n",
entity->name, src_pad_index,
sink->name, sink_pad->index);
return ret;
}
return 0;
}
static int sun6i_subdev_notify_complete(struct v4l2_async_notifier *notifier)
{
struct sun6i_csi_device *csi_dev =
container_of(notifier, struct sun6i_csi_device,
v4l2.notifier);
struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2;
struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev;
struct v4l2_subdev *sd;
int ret;
dev_dbg(csi_dev->dev, "notify complete, all subdevs registered\n");
sd = list_first_entry(&v4l2_dev->subdevs, struct v4l2_subdev, list);
if (!sd)
return -EINVAL;
ret = sun6i_csi_link_entity(csi_dev, &sd->entity, sd->fwnode);
if (ret < 0)
return ret;
ret = v4l2_device_register_subdev_nodes(v4l2_dev);
if (ret < 0)
return ret;
return 0;
}
static const struct v4l2_async_notifier_operations sun6i_csi_async_ops = {
.complete = sun6i_subdev_notify_complete,
};
static int sun6i_csi_fwnode_parse(struct device *dev,
struct v4l2_fwnode_endpoint *vep,
struct v4l2_async_subdev *asd)
{
struct sun6i_csi_device *csi_dev = dev_get_drvdata(dev);
if (vep->base.port || vep->base.id) {
dev_warn(dev, "Only support a single port with one endpoint\n");
return -ENOTCONN;
}
switch (vep->bus_type) {
case V4L2_MBUS_PARALLEL:
case V4L2_MBUS_BT656:
csi_dev->v4l2.v4l2_ep = *vep;
return 0;
default:
dev_err(dev, "Unsupported media bus type\n");
return -ENOTCONN;
}
}
static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev)
{
struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2;
struct media_device *media_dev = &v4l2->media_dev;
struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev;
struct v4l2_async_notifier *notifier = &v4l2->notifier;
struct device *dev = csi_dev->dev;
int ret;
......@@ -709,42 +619,8 @@ static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev)
goto error_media;
}
/* Video */
ret = sun6i_video_setup(csi_dev);
if (ret)
goto error_v4l2_device;
/* V4L2 Async */
v4l2_async_nf_init(notifier);
notifier->ops = &sun6i_csi_async_ops;
ret = v4l2_async_nf_parse_fwnode_endpoints(dev, notifier,
sizeof(struct
v4l2_async_subdev),
sun6i_csi_fwnode_parse);
if (ret)
goto error_video;
ret = v4l2_async_nf_register(v4l2_dev, notifier);
if (ret) {
dev_err(dev, "failed to register v4l2 async notifier: %d\n",
ret);
goto error_v4l2_async_notifier;
}
return 0;
error_v4l2_async_notifier:
v4l2_async_nf_cleanup(notifier);
error_video:
sun6i_video_cleanup(csi_dev);
error_v4l2_device:
v4l2_device_unregister(&v4l2->v4l2_dev);
error_media:
media_device_unregister(media_dev);
media_device_cleanup(media_dev);
......@@ -757,9 +633,6 @@ static void sun6i_csi_v4l2_cleanup(struct sun6i_csi_device *csi_dev)
struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2;
media_device_unregister(&v4l2->media_dev);
v4l2_async_nf_unregister(&v4l2->notifier);
v4l2_async_nf_cleanup(&v4l2->notifier);
sun6i_video_cleanup(csi_dev);
v4l2_device_unregister(&v4l2->v4l2_dev);
media_device_cleanup(&v4l2->media_dev);
}
......@@ -963,8 +836,22 @@ static int sun6i_csi_probe(struct platform_device *platform_dev)
if (ret)
goto error_resources;
ret = sun6i_csi_bridge_setup(csi_dev);
if (ret)
goto error_v4l2;
ret = sun6i_video_setup(csi_dev);
if (ret)
goto error_bridge;
return 0;
error_bridge:
sun6i_csi_bridge_cleanup(csi_dev);
error_v4l2:
sun6i_csi_v4l2_cleanup(csi_dev);
error_resources:
sun6i_csi_resources_cleanup(csi_dev);
......@@ -975,6 +862,8 @@ static int sun6i_csi_remove(struct platform_device *pdev)
{
struct sun6i_csi_device *csi_dev = platform_get_drvdata(pdev);
sun6i_video_cleanup(csi_dev);
sun6i_csi_bridge_cleanup(csi_dev);
sun6i_csi_v4l2_cleanup(csi_dev);
sun6i_csi_resources_cleanup(csi_dev);
......
......@@ -12,11 +12,16 @@
#include <media/v4l2-fwnode.h>
#include <media/videobuf2-v4l2.h>
#include "sun6i_csi_bridge.h"
#include "sun6i_video.h"
#define SUN6I_CSI_NAME "sun6i-csi"
#define SUN6I_CSI_DESCRIPTION "Allwinner A31 CSI Device"
enum sun6i_csi_port {
SUN6I_CSI_PORT_PARALLEL = 0,
};
struct sun6i_csi_buffer {
struct vb2_v4l2_buffer v4l2_buffer;
struct list_head list;
......@@ -44,10 +49,6 @@ struct sun6i_csi_config {
struct sun6i_csi_v4l2 {
struct v4l2_device v4l2_dev;
struct media_device media_dev;
struct v4l2_async_notifier notifier;
/* video port settings */
struct v4l2_fwnode_endpoint v4l2_ep;
};
struct sun6i_csi_device {
......@@ -55,6 +56,7 @@ struct sun6i_csi_device {
struct sun6i_csi_config config;
struct sun6i_csi_v4l2 v4l2;
struct sun6i_csi_bridge bridge;
struct sun6i_video video;
struct regmap *regmap;
......
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2021-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#ifndef _SUN6I_CSI_BRIDGE_H_
#define _SUN6I_CSI_BRIDGE_H_
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#define SUN6I_CSI_BRIDGE_NAME "sun6i-csi-bridge"
enum sun6i_csi_bridge_pad {
SUN6I_CSI_BRIDGE_PAD_SINK = 0,
SUN6I_CSI_BRIDGE_PAD_SOURCE = 1,
SUN6I_CSI_BRIDGE_PAD_COUNT = 2,
};
struct sun6i_csi_device;
struct sun6i_csi_bridge_source {
struct v4l2_subdev *subdev;
struct v4l2_fwnode_endpoint endpoint;
bool expected;
};
struct sun6i_csi_bridge_async_subdev {
struct v4l2_async_subdev async_subdev;
struct sun6i_csi_bridge_source *source;
};
struct sun6i_csi_bridge {
struct v4l2_subdev subdev;
struct v4l2_async_notifier notifier;
struct media_pad pads[2];
struct v4l2_mbus_framefmt mbus_format;
struct mutex lock; /* Mbus format lock. */
struct sun6i_csi_bridge_source source_parallel;
};
/* Bridge */
int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev);
void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev);
#endif
......@@ -632,6 +632,7 @@ int sun6i_video_setup(struct sun6i_csi_device *csi_dev)
{
struct sun6i_video *video = &csi_dev->video;
struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev;
struct v4l2_subdev *bridge_subdev = &csi_dev->bridge.subdev;
struct video_device *video_dev = &video->video_dev;
struct vb2_queue *queue = &video->queue;
struct media_pad *pad = &video->pad;
......@@ -712,8 +713,26 @@ int sun6i_video_setup(struct sun6i_csi_device *csi_dev)
goto error_media_entity;
}
/* Media Pad Link */
ret = media_create_pad_link(&bridge_subdev->entity,
SUN6I_CSI_BRIDGE_PAD_SOURCE,
&video_dev->entity, 0,
MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
if (ret < 0) {
v4l2_err(v4l2_dev, "failed to create %s:%u -> %s:%u link\n",
bridge_subdev->entity.name,
SUN6I_CSI_BRIDGE_PAD_SOURCE,
video_dev->entity.name, 0);
goto error_video_device;
}
return 0;
error_video_device:
vb2_video_unregister_device(video_dev);
error_media_entity:
media_entity_cleanup(&video_dev->entity);
......
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