Linux Documentation
 help / color / mirror / Atom feed
* [PATCH] Documentation/binfmt-misc.rst: Specify aux vector for "O" flag description
From: Charlie Jenkins @ 2026-04-18 21:08 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Kees Cook
  Cc: linux-doc, linux-mm, linux-kernel, Charlie Jenkins

Instead of replacing the file path in the argument vector, the file
descriptor is passed as AT_EXECFD in the auxilary vector. This appears
to have been the case at least since the git port, update the
documentation to reflect this.

Signed-off-by: Charlie Jenkins <thecharlesjenkins@gmail.com>
---
 Documentation/admin-guide/binfmt-misc.rst | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst
index 59cd902e3549..c0a34fbf8022 100644
--- a/Documentation/admin-guide/binfmt-misc.rst
+++ b/Documentation/admin-guide/binfmt-misc.rst
@@ -68,10 +68,10 @@ Here is what the fields mean:
 	    Legacy behavior of binfmt_misc is to pass the full path
             of the binary to the interpreter as an argument. When this flag is
             included, binfmt_misc will open the file for reading and pass its
-            descriptor as an argument, instead of the full path, thus allowing
-            the interpreter to execute non-readable binaries. This feature
-            should be used with care - the interpreter has to be trusted not to
-            emit the contents of the non-readable binary.
+            descriptor into the auxilary vector with the key "AT_EXECFD", thus
+            allowing the interpreter to execute non-readable binaries. This
+            feature should be used with care - the interpreter has to be trusted
+            not to emit the contents of the non-readable binary.
       ``C`` - credentials
             Currently, the behavior of binfmt_misc is to calculate
             the credentials and security token of the new process according to

---
base-commit: 028ef9c96e96197026887c0f092424679298aae8
change-id: ${change-id}

- Charlie


^ permalink raw reply related

* Re: [PATCH] kbuild: document generation of offset header files
From: Nathan Chancellor @ 2026-04-18 23:23 UTC (permalink / raw)
  To: Piyush Patle
  Cc: Nicolas Schier, Jonathan Corbet, linux-kbuild, linux-doc,
	Shuah Khan, Mark Rutland, Chen Pei, Randy Dunlap, Arnd Bergmann,
	Masahiro Yamada, linux-kernel
In-Reply-To: <CAMB+xkY2judiZiTV7S1DpHuFdZg6WNzpNnn2k0zEUxXmxfBpnw@mail.gmail.com>

Hi Piyush,

On Sat, Apr 18, 2026 at 07:08:33PM +0530, Piyush Patle wrote:
> On Sat, Apr 11, 2026 at 3:43 AM Piyush Patle <piyushpatle228@gmail.com> wrote:
> >
> > Replace the placeholder reference with a description of how Kbuild
> > generates offset header files such as include/generated/asm-offsets.h.
> >
> > Remove the corresponding TODO entry now that this is documented.
> >
> > Signed-off-by: Piyush Patle <piyushpatle228@gmail.com>
...
> Gentle ping on this patch.
> 
> I’d appreciate any feedback whenever you get time, or let me know if I
> should resend/rework anything.

Thanks for the ping. This is still in my review/handling queue but given
that we are in the merge window for 7.1, I am not looking at 7.2
material until 7.1-rc1 is out.

Cheers,
Nathan

^ permalink raw reply

* Re: [PATCH v2 1/2] docs: kdoc: Expand 'at_least' when creating parameter list
From: Randy Dunlap @ 2026-04-18 23:50 UTC (permalink / raw)
  To: Eric Biggers, linux-crypto
  Cc: linux-kernel, Ard Biesheuvel, Jason A . Donenfeld, Herbert Xu,
	linux-doc, Jonathan Corbet, Mauro Carvalho Chehab
In-Reply-To: <20260418192138.15556-2-ebiggers@kernel.org>



On 4/18/26 12:21 PM, Eric Biggers wrote:
> sphinx doesn't know that the kernel headers do:
> 
>     #define at_least static
> 
> Do this replacement before declarations are passed to it.
> 
> This prevents errors like the following from appearing once the
> lib/crypto/ kernel-doc is wired up to the sphinx build:
> 
>    linux/Documentation/crypto/libcrypto:128: ./include/crypto/sha2.h:773: WARNING: Error in declarator or parameters
> Error in declarator or parameters
> Invalid C declaration: Expected ']' in end of array operator. [error at 59]
>   void sha512_final (struct sha512_ctx *ctx, u8 out[at_least SHA512_DIGEST_SIZE])
> 
> Acked-by: Jonathan Corbet <corbet@lwn.net>
> Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
> Signed-off-by: Eric Biggers <ebiggers@kernel.org>

Acked-by: Randy Dunlap <rdunlap@infradead.org>
Tested-by: Randy Dunlap <rdunlap@infradead.org>

> ---
>  tools/lib/python/kdoc/kdoc_parser.py | 5 +++++
>  1 file changed, 5 insertions(+)
> 
> diff --git a/tools/lib/python/kdoc/kdoc_parser.py b/tools/lib/python/kdoc/kdoc_parser.py
> index 74af7ae47aa47..c3f966da533e0 100644
> --- a/tools/lib/python/kdoc/kdoc_parser.py
> +++ b/tools/lib/python/kdoc/kdoc_parser.py
> @@ -437,10 +437,15 @@ class KernelDoc:
>  
>          for arg in args.split(splitter):
>              # Ignore argument attributes
>              arg = KernRe(r'\sPOS0?\s').sub(' ', arg)
>  
> +            # Replace '[at_least ' with '[static '.  This allows sphinx to parse
> +            # array parameter declarations like 'char A[at_least 4]', where
> +            # 'at_least' is #defined to 'static' by the kernel headers.
> +            arg = arg.replace('[at_least ', '[static ')
> +
>              # Strip leading/trailing spaces
>              arg = arg.strip()
>              arg = KernRe(r'\s+').sub(' ', arg, count=1)
>  
>              if arg.startswith('#'):

-- 
~Randy

^ permalink raw reply

* Re: [PATCH v2 2/2] lib/crypto: docs: Add rst documentation to Documentation/crypto/
From: Randy Dunlap @ 2026-04-18 23:52 UTC (permalink / raw)
  To: Eric Biggers, linux-crypto
  Cc: linux-kernel, Ard Biesheuvel, Jason A . Donenfeld, Herbert Xu,
	linux-doc, Jonathan Corbet, Mauro Carvalho Chehab
In-Reply-To: <20260418192138.15556-3-ebiggers@kernel.org>



On 4/18/26 12:21 PM, Eric Biggers wrote:
> Add a documentation file Documentation/crypto/libcrypto.rst which
> provides a high-level overview of lib/crypto/.
> 
> Also add several sub-pages which include the kernel-doc for the
> algorithms that have it.  This makes the existing, quite extensive
> kernel-doc start being included in the HTML and PDF documentation.
> 
> Note that the intent is very much *not* that everyone has to read these
> Documentation/ files.  The library is intended to be straightforward and
> use familiar conventions; generally it should be possible to dive right
> into the kernel-doc.  You shouldn't need to read a lot of documentation
> to just call `sha256()`, for example, or to run the unit tests if you're
> already familiar with KUnit.  (This differs from the traditional crypto
> API which has a larger barrier to entry.)
> 
> Nevertheless, this seems worth adding.  Hopefully it is useful and makes
> LWN no longer consider the library to be "meticulously undocumented".
> 
> Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
> Signed-off-by: Eric Biggers <ebiggers@kernel.org>

Tested-by: Randy Dunlap <rdunlap@infradead.org>
Reviewed-by: Randy Dunlap <rdunlap@infradead.org>

> ---
>  Documentation/crypto/index.rst                |   2 +-
>  .../crypto/libcrypto-blockcipher.rst          |  19 ++
>  Documentation/crypto/libcrypto-hash.rst       |  86 +++++++++
>  Documentation/crypto/libcrypto-signature.rst  |  11 ++
>  Documentation/crypto/libcrypto-utils.rst      |   6 +
>  Documentation/crypto/libcrypto.rst            | 165 ++++++++++++++++++
>  Documentation/crypto/sha3.rst                 |   2 +
>  7 files changed, 290 insertions(+), 1 deletion(-)
>  create mode 100644 Documentation/crypto/libcrypto-blockcipher.rst
>  create mode 100644 Documentation/crypto/libcrypto-hash.rst
>  create mode 100644 Documentation/crypto/libcrypto-signature.rst
>  create mode 100644 Documentation/crypto/libcrypto-utils.rst
>  create mode 100644 Documentation/crypto/libcrypto.rst
> 

-- 
~Randy

^ permalink raw reply

* Re: [PATCH v2 01/11] MAINTAINERS: add an entry for media maintainers profile
From: Randy Dunlap @ 2026-04-19  0:02 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Jonathan Corbet, Linux Doc Mailing List
  Cc: linux-kernel, linux-riscv, workflows, Dan Williams,
	Mauro Carvalho Chehab
In-Reply-To: <361c00348573e45b4e06b674b2b45e47dc65c938.1776405189.git.mchehab+huawei@kernel.org>



On 4/16/26 11:11 PM, Mauro Carvalho Chehab wrote:
> The media subsystem has a maintainers entry profile, but its entry
> is missing at MAINTAINERS.
> 
> Add it.
> 
> Acked-by: Randy Dunlap <rdunlap@infradead.org>
> Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
> Message-ID: <5af4aa6a716228eea4d59dc26b97d642e1e7d419.1776176108.git.mchehab+huawei@kernel.org>
> ---
>  MAINTAINERS | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f0b106a4dd96..620219e48f98 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16115,6 +16115,7 @@ S:	Maintained
>  W:	https://linuxtv.org
>  Q:	http://patchwork.kernel.org/project/linux-media/list/
>  T:	git git://linuxtv.org/media.git
> +P:	Documentation/driver-api/media/maintainer-entry-profile.rst
>  F:	Documentation/admin-guide/media/
>  F:	Documentation/devicetree/bindings/media/
>  F:	Documentation/driver-api/media/

I now see 2 P: entries for MEDIA INPUT INFRASTRUCTURE
and 2 P: entries for X86 ARCHITECTURE.
(don't know how/why)

-- 
~Randy


^ permalink raw reply

* Re: [PATCH v2 00/11] Auto-generate maintainer profile entries
From: Randy Dunlap @ 2026-04-19  0:05 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Albert Ou, Jonathan Corbet,
	Mauro Carvalho Chehab, Palmer Dabbelt, Paul Walmsley
  Cc: linux-doc, linux-kernel, linux-riscv, workflows, Alexandre Ghiti,
	Shuah Khan, Dan Williams
In-Reply-To: <cover.1776405189.git.mchehab+huawei@kernel.org>



On 4/16/26 11:11 PM, Mauro Carvalho Chehab wrote:
> Hi Jon,
> 
> This patch series change the way maintainer entry profile links
> are added to the documentation. Instead of having an entry for
> each of them at an ReST file, get them from MAINTAINERS content.
> 
> That should likely make easier to maintain, as there will be a single
> point to place all such profiles.
> 
> The output is a per-subsystem sorted (*) series of links shown as a
> list like this:
> 
>     - Arm And Arm64 Soc Sub-Architectures (Common Parts)
>     - Arm/Samsung S3C, S5P And Exynos Arm Architectures
>     - Arm/Tesla Fsd Soc Support
>     ...
>     - Xfs Filesystem
> 
> Please notice that the series is doing one logical change per patch.
> I could have merged some changes altogether, but I opted doing it
> in small steps to help reviews. If you prefer, feel free to merge
> maintainers_include changes on merge.
> 
> There is one interesting side effect of this series: there is no
> need to add rst files containing profiles inside a TOC tree: Just
> creating the file anywhere inside Documentation and adding a P entry
> is enough. Adding them to a TOC won't hurt.
> 
> Reported-by: Randy Dunlap <rdunlap@infradead.org>
> Suggested-by: Dan Williams <djbw@kernel.org>
> Closes: https://lore.kernel.org/linux-doc/69dd6299440be_147c801005b@djbw-dev.notmuch/
> 
> (*) At the end, I opted to use sorted(), just to ensure it, even
>     knowing that MAINTAINER entries are supposed to be sorted, as
>     the cost of sorting ~20 already-sorted entries is negligible.
> 
> ---
> 
> v2:
>   - I placed the to MAINTAINERS changes at the beginning.
>   - fix a bug when O=DOCS is used;
>   - proper handle glob "P" entries (just in case, no profiles use it ATM);
>   - when SPHINXDIRS=process, instead of producing warnings, point to
>     entries at https://docs.kernel.org;
>   - MAINTAINERS parsing now happens just once;
>   - The output won't be numered for entries inside numered TOC trees;
>   - TOC tree is now hidden;
>   - instead of display a TOC tree, it shows a list of profiles,
>     ordered and named after file system name taken from MAINTAINERS file;
>   - At the output list, both https and file profiles are shown the same
>     way.
> 
> Mauro Carvalho Chehab (11):
>   MAINTAINERS: add an entry for media maintainers profile
>   MAINTAINERS: add maintainer-tip.rst to X86
>   docs: maintainers_include: auto-generate maintainer profile TOC
>   docs: auto-generate maintainer entry profile links
>   docs: maintainers_include: use a better title for profiles
>   docs: maintainers_include: add external profile URLs
>   docs: maintainers_include: preserve names for files under process/
>   docs: maintainers_include: Only show main entry for profiles
>   docs: maintainers_include: improve its output
>   docs: maintainers_include: fix support for O=dir
>   docs: maintainers_include: parse MAINTAINERS just once
> 
>  .../maintainer/maintainer-entry-profile.rst   |  24 +--
>  .../process/maintainer-handbooks.rst          |  17 +-
>  Documentation/sphinx/maintainers_include.py   | 161 +++++++++++++++---
>  MAINTAINERS                                   |   2 +
>  4 files changed, 150 insertions(+), 54 deletions(-)
> 

Just a note, not asking for a change or fix:

AFAICT, all P: entries are now listed nicely except for:

P:	rust/pin-init/CONTRIBUTING.md

so for the series:
Reviewed-by: Randy Dunlap <rdunlap@infradead.org>
Tested-by: Randy Dunlap <rdunlap@infradead.org>

thanks.
-- 
~Randy

^ permalink raw reply

* [PATCH] docs/ja_JP: translate more of submitting-patches.rst
From: Akiyoshi Kurita @ 2026-04-19  0:10 UTC (permalink / raw)
  To: linux-doc; +Cc: linux-kernel, corbet, akiyks, weibu

Translate the "Separate your changes", "Style-check your changes",
and "Select the recipients for your patch" sections in
Documentation/translations/ja_JP/process/submitting-patches.rst.

Keep the wording close to the English text and wrap lines to match
the style used in the surrounding Japanese translation.

Signed-off-by: Akiyoshi Kurita <weibu@redadmin.org>
---
 .../ja_JP/process/submitting-patches.rst      | 166 ++++++++++++++++++
 1 file changed, 166 insertions(+)

diff --git a/Documentation/translations/ja_JP/process/submitting-patches.rst b/Documentation/translations/ja_JP/process/submitting-patches.rst
index 91bd79a0e9dc..e1adab466507 100644
--- a/Documentation/translations/ja_JP/process/submitting-patches.rst
+++ b/Documentation/translations/ja_JP/process/submitting-patches.rst
@@ -180,3 +180,169 @@ lore.kernel.org のメッセージアーカイブサービスを使ってくだ
 
     $ git log -1 --pretty=fixes 54a4f0239f2e
     Fixes: 54a4f0239f2e ("KVM: MMU: make kvm_mmu_zap_page() return the number of pages it actually freed")
+
+.. _split_changes:
+
+変更を分割する
+--------------
+
+各 **論理的な変更** は、個別のパッチに
+分けてください。
+
+たとえば、単一のドライバに対する変更の中に
+バグ修正と性能改善の両方が含まれているなら、
+それらは 2 つ以上のパッチに分けてください。
+また、変更に API の更新と、その新しい API を
+使う新しいドライバが含まれているなら、
+それらは 2 つのパッチに分けてください。
+
+一方、多数のファイルに対して 1 つの変更を行う
+場合は、それらを 1 つのパッチにまとめてください。
+つまり、1 つの論理的な変更は
+1 つのパッチに収めるべきです。
+
+覚えておくべき点は、各パッチがレビューアに
+とって理解しやすく、検証できる変更に
+なっているべきだ、ということです。
+各パッチは、それ自体の妥当性によって
+正当化できなければなりません。
+
+変更を完成させるために、あるパッチが
+別のパッチに依存するのであれば、
+それでも構いません。その場合は、
+パッチの説明に **"this patch depends on patch X"**
+と書いてください。
+
+変更を一連のパッチに分ける際は、
+シリーズ中の各パッチを適用した後でも、
+カーネルが正しくビルドされ、正常に動作する
+ことを、特に注意して確認してください。
+問題の追跡に ``git bisect`` を使う開発者は、
+あなたのパッチシリーズを任意の地点で
+分割することがあります。途中でバグを
+持ち込めば、彼らに感謝されることは
+ないでしょう。
+
+パッチセットをこれ以上小さくできないなら、
+一度に投稿するのは 15 個程度までにして、
+レビューと統合を待ってください。
+
+
+変更のスタイルを確認する
+------------------------
+
+パッチに基本的なスタイル違反がないか
+確認してください。詳細は
+Documentation/process/coding-style.rst
+を参照してください。
+これを怠ると、単にレビューアの時間を
+無駄にするだけでなく、パッチは
+おそらく読まれもせずに却下されます。
+
+重要な例外が 1 つあります。コードを
+あるファイルから別のファイルへ移動する
+場合です。このときは、コードを移動する
+その同じパッチの中で、移動したコードに
+一切手を加えてはいけません。
+そうすることで、コードの移動という行為と、
+その上に加えた変更とを明確に区別できます。
+これは実際の差分のレビューを大いに助け、
+ツールがコード自体の履歴を、より正確に
+追跡する助けにもなります。
+
+提出前に、パッチスタイルチェッカー
+(``scripts/checkpatch.pl``) で
+パッチを確認してください。
+ただし、スタイルチェッカーは指針として
+見るべきであり、人間の判断の代わりでは
+ないことに注意してください。
+違反を残した方がコードとして見やすいなら、
+そのままにしておく方がよい場合もあります。
+
+チェッカーは 3 つのレベルで報告します:
+
+ - ERROR: 間違っている可能性が非常に高いもの
+ - WARNING: 慎重なレビューが必要なもの
+ - CHECK: 考慮を要するもの
+
+パッチに残した違反については、すべて
+理由を説明できなければなりません。
+
+
+パッチの宛先を選択する
+----------------------
+
+各パッチについて、そのコードを管理している
+適切なサブシステムメンテナと
+メーリングリストは、必ず Cc に
+入れてください。MAINTAINERS ファイルや
+ソースコードの改訂履歴を調べて、
+誰がそのメンテナかを確認してください。
+この段階では ``scripts/get_maintainer.pl`` が
+とても役に立ちます
+(パッチのパスを引数として
+``scripts/get_maintainer.pl`` に
+渡してください)。
+作業対象のサブシステムでメンテナが
+見つからない場合は、Andrew Morton
+(akpm@linux-foundation.org) が
+最後の手段となるメンテナです。
+
+すべてのパッチでは、デフォルトで
+linux-kernel@vger.kernel.org を
+使うべきです。ただし、このリストは
+流量が多いため、目を通さなくなった
+開発者も少なくありません。
+とはいえ、無関係なメーリングリストや
+無関係な人々にスパムを送らないでください。
+
+カーネル関連のメーリングリストの多くは
+kernel.org で運営されており、その一覧は
+https://subspace.kernel.org で
+確認できます。ただし、他所で運営されている
+カーネル関連のリストもあります。
+
+Linux カーネルに取り込まれる
+すべての変更について、最終的な裁定者は
+Linus Torvalds です。
+彼のメールアドレスは
+<torvalds@linux-foundation.org> です。
+Linus は大量のメールを受け取っており、
+現時点では彼に直接届くパッチは
+ごくわずかなので、通常は彼にメールを
+送ることをできるだけ避けてください。
+
+悪用可能なセキュリティバグを修正する
+パッチがあるなら、そのパッチは
+security@kernel.org に送ってください。
+深刻なバグについては、ディストリビュータが
+ユーザーへパッチを行き渡らせる時間を
+確保するため、短期間の embargo を
+検討することがあります。
+そのような場合、そのパッチを
+公開メーリングリストに送るべきでないのは
+当然です。Documentation/process/security-bugs.rst
+も参照してください。
+
+リリース済みカーネルの深刻なバグを
+修正するパッチは、次のような行を
+パッチの sign-off 欄に入れて、
+stable メンテナへ向けてください::
+
+  Cc: stable@vger.kernel.org
+
+これはメールの宛先ではない点に
+注意してください。また、この文書に加えて
+Documentation/process/stable-kernel-rules.rst
+も読んでください。
+
+変更がユーザー空間とカーネルの
+インターフェースに影響する場合は、
+MAINTAINERS ファイルに記載された
+MAN-PAGES メンテナへ man-pages 用の
+パッチ、少なくとも変更通知を送って、
+その情報がマニュアルページに
+反映されるようにしてください。
+ユーザー空間 API の変更は、
+linux-api@vger.kernel.org にも
+Cc してください。
-- 
2.47.3


^ permalink raw reply related

* Re: [PATCH v3] docs/zh_CN: add module-signing Chinese translation
From: Dongliang Mu @ 2026-04-19  3:20 UTC (permalink / raw)
  To: Yan Zhu; +Cc: alexs, seakeel, si.yanteng, corbet, skhan, linux-doc,
	linux-kernel
In-Reply-To: <tencent_91B9F750970BCBFEAC86BBACA3DF31480407@qq.com>


On 4/18/26 10:15 PM, Yan Zhu wrote:
> Hi Dongliang,
>
> On 4/18/2026 2:20 PM, Dongliang Mu wrote:
>>
>> On 4/18/26 12:45 PM, Yan Zhu wrote:
>>> Translate .../admin-guide/module-signing.rst into Chinese.
>>>
>>> Update the translation through commit 0ad9a71933e7
>>> ("modsign: Enable ML-DSA module signing")
>>>
>>> Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
>>> ---
>>
>> Hi Yan,
>>
>> Please remember to add your changelog under the "---". For example, 
>> v1-  >v2: XXX
>>
>
> I replied to the email according to the guidance of how-to.rst in the 
> Chinese thanslation. There was no special explanation there, so I 
> thought that the Chinese translation did not need the explanation of 
> version change. I think I should resend a patch to fill in this 
> description. 

I will submit a follow-up patch to add detailed explanations once my 
polish patch is merged.

The existing description in how-to.rst only briefly introduces 
patchsets, and requires developers to include changelog information in 
the cover letter at Line 407. But it lacks concrete example, I will add 
one example to show how to do this.

>
>> And I wonder where the v2 patch is as I don't find it in my mbox. Do 
>> I miss something?
>
> There may be something wrong with the mailbox. I see that you are in 
> the copy list for the v2 patch. The link is 
> https://lore.kernel.org/lkml/tencent_0101EEFDDBC5D532222BFE0EF2487DCC0805@qq.com

Ah, I have located the v2 patch in my local mbox. The confusion arose 
because you replied to my v1 patch, and subsequently submitted v3. It's 
fine now.

Dongliang Mu

>> Dongliang Mu
>>
>>> .../zh_CN/admin-guide/module-signing.rst      | 249 ++++++++++++++++++
>>>   1 file changed, 249 insertions(+)
>>>   create mode 100644 Documentation/translations/zh_CN/admin-guide/ 
>>> module-signing.rst
>>>
>>> diff --git a/Documentation/translations/zh_CN/admin-guide/module- 
>>> signing.rst b/Documentation/translations/zh_CN/admin-guide/module- 
>>> signing.rst
>>> new file mode 100644
>>> index 000000000000..04b0f1cbafd5
>>> --- /dev/null
>>> +++ b/Documentation/translations/zh_CN/admin-guide/module-signing.rst
>>> @@ -0,0 +1,249 @@
>>> +.. SPDX-License-Identifier: GPL-2.0
>>> +.. include:: ../disclaimer-zh_CN.rst
>>> +
>>> +:Original: Documentation/admin-guide/module-signing.rst
>>> +:翻译:
>>> + 朱岩 Yan Zhu <zhuyan2015@qq.com>
>>> +
>>> +
>>> +==========================
>>> +内核模块签名机制
>>> +==========================
>>> +
>>> +.. 目录
>>> +..
>>> +.. - 概述
>>> +.. - 配置模块签名
>>> +.. - 生成签名密钥
>>> +.. - 内核中的公钥
>>> +.. - 模块手动签名
>>> +.. - 已签名模块和剥离
>>> +.. - 加载已签名模块
>>> +.. - 无效签名和未签名模块
>>> +.. - 管理/保护私钥
>>> +
>>> +
>>> +概述
>>> +====
>>> +
>>> +内核模块签名机制在安装过程中对模块进行加密签名,然后在加载模块时检查 
>>> 签名。这
>>> +通过禁止加载未签名的模块或使用无效密钥签名的模块来提高内核安全性。模 
>>> 块签名通
>>> +过使恶意模块更难加载到内核中来增加安全性。模块签名检查在内核中完成, 
>>> 因此不需
>>> +要受信任的用户空间位。
>>> +
>>> +此机制使用 X.509 ITU-T 标准证书对涉及的公钥进行编码。签名本身不以任何 
>>> 工业标准
>>> +类型编码。内置机制目前仅支持 RSA、NIST P-384 ECDSA 和 NIST FIPS-204 
>>> ML-DSA
>>> +公钥签名标准(尽管它是可插拔的并允许使用其他标准)。对于 RSA 和 
>>> ECDSA,可以使
>>> +用的可能的哈希算法是大小为 256、384 和 512 的 SHA-2 和 SHA-3(算法由 
>>> 签名中的
>>> +数据选择);ML-DSA会自行进行哈希运算,但允许与SHA512哈希算法结合用于 
>>> 签名属性。
>>> +
>>> +配置模块签名
>>> +============
>>> +
>>> +通过进入内核配置的 :menuselection:`Enable Loadable Module Support` 菜 
>>> 单并打
>>> +开以下选项来启用模块签名机制::
>>> +
>>> +    CONFIG_MODULE_SIG    "Module signature verification"
>>> +
>>> +这有多个可用选项:
>>> +
>>> + (1) :menuselection:`Require modules to be validly signed`
>>> +     (``CONFIG_MODULE_SIG_FORCE``)
>>> +
>>> +     这指定了内核应如何处理其密钥未知或未签名的模块。
>>> +
>>> +     如果关闭(即"宽松模式"),则允许使用不可用密钥和未签名的模块,但 
>>> 内核将被
>>> +     标记为受污染,并且相关模块将被标记为受污染,显示字符'E'。
>>> +
>>> +     如果打开(即"限制模式"),只有具有有效签名且可由内核拥有的公钥验 
>>> 证的模块
>>> +     才会被加载。所有其他模块将生成错误。
>>> +
>>> +     无论此处的设置如何,如果模块的签名块无法解析,它将被直接拒绝。
>>> +
>>> +
>>> + (2) :menuselection:`Automatically sign all modules`
>>> +     (``CONFIG_MODULE_SIG_ALL``)
>>> +
>>> +     如果打开此选项,则在构建的 modules_install 
>>> 阶段期间将自动签名模块。
>>> +     如果关闭,则必须使用以下命令手动签名模块::
>>> +
>>> +    scripts/sign-file
>>> +
>>> +
>>> + (3) :menuselection:`Which hash algorithm should modules be signed 
>>> with?`
>>> +
>>> +     这提供了安装阶段将用于签名模块的哈希算法选择:
>>> +
>>> +    =============================== 
>>> ==========================================
>>> +    ``CONFIG_MODULE_SIG_SHA256``    :menuselection:`Sign modules 
>>> with SHA-256`
>>> +    ``CONFIG_MODULE_SIG_SHA384``    :menuselection:`Sign modules 
>>> with SHA-384`
>>> +    ``CONFIG_MODULE_SIG_SHA512``    :menuselection:`Sign modules 
>>> with SHA-512`
>>> +    ``CONFIG_MODULE_SIG_SHA3_256``    :menuselection:`Sign modules 
>>> with SHA3-256`
>>> +    ``CONFIG_MODULE_SIG_SHA3_384``    :menuselection:`Sign modules 
>>> with SHA3-384`
>>> +    ``CONFIG_MODULE_SIG_SHA3_512``    :menuselection:`Sign modules 
>>> with SHA3-512`
>>> +    =============================== 
>>> ==========================================
>>> +
>>> +     此处选择的算法也将被构建到内核中(而不是作为模块),以便使用该算 
>>> 法签名的
>>> +     模块可以在不导致循环依赖的情况下检查其签名。
>>> +
>>> +
>>> + (4) :menuselection:`File name or PKCS#11 URI of module signing key`
>>> +     (``CONFIG_MODULE_SIG_KEY``)
>>> +
>>> +     将此选项设置为除默认值 ``certs/signing_key.pem`` 之外的其他值将 
>>> 禁用签名
>>> +     密钥的自动生成,并允许使用您选择的密钥对内核模块进行签名。提供的 
>>> 字符串应
>>> +     标识包含私钥及其对应的 PEM 格式 X.509 证书的文件,或者在 OpenSSL
>>> +     ENGINE_pkcs11 功能正常的系统上,使用 RFC7512 定义的 PKCS#11 
>>> URI。在后一
>>> +     种情况下,PKCS#11 URI 应引用证书和私钥。
>>> +
>>> +     如果包含私钥的 PEM 文件已加密,或者 PKCS#11 令牌需要 PIN,可以通过 
>>>
>>> +     ``KBUILD_SIGN_PIN`` 变量在构建时提供。
>>> +
>>> +
>>> + (5) :menuselection:`Additional X.509 keys for default system keyring`
>>> +     (``CONFIG_SYSTEM_TRUSTED_KEYS``)
>>> +
>>> +     此选项可设置为包含附加证书的 PEM 编码文件的文件名,这些证书将默 
>>> 认包含在
>>> +     系统密钥环中。
>>> +
>>> +请注意,启用模块签名会为内核构建过程添加对执行签名工具的OpenSSL开发包 
>>> 的依赖。
>>> +
>>> +
>>> +生成签名密钥
>>> +============
>>> +
>>> +生成和检查签名需要加密密钥对。私钥用于生成签名,相应的公钥用于检查签 
>>> 名。私钥
>>> +仅在构建期间需要,之后可以删除或安全存储。公钥被构建到内核中,以便在 
>>> 加载模块
>>> +时可以使用它来检查签名。
>>> +
>>> +在正常情况下,当 ``CONFIG_MODULE_SIG_KEY`` 保持默认值时,如果文件中不 
>>> 存在密
>>> +钥对,内核构建将使用 openssl 自动生成新的密钥对::
>>> +
>>> +    certs/signing_key.pem
>>> +
>>> +在构建 vmlinux 期间(公钥需要构建到 vmlinux 中)使用参数::
>>> +
>>> +    certs/x509.genkey
>>> +
>>> +文件(如果尚不存在也会生成)。
>>> +
>>> +可以在 RSA(``MODULE_SIG_KEY_TYPE_RSA``)、
>>> +ECDSA(``MODULE_SIG_KEY_TYPE_ECDSA``)和
>>> +ML-DSA(``MODULE_SIG_KEY_TYPE_MLDSA_*``)之间选择生成 RSA 4k、NIST 
>>> P-384
>>> +密钥对或 ML-DSA 44、65 或 87 密钥对。
>>> +
>>> +强烈建议您提供自己的 x509.genkey 文件。
>>> +
>>> +最值得注意的是,在 x509.genkey 文件中,req_distinguished_name 部分应 
>>> 从默认值
>>> +更改::
>>> +
>>> +    [ req_distinguished_name ]
>>> +    #O = Unspecified company
>>> +    CN = Build time autogenerated kernel key
>>> +    #emailAddress = unspecified.user@unspecified.company
>>> +
>>> +生成的 RSA 密钥大小也可以通过以下方式设置::
>>> +
>>> +    [ req ]
>>> +    default_bits = 4096
>>> +
>>> +也可以使用位于 Linux 内核源代码树根节点中的 x509.genkey 密钥生成配置 
>>> 文件和
>>> +openssl 命令手动生成公钥/私钥文件。以下是生成公钥/私钥文件的示例::
>>> +
>>> +    openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 \
>>> +       -config x509.genkey -outform PEM -out kernel_key.pem \
>>> +       -keyout kernel_key.pem
>>> +
>>> +然后可以将生成的 kernel_key.pem 文件的完整路径名指定在
>>> +``CONFIG_MODULE_SIG_KEY``选项中,并且将使用其中的证书和密钥而不是自动 
>>> 生成的
>>> +密钥对。
>>> +
>>> +
>>> +内核中的公钥
>>> +============
>>> +
>>> +内核包含一个可由 root 查看的公钥环。它们在名为 ".builtin_trusted_keys" 
>>> 的密
>>> +钥环中,可以通过以下方式查看::
>>> +
>>> +    [root@deneb ~]# cat /proc/keys
>>> +    ...
>>> +    223c7853 I------     1 perm 1f030000     0     0 keyring   
>>> .builtin_trusted_keys: 1
>>> +    302d2d52 I------     1 perm 1f010000     0     0 asymmetri 
>>> Fedora kernel signing key: d69a84e6bce3d216b979e9505b3e3ef9a7118079: 
>>> X509.RSA a7118079 []
>>> +
>>> +除了专门为模块签名生成的公钥外,还可以在 ``CONFIG_SYSTEM_TRUSTED_KEYS`` 
>>> 配置
>>> +选项引用的 PEM 编码文件中提供其他受信任的证书。
>>> +
>>> +此外,架构代码可以从硬件存储中获取公钥并将其添加(例如从 UEFI 密钥数 
>>> 据库)。
>>> +
>>> +最后,可以通过以下方式添加其他公钥::
>>> +
>>> +    keyctl padd asymmetric "" [.builtin_trusted_keys-ID] <[key-file]
>>> +
>>> +例如::
>>> +
>>> +    keyctl padd asymmetric "" 0x223c7853 <my_public_key.x509
>>> +
>>> +但是,请注意,内核只允许将由已驻留在 ``.builtin_trusted_keys`` 中的密 
>>> 钥有效
>>> +签名的密钥添加到 ``.builtin_trusted_keys``。
>>> +
>>> +模块手动签名
>>> +============
>>> +
>>> +要手动对模块进行签名,请使用 Linux 内核源代码树中可用的 scripts/sign- 
>>> file 工
>>> +具。该脚本需要 4 个参数:
>>> +
>>> +    1.  哈希算法(例如,sha256)
>>> +    2.  私钥文件名或 PKCS#11 URI
>>> +    3.  公钥文件名
>>> +    4.  要签名的内核模块
>>> +
>>> +以下是签名内核模块的示例::
>>> +
>>> +    scripts/sign-file sha512 kernel-signkey.priv \
>>> +        kernel-signkey.x509 module.ko
>>> +
>>> +使用的哈希算法不必与配置的算法匹配,但如果不同,应确保哈希算法要么内 
>>> 置在内核
>>> +中,要么可以在不需要自身的情况下加载。
>>> +
>>> +如果私钥需要密码或 PIN,可以在 $KBUILD_SIGN_PIN 环境变量中提供。
>>> +
>>> +
>>> +已签名模块和剥离
>>> +================
>>> +
>>> +已签名模块在末尾简单地附加了数字签名。模块文件末尾的字符串
>>> +``~Module signature appended~.`` 确认签名存在,但不能确认签名有效!
>>> +
>>> +已签名模块是脆弱的,因为签名在定义的ELF容器之外。因此,一旦计算并附加 
>>> 签名,就
>>> +不得剥离它们。请注意,整个模块都是签名的有效载荷,包括签名时存在的任 
>>> 何和所有
>>> +调试信息。
>>> +
>>> +
>>> +加载已签名模块
>>> +==============
>>> +
>>> +模块通过 insmod、modprobe、``init_module()`` 或 ``finit_module()`` 加 
>>> 载,
>>> +与未签名模块完全一样,因为在用户空间中不进行任何处理。
>>> +所有签名检查都在内核内完成。
>>> +
>>> +
>>> +无效签名和未签名模块
>>> +====================
>>> +
>>> +如果启用了 ``CONFIG_MODULE_SIG_FORCE`` 或在内核启动命令提供了
>>> +module.sig_enforce=1,内核将仅加载具有有效签名且具有公钥的模块。否 
>>> 则,它还将
>>> +加载未签名的模块。任何具有不匹配签名的模块将不被允许加载。
>>> +
>>> +任何具有不可解析签名的模块将被拒绝。
>>> +
>>> +
>>> +管理/保护私钥
>>> +==============
>>> +
>>> +由于私钥用于签名模块,病毒和恶意软件可以使用私钥签名模块并危害操作系 
>>> 统。私钥
>>> +必须被销毁或移动到安全位置,而不是保存在内核源代码树的根节点中。
>>> +
>>> +如果使用相同的私钥为多个内核配置签名模块,必须确保模块版本信息足以防 
>>> 止将模块
>>> +加载到不同的内核中。要么设置 ``CONFIG_MODVERSIONS=y``,要么通过更改
>>> +``EXTRAVERSION`` 或 ``CONFIG_LOCALVERSION`` 确保每个配置具有不同的内 
>>> 核发布字
>>> +符串。
>


^ permalink raw reply

* [PATCH 0/2] Documentation/binfmt-misc.rst: Clarify "P" flag
From: Charlie Jenkins @ 2026-04-19  4:11 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Kees Cook
  Cc: linux-doc, linux-mm, linux-kernel, Charlie Jenkins

Improve the wording of the description of the "P" flag to explain that
the interpreter gets the path to the file provided by execve and not the
full path as well as documenting that AT_FLAGS can be read to see if the
"P" flag is set.

Signed-off-by: Charlie Jenkins <thecharlesjenkins@gmail.com>
---
Charlie Jenkins (2):
      Documentation/binfmt-misc.rst: Include AT_FLAGS info in "P" flag description
      Documenation/binfmt-misc.rst: Make "P" flag path desc more precise

 Documentation/admin-guide/binfmt-misc.rst | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
---
base-commit: 028ef9c96e96197026887c0f092424679298aae8
change-id: ${change-id}

- Charlie


^ permalink raw reply

* [PATCH 1/2] Documentation/binfmt-misc.rst: Include AT_FLAGS info in "P" flag description
From: Charlie Jenkins @ 2026-04-19  4:11 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Kees Cook
  Cc: linux-doc, linux-mm, linux-kernel, Charlie Jenkins
In-Reply-To: <20260419-binfmt_misc_doc_update_p-v1-0-757c12f33cc2@gmail.com>

Commit 2347961b11d4 ("binfmt_misc: pass binfmt_misc flags to the
interpreter") added a bit to AT_FLAGS in the aux vector to notify an
interpreter that the 'P' flag was set in binfmt-misc. Clarify that the
interpreter is able to be aware of the 'P' flag by using this bit.

Signed-off-by: Charlie Jenkins <thecharlesjenkins@gmail.com>
---
 Documentation/admin-guide/binfmt-misc.rst | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst
index 59cd902e3549..2e2be2922ba6 100644
--- a/Documentation/admin-guide/binfmt-misc.rst
+++ b/Documentation/admin-guide/binfmt-misc.rst
@@ -61,9 +61,11 @@ Here is what the fields mean:
             vector for this purpose, thus preserving the original ``argv[0]``.
             e.g. If your interp is set to ``/bin/foo`` and you run ``blah``
             (which is in ``/usr/local/bin``), then the kernel will execute
-            ``/bin/foo`` with ``argv[]`` set to ``["/bin/foo", "/usr/local/bin/blah", "blah"]``.  The interp has to be aware of this so it can
-            execute ``/usr/local/bin/blah``
-            with ``argv[]`` set to ``["blah"]``.
+            ``/bin/foo`` with ``argv[]`` set to ``["/bin/foo",
+            "/usr/local/bin/blah", "blah"]``.  The interp can be aware of this
+            by checking if bit 0 in AT_FLAGS in the auxilary vector is set to 1
+            so it can execute ``/usr/local/bin/blah`` with ``argv[]`` set to
+            ``["blah"]``.
       ``O`` - open-binary
 	    Legacy behavior of binfmt_misc is to pass the full path
             of the binary to the interpreter as an argument. When this flag is

-- 
2.53.0


^ permalink raw reply related

* [PATCH 2/2] Documenation/binfmt-misc.rst: Make "P" flag path desc more precise
From: Charlie Jenkins @ 2026-04-19  4:11 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, Kees Cook
  Cc: linux-doc, linux-mm, linux-kernel, Charlie Jenkins
In-Reply-To: <20260419-binfmt_misc_doc_update_p-v1-0-757c12f33cc2@gmail.com>

The "full path" is not passed through to the interpreter, but rather
whatever path was passed to execve. The user's shell is the mechanism
that is converting the executable name "blah" into the full path name of
"/usr/local/bin/blah" instead of the kernel. Clarify this in the
documentation by noting that the path is found in execve and including
"shell" in the conversation for locating "blah".

Signed-off-by: Charlie Jenkins <thecharlesjenkins@gmail.com>
---
 Documentation/admin-guide/binfmt-misc.rst | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst
index 2e2be2922ba6..aabf6599ac49 100644
--- a/Documentation/admin-guide/binfmt-misc.rst
+++ b/Documentation/admin-guide/binfmt-misc.rst
@@ -56,16 +56,16 @@ Here is what the fields mean:
 
       ``P`` - preserve-argv[0]
             Legacy behavior of binfmt_misc is to overwrite
-            the original argv[0] with the full path to the binary. When this
-            flag is included, binfmt_misc will add an argument to the argument
-            vector for this purpose, thus preserving the original ``argv[0]``.
-            e.g. If your interp is set to ``/bin/foo`` and you run ``blah``
-            (which is in ``/usr/local/bin``), then the kernel will execute
-            ``/bin/foo`` with ``argv[]`` set to ``["/bin/foo",
-            "/usr/local/bin/blah", "blah"]``.  The interp can be aware of this
-            by checking if bit 0 in AT_FLAGS in the auxilary vector is set to 1
-            so it can execute ``/usr/local/bin/blah`` with ``argv[]`` set to
-            ``["blah"]``.
+            the original argv[0] with the path to the binary found in execve.
+            When this flag is included, binfmt_misc will add an argument to the
+            argument vector for this purpose, thus preserving the original
+            ``argv[0]``. e.g. If your interp is set to ``/bin/foo`` and you run
+            ``blah`` (which your shell finds in ``/usr/local/bin``), then the
+            kernel will execute ``/bin/foo`` with ``argv[]`` set to
+            ``["/bin/foo", "/usr/local/bin/blah", "blah"]``.  The interp can be
+            aware of this by checking if bit 0 in AT_FLAGS in the auxilary
+            vector is set to 1 so it can execute ``/usr/local/bin/blah`` with
+            ``argv[]`` set to ``["blah"]``.
       ``O`` - open-binary
 	    Legacy behavior of binfmt_misc is to pass the full path
             of the binary to the interpreter as an argument. When this flag is

-- 
2.53.0


^ permalink raw reply related

* [PATCH v4 0/5] Add OneXPlayer Configuration HID Driver
From: Derek J. Clark @ 2026-04-19  4:26 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Zhouwang Huang,
	Derek J . Clark, linux-input, linux-doc, linux-kernel

Adds an HID driver for OneXPlayer HID configuration devices. There are
currently 2 generations of OneXPlayer HID protocol. The first (OneXPlayer
F1 series) only provides an RGB control interface over HID. The Second
(X1 mini series, G1 series, AOKZOE A1X) also includes a hardware level
button mapping interface, vibration intensity settings, and the ability
to switch output between xinput and a debug mode that can be used to debug
the button mapping. Some devices (G1 Series, APEX) use a hybrid of Gen1
RGB control and Gen 2 controller settings. To ensure there is no conflicts
when the driver is loaded, we skip creating the RGB interface for Gen 2
devices if there is a DMI match.

I'll also add a note that Gen 1 devices also have an interface for
setting the key map and debug mode, but that is done entirely over a
serial TTY device so it is not able to be added to this driver. There
are also some "Gen 0" devices (OneXPlayer 2 Series) also use it, but
the TTY interface also handles the RGB control so no support is
provided by this driver for those interfaces.

Signed-off-by: Derel J. Clark <derekjohn.clark@gmail.com>
---
v4:
  - Make all delayed work part of drvdata & ensure they are canceled
    during remove.

v3: https://lore.kernel.org/linux-input/20260412213444.2231505-1-derekjohn.clark@gmail.com/
  - Ensure default button map is properly init during probe.

v2: https://lore.kernel.org/linux-input/20260407041354.2283201-1-derekjohn.clark@gmail.com/
  - Add DMI quirks for certain devices that ship with both GEN1 and GEN2
    MCU to avoid clashing when initializing the RGB interface.
  - Add left & right vibration intensity attributes.
  - Add additional mappings for keyboard inputs.
  - Add a delayed work trigger to re-apply settings after the MCU
    completes initializing after a suspend/resume cycle.

v1: https://lore.kernel.org/linux-input/20260322031615.1524307-1-derekjohn.clark@gmail.com/

Derek J. Clark (5):
  HID: hid-oxp: Add OneXPlayer configuration driver
  HID: hid-oxp: Add Second Generation RGB Control
  HID: hid-oxp: Add Second Generation Gamepad Mode Switch
  HID: hid-oxp: Add Button Mapping Interface
  HID: hid-oxp: Add Vibration Intensity Attribute

 MAINTAINERS           |    6 +
 drivers/hid/Kconfig   |   13 +
 drivers/hid/Makefile  |    1 +
 drivers/hid/hid-ids.h |    6 +
 drivers/hid/hid-oxp.c | 1580 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1606 insertions(+)
 create mode 100644 drivers/hid/hid-oxp.c

-- 
2.53.0


^ permalink raw reply

* [PATCH v4 2/5] HID: hid-oxp: Add Second Generation RGB Control
From: Derek J. Clark @ 2026-04-19  4:26 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Zhouwang Huang,
	Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260419042624.625746-1-derekjohn.clark@gmail.com>

Adds support for the second generation of RGB Control for OneXPlayer
devices. The interface mirrors the first generation, with some
differences to how messages are formatted.

Some devices have both a GEN1 MCU for RGB control and a GEN2 MCU for
button mapping. To avoid conflicts, quirk these devices to skip RGB
setup for the GEN2_USAGE_PAGE.

Reviewed-by: Zhouwang Huang <honjow311@gmail.com>
Tested-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v2:
  - Add DMI quirks table.
---
 drivers/hid/Kconfig   |   1 +
 drivers/hid/hid-ids.h |   3 +
 drivers/hid/hid-oxp.c | 151 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 155 insertions(+)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 2deaec9f467d..b779088b80b6 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -924,6 +924,7 @@ config HID_OXP
 	depends on USB_HID
 	depends on LEDS_CLASS
 	depends on LEDS_CLASS_MULTICOLOR
+	depends on DMI
 	help
 	  Say Y here if you would like to enable support for OneXPlayer handheld
 	  devices that come with RGB LED rings around the joysticks and macro buttons.
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index dcc5a3a70eaf..0d1ff879e959 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1134,6 +1134,9 @@
 #define USB_VENDOR_ID_CRSC			0x1a2c
 #define USB_DEVICE_ID_ONEXPLAYER_GEN1		0xb001
 
+#define USB_VENDOR_ID_WCH			0x1a86
+#define USB_DEVICE_ID_ONEXPLAYER_GEN2		0xfe00
+
 #define USB_VENDOR_ID_ONTRAK		0x0a07
 #define USB_DEVICE_ID_ONTRAK_ADU100	0x0064
 
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index f72bc74a7e6e..835de2118e3c 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -10,6 +10,7 @@
 #include <linux/delay.h>
 #include <linux/dev_printk.h>
 #include <linux/device.h>
+#include <linux/dmi.h>
 #include <linux/hid.h>
 #include <linux/jiffies.h>
 #include <linux/kstrtox.h>
@@ -24,12 +25,15 @@
 #define OXP_PACKET_SIZE 64
 
 #define GEN1_MESSAGE_ID	0xff
+#define GEN2_MESSAGE_ID	0x3f
 
 #define GEN1_USAGE_PAGE	0xff01
+#define GEN2_USAGE_PAGE	0xff00
 
 enum oxp_function_index {
 	OXP_FID_GEN1_RGB_SET =		0x07,
 	OXP_FID_GEN1_RGB_REPLY =	0x0f,
+	OXP_FID_GEN2_STATUS_EVENT =	0xb8,
 };
 
 static struct oxp_hid_cfg {
@@ -122,6 +126,22 @@ struct oxp_gen_1_rgb_report {
 	u8 blue;
 } __packed;
 
+struct oxp_gen_2_rgb_report {
+	u8 report_id;
+	u8 header_id;
+	u8 padding_2;
+	u8 message_id;
+	u8 padding_4[2];
+	u8 enabled;
+	u8 speed;
+	u8 brightness;
+	u8 red;
+	u8 green;
+	u8 blue;
+	u8 padding_12[3];
+	u8 effect;
+} __packed;
+
 static u16 get_usage_page(struct hid_device *hdev)
 {
 	return hdev->collection[0].usage >> 16;
@@ -162,6 +182,44 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
 	return 0;
 }
 
+static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
+				   struct hid_report *report, u8 *data,
+				   int size)
+{
+	struct led_classdev_mc *led_mc = drvdata.led_mc;
+	struct oxp_gen_2_rgb_report *rgb_rep;
+
+	if (data[0] != OXP_FID_GEN2_STATUS_EVENT)
+		return 0;
+
+	if (data[3] != OXP_GET_PROPERTY)
+		return 0;
+
+	rgb_rep = (struct oxp_gen_2_rgb_report *)data;
+	/* Ensure we save monocolor as the list value */
+	drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
+			     OXP_EFFECT_MONO_LIST :
+			     rgb_rep->effect;
+	drvdata.rgb_speed = rgb_rep->speed;
+	drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
+						 OXP_FEAT_ENABLED;
+	drvdata.rgb_brightness = rgb_rep->brightness;
+	led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
+				      led_mc->led_cdev.max_brightness;
+	/* If monocolor had less than 100% brightness on the previous boot,
+	 * there will be no reliable way to determine the real intensity.
+	 * Since intensity scaling is used with a hardware brightness set at max,
+	 * our brightness will always look like 100%. Use the last set value to
+	 * prevent successive boots from lowering the brightness further.
+	 * Brightness will be "wrong" but the effect will remain the same visually.
+	 */
+	led_mc->subled_info[0].intensity = rgb_rep->red;
+	led_mc->subled_info[1].intensity = rgb_rep->green;
+	led_mc->subled_info[2].intensity = rgb_rep->blue;
+
+	return 0;
+}
+
 static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
 			     u8 *data, int size)
 {
@@ -172,6 +230,8 @@ static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
 	switch (up) {
 	case GEN1_USAGE_PAGE:
 		return oxp_hid_raw_event_gen_1(hdev, report, data, size);
+	case GEN2_USAGE_PAGE:
+		return oxp_hid_raw_event_gen_2(hdev, report, data, size);
 	default:
 		break;
 	}
@@ -217,6 +277,18 @@ static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data,
 	return mcu_property_out(header, header_size, data, data_size, NULL, 0);
 }
 
+static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data,
+				  u8 data_size)
+{
+	u8 header[] = { fid, GEN2_MESSAGE_ID, 0x01 };
+	u8 footer[] = { GEN2_MESSAGE_ID, fid };
+	size_t header_size = ARRAY_SIZE(header);
+	size_t footer_size = ARRAY_SIZE(footer);
+
+	return mcu_property_out(header, header_size, data, data_size, footer,
+				footer_size);
+}
+
 static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
 {
 	u16 up = get_usage_page(drvdata.hdev);
@@ -231,6 +303,11 @@ static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
 		if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
 			data[3] = 0x04;
 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4);
+	case GEN2_USAGE_PAGE:
+		data = (u8[6]) { OXP_SET_PROPERTY, 0x00, 0x02, enabled, speed, brightness };
+		if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
+			data[5] = 0x04;
+		return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 6);
 	default:
 		return -ENODEV;
 	}
@@ -245,6 +322,9 @@ static ssize_t oxp_rgb_status_show(void)
 	case GEN1_USAGE_PAGE:
 		data = (u8[1]) { OXP_GET_PROPERTY };
 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
+	case GEN2_USAGE_PAGE:
+		data = (u8[3]) { OXP_GET_PROPERTY, 0x00, 0x02 };
+		return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3);
 	default:
 		return -ENODEV;
 	}
@@ -275,6 +355,16 @@ static int oxp_rgb_color_set(void)
 			data[3 * i + 3] = blue;
 		}
 		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size);
+	case GEN2_USAGE_PAGE:
+		size = 57;
+		data = (u8[57]) { OXP_EFFECT_MONO_TRUE, 0x00, 0x02 };
+
+		for (i = 1; i < size / 3; i++) {
+			data[3 * i] = red;
+			data[3 * i + 1] = green;
+			data[3 * i + 2] = blue;
+		}
+		return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, size);
 	default:
 		return -ENODEV;
 	}
@@ -311,6 +401,10 @@ static int oxp_rgb_effect_set(u8 effect)
 			data = (u8[1]) { effect };
 			ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
 			break;
+		case GEN2_USAGE_PAGE:
+			data = (u8[3]) { effect, 0x00, 0x02 };
+			ret = oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3);
+			break;
 		default:
 			ret = -ENODEV;
 		}
@@ -559,6 +653,56 @@ static struct led_classdev_mc oxp_cdev_rgb = {
 	.subled_info = oxp_rgb_subled_info,
 };
 
+struct quirk_entry {
+	bool hybrid_mcu;
+};
+
+static struct quirk_entry quirk_hybrid_mcu = {
+	.hybrid_mcu = true,
+};
+
+static const struct dmi_system_id oxp_hybrid_mcu_list[] = {
+	{
+		.ident = "OneXPlayer Apex",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER APEX"),
+		},
+		.driver_data = &quirk_hybrid_mcu,
+	},
+	{
+		.ident = "OneXPlayer G1 AMD",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 A"),
+		},
+		.driver_data = &quirk_hybrid_mcu,
+	},
+	{
+		.ident = "OneXPlayer G1 Intel",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 i"),
+		},
+		.driver_data = &quirk_hybrid_mcu,
+	},
+	{},
+};
+
+static bool oxp_hybrid_mcu_device(void)
+{
+	const struct dmi_system_id *dmi_id;
+	struct quirk_entry *quirks;
+
+	dmi_id = dmi_first_match(oxp_hybrid_mcu_list);
+	if (!dmi_id)
+		return false;
+
+	quirks = dmi_id->driver_data;
+
+	return quirks->hybrid_mcu;
+}
+
 static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
 {
 	int ret;
@@ -566,6 +710,10 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
 	hid_set_drvdata(hdev, &drvdata);
 	mutex_init(&drvdata.cfg_mutex);
 	drvdata.hdev = hdev;
+
+	if (up == GEN2_USAGE_PAGE && oxp_hybrid_mcu_device())
+		goto skip_rgb;
+
 	drvdata.led_mc = &oxp_cdev_rgb;
 
 	INIT_DELAYED_WORK(&drvdata.oxp_rgb_queue, oxp_rgb_queue_fn);
@@ -585,6 +733,7 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
 		dev_warn(drvdata.led_mc->led_cdev.dev,
 			 "Failed to query RGB initial state: %i\n", ret);
 
+skip_rgb:
 	return 0;
 }
 
@@ -613,6 +762,7 @@ static int oxp_hid_probe(struct hid_device *hdev,
 
 	switch (up) {
 	case GEN1_USAGE_PAGE:
+	case GEN2_USAGE_PAGE:
 		ret = oxp_cfg_probe(hdev, up);
 		if (ret) {
 			hid_hw_close(hdev);
@@ -634,6 +784,7 @@ static void oxp_hid_remove(struct hid_device *hdev)
 
 static const struct hid_device_id oxp_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WCH, USB_DEVICE_ID_ONEXPLAYER_GEN2) },
 	{}
 };
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH v4 1/5] HID: hid-oxp: Add OneXPlayer configuration driver
From: Derek J. Clark @ 2026-04-19  4:26 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Zhouwang Huang,
	Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260419042624.625746-1-derekjohn.clark@gmail.com>

Adds OneXPlayer HID configuration driver. In this initial driver patch,
add the RGB interface for the first generation of HID based RGB control.

This interface provides the following attributes:
- brightness: provided by the LED core, this works in a fairly unique
  way on this device. The hardware accepts 5 brightness values (0-4),
  which affects the brightness of the multicolor and animated effects
  built into the MCU firmware. For monocolor settings, the device
  expects the hardware brightness value to be pushed to maximum, then we
  apply brightness adjustments mathematically based on % (0-100). This
  leads to some odd conversion as we need the brightness slider to reach
  the full range, but it has no affect when incrementing between the
  division points for other effects.
- multi-intensity: provided by the LED core for red, green, and blue.
- effect: Allows the MCU to set 19 individual effects.
- effect_index: Lists the 19 valid effect names for the interface.
- enabled: Allows the MCU to toggle the RGB interface on/off.
- enabled_index: Lists the valid states for enabled.
- speed: Allows the MCU to set the animation rate for the various
  effects.
- speed_range: Lists the valid range of speed (0-9).

The MCU also has a few odd quirks that make sending multiple synchronous
events challenging. It will essentially freeze if it receives another
message before it has finished processing the last command. It also will
not reply if you wait on it using a completion. To get around this, we
do a 200ms sleep inside a work queue thread and debounce all but the most
recent message using a 50ms mod_delayed_work. This will cache the last
write, queue the work, then return so userspace can release its write
thread. The work queue is only used for brightness/multi-intensity as
that is the path likely to receive rapid successive writes.

Reviewed-by: Zhouwang Huang <honjow311@gmail.com>
Tested-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
  - Make oxp_rgb_queue delayed work struct part of drvdata and properly
    init, add cancel during remove.
---
 MAINTAINERS           |   6 +
 drivers/hid/Kconfig   |  12 +
 drivers/hid/Makefile  |   1 +
 drivers/hid/hid-ids.h |   3 +
 drivers/hid/hid-oxp.c | 652 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 674 insertions(+)
 create mode 100644 drivers/hid/hid-oxp.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 6f6517bf4f97..dae814192fa4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19707,6 +19707,12 @@ S:	Maintained
 F:	drivers/mtd/nand/onenand/
 F:	include/linux/mtd/onenand*.h
 
+ONEXPLAYER HID DRIVER
+M:	Derek J. Clark <derekjohn.clark@gmail.com>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	drivers/hid/hid-oxp.c
+
 ONEXPLAYER PLATFORM EC DRIVER
 M:	Antheas Kapenekakis <lkml@antheas.dev>
 M:	Derek John Clark <derekjohn.clark@gmail.com>
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 3c034cd32fa8..2deaec9f467d 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -919,6 +919,18 @@ config HID_ORTEK
 	   - Ortek WKB-2000
 	   - Skycable wireless presenter
 
+config HID_OXP
+	tristate "OneXPlayer handheld controller configuration support"
+	depends on USB_HID
+	depends on LEDS_CLASS
+	depends on LEDS_CLASS_MULTICOLOR
+	help
+	  Say Y here if you would like to enable support for OneXPlayer handheld
+	  devices that come with RGB LED rings around the joysticks and macro buttons.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called hid-oxp.
+
 config HID_PANTHERLORD
 	tristate "Pantherlord/GreenAsia game controller"
 	help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 03ef72ec4499..bda8a24c9257 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -99,6 +99,7 @@ obj-$(CONFIG_HID_NTI)			+= hid-nti.o
 obj-$(CONFIG_HID_NTRIG)		+= hid-ntrig.o
 obj-$(CONFIG_HID_NVIDIA_SHIELD)	+= hid-nvidia-shield.o
 obj-$(CONFIG_HID_ORTEK)		+= hid-ortek.o
+obj-$(CONFIG_HID_OXP)		+= hid-oxp.o
 obj-$(CONFIG_HID_PRODIKEYS)	+= hid-prodikeys.o
 obj-$(CONFIG_HID_PANTHERLORD)	+= hid-pl.o
 obj-$(CONFIG_HID_PENMOUNT)	+= hid-penmount.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 5bad81222c6e..dcc5a3a70eaf 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1131,6 +1131,9 @@
 #define USB_VENDOR_ID_NVIDIA				0x0955
 #define USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER	0x7214
 
+#define USB_VENDOR_ID_CRSC			0x1a2c
+#define USB_DEVICE_ID_ONEXPLAYER_GEN1		0xb001
+
 #define USB_VENDOR_ID_ONTRAK		0x0a07
 #define USB_DEVICE_ID_ONTRAK_ADU100	0x0064
 
diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
new file mode 100644
index 000000000000..f72bc74a7e6e
--- /dev/null
+++ b/drivers/hid/hid-oxp.c
@@ -0,0 +1,652 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  HID driver for OneXPlayer gamepad configuration devices.
+ *
+ *  Copyright (c) 2026 Valve Corporation
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/jiffies.h>
+#include <linux/kstrtox.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include "hid-ids.h"
+
+#define OXP_PACKET_SIZE 64
+
+#define GEN1_MESSAGE_ID	0xff
+
+#define GEN1_USAGE_PAGE	0xff01
+
+enum oxp_function_index {
+	OXP_FID_GEN1_RGB_SET =		0x07,
+	OXP_FID_GEN1_RGB_REPLY =	0x0f,
+};
+
+static struct oxp_hid_cfg {
+	struct delayed_work oxp_rgb_queue;
+	struct led_classdev_mc *led_mc;
+	struct hid_device *hdev;
+	struct mutex cfg_mutex; /*ensure single synchronous output report*/
+	u8 rgb_brightness;
+	u8 rgb_effect;
+	u8 rgb_speed;
+	u8 rgb_en;
+} drvdata;
+
+enum oxp_feature_en_index {
+	OXP_FEAT_DISABLED,
+	OXP_FEAT_ENABLED,
+};
+
+static const char *const oxp_feature_en_text[] = {
+	[OXP_FEAT_DISABLED] = "false",
+	[OXP_FEAT_ENABLED] = "true",
+};
+
+enum oxp_rgb_effect_index {
+	OXP_UNKNOWN,
+	OXP_EFFECT_AURORA,
+	OXP_EFFECT_BIRTHDAY,
+	OXP_EFFECT_FLOWING,
+	OXP_EFFECT_CHROMA_1,
+	OXP_EFFECT_NEON,
+	OXP_EFFECT_CHROMA_2,
+	OXP_EFFECT_DREAMY,
+	OXP_EFFECT_WARM,
+	OXP_EFFECT_CYBERPUNK,
+	OXP_EFFECT_SEA,
+	OXP_EFFECT_SUNSET,
+	OXP_EFFECT_COLORFUL,
+	OXP_EFFECT_MONSTER,
+	OXP_EFFECT_GREEN,
+	OXP_EFFECT_BLUE,
+	OXP_EFFECT_YELLOW,
+	OXP_EFFECT_TEAL,
+	OXP_EFFECT_PURPLE,
+	OXP_EFFECT_FOGGY,
+	OXP_EFFECT_MONO_LIST, /* placeholder for effect_index_show */
+};
+
+/* These belong to rgb_effect_index, but we want to hide them from
+ * rgb_effect_text
+ */
+
+#define OXP_GET_PROPERTY 0xfc
+#define OXP_SET_PROPERTY 0xfd
+#define OXP_EFFECT_MONO_TRUE 0xfe /* actual index for monocolor */
+
+static const char *const oxp_rgb_effect_text[] = {
+	[OXP_UNKNOWN] = "unknown",
+	[OXP_EFFECT_AURORA] = "aurora",
+	[OXP_EFFECT_BIRTHDAY] = "birthday_cake",
+	[OXP_EFFECT_FLOWING] = "flowing_light",
+	[OXP_EFFECT_CHROMA_1] = "chroma_popping",
+	[OXP_EFFECT_NEON] = "neon",
+	[OXP_EFFECT_CHROMA_2] = "chroma_breathing",
+	[OXP_EFFECT_DREAMY] = "dreamy",
+	[OXP_EFFECT_WARM] = "warm_sun",
+	[OXP_EFFECT_CYBERPUNK] = "cyberpunk",
+	[OXP_EFFECT_SEA] = "sea_foam",
+	[OXP_EFFECT_SUNSET] = "sunset_afterglow",
+	[OXP_EFFECT_COLORFUL] = "colorful",
+	[OXP_EFFECT_MONSTER] = "monster_woke",
+	[OXP_EFFECT_GREEN] = "green_breathing",
+	[OXP_EFFECT_BLUE] = "blue_breathing",
+	[OXP_EFFECT_YELLOW] = "yellow_breathing",
+	[OXP_EFFECT_TEAL] = "teal_breathing",
+	[OXP_EFFECT_PURPLE] = "purple_breathing",
+	[OXP_EFFECT_FOGGY] = "foggy_haze",
+	[OXP_EFFECT_MONO_LIST] = "monocolor",
+};
+
+struct oxp_gen_1_rgb_report {
+	u8 report_id;
+	u8 message_id;
+	u8 padding_2[2];
+	u8 effect;
+	u8 enabled;
+	u8 speed;
+	u8 brightness;
+	u8 red;
+	u8 green;
+	u8 blue;
+} __packed;
+
+static u16 get_usage_page(struct hid_device *hdev)
+{
+	return hdev->collection[0].usage >> 16;
+}
+
+static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
+				   struct hid_report *report, u8 *data,
+				   int size)
+{
+	struct led_classdev_mc *led_mc = drvdata.led_mc;
+	struct oxp_gen_1_rgb_report *rgb_rep;
+
+	if (data[1] != OXP_FID_GEN1_RGB_REPLY)
+		return 0;
+
+	rgb_rep = (struct oxp_gen_1_rgb_report *)data;
+	/* Ensure we save monocolor as the list value */
+	drvdata.rgb_effect = rgb_rep->effect == OXP_EFFECT_MONO_TRUE ?
+			     OXP_EFFECT_MONO_LIST :
+			     rgb_rep->effect;
+	drvdata.rgb_speed = rgb_rep->speed;
+	drvdata.rgb_en = rgb_rep->enabled == 0 ? OXP_FEAT_DISABLED :
+						 OXP_FEAT_ENABLED;
+	drvdata.rgb_brightness = rgb_rep->brightness;
+	led_mc->led_cdev.brightness = rgb_rep->brightness / 4 *
+				      led_mc->led_cdev.max_brightness;
+	/* If monocolor had less than 100% brightness on the previous boot,
+	 * there will be no reliable way to determine the real intensity.
+	 * Since intensity scaling is used with a hardware brightness set at max,
+	 * our brightness will always look like 100%. Use the last set value to
+	 * prevent successive boots from lowering the brightness further.
+	 * Brightness will be "wrong" but the effect will remain the same visually.
+	 */
+	led_mc->subled_info[0].intensity = rgb_rep->red;
+	led_mc->subled_info[1].intensity = rgb_rep->green;
+	led_mc->subled_info[2].intensity = rgb_rep->blue;
+
+	return 0;
+}
+
+static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *report,
+			     u8 *data, int size)
+{
+	u16 up = get_usage_page(hdev);
+
+	dev_dbg(&hdev->dev, "raw event data: [%*ph]\n", OXP_PACKET_SIZE, data);
+
+	switch (up) {
+	case GEN1_USAGE_PAGE:
+		return oxp_hid_raw_event_gen_1(hdev, report, data, size);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int mcu_property_out(u8 *header, size_t header_size, u8 *data,
+			    size_t data_size, u8 *footer, size_t footer_size)
+{
+	unsigned char *dmabuf __free(kfree) = kzalloc(OXP_PACKET_SIZE, GFP_KERNEL);
+	int ret;
+
+	if (!dmabuf)
+		return -ENOMEM;
+
+	if (header_size + data_size + footer_size > OXP_PACKET_SIZE)
+		return -EINVAL;
+
+	guard(mutex)(&drvdata.cfg_mutex);
+	memcpy(dmabuf, header, header_size);
+	memcpy(dmabuf + header_size, data, data_size);
+	if (footer_size)
+		memcpy(dmabuf + OXP_PACKET_SIZE - footer_size, footer, footer_size);
+
+	dev_dbg(&drvdata.hdev->dev, "raw data: [%*ph]\n", OXP_PACKET_SIZE, dmabuf);
+
+	ret = hid_hw_output_report(drvdata.hdev, dmabuf, OXP_PACKET_SIZE);
+	if (ret < 0)
+		return ret;
+
+	/* MCU takes 200ms to be ready for another command. */
+	msleep(200);
+	return ret == OXP_PACKET_SIZE ? 0 : -EIO;
+}
+
+static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data,
+				  u8 data_size)
+{
+	u8 header[] = { fid, GEN1_MESSAGE_ID };
+	size_t header_size = ARRAY_SIZE(header);
+
+	return mcu_property_out(header, header_size, data, data_size, NULL, 0);
+}
+
+static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
+{
+	u16 up = get_usage_page(drvdata.hdev);
+	u8 *data;
+
+	/* Always default to max brightness and use intensity scaling when in
+	 * monocolor mode.
+	 */
+	switch (up) {
+	case GEN1_USAGE_PAGE:
+		data = (u8[4]) { OXP_SET_PROPERTY, enabled, speed, brightness };
+		if (drvdata.rgb_effect == OXP_EFFECT_MONO_LIST)
+			data[3] = 0x04;
+		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4);
+	default:
+		return -ENODEV;
+	}
+}
+
+static ssize_t oxp_rgb_status_show(void)
+{
+	u16 up = get_usage_page(drvdata.hdev);
+	u8 *data;
+
+	switch (up) {
+	case GEN1_USAGE_PAGE:
+		data = (u8[1]) { OXP_GET_PROPERTY };
+		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
+	default:
+		return -ENODEV;
+	}
+}
+
+static int oxp_rgb_color_set(void)
+{
+	u8 max_br = drvdata.led_mc->led_cdev.max_brightness;
+	u8 br = drvdata.led_mc->led_cdev.brightness;
+	u16 up = get_usage_page(drvdata.hdev);
+	u8 green, red, blue;
+	size_t size;
+	u8 *data;
+	int i;
+
+	red = br * drvdata.led_mc->subled_info[0].intensity / max_br;
+	green = br * drvdata.led_mc->subled_info[1].intensity / max_br;
+	blue = br * drvdata.led_mc->subled_info[2].intensity / max_br;
+
+	switch (up) {
+	case GEN1_USAGE_PAGE:
+		size = 55;
+		data = (u8[55]) { OXP_EFFECT_MONO_TRUE };
+
+		for (i = 0; i < (size - 1) / 3; i++) {
+			data[3 * i + 1] = red;
+			data[3 * i + 2] = green;
+			data[3 * i + 3] = blue;
+		}
+		return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size);
+	default:
+		return -ENODEV;
+	}
+}
+
+static int oxp_rgb_effect_set(u8 effect)
+{
+	u16 up = get_usage_page(drvdata.hdev);
+	u8 *data;
+	int ret;
+
+	switch (effect) {
+	case OXP_EFFECT_AURORA:
+	case OXP_EFFECT_BIRTHDAY:
+	case OXP_EFFECT_FLOWING:
+	case OXP_EFFECT_CHROMA_1:
+	case OXP_EFFECT_NEON:
+	case OXP_EFFECT_CHROMA_2:
+	case OXP_EFFECT_DREAMY:
+	case OXP_EFFECT_WARM:
+	case OXP_EFFECT_CYBERPUNK:
+	case OXP_EFFECT_SEA:
+	case OXP_EFFECT_SUNSET:
+	case OXP_EFFECT_COLORFUL:
+	case OXP_EFFECT_MONSTER:
+	case OXP_EFFECT_GREEN:
+	case OXP_EFFECT_BLUE:
+	case OXP_EFFECT_YELLOW:
+	case OXP_EFFECT_TEAL:
+	case OXP_EFFECT_PURPLE:
+	case OXP_EFFECT_FOGGY:
+		switch (up) {
+		case GEN1_USAGE_PAGE:
+			data = (u8[1]) { effect };
+			ret = oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1);
+			break;
+		default:
+			ret = -ENODEV;
+		}
+		break;
+	case OXP_EFFECT_MONO_LIST:
+		ret = oxp_rgb_color_set();
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (ret)
+		return ret;
+
+	drvdata.rgb_effect = effect;
+
+	return 0;
+}
+
+static ssize_t enabled_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	int ret;
+	u8 val;
+
+	ret = sysfs_match_string(oxp_feature_en_text, buf);
+	if (ret < 0)
+		return ret;
+	val = ret;
+
+	ret = oxp_rgb_status_store(val, drvdata.rgb_speed,
+				   drvdata.rgb_brightness);
+	if (ret)
+		return ret;
+
+	drvdata.rgb_en = val;
+	return count;
+}
+
+static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	int ret;
+
+	ret = oxp_rgb_status_show();
+	if (ret)
+		return ret;
+
+	if (drvdata.rgb_en >= ARRAY_SIZE(oxp_feature_en_text))
+		return -EINVAL;
+
+	return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.rgb_en]);
+}
+static DEVICE_ATTR_RW(enabled);
+
+static ssize_t enabled_index_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	size_t count = 0;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(oxp_feature_en_text); i++)
+		count += sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]);
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+static DEVICE_ATTR_RO(enabled_index);
+
+static ssize_t effect_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	int ret;
+	u8 val;
+
+	ret = sysfs_match_string(oxp_rgb_effect_text, buf);
+	if (ret < 0)
+		return ret;
+
+	val = ret;
+
+	ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed,
+				   drvdata.rgb_brightness);
+	if (ret)
+		return ret;
+
+	ret = oxp_rgb_effect_set(val);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t effect_show(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	int ret;
+
+	ret = oxp_rgb_status_show();
+	if (ret)
+		return ret;
+
+	if (drvdata.rgb_effect >= ARRAY_SIZE(oxp_rgb_effect_text))
+		return -EINVAL;
+
+	return sysfs_emit(buf, "%s\n", oxp_rgb_effect_text[drvdata.rgb_effect]);
+}
+
+static DEVICE_ATTR_RW(effect);
+
+static ssize_t effect_index_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	size_t count = 0;
+	unsigned int i;
+
+	for (i = 1; i < ARRAY_SIZE(oxp_rgb_effect_text); i++)
+		count += sysfs_emit_at(buf, count, "%s ", oxp_rgb_effect_text[i]);
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+static DEVICE_ATTR_RO(effect_index);
+
+static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	int ret;
+	u8 val;
+
+	ret = kstrtou8(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val > 9)
+		return -EINVAL;
+
+	ret = oxp_rgb_status_store(drvdata.rgb_en, val, drvdata.rgb_brightness);
+	if (ret)
+		return ret;
+
+	drvdata.rgb_speed = val;
+	return count;
+}
+
+static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	int ret;
+
+	ret = oxp_rgb_status_show();
+	if (ret)
+		return ret;
+
+	if (drvdata.rgb_speed > 9)
+		return -EINVAL;
+
+	return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed);
+}
+static DEVICE_ATTR_RW(speed);
+
+static ssize_t speed_range_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "0-9\n");
+}
+static DEVICE_ATTR_RO(speed_range);
+
+static void oxp_rgb_queue_fn(struct work_struct *work)
+{
+	unsigned int max_brightness = drvdata.led_mc->led_cdev.max_brightness;
+	unsigned int brightness = drvdata.led_mc->led_cdev.brightness;
+	u8 val = 4 * brightness / max_brightness;
+	int ret;
+
+	if (drvdata.rgb_brightness != val) {
+		ret = oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, val);
+		if (ret)
+			dev_err(drvdata.led_mc->led_cdev.dev,
+				"Error: Failed to write RGB Status: %i\n", ret);
+
+		drvdata.rgb_brightness = val;
+	}
+
+	if (drvdata.rgb_effect != OXP_EFFECT_MONO_LIST)
+		return;
+
+	ret = oxp_rgb_effect_set(drvdata.rgb_effect);
+	if (ret)
+		dev_err(drvdata.led_mc->led_cdev.dev, "Error: Failed to write RGB color: %i\n",
+			ret);
+}
+
+static void oxp_rgb_brightness_set(struct led_classdev *led_cdev,
+				   enum led_brightness brightness)
+{
+	led_cdev->brightness = brightness;
+	mod_delayed_work(system_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50));
+}
+
+static struct attribute *oxp_rgb_attrs[] = {
+	&dev_attr_effect.attr,
+	&dev_attr_effect_index.attr,
+	&dev_attr_enabled.attr,
+	&dev_attr_enabled_index.attr,
+	&dev_attr_speed.attr,
+	&dev_attr_speed_range.attr,
+	NULL,
+};
+
+static const struct attribute_group oxp_rgb_attr_group = {
+	.attrs = oxp_rgb_attrs,
+};
+
+static struct mc_subled oxp_rgb_subled_info[] = {
+	{
+		.color_index = LED_COLOR_ID_RED,
+		.intensity = 0x24,
+		.channel = 0x1,
+	},
+	{
+		.color_index = LED_COLOR_ID_GREEN,
+		.intensity = 0x22,
+		.channel = 0x2,
+	},
+	{
+		.color_index = LED_COLOR_ID_BLUE,
+		.intensity = 0x99,
+		.channel = 0x3,
+	},
+};
+
+static struct led_classdev_mc oxp_cdev_rgb = {
+	.led_cdev = {
+		.name = "oxp:rgb:joystick_rings",
+		.color = LED_COLOR_ID_RGB,
+		.brightness = 0x64,
+		.max_brightness = 0x64,
+		.brightness_set = oxp_rgb_brightness_set,
+	},
+	.num_colors = ARRAY_SIZE(oxp_rgb_subled_info),
+	.subled_info = oxp_rgb_subled_info,
+};
+
+static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
+{
+	int ret;
+
+	hid_set_drvdata(hdev, &drvdata);
+	mutex_init(&drvdata.cfg_mutex);
+	drvdata.hdev = hdev;
+	drvdata.led_mc = &oxp_cdev_rgb;
+
+	INIT_DELAYED_WORK(&drvdata.oxp_rgb_queue, oxp_rgb_queue_fn);
+	ret = devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb);
+	if (ret)
+		return dev_err_probe(&hdev->dev, ret,
+				     "Failed to create RGB device\n");
+
+	ret = devm_device_add_group(drvdata.led_mc->led_cdev.dev,
+				    &oxp_rgb_attr_group);
+	if (ret)
+		return dev_err_probe(drvdata.led_mc->led_cdev.dev, ret,
+				     "Failed to create RGB configuration attributes\n");
+
+	ret = oxp_rgb_status_show();
+	if (ret)
+		dev_warn(drvdata.led_mc->led_cdev.dev,
+			 "Failed to query RGB initial state: %i\n", ret);
+
+	return 0;
+}
+
+static int oxp_hid_probe(struct hid_device *hdev,
+			 const struct hid_device_id *id)
+{
+	int ret;
+	u16 up;
+
+	ret = hid_parse(hdev);
+	if (ret)
+		return dev_err_probe(&hdev->dev, ret, "Failed to parse HID device\n");
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret)
+		return dev_err_probe(&hdev->dev, ret, "Failed to start HID device\n");
+
+	ret = hid_hw_open(hdev);
+	if (ret) {
+		hid_hw_stop(hdev);
+		return dev_err_probe(&hdev->dev, ret, "Failed to open HID device\n");
+	}
+
+	up = get_usage_page(hdev);
+	dev_dbg(&hdev->dev, "Got usage page %04x\n", up);
+
+	switch (up) {
+	case GEN1_USAGE_PAGE:
+		ret = oxp_cfg_probe(hdev, up);
+		if (ret) {
+			hid_hw_close(hdev);
+			hid_hw_stop(hdev);
+		}
+
+		return ret;
+	default:
+		return 0;
+	}
+}
+
+static void oxp_hid_remove(struct hid_device *hdev)
+{
+	cancel_delayed_work(&drvdata.oxp_rgb_queue);
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id oxp_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) },
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, oxp_devices);
+static struct hid_driver hid_oxp = {
+	.name = "hid-oxp",
+	.id_table = oxp_devices,
+	.probe = oxp_hid_probe,
+	.remove = oxp_hid_remove,
+	.raw_event = oxp_hid_raw_event,
+};
+module_hid_driver(hid_oxp);
+
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Driver for OneXPlayer HID Interfaces");
+MODULE_LICENSE("GPL");
-- 
2.53.0


^ permalink raw reply related

* [PATCH v4 3/5] HID: hid-oxp: Add Second Generation Gamepad Mode Switch
From: Derek J. Clark @ 2026-04-19  4:26 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Zhouwang Huang,
	Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260419042624.625746-1-derekjohn.clark@gmail.com>

Adds "gamepad_mode" attribute to second generation OneXPlayer
configuration HID devices. This attribute initiates a mode shift in the
device MCU that puts it into a state where all events are routed to an
hidraw interface instead of the xpad evdev interface. This allows for
debugging the hardware input mapping added in the next patch.

Reviewed-by: Zhouwang Huang <honjow311@gmail.com>
Tested-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
  - Add oxp_mcu_init delayed work struct to drvdata & properly init,
    add cancel delayed work during remove.
v2:
  - Rename to gamepad_mode & show relevant gamepad modes instead of
    using a debug enable/disable paradigm, to match other drivers.
---
 drivers/hid/hid-oxp.c | 131 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 131 insertions(+)

diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index 835de2118e3c..2504b56b8f8a 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -33,20 +33,33 @@
 enum oxp_function_index {
 	OXP_FID_GEN1_RGB_SET =		0x07,
 	OXP_FID_GEN1_RGB_REPLY =	0x0f,
+	OXP_FID_GEN2_TOGGLE_MODE =	0xb2,
 	OXP_FID_GEN2_STATUS_EVENT =	0xb8,
 };
 
 static struct oxp_hid_cfg {
 	struct delayed_work oxp_rgb_queue;
+	struct delayed_work oxp_mcu_init;
 	struct led_classdev_mc *led_mc;
 	struct hid_device *hdev;
 	struct mutex cfg_mutex; /*ensure single synchronous output report*/
 	u8 rgb_brightness;
+	u8 gamepad_mode;
 	u8 rgb_effect;
 	u8 rgb_speed;
 	u8 rgb_en;
 } drvdata;
 
+enum oxp_gamepad_mode_index {
+	OXP_GP_MODE_XINPUT = 0x00,
+	OXP_GP_MODE_DEBUG = 0x03,
+};
+
+static const char *const oxp_gamepad_mode_text[] = {
+	[OXP_GP_MODE_XINPUT] = "xinput",
+	[OXP_GP_MODE_DEBUG] = "debug",
+};
+
 enum oxp_feature_en_index {
 	OXP_FEAT_DISABLED,
 	OXP_FEAT_ENABLED,
@@ -182,6 +195,30 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
 	return 0;
 }
 
+static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size);
+
+static void oxp_mcu_init_fn(struct work_struct *work)
+{
+	u8 gp_mode_data[3] = { OXP_GP_MODE_DEBUG, 0x01, 0x02 };
+	int ret;
+
+	/* Cycle the gamepad mode */
+	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3);
+	if (ret)
+		dev_err(&drvdata.hdev->dev,
+			"Error: Failed to set gamepad mode: %i\n", ret);
+
+	/* Remainder only applies for xinput mode */
+	if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG)
+		return;
+
+	gp_mode_data[0] = OXP_GP_MODE_XINPUT;
+	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3);
+	if (ret)
+		dev_err(&drvdata.hdev->dev,
+			"Error: Failed to set gamepad mode: %i\n", ret);
+}
+
 static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
 				   struct hid_report *report, u8 *data,
 				   int size)
@@ -192,6 +229,14 @@ static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
 	if (data[0] != OXP_FID_GEN2_STATUS_EVENT)
 		return 0;
 
+	/* Sent ~6s after resume event, indicating the MCU has fully reset.
+	 * Re-apply our settings after this has been received.
+	 */
+	if (data[3] == OXP_EFFECT_MONO_TRUE) {
+		mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
+		return 0;
+	}
+
 	if (data[3] != OXP_GET_PROPERTY)
 		return 0;
 
@@ -289,6 +334,77 @@ static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data,
 				footer_size);
 }
 
+static ssize_t gamepad_mode_store(struct device *dev,
+				  struct device_attribute *attr, const char *buf,
+				  size_t count)
+{
+	u16 up = get_usage_page(drvdata.hdev);
+	u8 data[3] = { 0x00, 0x01, 0x02 };
+	int ret = -EINVAL;
+	int i;
+
+	if (up != GEN2_USAGE_PAGE)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) {
+		if (oxp_gamepad_mode_text[i] && sysfs_streq(buf, oxp_gamepad_mode_text[i])) {
+			ret = i;
+			break;
+		}
+	}
+	if (ret < 0)
+		return ret;
+
+	data[0] = ret;
+
+	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3);
+	if (ret)
+		return ret;
+
+	drvdata.gamepad_mode = data[0];
+
+	return count;
+}
+
+static ssize_t gamepad_mode_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%s\n", oxp_gamepad_mode_text[drvdata.gamepad_mode]);
+}
+static DEVICE_ATTR_RW(gamepad_mode);
+
+static ssize_t gamepad_mode_index_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	ssize_t count = 0;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) {
+		if (!oxp_gamepad_mode_text[i] ||
+		    oxp_gamepad_mode_text[i][0] == '\0')
+			continue;
+
+		count += sysfs_emit_at(buf, count, "%s ", oxp_gamepad_mode_text[i]);
+	}
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+static DEVICE_ATTR_RO(gamepad_mode_index);
+
+static struct attribute *oxp_cfg_attrs[] = {
+	&dev_attr_gamepad_mode.attr,
+	&dev_attr_gamepad_mode_index.attr,
+	NULL,
+};
+
+static const struct attribute_group oxp_cfg_attrs_group = {
+	.attrs = oxp_cfg_attrs,
+};
+
 static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
 {
 	u16 up = get_usage_page(drvdata.hdev);
@@ -733,7 +849,21 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
 		dev_warn(drvdata.led_mc->led_cdev.dev,
 			 "Failed to query RGB initial state: %i\n", ret);
 
+	/* Below features are only implemented in gen 2 */
+	if (up != GEN2_USAGE_PAGE)
+		return 0;
+
 skip_rgb:
+	drvdata.gamepad_mode = OXP_GP_MODE_XINPUT;
+
+	INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn);
+	mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
+
+	ret = devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group);
+	if (ret)
+		return dev_err_probe(&hdev->dev, ret,
+				     "Failed to attach configuration attributes\n");
+
 	return 0;
 }
 
@@ -778,6 +908,7 @@ static int oxp_hid_probe(struct hid_device *hdev,
 static void oxp_hid_remove(struct hid_device *hdev)
 {
 	cancel_delayed_work(&drvdata.oxp_rgb_queue);
+	cancel_delayed_work(&drvdata.oxp_mcu_init);
 	hid_hw_close(hdev);
 	hid_hw_stop(hdev);
 }
-- 
2.53.0


^ permalink raw reply related

* [PATCH v4 4/5] HID: hid-oxp: Add Button Mapping Interface
From: Derek J. Clark @ 2026-04-19  4:26 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Zhouwang Huang,
	Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260419042624.625746-1-derekjohn.clark@gmail.com>

Adds button mapping interface for second generation OneXPlayer
configuration HID interfaces. This interface allows the MCU to swap
button mappings at the hardware level. The current state cannot be
retrieved, and the mappings may have been modified in Windows prior, so
we reset the button mapping at init and expose an attribute to allow
userspace to do this again at any time.

The interface requires two pages of button mapping data to be sent
before the settings will take place. Since the MCU requires a 200ms
delay after each message (total 400ms for these attributes) use the same
debounce work queue method we used for RGB. This will allow for
userspace or udev rules to rapidly map all buttons. The values will
be cached before the final write is finally sent to the device.

Reviewed-by: Zhouwang Huang <honjow311@gmail.com>
Tested-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
  - Make oxp_btn_queue delayed work struct part of drvdata, add cancel
    delayed work during remove.
v3:
  - Ensure default button map is properly init during probe.
v2:
  - Add detection of post-suspend MCU init to trigger setting the button
    map again.
---
 drivers/hid/hid-oxp.c | 568 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 568 insertions(+)

diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index 2504b56b8f8a..52002d4cbd0b 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -34,11 +34,147 @@ enum oxp_function_index {
 	OXP_FID_GEN1_RGB_SET =		0x07,
 	OXP_FID_GEN1_RGB_REPLY =	0x0f,
 	OXP_FID_GEN2_TOGGLE_MODE =	0xb2,
+	OXP_FID_GEN2_KEY_STATE =	0xb4,
 	OXP_FID_GEN2_STATUS_EVENT =	0xb8,
 };
 
+#define OXP_MAPPING_GAMEPAD	0x01
+#define OXP_MAPPING_KEYBOARD	0x02
+
+struct oxp_button_data {
+	u8 mode;
+	u8 index;
+	u8 key_id;
+	u8 padding[2];
+} __packed;
+
+struct oxp_button_entry {
+	struct oxp_button_data data;
+	const char *name;
+};
+
+static const struct oxp_button_entry oxp_button_table[] = {
+	/* Gamepad Buttons */
+	{ { OXP_MAPPING_GAMEPAD, 0x01 }, "BTN_A" },
+	{ { OXP_MAPPING_GAMEPAD, 0x02 }, "BTN_B" },
+	{ { OXP_MAPPING_GAMEPAD, 0x03 }, "BTN_X" },
+	{ { OXP_MAPPING_GAMEPAD, 0x04 }, "BTN_Y" },
+	{ { OXP_MAPPING_GAMEPAD, 0x05 }, "BTN_LB" },
+	{ { OXP_MAPPING_GAMEPAD, 0x06 }, "BTN_RB" },
+	{ { OXP_MAPPING_GAMEPAD, 0x07 }, "BTN_LT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x08 }, "BTN_RT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x09 }, "BTN_START" },
+	{ { OXP_MAPPING_GAMEPAD, 0x0a }, "BTN_SELECT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x0b }, "BTN_L3" },
+	{ { OXP_MAPPING_GAMEPAD, 0x0c }, "BTN_R3" },
+	{ { OXP_MAPPING_GAMEPAD, 0x0d }, "DPAD_UP" },
+	{ { OXP_MAPPING_GAMEPAD, 0x0e }, "DPAD_DOWN" },
+	{ { OXP_MAPPING_GAMEPAD, 0x0f }, "DPAD_LEFT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x10 }, "DPAD_RIGHT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x11 }, "JOY_L_UP" },
+	{ { OXP_MAPPING_GAMEPAD, 0x12 }, "JOY_L_UP_RIGHT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x13 }, "JOY_L_RIGHT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x14 }, "JOY_L_DOWN_RIGHT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x15 }, "JOY_L_DOWN" },
+	{ { OXP_MAPPING_GAMEPAD, 0x16 }, "JOY_L_DOWN_LEFT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x17 }, "JOY_L_LEFT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x18 }, "JOY_L_UP_LEFT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x19 }, "JOY_R_UP" },
+	{ { OXP_MAPPING_GAMEPAD, 0x1a }, "JOY_R_UP_RIGHT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x1b }, "JOY_R_RIGHT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x1c }, "JOY_R_DOWN_RIGHT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x1d }, "JOY_R_DOWN" },
+	{ { OXP_MAPPING_GAMEPAD, 0x1e }, "JOY_R_DOWN_LEFT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x1f }, "JOY_R_LEFT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x20 }, "JOY_R_UP_LEFT" },
+	{ { OXP_MAPPING_GAMEPAD, 0x22 }, "BTN_GUIDE" },
+	/* Keyboard Keys */
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x5a }, "KEY_F1" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x5b }, "KEY_F2" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x5c }, "KEY_F3" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x5d }, "KEY_F4" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x5e }, "KEY_F5" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x5f }, "KEY_F6" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x60 }, "KEY_F7" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x61 }, "KEY_F8" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x62 }, "KEY_F9" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x63 }, "KEY_F10" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x64 }, "KEY_F11" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x65 }, "KEY_F12" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x66 }, "KEY_F13" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x67 }, "KEY_F14" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x68 }, "KEY_F15" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x69 }, "KEY_F16" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x6a }, "KEY_F17" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x6b }, "KEY_F18" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x6c }, "KEY_F19" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x6d }, "KEY_F20" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x6e }, "KEY_F21" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x6f }, "KEY_F22" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x70 }, "KEY_F23" },
+	{ { OXP_MAPPING_KEYBOARD, 0x01, 0x71 }, "KEY_F24" },
+};
+
+enum oxp_joybutton_index {
+	BUTTON_A =	0x01,
+	BUTTON_B,
+	BUTTON_X,
+	BUTTON_Y,
+	BUTTON_LB,
+	BUTTON_RB,
+	BUTTON_LT,
+	BUTTON_RT,
+	BUTTON_START,
+	BUTTON_SELECT,
+	BUTTON_L3,
+	BUTTON_R3,
+	BUTTON_DUP,
+	BUTTON_DDOWN,
+	BUTTON_DLEFT,
+	BUTTON_DRIGHT,
+	BUTTON_M1 =	0x22,
+	BUTTON_M2,
+	/* These are unused currently, reserved for future devices */
+	BUTTON_M3,
+	BUTTON_M4,
+	BUTTON_M5,
+	BUTTON_M6,
+};
+
+struct oxp_button_idx {
+	enum oxp_joybutton_index button_idx;
+	u8 mapping_idx;
+} __packed;
+
+struct oxp_bmap_page_1 {
+	struct oxp_button_idx btn_a;
+	struct oxp_button_idx btn_b;
+	struct oxp_button_idx btn_x;
+	struct oxp_button_idx btn_y;
+	struct oxp_button_idx btn_lb;
+	struct oxp_button_idx btn_rb;
+	struct oxp_button_idx btn_lt;
+	struct oxp_button_idx btn_rt;
+	struct oxp_button_idx btn_start;
+} __packed;
+
+struct oxp_bmap_page_2 {
+	struct oxp_button_idx btn_select;
+	struct oxp_button_idx btn_l3;
+	struct oxp_button_idx btn_r3;
+	struct oxp_button_idx btn_dup;
+	struct oxp_button_idx btn_ddown;
+	struct oxp_button_idx btn_dleft;
+	struct oxp_button_idx btn_dright;
+	struct oxp_button_idx btn_m1;
+	struct oxp_button_idx btn_m2;
+} __packed;
+
 static struct oxp_hid_cfg {
 	struct delayed_work oxp_rgb_queue;
+	struct delayed_work oxp_btn_queue;
+	struct oxp_bmap_page_1 *bmap_1;
+	struct oxp_bmap_page_2 *bmap_2;
 	struct delayed_work oxp_mcu_init;
 	struct led_classdev_mc *led_mc;
 	struct hid_device *hdev;
@@ -50,6 +186,10 @@ static struct oxp_hid_cfg {
 	u8 rgb_en;
 } drvdata;
 
+#define OXP_FILL_PAGE_SLOT(page, btn)            \
+	{ .button_idx = (page)->btn.button_idx,  \
+	  .mapping_idx = (page)->btn.mapping_idx }
+
 enum oxp_gamepad_mode_index {
 	OXP_GP_MODE_XINPUT = 0x00,
 	OXP_GP_MODE_DEBUG = 0x03,
@@ -155,6 +295,10 @@ struct oxp_gen_2_rgb_report {
 	u8 effect;
 } __packed;
 
+struct oxp_attr {
+	u8 index;
+};
+
 static u16 get_usage_page(struct hid_device *hdev)
 {
 	return hdev->collection[0].usage >> 16;
@@ -196,12 +340,19 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
 }
 
 static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size);
+static int oxp_set_buttons(void);
 
 static void oxp_mcu_init_fn(struct work_struct *work)
 {
 	u8 gp_mode_data[3] = { OXP_GP_MODE_DEBUG, 0x01, 0x02 };
 	int ret;
 
+	/* Re-apply the button mapping */
+	ret = oxp_set_buttons();
+	if (ret)
+		dev_err(&drvdata.hdev->dev,
+			"Error: Failed to set button mapping: %i\n", ret);
+
 	/* Cycle the gamepad mode */
 	ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3);
 	if (ret)
@@ -395,9 +546,408 @@ static ssize_t gamepad_mode_index_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(gamepad_mode_index);
 
+static void oxp_set_defaults_bmap_1(struct oxp_bmap_page_1 *bmap)
+{
+	bmap->btn_a.button_idx = BUTTON_A;
+	bmap->btn_a.mapping_idx = 0;
+	bmap->btn_b.button_idx = BUTTON_B;
+	bmap->btn_b.mapping_idx = 1;
+	bmap->btn_x.button_idx = BUTTON_X;
+	bmap->btn_x.mapping_idx = 2;
+	bmap->btn_y.button_idx = BUTTON_Y;
+	bmap->btn_y.mapping_idx = 3;
+	bmap->btn_lb.button_idx = BUTTON_LB;
+	bmap->btn_lb.mapping_idx = 4;
+	bmap->btn_rb.button_idx = BUTTON_RB;
+	bmap->btn_rb.mapping_idx = 5;
+	bmap->btn_lt.button_idx = BUTTON_LT;
+	bmap->btn_lt.mapping_idx = 6;
+	bmap->btn_rt.button_idx = BUTTON_RT;
+	bmap->btn_rt.mapping_idx = 7;
+	bmap->btn_start.button_idx = BUTTON_START;
+	bmap->btn_start.mapping_idx = 8;
+}
+
+static void oxp_set_defaults_bmap_2(struct oxp_bmap_page_2 *bmap)
+{
+	bmap->btn_select.button_idx = BUTTON_SELECT;
+	bmap->btn_select.mapping_idx = 9;
+	bmap->btn_l3.button_idx = BUTTON_L3;
+	bmap->btn_l3.mapping_idx = 10;
+	bmap->btn_r3.button_idx = BUTTON_R3;
+	bmap->btn_r3.mapping_idx = 11;
+	bmap->btn_dup.button_idx = BUTTON_DUP;
+	bmap->btn_dup.mapping_idx = 12;
+	bmap->btn_ddown.button_idx = BUTTON_DDOWN;
+	bmap->btn_ddown.mapping_idx = 13;
+	bmap->btn_dleft.button_idx = BUTTON_DLEFT;
+	bmap->btn_dleft.mapping_idx = 14;
+	bmap->btn_dright.button_idx = BUTTON_DRIGHT;
+	bmap->btn_dright.mapping_idx = 15;
+	bmap->btn_m1.button_idx = BUTTON_M1;
+	bmap->btn_m1.mapping_idx = 48; /* KEY_F15 */
+	bmap->btn_m2.button_idx = BUTTON_M2;
+	bmap->btn_m2.mapping_idx = 49; /* KEY_F16 */
+}
+
+static void oxp_page_fill_data(char *buf, const struct oxp_button_idx *buttons,
+			       size_t len)
+{
+	size_t offset_increment = sizeof(u8) + sizeof(struct oxp_button_idx);
+	size_t offset = 5;
+	unsigned int i;
+
+	for (i = 0; i < len; i++, offset += offset_increment) {
+		buf[offset] = (u8)buttons[i].button_idx;
+		memcpy(buf + offset + 1,
+		       &oxp_button_table[buttons[i].mapping_idx].data,
+		       sizeof(struct oxp_button_data));
+	}
+}
+
+static int oxp_set_buttons(void)
+{
+	u8 page_1[59] = { 0x02, 0x38, 0x20, 0x01, 0x01 };
+	u8 page_2[59] = { 0x02, 0x38, 0x20, 0x02, 0x01 };
+	u16 up = get_usage_page(drvdata.hdev);
+	int ret;
+
+	if (up != GEN2_USAGE_PAGE)
+		return -EINVAL;
+
+	const struct oxp_button_idx p1[] = {
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_a),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_b),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_x),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_y),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_lb),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_rb),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_lt),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_rt),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_start),
+	};
+
+	const struct oxp_button_idx p2[] = {
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_select),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_l3),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_r3),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dup),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_ddown),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dleft),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dright),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_m1),
+		OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_m2),
+	};
+
+	oxp_page_fill_data(page_1, p1, ARRAY_SIZE(p1));
+	oxp_page_fill_data(page_2, p2, ARRAY_SIZE(p2));
+
+	ret = oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, page_1, ARRAY_SIZE(page_1));
+	if (ret)
+		return ret;
+
+	return oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, page_2, ARRAY_SIZE(page_2));
+}
+
+static void oxp_reset_buttons(void)
+{
+	oxp_set_defaults_bmap_1(drvdata.bmap_1);
+	oxp_set_defaults_bmap_2(drvdata.bmap_2);
+}
+
+static ssize_t reset_buttons_store(struct device *dev,
+				   struct device_attribute *attr, const char *buf,
+				   size_t count)
+{
+	int val, ret;
+
+	ret = kstrtoint(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val != 1)
+		return -EINVAL;
+
+	oxp_reset_buttons();
+	ret = oxp_set_buttons();
+	if (ret)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_WO(reset_buttons);
+
+static void oxp_btn_queue_fn(struct work_struct *work)
+{
+	int ret;
+
+	ret = oxp_set_buttons();
+	if (ret)
+		dev_err(&drvdata.hdev->dev,
+			"Error: Failed to write button mapping: %i\n", ret);
+}
+
+static int oxp_button_idx_from_str(const char *buf)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(oxp_button_table); i++)
+		if (sysfs_streq(buf, oxp_button_table[i].name))
+			return i;
+
+	return -EINVAL;
+}
+
+static ssize_t map_button_store(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t count, u8 index)
+{
+	int idx;
+
+	idx = oxp_button_idx_from_str(buf);
+	if (idx < 0)
+		return idx;
+
+	switch (index) {
+	case BUTTON_A:
+		drvdata.bmap_1->btn_a.mapping_idx = idx;
+		break;
+	case BUTTON_B:
+		drvdata.bmap_1->btn_b.mapping_idx = idx;
+		break;
+	case BUTTON_X:
+		drvdata.bmap_1->btn_x.mapping_idx = idx;
+		break;
+	case BUTTON_Y:
+		drvdata.bmap_1->btn_y.mapping_idx = idx;
+		break;
+	case BUTTON_LB:
+		drvdata.bmap_1->btn_lb.mapping_idx = idx;
+		break;
+	case BUTTON_RB:
+		drvdata.bmap_1->btn_rb.mapping_idx = idx;
+		break;
+	case BUTTON_LT:
+		drvdata.bmap_1->btn_lt.mapping_idx = idx;
+		break;
+	case BUTTON_RT:
+		drvdata.bmap_1->btn_rt.mapping_idx = idx;
+		break;
+	case BUTTON_START:
+		drvdata.bmap_1->btn_start.mapping_idx = idx;
+		break;
+	case BUTTON_SELECT:
+		drvdata.bmap_2->btn_select.mapping_idx = idx;
+		break;
+	case BUTTON_L3:
+		drvdata.bmap_2->btn_l3.mapping_idx = idx;
+		break;
+	case BUTTON_R3:
+		drvdata.bmap_2->btn_r3.mapping_idx = idx;
+		break;
+	case BUTTON_DUP:
+		drvdata.bmap_2->btn_dup.mapping_idx = idx;
+		break;
+	case BUTTON_DDOWN:
+		drvdata.bmap_2->btn_ddown.mapping_idx = idx;
+		break;
+	case BUTTON_DLEFT:
+		drvdata.bmap_2->btn_dleft.mapping_idx = idx;
+		break;
+	case BUTTON_DRIGHT:
+		drvdata.bmap_2->btn_dright.mapping_idx = idx;
+		break;
+	case BUTTON_M1:
+		drvdata.bmap_2->btn_m1.mapping_idx = idx;
+		break;
+	case BUTTON_M2:
+		drvdata.bmap_2->btn_m2.mapping_idx = idx;
+		break;
+	default:
+		return -EINVAL;
+	}
+	mod_delayed_work(system_wq, &drvdata.oxp_btn_queue, msecs_to_jiffies(50));
+	return count;
+}
+
+static ssize_t map_button_show(struct device *dev,
+			       struct device_attribute *attr, char *buf,
+			       u8 index)
+{
+	u8 i;
+
+	switch (index) {
+	case BUTTON_A:
+		i = drvdata.bmap_1->btn_a.mapping_idx;
+		break;
+	case BUTTON_B:
+		i = drvdata.bmap_1->btn_b.mapping_idx;
+		break;
+	case BUTTON_X:
+		i = drvdata.bmap_1->btn_x.mapping_idx;
+		break;
+	case BUTTON_Y:
+		i = drvdata.bmap_1->btn_y.mapping_idx;
+		break;
+	case BUTTON_LB:
+		i = drvdata.bmap_1->btn_lb.mapping_idx;
+		break;
+	case BUTTON_RB:
+		i = drvdata.bmap_1->btn_rb.mapping_idx;
+		break;
+	case BUTTON_LT:
+		i = drvdata.bmap_1->btn_lt.mapping_idx;
+		break;
+	case BUTTON_RT:
+		i = drvdata.bmap_1->btn_rt.mapping_idx;
+		break;
+	case BUTTON_START:
+		i = drvdata.bmap_1->btn_start.mapping_idx;
+		break;
+	case BUTTON_SELECT:
+		i = drvdata.bmap_2->btn_select.mapping_idx;
+		break;
+	case BUTTON_L3:
+		i = drvdata.bmap_2->btn_l3.mapping_idx;
+		break;
+	case BUTTON_R3:
+		i = drvdata.bmap_2->btn_r3.mapping_idx;
+		break;
+	case BUTTON_DUP:
+		i = drvdata.bmap_2->btn_dup.mapping_idx;
+		break;
+	case BUTTON_DDOWN:
+		i = drvdata.bmap_2->btn_ddown.mapping_idx;
+		break;
+	case BUTTON_DLEFT:
+		i = drvdata.bmap_2->btn_dleft.mapping_idx;
+		break;
+	case BUTTON_DRIGHT:
+		i = drvdata.bmap_2->btn_dright.mapping_idx;
+		break;
+	case BUTTON_M1:
+		i = drvdata.bmap_2->btn_m1.mapping_idx;
+		break;
+	case BUTTON_M2:
+		i = drvdata.bmap_2->btn_m2.mapping_idx;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (i >= ARRAY_SIZE(oxp_button_table))
+		return -EINVAL;
+
+	return sysfs_emit(buf, "%s\n", oxp_button_table[i].name);
+}
+
+static ssize_t button_mapping_options_show(struct device *dev,
+					   struct device_attribute *attr, char *buf)
+{
+	ssize_t count = 0;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(oxp_button_table); i++)
+		count += sysfs_emit_at(buf, count, "%s ", oxp_button_table[i].name);
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+static DEVICE_ATTR_RO(button_mapping_options);
+
+#define OXP_DEVICE_ATTR_RW(_name, _group)                                     \
+	static ssize_t _name##_store(struct device *dev,                      \
+				     struct device_attribute *attr,           \
+				     const char *buf, size_t count)           \
+	{                                                                     \
+		return _group##_store(dev, attr, buf, count, _name.index);    \
+	}                                                                     \
+	static ssize_t _name##_show(struct device *dev,                       \
+				    struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return _group##_show(dev, attr, buf, _name.index);            \
+	}                                                                     \
+	static DEVICE_ATTR_RW(_name)
+
+static struct oxp_attr button_a = { BUTTON_A };
+OXP_DEVICE_ATTR_RW(button_a, map_button);
+
+static struct oxp_attr button_b = { BUTTON_B };
+OXP_DEVICE_ATTR_RW(button_b, map_button);
+
+static struct oxp_attr button_x = { BUTTON_X };
+OXP_DEVICE_ATTR_RW(button_x, map_button);
+
+static struct oxp_attr button_y = { BUTTON_Y };
+OXP_DEVICE_ATTR_RW(button_y, map_button);
+
+static struct oxp_attr button_lb = { BUTTON_LB };
+OXP_DEVICE_ATTR_RW(button_lb, map_button);
+
+static struct oxp_attr button_rb = { BUTTON_RB };
+OXP_DEVICE_ATTR_RW(button_rb, map_button);
+
+static struct oxp_attr button_lt = { BUTTON_LT };
+OXP_DEVICE_ATTR_RW(button_lt, map_button);
+
+static struct oxp_attr button_rt = { BUTTON_RT };
+OXP_DEVICE_ATTR_RW(button_rt, map_button);
+
+static struct oxp_attr button_start = { BUTTON_START };
+OXP_DEVICE_ATTR_RW(button_start, map_button);
+
+static struct oxp_attr button_select = { BUTTON_SELECT };
+OXP_DEVICE_ATTR_RW(button_select, map_button);
+
+static struct oxp_attr button_l3 = { BUTTON_L3 };
+OXP_DEVICE_ATTR_RW(button_l3, map_button);
+
+static struct oxp_attr button_r3 = { BUTTON_R3 };
+OXP_DEVICE_ATTR_RW(button_r3, map_button);
+
+static struct oxp_attr button_d_up = { BUTTON_DUP };
+OXP_DEVICE_ATTR_RW(button_d_up, map_button);
+
+static struct oxp_attr button_d_down = { BUTTON_DDOWN };
+OXP_DEVICE_ATTR_RW(button_d_down, map_button);
+
+static struct oxp_attr button_d_left = { BUTTON_DLEFT };
+OXP_DEVICE_ATTR_RW(button_d_left, map_button);
+
+static struct oxp_attr button_d_right = { BUTTON_DRIGHT };
+OXP_DEVICE_ATTR_RW(button_d_right, map_button);
+
+static struct oxp_attr button_m1 = { BUTTON_M1 };
+OXP_DEVICE_ATTR_RW(button_m1, map_button);
+
+static struct oxp_attr button_m2 = { BUTTON_M2 };
+OXP_DEVICE_ATTR_RW(button_m2, map_button);
+
 static struct attribute *oxp_cfg_attrs[] = {
+	&dev_attr_button_a.attr,
+	&dev_attr_button_b.attr,
+	&dev_attr_button_d_down.attr,
+	&dev_attr_button_d_left.attr,
+	&dev_attr_button_d_right.attr,
+	&dev_attr_button_d_up.attr,
+	&dev_attr_button_l3.attr,
+	&dev_attr_button_lb.attr,
+	&dev_attr_button_lt.attr,
+	&dev_attr_button_m1.attr,
+	&dev_attr_button_m2.attr,
+	&dev_attr_button_mapping_options.attr,
+	&dev_attr_button_r3.attr,
+	&dev_attr_button_rb.attr,
+	&dev_attr_button_rt.attr,
+	&dev_attr_button_select.attr,
+	&dev_attr_button_start.attr,
+	&dev_attr_button_x.attr,
+	&dev_attr_button_y.attr,
 	&dev_attr_gamepad_mode.attr,
 	&dev_attr_gamepad_mode_index.attr,
+	&dev_attr_reset_buttons.attr,
 	NULL,
 };
 
@@ -821,6 +1371,8 @@ static bool oxp_hybrid_mcu_device(void)
 
 static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
 {
+	struct oxp_bmap_page_1 *bmap_1;
+	struct oxp_bmap_page_2 *bmap_2;
 	int ret;
 
 	hid_set_drvdata(hdev, &drvdata);
@@ -854,6 +1406,21 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
 		return 0;
 
 skip_rgb:
+	bmap_1 = devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_1), GFP_KERNEL);
+	if (!bmap_1)
+		return dev_err_probe(&hdev->dev, -ENOMEM,
+				     "Unable to allocate button map page 1\n");
+
+	bmap_2 = devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_2), GFP_KERNEL);
+	if (!bmap_2)
+		return dev_err_probe(&hdev->dev, -ENOMEM,
+				     "Unable to allocate button map page 2\n");
+
+	drvdata.bmap_1 = bmap_1;
+	drvdata.bmap_2 = bmap_2;
+	oxp_reset_buttons();
+	INIT_DELAYED_WORK(&drvdata.oxp_btn_queue, oxp_btn_queue_fn);
+
 	drvdata.gamepad_mode = OXP_GP_MODE_XINPUT;
 
 	INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn);
@@ -908,6 +1475,7 @@ static int oxp_hid_probe(struct hid_device *hdev,
 static void oxp_hid_remove(struct hid_device *hdev)
 {
 	cancel_delayed_work(&drvdata.oxp_rgb_queue);
+	cancel_delayed_work(&drvdata.oxp_btn_queue);
 	cancel_delayed_work(&drvdata.oxp_mcu_init);
 	hid_hw_close(hdev);
 	hid_hw_stop(hdev);
-- 
2.53.0


^ permalink raw reply related

* [PATCH v4 5/5] HID: hid-oxp: Add Vibration Intensity Attribute
From: Derek J. Clark @ 2026-04-19  4:26 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires
  Cc: Pierre-Loup A . Griffais, Lambert Fan, Zhouwang Huang,
	Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260419042624.625746-1-derekjohn.clark@gmail.com>

Adds attribute for setting the rumble intensity level. This setting must
be re-applied after the gamepad mode is set as doing so resets this to
the default value.

Reviewed-by: Zhouwang Huang <honjow311@gmail.com>
Tested-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
 drivers/hid/hid-oxp.c | 78 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 78 insertions(+)

diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c
index 52002d4cbd0b..20a54f337220 100644
--- a/drivers/hid/hid-oxp.c
+++ b/drivers/hid/hid-oxp.c
@@ -34,6 +34,7 @@ enum oxp_function_index {
 	OXP_FID_GEN1_RGB_SET =		0x07,
 	OXP_FID_GEN1_RGB_REPLY =	0x0f,
 	OXP_FID_GEN2_TOGGLE_MODE =	0xb2,
+	OXP_FID_GEN2_RUMBLE_SET =	0xb3,
 	OXP_FID_GEN2_KEY_STATE =	0xb4,
 	OXP_FID_GEN2_STATUS_EVENT =	0xb8,
 };
@@ -181,6 +182,7 @@ static struct oxp_hid_cfg {
 	struct mutex cfg_mutex; /*ensure single synchronous output report*/
 	u8 rgb_brightness;
 	u8 gamepad_mode;
+	u8 rumble_intensity;
 	u8 rgb_effect;
 	u8 rgb_speed;
 	u8 rgb_en;
@@ -266,6 +268,11 @@ static const char *const oxp_rgb_effect_text[] = {
 	[OXP_EFFECT_MONO_LIST] = "monocolor",
 };
 
+enum oxp_rumble_side_index {
+	OXP_RUMBLE_LEFT = 0x00,
+	OXP_RUMBLE_RIGHT,
+};
+
 struct oxp_gen_1_rgb_report {
 	u8 report_id;
 	u8 message_id;
@@ -341,6 +348,7 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *hdev,
 
 static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size);
 static int oxp_set_buttons(void);
+static int oxp_rumble_intensity_set(u8 intensity);
 
 static void oxp_mcu_init_fn(struct work_struct *work)
 {
@@ -368,6 +376,12 @@ static void oxp_mcu_init_fn(struct work_struct *work)
 	if (ret)
 		dev_err(&drvdata.hdev->dev,
 			"Error: Failed to set gamepad mode: %i\n", ret);
+
+	/* Set vibration level */
+	ret = oxp_rumble_intensity_set(drvdata.rumble_intensity);
+	if (ret)
+		dev_err(&drvdata.hdev->dev,
+			"Error: Failed to set rumble intensity: %i\n", ret);
 }
 
 static int oxp_hid_raw_event_gen_2(struct hid_device *hdev,
@@ -514,6 +528,14 @@ static ssize_t gamepad_mode_store(struct device *dev,
 
 	drvdata.gamepad_mode = data[0];
 
+	if (drvdata.gamepad_mode == OXP_GP_MODE_DEBUG)
+		return count;
+
+	/* Re-apply rumble settings as switching gamepad mode will override */
+	ret = oxp_rumble_intensity_set(drvdata.rumble_intensity);
+	if (ret)
+		return ret;
+
 	return count;
 }
 
@@ -857,6 +879,59 @@ static ssize_t button_mapping_options_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(button_mapping_options);
 
+static int oxp_rumble_intensity_set(u8 intensity)
+{
+	u8 header[15] = { 0x02, 0x38, 0x02, 0xe3, 0x39, 0xe3, 0x39, 0xe3,
+			  0x39, 0x01, intensity, 0x05, 0xe3, 0x39, 0xe3 };
+	u8 footer[9] = { 0x39, 0xe3, 0x39, 0xe3, 0xe3, 0x02, 0x04, 0x39, 0x39 };
+	size_t footer_size = ARRAY_SIZE(footer);
+	size_t header_size = ARRAY_SIZE(header);
+	u8 data[59] = { 0x0 };
+	size_t data_size = ARRAY_SIZE(data);
+
+	memcpy(data, header, header_size);
+	memcpy(data + data_size - footer_size, footer, footer_size);
+
+	return oxp_gen_2_property_out(OXP_FID_GEN2_RUMBLE_SET, data, data_size);
+}
+
+static ssize_t rumble_intensity_store(struct device *dev,
+				      struct device_attribute *attr, const char *buf,
+				      size_t count)
+{
+	int ret;
+	u8 val;
+
+	ret = kstrtou8(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val < 0 || val > 5)
+		return -EINVAL;
+
+	ret = oxp_rumble_intensity_set(val);
+	if (ret)
+		return ret;
+
+	drvdata.rumble_intensity = val;
+
+	return count;
+}
+
+static ssize_t rumble_intensity_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%i\n", drvdata.rumble_intensity);
+}
+static DEVICE_ATTR_RW(rumble_intensity);
+
+static ssize_t rumble_intensity_range_show(struct device *dev,
+					   struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "0-5\n");
+}
+static DEVICE_ATTR_RO(rumble_intensity_range);
+
 #define OXP_DEVICE_ATTR_RW(_name, _group)                                     \
 	static ssize_t _name##_store(struct device *dev,                      \
 				     struct device_attribute *attr,           \
@@ -948,6 +1023,8 @@ static struct attribute *oxp_cfg_attrs[] = {
 	&dev_attr_gamepad_mode.attr,
 	&dev_attr_gamepad_mode_index.attr,
 	&dev_attr_reset_buttons.attr,
+	&dev_attr_rumble_intensity.attr,
+	&dev_attr_rumble_intensity_range.attr,
 	NULL,
 };
 
@@ -1422,6 +1499,7 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 up)
 	INIT_DELAYED_WORK(&drvdata.oxp_btn_queue, oxp_btn_queue_fn);
 
 	drvdata.gamepad_mode = OXP_GP_MODE_XINPUT;
+	drvdata.rumble_intensity = 5;
 
 	INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn);
 	mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
-- 
2.53.0


^ permalink raw reply related

* [PATCH] Documentation: adopt new coding style of type-aware kmalloc-family
From: Manuel Ebner @ 2026-04-19  6:58 UTC (permalink / raw)
  To: Jonathan Corbet, Shuah Khan, linux-doc
  Cc: lrcu, linux-kernel, workflows, linux-sound, rcu, linux-media,
	Manuel Ebner

Update the documentation to reflect new type-aware kmalloc-family as
suggested in commit 2932ba8d9c99 ("slab: Introduce kmalloc_obj() and family")

ptr = kmalloc(sizeof(*ptr), gfp);
 -> ptr = kmalloc_obj(*ptr, gfp);
ptr = kmalloc(sizeof(struct some_obj_name), gfp);
 -> ptr = kmalloc_obj(*ptr, gfp);
ptr = kzalloc(sizeof(*ptr), gfp);
 -> ptr = kzalloc_obj(*ptr, gfp);
ptr = kmalloc_array(count, sizeof(*ptr), gfp);
 -> ptr = kmalloc_objs(*ptr, count, gfp);
ptr = kcalloc(count, sizeof(*ptr), gfp);
 -> ptr = kzalloc_objs(*ptr, count, gfp);

Signed-off-by: Manuel Ebner <manuelebner@mailbox.org>
---
 .../RCU/Design/Requirements/Requirements.rst         |  6 +++---
 Documentation/RCU/listRCU.rst                        |  2 +-
 Documentation/RCU/whatisRCU.rst                      |  4 ++--
 Documentation/core-api/kref.rst                      |  4 ++--
 Documentation/core-api/list.rst                      |  4 ++--
 Documentation/core-api/memory-allocation.rst         |  4 ++--
 Documentation/driver-api/mailbox.rst                 |  4 ++--
 Documentation/driver-api/media/v4l2-fh.rst           |  2 +-
 Documentation/kernel-hacking/locking.rst             |  4 ++--
 Documentation/locking/locktypes.rst                  |  4 ++--
 Documentation/process/coding-style.rst               |  8 ++++----
 .../sound/kernel-api/writing-an-alsa-driver.rst      | 12 ++++++------
 Documentation/spi/spi-summary.rst                    |  4 ++--
 .../translations/it_IT/kernel-hacking/locking.rst    |  4 ++--
 .../translations/it_IT/locking/locktypes.rst         |  4 ++--
 .../translations/it_IT/process/coding-style.rst      |  2 +-
 .../translations/sp_SP/process/coding-style.rst      |  2 +-
 Documentation/translations/zh_CN/core-api/kref.rst   |  4 ++--
 .../translations/zh_CN/process/coding-style.rst      |  2 +-
 .../zh_CN/video4linux/v4l2-framework.txt             |  2 +-
 .../translations/zh_TW/process/coding-style.rst      |  2 +-
 21 files changed, 42 insertions(+), 42 deletions(-)

diff --git a/Documentation/RCU/Design/Requirements/Requirements.rst b/Documentation/RCU/Design/Requirements/Requirements.rst
index b5cdbba3ec2e..faca5a9c8c12 100644
--- a/Documentation/RCU/Design/Requirements/Requirements.rst
+++ b/Documentation/RCU/Design/Requirements/Requirements.rst
@@ -206,7 +206,7 @@ non-\ ``NULL``, locklessly accessing the ``->a`` and ``->b`` fields.
 
        1 bool add_gp_buggy(int a, int b)
        2 {
-       3   p = kmalloc(sizeof(*p), GFP_KERNEL);
+       3   p = kmalloc_obj(*p, GFP_KERNEL);
        4   if (!p)
        5     return -ENOMEM;
        6   spin_lock(&gp_lock);
@@ -228,7 +228,7 @@ their rights to reorder this code as follows:
 
        1 bool add_gp_buggy_optimized(int a, int b)
        2 {
-       3   p = kmalloc(sizeof(*p), GFP_KERNEL);
+       3   p = kmalloc_obj(*p, GFP_KERNEL);
        4   if (!p)
        5     return -ENOMEM;
        6   spin_lock(&gp_lock);
@@ -264,7 +264,7 @@ shows an example of insertion:
 
        1 bool add_gp(int a, int b)
        2 {
-       3   p = kmalloc(sizeof(*p), GFP_KERNEL);
+       3   p = kmalloc_obj(*p, GFP_KERNEL);
        4   if (!p)
        5     return -ENOMEM;
        6   spin_lock(&gp_lock);
diff --git a/Documentation/RCU/listRCU.rst b/Documentation/RCU/listRCU.rst
index d8bb98623c12..48c7272a4ccc 100644
--- a/Documentation/RCU/listRCU.rst
+++ b/Documentation/RCU/listRCU.rst
@@ -276,7 +276,7 @@ The RCU version of audit_upd_rule() is as follows::
 
 		list_for_each_entry(e, list, list) {
 			if (!audit_compare_rule(rule, &e->rule)) {
-				ne = kmalloc(sizeof(*entry), GFP_ATOMIC);
+				ne = kmalloc_obj(*entry, GFP_ATOMIC);
 				if (ne == NULL)
 					return -ENOMEM;
 				audit_copy_rule(&ne->rule, &e->rule);
diff --git a/Documentation/RCU/whatisRCU.rst b/Documentation/RCU/whatisRCU.rst
index a1582bd653d1..770aab8ea36a 100644
--- a/Documentation/RCU/whatisRCU.rst
+++ b/Documentation/RCU/whatisRCU.rst
@@ -468,7 +468,7 @@ uses of RCU may be found in listRCU.rst and NMI-RCU.rst.
 		struct foo *new_fp;
 		struct foo *old_fp;
 
-		new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL);
+		new_fp = kmalloc_obj(*new_fp, GFP_KERNEL);
 		spin_lock(&foo_mutex);
 		old_fp = rcu_dereference_protected(gbl_foo, lockdep_is_held(&foo_mutex));
 		*new_fp = *old_fp;
@@ -570,7 +570,7 @@ The foo_update_a() function might then be written as follows::
 		struct foo *new_fp;
 		struct foo *old_fp;
 
-		new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL);
+		new_fp = kmalloc_obj(*new_fp, GFP_KERNEL);
 		spin_lock(&foo_mutex);
 		old_fp = rcu_dereference_protected(gbl_foo, lockdep_is_held(&foo_mutex));
 		*new_fp = *old_fp;
diff --git a/Documentation/core-api/kref.rst b/Documentation/core-api/kref.rst
index 8db9ff03d952..1c14c036699d 100644
--- a/Documentation/core-api/kref.rst
+++ b/Documentation/core-api/kref.rst
@@ -40,7 +40,7 @@ kref_init as so::
 
      struct my_data *data;
 
-     data = kmalloc(sizeof(*data), GFP_KERNEL);
+     data = kmalloc_obj(*data, GFP_KERNEL);
      if (!data)
             return -ENOMEM;
      kref_init(&data->refcount);
@@ -100,7 +100,7 @@ thread to process::
 	int rv = 0;
 	struct my_data *data;
 	struct task_struct *task;
-	data = kmalloc(sizeof(*data), GFP_KERNEL);
+	data = kmalloc_obj(*data, GFP_KERNEL);
 	if (!data)
 		return -ENOMEM;
 	kref_init(&data->refcount);
diff --git a/Documentation/core-api/list.rst b/Documentation/core-api/list.rst
index 241464ca0549..86cd0a1b77ea 100644
--- a/Documentation/core-api/list.rst
+++ b/Documentation/core-api/list.rst
@@ -112,7 +112,7 @@ list:
 
           /* State 1 */
 
-          grock = kzalloc(sizeof(*grock), GFP_KERNEL);
+          grock = kzalloc_obj(*grock, GFP_KERNEL);
           if (!grock)
                   return -ENOMEM;
           grock->name = "Grock";
@@ -123,7 +123,7 @@ list:
 
           /* State 2 */
 
-          dimitri = kzalloc(sizeof(*dimitri), GFP_KERNEL);
+          dimitri = kzalloc_obj(*dimitri, GFP_KERNEL);
           if (!dimitri)
                   return -ENOMEM;
           dimitri->name = "Dimitri";
diff --git a/Documentation/core-api/memory-allocation.rst b/Documentation/core-api/memory-allocation.rst
index 0f19dd524323..8379775f17d3 100644
--- a/Documentation/core-api/memory-allocation.rst
+++ b/Documentation/core-api/memory-allocation.rst
@@ -135,7 +135,7 @@ Selecting memory allocator
 The most straightforward way to allocate memory is to use a function
 from the kmalloc() family. And, to be on the safe side it's best to use
 routines that set memory to zero, like kzalloc(). If you need to
-allocate memory for an array, there are kmalloc_array() and kcalloc()
+allocate memory for an array, there are kmalloc_objs() and kzalloc_objs()
 helpers. The helpers struct_size(), array_size() and array3_size() can
 be used to safely calculate object sizes without overflowing.
 
@@ -151,7 +151,7 @@ sizes, the alignment is guaranteed to be at least the largest power-of-two
 divisor of the size.
 
 Chunks allocated with kmalloc() can be resized with krealloc(). Similarly
-to kmalloc_array(): a helper for resizing arrays is provided in the form of
+to kmalloc_objs(): a helper for resizing arrays is provided in the form of
 krealloc_array().
 
 For large allocations you can use vmalloc() and vzalloc(), or directly
diff --git a/Documentation/driver-api/mailbox.rst b/Documentation/driver-api/mailbox.rst
index 463dd032b96c..4bcd73a99115 100644
--- a/Documentation/driver-api/mailbox.rst
+++ b/Documentation/driver-api/mailbox.rst
@@ -87,8 +87,8 @@ a message and a callback function to the API and return immediately).
 		struct async_pkt ap;
 		struct sync_pkt sp;
 
-		dc_sync = kzalloc(sizeof(*dc_sync), GFP_KERNEL);
-		dc_async = kzalloc(sizeof(*dc_async), GFP_KERNEL);
+		dc_sync = kzalloc_obj(*dc_sync, GFP_KERNEL);
+		dc_async = kzalloc_obj(*dc_async, GFP_KERNEL);
 
 		/* Populate non-blocking mode client */
 		dc_async->cl.dev = &pdev->dev;
diff --git a/Documentation/driver-api/media/v4l2-fh.rst b/Documentation/driver-api/media/v4l2-fh.rst
index a934caa483a4..38319130ebf5 100644
--- a/Documentation/driver-api/media/v4l2-fh.rst
+++ b/Documentation/driver-api/media/v4l2-fh.rst
@@ -42,7 +42,7 @@ Example:
 
 		...
 
-		my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL);
+		my_fh = kzalloc_obj(*my_fh, GFP_KERNEL);
 
 		...
 
diff --git a/Documentation/kernel-hacking/locking.rst b/Documentation/kernel-hacking/locking.rst
index dff0646a717b..d02e62367c4f 100644
--- a/Documentation/kernel-hacking/locking.rst
+++ b/Documentation/kernel-hacking/locking.rst
@@ -442,7 +442,7 @@ to protect the cache and all the objects within it. Here's the code::
     {
             struct object *obj;
 
-            if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
+            if ((obj = kmalloc_obj(*obj, GFP_KERNEL)) == NULL)
                     return -ENOMEM;
 
             strscpy(obj->name, name, sizeof(obj->name));
@@ -517,7 +517,7 @@ which are taken away, and the ``+`` are lines which are added.
              struct object *obj;
     +        unsigned long flags;
 
-             if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
+             if ((obj = kmalloc_obj(*obj, GFP_KERNEL)) == NULL)
                      return -ENOMEM;
     @@ -63,30 +64,33 @@
              obj->id = id;
diff --git a/Documentation/locking/locktypes.rst b/Documentation/locking/locktypes.rst
index 37b6a5670c2f..ac1ad722a9e7 100644
--- a/Documentation/locking/locktypes.rst
+++ b/Documentation/locking/locktypes.rst
@@ -498,7 +498,7 @@ allocating memory.  Thus, on a non-PREEMPT_RT kernel the following code
 works perfectly::
 
   raw_spin_lock(&lock);
-  p = kmalloc(sizeof(*p), GFP_ATOMIC);
+  p = kmalloc_obj(*p, GFP_ATOMIC);
 
 But this code fails on PREEMPT_RT kernels because the memory allocator is
 fully preemptible and therefore cannot be invoked from truly atomic
@@ -507,7 +507,7 @@ while holding normal non-raw spinlocks because they do not disable
 preemption on PREEMPT_RT kernels::
 
   spin_lock(&lock);
-  p = kmalloc(sizeof(*p), GFP_ATOMIC);
+  p = kmalloc_obj(*p, GFP_ATOMIC);
 
 
 bit spinlocks
diff --git a/Documentation/process/coding-style.rst b/Documentation/process/coding-style.rst
index 35b381230f6e..a3bf75dc7c88 100644
--- a/Documentation/process/coding-style.rst
+++ b/Documentation/process/coding-style.rst
@@ -936,7 +936,7 @@ used.
 ---------------------
 
 The kernel provides the following general purpose memory allocators:
-kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc(), and
+kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc(), and
 vzalloc().  Please refer to the API documentation for further information
 about them.  :ref:`Documentation/core-api/memory-allocation.rst
 <memory_allocation>`
@@ -945,7 +945,7 @@ The preferred form for passing a size of a struct is the following:
 
 .. code-block:: c
 
-	p = kmalloc(sizeof(*p), ...);
+	p = kmalloc_obj(*p, ...);
 
 The alternative form where struct name is spelled out hurts readability and
 introduces an opportunity for a bug when the pointer variable type is changed
@@ -959,13 +959,13 @@ The preferred form for allocating an array is the following:
 
 .. code-block:: c
 
-	p = kmalloc_array(n, sizeof(...), ...);
+	p = kmalloc_objs(*ptr, n, ...);
 
 The preferred form for allocating a zeroed array is the following:
 
 .. code-block:: c
 
-	p = kcalloc(n, sizeof(...), ...);
+	p = kzalloc_objs(*ptr, n, ...);
 
 Both forms check for overflow on the allocation size n * sizeof(...),
 and return NULL if that occurred.
diff --git a/Documentation/sound/kernel-api/writing-an-alsa-driver.rst b/Documentation/sound/kernel-api/writing-an-alsa-driver.rst
index 895752cbcedd..12433612aa9c 100644
--- a/Documentation/sound/kernel-api/writing-an-alsa-driver.rst
+++ b/Documentation/sound/kernel-api/writing-an-alsa-driver.rst
@@ -266,7 +266,7 @@ to details explained in the following section.
               ....
 
               /* allocate a chip-specific data with zero filled */
-              chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+              chip = kzalloc_obj(*chip, GFP_KERNEL);
               if (chip == NULL)
                       return -ENOMEM;
 
@@ -628,7 +628,7 @@ After allocating a card instance via :c:func:`snd_card_new()`
   err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
                      0, &card);
   .....
-  chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+  chip = kzalloc_obj(*chip, GFP_KERNEL);
 
 The chip record should have the field to hold the card pointer at least,
 
@@ -747,7 +747,7 @@ destructor and PCI entries. Example code is shown first, below::
                       return -ENXIO;
               }
 
