* [PATCH v8 16/18] MAINTAINERS: add entry for KUnit the unit testing framework
From: Brendan Higgins @ 2019-07-10 7:15 UTC (permalink / raw)
To: frowand.list, gregkh, jpoimboe, keescook, kieran.bingham, mcgrof,
peterz, robh, sboyd, shuah, tytso, yamada.masahiro
Cc: devicetree, dri-devel, kunit-dev, linux-doc, linux-fsdevel,
linux-kbuild, linux-kernel, linux-kselftest, linux-nvdimm,
linux-um, Alexander.Levin, Tim.Bird, amir73il, dan.carpenter,
daniel, jdike, joel, julia.lawall, khilman, knut.omang, logang,
mpe, pmladek, rdunlap, richard, rientjes, rostedt, wfg,
Brendan Higgins
In-Reply-To: <20190710071508.173491-1-brendanhiggins@google.com>
Add myself as maintainer of KUnit, the Linux kernel's unit testing
framework.
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
---
MAINTAINERS | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 677ef41cb012c..48d04d180a988 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8599,6 +8599,17 @@ S: Maintained
F: tools/testing/selftests/
F: Documentation/dev-tools/kselftest*
+KERNEL UNIT TESTING FRAMEWORK (KUnit)
+M: Brendan Higgins <brendanhiggins@google.com>
+L: linux-kselftest@vger.kernel.org
+L: kunit-dev@googlegroups.com
+W: https://google.github.io/kunit-docs/third_party/kernel/docs/
+S: Maintained
+F: Documentation/dev-tools/kunit/
+F: include/kunit/
+F: kunit/
+F: tools/testing/kunit/
+
KERNEL USERMODE HELPER
M: Luis Chamberlain <mcgrof@kernel.org>
L: linux-kernel@vger.kernel.org
--
2.22.0.410.gd8fdbe21b5-goog
^ permalink raw reply related
* [PATCH v8 17/18] kernel/sysctl-test: Add null pointer test for sysctl.c:proc_dointvec()
From: Brendan Higgins @ 2019-07-10 7:15 UTC (permalink / raw)
To: frowand.list, gregkh, jpoimboe, keescook, kieran.bingham, mcgrof,
peterz, robh, sboyd, shuah, tytso, yamada.masahiro
Cc: devicetree, dri-devel, kunit-dev, linux-doc, linux-fsdevel,
linux-kbuild, linux-kernel, linux-kselftest, linux-nvdimm,
linux-um, Alexander.Levin, Tim.Bird, amir73il, dan.carpenter,
daniel, jdike, joel, julia.lawall, khilman, knut.omang, logang,
mpe, pmladek, rdunlap, richard, rientjes, rostedt, wfg,
Iurii Zaikin, Brendan Higgins
In-Reply-To: <20190710071508.173491-1-brendanhiggins@google.com>
From: Iurii Zaikin <yzaikin@google.com>
KUnit tests for initialized data behavior of proc_dointvec that is
explicitly checked in the code. Includes basic parsing tests including
int min/max overflow.
Signed-off-by: Iurii Zaikin <yzaikin@google.com>
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
Acked-by: Luis Chamberlain <mcgrof@kernel.org>
---
kernel/Makefile | 2 +
kernel/sysctl-test.c | 375 +++++++++++++++++++++++++++++++++++++++++++
lib/Kconfig.debug | 11 ++
3 files changed, 388 insertions(+)
create mode 100644 kernel/sysctl-test.c
diff --git a/kernel/Makefile b/kernel/Makefile
index a8d923b5481ba..50fd511cd0ee0 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -114,6 +114,8 @@ obj-$(CONFIG_HAS_IOMEM) += iomem.o
obj-$(CONFIG_ZONE_DEVICE) += memremap.o
obj-$(CONFIG_RSEQ) += rseq.o
+obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o
+
obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak.o
KASAN_SANITIZE_stackleak.o := n
KCOV_INSTRUMENT_stackleak.o := n
diff --git a/kernel/sysctl-test.c b/kernel/sysctl-test.c
new file mode 100644
index 0000000000000..fc27b2c1185ca
--- /dev/null
+++ b/kernel/sysctl-test.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit test of proc sysctl.
+ */
+
+#include <kunit/test.h>
+#include <linux/sysctl.h>
+
+#define KUNIT_PROC_READ 0
+#define KUNIT_PROC_WRITE 1
+
+static int i_zero;
+static int i_one_hundred = 100;
+
+/*
+ * Test that proc_dointvec will not try to use a NULL .data field even when the
+ * length is non-zero.
+ */
+static void sysctl_test_api_dointvec_null_tbl_data(struct kunit *test)
+{
+ struct ctl_table null_data_table = {
+ .procname = "foo",
+ /*
+ * Here we are testing that proc_dointvec behaves correctly when
+ * we give it a NULL .data field. Normally this would point to a
+ * piece of memory where the value would be stored.
+ */
+ .data = NULL,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &i_zero,
+ .extra2 = &i_one_hundred,
+ };
+ void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
+ size_t len;
+ loff_t pos;
+
+ /*
+ * We don't care what the starting length is since proc_dointvec should
+ * not try to read because .data is NULL.
+ */
+ len = 1234;
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&null_data_table,
+ KUNIT_PROC_READ, buffer, &len,
+ &pos));
+ KUNIT_EXPECT_EQ(test, (size_t)0, len);
+
+ /*
+ * See above.
+ */
+ len = 1234;
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&null_data_table,
+ KUNIT_PROC_WRITE, buffer, &len,
+ &pos));
+ KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
+
+/*
+ * Similar to the previous test, we create a struct ctrl_table that has a .data
+ * field that proc_dointvec cannot do anything with; however, this time it is
+ * because we tell proc_dointvec that the size is 0.
+ */
+static void sysctl_test_api_dointvec_table_maxlen_unset(struct kunit *test)
+{
+ int data = 0;
+ struct ctl_table data_maxlen_unset_table = {
+ .procname = "foo",
+ .data = &data,
+ /*
+ * So .data is no longer NULL, but we tell proc_dointvec its
+ * length is 0, so it still shouldn't try to use it.
+ */
+ .maxlen = 0,
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &i_zero,
+ .extra2 = &i_one_hundred,
+ };
+ void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
+ size_t len;
+ loff_t pos;
+
+ /*
+ * As before, we don't care what buffer length is because proc_dointvec
+ * cannot do anything because its internal .data buffer has zero length.
+ */
+ len = 1234;
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&data_maxlen_unset_table,
+ KUNIT_PROC_READ, buffer, &len,
+ &pos));
+ KUNIT_EXPECT_EQ(test, (size_t)0, len);
+
+ /*
+ * See previous comment.
+ */
+ len = 1234;
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&data_maxlen_unset_table,
+ KUNIT_PROC_WRITE, buffer, &len,
+ &pos));
+ KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
+
+/*
+ * Here we provide a valid struct ctl_table, but we try to read and write from
+ * it using a buffer of zero length, so it should still fail in a similar way as
+ * before.
+ */
+static void sysctl_test_api_dointvec_table_len_is_zero(struct kunit *test)
+{
+ int data = 0;
+ /* Good table. */
+ struct ctl_table table = {
+ .procname = "foo",
+ .data = &data,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &i_zero,
+ .extra2 = &i_one_hundred,
+ };
+ void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
+ /*
+ * However, now our read/write buffer has zero length.
+ */
+ size_t len = 0;
+ loff_t pos;
+
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_READ, buffer,
+ &len, &pos));
+ KUNIT_EXPECT_EQ(test, (size_t)0, len);
+
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_WRITE, buffer,
+ &len, &pos));
+ KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
+
+/*
+ * Test that proc_dointvec refuses to read when the file position is non-zero.
+ */
+static void sysctl_test_api_dointvec_table_read_but_position_set(
+ struct kunit *test)
+{
+ int data = 0;
+ /* Good table. */
+ struct ctl_table table = {
+ .procname = "foo",
+ .data = &data,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &i_zero,
+ .extra2 = &i_one_hundred,
+ };
+ void *buffer = kunit_kzalloc(test, sizeof(int), GFP_USER);
+ /*
+ * We don't care about our buffer length because we start off with a
+ * non-zero file position.
+ */
+ size_t len = 1234;
+ /*
+ * proc_dointvec should refuse to read into the buffer since the file
+ * pos is non-zero.
+ */
+ loff_t pos = 1;
+
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_READ, buffer,
+ &len, &pos));
+ KUNIT_EXPECT_EQ(test, (size_t)0, len);
+}
+
+/*
+ * Test that we can read a two digit number in a sufficiently size buffer.
+ * Nothing fancy.
+ */
+static void sysctl_test_dointvec_read_happy_single_positive(struct kunit *test)
+{
+ int data = 0;
+ /* Good table. */
+ struct ctl_table table = {
+ .procname = "foo",
+ .data = &data,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &i_zero,
+ .extra2 = &i_one_hundred,
+ };
+ /* Put something in the buffer for debugging purposes. */
+ char buf[] = "bogus";
+ size_t len = sizeof(buf) - 1;
+ loff_t pos = 0;
+ /* Store 13 in the data field. */
+ *((int *)table.data) = 13;
+
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_READ, buf,
+ &len, &pos));
+ KUNIT_ASSERT_EQ(test, (size_t)3, len);
+ buf[len] = '\0';
+ /* And we read 13 back out. */
+ KUNIT_EXPECT_STREQ(test, "13\n", (char *)buf);
+}
+
+/*
+ * Same as previous test, just now with negative numbers.
+ */
+static void sysctl_test_dointvec_read_happy_single_negative(struct kunit *test)
+{
+ int data = 0;
+ /* Good table. */
+ struct ctl_table table = {
+ .procname = "foo",
+ .data = &data,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &i_zero,
+ .extra2 = &i_one_hundred,
+ };
+ char buf[] = "bogus";
+ size_t len = sizeof(buf) - 1;
+ loff_t pos = 0;
+ *((int *)table.data) = -16;
+
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_READ, buf,
+ &len, &pos));
+ KUNIT_ASSERT_EQ(test, (size_t)4, len);
+ buf[len] = '\0';
+ KUNIT_EXPECT_STREQ(test, "-16\n", (char *)buf);
+}
+
+/*
+ * Test that a simple positive write works.
+ */
+static void sysctl_test_dointvec_write_happy_single_positive(struct kunit *test)
+{
+ int data = 0;
+ /* Good table. */
+ struct ctl_table table = {
+ .procname = "foo",
+ .data = &data,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &i_zero,
+ .extra2 = &i_one_hundred,
+ };
+ char input[] = "9";
+ size_t len = sizeof(input) - 1;
+ loff_t pos = 0;
+
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_WRITE, input,
+ &len, &pos));
+ KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
+ KUNIT_EXPECT_EQ(test, sizeof(input) - 1, (size_t)pos);
+ KUNIT_EXPECT_EQ(test, 9, *((int *)table.data));
+}
+
+/*
+ * Same as previous test, but now with negative numbers.
+ */
+static void sysctl_test_dointvec_write_happy_single_negative(struct kunit *test)
+{
+ int data = 0;
+ struct ctl_table table = {
+ .procname = "foo",
+ .data = &data,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &i_zero,
+ .extra2 = &i_one_hundred,
+ };
+ char input[] = "-9";
+ size_t len = sizeof(input) - 1;
+ loff_t pos = 0;
+
+ KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_WRITE, input,
+ &len, &pos));
+ KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
+ KUNIT_EXPECT_EQ(test, sizeof(input) - 1, (size_t)pos);
+ KUNIT_EXPECT_EQ(test, -9, *((int *)table.data));
+}
+
+/*
+ * Test that writing a value smaller than the minimum possible value is not
+ * allowed.
+ */
+static void sysctl_test_api_dointvec_write_single_less_int_min(
+ struct kunit *test)
+{
+ int data = 0;
+ struct ctl_table table = {
+ .procname = "foo",
+ .data = &data,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &i_zero,
+ .extra2 = &i_one_hundred,
+ };
+ char input[32];
+ size_t len = sizeof(input) - 1;
+ loff_t pos = 0;
+ unsigned long abs_of_less_than_min = (unsigned long)INT_MAX
+ - (INT_MAX + INT_MIN) + 1;
+
+ /*
+ * We use this rigmarole to create a string that contains a value one
+ * less than the minimum accepted value.
+ */
+ KUNIT_ASSERT_LT(test,
+ (size_t)snprintf(input, sizeof(input), "-%lu",
+ abs_of_less_than_min),
+ sizeof(input));
+
+ KUNIT_EXPECT_EQ(test, -EINVAL,
+ proc_dointvec(&table, KUNIT_PROC_WRITE, input, &len,
+ &pos));
+ KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
+ KUNIT_EXPECT_EQ(test, 0, *((int *)table.data));
+}
+
+/*
+ * Test that writing the maximum possible value works.
+ */
+static void sysctl_test_api_dointvec_write_single_greater_int_max(
+ struct kunit *test)
+{
+ int data = 0;
+ struct ctl_table table = {
+ .procname = "foo",
+ .data = &data,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec,
+ .extra1 = &i_zero,
+ .extra2 = &i_one_hundred,
+ };
+ char input[32];
+ size_t len = sizeof(input) - 1;
+ loff_t pos = 0;
+ unsigned long greater_than_max = (unsigned long)INT_MAX + 1;
+
+ KUNIT_ASSERT_GT(test, greater_than_max, (unsigned long)INT_MAX);
+ KUNIT_ASSERT_LT(test, (size_t)snprintf(input, sizeof(input), "%lu",
+ greater_than_max),
+ sizeof(input));
+ KUNIT_EXPECT_EQ(test, -EINVAL,
+ proc_dointvec(&table, KUNIT_PROC_WRITE, input, &len,
+ &pos));
+ KUNIT_ASSERT_EQ(test, sizeof(input) - 1, len);
+ KUNIT_EXPECT_EQ(test, 0, *((int *)table.data));
+}
+
+static struct kunit_case sysctl_test_cases[] = {
+ KUNIT_CASE(sysctl_test_api_dointvec_null_tbl_data),
+ KUNIT_CASE(sysctl_test_api_dointvec_table_maxlen_unset),
+ KUNIT_CASE(sysctl_test_api_dointvec_table_len_is_zero),
+ KUNIT_CASE(sysctl_test_api_dointvec_table_read_but_position_set),
+ KUNIT_CASE(sysctl_test_dointvec_read_happy_single_positive),
+ KUNIT_CASE(sysctl_test_dointvec_read_happy_single_negative),
+ KUNIT_CASE(sysctl_test_dointvec_write_happy_single_positive),
+ KUNIT_CASE(sysctl_test_dointvec_write_happy_single_negative),
+ KUNIT_CASE(sysctl_test_api_dointvec_write_single_less_int_min),
+ KUNIT_CASE(sysctl_test_api_dointvec_write_single_greater_int_max),
+ {}
+};
+
+static struct kunit_suite sysctl_test_suite = {
+ .name = "sysctl_test",
+ .test_cases = sysctl_test_cases,
+};
+
+kunit_test_suite(sysctl_test_suite);
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index cbdfae3798965..6f8007800a76f 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1939,6 +1939,17 @@ config TEST_SYSCTL
If unsure, say N.
+config SYSCTL_KUNIT_TEST
+ bool "KUnit test for sysctl"
+ depends on KUNIT
+ help
+ This builds the proc sysctl unit test, which runs on boot.
+ Tests the API contract and implementation correctness of sysctl.
+ For more information on KUnit and unit tests in general please refer
+ to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
+
config TEST_UDELAY
tristate "udelay test driver"
help
--
2.22.0.410.gd8fdbe21b5-goog
^ permalink raw reply related
* [PATCH v8 18/18] MAINTAINERS: add proc sysctl KUnit test to PROC SYSCTL section
From: Brendan Higgins @ 2019-07-10 7:15 UTC (permalink / raw)
To: frowand.list, gregkh, jpoimboe, keescook, kieran.bingham, mcgrof,
peterz, robh, sboyd, shuah, tytso, yamada.masahiro
Cc: devicetree, dri-devel, kunit-dev, linux-doc, linux-fsdevel,
linux-kbuild, linux-kernel, linux-kselftest, linux-nvdimm,
linux-um, Alexander.Levin, Tim.Bird, amir73il, dan.carpenter,
daniel, jdike, joel, julia.lawall, khilman, knut.omang, logang,
mpe, pmladek, rdunlap, richard, rientjes, rostedt, wfg,
Brendan Higgins, Iurii Zaikin
In-Reply-To: <20190710071508.173491-1-brendanhiggins@google.com>
Add entry for the new proc sysctl KUnit test to the PROC SYSCTL section,
and add Iurii as a maintainer.
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Cc: Iurii Zaikin <yzaikin@google.com>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
Acked-by: Luis Chamberlain <mcgrof@kernel.org>
---
MAINTAINERS | 2 ++
1 file changed, 2 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 48d04d180a988..f8204c75114da 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12721,12 +12721,14 @@ F: Documentation/filesystems/proc.txt
PROC SYSCTL
M: Luis Chamberlain <mcgrof@kernel.org>
M: Kees Cook <keescook@chromium.org>
+M: Iurii Zaikin <yzaikin@google.com>
L: linux-kernel@vger.kernel.org
L: linux-fsdevel@vger.kernel.org
S: Maintained
F: fs/proc/proc_sysctl.c
F: include/linux/sysctl.h
F: kernel/sysctl.c
+F: kernel/sysctl-test.c
F: tools/testing/selftests/sysctl/
PS3 NETWORK SUPPORT
--
2.22.0.410.gd8fdbe21b5-goog
^ permalink raw reply related
* Re: [PATCH v7 06/18] kbuild: enable building KUnit
From: Brendan Higgins @ 2019-07-10 7:19 UTC (permalink / raw)
To: Masahiro Yamada
Cc: Frank Rowand, Greg Kroah-Hartman, Josh Poimboeuf, Kees Cook,
Kieran Bingham, Luis R. Rodriguez, Peter Zijlstra (Intel),
Rob Herring, Stephen Boyd, Cc: Shuah Khan, Theodore Ts'o,
DTML, dri-devel, kunit-dev, open list:DOCUMENTATION,
linux-fsdevel, Linux Kbuild mailing list,
Linux Kernel Mailing List, KERNEL
In-Reply-To: <CAK7LNATx30AhZ51xozde=nO06-8UzuC0M9nfZXrqkyfmEFdu5w@mail.gmail.com>
On Tue, Jul 9, 2019 at 9:00 PM Masahiro Yamada
<yamada.masahiro@socionext.com> wrote:
>
> On Tue, Jul 9, 2019 at 3:34 PM Brendan Higgins
> <brendanhiggins@google.com> wrote:
> >
> > KUnit is a new unit testing framework for the kernel and when used is
> > built into the kernel as a part of it. Add KUnit to the root Kconfig and
> > Makefile to allow it to be actually built.
> >
> > Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
> > Cc: Masahiro Yamada <yamada.masahiro@socionext.com>
> > Cc: Michal Marek <michal.lkml@markovi.net>
> > Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> > Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
> > ---
> > Kconfig | 2 ++
> > Makefile | 2 +-
> > 2 files changed, 3 insertions(+), 1 deletion(-)
> >
> > diff --git a/Kconfig b/Kconfig
> > index 48a80beab6853..10428501edb78 100644
> > --- a/Kconfig
> > +++ b/Kconfig
> > @@ -30,3 +30,5 @@ source "crypto/Kconfig"
> > source "lib/Kconfig"
> >
> > source "lib/Kconfig.debug"
> > +
> > +source "kunit/Kconfig"
> > diff --git a/Makefile b/Makefile
> > index 3e4868a6498b2..60cf4f0813e0d 100644
> > --- a/Makefile
> > +++ b/Makefile
> > @@ -991,7 +991,7 @@ endif
> > PHONY += prepare0
> >
> > ifeq ($(KBUILD_EXTMOD),)
> > -core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
> > +core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ kunit/
> >
> > vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
> > $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
> > --
> > 2.22.0.410.gd8fdbe21b5-goog
>
>
> This is so trivial, and do not need to get ack from me.
Oh, sorry about that.
> Just a nit.
>
>
> When CONFIG_KUNIT is disable, is there any point in descending into kunit/ ?
>
> core-$(CONFIG_KUNIT) += kunit/
>
> ... might be useful to skip kunit/ entirely.
Makes sense. I just sent out a new change that does this.
Thanks!
> If you look at the top-level Makefile, some entries are doing this:
>
>
> init-y := init/
> drivers-y := drivers/ sound/
> drivers-$(CONFIG_SAMPLES) += samples/
> drivers-$(CONFIG_KERNEL_HEADER_TEST) += include/
> net-y := net/
> libs-y := lib/
> core-y := usr/
>
>
>
>
>
> --
> Best Regards
> Masahiro Yamada
^ permalink raw reply
* Re: [PATCH v14 2/2] dt-bindings: spi: Document Renesas R-Car Gen3 RPC-IF controller bindings
From: Geert Uytterhoeven @ 2019-07-10 7:22 UTC (permalink / raw)
To: Mason Yang
Cc: Mark Brown, Marek Vasut, Linux Kernel Mailing List, linux-spi,
Boris Brezillon, Linux-Renesas, Geert Uytterhoeven, Lee Jones,
Sergei Shtylyov, Rob Herring, Mark Rutland,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
juliensu, Simon Horman, Miquel Raynal
In-Reply-To: <1561023046-20886-3-git-send-email-masonccyang@mxic.com.tw>
Hi Mason,
On Thu, Jun 20, 2019 at 11:08 AM Mason Yang <masonccyang@mxic.com.tw> wrote:
> Dcument the bindings used by the Renesas R-Car Gen3 RPC-IF controller.
>
> Signed-off-by: Mason Yang <masonccyang@mxic.com.tw>
Thanks for your patch!
> index 0000000..e8edf99
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/spi/spi-renesas-rpc.txt
> @@ -0,0 +1,43 @@
> +Renesas R-Car Gen3 RPC-IF controller Device Tree Bindings
> +---------------------------------------------------------
> +
> +Required properties:
> +- compatible: should be an SoC-specific compatible value, followed by
> + "renesas,rcar-gen3-rpc" as a fallback.
> + supported SoC-specific values are:
> + "renesas,r8a77980-rpc" (R-Car V3H)
> + "renesas,r8a77995-rpc" (R-Car D3)
> +- reg: should contain three register areas:
> + first for the base address of RPC-IF registers,
> + second for the direct mapping read mode and
> + third for the write buffer area.
> +- reg-names: should contain "regs", "dirmap" and "wbuf"
> +- clocks: should contain the clock phandle/specifier pair for the module clock.
> +- clock-names: should contain "rpc"
> +- power-domain: should contain the power domain phandle/secifier pair.
power-domains
> +- resets: should contain the reset controller phandle/specifier pair.
> +- #address-cells: should be 1
> +- #size-cells: should be 0
> +
> +Example:
> +
> + rpc: spi@ee200000 {
> + compatible = "renesas,r8a77995-rpc", "renesas,rcar-gen3-rpc";
> + reg = <0 0xee200000 0 0x200>, <0 0x08000000 0 0x4000000>,
> + <0 0xee208000 0 0x100>;
> + reg-names = "regs", "dirmap", "wbuf";
> + clocks = <&cpg CPG_MOD 917>;
> + clock-names = "rpc";
> + power-domains = <&sysc R8A77995_PD_ALWAYS_ON>;
> + resets = <&cpg 917>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + flash@0 {
The subnode is not documented above.
> + compatible = "jedec,spi-nor";
> + reg = <0>;
> + spi-max-frequency = <40000000>;
> + spi-tx-bus-width = <1>;
> + spi-rx-bus-width = <1>;
> + };
> + };
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH v8 06/18] kbuild: enable building KUnit
From: Masahiro Yamada @ 2019-07-10 7:49 UTC (permalink / raw)
To: Brendan Higgins
Cc: Frank Rowand, Greg Kroah-Hartman, Josh Poimboeuf, Kees Cook,
Kieran Bingham, Luis R. Rodriguez, Peter Zijlstra (Intel),
Rob Herring, Stephen Boyd, Cc: Shuah Khan, Theodore Ts'o,
DTML, dri-devel, kunit-dev, open list:DOCUMENTATION,
linux-fsdevel, Linux Kbuild mailing list,
Linux Kernel Mailing List, KERNEL
In-Reply-To: <20190710071508.173491-7-brendanhiggins@google.com>
On Wed, Jul 10, 2019 at 4:16 PM Brendan Higgins
<brendanhiggins@google.com> wrote:
>
> KUnit is a new unit testing framework for the kernel and when used is
> built into the kernel as a part of it. Add KUnit to the root Kconfig and
> Makefile to allow it to be actually built.
>
> Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
> Cc: Masahiro Yamada <yamada.masahiro@socionext.com>
Please feel free to replace this with:
Acked-by: Masahiro Yamada <yamada.masahiro@socionext.com>
> Cc: Michal Marek <michal.lkml@markovi.net>
> Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Reviewed-by: Logan Gunthorpe <logang@deltatee.com>
--
Best Regards
Masahiro Yamada
^ permalink raw reply
* Re: [PATCH RFC 2/9] OPP: Export a number of helpers to prevent code duplication
From: Sibi Sankar @ 2019-07-10 8:01 UTC (permalink / raw)
To: Hsin-Yi Wang
Cc: Rob Herring, andy.gross, MyungJoo Ham, Kyungmin Park,
Rafael J. Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd,
georgi.djakov, bjorn.andersson, david.brown, Mark Rutland, lkml,
linux-arm-msm-owner, devicetree, rnayak, Chanwoo Choi, linux-pm,
evgreen, daidavid1, dianders
In-Reply-To: <CAJMQK-gcBC=ZyscuHzOe4t6xQzviTYo9W9_DSsppoaTZuiEOcw@mail.gmail.com>
Hi Hsin-Yi,
I'll get this addressed in the next re-spin which I plan to post by
end of this week.
On 7/8/19 8:58 AM, Hsin-Yi Wang wrote:
> On Thu, Mar 28, 2019 at 3:28 PM Sibi Sankar <sibis@codeaurora.org> wrote:
>
>> +
>> +/* The caller must call dev_pm_opp_put() after the OPP is used */
>> +struct dev_pm_opp *dev_pm_opp_find_opp_of_np(struct opp_table *opp_table,
>> + struct device_node *opp_np)
>> +{
>> + return _find_opp_of_np(opp_table, opp_np);
>> +}
> Hi Sibi,
>
> Though this is not the latest version, we've seen following issue:
>
> We would get lockdep warnings on this:
> [ 79.068957] Call trace:
> [ 79.071396] _find_opp_of_np+0xa0/0xa8
> [ 79.075136] dev_pm_opp_find_opp_of_np+0x24/0x30
> [ 79.079744] devfreq_passive_event_handler+0x304/0x51c
> [ 79.084872] devfreq_add_device+0x368/0x434
> [ 79.089046] devm_devfreq_add_device+0x68/0xb0
> [ 79.093480] mtk_cci_devfreq_probe+0x108/0x158
> [ 79.097915] platform_drv_probe+0x80/0xb0
> [ 79.101915] really_probe+0x1b4/0x28c
> [ 79.105568] driver_probe_device+0x64/0xfc
> [ 79.109655] __driver_attach+0x94/0xcc
> [ 79.113395] bus_for_each_dev+0x84/0xcc
> [ 79.117221] driver_attach+0x2c/0x38
> [ 79.120788] bus_add_driver+0x120/0x1f4
> [ 79.124614] driver_register+0x64/0xf8
> [ 79.128355] __platform_driver_register+0x4c/0x58
> [ 79.133049] mtk_cci_devfreq_init+0x1c/0x24
> [ 79.137224] do_one_initcall+0x1c0/0x3e0
> [ 79.141138] do_initcall_level+0x1f4/0x224
> [ 79.145225] do_basic_setup+0x34/0x4c
> [ 79.148878] kernel_init_freeable+0x10c/0x194
> [ 79.153225] kernel_init+0x14/0x100
> [ 79.156705] ret_from_fork+0x10/0x18
> [ 79.160270] irq event stamp: 238006
> [ 79.163750] hardirqs last enabled at (238005):
> [<ffffffa71fdea0a4>] _raw_spin_unlock_irqrestore+0x40/0x84
> [ 79.173391] hardirqs last disabled at (238006):
> [<ffffffa71f480e78>] do_debug_exception+0x70/0x198
> [ 79.182337] softirqs last enabled at (237998):
> [<ffffffa71f48165c>] __do_softirq+0x45c/0x4a4
> [ 79.190850] softirqs last disabled at (237987):
> [<ffffffa71f4bc0d4>] irq_exit+0xd8/0xf8
> [ 79.198842] ---[ end trace 0e66a55077a0abab ]---
>
> In _find_opp_of_np()[1], there's
> lockdep_assert_held(&opp_table_lock);
>
> [1] https://elixir.bootlin.com/linux/latest/source/drivers/opp/of.c#L75
>
> But in governor passive.c#cpufreq_passive_register(), it call
> dev_pm_opp_find_opp_of_np() directly, so it wouldn't access
> opp_table_lock lock.
>
> Another similar place is in dev_pm_opp_of_add_table(), most devfreq
> would call this to get opp table.
> dev_pm_opp_of_add_table
> --> _opp_add_static_v2
> --> _of_opp_alloc_required_opps // would goes here if opp
> table contains "required-opps" property.
> --> _find_opp_of_np
> cpufreq-map governor needs devfreq to have "required-opps" property.
> So it would also trigger above lockdep warning.
>
>
> The question is: Is lockdep_assert_held(&opp_table_lock); needed in
> above use cases? Since they don't need to modify device and opp lists.
>
> Thanks
>
>
>
--
Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc, is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
^ permalink raw reply
* RE: [PATCH v9 5/6] usb:cdns3 Add Cadence USB3 DRD Driver
From: Pawel Laszczak @ 2019-07-10 8:25 UTC (permalink / raw)
To: Felipe Balbi, devicetree@vger.kernel.org
Cc: gregkh@linuxfoundation.org, linux-usb@vger.kernel.org,
hdegoede@redhat.com, heikki.krogerus@linux.intel.com,
robh+dt@kernel.org, rogerq@ti.com, linux-kernel@vger.kernel.org,
jbergsagel@ti.com, nsekhar@ti.com, nm@ti.com, Suresh Punnoose,
peter.chen@nxp.com, Jayshri Dajiram Pawar, Rahul Kumar
In-Reply-To: <BYAPR07MB4709EF3753AC0B87606B1182DDF70@BYAPR07MB4709.namprd07.prod.outlook.com>
Hi Felipe
>>> +
>>> +static int cdns3_idle_init(struct cdns3 *cdns)
>>> +{
>>> + struct cdns3_role_driver *rdrv;
>>> +
>>> + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
>>> + if (!rdrv)
>>> + return -ENOMEM;
>>> +
>>> + rdrv->start = cdns3_idle_role_start;
>>> + rdrv->stop = cdns3_idle_role_stop;
>>> + rdrv->state = CDNS3_ROLE_STATE_INACTIVE;
>>> + rdrv->suspend = NULL;
>>> + rdrv->resume = NULL;
>>> + rdrv->name = "idle";
>>
>>why don't you use the mux framework for this? This looks a bit fishy
>>too. Why do you have your own driver registration structure for your
>>driver only?
>>
>
>I assume you mean interface defined in include/linux/usb/role.h.
>It's quite new framework and probably was added after I've start implementing cdns3 driver.
>At first glance it's look that I could use it.
I've started integrating driver with role switch framework.
Even if I use role switch interface , I still need this internal driver registration.
It's convenient to use fallowing structure.
struct cdns3_role_driver {
int (*start)(struct cdns3 *cdns);
void (*stop)(struct cdns3 *cdns);
int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
int (*resume)(struct cdns3 *cdns, bool hibernated);
const char *name;
#define CDNS3_ROLE_STATE_INACTIVE 0
#define CDNS3_ROLE_STATE_ACTIVE 1
int state;
};
Driver can supports: only Host, only Device or DRD - depending on configuration.
If current configuration support only Host then driver assigns:
rdrv_host->start = __cdns3_host_init;
rdrv_host->stop = cdns3_host_exit;
cdns->roles[CDNS3_ROLE_HOST] = rdrv_host
cdns->roles[CDNS3_ROLE_GADGET = NULL;
if support only Device then:
rdrv_dev->start = __cdns3_ gadget _init;
rdrv_dev->stop = cdns3_ gadget _exit;
cdns->roles[CDNS3_ROLE_HOST] = NULL;
for DRD:
cdns->roles[CDNS3_ROLE_HOST = rdrv_host;
cdns->roles[CDNS3_ROLE_GADGET] = rdrv_dev;
So for DRD we will have both filled, but for only Device or Host we
will have filled single element of array.
With such array we can easily start/stop role by
if (!cdns->roles[role])
not supported by configuration.
else
cdns->roles[role]->start / cdns->roles[role]->stop
I don't need any extra: switch instruction or #ifdef statement.
The name cdns3_role_driver can be misleading.
Driver doesn't register the driver but rather the interface to Device/Host.
Maybe I should change this name to cdns3_role_interface or cdns3_role_action ?
Now I have my private enum:
enum cdns3_roles {
CDNS3_ROLE_IDLE = 0,
CDNS3_ROLE_HOST,
CDNS3_ROLE_GADGET,
CDNS3_ROLE_END,
};
I think I could replace it with usb_role. I need one extra state for IDLE but
Instead of CDNS3_ROLE_IDLE I can use USB_ROLE_NONE.
It should little simplify the driver and improve readability.
Do you have any comments or suggestion ?
Cheers,
Pawel
>
>>> +
>>> + cdns->roles[CDNS3_ROLE_IDLE] = rdrv;
>>> +
>>> + return 0;
>>> +}
>>> +
^ permalink raw reply
* Re: [PATCH V4 2/2] gpio: inverter: document the inverter bindings
From: Harish Jenny K N @ 2019-07-10 8:28 UTC (permalink / raw)
To: Rob Herring
Cc: Linus Walleij, Bartosz Golaszewski, Mark Rutland, devicetree,
open list:GPIO SUBSYSTEM, Balasubramani Vivekanandan
In-Reply-To: <CAL_JsqLQvjtnfUsZ2RP4eozvdwMLzNxtgmT+XFaxW4xzoFjL=w@mail.gmail.com>
Hi,
On 09/07/19 9:38 PM, Rob Herring wrote:
> On Mon, Jul 8, 2019 at 11:25 PM Harish Jenny K N
> <harish_kandiga@mentor.com> wrote:
>> Hi Rob,
>>
>>
>> On 09/07/19 4:06 AM, Rob Herring wrote:
>>> On Fri, Jun 28, 2019 at 3:31 AM Harish Jenny K N
>>> <harish_kandiga@mentor.com> wrote:
>>>> Document the device tree binding for the inverter gpio
>>>> controller to configure the polarity of the gpio pins
>>>> used by the consumers.
>>>>
>>>> Signed-off-by: Harish Jenny K N <harish_kandiga@mentor.com>
>>>> ---
>>>> .../devicetree/bindings/gpio/gpio-inverter.txt | 29 ++++++++++++++++++++++
>>>> 1 file changed, 29 insertions(+)
>>>> create mode 100644 Documentation/devicetree/bindings/gpio/gpio-inverter.txt
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/gpio/gpio-inverter.txt b/Documentation/devicetree/bindings/gpio/gpio-inverter.txt
>>>> new file mode 100644
>>>> index 0000000..8bb6b2e
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/gpio/gpio-inverter.txt
>>>> @@ -0,0 +1,29 @@
>>>> +GPIO-INVERTER
>>>> +======
>>>> +This binding defines the gpio-inverter. The gpio-inverter is a driver that
>>>> +allows to properly describe the gpio polarities on the hardware.
>>> I don't understand. Please explain this in terms of the hardware, not a driver.
>>
>> gpio inverters can be used on different hardware to alter the polarity of gpio chips.
>> The polarity of pins can change from hardware to hardware with the use of inverters.
> Yes, I know what an inverter is.
>
>> This device tree binding models gpio inverters in the device tree to properly describe the hardware.
> We already define the active state of GPIOs in the consumers. If
> there's an inverter in the middle, the consumer active state is simply
> inverted. I don't agree that that is a hack as Linus said without some
> reasoning why an inverter needs to be modeled in DT. Anything about
> what 'userspace' needs is not a reason. That's a Linux thing that has
> little to do with hardware description.
Yes we are talking about the hardware level inversions here. The usecase is for those without the gpio consumer driver. The usecase started with the concept of allowing an abstraction of the underlying hardware for the userland controlling program such that this program does not care whether the GPIO lines are inverted or not physically. In other words, a single userland controlling program can work unmodified across a variety of hardware platforms with the device tree mapping the logical to physical relationship of the GPIO hardware.
I totally understand anything about what 'userspace' needs is not a reason, but this is not restricted to userspace alone as kernel drivers may need this just as much. Also we are just modelling/describing the hardware state in the device tree.
Just to mention that Linus Walleij had proposed this inverter model to describe the hardware and the gpio inverter driver is developed based on comments/review from him.
Also my sincere request to Linus Walleij to please let his opinion know on this.
Thanks,
Best Regards,
Harish Jenny K N
^ permalink raw reply
* Re: [v4 2/6] media: platform: dwc: Add MIPI CSI-2 controller driver
From: Eugen.Hristev @ 2019-07-10 8:59 UTC (permalink / raw)
To: Luis.Oliveira, mchehab, davem, gregkh, Jonathan.Cameron, robh,
Nicolas.Ferre, paulmck, mark.rutland, kishon, devicetree,
linux-media, linux-kernel
Cc: Joao.Pinto
In-Reply-To: <1560280855-18085-3-git-send-email-luis.oliveira@synopsys.com>
Hello Luis,
A few questions inline:
On 11.06.2019 22:20, Luis Oliveira wrote:
> Add the Synopsys MIPI CSI-2 controller driver. This
> controller driver is divided in platform functions and core functions.
> This way it serves as platform for future DesignWare drivers.
>
> Signed-off-by: Luis Oliveira <luis.oliveira@synopsys.com>
> ---
> Changelog
> v3-v4
> - fix v4l2_fwnode_endpoint bad initialization @eugen
> - removed extra lines and fixed coding style issues
>
> MAINTAINERS | 8 +
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/dwc/Kconfig | 19 +
> drivers/media/platform/dwc/Makefile | 9 +
> drivers/media/platform/dwc/dw-csi-plat.c | 475 +++++++++++++++++++++++
> drivers/media/platform/dwc/dw-csi-plat.h | 97 +++++
> drivers/media/platform/dwc/dw-csi-sysfs.c | 624 ++++++++++++++++++++++++++++++
> drivers/media/platform/dwc/dw-mipi-csi.c | 569 +++++++++++++++++++++++++++
> drivers/media/platform/dwc/dw-mipi-csi.h | 287 ++++++++++++++
> include/media/dwc/dw-mipi-csi-pltfrm.h | 104 +++++
> 11 files changed, 2195 insertions(+)
> create mode 100644 drivers/media/platform/dwc/Kconfig
> create mode 100644 drivers/media/platform/dwc/Makefile
> create mode 100644 drivers/media/platform/dwc/dw-csi-plat.c
> create mode 100644 drivers/media/platform/dwc/dw-csi-plat.h
> create mode 100644 drivers/media/platform/dwc/dw-csi-sysfs.c
> create mode 100644 drivers/media/platform/dwc/dw-mipi-csi.c
> create mode 100644 drivers/media/platform/dwc/dw-mipi-csi.h
> create mode 100644 include/media/dwc/dw-mipi-csi-pltfrm.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 16a97ba..6ffe4fd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15187,6 +15187,14 @@ S: Maintained
> F: drivers/dma/dwi-axi-dmac/
> F: Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.txt
>
> +SYNOPSYS DESIGNWARE MIPI DPHY CSI-2 HOST DRIVER
> +M: Luis Oliveira <luis.oliveira@synopsys.com>
> +L: linux-media@vger.kernel.org
> +T: git git://linuxtv.org/media_tree.git
> +S: Maintained
> +F: drivers/media/platform/dwc
> +F: Documentation/devicetree/bindings/media/snps,dw-csi.txt
> +
> SYNOPSYS DESIGNWARE DMAC DRIVER
> M: Viresh Kumar <vireshk@kernel.org>
> R: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 8a19654..b6fb139 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -147,6 +147,7 @@ source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> source "drivers/media/platform/sunxi/sun6i-csi/Kconfig"
> +source "drivers/media/platform/dwc/Kconfig"
>
> config VIDEO_TI_CAL
> tristate "TI CAL (Camera Adaptation Layer) driver"
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 7cbbd92..4807caf 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -101,3 +101,5 @@ obj-y += meson/
> obj-y += cros-ec-cec/
>
> obj-$(CONFIG_VIDEO_SUN6I_CSI) += sunxi/sun6i-csi/
> +
> +obj-y += dwc/
> diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
> new file mode 100644
> index 0000000..508ac21
> --- /dev/null
> +++ b/drivers/media/platform/dwc/Kconfig
> @@ -0,0 +1,19 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Synopsys DWC Platform drivers
> +# Drivers here are currently for MIPI CSI-2 support
> +
> +config DWC_MIPI_CSI2_HOST
> + tristate "Synopsys DesignWare CSI-2 Host Controller support"
> + select VIDEO_DEV
> + select VIDEO_V4L2
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + help
> + This selects the DesignWare MIPI CSI-2 host controller support. This
> + controller gives access to control a CSI-2 receiver acting as a V4L2
> + subdevice.
> +
> + If you have a controller with this interface, say Y.
> +
> + If unsure, say N.
> diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
> new file mode 100644
> index 0000000..057f137
> --- /dev/null
> +++ b/drivers/media/platform/dwc/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for Synopsys DWC Platform drivers
> +#
> +dw-csi-objs := dw-csi-plat.o dw-mipi-csi.o
> +ifeq ($(CONFIG_DWC_MIPI_TC_DPHY_GEN3),y)
> + dw-csi-objs += dw-csi-sysfs.o
> +endif
> +obj-$(CONFIG_DWC_MIPI_CSI2_HOST) += dw-csi.o
> diff --git a/drivers/media/platform/dwc/dw-csi-plat.c b/drivers/media/platform/dwc/dw-csi-plat.c
> new file mode 100644
> index 0000000..9828d55
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-csi-plat.c
> @@ -0,0 +1,475 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> + * Platform driver
> + *
> + * Author: Luis Oliveira <luis.oliveira@synopsys.com>
> + */
> +
> +#include <media/dwc/dw-dphy-data.h>
> +
> +#include "dw-csi-plat.h"
> +
> +const struct mipi_dt csi_dt[] = {
> + {
> + .hex = CSI_2_YUV420_8,
> + .name = "YUV420_8bits",
> + }, {
> + .hex = CSI_2_YUV420_10,
> + .name = "YUV420_10bits",
> + }, {
> + .hex = CSI_2_YUV420_8_LEG,
> + .name = "YUV420_8bits_LEGACY",
> + }, {
> + .hex = CSI_2_YUV420_8_SHIFT,
> + .name = "YUV420_8bits_SHIFT",
> + }, {
> + .hex = CSI_2_YUV420_10_SHIFT,
> + .name = "YUV420_10bits_SHIFT",
> + }, {
> + .hex = CSI_2_YUV422_8,
> + .name = "YUV442_8bits",
> + }, {
> + .hex = CSI_2_YUV422_10,
> + .name = "YUV442_10bits",
> + }, {
> + .hex = CSI_2_RGB444,
> + .name = "RGB444",
> + }, {
> + .hex = CSI_2_RGB555,
> + .name = "RGB555",
> + }, {
> + .hex = CSI_2_RGB565,
> + .name = "RGB565",
> + }, {
> + .hex = CSI_2_RGB666,
> + .name = "RGB666",
> + }, {
> + .hex = CSI_2_RGB888,
> + .name = "RGB888",
> + }, {
> + .hex = CSI_2_RAW6,
> + .name = "RAW6",
> + }, {
> + .hex = CSI_2_RAW7,
> + .name = "RAW7",
> + }, {
> + .hex = CSI_2_RAW8,
> + .name = "RAW8",
> + }, {
> + .hex = CSI_2_RAW10,
> + .name = "RAW10",
> + }, {
> + .hex = CSI_2_RAW12,
> + .name = "RAW12",
> + }, {
> + .hex = CSI_2_RAW14,
> + .name = "RAW14",
> + }, {
> + .hex = CSI_2_RAW16,
> + .name = "RAW16",
> + },
> +};
> +
> +static struct mipi_fmt *
> +find_dw_mipi_csi_format(struct v4l2_mbus_framefmt *mf)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(dw_mipi_csi_formats); i++)
> + if (mf->code == dw_mipi_csi_formats[i].mbus_code)
> + return &dw_mipi_csi_formats[i];
> +
> + return NULL;
> +}
> +
> +static int dw_mipi_csi_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->index >= ARRAY_SIZE(dw_mipi_csi_formats))
> + return -EINVAL;
> +
> + if (code->index != 0)
> + return -EINVAL;
What is the purpose of checking if index >= array_size, if any value
different from 0 will return an error anyway ?
> +
> + code->code = dw_mipi_csi_formats[code->index].mbus_code;
> + return 0;
> +}
> +
> +static struct mipi_fmt *
> +dw_mipi_csi_try_format(struct v4l2_mbus_framefmt *mf)
> +{
> + struct mipi_fmt *fmt;
> +
> + fmt = find_dw_mipi_csi_format(mf);
> + if (!fmt)
> + fmt = &dw_mipi_csi_formats[0];
> +
> + mf->code = fmt->mbus_code;
> +
> + return fmt;
> +}
> +
> +static struct v4l2_mbus_framefmt *
> +dw_mipi_csi_get_format(struct dw_csi *dev, struct v4l2_subdev_pad_config *cfg,
> + enum v4l2_subdev_format_whence which)
> +{
> + if (which == V4L2_SUBDEV_FORMAT_TRY)
> + return cfg ? v4l2_subdev_get_try_format(&dev->sd,
> + cfg,
> + 0) : NULL;
> + dev_dbg(dev->dev,
> + "%s got v4l2_mbus_pixelcode. 0x%x\n", __func__,
> + dev->format.code);
> + dev_dbg(dev->dev,
> + "%s got width. 0x%x\n", __func__,
> + dev->format.width);
> + dev_dbg(dev->dev,
> + "%s got height. 0x%x\n", __func__,
> + dev->format.height);
> + return &dev->format;
> +}
> +
> +static int
> +dw_mipi_csi_set_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_format *fmt)
> +{
> + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> + struct mipi_fmt *dev_fmt;
> + struct v4l2_mbus_framefmt *mf = dw_mipi_csi_get_format(dev, cfg,
> + fmt->which);
> + int i;
> +
> + dev_fmt = dw_mipi_csi_try_format(&fmt->format);
> +
> + if (dev_fmt) {
> + *mf = fmt->format;
> + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
> + dev->fmt = dev_fmt;
> + dev->fmt->mbus_code = mf->code;
> + dw_mipi_csi_set_ipi_fmt(dev);
> + }
> +
> + if (fmt->format.width > 0 && fmt->format.height > 0) {
> + dw_mipi_csi_fill_timings(dev, fmt);
> + } else {
> + dev_vdbg(dev->dev, "%s unacceptable values 0x%x.\n",
> + __func__, fmt->format.width);
> + dev_vdbg(dev->dev, "%s unacceptable values 0x%x.\n",
> + __func__, fmt->format.height);
> + return -EINVAL;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(csi_dt); i++)
> + if (csi_dt[i].hex == dev->ipi_dt) {
> + dev_vdbg(dev->dev, "Using data type %s\n",
> + csi_dt[i].name);
> + }
> + return 0;
> +}
> +
> +static int
> +dw_mipi_csi_get_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_format *fmt)
> +{
> + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> + struct v4l2_mbus_framefmt *mf;
> +
> + mf = dw_mipi_csi_get_format(dev, cfg, fmt->which);
> + if (!mf)
> + return -EINVAL;
> +
> + mutex_lock(&dev->lock);
> + fmt->format = *mf;
> + mutex_unlock(&dev->lock);
> +
> + return 0;
> +}
> +
> +static int
> +dw_mipi_csi_s_power(struct v4l2_subdev *sd, int on)
> +{
> + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> +
> + dev_vdbg(dev->dev, "%s: on=%d\n", __func__, on);
> +
> + if (on) {
> + dw_mipi_csi_hw_stdby(dev);
> + dw_mipi_csi_start(dev);
> + } else {
> + phy_power_off(dev->phy);
> + dw_mipi_csi_mask_irq_power_off(dev);
> + /* reset data type */
> + dev->ipi_dt = 0x0;
> + }
> + return 0;
> +}
> +
> +static int
> +dw_mipi_csi_log_status(struct v4l2_subdev *sd)
> +{
> + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> +
> + dw_mipi_csi_dump(dev);
> +
> + return 0;
> +}
> +
> +#if IS_ENABLED(CONFIG_VIDEO_ADV_DEBUG)
> +static int
> +dw_mipi_csi_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
> +{
> + struct dw_csi *dev = sd_to_mipi_csi_dev(sd);
> +
> + dev_vdbg(dev->dev, "%s: reg=%llu\n", __func__, reg->reg);
> + reg->val = dw_mipi_csi_read(dev, reg->reg);
> +
> + return 0;
> +}
> +#endif
> +static int dw_mipi_csi_init_cfg(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg)
> +{
> + struct v4l2_mbus_framefmt *format =
> + v4l2_subdev_get_try_format(sd, cfg, 0);
> +
> + format->colorspace = V4L2_COLORSPACE_SRGB;
> + format->code = MEDIA_BUS_FMT_RGB888_1X24;
> + format->field = V4L2_FIELD_NONE;
> +
> + return 0;
> +}
> +
> +static struct v4l2_subdev_core_ops dw_mipi_csi_core_ops = {
> + .s_power = dw_mipi_csi_s_power,
> + .log_status = dw_mipi_csi_log_status,
> +#if IS_ENABLED(CONFIG_VIDEO_ADV_DEBUG)
> + .g_register = dw_mipi_csi_g_register,
> +#endif
> +};
> +
> +static struct v4l2_subdev_pad_ops dw_mipi_csi_pad_ops = {
> + .init_cfg = dw_mipi_csi_init_cfg,
> + .enum_mbus_code = dw_mipi_csi_enum_mbus_code,
> + .get_fmt = dw_mipi_csi_get_fmt,
> + .set_fmt = dw_mipi_csi_set_fmt,
> +};
> +
> +static struct v4l2_subdev_ops dw_mipi_csi_subdev_ops = {
> + .core = &dw_mipi_csi_core_ops,
> + .pad = &dw_mipi_csi_pad_ops,
> +};
> +
> +static irqreturn_t dw_mipi_csi_irq1(int irq, void *dev_id)
This naming 'irq1' is pretty unfortunate, do you agree ?
> +{
> + struct dw_csi *csi_dev = dev_id;
> +
> + dw_mipi_csi_irq_handler(csi_dev);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int
> +dw_mipi_csi_parse_dt(struct platform_device *pdev, struct dw_csi *dev)
> +{
> + struct device_node *node = pdev->dev.of_node;
> + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> + int ret = 0;
> +
> + if (of_property_read_u32(node, "snps,output-type",
> + &dev->hw.output))
> + dev->hw.output = 2;
> +
> + node = of_graph_get_next_endpoint(node, NULL);
> + if (!node) {
> + dev_err(&pdev->dev, "No port node at %pOF\n",
> + pdev->dev.of_node);
> + return -EINVAL;
> + }
> + /* Get port node and validate MIPI-CSI channel id. */
> + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &ep);
> + if (ret)
> + goto err;
> +
> + dev->index = ep.base.port - 1;
> + if (dev->index >= CSI_MAX_ENTITIES) {
> + ret = -ENXIO;
> + goto err;
> + }
> + dev->hw.num_lanes = ep.bus.mipi_csi2.num_data_lanes;
> +err:
> + of_node_put(node);
> + return ret;
> +}
> +
> +static const struct of_device_id dw_mipi_csi_of_match[];
> +
> +static int dw_csi_probe(struct platform_device *pdev)
> +{
> + const struct of_device_id *of_id = NULL;
> + struct device *dev = &pdev->dev;
> + struct resource *res = NULL;
> + struct dw_csi *csi;
> + struct v4l2_subdev *sd;
> + int ret;
> +
> + dev_vdbg(dev, "Probing started\n");
> +
> + /* Resource allocation */
> + csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL);
> + if (!csi)
> + return -ENOMEM;
> +
> + mutex_init(&csi->lock);
> + spin_lock_init(&csi->slock);
> + csi->dev = dev;
> +
> + of_id = of_match_node(dw_mipi_csi_of_match, dev->of_node);
> + if (!of_id)
> + return -EINVAL;
> +
> + ret = dw_mipi_csi_parse_dt(pdev, csi);
> + if (ret < 0)
> + return ret;
> +
> + csi->phy = devm_of_phy_get(dev, dev->of_node, NULL);
> + if (IS_ERR(csi->phy)) {
> + dev_err(dev, "No DPHY available\n");
> + return PTR_ERR(csi->phy);
> + }
> + /* Registers mapping */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res)
> + return -ENXIO;
> +
> + csi->base_address = devm_ioremap_resource(dev, res);
> + if (IS_ERR(csi->base_address)) {
> + dev_err(dev, "Base address not set.\n");
> + return PTR_ERR(csi->base_address);
> + }
> +
> + csi->ctrl_irq_number = platform_get_irq(pdev, 0);
> + if (csi->ctrl_irq_number < 0) {
> + dev_err(dev, "irq number %d not set.\n", csi->ctrl_irq_number);
> + ret = csi->ctrl_irq_number;
> + goto end;
> + }
> +
> + csi->rst = devm_reset_control_get_optional_shared(dev, NULL);
> + if (IS_ERR(csi->rst)) {
> + dev_err(dev, "error getting reset control %d\n", ret);
this is optional ? in case it does not exist, there is no error ?
> + return PTR_ERR(csi->rst);
> + }
> +
> + ret = devm_request_irq(dev, csi->ctrl_irq_number,
> + dw_mipi_csi_irq1, IRQF_SHARED,
> + dev_name(dev), csi);
> + if (ret) {
> + dev_err(dev, "irq csi %s failed\n", of_id->name);
> +
> + goto end;
if this fails and you go to end, media entity cleanup is called, but it
was never initialized ? does it matter ?
> + }
> +
> + sd = &csi->sd;
> + v4l2_subdev_init(sd, &dw_mipi_csi_subdev_ops);
> + csi->sd.owner = THIS_MODULE;
> +
> + snprintf(sd->name, sizeof(sd->name), "%s.%d",
> + "dw-csi", csi->index);
> +
> + csi->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> + csi->fmt = &dw_mipi_csi_formats[0];
> + csi->format.code = dw_mipi_csi_formats[0].mbus_code;
> +
> + sd->entity.function = MEDIA_ENT_F_IO_V4L;
> + csi->pads[CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> + csi->pads[CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> + ret = media_entity_pads_init(&csi->sd.entity,
> + CSI_PADS_NUM, csi->pads);
> +
> + dev_vdbg(dev, "v4l2.name: %s\n", csi->v4l2_dev.name);
> +
> + if (ret < 0) {
> + dev_err(dev, "media entity init failed\n");
> + goto end;
> + }
> +
> + v4l2_set_subdevdata(&csi->sd, pdev);
> + platform_set_drvdata(pdev, &csi->sd);
> + dev_set_drvdata(dev, sd);
> +
> + if (csi->rst)
> + reset_control_deassert(csi->rst);
> +#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
> + dw_csi_create_capabilities_sysfs(pdev);
> +#endif
> + dw_mipi_csi_get_version(csi);
> + dw_mipi_csi_specific_mappings(csi);
> + dw_mipi_csi_mask_irq_power_off(csi);
> +
> + dev_info(dev, "DW MIPI CSI-2 Host registered successfully HW v%u.%u\n",
> + csi->hw_version_major, csi->hw_version_minor);
> +
> + phy_init(csi->phy);
> +
> + return 0;
> +
> +end:
> + media_entity_cleanup(&csi->sd.entity);
> + v4l2_device_unregister(csi->vdev.v4l2_dev);
> +
> + return ret;
> +}
> +
> +/**
> + * @short Exit routine - Exit point of the driver
> + * @param[in] pdev pointer to the platform device structure
> + * @return 0 on success
> + */
> +static int dw_csi_remove(struct platform_device *pdev)
> +{
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *mipi_csi = sd_to_mipi_csi_dev(sd);
> +
> + dev_dbg(&pdev->dev, "Removing MIPI CSI-2 module\n");
> +
> + if (mipi_csi->rst)
> + reset_control_assert(mipi_csi->rst);
> + media_entity_cleanup(&mipi_csi->sd.entity);
> +
> + return 0;
> +}
> +
> +/**
> + * @short of_device_id structure
> + */
> +static const struct of_device_id dw_mipi_csi_of_match[] = {
> + { .compatible = "snps,dw-csi" },
> + {},
> +};
> +
> +MODULE_DEVICE_TABLE(of, dw_mipi_csi_of_match);
> +
> +/**
> + * @short Platform driver structure
> + */
> +static struct platform_driver dw_mipi_csi_driver = {
> + .remove = dw_csi_remove,
> + .probe = dw_csi_probe,
> + .driver = {
> + .name = "dw-csi",
> + .owner = THIS_MODULE,
> + .of_match_table = of_match_ptr(dw_mipi_csi_of_match),
> + },
> +};
> +
> +module_platform_driver(dw_mipi_csi_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Luis Oliveira <luis.oliveira@synopsys.com>");
> +MODULE_DESCRIPTION("Synopsys DesignWare MIPI CSI-2 Host Platform driver");
> diff --git a/drivers/media/platform/dwc/dw-csi-plat.h b/drivers/media/platform/dwc/dw-csi-plat.h
> new file mode 100644
> index 0000000..e322592
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-csi-plat.h
> @@ -0,0 +1,97 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018 Synopsys, Inc.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> + * Supported bus formats
> + *
> + * Author: Luis Oliveira <Luis.Oliveira@synopsys.com>
> + */
> +
> +#ifndef _DW_CSI_PLAT_H__
> +#define _DW_CSI_PLAT_H__
> +
> +#include "dw-mipi-csi.h"
> +
> +/* Video formats supported by the MIPI CSI-2 */
> +struct mipi_fmt dw_mipi_csi_formats[] = {
> + {
> + /* RAW 8 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .depth = 8,
> + }, {
> + /* RAW 10 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .depth = 10,
> + }, {
> + /* RAW 12 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
> + .depth = 12,
> + }, {
> + /* RAW 14 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14,
> + .depth = 14,
> + }, {
> + /* RAW 16 */
> + .mbus_code = MEDIA_BUS_FMT_SBGGR16_1X16,
> + .depth = 16,
> + }, {
> + /* RGB 666 */
> + .mbus_code = MEDIA_BUS_FMT_RGB666_1X18,
> + .depth = 18,
> + }, {
> + /* RGB 565 */
> + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE,
> + .depth = 16,
> + }, {
> + /* BGR 565 */
> + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE,
> + .depth = 16,
> + }, {
> + /* RGB 555 */
> + .mbus_code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
> + .depth = 16,
> + }, {
> + /* BGR 555 */
> + .mbus_code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
> + .depth = 16,
> + }, {
> + /* RGB 444 */
> + .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE,
> + .depth = 16,
> + }, {
> + /* RGB 444 */
> + .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE,
> + .depth = 16,
> + }, {
> + /* RGB 888 */
> + .mbus_code = MEDIA_BUS_FMT_RGB888_2X12_LE,
> + .depth = 24,
> + }, {
> + /* BGR 888 */
> + .mbus_code = MEDIA_BUS_FMT_RGB888_2X12_BE,
> + .depth = 24,
> + }, {
> + /* BGR 888 */
> + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
> + .depth = 24,
> + }, {
> + /* YUV 422 8-bit */
> + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16,
> + .depth = 16,
> + }, {
> + /* YUV 422 10-bit */
> + .mbus_code = MEDIA_BUS_FMT_VYUY10_2X10,
> + .depth = 20,
> + }, {
> + /* YUV 420 8-bit LEGACY */
> + .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
> + .depth = 8,
> + }, {
> + /* YUV 420 10-bit */
> + .mbus_code = MEDIA_BUS_FMT_Y10_1X10,
> + .depth = 10,
> + },
> +};
> +
> +#endif /* _DW_CSI_PLAT_H__ */
> diff --git a/drivers/media/platform/dwc/dw-csi-sysfs.c b/drivers/media/platform/dwc/dw-csi-sysfs.c
> new file mode 100644
> index 0000000..e8d2bb9
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-csi-sysfs.c
> @@ -0,0 +1,624 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver.
> + * SysFS components for the platform driver
> + *
> + * Author: Luis Oliveira <Luis.Oliveira@synopsys.com>
> + */
> +
> +#include "dw-mipi-csi.h"
> +
> +static ssize_t core_version_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "v.%d.%d*\n", csi_dev->hw_version_major,
> + csi_dev->hw_version_minor);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t n_lanes_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long lanes;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &lanes);
> + if (ret < 0)
> + return ret;
> +
> + if (lanes > 8) {
> + dev_err(dev, "Invalid number of lanes %lu\n", lanes);
> + return count;
> + }
> +
> + dev_info(dev, "Lanes %lu\n", lanes);
> + csi_dev->hw.num_lanes = lanes;
> +
> + return count;
> +}
> +
> +static ssize_t n_lanes_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.num_lanes);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t core_reset_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + /* Reset Controller and DPHY */
> + phy_reset(csi_dev->phy);
> + dw_mipi_csi_reset(csi_dev);
> +
> + snprintf(buffer, 10, "Reset\n");
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t data_type_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long dt;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &dt);
> + if (ret < 0)
> + return ret;
> +
> + if (dt < 0x18 || dt > 0x2F) {
These aren't some top/start defines in your list ?
> + dev_err(dev, "Invalid data type %lx\n", dt);
> + return count;
> + }
> +
> + dev_info(dev, "Data type 0x%lx\n", dt);
> + csi_dev->ipi_dt = dt;
> +
> + return count;
> +}
> +
> +static ssize_t data_type_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->ipi_dt);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t hsa_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long hsa;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &hsa);
> + if (ret < 0)
> + return ret;
> +
> + if (hsa > 0xFFF) {
> + dev_err(dev, "Invalid HSA time %lx\n", hsa);
> + return count;
> + }
> +
> + dev_info(dev, "HSA time 0x%lx\n", hsa);
> + csi_dev->hw.hsa = hsa;
> +
> + return count;
> +}
> +
> +static ssize_t hsa_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.hsa);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t hbp_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long hbp;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &hbp);
> + if (ret < 0)
> + return ret;
> +
> + if (hbp > 0xFFF) {
> + dev_err(dev, "Invalid HBP time %lx\n", hbp);
> + return count;
> + }
> +
> + dev_info(dev, "HBP time 0x%lx\n", hbp);
> + csi_dev->hw.hbp = hbp;
> +
> + return count;
> +}
> +
> +static ssize_t hbp_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.hbp);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t hsd_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long hsd;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &hsd);
> + if (ret < 0)
> + return ret;
> +
> + if (hsd > 0xFF) {
> + dev_err(dev, "Invalid HSD time %lx\n", hsd);
> + return count;
> + }
> +
> + dev_info(dev, "HSD time 0x%lx\n", hsd);
> + csi_dev->hw.hsd = hsd;
> +
> + return count;
> +}
> +
> +static ssize_t hsd_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.hsd);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t vsa_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long vsa;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &vsa);
> + if (ret < 0)
> + return ret;
> +
> + if (vsa > 0x3FF) {
> + dev_err(dev, "Invalid VSA period %lx\n", vsa);
> + return count;
> + }
> +
> + dev_info(dev, "VSA period 0x%lx\n", vsa);
> + csi_dev->hw.vsa = vsa;
> +
> + return count;
> +}
> +
> +static ssize_t vsa_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.vsa);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t vbp_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long vbp;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &vbp);
> + if (ret < 0)
> + return ret;
> +
> + if (vbp > 0x2FF) {
> + dev_err(dev, "Invalid VBP period %lx\n", vbp);
> + return count;
> + }
> +
> + dev_info(dev, "VBP period 0x%lx\n", vbp);
> + csi_dev->hw.vbp = vbp;
> +
> + return count;
> +}
> +
> +static ssize_t vbp_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.vbp);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t vfp_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long vfp;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 16, &vfp);
> + if (ret < 0)
> + return ret;
> +
> + if (vfp > 0x3ff) {
> + dev_err(dev, "Invalid VFP period %lx\n", vfp);
> + return count;
> + }
> +
> + dev_info(dev, "VFP period 0x%lx\n", vfp);
> + csi_dev->hw.vfp = vfp;
> +
> + return count;
> +}
> +
> +static ssize_t vfp_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%x\n", csi_dev->hw.vfp);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t virtual_channel_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long virtual_ch;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &virtual_ch);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)virtual_ch < 0 || (signed int)virtual_ch > 8) {
> + dev_err(dev, "Invalid Virtual Channel %lu\n", virtual_ch);
> + return count;
> + }
> +
> + dev_info(dev, "Virtual Channel %lu\n", virtual_ch);
> + csi_dev->hw.virtual_ch = virtual_ch;
> +
> + return count;
> +}
> +
> +static ssize_t virtual_channel_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.virtual_ch);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t ipi_color_mode_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long ipi_color_mode;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &ipi_color_mode);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)ipi_color_mode < 0 || (signed int)ipi_color_mode > 1) {
> + dev_err(dev,
> + "Wrong Color Mode %lu, (48 bits -> 0 or 16 bits -> 1\n",
> + ipi_color_mode);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Color mode %lu\n", ipi_color_mode);
> + csi_dev->hw.ipi_color_mode = ipi_color_mode;
> +
> + return count;
> +}
> +
> +static ssize_t ipi_color_mode_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_color_mode);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t ipi_auto_flush_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long ipi_auto_flush;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &ipi_auto_flush);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)ipi_auto_flush < 0 || (signed int)ipi_auto_flush > 1) {
> + dev_err(dev,
> + "Invalid Auto Flush Mode %lu, (No -> 0 or Yes -> 1\n",
> + ipi_auto_flush);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Auto Flush %lu\n", ipi_auto_flush);
> + csi_dev->hw.ipi_auto_flush = ipi_auto_flush;
> +
> + return count;
> +}
> +
> +static ssize_t ipi_auto_flush_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_auto_flush);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t ipi_timings_mode_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long ipi_mode;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &ipi_mode);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)ipi_mode < 0 || (signed int)ipi_mode > 1) {
> + dev_err(dev,
> + "Invalid Timing Source %lu (Camera:0|Controller:1)\n",
> + ipi_mode);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Color mode %lu\n", ipi_mode);
> + csi_dev->hw.ipi_mode = ipi_mode;
> +
> + return count;
> +}
> +
> +static ssize_t ipi_timings_mode_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.ipi_mode);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static ssize_t output_type_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + int ret;
> + unsigned long output;
> +
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + ret = kstrtoul(buf, 10, &output);
> + if (ret < 0)
> + return ret;
> +
> + if ((signed int)output < 0 || (signed int)output > 1) {
> + dev_err(dev,
> + "Invalid Core output %lu to be used \
> + (IPI-> 0 or IDI->1 or BOTH- 2\n",
> + output);
> + return count;
> + }
> +
> + dev_info(dev, "IPI Color mode %lu\n", output);
> + csi_dev->hw.output = output;
> +
> + return count;
> +}
> +
> +static ssize_t output_type_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct v4l2_subdev *sd = platform_get_drvdata(pdev);
> + struct dw_csi *csi_dev = sd_to_mipi_csi_dev(sd);
> +
> + char buffer[10];
> +
> + snprintf(buffer, 10, "%d\n", csi_dev->hw.output);
> +
> + return strlcpy(buf, buffer, PAGE_SIZE);
> +}
> +
> +static DEVICE_ATTR_RO(core_version);
> +static DEVICE_ATTR_RO(core_reset);
> +static DEVICE_ATTR_RW(n_lanes);
> +static DEVICE_ATTR_RW(data_type);
> +static DEVICE_ATTR_RW(hsa);
> +static DEVICE_ATTR_RW(hbp);
> +static DEVICE_ATTR_RW(hsd);
> +static DEVICE_ATTR_RW(vsa);
> +static DEVICE_ATTR_RW(vbp);
> +static DEVICE_ATTR_RW(vfp);
> +static DEVICE_ATTR_RW(virtual_channel);
> +static DEVICE_ATTR_RW(ipi_color_mode);
> +static DEVICE_ATTR_RW(ipi_auto_flush);
> +static DEVICE_ATTR_RW(ipi_timings_mode);
> +static DEVICE_ATTR_RW(output_type);
> +
> +int dw_csi_create_capabilities_sysfs(struct platform_device *pdev)
> +{
> + device_create_file(&pdev->dev, &dev_attr_core_version);
> + device_create_file(&pdev->dev, &dev_attr_core_reset);
> + device_create_file(&pdev->dev, &dev_attr_n_lanes);
> + device_create_file(&pdev->dev, &dev_attr_data_type);
> + device_create_file(&pdev->dev, &dev_attr_hsa);
> + device_create_file(&pdev->dev, &dev_attr_hbp);
> + device_create_file(&pdev->dev, &dev_attr_hsd);
> + device_create_file(&pdev->dev, &dev_attr_vsa);
> + device_create_file(&pdev->dev, &dev_attr_vbp);
> + device_create_file(&pdev->dev, &dev_attr_vfp);
> + device_create_file(&pdev->dev, &dev_attr_virtual_channel);
> + device_create_file(&pdev->dev, &dev_attr_ipi_color_mode);
> + device_create_file(&pdev->dev, &dev_attr_ipi_auto_flush);
> + device_create_file(&pdev->dev, &dev_attr_ipi_timings_mode);
> + device_create_file(&pdev->dev, &dev_attr_output_type);
> +
> + return 0;
> +}
> diff --git a/drivers/media/platform/dwc/dw-mipi-csi.c b/drivers/media/platform/dwc/dw-mipi-csi.c
> new file mode 100644
> index 0000000..d01418d
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-mipi-csi.c
> @@ -0,0 +1,569 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver
> + * Core MIPI CSI-2 functions
> + *
> + * Author: Luis Oliveira <Luis.Oliveira@synopsys.com>
> + */
> +
> +#include "dw-mipi-csi.h"
> +
> +static struct R_CSI2 reg = {
> + .VERSION = 0x00,
> + .N_LANES = 0x04,
> + .CTRL_RESETN = 0x08,
> + .INTERRUPT = 0x0C,
> + .DATA_IDS_1 = 0x10,
> + .DATA_IDS_2 = 0x14,
> + .IPI_MODE = 0x80,
> + .IPI_VCID = 0x84,
> + .IPI_DATA_TYPE = 0x88,
> + .IPI_MEM_FLUSH = 0x8C,
> + .IPI_HSA_TIME = 0x90,
> + .IPI_HBP_TIME = 0x94,
> + .IPI_HSD_TIME = 0x98,
> + .IPI_HLINE_TIME = 0x9C,
> + .IPI_SOFTRSTN = 0xA0,
> + .IPI_ADV_FEATURES = 0xAC,
> + .IPI_VSA_LINES = 0xB0,
> + .IPI_VBP_LINES = 0xB4,
> + .IPI_VFP_LINES = 0xB8,
> + .IPI_VACTIVE_LINES = 0xBC,
> + .INT_PHY_FATAL = 0xe0,
> + .MASK_INT_PHY_FATAL = 0xe4,
> + .FORCE_INT_PHY_FATAL = 0xe8,
> + .INT_PKT_FATAL = 0xf0,
> + .MASK_INT_PKT_FATAL = 0xf4,
> + .FORCE_INT_PKT_FATAL = 0xf8,
> + .INT_PHY = 0x110,
> + .MASK_INT_PHY = 0x114,
> + .FORCE_INT_PHY = 0x118,
> + .INT_LINE = 0x130,
> + .MASK_INT_LINE = 0x134,
> + .FORCE_INT_LINE = 0x138,
> + .INT_IPI = 0x140,
> + .MASK_INT_IPI = 0x144,
> + .FORCE_INT_IPI = 0x148,
> +};
> +
> +struct interrupt_type csi_int = {
> + .PHY_FATAL = BIT(0),
> + .PKT_FATAL = BIT(1),
> + .PHY = BIT(16),
> +};
> +
> +#define dw_print(VAR) \
> + dev_info(csi_dev->dev, "%s: 0x%x: %X\n", "#VAR#",\
> + VAR, dw_mipi_csi_read(csi_dev, VAR))
> +
> +void dw_mipi_csi_write_part(struct dw_csi *dev, u32 address, u32 data,
> + u8 shift, u8 width)
> +{
> + u32 mask = (1 << width) - 1;
> + u32 temp = dw_mipi_csi_read(dev, address);
> +
> + temp &= ~(mask << shift);
> + temp |= (data & mask) << shift;
> + dw_mipi_csi_write(dev, address, temp);
> +}
> +
> +void dw_mipi_csi_reset(struct dw_csi *csi_dev)
> +{
> + dw_mipi_csi_write(csi_dev, reg.CTRL_RESETN, 0);
> + usleep_range(100, 200);
> + dw_mipi_csi_write(csi_dev, reg.CTRL_RESETN, 1);
> +}
> +
> +int dw_mipi_csi_mask_irq_power_off(struct dw_csi *csi_dev)
> +{
> + if (csi_dev->hw_version_major == 1) {
> + /* set only one lane (lane 0) as active (ON) */
> + dw_mipi_csi_write(csi_dev, reg.N_LANES, 0);
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY_FATAL, 0);
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PKT_FATAL, 0);
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY, 0);
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_LINE, 0);
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_IPI, 0);
> +
> + /* only for version 1.30 */
> + if (csi_dev->hw_version_minor == 30)
> + dw_mipi_csi_write(csi_dev,
> + reg.MASK_INT_FRAME_FATAL, 0);
> +
> + dw_mipi_csi_write(csi_dev, reg.CTRL_RESETN, 0);
> +
> + /* only for version 1.40 */
> + if (csi_dev->hw_version_minor == 40) {
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_BNDRY_FRAME_FATAL, 0);
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_SEQ_FRAME_FATAL, 0);
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_CRC_FRAME_FATAL, 0);
> + dw_mipi_csi_write(csi_dev, reg.MSK_PLD_CRC_FATAL, 0);
> + dw_mipi_csi_write(csi_dev, reg.MSK_DATA_ID, 0);
> + dw_mipi_csi_write(csi_dev, reg.MSK_ECC_CORRECT, 0);
> + }
> + }
> +
> + return 0;
> +}
> +
> +int dw_mipi_csi_hw_stdby(struct dw_csi *csi_dev)
> +{
> + if (csi_dev->hw_version_major == 1) {
> + /* set only one lane (lane 0) as active (ON) */
> + dw_mipi_csi_reset(csi_dev);
> + dw_mipi_csi_write(csi_dev, reg.N_LANES, 0);
> + phy_init(csi_dev->phy);
> +
> + /* only for version 1.30 */
> + if (csi_dev->hw_version_minor == 30)
> + dw_mipi_csi_write(csi_dev,
> + reg.MASK_INT_FRAME_FATAL,
> + GENMASK(31, 0));
> +
> + /* common */
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY_FATAL,
> + GENMASK(8, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PKT_FATAL,
> + GENMASK(1, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_PHY, GENMASK(23, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_LINE, GENMASK(23, 0));
> + dw_mipi_csi_write(csi_dev, reg.MASK_INT_IPI, GENMASK(5, 0));
> +
> + /* only for version 1.40 */
> + if (csi_dev->hw_version_minor == 40) {
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_BNDRY_FRAME_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_SEQ_FRAME_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_CRC_FRAME_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_PLD_CRC_FATAL,
> + GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_DATA_ID, GENMASK(31, 0));
> + dw_mipi_csi_write(csi_dev,
> + reg.MSK_ECC_CORRECT, GENMASK(31, 0));
> + }
> + }
> + return 0;
> +}
> +
> +void dw_mipi_csi_set_ipi_fmt(struct dw_csi *csi_dev)
> +{
> + struct device *dev = csi_dev->dev;
> +
> + if (csi_dev->ipi_dt) {
> + dw_mipi_csi_write(csi_dev, reg.IPI_DATA_TYPE, csi_dev->ipi_dt);
> + switch (csi_dev->ipi_dt) {
> + case CSI_2_YUV420_8:
> + case CSI_2_YUV420_8_LEG:
> + case CSI_2_YUV420_8_SHIFT:
> + break;
> + case CSI_2_YUV420_10:
> + case CSI_2_YUV420_10_SHIFT:
> + break;
> + }
any reason for this switch instruction? does basically nothing.
> + } else {
> + switch (csi_dev->fmt->mbus_code) {
> + /* RGB 666 */
> + case MEDIA_BUS_FMT_RGB666_1X18:
> + csi_dev->ipi_dt = CSI_2_RGB666;
> + break;
> + /* RGB 565 */
> + case MEDIA_BUS_FMT_RGB565_2X8_BE:
> + case MEDIA_BUS_FMT_RGB565_2X8_LE:
> + csi_dev->ipi_dt = CSI_2_RGB565;
> + break;
> + /* RGB 555 */
> + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE:
> + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE:
> + csi_dev->ipi_dt = CSI_2_RGB555;
> + break;
> + /* RGB 444 */
> + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE:
> + case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE:
> + csi_dev->ipi_dt = CSI_2_RGB444;
> + break;
> + /* RGB 888 */
> + break;
> + case MEDIA_BUS_FMT_RGB888_2X12_LE:
> + case MEDIA_BUS_FMT_RGB888_2X12_BE:
> + csi_dev->ipi_dt = CSI_2_RGB888;
> + break;
> + /* RAW 10 */
> + case MEDIA_BUS_FMT_SBGGR10_1X10:
> + case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE:
> + csi_dev->ipi_dt = CSI_2_RAW10;
> + break;
> + /* RAW 12 */
> + case MEDIA_BUS_FMT_SBGGR12_1X12:
> + csi_dev->ipi_dt = CSI_2_RAW12;
> + break;
> + /* RAW 14 */
> + case MEDIA_BUS_FMT_SBGGR14_1X14:
> + csi_dev->ipi_dt = CSI_2_RAW14;
> + break;
> + /* RAW 16 */
> + case MEDIA_BUS_FMT_SBGGR16_1X16:
> + csi_dev->ipi_dt = CSI_2_RAW16;
> + break;
> + /* RAW 8 */
> + case MEDIA_BUS_FMT_SBGGR8_1X8:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + /* YUV 422 8-bit */
> + case MEDIA_BUS_FMT_YVYU8_2X8:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + /* YUV 422 10-bit */
> + case MEDIA_BUS_FMT_VYUY8_1X16:
> + csi_dev->ipi_dt = CSI_2_YUV422_8;
> + break;
> + /* YUV 420 8-bit LEGACY */
> + case MEDIA_BUS_FMT_Y8_1X8:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + /* YUV 420 10-bit */
> + case MEDIA_BUS_FMT_Y10_1X10:
> + csi_dev->ipi_dt = CSI_2_RAW8;
> + break;
> + default:
> + break;
> + }
> + dw_mipi_csi_write(csi_dev, reg.IPI_DATA_TYPE, csi_dev->ipi_dt);
> + }
> + dev_info(dev, "Selected IPI Data Type 0x%X\n", csi_dev->ipi_dt);
> +}
> +
> +void dw_mipi_csi_fill_timings(struct dw_csi *dev,
> + struct v4l2_subdev_format *fmt)
> +{
> + /* expected values */
> + dev->hw.virtual_ch = 0;
> + dev->hw.ipi_color_mode = COLOR48;
> + dev->hw.ipi_auto_flush = 1;
> + dev->hw.ipi_mode = CAMERA_TIMING;
> + dev->hw.ipi_cut_through = CTINACTIVE;
> + dev->hw.ipi_adv_features = LINE_EVENT_SELECTION(EVSELAUTO);
> + dev->hw.htotal = fmt->format.width + dev->hw.hsa +
> + dev->hw.hbp + dev->hw.hsd;
> + dev->hw.vactive = fmt->format.height;
> + dev->hw.output = 2;
> +
> + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> + dev_dbg(dev->dev, "*********** timings *********\n");
> + dev_dbg(dev->dev, "Horizontal Sync Active: %d\n", dev->hw.hsa);
> + dev_dbg(dev->dev, "Horizontal Back Porch: %d\n", dev->hw.hbp);
> + dev_dbg(dev->dev, "Horizontal Width: %d\n", fmt->format.width);
> + dev_dbg(dev->dev, "Horizontal Total: %d\n", dev->hw.htotal);
> + dev_dbg(dev->dev, "Vertical Sync Active: %d\n", dev->hw.vsa);
> + dev_dbg(dev->dev, "Vertical Back Porch: %d\n", dev->hw.vbp);
> + dev_dbg(dev->dev, "Vertical Front Porch: %d\n", dev->hw.vfp);
> + dev_dbg(dev->dev, "Vertical Active: %d\n", dev->hw.vactive);
> + }
> +}
> +
> +void dw_mipi_csi_start(struct dw_csi *csi_dev)
> +{
> + struct device *dev = csi_dev->dev;
> +
> + dw_mipi_csi_write(csi_dev, reg.N_LANES, (csi_dev->hw.num_lanes - 1));
> + dev_info(dev, "number of lanes: %d\n", csi_dev->hw.num_lanes);
> +
> + /* IPI Related Configuration */
> + if (csi_dev->hw.output == IPI_OUT || csi_dev->hw.output == BOTH_OUT) {
> + if (csi_dev->hw_version_major >= 1) {
> + if (csi_dev->hw_version_minor >= 20)
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_ADV_FEATURES,
> + csi_dev->hw.ipi_adv_features);
> + if (csi_dev->hw_version_minor >= 30)
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_SOFTRSTN, 0x1);
> + }
> + /* address | data, | shift | width */
> + dw_mipi_csi_write_part(csi_dev, reg.IPI_MODE, 1, 24, 1);
> + dw_mipi_csi_write_part(csi_dev,
> + reg.IPI_MODE,
> + csi_dev->hw.ipi_mode,
> + 0, 1);
> + if (csi_dev->hw.ipi_mode == CAMERA_TIMING) {
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_ADV_FEATURES,
> + LINE_EVENT_SELECTION(EVSELPROG) |
> + EN_VIDEO |
> + EN_LINE_START |
> + EN_NULL |
> + EN_BLANKING |
> + EN_EMBEDDED);
> + }
> + dw_mipi_csi_write_part(csi_dev,
> + reg.IPI_MODE,
> + csi_dev->hw.ipi_color_mode,
> + 8, 1);
> + dw_mipi_csi_write_part(csi_dev,
> + reg.IPI_MODE,
> + csi_dev->hw.ipi_cut_through,
> + 16, 1);
> + dw_mipi_csi_write_part(csi_dev,
> + reg.IPI_VCID,
> + csi_dev->hw.virtual_ch,
> + 0, 2);
> + dw_mipi_csi_write_part(csi_dev,
> + reg.IPI_MEM_FLUSH,
> + csi_dev->hw.ipi_auto_flush,
> + 8, 1);
> +
> + dev_vdbg(dev, "*********** config *********\n");
> + dev_vdbg(dev, "IPI enable: %s\n",
> + csi_dev->hw.output ? "YES" : "NO");
> + dev_vdbg(dev, "video mode transmission type: %s timming\n",
> + csi_dev->hw.ipi_mode ? "controller" : "camera");
> + dev_vdbg(dev, "Color Mode: %s\n",
> + csi_dev->hw.ipi_color_mode ? "16 bits" : "48 bits");
> + dev_vdbg(dev, "Cut Through Mode: %s\n",
> + csi_dev->hw.ipi_cut_through ? "enable" : "disable");
> + dev_vdbg(dev, "Virtual Channel: %d\n",
> + csi_dev->hw.virtual_ch);
> + dev_vdbg(dev, "Auto-flush: %d\n",
> + csi_dev->hw.ipi_auto_flush);
> + dw_mipi_csi_write(csi_dev, reg.IPI_SOFTRSTN, 1);
> +
> + if (csi_dev->hw.ipi_mode == AUTO_TIMING)
> + phy_power_on(csi_dev->phy);
> +
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_HSA_TIME, csi_dev->hw.hsa);
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_HBP_TIME, csi_dev->hw.hbp);
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_HSD_TIME, csi_dev->hw.hsd);
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_HLINE_TIME, csi_dev->hw.htotal);
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_VSA_LINES, csi_dev->hw.vsa);
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_VBP_LINES, csi_dev->hw.vbp);
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_VFP_LINES, csi_dev->hw.vfp);
> + dw_mipi_csi_write(csi_dev,
> + reg.IPI_VACTIVE_LINES, csi_dev->hw.vactive);
> + }
> + phy_power_on(csi_dev->phy);
> +}
> +
> +int dw_mipi_csi_irq_handler(struct dw_csi *csi_dev)
> +{
> + struct device *dev = csi_dev->dev;
> + u32 global_int_status, i_sts;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&csi_dev->slock, flags);
> + global_int_status = dw_mipi_csi_read(csi_dev, reg.INTERRUPT);
> +
> + if (global_int_status & csi_int.PHY_FATAL) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PHY_FATAL);
> + dev_err_ratelimited(dev, "int %08X: PHY FATAL: %08X\n",
> + reg.INT_PHY_FATAL, i_sts);
> + }
> +
> + if (global_int_status & csi_int.PKT_FATAL) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PKT_FATAL);
> + dev_err_ratelimited(dev, "int %08X: PKT FATAL: %08X\n",
> + reg.INT_PKT_FATAL, i_sts);
> + }
> +
> + if (global_int_status & csi_int.FRAME_FATAL &&
> + csi_dev->hw_version_major == 1 &&
> + csi_dev->hw_version_minor == 30) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_FRAME_FATAL);
> + dev_err_ratelimited(dev, "int %08X: FRAME FATAL: %08X\n",
> + reg.INT_FRAME_FATAL, i_sts);
> + }
> +
> + if (global_int_status & csi_int.PHY) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PHY);
> + dev_err_ratelimited(dev, "int %08X: PHY: %08X\n",
> + reg.INT_PHY, i_sts);
> + }
> +
> + if (global_int_status & csi_int.PKT &&
> + csi_dev->hw_version_major == 1 &&
> + csi_dev->hw_version_minor <= 30) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_PKT);
> + dev_err_ratelimited(dev, "int %08X: PKT: %08X\n",
> + reg.INT_PKT, i_sts);
> + }
> +
> + if (global_int_status & csi_int.LINE) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_LINE);
> + dev_err_ratelimited(dev, "int %08X: LINE: %08X\n",
> + reg.INT_LINE, i_sts);
> + }
> +
> + if (global_int_status & csi_int.IPI) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.INT_IPI);
> + dev_err_ratelimited(dev, "int %08X: IPI: %08X\n",
> + reg.INT_IPI, i_sts);
> + }
> +
> + if (global_int_status & csi_int.BNDRY_FRAME_FATAL) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.ST_BNDRY_FRAME_FATAL);
> + dev_err_ratelimited(dev,
> + "int %08X: ST_BNDRY_FRAME_FATAL: %08X\n",
> + reg.ST_BNDRY_FRAME_FATAL, i_sts);
> + }
> +
> + if (global_int_status & csi_int.SEQ_FRAME_FATAL) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.ST_SEQ_FRAME_FATAL);
> + dev_err_ratelimited(dev,
> + "int %08X: ST_SEQ_FRAME_FATAL: %08X\n",
> + reg.ST_SEQ_FRAME_FATAL, i_sts);
> + }
> +
> + if (global_int_status & csi_int.CRC_FRAME_FATAL) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.ST_CRC_FRAME_FATAL);
> + dev_err_ratelimited(dev,
> + "int %08X: ST_CRC_FRAME_FATAL: %08X\n",
> + reg.ST_CRC_FRAME_FATAL, i_sts);
> + }
> +
> + if (global_int_status & csi_int.PLD_CRC_FATAL) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.ST_PLD_CRC_FATAL);
> + dev_err_ratelimited(dev,
> + "int %08X: ST_PLD_CRC_FATAL: %08X\n",
> + reg.ST_PLD_CRC_FATAL, i_sts);
> + }
> +
> + if (global_int_status & csi_int.DATA_ID) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.ST_DATA_ID);
> + dev_err_ratelimited(dev, "int %08X: ST_DATA_ID: %08X\n",
> + reg.ST_DATA_ID, i_sts);
> + }
> +
> + if (global_int_status & csi_int.ECC_CORRECTED) {
> + i_sts = dw_mipi_csi_read(csi_dev, reg.ST_ECC_CORRECT);
> + dev_err_ratelimited(dev, "int %08X: ST_ECC_CORRECT: %08X\n",
> + reg.ST_ECC_CORRECT, i_sts);
> + }
> +
> + spin_unlock_irqrestore(&csi_dev->slock, flags);
> +
> + return 1;
any reason for this return 1 ?
I see the code does not use this return value and it looks hardcoded.
> +}
> +
> +void dw_mipi_csi_get_version(struct dw_csi *csi_dev)
> +{
> + u32 hw_version;
> +
> + hw_version = dw_mipi_csi_read(csi_dev, reg.VERSION);
> + csi_dev->hw_version_major = (u8)((hw_version >> 24) - '0');
> + csi_dev->hw_version_minor = (u8)((hw_version >> 16) - '0');
> + csi_dev->hw_version_minor = csi_dev->hw_version_minor * 10;
> + csi_dev->hw_version_minor += (u8)((hw_version >> 8) - '0');
> +}
> +
> +int dw_mipi_csi_specific_mappings(struct dw_csi *csi_dev)
> +{
> + struct device *dev = csi_dev->dev;
> +
> + if (csi_dev->hw_version_major == 1) {
> + if (csi_dev->hw_version_minor == 30) {
> + /*
> + * Hardware registers that were
> + * exclusive to version < 1.40
> + */
> + reg.INT_FRAME_FATAL = 0x100;
> + reg.MASK_INT_FRAME_FATAL = 0x104;
> + reg.FORCE_INT_FRAME_FATAL = 0x108;
> + reg.INT_PKT = 0x120;
> + reg.MASK_INT_PKT = 0x124;
> + reg.FORCE_INT_PKT = 0x128;
> +
> + /* interrupt source present until this release */
> + csi_int.PKT = BIT(17);
> + csi_int.LINE = BIT(18);
> + csi_int.IPI = BIT(19);
> + csi_int.FRAME_FATAL = BIT(2);
> +
> + } else if (csi_dev->hw_version_minor == 40) {
> + /*
> + * HW registers that were added
> + * to version 1.40
> + */
> + reg.ST_BNDRY_FRAME_FATAL = 0x280;
> + reg.MSK_BNDRY_FRAME_FATAL = 0x284;
> + reg.FORCE_BNDRY_FRAME_FATAL = 0x288;
> + reg.ST_SEQ_FRAME_FATAL = 0x290;
> + reg.MSK_SEQ_FRAME_FATAL = 0x294;
> + reg.FORCE_SEQ_FRAME_FATAL = 0x298;
> + reg.ST_CRC_FRAME_FATAL = 0x2a0;
> + reg.MSK_CRC_FRAME_FATAL = 0x2a4;
> + reg.FORCE_CRC_FRAME_FATAL = 0x2a8;
> + reg.ST_PLD_CRC_FATAL = 0x2b0;
> + reg.MSK_PLD_CRC_FATAL = 0x2b4;
> + reg.FORCE_PLD_CRC_FATAL = 0x2b8;
> + reg.ST_DATA_ID = 0x2c0;
> + reg.MSK_DATA_ID = 0x2c4;
> + reg.FORCE_DATA_ID = 0x2c8;
> + reg.ST_ECC_CORRECT = 0x2d0;
> + reg.MSK_ECC_CORRECT = 0x2d4;
> + reg.FORCE_ECC_CORRECT = 0x2d8;
> + reg.DATA_IDS_VC_1 = 0x0;
> + reg.DATA_IDS_VC_2 = 0x0;
> + reg.VC_EXTENSION = 0x0;
> +
> + /* interrupts map were changed */
> + csi_int.LINE = BIT(17);
> + csi_int.IPI = BIT(18);
> + csi_int.BNDRY_FRAME_FATAL = BIT(2);
> + csi_int.SEQ_FRAME_FATAL = BIT(3);
> + csi_int.CRC_FRAME_FATAL = BIT(4);
> + csi_int.PLD_CRC_FATAL = BIT(5);
> + csi_int.DATA_ID = BIT(6);
> + csi_int.ECC_CORRECTED = BIT(7);
> +
> + } else {
> + dev_info(dev, "Version minor not supported.");
> + }
> + } else {
> + dev_info(dev, "Version major not supported.");
> + }
> + return 0;
any reason why this function returns anything ? unused and only 0 is the
possible return value
> +}
> +
> +void dw_mipi_csi_dump(struct dw_csi *csi_dev)
> +{
> + dw_print(reg.VERSION);
> + dw_print(reg.N_LANES);
> + dw_print(reg.CTRL_RESETN);
> + dw_print(reg.INTERRUPT);
> + dw_print(reg.DATA_IDS_1);
> + dw_print(reg.DATA_IDS_2);
> + dw_print(reg.IPI_MODE);
> + dw_print(reg.IPI_VCID);
> + dw_print(reg.IPI_DATA_TYPE);
> + dw_print(reg.IPI_MEM_FLUSH);
> + dw_print(reg.IPI_HSA_TIME);
> + dw_print(reg.IPI_HBP_TIME);
> + dw_print(reg.IPI_HSD_TIME);
> + dw_print(reg.IPI_HLINE_TIME);
> + dw_print(reg.IPI_SOFTRSTN);
> + dw_print(reg.IPI_ADV_FEATURES);
> + dw_print(reg.IPI_VSA_LINES);
> + dw_print(reg.IPI_VBP_LINES);
> + dw_print(reg.IPI_VFP_LINES);
> + dw_print(reg.IPI_VACTIVE_LINES);
> + dw_print(reg.IPI_DATA_TYPE);
> + dw_print(reg.VERSION);
> + dw_print(reg.IPI_ADV_FEATURES);
> +}
> diff --git a/drivers/media/platform/dwc/dw-mipi-csi.h b/drivers/media/platform/dwc/dw-mipi-csi.h
> new file mode 100644
> index 0000000..6df3688
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-mipi-csi.h
> @@ -0,0 +1,287 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host controller driver
> + *
> + * Author: Luis Oliveira <Luis.Oliveira@synopsys.com>
> + */
> +
> +#ifndef _DW_MIPI_CSI_H__
> +#define _DW_MIPI_CSI_H__
> +
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/io.h>
> +#include <linux/phy/phy.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/ratelimit.h>
> +#include <linux/reset.h>
> +#include <linux/videodev2.h>
> +#include <linux/wait.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/dwc/dw-mipi-csi-pltfrm.h>
> +
> +/* Advanced features */
> +#define IPI_DT_OVERWRITE BIT(0)
> +#define DATA_TYPE_OVERWRITE(dt) (((dt) & GENMASK(5, 0)) << 8)
> +#define LINE_EVENT_SELECTION(n) ((n) << 16)
> +
> +enum line_event {
> + EVSELAUTO = 0,
> + EVSELPROG = 1,
> +};
> +
> +#define EN_VIDEO BIT(17)
> +#define EN_LINE_START BIT(18)
> +#define EN_NULL BIT(19)
> +#define EN_BLANKING BIT(20)
> +#define EN_EMBEDDED BIT(21)
> +#define IPI_SYNC_EVENT_MODE(n) ((n) << 24)
> +
> +enum sync_event {
> + SYNCEVFSN = 0,
> + SYNCEVFS = 1,
> +};
> +
> +/* DW MIPI CSI-2 register addresses*/
> +
> +struct R_CSI2 {
> + u16 VERSION;
> + u16 N_LANES;
> + u16 CTRL_RESETN;
> + u16 INTERRUPT;
> + u16 DATA_IDS_1;
> + u16 DATA_IDS_2;
> + u16 DATA_IDS_VC_1;
> + u16 DATA_IDS_VC_2;
> + u16 IPI_MODE;
> + u16 IPI_VCID;
> + u16 IPI_DATA_TYPE;
> + u16 IPI_MEM_FLUSH;
> + u16 IPI_HSA_TIME;
> + u16 IPI_HBP_TIME;
> + u16 IPI_HSD_TIME;
> + u16 IPI_HLINE_TIME;
> + u16 IPI_SOFTRSTN;
> + u16 IPI_ADV_FEATURES;
> + u16 IPI_VSA_LINES;
> + u16 IPI_VBP_LINES;
> + u16 IPI_VFP_LINES;
> + u16 IPI_VACTIVE_LINES;
> + u16 VC_EXTENSION;
> + u16 INT_PHY_FATAL;
> + u16 MASK_INT_PHY_FATAL;
> + u16 FORCE_INT_PHY_FATAL;
> + u16 INT_PKT_FATAL;
> + u16 MASK_INT_PKT_FATAL;
> + u16 FORCE_INT_PKT_FATAL;
> + u16 INT_FRAME_FATAL;
> + u16 MASK_INT_FRAME_FATAL;
> + u16 FORCE_INT_FRAME_FATAL;
> + u16 INT_PHY;
> + u16 MASK_INT_PHY;
> + u16 FORCE_INT_PHY;
> + u16 INT_PKT;
> + u16 MASK_INT_PKT;
> + u16 FORCE_INT_PKT;
> + u16 INT_LINE;
> + u16 MASK_INT_LINE;
> + u16 FORCE_INT_LINE;
> + u16 INT_IPI;
> + u16 MASK_INT_IPI;
> + u16 FORCE_INT_IPI;
> + u16 ST_BNDRY_FRAME_FATAL;
> + u16 MSK_BNDRY_FRAME_FATAL;
> + u16 FORCE_BNDRY_FRAME_FATAL;
> + u16 ST_SEQ_FRAME_FATAL;
> + u16 MSK_SEQ_FRAME_FATAL;
> + u16 FORCE_SEQ_FRAME_FATAL;
> + u16 ST_CRC_FRAME_FATAL;
> + u16 MSK_CRC_FRAME_FATAL;
> + u16 FORCE_CRC_FRAME_FATAL;
> + u16 ST_PLD_CRC_FATAL;
> + u16 MSK_PLD_CRC_FATAL;
> + u16 FORCE_PLD_CRC_FATAL;
> + u16 ST_DATA_ID;
> + u16 MSK_DATA_ID;
> + u16 FORCE_DATA_ID;
> + u16 ST_ECC_CORRECT;
> + u16 MSK_ECC_CORRECT;
> + u16 FORCE_ECC_CORRECT;
> +};
> +
> +/* Interrupt Masks */
> +struct interrupt_type {
> + u32 PHY_FATAL;
> + u32 PKT_FATAL;
> + u32 FRAME_FATAL;
> + u32 PHY;
> + u32 PKT;
> + u32 LINE;
> + u32 IPI;
> + u32 BNDRY_FRAME_FATAL;
> + u32 SEQ_FRAME_FATAL;
> + u32 CRC_FRAME_FATAL;
> + u32 PLD_CRC_FATAL;
> + u32 DATA_ID;
> + u32 ECC_CORRECTED;
> +};
> +
> +/* IPI Data Types */
> +enum data_type {
> + CSI_2_YUV420_8 = 0x18,
> + CSI_2_YUV420_10 = 0x19,
> + CSI_2_YUV420_8_LEG = 0x1A,
> + CSI_2_YUV420_8_SHIFT = 0x1C,
> + CSI_2_YUV420_10_SHIFT = 0x1D,
> + CSI_2_YUV422_8 = 0x1E,
> + CSI_2_YUV422_10 = 0x1F,
> + CSI_2_RGB444 = 0x20,
> + CSI_2_RGB555 = 0x21,
> + CSI_2_RGB565 = 0x22,
> + CSI_2_RGB666 = 0x23,
> + CSI_2_RGB888 = 0x24,
> + CSI_2_RAW6 = 0x28,
> + CSI_2_RAW7 = 0x29,
> + CSI_2_RAW8 = 0x2A,
> + CSI_2_RAW10 = 0x2B,
> + CSI_2_RAW12 = 0x2C,
> + CSI_2_RAW14 = 0x2D,
> + CSI_2_RAW16 = 0x2E,
> + CSI_2_RAW20 = 0x2F,
> + USER_DEFINED_1 = 0x30,
> + USER_DEFINED_2 = 0x31,
> + USER_DEFINED_3 = 0x32,
> + USER_DEFINED_4 = 0x33,
> + USER_DEFINED_5 = 0x34,
> + USER_DEFINED_6 = 0x35,
> + USER_DEFINED_7 = 0x36,
> + USER_DEFINED_8 = 0x37,
> +};
These are csi2 standard defines... would be good to have them available
in a more generic header to not redefine them if they are needed elsewhere ?
> +
> +/* DWC MIPI CSI-2 output types */
> +enum output {
> + IPI_OUT = 0,
> + IDI_OUT = 1,
> + BOTH_OUT = 2
> +};
> +
> +/* IPI color components */
> +enum color_mode {
> + COLOR48 = 0,
> + COLOR16 = 1
> +};
> +
> +/* IPI cut through */
> +enum cut_through {
> + CTINACTIVE = 0,
> + CTACTIVE = 1
> +};
> +
> +/* IPI output types */
> +enum ipi_output {
> + CAMERA_TIMING = 0,
> + AUTO_TIMING = 1
> +};
> +
> +/* Format template */
> +struct mipi_fmt {
> + u32 mbus_code;
> + u8 depth;
> +};
> +
> +struct mipi_dt {
> + u32 hex;
> + char *name;
> +};
> +
> +/* CSI specific configuration */
> +struct csi_data {
> + u32 num_lanes;
> + u32 dphy_freq;
> + u32 pclk;
> + u32 fps;
> + u32 bpp;
> + u32 output;
> + u32 ipi_mode;
> + u32 ipi_adv_features;
> + u32 ipi_cut_through;
> + u32 ipi_color_mode;
> + u32 ipi_auto_flush;
> + u32 virtual_ch;
> + u32 hsa;
> + u32 hbp;
> + u32 hsd;
> + u32 htotal;
> + u32 vsa;
> + u32 vbp;
> + u32 vfp;
> + u32 vactive;
> +};
> +
> +/* Structure to embed device driver information */
> +struct dw_csi {
> + struct v4l2_subdev sd;
> + struct video_device vdev;
> + struct v4l2_device v4l2_dev;
> + struct device *dev;
> + struct media_pad pads[CSI_PADS_NUM];
> + struct mipi_fmt *fmt;
> + struct v4l2_mbus_framefmt format;
> + void __iomem *base_address;
> + void __iomem *demo;
> + void __iomem *csc;
> + int ctrl_irq_number;
> + int demosaic_irq;
> + struct csi_data hw;
> + struct reset_control *rst;
> + struct phy *phy;
> + struct dw_csih_pdata *config;
> + struct mutex lock; /* protect resources sharing */
> + spinlock_t slock; /* interrupt handling lock */
> + u8 ipi_dt;
> + u8 index;
> + u8 hw_version_major;
> + u16 hw_version_minor;
> +};
> +
> +static inline struct dw_csi *sd_to_mipi_csi_dev(struct v4l2_subdev *sdev)
> +{
> + return container_of(sdev, struct dw_csi, sd);
> +}
> +
> +void dw_mipi_csi_reset(struct dw_csi *csi_dev);
> +int dw_mipi_csi_mask_irq_power_off(struct dw_csi *csi_dev);
> +int dw_mipi_csi_hw_stdby(struct dw_csi *csi_dev);
> +void dw_mipi_csi_set_ipi_fmt(struct dw_csi *csi_dev);
> +void dw_mipi_csi_start(struct dw_csi *csi_dev);
> +int dw_mipi_csi_irq_handler(struct dw_csi *csi_dev);
> +void dw_mipi_csi_get_version(struct dw_csi *csi_dev);
> +int dw_mipi_csi_specific_mappings(struct dw_csi *csi_dev);
> +void dw_mipi_csi_fill_timings(struct dw_csi *dev,
> + struct v4l2_subdev_format *fmt);
> +void dw_mipi_csi_dump(struct dw_csi *csi_dev);
> +
> +#if IS_ENABLED(CONFIG_DWC_MIPI_TC_DPHY_GEN3)
> +int dw_csi_create_capabilities_sysfs(struct platform_device *pdev);
> +#endif
> +
> +static inline void dw_mipi_csi_write(struct dw_csi *dev,
> + u32 address, u32 data)
> +{
> + writel(data, dev->base_address + address);
> +}
> +
> +static inline u32 dw_mipi_csi_read(struct dw_csi *dev, u32 address)
> +{
> + return readl(dev->base_address + address);
> +}
> +
> +#endif /*_DW_MIPI_CSI_H__ */
> diff --git a/include/media/dwc/dw-mipi-csi-pltfrm.h b/include/media/dwc/dw-mipi-csi-pltfrm.h
> new file mode 100644
> index 0000000..948db4e
> --- /dev/null
> +++ b/include/media/dwc/dw-mipi-csi-pltfrm.h
> @@ -0,0 +1,104 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018-2019 Synopsys, Inc. and/or its affiliates.
> + *
> + * Synopsys DesignWare MIPI CSI-2 Host media entities
> + *
> + * Author: Luis Oliveira <Luis.Oliveira@synopsys.com>
> + */
> +
> +#ifndef __DW_MIPI_CSI_PLTFRM_INCLUDES_H_
> +#define __DW_MIPI_CSI_PLTFRM_INCLUDES_H_
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-mediabus.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define MAX_WIDTH 3280
> +#define MAX_HEIGHT 1852
> +
> +/* The subdevices' group IDs. */
> +#define GRP_ID_SENSOR (10)
> +#define GRP_ID_CSI (20)
> +#define GRP_ID_VIF (30)
> +#define GRP_ID_VIDEODEV (40)
> +
> +#define CSI_MAX_ENTITIES (2)
> +#define VIF_MAX_ENTITIES (2)
> +#define PLAT_MAX_SENSORS (2)
> +
> +struct pdata_names {
> + char *name;
> +};
> +
> +enum video_dev_pads {
> + VIDEO_DEV_SD_PAD_SINK_VIF1,
> + VIDEO_DEV_SD_PAD_SINK_VIF2,
> + VIDEO_DEV_SD_PAD_SOURCE_DMA,
> + VIDEO_DEV_SD_PADS_NUM,
> +};
> +
> +enum vif_pads {
> + VIF_PAD_SINK_CSI,
> + VIF_PAD_SOURCE_DMA,
> + VIF_PADS_NUM,
> +};
> +
> +enum mipi_csi_pads {
> + CSI_PAD_SINK,
> + CSI_PAD_SOURCE,
> + CSI_PADS_NUM,
> +};
> +
> +struct plat_csi_source_info {
> + u16 flags;
> + u16 mux_id;
> +};
> +
> +struct plat_csi_fmt {
> + char *name;
> + u32 mbus_code;
> + u32 fourcc;
> + u8 depth;
> +};
> +
> +struct plat_csi_media_pipeline;
> +
> +/*
> + * Media pipeline operations to be called from within a video node, i.e. the
> + * last entity within the pipeline. Implemented by related media device driver.
> + */
> +struct plat_csi_media_pipeline_ops {
> + int (*prepare)(struct plat_csi_media_pipeline *p,
> + struct media_entity *me);
> + int (*unprepare)(struct plat_csi_media_pipeline *p);
> + int (*open)(struct plat_csi_media_pipeline *p, struct media_entity *me,
> + bool resume);
> + int (*close)(struct plat_csi_media_pipeline *p);
> + int (*set_stream)(struct plat_csi_media_pipeline *p, bool state);
> + int (*set_format)(struct plat_csi_media_pipeline *p,
> + struct v4l2_subdev_format *fmt);
> +};
> +
> +struct plat_csi_video_entity {
> + struct video_device vdev;
> + struct plat_csi_media_pipeline *pipe;
> +};
> +
> +struct plat_csi_media_pipeline {
> + struct media_pipeline mp;
> + const struct plat_csi_media_pipeline_ops *ops;
> +};
> +
> +static inline struct plat_csi_video_entity
> +*vdev_to_plat_csi_video_entity(struct video_device *vdev)
> +{
> + return container_of(vdev, struct plat_csi_video_entity, vdev);
> +}
> +
> +#define plat_csi_pipeline_call(ent, op, args...) \
> + (!(ent) ? -ENOENT : (((ent)->pipe->ops && (ent)->pipe->ops->op) ? \
> + (ent)->pipe->ops->op(((ent)->pipe), ##args) : -ENOIOCTLCMD)) \
> +
> +#endif /* __DW_MIPI_CSI_PLTFRM_INCLUDES_H_ */
Would be useful to prefix these with 'dw_' to be sure they do not step
over some other symbols when included ?
>
^ permalink raw reply
* Re: [PATCH 0/3] add coupled regulators for Exynos5422/5800
From: Krzysztof Kozlowski @ 2019-07-10 9:00 UTC (permalink / raw)
To: k.konieczny
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Chanwoo Choi,
Kukjin Kim, Kyungmin Park, Mark Rutland, MyungJoo Ham,
Nishanth Menon, Rob Herring, Stephen Boyd, Viresh Kumar,
devicetree, linux-arm-kernel, linux-kernel, linux-pm,
linux-samsung-soc@vger.kernel.org
In-Reply-To: <20190708141140.24379-1-k.konieczny@partner.samsung.com>
On Mon, 8 Jul 2019 at 16:12, <k.konieczny@partner.samsung.com> wrote:
>
> From: Kamil Konieczny <k.konieczny@partner.samsung.com>
>
> Hi,
>
> The main purpose of this patch series is to add coupled regulators for
> Exynos5422/5800 to keep constrain on voltage difference between vdd_arm
> and vdd_int to be at most 300mV. In exynos-bus instead of using
> regulator_set_voltage_tol() with default voltage tolerance it should be
> used regulator_set_voltage_triplet() with volatege range, and this is
> already present in opp/core.c code, so it can be reused. While at this,
> move setting regulators into opp/core.
>
> This patchset was tested on Odroid XU3.
>
> The last patch depends on two previous.
So you break the ABI... I assume that patchset maintains
bisectability. However there is no explanation why ABI break is needed
so this does not look good...
Best regards,
Krzysztof
>
> Regards,
> Kamil
>
> Kamil Konieczny (2):
> opp: core: add regulators enable and disable
> devfreq: exynos-bus: convert to use dev_pm_opp_set_rate()
>
> Marek Szyprowski (1):
> ARM: dts: exynos: add initial data for coupled regulators for
> Exynos5422/5800
>
> arch/arm/boot/dts/exynos5420.dtsi | 34 ++--
> arch/arm/boot/dts/exynos5422-odroid-core.dtsi | 4 +
> arch/arm/boot/dts/exynos5800-peach-pi.dts | 4 +
> arch/arm/boot/dts/exynos5800.dtsi | 32 ++--
> drivers/devfreq/exynos-bus.c | 172 +++++++-----------
> drivers/opp/core.c | 13 ++
> 6 files changed, 120 insertions(+), 139 deletions(-)
>
> --
> 2.22.0
>
^ permalink raw reply
* Re: [PATCH 3/3] ARM: dts: exynos: add initial data for coupled regulators for Exynos5422/5800
From: Krzysztof Kozlowski @ 2019-07-10 9:02 UTC (permalink / raw)
To: k.konieczny
Cc: Marek Szyprowski, Bartlomiej Zolnierkiewicz, Chanwoo Choi,
Kukjin Kim, Kyungmin Park, Mark Rutland, MyungJoo Ham,
Nishanth Menon, Rob Herring, Stephen Boyd, Viresh Kumar,
devicetree, linux-arm-kernel, linux-kernel, linux-pm,
linux-samsung-soc@vger.kernel.org
In-Reply-To: <20190708141140.24379-4-k.konieczny@partner.samsung.com>
On Mon, 8 Jul 2019 at 16:12, <k.konieczny@partner.samsung.com> wrote:
>
> From: Marek Szyprowski <m.szyprowski@samsung.com>
>
> Declare Exynos5422/5800 voltage ranges for opp points for big cpu core and
> bus wcore and couple their voltage supllies as vdd_arm and vdd_int should
> be in 300mV range.
>
> Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
> Signed-off-by: Kamil Konieczny <k.konieczny@partner.samsung.com>
> ---
> arch/arm/boot/dts/exynos5420.dtsi | 34 +++++++++----------
> arch/arm/boot/dts/exynos5422-odroid-core.dtsi | 4 +++
> arch/arm/boot/dts/exynos5800-peach-pi.dts | 4 +++
> arch/arm/boot/dts/exynos5800.dtsi | 32 ++++++++---------
> 4 files changed, 41 insertions(+), 33 deletions(-)
Looks good, I assume bisectability is not affected, because of
dependency on the driver changes I will take it for next next release
(v5.5). Assuming that driver change goes into v5.4.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 11/13] arm64: dts: qcom: qcs404: Add CPR and populate OPP table
From: Viresh Kumar @ 2019-07-10 9:03 UTC (permalink / raw)
To: Niklas Cassel
Cc: Andy Gross, linux-arm-msm, jorge.ramirez-ortiz, sboyd, vireshk,
bjorn.andersson, ulf.hansson, Rob Herring, Mark Rutland,
devicetree, linux-kernel
In-Reply-To: <20190705095726.21433-12-niklas.cassel@linaro.org>
On 05-07-19, 11:57, Niklas Cassel wrote:
> diff --git a/arch/arm64/boot/dts/qcom/qcs404.dtsi b/arch/arm64/boot/dts/qcom/qcs404.dtsi
> cpu_opp_table: cpu-opp-table {
> - compatible = "operating-points-v2";
> + compatible = "operating-points-v2-kryo-cpu";
> opp-shared;
>
> opp-1094400000 {
> opp-hz = /bits/ 64 <1094400000>;
> - opp-microvolt = <1224000 1224000 1224000>;
> + required-opps = <&cpr_opp1>;
> };
> opp-1248000000 {
> opp-hz = /bits/ 64 <1248000000>;
> - opp-microvolt = <1288000 1288000 1288000>;
> + required-opps = <&cpr_opp2>;
> };
> opp-1401600000 {
> opp-hz = /bits/ 64 <1401600000>;
> - opp-microvolt = <1384000 1384000 1384000>;
> + required-opps = <&cpr_opp3>;
> + };
> + };
> +
> + cpr_opp_table: cpr-opp-table {
> + compatible = "operating-points-v2-qcom-level";
> +
> + cpr_opp1: opp1 {
> + opp-level = <1>;
> + qcom,opp-fuse-level = <1>;
> + opp-hz = /bits/ 64 <1094400000>;
> + };
> + cpr_opp2: opp2 {
> + opp-level = <2>;
> + qcom,opp-fuse-level = <2>;
> + opp-hz = /bits/ 64 <1248000000>;
> + };
> + cpr_opp3: opp3 {
> + opp-level = <3>;
> + qcom,opp-fuse-level = <3>;
> + opp-hz = /bits/ 64 <1401600000>;
> };
> };
- Do we ever have cases more complex than this for this version of CPR ?
- What about multiple devices with same CPR table, not big LITTLE
CPUs, but other devices like two different type of IO devices ? What
will we do with opp-hz in those cases ?
- If there are no such cases, can we live without opp-hz being used
here and reverse-engineer the highest frequency by looking directly
at CPUs OPP table ? i.e. by looking at required-opps field.
--
viresh
^ permalink raw reply
* Re: [RESEND, PATCH v4 0/2] cpufreq: Add sunxi nvmem based CPU scaling driver
From: Viresh Kumar @ 2019-07-10 9:15 UTC (permalink / raw)
To: Yangtao Li
Cc: vireshk, nm, sboyd, robh+dt, mark.rutland, maxime.ripard, wens,
rjw, davem, mchehab+samsung, gregkh, linus.walleij, nicolas.ferre,
paulmck, linux-pm, devicetree, linux-arm-kernel, linux-kernel
In-Reply-To: <20190612162816.31713-1-tiny.windzz@gmail.com>
On 12-06-19, 12:28, Yangtao Li wrote:
> Add sunxi nvmem based CPU scaling driver, refers to qcom-cpufreq-kryo.
>
> Yangtao Li (2):
> cpufreq: Add sun50i nvmem based CPU scaling driver
> dt-bindings: cpufreq: Document allwinner,sun50i-h6-operating-points
>
> .../bindings/opp/sun50i-nvmem-cpufreq.txt | 167 +++++++++++++
> MAINTAINERS | 7 +
> drivers/cpufreq/Kconfig.arm | 12 +
> drivers/cpufreq/Makefile | 1 +
> drivers/cpufreq/cpufreq-dt-platdev.c | 2 +
> drivers/cpufreq/sun50i-cpufreq-nvmem.c | 226 ++++++++++++++++++
> 6 files changed, 415 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/opp/sun50i-nvmem-cpufreq.txt
> create mode 100644 drivers/cpufreq/sun50i-cpufreq-nvmem.c
>
> ---
> v4:
> -Remove sunxi_cpufreq_soc_data structure for now.
> -Convert to less generic name.
> -Update soc_bin xlate.
Applied. Thanks.
I will push it only after 5.3-rc1 is released.
--
viresh
^ permalink raw reply
* Re: devicetree bindings for a generic led-based backlight driver
From: Jean-Jacques Hiblot @ 2019-07-10 9:26 UTC (permalink / raw)
To: Pavel Machek, Jacek Anaszewski
Cc: devicetree, linux-leds, robh, Valkeinen, Tomi
In-Reply-To: <20190706151941.GB9856@amd>
Hi Pavel
On 06/07/2019 17:19, Pavel Machek wrote:
> Hi!
>
>>>>> A few years ago (2015), Tomi Valkeinen posted a series implementing a
>>>>> backlight driver on top of a LED device.
>>>>>
>>>>> https://patchwork.kernel.org/patch/7293991/
>>>>> https://patchwork.kernel.org/patch/7294001/
>>>>> https://patchwork.kernel.org/patch/7293981/
>>>>>
>>>>> The discussion stopped� because he lacked the time to work on it.
>>>>>
>>>>> I will be taking over the task and, before heading in the wrong
>>>>> direction, wanted a confirmation that the binding Tomi last proposed in
>>>>> hist last email was indeed the preferred option.
>>>>>
>>>>> It will probably require some modifications in the LED core to create
>>>>> the right kind of led-device (normal, flash or backlight) based on the
>>>>> compatible option.
>>>> I recall that discussion. I gave my ack for the LED changes but
>>>> now we have more LED people that might want to look into that.
>>> Regarding the LED bindings as discussed by Tom and Rob in
>>> https://patchwork.kernel.org/patch/7293991/, what do you think of using
>>> a 'compatible' string to make a LED device also a backlight or a flash LED ?
>> After going through the referenced discussion and refreshing my memory
>> it looks to me the most reasonable way to go for backlight case.
>>
>> Nevertheless I'd not tamper at LED flash support - if it's not broken,
>> don't fix it.
>>
>> Best regards,
>> Jacek Anaszewski
>>
>>> Here is the example from Tomi at the end of the discussion:
>>>
>>> /* tlc59108 is an i2c device */
>>> tlc59116@40 {
>>> #address-cells = <1>;
>>> #size-cells = <0>;
>>> compatible = "ti,tlc59108";
>>> reg = <0x40>;
>>>
>>> wan@0 {
>>> label = "wrt1900ac:amber:wan";
>>> reg = <0x0>;
>>> };
>>>
>>> bl@2 {
>>> label = "backlight";
>>> reg = <0x2>;
>>>
>>> compatible = "led-backlight";
>>> brightness-levels = <0 243 245 247 248 249 251 252 255>;
>>> default-brightness-level = <8>;
>>>
>>> enable-gpios = <&pcf_lcd 13 GPIO_ACTIVE_LOW>;
> So... this needs some kind of reference to display it belongs to,
> right?
This is the reverse. The display uses a reference the backlight.
JJ
>
> Pavel
>
^ permalink raw reply
* Re: [RFC,v3 6/9] media: platform: Add Mediatek ISP P1 V4L2 functions
From: Tomasz Figa @ 2019-07-10 9:54 UTC (permalink / raw)
To: Jungo Lin
Cc: devicetree, sean.cheng, frederic.chen, rynn.wu, srv_heupstream,
robh, ryan.yu, frankie.chiu, hverkuil, ddavenport, sj.huang,
linux-mediatek, laurent.pinchart, matthias.bgg, mchehab,
linux-arm-kernel, linux-media
In-Reply-To: <20190611035344.29814-7-jungo.lin@mediatek.com>
Hi Jungo,
On Tue, Jun 11, 2019 at 11:53:41AM +0800, Jungo Lin wrote:
> Implement standard V4L2 video driver that utilizes V4L2
> and media framework APIs. In this driver, supports one media
> device, one sub-device and seven video devices during
> initialization. Moreover, it also connects with sensor and
> seninf drivers with V4L2 async APIs.
>
> (The current metadata interface used in meta input and partial
> meta nodes is only a temporary solution to kick off the driver
> development and is not ready to be reviewed yet.)
>
> Signed-off-by: Jungo Lin <jungo.lin@mediatek.com>
> ---
> This patch depends on "media: support Mediatek sensor interface driver"[1].
>
> ISP P1 sub-device communicates with seninf sub-device with CIO.
>
> [1]. media: support Mediatek sensor interface driver
> https://patchwork.kernel.org/cover/10979135/
> ---
> .../platform/mtk-isp/isp_50/cam/Makefile | 1 +
> .../mtk-isp/isp_50/cam/mtk_cam-v4l2-util.c | 1674 +++++++++++++++++
> .../mtk-isp/isp_50/cam/mtk_cam-v4l2-util.h | 173 ++
> 3 files changed, 1848 insertions(+)
> create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.c
> create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.h
>
Thanks for the patch. Please see my comments inline.
[snip]
> +static void mtk_cam_req_try_isp_queue(struct mtk_cam_dev *cam_dev,
> + struct media_request *new_req)
> +{
> + struct mtk_cam_dev_request *req, *req_safe, *cam_dev_req;
> + struct device *dev = &cam_dev->pdev->dev;
> +
> + dev_dbg(dev, "%s new req:%d", __func__, !new_req);
> +
> + if (!cam_dev->streaming) {
> + cam_dev_req = mtk_cam_req_to_dev_req(new_req);
> + spin_lock(&cam_dev->req_lock);
> + list_add_tail(&cam_dev_req->list, &cam_dev->req_list);
> + spin_unlock(&cam_dev->req_lock);
> + dev_dbg(dev, "%s: stream off, no ISP enqueue\n", __func__);
> + return;
> + }
> +
> + /* Normal enqueue flow */
> + if (new_req) {
> + mtk_isp_req_enqueue(dev, new_req);
> + return;
> + }
> +
> + /* Flush all media requests wehen first stream on */
> + list_for_each_entry_safe(req, req_safe, &cam_dev->req_list, list) {
> + list_del(&req->list);
> + mtk_isp_req_enqueue(dev, &req->req);
> + }
> +}
This will have to be redone, as per the other suggestions, but generally one
would have a function that tries to queue as much as possible from a list to
the hardware and another function that adds a request to the list and calls
the first function.
> +
> +static void mtk_cam_req_queue(struct media_request *req)
> +{
> + struct mtk_cam_dev *cam_dev = mtk_cam_mdev_to_dev(req->mdev);
> +
> + vb2_request_queue(req);
> + mtk_cam_req_try_isp_queue(cam_dev, req);
Looks like this driver is suffering from versy similar problems in request
handling as the DIP driver used to.
I'd prefer to save my time and avoid repeating the same comments, so please
check my comments for the DIP driver and apply them to this one too:
https://patchwork.kernel.org/patch/10905223/
> +}
> +
> +static struct media_request *mtk_cam_req_alloc(struct media_device *mdev)
> +{
> + struct mtk_cam_dev_request *cam_dev_req;
> +
> + cam_dev_req = kzalloc(sizeof(*cam_dev_req), GFP_KERNEL);
> +
> + return &cam_dev_req->req;
> +}
> +
> +static void mtk_cam_req_free(struct media_request *req)
> +{
> + struct mtk_cam_dev_request *cam_dev_req = mtk_cam_req_to_dev_req(req);
> +
> + kfree(cam_dev_req);
> +}
> +
> +static __u32 img_get_pixel_byte_by_fmt(__u32 pix_fmt)
Doesn't this function return bits not bytes?
> +{
> + switch (pix_fmt) {
> + case V4L2_PIX_FMT_MTISP_B8:
> + case V4L2_PIX_FMT_MTISP_F8:
> + return 8;
> + case V4L2_PIX_FMT_MTISP_B10:
> + case V4L2_PIX_FMT_MTISP_F10:
> + return 10;
> + case V4L2_PIX_FMT_MTISP_B12:
> + case V4L2_PIX_FMT_MTISP_F12:
> + return 12;
> + case V4L2_PIX_FMT_MTISP_B14:
> + case V4L2_PIX_FMT_MTISP_F14:
> + return 14;
> + default:
> + return 0;
> + }
> +}
> +
> +static __u32 img_cal_main_stream_stride(struct device *dev, __u32 width,
> + __u32 pix_fmt)
> +{
> + __u32 stride;
> + __u32 pixel_byte = img_get_pixel_byte_by_fmt(pix_fmt);
The __ prefixed types should be used only inside UAPI. Please change the
driver to use the normal ones.
> +
> + width = ALIGN(width, 4);
If there is some alignment requirement for width, it should be handled by
TRY_/S_FMT and here we should already assume everything properly aligned.
> + stride = ALIGN(DIV_ROUND_UP(width * pixel_byte, 8), 2);
> +
> + dev_dbg(dev, "main width:%d, stride:%d\n", width, stride);
> +
> + return stride;
> +}
> +
> +static __u32 img_cal_packed_out_stride(struct device *dev, __u32 width,
> + __u32 pix_fmt)
> +{
> + __u32 stride;
> + __u32 pixel_byte = img_get_pixel_byte_by_fmt(pix_fmt);
> +
> + width = ALIGN(width, 4);
Ditto.
> + stride = DIV_ROUND_UP(width * 3, 2);
Could we introduce a local variable for this intermediate value, so that its
name could explain what the value is?
> + stride = DIV_ROUND_UP(stride * pixel_byte, 8);
> +
> + if (pix_fmt == V4L2_PIX_FMT_MTISP_F10)
> + stride = ALIGN(stride, 4);
Is it expected that only the F10 format needs this alignment?
> +
> + dev_dbg(dev, "packed width:%d, stride:%d\n", width, stride);
> +
> + return stride;
> +}
> +
> +static __u32 img_cal_stride(struct device *dev,
> + int node_id,
> + __u32 width,
> + __u32 pix_fmt)
> +{
> + __u32 bpl;
> +
> + /* Currently, only support one_pixel_mode */
> + if (node_id == MTK_CAM_P1_MAIN_STREAM_OUT)
> + bpl = img_cal_main_stream_stride(dev, width, pix_fmt);
> + else if (node_id == MTK_CAM_P1_PACKED_BIN_OUT)
> + bpl = img_cal_packed_out_stride(dev, width, pix_fmt);
> +
> + /* For DIP HW constrained, it needs 4 byte alignment */
> + bpl = ALIGN(bpl, 4);
> +
> + return bpl;
> +}
> +
> +static const struct v4l2_format *
> +mtk_cam_dev_find_fmt(struct mtk_cam_dev_node_desc *desc, u32 format)
> +{
> + unsigned int i;
> + const struct v4l2_format *dev_fmt;
> +
> + for (i = 0; i < desc->num_fmts; i++) {
> + dev_fmt = &desc->fmts[i];
> + if (dev_fmt->fmt.pix_mp.pixelformat == format)
> + return dev_fmt;
> + }
> +
> + return NULL;
> +}
> +
> +/* Calcuate mplane pix format */
> +static void
> +mtk_cam_dev_cal_mplane_fmt(struct device *dev,
> + struct v4l2_pix_format_mplane *dest_fmt,
> + unsigned int node_id)
> +{
> + unsigned int i;
> + __u32 bpl, sizeimage, imagsize;
Perhaps s/sizeimage/plane_size/ and s/imagsize/total_size/?
> +
> + imagsize = 0;
> + for (i = 0 ; i < dest_fmt->num_planes; ++i) {
> + bpl = img_cal_stride(dev,
> + node_id,
> + dest_fmt->width,
> + dest_fmt->pixelformat);
> + sizeimage = bpl * dest_fmt->height;
> + imagsize += sizeimage;
> + dest_fmt->plane_fmt[i].bytesperline = bpl;
> + dest_fmt->plane_fmt[i].sizeimage = sizeimage;
> + memset(dest_fmt->plane_fmt[i].reserved,
> + 0, sizeof(dest_fmt->plane_fmt[i].reserved));
This memset is not needed. The core clears the reserved fields
automatically:
https://elixir.bootlin.com/linux/v5.2/source/drivers/media/v4l2-core/v4l2-ioctl.c#L1559
(We may want to backport the patch that added that to our 4.19 branch.)
> + dev_dbg(dev, "plane:%d,bpl:%d,sizeimage:%u\n",
> + i, bpl, dest_fmt->plane_fmt[i].sizeimage);
> + }
> +
> + if (dest_fmt->num_planes == 1)
> + dest_fmt->plane_fmt[0].sizeimage = imagsize;
Hmm, we only seem to support 1 plane raw formats in this driver. Does the
hardware support any formats with more than 1 plane? If not, all the code
should be simplified to just assume 1 plane.
> +}
> +
> +static void
> +mtk_cam_dev_set_img_fmt(struct device *dev,
> + struct v4l2_pix_format_mplane *dest_fmt,
> + const struct v4l2_pix_format_mplane *src_fmt,
> + unsigned int node_id)
> +{
> + dest_fmt->width = src_fmt->width;
> + dest_fmt->height = src_fmt->height;
> + dest_fmt->pixelformat = src_fmt->pixelformat;
> + dest_fmt->field = src_fmt->field;
> + dest_fmt->colorspace = src_fmt->colorspace;
> + dest_fmt->num_planes = src_fmt->num_planes;
> + /* Use default */
> + dest_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> + dest_fmt->quantization = V4L2_QUANTIZATION_DEFAULT;
> + dest_fmt->xfer_func =
> + V4L2_MAP_XFER_FUNC_DEFAULT(dest_fmt->colorspace);
> + memset(dest_fmt->reserved, 0, sizeof(dest_fmt->reserved));
Given that src_fmt should already be validated and have any fields adjusted
to match the driver requirements, wouldn't all the lines above be equivalent
to *dest_fmt = *src_fmt?
We probably want to move setting all the constant fields to
mtk_cam_vidioc_try_fmt().
> +
> + dev_dbg(dev, "%s: Dest Fmt:%c%c%c%c, w*h:%d*%d\n",
> + __func__,
> + (dest_fmt->pixelformat & 0xFF),
> + (dest_fmt->pixelformat >> 8) & 0xFF,
> + (dest_fmt->pixelformat >> 16) & 0xFF,
> + (dest_fmt->pixelformat >> 24) & 0xFF,
> + dest_fmt->width,
> + dest_fmt->height);
> +
> + mtk_cam_dev_cal_mplane_fmt(dev, dest_fmt, node_id);
This should have been called already before this function was called,
because src_fmt should be already expected to contain valid settings. In
fact, this is already called in mtk_cam_vidioc_try_fmt().
> +}
> +
> +/* Get the default format setting */
> +static void
> +mtk_cam_dev_load_default_fmt(struct device *dev,
Please don't pass struct device pointer around, but instead just the main
driver data struct, which should be much more convenient for accessing
various driver data. Please fix the other functions as well.
> + struct mtk_cam_dev_node_desc *queue_desc,
> + struct v4l2_format *dest)
> +{
> + const struct v4l2_format *default_fmt =
> + &queue_desc->fmts[queue_desc->default_fmt_idx];
> +
> + dest->type = queue_desc->buf_type;
> +
> + /* Configure default format based on node type */
> + if (queue_desc->image) {
> + mtk_cam_dev_set_img_fmt(dev,
> + &dest->fmt.pix_mp,
> + &default_fmt->fmt.pix_mp,
> + queue_desc->id);
We should probably just call mtk_cam_vidioc_s_fmt() here, with a dummy
v4l2_format struct and have any incorrect fields replaced by
mtk_cam_vidioc_try_fmt(), since it's the same logic, as if setting invalid
v4l2_format at runtime.
> + } else {
> + dest->fmt.meta.dataformat = default_fmt->fmt.meta.dataformat;
> + dest->fmt.meta.buffersize = default_fmt->fmt.meta.buffersize;
> + }
> +}
> +
> +static int mtk_cam_isp_open(struct file *file)
> +{
> + struct mtk_cam_dev *cam_dev = video_drvdata(file);
> + struct device *dev = &cam_dev->pdev->dev;
> + struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
> + int ret;
> +
> + mutex_lock(&cam_dev->lock);
> + ret = v4l2_fh_open(file);
> + if (ret)
> + goto unlock;
> +
> + ret = v4l2_pipeline_pm_use(&node->vdev.entity, 1);
Please don't power on open. Normally applications keep the device nodes open
all the time, so they would keep everything powered on.
Normally this should be done as late as possible, ideally when starting the
streaming.
> + if (ret)
> + dev_err(dev, "%s fail:%d", __func__, ret);
> +
> +unlock:
> + mutex_unlock(&cam_dev->lock);
> +
> + return ret;
> +}
> +
> +static int mtk_cam_isp_release(struct file *file)
> +{
> + struct mtk_cam_dev *cam_dev = video_drvdata(file);
> + struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
> +
> + mutex_lock(&cam_dev->lock);
> + v4l2_pipeline_pm_use(&node->vdev.entity, 0);
> + vb2_fop_release(file);
> + mutex_unlock(&cam_dev->lock);
> +
> + return 0;
> +}
If we remove power handling from open and release, we should be able to just
use v4l2_fh_open() and vb2_fop_release() directly in the
v4l2_file_operations struct.
> +
> +static struct v4l2_subdev *
> +mtk_cam_cio_get_active_sensor(struct mtk_cam_dev *cam_dev)
> +{
> + struct media_device *mdev = cam_dev->seninf->entity.graph_obj.mdev;
> + struct media_entity *entity;
> + struct device *dev = &cam_dev->pdev->dev;
> + struct v4l2_subdev *sensor;
This variable would be unitialized if there is no streaming sensor. Was
there no compiler warning generated for this?
> +
> + media_device_for_each_entity(entity, mdev) {
> + dev_dbg(dev, "media entity: %s:0x%x\n",
> + entity->name, entity->function);
> + if (entity->function == MEDIA_ENT_F_CAM_SENSOR &&
> + entity->stream_count) {
> + sensor = media_entity_to_v4l2_subdev(entity);
> + dev_dbg(dev, "Sensor found: %s\n", entity->name);
> + break;
> + }
> + }
> +
> + if (!sensor)
> + dev_err(dev, "Sensor is not connected\n");
> +
> + return sensor;
> +}
> +
> +static int mtk_cam_cio_stream_on(struct mtk_cam_dev *cam_dev)
> +{
> + struct device *dev = &cam_dev->pdev->dev;
> + int ret;
> +
> + /* Align vb2_core_streamon design */
> + if (cam_dev->streaming) {
> + dev_warn(dev, "already streaming\n", dev);
> + return 0;
> + }
Could we check this in the caller?
> +
> + if (!cam_dev->seninf) {
> + dev_err(dev, "no seninf connected:%d\n", ret);
> + return -EPERM;
I don't think -EPERM is a good error code here. It's about a missing seninf
device, so perhaps -ENODEV?
> + }
> +
> + /* Get active sensor from graph topology */
> + cam_dev->sensor = mtk_cam_cio_get_active_sensor(cam_dev);
> + if (!cam_dev->sensor)
> + return -EPERM;
> -ENODEV
> +
> + ret = mtk_isp_config(dev);
> + if (ret)
> + return -EPERM;
Maybe just return ret?
> +
> + /* Seninf must stream on first */
> + ret = v4l2_subdev_call(cam_dev->seninf, video, s_stream, 1);
> + if (ret) {
> + dev_err(dev, "%s stream on failed:%d\n",
> + cam_dev->seninf->entity.name, ret);
> + return -EPERM;
return ret?
> + }
> +
> + ret = v4l2_subdev_call(cam_dev->sensor, video, s_stream, 1);
> + if (ret) {
> + dev_err(dev, "%s stream on failed:%d\n",
> + cam_dev->sensor->entity.name, ret);
> + goto fail_sensor_on;
> + }
> +
> + cam_dev->streaming = true;
> + mtk_cam_req_try_isp_queue(cam_dev, NULL);
> + isp_composer_stream(dev, 1);
> + dev_dbg(dev, "streamed on Pass 1\n");
> +
> + return 0;
> +
> +fail_sensor_on:
> + v4l2_subdev_call(cam_dev->seninf, video, s_stream, 0);
> +
> + return -EPERM;
return ret?
> +}
> +
> +static int mtk_cam_cio_stream_off(struct mtk_cam_dev *cam_dev)
> +{
> + struct device *dev = &cam_dev->pdev->dev;
> + int ret;
> +
> + if (!cam_dev->streaming) {
> + dev_warn(dev, "already stream off");
> + return 0;
> + }
Could we check this in the caller?
> +
> + ret = v4l2_subdev_call(cam_dev->sensor, video, s_stream, 0);
> + if (ret) {
> + dev_err(dev, "%s stream off failed:%d\n",
> + cam_dev->sensor->entity.name, ret);
> + return -EPERM;
> + }
> +
> + ret = v4l2_subdev_call(cam_dev->seninf, video, s_stream, 0);
> + if (ret) {
> + dev_err(dev, "%s stream off failed:%d\n",
> + cam_dev->seninf->entity.name, ret);
> + return -EPERM;
> + }
> +
> + isp_composer_stream(dev, 0);
Shouldn't we synchronously wait for the streaming to stop here? Otherwise we
can't guarantee that the hardware releases all the memory that we're going
to free once this function returns.
> + cam_dev->streaming = false;
> + dev_dbg(dev, "streamed off Pass 1\n");
> +
> + return 0;
> +}
> +
> +static int mtk_cam_sd_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> + struct mtk_cam_dev *cam_dev = mtk_cam_subdev_to_dev(sd);
> +
> + if (enable)
> + return mtk_cam_cio_stream_on(cam_dev);
> + else
> + return mtk_cam_cio_stream_off(cam_dev);
> +}
> +
> +static int mtk_cam_sd_subscribe_event(struct v4l2_subdev *subdev,
> + struct v4l2_fh *fh,
> + struct v4l2_event_subscription *sub)
> +{
> + switch (sub->type) {
> + case V4L2_EVENT_FRAME_SYNC:
> + return v4l2_event_subscribe(fh, sub, 0, NULL);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int mtk_cam_sd_s_power(struct v4l2_subdev *sd, int on)
> +{
> + struct mtk_cam_dev *cam_dev = mtk_cam_subdev_to_dev(sd);
> +
> + dev_dbg(&cam_dev->pdev->dev, "%s:%d", __func__, on);
> +
> + return on ? mtk_isp_power_init(cam_dev) :
> + mtk_isp_power_release(&cam_dev->pdev->dev);
s_power is a historical thing and we shouldn't be implementing it. Instead,
we should use runtime PM and call pm_runtime_get_sync(), pm_runtime_put()
whenever we start and stop streaming respectively.
> +}
> +
> +static int mtk_cam_media_link_setup(struct media_entity *entity,
> + const struct media_pad *local,
> + const struct media_pad *remote, u32 flags)
> +{
> + struct mtk_cam_dev *cam_dev =
> + container_of(entity, struct mtk_cam_dev, subdev.entity);
> + u32 pad = local->index;
> +
> + dev_dbg(&cam_dev->pdev->dev, "%s: %d -> %d flags:0x%x\n",
> + __func__, pad, remote->index, flags);
> +
> + if (pad < MTK_CAM_P1_TOTAL_NODES)
I assume this check is needed, because the pads with higher indexes are not
video nodes? If so, a comment would be helpful here.
> + cam_dev->vdev_nodes[pad].enabled =
> + !!(flags & MEDIA_LNK_FL_ENABLED);
> +
> + return 0;
> +}
> +
> +static void mtk_cam_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> + struct mtk_cam_dev *mtk_cam_dev = vb2_get_drv_priv(vb->vb2_queue);
> + struct mtk_cam_video_device *node = mtk_cam_vbq_to_vdev(vb->vb2_queue);
> + struct device *dev = &mtk_cam_dev->pdev->dev;
> + struct mtk_cam_dev_buffer *buf;
> +
> + buf = mtk_cam_vb2_buf_to_dev_buf(vb);
This can be folded into the declaration.
> +
> + dev_dbg(dev, "%s: node:%d fd:%d idx:%d\n",
> + __func__,
> + node->id,
> + buf->vbb.request_fd,
> + buf->vbb.vb2_buf.index);
> +
> + /* For request buffers en-queue, handled in mtk_cam_req_try_queue */
> + if (vb->vb2_queue->uses_requests)
> + return;
I'd suggest removing non-request support from this driver. Even if we end up
with a need to provide compatibility for non-request mode, then it should be
built on top of the requests mode, so that the driver itself doesn't have to
deal with two modes.
> +
> + /* Added the buffer into the tracking list */
> + spin_lock(&node->slock);
> + list_add_tail(&buf->list, &node->pending_list);
> + spin_unlock(&node->slock);
> +
> + mtk_isp_enqueue(dev, node->desc.dma_port, buf);
> +}
> +
> +static int mtk_cam_vb2_buf_init(struct vb2_buffer *vb)
> +{
> + struct mtk_cam_dev *cam_dev = vb2_get_drv_priv(vb->vb2_queue);
> + struct device *smem_dev = cam_dev->smem_dev;
> + struct mtk_cam_video_device *node = mtk_cam_vbq_to_vdev(vb->vb2_queue);
> + struct mtk_cam_dev_buffer *buf;
> +
> + buf = mtk_cam_vb2_buf_to_dev_buf(vb);
> + buf->node_id = node->id;
> + buf->daddr = vb2_dma_contig_plane_dma_addr(&buf->vbb.vb2_buf, 0);
> + buf->scp_addr = 0;
Just a reminder that this will have to be reworked according to my comments
for the memory allocation patch.
> +
> + /* scp address is only valid for meta input buffer */
> + if (node->desc.smem_alloc)
> + buf->scp_addr = mtk_cam_smem_iova_to_scp_addr(smem_dev,
> + buf->daddr);
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
> + struct mtk_cam_video_device *node = mtk_cam_vbq_to_vdev(vb->vb2_queue);
> + const struct v4l2_format *fmt = &node->vdev_fmt;
> + unsigned int size;
> +
> + if (vb->vb2_queue->type == V4L2_BUF_TYPE_META_OUTPUT ||
> + vb->vb2_queue->type == V4L2_BUF_TYPE_META_CAPTURE)
> + size = fmt->fmt.meta.buffersize;
> + else
> + size = fmt->fmt.pix_mp.plane_fmt[0].sizeimage;
> +
> + if (vb2_plane_size(vb, 0) < size)
> + return -EINVAL;
For OUTPUT buffers we need to check if vb2_get_plane_payload() == size.
Otherwise we could get not enough or invalid data.
> +
> + v4l2_buf->field = V4L2_FIELD_NONE;
> + vb2_set_plane_payload(vb, 0, size);
This shouldn't be called on OUTPUT buffers.
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vb2_queue_setup(struct vb2_queue *vq,
> + unsigned int *num_buffers,
> + unsigned int *num_planes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct mtk_cam_video_device *node = mtk_cam_vbq_to_vdev(vq);
> + unsigned int max_buffer_count = node->desc.max_buf_count;
> + const struct v4l2_format *fmt = &node->vdev_fmt;
> + unsigned int size;
> +
> + /* Check the limitation of buffer size */
> + if (max_buffer_count)
> + *num_buffers = clamp_val(*num_buffers, 1, max_buffer_count);
> +
> + if (vq->type == V4L2_BUF_TYPE_META_OUTPUT ||
> + vq->type == V4L2_BUF_TYPE_META_CAPTURE)
> + size = fmt->fmt.meta.buffersize;
> + else
> + size = fmt->fmt.pix_mp.plane_fmt[0].sizeimage;
> +
> + /* Add for q.create_bufs with fmt.g_sizeimage(p) / 2 test */
> + if (*num_planes) {
We should also verify that *num_planes == 1, as we don't support more
planes in this driver.
> + if (sizes[0] < size)
> + return -EINVAL;
> + } else {
> + *num_planes = 1;
> + sizes[0] = size;
> + }
> +
> + return 0;
> +}
> +
> +static void mtk_cam_vb2_return_all_buffers(struct mtk_cam_dev *cam_dev,
> + struct mtk_cam_video_device *node,
> + enum vb2_buffer_state state)
> +{
> + struct mtk_cam_dev_buffer *b, *b0;
> + struct mtk_cam_dev_request *req, *req0;
> + struct media_request_object *obj, *obj0;
> + struct vb2_buffer *vb;
> +
> + dev_dbg(&cam_dev->pdev->dev, "%s: node:%s", __func__, node->vdev.name);
> +
> + /* Return all buffers */
> + spin_lock(&node->slock);
> + list_for_each_entry_safe(b, b0, &node->pending_list, list) {
nit: One would normally call the second argument "prev", or "b_prev".
> + vb = &b->vbb.vb2_buf;
> + if (vb->state == VB2_BUF_STATE_ACTIVE)
We shouldn't need to check the buffer state.
> + vb2_buffer_done(vb, state);
> + list_del(&b->list);
> + }
> + spin_unlock(&node->slock);
> +
> + spin_lock(&cam_dev->req_lock);
> + list_for_each_entry_safe(req, req0, &cam_dev->req_list, list) {
nit: Ditto.
> + list_for_each_entry_safe(obj, obj0, &req->req.objects, list) {
Need to check if the object is a buffer.
> + vb = container_of(obj, struct vb2_buffer, req_obj);
> + if (vb->state == VB2_BUF_STATE_ACTIVE)
vb->state shouldn't be accessed directly from the drivers.
Generally, the need to check the state here would suggest that there is
something wrong with how the driver manages the requests. The list that is
being iterated here shouldn't contain any requests that have buffers that
aren't active. That will be achieved if my comments for the request handling
in the DIP driver are applied to this driver as well.
> + vb2_buffer_done(vb, state);
> + }
> + list_del(&req->list);
> + }
> + spin_unlock(&cam_dev->req_lock);
> +
> + if (node->vbq.uses_requests)
> + mtk_isp_req_flush_buffers(&cam_dev->pdev->dev);
> +}
> +
> +static int mtk_cam_vb2_start_streaming(struct vb2_queue *vq,
> + unsigned int count)
> +{
> + struct mtk_cam_dev *cam_dev = vb2_get_drv_priv(vq);
> + struct mtk_cam_video_device *node = mtk_cam_vbq_to_vdev(vq);
> + struct device *dev = &cam_dev->pdev->dev;
> + unsigned int node_count = cam_dev->subdev.entity.use_count;
> + int ret;
> +
> + if (!node->enabled) {
How is this synchronized with mtk_cam_media_link_setup()?
> + dev_err(dev, "Node:%d is not enable\n", node->id);
> + ret = -ENOLINK;
> + goto fail_no_link;
> + }
> +
> + dev_dbg(dev, "%s: count info:%d:%d", __func__,
> + atomic_read(&cam_dev->streamed_node_count), node_count);
> +
> + if (atomic_inc_return(&cam_dev->streamed_node_count) < node_count)
> + return 0;
How do we guarantee that cam_dev->subdev.entity.use_count doesn't change
between calls to this function on different video nodes?
> +
> + /* Start streaming of the whole pipeline now */
> + ret = media_pipeline_start(&node->vdev.entity, &cam_dev->pipeline);
> + if (ret) {
> + dev_err(dev, "%s: Node:%d failed\n", __func__, node->id);
> + goto fail_start_pipeline;
> + }
> +
Related to the above comment: If we start the media pipeline when we start
streaming on the first node, we would naturally prevent the link
configuration changes until the last node stops streaming (as long as the
link is not DYNAMIC). Note that it would only mark the entities as
streaming, but it wouldn't call their s_stream, which I believe is exactly
what we would need to solve the problem above.
> + /* Stream on sub-devices node */
> + ret = v4l2_subdev_call(&cam_dev->subdev, video, s_stream, 1);
> + if (ret) {
> + dev_err(dev, "Node:%d s_stream on failed:%d\n", node->id, ret);
> + goto fail_stream_on;
> + }
> +
> + return 0;
> +
> +fail_stream_on:
> + media_pipeline_stop(&node->vdev.entity);
> +fail_start_pipeline:
> + atomic_dec(&cam_dev->streamed_node_count);
> +fail_no_link:
> + mtk_cam_vb2_return_all_buffers(cam_dev, node, VB2_BUF_STATE_QUEUED);
> +
> + return ret;
> +}
> +
> +static void mtk_cam_vb2_stop_streaming(struct vb2_queue *vq)
> +{
> + struct mtk_cam_dev *cam_dev = vb2_get_drv_priv(vq);
> + struct mtk_cam_video_device *node = mtk_cam_vbq_to_vdev(vq);
> + struct device *dev = &cam_dev->pdev->dev;
> +
> + if (!node->enabled)
> + return;
It shouldn't be possible for this to happen, because nobody could have
called start_streaming on a disabled node.
> +
> + mtk_cam_vb2_return_all_buffers(cam_dev, node, VB2_BUF_STATE_ERROR);
Shouldn't we stop streaming first, so that the hardware operation is
cancelled and any buffers owned by the hardware are released?
> +
> + dev_dbg(dev, "%s: count info:%d", __func__,
> + cam_dev->subdev.entity.stream_count);
> +
> + /* Check the first node to stream-off */
> + if (!cam_dev->subdev.entity.stream_count)
> + return;
> +
> + media_pipeline_stop(&node->vdev.entity);
> +
> + if (v4l2_subdev_call(&cam_dev->subdev, video, s_stream, 0))
> + dev_err(dev, "failed to stop streaming\n");
> +}
> +
> +static void mtk_cam_vb2_buf_request_complete(struct vb2_buffer *vb)
> +{
> + struct mtk_cam_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
> +
> + v4l2_ctrl_request_complete(vb->req_obj.req,
> + dev->v4l2_dev.ctrl_handler);
This would end up being called multiple times, once for each video node.
Instead, this should be called explicitly by the driver when it completed
the request - perhaps in the frame completion handler?
With that, we probably wouldn't even need this callback.
> +}
> +
> +static int mtk_cam_vidioc_querycap(struct file *file, void *fh,
> + struct v4l2_capability *cap)
> +{
> + struct mtk_cam_dev *cam_dev = video_drvdata(file);
> +
> + strscpy(cap->driver, MTK_CAM_DEV_P1_NAME, sizeof(cap->driver));
> + strscpy(cap->card, MTK_CAM_DEV_P1_NAME, sizeof(cap->card));
We could just use dev_driver_name(cam_dev->dev) for both.
> + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
> + dev_name(cam_dev->media_dev.dev));
We should just store dev in cam_dev.
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_enum_fmt(struct file *file, void *fh,
> + struct v4l2_fmtdesc *f)
> +{
> + struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
> +
> + if (f->index >= node->desc.num_fmts)
> + return -EINVAL;
> +
> + f->pixelformat = node->desc.fmts[f->index].fmt.pix_mp.pixelformat;
Is the set of formats available always the same regardless of the sensor
format?
> + f->flags = 0;
We need f->description too.
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_g_fmt(struct file *file, void *fh,
> + struct v4l2_format *f)
> +{
> + struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
> +
> + if (!node->desc.num_fmts)
> + return -EINVAL;
When would that condition happen?
> +
> + f->fmt = node->vdev_fmt.fmt;
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_try_fmt(struct file *file, void *fh,
> + struct v4l2_format *in_fmt)
> +{
> + struct mtk_cam_dev *cam_dev = video_drvdata(file);
> + struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
> + const struct v4l2_format *dev_fmt;
> + __u32 width, height;
Don't use __ types in implementation, they are here for UAPI purposes. There
is u32, which you could use instead, but for width and height you don't need
explicit size, so unsigned int should be good.
> +
> + dev_dbg(&cam_dev->pdev->dev, "%s: fmt:%c%c%c%c, w*h:%u*%u\n",
> + __func__,
> + (in_fmt->fmt.pix_mp.pixelformat & 0xFF),
> + (in_fmt->fmt.pix_mp.pixelformat >> 8) & 0xFF,
> + (in_fmt->fmt.pix_mp.pixelformat >> 16) & 0xFF,
> + (in_fmt->fmt.pix_mp.pixelformat >> 24) & 0xFF,
> + in_fmt->fmt.pix_mp.width, in_fmt->fmt.pix_mp.height);
> +
> + width = in_fmt->fmt.pix_mp.width;
> + height = in_fmt->fmt.pix_mp.height;
> +
> + dev_fmt = mtk_cam_dev_find_fmt(&node->desc,
> + in_fmt->fmt.pix_mp.pixelformat);
> + if (dev_fmt) {
> + mtk_cam_dev_set_img_fmt(&cam_dev->pdev->dev,
> + &in_fmt->fmt.pix_mp,
> + &dev_fmt->fmt.pix_mp,
> + node->id);
> + } else {
> + mtk_cam_dev_load_default_fmt(&cam_dev->pdev->dev,
> + &node->desc, in_fmt);
We shouldn't just load a default format. This function should validate all
the fields one by one and adjust them to something appropriate.
> + }
CodingStyle: No braces if both if and else bodies have only 1 statement
each.
> + in_fmt->fmt.pix_mp.width = clamp_t(u32,
> + width,
> + CAM_MIN_WIDTH,
> + in_fmt->fmt.pix_mp.width);
Shouldn't we clamp this with some maximum value too?
> + in_fmt->fmt.pix_mp.height = clamp_t(u32,
> + height,
> + CAM_MIN_HEIGHT,
> + in_fmt->fmt.pix_mp.height);
Ditto.
> + mtk_cam_dev_cal_mplane_fmt(&cam_dev->pdev->dev,
> + &in_fmt->fmt.pix_mp, node->id);
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_s_fmt(struct file *file, void *fh,
> + struct v4l2_format *f)
> +{
> + struct mtk_cam_dev *cam_dev = video_drvdata(file);
> + struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
> +
> + if (cam_dev->streaming)
> + return -EBUSY;
I think this should rather be something like vb2_queue_is_busy(), which
would prevent format changes if buffers are allocated.
> +
> + /* Get the valid format */
> + mtk_cam_vidioc_try_fmt(file, fh, f);
> +
> + /* Configure to video device */
> + mtk_cam_dev_set_img_fmt(&cam_dev->pdev->dev,
> + &node->vdev_fmt.fmt.pix_mp,
> + &f->fmt.pix_mp,
> + node->id);
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_enum_input(struct file *file, void *fh,
> + struct v4l2_input *input)
> +{
> + if (input->index)
> + return -EINVAL;
> +
> + strscpy(input->name, "camera", sizeof(input->name));
> + input->type = V4L2_INPUT_TYPE_CAMERA;
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_g_input(struct file *file, void *fh,
> + unsigned int *input)
> +{
> + *input = 0;
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_s_input(struct file *file,
> + void *fh, unsigned int input)
> +{
> + return input == 0 ? 0 : -EINVAL;
> +}
> +
> +static int mtk_cam_vidioc_enum_framesizes(struct file *filp, void *priv,
> + struct v4l2_frmsizeenum *sizes)
> +{
> + struct mtk_cam_video_device *node = file_to_mtk_cam_node(filp);
> + const struct v4l2_format *dev_fmt;
> +
> + dev_fmt = mtk_cam_dev_find_fmt(&node->desc, sizes->pixel_format);
> + if (!dev_fmt || sizes->index)
> + return -EINVAL;
> +
> + sizes->type = node->desc.frmsizes->type;
> + memcpy(&sizes->stepwise, &node->desc.frmsizes->stepwise,
> + sizeof(sizes->stepwise));
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_meta_enum_fmt(struct file *file, void *fh,
> + struct v4l2_fmtdesc *f)
> +{
> + struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
> +
> + if (f->index)
> + return -EINVAL;
> +
> + strscpy(f->description, node->desc.description,
> + sizeof(node->desc.description));
> + f->pixelformat = node->vdev_fmt.fmt.meta.dataformat;
> + f->flags = 0;
> +
> + return 0;
> +}
> +
> +static int mtk_cam_vidioc_g_meta_fmt(struct file *file, void *fh,
> + struct v4l2_format *f)
> +{
> + struct mtk_cam_video_device *node = file_to_mtk_cam_node(file);
> +
> + f->fmt.meta.dataformat = node->vdev_fmt.fmt.meta.dataformat;
> + f->fmt.meta.buffersize = node->vdev_fmt.fmt.meta.buffersize;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_subdev_core_ops mtk_cam_subdev_core_ops = {
> + .subscribe_event = mtk_cam_sd_subscribe_event,
> + .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> + .s_power = mtk_cam_sd_s_power,
> +};
> +
> +static const struct v4l2_subdev_video_ops mtk_cam_subdev_video_ops = {
> + .s_stream = mtk_cam_sd_s_stream,
> +};
> +
> +static const struct v4l2_subdev_ops mtk_cam_subdev_ops = {
> + .core = &mtk_cam_subdev_core_ops,
> + .video = &mtk_cam_subdev_video_ops,
> +};
> +
> +static const struct media_entity_operations mtk_cam_media_ops = {
nit: mtk_cam_media_entity_ops?
> + .link_setup = mtk_cam_media_link_setup,
> + .link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static const struct vb2_ops mtk_cam_vb2_ops = {
> + .queue_setup = mtk_cam_vb2_queue_setup,
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> + .buf_init = mtk_cam_vb2_buf_init,
> + .buf_prepare = mtk_cam_vb2_buf_prepare,
> + .start_streaming = mtk_cam_vb2_start_streaming,
> + .stop_streaming = mtk_cam_vb2_stop_streaming,
> + .buf_queue = mtk_cam_vb2_buf_queue,
> + .buf_request_complete = mtk_cam_vb2_buf_request_complete,
> +};
> +
> +static const struct v4l2_file_operations mtk_cam_v4l2_fops = {
> + .unlocked_ioctl = video_ioctl2,
> + .open = mtk_cam_isp_open,
> + .release = mtk_cam_isp_release,
> + .poll = vb2_fop_poll,
> + .mmap = vb2_fop_mmap,
> +#ifdef CONFIG_COMPAT
> + .compat_ioctl32 = v4l2_compat_ioctl32,
> +#endif
> +};
> +
> +static const struct media_device_ops mtk_cam_media_req_ops = {
nit: Those are media ops, so perhaps just mtk_cam_media_ops?
> + .link_notify = v4l2_pipeline_link_notify,
> + .req_alloc = mtk_cam_req_alloc,
> + .req_free = mtk_cam_req_free,
> + .req_validate = vb2_request_validate,
> + .req_queue = mtk_cam_req_queue,
> +};
> +
> +static int mtk_cam_media_register(struct device *dev,
> + struct media_device *media_dev)
> +{
> + media_dev->dev = dev;
> + strscpy(media_dev->model, MTK_CAM_DEV_P1_NAME,
Could we replace any use of this macro with dev_driver_string(dev) and then
delete the macro? The less name strings in the driver the better, as there
is less change for confusing the userspace if few different names are used
at the same time.
> + sizeof(media_dev->model));
> + snprintf(media_dev->bus_info, sizeof(media_dev->bus_info),
> + "platform:%s", dev_name(dev));
> + media_dev->hw_revision = 0;
> + media_device_init(media_dev);
> + media_dev->ops = &mtk_cam_media_req_ops;
> +
> + return media_device_register(media_dev);
> +}
> +
> +static int mtk_cam_video_register_device(struct mtk_cam_dev *cam_dev, u32 i)
> +{
> + struct device *dev = &cam_dev->pdev->dev;
> + struct mtk_cam_video_device *node = &cam_dev->vdev_nodes[i];
Would it make sense to pass node as an argument to this function instead of
(or in addition to) i?
> + struct video_device *vdev = &node->vdev;
> + struct vb2_queue *vbq = &node->vbq;
> + u32 output = !cam_dev->vdev_nodes[i].desc.capture;
Why not call it capture instead and avoid the inversion?
> + u32 link_flags = cam_dev->vdev_nodes[i].desc.link_flags;
> + int ret;
> +
> + cam_dev->subdev_pads[i].flags = output ?
> + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
> +
> + /* Initialize media entities */
> + ret = media_entity_pads_init(&vdev->entity, 1, &node->vdev_pad);
> + if (ret) {
> + dev_err(dev, "failed initialize media pad:%d\n", ret);
> + return ret;
> + }
> + node->enabled = false;
Are all the nodes optional? If there is any required node, it should be
always enabled and have the MEDIA_LNK_FL_IMMUTABLE flag set.
> + node->id = i;
> + node->vdev_pad.flags = cam_dev->subdev_pads[i].flags;
Hmm, shouldn't the subdev pads have opposite directions (sink vs source)?
> + mtk_cam_dev_load_default_fmt(&cam_dev->pdev->dev,
> + &node->desc,
> + &node->vdev_fmt);
> +
> + /* Initialize vbq */
> + vbq->type = node->vdev_fmt.type;
> + if (vbq->type == V4L2_BUF_TYPE_META_OUTPUT)
> + vbq->io_modes = VB2_MMAP;
> + else
> + vbq->io_modes = VB2_MMAP | VB2_DMABUF;
> +
> + if (node->desc.smem_alloc) {
> + vbq->bidirectional = 1;
> + vbq->dev = cam_dev->smem_dev;
> + } else {
> + vbq->dev = &cam_dev->pdev->dev;
> + }
> +
> + if (vbq->type == V4L2_BUF_TYPE_META_CAPTURE)
> + vdev->entity.function =
> + MEDIA_ENT_F_PROC_VIDEO_STATISTICS;
This is a video node, so it's just a DMA, not a processing entity. I believe
all the entities corresponding to video nodes should use MEDIA_ENT_F_IO_V4L.
> + vbq->ops = &mtk_cam_vb2_ops;
> + vbq->mem_ops = &vb2_dma_contig_memops;
> + vbq->buf_struct_size = sizeof(struct mtk_cam_dev_buffer);
> + vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> + vbq->min_buffers_needed = 0; /* Can streamon w/o buffers */
> + /* Put the process hub sub device in the vb2 private data */
What is "process hub" and what "sub device" is this about?
> + vbq->drv_priv = cam_dev;
> + vbq->lock = &node->lock;
> + vbq->supports_requests = true;
> +
> + ret = vb2_queue_init(vbq);
> + if (ret) {
> + dev_err(dev, "failed to init. vb2 queue:%d\n", ret);
> + goto fail_vb2_queue;
> + }
> +
> + /* Initialize vdev */
> + snprintf(vdev->name, sizeof(vdev->name), "%s %s",
> + MTK_CAM_DEV_P1_NAME, node->desc.name);
> + /* set cap/type/ioctl_ops of the video device */
> + vdev->device_caps = node->desc.cap | V4L2_CAP_STREAMING;
> + vdev->ioctl_ops = node->desc.ioctl_ops;
> + vdev->fops = &mtk_cam_v4l2_fops;
> + vdev->release = video_device_release_empty;
> + vdev->lock = &node->lock;
> + vdev->v4l2_dev = &cam_dev->v4l2_dev;
> + vdev->queue = &node->vbq;
> + vdev->vfl_dir = output ? VFL_DIR_TX : VFL_DIR_RX;
> + vdev->entity.ops = NULL;
> + /* Enable private control for image video devices */
> + if (node->desc.image) {
> + mtk_cam_ctrl_init(cam_dev, &node->ctrl_handler);
> + vdev->ctrl_handler = &node->ctrl_handler;
> + }
> + video_set_drvdata(vdev, cam_dev);
> + dev_dbg(dev, "register vdev:%d:%s\n", i, vdev->name);
> +
> + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
> + if (ret) {
> + dev_err(dev, "failed to register vde:%d\n", ret);
> + goto fail_vdev;
> + }
> +
> + /* Create link between video node and the subdev pad */
> + if (output) {
> + ret = media_create_pad_link(&vdev->entity, 0,
> + &cam_dev->subdev.entity,
> + i, link_flags);
> + } else {
> + ret = media_create_pad_link(&cam_dev->subdev.entity,
> + i, &vdev->entity, 0,
> + link_flags);
> + }
> + if (ret)
> + goto fail_link;
> +
> + /* Initialize miscellaneous variables */
> + mutex_init(&node->lock);
> + spin_lock_init(&node->slock);
> + INIT_LIST_HEAD(&node->pending_list);
This should be all initialized before registering the video device.
Otherwise userspace could open the device before these are initialized.
> +
> + return 0;
> +
> +fail_link:
> + video_unregister_device(vdev);
> +fail_vdev:
> + vb2_queue_release(vbq);
> +fail_vb2_queue:
> + media_entity_cleanup(&vdev->entity);
> +
> + return ret;
> +}
> +
> +static int mtk_cam_mem2mem2_v4l2_register(struct mtk_cam_dev *cam_dev)
This function doesn't have anything to do with mem2mem. How about
mtk_cam_v4l2_register()?
Perhaps it would make sense to move any media related code into into
mtk_cam_media_register(), keep only V4L2 related code here and have the
caller call the former first and then this one, rather than having such deep
sequence of nested calls, which makes the driver harder to read.
> +{
> + struct device *dev = &cam_dev->pdev->dev;
How about just storing dev, instead of pdev in the struct? Also, calling the
argument "cam", would make it as short as cam->dev.
> + /* Total pad numbers is video devices + one seninf pad */
> + unsigned int num_subdev_pads = MTK_CAM_CIO_PAD_SINK + 1;
> + unsigned int i;
> + int ret;
> +
> + ret = mtk_cam_media_register(dev,
> + &cam_dev->media_dev);
> + if (ret) {
> + dev_err(dev, "failed to register media device:%d\n", ret);
> + return ret;
> + }
> + dev_info(dev, "Register media device: %s, 0x%pK",
> + MTK_CAM_DEV_P1_NAME, cam_dev->media_dev);
An info message should be useful to the user in some way. Printing kernel
pointers isn't useful. Something like "registered media0" could be useful to
let the user know which media device is associated with this driver if there
is more than one in the system.
> +
> + /* Set up v4l2 device */
> + cam_dev->v4l2_dev.mdev = &cam_dev->media_dev;
> + ret = v4l2_device_register(dev, &cam_dev->v4l2_dev);
> + if (ret) {
> + dev_err(dev, "failed to register V4L2 device:%d\n", ret);
> + goto fail_v4l2_dev;
Please call the labels after the cleanup step that needs to be done. It
makes it easier to spot any ordering errors.
> + }
> + dev_info(dev, "Register v4l2 device: 0x%pK", cam_dev->v4l2_dev);
Same as above.
> +
> + /* Initialize subdev media entity */
> + cam_dev->subdev_pads = devm_kcalloc(dev, num_subdev_pads,
> + sizeof(*cam_dev->subdev_pads),
> + GFP_KERNEL);
> + if (!cam_dev->subdev_pads) {
> + ret = -ENOMEM;
> + goto fail_subdev_pads;
> + }
> +
> + ret = media_entity_pads_init(&cam_dev->subdev.entity,
> + num_subdev_pads,
> + cam_dev->subdev_pads);
> + if (ret) {
> + dev_err(dev, "failed initialize media pads:%d:\n", ret);
Stray ":" at the end of the message.
> + goto fail_subdev_pads;
> + }
> +
> + /* Initialize all pads with MEDIA_PAD_FL_SOURCE */
> + for (i = 0; i < num_subdev_pads; i++)
> + cam_dev->subdev_pads[i].flags = MEDIA_PAD_FL_SOURCE;
> +
> + /* Customize the last one pad as CIO sink pad. */
> + cam_dev->subdev_pads[MTK_CAM_CIO_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +
> + /* Initialize subdev */
> + v4l2_subdev_init(&cam_dev->subdev, &mtk_cam_subdev_ops);
> + cam_dev->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_STATISTICS;
> + cam_dev->subdev.entity.ops = &mtk_cam_media_ops;
> + cam_dev->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE |
> + V4L2_SUBDEV_FL_HAS_EVENTS;
> + snprintf(cam_dev->subdev.name, sizeof(cam_dev->subdev.name),
> + "%s", MTK_CAM_DEV_P1_NAME);
> + v4l2_set_subdevdata(&cam_dev->subdev, cam_dev);
> +
> + ret = v4l2_device_register_subdev(&cam_dev->v4l2_dev, &cam_dev->subdev);
> + if (ret) {
> + dev_err(dev, "failed initialize subdev:%d\n", ret);
> + goto fail_subdev;
> + }
> + dev_info(dev, "register subdev: %s\n", cam_dev->subdev.name);
> +
> + /* Create video nodes and links */
> + for (i = 0; i < MTK_CAM_P1_TOTAL_NODES; i++) {
> + ret = mtk_cam_video_register_device(cam_dev, i);
> + if (ret)
> + goto fail_video_register;
> + }
> +
> + vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32));
> +
> + return 0;
> +
> +fail_video_register:
> + i--;
This could be moved into the for clause, as the initialization statement.
> + for (; i >= 0; i--) {
i is unsigned. Did this compile without warnings?
> + video_unregister_device(&cam_dev->vdev_nodes[i].vdev);
> + media_entity_cleanup(&cam_dev->vdev_nodes[i].vdev.entity);
> + mutex_destroy(&cam_dev->vdev_nodes[i].lock);
Should we move this into mtk_cam_video_unregister_device() to be consistent
with registration?
> + }
> +fail_subdev:
> + media_entity_cleanup(&cam_dev->subdev.entity);
> +fail_subdev_pads:
> + v4l2_device_unregister(&cam_dev->v4l2_dev);
> +fail_v4l2_dev:
> + dev_err(dev, "fail_v4l2_dev mdev: 0x%pK:%d", &cam_dev->media_dev, ret);
Please print errors only where they actually happen, not at the cleanup.
> + media_device_unregister(&cam_dev->media_dev);
> + media_device_cleanup(&cam_dev->media_dev);
> +
> + return ret;
> +}
> +
> +static int mtk_cam_v4l2_unregister(struct mtk_cam_dev *cam_dev)
> +{
> + unsigned int i;
> + struct mtk_cam_video_device *dev;
nit: Move the declaration inside the for loop, since the variable is only
used there.
> +
> + for (i = 0; i < MTK_CAM_P1_TOTAL_NODES; i++) {
> + dev = &cam_dev->vdev_nodes[i];
> + video_unregister_device(&dev->vdev);
> + media_entity_cleanup(&dev->vdev.entity);
> + mutex_destroy(&dev->lock);
> + if (dev->desc.image)
> + v4l2_ctrl_handler_free(&dev->ctrl_handler);
> + }
> +
> + vb2_dma_contig_clear_max_seg_size(&cam_dev->pdev->dev);
> +
> + v4l2_device_unregister_subdev(&cam_dev->subdev);
> + media_entity_cleanup(&cam_dev->subdev.entity);
> + kfree(cam_dev->subdev_pads);
This was allocated using devm_kcalloc(), so no need to free it explicitly.
> +
> + v4l2_device_unregister(&cam_dev->v4l2_dev);
> + media_device_unregister(&cam_dev->media_dev);
> + media_device_cleanup(&cam_dev->media_dev);
> +
> + return 0;
> +}
> +
> +static int mtk_cam_dev_complete(struct v4l2_async_notifier *notifier)
> +{
> + struct mtk_cam_dev *cam_dev =
> + container_of(notifier, struct mtk_cam_dev, notifier);
> + struct device *dev = &cam_dev->pdev->dev;
> + int ret;
> +
> + ret = media_create_pad_link(&cam_dev->seninf->entity,
> + MTK_CAM_CIO_PAD_SRC,
> + &cam_dev->subdev.entity,
> + MTK_CAM_CIO_PAD_SINK,
> + 0);
> + if (ret) {
> + dev_err(dev, "fail to create pad link %s %s err:%d\n",
> + cam_dev->seninf->entity.name,
> + cam_dev->subdev.entity.name,
> + ret);
> + return ret;
> + }
> +
> + dev_info(dev, "Complete the v4l2 registration\n");
dev_dbg()
> +
> + ret = v4l2_device_register_subdev_nodes(&cam_dev->v4l2_dev);
> + if (ret) {
> + dev_err(dev, "failed initialize subdev nodes:%d\n", ret);
> + return ret;
> + }
> +
> + return ret;
> +}
Why not just put the contents of this function inside
mtk_cam_dev_notifier_complete()?
> +
> +static int mtk_cam_dev_notifier_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *sd,
> + struct v4l2_async_subdev *asd)
> +{
> + struct mtk_cam_dev *cam_dev =
> + container_of(notifier, struct mtk_cam_dev, notifier);
> +
Should we somehow check that the entity we got is seninf indeed and there
was no mistake in DT?
> + cam_dev->seninf = sd;
> + dev_info(&cam_dev->pdev->dev, "%s is bounded\n", sd->entity.name);
bound
Also please make this dev_dbg().
> + return 0;
> +}
> +
> +static void mtk_cam_dev_notifier_unbind(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *sd,
> + struct v4l2_async_subdev *asd)
> +{
> + struct mtk_cam_dev *cam_dev =
> + container_of(notifier, struct mtk_cam_dev, notifier);
> +
> + cam_dev->seninf = NULL;
> + dev_dbg(&cam_dev->pdev->dev, "%s is unbounded\n", sd->entity.name);
unbound
> +}
> +
> +static int mtk_cam_dev_notifier_complete(struct v4l2_async_notifier *notifier)
> +{
> + return mtk_cam_dev_complete(notifier);
> +}
> +
> +static const struct v4l2_async_notifier_operations mtk_cam_async_ops = {
> + .bound = mtk_cam_dev_notifier_bound,
> + .unbind = mtk_cam_dev_notifier_unbind,
> + .complete = mtk_cam_dev_notifier_complete,
> +};
> +
> +static int mtk_cam_v4l2_async_register(struct mtk_cam_dev *cam_dev)
> +{
> + struct device *dev = &cam_dev->pdev->dev;
> + int ret;
> +
> + ret = v4l2_async_notifier_parse_fwnode_endpoints(dev,
> + &cam_dev->notifier, sizeof(struct v4l2_async_subdev),
> + NULL);
> + if (ret)
> + return ret;
> +
> + if (!cam_dev->notifier.num_subdevs)
> + return -ENODEV;
Could we print some error messages for the 2 cases above?
> +
> + cam_dev->notifier.ops = &mtk_cam_async_ops;
> + dev_info(&cam_dev->pdev->dev, "mtk_cam v4l2_async_notifier_register\n");
dev_dbg()
> + ret = v4l2_async_notifier_register(&cam_dev->v4l2_dev,
> + &cam_dev->notifier);
> + if (ret) {
> + dev_err(&cam_dev->pdev->dev,
> + "failed to register async notifier : %d\n", ret);
> + v4l2_async_notifier_cleanup(&cam_dev->notifier);
> + }
> +
> + return ret;
> +}
> +
> +static void mtk_cam_v4l2_async_unregister(struct mtk_cam_dev *cam_dev)
> +{
> + v4l2_async_notifier_unregister(&cam_dev->notifier);
> + v4l2_async_notifier_cleanup(&cam_dev->notifier);
> +}
> +
> +static const struct v4l2_ioctl_ops mtk_cam_v4l2_vcap_ioctl_ops = {
> + .vidioc_querycap = mtk_cam_vidioc_querycap,
> + .vidioc_enum_framesizes = mtk_cam_vidioc_enum_framesizes,
> + .vidioc_enum_fmt_vid_cap_mplane = mtk_cam_vidioc_enum_fmt,
> + .vidioc_g_fmt_vid_cap_mplane = mtk_cam_vidioc_g_fmt,
> + .vidioc_s_fmt_vid_cap_mplane = mtk_cam_vidioc_s_fmt,
> + .vidioc_try_fmt_vid_cap_mplane = mtk_cam_vidioc_try_fmt,
> + .vidioc_enum_input = mtk_cam_vidioc_enum_input,
> + .vidioc_g_input = mtk_cam_vidioc_g_input,
> + .vidioc_s_input = mtk_cam_vidioc_s_input,
I don't think we need vidioc_*_input. At least the current implementation in
this patch doesn't seem to do anything useful.
> + /* buffer queue management */
Drop this comment, as it's obvious that the callbacks with "buf" in the name
are related to buffers, there are some non-buffer callbacks below too (e.g.
vidioc_subscribe_event) and also the other ops structs below don't have such
comment.
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_ioctl_ops mtk_cam_v4l2_meta_cap_ioctl_ops = {
> + .vidioc_querycap = mtk_cam_vidioc_querycap,
> + .vidioc_enum_fmt_meta_cap = mtk_cam_vidioc_meta_enum_fmt,
> + .vidioc_g_fmt_meta_cap = mtk_cam_vidioc_g_meta_fmt,
> + .vidioc_s_fmt_meta_cap = mtk_cam_vidioc_g_meta_fmt,
> + .vidioc_try_fmt_meta_cap = mtk_cam_vidioc_g_meta_fmt,
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> +};
> +
> +static const struct v4l2_ioctl_ops mtk_cam_v4l2_meta_out_ioctl_ops = {
> + .vidioc_querycap = mtk_cam_vidioc_querycap,
> + .vidioc_enum_fmt_meta_out = mtk_cam_vidioc_meta_enum_fmt,
> + .vidioc_g_fmt_meta_out = mtk_cam_vidioc_g_meta_fmt,
> + .vidioc_s_fmt_meta_out = mtk_cam_vidioc_g_meta_fmt,
> + .vidioc_try_fmt_meta_out = mtk_cam_vidioc_g_meta_fmt,
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> +};
> +
> +static const struct v4l2_format meta_fmts[] = {
> + {
> + .fmt.meta = {
> + .dataformat = V4L2_META_FMT_MTISP_PARAMS,
> + .buffersize = 128 * PAGE_SIZE,
PAGE_SIZE is a weird unit for specifying generic buffer sizes. How about
making it 512 * SZ_1K?
That said, it should normally be just sizeof(struct some_struct_used_here).
Same for the other entries below.
> + },
> + },
> + {
> + .fmt.meta = {
> + .dataformat = V4L2_META_FMT_MTISP_3A,
> + .buffersize = 300 * PAGE_SIZE,
> + },
> + },
> + {
> + .fmt.meta = {
> + .dataformat = V4L2_META_FMT_MTISP_AF,
> + .buffersize = 160 * PAGE_SIZE,
> + },
> + },
> + {
> + .fmt.meta = {
> + .dataformat = V4L2_META_FMT_MTISP_LCS,
> + .buffersize = 72 * PAGE_SIZE,
> + },
> + },
> + {
> + .fmt.meta = {
> + .dataformat = V4L2_META_FMT_MTISP_LMV,
> + .buffersize = 256,
> + },
> + },
> +};
> +
> +static const struct v4l2_format stream_out_fmts[] = {
> + {
> + .fmt.pix_mp = {
> + .width = IMG_MAX_WIDTH,
> + .height = IMG_MAX_HEIGHT,
> + .pixelformat = V4L2_PIX_FMT_MTISP_B8,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SRGB,
> + .num_planes = 1,
> + },
> + },
> + {
> + .fmt.pix_mp = {
> + .width = IMG_MAX_WIDTH,
> + .height = IMG_MAX_HEIGHT,
> + .pixelformat = V4L2_PIX_FMT_MTISP_B10,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SRGB,
> + .num_planes = 1,
> + },
> + },
> + {
> + .fmt.pix_mp = {
> + .width = IMG_MAX_WIDTH,
> + .height = IMG_MAX_HEIGHT,
> + .pixelformat = V4L2_PIX_FMT_MTISP_B12,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SRGB,
> + .num_planes = 1,
> + },
> + },
> + {
> + .fmt.pix_mp = {
> + .width = IMG_MAX_WIDTH,
> + .height = IMG_MAX_HEIGHT,
> + .pixelformat = V4L2_PIX_FMT_MTISP_B14,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SRGB,
> + .num_planes = 1,
> + },
> + },
> +};
> +
> +static const struct v4l2_format bin_out_fmts[] = {
> + {
> + .fmt.pix_mp = {
> + .width = RRZ_MAX_WIDTH,
> + .height = RRZ_MAX_HEIGHT,
> + .pixelformat = V4L2_PIX_FMT_MTISP_F8,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_RAW,
> + .num_planes = 1,
> + },
> + },
> + {
> + .fmt.pix_mp = {
> + .width = RRZ_MAX_WIDTH,
> + .height = RRZ_MAX_HEIGHT,
> + .pixelformat = V4L2_PIX_FMT_MTISP_F10,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_RAW,
> + .num_planes = 1,
> + },
> + },
> + {
> + .fmt.pix_mp = {
> + .width = RRZ_MAX_WIDTH,
> + .height = RRZ_MAX_HEIGHT,
> + .pixelformat = V4L2_PIX_FMT_MTISP_F12,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_RAW,
> + .num_planes = 1,
> + },
> + },
> + {
> + .fmt.pix_mp = {
> + .width = RRZ_MAX_WIDTH,
> + .height = RRZ_MAX_HEIGHT,
> + .pixelformat = V4L2_PIX_FMT_MTISP_F14,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_RAW,
> + .num_planes = 1,
> + },
> + },
> +};
> +
> +static const struct v4l2_frmsizeenum img_frm_size_nums[] = {
> + {
> + .index = 0,
> + .type = V4L2_FRMSIZE_TYPE_CONTINUOUS,
> + .stepwise = {
> + .max_width = IMG_MAX_WIDTH,
> + .min_width = IMG_MIN_WIDTH,
> + .max_height = IMG_MAX_HEIGHT,
> + .min_height = IMG_MIN_HEIGHT,
> + .step_height = 1,
> + .step_width = 1,
> + },
> + },
> + {
> + .index = 0,
> + .type = V4L2_FRMSIZE_TYPE_CONTINUOUS,
> + .stepwise = {
> + .max_width = RRZ_MAX_WIDTH,
> + .min_width = RRZ_MIN_WIDTH,
> + .max_height = RRZ_MAX_HEIGHT,
> + .min_height = RRZ_MIN_HEIGHT,
> + .step_height = 1,
> + .step_width = 1,
> + },
> + },
> +};
> +
> +static const struct
> +mtk_cam_dev_node_desc output_queues[MTK_CAM_P1_TOTAL_OUTPUT] = {
> + {
> + .id = MTK_CAM_P1_META_IN_0,
> + .name = "meta input",
> + .description = "ISP tuning parameters",
> + .cap = V4L2_CAP_META_OUTPUT,
> + .buf_type = V4L2_BUF_TYPE_META_OUTPUT,
> + .link_flags = 0,
> + .capture = false,
> + .image = false,
> + .smem_alloc = true,
> + .fmts = meta_fmts,
> + .num_fmts = ARRAY_SIZE(meta_fmts),
> + .default_fmt_idx = 0,
> + .max_buf_count = 10,
> + .ioctl_ops = &mtk_cam_v4l2_meta_out_ioctl_ops,
> + },
> +};
> +
> +static const struct
> +mtk_cam_dev_node_desc capture_queues[MTK_CAM_P1_TOTAL_CAPTURE] = {
> + {
> + .id = MTK_CAM_P1_MAIN_STREAM_OUT,
> + .name = "main stream",
> + .cap = V4L2_CAP_VIDEO_CAPTURE_MPLANE,
> + .buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
> + .link_flags = 0,
> + .capture = true,
> + .image = true,
> + .smem_alloc = false,
> + .dma_port = R_IMGO,
> + .fmts = stream_out_fmts,
> + .num_fmts = ARRAY_SIZE(stream_out_fmts),
> + .default_fmt_idx = 1,
Why not just make it always 0 and move the default format to the beginning
of stream_out_fmts[]?
> + .ioctl_ops = &mtk_cam_v4l2_vcap_ioctl_ops,
> + .frmsizes = &img_frm_size_nums[0],
nit: If you only need to point to these data once, you could define them in
place, as a compound literal, like
> .frmsizes = &(struct v4l2_framesizeenum) {
> // initialize here
> },
> + },
> + {
> + .id = MTK_CAM_P1_PACKED_BIN_OUT,
> + .name = "packed out",
> + .cap = V4L2_CAP_VIDEO_CAPTURE_MPLANE,
> + .buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
> + .link_flags = 0,
> + .capture = true,
> + .image = true,
> + .smem_alloc = false,
> + .dma_port = R_RRZO,
> + .fmts = bin_out_fmts,
> + .num_fmts = ARRAY_SIZE(bin_out_fmts),
> + .default_fmt_idx = 1,
> + .ioctl_ops = &mtk_cam_v4l2_vcap_ioctl_ops,
> + .frmsizes = &img_frm_size_nums[1],
> + },
> + {
> + .id = MTK_CAM_P1_META_OUT_0,
> + .name = "partial meta 0",
> + .description = "AE/AWB histogram",
> + .cap = V4L2_CAP_META_CAPTURE,
> + .buf_type = V4L2_BUF_TYPE_META_CAPTURE,
> + .link_flags = 0,
> + .capture = true,
> + .image = false,
> + .smem_alloc = false,
> + .dma_port = R_AAO | R_FLKO | R_PSO,
> + .fmts = meta_fmts,
> + .num_fmts = ARRAY_SIZE(meta_fmts),
> + .default_fmt_idx = 1,
> + .max_buf_count = 5,
> + .ioctl_ops = &mtk_cam_v4l2_meta_cap_ioctl_ops,
> + },
> + {
> + .id = MTK_CAM_P1_META_OUT_1,
> + .name = "partial meta 1",
> + .description = "AF histogram",
> + .cap = V4L2_CAP_META_CAPTURE,
> + .buf_type = V4L2_BUF_TYPE_META_CAPTURE,
> + .link_flags = 0,
> + .capture = true,
> + .image = false,
> + .smem_alloc = false,
> + .dma_port = R_AFO,
> + .fmts = meta_fmts,
> + .num_fmts = ARRAY_SIZE(meta_fmts),
> + .default_fmt_idx = 2,
> + .max_buf_count = 5,
> + .ioctl_ops = &mtk_cam_v4l2_meta_cap_ioctl_ops,
> + },
> + {
> + .id = MTK_CAM_P1_META_OUT_2,
> + .name = "partial meta 2",
> + .description = "Local contrast enhanced statistics",
> + .cap = V4L2_CAP_META_CAPTURE,
> + .buf_type = V4L2_BUF_TYPE_META_CAPTURE,
> + .link_flags = 0,
> + .capture = true,
> + .image = false,
> + .smem_alloc = false,
> + .dma_port = R_LCSO,
> + .fmts = meta_fmts,
> + .num_fmts = ARRAY_SIZE(meta_fmts),
> + .default_fmt_idx = 3,
> + .max_buf_count = 10,
> + .ioctl_ops = &mtk_cam_v4l2_meta_cap_ioctl_ops,
> + },
> + {
> + .id = MTK_CAM_P1_META_OUT_3,
> + .name = "partial meta 3",
> + .description = "Local motion vector histogram",
> + .cap = V4L2_CAP_META_CAPTURE,
> + .buf_type = V4L2_BUF_TYPE_META_CAPTURE,
> + .link_flags = 0,
link_flags seems to be 0 for all nodes.
> + .capture = true,
We already know this from .buf_type. We can check using the
V4L2_TYPE_IS_OUTPUT() macro.
> + .image = false,
> + .smem_alloc = false,
> + .dma_port = R_LMVO,
> + .fmts = meta_fmts,
> + .num_fmts = ARRAY_SIZE(meta_fmts),
I don't think this is correct. The description suggests one specific format
(local motion vector histogram), so the queue should probably be hardwired
to that format?
> + .default_fmt_idx = 4,
> + .max_buf_count = 10,
Where does this number come from?
> + .ioctl_ops = &mtk_cam_v4l2_meta_cap_ioctl_ops,
> + },
> +};
> +
> +/* The helper to configure the device context */
> +static void mtk_cam_dev_queue_setup(struct mtk_cam_dev *cam_dev)
> +{
> + unsigned int i, node_idx;
> +
> + node_idx = 0;
> +
> + /* Setup the output queue */
> + for (i = 0; i < MTK_CAM_P1_TOTAL_OUTPUT; i++)
< ARRAY_SIZE(output_queues)
> + cam_dev->vdev_nodes[node_idx++].desc = output_queues[i];
> +
> + /* Setup the capture queue */
> + for (i = 0; i < MTK_CAM_P1_TOTAL_CAPTURE; i++)
< ARRAY_SIZE(capture_queues)
> + cam_dev->vdev_nodes[node_idx++].desc = capture_queues[i];
> +}
> +
> +int mtk_cam_dev_init(struct platform_device *pdev,
> + struct mtk_cam_dev *cam_dev)
> +{
> + int ret;
> +
> + cam_dev->pdev = pdev;
Do we need this additional mtk_cam_dev struct? Couldn't we just use
mtk_isp_p1 here?
> + mtk_cam_dev_queue_setup(cam_dev);
> + /* v4l2 sub-device registration */
> +
> + dev_dbg(&cam_dev->pdev->dev, "mem2mem2.name: %s\n",
> + MTK_CAM_DEV_P1_NAME);
This debugging message doesn't seem very useful. Please remove.
> + ret = mtk_cam_mem2mem2_v4l2_register(cam_dev);
> + if (ret)
> + return ret;
> +
> + ret = mtk_cam_v4l2_async_register(cam_dev);
> + if (ret) {
> + mtk_cam_v4l2_unregister(cam_dev);
Please use an error path on the bottom of the function instead. With goto
labels named after the next clean-up step that needs to be performed.
> + return ret;
> + }
> +
> + spin_lock_init(&cam_dev->req_lock);
> + INIT_LIST_HEAD(&cam_dev->req_list);
> + mutex_init(&cam_dev->lock);
> +
> + return 0;
> +}
> +
> +int mtk_cam_dev_release(struct platform_device *pdev,
"release" is normally used for file_operations. How about "cleanup"?
> + struct mtk_cam_dev *cam_dev)
> +{
> + mtk_cam_v4l2_async_unregister(cam_dev);
> + mtk_cam_v4l2_unregister(cam_dev);
> +
> + mutex_destroy(&cam_dev->lock);
> +
> + return 0;
> +}
I'd suggest moving any generic API implementation code (platform_device,
V4L2, VB2, Media Controller, etc.) to mtk_cam.c and then moving any low
level hardware/firmware-related code from mtk_cam.c and mtk_cam-scp.c to
mtk_cam_hw.c.
> +
diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.h b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.h
new file mode 100644
index 000000000000..825cdf20643a
> --- /dev/null
> +++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-v4l2-util.h
@@ -0,0 +1,173 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018 MediaTek Inc.
> + */
> +
> +#ifndef __MTK_CAM_DEV_V4L2_H__
> +#define __MTK_CAM_DEV_V4L2_H__
> +
> +#include <linux/device.h>
> +#include <linux/types.h>
> +#include <linux/platform_device.h>
> +#include <linux/spinlock.h>
> +#include <linux/videodev2.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/videobuf2-core.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#define MTK_CAM_DEV_P1_NAME "MTK-ISP-P1-V4L2"
Maybe it's not a critical thing, but generally it's a good practice to just
explicitly specific this name somewhere, e.g. struct
platform_driver::driver::name and then just refer to dev_driver_name(). It
makes it easier to make sure that the name stays the same everywhere.
> +
> +#define MTK_CAM_P1_META_IN_0 0
> +#define MTK_CAM_P1_TOTAL_OUTPUT 1
Since these are just some utility definitions, we can use enum instead of
assigning the values by hand.
> +
> +#define MTK_CAM_P1_MAIN_STREAM_OUT 1
> +#define MTK_CAM_P1_PACKED_BIN_OUT 2
> +#define MTK_CAM_P1_META_OUT_0 3
> +#define MTK_CAM_P1_META_OUT_1 4
> +#define MTK_CAM_P1_META_OUT_2 5
> +#define MTK_CAM_P1_META_OUT_3 6
> +#define MTK_CAM_P1_TOTAL_CAPTURE 6
Ditto.
> +
> +#define MTK_CAM_P1_TOTAL_NODES 7
Please just add the two totals together rather than manually specifying the
value.
> +
> +struct mtk_cam_dev_request {
> + struct media_request req;
> + struct list_head list;
> +};
> +
> +struct mtk_cam_dev_buffer {
> + struct vb2_v4l2_buffer vbb;
> + struct list_head list;
> + /* Intenal part */
> + dma_addr_t daddr;
> + dma_addr_t scp_addr;
> + unsigned int node_id;
> +};
Could you add kerneldoc comments for the 2 structs?
> +
> +/*
> + * struct mtk_cam_dev_node_desc - node attributes
> + *
> + * @id: id of the context queue
> + * @name: media entity name
> + * @description: descritpion of node
> + * @cap: mapped to V4L2 capabilities
> + * @buf_type: mapped to V4L2 buffer type
> + * @dma_port: the dma port associated to the buffer
> + * @link_flags: default media link flags
> + * @smem_alloc: using the cam_smem_drv as alloc ctx or not
> + * @capture: true for capture queue (device to user)
> + * false for output queue (from user to device)
> + * @image: true for image node, false for meta node
> + * @num_fmts: the number of supported formats
> + * @default_fmt_idx: default format of this node
> + * @max_buf_count: maximum V4L2 buffer count
> + * @ioctl_ops: mapped to v4l2_ioctl_ops
> + * @fmts: supported format
> + * @frmsizes: supported frame size number
> + *
> + */
> +struct mtk_cam_dev_node_desc {
> + u8 id;
> + char *name;
> + char *description;
> + u32 cap;
> + u32 buf_type;
> + u32 dma_port;
> + u32 link_flags;
> + u8 smem_alloc:1;
> + u8 capture:1;
> + u8 image:1;
> + u8 num_fmts;
> + u8 default_fmt_idx;
> + u8 max_buf_count;
> + const struct v4l2_ioctl_ops *ioctl_ops;
> + const struct v4l2_format *fmts;
> + const struct v4l2_frmsizeenum *frmsizes;
> +};
> +
> +/*
> + * struct mtk_cam_video_device - Mediatek video device structure.
> + *
> + * @id: Id for mtk_cam_dev_node_desc or mem2mem2_nodes array
> + * @enabled: Indicate the device is enabled or not
> + * @vdev_fmt: The V4L2 format of video device
> + * @vdev_apd: The media pad graph object of video device
vdev_pad?
> + * @vbq: A videobuf queue of video device
> + * @desc: The node attributes of video device
> + * @ctrl_handler: The control handler of video device
> + * @pending_list: List for pending buffers before enqueuing into driver
> + * @lock: Serializes vb2 queue and video device operations.
> + * @slock: Protect for pending_list.
> + *
Please fix the order of the documentation to match the order of the struct.
> + */
> +struct mtk_cam_video_device {
> + unsigned int id;
> + unsigned int enabled;
> + struct v4l2_format vdev_fmt;
> + struct mtk_cam_dev_node_desc desc;
> + struct video_device vdev;
Not documented above.
> + struct media_pad vdev_pad;
> + struct vb2_queue vbq;
> + struct v4l2_ctrl_handler ctrl_handler;
> + struct list_head pending_list;
> + /* Used for vbq & vdev */
It's already documented in the kerneldoc comment.
> + struct mutex lock;
> + /* protect for pending_list */
It's already documented in the kerneldoc comment.
> + spinlock_t slock;
How about calling it pending_list_lock?
> +};
> +
> +/*
> + * struct mtk_cam_dev - Mediatek camera device structure.
> + *
> + * @pdev: Pointer to platform device
> + * @smem_pdev: Pointer to shared memory platform device
> + * @pipeline: Media pipeline information
> + * @media_dev: Media device
> + * @subdev: The V4L2 sub-device
> + * @v4l2_dev: The V4L2 device driver
> + * @notifier: The v4l2_device notifier data
> + * @subdev_pads: Pointer to the number of media pads of this sub-device
> + * @ctrl_handler: The control handler
> + * @vdev_nodes: The array list of mtk_cam_video_device nodes
> + * @seninf: Pointer to the seninf sub-device
> + * @sensor: Pointer to the active sensor V4L2 sub-device when streaming on
> + * @lock: The mutex protecting video device open/release operations
> + * @streaming: Indicate the overall streaming status is on or off
> + * @streamed_node_count: The number of V4L2 video device nodes are streaming on
> + * @req_list: Lins to keep media requests before streaming on
> + * @req_lock: Protect the req_list data
> + *
> + * Below is the graph topology for Camera IO connection.
> + * sensor 1 (main) --> sensor IF --> P1 sub-device
> + * sensor 2 (sub) -->
This probably isn't the best place for graph topology description. I think
we actually want a separate documentation file for this, similar to
Documentation/media/v4l-drivers/ipu3.rst.
> + *
> + */
> +struct mtk_cam_dev {
> + struct platform_device *pdev;
> + struct device *smem_dev;
> + struct media_pipeline pipeline;
> + struct media_device media_dev;
> + struct v4l2_subdev subdev;
> + struct v4l2_device v4l2_dev;
> + struct v4l2_async_notifier notifier;
> + struct media_pad *subdev_pads;
> + struct v4l2_ctrl_handler ctrl_handler;
> + struct mtk_cam_video_device vdev_nodes[MTK_CAM_P1_TOTAL_NODES];
> + struct v4l2_subdev *seninf;
> + struct v4l2_subdev *sensor;
> + /* protect video device open/release operations */
It's already documented in the kerneldoc comment.
> + struct mutex lock;
> + unsigned int streaming:1;
> + atomic_t streamed_node_count;
> + struct list_head req_list;
> + /* protect for req_list */
It's already documented in the kerneldoc comment.
> + spinlock_t req_lock;
How about calling it req_list_lock?
Best regards,
Tomasz
^ permalink raw reply
* Re: [RFC,v3 7/9] media: platform: Add Mediatek ISP P1 device driver
From: Tomasz Figa @ 2019-07-10 9:56 UTC (permalink / raw)
To: Jungo Lin
Cc: devicetree, sean.cheng, frederic.chen, rynn.wu, srv_heupstream,
robh, ryan.yu, frankie.chiu, hverkuil, ddavenport, sj.huang,
linux-mediatek, laurent.pinchart, matthias.bgg, mchehab,
linux-arm-kernel, linux-media
In-Reply-To: <20190611035344.29814-8-jungo.lin@mediatek.com>
Hi Jungo,
On Tue, Jun 11, 2019 at 11:53:42AM +0800, Jungo Lin wrote:
> This patch adds the Mediatek ISP P1 HW control device driver.
> It handles the ISP HW configuration, provides interrupt handling and
> initializes the V4L2 device nodes and other functions.
>
> (The current metadata interface used in meta input and partial
> meta nodes is only a temporary solution to kick off the driver
> development and is not ready to be reviewed yet.)
>
> Signed-off-by: Jungo Lin <jungo.lin@mediatek.com>
> ---
> .../platform/mtk-isp/isp_50/cam/Makefile | 1 +
> .../mtk-isp/isp_50/cam/mtk_cam-regs.h | 126 ++
> .../platform/mtk-isp/isp_50/cam/mtk_cam.c | 1087 +++++++++++++++++
> .../platform/mtk-isp/isp_50/cam/mtk_cam.h | 243 ++++
> 4 files changed, 1457 insertions(+)
> create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-regs.h
> create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam.c
> create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam.h
>
Thanks for the patch! Please see my comments inline.
[snip]
> diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-regs.h b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-regs.h
> new file mode 100644
> index 000000000000..9e59a6bfc6b7
> --- /dev/null
> +++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-regs.h
> @@ -0,0 +1,126 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018 MediaTek Inc.
> + */
> +
> +#ifndef _CAM_REGS_H
> +#define _CAM_REGS_H
> +
> +/* TG Bit Mask */
> +#define VFDATA_EN_BIT BIT(0)
> +#define CMOS_EN_BIT BIT(0)
> +
> +/* normal signal bit */
> +#define VS_INT_ST BIT(0)
> +#define HW_PASS1_DON_ST BIT(11)
> +#define SOF_INT_ST BIT(12)
> +#define SW_PASS1_DON_ST BIT(30)
> +
> +/* err status bit */
> +#define TG_ERR_ST BIT(4)
> +#define TG_GBERR_ST BIT(5)
> +#define CQ_CODE_ERR_ST BIT(6)
> +#define CQ_APB_ERR_ST BIT(7)
> +#define CQ_VS_ERR_ST BIT(8)
> +#define AMX_ERR_ST BIT(15)
> +#define RMX_ERR_ST BIT(16)
> +#define BMX_ERR_ST BIT(17)
> +#define RRZO_ERR_ST BIT(18)
> +#define AFO_ERR_ST BIT(19)
> +#define IMGO_ERR_ST BIT(20)
> +#define AAO_ERR_ST BIT(21)
> +#define PSO_ERR_ST BIT(22)
> +#define LCSO_ERR_ST BIT(23)
> +#define BNR_ERR_ST BIT(24)
> +#define LSCI_ERR_ST BIT(25)
> +#define DMA_ERR_ST BIT(29)
> +
> +/* CAM DMA done status */
> +#define FLKO_DONE_ST BIT(4)
> +#define AFO_DONE_ST BIT(5)
> +#define AAO_DONE_ST BIT(7)
> +#define PSO_DONE_ST BIT(14)
> +
> +/* IRQ signal mask */
> +#define INT_ST_MASK_CAM ( \
> + VS_INT_ST |\
> + SOF_INT_ST |\
> + HW_PASS1_DON_ST |\
> + SW_PASS1_DON_ST)
> +
> +/* IRQ Error Mask */
> +#define INT_ST_MASK_CAM_ERR ( \
> + TG_ERR_ST |\
> + TG_GBERR_ST |\
> + CQ_CODE_ERR_ST |\
> + CQ_APB_ERR_ST |\
> + CQ_VS_ERR_ST |\
> + BNR_ERR_ST |\
> + RMX_ERR_ST |\
> + BMX_ERR_ST |\
> + BNR_ERR_ST |\
> + LSCI_ERR_ST |\
> + DMA_ERR_ST)
> +
> +/* IRQ Signal Log Mask */
> +#define INT_ST_LOG_MASK_CAM ( \
> + SOF_INT_ST |\
> + SW_PASS1_DON_ST |\
> + HW_PASS1_DON_ST |\
> + VS_INT_ST |\
> + TG_ERR_ST |\
> + TG_GBERR_ST |\
> + RRZO_ERR_ST |\
> + AFO_ERR_ST |\
> + IMGO_ERR_ST |\
> + AAO_ERR_ST |\
> + DMA_ERR_ST)
> +
> +/* DMA Event Notification Mask */
> +#define DMA_ST_MASK_CAM ( \
> + AAO_DONE_ST |\
> + AFO_DONE_ST)
Could we define the values next to the addresses of registers they apply to?
Also without the _BIT suffix and with the values prefixed with register
names. For example:
#define REG_TG_SEN_MODE 0x0230
#define TG_SEN_MODE_CMOS_EN BIT(0)
#define REG_TG_VF_CON 0x0234
#define TG_VF_CON_VFDATA_EN BIT(0)
> +
> +/* Status check */
> +#define REG_CTL_EN 0x0004
> +#define REG_CTL_DMA_EN 0x0008
> +#define REG_CTL_FMT_SEL 0x0010
> +#define REG_CTL_EN2 0x0018
> +#define REG_CTL_RAW_INT_EN 0x0020
> +#define REG_CTL_RAW_INT_STAT 0x0024
> +#define REG_CTL_RAW_INT2_STAT 0x0034
> +
> +#define REG_TG_SEN_MODE 0x0230
> +#define REG_TG_VF_CON 0x0234
> +
> +#define REG_IMGO_BASE_ADDR 0x1020
> +#define REG_RRZO_BASE_ADDR 0x1050
> +
> +/* Error status log */
> +#define REG_IMGO_ERR_STAT 0x1360
> +#define REG_RRZO_ERR_STAT 0x1364
> +#define REG_AAO_ERR_STAT 0x1368
> +#define REG_AFO_ERR_STAT 0x136c
> +#define REG_LCSO_ERR_STAT 0x1370
> +#define REG_UFEO_ERR_STAT 0x1374
> +#define REG_PDO_ERR_STAT 0x1378
> +#define REG_BPCI_ERR_STAT 0x137c
> +#define REG_LSCI_ERR_STAT 0x1384
> +#define REG_PDI_ERR_STAT 0x138c
> +#define REG_LMVO_ERR_STAT 0x1390
> +#define REG_FLKO_ERR_STAT 0x1394
> +#define REG_PSO_ERR_STAT 0x13a0
> +
> +/* ISP command */
> +#define REG_CQ_THR0_BASEADDR 0x0198
> +#define REG_HW_FRAME_NUM 0x13b8
> +
> +/* META */
> +#define REG_META0_VB2_INDEX 0x14dc
> +#define REG_META1_VB2_INDEX 0x151c
I don't believe these registers are really for VB2 indexes.
> +
> +/* FBC */
> +#define REG_AAO_FBC_STATUS 0x013c
> +#define REG_AFO_FBC_STATUS 0x0134
> +
> +#endif /* _CAM_REGS_H */
> diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam.c b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam.c
> new file mode 100644
> index 000000000000..c5a3babed69d
> --- /dev/null
> +++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam.c
> @@ -0,0 +1,1087 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Copyright (c) 2018 MediaTek Inc.
> +
> +#include <linux/atomic.h>
> +#include <linux/cdev.h>
> +#include <linux/compat.h>
> +#include <linux/fs.h>
> +#include <linux/interrupt.h>
> +#include <linux/jiffies.h>
> +#include <linux/kernel.h>
> +#include <linux/ktime.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/platform_data/mtk_scp.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/remoteproc.h>
> +#include <linux/sched/clock.h>
> +#include <linux/spinlock.h>
> +#include <linux/types.h>
> +#include <linux/videodev2.h>
> +#include <linux/vmalloc.h>
> +#include <media/v4l2-event.h>
> +
> +#include "mtk_cam.h"
> +#include "mtk_cam-regs.h"
> +#include "mtk_cam-smem.h"
> +
> +static const struct of_device_id mtk_isp_of_ids[] = {
> + {.compatible = "mediatek,mt8183-camisp",},
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, mtk_isp_of_ids);
Please move below. Just above the platform_driver struct where it's used.
> +
> +/* List of clocks required by isp cam */
> +static const char * const mtk_isp_clks[] = {
> + "camsys_cam_cgpdn", "camsys_camtg_cgpdn"
> +};
Please move inside mtk_isp_probe, as a static const local variable. That
could also let you shorten the name, to clk_names for example.
> +
> +static void isp_dump_dma_status(struct isp_device *isp_dev)
> +{
> + dev_err(isp_dev->dev,
> + "IMGO:0x%x, RRZO:0x%x, AAO=0x%x, AFO=0x%x, LMVO=0x%x\n",
> + readl(isp_dev->regs + REG_IMGO_ERR_STAT),
> + readl(isp_dev->regs + REG_RRZO_ERR_STAT),
> + readl(isp_dev->regs + REG_AAO_ERR_STAT),
> + readl(isp_dev->regs + REG_AFO_ERR_STAT),
> + readl(isp_dev->regs + REG_LMVO_ERR_STAT));
> + dev_err(isp_dev->dev,
> + "LCSO=0x%x, PSO=0x%x, FLKO=0x%x, BPCI:0x%x, LSCI=0x%x\n",
> + readl(isp_dev->regs + REG_LCSO_ERR_STAT),
> + readl(isp_dev->regs + REG_PSO_ERR_STAT),
> + readl(isp_dev->regs + REG_FLKO_ERR_STAT),
> + readl(isp_dev->regs + REG_BPCI_ERR_STAT),
> + readl(isp_dev->regs + REG_LSCI_ERR_STAT));
> +}
> +
> +static void mtk_cam_dev_event_frame_sync(struct mtk_cam_dev *cam_dev,
> + __u32 frame_seq_no)
> +{
> + struct v4l2_event event;
> +
> + memset(&event, 0, sizeof(event));
> + event.type = V4L2_EVENT_FRAME_SYNC;
> + event.u.frame_sync.frame_sequence = frame_seq_no;
nit: You can just initialize the structure in the declaration.
> + v4l2_event_queue(cam_dev->subdev.devnode, &event);
> +}
> +
> +static void mtk_cam_dev_job_finish(struct mtk_isp_p1_ctx *isp_ctx,
> + unsigned int request_fd,
> + unsigned int frame_seq_no,
> + struct list_head *list_buf,
> + enum vb2_buffer_state state)
> +{
> + struct isp_p1_device *p1_dev = p1_ctx_to_dev(isp_ctx);
> + struct mtk_cam_dev *cam_dev = &p1_dev->cam_dev;
> + struct mtk_cam_dev_buffer *buf, *b0;
> + u64 timestamp;
Too many spaces between u64 and timestamp.
> +
> + if (!cam_dev->streaming)
> + return;
> +
> + dev_dbg(&p1_dev->pdev->dev, "%s request fd:%d frame_seq:%d state:%d\n",
> + __func__, request_fd, frame_seq_no, state);
> +
> + /*
> + * Set the buffer's VB2 status so that the user can dequeue
> + * the buffer.
> + */
> + timestamp = ktime_get_ns();
> + list_for_each_entry_safe(buf, b0, list_buf, list) {
nit: s/b0/buf_prev/
> + list_del(&buf->list);
> + buf->vbb.vb2_buf.timestamp = timestamp;
> + buf->vbb.sequence = frame_seq_no;
> + if (buf->vbb.vb2_buf.state == VB2_BUF_STATE_ACTIVE)
Any buffer that is not active shouldn't be on this list. If it is then it's
a bug somewhere else in the driver. Could be possibly related to the request
handling issues I pointed out in another comment.
> + vb2_buffer_done(&buf->vbb.vb2_buf, state);
> + }
> +}
> +
> +static void isp_deque_frame(struct isp_p1_device *p1_dev,
dequeue
> + unsigned int node_id, int vb2_index,
> + int frame_seq_no)
> +{
> + struct mtk_cam_dev *cam_dev = &p1_dev->cam_dev;
> + struct device *dev = &p1_dev->pdev->dev;
> + struct mtk_cam_video_device *node = &cam_dev->vdev_nodes[node_id];
> + struct mtk_cam_dev_buffer *b, *b0;
> + struct vb2_buffer *vb;
> +
> + if (!cam_dev->vdev_nodes[node_id].enabled || !cam_dev->streaming)
> + return;
> +
> + spin_lock(&node->slock);
> + b = list_first_entry(&node->pending_list,
> + struct mtk_cam_dev_buffer,
> + list);
> + list_for_each_entry_safe(b, b0, &node->pending_list, list) {
> + vb = &b->vbb.vb2_buf;
> + if (!vb->vb2_queue->uses_requests &&
> + vb->index == vb2_index &&
> + vb->state == VB2_BUF_STATE_ACTIVE) {
> + dev_dbg(dev, "%s:%d:%d", __func__, node_id, vb2_index);
> + vb->timestamp = ktime_get_ns();
> + b->vbb.sequence = frame_seq_no;
> + vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
> + list_del(&b->list);
> + break;
> + }
> + }
> + spin_unlock(&node->slock);
> +}
> +
> +static void isp_deque_request_frame(struct isp_p1_device *p1_dev,
dequeue
> + int frame_seq_no)
> +{
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + struct device *dev = &p1_dev->pdev->dev;
> + struct mtk_isp_queue_job *framejob, *tmp;
> + struct isp_queue *p1_enqueue_list = &isp_ctx->p1_enqueue_list;
> +
> + /* Match dequeue work and enqueue frame */
> + spin_lock(&p1_enqueue_list->lock);
> + list_for_each_entry_safe(framejob, tmp, &p1_enqueue_list->queue,
> + list_entry) {
> + dev_dbg(dev,
> + "%s frame_seq_no:%d, target frame_seq_no:%d\n",
> + __func__,
> + framejob->frame_seq_no, frame_seq_no);
> + /* Match by the en-queued request number */
> + if (framejob->frame_seq_no == frame_seq_no) {
> + /* Pass to user space */
> + mtk_cam_dev_job_finish(isp_ctx,
> + framejob->request_fd,
> + framejob->frame_seq_no,
> + &framejob->list_buf,
> + VB2_BUF_STATE_DONE);
> + atomic_dec(&p1_enqueue_list->queue_cnt);
> + dev_dbg(dev,
> + "frame_seq_no:%d is done, queue_cnt:%d\n",
> + framejob->frame_seq_no,
> + atomic_read(&p1_enqueue_list->queue_cnt));
> +
> + /* Remove only when frame ready */
> + list_del(&framejob->list_entry);
> + kfree(framejob);
> + break;
> + } else if (framejob->frame_seq_no < frame_seq_no) {
> + /* Pass to user space for frame drop */
> + mtk_cam_dev_job_finish(isp_ctx,
> + framejob->request_fd,
> + framejob->frame_seq_no,
> + &framejob->list_buf,
> + VB2_BUF_STATE_ERROR);
> + atomic_dec(&p1_enqueue_list->queue_cnt);
> + dev_warn(dev,
> + "frame_seq_no:%d drop, queue_cnt:%d\n",
> + framejob->frame_seq_no,
> + atomic_read(&p1_enqueue_list->queue_cnt));
> +
> + /* Remove only drop frame */
> + list_del(&framejob->list_entry);
> + kfree(framejob);
> + } else {
> + break;
> + }
> + }
> + spin_unlock(&p1_enqueue_list->lock);
> +}
> +
> +static int isp_deque_work(void *data)
dequeue
> +{
> + struct isp_p1_device *p1_dev = (struct isp_p1_device *)data;
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + struct mtk_cam_dev_stat_event_data event_data;
> + atomic_t *irq_data_end = &isp_ctx->irq_data_end;
> + atomic_t *irq_data_start = &isp_ctx->irq_data_start;
> + unsigned long flags;
> + int ret, i;
> +
> + while (1) {
> + ret = wait_event_interruptible(isp_ctx->isp_deque_thread.wq,
> + (atomic_read(irq_data_end) !=
> + atomic_read(irq_data_start)) ||
> + kthread_should_stop());
> +
> + if (kthread_should_stop())
> + break;
> +
> + spin_lock_irqsave(&isp_ctx->irq_dequeue_lock, flags);
> + i = atomic_read(&isp_ctx->irq_data_start);
> + memcpy(&event_data, &isp_ctx->irq_event_datas[i],
> + sizeof(event_data));
> + atomic_set(&isp_ctx->irq_data_start, ++i & 0x3);
> + spin_unlock_irqrestore(&isp_ctx->irq_dequeue_lock, flags);
> +
> + if (event_data.irq_status_mask & HW_PASS1_DON_ST &&
> + event_data.dma_status_mask & AAO_DONE_ST) {
> + isp_deque_frame(p1_dev,
> + MTK_CAM_P1_META_OUT_0,
> + event_data.meta0_vb2_index,
> + event_data.frame_seq_no);
> + }
> + if (event_data.dma_status_mask & AFO_DONE_ST) {
> + isp_deque_frame(p1_dev,
> + MTK_CAM_P1_META_OUT_1,
> + event_data.meta1_vb2_index,
> + event_data.frame_seq_no);
> + }
> + if (event_data.irq_status_mask & SW_PASS1_DON_ST) {
> + isp_deque_frame(p1_dev,
> + MTK_CAM_P1_META_OUT_0,
> + event_data.meta0_vb2_index,
> + event_data.frame_seq_no);
> + isp_deque_frame(p1_dev,
> + MTK_CAM_P1_META_OUT_1,
> + event_data.meta1_vb2_index,
> + event_data.frame_seq_no);
> + isp_deque_request_frame(p1_dev,
> + event_data.frame_seq_no);
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int irq_handle_sof(struct isp_device *isp_dev,
> + dma_addr_t base_addr,
> + unsigned int frame_num)
> +{
> + unsigned int addr_offset;
> + struct isp_p1_device *p1_dev = get_p1_device(isp_dev->dev);
> + int cq_num = atomic_read(&p1_dev->isp_ctx.composed_frame_id);
> +
> + isp_dev->sof_count += 1;
> +
> + if (cq_num <= frame_num) {
> + dev_dbg(isp_dev->dev,
> + "SOF_INT_ST, wait next, cq_num:%d, frame_num:%d",
> + cq_num, frame_num);
> + atomic_set(&p1_dev->isp_ctx.composing_frame, 0);
> + return cq_num;
> + }
> + atomic_set(&p1_dev->isp_ctx.composing_frame, cq_num - frame_num);
> +
> + addr_offset = CQ_ADDRESS_OFFSET * (frame_num % CQ_BUFFER_COUNT);
> + writel(base_addr + addr_offset, isp_dev->regs + REG_CQ_THR0_BASEADDR);
> + dev_dbg(isp_dev->dev,
> + "SOF_INT_ST, update next, cq_num:%d, frame_num:%d cq_addr:0x%x",
> + cq_num, frame_num, addr_offset);
> +
> + return cq_num;
> +}
> +
> +static void irq_handle_notify_event(struct isp_device *isp_dev,
> + unsigned int irq_status,
> + unsigned int dma_status,
> + bool sof_only)
> +{
> + struct isp_p1_device *p1_dev = get_p1_device(isp_dev->dev);
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + struct device *dev = isp_dev->dev;
> + unsigned long flags;
> + int i;
> +
> + if (irq_status & VS_INT_ST) {
> + /* Notify specific HW events to user space */
> + mtk_cam_dev_event_frame_sync(&p1_dev->cam_dev,
> + isp_dev->current_frame);
Shouldn't we call this at SOF_INT_ST and not VS? At least according to the
definition of the V4L2_EVENT_FRAME_SYNC event at
https://www.kernel.org/doc/html/latest/media/uapi/v4l/vidioc-dqevent.html
> + dev_dbg(dev,
> + "frame sync is sent:%d:%d\n",
> + isp_dev->sof_count,
> + isp_dev->current_frame);
> + if (sof_only)
> + return;
If this function can be called only to perform this block, perhaps it should
be split into two functions?
Also, what happens if we get sof_only, but we don't get VS_INT_ST set in
irq_status? Is it expected that in such case the other part of the function
is executed?
> + }
> +
> + if (irq_status & SW_PASS1_DON_ST) {
> + /* Notify TX thread to send if TX frame is blocked */
> + wake_up_interruptible(&isp_ctx->composer_tx_thread.wq);
> + }
> +
> + spin_lock_irqsave(&isp_ctx->irq_dequeue_lock, flags);
> + i = atomic_read(&isp_ctx->irq_data_end);
> + isp_ctx->irq_event_datas[i].frame_seq_no = isp_dev->current_frame;
> + isp_ctx->irq_event_datas[i].meta0_vb2_index = isp_dev->meta0_vb2_index;
> + isp_ctx->irq_event_datas[i].meta1_vb2_index = isp_dev->meta1_vb2_index;
> + isp_ctx->irq_event_datas[i].irq_status_mask =
> + (irq_status & INT_ST_MASK_CAM);
> + isp_ctx->irq_event_datas[i].dma_status_mask =
> + (dma_status & DMA_ST_MASK_CAM);
> + atomic_set(&isp_ctx->irq_data_end, ++i & 0x3);
> + spin_unlock_irqrestore(&isp_ctx->irq_dequeue_lock, flags);
> +
> + wake_up_interruptible(&isp_ctx->isp_deque_thread.wq);
I can see that all isp_deque_work() does is returning buffers to vb2. I
don't think we need this intricate system to do that, as we could just do
it inside the interrupt handler, in isp_irq_cam() directly.
> +
> + dev_dbg(dev,
> + "%s IRQ:0x%x DMA:0x%x seq:%d idx0:%d idx1:%d\n",
> + __func__,
> + (irq_status & INT_ST_MASK_CAM),
> + (dma_status & DMA_ST_MASK_CAM),
> + isp_dev->current_frame,
> + isp_dev->meta0_vb2_index,
> + isp_dev->meta1_vb2_index);
> +}
> +
> +irqreturn_t isp_irq_cam(int irq, void *data)
> +{
> + struct isp_device *isp_dev = (struct isp_device *)data;
> + struct isp_p1_device *p1_dev = get_p1_device(isp_dev->dev);
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + struct device *dev = isp_dev->dev;
> + unsigned int cam_idx, cq_num, hw_frame_num;
> + unsigned int meta0_vb2_index, meta1_vb2_index;
> + unsigned int irq_status, err_status, dma_status;
> + unsigned int aao_fbc, afo_fbc;
> + unsigned long flags;
> +
> + /* Check the streaming is off or not */
> + if (!p1_dev->cam_dev.streaming)
> + return IRQ_HANDLED;
This shouldn't be needed. The driver needs to unmask the interrupts in
hardware registers when it starts streaming and mask them when it stops.
Note that I mean the P1 hardware registers, not disable_irq(), which
shouldn't be used.
> +
> + cam_idx = isp_dev->isp_hw_module - ISP_CAM_A_IDX;
> + cq_num = 0;
> +
> + spin_lock_irqsave(&isp_dev->spinlock_irq, flags);
> + irq_status = readl(isp_dev->regs + REG_CTL_RAW_INT_STAT);
> + dma_status = readl(isp_dev->regs + REG_CTL_RAW_INT2_STAT);
> + hw_frame_num = readl(isp_dev->regs + REG_HW_FRAME_NUM);
> + meta0_vb2_index = readl(isp_dev->regs + REG_META0_VB2_INDEX);
> + meta1_vb2_index = readl(isp_dev->regs + REG_META1_VB2_INDEX);
Hmm, reading vb2 buffer index from hardware registers? Was this hardware
designed exclusively for V4L2? ;)
Jokes aside, how does the hardware know V4L2 buffer indexes?
> + aao_fbc = readl(isp_dev->regs + REG_AAO_FBC_STATUS);
> + afo_fbc = readl(isp_dev->regs + REG_AFO_FBC_STATUS);
> + spin_unlock_irqrestore(&isp_dev->spinlock_irq, flags);
> +
> + /* Ignore unnecessary IRQ */
> + if (!irq_status && (!(dma_status & DMA_ST_MASK_CAM)))
> + return IRQ_HANDLED;
Unnecessary IRQs should be masked in the hardware IRQ mask registers. If we
get an interrupt without any unmasked hardware IRQs active in the status,
that's an error somewhere and we should return IRQ_NONE.
> +
> + err_status = irq_status & INT_ST_MASK_CAM_ERR;
> +
> + /* Sof, done order check */
> + if ((irq_status & SOF_INT_ST) && (irq_status & HW_PASS1_DON_ST)) {
> + dev_dbg(dev, "sof_done block cnt:%d\n", isp_dev->sof_count);
> +
> + /* Notify IRQ event and enqueue frame */
> + irq_handle_notify_event(isp_dev, irq_status, dma_status, 0);
> + isp_dev->current_frame = hw_frame_num;
What exactly is hw_frame_num? Shouldn't we assign it before notifying the
event?
> + isp_dev->meta0_vb2_index = meta0_vb2_index;
> + isp_dev->meta1_vb2_index = meta1_vb2_index;
> + } else {
> + if (irq_status & SOF_INT_ST) {
> + isp_dev->current_frame = hw_frame_num;
> + isp_dev->meta0_vb2_index = meta0_vb2_index;
> + isp_dev->meta1_vb2_index = meta1_vb2_index;
> + }
> + irq_handle_notify_event(isp_dev, irq_status, dma_status, 1);
> + }
The if and else blocks do almost the same things just in different order. Is
it really expected?
> +
> + if (irq_status & SOF_INT_ST)
> + cq_num = irq_handle_sof(isp_dev, isp_ctx->scp_mem_iova,
> + hw_frame_num);
> +
> + /* Check ISP error status */
> + if (err_status) {
> + dev_err(dev,
> + "raw_int_err:0x%x/0x%x\n",
> + irq_status, err_status);
> + /* Show DMA errors in detail */
> + if (err_status & DMA_ERR_ST)
> + isp_dump_dma_status(isp_dev);
> + }
> +
> + if (irq_status & INT_ST_LOG_MASK_CAM)
> + dev_dbg(dev, IRQ_STAT_STR,
Please just put that string here, otherwise the reader would have no idea
what message is being printed here.
> + 'A' + cam_idx,
> + isp_dev->sof_count,
> + irq_status,
> + dma_status,
> + hw_frame_num,
> + cq_num,
> + aao_fbc,
> + afo_fbc);
nit: No need to put each argument in its own line.
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int isp_setup_scp_rproc(struct isp_p1_device *p1_dev)
> +{
> + phandle rproc_phandle;
> + struct device *dev = &p1_dev->pdev->dev;
> + int ret;
> +
> + p1_dev->scp_pdev = scp_get_pdev(p1_dev->pdev);
> + if (!p1_dev->scp_pdev) {
> + dev_err(dev, "Failed to get scp device\n");
> + return -ENODEV;
> + }
> +
> + ret = of_property_read_u32(dev->of_node, "mediatek,scp",
> + &rproc_phandle);
> + if (ret) {
> + dev_err(dev, "fail to get rproc_phandle:%d\n", ret);
> + return -EINVAL;
> + }
> +
> + p1_dev->rproc_handle = rproc_get_by_phandle(rproc_phandle);
> + dev_dbg(dev, "p1 rproc_phandle: 0x%pK\n\n", p1_dev->rproc_handle);
> + if (!p1_dev->rproc_handle) {
> + dev_err(dev, "fail to get rproc_handle\n");
> + return -EINVAL;
> + }
This look-up should be done once in probe(). Only the rproc_boot() should
happen dynamically.
> +
> + ret = rproc_boot(p1_dev->rproc_handle);
> + if (ret) {
> + /*
> + * Return 0 if downloading firmware successfully,
> + * otherwise it is failed
> + */
> + return -ENODEV;
> + }
> +
> + return 0;
> +}
> +
> +static int isp_init_context(struct isp_p1_device *p1_dev)
> +{
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + struct device *dev = &p1_dev->pdev->dev;
> + unsigned int i;
> +
> + dev_dbg(dev, "init irq work thread\n");
> + if (!isp_ctx->isp_deque_thread.thread) {
> + init_waitqueue_head(&isp_ctx->isp_deque_thread.wq);
> + isp_ctx->isp_deque_thread.thread =
> + kthread_run(isp_deque_work, (void *)p1_dev,
> + "isp_deque_work");
> + if (IS_ERR(isp_ctx->isp_deque_thread.thread)) {
> + dev_err(dev, "unable to alloc kthread\n");
> + isp_ctx->isp_deque_thread.thread = NULL;
> + return -ENOMEM;
> + }
> + }
> + spin_lock_init(&isp_ctx->irq_dequeue_lock);
> + mutex_init(&isp_ctx->lock);
> +
> + INIT_LIST_HEAD(&isp_ctx->p1_enqueue_list.queue);
> + atomic_set(&isp_ctx->p1_enqueue_list.queue_cnt, 0);
> +
> + for (i = 0; i < ISP_DEV_NODE_NUM; i++)
> + spin_lock_init(&p1_dev->isp_devs[i].spinlock_irq);
> +
> + spin_lock_init(&isp_ctx->p1_enqueue_list.lock);
> + spin_lock_init(&isp_ctx->composer_txlist.lock);
> +
> + atomic_set(&isp_ctx->irq_data_end, 0);
> + atomic_set(&isp_ctx->irq_data_start, 0);
> +
> + return 0;
Everything here looks like something that should be done once in probe. I
also don't see a point of having a separate mtk_isp_p1_ctx struct for the
data above. It could be just located in p1_dev, at least for now.
If we end up implementing support for multiple contexts, we could isolate
per-context data then, once we know what's really per-context. For now,
let's keep it simple.
> +}
> +
> +static int isp_uninit_context(struct device *dev)
> +{
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + struct mtk_isp_queue_job *framejob, *tmp_framejob;
> +
> + spin_lock_irq(&isp_ctx->p1_enqueue_list.lock);
> + list_for_each_entry_safe(framejob, tmp_framejob,
> + &isp_ctx->p1_enqueue_list.queue, list_entry) {
> + list_del(&framejob->list_entry);
> + kfree(framejob);
> + }
> + spin_unlock_irq(&isp_ctx->p1_enqueue_list.lock);
> +
> + if (isp_ctx->isp_deque_thread.thread) {
> + kthread_stop(isp_ctx->isp_deque_thread.thread);
> + wake_up_interruptible(&isp_ctx->isp_deque_thread.wq);
> + isp_ctx->isp_deque_thread.thread = NULL;
> + }
> +
> + mutex_destroy(&isp_ctx->lock);
> +
> + return 0;
> +}
> +
> +static unsigned int get_enabled_dma_ports(struct mtk_cam_dev *cam_dev)
> +{
> + unsigned int enabled_dma_ports, i;
> +
> + /* Get the enabled meta DMA ports */
> + enabled_dma_ports = 0;
> +
> + for (i = 0; i < MTK_CAM_P1_TOTAL_NODES; i++)
> + if (cam_dev->vdev_nodes[i].enabled)
> + enabled_dma_ports |=
> + cam_dev->vdev_nodes[i].desc.dma_port;
> +
> + dev_dbg(&cam_dev->pdev->dev, "%s :0x%x", __func__, enabled_dma_ports);
> +
> + return enabled_dma_ports;
> +}
> +
> +/* Utility functions */
> +static unsigned int get_sensor_pixel_id(unsigned int fmt)
> +{
> + switch (fmt) {
> + case MEDIA_BUS_FMT_SBGGR8_1X8:
> + case MEDIA_BUS_FMT_SBGGR10_1X10:
> + case MEDIA_BUS_FMT_SBGGR12_1X12:
> + case MEDIA_BUS_FMT_SBGGR14_1X14:
> + return RAW_PXL_ID_B;
> + case MEDIA_BUS_FMT_SGBRG8_1X8:
> + case MEDIA_BUS_FMT_SGBRG10_1X10:
> + case MEDIA_BUS_FMT_SGBRG12_1X12:
> + case MEDIA_BUS_FMT_SGBRG14_1X14:
> + return RAW_PXL_ID_GB;
> + case MEDIA_BUS_FMT_SGRBG8_1X8:
> + case MEDIA_BUS_FMT_SGRBG10_1X10:
> + case MEDIA_BUS_FMT_SGRBG12_1X12:
> + case MEDIA_BUS_FMT_SGRBG14_1X14:
> + return RAW_PXL_ID_GR;
> + case MEDIA_BUS_FMT_SRGGB8_1X8:
> + case MEDIA_BUS_FMT_SRGGB10_1X10:
> + case MEDIA_BUS_FMT_SRGGB12_1X12:
> + case MEDIA_BUS_FMT_SRGGB14_1X14:
> + return RAW_PXL_ID_R;
> + default:
Could we fail here instead?
> + return RAW_PXL_ID_B;
> + }
> +}
> +
> +static unsigned int get_sensor_fmt(unsigned int fmt)
> +{
> + switch (fmt) {
> + case MEDIA_BUS_FMT_SBGGR8_1X8:
> + case MEDIA_BUS_FMT_SGBRG8_1X8:
> + case MEDIA_BUS_FMT_SGRBG8_1X8:
> + case MEDIA_BUS_FMT_SRGGB8_1X8:
> + return IMG_FMT_BAYER8;
> + case MEDIA_BUS_FMT_SBGGR10_1X10:
> + case MEDIA_BUS_FMT_SGBRG10_1X10:
> + case MEDIA_BUS_FMT_SGRBG10_1X10:
> + case MEDIA_BUS_FMT_SRGGB10_1X10:
> + return IMG_FMT_BAYER10;
> + case MEDIA_BUS_FMT_SBGGR12_1X12:
> + case MEDIA_BUS_FMT_SGBRG12_1X12:
> + case MEDIA_BUS_FMT_SGRBG12_1X12:
> + case MEDIA_BUS_FMT_SRGGB12_1X12:
> + return IMG_FMT_BAYER12;
> + case MEDIA_BUS_FMT_SBGGR14_1X14:
> + case MEDIA_BUS_FMT_SGBRG14_1X14:
> + case MEDIA_BUS_FMT_SGRBG14_1X14:
> + case MEDIA_BUS_FMT_SRGGB14_1X14:
> + return IMG_FMT_BAYER14;
> + default:
> + return IMG_FMT_UNKNOWN;
> + }
> +}
> +
> +static unsigned int get_img_fmt(unsigned int fourcc)
> +{
> + switch (fourcc) {
> + case V4L2_PIX_FMT_MTISP_B8:
> + return IMG_FMT_BAYER8;
> + case V4L2_PIX_FMT_MTISP_F8:
> + return IMG_FMT_FG_BAYER8;
> + case V4L2_PIX_FMT_MTISP_B10:
> + return IMG_FMT_BAYER10;
> + case V4L2_PIX_FMT_MTISP_F10:
> + return IMG_FMT_FG_BAYER10;
> + case V4L2_PIX_FMT_MTISP_B12:
> + return IMG_FMT_BAYER12;
> + case V4L2_PIX_FMT_MTISP_F12:
> + return IMG_FMT_FG_BAYER12;
> + case V4L2_PIX_FMT_MTISP_B14:
> + return IMG_FMT_BAYER14;
> + case V4L2_PIX_FMT_MTISP_F14:
> + return IMG_FMT_FG_BAYER14;
> + default:
> + return IMG_FMT_UNKNOWN;
> + }
> +}
> +
> +static unsigned int get_pixel_byte(unsigned int fourcc)
> +{
> + switch (fourcc) {
> + case V4L2_PIX_FMT_MTISP_B8:
> + case V4L2_PIX_FMT_MTISP_F8:
> + return 8;
> + case V4L2_PIX_FMT_MTISP_B10:
> + case V4L2_PIX_FMT_MTISP_F10:
> + return 10;
> + case V4L2_PIX_FMT_MTISP_B12:
> + case V4L2_PIX_FMT_MTISP_F12:
> + return 12;
> + case V4L2_PIX_FMT_MTISP_B14:
> + case V4L2_PIX_FMT_MTISP_F14:
> + return 14;
> + default:
Could we fail here instead, so that we don't mask some potential errors?
> + return 10;
> + }
> +}
> +
> +static void config_img_fmt(struct device *dev, struct p1_img_output *out_fmt,
> + const struct v4l2_format *in_fmt,
> + const struct v4l2_subdev_format *sd_format)
> +{
> + out_fmt->img_fmt = get_img_fmt(in_fmt->fmt.pix_mp.pixelformat);
> + out_fmt->pixel_byte = get_pixel_byte(in_fmt->fmt.pix_mp.pixelformat);
> + out_fmt->size.w = in_fmt->fmt.pix_mp.width;
> + out_fmt->size.h = in_fmt->fmt.pix_mp.height;
> +
> + out_fmt->size.stride = in_fmt->fmt.pix_mp.plane_fmt[0].bytesperline;
> + out_fmt->size.xsize = in_fmt->fmt.pix_mp.plane_fmt[0].bytesperline;
Please group operations on the same field together, i.e. remove the blank
line above size.stride and add one blank line above size.w.
> +
> + out_fmt->crop.left = 0x0;
> + out_fmt->crop.top = 0x0;
> +
Remove the blank line.
> + out_fmt->crop.width = sd_format->format.width;
> + out_fmt->crop.height = sd_format->format.height;
> +
> + WARN_ONCE(in_fmt->fmt.pix_mp.width > out_fmt->crop.width ||
> + in_fmt->fmt.pix_mp.height > out_fmt->crop.height,
> + "img out:%d:%d in:%d:%d",
> + in_fmt->fmt.pix_mp.width, in_fmt->fmt.pix_mp.height,
> + out_fmt->crop.width, out_fmt->crop.height);
We should check this earlier and fail the streaming start if there is a
mismatch between sensor and video node configuration.
> +
> + dev_dbg(dev, "pixel_byte:%d img_fmt:0x%x\n",
> + out_fmt->pixel_byte,
> + out_fmt->img_fmt);
> + dev_dbg(dev,
> + "param:size=%0dx%0d, stride:%d, xsize:%d, crop=%0dx%0d\n",
> + out_fmt->size.w, out_fmt->size.h,
> + out_fmt->size.stride, out_fmt->size.xsize,
> + out_fmt->crop.width, out_fmt->crop.height);
> +}
> +
> +/* ISP P1 interface functions */
> +int mtk_isp_power_init(struct mtk_cam_dev *cam_dev)
> +{
> + struct device *dev = &cam_dev->pdev->dev;
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + int ret;
> +
> + ret = isp_setup_scp_rproc(p1_dev);
> + if (ret)
> + return ret;
> +
> + ret = isp_init_context(p1_dev);
> + if (ret)
> + return ret;
The above function doesn't really seem to be related to power management.
Should it be called from subdev stream on?
> +
> + ret = isp_composer_init(dev);
> + if (ret)
> + goto composer_err;
This also doesn't seem to be related to power management.
> +
> + pm_runtime_get_sync(dev);
> +
> + /* ISP HW INIT */
> + isp_ctx->isp_hw_module = ISP_CAM_B_IDX;
There is some amount of code handling various values of isp_hw_module in
this driver. If we're hardcoding ISP_CAM_B_IDX here, it's basically dead
code that can't be tested. Please either add support for all the indexes in
the driver or simplify all the code to just handle CAM_B.
> + /* Use pure RAW as default HW path */
> + isp_ctx->isp_raw_path = ISP_PURE_RAW_PATH;
> + atomic_set(&p1_dev->cam_dev.streamed_node_count, 0);
> +
> + isp_composer_hw_init(dev);
> + /* Check enabled DMAs which is configured by media setup */
> + isp_composer_meta_config(dev, get_enabled_dma_ports(cam_dev));
Hmm, this seems to be also configured by isp_compoer_hw_config(). Are both
necessary?
> +
> + dev_dbg(dev, "%s done\n", __func__);
> +
> + return 0;
> +
> +composer_err:
> + isp_uninit_context(dev);
> +
> + return ret;
> +}
> +
> +int mtk_isp_power_release(struct device *dev)
> +{
> + isp_composer_hw_deinit(dev);
> + isp_uninit_context(dev);
These two don't seem to be related to power either.
Instead, I don't see anything that could undo the rproc_boot() operation
here.
> +
> + dev_dbg(dev, "%s done\n", __func__);
> +
> + return 0;
> +}
> +
> +int mtk_isp_config(struct device *dev)
> +{
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + struct p1_config_param config_param;
> + struct mtk_cam_dev *cam_dev = &p1_dev->cam_dev;
> + struct v4l2_subdev_format sd_fmt;
> + unsigned int enabled_dma_ports;
> + struct v4l2_format *img_fmt;
> + int ret;
> +
> + p1_dev->isp_devs[isp_ctx->isp_hw_module].current_frame = 0;
> + p1_dev->isp_devs[isp_ctx->isp_hw_module].sof_count = 0;
> +
> + isp_ctx->frame_seq_no = 1;
> + atomic_set(&isp_ctx->composed_frame_id, 0);
> +
> + /* Get the enabled DMA ports */
> + enabled_dma_ports = get_enabled_dma_ports(cam_dev);
> + dev_dbg(dev, "%s enable_dma_ports:0x%x", __func__, enabled_dma_ports);
> +
> + /* Sensor config */
> + sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
> + ret = v4l2_subdev_call(cam_dev->sensor, pad, get_fmt, NULL, &sd_fmt);
> +
Unnecessary blank line.
> + if (ret) {
> + dev_dbg(dev, "sensor g_fmt on failed:%d\n", ret);
> + return -EPERM;
return ret?
> + }
> +
> + dev_dbg(dev,
> + "get_fmt ret=%d, w=%d, h=%d, code=0x%x, field=%d, color=%d\n",
> + ret, sd_fmt.format.width, sd_fmt.format.height,
> + sd_fmt.format.code, sd_fmt.format.field,
> + sd_fmt.format.colorspace);
> +
> + config_param.cfg_in_param.continuous = 0x1;
> + config_param.cfg_in_param.subsample = 0x0;
> + /* Fix to one pixel mode in default */
> + config_param.cfg_in_param.pixel_mode = 0x1;
> + /* Support normal pattern in default */
> + config_param.cfg_in_param.data_pattern = 0x0;
> +
> + config_param.cfg_in_param.crop.left = 0x0;
> + config_param.cfg_in_param.crop.top = 0x0;
Why hexadecimal numerals? Also, what's the meaning of these values? For
anything boolean, you could just use true and false as a substite of 0 and
1. For anything that has more values, please define the values using macros.
> +
> + config_param.cfg_in_param.raw_pixel_id =
> + get_sensor_pixel_id(sd_fmt.format.code);
> + config_param.cfg_in_param.img_fmt = get_sensor_fmt(sd_fmt.format.code);
> + config_param.cfg_in_param.crop.width = sd_fmt.format.width;
> + config_param.cfg_in_param.crop.height = sd_fmt.format.height;
Move the other crop settings from above to here.
> +
> + config_param.cfg_main_param.bypass = 1;
> + img_fmt = &cam_dev->vdev_nodes[MTK_CAM_P1_MAIN_STREAM_OUT].vdev_fmt;
> + if ((enabled_dma_ports & R_IMGO) == R_IMGO) {
No need for the == R_IMGO part.
> + config_param.cfg_main_param.bypass = 0;
> + config_param.cfg_main_param.pure_raw = isp_ctx->isp_raw_path;
> + config_param.cfg_main_param.pure_raw_pack = 1;
> + config_img_fmt(dev, &config_param.cfg_main_param.output,
> + img_fmt, &sd_fmt);
> + }
> +
> + config_param.cfg_resize_param.bypass = 1;
> + img_fmt = &cam_dev->vdev_nodes[MTK_CAM_P1_PACKED_BIN_OUT].vdev_fmt;
> + if ((enabled_dma_ports & R_RRZO) == R_RRZO) {
Ditto.
> + config_param.cfg_resize_param.bypass = 0;
> + config_img_fmt(dev, &config_param.cfg_resize_param.output,
> + img_fmt, &sd_fmt);
> + }
> +
> + /* Configure meta DMAs info. */
> + config_param.cfg_meta_param.enabled_meta_dmas = enabled_dma_ports;
Should image DMAs be masked out of this bitmap?
> +
> + isp_composer_hw_config(dev, &config_param);
> +
> + dev_dbg(dev, "%s done\n", __func__);
> +
> + return 0;
> +}
> +
> +void mtk_isp_enqueue(struct device *dev, unsigned int dma_port,
> + struct mtk_cam_dev_buffer *buffer)
> +{
> + struct mtk_isp_scp_p1_cmd frameparams;
> +
> + memset(&frameparams, 0, sizeof(frameparams));
> + frameparams.cmd_id = ISP_CMD_ENQUEUE_META;
> + frameparams.meta_frame.enabled_dma = dma_port;
> + frameparams.meta_frame.vb_index = buffer->vbb.vb2_buf.index;
> + frameparams.meta_frame.meta_addr.iova = buffer->daddr;
> + frameparams.meta_frame.meta_addr.scp_addr = buffer->scp_addr;
> +
> + isp_composer_enqueue(dev, &frameparams, SCP_ISP_CMD);
> +}
> +
> +void mtk_isp_req_flush_buffers(struct device *dev)
> +{
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
> + struct mtk_isp_queue_job *job, *j0;
> + struct mtk_cam_dev_buffer *buf, *b0;
> + struct isp_queue *p1_list = &p1_dev->isp_ctx.p1_enqueue_list;
> +
> + if (!atomic_read(&p1_list->queue_cnt))
> + return;
Do we need this explicit check? The code below wouldn't do anything if there
isn't anything in the list. IMHO we could even remove queue_cnt completely.
> +
> + spin_lock(&p1_list->lock);
> + list_for_each_entry_safe(job, j0, &p1_list->queue, list_entry) {
nit: s/j0/job_prev/
> + list_for_each_entry_safe(buf, b0, &job->list_buf, list) {
nit: s/b0/buf_pref/
Also, we should be able to replace this with iterating over the generic list
of request objects, rather than this internal list.
> + list_del(&buf->list);
> + if (buf->vbb.vb2_buf.state == VB2_BUF_STATE_ACTIVE)
It shouldn't be possible to happen. If you see this check failing, that
means a problem somewhere else in the driver.
> + vb2_buffer_done(&buf->vbb.vb2_buf,
> + VB2_BUF_STATE_ERROR);
> + }
> + list_del(&job->list_entry);
> + atomic_dec(&p1_list->queue_cnt);
> + kfree(job);
> + }
> + spin_unlock(&p1_list->lock);
> +}
> +
> +void mtk_isp_req_enqueue(struct device *dev, struct media_request *req)
> +{
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
Just pass p1_dev to this function instead of dev.
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + struct p1_frame_param frameparams;
> + struct mtk_isp_queue_job *framejob;
> + struct media_request_object *obj, *obj_safe;
> + struct vb2_buffer *vb;
> + struct mtk_cam_dev_buffer *buf;
> +
> + framejob = kzalloc(sizeof(*framejob), GFP_ATOMIC);
This allocation shouldn't be needed. The structure should be already a part
of the mtk_cam_dev_request struct that wraps media_request. Actually. I'd
even say that the contents of this struct should be just moved to that one
to avoid overabstracting.
> + memset(framejob, 0, sizeof(*framejob));
Putting the above comment aside, kzalloc() already zeroes the memory.
> + memset(&frameparams, 0, sizeof(frameparams));
> + INIT_LIST_HEAD(&framejob->list_buf);
> +
> + frameparams.frame_seq_no = isp_ctx->frame_seq_no++;
> + frameparams.sof_idx =
> + p1_dev->isp_devs[isp_ctx->isp_hw_module].sof_count;
How is this synchronized with the sof_count increment in irq_handle_sof()?
> + framejob->frame_seq_no = frameparams.frame_seq_no;
> +
> + list_for_each_entry_safe(obj, obj_safe, &req->objects, list) {
> + vb = container_of(obj, struct vb2_buffer, req_obj);
We should check that the object type before assuming it's a vb2_buffer by
calling vb2_request_object_is_buffer().
> + buf = container_of(vb, struct mtk_cam_dev_buffer, vbb.vb2_buf);
> + framejob->request_fd = buf->vbb.request_fd;
We shouldn't use request_fd as the key here. We already have req here, which
is the right key to use.
That said, I can see framejob->request_fd only used for printing a debugging
message in mtk_cam_dev_job_finish(). The request API core should already
print something for us once a request is completed, so perhaps that isn't
needed?
> + frameparams.dma_buffers[buf->node_id].iova = buf->daddr;
> + frameparams.dma_buffers[buf->node_id].scp_addr = buf->scp_addr;
> + list_add_tail(&buf->list, &framejob->list_buf);
Why do we need this private list? We could just call exactly the same
list_for_each() over the request objects.
> + }
> +
> + spin_lock(&isp_ctx->p1_enqueue_list.lock);
> + list_add_tail(&framejob->list_entry, &isp_ctx->p1_enqueue_list.queue);
We already have a list head in mtk_cam_dev_request.
> + atomic_inc(&isp_ctx->p1_enqueue_list.queue_cnt);
> + spin_unlock(&isp_ctx->p1_enqueue_list.lock);
> +
> + isp_composer_enqueue(dev, &frameparams, SCP_ISP_FRAME);
> + dev_dbg(dev, "request fd:%d frame_seq_no:%d is queued cnt:%d\n",
> + framejob->request_fd,
> + frameparams.frame_seq_no,
> + atomic_read(&isp_ctx->p1_enqueue_list.queue_cnt));
> +}
> +
> +static int enable_sys_clock(struct isp_p1_device *p1_dev)
> +{
> + struct device *dev = &p1_dev->pdev->dev;
> + int ret;
> +
> + dev_info(dev, "- %s\n", __func__);
dev_dbg()
> +
> + ret = clk_bulk_prepare_enable(p1_dev->isp_ctx.num_clks,
> + p1_dev->isp_ctx.clk_list);
> + if (ret)
> + goto clk_err;
> +
> + return 0;
> +
> +clk_err:
> + dev_err(dev, "cannot pre-en isp_cam clock:%d\n", ret);
> + clk_bulk_disable_unprepare(p1_dev->isp_ctx.num_clks,
> + p1_dev->isp_ctx.clk_list);
clk_bulk_prepare_enable() returns without any clocks enabled if it fails, so
this would disable the clocks second time.
> + return ret;
> +}
> +
> +static void disable_sys_clock(struct isp_p1_device *p1_dev)
> +{
> + dev_info(&p1_dev->pdev->dev, "- %s\n", __func__);
dev_dbg()
> + clk_bulk_disable_unprepare(p1_dev->isp_ctx.num_clks,
> + p1_dev->isp_ctx.clk_list);
> +}
There is no point in having wrapper functions to just call one function
inside. Please just call clk_bulk_*() directly.
> +
> +static int mtk_isp_suspend(struct device *dev)
> +{
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
> + int module = p1_dev->isp_ctx.isp_hw_module;
> + struct isp_device *isp_dev = &p1_dev->isp_devs[module];
> + unsigned int reg_val;
> +
> + dev_dbg(dev, "- %s\n", __func__);
> +
We need to check if the device isn't already runtime suspended. If it is, we
don't have to do anything here and can just return.
We also need to ensure that no new requests are queued to the hardware at
this point. This could be done by replacing any of the kthreads with
workqueues and making all of the workqueues freezable.
> + isp_dev = &p1_dev->isp_devs[module];
> + reg_val = readl(isp_dev->regs + REG_TG_VF_CON);
> + if (reg_val & VFDATA_EN_BIT) {
> + dev_dbg(dev, "Cam:%d suspend, disable VF\n", module);
> + /* Disable view finder */
> + writel((reg_val & (~VFDATA_EN_BIT)),
> + isp_dev->regs + REG_TG_VF_CON);
> + /*
> + * After VF enable, the TG frame count will be reset to 0;
> + */
> + reg_val = readl(isp_dev->regs + REG_TG_SEN_MODE);
> + writel((reg_val & (~CMOS_EN_BIT)),
> + isp_dev->regs + + REG_TG_SEN_MODE);
> + }
Are you sure this is the right handling? We need to make sure the hardware
finishes processing the current frame and stops.
> +
> + disable_sys_clock(p1_dev);
> +
> + return 0;
> +}
> +
> +static int mtk_isp_resume(struct device *dev)
> +{
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
> + int module = p1_dev->isp_ctx.isp_hw_module;
> + struct isp_device *isp_dev = &p1_dev->isp_devs[module];
> + unsigned int reg_val;
> +
> + dev_dbg(dev, "- %s\n", __func__);
> +
We need to check runtime PM status here as well, because if the device was
suspended, there is nothing to do here.
> + enable_sys_clock(p1_dev);
> +
> + /* V4L2 stream-on phase & restore HW stream-on status */
> + if (p1_dev->cam_dev.streaming) {
> + dev_dbg(dev, "Cam:%d resume,enable VF\n", module);
> + /* Enable CMOS */
> + reg_val = readl(isp_dev->regs + REG_TG_SEN_MODE);
> + writel((reg_val | CMOS_EN_BIT),
> + isp_dev->regs + REG_TG_SEN_MODE);
> + /* Enable VF */
> + reg_val = readl(isp_dev->regs + REG_TG_VF_CON);
> + writel((reg_val | VFDATA_EN_BIT),
> + isp_dev->regs + REG_TG_VF_CON);
> + }
Does the hardware keep all the state, e.g. queued buffers, during suspend?
Would the code above continue all the capture from the next buffer, as
queued by the userspace before the suspend?
> +
> + return 0;
> +}
> +
> +static int mtk_isp_probe(struct platform_device *pdev)
> +{
> + struct isp_p1_device *p1_dev;
> + struct mtk_isp_p1_ctx *isp_ctx;
> + struct isp_device *isp_dev;
> + struct device *dev = &pdev->dev;
> + struct resource *res;
> + int irq;
> + int ret;
> + unsigned int i;
> +
> + p1_dev = devm_kzalloc(dev, sizeof(*p1_dev), GFP_KERNEL);
> + if (!p1_dev)
> + return -ENOMEM;
> +
> + dev_set_drvdata(dev, p1_dev);
> + isp_ctx = &p1_dev->isp_ctx;
> + p1_dev->pdev = pdev;
Perhaps you want to store &pdev->dev instead of pdev? I'm not sure a
reference to platform_device is very useful later in the code.
> +
> + for (i = ISP_CAMSYS_CONFIG_IDX; i < ISP_DEV_NODE_NUM; i++) {
I think we want to start from 0 here?
> + isp_dev = &p1_dev->isp_devs[i];
> + isp_dev->isp_hw_module = i;
> + isp_dev->dev = dev;
p1_dev already includes a pointer to this.
> + res = platform_get_resource(pdev, IORESOURCE_MEM, i);
> + isp_dev->regs = devm_ioremap_resource(dev, res);
> +
> + dev_dbg(dev, "cam%u, map_addr=0x%lx\n",
> + i, (unsigned long)isp_dev->regs);
> +
> + if (!isp_dev->regs)
devm_ioremap_resource() returns ERR_PTR() not NULL on error.
> + return PTR_ERR(isp_dev->regs);
> +
> + /* Support IRQ from ISP_CAM_A_IDX */
> + if (i >= ISP_CAM_A_IDX) {
> + /* Reg & interrupts index is shifted with 1 */
The reader can already see that it's shifted from the code below. The
comment should say _why_ it is shifted.
> + irq = platform_get_irq(pdev, i - 1);
The bindings have 3 IRQs, but we only seem to request 2 here. Is that
expected?
> + if (irq) {
Please invert this if, so that it bails out on error. Also, this should
check for negative errors codes, not 0.
> + ret = devm_request_irq(dev, irq,
> + isp_irq_cam,
> + IRQF_SHARED,
> + dev_driver_string(dev),
Use dev_name().
> + (void *)isp_dev);
No need to cast to void *.
> + if (ret) {
> + dev_err(dev,
> + "req_irq fail, dev:%s irq=%d\n",
> + dev->of_node->name,
> + irq);
> + return ret;
> + }
> + dev_dbg(dev, "Registered irq=%d, ISR:%s\n",
> + irq, dev_driver_string(dev));
> + }
> + }
> + spin_lock_init(&isp_dev->spinlock_irq);
> + }
We might want to move out the body of this loop to a separate function to
keep this function shorter.
> +
> + p1_dev->isp_ctx.num_clks = ARRAY_SIZE(mtk_isp_clks);
> + p1_dev->isp_ctx.clk_list =
nit: "list" is the term commonly used for the list data structure. There is
also a convention to call the length of array XXX num_XXX, so how about
clks and num_clks?
> + devm_kcalloc(dev,
> + p1_dev->isp_ctx.num_clks,
> + sizeof(*p1_dev->isp_ctx.clk_list),
> + GFP_KERNEL);
> + if (!p1_dev->isp_ctx.clk_list)
> + return -ENOMEM;
> +
> + for (i = 0; i < p1_dev->isp_ctx.num_clks; ++i)
> + p1_dev->isp_ctx.clk_list->id = mtk_isp_clks[i];
Shouldn't this be clk_list[i].id?
> +
> + ret = devm_clk_bulk_get(dev,
> + p1_dev->isp_ctx.num_clks,
> + p1_dev->isp_ctx.clk_list);
> + if (ret) {
> + dev_err(dev, "cannot get isp cam clock:%d\n", ret);
> + return ret;
> + }
> +
> + /* Initialize reserved DMA memory */
> + ret = mtk_cam_reserved_memory_init(p1_dev);
> + if (ret) {
> + dev_err(dev, "failed to configure DMA memory:%d\n", ret);
> + goto err_init;
> + }
> +
> + /* Initialize the v4l2 common part */
> + ret = mtk_cam_dev_init(pdev, &p1_dev->cam_dev);
> + if (ret)
> + goto err_init;
> +
> + spin_lock_init(&isp_ctx->p1_enqueue_list.lock);
> + pm_runtime_enable(dev);
> +
> + return 0;
> +
> +err_init:
> + if (p1_dev->cam_dev.smem_dev)
> + device_unregister(p1_dev->cam_dev.smem_dev);
> +
> + return ret;
> +}
> +
> +static int mtk_isp_remove(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct isp_p1_device *p1_dev = dev_get_drvdata(dev);
> +
> + pm_runtime_disable(dev);
> + mtk_cam_dev_release(pdev, &p1_dev->cam_dev);
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops mtk_isp_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(mtk_isp_suspend, mtk_isp_resume)
> + SET_RUNTIME_PM_OPS(mtk_isp_suspend, mtk_isp_resume, NULL)
For V4L2 drivers system and runtime PM ops would normally be completely
different. Runtime PM ops would be called when the hardware is idle already
or is about to become active. System PM ops would be called at system power
state change and the hardware might be both idle or active. Please also see
my comments to mtk_isp_suspend() and mtk_isp_resume() above.
> +};
> +
> +static struct platform_driver mtk_isp_driver = {
> + .probe = mtk_isp_probe,
> + .remove = mtk_isp_remove,
> + .driver = {
> + .name = "mtk-cam",
Shouldn't this be something like mtk-cam-p1? Please make sure this
reasonably consistent with other drivers.
> + .of_match_table = of_match_ptr(mtk_isp_of_ids),
> + .pm = &mtk_isp_pm_ops,
> + }
> +};
> +
> +module_platform_driver(mtk_isp_driver);
> +
> +MODULE_DESCRIPTION("Camera ISP driver");
Mediatek Camera P1 ISP driver? Please make sure this is reasonably
consistent with other drivers (SenInf, DIP, FD).
> +MODULE_LICENSE("GPL");
GPL v2
diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam.h b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam.h
new file mode 100644
index 000000000000..6af3f569664c
> --- /dev/null
> +++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam.h
@@ -0,0 +1,243 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018 MediaTek Inc.
> + */
> +
> +#ifndef __CAMERA_ISP_H
> +#define __CAMERA_ISP_H
> +
> +#include <linux/cdev.h>
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/ioctl.h>
> +#include <linux/irqreturn.h>
> +#include <linux/miscdevice.h>
> +#include <linux/pm_qos.h>
> +#include <linux/scatterlist.h>
> +
> +#include "mtk_cam-scp.h"
> +#include "mtk_cam-v4l2-util.h"
> +
> +#define CAM_A_MAX_WIDTH 3328
> +#define CAM_A_MAX_HEIGHT 2496
> +#define CAM_B_MAX_WIDTH 5376
> +#define CAM_B_MAX_HEIGHT 4032
> +
> +#define CAM_MIN_WIDTH 80
> +#define CAM_MIN_HEIGHT 60
> +
> +#define IMG_MAX_WIDTH CAM_B_MAX_WIDTH
> +#define IMG_MAX_HEIGHT CAM_B_MAX_HEIGHT
> +#define IMG_MIN_WIDTH CAM_MIN_WIDTH
> +#define IMG_MIN_HEIGHT CAM_MIN_HEIGHT
> +
> +#define RRZ_MAX_WIDTH CAM_B_MAX_WIDTH
> +#define RRZ_MAX_HEIGHT CAM_B_MAX_HEIGHT
> +#define RRZ_MIN_WIDTH CAM_MIN_WIDTH
> +#define RRZ_MIN_HEIGHT CAM_MIN_HEIGHT
> +
> +#define R_IMGO BIT(0)
> +#define R_RRZO BIT(1)
> +#define R_AAO BIT(3)
> +#define R_AFO BIT(4)
> +#define R_LCSO BIT(5)
> +#define R_PDO BIT(6)
> +#define R_LMVO BIT(7)
> +#define R_FLKO BIT(8)
> +#define R_RSSO BIT(9)
> +#define R_PSO BIT(10)
> +
> +#define CQ_BUFFER_COUNT 3
> +#define IRQ_DATA_BUF_SIZE 4
> +#define CQ_ADDRESS_OFFSET 0x640
> +
> +#define ISP_COMPOSING_MAX_NUM 4
> +#define ISP_FRAME_COMPOSING_MAX_NUM 3
> +
> +#define IRQ_STAT_STR "cam%c, SOF_%d irq(0x%x), " \
> + "dma(0x%x), frame_num(%d)/cq_num(%d), " \
> + "fbc1(0x%x), fbc2(0x%x)\n"
> +
> +/*
> + * In order with the sequence of device nodes defined in dtsi rule,
> + * one hardware module should be mapping to one node.
> + */
> +enum isp_dev_node_enum {
> + ISP_CAMSYS_CONFIG_IDX = 0,
> + ISP_CAM_UNI_IDX,
> + ISP_CAM_A_IDX,
> + ISP_CAM_B_IDX,
> + ISP_DEV_NODE_NUM
> +};
> +
> +/* Image RAW path for ISP P1 module. */
> +enum isp_raw_path_enum {
> + ISP_PROCESS_RAW_PATH = 0,
> + ISP_PURE_RAW_PATH
> +};
> +
> +/* State for struct mtk_isp_p1_ctx: composer_state */
> +enum {
> + SCP_ON = 0,
> + SCP_OFF
> +};
Hmm, looks like bool.
> +
> +enum {
> + IMG_FMT_UNKNOWN = 0x0000,
> + IMG_FMT_RAW_START = 0x2200,
> + IMG_FMT_BAYER8 = IMG_FMT_RAW_START,
> + IMG_FMT_BAYER10,
> + IMG_FMT_BAYER12,
> + IMG_FMT_BAYER14,
> + IMG_FMT_FG_BAYER8,
> + IMG_FMT_FG_BAYER10,
> + IMG_FMT_FG_BAYER12,
> + IMG_FMT_FG_BAYER14,
> +};
> +
> +enum {
> + RAW_PXL_ID_B = 0,
> + RAW_PXL_ID_GB,
> + RAW_PXL_ID_GR,
> + RAW_PXL_ID_R
> +};
Please use macros with explicitly assigned values for hardware/firmware ABI
definitions.
> +
> +struct isp_queue {
> + struct list_head queue;
> + atomic_t queue_cnt;
> + spinlock_t lock; /* queue attributes protection */
> +};
> +
> +struct isp_thread {
> + struct task_struct *thread;
> + wait_queue_head_t wq;
> +};
> +
> +struct mtk_isp_queue_work {
> + union {
> + struct mtk_isp_scp_p1_cmd cmd;
> + struct p1_frame_param frameparams;
> + };
> + struct list_head list_entry;
> + enum mtk_isp_scp_type type;
> +};
> +
> +struct mtk_cam_dev_stat_event_data {
> + __u32 frame_seq_no;
> + __u32 meta0_vb2_index;
> + __u32 meta1_vb2_index;
> + __u32 irq_status_mask;
> + __u32 dma_status_mask;
> +};
> +
> +struct mtk_isp_queue_job {
> + struct list_head list_entry;
> + struct list_head list_buf;
> + unsigned int request_fd;
> + unsigned int frame_seq_no;
> +};
Please document the structs above using kerneldoc.
> +
> +/*
> + * struct isp_device - the ISP device information
> + *
> + * @dev: Pointer to struct device
> + * @regs: Camera ISP base register address
> + * @spinlock_irq: Used to protect register read/write data
> + * @current_frame: Current frame sequence number, set when SOF
> + * @meta0_vb2_index: Meta0 vb2 buffer index, set when SOF
> + * @meta1_vb2_index: Meta1 vb2 buffer index, set when SOF
> + * @sof_count: The accumulated SOF counter
> + * @isp_hw_module: Identity camera A or B
> + *
> + */
> +struct isp_device {
mtk_isp_device?
> + struct device *dev;
> + void __iomem *regs;
> + spinlock_t spinlock_irq; /* ISP reg setting integrity */
> + unsigned int current_frame;
> + unsigned int meta0_vb2_index;
> + unsigned int meta1_vb2_index;
> + u8 sof_count;
> + u8 isp_hw_module;
> +};
> +
> +/*
> + * struct mtk_isp_p1_ctx - the ISP device information
> + *
> + * @composer_txlist: Queue for SCP TX data including SCP_ISP_CMD & SCP_ISP_FRAME
> + * @composer_tx_thread: TX Thread for SCP data tranmission
> + * @cmd_queued: The number of SCP_ISP_CMD commands will be sent
> + * @ipi_occupied: The total number of SCP TX data has beent sent
> + * @scp_state: The state of SCP control
> + * @composing_frame: The total number of SCP_ISP_FRAME has beent sent
> + * @composed_frame_id: The ack. frame sequence by SCP
> + * @composer_deinit_thread: The de-initialized thread
> + * @p1_enqueue_list: Queue for ISP frame buffers
> + * @isp_deque_thread: Thread for handling ISP frame buffers dequeue
> + * @irq_event_datas: Ring buffer for struct mtk_cam_dev_stat_event_data data
> + * @irq_data_start: Start index of irq_event_datas ring buffer
> + * @irq_data_end: End index of irq_event_datas ring buffer
> + * @irq_dequeue_lock: Lock to protect irq_event_datas ring buffer
> + * @scp_mem_pa: DMA address for SCP device
> + * @scp_mem_iova: DMA address for ISP HW DMA devices
> + * @frame_seq_no: Sequence number for ISP frame buffer
> + * @isp_hw_module: Active camera HW module
> + * @num_clks: The number of driver's clock
> + * @clk_list: The list of clock data
> + * @lock: Lock to protect context operations
> + *
> + */
> +struct mtk_isp_p1_ctx {
> + struct isp_queue composer_txlist;
> + struct isp_thread composer_tx_thread;
> + atomic_t cmd_queued;
> + atomic_t ipi_occupied;
> + atomic_t scp_state;
> + atomic_t composing_frame;
> + atomic_t composed_frame_id;
> + struct isp_thread composer_deinit_thread;
> + struct isp_queue p1_enqueue_list;
> + struct isp_thread isp_deque_thread;
> + struct mtk_cam_dev_stat_event_data irq_event_datas[IRQ_DATA_BUF_SIZE];
> + atomic_t irq_data_start;
> + atomic_t irq_data_end;
> + spinlock_t irq_dequeue_lock; /* ISP frame dequeuq protection */
Already documented in kerneldoc.
> + dma_addr_t scp_mem_pa;
> + dma_addr_t scp_mem_iova;
> + int frame_seq_no;
> + unsigned int isp_hw_module;
> + unsigned int isp_raw_path;
Not documented above.
> + unsigned int num_clks;
> + struct clk_bulk_data *clk_list;
> + struct mutex lock; /* Protect context operations */
Already documented in kerneldoc.
> +};
> +
> +struct isp_p1_device {
> + struct platform_device *pdev;
> + struct platform_device *scp_pdev;
> + struct rproc *rproc_handle;
> + struct mtk_isp_p1_ctx isp_ctx;
> + struct mtk_cam_dev cam_dev;
> + struct isp_device isp_devs[ISP_DEV_NODE_NUM];
> +};
Please document in a kerneldoc comment.
> +
> +static inline struct isp_p1_device *
> +p1_ctx_to_dev(const struct mtk_isp_p1_ctx *__p1_ctx)
> +{
> + return container_of(__p1_ctx, struct isp_p1_device, isp_ctx);
> +}
> +
> +static inline struct isp_p1_device *get_p1_device(struct device *dev)
> +{
> + return ((struct isp_p1_device *)dev_get_drvdata(dev));
No need to cast. And, I don't think we need a function for this, just call
dev_get_drvdata() directly.
Best regards,
Tomasz
^ permalink raw reply
* Re: [RFC,v3 8/9] media: platform: Add Mediatek ISP P1 SCP communication
From: Tomasz Figa @ 2019-07-10 9:58 UTC (permalink / raw)
To: Jungo Lin
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
sean.cheng-NuS5LvNUpcJWk0Htik3J/w,
frederic.chen-NuS5LvNUpcJWk0Htik3J/w,
rynn.wu-NuS5LvNUpcJWk0Htik3J/w,
srv_heupstream-NuS5LvNUpcJWk0Htik3J/w,
robh-DgEjT+Ai2ygdnm+yROfE0A, ryan.yu-NuS5LvNUpcJWk0Htik3J/w,
frankie.chiu-NuS5LvNUpcJWk0Htik3J/w,
hverkuil-qWit8jRvyhVmR6Xm/wNWPw,
ddavenport-F7+t8E8rja9g9hUCZPvPmw,
sj.huang-NuS5LvNUpcJWk0Htik3J/w,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
laurent.pinchart-ryLnwIuWjnjg/C1BVhZhaw,
matthias.bgg-Re5JQEeQqe8AvxtiuMwx3w,
mchehab-DgEjT+Ai2ygdnm+yROfE0A,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-media-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20190611035344.29814-9-jungo.lin-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
Hi Jungo,
On Tue, Jun 11, 2019 at 11:53:43AM +0800, Jungo Lin wrote:
> This patch adds communication with the co-processor on the SoC
> through the SCP driver. It supports bi-directional commands
> to exchange data and perform command flow control function.
>
> Signed-off-by: Jungo Lin <jungo.lin-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> ---
> This patch depends on "Add support for mt8183 SCP"[1].
>
> [1] https://patchwork.kernel.org/cover/10972143/
> ---
> .../platform/mtk-isp/isp_50/cam/Makefile | 1 +
> .../platform/mtk-isp/isp_50/cam/mtk_cam-scp.c | 371 ++++++++++++++++++
> .../platform/mtk-isp/isp_50/cam/mtk_cam-scp.h | 207 ++++++++++
> 3 files changed, 579 insertions(+)
> create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-scp.c
> create mode 100644 drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-scp.h
>
Thanks for the patch! Please see my comments inline.
[snip]
> diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-scp.c b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-scp.c
> new file mode 100644
> index 000000000000..04519d0b942f
> --- /dev/null
> +++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-scp.c
> @@ -0,0 +1,371 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Copyright (c) 2018 MediaTek Inc.
> +
> +#include <linux/atomic.h>
> +#include <linux/kthread.h>
> +#include <linux/platform_data/mtk_scp.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/remoteproc.h>
> +#include <linux/sched.h>
> +#include <linux/spinlock.h>
> +#include <linux/types.h>
> +#include <linux/vmalloc.h>
> +
> +#include "mtk_cam.h"
> +
> +static void isp_composer_deinit(struct mtk_isp_p1_ctx *isp_ctx)
> +{
> + struct mtk_isp_queue_work *ipi_job, *tmp_ipi_job;
> + struct isp_p1_device *p1_dev = p1_ctx_to_dev(isp_ctx);
> +
> + atomic_set(&isp_ctx->cmd_queued, 0);
> + atomic_set(&isp_ctx->composer_txlist.queue_cnt, 0);
> + atomic_set(&isp_ctx->composing_frame, 0);
> + atomic_set(&isp_ctx->ipi_occupied, 0);
Is there any point to set them if we are deinitalizing? Moreover,
isp_composer_init() would set them once we start again.
> +
> + spin_lock(&isp_ctx->composer_txlist.lock);
> + list_for_each_entry_safe(ipi_job, tmp_ipi_job,
> + &isp_ctx->composer_txlist.queue,
> + list_entry) {
> + list_del(&ipi_job->list_entry);
> + kfree(ipi_job);
> + }
> + atomic_set(&isp_ctx->composer_txlist.queue_cnt, 0);
> + spin_unlock(&isp_ctx->composer_txlist.lock);
> +
> + mutex_lock(&isp_ctx->lock);
> + if (isp_ctx->composer_tx_thread.thread) {
> + kthread_stop(isp_ctx->composer_tx_thread.thread);
Shouldn't the thread be stopped at this point already? If not, wouldn't the
atomic_set() at the beginning of this function confuse it?
In any case, this should be greatly simplified after we move to a workqueue,
with one work per one task to do, as per other comments.
> + wake_up_interruptible(&isp_ctx->composer_tx_thread.wq);
> + isp_ctx->composer_tx_thread.thread = NULL;
> + }
> +
> + if (isp_ctx->composer_deinit_thread.thread) {
> + wake_up(&isp_ctx->composer_deinit_thread.wq);
> + isp_ctx->composer_deinit_thread.thread = NULL;
> + }
> + mutex_unlock(&isp_ctx->lock);
> +
> + pm_runtime_put_sync(&p1_dev->pdev->dev);
No need to use the sync variant.
> +}
> +
> +/*
> + * Two kinds of flow control in isp_composer_tx_work.
> + *
> + * Case 1: IPI commands flow control. The maximum number of command queues is 3.
> + * There are two types of IPI commands (SCP_ISP_CMD/SCP_ISP_FRAME) in P1 driver.
> + * It is controlled by ipi_occupied.
ISP_COMPOSING_MAX_NUM is defined to 4, not 3. Is that expected?
> + * The priority of SCP_ISP_CMD is higher than SCP_ISP_FRAME.
What does it mean and why is it so?
> + *
> + * Case 2: Frame buffers flow control. The maximum number of frame buffers is 3.
> + * It is controlled by composing_frame.
> + * Frame buffer is sent by SCP_ISP_FRAME command.
Case 1 already mentions SCP_ISP_FRAME. What's the difference between that
and case 2?
> + */
> +static int isp_composer_tx_work(void *data)
> +{
> + struct mtk_isp_p1_ctx *isp_ctx = (struct mtk_isp_p1_ctx *)data;
> + struct isp_p1_device *p1_dev = p1_ctx_to_dev(isp_ctx);
> + struct device *dev = &p1_dev->pdev->dev;
> + struct mtk_isp_queue_work *isp_composer_work, *tmp_ipi_job;
> + struct isp_queue *composer_txlist = &isp_ctx->composer_txlist;
> + int ret;
> +
> + while (1) {
> + ret = wait_event_interruptible
> + (isp_ctx->composer_tx_thread.wq,
> + (atomic_read(&composer_txlist->queue_cnt) > 0 &&
> + atomic_read(&isp_ctx->ipi_occupied)
> + < ISP_COMPOSING_MAX_NUM &&
> + atomic_read(&isp_ctx->composing_frame)
> + < ISP_FRAME_COMPOSING_MAX_NUM) ||
> + (atomic_read(&isp_ctx->cmd_queued) > 0 &&
> + atomic_read(&isp_ctx->ipi_occupied)
> + < ISP_COMPOSING_MAX_NUM) ||
> + kthread_should_stop());
> +
> + if (kthread_should_stop())
> + break;
> +
> + spin_lock(&composer_txlist->lock);
> + if (atomic_read(&isp_ctx->cmd_queued) > 0) {
> + list_for_each_entry_safe(isp_composer_work, tmp_ipi_job,
> + &composer_txlist->queue,
> + list_entry) {
> + if (isp_composer_work->type == SCP_ISP_CMD) {
> + dev_dbg(dev, "Found a cmd\n");
> + break;
> + }
> + }
> + } else {
> + if (atomic_read(&isp_ctx->composing_frame) >=
> + ISP_FRAME_COMPOSING_MAX_NUM) {
> + spin_unlock(&composer_txlist->lock);
> + continue;
> + }
> + isp_composer_work =
> + list_first_entry_or_null
> + (&composer_txlist->queue,
> + struct mtk_isp_queue_work,
> + list_entry);
> + }
I don't understand why this special handling of CMD vs FRAME is here, so I
might be missing something, but would we really lose anything if we just
simply removed it and queued everything in order?
Moreover, in V4L2, buffer queue and control operations are serialized wrt
each other, so we probably wouldn't even have a chance to hit a case when we
need to prioritize a CMD IPI over a FRAME IPI.
> +
> + list_del(&isp_composer_work->list_entry);
> + atomic_dec(&composer_txlist->queue_cnt);
> + spin_unlock(&composer_txlist->lock);
> +
> + if (isp_composer_work->type == SCP_ISP_CMD) {
> + scp_ipi_send
> + (p1_dev->scp_pdev,
> + SCP_IPI_ISP_CMD,
> + &isp_composer_work->cmd,
> + sizeof(isp_composer_work->cmd),
> + 0);
> + atomic_dec(&isp_ctx->cmd_queued);
> + atomic_inc(&isp_ctx->ipi_occupied);
> + dev_dbg(dev,
> + "%s cmd id %d sent, %d ipi buf occupied",
> + __func__,
> + isp_composer_work->cmd.cmd_id,
> + atomic_read(&isp_ctx->ipi_occupied));
> + } else if (isp_composer_work->type == SCP_ISP_FRAME) {
> + scp_ipi_send
> + (p1_dev->scp_pdev,
> + SCP_IPI_ISP_FRAME,
> + &isp_composer_work->frameparams,
> + sizeof(isp_composer_work->frameparams),
> + 0);
> + atomic_inc(&isp_ctx->ipi_occupied);
> + atomic_inc(&isp_ctx->composing_frame);
Why do we need composing frame here, if ipi_occupied already limits us to 3?
> + dev_dbg(dev,
> + "%s frame %d sent, %d ipi, %d CQ bufs occupied",
> + __func__,
> + isp_composer_work->frameparams.frame_seq_no,
> + atomic_read(&isp_ctx->ipi_occupied),
> + atomic_read(&isp_ctx->composing_frame));
> + } else {
> + dev_err(dev,
> + "ignore IPI type: %d!\n",
> + isp_composer_work->type);
> + }
> + kfree(isp_composer_work);
> + }
> + return ret;
> +}
The function above is way too complicated than it should be. I'd suggest a
model similar to what we ended up in the DIP driver:
> - a freezable workqueue created for ISP composing works,
> - each ISP composing work entry would have a struct work_struct embedded,
> - isp_composer_enqueue() would enqueue the work_struct to the workqueue
> above,
> - the workqueue would keep a queue of works itself, so driver's own list
> wouldn't be needed anymore,
> - similarly, each execution of the work func would operate on its own ISP
> composing work, so things like checking for list emptiness, waiting for
> work to be queued, etc. wouldn't be needed,
> - freezability of the workqueue would ensure nice synchonization with
> system suspend/resume (although one would still need to wait for the
> hardware/firmware to complete).
WDYT?
> +
> +static int isp_composer_deinit_work(void *data)
> +{
> + struct mtk_isp_p1_ctx *isp_ctx = (struct mtk_isp_p1_ctx *)data;
> + struct isp_p1_device *p1_dev = p1_ctx_to_dev(data);
> + struct device *dev = &p1_dev->pdev->dev;
> +
> + wait_event_interruptible(isp_ctx->composer_deinit_thread.wq,
> + atomic_read(&isp_ctx->scp_state) == SCP_OFF ||
> + kthread_should_stop());
> +
> + dev_dbg(dev, "%s run deinit", __func__);
> + isp_composer_deinit(isp_ctx);
> +
> + return 0;
> +}
> +
> +static void isp_composer_handler(void *data, unsigned int len, void *priv)
> +{
> + struct mtk_isp_p1_ctx *isp_ctx = (struct mtk_isp_p1_ctx *)priv;
> + struct isp_p1_device *p1_dev = p1_ctx_to_dev(isp_ctx);
> + struct device *dev = &p1_dev->pdev->dev;
> + struct mtk_isp_scp_p1_cmd *ipi_msg;
> +
> + ipi_msg = (struct mtk_isp_scp_p1_cmd *)data;
Should we check that len == sizeof(*ipi_msg)? (Or at least >=, if data could
contain some extra bytes at the end.)
> +
> + if (ipi_msg->cmd_id != ISP_CMD_ACK)
> + return;
> +
> + if (ipi_msg->ack_info.cmd_id == ISP_CMD_FRAME_ACK) {
> + dev_dbg(dev, "ack frame_num:%d",
> + ipi_msg->ack_info.frame_seq_no);
> + atomic_set(&isp_ctx->composed_frame_id,
> + ipi_msg->ack_info.frame_seq_no);
I suppose we are expecting here that ipi_msg->ack_info.frame_seq_no would be
just isp_ctx->composed_frame_id + 1, right? If not, we probably dropped some
frames and we should handle that somehow.
> + } else if (ipi_msg->ack_info.cmd_id == ISP_CMD_DEINIT) {
> + dev_dbg(dev, "ISP_CMD_DEINIT is acked");
> + atomic_set(&isp_ctx->scp_state, SCP_OFF);
> + wake_up_interruptible(&isp_ctx->composer_deinit_thread.wq);
> + }
> +
> + atomic_dec_return(&isp_ctx->ipi_occupied);
> + wake_up_interruptible(&isp_ctx->composer_tx_thread.wq);
> +}
> +
> +int isp_composer_init(struct device *dev)
> +{
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + int ret;
> +
> + ret = scp_ipi_register(p1_dev->scp_pdev,
> + SCP_IPI_ISP_CMD,
> + isp_composer_handler,
> + isp_ctx);
> + if (ret)
> + return ret;
> +
> + atomic_set(&isp_ctx->cmd_queued, 0);
> + atomic_set(&isp_ctx->composer_txlist.queue_cnt, 0);
> + atomic_set(&isp_ctx->composing_frame, 0);
> + atomic_set(&isp_ctx->ipi_occupied, 0);
> + atomic_set(&isp_ctx->scp_state, SCP_ON);
> +
> + mutex_lock(&isp_ctx->lock);
> + if (!isp_ctx->composer_tx_thread.thread) {
> + init_waitqueue_head(&isp_ctx->composer_tx_thread.wq);
> + INIT_LIST_HEAD(&isp_ctx->composer_txlist.queue);
> + spin_lock_init(&isp_ctx->composer_txlist.lock);
> + isp_ctx->composer_tx_thread.thread =
> + kthread_run(isp_composer_tx_work, isp_ctx,
> + "isp_composer_tx");
> + if (IS_ERR(isp_ctx->composer_tx_thread.thread)) {
> + dev_err(dev, "unable to start kthread\n");
> + isp_ctx->composer_tx_thread.thread = NULL;
> + goto nomem;
Why nomem?
> + }
> + } else {
> + dev_warn(dev, "old tx thread is existed\n");
This shouldn't be possible to happen.
> + }
> +
> + if (!isp_ctx->composer_deinit_thread.thread) {
> + init_waitqueue_head(&isp_ctx->composer_deinit_thread.wq);
> + isp_ctx->composer_deinit_thread.thread =
> + kthread_run(isp_composer_deinit_work, isp_ctx,
> + "isp_composer_deinit_work");
Why do we need to deinit from another kthread?
> + if (IS_ERR(isp_ctx->composer_deinit_thread.thread)) {
> + dev_err(dev, "unable to start kthread\n");
> + isp_ctx->composer_deinit_thread.thread = NULL;
> + goto nomem;
> + }
> + } else {
> + dev_warn(dev, "old rx thread is existed\n");
rx? The code above seems to refer to deinit.
> + }
> + mutex_unlock(&isp_ctx->lock);
> +
> + return 0;
> +
> +nomem:
> + mutex_unlock(&isp_ctx->lock);
> +
> + return -ENOMEM;
We should return the original error code here.
> +}
> +
> +void isp_composer_enqueue(struct device *dev,
> + void *data,
> + enum mtk_isp_scp_type type)
> +{
> + struct mtk_isp_queue_work *isp_composer_work;
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
Just pass p1_dev to this function instead of dev.
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> +
> + isp_composer_work = kzalloc(sizeof(*isp_composer_work), GFP_KERNEL);
For most of the cases, it should be possible to preallocate this, e.g.
> - for FRAME, this could be inside the request struct,
> - for buffer queue it could be inside the buffer struct.
I'd suggest making the caller responsible for allocating if needed.
> + isp_composer_work->type = type;
> +
> + switch (type) {
> + case SCP_ISP_CMD:
> + memcpy(&isp_composer_work->cmd, data,
> + sizeof(isp_composer_work->cmd));
> + dev_dbg(dev, "Enq ipi cmd id:%d\n",
> + isp_composer_work->cmd.cmd_id);
> +
> + spin_lock(&isp_ctx->composer_txlist.lock);
> + list_add_tail(&isp_composer_work->list_entry,
> + &isp_ctx->composer_txlist.queue);
> + atomic_inc(&isp_ctx->composer_txlist.queue_cnt);
> + spin_unlock(&isp_ctx->composer_txlist.lock);
> +
> + atomic_inc(&isp_ctx->cmd_queued);
> + wake_up_interruptible(&isp_ctx->composer_tx_thread.wq);
> + break;
> + case SCP_ISP_FRAME:
> + memcpy(&isp_composer_work->frameparams, data,
> + sizeof(isp_composer_work->frameparams));
> + dev_dbg(dev, "Enq ipi frame_num:%d\n",
> + isp_composer_work->frameparams.frame_seq_no);
> +
> + spin_lock(&isp_ctx->composer_txlist.lock);
> + list_add_tail(&isp_composer_work->list_entry,
> + &isp_ctx->composer_txlist.queue);
> + atomic_inc(&isp_ctx->composer_txlist.queue_cnt);
> + spin_unlock(&isp_ctx->composer_txlist.lock);
> +
> + wake_up_interruptible(&isp_ctx->composer_tx_thread.wq);
The code in both cases is almost exactly the same. The only difference is
the memcpy destination and size and whether isp_ctx->cmd_queued is
incremented or not.
The memcpy will go away if my comment above is addressed and so that would
go down to making the cmd_queued increment conditional.
> + break;
> + default:
> + break;
> + }
> +}
> +
> +void isp_composer_hw_init(struct device *dev)
> +{
> + struct mtk_isp_scp_p1_cmd composer_tx_cmd;
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> +
> + memset(&composer_tx_cmd, 0, sizeof(composer_tx_cmd));
> + composer_tx_cmd.cmd_id = ISP_CMD_INIT;
> + composer_tx_cmd.frameparam.hw_module = isp_ctx->isp_hw_module;
> + composer_tx_cmd.frameparam.cq_addr.iova = isp_ctx->scp_mem_iova;
> + composer_tx_cmd.frameparam.cq_addr.scp_addr = isp_ctx->scp_mem_pa;
Should we also specify the size of the buffer? Otherwise we could end up
with some undetectable overruns.
> + isp_composer_enqueue(dev, &composer_tx_cmd, SCP_ISP_CMD);
> +}
> +
> +void isp_composer_meta_config(struct device *dev,
> + unsigned int dma)
> +{
> + struct mtk_isp_scp_p1_cmd composer_tx_cmd;
> +
> + memset(&composer_tx_cmd, 0, sizeof(composer_tx_cmd));
> + composer_tx_cmd.cmd_id = ISP_CMD_CONFIG_META;
> + composer_tx_cmd.cfg_meta_out_param.enabled_meta_dmas = dma;
> + isp_composer_enqueue(dev, &composer_tx_cmd, SCP_ISP_CMD);
> +}
> +
> +void isp_composer_hw_config(struct device *dev,
> + struct p1_config_param *config_param)
> +{
> + struct mtk_isp_scp_p1_cmd composer_tx_cmd;
> +
> + memset(&composer_tx_cmd, 0, sizeof(composer_tx_cmd));
> + composer_tx_cmd.cmd_id = ISP_CMD_CONFIG;
> + memcpy(&composer_tx_cmd.config_param, config_param,
> + sizeof(*config_param));
> + isp_composer_enqueue(dev, &composer_tx_cmd, SCP_ISP_CMD);
> +}
> +
> +void isp_composer_stream(struct device *dev, int on)
> +{
> + struct mtk_isp_scp_p1_cmd composer_tx_cmd;
> +
> + memset(&composer_tx_cmd, 0, sizeof(composer_tx_cmd));
> + composer_tx_cmd.cmd_id = ISP_CMD_STREAM;
> + composer_tx_cmd.is_stream_on = on;
> + isp_composer_enqueue(dev, &composer_tx_cmd, SCP_ISP_CMD);
> +}
> +
> +void isp_composer_hw_deinit(struct device *dev)
> +{
> + struct mtk_isp_scp_p1_cmd composer_tx_cmd;
> + struct isp_p1_device *p1_dev = get_p1_device(dev);
> + struct mtk_isp_p1_ctx *isp_ctx = &p1_dev->isp_ctx;
> + int ret;
> +
> + memset(&composer_tx_cmd, 0, sizeof(composer_tx_cmd));
> + composer_tx_cmd.cmd_id = ISP_CMD_DEINIT;
> + isp_composer_enqueue(dev, &composer_tx_cmd, SCP_ISP_CMD);
> +
> + /* Wait for ISP_CMD_DEINIT command is handled done */
> + ret = wait_event_timeout(isp_ctx->composer_deinit_thread.wq,
> + atomic_read(&isp_ctx->scp_state) == SCP_OFF,
> + msecs_to_jiffies(2000));
> + if (ret)
> + return;
> +
> + dev_warn(dev, "Timeout & local de-init\n");
> + isp_composer_deinit(isp_ctx);
> +}
> diff --git a/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-scp.h b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-scp.h
> new file mode 100644
> index 000000000000..fbd8593e9c2d
> --- /dev/null
> +++ b/drivers/media/platform/mtk-isp/isp_50/cam/mtk_cam-scp.h
> @@ -0,0 +1,207 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2018 MediaTek Inc.
> + */
> +
> +#ifndef _MTK_ISP_SCP_H
> +#define _MTK_ISP_SCP_H
> +
> +#include <linux/types.h>
> +
> +#include "mtk_cam-v4l2-util.h"
> +
> +/*
> + * struct img_size - image size information.
> + *
> + * @w: image width, the unit is pixel
> + * @h: image height, the unit is pixel
> + * @xsize: bytes per line based on width.
> + * @stride: bytes per line when changing line.
> + * Normally, calculate new STRIDE based on
> + * xsize + HW constrain(page or align).
> + *
> + */
> +struct img_size {
> + __u32 w;
> + __u32 h;
> + __u32 xsize;
> + __u32 stride;
> +} __packed;
> +
> +/*
> + * struct img_buffer - buffer address information.
> + *
> + * @iova: DMA address for external devices.
> + * @scp_addr: SCP address for external co-process unit.
> + *
> + */
> +struct img_buffer {
> + __u32 iova;
> + __u32 scp_addr;
> +} __packed;
> +
> +struct p1_img_crop {
> + __u32 left;
> + __u32 top;
> + __u32 width;
> + __u32 height;
> +} __packed;
> +
> +struct p1_img_output {
> + struct img_buffer buffer;
> + struct img_size size;
> + struct p1_img_crop crop;
> + __u8 pixel_byte;
> + __u32 img_fmt;
> +} __packed;
Please document.
> +
> +/*
> + * struct cfg_in_param - image input parameters structure.
> + * Normally, it comes from sensor information.
> + *
> + * @continuous: indicate the sensor mode.
> + * 1: continuous
> + * 0: single
> + * @subsample: indicate to enables SOF subsample or not.
> + * @pixel_mode: describe 1/2/4 pixels per clock cycle.
> + * @data_pattern: describe input data pattern.
> + * @raw_pixel_id: bayer sequence.
> + * @tg_fps: the fps rate of TG (time generator).
> + * @img_fmt: the image format of input source.
> + * @p1_img_crop: the crop configuration of input source.
> + *
> + */
> +struct cfg_in_param {
> + __u8 continuous;
> + __u8 subsample;
> + __u8 pixel_mode;
> + __u8 data_pattern;
> + __u8 raw_pixel_id;
> + __u16 tg_fps;
> + __u32 img_fmt;
> + struct p1_img_crop crop;
> +} __packed;
> +
> +/*
> + * struct cfg_main_out_param - the image output parameters of main stream.
> + *
> + * @bypass: indicate this device is enabled or disabled or not .
Remove the space before the period.
> + * @pure_raw: indicate the image path control.
> + * 1: pure raw
> + * 0: processing raw
> + * @pure_raw_pack: indicate the image is packed or not.
> + * 1: packed mode
> + * 0: unpacked mode
> + * @p1_img_output: the output image information.
> + *
> + */
> +struct cfg_main_out_param {
> + /* Bypass main out parameters */
> + __u8 bypass;
> + /* Control HW image raw path */
> + __u8 pure_raw;
> + /* Control HW image pack function */
No need for these inline comments.
> + __u8 pure_raw_pack;
> + struct p1_img_output output;
> +} __packed;
> +
> +/*
> + * struct cfg_resize_out_param - the image output parameters of
> + * packed out stream.
> + *
> + * @bypass: indicate this device is enabled or disabled or not .
Remove the space before the period.
> + * @p1_img_output: the output image information.
> + *
> + */
> +struct cfg_resize_out_param {
> + /* Bypass resize parameters */
No need for this inline comment.
> + __u8 bypass;
> + struct p1_img_output output;
> +} __packed;
> +
> +/*
> + * struct cfg_meta_out_param - output meta information.
> + *
> + * @enabled_meta_dmas: indicate which meta DMAs are enabled.
> + *
> + */
> +struct cfg_meta_out_param {
> + __u32 enabled_meta_dmas;
> +} __packed;
> +
> +struct p1_config_param {
> + /* Sensor/TG info */
> + struct cfg_in_param cfg_in_param;
> + /* IMGO DMA */
> + struct cfg_main_out_param cfg_main_param;
> + /* RRZO DMA */
> + struct cfg_resize_out_param cfg_resize_param;
> + /* 3A DMAs and other. */
> + struct cfg_meta_out_param cfg_meta_param;
Please change the inline comments to a kerneldoc comment at the top.
> +} __packed;
> +
> +struct p1_frame_param {
> + /* frame sequence number */
> + __u32 frame_seq_no;
> + /* SOF index */
> + __u32 sof_idx;
> + /* The memory address of tuning buffer from user space */
Ditto.
> + struct img_buffer dma_buffers[MTK_CAM_P1_TOTAL_NODES];
> +} __packed;
> +
> +struct P1_meta_frame {
> + __u32 enabled_dma;
> + __u32 vb_index;
> + struct img_buffer meta_addr;
> +} __packed;
> +
> +struct isp_init_info {
> + __u8 hw_module;
> + struct img_buffer cq_addr;
> +} __packed;
> +
> +struct isp_ack_info {
> + __u8 cmd_id;
> + __u32 frame_seq_no;
> +} __packed;
> +
> +enum mtk_isp_scp_cmds {
> + ISP_CMD_INIT,
> + ISP_CMD_CONFIG,
> + ISP_CMD_STREAM,
> + ISP_CMD_DEINIT,
> + ISP_CMD_ACK,
> + ISP_CMD_FRAME_ACK,
> + ISP_CMD_CONFIG_META,
> + ISP_CMD_ENQUEUE_META,
> + ISP_CMD_RESERVED,
> +};
> +
> +struct mtk_isp_scp_p1_cmd {
> + __u8 cmd_id;
> + union {
> + struct isp_init_info frameparam;
> + struct p1_config_param config_param;
> + struct cfg_meta_out_param cfg_meta_out_param;
> + struct P1_meta_frame meta_frame;
> + __u8 is_stream_on;
> + struct isp_ack_info ack_info;
> + };
> +} __packed;
> +
> +enum mtk_isp_scp_type {
> + SCP_ISP_CMD = 0,
> + SCP_ISP_FRAME,
> +};
Please document all the structs and enum above using kerneldoc.
Best regards,
Tomasz
^ permalink raw reply
* Re: [PATCH 0/3] add coupled regulators for Exynos5422/5800
From: Kamil Konieczny @ 2019-07-10 10:03 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Chanwoo Choi,
Kukjin Kim, Kyungmin Park, Mark Rutland, MyungJoo Ham,
Nishanth Menon, Rob Herring, Stephen Boyd, Viresh Kumar,
devicetree, linux-arm-kernel, linux-kernel, linux-pm,
linux-samsung-soc@vger.kernel.org
In-Reply-To: <CAJKOXPd+UZ2MdrTVfBv5UYzK5LgKNQHUFzRbRNeq271EaDSchg@mail.gmail.com>
On 10.07.2019 11:00, Krzysztof Kozlowski wrote:
> On Mon, 8 Jul 2019 at 16:12, <k.konieczny@partner.samsung.com> wrote:
>>
>> From: Kamil Konieczny <k.konieczny@partner.samsung.com>
>>
>> Hi,
>>
>> The main purpose of this patch series is to add coupled regulators for
>> Exynos5422/5800 to keep constrain on voltage difference between vdd_arm
>> and vdd_int to be at most 300mV. In exynos-bus instead of using
>> regulator_set_voltage_tol() with default voltage tolerance it should be
>> used regulator_set_voltage_triplet() with volatege range, and this is
>> already present in opp/core.c code, so it can be reused. While at this,
>> move setting regulators into opp/core.
>>
>> This patchset was tested on Odroid XU3.
>>
>> The last patch depends on two previous.
>
> So you break the ABI... I assume that patchset maintains
> bisectability. However there is no explanation why ABI break is needed
> so this does not look good...
Patchset is bisectable, first one is simple and do not depends on others,
second depends on first, last depends on first and second.
What do you mean by breaking ABI ?
> Best regards,
> Krzysztof
>
>>
>> Regards,
>> Kamil
>>
>> Kamil Konieczny (2):
>> opp: core: add regulators enable and disable
>> devfreq: exynos-bus: convert to use dev_pm_opp_set_rate()
>>
>> Marek Szyprowski (1):
>> ARM: dts: exynos: add initial data for coupled regulators for
>> Exynos5422/5800
>>
>> arch/arm/boot/dts/exynos5420.dtsi | 34 ++--
>> arch/arm/boot/dts/exynos5422-odroid-core.dtsi | 4 +
>> arch/arm/boot/dts/exynos5800-peach-pi.dts | 4 +
>> arch/arm/boot/dts/exynos5800.dtsi | 32 ++--
>> drivers/devfreq/exynos-bus.c | 172 +++++++-----------
>> drivers/opp/core.c | 13 ++
>> 6 files changed, 120 insertions(+), 139 deletions(-)
>>
>> --
>> 2.22.0
--
Best regards,
Kamil Konieczny
Samsung R&D Institute Poland
^ permalink raw reply
* Re: [PATCH 0/3] add coupled regulators for Exynos5422/5800
From: Krzysztof Kozlowski @ 2019-07-10 10:14 UTC (permalink / raw)
To: Kamil Konieczny
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Chanwoo Choi,
Kukjin Kim, Kyungmin Park, Mark Rutland, MyungJoo Ham,
Nishanth Menon, Rob Herring, Stephen Boyd, Viresh Kumar,
devicetree, linux-arm-kernel, linux-kernel, linux-pm,
linux-samsung-soc@vger.kernel.org
In-Reply-To: <91f65527-3440-90fd-4096-5824fba1df78@partner.samsung.com>
On Wed, 10 Jul 2019 at 12:03, Kamil Konieczny
<k.konieczny@partner.samsung.com> wrote:
>
> On 10.07.2019 11:00, Krzysztof Kozlowski wrote:
> > On Mon, 8 Jul 2019 at 16:12, <k.konieczny@partner.samsung.com> wrote:
> >>
> >> From: Kamil Konieczny <k.konieczny@partner.samsung.com>
> >>
> >> Hi,
> >>
> >> The main purpose of this patch series is to add coupled regulators for
> >> Exynos5422/5800 to keep constrain on voltage difference between vdd_arm
> >> and vdd_int to be at most 300mV. In exynos-bus instead of using
> >> regulator_set_voltage_tol() with default voltage tolerance it should be
> >> used regulator_set_voltage_triplet() with volatege range, and this is
> >> already present in opp/core.c code, so it can be reused. While at this,
> >> move setting regulators into opp/core.
> >>
> >> This patchset was tested on Odroid XU3.
> >>
> >> The last patch depends on two previous.
> >
> > So you break the ABI... I assume that patchset maintains
> > bisectability. However there is no explanation why ABI break is needed
> > so this does not look good...
>
> Patchset is bisectable, first one is simple and do not depends on others,
> second depends on first, last depends on first and second.
>
> What do you mean by breaking ABI ?
I mean, that Linux kernel stops working with existing DTBs... or am I
mistaken and there is no problem? Maybe I confused the order...
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 1/3] opp: core: add regulators enable and disable
From: Kamil Konieczny @ 2019-07-10 10:16 UTC (permalink / raw)
To: Viresh Kumar
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Chanwoo Choi,
Krzysztof Kozlowski, Kukjin Kim, Kyungmin Park, Mark Rutland,
MyungJoo Ham, Nishanth Menon, Rob Herring, Stephen Boyd,
Viresh Kumar, devicetree, linux-arm-kernel, linux-kernel,
linux-pm, linux-samsung-soc
In-Reply-To: <20190709054014.o3g4e6gbovrq3vvn@vireshk-i7>
On 09.07.2019 07:40, Viresh Kumar wrote:
> On 08-07-19, 16:11, k.konieczny@partner.samsung.com wrote:
>> From: Kamil Konieczny <k.konieczny@partner.samsung.com>
>>
>> Add enable regulators to dev_pm_opp_set_regulators() and disable
>> regulators to dev_pm_opp_put_regulators(). This prepares for
>> converting exynos-bus devfreq driver to use dev_pm_opp_set_rate().
>>
>> Signed-off-by: Kamil Konieczny <k.konieczny@partner.samsung.com>
>> ---
>> drivers/opp/core.c | 13 +++++++++++++
>> 1 file changed, 13 insertions(+)
>>
>> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
>> index 0e7703fe733f..947cac452854 100644
>> --- a/drivers/opp/core.c
>> +++ b/drivers/opp/core.c
>> @@ -1580,8 +1580,19 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
>> if (ret)
>> goto free_regulators;
>>
>> + for (i = 0; i < opp_table->regulator_count; i++) {
>> + ret = regulator_enable(opp_table->regulators[i]);
>> + if (ret < 0)
>> + goto disable;
>> + }
>
> I am wondering on why is this really required as this isn't done for
> any other platform, probably because the regulators are enabled by
> bootloader and are always on.
It was not enabled for historical reasons, from design point regualtors
should be enabled before use.
--
Best regards,
Kamil Konieczny
Samsung R&D Institute Poland
^ permalink raw reply
* RE: [v4 1/6] dt-bindings: media: Document bindings for DW MIPI CSI-2 Host
From: Luis de Oliveira @ 2019-07-10 10:20 UTC (permalink / raw)
To: Sakari Ailus, Luis de Oliveira
Cc: mchehab@kernel.org, davem@davemloft.net,
gregkh@linuxfoundation.org, Jonathan.Cameron@huawei.com,
robh@kernel.org, nicolas.ferre@microchip.com,
paulmck@linux.ibm.com, mark.rutland@arm.com, kishon@ti.com,
devicetree@vger.kernel.org, linux-media@vger.kernel.org,
linux-kernel@vger.kernel.org, Joao.Pinto@synopsys.com
In-Reply-To: <20190709182500.3x544axnrgy72aje@valkosipuli.retiisi.org.uk>
Hi Sakari,
From: Sakari Ailus <sakari.ailus@iki.fi>
Date: Tue, Jul 09, 2019 at 19:25:00
> Hi Luis,
>
> On Mon, Jul 08, 2019 at 03:21:50PM +0000, Luis de Oliveira wrote:
> > Hi Sakari,
> >
> > Thank you for your feedback.
> > I have my comments inline.
> >
> > From: Sakari Ailus <sakari.ailus@iki.fi>
> > Date: Fri, Jun 28, 2019 at 15:13:26
> >
> > > Hi Luis,
> > >
> > > Thank you for the patchset.
> > >
> > > On Tue, Jun 11, 2019 at 09:20:50PM +0200, Luis Oliveira wrote:
> > > > From: Luis Oliveira <lolivei@synopsys.com>
> > > >
> > > > Add bindings for Synopsys DesignWare MIPI CSI-2 host.
> > > >
> > > > Signed-off-by: Luis Oliveira <lolivei@synopsys.com>
> > > > ---
> > > > Changelog
> > > > v3-v4
> > > > - remove "plat" from the block name @rob @laurent
> > > > - remove "phy-names" when single-entry @rob
> > > > - remove "snps,output-type" -> went to the driver config @laurent
> > > >
> > > > .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> > > > 1 file changed, 41 insertions(+)
> > > > create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > >
> > > > diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > > new file mode 100644
> > > > index 0000000..613b7f9
> > > > --- /dev/null
> > > > +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> > > > @@ -0,0 +1,41 @@
> > > > +Synopsys DesignWare CSI-2 Host controller
> > > > +
> > > > +Description
> > > > +-----------
> > > > +
> > > > +This HW block is used to receive image coming from an MIPI CSI-2 compatible
> > > > +camera.
> > > > +
> > > > +Required properties:
> > > > +- compatible : shall be "snps,dw-csi"
> > > > +- reg : physical base address and size of the device memory
> > > > + mapped registers;
> > > > +- interrupts : DW CSI-2 Host interrupts
> > > > +- phys : List of one PHY specifier (as defined in
> > > > + Documentation/devicetree/bindings/phy/phy-bindings.txt).
> > > > + This PHY is a MIPI DPHY working in RX mode.
> > > > +- resets : Reference to a reset controller (optional)
> > > > +
> > > > +The per-board settings:
> > > > + - port sub-node describing a single endpoint connected to the camera as
> > > > + described in video-interfaces.txt[1].
> > >
> > > Which endpoint properties in video-interfaces.txt are relevant for the
> > > hardware? Which values may they have?
> > >
> >
> > Currently I'm using only two properties "data-lanes" and "bus-width", but
> > I have plans to add blanking info also.
> > I will add more info.
>
> Isn't blanking defined by what the transmitter seneds? Or do you have
> hardware limitations on the receiver side?
>
When we use this IP in prototyping we configure blanking at the receiver
side.
Some cameras don't have blanking configuration capabilities so we
configure it on the RX side.
> I've only heard of one such case before, and it was a very old parallel
> receiver.
>
> If you have a CSI-2 receiver, bus-width isn't relevant --- it's for paralle
> interfaces only. Please add data-lanes to required endpoint properties.
>
I used bus-width property in the Synopsys IPI (Image Pixel Interface)
that enables direct video stream access.
This interface is an output that can be 16-bit or 48-bit, that's why I
used bus-width property.
> --
> Regards,
>
> Sakari Ailus
Thank you,
Luis
^ permalink raw reply
* RE: [v4 1/6] dt-bindings: media: Document bindings for DW MIPI CSI-2 Host
From: Luis de Oliveira @ 2019-07-10 10:23 UTC (permalink / raw)
To: Eugen.Hristev@microchip.com, Luis.Oliveira@synopsys.com,
mchehab@kernel.org, davem@davemloft.net,
gregkh@linuxfoundation.org, Jonathan.Cameron@huawei.com,
robh@kernel.org, Nicolas.Ferre@microchip.com,
paulmck@linux.ibm.com, mark.rutland@arm.com, kishon@ti.com,
devicetree@vger.kernel.org, linux-media@vger.kernel.org,
linux-kernel@vger.kernel.org
Cc: Joao.Pinto@synopsys.com
In-Reply-To: <415416c4-c6d8-f81e-82cb-126e4702801b@microchip.com>
Hi Eugen,
From: Eugen.Hristev@microchip.com <Eugen.Hristev@microchip.com>
Date: Wed, Jul 10, 2019 at 07:53:02
>
>
> On 09.07.2019 20:08, Luis de Oliveira wrote:
>
> >
> > Hi Eugen,
> >
> >
> > From: Eugen.Hristev@microchip.com <Eugen.Hristev@microchip.com>
> > Date: Tue, Jul 09, 2019 at 15:33:50
> >
> >>
> >>
> >> On 11.06.2019 22:20, Luis Oliveira wrote:
> >>> From: Luis Oliveira <lolivei@synopsys.com>
> >>>
> >>> Add bindings for Synopsys DesignWare MIPI CSI-2 host.
> >>>
> >>> Signed-off-by: Luis Oliveira <lolivei@synopsys.com>
> >>> ---
> >>> Changelog
> >>> v3-v4
> >>> - remove "plat" from the block name @rob @laurent
> >>> - remove "phy-names" when single-entry @rob
> >>> - remove "snps,output-type" -> went to the driver config @laurent
> >>>
> >>> .../devicetree/bindings/media/snps,dw-csi.txt | 41 ++++++++++++++++++++++
> >>> 1 file changed, 41 insertions(+)
> >>> create mode 100644 Documentation/devicetree/bindings/media/snps,dw-csi.txt
> >>>
> >>> diff --git a/Documentation/devicetree/bindings/media/snps,dw-csi.txt b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> >>> new file mode 100644
> >>> index 0000000..613b7f9
> >>> --- /dev/null
> >>> +++ b/Documentation/devicetree/bindings/media/snps,dw-csi.txt
> >>> @@ -0,0 +1,41 @@
> >>> +Synopsys DesignWare CSI-2 Host controller
> >>> +
> >>> +Description
> >>> +-----------
> >>> +
> >>> +This HW block is used to receive image coming from an MIPI CSI-2 compatible
> >>> +camera.
> >>> +
> >>> +Required properties:
> >>> +- compatible : shall be "snps,dw-csi"
> >>> +- reg : physical base address and size of the device memory
> >>> + mapped registers;
> >>> +- interrupts : DW CSI-2 Host interrupts
> >>> +- phys : List of one PHY specifier (as defined in
> >>> + Documentation/devicetree/bindings/phy/phy-bindings.txt).
> >>> + This PHY is a MIPI DPHY working in RX mode.
> >>> +- resets : Reference to a reset controller (optional)
> >>> +
> >>> +The per-board settings:
> >>> + - port sub-node describing a single endpoint connected to the camera as
> >>> + described in video-interfaces.txt[1].
> >>> +
> >>> +Example:
> >>> +
> >>> + csi2: csi2@3000 {
> >>> + compatible = "snps,dw-csi";
> >>> + #address-cells = <1>;
> >>> + #size-cells = <0>;
> >>> + reg = < 0x03000 0x7FF>;
> >>> + phys = <&mipi_dphy_rx>;
> >>> + resets = <&dw_rst 1>;
> >>> + interrupts = <2>;
> >>> +
> >>> + port@0 {
> >>> + reg = <0>;
> >>> + csi_ep1: endpoint {
> >>> + remote-endpoint = <&camera_1>;
> >>> + data-lanes = <1 2>;
> >>> + };
> >>
> >> Hello Luis,
> >>
> >> Which is the output port (endpoint) : how to connect the output of
> >> csi2host to another node ?
> >> I mean, the second port of this block, or, how is the data taken from
> >> csi2host ?
> >>
> >
> > I understand your question, I think you guessed this is not the complete
> > pipeline (I have a top driver that interacts with this one).
> > I was not planning to submit it, do you think I should?
>
> Yes please, you can have the patch with subject DO NOT MERGE if you do
> not want that patch to be included in the kernel and just for reference.
> but it would help me in understanding your setup
>
> Thanks !
>
Ok, thank you. I will included it next.
> >
> > The behavior is very similar with this one
> > ./drivers/media/platform/exynos4-is/media-dev.c
> >
> >
> >> Thanks,
> >>
> >> Eugen
> >>
> >>> + };
> >>> + };
> >>>
> >
> > Thanks,
> > Luis
> >
^ permalink raw reply
* Re: [PATCH V13 05/12] PCI: dwc: Add ext config space capability search API
From: Lorenzo Pieralisi @ 2019-07-10 10:37 UTC (permalink / raw)
To: Vidya Sagar
Cc: bhelgaas, robh+dt, mark.rutland, thierry.reding, jonathanh,
kishon, catalin.marinas, will.deacon, jingoohan1,
gustavo.pimentel, digetx, mperttunen, linux-pci, devicetree,
linux-tegra, linux-kernel, linux-arm-kernel, kthota, mmaddireddy,
sagar.tv
In-Reply-To: <20190710062212.1745-6-vidyas@nvidia.com>
On Wed, Jul 10, 2019 at 11:52:05AM +0530, Vidya Sagar wrote:
> Add extended configuration space capability search API using struct dw_pcie *
> pointer
Sentences are terminated with a period and this is v13 not v1, which
proves that you do not read the commit logs you write.
I need you guys to understand that I can't rewrite commit logs all
the time, I do not want to go as far as not accepting your patches
anymore so please do pay attention to commit log details they
are as important as the code itself.
https://lore.kernel.org/linux-pci/20171026223701.GA25649@bhelgaas-glaptop.roam.corp.google.com/
Thanks,
Lorenzo
> Signed-off-by: Vidya Sagar <vidyas@nvidia.com>
> Acked-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
> Acked-by: Thierry Reding <treding@nvidia.com>
> ---
> V13:
> * None
>
> V12:
> * None
>
> V11:
> * None
>
> V10:
> * None
>
> V9:
> * Added Acked-by from Thierry
>
> V8:
> * Changed data types of return and arguments to be inline with data being returned
> and passed.
>
> V7:
> * None
>
> V6:
> * None
>
> V5:
> * None
>
> V4:
> * None
>
> V3:
> * None
>
> V2:
> * This is a new patch in v2 series
>
> drivers/pci/controller/dwc/pcie-designware.c | 41 ++++++++++++++++++++
> drivers/pci/controller/dwc/pcie-designware.h | 1 +
> 2 files changed, 42 insertions(+)
>
> diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c
> index 7818b4febb08..181449e342f1 100644
> --- a/drivers/pci/controller/dwc/pcie-designware.c
> +++ b/drivers/pci/controller/dwc/pcie-designware.c
> @@ -53,6 +53,47 @@ u8 dw_pcie_find_capability(struct dw_pcie *pci, u8 cap)
> }
> EXPORT_SYMBOL_GPL(dw_pcie_find_capability);
>
> +static u16 dw_pcie_find_next_ext_capability(struct dw_pcie *pci, u16 start,
> + u8 cap)
> +{
> + u32 header;
> + int ttl;
> + int pos = PCI_CFG_SPACE_SIZE;
> +
> + /* minimum 8 bytes per capability */
> + ttl = (PCI_CFG_SPACE_EXP_SIZE - PCI_CFG_SPACE_SIZE) / 8;
> +
> + if (start)
> + pos = start;
> +
> + header = dw_pcie_readl_dbi(pci, pos);
> + /*
> + * If we have no capabilities, this is indicated by cap ID,
> + * cap version and next pointer all being 0.
> + */
> + if (header == 0)
> + return 0;
> +
> + while (ttl-- > 0) {
> + if (PCI_EXT_CAP_ID(header) == cap && pos != start)
> + return pos;
> +
> + pos = PCI_EXT_CAP_NEXT(header);
> + if (pos < PCI_CFG_SPACE_SIZE)
> + break;
> +
> + header = dw_pcie_readl_dbi(pci, pos);
> + }
> +
> + return 0;
> +}
> +
> +u16 dw_pcie_find_ext_capability(struct dw_pcie *pci, u8 cap)
> +{
> + return dw_pcie_find_next_ext_capability(pci, 0, cap);
> +}
> +EXPORT_SYMBOL_GPL(dw_pcie_find_ext_capability);
> +
> int dw_pcie_read(void __iomem *addr, int size, u32 *val)
> {
> if (!IS_ALIGNED((uintptr_t)addr, size)) {
> diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
> index d8c66a6827dc..11c223471416 100644
> --- a/drivers/pci/controller/dwc/pcie-designware.h
> +++ b/drivers/pci/controller/dwc/pcie-designware.h
> @@ -252,6 +252,7 @@ struct dw_pcie {
> container_of((endpoint), struct dw_pcie, ep)
>
> u8 dw_pcie_find_capability(struct dw_pcie *pci, u8 cap);
> +u16 dw_pcie_find_ext_capability(struct dw_pcie *pci, u8 cap);
>
> int dw_pcie_read(void __iomem *addr, int size, u32 *val);
> int dw_pcie_write(void __iomem *addr, int size, u32 val);
> --
> 2.17.1
>
^ permalink raw reply
* Re: [PATCH 1/3] opp: core: add regulators enable and disable
From: Kamil Konieczny @ 2019-07-10 10:42 UTC (permalink / raw)
To: Viresh Kumar
Cc: Mark Rutland, Nishanth Menon, linux-samsung-soc, Rob Herring,
linux-arm-kernel, Bartlomiej Zolnierkiewicz, Stephen Boyd,
Viresh Kumar, linux-pm, linux-kernel, Krzysztof Kozlowski,
Chanwoo Choi, Kyungmin Park, Kukjin Kim, MyungJoo Ham, devicetree,
Marek Szyprowski
In-Reply-To: <3d6eacb8-ed8d-d4ac-1c36-6224ab88a5dc@partner.samsung.com>
On 10.07.2019 12:16, Kamil Konieczny wrote:
>
>
> On 09.07.2019 07:40, Viresh Kumar wrote:
>> On 08-07-19, 16:11, k.konieczny@partner.samsung.com wrote:
>>> From: Kamil Konieczny <k.konieczny@partner.samsung.com>
>>>
>>> Add enable regulators to dev_pm_opp_set_regulators() and disable
>>> regulators to dev_pm_opp_put_regulators(). This prepares for
>>> converting exynos-bus devfreq driver to use dev_pm_opp_set_rate().
>>>
>>> Signed-off-by: Kamil Konieczny <k.konieczny@partner.samsung.com>
>>> ---
>>> drivers/opp/core.c | 13 +++++++++++++
>>> 1 file changed, 13 insertions(+)
>>>
>>> diff --git a/drivers/opp/core.c b/drivers/opp/core.c
>>> index 0e7703fe733f..947cac452854 100644
>>> --- a/drivers/opp/core.c
>>> +++ b/drivers/opp/core.c
>>> @@ -1580,8 +1580,19 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
>>> if (ret)
>>> goto free_regulators;
>>>
>>> + for (i = 0; i < opp_table->regulator_count; i++) {
>>> + ret = regulator_enable(opp_table->regulators[i]);
>>> + if (ret < 0)
>>> + goto disable;
>>> + }
>>
>> I am wondering on why is this really required as this isn't done for
>> any other platform, probably because the regulators are enabled by
>> bootloader and are always on.
>
> It was not enabled for historical reasons, from design point regualtors
> should be enabled before use.
On Exynos platform devfreq driver (exynos-bus) always enabled them,
so I wanted to preserve the current behaviour.
I've also checked the change with cpufreq-dt driver and it doesn't cause
issues.
Do you find this change acceptable?
--
Best regards,
Kamil Konieczny
Samsung R&D Institute Poland
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox