This series is the v2 of the series that attempts to support media controller
in the atmel ISC and XISC drivers.
This series also includes the csi2dc driver which was previously sent in a
separate series:
https://www.spinics.net/lists/linux-media/msg181042.html
https://www.spinics.net/lists/linux-media/msg181044.html
The driver now addresses comments received in latest v5 series from last year.
The series includes some minor changes and fixes that improve the isc common
code base, like removing the enum frameintervals VIDIOC, fixing bytesperline
for planar formats, etc.
Many thanks to folks from libcamera who helped a lot with understanding
how a media controller driver should behave.
Feedback is welcome !
Changes in v2:
- integrated many changes suggested by Jacopo in the review of the v1 series.
- add a few new patches
Eugen Hristev (25):
MAINTAINERS: add microchip csi2dc
dt-bindings: media: atmel: csi2dc: add bindings for microchip csi2dc
media: atmel: introduce microchip csi2dc driver
MAINTAINERS: atmel-isc: add new file atmel-isc-clk.c
media: atmel: atmel-isc: split the clock code into separate source
file
media: atmel: atmel-isc: replace video device name with module name
media: atmel: atmel-sama7g5-isc: fix ispck leftover
media: atmel: atmel-isc-base: use streaming status when queueing
buffers
media: atmel: atmel-isc-base: remove frameintervals VIDIOC
media: atmel: atmel-isc-base: report frame sizes as full supported
range
media: atmel: atmel-isc-base: implement mbus_code support in enumfmt
media: atmel: atmel-isc-base: fix bytesperline value for planar
formats
MAINTAINERS: atmel-isc: add new file atmel-isc-mc.c
media: atmel: atmel-isc: implement media controller
ARM: dts: at91: sama7g5: add nodes for video capture
ARM: configs: at91: sama7: add xisc and csi2dc
ARM: multi_v7_defconfig: add atmel video pipeline modules
media: atmel: atmel-sama5d2-isc: fix wrong mask in YUYV format check
media: atmel: atmel-isc-base: use mutex to lock awb workqueue from
streaming
media: atmel: atmel-isc-base: add wb debug messages
media: atmel: atmel-isc-base: clamp wb gain coefficients
media: atmel: atmel-sama7g5-isc: fix UYVY input format mbus_code typo
media: atmel: atmel-isc: add raw Bayer 8bit 10bit output formats
media: atmel: atmel-isc: compact the controller formats list
media: atmel: atmel-isc: change format propagation to subdev into only
verification
.../bindings/media/microchip,csi2dc.yaml | 159 ++++
MAINTAINERS | 9 +
arch/arm/boot/dts/sama7g5.dtsi | 49 ++
arch/arm/configs/multi_v7_defconfig | 3 +
arch/arm/configs/sama7_defconfig | 2 +
drivers/media/platform/Makefile | 1 +
drivers/media/platform/atmel/Kconfig | 15 +
drivers/media/platform/atmel/Makefile | 4 +-
drivers/media/platform/atmel/atmel-isc-base.c | 788 ++++++-----------
drivers/media/platform/atmel/atmel-isc-clk.c | 311 +++++++
drivers/media/platform/atmel/atmel-isc-mc.c | 287 +++++++
drivers/media/platform/atmel/atmel-isc.h | 49 ++
.../media/platform/atmel/atmel-sama5d2-isc.c | 59 +-
.../media/platform/atmel/atmel-sama7g5-isc.c | 72 +-
.../media/platform/atmel/microchip-csi2dc.c | 797 ++++++++++++++++++
15 files changed, 2036 insertions(+), 569 deletions(-)
create mode 100644 Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
create mode 100644 drivers/media/platform/atmel/atmel-isc-clk.c
create mode 100644 drivers/media/platform/atmel/atmel-isc-mc.c
create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c
--
2.25.1
The atmel-isc-base is getting crowded. Split the clock functions into
atmel-isc-clk.c.
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/Makefile | 3 +-
drivers/media/platform/atmel/atmel-isc-base.c | 295 -----------------
drivers/media/platform/atmel/atmel-isc-clk.c | 311 ++++++++++++++++++
drivers/media/platform/atmel/atmel-isc.h | 7 +
4 files changed, 320 insertions(+), 296 deletions(-)
create mode 100644 drivers/media/platform/atmel/atmel-isc-clk.c
diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
index 39f0a7eba702..794e8f739287 100644
--- a/drivers/media/platform/atmel/Makefile
+++ b/drivers/media/platform/atmel/Makefile
@@ -1,9 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
atmel-isc-objs = atmel-sama5d2-isc.o
atmel-xisc-objs = atmel-sama7g5-isc.o
+atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o
obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
-obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-base.o
+obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o
obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 660cd0ab6749..58f468d32613 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -8,10 +8,6 @@
* Author: Eugen Hristev <[email protected]>
*
*/
-
-#include <linux/clk.h>
-#include <linux/clkdev.h>
-#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/math64.h>
@@ -100,297 +96,6 @@ static inline void isc_reset_awb_ctrls(struct isc_device *isc)
}
}
-static int isc_wait_clk_stable(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- struct regmap *regmap = isc_clk->regmap;
- unsigned long timeout = jiffies + usecs_to_jiffies(1000);
- unsigned int status;
-
- while (time_before(jiffies, timeout)) {
- regmap_read(regmap, ISC_CLKSR, &status);
- if (!(status & ISC_CLKSR_SIP))
- return 0;
-
- usleep_range(10, 250);
- }
-
- return -ETIMEDOUT;
-}
-
-static int isc_clk_prepare(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- int ret;
-
- ret = pm_runtime_resume_and_get(isc_clk->dev);
- if (ret < 0)
- return ret;
-
- return isc_wait_clk_stable(hw);
-}
-
-static void isc_clk_unprepare(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
-
- isc_wait_clk_stable(hw);
-
- pm_runtime_put_sync(isc_clk->dev);
-}
-
-static int isc_clk_enable(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- u32 id = isc_clk->id;
- struct regmap *regmap = isc_clk->regmap;
- unsigned long flags;
- unsigned int status;
-
- dev_dbg(isc_clk->dev, "ISC CLK: %s, id = %d, div = %d, parent id = %d\n",
- __func__, id, isc_clk->div, isc_clk->parent_id);
-
- spin_lock_irqsave(&isc_clk->lock, flags);
- regmap_update_bits(regmap, ISC_CLKCFG,
- ISC_CLKCFG_DIV_MASK(id) | ISC_CLKCFG_SEL_MASK(id),
- (isc_clk->div << ISC_CLKCFG_DIV_SHIFT(id)) |
- (isc_clk->parent_id << ISC_CLKCFG_SEL_SHIFT(id)));
-
- regmap_write(regmap, ISC_CLKEN, ISC_CLK(id));
- spin_unlock_irqrestore(&isc_clk->lock, flags);
-
- regmap_read(regmap, ISC_CLKSR, &status);
- if (status & ISC_CLK(id))
- return 0;
- else
- return -EINVAL;
-}
-
-static void isc_clk_disable(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- u32 id = isc_clk->id;
- unsigned long flags;
-
- spin_lock_irqsave(&isc_clk->lock, flags);
- regmap_write(isc_clk->regmap, ISC_CLKDIS, ISC_CLK(id));
- spin_unlock_irqrestore(&isc_clk->lock, flags);
-}
-
-static int isc_clk_is_enabled(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- u32 status;
- int ret;
-
- ret = pm_runtime_resume_and_get(isc_clk->dev);
- if (ret < 0)
- return 0;
-
- regmap_read(isc_clk->regmap, ISC_CLKSR, &status);
-
- pm_runtime_put_sync(isc_clk->dev);
-
- return status & ISC_CLK(isc_clk->id) ? 1 : 0;
-}
-
-static unsigned long
-isc_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
-
- return DIV_ROUND_CLOSEST(parent_rate, isc_clk->div + 1);
-}
-
-static int isc_clk_determine_rate(struct clk_hw *hw,
- struct clk_rate_request *req)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- long best_rate = -EINVAL;
- int best_diff = -1;
- unsigned int i, div;
-
- for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
- struct clk_hw *parent;
- unsigned long parent_rate;
-
- parent = clk_hw_get_parent_by_index(hw, i);
- if (!parent)
- continue;
-
- parent_rate = clk_hw_get_rate(parent);
- if (!parent_rate)
- continue;
-
- for (div = 1; div < ISC_CLK_MAX_DIV + 2; div++) {
- unsigned long rate;
- int diff;
-
- rate = DIV_ROUND_CLOSEST(parent_rate, div);
- diff = abs(req->rate - rate);
-
- if (best_diff < 0 || best_diff > diff) {
- best_rate = rate;
- best_diff = diff;
- req->best_parent_rate = parent_rate;
- req->best_parent_hw = parent;
- }
-
- if (!best_diff || rate < req->rate)
- break;
- }
-
- if (!best_diff)
- break;
- }
-
- dev_dbg(isc_clk->dev,
- "ISC CLK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
- __func__, best_rate,
- __clk_get_name((req->best_parent_hw)->clk),
- req->best_parent_rate);
-
- if (best_rate < 0)
- return best_rate;
-
- req->rate = best_rate;
-
- return 0;
-}
-
-static int isc_clk_set_parent(struct clk_hw *hw, u8 index)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
-
- if (index >= clk_hw_get_num_parents(hw))
- return -EINVAL;
-
- isc_clk->parent_id = index;
-
- return 0;
-}
-
-static u8 isc_clk_get_parent(struct clk_hw *hw)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
-
- return isc_clk->parent_id;
-}
-
-static int isc_clk_set_rate(struct clk_hw *hw,
- unsigned long rate,
- unsigned long parent_rate)
-{
- struct isc_clk *isc_clk = to_isc_clk(hw);
- u32 div;
-
- if (!rate)
- return -EINVAL;
-
- div = DIV_ROUND_CLOSEST(parent_rate, rate);
- if (div > (ISC_CLK_MAX_DIV + 1) || !div)
- return -EINVAL;
-
- isc_clk->div = div - 1;
-
- return 0;
-}
-
-static const struct clk_ops isc_clk_ops = {
- .prepare = isc_clk_prepare,
- .unprepare = isc_clk_unprepare,
- .enable = isc_clk_enable,
- .disable = isc_clk_disable,
- .is_enabled = isc_clk_is_enabled,
- .recalc_rate = isc_clk_recalc_rate,
- .determine_rate = isc_clk_determine_rate,
- .set_parent = isc_clk_set_parent,
- .get_parent = isc_clk_get_parent,
- .set_rate = isc_clk_set_rate,
-};
-
-static int isc_clk_register(struct isc_device *isc, unsigned int id)
-{
- struct regmap *regmap = isc->regmap;
- struct device_node *np = isc->dev->of_node;
- struct isc_clk *isc_clk;
- struct clk_init_data init;
- const char *clk_name = np->name;
- const char *parent_names[3];
- int num_parents;
-
- if (id == ISC_ISPCK && !isc->ispck_required)
- return 0;
-
- num_parents = of_clk_get_parent_count(np);
- if (num_parents < 1 || num_parents > 3)
- return -EINVAL;
-
- if (num_parents > 2 && id == ISC_ISPCK)
- num_parents = 2;
-
- of_clk_parent_fill(np, parent_names, num_parents);
-
- if (id == ISC_MCK)
- of_property_read_string(np, "clock-output-names", &clk_name);
- else
- clk_name = "isc-ispck";
-
- init.parent_names = parent_names;
- init.num_parents = num_parents;
- init.name = clk_name;
- init.ops = &isc_clk_ops;
- init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
-
- isc_clk = &isc->isc_clks[id];
- isc_clk->hw.init = &init;
- isc_clk->regmap = regmap;
- isc_clk->id = id;
- isc_clk->dev = isc->dev;
- spin_lock_init(&isc_clk->lock);
-
- isc_clk->clk = clk_register(isc->dev, &isc_clk->hw);
- if (IS_ERR(isc_clk->clk)) {
- dev_err(isc->dev, "%s: clock register fail\n", clk_name);
- return PTR_ERR(isc_clk->clk);
- } else if (id == ISC_MCK)
- of_clk_add_provider(np, of_clk_src_simple_get, isc_clk->clk);
-
- return 0;
-}
-
-int isc_clk_init(struct isc_device *isc)
-{
- unsigned int i;
- int ret;
-
- for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++)
- isc->isc_clks[i].clk = ERR_PTR(-EINVAL);
-
- for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
- ret = isc_clk_register(isc, i);
- if (ret)
- return ret;
- }
-
- return 0;
-}
-EXPORT_SYMBOL_GPL(isc_clk_init);
-
-void isc_clk_cleanup(struct isc_device *isc)
-{
- unsigned int i;
-
- of_clk_del_provider(isc->dev->of_node);
-
- for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
- struct isc_clk *isc_clk = &isc->isc_clks[i];
-
- if (!IS_ERR(isc_clk->clk))
- clk_unregister(isc_clk->clk);
- }
-}
-EXPORT_SYMBOL_GPL(isc_clk_cleanup);
static int isc_queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers, unsigned int *nplanes,
diff --git a/drivers/media/platform/atmel/atmel-isc-clk.c b/drivers/media/platform/atmel/atmel-isc-clk.c
new file mode 100644
index 000000000000..2059fe376b00
--- /dev/null
+++ b/drivers/media/platform/atmel/atmel-isc-clk.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Microchip Image Sensor Controller (ISC) common clock driver setup
+ *
+ * Copyright (C) 2016 Microchip Technology, Inc.
+ *
+ * Author: Songjun Wu
+ * Author: Eugen Hristev <[email protected]>
+ *
+ */
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include "atmel-isc-regs.h"
+#include "atmel-isc.h"
+
+static int isc_wait_clk_stable(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ struct regmap *regmap = isc_clk->regmap;
+ unsigned long timeout = jiffies + usecs_to_jiffies(1000);
+ unsigned int status;
+
+ while (time_before(jiffies, timeout)) {
+ regmap_read(regmap, ISC_CLKSR, &status);
+ if (!(status & ISC_CLKSR_SIP))
+ return 0;
+
+ usleep_range(10, 250);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int isc_clk_prepare(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(isc_clk->dev);
+ if (ret < 0)
+ return ret;
+
+ return isc_wait_clk_stable(hw);
+}
+
+static void isc_clk_unprepare(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+
+ isc_wait_clk_stable(hw);
+
+ pm_runtime_put_sync(isc_clk->dev);
+}
+
+static int isc_clk_enable(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ u32 id = isc_clk->id;
+ struct regmap *regmap = isc_clk->regmap;
+ unsigned long flags;
+ unsigned int status;
+
+ dev_dbg(isc_clk->dev, "ISC CLK: %s, id = %d, div = %d, parent id = %d\n",
+ __func__, id, isc_clk->div, isc_clk->parent_id);
+
+ spin_lock_irqsave(&isc_clk->lock, flags);
+ regmap_update_bits(regmap, ISC_CLKCFG,
+ ISC_CLKCFG_DIV_MASK(id) | ISC_CLKCFG_SEL_MASK(id),
+ (isc_clk->div << ISC_CLKCFG_DIV_SHIFT(id)) |
+ (isc_clk->parent_id << ISC_CLKCFG_SEL_SHIFT(id)));
+
+ regmap_write(regmap, ISC_CLKEN, ISC_CLK(id));
+ spin_unlock_irqrestore(&isc_clk->lock, flags);
+
+ regmap_read(regmap, ISC_CLKSR, &status);
+ if (status & ISC_CLK(id))
+ return 0;
+ else
+ return -EINVAL;
+}
+
+static void isc_clk_disable(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ u32 id = isc_clk->id;
+ unsigned long flags;
+
+ spin_lock_irqsave(&isc_clk->lock, flags);
+ regmap_write(isc_clk->regmap, ISC_CLKDIS, ISC_CLK(id));
+ spin_unlock_irqrestore(&isc_clk->lock, flags);
+}
+
+static int isc_clk_is_enabled(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ u32 status;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(isc_clk->dev);
+ if (ret < 0)
+ return 0;
+
+ regmap_read(isc_clk->regmap, ISC_CLKSR, &status);
+
+ pm_runtime_put_sync(isc_clk->dev);
+
+ return status & ISC_CLK(isc_clk->id) ? 1 : 0;
+}
+
+static unsigned long
+isc_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+
+ return DIV_ROUND_CLOSEST(parent_rate, isc_clk->div + 1);
+}
+
+static int isc_clk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ long best_rate = -EINVAL;
+ int best_diff = -1;
+ unsigned int i, div;
+
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+ struct clk_hw *parent;
+ unsigned long parent_rate;
+
+ parent = clk_hw_get_parent_by_index(hw, i);
+ if (!parent)
+ continue;
+
+ parent_rate = clk_hw_get_rate(parent);
+ if (!parent_rate)
+ continue;
+
+ for (div = 1; div < ISC_CLK_MAX_DIV + 2; div++) {
+ unsigned long rate;
+ int diff;
+
+ rate = DIV_ROUND_CLOSEST(parent_rate, div);
+ diff = abs(req->rate - rate);
+
+ if (best_diff < 0 || best_diff > diff) {
+ best_rate = rate;
+ best_diff = diff;
+ req->best_parent_rate = parent_rate;
+ req->best_parent_hw = parent;
+ }
+
+ if (!best_diff || rate < req->rate)
+ break;
+ }
+
+ if (!best_diff)
+ break;
+ }
+
+ dev_dbg(isc_clk->dev,
+ "ISC CLK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
+ __func__, best_rate,
+ __clk_get_name((req->best_parent_hw)->clk),
+ req->best_parent_rate);
+
+ if (best_rate < 0)
+ return best_rate;
+
+ req->rate = best_rate;
+
+ return 0;
+}
+
+static int isc_clk_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+
+ if (index >= clk_hw_get_num_parents(hw))
+ return -EINVAL;
+
+ isc_clk->parent_id = index;
+
+ return 0;
+}
+
+static u8 isc_clk_get_parent(struct clk_hw *hw)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+
+ return isc_clk->parent_id;
+}
+
+static int isc_clk_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct isc_clk *isc_clk = to_isc_clk(hw);
+ u32 div;
+
+ if (!rate)
+ return -EINVAL;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+ if (div > (ISC_CLK_MAX_DIV + 1) || !div)
+ return -EINVAL;
+
+ isc_clk->div = div - 1;
+
+ return 0;
+}
+
+static const struct clk_ops isc_clk_ops = {
+ .prepare = isc_clk_prepare,
+ .unprepare = isc_clk_unprepare,
+ .enable = isc_clk_enable,
+ .disable = isc_clk_disable,
+ .is_enabled = isc_clk_is_enabled,
+ .recalc_rate = isc_clk_recalc_rate,
+ .determine_rate = isc_clk_determine_rate,
+ .set_parent = isc_clk_set_parent,
+ .get_parent = isc_clk_get_parent,
+ .set_rate = isc_clk_set_rate,
+};
+
+static int isc_clk_register(struct isc_device *isc, unsigned int id)
+{
+ struct regmap *regmap = isc->regmap;
+ struct device_node *np = isc->dev->of_node;
+ struct isc_clk *isc_clk;
+ struct clk_init_data init;
+ const char *clk_name = np->name;
+ const char *parent_names[3];
+ int num_parents;
+
+ if (id == ISC_ISPCK && !isc->ispck_required)
+ return 0;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents < 1 || num_parents > 3)
+ return -EINVAL;
+
+ if (num_parents > 2 && id == ISC_ISPCK)
+ num_parents = 2;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ if (id == ISC_MCK)
+ of_property_read_string(np, "clock-output-names", &clk_name);
+ else
+ clk_name = "isc-ispck";
+
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.name = clk_name;
+ init.ops = &isc_clk_ops;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
+
+ isc_clk = &isc->isc_clks[id];
+ isc_clk->hw.init = &init;
+ isc_clk->regmap = regmap;
+ isc_clk->id = id;
+ isc_clk->dev = isc->dev;
+ spin_lock_init(&isc_clk->lock);
+
+ isc_clk->clk = clk_register(isc->dev, &isc_clk->hw);
+ if (IS_ERR(isc_clk->clk)) {
+ dev_err(isc->dev, "%s: clock register fail\n", clk_name);
+ return PTR_ERR(isc_clk->clk);
+ } else if (id == ISC_MCK) {
+ of_clk_add_provider(np, of_clk_src_simple_get, isc_clk->clk);
+ }
+
+ return 0;
+}
+
+int isc_clk_init(struct isc_device *isc)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++)
+ isc->isc_clks[i].clk = ERR_PTR(-EINVAL);
+
+ for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
+ ret = isc_clk_register(isc, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(isc_clk_init);
+
+void isc_clk_cleanup(struct isc_device *isc)
+{
+ unsigned int i;
+
+ of_clk_del_provider(isc->dev->of_node);
+
+ for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) {
+ struct isc_clk *isc_clk = &isc->isc_clks[i];
+
+ if (!IS_ERR(isc_clk->clk))
+ clk_unregister(isc_clk->clk);
+ }
+}
+EXPORT_SYMBOL_GPL(isc_clk_cleanup);
diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
index 2bfcb135ef13..32448ccfc636 100644
--- a/drivers/media/platform/atmel/atmel-isc.h
+++ b/drivers/media/platform/atmel/atmel-isc.h
@@ -10,6 +10,13 @@
*/
#ifndef _ATMEL_ISC_H_
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-dma-contig.h>
+
#define ISC_CLK_MAX_DIV 255
enum isc_clk_id {
--
2.25.1
Add Microchip CSI2DC driver in the list.
Signed-off-by: Eugen Hristev <[email protected]>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 486bdf97f754..545c642c937f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12422,6 +12422,13 @@ L: [email protected] (moderated for non-subscribers)
S: Supported
F: sound/soc/atmel
+MICROCHIP CSI2DC DRIVER
+M: Eugen Hristev <[email protected]>
+L: [email protected]
+S: Supported
+F: Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
+F: drivers/media/platform/atmel/microchip-csi2dc.c
+
MICROCHIP ECC DRIVER
M: Tudor Ambarus <[email protected]>
L: [email protected]
--
2.25.1
Add bindings documentation for Microchip CSI2 Demultiplexer controller.
CSI2DC is a demultiplexer from Synopsys IDI interface specification to
parallel interface connection or direct memory access.
Signed-off-by: Eugen Hristev <[email protected]>
---
Changes in v2:
- changed the endpoint/port references as suggested by Rob
Changes in this version :
- fixed 'sink' name to be actually source.
- added dma properties and example with dma
Previous change log:
Changes in v5:
- modified bindings as per Rob Herring review
Changes in v4:
- Removed property for inter-line-delay and for clock continuous/non-continuous
- Removed virtual channel by reg for second endpoint
Changes in v3:
- Removed some text from description, as it was explained in the schema
- fixed other things as per Rob's review
- moved some text inside the schema, like the clock description
Changes in v2:
- fixed warnings reported by dt_binding_check
.../bindings/media/microchip,csi2dc.yaml | 159 ++++++++++++++++++
1 file changed, 159 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
diff --git a/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml b/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
new file mode 100644
index 000000000000..45418c964bf3
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
@@ -0,0 +1,159 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/microchip,csi2dc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip CSI2 Demux Controller (CSI2DC)
+
+maintainers:
+ - Eugen Hristev <[email protected]>
+
+description:
+ CSI2DC - Camera Serial Interface 2 Demux Controller
+
+ CSI2DC is a hardware block that receives incoming data from an IDI interface
+ and filters packets based on their data type and virtual channel identifier,
+ then converts the byte stream into a cross clock domain to a pixel stream
+ to a parallel interface that can be read by a sensor controller.
+ IDI interface is Synopsys proprietary.
+
+ CSI2DC provides two pipes, one video pipe and one data pipe. Video pipe
+ is connected to a sensor controller and the data pipe is accessible
+ as a DMA slave port to a DMA controller.
+
+ CSI2DC supports a single 'port' node as a sink port with Synopsys 32-bit
+ IDI interface. The connected endpoint must be a IDI interface compatible
+ device , that can provide 32-bit IDI interface connection as source port.
+ This port is mandatory.
+
+ CSI2DC supports one 'port' node as source port with parallel interface.
+ This is called video pipe.
+ This port has an 'endpoint' that can be connected to a sink port of another
+ controller (next in pipeline).
+
+ CSI2DC also supports direct access to the data through AHB, via DMA channel,
+ called data pipe.
+ Because of this, the source 'port' child node (second) is not mandatory.
+ If the source 'port' child node is missing, only data pipe is available.
+ For data pipe to be available, a dma controller must be referenced.
+
+properties:
+ compatible:
+ const: microchip,sama7g5-csi2dc
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 2
+
+ clock-names:
+ description:
+ CSI2DC must have two clocks to function correctly. One clock is the
+ peripheral clock for the inside functionality of the hardware block.
+ This is named 'pclk'. The second clock must be the cross domain clock,
+ in which CSI2DC will perform clock crossing. This clock must be fed
+ by the next controller in pipeline, which usually is a sensor controller.
+ Normally this clock should be given by this sensor controller who
+ is also a clock source. This clock is named 'scck', sensor controller clock.
+ items:
+ - const: pclk
+ - const: scck
+
+ dmas:
+ maxItems: 1
+
+ dma-names:
+ const: rx
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ Input port node, single endpoint describing the input port.
+
+ properties:
+ endpoint:
+ $ref: video-interfaces.yaml#
+ unevaluatedProperties: false
+ description: Endpoint connected to input device
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ Output port node, single endpoint describing the output port.
+
+ properties:
+ endpoint:
+ unevaluatedProperties: false
+ $ref: video-interfaces.yaml#
+ description: Endpoint connected to output device
+
+ required:
+ - port@0
+
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - ports
+
+examples:
+ # Example for connecting to a parallel sensor controller block
+ - |
+ csi2dc@e1404000 {
+ compatible = "microchip,sama7g5-csi2dc";
+ reg = <0xe1404000 0x500>;
+ clocks = <&pclk>, <&scck>;
+ clock-names = "pclk", "scck";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>; /* must be 0, first child port */
+ csi2dc_in: endpoint { /* input from IDI interface */
+ remote-endpoint = <&csi2host_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>; /* must be 1, second child port */
+ csi2dc_out: endpoint {
+ remote-endpoint = <&xisc_in>; /* output to sensor controller */
+ };
+ };
+ };
+ };
+
+ # Example for connecting to a DMA master as an AHB slave
+ - |
+ #include <dt-bindings/dma/at91.h>
+ csi2dc@e1404000 {
+ compatible = "microchip,sama7g5-csi2dc";
+ reg = <0xe1404000 0x500>;
+ clocks = <&pclk>, <&scck>;
+ clock-names = "pclk", "scck";
+ dmas = <&dma0 AT91_XDMAC_DT_PERID(34)>;
+ dma-names = "rx";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>; /* must be 0, first child port */
+ csi2dc_input: endpoint { /* input from IDI interface */
+ remote-endpoint = <&csi2host_out>;
+ };
+ };
+ };
+ };
+
+...
--
2.25.1
Microchip CSI2DC (CSI2 Demultiplexer Controller) is a misc bridge device
that converts a byte stream in IDI Synopsys format (coming from a CSI2HOST)
to a pixel stream that can be captured by a sensor controller.
Signed-off-by: Eugen Hristev <[email protected]>
---
Changes in v2:
- implement try formats
- added parallel mode
- moved to fwlink endpoints
- many other minor corrections from Jacopo's review
Changes in this revision:
- addressed comments by Jacopo and Laurent as in this thread:
https://www.spinics.net/lists/linux-media/msg181044.html
Previous change log :
Changes in v5:
- only in bindings
Changes in v4:
- now using get_mbus_config ops to get data from the subdevice, like the
virtual channel id, and the clock type.
- now having possibility to select any of the RAW10 data modes
- at completion time, select which formats are also available in the subdevice,
and move to the dynamic list accordingly
- changed the pipeline integration, do not advertise subdev ready at probe time.
wait until completion is done, and then start a workqueue that will register
this device as a subdevice for the next element in pipeline.
- moved the s_power code into a different function called now csi2dc_power
that is called with CONFIG_PM functions. This is also called at completion,
to have the device ready in case CONFIG_PM is not selected on the platform.
- merged try_fmt into set_fmt
- driver cleanup, wrapped lines over 80 characters
Changes in v2:
- moved driver to platform/atmel
- fixed minor things as per Sakari's review
- still some things from v2 review are not yet addressed, to be followed up
drivers/media/platform/Makefile | 1 +
drivers/media/platform/atmel/Kconfig | 15 +
drivers/media/platform/atmel/Makefile | 1 +
.../media/platform/atmel/microchip-csi2dc.c | 797 ++++++++++++++++++
4 files changed, 814 insertions(+)
create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 832357240e89..b18e5f704145 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/
obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/
obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel/
obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel/
+obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += atmel/
obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32/
diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig
index dda2f27da317..f83bee373d82 100644
--- a/drivers/media/platform/atmel/Kconfig
+++ b/drivers/media/platform/atmel/Kconfig
@@ -40,3 +40,18 @@ config VIDEO_ATMEL_ISI
help
This module makes the ATMEL Image Sensor Interface available
as a v4l2 device.
+
+config VIDEO_MICROCHIP_CSI2DC
+ tristate "Microchip CSI2 Demux Controller"
+ depends on VIDEO_V4L2 && COMMON_CLK && OF
+ depends on ARCH_AT91 || COMPILE_TEST
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+ help
+ CSI2 Demux Controller driver. CSI2DC is a helper chip
+ that converts IDI interface byte stream to a parallel pixel stream.
+ It supports various RAW formats as input.
+
+ To compile this driver as a module, choose M here: the
+ module will be called microchip-csi2dc.
diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
index 46d264ab7948..39f0a7eba702 100644
--- a/drivers/media/platform/atmel/Makefile
+++ b/drivers/media/platform/atmel/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-base.o
obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
+obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/atmel/microchip-csi2dc.c
new file mode 100644
index 000000000000..2b106f6fd5d0
--- /dev/null
+++ b/drivers/media/platform/atmel/microchip-csi2dc.c
@@ -0,0 +1,797 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Microchip CSI2 Demux Controller (CSI2DC) driver
+ *
+ * Copyright (C) 2018 Microchip Technology, Inc.
+ *
+ * Author: Eugen Hristev <[email protected]>
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+/* Global configuration register */
+#define CSI2DC_GCFG 0x0
+
+/* MIPI sensor pixel clock is free running */
+#define CSI2DC_GCFG_MIPIFRN BIT(0)
+/* GPIO parallel interface selection */
+#define CSI2DC_GCFG_GPIOSEL BIT(1)
+/* Output waveform inter-line minimum delay */
+#define CSI2DC_GCFG_HLC(v) ((v) << 4)
+#define CSI2DC_GCFG_HLC_MASK GENMASK(7, 4)
+/* SAMA7G5 requires a HLC delay of 15 */
+#define SAMA7G5_HLC (15)
+
+/* Global control register */
+#define CSI2DC_GCTLR 0x04
+#define CSI2DC_GCTLR_SWRST BIT(0)
+
+/* Global status register */
+#define CSI2DC_GS 0x08
+
+/* SSP interrupt status register */
+#define CSI2DC_SSPIS 0x28
+/* Pipe update register */
+#define CSI2DC_PU 0xc0
+/* Video pipe attributes update */
+#define CSI2DC_PU_VP BIT(0)
+
+/* Pipe update status register */
+#define CSI2DC_PUS 0xc4
+
+/* Video pipeline enable register */
+#define CSI2DC_VPE 0xf8
+#define CSI2DC_VPE_ENABLE BIT(0)
+
+/* Video pipeline configuration register */
+#define CSI2DC_VPCFG 0xfc
+/* Data type */
+#define CSI2DC_VPCFG_DT(v) ((v) << 0)
+#define CSI2DC_VPCFG_DT_MASK GENMASK(5, 0)
+/* Virtual channel identifier */
+#define CSI2DC_VPCFG_VC(v) ((v) << 6)
+#define CSI2DC_VPCFG_VC_MASK GENMASK(7, 6)
+/* Decompression enable */
+#define CSI2DC_VPCFG_DE BIT(8)
+/* Decoder mode */
+#define CSI2DC_VPCFG_DM(v) ((v) << 9)
+#define CSI2DC_VPCFG_DM_DECODER8TO12 0
+/* Decoder predictor 2 selection */
+#define CSI2DC_VPCFG_DP2 BIT(12)
+/* Recommended memory storage */
+#define CSI2DC_VPCFG_RMS BIT(13)
+/* Post adjustment */
+#define CSI2DC_VPCFG_PA BIT(14)
+
+/* Video pipeline column register */
+#define CSI2DC_VPCOL 0x100
+/* Column number */
+#define CSI2DC_VPCOL_COL(v) ((v) << 0)
+#define CSI2DC_VPCOL_COL_MASK GENMASK(15, 0)
+
+/* Video pipeline row register */
+#define CSI2DC_VPROW 0x104
+/* Row number */
+#define CSI2DC_VPROW_ROW(v) ((v) << 0)
+#define CSI2DC_VPROW_ROW_MASK GENMASK(15, 0)
+
+/* Version register */
+#define CSI2DC_VERSION 0x1fc
+
+/* register read/write helpers */
+#define csi2dc_readl(st, reg) readl_relaxed((st)->base + (reg))
+#define csi2dc_writel(st, reg, val) writel_relaxed((val), \
+ (st)->base + (reg))
+
+/* supported RAW data types */
+#define CSI2DC_DT_RAW6 0x28
+#define CSI2DC_DT_RAW7 0x29
+#define CSI2DC_DT_RAW8 0x2a
+#define CSI2DC_DT_RAW10 0x2b
+#define CSI2DC_DT_RAW12 0x2c
+#define CSI2DC_DT_RAW14 0x2d
+
+/*
+ * struct csi2dc_format - CSI2DC format type struct
+ * @mbus_code: Media bus code for the format
+ * @dt: Data type constant for this format
+ */
+struct csi2dc_format {
+ u32 mbus_code;
+ u32 dt;
+};
+
+static const struct csi2dc_format csi2dc_formats[] = {
+ {
+ .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .dt = CSI2DC_DT_RAW8,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .dt = CSI2DC_DT_RAW8,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .dt = CSI2DC_DT_RAW8,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .dt = CSI2DC_DT_RAW8,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .dt = CSI2DC_DT_RAW10,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .dt = CSI2DC_DT_RAW10,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .dt = CSI2DC_DT_RAW10,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .dt = CSI2DC_DT_RAW10,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ },
+};
+
+enum mipi_csi_pads {
+ CSI2DC_PAD_SINK = 0,
+ CSI2DC_PAD_SOURCE = 1,
+ CSI2DC_PADS_NUM = 2,
+};
+
+/*
+ * struct csi2dc_device - CSI2DC device driver data/config struct
+ * @base: Register map base address
+ * @csi2dc_sd: v4l2 subdevice for the csi2dc device
+ * This is the subdevice that the csi2dc device itself
+ * registers in v4l2 subsystem
+ * @dev: struct device for this csi2dc device
+ * @pclk: Peripheral clock reference
+ * Input clock that clocks the hardware block internal
+ * logic
+ * @scck: Sensor Controller clock reference
+ * Input clock that is used to generate the pixel clock
+ * @format: Current saved format used in g/s fmt
+ * @cur_fmt: Current state format
+ * @try_fmt: Try format that is being tried
+ * @pads: Media entity pads for the csi2dc subdevice
+ * @clk_gated: Whether the clock is gated or free running
+ * @video_pipe: Whether video pipeline is configured
+ * @parallel_mode: The underlying subdevice is connected on a parallel bus
+ * @vc: Current set virtual channel
+ * @notifier: Async notifier that is used to bound the underlying
+ * subdevice to the csi2dc subdevice
+ * @input_sd: Reference to the underlying subdevice bound to the
+ * csi2dc subdevice
+ * @remote_pad: Pad number of the underlying subdevice that is linked
+ * to the csi2dc subdevice sink pad.
+ */
+struct csi2dc_device {
+ void __iomem *base;
+ struct v4l2_subdev csi2dc_sd;
+ struct device *dev;
+ struct clk *pclk;
+ struct clk *scck;
+
+ struct v4l2_mbus_framefmt format;
+
+ const struct csi2dc_format *cur_fmt;
+ const struct csi2dc_format *try_fmt;
+
+ struct media_pad pads[CSI2DC_PADS_NUM];
+
+ bool clk_gated;
+ bool video_pipe;
+ bool parallel_mode;
+ u32 vc;
+
+ struct v4l2_async_notifier notifier;
+
+ struct v4l2_subdev *input_sd;
+
+ u32 remote_pad;
+};
+
+static inline struct csi2dc_device *
+csi2dc_sd_to_csi2dc_device(struct v4l2_subdev *csi2dc_sd)
+{
+ return container_of(csi2dc_sd, struct csi2dc_device, csi2dc_sd);
+}
+
+static int csi2dc_enum_mbus_code(struct v4l2_subdev *csi2dc_sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index >= ARRAY_SIZE(csi2dc_formats))
+ return -EINVAL;
+
+ code->code = csi2dc_formats[code->index].mbus_code;
+
+ return 0;
+}
+
+static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
+ struct v4l2_mbus_framefmt *v4l2_try_fmt;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+ v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
+ format->pad);
+ format->format = *v4l2_try_fmt;
+
+ return 0;
+ }
+
+ format->format = csi2dc->format;
+
+ return 0;
+}
+
+static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *req_fmt)
+{
+ struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
+ const struct csi2dc_format *fmt, *try_fmt = NULL;
+ struct v4l2_mbus_framefmt *v4l2_try_fmt;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(csi2dc_formats); i++) {
+ fmt = &csi2dc_formats[i];
+ if (req_fmt->format.code == fmt->mbus_code)
+ try_fmt = fmt;
+ fmt++;
+ }
+
+ /* in case we could not find the desired format, default to something */
+ if (!try_fmt) {
+ try_fmt = &csi2dc_formats[0];
+
+ dev_dbg(csi2dc->dev,
+ "CSI2DC unsupported format 0x%x, defaulting to 0x%x\n",
+ req_fmt->format.code, csi2dc_formats[0].mbus_code);
+ }
+
+ req_fmt->format.code = try_fmt->mbus_code;
+ req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
+ req_fmt->format.field = V4L2_FIELD_NONE;
+
+ if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
+ req_fmt->pad);
+ *v4l2_try_fmt = req_fmt->format;
+ /* Trying on the pad sink makes the source sink change too */
+ if (req_fmt->pad == CSI2DC_PAD_SINK) {
+ v4l2_try_fmt =
+ v4l2_subdev_get_try_format(csi2dc_sd,
+ sd_state,
+ CSI2DC_PAD_SOURCE);
+ *v4l2_try_fmt = req_fmt->format;
+ }
+ /* if we are just trying, we are done */
+ return 0;
+ }
+
+ /* save the format for later requests */
+ csi2dc->format = req_fmt->format;
+
+ /* update config */
+ csi2dc->cur_fmt = try_fmt;
+
+ dev_dbg(csi2dc->dev, "new format set: 0x%x @%dx%d\n",
+ csi2dc->format.code, csi2dc->format.width,
+ csi2dc->format.height);
+
+ return 0;
+}
+
+static int csi2dc_power(struct csi2dc_device *csi2dc, int on)
+{
+ int ret = 0;
+
+ if (on) {
+ ret = clk_prepare_enable(csi2dc->pclk);
+ if (ret) {
+ dev_err(csi2dc->dev, "failed to enable pclk:%d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(csi2dc->scck);
+ if (ret) {
+ dev_err(csi2dc->dev, "failed to enable scck:%d\n", ret);
+ clk_disable_unprepare(csi2dc->pclk);
+ return ret;
+ }
+
+ /* if powering up, deassert reset line */
+ csi2dc_writel(csi2dc, CSI2DC_GCTLR, CSI2DC_GCTLR_SWRST);
+ } else {
+ /* if powering down, assert reset line */
+ csi2dc_writel(csi2dc, CSI2DC_GCTLR, 0);
+
+ clk_disable_unprepare(csi2dc->scck);
+ clk_disable_unprepare(csi2dc->pclk);
+ }
+
+ return ret;
+}
+
+static int csi2dc_get_mbus_config(struct csi2dc_device *csi2dc)
+{
+ struct v4l2_mbus_config mbus_config = { 0 };
+ int ret;
+
+ ret = v4l2_subdev_call(csi2dc->input_sd, pad, get_mbus_config,
+ csi2dc->remote_pad, &mbus_config);
+ if (ret == -ENOIOCTLCMD) {
+ dev_dbg(csi2dc->dev,
+ "no remote mbus configuration available\n");
+ goto csi2dc_get_mbus_config_defaults;
+ }
+
+ if (ret) {
+ dev_err(csi2dc->dev,
+ "failed to get remote mbus configuration\n");
+ goto csi2dc_get_mbus_config_defaults;
+ }
+
+ if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_0)
+ csi2dc->vc = 0;
+ else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_1)
+ csi2dc->vc = 1;
+ else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_2)
+ csi2dc->vc = 2;
+ else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_3)
+ csi2dc->vc = 3;
+
+ dev_dbg(csi2dc->dev, "subdev sending on channel %d\n", csi2dc->vc);
+
+ csi2dc->clk_gated = mbus_config.flags &
+ V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+
+ dev_dbg(csi2dc->dev, "mbus_config: %s clock\n",
+ csi2dc->clk_gated ? "gated" : "free running");
+
+ return 0;
+
+csi2dc_get_mbus_config_defaults:
+ csi2dc->vc = 0; /* Virtual ID 0 by default */
+
+ return 0;
+}
+
+static void csi2dc_vp_update(struct csi2dc_device *csi2dc)
+{
+ u32 vp, gcfg;
+
+ if (!csi2dc->video_pipe) {
+ dev_err(csi2dc->dev, "video pipeline unavailable\n");
+ return;
+ }
+
+ if (csi2dc->parallel_mode) {
+ /* In parallel mode, GPIO parallel interface must be selected */
+ gcfg = csi2dc_readl(csi2dc, CSI2DC_GCFG);
+ gcfg |= CSI2DC_GCFG_GPIOSEL;
+ csi2dc_writel(csi2dc, CSI2DC_GCFG, gcfg);
+ return;
+ }
+
+ /* serial video pipeline */
+
+ csi2dc_writel(csi2dc, CSI2DC_GCFG,
+ (SAMA7G5_HLC & CSI2DC_GCFG_HLC_MASK) |
+ (csi2dc->clk_gated ? 0 : CSI2DC_GCFG_MIPIFRN));
+
+ csi2dc_writel(csi2dc, CSI2DC_VPCOL,
+ CSI2DC_VPCOL_COL(0xfff) & CSI2DC_VPCOL_COL_MASK);
+ csi2dc_writel(csi2dc, CSI2DC_VPROW,
+ CSI2DC_VPROW_ROW(0xfff) & CSI2DC_VPROW_ROW_MASK);
+
+ vp = CSI2DC_VPCFG_DT(csi2dc->cur_fmt->dt) & CSI2DC_VPCFG_DT_MASK;
+ vp |= CSI2DC_VPCFG_VC(csi2dc->vc) & CSI2DC_VPCFG_VC_MASK;
+ vp &= ~CSI2DC_VPCFG_DE;
+ vp |= CSI2DC_VPCFG_DM(CSI2DC_VPCFG_DM_DECODER8TO12);
+ vp &= ~CSI2DC_VPCFG_DP2;
+ vp &= ~CSI2DC_VPCFG_RMS;
+ vp |= CSI2DC_VPCFG_PA;
+
+ csi2dc_writel(csi2dc, CSI2DC_VPCFG, vp);
+ csi2dc_writel(csi2dc, CSI2DC_VPE, CSI2DC_VPE_ENABLE);
+ csi2dc_writel(csi2dc, CSI2DC_PU, CSI2DC_PU_VP);
+}
+
+static int csi2dc_s_stream(struct v4l2_subdev *csi2dc_sd, int enable)
+{
+ struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
+ int ret;
+
+ if (enable) {
+ ret = pm_runtime_resume_and_get(csi2dc->dev);
+ if (ret < 0)
+ return ret;
+
+ csi2dc_get_mbus_config(csi2dc);
+
+ csi2dc_vp_update(csi2dc);
+
+ return v4l2_subdev_call(csi2dc->input_sd, video, s_stream,
+ true);
+ }
+ /* stop streaming scenario */
+ ret = v4l2_subdev_call(csi2dc->input_sd, video, s_stream, false);
+
+ pm_runtime_put_sync(csi2dc->dev);
+
+ return ret;
+}
+
+static int csi2dc_init_cfg(struct v4l2_subdev *csi2dc_sd,
+ struct v4l2_subdev_state *sd_state)
+{
+ struct v4l2_mbus_framefmt *v4l2_try_fmt =
+ v4l2_subdev_get_try_format(csi2dc_sd, sd_state, 0);
+
+ v4l2_try_fmt->height = 480;
+ v4l2_try_fmt->width = 640;
+ v4l2_try_fmt->code = csi2dc_formats[0].mbus_code;
+ v4l2_try_fmt->colorspace = V4L2_COLORSPACE_SRGB;
+ v4l2_try_fmt->field = V4L2_FIELD_NONE;
+ v4l2_try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ v4l2_try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
+ v4l2_try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops csi2dc_pad_ops = {
+ .enum_mbus_code = csi2dc_enum_mbus_code,
+ .set_fmt = csi2dc_set_fmt,
+ .get_fmt = csi2dc_get_fmt,
+ .init_cfg = csi2dc_init_cfg,
+};
+
+static const struct v4l2_subdev_video_ops csi2dc_video_ops = {
+ .s_stream = csi2dc_s_stream,
+};
+
+static const struct v4l2_subdev_ops csi2dc_subdev_ops = {
+ .pad = &csi2dc_pad_ops,
+ .video = &csi2dc_video_ops,
+};
+
+static int csi2dc_async_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct csi2dc_device *csi2dc = container_of(notifier,
+ struct csi2dc_device, notifier);
+ int pad;
+ int ret;
+
+ csi2dc->input_sd = subdev;
+
+ pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
+ MEDIA_PAD_FL_SOURCE);
+ if (pad < 0) {
+ dev_err(csi2dc->dev, "Failed to find pad for %s\n",
+ subdev->name);
+ return pad;
+ }
+
+ csi2dc->remote_pad = pad;
+
+ ret = media_create_pad_link(&csi2dc->input_sd->entity,
+ csi2dc->remote_pad,
+ &csi2dc->csi2dc_sd.entity, 0,
+ MEDIA_LNK_FL_ENABLED);
+ if (ret) {
+ dev_err(csi2dc->dev,
+ "Failed to create pad link: %s to %s\n",
+ csi2dc->input_sd->entity.name,
+ csi2dc->csi2dc_sd.entity.name);
+ return ret;
+ }
+
+ dev_dbg(csi2dc->dev, "link with %s pad: %d\n",
+ csi2dc->input_sd->name, csi2dc->remote_pad);
+
+ return ret;
+}
+
+static const struct v4l2_async_notifier_operations csi2dc_async_ops = {
+ .bound = csi2dc_async_bound,
+};
+
+static int csi2dc_prepare_notifier(struct csi2dc_device *csi2dc,
+ struct fwnode_handle *input_fwnode)
+{
+ struct v4l2_async_subdev *asd;
+ int ret = 0;
+
+ v4l2_async_nf_init(&csi2dc->notifier);
+
+ asd = v4l2_async_nf_add_fwnode_remote(&csi2dc->notifier,
+ input_fwnode,
+ struct v4l2_async_subdev);
+
+ fwnode_handle_put(input_fwnode);
+
+ if (IS_ERR(asd)) {
+ ret = PTR_ERR(asd);
+ dev_err(csi2dc->dev,
+ "failed to add async notifier for node %pOF: %d\n",
+ to_of_node(input_fwnode), ret);
+ v4l2_async_nf_cleanup(&csi2dc->notifier);
+ return ret;
+ }
+
+ csi2dc->notifier.ops = &csi2dc_async_ops;
+
+ ret = v4l2_async_subdev_nf_register(&csi2dc->csi2dc_sd,
+ &csi2dc->notifier);
+
+ if (ret) {
+ dev_err(csi2dc->dev, "fail to register async notifier: %d\n",
+ ret);
+ v4l2_async_nf_cleanup(&csi2dc->notifier);
+ }
+
+ return ret;
+}
+
+static int csi2dc_of_parse(struct csi2dc_device *csi2dc,
+ struct device_node *of_node)
+{
+ struct fwnode_handle *input_fwnode, *output_fwnode;
+ struct v4l2_fwnode_endpoint input_endpoint = { 0 },
+ output_endpoint = { 0 };
+ int ret;
+
+ input_fwnode = fwnode_graph_get_next_endpoint(of_fwnode_handle(of_node),
+ NULL);
+
+ if (!input_fwnode) {
+ dev_err(csi2dc->dev,
+ "missing port node at %pOF, input node is mandatory.\n",
+ of_node);
+ return -EINVAL;
+ }
+
+ ret = v4l2_fwnode_endpoint_parse(input_fwnode, &input_endpoint);
+
+ if (ret) {
+ dev_err(csi2dc->dev, "endpoint not defined at %pOF\n", of_node);
+ goto csi2dc_of_parse_err;
+ }
+
+ if (input_endpoint.bus_type == V4L2_MBUS_PARALLEL ||
+ input_endpoint.bus_type == V4L2_MBUS_BT656) {
+ csi2dc->parallel_mode = true;
+ dev_dbg(csi2dc->dev,
+ "subdevice connected on parallel interface\n");
+ }
+
+ if (input_endpoint.bus_type == V4L2_MBUS_CSI2_DPHY) {
+ csi2dc->clk_gated = input_endpoint.bus.mipi_csi2.flags &
+ V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+ dev_dbg(csi2dc->dev,
+ "subdevice connected on serial interface\n");
+ dev_dbg(csi2dc->dev, "DT: %s clock\n",
+ csi2dc->clk_gated ? "gated" : "free running");
+ }
+
+ output_fwnode = fwnode_graph_get_next_endpoint
+ (of_fwnode_handle(of_node), input_fwnode);
+
+ if (output_fwnode)
+ ret = v4l2_fwnode_endpoint_parse(output_fwnode,
+ &output_endpoint);
+
+ fwnode_handle_put(output_fwnode);
+
+ if (!output_fwnode || ret) {
+ dev_info(csi2dc->dev,
+ "missing output node at %pOF, data pipe available only.\n",
+ of_node);
+ } else {
+ if (output_endpoint.bus_type != V4L2_MBUS_PARALLEL &&
+ output_endpoint.bus_type != V4L2_MBUS_BT656) {
+ dev_err(csi2dc->dev,
+ "output port must be parallel/bt656.\n");
+ ret = -EINVAL;
+ goto csi2dc_of_parse_err;
+ }
+
+ csi2dc->video_pipe = true;
+
+ dev_dbg(csi2dc->dev,
+ "block %pOF [%d.%d]->[%d.%d] video pipeline\n",
+ of_node, input_endpoint.base.port,
+ input_endpoint.base.id, output_endpoint.base.port,
+ output_endpoint.base.id);
+ }
+
+ /* prepare async notifier for subdevice completion */
+ return csi2dc_prepare_notifier(csi2dc, input_fwnode);
+
+csi2dc_of_parse_err:
+ fwnode_handle_put(input_fwnode);
+ return ret;
+}
+
+static void csi2dc_default_format(struct csi2dc_device *csi2dc)
+{
+ csi2dc->cur_fmt = &csi2dc_formats[0];
+
+ csi2dc->format.height = 480;
+ csi2dc->format.width = 640;
+ csi2dc->format.code = csi2dc_formats[0].mbus_code;
+ csi2dc->format.colorspace = V4L2_COLORSPACE_SRGB;
+ csi2dc->format.field = V4L2_FIELD_NONE;
+ csi2dc->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ csi2dc->format.quantization = V4L2_QUANTIZATION_DEFAULT;
+ csi2dc->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+}
+
+static int csi2dc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct csi2dc_device *csi2dc;
+ int ret = 0;
+ u32 ver;
+
+ csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL);
+ if (!csi2dc)
+ return -ENOMEM;
+
+ csi2dc->dev = dev;
+
+ csi2dc->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(csi2dc->base)) {
+ dev_err(dev, "base address not set\n");
+ return PTR_ERR(csi2dc->base);
+ }
+
+ csi2dc->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(csi2dc->pclk)) {
+ ret = PTR_ERR(csi2dc->pclk);
+ dev_err(dev, "failed to get pclk: %d\n", ret);
+ return ret;
+ }
+
+ csi2dc->scck = devm_clk_get(dev, "scck");
+ if (IS_ERR(csi2dc->scck)) {
+ ret = PTR_ERR(csi2dc->scck);
+ dev_err(dev, "failed to get scck: %d\n", ret);
+ return ret;
+ }
+
+ v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops);
+
+ csi2dc->csi2dc_sd.owner = THIS_MODULE;
+ csi2dc->csi2dc_sd.dev = dev;
+ snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name),
+ "csi2dc");
+
+ csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+
+ platform_set_drvdata(pdev, csi2dc);
+
+ ret = csi2dc_of_parse(csi2dc, dev->of_node);
+ if (ret)
+ goto csi2dc_probe_cleanup_entity;
+
+ csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ if (csi2dc->video_pipe)
+ csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity,
+ csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1,
+ csi2dc->pads);
+ if (ret < 0) {
+ dev_err(dev, "media entity init failed\n");
+ goto csi2dc_probe_cleanup_notifier;
+ }
+
+ csi2dc_default_format(csi2dc);
+
+ /* turn power on to validate capabilities */
+ ret = csi2dc_power(csi2dc, true);
+ if (ret < 0)
+ goto csi2dc_probe_cleanup_notifier;
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ ver = csi2dc_readl(csi2dc, CSI2DC_VERSION);
+ pm_request_idle(dev);
+
+ /*
+ * we must register the subdev after PM runtime has been requested,
+ * otherwise we might bound immediately and request pm_runtime_resume
+ * before runtime_enable.
+ */
+ ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd);
+ if (ret) {
+ dev_err(csi2dc->dev, "failed to register the subdevice\n");
+ goto csi2dc_probe_cleanup_notifier;
+ }
+
+ dev_info(dev, "Microchip CSI2DC version %x\n", ver);
+
+ return 0;
+
+csi2dc_probe_cleanup_notifier:
+ v4l2_async_nf_cleanup(&csi2dc->notifier);
+csi2dc_probe_cleanup_entity:
+ media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
+
+ return ret;
+}
+
+static int csi2dc_remove(struct platform_device *pdev)
+{
+ struct csi2dc_device *csi2dc = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd);
+ v4l2_async_nf_unregister(&csi2dc->notifier);
+ v4l2_async_nf_cleanup(&csi2dc->notifier);
+ media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
+
+ return 0;
+}
+
+static int __maybe_unused csi2dc_runtime_suspend(struct device *dev)
+{
+ struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
+
+ return csi2dc_power(csi2dc, false);
+}
+
+static int __maybe_unused csi2dc_runtime_resume(struct device *dev)
+{
+ struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
+
+ return csi2dc_power(csi2dc, true);
+}
+
+static const struct dev_pm_ops csi2dc_dev_pm_ops = {
+ SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
+};
+
+static const struct of_device_id csi2dc_of_match[] = {
+ { .compatible = "microchip,sama7g5-csi2dc" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, csi2dc_of_match);
+
+static struct platform_driver csi2dc_driver = {
+ .probe = csi2dc_probe,
+ .remove = csi2dc_remove,
+ .driver = {
+ .name = "microchip-csi2dc",
+ .pm = &csi2dc_dev_pm_ops,
+ .of_match_table = of_match_ptr(csi2dc_of_match),
+ },
+};
+
+module_platform_driver(csi2dc_driver);
+
+MODULE_AUTHOR("Eugen Hristev <[email protected]>");
+MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver");
+MODULE_LICENSE("GPL v2");
--
2.25.1
Add new file atmel-isc-clk.c to MAINTAINERS entry.
Signed-off-by: Eugen Hristev <[email protected]>
---
MAINTAINERS | 1 +
1 file changed, 1 insertion(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 545c642c937f..bb7b48e3605c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12455,6 +12455,7 @@ S: Supported
F: Documentation/devicetree/bindings/media/atmel,isc.yaml
F: Documentation/devicetree/bindings/media/microchip,xisc.yaml
F: drivers/media/platform/atmel/atmel-isc-base.c
+F: drivers/media/platform/atmel/atmel-isc-clk.c
F: drivers/media/platform/atmel/atmel-isc-regs.h
F: drivers/media/platform/atmel/atmel-isc.h
F: drivers/media/platform/atmel/atmel-sama5d2-isc.c
--
2.25.1
To have consistency with future media controller development,
replace the video device name with KBUILD_MODNAME.
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 58f468d32613..26a6090f056c 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1886,7 +1886,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
}
/* Register video device */
- strscpy(vdev->name, "microchip-isc", sizeof(vdev->name));
+ strscpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name));
vdev->release = video_device_release_empty;
vdev->fops = &isc_fops;
vdev->ioctl_ops = &isc_ioctl_ops;
--
2.25.1
The ispck is not used for sama7g5 variant of the ISC.
Calls to ispck have to be removed also from module insert/removal.
Fixes: d7f26849ed7c ("media: atmel: fix the ispck initialization")
Signed-off-by: Eugen Hristev <[email protected]>
Reviewed-by: Jacopo Mondi <[email protected]>
---
drivers/media/platform/atmel/atmel-sama7g5-isc.c | 6 ------
1 file changed, 6 deletions(-)
diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
index 5d1c76f680f3..2b1082295c13 100644
--- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
@@ -556,7 +556,6 @@ static int microchip_xisc_remove(struct platform_device *pdev)
v4l2_device_unregister(&isc->v4l2_dev);
- clk_disable_unprepare(isc->ispck);
clk_disable_unprepare(isc->hclock);
isc_clk_cleanup(isc);
@@ -568,7 +567,6 @@ static int __maybe_unused xisc_runtime_suspend(struct device *dev)
{
struct isc_device *isc = dev_get_drvdata(dev);
- clk_disable_unprepare(isc->ispck);
clk_disable_unprepare(isc->hclock);
return 0;
@@ -583,10 +581,6 @@ static int __maybe_unused xisc_runtime_resume(struct device *dev)
if (ret)
return ret;
- ret = clk_prepare_enable(isc->ispck);
- if (ret)
- clk_disable_unprepare(isc->hclock);
-
return ret;
}
--
2.25.1
VIDIOC_ENUM_FRAMEINTERVALS is not recommended for a top video driver.
The frame rate is defined by the sensor subdevice, thus it can be queried
directly by anyone interested in the frame intervals.
Signed-off-by: Eugen Hristev <[email protected]>
Reviewed-by: Jacopo Mondi <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 37 -------------------
1 file changed, 37 deletions(-)
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index e6c9071c04f0..3389671fbfb9 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1107,42 +1107,6 @@ static int isc_enum_framesizes(struct file *file, void *fh,
return 0;
}
-static int isc_enum_frameintervals(struct file *file, void *fh,
- struct v4l2_frmivalenum *fival)
-{
- struct isc_device *isc = video_drvdata(file);
- struct v4l2_subdev_frame_interval_enum fie = {
- .code = isc->config.sd_format->mbus_code,
- .index = fival->index,
- .width = fival->width,
- .height = fival->height,
- .which = V4L2_SUBDEV_FORMAT_ACTIVE,
- };
- int ret = -EINVAL;
- unsigned int i;
-
- for (i = 0; i < isc->num_user_formats; i++)
- if (isc->user_formats[i]->fourcc == fival->pixel_format)
- ret = 0;
-
- for (i = 0; i < isc->controller_formats_size; i++)
- if (isc->controller_formats[i].fourcc == fival->pixel_format)
- ret = 0;
-
- if (ret)
- return ret;
-
- ret = v4l2_subdev_call(isc->current_subdev->sd, pad,
- enum_frame_interval, NULL, &fie);
- if (ret)
- return ret;
-
- fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
- fival->discrete = fie.interval;
-
- return 0;
-}
-
static const struct v4l2_ioctl_ops isc_ioctl_ops = {
.vidioc_querycap = isc_querycap,
.vidioc_enum_fmt_vid_cap = isc_enum_fmt_vid_cap,
@@ -1167,7 +1131,6 @@ static const struct v4l2_ioctl_ops isc_ioctl_ops = {
.vidioc_g_parm = isc_g_parm,
.vidioc_s_parm = isc_s_parm,
.vidioc_enum_framesizes = isc_enum_framesizes,
- .vidioc_enum_frameintervals = isc_enum_frameintervals,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
--
2.25.1
During experiments with libcamera, it looks like vb2_is_streaming returns
true before our start streaming is called.
Order of operations is streamon -> queue -> start_streaming
ISC would have started the DMA immediately when a buffer is being added
to the vbqueue if the queue is streaming.
It is more safe to start the DMA after the start streaming of the driver is
called.
Thus, even if vb2queue is streaming, add the buffer to the dma queue of the
driver instead of actually starting the DMA process, if the start streaming
has not been called yet.
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 26a6090f056c..e6c9071c04f0 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -441,12 +441,14 @@ static void isc_buffer_queue(struct vb2_buffer *vb)
unsigned long flags;
spin_lock_irqsave(&isc->dma_queue_lock, flags);
- if (!isc->cur_frm && list_empty(&isc->dma_queue) &&
- vb2_is_streaming(vb->vb2_queue)) {
+
+ if (!isc->cur_frm && list_empty(&isc->dma_queue) && !isc->stop) {
isc->cur_frm = buf;
isc_start_dma(isc);
- } else
+ } else {
list_add_tail(&buf->list, &isc->dma_queue);
+ }
+
spin_unlock_irqrestore(&isc->dma_queue_lock, flags);
}
@@ -1014,7 +1016,7 @@ static int isc_s_fmt_vid_cap(struct file *file, void *priv,
{
struct isc_device *isc = video_drvdata(file);
- if (vb2_is_streaming(&isc->vb2_vidq))
+ if (!isc->stop)
return -EBUSY;
return isc_set_fmt(isc, f);
@@ -1536,7 +1538,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
isc_update_awb_ctrls(isc);
- if (vb2_is_streaming(&isc->vb2_vidq)) {
+ if (!isc->stop) {
/*
* If we are streaming, we can update profile to
* have the new settings in place.
@@ -1552,8 +1554,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
}
/* if we have autowhitebalance on, start histogram procedure */
- if (ctrls->awb == ISC_WB_AUTO &&
- vb2_is_streaming(&isc->vb2_vidq) &&
+ if (ctrls->awb == ISC_WB_AUTO && !isc->stop &&
ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
isc_set_histogram(isc, true);
@@ -1829,6 +1830,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
struct vb2_queue *q = &isc->vb2_vidq;
int ret = 0;
+ isc->stop = true;
+
INIT_WORK(&isc->awb_work, isc_awb_work);
ret = v4l2_device_register_subdev_nodes(&isc->v4l2_dev);
--
2.25.1
The ISC supports a full broad range of frame sizes.
Until now, the subdevice was queried for possible frame sizes and these
were reported to the user space.
However, the ISC should not care about which frame sizes the subdev supports,
as long as this frame size is supported.
Thus, report a continuous range from smallest frame size up to the max
resolution.
Signed-off-by: Eugen Hristev <[email protected]>
Reviewed-by: Jacopo Mondi <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 22 +++++++++----------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 3389671fbfb9..bf638d201b29 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1076,14 +1076,12 @@ static int isc_enum_framesizes(struct file *file, void *fh,
struct v4l2_frmsizeenum *fsize)
{
struct isc_device *isc = video_drvdata(file);
- struct v4l2_subdev_frame_size_enum fse = {
- .code = isc->config.sd_format->mbus_code,
- .index = fsize->index,
- .which = V4L2_SUBDEV_FORMAT_ACTIVE,
- };
int ret = -EINVAL;
int i;
+ if (fsize->index)
+ return -EINVAL;
+
for (i = 0; i < isc->num_user_formats; i++)
if (isc->user_formats[i]->fourcc == fsize->pixel_format)
ret = 0;
@@ -1095,14 +1093,14 @@ static int isc_enum_framesizes(struct file *file, void *fh,
if (ret)
return ret;
- ret = v4l2_subdev_call(isc->current_subdev->sd, pad, enum_frame_size,
- NULL, &fse);
- if (ret)
- return ret;
+ fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
- fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
- fsize->discrete.width = fse.max_width;
- fsize->discrete.height = fse.max_height;
+ fsize->stepwise.min_width = 16;
+ fsize->stepwise.max_width = isc->max_width;
+ fsize->stepwise.min_height = 16;
+ fsize->stepwise.max_height = isc->max_height;
+ fsize->stepwise.step_width = 1;
+ fsize->stepwise.step_height = 1;
return 0;
}
--
2.25.1
If enumfmt is called with an mbus_code, the enumfmt handler should only
return the formats that are supported for this mbus_code.
To make it more easy to understand the formats, changed the report order
to report first the native formats, and after that the formats that the ISC
can convert to.
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 51 ++++++++++++++++---
1 file changed, 43 insertions(+), 8 deletions(-)
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index bf638d201b29..2cb8446ff90c 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -498,21 +498,56 @@ static int isc_enum_fmt_vid_cap(struct file *file, void *priv,
u32 index = f->index;
u32 i, supported_index;
- if (index < isc->controller_formats_size) {
- f->pixelformat = isc->controller_formats[index].fourcc;
- return 0;
+ supported_index = 0;
+
+ for (i = 0; i < isc->formats_list_size; i++) {
+ if (!isc->formats_list[i].sd_support)
+ continue;
+ /*
+ * If specific mbus_code is requested, provide only
+ * supported formats with this mbus code
+ */
+ if (f->mbus_code && f->mbus_code !=
+ isc->formats_list[i].mbus_code)
+ continue;
+ if (supported_index == index) {
+ f->pixelformat = isc->formats_list[i].fourcc;
+ return 0;
+ }
+ supported_index++;
}
- index -= isc->controller_formats_size;
+ /*
+ * If the sensor does not support this mbus_code whatsoever,
+ * there is no reason to advertise any of our output formats
+ */
+ if (supported_index == 0)
+ return -EINVAL;
+
+ /*
+ * If the sensor uses a format that is not raw, then we cannot
+ * convert it to any of the formats that we usually can with a
+ * RAW sensor. Thus, do not advertise them.
+ */
+ if (!isc->config.sd_format ||
+ !ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
+ return -EINVAL;
+ /*
+ * Iterate again through the formats that we can convert to.
+ * However, to avoid duplicates, skip the formats that
+ * the sensor already supports directly
+ */
+ index -= supported_index;
supported_index = 0;
- for (i = 0; i < isc->formats_list_size; i++) {
- if (!ISC_IS_FORMAT_RAW(isc->formats_list[i].mbus_code) ||
- !isc->formats_list[i].sd_support)
+ for (i = 0; i < isc->controller_formats_size; i++) {
+ /* if this format is already supported by sensor, skip it */
+ if (find_format_by_fourcc(isc, isc->controller_formats[i].fourcc))
continue;
if (supported_index == index) {
- f->pixelformat = isc->formats_list[i].fourcc;
+ f->pixelformat =
+ isc->controller_formats[i].fourcc;
return 0;
}
supported_index++;
--
2.25.1
The bytesperline field of the pixfmt should be only for the first plane
in case of planar formats like YUV420 or YUV422.
The bytesperline is used by the driver to compute the framesize.
We have to report a different bpp (bytes per pixel) to v4l2 in bytesperline
than the actual bpp.
For example for YUV420, the real bpp is 12, but the first plane has only
8 bpp. Thus we report a bytesperline 8*width instead of 12*width.
However, for real framezise we have to compute 12*width*height.
Hence added a new variable to hold this information and to correctly
compute the frame size.
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 19 +++++++++++++++++--
drivers/media/platform/atmel/atmel-isc.h | 4 ++++
2 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 2cb8446ff90c..d0542b97a391 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -654,6 +654,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 8;
+ isc->try_config.bpp_v4l2 = 8;
break;
case V4L2_PIX_FMT_SBGGR10:
case V4L2_PIX_FMT_SGBRG10:
@@ -663,6 +664,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_SBGGR12:
case V4L2_PIX_FMT_SGBRG12:
@@ -672,24 +674,28 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_RGB565:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_RGB565;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_ARGB444:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB444;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_ARGB555:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB555;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_ABGR32:
case V4L2_PIX_FMT_XBGR32:
@@ -697,42 +703,49 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 32;
+ isc->try_config.bpp_v4l2 = 32;
break;
case V4L2_PIX_FMT_YUV420:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC420P;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR;
isc->try_config.bpp = 12;
+ isc->try_config.bpp_v4l2 = 8; /* only first plane */
break;
case V4L2_PIX_FMT_YUV422P:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC422P;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 8; /* only first plane */
break;
case V4L2_PIX_FMT_YUYV:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_YUYV;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_UYVY:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_UYVY;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_VYUY:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_VYUY;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
case V4L2_PIX_FMT_GREY:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY8;
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 8;
+ isc->try_config.bpp_v4l2 = 8;
break;
case V4L2_PIX_FMT_Y16:
isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY10 | ISC_RLP_CFG_LSH;
@@ -742,6 +755,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
isc->try_config.bpp = 16;
+ isc->try_config.bpp_v4l2 = 16;
break;
default:
return -EINVAL;
@@ -990,8 +1004,9 @@ static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f,
pixfmt->height = isc->max_height;
pixfmt->field = V4L2_FIELD_NONE;
- pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp) >> 3;
- pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height;
+ pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3;
+ pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) *
+ pixfmt->height;
if (code)
*code = mbus_code;
diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
index 32448ccfc636..07fa6dbf8460 100644
--- a/drivers/media/platform/atmel/atmel-isc.h
+++ b/drivers/media/platform/atmel/atmel-isc.h
@@ -102,6 +102,9 @@ struct isc_format {
configuration.
* @fourcc: Fourcc code for this format.
* @bpp: Bytes per pixel in the current format.
+ * @bpp_v4l2: Bytes per pixel in the current format, for v4l2.
+ This differs from 'bpp' in the sense that in planar
+ formats, it refers only to the first plane.
* @rlp_cfg_mode: Configuration of the RLP (rounding, limiting packaging)
* @dcfg_imode: Configuration of the input of the DMA module
* @dctrl_dview: Configuration of the output of the DMA module
@@ -112,6 +115,7 @@ struct fmt_config {
u32 fourcc;
u8 bpp;
+ u8 bpp_v4l2;
u32 rlp_cfg_mode;
u32 dcfg_imode;
--
2.25.1
Add new file atmel-isc-mc.c to MAINTAINERS entry.
Signed-off-by: Eugen Hristev <[email protected]>
---
MAINTAINERS | 1 +
1 file changed, 1 insertion(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index bb7b48e3605c..ed9ec0a426df 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12456,6 +12456,7 @@ F: Documentation/devicetree/bindings/media/atmel,isc.yaml
F: Documentation/devicetree/bindings/media/microchip,xisc.yaml
F: drivers/media/platform/atmel/atmel-isc-base.c
F: drivers/media/platform/atmel/atmel-isc-clk.c
+F: drivers/media/platform/atmel/atmel-isc-mc.c
F: drivers/media/platform/atmel/atmel-isc-regs.h
F: drivers/media/platform/atmel/atmel-isc.h
F: drivers/media/platform/atmel/atmel-sama5d2-isc.c
--
2.25.1
Implement the support for media-controller.
This means that the capabilities of the driver have changed and now
it also advertises the IO_MC .
The driver will register it's media device, and add the video entity to this
media device. The subdevices are registered to the same media device.
The ISC will have a base entity which is auto-detected as atmel_isc_base.
It will also register a subdevice that allows cropping of the incoming frame
to the maximum frame size supported by the ISC.
The ISC will create a link between the subdevice that is asynchronously
registered and the atmel_isc_scaler entity.
Then, the atmel_isc_scaler and atmel_isc_base are connected through another
link.
Signed-off-by: Eugen Hristev <[email protected]>
---
Changes in v2:
- implement try formats
drivers/media/platform/atmel/Makefile | 2 +-
drivers/media/platform/atmel/atmel-isc-base.c | 27 +-
drivers/media/platform/atmel/atmel-isc-mc.c | 287 ++++++++++++++++++
drivers/media/platform/atmel/atmel-isc.h | 36 +++
.../media/platform/atmel/atmel-sama5d2-isc.c | 14 +-
.../media/platform/atmel/atmel-sama7g5-isc.c | 12 +-
6 files changed, 370 insertions(+), 8 deletions(-)
create mode 100644 drivers/media/platform/atmel/atmel-isc-mc.c
diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
index 794e8f739287..aa7a18b428f6 100644
--- a/drivers/media/platform/atmel/Makefile
+++ b/drivers/media/platform/atmel/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
atmel-isc-objs = atmel-sama5d2-isc.o
atmel-xisc-objs = atmel-sama7g5-isc.o
-atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o
+atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o atmel-isc-mc.o
obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index d0542b97a391..b0c3ed21f372 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1726,6 +1726,7 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
struct isc_device, v4l2_dev);
struct isc_subdev_entity *subdev_entity =
container_of(notifier, struct isc_subdev_entity, notifier);
+ int pad;
if (video_is_registered(&isc->video_dev)) {
v4l2_err(&isc->v4l2_dev, "only supports one sub-device.\n");
@@ -1734,6 +1735,16 @@ static int isc_async_bound(struct v4l2_async_notifier *notifier,
subdev_entity->sd = subdev;
+ pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
+ MEDIA_PAD_FL_SOURCE);
+ if (pad < 0) {
+ v4l2_err(&isc->v4l2_dev, "failed to find pad for %s\n",
+ subdev->name);
+ return pad;
+ }
+
+ isc->remote_pad = pad;
+
return 0;
}
@@ -1748,8 +1759,8 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier,
v4l2_ctrl_handler_free(&isc->ctrls.handler);
}
-static struct isc_format *find_format_by_code(struct isc_device *isc,
- unsigned int code, int *index)
+struct isc_format *isc_find_format_by_code(struct isc_device *isc,
+ unsigned int code, int *index)
{
struct isc_format *fmt = &isc->formats_list[0];
unsigned int i;
@@ -1765,6 +1776,7 @@ static struct isc_format *find_format_by_code(struct isc_device *isc,
return NULL;
}
+EXPORT_SYMBOL_GPL(isc_find_format_by_code);
static int isc_formats_init(struct isc_device *isc)
{
@@ -1781,7 +1793,7 @@ static int isc_formats_init(struct isc_device *isc)
NULL, &mbus_code)) {
mbus_code.index++;
- fmt = find_format_by_code(isc, mbus_code.code, &i);
+ fmt = isc_find_format_by_code(isc, mbus_code.code, &i);
if (!fmt) {
v4l2_warn(&isc->v4l2_dev, "Mbus code %x not supported\n",
mbus_code.code);
@@ -1909,7 +1921,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
vdev->queue = q;
vdev->lock = &isc->lock;
vdev->ctrl_handler = &isc->ctrls.handler;
- vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_IO_MC;
video_set_drvdata(vdev, isc);
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
@@ -1919,8 +1932,14 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
goto isc_async_complete_err;
}
+ ret = isc_mc_register(isc);
+ if (ret < 0)
+ goto isc_async_complete_unregister_device;
return 0;
+isc_async_complete_unregister_device:
+ video_unregister_device(vdev);
+
isc_async_complete_err:
mutex_destroy(&isc->lock);
return ret;
diff --git a/drivers/media/platform/atmel/atmel-isc-mc.c b/drivers/media/platform/atmel/atmel-isc-mc.c
new file mode 100644
index 000000000000..2aea81d77b2f
--- /dev/null
+++ b/drivers/media/platform/atmel/atmel-isc-mc.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Microchip Image Sensor Controller (ISC) Media Controller support
+ *
+ * Copyright (C) 2021 Microchip Technology, Inc.
+ *
+ * Author: Eugen Hristev <[email protected]>
+ *
+ */
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+#include "atmel-isc-regs.h"
+#include "atmel-isc.h"
+
+static const struct media_device_ops isc_media_ops = {
+};
+
+static int isc_scaler_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
+ struct v4l2_mbus_framefmt *v4l2_try_fmt;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+ v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
+ format->pad);
+ format->format = *v4l2_try_fmt;
+
+ return 0;
+ }
+
+ format->format = isc->scaler_format;
+
+ return 0;
+}
+
+static int isc_scaler_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *req_fmt)
+{
+ struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
+ struct v4l2_mbus_framefmt *v4l2_try_fmt;
+ struct isc_format *fmt;
+ unsigned int i;
+
+ if (req_fmt->pad == ISC_SCALER_PAD_SOURCE)
+ v4l_bound_align_image
+ (&req_fmt->format.width, 16, isc->max_width, 0,
+ &req_fmt->format.height, 16, isc->max_height, 0, 0);
+ else
+ v4l_bound_align_image
+ (&req_fmt->format.width, 16, 10000, 0,
+ &req_fmt->format.height, 16, 10000, 0, 0);
+
+ req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
+ req_fmt->format.field = V4L2_FIELD_NONE;
+ req_fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ req_fmt->format.quantization = V4L2_QUANTIZATION_DEFAULT;
+ req_fmt->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ fmt = isc_find_format_by_code(isc, req_fmt->format.code, &i);
+
+ if (!fmt)
+ fmt = &isc->formats_list[0];
+
+ req_fmt->format.code = fmt->mbus_code;
+
+ if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ v4l2_try_fmt = v4l2_subdev_get_try_format(sd, sd_state,
+ req_fmt->pad);
+ *v4l2_try_fmt = req_fmt->format;
+ /* Trying on the pad sink makes the source sink change too */
+ if (req_fmt->pad == ISC_SCALER_PAD_SINK) {
+ v4l2_try_fmt =
+ v4l2_subdev_get_try_format(sd, sd_state,
+ ISC_SCALER_PAD_SOURCE);
+ *v4l2_try_fmt = req_fmt->format;
+
+ v4l_bound_align_image(&v4l2_try_fmt->width,
+ 16, isc->max_width, 0,
+ &v4l2_try_fmt->height,
+ 16, isc->max_height, 0, 0);
+ }
+ /* if we are just trying, we are done */
+ return 0;
+ }
+
+ isc->scaler_format = req_fmt->format;
+
+ return 0;
+}
+
+static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
+ int supported_index = 0;
+ int i;
+
+ for (i = 0; i < isc->formats_list_size; i++) {
+ if (!isc->formats_list[i].sd_support)
+ continue;
+ if (supported_index == code->index) {
+ code->code = isc->formats_list[i].mbus_code;
+ return 0;
+ }
+ supported_index++;
+ }
+
+ return -EINVAL;
+}
+
+static int isc_scaler_g_sel(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
+
+ if (sel->pad == ISC_SCALER_PAD_SOURCE)
+ return -EINVAL;
+
+ if (sel->target != V4L2_SEL_TGT_CROP_BOUNDS &&
+ sel->target != V4L2_SEL_TGT_CROP)
+ return -EINVAL;
+
+ sel->r.height = isc->max_height;
+ sel->r.width = isc->max_width;
+
+ sel->r.left = 0;
+ sel->r.top = 0;
+
+ return 0;
+}
+
+static int isc_scaler_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state)
+{
+ struct v4l2_mbus_framefmt *v4l2_try_fmt =
+ v4l2_subdev_get_try_format(sd, sd_state, 0);
+ struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd);
+
+ *v4l2_try_fmt = isc->scaler_format;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops isc_scaler_pad_ops = {
+ .enum_mbus_code = isc_scaler_enum_mbus_code,
+ .set_fmt = isc_scaler_set_fmt,
+ .get_fmt = isc_scaler_get_fmt,
+ .get_selection = isc_scaler_g_sel,
+ .init_cfg = isc_scaler_init_cfg,
+};
+
+static const struct v4l2_subdev_ops xisc_scaler_subdev_ops = {
+ .pad = &isc_scaler_pad_ops,
+};
+
+static int isc_init_own_sd(struct isc_device *isc)
+{
+ int ret;
+
+ v4l2_subdev_init(&isc->scaler_sd, &xisc_scaler_subdev_ops);
+
+ isc->scaler_sd.owner = THIS_MODULE;
+ isc->scaler_sd.dev = isc->dev;
+ snprintf(isc->scaler_sd.name, sizeof(isc->scaler_sd.name),
+ "atmel_isc_scaler");
+
+ isc->scaler_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ isc->scaler_sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+ isc->scaler_pads[ISC_SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ isc->scaler_pads[ISC_SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ isc->scaler_format.height = isc->max_height;
+ isc->scaler_format.width = isc->max_width;
+ isc->scaler_format.code = isc->formats_list[0].mbus_code;
+ isc->scaler_format.colorspace = V4L2_COLORSPACE_SRGB;
+ isc->scaler_format.field = V4L2_FIELD_NONE;
+ isc->scaler_format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ isc->scaler_format.quantization = V4L2_QUANTIZATION_DEFAULT;
+ isc->scaler_format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ ret = media_entity_pads_init(&isc->scaler_sd.entity,
+ ISC_SCALER_PADS_NUM,
+ isc->scaler_pads);
+ if (ret < 0) {
+ dev_err(isc->dev, "scaler sd media entity init failed\n");
+ return ret;
+ }
+ ret = v4l2_device_register_subdev(&isc->v4l2_dev, &isc->scaler_sd);
+ if (ret < 0) {
+ dev_err(isc->dev, "scaler sd failed to register subdev\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+int isc_mc_init(struct isc_device *isc, u32 ver)
+{
+ const struct of_device_id *match;
+ int ret;
+
+ isc->video_dev.entity.function = MEDIA_ENT_F_IO_V4L;
+ isc->video_dev.entity.flags = MEDIA_ENT_FL_DEFAULT;
+ isc->pads[ISC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+
+ ret = media_entity_pads_init(&isc->video_dev.entity, ISC_PADS_NUM,
+ isc->pads);
+ if (ret < 0) {
+ dev_err(isc->dev, "media entity init failed\n");
+ return ret;
+ }
+
+ isc->mdev.dev = isc->dev;
+ isc->mdev.ops = &isc_media_ops;
+
+ match = of_match_node(isc->dev->driver->of_match_table,
+ isc->dev->of_node);
+
+ strscpy(isc->mdev.driver_name, KBUILD_MODNAME,
+ sizeof(isc->mdev.driver_name));
+ strscpy(isc->mdev.model, match->compatible, sizeof(isc->mdev.model));
+ snprintf(isc->mdev.bus_info, sizeof(isc->mdev.bus_info), "platform:%s",
+ isc->v4l2_dev.name);
+ isc->mdev.hw_revision = ver;
+
+ media_device_init(&isc->mdev);
+
+ isc->v4l2_dev.mdev = &isc->mdev;
+
+ return isc_init_own_sd(isc);
+}
+EXPORT_SYMBOL_GPL(isc_mc_init);
+
+int isc_mc_register(struct isc_device *isc)
+{
+ int ret;
+
+ ret = media_create_pad_link(&isc->current_subdev->sd->entity,
+ isc->remote_pad, &isc->scaler_sd.entity,
+ ISC_SCALER_PAD_SINK,
+ MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+
+ if (ret < 0) {
+ v4l2_err(&isc->v4l2_dev,
+ "Failed to create pad link: %s to %s\n",
+ isc->current_subdev->sd->entity.name,
+ isc->scaler_sd.entity.name);
+ return ret;
+ }
+
+ dev_dbg(isc->dev, "link with %s pad: %d\n",
+ isc->current_subdev->sd->name, isc->remote_pad);
+
+ ret = media_create_pad_link(&isc->scaler_sd.entity,
+ ISC_SCALER_PAD_SOURCE,
+ &isc->video_dev.entity, ISC_PAD_SINK,
+ MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+
+ if (ret < 0) {
+ v4l2_err(&isc->v4l2_dev,
+ "Failed to create pad link: %s to %s\n",
+ isc->scaler_sd.entity.name,
+ isc->video_dev.entity.name);
+ return ret;
+ }
+
+ dev_dbg(isc->dev, "link with %s pad: %d\n", isc->scaler_sd.name,
+ ISC_SCALER_PAD_SOURCE);
+
+ return media_device_register(&isc->mdev);
+}
+EXPORT_SYMBOL_GPL(isc_mc_register);
+
+void isc_mc_cleanup(struct isc_device *isc)
+{
+ media_entity_cleanup(&isc->video_dev.entity);
+}
+EXPORT_SYMBOL_GPL(isc_mc_cleanup);
diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
index 07fa6dbf8460..0b6370d7775f 100644
--- a/drivers/media/platform/atmel/atmel-isc.h
+++ b/drivers/media/platform/atmel/atmel-isc.h
@@ -183,6 +183,17 @@ struct isc_reg_offsets {
u32 his_entry;
};
+enum isc_mc_pads {
+ ISC_PAD_SINK = 0,
+ ISC_PADS_NUM = 1,
+};
+
+enum isc_scaler_pads {
+ ISC_SCALER_PAD_SINK = 0,
+ ISC_SCALER_PAD_SOURCE = 1,
+ ISC_SCALER_PADS_NUM = 2,
+};
+
/*
* struct isc_device - ISC device driver data/config struct
* @regmap: Register map
@@ -258,6 +269,12 @@ struct isc_reg_offsets {
* be used as an input to the controller
* @controller_formats_size: size of controller_formats array
* @formats_list_size: size of formats_list array
+ * @pads: media controller pads for isc video entity
+ * @mdev: media device that is registered by the isc
+ * @remote_pad: remote pad on the connected subdevice
+ * @scaler_sd: subdevice for the scaler that isc registers
+ * @scaler_pads: media controller pads for the scaler subdevice
+ * @scaler_format: current format for the scaler subdevice
*/
struct isc_device {
struct regmap *regmap;
@@ -346,6 +363,19 @@ struct isc_device {
struct isc_format *formats_list;
u32 controller_formats_size;
u32 formats_list_size;
+
+ struct {
+ struct media_pad pads[ISC_PADS_NUM];
+ struct media_device mdev;
+
+ u32 remote_pad;
+ };
+
+ struct {
+ struct v4l2_subdev scaler_sd;
+ struct media_pad scaler_pads[ISC_SCALER_PADS_NUM];
+ struct v4l2_mbus_framefmt scaler_format;
+ };
};
extern const struct regmap_config isc_regmap_config;
@@ -357,4 +387,10 @@ int isc_clk_init(struct isc_device *isc);
void isc_subdev_cleanup(struct isc_device *isc);
void isc_clk_cleanup(struct isc_device *isc);
+int isc_mc_init(struct isc_device *isc, u32 ver);
+int isc_mc_register(struct isc_device *isc);
+void isc_mc_cleanup(struct isc_device *isc);
+
+struct isc_format *isc_find_format_by_code(struct isc_device *isc,
+ unsigned int code, int *index);
#endif
diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
index 1b2063cce0f7..a3e4c32a66c4 100644
--- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
@@ -529,6 +529,12 @@ static int atmel_isc_probe(struct platform_device *pdev)
break;
}
+ regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
+
+ ret = isc_mc_init(isc, ver);
+ if (ret < 0)
+ goto isc_probe_mc_init_err;
+
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_request_idle(dev);
@@ -538,7 +544,7 @@ static int atmel_isc_probe(struct platform_device *pdev)
ret = clk_prepare_enable(isc->ispck);
if (ret) {
dev_err(dev, "failed to enable ispck: %d\n", ret);
- goto cleanup_subdev;
+ goto isc_probe_mc_init_err;
}
/* ispck should be greater or equal to hclock */
@@ -548,7 +554,6 @@ static int atmel_isc_probe(struct platform_device *pdev)
goto unprepare_clk;
}
- regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
dev_info(dev, "Microchip ISC version %x\n", ver);
return 0;
@@ -556,6 +561,9 @@ static int atmel_isc_probe(struct platform_device *pdev)
unprepare_clk:
clk_disable_unprepare(isc->ispck);
+isc_probe_mc_init_err:
+ isc_mc_cleanup(isc);
+
cleanup_subdev:
isc_subdev_cleanup(isc);
@@ -576,6 +584,8 @@ static int atmel_isc_remove(struct platform_device *pdev)
pm_runtime_disable(&pdev->dev);
+ isc_mc_cleanup(isc);
+
isc_subdev_cleanup(isc);
v4l2_device_unregister(&isc->v4l2_dev);
diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
index 2b1082295c13..d2ac80b1ccc3 100644
--- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
@@ -523,15 +523,23 @@ static int microchip_xisc_probe(struct platform_device *pdev)
break;
}
+ regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
+
+ ret = isc_mc_init(isc, ver);
+ if (ret < 0)
+ goto isc_probe_mc_init_err;
+
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_request_idle(dev);
- regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver);
dev_info(dev, "Microchip XISC version %x\n", ver);
return 0;
+isc_probe_mc_init_err:
+ isc_mc_cleanup(isc);
+
cleanup_subdev:
isc_subdev_cleanup(isc);
@@ -552,6 +560,8 @@ static int microchip_xisc_remove(struct platform_device *pdev)
pm_runtime_disable(&pdev->dev);
+ isc_mc_cleanup(isc);
+
isc_subdev_cleanup(isc);
v4l2_device_unregister(&isc->v4l2_dev);
--
2.25.1
Add node for the XISC (eXtended Image Sensor Controller) and CSI2DC
(csi2 demux controller).
These nodes represent the top level of the video capture hardware pipeline
and are directly connected in hardware.
Signed-off-by: Eugen Hristev <[email protected]>
---
arch/arm/boot/dts/sama7g5.dtsi | 49 ++++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/arch/arm/boot/dts/sama7g5.dtsi b/arch/arm/boot/dts/sama7g5.dtsi
index 7039311bf678..a2323f55de0c 100644
--- a/arch/arm/boot/dts/sama7g5.dtsi
+++ b/arch/arm/boot/dts/sama7g5.dtsi
@@ -236,6 +236,55 @@ sdmmc2: mmc@e120c000 {
status = "disabled";
};
+ csi2dc: csi2dc@e1404000 {
+ compatible = "microchip,sama7g5-csi2dc";
+ reg = <0xe1404000 0x500>;
+ clocks = <&pmc PMC_TYPE_PERIPHERAL 34>, <&xisc>;
+ clock-names = "pclk", "scck";
+ assigned-clocks = <&xisc>;
+ assigned-clock-rates = <266000000>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ csi2dc_in: endpoint {
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ csi2dc_out: endpoint {
+ bus-width = <12>;
+ hsync-active = <1>;
+ vsync-active = <1>;
+ remote-endpoint = <&xisc_in>;
+ };
+ };
+ };
+ };
+
+ xisc: xisc@e1408000 {
+ compatible = "microchip,sama7g5-isc";
+ reg = <0xe1408000 0x2000>;
+ interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&pmc PMC_TYPE_PERIPHERAL 56>;
+ clock-names = "hclock";
+ #clock-cells = <0>;
+ clock-output-names = "isc-mck";
+
+ port {
+ xisc_in: endpoint {
+ bus-type = <5>; /* Parallel */
+ bus-width = <12>;
+ hsync-active = <1>;
+ vsync-active = <1>;
+ remote-endpoint = <&csi2dc_out>;
+ };
+ };
+ };
+
pwm: pwm@e1604000 {
compatible = "microchip,sama7g5-pwm", "atmel,sama5d2-pwm";
reg = <0xe1604000 0x4000>;
--
2.25.1
Enable XISC and CSI2DC drivers.
Signed-off-by: Eugen Hristev <[email protected]>
---
arch/arm/configs/sama7_defconfig | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/arm/configs/sama7_defconfig b/arch/arm/configs/sama7_defconfig
index 938aae4bd80b..15978f2ab4ea 100644
--- a/arch/arm/configs/sama7_defconfig
+++ b/arch/arm/configs/sama7_defconfig
@@ -126,6 +126,8 @@ CONFIG_MEDIA_SUPPORT_FILTER=y
CONFIG_MEDIA_CAMERA_SUPPORT=y
CONFIG_MEDIA_PLATFORM_SUPPORT=y
CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_ATMEL_XISC=y
+CONFIG_VIDEO_MICROCHIP_CSI2DC=y
CONFIG_VIDEO_IMX219=m
CONFIG_VIDEO_IMX274=m
CONFIG_VIDEO_OV5647=m
--
2.25.1
Add drivers for the atmel video capture pipeline: atmel isc, xisc and
microchip csi2dc.
Signed-off-by: Eugen Hristev <[email protected]>
---
arch/arm/configs/multi_v7_defconfig | 3 +++
1 file changed, 3 insertions(+)
diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index c951aeed2138..92b7749a6df8 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -638,7 +638,10 @@ CONFIG_VIDEO_S5P_MIPI_CSIS=m
CONFIG_VIDEO_EXYNOS_FIMC_LITE=m
CONFIG_VIDEO_EXYNOS4_FIMC_IS=m
CONFIG_VIDEO_RCAR_VIN=m
+CONFIG_VIDEO_ATMEL_ISC=m
+CONFIG_VIDEO_ATMEL_XISC=m
CONFIG_VIDEO_ATMEL_ISI=m
+CONFIG_VIDEO_MICROCHIP_CSI2DC=m
CONFIG_V4L_MEM2MEM_DRIVERS=y
CONFIG_VIDEO_SAMSUNG_S5P_JPEG=m
CONFIG_VIDEO_SAMSUNG_S5P_MFC=m
--
2.25.1
While this does not happen in production, this check should be done
versus the mask, as checking with the YCYC value may not include
some bits that may be set.
Is it correct and safe to check the whole mask.
Fixes: 123aaf816b95 ("media: atmel: atmel-sama5d2-isc: fix YUYV format")
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-sama5d2-isc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
index a3e4c32a66c4..a8d4ba60d3ac 100644
--- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
@@ -267,7 +267,7 @@ static void isc_sama5d2_config_rlp(struct isc_device *isc)
* Thus, if the YCYC mode is selected, replace it with the
* sama5d2-compliant mode which is YYCC .
*/
- if ((rlp_mode & ISC_RLP_CFG_MODE_YCYC) == ISC_RLP_CFG_MODE_YCYC) {
+ if ((rlp_mode & ISC_RLP_CFG_MODE_MASK) == ISC_RLP_CFG_MODE_YCYC) {
rlp_mode &= ~ISC_RLP_CFG_MODE_MASK;
rlp_mode |= ISC_RLP_CFG_MODE_YYCC;
}
--
2.25.1
The AWB workqueue runs in a kernel thread and needs to be synchronized
w.r.t. the streaming status.
It is possible that streaming is stopped while the AWB workq is running.
In this case it is likely that the check for isc->stop is done at one point
in time, but the AWB computations are done later, including a call to
isc_update_profile, which requires streaming to be started.
Thus , isc_update_profile will fail if during this operation sequence the
streaming was stopped.
To solve this issue, a mutex is added, that will serialize the awb work and
streaming stopping, with the mention that either streaming is stopped
completely including termination of the last frame is done, and after that
the AWB work can check stream status and stop; either first AWB work is
completed and after that the streaming can stop correctly.
The awb spin lock cannot be used since this spinlock is taken in the same
context and using it in the stop streaming will result in a recursion BUG.
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 31 ++++++++++++++++---
drivers/media/platform/atmel/atmel-isc.h | 1 +
2 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index b0c3ed21f372..53cac1aac0fd 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -401,6 +401,7 @@ static void isc_stop_streaming(struct vb2_queue *vq)
struct isc_buffer *buf;
int ret;
+ mutex_lock(&isc->awb_mutex);
v4l2_ctrl_activate(isc->do_wb_ctrl, false);
isc->stop = true;
@@ -410,6 +411,8 @@ static void isc_stop_streaming(struct vb2_queue *vq)
v4l2_err(&isc->v4l2_dev,
"Timeout waiting for end of the capture\n");
+ mutex_unlock(&isc->awb_mutex);
+
/* Disable DMA interrupt */
regmap_write(isc->regmap, ISC_INTDIS, ISC_INT_DDONE);
@@ -1416,10 +1419,6 @@ static void isc_awb_work(struct work_struct *w)
u32 min, max;
int ret;
- /* streaming is not active anymore */
- if (isc->stop)
- return;
-
if (ctrls->hist_stat != HIST_ENABLED)
return;
@@ -1470,7 +1469,24 @@ static void isc_awb_work(struct work_struct *w)
}
regmap_write(regmap, ISC_HIS_CFG + isc->offsets.his,
hist_id | baysel | ISC_HIS_CFG_RAR);
+
+ /*
+ * We have to make sure the streaming has not stopped meanwhile.
+ * ISC requires a frame to clock the internal profile update.
+ * To avoid issues, lock the sequence with a mutex
+ */
+ mutex_lock(&isc->awb_mutex);
+
+ /* streaming is not active anymore */
+ if (isc->stop) {
+ mutex_unlock(&isc->awb_mutex);
+ return;
+ };
+
isc_update_profile(isc);
+
+ mutex_unlock(&isc->awb_mutex);
+
/* if awb has been disabled, we don't need to start another histogram */
if (ctrls->awb)
regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ);
@@ -1549,6 +1565,8 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
isc_update_awb_ctrls(isc);
+ mutex_lock(&isc->awb_mutex);
+
if (!isc->stop) {
/*
* If we are streaming, we can update profile to
@@ -1563,6 +1581,7 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
*/
v4l2_ctrl_activate(isc->do_wb_ctrl, false);
}
+ mutex_unlock(&isc->awb_mutex);
/* if we have autowhitebalance on, start histogram procedure */
if (ctrls->awb == ISC_WB_AUTO && !isc->stop &&
@@ -1754,6 +1773,7 @@ static void isc_async_unbind(struct v4l2_async_notifier *notifier,
{
struct isc_device *isc = container_of(notifier->v4l2_dev,
struct isc_device, v4l2_dev);
+ mutex_destroy(&isc->awb_mutex);
cancel_work_sync(&isc->awb_work);
video_unregister_device(&isc->video_dev);
v4l2_ctrl_handler_free(&isc->ctrls.handler);
@@ -1866,6 +1886,8 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
isc->current_subdev = container_of(notifier,
struct isc_subdev_entity, notifier);
mutex_init(&isc->lock);
+ mutex_init(&isc->awb_mutex);
+
init_completion(&isc->comp);
/* Initialize videobuf2 queue */
@@ -1941,6 +1963,7 @@ static int isc_async_complete(struct v4l2_async_notifier *notifier)
video_unregister_device(vdev);
isc_async_complete_err:
+ mutex_destroy(&isc->awb_mutex);
mutex_destroy(&isc->lock);
return ret;
}
diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
index 0b6370d7775f..c2cb805faff3 100644
--- a/drivers/media/platform/atmel/atmel-isc.h
+++ b/drivers/media/platform/atmel/atmel-isc.h
@@ -307,6 +307,7 @@ struct isc_device {
struct work_struct awb_work;
struct mutex lock; /* serialize access to file operations */
+ struct mutex awb_mutex; /* serialize access to streaming status from awb work queue */
spinlock_t awb_lock; /* serialize access to DMA buffers from awb work queue */
struct regmap_field *pipeline[ISC_PIPE_LINE_NODE_NUM];
--
2.25.1
Add debug messages that make it easier to debug white balance algorithm.
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 53cac1aac0fd..f1f1019f9d82 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1329,10 +1329,15 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max)
if (!*min)
*min = 1;
+
+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "isc wb: hist_id %u, hist_count %u",
+ ctrls->hist_id, *hist_count);
}
static void isc_wb_update(struct isc_ctrls *ctrls)
{
+ struct isc_device *isc = container_of(ctrls, struct isc_device, ctrls);
u32 *hist_count = &ctrls->hist_count[0];
u32 c, offset[4];
u64 avg = 0;
@@ -1349,6 +1354,9 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
(u64)hist_count[ISC_HIS_CFG_MODE_GB];
avg >>= 1;
+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "isc wb: green components average %llu\n", avg);
+
/* Green histogram is null, nothing to do */
if (!avg)
return;
@@ -1401,9 +1409,15 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
else
gw_gain[c] = 1 << 9;
+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "isc wb: component %d, s_gain %u, gw_gain %u\n",
+ c, s_gain[c], gw_gain[c]);
/* multiply both gains and adjust for decimals */
ctrls->gain[c] = s_gain[c] * gw_gain[c];
ctrls->gain[c] >>= 9;
+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "isc wb: component %d, final gain %u\n",
+ c, ctrls->gain[c]);
}
}
@@ -1423,6 +1437,10 @@ static void isc_awb_work(struct work_struct *w)
return;
isc_hist_count(isc, &min, &max);
+
+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "isc wb mode %d: hist min %u , max %u\n", hist_id, min, max);
+
ctrls->hist_minmax[hist_id][HIST_MIN_INDEX] = min;
ctrls->hist_minmax[hist_id][HIST_MAX_INDEX] = max;
--
2.25.1
White balance computed gains can overflow above the 13 bits hardware
coefficient that can be used, in some specific scenarios like a subexposure
from the sensor when the image is mostly black.
In this case the computed gain has to be clamped to the maximum value
allowed by the hardware.
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index f1f1019f9d82..31c8e3029eee 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -1415,6 +1415,10 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
/* multiply both gains and adjust for decimals */
ctrls->gain[c] = s_gain[c] * gw_gain[c];
ctrls->gain[c] >>= 9;
+
+ /* make sure we are not out of range */
+ ctrls->gain[c] = clamp_val(ctrls->gain[c], 0, GENMASK(12, 0));
+
v4l2_dbg(1, debug, &isc->v4l2_dev,
"isc wb: component %d, final gain %u\n",
c, ctrls->gain[c]);
--
2.25.1
Fix the mbus code for UYVY input format type to the right color rotation.
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-sama7g5-isc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
index d2ac80b1ccc3..38721bd902e2 100644
--- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
@@ -188,7 +188,7 @@ static struct isc_format sama7g5_formats_list[] = {
},
{
.fourcc = V4L2_PIX_FMT_UYVY,
- .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
.pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT,
},
{
--
2.25.1
The ISC can dump the 8 bit and 10 bit raw bayer formats directly to
the memory.
Thus, add them to the supported output format list.
Signed-off-by: Eugen Hristev <[email protected]>
---
.../media/platform/atmel/atmel-sama5d2-isc.c | 24 +++++++++++++++++++
.../media/platform/atmel/atmel-sama7g5-isc.c | 24 +++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
index a8d4ba60d3ac..025c3e8a7e95 100644
--- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
@@ -88,6 +88,30 @@ static const struct isc_format sama5d2_controller_formats[] = {
{
.fourcc = V4L2_PIX_FMT_Y10,
},
+ {
+ .fourcc = V4L2_PIX_FMT_SBGGR8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGBRG8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGRBG8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SRGGB8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SBGGR10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGBRG10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGRBG10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SRGGB10,
+ },
};
/* This is a list of formats that the ISC can receive as *input* */
diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
index 38721bd902e2..9dc75eed0098 100644
--- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
@@ -100,6 +100,30 @@ static const struct isc_format sama7g5_controller_formats[] = {
{
.fourcc = V4L2_PIX_FMT_Y16,
},
+ {
+ .fourcc = V4L2_PIX_FMT_SBGGR8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGBRG8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGRBG8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SRGGB8,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SBGGR10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGBRG10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SGRBG10,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_SRGGB10,
+ },
};
/* This is a list of formats that the ISC can receive as *input* */
--
2.25.1
Compact the list array to be more readable.
No other changes, only cosmetic.
Signed-off-by: Eugen Hristev <[email protected]>
---
.../media/platform/atmel/atmel-sama5d2-isc.c | 51 ++++++----------
.../media/platform/atmel/atmel-sama7g5-isc.c | 60 +++++++------------
2 files changed, 37 insertions(+), 74 deletions(-)
diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
index 025c3e8a7e95..d96ee3373889 100644
--- a/drivers/media/platform/atmel/atmel-sama5d2-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c
@@ -60,56 +60,39 @@
static const struct isc_format sama5d2_controller_formats[] = {
{
.fourcc = V4L2_PIX_FMT_ARGB444,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_ARGB555,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_RGB565,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_ABGR32,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_XBGR32,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUV420,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUYV,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUV422P,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_GREY,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_Y10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SBGGR8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGBRG8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGRBG8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SRGGB8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SBGGR10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGBRG10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGRBG10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SRGGB10,
},
};
diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
index 9dc75eed0098..e07ae188c15f 100644
--- a/drivers/media/platform/atmel/atmel-sama7g5-isc.c
+++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c
@@ -63,65 +63,45 @@
static const struct isc_format sama7g5_controller_formats[] = {
{
.fourcc = V4L2_PIX_FMT_ARGB444,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_ARGB555,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_RGB565,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_ABGR32,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_XBGR32,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUV420,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_UYVY,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_VYUY,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUYV,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_YUV422P,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_GREY,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_Y10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_Y16,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SBGGR8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGBRG8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGRBG8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SRGGB8,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SBGGR10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGBRG10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SGRBG10,
- },
- {
+ }, {
.fourcc = V4L2_PIX_FMT_SRGGB10,
},
};
--
2.25.1
As a top MC video driver, the atmel-isc should not propagate the format to the
subdevice.
It should rather check at streamon() time if the subdev is properly configured
with a compatible format.
Removed the whole format finding logic, and reworked the format verification
at streamon time, such that the ISC will return an error if the subdevice
is not properly configured.
With this being done, the module parameter 'sensor_prefered' makes no sense
anymore. The ISC should not decide which format the sensor is using. The
ISC should only cope with the situation and inform userspace if the streaming
is possible in the current configuration.
Signed-off-by: Eugen Hristev <[email protected]>
---
drivers/media/platform/atmel/atmel-isc-base.c | 269 ++++++++----------
drivers/media/platform/atmel/atmel-isc.h | 1 +
2 files changed, 123 insertions(+), 147 deletions(-)
diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
index 31c8e3029eee..5f98b72b97c3 100644
--- a/drivers/media/platform/atmel/atmel-isc-base.c
+++ b/drivers/media/platform/atmel/atmel-isc-base.c
@@ -36,11 +36,6 @@ static unsigned int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "debug level (0-2)");
-static unsigned int sensor_preferred = 1;
-module_param(sensor_preferred, uint, 0644);
-MODULE_PARM_DESC(sensor_preferred,
- "Sensor is preferred to output the specified format (1-on 0-off), default 1");
-
#define ISC_IS_FORMAT_RAW(mbus_code) \
(((mbus_code) & 0xf000) == 0x3000)
@@ -532,7 +527,7 @@ static int isc_enum_fmt_vid_cap(struct file *file, void *priv,
* convert it to any of the formats that we usually can with a
* RAW sensor. Thus, do not advertise them.
*/
- if (!isc->config.sd_format ||
+ if (isc->config.sd_format &&
!ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
return -EINVAL;
@@ -621,20 +616,30 @@ static int isc_try_validate_formats(struct isc_device *isc)
break;
default:
/* any other different formats are not supported */
+ v4l2_err(&isc->v4l2_dev, "Requested unsupported format.\n");
ret = -EINVAL;
}
v4l2_dbg(1, debug, &isc->v4l2_dev,
"Format validation, requested rgb=%u, yuv=%u, grey=%u, bayer=%u\n",
rgb, yuv, grey, bayer);
- /* we cannot output RAW if we do not receive RAW */
- if ((bayer) && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
+ if ((bayer) &&
+ !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
+ v4l2_err(&isc->v4l2_dev, "Cannot output RAW if we do not receive RAW.\n");
return -EINVAL;
+ }
- /* we cannot output GREY if we do not receive RAW/GREY */
if (grey && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code) &&
- !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code))
+ !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
+ v4l2_err(&isc->v4l2_dev, "Cannot output GREY if we do not receive RAW/GREY.\n");
return -EINVAL;
+ }
+
+ if ((rgb || bayer || yuv) &&
+ ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) {
+ v4l2_err(&isc->v4l2_dev, "Cannot convert GREY to another format.\n");
+ return -EINVAL;
+ }
return ret;
}
@@ -862,7 +867,7 @@ static void isc_try_fse(struct isc_device *isc,
* If we do not know yet which format the subdev is using, we cannot
* do anything.
*/
- if (!isc->try_config.sd_format)
+ if (!isc->config.sd_format)
return;
fse.code = isc->try_config.sd_format->mbus_code;
@@ -883,180 +888,137 @@ static void isc_try_fse(struct isc_device *isc,
}
}
-static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f,
- u32 *code)
+static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f)
{
- int i;
- struct isc_format *sd_fmt = NULL, *direct_fmt = NULL;
struct v4l2_pix_format *pixfmt = &f->fmt.pix;
- struct v4l2_subdev_pad_config pad_cfg = {};
- struct v4l2_subdev_state pad_state = {
- .pads = &pad_cfg
- };
- struct v4l2_subdev_format format = {
- .which = V4L2_SUBDEV_FORMAT_TRY,
- };
- u32 mbus_code;
- int ret;
- bool rlp_dma_direct_dump = false;
+ unsigned int i;
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
- /* Step 1: find a RAW format that is supported */
- for (i = 0; i < isc->num_user_formats; i++) {
- if (ISC_IS_FORMAT_RAW(isc->user_formats[i]->mbus_code)) {
- sd_fmt = isc->user_formats[i];
+ isc->try_config.fourcc = isc->user_formats[0]->fourcc;
+
+ /* find if the format requested is supported */
+ for (i = 0; i < isc->controller_formats_size; i++)
+ if (isc->controller_formats[i].fourcc == pixfmt->pixelformat) {
+ isc->try_config.fourcc = pixfmt->pixelformat;
break;
}
- }
- /* Step 2: We can continue with this RAW format, or we can look
- * for better: maybe sensor supports directly what we need.
- */
- direct_fmt = find_format_by_fourcc(isc, pixfmt->pixelformat);
-
- /* Step 3: We have both. We decide given the module parameter which
- * one to use.
- */
- if (direct_fmt && sd_fmt && sensor_preferred)
- sd_fmt = direct_fmt;
-
- /* Step 4: we do not have RAW but we have a direct format. Use it. */
- if (direct_fmt && !sd_fmt)
- sd_fmt = direct_fmt;
-
- /* Step 5: if we are using a direct format, we need to package
- * everything as 8 bit data and just dump it
- */
- if (sd_fmt == direct_fmt)
- rlp_dma_direct_dump = true;
-
- /* Step 6: We have no format. This can happen if the userspace
- * requests some weird/invalid format.
- * In this case, default to whatever we have
- */
- if (!sd_fmt && !direct_fmt) {
- sd_fmt = isc->user_formats[isc->num_user_formats - 1];
- v4l2_dbg(1, debug, &isc->v4l2_dev,
- "Sensor not supporting %.4s, using %.4s\n",
- (char *)&pixfmt->pixelformat, (char *)&sd_fmt->fourcc);
- }
-
- if (!sd_fmt) {
- ret = -EINVAL;
- goto isc_try_fmt_err;
- }
-
- /* Step 7: Print out what we decided for debugging */
- v4l2_dbg(1, debug, &isc->v4l2_dev,
- "Preferring to have sensor using format %.4s\n",
- (char *)&sd_fmt->fourcc);
-
- /* Step 8: at this moment we decided which format the subdev will use */
- isc->try_config.sd_format = sd_fmt;
-
- /* Limit to Atmel ISC hardware capabilities */
- if (pixfmt->width > isc->max_width)
- pixfmt->width = isc->max_width;
- if (pixfmt->height > isc->max_height)
- pixfmt->height = isc->max_height;
-
- /*
- * The mbus format is the one the subdev outputs.
- * The pixels will be transferred in this format Sensor -> ISC
- */
- mbus_code = sd_fmt->mbus_code;
-
- /*
- * Validate formats. If the required format is not OK, default to raw.
- */
-
- isc->try_config.fourcc = pixfmt->pixelformat;
-
- if (isc_try_validate_formats(isc)) {
- pixfmt->pixelformat = isc->try_config.fourcc = sd_fmt->fourcc;
- /* Re-try to validate the new format */
- ret = isc_try_validate_formats(isc);
- if (ret)
- goto isc_try_fmt_err;
- }
-
- ret = isc_try_configure_rlp_dma(isc, rlp_dma_direct_dump);
- if (ret)
- goto isc_try_fmt_err;
-
- ret = isc_try_configure_pipeline(isc);
- if (ret)
- goto isc_try_fmt_err;
- /* Obtain frame sizes if possible to have crop requirements ready */
- isc_try_fse(isc, &pad_state);
-
- v4l2_fill_mbus_format(&format.format, pixfmt, mbus_code);
- ret = v4l2_subdev_call(isc->current_subdev->sd, pad, set_fmt,
- &pad_state, &format);
- if (ret < 0)
- goto isc_try_fmt_subdev_err;
+ /* If we did not find the requested format, we will fallback here */
+ pixfmt->pixelformat = isc->try_config.fourcc;
+ pixfmt->colorspace = V4L2_COLORSPACE_SRGB;
+ pixfmt->field = V4L2_FIELD_NONE;
- v4l2_fill_pix_format(pixfmt, &format.format);
+ isc_try_configure_rlp_dma(isc, false);
/* Limit to Atmel ISC hardware capabilities */
- if (pixfmt->width > isc->max_width)
- pixfmt->width = isc->max_width;
- if (pixfmt->height > isc->max_height)
- pixfmt->height = isc->max_height;
+ v4l_bound_align_image(&pixfmt->width, 16, isc->max_width, 0,
+ &pixfmt->height, 16, isc->max_height, 0, 0);
pixfmt->field = V4L2_FIELD_NONE;
pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3;
pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) *
pixfmt->height;
- if (code)
- *code = mbus_code;
+ isc->try_fmt = *f;
return 0;
+}
-isc_try_fmt_err:
- v4l2_err(&isc->v4l2_dev, "Could not find any possible format for a working pipeline\n");
-isc_try_fmt_subdev_err:
- memset(&isc->try_config, 0, sizeof(isc->try_config));
+static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
+{
+ isc_try_fmt(isc, f);
- return ret;
+ /* make the try configuration active */
+ isc->config = isc->try_config;
+ isc->fmt = isc->try_fmt;
+
+ v4l2_dbg(1, debug, &isc->v4l2_dev, "ISC set_fmt to %.4s @%dx%d\n",
+ (char *)&f->fmt.pix.pixelformat,
+ f->fmt.pix.width, f->fmt.pix.height);
+
+ return 0;
}
-static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
+static int isc_validate(struct isc_device *isc)
{
+ int ret;
+ int i;
+ struct isc_format *sd_fmt = NULL;
+ struct v4l2_pix_format *pixfmt = &isc->fmt.fmt.pix;
struct v4l2_subdev_format format = {
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ .pad = isc->remote_pad,
+ };
+ struct v4l2_subdev_pad_config pad_cfg = {};
+ struct v4l2_subdev_state pad_state = {
+ .pads = &pad_cfg,
};
- u32 mbus_code = 0;
- int ret;
- ret = isc_try_fmt(isc, f, &mbus_code);
+ /* Get current format from subdev */
+ ret = v4l2_subdev_call(isc->current_subdev->sd, pad, get_fmt, NULL,
+ &format);
if (ret)
return ret;
- v4l2_fill_mbus_format(&format.format, &f->fmt.pix, mbus_code);
- ret = v4l2_subdev_call(isc->current_subdev->sd, pad,
- set_fmt, NULL, &format);
- if (ret < 0)
- return ret;
+ /* Identify the subdev's format configuration */
+ for (i = 0; i < isc->num_user_formats; i++)
+ if (isc->user_formats[i]->mbus_code == format.format.code) {
+ sd_fmt = isc->user_formats[i];
+ break;
+ }
- /* Limit to Atmel ISC hardware capabilities */
- if (f->fmt.pix.width > isc->max_width)
- f->fmt.pix.width = isc->max_width;
- if (f->fmt.pix.height > isc->max_height)
- f->fmt.pix.height = isc->max_height;
+ /* Check if the format is not supported */
+ if (!sd_fmt) {
+ v4l2_err(&isc->v4l2_dev,
+ "Current subdevice is streaming a media bus code that is not supported 0x%x\n",
+ format.format.code);
+ return -EPIPE;
+ }
+
+ /* At this moment we know which format the subdev will use */
+ isc->try_config.sd_format = sd_fmt;
- isc->fmt = *f;
+ /* If the sensor is not RAW, we can only do a direct dump */
+ if (!ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
+ isc_try_configure_rlp_dma(isc, true);
+ /* Check if the frame size is the same. Otherwise we may overflow */
+ if (pixfmt->height != format.format.height ||
+ pixfmt->width != format.format.width) {
+ v4l2_err(&isc->v4l2_dev,
+ "ISC not configured with the proper frame size: %dx%d\n",
+ format.format.width, format.format.height);
+ return -EPIPE;
+ }
+
+ v4l2_dbg(1, debug, &isc->v4l2_dev,
+ "Identified subdev using format %.4s with %dx%d %d bpp\n",
+ (char *)&sd_fmt->fourcc, pixfmt->width, pixfmt->height,
+ isc->try_config.bpp);
+
+ /* Reset and restart AWB if the subdevice changed the format */
if (isc->try_config.sd_format && isc->config.sd_format &&
isc->try_config.sd_format != isc->config.sd_format) {
isc->ctrls.hist_stat = HIST_INIT;
isc_reset_awb_ctrls(isc);
isc_update_v4l2_ctrls(isc);
}
- /* make the try configuration active */
+
+ /* Validate formats */
+ ret = isc_try_validate_formats(isc);
+ if (ret)
+ return ret;
+
+ /* Obtain frame sizes if possible to have crop requirements ready */
+ isc_try_fse(isc, &pad_state);
+
+ /* Configure ISC pipeline for the config */
+ ret = isc_try_configure_pipeline(isc);
+ if (ret)
+ return ret;
+
isc->config = isc->try_config;
v4l2_dbg(1, debug, &isc->v4l2_dev, "New ISC configuration in place\n");
@@ -1064,6 +1026,19 @@ static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
return 0;
}
+static int isc_streamon(struct file *file, void *priv, enum v4l2_buf_type bt)
+{
+ struct isc_device *isc = video_drvdata(file);
+ int ret;
+
+ ret = isc_validate(isc);
+
+ if (ret)
+ return ret;
+
+ return vb2_ioctl_streamon(file, priv, bt);
+}
+
static int isc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
@@ -1080,7 +1055,7 @@ static int isc_try_fmt_vid_cap(struct file *file, void *priv,
{
struct isc_device *isc = video_drvdata(file);
- return isc_try_fmt(isc, f, NULL);
+ return isc_try_fmt(isc, f);
}
static int isc_enum_input(struct file *file, void *priv,
@@ -1176,7 +1151,7 @@ static const struct v4l2_ioctl_ops isc_ioctl_ops = {
.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_streamon = isc_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.vidioc_g_parm = isc_g_parm,
@@ -1879,7 +1854,7 @@ static int isc_set_default_fmt(struct isc_device *isc)
};
int ret;
- ret = isc_try_fmt(isc, &f, NULL);
+ ret = isc_try_fmt(isc, &f);
if (ret)
return ret;
diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
index c2cb805faff3..7081698adddd 100644
--- a/drivers/media/platform/atmel/atmel-isc.h
+++ b/drivers/media/platform/atmel/atmel-isc.h
@@ -297,6 +297,7 @@ struct isc_device {
struct completion comp;
struct v4l2_format fmt;
+ struct v4l2_format try_fmt;
struct isc_format **user_formats;
unsigned int num_user_formats;
--
2.25.1
On Fri, Nov 12, 2021 at 04:24:46PM +0200, Eugen Hristev wrote:
> Add bindings documentation for Microchip CSI2 Demultiplexer controller.
>
> CSI2DC is a demultiplexer from Synopsys IDI interface specification to
> parallel interface connection or direct memory access.
>
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> Changes in v2:
> - changed the endpoint/port references as suggested by Rob
>
> Changes in this version :
> - fixed 'sink' name to be actually source.
> - added dma properties and example with dma
>
> Previous change log:
> Changes in v5:
> - modified bindings as per Rob Herring review
>
> Changes in v4:
> - Removed property for inter-line-delay and for clock continuous/non-continuous
> - Removed virtual channel by reg for second endpoint
>
> Changes in v3:
> - Removed some text from description, as it was explained in the schema
> - fixed other things as per Rob's review
> - moved some text inside the schema, like the clock description
>
> Changes in v2:
> - fixed warnings reported by dt_binding_check
>
> .../bindings/media/microchip,csi2dc.yaml | 159 ++++++++++++++++++
> 1 file changed, 159 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
>
> diff --git a/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml b/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
> new file mode 100644
> index 000000000000..45418c964bf3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
> @@ -0,0 +1,159 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/microchip,csi2dc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Microchip CSI2 Demux Controller (CSI2DC)
> +
> +maintainers:
> + - Eugen Hristev <[email protected]>
> +
> +description:
> + CSI2DC - Camera Serial Interface 2 Demux Controller
> +
> + CSI2DC is a hardware block that receives incoming data from an IDI interface
> + and filters packets based on their data type and virtual channel identifier,
> + then converts the byte stream into a cross clock domain to a pixel stream
> + to a parallel interface that can be read by a sensor controller.
> + IDI interface is Synopsys proprietary.
> +
> + CSI2DC provides two pipes, one video pipe and one data pipe. Video pipe
> + is connected to a sensor controller and the data pipe is accessible
> + as a DMA slave port to a DMA controller.
> +
> + CSI2DC supports a single 'port' node as a sink port with Synopsys 32-bit
> + IDI interface. The connected endpoint must be a IDI interface compatible
> + device , that can provide 32-bit IDI interface connection as source port.
> + This port is mandatory.
> +
> + CSI2DC supports one 'port' node as source port with parallel interface.
> + This is called video pipe.
> + This port has an 'endpoint' that can be connected to a sink port of another
> + controller (next in pipeline).
> +
> + CSI2DC also supports direct access to the data through AHB, via DMA channel,
> + called data pipe.
> + Because of this, the source 'port' child node (second) is not mandatory.
> + If the source 'port' child node is missing, only data pipe is available.
> + For data pipe to be available, a dma controller must be referenced.
> +
> +properties:
> + compatible:
> + const: microchip,sama7g5-csi2dc
> +
> + reg:
> + maxItems: 1
> +
> + clocks:
> + maxItems: 2
> +
> + clock-names:
> + description:
> + CSI2DC must have two clocks to function correctly. One clock is the
> + peripheral clock for the inside functionality of the hardware block.
> + This is named 'pclk'. The second clock must be the cross domain clock,
> + in which CSI2DC will perform clock crossing. This clock must be fed
> + by the next controller in pipeline, which usually is a sensor controller.
> + Normally this clock should be given by this sensor controller who
> + is also a clock source. This clock is named 'scck', sensor controller clock.
> + items:
> + - const: pclk
> + - const: scck
> +
> + dmas:
> + maxItems: 1
> +
> + dma-names:
> + const: rx
> +
> + ports:
> + $ref: /schemas/graph.yaml#/properties/ports
> +
> + properties:
> + port@0:
> + $ref: /schemas/graph.yaml#/properties/port
This must be #/$defs/port-base instead of #/properties/port if
video-interfaces.yaml is used.
> + description:
> + Input port node, single endpoint describing the input port.
> +
> + properties:
> + endpoint:
> + $ref: video-interfaces.yaml#
But what properties from this do you use? It doesn't look like you need
this?
> + unevaluatedProperties: false
> + description: Endpoint connected to input device
> +
> + port@1:
> + $ref: /schemas/graph.yaml#/properties/port
> + description:
> + Output port node, single endpoint describing the output port.
> +
> + properties:
> + endpoint:
> + unevaluatedProperties: false
> + $ref: video-interfaces.yaml#
> + description: Endpoint connected to output device
> +
> + required:
> + - port@0
> +
> +additionalProperties: false
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - clock-names
> + - ports
> +
> +examples:
> + # Example for connecting to a parallel sensor controller block
> + - |
> + csi2dc@e1404000 {
> + compatible = "microchip,sama7g5-csi2dc";
> + reg = <0xe1404000 0x500>;
> + clocks = <&pclk>, <&scck>;
> + clock-names = "pclk", "scck";
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + port@0 {
> + reg = <0>; /* must be 0, first child port */
> + csi2dc_in: endpoint { /* input from IDI interface */
> + remote-endpoint = <&csi2host_out>;
> + };
> + };
> +
> + port@1 {
> + reg = <1>; /* must be 1, second child port */
> + csi2dc_out: endpoint {
> + remote-endpoint = <&xisc_in>; /* output to sensor controller */
> + };
> + };
> + };
> + };
> +
> + # Example for connecting to a DMA master as an AHB slave
> + - |
> + #include <dt-bindings/dma/at91.h>
> + csi2dc@e1404000 {
> + compatible = "microchip,sama7g5-csi2dc";
> + reg = <0xe1404000 0x500>;
> + clocks = <&pclk>, <&scck>;
> + clock-names = "pclk", "scck";
> + dmas = <&dma0 AT91_XDMAC_DT_PERID(34)>;
> + dma-names = "rx";
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + port@0 {
> + reg = <0>; /* must be 0, first child port */
> + csi2dc_input: endpoint { /* input from IDI interface */
> + remote-endpoint = <&csi2host_out>;
> + };
> + };
> + };
> + };
> +
> +...
> --
> 2.25.1
>
>
Hi Rob, Eugen,
On Mon, Nov 29, 2021 at 03:10:26PM -0600, Rob Herring wrote:
> > + properties:
> > + endpoint:
> > + $ref: video-interfaces.yaml#
>
> But what properties from this do you use? It doesn't look like you need
> this?
I'd expect to see data-lanes at least --- unless the number of lanes is
really fixed.
--
Sakari Ailus
Hi Eugen,
Thank you for the patch.
On Fri, Nov 12, 2021 at 04:24:46PM +0200, Eugen Hristev wrote:
> Add bindings documentation for Microchip CSI2 Demultiplexer controller.
>
> CSI2DC is a demultiplexer from Synopsys IDI interface specification to
> parallel interface connection or direct memory access.
>
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> Changes in v2:
> - changed the endpoint/port references as suggested by Rob
>
> Changes in this version :
> - fixed 'sink' name to be actually source.
> - added dma properties and example with dma
>
> Previous change log:
> Changes in v5:
> - modified bindings as per Rob Herring review
>
> Changes in v4:
> - Removed property for inter-line-delay and for clock continuous/non-continuous
> - Removed virtual channel by reg for second endpoint
>
> Changes in v3:
> - Removed some text from description, as it was explained in the schema
> - fixed other things as per Rob's review
> - moved some text inside the schema, like the clock description
>
> Changes in v2:
> - fixed warnings reported by dt_binding_check
>
> .../bindings/media/microchip,csi2dc.yaml | 159 ++++++++++++++++++
> 1 file changed, 159 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
>
> diff --git a/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml b/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
> new file mode 100644
> index 000000000000..45418c964bf3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
> @@ -0,0 +1,159 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/microchip,csi2dc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Microchip CSI2 Demux Controller (CSI2DC)
> +
> +maintainers:
> + - Eugen Hristev <[email protected]>
> +
> +description:
> + CSI2DC - Camera Serial Interface 2 Demux Controller
> +
> + CSI2DC is a hardware block that receives incoming data from an IDI interface
> + and filters packets based on their data type and virtual channel identifier,
> + then converts the byte stream into a cross clock domain to a pixel stream
> + to a parallel interface that can be read by a sensor controller.
> + IDI interface is Synopsys proprietary.
> +
> + CSI2DC provides two pipes, one video pipe and one data pipe. Video pipe
> + is connected to a sensor controller and the data pipe is accessible
> + as a DMA slave port to a DMA controller.
> +
> + CSI2DC supports a single 'port' node as a sink port with Synopsys 32-bit
> + IDI interface. The connected endpoint must be a IDI interface compatible
> + device , that can provide 32-bit IDI interface connection as source port.
s/ , //
> + This port is mandatory.
> +
> + CSI2DC supports one 'port' node as source port with parallel interface.
> + This is called video pipe.
> + This port has an 'endpoint' that can be connected to a sink port of another
> + controller (next in pipeline).
> +
> + CSI2DC also supports direct access to the data through AHB, via DMA channel,
> + called data pipe.
> + Because of this, the source 'port' child node (second) is not mandatory.
> + If the source 'port' child node is missing, only data pipe is available.
> + For data pipe to be available, a dma controller must be referenced.
Ports are hardware elements, so in that sense they should exist if the
hardware is synthesized with the port, even when the port isn't
connected. Connections are then described by endpoints. I think both
port@0 and port@1 should be required, and the above description updated
accordingly.
> +
> +properties:
> + compatible:
> + const: microchip,sama7g5-csi2dc
> +
> + reg:
> + maxItems: 1
> +
> + clocks:
> + maxItems: 2
> +
> + clock-names:
> + description:
> + CSI2DC must have two clocks to function correctly. One clock is the
> + peripheral clock for the inside functionality of the hardware block.
> + This is named 'pclk'. The second clock must be the cross domain clock,
> + in which CSI2DC will perform clock crossing. This clock must be fed
> + by the next controller in pipeline, which usually is a sensor controller.
> + Normally this clock should be given by this sensor controller who
> + is also a clock source. This clock is named 'scck', sensor controller clock.
> + items:
> + - const: pclk
> + - const: scck
> +
> + dmas:
> + maxItems: 1
> +
> + dma-names:
> + const: rx
> +
> + ports:
> + $ref: /schemas/graph.yaml#/properties/ports
> +
> + properties:
> + port@0:
> + $ref: /schemas/graph.yaml#/properties/port
> + description:
> + Input port node, single endpoint describing the input port.
> +
> + properties:
> + endpoint:
> + $ref: video-interfaces.yaml#
> + unevaluatedProperties: false
> + description: Endpoint connected to input device
> +
> + port@1:
> + $ref: /schemas/graph.yaml#/properties/port
> + description:
> + Output port node, single endpoint describing the output port.
> +
> + properties:
> + endpoint:
> + unevaluatedProperties: false
> + $ref: video-interfaces.yaml#
> + description: Endpoint connected to output device
> +
> + required:
> + - port@0
> +
> +additionalProperties: false
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - clock-names
> + - ports
> +
> +examples:
> + # Example for connecting to a parallel sensor controller block
> + - |
> + csi2dc@e1404000 {
> + compatible = "microchip,sama7g5-csi2dc";
> + reg = <0xe1404000 0x500>;
> + clocks = <&pclk>, <&scck>;
> + clock-names = "pclk", "scck";
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + port@0 {
> + reg = <0>; /* must be 0, first child port */
> + csi2dc_in: endpoint { /* input from IDI interface */
> + remote-endpoint = <&csi2host_out>;
> + };
> + };
> +
> + port@1 {
> + reg = <1>; /* must be 1, second child port */
> + csi2dc_out: endpoint {
> + remote-endpoint = <&xisc_in>; /* output to sensor controller */
> + };
> + };
> + };
> + };
> +
> + # Example for connecting to a DMA master as an AHB slave
> + - |
> + #include <dt-bindings/dma/at91.h>
> + csi2dc@e1404000 {
> + compatible = "microchip,sama7g5-csi2dc";
> + reg = <0xe1404000 0x500>;
> + clocks = <&pclk>, <&scck>;
> + clock-names = "pclk", "scck";
> + dmas = <&dma0 AT91_XDMAC_DT_PERID(34)>;
> + dma-names = "rx";
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + port@0 {
> + reg = <0>; /* must be 0, first child port */
> + csi2dc_input: endpoint { /* input from IDI interface */
> + remote-endpoint = <&csi2host_out>;
> + };
> + };
> + };
> + };
> +
> +...
--
Regards,
Laurent Pinchart
Hi Eugen,
Thank you for the patch.
On Fri, Nov 12, 2021 at 04:24:45PM +0200, Eugen Hristev wrote:
> Add Microchip CSI2DC driver in the list.
>
> Signed-off-by: Eugen Hristev <[email protected]>
Reviewed-by: Laurent Pinchart <[email protected]>
> ---
> MAINTAINERS | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 486bdf97f754..545c642c937f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12422,6 +12422,13 @@ L: [email protected] (moderated for non-subscribers)
> S: Supported
> F: sound/soc/atmel
>
> +MICROCHIP CSI2DC DRIVER
> +M: Eugen Hristev <[email protected]>
> +L: [email protected]
> +S: Supported
> +F: Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
> +F: drivers/media/platform/atmel/microchip-csi2dc.c
> +
> MICROCHIP ECC DRIVER
> M: Tudor Ambarus <[email protected]>
> L: [email protected]
--
Regards,
Laurent Pinchart
Hi Eugen,
Thank you for the patch.
On Fri, Nov 12, 2021 at 04:24:48PM +0200, Eugen Hristev wrote:
> Add new file atmel-isc-clk.c to MAINTAINERS entry.
I'd squash this with the patch that adds the new file, but even better
would be to use patterns instead of listing each file.
Some for the other patches touching MAINTAINERS in this series.
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> MAINTAINERS | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 545c642c937f..bb7b48e3605c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12455,6 +12455,7 @@ S: Supported
> F: Documentation/devicetree/bindings/media/atmel,isc.yaml
> F: Documentation/devicetree/bindings/media/microchip,xisc.yaml
> F: drivers/media/platform/atmel/atmel-isc-base.c
> +F: drivers/media/platform/atmel/atmel-isc-clk.c
> F: drivers/media/platform/atmel/atmel-isc-regs.h
> F: drivers/media/platform/atmel/atmel-isc.h
> F: drivers/media/platform/atmel/atmel-sama5d2-isc.c
--
Regards,
Laurent Pinchart
On 11/29/21 11:37 PM, Sakari Ailus wrote:
> Hi Rob, Eugen,
>
> On Mon, Nov 29, 2021 at 03:10:26PM -0600, Rob Herring wrote:
>>> + properties:
>>> + endpoint:
>>> + $ref: video-interfaces.yaml#
>>
>> But what properties from this do you use? It doesn't look like you need
>> this?
>
> I'd expect to see data-lanes at least --- unless the number of lanes is
> really fixed.
Hello Sakari, Rob,
Actually the input port can be either the Synopsys IDI interface, or
parallel.
Since there is no such bus type 'synopsys IDI' this input port
connection cannot have number of lanes, etc. (it is a 32 bit proprietary
synopsys interface which is a mux/repackage of the CSI2 bus)
Also, this input port could be connected to a parallel sensor. In this
case the csi2dc acts as a bypass between the input port and the output
port. However, the csi2dc still needs to be probed and still needs to be
configured, thus, it cannot be removed from the pipeline (otherwise the
hardware pipeline won't work)
So my current implementation is like this : if the input port is
parallel, use parallel mode, but if the bus type is set to CSI2-DPHY (
actually this is the bus interface used by the sensor ), use the csi2dc
demux video pipeline to demux from IDI to parallel interface.
maybe this schematic is more clear than my words above:
+-----------------+ +-------------+
|port 0 | | port 1 |
| | | |
| | | |
| | | |
| | | |
| | | |
+----------+ | +--------|-|-------+ |
| | | |+------------+ | |
| parallel|---------|--------|| bypass| | | ------|----
| sensor | parallel| || | | | | |
| | intf | || | | | | parallel
+----------+ | |+------------+ | output
| | | | | intf|
| | csi2dc | | | |
| | | | | |
+---------------+ | | | | | |
| |synopsys | | | | |
| synopsys host|IDI | | | | | |
| controller --------------| | | | |
| | | | | | | |
| | | +--------|-|-------+ |
+---------------+ | | | |
^ | | | |
csi2 bus | | | | |
+------------+ | | | |
| | | | | |
| csi2 sensor| +-----------------+ +-------------+
| |
| |
+------------+
In this case, what would you advise to use for the bindings of the csi2dc ?
One option is to add the Synopsys IDI interface and document this as a
possible bus type. I would rather not do this, since it's Synopsys
proprietary and it would be difficult for me to document it at this
moment (rather someone from Synopsys who is more familiar with this )
So, I should still refer video interfaces in the node, and then specify
that this bus can be either parallel or csi2 dphy, but without number of
lanes since it does not make sense (this would be between csi2 sensor
and synopsys host controller), and specify only parallel properties like
bus width , etc ?
Which would be a better way to go ?
Thanks,
Eugen
>
> --
> Sakari Ailus
>
Hello Eugen
thanks for addressing all my previous comments, just a few more
things and the driver looks good to me
On Fri, Nov 12, 2021 at 04:24:47PM +0200, Eugen Hristev wrote:
> Microchip CSI2DC (CSI2 Demultiplexer Controller) is a misc bridge device
> that converts a byte stream in IDI Synopsys format (coming from a CSI2HOST)
> to a pixel stream that can be captured by a sensor controller.
>
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> Changes in v2:
> - implement try formats
> - added parallel mode
> - moved to fwlink endpoints
> - many other minor corrections from Jacopo's review
>
> Changes in this revision:
> - addressed comments by Jacopo and Laurent as in this thread:
> https://www.spinics.net/lists/linux-media/msg181044.html
>
> Previous change log :
> Changes in v5:
> - only in bindings
>
> Changes in v4:
> - now using get_mbus_config ops to get data from the subdevice, like the
> virtual channel id, and the clock type.
> - now having possibility to select any of the RAW10 data modes
> - at completion time, select which formats are also available in the subdevice,
> and move to the dynamic list accordingly
> - changed the pipeline integration, do not advertise subdev ready at probe time.
> wait until completion is done, and then start a workqueue that will register
> this device as a subdevice for the next element in pipeline.
> - moved the s_power code into a different function called now csi2dc_power
> that is called with CONFIG_PM functions. This is also called at completion,
> to have the device ready in case CONFIG_PM is not selected on the platform.
> - merged try_fmt into set_fmt
> - driver cleanup, wrapped lines over 80 characters
>
> Changes in v2:
> - moved driver to platform/atmel
> - fixed minor things as per Sakari's review
> - still some things from v2 review are not yet addressed, to be followed up
>
>
> drivers/media/platform/Makefile | 1 +
> drivers/media/platform/atmel/Kconfig | 15 +
> drivers/media/platform/atmel/Makefile | 1 +
> .../media/platform/atmel/microchip-csi2dc.c | 797 ++++++++++++++++++
> 4 files changed, 814 insertions(+)
> create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c
>
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 832357240e89..b18e5f704145 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/
> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/
> obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel/
> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel/
> +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += atmel/
>
> obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32/
>
> diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig
> index dda2f27da317..f83bee373d82 100644
> --- a/drivers/media/platform/atmel/Kconfig
> +++ b/drivers/media/platform/atmel/Kconfig
> @@ -40,3 +40,18 @@ config VIDEO_ATMEL_ISI
> help
> This module makes the ATMEL Image Sensor Interface available
> as a v4l2 device.
> +
> +config VIDEO_MICROCHIP_CSI2DC
> + tristate "Microchip CSI2 Demux Controller"
> + depends on VIDEO_V4L2 && COMMON_CLK && OF
> + depends on ARCH_AT91 || COMPILE_TEST
> + select MEDIA_CONTROLLER
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + help
> + CSI2 Demux Controller driver. CSI2DC is a helper chip
> + that converts IDI interface byte stream to a parallel pixel stream.
> + It supports various RAW formats as input.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called microchip-csi2dc.
> diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
> index 46d264ab7948..39f0a7eba702 100644
> --- a/drivers/media/platform/atmel/Makefile
> +++ b/drivers/media/platform/atmel/Makefile
> @@ -6,3 +6,4 @@ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
> obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-base.o
> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
> +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
> diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/atmel/microchip-csi2dc.c
> new file mode 100644
> index 000000000000..2b106f6fd5d0
> --- /dev/null
> +++ b/drivers/media/platform/atmel/microchip-csi2dc.c
> @@ -0,0 +1,797 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Microchip CSI2 Demux Controller (CSI2DC) driver
> + *
> + * Copyright (C) 2018 Microchip Technology, Inc.
> + *
> + * Author: Eugen Hristev <[email protected]>
> + *
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-subdev.h>
> +
> +/* Global configuration register */
> +#define CSI2DC_GCFG 0x0
> +
> +/* MIPI sensor pixel clock is free running */
> +#define CSI2DC_GCFG_MIPIFRN BIT(0)
> +/* GPIO parallel interface selection */
> +#define CSI2DC_GCFG_GPIOSEL BIT(1)
> +/* Output waveform inter-line minimum delay */
> +#define CSI2DC_GCFG_HLC(v) ((v) << 4)
> +#define CSI2DC_GCFG_HLC_MASK GENMASK(7, 4)
> +/* SAMA7G5 requires a HLC delay of 15 */
> +#define SAMA7G5_HLC (15)
> +
> +/* Global control register */
> +#define CSI2DC_GCTLR 0x04
> +#define CSI2DC_GCTLR_SWRST BIT(0)
> +
> +/* Global status register */
> +#define CSI2DC_GS 0x08
> +
> +/* SSP interrupt status register */
> +#define CSI2DC_SSPIS 0x28
> +/* Pipe update register */
> +#define CSI2DC_PU 0xc0
> +/* Video pipe attributes update */
> +#define CSI2DC_PU_VP BIT(0)
> +
> +/* Pipe update status register */
> +#define CSI2DC_PUS 0xc4
> +
> +/* Video pipeline enable register */
> +#define CSI2DC_VPE 0xf8
> +#define CSI2DC_VPE_ENABLE BIT(0)
> +
> +/* Video pipeline configuration register */
> +#define CSI2DC_VPCFG 0xfc
> +/* Data type */
> +#define CSI2DC_VPCFG_DT(v) ((v) << 0)
> +#define CSI2DC_VPCFG_DT_MASK GENMASK(5, 0)
> +/* Virtual channel identifier */
> +#define CSI2DC_VPCFG_VC(v) ((v) << 6)
> +#define CSI2DC_VPCFG_VC_MASK GENMASK(7, 6)
> +/* Decompression enable */
> +#define CSI2DC_VPCFG_DE BIT(8)
> +/* Decoder mode */
> +#define CSI2DC_VPCFG_DM(v) ((v) << 9)
> +#define CSI2DC_VPCFG_DM_DECODER8TO12 0
> +/* Decoder predictor 2 selection */
> +#define CSI2DC_VPCFG_DP2 BIT(12)
> +/* Recommended memory storage */
> +#define CSI2DC_VPCFG_RMS BIT(13)
> +/* Post adjustment */
> +#define CSI2DC_VPCFG_PA BIT(14)
> +
> +/* Video pipeline column register */
> +#define CSI2DC_VPCOL 0x100
> +/* Column number */
> +#define CSI2DC_VPCOL_COL(v) ((v) << 0)
> +#define CSI2DC_VPCOL_COL_MASK GENMASK(15, 0)
> +
> +/* Video pipeline row register */
> +#define CSI2DC_VPROW 0x104
> +/* Row number */
> +#define CSI2DC_VPROW_ROW(v) ((v) << 0)
> +#define CSI2DC_VPROW_ROW_MASK GENMASK(15, 0)
> +
> +/* Version register */
> +#define CSI2DC_VERSION 0x1fc
> +
> +/* register read/write helpers */
> +#define csi2dc_readl(st, reg) readl_relaxed((st)->base + (reg))
> +#define csi2dc_writel(st, reg, val) writel_relaxed((val), \
> + (st)->base + (reg))
> +
> +/* supported RAW data types */
> +#define CSI2DC_DT_RAW6 0x28
> +#define CSI2DC_DT_RAW7 0x29
> +#define CSI2DC_DT_RAW8 0x2a
> +#define CSI2DC_DT_RAW10 0x2b
> +#define CSI2DC_DT_RAW12 0x2c
> +#define CSI2DC_DT_RAW14 0x2d
> +
> +/*
> + * struct csi2dc_format - CSI2DC format type struct
> + * @mbus_code: Media bus code for the format
> + * @dt: Data type constant for this format
> + */
> +struct csi2dc_format {
> + u32 mbus_code;
> + u32 dt;
> +};
> +
> +static const struct csi2dc_format csi2dc_formats[] = {
> + {
> + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .dt = CSI2DC_DT_RAW8,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .dt = CSI2DC_DT_RAW8,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .dt = CSI2DC_DT_RAW8,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .dt = CSI2DC_DT_RAW8,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
> + .dt = CSI2DC_DT_RAW10,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .dt = CSI2DC_DT_RAW10,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .dt = CSI2DC_DT_RAW10,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
> + .dt = CSI2DC_DT_RAW10,
> + }, {
> + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
Is this intentionally not associated with a CSI-2 DT code ?
> + },
> +};
> +
> +enum mipi_csi_pads {
> + CSI2DC_PAD_SINK = 0,
> + CSI2DC_PAD_SOURCE = 1,
> + CSI2DC_PADS_NUM = 2,
> +};
> +
> +/*
> + * struct csi2dc_device - CSI2DC device driver data/config struct
> + * @base: Register map base address
> + * @csi2dc_sd: v4l2 subdevice for the csi2dc device
> + * This is the subdevice that the csi2dc device itself
> + * registers in v4l2 subsystem
> + * @dev: struct device for this csi2dc device
> + * @pclk: Peripheral clock reference
> + * Input clock that clocks the hardware block internal
> + * logic
> + * @scck: Sensor Controller clock reference
> + * Input clock that is used to generate the pixel clock
> + * @format: Current saved format used in g/s fmt
> + * @cur_fmt: Current state format
> + * @try_fmt: Try format that is being tried
> + * @pads: Media entity pads for the csi2dc subdevice
> + * @clk_gated: Whether the clock is gated or free running
> + * @video_pipe: Whether video pipeline is configured
> + * @parallel_mode: The underlying subdevice is connected on a parallel bus
> + * @vc: Current set virtual channel
> + * @notifier: Async notifier that is used to bound the underlying
> + * subdevice to the csi2dc subdevice
> + * @input_sd: Reference to the underlying subdevice bound to the
> + * csi2dc subdevice
> + * @remote_pad: Pad number of the underlying subdevice that is linked
> + * to the csi2dc subdevice sink pad.
> + */
> +struct csi2dc_device {
> + void __iomem *base;
> + struct v4l2_subdev csi2dc_sd;
> + struct device *dev;
> + struct clk *pclk;
> + struct clk *scck;
> +
> + struct v4l2_mbus_framefmt format;
> +
> + const struct csi2dc_format *cur_fmt;
> + const struct csi2dc_format *try_fmt;
> +
> + struct media_pad pads[CSI2DC_PADS_NUM];
> +
> + bool clk_gated;
> + bool video_pipe;
> + bool parallel_mode;
> + u32 vc;
> +
> + struct v4l2_async_notifier notifier;
> +
> + struct v4l2_subdev *input_sd;
> +
> + u32 remote_pad;
> +};
> +
> +static inline struct csi2dc_device *
> +csi2dc_sd_to_csi2dc_device(struct v4l2_subdev *csi2dc_sd)
> +{
> + return container_of(csi2dc_sd, struct csi2dc_device, csi2dc_sd);
> +}
> +
> +static int csi2dc_enum_mbus_code(struct v4l2_subdev *csi2dc_sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->index >= ARRAY_SIZE(csi2dc_formats))
> + return -EINVAL;
> +
> + code->code = csi2dc_formats[code->index].mbus_code;
> +
> + return 0;
> +}
> +
> +static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *format)
> +{
> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
> + format->pad);
> + format->format = *v4l2_try_fmt;
> +
> + return 0;
> + }
> +
> + format->format = csi2dc->format;
> +
> + return 0;
> +}
> +
> +static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd,
> + struct v4l2_subdev_state *sd_state,
> + struct v4l2_subdev_format *req_fmt)
> +{
> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
> + const struct csi2dc_format *fmt, *try_fmt = NULL;
> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(csi2dc_formats); i++) {
> + fmt = &csi2dc_formats[i];
> + if (req_fmt->format.code == fmt->mbus_code)
> + try_fmt = fmt;
> + fmt++;
> + }
> +
> + /* in case we could not find the desired format, default to something */
> + if (!try_fmt) {
> + try_fmt = &csi2dc_formats[0];
> +
> + dev_dbg(csi2dc->dev,
> + "CSI2DC unsupported format 0x%x, defaulting to 0x%x\n",
> + req_fmt->format.code, csi2dc_formats[0].mbus_code);
> + }
> +
> + req_fmt->format.code = try_fmt->mbus_code;
> + req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
> + req_fmt->format.field = V4L2_FIELD_NONE;
> +
> + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
> + req_fmt->pad);
> + *v4l2_try_fmt = req_fmt->format;
> + /* Trying on the pad sink makes the source sink change too */
> + if (req_fmt->pad == CSI2DC_PAD_SINK) {
s/source sink/source pad/
> + v4l2_try_fmt =
> + v4l2_subdev_get_try_format(csi2dc_sd,
> + sd_state,
> + CSI2DC_PAD_SOURCE);
> + *v4l2_try_fmt = req_fmt->format;
Shouldn't this be the same for the active format then ? If this device
operates by transporting the same exact format from the sink to the
source, shouldn't you disable setting a format on the source, and
always propagate to the sink ? I would have done so, but maybe
Sakari/Laurent could tell you if that's fine.
> + }
> + /* if we are just trying, we are done */
> + return 0;
> + }
> +
> + /* save the format for later requests */
> + csi2dc->format = req_fmt->format;
> +
> + /* update config */
> + csi2dc->cur_fmt = try_fmt;
> +
> + dev_dbg(csi2dc->dev, "new format set: 0x%x @%dx%d\n",
> + csi2dc->format.code, csi2dc->format.width,
> + csi2dc->format.height);
> +
> + return 0;
> +}
> +
> +static int csi2dc_power(struct csi2dc_device *csi2dc, int on)
> +{
> + int ret = 0;
> +
> + if (on) {
> + ret = clk_prepare_enable(csi2dc->pclk);
> + if (ret) {
> + dev_err(csi2dc->dev, "failed to enable pclk:%d\n", ret);
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(csi2dc->scck);
> + if (ret) {
> + dev_err(csi2dc->dev, "failed to enable scck:%d\n", ret);
> + clk_disable_unprepare(csi2dc->pclk);
> + return ret;
> + }
> +
> + /* if powering up, deassert reset line */
> + csi2dc_writel(csi2dc, CSI2DC_GCTLR, CSI2DC_GCTLR_SWRST);
> + } else {
> + /* if powering down, assert reset line */
> + csi2dc_writel(csi2dc, CSI2DC_GCTLR, 0);
> +
> + clk_disable_unprepare(csi2dc->scck);
> + clk_disable_unprepare(csi2dc->pclk);
> + }
> +
> + return ret;
> +}
> +
> +static int csi2dc_get_mbus_config(struct csi2dc_device *csi2dc)
> +{
> + struct v4l2_mbus_config mbus_config = { 0 };
> + int ret;
> +
> + ret = v4l2_subdev_call(csi2dc->input_sd, pad, get_mbus_config,
> + csi2dc->remote_pad, &mbus_config);
> + if (ret == -ENOIOCTLCMD) {
> + dev_dbg(csi2dc->dev,
> + "no remote mbus configuration available\n");
> + goto csi2dc_get_mbus_config_defaults;
> + }
> +
> + if (ret) {
> + dev_err(csi2dc->dev,
> + "failed to get remote mbus configuration\n");
> + goto csi2dc_get_mbus_config_defaults;
> + }
> +
> + if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_0)
> + csi2dc->vc = 0;
> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_1)
> + csi2dc->vc = 1;
> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_2)
> + csi2dc->vc = 2;
> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_3)
> + csi2dc->vc = 3;
> +
> + dev_dbg(csi2dc->dev, "subdev sending on channel %d\n", csi2dc->vc);
> +
> + csi2dc->clk_gated = mbus_config.flags &
> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
> +
> + dev_dbg(csi2dc->dev, "mbus_config: %s clock\n",
> + csi2dc->clk_gated ? "gated" : "free running");
> +
> + return 0;
> +
> +csi2dc_get_mbus_config_defaults:
> + csi2dc->vc = 0; /* Virtual ID 0 by default */
> +
> + return 0;
> +}
> +
> +static void csi2dc_vp_update(struct csi2dc_device *csi2dc)
> +{
> + u32 vp, gcfg;
> +
> + if (!csi2dc->video_pipe) {
> + dev_err(csi2dc->dev, "video pipeline unavailable\n");
> + return;
> + }
> +
> + if (csi2dc->parallel_mode) {
> + /* In parallel mode, GPIO parallel interface must be selected */
> + gcfg = csi2dc_readl(csi2dc, CSI2DC_GCFG);
> + gcfg |= CSI2DC_GCFG_GPIOSEL;
> + csi2dc_writel(csi2dc, CSI2DC_GCFG, gcfg);
> + return;
> + }
> +
> + /* serial video pipeline */
> +
> + csi2dc_writel(csi2dc, CSI2DC_GCFG,
> + (SAMA7G5_HLC & CSI2DC_GCFG_HLC_MASK) |
> + (csi2dc->clk_gated ? 0 : CSI2DC_GCFG_MIPIFRN));
> +
> + csi2dc_writel(csi2dc, CSI2DC_VPCOL,
> + CSI2DC_VPCOL_COL(0xfff) & CSI2DC_VPCOL_COL_MASK);
> + csi2dc_writel(csi2dc, CSI2DC_VPROW,
> + CSI2DC_VPROW_ROW(0xfff) & CSI2DC_VPROW_ROW_MASK);
> +
> + vp = CSI2DC_VPCFG_DT(csi2dc->cur_fmt->dt) & CSI2DC_VPCFG_DT_MASK;
> + vp |= CSI2DC_VPCFG_VC(csi2dc->vc) & CSI2DC_VPCFG_VC_MASK;
> + vp &= ~CSI2DC_VPCFG_DE;
> + vp |= CSI2DC_VPCFG_DM(CSI2DC_VPCFG_DM_DECODER8TO12);
> + vp &= ~CSI2DC_VPCFG_DP2;
> + vp &= ~CSI2DC_VPCFG_RMS;
> + vp |= CSI2DC_VPCFG_PA;
> +
> + csi2dc_writel(csi2dc, CSI2DC_VPCFG, vp);
> + csi2dc_writel(csi2dc, CSI2DC_VPE, CSI2DC_VPE_ENABLE);
> + csi2dc_writel(csi2dc, CSI2DC_PU, CSI2DC_PU_VP);
> +}
> +
> +static int csi2dc_s_stream(struct v4l2_subdev *csi2dc_sd, int enable)
> +{
> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
> + int ret;
> +
> + if (enable) {
> + ret = pm_runtime_resume_and_get(csi2dc->dev);
> + if (ret < 0)
> + return ret;
> +
> + csi2dc_get_mbus_config(csi2dc);
> +
> + csi2dc_vp_update(csi2dc);
> +
> + return v4l2_subdev_call(csi2dc->input_sd, video, s_stream,
> + true);
> + }
> + /* stop streaming scenario */
> + ret = v4l2_subdev_call(csi2dc->input_sd, video, s_stream, false);
> +
> + pm_runtime_put_sync(csi2dc->dev);
> +
> + return ret;
> +}
> +
> +static int csi2dc_init_cfg(struct v4l2_subdev *csi2dc_sd,
> + struct v4l2_subdev_state *sd_state)
> +{
> + struct v4l2_mbus_framefmt *v4l2_try_fmt =
> + v4l2_subdev_get_try_format(csi2dc_sd, sd_state, 0);
> +
> + v4l2_try_fmt->height = 480;
> + v4l2_try_fmt->width = 640;
> + v4l2_try_fmt->code = csi2dc_formats[0].mbus_code;
> + v4l2_try_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> + v4l2_try_fmt->field = V4L2_FIELD_NONE;
> + v4l2_try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + v4l2_try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> + v4l2_try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops csi2dc_pad_ops = {
> + .enum_mbus_code = csi2dc_enum_mbus_code,
> + .set_fmt = csi2dc_set_fmt,
> + .get_fmt = csi2dc_get_fmt,
> + .init_cfg = csi2dc_init_cfg,
> +};
> +
> +static const struct v4l2_subdev_video_ops csi2dc_video_ops = {
> + .s_stream = csi2dc_s_stream,
> +};
> +
> +static const struct v4l2_subdev_ops csi2dc_subdev_ops = {
> + .pad = &csi2dc_pad_ops,
> + .video = &csi2dc_video_ops,
> +};
> +
> +static int csi2dc_async_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_subdev *asd)
> +{
> + struct csi2dc_device *csi2dc = container_of(notifier,
> + struct csi2dc_device, notifier);
> + int pad;
> + int ret;
> +
> + csi2dc->input_sd = subdev;
> +
> + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
> + MEDIA_PAD_FL_SOURCE);
> + if (pad < 0) {
> + dev_err(csi2dc->dev, "Failed to find pad for %s\n",
> + subdev->name);
> + return pad;
> + }
> +
> + csi2dc->remote_pad = pad;
> +
> + ret = media_create_pad_link(&csi2dc->input_sd->entity,
> + csi2dc->remote_pad,
> + &csi2dc->csi2dc_sd.entity, 0,
> + MEDIA_LNK_FL_ENABLED);
> + if (ret) {
> + dev_err(csi2dc->dev,
> + "Failed to create pad link: %s to %s\n",
> + csi2dc->input_sd->entity.name,
> + csi2dc->csi2dc_sd.entity.name);
> + return ret;
> + }
> +
> + dev_dbg(csi2dc->dev, "link with %s pad: %d\n",
> + csi2dc->input_sd->name, csi2dc->remote_pad);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_async_notifier_operations csi2dc_async_ops = {
> + .bound = csi2dc_async_bound,
> +};
> +
> +static int csi2dc_prepare_notifier(struct csi2dc_device *csi2dc,
> + struct fwnode_handle *input_fwnode)
> +{
> + struct v4l2_async_subdev *asd;
> + int ret = 0;
> +
> + v4l2_async_nf_init(&csi2dc->notifier);
> +
> + asd = v4l2_async_nf_add_fwnode_remote(&csi2dc->notifier,
> + input_fwnode,
> + struct v4l2_async_subdev);
> +
> + fwnode_handle_put(input_fwnode);
> +
> + if (IS_ERR(asd)) {
> + ret = PTR_ERR(asd);
> + dev_err(csi2dc->dev,
> + "failed to add async notifier for node %pOF: %d\n",
> + to_of_node(input_fwnode), ret);
> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> + return ret;
> + }
> +
> + csi2dc->notifier.ops = &csi2dc_async_ops;
> +
> + ret = v4l2_async_subdev_nf_register(&csi2dc->csi2dc_sd,
> + &csi2dc->notifier);
Drop this blank line
> +
> + if (ret) {
> + dev_err(csi2dc->dev, "fail to register async notifier: %d\n",
> + ret);
> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> + }
> +
> + return ret;
> +}
> +
> +static int csi2dc_of_parse(struct csi2dc_device *csi2dc,
> + struct device_node *of_node)
> +{
> + struct fwnode_handle *input_fwnode, *output_fwnode;
> + struct v4l2_fwnode_endpoint input_endpoint = { 0 },
> + output_endpoint = { 0 };
> + int ret;
> +
> + input_fwnode = fwnode_graph_get_next_endpoint(of_fwnode_handle(of_node),
> + NULL);
> +
> + if (!input_fwnode) {
> + dev_err(csi2dc->dev,
> + "missing port node at %pOF, input node is mandatory.\n",
> + of_node);
> + return -EINVAL;
> + }
> +
> + ret = v4l2_fwnode_endpoint_parse(input_fwnode, &input_endpoint);
> +
Drop this blank line
> + if (ret) {
> + dev_err(csi2dc->dev, "endpoint not defined at %pOF\n", of_node);
> + goto csi2dc_of_parse_err;
> + }
> +
> + if (input_endpoint.bus_type == V4L2_MBUS_PARALLEL ||
> + input_endpoint.bus_type == V4L2_MBUS_BT656) {
> + csi2dc->parallel_mode = true;
> + dev_dbg(csi2dc->dev,
> + "subdevice connected on parallel interface\n");
> + }
> +
> + if (input_endpoint.bus_type == V4L2_MBUS_CSI2_DPHY) {
> + csi2dc->clk_gated = input_endpoint.bus.mipi_csi2.flags &
> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
> + dev_dbg(csi2dc->dev,
> + "subdevice connected on serial interface\n");
> + dev_dbg(csi2dc->dev, "DT: %s clock\n",
> + csi2dc->clk_gated ? "gated" : "free running");
> + }
> +
> + output_fwnode = fwnode_graph_get_next_endpoint
> + (of_fwnode_handle(of_node), input_fwnode);
> +
> + if (output_fwnode)
> + ret = v4l2_fwnode_endpoint_parse(output_fwnode,
> + &output_endpoint);
> +
> + fwnode_handle_put(output_fwnode);
> +
> + if (!output_fwnode || ret) {
> + dev_info(csi2dc->dev,
> + "missing output node at %pOF, data pipe available only.\n",
> + of_node);
> + } else {
> + if (output_endpoint.bus_type != V4L2_MBUS_PARALLEL &&
> + output_endpoint.bus_type != V4L2_MBUS_BT656) {
> + dev_err(csi2dc->dev,
> + "output port must be parallel/bt656.\n");
> + ret = -EINVAL;
> + goto csi2dc_of_parse_err;
> + }
> +
> + csi2dc->video_pipe = true;
> +
> + dev_dbg(csi2dc->dev,
> + "block %pOF [%d.%d]->[%d.%d] video pipeline\n",
> + of_node, input_endpoint.base.port,
> + input_endpoint.base.id, output_endpoint.base.port,
> + output_endpoint.base.id);
> + }
> +
> + /* prepare async notifier for subdevice completion */
> + return csi2dc_prepare_notifier(csi2dc, input_fwnode);
> +
> +csi2dc_of_parse_err:
> + fwnode_handle_put(input_fwnode);
> + return ret;
> +}
> +
> +static void csi2dc_default_format(struct csi2dc_device *csi2dc)
> +{
> + csi2dc->cur_fmt = &csi2dc_formats[0];
> +
> + csi2dc->format.height = 480;
> + csi2dc->format.width = 640;
> + csi2dc->format.code = csi2dc_formats[0].mbus_code;
> + csi2dc->format.colorspace = V4L2_COLORSPACE_SRGB;
> + csi2dc->format.field = V4L2_FIELD_NONE;
> + csi2dc->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + csi2dc->format.quantization = V4L2_QUANTIZATION_DEFAULT;
> + csi2dc->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +}
> +
> +static int csi2dc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct csi2dc_device *csi2dc;
> + int ret = 0;
> + u32 ver;
> +
> + csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL);
> + if (!csi2dc)
> + return -ENOMEM;
> +
> + csi2dc->dev = dev;
> +
> + csi2dc->base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(csi2dc->base)) {
> + dev_err(dev, "base address not set\n");
> + return PTR_ERR(csi2dc->base);
> + }
> +
> + csi2dc->pclk = devm_clk_get(dev, "pclk");
> + if (IS_ERR(csi2dc->pclk)) {
> + ret = PTR_ERR(csi2dc->pclk);
> + dev_err(dev, "failed to get pclk: %d\n", ret);
> + return ret;
> + }
> +
> + csi2dc->scck = devm_clk_get(dev, "scck");
> + if (IS_ERR(csi2dc->scck)) {
> + ret = PTR_ERR(csi2dc->scck);
> + dev_err(dev, "failed to get scck: %d\n", ret);
> + return ret;
> + }
> +
> + v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops);
> +
> + csi2dc->csi2dc_sd.owner = THIS_MODULE;
> + csi2dc->csi2dc_sd.dev = dev;
> + snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name),
> + "csi2dc");
> +
> + csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> + csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +
> + platform_set_drvdata(pdev, csi2dc);
> +
> + ret = csi2dc_of_parse(csi2dc, dev->of_node);
> + if (ret)
> + goto csi2dc_probe_cleanup_entity;
> +
> + csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> + if (csi2dc->video_pipe)
> + csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +
> + ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity,
> + csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1,
> + csi2dc->pads);
> + if (ret < 0) {
> + dev_err(dev, "media entity init failed\n");
> + goto csi2dc_probe_cleanup_notifier;
> + }
> +
> + csi2dc_default_format(csi2dc);
> +
> + /* turn power on to validate capabilities */
> + ret = csi2dc_power(csi2dc, true);
> + if (ret < 0)
> + goto csi2dc_probe_cleanup_notifier;
> +
> + pm_runtime_set_active(dev);
> + pm_runtime_enable(dev);
> + ver = csi2dc_readl(csi2dc, CSI2DC_VERSION);
Shouldn't you verify that the version is the expected one, or that, at
least the device can be accessed ?
> + pm_request_idle(dev);
I'm not well versed when it comes to runtime_pm but I can read that
Documentation/power/runtime_pm.rst- `int pm_request_idle(struct device *dev);`
Documentation/power/runtime_pm.rst- - submit a request to execute the subsystem-level idle callback for the
Documentation/power/runtime_pm.rst- device (the request is represented by a work item in pm_wq); returns 0 on
Documentation/power/runtime_pm.rst- success or error code if the request has not been queued up
And looking below you have
+static const struct dev_pm_ops csi2dc_dev_pm_ops = {
+ SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
+};
Which expands to
include/linux/pm.h: SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \
so it seems to me you've no idle func defined, hence you above call
has no effect so you're device stays powered on after your above call
to
ret = csi2dc_power(csi2dc, true);
Also trying to suspend using runtime_pm while having powered on by
calling the resume function directly would leave the pm status
unbalanced ?
I would raher call pm_runtime_resume() and
pm_runtime_suspend() or call the power routine directly, and later
enable pm_runtime
csi2dc_power(true)
readl();
csi2fd_power(false);
pm_runtime_set_active();
pm_runtime_enable();
v4l2_async_register();
Again, not well versed in runtime_pm, so don't assume the above makes
sense :)
> +
> + /*
> + * we must register the subdev after PM runtime has been requested,
> + * otherwise we might bound immediately and request pm_runtime_resume
> + * before runtime_enable.
> + */
> + ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd);
> + if (ret) {
> + dev_err(csi2dc->dev, "failed to register the subdevice\n");
> + goto csi2dc_probe_cleanup_notifier;
> + }
> +
> + dev_info(dev, "Microchip CSI2DC version %x\n", ver);
> +
> + return 0;
> +
> +csi2dc_probe_cleanup_notifier:
> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> +csi2dc_probe_cleanup_entity:
> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
I wasn't honestly expecting this
/**
* media_entity_cleanup() - free resources associated with an entity
*
* @entity: entity where the pads belong
*
* This function must be called during the cleanup phase after unregistering
* the entity (currently, it does nothing).
*/
#if IS_ENABLED(CONFIG_MEDIA_CONTROLLER)
static inline void media_entity_cleanup(struct media_entity *entity) {}
:)
> +
> + return ret;
> +}
> +
> +static int csi2dc_remove(struct platform_device *pdev)
> +{
> + struct csi2dc_device *csi2dc = platform_get_drvdata(pdev);
> +
> + pm_runtime_disable(&pdev->dev);
> +
> + v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd);
> + v4l2_async_nf_unregister(&csi2dc->notifier);
> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
> +
> + return 0;
> +}
> +
> +static int __maybe_unused csi2dc_runtime_suspend(struct device *dev)
> +{
> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
> +
> + return csi2dc_power(csi2dc, false);
> +}
> +
> +static int __maybe_unused csi2dc_runtime_resume(struct device *dev)
> +{
> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
> +
> + return csi2dc_power(csi2dc, true);
> +}
> +
> +static const struct dev_pm_ops csi2dc_dev_pm_ops = {
> + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
> +};
> +
> +static const struct of_device_id csi2dc_of_match[] = {
> + { .compatible = "microchip,sama7g5-csi2dc" },
> + { }
> +};
> +
> +MODULE_DEVICE_TABLE(of, csi2dc_of_match);
> +
> +static struct platform_driver csi2dc_driver = {
> + .probe = csi2dc_probe,
> + .remove = csi2dc_remove,
> + .driver = {
> + .name = "microchip-csi2dc",
> + .pm = &csi2dc_dev_pm_ops,
> + .of_match_table = of_match_ptr(csi2dc_of_match),
> + },
> +};
> +
> +module_platform_driver(csi2dc_driver);
> +
> +MODULE_AUTHOR("Eugen Hristev <[email protected]>");
> +MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.25.1
>
On 12/6/21 12:51 PM, Jacopo Mondi wrote:
> Hello Eugen
>
> thanks for addressing all my previous comments, just a few more
> things and the driver looks good to me
Thanks for reviewing. I tried my best to accomodate your suggestions.
>
> On Fri, Nov 12, 2021 at 04:24:47PM +0200, Eugen Hristev wrote:
>> Microchip CSI2DC (CSI2 Demultiplexer Controller) is a misc bridge device
>> that converts a byte stream in IDI Synopsys format (coming from a CSI2HOST)
>> to a pixel stream that can be captured by a sensor controller.
>>
>> Signed-off-by: Eugen Hristev <[email protected]>
>> ---
>> Changes in v2:
>> - implement try formats
>> - added parallel mode
>> - moved to fwlink endpoints
>> - many other minor corrections from Jacopo's review
>>
>> Changes in this revision:
>> - addressed comments by Jacopo and Laurent as in this thread:
>> https://www.spinics.net/lists/linux-media/msg181044.html
>>
>> Previous change log :
>> Changes in v5:
>> - only in bindings
>>
>> Changes in v4:
>> - now using get_mbus_config ops to get data from the subdevice, like the
>> virtual channel id, and the clock type.
>> - now having possibility to select any of the RAW10 data modes
>> - at completion time, select which formats are also available in the subdevice,
>> and move to the dynamic list accordingly
>> - changed the pipeline integration, do not advertise subdev ready at probe time.
>> wait until completion is done, and then start a workqueue that will register
>> this device as a subdevice for the next element in pipeline.
>> - moved the s_power code into a different function called now csi2dc_power
>> that is called with CONFIG_PM functions. This is also called at completion,
>> to have the device ready in case CONFIG_PM is not selected on the platform.
>> - merged try_fmt into set_fmt
>> - driver cleanup, wrapped lines over 80 characters
>>
>> Changes in v2:
>> - moved driver to platform/atmel
>> - fixed minor things as per Sakari's review
>> - still some things from v2 review are not yet addressed, to be followed up
>>
>>
>> drivers/media/platform/Makefile | 1 +
>> drivers/media/platform/atmel/Kconfig | 15 +
>> drivers/media/platform/atmel/Makefile | 1 +
>> .../media/platform/atmel/microchip-csi2dc.c | 797 ++++++++++++++++++
>> 4 files changed, 814 insertions(+)
>> create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c
>>
>> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
>> index 832357240e89..b18e5f704145 100644
>> --- a/drivers/media/platform/Makefile
>> +++ b/drivers/media/platform/Makefile
>> @@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/
>> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/
>> obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel/
>> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel/
>> +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += atmel/
>>
>> obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32/
>>
>> diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig
>> index dda2f27da317..f83bee373d82 100644
>> --- a/drivers/media/platform/atmel/Kconfig
>> +++ b/drivers/media/platform/atmel/Kconfig
>> @@ -40,3 +40,18 @@ config VIDEO_ATMEL_ISI
>> help
>> This module makes the ATMEL Image Sensor Interface available
>> as a v4l2 device.
>> +
>> +config VIDEO_MICROCHIP_CSI2DC
>> + tristate "Microchip CSI2 Demux Controller"
>> + depends on VIDEO_V4L2 && COMMON_CLK && OF
>> + depends on ARCH_AT91 || COMPILE_TEST
>> + select MEDIA_CONTROLLER
>> + select VIDEO_V4L2_SUBDEV_API
>> + select V4L2_FWNODE
>> + help
>> + CSI2 Demux Controller driver. CSI2DC is a helper chip
>> + that converts IDI interface byte stream to a parallel pixel stream.
>> + It supports various RAW formats as input.
>> +
>> + To compile this driver as a module, choose M here: the
>> + module will be called microchip-csi2dc.
>> diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
>> index 46d264ab7948..39f0a7eba702 100644
>> --- a/drivers/media/platform/atmel/Makefile
>> +++ b/drivers/media/platform/atmel/Makefile
>> @@ -6,3 +6,4 @@ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
>> obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-base.o
>> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
>> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
>> +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
>> diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/atmel/microchip-csi2dc.c
>> new file mode 100644
>> index 000000000000..2b106f6fd5d0
>> --- /dev/null
>> +++ b/drivers/media/platform/atmel/microchip-csi2dc.c
>> @@ -0,0 +1,797 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Microchip CSI2 Demux Controller (CSI2DC) driver
>> + *
>> + * Copyright (C) 2018 Microchip Technology, Inc.
>> + *
>> + * Author: Eugen Hristev <[email protected]>
>> + *
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/mod_devicetable.h>
>> +#include <linux/module.h>
>> +#include <linux/of_graph.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/videodev2.h>
>> +
>> +#include <media/v4l2-fwnode.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +/* Global configuration register */
>> +#define CSI2DC_GCFG 0x0
>> +
>> +/* MIPI sensor pixel clock is free running */
>> +#define CSI2DC_GCFG_MIPIFRN BIT(0)
>> +/* GPIO parallel interface selection */
>> +#define CSI2DC_GCFG_GPIOSEL BIT(1)
>> +/* Output waveform inter-line minimum delay */
>> +#define CSI2DC_GCFG_HLC(v) ((v) << 4)
>> +#define CSI2DC_GCFG_HLC_MASK GENMASK(7, 4)
>> +/* SAMA7G5 requires a HLC delay of 15 */
>> +#define SAMA7G5_HLC (15)
>> +
>> +/* Global control register */
>> +#define CSI2DC_GCTLR 0x04
>> +#define CSI2DC_GCTLR_SWRST BIT(0)
>> +
>> +/* Global status register */
>> +#define CSI2DC_GS 0x08
>> +
>> +/* SSP interrupt status register */
>> +#define CSI2DC_SSPIS 0x28
>> +/* Pipe update register */
>> +#define CSI2DC_PU 0xc0
>> +/* Video pipe attributes update */
>> +#define CSI2DC_PU_VP BIT(0)
>> +
>> +/* Pipe update status register */
>> +#define CSI2DC_PUS 0xc4
>> +
>> +/* Video pipeline enable register */
>> +#define CSI2DC_VPE 0xf8
>> +#define CSI2DC_VPE_ENABLE BIT(0)
>> +
>> +/* Video pipeline configuration register */
>> +#define CSI2DC_VPCFG 0xfc
>> +/* Data type */
>> +#define CSI2DC_VPCFG_DT(v) ((v) << 0)
>> +#define CSI2DC_VPCFG_DT_MASK GENMASK(5, 0)
>> +/* Virtual channel identifier */
>> +#define CSI2DC_VPCFG_VC(v) ((v) << 6)
>> +#define CSI2DC_VPCFG_VC_MASK GENMASK(7, 6)
>> +/* Decompression enable */
>> +#define CSI2DC_VPCFG_DE BIT(8)
>> +/* Decoder mode */
>> +#define CSI2DC_VPCFG_DM(v) ((v) << 9)
>> +#define CSI2DC_VPCFG_DM_DECODER8TO12 0
>> +/* Decoder predictor 2 selection */
>> +#define CSI2DC_VPCFG_DP2 BIT(12)
>> +/* Recommended memory storage */
>> +#define CSI2DC_VPCFG_RMS BIT(13)
>> +/* Post adjustment */
>> +#define CSI2DC_VPCFG_PA BIT(14)
>> +
>> +/* Video pipeline column register */
>> +#define CSI2DC_VPCOL 0x100
>> +/* Column number */
>> +#define CSI2DC_VPCOL_COL(v) ((v) << 0)
>> +#define CSI2DC_VPCOL_COL_MASK GENMASK(15, 0)
>> +
>> +/* Video pipeline row register */
>> +#define CSI2DC_VPROW 0x104
>> +/* Row number */
>> +#define CSI2DC_VPROW_ROW(v) ((v) << 0)
>> +#define CSI2DC_VPROW_ROW_MASK GENMASK(15, 0)
>> +
>> +/* Version register */
>> +#define CSI2DC_VERSION 0x1fc
>> +
>> +/* register read/write helpers */
>> +#define csi2dc_readl(st, reg) readl_relaxed((st)->base + (reg))
>> +#define csi2dc_writel(st, reg, val) writel_relaxed((val), \
>> + (st)->base + (reg))
>> +
>> +/* supported RAW data types */
>> +#define CSI2DC_DT_RAW6 0x28
>> +#define CSI2DC_DT_RAW7 0x29
>> +#define CSI2DC_DT_RAW8 0x2a
>> +#define CSI2DC_DT_RAW10 0x2b
>> +#define CSI2DC_DT_RAW12 0x2c
>> +#define CSI2DC_DT_RAW14 0x2d
>> +
>> +/*
>> + * struct csi2dc_format - CSI2DC format type struct
>> + * @mbus_code: Media bus code for the format
>> + * @dt: Data type constant for this format
>> + */
>> +struct csi2dc_format {
>> + u32 mbus_code;
>> + u32 dt;
>> +};
>> +
>> +static const struct csi2dc_format csi2dc_formats[] = {
>> + {
>> + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
>> + .dt = CSI2DC_DT_RAW8,
>> + }, {
>> + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
>> + .dt = CSI2DC_DT_RAW8,
>> + }, {
>> + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
>> + .dt = CSI2DC_DT_RAW8,
>> + }, {
>> + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
>> + .dt = CSI2DC_DT_RAW8,
>> + }, {
>> + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
>> + .dt = CSI2DC_DT_RAW10,
>> + }, {
>> + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
>> + .dt = CSI2DC_DT_RAW10,
>> + }, {
>> + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
>> + .dt = CSI2DC_DT_RAW10,
>> + }, {
>> + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
>> + .dt = CSI2DC_DT_RAW10,
>> + }, {
>> + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
>
> Is this intentionally not associated with a CSI-2 DT code ?
Yes. It is not a raw format.
I do not have a sensor that could stream YUYV8_2X8 with CSI-2 .
I tested this mode with parallel sensor, and in this case, CSI-2 DT
value does not make sense and it's not used.
The idea would be that when and if I get my hands on a sensor that could
actually do this with CSI2, I would update the driver here (if it works.
I am not sure it actually does ...)
>
>> + },
>> +};
>> +
>> +enum mipi_csi_pads {
>> + CSI2DC_PAD_SINK = 0,
>> + CSI2DC_PAD_SOURCE = 1,
>> + CSI2DC_PADS_NUM = 2,
>> +};
>> +
>> +/*
>> + * struct csi2dc_device - CSI2DC device driver data/config struct
>> + * @base: Register map base address
>> + * @csi2dc_sd: v4l2 subdevice for the csi2dc device
>> + * This is the subdevice that the csi2dc device itself
>> + * registers in v4l2 subsystem
>> + * @dev: struct device for this csi2dc device
>> + * @pclk: Peripheral clock reference
>> + * Input clock that clocks the hardware block internal
>> + * logic
>> + * @scck: Sensor Controller clock reference
>> + * Input clock that is used to generate the pixel clock
>> + * @format: Current saved format used in g/s fmt
>> + * @cur_fmt: Current state format
>> + * @try_fmt: Try format that is being tried
>> + * @pads: Media entity pads for the csi2dc subdevice
>> + * @clk_gated: Whether the clock is gated or free running
>> + * @video_pipe: Whether video pipeline is configured
>> + * @parallel_mode: The underlying subdevice is connected on a parallel bus
>> + * @vc: Current set virtual channel
>> + * @notifier: Async notifier that is used to bound the underlying
>> + * subdevice to the csi2dc subdevice
>> + * @input_sd: Reference to the underlying subdevice bound to the
>> + * csi2dc subdevice
>> + * @remote_pad: Pad number of the underlying subdevice that is linked
>> + * to the csi2dc subdevice sink pad.
>> + */
>> +struct csi2dc_device {
>> + void __iomem *base;
>> + struct v4l2_subdev csi2dc_sd;
>> + struct device *dev;
>> + struct clk *pclk;
>> + struct clk *scck;
>> +
>> + struct v4l2_mbus_framefmt format;
>> +
>> + const struct csi2dc_format *cur_fmt;
>> + const struct csi2dc_format *try_fmt;
>> +
>> + struct media_pad pads[CSI2DC_PADS_NUM];
>> +
>> + bool clk_gated;
>> + bool video_pipe;
>> + bool parallel_mode;
>> + u32 vc;
>> +
>> + struct v4l2_async_notifier notifier;
>> +
>> + struct v4l2_subdev *input_sd;
>> +
>> + u32 remote_pad;
>> +};
>> +
>> +static inline struct csi2dc_device *
>> +csi2dc_sd_to_csi2dc_device(struct v4l2_subdev *csi2dc_sd)
>> +{
>> + return container_of(csi2dc_sd, struct csi2dc_device, csi2dc_sd);
>> +}
>> +
>> +static int csi2dc_enum_mbus_code(struct v4l2_subdev *csi2dc_sd,
>> + struct v4l2_subdev_state *sd_state,
>> + struct v4l2_subdev_mbus_code_enum *code)
>> +{
>> + if (code->index >= ARRAY_SIZE(csi2dc_formats))
>> + return -EINVAL;
>> +
>> + code->code = csi2dc_formats[code->index].mbus_code;
>> +
>> + return 0;
>> +}
>> +
>> +static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd,
>> + struct v4l2_subdev_state *sd_state,
>> + struct v4l2_subdev_format *format)
>> +{
>> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
>> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
>> +
>> + if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
>> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
>> + format->pad);
>> + format->format = *v4l2_try_fmt;
>> +
>> + return 0;
>> + }
>> +
>> + format->format = csi2dc->format;
>> +
>> + return 0;
>> +}
>> +
>> +static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd,
>> + struct v4l2_subdev_state *sd_state,
>> + struct v4l2_subdev_format *req_fmt)
>> +{
>> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
>> + const struct csi2dc_format *fmt, *try_fmt = NULL;
>> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
>> + unsigned int i;
>> +
>> + for (i = 0; i < ARRAY_SIZE(csi2dc_formats); i++) {
>> + fmt = &csi2dc_formats[i];
>> + if (req_fmt->format.code == fmt->mbus_code)
>> + try_fmt = fmt;
>> + fmt++;
>> + }
>> +
>> + /* in case we could not find the desired format, default to something */
>> + if (!try_fmt) {
>> + try_fmt = &csi2dc_formats[0];
>> +
>> + dev_dbg(csi2dc->dev,
>> + "CSI2DC unsupported format 0x%x, defaulting to 0x%x\n",
>> + req_fmt->format.code, csi2dc_formats[0].mbus_code);
>> + }
>> +
>> + req_fmt->format.code = try_fmt->mbus_code;
>> + req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
>> + req_fmt->format.field = V4L2_FIELD_NONE;
>> +
>> + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
>> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
>> + req_fmt->pad);
>> + *v4l2_try_fmt = req_fmt->format;
>> + /* Trying on the pad sink makes the source sink change too */
>> + if (req_fmt->pad == CSI2DC_PAD_SINK) {
>
> s/source sink/source pad/
>
>> + v4l2_try_fmt =
>> + v4l2_subdev_get_try_format(csi2dc_sd,
>> + sd_state,
>> + CSI2DC_PAD_SOURCE);
>> + *v4l2_try_fmt = req_fmt->format;
>
> Shouldn't this be the same for the active format then ? If this device
> operates by transporting the same exact format from the sink to the
> source, shouldn't you disable setting a format on the source, and
> always propagate to the sink ? I would have done so, but maybe
> Sakari/Laurent could tell you if that's fine.
Hm. I will try. To see if it works and v4l2-compliance is happy. ( I did
this propagation for try format because it wasn't )
>
>> + }
>> + /* if we are just trying, we are done */
>> + return 0;
>> + }
>> +
>> + /* save the format for later requests */
>> + csi2dc->format = req_fmt->format;
>> +
>> + /* update config */
>> + csi2dc->cur_fmt = try_fmt;
>> +
>> + dev_dbg(csi2dc->dev, "new format set: 0x%x @%dx%d\n",
>> + csi2dc->format.code, csi2dc->format.width,
>> + csi2dc->format.height);
>> +
>> + return 0;
>> +}
>> +
>> +static int csi2dc_power(struct csi2dc_device *csi2dc, int on)
>> +{
>> + int ret = 0;
>> +
>> + if (on) {
>> + ret = clk_prepare_enable(csi2dc->pclk);
>> + if (ret) {
>> + dev_err(csi2dc->dev, "failed to enable pclk:%d\n", ret);
>> + return ret;
>> + }
>> +
>> + ret = clk_prepare_enable(csi2dc->scck);
>> + if (ret) {
>> + dev_err(csi2dc->dev, "failed to enable scck:%d\n", ret);
>> + clk_disable_unprepare(csi2dc->pclk);
>> + return ret;
>> + }
>> +
>> + /* if powering up, deassert reset line */
>> + csi2dc_writel(csi2dc, CSI2DC_GCTLR, CSI2DC_GCTLR_SWRST);
>> + } else {
>> + /* if powering down, assert reset line */
>> + csi2dc_writel(csi2dc, CSI2DC_GCTLR, 0);
>> +
>> + clk_disable_unprepare(csi2dc->scck);
>> + clk_disable_unprepare(csi2dc->pclk);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static int csi2dc_get_mbus_config(struct csi2dc_device *csi2dc)
>> +{
>> + struct v4l2_mbus_config mbus_config = { 0 };
>> + int ret;
>> +
>> + ret = v4l2_subdev_call(csi2dc->input_sd, pad, get_mbus_config,
>> + csi2dc->remote_pad, &mbus_config);
>> + if (ret == -ENOIOCTLCMD) {
>> + dev_dbg(csi2dc->dev,
>> + "no remote mbus configuration available\n");
>> + goto csi2dc_get_mbus_config_defaults;
>> + }
>> +
>> + if (ret) {
>> + dev_err(csi2dc->dev,
>> + "failed to get remote mbus configuration\n");
>> + goto csi2dc_get_mbus_config_defaults;
>> + }
>> +
>> + if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_0)
>> + csi2dc->vc = 0;
>> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_1)
>> + csi2dc->vc = 1;
>> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_2)
>> + csi2dc->vc = 2;
>> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_3)
>> + csi2dc->vc = 3;
>> +
>> + dev_dbg(csi2dc->dev, "subdev sending on channel %d\n", csi2dc->vc);
>> +
>> + csi2dc->clk_gated = mbus_config.flags &
>> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
>> +
>> + dev_dbg(csi2dc->dev, "mbus_config: %s clock\n",
>> + csi2dc->clk_gated ? "gated" : "free running");
>> +
>> + return 0;
>> +
>> +csi2dc_get_mbus_config_defaults:
>> + csi2dc->vc = 0; /* Virtual ID 0 by default */
>> +
>> + return 0;
>> +}
>> +
>> +static void csi2dc_vp_update(struct csi2dc_device *csi2dc)
>> +{
>> + u32 vp, gcfg;
>> +
>> + if (!csi2dc->video_pipe) {
>> + dev_err(csi2dc->dev, "video pipeline unavailable\n");
>> + return;
>> + }
>> +
>> + if (csi2dc->parallel_mode) {
>> + /* In parallel mode, GPIO parallel interface must be selected */
>> + gcfg = csi2dc_readl(csi2dc, CSI2DC_GCFG);
>> + gcfg |= CSI2DC_GCFG_GPIOSEL;
>> + csi2dc_writel(csi2dc, CSI2DC_GCFG, gcfg);
>> + return;
>> + }
>> +
>> + /* serial video pipeline */
>> +
>> + csi2dc_writel(csi2dc, CSI2DC_GCFG,
>> + (SAMA7G5_HLC & CSI2DC_GCFG_HLC_MASK) |
>> + (csi2dc->clk_gated ? 0 : CSI2DC_GCFG_MIPIFRN));
>> +
>> + csi2dc_writel(csi2dc, CSI2DC_VPCOL,
>> + CSI2DC_VPCOL_COL(0xfff) & CSI2DC_VPCOL_COL_MASK);
>> + csi2dc_writel(csi2dc, CSI2DC_VPROW,
>> + CSI2DC_VPROW_ROW(0xfff) & CSI2DC_VPROW_ROW_MASK);
>> +
>> + vp = CSI2DC_VPCFG_DT(csi2dc->cur_fmt->dt) & CSI2DC_VPCFG_DT_MASK;
>> + vp |= CSI2DC_VPCFG_VC(csi2dc->vc) & CSI2DC_VPCFG_VC_MASK;
>> + vp &= ~CSI2DC_VPCFG_DE;
>> + vp |= CSI2DC_VPCFG_DM(CSI2DC_VPCFG_DM_DECODER8TO12);
>> + vp &= ~CSI2DC_VPCFG_DP2;
>> + vp &= ~CSI2DC_VPCFG_RMS;
>> + vp |= CSI2DC_VPCFG_PA;
>> +
>> + csi2dc_writel(csi2dc, CSI2DC_VPCFG, vp);
>> + csi2dc_writel(csi2dc, CSI2DC_VPE, CSI2DC_VPE_ENABLE);
>> + csi2dc_writel(csi2dc, CSI2DC_PU, CSI2DC_PU_VP);
>> +}
>> +
>> +static int csi2dc_s_stream(struct v4l2_subdev *csi2dc_sd, int enable)
>> +{
>> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
>> + int ret;
>> +
>> + if (enable) {
>> + ret = pm_runtime_resume_and_get(csi2dc->dev);
>> + if (ret < 0)
>> + return ret;
>> +
>> + csi2dc_get_mbus_config(csi2dc);
>> +
>> + csi2dc_vp_update(csi2dc);
>> +
>> + return v4l2_subdev_call(csi2dc->input_sd, video, s_stream,
>> + true);
>> + }
>> + /* stop streaming scenario */
>> + ret = v4l2_subdev_call(csi2dc->input_sd, video, s_stream, false);
>> +
>> + pm_runtime_put_sync(csi2dc->dev);
>> +
>> + return ret;
>> +}
>> +
>> +static int csi2dc_init_cfg(struct v4l2_subdev *csi2dc_sd,
>> + struct v4l2_subdev_state *sd_state)
>> +{
>> + struct v4l2_mbus_framefmt *v4l2_try_fmt =
>> + v4l2_subdev_get_try_format(csi2dc_sd, sd_state, 0);
>> +
>> + v4l2_try_fmt->height = 480;
>> + v4l2_try_fmt->width = 640;
>> + v4l2_try_fmt->code = csi2dc_formats[0].mbus_code;
>> + v4l2_try_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>> + v4l2_try_fmt->field = V4L2_FIELD_NONE;
>> + v4l2_try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>> + v4l2_try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
>> + v4l2_try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
>> +
>> + return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_pad_ops csi2dc_pad_ops = {
>> + .enum_mbus_code = csi2dc_enum_mbus_code,
>> + .set_fmt = csi2dc_set_fmt,
>> + .get_fmt = csi2dc_get_fmt,
>> + .init_cfg = csi2dc_init_cfg,
>> +};
>> +
>> +static const struct v4l2_subdev_video_ops csi2dc_video_ops = {
>> + .s_stream = csi2dc_s_stream,
>> +};
>> +
>> +static const struct v4l2_subdev_ops csi2dc_subdev_ops = {
>> + .pad = &csi2dc_pad_ops,
>> + .video = &csi2dc_video_ops,
>> +};
>> +
>> +static int csi2dc_async_bound(struct v4l2_async_notifier *notifier,
>> + struct v4l2_subdev *subdev,
>> + struct v4l2_async_subdev *asd)
>> +{
>> + struct csi2dc_device *csi2dc = container_of(notifier,
>> + struct csi2dc_device, notifier);
>> + int pad;
>> + int ret;
>> +
>> + csi2dc->input_sd = subdev;
>> +
>> + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
>> + MEDIA_PAD_FL_SOURCE);
>> + if (pad < 0) {
>> + dev_err(csi2dc->dev, "Failed to find pad for %s\n",
>> + subdev->name);
>> + return pad;
>> + }
>> +
>> + csi2dc->remote_pad = pad;
>> +
>> + ret = media_create_pad_link(&csi2dc->input_sd->entity,
>> + csi2dc->remote_pad,
>> + &csi2dc->csi2dc_sd.entity, 0,
>> + MEDIA_LNK_FL_ENABLED);
>> + if (ret) {
>> + dev_err(csi2dc->dev,
>> + "Failed to create pad link: %s to %s\n",
>> + csi2dc->input_sd->entity.name,
>> + csi2dc->csi2dc_sd.entity.name);
>> + return ret;
>> + }
>> +
>> + dev_dbg(csi2dc->dev, "link with %s pad: %d\n",
>> + csi2dc->input_sd->name, csi2dc->remote_pad);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations csi2dc_async_ops = {
>> + .bound = csi2dc_async_bound,
>> +};
>> +
>> +static int csi2dc_prepare_notifier(struct csi2dc_device *csi2dc,
>> + struct fwnode_handle *input_fwnode)
>> +{
>> + struct v4l2_async_subdev *asd;
>> + int ret = 0;
>> +
>> + v4l2_async_nf_init(&csi2dc->notifier);
>> +
>> + asd = v4l2_async_nf_add_fwnode_remote(&csi2dc->notifier,
>> + input_fwnode,
>> + struct v4l2_async_subdev);
>> +
>> + fwnode_handle_put(input_fwnode);
>> +
>> + if (IS_ERR(asd)) {
>> + ret = PTR_ERR(asd);
>> + dev_err(csi2dc->dev,
>> + "failed to add async notifier for node %pOF: %d\n",
>> + to_of_node(input_fwnode), ret);
>> + v4l2_async_nf_cleanup(&csi2dc->notifier);
>> + return ret;
>> + }
>> +
>> + csi2dc->notifier.ops = &csi2dc_async_ops;
>> +
>> + ret = v4l2_async_subdev_nf_register(&csi2dc->csi2dc_sd,
>> + &csi2dc->notifier);
>
> Drop this blank line
>
>> +
>> + if (ret) {
>> + dev_err(csi2dc->dev, "fail to register async notifier: %d\n",
>> + ret);
>> + v4l2_async_nf_cleanup(&csi2dc->notifier);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static int csi2dc_of_parse(struct csi2dc_device *csi2dc,
>> + struct device_node *of_node)
>> +{
>> + struct fwnode_handle *input_fwnode, *output_fwnode;
>> + struct v4l2_fwnode_endpoint input_endpoint = { 0 },
>> + output_endpoint = { 0 };
>> + int ret;
>> +
>> + input_fwnode = fwnode_graph_get_next_endpoint(of_fwnode_handle(of_node),
>> + NULL);
>> +
>> + if (!input_fwnode) {
>> + dev_err(csi2dc->dev,
>> + "missing port node at %pOF, input node is mandatory.\n",
>> + of_node);
>> + return -EINVAL;
>> + }
>> +
>> + ret = v4l2_fwnode_endpoint_parse(input_fwnode, &input_endpoint);
>> +
>
> Drop this blank line
>
>> + if (ret) {
>> + dev_err(csi2dc->dev, "endpoint not defined at %pOF\n", of_node);
>> + goto csi2dc_of_parse_err;
>> + }
>> +
>> + if (input_endpoint.bus_type == V4L2_MBUS_PARALLEL ||
>> + input_endpoint.bus_type == V4L2_MBUS_BT656) {
>> + csi2dc->parallel_mode = true;
>> + dev_dbg(csi2dc->dev,
>> + "subdevice connected on parallel interface\n");
>> + }
>> +
>> + if (input_endpoint.bus_type == V4L2_MBUS_CSI2_DPHY) {
>> + csi2dc->clk_gated = input_endpoint.bus.mipi_csi2.flags &
>> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
>> + dev_dbg(csi2dc->dev,
>> + "subdevice connected on serial interface\n");
>> + dev_dbg(csi2dc->dev, "DT: %s clock\n",
>> + csi2dc->clk_gated ? "gated" : "free running");
>> + }
>> +
>> + output_fwnode = fwnode_graph_get_next_endpoint
>> + (of_fwnode_handle(of_node), input_fwnode);
>> +
>> + if (output_fwnode)
>> + ret = v4l2_fwnode_endpoint_parse(output_fwnode,
>> + &output_endpoint);
>> +
>> + fwnode_handle_put(output_fwnode);
>> +
>> + if (!output_fwnode || ret) {
>> + dev_info(csi2dc->dev,
>> + "missing output node at %pOF, data pipe available only.\n",
>> + of_node);
>> + } else {
>> + if (output_endpoint.bus_type != V4L2_MBUS_PARALLEL &&
>> + output_endpoint.bus_type != V4L2_MBUS_BT656) {
>> + dev_err(csi2dc->dev,
>> + "output port must be parallel/bt656.\n");
>> + ret = -EINVAL;
>> + goto csi2dc_of_parse_err;
>> + }
>> +
>> + csi2dc->video_pipe = true;
>> +
>> + dev_dbg(csi2dc->dev,
>> + "block %pOF [%d.%d]->[%d.%d] video pipeline\n",
>> + of_node, input_endpoint.base.port,
>> + input_endpoint.base.id, output_endpoint.base.port,
>> + output_endpoint.base.id);
>> + }
>> +
>> + /* prepare async notifier for subdevice completion */
>> + return csi2dc_prepare_notifier(csi2dc, input_fwnode);
>> +
>> +csi2dc_of_parse_err:
>> + fwnode_handle_put(input_fwnode);
>> + return ret;
>> +}
>> +
>> +static void csi2dc_default_format(struct csi2dc_device *csi2dc)
>> +{
>> + csi2dc->cur_fmt = &csi2dc_formats[0];
>> +
>> + csi2dc->format.height = 480;
>> + csi2dc->format.width = 640;
>> + csi2dc->format.code = csi2dc_formats[0].mbus_code;
>> + csi2dc->format.colorspace = V4L2_COLORSPACE_SRGB;
>> + csi2dc->format.field = V4L2_FIELD_NONE;
>> + csi2dc->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>> + csi2dc->format.quantization = V4L2_QUANTIZATION_DEFAULT;
>> + csi2dc->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
>> +}
>> +
>> +static int csi2dc_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct csi2dc_device *csi2dc;
>> + int ret = 0;
>> + u32 ver;
>> +
>> + csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL);
>> + if (!csi2dc)
>> + return -ENOMEM;
>> +
>> + csi2dc->dev = dev;
>> +
>> + csi2dc->base = devm_platform_ioremap_resource(pdev, 0);
>> + if (IS_ERR(csi2dc->base)) {
>> + dev_err(dev, "base address not set\n");
>> + return PTR_ERR(csi2dc->base);
>> + }
>> +
>> + csi2dc->pclk = devm_clk_get(dev, "pclk");
>> + if (IS_ERR(csi2dc->pclk)) {
>> + ret = PTR_ERR(csi2dc->pclk);
>> + dev_err(dev, "failed to get pclk: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + csi2dc->scck = devm_clk_get(dev, "scck");
>> + if (IS_ERR(csi2dc->scck)) {
>> + ret = PTR_ERR(csi2dc->scck);
>> + dev_err(dev, "failed to get scck: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops);
>> +
>> + csi2dc->csi2dc_sd.owner = THIS_MODULE;
>> + csi2dc->csi2dc_sd.dev = dev;
>> + snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name),
>> + "csi2dc");
>> +
>> + csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>> + csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> +
>> + platform_set_drvdata(pdev, csi2dc);
>> +
>> + ret = csi2dc_of_parse(csi2dc, dev->of_node);
>> + if (ret)
>> + goto csi2dc_probe_cleanup_entity;
>> +
>> + csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>> + if (csi2dc->video_pipe)
>> + csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>> +
>> + ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity,
>> + csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1,
>> + csi2dc->pads);
>> + if (ret < 0) {
>> + dev_err(dev, "media entity init failed\n");
>> + goto csi2dc_probe_cleanup_notifier;
>> + }
>> +
>> + csi2dc_default_format(csi2dc);
>> +
>> + /* turn power on to validate capabilities */
>> + ret = csi2dc_power(csi2dc, true);
>> + if (ret < 0)
>> + goto csi2dc_probe_cleanup_notifier;
>> +
>> + pm_runtime_set_active(dev);
>> + pm_runtime_enable(dev);
>> + ver = csi2dc_readl(csi2dc, CSI2DC_VERSION);
>
> Shouldn't you verify that the version is the expected one, or that, at
> least the device can be accessed ?
If the read from the register worked, the device can be accessed. If the
device won't respond to this, we will get a bus fault or a freeze.
And printing the version number can help all the time by inspecting the
bootlog to see if something valid was printed.
Version changes from product to product but seeing a bootlog later will
make us easily understand which product was it, and if the version was
read correctly
>
>> + pm_request_idle(dev);
>
> I'm not well versed when it comes to runtime_pm but I can read that
>
> Documentation/power/runtime_pm.rst- `int pm_request_idle(struct device *dev);`
> Documentation/power/runtime_pm.rst- - submit a request to execute the subsystem-level idle callback for the
> Documentation/power/runtime_pm.rst- device (the request is represented by a work item in pm_wq); returns 0 on
> Documentation/power/runtime_pm.rst- success or error code if the request has not been queued up
>
> And looking below you have
>
> +static const struct dev_pm_ops csi2dc_dev_pm_ops = {
> + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
> +};
>
> Which expands to
>
> include/linux/pm.h: SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \
>
> so it seems to me you've no idle func defined, hence you above call
> has no effect so you're device stays powered on after your above call
> to
>
> ret = csi2dc_power(csi2dc, true);
>
> Also trying to suspend using runtime_pm while having powered on by
> calling the resume function directly would leave the pm status
> unbalanced ?
>
> I would raher call pm_runtime_resume() and
> pm_runtime_suspend() or call the power routine directly, and later
> enable pm_runtime
>
> csi2dc_power(true)
> readl();
> csi2fd_power(false);
>
> pm_runtime_set_active();
> pm_runtime_enable();
>
> v4l2_async_register();
>
> Again, not well versed in runtime_pm, so don't assume the above makes
> sense :)
I am not versed in runtime_pm either, I will try to remove this
'pm_request_idle' to see what happens at the first
pm_runtime_resume_and_get, and print messages in power on/power off to
see how and when they are called.
>
>> +
>> + /*
>> + * we must register the subdev after PM runtime has been requested,
>> + * otherwise we might bound immediately and request pm_runtime_resume
>> + * before runtime_enable.
>> + */
>> + ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd);
>> + if (ret) {
>> + dev_err(csi2dc->dev, "failed to register the subdevice\n");
>> + goto csi2dc_probe_cleanup_notifier;
>> + }
>> +
>> + dev_info(dev, "Microchip CSI2DC version %x\n", ver);
>> +
>> + return 0;
>> +
>> +csi2dc_probe_cleanup_notifier:
>> + v4l2_async_nf_cleanup(&csi2dc->notifier);
>> +csi2dc_probe_cleanup_entity:
>> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
>
> I wasn't honestly expecting this
>
> /**
> * media_entity_cleanup() - free resources associated with an entity
> *
> * @entity: entity where the pads belong
> *
> * This function must be called during the cleanup phase after unregistering
> * the entity (currently, it does nothing).
> */
> #if IS_ENABLED(CONFIG_MEDIA_CONTROLLER)
> static inline void media_entity_cleanup(struct media_entity *entity) {}
>
> :)
I saw it as well. But I thought that who knows, in the future it might
do something, so it's better to have it in the driver rather than not
have it at all.
Thanks again for reviewing,
Eugen
>
>> +
>> + return ret;
>> +}
>> +
>> +static int csi2dc_remove(struct platform_device *pdev)
>> +{
>> + struct csi2dc_device *csi2dc = platform_get_drvdata(pdev);
>> +
>> + pm_runtime_disable(&pdev->dev);
>> +
>> + v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd);
>> + v4l2_async_nf_unregister(&csi2dc->notifier);
>> + v4l2_async_nf_cleanup(&csi2dc->notifier);
>> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
>> +
>> + return 0;
>> +}
>> +
>> +static int __maybe_unused csi2dc_runtime_suspend(struct device *dev)
>> +{
>> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
>> +
>> + return csi2dc_power(csi2dc, false);
>> +}
>> +
>> +static int __maybe_unused csi2dc_runtime_resume(struct device *dev)
>> +{
>> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
>> +
>> + return csi2dc_power(csi2dc, true);
>> +}
>> +
>> +static const struct dev_pm_ops csi2dc_dev_pm_ops = {
>> + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
>> +};
>> +
>> +static const struct of_device_id csi2dc_of_match[] = {
>> + { .compatible = "microchip,sama7g5-csi2dc" },
>> + { }
>> +};
>> +
>> +MODULE_DEVICE_TABLE(of, csi2dc_of_match);
>> +
>> +static struct platform_driver csi2dc_driver = {
>> + .probe = csi2dc_probe,
>> + .remove = csi2dc_remove,
>> + .driver = {
>> + .name = "microchip-csi2dc",
>> + .pm = &csi2dc_dev_pm_ops,
>> + .of_match_table = of_match_ptr(csi2dc_of_match),
>> + },
>> +};
>> +
>> +module_platform_driver(csi2dc_driver);
>> +
>> +MODULE_AUTHOR("Eugen Hristev <[email protected]>");
>> +MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver");
>> +MODULE_LICENSE("GPL v2");
>> --
>> 2.25.1
>>
Hi Eugen
On Mon, Dec 06, 2021 at 11:42:25AM +0000, [email protected] wrote:
> On 12/6/21 12:51 PM, Jacopo Mondi wrote:
> > Hello Eugen
> >
> > thanks for addressing all my previous comments, just a few more
> > things and the driver looks good to me
>
> Thanks for reviewing. I tried my best to accomodate your suggestions.
> >
> > On Fri, Nov 12, 2021 at 04:24:47PM +0200, Eugen Hristev wrote:
> >> Microchip CSI2DC (CSI2 Demultiplexer Controller) is a misc bridge device
> >> that converts a byte stream in IDI Synopsys format (coming from a CSI2HOST)
> >> to a pixel stream that can be captured by a sensor controller.
> >>
> >> Signed-off-by: Eugen Hristev <[email protected]>
> >> ---
> >> Changes in v2:
> >> - implement try formats
> >> - added parallel mode
> >> - moved to fwlink endpoints
> >> - many other minor corrections from Jacopo's review
> >>
> >> Changes in this revision:
> >> - addressed comments by Jacopo and Laurent as in this thread:
> >> https://www.spinics.net/lists/linux-media/msg181044.html
> >>
> >> Previous change log :
> >> Changes in v5:
> >> - only in bindings
> >>
> >> Changes in v4:
> >> - now using get_mbus_config ops to get data from the subdevice, like the
> >> virtual channel id, and the clock type.
> >> - now having possibility to select any of the RAW10 data modes
> >> - at completion time, select which formats are also available in the subdevice,
> >> and move to the dynamic list accordingly
> >> - changed the pipeline integration, do not advertise subdev ready at probe time.
> >> wait until completion is done, and then start a workqueue that will register
> >> this device as a subdevice for the next element in pipeline.
> >> - moved the s_power code into a different function called now csi2dc_power
> >> that is called with CONFIG_PM functions. This is also called at completion,
> >> to have the device ready in case CONFIG_PM is not selected on the platform.
> >> - merged try_fmt into set_fmt
> >> - driver cleanup, wrapped lines over 80 characters
> >>
> >> Changes in v2:
> >> - moved driver to platform/atmel
> >> - fixed minor things as per Sakari's review
> >> - still some things from v2 review are not yet addressed, to be followed up
> >>
> >>
> >> drivers/media/platform/Makefile | 1 +
> >> drivers/media/platform/atmel/Kconfig | 15 +
> >> drivers/media/platform/atmel/Makefile | 1 +
> >> .../media/platform/atmel/microchip-csi2dc.c | 797 ++++++++++++++++++
> >> 4 files changed, 814 insertions(+)
> >> create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c
> >>
> >> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> >> index 832357240e89..b18e5f704145 100644
> >> --- a/drivers/media/platform/Makefile
> >> +++ b/drivers/media/platform/Makefile
> >> @@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/
> >> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/
> >> obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel/
> >> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel/
> >> +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += atmel/
> >>
> >> obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32/
> >>
> >> diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig
> >> index dda2f27da317..f83bee373d82 100644
> >> --- a/drivers/media/platform/atmel/Kconfig
> >> +++ b/drivers/media/platform/atmel/Kconfig
> >> @@ -40,3 +40,18 @@ config VIDEO_ATMEL_ISI
> >> help
> >> This module makes the ATMEL Image Sensor Interface available
> >> as a v4l2 device.
> >> +
> >> +config VIDEO_MICROCHIP_CSI2DC
> >> + tristate "Microchip CSI2 Demux Controller"
> >> + depends on VIDEO_V4L2 && COMMON_CLK && OF
> >> + depends on ARCH_AT91 || COMPILE_TEST
> >> + select MEDIA_CONTROLLER
> >> + select VIDEO_V4L2_SUBDEV_API
> >> + select V4L2_FWNODE
> >> + help
> >> + CSI2 Demux Controller driver. CSI2DC is a helper chip
> >> + that converts IDI interface byte stream to a parallel pixel stream.
> >> + It supports various RAW formats as input.
> >> +
> >> + To compile this driver as a module, choose M here: the
> >> + module will be called microchip-csi2dc.
> >> diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
> >> index 46d264ab7948..39f0a7eba702 100644
> >> --- a/drivers/media/platform/atmel/Makefile
> >> +++ b/drivers/media/platform/atmel/Makefile
> >> @@ -6,3 +6,4 @@ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
> >> obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-base.o
> >> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
> >> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
> >> +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
> >> diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/atmel/microchip-csi2dc.c
> >> new file mode 100644
> >> index 000000000000..2b106f6fd5d0
> >> --- /dev/null
> >> +++ b/drivers/media/platform/atmel/microchip-csi2dc.c
> >> @@ -0,0 +1,797 @@
> >> +// SPDX-License-Identifier: GPL-2.0-only
> >> +/*
> >> + * Microchip CSI2 Demux Controller (CSI2DC) driver
> >> + *
> >> + * Copyright (C) 2018 Microchip Technology, Inc.
> >> + *
> >> + * Author: Eugen Hristev <[email protected]>
> >> + *
> >> + */
> >> +
> >> +#include <linux/clk.h>
> >> +#include <linux/mod_devicetable.h>
> >> +#include <linux/module.h>
> >> +#include <linux/of_graph.h>
> >> +#include <linux/platform_device.h>
> >> +#include <linux/pm_runtime.h>
> >> +#include <linux/videodev2.h>
> >> +
> >> +#include <media/v4l2-fwnode.h>
> >> +#include <media/v4l2-subdev.h>
> >> +
> >> +/* Global configuration register */
> >> +#define CSI2DC_GCFG 0x0
> >> +
> >> +/* MIPI sensor pixel clock is free running */
> >> +#define CSI2DC_GCFG_MIPIFRN BIT(0)
> >> +/* GPIO parallel interface selection */
> >> +#define CSI2DC_GCFG_GPIOSEL BIT(1)
> >> +/* Output waveform inter-line minimum delay */
> >> +#define CSI2DC_GCFG_HLC(v) ((v) << 4)
> >> +#define CSI2DC_GCFG_HLC_MASK GENMASK(7, 4)
> >> +/* SAMA7G5 requires a HLC delay of 15 */
> >> +#define SAMA7G5_HLC (15)
> >> +
> >> +/* Global control register */
> >> +#define CSI2DC_GCTLR 0x04
> >> +#define CSI2DC_GCTLR_SWRST BIT(0)
> >> +
> >> +/* Global status register */
> >> +#define CSI2DC_GS 0x08
> >> +
> >> +/* SSP interrupt status register */
> >> +#define CSI2DC_SSPIS 0x28
> >> +/* Pipe update register */
> >> +#define CSI2DC_PU 0xc0
> >> +/* Video pipe attributes update */
> >> +#define CSI2DC_PU_VP BIT(0)
> >> +
> >> +/* Pipe update status register */
> >> +#define CSI2DC_PUS 0xc4
> >> +
> >> +/* Video pipeline enable register */
> >> +#define CSI2DC_VPE 0xf8
> >> +#define CSI2DC_VPE_ENABLE BIT(0)
> >> +
> >> +/* Video pipeline configuration register */
> >> +#define CSI2DC_VPCFG 0xfc
> >> +/* Data type */
> >> +#define CSI2DC_VPCFG_DT(v) ((v) << 0)
> >> +#define CSI2DC_VPCFG_DT_MASK GENMASK(5, 0)
> >> +/* Virtual channel identifier */
> >> +#define CSI2DC_VPCFG_VC(v) ((v) << 6)
> >> +#define CSI2DC_VPCFG_VC_MASK GENMASK(7, 6)
> >> +/* Decompression enable */
> >> +#define CSI2DC_VPCFG_DE BIT(8)
> >> +/* Decoder mode */
> >> +#define CSI2DC_VPCFG_DM(v) ((v) << 9)
> >> +#define CSI2DC_VPCFG_DM_DECODER8TO12 0
> >> +/* Decoder predictor 2 selection */
> >> +#define CSI2DC_VPCFG_DP2 BIT(12)
> >> +/* Recommended memory storage */
> >> +#define CSI2DC_VPCFG_RMS BIT(13)
> >> +/* Post adjustment */
> >> +#define CSI2DC_VPCFG_PA BIT(14)
> >> +
> >> +/* Video pipeline column register */
> >> +#define CSI2DC_VPCOL 0x100
> >> +/* Column number */
> >> +#define CSI2DC_VPCOL_COL(v) ((v) << 0)
> >> +#define CSI2DC_VPCOL_COL_MASK GENMASK(15, 0)
> >> +
> >> +/* Video pipeline row register */
> >> +#define CSI2DC_VPROW 0x104
> >> +/* Row number */
> >> +#define CSI2DC_VPROW_ROW(v) ((v) << 0)
> >> +#define CSI2DC_VPROW_ROW_MASK GENMASK(15, 0)
> >> +
> >> +/* Version register */
> >> +#define CSI2DC_VERSION 0x1fc
> >> +
> >> +/* register read/write helpers */
> >> +#define csi2dc_readl(st, reg) readl_relaxed((st)->base + (reg))
> >> +#define csi2dc_writel(st, reg, val) writel_relaxed((val), \
> >> + (st)->base + (reg))
> >> +
> >> +/* supported RAW data types */
> >> +#define CSI2DC_DT_RAW6 0x28
> >> +#define CSI2DC_DT_RAW7 0x29
> >> +#define CSI2DC_DT_RAW8 0x2a
> >> +#define CSI2DC_DT_RAW10 0x2b
> >> +#define CSI2DC_DT_RAW12 0x2c
> >> +#define CSI2DC_DT_RAW14 0x2d
> >> +
> >> +/*
> >> + * struct csi2dc_format - CSI2DC format type struct
> >> + * @mbus_code: Media bus code for the format
> >> + * @dt: Data type constant for this format
> >> + */
> >> +struct csi2dc_format {
> >> + u32 mbus_code;
> >> + u32 dt;
> >> +};
> >> +
> >> +static const struct csi2dc_format csi2dc_formats[] = {
> >> + {
> >> + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
> >> + .dt = CSI2DC_DT_RAW8,
> >> + }, {
> >> + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> >> + .dt = CSI2DC_DT_RAW8,
> >> + }, {
> >> + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
> >> + .dt = CSI2DC_DT_RAW8,
> >> + }, {
> >> + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
> >> + .dt = CSI2DC_DT_RAW8,
> >> + }, {
> >> + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
> >> + .dt = CSI2DC_DT_RAW10,
> >> + }, {
> >> + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> >> + .dt = CSI2DC_DT_RAW10,
> >> + }, {
> >> + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
> >> + .dt = CSI2DC_DT_RAW10,
> >> + }, {
> >> + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
> >> + .dt = CSI2DC_DT_RAW10,
> >> + }, {
> >> + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
> >
> > Is this intentionally not associated with a CSI-2 DT code ?
>
> Yes. It is not a raw format.
> I do not have a sensor that could stream YUYV8_2X8 with CSI-2 .
> I tested this mode with parallel sensor, and in this case, CSI-2 DT
> value does not make sense and it's not used.
> The idea would be that when and if I get my hands on a sensor that could
> actually do this with CSI2, I would update the driver here (if it works.
> I am not sure it actually does ...)
I understand, but the first user of your driver that connects a
YUV-capable sensor will be very disappointed, as debugging the fact
that the DT identifier is silently set to 0 will be quite painful (as
if I'm not mistaken fields not initialized in a designated initializer
list are set to 0).
As the DT identifiers are defined by the CSI-2 specification and will
not change, I think you can safely add it here.
Thanks
j
> >
> >> + },
> >> +};
> >> +
> >> +enum mipi_csi_pads {
> >> + CSI2DC_PAD_SINK = 0,
> >> + CSI2DC_PAD_SOURCE = 1,
> >> + CSI2DC_PADS_NUM = 2,
> >> +};
> >> +
> >> +/*
> >> + * struct csi2dc_device - CSI2DC device driver data/config struct
> >> + * @base: Register map base address
> >> + * @csi2dc_sd: v4l2 subdevice for the csi2dc device
> >> + * This is the subdevice that the csi2dc device itself
> >> + * registers in v4l2 subsystem
> >> + * @dev: struct device for this csi2dc device
> >> + * @pclk: Peripheral clock reference
> >> + * Input clock that clocks the hardware block internal
> >> + * logic
> >> + * @scck: Sensor Controller clock reference
> >> + * Input clock that is used to generate the pixel clock
> >> + * @format: Current saved format used in g/s fmt
> >> + * @cur_fmt: Current state format
> >> + * @try_fmt: Try format that is being tried
> >> + * @pads: Media entity pads for the csi2dc subdevice
> >> + * @clk_gated: Whether the clock is gated or free running
> >> + * @video_pipe: Whether video pipeline is configured
> >> + * @parallel_mode: The underlying subdevice is connected on a parallel bus
> >> + * @vc: Current set virtual channel
> >> + * @notifier: Async notifier that is used to bound the underlying
> >> + * subdevice to the csi2dc subdevice
> >> + * @input_sd: Reference to the underlying subdevice bound to the
> >> + * csi2dc subdevice
> >> + * @remote_pad: Pad number of the underlying subdevice that is linked
> >> + * to the csi2dc subdevice sink pad.
> >> + */
> >> +struct csi2dc_device {
> >> + void __iomem *base;
> >> + struct v4l2_subdev csi2dc_sd;
> >> + struct device *dev;
> >> + struct clk *pclk;
> >> + struct clk *scck;
> >> +
> >> + struct v4l2_mbus_framefmt format;
> >> +
> >> + const struct csi2dc_format *cur_fmt;
> >> + const struct csi2dc_format *try_fmt;
> >> +
> >> + struct media_pad pads[CSI2DC_PADS_NUM];
> >> +
> >> + bool clk_gated;
> >> + bool video_pipe;
> >> + bool parallel_mode;
> >> + u32 vc;
> >> +
> >> + struct v4l2_async_notifier notifier;
> >> +
> >> + struct v4l2_subdev *input_sd;
> >> +
> >> + u32 remote_pad;
> >> +};
> >> +
> >> +static inline struct csi2dc_device *
> >> +csi2dc_sd_to_csi2dc_device(struct v4l2_subdev *csi2dc_sd)
> >> +{
> >> + return container_of(csi2dc_sd, struct csi2dc_device, csi2dc_sd);
> >> +}
> >> +
> >> +static int csi2dc_enum_mbus_code(struct v4l2_subdev *csi2dc_sd,
> >> + struct v4l2_subdev_state *sd_state,
> >> + struct v4l2_subdev_mbus_code_enum *code)
> >> +{
> >> + if (code->index >= ARRAY_SIZE(csi2dc_formats))
> >> + return -EINVAL;
> >> +
> >> + code->code = csi2dc_formats[code->index].mbus_code;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd,
> >> + struct v4l2_subdev_state *sd_state,
> >> + struct v4l2_subdev_format *format)
> >> +{
> >> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
> >> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
> >> +
> >> + if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> >> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
> >> + format->pad);
> >> + format->format = *v4l2_try_fmt;
> >> +
> >> + return 0;
> >> + }
> >> +
> >> + format->format = csi2dc->format;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd,
> >> + struct v4l2_subdev_state *sd_state,
> >> + struct v4l2_subdev_format *req_fmt)
> >> +{
> >> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
> >> + const struct csi2dc_format *fmt, *try_fmt = NULL;
> >> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
> >> + unsigned int i;
> >> +
> >> + for (i = 0; i < ARRAY_SIZE(csi2dc_formats); i++) {
> >> + fmt = &csi2dc_formats[i];
> >> + if (req_fmt->format.code == fmt->mbus_code)
> >> + try_fmt = fmt;
> >> + fmt++;
> >> + }
> >> +
> >> + /* in case we could not find the desired format, default to something */
> >> + if (!try_fmt) {
> >> + try_fmt = &csi2dc_formats[0];
> >> +
> >> + dev_dbg(csi2dc->dev,
> >> + "CSI2DC unsupported format 0x%x, defaulting to 0x%x\n",
> >> + req_fmt->format.code, csi2dc_formats[0].mbus_code);
> >> + }
> >> +
> >> + req_fmt->format.code = try_fmt->mbus_code;
> >> + req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
> >> + req_fmt->format.field = V4L2_FIELD_NONE;
> >> +
> >> + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> >> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
> >> + req_fmt->pad);
> >> + *v4l2_try_fmt = req_fmt->format;
> >> + /* Trying on the pad sink makes the source sink change too */
> >> + if (req_fmt->pad == CSI2DC_PAD_SINK) {
> >
> > s/source sink/source pad/
> >
> >> + v4l2_try_fmt =
> >> + v4l2_subdev_get_try_format(csi2dc_sd,
> >> + sd_state,
> >> + CSI2DC_PAD_SOURCE);
> >> + *v4l2_try_fmt = req_fmt->format;
> >
> > Shouldn't this be the same for the active format then ? If this device
> > operates by transporting the same exact format from the sink to the
> > source, shouldn't you disable setting a format on the source, and
> > always propagate to the sink ? I would have done so, but maybe
> > Sakari/Laurent could tell you if that's fine.
>
> Hm. I will try. To see if it works and v4l2-compliance is happy. ( I did
> this propagation for try format because it wasn't )
>
> >
> >> + }
> >> + /* if we are just trying, we are done */
> >> + return 0;
> >> + }
> >> +
> >> + /* save the format for later requests */
> >> + csi2dc->format = req_fmt->format;
> >> +
> >> + /* update config */
> >> + csi2dc->cur_fmt = try_fmt;
> >> +
> >> + dev_dbg(csi2dc->dev, "new format set: 0x%x @%dx%d\n",
> >> + csi2dc->format.code, csi2dc->format.width,
> >> + csi2dc->format.height);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int csi2dc_power(struct csi2dc_device *csi2dc, int on)
> >> +{
> >> + int ret = 0;
> >> +
> >> + if (on) {
> >> + ret = clk_prepare_enable(csi2dc->pclk);
> >> + if (ret) {
> >> + dev_err(csi2dc->dev, "failed to enable pclk:%d\n", ret);
> >> + return ret;
> >> + }
> >> +
> >> + ret = clk_prepare_enable(csi2dc->scck);
> >> + if (ret) {
> >> + dev_err(csi2dc->dev, "failed to enable scck:%d\n", ret);
> >> + clk_disable_unprepare(csi2dc->pclk);
> >> + return ret;
> >> + }
> >> +
> >> + /* if powering up, deassert reset line */
> >> + csi2dc_writel(csi2dc, CSI2DC_GCTLR, CSI2DC_GCTLR_SWRST);
> >> + } else {
> >> + /* if powering down, assert reset line */
> >> + csi2dc_writel(csi2dc, CSI2DC_GCTLR, 0);
> >> +
> >> + clk_disable_unprepare(csi2dc->scck);
> >> + clk_disable_unprepare(csi2dc->pclk);
> >> + }
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +static int csi2dc_get_mbus_config(struct csi2dc_device *csi2dc)
> >> +{
> >> + struct v4l2_mbus_config mbus_config = { 0 };
> >> + int ret;
> >> +
> >> + ret = v4l2_subdev_call(csi2dc->input_sd, pad, get_mbus_config,
> >> + csi2dc->remote_pad, &mbus_config);
> >> + if (ret == -ENOIOCTLCMD) {
> >> + dev_dbg(csi2dc->dev,
> >> + "no remote mbus configuration available\n");
> >> + goto csi2dc_get_mbus_config_defaults;
> >> + }
> >> +
> >> + if (ret) {
> >> + dev_err(csi2dc->dev,
> >> + "failed to get remote mbus configuration\n");
> >> + goto csi2dc_get_mbus_config_defaults;
> >> + }
> >> +
> >> + if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_0)
> >> + csi2dc->vc = 0;
> >> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_1)
> >> + csi2dc->vc = 1;
> >> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_2)
> >> + csi2dc->vc = 2;
> >> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_3)
> >> + csi2dc->vc = 3;
> >> +
> >> + dev_dbg(csi2dc->dev, "subdev sending on channel %d\n", csi2dc->vc);
> >> +
> >> + csi2dc->clk_gated = mbus_config.flags &
> >> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
> >> +
> >> + dev_dbg(csi2dc->dev, "mbus_config: %s clock\n",
> >> + csi2dc->clk_gated ? "gated" : "free running");
> >> +
> >> + return 0;
> >> +
> >> +csi2dc_get_mbus_config_defaults:
> >> + csi2dc->vc = 0; /* Virtual ID 0 by default */
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static void csi2dc_vp_update(struct csi2dc_device *csi2dc)
> >> +{
> >> + u32 vp, gcfg;
> >> +
> >> + if (!csi2dc->video_pipe) {
> >> + dev_err(csi2dc->dev, "video pipeline unavailable\n");
> >> + return;
> >> + }
> >> +
> >> + if (csi2dc->parallel_mode) {
> >> + /* In parallel mode, GPIO parallel interface must be selected */
> >> + gcfg = csi2dc_readl(csi2dc, CSI2DC_GCFG);
> >> + gcfg |= CSI2DC_GCFG_GPIOSEL;
> >> + csi2dc_writel(csi2dc, CSI2DC_GCFG, gcfg);
> >> + return;
> >> + }
> >> +
> >> + /* serial video pipeline */
> >> +
> >> + csi2dc_writel(csi2dc, CSI2DC_GCFG,
> >> + (SAMA7G5_HLC & CSI2DC_GCFG_HLC_MASK) |
> >> + (csi2dc->clk_gated ? 0 : CSI2DC_GCFG_MIPIFRN));
> >> +
> >> + csi2dc_writel(csi2dc, CSI2DC_VPCOL,
> >> + CSI2DC_VPCOL_COL(0xfff) & CSI2DC_VPCOL_COL_MASK);
> >> + csi2dc_writel(csi2dc, CSI2DC_VPROW,
> >> + CSI2DC_VPROW_ROW(0xfff) & CSI2DC_VPROW_ROW_MASK);
> >> +
> >> + vp = CSI2DC_VPCFG_DT(csi2dc->cur_fmt->dt) & CSI2DC_VPCFG_DT_MASK;
> >> + vp |= CSI2DC_VPCFG_VC(csi2dc->vc) & CSI2DC_VPCFG_VC_MASK;
> >> + vp &= ~CSI2DC_VPCFG_DE;
> >> + vp |= CSI2DC_VPCFG_DM(CSI2DC_VPCFG_DM_DECODER8TO12);
> >> + vp &= ~CSI2DC_VPCFG_DP2;
> >> + vp &= ~CSI2DC_VPCFG_RMS;
> >> + vp |= CSI2DC_VPCFG_PA;
> >> +
> >> + csi2dc_writel(csi2dc, CSI2DC_VPCFG, vp);
> >> + csi2dc_writel(csi2dc, CSI2DC_VPE, CSI2DC_VPE_ENABLE);
> >> + csi2dc_writel(csi2dc, CSI2DC_PU, CSI2DC_PU_VP);
> >> +}
> >> +
> >> +static int csi2dc_s_stream(struct v4l2_subdev *csi2dc_sd, int enable)
> >> +{
> >> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
> >> + int ret;
> >> +
> >> + if (enable) {
> >> + ret = pm_runtime_resume_and_get(csi2dc->dev);
> >> + if (ret < 0)
> >> + return ret;
> >> +
> >> + csi2dc_get_mbus_config(csi2dc);
> >> +
> >> + csi2dc_vp_update(csi2dc);
> >> +
> >> + return v4l2_subdev_call(csi2dc->input_sd, video, s_stream,
> >> + true);
> >> + }
> >> + /* stop streaming scenario */
> >> + ret = v4l2_subdev_call(csi2dc->input_sd, video, s_stream, false);
> >> +
> >> + pm_runtime_put_sync(csi2dc->dev);
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +static int csi2dc_init_cfg(struct v4l2_subdev *csi2dc_sd,
> >> + struct v4l2_subdev_state *sd_state)
> >> +{
> >> + struct v4l2_mbus_framefmt *v4l2_try_fmt =
> >> + v4l2_subdev_get_try_format(csi2dc_sd, sd_state, 0);
> >> +
> >> + v4l2_try_fmt->height = 480;
> >> + v4l2_try_fmt->width = 640;
> >> + v4l2_try_fmt->code = csi2dc_formats[0].mbus_code;
> >> + v4l2_try_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> >> + v4l2_try_fmt->field = V4L2_FIELD_NONE;
> >> + v4l2_try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >> + v4l2_try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> >> + v4l2_try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static const struct v4l2_subdev_pad_ops csi2dc_pad_ops = {
> >> + .enum_mbus_code = csi2dc_enum_mbus_code,
> >> + .set_fmt = csi2dc_set_fmt,
> >> + .get_fmt = csi2dc_get_fmt,
> >> + .init_cfg = csi2dc_init_cfg,
> >> +};
> >> +
> >> +static const struct v4l2_subdev_video_ops csi2dc_video_ops = {
> >> + .s_stream = csi2dc_s_stream,
> >> +};
> >> +
> >> +static const struct v4l2_subdev_ops csi2dc_subdev_ops = {
> >> + .pad = &csi2dc_pad_ops,
> >> + .video = &csi2dc_video_ops,
> >> +};
> >> +
> >> +static int csi2dc_async_bound(struct v4l2_async_notifier *notifier,
> >> + struct v4l2_subdev *subdev,
> >> + struct v4l2_async_subdev *asd)
> >> +{
> >> + struct csi2dc_device *csi2dc = container_of(notifier,
> >> + struct csi2dc_device, notifier);
> >> + int pad;
> >> + int ret;
> >> +
> >> + csi2dc->input_sd = subdev;
> >> +
> >> + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
> >> + MEDIA_PAD_FL_SOURCE);
> >> + if (pad < 0) {
> >> + dev_err(csi2dc->dev, "Failed to find pad for %s\n",
> >> + subdev->name);
> >> + return pad;
> >> + }
> >> +
> >> + csi2dc->remote_pad = pad;
> >> +
> >> + ret = media_create_pad_link(&csi2dc->input_sd->entity,
> >> + csi2dc->remote_pad,
> >> + &csi2dc->csi2dc_sd.entity, 0,
> >> + MEDIA_LNK_FL_ENABLED);
> >> + if (ret) {
> >> + dev_err(csi2dc->dev,
> >> + "Failed to create pad link: %s to %s\n",
> >> + csi2dc->input_sd->entity.name,
> >> + csi2dc->csi2dc_sd.entity.name);
> >> + return ret;
> >> + }
> >> +
> >> + dev_dbg(csi2dc->dev, "link with %s pad: %d\n",
> >> + csi2dc->input_sd->name, csi2dc->remote_pad);
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +static const struct v4l2_async_notifier_operations csi2dc_async_ops = {
> >> + .bound = csi2dc_async_bound,
> >> +};
> >> +
> >> +static int csi2dc_prepare_notifier(struct csi2dc_device *csi2dc,
> >> + struct fwnode_handle *input_fwnode)
> >> +{
> >> + struct v4l2_async_subdev *asd;
> >> + int ret = 0;
> >> +
> >> + v4l2_async_nf_init(&csi2dc->notifier);
> >> +
> >> + asd = v4l2_async_nf_add_fwnode_remote(&csi2dc->notifier,
> >> + input_fwnode,
> >> + struct v4l2_async_subdev);
> >> +
> >> + fwnode_handle_put(input_fwnode);
> >> +
> >> + if (IS_ERR(asd)) {
> >> + ret = PTR_ERR(asd);
> >> + dev_err(csi2dc->dev,
> >> + "failed to add async notifier for node %pOF: %d\n",
> >> + to_of_node(input_fwnode), ret);
> >> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> >> + return ret;
> >> + }
> >> +
> >> + csi2dc->notifier.ops = &csi2dc_async_ops;
> >> +
> >> + ret = v4l2_async_subdev_nf_register(&csi2dc->csi2dc_sd,
> >> + &csi2dc->notifier);
> >
> > Drop this blank line
> >
> >> +
> >> + if (ret) {
> >> + dev_err(csi2dc->dev, "fail to register async notifier: %d\n",
> >> + ret);
> >> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> >> + }
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +static int csi2dc_of_parse(struct csi2dc_device *csi2dc,
> >> + struct device_node *of_node)
> >> +{
> >> + struct fwnode_handle *input_fwnode, *output_fwnode;
> >> + struct v4l2_fwnode_endpoint input_endpoint = { 0 },
> >> + output_endpoint = { 0 };
> >> + int ret;
> >> +
> >> + input_fwnode = fwnode_graph_get_next_endpoint(of_fwnode_handle(of_node),
> >> + NULL);
> >> +
> >> + if (!input_fwnode) {
> >> + dev_err(csi2dc->dev,
> >> + "missing port node at %pOF, input node is mandatory.\n",
> >> + of_node);
> >> + return -EINVAL;
> >> + }
> >> +
> >> + ret = v4l2_fwnode_endpoint_parse(input_fwnode, &input_endpoint);
> >> +
> >
> > Drop this blank line
> >
> >> + if (ret) {
> >> + dev_err(csi2dc->dev, "endpoint not defined at %pOF\n", of_node);
> >> + goto csi2dc_of_parse_err;
> >> + }
> >> +
> >> + if (input_endpoint.bus_type == V4L2_MBUS_PARALLEL ||
> >> + input_endpoint.bus_type == V4L2_MBUS_BT656) {
> >> + csi2dc->parallel_mode = true;
> >> + dev_dbg(csi2dc->dev,
> >> + "subdevice connected on parallel interface\n");
> >> + }
> >> +
> >> + if (input_endpoint.bus_type == V4L2_MBUS_CSI2_DPHY) {
> >> + csi2dc->clk_gated = input_endpoint.bus.mipi_csi2.flags &
> >> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
> >> + dev_dbg(csi2dc->dev,
> >> + "subdevice connected on serial interface\n");
> >> + dev_dbg(csi2dc->dev, "DT: %s clock\n",
> >> + csi2dc->clk_gated ? "gated" : "free running");
> >> + }
> >> +
> >> + output_fwnode = fwnode_graph_get_next_endpoint
> >> + (of_fwnode_handle(of_node), input_fwnode);
> >> +
> >> + if (output_fwnode)
> >> + ret = v4l2_fwnode_endpoint_parse(output_fwnode,
> >> + &output_endpoint);
> >> +
> >> + fwnode_handle_put(output_fwnode);
> >> +
> >> + if (!output_fwnode || ret) {
> >> + dev_info(csi2dc->dev,
> >> + "missing output node at %pOF, data pipe available only.\n",
> >> + of_node);
> >> + } else {
> >> + if (output_endpoint.bus_type != V4L2_MBUS_PARALLEL &&
> >> + output_endpoint.bus_type != V4L2_MBUS_BT656) {
> >> + dev_err(csi2dc->dev,
> >> + "output port must be parallel/bt656.\n");
> >> + ret = -EINVAL;
> >> + goto csi2dc_of_parse_err;
> >> + }
> >> +
> >> + csi2dc->video_pipe = true;
> >> +
> >> + dev_dbg(csi2dc->dev,
> >> + "block %pOF [%d.%d]->[%d.%d] video pipeline\n",
> >> + of_node, input_endpoint.base.port,
> >> + input_endpoint.base.id, output_endpoint.base.port,
> >> + output_endpoint.base.id);
> >> + }
> >> +
> >> + /* prepare async notifier for subdevice completion */
> >> + return csi2dc_prepare_notifier(csi2dc, input_fwnode);
> >> +
> >> +csi2dc_of_parse_err:
> >> + fwnode_handle_put(input_fwnode);
> >> + return ret;
> >> +}
> >> +
> >> +static void csi2dc_default_format(struct csi2dc_device *csi2dc)
> >> +{
> >> + csi2dc->cur_fmt = &csi2dc_formats[0];
> >> +
> >> + csi2dc->format.height = 480;
> >> + csi2dc->format.width = 640;
> >> + csi2dc->format.code = csi2dc_formats[0].mbus_code;
> >> + csi2dc->format.colorspace = V4L2_COLORSPACE_SRGB;
> >> + csi2dc->format.field = V4L2_FIELD_NONE;
> >> + csi2dc->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >> + csi2dc->format.quantization = V4L2_QUANTIZATION_DEFAULT;
> >> + csi2dc->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
> >> +}
> >> +
> >> +static int csi2dc_probe(struct platform_device *pdev)
> >> +{
> >> + struct device *dev = &pdev->dev;
> >> + struct csi2dc_device *csi2dc;
> >> + int ret = 0;
> >> + u32 ver;
> >> +
> >> + csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL);
> >> + if (!csi2dc)
> >> + return -ENOMEM;
> >> +
> >> + csi2dc->dev = dev;
> >> +
> >> + csi2dc->base = devm_platform_ioremap_resource(pdev, 0);
> >> + if (IS_ERR(csi2dc->base)) {
> >> + dev_err(dev, "base address not set\n");
> >> + return PTR_ERR(csi2dc->base);
> >> + }
> >> +
> >> + csi2dc->pclk = devm_clk_get(dev, "pclk");
> >> + if (IS_ERR(csi2dc->pclk)) {
> >> + ret = PTR_ERR(csi2dc->pclk);
> >> + dev_err(dev, "failed to get pclk: %d\n", ret);
> >> + return ret;
> >> + }
> >> +
> >> + csi2dc->scck = devm_clk_get(dev, "scck");
> >> + if (IS_ERR(csi2dc->scck)) {
> >> + ret = PTR_ERR(csi2dc->scck);
> >> + dev_err(dev, "failed to get scck: %d\n", ret);
> >> + return ret;
> >> + }
> >> +
> >> + v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops);
> >> +
> >> + csi2dc->csi2dc_sd.owner = THIS_MODULE;
> >> + csi2dc->csi2dc_sd.dev = dev;
> >> + snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name),
> >> + "csi2dc");
> >> +
> >> + csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> >> + csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> >> +
> >> + platform_set_drvdata(pdev, csi2dc);
> >> +
> >> + ret = csi2dc_of_parse(csi2dc, dev->of_node);
> >> + if (ret)
> >> + goto csi2dc_probe_cleanup_entity;
> >> +
> >> + csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> >> + if (csi2dc->video_pipe)
> >> + csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> >> +
> >> + ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity,
> >> + csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1,
> >> + csi2dc->pads);
> >> + if (ret < 0) {
> >> + dev_err(dev, "media entity init failed\n");
> >> + goto csi2dc_probe_cleanup_notifier;
> >> + }
> >> +
> >> + csi2dc_default_format(csi2dc);
> >> +
> >> + /* turn power on to validate capabilities */
> >> + ret = csi2dc_power(csi2dc, true);
> >> + if (ret < 0)
> >> + goto csi2dc_probe_cleanup_notifier;
> >> +
> >> + pm_runtime_set_active(dev);
> >> + pm_runtime_enable(dev);
> >> + ver = csi2dc_readl(csi2dc, CSI2DC_VERSION);
> >
> > Shouldn't you verify that the version is the expected one, or that, at
> > least the device can be accessed ?
>
> If the read from the register worked, the device can be accessed. If the
> device won't respond to this, we will get a bus fault or a freeze.
> And printing the version number can help all the time by inspecting the
> bootlog to see if something valid was printed.
> Version changes from product to product but seeing a bootlog later will
> make us easily understand which product was it, and if the version was
> read correctly
>
> >
> >> + pm_request_idle(dev);
> >
> > I'm not well versed when it comes to runtime_pm but I can read that
> >
> > Documentation/power/runtime_pm.rst- `int pm_request_idle(struct device *dev);`
> > Documentation/power/runtime_pm.rst- - submit a request to execute the subsystem-level idle callback for the
> > Documentation/power/runtime_pm.rst- device (the request is represented by a work item in pm_wq); returns 0 on
> > Documentation/power/runtime_pm.rst- success or error code if the request has not been queued up
> >
> > And looking below you have
> >
> > +static const struct dev_pm_ops csi2dc_dev_pm_ops = {
> > + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
> > +};
> >
> > Which expands to
> >
> > include/linux/pm.h: SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \
> >
> > so it seems to me you've no idle func defined, hence you above call
> > has no effect so you're device stays powered on after your above call
> > to
> >
> > ret = csi2dc_power(csi2dc, true);
> >
> > Also trying to suspend using runtime_pm while having powered on by
> > calling the resume function directly would leave the pm status
> > unbalanced ?
> >
> > I would raher call pm_runtime_resume() and
> > pm_runtime_suspend() or call the power routine directly, and later
> > enable pm_runtime
> >
> > csi2dc_power(true)
> > readl();
> > csi2fd_power(false);
> >
> > pm_runtime_set_active();
> > pm_runtime_enable();
> >
> > v4l2_async_register();
> >
> > Again, not well versed in runtime_pm, so don't assume the above makes
> > sense :)
>
> I am not versed in runtime_pm either, I will try to remove this
> 'pm_request_idle' to see what happens at the first
> pm_runtime_resume_and_get, and print messages in power on/power off to
> see how and when they are called.
>
>
>
> >
> >> +
> >> + /*
> >> + * we must register the subdev after PM runtime has been requested,
> >> + * otherwise we might bound immediately and request pm_runtime_resume
> >> + * before runtime_enable.
> >> + */
> >> + ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd);
> >> + if (ret) {
> >> + dev_err(csi2dc->dev, "failed to register the subdevice\n");
> >> + goto csi2dc_probe_cleanup_notifier;
> >> + }
> >> +
> >> + dev_info(dev, "Microchip CSI2DC version %x\n", ver);
> >> +
> >> + return 0;
> >> +
> >> +csi2dc_probe_cleanup_notifier:
> >> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> >> +csi2dc_probe_cleanup_entity:
> >> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
> >
> > I wasn't honestly expecting this
> >
> > /**
> > * media_entity_cleanup() - free resources associated with an entity
> > *
> > * @entity: entity where the pads belong
> > *
> > * This function must be called during the cleanup phase after unregistering
> > * the entity (currently, it does nothing).
> > */
> > #if IS_ENABLED(CONFIG_MEDIA_CONTROLLER)
> > static inline void media_entity_cleanup(struct media_entity *entity) {}
> >
> > :)
>
> I saw it as well. But I thought that who knows, in the future it might
> do something, so it's better to have it in the driver rather than not
> have it at all.
>
>
> Thanks again for reviewing,
> Eugen
>
> >
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +static int csi2dc_remove(struct platform_device *pdev)
> >> +{
> >> + struct csi2dc_device *csi2dc = platform_get_drvdata(pdev);
> >> +
> >> + pm_runtime_disable(&pdev->dev);
> >> +
> >> + v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd);
> >> + v4l2_async_nf_unregister(&csi2dc->notifier);
> >> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> >> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int __maybe_unused csi2dc_runtime_suspend(struct device *dev)
> >> +{
> >> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
> >> +
> >> + return csi2dc_power(csi2dc, false);
> >> +}
> >> +
> >> +static int __maybe_unused csi2dc_runtime_resume(struct device *dev)
> >> +{
> >> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
> >> +
> >> + return csi2dc_power(csi2dc, true);
> >> +}
> >> +
> >> +static const struct dev_pm_ops csi2dc_dev_pm_ops = {
> >> + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
> >> +};
> >> +
> >> +static const struct of_device_id csi2dc_of_match[] = {
> >> + { .compatible = "microchip,sama7g5-csi2dc" },
> >> + { }
> >> +};
> >> +
> >> +MODULE_DEVICE_TABLE(of, csi2dc_of_match);
> >> +
> >> +static struct platform_driver csi2dc_driver = {
> >> + .probe = csi2dc_probe,
> >> + .remove = csi2dc_remove,
> >> + .driver = {
> >> + .name = "microchip-csi2dc",
> >> + .pm = &csi2dc_dev_pm_ops,
> >> + .of_match_table = of_match_ptr(csi2dc_of_match),
> >> + },
> >> +};
> >> +
> >> +module_platform_driver(csi2dc_driver);
> >> +
> >> +MODULE_AUTHOR("Eugen Hristev <[email protected]>");
> >> +MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver");
> >> +MODULE_LICENSE("GPL v2");
> >> --
> >> 2.25.1
> >>
>
Hi Eugen
On Fri, Nov 12, 2021 at 04:24:56PM +0200, Eugen Hristev wrote:
> The bytesperline field of the pixfmt should be only for the first plane
> in case of planar formats like YUV420 or YUV422.
> The bytesperline is used by the driver to compute the framesize.
> We have to report a different bpp (bytes per pixel) to v4l2 in bytesperline
> than the actual bpp.
> For example for YUV420, the real bpp is 12, but the first plane has only
> 8 bpp. Thus we report a bytesperline 8*width instead of 12*width.
> However, for real framezise we have to compute 12*width*height.
> Hence added a new variable to hold this information and to correctly
> compute the frame size.
Using single planar API for multiplanar format is really painful :(
>
> Signed-off-by: Eugen Hristev <[email protected]>
> ---
> drivers/media/platform/atmel/atmel-isc-base.c | 19 +++++++++++++++++--
> drivers/media/platform/atmel/atmel-isc.h | 4 ++++
> 2 files changed, 21 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
> index 2cb8446ff90c..d0542b97a391 100644
> --- a/drivers/media/platform/atmel/atmel-isc-base.c
> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
> @@ -654,6 +654,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 8;
> + isc->try_config.bpp_v4l2 = 8;
> break;
> case V4L2_PIX_FMT_SBGGR10:
> case V4L2_PIX_FMT_SGBRG10:
> @@ -663,6 +664,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 16;
> + isc->try_config.bpp_v4l2 = 16;
> break;
> case V4L2_PIX_FMT_SBGGR12:
> case V4L2_PIX_FMT_SGBRG12:
> @@ -672,24 +674,28 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 16;
> + isc->try_config.bpp_v4l2 = 16;
> break;
> case V4L2_PIX_FMT_RGB565:
> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_RGB565;
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 16;
> + isc->try_config.bpp_v4l2 = 16;
> break;
> case V4L2_PIX_FMT_ARGB444:
> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB444;
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 16;
> + isc->try_config.bpp_v4l2 = 16;
> break;
> case V4L2_PIX_FMT_ARGB555:
> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB555;
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 16;
> + isc->try_config.bpp_v4l2 = 16;
> break;
> case V4L2_PIX_FMT_ABGR32:
> case V4L2_PIX_FMT_XBGR32:
> @@ -697,42 +703,49 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 32;
> + isc->try_config.bpp_v4l2 = 32;
> break;
> case V4L2_PIX_FMT_YUV420:
> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC;
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC420P;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR;
> isc->try_config.bpp = 12;
> + isc->try_config.bpp_v4l2 = 8; /* only first plane */
> break;
> case V4L2_PIX_FMT_YUV422P:
> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC;
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC422P;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR;
> isc->try_config.bpp = 16;
> + isc->try_config.bpp_v4l2 = 8; /* only first plane */
This could have also been described with by adding to the format a
subsampling factor for the CbCr plane and fixing the bpp to 8 as it
describes the first plane. In this way you could avoid setting
bpp_v4l2 for all formats that do not need it.
Something like a simple subsampling multiplier would do for planar YUV
formats, more complicated schemes would probably needed for other
formats.
420:
bpp = 8
subsampling = 12
bytesperline = w * bpp >> 3
sizeimage = w * h * subsampling >> 3
422:
bpp = 8
subsampling = 16
bytesperline = w * bpp >> 3
sizeimage = w * h * subsampling >> 3
444:
bpp = 8
subsampling = 24
bytesperline = w * bpp >> 3
sizeimage = w * h * subsampling >> 3
You would anyway need to set subsampling = 8 in other formats or
either
sizeimage = w * h * (subsampling ? subsampling : 8) >> 3
which is not super nice ;)
As you wish though, this is driver code, so anything goes
Reviewed-by: Jacopo Mondi <[email protected]>
Thanks
j
> break;
> case V4L2_PIX_FMT_YUYV:
> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_YUYV;
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 16;
> + isc->try_config.bpp_v4l2 = 16;
> break;
> case V4L2_PIX_FMT_UYVY:
> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_UYVY;
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 16;
> + isc->try_config.bpp_v4l2 = 16;
> break;
> case V4L2_PIX_FMT_VYUY:
> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_VYUY;
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 16;
> + isc->try_config.bpp_v4l2 = 16;
> break;
> case V4L2_PIX_FMT_GREY:
> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY8;
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 8;
> + isc->try_config.bpp_v4l2 = 8;
> break;
> case V4L2_PIX_FMT_Y16:
> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY10 | ISC_RLP_CFG_LSH;
> @@ -742,6 +755,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
> isc->try_config.bpp = 16;
> + isc->try_config.bpp_v4l2 = 16;
> break;
> default:
> return -EINVAL;
> @@ -990,8 +1004,9 @@ static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f,
> pixfmt->height = isc->max_height;
>
> pixfmt->field = V4L2_FIELD_NONE;
> - pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp) >> 3;
> - pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height;
> + pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3;
> + pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) *
> + pixfmt->height;
>
> if (code)
> *code = mbus_code;
> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
> index 32448ccfc636..07fa6dbf8460 100644
> --- a/drivers/media/platform/atmel/atmel-isc.h
> +++ b/drivers/media/platform/atmel/atmel-isc.h
> @@ -102,6 +102,9 @@ struct isc_format {
> configuration.
> * @fourcc: Fourcc code for this format.
> * @bpp: Bytes per pixel in the current format.
> + * @bpp_v4l2: Bytes per pixel in the current format, for v4l2.
> + This differs from 'bpp' in the sense that in planar
> + formats, it refers only to the first plane.
> * @rlp_cfg_mode: Configuration of the RLP (rounding, limiting packaging)
> * @dcfg_imode: Configuration of the input of the DMA module
> * @dctrl_dview: Configuration of the output of the DMA module
> @@ -112,6 +115,7 @@ struct fmt_config {
>
> u32 fourcc;
> u8 bpp;
> + u8 bpp_v4l2;
>
> u32 rlp_cfg_mode;
> u32 dcfg_imode;
> --
> 2.25.1
>
On 12/6/21 6:38 PM, Jacopo Mondi wrote:
> Hi Eugen
>
> On Fri, Nov 12, 2021 at 04:24:56PM +0200, Eugen Hristev wrote:
>> The bytesperline field of the pixfmt should be only for the first plane
>> in case of planar formats like YUV420 or YUV422.
>> The bytesperline is used by the driver to compute the framesize.
>> We have to report a different bpp (bytes per pixel) to v4l2 in bytesperline
>> than the actual bpp.
>> For example for YUV420, the real bpp is 12, but the first plane has only
>> 8 bpp. Thus we report a bytesperline 8*width instead of 12*width.
>> However, for real framezise we have to compute 12*width*height.
>> Hence added a new variable to hold this information and to correctly
>> compute the frame size.
>
> Using single planar API for multiplanar format is really painful :(
>
>>
>> Signed-off-by: Eugen Hristev <[email protected]>
>> ---
>> drivers/media/platform/atmel/atmel-isc-base.c | 19 +++++++++++++++++--
>> drivers/media/platform/atmel/atmel-isc.h | 4 ++++
>> 2 files changed, 21 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c
>> index 2cb8446ff90c..d0542b97a391 100644
>> --- a/drivers/media/platform/atmel/atmel-isc-base.c
>> +++ b/drivers/media/platform/atmel/atmel-isc-base.c
>> @@ -654,6 +654,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 8;
>> + isc->try_config.bpp_v4l2 = 8;
>> break;
>> case V4L2_PIX_FMT_SBGGR10:
>> case V4L2_PIX_FMT_SGBRG10:
>> @@ -663,6 +664,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 16;
>> + isc->try_config.bpp_v4l2 = 16;
>> break;
>> case V4L2_PIX_FMT_SBGGR12:
>> case V4L2_PIX_FMT_SGBRG12:
>> @@ -672,24 +674,28 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 16;
>> + isc->try_config.bpp_v4l2 = 16;
>> break;
>> case V4L2_PIX_FMT_RGB565:
>> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_RGB565;
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 16;
>> + isc->try_config.bpp_v4l2 = 16;
>> break;
>> case V4L2_PIX_FMT_ARGB444:
>> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB444;
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 16;
>> + isc->try_config.bpp_v4l2 = 16;
>> break;
>> case V4L2_PIX_FMT_ARGB555:
>> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB555;
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 16;
>> + isc->try_config.bpp_v4l2 = 16;
>> break;
>> case V4L2_PIX_FMT_ABGR32:
>> case V4L2_PIX_FMT_XBGR32:
>> @@ -697,42 +703,49 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 32;
>> + isc->try_config.bpp_v4l2 = 32;
>> break;
>> case V4L2_PIX_FMT_YUV420:
>> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC;
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC420P;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR;
>> isc->try_config.bpp = 12;
>> + isc->try_config.bpp_v4l2 = 8; /* only first plane */
>> break;
>> case V4L2_PIX_FMT_YUV422P:
>> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC;
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC422P;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR;
>> isc->try_config.bpp = 16;
>> + isc->try_config.bpp_v4l2 = 8; /* only first plane */
>
> This could have also been described with by adding to the format a
> subsampling factor for the CbCr plane and fixing the bpp to 8 as it
> describes the first plane. In this way you could avoid setting
> bpp_v4l2 for all formats that do not need it.
>
> Something like a simple subsampling multiplier would do for planar YUV
> formats, more complicated schemes would probably needed for other
> formats.
>
> 420:
> bpp = 8
> subsampling = 12
> bytesperline = w * bpp >> 3
> sizeimage = w * h * subsampling >> 3
>
> 422:
> bpp = 8
> subsampling = 16
> bytesperline = w * bpp >> 3
> sizeimage = w * h * subsampling >> 3
>
> 444:
> bpp = 8
> subsampling = 24
> bytesperline = w * bpp >> 3
> sizeimage = w * h * subsampling >> 3
>
> You would anyway need to set subsampling = 8 in other formats or
> either
> sizeimage = w * h * (subsampling ? subsampling : 8) >> 3
>
> which is not super nice ;)
>
> As you wish though, this is driver code, so anything goes
Hi Jacopo,
Yes, this also works, to have a different variable and compute the
bytesperline according to it.
I also considered this, but it was a bit more difficult to understand
later (what does subsampling 16 and subsampling 24 means ?)
I thought it would be easier to have a simple bpp which is the real bpp
of the format, and the bpp for v4l2 which requires the
bytesperpixel_in_first_plane_only .
So as you agree with this solution, I would keep it as-is
Thanks for reviewing,
>
> Reviewed-by: Jacopo Mondi <[email protected]>
>
> Thanks
> j
>
>
>
>
>> break;
>> case V4L2_PIX_FMT_YUYV:
>> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_YUYV;
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 16;
>> + isc->try_config.bpp_v4l2 = 16;
>> break;
>> case V4L2_PIX_FMT_UYVY:
>> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_UYVY;
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 16;
>> + isc->try_config.bpp_v4l2 = 16;
>> break;
>> case V4L2_PIX_FMT_VYUY:
>> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_VYUY;
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 16;
>> + isc->try_config.bpp_v4l2 = 16;
>> break;
>> case V4L2_PIX_FMT_GREY:
>> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY8;
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 8;
>> + isc->try_config.bpp_v4l2 = 8;
>> break;
>> case V4L2_PIX_FMT_Y16:
>> isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY10 | ISC_RLP_CFG_LSH;
>> @@ -742,6 +755,7 @@ static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump)
>> isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16;
>> isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED;
>> isc->try_config.bpp = 16;
>> + isc->try_config.bpp_v4l2 = 16;
>> break;
>> default:
>> return -EINVAL;
>> @@ -990,8 +1004,9 @@ static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f,
>> pixfmt->height = isc->max_height;
>>
>> pixfmt->field = V4L2_FIELD_NONE;
>> - pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp) >> 3;
>> - pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height;
>> + pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3;
>> + pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) *
>> + pixfmt->height;
>>
>> if (code)
>> *code = mbus_code;
>> diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h
>> index 32448ccfc636..07fa6dbf8460 100644
>> --- a/drivers/media/platform/atmel/atmel-isc.h
>> +++ b/drivers/media/platform/atmel/atmel-isc.h
>> @@ -102,6 +102,9 @@ struct isc_format {
>> configuration.
>> * @fourcc: Fourcc code for this format.
>> * @bpp: Bytes per pixel in the current format.
>> + * @bpp_v4l2: Bytes per pixel in the current format, for v4l2.
>> + This differs from 'bpp' in the sense that in planar
>> + formats, it refers only to the first plane.
>> * @rlp_cfg_mode: Configuration of the RLP (rounding, limiting packaging)
>> * @dcfg_imode: Configuration of the input of the DMA module
>> * @dctrl_dview: Configuration of the output of the DMA module
>> @@ -112,6 +115,7 @@ struct fmt_config {
>>
>> u32 fourcc;
>> u8 bpp;
>> + u8 bpp_v4l2;
>>
>> u32 rlp_cfg_mode;
>> u32 dcfg_imode;
>> --
>> 2.25.1
>>
On 12/6/21 2:06 PM, Jacopo Mondi wrote:
> Hi Eugen
>
> On Mon, Dec 06, 2021 at 11:42:25AM +0000, [email protected] wrote:
>> On 12/6/21 12:51 PM, Jacopo Mondi wrote:
>>> Hello Eugen
>>>
>>> thanks for addressing all my previous comments, just a few more
>>> things and the driver looks good to me
>>
>> Thanks for reviewing. I tried my best to accomodate your suggestions.
>>>
>>> On Fri, Nov 12, 2021 at 04:24:47PM +0200, Eugen Hristev wrote:
>>>> Microchip CSI2DC (CSI2 Demultiplexer Controller) is a misc bridge device
>>>> that converts a byte stream in IDI Synopsys format (coming from a CSI2HOST)
>>>> to a pixel stream that can be captured by a sensor controller.
>>>>
>>>> Signed-off-by: Eugen Hristev <[email protected]>
>>>> ---
>>>> Changes in v2:
>>>> - implement try formats
>>>> - added parallel mode
>>>> - moved to fwlink endpoints
>>>> - many other minor corrections from Jacopo's review
>>>>
>>>> Changes in this revision:
>>>> - addressed comments by Jacopo and Laurent as in this thread:
>>>> https://www.spinics.net/lists/linux-media/msg181044.html
>>>>
>>>> Previous change log :
>>>> Changes in v5:
>>>> - only in bindings
>>>>
>>>> Changes in v4:
>>>> - now using get_mbus_config ops to get data from the subdevice, like the
>>>> virtual channel id, and the clock type.
>>>> - now having possibility to select any of the RAW10 data modes
>>>> - at completion time, select which formats are also available in the subdevice,
>>>> and move to the dynamic list accordingly
>>>> - changed the pipeline integration, do not advertise subdev ready at probe time.
>>>> wait until completion is done, and then start a workqueue that will register
>>>> this device as a subdevice for the next element in pipeline.
>>>> - moved the s_power code into a different function called now csi2dc_power
>>>> that is called with CONFIG_PM functions. This is also called at completion,
>>>> to have the device ready in case CONFIG_PM is not selected on the platform.
>>>> - merged try_fmt into set_fmt
>>>> - driver cleanup, wrapped lines over 80 characters
>>>>
>>>> Changes in v2:
>>>> - moved driver to platform/atmel
>>>> - fixed minor things as per Sakari's review
>>>> - still some things from v2 review are not yet addressed, to be followed up
>>>>
>>>>
>>>> drivers/media/platform/Makefile | 1 +
>>>> drivers/media/platform/atmel/Kconfig | 15 +
>>>> drivers/media/platform/atmel/Makefile | 1 +
>>>> .../media/platform/atmel/microchip-csi2dc.c | 797 ++++++++++++++++++
>>>> 4 files changed, 814 insertions(+)
>>>> create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c
>>>>
>>>> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
>>>> index 832357240e89..b18e5f704145 100644
>>>> --- a/drivers/media/platform/Makefile
>>>> +++ b/drivers/media/platform/Makefile
>>>> @@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/
>>>> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/
>>>> obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel/
>>>> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel/
>>>> +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += atmel/
>>>>
>>>> obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32/
>>>>
>>>> diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig
>>>> index dda2f27da317..f83bee373d82 100644
>>>> --- a/drivers/media/platform/atmel/Kconfig
>>>> +++ b/drivers/media/platform/atmel/Kconfig
>>>> @@ -40,3 +40,18 @@ config VIDEO_ATMEL_ISI
>>>> help
>>>> This module makes the ATMEL Image Sensor Interface available
>>>> as a v4l2 device.
>>>> +
>>>> +config VIDEO_MICROCHIP_CSI2DC
>>>> + tristate "Microchip CSI2 Demux Controller"
>>>> + depends on VIDEO_V4L2 && COMMON_CLK && OF
>>>> + depends on ARCH_AT91 || COMPILE_TEST
>>>> + select MEDIA_CONTROLLER
>>>> + select VIDEO_V4L2_SUBDEV_API
>>>> + select V4L2_FWNODE
>>>> + help
>>>> + CSI2 Demux Controller driver. CSI2DC is a helper chip
>>>> + that converts IDI interface byte stream to a parallel pixel stream.
>>>> + It supports various RAW formats as input.
>>>> +
>>>> + To compile this driver as a module, choose M here: the
>>>> + module will be called microchip-csi2dc.
>>>> diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile
>>>> index 46d264ab7948..39f0a7eba702 100644
>>>> --- a/drivers/media/platform/atmel/Makefile
>>>> +++ b/drivers/media/platform/atmel/Makefile
>>>> @@ -6,3 +6,4 @@ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o
>>>> obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-base.o
>>>> obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o
>>>> obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o
>>>> +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o
>>>> diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/atmel/microchip-csi2dc.c
>>>> new file mode 100644
>>>> index 000000000000..2b106f6fd5d0
>>>> --- /dev/null
>>>> +++ b/drivers/media/platform/atmel/microchip-csi2dc.c
>>>> @@ -0,0 +1,797 @@
>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>> +/*
>>>> + * Microchip CSI2 Demux Controller (CSI2DC) driver
>>>> + *
>>>> + * Copyright (C) 2018 Microchip Technology, Inc.
>>>> + *
>>>> + * Author: Eugen Hristev <[email protected]>
>>>> + *
>>>> + */
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/mod_devicetable.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of_graph.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +#include <linux/videodev2.h>
>>>> +
>>>> +#include <media/v4l2-fwnode.h>
>>>> +#include <media/v4l2-subdev.h>
>>>> +
>>>> +/* Global configuration register */
>>>> +#define CSI2DC_GCFG 0x0
>>>> +
>>>> +/* MIPI sensor pixel clock is free running */
>>>> +#define CSI2DC_GCFG_MIPIFRN BIT(0)
>>>> +/* GPIO parallel interface selection */
>>>> +#define CSI2DC_GCFG_GPIOSEL BIT(1)
>>>> +/* Output waveform inter-line minimum delay */
>>>> +#define CSI2DC_GCFG_HLC(v) ((v) << 4)
>>>> +#define CSI2DC_GCFG_HLC_MASK GENMASK(7, 4)
>>>> +/* SAMA7G5 requires a HLC delay of 15 */
>>>> +#define SAMA7G5_HLC (15)
>>>> +
>>>> +/* Global control register */
>>>> +#define CSI2DC_GCTLR 0x04
>>>> +#define CSI2DC_GCTLR_SWRST BIT(0)
>>>> +
>>>> +/* Global status register */
>>>> +#define CSI2DC_GS 0x08
>>>> +
>>>> +/* SSP interrupt status register */
>>>> +#define CSI2DC_SSPIS 0x28
>>>> +/* Pipe update register */
>>>> +#define CSI2DC_PU 0xc0
>>>> +/* Video pipe attributes update */
>>>> +#define CSI2DC_PU_VP BIT(0)
>>>> +
>>>> +/* Pipe update status register */
>>>> +#define CSI2DC_PUS 0xc4
>>>> +
>>>> +/* Video pipeline enable register */
>>>> +#define CSI2DC_VPE 0xf8
>>>> +#define CSI2DC_VPE_ENABLE BIT(0)
>>>> +
>>>> +/* Video pipeline configuration register */
>>>> +#define CSI2DC_VPCFG 0xfc
>>>> +/* Data type */
>>>> +#define CSI2DC_VPCFG_DT(v) ((v) << 0)
>>>> +#define CSI2DC_VPCFG_DT_MASK GENMASK(5, 0)
>>>> +/* Virtual channel identifier */
>>>> +#define CSI2DC_VPCFG_VC(v) ((v) << 6)
>>>> +#define CSI2DC_VPCFG_VC_MASK GENMASK(7, 6)
>>>> +/* Decompression enable */
>>>> +#define CSI2DC_VPCFG_DE BIT(8)
>>>> +/* Decoder mode */
>>>> +#define CSI2DC_VPCFG_DM(v) ((v) << 9)
>>>> +#define CSI2DC_VPCFG_DM_DECODER8TO12 0
>>>> +/* Decoder predictor 2 selection */
>>>> +#define CSI2DC_VPCFG_DP2 BIT(12)
>>>> +/* Recommended memory storage */
>>>> +#define CSI2DC_VPCFG_RMS BIT(13)
>>>> +/* Post adjustment */
>>>> +#define CSI2DC_VPCFG_PA BIT(14)
>>>> +
>>>> +/* Video pipeline column register */
>>>> +#define CSI2DC_VPCOL 0x100
>>>> +/* Column number */
>>>> +#define CSI2DC_VPCOL_COL(v) ((v) << 0)
>>>> +#define CSI2DC_VPCOL_COL_MASK GENMASK(15, 0)
>>>> +
>>>> +/* Video pipeline row register */
>>>> +#define CSI2DC_VPROW 0x104
>>>> +/* Row number */
>>>> +#define CSI2DC_VPROW_ROW(v) ((v) << 0)
>>>> +#define CSI2DC_VPROW_ROW_MASK GENMASK(15, 0)
>>>> +
>>>> +/* Version register */
>>>> +#define CSI2DC_VERSION 0x1fc
>>>> +
>>>> +/* register read/write helpers */
>>>> +#define csi2dc_readl(st, reg) readl_relaxed((st)->base + (reg))
>>>> +#define csi2dc_writel(st, reg, val) writel_relaxed((val), \
>>>> + (st)->base + (reg))
>>>> +
>>>> +/* supported RAW data types */
>>>> +#define CSI2DC_DT_RAW6 0x28
>>>> +#define CSI2DC_DT_RAW7 0x29
>>>> +#define CSI2DC_DT_RAW8 0x2a
>>>> +#define CSI2DC_DT_RAW10 0x2b
>>>> +#define CSI2DC_DT_RAW12 0x2c
>>>> +#define CSI2DC_DT_RAW14 0x2d
>>>> +
>>>> +/*
>>>> + * struct csi2dc_format - CSI2DC format type struct
>>>> + * @mbus_code: Media bus code for the format
>>>> + * @dt: Data type constant for this format
>>>> + */
>>>> +struct csi2dc_format {
>>>> + u32 mbus_code;
>>>> + u32 dt;
>>>> +};
>>>> +
>>>> +static const struct csi2dc_format csi2dc_formats[] = {
>>>> + {
>>>> + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
>>>> + .dt = CSI2DC_DT_RAW8,
>>>> + }, {
>>>> + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
>>>> + .dt = CSI2DC_DT_RAW8,
>>>> + }, {
>>>> + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
>>>> + .dt = CSI2DC_DT_RAW8,
>>>> + }, {
>>>> + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
>>>> + .dt = CSI2DC_DT_RAW8,
>>>> + }, {
>>>> + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
>>>> + .dt = CSI2DC_DT_RAW10,
>>>> + }, {
>>>> + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
>>>> + .dt = CSI2DC_DT_RAW10,
>>>> + }, {
>>>> + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
>>>> + .dt = CSI2DC_DT_RAW10,
>>>> + }, {
>>>> + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
>>>> + .dt = CSI2DC_DT_RAW10,
>>>> + }, {
>>>> + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
>>>
>>> Is this intentionally not associated with a CSI-2 DT code ?
>>
>> Yes. It is not a raw format.
>> I do not have a sensor that could stream YUYV8_2X8 with CSI-2 .
>> I tested this mode with parallel sensor, and in this case, CSI-2 DT
>> value does not make sense and it's not used.
>> The idea would be that when and if I get my hands on a sensor that could
>> actually do this with CSI2, I would update the driver here (if it works.
>> I am not sure it actually does ...)
>
> I understand, but the first user of your driver that connects a
> YUV-capable sensor will be very disappointed, as debugging the fact
> that the DT identifier is silently set to 0 will be quite painful (as
> if I'm not mistaken fields not initialized in a designated initializer
> list are set to 0).
>
> As the DT identifiers are defined by the CSI-2 specification and will
> not change, I think you can safely add it here.
>
> Thanks
> j
>
>>>
>>>> + },
>>>> +};
>>>> +
>>>> +enum mipi_csi_pads {
>>>> + CSI2DC_PAD_SINK = 0,
>>>> + CSI2DC_PAD_SOURCE = 1,
>>>> + CSI2DC_PADS_NUM = 2,
>>>> +};
>>>> +
>>>> +/*
>>>> + * struct csi2dc_device - CSI2DC device driver data/config struct
>>>> + * @base: Register map base address
>>>> + * @csi2dc_sd: v4l2 subdevice for the csi2dc device
>>>> + * This is the subdevice that the csi2dc device itself
>>>> + * registers in v4l2 subsystem
>>>> + * @dev: struct device for this csi2dc device
>>>> + * @pclk: Peripheral clock reference
>>>> + * Input clock that clocks the hardware block internal
>>>> + * logic
>>>> + * @scck: Sensor Controller clock reference
>>>> + * Input clock that is used to generate the pixel clock
>>>> + * @format: Current saved format used in g/s fmt
>>>> + * @cur_fmt: Current state format
>>>> + * @try_fmt: Try format that is being tried
>>>> + * @pads: Media entity pads for the csi2dc subdevice
>>>> + * @clk_gated: Whether the clock is gated or free running
>>>> + * @video_pipe: Whether video pipeline is configured
>>>> + * @parallel_mode: The underlying subdevice is connected on a parallel bus
>>>> + * @vc: Current set virtual channel
>>>> + * @notifier: Async notifier that is used to bound the underlying
>>>> + * subdevice to the csi2dc subdevice
>>>> + * @input_sd: Reference to the underlying subdevice bound to the
>>>> + * csi2dc subdevice
>>>> + * @remote_pad: Pad number of the underlying subdevice that is linked
>>>> + * to the csi2dc subdevice sink pad.
>>>> + */
>>>> +struct csi2dc_device {
>>>> + void __iomem *base;
>>>> + struct v4l2_subdev csi2dc_sd;
>>>> + struct device *dev;
>>>> + struct clk *pclk;
>>>> + struct clk *scck;
>>>> +
>>>> + struct v4l2_mbus_framefmt format;
>>>> +
>>>> + const struct csi2dc_format *cur_fmt;
>>>> + const struct csi2dc_format *try_fmt;
>>>> +
>>>> + struct media_pad pads[CSI2DC_PADS_NUM];
>>>> +
>>>> + bool clk_gated;
>>>> + bool video_pipe;
>>>> + bool parallel_mode;
>>>> + u32 vc;
>>>> +
>>>> + struct v4l2_async_notifier notifier;
>>>> +
>>>> + struct v4l2_subdev *input_sd;
>>>> +
>>>> + u32 remote_pad;
>>>> +};
>>>> +
>>>> +static inline struct csi2dc_device *
>>>> +csi2dc_sd_to_csi2dc_device(struct v4l2_subdev *csi2dc_sd)
>>>> +{
>>>> + return container_of(csi2dc_sd, struct csi2dc_device, csi2dc_sd);
>>>> +}
>>>> +
>>>> +static int csi2dc_enum_mbus_code(struct v4l2_subdev *csi2dc_sd,
>>>> + struct v4l2_subdev_state *sd_state,
>>>> + struct v4l2_subdev_mbus_code_enum *code)
>>>> +{
>>>> + if (code->index >= ARRAY_SIZE(csi2dc_formats))
>>>> + return -EINVAL;
>>>> +
>>>> + code->code = csi2dc_formats[code->index].mbus_code;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd,
>>>> + struct v4l2_subdev_state *sd_state,
>>>> + struct v4l2_subdev_format *format)
>>>> +{
>>>> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
>>>> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
>>>> +
>>>> + if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
>>>> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
>>>> + format->pad);
>>>> + format->format = *v4l2_try_fmt;
>>>> +
>>>> + return 0;
>>>> + }
>>>> +
>>>> + format->format = csi2dc->format;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd,
>>>> + struct v4l2_subdev_state *sd_state,
>>>> + struct v4l2_subdev_format *req_fmt)
>>>> +{
>>>> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
>>>> + const struct csi2dc_format *fmt, *try_fmt = NULL;
>>>> + struct v4l2_mbus_framefmt *v4l2_try_fmt;
>>>> + unsigned int i;
>>>> +
>>>> + for (i = 0; i < ARRAY_SIZE(csi2dc_formats); i++) {
>>>> + fmt = &csi2dc_formats[i];
>>>> + if (req_fmt->format.code == fmt->mbus_code)
>>>> + try_fmt = fmt;
>>>> + fmt++;
>>>> + }
>>>> +
>>>> + /* in case we could not find the desired format, default to something */
>>>> + if (!try_fmt) {
>>>> + try_fmt = &csi2dc_formats[0];
>>>> +
>>>> + dev_dbg(csi2dc->dev,
>>>> + "CSI2DC unsupported format 0x%x, defaulting to 0x%x\n",
>>>> + req_fmt->format.code, csi2dc_formats[0].mbus_code);
>>>> + }
>>>> +
>>>> + req_fmt->format.code = try_fmt->mbus_code;
>>>> + req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
>>>> + req_fmt->format.field = V4L2_FIELD_NONE;
>>>> +
>>>> + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
>>>> + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state,
>>>> + req_fmt->pad);
>>>> + *v4l2_try_fmt = req_fmt->format;
>>>> + /* Trying on the pad sink makes the source sink change too */
>>>> + if (req_fmt->pad == CSI2DC_PAD_SINK) {
>>>
>>> s/source sink/source pad/
>>>
>>>> + v4l2_try_fmt =
>>>> + v4l2_subdev_get_try_format(csi2dc_sd,
>>>> + sd_state,
>>>> + CSI2DC_PAD_SOURCE);
>>>> + *v4l2_try_fmt = req_fmt->format;
>>>
>>> Shouldn't this be the same for the active format then ? If this device
>>> operates by transporting the same exact format from the sink to the
>>> source, shouldn't you disable setting a format on the source, and
>>> always propagate to the sink ? I would have done so, but maybe
>>> Sakari/Laurent could tell you if that's fine.
>>
>> Hm. I will try. To see if it works and v4l2-compliance is happy. ( I did
>> this propagation for try format because it wasn't )
>>
>>>
>>>> + }
>>>> + /* if we are just trying, we are done */
>>>> + return 0;
>>>> + }
>>>> +
>>>> + /* save the format for later requests */
>>>> + csi2dc->format = req_fmt->format;
>>>> +
>>>> + /* update config */
>>>> + csi2dc->cur_fmt = try_fmt;
>>>> +
>>>> + dev_dbg(csi2dc->dev, "new format set: 0x%x @%dx%d\n",
>>>> + csi2dc->format.code, csi2dc->format.width,
>>>> + csi2dc->format.height);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int csi2dc_power(struct csi2dc_device *csi2dc, int on)
>>>> +{
>>>> + int ret = 0;
>>>> +
>>>> + if (on) {
>>>> + ret = clk_prepare_enable(csi2dc->pclk);
>>>> + if (ret) {
>>>> + dev_err(csi2dc->dev, "failed to enable pclk:%d\n", ret);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + ret = clk_prepare_enable(csi2dc->scck);
>>>> + if (ret) {
>>>> + dev_err(csi2dc->dev, "failed to enable scck:%d\n", ret);
>>>> + clk_disable_unprepare(csi2dc->pclk);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + /* if powering up, deassert reset line */
>>>> + csi2dc_writel(csi2dc, CSI2DC_GCTLR, CSI2DC_GCTLR_SWRST);
>>>> + } else {
>>>> + /* if powering down, assert reset line */
>>>> + csi2dc_writel(csi2dc, CSI2DC_GCTLR, 0);
>>>> +
>>>> + clk_disable_unprepare(csi2dc->scck);
>>>> + clk_disable_unprepare(csi2dc->pclk);
>>>> + }
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int csi2dc_get_mbus_config(struct csi2dc_device *csi2dc)
>>>> +{
>>>> + struct v4l2_mbus_config mbus_config = { 0 };
>>>> + int ret;
>>>> +
>>>> + ret = v4l2_subdev_call(csi2dc->input_sd, pad, get_mbus_config,
>>>> + csi2dc->remote_pad, &mbus_config);
>>>> + if (ret == -ENOIOCTLCMD) {
>>>> + dev_dbg(csi2dc->dev,
>>>> + "no remote mbus configuration available\n");
>>>> + goto csi2dc_get_mbus_config_defaults;
>>>> + }
>>>> +
>>>> + if (ret) {
>>>> + dev_err(csi2dc->dev,
>>>> + "failed to get remote mbus configuration\n");
>>>> + goto csi2dc_get_mbus_config_defaults;
>>>> + }
>>>> +
>>>> + if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_0)
>>>> + csi2dc->vc = 0;
>>>> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_1)
>>>> + csi2dc->vc = 1;
>>>> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_2)
>>>> + csi2dc->vc = 2;
>>>> + else if (mbus_config.flags & V4L2_MBUS_CSI2_CHANNEL_3)
>>>> + csi2dc->vc = 3;
>>>> +
>>>> + dev_dbg(csi2dc->dev, "subdev sending on channel %d\n", csi2dc->vc);
>>>> +
>>>> + csi2dc->clk_gated = mbus_config.flags &
>>>> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
>>>> +
>>>> + dev_dbg(csi2dc->dev, "mbus_config: %s clock\n",
>>>> + csi2dc->clk_gated ? "gated" : "free running");
>>>> +
>>>> + return 0;
>>>> +
>>>> +csi2dc_get_mbus_config_defaults:
>>>> + csi2dc->vc = 0; /* Virtual ID 0 by default */
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static void csi2dc_vp_update(struct csi2dc_device *csi2dc)
>>>> +{
>>>> + u32 vp, gcfg;
>>>> +
>>>> + if (!csi2dc->video_pipe) {
>>>> + dev_err(csi2dc->dev, "video pipeline unavailable\n");
>>>> + return;
>>>> + }
>>>> +
>>>> + if (csi2dc->parallel_mode) {
>>>> + /* In parallel mode, GPIO parallel interface must be selected */
>>>> + gcfg = csi2dc_readl(csi2dc, CSI2DC_GCFG);
>>>> + gcfg |= CSI2DC_GCFG_GPIOSEL;
>>>> + csi2dc_writel(csi2dc, CSI2DC_GCFG, gcfg);
>>>> + return;
>>>> + }
>>>> +
>>>> + /* serial video pipeline */
>>>> +
>>>> + csi2dc_writel(csi2dc, CSI2DC_GCFG,
>>>> + (SAMA7G5_HLC & CSI2DC_GCFG_HLC_MASK) |
>>>> + (csi2dc->clk_gated ? 0 : CSI2DC_GCFG_MIPIFRN));
>>>> +
>>>> + csi2dc_writel(csi2dc, CSI2DC_VPCOL,
>>>> + CSI2DC_VPCOL_COL(0xfff) & CSI2DC_VPCOL_COL_MASK);
>>>> + csi2dc_writel(csi2dc, CSI2DC_VPROW,
>>>> + CSI2DC_VPROW_ROW(0xfff) & CSI2DC_VPROW_ROW_MASK);
>>>> +
>>>> + vp = CSI2DC_VPCFG_DT(csi2dc->cur_fmt->dt) & CSI2DC_VPCFG_DT_MASK;
>>>> + vp |= CSI2DC_VPCFG_VC(csi2dc->vc) & CSI2DC_VPCFG_VC_MASK;
>>>> + vp &= ~CSI2DC_VPCFG_DE;
>>>> + vp |= CSI2DC_VPCFG_DM(CSI2DC_VPCFG_DM_DECODER8TO12);
>>>> + vp &= ~CSI2DC_VPCFG_DP2;
>>>> + vp &= ~CSI2DC_VPCFG_RMS;
>>>> + vp |= CSI2DC_VPCFG_PA;
>>>> +
>>>> + csi2dc_writel(csi2dc, CSI2DC_VPCFG, vp);
>>>> + csi2dc_writel(csi2dc, CSI2DC_VPE, CSI2DC_VPE_ENABLE);
>>>> + csi2dc_writel(csi2dc, CSI2DC_PU, CSI2DC_PU_VP);
>>>> +}
>>>> +
>>>> +static int csi2dc_s_stream(struct v4l2_subdev *csi2dc_sd, int enable)
>>>> +{
>>>> + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd);
>>>> + int ret;
>>>> +
>>>> + if (enable) {
>>>> + ret = pm_runtime_resume_and_get(csi2dc->dev);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + csi2dc_get_mbus_config(csi2dc);
>>>> +
>>>> + csi2dc_vp_update(csi2dc);
>>>> +
>>>> + return v4l2_subdev_call(csi2dc->input_sd, video, s_stream,
>>>> + true);
>>>> + }
>>>> + /* stop streaming scenario */
>>>> + ret = v4l2_subdev_call(csi2dc->input_sd, video, s_stream, false);
>>>> +
>>>> + pm_runtime_put_sync(csi2dc->dev);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int csi2dc_init_cfg(struct v4l2_subdev *csi2dc_sd,
>>>> + struct v4l2_subdev_state *sd_state)
>>>> +{
>>>> + struct v4l2_mbus_framefmt *v4l2_try_fmt =
>>>> + v4l2_subdev_get_try_format(csi2dc_sd, sd_state, 0);
>>>> +
>>>> + v4l2_try_fmt->height = 480;
>>>> + v4l2_try_fmt->width = 640;
>>>> + v4l2_try_fmt->code = csi2dc_formats[0].mbus_code;
>>>> + v4l2_try_fmt->colorspace = V4L2_COLORSPACE_SRGB;
>>>> + v4l2_try_fmt->field = V4L2_FIELD_NONE;
>>>> + v4l2_try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>> + v4l2_try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
>>>> + v4l2_try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct v4l2_subdev_pad_ops csi2dc_pad_ops = {
>>>> + .enum_mbus_code = csi2dc_enum_mbus_code,
>>>> + .set_fmt = csi2dc_set_fmt,
>>>> + .get_fmt = csi2dc_get_fmt,
>>>> + .init_cfg = csi2dc_init_cfg,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_video_ops csi2dc_video_ops = {
>>>> + .s_stream = csi2dc_s_stream,
>>>> +};
>>>> +
>>>> +static const struct v4l2_subdev_ops csi2dc_subdev_ops = {
>>>> + .pad = &csi2dc_pad_ops,
>>>> + .video = &csi2dc_video_ops,
>>>> +};
>>>> +
>>>> +static int csi2dc_async_bound(struct v4l2_async_notifier *notifier,
>>>> + struct v4l2_subdev *subdev,
>>>> + struct v4l2_async_subdev *asd)
>>>> +{
>>>> + struct csi2dc_device *csi2dc = container_of(notifier,
>>>> + struct csi2dc_device, notifier);
>>>> + int pad;
>>>> + int ret;
>>>> +
>>>> + csi2dc->input_sd = subdev;
>>>> +
>>>> + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode,
>>>> + MEDIA_PAD_FL_SOURCE);
>>>> + if (pad < 0) {
>>>> + dev_err(csi2dc->dev, "Failed to find pad for %s\n",
>>>> + subdev->name);
>>>> + return pad;
>>>> + }
>>>> +
>>>> + csi2dc->remote_pad = pad;
>>>> +
>>>> + ret = media_create_pad_link(&csi2dc->input_sd->entity,
>>>> + csi2dc->remote_pad,
>>>> + &csi2dc->csi2dc_sd.entity, 0,
>>>> + MEDIA_LNK_FL_ENABLED);
>>>> + if (ret) {
>>>> + dev_err(csi2dc->dev,
>>>> + "Failed to create pad link: %s to %s\n",
>>>> + csi2dc->input_sd->entity.name,
>>>> + csi2dc->csi2dc_sd.entity.name);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + dev_dbg(csi2dc->dev, "link with %s pad: %d\n",
>>>> + csi2dc->input_sd->name, csi2dc->remote_pad);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static const struct v4l2_async_notifier_operations csi2dc_async_ops = {
>>>> + .bound = csi2dc_async_bound,
>>>> +};
>>>> +
>>>> +static int csi2dc_prepare_notifier(struct csi2dc_device *csi2dc,
>>>> + struct fwnode_handle *input_fwnode)
>>>> +{
>>>> + struct v4l2_async_subdev *asd;
>>>> + int ret = 0;
>>>> +
>>>> + v4l2_async_nf_init(&csi2dc->notifier);
>>>> +
>>>> + asd = v4l2_async_nf_add_fwnode_remote(&csi2dc->notifier,
>>>> + input_fwnode,
>>>> + struct v4l2_async_subdev);
>>>> +
>>>> + fwnode_handle_put(input_fwnode);
>>>> +
>>>> + if (IS_ERR(asd)) {
>>>> + ret = PTR_ERR(asd);
>>>> + dev_err(csi2dc->dev,
>>>> + "failed to add async notifier for node %pOF: %d\n",
>>>> + to_of_node(input_fwnode), ret);
>>>> + v4l2_async_nf_cleanup(&csi2dc->notifier);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + csi2dc->notifier.ops = &csi2dc_async_ops;
>>>> +
>>>> + ret = v4l2_async_subdev_nf_register(&csi2dc->csi2dc_sd,
>>>> + &csi2dc->notifier);
>>>
>>> Drop this blank line
>>>
>>>> +
>>>> + if (ret) {
>>>> + dev_err(csi2dc->dev, "fail to register async notifier: %d\n",
>>>> + ret);
>>>> + v4l2_async_nf_cleanup(&csi2dc->notifier);
>>>> + }
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int csi2dc_of_parse(struct csi2dc_device *csi2dc,
>>>> + struct device_node *of_node)
>>>> +{
>>>> + struct fwnode_handle *input_fwnode, *output_fwnode;
>>>> + struct v4l2_fwnode_endpoint input_endpoint = { 0 },
>>>> + output_endpoint = { 0 };
>>>> + int ret;
>>>> +
>>>> + input_fwnode = fwnode_graph_get_next_endpoint(of_fwnode_handle(of_node),
>>>> + NULL);
>>>> +
>>>> + if (!input_fwnode) {
>>>> + dev_err(csi2dc->dev,
>>>> + "missing port node at %pOF, input node is mandatory.\n",
>>>> + of_node);
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + ret = v4l2_fwnode_endpoint_parse(input_fwnode, &input_endpoint);
>>>> +
>>>
>>> Drop this blank line
>>>
>>>> + if (ret) {
>>>> + dev_err(csi2dc->dev, "endpoint not defined at %pOF\n", of_node);
>>>> + goto csi2dc_of_parse_err;
>>>> + }
>>>> +
>>>> + if (input_endpoint.bus_type == V4L2_MBUS_PARALLEL ||
>>>> + input_endpoint.bus_type == V4L2_MBUS_BT656) {
>>>> + csi2dc->parallel_mode = true;
>>>> + dev_dbg(csi2dc->dev,
>>>> + "subdevice connected on parallel interface\n");
>>>> + }
>>>> +
>>>> + if (input_endpoint.bus_type == V4L2_MBUS_CSI2_DPHY) {
>>>> + csi2dc->clk_gated = input_endpoint.bus.mipi_csi2.flags &
>>>> + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
>>>> + dev_dbg(csi2dc->dev,
>>>> + "subdevice connected on serial interface\n");
>>>> + dev_dbg(csi2dc->dev, "DT: %s clock\n",
>>>> + csi2dc->clk_gated ? "gated" : "free running");
>>>> + }
>>>> +
>>>> + output_fwnode = fwnode_graph_get_next_endpoint
>>>> + (of_fwnode_handle(of_node), input_fwnode);
>>>> +
>>>> + if (output_fwnode)
>>>> + ret = v4l2_fwnode_endpoint_parse(output_fwnode,
>>>> + &output_endpoint);
>>>> +
>>>> + fwnode_handle_put(output_fwnode);
>>>> +
>>>> + if (!output_fwnode || ret) {
>>>> + dev_info(csi2dc->dev,
>>>> + "missing output node at %pOF, data pipe available only.\n",
>>>> + of_node);
>>>> + } else {
>>>> + if (output_endpoint.bus_type != V4L2_MBUS_PARALLEL &&
>>>> + output_endpoint.bus_type != V4L2_MBUS_BT656) {
>>>> + dev_err(csi2dc->dev,
>>>> + "output port must be parallel/bt656.\n");
>>>> + ret = -EINVAL;
>>>> + goto csi2dc_of_parse_err;
>>>> + }
>>>> +
>>>> + csi2dc->video_pipe = true;
>>>> +
>>>> + dev_dbg(csi2dc->dev,
>>>> + "block %pOF [%d.%d]->[%d.%d] video pipeline\n",
>>>> + of_node, input_endpoint.base.port,
>>>> + input_endpoint.base.id, output_endpoint.base.port,
>>>> + output_endpoint.base.id);
>>>> + }
>>>> +
>>>> + /* prepare async notifier for subdevice completion */
>>>> + return csi2dc_prepare_notifier(csi2dc, input_fwnode);
>>>> +
>>>> +csi2dc_of_parse_err:
>>>> + fwnode_handle_put(input_fwnode);
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static void csi2dc_default_format(struct csi2dc_device *csi2dc)
>>>> +{
>>>> + csi2dc->cur_fmt = &csi2dc_formats[0];
>>>> +
>>>> + csi2dc->format.height = 480;
>>>> + csi2dc->format.width = 640;
>>>> + csi2dc->format.code = csi2dc_formats[0].mbus_code;
>>>> + csi2dc->format.colorspace = V4L2_COLORSPACE_SRGB;
>>>> + csi2dc->format.field = V4L2_FIELD_NONE;
>>>> + csi2dc->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
>>>> + csi2dc->format.quantization = V4L2_QUANTIZATION_DEFAULT;
>>>> + csi2dc->format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
>>>> +}
>>>> +
>>>> +static int csi2dc_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct device *dev = &pdev->dev;
>>>> + struct csi2dc_device *csi2dc;
>>>> + int ret = 0;
>>>> + u32 ver;
>>>> +
>>>> + csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL);
>>>> + if (!csi2dc)
>>>> + return -ENOMEM;
>>>> +
>>>> + csi2dc->dev = dev;
>>>> +
>>>> + csi2dc->base = devm_platform_ioremap_resource(pdev, 0);
>>>> + if (IS_ERR(csi2dc->base)) {
>>>> + dev_err(dev, "base address not set\n");
>>>> + return PTR_ERR(csi2dc->base);
>>>> + }
>>>> +
>>>> + csi2dc->pclk = devm_clk_get(dev, "pclk");
>>>> + if (IS_ERR(csi2dc->pclk)) {
>>>> + ret = PTR_ERR(csi2dc->pclk);
>>>> + dev_err(dev, "failed to get pclk: %d\n", ret);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + csi2dc->scck = devm_clk_get(dev, "scck");
>>>> + if (IS_ERR(csi2dc->scck)) {
>>>> + ret = PTR_ERR(csi2dc->scck);
>>>> + dev_err(dev, "failed to get scck: %d\n", ret);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops);
>>>> +
>>>> + csi2dc->csi2dc_sd.owner = THIS_MODULE;
>>>> + csi2dc->csi2dc_sd.dev = dev;
>>>> + snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name),
>>>> + "csi2dc");
>>>> +
>>>> + csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>>> + csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>>>> +
>>>> + platform_set_drvdata(pdev, csi2dc);
>>>> +
>>>> + ret = csi2dc_of_parse(csi2dc, dev->of_node);
>>>> + if (ret)
>>>> + goto csi2dc_probe_cleanup_entity;
>>>> +
>>>> + csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
>>>> + if (csi2dc->video_pipe)
>>>> + csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>>>> +
>>>> + ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity,
>>>> + csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1,
>>>> + csi2dc->pads);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "media entity init failed\n");
>>>> + goto csi2dc_probe_cleanup_notifier;
>>>> + }
>>>> +
>>>> + csi2dc_default_format(csi2dc);
>>>> +
>>>> + /* turn power on to validate capabilities */
>>>> + ret = csi2dc_power(csi2dc, true);
>>>> + if (ret < 0)
>>>> + goto csi2dc_probe_cleanup_notifier;
>>>> +
>>>> + pm_runtime_set_active(dev);
>>>> + pm_runtime_enable(dev);
>>>> + ver = csi2dc_readl(csi2dc, CSI2DC_VERSION);
>>>
>>> Shouldn't you verify that the version is the expected one, or that, at
>>> least the device can be accessed ?
>>
>> If the read from the register worked, the device can be accessed. If the
>> device won't respond to this, we will get a bus fault or a freeze.
>> And printing the version number can help all the time by inspecting the
>> bootlog to see if something valid was printed.
>> Version changes from product to product but seeing a bootlog later will
>> make us easily understand which product was it, and if the version was
>> read correctly
>>
>>>
>>>> + pm_request_idle(dev);
>>>
>>> I'm not well versed when it comes to runtime_pm but I can read that
>>>
>>> Documentation/power/runtime_pm.rst- `int pm_request_idle(struct device *dev);`
>>> Documentation/power/runtime_pm.rst- - submit a request to execute the subsystem-level idle callback for the
>>> Documentation/power/runtime_pm.rst- device (the request is represented by a work item in pm_wq); returns 0 on
>>> Documentation/power/runtime_pm.rst- success or error code if the request has not been queued up
>>>
>>> And looking below you have
>>>
>>> +static const struct dev_pm_ops csi2dc_dev_pm_ops = {
>>> + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
>>> +};
>>>
>>> Which expands to
>>>
>>> include/linux/pm.h: SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \
>>>
>>> so it seems to me you've no idle func defined, hence you above call
>>> has no effect so you're device stays powered on after your above call
>>> to
>>>
>>> ret = csi2dc_power(csi2dc, true);
>>>
>>> Also trying to suspend using runtime_pm while having powered on by
>>> calling the resume function directly would leave the pm status
>>> unbalanced ?
>>>
>>> I would raher call pm_runtime_resume() and
>>> pm_runtime_suspend() or call the power routine directly, and later
>>> enable pm_runtime
>>>
>>> csi2dc_power(true)
>>> readl();
>>> csi2fd_power(false);
>>>
>>> pm_runtime_set_active();
>>> pm_runtime_enable();
>>>
>>> v4l2_async_register();
>>>
>>> Again, not well versed in runtime_pm, so don't assume the above makes
>>> sense :)
>>
>> I am not versed in runtime_pm either, I will try to remove this
>> 'pm_request_idle' to see what happens at the first
>> pm_runtime_resume_and_get, and print messages in power on/power off to
>> see how and when they are called.
>>
>
Hi Jacopo,
I added traces in my power on / power off functions to see who calls them.
Here is the log :
power on
------------[ cut here ]------------
WARNING: CPU: 0 PID: 1 at
drivers/media/platform/atmel/microchip-csi2dc.c:331 csi2dc_power+0xd0/0x134
Modules linked in:
CPU: 0 PID: 1 Comm: swapper Not tainted 5.16.0-rc4+ #62
Hardware name: Microchip SAMA7
[<c010b9dc>] (unwind_backtrace) from [<c010975c>] (show_stack+0x10/0x14)
[<c010975c>] (show_stack) from [<c0755aa0>] (__warn+0xd0/0x128)
[<c0755aa0>] (__warn) from [<c0755b70>] (warn_slowpath_fmt+0x78/0xac)
[<c0755b70>] (warn_slowpath_fmt) from [<c0501114>] (csi2dc_power+0xd0/0x134)
[<c0501114>] (csi2dc_power) from [<c05012dc>] (csi2dc_probe+0x158/0x23c)
[<c05012dc>] (csi2dc_probe) from [<c04067cc>] (platform_probe+0x5c/0xb8)
[<c04067cc>] (platform_probe) from [<c04043f8>]
(really_probe.part.0+0x9c/0x32c)
[<c04043f8>] (really_probe.part.0) from [<c0404730>]
(__driver_probe_device+0xa8/0x13c)
[<c0404730>] (__driver_probe_device) from [<c04047f8>]
(driver_probe_device+0x34/0x108)
[<c04047f8>] (driver_probe_device) from [<c0404ea0>]
(__driver_attach+0xb4/0x180)
[<c0404ea0>] (__driver_attach) from [<c040245c>]
(bus_for_each_dev+0x74/0xc0)
[<c040245c>] (bus_for_each_dev) from [<c0403804>]
(bus_add_driver+0x15c/0x1e0)
[<c0403804>] (bus_add_driver) from [<c040572c>] (driver_register+0x8c/0x124)
[<c040572c>] (driver_register) from [<c01014b0>]
(do_one_initcall+0x54/0x1d0)
[<c01014b0>] (do_one_initcall) from [<c0b0114c>]
(kernel_init_freeable+0x19c/0x200)
[<c0b0114c>] (kernel_init_freeable) from [<c075c9e0>]
(kernel_init+0x18/0x128)
[<c075c9e0>] (kernel_init) from [<c0100140>] (ret_from_fork+0x14/0x34)
Exception stack(0xc4033fb0 to 0xc4033ff8)
3fa0: 00000000 00000000 00000000
00000000
3fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000
3fe0: 00000000 00000000 00000000 00000000 00000013 00000000
---[ end trace a5faddd6c4e11ca2 ]---
microchip-csi2dc e1404000.csi2dc: Microchip CSI2DC version 145
power off
------------[ cut here ]------------
WARNING: CPU: 0 PID: 28 at
drivers/media/platform/atmel/microchip-csi2dc.c:335
csi2dc_runtime_suspend+0x34/0x6c
Modules linked in:
CPU: 0 PID: 28 Comm: kworker/0:9 Tainted: G W 5.16.0-rc4+ #62
Hardware name: Microchip SAMA7
Workqueue: pm pm_runtime_work
[<c010b9dc>] (unwind_backtrace) from [<c010975c>] (show_stack+0x10/0x14)
[<c010975c>] (show_stack) from [<c0755aa0>] (__warn+0xd0/0x128)
[<c0755aa0>] (__warn) from [<c0755b70>] (warn_slowpath_fmt+0x78/0xac)
[<c0755b70>] (warn_slowpath_fmt) from [<c075b4cc>]
(csi2dc_runtime_suspend+0x34/0x6c)
[<c075b4cc>] (csi2dc_runtime_suspend) from [<c04114b8>]
(__rpm_callback+0x30/0xe4)
[<c04114b8>] (__rpm_callback) from [<c04115cc>] (rpm_callback+0x60/0x64)
[<c04115cc>] (rpm_callback) from [<c0410e28>] (rpm_suspend+0xd0/0x4bc)
[<c0410e28>] (rpm_suspend) from [<c041244c>] (pm_runtime_work+0x90/0x98)
[<c041244c>] (pm_runtime_work) from [<c012b568>]
(process_one_work+0x198/0x410)
[<c012b568>] (process_one_work) from [<c012b85c>] (worker_thread+0x7c/0x570)
[<c012b85c>] (worker_thread) from [<c0132168>] (kthread+0x154/0x174)
[<c0132168>] (kthread) from [<c0100140>] (ret_from_fork+0x14/0x34)
Exception stack(0xc4327fb0 to 0xc4327ff8)
7fa0: 00000000 00000000 00000000
00000000
7fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000
7fe0: 00000000 00000000 00000000 00000000 00000013 00000000
---[ end trace a5faddd6c4e11ca3 ]---
>
So it looks like after probing is finished, the runtime PM system
switches the device back to suspend mode (as probably there are no 'get'
operations on it ).
I removed the pm_request_idle as it looks you are right, it does nothing.
When trying to capture a frame, this happens:
# cam -c1 -C1
[0:01:14.779664000] [229] INFO IPAManager ipa_manager.cpp:138 libcamera
is not installed. Adding '//src/ipa' to the IPA search path
[0:01:14.784039600] [229] WARN IPAManager ipa_manager.cpp:149 No IPA
found in '/usr/lib/libcamera'
[0:01:14.784219400] [229] INFO Camera camera_manager.cpp:293 libcamera
v0.0.0+60036-2021.11-rc1-182-gd72a7997-dirty (2021-11-15T11:27:09+02:00)
[0:01:14.800801800] [230] WARN CameraSensor camera_sensor.cpp:197
'imx219 1-0010': Recommended V4L2 control 0x009a0922 not supported
[0:01:14.800952200] [230] WARN CameraSensor camera_sensor.cpp:249
'imx219 1-0010': The sensor kernel driver needs to be fixed
[0:01:14.801018400] [230] WARN CameraSensor camera_sensor.cpp:251
'imx219 1-0010': See Documentation/sensor_driver_requirements.rst in the
libcamera sources for more information
[0:01:14.803197200] [230] WARN CameraSensor camera_sensor.cpp:414
'imx219 1-0010': Failed to retrieve the camera location
[0:01:14.805444000] [230] WARN V4L2 v4l2_pixelformat.cpp:287
Unsupported V4L2 pixel format Y16
[0:01:14.805541200] [230] WARN V4L2 v4l2_pixelformat.cpp:287
Unsupported V4L2 pixel format AR12
[0:01:14.805612200] [230] WARN V4L2 v4l2_pixelformat.cpp:287
Unsupported V4L2 pixel format AR15
Using camera /base/soc/flexcom@e2818000/i2c@600/camera@10 as cam0
[0:01:14.806657000] [229] INFO Camera camera.cpp:945 configuring
streams: (0) 3264x2464-R8
power on
------------[ cut here ]------------
WARNING: CPU: 0 PID: 230 at
drivers/media/platform/atmel/microchip-csi2dc.c:331 csi2dc_power+0xd0/0x134
Modules linked in: imx219 dw_csi dw_dphy
CPU: 0 PID: 230 Comm: cam Tainted: G W 5.16.0-rc4+ #63
Hardware name: Microchip SAMA7
[<c010b9dc>] (unwind_backtrace) from [<c010975c>] (show_stack+0x10/0x14)
[<c010975c>] (show_stack) from [<c0755aa0>] (__warn+0xd0/0x128)
[<c0755aa0>] (__warn) from [<c0755b70>] (warn_slowpath_fmt+0x78/0xac)
[<c0755b70>] (warn_slowpath_fmt) from [<c0501114>] (csi2dc_power+0xd0/0x134)
[<c0501114>] (csi2dc_power) from [<c04114b8>] (__rpm_callback+0x30/0xe4)
[<c04114b8>] (__rpm_callback) from [<c04115cc>] (rpm_callback+0x60/0x64)
[<c04115cc>] (rpm_callback) from [<c0411984>] (rpm_resume+0x3b4/0x550)
[<c0411984>] (rpm_resume) from [<c0411b54>] (__pm_runtime_resume+0x34/0x3c)
[<c0411b54>] (__pm_runtime_resume) from [<c0500dcc>]
(csi2dc_s_stream+0xb8/0x330)
[<c0500dcc>] (csi2dc_s_stream) from [<c04fee9c>]
(isc_start_streaming+0x3a4/0x498)
[<c04fee9c>] (isc_start_streaming) from [<c04f5280>]
(vb2_start_streaming+0x98/0x1a8)
[<c04f5280>] (vb2_start_streaming) from [<c04f57d0>]
(vb2_core_qbuf+0x440/0x5d4)
[<c04f57d0>] (vb2_core_qbuf) from [<c04f9490>] (vb2_qbuf+0x80/0xdc)
[<c04f9490>] (vb2_qbuf) from [<c04dd0dc>] (__video_do_ioctl+0x234/0x490)
[<c04dd0dc>] (__video_do_ioctl) from [<c04de410>]
(video_usercopy+0x1b4/0x550)
[<c04de410>] (video_usercopy) from [<c01e77b8>] (sys_ioctl+0x224/0xa0c)
[<c01e77b8>] (sys_ioctl) from [<c0100060>] (ret_fast_syscall+0x0/0x50)
Exception stack(0xc46e7fa8 to 0xc46e7ff0)
7fa0: b5d07088 b5d113c8 00000011 c044560f b66da594
b6e14000
7fc0: b5d07088 b5d113c8 b66da594 00000036 b66da5d8 fffffff8 b66da6cc
b66da8e0
7fe0: b6fb63a8 b66da57c b6f70d88 b6ba9fec
---[ end trace 7204e1871cae96e7 ]---
cam0dw-csi e1400000.csi2host: number of lanes: 2
phy phy-e1400040.dphy.0: PHY Stop state not reached 0
75.378082power off
------------[ cut here ]----e-------
4WARNING: CPU: 0 PID: 230 at
drivers/media/platform/atmel/microchip-csi2dc.c:335
csi2dc_runtime_suspend+0x34/0x6c
Modules linked in: imx219 dw_csi dw_dphy
CPU: 0 PID: 230 Comm: cam Tainted: G W 5.16.0-rc4+ #63
Hardware name: Microchip SAMA7
[<c010b9dc>] (unwind_backtrace) from [<c010975c>] (show_stack+0x10/0x14)
[<c010975c>] (show_stack) from [<c0755aa0>] (__warn+0xd0/0x128)
[<c0755aa0>] (__warn) from [<c0755b70>] (warn_slowpath_fmt+0x78/0xac)
[<c0755b70>] (warn_slowpath_fmt) from [<c075b4cc>]
(csi2dc_runtime_suspend+0x34/0x6c)
[<c075b4cc>] (csi2dc_runtime_suspend) from [<c04114b8>]
(__rpm_callback+0x30/0xe4)
[<c04114b8>] (__rpm_callback) from [<c04115cc>] (rpm_callback+0x60/0x64)
[<c04115cc>] (rpm_callback) from [<c0410e28>] (rpm_suspend+0xd0/0x4bc)
[<c0410e28>] (rpm_suspend) from [<c041136c>] (__pm_runtime_idle+0x3c/0x4c)
[<c041136c>] (__pm_runtime_idle) from [<c0500d98>]
(csi2dc_s_stream+0x84/0x330)
[<c0500d98>] (csi2dc_s_stream) from [<c04fcc7c>]
(isc_stop_streaming+0x134/0x174)
[<c04fcc7c>] (isc_stop_streaming) from [<c04f5dd8>]
(__vb2_queue_cancel+0x28/0x2a4)
[<c04f5dd8>] (__vb2_queue_cancel) from [<c04f606c>]
(vb2_core_streamoff+0x18/0xac)
[<c04f606c>] (vb2_core_streamoff) from [<c04dd0dc>]
(__video_do_ioctl+0x234/0x490)
[<c04dd0dc>] (__video_do_ioctl) from [<c04de410>]
(video_usercopy+0x1b4/0x550)
[<c04de410>] (video_usercopy) from [<c01e77b8>] (sys_ioctl+0x224/0xa0c)
[<c01e77b8>] (sys_ioctl) from [<c0100060>] (ret_fast_syscall+0x0/0x50)
Exception stack(0xc46e7fa8 to 0xc46e7ff0)
7fa0: b5d07088 b6fb5d58 00000011 40045613 b5d071bc
00000001
7fc0: b5d07088 b6fb5d58 b5d01e20 00000036 b5d008b8 00000000 b5d01e20
0054ec00
7fe0: b6fb63a8 b66da7b4 b6f70d88 b6ba9fec
---[ end trace 7204e1871cae96e8 ]---
#
#
Which is expected, on start streaming, the power on is called, and on
stop streaming the power off.
So it looks like the driver does pm_runtime_set_active, meaning the
power state is active (which is something that I do by calling first
csi2dc_power(true)), and after probing is done, it's automatically sent
to suspend mode.
So if I do csi2dc_power(false) before finish probing, there will be a
problem because it will be called again by the subsystem, leading to
issues with double clock unpreparing... etc.
So you are fine with this sequence?
..init this..
..init that..
csi2dc_power(true)
readl();
pm_runtime_set_active();
pm_runtime_enable();
..finish probing..
Thanks again,
Eugen
>>
>>>
>>>> +
>>>> + /*
>>>> + * we must register the subdev after PM runtime has been requested,
>>>> + * otherwise we might bound immediately and request pm_runtime_resume
>>>> + * before runtime_enable.
>>>> + */
>>>> + ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd);
>>>> + if (ret) {
>>>> + dev_err(csi2dc->dev, "failed to register the subdevice\n");
>>>> + goto csi2dc_probe_cleanup_notifier;
>>>> + }
>>>> +
>>>> + dev_info(dev, "Microchip CSI2DC version %x\n", ver);
>>>> +
>>>> + return 0;
>>>> +
>>>> +csi2dc_probe_cleanup_notifier:
>>>> + v4l2_async_nf_cleanup(&csi2dc->notifier);
>>>> +csi2dc_probe_cleanup_entity:
>>>> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
>>>
>>> I wasn't honestly expecting this
>>>
>>> /**
>>> * media_entity_cleanup() - free resources associated with an entity
>>> *
>>> * @entity: entity where the pads belong
>>> *
>>> * This function must be called during the cleanup phase after unregistering
>>> * the entity (currently, it does nothing).
>>> */
>>> #if IS_ENABLED(CONFIG_MEDIA_CONTROLLER)
>>> static inline void media_entity_cleanup(struct media_entity *entity) {}
>>>
>>> :)
>>
>> I saw it as well. But I thought that who knows, in the future it might
>> do something, so it's better to have it in the driver rather than not
>> have it at all.
>>
>>
>> Thanks again for reviewing,
>> Eugen
>>
>>>
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int csi2dc_remove(struct platform_device *pdev)
>>>> +{
>>>> + struct csi2dc_device *csi2dc = platform_get_drvdata(pdev);
>>>> +
>>>> + pm_runtime_disable(&pdev->dev);
>>>> +
>>>> + v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd);
>>>> + v4l2_async_nf_unregister(&csi2dc->notifier);
>>>> + v4l2_async_nf_cleanup(&csi2dc->notifier);
>>>> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int __maybe_unused csi2dc_runtime_suspend(struct device *dev)
>>>> +{
>>>> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
>>>> +
>>>> + return csi2dc_power(csi2dc, false);
>>>> +}
>>>> +
>>>> +static int __maybe_unused csi2dc_runtime_resume(struct device *dev)
>>>> +{
>>>> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
>>>> +
>>>> + return csi2dc_power(csi2dc, true);
>>>> +}
>>>> +
>>>> +static const struct dev_pm_ops csi2dc_dev_pm_ops = {
>>>> + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
>>>> +};
>>>> +
>>>> +static const struct of_device_id csi2dc_of_match[] = {
>>>> + { .compatible = "microchip,sama7g5-csi2dc" },
>>>> + { }
>>>> +};
>>>> +
>>>> +MODULE_DEVICE_TABLE(of, csi2dc_of_match);
>>>> +
>>>> +static struct platform_driver csi2dc_driver = {
>>>> + .probe = csi2dc_probe,
>>>> + .remove = csi2dc_remove,
>>>> + .driver = {
>>>> + .name = "microchip-csi2dc",
>>>> + .pm = &csi2dc_dev_pm_ops,
>>>> + .of_match_table = of_match_ptr(csi2dc_of_match),
>>>> + },
>>>> +};
>>>> +
>>>> +module_platform_driver(csi2dc_driver);
>>>> +
>>>> +MODULE_AUTHOR("Eugen Hristev <[email protected]>");
>>>> +MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver");
>>>> +MODULE_LICENSE("GPL v2");
>>>> --
>>>> 2.25.1
>>>>
>>
Hello Eugen
On Fri, Dec 10, 2021 at 11:07:15AM +0000, [email protected] wrote:
> On 12/6/21 2:06 PM, Jacopo Mondi wrote:
> > Hi Eugen
> >
> > On Mon, Dec 06, 2021 at 11:42:25AM +0000, [email protected] wrote:
> >> On 12/6/21 12:51 PM, Jacopo Mondi wrote:
> >>> Hello Eugen
> >>>
> >>> thanks for addressing all my previous comments, just a few more
> >>> things and the driver looks good to me
> >>
> >> Thanks for reviewing. I tried my best to accomodate your suggestions.
> >>>
> >>> On Fri, Nov 12, 2021 at 04:24:47PM +0200, Eugen Hristev wrote:
[snip]
> >>>> +
> >>>> +static int csi2dc_probe(struct platform_device *pdev)
> >>>> +{
> >>>> + struct device *dev = &pdev->dev;
> >>>> + struct csi2dc_device *csi2dc;
> >>>> + int ret = 0;
> >>>> + u32 ver;
> >>>> +
> >>>> + csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL);
> >>>> + if (!csi2dc)
> >>>> + return -ENOMEM;
> >>>> +
> >>>> + csi2dc->dev = dev;
> >>>> +
> >>>> + csi2dc->base = devm_platform_ioremap_resource(pdev, 0);
> >>>> + if (IS_ERR(csi2dc->base)) {
> >>>> + dev_err(dev, "base address not set\n");
> >>>> + return PTR_ERR(csi2dc->base);
> >>>> + }
> >>>> +
> >>>> + csi2dc->pclk = devm_clk_get(dev, "pclk");
> >>>> + if (IS_ERR(csi2dc->pclk)) {
> >>>> + ret = PTR_ERR(csi2dc->pclk);
> >>>> + dev_err(dev, "failed to get pclk: %d\n", ret);
> >>>> + return ret;
> >>>> + }
> >>>> +
> >>>> + csi2dc->scck = devm_clk_get(dev, "scck");
> >>>> + if (IS_ERR(csi2dc->scck)) {
> >>>> + ret = PTR_ERR(csi2dc->scck);
> >>>> + dev_err(dev, "failed to get scck: %d\n", ret);
> >>>> + return ret;
> >>>> + }
> >>>> +
> >>>> + v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops);
> >>>> +
> >>>> + csi2dc->csi2dc_sd.owner = THIS_MODULE;
> >>>> + csi2dc->csi2dc_sd.dev = dev;
> >>>> + snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name),
> >>>> + "csi2dc");
> >>>> +
> >>>> + csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> >>>> + csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> >>>> +
> >>>> + platform_set_drvdata(pdev, csi2dc);
> >>>> +
> >>>> + ret = csi2dc_of_parse(csi2dc, dev->of_node);
> >>>> + if (ret)
> >>>> + goto csi2dc_probe_cleanup_entity;
> >>>> +
> >>>> + csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> >>>> + if (csi2dc->video_pipe)
> >>>> + csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> >>>> +
> >>>> + ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity,
> >>>> + csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1,
> >>>> + csi2dc->pads);
> >>>> + if (ret < 0) {
> >>>> + dev_err(dev, "media entity init failed\n");
> >>>> + goto csi2dc_probe_cleanup_notifier;
> >>>> + }
> >>>> +
> >>>> + csi2dc_default_format(csi2dc);
> >>>> +
> >>>> + /* turn power on to validate capabilities */
> >>>> + ret = csi2dc_power(csi2dc, true);
> >>>> + if (ret < 0)
> >>>> + goto csi2dc_probe_cleanup_notifier;
> >>>> +
> >>>> + pm_runtime_set_active(dev);
> >>>> + pm_runtime_enable(dev);
> >>>> + ver = csi2dc_readl(csi2dc, CSI2DC_VERSION);
> >>>
> >>> Shouldn't you verify that the version is the expected one, or that, at
> >>> least the device can be accessed ?
> >>
> >> If the read from the register worked, the device can be accessed. If the
> >> device won't respond to this, we will get a bus fault or a freeze.
> >> And printing the version number can help all the time by inspecting the
> >> bootlog to see if something valid was printed.
> >> Version changes from product to product but seeing a bootlog later will
> >> make us easily understand which product was it, and if the version was
> >> read correctly
> >>
> >>>
> >>>> + pm_request_idle(dev);
> >>>
> >>> I'm not well versed when it comes to runtime_pm but I can read that
> >>>
> >>> Documentation/power/runtime_pm.rst- `int pm_request_idle(struct device *dev);`
> >>> Documentation/power/runtime_pm.rst- - submit a request to execute the subsystem-level idle callback for the
> >>> Documentation/power/runtime_pm.rst- device (the request is represented by a work item in pm_wq); returns 0 on
> >>> Documentation/power/runtime_pm.rst- success or error code if the request has not been queued up
> >>>
> >>> And looking below you have
> >>>
> >>> +static const struct dev_pm_ops csi2dc_dev_pm_ops = {
> >>> + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
> >>> +};
> >>>
> >>> Which expands to
> >>>
> >>> include/linux/pm.h: SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \
> >>>
> >>> so it seems to me you've no idle func defined, hence you above call
> >>> has no effect so you're device stays powered on after your above call
> >>> to
> >>>
> >>> ret = csi2dc_power(csi2dc, true);
> >>>
> >>> Also trying to suspend using runtime_pm while having powered on by
> >>> calling the resume function directly would leave the pm status
> >>> unbalanced ?
> >>>
> >>> I would raher call pm_runtime_resume() and
> >>> pm_runtime_suspend() or call the power routine directly, and later
> >>> enable pm_runtime
> >>>
> >>> csi2dc_power(true)
> >>> readl();
> >>> csi2fd_power(false);
> >>>
> >>> pm_runtime_set_active();
> >>> pm_runtime_enable();
> >>>
> >>> v4l2_async_register();
> >>>
> >>> Again, not well versed in runtime_pm, so don't assume the above makes
> >>> sense :)
> >>
> >> I am not versed in runtime_pm either, I will try to remove this
> >> 'pm_request_idle' to see what happens at the first
> >> pm_runtime_resume_and_get, and print messages in power on/power off to
> >> see how and when they are called.
> >>
> >
>
>
> Hi Jacopo,
>
> I added traces in my power on / power off functions to see who calls them.
>
> Here is the log :
>
> power on
> ------------[ cut here ]------------
> WARNING: CPU: 0 PID: 1 at
> drivers/media/platform/atmel/microchip-csi2dc.c:331 csi2dc_power+0xd0/0x134
> Modules linked in:
> CPU: 0 PID: 1 Comm: swapper Not tainted 5.16.0-rc4+ #62
> Hardware name: Microchip SAMA7
> [<c010b9dc>] (unwind_backtrace) from [<c010975c>] (show_stack+0x10/0x14)
> [<c010975c>] (show_stack) from [<c0755aa0>] (__warn+0xd0/0x128)
> [<c0755aa0>] (__warn) from [<c0755b70>] (warn_slowpath_fmt+0x78/0xac)
> [<c0755b70>] (warn_slowpath_fmt) from [<c0501114>] (csi2dc_power+0xd0/0x134)
> [<c0501114>] (csi2dc_power) from [<c05012dc>] (csi2dc_probe+0x158/0x23c)
> [<c05012dc>] (csi2dc_probe) from [<c04067cc>] (platform_probe+0x5c/0xb8)
> [<c04067cc>] (platform_probe) from [<c04043f8>]
> (really_probe.part.0+0x9c/0x32c)
> [<c04043f8>] (really_probe.part.0) from [<c0404730>]
> (__driver_probe_device+0xa8/0x13c)
> [<c0404730>] (__driver_probe_device) from [<c04047f8>]
> (driver_probe_device+0x34/0x108)
> [<c04047f8>] (driver_probe_device) from [<c0404ea0>]
> (__driver_attach+0xb4/0x180)
> [<c0404ea0>] (__driver_attach) from [<c040245c>]
> (bus_for_each_dev+0x74/0xc0)
> [<c040245c>] (bus_for_each_dev) from [<c0403804>]
> (bus_add_driver+0x15c/0x1e0)
> [<c0403804>] (bus_add_driver) from [<c040572c>] (driver_register+0x8c/0x124)
> [<c040572c>] (driver_register) from [<c01014b0>]
> (do_one_initcall+0x54/0x1d0)
> [<c01014b0>] (do_one_initcall) from [<c0b0114c>]
> (kernel_init_freeable+0x19c/0x200)
> [<c0b0114c>] (kernel_init_freeable) from [<c075c9e0>]
> (kernel_init+0x18/0x128)
> [<c075c9e0>] (kernel_init) from [<c0100140>] (ret_from_fork+0x14/0x34)
> Exception stack(0xc4033fb0 to 0xc4033ff8)
> 3fa0: 00000000 00000000 00000000
> 00000000
> 3fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000
> 00000000
> 3fe0: 00000000 00000000 00000000 00000000 00000013 00000000
> ---[ end trace a5faddd6c4e11ca2 ]---
> microchip-csi2dc e1404000.csi2dc: Microchip CSI2DC version 145
> power off
> ------------[ cut here ]------------
> WARNING: CPU: 0 PID: 28 at
> drivers/media/platform/atmel/microchip-csi2dc.c:335
> csi2dc_runtime_suspend+0x34/0x6c
> Modules linked in:
> CPU: 0 PID: 28 Comm: kworker/0:9 Tainted: G W 5.16.0-rc4+ #62
> Hardware name: Microchip SAMA7
> Workqueue: pm pm_runtime_work
> [<c010b9dc>] (unwind_backtrace) from [<c010975c>] (show_stack+0x10/0x14)
> [<c010975c>] (show_stack) from [<c0755aa0>] (__warn+0xd0/0x128)
> [<c0755aa0>] (__warn) from [<c0755b70>] (warn_slowpath_fmt+0x78/0xac)
> [<c0755b70>] (warn_slowpath_fmt) from [<c075b4cc>]
> (csi2dc_runtime_suspend+0x34/0x6c)
> [<c075b4cc>] (csi2dc_runtime_suspend) from [<c04114b8>]
> (__rpm_callback+0x30/0xe4)
> [<c04114b8>] (__rpm_callback) from [<c04115cc>] (rpm_callback+0x60/0x64)
> [<c04115cc>] (rpm_callback) from [<c0410e28>] (rpm_suspend+0xd0/0x4bc)
> [<c0410e28>] (rpm_suspend) from [<c041244c>] (pm_runtime_work+0x90/0x98)
> [<c041244c>] (pm_runtime_work) from [<c012b568>]
> (process_one_work+0x198/0x410)
> [<c012b568>] (process_one_work) from [<c012b85c>] (worker_thread+0x7c/0x570)
> [<c012b85c>] (worker_thread) from [<c0132168>] (kthread+0x154/0x174)
> [<c0132168>] (kthread) from [<c0100140>] (ret_from_fork+0x14/0x34)
> Exception stack(0xc4327fb0 to 0xc4327ff8)
> 7fa0: 00000000 00000000 00000000
> 00000000
> 7fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000
> 00000000
> 7fe0: 00000000 00000000 00000000 00000000 00000013 00000000
> ---[ end trace a5faddd6c4e11ca3 ]---
> >
>
>
> So it looks like after probing is finished, the runtime PM system
> switches the device back to suspend mode (as probably there are no 'get'
> operations on it ).
>
> I removed the pm_request_idle as it looks you are right, it does nothing.
>
> When trying to capture a frame, this happens:
>
> # cam -c1 -C1
> [0:01:14.779664000] [229] INFO IPAManager ipa_manager.cpp:138 libcamera
> is not installed. Adding '//src/ipa' to the IPA search path
> [0:01:14.784039600] [229] WARN IPAManager ipa_manager.cpp:149 No IPA
> found in '/usr/lib/libcamera'
> [0:01:14.784219400] [229] INFO Camera camera_manager.cpp:293 libcamera
> v0.0.0+60036-2021.11-rc1-182-gd72a7997-dirty (2021-11-15T11:27:09+02:00)
> [0:01:14.800801800] [230] WARN CameraSensor camera_sensor.cpp:197
> 'imx219 1-0010': Recommended V4L2 control 0x009a0922 not supported
> [0:01:14.800952200] [230] WARN CameraSensor camera_sensor.cpp:249
> 'imx219 1-0010': The sensor kernel driver needs to be fixed
> [0:01:14.801018400] [230] WARN CameraSensor camera_sensor.cpp:251
> 'imx219 1-0010': See Documentation/sensor_driver_requirements.rst in the
> libcamera sources for more information
> [0:01:14.803197200] [230] WARN CameraSensor camera_sensor.cpp:414
> 'imx219 1-0010': Failed to retrieve the camera location
> [0:01:14.805444000] [230] WARN V4L2 v4l2_pixelformat.cpp:287
> Unsupported V4L2 pixel format Y16
> [0:01:14.805541200] [230] WARN V4L2 v4l2_pixelformat.cpp:287
> Unsupported V4L2 pixel format AR12
> [0:01:14.805612200] [230] WARN V4L2 v4l2_pixelformat.cpp:287
> Unsupported V4L2 pixel format AR15
> Using camera /base/soc/flexcom@e2818000/i2c@600/camera@10 as cam0
> [0:01:14.806657000] [229] INFO Camera camera.cpp:945 configuring
> streams: (0) 3264x2464-R8
> power on
> ------------[ cut here ]------------
> WARNING: CPU: 0 PID: 230 at
> drivers/media/platform/atmel/microchip-csi2dc.c:331 csi2dc_power+0xd0/0x134
> Modules linked in: imx219 dw_csi dw_dphy
> CPU: 0 PID: 230 Comm: cam Tainted: G W 5.16.0-rc4+ #63
> Hardware name: Microchip SAMA7
> [<c010b9dc>] (unwind_backtrace) from [<c010975c>] (show_stack+0x10/0x14)
> [<c010975c>] (show_stack) from [<c0755aa0>] (__warn+0xd0/0x128)
> [<c0755aa0>] (__warn) from [<c0755b70>] (warn_slowpath_fmt+0x78/0xac)
> [<c0755b70>] (warn_slowpath_fmt) from [<c0501114>] (csi2dc_power+0xd0/0x134)
> [<c0501114>] (csi2dc_power) from [<c04114b8>] (__rpm_callback+0x30/0xe4)
> [<c04114b8>] (__rpm_callback) from [<c04115cc>] (rpm_callback+0x60/0x64)
> [<c04115cc>] (rpm_callback) from [<c0411984>] (rpm_resume+0x3b4/0x550)
> [<c0411984>] (rpm_resume) from [<c0411b54>] (__pm_runtime_resume+0x34/0x3c)
> [<c0411b54>] (__pm_runtime_resume) from [<c0500dcc>]
> (csi2dc_s_stream+0xb8/0x330)
> [<c0500dcc>] (csi2dc_s_stream) from [<c04fee9c>]
> (isc_start_streaming+0x3a4/0x498)
> [<c04fee9c>] (isc_start_streaming) from [<c04f5280>]
> (vb2_start_streaming+0x98/0x1a8)
> [<c04f5280>] (vb2_start_streaming) from [<c04f57d0>]
> (vb2_core_qbuf+0x440/0x5d4)
> [<c04f57d0>] (vb2_core_qbuf) from [<c04f9490>] (vb2_qbuf+0x80/0xdc)
> [<c04f9490>] (vb2_qbuf) from [<c04dd0dc>] (__video_do_ioctl+0x234/0x490)
> [<c04dd0dc>] (__video_do_ioctl) from [<c04de410>]
> (video_usercopy+0x1b4/0x550)
> [<c04de410>] (video_usercopy) from [<c01e77b8>] (sys_ioctl+0x224/0xa0c)
> [<c01e77b8>] (sys_ioctl) from [<c0100060>] (ret_fast_syscall+0x0/0x50)
> Exception stack(0xc46e7fa8 to 0xc46e7ff0)
> 7fa0: b5d07088 b5d113c8 00000011 c044560f b66da594
> b6e14000
> 7fc0: b5d07088 b5d113c8 b66da594 00000036 b66da5d8 fffffff8 b66da6cc
> b66da8e0
> 7fe0: b6fb63a8 b66da57c b6f70d88 b6ba9fec
> ---[ end trace 7204e1871cae96e7 ]---
> cam0dw-csi e1400000.csi2host: number of lanes: 2
>
> phy phy-e1400040.dphy.0: PHY Stop state not reached 0
> 75.378082power off
> ------------[ cut here ]----e-------
> 4WARNING: CPU: 0 PID: 230 at
> drivers/media/platform/atmel/microchip-csi2dc.c:335
> csi2dc_runtime_suspend+0x34/0x6c
> Modules linked in: imx219 dw_csi dw_dphy
> CPU: 0 PID: 230 Comm: cam Tainted: G W 5.16.0-rc4+ #63
> Hardware name: Microchip SAMA7
> [<c010b9dc>] (unwind_backtrace) from [<c010975c>] (show_stack+0x10/0x14)
> [<c010975c>] (show_stack) from [<c0755aa0>] (__warn+0xd0/0x128)
> [<c0755aa0>] (__warn) from [<c0755b70>] (warn_slowpath_fmt+0x78/0xac)
> [<c0755b70>] (warn_slowpath_fmt) from [<c075b4cc>]
> (csi2dc_runtime_suspend+0x34/0x6c)
> [<c075b4cc>] (csi2dc_runtime_suspend) from [<c04114b8>]
> (__rpm_callback+0x30/0xe4)
> [<c04114b8>] (__rpm_callback) from [<c04115cc>] (rpm_callback+0x60/0x64)
> [<c04115cc>] (rpm_callback) from [<c0410e28>] (rpm_suspend+0xd0/0x4bc)
> [<c0410e28>] (rpm_suspend) from [<c041136c>] (__pm_runtime_idle+0x3c/0x4c)
> [<c041136c>] (__pm_runtime_idle) from [<c0500d98>]
> (csi2dc_s_stream+0x84/0x330)
> [<c0500d98>] (csi2dc_s_stream) from [<c04fcc7c>]
> (isc_stop_streaming+0x134/0x174)
> [<c04fcc7c>] (isc_stop_streaming) from [<c04f5dd8>]
> (__vb2_queue_cancel+0x28/0x2a4)
> [<c04f5dd8>] (__vb2_queue_cancel) from [<c04f606c>]
> (vb2_core_streamoff+0x18/0xac)
> [<c04f606c>] (vb2_core_streamoff) from [<c04dd0dc>]
> (__video_do_ioctl+0x234/0x490)
> [<c04dd0dc>] (__video_do_ioctl) from [<c04de410>]
> (video_usercopy+0x1b4/0x550)
> [<c04de410>] (video_usercopy) from [<c01e77b8>] (sys_ioctl+0x224/0xa0c)
> [<c01e77b8>] (sys_ioctl) from [<c0100060>] (ret_fast_syscall+0x0/0x50)
> Exception stack(0xc46e7fa8 to 0xc46e7ff0)
> 7fa0: b5d07088 b6fb5d58 00000011 40045613 b5d071bc
> 00000001
> 7fc0: b5d07088 b6fb5d58 b5d01e20 00000036 b5d008b8 00000000 b5d01e20
> 0054ec00
> 7fe0: b6fb63a8 b66da7b4 b6f70d88 b6ba9fec
> ---[ end trace 7204e1871cae96e8 ]---
> #
> #
>
> Which is expected, on start streaming, the power on is called, and on
> stop streaming the power off.
>
>
> So it looks like the driver does pm_runtime_set_active, meaning the
> power state is active (which is something that I do by calling first
> csi2dc_power(true)), and after probing is done, it's automatically sent
> to suspend mode.
>
> So if I do csi2dc_power(false) before finish probing, there will be a
> problem because it will be called again by the subsystem, leading to
> issues with double clock unpreparing... etc.
>
> So you are fine with this sequence?
>
> ..init this..
> ..init that..
> csi2dc_power(true)
> readl();
>
> pm_runtime_set_active();
> pm_runtime_enable();
>
> ..finish probing..
>
Thanks for the detailed analysis !
If it all works like it's expected and the device gets powered off
after probe and works well at s_stream() time, I'm of course ok with
that :)
Thanks
>
> Thanks again,
> Eugen
>
>
> >>
> >>>
> >>>> +
> >>>> + /*
> >>>> + * we must register the subdev after PM runtime has been requested,
> >>>> + * otherwise we might bound immediately and request pm_runtime_resume
> >>>> + * before runtime_enable.
> >>>> + */
> >>>> + ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd);
> >>>> + if (ret) {
> >>>> + dev_err(csi2dc->dev, "failed to register the subdevice\n");
> >>>> + goto csi2dc_probe_cleanup_notifier;
> >>>> + }
> >>>> +
> >>>> + dev_info(dev, "Microchip CSI2DC version %x\n", ver);
> >>>> +
> >>>> + return 0;
> >>>> +
> >>>> +csi2dc_probe_cleanup_notifier:
> >>>> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> >>>> +csi2dc_probe_cleanup_entity:
> >>>> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
> >>>
> >>> I wasn't honestly expecting this
> >>>
> >>> /**
> >>> * media_entity_cleanup() - free resources associated with an entity
> >>> *
> >>> * @entity: entity where the pads belong
> >>> *
> >>> * This function must be called during the cleanup phase after unregistering
> >>> * the entity (currently, it does nothing).
> >>> */
> >>> #if IS_ENABLED(CONFIG_MEDIA_CONTROLLER)
> >>> static inline void media_entity_cleanup(struct media_entity *entity) {}
> >>>
> >>> :)
> >>
> >> I saw it as well. But I thought that who knows, in the future it might
> >> do something, so it's better to have it in the driver rather than not
> >> have it at all.
> >>
> >>
> >> Thanks again for reviewing,
> >> Eugen
> >>
> >>>
> >>>> +
> >>>> + return ret;
> >>>> +}
> >>>> +
> >>>> +static int csi2dc_remove(struct platform_device *pdev)
> >>>> +{
> >>>> + struct csi2dc_device *csi2dc = platform_get_drvdata(pdev);
> >>>> +
> >>>> + pm_runtime_disable(&pdev->dev);
> >>>> +
> >>>> + v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd);
> >>>> + v4l2_async_nf_unregister(&csi2dc->notifier);
> >>>> + v4l2_async_nf_cleanup(&csi2dc->notifier);
> >>>> + media_entity_cleanup(&csi2dc->csi2dc_sd.entity);
> >>>> +
> >>>> + return 0;
> >>>> +}
> >>>> +
> >>>> +static int __maybe_unused csi2dc_runtime_suspend(struct device *dev)
> >>>> +{
> >>>> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
> >>>> +
> >>>> + return csi2dc_power(csi2dc, false);
> >>>> +}
> >>>> +
> >>>> +static int __maybe_unused csi2dc_runtime_resume(struct device *dev)
> >>>> +{
> >>>> + struct csi2dc_device *csi2dc = dev_get_drvdata(dev);
> >>>> +
> >>>> + return csi2dc_power(csi2dc, true);
> >>>> +}
> >>>> +
> >>>> +static const struct dev_pm_ops csi2dc_dev_pm_ops = {
> >>>> + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL)
> >>>> +};
> >>>> +
> >>>> +static const struct of_device_id csi2dc_of_match[] = {
> >>>> + { .compatible = "microchip,sama7g5-csi2dc" },
> >>>> + { }
> >>>> +};
> >>>> +
> >>>> +MODULE_DEVICE_TABLE(of, csi2dc_of_match);
> >>>> +
> >>>> +static struct platform_driver csi2dc_driver = {
> >>>> + .probe = csi2dc_probe,
> >>>> + .remove = csi2dc_remove,
> >>>> + .driver = {
> >>>> + .name = "microchip-csi2dc",
> >>>> + .pm = &csi2dc_dev_pm_ops,
> >>>> + .of_match_table = of_match_ptr(csi2dc_of_match),
> >>>> + },
> >>>> +};
> >>>> +
> >>>> +module_platform_driver(csi2dc_driver);
> >>>> +
> >>>> +MODULE_AUTHOR("Eugen Hristev <[email protected]>");
> >>>> +MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver");
> >>>> +MODULE_LICENSE("GPL v2");
> >>>> --
> >>>> 2.25.1
> >>>>
> >>
>
On 30/11/2021 at 00:14, Laurent Pinchart wrote:
> Hi Eugen,
>
> Thank you for the patch.
>
> On Fri, Nov 12, 2021 at 04:24:45PM +0200, Eugen Hristev wrote:
>> Add Microchip CSI2DC driver in the list.
>>
>> Signed-off-by: Eugen Hristev <[email protected]>
>
> Reviewed-by: Laurent Pinchart <[email protected]>
If needed:
Acked-by: Nicolas Ferre <[email protected]>
Thanks a lot Eugen!
Best regards,
Nicolas
>> ---
>> MAINTAINERS | 7 +++++++
>> 1 file changed, 7 insertions(+)
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 486bdf97f754..545c642c937f 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -12422,6 +12422,13 @@ L: [email protected] (moderated for non-subscribers)
>> S: Supported
>> F: sound/soc/atmel
>>
>> +MICROCHIP CSI2DC DRIVER
>> +M: Eugen Hristev <[email protected]>
>> +L: [email protected]
>> +S: Supported
>> +F: Documentation/devicetree/bindings/media/microchip,csi2dc.yaml
>> +F: drivers/media/platform/atmel/microchip-csi2dc.c
>> +
>> MICROCHIP ECC DRIVER
>> M: Tudor Ambarus <[email protected]>
>> L: [email protected]
>
> --
> Regards,
>
> Laurent Pinchart
>
--
Nicolas Ferre