-              chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+              chip = kzalloc_obj(*chip, GFP_KERNEL);
               if (chip == NULL) {
                       pci_disable_device(pci);
                       return -ENOMEM;
@@ -1737,7 +1737,7 @@ callback::
   {
           struct my_pcm_data *data;
           ....
-          data = kmalloc(sizeof(*data), GFP_KERNEL);
+          data = kmalloc_obj(*data, GFP_KERNEL);
           substream->runtime->private_data = data;
           ....
   }
@@ -3301,7 +3301,7 @@ You can then pass any pointer value to the ``private_data``. If you
 assign private data, you should define a destructor, too. The
 destructor function is set in the ``private_free`` field::
 
-  struct mydata *p = kmalloc(sizeof(*p), GFP_KERNEL);
+  struct mydata *p = kmalloc_obj(*p, GFP_KERNEL);
   hw->private_data = p;
   hw->private_free = mydata_free;
 
@@ -3833,7 +3833,7 @@ chip data individually::
           err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
                              0, &card);
           ....
-          chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+          chip = kzalloc_obj(*chip, GFP_KERNEL);
           ....
           card->private_data = chip;
           ....
diff --git a/Documentation/spi/spi-summary.rst b/Documentation/spi/spi-summary.rst
index 6e21e6f86912..7ad6af76c247 100644
--- a/Documentation/spi/spi-summary.rst
+++ b/Documentation/spi/spi-summary.rst
@@ -249,7 +249,7 @@ And SOC-specific utility code might look something like::
 	{
 		struct mysoc_spi_data *pdata2;
 
-		pdata2 = kmalloc(sizeof *pdata2, GFP_KERNEL);
+		pdata2 = kmalloc_obj(*pdata2, GFP_KERNEL);
 		*pdata2 = pdata;
 		...
 		if (n == 2) {
@@ -373,7 +373,7 @@ a bus (appearing under /sys/class/spi_master).
 			return -ENODEV;
 
 		/* get memory for driver's per-chip state */
-		chip = kzalloc(sizeof *chip, GFP_KERNEL);
+		chip = kzalloc(*chip, GFP_KERNEL);
 		if (!chip)
 			return -ENOMEM;
 		spi_set_drvdata(spi, chip);
diff --git a/Documentation/translations/it_IT/kernel-hacking/locking.rst b/Documentation/translations/it_IT/kernel-hacking/locking.rst
index 4c21cf60f775..acca89a3743a 100644
--- a/Documentation/translations/it_IT/kernel-hacking/locking.rst
+++ b/Documentation/translations/it_IT/kernel-hacking/locking.rst
@@ -462,7 +462,7 @@ e tutti gli oggetti che contiene. Ecco il codice::
     {
             struct object *obj;
 
-            if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
+            if ((obj = kmalloc_obj(*obj, GFP_KERNEL)) == NULL)
                     return -ENOMEM;
 
             strscpy(obj->name, name, sizeof(obj->name));
@@ -537,7 +537,7 @@ sono quelle rimosse, mentre quelle ``+`` sono quelle aggiunte.
              struct object *obj;
     +        unsigned long flags;
 
-             if ((obj = kmalloc(sizeof(*obj), GFP_KERNEL)) == NULL)
+             if ((obj = kmalloc_obj(*obj, GFP_KERNEL)) == NULL)
                      return -ENOMEM;
     @@ -63,30 +64,33 @@
              obj->id = id;
diff --git a/Documentation/translations/it_IT/locking/locktypes.rst b/Documentation/translations/it_IT/locking/locktypes.rst
index 1c7056283b9d..d5fa36aa05cc 100644
--- a/Documentation/translations/it_IT/locking/locktypes.rst
+++ b/Documentation/translations/it_IT/locking/locktypes.rst
@@ -488,7 +488,7 @@ o rwlock_t. Per esempio, la sezione critica non deve fare allocazioni di
 memoria. Su un kernel non-PREEMPT_RT il seguente codice funziona perfettamente::
 
   raw_spin_lock(&lock);
-  p = kmalloc(sizeof(*p), GFP_ATOMIC);
+  p = kmalloc_obj(*p, GFP_ATOMIC);
 
 Ma lo stesso codice non funziona su un kernel PREEMPT_RT perché l'allocatore di
 memoria può essere oggetto di prelazione e quindi non può essere chiamato in un
@@ -497,7 +497,7 @@ trattiene un blocco *non-raw* perché non disabilitano la prelazione sui kernel
 PREEMPT_RT::
 
   spin_lock(&lock);
-  p = kmalloc(sizeof(*p), GFP_ATOMIC);
+  p = kmalloc_obj(*p, GFP_ATOMIC);
 
 
 bit spinlocks
diff --git a/Documentation/translations/it_IT/process/coding-style.rst b/Documentation/translations/it_IT/process/coding-style.rst
index c0dc786b8474..2a499412a2e3 100644
--- a/Documentation/translations/it_IT/process/coding-style.rst
+++ b/Documentation/translations/it_IT/process/coding-style.rst
@@ -943,7 +943,7 @@ Il modo preferito per passare la dimensione di una struttura è il seguente:
 
 .. code-block:: c
 
-	p = kmalloc(sizeof(*p), ...);
+	p = kmalloc_obj(*p, ...);
 
 La forma alternativa, dove il nome della struttura viene scritto interamente,
 peggiora la leggibilità e introduce possibili bachi quando il tipo di
diff --git a/Documentation/translations/sp_SP/process/coding-style.rst b/Documentation/translations/sp_SP/process/coding-style.rst
index 7d63aa8426e6..44c93d5f6beb 100644
--- a/Documentation/translations/sp_SP/process/coding-style.rst
+++ b/Documentation/translations/sp_SP/process/coding-style.rst
@@ -955,7 +955,7 @@ La forma preferida para pasar el tamaño de una estructura es la siguiente:
 
 .. code-block:: c
 
-	p = kmalloc(sizeof(*p), ...);
+	p = kmalloc_obj(*p, ...);
 
 La forma alternativa donde se deletrea el nombre de la estructura perjudica
 la legibilidad, y presenta una oportunidad para un error cuando se cambia
diff --git a/Documentation/translations/zh_CN/core-api/kref.rst b/Documentation/translations/zh_CN/core-api/kref.rst
index b9902af310c5..fcff01e99852 100644
--- a/Documentation/translations/zh_CN/core-api/kref.rst
+++ b/Documentation/translations/zh_CN/core-api/kref.rst
@@ -52,7 +52,7 @@ kref可以出现在数据结构体中的任何地方。
 
      struct my_data *data;
 
-     data = kmalloc(sizeof(*data), GFP_KERNEL);
+     data = kmalloc_obj(*data, GFP_KERNEL);
      if (!data)
             return -ENOMEM;
      kref_init(&data->refcount);
@@ -106,7 +106,7 @@ Kref规则
 	int rv = 0;
 	struct my_data *data;
 	struct task_struct *task;
-	data = kmalloc(sizeof(*data), GFP_KERNEL);
+	data = kmalloc_obj(*data, GFP_KERNEL);
 	if (!data)
 		return -ENOMEM;
 	kref_init(&data->refcount);
diff --git a/Documentation/translations/zh_CN/process/coding-style.rst b/Documentation/translations/zh_CN/process/coding-style.rst
index 5a342a024c01..55d5da974d89 100644
--- a/Documentation/translations/zh_CN/process/coding-style.rst
+++ b/Documentation/translations/zh_CN/process/coding-style.rst
@@ -813,7 +813,7 @@ Documentation/translations/zh_CN/core-api/memory-allocation.rst 。
 
 .. code-block:: c
 
-	p = kmalloc(sizeof(*p), ...);
+	p = kmalloc_obj(*p, ...);
 
 另外一种传递方式中,sizeof 的操作数是结构体的名字,这样会降低可读性,并且可能
 会引入 bug。有可能指针变量类型被改变时,而对应的传递给内存分配函数的 sizeof
diff --git a/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt b/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt
index f0be21a60a0f..ba43c5c4797c 100644
--- a/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt
+++ b/Documentation/translations/zh_CN/video4linux/v4l2-framework.txt
@@ -799,7 +799,7 @@ int my_open(struct file *file)
 
 	...
 
-	my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL);
+	my_fh = kzalloc_obj(*my_fh, GFP_KERNEL);
 
 	...
 
diff --git a/Documentation/translations/zh_TW/process/coding-style.rst b/Documentation/translations/zh_TW/process/coding-style.rst
index e2ba97b3d8bb..63c78982a1af 100644
--- a/Documentation/translations/zh_TW/process/coding-style.rst
+++ b/Documentation/translations/zh_TW/process/coding-style.rst
@@ -827,7 +827,7 @@ Documentation/translations/zh_CN/core-api/memory-allocation.rst 。
 
 .. code-block:: c
 
-	p = kmalloc(sizeof(*p), ...);
+	p = kmalloc_obj(*p, ...);
 
 另外一種傳遞方式中,sizeof 的操作數是結構體的名字,這樣會降低可讀性,並且可能
 會引入 bug。有可能指針變量類型被改變時,而對應的傳遞給內存分配函數的 sizeof
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH v4 2/8] dt-bindings: arm: Add zx297520v3 board binding
From: Stefan Dösinger @ 2026-04-19  8:30 UTC (permalink / raw)
  To: Rob Herring (Arm)
  Cc: linux-kernel, Conor Dooley, Jonathan Corbet, Alexandre Belloni,
	Greg Kroah-Hartman, linux-doc, devicetree, Drew Fustini,
	Linus Walleij, Jiri Slaby, Russell King, soc, Arnd Bergmann,
	Krzysztof Kozlowski, Krzysztof Kozlowski, linux-arm-kernel,
	linux-serial, Shuah Khan
In-Reply-To: <177646012448.2165534.5760108355183774935.robh@kernel.org>

[-- Attachment #1: Type: text/plain, Size: 710 bytes --]

Hi Rob,

Am Samstag, 18. April 2026, 00:08:44 Ostafrikanische Zeit schrieben Sie:

> If you already ran 'make dt_binding_check' and didn't see the above
> error(s), then make sure 'yamllint' is installed and dt-schema is up to
> date:

Here is a new PEBKAC issue for your mail template: I ran dt_binding_check, it 
wrote the warning you pointed out, but I only checked the return value - which 
indicated success. Which I guess makes sense for a warning, since there seem 
to be a few preexisting ones. The warning itself was somewhere in the 
scrollback because I let dt_binding_check check all the files.

So I learned I have to actually look at the output to see if there are any 
warnings.

Cheers,
Stefan

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 870 bytes --]

^ permalink raw reply

* [PATCH v2 0/3] Update APDS990x ALS to support device trees
From: Svyatoslav Ryhel @ 2026-04-19  8:31 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, Arnd Bergmann, Greg Kroah-Hartman, Svyatoslav Ryhel,
	Randy Dunlap
  Cc: linux-iio, devicetree, linux-kernel, linux-doc

Document Avago APDS9900/9901 ALS/Proximity sensor in schema and add its support
to tsl2772 driver.

---
Changes in v2:
- dropped all previous patches
- apds990x was documented in tsl2772.yaml
- apds990x support was added to tsl2772.c
- original apds990x driver removed from misc
---

Svyatoslav Ryhel (3):
  dt-bindings: iio: light: Document Avago APDS9900/9901 ALS/Proximity
    sensor
  iio: tsl2772: add support for Avago APDS9900/9901 ALS/Proximity sensor
  misc: Remove old APDS990x driver

 .../bindings/iio/light/tsl2772.yaml           |    2 +
 Documentation/misc-devices/apds990x.rst       |  128 --
 drivers/iio/light/tsl2772.c                   |   16 +
 drivers/misc/Kconfig                          |   10 -
 drivers/misc/Makefile                         |    1 -
 drivers/misc/apds990x.c                       | 1284 -----------------
 include/linux/platform_data/apds990x.h        |   65 -
 7 files changed, 18 insertions(+), 1488 deletions(-)
 delete mode 100644 Documentation/misc-devices/apds990x.rst
 delete mode 100644 drivers/misc/apds990x.c
 delete mode 100644 include/linux/platform_data/apds990x.h

-- 
2.51.0


^ permalink raw reply

* [PATCH v2 1/3] dt-bindings: iio: light: Document Avago APDS9900/9901 ALS/Proximity sensor
From: Svyatoslav Ryhel @ 2026-04-19  8:31 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, Arnd Bergmann, Greg Kroah-Hartman, Svyatoslav Ryhel,
	Randy Dunlap
  Cc: linux-iio, devicetree, linux-kernel, linux-doc
In-Reply-To: <20260419083125.35572-1-clamor95@gmail.com>

Document Avago APDS-9900/9901 combined ALS/IR-LED/Proximity sensor.

Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
 Documentation/devicetree/bindings/iio/light/tsl2772.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/light/tsl2772.yaml b/Documentation/devicetree/bindings/iio/light/tsl2772.yaml
index d81229857944..9921ccaa64a0 100644
--- a/Documentation/devicetree/bindings/iio/light/tsl2772.yaml
+++ b/Documentation/devicetree/bindings/iio/light/tsl2772.yaml
@@ -26,6 +26,8 @@ properties:
       - amstaos,tmd2672
       - amstaos,tsl2772
       - amstaos,tmd2772
+      - avago,apds9900
+      - avago,apds9901
       - avago,apds9930
 
   reg:
-- 
2.51.0


^ permalink raw reply related

* [PATCH v2 2/3] iio: tsl2772: add support for Avago APDS9900/9901 ALS/Proximity sensor
From: Svyatoslav Ryhel @ 2026-04-19  8:31 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, Arnd Bergmann, Greg Kroah-Hartman, Svyatoslav Ryhel,
	Randy Dunlap
  Cc: linux-iio, devicetree, linux-kernel, linux-doc
In-Reply-To: <20260419083125.35572-1-clamor95@gmail.com>

The Avago APDS990x has the same register set as the TAOS/AMS TSL2772 so
just add the correct bindings and the appropriate LUX table derived from
the values in the datasheet. Driver was tested on the LG Optimus Vu P895.

Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
 drivers/iio/light/tsl2772.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/drivers/iio/light/tsl2772.c b/drivers/iio/light/tsl2772.c
index c8f15ba95267..8dab34bf00ca 100644
--- a/drivers/iio/light/tsl2772.c
+++ b/drivers/iio/light/tsl2772.c
@@ -127,6 +127,7 @@ enum {
 	tmd2672,
 	tsl2772,
 	tmd2772,
+	apds990x,
 	apds9930,
 };
 
@@ -221,6 +222,12 @@ static const struct tsl2772_lux tmd2x72_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = {
 	{     0,      0 },
 };
 
+static const struct tsl2772_lux apds990x_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = {
+	{ 52000,  115960 },
+	{ 36400,   73840 },
+	{     0,       0 },
+};
+
 static const struct tsl2772_lux apds9930_lux_table[TSL2772_DEF_LUX_TABLE_SZ] = {
 	{ 52000,  96824 },
 	{ 38792,  67132 },
@@ -238,6 +245,7 @@ static const struct tsl2772_lux *tsl2772_default_lux_table_group[] = {
 	[tmd2672] = tmd2x72_lux_table,
 	[tsl2772] = tsl2x72_lux_table,
 	[tmd2772] = tmd2x72_lux_table,
+	[apds990x] = apds990x_lux_table,
 	[apds9930] = apds9930_lux_table,
 };
 
@@ -289,6 +297,7 @@ static const int tsl2772_int_time_avail[][6] = {
 	[tmd2672] = { 0, 2730, 0, 2730, 0, 699000 },
 	[tsl2772] = { 0, 2730, 0, 2730, 0, 699000 },
 	[tmd2772] = { 0, 2730, 0, 2730, 0, 699000 },
+	[apds990x] = { 0, 2720, 0, 2720, 0, 696000 },
 	[apds9930] = { 0, 2730, 0, 2730, 0, 699000 },
 };
 
@@ -316,6 +325,7 @@ static const u8 device_channel_config[] = {
 	[tmd2672] = PRX2,
 	[tsl2772] = ALSPRX2,
 	[tmd2772] = ALSPRX2,
+	[apds990x] = ALSPRX,
 	[apds9930] = ALSPRX2,
 };
 
@@ -530,6 +540,7 @@ static int tsl2772_get_prox(struct iio_dev *indio_dev)
 	case tmd2672:
 	case tsl2772:
 	case tmd2772:
+	case apds990x:
 	case apds9930:
 		if (!(ret & TSL2772_STA_PRX_VALID)) {
 			ret = -EINVAL;
@@ -1367,6 +1378,7 @@ static int tsl2772_device_id_verif(int id, int target)
 		return (id & 0xf0) == TRITON_ID;
 	case tmd2671:
 	case tmd2771:
+	case apds990x:
 		return (id & 0xf0) == HALIBUT_ID;
 	case tsl2572:
 	case tsl2672:
@@ -1898,6 +1910,8 @@ static const struct i2c_device_id tsl2772_idtable[] = {
 	{ "tmd2672", tmd2672 },
 	{ "tsl2772", tsl2772 },
 	{ "tmd2772", tmd2772 },
+	{ "apds9900", apds990x },
+	{ "apds9901", apds990x },
 	{ "apds9930", apds9930 },
 	{ }
 };
@@ -1915,6 +1929,8 @@ static const struct of_device_id tsl2772_of_match[] = {
 	{ .compatible = "amstaos,tmd2672" },
 	{ .compatible = "amstaos,tsl2772" },
 	{ .compatible = "amstaos,tmd2772" },
+	{ .compatible = "avago,apds9900" },
+	{ .compatible = "avago,apds9901" },
 	{ .compatible = "avago,apds9930" },
 	{ }
 };
-- 
2.51.0


^ permalink raw reply related

* [PATCH v2 3/3] misc: Remove old APDS990x driver
From: Svyatoslav Ryhel @ 2026-04-19  8:31 UTC (permalink / raw)
  To: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, Arnd Bergmann, Greg Kroah-Hartman, Svyatoslav Ryhel,
	Randy Dunlap
  Cc: linux-iio, devicetree, linux-kernel, linux-doc
In-Reply-To: <20260419083125.35572-1-clamor95@gmail.com>

The Avago APDS9900/9901 ALS/Proximity sensor is now supported by tsl2772
IIO driver so there is no need to keep this old implementation. Remove it.

Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
 Documentation/misc-devices/apds990x.rst |  128 ---
 drivers/misc/Kconfig                    |   10 -
 drivers/misc/Makefile                   |    1 -
 drivers/misc/apds990x.c                 | 1284 -----------------------
 include/linux/platform_data/apds990x.h  |   65 --
 5 files changed, 1488 deletions(-)
 delete mode 100644 Documentation/misc-devices/apds990x.rst
 delete mode 100644 drivers/misc/apds990x.c
 delete mode 100644 include/linux/platform_data/apds990x.h

diff --git a/Documentation/misc-devices/apds990x.rst b/Documentation/misc-devices/apds990x.rst
deleted file mode 100644
index e2f75577f731..000000000000
--- a/Documentation/misc-devices/apds990x.rst
+++ /dev/null
@@ -1,128 +0,0 @@
-.. SPDX-License-Identifier: GPL-2.0
-
-======================
-Kernel driver apds990x
-======================
-
-Supported chips:
-Avago APDS990X
-
-Data sheet:
-Not freely available
-
-Author:
-Samu Onkalo <samu.p.onkalo@nokia.com>
-
-Description
------------
-
-APDS990x is a combined ambient light and proximity sensor. ALS and proximity
-functionality are highly connected. ALS measurement path must be running
-while the proximity functionality is enabled.
-
-ALS produces raw measurement values for two channels: Clear channel
-(infrared + visible light) and IR only. However, threshold comparisons happen
-using clear channel only. Lux value and the threshold level on the HW
-might vary quite much depending the spectrum of the light source.
-
-Driver makes necessary conversions to both directions so that user handles
-only lux values. Lux value is calculated using information from the both
-channels. HW threshold level is calculated from the given lux value to match
-with current type of the lightning. Sometimes inaccuracy of the estimations
-lead to false interrupt, but that doesn't harm.
-
-ALS contains 4 different gain steps. Driver automatically
-selects suitable gain step. After each measurement, reliability of the results
-is estimated and new measurement is triggered if necessary.
-
-Platform data can provide tuned values to the conversion formulas if
-values are known. Otherwise plain sensor default values are used.
-
-Proximity side is little bit simpler. There is no need for complex conversions.
-It produces directly usable values.
-
-Driver controls chip operational state using pm_runtime framework.
-Voltage regulators are controlled based on chip operational state.
-
-SYSFS
------
-
-
-chip_id
-	RO - shows detected chip type and version
-
-power_state
-	RW - enable / disable chip. Uses counting logic
-
-	     1 enables the chip
-	     0 disables the chip
-lux0_input
-	RO - measured lux value
-
-	     sysfs_notify called when threshold interrupt occurs
-
-lux0_sensor_range
-	RO - lux0_input max value.
-
-	     Actually never reaches since sensor tends
-	     to saturate much before that. Real max value varies depending
-	     on the light spectrum etc.
-
-lux0_rate
-	RW - measurement rate in Hz
-
-lux0_rate_avail
-	RO - supported measurement rates
-
-lux0_calibscale
-	RW - calibration value.
-
-	     Set to neutral value by default.
-	     Output results are multiplied with calibscale / calibscale_default
-	     value.
-
-lux0_calibscale_default
-	RO - neutral calibration value
-
-lux0_thresh_above_value
-	RW - HI level threshold value.
-
-	     All results above the value
-	     trigs an interrupt. 65535 (i.e. sensor_range) disables the above
-	     interrupt.
-
-lux0_thresh_below_value
-	RW - LO level threshold value.
-
-	     All results below the value
-	     trigs an interrupt. 0 disables the below interrupt.
-
-prox0_raw
-	RO - measured proximity value
-
-	     sysfs_notify called when threshold interrupt occurs
-
-prox0_sensor_range
-	RO - prox0_raw max value (1023)
-
-prox0_raw_en
-	RW - enable / disable proximity - uses counting logic
-
-	     - 1 enables the proximity
-	     - 0 disables the proximity
-
-prox0_reporting_mode
-	RW - trigger / periodic.
-
-	     In "trigger" mode the driver tells two possible
-	     values: 0 or prox0_sensor_range value. 0 means no proximity,
-	     1023 means proximity. This causes minimal number of interrupts.
-	     In "periodic" mode the driver reports all values above
-	     prox0_thresh_above. This causes more interrupts, but it can give
-	     _rough_ estimate about the distance.
-
-prox0_reporting_mode_avail
-	RO - accepted values to prox0_reporting_mode (trigger, periodic)
-
-prox0_thresh_above_value
-	RW - threshold level which trigs proximity events.
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 8cbd71a0dc35..051cf2c44b90 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -392,16 +392,6 @@ config SENSORS_BH1770
 	   To compile this driver as a module, choose M here: the
 	   module will be called bh1770glc. If unsure, say N here.
 
-config SENSORS_APDS990X
-	 tristate "APDS990X combined als and proximity sensors"
-	 depends on I2C
-	help
-	   Say Y here if you want to build a driver for Avago APDS990x
-	   combined ambient light and proximity sensor chip.
-
-	   To compile this driver as a module, choose M here: the
-	   module will be called apds990x. If unsure, say N here.
-
 config HMC6352
 	tristate "Honeywell HMC6352 compass"
 	depends on I2C
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 62c3d03206e9..bfad6982591c 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -20,7 +20,6 @@ obj-$(CONFIG_RPMB)		+= rpmb-core.o
 obj-$(CONFIG_QCOM_COINCELL)	+= qcom-coincell.o
 obj-$(CONFIG_QCOM_FASTRPC)	+= fastrpc.o
 obj-$(CONFIG_SENSORS_BH1770)	+= bh1770glc.o
-obj-$(CONFIG_SENSORS_APDS990X)	+= apds990x.o
 obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o
 obj-$(CONFIG_KGDB_TESTS)	+= kgdbts.o
 obj-$(CONFIG_SGI_XP)		+= sgi-xp/
diff --git a/drivers/misc/apds990x.c b/drivers/misc/apds990x.c
deleted file mode 100644
index b69c3a1c94d1..000000000000
--- a/drivers/misc/apds990x.c
+++ /dev/null
@@ -1,1284 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * This file is part of the APDS990x sensor driver.
- * Chip is combined proximity and ambient light sensor.
- *
- * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
- *
- * Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
- */
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/i2c.h>
-#include <linux/interrupt.h>
-#include <linux/mutex.h>
-#include <linux/regulator/consumer.h>
-#include <linux/pm_runtime.h>
-#include <linux/delay.h>
-#include <linux/wait.h>
-#include <linux/slab.h>
-#include <linux/platform_data/apds990x.h>
-
-/* Register map */
-#define APDS990X_ENABLE	 0x00 /* Enable of states and interrupts */
-#define APDS990X_ATIME	 0x01 /* ALS ADC time  */
-#define APDS990X_PTIME	 0x02 /* Proximity ADC time  */
-#define APDS990X_WTIME	 0x03 /* Wait time  */
-#define APDS990X_AILTL	 0x04 /* ALS interrupt low threshold low byte */
-#define APDS990X_AILTH	 0x05 /* ALS interrupt low threshold hi byte */
-#define APDS990X_AIHTL	 0x06 /* ALS interrupt hi threshold low byte */
-#define APDS990X_AIHTH	 0x07 /* ALS interrupt hi threshold hi byte */
-#define APDS990X_PILTL	 0x08 /* Proximity interrupt low threshold low byte */
-#define APDS990X_PILTH	 0x09 /* Proximity interrupt low threshold hi byte */
-#define APDS990X_PIHTL	 0x0a /* Proximity interrupt hi threshold low byte */
-#define APDS990X_PIHTH	 0x0b /* Proximity interrupt hi threshold hi byte */
-#define APDS990X_PERS	 0x0c /* Interrupt persistence filters */
-#define APDS990X_CONFIG	 0x0d /* Configuration */
-#define APDS990X_PPCOUNT 0x0e /* Proximity pulse count */
-#define APDS990X_CONTROL 0x0f /* Gain control register */
-#define APDS990X_REV	 0x11 /* Revision Number */
-#define APDS990X_ID	 0x12 /* Device ID */
-#define APDS990X_STATUS	 0x13 /* Device status */
-#define APDS990X_CDATAL	 0x14 /* Clear ADC low data register */
-#define APDS990X_CDATAH	 0x15 /* Clear ADC high data register */
-#define APDS990X_IRDATAL 0x16 /* IR ADC low data register */
-#define APDS990X_IRDATAH 0x17 /* IR ADC high data register */
-#define APDS990X_PDATAL	 0x18 /* Proximity ADC low data register */
-#define APDS990X_PDATAH	 0x19 /* Proximity ADC high data register */
-
-/* Control */
-#define APDS990X_MAX_AGAIN	3
-
-/* Enable register */
-#define APDS990X_EN_PIEN	(0x1 << 5)
-#define APDS990X_EN_AIEN	(0x1 << 4)
-#define APDS990X_EN_WEN		(0x1 << 3)
-#define APDS990X_EN_PEN		(0x1 << 2)
-#define APDS990X_EN_AEN		(0x1 << 1)
-#define APDS990X_EN_PON		(0x1 << 0)
-#define APDS990X_EN_DISABLE_ALL 0
-
-/* Status register */
-#define APDS990X_ST_PINT	(0x1 << 5)
-#define APDS990X_ST_AINT	(0x1 << 4)
-
-/* I2C access types */
-#define APDS990x_CMD_TYPE_MASK	(0x03 << 5)
-#define APDS990x_CMD_TYPE_RB	(0x00 << 5) /* Repeated byte */
-#define APDS990x_CMD_TYPE_INC	(0x01 << 5) /* Auto increment */
-#define APDS990x_CMD_TYPE_SPE	(0x03 << 5) /* Special function */
-
-#define APDS990x_ADDR_SHIFT	0
-#define APDS990x_CMD		0x80
-
-/* Interrupt ack commands */
-#define APDS990X_INT_ACK_ALS	0x6
-#define APDS990X_INT_ACK_PS	0x5
-#define APDS990X_INT_ACK_BOTH	0x7
-
-/* ptime */
-#define APDS990X_PTIME_DEFAULT	0xff /* Recommended conversion time 2.7ms*/
-
-/* wtime */
-#define APDS990X_WTIME_DEFAULT	0xee /* ~50ms wait time */
-
-#define APDS990X_TIME_TO_ADC	1024 /* One timetick as ADC count value */
-
-/* Persistence */
-#define APDS990X_APERS_SHIFT	0
-#define APDS990X_PPERS_SHIFT	4
-
-/* Supported ID:s */
-#define APDS990X_ID_0		0x0
-#define APDS990X_ID_4		0x4
-#define APDS990X_ID_29		0x29
-
-/* pgain and pdiode settings */
-#define APDS_PGAIN_1X	       0x0
-#define APDS_PDIODE_IR	       0x2
-
-#define APDS990X_LUX_OUTPUT_SCALE 10
-
-/* Reverse chip factors for threshold calculation */
-struct reverse_factors {
-	u32 afactor;
-	int cf1;
-	int irf1;
-	int cf2;
-	int irf2;
-};
-
-struct apds990x_chip {
-	struct apds990x_platform_data	*pdata;
-	struct i2c_client		*client;
-	struct mutex			mutex; /* avoid parallel access */
-	struct regulator_bulk_data	regs[2];
-	wait_queue_head_t		wait;
-
-	int	prox_en;
-	bool	prox_continuous_mode;
-	bool	lux_wait_fresh_res;
-
-	/* Chip parameters */
-	struct	apds990x_chip_factors	cf;
-	struct	reverse_factors		rcf;
-	u16	atime;		/* als integration time */
-	u16	arate;		/* als reporting rate */
-	u16	a_max_result;	/* Max possible ADC value with current atime */
-	u8	again_meas;	/* Gain used in last measurement */
-	u8	again_next;	/* Next calculated gain */
-	u8	pgain;
-	u8	pdiode;
-	u8	pdrive;
-	u8	lux_persistence;
-	u8	prox_persistence;
-
-	u32	lux_raw;
-	u32	lux;
-	u16	lux_clear;
-	u16	lux_ir;
-	u16	lux_calib;
-	u32	lux_thres_hi;
-	u32	lux_thres_lo;
-
-	u32	prox_thres;
-	u16	prox_data;
-	u16	prox_calib;
-
-	char	chipname[10];
-	u8	revision;
-};
-
-#define APDS_CALIB_SCALER		8192
-#define APDS_LUX_NEUTRAL_CALIB_VALUE	(1 * APDS_CALIB_SCALER)
-#define APDS_PROX_NEUTRAL_CALIB_VALUE	(1 * APDS_CALIB_SCALER)
-
-#define APDS_PROX_DEF_THRES		600
-#define APDS_PROX_HYSTERESIS		50
-#define APDS_LUX_DEF_THRES_HI		101
-#define APDS_LUX_DEF_THRES_LO		100
-#define APDS_DEFAULT_PROX_PERS		1
-
-#define APDS_TIMEOUT			2000
-#define APDS_STARTUP_DELAY		25000 /* us */
-#define APDS_RANGE			65535
-#define APDS_PROX_RANGE			1023
-#define APDS_LUX_GAIN_LO_LIMIT		100
-#define APDS_LUX_GAIN_LO_LIMIT_STRICT	25
-
-#define TIMESTEP			87 /* 2.7ms is about 87 / 32 */
-#define TIME_STEP_SCALER		32
-
-#define APDS_LUX_AVERAGING_TIME		50 /* tolerates 50/60Hz ripple */
-#define APDS_LUX_DEFAULT_RATE		200
-
-static const u8 again[]	= {1, 8, 16, 120}; /* ALS gain steps */
-
-/* Following two tables must match i.e 10Hz rate means 1 as persistence value */
-static const u16 arates_hz[] = {10, 5, 2, 1};
-static const u8 apersis[] = {1, 2, 4, 5};
-
-/* Regulators */
-static const char reg_vcc[] = "Vdd";
-static const char reg_vled[] = "Vled";
-
-static int apds990x_read_byte(struct apds990x_chip *chip, u8 reg, u8 *data)
-{
-	struct i2c_client *client = chip->client;
-	s32 ret;
-
-	reg &= ~APDS990x_CMD_TYPE_MASK;
-	reg |= APDS990x_CMD | APDS990x_CMD_TYPE_RB;
-
-	ret = i2c_smbus_read_byte_data(client, reg);
-	*data = ret;
-	return (int)ret;
-}
-
-static int apds990x_read_word(struct apds990x_chip *chip, u8 reg, u16 *data)
-{
-	struct i2c_client *client = chip->client;
-	s32 ret;
-
-	reg &= ~APDS990x_CMD_TYPE_MASK;
-	reg |= APDS990x_CMD | APDS990x_CMD_TYPE_INC;
-
-	ret = i2c_smbus_read_word_data(client, reg);
-	*data = ret;
-	return (int)ret;
-}
-
-static int apds990x_write_byte(struct apds990x_chip *chip, u8 reg, u8 data)
-{
-	struct i2c_client *client = chip->client;
-	s32 ret;
-
-	reg &= ~APDS990x_CMD_TYPE_MASK;
-	reg |= APDS990x_CMD | APDS990x_CMD_TYPE_RB;
-
-	ret = i2c_smbus_write_byte_data(client, reg, data);
-	return (int)ret;
-}
-
-static int apds990x_write_word(struct apds990x_chip *chip, u8 reg, u16 data)
-{
-	struct i2c_client *client = chip->client;
-	s32 ret;
-
-	reg &= ~APDS990x_CMD_TYPE_MASK;
-	reg |= APDS990x_CMD | APDS990x_CMD_TYPE_INC;
-
-	ret = i2c_smbus_write_word_data(client, reg, data);
-	return (int)ret;
-}
-
-static int apds990x_mode_on(struct apds990x_chip *chip)
-{
-	/* ALS is mandatory, proximity optional */
-	u8 reg = APDS990X_EN_AIEN | APDS990X_EN_PON | APDS990X_EN_AEN |
-		APDS990X_EN_WEN;
-
-	if (chip->prox_en)
-		reg |= APDS990X_EN_PIEN | APDS990X_EN_PEN;
-
-	return apds990x_write_byte(chip, APDS990X_ENABLE, reg);
-}
-
-static u16 apds990x_lux_to_threshold(struct apds990x_chip *chip, u32 lux)
-{
-	u32 thres;
-	u32 cpl;
-	u32 ir;
-
-	if (lux == 0)
-		return 0;
-	else if (lux == APDS_RANGE)
-		return APDS_RANGE;
-
-	/*
-	 * Reported LUX value is a combination of the IR and CLEAR channel
-	 * values. However, interrupt threshold is only for clear channel.
-	 * This function approximates needed HW threshold value for a given
-	 * LUX value in the current lightning type.
-	 * IR level compared to visible light varies heavily depending on the
-	 * source of the light
-	 *
-	 * Calculate threshold value for the next measurement period.
-	 * Math: threshold = lux * cpl where
-	 * cpl = atime * again / (glass_attenuation * device_factor)
-	 * (count-per-lux)
-	 *
-	 * First remove calibration. Division by four is to avoid overflow
-	 */
-	lux = lux * (APDS_CALIB_SCALER / 4) / (chip->lux_calib / 4);
-
-	/* Multiplication by 64 is to increase accuracy */
-	cpl = ((u32)chip->atime * (u32)again[chip->again_next] *
-		APDS_PARAM_SCALE * 64) / (chip->cf.ga * chip->cf.df);
-
-	thres = lux * cpl / 64;
-	/*
-	 * Convert IR light from the latest result to match with
-	 * new gain step. This helps to adapt with the current
-	 * source of light.
-	 */
-	ir = (u32)chip->lux_ir * (u32)again[chip->again_next] /
-		(u32)again[chip->again_meas];
-
-	/*
-	 * Compensate count with IR light impact
-	 * IAC1 > IAC2 (see apds990x_get_lux for formulas)
-	 */
-	if (chip->lux_clear * APDS_PARAM_SCALE >=
-		chip->rcf.afactor * chip->lux_ir)
-		thres = (chip->rcf.cf1 * thres + chip->rcf.irf1 * ir) /
-			APDS_PARAM_SCALE;
-	else
-		thres = (chip->rcf.cf2 * thres + chip->rcf.irf2 * ir) /
-			APDS_PARAM_SCALE;
-
-	if (thres >= chip->a_max_result)
-		thres = chip->a_max_result - 1;
-	return thres;
-}
-
-static inline int apds990x_set_atime(struct apds990x_chip *chip, u32 time_ms)
-{
-	u8 reg_value;
-
-	chip->atime = time_ms;
-	/* Formula is specified in the data sheet */
-	reg_value = 256 - ((time_ms * TIME_STEP_SCALER) / TIMESTEP);
-	/* Calculate max ADC value for given integration time */
-	chip->a_max_result = (u16)(256 - reg_value) * APDS990X_TIME_TO_ADC;
-	return apds990x_write_byte(chip, APDS990X_ATIME, reg_value);
-}
-
-/* Called always with mutex locked */
-static int apds990x_refresh_pthres(struct apds990x_chip *chip, int data)
-{
-	int ret, lo, hi;
-
-	/* If the chip is not in use, don't try to access it */
-	if (pm_runtime_suspended(&chip->client->dev))
-		return 0;
-
-	if (data < chip->prox_thres) {
-		lo = 0;
-		hi = chip->prox_thres;
-	} else {
-		lo = chip->prox_thres - APDS_PROX_HYSTERESIS;
-		if (chip->prox_continuous_mode)
-			hi = chip->prox_thres;
-		else
-			hi = APDS_RANGE;
-	}
-
-	ret = apds990x_write_word(chip, APDS990X_PILTL, lo);
-	ret |= apds990x_write_word(chip, APDS990X_PIHTL, hi);
-	return ret;
-}
-
-/* Called always with mutex locked */
-static int apds990x_refresh_athres(struct apds990x_chip *chip)
-{
-	int ret;
-	/* If the chip is not in use, don't try to access it */
-	if (pm_runtime_suspended(&chip->client->dev))
-		return 0;
-
-	ret = apds990x_write_word(chip, APDS990X_AILTL,
-			apds990x_lux_to_threshold(chip, chip->lux_thres_lo));
-	ret |= apds990x_write_word(chip, APDS990X_AIHTL,
-			apds990x_lux_to_threshold(chip, chip->lux_thres_hi));
-
-	return ret;
-}
-
-/* Called always with mutex locked */
-static void apds990x_force_a_refresh(struct apds990x_chip *chip)
-{
-	/* This will force ALS interrupt after the next measurement. */
-	apds990x_write_word(chip, APDS990X_AILTL, APDS_LUX_DEF_THRES_LO);
-	apds990x_write_word(chip, APDS990X_AIHTL, APDS_LUX_DEF_THRES_HI);
-}
-
-/* Called always with mutex locked */
-static void apds990x_force_p_refresh(struct apds990x_chip *chip)
-{
-	/* This will force proximity interrupt after the next measurement. */
-	apds990x_write_word(chip, APDS990X_PILTL, APDS_PROX_DEF_THRES - 1);
-	apds990x_write_word(chip, APDS990X_PIHTL, APDS_PROX_DEF_THRES);
-}
-
-/* Called always with mutex locked */
-static int apds990x_calc_again(struct apds990x_chip *chip)
-{
-	int curr_again = chip->again_meas;
-	int next_again = chip->again_meas;
-	int ret = 0;
-
-	/* Calculate suitable als gain */
-	if (chip->lux_clear == chip->a_max_result)
-		next_again -= 2; /* ALS saturated. Decrease gain by 2 steps */
-	else if (chip->lux_clear > chip->a_max_result / 2)
-		next_again--;
-	else if (chip->lux_clear < APDS_LUX_GAIN_LO_LIMIT_STRICT)
-		next_again += 2; /* Too dark. Increase gain by 2 steps */
-	else if (chip->lux_clear < APDS_LUX_GAIN_LO_LIMIT)
-		next_again++;
-
-	/* Limit gain to available range */
-	if (next_again < 0)
-		next_again = 0;
-	else if (next_again > APDS990X_MAX_AGAIN)
-		next_again = APDS990X_MAX_AGAIN;
-
-	/* Let's check can we trust the measured result */
-	if (chip->lux_clear == chip->a_max_result)
-		/* Result can be totally garbage due to saturation */
-		ret = -ERANGE;
-	else if (next_again != curr_again &&
-		chip->lux_clear < APDS_LUX_GAIN_LO_LIMIT_STRICT)
-		/*
-		 * Gain is changed and measurement result is very small.
-		 * Result can be totally garbage due to underflow
-		 */
-		ret = -ERANGE;
-
-	chip->again_next = next_again;
-	apds990x_write_byte(chip, APDS990X_CONTROL,
-			(chip->pdrive << 6) |
-			(chip->pdiode << 4) |
-			(chip->pgain << 2) |
-			(chip->again_next << 0));
-
-	/*
-	 * Error means bad result -> re-measurement is needed. The forced
-	 * refresh uses fastest possible persistence setting to get result
-	 * as soon as possible.
-	 */
-	if (ret < 0)
-		apds990x_force_a_refresh(chip);
-	else
-		apds990x_refresh_athres(chip);
-
-	return ret;
-}
-
-/* Called always with mutex locked */
-static int apds990x_get_lux(struct apds990x_chip *chip, int clear, int ir)
-{
-	int iac, iac1, iac2; /* IR adjusted counts */
-	u32 lpc; /* Lux per count */
-
-	/* Formulas:
-	 * iac1 = CF1 * CLEAR_CH - IRF1 * IR_CH
-	 * iac2 = CF2 * CLEAR_CH - IRF2 * IR_CH
-	 */
-	iac1 = (chip->cf.cf1 * clear - chip->cf.irf1 * ir) / APDS_PARAM_SCALE;
-	iac2 = (chip->cf.cf2 * clear - chip->cf.irf2 * ir) / APDS_PARAM_SCALE;
-
-	iac = max(iac1, iac2);
-	iac = max(iac, 0);
-
-	lpc = APDS990X_LUX_OUTPUT_SCALE * (chip->cf.df * chip->cf.ga) /
-		(u32)(again[chip->again_meas] * (u32)chip->atime);
-
-	return (iac * lpc) / APDS_PARAM_SCALE;
-}
-
-static int apds990x_ack_int(struct apds990x_chip *chip, u8 mode)
-{
-	struct i2c_client *client = chip->client;
-	s32 ret;
-	u8 reg = APDS990x_CMD | APDS990x_CMD_TYPE_SPE;
-
-	switch (mode & (APDS990X_ST_AINT | APDS990X_ST_PINT)) {
-	case APDS990X_ST_AINT:
-		reg |= APDS990X_INT_ACK_ALS;
-		break;
-	case APDS990X_ST_PINT:
-		reg |= APDS990X_INT_ACK_PS;
-		break;
-	default:
-		reg |= APDS990X_INT_ACK_BOTH;
-		break;
-	}
-
-	ret = i2c_smbus_read_byte_data(client, reg);
-	return (int)ret;
-}
-
-static irqreturn_t apds990x_irq(int irq, void *data)
-{
-	struct apds990x_chip *chip = data;
-	u8 status;
-
-	apds990x_read_byte(chip, APDS990X_STATUS, &status);
-	apds990x_ack_int(chip, status);
-
-	mutex_lock(&chip->mutex);
-	if (!pm_runtime_suspended(&chip->client->dev)) {
-		if (status & APDS990X_ST_AINT) {
-			apds990x_read_word(chip, APDS990X_CDATAL,
-					&chip->lux_clear);
-			apds990x_read_word(chip, APDS990X_IRDATAL,
-					&chip->lux_ir);
-			/* Store used gain for calculations */
-			chip->again_meas = chip->again_next;
-
-			chip->lux_raw = apds990x_get_lux(chip,
-							chip->lux_clear,
-							chip->lux_ir);
-
-			if (apds990x_calc_again(chip) == 0) {
-				/* Result is valid */
-				chip->lux = chip->lux_raw;
-				chip->lux_wait_fresh_res = false;
-				wake_up(&chip->wait);
-				sysfs_notify(&chip->client->dev.kobj,
-					NULL, "lux0_input");
-			}
-		}
-
-		if ((status & APDS990X_ST_PINT) && chip->prox_en) {
-			u16 clr_ch;
-
-			apds990x_read_word(chip, APDS990X_CDATAL, &clr_ch);
-			/*
-			 * If ALS channel is saturated at min gain,
-			 * proximity gives false posivite values.
-			 * Just ignore them.
-			 */
-			if (chip->again_meas == 0 &&
-				clr_ch == chip->a_max_result)
-				chip->prox_data = 0;
-			else
-				apds990x_read_word(chip,
-						APDS990X_PDATAL,
-						&chip->prox_data);
-
-			apds990x_refresh_pthres(chip, chip->prox_data);
-			if (chip->prox_data < chip->prox_thres)
-				chip->prox_data = 0;
-			else if (!chip->prox_continuous_mode)
-				chip->prox_data = APDS_PROX_RANGE;
-			sysfs_notify(&chip->client->dev.kobj,
-				NULL, "prox0_raw");
-		}
-	}
-	mutex_unlock(&chip->mutex);
-	return IRQ_HANDLED;
-}
-
-static int apds990x_configure(struct apds990x_chip *chip)
-{
-	/* It is recommended to use disabled mode during these operations */
-	apds990x_write_byte(chip, APDS990X_ENABLE, APDS990X_EN_DISABLE_ALL);
-
-	/* conversion and wait times for different state machince states */
-	apds990x_write_byte(chip, APDS990X_PTIME, APDS990X_PTIME_DEFAULT);
-	apds990x_write_byte(chip, APDS990X_WTIME, APDS990X_WTIME_DEFAULT);
-	apds990x_set_atime(chip, APDS_LUX_AVERAGING_TIME);
-
-	apds990x_write_byte(chip, APDS990X_CONFIG, 0);
-
-	/* Persistence levels */
-	apds990x_write_byte(chip, APDS990X_PERS,
-			(chip->lux_persistence << APDS990X_APERS_SHIFT) |
-			(chip->prox_persistence << APDS990X_PPERS_SHIFT));
-
-	apds990x_write_byte(chip, APDS990X_PPCOUNT, chip->pdata->ppcount);
-
-	/* Start with relatively small gain */
-	chip->again_meas = 1;
-	chip->again_next = 1;
-	apds990x_write_byte(chip, APDS990X_CONTROL,
-			(chip->pdrive << 6) |
-			(chip->pdiode << 4) |
-			(chip->pgain << 2) |
-			(chip->again_next << 0));
-	return 0;
-}
-
-static int apds990x_detect(struct apds990x_chip *chip)
-{
-	struct i2c_client *client = chip->client;
-	int ret;
-	u8 id;
-
-	ret = apds990x_read_byte(chip, APDS990X_ID, &id);
-	if (ret < 0) {
-		dev_err(&client->dev, "ID read failed\n");
-		return ret;
-	}
-
-	ret = apds990x_read_byte(chip, APDS990X_REV, &chip->revision);
-	if (ret < 0) {
-		dev_err(&client->dev, "REV read failed\n");
-		return ret;
-	}
-
-	switch (id) {
-	case APDS990X_ID_0:
-	case APDS990X_ID_4:
-	case APDS990X_ID_29:
-		snprintf(chip->chipname, sizeof(chip->chipname), "APDS-990x");
-		break;
-	default:
-		ret = -ENODEV;
-		break;
-	}
-	return ret;
-}
-
-#ifdef CONFIG_PM
-static int apds990x_chip_on(struct apds990x_chip *chip)
-{
-	int err	 = regulator_bulk_enable(ARRAY_SIZE(chip->regs),
-					chip->regs);
-	if (err < 0)
-		return err;
-
-	usleep_range(APDS_STARTUP_DELAY, 2 * APDS_STARTUP_DELAY);
-
-	/* Refresh all configs in case of regulators were off */
-	chip->prox_data = 0;
-	apds990x_configure(chip);
-	apds990x_mode_on(chip);
-	return 0;
-}
-#endif
-
-static int apds990x_chip_off(struct apds990x_chip *chip)
-{
-	apds990x_write_byte(chip, APDS990X_ENABLE, APDS990X_EN_DISABLE_ALL);
-	regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs);
-	return 0;
-}
-
-static ssize_t apds990x_lux_show(struct device *dev,
-				 struct device_attribute *attr, char *buf)
-{
-	struct apds990x_chip *chip = dev_get_drvdata(dev);
-	ssize_t ret;
-	u32 result;
-	long time_left;
-
-	if (pm_runtime_suspended(dev))
-		return -EIO;
-
-	time_left = wait_event_interruptible_timeout(chip->wait,
-						     !chip->lux_wait_fresh_res,
-						     msecs_to_jiffies(APDS_TIMEOUT));
-	if (!time_left)
-		return -EIO;
-
-	mutex_lock(&chip->mutex);
-	result = (chip->lux * chip->lux_calib) / APDS_CALIB_SCALER;
-	if (result > (APDS_RANGE * APDS990X_LUX_OUTPUT_SCALE))
-		result = APDS_RANGE * APDS990X_LUX_OUTPUT_SCALE;
-
-	ret = sprintf(buf, "%d.%d\n",
-		result / APDS990X_LUX_OUTPUT_SCALE,
-		result % APDS990X_LUX_OUTPUT_SCALE);
-	mutex_unlock(&chip->mutex);
-	return ret;
-}
-
-static DEVICE_ATTR(lux0_input, S_IRUGO, apds990x_lux_show, NULL);
-
-static ssize_t apds990x_lux_range_show(struct device *dev,
-				 struct device_attribute *attr, char *buf)
-{
-	return sprintf(buf, "%u\n", APDS_RANGE);
-}
-
-static DEVICE_ATTR(lux0_sensor_range, S_IRUGO, apds990x_lux_range_show, NULL);
-
-static ssize_t apds990x_lux_calib_format_show(struct device *dev,
-				 struct device_attribute *attr, char *buf)
-{
-	return sprintf(buf, "%u\n", APDS_CALIB_SCALER);
-}
-
-static DEVICE_ATTR(lux0_calibscale_default, S_IRUGO,
-		apds990x_lux_calib_format_show, NULL);
-
-static ssize_t apds990x_lux_calib_show(struct device *dev,
-				 struct device_attribute *attr, char *buf)
-{
-	struct apds990x_chip *chip = dev_get_drvdata(dev);
-
-	return sprintf(buf, "%u\n", chip->lux_calib);
-}
-
-static ssize_t apds990x_lux_calib_store(struct device *dev,
-				  struct device_attribute *attr,
-				  const char *buf, size_t len)
-{
-	struct apds990x_chip *chip = dev_get_drvdata(dev);
-	unsigned long value;
-	int ret;
-
-	ret = kstrtoul(buf, 0, &value);
-	if (ret)
-		return ret;
-
-	chip->lux_calib = value;
-
-	return len;
-}
-
-static DEVICE_ATTR(lux0_calibscale, S_IRUGO | S_IWUSR, apds990x_lux_calib_show,
-		apds990x_lux_calib_store);
-
-static ssize_t apds990x_rate_avail(struct device *dev,
-				   struct device_attribute *attr, char *buf)
-{
-	int i;
-	int pos = 0;
-
-	for (i = 0; i < ARRAY_SIZE(arates_hz); i++)
-		pos += sprintf(buf + pos, "%d ", arates_hz[i]);
-	sprintf(buf + pos - 1, "\n");
-	return pos;
-}
-
-static ssize_t apds990x_rate_show(struct device *dev,
-				   struct device_attribute *attr, char *buf)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-
-	return sprintf(buf, "%d\n", chip->arate);
-}
-
-static int apds990x_set_arate(struct apds990x_chip *chip, int rate)
-{
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(arates_hz); i++)
-		if (rate >= arates_hz[i])
-			break;
-
-	if (i == ARRAY_SIZE(arates_hz))
-		return -EINVAL;
-
-	/* Pick up corresponding persistence value */
-	chip->lux_persistence = apersis[i];
-	chip->arate = arates_hz[i];
-
-	/* If the chip is not in use, don't try to access it */
-	if (pm_runtime_suspended(&chip->client->dev))
-		return 0;
-
-	/* Persistence levels */
-	return apds990x_write_byte(chip, APDS990X_PERS,
-			(chip->lux_persistence << APDS990X_APERS_SHIFT) |
-			(chip->prox_persistence << APDS990X_PPERS_SHIFT));
-}
-
-static ssize_t apds990x_rate_store(struct device *dev,
-				  struct device_attribute *attr,
-				  const char *buf, size_t len)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-	unsigned long value;
-	int ret;
-
-	ret = kstrtoul(buf, 0, &value);
-	if (ret)
-		return ret;
-
-	mutex_lock(&chip->mutex);
-	ret = apds990x_set_arate(chip, value);
-	mutex_unlock(&chip->mutex);
-
-	if (ret < 0)
-		return ret;
-	return len;
-}
-
-static DEVICE_ATTR(lux0_rate_avail, S_IRUGO, apds990x_rate_avail, NULL);
-
-static DEVICE_ATTR(lux0_rate, S_IRUGO | S_IWUSR, apds990x_rate_show,
-						 apds990x_rate_store);
-
-static ssize_t apds990x_prox_show(struct device *dev,
-				 struct device_attribute *attr, char *buf)
-{
-	ssize_t ret;
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-
-	if (pm_runtime_suspended(dev) || !chip->prox_en)
-		return -EIO;
-
-	mutex_lock(&chip->mutex);
-	ret = sprintf(buf, "%d\n", chip->prox_data);
-	mutex_unlock(&chip->mutex);
-	return ret;
-}
-
-static DEVICE_ATTR(prox0_raw, S_IRUGO, apds990x_prox_show, NULL);
-
-static ssize_t apds990x_prox_range_show(struct device *dev,
-				 struct device_attribute *attr, char *buf)
-{
-	return sprintf(buf, "%u\n", APDS_PROX_RANGE);
-}
-
-static DEVICE_ATTR(prox0_sensor_range, S_IRUGO, apds990x_prox_range_show, NULL);
-
-static ssize_t apds990x_prox_enable_show(struct device *dev,
-				   struct device_attribute *attr, char *buf)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-
-	return sprintf(buf, "%d\n", chip->prox_en);
-}
-
-static ssize_t apds990x_prox_enable_store(struct device *dev,
-				  struct device_attribute *attr,
-				  const char *buf, size_t len)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-	unsigned long value;
-	int ret;
-
-	ret = kstrtoul(buf, 0, &value);
-	if (ret)
-		return ret;
-
-	mutex_lock(&chip->mutex);
-
-	if (!chip->prox_en)
-		chip->prox_data = 0;
-
-	if (value)
-		chip->prox_en++;
-	else if (chip->prox_en > 0)
-		chip->prox_en--;
-
-	if (!pm_runtime_suspended(dev))
-		apds990x_mode_on(chip);
-	mutex_unlock(&chip->mutex);
-	return len;
-}
-
-static DEVICE_ATTR(prox0_raw_en, S_IRUGO | S_IWUSR, apds990x_prox_enable_show,
-						   apds990x_prox_enable_store);
-
-static const char *reporting_modes[] = {"trigger", "periodic"};
-
-static ssize_t apds990x_prox_reporting_mode_show(struct device *dev,
-				   struct device_attribute *attr, char *buf)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-
-	return sprintf(buf, "%s\n",
-		reporting_modes[!!chip->prox_continuous_mode]);
-}
-
-static ssize_t apds990x_prox_reporting_mode_store(struct device *dev,
-				  struct device_attribute *attr,
-				  const char *buf, size_t len)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-	int ret;
-
-	ret = sysfs_match_string(reporting_modes, buf);
-	if (ret < 0)
-		return ret;
-
-	chip->prox_continuous_mode = ret;
-	return len;
-}
-
-static DEVICE_ATTR(prox0_reporting_mode, S_IRUGO | S_IWUSR,
-		apds990x_prox_reporting_mode_show,
-		apds990x_prox_reporting_mode_store);
-
-static ssize_t apds990x_prox_reporting_avail_show(struct device *dev,
-				   struct device_attribute *attr, char *buf)
-{
-	return sprintf(buf, "%s %s\n", reporting_modes[0], reporting_modes[1]);
-}
-
-static DEVICE_ATTR(prox0_reporting_mode_avail, S_IRUGO | S_IWUSR,
-		apds990x_prox_reporting_avail_show, NULL);
-
-
-static ssize_t apds990x_lux_thresh_above_show(struct device *dev,
-				   struct device_attribute *attr, char *buf)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-
-	return sprintf(buf, "%d\n", chip->lux_thres_hi);
-}
-
-static ssize_t apds990x_lux_thresh_below_show(struct device *dev,
-				   struct device_attribute *attr, char *buf)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-
-	return sprintf(buf, "%d\n", chip->lux_thres_lo);
-}
-
-static ssize_t apds990x_set_lux_thresh(struct apds990x_chip *chip, u32 *target,
-				const char *buf)
-{
-	unsigned long thresh;
-	int ret;
-
-	ret = kstrtoul(buf, 0, &thresh);
-	if (ret)
-		return ret;
-
-	if (thresh > APDS_RANGE)
-		return -EINVAL;
-
-	mutex_lock(&chip->mutex);
-	*target = thresh;
-	/*
-	 * Don't update values in HW if we are still waiting for
-	 * first interrupt to come after device handle open call.
-	 */
-	if (!chip->lux_wait_fresh_res)
-		apds990x_refresh_athres(chip);
-	mutex_unlock(&chip->mutex);
-	return ret;
-
-}
-
-static ssize_t apds990x_lux_thresh_above_store(struct device *dev,
-				  struct device_attribute *attr,
-				  const char *buf, size_t len)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-	int ret = apds990x_set_lux_thresh(chip, &chip->lux_thres_hi, buf);
-
-	if (ret < 0)
-		return ret;
-	return len;
-}
-
-static ssize_t apds990x_lux_thresh_below_store(struct device *dev,
-				  struct device_attribute *attr,
-				  const char *buf, size_t len)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-	int ret = apds990x_set_lux_thresh(chip, &chip->lux_thres_lo, buf);
-
-	if (ret < 0)
-		return ret;
-	return len;
-}
-
-static DEVICE_ATTR(lux0_thresh_above_value, S_IRUGO | S_IWUSR,
-		apds990x_lux_thresh_above_show,
-		apds990x_lux_thresh_above_store);
-
-static DEVICE_ATTR(lux0_thresh_below_value, S_IRUGO | S_IWUSR,
-		apds990x_lux_thresh_below_show,
-		apds990x_lux_thresh_below_store);
-
-static ssize_t apds990x_prox_threshold_show(struct device *dev,
-				   struct device_attribute *attr, char *buf)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-
-	return sprintf(buf, "%d\n", chip->prox_thres);
-}
-
-static ssize_t apds990x_prox_threshold_store(struct device *dev,
-				  struct device_attribute *attr,
-				  const char *buf, size_t len)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-	unsigned long value;
-	int ret;
-
-	ret = kstrtoul(buf, 0, &value);
-	if (ret)
-		return ret;
-
-	if ((value > APDS_RANGE) || (value == 0) ||
-		(value < APDS_PROX_HYSTERESIS))
-		return -EINVAL;
-
-	mutex_lock(&chip->mutex);
-	chip->prox_thres = value;
-
-	apds990x_force_p_refresh(chip);
-	mutex_unlock(&chip->mutex);
-	return len;
-}
-
-static DEVICE_ATTR(prox0_thresh_above_value, S_IRUGO | S_IWUSR,
-		apds990x_prox_threshold_show,
-		apds990x_prox_threshold_store);
-
-static ssize_t apds990x_power_state_show(struct device *dev,
-				   struct device_attribute *attr, char *buf)
-{
-	return sprintf(buf, "%d\n", !pm_runtime_suspended(dev));
-}
-
-static ssize_t apds990x_power_state_store(struct device *dev,
-				  struct device_attribute *attr,
-				  const char *buf, size_t len)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-	unsigned long value;
-	int ret;
-
-	ret = kstrtoul(buf, 0, &value);
-	if (ret)
-		return ret;
-
-	if (value) {
-		pm_runtime_get_sync(dev);
-		mutex_lock(&chip->mutex);
-		chip->lux_wait_fresh_res = true;
-		apds990x_force_a_refresh(chip);
-		apds990x_force_p_refresh(chip);
-		mutex_unlock(&chip->mutex);
-	} else {
-		if (!pm_runtime_suspended(dev))
-			pm_runtime_put(dev);
-	}
-	return len;
-}
-
-static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR,
-		apds990x_power_state_show,
-		apds990x_power_state_store);
-
-static ssize_t apds990x_chip_id_show(struct device *dev,
-				   struct device_attribute *attr, char *buf)
-{
-	struct apds990x_chip *chip =  dev_get_drvdata(dev);
-
-	return sprintf(buf, "%s %d\n", chip->chipname, chip->revision);
-}
-
-static DEVICE_ATTR(chip_id, S_IRUGO, apds990x_chip_id_show, NULL);
-
-static struct attribute *sysfs_attrs_ctrl[] = {
-	&dev_attr_lux0_calibscale.attr,
-	&dev_attr_lux0_calibscale_default.attr,
-	&dev_attr_lux0_input.attr,
-	&dev_attr_lux0_sensor_range.attr,
-	&dev_attr_lux0_rate.attr,
-	&dev_attr_lux0_rate_avail.attr,
-	&dev_attr_lux0_thresh_above_value.attr,
-	&dev_attr_lux0_thresh_below_value.attr,
-	&dev_attr_prox0_raw_en.attr,
-	&dev_attr_prox0_raw.attr,
-	&dev_attr_prox0_sensor_range.attr,
-	&dev_attr_prox0_thresh_above_value.attr,
-	&dev_attr_prox0_reporting_mode.attr,
-	&dev_attr_prox0_reporting_mode_avail.attr,
-	&dev_attr_chip_id.attr,
-	&dev_attr_power_state.attr,
-	NULL
-};
-
-static const struct attribute_group apds990x_attribute_group[] = {
-	{.attrs = sysfs_attrs_ctrl },
-};
-
-static int apds990x_probe(struct i2c_client *client)
-{
-	struct apds990x_chip *chip;
-	int err;
-
-	chip = kzalloc_obj(*chip);
-	if (!chip)
-		return -ENOMEM;
-
-	i2c_set_clientdata(client, chip);
-	chip->client  = client;
-
-	init_waitqueue_head(&chip->wait);
-	mutex_init(&chip->mutex);
-	chip->pdata	= client->dev.platform_data;
-
-	if (chip->pdata == NULL) {
-		dev_err(&client->dev, "platform data is mandatory\n");
-		err = -EINVAL;
-		goto fail1;
-	}
-
-	if (chip->pdata->cf.ga == 0) {
-		/* set uncovered sensor default parameters */
-		chip->cf.ga = 1966; /* 0.48 * APDS_PARAM_SCALE */
-		chip->cf.cf1 = 4096; /* 1.00 * APDS_PARAM_SCALE */
-		chip->cf.irf1 = 9134; /* 2.23 * APDS_PARAM_SCALE */
-		chip->cf.cf2 = 2867; /* 0.70 * APDS_PARAM_SCALE */
-		chip->cf.irf2 = 5816; /* 1.42 * APDS_PARAM_SCALE */
-		chip->cf.df = 52;
-	} else {
-		chip->cf = chip->pdata->cf;
-	}
-
-	/* precalculate inverse chip factors for threshold control */
-	chip->rcf.afactor =
-		(chip->cf.irf1 - chip->cf.irf2) * APDS_PARAM_SCALE /
-		(chip->cf.cf1 - chip->cf.cf2);
-	chip->rcf.cf1 = APDS_PARAM_SCALE * APDS_PARAM_SCALE /
-		chip->cf.cf1;
-	chip->rcf.irf1 = chip->cf.irf1 * APDS_PARAM_SCALE /
-		chip->cf.cf1;
-	chip->rcf.cf2 = APDS_PARAM_SCALE * APDS_PARAM_SCALE /
-		chip->cf.cf2;
-	chip->rcf.irf2 = chip->cf.irf2 * APDS_PARAM_SCALE /
-		chip->cf.cf2;
-
-	/* Set something to start with */
-	chip->lux_thres_hi = APDS_LUX_DEF_THRES_HI;
-	chip->lux_thres_lo = APDS_LUX_DEF_THRES_LO;
-	chip->lux_calib = APDS_LUX_NEUTRAL_CALIB_VALUE;
-
-	chip->prox_thres = APDS_PROX_DEF_THRES;
-	chip->pdrive = chip->pdata->pdrive;
-	chip->pdiode = APDS_PDIODE_IR;
-	chip->pgain = APDS_PGAIN_1X;
-	chip->prox_calib = APDS_PROX_NEUTRAL_CALIB_VALUE;
-	chip->prox_persistence = APDS_DEFAULT_PROX_PERS;
-	chip->prox_continuous_mode = false;
-
-	chip->regs[0].supply = reg_vcc;
-	chip->regs[1].supply = reg_vled;
-
-	err = regulator_bulk_get(&client->dev,
-				 ARRAY_SIZE(chip->regs), chip->regs);
-	if (err < 0) {
-		dev_err(&client->dev, "Cannot get regulators\n");
-		goto fail1;
-	}
-
-	err = regulator_bulk_enable(ARRAY_SIZE(chip->regs), chip->regs);
-	if (err < 0) {
-		dev_err(&client->dev, "Cannot enable regulators\n");
-		goto fail2;
-	}
-
-	usleep_range(APDS_STARTUP_DELAY, 2 * APDS_STARTUP_DELAY);
-
-	err = apds990x_detect(chip);
-	if (err < 0) {
-		dev_err(&client->dev, "APDS990X not found\n");
-		goto fail3;
-	}
-
-	pm_runtime_set_active(&client->dev);
-
-	apds990x_configure(chip);
-	apds990x_set_arate(chip, APDS_LUX_DEFAULT_RATE);
-	apds990x_mode_on(chip);
-
-	pm_runtime_enable(&client->dev);
-
-	if (chip->pdata->setup_resources) {
-		err = chip->pdata->setup_resources();
-		if (err) {
-			err = -EINVAL;
-			goto fail4;
-		}
-	}
-
-	err = sysfs_create_group(&chip->client->dev.kobj,
-				apds990x_attribute_group);
-	if (err < 0) {
-		dev_err(&chip->client->dev, "Sysfs registration failed\n");
-		goto fail5;
-	}
-
-	err = request_threaded_irq(client->irq, NULL,
-				apds990x_irq,
-				IRQF_TRIGGER_FALLING | IRQF_TRIGGER_LOW |
-				IRQF_ONESHOT,
-				"apds990x", chip);
-	if (err) {
-		dev_err(&client->dev, "could not get IRQ %d\n",
-			client->irq);
-		goto fail6;
-	}
-	return err;
-fail6:
-	sysfs_remove_group(&chip->client->dev.kobj,
-			&apds990x_attribute_group[0]);
-fail5:
-	if (chip->pdata && chip->pdata->release_resources)
-		chip->pdata->release_resources();
-fail4:
-	pm_runtime_disable(&client->dev);
-fail3:
-	regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs);
-fail2:
-	regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs);
-fail1:
-	kfree(chip);
-	return err;
-}
-
-static void apds990x_remove(struct i2c_client *client)
-{
-	struct apds990x_chip *chip = i2c_get_clientdata(client);
-
-	free_irq(client->irq, chip);
-	sysfs_remove_group(&chip->client->dev.kobj,
-			apds990x_attribute_group);
-
-	if (chip->pdata && chip->pdata->release_resources)
-		chip->pdata->release_resources();
-
-	if (!pm_runtime_suspended(&client->dev))
-		apds990x_chip_off(chip);
-
-	pm_runtime_disable(&client->dev);
-	pm_runtime_set_suspended(&client->dev);
-
-	regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs);
-
-	kfree(chip);
-}
-
-#ifdef CONFIG_PM_SLEEP
-static int apds990x_suspend(struct device *dev)
-{
-	struct i2c_client *client = to_i2c_client(dev);
-	struct apds990x_chip *chip = i2c_get_clientdata(client);
-
-	apds990x_chip_off(chip);
-	return 0;
-}
-
-static int apds990x_resume(struct device *dev)
-{
-	struct i2c_client *client = to_i2c_client(dev);
-	struct apds990x_chip *chip = i2c_get_clientdata(client);
-
-	/*
-	 * If we were enabled at suspend time, it is expected
-	 * everything works nice and smoothly. Chip_on is enough
-	 */
-	apds990x_chip_on(chip);
-
-	return 0;
-}
-#endif
-
-#ifdef CONFIG_PM
-static int apds990x_runtime_suspend(struct device *dev)
-{
-	struct i2c_client *client = to_i2c_client(dev);
-	struct apds990x_chip *chip = i2c_get_clientdata(client);
-
-	apds990x_chip_off(chip);
-	return 0;
-}
-
-static int apds990x_runtime_resume(struct device *dev)
-{
-	struct i2c_client *client = to_i2c_client(dev);
-	struct apds990x_chip *chip = i2c_get_clientdata(client);
-
-	apds990x_chip_on(chip);
-	return 0;
-}
-
-#endif
-
-static const struct i2c_device_id apds990x_id[] = {
-	{ "apds990x" },
-	{}
-};
-
-MODULE_DEVICE_TABLE(i2c, apds990x_id);
-
-static const struct dev_pm_ops apds990x_pm_ops = {
-	SET_SYSTEM_SLEEP_PM_OPS(apds990x_suspend, apds990x_resume)
-	SET_RUNTIME_PM_OPS(apds990x_runtime_suspend,
-			apds990x_runtime_resume,
-			NULL)
-};
-
-static struct i2c_driver apds990x_driver = {
-	.driver	  = {
-		.name	= "apds990x",
-		.pm	= &apds990x_pm_ops,
-	},
-	.probe    = apds990x_probe,
-	.remove	  = apds990x_remove,
-	.id_table = apds990x_id,
-};
-
-module_i2c_driver(apds990x_driver);
-
-MODULE_DESCRIPTION("APDS990X combined ALS and proximity sensor");
-MODULE_AUTHOR("Samu Onkalo, Nokia Corporation");
-MODULE_LICENSE("GPL v2");
diff --git a/include/linux/platform_data/apds990x.h b/include/linux/platform_data/apds990x.h
deleted file mode 100644
index 37684f68c04f..000000000000
--- a/include/linux/platform_data/apds990x.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * This file is part of the APDS990x sensor driver.
- * Chip is combined proximity and ambient light sensor.
- *
- * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
- *
- * Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
- */
-
-#ifndef __APDS990X_H__
-#define __APDS990X_H__
-
-
-#define APDS_IRLED_CURR_12mA	0x3
-#define APDS_IRLED_CURR_25mA	0x2
-#define APDS_IRLED_CURR_50mA	0x1
-#define APDS_IRLED_CURR_100mA	0x0
-
-/**
- * struct apds990x_chip_factors - defines effect of the cover window
- * @ga: Total glass attenuation
- * @cf1: clear channel factor 1 for raw to lux conversion
- * @irf1: IR channel factor 1 for raw to lux conversion
- * @cf2: clear channel factor 2 for raw to lux conversion
- * @irf2: IR channel factor 2 for raw to lux conversion
- * @df: device factor for conversion formulas
- *
- * Structure for tuning ALS calculation to match with environment.
- * Values depend on the material above the sensor and the sensor
- * itself. If the GA is zero, driver will use uncovered sensor default values
- * format: decimal value * APDS_PARAM_SCALE except df which is plain integer.
- */
-struct apds990x_chip_factors {
-	int ga;
-	int cf1;
-	int irf1;
-	int cf2;
-	int irf2;
-	int df;
-};
-#define APDS_PARAM_SCALE 4096
-
-/**
- * struct apds990x_platform_data - platform data for apsd990x.c driver
- * @cf: chip factor data
- * @pdrive: IR-led driving current
- * @ppcount: number of IR pulses used for proximity estimation
- * @setup_resources: interrupt line setup call back function
- * @release_resources: interrupt line release call back function
- *
- * Proximity detection result depends heavily on correct ppcount, pdrive
- * and cover window.
- *
- */
-
-struct apds990x_platform_data {
-	struct apds990x_chip_factors cf;
-	u8     pdrive;
-	u8     ppcount;
-	int    (*setup_resources)(void);
-	int    (*release_resources)(void);
-};
-
-#endif
-- 
2.51.0


