* [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-27 5:01 [PATCH v2 0/3] Initial Allwinner V3s CSI Support Yong Deng
@ 2017-07-27 5:01 ` Yong Deng
[not found] ` <1501131697-1359-2-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
` (2 more replies)
2017-07-27 5:01 ` [PATCH v2 2/3] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI) Yong Deng
2017-07-27 5:01 ` [PATCH v2 3/3] media: MAINTAINERS: add entries for Allwinner V3s CSI Yong Deng
2 siblings, 3 replies; 56+ messages in thread
From: Yong Deng @ 2017-07-27 5:01 UTC (permalink / raw)
To: maxime.ripard
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik
Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
and CSI1 is used for parallel interface. This is not documented in
datasheet but by testing and guess.
This patch implement a v4l2 framework driver for it.
Currently, the driver only support the parallel interface. MIPI-CSI2,
ISP's support are not included in this patch.
Signed-off-by: Yong Deng <yong.deng@magewell.com>
---
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/sun6i-csi/Kconfig | 9 +
drivers/media/platform/sun6i-csi/Makefile | 3 +
drivers/media/platform/sun6i-csi/sun6i_csi.c | 545 +++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi.h | 203 ++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c | 827 +++++++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h | 206 ++++++
drivers/media/platform/sun6i-csi/sun6i_video.c | 663 ++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_video.h | 61 ++
10 files changed, 2520 insertions(+)
create mode 100644 drivers/media/platform/sun6i-csi/Kconfig
create mode 100644 drivers/media/platform/sun6i-csi/Makefile
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.h
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.h
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 0c741d1..8371a87 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -143,6 +143,7 @@ source "drivers/media/platform/am437x/Kconfig"
source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/sun6i-csi/Kconfig"
config VIDEO_TI_CAL
tristate "TI CAL (Camera Adaptation Layer) driver"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 9beadc7..fb2459c 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -86,3 +86,5 @@ obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/
obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk-jpeg/
obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi/
diff --git a/drivers/media/platform/sun6i-csi/Kconfig b/drivers/media/platform/sun6i-csi/Kconfig
new file mode 100644
index 0000000..314188a
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Kconfig
@@ -0,0 +1,9 @@
+config VIDEO_SUN6I_CSI
+ tristate "Allwinner V3s Camera Sensor Interface driver"
+ depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select REGMAP_MMIO
+ select V4L2_FWNODE
+ ---help---
+ Support for the Allwinner Camera Sensor Interface Controller on V3s.
diff --git a/drivers/media/platform/sun6i-csi/Makefile b/drivers/media/platform/sun6i-csi/Makefile
new file mode 100644
index 0000000..a9b527b
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Makefile
@@ -0,0 +1,3 @@
+sun6i-csi-y += sun6i_csi.o sun6i_video.o sun6i_csi_v3s.o
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sun6i-csi/sun6i_csi.c
new file mode 100644
index 0000000..7a4bf53
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.c
@@ -0,0 +1,545 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing),
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * Based on drivers/media/platform/xilinx/xilinx-vipp.c
+ * Copyright (C) 2013-2015 Ideas on Board
+ * Copyright (C) 2013-2015 Xilinx, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/of_graph.h>
+
+#include "sun6i_csi.h"
+
+/*
+ * struct sun6i_graph_entity - Entity in the video graph
+ * @list: list entry in a graph entities list
+ * @node: the entity's DT node
+ * @entity: media entity, from the corresponding V4L2 subdev
+ * @asd: subdev asynchronous registration information
+ * @subdev: V4L2 subdev
+ */
+struct sun6i_graph_entity {
+ struct list_head list;
+ struct device_node *node;
+ struct media_entity *entity;
+
+ struct v4l2_async_subdev asd;
+ struct v4l2_subdev *subdev;
+};
+
+/* -----------------------------------------------------------------------------
+ * Graph Management
+ */
+
+static struct sun6i_graph_entity *
+sun6i_graph_find_entity(struct sun6i_csi *csi,
+ const struct device_node *node)
+{
+ struct sun6i_graph_entity *entity;
+
+ list_for_each_entry(entity, &csi->entities, list) {
+ if (entity->node == node)
+ return entity;
+ }
+
+ return NULL;
+}
+
+static int sun6i_graph_build_one(struct sun6i_csi *csi,
+ struct sun6i_graph_entity *entity)
+{
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ struct media_entity *local = entity->entity;
+ struct media_entity *remote;
+ struct media_pad *local_pad;
+ struct media_pad *remote_pad;
+ struct sun6i_graph_entity *ent;
+ struct v4l2_fwnode_link link;
+ struct device_node *ep = NULL;
+ struct device_node *next;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "creating links for entity %s\n", local->name);
+
+ while (1) {
+ /* Get the next endpoint and parse its link. */
+ next = of_graph_get_next_endpoint(entity->node, ep);
+ if (next == NULL)
+ break;
+
+ of_node_put(ep);
+ ep = next;
+
+ dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
+
+ ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to parse link for %s\n",
+ ep->full_name);
+ continue;
+ }
+
+ /* Skip sink ports, they will be processed from the other end of
+ * the link.
+ */
+ if (link.local_port >= local->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.local_port,
+ to_of_node(link.local_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ local_pad = &local->pads[link.local_port];
+
+ if (local_pad->flags & MEDIA_PAD_FL_SINK) {
+ dev_dbg(csi->dev, "skipping sink port %s:%u\n",
+ to_of_node(link.local_node)->full_name,
+ link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* Skip video node, they will be processed separately. */
+ if (link.remote_node == of_fwnode_handle(csi->dev->of_node)) {
+ dev_dbg(csi->dev, "skipping CSI port %s:%u\n",
+ to_of_node(link.local_node)->full_name,
+ link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* Find the remote entity. */
+ ent = sun6i_graph_find_entity(csi,
+ to_of_node(link.remote_node));
+ if (ent == NULL) {
+ dev_err(csi->dev, "no entity found for %s\n",
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ remote = ent->entity;
+
+ if (link.remote_port >= remote->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.remote_port,
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ remote_pad = &remote->pads[link.remote_port];
+
+ v4l2_fwnode_put_link(&link);
+
+ /* Create the media link. */
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+
+ ret = media_create_pad_link(local, local_pad->index,
+ remote, remote_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to create %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+ break;
+ }
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_build_video(struct sun6i_csi *csi)
+{
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ struct device_node *node = csi->dev->of_node;
+ struct media_entity *source;
+ struct media_entity *sink;
+ struct media_pad *source_pad;
+ struct media_pad *sink_pad;
+ struct sun6i_graph_entity *ent;
+ struct v4l2_fwnode_link link;
+ struct device_node *ep = NULL;
+ struct device_node *next;
+ struct sun6i_video *video = &csi->video;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "creating link for video node\n");
+
+ while (1) {
+ /* Get the next endpoint and parse its link. */
+ next = of_graph_get_next_endpoint(node, ep);
+ if (next == NULL)
+ break;
+
+ of_node_put(ep);
+ ep = next;
+
+ dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
+
+ ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to parse link for %s\n",
+ ep->full_name);
+ continue;
+ }
+
+ /* Save the video port settings */
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep),
+ &csi->v4l2_ep);
+ if (ret) {
+ ret = -EINVAL;
+ dev_err(csi->dev, "Could not parse the endpoint\n");
+ v4l2_fwnode_put_link(&link);
+ break;
+ }
+
+ dev_dbg(csi->dev, "creating link for video node %s\n",
+ video->vdev.name);
+
+ /* Find the remote entity. */
+ ent = sun6i_graph_find_entity(csi,
+ to_of_node(link.remote_node));
+ if (ent == NULL) {
+ dev_err(csi->dev, "no entity found for %s\n",
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ if (link.remote_port >= ent->entity->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.remote_port,
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ source = ent->entity;
+ source_pad = &source->pads[link.remote_port];
+ sink = &video->vdev.entity;
+ sink_pad = &video->pad;
+
+ v4l2_fwnode_put_link(&link);
+
+ /* Create the media link. */
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+
+ ret = media_create_pad_link(source, source_pad->index,
+ sink, sink_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to create %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+ break;
+ }
+
+ /* Notify video node */
+ ret = media_entity_call(sink, link_setup, sink_pad, source_pad,
+ link_flags);
+ if (ret == -ENOIOCTLCMD)
+ ret = 0;
+
+ /* found one */
+ break;
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct sun6i_csi *csi =
+ container_of(notifier, struct sun6i_csi, notifier);
+ struct sun6i_graph_entity *entity;
+ int ret;
+
+ dev_dbg(csi->dev, "notify complete, all subdevs registered\n");
+
+ /* Create links for every entity. */
+ list_for_each_entry(entity, &csi->entities, list) {
+ ret = sun6i_graph_build_one(csi, entity);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Create links for video node. */
+ ret = sun6i_graph_build_video(csi);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
+ if (ret < 0)
+ dev_err(csi->dev, "failed to register subdev nodes\n");
+
+ return media_device_register(&csi->media_dev);
+}
+
+static int sun6i_graph_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct sun6i_csi *csi =
+ container_of(notifier, struct sun6i_csi, notifier);
+ struct sun6i_graph_entity *entity;
+
+ /* Locate the entity corresponding to the bound subdev and store the
+ * subdev pointer.
+ */
+ list_for_each_entry(entity, &csi->entities, list) {
+ if (entity->node != subdev->dev->of_node)
+ continue;
+
+ if (entity->subdev) {
+ dev_err(csi->dev, "duplicate subdev for node %s\n",
+ entity->node->full_name);
+ return -EINVAL;
+ }
+
+ dev_dbg(csi->dev, "subdev %s bound\n", subdev->name);
+ entity->entity = &subdev->entity;
+ entity->subdev = subdev;
+ return 0;
+ }
+
+ dev_err(csi->dev, "no entity for subdev %s\n", subdev->name);
+ return -EINVAL;
+}
+
+static int sun6i_graph_parse_one(struct sun6i_csi *csi,
+ struct device_node *node)
+{
+ struct sun6i_graph_entity *entity;
+ struct device_node *remote;
+ struct device_node *ep = NULL;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "parsing node %s\n", node->full_name);
+
+ while (1) {
+ ep = of_graph_get_next_endpoint(node, ep);
+ if (ep == NULL)
+ break;
+
+ dev_dbg(csi->dev, "handling endpoint %s\n", ep->full_name);
+
+ remote = of_graph_get_remote_port_parent(ep);
+ if (remote == NULL) {
+ ret = -EINVAL;
+ break;
+ }
+
+ /* Skip entities that we have already processed. */
+ if (remote == csi->dev->of_node ||
+ sun6i_graph_find_entity(csi, remote)) {
+ of_node_put(remote);
+ continue;
+ }
+
+ entity = devm_kzalloc(csi->dev, sizeof(*entity), GFP_KERNEL);
+ if (entity == NULL) {
+ of_node_put(remote);
+ ret = -ENOMEM;
+ break;
+ }
+
+ entity->node = remote;
+ entity->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+ entity->asd.match.fwnode.fwnode = of_fwnode_handle(remote);
+ list_add_tail(&entity->list, &csi->entities);
+ csi->num_subdevs++;
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_parse(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entity;
+ int ret;
+
+ /*
+ * Walk the links to parse the full graph. Start by parsing the
+ * composite node and then parse entities in turn. The list_for_each
+ * loop will handle entities added at the end of the list while walking
+ * the links.
+ */
+ ret = sun6i_graph_parse_one(csi, csi->dev->of_node);
+ if (ret < 0)
+ return 0;
+
+ list_for_each_entry(entity, &csi->entities, list) {
+ ret = sun6i_graph_parse_one(csi, entity->node);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static void sun6i_graph_cleanup(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entityp;
+ struct sun6i_graph_entity *entity;
+
+ v4l2_async_notifier_unregister(&csi->notifier);
+
+ list_for_each_entry_safe(entity, entityp, &csi->entities, list) {
+ of_node_put(entity->node);
+ list_del(&entity->list);
+ }
+}
+
+static int sun6i_graph_init(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entity;
+ struct v4l2_async_subdev **subdevs = NULL;
+ unsigned int num_subdevs;
+ unsigned int i;
+ int ret;
+
+ /* Parse the graph to extract a list of subdevice DT nodes. */
+ ret = sun6i_graph_parse(csi);
+ if (ret < 0) {
+ dev_err(csi->dev, "graph parsing failed\n");
+ goto done;
+ }
+
+ if (!csi->num_subdevs) {
+ dev_err(csi->dev, "no subdev found in graph\n");
+ goto done;
+ }
+
+ /* Register the subdevices notifier. */
+ num_subdevs = csi->num_subdevs;
+ subdevs = devm_kzalloc(csi->dev, sizeof(*subdevs) * num_subdevs,
+ GFP_KERNEL);
+ if (subdevs == NULL) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ i = 0;
+ list_for_each_entry(entity, &csi->entities, list)
+ subdevs[i++] = &entity->asd;
+
+ csi->notifier.subdevs = subdevs;
+ csi->notifier.num_subdevs = num_subdevs;
+ csi->notifier.bound = sun6i_graph_notify_bound;
+ csi->notifier.complete = sun6i_graph_notify_complete;
+
+ ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);
+ if (ret < 0) {
+ dev_err(csi->dev, "notifier registration failed\n");
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret < 0)
+ sun6i_graph_cleanup(csi);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Media Controller and V4L2
+ */
+
+static void sun6i_csi_v4l2_cleanup(struct sun6i_csi *csi)
+{
+ v4l2_device_unregister(&csi->v4l2_dev);
+ media_device_unregister(&csi->media_dev);
+ media_device_cleanup(&csi->media_dev);
+}
+
+static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->media_dev.dev = csi->dev;
+ strlcpy(csi->media_dev.model, "Allwinner Video Capture Device",
+ sizeof(csi->media_dev.model));
+ csi->media_dev.hw_revision = 0;
+
+ media_device_init(&csi->media_dev);
+
+ csi->v4l2_dev.mdev = &csi->media_dev;
+ ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
+ if (ret < 0) {
+ dev_err(csi->dev, "V4L2 device registration failed (%d)\n",
+ ret);
+ media_device_cleanup(&csi->media_dev);
+ return ret;
+ }
+ return 0;
+}
+
+int sun6i_csi_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->num_subdevs = 0;
+ INIT_LIST_HEAD(&csi->entities);
+
+ ret = sun6i_csi_v4l2_init(csi);
+ if (ret < 0)
+ return ret;
+
+ ret = sun6i_video_init(&csi->video, csi, "sun6i-csi");
+ if (ret < 0)
+ goto v4l2_clean;
+
+ ret = sun6i_graph_init(csi);
+ if (ret < 0)
+ goto video_clean;
+
+ return 0;
+
+video_clean:
+ sun6i_video_cleanup(&csi->video);
+v4l2_clean:
+ sun6i_csi_v4l2_cleanup(csi);
+ return ret;
+}
+
+int sun6i_csi_cleanup(struct sun6i_csi *csi)
+{
+ sun6i_video_cleanup(&csi->video);
+ sun6i_graph_cleanup(csi);
+ sun6i_csi_v4l2_cleanup(csi);
+
+ return 0;
+}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sun6i-csi/sun6i_csi.h
new file mode 100644
index 0000000..167f6bc
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_H__
+#define __SUN6I_CSI_H__
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#include "sun6i_video.h"
+
+struct sun6i_csi;
+
+/**
+ * struct sun6i_csi_config - configs for sun6i csi
+ * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @code: media bus format code (MEDIA_BUS_FMT_*)
+ * @field: used interlacing type (enum v4l2_field)
+ * @width: frame width
+ * @height: frame height
+ */
+struct sun6i_csi_config {
+ u32 pixelformat;
+ u32 code;
+ u32 field;
+ u32 width;
+ u32 height;
+};
+
+struct sun6i_csi_ops {
+ int (*get_supported_pixformats)(struct sun6i_csi *csi,
+ const u32 **pixformats);
+ bool (*is_format_support)(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code);
+ int (*s_power)(struct sun6i_csi *csi, bool enable);
+ int (*update_config)(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config);
+ int (*update_buf_addr)(struct sun6i_csi *csi, dma_addr_t addr);
+ int (*s_stream)(struct sun6i_csi *csi, bool enable);
+};
+
+struct sun6i_csi {
+ struct device *dev;
+ struct v4l2_device v4l2_dev;
+ struct media_device media_dev;
+
+ struct list_head entities;
+ unsigned int num_subdevs;
+ struct v4l2_async_notifier notifier;
+
+ /* video port settings */
+ struct v4l2_fwnode_endpoint v4l2_ep;
+
+ struct sun6i_csi_config config;
+
+ struct sun6i_video video;
+
+ struct sun6i_csi_ops *ops;
+};
+
+int sun6i_csi_init(struct sun6i_csi *csi);
+int sun6i_csi_cleanup(struct sun6i_csi *csi);
+
+/**
+ * sun6i_csi_get_supported_pixformats() - get csi supported pixformats
+ * @csi: pointer to the csi
+ * @pixformats: supported pixformats return from csi
+ *
+ * @return the count of pixformats or error(< 0)
+ */
+static inline int
+sun6i_csi_get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (csi->ops != NULL && csi->ops->get_supported_pixformats != NULL)
+ return csi->ops->get_supported_pixformats(csi, pixformats);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_is_format_support() - check if the format supported by csi
+ * @csi: pointer to the csi
+ * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @mbus_code: media bus format code (MEDIA_BUS_FMT_*)
+ */
+static inline bool
+sun6i_csi_is_format_support(struct sun6i_csi *csi, u32 pixformat, u32 mbus_code)
+{
+ if (csi->ops != NULL && csi->ops->is_format_support != NULL)
+ return csi->ops->is_format_support(csi, pixformat, mbus_code);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_power() - power on/off the csi
+ * @csi: pointer to the csi
+ * @enable: on/off
+ */
+static inline int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable)
+{
+ if (csi->ops != NULL && csi->ops->s_power != NULL)
+ return csi->ops->s_power(csi, enable);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_config() - update the csi register setttings
+ * @csi: pointer to the csi
+ * @config: see struct sun6i_csi_config
+ */
+static inline int
+sun6i_csi_update_config(struct sun6i_csi *csi, struct sun6i_csi_config *config)
+{
+ if (csi->ops != NULL && csi->ops->update_config != NULL)
+ return csi->ops->update_config(csi, config);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_buf_addr() - update the csi frame buffer address
+ * @csi: pointer to the csi
+ * @addr: frame buffer's physical address
+ */
+static inline int sun6i_csi_update_buf_addr(struct sun6i_csi *csi,
+ dma_addr_t addr)
+{
+ if (csi->ops != NULL && csi->ops->update_buf_addr != NULL)
+ return csi->ops->update_buf_addr(csi, addr);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_stream() - start/stop csi streaming
+ * @csi: pointer to the csi
+ * @enable: start/stop
+ */
+static inline int sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable)
+{
+ if (csi->ops != NULL && csi->ops->s_stream != NULL)
+ return csi->ops->s_stream(csi, enable);
+
+ return -ENOIOCTLCMD;
+}
+
+static inline int v4l2_pixformat_get_bpp(unsigned int pixformat)
+{
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return 8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return 10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return 12;
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV422P:
+ return 16;
+ case V4L2_PIX_FMT_RGB24:
+ case V4L2_PIX_FMT_BGR24:
+ return 24;
+ case V4L2_PIX_FMT_RGB32:
+ case V4L2_PIX_FMT_BGR32:
+ return 32;
+ }
+
+ return 0;
+}
+
+#endif /* __SUN6I_CSI_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
new file mode 100644
index 0000000..f940daa
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_csi_v3s.h"
+
+#define MODULE_NAME "sun6i-csi"
+
+struct sun6i_csi_dev {
+ struct sun6i_csi csi;
+ struct device *dev;
+
+ struct regmap *regmap;
+ struct clk *clk_ahb;
+ struct clk *clk_mod;
+ struct clk *clk_ram;
+ struct reset_control *rstc_ahb;
+
+ int planar_offset[3];
+};
+
+static const u32 supported_pixformats[] = {
+ V4L2_PIX_FMT_SBGGR8,
+ V4L2_PIX_FMT_SGBRG8,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_PIX_FMT_SRGGB8,
+ V4L2_PIX_FMT_SBGGR10,
+ V4L2_PIX_FMT_SGBRG10,
+ V4L2_PIX_FMT_SGRBG10,
+ V4L2_PIX_FMT_SRGGB10,
+ V4L2_PIX_FMT_SBGGR12,
+ V4L2_PIX_FMT_SGBRG12,
+ V4L2_PIX_FMT_SGRBG12,
+ V4L2_PIX_FMT_SRGGB12,
+ V4L2_PIX_FMT_YUYV,
+ V4L2_PIX_FMT_YVYU,
+ V4L2_PIX_FMT_UYVY,
+ V4L2_PIX_FMT_VYUY,
+ V4L2_PIX_FMT_HM12,
+ V4L2_PIX_FMT_NV12,
+ V4L2_PIX_FMT_NV21,
+ V4L2_PIX_FMT_YUV420,
+ V4L2_PIX_FMT_YVU420,
+ V4L2_PIX_FMT_NV16,
+ V4L2_PIX_FMT_NV61,
+ V4L2_PIX_FMT_YUV422P,
+};
+
+static inline struct sun6i_csi_dev *sun6i_csi_to_dev(struct sun6i_csi *csi)
+{
+ return container_of(csi, struct sun6i_csi_dev, csi);
+}
+
+/* TODO add 10&12 bit YUV, RGB support */
+static bool __is_format_support(struct sun6i_csi_dev *sdev,
+ u32 fourcc, u32 mbus_code)
+{
+ /*
+ * Some video receiver have capability both 8bit and 16bit.
+ * Identify the media bus format from device tree.
+ */
+ if (((sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_PARALLEL
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_BT656)
+ && sdev->csi.v4l2_ep.bus.parallel.bus_width == 16)
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_CSI2) {
+ switch (fourcc) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ switch (fourcc) {
+ case V4L2_PIX_FMT_SBGGR8:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG8:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG8:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB8:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SBGGR10:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG10:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG10:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB10:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SBGGR12:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG12:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG12:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB12:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12)
+ return true;
+ break;
+
+ case V4L2_PIX_FMT_YUYV:
+ if (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_YVYU:
+ if (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ if (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_VYUY:
+ if (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8)
+ return true;
+ break;
+
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+static enum csi_input_fmt get_csi_input_format(u32 mbus_code, u32 pixformat)
+{
+ /* bayer */
+ if ((mbus_code & 0xF000) == 0x3000)
+ return CSI_INPUT_FORMAT_RAW;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return CSI_INPUT_FORMAT_RAW;
+ }
+
+ /* not support YUV420 input format yet */
+ return CSI_INPUT_FORMAT_YUV422;
+}
+
+static enum csi_output_fmt get_csi_output_format(u32 pixformat, u32 field)
+{
+ bool buf_interlaced = false;
+ if (field == V4L2_FIELD_INTERLACED
+ || field == V4L2_FIELD_INTERLACED_TB
+ || field == V4L2_FIELD_INTERLACED_BT)
+ buf_interlaced = true;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12;
+
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+
+ case V4L2_PIX_FMT_HM12:
+ return buf_interlaced ? CSI_FRAME_MB_YUV420 :
+ CSI_FIELD_MB_YUV420;
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 :
+ CSI_FIELD_UV_CB_YUV420;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 :
+ CSI_FIELD_PLANAR_YUV420;
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 :
+ CSI_FIELD_UV_CB_YUV422;
+ case V4L2_PIX_FMT_YUV422P:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 :
+ CSI_FIELD_PLANAR_YUV422;
+ }
+
+ return 0;
+}
+
+static enum csi_input_seq get_csi_input_seq(u32 mbus_code, u32 pixformat)
+{
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch(mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YUYV;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YVYU;
+ }
+ break;
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YVU420:
+ switch(mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YVYU;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YUYV;
+ }
+ break;
+ }
+
+ return CSI_INPUT_SEQ_YUYV;
+}
+
+#ifdef DEBUG
+static void sun6i_csi_dump_regs(struct sun6i_csi_dev *sdev)
+{
+ struct regmap *regmap = sdev->regmap;
+ u32 val;
+
+ regmap_read(regmap, CSI_EN_REG, &val);
+ printk("CSI_EN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_IF_CFG_REG, &val);
+ printk("CSI_IF_CFG_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CAP_REG, &val);
+ printk("CSI_CAP_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_SYNC_CNT_REG, &val);
+ printk("CSI_SYNC_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_FIFO_THRS_REG, &val);
+ printk("CSI_FIFO_THRS_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_PTN_LEN_REG, &val);
+ printk("CSI_PTN_LEN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_PTN_ADDR_REG, &val);
+ printk("CSI_PTN_ADDR_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_VER_REG, &val);
+ printk("CSI_VER_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_CFG_REG, &val);
+ printk("CSI_CH_CFG_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_SCALE_REG, &val);
+ printk("CSI_CH_SCALE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F0_BUFA_REG, &val);
+ printk("CSI_CH_F0_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F1_BUFA_REG, &val);
+ printk("CSI_CH_F1_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F2_BUFA_REG, &val);
+ printk("CSI_CH_F2_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_STA_REG, &val);
+ printk("CSI_CH_STA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_INT_EN_REG, &val);
+ printk("CSI_CH_INT_EN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &val);
+ printk("CSI_CH_INT_STA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FLD1_VSIZE_REG, &val);
+ printk("CSI_CH_FLD1_VSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_HSIZE_REG, &val);
+ printk("CSI_CH_HSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_VSIZE_REG, &val);
+ printk("CSI_CH_VSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_BUF_LEN_REG, &val);
+ printk("CSI_CH_BUF_LEN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FLIP_SIZE_REG, &val);
+ printk("CSI_CH_FLIP_SIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FRM_CLK_CNT_REG, &val);
+ printk("CSI_CH_FRM_CLK_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_ACC_ITNL_CLK_CNT_REG, &val);
+ printk("CSI_CH_ACC_ITNL_CLK_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FIFO_STAT_REG, &val);
+ printk("CSI_CH_FIFO_STAT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_PCLK_STAT_REG, &val);
+ printk("CSI_CH_PCLK_STAT_REG=0x%x\n", val);
+}
+#endif
+
+static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev)
+{
+ struct v4l2_fwnode_endpoint *endpoint = &sdev->csi.v4l2_ep;
+ unsigned char bus_width;
+ u32 flags;
+ u32 cfg;
+
+ bus_width = endpoint->bus.parallel.bus_width;
+
+ regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK |
+ CSI_IF_CFG_IF_DATA_WIDTH_MASK |
+ CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK |
+ CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK);
+
+ switch (endpoint->bus_type) {
+ case V4L2_MBUS_CSI2:
+ cfg |= CSI_IF_CFG_MIPI_IF_MIPI;
+ break;
+ case V4L2_MBUS_PARALLEL:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT :
+ CSI_IF_CFG_CSI_IF_YUV422_INTLV;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_VREF_POL_POSITIVE;
+ if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_HREF_POL_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ case V4L2_MBUS_BT656:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 :
+ CSI_IF_CFG_CSI_IF_BT656;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ default:
+ BUG();
+ break;
+ }
+
+ switch (bus_width) {
+ case 8:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT;
+ break;
+ case 10:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT;
+ break;
+ case 12:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT;
+ break;
+ default:
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_IF_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_format(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi *csi = &sdev->csi;
+ u32 cfg;
+ u32 val;
+
+ regmap_read(sdev->regmap, CSI_CH_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK |
+ CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN |
+ CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK |
+ CSI_CH_CFG_INPUT_SEQ_MASK);
+
+ val = get_csi_input_format(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_FMT(val);
+
+ val = get_csi_output_format(csi->config.code, csi->config.field);
+ cfg |= CSI_CH_CFG_OUTPUT_FMT(val);
+
+ val = get_csi_input_seq(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_SEQ(val);
+
+ if (csi->config.field == V4L2_FIELD_TOP)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0;
+ else if (csi->config.field == V4L2_FIELD_BOTTOM)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1;
+ else
+ cfg |= CSI_CH_CFG_FIELD_SEL_BOTH;
+
+ regmap_write(sdev->regmap, CSI_CH_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_window(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi_config *config = &sdev->csi.config;
+ u32 bytesperline_y;
+ u32 bytesperline_c;
+ int *planar_offset = sdev->planar_offset;
+
+ regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
+ CSI_CH_HSIZE_HOR_LEN(config->width) |
+ CSI_CH_HSIZE_HOR_START(0));
+ regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
+ CSI_CH_VSIZE_VER_LEN(config->height) |
+ CSI_CH_VSIZE_VER_START(0));
+
+ planar_offset[0] = 0;
+ switch(config->pixelformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = -1;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width / 2;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * config->height / 2;
+ break;
+ case V4L2_PIX_FMT_YUV422P:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width / 2;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * config->height;
+ break;
+ default: /* raw */
+ bytesperline_y = (v4l2_pixformat_get_bpp(config->pixelformat) *
+ config->width) / 8;
+ bytesperline_c = 0;
+ planar_offset[1] = -1;
+ planar_offset[2] = -1;
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_CH_BUF_LEN_REG,
+ CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) |
+ CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y));
+}
+
+static int get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (pixformats != NULL)
+ *pixformats = supported_pixformats;
+
+ return ARRAY_SIZE(supported_pixformats);
+}
+
+static bool is_format_support(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ return __is_format_support(sdev, pixformat, mbus_code);
+}
+
+static int set_power(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+ int ret;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+
+ clk_disable_unprepare(sdev->clk_ram);
+ clk_disable_unprepare(sdev->clk_mod);
+ clk_disable_unprepare(sdev->clk_ahb);
+ reset_control_assert(sdev->rstc_ahb);
+ return 0;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ahb);
+ if (ret) {
+ dev_err(sdev->dev, "Enable ahb clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_mod);
+ if (ret) {
+ dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ram);
+ if (ret) {
+ dev_err(sdev->dev, "Enable clk_dram_csi clk err %d\n", ret);
+ return ret;
+ }
+
+ if (!IS_ERR_OR_NULL(sdev->rstc_ahb)) {
+ ret = reset_control_deassert(sdev->rstc_ahb);
+ if (ret) {
+ dev_err(sdev->dev, "reset err %d\n", ret);
+ return ret;
+ }
+ }
+
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
+
+ return 0;
+}
+
+static int update_config(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ if (config == NULL)
+ return -EINVAL;
+
+ memcpy(&csi->config, config, sizeof(csi->config));
+
+ sun6i_csi_setup_bus(sdev);
+ sun6i_csi_set_format(sdev);
+ sun6i_csi_set_window(sdev);
+
+ return 0;
+}
+
+static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ /* transform physical address to bus address */
+ dma_addr_t bus_addr = addr - 0x40000000;
+
+ regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
+ (bus_addr + sdev->planar_offset[0]) >> 2);
+ if (sdev->planar_offset[1] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
+ (bus_addr + sdev->planar_offset[1]) >> 2);
+ if (sdev->planar_offset[2] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
+ (bus_addr + sdev->planar_offset[2]) >> 2);
+
+ return 0;
+}
+
+static int set_stream(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0);
+ regmap_write(regmap, CSI_CH_INT_EN_REG, 0);
+ return 0;
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF);
+ regmap_write(regmap, CSI_CH_INT_EN_REG,
+ CSI_CH_INT_EN_HB_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO2_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO1_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO0_OF_INT_EN |
+ CSI_CH_INT_EN_FD_INT_EN |
+ CSI_CH_INT_EN_CD_INT_EN);
+
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON,
+ CSI_CAP_CH0_VCAP_ON);
+
+ return 0;
+}
+
+static struct sun6i_csi_ops csi_ops = {
+ .get_supported_pixformats = get_supported_pixformats,
+ .is_format_support = is_format_support,
+ .s_power = set_power,
+ .update_config = update_config,
+ .update_buf_addr = update_buf_addr,
+ .s_stream = set_stream,
+};
+
+static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
+{
+ struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
+ struct regmap *regmap = sdev->regmap;
+ u32 status;
+
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
+
+ if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
+ (status & CSI_CH_INT_STA_HB_OF_PD)) {
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
+ CSI_EN_CSI_EN);
+ return IRQ_HANDLED;
+ }
+
+ if (status & CSI_CH_INT_STA_FD_PD) {
+ sun6i_video_frame_done(&sdev->csi.video);
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config sun6i_csi_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x1000,
+};
+
+static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+ void __iomem *io_base;
+ int ret;
+ int irq;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ io_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(io_base))
+ return PTR_ERR(io_base);
+
+ sdev->regmap = devm_regmap_init_mmio(&pdev->dev, io_base,
+ &sun6i_csi_regmap_config);
+ if (IS_ERR(sdev->regmap)) {
+ dev_err(&pdev->dev, "Failed to init register map\n");
+ return PTR_ERR(sdev->regmap);
+ }
+
+ sdev->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
+ if (IS_ERR(sdev->clk_ahb)) {
+ dev_err(&pdev->dev, "Unable to acquire ahb clock\n");
+ return PTR_ERR(sdev->clk_ahb);
+ }
+
+ sdev->clk_mod = devm_clk_get(&pdev->dev, "mod");
+ if (IS_ERR(sdev->clk_mod)) {
+ dev_err(&pdev->dev, "Unable to acquire csi clock\n");
+ return PTR_ERR(sdev->clk_mod);
+ }
+
+ sdev->clk_ram = devm_clk_get(&pdev->dev, "ram");
+ if (IS_ERR(sdev->clk_ram)) {
+ dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n");
+ return PTR_ERR(sdev->clk_ram);
+ }
+
+ sdev->rstc_ahb = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(sdev->rstc_ahb)) {
+ dev_err(&pdev->dev, "Cannot get reset controller\n");
+ return PTR_ERR(sdev->rstc_ahb);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "No csi IRQ specified\n");
+ ret = -ENXIO;
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, sun6i_csi_isr, 0, MODULE_NAME,
+ sdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot request csi IRQ\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int sun6i_csi_probe(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev;
+ int ret;
+
+ sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
+ if (!sdev)
+ return -ENOMEM;
+
+ sdev->dev = &pdev->dev;
+
+ ret = sun6i_csi_resource_request(sdev, pdev);
+ if (ret)
+ return ret;
+
+ sdev->csi.dev = &pdev->dev;
+ sdev->csi.ops = &csi_ops;
+ ret = sun6i_csi_init(&sdev->csi);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, sdev);
+
+ return 0;
+}
+
+static int sun6i_csi_remove(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev = platform_get_drvdata(pdev);
+
+ sun6i_csi_cleanup(&sdev->csi);
+
+ return 0;
+}
+
+static const struct of_device_id sun6i_csi_of_match[] = {
+ { .compatible = "allwinner,sun8i-v3s-csi", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sun6i_csi_of_match);
+
+static struct platform_driver sun6i_csi_platform_driver = {
+ .probe = sun6i_csi_probe,
+ .remove = sun6i_csi_remove,
+ .driver = {
+ .name = MODULE_NAME,
+ .of_match_table = of_match_ptr(sun6i_csi_of_match),
+ },
+};
+module_platform_driver(sun6i_csi_platform_driver);
+
+MODULE_DESCRIPTION("Allwinner V3s Camera Sensor Interface driver");
+MODULE_AUTHOR("Yong Deng <yong.deng@magewell.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
new file mode 100644
index 0000000..6c87b74
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_V3S_H__
+#define __SUN6I_CSI_V3S_H__
+
+#include <linux/kernel.h>
+
+#define CSI_EN_REG 0x0
+#define CSI_EN_VER_EN BIT(30)
+#define CSI_EN_CSI_EN BIT(0)
+
+#define CSI_IF_CFG_REG 0x4
+#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21)
+#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_FPS_DS_EN BIT(20)
+#define CSI_IF_CFG_FIELD_MASK BIT(19)
+#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_VREF_POL_MASK BIT(18)
+#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_MASK BIT(17)
+#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_MASK BIT(16)
+#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8)
+#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_MIPI_IF_MASK BIT(7)
+#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7)
+#define CSI_IF_CFG_MIPI_IF_MIPI (1 << 7)
+#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0)
+#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+
+#define CSI_CAP_REG 0x8
+#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2)
+#define CSI_CAP_CH0_CAP_MASK(count) ((count << 2) & CSI_CAP_CH0_CAP_MASK_MASK)
+#define CSI_CAP_CH0_VCAP_ON BIT(1)
+#define CSI_CAP_CH0_SCAP_ON BIT(0)
+
+#define CSI_SYNC_CNT_REG 0xc
+#define CSI_FIFO_THRS_REG 0x10
+#define CSI_BT656_HEAD_CFG_REG 0x14
+#define CSI_PTN_LEN_REG 0x30
+#define CSI_PTN_ADDR_REG 0x34
+#define CSI_VER_REG 0x3c
+
+#define CSI_CH_CFG_REG 0x44
+#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20)
+#define CSI_CH_CFG_INPUT_FMT(fmt) ((fmt << 20) & CSI_CH_CFG_INPUT_FMT_MASK)
+#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16)
+#define CSI_CH_CFG_OUTPUT_FMT(fmt) ((fmt << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK)
+#define CSI_CH_CFG_VFLIP_EN BIT(13)
+#define CSI_CH_CFG_HFLIP_EN BIT(12)
+#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10)
+#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8)
+#define CSI_CH_CFG_INPUT_SEQ(seq) ((seq << 8) & CSI_CH_CFG_INPUT_SEQ_MASK)
+
+#define CSI_CH_SCALE_REG 0x4c
+#define CSI_CH_SCALE_QUART_EN BIT(0)
+
+#define CSI_CH_F0_BUFA_REG 0x50
+
+#define CSI_CH_F1_BUFA_REG 0x58
+
+#define CSI_CH_F2_BUFA_REG 0x60
+
+#define CSI_CH_STA_REG 0x6c
+#define CSI_CH_STA_FIELD_STA_MASK BIT(2)
+#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_VCAP_STA BIT(1)
+#define CSI_CH_STA_SCAP_STA BIT(0)
+
+#define CSI_CH_INT_EN_REG 0x70
+#define CSI_CH_INT_EN_VS_INT_EN BIT(7)
+#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6)
+#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5)
+#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4)
+#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3)
+#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2)
+#define CSI_CH_INT_EN_FD_INT_EN BIT(1)
+#define CSI_CH_INT_EN_CD_INT_EN BIT(0)
+
+#define CSI_CH_INT_STA_REG 0x74
+#define CSI_CH_INT_STA_VS_PD BIT(7)
+#define CSI_CH_INT_STA_HB_OF_PD BIT(6)
+#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5)
+#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4)
+#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3)
+#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2)
+#define CSI_CH_INT_STA_FD_PD BIT(1)
+#define CSI_CH_INT_STA_CD_PD BIT(0)
+
+#define CSI_CH_FLD1_VSIZE_REG 0x74
+
+#define CSI_CH_HSIZE_REG 0x80
+#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_HSIZE_HOR_LEN(len) ((len << 16) & CSI_CH_HSIZE_HOR_LEN_MASK)
+#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0)
+#define CSI_CH_HSIZE_HOR_START(start) ((start << 0) & CSI_CH_HSIZE_HOR_START_MASK)
+
+#define CSI_CH_VSIZE_REG 0x84
+#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_VSIZE_VER_LEN(len) ((len << 16) & CSI_CH_VSIZE_VER_LEN_MASK)
+#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0)
+#define CSI_CH_VSIZE_VER_START(start) ((start << 0) & CSI_CH_VSIZE_VER_START_MASK)
+
+#define CSI_CH_BUF_LEN_REG 0x88
+#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16)
+#define CSI_CH_BUF_LEN_BUF_LEN_C(len) ((len << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) ((len << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK)
+
+#define CSI_CH_FLIP_SIZE_REG 0x8c
+#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_FLIP_SIZE_VER_LEN(len) ((len << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK)
+#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0)
+#define CSI_CH_FLIP_SIZE_VALID_LEN(len) ((len << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK)
+
+#define CSI_CH_FRM_CLK_CNT_REG 0x90
+#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94
+#define CSI_CH_FIFO_STAT_REG 0x98
+#define CSI_CH_PCLK_STAT_REG 0x9c
+
+/*
+ * csi input data format
+ */
+enum csi_input_fmt
+{
+ CSI_INPUT_FORMAT_RAW = 0,
+ CSI_INPUT_FORMAT_YUV422 = 3,
+ CSI_INPUT_FORMAT_YUV420 = 4,
+};
+
+/*
+ * csi output data format
+ */
+enum csi_output_fmt
+{
+ /* only when input format is RAW */
+ CSI_FIELD_RAW_8 = 0,
+ CSI_FIELD_RAW_10 = 1,
+ CSI_FIELD_RAW_12 = 2,
+ CSI_FIELD_RGB565 = 4,
+ CSI_FIELD_RGB888 = 5,
+ CSI_FIELD_PRGB888 = 6,
+ CSI_FRAME_RAW_8 = 8,
+ CSI_FRAME_RAW_10 = 9,
+ CSI_FRAME_RAW_12 = 10,
+ CSI_FRAME_RGB565 = 12,
+ CSI_FRAME_RGB888 = 13,
+ CSI_FRAME_PRGB888 = 14,
+
+ /* only when input format is YUV422/YUV420 */
+ CSI_FIELD_PLANAR_YUV422 = 0,
+ CSI_FIELD_PLANAR_YUV420 = 1,
+ CSI_FRAME_PLANAR_YUV420 = 2,
+ CSI_FRAME_PLANAR_YUV422 = 3,
+ CSI_FIELD_UV_CB_YUV422 = 4,
+ CSI_FIELD_UV_CB_YUV420 = 5,
+ CSI_FRAME_UV_CB_YUV420 = 6,
+ CSI_FRAME_UV_CB_YUV422 = 7,
+ CSI_FIELD_MB_YUV422 = 8,
+ CSI_FIELD_MB_YUV420 = 9,
+ CSI_FRAME_MB_YUV420 = 10,
+ CSI_FRAME_MB_YUV422 = 11,
+ CSI_FIELD_UV_CB_YUV422_10 = 12,
+ CSI_FIELD_UV_CB_YUV420_10 = 13,
+};
+
+/*
+ * csi YUV input data sequence
+ */
+enum csi_input_seq
+{
+ /* only when input format is YUV422 */
+ CSI_INPUT_SEQ_YUYV = 0,
+ CSI_INPUT_SEQ_YVYU,
+ CSI_INPUT_SEQ_UYVY,
+ CSI_INPUT_SEQ_VYUY,
+};
+
+#endif /* __SUN6I_CSI_V3S_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.c b/drivers/media/platform/sun6i-csi/sun6i_video.c
new file mode 100644
index 0000000..0c5dbd2
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_video.c
@@ -0,0 +1,663 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/of.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_video.h"
+
+struct sun6i_csi_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+
+ dma_addr_t dma_addr;
+};
+
+static struct sun6i_csi_format *
+find_format_by_fourcc(struct sun6i_video *video, unsigned int fourcc)
+{
+ unsigned int num_formats = video->num_formats;
+ struct sun6i_csi_format *fmt;
+ unsigned int i;
+
+ for (i = 0; i < num_formats; i++) {
+ fmt = &video->formats[i];
+ if (fmt->fourcc == fourcc)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+static struct v4l2_subdev *
+sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad)
+{
+ struct media_pad *remote;
+
+ remote = media_entity_remote_pad(&video->pad);
+
+ if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
+ return NULL;
+
+ if (pad)
+ *pad = remote->index;
+
+ return media_entity_to_v4l2_subdev(remote->entity);
+}
+
+static int sun6i_video_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned int size = video->fmt.fmt.pix.sizeimage;
+
+ if (*nplanes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static int sun6i_video_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long size = video->fmt.fmt.pix.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+
+ buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ vbuf->field = video->fmt.fmt.pix.field;
+
+ return 0;
+}
+
+static int sun6i_pipeline_set_stream(struct sun6i_video *video, bool enable)
+{
+ struct media_entity *entity;
+ struct media_pad *pad;
+ struct v4l2_subdev *subdev;
+ int ret;
+
+ entity = &video->vdev.entity;
+ while (1) {
+ pad = &entity->pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+
+ pad = media_entity_remote_pad(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+
+ entity = pad->entity;
+ subdev = media_entity_to_v4l2_subdev(entity);
+
+ ret = v4l2_subdev_call(subdev, video, s_stream, enable);
+ if (enable && ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ struct sun6i_csi_buffer *buf;
+ struct sun6i_csi_config config;
+ unsigned long flags;
+ int ret;
+
+ video->sequence = 0;
+
+ ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe);
+ if (ret < 0)
+ goto err_start_pipeline;
+
+ ret = sun6i_pipeline_set_stream(video, true);
+ if (ret < 0)
+ goto err_start_stream;
+
+ config.pixelformat = video->fmt.fmt.pix.pixelformat;
+ config.code = video->current_fmt->mbus_code;
+ config.field = video->fmt.fmt.pix.field;
+ config.width = video->fmt.fmt.pix.width;
+ config.height = video->fmt.fmt.pix.height;
+
+ ret = sun6i_csi_update_config(video->csi, &config);
+ if (ret < 0)
+ goto err_update_config;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ video->cur_frm = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ list_del(&video->cur_frm->list);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ ret = sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ if (ret < 0)
+ goto err_update_addr;
+
+ ret = sun6i_csi_set_stream(video->csi, true);
+ if (ret < 0)
+ goto err_csi_stream;
+
+ return 0;
+
+err_csi_stream:
+err_update_addr:
+err_update_config:
+ sun6i_pipeline_set_stream(video, false);
+err_start_stream:
+ media_pipeline_stop(&video->vdev.entity);
+err_start_pipeline:
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ return ret;
+}
+
+static void sun6i_video_stop_streaming(struct vb2_queue *vq)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned long flags;
+ struct sun6i_csi_buffer *buf;
+
+ sun6i_pipeline_set_stream(video, false);
+
+ sun6i_csi_set_stream(video->csi, false);
+
+ media_pipeline_stop(&video->vdev.entity);
+
+ /* Release all active buffers */
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ if (unlikely(video->cur_frm)) {
+ vb2_buffer_done(&video->cur_frm->vb.vb2_buf,
+ VB2_BUF_STATE_ERROR);
+ video->cur_frm = NULL;
+ }
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+static void sun6i_video_buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long flags;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ if (!video->cur_frm && list_empty(&video->dma_queue) &&
+ vb2_is_streaming(vb->vb2_queue)) {
+ video->cur_frm = buf;
+ sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ sun6i_csi_set_stream(video->csi, 1);
+ } else
+ list_add_tail(&buf->list, &video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+void sun6i_video_frame_done(struct sun6i_video *video)
+{
+ spin_lock(&video->dma_queue_lock);
+
+ if (video->cur_frm) {
+ struct vb2_v4l2_buffer *vbuf = &video->cur_frm->vb;
+ struct vb2_buffer *vb = &vbuf->vb2_buf;
+
+ vb->timestamp = ktime_get_ns();
+ vbuf->sequence = video->sequence++;
+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+ video->cur_frm = NULL;
+ }
+
+ if (!list_empty(&video->dma_queue)
+ && vb2_is_streaming(&video->vb2_vidq)) {
+ video->cur_frm = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ list_del(&video->cur_frm->list);
+ sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ } else
+ sun6i_csi_set_stream(video->csi, 0);
+
+ spin_unlock(&video->dma_queue_lock);
+}
+
+static struct vb2_ops sun6i_csi_vb2_ops = {
+ .queue_setup = sun6i_video_queue_setup,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .buf_prepare = sun6i_video_buffer_prepare,
+ .start_streaming = sun6i_video_start_streaming,
+ .stop_streaming = sun6i_video_stop_streaming,
+ .buf_queue = sun6i_video_buffer_queue,
+};
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ strlcpy(cap->driver, "sun6i-video", sizeof(cap->driver));
+ strlcpy(cap->card, video->vdev.name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ video->csi->dev->of_node->name);
+
+ return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ u32 index = f->index;
+
+ if (index >= video->num_formats)
+ return -EINVAL;
+
+ f->pixelformat = video->formats[index].fourcc;
+
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ *fmt = video->fmt;
+
+ return 0;
+}
+
+static int sun6i_video_try_fmt(struct sun6i_video *video, struct v4l2_format *f,
+ struct sun6i_csi_format **current_fmt)
+{
+ struct sun6i_csi_format *csi_fmt;
+ struct v4l2_pix_format *pixfmt = &f->fmt.pix;
+ struct v4l2_subdev_format format;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ csi_fmt = find_format_by_fourcc(video, pixfmt->pixelformat);
+ if (csi_fmt == NULL)
+ return -EINVAL;
+
+ format.pad = pad;
+ format.which = V4L2_SUBDEV_FORMAT_TRY;
+ v4l2_fill_mbus_format(&format.format, pixfmt, csi_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format);
+ if (ret)
+ return ret;
+
+ v4l2_fill_pix_format(pixfmt, &format.format);
+
+ pixfmt->bytesperline = (pixfmt->width * csi_fmt->bpp) >> 3;
+ pixfmt->sizeimage = (pixfmt->width * csi_fmt->bpp * pixfmt->height) / 8;
+
+ if (current_fmt)
+ *current_fmt = csi_fmt;
+
+ return 0;
+}
+
+static int sun6i_video_set_fmt(struct sun6i_video *video, struct v4l2_format *f)
+{
+ struct v4l2_subdev_format format;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ ret = sun6i_video_try_fmt(video, f, ¤t_fmt);
+ if (ret)
+ return ret;
+
+ format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ v4l2_fill_mbus_format(&format.format, &f->fmt.pix,
+ current_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format);
+ if (ret < 0)
+ return ret;
+
+ video->fmt = *f;
+ video->current_fmt = current_fmt;
+
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ if (vb2_is_streaming(&video->vb2_vidq))
+ return -EBUSY;
+
+ return sun6i_video_set_fmt(video, f);
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ return sun6i_video_try_fmt(video, f, NULL);
+}
+
+static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 file operations
+ */
+static int sun6i_video_open(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ struct v4l2_format format;
+ int ret;
+
+ if (mutex_lock_interruptible(&video->lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto unlock;
+
+ ret = v4l2_pipeline_pm_use(&video->vdev.entity, 1);
+ if (ret < 0)
+ goto fh_release;
+
+ if (!v4l2_fh_is_singular_file(file))
+ goto unlock;
+
+ ret = sun6i_csi_set_power(video->csi, true);
+ if (ret < 0)
+ goto fh_release;
+
+ /* setup default format */
+ if (video->num_formats > 0) {
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ format.fmt.pix.width = 1280;
+ format.fmt.pix.height = 720;
+ format.fmt.pix.pixelformat = video->formats[0].fourcc;
+ sun6i_video_set_fmt(video, &format);
+ }
+
+ mutex_unlock(&video->lock);
+ return 0;
+
+fh_release:
+ v4l2_fh_release(file);
+unlock:
+ mutex_unlock(&video->lock);
+ return ret;
+}
+
+static int sun6i_video_close(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ bool last_fh;
+
+ mutex_lock(&video->lock);
+
+ last_fh = v4l2_fh_is_singular_file(file);
+
+ _vb2_fop_release(file, NULL);
+
+ v4l2_pipeline_pm_use(&video->vdev.entity, 0);
+
+ if (last_fh)
+ sun6i_csi_set_power(video->csi, false);
+
+ mutex_unlock(&video->lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations sun6i_video_fops = {
+ .owner = THIS_MODULE,
+ .open = sun6i_video_open,
+ .release = sun6i_video_close,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+ .poll = vb2_fop_poll
+};
+
+/* -----------------------------------------------------------------------------
+ * Media Operations
+ */
+static int sun6i_video_formats_init(struct sun6i_video *video)
+{
+ struct v4l2_subdev_mbus_code_enum mbus_code = { 0 };
+ struct sun6i_csi *csi = video->csi;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ const u32 *pixformats;
+ int pixformat_count = 0;
+ u32 subdev_codes[32]; /* subdev format codes, 32 should be enough */
+ int codes_count = 0;
+ int num_fmts = 0;
+ int i, j;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ /* Get supported pixformats of CSI */
+ pixformat_count = sun6i_csi_get_supported_pixformats(csi, &pixformats);
+ if (pixformat_count <= 0)
+ return -ENXIO;
+
+ /* Get subdev formats codes */
+ mbus_code.pad = pad;
+ mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL,
+ &mbus_code)) {
+ subdev_codes[codes_count] = mbus_code.code;
+ codes_count++;
+ mbus_code.index++;
+ }
+
+ if (!codes_count)
+ return -ENXIO;
+
+ /* Get supported formats count */
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ mbus_code.code)) {
+ continue;
+ }
+ num_fmts++;
+ }
+ }
+
+ if (!num_fmts)
+ return -ENXIO;
+
+ video->num_formats = num_fmts;
+ video->formats = devm_kcalloc(video->csi->dev, num_fmts,
+ sizeof(struct sun6i_csi_format), GFP_KERNEL);
+ if (!video->formats) {
+ dev_err(video->csi->dev, "could not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ /* Get supported formats */
+ num_fmts = 0;
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ mbus_code.code)) {
+ continue;
+ }
+
+ video->formats[num_fmts].fourcc = pixformats[j];
+ video->formats[num_fmts].mbus_code =
+ mbus_code.code;
+ video->formats[num_fmts].bpp =
+ v4l2_pixformat_get_bpp(pixformats[j]);
+ num_fmts++;
+ }
+ }
+
+ return 0;
+}
+
+static int sun6i_video_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct video_device *vdev = media_entity_to_video_device(entity);
+ struct sun6i_video *video = video_get_drvdata(vdev);
+
+ if (WARN_ON(video == NULL))
+ return 0;
+
+ return sun6i_video_formats_init(video);
+}
+
+static const struct media_entity_operations sun6i_video_media_ops = {
+ .link_setup = sun6i_video_link_setup,
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name)
+{
+ struct video_device *vdev = &video->vdev;
+ struct vb2_queue *vidq = &video->vb2_vidq;
+ int ret;
+
+ video->csi = csi;
+
+ /* Initialize the media entity... */
+ video->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
+ vdev->entity.ops = &sun6i_video_media_ops;
+ ret = media_entity_pads_init(&vdev->entity, 1, &video->pad);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&video->lock);
+
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_lock_init(&video->dma_queue_lock);
+
+ video->cur_frm = NULL;
+ video->sequence = 0;
+ video->num_formats = 0;
+
+ /* Initialize videobuf2 queue */
+ vidq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ vidq->io_modes = VB2_MMAP | VB2_DMABUF;
+ vidq->drv_priv = video;
+ vidq->buf_struct_size = sizeof(struct sun6i_csi_buffer);
+ vidq->ops = &sun6i_csi_vb2_ops;
+ vidq->mem_ops = &vb2_dma_contig_memops;
+ vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ vidq->lock = &video->lock;
+ vidq->min_buffers_needed = 1;
+ vidq->dev = csi->dev;
+
+ ret = vb2_queue_init(vidq);
+ if (ret) {
+ v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret);
+ goto error;
+ }
+
+ /* Register video device */
+ strlcpy(vdev->name, name, sizeof(vdev->name));
+ vdev->release = video_device_release_empty;
+ vdev->fops = &sun6i_video_fops;
+ vdev->ioctl_ops = &sun6i_video_ioctl_ops;
+ vdev->vfl_type = VFL_TYPE_GRABBER;
+ vdev->vfl_dir = VFL_DIR_RX;
+ vdev->v4l2_dev = &csi->v4l2_dev;
+ vdev->queue = vidq;
+ vdev->lock = &video->lock;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+ video_set_drvdata(vdev, video);
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret < 0) {
+ v4l2_err(&csi->v4l2_dev,
+ "video_register_device failed: %d\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ sun6i_video_cleanup(video);
+ return ret;
+}
+
+void sun6i_video_cleanup(struct sun6i_video *video)
+{
+ if (video_is_registered(&video->vdev))
+ video_unregister_device(&video->vdev);
+
+ media_entity_cleanup(&video->vdev.entity);
+}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.h b/drivers/media/platform/sun6i-csi/sun6i_video.h
new file mode 100644
index 0000000..14eac6e
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_video.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_VIDEO_H__
+#define __SUN6I_VIDEO_H__
+
+#include <media/v4l2-dev.h>
+#include <media/videobuf2-core.h>
+
+/*
+ * struct sun6i_csi_format - CSI media bus format information
+ * @fourcc: Fourcc code for this format
+ * @mbus_code: V4L2 media bus format code.
+ * @bpp: Bytes per pixel (when stored in memory)
+ */
+struct sun6i_csi_format {
+ u32 fourcc;
+ u32 mbus_code;
+ u8 bpp;
+};
+
+struct sun6i_csi;
+
+struct sun6i_video {
+ struct video_device vdev;
+ struct media_pad pad;
+ struct sun6i_csi *csi;
+
+ struct mutex lock;
+
+ struct vb2_queue vb2_vidq;
+ spinlock_t dma_queue_lock;
+ struct list_head dma_queue;
+
+ struct sun6i_csi_buffer *cur_frm;
+ unsigned int sequence;
+
+ struct sun6i_csi_format *formats;
+ unsigned int num_formats;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_format fmt;
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name);
+void sun6i_video_cleanup(struct sun6i_video *video);
+
+void sun6i_video_frame_done(struct sun6i_video *video);
+
+#endif /* __SUN6I_VIDEO_H__ */
--
1.8.3.1
^ permalink raw reply related [flat|nested] 56+ messages in thread
[parent not found: <1501131697-1359-2-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>]
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
[not found] ` <1501131697-1359-2-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
@ 2017-07-27 12:16 ` Baruch Siach
2017-07-27 12:25 ` Maxime Ripard
2017-07-28 16:02 ` Maxime Ripard
` (3 subsequent siblings)
4 siblings, 1 reply; 56+ messages in thread
From: Baruch Siach @ 2017-07-27 12:16 UTC (permalink / raw)
To: Yong Deng
Cc: maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert
Hi Yong,
I managed to get the Frame Done interrupt with the previous version of this
driver on the A33 OLinuXino. No data yet (all zeros). I'm still working on it.
One comment below.
On Thu, Jul 27, 2017 at 01:01:35PM +0800, Yong Deng wrote:
> Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> and CSI1 is used for parallel interface. This is not documented in
> datasheet but by testing and guess.
>
> This patch implement a v4l2 framework driver for it.
>
> Currently, the driver only support the parallel interface. MIPI-CSI2,
> ISP's support are not included in this patch.
>
> Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
> ---
[...]
> +static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> + /* transform physical address to bus address */
> + dma_addr_t bus_addr = addr - 0x40000000;
What is the source of this magic number? Is it platform dependent? Are there
other devices doing DMA that need this adjustment?
baruch
> +
> + regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
> + (bus_addr + sdev->planar_offset[0]) >> 2);
> + if (sdev->planar_offset[1] != -1)
> + regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
> + (bus_addr + sdev->planar_offset[1]) >> 2);
> + if (sdev->planar_offset[2] != -1)
> + regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
> + (bus_addr + sdev->planar_offset[2]) >> 2);
> +
> + return 0;
> +}
--
http://baruch.siach.name/blog/ ~. .~ Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
- baruch-NswTu9S1W3P6gbPvEgmw2w@public.gmane.org - tel: +972.52.368.4656, http://www.tkos.co.il -
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-27 12:16 ` Baruch Siach
@ 2017-07-27 12:25 ` Maxime Ripard
[not found] ` <20170727122551.qca4atjeet6whfrs-ZC1Zs529Oq4@public.gmane.org>
0 siblings, 1 reply; 56+ messages in thread
From: Maxime Ripard @ 2017-07-27 12:25 UTC (permalink / raw)
To: Baruch Siach
Cc: Yong Deng, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, Greg Kroah-Hartman, David S. Miller, Hans Verkuil,
Arnd Bergmann, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Benoit Parrot, Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
[-- Attachment #1: Type: text/plain, Size: 1397 bytes --]
On Thu, Jul 27, 2017 at 03:16:44PM +0300, Baruch Siach wrote:
> Hi Yong,
>
> I managed to get the Frame Done interrupt with the previous version of this
> driver on the A33 OLinuXino. No data yet (all zeros). I'm still working on it.
>
> One comment below.
>
> On Thu, Jul 27, 2017 at 01:01:35PM +0800, Yong Deng wrote:
> > Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> > and CSI1 is used for parallel interface. This is not documented in
> > datasheet but by testing and guess.
> >
> > This patch implement a v4l2 framework driver for it.
> >
> > Currently, the driver only support the parallel interface. MIPI-CSI2,
> > ISP's support are not included in this patch.
> >
> > Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
> > ---
>
> [...]
>
> > +static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
> > +{
> > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> > + /* transform physical address to bus address */
> > + dma_addr_t bus_addr = addr - 0x40000000;
>
> What is the source of this magic number? Is it platform dependent? Are there
> other devices doing DMA that need this adjustment?
This is the RAM base address in most (but not all) Allwinner
SoCs. You'll want to use PHYS_OFFSET instead.
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
[not found] ` <1501131697-1359-2-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
2017-07-27 12:16 ` Baruch Siach
@ 2017-07-28 16:02 ` Maxime Ripard
2017-07-30 6:08 ` Baruch Siach
[not found] ` <20170728160233.xooevio4hoqkgfaq-ZC1Zs529Oq4@public.gmane.org>
2017-08-21 14:37 ` Hans Verkuil
` (2 subsequent siblings)
4 siblings, 2 replies; 56+ messages in thread
From: Maxime Ripard @ 2017-07-28 16:02 UTC (permalink / raw)
To: Yong Deng
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
[-- Attachment #1: Type: text/plain, Size: 21567 bytes --]
Hi,
Thanks for the second iteration!
On Thu, Jul 27, 2017 at 01:01:35PM +0800, Yong Deng wrote:
> Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> and CSI1 is used for parallel interface. This is not documented in
> datasheet but by testing and guess.
>
> This patch implement a v4l2 framework driver for it.
>
> Currently, the driver only support the parallel interface. MIPI-CSI2,
> ISP's support are not included in this patch.
>
> Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
There's a significant amount of checkpatch warnings (and quite
important checks) in your driver. You should fix everything checkpatch
--strict reports.
> ---
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/sun6i-csi/Kconfig | 9 +
> drivers/media/platform/sun6i-csi/Makefile | 3 +
> drivers/media/platform/sun6i-csi/sun6i_csi.c | 545 +++++++++++++++
> drivers/media/platform/sun6i-csi/sun6i_csi.h | 203 ++++++
> drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c | 827 +++++++++++++++++++++++
> drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h | 206 ++++++
> drivers/media/platform/sun6i-csi/sun6i_video.c | 663 ++++++++++++++++++
> drivers/media/platform/sun6i-csi/sun6i_video.h | 61 ++
> 10 files changed, 2520 insertions(+)
> create mode 100644 drivers/media/platform/sun6i-csi/Kconfig
> create mode 100644 drivers/media/platform/sun6i-csi/Makefile
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.c
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.h
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.c
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.h
>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 0c741d1..8371a87 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -143,6 +143,7 @@ source "drivers/media/platform/am437x/Kconfig"
> source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> +source "drivers/media/platform/sun6i-csi/Kconfig"
We're going to have several different drivers in v4l eventually, so I
guess it would make sense to move to a directory of our own.
> + dev_dbg(csi->dev, "creating links for entity %s\n", local->name);
> +
> + while (1) {
> + /* Get the next endpoint and parse its link. */
> + next = of_graph_get_next_endpoint(entity->node, ep);
> + if (next == NULL)
> + break;
> +
> + of_node_put(ep);
> + ep = next;
> +
> + dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
> +
> + ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
> + if (ret < 0) {
> + dev_err(csi->dev, "failed to parse link for %s\n",
> + ep->full_name);
> + continue;
> + }
> +
> + /* Skip sink ports, they will be processed from the other end of
> + * the link.
> + */
> + if (link.local_port >= local->num_pads) {
> + dev_err(csi->dev, "invalid port number %u on %s\n",
> + link.local_port,
> + to_of_node(link.local_node)->full_name);
> + v4l2_fwnode_put_link(&link);
> + ret = -EINVAL;
> + break;
> + }
> +
> + local_pad = &local->pads[link.local_port];
> +
> + if (local_pad->flags & MEDIA_PAD_FL_SINK) {
> + dev_dbg(csi->dev, "skipping sink port %s:%u\n",
> + to_of_node(link.local_node)->full_name,
> + link.local_port);
> + v4l2_fwnode_put_link(&link);
> + continue;
> + }
> +
> + /* Skip video node, they will be processed separately. */
> + if (link.remote_node == of_fwnode_handle(csi->dev->of_node)) {
> + dev_dbg(csi->dev, "skipping CSI port %s:%u\n",
> + to_of_node(link.local_node)->full_name,
> + link.local_port);
> + v4l2_fwnode_put_link(&link);
> + continue;
> + }
> +
> + /* Find the remote entity. */
> + ent = sun6i_graph_find_entity(csi,
> + to_of_node(link.remote_node));
> + if (ent == NULL) {
> + dev_err(csi->dev, "no entity found for %s\n",
> + to_of_node(link.remote_node)->full_name);
> + v4l2_fwnode_put_link(&link);
> + ret = -ENODEV;
> + break;
> + }
> +
> + remote = ent->entity;
> +
> + if (link.remote_port >= remote->num_pads) {
> + dev_err(csi->dev, "invalid port number %u on %s\n",
> + link.remote_port,
> + to_of_node(link.remote_node)->full_name);
> + v4l2_fwnode_put_link(&link);
> + ret = -EINVAL;
> + break;
> + }
> +
> + remote_pad = &remote->pads[link.remote_port];
> +
> + v4l2_fwnode_put_link(&link);
> +
> + /* Create the media link. */
> + dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
> + local->name, local_pad->index,
> + remote->name, remote_pad->index);
> +
> + ret = media_create_pad_link(local, local_pad->index,
> + remote, remote_pad->index,
> + link_flags);
> + if (ret < 0) {
> + dev_err(csi->dev,
> + "failed to create %s:%u -> %s:%u link\n",
> + local->name, local_pad->index,
> + remote->name, remote_pad->index);
> + break;
> + }
> + }
It's not really clear to me what you're trying to do here. Your
binding only states that it has a single port, with an endpoint to
your sensor.
Can't you just use of_graph_get_endpoint_by_regs(0, 0) ?
This will get you the endpoint you need, that you can parse later on
with v4l2_fwnode_endpoint_parse.
> +
> + of_node_put(ep);
> + return ret;
> +}
> +
> +static int sun6i_graph_build_video(struct sun6i_csi *csi)
> +{
> + u32 link_flags = MEDIA_LNK_FL_ENABLED;
> + struct device_node *node = csi->dev->of_node;
> + struct media_entity *source;
> + struct media_entity *sink;
> + struct media_pad *source_pad;
> + struct media_pad *sink_pad;
> + struct sun6i_graph_entity *ent;
> + struct v4l2_fwnode_link link;
> + struct device_node *ep = NULL;
> + struct device_node *next;
> + struct sun6i_video *video = &csi->video;
> + int ret = 0;
> +
> + dev_dbg(csi->dev, "creating link for video node\n");
> +
> + while (1) {
> + /* Get the next endpoint and parse its link. */
> + next = of_graph_get_next_endpoint(node, ep);
> + if (next == NULL)
> + break;
> +
> + of_node_put(ep);
> + ep = next;
> +
> + dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
> +
> + ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
> + if (ret < 0) {
> + dev_err(csi->dev, "failed to parse link for %s\n",
> + ep->full_name);
> + continue;
> + }
> +
> + /* Save the video port settings */
> + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep),
> + &csi->v4l2_ep);
> + if (ret) {
> + ret = -EINVAL;
> + dev_err(csi->dev, "Could not parse the endpoint\n");
> + v4l2_fwnode_put_link(&link);
> + break;
> + }
> +
> + dev_dbg(csi->dev, "creating link for video node %s\n",
> + video->vdev.name);
> +
> + /* Find the remote entity. */
> + ent = sun6i_graph_find_entity(csi,
> + to_of_node(link.remote_node));
> + if (ent == NULL) {
> + dev_err(csi->dev, "no entity found for %s\n",
> + to_of_node(link.remote_node)->full_name);
> + v4l2_fwnode_put_link(&link);
> + ret = -ENODEV;
> + break;
> + }
> +
> + if (link.remote_port >= ent->entity->num_pads) {
> + dev_err(csi->dev, "invalid port number %u on %s\n",
> + link.remote_port,
> + to_of_node(link.remote_node)->full_name);
> + v4l2_fwnode_put_link(&link);
> + ret = -EINVAL;
> + break;
> + }
> +
> + source = ent->entity;
> + source_pad = &source->pads[link.remote_port];
> + sink = &video->vdev.entity;
> + sink_pad = &video->pad;
> +
> + v4l2_fwnode_put_link(&link);
> +
> + /* Create the media link. */
> + dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
> + source->name, source_pad->index,
> + sink->name, sink_pad->index);
> +
> + ret = media_create_pad_link(source, source_pad->index,
> + sink, sink_pad->index,
> + link_flags);
> + if (ret < 0) {
> + dev_err(csi->dev,
> + "failed to create %s:%u -> %s:%u link\n",
> + source->name, source_pad->index,
> + sink->name, sink_pad->index);
> + break;
> + }
> +
> + /* Notify video node */
> + ret = media_entity_call(sink, link_setup, sink_pad, source_pad,
> + link_flags);
> + if (ret == -ENOIOCTLCMD)
> + ret = 0;
> +
> + /* found one */
> + break;
> + }
> +
> + of_node_put(ep);
> + return ret;
> +}
> +
> +static int sun6i_graph_notify_complete(struct v4l2_async_notifier *notifier)
> +{
> + struct sun6i_csi *csi =
> + container_of(notifier, struct sun6i_csi, notifier);
> + struct sun6i_graph_entity *entity;
> + int ret;
> +
> + dev_dbg(csi->dev, "notify complete, all subdevs registered\n");
> +
> + /* Create links for every entity. */
> + list_for_each_entry(entity, &csi->entities, list) {
> + ret = sun6i_graph_build_one(csi, entity);
> + if (ret < 0)
> + return ret;
> + }
> +
> + /* Create links for video node. */
> + ret = sun6i_graph_build_video(csi);
> + if (ret < 0)
> + return ret;
Can you elaborate a bit on the difference between a node parsed with
_graph_build_one and _graph_build_video? Can't you just store the
remote sensor when you build the notifier, and reuse it here?
> +struct sun6i_csi_ops {
> + int (*get_supported_pixformats)(struct sun6i_csi *csi,
> + const u32 **pixformats);
> + bool (*is_format_support)(struct sun6i_csi *csi, u32 pixformat,
> + u32 mbus_code);
> + int (*s_power)(struct sun6i_csi *csi, bool enable);
> + int (*update_config)(struct sun6i_csi *csi,
> + struct sun6i_csi_config *config);
> + int (*update_buf_addr)(struct sun6i_csi *csi, dma_addr_t addr);
> + int (*s_stream)(struct sun6i_csi *csi, bool enable);
> +};
Didn't we agreed on removing those in the first iteration? It's not
really clear at this point whether they will be needed at all. Make
something simple first, without those ops. When we'll support other
SoCs we'll have a better chance at seeing what and how we should deal
with potential quirks.
> +#ifdef DEBUG
> +static void sun6i_csi_dump_regs(struct sun6i_csi_dev *sdev)
> +{
> + struct regmap *regmap = sdev->regmap;
> + u32 val;
> +
> + regmap_read(regmap, CSI_EN_REG, &val);
> + printk("CSI_EN_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_IF_CFG_REG, &val);
> + printk("CSI_IF_CFG_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CAP_REG, &val);
> + printk("CSI_CAP_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_SYNC_CNT_REG, &val);
> + printk("CSI_SYNC_CNT_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_FIFO_THRS_REG, &val);
> + printk("CSI_FIFO_THRS_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_PTN_LEN_REG, &val);
> + printk("CSI_PTN_LEN_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_PTN_ADDR_REG, &val);
> + printk("CSI_PTN_ADDR_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_VER_REG, &val);
> + printk("CSI_VER_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_CFG_REG, &val);
> + printk("CSI_CH_CFG_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_SCALE_REG, &val);
> + printk("CSI_CH_SCALE_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_F0_BUFA_REG, &val);
> + printk("CSI_CH_F0_BUFA_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_F1_BUFA_REG, &val);
> + printk("CSI_CH_F1_BUFA_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_F2_BUFA_REG, &val);
> + printk("CSI_CH_F2_BUFA_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_STA_REG, &val);
> + printk("CSI_CH_STA_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_INT_EN_REG, &val);
> + printk("CSI_CH_INT_EN_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_INT_STA_REG, &val);
> + printk("CSI_CH_INT_STA_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_FLD1_VSIZE_REG, &val);
> + printk("CSI_CH_FLD1_VSIZE_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_HSIZE_REG, &val);
> + printk("CSI_CH_HSIZE_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_VSIZE_REG, &val);
> + printk("CSI_CH_VSIZE_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_BUF_LEN_REG, &val);
> + printk("CSI_CH_BUF_LEN_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_FLIP_SIZE_REG, &val);
> + printk("CSI_CH_FLIP_SIZE_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_FRM_CLK_CNT_REG, &val);
> + printk("CSI_CH_FRM_CLK_CNT_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_ACC_ITNL_CLK_CNT_REG, &val);
> + printk("CSI_CH_ACC_ITNL_CLK_CNT_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_FIFO_STAT_REG, &val);
> + printk("CSI_CH_FIFO_STAT_REG=0x%x\n", val);
> + regmap_read(regmap, CSI_CH_PCLK_STAT_REG, &val);
> + printk("CSI_CH_PCLK_STAT_REG=0x%x\n", val);
> +}
> +#endif
You can already dump a regmap through debugfs, that's redundant.
> +
> +static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev)
> +{
> + struct v4l2_fwnode_endpoint *endpoint = &sdev->csi.v4l2_ep;
> + unsigned char bus_width;
> + u32 flags;
> + u32 cfg;
> +
> + bus_width = endpoint->bus.parallel.bus_width;
> +
> + regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg);
> +
> + cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK |
> + CSI_IF_CFG_IF_DATA_WIDTH_MASK |
> + CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK |
> + CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK);
> +
> + switch (endpoint->bus_type) {
> + case V4L2_MBUS_CSI2:
> + cfg |= CSI_IF_CFG_MIPI_IF_MIPI;
> + break;
> + case V4L2_MBUS_PARALLEL:
> + cfg |= CSI_IF_CFG_MIPI_IF_CSI;
> +
> + flags = endpoint->bus.parallel.flags;
> +
> + cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT :
> + CSI_IF_CFG_CSI_IF_YUV422_INTLV;
> +
> + if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
> + cfg |= CSI_IF_CFG_FIELD_POSITIVE;
> +
> + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
> + cfg |= CSI_IF_CFG_VREF_POL_POSITIVE;
> + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
> + cfg |= CSI_IF_CFG_HREF_POL_POSITIVE;
> +
> + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
> + cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
> + break;
> + case V4L2_MBUS_BT656:
> + cfg |= CSI_IF_CFG_MIPI_IF_CSI;
> +
> + flags = endpoint->bus.parallel.flags;
> +
> + cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 :
> + CSI_IF_CFG_CSI_IF_BT656;
> +
> + if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
> + cfg |= CSI_IF_CFG_FIELD_POSITIVE;
> +
> + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
> + cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
> + break;
> + default:
> + BUG();
BUG() will generate a kernel panic, which seems a bit too extreme :)
How about a warning instead?
> +static int set_power(struct sun6i_csi *csi, bool enable)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> + struct regmap *regmap = sdev->regmap;
> + int ret;
> +
> + if (!enable) {
> + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
> +
> + clk_disable_unprepare(sdev->clk_ram);
> + clk_disable_unprepare(sdev->clk_mod);
> + clk_disable_unprepare(sdev->clk_ahb);
> + reset_control_assert(sdev->rstc_ahb);
> + return 0;
> + }
> +
> + ret = clk_prepare_enable(sdev->clk_ahb);
> + if (ret) {
> + dev_err(sdev->dev, "Enable ahb clk err %d\n", ret);
> + return ret;
> + }
The ahb clock doesn't need to be running all the time, at least since
the SoCs that have a reset line for every
controller. regmap_init_mmio_clk will take care of enabling it only
when you access the registers, which is what you need.
> + ret = clk_prepare_enable(sdev->clk_mod);
> + if (ret) {
> + dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(sdev->clk_ram);
> + if (ret) {
> + dev_err(sdev->dev, "Enable clk_dram_csi clk err %d\n", ret);
> + return ret;
> + }
> +
> + if (!IS_ERR_OR_NULL(sdev->rstc_ahb)) {
A missing reset line should not be allowed to probe, just like the
clocks aren't, so this condition should never fail (and therefore is
basically useless).
> + ret = reset_control_deassert(sdev->rstc_ahb);
> + if (ret) {
> + dev_err(sdev->dev, "reset err %d\n", ret);
> + return ret;
> + }
> + }
> +
> + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
> +
> + return 0;
> +}
> +
> +static int update_config(struct sun6i_csi *csi,
> + struct sun6i_csi_config *config)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> +
> + if (config == NULL)
> + return -EINVAL;
> +
> + memcpy(&csi->config, config, sizeof(csi->config));
> +
> + sun6i_csi_setup_bus(sdev);
> + sun6i_csi_set_format(sdev);
> + sun6i_csi_set_window(sdev);
> +
> + return 0;
> +}
> +
> +static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> + /* transform physical address to bus address */
> + dma_addr_t bus_addr = addr - 0x40000000;
Like Baruch noticed, you should use PHYS_OFFSET here. The A80 for
example has a different RAM base address.
> +
> + regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
> + (bus_addr + sdev->planar_offset[0]) >> 2);
> + if (sdev->planar_offset[1] != -1)
> + regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
> + (bus_addr + sdev->planar_offset[1]) >> 2);
> + if (sdev->planar_offset[2] != -1)
> + regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
> + (bus_addr + sdev->planar_offset[2]) >> 2);
> +
> + return 0;
> +}
> +
> +static int set_stream(struct sun6i_csi *csi, bool enable)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> + struct regmap *regmap = sdev->regmap;
> +
> + if (!enable) {
> + regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0);
> + regmap_write(regmap, CSI_CH_INT_EN_REG, 0);
> + return 0;
> + }
> +
> + regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF);
> + regmap_write(regmap, CSI_CH_INT_EN_REG,
> + CSI_CH_INT_EN_HB_OF_INT_EN |
> + CSI_CH_INT_EN_FIFO2_OF_INT_EN |
> + CSI_CH_INT_EN_FIFO1_OF_INT_EN |
> + CSI_CH_INT_EN_FIFO0_OF_INT_EN |
> + CSI_CH_INT_EN_FD_INT_EN |
> + CSI_CH_INT_EN_CD_INT_EN);
> +
> + regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON,
> + CSI_CAP_CH0_VCAP_ON);
> +
> + return 0;
> +}
> +
> +static struct sun6i_csi_ops csi_ops = {
> + .get_supported_pixformats = get_supported_pixformats,
> + .is_format_support = is_format_support,
> + .s_power = set_power,
> + .update_config = update_config,
> + .update_buf_addr = update_buf_addr,
> + .s_stream = set_stream,
> +};
> +
> +static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
> +{
> + struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
> + struct regmap *regmap = sdev->regmap;
> + u32 status;
> +
> + regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
> +
> + if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
> + (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
> + (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
> + (status & CSI_CH_INT_STA_HB_OF_PD)) {
> + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
> + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
> + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
> + CSI_EN_CSI_EN);
You need to enable / disable it at every frame? How do you deal with
double buffering? (or did you choose to ignore it for now?)
> + return IRQ_HANDLED;
> + }
> +
> + if (status & CSI_CH_INT_STA_FD_PD) {
> + sun6i_video_frame_done(&sdev->csi.video);
> + }
> +
> + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
Isn't it redundant with the one you did in the condition a bit above?
You should also check that your device indeed generated an
interrupt. In the occurence of a spourious interrupt, your code will
return IRQ_HANDLED, which is the wrong thing to do.
I think you should reverse your logic a bit here to make this
easier. You should just check that your status flags are indeed set,
and if not just bail out and return IRQ_NONE.
And if they are, go on with treating your interrupt.
> +
> + return IRQ_HANDLED;
> +}
> +
> +static const struct regmap_config sun6i_csi_regmap_config = {
> + .reg_bits = 32,
> + .reg_stride = 4,
> + .val_bits = 32,
> + .max_register = 0x1000,
> +};
> +
> +static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev,
> + struct platform_device *pdev)
> +{
> + struct resource *res;
> + void __iomem *io_base;
> + int ret;
> + int irq;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + io_base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(io_base))
> + return PTR_ERR(io_base);
> +
> + sdev->regmap = devm_regmap_init_mmio(&pdev->dev, io_base,
> + &sun6i_csi_regmap_config);
> + if (IS_ERR(sdev->regmap)) {
> + dev_err(&pdev->dev, "Failed to init register map\n");
> + return PTR_ERR(sdev->regmap);
> + }
> +
> + sdev->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
> + if (IS_ERR(sdev->clk_ahb)) {
> + dev_err(&pdev->dev, "Unable to acquire ahb clock\n");
> + return PTR_ERR(sdev->clk_ahb);
> + }
> + sdev->clk_mod = devm_clk_get(&pdev->dev, "mod");
> + if (IS_ERR(sdev->clk_mod)) {
> + dev_err(&pdev->dev, "Unable to acquire csi clock\n");
> + return PTR_ERR(sdev->clk_mod);
> + }
> +
> + sdev->clk_ram = devm_clk_get(&pdev->dev, "ram");
> + if (IS_ERR(sdev->clk_ram)) {
> + dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n");
> + return PTR_ERR(sdev->clk_ram);
> + }
> +
> + sdev->rstc_ahb = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
It is not optional, the reset line is always going to be there (at
least on the SoCs that have been out so far), and a missing reset line
in the DT must be reported as an error, since the device will not be
able to operate properly.
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-28 16:02 ` Maxime Ripard
@ 2017-07-30 6:08 ` Baruch Siach
2017-07-31 1:48 ` Yong
2017-08-21 20:21 ` Maxime Ripard
[not found] ` <20170728160233.xooevio4hoqkgfaq-ZC1Zs529Oq4@public.gmane.org>
1 sibling, 2 replies; 56+ messages in thread
From: Baruch Siach @ 2017-07-30 6:08 UTC (permalink / raw)
To: Maxime Ripard, Yong Deng
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik
Hi Maxime, Yong,
On Fri, Jul 28, 2017 at 06:02:33PM +0200, Maxime Ripard wrote:
> Hi,
>
> Thanks for the second iteration!
>
> On Thu, Jul 27, 2017 at 01:01:35PM +0800, Yong Deng wrote:
> > Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> > and CSI1 is used for parallel interface. This is not documented in
> > datasheet but by testing and guess.
> >
> > This patch implement a v4l2 framework driver for it.
> >
> > Currently, the driver only support the parallel interface. MIPI-CSI2,
> > ISP's support are not included in this patch.
> >
> > Signed-off-by: Yong Deng <yong.deng@magewell.com>
[...]
> > +#ifdef DEBUG
> > +static void sun6i_csi_dump_regs(struct sun6i_csi_dev *sdev)
> > +{
> > + struct regmap *regmap = sdev->regmap;
> > + u32 val;
> > +
> > + regmap_read(regmap, CSI_EN_REG, &val);
> > + printk("CSI_EN_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_IF_CFG_REG, &val);
> > + printk("CSI_IF_CFG_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CAP_REG, &val);
> > + printk("CSI_CAP_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_SYNC_CNT_REG, &val);
> > + printk("CSI_SYNC_CNT_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_FIFO_THRS_REG, &val);
> > + printk("CSI_FIFO_THRS_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_PTN_LEN_REG, &val);
> > + printk("CSI_PTN_LEN_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_PTN_ADDR_REG, &val);
> > + printk("CSI_PTN_ADDR_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_VER_REG, &val);
> > + printk("CSI_VER_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_CFG_REG, &val);
> > + printk("CSI_CH_CFG_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_SCALE_REG, &val);
> > + printk("CSI_CH_SCALE_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_F0_BUFA_REG, &val);
> > + printk("CSI_CH_F0_BUFA_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_F1_BUFA_REG, &val);
> > + printk("CSI_CH_F1_BUFA_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_F2_BUFA_REG, &val);
> > + printk("CSI_CH_F2_BUFA_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_STA_REG, &val);
> > + printk("CSI_CH_STA_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_INT_EN_REG, &val);
> > + printk("CSI_CH_INT_EN_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_INT_STA_REG, &val);
> > + printk("CSI_CH_INT_STA_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_FLD1_VSIZE_REG, &val);
> > + printk("CSI_CH_FLD1_VSIZE_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_HSIZE_REG, &val);
> > + printk("CSI_CH_HSIZE_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_VSIZE_REG, &val);
> > + printk("CSI_CH_VSIZE_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_BUF_LEN_REG, &val);
> > + printk("CSI_CH_BUF_LEN_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_FLIP_SIZE_REG, &val);
> > + printk("CSI_CH_FLIP_SIZE_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_FRM_CLK_CNT_REG, &val);
> > + printk("CSI_CH_FRM_CLK_CNT_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_ACC_ITNL_CLK_CNT_REG, &val);
> > + printk("CSI_CH_ACC_ITNL_CLK_CNT_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_FIFO_STAT_REG, &val);
> > + printk("CSI_CH_FIFO_STAT_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_PCLK_STAT_REG, &val);
> > + printk("CSI_CH_PCLK_STAT_REG=0x%x\n", val);
> > +}
> > +#endif
>
> You can already dump a regmap through debugfs, that's redundant.
The advantage of in-code registers dump routine is the ability to synchronize
the snapshot with the driver code execution. This is particularly important
for the capture statistics registers. I have found it useful here.
[...]
> > +static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
> > +{
> > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> > + /* transform physical address to bus address */
> > + dma_addr_t bus_addr = addr - 0x40000000;
>
> Like Baruch noticed, you should use PHYS_OFFSET here. The A80 for
> example has a different RAM base address.
>
> > +
> > + regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
> > + (bus_addr + sdev->planar_offset[0]) >> 2);
Why do you need the bit shift? Does that work for you?
The User Manuals of both the V3s and the and the A33 (AKA R16) state that the
BUFA field size in this register is 31:00, that is 32bit. I have found no
indication of this bit shift in the Olimex provided sunxi-vfe[1] driver. On
the A33 I have found that only after removing the bit-shift, (some sort of)
data started to appear in the buffer.
[1] https://github.com/hehopmajieh/a33_linux/tree/master/drivers/media/video/sunxi-vfe
[...]
> > +static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
> > +{
> > + struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
> > + struct regmap *regmap = sdev->regmap;
> > + u32 status;
> > +
> > + regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
> > +
> > + if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
> > + (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
> > + (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
> > + (status & CSI_CH_INT_STA_HB_OF_PD)) {
> > + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
> > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
> > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
> > + CSI_EN_CSI_EN);
>
> You need to enable / disable it at every frame? How do you deal with
> double buffering? (or did you choose to ignore it for now?)
These *_OF_PD status bits indicate an overflow error condition.
> > + return IRQ_HANDLED;
> > + }
> > +
> > + if (status & CSI_CH_INT_STA_FD_PD) {
> > + sun6i_video_frame_done(&sdev->csi.video);
> > + }
> > +
> > + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
>
> Isn't it redundant with the one you did in the condition a bit above?
>
> You should also check that your device indeed generated an
> interrupt. In the occurence of a spourious interrupt, your code will
> return IRQ_HANDLED, which is the wrong thing to do.
>
> I think you should reverse your logic a bit here to make this
> easier. You should just check that your status flags are indeed set,
> and if not just bail out and return IRQ_NONE.
>
> And if they are, go on with treating your interrupt.
>
> > +
> > + return IRQ_HANDLED;
> > +}
baruch
--
http://baruch.siach.name/blog/ ~. .~ Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
- baruch@tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-30 6:08 ` Baruch Siach
@ 2017-07-31 1:48 ` Yong
2017-07-31 5:13 ` Baruch Siach
2017-08-21 20:21 ` Maxime Ripard
1 sibling, 1 reply; 56+ messages in thread
From: Yong @ 2017-07-31 1:48 UTC (permalink / raw)
To: Baruch Siach
Cc: Maxime Ripard, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, Greg Kroah-Hartman, David S. Miller, Hans Verkuil,
Arnd Bergmann, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Benoit Parrot, Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWMMKQXVYFwzLw
Hi,
On Sun, 30 Jul 2017 09:08:01 +0300
Baruch Siach <baruch-NswTu9S1W3P6gbPvEgmw2w@public.gmane.org> wrote:
> Hi Maxime, Yong,
>
> On Fri, Jul 28, 2017 at 06:02:33PM +0200, Maxime Ripard wrote:
> > Hi,
> >
> > Thanks for the second iteration!
> >
> > On Thu, Jul 27, 2017 at 01:01:35PM +0800, Yong Deng wrote:
> > > Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> > > and CSI1 is used for parallel interface. This is not documented in
> > > datasheet but by testing and guess.
> > >
> > > This patch implement a v4l2 framework driver for it.
> > >
> > > Currently, the driver only support the parallel interface. MIPI-CSI2,
> > > ISP's support are not included in this patch.
> > >
> > > Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
>
> [...]
>
> > > +#ifdef DEBUG
> > > +static void sun6i_csi_dump_regs(struct sun6i_csi_dev *sdev)
> > > +{
> > > + struct regmap *regmap = sdev->regmap;
> > > + u32 val;
> > > +
> > > + regmap_read(regmap, CSI_EN_REG, &val);
> > > + printk("CSI_EN_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_IF_CFG_REG, &val);
> > > + printk("CSI_IF_CFG_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CAP_REG, &val);
> > > + printk("CSI_CAP_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_SYNC_CNT_REG, &val);
> > > + printk("CSI_SYNC_CNT_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_FIFO_THRS_REG, &val);
> > > + printk("CSI_FIFO_THRS_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_PTN_LEN_REG, &val);
> > > + printk("CSI_PTN_LEN_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_PTN_ADDR_REG, &val);
> > > + printk("CSI_PTN_ADDR_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_VER_REG, &val);
> > > + printk("CSI_VER_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_CFG_REG, &val);
> > > + printk("CSI_CH_CFG_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_SCALE_REG, &val);
> > > + printk("CSI_CH_SCALE_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_F0_BUFA_REG, &val);
> > > + printk("CSI_CH_F0_BUFA_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_F1_BUFA_REG, &val);
> > > + printk("CSI_CH_F1_BUFA_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_F2_BUFA_REG, &val);
> > > + printk("CSI_CH_F2_BUFA_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_STA_REG, &val);
> > > + printk("CSI_CH_STA_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_INT_EN_REG, &val);
> > > + printk("CSI_CH_INT_EN_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_INT_STA_REG, &val);
> > > + printk("CSI_CH_INT_STA_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_FLD1_VSIZE_REG, &val);
> > > + printk("CSI_CH_FLD1_VSIZE_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_HSIZE_REG, &val);
> > > + printk("CSI_CH_HSIZE_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_VSIZE_REG, &val);
> > > + printk("CSI_CH_VSIZE_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_BUF_LEN_REG, &val);
> > > + printk("CSI_CH_BUF_LEN_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_FLIP_SIZE_REG, &val);
> > > + printk("CSI_CH_FLIP_SIZE_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_FRM_CLK_CNT_REG, &val);
> > > + printk("CSI_CH_FRM_CLK_CNT_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_ACC_ITNL_CLK_CNT_REG, &val);
> > > + printk("CSI_CH_ACC_ITNL_CLK_CNT_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_FIFO_STAT_REG, &val);
> > > + printk("CSI_CH_FIFO_STAT_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_PCLK_STAT_REG, &val);
> > > + printk("CSI_CH_PCLK_STAT_REG=0x%x\n", val);
> > > +}
> > > +#endif
> >
> > You can already dump a regmap through debugfs, that's redundant.
>
> The advantage of in-code registers dump routine is the ability to synchronize
> the snapshot with the driver code execution. This is particularly important
> for the capture statistics registers. I have found it useful here.
Agree. It is not used to expose the registers value to user space. If you
think it is redundant, I will delete it.
>
> [...]
>
> > > +static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
> > > +{
> > > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> > > + /* transform physical address to bus address */
> > > + dma_addr_t bus_addr = addr - 0x40000000;
> >
> > Like Baruch noticed, you should use PHYS_OFFSET here. The A80 for
> > example has a different RAM base address.
> >
> > > +
> > > + regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
> > > + (bus_addr + sdev->planar_offset[0]) >> 2);
>
> Why do you need the bit shift? Does that work for you?
>
> The User Manuals of both the V3s and the and the A33 (AKA R16) state that the
> BUFA field size in this register is 31:00, that is 32bit. I have found no
> indication of this bit shift in the Olimex provided sunxi-vfe[1] driver. On
> the A33 I have found that only after removing the bit-shift, (some sort of)
> data started to appear in the buffer.
>
> [1] https://github.com/hehopmajieh/a33_linux/tree/master/drivers/media/video/sunxi-vfe
>
The Users Manuals do not document this bit shift. You should see line 10 to
32 in https://github.com/hehopmajieh/a33_linux/blob/master/drivers/media/video/sunxi-vfe/csi/csi_reg.c
> [...]
>
> > > +static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
> > > +{
> > > + struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
> > > + struct regmap *regmap = sdev->regmap;
> > > + u32 status;
> > > +
> > > + regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
> > > +
> > > + if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
> > > + (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
> > > + (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
> > > + (status & CSI_CH_INT_STA_HB_OF_PD)) {
> > > + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
> > > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
> > > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
> > > + CSI_EN_CSI_EN);
> >
> > You need to enable / disable it at every frame? How do you deal with
> > double buffering? (or did you choose to ignore it for now?)
>
> These *_OF_PD status bits indicate an overflow error condition.
Right.
>
> > > + return IRQ_HANDLED;
> > > + }
> > > +
> > > + if (status & CSI_CH_INT_STA_FD_PD) {
> > > + sun6i_video_frame_done(&sdev->csi.video);
> > > + }
> > > +
> > > + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
> >
> > Isn't it redundant with the one you did in the condition a bit above?
> >
> > You should also check that your device indeed generated an
> > interrupt. In the occurence of a spourious interrupt, your code will
> > return IRQ_HANDLED, which is the wrong thing to do.
> >
> > I think you should reverse your logic a bit here to make this
> > easier. You should just check that your status flags are indeed set,
> > and if not just bail out and return IRQ_NONE.
> >
> > And if they are, go on with treating your interrupt.
> >
> > > +
> > > + return IRQ_HANDLED;
> > > +}
>
> baruch
>
> --
> http://baruch.siach.name/blog/ ~. .~ Tk Open Systems
> =}------------------------------------------------ooO--U--Ooo------------{=
> - baruch-NswTu9S1W3P6gbPvEgmw2w@public.gmane.org - tel: +972.52.368.4656, http://www.tkos.co.il -
Thanks,
Yong
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-31 1:48 ` Yong
@ 2017-07-31 5:13 ` Baruch Siach
0 siblings, 0 replies; 56+ messages in thread
From: Baruch Siach @ 2017-07-31 5:13 UTC (permalink / raw)
To: Yong
Cc: Maxime Ripard, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, Greg Kroah-Hartman, David S. Miller, Hans Verkuil,
Arnd Bergmann, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Benoit Parrot, Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski
Hi Yong,
On Mon, Jul 31, 2017 at 09:48:06AM +0800, Yong wrote:
> On Sun, 30 Jul 2017 09:08:01 +0300
> Baruch Siach <baruch@tkos.co.il> wrote:
> > On Fri, Jul 28, 2017 at 06:02:33PM +0200, Maxime Ripard wrote:
> > > On Thu, Jul 27, 2017 at 01:01:35PM +0800, Yong Deng wrote:
> > > > + regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
> > > > + (bus_addr + sdev->planar_offset[0]) >> 2);
> >
> > Why do you need the bit shift? Does that work for you?
> >
> > The User Manuals of both the V3s and the and the A33 (AKA R16) state that the
> > BUFA field size in this register is 31:00, that is 32bit. I have found no
> > indication of this bit shift in the Olimex provided sunxi-vfe[1] driver. On
> > the A33 I have found that only after removing the bit-shift, (some sort of)
> > data started to appear in the buffer.
> >
> > [1] https://github.com/hehopmajieh/a33_linux/tree/master/drivers/media/video/sunxi-vfe
>
> The Users Manuals do not document this bit shift. You should see line 10 to
> 32 in https://github.com/hehopmajieh/a33_linux/blob/master/drivers/media/video/sunxi-vfe/csi/csi_reg.c
Thanks. So for my reference, the SoCs that don't need bit shift are A31, A23,
and A33. SoCs that need bit shift are A80, A83, H3, and V3s (AKA V30).
baruch
--
http://baruch.siach.name/blog/ ~. .~ Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
- baruch@tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-30 6:08 ` Baruch Siach
2017-07-31 1:48 ` Yong
@ 2017-08-21 20:21 ` Maxime Ripard
[not found] ` <20170821202145.kmxancepyq55v3o2-ZC1Zs529Oq4@public.gmane.org>
1 sibling, 1 reply; 56+ messages in thread
From: Maxime Ripard @ 2017-08-21 20:21 UTC (permalink / raw)
To: Baruch Siach
Cc: Yong Deng, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, Greg Kroah-Hartman, David S. Miller, Hans Verkuil,
Arnd Bergmann, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Benoit Parrot, Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
[-- Attachment #1: Type: text/plain, Size: 5086 bytes --]
Hi Baruch,
On Sun, Jul 30, 2017 at 09:08:01AM +0300, Baruch Siach wrote:
> On Fri, Jul 28, 2017 at 06:02:33PM +0200, Maxime Ripard wrote:
> > Hi,
> >
> > Thanks for the second iteration!
> >
> > On Thu, Jul 27, 2017 at 01:01:35PM +0800, Yong Deng wrote:
> > > Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> > > and CSI1 is used for parallel interface. This is not documented in
> > > datasheet but by testing and guess.
> > >
> > > This patch implement a v4l2 framework driver for it.
> > >
> > > Currently, the driver only support the parallel interface. MIPI-CSI2,
> > > ISP's support are not included in this patch.
> > >
> > > Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
>
> [...]
>
> > > +#ifdef DEBUG
> > > +static void sun6i_csi_dump_regs(struct sun6i_csi_dev *sdev)
> > > +{
> > > + struct regmap *regmap = sdev->regmap;
> > > + u32 val;
> > > +
> > > + regmap_read(regmap, CSI_EN_REG, &val);
> > > + printk("CSI_EN_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_IF_CFG_REG, &val);
> > > + printk("CSI_IF_CFG_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CAP_REG, &val);
> > > + printk("CSI_CAP_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_SYNC_CNT_REG, &val);
> > > + printk("CSI_SYNC_CNT_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_FIFO_THRS_REG, &val);
> > > + printk("CSI_FIFO_THRS_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_PTN_LEN_REG, &val);
> > > + printk("CSI_PTN_LEN_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_PTN_ADDR_REG, &val);
> > > + printk("CSI_PTN_ADDR_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_VER_REG, &val);
> > > + printk("CSI_VER_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_CFG_REG, &val);
> > > + printk("CSI_CH_CFG_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_SCALE_REG, &val);
> > > + printk("CSI_CH_SCALE_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_F0_BUFA_REG, &val);
> > > + printk("CSI_CH_F0_BUFA_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_F1_BUFA_REG, &val);
> > > + printk("CSI_CH_F1_BUFA_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_F2_BUFA_REG, &val);
> > > + printk("CSI_CH_F2_BUFA_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_STA_REG, &val);
> > > + printk("CSI_CH_STA_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_INT_EN_REG, &val);
> > > + printk("CSI_CH_INT_EN_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_INT_STA_REG, &val);
> > > + printk("CSI_CH_INT_STA_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_FLD1_VSIZE_REG, &val);
> > > + printk("CSI_CH_FLD1_VSIZE_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_HSIZE_REG, &val);
> > > + printk("CSI_CH_HSIZE_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_VSIZE_REG, &val);
> > > + printk("CSI_CH_VSIZE_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_BUF_LEN_REG, &val);
> > > + printk("CSI_CH_BUF_LEN_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_FLIP_SIZE_REG, &val);
> > > + printk("CSI_CH_FLIP_SIZE_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_FRM_CLK_CNT_REG, &val);
> > > + printk("CSI_CH_FRM_CLK_CNT_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_ACC_ITNL_CLK_CNT_REG, &val);
> > > + printk("CSI_CH_ACC_ITNL_CLK_CNT_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_FIFO_STAT_REG, &val);
> > > + printk("CSI_CH_FIFO_STAT_REG=0x%x\n", val);
> > > + regmap_read(regmap, CSI_CH_PCLK_STAT_REG, &val);
> > > + printk("CSI_CH_PCLK_STAT_REG=0x%x\n", val);
> > > +}
> > > +#endif
> >
> > You can already dump a regmap through debugfs, that's redundant.
>
> The advantage of in-code registers dump routine is the ability to
> synchronize the snapshot with the driver code execution. This is
> particularly important for the capture statistics registers. I have
> found it useful here.
You also have the option to use the traces to do that, but if that's
useful, this should be added to regmap itself. It can benefit others
too.
> > > +static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
> > > +{
> > > + struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
> > > + struct regmap *regmap = sdev->regmap;
> > > + u32 status;
> > > +
> > > + regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
> > > +
> > > + if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
> > > + (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
> > > + (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
> > > + (status & CSI_CH_INT_STA_HB_OF_PD)) {
> > > + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
> > > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
> > > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
> > > + CSI_EN_CSI_EN);
> >
> > You need to enable / disable it at every frame? How do you deal with
> > double buffering? (or did you choose to ignore it for now?)
>
> These *_OF_PD status bits indicate an overflow error condition.
Shouldn't we return an error code then? The names of these flags could
be better too.
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 56+ messages in thread
[parent not found: <20170728160233.xooevio4hoqkgfaq-ZC1Zs529Oq4@public.gmane.org>]
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
[not found] ` <20170728160233.xooevio4hoqkgfaq-ZC1Zs529Oq4@public.gmane.org>
@ 2017-07-31 3:16 ` Yong
2017-08-22 17:43 ` Maxime Ripard
0 siblings, 1 reply; 56+ messages in thread
From: Yong @ 2017-07-31 3:16 UTC (permalink / raw)
To: Maxime Ripard
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, lin
Hi,
On Fri, 28 Jul 2017 18:02:33 +0200
Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> Hi,
>
> Thanks for the second iteration!
>
> On Thu, Jul 27, 2017 at 01:01:35PM +0800, Yong Deng wrote:
> > Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> > and CSI1 is used for parallel interface. This is not documented in
> > datasheet but by testing and guess.
> >
> > This patch implement a v4l2 framework driver for it.
> >
> > Currently, the driver only support the parallel interface. MIPI-CSI2,
> > ISP's support are not included in this patch.
> >
> > Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
>
> There's a significant amount of checkpatch warnings (and quite
> important checks) in your driver. You should fix everything checkpatch
> --strict reports.
OK. I will check and fix.
>
> > ---
> > drivers/media/platform/Kconfig | 1 +
> > drivers/media/platform/Makefile | 2 +
> > drivers/media/platform/sun6i-csi/Kconfig | 9 +
> > drivers/media/platform/sun6i-csi/Makefile | 3 +
> > drivers/media/platform/sun6i-csi/sun6i_csi.c | 545 +++++++++++++++
> > drivers/media/platform/sun6i-csi/sun6i_csi.h | 203 ++++++
> > drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c | 827 +++++++++++++++++++++++
> > drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h | 206 ++++++
> > drivers/media/platform/sun6i-csi/sun6i_video.c | 663 ++++++++++++++++++
> > drivers/media/platform/sun6i-csi/sun6i_video.h | 61 ++
> > 10 files changed, 2520 insertions(+)
> > create mode 100644 drivers/media/platform/sun6i-csi/Kconfig
> > create mode 100644 drivers/media/platform/sun6i-csi/Makefile
> > create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.c
> > create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.h
> > create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
> > create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
> > create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.c
> > create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.h
> >
> > diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> > index 0c741d1..8371a87 100644
> > --- a/drivers/media/platform/Kconfig
> > +++ b/drivers/media/platform/Kconfig
> > @@ -143,6 +143,7 @@ source "drivers/media/platform/am437x/Kconfig"
> > source "drivers/media/platform/xilinx/Kconfig"
> > source "drivers/media/platform/rcar-vin/Kconfig"
> > source "drivers/media/platform/atmel/Kconfig"
> > +source "drivers/media/platform/sun6i-csi/Kconfig"
>
> We're going to have several different drivers in v4l eventually, so I
> guess it would make sense to move to a directory of our own.
Like this?
drivers/media/platform/sunxi/sun6i-csi
>
> > + dev_dbg(csi->dev, "creating links for entity %s\n", local->name);
> > +
> > + while (1) {
> > + /* Get the next endpoint and parse its link. */
> > + next = of_graph_get_next_endpoint(entity->node, ep);
> > + if (next == NULL)
> > + break;
> > +
> > + of_node_put(ep);
> > + ep = next;
> > +
> > + dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
> > +
> > + ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
> > + if (ret < 0) {
> > + dev_err(csi->dev, "failed to parse link for %s\n",
> > + ep->full_name);
> > + continue;
> > + }
> > +
> > + /* Skip sink ports, they will be processed from the other end of
> > + * the link.
> > + */
> > + if (link.local_port >= local->num_pads) {
> > + dev_err(csi->dev, "invalid port number %u on %s\n",
> > + link.local_port,
> > + to_of_node(link.local_node)->full_name);
> > + v4l2_fwnode_put_link(&link);
> > + ret = -EINVAL;
> > + break;
> > + }
> > +
> > + local_pad = &local->pads[link.local_port];
> > +
> > + if (local_pad->flags & MEDIA_PAD_FL_SINK) {
> > + dev_dbg(csi->dev, "skipping sink port %s:%u\n",
> > + to_of_node(link.local_node)->full_name,
> > + link.local_port);
> > + v4l2_fwnode_put_link(&link);
> > + continue;
> > + }
> > +
> > + /* Skip video node, they will be processed separately. */
> > + if (link.remote_node == of_fwnode_handle(csi->dev->of_node)) {
> > + dev_dbg(csi->dev, "skipping CSI port %s:%u\n",
> > + to_of_node(link.local_node)->full_name,
> > + link.local_port);
> > + v4l2_fwnode_put_link(&link);
> > + continue;
> > + }
> > +
> > + /* Find the remote entity. */
> > + ent = sun6i_graph_find_entity(csi,
> > + to_of_node(link.remote_node));
> > + if (ent == NULL) {
> > + dev_err(csi->dev, "no entity found for %s\n",
> > + to_of_node(link.remote_node)->full_name);
> > + v4l2_fwnode_put_link(&link);
> > + ret = -ENODEV;
> > + break;
> > + }
> > +
> > + remote = ent->entity;
> > +
> > + if (link.remote_port >= remote->num_pads) {
> > + dev_err(csi->dev, "invalid port number %u on %s\n",
> > + link.remote_port,
> > + to_of_node(link.remote_node)->full_name);
> > + v4l2_fwnode_put_link(&link);
> > + ret = -EINVAL;
> > + break;
> > + }
> > +
> > + remote_pad = &remote->pads[link.remote_port];
> > +
> > + v4l2_fwnode_put_link(&link);
> > +
> > + /* Create the media link. */
> > + dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
> > + local->name, local_pad->index,
> > + remote->name, remote_pad->index);
> > +
> > + ret = media_create_pad_link(local, local_pad->index,
> > + remote, remote_pad->index,
> > + link_flags);
> > + if (ret < 0) {
> > + dev_err(csi->dev,
> > + "failed to create %s:%u -> %s:%u link\n",
> > + local->name, local_pad->index,
> > + remote->name, remote_pad->index);
> > + break;
> > + }
> > + }
>
> It's not really clear to me what you're trying to do here. Your
> binding only states that it has a single port, with an endpoint to
> your sensor.
>
> Can't you just use of_graph_get_endpoint_by_regs(0, 0) ?
>
> This will get you the endpoint you need, that you can parse later on
> with v4l2_fwnode_endpoint_parse.
See below.
>
> > +
> > + of_node_put(ep);
> > + return ret;
> > +}
> > +
> > +static int sun6i_graph_build_video(struct sun6i_csi *csi)
> > +{
> > + u32 link_flags = MEDIA_LNK_FL_ENABLED;
> > + struct device_node *node = csi->dev->of_node;
> > + struct media_entity *source;
> > + struct media_entity *sink;
> > + struct media_pad *source_pad;
> > + struct media_pad *sink_pad;
> > + struct sun6i_graph_entity *ent;
> > + struct v4l2_fwnode_link link;
> > + struct device_node *ep = NULL;
> > + struct device_node *next;
> > + struct sun6i_video *video = &csi->video;
> > + int ret = 0;
> > +
> > + dev_dbg(csi->dev, "creating link for video node\n");
> > +
> > + while (1) {
> > + /* Get the next endpoint and parse its link. */
> > + next = of_graph_get_next_endpoint(node, ep);
> > + if (next == NULL)
> > + break;
> > +
> > + of_node_put(ep);
> > + ep = next;
> > +
> > + dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
> > +
> > + ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
> > + if (ret < 0) {
> > + dev_err(csi->dev, "failed to parse link for %s\n",
> > + ep->full_name);
> > + continue;
> > + }
> > +
> > + /* Save the video port settings */
> > + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep),
> > + &csi->v4l2_ep);
> > + if (ret) {
> > + ret = -EINVAL;
> > + dev_err(csi->dev, "Could not parse the endpoint\n");
> > + v4l2_fwnode_put_link(&link);
> > + break;
> > + }
> > +
> > + dev_dbg(csi->dev, "creating link for video node %s\n",
> > + video->vdev.name);
> > +
> > + /* Find the remote entity. */
> > + ent = sun6i_graph_find_entity(csi,
> > + to_of_node(link.remote_node));
> > + if (ent == NULL) {
> > + dev_err(csi->dev, "no entity found for %s\n",
> > + to_of_node(link.remote_node)->full_name);
> > + v4l2_fwnode_put_link(&link);
> > + ret = -ENODEV;
> > + break;
> > + }
> > +
> > + if (link.remote_port >= ent->entity->num_pads) {
> > + dev_err(csi->dev, "invalid port number %u on %s\n",
> > + link.remote_port,
> > + to_of_node(link.remote_node)->full_name);
> > + v4l2_fwnode_put_link(&link);
> > + ret = -EINVAL;
> > + break;
> > + }
> > +
> > + source = ent->entity;
> > + source_pad = &source->pads[link.remote_port];
> > + sink = &video->vdev.entity;
> > + sink_pad = &video->pad;
> > +
> > + v4l2_fwnode_put_link(&link);
> > +
> > + /* Create the media link. */
> > + dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
> > + source->name, source_pad->index,
> > + sink->name, sink_pad->index);
> > +
> > + ret = media_create_pad_link(source, source_pad->index,
> > + sink, sink_pad->index,
> > + link_flags);
> > + if (ret < 0) {
> > + dev_err(csi->dev,
> > + "failed to create %s:%u -> %s:%u link\n",
> > + source->name, source_pad->index,
> > + sink->name, sink_pad->index);
> > + break;
> > + }
> > +
> > + /* Notify video node */
> > + ret = media_entity_call(sink, link_setup, sink_pad, source_pad,
> > + link_flags);
> > + if (ret == -ENOIOCTLCMD)
> > + ret = 0;
> > +
> > + /* found one */
> > + break;
> > + }
> > +
> > + of_node_put(ep);
> > + return ret;
> > +}
> > +
> > +static int sun6i_graph_notify_complete(struct v4l2_async_notifier *notifier)
> > +{
> > + struct sun6i_csi *csi =
> > + container_of(notifier, struct sun6i_csi, notifier);
> > + struct sun6i_graph_entity *entity;
> > + int ret;
> > +
> > + dev_dbg(csi->dev, "notify complete, all subdevs registered\n");
> > +
> > + /* Create links for every entity. */
> > + list_for_each_entry(entity, &csi->entities, list) {
> > + ret = sun6i_graph_build_one(csi, entity);
> > + if (ret < 0)
> > + return ret;
> > + }
> > +
> > + /* Create links for video node. */
> > + ret = sun6i_graph_build_video(csi);
> > + if (ret < 0)
> > + return ret;
>
> Can you elaborate a bit on the difference between a node parsed with
> _graph_build_one and _graph_build_video? Can't you just store the
> remote sensor when you build the notifier, and reuse it here?
There maybe many usercases:
1. CSI->Sensor.
2. CSI->MIPI->Sensor.
3. CSI->FPGA->Sensor1
->Sensor2.
FPGA maybe some other video processor. FPGA, MIPI, Sensor can be
registered as v4l2 subdevs. We do not care about the driver code
of them. But they should be linked together here.
So, the _graph_build_one is used to link CSI port and subdevs.
_graph_build_video is used to link CSI port and video node.
This part is also difficult to understand for me. The one CSI module
have only one DMA channel(single port). But thay can be linked to
different physical port (Parallel or MIPI)(multiple ep) by IF select
register.
For now, the binding can have several ep, the driver will just pick
the first valid one.
>
> > +struct sun6i_csi_ops {
> > + int (*get_supported_pixformats)(struct sun6i_csi *csi,
> > + const u32 **pixformats);
> > + bool (*is_format_support)(struct sun6i_csi *csi, u32 pixformat,
> > + u32 mbus_code);
> > + int (*s_power)(struct sun6i_csi *csi, bool enable);
> > + int (*update_config)(struct sun6i_csi *csi,
> > + struct sun6i_csi_config *config);
> > + int (*update_buf_addr)(struct sun6i_csi *csi, dma_addr_t addr);
> > + int (*s_stream)(struct sun6i_csi *csi, bool enable);
> > +};
>
> Didn't we agreed on removing those in the first iteration? It's not
> really clear at this point whether they will be needed at all. Make
> something simple first, without those ops. When we'll support other
> SoCs we'll have a better chance at seeing what and how we should deal
> with potential quirks.
OK. But without ops, it is inappropriate to sun6i_csi and sun6i_csi.
Maybe I should merge the two files.
>
> > +#ifdef DEBUG
> > +static void sun6i_csi_dump_regs(struct sun6i_csi_dev *sdev)
> > +{
> > + struct regmap *regmap = sdev->regmap;
> > + u32 val;
> > +
> > + regmap_read(regmap, CSI_EN_REG, &val);
> > + printk("CSI_EN_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_IF_CFG_REG, &val);
> > + printk("CSI_IF_CFG_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CAP_REG, &val);
> > + printk("CSI_CAP_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_SYNC_CNT_REG, &val);
> > + printk("CSI_SYNC_CNT_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_FIFO_THRS_REG, &val);
> > + printk("CSI_FIFO_THRS_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_PTN_LEN_REG, &val);
> > + printk("CSI_PTN_LEN_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_PTN_ADDR_REG, &val);
> > + printk("CSI_PTN_ADDR_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_VER_REG, &val);
> > + printk("CSI_VER_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_CFG_REG, &val);
> > + printk("CSI_CH_CFG_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_SCALE_REG, &val);
> > + printk("CSI_CH_SCALE_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_F0_BUFA_REG, &val);
> > + printk("CSI_CH_F0_BUFA_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_F1_BUFA_REG, &val);
> > + printk("CSI_CH_F1_BUFA_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_F2_BUFA_REG, &val);
> > + printk("CSI_CH_F2_BUFA_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_STA_REG, &val);
> > + printk("CSI_CH_STA_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_INT_EN_REG, &val);
> > + printk("CSI_CH_INT_EN_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_INT_STA_REG, &val);
> > + printk("CSI_CH_INT_STA_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_FLD1_VSIZE_REG, &val);
> > + printk("CSI_CH_FLD1_VSIZE_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_HSIZE_REG, &val);
> > + printk("CSI_CH_HSIZE_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_VSIZE_REG, &val);
> > + printk("CSI_CH_VSIZE_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_BUF_LEN_REG, &val);
> > + printk("CSI_CH_BUF_LEN_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_FLIP_SIZE_REG, &val);
> > + printk("CSI_CH_FLIP_SIZE_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_FRM_CLK_CNT_REG, &val);
> > + printk("CSI_CH_FRM_CLK_CNT_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_ACC_ITNL_CLK_CNT_REG, &val);
> > + printk("CSI_CH_ACC_ITNL_CLK_CNT_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_FIFO_STAT_REG, &val);
> > + printk("CSI_CH_FIFO_STAT_REG=0x%x\n", val);
> > + regmap_read(regmap, CSI_CH_PCLK_STAT_REG, &val);
> > + printk("CSI_CH_PCLK_STAT_REG=0x%x\n", val);
> > +}
> > +#endif
>
> You can already dump a regmap through debugfs, that's redundant.
>
> > +
> > +static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev)
> > +{
> > + struct v4l2_fwnode_endpoint *endpoint = &sdev->csi.v4l2_ep;
> > + unsigned char bus_width;
> > + u32 flags;
> > + u32 cfg;
> > +
> > + bus_width = endpoint->bus.parallel.bus_width;
> > +
> > + regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg);
> > +
> > + cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK |
> > + CSI_IF_CFG_IF_DATA_WIDTH_MASK |
> > + CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK |
> > + CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK);
> > +
> > + switch (endpoint->bus_type) {
> > + case V4L2_MBUS_CSI2:
> > + cfg |= CSI_IF_CFG_MIPI_IF_MIPI;
> > + break;
> > + case V4L2_MBUS_PARALLEL:
> > + cfg |= CSI_IF_CFG_MIPI_IF_CSI;
> > +
> > + flags = endpoint->bus.parallel.flags;
> > +
> > + cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT :
> > + CSI_IF_CFG_CSI_IF_YUV422_INTLV;
> > +
> > + if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
> > + cfg |= CSI_IF_CFG_FIELD_POSITIVE;
> > +
> > + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
> > + cfg |= CSI_IF_CFG_VREF_POL_POSITIVE;
> > + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
> > + cfg |= CSI_IF_CFG_HREF_POL_POSITIVE;
> > +
> > + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
> > + cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
> > + break;
> > + case V4L2_MBUS_BT656:
> > + cfg |= CSI_IF_CFG_MIPI_IF_CSI;
> > +
> > + flags = endpoint->bus.parallel.flags;
> > +
> > + cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 :
> > + CSI_IF_CFG_CSI_IF_BT656;
> > +
> > + if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
> > + cfg |= CSI_IF_CFG_FIELD_POSITIVE;
> > +
> > + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
> > + cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
> > + break;
> > + default:
> > + BUG();
>
> BUG() will generate a kernel panic, which seems a bit too extreme :)
> How about a warning instead?
OK.
>
> > +static int set_power(struct sun6i_csi *csi, bool enable)
> > +{
> > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> > + struct regmap *regmap = sdev->regmap;
> > + int ret;
> > +
> > + if (!enable) {
> > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
> > +
> > + clk_disable_unprepare(sdev->clk_ram);
> > + clk_disable_unprepare(sdev->clk_mod);
> > + clk_disable_unprepare(sdev->clk_ahb);
> > + reset_control_assert(sdev->rstc_ahb);
> > + return 0;
> > + }
> > +
> > + ret = clk_prepare_enable(sdev->clk_ahb);
> > + if (ret) {
> > + dev_err(sdev->dev, "Enable ahb clk err %d\n", ret);
> > + return ret;
> > + }
>
> The ahb clock doesn't need to be running all the time, at least since
> the SoCs that have a reset line for every
> controller. regmap_init_mmio_clk will take care of enabling it only
> when you access the registers, which is what you need.
I will check this.
>
> > + ret = clk_prepare_enable(sdev->clk_mod);
> > + if (ret) {
> > + dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
> > + return ret;
> > + }
> > +
> > + ret = clk_prepare_enable(sdev->clk_ram);
> > + if (ret) {
> > + dev_err(sdev->dev, "Enable clk_dram_csi clk err %d\n", ret);
> > + return ret;
> > + }
> > +
> > + if (!IS_ERR_OR_NULL(sdev->rstc_ahb)) {
>
> A missing reset line should not be allowed to probe, just like the
> clocks aren't, so this condition should never fail (and therefore is
> basically useless).
OK.
>
> > + ret = reset_control_deassert(sdev->rstc_ahb);
> > + if (ret) {
> > + dev_err(sdev->dev, "reset err %d\n", ret);
> > + return ret;
> > + }
> > + }
> > +
> > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
> > +
> > + return 0;
> > +}
> > +
> > +static int update_config(struct sun6i_csi *csi,
> > + struct sun6i_csi_config *config)
> > +{
> > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> > +
> > + if (config == NULL)
> > + return -EINVAL;
> > +
> > + memcpy(&csi->config, config, sizeof(csi->config));
> > +
> > + sun6i_csi_setup_bus(sdev);
> > + sun6i_csi_set_format(sdev);
> > + sun6i_csi_set_window(sdev);
> > +
> > + return 0;
> > +}
> > +
> > +static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
> > +{
> > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> > + /* transform physical address to bus address */
> > + dma_addr_t bus_addr = addr - 0x40000000;
>
> Like Baruch noticed, you should use PHYS_OFFSET here. The A80 for
> example has a different RAM base address.
OK. I will try it again.
>
> > +
> > + regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
> > + (bus_addr + sdev->planar_offset[0]) >> 2);
> > + if (sdev->planar_offset[1] != -1)
> > + regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
> > + (bus_addr + sdev->planar_offset[1]) >> 2);
> > + if (sdev->planar_offset[2] != -1)
> > + regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
> > + (bus_addr + sdev->planar_offset[2]) >> 2);
> > +
> > + return 0;
> > +}
> > +
> > +static int set_stream(struct sun6i_csi *csi, bool enable)
> > +{
> > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> > + struct regmap *regmap = sdev->regmap;
> > +
> > + if (!enable) {
> > + regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0);
> > + regmap_write(regmap, CSI_CH_INT_EN_REG, 0);
> > + return 0;
> > + }
> > +
> > + regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF);
> > + regmap_write(regmap, CSI_CH_INT_EN_REG,
> > + CSI_CH_INT_EN_HB_OF_INT_EN |
> > + CSI_CH_INT_EN_FIFO2_OF_INT_EN |
> > + CSI_CH_INT_EN_FIFO1_OF_INT_EN |
> > + CSI_CH_INT_EN_FIFO0_OF_INT_EN |
> > + CSI_CH_INT_EN_FD_INT_EN |
> > + CSI_CH_INT_EN_CD_INT_EN);
> > +
> > + regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON,
> > + CSI_CAP_CH0_VCAP_ON);
> > +
> > + return 0;
> > +}
> > +
> > +static struct sun6i_csi_ops csi_ops = {
> > + .get_supported_pixformats = get_supported_pixformats,
> > + .is_format_support = is_format_support,
> > + .s_power = set_power,
> > + .update_config = update_config,
> > + .update_buf_addr = update_buf_addr,
> > + .s_stream = set_stream,
> > +};
> > +
> > +static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
> > +{
> > + struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
> > + struct regmap *regmap = sdev->regmap;
> > + u32 status;
> > +
> > + regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
> > +
> > + if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
> > + (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
> > + (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
> > + (status & CSI_CH_INT_STA_HB_OF_PD)) {
> > + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
> > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
> > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
> > + CSI_EN_CSI_EN);
>
> You need to enable / disable it at every frame? How do you deal with
> double buffering? (or did you choose to ignore it for now?)
Quoting Baruch's reply:
These *_OF_PD status bits indicate an overflow error condition.
>
> > + return IRQ_HANDLED;
> > + }
> > +
> > + if (status & CSI_CH_INT_STA_FD_PD) {
> > + sun6i_video_frame_done(&sdev->csi.video);
> > + }
> > +
> > + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
>
> Isn't it redundant with the one you did in the condition a bit above?
>
> You should also check that your device indeed generated an
> interrupt. In the occurence of a spourious interrupt, your code will
> return IRQ_HANDLED, which is the wrong thing to do.
>
> I think you should reverse your logic a bit here to make this
> easier. You should just check that your status flags are indeed set,
> and if not just bail out and return IRQ_NONE.
>
> And if they are, go on with treating your interrupt.
OK. I will add check for status flags.
BTW, how can a spurious interrupt occurred?
>
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static const struct regmap_config sun6i_csi_regmap_config = {
> > + .reg_bits = 32,
> > + .reg_stride = 4,
> > + .val_bits = 32,
> > + .max_register = 0x1000,
> > +};
> > +
> > +static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev,
> > + struct platform_device *pdev)
> > +{
> > + struct resource *res;
> > + void __iomem *io_base;
> > + int ret;
> > + int irq;
> > +
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + io_base = devm_ioremap_resource(&pdev->dev, res);
> > + if (IS_ERR(io_base))
> > + return PTR_ERR(io_base);
> > +
> > + sdev->regmap = devm_regmap_init_mmio(&pdev->dev, io_base,
> > + &sun6i_csi_regmap_config);
> > + if (IS_ERR(sdev->regmap)) {
> > + dev_err(&pdev->dev, "Failed to init register map\n");
> > + return PTR_ERR(sdev->regmap);
> > + }
> > +
> > + sdev->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
> > + if (IS_ERR(sdev->clk_ahb)) {
> > + dev_err(&pdev->dev, "Unable to acquire ahb clock\n");
> > + return PTR_ERR(sdev->clk_ahb);
> > + }
> > + sdev->clk_mod = devm_clk_get(&pdev->dev, "mod");
> > + if (IS_ERR(sdev->clk_mod)) {
> > + dev_err(&pdev->dev, "Unable to acquire csi clock\n");
> > + return PTR_ERR(sdev->clk_mod);
> > + }
> > +
> > + sdev->clk_ram = devm_clk_get(&pdev->dev, "ram");
> > + if (IS_ERR(sdev->clk_ram)) {
> > + dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n");
> > + return PTR_ERR(sdev->clk_ram);
> > + }
> > +
> > + sdev->rstc_ahb = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
>
> It is not optional, the reset line is always going to be there (at
> least on the SoCs that have been out so far), and a missing reset line
> in the DT must be reported as an error, since the device will not be
> able to operate properly.
The CSI0 and CSI1 share the same bus clk and reset line. I should use
the devm_reset_control_get_shared instead.
>
> Thanks!
> Maxime
>
> --
> Maxime Ripard, Free Electrons
> Embedded Linux and Kernel engineering
> http://free-electrons.com
Thanks,
Yong
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-31 3:16 ` Yong
@ 2017-08-22 17:43 ` Maxime Ripard
[not found] ` <20170822174339.6woauylgzkgqxygk-ZC1Zs529Oq4@public.gmane.org>
0 siblings, 1 reply; 56+ messages in thread
From: Maxime Ripard @ 2017-08-22 17:43 UTC (permalink / raw)
To: Yong
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik
[-- Attachment #1: Type: text/plain, Size: 5222 bytes --]
Hi Yong,
On Mon, Jul 31, 2017 at 11:16:40AM +0800, Yong wrote:
> > > @@ -143,6 +143,7 @@ source "drivers/media/platform/am437x/Kconfig"
> > > source "drivers/media/platform/xilinx/Kconfig"
> > > source "drivers/media/platform/rcar-vin/Kconfig"
> > > source "drivers/media/platform/atmel/Kconfig"
> > > +source "drivers/media/platform/sun6i-csi/Kconfig"
> >
> > We're going to have several different drivers in v4l eventually, so I
> > guess it would make sense to move to a directory of our own.
>
> Like this?
> drivers/media/platform/sunxi/sun6i-csi
Yep.
> > > +static int sun6i_graph_notify_complete(struct v4l2_async_notifier *notifier)
> > > +{
> > > + struct sun6i_csi *csi =
> > > + container_of(notifier, struct sun6i_csi, notifier);
> > > + struct sun6i_graph_entity *entity;
> > > + int ret;
> > > +
> > > + dev_dbg(csi->dev, "notify complete, all subdevs registered\n");
> > > +
> > > + /* Create links for every entity. */
> > > + list_for_each_entry(entity, &csi->entities, list) {
> > > + ret = sun6i_graph_build_one(csi, entity);
> > > + if (ret < 0)
> > > + return ret;
> > > + }
> > > +
> > > + /* Create links for video node. */
> > > + ret = sun6i_graph_build_video(csi);
> > > + if (ret < 0)
> > > + return ret;
> >
> > Can you elaborate a bit on the difference between a node parsed with
> > _graph_build_one and _graph_build_video? Can't you just store the
> > remote sensor when you build the notifier, and reuse it here?
>
> There maybe many usercases:
> 1. CSI->Sensor.
> 2. CSI->MIPI->Sensor.
> 3. CSI->FPGA->Sensor1
> ->Sensor2.
> FPGA maybe some other video processor. FPGA, MIPI, Sensor can be
> registered as v4l2 subdevs. We do not care about the driver code
> of them. But they should be linked together here.
>
> So, the _graph_build_one is used to link CSI port and subdevs.
> _graph_build_video is used to link CSI port and video node.
So the graph_build_one is for the two first cases, and the
_build_video for the latter case?
If so, you should take a look at the last iteration of the
subnotifiers rework by Nikas Söderlund (v4l2-async: add subnotifier
registration for subdevices).
It allows subdevs to register notifiers, and you don't have to build
the graph from the video device, each device and subdev can only care
about what's next in the pipeline, but not really what's behind it.
That would mean in your case that you can only deal with your single
CSI pad, and whatever subdev driver will use it care about its own.
> This part is also difficult to understand for me. The one CSI module
> have only one DMA channel(single port). But thay can be linked to
> different physical port (Parallel or MIPI)(multiple ep) by IF select
> register.
>
> For now, the binding can have several ep, the driver will just pick
> the first valid one.
Yeah, I'm not really sure how we could deal with that, but I guess we
can do it later on.
> >
> > > +struct sun6i_csi_ops {
> > > + int (*get_supported_pixformats)(struct sun6i_csi *csi,
> > > + const u32 **pixformats);
> > > + bool (*is_format_support)(struct sun6i_csi *csi, u32 pixformat,
> > > + u32 mbus_code);
> > > + int (*s_power)(struct sun6i_csi *csi, bool enable);
> > > + int (*update_config)(struct sun6i_csi *csi,
> > > + struct sun6i_csi_config *config);
> > > + int (*update_buf_addr)(struct sun6i_csi *csi, dma_addr_t addr);
> > > + int (*s_stream)(struct sun6i_csi *csi, bool enable);
> > > +};
> >
> > Didn't we agreed on removing those in the first iteration? It's not
> > really clear at this point whether they will be needed at all. Make
> > something simple first, without those ops. When we'll support other
> > SoCs we'll have a better chance at seeing what and how we should deal
> > with potential quirks.
>
> OK. But without ops, it is inappropriate to sun6i_csi and sun6i_csi.
> Maybe I should merge the two files.
I'm not sure what you meant here, but if you think that's appropriate,
please go ahead.
> > > + return IRQ_HANDLED;
> > > + }
> > > +
> > > + if (status & CSI_CH_INT_STA_FD_PD) {
> > > + sun6i_video_frame_done(&sdev->csi.video);
> > > + }
> > > +
> > > + regmap_write(regmap, CSI_CH_INT_STA_REG, status);
> >
> > Isn't it redundant with the one you did in the condition a bit above?
> >
> > You should also check that your device indeed generated an
> > interrupt. In the occurence of a spourious interrupt, your code will
> > return IRQ_HANDLED, which is the wrong thing to do.
> >
> > I think you should reverse your logic a bit here to make this
> > easier. You should just check that your status flags are indeed set,
> > and if not just bail out and return IRQ_NONE.
> >
> > And if they are, go on with treating your interrupt.
>
> OK. I will add check for status flags.
> BTW, how can a spurious interrupt occurred?
Usually it's either through some interference, or some poorly designed
controller. This is unlikely, but it's something you should take into
account.
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
[not found] ` <1501131697-1359-2-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
2017-07-27 12:16 ` Baruch Siach
2017-07-28 16:02 ` Maxime Ripard
@ 2017-08-21 14:37 ` Hans Verkuil
[not found] ` <5082b6d6-29a7-f101-8cba-13fce8983c89-qWit8jRvyhVmR6Xm/wNWPw@public.gmane.org>
2017-09-21 13:45 ` 'Ondřej Jirman' via linux-sunxi
2017-11-21 15:48 ` Maxime Ripard
4 siblings, 1 reply; 56+ messages in thread
From: Hans Verkuil @ 2017-08-21 14:37 UTC (permalink / raw)
To: Yong Deng, maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
Hi Yong,
First two high-level comments before I start the review:
1) Can you provide the v4l2-compliance output? I can't merge this unless I
see that it passes the compliance tests. Make sure you compile from the git
repo (https://git.linuxtv.org/v4l-utils.git/) so you are using the latest
version of the compliance test. Test with just 'v4l2-compliance' and also
with the -s option (to test streaming) and with the -f option (to test all
the various pixel formats).
2) I see you support interlaced formats. Did you actually test that? Interlaced
is tricky and you shouldn't add support for it unless you know it works and
that it passes the 'v4l2-compliance -s' test.
On 07/27/2017 07:01 AM, Yong Deng wrote:
> Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> and CSI1 is used for parallel interface. This is not documented in
> datasheet but by testing and guess.
>
> This patch implement a v4l2 framework driver for it.
>
> Currently, the driver only support the parallel interface. MIPI-CSI2,
> ISP's support are not included in this patch.
>
> Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
> ---
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/sun6i-csi/Kconfig | 9 +
> drivers/media/platform/sun6i-csi/Makefile | 3 +
> drivers/media/platform/sun6i-csi/sun6i_csi.c | 545 +++++++++++++++
> drivers/media/platform/sun6i-csi/sun6i_csi.h | 203 ++++++
> drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c | 827 +++++++++++++++++++++++
> drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h | 206 ++++++
> drivers/media/platform/sun6i-csi/sun6i_video.c | 663 ++++++++++++++++++
> drivers/media/platform/sun6i-csi/sun6i_video.h | 61 ++
> 10 files changed, 2520 insertions(+)
> create mode 100644 drivers/media/platform/sun6i-csi/Kconfig
> create mode 100644 drivers/media/platform/sun6i-csi/Makefile
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.c
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.h
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.c
> create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.h
>
<snip>
> diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.c b/drivers/media/platform/sun6i-csi/sun6i_video.c
> new file mode 100644
> index 0000000..0c5dbd2
> --- /dev/null
> +++ b/drivers/media/platform/sun6i-csi/sun6i_video.c
> @@ -0,0 +1,663 @@
> +/*
> + * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
> + * All rights reserved.
> + * Author: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/of.h>
> +
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#include "sun6i_csi.h"
> +#include "sun6i_video.h"
> +
> +struct sun6i_csi_buffer {
> + struct vb2_v4l2_buffer vb;
> + struct list_head list;
> +
> + dma_addr_t dma_addr;
> +};
> +
> +static struct sun6i_csi_format *
> +find_format_by_fourcc(struct sun6i_video *video, unsigned int fourcc)
> +{
> + unsigned int num_formats = video->num_formats;
> + struct sun6i_csi_format *fmt;
> + unsigned int i;
> +
> + for (i = 0; i < num_formats; i++) {
> + fmt = &video->formats[i];
> + if (fmt->fourcc == fourcc)
> + return fmt;
> + }
> +
> + return NULL;
> +}
> +
> +static struct v4l2_subdev *
> +sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad)
> +{
> + struct media_pad *remote;
> +
> + remote = media_entity_remote_pad(&video->pad);
> +
> + if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
> + return NULL;
> +
> + if (pad)
> + *pad = remote->index;
> +
> + return media_entity_to_v4l2_subdev(remote->entity);
> +}
> +
> +static int sun6i_video_queue_setup(struct vb2_queue *vq,
> + unsigned int *nbuffers, unsigned int *nplanes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct sun6i_video *video = vb2_get_drv_priv(vq);
> + unsigned int size = video->fmt.fmt.pix.sizeimage;
> +
> + if (*nplanes)
> + return sizes[0] < size ? -EINVAL : 0;
> +
> + *nplanes = 1;
> + sizes[0] = size;
> +
> + return 0;
> +}
> +
> +static int sun6i_video_buffer_prepare(struct vb2_buffer *vb)
> +{
> + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> + struct sun6i_csi_buffer *buf =
> + container_of(vbuf, struct sun6i_csi_buffer, vb);
> + struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
> + unsigned long size = video->fmt.fmt.pix.sizeimage;
> +
> + if (vb2_plane_size(vb, 0) < size) {
> + v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n",
> + vb2_plane_size(vb, 0), size);
> + return -EINVAL;
> + }
> +
> + vb2_set_plane_payload(vb, 0, size);
> +
> + buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
> +
> + vbuf->field = video->fmt.fmt.pix.field;
> +
> + return 0;
> +}
> +
> +static int sun6i_pipeline_set_stream(struct sun6i_video *video, bool enable)
> +{
> + struct media_entity *entity;
> + struct media_pad *pad;
> + struct v4l2_subdev *subdev;
> + int ret;
> +
> + entity = &video->vdev.entity;
> + while (1) {
> + pad = &entity->pads[0];
> + if (!(pad->flags & MEDIA_PAD_FL_SINK))
> + break;
> +
> + pad = media_entity_remote_pad(pad);
> + if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
> + break;
> +
> + entity = pad->entity;
> + subdev = media_entity_to_v4l2_subdev(entity);
> +
> + ret = v4l2_subdev_call(subdev, video, s_stream, enable);
> + if (enable && ret < 0 && ret != -ENOIOCTLCMD)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> + struct sun6i_video *video = vb2_get_drv_priv(vq);
> + struct sun6i_csi_buffer *buf;
> + struct sun6i_csi_config config;
> + unsigned long flags;
> + int ret;
> +
> + video->sequence = 0;
> +
> + ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe);
> + if (ret < 0)
> + goto err_start_pipeline;
> +
> + ret = sun6i_pipeline_set_stream(video, true);
> + if (ret < 0)
> + goto err_start_stream;
> +
> + config.pixelformat = video->fmt.fmt.pix.pixelformat;
> + config.code = video->current_fmt->mbus_code;
> + config.field = video->fmt.fmt.pix.field;
> + config.width = video->fmt.fmt.pix.width;
> + config.height = video->fmt.fmt.pix.height;
> +
> + ret = sun6i_csi_update_config(video->csi, &config);
> + if (ret < 0)
> + goto err_update_config;
> +
> + spin_lock_irqsave(&video->dma_queue_lock, flags);
> + video->cur_frm = list_first_entry(&video->dma_queue,
> + struct sun6i_csi_buffer, list);
> + list_del(&video->cur_frm->list);
> + spin_unlock_irqrestore(&video->dma_queue_lock, flags);
> +
> + ret = sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
> + if (ret < 0)
> + goto err_update_addr;
> +
> + ret = sun6i_csi_set_stream(video->csi, true);
> + if (ret < 0)
> + goto err_csi_stream;
> +
> + return 0;
> +
> +err_csi_stream:
> +err_update_addr:
> +err_update_config:
> + sun6i_pipeline_set_stream(video, false);
> +err_start_stream:
> + media_pipeline_stop(&video->vdev.entity);
> +err_start_pipeline:
> + spin_lock_irqsave(&video->dma_queue_lock, flags);
> + list_for_each_entry(buf, &video->dma_queue, list)
> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
> + INIT_LIST_HEAD(&video->dma_queue);
> + spin_unlock_irqrestore(&video->dma_queue_lock, flags);
> +
> + return ret;
> +}
> +
> +static void sun6i_video_stop_streaming(struct vb2_queue *vq)
> +{
> + struct sun6i_video *video = vb2_get_drv_priv(vq);
> + unsigned long flags;
> + struct sun6i_csi_buffer *buf;
> +
> + sun6i_pipeline_set_stream(video, false);
> +
> + sun6i_csi_set_stream(video->csi, false);
> +
> + media_pipeline_stop(&video->vdev.entity);
> +
> + /* Release all active buffers */
> + spin_lock_irqsave(&video->dma_queue_lock, flags);
> + if (unlikely(video->cur_frm)) {
> + vb2_buffer_done(&video->cur_frm->vb.vb2_buf,
> + VB2_BUF_STATE_ERROR);
> + video->cur_frm = NULL;
> + }
> + list_for_each_entry(buf, &video->dma_queue, list)
> + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> + INIT_LIST_HEAD(&video->dma_queue);
> + spin_unlock_irqrestore(&video->dma_queue_lock, flags);
> +}
> +
> +static void sun6i_video_buffer_queue(struct vb2_buffer *vb)
> +{
> + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> + struct sun6i_csi_buffer *buf =
> + container_of(vbuf, struct sun6i_csi_buffer, vb);
> + struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&video->dma_queue_lock, flags);
> + if (!video->cur_frm && list_empty(&video->dma_queue) &&
> + vb2_is_streaming(vb->vb2_queue)) {
> + video->cur_frm = buf;
> + sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
> + sun6i_csi_set_stream(video->csi, 1);
> + } else
> + list_add_tail(&buf->list, &video->dma_queue);
> + spin_unlock_irqrestore(&video->dma_queue_lock, flags);
> +}
> +
> +void sun6i_video_frame_done(struct sun6i_video *video)
> +{
> + spin_lock(&video->dma_queue_lock);
> +
> + if (video->cur_frm) {
> + struct vb2_v4l2_buffer *vbuf = &video->cur_frm->vb;
> + struct vb2_buffer *vb = &vbuf->vb2_buf;
> +
> + vb->timestamp = ktime_get_ns();
> + vbuf->sequence = video->sequence++;
> + vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
> + video->cur_frm = NULL;
> + }
> +
> + if (!list_empty(&video->dma_queue)
> + && vb2_is_streaming(&video->vb2_vidq)) {
> + video->cur_frm = list_first_entry(&video->dma_queue,
> + struct sun6i_csi_buffer, list);
> + list_del(&video->cur_frm->list);
> + sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
> + } else
> + sun6i_csi_set_stream(video->csi, 0);
> +
> + spin_unlock(&video->dma_queue_lock);
> +}
> +
> +static struct vb2_ops sun6i_csi_vb2_ops = {
> + .queue_setup = sun6i_video_queue_setup,
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> + .buf_prepare = sun6i_video_buffer_prepare,
> + .start_streaming = sun6i_video_start_streaming,
> + .stop_streaming = sun6i_video_stop_streaming,
> + .buf_queue = sun6i_video_buffer_queue,
> +};
> +
> +static int vidioc_querycap(struct file *file, void *priv,
> + struct v4l2_capability *cap)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> +
> + strlcpy(cap->driver, "sun6i-video", sizeof(cap->driver));
> + strlcpy(cap->card, video->vdev.name, sizeof(cap->card));
> + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
> + video->csi->dev->of_node->name);
> +
> + return 0;
> +}
> +
> +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_fmtdesc *f)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> + u32 index = f->index;
> +
> + if (index >= video->num_formats)
> + return -EINVAL;
> +
> + f->pixelformat = video->formats[index].fourcc;
> +
> + return 0;
> +}
> +
> +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *fmt)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> +
> + *fmt = video->fmt;
> +
> + return 0;
> +}
> +
> +static int sun6i_video_try_fmt(struct sun6i_video *video, struct v4l2_format *f,
> + struct sun6i_csi_format **current_fmt)
> +{
> + struct sun6i_csi_format *csi_fmt;
> + struct v4l2_pix_format *pixfmt = &f->fmt.pix;
> + struct v4l2_subdev_format format;
> + struct v4l2_subdev *subdev;
> + u32 pad;
> + int ret;
> +
> + subdev = sun6i_video_remote_subdev(video, &pad);
> + if (subdev == NULL)
> + return -ENXIO;
> +
> + csi_fmt = find_format_by_fourcc(video, pixfmt->pixelformat);
> + if (csi_fmt == NULL)
> + return -EINVAL;
> +
> + format.pad = pad;
> + format.which = V4L2_SUBDEV_FORMAT_TRY;
> + v4l2_fill_mbus_format(&format.format, pixfmt, csi_fmt->mbus_code);
> + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format);
> + if (ret)
> + return ret;
> +
> + v4l2_fill_pix_format(pixfmt, &format.format);
> +
> + pixfmt->bytesperline = (pixfmt->width * csi_fmt->bpp) >> 3;
> + pixfmt->sizeimage = (pixfmt->width * csi_fmt->bpp * pixfmt->height) / 8;
> +
> + if (current_fmt)
> + *current_fmt = csi_fmt;
> +
> + return 0;
> +}
> +
> +static int sun6i_video_set_fmt(struct sun6i_video *video, struct v4l2_format *f)
> +{
> + struct v4l2_subdev_format format;
> + struct sun6i_csi_format *current_fmt;
> + struct v4l2_subdev *subdev;
> + u32 pad;
> + int ret;
> +
> + subdev = sun6i_video_remote_subdev(video, &pad);
> + if (subdev == NULL)
> + return -ENXIO;
> +
> + ret = sun6i_video_try_fmt(video, f, ¤t_fmt);
> + if (ret)
> + return ret;
> +
> + format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> + v4l2_fill_mbus_format(&format.format, &f->fmt.pix,
> + current_fmt->mbus_code);
> + ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format);
> + if (ret < 0)
> + return ret;
> +
> + video->fmt = *f;
> + video->current_fmt = current_fmt;
> +
> + return 0;
> +}
> +
> +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> +
> + if (vb2_is_streaming(&video->vb2_vidq))
This should be vb2_is_busy(). Once buffers are allocated you are no longer allowed
to change the format, regardless of whether you are streaming or not.
> + return -EBUSY;
> +
> + return sun6i_video_set_fmt(video, f);
> +}
> +
> +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> +
> + return sun6i_video_try_fmt(video, f, NULL);
> +}
> +
> +static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = {
> + .vidioc_querycap = vidioc_querycap,
> + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
> + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
> + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
> + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
> +
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 file operations
> + */
> +static int sun6i_video_open(struct file *file)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> + struct v4l2_format format;
> + int ret;
> +
> + if (mutex_lock_interruptible(&video->lock))
> + return -ERESTARTSYS;
> +
> + ret = v4l2_fh_open(file);
> + if (ret < 0)
> + goto unlock;
> +
> + ret = v4l2_pipeline_pm_use(&video->vdev.entity, 1);
> + if (ret < 0)
> + goto fh_release;
> +
> + if (!v4l2_fh_is_singular_file(file))
> + goto unlock;
> +
> + ret = sun6i_csi_set_power(video->csi, true);
> + if (ret < 0)
> + goto fh_release;
> +
> + /* setup default format */
> + if (video->num_formats > 0) {
> + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> + format.fmt.pix.width = 1280;
> + format.fmt.pix.height = 720;
> + format.fmt.pix.pixelformat = video->formats[0].fourcc;
> + sun6i_video_set_fmt(video, &format);
> + }
No, this should happen when the driver is initialized. The format is
persistent over open()/close() calls so should not be re-initialized
here.
> +
> + mutex_unlock(&video->lock);
> + return 0;
> +
> +fh_release:
> + v4l2_fh_release(file);
> +unlock:
> + mutex_unlock(&video->lock);
> + return ret;
> +}
> +
> +static int sun6i_video_close(struct file *file)
> +{
> + struct sun6i_video *video = video_drvdata(file);
> + bool last_fh;
> +
> + mutex_lock(&video->lock);
> +
> + last_fh = v4l2_fh_is_singular_file(file);
> +
> + _vb2_fop_release(file, NULL);
> +
> + v4l2_pipeline_pm_use(&video->vdev.entity, 0);
> +
> + if (last_fh)
> + sun6i_csi_set_power(video->csi, false);
> +
> + mutex_unlock(&video->lock);
> +
> + return 0;
> +}
> +
> +static const struct v4l2_file_operations sun6i_video_fops = {
> + .owner = THIS_MODULE,
> + .open = sun6i_video_open,
> + .release = sun6i_video_close,
> + .unlocked_ioctl = video_ioctl2,
> + .mmap = vb2_fop_mmap,
> + .poll = vb2_fop_poll
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Media Operations
> + */
> +static int sun6i_video_formats_init(struct sun6i_video *video)
> +{
> + struct v4l2_subdev_mbus_code_enum mbus_code = { 0 };
> + struct sun6i_csi *csi = video->csi;
> + struct v4l2_subdev *subdev;
> + u32 pad;
> + const u32 *pixformats;
> + int pixformat_count = 0;
> + u32 subdev_codes[32]; /* subdev format codes, 32 should be enough */
> + int codes_count = 0;
> + int num_fmts = 0;
> + int i, j;
> +
> + subdev = sun6i_video_remote_subdev(video, &pad);
> + if (subdev == NULL)
> + return -ENXIO;
> +
> + /* Get supported pixformats of CSI */
> + pixformat_count = sun6i_csi_get_supported_pixformats(csi, &pixformats);
> + if (pixformat_count <= 0)
> + return -ENXIO;
> +
> + /* Get subdev formats codes */
> + mbus_code.pad = pad;
> + mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> + while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL,
> + &mbus_code)) {
> + subdev_codes[codes_count] = mbus_code.code;
This definitely needs a check when codes_count >= ARRAY_SIZE(subdev_codes).
> + codes_count++;
> + mbus_code.index++;
> + }
> +
> + if (!codes_count)
> + return -ENXIO;
> +
> + /* Get supported formats count */
> + for (i = 0; i < codes_count; i++) {
> + for (j = 0; j < pixformat_count; j++) {
> + if (!sun6i_csi_is_format_support(csi, pixformats[j],
> + mbus_code.code)) {
> + continue;
> + }
> + num_fmts++;
> + }
> + }
> +
> + if (!num_fmts)
> + return -ENXIO;
> +
> + video->num_formats = num_fmts;
> + video->formats = devm_kcalloc(video->csi->dev, num_fmts,
> + sizeof(struct sun6i_csi_format), GFP_KERNEL);
> + if (!video->formats) {
> + dev_err(video->csi->dev, "could not allocate memory\n");
> + return -ENOMEM;
> + }
> +
> + /* Get supported formats */
> + num_fmts = 0;
> + for (i = 0; i < codes_count; i++) {
> + for (j = 0; j < pixformat_count; j++) {
> + if (!sun6i_csi_is_format_support(csi, pixformats[j],
> + mbus_code.code)) {
> + continue;
> + }
> +
> + video->formats[num_fmts].fourcc = pixformats[j];
> + video->formats[num_fmts].mbus_code =
> + mbus_code.code;
> + video->formats[num_fmts].bpp =
> + v4l2_pixformat_get_bpp(pixformats[j]);
> + num_fmts++;
> + }
> + }
As mentioned above, the initial format should probably be configure here.
> +
> + return 0;
> +}
> +
> +static int sun6i_video_link_setup(struct media_entity *entity,
> + const struct media_pad *local,
> + const struct media_pad *remote, u32 flags)
> +{
> + struct video_device *vdev = media_entity_to_video_device(entity);
> + struct sun6i_video *video = video_get_drvdata(vdev);
> +
> + if (WARN_ON(video == NULL))
> + return 0;
> +
> + return sun6i_video_formats_init(video);
Why is this called here? Why not in video_init()?
> +}
> +
> +static const struct media_entity_operations sun6i_video_media_ops = {
> + .link_setup = sun6i_video_link_setup,
> +};
> +
> +int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
> + const char *name)
> +{
> + struct video_device *vdev = &video->vdev;
> + struct vb2_queue *vidq = &video->vb2_vidq;
> + int ret;
> +
> + video->csi = csi;
> +
> + /* Initialize the media entity... */
> + video->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
> + vdev->entity.ops = &sun6i_video_media_ops;
> + ret = media_entity_pads_init(&vdev->entity, 1, &video->pad);
> + if (ret < 0)
> + return ret;
> +
> + mutex_init(&video->lock);
> +
> + INIT_LIST_HEAD(&video->dma_queue);
> + spin_lock_init(&video->dma_queue_lock);
> +
> + video->cur_frm = NULL;
> + video->sequence = 0;
> + video->num_formats = 0;
> +
> + /* Initialize videobuf2 queue */
> + vidq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> + vidq->io_modes = VB2_MMAP | VB2_DMABUF;
> + vidq->drv_priv = video;
> + vidq->buf_struct_size = sizeof(struct sun6i_csi_buffer);
> + vidq->ops = &sun6i_csi_vb2_ops;
> + vidq->mem_ops = &vb2_dma_contig_memops;
> + vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> + vidq->lock = &video->lock;
> + vidq->min_buffers_needed = 1;
> + vidq->dev = csi->dev;
> +
> + ret = vb2_queue_init(vidq);
> + if (ret) {
> + v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret);
> + goto error;
> + }
> +
> + /* Register video device */
> + strlcpy(vdev->name, name, sizeof(vdev->name));
> + vdev->release = video_device_release_empty;
> + vdev->fops = &sun6i_video_fops;
> + vdev->ioctl_ops = &sun6i_video_ioctl_ops;
> + vdev->vfl_type = VFL_TYPE_GRABBER;
> + vdev->vfl_dir = VFL_DIR_RX;
> + vdev->v4l2_dev = &csi->v4l2_dev;
> + vdev->queue = vidq;
> + vdev->lock = &video->lock;
> + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
> + video_set_drvdata(vdev, video);
> +
> + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
> + if (ret < 0) {
> + v4l2_err(&csi->v4l2_dev,
> + "video_register_device failed: %d\n", ret);
> + goto error;
> + }
> +
> + return 0;
> +
> +error:
> + sun6i_video_cleanup(video);
> + return ret;
> +}
> +
> +void sun6i_video_cleanup(struct sun6i_video *video)
> +{
> + if (video_is_registered(&video->vdev))
> + video_unregister_device(&video->vdev);
> +
> + media_entity_cleanup(&video->vdev.entity);
> +}
> diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.h b/drivers/media/platform/sun6i-csi/sun6i_video.h
> new file mode 100644
> index 0000000..14eac6e
> --- /dev/null
> +++ b/drivers/media/platform/sun6i-csi/sun6i_video.h
> @@ -0,0 +1,61 @@
> +/*
> + * Copyright (c) 2017 Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef __SUN6I_VIDEO_H__
> +#define __SUN6I_VIDEO_H__
> +
> +#include <media/v4l2-dev.h>
> +#include <media/videobuf2-core.h>
> +
> +/*
> + * struct sun6i_csi_format - CSI media bus format information
> + * @fourcc: Fourcc code for this format
> + * @mbus_code: V4L2 media bus format code.
> + * @bpp: Bytes per pixel (when stored in memory)
> + */
> +struct sun6i_csi_format {
> + u32 fourcc;
> + u32 mbus_code;
> + u8 bpp;
> +};
> +
> +struct sun6i_csi;
> +
> +struct sun6i_video {
> + struct video_device vdev;
> + struct media_pad pad;
> + struct sun6i_csi *csi;
> +
> + struct mutex lock;
> +
> + struct vb2_queue vb2_vidq;
> + spinlock_t dma_queue_lock;
> + struct list_head dma_queue;
> +
> + struct sun6i_csi_buffer *cur_frm;
> + unsigned int sequence;
> +
> + struct sun6i_csi_format *formats;
> + unsigned int num_formats;
> + struct sun6i_csi_format *current_fmt;
> + struct v4l2_format fmt;
> +};
> +
> +int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
> + const char *name);
> +void sun6i_video_cleanup(struct sun6i_video *video);
> +
> +void sun6i_video_frame_done(struct sun6i_video *video);
> +
> +#endif /* __SUN6I_VIDEO_H__ */
>
Regards,
Hans
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
[not found] ` <1501131697-1359-2-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
` (2 preceding siblings ...)
2017-08-21 14:37 ` Hans Verkuil
@ 2017-09-21 13:45 ` 'Ondřej Jirman' via linux-sunxi
2017-11-21 15:48 ` Maxime Ripard
4 siblings, 0 replies; 56+ messages in thread
From: 'Ondřej Jirman' via linux-sunxi @ 2017-09-21 13:45 UTC (permalink / raw)
To: yong.deng-+3dxTMOEIRNWk0Htik3J/w,
maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
[-- Attachment #1: Type: text/plain, Size: 4740 bytes --]
Hello Yong,
I noticed one issue in the register macros. See below.
Yong Deng píše v Čt 27. 07. 2017 v 13:01 +0800:
> Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> and CSI1 is used for parallel interface. This is not documented in
> datasheet but by testing and guess.
>
> This patch implement a v4l2 framework driver for it.
>
> Currently, the driver only support the parallel interface. MIPI-CSI2,
> ISP's support are not included in this patch.
>
> Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
> ---
>
[snip]
> +
> +#define CSI_CH_INT_EN_REG 0x70
> +#define CSI_CH_INT_EN_VS_INT_EN BIT(7)
> +#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6)
> +#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5)
> +#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4)
> +#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3)
> +#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2)
> +#define CSI_CH_INT_EN_FD_INT_EN BIT(1)
> +#define CSI_CH_INT_EN_CD_INT_EN BIT(0)
> +
> +#define CSI_CH_INT_STA_REG 0x74
> +#define CSI_CH_INT_STA_VS_PD BIT(7)
> +#define CSI_CH_INT_STA_HB_OF_PD BIT(6)
> +#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5)
> +#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4)
> +#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3)
> +#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2)
> +#define CSI_CH_INT_STA_FD_PD BIT(1)
> +#define CSI_CH_INT_STA_CD_PD BIT(0)
> +
> +#define CSI_CH_FLD1_VSIZE_REG 0x74
This register should be 0x78 according to the V3s manual. Though it's
not used in your driver yet, so it is not yet causing any issues.
> +#define CSI_CH_HSIZE_REG 0x80
> +#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16)
> +#define CSI_CH_HSIZE_HOR_LEN(len) ((len << 16) & CSI_CH_HSIZE_HOR_LEN_MASK)
> +#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0)
> +#define CSI_CH_HSIZE_HOR_START(start) ((start << 0) & CSI_CH_HSIZE_HOR_START_MASK)
> +
> +#define CSI_CH_VSIZE_REG 0x84
> +#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16)
> +#define CSI_CH_VSIZE_VER_LEN(len) ((len << 16) & CSI_CH_VSIZE_VER_LEN_MASK)
> +#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0)
> +#define CSI_CH_VSIZE_VER_START(start) ((start << 0) & CSI_CH_VSIZE_VER_START_MASK)
> +
> +#define CSI_CH_BUF_LEN_REG 0x88
> +#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16)
> +#define CSI_CH_BUF_LEN_BUF_LEN_C(len) ((len << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK)
> +#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0)
> +#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) ((len << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK)
> +
> +#define CSI_CH_FLIP_SIZE_REG 0x8c
> +#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16)
> +#define CSI_CH_FLIP_SIZE_VER_LEN(len) ((len << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK)
> +#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0)
> +#define CSI_CH_FLIP_SIZE_VALID_LEN(len) ((len << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK)
> +
> +#define CSI_CH_FRM_CLK_CNT_REG 0x90
> +#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94
> +#define CSI_CH_FIFO_STAT_REG 0x98
> +#define CSI_CH_PCLK_STAT_REG 0x9c
> +
> +/*
> + * csi input data format
> + */
> +enum csi_input_fmt
> +{
> + CSI_INPUT_FORMAT_RAW = 0,
> + CSI_INPUT_FORMAT_YUV422 = 3,
> + CSI_INPUT_FORMAT_YUV420 = 4,
> +};
> +
> +/*
> + * csi output data format
> + */
> +enum csi_output_fmt
> +{
> + /* only when input format is RAW */
> + CSI_FIELD_RAW_8 = 0,
> + CSI_FIELD_RAW_10 = 1,
> + CSI_FIELD_RAW_12 = 2,
> + CSI_FIELD_RGB565 = 4,
> + CSI_FIELD_RGB888 = 5,
> + CSI_FIELD_PRGB888 = 6,
> + CSI_FRAME_RAW_8 = 8,
> + CSI_FRAME_RAW_10 = 9,
> + CSI_FRAME_RAW_12 = 10,
> + CSI_FRAME_RGB565 = 12,
> + CSI_FRAME_RGB888 = 13,
> + CSI_FRAME_PRGB888 = 14,
> +
> + /* only when input format is YUV422/YUV420 */
Other limitation is that when input is YUV420 output can only be YUV420
and not YUV422.
> + CSI_FIELD_PLANAR_YUV422 = 0,
> + CSI_FIELD_PLANAR_YUV420 = 1,
> + CSI_FRAME_PLANAR_YUV420 = 2,
> + CSI_FRAME_PLANAR_YUV422 = 3,
> + CSI_FIELD_UV_CB_YUV422 = 4,
> + CSI_FIELD_UV_CB_YUV420 = 5,
> + CSI_FRAME_UV_CB_YUV420 = 6,
> + CSI_FRAME_UV_CB_YUV422 = 7,
> + CSI_FIELD_MB_YUV422 = 8,
> + CSI_FIELD_MB_YUV420 = 9,
> + CSI_FRAME_MB_YUV420 = 10,
> + CSI_FRAME_MB_YUV422 = 11,
> + CSI_FIELD_UV_CB_YUV422_10 = 12,
> + CSI_FIELD_UV_CB_YUV420_10 = 13,
> +};
> +
thank you and regards,
Ondrej
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
[not found] ` <1501131697-1359-2-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
` (3 preceding siblings ...)
2017-09-21 13:45 ` 'Ondřej Jirman' via linux-sunxi
@ 2017-11-21 15:48 ` Maxime Ripard
[not found] ` <20171121154827.5a35xa6zlqrrvkxx-ZC1Zs529Oq4@public.gmane.org>
4 siblings, 1 reply; 56+ messages in thread
From: Maxime Ripard @ 2017-11-21 15:48 UTC (permalink / raw)
To: Yong Deng
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
[-- Attachment #1: Type: text/plain, Size: 1684 bytes --]
Hi,
On Thu, Jul 27, 2017 at 01:01:35PM +0800, Yong Deng wrote:
> Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> and CSI1 is used for parallel interface. This is not documented in
> datasheet but by testing and guess.
>
> This patch implement a v4l2 framework driver for it.
>
> Currently, the driver only support the parallel interface. MIPI-CSI2,
> ISP's support are not included in this patch.
>
> Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
Thanks again for this driver.
It seems like at least this iteration is behaving in a weird way with
DMA transfers for at least YU12 and NV12 (and I would assume YV12).
Starting a transfer of multiple frames in either of these formats,
using either ffmpeg (ffmpeg -f v4l2 -video_size 640x480 -framerate 30
-i /dev/video0 output.mkv) or yavta (yavta -c80 -p -F --skip 0 -f NV12
-s 640x480 $(media-c tl -e 'sun6i-csi')) will end up in a panic.
The panic seems to be generated with random data going into parts of
the kernel memory, the pattern being in my case something like
0x8287868a which is very odd (always around 0x88)
It turns out that when you cover the sensor, the values change to
around 0x28, so it really seems like it's pixels that have been copied
there.
I've looked quickly at the DMA setup, and it seems reasonable to
me. Do you have the same issue on your side? Have you been able to
test those formats using your hardware?
Given that they all are planar formats and YUYV and the likes work
just fine, maybe we can leave them aside for now?
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 56+ messages in thread
* 回复:[linux-sunxi] [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-27 5:01 ` [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI Yong Deng
[not found] ` <1501131697-1359-2-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
@ 2017-09-22 0:57 ` 邓永
2017-09-22 8:44 ` Mylene JOSSERAND
2 siblings, 0 replies; 56+ messages in thread
From: 邓永 @ 2017-09-22 0:57 UTC (permalink / raw)
To: Ondřej Jirman, maxime.ripard
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media, devicetree
[-- Attachment #1: Type: text/plain, Size: 6476 bytes --]
Hi Ondrej,
Thank you!
I will fix it.
Thanks
Yong
------------------------------------------------------------------发件人:Ondřej Jirman <megous@megous.com>发送时间:2017年9月21日(星期四) 21:45收件人:邓永 <yong.deng@magewell.com>; maxime.ripard <maxime.ripard@free-electrons.com>抄 送:Mauro Carvalho Chehab <mchehab@kernel.org>; Rob Herring <robh+dt@kernel.org>; Mark Rutland <mark.rutland@arm.com>; Chen-Yu Tsai <wens@csie.org>; Greg Kroah-Hartman <gregkh@linuxfoundation.org>; David S. Miller <davem@davemloft.net>; Hans Verkuil <hverkuil@xs4all.nl>; Arnd Bergmann <arnd@arndb.de>; Hugues Fruchet <hugues.fruchet@st.com>; Yannick Fertre <yannick.fertre@st.com>; Philipp Zabel <p.zabel@pengutronix.de>; Benoit Parrot <bparrot@ti.com>; Benjamin Gaignard <benjamin.gaignard@linaro.org>; Jean-Christophe Trotin <jean-christophe.trotin@st.com>; Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>; Minghsiu Tsai <minghsiu.tsai@mediatek.com>; Krzysztof Kozlowski <krzk@kernel.org>; Robert Jarzmik <robert.jarzmik@free.fr>; linux-media <linux-media@vger.kernel.org>; devicetree <devicetree@vger.kernel.org>; linux-arm-kernel <linux-arm-kernel@lists.infradead.org>; linux-kernel <linux-kernel@vger.kernel.org>; linux-sunxi <linux-sunxi@googlegroups.com>主 题:Re: [linux-sunxi] [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
Hello Yong,
I noticed one issue in the register macros. See below.
Yong Deng píše v Čt 27. 07. 2017 v 13:01 +0800:
> Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> and CSI1 is used for parallel interface. This is not documented in
> datasheet but by testing and guess.
>
> This patch implement a v4l2 framework driver for it.
>
> Currently, the driver only support the parallel interface. MIPI-CSI2,
> ISP's support are not included in this patch.
>
> Signed-off-by: Yong Deng <yong.deng@magewell.com>
> ---
>
[snip]
> +
> +#define CSI_CH_INT_EN_REG 0x70
> +#define CSI_CH_INT_EN_VS_INT_EN BIT(7)
> +#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6)
> +#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5)
> +#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4)
> +#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3)
> +#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2)
> +#define CSI_CH_INT_EN_FD_INT_EN BIT(1)
> +#define CSI_CH_INT_EN_CD_INT_EN BIT(0)
> +
> +#define CSI_CH_INT_STA_REG 0x74
> +#define CSI_CH_INT_STA_VS_PD BIT(7)
> +#define CSI_CH_INT_STA_HB_OF_PD BIT(6)
> +#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5)
> +#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4)
> +#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3)
> +#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2)
> +#define CSI_CH_INT_STA_FD_PD BIT(1)
> +#define CSI_CH_INT_STA_CD_PD BIT(0)
> +
> +#define CSI_CH_FLD1_VSIZE_REG 0x74
This register should be 0x78 according to the V3s manual. Though it's
not used in your driver yet, so it is not yet causing any issues.
> +#define CSI_CH_HSIZE_REG 0x80
> +#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16)
> +#define CSI_CH_HSIZE_HOR_LEN(len) ((len << 16) & CSI_CH_HSIZE_HOR_LEN_MASK)
> +#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0)
> +#define CSI_CH_HSIZE_HOR_START(start) ((start << 0) & CSI_CH_HSIZE_HOR_START_MASK)
> +
> +#define CSI_CH_VSIZE_REG 0x84
> +#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16)
> +#define CSI_CH_VSIZE_VER_LEN(len) ((len << 16) & CSI_CH_VSIZE_VER_LEN_MASK)
> +#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0)
> +#define CSI_CH_VSIZE_VER_START(start) ((start << 0) & CSI_CH_VSIZE_VER_START_MASK)
> +
> +#define CSI_CH_BUF_LEN_REG 0x88
> +#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16)
> +#define CSI_CH_BUF_LEN_BUF_LEN_C(len) ((len << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK)
> +#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0)
> +#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) ((len << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK)
> +
> +#define CSI_CH_FLIP_SIZE_REG 0x8c
> +#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16)
> +#define CSI_CH_FLIP_SIZE_VER_LEN(len) ((len << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK)
> +#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0)
> +#define CSI_CH_FLIP_SIZE_VALID_LEN(len) ((len << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK)
> +
> +#define CSI_CH_FRM_CLK_CNT_REG 0x90
> +#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94
> +#define CSI_CH_FIFO_STAT_REG 0x98
> +#define CSI_CH_PCLK_STAT_REG 0x9c
> +
> +/*
> + * csi input data format
> + */
> +enum csi_input_fmt
> +{
> + CSI_INPUT_FORMAT_RAW = 0,
> + CSI_INPUT_FORMAT_YUV422 = 3,
> + CSI_INPUT_FORMAT_YUV420 = 4,
> +};
> +
> +/*
> + * csi output data format
> + */
> +enum csi_output_fmt
> +{
> + /* only when input format is RAW */
> + CSI_FIELD_RAW_8 = 0,
> + CSI_FIELD_RAW_10 = 1,
> + CSI_FIELD_RAW_12 = 2,
> + CSI_FIELD_RGB565 = 4,
> + CSI_FIELD_RGB888 = 5,
> + CSI_FIELD_PRGB888 = 6,
> + CSI_FRAME_RAW_8 = 8,
> + CSI_FRAME_RAW_10 = 9,
> + CSI_FRAME_RAW_12 = 10,
> + CSI_FRAME_RGB565 = 12,
> + CSI_FRAME_RGB888 = 13,
> + CSI_FRAME_PRGB888 = 14,
> +
> + /* only when input format is YUV422/YUV420 */
Other limitation is that when input is YUV420 output can only be YUV420
and not YUV422.
> + CSI_FIELD_PLANAR_YUV422 = 0,
> + CSI_FIELD_PLANAR_YUV420 = 1,
> + CSI_FRAME_PLANAR_YUV420 = 2,
> + CSI_FRAME_PLANAR_YUV422 = 3,
> + CSI_FIELD_UV_CB_YUV422 = 4,
> + CSI_FIELD_UV_CB_YUV420 = 5,
> + CSI_FRAME_UV_CB_YUV420 = 6,
> + CSI_FRAME_UV_CB_YUV422 = 7,
> + CSI_FIELD_MB_YUV422 = 8,
> + CSI_FIELD_MB_YUV420 = 9,
> + CSI_FRAME_MB_YUV420 = 10,
> + CSI_FRAME_MB_YUV422 = 11,
> + CSI_FIELD_UV_CB_YUV422_10 = 12,
> + CSI_FIELD_UV_CB_YUV420_10 = 13,
> +};
> +
thank you and regards,
Ondrej
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
[-- Attachment #2: Type: text/html, Size: 12595 bytes --]
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-27 5:01 ` [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI Yong Deng
[not found] ` <1501131697-1359-2-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
2017-09-22 0:57 ` 回复:[linux-sunxi] " 邓永
@ 2017-09-22 8:44 ` Mylene JOSSERAND
[not found] ` <20170922104413.5d7d64e7-K8i4uRanGBt8XcdJbWeDu7NAH6kLmebB@public.gmane.org>
2 siblings, 1 reply; 56+ messages in thread
From: Mylene JOSSERAND @ 2017-09-22 8:44 UTC (permalink / raw)
To: Yong Deng
Cc: maxime.ripard, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, Greg Kroah-Hartman, David S. Miller, Hans Verkuil,
Arnd Bergmann, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Benoit Parrot, Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert
Hello Yong,
Thank you for these drivers!
I tested it with an OV5640 camera on an Nanopi M1 plus (Allwinner H3)
and I noticed that I got a frame correctly displayed only on a half of
the frame's size.
It is related to your "sun6i_csi_set_window" function (see
below).
> Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
> and CSI1 is used for parallel interface. This is not documented in
> datasheet but by testing and guess.
>
> This patch implement a v4l2 framework driver for it.
>
> Currently, the driver only support the parallel interface. MIPI-CSI2,
> ISP's support are not included in this patch.
>
> Signed-off-by: Yong Deng <yong.deng@magewell.com>
> ---
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/sun6i-csi/Kconfig | 9 +
> drivers/media/platform/sun6i-csi/Makefile | 3 +
> drivers/media/platform/sun6i-csi/sun6i_csi.c | 545
> +++++++++++++++ drivers/media/platform/sun6i-csi/sun6i_csi.h |
> 203 ++++++ drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c | 827
> +++++++++++++++++++++++
> drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h | 206 ++++++
> drivers/media/platform/sun6i-csi/sun6i_video.c | 663
> ++++++++++++++++++ drivers/media/platform/sun6i-csi/sun6i_video.h
> | 61 ++ 10 files changed, 2520 insertions(+) create mode 100644
> drivers/media/platform/sun6i-csi/Kconfig create mode 100644
> drivers/media/platform/sun6i-csi/Makefile create mode 100644
> drivers/media/platform/sun6i-csi/sun6i_csi.c create mode 100644
> drivers/media/platform/sun6i-csi/sun6i_csi.h create mode 100644
> drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c create mode 100644
> drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h create mode 100644
> drivers/media/platform/sun6i-csi/sun6i_video.c create mode 100644
> drivers/media/platform/sun6i-csi/sun6i_video.h
>
> diff --git a/drivers/media/platform/Kconfig
> b/drivers/media/platform/Kconfig index 0c741d1..8371a87 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -143,6 +143,7 @@ source "drivers/media/platform/am437x/Kconfig"
> source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> +source "drivers/media/platform/sun6i-csi/Kconfig"
>
<snip>
> +static void sun6i_csi_set_format(struct sun6i_csi_dev *sdev)
> +{
> + struct sun6i_csi *csi = &sdev->csi;
> + u32 cfg;
> + u32 val;
> +
> + regmap_read(sdev->regmap, CSI_CH_CFG_REG, &cfg);
> +
> + cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK |
> + CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN |
> + CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK |
> + CSI_CH_CFG_INPUT_SEQ_MASK);
> +
> + val = get_csi_input_format(csi->config.code,
> csi->config.pixelformat);
> + cfg |= CSI_CH_CFG_INPUT_FMT(val);
> +
> + val = get_csi_output_format(csi->config.code,
> csi->config.field);
> + cfg |= CSI_CH_CFG_OUTPUT_FMT(val);
> +
> + val = get_csi_input_seq(csi->config.code,
> csi->config.pixelformat);
> + cfg |= CSI_CH_CFG_INPUT_SEQ(val);
> +
> + if (csi->config.field == V4L2_FIELD_TOP)
> + cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0;
> + else if (csi->config.field == V4L2_FIELD_BOTTOM)
> + cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1;
> + else
> + cfg |= CSI_CH_CFG_FIELD_SEL_BOTH;
> +
> + regmap_write(sdev->regmap, CSI_CH_CFG_REG, cfg);
> +}
> +
> +static void sun6i_csi_set_window(struct sun6i_csi_dev *sdev)
> +{
> + struct sun6i_csi_config *config = &sdev->csi.config;
> + u32 bytesperline_y;
> + u32 bytesperline_c;
> + int *planar_offset = sdev->planar_offset;
> +
> + regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
> + CSI_CH_HSIZE_HOR_LEN(config->width) |
> + CSI_CH_HSIZE_HOR_START(0));
> + regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
> + CSI_CH_VSIZE_VER_LEN(config->height) |
> + CSI_CH_VSIZE_VER_START(0));
In my case, the HOR_LEN and VER_LEN were not correctly configured.
They were configured to width and height (640 * 480) but as I was
using a YUYV format, the pixel's size is 2 bytes so the
horizontal/vertical lines' lengths were divided by 2.
Currently, I fixed that by getting the number of bytes per pixel with
"v4l2_pixformat_get_bpp()":
+ int bytes_pixel;
+
+ bytes_pixel = v4l2_pixformat_get_bpp(config->pixelformat) / 8;
regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
- CSI_CH_HSIZE_HOR_LEN(config->width) |
+ CSI_CH_HSIZE_HOR_LEN(config->width * bytes_pixel) |
CSI_CH_HSIZE_HOR_START(0));
regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
- CSI_CH_VSIZE_VER_LEN(config->height) |
+ CSI_CH_VSIZE_VER_LEN(config->height * bytes_pixel)
| CSI_CH_VSIZE_VER_START(0));
There is certainly a nicer way to handle that.
> +
> + planar_offset[0] = 0;
> + switch(config->pixelformat) {
> + case V4L2_PIX_FMT_HM12:
> + case V4L2_PIX_FMT_NV12:
> + case V4L2_PIX_FMT_NV21:
> + case V4L2_PIX_FMT_NV16:
> + case V4L2_PIX_FMT_NV61:
> + bytesperline_y = config->width;
> + bytesperline_c = config->width;
> + planar_offset[1] = bytesperline_y * config->height;
> + planar_offset[2] = -1;
> + break;
> + case V4L2_PIX_FMT_YUV420:
> + case V4L2_PIX_FMT_YVU420:
> + bytesperline_y = config->width;
> + bytesperline_c = config->width / 2;
> + planar_offset[1] = bytesperline_y * config->height;
> + planar_offset[2] = planar_offset[1] +
> + bytesperline_c * config->height / 2;
> + break;
> + case V4L2_PIX_FMT_YUV422P:
> + bytesperline_y = config->width;
> + bytesperline_c = config->width / 2;
> + planar_offset[1] = bytesperline_y * config->height;
> + planar_offset[2] = planar_offset[1] +
> + bytesperline_c * config->height;
> + break;
> + default: /* raw */
> + bytesperline_y =
> (v4l2_pixformat_get_bpp(config->pixelformat) *
> + config->width) / 8;
> + bytesperline_c = 0;
> + planar_offset[1] = -1;
> + planar_offset[2] = -1;
> + break;
> + }
> +
> + regmap_write(sdev->regmap, CSI_CH_BUF_LEN_REG,
> + CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) |
> + CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y));
> +}
> +
> +static int get_supported_pixformats(struct sun6i_csi *csi,
> + const u32 **pixformats)
> +{
> + if (pixformats != NULL)
> + *pixformats = supported_pixformats;
> +
> + return ARRAY_SIZE(supported_pixformats);
> +}
> +
> +static bool is_format_support(struct sun6i_csi *csi, u32 pixformat,
> + u32 mbus_code)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> +
> + return __is_format_support(sdev, pixformat, mbus_code);
> +}
> +
> +static int set_power(struct sun6i_csi *csi, bool enable)
> +{
> + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
> + struct regmap *regmap = sdev->regmap;
> + int ret;
> +
> + if (!enable) {
> + regmap_update_bits(regmap, CSI_EN_REG,
> CSI_EN_CSI_EN, 0); +
> + clk_disable_unprepare(sdev->clk_ram);
> + clk_disable_unprepare(sdev->clk_mod);
> + clk_disable_unprepare(sdev->clk_ahb);
> + reset_control_assert(sdev->rstc_ahb);
> + return 0;
> + }
> +
> + ret = clk_prepare_enable(sdev->clk_ahb);
> + if (ret) {
> + dev_err(sdev->dev, "Enable ahb clk err %d\n", ret);
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(sdev->clk_mod);
> + if (ret) {
> + dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(sdev->clk_ram);
> + if (ret) {
> + dev_err(sdev->dev, "Enable clk_dram_csi clk err
> %d\n", ret);
> + return ret;
> + }
> +
> + if (!IS_ERR_OR_NULL(sdev->rstc_ahb)) {
> + ret = reset_control_deassert(sdev->rstc_ahb);
> + if (ret) {
> + dev_err(sdev->dev, "reset err %d\n", ret);
> + return ret;
> + }
> + }
> +
> + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
> CSI_EN_CSI_EN); +
> + return 0;
> +}
<snip>
Thank you in advance!
Best regards,
--
Mylène Josserand, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH v2 2/3] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI)
2017-07-27 5:01 [PATCH v2 0/3] Initial Allwinner V3s CSI Support Yong Deng
2017-07-27 5:01 ` [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI Yong Deng
@ 2017-07-27 5:01 ` Yong Deng
[not found] ` <1501131697-1359-3-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
2017-12-19 11:53 ` Sakari Ailus
2017-07-27 5:01 ` [PATCH v2 3/3] media: MAINTAINERS: add entries for Allwinner V3s CSI Yong Deng
2 siblings, 2 replies; 56+ messages in thread
From: Yong Deng @ 2017-07-27 5:01 UTC (permalink / raw)
To: maxime.ripard
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik
Add binding documentation for Allwinner V3s CSI.
Signed-off-by: Yong Deng <yong.deng@magewell.com>
---
.../devicetree/bindings/media/sun6i-csi.txt | 49 ++++++++++++++++++++++
1 file changed, 49 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/sun6i-csi.txt
diff --git a/Documentation/devicetree/bindings/media/sun6i-csi.txt b/Documentation/devicetree/bindings/media/sun6i-csi.txt
new file mode 100644
index 0000000..f8d83f6
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/sun6i-csi.txt
@@ -0,0 +1,49 @@
+Allwinner V3s Camera Sensor Interface
+------------------------------
+
+Required properties:
+ - compatible: value must be "allwinner,sun8i-v3s-csi"
+ - reg: base address and size of the memory-mapped region.
+ - interrupts: interrupt associated to this IP
+ - clocks: phandles to the clocks feeding the CSI
+ * ahb: the CSI interface clock
+ * mod: the CSI module clock
+ * ram: the CSI DRAM clock
+ - clock-names: the clock names mentioned above
+ - resets: phandles to the reset line driving the CSI
+
+- ports: A ports node with endpoint definitions as defined in
+ Documentation/devicetree/bindings/media/video-interfaces.txt.
+
+Example:
+
+ csi1: csi@01cb4000 {
+ compatible = "allwinner,sun8i-v3s-csi";
+ reg = <0x01cb4000 0x1000>;
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_CSI>,
+ <&ccu CLK_CSI1_SCLK>,
+ <&ccu CLK_DRAM_CSI>;
+ clock-names = "ahb", "mod", "ram";
+ resets = <&ccu RST_BUS_CSI>;
+
+ port {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* Parallel bus endpoint */
+ csi1_ep: endpoint {
+ remote-endpoint = <&adv7611_ep>;
+ bus-width = <16>;
+ data-shift = <0>;
+
+ /* If hsync-active/vsync-active are missing,
+ embedded BT.656 sync is used */
+ hsync-active = <0>; /* Active low */
+ vsync-active = <0>; /* Active low */
+ data-active = <1>; /* Active high */
+ pclk-sample = <1>; /* Rising */
+ };
+ };
+ };
+
--
1.8.3.1
^ permalink raw reply related [flat|nested] 56+ messages in thread
[parent not found: <1501131697-1359-3-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>]
* Re: [PATCH v2 2/3] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI)
[not found] ` <1501131697-1359-3-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
@ 2017-07-28 16:03 ` Maxime Ripard
[not found] ` <20170728160353.gqb2f47v6ujozvxz-ZC1Zs529Oq4@public.gmane.org>
2017-08-03 19:14 ` Rob Herring
1 sibling, 1 reply; 56+ messages in thread
From: Maxime Ripard @ 2017-07-28 16:03 UTC (permalink / raw)
To: Yong Deng
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, lin
[-- Attachment #1: Type: text/plain, Size: 1320 bytes --]
Hi,
On Thu, Jul 27, 2017 at 01:01:36PM +0800, Yong Deng wrote:
> Add binding documentation for Allwinner V3s CSI.
>
> Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
> ---
> .../devicetree/bindings/media/sun6i-csi.txt | 49 ++++++++++++++++++++++
> 1 file changed, 49 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/sun6i-csi.txt
>
> diff --git a/Documentation/devicetree/bindings/media/sun6i-csi.txt b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> new file mode 100644
> index 0000000..f8d83f6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> @@ -0,0 +1,49 @@
> +Allwinner V3s Camera Sensor Interface
> +------------------------------
> +
> +Required properties:
> + - compatible: value must be "allwinner,sun8i-v3s-csi"
> + - reg: base address and size of the memory-mapped region.
> + - interrupts: interrupt associated to this IP
> + - clocks: phandles to the clocks feeding the CSI
> + * ahb: the CSI interface clock
We've been bad at this, but we're trying to have the same clock name
here across all devices, and to use "bus" instead of "ahb".
Thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 2/3] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI)
[not found] ` <1501131697-1359-3-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
2017-07-28 16:03 ` Maxime Ripard
@ 2017-08-03 19:14 ` Rob Herring
2017-08-07 1:00 ` Yong
1 sibling, 1 reply; 56+ messages in thread
From: Rob Herring @ 2017-08-03 19:14 UTC (permalink / raw)
To: Yong Deng
Cc: maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
Mauro Carvalho Chehab, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, lin
On Thu, Jul 27, 2017 at 01:01:36PM +0800, Yong Deng wrote:
> Add binding documentation for Allwinner V3s CSI.
>
> Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
> ---
> .../devicetree/bindings/media/sun6i-csi.txt | 49 ++++++++++++++++++++++
> 1 file changed, 49 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/sun6i-csi.txt
>
> diff --git a/Documentation/devicetree/bindings/media/sun6i-csi.txt b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> new file mode 100644
> index 0000000..f8d83f6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> @@ -0,0 +1,49 @@
> +Allwinner V3s Camera Sensor Interface
> +------------------------------
> +
> +Required properties:
> + - compatible: value must be "allwinner,sun8i-v3s-csi"
> + - reg: base address and size of the memory-mapped region.
> + - interrupts: interrupt associated to this IP
> + - clocks: phandles to the clocks feeding the CSI
> + * ahb: the CSI interface clock
> + * mod: the CSI module clock
> + * ram: the CSI DRAM clock
> + - clock-names: the clock names mentioned above
> + - resets: phandles to the reset line driving the CSI
> +
> +- ports: A ports node with endpoint definitions as defined in
> + Documentation/devicetree/bindings/media/video-interfaces.txt.
Need to be explicit about how many ports and endpoints and what each one
represents.
> +
> +Example:
> +
> + csi1: csi@01cb4000 {
> + compatible = "allwinner,sun8i-v3s-csi";
> + reg = <0x01cb4000 0x1000>;
> + interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&ccu CLK_BUS_CSI>,
> + <&ccu CLK_CSI1_SCLK>,
> + <&ccu CLK_DRAM_CSI>;
> + clock-names = "ahb", "mod", "ram";
> + resets = <&ccu RST_BUS_CSI>;
> +
> + port {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + /* Parallel bus endpoint */
> + csi1_ep: endpoint {
> + remote-endpoint = <&adv7611_ep>;
> + bus-width = <16>;
> + data-shift = <0>;
> +
> + /* If hsync-active/vsync-active are missing,
> + embedded BT.656 sync is used */
> + hsync-active = <0>; /* Active low */
> + vsync-active = <0>; /* Active low */
> + data-active = <1>; /* Active high */
> + pclk-sample = <1>; /* Rising */
> + };
> + };
> + };
> +
> --
> 1.8.3.1
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 2/3] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI)
2017-08-03 19:14 ` Rob Herring
@ 2017-08-07 1:00 ` Yong
0 siblings, 0 replies; 56+ messages in thread
From: Yong @ 2017-08-07 1:00 UTC (permalink / raw)
To: Rob Herring
Cc: maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
Mauro Carvalho Chehab, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, lin
On Thu, 3 Aug 2017 14:14:15 -0500
Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
> On Thu, Jul 27, 2017 at 01:01:36PM +0800, Yong Deng wrote:
> > Add binding documentation for Allwinner V3s CSI.
> >
> > Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
> > ---
> > .../devicetree/bindings/media/sun6i-csi.txt | 49 ++++++++++++++++++++++
> > 1 file changed, 49 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/media/sun6i-csi.txt
> >
> > diff --git a/Documentation/devicetree/bindings/media/sun6i-csi.txt b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> > new file mode 100644
> > index 0000000..f8d83f6
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> > @@ -0,0 +1,49 @@
> > +Allwinner V3s Camera Sensor Interface
> > +------------------------------
> > +
> > +Required properties:
> > + - compatible: value must be "allwinner,sun8i-v3s-csi"
> > + - reg: base address and size of the memory-mapped region.
> > + - interrupts: interrupt associated to this IP
> > + - clocks: phandles to the clocks feeding the CSI
> > + * ahb: the CSI interface clock
> > + * mod: the CSI module clock
> > + * ram: the CSI DRAM clock
> > + - clock-names: the clock names mentioned above
> > + - resets: phandles to the reset line driving the CSI
> > +
> > +- ports: A ports node with endpoint definitions as defined in
> > + Documentation/devicetree/bindings/media/video-interfaces.txt.
>
> Need to be explicit about how many ports and endpoints and what each one
> represents.
OK.
>
> > +
> > +Example:
> > +
> > + csi1: csi@01cb4000 {
> > + compatible = "allwinner,sun8i-v3s-csi";
> > + reg = <0x01cb4000 0x1000>;
> > + interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> > + clocks = <&ccu CLK_BUS_CSI>,
> > + <&ccu CLK_CSI1_SCLK>,
> > + <&ccu CLK_DRAM_CSI>;
> > + clock-names = "ahb", "mod", "ram";
> > + resets = <&ccu RST_BUS_CSI>;
> > +
> > + port {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + /* Parallel bus endpoint */
> > + csi1_ep: endpoint {
> > + remote-endpoint = <&adv7611_ep>;
> > + bus-width = <16>;
> > + data-shift = <0>;
> > +
> > + /* If hsync-active/vsync-active are missing,
> > + embedded BT.656 sync is used */
> > + hsync-active = <0>; /* Active low */
> > + vsync-active = <0>; /* Active low */
> > + data-active = <1>; /* Active high */
> > + pclk-sample = <1>; /* Rising */
> > + };
> > + };
> > + };
> > +
> > --
> > 1.8.3.1
> >
Thanks,
Yong
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH v2 2/3] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI)
2017-07-27 5:01 ` [PATCH v2 2/3] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI) Yong Deng
[not found] ` <1501131697-1359-3-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
@ 2017-12-19 11:53 ` Sakari Ailus
[not found] ` <20171219115327.ofs5xwwimpn7x72n-S+BSfZ9RZZmRSg0ZkenSGLdO1Tsj/99ntUK59QYPAWc@public.gmane.org>
1 sibling, 1 reply; 56+ messages in thread
From: Sakari Ailus @ 2017-12-19 11:53 UTC (permalink / raw)
To: Yong Deng
Cc: maxime.ripard, Mauro Carvalho Chehab, Rob Herring, Mark Rutland,
Chen-Yu Tsai, Greg Kroah-Hartman, David S. Miller, Hans Verkuil,
Arnd Bergmann, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Benoit Parrot, Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert
Hi Yong,
On Thu, Jul 27, 2017 at 01:01:36PM +0800, Yong Deng wrote:
> Add binding documentation for Allwinner V3s CSI.
>
> Signed-off-by: Yong Deng <yong.deng@magewell.com>
DT bindings should precede the driver.
> ---
> .../devicetree/bindings/media/sun6i-csi.txt | 49 ++++++++++++++++++++++
> 1 file changed, 49 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/sun6i-csi.txt
>
> diff --git a/Documentation/devicetree/bindings/media/sun6i-csi.txt b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> new file mode 100644
> index 0000000..f8d83f6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/sun6i-csi.txt
> @@ -0,0 +1,49 @@
> +Allwinner V3s Camera Sensor Interface
> +------------------------------
> +
> +Required properties:
> + - compatible: value must be "allwinner,sun8i-v3s-csi"
What are sun6i and sun8i? Is this device first present in sun6i SoCs,
whereas you have only defined bindings for sun8i?
> + - reg: base address and size of the memory-mapped region.
> + - interrupts: interrupt associated to this IP
> + - clocks: phandles to the clocks feeding the CSI
> + * ahb: the CSI interface clock
> + * mod: the CSI module clock
> + * ram: the CSI DRAM clock
> + - clock-names: the clock names mentioned above
> + - resets: phandles to the reset line driving the CSI
> +
> +- ports: A ports node with endpoint definitions as defined in
> + Documentation/devicetree/bindings/media/video-interfaces.txt.
Please document mandatory and optional endpoint properties relevant for the
hardware.
> +
> +Example:
> +
> + csi1: csi@01cb4000 {
> + compatible = "allwinner,sun8i-v3s-csi";
> + reg = <0x01cb4000 0x1000>;
> + interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&ccu CLK_BUS_CSI>,
> + <&ccu CLK_CSI1_SCLK>,
> + <&ccu CLK_DRAM_CSI>;
> + clock-names = "ahb", "mod", "ram";
> + resets = <&ccu RST_BUS_CSI>;
> +
> + port {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + /* Parallel bus endpoint */
> + csi1_ep: endpoint {
> + remote-endpoint = <&adv7611_ep>;
> + bus-width = <16>;
> + data-shift = <0>;
> +
> + /* If hsync-active/vsync-active are missing,
> + embedded BT.656 sync is used */
> + hsync-active = <0>; /* Active low */
> + vsync-active = <0>; /* Active low */
> + data-active = <1>; /* Active high */
> + pclk-sample = <1>; /* Rising */
> + };
> + };
> + };
> +
--
Kind regards,
Sakari Ailus
e-mail: sakari.ailus@iki.fi
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH v2 3/3] media: MAINTAINERS: add entries for Allwinner V3s CSI
2017-07-27 5:01 [PATCH v2 0/3] Initial Allwinner V3s CSI Support Yong Deng
2017-07-27 5:01 ` [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI Yong Deng
2017-07-27 5:01 ` [PATCH v2 2/3] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI) Yong Deng
@ 2017-07-27 5:01 ` Yong Deng
[not found] ` <1501131697-1359-4-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
2 siblings, 1 reply; 56+ messages in thread
From: Yong Deng @ 2017-07-27 5:01 UTC (permalink / raw)
To: maxime.ripard
Cc: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Chen-Yu Tsai,
Greg Kroah-Hartman, David S. Miller, Hans Verkuil, Arnd Bergmann,
Hugues Fruchet, Yannick Fertre, Philipp Zabel, Benoit Parrot,
Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik
Signed-off-by: Yong Deng <yong.deng@magewell.com>
---
MAINTAINERS | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 9826a91..b91fa27 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3686,6 +3686,14 @@ M: Jaya Kumar <jayakumar.alsa@gmail.com>
S: Maintained
F: sound/pci/cs5535audio/
+CSI DRIVERS FOR ALLWINNER V3s
+M: Yong Deng <yong.deng@magewell.com>
+L: linux-media@vger.kernel.org
+T: git git://linuxtv.org/media_tree.git
+S: Maintained
+F: drivers/media/platform/sun6i-csi/
+F: Documentation/devicetree/bindings/media/sun6i-csi.txt
+
CW1200 WLAN driver
M: Solomon Peachy <pizza@shaftnet.org>
S: Maintained
--
1.8.3.1
^ permalink raw reply related [flat|nested] 56+ messages in thread
* [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-27 3:51 [PATCH v2 0/3] Initial Allwinner V3s CSI Support Yong Deng
@ 2017-07-27 3:51 ` Yong Deng
2017-07-27 3:51 ` Yong Deng
[not found] ` <1501127474-40895-1-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
2 siblings, 0 replies; 56+ messages in thread
From: Yong Deng @ 2017-07-27 3:51 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Maxime Ripard,
Chen-Yu Tsai, Greg Kroah-Hartman, David S. Miller, Hans Verkuil,
Arnd Bergmann, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Benoit Parrot, Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media, devicetree, linux-arm-kernel,
linux-kernel, linux-sunxi
Cc: Yong Deng
Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
and CSI1 is used for parallel interface. This is not documented in
datasheet but by testing and guess.
This patch implement a v4l2 framework driver for it.
Currently, the driver only support the parallel interface. MIPI-CSI2,
ISP's support are not included in this patch.
Signed-off-by: Yong Deng <yong.deng@magewell.com>
---
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/sun6i-csi/Kconfig | 9 +
drivers/media/platform/sun6i-csi/Makefile | 3 +
drivers/media/platform/sun6i-csi/sun6i_csi.c | 545 +++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi.h | 203 ++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c | 827 +++++++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h | 206 ++++++
drivers/media/platform/sun6i-csi/sun6i_video.c | 663 ++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_video.h | 61 ++
10 files changed, 2520 insertions(+)
create mode 100644 drivers/media/platform/sun6i-csi/Kconfig
create mode 100644 drivers/media/platform/sun6i-csi/Makefile
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.h
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.h
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 0c741d1..8371a87 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -143,6 +143,7 @@ source "drivers/media/platform/am437x/Kconfig"
source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/sun6i-csi/Kconfig"
config VIDEO_TI_CAL
tristate "TI CAL (Camera Adaptation Layer) driver"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 9beadc7..fb2459c 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -86,3 +86,5 @@ obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/
obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk-jpeg/
obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi/
diff --git a/drivers/media/platform/sun6i-csi/Kconfig b/drivers/media/platform/sun6i-csi/Kconfig
new file mode 100644
index 0000000..314188a
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Kconfig
@@ -0,0 +1,9 @@
+config VIDEO_SUN6I_CSI
+ tristate "Allwinner V3s Camera Sensor Interface driver"
+ depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select REGMAP_MMIO
+ select V4L2_FWNODE
+ ---help---
+ Support for the Allwinner Camera Sensor Interface Controller on V3s.
diff --git a/drivers/media/platform/sun6i-csi/Makefile b/drivers/media/platform/sun6i-csi/Makefile
new file mode 100644
index 0000000..a9b527b
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Makefile
@@ -0,0 +1,3 @@
+sun6i-csi-y += sun6i_csi.o sun6i_video.o sun6i_csi_v3s.o
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sun6i-csi/sun6i_csi.c
new file mode 100644
index 0000000..7a4bf53
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.c
@@ -0,0 +1,545 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing),
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * Based on drivers/media/platform/xilinx/xilinx-vipp.c
+ * Copyright (C) 2013-2015 Ideas on Board
+ * Copyright (C) 2013-2015 Xilinx, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/of_graph.h>
+
+#include "sun6i_csi.h"
+
+/*
+ * struct sun6i_graph_entity - Entity in the video graph
+ * @list: list entry in a graph entities list
+ * @node: the entity's DT node
+ * @entity: media entity, from the corresponding V4L2 subdev
+ * @asd: subdev asynchronous registration information
+ * @subdev: V4L2 subdev
+ */
+struct sun6i_graph_entity {
+ struct list_head list;
+ struct device_node *node;
+ struct media_entity *entity;
+
+ struct v4l2_async_subdev asd;
+ struct v4l2_subdev *subdev;
+};
+
+/* -----------------------------------------------------------------------------
+ * Graph Management
+ */
+
+static struct sun6i_graph_entity *
+sun6i_graph_find_entity(struct sun6i_csi *csi,
+ const struct device_node *node)
+{
+ struct sun6i_graph_entity *entity;
+
+ list_for_each_entry(entity, &csi->entities, list) {
+ if (entity->node == node)
+ return entity;
+ }
+
+ return NULL;
+}
+
+static int sun6i_graph_build_one(struct sun6i_csi *csi,
+ struct sun6i_graph_entity *entity)
+{
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ struct media_entity *local = entity->entity;
+ struct media_entity *remote;
+ struct media_pad *local_pad;
+ struct media_pad *remote_pad;
+ struct sun6i_graph_entity *ent;
+ struct v4l2_fwnode_link link;
+ struct device_node *ep = NULL;
+ struct device_node *next;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "creating links for entity %s\n", local->name);
+
+ while (1) {
+ /* Get the next endpoint and parse its link. */
+ next = of_graph_get_next_endpoint(entity->node, ep);
+ if (next == NULL)
+ break;
+
+ of_node_put(ep);
+ ep = next;
+
+ dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
+
+ ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to parse link for %s\n",
+ ep->full_name);
+ continue;
+ }
+
+ /* Skip sink ports, they will be processed from the other end of
+ * the link.
+ */
+ if (link.local_port >= local->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.local_port,
+ to_of_node(link.local_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ local_pad = &local->pads[link.local_port];
+
+ if (local_pad->flags & MEDIA_PAD_FL_SINK) {
+ dev_dbg(csi->dev, "skipping sink port %s:%u\n",
+ to_of_node(link.local_node)->full_name,
+ link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* Skip video node, they will be processed separately. */
+ if (link.remote_node == of_fwnode_handle(csi->dev->of_node)) {
+ dev_dbg(csi->dev, "skipping CSI port %s:%u\n",
+ to_of_node(link.local_node)->full_name,
+ link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* Find the remote entity. */
+ ent = sun6i_graph_find_entity(csi,
+ to_of_node(link.remote_node));
+ if (ent == NULL) {
+ dev_err(csi->dev, "no entity found for %s\n",
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ remote = ent->entity;
+
+ if (link.remote_port >= remote->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.remote_port,
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ remote_pad = &remote->pads[link.remote_port];
+
+ v4l2_fwnode_put_link(&link);
+
+ /* Create the media link. */
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+
+ ret = media_create_pad_link(local, local_pad->index,
+ remote, remote_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to create %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+ break;
+ }
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_build_video(struct sun6i_csi *csi)
+{
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ struct device_node *node = csi->dev->of_node;
+ struct media_entity *source;
+ struct media_entity *sink;
+ struct media_pad *source_pad;
+ struct media_pad *sink_pad;
+ struct sun6i_graph_entity *ent;
+ struct v4l2_fwnode_link link;
+ struct device_node *ep = NULL;
+ struct device_node *next;
+ struct sun6i_video *video = &csi->video;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "creating link for video node\n");
+
+ while (1) {
+ /* Get the next endpoint and parse its link. */
+ next = of_graph_get_next_endpoint(node, ep);
+ if (next == NULL)
+ break;
+
+ of_node_put(ep);
+ ep = next;
+
+ dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
+
+ ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to parse link for %s\n",
+ ep->full_name);
+ continue;
+ }
+
+ /* Save the video port settings */
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep),
+ &csi->v4l2_ep);
+ if (ret) {
+ ret = -EINVAL;
+ dev_err(csi->dev, "Could not parse the endpoint\n");
+ v4l2_fwnode_put_link(&link);
+ break;
+ }
+
+ dev_dbg(csi->dev, "creating link for video node %s\n",
+ video->vdev.name);
+
+ /* Find the remote entity. */
+ ent = sun6i_graph_find_entity(csi,
+ to_of_node(link.remote_node));
+ if (ent == NULL) {
+ dev_err(csi->dev, "no entity found for %s\n",
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ if (link.remote_port >= ent->entity->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.remote_port,
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ source = ent->entity;
+ source_pad = &source->pads[link.remote_port];
+ sink = &video->vdev.entity;
+ sink_pad = &video->pad;
+
+ v4l2_fwnode_put_link(&link);
+
+ /* Create the media link. */
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+
+ ret = media_create_pad_link(source, source_pad->index,
+ sink, sink_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to create %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+ break;
+ }
+
+ /* Notify video node */
+ ret = media_entity_call(sink, link_setup, sink_pad, source_pad,
+ link_flags);
+ if (ret == -ENOIOCTLCMD)
+ ret = 0;
+
+ /* found one */
+ break;
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct sun6i_csi *csi =
+ container_of(notifier, struct sun6i_csi, notifier);
+ struct sun6i_graph_entity *entity;
+ int ret;
+
+ dev_dbg(csi->dev, "notify complete, all subdevs registered\n");
+
+ /* Create links for every entity. */
+ list_for_each_entry(entity, &csi->entities, list) {
+ ret = sun6i_graph_build_one(csi, entity);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Create links for video node. */
+ ret = sun6i_graph_build_video(csi);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
+ if (ret < 0)
+ dev_err(csi->dev, "failed to register subdev nodes\n");
+
+ return media_device_register(&csi->media_dev);
+}
+
+static int sun6i_graph_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct sun6i_csi *csi =
+ container_of(notifier, struct sun6i_csi, notifier);
+ struct sun6i_graph_entity *entity;
+
+ /* Locate the entity corresponding to the bound subdev and store the
+ * subdev pointer.
+ */
+ list_for_each_entry(entity, &csi->entities, list) {
+ if (entity->node != subdev->dev->of_node)
+ continue;
+
+ if (entity->subdev) {
+ dev_err(csi->dev, "duplicate subdev for node %s\n",
+ entity->node->full_name);
+ return -EINVAL;
+ }
+
+ dev_dbg(csi->dev, "subdev %s bound\n", subdev->name);
+ entity->entity = &subdev->entity;
+ entity->subdev = subdev;
+ return 0;
+ }
+
+ dev_err(csi->dev, "no entity for subdev %s\n", subdev->name);
+ return -EINVAL;
+}
+
+static int sun6i_graph_parse_one(struct sun6i_csi *csi,
+ struct device_node *node)
+{
+ struct sun6i_graph_entity *entity;
+ struct device_node *remote;
+ struct device_node *ep = NULL;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "parsing node %s\n", node->full_name);
+
+ while (1) {
+ ep = of_graph_get_next_endpoint(node, ep);
+ if (ep == NULL)
+ break;
+
+ dev_dbg(csi->dev, "handling endpoint %s\n", ep->full_name);
+
+ remote = of_graph_get_remote_port_parent(ep);
+ if (remote == NULL) {
+ ret = -EINVAL;
+ break;
+ }
+
+ /* Skip entities that we have already processed. */
+ if (remote == csi->dev->of_node ||
+ sun6i_graph_find_entity(csi, remote)) {
+ of_node_put(remote);
+ continue;
+ }
+
+ entity = devm_kzalloc(csi->dev, sizeof(*entity), GFP_KERNEL);
+ if (entity == NULL) {
+ of_node_put(remote);
+ ret = -ENOMEM;
+ break;
+ }
+
+ entity->node = remote;
+ entity->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+ entity->asd.match.fwnode.fwnode = of_fwnode_handle(remote);
+ list_add_tail(&entity->list, &csi->entities);
+ csi->num_subdevs++;
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_parse(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entity;
+ int ret;
+
+ /*
+ * Walk the links to parse the full graph. Start by parsing the
+ * composite node and then parse entities in turn. The list_for_each
+ * loop will handle entities added at the end of the list while walking
+ * the links.
+ */
+ ret = sun6i_graph_parse_one(csi, csi->dev->of_node);
+ if (ret < 0)
+ return 0;
+
+ list_for_each_entry(entity, &csi->entities, list) {
+ ret = sun6i_graph_parse_one(csi, entity->node);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static void sun6i_graph_cleanup(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entityp;
+ struct sun6i_graph_entity *entity;
+
+ v4l2_async_notifier_unregister(&csi->notifier);
+
+ list_for_each_entry_safe(entity, entityp, &csi->entities, list) {
+ of_node_put(entity->node);
+ list_del(&entity->list);
+ }
+}
+
+static int sun6i_graph_init(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entity;
+ struct v4l2_async_subdev **subdevs = NULL;
+ unsigned int num_subdevs;
+ unsigned int i;
+ int ret;
+
+ /* Parse the graph to extract a list of subdevice DT nodes. */
+ ret = sun6i_graph_parse(csi);
+ if (ret < 0) {
+ dev_err(csi->dev, "graph parsing failed\n");
+ goto done;
+ }
+
+ if (!csi->num_subdevs) {
+ dev_err(csi->dev, "no subdev found in graph\n");
+ goto done;
+ }
+
+ /* Register the subdevices notifier. */
+ num_subdevs = csi->num_subdevs;
+ subdevs = devm_kzalloc(csi->dev, sizeof(*subdevs) * num_subdevs,
+ GFP_KERNEL);
+ if (subdevs == NULL) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ i = 0;
+ list_for_each_entry(entity, &csi->entities, list)
+ subdevs[i++] = &entity->asd;
+
+ csi->notifier.subdevs = subdevs;
+ csi->notifier.num_subdevs = num_subdevs;
+ csi->notifier.bound = sun6i_graph_notify_bound;
+ csi->notifier.complete = sun6i_graph_notify_complete;
+
+ ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);
+ if (ret < 0) {
+ dev_err(csi->dev, "notifier registration failed\n");
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret < 0)
+ sun6i_graph_cleanup(csi);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Media Controller and V4L2
+ */
+
+static void sun6i_csi_v4l2_cleanup(struct sun6i_csi *csi)
+{
+ v4l2_device_unregister(&csi->v4l2_dev);
+ media_device_unregister(&csi->media_dev);
+ media_device_cleanup(&csi->media_dev);
+}
+
+static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->media_dev.dev = csi->dev;
+ strlcpy(csi->media_dev.model, "Allwinner Video Capture Device",
+ sizeof(csi->media_dev.model));
+ csi->media_dev.hw_revision = 0;
+
+ media_device_init(&csi->media_dev);
+
+ csi->v4l2_dev.mdev = &csi->media_dev;
+ ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
+ if (ret < 0) {
+ dev_err(csi->dev, "V4L2 device registration failed (%d)\n",
+ ret);
+ media_device_cleanup(&csi->media_dev);
+ return ret;
+ }
+ return 0;
+}
+
+int sun6i_csi_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->num_subdevs = 0;
+ INIT_LIST_HEAD(&csi->entities);
+
+ ret = sun6i_csi_v4l2_init(csi);
+ if (ret < 0)
+ return ret;
+
+ ret = sun6i_video_init(&csi->video, csi, "sun6i-csi");
+ if (ret < 0)
+ goto v4l2_clean;
+
+ ret = sun6i_graph_init(csi);
+ if (ret < 0)
+ goto video_clean;
+
+ return 0;
+
+video_clean:
+ sun6i_video_cleanup(&csi->video);
+v4l2_clean:
+ sun6i_csi_v4l2_cleanup(csi);
+ return ret;
+}
+
+int sun6i_csi_cleanup(struct sun6i_csi *csi)
+{
+ sun6i_video_cleanup(&csi->video);
+ sun6i_graph_cleanup(csi);
+ sun6i_csi_v4l2_cleanup(csi);
+
+ return 0;
+}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sun6i-csi/sun6i_csi.h
new file mode 100644
index 0000000..167f6bc
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_H__
+#define __SUN6I_CSI_H__
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#include "sun6i_video.h"
+
+struct sun6i_csi;
+
+/**
+ * struct sun6i_csi_config - configs for sun6i csi
+ * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @code: media bus format code (MEDIA_BUS_FMT_*)
+ * @field: used interlacing type (enum v4l2_field)
+ * @width: frame width
+ * @height: frame height
+ */
+struct sun6i_csi_config {
+ u32 pixelformat;
+ u32 code;
+ u32 field;
+ u32 width;
+ u32 height;
+};
+
+struct sun6i_csi_ops {
+ int (*get_supported_pixformats)(struct sun6i_csi *csi,
+ const u32 **pixformats);
+ bool (*is_format_support)(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code);
+ int (*s_power)(struct sun6i_csi *csi, bool enable);
+ int (*update_config)(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config);
+ int (*update_buf_addr)(struct sun6i_csi *csi, dma_addr_t addr);
+ int (*s_stream)(struct sun6i_csi *csi, bool enable);
+};
+
+struct sun6i_csi {
+ struct device *dev;
+ struct v4l2_device v4l2_dev;
+ struct media_device media_dev;
+
+ struct list_head entities;
+ unsigned int num_subdevs;
+ struct v4l2_async_notifier notifier;
+
+ /* video port settings */
+ struct v4l2_fwnode_endpoint v4l2_ep;
+
+ struct sun6i_csi_config config;
+
+ struct sun6i_video video;
+
+ struct sun6i_csi_ops *ops;
+};
+
+int sun6i_csi_init(struct sun6i_csi *csi);
+int sun6i_csi_cleanup(struct sun6i_csi *csi);
+
+/**
+ * sun6i_csi_get_supported_pixformats() - get csi supported pixformats
+ * @csi: pointer to the csi
+ * @pixformats: supported pixformats return from csi
+ *
+ * @return the count of pixformats or error(< 0)
+ */
+static inline int
+sun6i_csi_get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (csi->ops != NULL && csi->ops->get_supported_pixformats != NULL)
+ return csi->ops->get_supported_pixformats(csi, pixformats);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_is_format_support() - check if the format supported by csi
+ * @csi: pointer to the csi
+ * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @mbus_code: media bus format code (MEDIA_BUS_FMT_*)
+ */
+static inline bool
+sun6i_csi_is_format_support(struct sun6i_csi *csi, u32 pixformat, u32 mbus_code)
+{
+ if (csi->ops != NULL && csi->ops->is_format_support != NULL)
+ return csi->ops->is_format_support(csi, pixformat, mbus_code);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_power() - power on/off the csi
+ * @csi: pointer to the csi
+ * @enable: on/off
+ */
+static inline int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable)
+{
+ if (csi->ops != NULL && csi->ops->s_power != NULL)
+ return csi->ops->s_power(csi, enable);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_config() - update the csi register setttings
+ * @csi: pointer to the csi
+ * @config: see struct sun6i_csi_config
+ */
+static inline int
+sun6i_csi_update_config(struct sun6i_csi *csi, struct sun6i_csi_config *config)
+{
+ if (csi->ops != NULL && csi->ops->update_config != NULL)
+ return csi->ops->update_config(csi, config);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_buf_addr() - update the csi frame buffer address
+ * @csi: pointer to the csi
+ * @addr: frame buffer's physical address
+ */
+static inline int sun6i_csi_update_buf_addr(struct sun6i_csi *csi,
+ dma_addr_t addr)
+{
+ if (csi->ops != NULL && csi->ops->update_buf_addr != NULL)
+ return csi->ops->update_buf_addr(csi, addr);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_stream() - start/stop csi streaming
+ * @csi: pointer to the csi
+ * @enable: start/stop
+ */
+static inline int sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable)
+{
+ if (csi->ops != NULL && csi->ops->s_stream != NULL)
+ return csi->ops->s_stream(csi, enable);
+
+ return -ENOIOCTLCMD;
+}
+
+static inline int v4l2_pixformat_get_bpp(unsigned int pixformat)
+{
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return 8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return 10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return 12;
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV422P:
+ return 16;
+ case V4L2_PIX_FMT_RGB24:
+ case V4L2_PIX_FMT_BGR24:
+ return 24;
+ case V4L2_PIX_FMT_RGB32:
+ case V4L2_PIX_FMT_BGR32:
+ return 32;
+ }
+
+ return 0;
+}
+
+#endif /* __SUN6I_CSI_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
new file mode 100644
index 0000000..f940daa
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_csi_v3s.h"
+
+#define MODULE_NAME "sun6i-csi"
+
+struct sun6i_csi_dev {
+ struct sun6i_csi csi;
+ struct device *dev;
+
+ struct regmap *regmap;
+ struct clk *clk_ahb;
+ struct clk *clk_mod;
+ struct clk *clk_ram;
+ struct reset_control *rstc_ahb;
+
+ int planar_offset[3];
+};
+
+static const u32 supported_pixformats[] = {
+ V4L2_PIX_FMT_SBGGR8,
+ V4L2_PIX_FMT_SGBRG8,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_PIX_FMT_SRGGB8,
+ V4L2_PIX_FMT_SBGGR10,
+ V4L2_PIX_FMT_SGBRG10,
+ V4L2_PIX_FMT_SGRBG10,
+ V4L2_PIX_FMT_SRGGB10,
+ V4L2_PIX_FMT_SBGGR12,
+ V4L2_PIX_FMT_SGBRG12,
+ V4L2_PIX_FMT_SGRBG12,
+ V4L2_PIX_FMT_SRGGB12,
+ V4L2_PIX_FMT_YUYV,
+ V4L2_PIX_FMT_YVYU,
+ V4L2_PIX_FMT_UYVY,
+ V4L2_PIX_FMT_VYUY,
+ V4L2_PIX_FMT_HM12,
+ V4L2_PIX_FMT_NV12,
+ V4L2_PIX_FMT_NV21,
+ V4L2_PIX_FMT_YUV420,
+ V4L2_PIX_FMT_YVU420,
+ V4L2_PIX_FMT_NV16,
+ V4L2_PIX_FMT_NV61,
+ V4L2_PIX_FMT_YUV422P,
+};
+
+static inline struct sun6i_csi_dev *sun6i_csi_to_dev(struct sun6i_csi *csi)
+{
+ return container_of(csi, struct sun6i_csi_dev, csi);
+}
+
+/* TODO add 10&12 bit YUV, RGB support */
+static bool __is_format_support(struct sun6i_csi_dev *sdev,
+ u32 fourcc, u32 mbus_code)
+{
+ /*
+ * Some video receiver have capability both 8bit and 16bit.
+ * Identify the media bus format from device tree.
+ */
+ if (((sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_PARALLEL
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_BT656)
+ && sdev->csi.v4l2_ep.bus.parallel.bus_width == 16)
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_CSI2) {
+ switch (fourcc) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ switch (fourcc) {
+ case V4L2_PIX_FMT_SBGGR8:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG8:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG8:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB8:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SBGGR10:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG10:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG10:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB10:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SBGGR12:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG12:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG12:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB12:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12)
+ return true;
+ break;
+
+ case V4L2_PIX_FMT_YUYV:
+ if (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_YVYU:
+ if (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ if (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_VYUY:
+ if (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8)
+ return true;
+ break;
+
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+static enum csi_input_fmt get_csi_input_format(u32 mbus_code, u32 pixformat)
+{
+ /* bayer */
+ if ((mbus_code & 0xF000) == 0x3000)
+ return CSI_INPUT_FORMAT_RAW;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return CSI_INPUT_FORMAT_RAW;
+ }
+
+ /* not support YUV420 input format yet */
+ return CSI_INPUT_FORMAT_YUV422;
+}
+
+static enum csi_output_fmt get_csi_output_format(u32 pixformat, u32 field)
+{
+ bool buf_interlaced = false;
+ if (field == V4L2_FIELD_INTERLACED
+ || field == V4L2_FIELD_INTERLACED_TB
+ || field == V4L2_FIELD_INTERLACED_BT)
+ buf_interlaced = true;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12;
+
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+
+ case V4L2_PIX_FMT_HM12:
+ return buf_interlaced ? CSI_FRAME_MB_YUV420 :
+ CSI_FIELD_MB_YUV420;
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 :
+ CSI_FIELD_UV_CB_YUV420;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 :
+ CSI_FIELD_PLANAR_YUV420;
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 :
+ CSI_FIELD_UV_CB_YUV422;
+ case V4L2_PIX_FMT_YUV422P:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 :
+ CSI_FIELD_PLANAR_YUV422;
+ }
+
+ return 0;
+}
+
+static enum csi_input_seq get_csi_input_seq(u32 mbus_code, u32 pixformat)
+{
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch(mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YUYV;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YVYU;
+ }
+ break;
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YVU420:
+ switch(mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YVYU;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YUYV;
+ }
+ break;
+ }
+
+ return CSI_INPUT_SEQ_YUYV;
+}
+
+#ifdef DEBUG
+static void sun6i_csi_dump_regs(struct sun6i_csi_dev *sdev)
+{
+ struct regmap *regmap = sdev->regmap;
+ u32 val;
+
+ regmap_read(regmap, CSI_EN_REG, &val);
+ printk("CSI_EN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_IF_CFG_REG, &val);
+ printk("CSI_IF_CFG_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CAP_REG, &val);
+ printk("CSI_CAP_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_SYNC_CNT_REG, &val);
+ printk("CSI_SYNC_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_FIFO_THRS_REG, &val);
+ printk("CSI_FIFO_THRS_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_PTN_LEN_REG, &val);
+ printk("CSI_PTN_LEN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_PTN_ADDR_REG, &val);
+ printk("CSI_PTN_ADDR_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_VER_REG, &val);
+ printk("CSI_VER_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_CFG_REG, &val);
+ printk("CSI_CH_CFG_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_SCALE_REG, &val);
+ printk("CSI_CH_SCALE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F0_BUFA_REG, &val);
+ printk("CSI_CH_F0_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F1_BUFA_REG, &val);
+ printk("CSI_CH_F1_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F2_BUFA_REG, &val);
+ printk("CSI_CH_F2_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_STA_REG, &val);
+ printk("CSI_CH_STA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_INT_EN_REG, &val);
+ printk("CSI_CH_INT_EN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &val);
+ printk("CSI_CH_INT_STA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FLD1_VSIZE_REG, &val);
+ printk("CSI_CH_FLD1_VSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_HSIZE_REG, &val);
+ printk("CSI_CH_HSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_VSIZE_REG, &val);
+ printk("CSI_CH_VSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_BUF_LEN_REG, &val);
+ printk("CSI_CH_BUF_LEN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FLIP_SIZE_REG, &val);
+ printk("CSI_CH_FLIP_SIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FRM_CLK_CNT_REG, &val);
+ printk("CSI_CH_FRM_CLK_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_ACC_ITNL_CLK_CNT_REG, &val);
+ printk("CSI_CH_ACC_ITNL_CLK_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FIFO_STAT_REG, &val);
+ printk("CSI_CH_FIFO_STAT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_PCLK_STAT_REG, &val);
+ printk("CSI_CH_PCLK_STAT_REG=0x%x\n", val);
+}
+#endif
+
+static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev)
+{
+ struct v4l2_fwnode_endpoint *endpoint = &sdev->csi.v4l2_ep;
+ unsigned char bus_width;
+ u32 flags;
+ u32 cfg;
+
+ bus_width = endpoint->bus.parallel.bus_width;
+
+ regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK |
+ CSI_IF_CFG_IF_DATA_WIDTH_MASK |
+ CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK |
+ CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK);
+
+ switch (endpoint->bus_type) {
+ case V4L2_MBUS_CSI2:
+ cfg |= CSI_IF_CFG_MIPI_IF_MIPI;
+ break;
+ case V4L2_MBUS_PARALLEL:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT :
+ CSI_IF_CFG_CSI_IF_YUV422_INTLV;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_VREF_POL_POSITIVE;
+ if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_HREF_POL_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ case V4L2_MBUS_BT656:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 :
+ CSI_IF_CFG_CSI_IF_BT656;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ default:
+ BUG();
+ break;
+ }
+
+ switch (bus_width) {
+ case 8:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT;
+ break;
+ case 10:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT;
+ break;
+ case 12:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT;
+ break;
+ default:
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_IF_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_format(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi *csi = &sdev->csi;
+ u32 cfg;
+ u32 val;
+
+ regmap_read(sdev->regmap, CSI_CH_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK |
+ CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN |
+ CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK |
+ CSI_CH_CFG_INPUT_SEQ_MASK);
+
+ val = get_csi_input_format(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_FMT(val);
+
+ val = get_csi_output_format(csi->config.code, csi->config.field);
+ cfg |= CSI_CH_CFG_OUTPUT_FMT(val);
+
+ val = get_csi_input_seq(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_SEQ(val);
+
+ if (csi->config.field == V4L2_FIELD_TOP)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0;
+ else if (csi->config.field == V4L2_FIELD_BOTTOM)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1;
+ else
+ cfg |= CSI_CH_CFG_FIELD_SEL_BOTH;
+
+ regmap_write(sdev->regmap, CSI_CH_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_window(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi_config *config = &sdev->csi.config;
+ u32 bytesperline_y;
+ u32 bytesperline_c;
+ int *planar_offset = sdev->planar_offset;
+
+ regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
+ CSI_CH_HSIZE_HOR_LEN(config->width) |
+ CSI_CH_HSIZE_HOR_START(0));
+ regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
+ CSI_CH_VSIZE_VER_LEN(config->height) |
+ CSI_CH_VSIZE_VER_START(0));
+
+ planar_offset[0] = 0;
+ switch(config->pixelformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = -1;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width / 2;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * config->height / 2;
+ break;
+ case V4L2_PIX_FMT_YUV422P:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width / 2;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * config->height;
+ break;
+ default: /* raw */
+ bytesperline_y = (v4l2_pixformat_get_bpp(config->pixelformat) *
+ config->width) / 8;
+ bytesperline_c = 0;
+ planar_offset[1] = -1;
+ planar_offset[2] = -1;
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_CH_BUF_LEN_REG,
+ CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) |
+ CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y));
+}
+
+static int get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (pixformats != NULL)
+ *pixformats = supported_pixformats;
+
+ return ARRAY_SIZE(supported_pixformats);
+}
+
+static bool is_format_support(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ return __is_format_support(sdev, pixformat, mbus_code);
+}
+
+static int set_power(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+ int ret;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+
+ clk_disable_unprepare(sdev->clk_ram);
+ clk_disable_unprepare(sdev->clk_mod);
+ clk_disable_unprepare(sdev->clk_ahb);
+ reset_control_assert(sdev->rstc_ahb);
+ return 0;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ahb);
+ if (ret) {
+ dev_err(sdev->dev, "Enable ahb clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_mod);
+ if (ret) {
+ dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ram);
+ if (ret) {
+ dev_err(sdev->dev, "Enable clk_dram_csi clk err %d\n", ret);
+ return ret;
+ }
+
+ if (!IS_ERR_OR_NULL(sdev->rstc_ahb)) {
+ ret = reset_control_deassert(sdev->rstc_ahb);
+ if (ret) {
+ dev_err(sdev->dev, "reset err %d\n", ret);
+ return ret;
+ }
+ }
+
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
+
+ return 0;
+}
+
+static int update_config(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ if (config == NULL)
+ return -EINVAL;
+
+ memcpy(&csi->config, config, sizeof(csi->config));
+
+ sun6i_csi_setup_bus(sdev);
+ sun6i_csi_set_format(sdev);
+ sun6i_csi_set_window(sdev);
+
+ return 0;
+}
+
+static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ /* transform physical address to bus address */
+ dma_addr_t bus_addr = addr - 0x40000000;
+
+ regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
+ (bus_addr + sdev->planar_offset[0]) >> 2);
+ if (sdev->planar_offset[1] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
+ (bus_addr + sdev->planar_offset[1]) >> 2);
+ if (sdev->planar_offset[2] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
+ (bus_addr + sdev->planar_offset[2]) >> 2);
+
+ return 0;
+}
+
+static int set_stream(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0);
+ regmap_write(regmap, CSI_CH_INT_EN_REG, 0);
+ return 0;
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF);
+ regmap_write(regmap, CSI_CH_INT_EN_REG,
+ CSI_CH_INT_EN_HB_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO2_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO1_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO0_OF_INT_EN |
+ CSI_CH_INT_EN_FD_INT_EN |
+ CSI_CH_INT_EN_CD_INT_EN);
+
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON,
+ CSI_CAP_CH0_VCAP_ON);
+
+ return 0;
+}
+
+static struct sun6i_csi_ops csi_ops = {
+ .get_supported_pixformats = get_supported_pixformats,
+ .is_format_support = is_format_support,
+ .s_power = set_power,
+ .update_config = update_config,
+ .update_buf_addr = update_buf_addr,
+ .s_stream = set_stream,
+};
+
+static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
+{
+ struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
+ struct regmap *regmap = sdev->regmap;
+ u32 status;
+
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
+
+ if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
+ (status & CSI_CH_INT_STA_HB_OF_PD)) {
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
+ CSI_EN_CSI_EN);
+ return IRQ_HANDLED;
+ }
+
+ if (status & CSI_CH_INT_STA_FD_PD) {
+ sun6i_video_frame_done(&sdev->csi.video);
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config sun6i_csi_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x1000,
+};
+
+static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+ void __iomem *io_base;
+ int ret;
+ int irq;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ io_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(io_base))
+ return PTR_ERR(io_base);
+
+ sdev->regmap = devm_regmap_init_mmio(&pdev->dev, io_base,
+ &sun6i_csi_regmap_config);
+ if (IS_ERR(sdev->regmap)) {
+ dev_err(&pdev->dev, "Failed to init register map\n");
+ return PTR_ERR(sdev->regmap);
+ }
+
+ sdev->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
+ if (IS_ERR(sdev->clk_ahb)) {
+ dev_err(&pdev->dev, "Unable to acquire ahb clock\n");
+ return PTR_ERR(sdev->clk_ahb);
+ }
+
+ sdev->clk_mod = devm_clk_get(&pdev->dev, "mod");
+ if (IS_ERR(sdev->clk_mod)) {
+ dev_err(&pdev->dev, "Unable to acquire csi clock\n");
+ return PTR_ERR(sdev->clk_mod);
+ }
+
+ sdev->clk_ram = devm_clk_get(&pdev->dev, "ram");
+ if (IS_ERR(sdev->clk_ram)) {
+ dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n");
+ return PTR_ERR(sdev->clk_ram);
+ }
+
+ sdev->rstc_ahb = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(sdev->rstc_ahb)) {
+ dev_err(&pdev->dev, "Cannot get reset controller\n");
+ return PTR_ERR(sdev->rstc_ahb);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "No csi IRQ specified\n");
+ ret = -ENXIO;
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, sun6i_csi_isr, 0, MODULE_NAME,
+ sdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot request csi IRQ\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int sun6i_csi_probe(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev;
+ int ret;
+
+ sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
+ if (!sdev)
+ return -ENOMEM;
+
+ sdev->dev = &pdev->dev;
+
+ ret = sun6i_csi_resource_request(sdev, pdev);
+ if (ret)
+ return ret;
+
+ sdev->csi.dev = &pdev->dev;
+ sdev->csi.ops = &csi_ops;
+ ret = sun6i_csi_init(&sdev->csi);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, sdev);
+
+ return 0;
+}
+
+static int sun6i_csi_remove(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev = platform_get_drvdata(pdev);
+
+ sun6i_csi_cleanup(&sdev->csi);
+
+ return 0;
+}
+
+static const struct of_device_id sun6i_csi_of_match[] = {
+ { .compatible = "allwinner,sun8i-v3s-csi", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sun6i_csi_of_match);
+
+static struct platform_driver sun6i_csi_platform_driver = {
+ .probe = sun6i_csi_probe,
+ .remove = sun6i_csi_remove,
+ .driver = {
+ .name = MODULE_NAME,
+ .of_match_table = of_match_ptr(sun6i_csi_of_match),
+ },
+};
+module_platform_driver(sun6i_csi_platform_driver);
+
+MODULE_DESCRIPTION("Allwinner V3s Camera Sensor Interface driver");
+MODULE_AUTHOR("Yong Deng <yong.deng@magewell.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
new file mode 100644
index 0000000..6c87b74
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_V3S_H__
+#define __SUN6I_CSI_V3S_H__
+
+#include <linux/kernel.h>
+
+#define CSI_EN_REG 0x0
+#define CSI_EN_VER_EN BIT(30)
+#define CSI_EN_CSI_EN BIT(0)
+
+#define CSI_IF_CFG_REG 0x4
+#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21)
+#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_FPS_DS_EN BIT(20)
+#define CSI_IF_CFG_FIELD_MASK BIT(19)
+#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_VREF_POL_MASK BIT(18)
+#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_MASK BIT(17)
+#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_MASK BIT(16)
+#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8)
+#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_MIPI_IF_MASK BIT(7)
+#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7)
+#define CSI_IF_CFG_MIPI_IF_MIPI (1 << 7)
+#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0)
+#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+
+#define CSI_CAP_REG 0x8
+#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2)
+#define CSI_CAP_CH0_CAP_MASK(count) ((count << 2) & CSI_CAP_CH0_CAP_MASK_MASK)
+#define CSI_CAP_CH0_VCAP_ON BIT(1)
+#define CSI_CAP_CH0_SCAP_ON BIT(0)
+
+#define CSI_SYNC_CNT_REG 0xc
+#define CSI_FIFO_THRS_REG 0x10
+#define CSI_BT656_HEAD_CFG_REG 0x14
+#define CSI_PTN_LEN_REG 0x30
+#define CSI_PTN_ADDR_REG 0x34
+#define CSI_VER_REG 0x3c
+
+#define CSI_CH_CFG_REG 0x44
+#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20)
+#define CSI_CH_CFG_INPUT_FMT(fmt) ((fmt << 20) & CSI_CH_CFG_INPUT_FMT_MASK)
+#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16)
+#define CSI_CH_CFG_OUTPUT_FMT(fmt) ((fmt << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK)
+#define CSI_CH_CFG_VFLIP_EN BIT(13)
+#define CSI_CH_CFG_HFLIP_EN BIT(12)
+#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10)
+#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8)
+#define CSI_CH_CFG_INPUT_SEQ(seq) ((seq << 8) & CSI_CH_CFG_INPUT_SEQ_MASK)
+
+#define CSI_CH_SCALE_REG 0x4c
+#define CSI_CH_SCALE_QUART_EN BIT(0)
+
+#define CSI_CH_F0_BUFA_REG 0x50
+
+#define CSI_CH_F1_BUFA_REG 0x58
+
+#define CSI_CH_F2_BUFA_REG 0x60
+
+#define CSI_CH_STA_REG 0x6c
+#define CSI_CH_STA_FIELD_STA_MASK BIT(2)
+#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_VCAP_STA BIT(1)
+#define CSI_CH_STA_SCAP_STA BIT(0)
+
+#define CSI_CH_INT_EN_REG 0x70
+#define CSI_CH_INT_EN_VS_INT_EN BIT(7)
+#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6)
+#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5)
+#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4)
+#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3)
+#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2)
+#define CSI_CH_INT_EN_FD_INT_EN BIT(1)
+#define CSI_CH_INT_EN_CD_INT_EN BIT(0)
+
+#define CSI_CH_INT_STA_REG 0x74
+#define CSI_CH_INT_STA_VS_PD BIT(7)
+#define CSI_CH_INT_STA_HB_OF_PD BIT(6)
+#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5)
+#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4)
+#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3)
+#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2)
+#define CSI_CH_INT_STA_FD_PD BIT(1)
+#define CSI_CH_INT_STA_CD_PD BIT(0)
+
+#define CSI_CH_FLD1_VSIZE_REG 0x74
+
+#define CSI_CH_HSIZE_REG 0x80
+#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_HSIZE_HOR_LEN(len) ((len << 16) & CSI_CH_HSIZE_HOR_LEN_MASK)
+#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0)
+#define CSI_CH_HSIZE_HOR_START(start) ((start << 0) & CSI_CH_HSIZE_HOR_START_MASK)
+
+#define CSI_CH_VSIZE_REG 0x84
+#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_VSIZE_VER_LEN(len) ((len << 16) & CSI_CH_VSIZE_VER_LEN_MASK)
+#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0)
+#define CSI_CH_VSIZE_VER_START(start) ((start << 0) & CSI_CH_VSIZE_VER_START_MASK)
+
+#define CSI_CH_BUF_LEN_REG 0x88
+#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16)
+#define CSI_CH_BUF_LEN_BUF_LEN_C(len) ((len << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) ((len << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK)
+
+#define CSI_CH_FLIP_SIZE_REG 0x8c
+#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_FLIP_SIZE_VER_LEN(len) ((len << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK)
+#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0)
+#define CSI_CH_FLIP_SIZE_VALID_LEN(len) ((len << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK)
+
+#define CSI_CH_FRM_CLK_CNT_REG 0x90
+#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94
+#define CSI_CH_FIFO_STAT_REG 0x98
+#define CSI_CH_PCLK_STAT_REG 0x9c
+
+/*
+ * csi input data format
+ */
+enum csi_input_fmt
+{
+ CSI_INPUT_FORMAT_RAW = 0,
+ CSI_INPUT_FORMAT_YUV422 = 3,
+ CSI_INPUT_FORMAT_YUV420 = 4,
+};
+
+/*
+ * csi output data format
+ */
+enum csi_output_fmt
+{
+ /* only when input format is RAW */
+ CSI_FIELD_RAW_8 = 0,
+ CSI_FIELD_RAW_10 = 1,
+ CSI_FIELD_RAW_12 = 2,
+ CSI_FIELD_RGB565 = 4,
+ CSI_FIELD_RGB888 = 5,
+ CSI_FIELD_PRGB888 = 6,
+ CSI_FRAME_RAW_8 = 8,
+ CSI_FRAME_RAW_10 = 9,
+ CSI_FRAME_RAW_12 = 10,
+ CSI_FRAME_RGB565 = 12,
+ CSI_FRAME_RGB888 = 13,
+ CSI_FRAME_PRGB888 = 14,
+
+ /* only when input format is YUV422/YUV420 */
+ CSI_FIELD_PLANAR_YUV422 = 0,
+ CSI_FIELD_PLANAR_YUV420 = 1,
+ CSI_FRAME_PLANAR_YUV420 = 2,
+ CSI_FRAME_PLANAR_YUV422 = 3,
+ CSI_FIELD_UV_CB_YUV422 = 4,
+ CSI_FIELD_UV_CB_YUV420 = 5,
+ CSI_FRAME_UV_CB_YUV420 = 6,
+ CSI_FRAME_UV_CB_YUV422 = 7,
+ CSI_FIELD_MB_YUV422 = 8,
+ CSI_FIELD_MB_YUV420 = 9,
+ CSI_FRAME_MB_YUV420 = 10,
+ CSI_FRAME_MB_YUV422 = 11,
+ CSI_FIELD_UV_CB_YUV422_10 = 12,
+ CSI_FIELD_UV_CB_YUV420_10 = 13,
+};
+
+/*
+ * csi YUV input data sequence
+ */
+enum csi_input_seq
+{
+ /* only when input format is YUV422 */
+ CSI_INPUT_SEQ_YUYV = 0,
+ CSI_INPUT_SEQ_YVYU,
+ CSI_INPUT_SEQ_UYVY,
+ CSI_INPUT_SEQ_VYUY,
+};
+
+#endif /* __SUN6I_CSI_V3S_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.c b/drivers/media/platform/sun6i-csi/sun6i_video.c
new file mode 100644
index 0000000..0c5dbd2
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_video.c
@@ -0,0 +1,663 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/of.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_video.h"
+
+struct sun6i_csi_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+
+ dma_addr_t dma_addr;
+};
+
+static struct sun6i_csi_format *
+find_format_by_fourcc(struct sun6i_video *video, unsigned int fourcc)
+{
+ unsigned int num_formats = video->num_formats;
+ struct sun6i_csi_format *fmt;
+ unsigned int i;
+
+ for (i = 0; i < num_formats; i++) {
+ fmt = &video->formats[i];
+ if (fmt->fourcc == fourcc)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+static struct v4l2_subdev *
+sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad)
+{
+ struct media_pad *remote;
+
+ remote = media_entity_remote_pad(&video->pad);
+
+ if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
+ return NULL;
+
+ if (pad)
+ *pad = remote->index;
+
+ return media_entity_to_v4l2_subdev(remote->entity);
+}
+
+static int sun6i_video_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned int size = video->fmt.fmt.pix.sizeimage;
+
+ if (*nplanes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static int sun6i_video_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long size = video->fmt.fmt.pix.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+
+ buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ vbuf->field = video->fmt.fmt.pix.field;
+
+ return 0;
+}
+
+static int sun6i_pipeline_set_stream(struct sun6i_video *video, bool enable)
+{
+ struct media_entity *entity;
+ struct media_pad *pad;
+ struct v4l2_subdev *subdev;
+ int ret;
+
+ entity = &video->vdev.entity;
+ while (1) {
+ pad = &entity->pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+
+ pad = media_entity_remote_pad(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+
+ entity = pad->entity;
+ subdev = media_entity_to_v4l2_subdev(entity);
+
+ ret = v4l2_subdev_call(subdev, video, s_stream, enable);
+ if (enable && ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ struct sun6i_csi_buffer *buf;
+ struct sun6i_csi_config config;
+ unsigned long flags;
+ int ret;
+
+ video->sequence = 0;
+
+ ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe);
+ if (ret < 0)
+ goto err_start_pipeline;
+
+ ret = sun6i_pipeline_set_stream(video, true);
+ if (ret < 0)
+ goto err_start_stream;
+
+ config.pixelformat = video->fmt.fmt.pix.pixelformat;
+ config.code = video->current_fmt->mbus_code;
+ config.field = video->fmt.fmt.pix.field;
+ config.width = video->fmt.fmt.pix.width;
+ config.height = video->fmt.fmt.pix.height;
+
+ ret = sun6i_csi_update_config(video->csi, &config);
+ if (ret < 0)
+ goto err_update_config;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ video->cur_frm = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ list_del(&video->cur_frm->list);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ ret = sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ if (ret < 0)
+ goto err_update_addr;
+
+ ret = sun6i_csi_set_stream(video->csi, true);
+ if (ret < 0)
+ goto err_csi_stream;
+
+ return 0;
+
+err_csi_stream:
+err_update_addr:
+err_update_config:
+ sun6i_pipeline_set_stream(video, false);
+err_start_stream:
+ media_pipeline_stop(&video->vdev.entity);
+err_start_pipeline:
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ return ret;
+}
+
+static void sun6i_video_stop_streaming(struct vb2_queue *vq)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned long flags;
+ struct sun6i_csi_buffer *buf;
+
+ sun6i_pipeline_set_stream(video, false);
+
+ sun6i_csi_set_stream(video->csi, false);
+
+ media_pipeline_stop(&video->vdev.entity);
+
+ /* Release all active buffers */
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ if (unlikely(video->cur_frm)) {
+ vb2_buffer_done(&video->cur_frm->vb.vb2_buf,
+ VB2_BUF_STATE_ERROR);
+ video->cur_frm = NULL;
+ }
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+static void sun6i_video_buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long flags;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ if (!video->cur_frm && list_empty(&video->dma_queue) &&
+ vb2_is_streaming(vb->vb2_queue)) {
+ video->cur_frm = buf;
+ sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ sun6i_csi_set_stream(video->csi, 1);
+ } else
+ list_add_tail(&buf->list, &video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+void sun6i_video_frame_done(struct sun6i_video *video)
+{
+ spin_lock(&video->dma_queue_lock);
+
+ if (video->cur_frm) {
+ struct vb2_v4l2_buffer *vbuf = &video->cur_frm->vb;
+ struct vb2_buffer *vb = &vbuf->vb2_buf;
+
+ vb->timestamp = ktime_get_ns();
+ vbuf->sequence = video->sequence++;
+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+ video->cur_frm = NULL;
+ }
+
+ if (!list_empty(&video->dma_queue)
+ && vb2_is_streaming(&video->vb2_vidq)) {
+ video->cur_frm = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ list_del(&video->cur_frm->list);
+ sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ } else
+ sun6i_csi_set_stream(video->csi, 0);
+
+ spin_unlock(&video->dma_queue_lock);
+}
+
+static struct vb2_ops sun6i_csi_vb2_ops = {
+ .queue_setup = sun6i_video_queue_setup,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .buf_prepare = sun6i_video_buffer_prepare,
+ .start_streaming = sun6i_video_start_streaming,
+ .stop_streaming = sun6i_video_stop_streaming,
+ .buf_queue = sun6i_video_buffer_queue,
+};
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ strlcpy(cap->driver, "sun6i-video", sizeof(cap->driver));
+ strlcpy(cap->card, video->vdev.name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ video->csi->dev->of_node->name);
+
+ return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ u32 index = f->index;
+
+ if (index >= video->num_formats)
+ return -EINVAL;
+
+ f->pixelformat = video->formats[index].fourcc;
+
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ *fmt = video->fmt;
+
+ return 0;
+}
+
+static int sun6i_video_try_fmt(struct sun6i_video *video, struct v4l2_format *f,
+ struct sun6i_csi_format **current_fmt)
+{
+ struct sun6i_csi_format *csi_fmt;
+ struct v4l2_pix_format *pixfmt = &f->fmt.pix;
+ struct v4l2_subdev_format format;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ csi_fmt = find_format_by_fourcc(video, pixfmt->pixelformat);
+ if (csi_fmt == NULL)
+ return -EINVAL;
+
+ format.pad = pad;
+ format.which = V4L2_SUBDEV_FORMAT_TRY;
+ v4l2_fill_mbus_format(&format.format, pixfmt, csi_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format);
+ if (ret)
+ return ret;
+
+ v4l2_fill_pix_format(pixfmt, &format.format);
+
+ pixfmt->bytesperline = (pixfmt->width * csi_fmt->bpp) >> 3;
+ pixfmt->sizeimage = (pixfmt->width * csi_fmt->bpp * pixfmt->height) / 8;
+
+ if (current_fmt)
+ *current_fmt = csi_fmt;
+
+ return 0;
+}
+
+static int sun6i_video_set_fmt(struct sun6i_video *video, struct v4l2_format *f)
+{
+ struct v4l2_subdev_format format;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ ret = sun6i_video_try_fmt(video, f, ¤t_fmt);
+ if (ret)
+ return ret;
+
+ format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ v4l2_fill_mbus_format(&format.format, &f->fmt.pix,
+ current_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format);
+ if (ret < 0)
+ return ret;
+
+ video->fmt = *f;
+ video->current_fmt = current_fmt;
+
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ if (vb2_is_streaming(&video->vb2_vidq))
+ return -EBUSY;
+
+ return sun6i_video_set_fmt(video, f);
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ return sun6i_video_try_fmt(video, f, NULL);
+}
+
+static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 file operations
+ */
+static int sun6i_video_open(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ struct v4l2_format format;
+ int ret;
+
+ if (mutex_lock_interruptible(&video->lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto unlock;
+
+ ret = v4l2_pipeline_pm_use(&video->vdev.entity, 1);
+ if (ret < 0)
+ goto fh_release;
+
+ if (!v4l2_fh_is_singular_file(file))
+ goto unlock;
+
+ ret = sun6i_csi_set_power(video->csi, true);
+ if (ret < 0)
+ goto fh_release;
+
+ /* setup default format */
+ if (video->num_formats > 0) {
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ format.fmt.pix.width = 1280;
+ format.fmt.pix.height = 720;
+ format.fmt.pix.pixelformat = video->formats[0].fourcc;
+ sun6i_video_set_fmt(video, &format);
+ }
+
+ mutex_unlock(&video->lock);
+ return 0;
+
+fh_release:
+ v4l2_fh_release(file);
+unlock:
+ mutex_unlock(&video->lock);
+ return ret;
+}
+
+static int sun6i_video_close(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ bool last_fh;
+
+ mutex_lock(&video->lock);
+
+ last_fh = v4l2_fh_is_singular_file(file);
+
+ _vb2_fop_release(file, NULL);
+
+ v4l2_pipeline_pm_use(&video->vdev.entity, 0);
+
+ if (last_fh)
+ sun6i_csi_set_power(video->csi, false);
+
+ mutex_unlock(&video->lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations sun6i_video_fops = {
+ .owner = THIS_MODULE,
+ .open = sun6i_video_open,
+ .release = sun6i_video_close,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+ .poll = vb2_fop_poll
+};
+
+/* -----------------------------------------------------------------------------
+ * Media Operations
+ */
+static int sun6i_video_formats_init(struct sun6i_video *video)
+{
+ struct v4l2_subdev_mbus_code_enum mbus_code = { 0 };
+ struct sun6i_csi *csi = video->csi;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ const u32 *pixformats;
+ int pixformat_count = 0;
+ u32 subdev_codes[32]; /* subdev format codes, 32 should be enough */
+ int codes_count = 0;
+ int num_fmts = 0;
+ int i, j;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ /* Get supported pixformats of CSI */
+ pixformat_count = sun6i_csi_get_supported_pixformats(csi, &pixformats);
+ if (pixformat_count <= 0)
+ return -ENXIO;
+
+ /* Get subdev formats codes */
+ mbus_code.pad = pad;
+ mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL,
+ &mbus_code)) {
+ subdev_codes[codes_count] = mbus_code.code;
+ codes_count++;
+ mbus_code.index++;
+ }
+
+ if (!codes_count)
+ return -ENXIO;
+
+ /* Get supported formats count */
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ mbus_code.code)) {
+ continue;
+ }
+ num_fmts++;
+ }
+ }
+
+ if (!num_fmts)
+ return -ENXIO;
+
+ video->num_formats = num_fmts;
+ video->formats = devm_kcalloc(video->csi->dev, num_fmts,
+ sizeof(struct sun6i_csi_format), GFP_KERNEL);
+ if (!video->formats) {
+ dev_err(video->csi->dev, "could not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ /* Get supported formats */
+ num_fmts = 0;
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ mbus_code.code)) {
+ continue;
+ }
+
+ video->formats[num_fmts].fourcc = pixformats[j];
+ video->formats[num_fmts].mbus_code =
+ mbus_code.code;
+ video->formats[num_fmts].bpp =
+ v4l2_pixformat_get_bpp(pixformats[j]);
+ num_fmts++;
+ }
+ }
+
+ return 0;
+}
+
+static int sun6i_video_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct video_device *vdev = media_entity_to_video_device(entity);
+ struct sun6i_video *video = video_get_drvdata(vdev);
+
+ if (WARN_ON(video == NULL))
+ return 0;
+
+ return sun6i_video_formats_init(video);
+}
+
+static const struct media_entity_operations sun6i_video_media_ops = {
+ .link_setup = sun6i_video_link_setup,
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name)
+{
+ struct video_device *vdev = &video->vdev;
+ struct vb2_queue *vidq = &video->vb2_vidq;
+ int ret;
+
+ video->csi = csi;
+
+ /* Initialize the media entity... */
+ video->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
+ vdev->entity.ops = &sun6i_video_media_ops;
+ ret = media_entity_pads_init(&vdev->entity, 1, &video->pad);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&video->lock);
+
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_lock_init(&video->dma_queue_lock);
+
+ video->cur_frm = NULL;
+ video->sequence = 0;
+ video->num_formats = 0;
+
+ /* Initialize videobuf2 queue */
+ vidq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ vidq->io_modes = VB2_MMAP | VB2_DMABUF;
+ vidq->drv_priv = video;
+ vidq->buf_struct_size = sizeof(struct sun6i_csi_buffer);
+ vidq->ops = &sun6i_csi_vb2_ops;
+ vidq->mem_ops = &vb2_dma_contig_memops;
+ vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ vidq->lock = &video->lock;
+ vidq->min_buffers_needed = 1;
+ vidq->dev = csi->dev;
+
+ ret = vb2_queue_init(vidq);
+ if (ret) {
+ v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret);
+ goto error;
+ }
+
+ /* Register video device */
+ strlcpy(vdev->name, name, sizeof(vdev->name));
+ vdev->release = video_device_release_empty;
+ vdev->fops = &sun6i_video_fops;
+ vdev->ioctl_ops = &sun6i_video_ioctl_ops;
+ vdev->vfl_type = VFL_TYPE_GRABBER;
+ vdev->vfl_dir = VFL_DIR_RX;
+ vdev->v4l2_dev = &csi->v4l2_dev;
+ vdev->queue = vidq;
+ vdev->lock = &video->lock;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+ video_set_drvdata(vdev, video);
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret < 0) {
+ v4l2_err(&csi->v4l2_dev,
+ "video_register_device failed: %d\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ sun6i_video_cleanup(video);
+ return ret;
+}
+
+void sun6i_video_cleanup(struct sun6i_video *video)
+{
+ if (video_is_registered(&video->vdev))
+ video_unregister_device(&video->vdev);
+
+ media_entity_cleanup(&video->vdev.entity);
+}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.h b/drivers/media/platform/sun6i-csi/sun6i_video.h
new file mode 100644
index 0000000..14eac6e
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_video.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_VIDEO_H__
+#define __SUN6I_VIDEO_H__
+
+#include <media/v4l2-dev.h>
+#include <media/videobuf2-core.h>
+
+/*
+ * struct sun6i_csi_format - CSI media bus format information
+ * @fourcc: Fourcc code for this format
+ * @mbus_code: V4L2 media bus format code.
+ * @bpp: Bytes per pixel (when stored in memory)
+ */
+struct sun6i_csi_format {
+ u32 fourcc;
+ u32 mbus_code;
+ u8 bpp;
+};
+
+struct sun6i_csi;
+
+struct sun6i_video {
+ struct video_device vdev;
+ struct media_pad pad;
+ struct sun6i_csi *csi;
+
+ struct mutex lock;
+
+ struct vb2_queue vb2_vidq;
+ spinlock_t dma_queue_lock;
+ struct list_head dma_queue;
+
+ struct sun6i_csi_buffer *cur_frm;
+ unsigned int sequence;
+
+ struct sun6i_csi_format *formats;
+ unsigned int num_formats;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_format fmt;
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name);
+void sun6i_video_cleanup(struct sun6i_video *video);
+
+void sun6i_video_frame_done(struct sun6i_video *video);
+
+#endif /* __SUN6I_VIDEO_H__ */
--
1.8.3.1
^ permalink raw reply related [flat|nested] 56+ messages in thread
* [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
2017-07-27 3:51 [PATCH v2 0/3] Initial Allwinner V3s CSI Support Yong Deng
2017-07-27 3:51 ` [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI Yong Deng
@ 2017-07-27 3:51 ` Yong Deng
[not found] ` <1501127474-40895-1-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
2 siblings, 0 replies; 56+ messages in thread
From: Yong Deng @ 2017-07-27 3:51 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Maxime Ripard,
Chen-Yu Tsai, Greg Kroah-Hartman, David S. Miller, Hans Verkuil,
Arnd Bergmann, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Benoit Parrot, Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media, devicetree, linux-arm-kernel
Cc: Yong Deng
Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
and CSI1 is used for parallel interface. This is not documented in
datasheet but by testing and guess.
This patch implement a v4l2 framework driver for it.
Currently, the driver only support the parallel interface. MIPI-CSI2,
ISP's support are not included in this patch.
Signed-off-by: Yong Deng <yong.deng@magewell.com>
---
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/sun6i-csi/Kconfig | 9 +
drivers/media/platform/sun6i-csi/Makefile | 3 +
drivers/media/platform/sun6i-csi/sun6i_csi.c | 545 +++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi.h | 203 ++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c | 827 +++++++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h | 206 ++++++
drivers/media/platform/sun6i-csi/sun6i_video.c | 663 ++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_video.h | 61 ++
10 files changed, 2520 insertions(+)
create mode 100644 drivers/media/platform/sun6i-csi/Kconfig
create mode 100644 drivers/media/platform/sun6i-csi/Makefile
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.h
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.h
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 0c741d1..8371a87 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -143,6 +143,7 @@ source "drivers/media/platform/am437x/Kconfig"
source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/sun6i-csi/Kconfig"
config VIDEO_TI_CAL
tristate "TI CAL (Camera Adaptation Layer) driver"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 9beadc7..fb2459c 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -86,3 +86,5 @@ obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/
obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk-jpeg/
obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi/
diff --git a/drivers/media/platform/sun6i-csi/Kconfig b/drivers/media/platform/sun6i-csi/Kconfig
new file mode 100644
index 0000000..314188a
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Kconfig
@@ -0,0 +1,9 @@
+config VIDEO_SUN6I_CSI
+ tristate "Allwinner V3s Camera Sensor Interface driver"
+ depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select REGMAP_MMIO
+ select V4L2_FWNODE
+ ---help---
+ Support for the Allwinner Camera Sensor Interface Controller on V3s.
diff --git a/drivers/media/platform/sun6i-csi/Makefile b/drivers/media/platform/sun6i-csi/Makefile
new file mode 100644
index 0000000..a9b527b
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Makefile
@@ -0,0 +1,3 @@
+sun6i-csi-y += sun6i_csi.o sun6i_video.o sun6i_csi_v3s.o
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sun6i-csi/sun6i_csi.c
new file mode 100644
index 0000000..7a4bf53
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.c
@@ -0,0 +1,545 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing),
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * Based on drivers/media/platform/xilinx/xilinx-vipp.c
+ * Copyright (C) 2013-2015 Ideas on Board
+ * Copyright (C) 2013-2015 Xilinx, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/of_graph.h>
+
+#include "sun6i_csi.h"
+
+/*
+ * struct sun6i_graph_entity - Entity in the video graph
+ * @list: list entry in a graph entities list
+ * @node: the entity's DT node
+ * @entity: media entity, from the corresponding V4L2 subdev
+ * @asd: subdev asynchronous registration information
+ * @subdev: V4L2 subdev
+ */
+struct sun6i_graph_entity {
+ struct list_head list;
+ struct device_node *node;
+ struct media_entity *entity;
+
+ struct v4l2_async_subdev asd;
+ struct v4l2_subdev *subdev;
+};
+
+/* -----------------------------------------------------------------------------
+ * Graph Management
+ */
+
+static struct sun6i_graph_entity *
+sun6i_graph_find_entity(struct sun6i_csi *csi,
+ const struct device_node *node)
+{
+ struct sun6i_graph_entity *entity;
+
+ list_for_each_entry(entity, &csi->entities, list) {
+ if (entity->node == node)
+ return entity;
+ }
+
+ return NULL;
+}
+
+static int sun6i_graph_build_one(struct sun6i_csi *csi,
+ struct sun6i_graph_entity *entity)
+{
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ struct media_entity *local = entity->entity;
+ struct media_entity *remote;
+ struct media_pad *local_pad;
+ struct media_pad *remote_pad;
+ struct sun6i_graph_entity *ent;
+ struct v4l2_fwnode_link link;
+ struct device_node *ep = NULL;
+ struct device_node *next;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "creating links for entity %s\n", local->name);
+
+ while (1) {
+ /* Get the next endpoint and parse its link. */
+ next = of_graph_get_next_endpoint(entity->node, ep);
+ if (next == NULL)
+ break;
+
+ of_node_put(ep);
+ ep = next;
+
+ dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
+
+ ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to parse link for %s\n",
+ ep->full_name);
+ continue;
+ }
+
+ /* Skip sink ports, they will be processed from the other end of
+ * the link.
+ */
+ if (link.local_port >= local->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.local_port,
+ to_of_node(link.local_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ local_pad = &local->pads[link.local_port];
+
+ if (local_pad->flags & MEDIA_PAD_FL_SINK) {
+ dev_dbg(csi->dev, "skipping sink port %s:%u\n",
+ to_of_node(link.local_node)->full_name,
+ link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* Skip video node, they will be processed separately. */
+ if (link.remote_node == of_fwnode_handle(csi->dev->of_node)) {
+ dev_dbg(csi->dev, "skipping CSI port %s:%u\n",
+ to_of_node(link.local_node)->full_name,
+ link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* Find the remote entity. */
+ ent = sun6i_graph_find_entity(csi,
+ to_of_node(link.remote_node));
+ if (ent == NULL) {
+ dev_err(csi->dev, "no entity found for %s\n",
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ remote = ent->entity;
+
+ if (link.remote_port >= remote->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.remote_port,
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ remote_pad = &remote->pads[link.remote_port];
+
+ v4l2_fwnode_put_link(&link);
+
+ /* Create the media link. */
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+
+ ret = media_create_pad_link(local, local_pad->index,
+ remote, remote_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to create %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+ break;
+ }
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_build_video(struct sun6i_csi *csi)
+{
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ struct device_node *node = csi->dev->of_node;
+ struct media_entity *source;
+ struct media_entity *sink;
+ struct media_pad *source_pad;
+ struct media_pad *sink_pad;
+ struct sun6i_graph_entity *ent;
+ struct v4l2_fwnode_link link;
+ struct device_node *ep = NULL;
+ struct device_node *next;
+ struct sun6i_video *video = &csi->video;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "creating link for video node\n");
+
+ while (1) {
+ /* Get the next endpoint and parse its link. */
+ next = of_graph_get_next_endpoint(node, ep);
+ if (next == NULL)
+ break;
+
+ of_node_put(ep);
+ ep = next;
+
+ dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
+
+ ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to parse link for %s\n",
+ ep->full_name);
+ continue;
+ }
+
+ /* Save the video port settings */
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep),
+ &csi->v4l2_ep);
+ if (ret) {
+ ret = -EINVAL;
+ dev_err(csi->dev, "Could not parse the endpoint\n");
+ v4l2_fwnode_put_link(&link);
+ break;
+ }
+
+ dev_dbg(csi->dev, "creating link for video node %s\n",
+ video->vdev.name);
+
+ /* Find the remote entity. */
+ ent = sun6i_graph_find_entity(csi,
+ to_of_node(link.remote_node));
+ if (ent == NULL) {
+ dev_err(csi->dev, "no entity found for %s\n",
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ if (link.remote_port >= ent->entity->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.remote_port,
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ source = ent->entity;
+ source_pad = &source->pads[link.remote_port];
+ sink = &video->vdev.entity;
+ sink_pad = &video->pad;
+
+ v4l2_fwnode_put_link(&link);
+
+ /* Create the media link. */
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+
+ ret = media_create_pad_link(source, source_pad->index,
+ sink, sink_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to create %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+ break;
+ }
+
+ /* Notify video node */
+ ret = media_entity_call(sink, link_setup, sink_pad, source_pad,
+ link_flags);
+ if (ret == -ENOIOCTLCMD)
+ ret = 0;
+
+ /* found one */
+ break;
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct sun6i_csi *csi =
+ container_of(notifier, struct sun6i_csi, notifier);
+ struct sun6i_graph_entity *entity;
+ int ret;
+
+ dev_dbg(csi->dev, "notify complete, all subdevs registered\n");
+
+ /* Create links for every entity. */
+ list_for_each_entry(entity, &csi->entities, list) {
+ ret = sun6i_graph_build_one(csi, entity);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Create links for video node. */
+ ret = sun6i_graph_build_video(csi);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
+ if (ret < 0)
+ dev_err(csi->dev, "failed to register subdev nodes\n");
+
+ return media_device_register(&csi->media_dev);
+}
+
+static int sun6i_graph_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct sun6i_csi *csi =
+ container_of(notifier, struct sun6i_csi, notifier);
+ struct sun6i_graph_entity *entity;
+
+ /* Locate the entity corresponding to the bound subdev and store the
+ * subdev pointer.
+ */
+ list_for_each_entry(entity, &csi->entities, list) {
+ if (entity->node != subdev->dev->of_node)
+ continue;
+
+ if (entity->subdev) {
+ dev_err(csi->dev, "duplicate subdev for node %s\n",
+ entity->node->full_name);
+ return -EINVAL;
+ }
+
+ dev_dbg(csi->dev, "subdev %s bound\n", subdev->name);
+ entity->entity = &subdev->entity;
+ entity->subdev = subdev;
+ return 0;
+ }
+
+ dev_err(csi->dev, "no entity for subdev %s\n", subdev->name);
+ return -EINVAL;
+}
+
+static int sun6i_graph_parse_one(struct sun6i_csi *csi,
+ struct device_node *node)
+{
+ struct sun6i_graph_entity *entity;
+ struct device_node *remote;
+ struct device_node *ep = NULL;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "parsing node %s\n", node->full_name);
+
+ while (1) {
+ ep = of_graph_get_next_endpoint(node, ep);
+ if (ep == NULL)
+ break;
+
+ dev_dbg(csi->dev, "handling endpoint %s\n", ep->full_name);
+
+ remote = of_graph_get_remote_port_parent(ep);
+ if (remote == NULL) {
+ ret = -EINVAL;
+ break;
+ }
+
+ /* Skip entities that we have already processed. */
+ if (remote == csi->dev->of_node ||
+ sun6i_graph_find_entity(csi, remote)) {
+ of_node_put(remote);
+ continue;
+ }
+
+ entity = devm_kzalloc(csi->dev, sizeof(*entity), GFP_KERNEL);
+ if (entity == NULL) {
+ of_node_put(remote);
+ ret = -ENOMEM;
+ break;
+ }
+
+ entity->node = remote;
+ entity->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+ entity->asd.match.fwnode.fwnode = of_fwnode_handle(remote);
+ list_add_tail(&entity->list, &csi->entities);
+ csi->num_subdevs++;
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_parse(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entity;
+ int ret;
+
+ /*
+ * Walk the links to parse the full graph. Start by parsing the
+ * composite node and then parse entities in turn. The list_for_each
+ * loop will handle entities added at the end of the list while walking
+ * the links.
+ */
+ ret = sun6i_graph_parse_one(csi, csi->dev->of_node);
+ if (ret < 0)
+ return 0;
+
+ list_for_each_entry(entity, &csi->entities, list) {
+ ret = sun6i_graph_parse_one(csi, entity->node);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static void sun6i_graph_cleanup(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entityp;
+ struct sun6i_graph_entity *entity;
+
+ v4l2_async_notifier_unregister(&csi->notifier);
+
+ list_for_each_entry_safe(entity, entityp, &csi->entities, list) {
+ of_node_put(entity->node);
+ list_del(&entity->list);
+ }
+}
+
+static int sun6i_graph_init(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entity;
+ struct v4l2_async_subdev **subdevs = NULL;
+ unsigned int num_subdevs;
+ unsigned int i;
+ int ret;
+
+ /* Parse the graph to extract a list of subdevice DT nodes. */
+ ret = sun6i_graph_parse(csi);
+ if (ret < 0) {
+ dev_err(csi->dev, "graph parsing failed\n");
+ goto done;
+ }
+
+ if (!csi->num_subdevs) {
+ dev_err(csi->dev, "no subdev found in graph\n");
+ goto done;
+ }
+
+ /* Register the subdevices notifier. */
+ num_subdevs = csi->num_subdevs;
+ subdevs = devm_kzalloc(csi->dev, sizeof(*subdevs) * num_subdevs,
+ GFP_KERNEL);
+ if (subdevs == NULL) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ i = 0;
+ list_for_each_entry(entity, &csi->entities, list)
+ subdevs[i++] = &entity->asd;
+
+ csi->notifier.subdevs = subdevs;
+ csi->notifier.num_subdevs = num_subdevs;
+ csi->notifier.bound = sun6i_graph_notify_bound;
+ csi->notifier.complete = sun6i_graph_notify_complete;
+
+ ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);
+ if (ret < 0) {
+ dev_err(csi->dev, "notifier registration failed\n");
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret < 0)
+ sun6i_graph_cleanup(csi);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Media Controller and V4L2
+ */
+
+static void sun6i_csi_v4l2_cleanup(struct sun6i_csi *csi)
+{
+ v4l2_device_unregister(&csi->v4l2_dev);
+ media_device_unregister(&csi->media_dev);
+ media_device_cleanup(&csi->media_dev);
+}
+
+static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->media_dev.dev = csi->dev;
+ strlcpy(csi->media_dev.model, "Allwinner Video Capture Device",
+ sizeof(csi->media_dev.model));
+ csi->media_dev.hw_revision = 0;
+
+ media_device_init(&csi->media_dev);
+
+ csi->v4l2_dev.mdev = &csi->media_dev;
+ ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
+ if (ret < 0) {
+ dev_err(csi->dev, "V4L2 device registration failed (%d)\n",
+ ret);
+ media_device_cleanup(&csi->media_dev);
+ return ret;
+ }
+ return 0;
+}
+
+int sun6i_csi_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->num_subdevs = 0;
+ INIT_LIST_HEAD(&csi->entities);
+
+ ret = sun6i_csi_v4l2_init(csi);
+ if (ret < 0)
+ return ret;
+
+ ret = sun6i_video_init(&csi->video, csi, "sun6i-csi");
+ if (ret < 0)
+ goto v4l2_clean;
+
+ ret = sun6i_graph_init(csi);
+ if (ret < 0)
+ goto video_clean;
+
+ return 0;
+
+video_clean:
+ sun6i_video_cleanup(&csi->video);
+v4l2_clean:
+ sun6i_csi_v4l2_cleanup(csi);
+ return ret;
+}
+
+int sun6i_csi_cleanup(struct sun6i_csi *csi)
+{
+ sun6i_video_cleanup(&csi->video);
+ sun6i_graph_cleanup(csi);
+ sun6i_csi_v4l2_cleanup(csi);
+
+ return 0;
+}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sun6i-csi/sun6i_csi.h
new file mode 100644
index 0000000..167f6bc
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_H__
+#define __SUN6I_CSI_H__
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#include "sun6i_video.h"
+
+struct sun6i_csi;
+
+/**
+ * struct sun6i_csi_config - configs for sun6i csi
+ * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @code: media bus format code (MEDIA_BUS_FMT_*)
+ * @field: used interlacing type (enum v4l2_field)
+ * @width: frame width
+ * @height: frame height
+ */
+struct sun6i_csi_config {
+ u32 pixelformat;
+ u32 code;
+ u32 field;
+ u32 width;
+ u32 height;
+};
+
+struct sun6i_csi_ops {
+ int (*get_supported_pixformats)(struct sun6i_csi *csi,
+ const u32 **pixformats);
+ bool (*is_format_support)(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code);
+ int (*s_power)(struct sun6i_csi *csi, bool enable);
+ int (*update_config)(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config);
+ int (*update_buf_addr)(struct sun6i_csi *csi, dma_addr_t addr);
+ int (*s_stream)(struct sun6i_csi *csi, bool enable);
+};
+
+struct sun6i_csi {
+ struct device *dev;
+ struct v4l2_device v4l2_dev;
+ struct media_device media_dev;
+
+ struct list_head entities;
+ unsigned int num_subdevs;
+ struct v4l2_async_notifier notifier;
+
+ /* video port settings */
+ struct v4l2_fwnode_endpoint v4l2_ep;
+
+ struct sun6i_csi_config config;
+
+ struct sun6i_video video;
+
+ struct sun6i_csi_ops *ops;
+};
+
+int sun6i_csi_init(struct sun6i_csi *csi);
+int sun6i_csi_cleanup(struct sun6i_csi *csi);
+
+/**
+ * sun6i_csi_get_supported_pixformats() - get csi supported pixformats
+ * @csi: pointer to the csi
+ * @pixformats: supported pixformats return from csi
+ *
+ * @return the count of pixformats or error(< 0)
+ */
+static inline int
+sun6i_csi_get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (csi->ops != NULL && csi->ops->get_supported_pixformats != NULL)
+ return csi->ops->get_supported_pixformats(csi, pixformats);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_is_format_support() - check if the format supported by csi
+ * @csi: pointer to the csi
+ * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @mbus_code: media bus format code (MEDIA_BUS_FMT_*)
+ */
+static inline bool
+sun6i_csi_is_format_support(struct sun6i_csi *csi, u32 pixformat, u32 mbus_code)
+{
+ if (csi->ops != NULL && csi->ops->is_format_support != NULL)
+ return csi->ops->is_format_support(csi, pixformat, mbus_code);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_power() - power on/off the csi
+ * @csi: pointer to the csi
+ * @enable: on/off
+ */
+static inline int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable)
+{
+ if (csi->ops != NULL && csi->ops->s_power != NULL)
+ return csi->ops->s_power(csi, enable);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_config() - update the csi register setttings
+ * @csi: pointer to the csi
+ * @config: see struct sun6i_csi_config
+ */
+static inline int
+sun6i_csi_update_config(struct sun6i_csi *csi, struct sun6i_csi_config *config)
+{
+ if (csi->ops != NULL && csi->ops->update_config != NULL)
+ return csi->ops->update_config(csi, config);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_buf_addr() - update the csi frame buffer address
+ * @csi: pointer to the csi
+ * @addr: frame buffer's physical address
+ */
+static inline int sun6i_csi_update_buf_addr(struct sun6i_csi *csi,
+ dma_addr_t addr)
+{
+ if (csi->ops != NULL && csi->ops->update_buf_addr != NULL)
+ return csi->ops->update_buf_addr(csi, addr);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_stream() - start/stop csi streaming
+ * @csi: pointer to the csi
+ * @enable: start/stop
+ */
+static inline int sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable)
+{
+ if (csi->ops != NULL && csi->ops->s_stream != NULL)
+ return csi->ops->s_stream(csi, enable);
+
+ return -ENOIOCTLCMD;
+}
+
+static inline int v4l2_pixformat_get_bpp(unsigned int pixformat)
+{
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return 8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return 10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return 12;
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV422P:
+ return 16;
+ case V4L2_PIX_FMT_RGB24:
+ case V4L2_PIX_FMT_BGR24:
+ return 24;
+ case V4L2_PIX_FMT_RGB32:
+ case V4L2_PIX_FMT_BGR32:
+ return 32;
+ }
+
+ return 0;
+}
+
+#endif /* __SUN6I_CSI_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
new file mode 100644
index 0000000..f940daa
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_csi_v3s.h"
+
+#define MODULE_NAME "sun6i-csi"
+
+struct sun6i_csi_dev {
+ struct sun6i_csi csi;
+ struct device *dev;
+
+ struct regmap *regmap;
+ struct clk *clk_ahb;
+ struct clk *clk_mod;
+ struct clk *clk_ram;
+ struct reset_control *rstc_ahb;
+
+ int planar_offset[3];
+};
+
+static const u32 supported_pixformats[] = {
+ V4L2_PIX_FMT_SBGGR8,
+ V4L2_PIX_FMT_SGBRG8,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_PIX_FMT_SRGGB8,
+ V4L2_PIX_FMT_SBGGR10,
+ V4L2_PIX_FMT_SGBRG10,
+ V4L2_PIX_FMT_SGRBG10,
+ V4L2_PIX_FMT_SRGGB10,
+ V4L2_PIX_FMT_SBGGR12,
+ V4L2_PIX_FMT_SGBRG12,
+ V4L2_PIX_FMT_SGRBG12,
+ V4L2_PIX_FMT_SRGGB12,
+ V4L2_PIX_FMT_YUYV,
+ V4L2_PIX_FMT_YVYU,
+ V4L2_PIX_FMT_UYVY,
+ V4L2_PIX_FMT_VYUY,
+ V4L2_PIX_FMT_HM12,
+ V4L2_PIX_FMT_NV12,
+ V4L2_PIX_FMT_NV21,
+ V4L2_PIX_FMT_YUV420,
+ V4L2_PIX_FMT_YVU420,
+ V4L2_PIX_FMT_NV16,
+ V4L2_PIX_FMT_NV61,
+ V4L2_PIX_FMT_YUV422P,
+};
+
+static inline struct sun6i_csi_dev *sun6i_csi_to_dev(struct sun6i_csi *csi)
+{
+ return container_of(csi, struct sun6i_csi_dev, csi);
+}
+
+/* TODO add 10&12 bit YUV, RGB support */
+static bool __is_format_support(struct sun6i_csi_dev *sdev,
+ u32 fourcc, u32 mbus_code)
+{
+ /*
+ * Some video receiver have capability both 8bit and 16bit.
+ * Identify the media bus format from device tree.
+ */
+ if (((sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_PARALLEL
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_BT656)
+ && sdev->csi.v4l2_ep.bus.parallel.bus_width == 16)
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_CSI2) {
+ switch (fourcc) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ switch (fourcc) {
+ case V4L2_PIX_FMT_SBGGR8:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG8:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG8:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB8:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SBGGR10:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG10:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG10:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB10:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SBGGR12:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG12:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG12:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB12:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12)
+ return true;
+ break;
+
+ case V4L2_PIX_FMT_YUYV:
+ if (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_YVYU:
+ if (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ if (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_VYUY:
+ if (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8)
+ return true;
+ break;
+
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+static enum csi_input_fmt get_csi_input_format(u32 mbus_code, u32 pixformat)
+{
+ /* bayer */
+ if ((mbus_code & 0xF000) == 0x3000)
+ return CSI_INPUT_FORMAT_RAW;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return CSI_INPUT_FORMAT_RAW;
+ }
+
+ /* not support YUV420 input format yet */
+ return CSI_INPUT_FORMAT_YUV422;
+}
+
+static enum csi_output_fmt get_csi_output_format(u32 pixformat, u32 field)
+{
+ bool buf_interlaced = false;
+ if (field == V4L2_FIELD_INTERLACED
+ || field == V4L2_FIELD_INTERLACED_TB
+ || field == V4L2_FIELD_INTERLACED_BT)
+ buf_interlaced = true;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12;
+
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+
+ case V4L2_PIX_FMT_HM12:
+ return buf_interlaced ? CSI_FRAME_MB_YUV420 :
+ CSI_FIELD_MB_YUV420;
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 :
+ CSI_FIELD_UV_CB_YUV420;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 :
+ CSI_FIELD_PLANAR_YUV420;
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 :
+ CSI_FIELD_UV_CB_YUV422;
+ case V4L2_PIX_FMT_YUV422P:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 :
+ CSI_FIELD_PLANAR_YUV422;
+ }
+
+ return 0;
+}
+
+static enum csi_input_seq get_csi_input_seq(u32 mbus_code, u32 pixformat)
+{
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch(mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YUYV;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YVYU;
+ }
+ break;
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YVU420:
+ switch(mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YVYU;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YUYV;
+ }
+ break;
+ }
+
+ return CSI_INPUT_SEQ_YUYV;
+}
+
+#ifdef DEBUG
+static void sun6i_csi_dump_regs(struct sun6i_csi_dev *sdev)
+{
+ struct regmap *regmap = sdev->regmap;
+ u32 val;
+
+ regmap_read(regmap, CSI_EN_REG, &val);
+ printk("CSI_EN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_IF_CFG_REG, &val);
+ printk("CSI_IF_CFG_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CAP_REG, &val);
+ printk("CSI_CAP_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_SYNC_CNT_REG, &val);
+ printk("CSI_SYNC_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_FIFO_THRS_REG, &val);
+ printk("CSI_FIFO_THRS_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_PTN_LEN_REG, &val);
+ printk("CSI_PTN_LEN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_PTN_ADDR_REG, &val);
+ printk("CSI_PTN_ADDR_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_VER_REG, &val);
+ printk("CSI_VER_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_CFG_REG, &val);
+ printk("CSI_CH_CFG_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_SCALE_REG, &val);
+ printk("CSI_CH_SCALE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F0_BUFA_REG, &val);
+ printk("CSI_CH_F0_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F1_BUFA_REG, &val);
+ printk("CSI_CH_F1_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F2_BUFA_REG, &val);
+ printk("CSI_CH_F2_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_STA_REG, &val);
+ printk("CSI_CH_STA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_INT_EN_REG, &val);
+ printk("CSI_CH_INT_EN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &val);
+ printk("CSI_CH_INT_STA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FLD1_VSIZE_REG, &val);
+ printk("CSI_CH_FLD1_VSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_HSIZE_REG, &val);
+ printk("CSI_CH_HSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_VSIZE_REG, &val);
+ printk("CSI_CH_VSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_BUF_LEN_REG, &val);
+ printk("CSI_CH_BUF_LEN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FLIP_SIZE_REG, &val);
+ printk("CSI_CH_FLIP_SIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FRM_CLK_CNT_REG, &val);
+ printk("CSI_CH_FRM_CLK_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_ACC_ITNL_CLK_CNT_REG, &val);
+ printk("CSI_CH_ACC_ITNL_CLK_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FIFO_STAT_REG, &val);
+ printk("CSI_CH_FIFO_STAT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_PCLK_STAT_REG, &val);
+ printk("CSI_CH_PCLK_STAT_REG=0x%x\n", val);
+}
+#endif
+
+static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev)
+{
+ struct v4l2_fwnode_endpoint *endpoint = &sdev->csi.v4l2_ep;
+ unsigned char bus_width;
+ u32 flags;
+ u32 cfg;
+
+ bus_width = endpoint->bus.parallel.bus_width;
+
+ regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK |
+ CSI_IF_CFG_IF_DATA_WIDTH_MASK |
+ CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK |
+ CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK);
+
+ switch (endpoint->bus_type) {
+ case V4L2_MBUS_CSI2:
+ cfg |= CSI_IF_CFG_MIPI_IF_MIPI;
+ break;
+ case V4L2_MBUS_PARALLEL:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT :
+ CSI_IF_CFG_CSI_IF_YUV422_INTLV;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_VREF_POL_POSITIVE;
+ if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_HREF_POL_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ case V4L2_MBUS_BT656:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 :
+ CSI_IF_CFG_CSI_IF_BT656;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ default:
+ BUG();
+ break;
+ }
+
+ switch (bus_width) {
+ case 8:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT;
+ break;
+ case 10:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT;
+ break;
+ case 12:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT;
+ break;
+ default:
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_IF_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_format(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi *csi = &sdev->csi;
+ u32 cfg;
+ u32 val;
+
+ regmap_read(sdev->regmap, CSI_CH_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK |
+ CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN |
+ CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK |
+ CSI_CH_CFG_INPUT_SEQ_MASK);
+
+ val = get_csi_input_format(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_FMT(val);
+
+ val = get_csi_output_format(csi->config.code, csi->config.field);
+ cfg |= CSI_CH_CFG_OUTPUT_FMT(val);
+
+ val = get_csi_input_seq(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_SEQ(val);
+
+ if (csi->config.field == V4L2_FIELD_TOP)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0;
+ else if (csi->config.field == V4L2_FIELD_BOTTOM)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1;
+ else
+ cfg |= CSI_CH_CFG_FIELD_SEL_BOTH;
+
+ regmap_write(sdev->regmap, CSI_CH_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_window(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi_config *config = &sdev->csi.config;
+ u32 bytesperline_y;
+ u32 bytesperline_c;
+ int *planar_offset = sdev->planar_offset;
+
+ regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
+ CSI_CH_HSIZE_HOR_LEN(config->width) |
+ CSI_CH_HSIZE_HOR_START(0));
+ regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
+ CSI_CH_VSIZE_VER_LEN(config->height) |
+ CSI_CH_VSIZE_VER_START(0));
+
+ planar_offset[0] = 0;
+ switch(config->pixelformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = -1;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width / 2;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * config->height / 2;
+ break;
+ case V4L2_PIX_FMT_YUV422P:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width / 2;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * config->height;
+ break;
+ default: /* raw */
+ bytesperline_y = (v4l2_pixformat_get_bpp(config->pixelformat) *
+ config->width) / 8;
+ bytesperline_c = 0;
+ planar_offset[1] = -1;
+ planar_offset[2] = -1;
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_CH_BUF_LEN_REG,
+ CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) |
+ CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y));
+}
+
+static int get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (pixformats != NULL)
+ *pixformats = supported_pixformats;
+
+ return ARRAY_SIZE(supported_pixformats);
+}
+
+static bool is_format_support(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ return __is_format_support(sdev, pixformat, mbus_code);
+}
+
+static int set_power(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+ int ret;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+
+ clk_disable_unprepare(sdev->clk_ram);
+ clk_disable_unprepare(sdev->clk_mod);
+ clk_disable_unprepare(sdev->clk_ahb);
+ reset_control_assert(sdev->rstc_ahb);
+ return 0;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ahb);
+ if (ret) {
+ dev_err(sdev->dev, "Enable ahb clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_mod);
+ if (ret) {
+ dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ram);
+ if (ret) {
+ dev_err(sdev->dev, "Enable clk_dram_csi clk err %d\n", ret);
+ return ret;
+ }
+
+ if (!IS_ERR_OR_NULL(sdev->rstc_ahb)) {
+ ret = reset_control_deassert(sdev->rstc_ahb);
+ if (ret) {
+ dev_err(sdev->dev, "reset err %d\n", ret);
+ return ret;
+ }
+ }
+
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
+
+ return 0;
+}
+
+static int update_config(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ if (config == NULL)
+ return -EINVAL;
+
+ memcpy(&csi->config, config, sizeof(csi->config));
+
+ sun6i_csi_setup_bus(sdev);
+ sun6i_csi_set_format(sdev);
+ sun6i_csi_set_window(sdev);
+
+ return 0;
+}
+
+static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ /* transform physical address to bus address */
+ dma_addr_t bus_addr = addr - 0x40000000;
+
+ regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
+ (bus_addr + sdev->planar_offset[0]) >> 2);
+ if (sdev->planar_offset[1] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
+ (bus_addr + sdev->planar_offset[1]) >> 2);
+ if (sdev->planar_offset[2] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
+ (bus_addr + sdev->planar_offset[2]) >> 2);
+
+ return 0;
+}
+
+static int set_stream(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0);
+ regmap_write(regmap, CSI_CH_INT_EN_REG, 0);
+ return 0;
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF);
+ regmap_write(regmap, CSI_CH_INT_EN_REG,
+ CSI_CH_INT_EN_HB_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO2_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO1_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO0_OF_INT_EN |
+ CSI_CH_INT_EN_FD_INT_EN |
+ CSI_CH_INT_EN_CD_INT_EN);
+
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON,
+ CSI_CAP_CH0_VCAP_ON);
+
+ return 0;
+}
+
+static struct sun6i_csi_ops csi_ops = {
+ .get_supported_pixformats = get_supported_pixformats,
+ .is_format_support = is_format_support,
+ .s_power = set_power,
+ .update_config = update_config,
+ .update_buf_addr = update_buf_addr,
+ .s_stream = set_stream,
+};
+
+static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
+{
+ struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
+ struct regmap *regmap = sdev->regmap;
+ u32 status;
+
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
+
+ if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
+ (status & CSI_CH_INT_STA_HB_OF_PD)) {
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
+ CSI_EN_CSI_EN);
+ return IRQ_HANDLED;
+ }
+
+ if (status & CSI_CH_INT_STA_FD_PD) {
+ sun6i_video_frame_done(&sdev->csi.video);
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config sun6i_csi_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x1000,
+};
+
+static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+ void __iomem *io_base;
+ int ret;
+ int irq;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ io_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(io_base))
+ return PTR_ERR(io_base);
+
+ sdev->regmap = devm_regmap_init_mmio(&pdev->dev, io_base,
+ &sun6i_csi_regmap_config);
+ if (IS_ERR(sdev->regmap)) {
+ dev_err(&pdev->dev, "Failed to init register map\n");
+ return PTR_ERR(sdev->regmap);
+ }
+
+ sdev->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
+ if (IS_ERR(sdev->clk_ahb)) {
+ dev_err(&pdev->dev, "Unable to acquire ahb clock\n");
+ return PTR_ERR(sdev->clk_ahb);
+ }
+
+ sdev->clk_mod = devm_clk_get(&pdev->dev, "mod");
+ if (IS_ERR(sdev->clk_mod)) {
+ dev_err(&pdev->dev, "Unable to acquire csi clock\n");
+ return PTR_ERR(sdev->clk_mod);
+ }
+
+ sdev->clk_ram = devm_clk_get(&pdev->dev, "ram");
+ if (IS_ERR(sdev->clk_ram)) {
+ dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n");
+ return PTR_ERR(sdev->clk_ram);
+ }
+
+ sdev->rstc_ahb = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(sdev->rstc_ahb)) {
+ dev_err(&pdev->dev, "Cannot get reset controller\n");
+ return PTR_ERR(sdev->rstc_ahb);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "No csi IRQ specified\n");
+ ret = -ENXIO;
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, sun6i_csi_isr, 0, MODULE_NAME,
+ sdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot request csi IRQ\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int sun6i_csi_probe(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev;
+ int ret;
+
+ sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
+ if (!sdev)
+ return -ENOMEM;
+
+ sdev->dev = &pdev->dev;
+
+ ret = sun6i_csi_resource_request(sdev, pdev);
+ if (ret)
+ return ret;
+
+ sdev->csi.dev = &pdev->dev;
+ sdev->csi.ops = &csi_ops;
+ ret = sun6i_csi_init(&sdev->csi);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, sdev);
+
+ return 0;
+}
+
+static int sun6i_csi_remove(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev = platform_get_drvdata(pdev);
+
+ sun6i_csi_cleanup(&sdev->csi);
+
+ return 0;
+}
+
+static const struct of_device_id sun6i_csi_of_match[] = {
+ { .compatible = "allwinner,sun8i-v3s-csi", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sun6i_csi_of_match);
+
+static struct platform_driver sun6i_csi_platform_driver = {
+ .probe = sun6i_csi_probe,
+ .remove = sun6i_csi_remove,
+ .driver = {
+ .name = MODULE_NAME,
+ .of_match_table = of_match_ptr(sun6i_csi_of_match),
+ },
+};
+module_platform_driver(sun6i_csi_platform_driver);
+
+MODULE_DESCRIPTION("Allwinner V3s Camera Sensor Interface driver");
+MODULE_AUTHOR("Yong Deng <yong.deng@magewell.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
new file mode 100644
index 0000000..6c87b74
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_V3S_H__
+#define __SUN6I_CSI_V3S_H__
+
+#include <linux/kernel.h>
+
+#define CSI_EN_REG 0x0
+#define CSI_EN_VER_EN BIT(30)
+#define CSI_EN_CSI_EN BIT(0)
+
+#define CSI_IF_CFG_REG 0x4
+#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21)
+#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_FPS_DS_EN BIT(20)
+#define CSI_IF_CFG_FIELD_MASK BIT(19)
+#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_VREF_POL_MASK BIT(18)
+#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_MASK BIT(17)
+#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_MASK BIT(16)
+#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8)
+#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_MIPI_IF_MASK BIT(7)
+#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7)
+#define CSI_IF_CFG_MIPI_IF_MIPI (1 << 7)
+#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0)
+#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+
+#define CSI_CAP_REG 0x8
+#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2)
+#define CSI_CAP_CH0_CAP_MASK(count) ((count << 2) & CSI_CAP_CH0_CAP_MASK_MASK)
+#define CSI_CAP_CH0_VCAP_ON BIT(1)
+#define CSI_CAP_CH0_SCAP_ON BIT(0)
+
+#define CSI_SYNC_CNT_REG 0xc
+#define CSI_FIFO_THRS_REG 0x10
+#define CSI_BT656_HEAD_CFG_REG 0x14
+#define CSI_PTN_LEN_REG 0x30
+#define CSI_PTN_ADDR_REG 0x34
+#define CSI_VER_REG 0x3c
+
+#define CSI_CH_CFG_REG 0x44
+#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20)
+#define CSI_CH_CFG_INPUT_FMT(fmt) ((fmt << 20) & CSI_CH_CFG_INPUT_FMT_MASK)
+#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16)
+#define CSI_CH_CFG_OUTPUT_FMT(fmt) ((fmt << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK)
+#define CSI_CH_CFG_VFLIP_EN BIT(13)
+#define CSI_CH_CFG_HFLIP_EN BIT(12)
+#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10)
+#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8)
+#define CSI_CH_CFG_INPUT_SEQ(seq) ((seq << 8) & CSI_CH_CFG_INPUT_SEQ_MASK)
+
+#define CSI_CH_SCALE_REG 0x4c
+#define CSI_CH_SCALE_QUART_EN BIT(0)
+
+#define CSI_CH_F0_BUFA_REG 0x50
+
+#define CSI_CH_F1_BUFA_REG 0x58
+
+#define CSI_CH_F2_BUFA_REG 0x60
+
+#define CSI_CH_STA_REG 0x6c
+#define CSI_CH_STA_FIELD_STA_MASK BIT(2)
+#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_VCAP_STA BIT(1)
+#define CSI_CH_STA_SCAP_STA BIT(0)
+
+#define CSI_CH_INT_EN_REG 0x70
+#define CSI_CH_INT_EN_VS_INT_EN BIT(7)
+#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6)
+#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5)
+#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4)
+#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3)
+#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2)
+#define CSI_CH_INT_EN_FD_INT_EN BIT(1)
+#define CSI_CH_INT_EN_CD_INT_EN BIT(0)
+
+#define CSI_CH_INT_STA_REG 0x74
+#define CSI_CH_INT_STA_VS_PD BIT(7)
+#define CSI_CH_INT_STA_HB_OF_PD BIT(6)
+#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5)
+#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4)
+#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3)
+#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2)
+#define CSI_CH_INT_STA_FD_PD BIT(1)
+#define CSI_CH_INT_STA_CD_PD BIT(0)
+
+#define CSI_CH_FLD1_VSIZE_REG 0x74
+
+#define CSI_CH_HSIZE_REG 0x80
+#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_HSIZE_HOR_LEN(len) ((len << 16) & CSI_CH_HSIZE_HOR_LEN_MASK)
+#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0)
+#define CSI_CH_HSIZE_HOR_START(start) ((start << 0) & CSI_CH_HSIZE_HOR_START_MASK)
+
+#define CSI_CH_VSIZE_REG 0x84
+#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_VSIZE_VER_LEN(len) ((len << 16) & CSI_CH_VSIZE_VER_LEN_MASK)
+#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0)
+#define CSI_CH_VSIZE_VER_START(start) ((start << 0) & CSI_CH_VSIZE_VER_START_MASK)
+
+#define CSI_CH_BUF_LEN_REG 0x88
+#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16)
+#define CSI_CH_BUF_LEN_BUF_LEN_C(len) ((len << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) ((len << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK)
+
+#define CSI_CH_FLIP_SIZE_REG 0x8c
+#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_FLIP_SIZE_VER_LEN(len) ((len << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK)
+#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0)
+#define CSI_CH_FLIP_SIZE_VALID_LEN(len) ((len << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK)
+
+#define CSI_CH_FRM_CLK_CNT_REG 0x90
+#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94
+#define CSI_CH_FIFO_STAT_REG 0x98
+#define CSI_CH_PCLK_STAT_REG 0x9c
+
+/*
+ * csi input data format
+ */
+enum csi_input_fmt
+{
+ CSI_INPUT_FORMAT_RAW = 0,
+ CSI_INPUT_FORMAT_YUV422 = 3,
+ CSI_INPUT_FORMAT_YUV420 = 4,
+};
+
+/*
+ * csi output data format
+ */
+enum csi_output_fmt
+{
+ /* only when input format is RAW */
+ CSI_FIELD_RAW_8 = 0,
+ CSI_FIELD_RAW_10 = 1,
+ CSI_FIELD_RAW_12 = 2,
+ CSI_FIELD_RGB565 = 4,
+ CSI_FIELD_RGB888 = 5,
+ CSI_FIELD_PRGB888 = 6,
+ CSI_FRAME_RAW_8 = 8,
+ CSI_FRAME_RAW_10 = 9,
+ CSI_FRAME_RAW_12 = 10,
+ CSI_FRAME_RGB565 = 12,
+ CSI_FRAME_RGB888 = 13,
+ CSI_FRAME_PRGB888 = 14,
+
+ /* only when input format is YUV422/YUV420 */
+ CSI_FIELD_PLANAR_YUV422 = 0,
+ CSI_FIELD_PLANAR_YUV420 = 1,
+ CSI_FRAME_PLANAR_YUV420 = 2,
+ CSI_FRAME_PLANAR_YUV422 = 3,
+ CSI_FIELD_UV_CB_YUV422 = 4,
+ CSI_FIELD_UV_CB_YUV420 = 5,
+ CSI_FRAME_UV_CB_YUV420 = 6,
+ CSI_FRAME_UV_CB_YUV422 = 7,
+ CSI_FIELD_MB_YUV422 = 8,
+ CSI_FIELD_MB_YUV420 = 9,
+ CSI_FRAME_MB_YUV420 = 10,
+ CSI_FRAME_MB_YUV422 = 11,
+ CSI_FIELD_UV_CB_YUV422_10 = 12,
+ CSI_FIELD_UV_CB_YUV420_10 = 13,
+};
+
+/*
+ * csi YUV input data sequence
+ */
+enum csi_input_seq
+{
+ /* only when input format is YUV422 */
+ CSI_INPUT_SEQ_YUYV = 0,
+ CSI_INPUT_SEQ_YVYU,
+ CSI_INPUT_SEQ_UYVY,
+ CSI_INPUT_SEQ_VYUY,
+};
+
+#endif /* __SUN6I_CSI_V3S_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.c b/drivers/media/platform/sun6i-csi/sun6i_video.c
new file mode 100644
index 0000000..0c5dbd2
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_video.c
@@ -0,0 +1,663 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/of.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_video.h"
+
+struct sun6i_csi_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+
+ dma_addr_t dma_addr;
+};
+
+static struct sun6i_csi_format *
+find_format_by_fourcc(struct sun6i_video *video, unsigned int fourcc)
+{
+ unsigned int num_formats = video->num_formats;
+ struct sun6i_csi_format *fmt;
+ unsigned int i;
+
+ for (i = 0; i < num_formats; i++) {
+ fmt = &video->formats[i];
+ if (fmt->fourcc == fourcc)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+static struct v4l2_subdev *
+sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad)
+{
+ struct media_pad *remote;
+
+ remote = media_entity_remote_pad(&video->pad);
+
+ if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
+ return NULL;
+
+ if (pad)
+ *pad = remote->index;
+
+ return media_entity_to_v4l2_subdev(remote->entity);
+}
+
+static int sun6i_video_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned int size = video->fmt.fmt.pix.sizeimage;
+
+ if (*nplanes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static int sun6i_video_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long size = video->fmt.fmt.pix.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+
+ buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ vbuf->field = video->fmt.fmt.pix.field;
+
+ return 0;
+}
+
+static int sun6i_pipeline_set_stream(struct sun6i_video *video, bool enable)
+{
+ struct media_entity *entity;
+ struct media_pad *pad;
+ struct v4l2_subdev *subdev;
+ int ret;
+
+ entity = &video->vdev.entity;
+ while (1) {
+ pad = &entity->pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+
+ pad = media_entity_remote_pad(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+
+ entity = pad->entity;
+ subdev = media_entity_to_v4l2_subdev(entity);
+
+ ret = v4l2_subdev_call(subdev, video, s_stream, enable);
+ if (enable && ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ struct sun6i_csi_buffer *buf;
+ struct sun6i_csi_config config;
+ unsigned long flags;
+ int ret;
+
+ video->sequence = 0;
+
+ ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe);
+ if (ret < 0)
+ goto err_start_pipeline;
+
+ ret = sun6i_pipeline_set_stream(video, true);
+ if (ret < 0)
+ goto err_start_stream;
+
+ config.pixelformat = video->fmt.fmt.pix.pixelformat;
+ config.code = video->current_fmt->mbus_code;
+ config.field = video->fmt.fmt.pix.field;
+ config.width = video->fmt.fmt.pix.width;
+ config.height = video->fmt.fmt.pix.height;
+
+ ret = sun6i_csi_update_config(video->csi, &config);
+ if (ret < 0)
+ goto err_update_config;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ video->cur_frm = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ list_del(&video->cur_frm->list);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ ret = sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ if (ret < 0)
+ goto err_update_addr;
+
+ ret = sun6i_csi_set_stream(video->csi, true);
+ if (ret < 0)
+ goto err_csi_stream;
+
+ return 0;
+
+err_csi_stream:
+err_update_addr:
+err_update_config:
+ sun6i_pipeline_set_stream(video, false);
+err_start_stream:
+ media_pipeline_stop(&video->vdev.entity);
+err_start_pipeline:
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ return ret;
+}
+
+static void sun6i_video_stop_streaming(struct vb2_queue *vq)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned long flags;
+ struct sun6i_csi_buffer *buf;
+
+ sun6i_pipeline_set_stream(video, false);
+
+ sun6i_csi_set_stream(video->csi, false);
+
+ media_pipeline_stop(&video->vdev.entity);
+
+ /* Release all active buffers */
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ if (unlikely(video->cur_frm)) {
+ vb2_buffer_done(&video->cur_frm->vb.vb2_buf,
+ VB2_BUF_STATE_ERROR);
+ video->cur_frm = NULL;
+ }
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+static void sun6i_video_buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long flags;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ if (!video->cur_frm && list_empty(&video->dma_queue) &&
+ vb2_is_streaming(vb->vb2_queue)) {
+ video->cur_frm = buf;
+ sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ sun6i_csi_set_stream(video->csi, 1);
+ } else
+ list_add_tail(&buf->list, &video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+void sun6i_video_frame_done(struct sun6i_video *video)
+{
+ spin_lock(&video->dma_queue_lock);
+
+ if (video->cur_frm) {
+ struct vb2_v4l2_buffer *vbuf = &video->cur_frm->vb;
+ struct vb2_buffer *vb = &vbuf->vb2_buf;
+
+ vb->timestamp = ktime_get_ns();
+ vbuf->sequence = video->sequence++;
+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+ video->cur_frm = NULL;
+ }
+
+ if (!list_empty(&video->dma_queue)
+ && vb2_is_streaming(&video->vb2_vidq)) {
+ video->cur_frm = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ list_del(&video->cur_frm->list);
+ sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ } else
+ sun6i_csi_set_stream(video->csi, 0);
+
+ spin_unlock(&video->dma_queue_lock);
+}
+
+static struct vb2_ops sun6i_csi_vb2_ops = {
+ .queue_setup = sun6i_video_queue_setup,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .buf_prepare = sun6i_video_buffer_prepare,
+ .start_streaming = sun6i_video_start_streaming,
+ .stop_streaming = sun6i_video_stop_streaming,
+ .buf_queue = sun6i_video_buffer_queue,
+};
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ strlcpy(cap->driver, "sun6i-video", sizeof(cap->driver));
+ strlcpy(cap->card, video->vdev.name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ video->csi->dev->of_node->name);
+
+ return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ u32 index = f->index;
+
+ if (index >= video->num_formats)
+ return -EINVAL;
+
+ f->pixelformat = video->formats[index].fourcc;
+
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ *fmt = video->fmt;
+
+ return 0;
+}
+
+static int sun6i_video_try_fmt(struct sun6i_video *video, struct v4l2_format *f,
+ struct sun6i_csi_format **current_fmt)
+{
+ struct sun6i_csi_format *csi_fmt;
+ struct v4l2_pix_format *pixfmt = &f->fmt.pix;
+ struct v4l2_subdev_format format;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ csi_fmt = find_format_by_fourcc(video, pixfmt->pixelformat);
+ if (csi_fmt == NULL)
+ return -EINVAL;
+
+ format.pad = pad;
+ format.which = V4L2_SUBDEV_FORMAT_TRY;
+ v4l2_fill_mbus_format(&format.format, pixfmt, csi_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format);
+ if (ret)
+ return ret;
+
+ v4l2_fill_pix_format(pixfmt, &format.format);
+
+ pixfmt->bytesperline = (pixfmt->width * csi_fmt->bpp) >> 3;
+ pixfmt->sizeimage = (pixfmt->width * csi_fmt->bpp * pixfmt->height) / 8;
+
+ if (current_fmt)
+ *current_fmt = csi_fmt;
+
+ return 0;
+}
+
+static int sun6i_video_set_fmt(struct sun6i_video *video, struct v4l2_format *f)
+{
+ struct v4l2_subdev_format format;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ ret = sun6i_video_try_fmt(video, f, ¤t_fmt);
+ if (ret)
+ return ret;
+
+ format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ v4l2_fill_mbus_format(&format.format, &f->fmt.pix,
+ current_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format);
+ if (ret < 0)
+ return ret;
+
+ video->fmt = *f;
+ video->current_fmt = current_fmt;
+
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ if (vb2_is_streaming(&video->vb2_vidq))
+ return -EBUSY;
+
+ return sun6i_video_set_fmt(video, f);
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ return sun6i_video_try_fmt(video, f, NULL);
+}
+
+static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 file operations
+ */
+static int sun6i_video_open(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ struct v4l2_format format;
+ int ret;
+
+ if (mutex_lock_interruptible(&video->lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto unlock;
+
+ ret = v4l2_pipeline_pm_use(&video->vdev.entity, 1);
+ if (ret < 0)
+ goto fh_release;
+
+ if (!v4l2_fh_is_singular_file(file))
+ goto unlock;
+
+ ret = sun6i_csi_set_power(video->csi, true);
+ if (ret < 0)
+ goto fh_release;
+
+ /* setup default format */
+ if (video->num_formats > 0) {
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ format.fmt.pix.width = 1280;
+ format.fmt.pix.height = 720;
+ format.fmt.pix.pixelformat = video->formats[0].fourcc;
+ sun6i_video_set_fmt(video, &format);
+ }
+
+ mutex_unlock(&video->lock);
+ return 0;
+
+fh_release:
+ v4l2_fh_release(file);
+unlock:
+ mutex_unlock(&video->lock);
+ return ret;
+}
+
+static int sun6i_video_close(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ bool last_fh;
+
+ mutex_lock(&video->lock);
+
+ last_fh = v4l2_fh_is_singular_file(file);
+
+ _vb2_fop_release(file, NULL);
+
+ v4l2_pipeline_pm_use(&video->vdev.entity, 0);
+
+ if (last_fh)
+ sun6i_csi_set_power(video->csi, false);
+
+ mutex_unlock(&video->lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations sun6i_video_fops = {
+ .owner = THIS_MODULE,
+ .open = sun6i_video_open,
+ .release = sun6i_video_close,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+ .poll = vb2_fop_poll
+};
+
+/* -----------------------------------------------------------------------------
+ * Media Operations
+ */
+static int sun6i_video_formats_init(struct sun6i_video *video)
+{
+ struct v4l2_subdev_mbus_code_enum mbus_code = { 0 };
+ struct sun6i_csi *csi = video->csi;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ const u32 *pixformats;
+ int pixformat_count = 0;
+ u32 subdev_codes[32]; /* subdev format codes, 32 should be enough */
+ int codes_count = 0;
+ int num_fmts = 0;
+ int i, j;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ /* Get supported pixformats of CSI */
+ pixformat_count = sun6i_csi_get_supported_pixformats(csi, &pixformats);
+ if (pixformat_count <= 0)
+ return -ENXIO;
+
+ /* Get subdev formats codes */
+ mbus_code.pad = pad;
+ mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL,
+ &mbus_code)) {
+ subdev_codes[codes_count] = mbus_code.code;
+ codes_count++;
+ mbus_code.index++;
+ }
+
+ if (!codes_count)
+ return -ENXIO;
+
+ /* Get supported formats count */
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ mbus_code.code)) {
+ continue;
+ }
+ num_fmts++;
+ }
+ }
+
+ if (!num_fmts)
+ return -ENXIO;
+
+ video->num_formats = num_fmts;
+ video->formats = devm_kcalloc(video->csi->dev, num_fmts,
+ sizeof(struct sun6i_csi_format), GFP_KERNEL);
+ if (!video->formats) {
+ dev_err(video->csi->dev, "could not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ /* Get supported formats */
+ num_fmts = 0;
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ mbus_code.code)) {
+ continue;
+ }
+
+ video->formats[num_fmts].fourcc = pixformats[j];
+ video->formats[num_fmts].mbus_code =
+ mbus_code.code;
+ video->formats[num_fmts].bpp =
+ v4l2_pixformat_get_bpp(pixformats[j]);
+ num_fmts++;
+ }
+ }
+
+ return 0;
+}
+
+static int sun6i_video_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct video_device *vdev = media_entity_to_video_device(entity);
+ struct sun6i_video *video = video_get_drvdata(vdev);
+
+ if (WARN_ON(video == NULL))
+ return 0;
+
+ return sun6i_video_formats_init(video);
+}
+
+static const struct media_entity_operations sun6i_video_media_ops = {
+ .link_setup = sun6i_video_link_setup,
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name)
+{
+ struct video_device *vdev = &video->vdev;
+ struct vb2_queue *vidq = &video->vb2_vidq;
+ int ret;
+
+ video->csi = csi;
+
+ /* Initialize the media entity... */
+ video->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
+ vdev->entity.ops = &sun6i_video_media_ops;
+ ret = media_entity_pads_init(&vdev->entity, 1, &video->pad);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&video->lock);
+
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_lock_init(&video->dma_queue_lock);
+
+ video->cur_frm = NULL;
+ video->sequence = 0;
+ video->num_formats = 0;
+
+ /* Initialize videobuf2 queue */
+ vidq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ vidq->io_modes = VB2_MMAP | VB2_DMABUF;
+ vidq->drv_priv = video;
+ vidq->buf_struct_size = sizeof(struct sun6i_csi_buffer);
+ vidq->ops = &sun6i_csi_vb2_ops;
+ vidq->mem_ops = &vb2_dma_contig_memops;
+ vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ vidq->lock = &video->lock;
+ vidq->min_buffers_needed = 1;
+ vidq->dev = csi->dev;
+
+ ret = vb2_queue_init(vidq);
+ if (ret) {
+ v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret);
+ goto error;
+ }
+
+ /* Register video device */
+ strlcpy(vdev->name, name, sizeof(vdev->name));
+ vdev->release = video_device_release_empty;
+ vdev->fops = &sun6i_video_fops;
+ vdev->ioctl_ops = &sun6i_video_ioctl_ops;
+ vdev->vfl_type = VFL_TYPE_GRABBER;
+ vdev->vfl_dir = VFL_DIR_RX;
+ vdev->v4l2_dev = &csi->v4l2_dev;
+ vdev->queue = vidq;
+ vdev->lock = &video->lock;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+ video_set_drvdata(vdev, video);
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret < 0) {
+ v4l2_err(&csi->v4l2_dev,
+ "video_register_device failed: %d\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ sun6i_video_cleanup(video);
+ return ret;
+}
+
+void sun6i_video_cleanup(struct sun6i_video *video)
+{
+ if (video_is_registered(&video->vdev))
+ video_unregister_device(&video->vdev);
+
+ media_entity_cleanup(&video->vdev.entity);
+}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.h b/drivers/media/platform/sun6i-csi/sun6i_video.h
new file mode 100644
index 0000000..14eac6e
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_video.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng@magewell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_VIDEO_H__
+#define __SUN6I_VIDEO_H__
+
+#include <media/v4l2-dev.h>
+#include <media/videobuf2-core.h>
+
+/*
+ * struct sun6i_csi_format - CSI media bus format information
+ * @fourcc: Fourcc code for this format
+ * @mbus_code: V4L2 media bus format code.
+ * @bpp: Bytes per pixel (when stored in memory)
+ */
+struct sun6i_csi_format {
+ u32 fourcc;
+ u32 mbus_code;
+ u8 bpp;
+};
+
+struct sun6i_csi;
+
+struct sun6i_video {
+ struct video_device vdev;
+ struct media_pad pad;
+ struct sun6i_csi *csi;
+
+ struct mutex lock;
+
+ struct vb2_queue vb2_vidq;
+ spinlock_t dma_queue_lock;
+ struct list_head dma_queue;
+
+ struct sun6i_csi_buffer *cur_frm;
+ unsigned int sequence;
+
+ struct sun6i_csi_format *formats;
+ unsigned int num_formats;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_format fmt;
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name);
+void sun6i_video_cleanup(struct sun6i_video *video);
+
+void sun6i_video_frame_done(struct sun6i_video *video);
+
+#endif /* __SUN6I_VIDEO_H__ */
--
1.8.3.1
^ permalink raw reply related [flat|nested] 56+ messages in thread
[parent not found: <1501127474-40895-1-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>]
* [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
[not found] ` <1501127474-40895-1-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
@ 2017-07-27 3:51 ` Yong Deng
2017-07-27 3:51 ` Yong Deng
1 sibling, 0 replies; 56+ messages in thread
From: Yong Deng @ 2017-07-27 3:51 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Maxime Ripard,
Chen-Yu Tsai, Greg Kroah-Hartman, David S. Miller, Hans Verkuil,
Arnd Bergmann, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Benoit Parrot, Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski
Cc: Yong Deng
Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
and CSI1 is used for parallel interface. This is not documented in
datasheet but by testing and guess.
This patch implement a v4l2 framework driver for it.
Currently, the driver only support the parallel interface. MIPI-CSI2,
ISP's support are not included in this patch.
Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
---
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/sun6i-csi/Kconfig | 9 +
drivers/media/platform/sun6i-csi/Makefile | 3 +
drivers/media/platform/sun6i-csi/sun6i_csi.c | 545 +++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi.h | 203 ++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c | 827 +++++++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h | 206 ++++++
drivers/media/platform/sun6i-csi/sun6i_video.c | 663 ++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_video.h | 61 ++
10 files changed, 2520 insertions(+)
create mode 100644 drivers/media/platform/sun6i-csi/Kconfig
create mode 100644 drivers/media/platform/sun6i-csi/Makefile
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.h
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.h
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 0c741d1..8371a87 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -143,6 +143,7 @@ source "drivers/media/platform/am437x/Kconfig"
source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/sun6i-csi/Kconfig"
config VIDEO_TI_CAL
tristate "TI CAL (Camera Adaptation Layer) driver"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 9beadc7..fb2459c 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -86,3 +86,5 @@ obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/
obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk-jpeg/
obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi/
diff --git a/drivers/media/platform/sun6i-csi/Kconfig b/drivers/media/platform/sun6i-csi/Kconfig
new file mode 100644
index 0000000..314188a
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Kconfig
@@ -0,0 +1,9 @@
+config VIDEO_SUN6I_CSI
+ tristate "Allwinner V3s Camera Sensor Interface driver"
+ depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select REGMAP_MMIO
+ select V4L2_FWNODE
+ ---help---
+ Support for the Allwinner Camera Sensor Interface Controller on V3s.
diff --git a/drivers/media/platform/sun6i-csi/Makefile b/drivers/media/platform/sun6i-csi/Makefile
new file mode 100644
index 0000000..a9b527b
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Makefile
@@ -0,0 +1,3 @@
+sun6i-csi-y += sun6i_csi.o sun6i_video.o sun6i_csi_v3s.o
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sun6i-csi/sun6i_csi.c
new file mode 100644
index 0000000..7a4bf53
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.c
@@ -0,0 +1,545 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing),
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * Based on drivers/media/platform/xilinx/xilinx-vipp.c
+ * Copyright (C) 2013-2015 Ideas on Board
+ * Copyright (C) 2013-2015 Xilinx, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/of_graph.h>
+
+#include "sun6i_csi.h"
+
+/*
+ * struct sun6i_graph_entity - Entity in the video graph
+ * @list: list entry in a graph entities list
+ * @node: the entity's DT node
+ * @entity: media entity, from the corresponding V4L2 subdev
+ * @asd: subdev asynchronous registration information
+ * @subdev: V4L2 subdev
+ */
+struct sun6i_graph_entity {
+ struct list_head list;
+ struct device_node *node;
+ struct media_entity *entity;
+
+ struct v4l2_async_subdev asd;
+ struct v4l2_subdev *subdev;
+};
+
+/* -----------------------------------------------------------------------------
+ * Graph Management
+ */
+
+static struct sun6i_graph_entity *
+sun6i_graph_find_entity(struct sun6i_csi *csi,
+ const struct device_node *node)
+{
+ struct sun6i_graph_entity *entity;
+
+ list_for_each_entry(entity, &csi->entities, list) {
+ if (entity->node == node)
+ return entity;
+ }
+
+ return NULL;
+}
+
+static int sun6i_graph_build_one(struct sun6i_csi *csi,
+ struct sun6i_graph_entity *entity)
+{
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ struct media_entity *local = entity->entity;
+ struct media_entity *remote;
+ struct media_pad *local_pad;
+ struct media_pad *remote_pad;
+ struct sun6i_graph_entity *ent;
+ struct v4l2_fwnode_link link;
+ struct device_node *ep = NULL;
+ struct device_node *next;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "creating links for entity %s\n", local->name);
+
+ while (1) {
+ /* Get the next endpoint and parse its link. */
+ next = of_graph_get_next_endpoint(entity->node, ep);
+ if (next == NULL)
+ break;
+
+ of_node_put(ep);
+ ep = next;
+
+ dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
+
+ ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to parse link for %s\n",
+ ep->full_name);
+ continue;
+ }
+
+ /* Skip sink ports, they will be processed from the other end of
+ * the link.
+ */
+ if (link.local_port >= local->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.local_port,
+ to_of_node(link.local_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ local_pad = &local->pads[link.local_port];
+
+ if (local_pad->flags & MEDIA_PAD_FL_SINK) {
+ dev_dbg(csi->dev, "skipping sink port %s:%u\n",
+ to_of_node(link.local_node)->full_name,
+ link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* Skip video node, they will be processed separately. */
+ if (link.remote_node == of_fwnode_handle(csi->dev->of_node)) {
+ dev_dbg(csi->dev, "skipping CSI port %s:%u\n",
+ to_of_node(link.local_node)->full_name,
+ link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* Find the remote entity. */
+ ent = sun6i_graph_find_entity(csi,
+ to_of_node(link.remote_node));
+ if (ent == NULL) {
+ dev_err(csi->dev, "no entity found for %s\n",
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ remote = ent->entity;
+
+ if (link.remote_port >= remote->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.remote_port,
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ remote_pad = &remote->pads[link.remote_port];
+
+ v4l2_fwnode_put_link(&link);
+
+ /* Create the media link. */
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+
+ ret = media_create_pad_link(local, local_pad->index,
+ remote, remote_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to create %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+ break;
+ }
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_build_video(struct sun6i_csi *csi)
+{
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ struct device_node *node = csi->dev->of_node;
+ struct media_entity *source;
+ struct media_entity *sink;
+ struct media_pad *source_pad;
+ struct media_pad *sink_pad;
+ struct sun6i_graph_entity *ent;
+ struct v4l2_fwnode_link link;
+ struct device_node *ep = NULL;
+ struct device_node *next;
+ struct sun6i_video *video = &csi->video;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "creating link for video node\n");
+
+ while (1) {
+ /* Get the next endpoint and parse its link. */
+ next = of_graph_get_next_endpoint(node, ep);
+ if (next == NULL)
+ break;
+
+ of_node_put(ep);
+ ep = next;
+
+ dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
+
+ ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to parse link for %s\n",
+ ep->full_name);
+ continue;
+ }
+
+ /* Save the video port settings */
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep),
+ &csi->v4l2_ep);
+ if (ret) {
+ ret = -EINVAL;
+ dev_err(csi->dev, "Could not parse the endpoint\n");
+ v4l2_fwnode_put_link(&link);
+ break;
+ }
+
+ dev_dbg(csi->dev, "creating link for video node %s\n",
+ video->vdev.name);
+
+ /* Find the remote entity. */
+ ent = sun6i_graph_find_entity(csi,
+ to_of_node(link.remote_node));
+ if (ent == NULL) {
+ dev_err(csi->dev, "no entity found for %s\n",
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ if (link.remote_port >= ent->entity->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.remote_port,
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ source = ent->entity;
+ source_pad = &source->pads[link.remote_port];
+ sink = &video->vdev.entity;
+ sink_pad = &video->pad;
+
+ v4l2_fwnode_put_link(&link);
+
+ /* Create the media link. */
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+
+ ret = media_create_pad_link(source, source_pad->index,
+ sink, sink_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to create %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+ break;
+ }
+
+ /* Notify video node */
+ ret = media_entity_call(sink, link_setup, sink_pad, source_pad,
+ link_flags);
+ if (ret == -ENOIOCTLCMD)
+ ret = 0;
+
+ /* found one */
+ break;
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct sun6i_csi *csi =
+ container_of(notifier, struct sun6i_csi, notifier);
+ struct sun6i_graph_entity *entity;
+ int ret;
+
+ dev_dbg(csi->dev, "notify complete, all subdevs registered\n");
+
+ /* Create links for every entity. */
+ list_for_each_entry(entity, &csi->entities, list) {
+ ret = sun6i_graph_build_one(csi, entity);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Create links for video node. */
+ ret = sun6i_graph_build_video(csi);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
+ if (ret < 0)
+ dev_err(csi->dev, "failed to register subdev nodes\n");
+
+ return media_device_register(&csi->media_dev);
+}
+
+static int sun6i_graph_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct sun6i_csi *csi =
+ container_of(notifier, struct sun6i_csi, notifier);
+ struct sun6i_graph_entity *entity;
+
+ /* Locate the entity corresponding to the bound subdev and store the
+ * subdev pointer.
+ */
+ list_for_each_entry(entity, &csi->entities, list) {
+ if (entity->node != subdev->dev->of_node)
+ continue;
+
+ if (entity->subdev) {
+ dev_err(csi->dev, "duplicate subdev for node %s\n",
+ entity->node->full_name);
+ return -EINVAL;
+ }
+
+ dev_dbg(csi->dev, "subdev %s bound\n", subdev->name);
+ entity->entity = &subdev->entity;
+ entity->subdev = subdev;
+ return 0;
+ }
+
+ dev_err(csi->dev, "no entity for subdev %s\n", subdev->name);
+ return -EINVAL;
+}
+
+static int sun6i_graph_parse_one(struct sun6i_csi *csi,
+ struct device_node *node)
+{
+ struct sun6i_graph_entity *entity;
+ struct device_node *remote;
+ struct device_node *ep = NULL;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "parsing node %s\n", node->full_name);
+
+ while (1) {
+ ep = of_graph_get_next_endpoint(node, ep);
+ if (ep == NULL)
+ break;
+
+ dev_dbg(csi->dev, "handling endpoint %s\n", ep->full_name);
+
+ remote = of_graph_get_remote_port_parent(ep);
+ if (remote == NULL) {
+ ret = -EINVAL;
+ break;
+ }
+
+ /* Skip entities that we have already processed. */
+ if (remote == csi->dev->of_node ||
+ sun6i_graph_find_entity(csi, remote)) {
+ of_node_put(remote);
+ continue;
+ }
+
+ entity = devm_kzalloc(csi->dev, sizeof(*entity), GFP_KERNEL);
+ if (entity == NULL) {
+ of_node_put(remote);
+ ret = -ENOMEM;
+ break;
+ }
+
+ entity->node = remote;
+ entity->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+ entity->asd.match.fwnode.fwnode = of_fwnode_handle(remote);
+ list_add_tail(&entity->list, &csi->entities);
+ csi->num_subdevs++;
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_parse(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entity;
+ int ret;
+
+ /*
+ * Walk the links to parse the full graph. Start by parsing the
+ * composite node and then parse entities in turn. The list_for_each
+ * loop will handle entities added at the end of the list while walking
+ * the links.
+ */
+ ret = sun6i_graph_parse_one(csi, csi->dev->of_node);
+ if (ret < 0)
+ return 0;
+
+ list_for_each_entry(entity, &csi->entities, list) {
+ ret = sun6i_graph_parse_one(csi, entity->node);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static void sun6i_graph_cleanup(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entityp;
+ struct sun6i_graph_entity *entity;
+
+ v4l2_async_notifier_unregister(&csi->notifier);
+
+ list_for_each_entry_safe(entity, entityp, &csi->entities, list) {
+ of_node_put(entity->node);
+ list_del(&entity->list);
+ }
+}
+
+static int sun6i_graph_init(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entity;
+ struct v4l2_async_subdev **subdevs = NULL;
+ unsigned int num_subdevs;
+ unsigned int i;
+ int ret;
+
+ /* Parse the graph to extract a list of subdevice DT nodes. */
+ ret = sun6i_graph_parse(csi);
+ if (ret < 0) {
+ dev_err(csi->dev, "graph parsing failed\n");
+ goto done;
+ }
+
+ if (!csi->num_subdevs) {
+ dev_err(csi->dev, "no subdev found in graph\n");
+ goto done;
+ }
+
+ /* Register the subdevices notifier. */
+ num_subdevs = csi->num_subdevs;
+ subdevs = devm_kzalloc(csi->dev, sizeof(*subdevs) * num_subdevs,
+ GFP_KERNEL);
+ if (subdevs == NULL) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ i = 0;
+ list_for_each_entry(entity, &csi->entities, list)
+ subdevs[i++] = &entity->asd;
+
+ csi->notifier.subdevs = subdevs;
+ csi->notifier.num_subdevs = num_subdevs;
+ csi->notifier.bound = sun6i_graph_notify_bound;
+ csi->notifier.complete = sun6i_graph_notify_complete;
+
+ ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);
+ if (ret < 0) {
+ dev_err(csi->dev, "notifier registration failed\n");
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret < 0)
+ sun6i_graph_cleanup(csi);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Media Controller and V4L2
+ */
+
+static void sun6i_csi_v4l2_cleanup(struct sun6i_csi *csi)
+{
+ v4l2_device_unregister(&csi->v4l2_dev);
+ media_device_unregister(&csi->media_dev);
+ media_device_cleanup(&csi->media_dev);
+}
+
+static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->media_dev.dev = csi->dev;
+ strlcpy(csi->media_dev.model, "Allwinner Video Capture Device",
+ sizeof(csi->media_dev.model));
+ csi->media_dev.hw_revision = 0;
+
+ media_device_init(&csi->media_dev);
+
+ csi->v4l2_dev.mdev = &csi->media_dev;
+ ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
+ if (ret < 0) {
+ dev_err(csi->dev, "V4L2 device registration failed (%d)\n",
+ ret);
+ media_device_cleanup(&csi->media_dev);
+ return ret;
+ }
+ return 0;
+}
+
+int sun6i_csi_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->num_subdevs = 0;
+ INIT_LIST_HEAD(&csi->entities);
+
+ ret = sun6i_csi_v4l2_init(csi);
+ if (ret < 0)
+ return ret;
+
+ ret = sun6i_video_init(&csi->video, csi, "sun6i-csi");
+ if (ret < 0)
+ goto v4l2_clean;
+
+ ret = sun6i_graph_init(csi);
+ if (ret < 0)
+ goto video_clean;
+
+ return 0;
+
+video_clean:
+ sun6i_video_cleanup(&csi->video);
+v4l2_clean:
+ sun6i_csi_v4l2_cleanup(csi);
+ return ret;
+}
+
+int sun6i_csi_cleanup(struct sun6i_csi *csi)
+{
+ sun6i_video_cleanup(&csi->video);
+ sun6i_graph_cleanup(csi);
+ sun6i_csi_v4l2_cleanup(csi);
+
+ return 0;
+}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sun6i-csi/sun6i_csi.h
new file mode 100644
index 0000000..167f6bc
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_H__
+#define __SUN6I_CSI_H__
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#include "sun6i_video.h"
+
+struct sun6i_csi;
+
+/**
+ * struct sun6i_csi_config - configs for sun6i csi
+ * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @code: media bus format code (MEDIA_BUS_FMT_*)
+ * @field: used interlacing type (enum v4l2_field)
+ * @width: frame width
+ * @height: frame height
+ */
+struct sun6i_csi_config {
+ u32 pixelformat;
+ u32 code;
+ u32 field;
+ u32 width;
+ u32 height;
+};
+
+struct sun6i_csi_ops {
+ int (*get_supported_pixformats)(struct sun6i_csi *csi,
+ const u32 **pixformats);
+ bool (*is_format_support)(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code);
+ int (*s_power)(struct sun6i_csi *csi, bool enable);
+ int (*update_config)(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config);
+ int (*update_buf_addr)(struct sun6i_csi *csi, dma_addr_t addr);
+ int (*s_stream)(struct sun6i_csi *csi, bool enable);
+};
+
+struct sun6i_csi {
+ struct device *dev;
+ struct v4l2_device v4l2_dev;
+ struct media_device media_dev;
+
+ struct list_head entities;
+ unsigned int num_subdevs;
+ struct v4l2_async_notifier notifier;
+
+ /* video port settings */
+ struct v4l2_fwnode_endpoint v4l2_ep;
+
+ struct sun6i_csi_config config;
+
+ struct sun6i_video video;
+
+ struct sun6i_csi_ops *ops;
+};
+
+int sun6i_csi_init(struct sun6i_csi *csi);
+int sun6i_csi_cleanup(struct sun6i_csi *csi);
+
+/**
+ * sun6i_csi_get_supported_pixformats() - get csi supported pixformats
+ * @csi: pointer to the csi
+ * @pixformats: supported pixformats return from csi
+ *
+ * @return the count of pixformats or error(< 0)
+ */
+static inline int
+sun6i_csi_get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (csi->ops != NULL && csi->ops->get_supported_pixformats != NULL)
+ return csi->ops->get_supported_pixformats(csi, pixformats);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_is_format_support() - check if the format supported by csi
+ * @csi: pointer to the csi
+ * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @mbus_code: media bus format code (MEDIA_BUS_FMT_*)
+ */
+static inline bool
+sun6i_csi_is_format_support(struct sun6i_csi *csi, u32 pixformat, u32 mbus_code)
+{
+ if (csi->ops != NULL && csi->ops->is_format_support != NULL)
+ return csi->ops->is_format_support(csi, pixformat, mbus_code);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_power() - power on/off the csi
+ * @csi: pointer to the csi
+ * @enable: on/off
+ */
+static inline int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable)
+{
+ if (csi->ops != NULL && csi->ops->s_power != NULL)
+ return csi->ops->s_power(csi, enable);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_config() - update the csi register setttings
+ * @csi: pointer to the csi
+ * @config: see struct sun6i_csi_config
+ */
+static inline int
+sun6i_csi_update_config(struct sun6i_csi *csi, struct sun6i_csi_config *config)
+{
+ if (csi->ops != NULL && csi->ops->update_config != NULL)
+ return csi->ops->update_config(csi, config);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_buf_addr() - update the csi frame buffer address
+ * @csi: pointer to the csi
+ * @addr: frame buffer's physical address
+ */
+static inline int sun6i_csi_update_buf_addr(struct sun6i_csi *csi,
+ dma_addr_t addr)
+{
+ if (csi->ops != NULL && csi->ops->update_buf_addr != NULL)
+ return csi->ops->update_buf_addr(csi, addr);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_stream() - start/stop csi streaming
+ * @csi: pointer to the csi
+ * @enable: start/stop
+ */
+static inline int sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable)
+{
+ if (csi->ops != NULL && csi->ops->s_stream != NULL)
+ return csi->ops->s_stream(csi, enable);
+
+ return -ENOIOCTLCMD;
+}
+
+static inline int v4l2_pixformat_get_bpp(unsigned int pixformat)
+{
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return 8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return 10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return 12;
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV422P:
+ return 16;
+ case V4L2_PIX_FMT_RGB24:
+ case V4L2_PIX_FMT_BGR24:
+ return 24;
+ case V4L2_PIX_FMT_RGB32:
+ case V4L2_PIX_FMT_BGR32:
+ return 32;
+ }
+
+ return 0;
+}
+
+#endif /* __SUN6I_CSI_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
new file mode 100644
index 0000000..f940daa
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_csi_v3s.h"
+
+#define MODULE_NAME "sun6i-csi"
+
+struct sun6i_csi_dev {
+ struct sun6i_csi csi;
+ struct device *dev;
+
+ struct regmap *regmap;
+ struct clk *clk_ahb;
+ struct clk *clk_mod;
+ struct clk *clk_ram;
+ struct reset_control *rstc_ahb;
+
+ int planar_offset[3];
+};
+
+static const u32 supported_pixformats[] = {
+ V4L2_PIX_FMT_SBGGR8,
+ V4L2_PIX_FMT_SGBRG8,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_PIX_FMT_SRGGB8,
+ V4L2_PIX_FMT_SBGGR10,
+ V4L2_PIX_FMT_SGBRG10,
+ V4L2_PIX_FMT_SGRBG10,
+ V4L2_PIX_FMT_SRGGB10,
+ V4L2_PIX_FMT_SBGGR12,
+ V4L2_PIX_FMT_SGBRG12,
+ V4L2_PIX_FMT_SGRBG12,
+ V4L2_PIX_FMT_SRGGB12,
+ V4L2_PIX_FMT_YUYV,
+ V4L2_PIX_FMT_YVYU,
+ V4L2_PIX_FMT_UYVY,
+ V4L2_PIX_FMT_VYUY,
+ V4L2_PIX_FMT_HM12,
+ V4L2_PIX_FMT_NV12,
+ V4L2_PIX_FMT_NV21,
+ V4L2_PIX_FMT_YUV420,
+ V4L2_PIX_FMT_YVU420,
+ V4L2_PIX_FMT_NV16,
+ V4L2_PIX_FMT_NV61,
+ V4L2_PIX_FMT_YUV422P,
+};
+
+static inline struct sun6i_csi_dev *sun6i_csi_to_dev(struct sun6i_csi *csi)
+{
+ return container_of(csi, struct sun6i_csi_dev, csi);
+}
+
+/* TODO add 10&12 bit YUV, RGB support */
+static bool __is_format_support(struct sun6i_csi_dev *sdev,
+ u32 fourcc, u32 mbus_code)
+{
+ /*
+ * Some video receiver have capability both 8bit and 16bit.
+ * Identify the media bus format from device tree.
+ */
+ if (((sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_PARALLEL
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_BT656)
+ && sdev->csi.v4l2_ep.bus.parallel.bus_width == 16)
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_CSI2) {
+ switch (fourcc) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ switch (fourcc) {
+ case V4L2_PIX_FMT_SBGGR8:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG8:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG8:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB8:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SBGGR10:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG10:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG10:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB10:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SBGGR12:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG12:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG12:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB12:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12)
+ return true;
+ break;
+
+ case V4L2_PIX_FMT_YUYV:
+ if (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_YVYU:
+ if (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ if (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_VYUY:
+ if (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8)
+ return true;
+ break;
+
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+static enum csi_input_fmt get_csi_input_format(u32 mbus_code, u32 pixformat)
+{
+ /* bayer */
+ if ((mbus_code & 0xF000) == 0x3000)
+ return CSI_INPUT_FORMAT_RAW;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return CSI_INPUT_FORMAT_RAW;
+ }
+
+ /* not support YUV420 input format yet */
+ return CSI_INPUT_FORMAT_YUV422;
+}
+
+static enum csi_output_fmt get_csi_output_format(u32 pixformat, u32 field)
+{
+ bool buf_interlaced = false;
+ if (field == V4L2_FIELD_INTERLACED
+ || field == V4L2_FIELD_INTERLACED_TB
+ || field == V4L2_FIELD_INTERLACED_BT)
+ buf_interlaced = true;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12;
+
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+
+ case V4L2_PIX_FMT_HM12:
+ return buf_interlaced ? CSI_FRAME_MB_YUV420 :
+ CSI_FIELD_MB_YUV420;
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 :
+ CSI_FIELD_UV_CB_YUV420;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 :
+ CSI_FIELD_PLANAR_YUV420;
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 :
+ CSI_FIELD_UV_CB_YUV422;
+ case V4L2_PIX_FMT_YUV422P:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 :
+ CSI_FIELD_PLANAR_YUV422;
+ }
+
+ return 0;
+}
+
+static enum csi_input_seq get_csi_input_seq(u32 mbus_code, u32 pixformat)
+{
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch(mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YUYV;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YVYU;
+ }
+ break;
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YVU420:
+ switch(mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YVYU;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YUYV;
+ }
+ break;
+ }
+
+ return CSI_INPUT_SEQ_YUYV;
+}
+
+#ifdef DEBUG
+static void sun6i_csi_dump_regs(struct sun6i_csi_dev *sdev)
+{
+ struct regmap *regmap = sdev->regmap;
+ u32 val;
+
+ regmap_read(regmap, CSI_EN_REG, &val);
+ printk("CSI_EN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_IF_CFG_REG, &val);
+ printk("CSI_IF_CFG_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CAP_REG, &val);
+ printk("CSI_CAP_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_SYNC_CNT_REG, &val);
+ printk("CSI_SYNC_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_FIFO_THRS_REG, &val);
+ printk("CSI_FIFO_THRS_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_PTN_LEN_REG, &val);
+ printk("CSI_PTN_LEN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_PTN_ADDR_REG, &val);
+ printk("CSI_PTN_ADDR_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_VER_REG, &val);
+ printk("CSI_VER_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_CFG_REG, &val);
+ printk("CSI_CH_CFG_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_SCALE_REG, &val);
+ printk("CSI_CH_SCALE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F0_BUFA_REG, &val);
+ printk("CSI_CH_F0_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F1_BUFA_REG, &val);
+ printk("CSI_CH_F1_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F2_BUFA_REG, &val);
+ printk("CSI_CH_F2_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_STA_REG, &val);
+ printk("CSI_CH_STA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_INT_EN_REG, &val);
+ printk("CSI_CH_INT_EN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &val);
+ printk("CSI_CH_INT_STA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FLD1_VSIZE_REG, &val);
+ printk("CSI_CH_FLD1_VSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_HSIZE_REG, &val);
+ printk("CSI_CH_HSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_VSIZE_REG, &val);
+ printk("CSI_CH_VSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_BUF_LEN_REG, &val);
+ printk("CSI_CH_BUF_LEN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FLIP_SIZE_REG, &val);
+ printk("CSI_CH_FLIP_SIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FRM_CLK_CNT_REG, &val);
+ printk("CSI_CH_FRM_CLK_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_ACC_ITNL_CLK_CNT_REG, &val);
+ printk("CSI_CH_ACC_ITNL_CLK_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FIFO_STAT_REG, &val);
+ printk("CSI_CH_FIFO_STAT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_PCLK_STAT_REG, &val);
+ printk("CSI_CH_PCLK_STAT_REG=0x%x\n", val);
+}
+#endif
+
+static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev)
+{
+ struct v4l2_fwnode_endpoint *endpoint = &sdev->csi.v4l2_ep;
+ unsigned char bus_width;
+ u32 flags;
+ u32 cfg;
+
+ bus_width = endpoint->bus.parallel.bus_width;
+
+ regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK |
+ CSI_IF_CFG_IF_DATA_WIDTH_MASK |
+ CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK |
+ CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK);
+
+ switch (endpoint->bus_type) {
+ case V4L2_MBUS_CSI2:
+ cfg |= CSI_IF_CFG_MIPI_IF_MIPI;
+ break;
+ case V4L2_MBUS_PARALLEL:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT :
+ CSI_IF_CFG_CSI_IF_YUV422_INTLV;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_VREF_POL_POSITIVE;
+ if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_HREF_POL_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ case V4L2_MBUS_BT656:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 :
+ CSI_IF_CFG_CSI_IF_BT656;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ default:
+ BUG();
+ break;
+ }
+
+ switch (bus_width) {
+ case 8:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT;
+ break;
+ case 10:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT;
+ break;
+ case 12:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT;
+ break;
+ default:
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_IF_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_format(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi *csi = &sdev->csi;
+ u32 cfg;
+ u32 val;
+
+ regmap_read(sdev->regmap, CSI_CH_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK |
+ CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN |
+ CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK |
+ CSI_CH_CFG_INPUT_SEQ_MASK);
+
+ val = get_csi_input_format(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_FMT(val);
+
+ val = get_csi_output_format(csi->config.code, csi->config.field);
+ cfg |= CSI_CH_CFG_OUTPUT_FMT(val);
+
+ val = get_csi_input_seq(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_SEQ(val);
+
+ if (csi->config.field == V4L2_FIELD_TOP)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0;
+ else if (csi->config.field == V4L2_FIELD_BOTTOM)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1;
+ else
+ cfg |= CSI_CH_CFG_FIELD_SEL_BOTH;
+
+ regmap_write(sdev->regmap, CSI_CH_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_window(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi_config *config = &sdev->csi.config;
+ u32 bytesperline_y;
+ u32 bytesperline_c;
+ int *planar_offset = sdev->planar_offset;
+
+ regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
+ CSI_CH_HSIZE_HOR_LEN(config->width) |
+ CSI_CH_HSIZE_HOR_START(0));
+ regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
+ CSI_CH_VSIZE_VER_LEN(config->height) |
+ CSI_CH_VSIZE_VER_START(0));
+
+ planar_offset[0] = 0;
+ switch(config->pixelformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = -1;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width / 2;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * config->height / 2;
+ break;
+ case V4L2_PIX_FMT_YUV422P:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width / 2;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * config->height;
+ break;
+ default: /* raw */
+ bytesperline_y = (v4l2_pixformat_get_bpp(config->pixelformat) *
+ config->width) / 8;
+ bytesperline_c = 0;
+ planar_offset[1] = -1;
+ planar_offset[2] = -1;
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_CH_BUF_LEN_REG,
+ CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) |
+ CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y));
+}
+
+static int get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (pixformats != NULL)
+ *pixformats = supported_pixformats;
+
+ return ARRAY_SIZE(supported_pixformats);
+}
+
+static bool is_format_support(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ return __is_format_support(sdev, pixformat, mbus_code);
+}
+
+static int set_power(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+ int ret;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+
+ clk_disable_unprepare(sdev->clk_ram);
+ clk_disable_unprepare(sdev->clk_mod);
+ clk_disable_unprepare(sdev->clk_ahb);
+ reset_control_assert(sdev->rstc_ahb);
+ return 0;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ahb);
+ if (ret) {
+ dev_err(sdev->dev, "Enable ahb clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_mod);
+ if (ret) {
+ dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ram);
+ if (ret) {
+ dev_err(sdev->dev, "Enable clk_dram_csi clk err %d\n", ret);
+ return ret;
+ }
+
+ if (!IS_ERR_OR_NULL(sdev->rstc_ahb)) {
+ ret = reset_control_deassert(sdev->rstc_ahb);
+ if (ret) {
+ dev_err(sdev->dev, "reset err %d\n", ret);
+ return ret;
+ }
+ }
+
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
+
+ return 0;
+}
+
+static int update_config(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ if (config == NULL)
+ return -EINVAL;
+
+ memcpy(&csi->config, config, sizeof(csi->config));
+
+ sun6i_csi_setup_bus(sdev);
+ sun6i_csi_set_format(sdev);
+ sun6i_csi_set_window(sdev);
+
+ return 0;
+}
+
+static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ /* transform physical address to bus address */
+ dma_addr_t bus_addr = addr - 0x40000000;
+
+ regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
+ (bus_addr + sdev->planar_offset[0]) >> 2);
+ if (sdev->planar_offset[1] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
+ (bus_addr + sdev->planar_offset[1]) >> 2);
+ if (sdev->planar_offset[2] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
+ (bus_addr + sdev->planar_offset[2]) >> 2);
+
+ return 0;
+}
+
+static int set_stream(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0);
+ regmap_write(regmap, CSI_CH_INT_EN_REG, 0);
+ return 0;
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF);
+ regmap_write(regmap, CSI_CH_INT_EN_REG,
+ CSI_CH_INT_EN_HB_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO2_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO1_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO0_OF_INT_EN |
+ CSI_CH_INT_EN_FD_INT_EN |
+ CSI_CH_INT_EN_CD_INT_EN);
+
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON,
+ CSI_CAP_CH0_VCAP_ON);
+
+ return 0;
+}
+
+static struct sun6i_csi_ops csi_ops = {
+ .get_supported_pixformats = get_supported_pixformats,
+ .is_format_support = is_format_support,
+ .s_power = set_power,
+ .update_config = update_config,
+ .update_buf_addr = update_buf_addr,
+ .s_stream = set_stream,
+};
+
+static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
+{
+ struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
+ struct regmap *regmap = sdev->regmap;
+ u32 status;
+
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
+
+ if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
+ (status & CSI_CH_INT_STA_HB_OF_PD)) {
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
+ CSI_EN_CSI_EN);
+ return IRQ_HANDLED;
+ }
+
+ if (status & CSI_CH_INT_STA_FD_PD) {
+ sun6i_video_frame_done(&sdev->csi.video);
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config sun6i_csi_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x1000,
+};
+
+static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+ void __iomem *io_base;
+ int ret;
+ int irq;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ io_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(io_base))
+ return PTR_ERR(io_base);
+
+ sdev->regmap = devm_regmap_init_mmio(&pdev->dev, io_base,
+ &sun6i_csi_regmap_config);
+ if (IS_ERR(sdev->regmap)) {
+ dev_err(&pdev->dev, "Failed to init register map\n");
+ return PTR_ERR(sdev->regmap);
+ }
+
+ sdev->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
+ if (IS_ERR(sdev->clk_ahb)) {
+ dev_err(&pdev->dev, "Unable to acquire ahb clock\n");
+ return PTR_ERR(sdev->clk_ahb);
+ }
+
+ sdev->clk_mod = devm_clk_get(&pdev->dev, "mod");
+ if (IS_ERR(sdev->clk_mod)) {
+ dev_err(&pdev->dev, "Unable to acquire csi clock\n");
+ return PTR_ERR(sdev->clk_mod);
+ }
+
+ sdev->clk_ram = devm_clk_get(&pdev->dev, "ram");
+ if (IS_ERR(sdev->clk_ram)) {
+ dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n");
+ return PTR_ERR(sdev->clk_ram);
+ }
+
+ sdev->rstc_ahb = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(sdev->rstc_ahb)) {
+ dev_err(&pdev->dev, "Cannot get reset controller\n");
+ return PTR_ERR(sdev->rstc_ahb);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "No csi IRQ specified\n");
+ ret = -ENXIO;
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, sun6i_csi_isr, 0, MODULE_NAME,
+ sdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot request csi IRQ\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int sun6i_csi_probe(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev;
+ int ret;
+
+ sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
+ if (!sdev)
+ return -ENOMEM;
+
+ sdev->dev = &pdev->dev;
+
+ ret = sun6i_csi_resource_request(sdev, pdev);
+ if (ret)
+ return ret;
+
+ sdev->csi.dev = &pdev->dev;
+ sdev->csi.ops = &csi_ops;
+ ret = sun6i_csi_init(&sdev->csi);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, sdev);
+
+ return 0;
+}
+
+static int sun6i_csi_remove(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev = platform_get_drvdata(pdev);
+
+ sun6i_csi_cleanup(&sdev->csi);
+
+ return 0;
+}
+
+static const struct of_device_id sun6i_csi_of_match[] = {
+ { .compatible = "allwinner,sun8i-v3s-csi", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sun6i_csi_of_match);
+
+static struct platform_driver sun6i_csi_platform_driver = {
+ .probe = sun6i_csi_probe,
+ .remove = sun6i_csi_remove,
+ .driver = {
+ .name = MODULE_NAME,
+ .of_match_table = of_match_ptr(sun6i_csi_of_match),
+ },
+};
+module_platform_driver(sun6i_csi_platform_driver);
+
+MODULE_DESCRIPTION("Allwinner V3s Camera Sensor Interface driver");
+MODULE_AUTHOR("Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
new file mode 100644
index 0000000..6c87b74
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_V3S_H__
+#define __SUN6I_CSI_V3S_H__
+
+#include <linux/kernel.h>
+
+#define CSI_EN_REG 0x0
+#define CSI_EN_VER_EN BIT(30)
+#define CSI_EN_CSI_EN BIT(0)
+
+#define CSI_IF_CFG_REG 0x4
+#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21)
+#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_FPS_DS_EN BIT(20)
+#define CSI_IF_CFG_FIELD_MASK BIT(19)
+#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_VREF_POL_MASK BIT(18)
+#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_MASK BIT(17)
+#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_MASK BIT(16)
+#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8)
+#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_MIPI_IF_MASK BIT(7)
+#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7)
+#define CSI_IF_CFG_MIPI_IF_MIPI (1 << 7)
+#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0)
+#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+
+#define CSI_CAP_REG 0x8
+#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2)
+#define CSI_CAP_CH0_CAP_MASK(count) ((count << 2) & CSI_CAP_CH0_CAP_MASK_MASK)
+#define CSI_CAP_CH0_VCAP_ON BIT(1)
+#define CSI_CAP_CH0_SCAP_ON BIT(0)
+
+#define CSI_SYNC_CNT_REG 0xc
+#define CSI_FIFO_THRS_REG 0x10
+#define CSI_BT656_HEAD_CFG_REG 0x14
+#define CSI_PTN_LEN_REG 0x30
+#define CSI_PTN_ADDR_REG 0x34
+#define CSI_VER_REG 0x3c
+
+#define CSI_CH_CFG_REG 0x44
+#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20)
+#define CSI_CH_CFG_INPUT_FMT(fmt) ((fmt << 20) & CSI_CH_CFG_INPUT_FMT_MASK)
+#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16)
+#define CSI_CH_CFG_OUTPUT_FMT(fmt) ((fmt << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK)
+#define CSI_CH_CFG_VFLIP_EN BIT(13)
+#define CSI_CH_CFG_HFLIP_EN BIT(12)
+#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10)
+#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8)
+#define CSI_CH_CFG_INPUT_SEQ(seq) ((seq << 8) & CSI_CH_CFG_INPUT_SEQ_MASK)
+
+#define CSI_CH_SCALE_REG 0x4c
+#define CSI_CH_SCALE_QUART_EN BIT(0)
+
+#define CSI_CH_F0_BUFA_REG 0x50
+
+#define CSI_CH_F1_BUFA_REG 0x58
+
+#define CSI_CH_F2_BUFA_REG 0x60
+
+#define CSI_CH_STA_REG 0x6c
+#define CSI_CH_STA_FIELD_STA_MASK BIT(2)
+#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_VCAP_STA BIT(1)
+#define CSI_CH_STA_SCAP_STA BIT(0)
+
+#define CSI_CH_INT_EN_REG 0x70
+#define CSI_CH_INT_EN_VS_INT_EN BIT(7)
+#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6)
+#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5)
+#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4)
+#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3)
+#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2)
+#define CSI_CH_INT_EN_FD_INT_EN BIT(1)
+#define CSI_CH_INT_EN_CD_INT_EN BIT(0)
+
+#define CSI_CH_INT_STA_REG 0x74
+#define CSI_CH_INT_STA_VS_PD BIT(7)
+#define CSI_CH_INT_STA_HB_OF_PD BIT(6)
+#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5)
+#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4)
+#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3)
+#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2)
+#define CSI_CH_INT_STA_FD_PD BIT(1)
+#define CSI_CH_INT_STA_CD_PD BIT(0)
+
+#define CSI_CH_FLD1_VSIZE_REG 0x74
+
+#define CSI_CH_HSIZE_REG 0x80
+#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_HSIZE_HOR_LEN(len) ((len << 16) & CSI_CH_HSIZE_HOR_LEN_MASK)
+#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0)
+#define CSI_CH_HSIZE_HOR_START(start) ((start << 0) & CSI_CH_HSIZE_HOR_START_MASK)
+
+#define CSI_CH_VSIZE_REG 0x84
+#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_VSIZE_VER_LEN(len) ((len << 16) & CSI_CH_VSIZE_VER_LEN_MASK)
+#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0)
+#define CSI_CH_VSIZE_VER_START(start) ((start << 0) & CSI_CH_VSIZE_VER_START_MASK)
+
+#define CSI_CH_BUF_LEN_REG 0x88
+#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16)
+#define CSI_CH_BUF_LEN_BUF_LEN_C(len) ((len << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) ((len << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK)
+
+#define CSI_CH_FLIP_SIZE_REG 0x8c
+#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_FLIP_SIZE_VER_LEN(len) ((len << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK)
+#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0)
+#define CSI_CH_FLIP_SIZE_VALID_LEN(len) ((len << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK)
+
+#define CSI_CH_FRM_CLK_CNT_REG 0x90
+#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94
+#define CSI_CH_FIFO_STAT_REG 0x98
+#define CSI_CH_PCLK_STAT_REG 0x9c
+
+/*
+ * csi input data format
+ */
+enum csi_input_fmt
+{
+ CSI_INPUT_FORMAT_RAW = 0,
+ CSI_INPUT_FORMAT_YUV422 = 3,
+ CSI_INPUT_FORMAT_YUV420 = 4,
+};
+
+/*
+ * csi output data format
+ */
+enum csi_output_fmt
+{
+ /* only when input format is RAW */
+ CSI_FIELD_RAW_8 = 0,
+ CSI_FIELD_RAW_10 = 1,
+ CSI_FIELD_RAW_12 = 2,
+ CSI_FIELD_RGB565 = 4,
+ CSI_FIELD_RGB888 = 5,
+ CSI_FIELD_PRGB888 = 6,
+ CSI_FRAME_RAW_8 = 8,
+ CSI_FRAME_RAW_10 = 9,
+ CSI_FRAME_RAW_12 = 10,
+ CSI_FRAME_RGB565 = 12,
+ CSI_FRAME_RGB888 = 13,
+ CSI_FRAME_PRGB888 = 14,
+
+ /* only when input format is YUV422/YUV420 */
+ CSI_FIELD_PLANAR_YUV422 = 0,
+ CSI_FIELD_PLANAR_YUV420 = 1,
+ CSI_FRAME_PLANAR_YUV420 = 2,
+ CSI_FRAME_PLANAR_YUV422 = 3,
+ CSI_FIELD_UV_CB_YUV422 = 4,
+ CSI_FIELD_UV_CB_YUV420 = 5,
+ CSI_FRAME_UV_CB_YUV420 = 6,
+ CSI_FRAME_UV_CB_YUV422 = 7,
+ CSI_FIELD_MB_YUV422 = 8,
+ CSI_FIELD_MB_YUV420 = 9,
+ CSI_FRAME_MB_YUV420 = 10,
+ CSI_FRAME_MB_YUV422 = 11,
+ CSI_FIELD_UV_CB_YUV422_10 = 12,
+ CSI_FIELD_UV_CB_YUV420_10 = 13,
+};
+
+/*
+ * csi YUV input data sequence
+ */
+enum csi_input_seq
+{
+ /* only when input format is YUV422 */
+ CSI_INPUT_SEQ_YUYV = 0,
+ CSI_INPUT_SEQ_YVYU,
+ CSI_INPUT_SEQ_UYVY,
+ CSI_INPUT_SEQ_VYUY,
+};
+
+#endif /* __SUN6I_CSI_V3S_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.c b/drivers/media/platform/sun6i-csi/sun6i_video.c
new file mode 100644
index 0000000..0c5dbd2
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_video.c
@@ -0,0 +1,663 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/of.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_video.h"
+
+struct sun6i_csi_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+
+ dma_addr_t dma_addr;
+};
+
+static struct sun6i_csi_format *
+find_format_by_fourcc(struct sun6i_video *video, unsigned int fourcc)
+{
+ unsigned int num_formats = video->num_formats;
+ struct sun6i_csi_format *fmt;
+ unsigned int i;
+
+ for (i = 0; i < num_formats; i++) {
+ fmt = &video->formats[i];
+ if (fmt->fourcc == fourcc)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+static struct v4l2_subdev *
+sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad)
+{
+ struct media_pad *remote;
+
+ remote = media_entity_remote_pad(&video->pad);
+
+ if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
+ return NULL;
+
+ if (pad)
+ *pad = remote->index;
+
+ return media_entity_to_v4l2_subdev(remote->entity);
+}
+
+static int sun6i_video_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned int size = video->fmt.fmt.pix.sizeimage;
+
+ if (*nplanes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static int sun6i_video_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long size = video->fmt.fmt.pix.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+
+ buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ vbuf->field = video->fmt.fmt.pix.field;
+
+ return 0;
+}
+
+static int sun6i_pipeline_set_stream(struct sun6i_video *video, bool enable)
+{
+ struct media_entity *entity;
+ struct media_pad *pad;
+ struct v4l2_subdev *subdev;
+ int ret;
+
+ entity = &video->vdev.entity;
+ while (1) {
+ pad = &entity->pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+
+ pad = media_entity_remote_pad(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+
+ entity = pad->entity;
+ subdev = media_entity_to_v4l2_subdev(entity);
+
+ ret = v4l2_subdev_call(subdev, video, s_stream, enable);
+ if (enable && ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ struct sun6i_csi_buffer *buf;
+ struct sun6i_csi_config config;
+ unsigned long flags;
+ int ret;
+
+ video->sequence = 0;
+
+ ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe);
+ if (ret < 0)
+ goto err_start_pipeline;
+
+ ret = sun6i_pipeline_set_stream(video, true);
+ if (ret < 0)
+ goto err_start_stream;
+
+ config.pixelformat = video->fmt.fmt.pix.pixelformat;
+ config.code = video->current_fmt->mbus_code;
+ config.field = video->fmt.fmt.pix.field;
+ config.width = video->fmt.fmt.pix.width;
+ config.height = video->fmt.fmt.pix.height;
+
+ ret = sun6i_csi_update_config(video->csi, &config);
+ if (ret < 0)
+ goto err_update_config;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ video->cur_frm = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ list_del(&video->cur_frm->list);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ ret = sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ if (ret < 0)
+ goto err_update_addr;
+
+ ret = sun6i_csi_set_stream(video->csi, true);
+ if (ret < 0)
+ goto err_csi_stream;
+
+ return 0;
+
+err_csi_stream:
+err_update_addr:
+err_update_config:
+ sun6i_pipeline_set_stream(video, false);
+err_start_stream:
+ media_pipeline_stop(&video->vdev.entity);
+err_start_pipeline:
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ return ret;
+}
+
+static void sun6i_video_stop_streaming(struct vb2_queue *vq)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned long flags;
+ struct sun6i_csi_buffer *buf;
+
+ sun6i_pipeline_set_stream(video, false);
+
+ sun6i_csi_set_stream(video->csi, false);
+
+ media_pipeline_stop(&video->vdev.entity);
+
+ /* Release all active buffers */
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ if (unlikely(video->cur_frm)) {
+ vb2_buffer_done(&video->cur_frm->vb.vb2_buf,
+ VB2_BUF_STATE_ERROR);
+ video->cur_frm = NULL;
+ }
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+static void sun6i_video_buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long flags;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ if (!video->cur_frm && list_empty(&video->dma_queue) &&
+ vb2_is_streaming(vb->vb2_queue)) {
+ video->cur_frm = buf;
+ sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ sun6i_csi_set_stream(video->csi, 1);
+ } else
+ list_add_tail(&buf->list, &video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+void sun6i_video_frame_done(struct sun6i_video *video)
+{
+ spin_lock(&video->dma_queue_lock);
+
+ if (video->cur_frm) {
+ struct vb2_v4l2_buffer *vbuf = &video->cur_frm->vb;
+ struct vb2_buffer *vb = &vbuf->vb2_buf;
+
+ vb->timestamp = ktime_get_ns();
+ vbuf->sequence = video->sequence++;
+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+ video->cur_frm = NULL;
+ }
+
+ if (!list_empty(&video->dma_queue)
+ && vb2_is_streaming(&video->vb2_vidq)) {
+ video->cur_frm = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ list_del(&video->cur_frm->list);
+ sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ } else
+ sun6i_csi_set_stream(video->csi, 0);
+
+ spin_unlock(&video->dma_queue_lock);
+}
+
+static struct vb2_ops sun6i_csi_vb2_ops = {
+ .queue_setup = sun6i_video_queue_setup,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .buf_prepare = sun6i_video_buffer_prepare,
+ .start_streaming = sun6i_video_start_streaming,
+ .stop_streaming = sun6i_video_stop_streaming,
+ .buf_queue = sun6i_video_buffer_queue,
+};
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ strlcpy(cap->driver, "sun6i-video", sizeof(cap->driver));
+ strlcpy(cap->card, video->vdev.name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ video->csi->dev->of_node->name);
+
+ return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ u32 index = f->index;
+
+ if (index >= video->num_formats)
+ return -EINVAL;
+
+ f->pixelformat = video->formats[index].fourcc;
+
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ *fmt = video->fmt;
+
+ return 0;
+}
+
+static int sun6i_video_try_fmt(struct sun6i_video *video, struct v4l2_format *f,
+ struct sun6i_csi_format **current_fmt)
+{
+ struct sun6i_csi_format *csi_fmt;
+ struct v4l2_pix_format *pixfmt = &f->fmt.pix;
+ struct v4l2_subdev_format format;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ csi_fmt = find_format_by_fourcc(video, pixfmt->pixelformat);
+ if (csi_fmt == NULL)
+ return -EINVAL;
+
+ format.pad = pad;
+ format.which = V4L2_SUBDEV_FORMAT_TRY;
+ v4l2_fill_mbus_format(&format.format, pixfmt, csi_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format);
+ if (ret)
+ return ret;
+
+ v4l2_fill_pix_format(pixfmt, &format.format);
+
+ pixfmt->bytesperline = (pixfmt->width * csi_fmt->bpp) >> 3;
+ pixfmt->sizeimage = (pixfmt->width * csi_fmt->bpp * pixfmt->height) / 8;
+
+ if (current_fmt)
+ *current_fmt = csi_fmt;
+
+ return 0;
+}
+
+static int sun6i_video_set_fmt(struct sun6i_video *video, struct v4l2_format *f)
+{
+ struct v4l2_subdev_format format;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ ret = sun6i_video_try_fmt(video, f, ¤t_fmt);
+ if (ret)
+ return ret;
+
+ format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ v4l2_fill_mbus_format(&format.format, &f->fmt.pix,
+ current_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format);
+ if (ret < 0)
+ return ret;
+
+ video->fmt = *f;
+ video->current_fmt = current_fmt;
+
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ if (vb2_is_streaming(&video->vb2_vidq))
+ return -EBUSY;
+
+ return sun6i_video_set_fmt(video, f);
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ return sun6i_video_try_fmt(video, f, NULL);
+}
+
+static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 file operations
+ */
+static int sun6i_video_open(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ struct v4l2_format format;
+ int ret;
+
+ if (mutex_lock_interruptible(&video->lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto unlock;
+
+ ret = v4l2_pipeline_pm_use(&video->vdev.entity, 1);
+ if (ret < 0)
+ goto fh_release;
+
+ if (!v4l2_fh_is_singular_file(file))
+ goto unlock;
+
+ ret = sun6i_csi_set_power(video->csi, true);
+ if (ret < 0)
+ goto fh_release;
+
+ /* setup default format */
+ if (video->num_formats > 0) {
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ format.fmt.pix.width = 1280;
+ format.fmt.pix.height = 720;
+ format.fmt.pix.pixelformat = video->formats[0].fourcc;
+ sun6i_video_set_fmt(video, &format);
+ }
+
+ mutex_unlock(&video->lock);
+ return 0;
+
+fh_release:
+ v4l2_fh_release(file);
+unlock:
+ mutex_unlock(&video->lock);
+ return ret;
+}
+
+static int sun6i_video_close(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ bool last_fh;
+
+ mutex_lock(&video->lock);
+
+ last_fh = v4l2_fh_is_singular_file(file);
+
+ _vb2_fop_release(file, NULL);
+
+ v4l2_pipeline_pm_use(&video->vdev.entity, 0);
+
+ if (last_fh)
+ sun6i_csi_set_power(video->csi, false);
+
+ mutex_unlock(&video->lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations sun6i_video_fops = {
+ .owner = THIS_MODULE,
+ .open = sun6i_video_open,
+ .release = sun6i_video_close,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+ .poll = vb2_fop_poll
+};
+
+/* -----------------------------------------------------------------------------
+ * Media Operations
+ */
+static int sun6i_video_formats_init(struct sun6i_video *video)
+{
+ struct v4l2_subdev_mbus_code_enum mbus_code = { 0 };
+ struct sun6i_csi *csi = video->csi;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ const u32 *pixformats;
+ int pixformat_count = 0;
+ u32 subdev_codes[32]; /* subdev format codes, 32 should be enough */
+ int codes_count = 0;
+ int num_fmts = 0;
+ int i, j;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ /* Get supported pixformats of CSI */
+ pixformat_count = sun6i_csi_get_supported_pixformats(csi, &pixformats);
+ if (pixformat_count <= 0)
+ return -ENXIO;
+
+ /* Get subdev formats codes */
+ mbus_code.pad = pad;
+ mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL,
+ &mbus_code)) {
+ subdev_codes[codes_count] = mbus_code.code;
+ codes_count++;
+ mbus_code.index++;
+ }
+
+ if (!codes_count)
+ return -ENXIO;
+
+ /* Get supported formats count */
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ mbus_code.code)) {
+ continue;
+ }
+ num_fmts++;
+ }
+ }
+
+ if (!num_fmts)
+ return -ENXIO;
+
+ video->num_formats = num_fmts;
+ video->formats = devm_kcalloc(video->csi->dev, num_fmts,
+ sizeof(struct sun6i_csi_format), GFP_KERNEL);
+ if (!video->formats) {
+ dev_err(video->csi->dev, "could not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ /* Get supported formats */
+ num_fmts = 0;
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ mbus_code.code)) {
+ continue;
+ }
+
+ video->formats[num_fmts].fourcc = pixformats[j];
+ video->formats[num_fmts].mbus_code =
+ mbus_code.code;
+ video->formats[num_fmts].bpp =
+ v4l2_pixformat_get_bpp(pixformats[j]);
+ num_fmts++;
+ }
+ }
+
+ return 0;
+}
+
+static int sun6i_video_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct video_device *vdev = media_entity_to_video_device(entity);
+ struct sun6i_video *video = video_get_drvdata(vdev);
+
+ if (WARN_ON(video == NULL))
+ return 0;
+
+ return sun6i_video_formats_init(video);
+}
+
+static const struct media_entity_operations sun6i_video_media_ops = {
+ .link_setup = sun6i_video_link_setup,
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name)
+{
+ struct video_device *vdev = &video->vdev;
+ struct vb2_queue *vidq = &video->vb2_vidq;
+ int ret;
+
+ video->csi = csi;
+
+ /* Initialize the media entity... */
+ video->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
+ vdev->entity.ops = &sun6i_video_media_ops;
+ ret = media_entity_pads_init(&vdev->entity, 1, &video->pad);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&video->lock);
+
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_lock_init(&video->dma_queue_lock);
+
+ video->cur_frm = NULL;
+ video->sequence = 0;
+ video->num_formats = 0;
+
+ /* Initialize videobuf2 queue */
+ vidq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ vidq->io_modes = VB2_MMAP | VB2_DMABUF;
+ vidq->drv_priv = video;
+ vidq->buf_struct_size = sizeof(struct sun6i_csi_buffer);
+ vidq->ops = &sun6i_csi_vb2_ops;
+ vidq->mem_ops = &vb2_dma_contig_memops;
+ vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ vidq->lock = &video->lock;
+ vidq->min_buffers_needed = 1;
+ vidq->dev = csi->dev;
+
+ ret = vb2_queue_init(vidq);
+ if (ret) {
+ v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret);
+ goto error;
+ }
+
+ /* Register video device */
+ strlcpy(vdev->name, name, sizeof(vdev->name));
+ vdev->release = video_device_release_empty;
+ vdev->fops = &sun6i_video_fops;
+ vdev->ioctl_ops = &sun6i_video_ioctl_ops;
+ vdev->vfl_type = VFL_TYPE_GRABBER;
+ vdev->vfl_dir = VFL_DIR_RX;
+ vdev->v4l2_dev = &csi->v4l2_dev;
+ vdev->queue = vidq;
+ vdev->lock = &video->lock;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+ video_set_drvdata(vdev, video);
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret < 0) {
+ v4l2_err(&csi->v4l2_dev,
+ "video_register_device failed: %d\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ sun6i_video_cleanup(video);
+ return ret;
+}
+
+void sun6i_video_cleanup(struct sun6i_video *video)
+{
+ if (video_is_registered(&video->vdev))
+ video_unregister_device(&video->vdev);
+
+ media_entity_cleanup(&video->vdev.entity);
+}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.h b/drivers/media/platform/sun6i-csi/sun6i_video.h
new file mode 100644
index 0000000..14eac6e
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_video.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_VIDEO_H__
+#define __SUN6I_VIDEO_H__
+
+#include <media/v4l2-dev.h>
+#include <media/videobuf2-core.h>
+
+/*
+ * struct sun6i_csi_format - CSI media bus format information
+ * @fourcc: Fourcc code for this format
+ * @mbus_code: V4L2 media bus format code.
+ * @bpp: Bytes per pixel (when stored in memory)
+ */
+struct sun6i_csi_format {
+ u32 fourcc;
+ u32 mbus_code;
+ u8 bpp;
+};
+
+struct sun6i_csi;
+
+struct sun6i_video {
+ struct video_device vdev;
+ struct media_pad pad;
+ struct sun6i_csi *csi;
+
+ struct mutex lock;
+
+ struct vb2_queue vb2_vidq;
+ spinlock_t dma_queue_lock;
+ struct list_head dma_queue;
+
+ struct sun6i_csi_buffer *cur_frm;
+ unsigned int sequence;
+
+ struct sun6i_csi_format *formats;
+ unsigned int num_formats;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_format fmt;
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name);
+void sun6i_video_cleanup(struct sun6i_video *video);
+
+void sun6i_video_frame_done(struct sun6i_video *video);
+
+#endif /* __SUN6I_VIDEO_H__ */
--
1.8.3.1
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 56+ messages in thread
* [PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.
[not found] ` <1501127474-40895-1-git-send-email-yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
2017-07-27 3:51 ` Yong Deng
@ 2017-07-27 3:51 ` Yong Deng
1 sibling, 0 replies; 56+ messages in thread
From: Yong Deng @ 2017-07-27 3:51 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Rob Herring, Mark Rutland, Maxime Ripard,
Chen-Yu Tsai, Greg Kroah-Hartman, David S. Miller, Hans Verkuil,
Arnd Bergmann, Hugues Fruchet, Yannick Fertre, Philipp Zabel,
Benoit Parrot, Benjamin Gaignard, Jean-Christophe Trotin,
Ramesh Shanmugasundaram, Minghsiu Tsai, Krzysztof Kozlowski,
Robert Jarzmik, linux-media-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA, linux-arm-kernel
Cc: Yong Deng
Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
and CSI1 is used for parallel interface. This is not documented in
datasheet but by testing and guess.
This patch implement a v4l2 framework driver for it.
Currently, the driver only support the parallel interface. MIPI-CSI2,
ISP's support are not included in this patch.
Signed-off-by: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
---
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/sun6i-csi/Kconfig | 9 +
drivers/media/platform/sun6i-csi/Makefile | 3 +
drivers/media/platform/sun6i-csi/sun6i_csi.c | 545 +++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi.h | 203 ++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c | 827 +++++++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h | 206 ++++++
drivers/media/platform/sun6i-csi/sun6i_video.c | 663 ++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_video.h | 61 ++
10 files changed, 2520 insertions(+)
create mode 100644 drivers/media/platform/sun6i-csi/Kconfig
create mode 100644 drivers/media/platform/sun6i-csi/Makefile
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi.h
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.c
create mode 100644 drivers/media/platform/sun6i-csi/sun6i_video.h
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 0c741d1..8371a87 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -143,6 +143,7 @@ source "drivers/media/platform/am437x/Kconfig"
source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/sun6i-csi/Kconfig"
config VIDEO_TI_CAL
tristate "TI CAL (Camera Adaptation Layer) driver"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 9beadc7..fb2459c 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -86,3 +86,5 @@ obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/
obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk-jpeg/
obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi/
diff --git a/drivers/media/platform/sun6i-csi/Kconfig b/drivers/media/platform/sun6i-csi/Kconfig
new file mode 100644
index 0000000..314188a
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Kconfig
@@ -0,0 +1,9 @@
+config VIDEO_SUN6I_CSI
+ tristate "Allwinner V3s Camera Sensor Interface driver"
+ depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select REGMAP_MMIO
+ select V4L2_FWNODE
+ ---help---
+ Support for the Allwinner Camera Sensor Interface Controller on V3s.
diff --git a/drivers/media/platform/sun6i-csi/Makefile b/drivers/media/platform/sun6i-csi/Makefile
new file mode 100644
index 0000000..a9b527b
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/Makefile
@@ -0,0 +1,3 @@
+sun6i-csi-y += sun6i_csi.o sun6i_video.o sun6i_csi_v3s.o
+
+obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sun6i-csi/sun6i_csi.c
new file mode 100644
index 0000000..7a4bf53
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.c
@@ -0,0 +1,545 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing),
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * Based on drivers/media/platform/xilinx/xilinx-vipp.c
+ * Copyright (C) 2013-2015 Ideas on Board
+ * Copyright (C) 2013-2015 Xilinx, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/of_graph.h>
+
+#include "sun6i_csi.h"
+
+/*
+ * struct sun6i_graph_entity - Entity in the video graph
+ * @list: list entry in a graph entities list
+ * @node: the entity's DT node
+ * @entity: media entity, from the corresponding V4L2 subdev
+ * @asd: subdev asynchronous registration information
+ * @subdev: V4L2 subdev
+ */
+struct sun6i_graph_entity {
+ struct list_head list;
+ struct device_node *node;
+ struct media_entity *entity;
+
+ struct v4l2_async_subdev asd;
+ struct v4l2_subdev *subdev;
+};
+
+/* -----------------------------------------------------------------------------
+ * Graph Management
+ */
+
+static struct sun6i_graph_entity *
+sun6i_graph_find_entity(struct sun6i_csi *csi,
+ const struct device_node *node)
+{
+ struct sun6i_graph_entity *entity;
+
+ list_for_each_entry(entity, &csi->entities, list) {
+ if (entity->node == node)
+ return entity;
+ }
+
+ return NULL;
+}
+
+static int sun6i_graph_build_one(struct sun6i_csi *csi,
+ struct sun6i_graph_entity *entity)
+{
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ struct media_entity *local = entity->entity;
+ struct media_entity *remote;
+ struct media_pad *local_pad;
+ struct media_pad *remote_pad;
+ struct sun6i_graph_entity *ent;
+ struct v4l2_fwnode_link link;
+ struct device_node *ep = NULL;
+ struct device_node *next;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "creating links for entity %s\n", local->name);
+
+ while (1) {
+ /* Get the next endpoint and parse its link. */
+ next = of_graph_get_next_endpoint(entity->node, ep);
+ if (next == NULL)
+ break;
+
+ of_node_put(ep);
+ ep = next;
+
+ dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
+
+ ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to parse link for %s\n",
+ ep->full_name);
+ continue;
+ }
+
+ /* Skip sink ports, they will be processed from the other end of
+ * the link.
+ */
+ if (link.local_port >= local->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.local_port,
+ to_of_node(link.local_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ local_pad = &local->pads[link.local_port];
+
+ if (local_pad->flags & MEDIA_PAD_FL_SINK) {
+ dev_dbg(csi->dev, "skipping sink port %s:%u\n",
+ to_of_node(link.local_node)->full_name,
+ link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* Skip video node, they will be processed separately. */
+ if (link.remote_node == of_fwnode_handle(csi->dev->of_node)) {
+ dev_dbg(csi->dev, "skipping CSI port %s:%u\n",
+ to_of_node(link.local_node)->full_name,
+ link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* Find the remote entity. */
+ ent = sun6i_graph_find_entity(csi,
+ to_of_node(link.remote_node));
+ if (ent == NULL) {
+ dev_err(csi->dev, "no entity found for %s\n",
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ remote = ent->entity;
+
+ if (link.remote_port >= remote->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.remote_port,
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ remote_pad = &remote->pads[link.remote_port];
+
+ v4l2_fwnode_put_link(&link);
+
+ /* Create the media link. */
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+
+ ret = media_create_pad_link(local, local_pad->index,
+ remote, remote_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to create %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+ break;
+ }
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_build_video(struct sun6i_csi *csi)
+{
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ struct device_node *node = csi->dev->of_node;
+ struct media_entity *source;
+ struct media_entity *sink;
+ struct media_pad *source_pad;
+ struct media_pad *sink_pad;
+ struct sun6i_graph_entity *ent;
+ struct v4l2_fwnode_link link;
+ struct device_node *ep = NULL;
+ struct device_node *next;
+ struct sun6i_video *video = &csi->video;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "creating link for video node\n");
+
+ while (1) {
+ /* Get the next endpoint and parse its link. */
+ next = of_graph_get_next_endpoint(node, ep);
+ if (next == NULL)
+ break;
+
+ of_node_put(ep);
+ ep = next;
+
+ dev_dbg(csi->dev, "processing endpoint %s\n", ep->full_name);
+
+ ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
+ if (ret < 0) {
+ dev_err(csi->dev, "failed to parse link for %s\n",
+ ep->full_name);
+ continue;
+ }
+
+ /* Save the video port settings */
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep),
+ &csi->v4l2_ep);
+ if (ret) {
+ ret = -EINVAL;
+ dev_err(csi->dev, "Could not parse the endpoint\n");
+ v4l2_fwnode_put_link(&link);
+ break;
+ }
+
+ dev_dbg(csi->dev, "creating link for video node %s\n",
+ video->vdev.name);
+
+ /* Find the remote entity. */
+ ent = sun6i_graph_find_entity(csi,
+ to_of_node(link.remote_node));
+ if (ent == NULL) {
+ dev_err(csi->dev, "no entity found for %s\n",
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ if (link.remote_port >= ent->entity->num_pads) {
+ dev_err(csi->dev, "invalid port number %u on %s\n",
+ link.remote_port,
+ to_of_node(link.remote_node)->full_name);
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ source = ent->entity;
+ source_pad = &source->pads[link.remote_port];
+ sink = &video->vdev.entity;
+ sink_pad = &video->pad;
+
+ v4l2_fwnode_put_link(&link);
+
+ /* Create the media link. */
+ dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+
+ ret = media_create_pad_link(source, source_pad->index,
+ sink, sink_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(csi->dev,
+ "failed to create %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+ break;
+ }
+
+ /* Notify video node */
+ ret = media_entity_call(sink, link_setup, sink_pad, source_pad,
+ link_flags);
+ if (ret == -ENOIOCTLCMD)
+ ret = 0;
+
+ /* found one */
+ break;
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct sun6i_csi *csi =
+ container_of(notifier, struct sun6i_csi, notifier);
+ struct sun6i_graph_entity *entity;
+ int ret;
+
+ dev_dbg(csi->dev, "notify complete, all subdevs registered\n");
+
+ /* Create links for every entity. */
+ list_for_each_entry(entity, &csi->entities, list) {
+ ret = sun6i_graph_build_one(csi, entity);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Create links for video node. */
+ ret = sun6i_graph_build_video(csi);
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev);
+ if (ret < 0)
+ dev_err(csi->dev, "failed to register subdev nodes\n");
+
+ return media_device_register(&csi->media_dev);
+}
+
+static int sun6i_graph_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct sun6i_csi *csi =
+ container_of(notifier, struct sun6i_csi, notifier);
+ struct sun6i_graph_entity *entity;
+
+ /* Locate the entity corresponding to the bound subdev and store the
+ * subdev pointer.
+ */
+ list_for_each_entry(entity, &csi->entities, list) {
+ if (entity->node != subdev->dev->of_node)
+ continue;
+
+ if (entity->subdev) {
+ dev_err(csi->dev, "duplicate subdev for node %s\n",
+ entity->node->full_name);
+ return -EINVAL;
+ }
+
+ dev_dbg(csi->dev, "subdev %s bound\n", subdev->name);
+ entity->entity = &subdev->entity;
+ entity->subdev = subdev;
+ return 0;
+ }
+
+ dev_err(csi->dev, "no entity for subdev %s\n", subdev->name);
+ return -EINVAL;
+}
+
+static int sun6i_graph_parse_one(struct sun6i_csi *csi,
+ struct device_node *node)
+{
+ struct sun6i_graph_entity *entity;
+ struct device_node *remote;
+ struct device_node *ep = NULL;
+ int ret = 0;
+
+ dev_dbg(csi->dev, "parsing node %s\n", node->full_name);
+
+ while (1) {
+ ep = of_graph_get_next_endpoint(node, ep);
+ if (ep == NULL)
+ break;
+
+ dev_dbg(csi->dev, "handling endpoint %s\n", ep->full_name);
+
+ remote = of_graph_get_remote_port_parent(ep);
+ if (remote == NULL) {
+ ret = -EINVAL;
+ break;
+ }
+
+ /* Skip entities that we have already processed. */
+ if (remote == csi->dev->of_node ||
+ sun6i_graph_find_entity(csi, remote)) {
+ of_node_put(remote);
+ continue;
+ }
+
+ entity = devm_kzalloc(csi->dev, sizeof(*entity), GFP_KERNEL);
+ if (entity == NULL) {
+ of_node_put(remote);
+ ret = -ENOMEM;
+ break;
+ }
+
+ entity->node = remote;
+ entity->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+ entity->asd.match.fwnode.fwnode = of_fwnode_handle(remote);
+ list_add_tail(&entity->list, &csi->entities);
+ csi->num_subdevs++;
+ }
+
+ of_node_put(ep);
+ return ret;
+}
+
+static int sun6i_graph_parse(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entity;
+ int ret;
+
+ /*
+ * Walk the links to parse the full graph. Start by parsing the
+ * composite node and then parse entities in turn. The list_for_each
+ * loop will handle entities added at the end of the list while walking
+ * the links.
+ */
+ ret = sun6i_graph_parse_one(csi, csi->dev->of_node);
+ if (ret < 0)
+ return 0;
+
+ list_for_each_entry(entity, &csi->entities, list) {
+ ret = sun6i_graph_parse_one(csi, entity->node);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static void sun6i_graph_cleanup(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entityp;
+ struct sun6i_graph_entity *entity;
+
+ v4l2_async_notifier_unregister(&csi->notifier);
+
+ list_for_each_entry_safe(entity, entityp, &csi->entities, list) {
+ of_node_put(entity->node);
+ list_del(&entity->list);
+ }
+}
+
+static int sun6i_graph_init(struct sun6i_csi *csi)
+{
+ struct sun6i_graph_entity *entity;
+ struct v4l2_async_subdev **subdevs = NULL;
+ unsigned int num_subdevs;
+ unsigned int i;
+ int ret;
+
+ /* Parse the graph to extract a list of subdevice DT nodes. */
+ ret = sun6i_graph_parse(csi);
+ if (ret < 0) {
+ dev_err(csi->dev, "graph parsing failed\n");
+ goto done;
+ }
+
+ if (!csi->num_subdevs) {
+ dev_err(csi->dev, "no subdev found in graph\n");
+ goto done;
+ }
+
+ /* Register the subdevices notifier. */
+ num_subdevs = csi->num_subdevs;
+ subdevs = devm_kzalloc(csi->dev, sizeof(*subdevs) * num_subdevs,
+ GFP_KERNEL);
+ if (subdevs == NULL) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ i = 0;
+ list_for_each_entry(entity, &csi->entities, list)
+ subdevs[i++] = &entity->asd;
+
+ csi->notifier.subdevs = subdevs;
+ csi->notifier.num_subdevs = num_subdevs;
+ csi->notifier.bound = sun6i_graph_notify_bound;
+ csi->notifier.complete = sun6i_graph_notify_complete;
+
+ ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);
+ if (ret < 0) {
+ dev_err(csi->dev, "notifier registration failed\n");
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret < 0)
+ sun6i_graph_cleanup(csi);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Media Controller and V4L2
+ */
+
+static void sun6i_csi_v4l2_cleanup(struct sun6i_csi *csi)
+{
+ v4l2_device_unregister(&csi->v4l2_dev);
+ media_device_unregister(&csi->media_dev);
+ media_device_cleanup(&csi->media_dev);
+}
+
+static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->media_dev.dev = csi->dev;
+ strlcpy(csi->media_dev.model, "Allwinner Video Capture Device",
+ sizeof(csi->media_dev.model));
+ csi->media_dev.hw_revision = 0;
+
+ media_device_init(&csi->media_dev);
+
+ csi->v4l2_dev.mdev = &csi->media_dev;
+ ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
+ if (ret < 0) {
+ dev_err(csi->dev, "V4L2 device registration failed (%d)\n",
+ ret);
+ media_device_cleanup(&csi->media_dev);
+ return ret;
+ }
+ return 0;
+}
+
+int sun6i_csi_init(struct sun6i_csi *csi)
+{
+ int ret;
+
+ csi->num_subdevs = 0;
+ INIT_LIST_HEAD(&csi->entities);
+
+ ret = sun6i_csi_v4l2_init(csi);
+ if (ret < 0)
+ return ret;
+
+ ret = sun6i_video_init(&csi->video, csi, "sun6i-csi");
+ if (ret < 0)
+ goto v4l2_clean;
+
+ ret = sun6i_graph_init(csi);
+ if (ret < 0)
+ goto video_clean;
+
+ return 0;
+
+video_clean:
+ sun6i_video_cleanup(&csi->video);
+v4l2_clean:
+ sun6i_csi_v4l2_cleanup(csi);
+ return ret;
+}
+
+int sun6i_csi_cleanup(struct sun6i_csi *csi)
+{
+ sun6i_video_cleanup(&csi->video);
+ sun6i_graph_cleanup(csi);
+ sun6i_csi_v4l2_cleanup(csi);
+
+ return 0;
+}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sun6i-csi/sun6i_csi.h
new file mode 100644
index 0000000..167f6bc
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi.h
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_H__
+#define __SUN6I_CSI_H__
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+
+#include "sun6i_video.h"
+
+struct sun6i_csi;
+
+/**
+ * struct sun6i_csi_config - configs for sun6i csi
+ * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @code: media bus format code (MEDIA_BUS_FMT_*)
+ * @field: used interlacing type (enum v4l2_field)
+ * @width: frame width
+ * @height: frame height
+ */
+struct sun6i_csi_config {
+ u32 pixelformat;
+ u32 code;
+ u32 field;
+ u32 width;
+ u32 height;
+};
+
+struct sun6i_csi_ops {
+ int (*get_supported_pixformats)(struct sun6i_csi *csi,
+ const u32 **pixformats);
+ bool (*is_format_support)(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code);
+ int (*s_power)(struct sun6i_csi *csi, bool enable);
+ int (*update_config)(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config);
+ int (*update_buf_addr)(struct sun6i_csi *csi, dma_addr_t addr);
+ int (*s_stream)(struct sun6i_csi *csi, bool enable);
+};
+
+struct sun6i_csi {
+ struct device *dev;
+ struct v4l2_device v4l2_dev;
+ struct media_device media_dev;
+
+ struct list_head entities;
+ unsigned int num_subdevs;
+ struct v4l2_async_notifier notifier;
+
+ /* video port settings */
+ struct v4l2_fwnode_endpoint v4l2_ep;
+
+ struct sun6i_csi_config config;
+
+ struct sun6i_video video;
+
+ struct sun6i_csi_ops *ops;
+};
+
+int sun6i_csi_init(struct sun6i_csi *csi);
+int sun6i_csi_cleanup(struct sun6i_csi *csi);
+
+/**
+ * sun6i_csi_get_supported_pixformats() - get csi supported pixformats
+ * @csi: pointer to the csi
+ * @pixformats: supported pixformats return from csi
+ *
+ * @return the count of pixformats or error(< 0)
+ */
+static inline int
+sun6i_csi_get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (csi->ops != NULL && csi->ops->get_supported_pixformats != NULL)
+ return csi->ops->get_supported_pixformats(csi, pixformats);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_is_format_support() - check if the format supported by csi
+ * @csi: pointer to the csi
+ * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*)
+ * @mbus_code: media bus format code (MEDIA_BUS_FMT_*)
+ */
+static inline bool
+sun6i_csi_is_format_support(struct sun6i_csi *csi, u32 pixformat, u32 mbus_code)
+{
+ if (csi->ops != NULL && csi->ops->is_format_support != NULL)
+ return csi->ops->is_format_support(csi, pixformat, mbus_code);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_power() - power on/off the csi
+ * @csi: pointer to the csi
+ * @enable: on/off
+ */
+static inline int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable)
+{
+ if (csi->ops != NULL && csi->ops->s_power != NULL)
+ return csi->ops->s_power(csi, enable);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_config() - update the csi register setttings
+ * @csi: pointer to the csi
+ * @config: see struct sun6i_csi_config
+ */
+static inline int
+sun6i_csi_update_config(struct sun6i_csi *csi, struct sun6i_csi_config *config)
+{
+ if (csi->ops != NULL && csi->ops->update_config != NULL)
+ return csi->ops->update_config(csi, config);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_update_buf_addr() - update the csi frame buffer address
+ * @csi: pointer to the csi
+ * @addr: frame buffer's physical address
+ */
+static inline int sun6i_csi_update_buf_addr(struct sun6i_csi *csi,
+ dma_addr_t addr)
+{
+ if (csi->ops != NULL && csi->ops->update_buf_addr != NULL)
+ return csi->ops->update_buf_addr(csi, addr);
+
+ return -ENOIOCTLCMD;
+}
+
+/**
+ * sun6i_csi_set_stream() - start/stop csi streaming
+ * @csi: pointer to the csi
+ * @enable: start/stop
+ */
+static inline int sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable)
+{
+ if (csi->ops != NULL && csi->ops->s_stream != NULL)
+ return csi->ops->s_stream(csi, enable);
+
+ return -ENOIOCTLCMD;
+}
+
+static inline int v4l2_pixformat_get_bpp(unsigned int pixformat)
+{
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return 8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return 10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return 12;
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV422P:
+ return 16;
+ case V4L2_PIX_FMT_RGB24:
+ case V4L2_PIX_FMT_BGR24:
+ return 24;
+ case V4L2_PIX_FMT_RGB32:
+ case V4L2_PIX_FMT_BGR32:
+ return 32;
+ }
+
+ return 0;
+}
+
+#endif /* __SUN6I_CSI_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
new file mode 100644
index 0000000..f940daa
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_csi_v3s.h"
+
+#define MODULE_NAME "sun6i-csi"
+
+struct sun6i_csi_dev {
+ struct sun6i_csi csi;
+ struct device *dev;
+
+ struct regmap *regmap;
+ struct clk *clk_ahb;
+ struct clk *clk_mod;
+ struct clk *clk_ram;
+ struct reset_control *rstc_ahb;
+
+ int planar_offset[3];
+};
+
+static const u32 supported_pixformats[] = {
+ V4L2_PIX_FMT_SBGGR8,
+ V4L2_PIX_FMT_SGBRG8,
+ V4L2_PIX_FMT_SGRBG8,
+ V4L2_PIX_FMT_SRGGB8,
+ V4L2_PIX_FMT_SBGGR10,
+ V4L2_PIX_FMT_SGBRG10,
+ V4L2_PIX_FMT_SGRBG10,
+ V4L2_PIX_FMT_SRGGB10,
+ V4L2_PIX_FMT_SBGGR12,
+ V4L2_PIX_FMT_SGBRG12,
+ V4L2_PIX_FMT_SGRBG12,
+ V4L2_PIX_FMT_SRGGB12,
+ V4L2_PIX_FMT_YUYV,
+ V4L2_PIX_FMT_YVYU,
+ V4L2_PIX_FMT_UYVY,
+ V4L2_PIX_FMT_VYUY,
+ V4L2_PIX_FMT_HM12,
+ V4L2_PIX_FMT_NV12,
+ V4L2_PIX_FMT_NV21,
+ V4L2_PIX_FMT_YUV420,
+ V4L2_PIX_FMT_YVU420,
+ V4L2_PIX_FMT_NV16,
+ V4L2_PIX_FMT_NV61,
+ V4L2_PIX_FMT_YUV422P,
+};
+
+static inline struct sun6i_csi_dev *sun6i_csi_to_dev(struct sun6i_csi *csi)
+{
+ return container_of(csi, struct sun6i_csi_dev, csi);
+}
+
+/* TODO add 10&12 bit YUV, RGB support */
+static bool __is_format_support(struct sun6i_csi_dev *sdev,
+ u32 fourcc, u32 mbus_code)
+{
+ /*
+ * Some video receiver have capability both 8bit and 16bit.
+ * Identify the media bus format from device tree.
+ */
+ if (((sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_PARALLEL
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_BT656)
+ && sdev->csi.v4l2_ep.bus.parallel.bus_width == 16)
+ || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_CSI2) {
+ switch (fourcc) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ switch (fourcc) {
+ case V4L2_PIX_FMT_SBGGR8:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG8:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG8:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB8:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SBGGR10:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG10:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG10:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB10:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SBGGR12:
+ if (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGBRG12:
+ if (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SGRBG12:
+ if (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12)
+ return true;
+ break;
+ case V4L2_PIX_FMT_SRGGB12:
+ if (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12)
+ return true;
+ break;
+
+ case V4L2_PIX_FMT_YUYV:
+ if (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_YVYU:
+ if (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ if (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8)
+ return true;
+ break;
+ case V4L2_PIX_FMT_VYUY:
+ if (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8)
+ return true;
+ break;
+
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch (mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+static enum csi_input_fmt get_csi_input_format(u32 mbus_code, u32 pixformat)
+{
+ /* bayer */
+ if ((mbus_code & 0xF000) == 0x3000)
+ return CSI_INPUT_FORMAT_RAW;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return CSI_INPUT_FORMAT_RAW;
+ }
+
+ /* not support YUV420 input format yet */
+ return CSI_INPUT_FORMAT_YUV422;
+}
+
+static enum csi_output_fmt get_csi_output_format(u32 pixformat, u32 field)
+{
+ bool buf_interlaced = false;
+ if (field == V4L2_FIELD_INTERLACED
+ || field == V4L2_FIELD_INTERLACED_TB
+ || field == V4L2_FIELD_INTERLACED_BT)
+ buf_interlaced = true;
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_SBGGR8:
+ case V4L2_PIX_FMT_SGBRG8:
+ case V4L2_PIX_FMT_SGRBG8:
+ case V4L2_PIX_FMT_SRGGB8:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+ case V4L2_PIX_FMT_SBGGR10:
+ case V4L2_PIX_FMT_SGBRG10:
+ case V4L2_PIX_FMT_SGRBG10:
+ case V4L2_PIX_FMT_SRGGB10:
+ return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10;
+ case V4L2_PIX_FMT_SBGGR12:
+ case V4L2_PIX_FMT_SGBRG12:
+ case V4L2_PIX_FMT_SGRBG12:
+ case V4L2_PIX_FMT_SRGGB12:
+ return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12;
+
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8;
+
+ case V4L2_PIX_FMT_HM12:
+ return buf_interlaced ? CSI_FRAME_MB_YUV420 :
+ CSI_FIELD_MB_YUV420;
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 :
+ CSI_FIELD_UV_CB_YUV420;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 :
+ CSI_FIELD_PLANAR_YUV420;
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 :
+ CSI_FIELD_UV_CB_YUV422;
+ case V4L2_PIX_FMT_YUV422P:
+ return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 :
+ CSI_FIELD_PLANAR_YUV422;
+ }
+
+ return 0;
+}
+
+static enum csi_input_seq get_csi_input_seq(u32 mbus_code, u32 pixformat)
+{
+
+ switch (pixformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YUV422P:
+ switch(mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YUYV;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YVYU;
+ }
+ break;
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV61:
+ case V4L2_PIX_FMT_YVU420:
+ switch(mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return CSI_INPUT_SEQ_VYUY;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_1X16:
+ return CSI_INPUT_SEQ_UYVY;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return CSI_INPUT_SEQ_YVYU;
+ case MEDIA_BUS_FMT_YVYU8_1X16:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ return CSI_INPUT_SEQ_YUYV;
+ }
+ break;
+ }
+
+ return CSI_INPUT_SEQ_YUYV;
+}
+
+#ifdef DEBUG
+static void sun6i_csi_dump_regs(struct sun6i_csi_dev *sdev)
+{
+ struct regmap *regmap = sdev->regmap;
+ u32 val;
+
+ regmap_read(regmap, CSI_EN_REG, &val);
+ printk("CSI_EN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_IF_CFG_REG, &val);
+ printk("CSI_IF_CFG_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CAP_REG, &val);
+ printk("CSI_CAP_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_SYNC_CNT_REG, &val);
+ printk("CSI_SYNC_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_FIFO_THRS_REG, &val);
+ printk("CSI_FIFO_THRS_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_PTN_LEN_REG, &val);
+ printk("CSI_PTN_LEN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_PTN_ADDR_REG, &val);
+ printk("CSI_PTN_ADDR_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_VER_REG, &val);
+ printk("CSI_VER_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_CFG_REG, &val);
+ printk("CSI_CH_CFG_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_SCALE_REG, &val);
+ printk("CSI_CH_SCALE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F0_BUFA_REG, &val);
+ printk("CSI_CH_F0_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F1_BUFA_REG, &val);
+ printk("CSI_CH_F1_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_F2_BUFA_REG, &val);
+ printk("CSI_CH_F2_BUFA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_STA_REG, &val);
+ printk("CSI_CH_STA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_INT_EN_REG, &val);
+ printk("CSI_CH_INT_EN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &val);
+ printk("CSI_CH_INT_STA_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FLD1_VSIZE_REG, &val);
+ printk("CSI_CH_FLD1_VSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_HSIZE_REG, &val);
+ printk("CSI_CH_HSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_VSIZE_REG, &val);
+ printk("CSI_CH_VSIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_BUF_LEN_REG, &val);
+ printk("CSI_CH_BUF_LEN_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FLIP_SIZE_REG, &val);
+ printk("CSI_CH_FLIP_SIZE_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FRM_CLK_CNT_REG, &val);
+ printk("CSI_CH_FRM_CLK_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_ACC_ITNL_CLK_CNT_REG, &val);
+ printk("CSI_CH_ACC_ITNL_CLK_CNT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_FIFO_STAT_REG, &val);
+ printk("CSI_CH_FIFO_STAT_REG=0x%x\n", val);
+ regmap_read(regmap, CSI_CH_PCLK_STAT_REG, &val);
+ printk("CSI_CH_PCLK_STAT_REG=0x%x\n", val);
+}
+#endif
+
+static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev)
+{
+ struct v4l2_fwnode_endpoint *endpoint = &sdev->csi.v4l2_ep;
+ unsigned char bus_width;
+ u32 flags;
+ u32 cfg;
+
+ bus_width = endpoint->bus.parallel.bus_width;
+
+ regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK |
+ CSI_IF_CFG_IF_DATA_WIDTH_MASK |
+ CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK |
+ CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK);
+
+ switch (endpoint->bus_type) {
+ case V4L2_MBUS_CSI2:
+ cfg |= CSI_IF_CFG_MIPI_IF_MIPI;
+ break;
+ case V4L2_MBUS_PARALLEL:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT :
+ CSI_IF_CFG_CSI_IF_YUV422_INTLV;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_VREF_POL_POSITIVE;
+ if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+ cfg |= CSI_IF_CFG_HREF_POL_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ case V4L2_MBUS_BT656:
+ cfg |= CSI_IF_CFG_MIPI_IF_CSI;
+
+ flags = endpoint->bus.parallel.flags;
+
+ cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 :
+ CSI_IF_CFG_CSI_IF_BT656;
+
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CSI_IF_CFG_FIELD_POSITIVE;
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE;
+ break;
+ default:
+ BUG();
+ break;
+ }
+
+ switch (bus_width) {
+ case 8:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT;
+ break;
+ case 10:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT;
+ break;
+ case 12:
+ cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT;
+ break;
+ default:
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_IF_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_format(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi *csi = &sdev->csi;
+ u32 cfg;
+ u32 val;
+
+ regmap_read(sdev->regmap, CSI_CH_CFG_REG, &cfg);
+
+ cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK |
+ CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN |
+ CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK |
+ CSI_CH_CFG_INPUT_SEQ_MASK);
+
+ val = get_csi_input_format(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_FMT(val);
+
+ val = get_csi_output_format(csi->config.code, csi->config.field);
+ cfg |= CSI_CH_CFG_OUTPUT_FMT(val);
+
+ val = get_csi_input_seq(csi->config.code, csi->config.pixelformat);
+ cfg |= CSI_CH_CFG_INPUT_SEQ(val);
+
+ if (csi->config.field == V4L2_FIELD_TOP)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0;
+ else if (csi->config.field == V4L2_FIELD_BOTTOM)
+ cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1;
+ else
+ cfg |= CSI_CH_CFG_FIELD_SEL_BOTH;
+
+ regmap_write(sdev->regmap, CSI_CH_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_window(struct sun6i_csi_dev *sdev)
+{
+ struct sun6i_csi_config *config = &sdev->csi.config;
+ u32 bytesperline_y;
+ u32 bytesperline_c;
+ int *planar_offset = sdev->planar_offset;
+
+ regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
+ CSI_CH_HSIZE_HOR_LEN(config->width) |
+ CSI_CH_HSIZE_HOR_START(0));
+ regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
+ CSI_CH_VSIZE_VER_LEN(config->height) |
+ CSI_CH_VSIZE_VER_START(0));
+
+ planar_offset[0] = 0;
+ switch(config->pixelformat) {
+ case V4L2_PIX_FMT_HM12:
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ case V4L2_PIX_FMT_NV16:
+ case V4L2_PIX_FMT_NV61:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = -1;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width / 2;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * config->height / 2;
+ break;
+ case V4L2_PIX_FMT_YUV422P:
+ bytesperline_y = config->width;
+ bytesperline_c = config->width / 2;
+ planar_offset[1] = bytesperline_y * config->height;
+ planar_offset[2] = planar_offset[1] +
+ bytesperline_c * config->height;
+ break;
+ default: /* raw */
+ bytesperline_y = (v4l2_pixformat_get_bpp(config->pixelformat) *
+ config->width) / 8;
+ bytesperline_c = 0;
+ planar_offset[1] = -1;
+ planar_offset[2] = -1;
+ break;
+ }
+
+ regmap_write(sdev->regmap, CSI_CH_BUF_LEN_REG,
+ CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) |
+ CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y));
+}
+
+static int get_supported_pixformats(struct sun6i_csi *csi,
+ const u32 **pixformats)
+{
+ if (pixformats != NULL)
+ *pixformats = supported_pixformats;
+
+ return ARRAY_SIZE(supported_pixformats);
+}
+
+static bool is_format_support(struct sun6i_csi *csi, u32 pixformat,
+ u32 mbus_code)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ return __is_format_support(sdev, pixformat, mbus_code);
+}
+
+static int set_power(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+ int ret;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+
+ clk_disable_unprepare(sdev->clk_ram);
+ clk_disable_unprepare(sdev->clk_mod);
+ clk_disable_unprepare(sdev->clk_ahb);
+ reset_control_assert(sdev->rstc_ahb);
+ return 0;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ahb);
+ if (ret) {
+ dev_err(sdev->dev, "Enable ahb clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_mod);
+ if (ret) {
+ dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(sdev->clk_ram);
+ if (ret) {
+ dev_err(sdev->dev, "Enable clk_dram_csi clk err %d\n", ret);
+ return ret;
+ }
+
+ if (!IS_ERR_OR_NULL(sdev->rstc_ahb)) {
+ ret = reset_control_deassert(sdev->rstc_ahb);
+ if (ret) {
+ dev_err(sdev->dev, "reset err %d\n", ret);
+ return ret;
+ }
+ }
+
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN);
+
+ return 0;
+}
+
+static int update_config(struct sun6i_csi *csi,
+ struct sun6i_csi_config *config)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+ if (config == NULL)
+ return -EINVAL;
+
+ memcpy(&csi->config, config, sizeof(csi->config));
+
+ sun6i_csi_setup_bus(sdev);
+ sun6i_csi_set_format(sdev);
+ sun6i_csi_set_window(sdev);
+
+ return 0;
+}
+
+static int update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ /* transform physical address to bus address */
+ dma_addr_t bus_addr = addr - 0x40000000;
+
+ regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG,
+ (bus_addr + sdev->planar_offset[0]) >> 2);
+ if (sdev->planar_offset[1] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG,
+ (bus_addr + sdev->planar_offset[1]) >> 2);
+ if (sdev->planar_offset[2] != -1)
+ regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG,
+ (bus_addr + sdev->planar_offset[2]) >> 2);
+
+ return 0;
+}
+
+static int set_stream(struct sun6i_csi *csi, bool enable)
+{
+ struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+ struct regmap *regmap = sdev->regmap;
+
+ if (!enable) {
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0);
+ regmap_write(regmap, CSI_CH_INT_EN_REG, 0);
+ return 0;
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF);
+ regmap_write(regmap, CSI_CH_INT_EN_REG,
+ CSI_CH_INT_EN_HB_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO2_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO1_OF_INT_EN |
+ CSI_CH_INT_EN_FIFO0_OF_INT_EN |
+ CSI_CH_INT_EN_FD_INT_EN |
+ CSI_CH_INT_EN_CD_INT_EN);
+
+ regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON,
+ CSI_CAP_CH0_VCAP_ON);
+
+ return 0;
+}
+
+static struct sun6i_csi_ops csi_ops = {
+ .get_supported_pixformats = get_supported_pixformats,
+ .is_format_support = is_format_support,
+ .s_power = set_power,
+ .update_config = update_config,
+ .update_buf_addr = update_buf_addr,
+ .s_stream = set_stream,
+};
+
+static irqreturn_t sun6i_csi_isr(int irq, void *dev_id)
+{
+ struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id;
+ struct regmap *regmap = sdev->regmap;
+ u32 status;
+
+ regmap_read(regmap, CSI_CH_INT_STA_REG, &status);
+
+ if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO1_OF_PD) ||
+ (status & CSI_CH_INT_STA_FIFO2_OF_PD) ||
+ (status & CSI_CH_INT_STA_HB_OF_PD)) {
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0);
+ regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
+ CSI_EN_CSI_EN);
+ return IRQ_HANDLED;
+ }
+
+ if (status & CSI_CH_INT_STA_FD_PD) {
+ sun6i_video_frame_done(&sdev->csi.video);
+ }
+
+ regmap_write(regmap, CSI_CH_INT_STA_REG, status);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config sun6i_csi_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x1000,
+};
+
+static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+ void __iomem *io_base;
+ int ret;
+ int irq;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ io_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(io_base))
+ return PTR_ERR(io_base);
+
+ sdev->regmap = devm_regmap_init_mmio(&pdev->dev, io_base,
+ &sun6i_csi_regmap_config);
+ if (IS_ERR(sdev->regmap)) {
+ dev_err(&pdev->dev, "Failed to init register map\n");
+ return PTR_ERR(sdev->regmap);
+ }
+
+ sdev->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
+ if (IS_ERR(sdev->clk_ahb)) {
+ dev_err(&pdev->dev, "Unable to acquire ahb clock\n");
+ return PTR_ERR(sdev->clk_ahb);
+ }
+
+ sdev->clk_mod = devm_clk_get(&pdev->dev, "mod");
+ if (IS_ERR(sdev->clk_mod)) {
+ dev_err(&pdev->dev, "Unable to acquire csi clock\n");
+ return PTR_ERR(sdev->clk_mod);
+ }
+
+ sdev->clk_ram = devm_clk_get(&pdev->dev, "ram");
+ if (IS_ERR(sdev->clk_ram)) {
+ dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n");
+ return PTR_ERR(sdev->clk_ram);
+ }
+
+ sdev->rstc_ahb = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(sdev->rstc_ahb)) {
+ dev_err(&pdev->dev, "Cannot get reset controller\n");
+ return PTR_ERR(sdev->rstc_ahb);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "No csi IRQ specified\n");
+ ret = -ENXIO;
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, sun6i_csi_isr, 0, MODULE_NAME,
+ sdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot request csi IRQ\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int sun6i_csi_probe(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev;
+ int ret;
+
+ sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL);
+ if (!sdev)
+ return -ENOMEM;
+
+ sdev->dev = &pdev->dev;
+
+ ret = sun6i_csi_resource_request(sdev, pdev);
+ if (ret)
+ return ret;
+
+ sdev->csi.dev = &pdev->dev;
+ sdev->csi.ops = &csi_ops;
+ ret = sun6i_csi_init(&sdev->csi);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, sdev);
+
+ return 0;
+}
+
+static int sun6i_csi_remove(struct platform_device *pdev)
+{
+ struct sun6i_csi_dev *sdev = platform_get_drvdata(pdev);
+
+ sun6i_csi_cleanup(&sdev->csi);
+
+ return 0;
+}
+
+static const struct of_device_id sun6i_csi_of_match[] = {
+ { .compatible = "allwinner,sun8i-v3s-csi", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sun6i_csi_of_match);
+
+static struct platform_driver sun6i_csi_platform_driver = {
+ .probe = sun6i_csi_probe,
+ .remove = sun6i_csi_remove,
+ .driver = {
+ .name = MODULE_NAME,
+ .of_match_table = of_match_ptr(sun6i_csi_of_match),
+ },
+};
+module_platform_driver(sun6i_csi_platform_driver);
+
+MODULE_DESCRIPTION("Allwinner V3s Camera Sensor Interface driver");
+MODULE_AUTHOR("Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
new file mode 100644
index 0000000..6c87b74
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_CSI_V3S_H__
+#define __SUN6I_CSI_V3S_H__
+
+#include <linux/kernel.h>
+
+#define CSI_EN_REG 0x0
+#define CSI_EN_VER_EN BIT(30)
+#define CSI_EN_CSI_EN BIT(0)
+
+#define CSI_IF_CFG_REG 0x4
+#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21)
+#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK)
+#define CSI_IF_CFG_FPS_DS_EN BIT(20)
+#define CSI_IF_CFG_FIELD_MASK BIT(19)
+#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK)
+#define CSI_IF_CFG_VREF_POL_MASK BIT(18)
+#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_MASK BIT(17)
+#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_MASK BIT(16)
+#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8)
+#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK)
+#define CSI_IF_CFG_MIPI_IF_MASK BIT(7)
+#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7)
+#define CSI_IF_CFG_MIPI_IF_MIPI (1 << 7)
+#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0)
+#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK)
+
+#define CSI_CAP_REG 0x8
+#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2)
+#define CSI_CAP_CH0_CAP_MASK(count) ((count << 2) & CSI_CAP_CH0_CAP_MASK_MASK)
+#define CSI_CAP_CH0_VCAP_ON BIT(1)
+#define CSI_CAP_CH0_SCAP_ON BIT(0)
+
+#define CSI_SYNC_CNT_REG 0xc
+#define CSI_FIFO_THRS_REG 0x10
+#define CSI_BT656_HEAD_CFG_REG 0x14
+#define CSI_PTN_LEN_REG 0x30
+#define CSI_PTN_ADDR_REG 0x34
+#define CSI_VER_REG 0x3c
+
+#define CSI_CH_CFG_REG 0x44
+#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20)
+#define CSI_CH_CFG_INPUT_FMT(fmt) ((fmt << 20) & CSI_CH_CFG_INPUT_FMT_MASK)
+#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16)
+#define CSI_CH_CFG_OUTPUT_FMT(fmt) ((fmt << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK)
+#define CSI_CH_CFG_VFLIP_EN BIT(13)
+#define CSI_CH_CFG_HFLIP_EN BIT(12)
+#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10)
+#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK)
+#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8)
+#define CSI_CH_CFG_INPUT_SEQ(seq) ((seq << 8) & CSI_CH_CFG_INPUT_SEQ_MASK)
+
+#define CSI_CH_SCALE_REG 0x4c
+#define CSI_CH_SCALE_QUART_EN BIT(0)
+
+#define CSI_CH_F0_BUFA_REG 0x50
+
+#define CSI_CH_F1_BUFA_REG 0x58
+
+#define CSI_CH_F2_BUFA_REG 0x60
+
+#define CSI_CH_STA_REG 0x6c
+#define CSI_CH_STA_FIELD_STA_MASK BIT(2)
+#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK)
+#define CSI_CH_STA_VCAP_STA BIT(1)
+#define CSI_CH_STA_SCAP_STA BIT(0)
+
+#define CSI_CH_INT_EN_REG 0x70
+#define CSI_CH_INT_EN_VS_INT_EN BIT(7)
+#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6)
+#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5)
+#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4)
+#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3)
+#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2)
+#define CSI_CH_INT_EN_FD_INT_EN BIT(1)
+#define CSI_CH_INT_EN_CD_INT_EN BIT(0)
+
+#define CSI_CH_INT_STA_REG 0x74
+#define CSI_CH_INT_STA_VS_PD BIT(7)
+#define CSI_CH_INT_STA_HB_OF_PD BIT(6)
+#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5)
+#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4)
+#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3)
+#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2)
+#define CSI_CH_INT_STA_FD_PD BIT(1)
+#define CSI_CH_INT_STA_CD_PD BIT(0)
+
+#define CSI_CH_FLD1_VSIZE_REG 0x74
+
+#define CSI_CH_HSIZE_REG 0x80
+#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_HSIZE_HOR_LEN(len) ((len << 16) & CSI_CH_HSIZE_HOR_LEN_MASK)
+#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0)
+#define CSI_CH_HSIZE_HOR_START(start) ((start << 0) & CSI_CH_HSIZE_HOR_START_MASK)
+
+#define CSI_CH_VSIZE_REG 0x84
+#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_VSIZE_VER_LEN(len) ((len << 16) & CSI_CH_VSIZE_VER_LEN_MASK)
+#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0)
+#define CSI_CH_VSIZE_VER_START(start) ((start << 0) & CSI_CH_VSIZE_VER_START_MASK)
+
+#define CSI_CH_BUF_LEN_REG 0x88
+#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16)
+#define CSI_CH_BUF_LEN_BUF_LEN_C(len) ((len << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0)
+#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) ((len << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK)
+
+#define CSI_CH_FLIP_SIZE_REG 0x8c
+#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16)
+#define CSI_CH_FLIP_SIZE_VER_LEN(len) ((len << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK)
+#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0)
+#define CSI_CH_FLIP_SIZE_VALID_LEN(len) ((len << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK)
+
+#define CSI_CH_FRM_CLK_CNT_REG 0x90
+#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94
+#define CSI_CH_FIFO_STAT_REG 0x98
+#define CSI_CH_PCLK_STAT_REG 0x9c
+
+/*
+ * csi input data format
+ */
+enum csi_input_fmt
+{
+ CSI_INPUT_FORMAT_RAW = 0,
+ CSI_INPUT_FORMAT_YUV422 = 3,
+ CSI_INPUT_FORMAT_YUV420 = 4,
+};
+
+/*
+ * csi output data format
+ */
+enum csi_output_fmt
+{
+ /* only when input format is RAW */
+ CSI_FIELD_RAW_8 = 0,
+ CSI_FIELD_RAW_10 = 1,
+ CSI_FIELD_RAW_12 = 2,
+ CSI_FIELD_RGB565 = 4,
+ CSI_FIELD_RGB888 = 5,
+ CSI_FIELD_PRGB888 = 6,
+ CSI_FRAME_RAW_8 = 8,
+ CSI_FRAME_RAW_10 = 9,
+ CSI_FRAME_RAW_12 = 10,
+ CSI_FRAME_RGB565 = 12,
+ CSI_FRAME_RGB888 = 13,
+ CSI_FRAME_PRGB888 = 14,
+
+ /* only when input format is YUV422/YUV420 */
+ CSI_FIELD_PLANAR_YUV422 = 0,
+ CSI_FIELD_PLANAR_YUV420 = 1,
+ CSI_FRAME_PLANAR_YUV420 = 2,
+ CSI_FRAME_PLANAR_YUV422 = 3,
+ CSI_FIELD_UV_CB_YUV422 = 4,
+ CSI_FIELD_UV_CB_YUV420 = 5,
+ CSI_FRAME_UV_CB_YUV420 = 6,
+ CSI_FRAME_UV_CB_YUV422 = 7,
+ CSI_FIELD_MB_YUV422 = 8,
+ CSI_FIELD_MB_YUV420 = 9,
+ CSI_FRAME_MB_YUV420 = 10,
+ CSI_FRAME_MB_YUV422 = 11,
+ CSI_FIELD_UV_CB_YUV422_10 = 12,
+ CSI_FIELD_UV_CB_YUV420_10 = 13,
+};
+
+/*
+ * csi YUV input data sequence
+ */
+enum csi_input_seq
+{
+ /* only when input format is YUV422 */
+ CSI_INPUT_SEQ_YUYV = 0,
+ CSI_INPUT_SEQ_YVYU,
+ CSI_INPUT_SEQ_UYVY,
+ CSI_INPUT_SEQ_VYUY,
+};
+
+#endif /* __SUN6I_CSI_V3S_H__ */
diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.c b/drivers/media/platform/sun6i-csi/sun6i_video.c
new file mode 100644
index 0000000..0c5dbd2
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_video.c
@@ -0,0 +1,663 @@
+/*
+ * Copyright (c) 2017 Magewell Electronics Co., Ltd. (Nanjing).
+ * All rights reserved.
+ * Author: Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/of.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "sun6i_csi.h"
+#include "sun6i_video.h"
+
+struct sun6i_csi_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+
+ dma_addr_t dma_addr;
+};
+
+static struct sun6i_csi_format *
+find_format_by_fourcc(struct sun6i_video *video, unsigned int fourcc)
+{
+ unsigned int num_formats = video->num_formats;
+ struct sun6i_csi_format *fmt;
+ unsigned int i;
+
+ for (i = 0; i < num_formats; i++) {
+ fmt = &video->formats[i];
+ if (fmt->fourcc == fourcc)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+static struct v4l2_subdev *
+sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad)
+{
+ struct media_pad *remote;
+
+ remote = media_entity_remote_pad(&video->pad);
+
+ if (!remote || !is_media_entity_v4l2_subdev(remote->entity))
+ return NULL;
+
+ if (pad)
+ *pad = remote->index;
+
+ return media_entity_to_v4l2_subdev(remote->entity);
+}
+
+static int sun6i_video_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned int size = video->fmt.fmt.pix.sizeimage;
+
+ if (*nplanes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static int sun6i_video_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long size = video->fmt.fmt.pix.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+
+ buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ vbuf->field = video->fmt.fmt.pix.field;
+
+ return 0;
+}
+
+static int sun6i_pipeline_set_stream(struct sun6i_video *video, bool enable)
+{
+ struct media_entity *entity;
+ struct media_pad *pad;
+ struct v4l2_subdev *subdev;
+ int ret;
+
+ entity = &video->vdev.entity;
+ while (1) {
+ pad = &entity->pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+
+ pad = media_entity_remote_pad(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+
+ entity = pad->entity;
+ subdev = media_entity_to_v4l2_subdev(entity);
+
+ ret = v4l2_subdev_call(subdev, video, s_stream, enable);
+ if (enable && ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ struct sun6i_csi_buffer *buf;
+ struct sun6i_csi_config config;
+ unsigned long flags;
+ int ret;
+
+ video->sequence = 0;
+
+ ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe);
+ if (ret < 0)
+ goto err_start_pipeline;
+
+ ret = sun6i_pipeline_set_stream(video, true);
+ if (ret < 0)
+ goto err_start_stream;
+
+ config.pixelformat = video->fmt.fmt.pix.pixelformat;
+ config.code = video->current_fmt->mbus_code;
+ config.field = video->fmt.fmt.pix.field;
+ config.width = video->fmt.fmt.pix.width;
+ config.height = video->fmt.fmt.pix.height;
+
+ ret = sun6i_csi_update_config(video->csi, &config);
+ if (ret < 0)
+ goto err_update_config;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ video->cur_frm = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ list_del(&video->cur_frm->list);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ ret = sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ if (ret < 0)
+ goto err_update_addr;
+
+ ret = sun6i_csi_set_stream(video->csi, true);
+ if (ret < 0)
+ goto err_csi_stream;
+
+ return 0;
+
+err_csi_stream:
+err_update_addr:
+err_update_config:
+ sun6i_pipeline_set_stream(video, false);
+err_start_stream:
+ media_pipeline_stop(&video->vdev.entity);
+err_start_pipeline:
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+
+ return ret;
+}
+
+static void sun6i_video_stop_streaming(struct vb2_queue *vq)
+{
+ struct sun6i_video *video = vb2_get_drv_priv(vq);
+ unsigned long flags;
+ struct sun6i_csi_buffer *buf;
+
+ sun6i_pipeline_set_stream(video, false);
+
+ sun6i_csi_set_stream(video->csi, false);
+
+ media_pipeline_stop(&video->vdev.entity);
+
+ /* Release all active buffers */
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ if (unlikely(video->cur_frm)) {
+ vb2_buffer_done(&video->cur_frm->vb.vb2_buf,
+ VB2_BUF_STATE_ERROR);
+ video->cur_frm = NULL;
+ }
+ list_for_each_entry(buf, &video->dma_queue, list)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+static void sun6i_video_buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct sun6i_csi_buffer *buf =
+ container_of(vbuf, struct sun6i_csi_buffer, vb);
+ struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long flags;
+
+ spin_lock_irqsave(&video->dma_queue_lock, flags);
+ if (!video->cur_frm && list_empty(&video->dma_queue) &&
+ vb2_is_streaming(vb->vb2_queue)) {
+ video->cur_frm = buf;
+ sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ sun6i_csi_set_stream(video->csi, 1);
+ } else
+ list_add_tail(&buf->list, &video->dma_queue);
+ spin_unlock_irqrestore(&video->dma_queue_lock, flags);
+}
+
+void sun6i_video_frame_done(struct sun6i_video *video)
+{
+ spin_lock(&video->dma_queue_lock);
+
+ if (video->cur_frm) {
+ struct vb2_v4l2_buffer *vbuf = &video->cur_frm->vb;
+ struct vb2_buffer *vb = &vbuf->vb2_buf;
+
+ vb->timestamp = ktime_get_ns();
+ vbuf->sequence = video->sequence++;
+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+ video->cur_frm = NULL;
+ }
+
+ if (!list_empty(&video->dma_queue)
+ && vb2_is_streaming(&video->vb2_vidq)) {
+ video->cur_frm = list_first_entry(&video->dma_queue,
+ struct sun6i_csi_buffer, list);
+ list_del(&video->cur_frm->list);
+ sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr);
+ } else
+ sun6i_csi_set_stream(video->csi, 0);
+
+ spin_unlock(&video->dma_queue_lock);
+}
+
+static struct vb2_ops sun6i_csi_vb2_ops = {
+ .queue_setup = sun6i_video_queue_setup,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .buf_prepare = sun6i_video_buffer_prepare,
+ .start_streaming = sun6i_video_start_streaming,
+ .stop_streaming = sun6i_video_stop_streaming,
+ .buf_queue = sun6i_video_buffer_queue,
+};
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ strlcpy(cap->driver, "sun6i-video", sizeof(cap->driver));
+ strlcpy(cap->card, video->vdev.name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ video->csi->dev->of_node->name);
+
+ return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ u32 index = f->index;
+
+ if (index >= video->num_formats)
+ return -EINVAL;
+
+ f->pixelformat = video->formats[index].fourcc;
+
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *fmt)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ *fmt = video->fmt;
+
+ return 0;
+}
+
+static int sun6i_video_try_fmt(struct sun6i_video *video, struct v4l2_format *f,
+ struct sun6i_csi_format **current_fmt)
+{
+ struct sun6i_csi_format *csi_fmt;
+ struct v4l2_pix_format *pixfmt = &f->fmt.pix;
+ struct v4l2_subdev_format format;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ csi_fmt = find_format_by_fourcc(video, pixfmt->pixelformat);
+ if (csi_fmt == NULL)
+ return -EINVAL;
+
+ format.pad = pad;
+ format.which = V4L2_SUBDEV_FORMAT_TRY;
+ v4l2_fill_mbus_format(&format.format, pixfmt, csi_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format);
+ if (ret)
+ return ret;
+
+ v4l2_fill_pix_format(pixfmt, &format.format);
+
+ pixfmt->bytesperline = (pixfmt->width * csi_fmt->bpp) >> 3;
+ pixfmt->sizeimage = (pixfmt->width * csi_fmt->bpp * pixfmt->height) / 8;
+
+ if (current_fmt)
+ *current_fmt = csi_fmt;
+
+ return 0;
+}
+
+static int sun6i_video_set_fmt(struct sun6i_video *video, struct v4l2_format *f)
+{
+ struct v4l2_subdev_format format;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ int ret;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ ret = sun6i_video_try_fmt(video, f, ¤t_fmt);
+ if (ret)
+ return ret;
+
+ format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ v4l2_fill_mbus_format(&format.format, &f->fmt.pix,
+ current_fmt->mbus_code);
+ ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format);
+ if (ret < 0)
+ return ret;
+
+ video->fmt = *f;
+ video->current_fmt = current_fmt;
+
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ if (vb2_is_streaming(&video->vb2_vidq))
+ return -EBUSY;
+
+ return sun6i_video_set_fmt(video, f);
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct sun6i_video *video = video_drvdata(file);
+
+ return sun6i_video_try_fmt(video, f, NULL);
+}
+
+static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 file operations
+ */
+static int sun6i_video_open(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ struct v4l2_format format;
+ int ret;
+
+ if (mutex_lock_interruptible(&video->lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto unlock;
+
+ ret = v4l2_pipeline_pm_use(&video->vdev.entity, 1);
+ if (ret < 0)
+ goto fh_release;
+
+ if (!v4l2_fh_is_singular_file(file))
+ goto unlock;
+
+ ret = sun6i_csi_set_power(video->csi, true);
+ if (ret < 0)
+ goto fh_release;
+
+ /* setup default format */
+ if (video->num_formats > 0) {
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ format.fmt.pix.width = 1280;
+ format.fmt.pix.height = 720;
+ format.fmt.pix.pixelformat = video->formats[0].fourcc;
+ sun6i_video_set_fmt(video, &format);
+ }
+
+ mutex_unlock(&video->lock);
+ return 0;
+
+fh_release:
+ v4l2_fh_release(file);
+unlock:
+ mutex_unlock(&video->lock);
+ return ret;
+}
+
+static int sun6i_video_close(struct file *file)
+{
+ struct sun6i_video *video = video_drvdata(file);
+ bool last_fh;
+
+ mutex_lock(&video->lock);
+
+ last_fh = v4l2_fh_is_singular_file(file);
+
+ _vb2_fop_release(file, NULL);
+
+ v4l2_pipeline_pm_use(&video->vdev.entity, 0);
+
+ if (last_fh)
+ sun6i_csi_set_power(video->csi, false);
+
+ mutex_unlock(&video->lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations sun6i_video_fops = {
+ .owner = THIS_MODULE,
+ .open = sun6i_video_open,
+ .release = sun6i_video_close,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+ .poll = vb2_fop_poll
+};
+
+/* -----------------------------------------------------------------------------
+ * Media Operations
+ */
+static int sun6i_video_formats_init(struct sun6i_video *video)
+{
+ struct v4l2_subdev_mbus_code_enum mbus_code = { 0 };
+ struct sun6i_csi *csi = video->csi;
+ struct v4l2_subdev *subdev;
+ u32 pad;
+ const u32 *pixformats;
+ int pixformat_count = 0;
+ u32 subdev_codes[32]; /* subdev format codes, 32 should be enough */
+ int codes_count = 0;
+ int num_fmts = 0;
+ int i, j;
+
+ subdev = sun6i_video_remote_subdev(video, &pad);
+ if (subdev == NULL)
+ return -ENXIO;
+
+ /* Get supported pixformats of CSI */
+ pixformat_count = sun6i_csi_get_supported_pixformats(csi, &pixformats);
+ if (pixformat_count <= 0)
+ return -ENXIO;
+
+ /* Get subdev formats codes */
+ mbus_code.pad = pad;
+ mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL,
+ &mbus_code)) {
+ subdev_codes[codes_count] = mbus_code.code;
+ codes_count++;
+ mbus_code.index++;
+ }
+
+ if (!codes_count)
+ return -ENXIO;
+
+ /* Get supported formats count */
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ mbus_code.code)) {
+ continue;
+ }
+ num_fmts++;
+ }
+ }
+
+ if (!num_fmts)
+ return -ENXIO;
+
+ video->num_formats = num_fmts;
+ video->formats = devm_kcalloc(video->csi->dev, num_fmts,
+ sizeof(struct sun6i_csi_format), GFP_KERNEL);
+ if (!video->formats) {
+ dev_err(video->csi->dev, "could not allocate memory\n");
+ return -ENOMEM;
+ }
+
+ /* Get supported formats */
+ num_fmts = 0;
+ for (i = 0; i < codes_count; i++) {
+ for (j = 0; j < pixformat_count; j++) {
+ if (!sun6i_csi_is_format_support(csi, pixformats[j],
+ mbus_code.code)) {
+ continue;
+ }
+
+ video->formats[num_fmts].fourcc = pixformats[j];
+ video->formats[num_fmts].mbus_code =
+ mbus_code.code;
+ video->formats[num_fmts].bpp =
+ v4l2_pixformat_get_bpp(pixformats[j]);
+ num_fmts++;
+ }
+ }
+
+ return 0;
+}
+
+static int sun6i_video_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct video_device *vdev = media_entity_to_video_device(entity);
+ struct sun6i_video *video = video_get_drvdata(vdev);
+
+ if (WARN_ON(video == NULL))
+ return 0;
+
+ return sun6i_video_formats_init(video);
+}
+
+static const struct media_entity_operations sun6i_video_media_ops = {
+ .link_setup = sun6i_video_link_setup,
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name)
+{
+ struct video_device *vdev = &video->vdev;
+ struct vb2_queue *vidq = &video->vb2_vidq;
+ int ret;
+
+ video->csi = csi;
+
+ /* Initialize the media entity... */
+ video->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
+ vdev->entity.ops = &sun6i_video_media_ops;
+ ret = media_entity_pads_init(&vdev->entity, 1, &video->pad);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&video->lock);
+
+ INIT_LIST_HEAD(&video->dma_queue);
+ spin_lock_init(&video->dma_queue_lock);
+
+ video->cur_frm = NULL;
+ video->sequence = 0;
+ video->num_formats = 0;
+
+ /* Initialize videobuf2 queue */
+ vidq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ vidq->io_modes = VB2_MMAP | VB2_DMABUF;
+ vidq->drv_priv = video;
+ vidq->buf_struct_size = sizeof(struct sun6i_csi_buffer);
+ vidq->ops = &sun6i_csi_vb2_ops;
+ vidq->mem_ops = &vb2_dma_contig_memops;
+ vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ vidq->lock = &video->lock;
+ vidq->min_buffers_needed = 1;
+ vidq->dev = csi->dev;
+
+ ret = vb2_queue_init(vidq);
+ if (ret) {
+ v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret);
+ goto error;
+ }
+
+ /* Register video device */
+ strlcpy(vdev->name, name, sizeof(vdev->name));
+ vdev->release = video_device_release_empty;
+ vdev->fops = &sun6i_video_fops;
+ vdev->ioctl_ops = &sun6i_video_ioctl_ops;
+ vdev->vfl_type = VFL_TYPE_GRABBER;
+ vdev->vfl_dir = VFL_DIR_RX;
+ vdev->v4l2_dev = &csi->v4l2_dev;
+ vdev->queue = vidq;
+ vdev->lock = &video->lock;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+ video_set_drvdata(vdev, video);
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret < 0) {
+ v4l2_err(&csi->v4l2_dev,
+ "video_register_device failed: %d\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ sun6i_video_cleanup(video);
+ return ret;
+}
+
+void sun6i_video_cleanup(struct sun6i_video *video)
+{
+ if (video_is_registered(&video->vdev))
+ video_unregister_device(&video->vdev);
+
+ media_entity_cleanup(&video->vdev.entity);
+}
diff --git a/drivers/media/platform/sun6i-csi/sun6i_video.h b/drivers/media/platform/sun6i-csi/sun6i_video.h
new file mode 100644
index 0000000..14eac6e
--- /dev/null
+++ b/drivers/media/platform/sun6i-csi/sun6i_video.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 Yong Deng <yong.deng-+3dxTMOEIRNWk0Htik3J/w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SUN6I_VIDEO_H__
+#define __SUN6I_VIDEO_H__
+
+#include <media/v4l2-dev.h>
+#include <media/videobuf2-core.h>
+
+/*
+ * struct sun6i_csi_format - CSI media bus format information
+ * @fourcc: Fourcc code for this format
+ * @mbus_code: V4L2 media bus format code.
+ * @bpp: Bytes per pixel (when stored in memory)
+ */
+struct sun6i_csi_format {
+ u32 fourcc;
+ u32 mbus_code;
+ u8 bpp;
+};
+
+struct sun6i_csi;
+
+struct sun6i_video {
+ struct video_device vdev;
+ struct media_pad pad;
+ struct sun6i_csi *csi;
+
+ struct mutex lock;
+
+ struct vb2_queue vb2_vidq;
+ spinlock_t dma_queue_lock;
+ struct list_head dma_queue;
+
+ struct sun6i_csi_buffer *cur_frm;
+ unsigned int sequence;
+
+ struct sun6i_csi_format *formats;
+ unsigned int num_formats;
+ struct sun6i_csi_format *current_fmt;
+ struct v4l2_format fmt;
+};
+
+int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
+ const char *name);
+void sun6i_video_cleanup(struct sun6i_video *video);
+
+void sun6i_video_frame_done(struct sun6i_video *video);
+
+#endif /* __SUN6I_VIDEO_H__ */
--
1.8.3.1
^ permalink raw reply related [flat|nested] 56+ messages in thread