* Re: [PATCH v3 3/3] arm64: dts: qcom: sm6125-xiaomi-laurel-sprout: Add Focaltech FT3518 touchscreen
From: Krzysztof Kozlowski @ 2026-01-20 7:13 UTC (permalink / raw)
To: yedaya.ka, Kamil Gołda, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm
In-Reply-To: <20260118-touchscreen-patches-v3-3-1c6a729c5eb4@gmail.com>
On 18/01/2026 21:29, Yedaya Katsman via B4 Relay wrote:
> +
> &rpm_requests {
> regulators-0 {
> compatible = "qcom,rpm-pm6125-regulators";
> @@ -387,6 +425,81 @@ &sdhc_2 {
>
> &tlmm {
> gpio-reserved-ranges = <22 2>, <28 6>;
> +
> + pmx_ts_reset_active {
You must follow DTS coding style. Do not copy here code from downstream,
ever.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH bpf-next v2 05/13] resolve_btfids: Support for KF_IMPLICIT_ARGS
From: Eduard Zingerman @ 2026-01-20 0:55 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau
Cc: Mykyta Yatsenko, Tejun Heo, Alan Maguire, Benjamin Tissoires,
Jiri Kosina, Amery Hung, bpf, linux-kernel, linux-input,
sched-ext
In-Reply-To: <20260116201700.864797-6-ihor.solodrai@linux.dev>
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
> Implement BTF modifications in resolve_btfids to support BPF kernel
> functions with implicit arguments.
>
> For a kfunc marked with KF_IMPLICIT_ARGS flag, a new function
> prototype is added to BTF that does not have implicit arguments. The
> kfunc's prototype is then updated to a new one in BTF. This prototype
> is the intended interface for the BPF programs.
>
> A <func_name>_impl function is added to BTF to make the original kfunc
> prototype searchable for the BPF verifier. If a <func_name>_impl
> function already exists in BTF, its interpreted as a legacy case, and
> this step is skipped.
>
> Whether an argument is implicit is determined by its type:
> currently only `struct bpf_prog_aux *` is supported.
>
> As a result, the BTF associated with kfunc is changed from
>
> __bpf_kfunc bpf_foo(int arg1, struct bpf_prog_aux *aux);
>
> into
>
> bpf_foo_impl(int arg1, struct bpf_prog_aux *aux);
> __bpf_kfunc bpf_foo(int arg1);
>
> For more context see previous discussions and patches [1][2].
>
> [1] https://lore.kernel.org/dwarves/ba1650aa-fafd-49a8-bea4-bdddee7c38c9@linux.dev/
> [2] https://lore.kernel.org/bpf/20251029190113.3323406-1-ihor.solodrai@linux.dev/
>
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
Patch logic looks good to me, modulo LLM's memory management concern
and nit from Andrii.
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
> @@ -837,6 +854,369 @@ static int dump_raw_btf(struct btf *btf, const char *out_path)
> return 0;
> }
>
> +static const struct btf_type *btf_type_skip_qualifiers(const struct btf *btf, s32 type_id)
> +{
> + const struct btf_type *t = btf__type_by_id(btf, type_id);
> +
> + while (btf_is_mod(t))
> + t = btf__type_by_id(btf, t->type);
> +
> + return t;
> +}
> +
> +static const struct btf_decl_tag *btf_type_decl_tag(const struct btf_type *t)
> +{
> + return (const struct btf_decl_tag *)(t + 1);
> +}
Nit: there is a utility function btf_decl_tag() in bpf/btf.h
which does exactly the same.
[...]
^ permalink raw reply
* [syzbot] [input?] KASAN: stack-out-of-bounds Read in ft260_smbus_write
From: syzbot @ 2026-01-20 2:52 UTC (permalink / raw)
To: bentiss, jikos, linux-i2c, linux-input, linux-kernel,
michael.zaidman, syzkaller-bugs
Hello,
syzbot found the following issue on:
HEAD commit: 603c05a1639f Merge tag 'nfs-for-6.19-2' of git://git.linux..
git tree: upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=120d339a580000
kernel config: https://syzkaller.appspot.com/x/.config?x=1859476832863c41
dashboard link: https://syzkaller.appspot.com/bug?extid=64ca69977b37604cd6d9
compiler: gcc (Debian 12.2.0-14+deb12u1) 12.2.0, GNU ld (GNU Binutils for Debian) 2.40
syz repro: https://syzkaller.appspot.com/x/repro.syz?x=161e0852580000
C reproducer: https://syzkaller.appspot.com/x/repro.c?x=13115522580000
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/b694f12a8d6f/disk-603c05a1.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/91efc898c41a/vmlinux-603c05a1.xz
kernel image: https://storage.googleapis.com/syzbot-assets/d0cbdf66f29d/bzImage-603c05a1.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+64ca69977b37604cd6d9@syzkaller.appspotmail.com
==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x19b/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc90003427d81 by task syz.2.65/6119
CPU: 0 UID: 0 PID: 6119 Comm: syz.2.65 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x116/0x1f0 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0xcd/0x630 mm/kasan/report.c:482
kasan_report+0xe0/0x110 mm/kasan/report.c:595
check_region_inline mm/kasan/generic.c:194 [inline]
kasan_check_range+0x100/0x1b0 mm/kasan/generic.c:200
__asan_memcpy+0x23/0x60 mm/kasan/shadow.c:105
ft260_smbus_write+0x19b/0x2f0 drivers/hid/hid-ft260.c:486
ft260_smbus_xfer+0x22c/0x640 drivers/hid/hid-ft260.c:736
__i2c_smbus_xfer drivers/i2c/i2c-core-smbus.c:591 [inline]
__i2c_smbus_xfer+0x4f0/0xf60 drivers/i2c/i2c-core-smbus.c:554
i2c_smbus_xfer drivers/i2c/i2c-core-smbus.c:546 [inline]
i2c_smbus_xfer+0x200/0x3c0 drivers/i2c/i2c-core-smbus.c:536
i2cdev_ioctl_smbus+0x237/0x990 drivers/i2c/i2c-dev.c:389
i2cdev_ioctl+0x361/0x840 drivers/i2c/i2c-dev.c:478
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl fs/ioctl.c:583 [inline]
__x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xcd/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f88dd98f749
Code: ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 a8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffeebbe43a8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007f88ddbe5fa0 RCX: 00007f88dd98f749
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00007f88dda13f91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f88ddbe5fa0 R14: 00007f88ddbe5fa0 R15: 0000000000000003
</TASK>
The buggy address belongs to stack of task syz.2.65/6119
and is located at offset 49 in frame:
i2cdev_ioctl_smbus+0x0/0x990 drivers/i2c/i2c-dev.c:632
This frame has 1 object:
[48, 82) 'temp'
The buggy address belongs to a 8-page vmalloc region starting at 0xffffc90003420000 allocated at kernel_clone+0xfc/0x910 kernel/fork.c:2651
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x22397
memcg:ffff888074f11282
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 ffffea000088e5c8 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff ffff888074f11282
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 5928, tgid 5928 (syz-executor), ts 80598654650, free_ts 80595057505
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x1af/0x220 mm/page_alloc.c:1884
prep_new_page mm/page_alloc.c:1892 [inline]
get_page_from_freelist+0xd0b/0x31a0 mm/page_alloc.c:3945
__alloc_frozen_pages_noprof+0x25f/0x2430 mm/page_alloc.c:5240
alloc_pages_mpol+0x1fb/0x550 mm/mempolicy.c:2486
alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
alloc_pages_noprof+0x131/0x390 mm/mempolicy.c:2577
vm_area_alloc_pages mm/vmalloc.c:3649 [inline]
__vmalloc_area_node mm/vmalloc.c:3863 [inline]
__vmalloc_node_range_noprof+0xe6c/0x16b0 mm/vmalloc.c:4051
__vmalloc_node_noprof+0xad/0xf0 mm/vmalloc.c:4111
alloc_thread_stack_node kernel/fork.c:354 [inline]
dup_task_struct kernel/fork.c:923 [inline]
copy_process+0x619/0x7430 kernel/fork.c:2052
kernel_clone+0xfc/0x910 kernel/fork.c:2651
__do_sys_clone+0xce/0x120 kernel/fork.c:2792
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xcd/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5928 tgid 5928 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
free_pages_prepare mm/page_alloc.c:1433 [inline]
__free_frozen_pages+0x7df/0x1170 mm/page_alloc.c:2973
discard_slab mm/slub.c:3346 [inline]
__put_partials+0x130/0x170 mm/slub.c:3886
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x4c/0xf0 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x195/0x1e0 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x69/0x90 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4953 [inline]
slab_alloc_node mm/slub.c:5263 [inline]
__kmalloc_cache_node_noprof+0x299/0x830 mm/slub.c:5784
kmalloc_node_noprof include/linux/slab.h:983 [inline]
__get_vm_area_node+0x101/0x330 mm/vmalloc.c:3208
__vmalloc_node_range_noprof+0x247/0x16b0 mm/vmalloc.c:4011
__vmalloc_node_noprof+0xad/0xf0 mm/vmalloc.c:4111
xt_counters_alloc+0x4c/0x70 net/netfilter/x_tables.c:1381
__do_replace+0x97/0x9e0 net/ipv4/netfilter/ip_tables.c:1046
do_replace net/ipv4/netfilter/ip_tables.c:1141 [inline]
do_ipt_set_ctl+0x93b/0xc40 net/ipv4/netfilter/ip_tables.c:1635
nf_setsockopt+0x8d/0xf0 net/netfilter/nf_sockopt.c:101
ip_setsockopt+0xcb/0xf0 net/ipv4/ip_sockglue.c:1424
tcp_setsockopt+0xa7/0x100 net/ipv4/tcp.c:4164
do_sock_setsockopt+0xf3/0x1d0 net/socket.c:2322
Memory state around the buggy address:
ffffc90003427c80: f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00
ffffc90003427d00: 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 f1 f1
>ffffc90003427d80: 00 00 00 00 02 f3 f3 f3 f3 f3 00 00 00 00 00 00
^
ffffc90003427e00: 00 00 00 00 00 00 f1 f1 f1 f1 f1 f1 04 f2 00 00
ffffc90003427e80: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
==================================================================
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title
If you want syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.
If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)
If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report
If you want to undo deduplication, reply with:
#syz undup
^ permalink raw reply
* Re: [PATCH bpf-next v2 12/13] bpf: Remove __prog kfunc arg annotation
From: Eduard Zingerman @ 2026-01-20 2:01 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau
Cc: Mykyta Yatsenko, Tejun Heo, Alan Maguire, Benjamin Tissoires,
Jiri Kosina, Amery Hung, bpf, linux-kernel, linux-input,
sched-ext
In-Reply-To: <20260116201700.864797-13-ihor.solodrai@linux.dev>
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
> Now that all the __prog suffix users in the kernel tree migrated to
> KF_IMPLICIT_ARGS, remove it from the verifier.
>
> See prior discussion for context [1].
>
> [1] https://lore.kernel.org/bpf/CAEf4BzbgPfRm9BX=TsZm-TsHFAHcwhPY4vTt=9OT-uhWqf8tqw@mail.gmail.com/
>
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
[...]
^ permalink raw reply
* Re: [PATCH bpf-next v2 11/13] selftests/bpf: Migrate struct_ops_assoc test to KF_IMPLICIT_ARGS
From: Eduard Zingerman @ 2026-01-20 1:59 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau
Cc: Mykyta Yatsenko, Tejun Heo, Alan Maguire, Benjamin Tissoires,
Jiri Kosina, Amery Hung, bpf, linux-kernel, linux-input,
sched-ext
In-Reply-To: <20260116201700.864797-12-ihor.solodrai@linux.dev>
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
[...]
> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
> index 2357a0340ffe..225ea30c4e3d 100644
> --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
> +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
> @@ -161,7 +161,9 @@ void bpf_kfunc_rcu_task_test(struct task_struct *ptr) __ksym;
> struct task_struct *bpf_kfunc_ret_rcu_test(void) __ksym;
> int *bpf_kfunc_ret_rcu_test_nostruct(int rdonly_buf_size) __ksym;
>
> -int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id) __ksym;
> -int bpf_kfunc_multi_st_ops_test_1_impl(struct st_ops_args *args, void *aux__prog) __ksym;
> +#ifndef __KERNEL__
> +extern int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id) __weak __ksym;
> +extern int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args) __weak __ksym;
> +#endif
Nit: wbpf_kfunc_multi_st_ops_test_1 change is not necessary, right?
^ permalink raw reply
* Re: [PATCH v3 0/3] Support FT3518 touchscreen in xiaomi-laurel
From: Rob Herring @ 2026-01-20 1:57 UTC (permalink / raw)
To: Yedaya Katsman
Cc: Krzysztof Kozlowski, linux-input, Bjorn Andersson,
Krzysztof Kozlowski, devicetree, Kamil Gołda, Conor Dooley,
Dmitry Torokhov, Dmitry Baryshkov, linux-arm-msm, linux-kernel,
Konrad Dybcio
In-Reply-To: <20260118-touchscreen-patches-v3-0-1c6a729c5eb4@gmail.com>
On Sun, 18 Jan 2026 22:29:39 +0200, Yedaya Katsman wrote:
> Adds support for the touchscreen in the Xiaomi Mi A3 (xiaomi-laurel)
> smartphone, FocalTech FT3518
>
> Original tree was here:
> Link: https://gitlab.postmarketos.org/SzczurekYT/linux/-/commits/laurel
>
> Signed-off-by: Yedaya Katsman <yedaya.ka@gmail.com>
> ---
> Changes in v3:
> - Rename regulator node and reorder nodes
> - Add gpio pin configuration for pmx_ts_* in sm6125, and reference in the
> touchscreen configuration as pinctrl-*. Doesn't have configuration for
> the gpio 83 pin since it isn't documented downstream.
> - Link to v2: https://lore.kernel.org/r/20260114-touchscreen-patches-v2-0-4215f94c8aba@gmail.com
>
> Changes in v2:
> - Fixed name and email in signoffs
> - Link to v1: https://lore.kernel.org/r/20260113-touchscreen-patches-v1-0-a10957f32dd8@gmail.com
>
> ---
> Yedaya Katsman (3):
> dt-bindings: input: touchscreen: edt-ft5x06: Add FocalTech FT3518
> drivers: input: touchscreen: edt-ft5x06: Add FocalTech FT3518
> arm64: dts: qcom: sm6125-xiaomi-laurel-sprout: Add Focaltech FT3518 touchscreen
>
> .../bindings/input/touchscreen/edt-ft5x06.yaml | 1 +
> .../boot/dts/qcom/sm6125-xiaomi-laurel-sprout.dts | 113 +++++++++++++++++++++
> drivers/input/touchscreen/edt-ft5x06.c | 6 ++
> 3 files changed, 120 insertions(+)
> ---
> base-commit: b71e635feefc852405b14620a7fc58c4c80c0f73
> change-id: 20260113-touchscreen-patches-beb2526bd5fb
>
> Best regards,
> --
> Yedaya Katsman <yedaya.ka@gmail.com>
>
>
>
My bot found new DTB warnings on the .dts files added or changed in this
series.
Some warnings may be from an existing SoC .dtsi. Or perhaps the warnings
are fixed by another series. Ultimately, it is up to the platform
maintainer whether these warnings are acceptable or not. No need to reply
unless the platform maintainer has comments.
If you already ran DT checks and didn't see these error(s), then
make sure dt-schema is up to date:
pip3 install dtschema --upgrade
This patch series was applied (using b4) to base:
Base: b71e635feefc852405b14620a7fc58c4c80c0f73 (use --merge-base to override)
If this is not the correct base, please add 'base-commit' tag
(or use b4 which does this automatically)
Warnings in base: 429
Warnings after series: 431
New warnings running 'make CHECK_DTBS=y for arch/arm64/boot/dts/qcom/' for 20260118-touchscreen-patches-v3-0-1c6a729c5eb4@gmail.com:
arch/arm64/boot/dts/qcom/sm6125-xiaomi-laurel-sprout.dtb: pinctrl@500000 (qcom,sm6125-tlmm): Unevaluated properties are not allowed ('pmx_ts_int_active', 'pmx_ts_int_suspend', 'pmx_ts_release', 'pmx_ts_reset_active', 'pmx_ts_reset_suspend' were unexpected)
from schema $id: http://devicetree.org/schemas/pinctrl/qcom,sm6125-tlmm.yaml
^ permalink raw reply
* Re: [PATCH bpf-next v2 03/13] bpf: Verifier support for KF_IMPLICIT_ARGS
From: Eduard Zingerman @ 2026-01-20 0:03 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau
Cc: Mykyta Yatsenko, Tejun Heo, Alan Maguire, Benjamin Tissoires,
Jiri Kosina, Amery Hung, bpf, linux-kernel, linux-input,
sched-ext
In-Reply-To: <20260116201700.864797-4-ihor.solodrai@linux.dev>
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
> A kernel function bpf_foo marked with KF_IMPLICIT_ARGS flag is
> expected to have two associated types in BTF:
> * `bpf_foo` with a function prototype that omits implicit arguments
> * `bpf_foo_impl` with a function prototype that matches the kernel
> declaration of `bpf_foo`, but doesn't have a ksym associated with
> its name
>
> In order to support kfuncs with implicit arguments, the verifier has
> to know how to resolve a call of `bpf_foo` to the correct BTF function
> prototype and address.
>
> To implement this, in add_kfunc_call() kfunc flags are checked for
> KF_IMPLICIT_ARGS. For such kfuncs a BTF func prototype is adjusted to
> the one found for `bpf_foo_impl` (func_name + "_impl" suffix, by
> convention) function in BTF.
>
> This effectively changes the signature of the `bpf_foo` kfunc in the
> context of verification: from one without implicit args to the one
> with full argument list.
>
> The values of implicit arguments by design are provided by the
> verifier, and so they can only be of particular types. In this patch
> the only allowed implicit arg type is a pointer to struct
> bpf_prog_aux.
>
> In order for the verifier to correctly set an implicit bpf_prog_aux
> arg value at runtime, is_kfunc_arg_prog() is extended to check for the
> arg type. At a point when prog arg is determined in check_kfunc_args()
> the kfunc with implicit args already has a prototype with full
> argument list, so the existing value patch mechanism just works.
>
> If a new kfunc with KF_IMPLICIT_ARG is declared for an existing kfunc
> that uses a __prog argument (a legacy case), the prototype
> substitution works in exactly the same way, assuming the kfunc follows
> the _impl naming convention. The difference is only in how _impl
> prototype is added to the BTF, which is not the verifier's
> concern. See a subsequent resolve_btfids patch for details.
>
> __prog suffix is still supported at this point, but will be removed in
> a subsequent patch, after current users are moved to KF_IMPLICIT_ARGS.
>
> Introduction of KF_IMPLICIT_ARGS revealed an issue with zero-extension
> tracking, because an explicit rX = 0 in place of the verifier-supplied
> argument is now absent if the arg is implicit (the BPF prog doesn't
> pass a dummy NULL anymore). To mitigate this, reset the subreg_def of
> all caller saved registers in check_kfunc_call() [1].
>
> [1] https://lore.kernel.org/bpf/b4a760ef828d40dac7ea6074d39452bb0dc82caa.camel@gmail.com/
>
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
[...]
> @@ -14177,8 +14223,12 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
> }
> }
>
> - for (i = 0; i < CALLER_SAVED_REGS; i++)
> - mark_reg_not_init(env, regs, caller_saved[i]);
> + for (i = 0; i < CALLER_SAVED_REGS; i++) {
> + u32 regno = caller_saved[i];
> +
> + mark_reg_not_init(env, regs, regno);
> + regs[regno].subreg_def = DEF_NOT_SUBREG;
> + }
But we still need to understand why .subreg_def assignment can't be
moved inside mark_reg_not_init().
[...]
^ permalink raw reply
* Re: [PATCH bpf-next v2 10/13] bpf: Migrate bpf_stream_vprintk() to KF_IMPLICIT_ARGS
From: Eduard Zingerman @ 2026-01-20 1:53 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau
Cc: Mykyta Yatsenko, Tejun Heo, Alan Maguire, Benjamin Tissoires,
Jiri Kosina, Amery Hung, bpf, linux-kernel, linux-input,
sched-ext
In-Reply-To: <20260116201700.864797-11-ihor.solodrai@linux.dev>
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
> Implement bpf_stream_vprintk with an implicit bpf_prog_aux argument,
> and remote bpf_stream_vprintk_impl from the kernel.
>
> Update the selftests to use the new API with implicit argument.
>
> bpf_stream_vprintk macro is changed to use the new bpf_stream_vprintk
> kfunc, and the extern definition of bpf_stream_vprintk_impl is
> replaced accordingly.
>
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
Reviewed-by: Eduard Zingerman <eddyz87@gmail.com>
[...]
^ permalink raw reply
* Re: [PATCH bpf-next v2 09/13] bpf: Migrate bpf_task_work_schedule_* kfuncs to KF_IMPLICIT_ARGS
From: Eduard Zingerman @ 2026-01-20 1:52 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau
Cc: Mykyta Yatsenko, Tejun Heo, Alan Maguire, Benjamin Tissoires,
Jiri Kosina, Amery Hung, bpf, linux-kernel, linux-input,
sched-ext
In-Reply-To: <20260116201700.864797-10-ihor.solodrai@linux.dev>
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
> Implement bpf_task_work_schedule_* with an implicit bpf_prog_aux
> argument, and remove corresponding _impl funcs from the kernel.
>
> Update special kfunc checks in the verifier accordingly.
>
> Update the selftests to use the new API with implicit argument.
>
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
Reviewed-by: Eduard Zingerman <eddyz87@gmail.com>
^ permalink raw reply
* Re: [PATCH bpf-next v2 07/13] bpf: Migrate bpf_wq_set_callback_impl() to KF_IMPLICIT_ARGS
From: Eduard Zingerman @ 2026-01-20 1:50 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau
Cc: Mykyta Yatsenko, Tejun Heo, Alan Maguire, Benjamin Tissoires,
Jiri Kosina, Amery Hung, bpf, linux-kernel, linux-input,
sched-ext
In-Reply-To: <20260116201700.864797-8-ihor.solodrai@linux.dev>
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
> Implement bpf_wq_set_callback() with an implicit bpf_prog_aux
> argument, and remove bpf_wq_set_callback_impl().
>
> Update special kfunc checks in the verifier accordingly.
>
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
Reviewed-by: Eduard Zingerman <eddyz87@gmail.com>
^ permalink raw reply
* Re: [PATCH bpf-next v2 00/13] bpf: Kernel functions with KF_IMPLICIT_ARGS
From: Eduard Zingerman @ 2026-01-20 1:49 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau
Cc: Mykyta Yatsenko, Tejun Heo, Alan Maguire, Benjamin Tissoires,
Jiri Kosina, Amery Hung, bpf, linux-kernel, linux-input,
sched-ext
In-Reply-To: <20260116201700.864797-1-ihor.solodrai@linux.dev>
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
[...]
> v1->v2:
> - Replace the following kernel functions with KF_IMPLICIT_ARGS version:
> - bpf_stream_vprintk_impl -> bpf_stream_vprintk
> - bpf_task_work_schedule_resume_impl -> bpf_task_work_schedule_resume
> - bpf_task_work_schedule_signal_impl -> bpf_task_work_schedule_signal
> - bpf_wq_set_callback_impl -> bpf_wq_set_callback_impl
Just to clarify my understanding, this is a breaking change, right?
E.g. bpf_stream_vprintk_impl is no longer in vmlinux.h and on a load
attempt an error is reported:
kfunc 'bpf_stream_vprintk_impl' is referenced but wasn't resolved
Maybe call it out explicitly in the cover letter?
^ permalink raw reply
* Re: [PATCH bpf-next v2 04/13] resolve_btfids: Introduce finalize_btf() step
From: Eduard Zingerman @ 2026-01-20 0:13 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau
Cc: Mykyta Yatsenko, Tejun Heo, Alan Maguire, Benjamin Tissoires,
Jiri Kosina, Amery Hung, bpf, linux-kernel, linux-input,
sched-ext
In-Reply-To: <20260116201700.864797-5-ihor.solodrai@linux.dev>
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
> Since recently [1][2] resolve_btfids executes final adjustments to the
> kernel/module BTF before it's embedded into the target binary.
>
> To keep the implementation simple, a clear and stable "pipeline" of
> how BTF data flows through resolve_btfids would be helpful. Some BTF
> modifications may change the ids of the types, so it is important to
> maintain correct order of operations with respect to .BTF_ids
> resolution too.
>
> This patch refactors the BTF handling to establish the following
> sequence:
> - load target ELF sections
> - load .BTF_ids symbols
> - this will be a dependency of btf2btf transformations in
> subsequent patches
> - load BTF and its base as is
> - (*) btf2btf transformations will happen here
> - finalize_btf(), introduced in this patch
> - does distill base and sort BTF
> - resolve and patch .BTF_ids
>
> This approach helps to avoid fixups in .BTF_ids data in case the ids
> change at any point of BTF processing, because symbol resolution
> happens on the finalized, ready to dump, BTF data.
>
> This also gives flexibility in BTF transformations, because they will
> happen on BTF that is not distilled and/or sorted yet, allowing to
> freely add, remove and modify BTF types.
>
> [1] https://lore.kernel.org/bpf/20251219181321.1283664-1-ihor.solodrai@linux.dev/
> [2] https://lore.kernel.org/bpf/20260109130003.3313716-1-dolinux.peng@gmail.com/
>
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
> @@ -1099,12 +1116,22 @@ int main(int argc, const char **argv)
> if (obj.efile.idlist_shndx == -1 ||
> obj.efile.symbols_shndx == -1) {
> pr_debug("Cannot find .BTF_ids or symbols sections, skip symbols resolution\n");
> - goto dump_btf;
> + resolve_btfids = false;
> }
>
> - if (symbols_collect(&obj))
> + if (resolve_btfids)
> + if (symbols_collect(&obj))
> + goto out;
Nit: check obj.efile.idlist_shndx and obj.efile.symbols_shndx inside symbols_collect()?
To avoid resolve_btfids flag and the `goto dump_btf;` below.
> +
> + if (load_btf(&obj))
> goto out;
>
> + if (finalize_btf(&obj))
> + goto out;
> +
> + if (!resolve_btfids)
> + goto dump_btf;
> +
> if (symbols_resolve(&obj))
> goto out;
>
^ permalink raw reply
* Re: [PATCH bpf-next v2 06/13] selftests/bpf: Add tests for KF_IMPLICIT_ARGS
From: Eduard Zingerman @ 2026-01-20 1:24 UTC (permalink / raw)
To: Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau
Cc: Mykyta Yatsenko, Tejun Heo, Alan Maguire, Benjamin Tissoires,
Jiri Kosina, Amery Hung, bpf, linux-kernel, linux-input,
sched-ext
In-Reply-To: <20260116201700.864797-7-ihor.solodrai@linux.dev>
On Fri, 2026-01-16 at 12:16 -0800, Ihor Solodrai wrote:
> Add trivial end-to-end tests to validate that KF_IMPLICIT_ARGS flag is
> properly handled by both resolve_btfids and the verifier.
>
> Declare kfuncs in bpf_testmod. Check that bpf_prog_aux pointer is set
> in the kfunc implementation. Verify that calls with implicit args and
> a legacy case all work.
>
> Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
[...]
^ permalink raw reply
* Re: [PATCH bpf-next v2 05/13] resolve_btfids: Support for KF_IMPLICIT_ARGS
From: Eduard Zingerman @ 2026-01-20 0:24 UTC (permalink / raw)
To: Ihor Solodrai, Andrii Nakryiko
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Mykyta Yatsenko, Tejun Heo, Alan Maguire,
Benjamin Tissoires, Jiri Kosina, Amery Hung, bpf, linux-kernel,
linux-input, sched-ext
In-Reply-To: <aff3f58f-aa81-44a3-ae5f-078befeceb39@linux.dev>
On Fri, 2026-01-16 at 22:36 -0800, Ihor Solodrai wrote:
[...]
> > > +static int collect_decl_tags(struct btf2btf_context *ctx)
> > > +{
> > > + const u32 type_cnt = btf__type_cnt(ctx->btf);
> > > + struct btf *btf = ctx->btf;
> > > + const struct btf_type *t;
> > > + u32 *tags, *tmp;
> > > + u32 nr_tags = 0;
> > > +
> > > + tags = malloc(type_cnt * sizeof(u32));
> >
> > waste of memory, really, see below
> >
> > > + if (!tags)
> > > + return -ENOMEM;
> > > +
> > > + for (u32 id = 1; id < type_cnt; id++) {
> > > + t = btf__type_by_id(btf, id);
> > > + if (!btf_is_decl_tag(t))
> > > + continue;
> > > + tags[nr_tags++] = id;
> > > + }
> > > +
> > > + if (nr_tags == 0) {
> > > + ctx->decl_tags = NULL;
> > > + free(tags);
> > > + return 0;
> > > + }
> > > +
> > > + tmp = realloc(tags, nr_tags * sizeof(u32));
> > > + if (!tmp) {
> > > + free(tags);
> > > + return -ENOMEM;
> > > + }
> >
> > This is an interesting realloc() usage pattern, it's quite
> > unconventional to preallocate too much memory, and then shrink (in C
> > world)
> >
> > check libbpf's libbpf_add_mem(), that's a generic "primitive" inside
> > the libbpf. Do not reuse it as is, but it should give you an idea of a
> > common pattern: you start with NULL (empty data), when you need to add
> > a new element, you calculate a new array size which normally would be
> > some minimal value (to avoid going through 1 -> 2 -> 4 -> 8, many
> > small and wasteful steps; normally we just jump straight to 16 or so)
> > or some factor of previous size (doesn't have to be 2x,
> > libbpf_add_mem() expands by 25%, for instance).
> >
> > This is a super common approach in C. Please utilize it here as well.
>
> Hi Andrii, thanks for taking a quick look.
>
> I am aware of the typical size doubling (or whatever the multiplier
> is) pattern for growing arrays. Amortized cost and all that.
>
> I don't know if this pre-alloc + shrink is common, but I did use it in
> pahole before [1], for example.
>
> The chain of thought that makes me like it is:
> * if we knew the array size beforehand, we'd simply pre-allocate it
> * here we don't, but we do know an upper limit (and it's not crazy)
> * if we pre-allocate to upper limit, we can use the array without
> worrying about the bounds checks and growing on every use
> * if we care (we might not), we can shrink to the actual size
>
> The dynamic array approach is certainly more generic, and helpers can
> be written to make it easy. But in cases like this - collect something
> once and then use - over-pre-allocating makes more sense to me.
>
> Re waste we are talking <1Mb (~100k types * 4), so it's whatever.
>
> In any case it's not super important, so I don't mind changing this if
> you insist. Being conventional has it's benefits too.
>
> [1] https://git.kernel.org/pub/scm/devel/pahole/pahole.git/tree/btf_encoder.c?h=v1.31#n2182
In my test kernel there are ~70K types and ~300 decl tags.
Allocating an array of 70K elements to store 300 seem to be quite an overkill.
I'd move to what Andrii suggests just to reduce the surprise factor for the reader.
[...]
^ permalink raw reply
* Re: [PATCH v5 3/3] Documentation: thinkpad-acpi - Document doubletap_enable attribute
From: Bagas Sanjaya @ 2026-01-20 0:04 UTC (permalink / raw)
To: Vishnu Sankar, dmitry.torokhov, hmh, hansg, ilpo.jarvinen, corbet,
derekjohn.clark
Cc: mpearson-lenovo, linux-doc, linux-input, linux-kernel,
ibm-acpi-devel, platform-driver-x86, vsankar
In-Reply-To: <20251226235101.106451-4-vishnuocv@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 317 bytes --]
On Sat, Dec 27, 2025 at 08:51:01AM +0900, Vishnu Sankar wrote:
> +Values:
> + * 1 - doubletap events are processed (default)
> + * 0 - doubletap events are filtered out (ignored)
Please separate the bullet list from "Values:" paragraph.
Thanks.
--
An old man doll... just what I always wanted! - Clara
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v5 0/3] TrackPoint doubletap enablement and user control
From: Vishnu Sankar @ 2026-01-19 23:20 UTC (permalink / raw)
To: dmitry.torokhov, hmh, hansg, ilpo.jarvinen, corbet,
derekjohn.clark
Cc: mpearson-lenovo, linux-doc, linux-input, linux-kernel,
ibm-acpi-devel, platform-driver-x86, vsankar
In-Reply-To: <20251226235101.106451-1-vishnuocv@gmail.com>
Hi All,
I’m writing to kindly follow up on the patch series I have submitted.
I haven’t seen any review feedback yet, so I just wanted to check if it might
have been missed in the queue. If there’s anything I should revise, test, or
clarify, I’d be happy to provide it.
Thank you for your time, and sorry for the inconvenience.
On Sat, Dec 27, 2025 at 8:53 AM Vishnu Sankar <vishnuocv@gmail.com> wrote:
>
> This patch series adds support for TrackPoint doubletap with a clear and
> simple separation of responsibilities between drivers:
>
> 1. Firmware enablement (trackpoint.c):
> Automatically enables doubletap on capable hardware during device
> detection.
>
> 2. User control (thinkpad_acpi.c):
> Provides a sysfs interface to enable or disable delivery of doubletap
> events to userspace.
>
> The approach follows the KISS principle:
> - The TrackPoint driver enables hardware functionality by default.
> - The thinkpad_acpi driver controls whether ACPI doubletap events are
> delivered, using existing hotkey filtering infrastructure.
> - No cross-driver APIs or dual filtering paths are introduced.
>
> Changes in v5:
> - Rename sysfs attribute from doubletap_filter to doubletap_enable to
> reflect actual behavior.
> - Fix inverted logic so events are delivered only when doubletap is
> enabled.
> - Suppress ACPI hotkey delivery instead of injecting or filtering input
> events.
> - Register the sysfs attribute via hotkey_attributes[] instead of
> device_create_file().
> - Drop unnecessary helper wrappers and debug logging.
> - Update Documentation to reflect the new naming and semantics.
>
> Changes in v4:
> - Complete redesign based on reviewer feedback.
> - trackpoint.c: Simplified to only enable doubletap by default.
> - trackpoint.c: Removed all sysfs attributes and global variables.
> - trackpoint.c: Uses firmware ID detection with deny list.
> - thinkpad_acpi.c: Added sysfs interface for kernel-level event control.
> - thinkpad_acpi.c: No cross-driver dependencies.
> - Documentation: Updated to reflect simplified sysfs approach.
>
> Changes in v3:
> - No changes.
>
> Changes in v2:
> - Improved commit messages.
> - Removed unnecessary comments and debug messages.
> - Switched to strstarts() usage.
> - Simplified firmware capability detection logic.
>
> This version addresses the remaining review feedback by correcting the
> naming and logic inversion, aligning sysfs semantics with behavior, and
> fully integrating with existing thinkpad_acpi hotkey handling.
>
> Vishnu Sankar (3):
> input: trackpoint - Enable doubletap by default on capable devices
> platform/x86: thinkpad_acpi: Add sysfs control for TrackPoint
> double-tap
> Documentation: thinkpad-acpi - Document doubletap_enable attribute
>
> .../admin-guide/laptops/thinkpad-acpi.rst | 20 ++++++++
> drivers/input/mouse/trackpoint.c | 47 +++++++++++++++++++
> drivers/input/mouse/trackpoint.h | 5 ++
> drivers/platform/x86/lenovo/thinkpad_acpi.c | 42 ++++++++++++++---
> 4 files changed, 107 insertions(+), 7 deletions(-)
>
> --
> 2.51.0
>
--
Regards,
Vishnu Sankar
+817015150407 (Japan)
^ permalink raw reply
* Re: [PATCH v5 4/4] Input: Add TouchNetix aXiom I2C Touchscreen support
From: Marco Felsch @ 2026-01-19 21:17 UTC (permalink / raw)
To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
Rafael J. Wysocki, Andrew Morton, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
Marco Felsch, Henrik Rydberg, Danilo Krummrich
Cc: linux-kernel, devicetree, linux-input
In-Reply-To: <20260111-v6-10-topic-touchscreen-axiom-v5-4-f94e0ae266cb@pengutronix.de>
Hi Dmitry,
gentle ping since we never received feedback from one of the input
maintainers.
Regards,
Marco
On 26-01-11, Marco Felsch wrote:
> This adds the initial support for the TouchNetix AX54A touchcontroller
> which is part of TouchNetix's aXiom touchscreen controller family.
>
> The TouchNetix aXiom family provides two physical interfaces: SPI and
> I2C. This patch covers only the I2C interface.
>
> Apart the input event handling the driver supports firmware updates too.
> One firmware interface handles the touchcontroller firmware (AXFW and
> ALC) update the other handles the touchcontroller configuration
> (TH2CFGBIN) update.
>
> Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
> ---
> .../testing/sysfs-driver-input-touchnetix-axiom | 81 +
> drivers/input/touchscreen/Kconfig | 17 +
> drivers/input/touchscreen/Makefile | 1 +
> drivers/input/touchscreen/touchnetix_axiom.c | 2974 ++++++++++++++++++++
> 4 files changed, 3073 insertions(+)
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-input-touchnetix-axiom b/Documentation/ABI/testing/sysfs-driver-input-touchnetix-axiom
> new file mode 100644
> index 0000000000000000000000000000000000000000..31c1c6510c55da80659ddf7bea2d0ce681fde323
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-input-touchnetix-axiom
> @@ -0,0 +1,81 @@
> +What: /sys/bus/i2c/devices/xxx/fw_major
> +Date: Jan 2026
> +Contact: linux-input@vger.kernel.org
> +Description:
> + Reports the firmware major version provided by the touchscreen.
> +
> + Access: Read
> +
> + Valid values: Represented as string
> +
> +What: /sys/bus/i2c/devices/xxx/fw_minor
> +Date: Jan 2026
> +Contact: linux-input@vger.kernel.org
> +Description:
> + Reports the firmware minor version provided by the touchscreen.
> +
> + Access: Read
> +
> + Valid values: Represented as string
> +
> +What: /sys/bus/i2c/devices/xxx/fw_rc
> +Date: Jan 2026
> +Contact: linux-input@vger.kernel.org
> +Description:
> + Reports the firmware release canidate version provided by the touchscreen.
> +
> + Access: Read
> +
> + Valid values: Represented as string
> +
> +What: /sys/bus/i2c/devices/xxx/fw_status
> +Date: Jan 2026
> +Contact: linux-input@vger.kernel.org
> +Description:
> + Reports the firmware status provided by the touchscreen. It may
> + be either "release" or "engineering".
> +
> + Access: Read
> +
> + Valid values: Represented as string
> +
> +What: /sys/bus/i2c/devices/xxx/fw_variant
> +Date: Jan 2026
> +Contact: linux-input@vger.kernel.org
> +Description:
> + Reports the firmware variant provided by the touchscreen. It may
> + be either: "3d", "2d", "force" or "unknown".
> +
> + Access: Read
> +
> + Valid values: Represented as string
> +
> +What: /sys/bus/i2c/devices/xxx/device_id
> +Date: Jan 2026
> +Contact: linux-input@vger.kernel.org
> +Description:
> + Reports the touchscreen device id, for example: "54" for the AX54A.
> +
> + Access: Read
> +
> + Valid values: Represented as string
> +
> +What: /sys/bus/i2c/devices/xxx/device_state
> +Date: Jan 2026
> +Contact: linux-input@vger.kernel.org
> +Description:
> + Reports the touchscreen device current runtime state. The
> + following values are reported:
> +
> + discovery: Device is in discovery mode.
> + tcp: Device is in touch-control-protocol (tcp) mode. This is
> + the normal working mode.
> + th2cfg-update: Device is in configuration update mode.
> + bootloader-pre: Device bootloader mode enter was triggered
> + bootloader: Device is in bootloader mode, used for firmware
> + updates.
> + unknown: Device mode is unknown.
> +
> + Access: Read
> +
> + Valid values: Represented as string
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 7d5b72ee07fa1313da39a625b5129a0459720865..449ae5e29cb4bb1f5335afdee82e91f0aa30a209 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -828,6 +828,23 @@ config TOUCHSCREEN_MIGOR
> To compile this driver as a module, choose M here: the
> module will be called migor_ts.
>
> +config TOUCHSCREEN_TOUCHNETIX_AXIOM
> + tristate "TouchNetix aXiom based touchscreen controllers"
> + # We need to call into panel code so if DRM=m, this can't be 'y'
> + depends on DRM || !DRM
> + depends on I2C
> + select CRC16
> + select CRC32
> + select REGMAP_I2C
> + help
> + Say Y here if you have a axiom touchscreen connected to
> + your system.
> +
> + If unsure, say N.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called touchnetix_axiom.
> +
> config TOUCHSCREEN_TOUCHRIGHT
> tristate "Touchright serial touchscreen"
> select SERIO
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index ab9abd151078831a4b22d6998e00ef74fe01c356..540df3ada4b2b6ad05ffeba67f44ff262f93c11f 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -90,6 +90,7 @@ obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o
> obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI) += surface3_spi.o
> obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC) += ti_am335x_tsc.o
> obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o
> +obj-$(CONFIG_TOUCHSCREEN_TOUCHNETIX_AXIOM) += touchnetix_axiom.o
> obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o
> obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o
> obj-$(CONFIG_TOUCHSCREEN_TS4800) += ts4800-ts.o
> diff --git a/drivers/input/touchscreen/touchnetix_axiom.c b/drivers/input/touchscreen/touchnetix_axiom.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..e8f56a8f7e8a83361b04bb858caadf9658fb7e05
> --- /dev/null
> +++ b/drivers/input/touchscreen/touchnetix_axiom.c
> @@ -0,0 +1,2974 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * TouchNetix aXiom Touchscreen Driver
> + *
> + * Copyright (C) 2024 Pengutronix
> + *
> + * Marco Felsch <kernel@pengutronix.de>
> + */
> +
> +#include <drm/drm_panel.h>
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/completion.h>
> +#include <linux/crc16.h>
> +#include <linux/crc32.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/firmware.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/input.h>
> +#include <linux/input/mt.h>
> +#include <linux/input/touchscreen.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/time.h>
> +#include <linux/unaligned.h>
> +
> +/*
> + * Short introduction for developers:
> + * The programming manual is written based on u(sages):
> + * - Max. 0xff usages possible
> + * - A usage is a group of registers (0x00 ... 0xff)
> + * - The usage base address must be discovered (FW dependent)
> + * - Partial RW usage access is allowed
> + * - Each usage has a revision (FW dependent)
> + * - Only u31 is always at address 0x0 (used for discovery)
> + *
> + * E.x. Reading register 0x01 for usage u03 with baseaddr 0x20 results in the
> + * following physical 16bit I2C address: 0x2001.
> + *
> + * Note the datasheet specifies the usage numbers in hex and the internal
> + * offsets in decimal. Keep it that way to make it more developer friendly.
> + */
> +#define AXIOM_U01 0x01
> +#define AXIOM_U01_REV1_REPORTTYPE_REG 0
> +#define AXIOM_U01_REV1_REPORTTYPE_HELLO 0
> +#define AXIOM_U01_REV1_REPORTTYPE_HEARTBEAT 1
> +#define AXIOM_U01_REV1_REPORTTYPE_OPCOMPLETE 3
> +
> +#define AXIOM_U02 0x02
> +#define AXIOM_U02_REV1_COMMAND_REG 0
> +#define AXIOM_U02_REV1_CMD_HARDRESET 0x0001
> +#define AXIOM_U02_REV1_CMD_SOFTRESET 0x0002
> +#define AXIOM_U02_REV1_CMD_STOP 0x0005
> +#define AXIOM_U02_REV1_CMD_SAVEVLTLCFG2NVM 0x0007
> +#define AXIOM_U02_REV1_PARAM1_SAVEVLTLCFG2NVM 0xb10c
> +#define AXIOM_U02_REV1_PARAM2_SAVEVLTLCFG2NVM 0xc0de
> +#define AXIOM_U02_REV1_CMD_HANDSHAKENVM 0x0008
> +#define AXIOM_U02_REV1_CMD_COMPUTECRCS 0x0009
> +#define AXIOM_U02_REV1_CMD_FILLCONFIG 0x000a
> +#define AXIOM_U02_REV1_PARAM0_FILLCONFIG 0x5555
> +#define AXIOM_U02_REV1_PARAM1_FILLCONFIG 0xaaaa
> +#define AXIOM_U02_REV1_PARAM2_FILLCONFIG_ZERO 0xa55a
> +#define AXIOM_U02_REV1_CMD_ENTERBOOTLOADER 0x000b
> +#define AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY1 0x5555
> +#define AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY2 0xaaaa
> +#define AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY3 0xa55a
> +#define AXIOM_U02_REV1_RESP_SUCCESS 0x0000
> +
> +struct axiom_u02_rev1_system_manager_msg {
> + union {
> + __le16 command;
> + __le16 response;
> + };
> + __le16 parameters[3];
> +};
> +
> +#define AXIOM_U04 0x04
> +#define AXIOM_U04_REV1_SIZE_BYTES 128
> +
> +#define AXIOM_U05 0x05 /* CDU */
> +
> +#define AXIOM_U22 0x22 /* CDU */
> +
> +#define AXIOM_U31 0x31
> +#define AXIOM_U31_REV1_PAGE0 0x0000
> +#define AXIOM_U31_REV1_DEVICE_ID_LOW_REG (AXIOM_U31_REV1_PAGE0 + 0)
> +#define AXIOM_U31_REV1_DEVICE_ID_HIGH_REG (AXIOM_U31_REV1_PAGE0 + 1)
> +#define AXIOM_U31_REV1_MODE_MASK BIT(7)
> +#define AXIOM_U31_REV1_MODE_BLP 1
> +#define AXIOM_U31_REV1_DEVICE_ID_HIGH_MASK GENMASK(6, 0)
> +#define AXIOM_U31_REV1_RUNTIME_FW_MIN_REG (AXIOM_U31_REV1_PAGE0 + 2)
> +#define AXIOM_U31_REV1_RUNTIME_FW_MAJ_REG (AXIOM_U31_REV1_PAGE0 + 3)
> +#define AXIOM_U31_REV1_RUNTIME_FW_STATUS_REG (AXIOM_U31_REV1_PAGE0 + 4)
> +#define AXIOM_U31_REV1_RUNTIME_FW_STATUS BIT(7)
> +#define AXIOM_U31_REV1_RUNTIME_FW_VARIANT GENMASK(6, 0)
> +#define AXIOM_U31_REV1_JEDEC_ID_LOW_REG (AXIOM_U31_REV1_PAGE0 + 8)
> +#define AXIOM_U31_REV1_JEDEC_ID_HIGH_REG (AXIOM_U31_REV1_PAGE0 + 9)
> +#define AXIOM_U31_REV1_NUM_USAGES_REG (AXIOM_U31_REV1_PAGE0 + 10)
> +#define AXIOM_U31_REV1_RUNTIME_FW_RC_REG (AXIOM_U31_REV1_PAGE0 + 11)
> +#define AXIOM_U31_REV1_RUNTIME_FW_RC_MASK GENMASK(7, 4)
> +#define AXIOM_U31_REV1_SILICON_REV_MASK GENMASK(3, 0)
> +
> +#define AXIOM_U31_REV1_PAGE1 0x0100
> +#define AXIOM_U31_REV1_OFFSET_TYPE_MASK BIT(7)
> +#define AXIOM_U31_REV1_MAX_OFFSET_MASK GENMASK(6, 0)
> +
> +#define AXIOM_U32 0x32
> +
> +struct axiom_u31_usage_table_entry {
> + u8 usage_num;
> + u8 start_page;
> + u8 num_pages;
> + u8 max_offset;
> + u8 uifrevision;
> + u8 reserved;
> +} __packed;
> +
> +#define AXIOM_U33 0x33
> +
> +struct axiom_u33_rev2 {
> + __le32 runtime_crc;
> + __le32 runtime_nvm_crc;
> + __le32 bootloader_crc;
> + __le32 nvltlusageconfig_crc;
> + __le32 vltusageconfig_crc;
> + __le32 u22_sequencedata_crc;
> + __le32 u43_hotspots_crc;
> + __le32 u93_profiles_crc;
> + __le32 u94_deltascalemap_crc;
> + __le32 runtimehash_crc;
> +};
> +
> +struct axiom_u33_rev3 {
> + __le32 runtime_crc;
> + __le32 runtime_nvm_crc;
> + __le32 bootloader_crc;
> + __le32 nvltlusageconfig_crc;
> + __le32 vltusageconfig_crc;
> + __le32 u22_sequencedata_crc;
> + __le32 u43_hotspots_crc;
> + __le32 u77_dod_data_crc;
> + __le32 u93_profiles_crc;
> + __le32 u94_deltascalemap_crc;
> + __le32 runtimehash_crc;
> +};
> +
> +#define AXIOM_U34 0x34
> +#define AXIOM_U34_REV1_OVERFLOW_MASK BIT(7)
> +#define AXIOM_U34_REV1_REPORTLENGTH_MASK GENMASK(6, 0)
> +#define AXIOM_U34_REV1_PREAMBLE_BYTES 2
> +#define AXIOM_U34_REV1_POSTAMBLE_BYTES 4
> +
> +#define AXIOM_U36 0x36
> +
> +#define AXIOM_U41 0x41
> +#define AXIOM_U41_REV2_TARGETSTATUS_REG 0
> +#define AXIOM_U41_REV2_X_REG(id) ((4 * (id)) + 2)
> +#define AXIOM_U41_REV2_Y_REG(id) ((4 * (id)) + 4)
> +#define AXIOM_U41_REV2_Z_REG(id) ((id) + 42)
> +
> +#define AXIOM_U42 0x42
> +#define AXIOM_U42_REV1_REPORT_ID_CONTAINS(id) ((id) + 2)
> +#define AXIOM_U42_REV1_REPORT_ID_TOUCH 1 /* Touch, Proximity, Hover */
> +
> +#define AXIOM_U42_REV4_REPORT_ID_CONTAINS(id) ((id) + 8)
> +#define AXIOM_U42_REV4_REPORT_ID_TOUCH 1 /* Touch, Proximity, Hover */
> +
> +#define AXIOM_U43 0x43 /* CDU */
> +
> +#define AXIOM_U64 0x64
> +#define AXIOM_U64_REV2_ENABLECDSPROCESSING_REG 0
> +#define AXIOM_U64_REV2_ENABLECDSPROCESSING_MASK BIT(0)
> +
> +#define AXIOM_U77 0x77 /* CDU */
> +#define AXIOM_U82 0x82
> +#define AXIOM_U93 0x93 /* CDU */
> +#define AXIOM_U94 0x94 /* CDU */
> +
> +/*
> + * Axiom CDU usage structure copied from downstream CDU_Common.py. Downstream
> + * doesn't mention any revision. According downstream all CDU register windows
> + * are 56 byte wide (8 byte header + 48 byte data).
> + */
> +#define AXIOM_CDU_CMD_STORE 0x0002
> +#define AXIOM_CDU_CMD_COMMIT 0x0003
> +#define AXIOM_CDU_PARAM0_COMMIT 0xb10c
> +#define AXIOM_CDU_PARAM1_COMMIT 0xc0de
> +
> +#define AXIOM_CDU_RESP_SUCCESS 0x0000
> +#define AXIOM_CDU_MAX_DATA_BYTES 48
> +
> +struct axiom_cdu_usage {
> + union {
> + __le16 command;
> + __le16 response;
> + };
> + __le16 parameters[3];
> + u8 data[AXIOM_CDU_MAX_DATA_BYTES];
> +};
> +
> +/*
> + * u01 for the bootloader protocol (BLP)
> + *
> + * Values taken from Bootloader.py [1] which had a comment that documentation
> + * values are out dated. The BLP does not have different versions according the
> + * documentation python helper.
> + *
> + * [1] https://github.com/TouchNetix/axiom_pylib
> + */
> +#define AXIOM_U01_BLP_COMMAND_REG 0x0100
> +#define AXIOM_U01_BLP_COMMAND_RESET BIT(1)
> +#define AXIOM_U01_BLP_SATUS_REG 0x0100
> +#define AXIOM_U01_BLP_STATUS_BUSY BIT(0)
> +#define AXIOM_U01_BLP_FIFO_REG 0x0102
> +#define AXIOM_U01_BLP_FIFO_CHK_SIZE_BYTES 255
> +
> +#define AXIOM_PROX_LEVEL -128
> +#define AXIOM_STARTUP_TIME_MS 110
> +
> +#define AXIOM_USAGE_BASEADDR_MASK GENMASK(15, 8)
> +#define AXIOM_MAX_USAGES 256 /* u00 - uFF */
> +/*
> + * The devices have a 16bit ADC but Touchnetix used the lower two bits for other
> + * information.
> + */
> +#define AXIOM_MAX_XY (65535 - 3)
> +#define AXIOM_DEFAULT_POLL_INTERVAL_MS 10
> +#define AXIOM_PAGE_BYTE_LEN 256
> +#define AXIOM_MAX_XFERLEN 0x7fff
> +#define AXIOM_MAX_TOUCHSLOTS 10
> +#define AXIOM_MAX_TOUCHSLOTS_MASK GENMASK(9, 0)
> +
> +/* aXiom firmware (.axfw) */
> +#define AXIOM_FW_AXFW_SIGNATURE "AXFW"
> +#define AXIOM_FW_AXFW_FILE_FMT_VER 0x0200
> +
> +struct axiom_fw_axfw_hdr {
> + u8 signature[4];
> + __le32 file_crc32;
> + __le16 file_format_ver;
> + __le16 device_id;
> + u8 variant;
> + u8 minor_ver;
> + u8 major_ver;
> + u8 rc_ver;
> + u8 status;
> + __le16 silicon_ver;
> + u8 silicon_rev;
> + __le32 fw_crc32;
> +} __packed;
> +
> +struct axiom_fw_axfw_chunk_hdr {
> + u8 internal[6]; /* no description */
> + __be16 payload_length;
> +};
> +
> +/* aXiom config (.th2cfgbin) */
> +#define AXIOM_FW_CFG_SIGNATURE 0x20071969
> +
> +struct axiom_fw_cfg_hdr {
> + __be32 signature;
> + __le16 file_format_ver;
> + __le16 tcp_file_rev_major;
> + __le16 tcp_file_rev_minor;
> + __le16 tcp_file_rev_patch;
> + u8 tcp_version;
> +} __packed;
> +
> +struct axiom_fw_cfg_chunk_hdr {
> + u8 usage_num;
> + u8 usage_rev;
> + u8 reserved;
> + __le16 usage_length;
> +} __packed;
> +
> +struct axiom_fw_cfg_chunk {
> + u8 usage_num;
> + u8 usage_rev;
> + u16 usage_length;
> + const u8 *usage_content;
> +};
> +
> +enum axiom_fw_type {
> + AXIOM_FW_AXFW,
> + AXIOM_FW_CFG,
> + AXIOM_FW_NUM
> +};
> +
> +enum axiom_crc_type {
> + AXIOM_CRC_CUR,
> + AXIOM_CRC_NEW,
> + AXIOM_CRC_NUM
> +};
> +
> +struct axiom_data;
> +
> +struct axiom_usage_info {
> + unsigned char usage_num; /* uXX number (XX in hex) */
> + unsigned int rev_num; /* rev.X (X in dec) */
> + bool is_cdu;
> + bool is_ro;
> +
> + /* Optional hooks */
> + int (*process_report)(struct axiom_data *ts, const u8 *buf, size_t bufsize);
> +};
> +
> +enum axiom_runmode {
> + AXIOM_DISCOVERY_MODE,
> + AXIOM_TCP_MODE,
> + AXIOM_TCP_CFG_UPDATE_MODE,
> + AXIOM_BLP_PRE_MODE,
> + AXIOM_BLP_MODE,
> +};
> +
> +struct axiom_data {
> + struct input_dev *input;
> + struct device *dev;
> +
> + struct gpio_desc *reset_gpio;
> + struct regulator_bulk_data supplies[2];
> + unsigned int num_supplies;
> +
> + struct regmap *regmap;
> + struct touchscreen_properties prop;
> + bool irq_setup_done;
> + u32 poll_interval;
> +
> + struct drm_panel_follower panel_follower;
> + bool is_panel_follower;
> +
> + enum axiom_runmode mode;
> + /*
> + * Two completion types to support firmware updates
> + * in irq and poll mode.
> + */
> + struct axiom_completion {
> + struct completion completion;
> + bool poll_done;
> + } nvm_write, boot_complete;
> +
> + /* Lock to protect both firmware interfaces */
> + struct mutex fwupdate_lock;
> + struct axiom_firmware {
> + /* Lock to protect cancel */
> + struct mutex lock;
> + bool cancel;
> + struct fw_upload *fwl;
> + } fw[AXIOM_FW_NUM];
> +
> + unsigned int fw_major;
> + unsigned int fw_minor;
> + unsigned int fw_rc;
> + unsigned int fw_status;
> + unsigned int fw_variant;
> + u16 device_id;
> + u16 jedec_id;
> + u8 silicon_rev;
> +
> + /* CRCs we need to check during a config update */
> + struct axiom_crc {
> + u32 runtime;
> + u32 vltusageconfig;
> + u32 nvltlusageconfig;
> + u32 u22_sequencedata;
> + u32 u43_hotspots;
> + u32 u77_dod_data;
> + u32 u93_profiles;
> + u32 u94_deltascalemap;
> + } crc[AXIOM_CRC_NUM];
> +
> + bool cds_enabled;
> + unsigned long enabled_slots;
> + unsigned int num_slots;
> +
> + unsigned int max_report_byte_len;
> + struct axiom_usage_table_entry {
> + bool populated;
> + unsigned int baseaddr;
> + unsigned int size_bytes;
> + const struct axiom_usage_info *info;
> + } usage_table[AXIOM_MAX_USAGES];
> +};
> +
> +static int axiom_u01_rev1_process_report(struct axiom_data *ts, const u8 *buf,
> + size_t bufsize);
> +static int axiom_u34_rev1_process_report(struct axiom_data *ts, const u8 *_buf,
> + size_t bufsize);
> +static int axiom_u41_rev2_process_report(struct axiom_data *ts, const u8 *buf,
> + size_t bufsize);
> +
> +#define AXIOM_USAGE(num, rev) \
> + { \
> + .usage_num = num, \
> + .rev_num = rev, \
> + }
> +
> +#define AXIOM_RO_USAGE(num, rev) \
> + { \
> + .usage_num = num, \
> + .rev_num = rev, \
> + .is_ro = true, \
> + }
> +
> +#define AXIOM_CDU_USAGE(num, rev) \
> + { \
> + .usage_num = num, \
> + .rev_num = rev, \
> + .is_cdu = true, \
> + }
> +
> +#define AXIOM_REPORT_USAGE(num, rev, func) \
> + { \
> + .usage_num = num, \
> + .rev_num = rev, \
> + .process_report = func, \
> + }
> +
> +#define AXIOM_USAGE_REV_UNUSED (-1)
> +
> +/*
> + * All usages used by driver must be added to this list to ensure the correct
> + * communictation with the devices. The list can contain multiple entries of the
> + * same usage to handle different usage revisions.
> + *
> + * Note:
> + * During a th2cfgbin update the driver may use usages not listed here.
> + * Therefore the th2cfgbin update compares the current running FW again the
> + * th2cfgbin targets FW.
> + */
> +static const struct axiom_usage_info driver_required_usages[] = {
> + AXIOM_REPORT_USAGE(AXIOM_U01, 1, axiom_u01_rev1_process_report),
> + AXIOM_REPORT_USAGE(AXIOM_U01, 3, axiom_u01_rev1_process_report),
> + AXIOM_USAGE(AXIOM_U02, 1),
> + AXIOM_USAGE(AXIOM_U02, 2),
> + AXIOM_USAGE(AXIOM_U04, 1),
> + AXIOM_RO_USAGE(AXIOM_U33, 2),
> + AXIOM_RO_USAGE(AXIOM_U33, 3),
> + AXIOM_REPORT_USAGE(AXIOM_U34, 1, axiom_u34_rev1_process_report),
> + AXIOM_REPORT_USAGE(AXIOM_U41, 2, axiom_u41_rev2_process_report),
> + AXIOM_REPORT_USAGE(AXIOM_U41, 4, axiom_u41_rev2_process_report),
> + AXIOM_USAGE(AXIOM_U42, 1),
> + AXIOM_USAGE(AXIOM_U42, 4),
> + AXIOM_USAGE(AXIOM_U64, 2),
> + AXIOM_USAGE(AXIOM_U64, 4),
> + { /* sentinel */ }
> +};
> +
> +/*
> + * All usages below are unused but the driver needs to know the type (ro, cdu)
> + * to handle them correctly. Unfortunately the type is not discoverable. Once
> + * a usage is actually used, it must be shifted to driver_required_usages and
> + * the revision must be set accordingly.
> + */
> +static const struct axiom_usage_info driver_additional_usages[] = {
> + AXIOM_CDU_USAGE(AXIOM_U05, AXIOM_USAGE_REV_UNUSED),
> + AXIOM_CDU_USAGE(AXIOM_U22, AXIOM_USAGE_REV_UNUSED),
> + AXIOM_RO_USAGE(AXIOM_U31, AXIOM_USAGE_REV_UNUSED),
> + AXIOM_RO_USAGE(AXIOM_U32, AXIOM_USAGE_REV_UNUSED),
> + AXIOM_RO_USAGE(AXIOM_U36, AXIOM_USAGE_REV_UNUSED),
> + AXIOM_CDU_USAGE(AXIOM_U43, AXIOM_USAGE_REV_UNUSED),
> + AXIOM_CDU_USAGE(AXIOM_U77, AXIOM_USAGE_REV_UNUSED),
> + AXIOM_RO_USAGE(AXIOM_U82, AXIOM_USAGE_REV_UNUSED),
> + AXIOM_CDU_USAGE(AXIOM_U93, AXIOM_USAGE_REV_UNUSED),
> + AXIOM_CDU_USAGE(AXIOM_U94, AXIOM_USAGE_REV_UNUSED),
> + { /* sentinel */ }
> +};
> +
> +/************************ Common helpers **************************************/
> +
> +static void axiom_set_runmode(struct axiom_data *ts, enum axiom_runmode mode)
> +{
> + ts->mode = mode;
> +}
> +
> +static enum axiom_runmode axiom_get_runmode(struct axiom_data *ts)
> +{
> + return ts->mode;
> +}
> +
> +static const char *axiom_runmode_to_string(struct axiom_data *ts)
> +{
> + switch (ts->mode) {
> + case AXIOM_DISCOVERY_MODE: return "discovery";
> + case AXIOM_TCP_MODE: return "tcp";
> + case AXIOM_TCP_CFG_UPDATE_MODE: return "th2cfg-update";
> + case AXIOM_BLP_PRE_MODE: return "bootloader-pre";
> + case AXIOM_BLP_MODE: return "bootlaoder";
> + default: return "unknown";
> + }
> +}
> +
> +static bool axiom_skip_usage_check(struct axiom_data *ts)
> +{
> + switch (ts->mode) {
> + case AXIOM_TCP_CFG_UPDATE_MODE:
> + case AXIOM_DISCOVERY_MODE:
> + case AXIOM_BLP_MODE:
> + return true;
> + case AXIOM_BLP_PRE_MODE:
> + case AXIOM_TCP_MODE:
> + default:
> + return false;
> + }
> +}
> +
> +static unsigned int
> +axiom_usage_baseaddr(struct axiom_data *ts, unsigned char usage_num)
> +{
> + return ts->usage_table[usage_num].baseaddr;
> +}
> +
> +static unsigned int
> +axiom_usage_size(struct axiom_data *ts, unsigned char usage_num)
> +{
> + return ts->usage_table[usage_num].size_bytes;
> +}
> +
> +static int
> +axiom_usage_rev(struct axiom_data *ts, unsigned char usage_num)
> +{
> + struct axiom_usage_table_entry *entry = &ts->usage_table[usage_num];
> +
> + if (!entry->info)
> + return -EINVAL;
> +
> + return entry->info->rev_num;
> +}
> +
> +static bool
> +axiom_driver_supports_usage(struct axiom_data *ts, unsigned char usage_num)
> +{
> + const struct axiom_usage_info *iter = driver_required_usages;
> + struct device *dev = ts->dev;
> + int rev;
> +
> + /*
> + * Some features depend on the current running firmware. Don't print an
> + * error if the usage for an optional feature is missing.
> + */
> + if (!ts->usage_table[usage_num].populated) {
> + dev_dbg(dev, "u%02X is not supported by the current firmware\n",
> + usage_num);
> + return false;
> + }
> +
> + rev = axiom_usage_rev(ts, usage_num);
> + if (rev < 0) {
> + dev_warn(dev, "Driver doesn't support u%02X yet\n", usage_num);
> + return false;
> + }
> +
> + for (; iter; iter++) {
> + if (iter->usage_num != usage_num)
> + continue;
> +
> + if (iter->rev_num == rev)
> + return true;
> + }
> +
> + dev_warn(dev, "Driver doesn't support u%02X rev.%d yet\n",
> + usage_num, rev);
> +
> + return false;
> +}
> +
> +static bool
> +axiom_usage_entry_is_report(struct axiom_u31_usage_table_entry *entry)
> +{
> + return entry->num_pages == 0;
> +}
> +
> +static unsigned int
> +axiom_get_usage_size_bytes(struct axiom_u31_usage_table_entry *entry)
> +{
> + unsigned char max_offset;
> +
> + max_offset = FIELD_GET(AXIOM_U31_REV1_MAX_OFFSET_MASK,
> + entry->max_offset) + 1;
> + max_offset *= 2;
> +
> + if (axiom_usage_entry_is_report(entry))
> + return max_offset;
> +
> + if (FIELD_GET(AXIOM_U31_REV1_OFFSET_TYPE_MASK, entry->max_offset))
> + return (entry->num_pages - 1) * AXIOM_PAGE_BYTE_LEN + max_offset;
> +
> + return max_offset;
> +}
> +
> +static void axiom_dump_usage_entry(struct device *dev,
> + struct axiom_u31_usage_table_entry *entry)
> +{
> + unsigned int page_len, total_len;
> +
> + total_len = axiom_get_usage_size_bytes(entry);
> +
> + if (total_len > AXIOM_PAGE_BYTE_LEN)
> + page_len = AXIOM_PAGE_BYTE_LEN;
> + else
> + page_len = total_len;
> +
> + if (axiom_usage_entry_is_report(entry))
> + dev_dbg(dev,
> + "u%02X rev.%d total-len:%u [REPORT]\n",
> + entry->usage_num, entry->uifrevision, total_len);
> + else
> + dev_dbg(dev,
> + "u%02X rev.%d first-page:%#02x page-len:%u num-pages:%u total-len:%u\n",
> + entry->usage_num, entry->uifrevision, entry->start_page, page_len,
> + entry->num_pages, total_len);
> +}
> +
> +static const struct axiom_usage_info *
> +axiom_get_usage_info(struct axiom_u31_usage_table_entry *query)
> +{
> + const struct axiom_usage_info *info = driver_required_usages;
> + bool required = false;
> + bool found = false;
> +
> + for (; info->usage_num; info++) {
> + /* Skip all usages not used by the driver */
> + if (query->usage_num != info->usage_num)
> + continue;
> +
> + /* The usage is used so we need to mark it as required */
> + required = true;
> +
> + /* Continue with the next usage if the revision doesn't match */
> + if (query->uifrevision != info->rev_num)
> + continue;
> +
> + found = true;
> + break;
> + }
> +
> + if (found)
> + return info;
> +
> + /* Return an error if not found but required */
> + if (required)
> + return ERR_PTR(-EINVAL);
> +
> + info = driver_additional_usages;
> + for (; info->usage_num; info++) {
> + if (query->usage_num != info->usage_num)
> + continue;
> +
> + /*
> + * No need to check the revision since these usages are not
> + * used actually but the driver needs the type information.
> + */
> + return info;
> + }
> +
> + /* No info found */
> + return NULL;
> +}
> +
> +static bool axiom_usage_supported(struct axiom_data *ts, unsigned int baseaddr)
> +{
> + struct axiom_usage_table_entry *entry;
> + unsigned int i;
> +
> + if (axiom_skip_usage_check(ts))
> + return true;
> +
> + dev_dbg(ts->dev, "Checking support for baseaddr: %#x\n", baseaddr);
> +
> + for (i = 0; i < ARRAY_SIZE(ts->usage_table); i++) {
> + entry = &ts->usage_table[i];
> +
> + if (!entry->populated)
> + continue;
> +
> + if (entry->baseaddr != baseaddr)
> + continue;
> +
> + break;
> + }
> +
> + if (i == ARRAY_SIZE(ts->usage_table)) {
> + dev_warn(ts->dev, "Usage not found\n");
> + return false;
> + }
> +
> + if (!entry->info)
> + dev_warn(ts->dev, "Unsupported usage u%02X used, driver bug!", i);
> +
> + return !!entry->info;
> +}
> +
> +static void axiom_poll(struct input_dev *input);
> +
> +static unsigned long
> +axiom_wait_for_completion_timeout(struct axiom_data *ts, struct axiom_completion *x,
> + long timeout)
> +{
> + struct i2c_client *client = to_i2c_client(ts->dev);
> + unsigned long poll_timeout;
> +
> + if (client->irq)
> + return wait_for_completion_timeout(&x->completion, timeout);
> +
> + /*
> + * Only firmware update cases do wait for completion. Since they require
> + * the input device to be closed, the poller is not running. So we need
> + * to do the polling manually.
> + */
> + poll_timeout = timeout / 10;
> +
> + /*
> + * Very basic and not very accurate but it does the job because there
> + * are no known timeout constraints.
> + */
> + do {
> + axiom_poll(ts->input);
> + fsleep(jiffies_to_usecs(poll_timeout));
> + if (x->poll_done)
> + break;
> + timeout -= poll_timeout;
> + } while (timeout > 0);
> +
> + x->poll_done = false;
> +
> + return timeout > 0 ? timeout : 0;
> +}
> +
> +static void axiom_complete(struct axiom_data *ts, struct axiom_completion *x)
> +{
> + struct i2c_client *client = to_i2c_client(ts->dev);
> +
> + if (client->irq)
> + complete(&x->completion);
> + else
> + x->poll_done = true;
> +}
> +
> +/*************************** Usage handling ***********************************/
> +/*
> + * Wrapper functions to handle the usage access. Wrappers are used to add
> + * different revision handling later on more easily.
> + */
> +static int axiom_u02_wait_idle(struct axiom_data *ts)
> +{
> + unsigned int reg;
> + int ret, _ret;
> + u16 cmd;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U02))
> + return -EINVAL;
> +
> + reg = axiom_usage_baseaddr(ts, AXIOM_U02);
> + reg += AXIOM_U02_REV1_COMMAND_REG;
> +
> + /*
> + * Missing regmap_raw_read_poll_timeout for now. RESP_SUCCESS means that
> + * the last command successfully completed and the device is idle.
> + */
> + ret = read_poll_timeout(regmap_raw_read, _ret,
> + _ret || cmd == AXIOM_U02_REV1_RESP_SUCCESS,
> + 10 * USEC_PER_MSEC, 1 * USEC_PER_SEC, false,
> + ts->regmap, reg, &cmd, 2);
> + if (ret)
> + dev_err(ts->dev, "Poll u02 timedout with: %#x\n", cmd);
> +
> + return ret;
> +}
> +
> +static int
> +axiom_u02_send_msg(struct axiom_data *ts,
> + const struct axiom_u02_rev1_system_manager_msg *msg,
> + bool validate_response)
> +{
> + unsigned int reg;
> + int ret;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U02))
> + return -EINVAL;
> +
> + reg = axiom_usage_baseaddr(ts, AXIOM_U02);
> + reg += AXIOM_U02_REV1_COMMAND_REG;
> +
> + ret = regmap_raw_write(ts->regmap, reg, msg, sizeof(*msg));
> + if (ret)
> + return ret;
> +
> + if (!validate_response)
> + return 0;
> +
> + return axiom_u02_wait_idle(ts);
> +}
> +
> +static int
> +axiom_u02_rev1_send_single_cmd(struct axiom_data *ts, u16 cmd)
> +{
> + struct axiom_u02_rev1_system_manager_msg msg = {
> + .command = cpu_to_le16(cmd)
> + };
> +
> + return axiom_u02_send_msg(ts, &msg, true);
> +}
> +
> +static int axiom_u02_handshakenvm(struct axiom_data *ts)
> +{
> + return axiom_u02_rev1_send_single_cmd(ts, AXIOM_U02_REV1_CMD_HANDSHAKENVM);
> +}
> +
> +static int axiom_u02_computecrc(struct axiom_data *ts)
> +{
> + return axiom_u02_rev1_send_single_cmd(ts, AXIOM_U02_REV1_CMD_COMPUTECRCS);
> +}
> +
> +static int axiom_u02_stop(struct axiom_data *ts)
> +{
> + return axiom_u02_rev1_send_single_cmd(ts, AXIOM_U02_REV1_CMD_STOP);
> +}
> +
> +static int axiom_u02_save_config(struct axiom_data *ts)
> +{
> + struct axiom_u02_rev1_system_manager_msg msg;
> + int ret;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U02))
> + return -EINVAL;
> +
> + msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_SAVEVLTLCFG2NVM);
> + msg.parameters[0] = 0; /* Don't care */
> + msg.parameters[1] = cpu_to_le16(AXIOM_U02_REV1_PARAM1_SAVEVLTLCFG2NVM);
> + msg.parameters[2] = cpu_to_le16(AXIOM_U02_REV1_PARAM2_SAVEVLTLCFG2NVM);
> +
> + ret = axiom_u02_send_msg(ts, &msg, false);
> + if (ret)
> + return ret;
> +
> + /* Downstream axcfg.py waits for 2sec without checking U01 response */
> + ret = axiom_wait_for_completion_timeout(ts, &ts->nvm_write,
> + msecs_to_jiffies(2 * MSEC_PER_SEC));
> + if (!ret)
> + dev_err(ts->dev, "Error save volatile config timedout\n");
> +
> + return ret ? 0 : -ETIMEDOUT;
> +}
> +
> +static int axiom_u02_swreset(struct axiom_data *ts)
> +{
> + struct axiom_u02_rev1_system_manager_msg msg = { };
> + int ret;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U02))
> + return -EINVAL;
> +
> + msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_SOFTRESET);
> + ret = axiom_u02_send_msg(ts, &msg, false);
> + if (ret)
> + return ret;
> +
> + /*
> + * Downstream axcfg.py waits for 1sec without checking U01 hello. Tests
> + * showed that waiting for the hello message isn't enough therefore we
> + * need both to make it robuster.
> + */
> + ret = axiom_wait_for_completion_timeout(ts, &ts->boot_complete,
> + msecs_to_jiffies(1 * MSEC_PER_SEC));
> + if (!ret)
> + dev_err(ts->dev, "Error swreset timedout\n");
> +
> + fsleep(USEC_PER_SEC);
> +
> + return ret ? 0 : -ETIMEDOUT;
> +}
> +
> +static int axiom_u02_fillconfig(struct axiom_data *ts)
> +{
> + struct axiom_u02_rev1_system_manager_msg msg;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U02))
> + return -EINVAL;
> +
> + msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_FILLCONFIG);
> + msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_FILLCONFIG);
> + msg.parameters[1] = cpu_to_le16(AXIOM_U02_REV1_PARAM1_FILLCONFIG);
> + msg.parameters[2] = cpu_to_le16(AXIOM_U02_REV1_PARAM2_FILLCONFIG_ZERO);
> +
> + return axiom_u02_send_msg(ts, &msg, true);
> +}
> +
> +static int axiom_u02_enter_bootloader(struct axiom_data *ts)
> +{
> + struct axiom_u02_rev1_system_manager_msg msg = { };
> + struct device *dev = ts->dev;
> + unsigned int val;
> + int ret;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U02))
> + return -EINVAL;
> +
> + /*
> + * Enter the bootloader mode requires 3 consecutive messages so we can't
> + * check for the response.
> + */
> + msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_ENTERBOOTLOADER);
> + msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY1);
> + ret = axiom_u02_send_msg(ts, &msg, false);
> + if (ret) {
> + dev_err(dev, "Failed to send bootloader-key1: %d\n", ret);
> + return ret;
> + }
> +
> + msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY2);
> + ret = axiom_u02_send_msg(ts, &msg, false);
> + if (ret) {
> + dev_err(dev, "Failed to send bootloader-key2: %d\n", ret);
> + return ret;
> + }
> +
> + msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY3);
> + ret = axiom_u02_send_msg(ts, &msg, false);
> + if (ret) {
> + dev_err(dev, "Failed to send bootloader-key3: %d\n", ret);
> + return ret;
> + }
> +
> + /* Sleep before the first read to give the device time */
> + fsleep(250 * USEC_PER_MSEC);
> +
> + /* Wait till the device reports it is in bootloader mode */
> + return regmap_read_poll_timeout(ts->regmap,
> + AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, val,
> + FIELD_GET(AXIOM_U31_REV1_MODE_MASK, val) ==
> + AXIOM_U31_REV1_MODE_BLP, 250 * USEC_PER_MSEC,
> + USEC_PER_SEC);
> +}
> +
> +static int axiom_u04_get(struct axiom_data *ts, u8 **_buf)
> +{
> + u8 buf[AXIOM_U04_REV1_SIZE_BYTES];
> + unsigned int reg;
> + int ret;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U04))
> + return -EINVAL;
> +
> + reg = axiom_usage_baseaddr(ts, AXIOM_U04);
> + ret = regmap_raw_read(ts->regmap, reg, buf, sizeof(buf));
> + if (ret)
> + return ret;
> +
> + *_buf = kmemdup(buf, sizeof(buf), GFP_KERNEL);
> +
> + return sizeof(buf);
> +}
> +
> +static int axiom_u04_set(struct axiom_data *ts, u8 *buf, unsigned int bufsize)
> +{
> + unsigned int reg;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U04))
> + return -EINVAL;
> +
> + reg = axiom_usage_baseaddr(ts, AXIOM_U04);
> + return regmap_raw_write(ts->regmap, reg, buf, bufsize);
> +}
> +
> +/*
> + * U31 revision must be always rev.1 else the whole self discovery mechanism
> + * fall apart.
> + */
> +static int axiom_u31_parse_device_info(struct axiom_data *ts)
> +{
> + struct regmap *regmap = ts->regmap;
> + unsigned int id_low, id_high, val;
> + int ret;
> +
> + ret = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, &id_high);
> + if (ret)
> + return ret;
> + id_high = FIELD_GET(AXIOM_U31_REV1_DEVICE_ID_HIGH_MASK, id_high);
> +
> + ret = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_LOW_REG, &id_low);
> + if (ret)
> + return ret;
> + ts->device_id = id_high << 8 | id_low;
> +
> + ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_MAJ_REG, &val);
> + if (ret)
> + return ret;
> + ts->fw_major = val;
> +
> + ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_MIN_REG, &val);
> + if (ret)
> + return ret;
> + ts->fw_minor = val;
> +
> + /* All other fields are not allowed to be read in BLP mode */
> + if (axiom_get_runmode(ts) == AXIOM_BLP_MODE)
> + return 0;
> +
> + ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_RC_REG, &val);
> + if (ret)
> + return ret;
> + ts->fw_rc = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_RC_MASK, val);
> + ts->silicon_rev = FIELD_GET(AXIOM_U31_REV1_SILICON_REV_MASK, val);
> +
> + ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_STATUS_REG, &val);
> + if (ret)
> + return ret;
> + ts->fw_status = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_STATUS, val);
> + ts->fw_variant = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_VARIANT, val);
> +
> + ret = regmap_read(regmap, AXIOM_U31_REV1_JEDEC_ID_HIGH_REG, &val);
> + if (ret)
> + return ret;
> + ts->jedec_id = val << 8;
> +
> + ret = regmap_read(regmap, AXIOM_U31_REV1_JEDEC_ID_LOW_REG, &val);
> + if (ret)
> + return ret;
> + ts->jedec_id |= val;
> +
> + return 0;
> +}
> +
> +static int axiom_u33_read(struct axiom_data *ts, struct axiom_crc *crc);
> +
> +static int axiom_u31_device_discover(struct axiom_data *ts)
> +{
> + struct axiom_u31_usage_table_entry *u31_usage_table __free(kfree) = NULL;
> + struct axiom_u31_usage_table_entry *entry;
> + struct regmap *regmap = ts->regmap;
> + unsigned int mode, num_usages;
> + struct device *dev = ts->dev;
> + unsigned int i;
> + int ret;
> +
> + axiom_set_runmode(ts, AXIOM_DISCOVERY_MODE);
> +
> + ret = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, &mode);
> + if (ret) {
> + dev_err(dev, "Failed to read MODE\n");
> + return ret;
> + }
> +
> + /* Abort if the device is in bootloader protocol mode */
> + mode = FIELD_GET(AXIOM_U31_REV1_MODE_MASK, mode);
> + if (mode == AXIOM_U31_REV1_MODE_BLP)
> + axiom_set_runmode(ts, AXIOM_BLP_MODE);
> +
> + /* Since we are not in bootloader mode we can parse the device info */
> + ret = axiom_u31_parse_device_info(ts);
> + if (ret) {
> + dev_err(dev, "Failed to parse device info\n");
> + return ret;
> + }
> +
> + /* All other fields are not allowed to be read in BLP mode */
> + if (axiom_get_runmode(ts) == AXIOM_BLP_MODE) {
> + dev_info(dev, "Device in Bootloader mode, firmware upload required\n");
> + return -EACCES;
> + }
> +
> + ret = regmap_read(regmap, AXIOM_U31_REV1_NUM_USAGES_REG, &num_usages);
> + if (ret) {
> + dev_err(dev, "Failed to read NUM_USAGES\n");
> + return ret;
> + }
> +
> + u31_usage_table = kcalloc(num_usages, sizeof(*u31_usage_table),
> + GFP_KERNEL);
> + if (!u31_usage_table)
> + return -ENOMEM;
> +
> + ret = regmap_raw_read(regmap, AXIOM_U31_REV1_PAGE1, u31_usage_table,
> + array_size(num_usages, sizeof(*u31_usage_table)));
> + if (ret) {
> + dev_err(dev, "Failed to read NUM_USAGES\n");
> + return ret;
> + }
> +
> + /*
> + * axiom_u31_device_discover() is call after fw update too, so ensure
> + * that the usage_table is cleared.
> + */
> + memset(ts->usage_table, 0, sizeof(ts->usage_table));
> +
> + for (i = 0, entry = u31_usage_table; i < num_usages; i++, entry++) {
> + unsigned char idx = entry->usage_num;
> + const struct axiom_usage_info *info;
> + unsigned int size_bytes;
> +
> + axiom_dump_usage_entry(dev, entry);
> +
> + /*
> + * Verify that the driver used usages are supported. Don't abort
> + * yet if a usage isn't supported to allow the user to dump the
> + * actual usage table.
> + */
> + info = axiom_get_usage_info(entry);
> + if (IS_ERR(info)) {
> + dev_info(dev, "Required usage u%02X isn't supported for rev.%u\n",
> + entry->usage_num, entry->uifrevision);
> + ret = -EACCES;
> + }
> +
> + size_bytes = axiom_get_usage_size_bytes(entry);
> +
> + ts->usage_table[idx].baseaddr = entry->start_page << 8;
> + ts->usage_table[idx].size_bytes = size_bytes;
> + ts->usage_table[idx].populated = true;
> + ts->usage_table[idx].info = info;
> +
> + if (axiom_usage_entry_is_report(entry) &&
> + ts->max_report_byte_len < size_bytes)
> + ts->max_report_byte_len = size_bytes;
> + }
> +
> + if (ret)
> + return ret;
> +
> + /* From now on we are in TCP mode to include usage revision checks */
> + axiom_set_runmode(ts, AXIOM_TCP_MODE);
> +
> + return axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
> +}
> +
> +static int axiom_u33_read(struct axiom_data *ts, struct axiom_crc *crc)
> +{
> + struct device *dev = ts->dev;
> + unsigned int reg;
> + int ret;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U33))
> + return -EINVAL;
> +
> + if (axiom_usage_rev(ts, AXIOM_U33) == 2) {
> + struct axiom_u33_rev2 val;
> +
> + reg = axiom_usage_baseaddr(ts, AXIOM_U33);
> + ret = regmap_raw_read(ts->regmap, reg, &val, sizeof(val));
> + if (ret) {
> + dev_err(dev, "Failed to read u33\n");
> + return ret;
> + }
> +
> + crc->runtime = le32_to_cpu(val.runtime_crc);
> + crc->vltusageconfig = le32_to_cpu(val.vltusageconfig_crc);
> + crc->nvltlusageconfig = le32_to_cpu(val.nvltlusageconfig_crc);
> + crc->u22_sequencedata = le32_to_cpu(val.u22_sequencedata_crc);
> + crc->u43_hotspots = le32_to_cpu(val.u43_hotspots_crc);
> + crc->u93_profiles = le32_to_cpu(val.u93_profiles_crc);
> + crc->u94_deltascalemap = le32_to_cpu(val.u94_deltascalemap_crc);
> + } else if (axiom_usage_rev(ts, AXIOM_U33) == 3) {
> + struct axiom_u33_rev3 val;
> +
> + reg = axiom_usage_baseaddr(ts, AXIOM_U33);
> + ret = regmap_raw_read(ts->regmap, reg, &val, sizeof(val));
> + if (ret) {
> + dev_err(dev, "Failed to read u33\n");
> + return ret;
> + }
> +
> + crc->runtime = le32_to_cpu(val.runtime_crc);
> + crc->vltusageconfig = le32_to_cpu(val.vltusageconfig_crc);
> + crc->nvltlusageconfig = le32_to_cpu(val.nvltlusageconfig_crc);
> + crc->u22_sequencedata = le32_to_cpu(val.u22_sequencedata_crc);
> + crc->u43_hotspots = le32_to_cpu(val.u43_hotspots_crc);
> + crc->u77_dod_data = le32_to_cpu(val.u77_dod_data_crc);
> + crc->u93_profiles = le32_to_cpu(val.u93_profiles_crc);
> + crc->u94_deltascalemap = le32_to_cpu(val.u94_deltascalemap_crc);
> + }
> +
> + return 0;
> +}
> +
> +static bool axiom_u42_touch_enabled(struct axiom_data *ts, const u8 *buf,
> + unsigned int touch_num)
> +{
> + switch (axiom_usage_rev(ts, AXIOM_U42)) {
> + case 1:
> + return buf[AXIOM_U42_REV1_REPORT_ID_CONTAINS(touch_num)] ==
> + AXIOM_U42_REV1_REPORT_ID_TOUCH;
> + case 4:
> + return buf[AXIOM_U42_REV4_REPORT_ID_CONTAINS(touch_num)] ==
> + AXIOM_U42_REV4_REPORT_ID_TOUCH;
> + default:
> + /* Should never happen */
> + return false;
> + }
> +}
> +
> +static void axiom_u42_get_touchslots(struct axiom_data *ts)
> +{
> + u8 *buf __free(kfree) = NULL;
> + struct device *dev = ts->dev;
> + unsigned int bufsize;
> + unsigned int reg;
> + int ret, i;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U42)) {
> + dev_warn(dev, "Use default touchslots num\n");
> + goto fallback;
> + }
> +
> + bufsize = axiom_usage_size(ts, AXIOM_U42);
> + buf = kzalloc(bufsize, GFP_KERNEL);
> + if (!buf) {
> + dev_warn(dev, "Failed to alloc u42 read buffer, use default value\n");
> + goto fallback;
> + }
> +
> + reg = axiom_usage_baseaddr(ts, AXIOM_U42);
> + ret = regmap_raw_read(ts->regmap, reg, buf, bufsize);
> + if (ret) {
> + dev_warn(dev, "Failed to read u42, use default value\n");
> + goto fallback;
> + }
> +
> + ts->enabled_slots = 0;
> + ts->num_slots = 0;
> +
> + for (i = 0; i < AXIOM_MAX_TOUCHSLOTS; i++) {
> + if (axiom_u42_touch_enabled(ts, buf, i)) {
> + ts->enabled_slots |= BIT(i);
> + ts->num_slots++;
> + }
> + }
> +
> + return;
> +
> +fallback:
> + ts->enabled_slots = AXIOM_MAX_TOUCHSLOTS_MASK;
> + ts->num_slots = AXIOM_MAX_TOUCHSLOTS;
> +}
> +
> +static void axiom_u64_cds_enabled(struct axiom_data *ts)
> +{
> + unsigned int reg, val;
> + int ret;
> +
> + if (!axiom_driver_supports_usage(ts, AXIOM_U64))
> + goto fallback_out;
> +
> + reg = axiom_usage_baseaddr(ts, AXIOM_U64);
> + reg += AXIOM_U64_REV2_ENABLECDSPROCESSING_REG;
> +
> + ret = regmap_read(ts->regmap, reg, &val);
> + if (ret)
> + goto fallback_out;
> +
> + val = FIELD_GET(AXIOM_U64_REV2_ENABLECDSPROCESSING_MASK, val);
> + ts->cds_enabled = val ? true : false;
> +
> + return;
> +
> +fallback_out:
> + ts->cds_enabled = false;
> +}
> +
> +static int axiom_cdu_wait_idle(struct axiom_data *ts, u8 cdu_usage_num)
> +{
> + unsigned int reg;
> + int ret, _ret;
> + u16 cmd;
> +
> + reg = axiom_usage_baseaddr(ts, cdu_usage_num);
> +
> + /*
> + * Missing regmap_raw_read_poll_timeout for now. RESP_SUCCESS means that
> + * the last command successfully completed and the device is idle.
> + */
> + ret = read_poll_timeout(regmap_raw_read, _ret,
> + _ret || cmd == AXIOM_CDU_RESP_SUCCESS,
> + 10 * USEC_PER_MSEC, 1 * USEC_PER_SEC, false,
> + ts->regmap, reg, &cmd, 2);
> + if (ret)
> + dev_err(ts->dev, "Poll CDU u%02X timedout with: %#x\n",
> + cdu_usage_num, cmd);
> +
> + return ret;
> +}
> +
> +/*********************** Report usage handling ********************************/
> +
> +static int axiom_process_report(struct axiom_data *ts, unsigned char usage_num,
> + const u8 *buf, size_t buflen)
> +{
> + struct axiom_usage_table_entry *entry = &ts->usage_table[usage_num];
> +
> + /* Skip processing if not in TCP mode */
> + if ((axiom_get_runmode(ts) != AXIOM_TCP_MODE) &&
> + (axiom_get_runmode(ts) != AXIOM_TCP_CFG_UPDATE_MODE))
> + return 0;
> +
> + /* May happen if an unsupported usage was requested */
> + if (!entry) {
> + dev_info(ts->dev, "Unsupported usage U%x request\n", usage_num);
> + return 0;
> + }
> +
> + /* Supported report usages need to have a process_report hook */
> + if (!entry->info || !entry->info->process_report)
> + return -EINVAL;
> +
> + return entry->info->process_report(ts, buf, buflen);
> +}
> +
> +/* Make use of datasheet method 1 - single transfer read */
> +static int
> +axiom_u34_rev1_process_report(struct axiom_data *ts, const u8 *_buf, size_t bufsize)
> +{
> + unsigned int reg = axiom_usage_baseaddr(ts, AXIOM_U34);
> + struct regmap *regmap = ts->regmap;
> + u8 buf[AXIOM_PAGE_BYTE_LEN] = { };
> + struct device *dev = ts->dev;
> + unsigned char report_usage;
> + u16 crc_report, crc_calc;
> + unsigned int len;
> + u8 *payload;
> + int ret;
> +
> + ret = regmap_raw_read(regmap, reg, buf, ts->max_report_byte_len);
> + if (ret)
> + return ret;
> +
> + /* TODO: Add overflow statistics */
> +
> + /* REPORTLENGTH is in uint16 */
> + len = FIELD_GET(AXIOM_U34_REV1_REPORTLENGTH_MASK, buf[0]);
> + len *= 2;
> +
> + /*
> + * Downstream ignores zero length reports, extend the check to validate
> + * the upper bound too.
> + */
> + if (len == 0 || len > AXIOM_PAGE_BYTE_LEN) {
> + dev_dbg_ratelimited(dev, "Invalid report length: %u\n", len);
> + return -EINVAL;
> + }
> +
> + /*
> + * The CRC16 value can be queried at the last two bytes of the report.
> + * The value itself is covering the complete report excluding the CRC16
> + * value at the end.
> + */
> + crc_report = get_unaligned_le16(&buf[len - 2]);
> + crc_calc = crc16(0, buf, (len - 2));
> +
> + if (crc_calc != crc_report) {
> + dev_err_ratelimited(dev, "CRC16 mismatch!\n");
> + return -EINVAL;
> + }
> +
> + report_usage = buf[1];
> + payload = &buf[AXIOM_U34_REV1_PREAMBLE_BYTES];
> + len -= AXIOM_U34_REV1_PREAMBLE_BYTES - AXIOM_U34_REV1_POSTAMBLE_BYTES;
> +
> + switch (report_usage) {
> + case AXIOM_U01:
> + case AXIOM_U41:
> + return axiom_process_report(ts, report_usage, payload, len);
> + default:
> + dev_dbg(dev, "Unsupported report u%02X received\n",
> + report_usage);
> + }
> +
> + return 0;
> +}
> +
> +static void
> +axiom_u41_rev2_decode_target(const u8 *buf, u8 id, u16 *x, u16 *y, s8 *z)
> +{
> + u16 val;
> +
> + val = get_unaligned_le16(&buf[AXIOM_U41_REV2_X_REG(id)]);
> + val &= AXIOM_MAX_XY;
> + *x = val;
> +
> + val = get_unaligned_le16(&buf[AXIOM_U41_REV2_Y_REG(id)]);
> + val &= AXIOM_MAX_XY;
> + *y = val;
> +
> + *z = buf[AXIOM_U41_REV2_Z_REG(id)];
> +}
> +
> +static int
> +axiom_u41_rev2_process_report(struct axiom_data *ts, const u8 *buf, size_t bufsize)
> +{
> + struct input_dev *input = ts->input;
> + unsigned char id;
> + u16 targets;
> +
> + /*
> + * The input registration can be postponed but the touchscreen FW is
> + * sending u41 reports regardless.
> + */
> + if (!input)
> + return 0;
> +
> + targets = get_unaligned_le16(&buf[AXIOM_U41_REV2_TARGETSTATUS_REG]);
> +
> + for_each_set_bit(id, &ts->enabled_slots, AXIOM_MAX_TOUCHSLOTS) {
> + bool present;
> + u16 x, y;
> + s8 z;
> +
> + axiom_u41_rev2_decode_target(buf, id, &x, &y, &z);
> +
> + present = targets & BIT(id);
> + /* Ignore possible jitters */
> + if (z == AXIOM_PROX_LEVEL)
> + present = false;
> +
> + dev_dbg(ts->dev, "id:%u x:%u y:%u z:%d present:%u",
> + id, x, y, z, present);
> +
> + input_mt_slot(input, id);
> + if (input_mt_report_slot_state(input, MT_TOOL_FINGER, present))
> + touchscreen_report_pos(input, &ts->prop, x, y, true);
> +
> + if (!present)
> + continue;
> +
> + input_report_abs(input, ABS_MT_DISTANCE, z < 0 ? -z : 0);
> + if (ts->cds_enabled)
> + input_report_abs(input, ABS_MT_PRESSURE, z >= 0 ? z : 0);
> + }
> +
> + input_sync(input);
> +
> + return 0;
> +}
> +
> +static int
> +axiom_u01_rev1_process_report(struct axiom_data *ts, const u8 *buf, size_t bufsize)
> +{
> + switch (buf[AXIOM_U01_REV1_REPORTTYPE_REG]) {
> + case AXIOM_U01_REV1_REPORTTYPE_HELLO:
> + dev_dbg(ts->dev, "u01 HELLO received\n");
> + axiom_complete(ts, &ts->boot_complete);
> + return 0;
> + case AXIOM_U01_REV1_REPORTTYPE_HEARTBEAT:
> + dev_dbg_ratelimited(ts->dev, "u01 HEARTBEAT received\n");
> + return 0;
> + case AXIOM_U01_REV1_REPORTTYPE_OPCOMPLETE:
> + dev_dbg(ts->dev, "u01 OPCOMPLETE received\n");
> + axiom_u02_handshakenvm(ts);
> + axiom_complete(ts, &ts->nvm_write);
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +/**************************** Regmap handling *********************************/
> +
> +#define AXIOM_CMD_HDR_DIR_MASK BIT(15)
> +#define AXIOM_CMD_HDR_READ 1
> +#define AXIOM_CMD_HDR_WRITE 0
> +#define AXIOM_CMD_HDR_LEN_MASK GENMASK(14, 0)
> +
> +struct axiom_cmd_header {
> + __le16 target_address;
> + __le16 xferlen;
> +};
> +
> +/* Custom regmap read/write handling is required due to the aXiom protocol */
> +static int axiom_regmap_read(void *context, const void *reg_buf, size_t reg_size,
> + void *val_buf, size_t val_size)
> +{
> + struct device *dev = context;
> + struct i2c_client *i2c = to_i2c_client(dev);
> + struct axiom_data *ts = i2c_get_clientdata(i2c);
> + struct axiom_cmd_header hdr;
> + u16 xferlen, addr, baseaddr;
> + struct i2c_msg xfer[2];
> + int ret;
> +
> + if (val_size > AXIOM_MAX_XFERLEN) {
> + dev_err(ts->dev, "Exceed max xferlen: %zu > %u\n",
> + val_size, AXIOM_MAX_XFERLEN);
> + return -EINVAL;
> + }
> +
> + addr = *((u16 *)reg_buf);
> + hdr.target_address = cpu_to_le16(addr);
> + xferlen = FIELD_PREP(AXIOM_CMD_HDR_DIR_MASK, AXIOM_CMD_HDR_READ) |
> + FIELD_PREP(AXIOM_CMD_HDR_LEN_MASK, val_size);
> + hdr.xferlen = cpu_to_le16(xferlen);
> +
> + /* Verify that usage including the usage rev is supported */
> + baseaddr = addr & AXIOM_USAGE_BASEADDR_MASK;
> + if (!axiom_usage_supported(ts, baseaddr))
> + return -EINVAL;
> +
> + xfer[0].addr = i2c->addr;
> + xfer[0].flags = 0;
> + xfer[0].len = sizeof(hdr);
> + xfer[0].buf = (u8 *)&hdr;
> +
> + xfer[1].addr = i2c->addr;
> + xfer[1].flags = I2C_M_RD;
> + xfer[1].len = val_size;
> + xfer[1].buf = val_buf;
> +
> + ret = i2c_transfer(i2c->adapter, xfer, 2);
> + if (ret == 2)
> + return 0;
> + else if (ret < 0)
> + return ret;
> + else
> + return -EIO;
> +}
> +
> +static int axiom_regmap_write(void *context, const void *data, size_t count)
> +{
> + struct device *dev = context;
> + struct i2c_client *i2c = to_i2c_client(dev);
> + struct axiom_data *ts = i2c_get_clientdata(i2c);
> + char *buf __free(kfree) = NULL;
> + struct axiom_cmd_header hdr;
> + u16 xferlen, addr, baseaddr;
> + size_t val_size, msg_size;
> + int ret;
> +
> + val_size = count - sizeof(addr);
> + if (val_size > AXIOM_MAX_XFERLEN) {
> + dev_err(ts->dev, "Exceed max xferlen: %zu > %u\n",
> + val_size, AXIOM_MAX_XFERLEN);
> + return -EINVAL;
> + }
> +
> + addr = *((u16 *)data);
> + hdr.target_address = cpu_to_le16(addr);
> + xferlen = FIELD_PREP(AXIOM_CMD_HDR_DIR_MASK, AXIOM_CMD_HDR_WRITE) |
> + FIELD_PREP(AXIOM_CMD_HDR_LEN_MASK, val_size);
> + hdr.xferlen = cpu_to_le16(xferlen);
> +
> + /* Verify that usage including the usage rev is supported */
> + baseaddr = addr & AXIOM_USAGE_BASEADDR_MASK;
> + if (!axiom_usage_supported(ts, baseaddr))
> + return -EINVAL;
> +
> + msg_size = sizeof(hdr) + val_size;
> + buf = kzalloc(msg_size, GFP_KERNEL);
> + if (!buf)
> + return -ENOMEM;
> +
> + memcpy(buf, &hdr, sizeof(hdr));
> + memcpy(&buf[sizeof(hdr)], &((char *)data)[2], val_size);
> +
> + ret = i2c_master_send(i2c, buf, msg_size);
> +
> + return ret == msg_size ? 0 : ret;
> +}
> +
> +static const struct regmap_config axiom_i2c_regmap_config = {
> + .reg_bits = 16,
> + .val_bits = 8,
> + .read = axiom_regmap_read,
> + .write = axiom_regmap_write,
> +};
> +
> +/************************ FW update handling **********************************/
> +
> +static int axiom_update_input_dev(struct axiom_data *ts);
> +
> +static enum fw_upload_err
> +axiom_axfw_fw_prepare(struct fw_upload *fw_upload, const u8 *data, u32 size)
> +{
> + struct axiom_data *ts = fw_upload->dd_handle;
> + struct axiom_firmware *afw = &ts->fw[AXIOM_FW_AXFW];
> + u8 major_ver, minor_ver, rc_ver, status, variant;
> + u32 fw_file_crc32, crc32_calc;
> + struct device *dev = ts->dev;
> + unsigned int signature_len;
> + enum fw_upload_err ret;
> + u16 fw_file_format_ver;
> + u16 fw_file_device_id;
> +
> + mutex_lock(&afw->lock);
> + afw->cancel = false;
> + mutex_unlock(&afw->lock);
> +
> + mutex_lock(&ts->fwupdate_lock);
> +
> + if (size < sizeof(struct axiom_fw_axfw_hdr)) {
> + dev_err(dev, "Invalid AXFW file size\n");
> + ret = FW_UPLOAD_ERR_INVALID_SIZE;
> + goto out;
> + }
> +
> + signature_len = strlen(AXIOM_FW_AXFW_SIGNATURE);
> + if (strncmp(data, AXIOM_FW_AXFW_SIGNATURE, signature_len)) {
> + /*
> + * AXFW has a header which can be used to perform validations,
> + * ALC don't. Therefore the AXFW format is preferred.
> + */
> + dev_warn(dev, "No AXFW signature, assume ALC firmware\n");
> + ret = FW_UPLOAD_ERR_NONE;
> + goto out;
> + }
> +
> + fw_file_crc32 = get_unaligned_le32(&data[signature_len]);
> + crc32_calc = crc32(~0, &data[8], size - 8) ^ 0xffffffff;
> + if (fw_file_crc32 != crc32_calc) {
> + dev_err(dev, "AXFW CRC32 doesn't match (fw:%#x calc:%#x)\n",
> + fw_file_crc32, crc32_calc);
> + ret = FW_UPLOAD_ERR_FW_INVALID;
> + goto out;
> + }
> +
> + data += signature_len + sizeof(fw_file_crc32);
> + fw_file_format_ver = get_unaligned_le16(data);
> + if (fw_file_format_ver != AXIOM_FW_AXFW_FILE_FMT_VER) {
> + dev_err(dev, "Invalid AXFW file format version: %04x",
> + fw_file_format_ver);
> + ret = FW_UPLOAD_ERR_FW_INVALID;
> + goto out;
> + }
> +
> + data += sizeof(fw_file_format_ver);
> + fw_file_device_id = get_unaligned_le16(data);
> + if (fw_file_device_id != ts->device_id) {
> + dev_err(dev, "Invalid AXFW target device (fw:%#04x dev:%#04x)\n",
> + fw_file_device_id, ts->device_id);
> + ret = FW_UPLOAD_ERR_FW_INVALID;
> + goto out;
> + }
> +
> + /*
> + * This can happen if:
> + * * the device came up in bootloader mode, or
> + * * downloading the firmware failed in between, or
> + * * the following usage discovery failed.
> + *
> + * All cases are crcitical and we need to use any firmware to
> + * bring the device back into a working state which is supported by the
> + * host.
> + */
> + if (axiom_get_runmode(ts) != AXIOM_TCP_MODE)
> + return FW_UPLOAD_ERR_NONE;
> +
> + data += sizeof(fw_file_device_id);
> + variant = *data++;
> + minor_ver = *data++;
> + major_ver = *data++;
> + rc_ver = *data++;
> + status = *data++;
> +
> + if (major_ver == ts->fw_major && minor_ver == ts->fw_minor &&
> + rc_ver == ts->fw_rc && status == ts->fw_status &&
> + variant == ts->fw_variant) {
> + ret = FW_UPLOAD_ERR_DUPLICATE;
> + goto out;
> + }
> +
> + dev_info(dev, "Detected AXFW %02u.%02u.%02u (%s)\n",
> + major_ver, minor_ver, rc_ver,
> + status ? "production" : "engineering");
> +
> + mutex_lock(&afw->lock);
> + ret = afw->cancel ? FW_UPLOAD_ERR_CANCELED : FW_UPLOAD_ERR_NONE;
> + mutex_unlock(&afw->lock);
> +
> +out:
> + /*
> + * In FW_UPLOAD_ERR_NONE case the complete handler will release the
> + * lock.
> + */
> + if (ret != FW_UPLOAD_ERR_NONE)
> + mutex_unlock(&ts->fwupdate_lock);
> +
> + return ret;
> +}
> +
> +static int axiom_enter_bootloader_mode(struct axiom_data *ts)
> +{
> + struct device *dev = ts->dev;
> + int ret;
> +
> + axiom_set_runmode(ts, AXIOM_BLP_PRE_MODE);
> +
> + ret = axiom_u02_wait_idle(ts);
> + if (ret)
> + goto err_out;
> +
> + ret = axiom_u02_enter_bootloader(ts);
> + if (ret) {
> + dev_err(dev, "Failed to enter bootloader mode\n");
> + goto err_out;
> + }
> +
> + axiom_set_runmode(ts, AXIOM_BLP_MODE);
> +
> + return 0;
> +
> +err_out:
> + axiom_set_runmode(ts, AXIOM_TCP_MODE);
> +
> + return ret;
> +}
> +
> +static int axoim_blp_wait_ready(struct axiom_data *ts)
> +{
> + struct device *dev = ts->dev;
> + unsigned int reg;
> + int tmp, ret;
> + u8 buf[4];
> +
> + reg = AXIOM_U01_BLP_SATUS_REG;
> +
> + /* BLP busy poll requires to read 4 bytes! */
> + ret = read_poll_timeout(regmap_raw_read, tmp,
> + tmp || !(buf[2] & AXIOM_U01_BLP_STATUS_BUSY),
> + 10 * USEC_PER_MSEC, 5 * USEC_PER_SEC, false,
> + ts->regmap, reg, &buf, 4);
> + if (ret)
> + dev_err(dev, "Bootloader wait processing packets failed %d\n", ret);
> +
> + return ret;
> +}
> +
> +static int
> +axiom_blp_write_chunk(struct axiom_data *ts, const u8 *data, u16 length)
> +{
> + unsigned int chunk_size = AXIOM_U01_BLP_FIFO_CHK_SIZE_BYTES;
> + unsigned int reg = AXIOM_U01_BLP_FIFO_REG;
> + struct device *dev = ts->dev;
> + unsigned int pos = 0;
> + int ret;
> +
> + ret = axoim_blp_wait_ready(ts);
> + if (ret)
> + return ret;
> +
> + /*
> + * TODO: Downstream does this chunk transfers. Verify if this is
> + * required if one fw-chunk <= AXIOM_MAX_XFERLEN
> + */
> + while (pos < length) {
> + u16 len;
> +
> + len = chunk_size;
> + if ((pos + chunk_size) > length)
> + len = length - pos;
> +
> + ret = regmap_raw_write(ts->regmap, reg, &data[pos], len);
> + if (ret) {
> + dev_err(dev, "Bootloader download AXFW chunk failed %d\n", ret);
> + return ret;
> + }
> +
> + pos += len;
> + ret = axoim_blp_wait_ready(ts);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int axiom_blp_reset(struct axiom_data *ts)
> +{
> + __le16 reset_cmd = cpu_to_le16(AXIOM_U01_BLP_COMMAND_RESET);
> + unsigned int reg = AXIOM_U01_BLP_COMMAND_REG;
> + struct device *dev = ts->dev;
> + unsigned int attempts = 20;
> + unsigned int mode;
> + int ret;
> +
> + ret = axoim_blp_wait_ready(ts);
> + if (ret)
> + return ret;
> +
> + /*
> + * For some reason this write fail with -ENXIO. Skip checking the return
> + * code (which is also done by the downstream axfw.py tool and poll u31
> + * instead.
> + */
> + regmap_raw_write(ts->regmap, reg, &reset_cmd, sizeof(reset_cmd));
> +
> + do {
> + ret = regmap_read(ts->regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG,
> + &mode);
> + if (!ret)
> + break;
> +
> + fsleep(250 * USEC_PER_MSEC);
> + } while (attempts--);
> +
> + if (ret) {
> + dev_err(dev, "Failed to read MODE after BLP reset: %d\n", ret);
> + return ret;
> + }
> +
> + mode = FIELD_GET(AXIOM_U31_REV1_MODE_MASK, mode);
> + if (mode == AXIOM_U31_REV1_MODE_BLP) {
> + dev_err(dev, "Device still in BLP mode, abort\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static void axiom_lock_input_device(struct axiom_data *ts)
> +{
> + if (!ts->input)
> + return;
> +
> + mutex_lock(&ts->input->mutex);
> +}
> +
> +static void axiom_unlock_input_device(struct axiom_data *ts)
> +{
> + if (!ts->input)
> + return;
> +
> + mutex_unlock(&ts->input->mutex);
> +}
> +
> +static void axiom_unregister_input_dev(struct axiom_data *ts)
> +{
> + if (ts->input)
> + input_unregister_device(ts->input);
> +
> + ts->input = NULL;
> +}
> +
> +static enum fw_upload_err
> +axiom_axfw_fw_write(struct fw_upload *fw_upload, const u8 *data, u32 offset,
> + u32 size, u32 *written)
> +{
> + struct axiom_data *ts = fw_upload->dd_handle;
> + struct axiom_firmware *afw = &ts->fw[AXIOM_FW_AXFW];
> + struct device *dev = ts->dev;
> + bool cancel;
> + int ret;
> +
> + /* Done before cancel check due to cleanup based put */
> + ret = pm_runtime_resume_and_get(ts->dev);
> + if (ret)
> + return FW_UPLOAD_ERR_HW_ERROR;
> +
> + mutex_lock(&afw->lock);
> + cancel = afw->cancel;
> + mutex_unlock(&afw->lock);
> +
> + if (cancel)
> + return FW_UPLOAD_ERR_CANCELED;
> +
> + axiom_lock_input_device(ts);
> +
> + if (ts->input && input_device_enabled(ts->input)) {
> + dev_err(dev, "Input device not idle, abort AXFW/ALC update\n");
> + goto err;
> + }
> +
> + if (!strncmp(data, AXIOM_FW_AXFW_SIGNATURE,
> + strlen(AXIOM_FW_AXFW_SIGNATURE))) {
> + /* Set the pointer to the first fw chunk */
> + data += sizeof(struct axiom_fw_axfw_hdr);
> + size -= sizeof(struct axiom_fw_axfw_hdr);
> + *written += sizeof(struct axiom_fw_axfw_hdr);
> + }
> +
> + if (axiom_enter_bootloader_mode(ts))
> + goto err;
> +
> + while (size) {
> + u16 chunk_len, len;
> +
> + chunk_len = get_unaligned_be16(&data[6]);
> + len = chunk_len + sizeof(struct axiom_fw_axfw_chunk_hdr);
> +
> + /*
> + * The bootlaoder FW can handle the complete chunk incl. the
> + * header.
> + */
> + ret = axiom_blp_write_chunk(ts, data, len);
> + if (ret)
> + goto err;
> +
> + size -= len;
> + *written += len;
> + data += len;
> + }
> +
> + ret = axiom_blp_reset(ts);
> + if (ret)
> + dev_warn(dev, "BLP reset failed\n");
> +
> + ret = axiom_u31_device_discover(ts);
> + if (ret) {
> + /*
> + * This is critical and we need to avoid that the user-space can
> + * still use the input-dev.
> + */
> + axiom_unlock_input_device(ts);
> + axiom_unregister_input_dev(ts);
> + dev_err(dev, "Device discovery failed after AXFW/ALC firmware update\n");
> + goto err;
> + }
> +
> + /* Unlock before the input device gets unregistered */
> + axiom_unlock_input_device(ts);
> +
> + ret = axiom_update_input_dev(ts);
> + if (ret) {
> + dev_err(dev, "Input device update failed after AXFW/ALC firmware update\n");
> + return FW_UPLOAD_ERR_HW_ERROR;
> + }
> +
> + dev_info(dev, "AXFW update successful\n");
> +
> + return FW_UPLOAD_ERR_NONE;
> +
> +err:
> + axiom_unlock_input_device(ts);
> + return FW_UPLOAD_ERR_HW_ERROR;
> +}
> +
> +static enum fw_upload_err axiom_fw_poll_complete(struct fw_upload *fw_upload)
> +{
> + return FW_UPLOAD_ERR_NONE;
> +}
> +
> +static void axiom_axfw_fw_cancel(struct fw_upload *fw_upload)
> +{
> + struct axiom_data *ts = fw_upload->dd_handle;
> + struct axiom_firmware *afw = &ts->fw[AXIOM_FW_AXFW];
> +
> + mutex_lock(&afw->lock);
> + afw->cancel = true;
> + mutex_unlock(&afw->lock);
> +}
> +
> +static void axiom_axfw_fw_cleanup(struct fw_upload *fw_upload)
> +{
> + struct axiom_data *ts = fw_upload->dd_handle;
> +
> + mutex_unlock(&ts->fwupdate_lock);
> + pm_runtime_mark_last_busy(ts->dev);
> + pm_runtime_put_sync_autosuspend(ts->dev);
> +}
> +
> +static const struct fw_upload_ops axiom_axfw_fw_upload_ops = {
> + .prepare = axiom_axfw_fw_prepare,
> + .write = axiom_axfw_fw_write,
> + .poll_complete = axiom_fw_poll_complete,
> + .cancel = axiom_axfw_fw_cancel,
> + .cleanup = axiom_axfw_fw_cleanup,
> +};
> +
> +static int
> +axiom_set_new_crcs(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *cfg)
> +{
> + struct axiom_crc *crc = &ts->crc[AXIOM_CRC_NEW];
> + const u32 *u33_data = (const u32 *)cfg->usage_content;
> +
> + if (cfg->usage_rev != 2 && cfg->usage_rev != 3) {
> + dev_err(ts->dev, "The driver doesn't support u33 revision %u\n",
> + cfg->usage_rev);
> + return -EINVAL;
> + }
> +
> + crc->runtime = get_unaligned_le32(u33_data);
> + crc->nvltlusageconfig = get_unaligned_le32(&u33_data[3]);
> + crc->vltusageconfig = get_unaligned_le32(&u33_data[4]);
> + crc->u22_sequencedata = get_unaligned_le32(&u33_data[5]);
> + crc->u43_hotspots = get_unaligned_le32(&u33_data[6]);
> + if (cfg->usage_rev == 2) {
> + crc->u93_profiles = get_unaligned_le32(&u33_data[7]);
> + crc->u94_deltascalemap = get_unaligned_le32(&u33_data[8]);
> + } else if (cfg->usage_rev == 3) {
> + crc->u77_dod_data = get_unaligned_le32(&u33_data[7]);
> + crc->u93_profiles = get_unaligned_le32(&u33_data[8]);
> + crc->u94_deltascalemap = get_unaligned_le32(&u33_data[9]);
> + }
> +
> + return 0;
> +}
> +
> +static unsigned int
> +axiom_cfg_fw_prepare_chunk(struct axiom_fw_cfg_chunk *chunk, const u8 *data)
> +{
> + chunk->usage_num = data[0];
> + chunk->usage_rev = data[1];
> + chunk->usage_length = get_unaligned_le16(&data[3]);
> + chunk->usage_content = &data[5];
> +
> + return chunk->usage_length + sizeof(struct axiom_fw_cfg_chunk_hdr);
> +}
> +
> +/*
> + * To overcome buggy firmware we need to check if a given usage is used by the
> + * current running firmware. Return true if the usage is unused/not populated
> + * by the firmware since we can't perform the actual check.
> + */
> +#define axiom_usage_crc_match(_ts, _usage_num, _cur, _new, _field) \
> + (!_ts->usage_table[_usage_num].populated || (_cur->_field == _new->_field))
> +
> +static bool axiom_cfg_fw_update_required(struct axiom_data *ts)
> +{
> + struct axiom_crc *cur, *new;
> +
> + cur = &ts->crc[AXIOM_CRC_CUR];
> + new = &ts->crc[AXIOM_CRC_NEW];
> +
> + if (cur->nvltlusageconfig != new->nvltlusageconfig ||
> + !axiom_usage_crc_match(ts, AXIOM_U22, cur, new, u22_sequencedata) ||
> + !axiom_usage_crc_match(ts, AXIOM_U43, cur, new, u43_hotspots) ||
> + !axiom_usage_crc_match(ts, AXIOM_U93, cur, new, u93_profiles) ||
> + !axiom_usage_crc_match(ts, AXIOM_U94, cur, new, u94_deltascalemap))
> + return true;
> +
> + return false;
> +}
> +
> +static enum fw_upload_err
> +axiom_cfg_fw_prepare(struct fw_upload *fw_upload, const u8 *data, u32 size)
> +{
> + struct axiom_data *ts = fw_upload->dd_handle;
> + struct axiom_firmware *afw = &ts->fw[AXIOM_FW_CFG];
> + u32 cur_runtime_crc, fw_runtime_crc;
> + struct axiom_fw_cfg_chunk chunk;
> + struct device *dev = ts->dev;
> + enum fw_upload_err ret;
> + u32 signature;
> +
> + mutex_lock(&afw->lock);
> + afw->cancel = false;
> + mutex_unlock(&afw->lock);
> +
> + mutex_lock(&ts->fwupdate_lock);
> +
> + if (axiom_get_runmode(ts) != AXIOM_TCP_MODE) {
> + dev_err(dev, "Device not in TCP mode, abort TH2CFG update\n");
> + ret = FW_UPLOAD_ERR_HW_ERROR;
> + goto out;
> + }
> +
> + if (size < sizeof(struct axiom_fw_cfg_hdr)) {
> + dev_err(dev, "Invalid TH2CFG file size\n");
> + ret = FW_UPLOAD_ERR_INVALID_SIZE;
> + goto out;
> + }
> +
> + signature = get_unaligned_be32(data);
> + if (signature != AXIOM_FW_CFG_SIGNATURE) {
> + dev_err(dev, "Invalid TH2CFG signature\n");
> + ret = FW_UPLOAD_ERR_FW_INVALID;
> + goto out;
> + }
> +
> + /* Skip to the first fw chunk */
> + data += sizeof(struct axiom_fw_cfg_hdr);
> + size -= sizeof(struct axiom_fw_cfg_hdr);
> +
> + /*
> + * Search for u33 which contains the CRC information and perform only
> + * the runtime-crc check.
> + */
> + while (size) {
> + unsigned int chunk_len;
> +
> + chunk_len = axiom_cfg_fw_prepare_chunk(&chunk, data);
> + if (chunk.usage_num == AXIOM_U33)
> + break;
> +
> + data += chunk_len;
> + size -= chunk_len;
> + }
> +
> + if (size == 0) {
> + dev_err(dev, "Failed to find the u33 entry in TH2CFG\n");
> + ret = FW_UPLOAD_ERR_FW_INVALID;
> + goto out;
> + }
> +
> + ret = axiom_set_new_crcs(ts, &chunk);
> + if (ret) {
> + ret = FW_UPLOAD_ERR_FW_INVALID;
> + goto out;
> + }
> +
> + /*
> + * Nothing to do if the CRCs are the same. TODO: Must be extended once
> + * the CDU update is added.
> + */
> + if (!axiom_cfg_fw_update_required(ts)) {
> + ret = FW_UPLOAD_ERR_DUPLICATE;
> + goto out;
> + }
> +
> + cur_runtime_crc = ts->crc[AXIOM_CRC_CUR].runtime;
> + fw_runtime_crc = ts->crc[AXIOM_CRC_NEW].runtime;
> + if (cur_runtime_crc != fw_runtime_crc) {
> + dev_err(dev, "TH2CFG and device runtime CRC doesn't match: %#x != %#x\n",
> + fw_runtime_crc, cur_runtime_crc);
> + ret = FW_UPLOAD_ERR_FW_INVALID;
> + goto out;
> + }
> +
> + mutex_lock(&afw->lock);
> + ret = afw->cancel ? FW_UPLOAD_ERR_CANCELED : FW_UPLOAD_ERR_NONE;
> + mutex_unlock(&afw->lock);
> +
> +out:
> + /*
> + * In FW_UPLOAD_ERR_NONE case the complete handler will release the
> + * lock.
> + */
> + if (ret != FW_UPLOAD_ERR_NONE)
> + mutex_unlock(&ts->fwupdate_lock);
> +
> + return ret;
> +}
> +
> +static int axiom_zero_volatile_mem(struct axiom_data *ts)
> +{
> + int ret, size;
> + u8 *buf;
> +
> + /* Zero out the volatile memory except for the user content in u04 */
> + ret = axiom_u04_get(ts, &buf);
> + if (ret < 0)
> + return ret;
> + size = ret;
> +
> + ret = axiom_u02_fillconfig(ts);
> + if (ret)
> + goto out;
> +
> + ret = axiom_u04_set(ts, buf, size);
> +out:
> + kfree(buf);
> + return ret;
> +}
> +
> +static bool
> +axiom_skip_cfg_chunk(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *chunk)
> +{
> + u8 usage_num = chunk->usage_num;
> +
> + if (!ts->usage_table[usage_num].populated) {
> + dev_warn(ts->dev, "Unknown usage chunk for u%02X\n", usage_num);
> + return true;
> + }
> +
> + /* Skip read-only usages */
> + if (ts->usage_table[usage_num].info &&
> + ts->usage_table[usage_num].info->is_ro)
> + return true;
> +
> + return false;
> +}
> +
> +static int
> +axiom_write_cdu_usage(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *chunk)
> +{
> + struct axiom_cdu_usage cdu = { };
> + struct device *dev = ts->dev;
> + unsigned int remaining;
> + unsigned int reg;
> + unsigned int pos;
> + int ret;
> +
> + pos = 0;
> + remaining = chunk->usage_length;
> + cdu.command = cpu_to_le16(AXIOM_CDU_CMD_STORE);
> + reg = axiom_usage_baseaddr(ts, chunk->usage_num);
> +
> + while (remaining) {
> + unsigned int size;
> +
> + cdu.parameters[1] = cpu_to_le16(pos);
> +
> + size = remaining;
> + if (size > AXIOM_CDU_MAX_DATA_BYTES)
> + size = AXIOM_CDU_MAX_DATA_BYTES;
> +
> + memset(cdu.data, 0, sizeof(cdu.data));
> + memcpy(cdu.data, &chunk->usage_content[pos], size);
> +
> + ret = regmap_raw_write(ts->regmap, reg, &cdu, sizeof(cdu));
> + if (ret) {
> + dev_err(dev, "Failed to write CDU u%02X\n",
> + chunk->usage_num);
> + return ret;
> + }
> +
> + ret = axiom_cdu_wait_idle(ts, chunk->usage_num);
> + if (ret) {
> + dev_err(dev, "CDU write wait-idle failed\n");
> + return ret;
> + }
> +
> + remaining -= size;
> + pos += size;
> + }
> +
> + /*
> + * TODO: Check if we really need to send 48 zero bytes of data like
> + * downstream does.
> + */
> + memset(&cdu, 0, sizeof(cdu));
> + cdu.command = cpu_to_le16(AXIOM_CDU_CMD_COMMIT);
> + cdu.parameters[0] = cpu_to_le16(AXIOM_CDU_PARAM0_COMMIT);
> + cdu.parameters[1] = cpu_to_le16(AXIOM_CDU_PARAM1_COMMIT);
> +
> + ret = regmap_raw_write(ts->regmap, reg, &cdu, sizeof(cdu));
> + if (ret) {
> + dev_err(dev, "Failed to commit CDU u%02X to NVM\n",
> + chunk->usage_num);
> + return ret;
> + }
> +
> + ret = axiom_wait_for_completion_timeout(ts, &ts->nvm_write,
> + msecs_to_jiffies(5 * MSEC_PER_SEC));
> + if (!ret) {
> + dev_err(ts->dev, "Error CDU u%02X commit timedout\n",
> + chunk->usage_num);
> + return -ETIMEDOUT;
> + }
> +
> + return axiom_cdu_wait_idle(ts, chunk->usage_num);
> +}
> +
> +static int
> +axiom_write_cfg_chunk(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *chunk)
> +{
> + unsigned int reg;
> + int ret;
> +
> + if (ts->usage_table[chunk->usage_num].info &&
> + ts->usage_table[chunk->usage_num].info->is_cdu) {
> + ret = axiom_write_cdu_usage(ts, chunk);
> + if (ret)
> + return ret;
> + goto out;
> + }
> +
> + reg = axiom_usage_baseaddr(ts, chunk->usage_num);
> + ret = regmap_raw_write(ts->regmap, reg, chunk->usage_content, chunk->usage_length);
> + if (ret)
> + return ret;
> +
> +out:
> + return axiom_u02_wait_idle(ts);
> +}
> +
> +static int axiom_verify_volatile_mem(struct axiom_data *ts)
> +{
> + int ret;
> +
> + ret = axiom_u02_computecrc(ts);
> + if (ret)
> + return ret;
> +
> + /* Query the new CRCs after they are re-computed */
> + ret = axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
> + if (ret)
> + return ret;
> +
> + return ts->crc[AXIOM_CRC_CUR].vltusageconfig ==
> + ts->crc[AXIOM_CRC_NEW].vltusageconfig ? 0 : -EINVAL;
> +}
> +
> +static int axiom_verify_crcs(struct axiom_data *ts)
> +{
> + struct device *dev = ts->dev;
> + struct axiom_crc *cur, *new;
> +
> + cur = &ts->crc[AXIOM_CRC_CUR];
> + new = &ts->crc[AXIOM_CRC_NEW];
> +
> + if (new->vltusageconfig != cur->vltusageconfig) {
> + dev_err(dev, "VLTUSAGECONFIG CRC32 mismatch (dev:%#x != fw:%#x)\n",
> + cur->vltusageconfig, new->vltusageconfig);
> + return -EINVAL;
> + } else if (new->nvltlusageconfig != cur->nvltlusageconfig) {
> + dev_err(dev, "NVLTUSAGECONFIG CRC32 mismatch (dev:%#x != fw:%#x)\n",
> + cur->nvltlusageconfig, new->nvltlusageconfig);
> + return -EINVAL;
> + } else if (!axiom_usage_crc_match(ts, AXIOM_U22, cur, new, u22_sequencedata)) {
> + dev_err(dev, "U22_SEQUENCEDATA CRC32 mismatch (dev:%#x != fw:%#x)\n",
> + cur->u22_sequencedata, new->u22_sequencedata);
> + return -EINVAL;
> + } else if (!axiom_usage_crc_match(ts, AXIOM_U43, cur, new, u43_hotspots)) {
> + dev_err(dev, "U43_HOTSPOTS CRC32 mismatch (dev:%#x != fw:%#x)\n",
> + cur->u43_hotspots, new->u43_hotspots);
> + return -EINVAL;
> + } else if (!axiom_usage_crc_match(ts, AXIOM_U93, cur, new, u93_profiles)) {
> + dev_err(dev, "U93_PROFILES CRC32 mismatch (dev:%#x != fw:%#x)\n",
> + cur->u93_profiles, new->u93_profiles);
> + return -EINVAL;
> + } else if (!axiom_usage_crc_match(ts, AXIOM_U94, cur, new, u94_deltascalemap)) {
> + dev_err(dev, "U94_DELTASCALEMAP CRC32 mismatch (dev:%#x != fw:%#x)\n",
> + cur->u94_deltascalemap, new->u94_deltascalemap);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static enum fw_upload_err
> +axiom_cfg_fw_write(struct fw_upload *fw_upload, const u8 *data, u32 offset,
> + u32 size, u32 *written)
> +{
> + struct axiom_data *ts = fw_upload->dd_handle;
> + struct axiom_firmware *afw = &ts->fw[AXIOM_FW_CFG];
> + struct device *dev = ts->dev;
> + bool cancel;
> + int ret;
> +
> + /* Done before cancel check due to cleanup based put */
> + ret = pm_runtime_resume_and_get(ts->dev);
> + if (ret)
> + return FW_UPLOAD_ERR_HW_ERROR;
> +
> + mutex_lock(&afw->lock);
> + cancel = afw->cancel;
> + mutex_unlock(&afw->lock);
> +
> + if (cancel)
> + return FW_UPLOAD_ERR_CANCELED;
> +
> + axiom_lock_input_device(ts);
> +
> + if (ts->input && input_device_enabled(ts->input)) {
> + dev_err(dev, "Input device not idle, abort TH2CFG update\n");
> + axiom_unlock_input_device(ts);
> + return FW_UPLOAD_ERR_HW_ERROR;
> + }
> +
> + ret = axiom_u02_stop(ts);
> + if (ret)
> + goto err_swreset;
> +
> + ret = axiom_zero_volatile_mem(ts);
> + if (ret)
> + goto err_swreset;
> +
> + /* Skip to the first fw chunk */
> + data += sizeof(struct axiom_fw_cfg_hdr);
> + size -= sizeof(struct axiom_fw_cfg_hdr);
> + *written += sizeof(struct axiom_fw_cfg_hdr);
> +
> + axiom_set_runmode(ts, AXIOM_TCP_CFG_UPDATE_MODE);
> +
> + while (size) {
> + struct axiom_fw_cfg_chunk chunk;
> + unsigned int chunk_len;
> +
> + chunk_len = axiom_cfg_fw_prepare_chunk(&chunk, data);
> + if (axiom_skip_cfg_chunk(ts, &chunk)) {
> + dev_dbg(dev, "Skip TH2CFG usage u%02X\n", chunk.usage_num);
> + goto next_chunk;
> + }
> +
> + ret = axiom_write_cfg_chunk(ts, &chunk);
> + if (ret) {
> + axiom_set_runmode(ts, AXIOM_TCP_MODE);
> + goto err_swreset;
> + }
> +
> +next_chunk:
> + data += chunk_len;
> + size -= chunk_len;
> + *written += chunk_len;
> + }
> +
> + axiom_set_runmode(ts, AXIOM_TCP_MODE);
> +
> + /* Ensure that the chunks are written correctly */
> + ret = axiom_verify_volatile_mem(ts);
> + if (ret) {
> + dev_err(dev, "Failed to verify written config, abort\n");
> + goto err_swreset;
> + }
> +
> + ret = axiom_u02_save_config(ts);
> + if (ret)
> + goto err_swreset;
> +
> + /*
> + * TODO: Check if u02 start would be sufficient to load the new config
> + * values
> + */
> + ret = axiom_u02_swreset(ts);
> + if (ret) {
> + dev_err(dev, "Soft reset failed\n");
> + goto err_unlock;
> + }
> +
> + ret = axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
> + if (ret)
> + goto err_unlock;
> +
> + if (axiom_verify_crcs(ts))
> + goto err_unlock;
> +
> + /* Unlock before the input device gets unregistered */
> + axiom_unlock_input_device(ts);
> +
> + ret = axiom_update_input_dev(ts);
> + if (ret) {
> + dev_err(dev, "Input device update failed after TH2CFG firmware update\n");
> + goto err_out;
> + }
> +
> + dev_info(dev, "TH2CFG update successful\n");
> +
> + return FW_UPLOAD_ERR_NONE;
> +
> +err_swreset:
> + axiom_u02_swreset(ts);
> +err_unlock:
> + axiom_unlock_input_device(ts);
> +err_out:
> + return ret == -ETIMEDOUT ? FW_UPLOAD_ERR_TIMEOUT : FW_UPLOAD_ERR_HW_ERROR;
> +}
> +
> +static void axiom_cfg_fw_cancel(struct fw_upload *fw_upload)
> +{
> + struct axiom_data *ts = fw_upload->dd_handle;
> + struct axiom_firmware *afw = &ts->fw[AXIOM_FW_CFG];
> +
> + mutex_lock(&afw->lock);
> + afw->cancel = true;
> + mutex_unlock(&afw->lock);
> +}
> +
> +static void axiom_cfg_fw_cleanup(struct fw_upload *fw_upload)
> +{
> + struct axiom_data *ts = fw_upload->dd_handle;
> +
> + mutex_unlock(&ts->fwupdate_lock);
> + pm_runtime_mark_last_busy(ts->dev);
> + pm_runtime_put_sync_autosuspend(ts->dev);
> +}
> +
> +static const struct fw_upload_ops axiom_cfg_fw_upload_ops = {
> + .prepare = axiom_cfg_fw_prepare,
> + .write = axiom_cfg_fw_write,
> + .poll_complete = axiom_fw_poll_complete,
> + .cancel = axiom_cfg_fw_cancel,
> + .cleanup = axiom_cfg_fw_cleanup,
> +};
> +
> +static void axiom_remove_axfw_fwl_action(void *data)
> +{
> + struct axiom_data *ts = data;
> +
> + firmware_upload_unregister(ts->fw[AXIOM_FW_AXFW].fwl);
> +}
> +
> +static void axiom_remove_cfg_fwl_action(void *data)
> +{
> + struct axiom_data *ts = data;
> +
> + firmware_upload_unregister(ts->fw[AXIOM_FW_CFG].fwl);
> +}
> +
> +static int axiom_register_fwl(struct axiom_data *ts)
> +{
> + struct device *dev = ts->dev;
> + struct fw_upload *fwl;
> + char *fw_name;
> + int ret;
> +
> + if (!IS_ENABLED(CONFIG_FW_UPLOAD)) {
> + dev_dbg(dev, "axfw and th2cfgbin update disabled\n");
> + return 0;
> + }
> +
> + mutex_init(&ts->fw[AXIOM_FW_AXFW].lock);
> + fw_name = kasprintf(GFP_KERNEL, "i2c:%s.axfw", dev_name(dev));
> + fwl = firmware_upload_register(THIS_MODULE, ts->dev, fw_name,
> + &axiom_axfw_fw_upload_ops, ts);
> + kfree(fw_name);
> + if (IS_ERR(fwl))
> + return dev_err_probe(dev, PTR_ERR(fwl),
> + "Failed to register firmware upload\n");
> +
> + ret = devm_add_action_or_reset(dev, axiom_remove_axfw_fwl_action, ts);
> + if (ret)
> + return ret;
> +
> + ts->fw[AXIOM_FW_AXFW].fwl = fwl;
> +
> + mutex_init(&ts->fw[AXIOM_FW_CFG].lock);
> + fw_name = kasprintf(GFP_KERNEL, "i2c:%s.th2cfgbin", dev_name(dev));
> + fwl = firmware_upload_register(THIS_MODULE, ts->dev, fw_name,
> + &axiom_cfg_fw_upload_ops, ts);
> + kfree(fw_name);
> + if (IS_ERR(fwl))
> + return dev_err_probe(dev, PTR_ERR(fwl),
> + "Failed to register cfg firmware upload\n");
> +
> + ret = devm_add_action_or_reset(dev, axiom_remove_cfg_fwl_action, ts);
> + if (ret)
> + return ret;
> +
> + ts->fw[AXIOM_FW_CFG].fwl = fwl;
> +
> + return 0;
> +}
> +
> +/************************* Device handlig *************************************/
> +
> +#define AXIOM_SIMPLE_FW_DEVICE_ATTR(attr) \
> + static ssize_t \
> + fw_ ## attr ## _show(struct device *dev, \
> + struct device_attribute *_attr, char *buf) \
> + { \
> + struct i2c_client *i2c = to_i2c_client(dev); \
> + struct axiom_data *ts = i2c_get_clientdata(i2c); \
> + \
> + return sysfs_emit(buf, "%u\n", ts->fw_##attr); \
> + } \
> + static DEVICE_ATTR_RO(fw_##attr)
> +
> +AXIOM_SIMPLE_FW_DEVICE_ATTR(major);
> +AXIOM_SIMPLE_FW_DEVICE_ATTR(minor);
> +AXIOM_SIMPLE_FW_DEVICE_ATTR(rc);
> +
> +static ssize_t fw_status_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct i2c_client *i2c = to_i2c_client(dev);
> + struct axiom_data *ts = i2c_get_clientdata(i2c);
> + const char *val;
> +
> + if (ts->fw_status)
> + val = "production";
> + else
> + val = "engineering";
> +
> + return sysfs_emit(buf, "%s\n", val);
> +}
> +static DEVICE_ATTR_RO(fw_status);
> +
> +static ssize_t fw_variant_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct i2c_client *i2c = to_i2c_client(dev);
> + struct axiom_data *ts = i2c_get_clientdata(i2c);
> + const char *val;
> +
> + switch (ts->fw_variant) {
> + case 0:
> + val = "3d";
> + break;
> + case 1:
> + val = "2d";
> + break;
> + case 3:
> + val = "force";
> + break;
> + default:
> + val = "unknown";
> + break;
> + }
> +
> + return sysfs_emit(buf, "%s\n", val);
> +}
> +static DEVICE_ATTR_RO(fw_variant);
> +
> +static ssize_t device_id_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct i2c_client *i2c = to_i2c_client(dev);
> + struct axiom_data *ts = i2c_get_clientdata(i2c);
> +
> + return sysfs_emit(buf, "%u\n", ts->device_id);
> +}
> +static DEVICE_ATTR_RO(device_id);
> +
> +static ssize_t device_state_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct i2c_client *i2c = to_i2c_client(dev);
> + struct axiom_data *ts = i2c_get_clientdata(i2c);
> +
> + return sysfs_emit(buf, "%s\n", axiom_runmode_to_string(ts));
> +}
> +static DEVICE_ATTR_RO(device_state);
> +
> +static struct attribute *axiom_attrs[] = {
> + &dev_attr_fw_major.attr,
> + &dev_attr_fw_minor.attr,
> + &dev_attr_fw_rc.attr,
> + &dev_attr_fw_status.attr,
> + &dev_attr_fw_variant.attr,
> + &dev_attr_device_id.attr,
> + &dev_attr_device_state.attr,
> + NULL
> +};
> +ATTRIBUTE_GROUPS(axiom);
> +
> +static void axiom_poll(struct input_dev *input)
> +{
> + struct axiom_data *ts = input_get_drvdata(input);
> +
> + axiom_process_report(ts, AXIOM_U34, NULL, 0);
> +}
> +
> +static irqreturn_t axiom_irq(int irq, void *dev_id)
> +{
> + struct axiom_data *ts = dev_id;
> +
> + axiom_process_report(ts, AXIOM_U34, NULL, 0);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int axiom_input_open(struct input_dev *dev)
> +{
> + struct axiom_data *ts = input_get_drvdata(dev);
> +
> + return pm_runtime_resume_and_get(ts->dev);
> +}
> +
> +static void axiom_input_close(struct input_dev *dev)
> +{
> + struct axiom_data *ts = input_get_drvdata(dev);
> +
> + pm_runtime_mark_last_busy(ts->dev);
> + pm_runtime_put_sync_autosuspend(ts->dev);
> +}
> +
> +static int axiom_register_input_dev(struct axiom_data *ts,
> + bool update_in_process)
> +{
> + struct device *dev = ts->dev;
> + struct i2c_client *client = to_i2c_client(dev);
> + struct input_dev *input;
> + int ret;
> +
> + input = input_allocate_device();
> + if (!input) {
> + dev_err(dev, "Failed to allocate input driver data\n");
> + return -ENOMEM;
> + }
> +
> + input->dev.parent = dev;
> + input->name = "TouchNetix aXiom Touchscreen";
> + input->id.bustype = BUS_I2C;
> + input->id.vendor = ts->jedec_id;
> + input->id.product = ts->device_id;
> + input->id.version = ts->silicon_rev;
> +
> + /* Either follow the panel or the open user count, not both */
> + if (!ts->is_panel_follower) {
> + input->open = axiom_input_open;
> + input->close = axiom_input_close;
> + }
> +
> + axiom_u64_cds_enabled(ts);
> + input_set_abs_params(input, ABS_MT_POSITION_X, 0, AXIOM_MAX_XY - 1, 0, 0);
> + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, AXIOM_MAX_XY - 1, 0, 0);
> + input_set_abs_params(input, ABS_MT_DISTANCE, 0, 127, 0, 0);
> + if (ts->cds_enabled)
> + input_set_abs_params(input, ABS_MT_PRESSURE, 0, 127, 0, 0);
> +
> + touchscreen_parse_properties(input, true, &ts->prop);
> +
> + axiom_u42_get_touchslots(ts);
> + if (!ts->num_slots && update_in_process) {
> + input_free_device(input);
> + /*
> + * Skip input device registration but don't throw an error to
> + * not abort the update since some FW updates require a
> + * following CFG update to re-initialize the touchslot handling.
> + */
> + if (update_in_process) {
> + dev_info(dev, "No touchslots found after FW or CFG update, skip registering input device\n");
> + return 0;
> + }
> +
> + dev_err(dev, "Error firmware has no touchslots enabled\n");
> + return -EINVAL;
> + }
> +
> + ret = input_mt_init_slots(input, ts->num_slots, INPUT_MT_DIRECT);
> + if (ret) {
> + input_free_device(input);
> + dev_err(dev, "Failed to init mt slots\n");
> + return ret;
> + }
> +
> + /*
> + * Ensure that the IRQ setup is done only once since the handler belong
> + * to the i2c-dev whereas the input-poller belong to the input-dev. The
> + * input-dev can get unregistered during a firmware update to reflect
> + * the new firmware state. Therefore the input-poller setup must be done
> + * always.
> + */
> + if (!ts->irq_setup_done && client->irq) {
> + ret = devm_request_threaded_irq(dev, client->irq, NULL, axiom_irq,
> + IRQF_ONESHOT, dev_name(dev), ts);
> + if (ret) {
> + dev_err(dev, "Failed to request IRQ\n");
> + return ret;
> + }
> + ts->irq_setup_done = true;
> + } else {
> + ret = input_setup_polling(input, axiom_poll);
> + if (ret) {
> + input_free_device(input);
> + dev_err(dev, "Setup polling mode failed\n");
> + return ret;
> + }
> +
> + input_set_poll_interval(input, ts->poll_interval);
> + }
> +
> + input_set_drvdata(input, ts);
> + ts->input = input;
> +
> + ret = input_register_device(input);
> + if (ret) {
> + input_free_device(input);
> + ts->input = NULL;
> + dev_err(dev, "Failed to register input device\n");
> + };
> +
> + return ret;
> +}
> +
> +static int axiom_update_input_dev(struct axiom_data *ts)
> +{
> + axiom_unregister_input_dev(ts);
> +
> + return axiom_register_input_dev(ts, true);
> +}
> +
> +static int axiom_parse_firmware(struct axiom_data *ts)
> +{
> + struct device *dev = ts->dev;
> + struct gpio_desc *gpio;
> + int ret;
> +
> + ts->supplies[0].supply = "vddi";
> + ts->supplies[1].supply = "vdda";
> + ts->num_supplies = ARRAY_SIZE(ts->supplies);
> +
> + ret = devm_regulator_bulk_get(dev, ts->num_supplies, ts->supplies);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "Failed to get power supplies\n");
> +
> + gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(gpio))
> + return dev_err_probe(dev, PTR_ERR(gpio),
> + "Failed to get reset GPIO\n");
> + ts->reset_gpio = gpio;
> +
> + ts->poll_interval = AXIOM_DEFAULT_POLL_INTERVAL_MS;
> + device_property_read_u32(dev, "poll-interval", &ts->poll_interval);
> +
> + return 0;
> +}
> +
> +static int axiom_power_device(struct axiom_data *ts, unsigned int enable)
> +{
> + struct device *dev = ts->dev;
> + int ret;
> +
> + if (!enable) {
> + regulator_bulk_disable(ts->num_supplies, ts->supplies);
> + return 0;
> + }
> +
> + ret = regulator_bulk_enable(ts->num_supplies, ts->supplies);
> + if (ret) {
> + dev_err(dev, "Failed to enable power supplies\n");
> + return ret;
> + }
> +
> + gpiod_set_value_cansleep(ts->reset_gpio, 1);
> + fsleep(2000);
> + gpiod_set_value_cansleep(ts->reset_gpio, 0);
> +
> + fsleep(AXIOM_STARTUP_TIME_MS);
> +
> + return 0;
> +}
> +
> +static int axiom_panel_prepared(struct drm_panel_follower *follower)
> +{
> + struct axiom_data *ts = container_of(follower, struct axiom_data,
> + panel_follower);
> +
> + return pm_runtime_resume_and_get(ts->dev);
> +}
> +
> +static int axiom_panel_unpreparing(struct drm_panel_follower *follower)
> +{
> + struct axiom_data *ts = container_of(follower, struct axiom_data,
> + panel_follower);
> +
> + return pm_runtime_put_sync_suspend(ts->dev);
> +}
> +
> +static const struct drm_panel_follower_funcs axiom_panel_follower_funcs = {
> + .panel_prepared = axiom_panel_prepared,
> + .panel_unpreparing = axiom_panel_unpreparing,
> +};
> +
> +static int axiom_register_panel_follower(struct axiom_data *ts)
> +{
> + struct device *dev = ts->dev;
> +
> + if (!drm_is_panel_follower(dev))
> + return 0;
> +
> + if (device_can_wakeup(dev)) {
> + dev_warn(dev, "Can't follow panel if marked as wakup device\n");
> + return 0;
> + }
> +
> + ts->panel_follower.funcs = &axiom_panel_follower_funcs;
> + ts->is_panel_follower = true;
> +
> + return devm_drm_panel_add_follower(dev, &ts->panel_follower);
> +}
> +
> +static int axiom_i2c_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct axiom_data *ts;
> + int ret;
> +
> + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
> + if (!ts)
> + return dev_err_probe(dev, -ENOMEM,
> + "Failed to allocate driver data\n");
> +
> + ts->regmap = devm_regmap_init_i2c(client, &axiom_i2c_regmap_config);
> + if (IS_ERR(ts->regmap))
> + return dev_err_probe(dev, PTR_ERR(ts->regmap),
> + "Failed to initialize regmap\n");
> +
> + i2c_set_clientdata(client, ts);
> + ts->dev = dev;
> +
> + init_completion(&ts->boot_complete.completion);
> + init_completion(&ts->nvm_write.completion);
> + mutex_init(&ts->fwupdate_lock);
> +
> + ret = axiom_register_fwl(ts);
> + if (ret)
> + return ret;
> +
> + ret = axiom_parse_firmware(ts);
> + if (ret)
> + return ret;
> +
> + ret = axiom_power_device(ts, 1);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to power-on device\n");
> +
> + pm_runtime_set_autosuspend_delay(dev, 10 * MSEC_PER_SEC);
> + pm_runtime_use_autosuspend(dev);
> + pm_runtime_set_active(dev);
> + pm_runtime_get_noresume(dev);
> + ret = devm_pm_runtime_enable(dev);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to enable pm-runtime\n");
> +
> + ret = axiom_register_panel_follower(ts);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to register panel follower\n");
> +
> + ret = axiom_u31_device_discover(ts);
> + /*
> + * Register the device to allow FW updates in case that the current FW
> + * doesn't support the required driver usages or if the device is in
> + * bootloader mode.
> + */
> + if (ret && ret == -EACCES && IS_ENABLED(CONFIG_FW_UPLOAD)) {
> + dev_warn(dev, "Device discovery failed, wait for user fw update\n");
> + pm_runtime_mark_last_busy(dev);
> + pm_runtime_put_sync_autosuspend(dev);
> + return 0;
> + } else if (ret) {
> + pm_runtime_put_sync(dev);
> + return dev_err_probe(dev, ret, "Device discovery failed\n");
> + }
> +
> + ret = axiom_register_input_dev(ts, false);
> + pm_runtime_mark_last_busy(dev);
> + pm_runtime_put_sync_autosuspend(dev);
> + if (ret && IS_ENABLED(CONFIG_FW_UPLOAD))
> + dev_warn(dev, "Failed to register the input device, wait for user fw update\n");
> + else if (ret)
> + return dev_err_probe(dev, ret, "Failed to register input device\n");
> +
> + return 0;
> +}
> +
> +static void axiom_i2c_remove(struct i2c_client *client)
> +{
> + struct axiom_data *ts = i2c_get_clientdata(client);
> +
> + axiom_unregister_input_dev(ts);
> +}
> +
> +static int axiom_runtime_suspend(struct device *dev)
> +{
> + struct axiom_data *ts = dev_get_drvdata(dev);
> + struct i2c_client *client = to_i2c_client(dev);
> +
> + if (client->irq && ts->irq_setup_done)
> + disable_irq(client->irq);
> +
> + return axiom_power_device(ts, 0);
> +}
> +
> +static int axiom_runtime_resume(struct device *dev)
> +{
> + struct axiom_data *ts = dev_get_drvdata(dev);
> + struct i2c_client *client = to_i2c_client(dev);
> + int ret;
> +
> + ret = axiom_power_device(ts, 1);
> + if (ret)
> + return ret;
> +
> + if (client->irq && ts->irq_setup_done)
> + enable_irq(client->irq);
> +
> + return 0;
> +}
> +
> +static DEFINE_RUNTIME_DEV_PM_OPS(axiom_pm_ops, axiom_runtime_suspend,
> + axiom_runtime_resume, NULL);
> +
> +static const struct i2c_device_id axiom_i2c_id_table[] = {
> + { "ax54a" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(i2c, axiom_i2c_id_table);
> +
> +static const struct of_device_id axiom_of_match[] = {
> + { .compatible = "touchnetix,ax54a", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, axiom_of_match);
> +
> +static struct i2c_driver axiom_i2c_driver = {
> + .driver = {
> + .name = KBUILD_MODNAME,
> + .dev_groups = axiom_groups,
> + .pm = pm_ptr(&axiom_pm_ops),
> + .of_match_table = axiom_of_match,
> + },
> + .id_table = axiom_i2c_id_table,
> + .probe = axiom_i2c_probe,
> + .remove = axiom_i2c_remove,
> +};
> +module_i2c_driver(axiom_i2c_driver);
> +
> +MODULE_DESCRIPTION("TouchNetix aXiom touchscreen I2C bus driver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.47.3
>
--
#gernperDu
#CallMeByMyFirstName
Pengutronix e.K. | |
Steuerwalder Str. 21 | https://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-9 |
^ permalink raw reply
* Re: [PATCH] Input: xpad - add support for the ZD O+ Excellence controller
From: Philippos Boon Alexaki @ 2026-01-19 12:35 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel
In-Reply-To: <copxfwjjvudsgq3s72wqimsdcqmrwxzjry3wweg7yrifr6s3cd@gv6w23g4open>
Hi Dmitry,
On Thu, Dec 18, 2025 at 09:08:03PM -0800, Dmitry Torokhov wrote:
> Hi Philippos,
>
> On Thu, Oct 30, 2025 at 11:56:06PM +0100, Philippos Boon Alexaki wrote:
> > Adds support for the ZD O+ Excellence controller.
> > Tested with a wired connection.
> >
> > Signed-off-by: Philippos Boon Alexaki <palexaki@xs4all.nl>
> > ---
> > drivers/input/joystick/xpad.c | 2 ++
> > 1 file changed, 2 insertions(+)
> >
> > diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
> > index 4c94297e17e6..3805780d5be3 100644
> > --- a/drivers/input/joystick/xpad.c
> > +++ b/drivers/input/joystick/xpad.c
> > @@ -296,6 +296,7 @@ static const struct xpad_device {
> > { 0x10f5, 0x7005, "Turtle Beach Recon Controller", 0, XTYPE_XBOXONE },
> > { 0x10f5, 0x7008, "Turtle Beach Recon Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
> > { 0x10f5, 0x7073, "Turtle Beach Stealth Ultra Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
> > + { 0x11c0, 0x5505, "ZD O+ Excellence", 0, XTYPE_XBOX360 },
> > { 0x11c9, 0x55f0, "Nacon GC-100XF", 0, XTYPE_XBOX360 },
> > { 0x11ff, 0x0511, "PXN V900", 0, XTYPE_XBOX360 },
> > { 0x1209, 0x2882, "Ardwiino Controller", 0, XTYPE_XBOX360 },
> > @@ -541,6 +542,7 @@ static const struct usb_device_id xpad_table[] = {
> > XPAD_XBOXONE_VENDOR(0x0f0d), /* Hori controllers */
> > XPAD_XBOX360_VENDOR(0x1038), /* SteelSeries controllers */
> > XPAD_XBOXONE_VENDOR(0x10f5), /* Turtle Beach Controllers */
> > + XPAD_XBOXONE_VENDOR(0x11c0), /* ZD O+ Excellence */
>
> It is really weird that the matching is added for XboxOne but the type
> is set for Xbox360...
This is a typo, it behaves like a Xbox360 so this should be changed
to reflect that. Should I resubmit my patch?
>
> > XPAD_XBOX360_VENDOR(0x11c9), /* Nacon GC100XF */
> > XPAD_XBOX360_VENDOR(0x11ff), /* PXN V900 */
> > XPAD_XBOX360_VENDOR(0x1209), /* Ardwiino Controllers */
>
> Thanks.
>
> --
> Dmitry
Kind regards,
Philippos
^ permalink raw reply
* Re: [PATCH v11 07/11] platform/x86: asus-wmi: Add support for multiple kbd led handlers
From: Antheas Kapenekakis @ 2026-01-19 11:34 UTC (permalink / raw)
To: Ilpo Järvinen
Cc: Denis Benato, platform-driver-x86, linux-input, LKML, Jiri Kosina,
Benjamin Tissoires, Corentin Chary, Luke D . Jones, Hans de Goede
In-Reply-To: <73924a7a-ab91-a11d-313e-5f94f73f5e53@linux.intel.com>
On Mon, 19 Jan 2026 at 10:53, Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Sat, 17 Jan 2026, Denis Benato wrote:
> > On 1/17/26 15:11, Antheas Kapenekakis wrote:
> > > On Sat, 17 Jan 2026 at 14:56, Denis Benato <denis.benato@linux.dev> wrote:
> > >>
> > >> On 1/17/26 14:49, Antheas Kapenekakis wrote:
> > >>> On Sat, 17 Jan 2026 at 14:16, Denis Benato <denis.benato@linux.dev> wrote:
> > >>>> On 1/16/26 14:31, Antheas Kapenekakis wrote:
> > >>>>> Some devices, such as the Z13 have multiple Aura devices connected
> > >>>>> to them by USB. In addition, they might have a WMI interface for
> > >>>>> RGB. In Windows, Armoury Crate exposes a unified brightness slider
> > >>>>> for all of them, with 3 brightness levels.
> > >>>>>
> > >>>>> Therefore, to be synergistic in Linux, and support existing tooling
> > >>>>> such as UPower, allow adding listeners to the RGB device of the WMI
> > >>>>> interface. If WMI does not exist, lazy initialize the interface.
> > >>>>>
> > >>>>> Since hid-asus and asus-wmi can both interact with the led objects
> > >>>>> including from an atomic context, protect the brightness access with a
> > >>>>> spinlock and update the values from a workqueue. Use this workqueue to
> > >>>>> also process WMI keyboard events, so they are handled asynchronously.
> > >>>>>
> > >>>>> Acked-by: Benjamin Tissoires <bentiss@kernel.org>
> > >>>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > >>>>> ---
> > >>>>> drivers/platform/x86/asus-wmi.c | 183 ++++++++++++++++++---
> > >>>>> include/linux/platform_data/x86/asus-wmi.h | 15 ++
> > >>>>> 2 files changed, 173 insertions(+), 25 deletions(-)
> > >>>>>
> > >>>>> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
> > >>>>> index 4aec7ec69250..df2365efb2b8 100644
> > >>>>> --- a/drivers/platform/x86/asus-wmi.c
> > >>>>> +++ b/drivers/platform/x86/asus-wmi.c
> > >>>>> @@ -31,13 +31,13 @@
> > >>>>> #include <linux/pci.h>
> > >>>>> #include <linux/pci_hotplug.h>
> > >>>>> #include <linux/platform_data/x86/asus-wmi.h>
> > >>>>> -#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
> > >>>>> #include <linux/platform_device.h>
> > >>>>> #include <linux/platform_profile.h>
> > >>>>> #include <linux/power_supply.h>
> > >>>>> #include <linux/rfkill.h>
> > >>>>> #include <linux/seq_file.h>
> > >>>>> #include <linux/slab.h>
> > >>>>> +#include <linux/spinlock.h>
> > >>>>> #include <linux/types.h>
> > >>>>> #include <linux/units.h>
> > >>>>>
> > >>>>> @@ -256,6 +256,9 @@ struct asus_wmi {
> > >>>>> int tpd_led_wk;
> > >>>>> struct led_classdev kbd_led;
> > >>>>> int kbd_led_wk;
> > >>>>> + bool kbd_led_notify;
> > >>>>> + bool kbd_led_avail;
> > >>>>> + bool kbd_led_registered;
> > >>>>> struct led_classdev lightbar_led;
> > >>>>> int lightbar_led_wk;
> > >>>>> struct led_classdev micmute_led;
> > >>>>> @@ -264,6 +267,7 @@ struct asus_wmi {
> > >>>>> struct work_struct tpd_led_work;
> > >>>>> struct work_struct wlan_led_work;
> > >>>>> struct work_struct lightbar_led_work;
> > >>>>> + struct work_struct kbd_led_work;
> > >>>>>
> > >>>>> struct asus_rfkill wlan;
> > >>>>> struct asus_rfkill bluetooth;
> > >>>>> @@ -1615,6 +1619,106 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
> > >>>>>
> > >>>>> /* LEDs ***********************************************************************/
> > >>>>>
> > >>>>> +struct asus_hid_ref {
> > >>>>> + struct list_head listeners;
> > >>>>> + struct asus_wmi *asus;
> > >>>>> + /* Protects concurrent access from hid-asus and asus-wmi to leds */
> > >>>>> + spinlock_t lock;
> > >>>>> +};
> > >>>>> +
> > >>>>> +static struct asus_hid_ref asus_ref = {
> > >>>>> + .listeners = LIST_HEAD_INIT(asus_ref.listeners),
> > >>>>> + .asus = NULL,
> > >>>>> + /*
> > >>>>> + * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other
> > >>>>> + * asus variables are read-only after .asus is set.
> > >>>>> + *
> > >>>>> + * The led cdev device is not protected because it calls backlight_get
> > >>>>> + * during initialization, which would result in a nested lock attempt.
> > >>>>> + *
> > >>>>> + * The led cdev is safe to access without a lock because if
> > >>>>> + * kbd_led_avail is true it is initialized before .asus is set and never
> > >>>>> + * changed until .asus is dropped. If kbd_led_avail is false, the led
> > >>>>> + * cdev is registered by the workqueue, which is single-threaded and
> > >>>>> + * cancelled before asus-wmi would access the led cdev to unregister it.
> > >>>>> + *
> > >>>>> + * A spinlock is used, because the protected variables can be accessed
> > >>>>> + * from an IRQ context from asus-hid.
> > >>>>> + */
> > >>>>> + .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
> > >>>>> +};
> > >>>>> +
> > >>>>> +/*
> > >>>>> + * Allows registering hid-asus listeners that want to be notified of
> > >>>>> + * keyboard backlight changes.
> > >>>>> + */
> > >>>>> +int asus_hid_register_listener(struct asus_hid_listener *bdev)
> > >>>>> +{
> > >>>>> + struct asus_wmi *asus;
> > >>>>> +
> > >>>>> + guard(spinlock_irqsave)(&asus_ref.lock);
> > >>>>> + list_add_tail(&bdev->list, &asus_ref.listeners);
> > >>>>> + asus = asus_ref.asus;
> > >>>>> + if (asus)
> > >>>>> + queue_work(asus->led_workqueue, &asus->kbd_led_work);
> > >>>>> + return 0;
> > >>>>> +}
> > >>>>> +EXPORT_SYMBOL_GPL(asus_hid_register_listener);
> > >>>>> +
> > >>>>> +/*
> > >>>>> + * Allows unregistering hid-asus listeners that were added with
> > >>>>> + * asus_hid_register_listener().
> > >>>>> + */
> > >>>>> +void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
> > >>>>> +{
> > >>>>> + guard(spinlock_irqsave)(&asus_ref.lock);
> > >>>>> + list_del(&bdev->list);
> > >>>>> +}
> > >>>>> +EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
> > >>>>> +
> > >>>>> +static void do_kbd_led_set(struct led_classdev *led_cdev, int value);
> > >>>>> +
> > >>>>> +static void kbd_led_update_all(struct work_struct *work)
> > >>>>> +{
> > >>>>> + struct asus_wmi *asus;
> > >>>>> + bool registered, notify;
> > >>>>> + int ret, value;
> > >>>>> +
> > >>>>> + asus = container_of(work, struct asus_wmi, kbd_led_work);
> > >>>>> +
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> > >>>>> + registered = asus->kbd_led_registered;
> > >>>>> + value = asus->kbd_led_wk;
> > >>>>> + notify = asus->kbd_led_notify;
> > >>>>> + }
> > >>>>> +
> > >>>>> + if (!registered) {
> > >>>>> + /*
> > >>>>> + * This workqueue runs under asus-wmi, which means probe has
> > >>>>> + * completed and asus-wmi will keep running until it finishes.
> > >>>>> + * Therefore, we can safely register the LED without holding
> > >>>>> + * a spinlock.
> > >>>>> + */
> > >>>>> + ret = devm_led_classdev_register(&asus->platform_device->dev,
> > >>>>> + &asus->kbd_led);
> > >>>>> + if (!ret) {
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> > >>>>> + asus->kbd_led_registered = true;
> > >>>>> + } else {
> > >>>>> + pr_warn("Failed to register keyboard backlight LED: %d\n", ret);
> > >>>>> + return;
> > >>>>> + }
> > >>>>> + }
> > >>>>> +
> > >>>>> + if (value >= 0)
> > >>>>> + do_kbd_led_set(&asus->kbd_led, value);
> > >>>>> + if (notify) {
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> > >>>>> + asus->kbd_led_notify = false;
> > >>>>> + led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value);
> > >>>>> + }
> > >>>>> +}
> > >>>>> +
> > >>>>> /*
> > >>>>> * These functions actually update the LED's, and are called from a
> > >>>>> * workqueue. By doing this as separate work rather than when the LED
> > >>>>> @@ -1661,7 +1765,8 @@ static void kbd_led_update(struct asus_wmi *asus)
> > >>>>> {
> > >>>>> int ctrl_param = 0;
> > >>>>>
> > >>>>> - ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> > >>>>> + ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
> > >>>>> asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
> > >>>>> }
> > >>>>>
> > >>>>> @@ -1694,14 +1799,23 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
> > >>>>>
> > >>>>> static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
> > >>>>> {
> > >>>>> + struct asus_hid_listener *listener;
> > >>>>> struct asus_wmi *asus;
> > >>>>> int max_level;
> > >>>>>
> > >>>>> asus = container_of(led_cdev, struct asus_wmi, kbd_led);
> > >>>>> max_level = asus->kbd_led.max_brightness;
> > >>>>>
> > >>>>> - asus->kbd_led_wk = clamp_val(value, 0, max_level);
> > >>>>> - kbd_led_update(asus);
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> > >>>>> + asus->kbd_led_wk = clamp_val(value, 0, max_level);
> > >>>>> +
> > >>>>> + if (asus->kbd_led_avail)
> > >>>>> + kbd_led_update(asus);
> > >>>>> +
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> > >>>>> + list_for_each_entry(listener, &asus_ref.listeners, list)
> > >>>>> + listener->brightness_set(listener, asus->kbd_led_wk);
> > >>>>> + }
> > >>>>> }
> > >>>>>
> > >>>>> static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
> > >>>>> @@ -1716,10 +1830,11 @@ static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
> > >>>>>
> > >>>>> static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
> > >>>>> {
> > >>>>> - struct led_classdev *led_cdev = &asus->kbd_led;
> > >>>>> -
> > >>>>> - do_kbd_led_set(led_cdev, value);
> > >>>>> - led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> > >>>>> + asus->kbd_led_wk = value;
> > >>>>> + asus->kbd_led_notify = true;
> > >>>>> + }
> > >>>>> + queue_work(asus->led_workqueue, &asus->kbd_led_work);
> > >>>>> }
> > >>>>>
> > >>>>> static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
> > >>>>> @@ -1729,10 +1844,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
> > >>>>>
> > >>>>> asus = container_of(led_cdev, struct asus_wmi, kbd_led);
> > >>>>>
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> > >>>>> + if (!asus->kbd_led_avail)
> > >>>>> + return asus->kbd_led_wk;
> > >>>>> + }
> > >>>>> +
> > >>>>> retval = kbd_led_read(asus, &value, NULL);
> > >>>>> if (retval < 0)
> > >>>>> return retval;
> > >>>>>
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> > >>>>> + asus->kbd_led_wk = value;
> > >>>>> +
> > >>>>> return value;
> > >>>>> }
> > >>>>>
> > >>>>> @@ -1844,7 +1967,9 @@ static int camera_led_set(struct led_classdev *led_cdev,
> > >>>>>
> > >>>>> static void asus_wmi_led_exit(struct asus_wmi *asus)
> > >>>>> {
> > >>>>> - led_classdev_unregister(&asus->kbd_led);
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> > >>>>> + asus_ref.asus = NULL;
> > >>>>> +
> > >>>>> led_classdev_unregister(&asus->tpd_led);
> > >>>>> led_classdev_unregister(&asus->wlan_led);
> > >>>>> led_classdev_unregister(&asus->lightbar_led);
> > >>>>> @@ -1882,22 +2007,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
> > >>>>> goto error;
> > >>>>> }
> > >>>>>
> > >>>>> - if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
> > >>>>> - pr_info("using asus-wmi for asus::kbd_backlight\n");
> > >>>>> - asus->kbd_led_wk = led_val;
> > >>>>> - asus->kbd_led.name = "asus::kbd_backlight";
> > >>>>> - asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
> > >>>>> - asus->kbd_led.brightness_set_blocking = kbd_led_set;
> > >>>>> - asus->kbd_led.brightness_get = kbd_led_get;
> > >>>>> - asus->kbd_led.max_brightness = 3;
> > >>>>> + asus->kbd_led.name = "asus::kbd_backlight";
> > >>>>> + asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
> > >>>>> + asus->kbd_led.brightness_set_blocking = kbd_led_set;
> > >>>>> + asus->kbd_led.brightness_get = kbd_led_get;
> > >>>>> + asus->kbd_led.max_brightness = 3;
> > >>>>> + asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
> > >>>>> + INIT_WORK(&asus->kbd_led_work, kbd_led_update_all);
> > >>>>>
> > >>>>> + if (asus->kbd_led_avail) {
> > >>>>> + asus->kbd_led_wk = led_val;
> > >>>>> if (num_rgb_groups != 0)
> > >>>>> asus->kbd_led.groups = kbd_rgb_mode_groups;
> > >>>>> + } else {
> > >>>>> + asus->kbd_led_wk = -1;
> > >>>>> + }
> > >>>>>
> > >>>>> - rv = led_classdev_register(&asus->platform_device->dev,
> > >>>>> - &asus->kbd_led);
> > >>>>> - if (rv)
> > >>>>> - goto error;
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> > >>>>> + asus_ref.asus = asus;
> > >>>>> + if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners))
> > >>>>> + queue_work(asus->led_workqueue, &asus->kbd_led_work);
> > >>>>> }
> > >>>>>
> > >>>>> if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
> > >>>>> @@ -4372,6 +4501,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj)
> > >>>>>
> > >>>>> static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
> > >>>>> {
> > >>>>> + enum led_brightness led_value;
> > >>>>> unsigned int key_value = 1;
> > >>>>> bool autorelease = 1;
> > >>>>>
> > >>>>> @@ -4388,19 +4518,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
> > >>>>> return;
> > >>>>> }
> > >>>>>
> > >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> > >>>>> + led_value = asus->kbd_led_wk;
> > >>>>> +
> > >>>>> if (code == NOTIFY_KBD_BRTUP) {
> > >>>>> - kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
> > >>>>> + kbd_led_set_by_kbd(asus, led_value + 1);
> > >>>>> return;
> > >>>>> }
> > >>>>> if (code == NOTIFY_KBD_BRTDWN) {
> > >>>>> - kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
> > >>>>> + kbd_led_set_by_kbd(asus, led_value - 1);
> > >>>>> return;
> > >>>>> }
> > >>>>> if (code == NOTIFY_KBD_BRTTOGGLE) {
> > >>>>> - if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
> > >>>>> + if (led_value == asus->kbd_led.max_brightness)
> > >>>>> kbd_led_set_by_kbd(asus, 0);
> > >>>> This is the toggle leds button, right? I would expect that pressing the toggle
> > >>>> button turns off leds if they are on and turns them on if they are off.
> > >>>>
> > >>>> so if (led_value > 0) { .... }.
> > >>>>
> > >>>> I see the previous code was equivalent to yours but is that what we want?
> > >>> It is common to do 0->1->2->3->0 for the toggle. This is what is
> > >>> currently done for WMI Asus keyboards and e.g., Thinkpads. This patch
> > >>> unifies the behavior for USB keyboards too.
> > >>>
> > >>> I would argue it is better, as you do not need to reach for a
> > >>> userspace slider to set a lower brightness.
> > >>>
> > >>> The current behavior of KDE is 0->3->0 and if the event goes to
> > >>> userspace this is what happens currently. Unless the keyboard
> > >>> reconnects, where brightness just stops working afterwards because
> > >>> upower only probes at boot (I have a follow patch to fix this for
> > >>> Duos).
> > >> Whatever goes for me if hid and wmi handles them the same,
> > >> especially if userspace does the same thing.
> > >>
> > >> In 11/11 you do:
> > >>
> > >> caseASUS_EV_BRTTOGGLE:
> > >> if(brightness >=ASUS_EV_MAX_BRIGHTNESS)
> > >> brightness =0;
> > >> else
> > >> brightness +=1;
> > >> break;
> > >> }
> > >>
> > >> So perhaps here you should do
> > >>
> > >> if (led_value >= asus->kbd_led.max_brightness)
> > >>
> > >>
> > >> purely for consistency?
> > > If I reroll the series I will do it, otherwise you can do a followup
> >
> > The current version is functionally okay.
> >
> > I would only change the name of _LEGACY and see this series merged.
> >
> > Or I can do it after it has been merged together with this change,
> > whatever maintainers think it's best.
>
> In general, I'd prefer we address all known things right away. This is
> kind of same as we prefer not to do back-and-forth changes within a
> series. But since this is equivalent to existing code, it would be nice to
> finally get this series merged so this particular thing can be addressed
> later.
>
> Also, having lots of back-and-forth emails over some small thing usually
> consumes considerable amount of time from multiple people who have to
> read all the discussion through (including me), so if a review request is
> not unreasonable, it often would be the path of least resistance and least
> burdensome to just act upon the feedback instead of arguing whether it can
> be delayed.
Hi,
I agree. As for the name, my reasoning for legacy is that this init
only affects legacy devices (pre 2022) as far as I know (some devices
might refuse RGB commands without an ID1 init, but this driver does
not provide RGB currently). Denis has a (modern) device with one of
the affected PIDs, but he did not test if his device malfunctions or
if it does in which way. He also did not suggest an alternative name
So I am stuck from my side.
Best,
Antheas
> --
> i.
>
>
^ permalink raw reply
* Re: [PATCH v11 07/11] platform/x86: asus-wmi: Add support for multiple kbd led handlers
From: Ilpo Järvinen @ 2026-01-19 9:53 UTC (permalink / raw)
To: Denis Benato
Cc: Antheas Kapenekakis, platform-driver-x86, linux-input, LKML,
Jiri Kosina, Benjamin Tissoires, Corentin Chary, Luke D . Jones,
Hans de Goede
In-Reply-To: <22651aa1-5677-4d76-8850-49e6a348a257@linux.dev>
On Sat, 17 Jan 2026, Denis Benato wrote:
> On 1/17/26 15:11, Antheas Kapenekakis wrote:
> > On Sat, 17 Jan 2026 at 14:56, Denis Benato <denis.benato@linux.dev> wrote:
> >>
> >> On 1/17/26 14:49, Antheas Kapenekakis wrote:
> >>> On Sat, 17 Jan 2026 at 14:16, Denis Benato <denis.benato@linux.dev> wrote:
> >>>> On 1/16/26 14:31, Antheas Kapenekakis wrote:
> >>>>> Some devices, such as the Z13 have multiple Aura devices connected
> >>>>> to them by USB. In addition, they might have a WMI interface for
> >>>>> RGB. In Windows, Armoury Crate exposes a unified brightness slider
> >>>>> for all of them, with 3 brightness levels.
> >>>>>
> >>>>> Therefore, to be synergistic in Linux, and support existing tooling
> >>>>> such as UPower, allow adding listeners to the RGB device of the WMI
> >>>>> interface. If WMI does not exist, lazy initialize the interface.
> >>>>>
> >>>>> Since hid-asus and asus-wmi can both interact with the led objects
> >>>>> including from an atomic context, protect the brightness access with a
> >>>>> spinlock and update the values from a workqueue. Use this workqueue to
> >>>>> also process WMI keyboard events, so they are handled asynchronously.
> >>>>>
> >>>>> Acked-by: Benjamin Tissoires <bentiss@kernel.org>
> >>>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> >>>>> ---
> >>>>> drivers/platform/x86/asus-wmi.c | 183 ++++++++++++++++++---
> >>>>> include/linux/platform_data/x86/asus-wmi.h | 15 ++
> >>>>> 2 files changed, 173 insertions(+), 25 deletions(-)
> >>>>>
> >>>>> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
> >>>>> index 4aec7ec69250..df2365efb2b8 100644
> >>>>> --- a/drivers/platform/x86/asus-wmi.c
> >>>>> +++ b/drivers/platform/x86/asus-wmi.c
> >>>>> @@ -31,13 +31,13 @@
> >>>>> #include <linux/pci.h>
> >>>>> #include <linux/pci_hotplug.h>
> >>>>> #include <linux/platform_data/x86/asus-wmi.h>
> >>>>> -#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
> >>>>> #include <linux/platform_device.h>
> >>>>> #include <linux/platform_profile.h>
> >>>>> #include <linux/power_supply.h>
> >>>>> #include <linux/rfkill.h>
> >>>>> #include <linux/seq_file.h>
> >>>>> #include <linux/slab.h>
> >>>>> +#include <linux/spinlock.h>
> >>>>> #include <linux/types.h>
> >>>>> #include <linux/units.h>
> >>>>>
> >>>>> @@ -256,6 +256,9 @@ struct asus_wmi {
> >>>>> int tpd_led_wk;
> >>>>> struct led_classdev kbd_led;
> >>>>> int kbd_led_wk;
> >>>>> + bool kbd_led_notify;
> >>>>> + bool kbd_led_avail;
> >>>>> + bool kbd_led_registered;
> >>>>> struct led_classdev lightbar_led;
> >>>>> int lightbar_led_wk;
> >>>>> struct led_classdev micmute_led;
> >>>>> @@ -264,6 +267,7 @@ struct asus_wmi {
> >>>>> struct work_struct tpd_led_work;
> >>>>> struct work_struct wlan_led_work;
> >>>>> struct work_struct lightbar_led_work;
> >>>>> + struct work_struct kbd_led_work;
> >>>>>
> >>>>> struct asus_rfkill wlan;
> >>>>> struct asus_rfkill bluetooth;
> >>>>> @@ -1615,6 +1619,106 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
> >>>>>
> >>>>> /* LEDs ***********************************************************************/
> >>>>>
> >>>>> +struct asus_hid_ref {
> >>>>> + struct list_head listeners;
> >>>>> + struct asus_wmi *asus;
> >>>>> + /* Protects concurrent access from hid-asus and asus-wmi to leds */
> >>>>> + spinlock_t lock;
> >>>>> +};
> >>>>> +
> >>>>> +static struct asus_hid_ref asus_ref = {
> >>>>> + .listeners = LIST_HEAD_INIT(asus_ref.listeners),
> >>>>> + .asus = NULL,
> >>>>> + /*
> >>>>> + * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other
> >>>>> + * asus variables are read-only after .asus is set.
> >>>>> + *
> >>>>> + * The led cdev device is not protected because it calls backlight_get
> >>>>> + * during initialization, which would result in a nested lock attempt.
> >>>>> + *
> >>>>> + * The led cdev is safe to access without a lock because if
> >>>>> + * kbd_led_avail is true it is initialized before .asus is set and never
> >>>>> + * changed until .asus is dropped. If kbd_led_avail is false, the led
> >>>>> + * cdev is registered by the workqueue, which is single-threaded and
> >>>>> + * cancelled before asus-wmi would access the led cdev to unregister it.
> >>>>> + *
> >>>>> + * A spinlock is used, because the protected variables can be accessed
> >>>>> + * from an IRQ context from asus-hid.
> >>>>> + */
> >>>>> + .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
> >>>>> +};
> >>>>> +
> >>>>> +/*
> >>>>> + * Allows registering hid-asus listeners that want to be notified of
> >>>>> + * keyboard backlight changes.
> >>>>> + */
> >>>>> +int asus_hid_register_listener(struct asus_hid_listener *bdev)
> >>>>> +{
> >>>>> + struct asus_wmi *asus;
> >>>>> +
> >>>>> + guard(spinlock_irqsave)(&asus_ref.lock);
> >>>>> + list_add_tail(&bdev->list, &asus_ref.listeners);
> >>>>> + asus = asus_ref.asus;
> >>>>> + if (asus)
> >>>>> + queue_work(asus->led_workqueue, &asus->kbd_led_work);
> >>>>> + return 0;
> >>>>> +}
> >>>>> +EXPORT_SYMBOL_GPL(asus_hid_register_listener);
> >>>>> +
> >>>>> +/*
> >>>>> + * Allows unregistering hid-asus listeners that were added with
> >>>>> + * asus_hid_register_listener().
> >>>>> + */
> >>>>> +void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
> >>>>> +{
> >>>>> + guard(spinlock_irqsave)(&asus_ref.lock);
> >>>>> + list_del(&bdev->list);
> >>>>> +}
> >>>>> +EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
> >>>>> +
> >>>>> +static void do_kbd_led_set(struct led_classdev *led_cdev, int value);
> >>>>> +
> >>>>> +static void kbd_led_update_all(struct work_struct *work)
> >>>>> +{
> >>>>> + struct asus_wmi *asus;
> >>>>> + bool registered, notify;
> >>>>> + int ret, value;
> >>>>> +
> >>>>> + asus = container_of(work, struct asus_wmi, kbd_led_work);
> >>>>> +
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> >>>>> + registered = asus->kbd_led_registered;
> >>>>> + value = asus->kbd_led_wk;
> >>>>> + notify = asus->kbd_led_notify;
> >>>>> + }
> >>>>> +
> >>>>> + if (!registered) {
> >>>>> + /*
> >>>>> + * This workqueue runs under asus-wmi, which means probe has
> >>>>> + * completed and asus-wmi will keep running until it finishes.
> >>>>> + * Therefore, we can safely register the LED without holding
> >>>>> + * a spinlock.
> >>>>> + */
> >>>>> + ret = devm_led_classdev_register(&asus->platform_device->dev,
> >>>>> + &asus->kbd_led);
> >>>>> + if (!ret) {
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> >>>>> + asus->kbd_led_registered = true;
> >>>>> + } else {
> >>>>> + pr_warn("Failed to register keyboard backlight LED: %d\n", ret);
> >>>>> + return;
> >>>>> + }
> >>>>> + }
> >>>>> +
> >>>>> + if (value >= 0)
> >>>>> + do_kbd_led_set(&asus->kbd_led, value);
> >>>>> + if (notify) {
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> >>>>> + asus->kbd_led_notify = false;
> >>>>> + led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value);
> >>>>> + }
> >>>>> +}
> >>>>> +
> >>>>> /*
> >>>>> * These functions actually update the LED's, and are called from a
> >>>>> * workqueue. By doing this as separate work rather than when the LED
> >>>>> @@ -1661,7 +1765,8 @@ static void kbd_led_update(struct asus_wmi *asus)
> >>>>> {
> >>>>> int ctrl_param = 0;
> >>>>>
> >>>>> - ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> >>>>> + ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
> >>>>> asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
> >>>>> }
> >>>>>
> >>>>> @@ -1694,14 +1799,23 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
> >>>>>
> >>>>> static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
> >>>>> {
> >>>>> + struct asus_hid_listener *listener;
> >>>>> struct asus_wmi *asus;
> >>>>> int max_level;
> >>>>>
> >>>>> asus = container_of(led_cdev, struct asus_wmi, kbd_led);
> >>>>> max_level = asus->kbd_led.max_brightness;
> >>>>>
> >>>>> - asus->kbd_led_wk = clamp_val(value, 0, max_level);
> >>>>> - kbd_led_update(asus);
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> >>>>> + asus->kbd_led_wk = clamp_val(value, 0, max_level);
> >>>>> +
> >>>>> + if (asus->kbd_led_avail)
> >>>>> + kbd_led_update(asus);
> >>>>> +
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> >>>>> + list_for_each_entry(listener, &asus_ref.listeners, list)
> >>>>> + listener->brightness_set(listener, asus->kbd_led_wk);
> >>>>> + }
> >>>>> }
> >>>>>
> >>>>> static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
> >>>>> @@ -1716,10 +1830,11 @@ static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
> >>>>>
> >>>>> static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
> >>>>> {
> >>>>> - struct led_classdev *led_cdev = &asus->kbd_led;
> >>>>> -
> >>>>> - do_kbd_led_set(led_cdev, value);
> >>>>> - led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> >>>>> + asus->kbd_led_wk = value;
> >>>>> + asus->kbd_led_notify = true;
> >>>>> + }
> >>>>> + queue_work(asus->led_workqueue, &asus->kbd_led_work);
> >>>>> }
> >>>>>
> >>>>> static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
> >>>>> @@ -1729,10 +1844,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
> >>>>>
> >>>>> asus = container_of(led_cdev, struct asus_wmi, kbd_led);
> >>>>>
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> >>>>> + if (!asus->kbd_led_avail)
> >>>>> + return asus->kbd_led_wk;
> >>>>> + }
> >>>>> +
> >>>>> retval = kbd_led_read(asus, &value, NULL);
> >>>>> if (retval < 0)
> >>>>> return retval;
> >>>>>
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> >>>>> + asus->kbd_led_wk = value;
> >>>>> +
> >>>>> return value;
> >>>>> }
> >>>>>
> >>>>> @@ -1844,7 +1967,9 @@ static int camera_led_set(struct led_classdev *led_cdev,
> >>>>>
> >>>>> static void asus_wmi_led_exit(struct asus_wmi *asus)
> >>>>> {
> >>>>> - led_classdev_unregister(&asus->kbd_led);
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> >>>>> + asus_ref.asus = NULL;
> >>>>> +
> >>>>> led_classdev_unregister(&asus->tpd_led);
> >>>>> led_classdev_unregister(&asus->wlan_led);
> >>>>> led_classdev_unregister(&asus->lightbar_led);
> >>>>> @@ -1882,22 +2007,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
> >>>>> goto error;
> >>>>> }
> >>>>>
> >>>>> - if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
> >>>>> - pr_info("using asus-wmi for asus::kbd_backlight\n");
> >>>>> - asus->kbd_led_wk = led_val;
> >>>>> - asus->kbd_led.name = "asus::kbd_backlight";
> >>>>> - asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
> >>>>> - asus->kbd_led.brightness_set_blocking = kbd_led_set;
> >>>>> - asus->kbd_led.brightness_get = kbd_led_get;
> >>>>> - asus->kbd_led.max_brightness = 3;
> >>>>> + asus->kbd_led.name = "asus::kbd_backlight";
> >>>>> + asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
> >>>>> + asus->kbd_led.brightness_set_blocking = kbd_led_set;
> >>>>> + asus->kbd_led.brightness_get = kbd_led_get;
> >>>>> + asus->kbd_led.max_brightness = 3;
> >>>>> + asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
> >>>>> + INIT_WORK(&asus->kbd_led_work, kbd_led_update_all);
> >>>>>
> >>>>> + if (asus->kbd_led_avail) {
> >>>>> + asus->kbd_led_wk = led_val;
> >>>>> if (num_rgb_groups != 0)
> >>>>> asus->kbd_led.groups = kbd_rgb_mode_groups;
> >>>>> + } else {
> >>>>> + asus->kbd_led_wk = -1;
> >>>>> + }
> >>>>>
> >>>>> - rv = led_classdev_register(&asus->platform_device->dev,
> >>>>> - &asus->kbd_led);
> >>>>> - if (rv)
> >>>>> - goto error;
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> >>>>> + asus_ref.asus = asus;
> >>>>> + if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners))
> >>>>> + queue_work(asus->led_workqueue, &asus->kbd_led_work);
> >>>>> }
> >>>>>
> >>>>> if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
> >>>>> @@ -4372,6 +4501,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj)
> >>>>>
> >>>>> static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
> >>>>> {
> >>>>> + enum led_brightness led_value;
> >>>>> unsigned int key_value = 1;
> >>>>> bool autorelease = 1;
> >>>>>
> >>>>> @@ -4388,19 +4518,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
> >>>>> return;
> >>>>> }
> >>>>>
> >>>>> + scoped_guard(spinlock_irqsave, &asus_ref.lock)
> >>>>> + led_value = asus->kbd_led_wk;
> >>>>> +
> >>>>> if (code == NOTIFY_KBD_BRTUP) {
> >>>>> - kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
> >>>>> + kbd_led_set_by_kbd(asus, led_value + 1);
> >>>>> return;
> >>>>> }
> >>>>> if (code == NOTIFY_KBD_BRTDWN) {
> >>>>> - kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
> >>>>> + kbd_led_set_by_kbd(asus, led_value - 1);
> >>>>> return;
> >>>>> }
> >>>>> if (code == NOTIFY_KBD_BRTTOGGLE) {
> >>>>> - if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
> >>>>> + if (led_value == asus->kbd_led.max_brightness)
> >>>>> kbd_led_set_by_kbd(asus, 0);
> >>>> This is the toggle leds button, right? I would expect that pressing the toggle
> >>>> button turns off leds if they are on and turns them on if they are off.
> >>>>
> >>>> so if (led_value > 0) { .... }.
> >>>>
> >>>> I see the previous code was equivalent to yours but is that what we want?
> >>> It is common to do 0->1->2->3->0 for the toggle. This is what is
> >>> currently done for WMI Asus keyboards and e.g., Thinkpads. This patch
> >>> unifies the behavior for USB keyboards too.
> >>>
> >>> I would argue it is better, as you do not need to reach for a
> >>> userspace slider to set a lower brightness.
> >>>
> >>> The current behavior of KDE is 0->3->0 and if the event goes to
> >>> userspace this is what happens currently. Unless the keyboard
> >>> reconnects, where brightness just stops working afterwards because
> >>> upower only probes at boot (I have a follow patch to fix this for
> >>> Duos).
> >> Whatever goes for me if hid and wmi handles them the same,
> >> especially if userspace does the same thing.
> >>
> >> In 11/11 you do:
> >>
> >> caseASUS_EV_BRTTOGGLE:
> >> if(brightness >=ASUS_EV_MAX_BRIGHTNESS)
> >> brightness =0;
> >> else
> >> brightness +=1;
> >> break;
> >> }
> >>
> >> So perhaps here you should do
> >>
> >> if (led_value >= asus->kbd_led.max_brightness)
> >>
> >>
> >> purely for consistency?
> > If I reroll the series I will do it, otherwise you can do a followup
>
> The current version is functionally okay.
>
> I would only change the name of _LEGACY and see this series merged.
>
> Or I can do it after it has been merged together with this change,
> whatever maintainers think it's best.
In general, I'd prefer we address all known things right away. This is
kind of same as we prefer not to do back-and-forth changes within a
series. But since this is equivalent to existing code, it would be nice to
finally get this series merged so this particular thing can be addressed
later.
Also, having lots of back-and-forth emails over some small thing usually
consumes considerable amount of time from multiple people who have to
read all the discussion through (including me), so if a review request is
not unreasonable, it often would be the path of least resistance and least
burdensome to just act upon the feedback instead of arguing whether it can
be delayed.
--
i.
^ permalink raw reply
* [syzbot] Monthly input report (Jan 2026)
From: syzbot @ 2026-01-19 8:22 UTC (permalink / raw)
To: linux-input, linux-kernel, syzkaller-bugs
Hello input maintainers/developers,
This is a 31-day syzbot report for the input subsystem.
All related reports/information can be found at:
https://syzkaller.appspot.com/upstream/s/input
During the period, 0 new issues were detected and 0 were fixed.
In total, 21 issues are still open and 63 have already been fixed.
Some of the still happening issues:
Ref Crashes Repro Title
<1> 3381 Yes WARNING in cm109_urb_irq_callback/usb_submit_urb
https://syzkaller.appspot.com/bug?extid=2d6d691af5ab4b7e66df
<2> 1659 No possible deadlock in evdev_pass_values (2)
https://syzkaller.appspot.com/bug?extid=13d3cb2a3dc61e6092f5
<3> 330 Yes KASAN: slab-out-of-bounds Read in mcp2221_raw_event (2)
https://syzkaller.appspot.com/bug?extid=1018672fe70298606e5f
<4> 166 Yes KASAN: stack-out-of-bounds Read in sched_show_task
https://syzkaller.appspot.com/bug?extid=8d2757d62d403b2d9275
<5> 112 Yes WARNING in cm109_input_open/usb_submit_urb (3)
https://syzkaller.appspot.com/bug?extid=ac0f9c4cc1e034160492
<6> 84 Yes possible deadlock in uinput_request_submit
https://syzkaller.appspot.com/bug?extid=159077b1355b8cd72757
<7> 55 No KASAN: slab-use-after-free Read in report_descriptor_read
https://syzkaller.appspot.com/bug?extid=bc537ca7a0efe33988eb
<8> 19 Yes INFO: task hung in __input_unregister_device (5)
https://syzkaller.appspot.com/bug?extid=78e2288f58b881ed3c45
<9> 13 Yes INFO: task hung in console_callback (6)
https://syzkaller.appspot.com/bug?extid=6027421afa74a2ba440d
<10> 5 Yes possible deadlock in input_ff_flush
https://syzkaller.appspot.com/bug?extid=ed7c6209f62eba1565aa
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
To disable reminders for individual bugs, reply with the following command:
#syz set <Ref> no-reminders
To change bug's subsystems, reply with:
#syz set <Ref> subsystems: new-subsystem
You may send multiple commands in a single email message.
^ permalink raw reply
* Re: [PATCH] Input: goodix - fix inverted Y coordinate on SUPI S10
From: YAJAT APPS @ 2026-01-18 23:41 UTC (permalink / raw)
To: Hans de Goede; +Cc: Dmitry Torokhov, open list:GOODIX TOUCHSCREEN, open list
In-Reply-To: <fa17c5bb-9ab9-46f7-86fb-c06e09617279@kernel.org>
Subject: Re: [PATCH] touchscreen-dmi quirk for SUPI S10
Hi Hans,
I’ve tested the attached patch on the SUPI S10.
The touchscreen uses ACPI HID GDIX1001, so no change is needed there.
With the patch applied, the Y axis is no longer inverted and touch
input now matches the display orientation correctly.
You can add:
Tested-by: Yajat Kumar <yajatapps3@gmail.com>
Thanks for the quick fix and explanation.
Regards,
Yajat Kumar
On Sun, Jan 11, 2026 at 4:30 PM Hans de Goede <hansg@kernel.org> wrote:
>
> Hi Yajat,
>
> On 30-Dec-25 23:16, Yajat Kumar wrote:
> > The touchscreen on the SUPI S10 reports inverted Y coordinates, causing
> > touch input to be mirrored vertically relative to the display.
> >
> > Add a DMI-based quirk to invert the Y coordinate on this device so that
> > touch input matches the display orientation.
> >
> > Tested on SUPI S10 tablet with Goodix touchscreen controller.
> >
> > Signed-off-by: Yajat Kumar <yajatapps3@gmail.com>
>
> Thank you for your patch. This kind of quirks really belong
> in drivers/platform/x86/touchscreen_dmi.c instead of in individual
> touchscreen drivers.
>
> The inverted_x_screen[] DMI quirk is a left-over from before we
> moved these quirks to touchscreen_dmi.c and unfortunately we cannot
> move this because we've not been able to find someone to test this.
>
> I've attached a patch which should fix the issue using
> touchscreen_dmi.c . Note you may need to change the GDIX1001 in
> the patch to GDIX1002, see "ls /sys/bus/i2c/devices" to see which
> ACPI HID your touchscreen is using.
>
> Can you please test the attached patch ?
>
> Regards,
>
> Hans
>
>
>
> > ---
> > drivers/input/touchscreen/goodix.c | 22 ++++++++++++++++++++++
> > 1 file changed, 22 insertions(+)
> >
> > diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c
> > index f8798d11ec03..d675a85a9312 100644
> > --- a/drivers/input/touchscreen/goodix.c
> > +++ b/drivers/input/touchscreen/goodix.c
> > @@ -160,6 +160,22 @@ static const struct dmi_system_id inverted_x_screen[] = {
> > {}
> > };
> >
> > +/*
> > + * Those tablets have their y coordinate inverted
> > + */
> > +static const struct dmi_system_id inverted_y_screen[] = {
> > +#if defined(CONFIG_DMI) && defined(CONFIG_X86)
> > + {
> > + .ident = "SUPI S10",
> > + .matches = {
> > + DMI_MATCH(DMI_SYS_VENDOR, "SUPI"),
> > + DMI_MATCH(DMI_PRODUCT_NAME, "S10")
> > + },
> > + },
> > +#endif
> > + {}
> > +};
> > +
> > /**
> > * goodix_i2c_read - read data from a register of the i2c slave device.
> > *
> > @@ -1212,6 +1228,12 @@ static int goodix_configure_dev(struct goodix_ts_data *ts)
> > "Applying 'inverted x screen' quirk\n");
> > }
> >
> > + if (dmi_check_system(inverted_y_screen)) {
> > + ts->prop.invert_y = true;
> > + dev_dbg(&ts->client->dev,
> > + "Applying 'inverted y screen' quirk\n");
> > + }
> > +
> > error = input_mt_init_slots(ts->input_dev, ts->max_touch_num,
> > INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
> > if (error) {
^ permalink raw reply
* [PATCH v3 0/3] Support FT3518 touchscreen in xiaomi-laurel
From: Yedaya Katsman via B4 Relay @ 2026-01-18 20:29 UTC (permalink / raw)
To: Kamil Gołda, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
Yedaya Katsman, Krzysztof Kozlowski, Dmitry Baryshkov
Adds support for the touchscreen in the Xiaomi Mi A3 (xiaomi-laurel)
smartphone, FocalTech FT3518
Original tree was here:
Link: https://gitlab.postmarketos.org/SzczurekYT/linux/-/commits/laurel
Signed-off-by: Yedaya Katsman <yedaya.ka@gmail.com>
---
Changes in v3:
- Rename regulator node and reorder nodes
- Add gpio pin configuration for pmx_ts_* in sm6125, and reference in the
touchscreen configuration as pinctrl-*. Doesn't have configuration for
the gpio 83 pin since it isn't documented downstream.
- Link to v2: https://lore.kernel.org/r/20260114-touchscreen-patches-v2-0-4215f94c8aba@gmail.com
Changes in v2:
- Fixed name and email in signoffs
- Link to v1: https://lore.kernel.org/r/20260113-touchscreen-patches-v1-0-a10957f32dd8@gmail.com
---
Yedaya Katsman (3):
dt-bindings: input: touchscreen: edt-ft5x06: Add FocalTech FT3518
drivers: input: touchscreen: edt-ft5x06: Add FocalTech FT3518
arm64: dts: qcom: sm6125-xiaomi-laurel-sprout: Add Focaltech FT3518 touchscreen
.../bindings/input/touchscreen/edt-ft5x06.yaml | 1 +
.../boot/dts/qcom/sm6125-xiaomi-laurel-sprout.dts | 113 +++++++++++++++++++++
drivers/input/touchscreen/edt-ft5x06.c | 6 ++
3 files changed, 120 insertions(+)
---
base-commit: b71e635feefc852405b14620a7fc58c4c80c0f73
change-id: 20260113-touchscreen-patches-beb2526bd5fb
Best regards,
--
Yedaya Katsman <yedaya.ka@gmail.com>
^ permalink raw reply
* [PATCH v3 1/3] dt-bindings: input: touchscreen: edt-ft5x06: Add FocalTech FT3518
From: Yedaya Katsman via B4 Relay @ 2026-01-18 20:29 UTC (permalink / raw)
To: Kamil Gołda, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
Yedaya Katsman, Krzysztof Kozlowski
In-Reply-To: <20260118-touchscreen-patches-v3-0-1c6a729c5eb4@gmail.com>
From: Yedaya Katsman <yedaya.ka@gmail.com>
Document FocalTech FT3518 support by adding the compatible.
Co-developed-by: Kamil Gołda <kamil.golda@protonmail.com>
Signed-off-by: Kamil Gołda <kamil.golda@protonmail.com>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Yedaya Katsman <yedaya.ka@gmail.com>
---
Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.yaml b/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.yaml
index 7d3edb58f72d84ed19fb87fdd136c97f855aba00..6f90522de8c0afbe2d9d1e04578316350f66ec58 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.yaml
+++ b/Documentation/devicetree/bindings/input/touchscreen/edt-ft5x06.yaml
@@ -39,6 +39,7 @@ properties:
- edt,edt-ft5406
- edt,edt-ft5506
- evervision,ev-ft5726
+ - focaltech,ft3518
- focaltech,ft5426
- focaltech,ft5452
- focaltech,ft6236
--
2.52.0
^ permalink raw reply related
* [PATCH v3 3/3] arm64: dts: qcom: sm6125-xiaomi-laurel-sprout: Add Focaltech FT3518 touchscreen
From: Yedaya Katsman via B4 Relay @ 2026-01-18 20:29 UTC (permalink / raw)
To: Kamil Gołda, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio
Cc: linux-input, devicetree, linux-kernel, linux-arm-msm,
Yedaya Katsman
In-Reply-To: <20260118-touchscreen-patches-v3-0-1c6a729c5eb4@gmail.com>
From: Yedaya Katsman <yedaya.ka@gmail.com>
Add device tree node for the Focaltech FT3518 touchscreen on
Xiaomi Mi A3 (laurel-sprout).
Add pmx_ts_* gpio configurations and reference them in the touchscreen
node.
Note that gpio pin 83 for the regulator isn't documented downstream
except in the touchscreen node so it's not defined in the tlmm.
Enable qupv3_id_0 and i2c2 bus that the touchscreen is on.
Downstream references:
Link: https://github.com/MiCode/Xiaomi_Kernel_OpenSource/blob/laurel-r-oss/arch/arm64/boot/dts/qcom/trinket-pinctrl.dtsi
Link: https://github.com/MiCode/Xiaomi_Kernel_OpenSource/blob/laurel-r-oss/arch/arm64/boot/dts/qcom/laurel_sprout-qrd.dtsi
Co-developed-by: Kamil Gołda <kamil.golda@protonmail.com>
Signed-off-by: Kamil Gołda <kamil.golda@protonmail.com>
Signed-off-by: Yedaya Katsman <yedaya.ka@gmail.com>
---
.../boot/dts/qcom/sm6125-xiaomi-laurel-sprout.dts | 113 +++++++++++++++++++++
1 file changed, 113 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sm6125-xiaomi-laurel-sprout.dts b/arch/arm64/boot/dts/qcom/sm6125-xiaomi-laurel-sprout.dts
index 994fb0412fcbdf5466f87a325c48b697a37b514b..5e55acacee9585f34eead20661268103f0b7889c 100644
--- a/arch/arm64/boot/dts/qcom/sm6125-xiaomi-laurel-sprout.dts
+++ b/arch/arm64/boot/dts/qcom/sm6125-xiaomi-laurel-sprout.dts
@@ -82,6 +82,18 @@ key-volume-up {
};
};
+ ts_vdd_supply: regulator-ts-vdd {
+ compatible = "regulator-fixed";
+ regulator-name = "ts_vdd_supply";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+
+ gpio = <&tlmm 83 GPIO_ACTIVE_HIGH>;
+ enable-active-high;
+
+ startup-delay-us = <70000>;
+ };
+
thermal-zones {
rf-pa0-thermal {
thermal-sensors = <&pm6125_adc_tm 0>;
@@ -128,6 +140,28 @@ &hsusb_phy1 {
status = "okay";
};
+&i2c2 {
+ status = "okay";
+
+ touchscreen@38 {
+ compatible = "focaltech,ft3518";
+ reg = <0x38>;
+ interrupts-extended = <&tlmm 88 IRQ_TYPE_EDGE_FALLING>;
+
+ vcc-supply = <&ts_vdd_supply>;
+
+ pinctrl-names = "pmx_ts_active","pmx_ts_suspend","pmx_ts_release";
+ pinctrl-0 = <&ts_int_active &ts_reset_active>;
+ pinctrl-1 = <&ts_int_suspend &ts_reset_suspend>;
+ pinctrl-2 = <&ts_release>;
+
+ reset-gpios = <&tlmm 87 GPIO_ACTIVE_LOW>;
+
+ touchscreen-size-x = <720>;
+ touchscreen-size-y = <1560>;
+ };
+};
+
&pm6125_adc {
pinctrl-names = "default";
pinctrl-0 = <&camera_flash_therm &emmc_ufs_therm>;
@@ -220,6 +254,10 @@ &pon_resin {
status = "okay";
};
+&qupv3_id_0 {
+ status = "okay";
+};
+
&rpm_requests {
regulators-0 {
compatible = "qcom,rpm-pm6125-regulators";
@@ -387,6 +425,81 @@ &sdhc_2 {
&tlmm {
gpio-reserved-ranges = <22 2>, <28 6>;
+
+ pmx_ts_reset_active {
+ ts_reset_active: ts_reset_active {
+ mux {
+ pins = "gpio87";
+ function = "gpio";
+ };
+
+ config {
+ pins = "gpio87";
+ drive-strength = <8>;
+ bias-pull-up;
+ };
+ };
+ };
+
+ pmx_ts_reset_suspend {
+ ts_reset_suspend: ts_reset_suspend {
+ mux {
+ pins = "gpio87";
+ function = "gpio";
+ };
+
+ config {
+ pins = "gpio87";
+ drive-strength = <2>;
+ bias-pull-down;
+ };
+ };
+ };
+
+ pmx_ts_int_active {
+ ts_int_active: ts_int_active {
+ mux {
+ pins = "gpio88";
+ function = "gpio";
+ };
+
+ config {
+ pins = "gpio88";
+ drive-strength = <8>;
+ bias-pull-up;
+ };
+ };
+ };
+
+ pmx_ts_int_suspend {
+ ts_int_suspend: ts_int_suspend {
+ mux {
+ pins = "gpio88";
+ function = "gpio";
+ };
+
+ config {
+ pins = "gpio88";
+ drive-strength = <2>;
+ bias-pull-down;
+ };
+ };
+ };
+
+ pmx_ts_release {
+ ts_release: ts_release {
+ mux {
+ pins = "gpio87", "gpio88";
+ function = "gpio";
+ };
+
+ config {
+ pins = "gpio87", "gpio88";
+ drive-strength = <2>;
+ bias-pull-down;
+ };
+ };
+ };
};
&ufs_mem_hc {
--
2.52.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox