* Re: [PATCH 07/16] ASoC: dt-bindings: add TI PCM1773
From: H. Nikolaus Schaller @ 2026-07-13 8:27 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: linux-fbdev, Kuninori Morimoto, Tony Lindgren, dri-devel,
Liam Girdwood, Niranjan H Y, Sen Wang, Rob Herring, Aaro Koskinen,
Kevin Hilman, Helge Deller, Lee Jones, Russell King, linux-omap,
Daniel Thompson, Grazvydas Ignotas, devicetree, Conor Dooley,
Charles Keepax, Arnd Bergmann, Sascha Hauer, mfd, linux-sound,
Richard Fitzgerald, Mark Brown, Takashi Iwai, Jaroslav Kysela,
Srinivas Kandagatla, linux-arm-kernel, Roger Quadros, Jingoo Han,
linux-kernel, kernel, Jarkko Nikula, Ethan Nelson-Moore,
Discussions about the Letux Kernel
In-Reply-To: <20260713-abiding-jumping-ape-7ce8ff@quoll>
Hi Krzysztof,
Thank you for the feedback.
> Am 13.07.2026 um 09:38 schrieb Krzysztof Kozlowski <krzk@kernel.org>:
>
> On Sat, Jul 11, 2026 at 08:01:54AM +0200, H. Nikolaus Schaller wrote:
>> PCM1771/3 is a simple audio codec that can be enabled through an
>> enable-gpio.
>>
>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>> ---
>> .../devicetree/bindings/sound/pcm1773.yaml | 32 +++++++++++++++++++
>> 1 file changed, 32 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/sound/pcm1773.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/sound/pcm1773.yaml b/Documentation/devicetree/bindings/sound/pcm1773.yaml
>> new file mode 100644
>> index 0000000000000..f3e640705bf70
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/sound/pcm1773.yaml
>> @@ -0,0 +1,32 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/bindings/sound/pcm1773.yaml#
>
> You need to use latest existing bindings as starting point and test your
> code.
Resulting code from compiler was runtime tested.
Actually, we used the latest bindings of some other audio dac as a starting point.
However, we introduced errors into the finally submitted file.
We will verify and test the schema next time.
Regarding the checks: both the rob herring bot and the sashiko bot also reported several issues directly to us,
so we are already aware of some other remaining errors.
We are currently working on a v2 to clean everything up.
Thanks and BR,
Nikolaus
^ permalink raw reply
* Re: [PATCH 01/16] dt-bindings: twl-regulator: Add bindings for exposing ti,twl4030-regen
From: Krzysztof Kozlowski @ 2026-07-13 9:19 UTC (permalink / raw)
To: H. Nikolaus Schaller, Grond
Cc: Stefan Leichter, Grazvydas Ignotas, Tony Lindgren,
Ethan Nelson-Moore, Jarkko Nikula, Sascha Hauer, Andreas Kemnade,
Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Liam Girdwood, Mark Brown, Aaro Koskinen, Kevin Hilman,
Roger Quadros, Russell King, Daniel Thompson, Jingoo Han,
Helge Deller, Jaroslav Kysela, Takashi Iwai, Sen Wang,
Richard Fitzgerald, Arnd Bergmann, Srinivas Kandagatla,
Kuninori Morimoto, Charles Keepax, Niranjan H Y, letux-kernel,
devicetree, linux-kernel, linux-sound, linux-omap,
linux-arm-kernel, dri-devel, linux-fbdev, kernel, mfd
In-Reply-To: <3D58F644-3EAA-4BE7-88FB-1DCEAE7D5768@goldelico.com>
On 13/07/2026 10:26, H. Nikolaus Schaller wrote:
> Hi Krzysztof,
>
> Thank you for the feedback.
>
>> Am 13.07.2026 um 09:39 schrieb Krzysztof Kozlowski <krzk@kernel.org>:
>>
>> On Sat, Jul 11, 2026 at 08:01:48AM +0200, H. Nikolaus Schaller wrote:
>>> From: Grond <grond66@riseup.net>
>>>
>>> FIXME: not reflected in the bindings
>>>
>>> +Reguired properties:
>>> +For twl3040 REGEN signal:
>>> + - regulator-min-microvolt:
>>> + - Same meaning as in bindings/regulator/regulator.yaml, but must match
>>> + regulator-max-microvolt.
>>> + - regulator-max-microvolt:
>>> + - Same meaning as in bindings/regulator/regulator.yaml, but must match
>>> + regulator-min-microvolt.
>>> + - startup-delay-us:
>>> + - Same meaning as in bindings/regulator/fixed-regulator.yaml.
>>
>> I don't know what that means.
>>
>>>
>>> Signed-off-by: Grond <grond66@riseup.net>
>>
>> As explicitly written in submitting patches: we do not take anonymous
>> contributions.
>
> Thanks for pointing this out. In more than 10 years of contributing, I actually have never run into this specific issue before.
>
> To give some context: this is a contribution we received on our mailing list about 5 years ago. We do not know the author personally, but the code looks good and has been thoroughly tested by us. I kept their Signed-off-by because I didn't want to falsely claim authorship.
Take the authorship. You are allowed to do that by both DCO and common
sense (you are not doing something immoral, unless author shows
interest/hints to fix it by themself).
Commit msg also needs fixing/rewriting.
Beside, one more nit since there will be a new version:
A nit, subject: drop second/last, redundant "bindings for". The
"dt-bindings" prefix is already stating that these are bindings.
See also:
https://elixir.bootlin.com/linux/v7.1-rc7/source/Documentation/devicetree/bindings/submitting-patches.rst#L23
Also, please wrap your replies to mailing list wrapping style.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 09/16] ASoC: dt-bindings: add OpenPandora Sound Card
From: Krzysztof Kozlowski @ 2026-07-13 9:21 UTC (permalink / raw)
To: H. Nikolaus Schaller
Cc: Grond, Stefan Leichter, Grazvydas Ignotas, Tony Lindgren,
Ethan Nelson-Moore, Jarkko Nikula, Sascha Hauer, Andreas Kemnade,
Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Liam Girdwood, Mark Brown, Aaro Koskinen, Kevin Hilman,
Roger Quadros, Russell King, Daniel Thompson, Jingoo Han,
Helge Deller, Jaroslav Kysela, Takashi Iwai, Sen Wang,
Richard Fitzgerald, Arnd Bergmann, Srinivas Kandagatla,
Kuninori Morimoto, Charles Keepax, Niranjan H Y, letux-kernel,
devicetree, linux-kernel, linux-sound, linux-omap,
linux-arm-kernel, dri-devel, linux-fbdev, kernel, mfd
In-Reply-To: <BF2B6C4A-A4CC-48D7-9854-DC014950184B@goldelico.com>
On 13/07/2026 10:27, H. Nikolaus Schaller wrote:
> Hi Krzysztof,
>
> Thank you for the feedback.
>
>> Am 13.07.2026 um 09:37 schrieb Krzysztof Kozlowski <krzk@kernel.org>:
>>
>> On Sat, Jul 11, 2026 at 08:01:56AM +0200, H. Nikolaus Schaller wrote:
>>> The OpenPandora audio subsystem describes the routing links between the
>>> OMAP3 McBSP interface, the external PCM1773 DAC, and the TWL4030 audio
>>> codec, alongside amplifiers and power supplies.
>>>
>>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>>> ---
>>> .../sound/openpandora,omap3pandora-sound.yaml | 94 +++++++++++++++++++
>>> 1 file changed, 94 insertions(+)
>>> create mode 100644 Documentation/devicetree/bindings/sound/openpandora,omap3pandora-sound.yaml
>>>
>>> diff --git a/Documentation/devicetree/bindings/sound/openpandora,omap3pandora-sound.yaml b/Documentation/devicetree/bindings/sound/openpandora,omap3pandora-sound.yaml
>>> new file mode 100644
>>> index 0000000000000..d3c747e5c58d6
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/sound/openpandora,omap3pandora-sound.yaml
>>> @@ -0,0 +1,94 @@
>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>> +%YAML 1.2
>>> +---
>>> +$id: http://devicetree.org
>>> +$schema: http://devicetree.org
>>
>> This wasn't ever tested. Why?
>
> It does not emit any C compiler issues or runtime problems so it remained unnoticed.
I do not believe.
There is a clear warning from Rob's bot.
You just did not follow the process of building the code (compiler has
nothing to do here). Read submitting patches in DT dir and then writing
schema for further instructions. This is ABSOLUTE MINIMUM. We are not
your build test service.
>
> It was notified by the sahiko-bot and will be fixed in v2.
Sashiko has nothing to do with that.
You DID NOT BUILD the code.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 09/16] ASoC: dt-bindings: add OpenPandora Sound Card
From: H. Nikolaus Schaller @ 2026-07-13 9:38 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Grond, Stefan Leichter, Grazvydas Ignotas, Tony Lindgren,
Ethan Nelson-Moore, Jarkko Nikula, Sascha Hauer, Andreas Kemnade,
Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Liam Girdwood, Mark Brown, Aaro Koskinen, Kevin Hilman,
Roger Quadros, Russell King, Daniel Thompson, Jingoo Han,
Helge Deller, Jaroslav Kysela, Takashi Iwai, Sen Wang,
Richard Fitzgerald, Arnd Bergmann, Srinivas Kandagatla,
Kuninori Morimoto, Charles Keepax, Niranjan H Y, letux-kernel,
devicetree, linux-kernel, linux-sound, linux-omap,
linux-arm-kernel, dri-devel, linux-fbdev, kernel, mfd
In-Reply-To: <5667350d-cc3f-421d-899f-f353b05c5ea1@kernel.org>
> Am 13.07.2026 um 11:21 schrieb Krzysztof Kozlowski <krzk@kernel.org>:
>
> On 13/07/2026 10:27, H. Nikolaus Schaller wrote:
>> Hi Krzysztof,
>>
>> Thank you for the feedback.
>>
>>
>> It does not emit any C compiler issues or runtime problems so it remained unnoticed.
>
> I do not believe.
>
> There is a clear warning from Rob's bot.
>
> You just did not follow the process of building the code (compiler has
> nothing to do here). Read submitting patches in DT dir and then writing
> schema for further instructions. This is ABSOLUTE MINIMUM. We are not
> your build test service.
>
>>
>> It was notified by the sahiko-bot and will be fixed in v2.
>
> Sashiko has nothing to do with that.
>
> You DID NOT BUILD the code.
Hm. You are confusing me. I did "make" and did not get any errors...
And the resulting code works as intended.
Best regards and thanks,
Nikolaus
^ permalink raw reply
* Re: [PATCH 01/16] dt-bindings: twl-regulator: Add bindings for exposing ti,twl4030-regen
From: H. Nikolaus Schaller @ 2026-07-13 9:36 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Grond, Stefan Leichter, Grazvydas Ignotas, Tony Lindgren,
Ethan Nelson-Moore, Jarkko Nikula, Sascha Hauer, Andreas Kemnade,
Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Liam Girdwood, Mark Brown, Aaro Koskinen, Kevin Hilman,
Roger Quadros, Russell King, Daniel Thompson, Jingoo Han,
Helge Deller, Jaroslav Kysela, Takashi Iwai, Sen Wang,
Richard Fitzgerald, Arnd Bergmann, Srinivas Kandagatla,
Kuninori Morimoto, Charles Keepax, Niranjan H Y, letux-kernel,
devicetree, linux-kernel, linux-sound, linux-omap,
linux-arm-kernel, dri-devel, linux-fbdev, kernel, mfd
In-Reply-To: <17497012-97d8-4c52-89b8-f433179f1eda@kernel.org>
> Am 13.07.2026 um 11:19 schrieb Krzysztof Kozlowski <krzk@kernel.org>:
>
> On 13/07/2026 10:26, H. Nikolaus Schaller wrote:
>> Hi Krzysztof,
>>
>> Thank you for the feedback.
>>
>>> Am 13.07.2026 um 09:39 schrieb Krzysztof Kozlowski <krzk@kernel.org>:
>>>
>>> On Sat, Jul 11, 2026 at 08:01:48AM +0200, H. Nikolaus Schaller wrote:
>>>> From: Grond <grond66@riseup.net>
>>>>
>>>
>>>>
>>>> Signed-off-by: Grond <grond66@riseup.net>
>>>
>>> As explicitly written in submitting patches: we do not take anonymous
>>> contributions.
>>
>> Thanks for pointing this out. In more than 10 years of contributing, I actually have never run into this specific issue before.
>>
>> To give some context: this is a contribution we received on our mailing list about 5 years ago. We do not know the author personally, but the code looks good and has been thoroughly tested by us. I kept their Signed-off-by because I didn't want to falsely claim authorship.
>
> Take the authorship. You are allowed to do that by both DCO and common
> sense (you are not doing something immoral, unless author shows
> interest/hints to fix it by themself).
Ok, let's give him some days to respond. Thanks for the procedure
to resolve a potential deadlock.
>
> Commit msg also needs fixing/rewriting.
>
> Beside, one more nit since there will be a new version:
>
> A nit, subject: drop second/last, redundant "bindings for". The
> "dt-bindings" prefix is already stating that these are bindings.
Ok.
> Best regards,
> Krzysztof
^ permalink raw reply
* Re: [PATCH 09/16] ASoC: dt-bindings: add OpenPandora Sound Card
From: Krzysztof Kozlowski @ 2026-07-13 9:42 UTC (permalink / raw)
To: H. Nikolaus Schaller
Cc: Grond, Stefan Leichter, Grazvydas Ignotas, Tony Lindgren,
Ethan Nelson-Moore, Jarkko Nikula, Sascha Hauer, Andreas Kemnade,
Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Liam Girdwood, Mark Brown, Aaro Koskinen, Kevin Hilman,
Roger Quadros, Russell King, Daniel Thompson, Jingoo Han,
Helge Deller, Jaroslav Kysela, Takashi Iwai, Sen Wang,
Richard Fitzgerald, Arnd Bergmann, Srinivas Kandagatla,
Kuninori Morimoto, Charles Keepax, Niranjan H Y, letux-kernel,
devicetree, linux-kernel, linux-sound, linux-omap,
linux-arm-kernel, dri-devel, linux-fbdev, kernel, mfd
In-Reply-To: <8A0C119B-7885-4538-8D4F-AC60E5C91B7E@goldelico.com>
On 13/07/2026 11:38, H. Nikolaus Schaller wrote:
>
>
>> Am 13.07.2026 um 11:21 schrieb Krzysztof Kozlowski <krzk@kernel.org>:
>>
>> On 13/07/2026 10:27, H. Nikolaus Schaller wrote:
>>> Hi Krzysztof,
>>>
>>> Thank you for the feedback.
>>>
>>>
>>> It does not emit any C compiler issues or runtime problems so it remained unnoticed.
>>
>> I do not believe.
>>
>> There is a clear warning from Rob's bot.
>>
>> You just did not follow the process of building the code (compiler has
>> nothing to do here). Read submitting patches in DT dir and then writing
>> schema for further instructions. This is ABSOLUTE MINIMUM. We are not
>> your build test service.
>>
>>>
>>> It was notified by the sahiko-bot and will be fixed in v2.
>>
>> Sashiko has nothing to do with that.
>>
>> You DID NOT BUILD the code.
>
> Hm. You are confusing me. I did "make" and did not get any errors...
> And the resulting code works as intended.
'make' alone does not build that, just like other pieces of code they
have own - well documented and always expected - build processes.
I don't know what is confusing in my message, since I pointed you to
SPECIFIC guide telling you EXACTLY what to do (and which you did not do).
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH] fbdev: arkfb: Move a variable assignment behind a condition check in ics5342_init()
From: Markus Elfring @ 2026-07-13 10:18 UTC (permalink / raw)
To: linux-fbdev, dri-devel, Helge Deller, Kees Cook,
Uwe Kleine-König
Cc: LKML, kernel-janitors
From: Markus Elfring <elfring@users.sourceforge.net>
Date: Mon, 13 Jul 2026 12:12:52 +0200
The address of a data structure member was determined before
a corresponding null pointer check in the implementation of
the function “ics5342_init”.
Thus avoid the risk for undefined behaviour by moving the assignment
for the variable “info” behind a condition check.
This issue was detected by using the Coccinelle software.
Fixes: ede481f6dad47d40b7e561cfbc6c04286a9faf1a ("fbdev: arkfb: Cast ics5342_init() allocation type")
Cc: stable@vger.kernel.org
Signed-off-by: Markus Elfring <elfring@users.sourceforge.net>
---
drivers/video/fbdev/arkfb.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/video/fbdev/arkfb.c b/drivers/video/fbdev/arkfb.c
index 195dbf4a5142..9658f407b79a 100644
--- a/drivers/video/fbdev/arkfb.c
+++ b/drivers/video/fbdev/arkfb.c
@@ -432,11 +432,12 @@ static struct dac_ops ics5342_ops = {
static struct dac_info * ics5342_init(dac_read_regs_t drr, dac_write_regs_t dwr, void *data)
{
struct ics5342_info *ics_info = kzalloc_obj(struct ics5342_info);
- struct dac_info *info = &ics_info->dac;
+ struct dac_info *info;
if (!ics_info)
return NULL;
+ info = &ics_info->dac;
info->dacops = &ics5342_ops;
info->dac_read_regs = drr;
info->dac_write_regs = dwr;
--
2.55.0
^ permalink raw reply related
* [PATCH] fbdev: pvr2fb: correct user pointer annotation and sentinel initializer
From: Florian Fuchs @ 2026-07-13 10:16 UTC (permalink / raw)
To: Helge Deller, John Paul Adrian Glaubitz
Cc: Uwe Kleine-König, linux-fbdev, dri-devel, linux-kernel,
Florian Fuchs, kernel test robot
Add __user annotation to buf, as it is passed as a user pointer in
pin_user_pages_fast(). Use an empty initializer for the sentinel
board-table entry to avoid initializing a function pointer with an
integer literal.
Fixes: 5f566c0ac51c ("fbdev: pvr2fb: Fix leftover reference to ONCHIP_NR_DMA_CHANNELS")
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202607131247.fpQ6eTc7-lkp@intel.com/
Signed-off-by: Florian Fuchs <fuchsfl@gmail.com>
---
Not sure, if we do such changes, but as I received a test robot email, I
leave it up to you all :) (I also compiled and tested the change)
drivers/video/fbdev/pvr2fb.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/video/fbdev/pvr2fb.c b/drivers/video/fbdev/pvr2fb.c
index 9428716e2dc4..a6e7abca7a06 100644
--- a/drivers/video/fbdev/pvr2fb.c
+++ b/drivers/video/fbdev/pvr2fb.c
@@ -639,7 +639,7 @@ static irqreturn_t __maybe_unused pvr2fb_interrupt(int irq, void *dev_id)
}
#ifdef CONFIG_PVR2_DMA
-static ssize_t pvr2fb_write(struct fb_info *info, const char *buf,
+static ssize_t pvr2fb_write(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos)
{
unsigned long dst, start, end, len;
@@ -1077,7 +1077,7 @@ static struct pvr2_board {
#ifdef CONFIG_PCI
{ pvr2fb_pci_init, pvr2fb_pci_exit, "PCI PVR2" },
#endif
- { 0, },
+ { },
};
static int __init pvr2fb_init(void)
--
2.43.0
^ permalink raw reply related
* Re: [PATCH 09/16] ASoC: dt-bindings: add OpenPandora Sound Card
From: H. Nikolaus Schaller @ 2026-07-13 11:53 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Grond, Stefan Leichter, Grazvydas Ignotas, Tony Lindgren,
Ethan Nelson-Moore, Jarkko Nikula, Sascha Hauer, Andreas Kemnade,
Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Liam Girdwood, Mark Brown, Aaro Koskinen, Kevin Hilman,
Roger Quadros, Russell King, Daniel Thompson, Jingoo Han,
Helge Deller, Jaroslav Kysela, Takashi Iwai, Sen Wang,
Richard Fitzgerald, Arnd Bergmann, Srinivas Kandagatla,
Kuninori Morimoto, Charles Keepax, Niranjan H Y, letux-kernel,
devicetree, linux-kernel, linux-sound, linux-omap,
linux-arm-kernel, dri-devel, linux-fbdev, kernel, mfd
In-Reply-To: <741706c6-e6ff-4bc2-8ea1-28ebada6032c@kernel.org>
> Am 13.07.2026 um 11:42 schrieb Krzysztof Kozlowski <krzk@kernel.org>:
>
> On 13/07/2026 11:38, H. Nikolaus Schaller wrote:
>>
>>
>>> Am 13.07.2026 um 11:21 schrieb Krzysztof Kozlowski <krzk@kernel.org>:
>>>
>>> On 13/07/2026 10:27, H. Nikolaus Schaller wrote:
>>>> Hi Krzysztof,
>>>>
>>>> Thank you for the feedback.
>>>>
>>>>
>>>> It does not emit any C compiler issues or runtime problems so it remained unnoticed.
>>>
>>> I do not believe.
>>>
>>> There is a clear warning from Rob's bot.
>>>
>>> You just did not follow the process of building the code (compiler has
>>> nothing to do here). Read submitting patches in DT dir and then writing
>>> schema for further instructions. This is ABSOLUTE MINIMUM. We are not
>>> your build test service.
>>>
>>>>
>>>> It was notified by the sahiko-bot and will be fixed in v2.
>>>
>>> Sashiko has nothing to do with that.
>>>
>>> You DID NOT BUILD the code.
>>
>> Hm. You are confusing me. I did "make" and did not get any errors...
>> And the resulting code works as intended.
>
> 'make' alone does not build that, just like other pieces of code they
> have own - well documented and always expected - build processes.
>
> I don't know what is confusing in my message, since I pointed you to
> SPECIFIC guide telling you EXACTLY what to do (and which you did not do).
Confusing was the phrase "building the code". For my understanding
"building the code" is the process of building ("translating the code")
to get vmlinux or similar and a DTB and kernel modules.
As far as I understand the bindings they are used to check the DTS source
files against. So here is no translation of bindings into a real DTB that
finaly runs on a device. So I had assumed that schemas are "validated"
(not "built") by dt_binding_check.
This made me confused.
Back to topic:
It finally turned out that our script to run the required checks automatically
was no longer operational and silently ignored the validations. Therefore
nobody noticed before the bots did. Good to have a second and independent
check.
So sorry for the confusion we have introduced...
v2 will be checked as it previously was.
BR and thanks,
Nikolaus
^ permalink raw reply
* Re: [PATCH v2 00/13] mm: convert more vm_flags_t users to vma_flags_t
From: Andrew Morton @ 2026-07-14 2:25 UTC (permalink / raw)
To: Lorenzo Stoakes
Cc: David Hildenbrand, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Thomas Bogendoerfer, Benjamin LaHaise, Alexander Viro,
Christian Brauner, Jan Kara, Hugh Dickins, Baolin Wang, Jann Horn,
Pedro Falcato, Muchun Song, Oscar Salvador, Zi Yan, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Lucas Stach,
Russell King, Christian Gmeiner, Inki Dae, Seung-Woo Kim,
Kyungmin Park, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Jani Nikula, Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin,
Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Lyude Paul, Danilo Krummrich,
Tomi Valkeinen, Sandy Huang, Heiko Stübner, Andy Yan,
Thierry Reding, Mikko Perttunen, Jonathan Hunter, Gerd Hoffmann,
Dmitry Osipenko, Gurchetan Singh, Chia-I Wu, Zack Rusin,
Broadcom internal kernel review list, Matthew Brost,
Thomas Hellström, Oleksandr Andrushchenko, Helge Deller,
Kees Cook, Jaroslav Kysela, Takashi Iwai, Boris Brezillon,
Steven Price, Liviu Dudau, linux-mm, linux-kernel, linux-mips,
linux-aio, linux-fsdevel, linuxppc-dev, dri-devel, etnaviv,
linux-arm-kernel, linux-samsung-soc, intel-gfx, linux-arm-msm,
freedreno, nouveau, linux-rockchip, linux-tegra, virtualization,
intel-xe, xen-devel, linux-fbdev, linux-sound, Jani Nikula
In-Reply-To: <20260711-b4-vma-flags-mm-v2-0-0fa2357d5431@kernel.org>
On Sat, 11 Jul 2026 19:44:57 +0100 Lorenzo Stoakes <ljs@kernel.org> wrote:
> This series makes further progress in converting usage of the deprecated
> vm_flags_t type to its replacement, vma_flags_t.
>
> It focuses on mm, though updates some users of mm APIs also.
>
> It updates:
>
> * The core do_mmap() code path for VMA mapping.
> * Unmapped area logic.
> * The usage of mm->def_vma_flags.
> * VMA page protection bit logic.
> * General usage of VMA flags in core mm code, mlock, mprotect, mremap.
Added to mm-new, thanks.
And oh my, what a lot of pre-existing issues:
https://sashiko.dev/#/patchset/20260711-b4-vma-flags-mm-v2-0-0fa2357d5431@kernel.org
^ permalink raw reply
* Re: [PATCH v2 12/13] mm/mprotect: convert mprotect code to use vma_flags_t
From: Zi Yan @ 2026-07-14 2:46 UTC (permalink / raw)
To: Lorenzo Stoakes
Cc: Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Thomas Bogendoerfer, Benjamin LaHaise, Alexander Viro,
Christian Brauner, Jan Kara, Hugh Dickins, Baolin Wang, Jann Horn,
Pedro Falcato, Muchun Song, Oscar Salvador, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Lucas Stach,
Russell King, Christian Gmeiner, Inki Dae, Seung-Woo Kim,
Kyungmin Park, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Jani Nikula, Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin,
Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Lyude Paul, Danilo Krummrich,
Tomi Valkeinen, Sandy Huang, Heiko Stübner, Andy Yan,
Thierry Reding, Mikko Perttunen, Jonathan Hunter, Gerd Hoffmann,
Dmitry Osipenko, Gurchetan Singh, Chia-I Wu, Zack Rusin,
Broadcom internal kernel review list, Matthew Brost,
Thomas Hellström, Oleksandr Andrushchenko, Helge Deller,
Kees Cook, Jaroslav Kysela, Takashi Iwai, Boris Brezillon,
Steven Price, Liviu Dudau, linux-mm, linux-kernel, linux-mips,
linux-aio, linux-fsdevel, linuxppc-dev, dri-devel, etnaviv,
linux-arm-kernel, linux-samsung-soc, intel-gfx, linux-arm-msm,
freedreno, nouveau, linux-rockchip, linux-tegra, virtualization,
intel-xe, xen-devel, linux-fbdev, linux-sound
In-Reply-To: <20260711-b4-vma-flags-mm-v2-12-0fa2357d5431@kernel.org>
On 11 Jul 2026, at 14:45, Lorenzo Stoakes wrote:
> Replace use of the legacy vm_flags_t flags with vma_flags_t values
> throughout the mprotect logic.
>
> Note that we retain the legacy vm_flags_t bit shifting code in
> do_mprotect_pkey(), deferring a vma_flags_t approach to this for the time
> being.
>
> Additionally update comments to reflect the changes to be consistent.
>
> No functional change intended.
>
> Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> mm/mprotect.c | 16 ++++++++--------
> 1 file changed, 8 insertions(+), 8 deletions(-)
>
LGTM.
Reviewed-by: Zi Yan <ziy@nvidia.com>
Best Regards,
Yan, Zi
^ permalink raw reply
* Re: [PATCH v5 02/14] mfd: lm3533: Remove driver specific regmap wrappers
From: Svyatoslav Ryhel @ 2026-07-14 13:26 UTC (permalink / raw)
To: Johan Hovold
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
dri-devel, linux-leds, devicetree, linux-kernel, linux-iio,
linux-fbdev
In-Reply-To: <akeEd0GcHSqScGIl@hovoldconsulting.com>
пт, 3 лип. 2026 р. о 12:44 Johan Hovold <johan@kernel.org> пише:
>
> On Wed, Jun 17, 2026 at 11:00:19AM +0300, Svyatoslav Ryhel wrote:
> > Remove driver-specific regmap wrappers in favor of using regmap helpers
> > directly.
>
> The commit message should explain why you think this is a good idea.
>
Removing custom wrappers over standard regmap helpers is always a good
idea, that is self explanatory.
> Based on a quick look you lose the errnos that were logged on errors and
> also the register debugging provided by those helpers.
>
That is not true, regmap helpers will provide logging and you should
not include debug stuff into the final submitted driver unless you
have a extremely valid justification for this, debug logs should have
been removed once the driver development was completed.
> Johan
^ permalink raw reply
* Re: [PATCH v5 01/14] dt-bindings: leds: Document TI LM3533 LED controller
From: Svyatoslav Ryhel @ 2026-07-14 13:27 UTC (permalink / raw)
To: Johan Hovold
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
dri-devel, linux-leds, devicetree, linux-kernel, linux-iio,
linux-fbdev
In-Reply-To: <akeFmMQ_S1Y18hlu@hovoldconsulting.com>
пт, 3 лип. 2026 р. о 12:49 Johan Hovold <johan@kernel.org> пише:
>
> On Wed, Jun 17, 2026 at 11:00:18AM +0300, Svyatoslav Ryhel wrote:
> > Document the LM3533 - a complete power source for backlight, keypad and
> > indicator LEDs in smartphone handsets. The high-voltage inductive boost
> > converter provides the power for two series LED strings display backlight
> > and keypad functions.
> >
> > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > Reviewed-by: Jonathan Cameron <jic23@kernel.org> #for light sensor
> > Reviewed-by: Daniel Thompson (RISCstar) <danielt@kernel.org> #for backlight
> > Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
>
> > +examples:
> > + - |
> > + #include <dt-bindings/gpio/gpio.h>
> > + #include <dt-bindings/interrupt-controller/irq.h>
> > +
> > + i2c {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + led-controller@36 {
> > + compatible = "ti,lm3533";
> > + reg = <0x36>;
> > +
> > + enable-gpios = <&gpio 110 GPIO_ACTIVE_HIGH>;
> > + vin-supply = <&vdd_3v3_bat>;
> > +
> > + ti,boost-ovp-microvolt = <24000000>;
> > + ti,boost-freq-hz = <500000>;
> > +
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + backlight@0 {
> > + compatible = "ti,lm3533-backlight";
> > + reg = <0>;
> > +
> > + default-brightness = <113>;
> > +
> > + led-max-microamp = <23400>;
> > + led-sources = <0 1>;
> > + };
> > +
> > + led@2 {
> > + compatible = "ti,lm3533-leds";
> > + reg = <2>;
> > +
> > + led-max-microamp = <23400>;
> > + led-sources = <0 1>;
> > + };
> > +
> > + led@4 {
> > + compatible = "ti,lm3533-leds";
> > + reg = <4>;
> > +
> > + led-max-microamp = <23400>;
> > + led-sources = <2>;
> > + };
> > +
> > + led@5 {
> > + compatible = "ti,lm3533-leds";
> > + reg = <5>;
> > +
> > + led-max-microamp = <23400>;
> > + led-sources = <3 4>;
> > + };
> > +
> > + light-sensor {
>
> Doesn't the binding (or dts) checker complain about this node not having
> an address?
>
No it does not
> > + compatible = "ti,lm3533-als";
> > +
> > + interrupt-parent = <&gpio>;
> > + interrupts = <80 IRQ_TYPE_LEVEL_LOW>;
> > +
> > + ti,pwm-mode;
> > + };
> > + };
> > + };
> > +...
>
> Johan
^ permalink raw reply
* Re: [PATCH v5 04/14] mfd: lm3533: Pass only regmap and light sensor presence to child devices
From: Svyatoslav Ryhel @ 2026-07-14 13:28 UTC (permalink / raw)
To: Johan Hovold
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
dri-devel, linux-leds, devicetree, linux-kernel, linux-iio,
linux-fbdev
In-Reply-To: <akeF_VrijPbebz3I@hovoldconsulting.com>
пт, 3 лип. 2026 р. о 12:50 Johan Hovold <johan@kernel.org> пише:
>
> On Wed, Jun 17, 2026 at 11:00:21AM +0300, Svyatoslav Ryhel wrote:
> > Instead of passing the entire lm3533 core data structure, only pass the
> > regmap and the light sensor presence flag to child devices.
>
> Again, why?
>
Because none of the child cells needs entire parents private
structure, regmap is all that is used.
> Johan
^ permalink raw reply
* Re: [PATCH v5 05/14] iio: light: lm3533-als: Remove redundant pdata helpers
From: Svyatoslav Ryhel @ 2026-07-14 13:31 UTC (permalink / raw)
To: Johan Hovold
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
dri-devel, linux-leds, devicetree, linux-kernel, linux-iio,
linux-fbdev
In-Reply-To: <akeHC-OA8tqM941f@hovoldconsulting.com>
пт, 3 лип. 2026 р. о 12:55 Johan Hovold <johan@kernel.org> пише:
>
> On Wed, Jun 17, 2026 at 11:00:22AM +0300, Svyatoslav Ryhel wrote:
> > The lm3533_als_set_input_mode() and lm3533_als_set_resistor() functions
> > are used only in lm3533_als_setup(). Incorporate their code into
> > lm3533_als_setup() directly to simplify driver readability.
>
> That's a debatable claim.
>
Adding helpers to wrap custom regmap wrappers seems redundant twice.
> > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > ---
> > drivers/iio/light/lm3533-als.c | 56 ++++++++++------------------------
> > 1 file changed, 16 insertions(+), 40 deletions(-)
> >
> > diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
> > index a9af8e2b965f..69bac1b202f1 100644
> > --- a/drivers/iio/light/lm3533-als.c
> > +++ b/drivers/iio/light/lm3533-als.c
>
> > -static int lm3533_als_setup(struct lm3533_als *als,
> > - const struct lm3533_als_platform_data *pdata)
> > -{
> > - int ret;
> > -
> > - ret = lm3533_als_set_input_mode(als, pdata->pwm_mode);
> > + pdata->r_select);
> > if (ret)
> > - return ret;
> > -
> > - /* ALS input is always high impedance in PWM-mode. */
> > - if (!pdata->pwm_mode) {
> > - ret = lm3533_als_set_resistor(als, pdata->r_select);
> > - if (ret)
> > - return ret;
> > - }
> > + return dev_err_probe(dev, ret, "failed to set resistor\n");
> > return 0;
> > }
>
> There's nothing hard to read about the above. To the contrary the logic
> is more obvious this way.
>
> You also remove the high-impedance comment for no good reason.
>
Removing one level of indentations without obscuring readability is
always a good idea.
> Johan
^ permalink raw reply
* Re: [PATCH v5 06/14] mfd: lm3533-core: Remove redundant pdata helpers
From: Svyatoslav Ryhel @ 2026-07-14 13:32 UTC (permalink / raw)
To: Johan Hovold
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
dri-devel, linux-leds, devicetree, linux-kernel, linux-iio,
linux-fbdev
In-Reply-To: <akeHfV45fs_wabFl@hovoldconsulting.com>
пт, 3 лип. 2026 р. о 12:57 Johan Hovold <johan@kernel.org> пише:
>
> On Wed, Jun 17, 2026 at 11:00:23AM +0300, Svyatoslav Ryhel wrote:
> > The lm3533_set_boost_freq() and lm3533_set_boost_ovp() functions are used
> > only in lm3533_device_setup(), which in turn is only called by
> > lm3533_device_init(). Incorporate their code directly into
> > lm3533_device_init() to simplify driver readability.
>
> Again, this isn't needed. Why are you removing abstraction that improve
> readability?
>
Adding helpers to wrap custom regmap wrappers seems redundant. Regmap
operations are pretty self explanatory without need in one-time-use
helpers.
> Johan
^ permalink raw reply
* Re: [PATCH v5 08/14] mfd: lm3533: Convert to use OF bindings
From: Svyatoslav Ryhel @ 2026-07-14 13:57 UTC (permalink / raw)
To: Johan Hovold
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
dri-devel, linux-leds, devicetree, linux-kernel, linux-iio,
linux-fbdev
In-Reply-To: <akeXAOpb13hupUGM@hovoldconsulting.com>
пт, 3 лип. 2026 р. о 14:03 Johan Hovold <johan@kernel.org> пише:
>
> On Wed, Jun 17, 2026 at 11:00:25AM +0300, Svyatoslav Ryhel wrote:
> > Since there are no users of this driver via platform data, remove the
> > platform data support and switch to using Device Tree bindings.
> >
> > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > Reviewed-by: Daniel Thompson (RISCstar) <danielt@kernel.org> #for backlight
> > ---
> > drivers/iio/light/lm3533-als.c | 67 +++++---
> > drivers/leds/leds-lm3533.c | 50 ++++--
> > drivers/mfd/lm3533-core.c | 236 ++++++++++++----------------
> > drivers/mfd/lm3533-ctrlbank.c | 5 -
> > drivers/video/backlight/lm3533_bl.c | 55 +++++--
> > include/linux/mfd/lm3533.h | 52 +-----
> > 6 files changed, 220 insertions(+), 245 deletions(-)
>
> > static int lm3533_als_probe(struct platform_device *pdev)
> > {
> > - const struct lm3533_als_platform_data *pdata;
> > struct lm3533 *lm3533;
> > struct lm3533_als *als;
> > struct iio_dev *indio_dev;
> > @@ -803,12 +817,6 @@ static int lm3533_als_probe(struct platform_device *pdev)
> > if (!lm3533)
> > return -EINVAL;
> >
> > - pdata = dev_get_platdata(&pdev->dev);
> > - if (!pdata) {
> > - dev_err(&pdev->dev, "no platform data\n");
> > - return -EINVAL;
> > - }
> > -
> > indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*als));
> > if (!indio_dev)
> > return -ENOMEM;
> > @@ -817,25 +825,27 @@ static int lm3533_als_probe(struct platform_device *pdev)
> > indio_dev->channels = lm3533_als_channels;
> > indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> > indio_dev->name = dev_name(&pdev->dev);
> > - iio_device_set_parent(indio_dev, pdev->dev.parent);
>
> Why are you reparenting the iio device here?
>
Because every cell has its own binding now and using phandle to parent
when device has its own node is not a good practice.
> That's an ABI break.
>
This driver does not have any active users in the kernel and no
activity for more then 2 years.
> > +static const struct of_device_id lm3533_als_match_table[] = {
> > + { .compatible = "ti,lm3533-als" },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(of, lm3533_als_match_table);
> > +
> > static struct platform_driver lm3533_als_driver = {
> > .driver = {
> > .name = "lm3533-als",
> > + .of_match_table = lm3533_als_match_table,
> > },
> > .probe = lm3533_als_probe,
> > .remove = lm3533_als_remove,
>
> You should also remove the platform module alias below.
>
Why?
> > diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
> > index 0cb0585eb960..ed810c23f30f 100644
> > --- a/drivers/leds/leds-lm3533.c
> > +++ b/drivers/leds/leds-lm3533.c
> > @@ -10,8 +10,10 @@
> > #include <linux/module.h>
> > #include <linux/leds.h>
> > #include <linux/mfd/core.h>
> > +#include <linux/mod_devicetable.h>
> > #include <linux/mutex.h>
> > #include <linux/platform_device.h>
> > +#include <linux/property.h>
> > #include <linux/regmap.h>
> > #include <linux/slab.h>
> >
> > @@ -50,6 +52,9 @@ struct lm3533_led {
> > struct mutex mutex;
> > unsigned long flags;
> >
> > + u32 max_current;
> > + u32 pwm;
> > +
> > bool have_als;
> > };
> >
> > @@ -616,22 +621,20 @@ static const struct attribute_group *lm3533_led_attribute_groups[] = {
> > NULL
> > };
> >
> > -static int lm3533_led_setup(struct lm3533_led *led,
> > - struct lm3533_led_platform_data *pdata)
> > +static int lm3533_led_setup(struct lm3533_led *led)
> > {
> > int ret;
> >
> > - ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
> > + ret = lm3533_ctrlbank_set_max_current(&led->cb, led->max_current);
> > if (ret)
> > return ret;
> >
> > - return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
> > + return lm3533_ctrlbank_set_pwm(&led->cb, led->pwm);
> > }
> >
> > static int lm3533_led_probe(struct platform_device *pdev)
> > {
> > struct lm3533 *lm3533;
> > - struct lm3533_led_platform_data *pdata;
> > struct lm3533_led *led;
> > int ret;
> >
> > @@ -641,12 +644,6 @@ static int lm3533_led_probe(struct platform_device *pdev)
> > if (!lm3533)
> > return -EINVAL;
> >
> > - pdata = dev_get_platdata(&pdev->dev);
> > - if (!pdata) {
> > - dev_err(&pdev->dev, "no platform data\n");
> > - return -EINVAL;
> > - }
> > -
> > if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
> > dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
> > return -EINVAL;
> > @@ -659,8 +656,6 @@ static int lm3533_led_probe(struct platform_device *pdev)
> > led->regmap = lm3533->regmap;
> > led->have_als = lm3533->have_als;
> >
> > - led->cdev.name = pdata->name;
> > - led->cdev.default_trigger = pdata->default_trigger;
> > led->cdev.brightness_set_blocking = lm3533_led_set;
> > led->cdev.brightness_get = lm3533_led_get;
> > led->cdev.blink_set = lm3533_led_blink_set;
> > @@ -668,6 +663,15 @@ static int lm3533_led_probe(struct platform_device *pdev)
> > led->cdev.groups = lm3533_led_attribute_groups;
> > led->id = pdev->id;
> >
> > + led->cdev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s-%d",
> > + pdev->name, led->id);
>
> Is "led-2", etc. unique enough here?
>
lm3533-leds-2
> > + if (!led->cdev.name)
> > + return -ENOMEM;
> > +
> > + led->cdev.default_trigger = "none";
> > + device_property_read_string(&pdev->dev, "linux,default-trigger",
> > + &led->cdev.default_trigger);
> > +
> > mutex_init(&led->mutex);
> >
> > /* The class framework makes a callback to get brightness during
> > @@ -680,15 +684,22 @@ static int lm3533_led_probe(struct platform_device *pdev)
> >
> > platform_set_drvdata(pdev, led);
> >
> > - ret = led_classdev_register(pdev->dev.parent, &led->cdev);
> > + ret = led_classdev_register(&pdev->dev, &led->cdev);
>
> Here too you appear to be reparenting the class devices.
>
> > if (ret) {
> > - dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
> > + dev_err(&pdev->dev, "failed to register LED %d\n", led->id);
>
> This does not seem to be necessary.
>
Agreed.
> > return ret;
> > }
> >
> > led->cb.dev = led->cdev.dev;
> >
> > - ret = lm3533_led_setup(led, pdata);
> > + device_property_read_u32(&pdev->dev, "led-max-microamp",
> > + &led->max_current);
> > + led->max_current = clamp(led->max_current, LM3533_MAX_CURRENT_MIN,
> > + LM3533_MAX_CURRENT_MAX);
>
> Why clamp instead of having lm3533_led_setup() fail below?
>
According to OF schema default lower margin is set to
LM3533_MAX_CURRENT_MIN so clamping seems a good option here, even
though it will clamp max value.
> > +
> > + device_property_read_u32(&pdev->dev, "ti,pwm-config-mask", &led->pwm);
> > +
> > + ret = lm3533_led_setup(led);
> > if (ret)
> > goto err_deregister;
> >
> > @@ -725,9 +736,16 @@ static void lm3533_led_shutdown(struct platform_device *pdev)
> > lm3533_led_set(&led->cdev, LED_OFF); /* disable blink */
> > }
> >
> > +static const struct of_device_id lm3533_led_match_table[] = {
> > + { .compatible = "ti,lm3533-leds" },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(of, lm3533_led_match_table);
> > +
> > static struct platform_driver lm3533_led_driver = {
> > .driver = {
> > .name = "lm3533-leds",
> > + .of_match_table = lm3533_led_match_table,
> > },
> > .probe = lm3533_led_probe,
> > .remove = lm3533_led_remove,
>
> Remove platform alias below as well.
>
Why?
> > diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c
> > index b03a3ae96c10..a5aa7da9668b 100644
> > --- a/drivers/mfd/lm3533-core.c
> > +++ b/drivers/mfd/lm3533-core.c
> > @@ -14,19 +14,26 @@
> > #include <linux/gpio/consumer.h>
> > #include <linux/i2c.h>
> > #include <linux/mfd/core.h>
> > +#include <linux/mod_devicetable.h>
> > +#include <linux/property.h>
> > #include <linux/regmap.h>
> > #include <linux/seq_file.h>
> > #include <linux/slab.h>
> > #include <linux/uaccess.h>
> > +#include <linux/units.h>
> >
> > #include <linux/mfd/lm3533.h>
> >
> >
> > #define LM3533_BOOST_OVP_MASK 0x06
> > #define LM3533_BOOST_OVP_SHIFT 1
> > +#define LM3533_BOOST_OVP_MIN (16 * MICRO)
> > +#define LM3533_BOOST_OVP_MAX (40 * MICRO)
> >
> > #define LM3533_BOOST_FREQ_MASK 0x01
> > #define LM3533_BOOST_FREQ_SHIFT 0
> > +#define LM3533_BOOST_FREQ_MIN (500 * HZ_PER_KHZ)
> > +#define LM3533_BOOST_FREQ_MAX (1000 * HZ_PER_KHZ)
> >
> > #define LM3533_BL_ID_MASK 1
> > #define LM3533_LED_ID_MASK 3
> > @@ -35,6 +42,7 @@
> >
> > #define LM3533_HVLED_ID_MAX 2
> > #define LM3533_LVLED_ID_MAX 5
> > +#define LM3533_CELLS_MAX 7
> >
> > #define LM3533_REG_OUTPUT_CONF1 0x10
> > #define LM3533_REG_OUTPUT_CONF2 0x11
> > @@ -42,44 +50,6 @@
> >
> > #define LM3533_REG_MAX 0xb2
> >
> > -
> > -static struct mfd_cell lm3533_als_devs[] = {
> > - {
> > - .name = "lm3533-als",
> > - .id = -1,
> > - },
> > -};
> > -
> > -static struct mfd_cell lm3533_bl_devs[] = {
> > - {
> > - .name = "lm3533-backlight",
> > - .id = 0,
> > - },
> > - {
> > - .name = "lm3533-backlight",
> > - .id = 1,
> > - },
> > -};
> > -
> > -static struct mfd_cell lm3533_led_devs[] = {
> > - {
> > - .name = "lm3533-leds",
> > - .id = 0,
> > - },
> > - {
> > - .name = "lm3533-leds",
> > - .id = 1,
> > - },
> > - {
> > - .name = "lm3533-leds",
> > - .id = 2,
> > - },
> > - {
> > - .name = "lm3533-leds",
> > - .id = 3,
> > - },
> > -};
> > -
> > /*
> > * HVLED output config -- output hvled controlled by backlight bl
> > */
> > @@ -301,125 +271,91 @@ static const struct attribute_group *lm3533_attribute_groups[] = {
> > NULL,
> > };
> >
> > -static int lm3533_device_als_init(struct lm3533 *lm3533)
> > -{
> > - struct lm3533_platform_data *pdata = dev_get_platdata(lm3533->dev);
> > - int ret;
> > -
> > - if (!pdata->als)
> > - return 0;
> > -
> > - lm3533_als_devs[0].platform_data = pdata->als;
> > - lm3533_als_devs[0].pdata_size = sizeof(*pdata->als);
> > -
> > - ret = mfd_add_devices(lm3533->dev, 0, lm3533_als_devs, 1, NULL,
> > - 0, NULL);
> > - if (ret) {
> > - dev_err(lm3533->dev, "failed to add ALS device\n");
> > - return ret;
> > - }
> > -
> > - lm3533->have_als = 1;
> > -
> > - return 0;
> > -}
> > -
> > -static int lm3533_device_bl_init(struct lm3533 *lm3533)
> > -{
> > - struct lm3533_platform_data *pdata = dev_get_platdata(lm3533->dev);
> > - int i;
> > - int ret;
> > -
> > - if (!pdata->backlights || pdata->num_backlights == 0)
> > - return 0;
> > -
> > - if (pdata->num_backlights > ARRAY_SIZE(lm3533_bl_devs))
> > - pdata->num_backlights = ARRAY_SIZE(lm3533_bl_devs);
> > -
> > - for (i = 0; i < pdata->num_backlights; ++i) {
> > - lm3533_bl_devs[i].platform_data = &pdata->backlights[i];
> > - lm3533_bl_devs[i].pdata_size = sizeof(pdata->backlights[i]);
> > - }
> > -
> > - ret = mfd_add_devices(lm3533->dev, 0, lm3533_bl_devs,
> > - pdata->num_backlights, NULL, 0, NULL);
> > - if (ret) {
> > - dev_err(lm3533->dev, "failed to add backlight devices\n");
> > - return ret;
> > - }
> > -
> > - lm3533->have_backlights = 1;
> > -
> > - return 0;
> > -}
> > -
> > -static int lm3533_device_led_init(struct lm3533 *lm3533)
> > -{
> > - struct lm3533_platform_data *pdata = dev_get_platdata(lm3533->dev);
> > - int i;
> > - int ret;
> > -
> > - if (!pdata->leds || pdata->num_leds == 0)
> > - return 0;
> > -
> > - if (pdata->num_leds > ARRAY_SIZE(lm3533_led_devs))
> > - pdata->num_leds = ARRAY_SIZE(lm3533_led_devs);
> > -
> > - for (i = 0; i < pdata->num_leds; ++i) {
> > - lm3533_led_devs[i].platform_data = &pdata->leds[i];
> > - lm3533_led_devs[i].pdata_size = sizeof(pdata->leds[i]);
> > - }
> > -
> > - ret = mfd_add_devices(lm3533->dev, 0, lm3533_led_devs,
> > - pdata->num_leds, NULL, 0, NULL);
> > - if (ret) {
> > - dev_err(lm3533->dev, "failed to add LED devices\n");
> > - return ret;
> > - }
> > -
> > - lm3533->have_leds = 1;
> > -
> > - return 0;
> > -}
> > -
> > static int lm3533_device_init(struct lm3533 *lm3533)
> > {
> > - struct lm3533_platform_data *pdata = dev_get_platdata(lm3533->dev);
> > + struct device *dev = lm3533->dev;
> > + struct mfd_cell *lm3533_devices;
> > + u32 count = 0, reg, nchilds;
>
> Don't mix multiple declarations with initialisation like this.
>
Checkpatch does not complain on style issue, hence this is not prohibited.
> > int ret;
> >
> > - dev_dbg(lm3533->dev, "%s\n", __func__);
> > + nchilds = device_get_child_node_count(dev);
> > + if (!nchilds || nchilds > LM3533_CELLS_MAX)
> > + return dev_err_probe(dev, -ENODEV,
> > + "num of child nodes is not supported\n");
> >
> > - if (!pdata) {
> > - dev_err(lm3533->dev, "no platform data\n");
> > - return -EINVAL;
> > - }
> > + lm3533_devices = devm_kcalloc(dev, nchilds, sizeof(*lm3533_devices),
> > + GFP_KERNEL);
> > + if (!lm3533_devices)
> > + return -ENOMEM;
> >
> > - lm3533->hwen = devm_gpiod_get(lm3533->dev, NULL, GPIOD_OUT_LOW);
> > - if (IS_ERR(lm3533->hwen))
> > - return dev_err_probe(lm3533->dev, PTR_ERR(lm3533->hwen), "failed to request HWEN GPIO\n");
> > - gpiod_set_consumer_name(lm3533->hwen, "lm3533-hwen");
> > + device_for_each_child_node_scoped(dev, child) {
> > + if (count >= nchilds)
> > + break;
>
> How could count be larger than nchilds?
>
Only if the tree is malformed, hence this check was added.
> > +
> > + if (fwnode_device_is_compatible(child, "ti,lm3533-als")) {
> > + lm3533_devices[count].name = "lm3533-als";
> > + lm3533_devices[count].of_compatible = "ti,lm3533-als";
> > + lm3533_devices[count].id = PLATFORM_DEVID_NONE;
> > +
> > + lm3533->have_als = true;
> > + count++;
> > + } else if (fwnode_device_is_compatible(child, "ti,lm3533-backlight")) {
> > + ret = fwnode_property_read_u32(child, "reg", ®);
> > + if (ret || reg >= LM3533_HVLED_ID_MAX) {
> > + dev_err(dev, "invalid backlight node %pfw\n", child);
> > + continue;
> > + }
> > +
> > + lm3533_devices[count].name = "lm3533-backlight";
> > + lm3533_devices[count].of_compatible = "ti,lm3533-backlight";
> > + lm3533_devices[count].id = reg;
> > + lm3533_devices[count].of_reg = reg;
> > + lm3533_devices[count].use_of_reg = true;
> > +
> > + lm3533->have_backlights = true;
> > + count++;
> > + } else if (fwnode_device_is_compatible(child, "ti,lm3533-leds")) {
> > + ret = fwnode_property_read_u32(child, "reg", ®);
> > + if (ret || reg < LM3533_HVLED_ID_MAX ||
> > + reg > LM3533_LVLED_ID_MAX) {
> > + dev_err(dev, "invalid LED node %pfw\n", child);
> > + continue;
> > + }
> > +
> > + lm3533_devices[count].name = "lm3533-leds";
> > + lm3533_devices[count].of_compatible = "ti,lm3533-leds";
> > + lm3533_devices[count].id = reg - LM3533_HVLED_ID_MAX;
> > + lm3533_devices[count].of_reg = reg;
> > + lm3533_devices[count].use_of_reg = true;
> > +
> > + lm3533->have_leds = true;
> > + count++;
> > + }
> > + }
>
> Why do you need the above at all? Shouldn't you be able to just use
> of_platform_populate().
>
of_platform_populate() is not a part of mfd framework.
> >
> > lm3533_enable(lm3533);
> >
> > ret = regmap_update_bits(lm3533->regmap, LM3533_REG_BOOST_PWM,
> > LM3533_BOOST_FREQ_MASK,
> > - pdata->boost_freq << LM3533_BOOST_FREQ_SHIFT);
> > + lm3533->boost_freq << LM3533_BOOST_FREQ_SHIFT);
> > if (ret) {
> > - dev_err(lm3533->dev, "failed to set boost frequency\n");
> > + dev_err(dev, "failed to set boost frequency\n");
> > goto err_disable;
> > }
> >
> > ret = regmap_update_bits(lm3533->regmap, LM3533_REG_BOOST_PWM,
> > LM3533_BOOST_OVP_MASK,
> > - pdata->boost_ovp << LM3533_BOOST_OVP_SHIFT);
> > + lm3533->boost_ovp << LM3533_BOOST_OVP_SHIFT);
> > if (ret) {
> > - dev_err(lm3533->dev, "failed to set boost ovp\n");
> > + dev_err(dev, "failed to set boost ovp\n");
> > goto err_disable;
> > }
> >
> > - lm3533_device_als_init(lm3533);
> > - lm3533_device_bl_init(lm3533);
> > - lm3533_device_led_init(lm3533);
> > + ret = mfd_add_devices(dev, 0, lm3533_devices, count, NULL, 0, NULL);
> > + if (ret) {
> > + dev_err(dev, "failed to add MFD devices: %d\n", ret);
> > + goto err_disable;
> > + }
> >
> > return 0;
> >
> > @@ -504,7 +440,26 @@ static int lm3533_i2c_probe(struct i2c_client *i2c)
> > return PTR_ERR(lm3533->regmap);
> >
> > lm3533->dev = &i2c->dev;
> > - lm3533->irq = i2c->irq;
> > +
> > + lm3533->hwen = devm_gpiod_get_optional(lm3533->dev, "enable",
> > + GPIOD_OUT_LOW);
> > + if (IS_ERR(lm3533->hwen))
> > + return dev_err_probe(lm3533->dev, PTR_ERR(lm3533->hwen),
> > + "failed to get HWEN GPIO\n");
>
> Please use brackets around multline statements for readability
> throughout.
>
Checkpatch does not complain on style issue, hence this is not prohibited.
> > +
> > + device_property_read_u32(lm3533->dev, "ti,boost-ovp-microvolt",
> > + &lm3533->boost_ovp);
> > +
> > + lm3533->boost_ovp = clamp(lm3533->boost_ovp, LM3533_BOOST_OVP_MIN,
> > + LM3533_BOOST_OVP_MAX);
> > + lm3533->boost_ovp = lm3533->boost_ovp / (8 * MICRO) - 2;
> > +
> > + device_property_read_u32(lm3533->dev, "ti,boost-freq-hz",
> > + &lm3533->boost_freq);
> > +
> > + lm3533->boost_freq = clamp(lm3533->boost_freq, LM3533_BOOST_FREQ_MIN,
> > + LM3533_BOOST_FREQ_MAX);
> > + lm3533->boost_freq = lm3533->boost_freq / (500 * KILO) - 1;
>
> Again, why clamp instead of failing probe?
>
According to OF schema default lower margin is set to
LM3533_BOOST_FREQ_MIN so clamping seems a good option here, even
though it will clamp max value.
> > return lm3533_device_init(lm3533);
> > }
> > @@ -518,6 +473,12 @@ static void lm3533_i2c_remove(struct i2c_client *i2c)
> > lm3533_device_exit(lm3533);
> > }
> >
> > +static const struct of_device_id lm3533_match_table[] = {
> > + { .compatible = "ti,lm3533" },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(of, lm3533_match_table);
> > +
> > static const struct i2c_device_id lm3533_i2c_ids[] = {
> > { "lm3533" },
> > { }
> > @@ -528,6 +489,7 @@ static struct i2c_driver lm3533_i2c_driver = {
> > .driver = {
> > .name = "lm3533",
> > .dev_groups = lm3533_attribute_groups,
> > + .of_match_table = lm3533_match_table,
> > },
> > .id_table = lm3533_i2c_ids,
> > .probe = lm3533_i2c_probe,
> > diff --git a/drivers/mfd/lm3533-ctrlbank.c b/drivers/mfd/lm3533-ctrlbank.c
> > index 91e13cfa3cf0..3aab8ece4e8c 100644
> > --- a/drivers/mfd/lm3533-ctrlbank.c
> > +++ b/drivers/mfd/lm3533-ctrlbank.c
> > @@ -13,11 +13,6 @@
> >
> > #include <linux/mfd/lm3533.h>
> >
> > -
> > -#define LM3533_MAX_CURRENT_MIN 5000
> > -#define LM3533_MAX_CURRENT_MAX 29800
> > -#define LM3533_MAX_CURRENT_STEP 800
> > -
> > #define LM3533_PWM_MAX 0x3f
> >
> > #define LM3533_REG_PWM_BASE 0x14
> > diff --git a/drivers/video/backlight/lm3533_bl.c b/drivers/video/backlight/lm3533_bl.c
> > index 9ef171d3aaea..2c24647fc17a 100644
> > --- a/drivers/video/backlight/lm3533_bl.c
> > +++ b/drivers/video/backlight/lm3533_bl.c
> > @@ -9,7 +9,9 @@
> >
> > #include <linux/module.h>
> > #include <linux/init.h>
> > +#include <linux/mod_devicetable.h>
> > #include <linux/platform_device.h>
> > +#include <linux/property.h>
> > #include <linux/backlight.h>
> > #include <linux/regmap.h>
> > #include <linux/slab.h>
> > @@ -29,6 +31,9 @@ struct lm3533_bl {
> > struct backlight_device *bd;
> > int id;
> >
> > + u32 max_current;
> > + u32 pwm;
> > +
> > bool have_als;
> > };
> >
> > @@ -242,25 +247,25 @@ static const struct attribute_group *lm3533_bl_attribute_groups[] = {
> > NULL,
> > };
> >
> > -static int lm3533_bl_setup(struct lm3533_bl *bl,
> > - struct lm3533_bl_platform_data *pdata)
> > +static int lm3533_bl_setup(struct lm3533_bl *bl)
> > {
> > int ret;
> >
> > - ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
> > + ret = lm3533_ctrlbank_set_max_current(&bl->cb, bl->max_current);
> > if (ret)
> > return ret;
> >
> > - return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
> > + return lm3533_ctrlbank_set_pwm(&bl->cb, bl->pwm);
> > }
> >
> > static int lm3533_bl_probe(struct platform_device *pdev)
> > {
> > struct lm3533 *lm3533;
> > - struct lm3533_bl_platform_data *pdata;
> > struct lm3533_bl *bl;
> > struct backlight_device *bd;
> > struct backlight_properties props;
> > + char *name = NULL;
> > + u32 default_brightness = LM3533_BL_MAX_BRIGHTNESS;
> > int ret;
> >
> > dev_dbg(&pdev->dev, "%s\n", __func__);
> > @@ -269,12 +274,6 @@ static int lm3533_bl_probe(struct platform_device *pdev)
> > if (!lm3533)
> > return -EINVAL;
> >
> > - pdata = dev_get_platdata(&pdev->dev);
> > - if (!pdata) {
> > - dev_err(&pdev->dev, "no platform data\n");
> > - return -EINVAL;
> > - }
> > -
> > if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
> > dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
> > return -EINVAL;
> > @@ -292,13 +291,21 @@ static int lm3533_bl_probe(struct platform_device *pdev)
> > bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
> > bl->cb.dev = NULL; /* until registered */
> >
> > + name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s-%d",
> > + pdev->name, pdev->id);
>
> Unique enough (e.g. backlight-0)?
>
lm3533-backlight-0
> > + if (!name)
> > + return -ENOMEM;
> > +
> > + device_property_read_u32(&pdev->dev, "default-brightness",
> > + &default_brightness);
> > +
> > memset(&props, 0, sizeof(props));
> > props.type = BACKLIGHT_RAW;
> > props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
> > - props.brightness = pdata->default_brightness;
> > - bd = devm_backlight_device_register(&pdev->dev, pdata->name,
> > - pdev->dev.parent, bl, &lm3533_bl_ops,
> > - &props);
> > + props.brightness = default_brightness;
> > +
> > + bd = devm_backlight_device_register(&pdev->dev, name, &pdev->dev,
> > + bl, &lm3533_bl_ops, &props);
>
> Here too you are reparenting, which results in an ABI break.
>
> > if (IS_ERR(bd)) {
> > dev_err(&pdev->dev, "failed to register backlight device\n");
> > return PTR_ERR(bd);
> > @@ -309,12 +316,19 @@ static int lm3533_bl_probe(struct platform_device *pdev)
> >
> > platform_set_drvdata(pdev, bl);
> >
> > - backlight_update_status(bd);
> > + device_property_read_u32(&pdev->dev, "led-max-microamp",
> > + &bl->max_current);
> > + bl->max_current = clamp(bl->max_current, LM3533_MAX_CURRENT_MIN,
> > + LM3533_MAX_CURRENT_MAX);
>
> Clamping instead of failing.
>
> >
> > - ret = lm3533_bl_setup(bl, pdata);
> > + device_property_read_u32(&pdev->dev, "ti,pwm-config-mask", &bl->pwm);
> > +
> > + ret = lm3533_bl_setup(bl);
> > if (ret)
> > return ret;
> >
> > + backlight_update_status(bd);
> > +
> > ret = lm3533_ctrlbank_enable(&bl->cb);
> > if (ret)
> > return ret;
> > @@ -366,11 +380,18 @@ static void lm3533_bl_shutdown(struct platform_device *pdev)
> > lm3533_ctrlbank_disable(&bl->cb);
> > }
> >
> > +static const struct of_device_id lm3533_bl_match_table[] = {
> > + { .compatible = "ti,lm3533-backlight" },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(of, lm3533_bl_match_table);
> > +
> > static struct platform_driver lm3533_bl_driver = {
> > .driver = {
> > .name = "lm3533-backlight",
> > .pm = &lm3533_bl_pm_ops,
> > .dev_groups = lm3533_bl_attribute_groups,
> > + .of_match_table = lm3533_bl_match_table,
> > },
> > .probe = lm3533_bl_probe,
> > .remove = lm3533_bl_remove,
>
> Drop platform module alias below.
>
> Johan
^ permalink raw reply
* Re: [PATCH v5 09/14] mfd: lm3533: Add support for VIN power supply
From: Svyatoslav Ryhel @ 2026-07-14 14:00 UTC (permalink / raw)
To: Johan Hovold
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
dri-devel, linux-leds, devicetree, linux-kernel, linux-iio,
linux-fbdev
In-Reply-To: <akeYWRF-wIlrHTF5@hovoldconsulting.com>
пт, 3 лип. 2026 р. о 14:09 Johan Hovold <johan@kernel.org> пише:
>
> On Wed, Jun 17, 2026 at 11:00:26AM +0300, Svyatoslav Ryhel wrote:
> > Add support for 2.7V-5.5V VIN power supply.
> >
> > Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
>
> > -static void lm3533_enable(struct lm3533 *lm3533)
> > +static int lm3533_enable(struct lm3533 *lm3533)
> > {
> > + int ret;
> > +
> > + ret = regulator_enable(lm3533->vin_supply);
> > + if (ret) {
> > + dev_err(lm3533->dev, "failed to enable vin power supply\n");
> > + return ret;
> > + }
> > +
>
> No delay needed?
>
I have not found any in the datasheet, if you are aware of the delay
duration needed please elaborate.
> > gpiod_set_value(lm3533->hwen, 1);
> > +
> > + return 0;
> > }
>
> Johan
^ permalink raw reply
* [PATCH v2 0/8] Add support for Advantech EIO MFD series devices
From: Ramiro Oliveira @ 2026-07-14 15:54 UTC (permalink / raw)
To: Lee Jones, Linus Walleij, Bartosz Golaszewski, Guenter Roeck,
Andi Shyti, Daniel Thompson, Jingoo Han, Helge Deller,
Wim Van Sebroeck, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba
Cc: linux-kernel, mfd, linux-gpio, linux-hwmon, linux-i2c, dri-devel,
linux-fbdev, linux-watchdog, linux-pm, Wenkai Chung,
Francisco Aragon-Trivino, Hongzhi Wang, Mikhail Tsukerman,
Thomas Kastner, Ramiro Oliveira
This series implements a set of drivers allowing to support the
Advantech EIO-2xx series of devices.
This includes GPIO, hwmon, I2C bus, backlight controller, watchdog,
thermal and a fan driver.
This series of patches targets several different subsystems, but the MFD
subsystem is the main target.
Changes in v2:
- Rebased on v7.2
MFD
- code cleanup
- Remove timeout from sysfs
- Propagate errors
- Remove version.h
GPIO
- Code cleanup
- Move gpio_chip to probe
- Remove timeout from sysfs
HWMON
- Code cleanup
- Refactor to use hwmon register with info
- Remove tacho, same interfaces as fan
I2C
- Remove timeout from sysfs
Backlight
- Code cleanup, remove ununsed max parameter
- Remove timeout sysfs
- Use helper functions instead of direct calls
- Correct return value in bl_init
- Initializing scal ein backlight_properties
- Return correct value when no backlight is enabled
- Remove log on successful probe
Watchdog
- Remove timeout and IRQ from sysfs
- Remove unnecessary logging from set_timeout/pretimeout wdt_get_type
- Remove timeleft function
- Rewrite wdt_set_irq_io to correctly handle return codes
- Remove unneeded watchdog_init_timeout
Thermal
- Remote timeout from sysfs
Fan
- Remove timeout from sysfs
- Remove unused variable temps_mc
- Link to v1: https://lore.kernel.org/all/20251212-upstream-v1-v1-0-d50d40ec8d8a@advantech.com/
Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
Ramiro Oliveira (8):
Add Advantech EIO driver
Add Advantech EIO GPIO driver
Add Advantech EIO Hardware Monitor driver
Add Advantech EIO I2C driver
Add Advantech EIO Backlight driver
Add Advantech EIO Watchdog driver
Add Advantech EIO Thermal driver
Add Advantech EIO Fan driver
MAINTAINERS | 19 +
drivers/gpio/Kconfig | 6 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-eio.c | 252 +++++++++
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/eio-hwmon.c | 461 ++++++++++++++++
drivers/i2c/busses/Kconfig | 6 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-eio.c | 1135 ++++++++++++++++++++++++++++++++++++++
drivers/mfd/Kconfig | 10 +
drivers/mfd/Makefile | 1 +
drivers/mfd/eio_core.c | 644 +++++++++++++++++++++
drivers/thermal/Kconfig | 17 +
drivers/thermal/Makefile | 2 +
drivers/thermal/eio_fan.c | 478 ++++++++++++++++
drivers/thermal/eio_thermal.c | 346 ++++++++++++
drivers/video/backlight/Kconfig | 6 +
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/eio_bl.c | 243 ++++++++
drivers/watchdog/Kconfig | 7 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/eio_wdt.c | 641 +++++++++++++++++++++
include/linux/mfd/eio.h | 124 +++++
24 files changed, 4413 insertions(+)
---
base-commit: 50c051a08a731c52933608fa8e9174cd9875fcc4
change-id: 20260714-upstream-v2-beaec2baac0d
Best regards,
--
Ramiro Oliveira <ramiro.oliveira@advantech.com>
^ permalink raw reply
* [PATCH v2 1/8] Add Advantech EIO driver
From: Ramiro Oliveira @ 2026-07-14 15:54 UTC (permalink / raw)
To: Lee Jones, Linus Walleij, Bartosz Golaszewski, Guenter Roeck,
Andi Shyti, Daniel Thompson, Jingoo Han, Helge Deller,
Wim Van Sebroeck, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba
Cc: linux-kernel, mfd, linux-gpio, linux-hwmon, linux-i2c, dri-devel,
linux-fbdev, linux-watchdog, linux-pm, Wenkai Chung,
Francisco Aragon-Trivino, Hongzhi Wang, Mikhail Tsukerman,
Thomas Kastner, Ramiro Oliveira
In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com>
Creating the core driver for Advantech EIO, all other drivers depend on this.
This core driver provides register access and coordination for the EIO's
subdevices (GPIO, watchdog, hwmon, thermal, backlight, I2C).
This driver supports EIO-IS200, EIO-201, EIO-210 and EIO-211, these
devices are available in several of Advantech x86-based boards, and
provide several different interfaces like I2C, GPIO, Watchdog, Hardware
Monitoring, Thermal Control, and Backlight control.
Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
MAINTAINERS | 6 +
drivers/mfd/Kconfig | 10 +
drivers/mfd/Makefile | 1 +
drivers/mfd/eio_core.c | 644 ++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/eio.h | 124 ++++++++++
5 files changed, 785 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index dcffe9fc54f6..53b5f7412966 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -615,6 +615,12 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/adv_swbutton.c
+ADVANTECH EIO DRIVER
+M: Ramiro Oliveira <ramiro.oliveira@advantech.com>
+S: Maintained
+F: drivers/mfd/eio_core.c
+F: include/linux/mfd/eio.h
+
ADXL313 THREE-AXIS DIGITAL ACCELEROMETER DRIVER
M: Lucas Stankus <lucas.p.stankus@gmail.com>
S: Supported
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 35f6e9b76d05..42d133a2eb8d 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -517,6 +517,16 @@ config MFD_DLN2
etc. must be enabled in order to use the functionality of
the device.
+config MFD_EIO
+ tristate "Advantech EIO core"
+ select MFD_CORE
+ help
+ This enables support for the Advantech EIO multi-function device.
+ This core driver provides register access and coordination for the
+ EIO's subdevices (GPIO, watchdog, hwmon, thermal, backlight, I2C).
+ This driver supports EIO-IS200, EIO-201, EIO-210 and EIO-211.
+
+
config MFD_ENE_KB3930
tristate "ENE KB3930 Embedded Controller support"
depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index dd4bb7e77c33..2013de45528c 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o
obj-$(CONFIG_MFD_CS42L43) += cs42l43.o
obj-$(CONFIG_MFD_CS42L43_I2C) += cs42l43-i2c.o
obj-$(CONFIG_MFD_CS42L43_SDW) += cs42l43-sdw.o
+obj-$(CONFIG_MFD_EIO) += eio_core.o
obj-$(CONFIG_MFD_ENE_KB3930) += ene-kb3930.o
obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o
obj-$(CONFIG_MFD_GATEWORKS_GSC) += gateworks-gsc.o
diff --git a/drivers/mfd/eio_core.c b/drivers/mfd/eio_core.c
new file mode 100644
index 000000000000..25873c061812
--- /dev/null
+++ b/drivers/mfd/eio_core.c
@@ -0,0 +1,644 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Advantech Embedded Controller base Driver
+ *
+ * This driver provides an interface to access the EIO Series EC
+ * firmware via its own Power Management Channel (PMC) for subdrivers:
+ *
+ * A system may have one or two independent EIO devices.
+ *
+ * Copyright (C) 2025 Advantech Co., Ltd.
+ */
+
+#include <linux/delay.h>
+#include <linux/isa.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+#include <linux/time.h>
+#include <linux/uaccess.h>
+
+#define DEFAULT_TIMEOUT 5000
+#define SLEEP_MAX 200
+#define NUM_EC 1
+
+struct eio_dev_port {
+ u16 idx_port;
+ u16 data_port;
+};
+
+static struct eio_dev_port pnp_port[] = {
+ { .idx_port = EIO_PNP_INDEX, .data_port = EIO_PNP_DATA },
+ { .idx_port = EIO_SUB_PNP_INDEX, .data_port = EIO_SUB_PNP_DATA },
+};
+
+static struct mfd_cell eio_devs[] = {
+ MFD_CELL_NAME("eio_wdt"),
+ MFD_CELL_NAME("gpio_eio"),
+ MFD_CELL_NAME("eio_hwmon"),
+ MFD_CELL_NAME("i2c_eio"),
+ MFD_CELL_NAME("eio_thermal"),
+ MFD_CELL_NAME("eio_fan"),
+ MFD_CELL_NAME("eio_bl"),
+};
+
+static const struct regmap_range eio_range[] = {
+ regmap_reg_range(EIO_PNP_INDEX, EIO_PNP_DATA),
+ regmap_reg_range(EIO_SUB_PNP_INDEX, EIO_SUB_PNP_DATA),
+ regmap_reg_range(0x200, 0x3FF),
+};
+
+static const struct regmap_access_table volatile_regs = {
+ .yes_ranges = eio_range,
+ .n_yes_ranges = ARRAY_SIZE(eio_range),
+};
+
+static const struct regmap_config pnp_regmap_config = {
+ .name = "eio_core",
+ .reg_bits = 16,
+ .val_bits = 8,
+ .volatile_table = &volatile_regs,
+ .io_port = true,
+ .cache_type = REGCACHE_NONE,
+};
+
+static struct {
+ char name[32];
+ int cmd;
+ int ctrl;
+ int dev;
+ int size;
+ enum {
+ HEX,
+ NUMBER,
+ PNP_ID,
+ } type;
+} attrs[] = {
+ { "board_name", 0x53, 0x10, 0, 16 },
+ { "board_serial", 0x53, 0x1F, 0, 16 },
+ { "board_manufacturer", 0x53, 0x11, 0, 16 },
+ { "board_id", 0x53, 0x1E, 0, 4 },
+ { "firmware_version", 0x53, 0x21, 0, 4 },
+ { "firmware_name", 0x53, 0x22, 0, 16 },
+ { "firmware_build", 0x53, 0x23, 0, 26 },
+ { "firmware_date", 0x53, 0x24, 0, 16 },
+ { "chip_id", 0x53, 0x12, 0, 12 },
+ { "chip_detect", 0x53, 0x15, 0, 12 },
+ { "platform_type", 0x53, 0x13, 0, 16 },
+ { "platform_revision", 0x53, 0x04, 0x44, 4 },
+ { "eapi_version", 0x53, 0x04, 0x64, 4 },
+ { "eapi_id", 0x53, 0x31, 0, 4 },
+ { "boot_count", 0x55, 0x10, 0, 4, NUMBER },
+ { "powerup_hour", 0x55, 0x11, 0, 4, NUMBER },
+ { "pnp_id", 0x53, 0x04, 0x68, 4, PNP_ID },
+};
+
+static ssize_t info_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ uint i;
+
+ for (i = 0; i < ARRAY_SIZE(attrs); i++) {
+ int ret;
+ char str[32] = "";
+ int val;
+
+ struct pmc_op op = {
+ .cmd = attrs[i].cmd,
+ .control = attrs[i].ctrl,
+ .device_id = attrs[i].dev,
+ .payload = (u8 *)str,
+ .size = attrs[i].size,
+ };
+
+ if (strcmp(attr->attr.name, attrs[i].name))
+ continue;
+
+ ret = eio_core_pmc_operation(dev, &op);
+ if (ret)
+ return ret;
+
+ if (attrs[i].size != 4)
+ return sysfs_emit(buf, "%s\n", str);
+
+ val = *(u32 *)str;
+
+ if (attrs[i].type == HEX)
+ return sysfs_emit(buf, "0x%08X\n", val);
+
+ if (attrs[i].type == NUMBER)
+ return sysfs_emit(buf, "%d\n", val);
+
+ if (attrs[i].type == PNP_ID)
+ return sysfs_emit(buf, "%c%c%c, %X\n",
+ (val >> 14 & 0x3F) + 0x40,
+ ((val >> 9 & 0x18) | (val >> 25 & 0x07)) + 0x40,
+ (val >> 20 & 0x1F) + 0x40,
+ val & 0xFFF);
+ }
+
+ return -EINVAL;
+}
+
+
+PMC_DEVICE_ATTR_RO(board_name);
+PMC_DEVICE_ATTR_RO(board_serial);
+PMC_DEVICE_ATTR_RO(board_manufacturer);
+PMC_DEVICE_ATTR_RO(firmware_name);
+PMC_DEVICE_ATTR_RO(firmware_version);
+PMC_DEVICE_ATTR_RO(firmware_build);
+PMC_DEVICE_ATTR_RO(firmware_date);
+PMC_DEVICE_ATTR_RO(chip_id);
+PMC_DEVICE_ATTR_RO(chip_detect);
+PMC_DEVICE_ATTR_RO(platform_type);
+PMC_DEVICE_ATTR_RO(platform_revision);
+PMC_DEVICE_ATTR_RO(board_id);
+PMC_DEVICE_ATTR_RO(eapi_version);
+PMC_DEVICE_ATTR_RO(eapi_id);
+PMC_DEVICE_ATTR_RO(boot_count);
+PMC_DEVICE_ATTR_RO(powerup_hour);
+PMC_DEVICE_ATTR_RO(pnp_id);
+
+static struct attribute *pmc_attrs[] = {
+ &dev_attr_board_name.attr,
+ &dev_attr_board_serial.attr,
+ &dev_attr_board_manufacturer.attr,
+ &dev_attr_firmware_name.attr,
+ &dev_attr_firmware_version.attr,
+ &dev_attr_firmware_build.attr,
+ &dev_attr_firmware_date.attr,
+ &dev_attr_chip_id.attr,
+ &dev_attr_chip_detect.attr,
+ &dev_attr_platform_type.attr,
+ &dev_attr_platform_revision.attr,
+ &dev_attr_board_id.attr,
+ &dev_attr_eapi_version.attr,
+ &dev_attr_eapi_id.attr,
+ &dev_attr_boot_count.attr,
+ &dev_attr_powerup_hour.attr,
+ &dev_attr_pnp_id.attr,
+ NULL
+};
+
+ATTRIBUTE_GROUPS(pmc);
+
+static int eio_pnp_read(struct device *dev, struct eio_dev_port *port, u8 idx, unsigned int *val)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regmap_write(eio->map, port->idx_port, idx);
+ if (ret) {
+ dev_err(dev, "Error port write 0x%X\n", port->idx_port);
+ return ret;
+ }
+
+ ret = regmap_read(eio->map, port->data_port, val);
+ if (ret)
+ dev_err(dev, "Error port read 0x%X\n", port->data_port);
+
+ return ret;
+}
+
+static int eio_pnp_write(struct device *dev, struct eio_dev_port *port, u8 idx, u8 data)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regmap_write(eio->map, port->idx_port, idx);
+ if (ret) {
+ dev_err(dev, "Error port write 0x%X %X\n", port->idx_port, port->data_port);
+ return ret;
+ }
+
+ ret = regmap_write(eio->map, port->data_port, data);
+ if (ret)
+ dev_err(dev, "Error port write 0x%X %X\n", port->idx_port, port->data_port);
+
+ return ret;
+}
+
+static int eio_unlock_port(struct device *dev, struct eio_dev_port *port)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ int ret;
+
+ /* Write 0x87 to index port twice to unlock IO port */
+ ret = regmap_write(eio->map, port->idx_port, EIO_EXT_MODE_ENTER);
+ if (ret) {
+ dev_err(dev, "Error entering PNP mode on 0x%X: %d\n", port->idx_port, ret);
+ return ret;
+ }
+
+ ret = regmap_write(eio->map, port->idx_port, EIO_EXT_MODE_ENTER);
+ if (ret)
+ dev_err(dev, "Error entering PNP mode on 0x%X: %d\n", port->idx_port, ret);
+
+ return ret;
+}
+
+static int eio_lock_port(struct device *dev, struct eio_dev_port *port)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ int ret;
+ /* Write 0xAA to index port once to lock IO port */
+ ret = regmap_write(eio->map, port->idx_port, EIO_EXT_MODE_EXIT);
+ if (ret)
+ dev_err(dev, "Error port write 0x%X\n", port->idx_port);
+
+ return ret;
+}
+
+static int pmc_write_data(struct device *dev, int id, u8 value)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ int ret;
+
+ ret = eio_core_pmc_wait(dev, id, PMC_WAIT_INPUT);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(eio->map, eio->pmc[id].data, value);
+ if (ret)
+ dev_err(dev, "Error PMC write %X:%X\n",
+ eio->pmc[id].data, value);
+
+ return ret;
+}
+
+static int pmc_write_cmd(struct device *dev, int id, u8 value)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ int ret;
+
+ ret = eio_core_pmc_wait(dev, id, PMC_WAIT_INPUT);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(eio->map, eio->pmc[id].cmd, value);
+ if (ret)
+ dev_err(dev, "Error PMC write %X:%X\n",
+ eio->pmc[id].cmd, value);
+
+ return ret;
+}
+
+static int pmc_read_data(struct device *dev, int id, u8 *value)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ unsigned int val;
+ int ret;
+
+ ret = eio_core_pmc_wait(dev, id, PMC_WAIT_OUTPUT);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(eio->map, eio->pmc[id].data, &val);
+ if (ret)
+ dev_err(dev, "Error PMC read %X\n", eio->pmc[id].data);
+ else
+ *value = (u8)(val & 0xFF);
+
+ return ret;
+}
+
+static int pmc_read_status(struct device *dev, int id)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ unsigned int val;
+
+ if (regmap_read(eio->map, eio->pmc[id].status, &val)) {
+ dev_err(dev, "Error PMC read %X\n",
+ eio->pmc[id].status);
+ return 0;
+ }
+
+ return val;
+}
+
+static void pmc_clear(struct device *dev, int id)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ unsigned int val;
+
+ /* Check if input buffer blocked */
+ if ((pmc_read_status(dev, id) & EIO_PMC_STATUS_IBF) == 0)
+ return;
+
+ /* Read out previous garbage */
+ if (regmap_read(eio->map, eio->pmc[id].data, &val))
+ dev_err(dev, "Error pmc clear\n");
+
+ usleep_range(10, 100);
+}
+
+int eio_core_pmc_wait(struct device *dev, int id, enum eio_pmc_wait wait)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ uint val;
+ int timeout = DEFAULT_TIMEOUT;
+
+ if (wait == PMC_WAIT_INPUT)
+ return regmap_read_poll_timeout(eio->map, eio->pmc[id].status,
+ val, (val & EIO_PMC_STATUS_IBF) == 0,
+ SLEEP_MAX, timeout);
+ return regmap_read_poll_timeout(eio->map,
+ eio->pmc[id].status, val,
+ (val & EIO_PMC_STATUS_OBF) != 0,
+ SLEEP_MAX, timeout);
+}
+EXPORT_SYMBOL_GPL(eio_core_pmc_wait);
+
+int eio_core_pmc_operation(struct device *dev, struct pmc_op *op)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ u8 i;
+ int ret;
+ bool reading = op->cmd & EIO_FLAG_PMC_READ;
+
+ mutex_lock(&eio->mutex);
+
+ pmc_clear(dev, op->chip);
+
+ ret = pmc_write_cmd(dev, op->chip, op->cmd);
+ if (ret)
+ goto err;
+
+ ret = pmc_write_data(dev, op->chip, op->control);
+ if (ret)
+ goto err;
+
+ ret = pmc_write_data(dev, op->chip, op->device_id);
+ if (ret)
+ goto err;
+
+ ret = pmc_write_data(dev, op->chip, op->size);
+ if (ret)
+ goto err;
+
+ for (i = 0; i < op->size; i++) {
+ if (reading)
+ ret = pmc_read_data(dev, op->chip, &op->payload[i]);
+ else
+ ret = pmc_write_data(dev, op->chip, op->payload[i]);
+
+ if (ret)
+ break;
+ }
+
+err:
+ mutex_unlock(&eio->mutex);
+ if (ret)
+ return ret;
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(eio_core_pmc_operation);
+
+static int get_pmc_port(struct device *dev, int id,
+ struct eio_dev_port *port)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ struct _pmc_port *pmc = &eio->pmc[id];
+ int ret;
+ unsigned int high, low;
+
+ ret = eio_unlock_port(dev, port);
+ if (ret)
+ return ret;
+
+ /* Switch to PMC device page */
+ ret = eio_pnp_write(dev, port, EIO_LDN, EIO_LDN_PMC1);
+ if (ret)
+ return ret;
+
+ /* Active this device */
+ ret = eio_pnp_write(dev, port, EIO_LDAR, EIO_LDAR_LDACT);
+ if (ret)
+ return ret;
+
+ /* Get PMC data port */
+ ret = eio_pnp_read(dev, port, EIO_IOBA0H, &high);
+ if (ret)
+ return ret;
+ ret = eio_pnp_read(dev, port, EIO_IOBA0L, &low);
+ if (ret)
+ return ret;
+ pmc->data = (high << 8) | low;
+
+ /* Get PMC cmd */
+ ret = eio_pnp_read(dev, port, EIO_IOBA1H, &high);
+ if (ret)
+ return ret;
+ ret = eio_pnp_read(dev, port, EIO_IOBA1L, &low);
+ if (ret)
+ return ret;
+ pmc->cmd = (high << 8) | low;
+
+ /* Disable IRQ */
+ ret = eio_pnp_write(dev, port, EIO_IRQCTRL, 0);
+ if (ret)
+ return ret;
+
+ ret = eio_lock_port(dev, port);
+ if (ret)
+ return ret;
+
+ /* Make sure IO ports are not occupied */
+ if (!devm_request_region(dev, pmc->data, 2, KBUILD_MODNAME)) {
+ dev_err(dev, "Request region %X error\n", pmc->data);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int eio_init(struct device *dev)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ u16 chip_id;
+ unsigned int msb, lsb;
+ int tmp;
+ int ret = -ENOMEM;
+
+ for (int chip = 0; chip < ARRAY_SIZE(pnp_port); chip++) {
+ struct eio_dev_port *port = pnp_port + chip;
+
+ if (!devm_request_region(dev, pnp_port[chip].idx_port,
+ pnp_port[chip].data_port -
+ pnp_port[chip].idx_port,
+ KBUILD_MODNAME))
+ continue;
+
+ ret = eio_unlock_port(dev, port);
+ if (ret)
+ return ret;
+
+ ret = eio_pnp_read(dev, port, EIO_CHIPID1, &msb);
+ if (ret)
+ return ret;
+
+ ret = eio_pnp_read(dev, port, EIO_CHIPID2, &lsb);
+ if (ret)
+ return ret;
+
+ chip_id = (msb << 8) | lsb;
+
+ if (chip_id != EIO200_CHIPID && chip_id != EIO201_211_CHIPID)
+ continue;
+
+ /* Turn on the enable flag */
+ ret = eio_pnp_read(dev, port, EIO_SIOCTRL, &tmp);
+ if (ret)
+ return ret;
+
+ tmp |= EIO_SIOCTRL_SIOEN;
+
+ ret = eio_pnp_write(dev, port, EIO_SIOCTRL, tmp);
+ if (ret)
+ return ret;
+
+ ret = eio_lock_port(dev, port);
+ if (ret)
+ return ret;
+
+ ret = get_pmc_port(dev, chip, port);
+ if (ret)
+ return ret;
+
+ if (chip == 0)
+ eio->flag |= EIO_F_CHIP_EXIST;
+ else
+ eio->flag |= EIO_F_SUB_CHIP_EXIST;
+ }
+
+ return ret;
+}
+
+static uint8_t acpi_ram_access(struct device *dev, uint8_t offset)
+{
+ uint8_t val;
+ int ret;
+ struct eio_dev *eio = dev_get_drvdata(dev);
+
+ /* We only store information on primary EC */
+ int chip = 0;
+
+ mutex_lock(&eio->mutex);
+
+ pmc_clear(dev, chip);
+
+ ret = pmc_write_cmd(dev, chip, EIO_PMC_CMD_ACPIRAM_READ);
+ if (ret)
+ goto err;
+
+ ret = pmc_write_data(dev, chip, offset);
+ if (ret)
+ goto err;
+
+ ret = pmc_write_data(dev, chip, sizeof(val));
+ if (ret)
+ goto err;
+
+ ret = pmc_read_data(dev, chip, &val);
+ if (ret)
+ goto err;
+
+ ret = val;
+
+err:
+ mutex_unlock(&eio->mutex);
+ return ret;
+}
+
+static int firmware_code_base(struct device *dev)
+{
+ struct eio_dev *eio = dev_get_drvdata(dev);
+ u8 ic_vendor, ic_code, code_base;
+
+ ic_vendor = acpi_ram_access(dev, EIO_ACPIRAM_ICVENDOR);
+ ic_code = acpi_ram_access(dev, EIO_ACPIRAM_ICCODE);
+ code_base = acpi_ram_access(dev, EIO_ACPIRAM_CODEBASE);
+
+ if (ic_vendor != 'R')
+ return -ENODEV;
+
+ if (ic_code != EIO200_ICCODE && ic_code != EIO201_ICCODE &&
+ ic_code != EIO211_ICCODE)
+ goto err;
+
+ if (code_base == EIO_ACPIRAM_CODEBASE_NEW) {
+ eio->flag |= EIO_F_NEW_CODE_BASE;
+ return 0;
+ }
+
+ if (code_base == 0 &&
+ (ic_code != EIO201_ICCODE && ic_code != EIO211_ICCODE)) {
+ dev_info(dev, "Old code base not supported.");
+ return -ENODEV;
+ }
+
+err:
+ /* Codebase error. This should only happen on firmware error. */
+ dev_err(dev,
+ "Codebase check fail: vendor: 0x%X, code: 0x%X, base: 0x%X\n",
+ ic_vendor, ic_code, code_base);
+ return -ENODEV;
+}
+
+static int eio_probe(struct device *dev, unsigned int id)
+{
+ int ret = 0;
+ struct eio_dev *eio;
+
+ eio = devm_kzalloc(dev, sizeof(*eio), GFP_KERNEL);
+ if (!eio)
+ return -ENOMEM;
+
+ eio->dev = dev;
+ mutex_init(&eio->mutex);
+
+ eio->iomem = devm_ioport_map(dev, 0, EIO_SUB_PNP_DATA + 1);
+ if (IS_ERR(eio->iomem))
+ return PTR_ERR(eio->iomem);
+
+ eio->map = devm_regmap_init_mmio(dev, eio->iomem, &pnp_regmap_config);
+ if (IS_ERR(eio->map))
+ return PTR_ERR(eio->map);
+
+ /* Publish instance for subdrivers (dev_get_drvdata(dev->parent)) */
+ dev_set_drvdata(dev, eio);
+
+ if (eio_init(dev))
+ return -ENODEV;
+
+ ret = firmware_code_base(dev);
+ if (ret) {
+ dev_err(dev, "Chip code base check fail\n");
+ return ret;
+ }
+
+ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+ eio_devs, ARRAY_SIZE(eio_devs),
+ NULL, 0, NULL);
+ if (ret)
+ dev_err(dev, "Cannot register child devices (error = %d)\n", ret);
+
+ return ret;
+}
+
+static struct isa_driver eio_driver = {
+ .probe = eio_probe,
+ .driver = {
+ .name = "eio_core",
+ .dev_groups = pmc_groups,
+ },
+};
+module_isa_driver(eio_driver, NUM_EC);
+
+MODULE_AUTHOR("Wenkai Chung <wenkai.chung@advantech.com.tw>");
+MODULE_AUTHOR("Ramiro Oliveira <ramiro.oliveira@advantech.com>");
+MODULE_DESCRIPTION("Advantech EIO series EC core driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/eio.h b/include/linux/mfd/eio.h
new file mode 100644
index 000000000000..c2daad41318b
--- /dev/null
+++ b/include/linux/mfd/eio.h
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Advantech Co., Ltd.
+ */
+
+#ifndef _MFD_EIO_H_
+#define _MFD_EIO_H_
+
+#include <linux/io.h>
+#include <linux/regmap.h>
+
+/* CHIP ID versions */
+#define EIO_CHIPID1 0x20
+#define EIO_CHIPID2 0x21
+#define EIO_CHIPVER 0x22
+#define EIO_SIOCTRL 0x23
+#define EIO_SIOCTRL_SIOEN BIT(0)
+#define EIO_SIOCTRL_SWRST BIT(1)
+#define EIO_IRQCTRL 0x70
+#define EIO200_CHIPID 0x9610
+#define EIO201_211_CHIPID 0x9620
+#define EIO200_ICCODE 0x10
+#define EIO201_ICCODE 0x20
+#define EIO211_ICCODE 0x21
+
+/* LPC PNP */
+#define EIO_PNP_INDEX 0x299
+#define EIO_PNP_DATA 0x29A
+#define EIO_SUB_PNP_INDEX 0x499
+#define EIO_SUB_PNP_DATA 0x49A
+#define EIO_EXT_MODE_ENTER 0x87
+#define EIO_EXT_MODE_EXIT 0xAA
+
+/* LPC LDN */
+#define EIO_LDN 0x07
+#define EIO_LDN_PMC0 0x0C
+#define EIO_LDN_PMC1 0x0D
+
+/* PMC registers */
+#define EIO_PMC_STATUS_IBF BIT(1)
+#define EIO_PMC_STATUS_OBF BIT(0)
+#define EIO_LDAR 0x30
+#define EIO_LDAR_LDACT BIT(0)
+#define EIO_IOBA0H 0x60
+#define EIO_IOBA0L 0x61
+#define EIO_IOBA1H 0x62
+#define EIO_IOBA1L 0x63
+#define EIO_FLAG_PMC_READ BIT(0)
+
+/* PMC command list */
+#define EIO_PMC_CMD_ACPIRAM_READ 0x31
+#define EIO_PMC_CMD_CFG_SAVE 0x56
+
+/* OLD PMC */
+#define EIO_PMC_NO_INDEX 0xFF
+
+/* ACPI RAM Address Table */
+#define EIO_ACPIRAM_VERSIONSECTION (0xFA)
+#define EIO_ACPIRAM_ICVENDOR (EIO_ACPIRAM_VERSIONSECTION + 0x00)
+#define EIO_ACPIRAM_ICCODE (EIO_ACPIRAM_VERSIONSECTION + 0x01)
+#define EIO_ACPIRAM_CODEBASE (EIO_ACPIRAM_VERSIONSECTION + 0x02)
+
+#define EIO_ACPIRAM_CODEBASE_NEW BIT(7)
+
+/* Firmware */
+#define EIO_F_SUB_NEW_CODE_BASE BIT(6)
+#define EIO_F_SUB_CHANGED BIT(7)
+#define EIO_F_NEW_CODE_BASE BIT(8)
+#define EIO_F_CHANGED BIT(9)
+#define EIO_F_SUB_CHIP_EXIST BIT(30)
+#define EIO_F_CHIP_EXIST BIT(31)
+
+/* Others */
+#define EIO_EC_NUM 2
+
+struct _pmc_port {
+ union {
+ u16 cmd;
+ u16 status;
+ };
+ u16 data;
+};
+
+struct pmc_op {
+ u8 cmd;
+ u8 control;
+ u8 device_id;
+ u8 size;
+ u8 *payload;
+ u8 chip;
+};
+
+enum eio_rw_operation {
+ OPERATION_READ,
+ OPERATION_WRITE,
+};
+
+struct eio_dev {
+ struct device *dev;
+ struct regmap *map;
+ void __iomem *iomem;
+ struct mutex mutex; /* Protects PMC command access */
+ struct _pmc_port pmc[EIO_EC_NUM];
+ u32 flag;
+};
+
+int eio_core_pmc_operation(struct device *dev, struct pmc_op *operation);
+
+enum eio_pmc_wait {
+ PMC_WAIT_INPUT,
+ PMC_WAIT_OUTPUT,
+};
+
+int eio_core_pmc_wait(struct device *dev, int id, enum eio_pmc_wait wait);
+
+#define PMC_DEVICE_ATTR_RO(_name) \
+ static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ return info_show(dev, attr, buf); \
+ } \
+ static DEVICE_ATTR_RO(_name)
+
+#endif
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/8] Add Advantech EIO GPIO driver
From: Ramiro Oliveira @ 2026-07-14 15:54 UTC (permalink / raw)
To: Lee Jones, Linus Walleij, Bartosz Golaszewski, Guenter Roeck,
Andi Shyti, Daniel Thompson, Jingoo Han, Helge Deller,
Wim Van Sebroeck, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba
Cc: linux-kernel, mfd, linux-gpio, linux-hwmon, linux-i2c, dri-devel,
linux-fbdev, linux-watchdog, linux-pm, Wenkai Chung,
Francisco Aragon-Trivino, Hongzhi Wang, Mikhail Tsukerman,
Thomas Kastner, Ramiro Oliveira
In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com>
This driver controls the GPIO component of the Advantech EIO chip.
Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
MAINTAINERS | 7 ++
drivers/gpio/Kconfig | 6 ++
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-eio.c | 252 ++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 266 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 53b5f7412966..a7da47393815 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -609,6 +609,13 @@ S: Maintained
F: Documentation/scsi/advansys.rst
F: drivers/scsi/advansys.c
+ADVANTECH EIO DRIVER
+M: Ramiro Oliveira <ramiro.oliveira@advantech.com>
+S: Maintained
+F: drivers/gpio/gpio-eio.c
+F: drivers/mfd/eio_core.c
+F: include/linux/mfd/eio.h
+
ADVANTECH SWBTN DRIVER
M: Andrea Ho <Andrea.Ho@advantech.com.tw>
L: platform-driver-x86@vger.kernel.org
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index f03c05288376..696065d77235 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -295,6 +295,12 @@ config GPIO_DWAPB
Say Y or M here to build support for the Synopsys DesignWare APB
GPIO block.
+config GPIO_EIO
+ tristate "Advantech EIO GPIO"
+ depends on MFD_EIO
+ help
+ Say Y or M to build support for Advantech EIO GPIO block.
+
config GPIO_EIC_SPRD
tristate "Spreadtrum EIC support"
depends on ARCH_SPRD || COMPILE_TEST
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index fa14581e3995..628596705c21 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_GPIO_DLN2) += gpio-dln2.o
obj-$(CONFIG_GPIO_DS4520) += gpio-ds4520.o
obj-$(CONFIG_GPIO_DWAPB) += gpio-dwapb.o
obj-$(CONFIG_GPIO_EIC_SPRD) += gpio-eic-sprd.o
+obj-$(CONFIG_GPIO_EIO) += gpio-eio.o
obj-$(CONFIG_GPIO_ELKHARTLAKE) += gpio-elkhartlake.o
obj-$(CONFIG_GPIO_EM) += gpio-em.o
obj-$(CONFIG_GPIO_EN7523) += gpio-en7523.o
diff --git a/drivers/gpio/gpio-eio.c b/drivers/gpio/gpio-eio.c
new file mode 100644
index 000000000000..34e1aefd0716
--- /dev/null
+++ b/drivers/gpio/gpio-eio.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driver for Advantech EIO Embedded controller.
+ *
+ * Copyright (C) 2025 Advantech Corporation. All rights reserved.
+ */
+
+#include <linux/errno.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+
+#define EIO_GPIO_MAX_PINS 48
+#define EIO_GPIO_WRITE 0x18
+#define EIO_GPIO_READ 0x19
+
+struct eio_gpio_dev {
+ u64 avail;
+ int max;
+ struct gpio_chip chip;
+ struct device *dev;
+};
+
+static struct {
+ int size;
+ bool write;
+} ctrl_para[] = {
+ { 0x01, false }, { 0x00, false }, { 0x00, false }, { 0x02, false },
+ { 0x01, false }, { 0x00, false }, { 0x00, false }, { 0x00, false },
+ { 0x00, false }, { 0x00, false }, { 0x00, false }, { 0x00, false },
+ { 0x00, false }, { 0x00, false }, { 0x00, false }, { 0x00, false },
+ { 0x01, true }, { 0x01, true }, { 0x02, true }, { 0x02, true },
+ { 0x02, false }, { 0x10, false }
+};
+
+enum gpio_ctrl {
+ EIO_GPIO_STATUS = 0x0,
+ EIO_GPIO_GROUP_AVAIL = 0x3,
+ EIO_GPIO_ERROR = 0x04,
+ EIO_GPIO_PIN_DIR = 0x10,
+ EIO_GPIO_PIN_LEVEL = 0x11,
+ EIO_GPIO_GROUP_DIR = 0x12,
+ EIO_GPIO_GROUP_LEVEL = 0x13,
+ EIO_GPIO_MAPPING = 0x14,
+ EIO_GPIO_NAME = 0x15
+};
+
+static struct {
+ int group;
+ int port;
+} group_map[] = {
+ { 0, 0 }, { 0, 1 },
+ { 1, 0 }, { 1, 1 },
+ { 2, 0 }, { 2, 1 },
+ { 3, 0 }, { 3, 1 },
+ { 3, 2 }, { 3, 3 },
+ { 3, 4 }, { 3, 5 },
+ { 3, 6 }, { 3, 7 }
+};
+
+static int pmc_write(struct device *mfd_dev, u8 ctrl, u8 dev_id, void *data)
+{
+ struct pmc_op op = {
+ .cmd = EIO_GPIO_WRITE,
+ .control = ctrl,
+ .device_id = dev_id,
+ .payload = (u8 *)data,
+ };
+
+ if (ctrl >= ARRAY_SIZE(ctrl_para))
+ return -ENOMEM;
+
+ if (!ctrl_para[ctrl].write)
+ return -EINVAL;
+
+ op.size = ctrl_para[ctrl].size;
+
+ return eio_core_pmc_operation(mfd_dev, &op);
+}
+
+static int pmc_read(struct device *mfd_dev, u8 ctrl, u8 dev_id, void *data)
+{
+ struct pmc_op op = {
+ .cmd = EIO_GPIO_READ,
+ .control = ctrl,
+ .device_id = dev_id,
+ .payload = (u8 *)data,
+ };
+
+ if (ctrl > ARRAY_SIZE(ctrl_para))
+ return -ENOMEM;
+
+ op.size = ctrl_para[ctrl].size;
+
+ return eio_core_pmc_operation(mfd_dev, &op);
+}
+
+static int get_dir(struct gpio_chip *chip, unsigned int offset)
+{
+ u8 dir;
+ int ret;
+
+ ret = pmc_read(chip->parent, EIO_GPIO_PIN_DIR, offset, &dir);
+ if (ret)
+ return ret;
+
+ return dir ? 0 : 1;
+}
+
+static int dir_input(struct gpio_chip *chip, unsigned int offset)
+{
+ u8 dir = 0;
+
+ return pmc_write(chip->parent, EIO_GPIO_PIN_DIR, offset, &dir);
+}
+
+static int dir_output(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ u8 dir = 1;
+ u8 val = value;
+
+ pmc_write(chip->parent, EIO_GPIO_PIN_DIR, offset, &dir);
+
+ return pmc_write(chip->parent, EIO_GPIO_PIN_LEVEL, offset, &val);
+}
+
+static int gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ u8 level;
+ int ret;
+
+ ret = pmc_read(chip->parent, EIO_GPIO_PIN_LEVEL, offset, &level);
+ if (ret)
+ return ret;
+
+ return level;
+}
+
+static int gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ u8 val = value;
+
+ return pmc_write(chip->parent, EIO_GPIO_PIN_LEVEL, offset, &val);
+}
+
+static int check_support(struct device *dev)
+{
+ u8 data;
+ int ret;
+
+ ret = pmc_read(dev, EIO_GPIO_STATUS, 0, &data);
+ if (ret)
+ return ret;
+
+ if ((data & 0x01) == 0)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int check_pin(struct device *dev, int pin)
+{
+ int ret;
+ int group, bit;
+ u16 data;
+
+ /* Get pin mapping */
+ ret = pmc_read(dev, EIO_GPIO_MAPPING, pin, &data);
+ if (ret)
+ return ret;
+
+ if ((data & 0xFF) > ARRAY_SIZE(group_map))
+ return -EINVAL;
+
+ group = group_map[data & 0xFF].group;
+ bit = data >> 8;
+
+ /* Check mapped pin */
+ ret = pmc_read(dev, EIO_GPIO_GROUP_AVAIL, group, &data);
+ if (ret)
+ return ret;
+
+ return data & BIT(bit) ? 0 : -EOPNOTSUPP;
+}
+
+static int gpio_init(struct device *mfd, struct eio_gpio_dev *eio_gpio)
+{
+ int ret, i;
+
+ ret = check_support(mfd);
+ if (ret)
+ return dev_err_probe(eio_gpio->dev, ret, "GPIO not supported\n");
+
+ eio_gpio->avail = 0;
+
+ for (i = 0 ; i < EIO_GPIO_MAX_PINS ; i++) {
+ ret = check_pin(mfd, i);
+ if (ret)
+ continue;
+
+ eio_gpio->avail |= BIT(i);
+ eio_gpio->max = i + 1;
+ }
+
+ return eio_gpio->max ? 0 : -EOPNOTSUPP;
+}
+
+static int gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct eio_gpio_dev *eio_gpio;
+ struct eio_dev *eio_dev = dev_get_drvdata(dev->parent);
+
+ if (!eio_dev)
+ return dev_err_probe(dev, -ENODEV, "Error contact eio_core\n");
+
+ eio_gpio = devm_kzalloc(dev, sizeof(*eio_gpio), GFP_KERNEL);
+ if (!eio_gpio)
+ return -ENOMEM;
+
+ eio_gpio->dev = dev;
+
+ if (gpio_init(dev->parent, eio_gpio))
+ return -EIO;
+
+ eio_gpio->chip.parent = dev->parent;
+ eio_gpio->chip.ngpio = eio_gpio->max;
+ eio_gpio->chip.label = KBUILD_MODNAME;
+ eio_gpio->chip.owner = THIS_MODULE;
+ eio_gpio->chip.direction_input = dir_input;
+ eio_gpio->chip.get = gpio_get;
+ eio_gpio->chip.direction_output = dir_output;
+ eio_gpio->chip.set = gpio_set;
+ eio_gpio->chip.get_direction = get_dir;
+ eio_gpio->chip.base = -1;
+ eio_gpio->chip.can_sleep = true;
+
+ return devm_gpiochip_add_data(dev, &eio_gpio->chip, eio_gpio);
+}
+
+static struct platform_driver gpio_driver = {
+ .probe = gpio_probe,
+ .driver = { .name = KBUILD_MODNAME, },
+};
+
+module_platform_driver(gpio_driver);
+
+MODULE_AUTHOR("Wenkai Chung <wenkai.chung@advantech.com.tw>");
+MODULE_AUTHOR("Ramiro Oliveira <ramiro.oliveira@advantech.com>");
+MODULE_DESCRIPTION("GPIO driver for Advantech EIO embedded controller");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v2 3/8] Add Advantech EIO Hardware Monitor driver
From: Ramiro Oliveira @ 2026-07-14 15:54 UTC (permalink / raw)
To: Lee Jones, Linus Walleij, Bartosz Golaszewski, Guenter Roeck,
Andi Shyti, Daniel Thompson, Jingoo Han, Helge Deller,
Wim Van Sebroeck, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba
Cc: linux-kernel, mfd, linux-gpio, linux-hwmon, linux-i2c, dri-devel,
linux-fbdev, linux-watchdog, linux-pm, Wenkai Chung,
Francisco Aragon-Trivino, Hongzhi Wang, Mikhail Tsukerman,
Thomas Kastner, Ramiro Oliveira
In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com>
This driver controls the Hardware Monitor block of the Advantech EIO chip.
There is a separate driver for the thermal device driver
since it's a use case we support currently, but if it's
really necessary we can move it to the same driver.
Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
MAINTAINERS | 1 +
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/eio-hwmon.c | 461 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 473 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index a7da47393815..6d727d5182e4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -613,6 +613,7 @@ ADVANTECH EIO DRIVER
M: Ramiro Oliveira <ramiro.oliveira@advantech.com>
S: Maintained
F: drivers/gpio/gpio-eio.c
+F: drivers/hwmon/eio-hwmon.c
F: drivers/mfd/eio_core.c
F: include/linux/mfd/eio.h
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 08c29685126a..b8fd8ded846a 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2094,6 +2094,16 @@ config SENSORS_EIC7700_PVT
This driver can also be built as a module. If so, the module will be
called eic7700-pvt.
+config SENSORS_EIO
+ tristate "Advantech EIO HWMON"
+ depends on MFD_EIO
+ help
+ If you say yes here you get support for the Advantech EIO
+ temperature, voltage and fan speed monitoring block.
+
+ This driver can also be built as a module. If so, the module
+ will be called eio-hwmon
+
config SENSORS_EMC1403
tristate "SMSC EMC1403/23 thermal sensor"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 63809eeec2f4..8e81a2bdc99b 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -73,6 +73,7 @@ obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o
obj-$(CONFIG_SENSORS_DS620) += ds620.o
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
obj-$(CONFIG_SENSORS_EIC7700_PVT) += eic7700-pvt.o
+obj-$(CONFIG_SENSORS_EIO) += eio-hwmon.o
obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
obj-$(CONFIG_SENSORS_EMC1812) += emc1812.o
obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o
diff --git a/drivers/hwmon/eio-hwmon.c b/drivers/hwmon/eio-hwmon.c
new file mode 100644
index 000000000000..dd877664e9e8
--- /dev/null
+++ b/drivers/hwmon/eio-hwmon.c
@@ -0,0 +1,461 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Hardware Monitoring driver for Advantech EIO embedded controller.
+ *
+ * Copyright (C) 2025 Advantech Corporation. All rights reserved.
+ */
+
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+
+#define MAX_CMD_SIZE 4
+#define MAX_NAME 32
+
+#define EIO_ITEM_INPUT 1
+#define EIO_ITEM_MAX 2
+#define EIO_ITEM_MIN 3
+#define EIO_ITEM_CRIT 4
+#define EIO_ITEM_EMERGENCY 5
+
+#define EIO_MAX_IN 8
+#define EIO_MAX_CURR 2
+#define EIO_MAX_TEMP 4
+#define EIO_MAX_FAN 4
+#define EIO_MAX_INTRUSION 1
+
+enum _sen_type {
+ NONE,
+ VOLTAGE,
+ CURRENT,
+ TEMP,
+ FAN,
+ CASEOPEN,
+};
+
+struct eio_chan {
+ bool available;
+ u8 label_idx;
+};
+
+struct eio_hwmon_dev {
+ struct device *mfd;
+ struct eio_chan in[EIO_MAX_IN];
+ struct eio_chan curr[EIO_MAX_CURR];
+ struct eio_chan temp[EIO_MAX_TEMP];
+ struct eio_chan fan[EIO_MAX_FAN];
+ struct eio_chan intrusion;
+};
+
+static struct {
+ u8 cmd;
+ u8 max;
+ signed int shift;
+ char name[MAX_NAME];
+ u8 ctrl[16];
+ u16 multi[16];
+ char item[16][MAX_NAME];
+ char labels[32][MAX_NAME];
+} sen_info[] = {
+ { 0x00, 0, 0, "none" },
+ { 0x12, 8, 0, "in",
+ { 0xFF, 0x10, 0x11, 0x12 },
+ { 1, 10, 10, 10 },
+ { "label", "input", "max", "min" },
+ { "5V", "5Vs5", "12V", "12Vs5",
+ "3V3", "3V3", "5Vsb", "3Vsb",
+ "Vcmos", "Vbat", "Vdc", "Vstb",
+ "Vcore_a", "Vcore_b", "", "",
+ "Voem0", "Voem1", "Voem2", "Voem3"
+ },
+ },
+ { 0x1a, 2, 0, "curr",
+ { 0xFF, 0x10, 0x11, 0x12 },
+ { 1, 10, 10, 10 },
+ { "label", "input", "max", "min" },
+ { "dc", "oem0" },
+ },
+ { 0x10, 4, -2731, "temp",
+ { 0xFF, 0x10, 0x11, 0x12, 0x21, 0x41 },
+ { 1, 100, 100, 100, 100, 100 },
+ { "label", "input", "max", "min", "crit", "emergency" },
+ { "cpu0", "cpu1", "cpu2", "cpu3",
+ "sys0", "sys1", "sys2", "sys3",
+ "aux0", "aux1", "aux2", "aux3",
+ "dimm0", "dimm1", "dimm2", "dimm3",
+ "pch", "gpu", "", "",
+ "", "", "", "",
+ "", "", "", "",
+ "oem0", "oem1", "oem", "oem3" },
+ },
+ { 0x24, 4, 0, "fan",
+ { 0xFF, 0x1A },
+ { 1, 1 },
+ { "label", "input"},
+ { "cpu0", "cpu1", "cpu2", "cpu3",
+ "sys0", "sys1", "sys2", "sys3",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "",
+ "oem0", "oem1", "oem2", "oem3",
+ },
+ },
+ { 0x28, 1, 0, "intrusion",
+ { 0xFF, 0x02 },
+ { 1, 1 },
+ { "label", "input" },
+ { "case_open" }
+ }
+};
+
+static struct {
+ enum _sen_type type;
+ u8 ctrl;
+ int size;
+ bool write;
+} ctrl_para[] = {
+ { NONE, 0x00, 0, false },
+
+ { VOLTAGE, 0x00, 1, false }, { VOLTAGE, 0x01, 1, false },
+ { VOLTAGE, 0x10, 2, false }, { VOLTAGE, 0x11, 2, false },
+ { VOLTAGE, 0x12, 2, false },
+
+ { CURRENT, 0x00, 1, false }, { CURRENT, 0x01, 1, false },
+ { CURRENT, 0x10, 2, false }, { CURRENT, 0x11, 2, false },
+ { CURRENT, 0x12, 2, false },
+
+ { TEMP, 0x00, 2, false }, { TEMP, 0x01, 1, false },
+ { TEMP, 0x04, 1, false }, { TEMP, 0x10, 2, false },
+ { TEMP, 0x11, 2, false }, { TEMP, 0x12, 2, false },
+ { TEMP, 0x21, 2, false }, { TEMP, 0x41, 2, false },
+
+ { FAN, 0x00, 1, false }, { FAN, 0x01, 1, false },
+ { FAN, 0x03, 1, true }, { FAN, 0x1A, 2, false },
+
+ { CASEOPEN, 0x00, 1, false }, { CASEOPEN, 0x02, 1, true },
+};
+
+static int para_idx(enum _sen_type type, u8 ctrl)
+{
+ int i;
+
+ for (i = 1 ; i < ARRAY_SIZE(ctrl_para) ; i++)
+ if (type == ctrl_para[i].type &&
+ ctrl == ctrl_para[i].ctrl)
+ return i;
+
+ return 0;
+}
+
+static int pmc_read(struct device *mfd, enum _sen_type type, u8 dev_id, u8 ctrl, void *data)
+{
+ int idx = para_idx(type, ctrl);
+ int ret = 0;
+
+ if (idx == 0)
+ return -EINVAL;
+
+ if (WARN_ON(!data))
+ return -EINVAL;
+
+ struct pmc_op op = {
+ .cmd = sen_info[type].cmd | EIO_FLAG_PMC_READ,
+ .control = ctrl,
+ .device_id = dev_id,
+ .size = ctrl_para[idx].size,
+ .payload = (u8 *)data,
+ };
+
+ ret = eio_core_pmc_operation(mfd, &op);
+ return ret;
+}
+
+
+static umode_t eio_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct eio_hwmon_dev *eio = drvdata;
+
+ switch (type) {
+ case hwmon_in:
+ return (channel < EIO_MAX_IN && eio->in[channel].available) ? 0444 : 0;
+ case hwmon_curr:
+ return (channel < EIO_MAX_CURR && eio->curr[channel].available) ? 0444 : 0;
+ case hwmon_temp:
+ return (channel < EIO_MAX_TEMP && eio->temp[channel].available) ? 0444 : 0;
+ case hwmon_fan:
+ return (channel < EIO_MAX_FAN && eio->fan[channel].available) ? 0444 : 0;
+ case hwmon_intrusion:
+ return (channel < EIO_MAX_INTRUSION && eio->intrusion.available) ? 0444 : 0;
+ default:
+ return 0;
+ }
+}
+
+static int eio_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct eio_hwmon_dev *eio = dev_get_drvdata(dev);
+ u8 data[MAX_CMD_SIZE] = {};
+ enum _sen_type ec_type;
+ u8 item;
+ u32 raw;
+ int ret;
+
+ switch (type) {
+ case hwmon_in:
+ switch (attr) {
+ case hwmon_in_input:
+ item = EIO_ITEM_INPUT;
+ break;
+ case hwmon_in_max:
+ item = EIO_ITEM_MAX;
+ break;
+ case hwmon_in_min:
+ item = EIO_ITEM_MIN;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ ec_type = VOLTAGE;
+ break;
+ case hwmon_curr:
+ switch (attr) {
+ case hwmon_curr_input:
+ item = EIO_ITEM_INPUT;
+ break;
+ case hwmon_curr_max:
+ item = EIO_ITEM_MAX;
+ break;
+ case hwmon_curr_min:
+ item = EIO_ITEM_MIN;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ ec_type = CURRENT;
+ break;
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ item = EIO_ITEM_INPUT;
+ break;
+ case hwmon_temp_max:
+ item = EIO_ITEM_MAX;
+ break;
+ case hwmon_temp_min:
+ item = EIO_ITEM_MIN;
+ break;
+ case hwmon_temp_crit:
+ item = EIO_ITEM_CRIT;
+ break;
+ case hwmon_temp_emergency:
+ item = EIO_ITEM_EMERGENCY;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ ec_type = TEMP;
+ break;
+ case hwmon_fan:
+ if (attr != hwmon_fan_input)
+ return -EOPNOTSUPP;
+ item = EIO_ITEM_INPUT;
+ ec_type = FAN;
+ break;
+ case hwmon_intrusion:
+ if (attr != hwmon_intrusion_alarm)
+ return -EOPNOTSUPP;
+ item = EIO_ITEM_INPUT;
+ ec_type = CASEOPEN;
+ channel = 0;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ ret = pmc_read(eio->mfd, ec_type, channel, sen_info[ec_type].ctrl[item], data);
+ if (ret)
+ return ret;
+
+ raw = (u32)data[0] | (u32)data[1] << 8 | (u32)data[2] << 16 | (u32)data[3] << 24;
+ *val = ((long)(signed int)raw + sen_info[ec_type].shift) *
+ (long)sen_info[ec_type].multi[item];
+ return 0;
+}
+
+static int eio_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ struct eio_hwmon_dev *eio = dev_get_drvdata(dev);
+ enum _sen_type ec_type;
+ u8 label_idx;
+
+ switch (type) {
+ case hwmon_in:
+ if (attr != hwmon_in_label)
+ return -EOPNOTSUPP;
+ ec_type = VOLTAGE;
+ label_idx = eio->in[channel].label_idx;
+ break;
+ case hwmon_curr:
+ if (attr != hwmon_curr_label)
+ return -EOPNOTSUPP;
+ ec_type = CURRENT;
+ label_idx = eio->curr[channel].label_idx;
+ break;
+ case hwmon_temp:
+ if (attr != hwmon_temp_label)
+ return -EOPNOTSUPP;
+ ec_type = TEMP;
+ label_idx = eio->temp[channel].label_idx;
+ break;
+ case hwmon_fan:
+ if (attr != hwmon_fan_label)
+ return -EOPNOTSUPP;
+ ec_type = FAN;
+ label_idx = eio->fan[channel].label_idx;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (label_idx >= ARRAY_SIZE(sen_info[ec_type].labels))
+ return -EOPNOTSUPP;
+
+ *str = sen_info[ec_type].labels[label_idx];
+ return 0;
+}
+
+static const struct hwmon_ops eio_hwmon_ops = {
+ .is_visible = eio_is_visible,
+ .read = eio_read,
+ .read_string = eio_read_string,
+};
+
+static const struct hwmon_channel_info * const eio_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN,
+ HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN,
+ HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN,
+ HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN,
+ HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN,
+ HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN,
+ HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN,
+ HWMON_I_LABEL | HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MIN),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_LABEL | HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MIN,
+ HWMON_C_LABEL | HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MIN),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_CRIT | HWMON_T_EMERGENCY,
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_CRIT | HWMON_T_EMERGENCY,
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_CRIT | HWMON_T_EMERGENCY,
+ HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_CRIT | HWMON_T_EMERGENCY),
+ HWMON_CHANNEL_INFO(fan,
+ HWMON_F_LABEL | HWMON_F_INPUT,
+ HWMON_F_LABEL | HWMON_F_INPUT,
+ HWMON_F_LABEL | HWMON_F_INPUT,
+ HWMON_F_LABEL | HWMON_F_INPUT),
+ HWMON_CHANNEL_INFO(intrusion, HWMON_INTRUSION_ALARM),
+ NULL,
+};
+
+static const struct hwmon_chip_info eio_chip_info = {
+ .ops = &eio_hwmon_ops,
+ .info = eio_hwmon_info,
+};
+
+static int hwmon_init(struct device *mfd, struct eio_hwmon_dev *eio)
+{
+ enum _sen_type type;
+ u8 i, data[MAX_CMD_SIZE];
+ int n = 0;
+ int ret;
+
+ for (type = VOLTAGE ; type <= TEMP ; type++) {
+ struct eio_chan *chans = (type == VOLTAGE) ? eio->in :
+ (type == CURRENT) ? eio->curr : eio->temp;
+
+ for (i = 0 ; i < sen_info[type].max ; i++) {
+ memset(data, 0, sizeof(data));
+ if (pmc_read(mfd, type, i, 0x00, data) ||
+ (data[0] & 0x01) == 0)
+ continue;
+
+ memset(data, 0, sizeof(data));
+ ret = pmc_read(mfd, type, i, 0x01, data);
+ if (ret != 0 && ret != -EINVAL)
+ continue;
+
+ chans[i].available = true;
+ chans[i].label_idx = data[0];
+ n++;
+ }
+ }
+
+ for (i = 0 ; i < sen_info[FAN].max ; i++) {
+ memset(data, 0, sizeof(data));
+ if (pmc_read(mfd, FAN, i, 0x00, data) ||
+ (data[0] & 0x01) == 0)
+ continue;
+
+ memset(data, 0, sizeof(data));
+ ret = pmc_read(mfd, FAN, i, 0x01, data);
+ if (ret != 0 && ret != -EINVAL)
+ continue;
+
+ eio->fan[i].available = true;
+ eio->fan[i].label_idx = data[0];
+ n++;
+ }
+
+ memset(data, 0, sizeof(data));
+ if (!pmc_read(mfd, CASEOPEN, 0, 0x00, data) && (data[0] & 0x01)) {
+ eio->intrusion.available = true;
+ n++;
+ }
+
+ return n;
+}
+
+static int hwmon_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct eio_hwmon_dev *eio;
+ struct eio_dev *eio_dev = dev_get_drvdata(dev->parent);
+ struct device *hwmon;
+
+ if (!eio_dev) {
+ dev_err(dev, "Error contact eio_core\n");
+ return -ENODEV;
+ }
+
+ eio = devm_kzalloc(dev, sizeof(*eio), GFP_KERNEL);
+ if (!eio)
+ return -ENOMEM;
+
+ eio->mfd = dev->parent;
+
+ if (hwmon_init(dev->parent, eio) <= 0)
+ return -ENODEV;
+
+ hwmon = devm_hwmon_device_register_with_info(dev, KBUILD_MODNAME,
+ eio, &eio_chip_info, NULL);
+ return PTR_ERR_OR_ZERO(hwmon);
+}
+
+static struct platform_driver eio_hwmon_driver = {
+ .probe = hwmon_probe,
+ .driver = {
+ .name = "eio_hwmon",
+ },
+};
+
+module_platform_driver(eio_hwmon_driver);
+
+MODULE_AUTHOR("Wenkai Chung <wenkai.chung@advantech.com.tw>");
+MODULE_AUTHOR("Ramiro Oliveira <ramiro.oliveira@advantech.com>");
+MODULE_DESCRIPTION("Hardware monitor driver for Advantech EIO embedded controller");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v2 4/8] Add Advantech EIO I2C driver
From: Ramiro Oliveira @ 2026-07-14 15:54 UTC (permalink / raw)
To: Lee Jones, Linus Walleij, Bartosz Golaszewski, Guenter Roeck,
Andi Shyti, Daniel Thompson, Jingoo Han, Helge Deller,
Wim Van Sebroeck, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba
Cc: linux-kernel, mfd, linux-gpio, linux-hwmon, linux-i2c, dri-devel,
linux-fbdev, linux-watchdog, linux-pm, Wenkai Chung,
Francisco Aragon-Trivino, Hongzhi Wang, Mikhail Tsukerman,
Thomas Kastner, Ramiro Oliveira
In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com>
This commit adds the driver to control the Advantech EIO I2C block, this
block is included in the Advantech EIO MFD.
Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
MAINTAINERS | 1 +
drivers/i2c/busses/Kconfig | 6 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-eio.c | 1135 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1143 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 6d727d5182e4..e228d12a2326 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -614,6 +614,7 @@ M: Ramiro Oliveira <ramiro.oliveira@advantech.com>
S: Maintained
F: drivers/gpio/gpio-eio.c
F: drivers/hwmon/eio-hwmon.c
+F: drivers/i2c/busses/i2c-eio.c
F: drivers/mfd/eio_core.c
F: include/linux/mfd/eio.h
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index d7b89508311f..ba982bb00cf9 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -649,6 +649,12 @@ config I2C_DIGICOLOR
This driver can also be built as a module. If so, the module
will be called i2c-digicolor.
+config I2C_EIO
+ tristate "Advantech EIO I2C bus"
+ depends on MFD_EIO
+ help
+ Say Y or M to build support for Advantech EIO I2C block.
+
config I2C_EG20T
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) I2C"
depends on PCI && (X86_32 || MIPS || COMPILE_TEST)
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 3755c54b3d82..2d8d4f37838c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_I2C_DESIGNWARE_AMDISP) += i2c-designware-amdisp.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-y := i2c-designware-pcidrv.o
obj-$(CONFIG_I2C_DIGICOLOR) += i2c-digicolor.o
+obj-$(CONFIG_I2C_EIO) += i2c-eio.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
obj-$(CONFIG_I2C_EMEV2) += i2c-emev2.o
obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
diff --git a/drivers/i2c/busses/i2c-eio.c b/drivers/i2c/busses/i2c-eio.c
new file mode 100644
index 000000000000..56a89ca18f32
--- /dev/null
+++ b/drivers/i2c/busses/i2c-eio.c
@@ -0,0 +1,1135 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * I2C and SMBus driver of EIO embedded driver
+ *
+ * Copyright (C) 2025 Advantech Co., Ltd.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#define SUPPORTED_COMMON (I2C_FUNC_I2C | \
+ I2C_FUNC_SMBUS_QUICK | \
+ I2C_FUNC_SMBUS_BYTE | \
+ I2C_FUNC_SMBUS_BYTE_DATA | \
+ I2C_FUNC_SMBUS_WORD_DATA | \
+ I2C_FUNC_SMBUS_I2C_BLOCK)
+#define SUPPORTED_SMB (SUPPORTED_COMMON | I2C_FUNC_SMBUS_BLOCK_DATA)
+#define SUPPORTED_I2C (SUPPORTED_COMMON | I2C_FUNC_10BIT_ADDR)
+
+#define MAX_I2C_SMB 4
+
+#define REG_PNP_INDEX 0x299
+#define REG_PNP_DATA 0x29A
+#define REG_SUB_PNP_INDEX 0x499
+#define REG_SUB_PNP_DATA 0x49A
+#define REG_EXT_MODE_ENTER 0x87
+#define REG_EXT_MODE_EXIT 0xAA
+#define REG_LDN 0x07
+
+#define LDN_I2C0 0x20
+#define LDN_I2C1 0x21
+#define LDN_SMBUS0 0x22
+#define LDN_SMBUS1 0x23
+
+#define REG_BASE_HI 0x60
+#define REG_BASE_LO 0x61
+
+#define I2C_REG_CTRL 0x00
+#define I2C_CTRL_STOP BIT(1)
+
+#define I2C_REG_STAT 0x01
+#define I2C_STAT_RXREADY BIT(6)
+#define I2C_STAT_TXDONE BIT(5)
+#define I2C_STAT_NAK_ERR BIT(4)
+#define I2C_STAT_ARL_ERR BIT(3)
+#define I2C_STAT_SLV_STP BIT(2)
+#define I2C_STAT_BUSY BIT(1)
+#define I2C_STAT_MST_SLV BIT(0)
+
+#define I2C_REG_MYADDR 0x02
+#define I2C_REG_ADDR 0x03
+#define I2C_REG_DATA 0x04
+#define I2C_REG_PRESCALE1 0x05
+#define I2C_REG_PRESCALE2 0x06
+
+#define I2C_REG_ECTRL 0x07
+#define I2C_ECTRL_RST BIT(7)
+
+#define I2C_REG_SEM 0x08
+#define I2C_SEM_INUSE BIT(1)
+
+#define SMB_REG_HC2 0x0C
+
+#define SMB_REG_HS 0x00
+#define SMB_HS_BUSY BIT(0)
+#define SMB_HS_FINISH BIT(1)
+#define SMB_HS_ARL_ERR BIT(3)
+#define SMB_HS_FAILED BIT(4)
+#define SMB_HS_RX_READY BIT(5)
+#define SMB_HS_INUSE BIT(6)
+#define SMB_HS_TX_DONE BIT(7)
+
+#define SMB_REG_HS2 0x01
+#define SMB_HS2_HNOTIFY BIT(0)
+#define SMB_HS2_PEC_ERR BIT(1)
+#define SMB_HS2_NACK_ERR BIT(2)
+#define SMB_HS2_ALERT_STS BIT(3)
+#define SMB_HS2_TO_ERR BIT(4)
+#define SMB_HS2_SSTOP_STS BIT(5)
+#define SMB_HS2_STX_REQ BIT(6)
+#define SMB_HS2_SMODE BIT(7)
+
+#define SMB_REG_HC 0x02
+#define SMB_HC_I2C_NACKEN BIT(0)
+#define SMB_HC_KILL BIT(1)
+#define SMB_HC_CMD_SHIFT 2
+#define SMB_HC_LAST_BYTE BIT(5)
+#define SMB_HC_START BIT(6)
+#define SMB_HC_PEC_EN BIT(7)
+
+#define SMB_REG_HCMD 0x03
+#define SMB_REG_HADDR 0x04
+#define SMB_REG_HD0 0x05
+#define SMB_REG_HD1 0x06
+#define SMB_REG_HBLOCK 0x07
+#define SMB_REG_HPEC 0x08
+#define SMB_REG_SADDR 0x09
+#define SMB_REG_SD0 0x0A
+#define SMB_REG_SD1 0x0B
+
+#define SMB_REG_HC2 0x0C
+#define SMB_HC2_HNOTIFY_DIS BIT(0)
+#define SMB_HC2_I2C_EN BIT(1)
+#define SMB_HC2_AAPEC BIT(2)
+#define SMB_HC2_E32B BIT(3)
+#define SMB_HC2_SRESET BIT(7)
+
+#define SMB_REG_HPIN 0x0D
+#define SMB_REG_HC3 0x0E
+#define SMB_REG_HC4 0x0F
+#define SMB_REG_NOTIFY_D0 0x11
+#define SMB_REG_NOTIFY_D1 0x12
+#define SMB_REG_HPRESCALE1 0x13
+#define SMB_REG_HPRESCALE2 0x14
+#define SMB_REG_HEXTRA 0x15
+
+#define I2C_TIMEOUT (10 * USEC_PER_MSEC)
+#define USE_DEFAULT -1
+
+#define CHIP_CLK 50000
+#define I2C_SCLH_HIGH 2500
+#define I2C_SCLH_LOW 1000
+#define I2C_SCL_FAST_MODE 0x80
+#define I2C_THRESHOLD_SPEED 100
+#define I2C_THRESHOLD_SCLH 30
+#define I2C_FREQ_MAX 400
+#define I2C_FREQ_MIN 8
+
+enum eio_chan_id {
+ EIO_I2C0 = 0,
+ EIO_I2C1,
+ EIO_SMB0,
+ EIO_SMB1,
+};
+
+struct eio_i2c_dev {
+ struct device *dev;
+ struct device *mfd;
+ struct regmap *regmap;
+ struct mutex pnp_mutex; /* Mutex for PNP acces */
+ struct eio_i2c_chan *chan[MAX_I2C_SMB];
+};
+
+struct eio_i2c_chan {
+ u16 base;
+ enum eio_chan_id id;
+ struct eio_i2c_dev *parent;
+ struct i2c_adapter adap;
+ struct mutex lock; /* Mutex for regmap writes */
+ int freq_override; /* kHz or USE_DEFAULT */
+};
+
+static int i2c0_freq = USE_DEFAULT;
+module_param(i2c0_freq, int, 0444);
+MODULE_PARM_DESC(i2c0_freq, "Set EIO's I2C0 freq.\n");
+
+static int i2c1_freq = USE_DEFAULT;
+module_param(i2c1_freq, int, 0444);
+MODULE_PARM_DESC(i2c1_freq, "Set EIO's I2C1 freq.\n");
+
+static int smb0_freq = USE_DEFAULT;
+module_param(smb0_freq, int, 0444);
+MODULE_PARM_DESC(smb0_freq, "Set EIO's SMB0 freq.\n");
+
+static int smb1_freq = USE_DEFAULT;
+module_param(smb1_freq, int, 0444);
+MODULE_PARM_DESC(smb1_freq, "Set EIO's SMB1 freq.\n");
+
+static inline u16 eio_enc_7bit_addr(u16 x)
+{
+ return ((x & 0x07F) << 1);
+}
+
+static inline u16 eio_enc_10bit_addr(u16 x)
+{
+ return ((x & 0xFF) | ((x & 0x0300) << 1) | 0xF000);
+}
+
+static inline bool is_i2c(const struct eio_i2c_chan *i2c_chan)
+{
+ return i2c_chan->id == EIO_I2C0 || i2c_chan->id == EIO_I2C1;
+}
+
+static inline struct device *eio_dev(const struct eio_i2c_chan *i2c_chan)
+{
+ return i2c_chan->parent->dev;
+}
+
+static inline struct regmap *eio_map(const struct eio_i2c_chan *i2c_chan)
+{
+ return i2c_chan->parent->regmap;
+}
+
+static inline int eio_reg_write(struct eio_i2c_chan *i2c_chan,
+ unsigned int reg_off, unsigned int val)
+{
+ return regmap_write(eio_map(i2c_chan), i2c_chan->base + reg_off, val);
+}
+
+static inline int eio_reg_read(const struct eio_i2c_chan *chan,
+ unsigned int reg, unsigned int *val)
+{
+ int ret;
+
+ ret = regmap_read(chan->parent->regmap, chan->base + reg, val);
+ return ret;
+}
+
+static inline int eio_reg_set_bits(const struct eio_i2c_chan *chan,
+ unsigned int reg, unsigned int mask)
+{
+ return regmap_update_bits(chan->parent->regmap, reg, mask, mask);
+}
+
+static inline int eio_reg_clear_bits(const struct eio_i2c_chan *chan,
+ unsigned int reg, unsigned int mask)
+{
+ return regmap_update_bits(chan->parent->regmap, reg, mask, 0);
+}
+
+static inline int eio_reg_or(struct eio_i2c_chan *chan,
+ unsigned int reg, unsigned int mask)
+{
+ return eio_reg_set_bits(chan, reg, mask);
+}
+
+static inline int eio_reg_and(struct eio_i2c_chan *chan,
+ unsigned int reg, unsigned int mask)
+{
+ return eio_reg_clear_bits(chan, reg, ~mask);
+}
+
+static inline unsigned int eio_chan_reg(const struct eio_i2c_chan *i2c_chan,
+ unsigned int i2c_reg,
+ unsigned int smb_reg)
+{
+ return is_i2c(i2c_chan) ? i2c_reg : smb_reg;
+}
+
+static inline int eio_trigger_read(struct eio_i2c_chan *i2c_chan, u32 *data)
+{
+ unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_DATA, SMB_REG_HD0);
+
+ return eio_reg_read(i2c_chan, reg, data);
+}
+
+static int wait_busy(struct eio_i2c_chan *i2c_chan)
+{
+ ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+ unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_STAT, SMB_REG_HS);
+ unsigned int target = eio_chan_reg(i2c_chan, I2C_STAT_BUSY, SMB_HS_BUSY);
+ unsigned int val;
+ int cnt = 0;
+
+ do {
+ fsleep(cnt++);
+
+ if (ktime_after(ktime_get(), time_end)) {
+ dev_err(eio_dev(i2c_chan), "Wait I2C bus busy timeout\n");
+ return -ETIME;
+ }
+
+ if (eio_reg_read(i2c_chan, reg, &val))
+ return -EIO;
+
+ } while (val & target);
+
+ return 0;
+}
+
+static void reset_bus(struct eio_i2c_chan *i2c_chan)
+{
+ ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+ unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_ECTRL, SMB_REG_HC2);
+ unsigned int target = eio_chan_reg(i2c_chan, I2C_ECTRL_RST, SMB_HC2_SRESET);
+ unsigned int val = 0;
+ unsigned int cnt = 0;
+
+ dev_dbg(eio_dev(i2c_chan), "i2c[%d] bus reset\n", i2c_chan->id);
+
+ if (is_i2c(i2c_chan))
+ eio_reg_write(i2c_chan, I2C_REG_ECTRL, I2C_ECTRL_RST);
+ else
+ eio_reg_or(i2c_chan, SMB_REG_HC2, SMB_HC2_SRESET);
+
+ do {
+ fsleep(cnt++);
+
+ if (ktime_after(ktime_get(), time_end)) {
+ dev_err(eio_dev(i2c_chan), "bus reset timeout\n");
+ return;
+ }
+
+ if (eio_reg_read(i2c_chan, reg, &val))
+ return;
+
+ } while (val & target);
+
+ wait_busy(i2c_chan);
+}
+
+static int wait_bus_free(struct eio_i2c_chan *i2c_chan)
+{
+ ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+ unsigned int val;
+ int cnt = 1;
+
+ /* Wait if channel is resetting */
+ do {
+ fsleep(cnt);
+
+ if (ktime_after(ktime_get(), time_end)) {
+ dev_err(eio_dev(i2c_chan), "Wait bus reset timeout\n");
+ return -ETIME;
+ }
+
+ if (eio_reg_read(i2c_chan,
+ eio_chan_reg(i2c_chan, I2C_REG_ECTRL, SMB_REG_HC2),
+ &val))
+ return -EIO;
+
+ } while (val & eio_chan_reg(i2c_chan, I2C_ECTRL_RST, SMB_HC2_SRESET));
+
+ /* Wait INUSE */
+ time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+
+ do {
+ fsleep(cnt);
+
+ if (ktime_after(ktime_get(), time_end)) {
+ dev_err(eio_dev(i2c_chan), "Timeout: I2C bus in use\n");
+ return -ETIME;
+ }
+
+ if (eio_reg_read(i2c_chan,
+ eio_chan_reg(i2c_chan, I2C_REG_SEM, SMB_REG_HS),
+ &val))
+ return -EIO;
+
+ } while (val & eio_chan_reg(i2c_chan, I2C_SEM_INUSE, SMB_HS_INUSE));
+
+ return 0;
+}
+
+static int let_stop(struct eio_i2c_chan *i2c_chan)
+{
+ unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_CTRL, SMB_REG_HC);
+ unsigned int target = eio_chan_reg(i2c_chan, I2C_CTRL_STOP, SMB_HC_LAST_BYTE);
+
+ return eio_reg_or(i2c_chan, reg, target);
+}
+
+static int clr_inuse(struct eio_i2c_chan *i2c_chan)
+{
+ if (is_i2c(i2c_chan))
+ return eio_reg_write(i2c_chan, I2C_REG_SEM, I2C_SEM_INUSE);
+
+ return eio_reg_or(i2c_chan, SMB_REG_HS, SMB_HS_INUSE);
+}
+
+static int bus_stop(struct eio_i2c_chan *i2c_chan)
+{
+ ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+ unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_CTRL, SMB_REG_HC);
+ unsigned int target = eio_chan_reg(i2c_chan, I2C_CTRL_STOP, SMB_HC_LAST_BYTE);
+ unsigned int val = 0;
+ int cnt = 0;
+
+ /* Set STOP bit */
+ eio_reg_or(i2c_chan, reg, target);
+
+ /* Wait until STOP bit clears */
+ do {
+ fsleep(cnt++);
+
+ if (ktime_after(ktime_get(), time_end))
+ return -ETIME;
+
+ if (eio_reg_read(i2c_chan, reg, &val))
+ return -EIO;
+
+ } while (val & target);
+
+ return 0;
+}
+
+static void switch_i2c_mode(struct eio_i2c_chan *i2c_chan, bool on)
+{
+ u32 tmp;
+
+ if (is_i2c(i2c_chan))
+ return;
+
+ if (eio_reg_read(i2c_chan, SMB_REG_HC2, &tmp))
+ return;
+
+ eio_reg_write(i2c_chan, SMB_REG_HC2,
+ on ? (tmp | SMB_HC2_I2C_EN | SMB_HC2_SRESET)
+ : (tmp & ~SMB_HC2_I2C_EN));
+}
+
+static void i2c_clear(struct eio_i2c_chan *i2c_chan)
+{
+ if (is_i2c(i2c_chan)) {
+ eio_reg_write(i2c_chan, I2C_REG_STAT, 0xFF);
+ } else {
+ eio_reg_or(i2c_chan, SMB_REG_HS, 0xA9);
+ eio_reg_or(i2c_chan, SMB_REG_HS2, 0x4C);
+ }
+}
+
+static int wait_write_done(struct eio_i2c_chan *i2c_chan, bool no_ack)
+{
+ ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+ unsigned int val = 0;
+ int cnt = 0;
+ unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_STAT, SMB_REG_HS);
+ unsigned int target = eio_chan_reg(i2c_chan, I2C_STAT_TXDONE, SMB_HS_TX_DONE);
+
+ do {
+ fsleep(cnt++);
+ if (ktime_after(ktime_get(), time_end)) {
+ if (is_i2c(i2c_chan)) {
+ eio_reg_or(i2c_chan, I2C_REG_STAT, 0);
+ } else {
+ eio_reg_or(i2c_chan, SMB_REG_HS, 0);
+ eio_reg_or(i2c_chan, SMB_REG_HS2, 0);
+ }
+ dev_err(eio_dev(i2c_chan), "wait write complete timeout %X %X\n",
+ val, target);
+ return -ETIME;
+ }
+ if (eio_reg_read(i2c_chan, reg, &val))
+ return -EIO;
+
+ } while ((val & target) == 0);
+
+ if (no_ack)
+ return 0;
+
+ if (is_i2c(i2c_chan)) {
+ eio_reg_or(i2c_chan, I2C_REG_STAT, 0);
+ return (val & I2C_STAT_NAK_ERR) ? -EIO : 0;
+ }
+
+ eio_reg_or(i2c_chan, SMB_REG_HS, 0);
+ if (eio_reg_read(i2c_chan, SMB_REG_HS2, &val))
+ return -EIO;
+ eio_reg_write(i2c_chan, SMB_REG_HS2, val);
+
+ return (val & SMB_HS2_NACK_ERR) ? -EIO : 0;
+}
+
+static int wait_ready(struct eio_i2c_chan *i2c_chan)
+{
+ int ret;
+
+ ret = wait_bus_free(i2c_chan);
+ if (ret)
+ return ret;
+
+ if (wait_busy(i2c_chan) == 0)
+ return 0;
+
+ reset_bus(i2c_chan);
+
+ return wait_busy(i2c_chan);
+}
+
+static int write_addr(struct eio_i2c_chan *i2c_chan, int addr, bool no_ack)
+{
+ eio_reg_write(i2c_chan, eio_chan_reg(i2c_chan, I2C_REG_ADDR, SMB_REG_HADDR),
+ addr);
+
+ return wait_write_done(i2c_chan, no_ack);
+}
+
+static int write_data(struct eio_i2c_chan *i2c_chan, int data, bool no_ack)
+{
+ eio_reg_write(i2c_chan, eio_chan_reg(i2c_chan, I2C_REG_DATA, SMB_REG_HD0),
+ data);
+
+ return wait_write_done(i2c_chan, no_ack);
+}
+
+static int read_data(struct eio_i2c_chan *i2c_chan, u8 *data)
+{
+ unsigned int val = 0, tmp;
+ int cnt = 0;
+ ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+ unsigned int stat = eio_chan_reg(i2c_chan, I2C_REG_STAT, SMB_REG_HS);
+ unsigned int target = eio_chan_reg(i2c_chan, I2C_STAT_RXREADY, SMB_HS_RX_READY);
+ unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_DATA, SMB_REG_HD0);
+
+ do {
+ fsleep(cnt++);
+
+ if (ktime_after(ktime_get(), time_end)) {
+ eio_reg_or(i2c_chan, stat, 0);
+ dev_err(eio_dev(i2c_chan), "read data timeout\n");
+ return -ETIME;
+ }
+
+ if (eio_reg_read(i2c_chan, stat, &val))
+ return -EIO;
+
+ } while ((val & target) != target);
+
+ /* clear status */
+ eio_reg_write(i2c_chan, stat, val);
+
+ /* Must read data after clearing status */
+ if (eio_reg_read(i2c_chan, reg, &tmp))
+ return -EIO;
+ *data = (u8)tmp;
+
+ return 0;
+}
+
+static int set_freq(struct eio_i2c_chan *i2c_chan, int freq)
+{
+ u8 pre1, pre2;
+ u16 speed;
+ unsigned int reg1 = eio_chan_reg(i2c_chan, I2C_REG_PRESCALE1, SMB_REG_HPRESCALE1);
+ unsigned int reg2 = eio_chan_reg(i2c_chan, I2C_REG_PRESCALE2, SMB_REG_HPRESCALE2);
+
+ dev_dbg(eio_dev(i2c_chan), "set freq: %dkHz\n", freq);
+ if (freq > I2C_FREQ_MAX || freq < I2C_FREQ_MIN) {
+ dev_err(eio_dev(i2c_chan), "Invalid i2c freq: %d\n", freq);
+ return -EINVAL;
+ }
+
+ speed = (freq < I2C_THRESHOLD_SCLH) ? I2C_SCLH_LOW : I2C_SCLH_HIGH;
+
+ pre1 = (u8)(CHIP_CLK / speed);
+ pre2 = (u8)((speed / freq) - 1);
+
+ if (freq > I2C_THRESHOLD_SCLH)
+ pre2 |= I2C_SCL_FAST_MODE;
+
+ eio_reg_write(i2c_chan, reg1, pre1);
+ eio_reg_write(i2c_chan, reg2, pre2);
+
+ return 0;
+}
+
+static int get_freq(struct eio_i2c_chan *i2c_chan, int *freq)
+{
+ int clk;
+ unsigned int pre1 = 0, pre2 = 0;
+ unsigned int reg1 = eio_chan_reg(i2c_chan, I2C_REG_PRESCALE1, SMB_REG_HPRESCALE1);
+ unsigned int reg2 = eio_chan_reg(i2c_chan, I2C_REG_PRESCALE2, SMB_REG_HPRESCALE2);
+
+ if (eio_reg_read(i2c_chan, reg1, &pre1))
+ return -EIO;
+ if (eio_reg_read(i2c_chan, reg2, &pre2))
+ return -EIO;
+
+ clk = (pre2 & I2C_SCL_FAST_MODE) ? I2C_SCLH_HIGH : I2C_SCLH_LOW;
+ pre2 &= ~I2C_SCL_FAST_MODE;
+
+ *freq = clk / ((int)pre2 + 1);
+
+ return 0;
+}
+
+static int smb_access(struct eio_i2c_chan *i2c_chan, u8 addr, bool is_read, u8 cmd,
+ int size, union i2c_smbus_data *data)
+{
+ int i, tmp, ret = 0;
+ unsigned int st1, st2;
+ int len = 0;
+
+ mutex_lock(&i2c_chan->lock);
+
+ ret = wait_ready(i2c_chan);
+ if (ret)
+ goto exit;
+
+ /* Force SMBus mode */
+ switch_i2c_mode(i2c_chan, false);
+
+ addr = eio_enc_7bit_addr(addr) | (is_read ? 1 : 0);
+ eio_reg_write(i2c_chan, SMB_REG_HADDR, addr);
+ eio_reg_write(i2c_chan, SMB_REG_HCMD, cmd);
+
+ dev_dbg(eio_dev(i2c_chan), "SMB[%d], addr:0x%02X, cmd:0x%02X size=%d\n",
+ i2c_chan->id, addr, cmd, size);
+
+ switch (size) {
+ case I2C_SMBUS_QUICK:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_QUICK\n");
+ break;
+
+ case I2C_SMBUS_BYTE:
+ if (!is_read) {
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE\n");
+ eio_reg_write(i2c_chan, SMB_REG_HCMD, cmd);
+ }
+ break;
+
+ case I2C_SMBUS_BYTE_DATA:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE_DATA\n");
+ if (!is_read) {
+ eio_reg_write(i2c_chan, SMB_REG_HD0, data->byte);
+ dev_dbg(eio_dev(i2c_chan), "write %X\n", data->byte);
+ }
+ break;
+
+ case I2C_SMBUS_WORD_DATA:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_WORD_DATA\n");
+ if (!is_read) {
+ eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]);
+ eio_reg_write(i2c_chan, SMB_REG_HD1, data->block[1]);
+ }
+ break;
+
+ case I2C_SMBUS_PROC_CALL:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_PROC_CALL\n");
+ eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]);
+ eio_reg_write(i2c_chan, SMB_REG_HD1, data->block[1]);
+ break;
+
+ case I2C_SMBUS_BLOCK_DATA:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_DATA\n");
+ if (is_read)
+ break;
+
+ /* Program command type */
+ eio_reg_read(i2c_chan, SMB_REG_HC, (unsigned int *)&tmp);
+ tmp &= ~(0x07 << SMB_HC_CMD_SHIFT);
+ tmp |= (size << SMB_HC_CMD_SHIFT);
+ eio_reg_write(i2c_chan, SMB_REG_HC, tmp);
+
+ /* Force write for payload stage */
+ eio_reg_write(i2c_chan, SMB_REG_HADDR, addr & ~0x01);
+
+ /* Reset internal buffer index pointer */
+ eio_reg_and(i2c_chan, SMB_REG_HC2, (int)~SMB_HC2_E32B);
+ eio_reg_or(i2c_chan, SMB_REG_HC2, SMB_HC2_E32B);
+
+ /* Write length + data */
+ eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]);
+ for (i = 1; i <= data->block[0]; i++)
+ eio_reg_write(i2c_chan, SMB_REG_HBLOCK, data->block[i]);
+ break;
+
+ case I2C_SMBUS_BLOCK_PROC_CALL:
+ /* Set command type field */
+ eio_reg_and(i2c_chan, SMB_REG_HC, (0x07 << SMB_HC_CMD_SHIFT));
+ eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]);
+
+ /* Reset buffer index */
+ eio_reg_and(i2c_chan, SMB_REG_HC2, (int)~SMB_HC2_E32B);
+ eio_reg_or(i2c_chan, SMB_REG_HC2, SMB_HC2_E32B);
+
+ for (i = 1; i <= data->block[0]; i++)
+ eio_reg_write(i2c_chan, SMB_REG_HBLOCK, data->block[i]);
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* Launch transaction */
+ eio_reg_read(i2c_chan, SMB_REG_HC, (unsigned int *)&tmp);
+ tmp &= ~(0x07 << SMB_HC_CMD_SHIFT);
+ tmp |= (size << SMB_HC_CMD_SHIFT) | SMB_HC_START;
+ tmp &= ~(SMB_HC_I2C_NACKEN | SMB_HC_KILL | SMB_HC_PEC_EN);
+ eio_reg_write(i2c_chan, SMB_REG_HC, tmp);
+
+ ret = wait_busy(i2c_chan);
+ if (ret)
+ goto exit;
+
+ eio_reg_read(i2c_chan, SMB_REG_HS, &st1);
+ eio_reg_read(i2c_chan, SMB_REG_HS2, &st2);
+
+ if (st1 & SMB_HS_FAILED) {
+ dev_err(eio_dev(i2c_chan), "HS FAILED\n");
+ ret = -EIO;
+ } else if (st1 & SMB_HS_ARL_ERR) {
+ dev_err(eio_dev(i2c_chan), "ARL FAILED\n");
+ ret = -EIO;
+ } else if (st2 & SMB_HS2_TO_ERR) {
+ dev_err(eio_dev(i2c_chan), "timeout\n");
+ ret = -ETIME;
+ } else if (st2 & SMB_HS2_NACK_ERR) {
+ dev_err(eio_dev(i2c_chan), "NACK err\n");
+ ret = -EIO;
+ } else if (st2 & SMB_HS2_PEC_ERR) {
+ dev_err(eio_dev(i2c_chan), "PEC err\n");
+ ret = -EIO;
+ }
+ if (ret)
+ goto exit;
+
+ switch (size) {
+ case I2C_SMBUS_QUICK:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_QUICK\n");
+ break;
+
+ case I2C_SMBUS_BYTE:
+ case I2C_SMBUS_BYTE_DATA:
+ if (is_read) {
+ unsigned int v;
+
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE/I2C_SMBUS_BYTE_DATA\n");
+ eio_reg_read(i2c_chan, SMB_REG_HD0, &v);
+ data->block[0] = (u8)v;
+ dev_dbg(eio_dev(i2c_chan), "read %X\n", data->block[0]);
+ }
+ break;
+
+ case I2C_SMBUS_WORD_DATA: {
+ unsigned int v0, v1;
+
+ if (is_read) {
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_WORD_DATA\n");
+ eio_reg_read(i2c_chan, SMB_REG_HD0, &v0);
+ eio_reg_read(i2c_chan, SMB_REG_HD1, &v1);
+ data->block[0] = (u8)v0;
+ data->block[1] = (u8)v1;
+ }
+ break;
+ }
+
+ case I2C_SMBUS_PROC_CALL: {
+ unsigned int v0, v1;
+
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_PROC_CALL\n");
+ eio_reg_read(i2c_chan, SMB_REG_HD0, &v0);
+ eio_reg_read(i2c_chan, SMB_REG_HD1, &v1);
+ data->block[0] = (u8)v0;
+ data->block[1] = (u8)v1;
+ break;
+ }
+
+ case I2C_SMBUS_BLOCK_DATA:
+ if (!is_read)
+ break;
+
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_DATA\n");
+ eio_reg_read(i2c_chan, SMB_REG_HD0, (unsigned int *)&len);
+ len = min(len, I2C_SMBUS_BLOCK_MAX);
+ data->block[0] = len;
+
+ for (i = 1; i <= len; i++)
+ eio_reg_read(i2c_chan, SMB_REG_HBLOCK,
+ (unsigned int *)&data->block[i]);
+ break;
+
+ default:
+ ret = -EINVAL;
+ goto exit;
+ }
+
+exit:
+ /* Clear latched status */
+ eio_reg_write(i2c_chan, SMB_REG_HS, 0xFF);
+ eio_reg_write(i2c_chan, SMB_REG_HS2, 0xFF);
+
+ mutex_unlock(&i2c_chan->lock);
+ return ret;
+}
+
+static int i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int nmsgs)
+{
+ int msg, data;
+ int addr = 0;
+ int dummy;
+ int ret = 0;
+ struct eio_i2c_chan *i2c_chan = i2c_get_adapdata(adap);
+
+ mutex_lock(&i2c_chan->lock);
+
+ ret = wait_ready(i2c_chan);
+ if (ret)
+ goto exit;
+
+ switch_i2c_mode(i2c_chan, true);
+
+ dev_dbg(eio_dev(i2c_chan), "Transmit %d I2C messages\n", nmsgs);
+ for (msg = 0; msg < nmsgs; msg++) {
+ int is_read = msgs[msg].flags & I2C_M_RD;
+ bool no_ack = msgs[msg].flags & I2C_M_IGNORE_NAK;
+
+ dev_dbg(eio_dev(i2c_chan), "message %d len=%d\n", msg, msgs[msg].len);
+
+ if (!msgs[msg].len)
+ let_stop(i2c_chan);
+
+ if (msgs[msg].flags & I2C_M_TEN) {
+ addr = eio_enc_10bit_addr(msgs[msg].addr);
+ addr |= is_read;
+ dev_dbg(eio_dev(i2c_chan), "10-bit addr: %X\n", addr);
+
+ ret = write_addr(i2c_chan, addr >> 8, no_ack);
+ if (!ret)
+ ret = write_data(i2c_chan, addr & 0x7F, no_ack);
+ } else {
+ addr = eio_enc_7bit_addr(msgs[msg].addr);
+ addr |= is_read;
+ dev_dbg(eio_dev(i2c_chan), "7-bit addr: %X\n", addr);
+
+ ret = write_addr(i2c_chan, addr, no_ack);
+ }
+
+ if (ret)
+ goto exit;
+
+ if (!msgs[msg].len)
+ goto exit;
+
+ if (is_read)
+ ret = eio_trigger_read(i2c_chan, (u32 *)&dummy);
+
+ /* Transmit all messages */
+ for (data = 0; data < msgs[msg].len; data++) {
+ if (msgs[msg].flags & I2C_M_RD) {
+ bool last = (msgs[msg].len == data + 1);
+
+ if (last)
+ let_stop(i2c_chan);
+
+ ret = read_data(i2c_chan, &msgs[msg].buf[data]);
+ dev_dbg(eio_dev(i2c_chan), "I2C read[%d] = %x\n",
+ data, msgs[msg].buf[data]);
+
+ /* Don't stop twice */
+ if (last && ret == 0)
+ goto exit;
+ } else {
+ ret = write_data(i2c_chan, msgs[msg].buf[data], no_ack);
+ dev_dbg(eio_dev(i2c_chan), "I2C write[%d] = %x\n",
+ data, msgs[msg].buf[data]);
+ }
+ if (ret)
+ goto exit;
+ }
+ }
+
+ if (!ret)
+ ret = bus_stop(i2c_chan);
+
+ if (!ret)
+ goto exit;
+
+exit:
+ if (ret)
+ reset_bus(i2c_chan);
+
+ i2c_clear(i2c_chan);
+ clr_inuse(i2c_chan);
+
+ mutex_unlock(&i2c_chan->lock);
+ return ret ? ret : nmsgs;
+}
+
+static int smbus_xfer(struct i2c_adapter *adap, u16 addr,
+ u16 flags, char is_read, u8 cmd,
+ int size, union i2c_smbus_data *data)
+{
+ int ret;
+ struct eio_i2c_chan *i2c_chan = i2c_get_adapdata(adap);
+ int nmsgs = is_read ? 2 : 1;
+ u8 buf[I2C_SMBUS_BLOCK_MAX + sizeof(u32)] = { cmd, };
+ struct i2c_msg msgs[2] = {
+ { .addr = addr, .flags = flags & ~I2C_M_RD, .buf = buf + 0 },
+ { .addr = addr, .flags = flags | I2C_M_RD, .buf = buf + 1 },
+ };
+
+ /* Non-I2C channels use the SMB engine, except I2C block variants we emulate */
+ if (!is_i2c(i2c_chan) && size != I2C_SMBUS_I2C_BLOCK_DATA)
+ return smb_access(i2c_chan, addr, is_read, cmd, size, data);
+
+ if (data) {
+ buf[0] = cmd;
+ /* FIX: preserve other flags; only toggle I2C_M_RD */
+ msgs[0].flags = is_read ? (flags | I2C_M_RD) : (flags & ~I2C_M_RD);
+ msgs[1].buf = data->block;
+ }
+
+ switch (size) {
+ case I2C_SMBUS_QUICK:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_QUICK on I2C\n");
+ nmsgs = 1;
+ break;
+
+ case I2C_SMBUS_BYTE:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE on I2C\n");
+ nmsgs = 1;
+ msgs[0].len = 1;
+ msgs[0].buf = is_read ? data->block : buf;
+ msgs[0].flags = is_read ? (flags | I2C_M_RD) : (flags & ~I2C_M_RD);
+ break;
+
+ case I2C_SMBUS_BYTE_DATA:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE_DATA on I2C\n");
+ if (!data)
+ return -EINVAL;
+ msgs[0].len = is_read ? 1 : 2;
+ buf[1] = data->block[0];
+ msgs[1].len = 1;
+ break;
+
+ case I2C_SMBUS_WORD_DATA:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_WORD_DATA on I2C\n");
+ if (!data)
+ return -EINVAL;
+ msgs[0].len = is_read ? 1 : 3;
+ msgs[1].len = 2;
+ buf[1] = data->block[0];
+ buf[2] = data->block[1];
+ msgs[1].buf = data->block;
+ break;
+
+ case I2C_SMBUS_I2C_BLOCK_DATA:
+ case I2C_SMBUS_I2C_BLOCK_BROKEN:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_I2C_BLOCK_(DATA/BROKEN) on I2C len=%d\n",
+ data->block[0]);
+ if (!data)
+ return -EINVAL;
+ msgs[0].len = is_read ? 1 : data->block[0] + 1;
+ msgs[1].len = data->block[0];
+ msgs[1].buf = data->block + 1;
+ if (msgs[0].len >= I2C_SMBUS_BLOCK_MAX ||
+ msgs[1].len >= I2C_SMBUS_BLOCK_MAX)
+ return -EINVAL;
+ if (!is_read)
+ memcpy(buf + 1, data->block + 1, msgs[0].len);
+ break;
+
+ case I2C_SMBUS_PROC_CALL:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_PROC_CALL on I2C\n");
+ if (!data)
+ return -EINVAL;
+ nmsgs = 2;
+ msgs[0].flags = flags & ~I2C_M_RD;
+ msgs[0].len = 3;
+ buf[1] = data->block[0];
+ buf[2] = data->block[1];
+ msgs[1].len = 2;
+ break;
+
+ case I2C_SMBUS_BLOCK_DATA:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_DATA on I2C not supported\n");
+ return -EINVAL;
+
+ case I2C_SMBUS_BLOCK_PROC_CALL:
+ dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_PROC_CALL on I2C not supported\n");
+ return -EINVAL;
+
+ default:
+ return -EINVAL;
+ }
+
+ ret = i2c_xfer(adap, msgs, nmsgs);
+ return ret < 0 ? ret : 0;
+}
+
+static int load_i2c(struct device *dev, enum eio_chan_id id,
+ struct eio_i2c_chan *i2c_chan)
+{
+ u32 base_lo, base_hi, base;
+ int ldn = LDN_I2C0 + id;
+ struct eio_i2c_dev *eio_i2c = i2c_chan->parent;
+ struct regmap *map;
+
+ if (!eio_i2c || !eio_i2c->regmap)
+ return dev_err_probe(dev, -ENODEV, "missing parent/regmap\n");
+
+ map = eio_i2c->regmap;
+
+ /* Read channel I/O base via shared PNP window */
+ mutex_lock(&eio_i2c->pnp_mutex);
+ if (regmap_write(map, REG_PNP_INDEX, REG_EXT_MODE_ENTER) ||
+ regmap_write(map, REG_PNP_INDEX, REG_EXT_MODE_ENTER) ||
+ regmap_write(map, REG_PNP_INDEX, REG_LDN) ||
+ regmap_write(map, REG_PNP_DATA, ldn) ||
+ regmap_write(map, REG_PNP_INDEX, REG_BASE_HI) ||
+ regmap_read(map, REG_PNP_DATA, &base_hi) ||
+ regmap_write(map, REG_PNP_INDEX, REG_BASE_LO) ||
+ regmap_read(map, REG_PNP_DATA, &base_lo) ||
+ regmap_write(map, REG_PNP_INDEX, REG_EXT_MODE_EXIT)) {
+ mutex_unlock(&eio_i2c->pnp_mutex);
+ dev_err(dev, "error read/write I2C[%d] IO port\n", id);
+ return -EIO;
+ }
+ mutex_unlock(&eio_i2c->pnp_mutex);
+
+ base = (base_hi << 8) | base_lo;
+ if (base == 0xFFFF || base == 0) {
+ dev_dbg(dev, "i2c[%d] base addr=%#x (not in-use)\n", id, base);
+ return -ENODEV;
+ }
+
+ dev_dbg(dev, "i2c[%d] base addr=%#x\n", id, base);
+
+ /* Bind channel (no per-chan dev) */
+ i2c_chan->base = (u16)base;
+ i2c_chan->id = id;
+
+ /* Per-channel frequency policy */
+ if (i2c_chan->freq_override != USE_DEFAULT)
+ set_freq(i2c_chan, i2c_chan->freq_override);
+
+ get_freq(i2c_chan, &i2c_chan->freq_override);
+
+ return 0;
+}
+
+static u32 functionality(struct i2c_adapter *adap)
+{
+ struct eio_i2c_chan *i2c_chan = i2c_get_adapdata(adap);
+
+ return is_i2c(i2c_chan) ? SUPPORTED_I2C : SUPPORTED_SMB;
+}
+
+static const struct i2c_algorithm algo = {
+ .smbus_xfer = smbus_xfer,
+ .master_xfer = i2c_xfer,
+ .functionality = functionality,
+};
+
+static int eio_i2c_probe(struct platform_device *pdev)
+{
+ static const char * const names[] = { "i2c0", "i2c1", "smb0", "smb1" };
+ struct device *dev = &pdev->dev;
+ struct eio_i2c_dev *eio_i2c;
+ struct eio_dev *eio_dev = dev_get_drvdata(dev->parent);
+ int ret = 0;
+ enum eio_chan_id ch;
+
+ if (!eio_dev) {
+ dev_err(dev, "Error contact eio_core\n");
+ return -ENODEV;
+ }
+
+ eio_i2c = devm_kzalloc(dev, sizeof(*eio_i2c), GFP_KERNEL);
+ if (!eio_i2c)
+ return -ENOMEM;
+
+ eio_i2c->dev = dev;
+ eio_i2c->mfd = dev->parent;
+ eio_i2c->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!eio_i2c->regmap)
+ return dev_err_probe(dev, -ENODEV, "parent regmap not found\n");
+
+ mutex_init(&eio_i2c->pnp_mutex);
+ platform_set_drvdata(pdev, eio_i2c);
+
+ for (ch = EIO_I2C0; ch < MAX_I2C_SMB; ch++) {
+ struct eio_i2c_chan *i2c_chan;
+
+ i2c_chan = devm_kzalloc(dev, sizeof(*i2c_chan), GFP_KERNEL);
+ if (!i2c_chan) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ i2c_chan->parent = eio_i2c;
+ i2c_chan->freq_override = USE_DEFAULT;
+ mutex_init(&i2c_chan->lock);
+
+ if (load_i2c(dev, ch, i2c_chan)) {
+ dev_info(dev, "No %s%d!\n", (ch < 2) ? "I2C" : "SMBus", ch & 1);
+ continue;
+ }
+
+ i2c_chan->adap.owner = THIS_MODULE;
+ i2c_chan->adap.class = I2C_CLASS_HWMON;
+ i2c_chan->adap.algo = &algo;
+ i2c_chan->adap.dev.parent = dev;
+ snprintf(i2c_chan->adap.name, sizeof(i2c_chan->adap.name), "eio-%s",
+ names[ch]);
+
+ i2c_set_adapdata(&i2c_chan->adap, i2c_chan);
+
+ ret = i2c_add_adapter(&i2c_chan->adap);
+ dev_info(dev, "Add %s%d %s. %d\n", (ch < 2) ? "I2C" : "SMBus",
+ ch, ret ? "Error" : "Success", ret);
+ if (ret)
+ break;
+
+ eio_i2c->chan[ch] = i2c_chan;
+ }
+
+ if (ret) {
+ for (ch = EIO_I2C0; ch < MAX_I2C_SMB; ch++) {
+ if (eio_i2c->chan[ch]) {
+ i2c_del_adapter(&eio_i2c->chan[ch]->adap);
+ eio_i2c->chan[ch] = NULL;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static void eio_i2c_remove(struct platform_device *pdev)
+{
+ struct eio_i2c_dev *eio_i2c = platform_get_drvdata(pdev);
+ enum eio_chan_id ch;
+
+ for (ch = EIO_I2C0; ch < MAX_I2C_SMB; ch++) {
+ if (eio_i2c->chan[ch]) {
+ i2c_del_adapter(&eio_i2c->chan[ch]->adap);
+ eio_i2c->chan[ch] = NULL;
+ }
+ }
+}
+
+static struct platform_driver eio_i2c_driver = {
+ .probe = eio_i2c_probe,
+ .remove = eio_i2c_remove,
+ .driver = {
+ .name = "i2c_eio",
+ },
+};
+
+module_platform_driver(eio_i2c_driver);
+
+MODULE_AUTHOR("Wenkai Chung <wenkai.chung@advantech.com.tw>");
+MODULE_AUTHOR("Ramiro Oliveira <ramiro.oliveira@advantech.com>");
+MODULE_DESCRIPTION("I2C driver for Advantech EIO embedded controller");
+MODULE_LICENSE("GPL");
+MODULE_SOFTDEP("pre: eio_core");
--
2.43.0
^ permalink raw reply related
* [PATCH v2 5/8] Add Advantech EIO Backlight driver
From: Ramiro Oliveira @ 2026-07-14 15:54 UTC (permalink / raw)
To: Lee Jones, Linus Walleij, Bartosz Golaszewski, Guenter Roeck,
Andi Shyti, Daniel Thompson, Jingoo Han, Helge Deller,
Wim Van Sebroeck, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba
Cc: linux-kernel, mfd, linux-gpio, linux-hwmon, linux-i2c, dri-devel,
linux-fbdev, linux-watchdog, linux-pm, Wenkai Chung,
Francisco Aragon-Trivino, Hongzhi Wang, Mikhail Tsukerman,
Thomas Kastner, Ramiro Oliveira
In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com>
This driver controls the Video Backlight block of the Advantech EIO chip.
Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
MAINTAINERS | 1 +
drivers/video/backlight/Kconfig | 6 +
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/eio_bl.c | 243 +++++++++++++++++++++++++++++++++++++++
4 files changed, 251 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index e228d12a2326..d2d7d815ed44 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -616,6 +616,7 @@ F: drivers/gpio/gpio-eio.c
F: drivers/hwmon/eio-hwmon.c
F: drivers/i2c/busses/i2c-eio.c
F: drivers/mfd/eio_core.c
+F: drivers/video/backlight/eio_bl.c
F: include/linux/mfd/eio.h
ADVANTECH SWBTN DRIVER
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 7c66b8840d88..a5559a7db75d 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -522,6 +522,12 @@ config BACKLIGHT_RAVE_SP
help
Support for backlight control on RAVE SP device.
+config BACKLIGHT_EIO
+ tristate "Advantech EIO Backlight"
+ depends on MFD_EIO && BACKLIGHT_CLASS_DEVICE
+ help
+ Backlight driver for Advantech EIO.
+
config BACKLIGHT_LED
tristate "Generic LED based Backlight Driver"
depends on LEDS_CLASS && OF
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 34469711c6cd..724246242526 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_BACKLIGHT_CGBC) += cgbc_bl.o
obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o
obj-$(CONFIG_BACKLIGHT_DA903X) += da903x_bl.o
obj-$(CONFIG_BACKLIGHT_DA9052) += da9052_bl.o
+obj-$(CONFIG_BACKLIGHT_EIO) += eio_bl.o
obj-$(CONFIG_BACKLIGHT_EP93XX) += ep93xx_bl.o
obj-$(CONFIG_BACKLIGHT_GPIO) += gpio_backlight.o
obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o
diff --git a/drivers/video/backlight/eio_bl.c b/drivers/video/backlight/eio_bl.c
new file mode 100644
index 000000000000..c2826400de45
--- /dev/null
+++ b/drivers/video/backlight/eio_bl.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Backlight driver for Advantech EIO Embedded controller.
+ *
+ * Copyright (C) 2025 Advantech Corporation. All rights reserved.
+ */
+
+#include <linux/backlight.h>
+#include <linux/errno.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+
+#define PMC_BL_WRITE 0x20
+#define PMC_BL_READ 0x21
+
+#define BL_CTRL_STATUS 0x00
+#define BL_CTRL_ENABLE 0x12
+#define BL_CTRL_ENABLE_INVERT 0x13
+#define BL_CTRL_DUTY 0x14
+#define BL_CTRL_INVERT 0x15
+#define BL_CTRL_FREQ 0x16
+
+#define BL_MAX 2
+
+#define BL_STATUS_AVAIL 0x01
+#define BL_ENABLE_OFF 0x00
+#define BL_ENABLE_ON 0x01
+#define BL_ENABLE_AUTO BIT(1)
+
+#define USE_DEFAULT -1
+#define THERMAL_MAX 100
+
+static uint bri_freq = USE_DEFAULT;
+module_param(bri_freq, uint, 0444);
+MODULE_PARM_DESC(bri_freq, "Setup backlight PWM frequency.\n");
+
+static int bri_invert = USE_DEFAULT;
+module_param(bri_invert, int, 0444);
+MODULE_PARM_DESC(bri_invert, "Setup backlight PWM polarity.\n");
+
+static int bl_power_invert = USE_DEFAULT;
+module_param(bl_power_invert, int, 0444);
+MODULE_PARM_DESC(bl_power_invert, "Setup backlight enable pin polarity.\n");
+
+struct eio_bl_dev {
+ struct device *mfd;
+ u8 id;
+};
+
+static int pmc_write(struct device *mfd, u8 ctrl, u8 dev_id, void *data)
+{
+ struct pmc_op op = {
+ .cmd = PMC_BL_WRITE,
+ .control = ctrl,
+ .device_id = dev_id,
+ .payload = (u8 *)data,
+ .size = (ctrl == BL_CTRL_FREQ) ? 4 : 1,
+ };
+
+ return eio_core_pmc_operation(mfd, &op);
+}
+
+static int pmc_read(struct device *mfd, u8 ctrl, u8 dev_id, void *data)
+{
+ struct pmc_op op = {
+ .cmd = PMC_BL_READ,
+ .control = ctrl,
+ .device_id = dev_id,
+ .payload = (u8 *)data,
+ .size = (ctrl == BL_CTRL_FREQ) ? 4 : 1,
+ };
+
+ return eio_core_pmc_operation(mfd, &op);
+}
+
+static int bl_update_status(struct backlight_device *bl)
+{
+ struct eio_bl_dev *eio_bl = bl_get_data(bl);
+ u8 duty = clamp_val(backlight_get_brightness(bl), 0, THERMAL_MAX);
+ u8 sw = backlight_is_blank(bl);
+ int ret;
+
+ /* Setup PWM duty */
+ ret = pmc_write(eio_bl->mfd, BL_CTRL_DUTY, eio_bl->id, &duty);
+ if (ret)
+ return ret;
+
+ /* Setup backlight enable pin */
+ return pmc_write(eio_bl->mfd, BL_CTRL_ENABLE, eio_bl->id, &sw);
+}
+
+static int bl_get_brightness(struct backlight_device *bl)
+{
+ struct eio_bl_dev *eio_bl = bl_get_data(bl);
+ u8 duty = 0;
+ int ret;
+
+ ret = pmc_read(eio_bl->mfd, BL_CTRL_DUTY, eio_bl->id, &duty);
+
+ if (ret)
+ return ret;
+
+ return duty;
+}
+
+static const struct backlight_ops bl_ops = {
+ .get_brightness = bl_get_brightness,
+ .update_status = bl_update_status,
+ .options = BL_CORE_SUSPENDRESUME,
+};
+
+static int bl_init(struct device *dev, int id,
+ struct backlight_properties *props)
+{
+ int ret;
+ u8 enabled = 0;
+ u8 status = 0;
+
+ /* Check EC-supported backlight */
+ ret = pmc_read(dev, BL_CTRL_STATUS, id, &status);
+ if (ret)
+ return ret;
+
+ if (!(status & BL_STATUS_AVAIL)) {
+ dev_dbg(dev, "eio_bl%d hardware report disabled.\n", id);
+ return -ENODEV;
+ }
+
+ ret = pmc_read(dev, BL_CTRL_DUTY, id, &props->brightness);
+ if (ret)
+ return ret;
+
+ /* Invert PWM */
+ if (bri_invert > USE_DEFAULT) {
+ ret = pmc_write(dev, BL_CTRL_INVERT, id, &bri_invert);
+ if (ret)
+ return ret;
+ }
+
+ ret = pmc_read(dev, BL_CTRL_INVERT, id, &bri_invert);
+ if (ret)
+ return ret;
+
+ if (bri_freq != USE_DEFAULT) {
+ ret = pmc_write(dev, BL_CTRL_FREQ, id, &bri_freq);
+ if (ret)
+ return ret;
+ }
+
+ ret = pmc_read(dev, BL_CTRL_FREQ, id, &bri_freq);
+ if (ret)
+ return ret;
+
+ if (bl_power_invert >= USE_DEFAULT) {
+ ret = pmc_write(dev, BL_CTRL_ENABLE_INVERT, id, &bl_power_invert);
+ if (ret)
+ return ret;
+ }
+
+ ret = pmc_read(dev, BL_CTRL_ENABLE_INVERT, id, &bl_power_invert);
+ if (ret)
+ return ret;
+
+ /* Read power state */
+ ret = pmc_read(dev, BL_CTRL_ENABLE, id, &enabled);
+ if (ret)
+ return ret;
+
+ props->power = enabled ? BACKLIGHT_POWER_OFF : BACKLIGHT_POWER_ON;
+
+ return 0;
+}
+
+static int bl_probe(struct platform_device *pdev)
+{
+ u8 id;
+ int ret;
+ struct device *dev = &pdev->dev;
+ struct eio_dev *eio_dev = dev_get_drvdata(dev->parent);
+
+ if (!eio_dev) {
+ dev_err(dev, "eio_core not present\n");
+ return -ENODEV;
+ }
+
+ for (id = 0; id < BL_MAX; id++) {
+ char name[32];
+ struct backlight_properties props;
+ struct eio_bl_dev *eio_bl;
+ struct backlight_device *bl;
+
+ memset(&props, 0, sizeof(props));
+ props.type = BACKLIGHT_RAW;
+ props.max_brightness = THERMAL_MAX;
+ props.power = BACKLIGHT_POWER_OFF;
+ props.brightness = props.max_brightness;
+ props.scale = BACKLIGHT_SCALE_NON_LINEAR;
+
+ eio_bl = devm_kzalloc(dev, sizeof(*eio_bl), GFP_KERNEL);
+ if (!eio_bl)
+ return -ENOMEM;
+
+ eio_bl->mfd = dev->parent;
+ eio_bl->id = id;
+
+ ret = bl_init(eio_bl->mfd, id, &props);
+ if (ret) {
+ dev_info(dev, "%d No Backlight %u enabled!\n", ret, id);
+ continue;
+ }
+
+ snprintf(name, sizeof(name), "%s%u", pdev->name, id);
+
+ bl = devm_backlight_device_register(dev, name, dev, eio_bl,
+ &bl_ops, &props);
+
+ if (IS_ERR(bl)) {
+ ret = PTR_ERR(bl);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+
+ dev_err(dev, "register %s failed: %d\n", name, ret);
+ continue;
+ }
+ }
+ return ret;
+}
+
+static struct platform_driver bl_driver = {
+ .probe = bl_probe,
+ .driver = {
+ .name = "eio_bl",
+ },
+};
+
+module_platform_driver(bl_driver);
+
+MODULE_AUTHOR("Wenkai Chung <wenkai.chung@advantech.com.tw>");
+MODULE_AUTHOR("Ramiro Oliveira <ramiro.oliveira@advantech.com>");
+MODULE_DESCRIPTION("Backlight driver for Advantech EIO embedded controller");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v2 6/8] Add Advantech EIO Watchdog driver
From: Ramiro Oliveira @ 2026-07-14 15:54 UTC (permalink / raw)
To: Lee Jones, Linus Walleij, Bartosz Golaszewski, Guenter Roeck,
Andi Shyti, Daniel Thompson, Jingoo Han, Helge Deller,
Wim Van Sebroeck, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba
Cc: linux-kernel, mfd, linux-gpio, linux-hwmon, linux-i2c, dri-devel,
linux-fbdev, linux-watchdog, linux-pm, Wenkai Chung,
Francisco Aragon-Trivino, Hongzhi Wang, Mikhail Tsukerman,
Thomas Kastner, Ramiro Oliveira
In-Reply-To: <20260714-upstream-v2-v2-0-76e5e41026db@advantech.com>
This commit adds the driver to control the Advantech EIO Watchdog block,
this block is included in the Advantech EIO Embedded Controller.
Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
MAINTAINERS | 1 +
drivers/watchdog/Kconfig | 7 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/eio_wdt.c | 641 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 650 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index d2d7d815ed44..8d35fd7c5599 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -617,6 +617,7 @@ F: drivers/hwmon/eio-hwmon.c
F: drivers/i2c/busses/i2c-eio.c
F: drivers/mfd/eio_core.c
F: drivers/video/backlight/eio_bl.c
+F: drivers/watchdog/eio_wdt.c
F: include/linux/mfd/eio.h
ADVANTECH SWBTN DRIVER
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 9f013d774897..bc0f13f4f79c 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -265,6 +265,13 @@ config DA9062_WATCHDOG
This driver can be built as a module. The module name is da9062_wdt.
+config EIO_WATCHDOG
+ tristate "Advantech EIO Watchdog"
+ depends on MFD_EIO
+ help
+ Watchdog timer driver for the Advantech EIO.
+ If unsure, say N.
+
config GPIO_WATCHDOG
tristate "Watchdog device controlled through GPIO-line"
depends on GPIOLIB && (ACPI || OF)
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index cb78e9932eae..eee059cfe1da 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -229,6 +229,7 @@ obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
+obj-$(CONFIG_EIO_WATCHDOG) += eio_wdt.o
obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o
obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
diff --git a/drivers/watchdog/eio_wdt.c b/drivers/watchdog/eio_wdt.c
new file mode 100644
index 000000000000..b3c036a004b3
--- /dev/null
+++ b/drivers/watchdog/eio_wdt.c
@@ -0,0 +1,641 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Advantech EIO Watchdog Driver
+ *
+ * Copyright (C) 2025 Advantech Co., Ltd.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+
+#define WATCHDOG_TIMEOUT 60
+#define WATCHDOG_PRETIMEOUT 10
+
+/* Support Flags */
+#define SUPPORT_AVAILABLE BIT(0)
+#define SUPPORT_PWRBTN BIT(3)
+#define SUPPORT_IRQ BIT(4)
+#define SUPPORT_SCI BIT(5)
+#define SUPPORT_PIN BIT(6)
+#define SUPPORT_RESET BIT(7)
+
+/* PMC registers */
+#define REG_STATUS 0x00
+#define REG_CONTROL 0x02
+#define REG_EVENT 0x10
+#define REG_PWR_EVENT_TIME 0x12
+#define REG_IRQ_EVENT_TIME 0x13
+#define REG_RESET_EVENT_TIME 0x14
+#define REG_PIN_EVENT_TIME 0x15
+#define REG_SCI_EVENT_TIME 0x16
+#define REG_IRQ_NUMBER 0x17
+
+/* PMC command and control */
+#define CMD_WDT_WRITE 0x2A
+#define CMD_WDT_READ 0x2B
+#define CTRL_STOP 0x00
+#define CTRL_START 0x01
+#define CTRL_TRIGGER 0x02
+
+/* I/O register and its flags */
+#define IOREG_UNLOCK 0x87
+#define IOREG_LOCK 0xAA
+#define IOREG_LDN 0x07
+#define IOREG_LDN_PMCIO 0x0F
+#define IOREG_IRQ 0x70
+#define IOREG_WDT_STATUS 0x30
+
+/* Flags */
+#define FLAG_WDT_ENABLED 0x01
+#define FLAG_TRIGGER_IRQ BIT(4)
+
+/* Mapping event type to supported bit */
+#define EVENT_BIT(type) BIT(type + 2)
+
+enum event_type {
+ EVENT_NONE,
+ EVENT_PWRBTN,
+ EVENT_IRQ,
+ EVENT_SCI,
+ EVENT_PIN
+};
+
+struct eio_wdt_dev {
+ u32 event_type;
+ u32 support;
+ int irq;
+ unsigned long last_time;
+ struct regmap *iomap;
+ struct device *mfd;
+ struct device *dev;
+ struct watchdog_device wdd;
+ struct eio_dev *core;
+};
+
+static char * const type_strs[] = {
+ "NONE",
+ "PWRBTN",
+ "IRQ",
+ "SCI",
+ "PIN",
+};
+
+static u32 type_regs[] = {
+ REG_RESET_EVENT_TIME,
+ REG_PWR_EVENT_TIME,
+ REG_IRQ_EVENT_TIME,
+ REG_SCI_EVENT_TIME,
+ REG_PIN_EVENT_TIME,
+};
+
+/* Specify the pin triggered on pretimeout or timeout */
+static char *event_type = "NONE";
+module_param(event_type, charp, 0);
+MODULE_PARM_DESC(event_type, "Watchdog timeout event type (NONE, PWRBTN, IRQ, SCI, PIN)");
+
+static int pmc_write(struct device *dev, u8 ctrl, void *data)
+{
+ struct pmc_op op = {
+ .cmd = CMD_WDT_WRITE,
+ .control = ctrl,
+ .payload = data,
+ .size = (ctrl <= REG_EVENT) ? 1 :
+ (ctrl >= REG_IRQ_NUMBER) ? 1 : 4,
+ };
+ return eio_core_pmc_operation(dev, &op);
+}
+
+static int pmc_read(struct device *dev, u8 ctrl, void *data)
+{
+ struct pmc_op op = {
+ .cmd = CMD_WDT_READ,
+ .control = ctrl,
+ .payload = data,
+ .size = (ctrl <= REG_EVENT) ? 1 :
+ (ctrl >= REG_IRQ_NUMBER) ? 1 : 4,
+ };
+ return eio_core_pmc_operation(dev, &op);
+}
+
+static int wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
+{
+ wdd->timeout = timeout;
+ return 0;
+}
+
+static int wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int pretimeout)
+{
+ wdd->pretimeout = pretimeout;
+ return 0;
+}
+
+static int wdt_get_type(struct eio_wdt_dev *eio_wdt)
+{
+ int i;
+
+ for (i = 1; i < ARRAY_SIZE(type_strs); i++) {
+ if (strcasecmp(event_type, type_strs[i]) == 0) {
+ if ((eio_wdt->support & EVENT_BIT(i)) == 0) {
+ dev_err(eio_wdt->dev,
+ "This board doesn't support %s trigger type\n",
+ event_type);
+ return -EINVAL;
+ }
+ eio_wdt->event_type = i;
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static int get_time(struct eio_wdt_dev *eio_wdt, u8 ctrl, u32 *val)
+{
+ int ret;
+
+ ret = pmc_read(eio_wdt->mfd, ctrl, val);
+ if (ret)
+ return ret;
+
+ /* ms to sec */
+ *val /= 1000;
+
+ return 0;
+}
+
+static int set_time(struct eio_wdt_dev *eio_wdt, u8 ctrl, u32 time)
+{
+ /* sec to ms */
+ time *= 1000;
+
+ return pmc_write(eio_wdt->mfd, ctrl, &time);
+}
+
+static int wdt_set_config(struct eio_wdt_dev *eio_wdt)
+{
+ int ret, type;
+ u32 event_time = 0;
+ u32 reset_time = 0;
+
+ if (eio_wdt->event_type > EVENT_PIN)
+ return -EFAULT;
+
+ /* Calculate event time and reset time */
+ if (eio_wdt->wdd.pretimeout && eio_wdt->wdd.timeout) {
+ if (eio_wdt->wdd.timeout < eio_wdt->wdd.pretimeout)
+ return -EINVAL;
+
+ reset_time = eio_wdt->wdd.timeout;
+ event_time = eio_wdt->wdd.timeout - eio_wdt->wdd.pretimeout;
+
+ } else if (eio_wdt->wdd.timeout) {
+ reset_time = eio_wdt->event_type ? 0 : eio_wdt->wdd.timeout;
+ event_time = eio_wdt->event_type ? eio_wdt->wdd.timeout : 0;
+ }
+
+ /* Set reset time */
+ ret = set_time(eio_wdt, REG_RESET_EVENT_TIME, reset_time);
+ if (ret)
+ return ret;
+
+ /* Set every other times */
+ for (type = 1; type < ARRAY_SIZE(type_regs); type++) {
+ ret = set_time(eio_wdt, type_regs[type],
+ (eio_wdt->event_type == type) ? event_time : 0);
+ if (ret)
+ return ret;
+ }
+
+ dev_dbg(eio_wdt->dev, "Config wdt reset time %u\n", reset_time);
+ dev_dbg(eio_wdt->dev, "Config wdt event time %u\n", event_time);
+ dev_dbg(eio_wdt->dev, "Config wdt event type %s\n",
+ type_strs[eio_wdt->event_type]);
+
+ return 0;
+}
+
+static int wdt_get_config(struct eio_wdt_dev *eio_wdt)
+{
+ int ret, type;
+ u32 event_time = 0, reset_time = 0;
+
+ /* Get Reset Time */
+ ret = get_time(eio_wdt, REG_RESET_EVENT_TIME, &reset_time);
+ if (ret)
+ return ret;
+
+ dev_dbg(eio_wdt->dev, "Timeout H/W default timeout: %u secs\n", reset_time);
+
+ /* Get every other times */
+ for (type = 1; type < ARRAY_SIZE(type_regs); type++) {
+ if ((eio_wdt->support & EVENT_BIT(type)) == 0)
+ continue;
+
+ ret = get_time(eio_wdt, type_regs[type], &event_time);
+ if (ret)
+ return ret;
+
+ if (event_time == 0)
+ continue;
+
+ if (reset_time) {
+ if (reset_time < event_time)
+ continue;
+
+ eio_wdt->wdd.timeout = reset_time;
+ eio_wdt->wdd.pretimeout = reset_time - event_time;
+
+ dev_dbg(eio_wdt->dev,
+ "Pretimeout H/W enabled with event %s of %u secs\n",
+ type_strs[type], eio_wdt->wdd.pretimeout);
+ } else {
+ eio_wdt->wdd.timeout = event_time;
+ eio_wdt->wdd.pretimeout = 0;
+ }
+
+ eio_wdt->event_type = type;
+
+ dev_dbg(eio_wdt->dev, "Timeout H/W enabled of %u secs\n",
+ eio_wdt->wdd.timeout);
+ return 0;
+ }
+
+ eio_wdt->event_type = EVENT_NONE;
+ eio_wdt->wdd.pretimeout = reset_time ? 0 : WATCHDOG_PRETIMEOUT;
+ eio_wdt->wdd.timeout = reset_time ? reset_time : WATCHDOG_TIMEOUT;
+
+ return 0;
+}
+
+static int set_ctrl(struct eio_wdt_dev *eio_wdt, u8 ctrl)
+{
+ return pmc_write(eio_wdt->mfd, REG_CONTROL, &ctrl);
+}
+
+static int wdt_start(struct watchdog_device *wdd)
+{
+ struct eio_wdt_dev *eio_wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ ret = wdt_set_config(eio_wdt);
+ if (ret)
+ return ret;
+
+ ret = set_ctrl(eio_wdt, CTRL_START);
+ if (!ret) {
+ eio_wdt->last_time = jiffies;
+ dev_dbg(eio_wdt->dev, "Watchdog started\n");
+ }
+
+ return ret;
+}
+
+static int wdt_stop(struct watchdog_device *wdd)
+{
+ struct eio_wdt_dev *eio_wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ dev_dbg(eio_wdt->dev, "Watchdog stopped\n");
+ eio_wdt->last_time = 0;
+
+ ret = set_ctrl(eio_wdt, CTRL_STOP);
+ return ret;
+}
+
+static int wdt_ping(struct watchdog_device *wdd)
+{
+ struct eio_wdt_dev *eio_wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ dev_dbg(eio_wdt->dev, "Watchdog ping\n");
+
+ ret = set_ctrl(eio_wdt, CTRL_TRIGGER);
+ if (!ret)
+ eio_wdt->last_time = jiffies;
+
+ return ret;
+}
+
+static int wdt_support(struct eio_wdt_dev *eio_wdt)
+{
+ u8 support;
+
+ if (pmc_read(eio_wdt->mfd, REG_STATUS, &support))
+ return -EIO;
+
+ if (!(support & SUPPORT_AVAILABLE))
+ return -ENODEV;
+
+ if ((support & SUPPORT_RESET) != SUPPORT_RESET)
+ return -ENODEV;
+
+ eio_wdt->support = support;
+
+ return 0;
+}
+
+static int wdt_get_irq_io(struct eio_wdt_dev *eio_wdt)
+{
+ int ret = 0;
+ int idx = EIO_PNP_INDEX;
+ int data = EIO_PNP_DATA;
+ struct regmap *map = eio_wdt->iomap;
+
+ mutex_lock(&eio_wdt->core->mutex);
+
+ /* Unlock EC IO port */
+ ret |= regmap_write(map, idx, IOREG_UNLOCK);
+ ret |= regmap_write(map, idx, IOREG_UNLOCK);
+
+ /* Select logical device to PMC */
+ ret |= regmap_write(map, idx, IOREG_LDN);
+ ret |= regmap_write(map, data, IOREG_LDN_PMCIO);
+
+ /* Get IRQ number */
+ ret |= regmap_write(map, idx, IOREG_IRQ);
+ ret |= regmap_read(map, data, &eio_wdt->irq);
+
+ /* Lock back */
+ ret |= regmap_write(map, idx, IOREG_LOCK);
+
+ mutex_unlock(&eio_wdt->core->mutex);
+
+ return ret ? -EIO : 0;
+}
+
+static int wdt_get_irq_pmc(struct eio_wdt_dev *eio_wdt)
+{
+ return pmc_read(eio_wdt->mfd, REG_IRQ_NUMBER, &eio_wdt->irq);
+}
+
+static int wdt_get_irq(struct eio_wdt_dev *eio_wdt)
+{
+ int ret;
+
+ if (!(eio_wdt->support & BIT(EVENT_IRQ)))
+ return -ENODEV;
+
+ ret = wdt_get_irq_pmc(eio_wdt);
+ if (ret) {
+ dev_err(eio_wdt->dev, "Error get irq by pmc\n");
+ return ret;
+ }
+
+ if (eio_wdt->irq)
+ return 0;
+
+ /* Fallback: get IRQ number from EC IO space */
+ ret = wdt_get_irq_io(eio_wdt);
+ if (ret) {
+ dev_err(eio_wdt->dev, "Error get irq by io\n");
+ return ret;
+ }
+
+ if (!eio_wdt->irq) {
+ dev_err(eio_wdt->dev, "Error IRQ number = 0\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int wdt_set_irq_io(struct eio_wdt_dev *eio_wdt)
+{
+ int ret = 0;
+ int idx = EIO_PNP_INDEX;
+ int data = EIO_PNP_DATA;
+ struct regmap *map = eio_wdt->iomap;
+
+ mutex_lock(&eio_wdt->core->mutex);
+
+ /* Unlock EC IO port */
+ ret = regmap_write(map, idx, IOREG_UNLOCK);
+ if (ret)
+ goto unlock;
+ ret = regmap_write(map, idx, IOREG_UNLOCK);
+ if (ret)
+ goto unlock;
+
+ /* Select logical device to PMC */
+ ret = regmap_write(map, idx, IOREG_LDN);
+ if (ret)
+ goto unlock;
+ ret = regmap_write(map, data, IOREG_LDN_PMCIO);
+ if (ret)
+ goto unlock;
+
+ /* Enable WDT */
+ ret = regmap_write(map, idx, IOREG_WDT_STATUS);
+ if (ret)
+ goto unlock;
+ ret = regmap_write(map, data, FLAG_WDT_ENABLED);
+ if (ret)
+ goto unlock;
+
+ /* Set IRQ number */
+ ret = regmap_write(map, idx, IOREG_IRQ);
+ if (ret)
+ goto unlock;
+ ret = regmap_write(map, data, eio_wdt->irq);
+ if (ret)
+ goto unlock;
+
+ /* Lock back */
+ ret = regmap_write(map, idx, IOREG_LOCK);
+
+unlock:
+ mutex_unlock(&eio_wdt->core->mutex);
+ return ret;
+}
+
+static int wdt_set_irq_pmc(struct eio_wdt_dev *eio_wdt)
+{
+ return pmc_write(eio_wdt->mfd, REG_IRQ_NUMBER, &eio_wdt->irq);
+}
+
+static int wdt_set_irq(struct eio_wdt_dev *eio_wdt)
+{
+ int ret;
+
+ if (!(eio_wdt->support & BIT(EVENT_IRQ)))
+ return -ENODEV;
+
+ ret = wdt_set_irq_io(eio_wdt);
+ if (ret) {
+ dev_err(eio_wdt->dev, "Error set irq by io\n");
+ return ret;
+ }
+
+ ret = wdt_set_irq_pmc(eio_wdt);
+ if (ret) {
+ dev_err(eio_wdt->dev, "Error set irq by pmc\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int wdt_get_irq_event(struct eio_wdt_dev *eio_wdt)
+{
+ u8 status;
+
+ if (pmc_read(eio_wdt->mfd, REG_EVENT, &status))
+ return 0;
+
+ return status;
+}
+
+static irqreturn_t wdt_isr(int irq, void *arg)
+{
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t wdt_threaded_isr(int irq, void *arg)
+{
+ struct eio_wdt_dev *eio_wdt = arg;
+ u8 status = wdt_get_irq_event(eio_wdt) & FLAG_TRIGGER_IRQ;
+
+ if (!status)
+ return IRQ_NONE;
+
+ if (eio_wdt->wdd.pretimeout) {
+ watchdog_notify_pretimeout(&eio_wdt->wdd);
+ } else {
+ dev_crit(eio_wdt->dev, "Watchdog expired, rebooting\n");
+ emergency_restart();
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int query_irq(struct eio_wdt_dev *eio_wdt)
+{
+ int ret = 0;
+
+ ret = wdt_get_irq(eio_wdt);
+ if (ret)
+ return ret;
+
+ dev_dbg(eio_wdt->dev, "IRQ = %d\n", eio_wdt->irq);
+
+ return wdt_set_irq(eio_wdt);
+}
+
+static int wdt_init(struct eio_wdt_dev *eio_wdt)
+{
+ int ret;
+
+ ret = wdt_support(eio_wdt);
+ if (ret)
+ return ret;
+
+ ret = wdt_get_config(eio_wdt);
+ if (ret)
+ return ret;
+
+ ret = wdt_get_type(eio_wdt);
+ if (ret)
+ return ret;
+
+ if (eio_wdt->event_type == EVENT_IRQ)
+ ret = query_irq(eio_wdt);
+
+ return ret;
+}
+
+static const struct watchdog_ops wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wdt_start,
+ .stop = wdt_stop,
+ .ping = wdt_ping,
+ .set_timeout = wdt_set_timeout,
+ .set_pretimeout = wdt_set_pretimeout,
+};
+
+static struct watchdog_info wdinfo = {
+ .identity = KBUILD_MODNAME,
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_PRETIMEOUT | WDIOF_MAGICCLOSE,
+};
+
+static int eio_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct eio_wdt_dev *eio_wdt;
+ struct watchdog_device *wdd;
+ int ret = 0;
+
+ eio_wdt = devm_kzalloc(dev, sizeof(*eio_wdt), GFP_KERNEL);
+ if (!eio_wdt)
+ return -ENOMEM;
+
+ eio_wdt->dev = dev;
+ eio_wdt->mfd = dev->parent;
+ eio_wdt->iomap = dev_get_regmap(dev->parent, NULL);
+ if (!eio_wdt->iomap)
+ return dev_err_probe(dev, -ENODEV, "parent regmap missing\n");
+
+ eio_wdt->core = dev_get_drvdata(dev->parent);
+ if (!eio_wdt->core)
+ return dev_err_probe(dev, -ENODEV, "eio_core not present\n");
+
+ ret = wdt_init(eio_wdt);
+ if (ret) {
+ dev_err(dev, "wdt_init fail\n");
+ return -EIO;
+ }
+
+ if (eio_wdt->event_type == EVENT_IRQ) {
+ ret = devm_request_threaded_irq(dev, eio_wdt->irq,
+ wdt_isr, wdt_threaded_isr,
+ IRQF_SHARED | IRQF_ONESHOT, pdev->name,
+ eio_wdt);
+ if (ret) {
+ dev_err_probe(dev, ret, "IRQ %u request fail: Disabled.\n",
+ eio_wdt->irq);
+ return ret;
+ }
+ }
+
+ wdd = &eio_wdt->wdd;
+ wdd->info = &wdinfo;
+ wdd->ops = &wdt_ops;
+ wdd->parent = dev;
+ wdd->min_timeout = 1;
+ wdd->max_timeout = 0x7FFF;
+
+ if (ret) {
+ dev_err(dev, "Init timeout fail\n");
+ return ret;
+ }
+
+ watchdog_stop_on_reboot(&eio_wdt->wdd);
+ watchdog_stop_on_unregister(&eio_wdt->wdd);
+
+ watchdog_set_drvdata(&eio_wdt->wdd, eio_wdt);
+ platform_set_drvdata(pdev, eio_wdt);
+
+ ret = devm_watchdog_register_device(dev, &eio_wdt->wdd);
+ if (ret)
+ dev_err(dev, "Cannot register watchdog device (err: %d)\n", ret);
+
+ return ret;
+}
+
+static struct platform_driver eio_wdt_driver = {
+ .probe = eio_wdt_probe,
+ .driver = {
+ .name = "eio_wdt",
+ },
+};
+module_platform_driver(eio_wdt_driver);
+
+MODULE_AUTHOR("Wenkai Chung <wenkai.chung@advantech.com.tw>");
+MODULE_AUTHOR("Ramiro Oliveira <ramiro.oliveira@advantech.com>");
+MODULE_DESCRIPTION("Watchdog interface for Advantech EIO embedded controller");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox