From c0b9713f6219f0f0bcfd4c29525849ced8f99b3c Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Sun, 31 Jul 2022 18:38:29 -0700 Subject: [PATCH 01/16] tools include: add dis-asm-compat.h to handle version differences commit a45b3d6926231c3d024ea0de4f7bd967f83709ee upstream. binutils changed the signature of init_disassemble_info(), which now causes compilation failures for tools/{perf,bpf}, e.g. on debian unstable. Relevant binutils commit: https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=60a3da00bd5407f07 This commit introduces a wrapper for init_disassemble_info(), to avoid spreading #ifdef DISASM_INIT_STYLED to a bunch of places. Subsequent commits will use it to fix the build failures. It likely is worth adding a wrapper for disassember(), to avoid the already existing DISASM_FOUR_ARGS_SIGNATURE ifdefery. Signed-off-by: Andres Freund Signed-off-by: Ben Hutchings Acked-by: Quentin Monnet Cc: Alexei Starovoitov Cc: Ben Hutchings Cc: Jiri Olsa Cc: Quentin Monnet Cc: Sedat Dilek Cc: bpf@vger.kernel.org Link: http://lore.kernel.org/lkml/20220622181918.ykrs5rsnmx3og4sv@alap3.anarazel.de Link: https://lore.kernel.org/r/20220801013834.156015-4-andres@anarazel.de Signed-off-by: Arnaldo Carvalho de Melo Cc: Hauke Mehrtens Signed-off-by: Greg Kroah-Hartman [sw0312.kim: cherry-pick stable linux-5.15.y commit 451c9d7b1616 to resolve gcc-12 build issue] Signed-off-by: Seung-Woo Kim Change-Id: I41fde6de2055b838dfbdc3d6de1b64eea3ab20c5 --- tools/include/tools/dis-asm-compat.h | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tools/include/tools/dis-asm-compat.h diff --git a/tools/include/tools/dis-asm-compat.h b/tools/include/tools/dis-asm-compat.h new file mode 100644 index 0000000..70f331e --- /dev/null +++ b/tools/include/tools/dis-asm-compat.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */ +#ifndef _TOOLS_DIS_ASM_COMPAT_H +#define _TOOLS_DIS_ASM_COMPAT_H + +#include +#include + +/* define types for older binutils version, to centralize ifdef'ery a bit */ +#ifndef DISASM_INIT_STYLED +enum disassembler_style {DISASSEMBLER_STYLE_NOT_EMPTY}; +typedef int (*fprintf_styled_ftype) (void *, enum disassembler_style, const char*, ...); +#endif + +/* + * Trivial fprintf wrapper to be used as the fprintf_styled_func argument to + * init_disassemble_info_compat() when normal fprintf suffices. + */ +static inline int fprintf_styled(void *out, + enum disassembler_style style, + const char *fmt, ...) +{ + va_list args; + int r; + + (void)style; + + va_start(args, fmt); + r = vfprintf(out, fmt, args); + va_end(args); + + return r; +} + +/* + * Wrapper for init_disassemble_info() that hides version + * differences. Depending on binutils version and architecture either + * fprintf_func or fprintf_styled_func will be called. + */ +static inline void init_disassemble_info_compat(struct disassemble_info *info, + void *stream, + fprintf_ftype unstyled_func, + fprintf_styled_ftype styled_func) +{ +#ifdef DISASM_INIT_STYLED + init_disassemble_info(info, stream, + unstyled_func, + styled_func); +#else + (void)styled_func; + init_disassemble_info(info, stream, + unstyled_func); +#endif +} + +#endif /* _TOOLS_DIS_ASM_COMPAT_H */ -- 2.7.4 From 5d77f2dd1a8d1c026205bb5a9dce9d6cf9a6cec2 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Sun, 31 Jul 2022 18:38:30 -0700 Subject: [PATCH 02/16] tools perf: Fix compilation error with new binutils commit 83aa0120487e8bc3f231e72c460add783f71f17c upstream. binutils changed the signature of init_disassemble_info(), which now causes compilation failures for tools/perf/util/annotate.c, e.g. on debian unstable. Relevant binutils commit: https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=60a3da00bd5407f07 Wire up the feature test and switch to init_disassemble_info_compat(), which were introduced in prior commits, fixing the compilation failure. I verified that perf can still disassemble bpf programs by using bpftrace under load, recording a perf trace, and then annotating the bpf "function" with and without the changes. With old binutils there's no change in output before/after this patch. When comparing the output from old binutils (2.35) to new bintuils with the patch (upstream snapshot) there are a few output differences, but they are unrelated to this patch. An example hunk is: 1.15 : 55:mov %rbp,%rdx 0.00 : 58:add $0xfffffffffffffff8,%rdx 0.00 : 5c:xor %ecx,%ecx - 1.03 : 5e:callq 0xffffffffe12aca3c + 1.03 : 5e:call 0xffffffffe12aca3c 0.00 : 63:xor %eax,%eax - 2.18 : 65:leaveq - 2.82 : 66:retq + 2.18 : 65:leave + 2.82 : 66:ret Signed-off-by: Andres Freund Acked-by: Quentin Monnet Cc: Alexei Starovoitov Cc: Ben Hutchings Cc: Jiri Olsa Cc: Sedat Dilek Cc: bpf@vger.kernel.org Link: http://lore.kernel.org/lkml/20220622181918.ykrs5rsnmx3og4sv@alap3.anarazel.de Link: https://lore.kernel.org/r/20220801013834.156015-5-andres@anarazel.de Signed-off-by: Arnaldo Carvalho de Melo Cc: Hauke Mehrtens Signed-off-by: Greg Kroah-Hartman [sw0312.kim: cherry-pick stable linux-5.15.y commit 97f005c0bdba to resolve gcc-12 build issue] Signed-off-by: Seung-Woo Kim Change-Id: Ib9c0a83b11e28e1d1f5846b7b1bf21e007aaa1fd --- tools/perf/Makefile.config | 8 ++++++++ tools/perf/util/annotate.c | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config index 35e1f2a..2c30a2b 100644 --- a/tools/perf/Makefile.config +++ b/tools/perf/Makefile.config @@ -296,6 +296,7 @@ FEATURE_CHECK_LDFLAGS-libpython := $(PYTHON_EMBED_LDOPTS) FEATURE_CHECK_LDFLAGS-libaio = -lrt FEATURE_CHECK_LDFLAGS-disassembler-four-args = -lbfd -lopcodes -ldl +FEATURE_CHECK_LDFLAGS-disassembler-init-styled = -lbfd -lopcodes -ldl CORE_CFLAGS += -fno-omit-frame-pointer CORE_CFLAGS += -ggdb3 @@ -872,13 +873,16 @@ ifndef NO_LIBBFD ifeq ($(feature-libbfd-liberty), 1) EXTLIBS += -lbfd -lopcodes -liberty FEATURE_CHECK_LDFLAGS-disassembler-four-args += -liberty -ldl + FEATURE_CHECK_LDFLAGS-disassembler-init-styled += -liberty -ldl else ifeq ($(feature-libbfd-liberty-z), 1) EXTLIBS += -lbfd -lopcodes -liberty -lz FEATURE_CHECK_LDFLAGS-disassembler-four-args += -liberty -lz -ldl + FEATURE_CHECK_LDFLAGS-disassembler-init-styled += -liberty -lz -ldl endif endif $(call feature_check,disassembler-four-args) + $(call feature_check,disassembler-init-styled) endif ifeq ($(feature-libbfd-buildid), 1) @@ -992,6 +996,10 @@ ifeq ($(feature-disassembler-four-args), 1) CFLAGS += -DDISASM_FOUR_ARGS_SIGNATURE endif +ifeq ($(feature-disassembler-init-styled), 1) + CFLAGS += -DDISASM_INIT_STYLED +endif + ifeq (${IS_64_BIT}, 1) ifndef NO_PERF_READ_VDSO32 $(call feature_check,compile-32) diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index 0bae061..a5e87c7 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -1694,6 +1694,7 @@ fallback: #include #include #include +#include static int symbol__disassemble_bpf(struct symbol *sym, struct annotate_args *args) @@ -1736,9 +1737,9 @@ static int symbol__disassemble_bpf(struct symbol *sym, ret = errno; goto out; } - init_disassemble_info(&info, s, - (fprintf_ftype) fprintf); - + init_disassemble_info_compat(&info, s, + (fprintf_ftype) fprintf, + fprintf_styled); info.arch = bfd_get_arch(bfdf); info.mach = bfd_get_mach(bfdf); -- 2.7.4 From 213727db0b99505da19acf50d5b36a05b2a3b192 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Sun, 31 Jul 2022 18:38:31 -0700 Subject: [PATCH 03/16] tools bpf_jit_disasm: Fix compilation error with new binutils commit 96ed066054abf11c7d3e106e3011a51f3f1227a3 upstream. binutils changed the signature of init_disassemble_info(), which now causes compilation to fail for tools/bpf/bpf_jit_disasm.c, e.g. on debian unstable. Relevant binutils commit: https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=60a3da00bd5407f07 Wire up the feature test and switch to init_disassemble_info_compat(), which were introduced in prior commits, fixing the compilation failure. I verified that bpf_jit_disasm can still disassemble bpf programs, both with the old and new dis-asm.h API. With old binutils there's no change in output before/after this patch. When comparing the output from old binutils (2.35) to new bintuils with the patch (upstream snapshot) there are a few output differences, but they are unrelated to this patch. An example hunk is: f4: mov %r14,%rsi f7: mov %r15,%rdx fa: mov $0x2a,%ecx - ff: callq 0xffffffffea8c4988 + ff: call 0xffffffffea8c4988 104: test %rax,%rax 107: jge 0x0000000000000110 109: xor %eax,%eax - 10b: jmpq 0x0000000000000073 + 10b: jmp 0x0000000000000073 110: cmp $0x16,%rax However, I had to use an older kernel to generate the bpf_jit_enabled = 2 output, as that has been broken since 5.18 / 1022a5498f6f745c ("bpf, x86_64: Use bpf_jit_binary_pack_alloc"). https://lore.kernel.org/20220703030210.pmjft7qc2eajzi6c@alap3.anarazel.de Signed-off-by: Andres Freund Acked-by: Quentin Monnet Cc: Alexei Starovoitov Cc: Ben Hutchings Cc: Daniel Borkmann Cc: Jiri Olsa Cc: Quentin Monnet Cc: Sedat Dilek Cc: bpf@vger.kernel.org Link: http://lore.kernel.org/lkml/20220622181918.ykrs5rsnmx3og4sv@alap3.anarazel.de Link: https://lore.kernel.org/r/20220801013834.156015-6-andres@anarazel.de Signed-off-by: Arnaldo Carvalho de Melo Cc: Hauke Mehrtens Signed-off-by: Greg Kroah-Hartman [sw0312.kim: cherry-pick stable linux-5.15.y commit 1c27fab24333 to resolve gcc-12-build issue] Signed-off-by: Seung-Woo Kim Change-Id: Ie64ecd2f88c14389110d8fab6e1f595bd38b1516 --- tools/bpf/Makefile | 5 ++++- tools/bpf/bpf_jit_disasm.c | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/bpf/Makefile b/tools/bpf/Makefile index b11cfc8..664601a 100644 --- a/tools/bpf/Makefile +++ b/tools/bpf/Makefile @@ -34,7 +34,7 @@ else endif FEATURE_USER = .bpf -FEATURE_TESTS = libbfd disassembler-four-args +FEATURE_TESTS = libbfd disassembler-four-args disassembler-init-styled FEATURE_DISPLAY = libbfd disassembler-four-args check_feat := 1 @@ -56,6 +56,9 @@ endif ifeq ($(feature-disassembler-four-args), 1) CFLAGS += -DDISASM_FOUR_ARGS_SIGNATURE endif +ifeq ($(feature-disassembler-init-styled), 1) +CFLAGS += -DDISASM_INIT_STYLED +endif $(OUTPUT)%.yacc.c: $(srctree)/tools/bpf/%.y $(QUIET_BISON)$(YACC) -o $@ -d $< diff --git a/tools/bpf/bpf_jit_disasm.c b/tools/bpf/bpf_jit_disasm.c index c8ae958..a90a5d1 100644 --- a/tools/bpf/bpf_jit_disasm.c +++ b/tools/bpf/bpf_jit_disasm.c @@ -28,6 +28,7 @@ #include #include #include +#include #define CMD_ACTION_SIZE_BUFFER 10 #define CMD_ACTION_READ_ALL 3 @@ -64,7 +65,9 @@ static void get_asm_insns(uint8_t *image, size_t len, int opcodes) assert(bfdf); assert(bfd_check_format(bfdf, bfd_object)); - init_disassemble_info(&info, stdout, (fprintf_ftype) fprintf); + init_disassemble_info_compat(&info, stdout, + (fprintf_ftype) fprintf, + fprintf_styled); info.arch = bfd_get_arch(bfdf); info.mach = bfd_get_mach(bfdf); info.buffer = image; -- 2.7.4 From dedbb9a1439d6f5cc837c05c0a00a68db2a03965 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Sun, 31 Jul 2022 18:38:33 -0700 Subject: [PATCH 04/16] tools bpftool: Fix compilation error with new binutils commit 600b7b26c07a070d0153daa76b3806c1e52c9e00 upstream. binutils changed the signature of init_disassemble_info(), which now causes compilation to fail for tools/bpf/bpftool/jit_disasm.c, e.g. on debian unstable. Relevant binutils commit: https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=60a3da00bd5407f07 Wire up the feature test and switch to init_disassemble_info_compat(), which were introduced in prior commits, fixing the compilation failure. I verified that bpftool can still disassemble bpf programs, both with an old and new dis-asm.h API. There are no output changes for plain and json formats. When comparing the output from old binutils (2.35) to new bintuils with the patch (upstream snapshot) there are a few output differences, but they are unrelated to this patch. An example hunk is: 2f: pop %r14 31: pop %r13 33: pop %rbx - 34: leaveq - 35: retq + 34: leave + 35: ret Signed-off-by: Andres Freund Acked-by: Quentin Monnet Cc: Alexei Starovoitov Cc: Ben Hutchings Cc: Jiri Olsa Cc: Quentin Monnet Cc: Sedat Dilek Cc: bpf@vger.kernel.org Link: http://lore.kernel.org/lkml/20220622181918.ykrs5rsnmx3og4sv@alap3.anarazel.de Link: https://lore.kernel.org/r/20220801013834.156015-8-andres@anarazel.de Signed-off-by: Arnaldo Carvalho de Melo Cc: Hauke Mehrtens Signed-off-by: Greg Kroah-Hartman [sw0312.kim: cherry-pick stable linux-5.15.y commit 4441a9009193 to resolve gcc-12-build issue] Signed-off-by: Seung-Woo Kim Change-Id: I4bc40a822995acad090f987a2937a6db47a6f511 --- tools/bpf/bpftool/Makefile | 5 ++++- tools/bpf/bpftool/jit_disasm.c | 42 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile index cce52df..11266c7 100644 --- a/tools/bpf/bpftool/Makefile +++ b/tools/bpf/bpftool/Makefile @@ -76,7 +76,7 @@ INSTALL ?= install RM ?= rm -f FEATURE_USER = .bpftool -FEATURE_TESTS = libbfd disassembler-four-args reallocarray zlib libcap \ +FEATURE_TESTS = libbfd disassembler-four-args disassembler-init-styled reallocarray zlib libcap \ clang-bpf-co-re FEATURE_DISPLAY = libbfd disassembler-four-args zlib libcap \ clang-bpf-co-re @@ -111,6 +111,9 @@ ifeq ($(feature-libcap), 1) CFLAGS += -DUSE_LIBCAP LIBS += -lcap endif +ifeq ($(feature-disassembler-init-styled), 1) + CFLAGS += -DDISASM_INIT_STYLED +endif include $(wildcard $(OUTPUT)*.d) diff --git a/tools/bpf/bpftool/jit_disasm.c b/tools/bpf/bpftool/jit_disasm.c index 24734f2..aaf99a0 100644 --- a/tools/bpf/bpftool/jit_disasm.c +++ b/tools/bpf/bpftool/jit_disasm.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "json_writer.h" #include "main.h" @@ -39,15 +40,12 @@ static void get_exec_path(char *tpath, size_t size) } static int oper_count; -static int fprintf_json(void *out, const char *fmt, ...) +static int printf_json(void *out, const char *fmt, va_list ap) { - va_list ap; char *s; int err; - va_start(ap, fmt); err = vasprintf(&s, fmt, ap); - va_end(ap); if (err < 0) return -1; @@ -73,6 +71,32 @@ static int fprintf_json(void *out, const char *fmt, ...) return 0; } +static int fprintf_json(void *out, const char *fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = printf_json(out, fmt, ap); + va_end(ap); + + return r; +} + +static int fprintf_json_styled(void *out, + enum disassembler_style style __maybe_unused, + const char *fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = printf_json(out, fmt, ap); + va_end(ap); + + return r; +} + void disasm_print_insn(unsigned char *image, ssize_t len, int opcodes, const char *arch, const char *disassembler_options, const struct btf *btf, @@ -99,11 +123,13 @@ void disasm_print_insn(unsigned char *image, ssize_t len, int opcodes, assert(bfd_check_format(bfdf, bfd_object)); if (json_output) - init_disassemble_info(&info, stdout, - (fprintf_ftype) fprintf_json); + init_disassemble_info_compat(&info, stdout, + (fprintf_ftype) fprintf_json, + fprintf_json_styled); else - init_disassemble_info(&info, stdout, - (fprintf_ftype) fprintf); + init_disassemble_info_compat(&info, stdout, + (fprintf_ftype) fprintf, + fprintf_styled); /* Update architecture info for offload. */ if (arch) { -- 2.7.4 From 78cd8f505b7e8843194214760a579d372d9406b1 Mon Sep 17 00:00:00 2001 From: Seung-Woo Kim Date: Tue, 2 May 2023 18:16:04 +0900 Subject: [PATCH 05/16] packaging: ignore Werror for tools With gcc-12 and new binutils, there are some warnings in tools. Do not cause error for the warnings. Change-Id: If75607ca3acc2104a6a33bbc3d13b02757ddbcf7 Signed-off-by: Seung-Woo Kim --- packaging/linux-rpi4.spec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packaging/linux-rpi4.spec b/packaging/linux-rpi4.spec index 317ae6e..431b126 100644 --- a/packaging/linux-rpi4.spec +++ b/packaging/linux-rpi4.spec @@ -146,8 +146,8 @@ for target in %{variant} %{variant}-rt; do make O=build/${target} %{config_name} %{?_smp_mflags} # Build perf - make O=build/${target} -s -C tools/lib/traceevent %{?_smp_mflags} - make O=build/${target} -s -C tools/perf EXTRA_CFLAGS="-fPIE -rdynamic" %{?_smp_mflags} + WERROR=0 make O=build/${target} -s -C tools/lib/traceevent %{?_smp_mflags} + WERROR=0 make O=build/${target} -s -C tools/perf EXTRA_CFLAGS="-fPIE -rdynamic" %{?_smp_mflags} ;; %{variant}-rt) cat _localversion-rt > localversion-rt @@ -205,7 +205,7 @@ for target in %{variant} %{variant}-rt; do %endif # Install perf - make O=build/${target} -s -C tools/perf EXTRA_CFLAGS="-fPIE -rdynamic" DESTDIR=%{buildroot}/usr install + WERROR=0 make O=build/${target} -s -C tools/perf EXTRA_CFLAGS="-fPIE -rdynamic" DESTDIR=%{buildroot}/usr install rm -rf %{buildroot}/usr/etc rm -rf %{buildroot}/usr/lib/debug rm -rf %{buildroot}/usr/lib/perf -- 2.7.4 From 599bb2c6706b1a8b88a765a09e120d2f00ce210f Mon Sep 17 00:00:00 2001 From: Anuj Jain Date: Tue, 16 May 2023 19:25:19 +0530 Subject: [PATCH 06/16] Revert "Bluetooth: Store advertising handle so it can be re-enabled" This reverts commit 7087c4f69487f017722df7d299ef9b7709996b79. Patch revert is needed as it is the cause for GATT Server TCT fail. Change-Id: I55bedbb6b0d44058c13ac06df121c5d936611c74 Signed-off-by: Anuj Jain --- include/net/bluetooth/hci_core.h | 1 - net/bluetooth/hci_event.c | 45 ++++++++++++++-------------------------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 44bd0b5..f9dcd5d 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -646,7 +646,6 @@ struct hci_conn { __u8 init_addr_type; bdaddr_t resp_addr; __u8 resp_addr_type; - __u8 adv_instance; __u16 handle; __u16 state; __u8 mode; diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 93792f3..0bc65c3 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -2543,20 +2543,19 @@ static void hci_cs_disconnect(struct hci_dev *hdev, u8 status) conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle)); if (conn) { + u8 type = conn->type; + mgmt_disconnect_failed(hdev, &conn->dst, conn->type, conn->dst_type, status); - if (conn->type == LE_LINK) { - hdev->cur_adv_instance = conn->adv_instance; - hci_req_reenable_advertising(hdev); - } - /* If the disconnection failed for any reason, the upper layer * does not retry to disconnect in current implementation. * Hence, we need to do some basic cleanup here and re-enable * advertising if necessary. */ hci_conn_del(conn); + if (type == LE_LINK) + hci_req_reenable_advertising(hdev); } hci_dev_unlock(hdev); @@ -3104,6 +3103,7 @@ static void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) struct hci_conn_params *params; struct hci_conn *conn; bool mgmt_connected; + u8 type; BT_DBG("%s status 0x%2.2x", hdev->name, ev->status); @@ -3158,7 +3158,10 @@ static void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) } } + type = conn->type; + hci_disconn_cfm(conn, ev->reason); + hci_conn_del(conn); /* The suspend notifier is waiting for all devices to disconnect so * clear the bit from pending tasks and inform the wait queue. @@ -3178,12 +3181,8 @@ static void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) * or until a connection is created or until the Advertising * is timed out due to Directed Advertising." */ - if (conn->type == LE_LINK) { - hdev->cur_adv_instance = conn->adv_instance; + if (type == LE_LINK) hci_req_reenable_advertising(hdev); - } - - hci_conn_del(conn); #ifdef TIZEN_BT if (conn->type == ACL_LINK && !hci_conn_num(hdev, ACL_LINK)) { @@ -5604,13 +5603,6 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status, conn->handle = handle; conn->state = BT_CONFIG; - /* Store current advertising instance as connection advertising instance - * when sotfware rotation is in use so it can be re-enabled when - * disconnected. - */ - if (!ext_adv_capable(hdev)) - conn->adv_instance = hdev->cur_adv_instance; - conn->le_conn_interval = interval; conn->le_conn_latency = latency; conn->le_supv_timeout = supervision_timeout; @@ -5694,13 +5686,13 @@ static void hci_le_ext_adv_term_evt(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_evt_le_ext_adv_set_term *ev = (void *) skb->data; struct hci_conn *conn; - struct adv_info *adv; BT_DBG("%s status 0x%2.2x", hdev->name, ev->status); - adv = hci_find_adv_instance(hdev, ev->handle); - if (ev->status) { + struct adv_info *adv; + + adv = hci_find_adv_instance(hdev, ev->handle); if (!adv) return; @@ -5711,15 +5703,9 @@ static void hci_le_ext_adv_term_evt(struct hci_dev *hdev, struct sk_buff *skb) return; } - if (adv) - adv->enabled = false; - conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->conn_handle)); if (conn) { - /* Store handle in the connection so the correct advertising - * instance can be re-enabled when disconnected. - */ - conn->adv_instance = ev->handle; + struct adv_info *adv_instance; if (hdev->adv_addr_type != ADDR_LE_DEV_RANDOM || bacmp(&conn->resp_addr, BDADDR_ANY)) @@ -5730,8 +5716,9 @@ static void hci_le_ext_adv_term_evt(struct hci_dev *hdev, struct sk_buff *skb) return; } - if (adv) - bacpy(&conn->resp_addr, &adv->random_addr); + adv_instance = hci_find_adv_instance(hdev, ev->handle); + if (adv_instance) + bacpy(&conn->resp_addr, &adv_instance->random_addr); } } -- 2.7.4 From 23efcdfbeb1be17807e0d4694a3d8b9787148d55 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Thu, 22 Dec 2022 14:08:35 +0000 Subject: [PATCH 07/16] dtbindings: media: i2c: Add IMX708 CMOS sensor binding Add YAML devicetree binding for IMX708 CMOS image sensor. Let's also add a MAINTAINERS entry for the binding and driver. Signed-off-by: Dave Stevenson [sw0312.kim: cherry-pick raspberry pi kernel upstream to support rpi camera module v3] Signed-off-by: Seung-Woo Kim Change-Id: I32c38670d2bb33336a407b95bbcf20aa967e1a07 --- .../devicetree/bindings/media/i2c/imx708.yaml | 119 +++++++++++++++++++++ MAINTAINERS | 8 ++ 2 files changed, 127 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/imx708.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/imx708.yaml b/Documentation/devicetree/bindings/media/i2c/imx708.yaml new file mode 100644 index 0000000..4b8645f --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/imx708.yaml @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/imx708.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor + +maintainers: + - Naushir Patuck + +description: |- + The Sony IMX708 is a 1/2.3-inch CMOS active pixel digital image sensor + with an active array size of 4608H x 2592V. It is programmable through + I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet. + Image data is sent through MIPI CSI-2, which is configured as either 2 or + 4 data lanes. + +properties: + compatible: + const: sony,imx708 + + reg: + description: I2C device address + maxItems: 1 + + clocks: + maxItems: 1 + + VDIG-supply: + description: + Digital I/O voltage supply, 1.1 volts + + VANA1-supply: + description: + Analog1 voltage supply, 2.8 volts + + VANA2-supply: + description: + Analog2 voltage supply, 1.8 volts + + VDDL-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: |- + Reference to the GPIO connected to the xclr pin, if any. + Must be released (set high) after all supplies and INCK are applied. + + # See ../video-interfaces.txt for more details + port: + type: object + properties: + endpoint: + type: object + properties: + data-lanes: + description: |- + The sensor supports either two-lane, or four-lane operation. + For two-lane operation the property must be set to <1 2>. + items: + - const: 1 + - const: 2 + + clock-noncontinuous: + type: boolean + description: |- + MIPI CSI-2 clock is non-continuous if this property is present, + otherwise it's continuous. + + link-frequencies: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint64-array + description: + Allowed data bus frequencies. + + required: + - link-frequencies + +required: + - compatible + - reg + - clocks + - VANA1-supply + - VANA2-supply + - VDIG-supply + - VDDL-supply + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + imx708: sensor@1a { + compatible = "sony,imx708"; + reg = <0x1a>; + clocks = <&imx708_clk>; + VANA1-supply = <&imx708_vana1>; /* 1.8v */ + VANA2-supply = <&imx708_vana2>; /* 2.8v */ + VDIG-supply = <&imx708_vdig>; /* 1.1v */ + VDDL-supply = <&imx708_vddl>; /* 1.8v */ + + port { + imx708_0: endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <450000000>; + }; + }; + }; + }; + +... diff --git a/MAINTAINERS b/MAINTAINERS index 3501023..ba3f184 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17584,6 +17584,14 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/imx519.yaml F: drivers/media/i2c/imx519.c +SONY IMX708 SENSOR DRIVER +M: Raspberry Pi Kernel Maintenance +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/imx708.yaml +F: drivers/media/i2c/imx708.c + SONY MEMORYSTICK SUBSYSTEM M: Maxim Levitsky M: Alex Dubov -- 2.7.4 From ea5095b0013ce1daad4aabc202b8c2c9961be8dd Mon Sep 17 00:00:00 2001 From: Nick Hollinghurst Date: Thu, 22 Dec 2022 13:59:33 +0000 Subject: [PATCH 08/16] media/i2c: Add a driver for the Sony IMX708 image sensor The imx708 is a 12MP MIPI sensor with a 16:9 aspect ratio, here using two CSI-2 lanes. It is a "quad Bayer" sensor with all 3 modes offering 10-bit output: 12MP: 4608x2592 up to 14.35fps (full FoV) 1080p: 2304x1296 up to 56.02fps (full FoV) 720p: 1536x864 up to 120.12fps (cropped) This imx708 sensor driver is based heavily on the imx477 driver and has been tested on the Raspberry Pi platform using libcamera. Signed-off-by: Nick Hollinghurst Signed-off-by: Dave Stevenson [sw0312.kim: cherry-pick raspberry pi kernel upstream to support rpi camera module v3] Signed-off-by: Seung-Woo Kim Change-Id: I36398cb03c4843c4569ea11526980a10e064bf7d --- drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/imx708.c | 1989 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2001 insertions(+) create mode 100644 drivers/media/i2c/imx708.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index d14dd06..5e8e94e 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -939,6 +939,17 @@ config VIDEO_IMX519 To compile this driver as a module, choose M here: the module will be called IMX519. +config VIDEO_IMX708 + tristate "Sony IMX708 sensor support" + depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_CAMERA_SUPPORT + help + This is a Video4Linux2 sensor driver for the Sony + IMX708 camera. + + To compile this driver as a module, choose M here: the + module will be called imx708. + config VIDEO_OV02A10 tristate "OmniVision OV02A10 sensor support" depends on VIDEO_V4L2 && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 253f330..a6dc549 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -137,6 +137,7 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o obj-$(CONFIG_VIDEO_IMX355) += imx355.o obj-$(CONFIG_VIDEO_IMX412) += imx412.o obj-$(CONFIG_VIDEO_IMX519) += imx519.o +obj-$(CONFIG_VIDEO_IMX708) += imx708.o obj-$(CONFIG_VIDEO_MAX9286) += max9286.o obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o obj-$(CONFIG_VIDEO_RDACM20) += rdacm20.o diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c new file mode 100644 index 0000000..ad4eeb9 --- /dev/null +++ b/drivers/media/i2c/imx708.c @@ -0,0 +1,1989 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX708 cameras. + * Copyright (C) 2022, Raspberry Pi Ltd + * + * Based on Sony imx477 camera driver + * Copyright (C) 2020 Raspberry Pi Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMX708_REG_VALUE_08BIT 1 +#define IMX708_REG_VALUE_16BIT 2 + +/* Chip ID */ +#define IMX708_REG_CHIP_ID 0x0016 +#define IMX708_CHIP_ID 0x0708 + +#define IMX708_REG_MODE_SELECT 0x0100 +#define IMX708_MODE_STANDBY 0x00 +#define IMX708_MODE_STREAMING 0x01 + +#define IMX708_REG_ORIENTATION 0x101 + +#define IMX708_XCLK_FREQ 24000000 + +#define IMX708_DEFAULT_LINK_FREQ 450000000 + +/* Default initial pixel rate, will get updated for each mode. */ +#define IMX708_INITIAL_PIXEL_RATE 590000000 + +/* V_TIMING internal */ +#define IMX708_REG_FRAME_LENGTH 0x0340 +#define IMX708_FRAME_LENGTH_MAX 0xffff + +/* Exposure control */ +#define IMX708_REG_EXPOSURE 0x0202 +#define IMX708_EXPOSURE_OFFSET 48 +#define IMX708_EXPOSURE_DEFAULT 0x640 +#define IMX708_EXPOSURE_STEP 1 +#define IMX708_EXPOSURE_MIN 1 +#define IMX708_EXPOSURE_MAX (IMX708_FRAME_LENGTH_MAX - \ + IMX708_EXPOSURE_OFFSET) + +/* Analog gain control */ +#define IMX708_REG_ANALOG_GAIN 0x0204 +#define IMX708_ANA_GAIN_MIN 112 +#define IMX708_ANA_GAIN_MAX 960 +#define IMX708_ANA_GAIN_STEP 1 +#define IMX708_ANA_GAIN_DEFAULT IMX708_ANA_GAIN_MIN + +/* Digital gain control */ +#define IMX708_REG_DIGITAL_GAIN 0x020e +#define IMX708_DGTL_GAIN_MIN 0x0100 +#define IMX708_DGTL_GAIN_MAX 0xffff +#define IMX708_DGTL_GAIN_DEFAULT 0x0100 +#define IMX708_DGTL_GAIN_STEP 1 + +/* Colour balance controls */ +#define IMX708_REG_COLOUR_BALANCE_RED 0x0b90 +#define IMX708_REG_COLOUR_BALANCE_BLUE 0x0b92 +#define IMX708_COLOUR_BALANCE_MIN 0x01 +#define IMX708_COLOUR_BALANCE_MAX 0xffff +#define IMX708_COLOUR_BALANCE_STEP 0x01 +#define IMX708_COLOUR_BALANCE_DEFAULT 0x100 + +/* Test Pattern Control */ +#define IMX708_REG_TEST_PATTERN 0x0600 +#define IMX708_TEST_PATTERN_DISABLE 0 +#define IMX708_TEST_PATTERN_SOLID_COLOR 1 +#define IMX708_TEST_PATTERN_COLOR_BARS 2 +#define IMX708_TEST_PATTERN_GREY_COLOR 3 +#define IMX708_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define IMX708_REG_TEST_PATTERN_R 0x0602 +#define IMX708_REG_TEST_PATTERN_GR 0x0604 +#define IMX708_REG_TEST_PATTERN_B 0x0606 +#define IMX708_REG_TEST_PATTERN_GB 0x0608 +#define IMX708_TEST_PATTERN_COLOUR_MIN 0 +#define IMX708_TEST_PATTERN_COLOUR_MAX 0x0fff +#define IMX708_TEST_PATTERN_COLOUR_STEP 1 + +#define IMX708_REG_BASE_SPC_GAINS_L 0x7b10 +#define IMX708_REG_BASE_SPC_GAINS_R 0x7c00 + +/* HDR exposure ratio (long:med == med:short) */ +#define IMX708_HDR_EXPOSURE_RATIO 4 +#define IMX708_REG_MID_EXPOSURE 0x3116 +#define IMX708_REG_SHT_EXPOSURE 0x0224 +#define IMX708_REG_MID_ANALOG_GAIN 0x3118 +#define IMX708_REG_SHT_ANALOG_GAIN 0x0216 + +/* + * Metadata buffer holds a variety of data, all sent with the same VC/DT (0x12). + * It comprises two scanlines (of up to 5760 bytes each, for 4608 pixels) + * of embedded data, one line of PDAF data, and two lines of AE-HIST data + * (AE histograms are valid for HDR mode and empty in non-HDR modes). + */ +#define IMX708_EMBEDDED_LINE_WIDTH (5 * 5760) +#define IMX708_NUM_EMBEDDED_LINES 1 + +enum pad_types { + IMAGE_PAD, + METADATA_PAD, + NUM_PADS +}; + +/* IMX708 native and active pixel array size. */ +#define IMX708_NATIVE_WIDTH 4640U +#define IMX708_NATIVE_HEIGHT 2658U +#define IMX708_PIXEL_ARRAY_LEFT 16U +#define IMX708_PIXEL_ARRAY_TOP 24U +#define IMX708_PIXEL_ARRAY_WIDTH 4608U +#define IMX708_PIXEL_ARRAY_HEIGHT 2592U + +struct imx708_reg { + u16 address; + u8 val; +}; + +struct imx708_reg_list { + unsigned int num_of_regs; + const struct imx708_reg *regs; +}; + +/* Mode : resolution and related config&values */ +struct imx708_mode { + /* Frame width */ + unsigned int width; + + /* Frame height */ + unsigned int height; + + /* H-timing in pixels */ + unsigned int line_length_pix; + + /* Analog crop rectangle. */ + struct v4l2_rect crop; + + /* Highest possible framerate. */ + unsigned int vblank_min; + + /* Default framerate. */ + unsigned int vblank_default; + + /* Default register values */ + struct imx708_reg_list reg_list; + + /* Not all modes have the same pixel rate. */ + u64 pixel_rate; + + /* Not all modes have the same minimum exposure. */ + u32 exposure_lines_min; + + /* Not all modes have the same exposure lines step. */ + u32 exposure_lines_step; + + /* HDR flag, currently not used at runtime */ + bool hdr; +}; + +/* Default PDAF pixel correction gains */ +static const u8 pdaf_gains[2][9] = { + { 0x4c, 0x4c, 0x4c, 0x46, 0x3e, 0x38, 0x35, 0x35, 0x35 }, + { 0x35, 0x35, 0x35, 0x38, 0x3e, 0x46, 0x4c, 0x4c, 0x4c } +}; + +static const struct imx708_reg mode_common_regs[] = { + {0x0100, 0x00}, + {0x0136, 0x18}, + {0x0137, 0x00}, + {0x33F0, 0x02}, + {0x33F1, 0x05}, + {0x3062, 0x00}, + {0x3063, 0x12}, + {0x3068, 0x00}, + {0x3069, 0x12}, + {0x306A, 0x00}, + {0x306B, 0x30}, + {0x3076, 0x00}, + {0x3077, 0x30}, + {0x3078, 0x00}, + {0x3079, 0x30}, + {0x5E54, 0x0C}, + {0x6E44, 0x00}, + {0xB0B6, 0x01}, + {0xE829, 0x00}, + {0xF001, 0x08}, + {0xF003, 0x08}, + {0xF00D, 0x10}, + {0xF00F, 0x10}, + {0xF031, 0x08}, + {0xF033, 0x08}, + {0xF03D, 0x10}, + {0xF03F, 0x10}, + {0x0112, 0x0A}, + {0x0113, 0x0A}, + {0x0114, 0x01}, + {0x0B8E, 0x01}, + {0x0B8F, 0x00}, + {0x0B94, 0x01}, + {0x0B95, 0x00}, + {0x3400, 0x01}, + {0x3478, 0x01}, + {0x3479, 0x1c}, + {0x3091, 0x01}, + {0x3092, 0x00}, + {0x3419, 0x00}, + {0xBCF1, 0x02}, + {0x3094, 0x01}, + {0x3095, 0x01}, + {0x3362, 0x00}, + {0x3363, 0x00}, + {0x3364, 0x00}, + {0x3365, 0x00}, + {0x0138, 0x01}, +}; + +/* 10-bit. */ +static const struct imx708_reg mode_4608x2592_regs[] = { + {0x0342, 0x3D}, + {0x0343, 0x20}, + {0x0340, 0x0A}, + {0x0341, 0x59}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x11}, + {0x0349, 0xFF}, + {0x034A, 0X0A}, + {0x034B, 0x1F}, + {0x0220, 0x62}, + {0x0222, 0x01}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x0A}, + {0x3200, 0x01}, + {0x3201, 0x01}, + {0x32D5, 0x01}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x00}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x12}, + {0x040D, 0x00}, + {0x040E, 0x0A}, + {0x040F, 0x20}, + {0x034C, 0x12}, + {0x034D, 0x00}, + {0x034E, 0x0A}, + {0x034F, 0x20}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0x7C}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x030E, 0x01}, + {0x030F, 0x2C}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x64}, + {0x3CA4, 0x00}, + {0x3CA5, 0x00}, + {0x3CA6, 0x00}, + {0x3CA7, 0x00}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x08}, + {0x3CBA, 0x00}, + {0x3CBB, 0x00}, + {0x3CBC, 0x00}, + {0x3CBD, 0x3C}, + {0x3CBE, 0x00}, + {0x3CBF, 0x00}, + {0x0202, 0x0A}, + {0x0203, 0x29}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + {0x3116, 0x01}, + {0x3117, 0xF4}, + {0x0204, 0x00}, + {0x0205, 0x00}, + {0x0216, 0x00}, + {0x0217, 0x00}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x00}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x01}, + {0x341f, 0x20}, + {0x3420, 0x00}, + {0x3421, 0xd8}, + {0xC428, 0x00}, + {0xC429, 0x04}, + {0x3366, 0x00}, + {0x3367, 0x00}, + {0x3368, 0x00}, + {0x3369, 0x00}, +}; + +static const struct imx708_reg mode_2x2binned_regs[] = { + {0x0342, 0x1E}, + {0x0343, 0x90}, + {0x0340, 0x05}, + {0x0341, 0x38}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x11}, + {0x0349, 0xFF}, + {0x034A, 0X0A}, + {0x034B, 0x1F}, + {0x0220, 0x62}, + {0x0222, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x08}, + {0x3200, 0x41}, + {0x3201, 0x41}, + {0x32D5, 0x00}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x00}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x09}, + {0x040D, 0x00}, + {0x040E, 0x05}, + {0x040F, 0x10}, + {0x034C, 0x09}, + {0x034D, 0x00}, + {0x034E, 0x05}, + {0x034F, 0x10}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0x7A}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x030E, 0x01}, + {0x030F, 0x2C}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x3C}, + {0x3CA4, 0x00}, + {0x3CA5, 0x3C}, + {0x3CA6, 0x00}, + {0x3CA7, 0x00}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x1C}, + {0x3CBA, 0x00}, + {0x3CBB, 0x08}, + {0x3CBC, 0x00}, + {0x3CBD, 0x1E}, + {0x3CBE, 0x00}, + {0x3CBF, 0x0A}, + {0x0202, 0x05}, + {0x0203, 0x08}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + {0x3116, 0x01}, + {0x3117, 0xF4}, + {0x0204, 0x00}, + {0x0205, 0x70}, + {0x0216, 0x00}, + {0x0217, 0x70}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x70}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x90}, + {0x3420, 0x00}, + {0x3421, 0x6c}, + {0x3366, 0x00}, + {0x3367, 0x00}, + {0x3368, 0x00}, + {0x3369, 0x00}, +}; + +static const struct imx708_reg mode_2x2binned_720p_regs[] = { + {0x0342, 0x14}, + {0x0343, 0x60}, + {0x0340, 0x04}, + {0x0341, 0xB6}, + {0x0344, 0x03}, + {0x0345, 0x00}, + {0x0346, 0x01}, + {0x0347, 0xB0}, + {0x0348, 0x0E}, + {0x0349, 0xFF}, + {0x034A, 0x08}, + {0x034B, 0x6F}, + {0x0220, 0x62}, + {0x0222, 0x01}, + {0x0900, 0x01}, + {0x0901, 0x22}, + {0x0902, 0x08}, + {0x3200, 0x41}, + {0x3201, 0x41}, + {0x32D5, 0x00}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x01}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x06}, + {0x040D, 0x00}, + {0x040E, 0x03}, + {0x040F, 0x60}, + {0x034C, 0x06}, + {0x034D, 0x00}, + {0x034E, 0x03}, + {0x034F, 0x60}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0x76}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x030E, 0x01}, + {0x030F, 0x2C}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x3C}, + {0x3CA4, 0x01}, + {0x3CA5, 0x5E}, + {0x3CA6, 0x00}, + {0x3CA7, 0x00}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x0C}, + {0x3CBA, 0x00}, + {0x3CBB, 0x04}, + {0x3CBC, 0x00}, + {0x3CBD, 0x1E}, + {0x3CBE, 0x00}, + {0x3CBF, 0x05}, + {0x0202, 0x04}, + {0x0203, 0x86}, + {0x0224, 0x01}, + {0x0225, 0xF4}, + {0x3116, 0x01}, + {0x3117, 0xF4}, + {0x0204, 0x00}, + {0x0205, 0x70}, + {0x0216, 0x00}, + {0x0217, 0x70}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x70}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x60}, + {0x3420, 0x00}, + {0x3421, 0x48}, + {0x3366, 0x00}, + {0x3367, 0x00}, + {0x3368, 0x00}, + {0x3369, 0x00}, +}; + +static const struct imx708_reg mode_hdr_regs[] = { + {0x0342, 0x14}, + {0x0343, 0x60}, + {0x0340, 0x0A}, + {0x0341, 0x5B}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x0348, 0x11}, + {0x0349, 0xFF}, + {0x034A, 0X0A}, + {0x034B, 0x1F}, + {0x0220, 0x01}, + {0x0222, IMX708_HDR_EXPOSURE_RATIO}, + {0x0900, 0x00}, + {0x0901, 0x11}, + {0x0902, 0x0A}, + {0x3200, 0x01}, + {0x3201, 0x01}, + {0x32D5, 0x00}, + {0x32D6, 0x00}, + {0x32DB, 0x01}, + {0x32DF, 0x00}, + {0x350C, 0x00}, + {0x350D, 0x00}, + {0x0408, 0x00}, + {0x0409, 0x00}, + {0x040A, 0x00}, + {0x040B, 0x00}, + {0x040C, 0x09}, + {0x040D, 0x00}, + {0x040E, 0x05}, + {0x040F, 0x10}, + {0x034C, 0x09}, + {0x034D, 0x00}, + {0x034E, 0x05}, + {0x034F, 0x10}, + {0x0301, 0x05}, + {0x0303, 0x02}, + {0x0305, 0x02}, + {0x0306, 0x00}, + {0x0307, 0xA2}, + {0x030B, 0x02}, + {0x030D, 0x04}, + {0x030E, 0x01}, + {0x030F, 0x2C}, + {0x0310, 0x01}, + {0x3CA0, 0x00}, + {0x3CA1, 0x00}, + {0x3CA4, 0x00}, + {0x3CA5, 0x00}, + {0x3CA6, 0x00}, + {0x3CA7, 0x28}, + {0x3CAA, 0x00}, + {0x3CAB, 0x00}, + {0x3CB8, 0x00}, + {0x3CB9, 0x30}, + {0x3CBA, 0x00}, + {0x3CBB, 0x00}, + {0x3CBC, 0x00}, + {0x3CBD, 0x32}, + {0x3CBE, 0x00}, + {0x3CBF, 0x00}, + {0x0202, 0x0A}, + {0x0203, 0x2B}, + {0x0224, 0x0A}, + {0x0225, 0x2B}, + {0x3116, 0x0A}, + {0x3117, 0x2B}, + {0x0204, 0x00}, + {0x0205, 0x00}, + {0x0216, 0x00}, + {0x0217, 0x00}, + {0x0218, 0x01}, + {0x0219, 0x00}, + {0x020E, 0x01}, + {0x020F, 0x00}, + {0x3118, 0x00}, + {0x3119, 0x00}, + {0x311A, 0x01}, + {0x311B, 0x00}, + {0x341a, 0x00}, + {0x341b, 0x00}, + {0x341c, 0x00}, + {0x341d, 0x00}, + {0x341e, 0x00}, + {0x341f, 0x90}, + {0x3420, 0x00}, + {0x3421, 0x6c}, + {0x3360, 0x01}, + {0x3361, 0x01}, + {0x3366, 0x09}, + {0x3367, 0x00}, + {0x3368, 0x05}, + {0x3369, 0x10}, +}; + +/* Mode configs. Keep separate lists for when HDR is enabled or not. */ +static const struct imx708_mode supported_modes_10bit_no_hdr[] = { + { + /* Full resolution. */ + .width = 4608, + .height = 2592, + .line_length_pix = 0x3d20, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 58, + .vblank_default = 58, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_4608x2592_regs), + .regs = mode_4608x2592_regs, + }, + .pixel_rate = 595200000, + .exposure_lines_min = 8, + .exposure_lines_step = 1, + .hdr = false + }, + { + /* regular 2x2 binned. */ + .width = 2304, + .height = 1296, + .line_length_pix = 0x1e90, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 40, + .vblank_default = 1198, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2x2binned_regs), + .regs = mode_2x2binned_regs, + }, + .pixel_rate = 585600000, + .exposure_lines_min = 4, + .exposure_lines_step = 2, + .hdr = false + }, + { + /* 2x2 binned and cropped for 720p. */ + .width = 1536, + .height = 864, + .line_length_pix = 0x1460, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 40, + .vblank_default = 2755, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_2x2binned_720p_regs), + .regs = mode_2x2binned_720p_regs, + }, + .pixel_rate = 566400000, + .exposure_lines_min = 4, + .exposure_lines_step = 2, + .hdr = false + }, +}; + +static const struct imx708_mode supported_modes_10bit_hdr[] = { + { + /* There's only one HDR mode, which is 2x2 downscaled */ + .width = 2304, + .height = 1296, + .line_length_pix = 0x1460, + .crop = { + .left = IMX708_PIXEL_ARRAY_LEFT, + .top = IMX708_PIXEL_ARRAY_TOP, + .width = 4608, + .height = 2592, + }, + .vblank_min = 3673, + .vblank_default = 3673, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_hdr_regs), + .regs = mode_hdr_regs, + }, + .pixel_rate = 777600000, + .exposure_lines_min = 8 * IMX708_HDR_EXPOSURE_RATIO * IMX708_HDR_EXPOSURE_RATIO, + .exposure_lines_step = 2 * IMX708_HDR_EXPOSURE_RATIO * IMX708_HDR_EXPOSURE_RATIO, + .hdr = true + } +}; + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static const char * const imx708_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9" +}; + +static const int imx708_test_pattern_val[] = { + IMX708_TEST_PATTERN_DISABLE, + IMX708_TEST_PATTERN_COLOR_BARS, + IMX708_TEST_PATTERN_SOLID_COLOR, + IMX708_TEST_PATTERN_GREY_COLOR, + IMX708_TEST_PATTERN_PN9, +}; + +/* regulator supplies */ +static const char * const imx708_supply_name[] = { + /* Supplies can be enabled in any order */ + "VANA1", /* Analog1 (2.8V) supply */ + "VANA2", /* Analog2 (1.8V) supply */ + "VDIG", /* Digital Core (1.1V) supply */ + "VDDL", /* IF (1.8V) supply */ +}; + +#define IMX708_NUM_SUPPLIES ARRAY_SIZE(imx708_supply_name) + +/* + * Initialisation delay between XCLR low->high and the moment when the sensor + * can start capture (i.e. can leave software standby), given by T7 in the + * datasheet is 8ms. This does include I2C setup time as well. + * + * Note, that delay between XCLR low->high and reading the CCI ID register (T6 + * in the datasheet) is much smaller - 600us. + */ +#define IMX708_XCLR_MIN_DELAY_US 8000 +#define IMX708_XCLR_DELAY_RANGE_US 1000 + +struct imx708 { + struct v4l2_subdev sd; + struct media_pad pad[NUM_PADS]; + + struct v4l2_mbus_framefmt fmt; + + struct clk *xclk; + u32 xclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[IMX708_NUM_SUPPLIES]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *red_balance; + struct v4l2_ctrl *blue_balance; + struct v4l2_ctrl *notify_gains; + struct v4l2_ctrl *hdr_mode; + + /* Current mode */ + const struct imx708_mode *mode; + + /* + * Mutex for serialized access: + * Protect sensor module set pad format and start/stop streaming safely. + */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + /* Rewrite common registers on stream on? */ + bool common_regs_written; +}; + +static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd) +{ + return container_of(_sd, struct imx708, sd); +} + +static inline void get_mode_table(unsigned int code, + const struct imx708_mode **mode_list, + unsigned int *num_modes, + bool hdr_enable) +{ + switch (code) { + /* 10-bit */ + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + if (hdr_enable) { + *mode_list = supported_modes_10bit_hdr; + *num_modes = ARRAY_SIZE(supported_modes_10bit_hdr); + } else { + *mode_list = supported_modes_10bit_no_hdr; + *num_modes = ARRAY_SIZE(supported_modes_10bit_no_hdr); + } + break; + default: + *mode_list = NULL; + *num_modes = 0; + } +} + +/* Read registers up to 2 at a time */ +static int imx708_read_reg(struct imx708 *imx708, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2] = { reg >> 8, reg & 0xff }; + u8 data_buf[4] = { 0, }; + int ret; + + if (len > 4) + return -EINVAL; + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +/* Write registers up to 2 at a time */ +static int imx708_write_reg(struct imx708 *imx708, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << (8 * (4 - len)), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int imx708_write_regs(struct imx708 *imx708, + const struct imx708_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = imx708_write_reg(imx708, regs[i].address, 1, regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +/* Get bayer order based on flip setting. */ +static u32 imx708_get_format_code(struct imx708 *imx708) +{ + unsigned int i; + + lockdep_assert_held(&imx708->mutex); + + i = (imx708->vflip->val ? 2 : 0) | + (imx708->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx708_set_default_format(struct imx708 *imx708) +{ + struct v4l2_mbus_framefmt *fmt = &imx708->fmt; + + /* Set default mode to max resolution */ + imx708->mode = &supported_modes_10bit_no_hdr[0]; + + /* fmt->code not set as it will always be computed based on flips */ + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + fmt->width = imx708->mode->width; + fmt->height = imx708->mode->height; + fmt->field = V4L2_FIELD_NONE; +} + +static int imx708_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct imx708 *imx708 = to_imx708(sd); + struct v4l2_mbus_framefmt *try_fmt_img = + v4l2_subdev_get_try_format(sd, fh->state, IMAGE_PAD); + struct v4l2_mbus_framefmt *try_fmt_meta = + v4l2_subdev_get_try_format(sd, fh->state, METADATA_PAD); + struct v4l2_rect *try_crop; + + mutex_lock(&imx708->mutex); + + /* Initialize try_fmt for the image pad */ + if (imx708->hdr_mode->val) { + try_fmt_img->width = supported_modes_10bit_hdr[0].width; + try_fmt_img->height = supported_modes_10bit_hdr[0].height; + } else { + try_fmt_img->width = supported_modes_10bit_no_hdr[0].width; + try_fmt_img->height = supported_modes_10bit_no_hdr[0].height; + } + try_fmt_img->code = imx708_get_format_code(imx708); + try_fmt_img->field = V4L2_FIELD_NONE; + + /* Initialize try_fmt for the embedded metadata pad */ + try_fmt_meta->width = IMX708_EMBEDDED_LINE_WIDTH; + try_fmt_meta->height = IMX708_NUM_EMBEDDED_LINES; + try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA; + try_fmt_meta->field = V4L2_FIELD_NONE; + + /* Initialize try_crop */ + try_crop = v4l2_subdev_get_try_crop(sd, fh->state, IMAGE_PAD); + try_crop->left = IMX708_PIXEL_ARRAY_LEFT; + try_crop->top = IMX708_PIXEL_ARRAY_TOP; + try_crop->width = IMX708_PIXEL_ARRAY_WIDTH; + try_crop->height = IMX708_PIXEL_ARRAY_HEIGHT; + + mutex_unlock(&imx708->mutex); + + return 0; +} + +static int imx708_set_exposure(struct imx708 *imx708, unsigned int val) +{ + int ret; + + val = max(val, imx708->mode->exposure_lines_min); + val -= val % imx708->mode->exposure_lines_step; + + /* + * In HDR mode this will set the longest exposure. The sensor + * will automatically divide the medium and short ones by 4,16. + */ + ret = imx708_write_reg(imx708, IMX708_REG_EXPOSURE, + IMX708_REG_VALUE_16BIT, val); + + return ret; +} + +static void imx708_adjust_exposure_range(struct imx708 *imx708, + struct v4l2_ctrl *ctrl) +{ + int exposure_max, exposure_def; + + /* Honour the VBLANK limits when setting exposure. */ + exposure_max = imx708->mode->height + imx708->vblank->val - + IMX708_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, imx708->exposure->val); + __v4l2_ctrl_modify_range(imx708->exposure, imx708->exposure->minimum, + exposure_max, imx708->exposure->step, + exposure_def); +} + +static int imx708_set_analogue_gain(struct imx708 *imx708, unsigned int val) +{ + int ret; + + /* + * In HDR mode this will set the gain for the longest exposure, + * and by default the sensor uses the same gain for all of them. + */ + ret = imx708_write_reg(imx708, IMX708_REG_ANALOG_GAIN, + IMX708_REG_VALUE_16BIT, val); + + return ret; +} + +static void imx708_set_framing_limits(struct imx708 *imx708) +{ + unsigned int hblank; + const struct imx708_mode *mode = imx708->mode; + + __v4l2_ctrl_modify_range(imx708->pixel_rate, + mode->pixel_rate, mode->pixel_rate, + 1, mode->pixel_rate); + + /* Update limits and set FPS to default */ + __v4l2_ctrl_modify_range(imx708->vblank, mode->vblank_min, + IMX708_FRAME_LENGTH_MAX - mode->height, + 1, mode->vblank_default); + + /* Setting this will adjust the exposure limits as well. */ + __v4l2_ctrl_s_ctrl(imx708->vblank, mode->vblank_default); + + /* + * Currently PPL is fixed to the mode specified value, so hblank + * depends on mode->width only, and is not changeable in any + * way other than changing the mode. + */ + hblank = mode->line_length_pix - mode->width; + __v4l2_ctrl_modify_range(imx708->hblank, hblank, hblank, 1, hblank); +} + +static int imx708_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx708 *imx708 = + container_of(ctrl->handler, struct imx708, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + const struct imx708_mode *mode_list; + unsigned int code, num_modes; + int ret = 0; + + /* + * The VBLANK control may change the limits of usable exposure, so check + * and adjust if necessary. + */ + if (ctrl->id == V4L2_CID_VBLANK) + imx708_adjust_exposure_range(imx708, ctrl); + + /* + * Applying V4L2 control value only happens + * when power is up for streaming + */ + if (pm_runtime_get_if_in_use(&client->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + imx708_set_analogue_gain(imx708, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = imx708_set_exposure(imx708, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = imx708_write_reg(imx708, IMX708_REG_DIGITAL_GAIN, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN, + IMX708_REG_VALUE_16BIT, + imx708_test_pattern_val[ctrl->val]); + break; + case V4L2_CID_TEST_PATTERN_RED: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_R, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GR, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_B, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GB, + IMX708_REG_VALUE_16BIT, ctrl->val); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + ret = imx708_write_reg(imx708, IMX708_REG_ORIENTATION, 1, + imx708->hflip->val | + imx708->vflip->val << 1); + break; + case V4L2_CID_VBLANK: + ret = imx708_write_reg(imx708, IMX708_REG_FRAME_LENGTH, + IMX708_REG_VALUE_16BIT, + imx708->mode->height + ctrl->val); + break; + case V4L2_CID_NOTIFY_GAINS: + ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_BLUE, + IMX708_REG_VALUE_16BIT, + imx708->notify_gains->p_new.p_u32[0]); + if (ret) + break; + ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_RED, + IMX708_REG_VALUE_16BIT, + imx708->notify_gains->p_new.p_u32[3]); + break; + case V4L2_CID_WIDE_DYNAMIC_RANGE: + code = imx708_get_format_code(imx708); + get_mode_table(code, &mode_list, &num_modes, ctrl->val); + imx708->mode = v4l2_find_nearest_size(mode_list, + num_modes, + width, height, + imx708->mode->width, + imx708->mode->height); + imx708_set_framing_limits(imx708); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx708_ctrl_ops = { + .s_ctrl = imx708_set_ctrl, +}; + +static int imx708_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (code->pad >= NUM_PADS) + return -EINVAL; + + if (code->pad == IMAGE_PAD) { + if (code->index >= (ARRAY_SIZE(codes) / 4)) + return -EINVAL; + + code->code = imx708_get_format_code(imx708); + } else { + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SENSOR_DATA; + } + + return 0; +} + +static int imx708_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (fse->pad >= NUM_PADS) + return -EINVAL; + + if (fse->pad == IMAGE_PAD) { + const struct imx708_mode *mode_list; + unsigned int num_modes; + + get_mode_table(fse->code, &mode_list, &num_modes, + imx708->hdr_mode->val); + + if (fse->index >= num_modes) + return -EINVAL; + + if (fse->code != imx708_get_format_code(imx708)) + return -EINVAL; + + fse->min_width = mode_list[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = mode_list[fse->index].height; + fse->max_height = fse->min_height; + } else { + if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0) + return -EINVAL; + + fse->min_width = IMX708_EMBEDDED_LINE_WIDTH; + fse->max_width = fse->min_width; + fse->min_height = IMX708_NUM_EMBEDDED_LINES; + fse->max_height = fse->min_height; + } + + return 0; +} + +static void imx708_reset_colorspace(struct v4l2_mbus_framefmt *fmt) +{ + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); +} + +static void imx708_update_image_pad_format(struct imx708 *imx708, + const struct imx708_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + imx708_reset_colorspace(&fmt->format); +} + +static void imx708_update_metadata_pad_format(struct v4l2_subdev_format *fmt) +{ + fmt->format.width = IMX708_EMBEDDED_LINE_WIDTH; + fmt->format.height = IMX708_NUM_EMBEDDED_LINES; + fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int imx708_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx708->mutex); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(&imx708->sd, sd_state, + fmt->pad); + /* update the code which could change due to vflip or hflip */ + try_fmt->code = fmt->pad == IMAGE_PAD ? + imx708_get_format_code(imx708) : + MEDIA_BUS_FMT_SENSOR_DATA; + fmt->format = *try_fmt; + } else { + if (fmt->pad == IMAGE_PAD) { + imx708_update_image_pad_format(imx708, imx708->mode, + fmt); + fmt->format.code = imx708_get_format_code(imx708); + } else { + imx708_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&imx708->mutex); + return 0; +} + +static int imx708_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + const struct imx708_mode *mode; + struct imx708 *imx708 = to_imx708(sd); + + if (fmt->pad >= NUM_PADS) + return -EINVAL; + + mutex_lock(&imx708->mutex); + + if (fmt->pad == IMAGE_PAD) { + const struct imx708_mode *mode_list; + unsigned int num_modes; + + /* Bayer order varies with flips */ + fmt->format.code = imx708_get_format_code(imx708); + + get_mode_table(fmt->format.code, &mode_list, &num_modes, + imx708->hdr_mode->val); + + mode = v4l2_find_nearest_size(mode_list, + num_modes, + width, height, + fmt->format.width, + fmt->format.height); + imx708_update_image_pad_format(imx708, mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + imx708->mode = mode; + imx708_set_framing_limits(imx708); + } + } else { + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, + fmt->pad); + *framefmt = fmt->format; + } else { + /* Only one embedded data mode is supported */ + imx708_update_metadata_pad_format(fmt); + } + } + + mutex_unlock(&imx708->mutex); + + return 0; +} + +static const struct v4l2_rect * +__imx708_get_pad_crop(struct imx708 *imx708, struct v4l2_subdev_state *sd_state, + unsigned int pad, enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_crop(&imx708->sd, sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &imx708->mode->crop; + } + + return NULL; +} + +static int imx708_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct imx708 *imx708 = to_imx708(sd); + + mutex_lock(&imx708->mutex); + sel->r = *__imx708_get_pad_crop(imx708, sd_state, sel->pad, + sel->which); + mutex_unlock(&imx708->mutex); + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX708_NATIVE_WIDTH; + sel->r.height = IMX708_NATIVE_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = IMX708_PIXEL_ARRAY_LEFT; + sel->r.top = IMX708_PIXEL_ARRAY_TOP; + sel->r.width = IMX708_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX708_PIXEL_ARRAY_HEIGHT; + + return 0; + } + + return -EINVAL; +} + +/* Start streaming */ +static int imx708_start_streaming(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + const struct imx708_reg_list *reg_list; + int i, ret; + u32 val; + + if (!imx708->common_regs_written) { + ret = imx708_write_regs(imx708, mode_common_regs, + ARRAY_SIZE(mode_common_regs)); + if (ret) { + dev_err(&client->dev, "%s failed to set common settings\n", + __func__); + return ret; + } + + ret = imx708_read_reg(imx708, IMX708_REG_BASE_SPC_GAINS_L, + IMX708_REG_VALUE_08BIT, &val); + if (ret == 0 && val == 0x40) { + for (i = 0; i < 54 && ret == 0; i++) { + ret = imx708_write_reg(imx708, + IMX708_REG_BASE_SPC_GAINS_L + i, + IMX708_REG_VALUE_08BIT, + pdaf_gains[0][i % 9]); + } + for (i = 0; i < 54 && ret == 0; i++) { + ret = imx708_write_reg(imx708, + IMX708_REG_BASE_SPC_GAINS_R + i, + IMX708_REG_VALUE_08BIT, + pdaf_gains[1][i % 9]); + } + } + if (ret) { + dev_err(&client->dev, "%s failed to set PDAF gains\n", + __func__); + return ret; + } + + imx708->common_regs_written = true; + } + + /* Apply default values of current mode */ + reg_list = &imx708->mode->reg_list; + ret = imx708_write_regs(imx708, reg_list->regs, reg_list->num_of_regs); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx708->sd.ctrl_handler); + if (ret) + return ret; + + /* set stream on register */ + return imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, + IMX708_REG_VALUE_08BIT, IMX708_MODE_STREAMING); +} + +/* Stop streaming */ +static void imx708_stop_streaming(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret; + + /* set stream off register */ + ret = imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, + IMX708_REG_VALUE_08BIT, IMX708_MODE_STANDBY); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); +} + +static int imx708_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx708 *imx708 = to_imx708(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&imx708->mutex); + if (imx708->streaming == enable) { + mutex_unlock(&imx708->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + goto err_unlock; + } + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = imx708_start_streaming(imx708); + if (ret) + goto err_rpm_put; + } else { + imx708_stop_streaming(imx708); + pm_runtime_put(&client->dev); + } + + imx708->streaming = enable; + + /* vflip/hflip and hdr mode cannot change during streaming */ + __v4l2_ctrl_grab(imx708->vflip, enable); + __v4l2_ctrl_grab(imx708->hflip, enable); + __v4l2_ctrl_grab(imx708->hdr_mode, enable); + + mutex_unlock(&imx708->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&imx708->mutex); + + return ret; +} + +/* Power/clock management functions */ +static int imx708_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + int ret; + + ret = regulator_bulk_enable(IMX708_NUM_SUPPLIES, + imx708->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(imx708->xclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + gpiod_set_value_cansleep(imx708->reset_gpio, 1); + usleep_range(IMX708_XCLR_MIN_DELAY_US, + IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US); + + return 0; + +reg_off: + regulator_bulk_disable(IMX708_NUM_SUPPLIES, imx708->supplies); + return ret; +} + +static int imx708_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + gpiod_set_value_cansleep(imx708->reset_gpio, 0); + regulator_bulk_disable(IMX708_NUM_SUPPLIES, imx708->supplies); + clk_disable_unprepare(imx708->xclk); + + /* Force reprogramming of the common registers when powered up again. */ + imx708->common_regs_written = false; + + return 0; +} + +static int __maybe_unused imx708_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + if (imx708->streaming) + imx708_stop_streaming(imx708); + + return 0; +} + +static int __maybe_unused imx708_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + int ret; + + if (imx708->streaming) { + ret = imx708_start_streaming(imx708); + if (ret) + goto error; + } + + return 0; + +error: + imx708_stop_streaming(imx708); + imx708->streaming = 0; + return ret; +} + +static int imx708_get_regulators(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + unsigned int i; + + for (i = 0; i < IMX708_NUM_SUPPLIES; i++) + imx708->supplies[i].supply = imx708_supply_name[i]; + + return devm_regulator_bulk_get(&client->dev, + IMX708_NUM_SUPPLIES, + imx708->supplies); +} + +/* Verify chip ID */ +static int imx708_identify_module(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret; + u32 val; + + ret = imx708_read_reg(imx708, IMX708_REG_CHIP_ID, + IMX708_REG_VALUE_16BIT, &val); + if (ret) { + dev_err(&client->dev, "failed to read chip id %x, with error %d\n", + IMX708_CHIP_ID, ret); + return ret; + } + + if (val != IMX708_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + IMX708_CHIP_ID, val); + return -EIO; + } + + ret = imx708_read_reg(imx708, 0x0000, IMX708_REG_VALUE_16BIT, &val); + if (!ret) { + dev_info(&client->dev, "camera module ID 0x%04x\n", val); + snprintf(imx708->sd.name, sizeof(imx708->sd.name), "imx708%s%s", + val & 0x02 ? "_wide" : "", + val & 0x80 ? "_noir" : ""); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops imx708_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx708_video_ops = { + .s_stream = imx708_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx708_pad_ops = { + .enum_mbus_code = imx708_enum_mbus_code, + .get_fmt = imx708_get_pad_format, + .set_fmt = imx708_set_pad_format, + .get_selection = imx708_get_selection, + .enum_frame_size = imx708_enum_frame_size, +}; + +static const struct v4l2_subdev_ops imx708_subdev_ops = { + .core = &imx708_core_ops, + .video = &imx708_video_ops, + .pad = &imx708_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx708_internal_ops = { + .open = imx708_open, +}; + +static const struct v4l2_ctrl_config imx708_notify_gains_ctrl = { + .ops = &imx708_ctrl_ops, + .id = V4L2_CID_NOTIFY_GAINS, + .type = V4L2_CTRL_TYPE_U32, + .min = IMX708_COLOUR_BALANCE_MIN, + .max = IMX708_COLOUR_BALANCE_MAX, + .step = IMX708_COLOUR_BALANCE_STEP, + .def = IMX708_COLOUR_BALANCE_DEFAULT, + .dims = { 4 }, + .elem_size = sizeof(u32), +}; + +/* Initialize control handlers */ +static int imx708_init_controls(struct imx708 *imx708) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct v4l2_fwnode_device_properties props; + unsigned int i; + int ret; + + ctrl_hdlr = &imx708->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); + if (ret) + return ret; + + mutex_init(&imx708->mutex); + ctrl_hdlr->lock = &imx708->mutex; + + /* By default, PIXEL_RATE is read only */ + imx708->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_PIXEL_RATE, + IMX708_INITIAL_PIXEL_RATE, + IMX708_INITIAL_PIXEL_RATE, 1, + IMX708_INITIAL_PIXEL_RATE); + + /* + * Create the controls here, but mode specific limits are setup + * in the imx708_set_framing_limits() call below. + */ + imx708->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_VBLANK, 0, 0xffff, 1, 0); + imx708->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_HBLANK, 0, 0xffff, 1, 0); + + imx708->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX708_EXPOSURE_MIN, + IMX708_EXPOSURE_MAX, + IMX708_EXPOSURE_STEP, + IMX708_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX708_ANA_GAIN_MIN, IMX708_ANA_GAIN_MAX, + IMX708_ANA_GAIN_STEP, IMX708_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX708_DGTL_GAIN_MIN, IMX708_DGTL_GAIN_MAX, + IMX708_DGTL_GAIN_STEP, IMX708_DGTL_GAIN_DEFAULT); + + imx708->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + imx708->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx708_test_pattern_menu) - 1, + 0, 0, imx708_test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 + * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 + * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + IMX708_TEST_PATTERN_COLOUR_MIN, + IMX708_TEST_PATTERN_COLOUR_MAX, + IMX708_TEST_PATTERN_COLOUR_STEP, + IMX708_TEST_PATTERN_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + imx708->notify_gains = v4l2_ctrl_new_custom(ctrl_hdlr, + &imx708_notify_gains_ctrl, NULL); + + imx708->hdr_mode = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_WIDE_DYNAMIC_RANGE, + 0, 1, 1, 0); + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx708_ctrl_ops, &props); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + imx708->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + imx708->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx708->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx708->hdr_mode->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx708->sd.ctrl_handler = ctrl_hdlr; + + /* Setup exposure and frame/line length limits. */ + imx708_set_framing_limits(imx708); + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&imx708->mutex); + + return ret; +} + +static void imx708_free_controls(struct imx708 *imx708) +{ + v4l2_ctrl_handler_free(imx708->sd.ctrl_handler); + mutex_destroy(&imx708->mutex); +} + +static int imx708_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + goto error_out; + } + + /* Check the link frequency set in device tree */ + if (!ep_cfg.nr_of_link_frequencies) { + dev_err(dev, "link-frequency property not found in DT\n"); + goto error_out; + } + + if (ep_cfg.nr_of_link_frequencies != 1 || + ep_cfg.link_frequencies[0] != IMX708_DEFAULT_LINK_FREQ) { + dev_err(dev, "Link frequency not supported: %lld\n", + ep_cfg.link_frequencies[0]); + goto error_out; + } + + ret = 0; + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int imx708_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx708 *imx708; + int ret; + + imx708 = devm_kzalloc(&client->dev, sizeof(*imx708), GFP_KERNEL); + if (!imx708) + return -ENOMEM; + + v4l2_i2c_subdev_init(&imx708->sd, client, &imx708_subdev_ops); + + /* Check the hardware configuration in device tree */ + if (imx708_check_hwcfg(dev)) + return -EINVAL; + + /* Get system clock (xclk) */ + imx708->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(imx708->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(imx708->xclk); + } + + imx708->xclk_freq = clk_get_rate(imx708->xclk); + if (imx708->xclk_freq != IMX708_XCLK_FREQ) { + dev_err(dev, "xclk frequency not supported: %d Hz\n", + imx708->xclk_freq); + return -EINVAL; + } + + ret = imx708_get_regulators(imx708); + if (ret) { + dev_err(dev, "failed to get regulators\n"); + return ret; + } + + /* Request optional enable pin */ + imx708->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + /* + * The sensor must be powered for imx708_identify_module() + * to be able to read the CHIP_ID register + */ + ret = imx708_power_on(dev); + if (ret) + return ret; + + ret = imx708_identify_module(imx708); + if (ret) + goto error_power_off; + + /* Initialize default format */ + imx708_set_default_format(imx708); + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + /* This needs the pm runtime to be registered. */ + ret = imx708_init_controls(imx708); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + imx708->sd.internal_ops = &imx708_internal_ops; + imx708->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + imx708->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pads */ + imx708->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE; + imx708->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx708->sd.entity, NUM_PADS, imx708->pad); + if (ret) { + dev_err(dev, "failed to init entity pads: %d\n", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&imx708->sd); + if (ret < 0) { + dev_err(dev, "failed to register sensor sub-device: %d\n", ret); + goto error_media_entity; + } + + return 0; + +error_media_entity: + media_entity_cleanup(&imx708->sd.entity); + +error_handler_free: + imx708_free_controls(imx708); + +error_power_off: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + imx708_power_off(&client->dev); + + return ret; +} + +static int imx708_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + imx708_free_controls(imx708); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx708_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +static const struct of_device_id imx708_dt_ids[] = { + { .compatible = "sony,imx708" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx708_dt_ids); + +static const struct dev_pm_ops imx708_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx708_suspend, imx708_resume) + SET_RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL) +}; + +static struct i2c_driver imx708_i2c_driver = { + .driver = { + .name = "imx708", + .of_match_table = imx708_dt_ids, + .pm = &imx708_pm_ops, + }, + .probe_new = imx708_probe, + .remove = imx708_remove, +}; + +module_i2c_driver(imx708_i2c_driver); + +MODULE_AUTHOR("David Plowman "); +MODULE_DESCRIPTION("Sony IMX708 sensor driver"); +MODULE_LICENSE("GPL v2"); -- 2.7.4 From 8f5c061430cd16e212a91573f4d84191f33059ed Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Thu, 22 Dec 2022 14:28:15 +0000 Subject: [PATCH 09/16] defconfigs: Add IMX708 to all Pi defconfigs Adds the IMX708 image sensor driver to the defconfigs. Signed-off-by: Dave Stevenson [sw0312.kim: cherry-pick raspberry pi kernel upstream to support rpi camera module v3] Signed-off-by: Seung-Woo Kim Change-Id: Icfb7040c6656401f5760e6175a2a79de27b216fc --- arch/arm/configs/bcm2709_defconfig | 2 ++ arch/arm/configs/bcm2711_defconfig | 2 ++ arch/arm/configs/bcmrpi_defconfig | 2 ++ arch/arm64/configs/bcm2711_defconfig | 2 ++ arch/arm64/configs/bcmrpi3_defconfig | 2 ++ 5 files changed, 10 insertions(+) diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig index d3f21b5..4426454 100644 --- a/arch/arm/configs/bcm2709_defconfig +++ b/arch/arm/configs/bcm2709_defconfig @@ -938,6 +938,7 @@ CONFIG_VIDEO_IMX290=m CONFIG_VIDEO_IMX296=m CONFIG_VIDEO_IMX477=m CONFIG_VIDEO_IMX519=m +CONFIG_VIDEO_IMX708=m CONFIG_VIDEO_OV2311=m CONFIG_VIDEO_OV5647=m CONFIG_VIDEO_OV7251=m @@ -946,6 +947,7 @@ CONFIG_VIDEO_OV9281=m CONFIG_VIDEO_IRS1125=m CONFIG_VIDEO_MT9V011=m CONFIG_VIDEO_AD5398=m +CONFIG_VIDEO_DW9807_VCM=m CONFIG_DRM=m CONFIG_DRM_LOAD_EDID_FIRMWARE=y CONFIG_DRM_UDL=m diff --git a/arch/arm/configs/bcm2711_defconfig b/arch/arm/configs/bcm2711_defconfig index 728cf8f..74801d1 100644 --- a/arch/arm/configs/bcm2711_defconfig +++ b/arch/arm/configs/bcm2711_defconfig @@ -956,6 +956,7 @@ CONFIG_VIDEO_IMX290=m CONFIG_VIDEO_IMX296=m CONFIG_VIDEO_IMX477=m CONFIG_VIDEO_IMX519=m +CONFIG_VIDEO_IMX708=m CONFIG_VIDEO_OV2311=m CONFIG_VIDEO_OV5647=m CONFIG_VIDEO_OV7251=m @@ -964,6 +965,7 @@ CONFIG_VIDEO_OV9281=m CONFIG_VIDEO_IRS1125=m CONFIG_VIDEO_MT9V011=m CONFIG_VIDEO_AD5398=m +CONFIG_VIDEO_DW9807_VCM=m CONFIG_DRM=m CONFIG_DRM_LOAD_EDID_FIRMWARE=y CONFIG_DRM_UDL=m diff --git a/arch/arm/configs/bcmrpi_defconfig b/arch/arm/configs/bcmrpi_defconfig index c2f4cb4..a4d72d5 100644 --- a/arch/arm/configs/bcmrpi_defconfig +++ b/arch/arm/configs/bcmrpi_defconfig @@ -931,6 +931,7 @@ CONFIG_VIDEO_IMX290=m CONFIG_VIDEO_IMX296=m CONFIG_VIDEO_IMX477=m CONFIG_VIDEO_IMX519=m +CONFIG_VIDEO_IMX708=m CONFIG_VIDEO_OV2311=m CONFIG_VIDEO_OV5647=m CONFIG_VIDEO_OV7251=m @@ -939,6 +940,7 @@ CONFIG_VIDEO_OV9281=m CONFIG_VIDEO_IRS1125=m CONFIG_VIDEO_MT9V011=m CONFIG_VIDEO_AD5398=m +CONFIG_VIDEO_DW9807_VCM=m CONFIG_DRM=m CONFIG_DRM_LOAD_EDID_FIRMWARE=y CONFIG_DRM_UDL=m diff --git a/arch/arm64/configs/bcm2711_defconfig b/arch/arm64/configs/bcm2711_defconfig index a0eb3c6..31e9694 100644 --- a/arch/arm64/configs/bcm2711_defconfig +++ b/arch/arm64/configs/bcm2711_defconfig @@ -966,6 +966,7 @@ CONFIG_VIDEO_IMX290=m CONFIG_VIDEO_IMX296=m CONFIG_VIDEO_IMX477=m CONFIG_VIDEO_IMX519=m +CONFIG_VIDEO_IMX708=m CONFIG_VIDEO_OV2311=m CONFIG_VIDEO_OV5647=m CONFIG_VIDEO_OV7251=m @@ -974,6 +975,7 @@ CONFIG_VIDEO_OV9281=m CONFIG_VIDEO_IRS1125=m CONFIG_VIDEO_MT9V011=m CONFIG_VIDEO_AD5398=m +CONFIG_VIDEO_DW9807_VCM=m CONFIG_DRM=m CONFIG_DRM_LOAD_EDID_FIRMWARE=y CONFIG_DRM_UDL=m diff --git a/arch/arm64/configs/bcmrpi3_defconfig b/arch/arm64/configs/bcmrpi3_defconfig index 8ea80d6..d615da5 100644 --- a/arch/arm64/configs/bcmrpi3_defconfig +++ b/arch/arm64/configs/bcmrpi3_defconfig @@ -903,6 +903,7 @@ CONFIG_VIDEO_IMX290=m CONFIG_VIDEO_IMX296=m CONFIG_VIDEO_IMX477=m CONFIG_VIDEO_IMX519=m +CONFIG_VIDEO_IMX708=m CONFIG_VIDEO_OV2311=m CONFIG_VIDEO_OV5647=m CONFIG_VIDEO_OV7251=m @@ -911,6 +912,7 @@ CONFIG_VIDEO_OV9281=m CONFIG_VIDEO_IRS1125=m CONFIG_VIDEO_MT9V011=m CONFIG_VIDEO_AD5398=m +CONFIG_VIDEO_DW9807_VCM=m CONFIG_DRM=m CONFIG_DRM_LOAD_EDID_FIRMWARE=y CONFIG_DRM_UDL=m -- 2.7.4 From 0233026961113958b30fc4691166de6af289b334 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Thu, 22 Dec 2022 14:12:54 +0000 Subject: [PATCH 10/16] dtoverlays: Add overlays for the IMX708 image sensor New overlay to support the Sony IMX708 image sensor. This includes a VCM for lens control. Also adds support to the camera mux overlays. Signed-off-by: Dave Stevenson [sw0312.kim: cherry-pick raspberry pi kernel upstream to support rpi camera module v3] Signed-off-by: Seung-Woo Kim Change-Id: I380c4c25ec09e786615dfc13e61fc9fe5ebbbc37 --- arch/arm/boot/dts/overlays/Makefile | 1 + arch/arm/boot/dts/overlays/README | 21 +++++ .../boot/dts/overlays/camera-mux-2port-overlay.dts | 32 +++++++ .../boot/dts/overlays/camera-mux-4port-overlay.dts | 64 +++++++++++++ arch/arm/boot/dts/overlays/imx708-overlay.dts | 104 +++++++++++++++++++++ arch/arm/boot/dts/overlays/imx708.dtsi | 35 +++++++ 6 files changed, 257 insertions(+) create mode 100644 arch/arm/boot/dts/overlays/imx708-overlay.dts create mode 100644 arch/arm/boot/dts/overlays/imx708.dtsi diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile index 8bc3251..9221c97 100644 --- a/arch/arm/boot/dts/overlays/Makefile +++ b/arch/arm/boot/dts/overlays/Makefile @@ -120,6 +120,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \ imx462.dtbo \ imx477.dtbo \ imx519.dtbo \ + imx708.dtbo \ iqaudio-codec.dtbo \ iqaudio-dac.dtbo \ iqaudio-dacplus.dtbo \ diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README index 04ba527..cfc61c1 100644 --- a/arch/arm/boot/dts/overlays/README +++ b/arch/arm/boot/dts/overlays/README @@ -710,6 +710,7 @@ Params: cam0-imx219 Select IMX219 for camera on port 0 cam0-imx258 Select IMX258 for camera on port 0 cam0-imx290 Select IMX290 for camera on port 0 cam0-imx477 Select IMX477 for camera on port 0 + cam0-imx708 Select IMX708 for camera on port 0 cam0-ov2311 Select OV2311 for camera on port 0 cam0-ov5647 Select OV5647 for camera on port 0 cam0-ov7251 Select OV7251 for camera on port 0 @@ -719,6 +720,7 @@ Params: cam0-imx219 Select IMX219 for camera on port 0 cam1-imx258 Select IMX258 for camera on port 1 cam1-imx290 Select IMX290 for camera on port 1 cam1-imx477 Select IMX477 for camera on port 1 + cam1-imx708 Select IMX708 for camera on port 1 cam1-ov2311 Select OV2311 for camera on port 1 cam1-ov5647 Select OV5647 for camera on port 1 cam1-ov7251 Select OV7251 for camera on port 1 @@ -735,6 +737,7 @@ Params: cam0-imx219 Select IMX219 for camera on port 0 cam0-imx258 Select IMX258 for camera on port 0 cam0-imx290 Select IMX290 for camera on port 0 cam0-imx477 Select IMX477 for camera on port 0 + cam0-imx708 Select IMX708 for camera on port 0 cam0-ov2311 Select OV2311 for camera on port 0 cam0-ov5647 Select OV5647 for camera on port 0 cam0-ov7251 Select OV7251 for camera on port 0 @@ -744,6 +747,7 @@ Params: cam0-imx219 Select IMX219 for camera on port 0 cam1-imx258 Select IMX258 for camera on port 1 cam1-imx290 Select IMX290 for camera on port 1 cam1-imx477 Select IMX477 for camera on port 1 + cam1-imx708 Select IMX708 for camera on port 1 cam1-ov2311 Select OV2311 for camera on port 1 cam1-ov5647 Select OV5647 for camera on port 1 cam1-ov7251 Select OV7251 for camera on port 1 @@ -753,6 +757,7 @@ Params: cam0-imx219 Select IMX219 for camera on port 0 cam2-imx258 Select IMX258 for camera on port 2 cam2-imx290 Select IMX290 for camera on port 2 cam2-imx477 Select IMX477 for camera on port 2 + cam2-imx708 Select IMX708 for camera on port 2 cam2-ov2311 Select OV2311 for camera on port 2 cam2-ov5647 Select OV5647 for camera on port 2 cam2-ov7251 Select OV7251 for camera on port 2 @@ -762,6 +767,7 @@ Params: cam0-imx219 Select IMX219 for camera on port 0 cam3-imx258 Select IMX258 for camera on port 3 cam3-imx290 Select IMX290 for camera on port 3 cam3-imx477 Select IMX477 for camera on port 3 + cam3-imx708 Select IMX708 for camera on port 3 cam3-ov2311 Select OV2311 for camera on port 3 cam3-ov5647 Select OV5647 for camera on port 3 cam3-ov7251 Select OV7251 for camera on port 3 @@ -2304,6 +2310,21 @@ Params: rotation Mounting rotation of the camera sensor (0 or Compute Module (CSI0, i2c_vc, and cam0_reg). +Name: imx708 +Info: Sony IMX708 camera module. + Uses Unicam 1, which is the standard camera connector on most Pi + variants. +Load: dtoverlay=imx708,= +Params: rotation Mounting rotation of the camera sensor (0 or + 180, default 180) + vcm Select lens driver state. Default is enabled, + but vcm=off will disable. + media-controller Configure use of Media Controller API for + configuring the sensor (default on) + cam0 Adopt the default configuration for CAM0 on a + Compute Module (CSI0, i2c_vc, and cam0_reg). + + Name: iqaudio-codec Info: Configures the IQaudio Codec audio card Load: dtoverlay=iqaudio-codec diff --git a/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts b/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts index ef24d5a..705c7ee 100644 --- a/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts +++ b/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts @@ -112,6 +112,16 @@ #undef cam_endpoint #undef cam1_clk + #define cam_node imx708_0 + #define cam_endpoint imx708_0_ep + #define vcm_node imx708_0_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + #define cam_node ov5647_0 #define cam_endpoint ov5647_0_ep #define cam1_clk clk_25mhz @@ -182,6 +192,16 @@ #undef cam_endpoint #undef cam1_clk + #define cam_node imx708_1 + #define cam_endpoint imx708_1_ep + #define vcm_node imx708_1_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + #define cam_node ov5647_1 #define cam_endpoint ov5647_1_ep #define cam1_clk clk_25mhz @@ -353,6 +373,12 @@ <&imx477_0_ep>, "remote-endpoint:0=",<&mux_in0>, <&mux_in0>, "clock-noncontinuous?", <&imx477_0>, "status=okay"; + cam0-imx708 = <&mux_in0>, "remote-endpoint:0=",<&imx708_0_ep>, + <&imx708_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&imx708_0>, "status=okay", + <&imx708_0_vcm>, "status=okay", + <&imx708_0>,"lens-focus:0=", <&imx708_0_vcm>; cam0-ov5647 = <&mux_in0>, "remote-endpoint:0=",<&ov5647_0_ep>, <&ov5647_0_ep>, "remote-endpoint:0=",<&mux_in0>, <&ov5647_0>, "status=okay"; @@ -381,6 +407,12 @@ <&imx477_1_ep>, "remote-endpoint:0=",<&mux_in1>, <&mux_in1>, "clock-noncontinuous?", <&imx477_1>, "status=okay"; + cam1-imx708 = <&mux_in1>, "remote-endpoint:0=",<&imx708_1_ep>, + <&imx708_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&imx708_1>, "status=okay", + <&imx708_1_vcm>, "status=okay", + <&imx708_1>,"lens-focus:0=", <&imx708_1_vcm>; cam1-ov5647 = <&mux_in1>, "remote-endpoint:0=",<&ov5647_1_ep>, <&ov5647_1_ep>, "remote-endpoint:0=",<&mux_in1>, <&ov5647_1>, "status=okay"; diff --git a/arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts b/arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts index e1a9529..222fda6 100644 --- a/arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts +++ b/arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts @@ -170,6 +170,16 @@ #undef cam_endpoint #undef cam1_clk + #define cam_node imx708_0 + #define cam_endpoint imx708_0_ep + #define vcm_node imx708_0_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + #define cam_node ov5647_0 #define cam_endpoint ov5647_0_ep #define cam1_clk clk_25mhz @@ -240,6 +250,16 @@ #undef cam_endpoint #undef cam1_clk + #define cam_node imx708_1 + #define cam_endpoint imx708_1_ep + #define vcm_node imx708_1_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + #define cam_node ov5647_1 #define cam_endpoint ov5647_1_ep #define cam1_clk clk_25mhz @@ -310,6 +330,16 @@ #undef cam_endpoint #undef cam1_clk + #define cam_node imx708_2 + #define cam_endpoint imx708_2_ep + #define vcm_node imx708_2_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + #define cam_node ov5647_2 #define cam_endpoint ov5647_2_ep #define cam1_clk clk_25mhz @@ -380,6 +410,16 @@ #undef cam_endpoint #undef cam1_clk + #define cam_node imx708_3 + #define cam_endpoint imx708_3_ep + #define vcm_node imx708_3_vcm + #define cam1_clk clk_24mhz + #include "imx708.dtsi" + #undef cam_node + #undef cam_endpoint + #undef vcm_node + #undef cam1_clk + #define cam_node ov5647_3 #define cam_endpoint ov5647_3_ep #define cam1_clk clk_25mhz @@ -568,6 +608,12 @@ <&imx477_0_ep>, "remote-endpoint:0=",<&mux_in0>, <&mux_in0>, "clock-noncontinuous?", <&imx477_0>, "status=okay"; + cam0-imx708 = <&mux_in0>, "remote-endpoint:0=",<&imx708_0_ep>, + <&imx708_0_ep>, "remote-endpoint:0=",<&mux_in0>, + <&mux_in0>, "clock-noncontinuous?", + <&imx708_0>, "status=okay", + <&imx708_0_vcm>, "status=okay", + <&imx708_0>,"lens-focus:0=", <&imx708_0_vcm>; cam0-ov5647 = <&mux_in0>, "remote-endpoint:0=",<&ov5647_0_ep>, <&ov5647_0_ep>, "remote-endpoint:0=",<&mux_in0>, <&ov5647_0>, "status=okay"; @@ -596,6 +642,12 @@ <&imx477_1_ep>, "remote-endpoint:0=",<&mux_in1>, <&mux_in1>, "clock-noncontinuous?", <&imx477_1>, "status=okay"; + cam1-imx708 = <&mux_in1>, "remote-endpoint:0=",<&imx708_1_ep>, + <&imx708_1_ep>, "remote-endpoint:0=",<&mux_in1>, + <&mux_in1>, "clock-noncontinuous?", + <&imx708_1>, "status=okay", + <&imx708_1_vcm>, "status=okay", + <&imx708_1>,"lens-focus:0=", <&imx708_1_vcm>; cam1-ov5647 = <&mux_in1>, "remote-endpoint:0=",<&ov5647_1_ep>, <&ov5647_1_ep>, "remote-endpoint:0=",<&mux_in1>, <&ov5647_1>, "status=okay"; @@ -624,6 +676,12 @@ <&imx477_2_ep>, "remote-endpoint:0=",<&mux_in2>, <&mux_in2>, "clock-noncontinuous?", <&imx477_2>, "status=okay"; + cam2-imx708 = <&mux_in2>, "remote-endpoint:0=",<&imx708_2_ep>, + <&imx708_2_ep>, "remote-endpoint:0=",<&mux_in2>, + <&mux_in2>, "clock-noncontinuous?", + <&imx708_2>, "status=okay", + <&imx708_2_vcm>, "status=okay", + <&imx708_2>,"lens-focus:0=", <&imx708_2_vcm>; cam2-ov5647 = <&mux_in2>, "remote-endpoint:0=",<&ov5647_2_ep>, <&ov5647_2_ep>, "remote-endpoint:0=",<&mux_in2>, <&ov5647_2>, "status=okay"; @@ -652,6 +710,12 @@ <&imx477_3_ep>, "remote-endpoint:0=",<&mux_in3>, <&mux_in3>, "clock-noncontinuous?", <&imx477_3>, "status=okay"; + cam3-imx708 = <&mux_in3>, "remote-endpoint:0=",<&imx708_3_ep>, + <&imx708_3_ep>, "remote-endpoint:0=",<&mux_in3>, + <&mux_in3>, "clock-noncontinuous?", + <&imx708_3>, "status=okay", + <&imx708_3_vcm>, "status=okay", + <&imx708_3>,"lens-focus:0=", <&imx708_3_vcm>; cam3-ov5647 = <&mux_in3>, "remote-endpoint:0=",<&ov5647_3_ep>, <&ov5647_3_ep>, "remote-endpoint:0=",<&mux_in3>, <&ov5647_3>, "status=okay"; diff --git a/arch/arm/boot/dts/overlays/imx708-overlay.dts b/arch/arm/boot/dts/overlays/imx708-overlay.dts new file mode 100644 index 0000000..9107e985 --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx708-overlay.dts @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Definitions for IMX708 camera module on VC I2C bus +/dts-v1/; +/plugin/; + +#include + +/{ + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c0if>; + __overlay__ { + status = "okay"; + }; + }; + + clk_frag: fragment@1 { + target = <&cam1_clk>; + __overlay__ { + status = "okay"; + clock-frequency = <24000000>; + }; + }; + + fragment@2 { + target = <&i2c0mux>; + __overlay__ { + status = "okay"; + }; + }; + + reg_frag: fragment@3 { + target = <&cam1_reg>; + cam_reg: __overlay__ { + startup-delay-us = <70000>; + off-on-delay-us = <30000>; + regulator-min-microvolt = <2700000>; + regulator-max-microvolt = <2700000>; + }; + }; + + fragment@4 { + target = <&cam_node>; + __overlay__ { + lens-focus = <&vcm_node>; + }; + }; + + i2c_frag: fragment@100 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + #include "imx708.dtsi" + }; + }; + + csi_frag: fragment@101 { + target = <&csi1>; + csi: __overlay__ { + status = "okay"; + brcm,media-controller; + + port { + csi_ep: endpoint { + remote-endpoint = <&cam_endpoint>; + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + }; + }; + }; + }; + + __overrides__ { + rotation = <&cam_node>,"rotation:0"; + orientation = <&cam_node>,"orientation:0"; + media-controller = <&csi>,"brcm,media-controller?"; + cam0 = <&i2c_frag>, "target:0=",<&i2c_vc>, + <&csi_frag>, "target:0=",<&csi0>, + <&clk_frag>, "target:0=",<&cam0_clk>, + <®_frag>, "target:0=",<&cam0_reg>, + <&cam_node>, "clocks:0=",<&cam0_clk>, + <&cam_node>, "VANA1-supply:0=",<&cam0_reg>, + <&vcm_node>, "VDD-supply:0=",<&cam0_reg>; + vcm = <&vcm_node>, "status", + <0>, "=4"; + }; +}; + +&cam_node { + status = "okay"; +}; + +&cam_endpoint { + remote-endpoint = <&csi_ep>; +}; + +&vcm_node { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/overlays/imx708.dtsi b/arch/arm/boot/dts/overlays/imx708.dtsi new file mode 100644 index 0000000..1381caf --- /dev/null +++ b/arch/arm/boot/dts/overlays/imx708.dtsi @@ -0,0 +1,35 @@ +// Fragment that configures a Sony IMX708 + +cam_node: imx708@1a { + compatible = "sony,imx708"; + reg = <0x1a>; + status = "disabled"; + + clocks = <&cam1_clk>; + clock-names = "xclk"; + + VANA1-supply = <&cam1_reg>; /* 2.8v */ + VANA2-supply = <&cam_dummy_reg>;/* 1.8v */ + VDIG-supply = <&cam_dummy_reg>; /* 1.1v */ + VDDL-supply = <&cam_dummy_reg>; /* 1.8v */ + + rotation = <180>; + orientation = <2>; + + port { + cam_endpoint: endpoint { + clock-lanes = <0>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = + /bits/ 64 <450000000>; + }; + }; +}; + +vcm_node: dw9817@c { + compatible = "dongwoon,dw9817-vcm"; + reg = <0x0c>; + status = "disabled"; + VDD-supply = <&cam1_reg>; +}; -- 2.7.4 From f4a4f50abe5755711174a40216d3ae40c80e51d3 Mon Sep 17 00:00:00 2001 From: Naushir Patuck Date: Thu, 5 Jan 2023 11:04:48 +0000 Subject: [PATCH 11/16] imx708: Do not reset vblank to a default value imx708_set_framing_limits resets the vblank control to the mode default value unconditionally. This causes it to overwrite the user specified vblank and exposure control values when starting the sensor, since it is called when handling V4L2_CID_WIDE_DYNAMIC_RANGE. Remove this call to s_ctrl as it is unnecessary. Signed-off-by: Naushir Patuck [sw0312.kim: cherry-pick raspberry pi kernel upstream to support rpi camera module v3] Signed-off-by: Seung-Woo Kim Change-Id: I60555efb022b889febd1720a6e15bb3e009bb55c --- drivers/media/i2c/imx708.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c index ad4eeb9..8961ea1 100644 --- a/drivers/media/i2c/imx708.c +++ b/drivers/media/i2c/imx708.c @@ -1041,9 +1041,6 @@ static void imx708_set_framing_limits(struct imx708 *imx708) IMX708_FRAME_LENGTH_MAX - mode->height, 1, mode->vblank_default); - /* Setting this will adjust the exposure limits as well. */ - __v4l2_ctrl_s_ctrl(imx708->vblank, mode->vblank_default); - /* * Currently PPL is fixed to the mode specified value, so hblank * depends on mode->width only, and is not changeable in any -- 2.7.4 From 69dd7b37f3391c93dd24d697e7f8446dcd1012ab Mon Sep 17 00:00:00 2001 From: Naushir Patuck Date: Thu, 5 Jan 2023 14:44:48 +0000 Subject: [PATCH 12/16] drivers: media: imx708: Enable long exposure mode Enable long exposure modes by using the long exposure shift register setting in the imx708 sensor. Signed-off-by: Naushir Patuck [sw0312.kim: cherry-pick raspberry pi kernel upstream to support rpi camera module v3] Signed-off-by: Seung-Woo Kim Change-Id: I73bfd16537aa7784652f45b9b1f9930677b72304 --- drivers/media/i2c/imx708.c | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c index 8961ea1..785275c 100644 --- a/drivers/media/i2c/imx708.c +++ b/drivers/media/i2c/imx708.c @@ -44,6 +44,10 @@ #define IMX708_REG_FRAME_LENGTH 0x0340 #define IMX708_FRAME_LENGTH_MAX 0xffff +/* Long exposure multiplier */ +#define IMX708_LONG_EXP_SHIFT_MAX 7 +#define IMX708_LONG_EXP_SHIFT_REG 0x3100 + /* Exposure control */ #define IMX708_REG_EXPOSURE 0x0202 #define IMX708_EXPOSURE_OFFSET 48 @@ -806,6 +810,9 @@ struct imx708 { /* Rewrite common registers on stream on? */ bool common_regs_written; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; }; static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd) @@ -994,7 +1001,8 @@ static int imx708_set_exposure(struct imx708 *imx708, unsigned int val) * will automatically divide the medium and short ones by 4,16. */ ret = imx708_write_reg(imx708, IMX708_REG_EXPOSURE, - IMX708_REG_VALUE_16BIT, val); + IMX708_REG_VALUE_16BIT, + val >> imx708->long_exp_shift); return ret; } @@ -1027,18 +1035,42 @@ static int imx708_set_analogue_gain(struct imx708 *imx708, unsigned int val) return ret; } +static int imx708_set_frame_length(struct imx708 *imx708, unsigned int val) +{ + int ret = 0; + + imx708->long_exp_shift = 0; + + while (val > IMX708_FRAME_LENGTH_MAX) { + imx708->long_exp_shift++; + val >>= 1; + } + + ret = imx708_write_reg(imx708, IMX708_REG_FRAME_LENGTH, + IMX708_REG_VALUE_16BIT, val); + if (ret) + return ret; + + return imx708_write_reg(imx708, IMX708_LONG_EXP_SHIFT_REG, + IMX708_REG_VALUE_08BIT, imx708->long_exp_shift); +} + static void imx708_set_framing_limits(struct imx708 *imx708) { unsigned int hblank; const struct imx708_mode *mode = imx708->mode; + /* Default to no long exposure multiplier */ + imx708->long_exp_shift = 0; + __v4l2_ctrl_modify_range(imx708->pixel_rate, mode->pixel_rate, mode->pixel_rate, 1, mode->pixel_rate); /* Update limits and set FPS to default */ __v4l2_ctrl_modify_range(imx708->vblank, mode->vblank_min, - IMX708_FRAME_LENGTH_MAX - mode->height, + ((1 << IMX708_LONG_EXP_SHIFT_MAX) * + IMX708_FRAME_LENGTH_MAX) - mode->height, 1, mode->vblank_default); /* @@ -1112,9 +1144,8 @@ static int imx708_set_ctrl(struct v4l2_ctrl *ctrl) imx708->vflip->val << 1); break; case V4L2_CID_VBLANK: - ret = imx708_write_reg(imx708, IMX708_REG_FRAME_LENGTH, - IMX708_REG_VALUE_16BIT, - imx708->mode->height + ctrl->val); + ret = imx708_set_frame_length(imx708, + imx708->mode->height + ctrl->val); break; case V4L2_CID_NOTIFY_GAINS: ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_BLUE, -- 2.7.4 From fbe7330ef58680361d3aa56e7d08ebc9981a663e Mon Sep 17 00:00:00 2001 From: David Plowman Date: Tue, 10 Jan 2023 13:14:27 +0000 Subject: [PATCH 13/16] drivers: media: i2c: imx708: Fix crop information The 1536x864 mode contained incorrect crop information. Signed-off-by: David Plowman [sw0312.kim: cherry-pick raspberry pi kernel upstream to support rpi camera module v3] Signed-off-by: Seung-Woo Kim Change-Id: Ic92f91f80bb8e088a681474f10fee7b6d39a55e6 --- drivers/media/i2c/imx708.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c index 785275c..bd04eea 100644 --- a/drivers/media/i2c/imx708.c +++ b/drivers/media/i2c/imx708.c @@ -673,10 +673,10 @@ static const struct imx708_mode supported_modes_10bit_no_hdr[] = { .height = 864, .line_length_pix = 0x1460, .crop = { - .left = IMX708_PIXEL_ARRAY_LEFT, - .top = IMX708_PIXEL_ARRAY_TOP, - .width = 4608, - .height = 2592, + .left = IMX708_PIXEL_ARRAY_LEFT + 768, + .top = IMX708_PIXEL_ARRAY_TOP + 432, + .width = 3072, + .height = 1728, }, .vblank_min = 40, .vblank_default = 2755, -- 2.7.4 From 9fede09452dc4e52cccee9e8f4a00b162cfa9f16 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 17 Aug 2021 08:51:34 +0200 Subject: [PATCH 14/16] media: dt-bindings: Convert Dongwoon dw9807-vcm bindings to json-schema Upstream commit a1f4626b282d4e419047d47c1b0b1055f3c12a19. Convert the old text based dw9807-vcm chip DT bindings to json-schema. Signed-off-by: Sakari Ailus Reviewed-by: Krzysztof Kozlowski Signed-off-by: Mauro Carvalho Chehab [sw0312.kim: cherry-pick raspberry pi kernel upstream to support rpi camera module v3] Signed-off-by: Seung-Woo Kim Change-Id: Ia6328b98ab46d6bf698ae65a66f8155e2a78242a --- .../bindings/media/i2c/dongwoon,dw9807-vcm.txt | 9 ----- .../bindings/media/i2c/dongwoon,dw9807-vcm.yaml | 41 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 9 deletions(-) delete mode 100644 Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.txt create mode 100644 Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.txt b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.txt deleted file mode 100644 index c4701f1..0000000 --- a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.txt +++ /dev/null @@ -1,9 +0,0 @@ -Dongwoon Anatech DW9807 voice coil lens driver - -DW9807 is a 10-bit DAC with current sink capability. It is intended for -controlling voice coil lenses. - -Mandatory properties: - -- compatible: "dongwoon,dw9807-vcm" -- reg: I2C slave address diff --git a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml new file mode 100644 index 0000000..aae246c --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2018, 2021 Intel Corporation +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/dongwoon,dw9807-vcm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Dongwoon Anatech DW9807 voice coil lens driver + +maintainers: + - Sakari Ailus + +description: | + DW9807 is a 10-bit DAC with current sink capability. It is intended for + controlling voice coil lenses. + +properties: + compatible: + const: dongwoon,dw9807-vcm + + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + lens@e { + compatible = "dongwoon,dw9807-vcm"; + reg = <0x0e>; + }; + }; +... -- 2.7.4 From 8b159da74a5bd75c4906a5fd464d6694b0158517 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 3 Jan 2023 16:41:08 +0000 Subject: [PATCH 15/16] media: dt-bindings: Add regulator to dw9807-vcm The VCM driver will often be controlled via a regulator, therefore add in the relevant DT hooks. Signed-off-by: Dave Stevenson [sw0312.kim: cherry-pick raspberry pi kernel upstream to support rpi camera module v3] Signed-off-by: Seung-Woo Kim Change-Id: Ie0eb5024083910c61586f1f2f5d7e9619a0ffd3e --- Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml | 4 ++++ drivers/media/i2c/dw9807-vcm.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml index aae246c..51573d6 100644 --- a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml +++ b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml @@ -21,6 +21,10 @@ properties: reg: maxItems: 1 + VDD-supply: + description: + Definition of the regulator used as VDD power supply to the driver. + required: - compatible - reg diff --git a/drivers/media/i2c/dw9807-vcm.c b/drivers/media/i2c/dw9807-vcm.c index 95e06f1..d31c2e8 100644 --- a/drivers/media/i2c/dw9807-vcm.c +++ b/drivers/media/i2c/dw9807-vcm.c @@ -221,6 +221,10 @@ static int dw9807_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); + if (dw9807_dev->vdd) + regulator_unregister_notifier(dw9807_dev->vdd, + &dw9807_dev->notifier); + pm_runtime_disable(&client->dev); dw9807_subdev_cleanup(dw9807_dev); -- 2.7.4 From 855879ae1726a016419b677257fc212ffbfb37b2 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 3 Jan 2023 15:38:08 +0000 Subject: [PATCH 16/16] media: dw9807-vcm: Add support for DW9817 bidirectional VCM driver The DW9817 is effectively the same as DW9807 from a programming interface, however it drives +/-100mA instead of 0-100mA. This means that the power on ramp needs to take the lens from the midpoint, and power off return it there. It also changes the default position for the module. Signed-off-by: Dave Stevenson [sw0312.kim: cherry-pick raspberry pi kernel upstream to support rpi camera module v3] Signed-off-by: Seung-Woo Kim Change-Id: Ieafb4e02797a9556371a87f327d87adcc9b4d48d --- drivers/media/i2c/dw9807-vcm.c | 110 ++++++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 24 deletions(-) diff --git a/drivers/media/i2c/dw9807-vcm.c b/drivers/media/i2c/dw9807-vcm.c index d31c2e8..6103029 100644 --- a/drivers/media/i2c/dw9807-vcm.c +++ b/drivers/media/i2c/dw9807-vcm.c @@ -1,6 +1,14 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2018 Intel Corporation +/* + * DW9807 is a 10-bit DAC driver, capable of sinking up to 100mA. + * + * DW9817 is a bidirectional 10-bit driver, driving up to +/- 100mA. + * Operationally it is identical to DW9807, except that the idle position is + * the mid-point, not 0. + */ + #include #include #include @@ -38,10 +46,16 @@ #define MAX_RETRY 10 +struct dw9807_cfg { + unsigned int idle_pos; + unsigned int default_pos; +}; + struct dw9807_device { struct v4l2_ctrl_handler ctrls_vcm; struct v4l2_subdev sd; u16 current_val; + u16 idle_pos; }; static inline struct dw9807_device *sd_to_dw9807_vcm( @@ -109,6 +123,40 @@ static int dw9807_set_dac(struct i2c_client *client, u16 data) return 0; } +/* + * The lens position is gradually moved in units of DW9807_CTRL_STEPS, + * to make the movements smoothly. In all cases, even when "start" and + * "end" are the same, the lens will be set to the "end" position. + * + * (We don't use hardware slew rate control, because it differs widely + * between otherwise-compatible ICs, and may need lens-specific tuning.) + */ +static int dw9807_ramp(struct i2c_client *client, int start, int end) +{ + int step, val, ret; + + if (start < end) + step = DW9807_CTRL_STEPS; + else + step = -DW9807_CTRL_STEPS; + + val = start; + while (true) { + val += step; + if (step * (val - end) >= 0) + val = end; + ret = dw9807_set_dac(client, val); + if (ret) + dev_err_ratelimited(&client->dev, "%s I2C failure: %d", + __func__, ret); + if (val == end) + break; + usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); + } + + return ret; +} + static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) { struct dw9807_device *dev_vcm = container_of(ctrl->handler, @@ -118,7 +166,7 @@ static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl) struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd); dev_vcm->current_val = ctrl->val; - return dw9807_set_dac(client, ctrl->val); + return dw9807_ramp(client, ctrl->val, ctrl->val); } return -EINVAL; @@ -163,7 +211,8 @@ static int dw9807_init_controls(struct dw9807_device *dev_vcm) v4l2_ctrl_handler_init(hdl, 1); v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, - 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, 0); + 0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, + dev_vcm->current_val); dev_vcm->sd.ctrl_handler = hdl; if (hdl->error) { @@ -175,9 +224,32 @@ static int dw9807_init_controls(struct dw9807_device *dev_vcm) return 0; } +/* Compatible devices; in fact there are many similar chips. + * "data" holds the powered-off (zero current) lens position and a + * default/initial control value (which need not be the same as the powered-off + * value). + */ +static const struct dw9807_cfg dw9807_cfg = { + .idle_pos = 0, + .default_pos = 0 +}; + +static const struct dw9807_cfg dw9817_cfg = { + .idle_pos = 512, + .default_pos = 480, +}; + +static const struct of_device_id dw9807_of_table[] = { + { .compatible = "dongwoon,dw9807-vcm", .data = &dw9807_cfg }, + { .compatible = "dongwoon,dw9817-vcm", .data = &dw9817_cfg }, + { /* sentinel */ } +}; + static int dw9807_probe(struct i2c_client *client) { struct dw9807_device *dw9807_dev; + const struct of_device_id *match; + const struct dw9807_cfg *cfg; int rval; dw9807_dev = devm_kzalloc(&client->dev, sizeof(*dw9807_dev), @@ -185,6 +257,13 @@ static int dw9807_probe(struct i2c_client *client) if (dw9807_dev == NULL) return -ENOMEM; + match = i2c_of_match_device(dw9807_of_table, client); + if (match) { + cfg = (const struct dw9807_cfg *)match->data; + dw9807_dev->idle_pos = cfg->idle_pos; + dw9807_dev->current_val = cfg->default_pos; + } + v4l2_i2c_subdev_init(&dw9807_dev->sd, client, &dw9807_ops); dw9807_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; dw9807_dev->sd.internal_ops = &dw9807_int_ops; @@ -243,15 +322,10 @@ static int __maybe_unused dw9807_vcm_suspend(struct device *dev) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 }; - int ret, val; + int ret; - for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1); - val >= 0; val -= DW9807_CTRL_STEPS) { - ret = dw9807_set_dac(client, val); - if (ret) - dev_err_once(dev, "%s I2C failure: %d", __func__, ret); - usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); - } + if (abs(dw9807_dev->current_val - dw9807_dev->idle_pos) > DW9807_CTRL_STEPS) + dw9807_ramp(client, dw9807_dev->current_val, dw9807_dev->idle_pos); /* Power down */ ret = i2c_master_send(client, tx_data, sizeof(tx_data)); @@ -275,7 +349,7 @@ static int __maybe_unused dw9807_vcm_resume(struct device *dev) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd); const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 }; - int ret, val; + int ret; /* Power on */ ret = i2c_master_send(client, tx_data, sizeof(tx_data)); @@ -284,23 +358,11 @@ static int __maybe_unused dw9807_vcm_resume(struct device *dev) return ret; } - for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS; - val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1; - val += DW9807_CTRL_STEPS) { - ret = dw9807_set_dac(client, val); - if (ret) - dev_err_ratelimited(dev, "%s I2C failure: %d", - __func__, ret); - usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10); - } + dw9807_ramp(client, dw9807_dev->idle_pos, dw9807_dev->current_val); return 0; } -static const struct of_device_id dw9807_of_table[] = { - { .compatible = "dongwoon,dw9807-vcm" }, - { /* sentinel */ } -}; MODULE_DEVICE_TABLE(of, dw9807_of_table); static const struct dev_pm_ops dw9807_pm_ops = { -- 2.7.4