^ permalink raw reply related

* Re: [PATCH] docs: Add overview and SLUB allocator sections to slab documentation
From: David Hildenbrand (Arm) @ 2026-04-19  8:35 UTC (permalink / raw)
  To: Matthew Wilcox, Lorenzo Stoakes
  Cc: Nick Huang, Vlastimil Babka, Harry Yoo, Andrew Morton,
	Jonathan Corbet, Hao Li, Christoph Lameter, David Rientjes,
	Roman Gushchin, Liam R . Howlett, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Shuah Khan, linux-mm, linux-doc,
	linux-kernel
In-Reply-To: <aeOuCH8ydw_yzdXZ@casper.infradead.org>

On 4/18/26 18:15, Matthew Wilcox wrote:
> On Sat, Apr 18, 2026 at 10:07:22AM +0100, Lorenzo Stoakes wrote:
>> On Sat, Apr 18, 2026 at 12:06:19AM +0000, Nick Huang wrote:
>>> - Add "Overview" section explaining the slab allocator's role and purpose
>>> - Document the three main slab allocator implementations (SLAB, SLUB, SLOB)
>>
>> The fact you're insanely wrong about the current state of slab only makes this
>> worse.
> 
> This is actually a new low.  We've always had to contend with people
> putting up outdated or just wrong information on web pages, and there's
> little we can do about it.  Witness all the outdated information about
> THP that's based on code that's been deleted for over a decade.
> 
> But now we've got AI trained on all this wrong/ out of date information,
> and, er, "enthusiasts" who are trying to change the correct information
> in the kernel to match what the deluded AI "thinks" should be true.
> 
> Let that sink in.
> 

I think we should make it very clear that we don't want doc updates from someone
that is not a renowned expert in that area or wants to become an expert in that
area (and already discussed working on the docs with maintainers/experts).

Otherwise we'll have this same discussion over and over again.

diff --git a/Documentation/mm/index.rst b/Documentation/mm/index.rst
index 7aa2a88869083..8c5721001c8bb 100644
--- a/Documentation/mm/index.rst
+++ b/Documentation/mm/index.rst
@@ -7,6 +7,11 @@ of Linux.  If you are looking for advice on simply allocating
memory,
  see the :ref:`memory_allocation`.  For controlling and tuning guides,
  see the :doc:`admin guide <../admin-guide/mm/index>`.

+A lot of documentation in this guide is still incomplete. If you are not
+a renowned expert in the specific area, but you want to contribute bigger
+chunks of documentation, talk to the respective MM experts first. LLM
+generated slop from non-experts will be rejected without further comments.
+
  .. toctree::
     :maxdepth: 1



LLMs are just the tip of the iceberg. It will all be developmend-by review with
inexperienced contributors. And we are only willing to put in the effort to
teach contributors if the contributors are not actually worth our time: i.e.,
LLM kiddies that will actually stick around and help the subsystem in the long run.


The whole doc update stuff is similar to people just grepping for TODOs in the
kernel and then using an LLM to produce code they have no idea about.

It's the evolution of typo fixes: review load without any benefit.

-- 
Cheers,

David


^ permalink raw reply related

* Re: [PATCH v2 3/3] misc: Remove old APDS990x driver
From: Greg Kroah-Hartman @ 2026-04-19  8:42 UTC (permalink / raw)
  To: Svyatoslav Ryhel
  Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, Arnd Bergmann, Randy Dunlap, linux-iio, devicetree,
	linux-kernel, linux-doc
In-Reply-To: <20260419083125.35572-4-clamor95@gmail.com>

On Sun, Apr 19, 2026 at 11:31:24AM +0300, Svyatoslav Ryhel wrote:
> The Avago APDS9900/9901 ALS/Proximity sensor is now supported by tsl2772
> IIO driver so there is no need to keep this old implementation. Remove it.
> 
> Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> ---
>  Documentation/misc-devices/apds990x.rst |  128 ---
>  drivers/misc/Kconfig                    |   10 -
>  drivers/misc/Makefile                   |    1 -
>  drivers/misc/apds990x.c                 | 1284 -----------------------
>  include/linux/platform_data/apds990x.h  |   65 --
>  5 files changed, 1488 deletions(-)
>  delete mode 100644 Documentation/misc-devices/apds990x.rst
>  delete mode 100644 drivers/misc/apds990x.c
>  delete mode 100644 include/linux/platform_data/apds990x.h

Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox