* [PATCH 0/2] staging: sm750fb: remove dead VALIDATION_CHIP code
From: Nils Lehnen @ 2026-07-18 5:51 UTC (permalink / raw)
To: Sudip Mukherjee, Teddy Wang, Greg Kroah-Hartman
Cc: linux-fbdev, linux-staging, linux-kernel, Nils Lehnen
VALIDATION_CHIP is defined nowhere in the tree, yet fourteen
preprocessor conditionals in the sm750fb DDK files still test it.
Patch 1 resolves all of them (dead branches dropped, always-true
branches kept), as the driver TODO asks under "remove unused code";
patch 2 cleans up the indentation and blank lines the removal leaves
behind.
Verification, both patches: the disassembly of all ten sm750fb object
files (objdump -d, x86, CONFIG_FB_SM750=m) is bit-identical across the
series. Compile-tested only; no SM750 hardware available.
All English text in this series (cover letter, commit messages, code
comments) was translated from a German draft with the assistance of
Claude Fable 5.
Nils Lehnen (2):
staging: sm750fb: remove dead VALIDATION_CHIP conditionals
staging: sm750fb: unindent defines left by conditional removal
drivers/staging/sm750fb/ddk750_reg.h | 84 ++++++++------------------
drivers/staging/sm750fb/ddk750_chip.c | 4 --
drivers/staging/sm750fb/ddk750_power.c | 6 --
3 files changed, 24 insertions(+), 70 deletions(-)
base-commit: 64276d9bfe4d1fdd5cf2636f1065f7ea55c2defb
--
2.43.0
^ permalink raw reply
* Re: [PATCH] staging: sm750fb: fix mono image source stride mismatch in lynxfb_ops_imageblit()
From: Dan Carpenter @ 2026-07-18 5:44 UTC (permalink / raw)
To: Muhammad Bilal
Cc: Sudip Mukherjee, Teddy Wang, Greg Kroah-Hartman, linux-fbdev,
linux-staging, linux-kernel
In-Reply-To: <20260718045641.468640-1-meatuni001@gmail.com>
On Sat, Jul 18, 2026 at 09:56:41AM +0500, Muhammad Bilal wrote:
> sm750_hw_imageblit() advances its monochrome source pointer by
> src_delta per scanline, and computes the correct rounded-up stride
> internally as:
>
> bytes_per_scan = (width + start_bit + 7) / 8;
>
> Its only caller, lynxfb_ops_imageblit(), instead passes src_delta as
> image->width >> 3. For widths not a multiple of 8 this under-counts
> the stride, so the source pointer falls further behind the real
> per-scanline layout on every line, corrupting the rendered image.
>
> Use DIV_ROUND_UP() so the stride passed in matches what
> sm750_hw_imageblit() already assumes.
>
> Signed-off-by: Muhammad Bilal <meatuni001@gmail.com>
Looks reasonable. Add a Fixes tag.
You have this hardware and are finding these with testing?
It would be better to move the source delta calculation into
the the sm750_hw_imageblit() since it's just width rounded up
(or rounded down if you want to be wrong I suppose).
regards,
dan carpenter
^ permalink raw reply
* [PATCH] staging: sm750fb: fix mono image source stride mismatch in lynxfb_ops_imageblit()
From: Muhammad Bilal @ 2026-07-18 4:56 UTC (permalink / raw)
To: Sudip Mukherjee, Teddy Wang
Cc: Greg Kroah-Hartman, linux-fbdev, linux-staging, linux-kernel,
Muhammad Bilal
sm750_hw_imageblit() advances its monochrome source pointer by
src_delta per scanline, and computes the correct rounded-up stride
internally as:
bytes_per_scan = (width + start_bit + 7) / 8;
Its only caller, lynxfb_ops_imageblit(), instead passes src_delta as
image->width >> 3. For widths not a multiple of 8 this under-counts
the stride, so the source pointer falls further behind the real
per-scanline layout on every line, corrupting the rendered image.
Use DIV_ROUND_UP() so the stride passed in matches what
sm750_hw_imageblit() already assumes.
Signed-off-by: Muhammad Bilal <meatuni001@gmail.com>
---
drivers/staging/sm750fb/sm750.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/staging/sm750fb/sm750.c b/drivers/staging/sm750fb/sm750.c
index 89c811e0806c..69e803f4f175 100644
--- a/drivers/staging/sm750fb/sm750.c
+++ b/drivers/staging/sm750fb/sm750.c
@@ -261,7 +261,7 @@ static void lynxfb_ops_imageblit(struct fb_info *info,
spin_lock(&sm750_dev->slock);
sm750_dev->accel.de_imageblit(&sm750_dev->accel,
- image->data, image->width >> 3, 0,
+ image->data, DIV_ROUND_UP(image->width, 8), 0,
base, pitch, bpp,
image->dx, image->dy,
image->width, image->height,
--
2.55.0
^ permalink raw reply related
* Re: [PATCH 00/32] x86/msr: Drop 32-bit MSR interfaces
From: Ingo Molnar @ 2026-07-17 9:38 UTC (permalink / raw)
To: Juergen Gross
Cc: Sean Christopherson, Arnd Bergmann, linux-kernel, linux-pm,
linux-edac@vger.kernel.org, x86, linux-acpi, kvm, linux-coco,
linux-pci, virtualization, linux-ide, dri-devel, linux-fbdev,
linux-crypto, open list:GPIO SUBSYSTEM, linux-hyperv, linux-hwmon,
linux-perf-users, linux-mtd, platform-driver-x86,
Rafael J . Wysocki, Daniel Lezcano, Zhang Rui,
lukasz.luba@arm.com, Jason Baron, Borislav Petkov, Tony Luck,
Yazen Ghannam, Len Brown, Pavel Machek, Thomas Gleixner,
Ingo Molnar, Dave Hansen, H. Peter Anvin, Paolo Bonzini,
Kirill A. Shutemov, Rick Edgecombe, Pu Wen, Bjorn Helgaas,
Ajay Kaher, Alexey Makhalov, Broadcom internal kernel review list,
Viresh Kumar, Reinette Chatre, Dave Martin, James Morse,
Babu Moger, Tony W Wang-oc, Damien Le Moal, Niklas Cassel,
Dave Airlie, Helge Deller, linux-geode, Olivia Mackall,
Herbert Xu, Linus Walleij, Bartosz Golaszewski,
Greg Kroah-Hartman, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Guenter Roeck, Peter Zijlstra,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, Josh Poimboeuf, Pawan Gupta, Vitaly Kuznetsov,
Andy Lutomirski, Boris Ostrovsky, Huang Rui, Mario Limonciello,
Perry Yuan, K Prateek Nayak, srinivas.pandruvada@linux.intel.com,
Artem Bityutskiy, Artem Bityutskiy, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Ashok Raj, Hans de Goede,
Ilpo Järvinen, Rajneesh Bhardwaj, David E Box, xen-devel
In-Reply-To: <99228803-b8b7-47a3-b77c-6fdf3b785730@suse.com>
* Juergen Gross <jgross@suse.com> wrote:
> On 02.07.26 12:07, Ingo Molnar wrote:
> >
> > * Sean Christopherson <seanjc@google.com> wrote:
> >
> > > > Note that the individual patches are IMO significantly easier to review
> > > > through the actual 32-bit => 64-bit variable assignment changes done
> > > > in isolation (which sometimes include minor cleanups), while
> > > > the Coccinelle semantic patch:
> > > >
> > > > { a(b,c) => c = a(b) }
> > > >
> > > > which changes both the function signature and the order of terms as
> > > > well, is just a single add-on treewide patch.
> > >
> > > Is the plan for subsystem maintainers to pick up the relevant patches,
> > > and then do the treewide change one release cycle later?
> >
> > I'll try to keep the patches in a single tree (tip:x86/msr)
> > in the hope of not prolonging the pain two cycles - but it's
> > of course fine for maintainers to pick up the patches too
> > (most of them are standalone), we'll sort it all out in the end.
>
> Ingo, would you be fine with me posting patch updates just as replies to the
> original patch emails? This would speed things up, as I wouldn't need to wait
> for more review input of all the patches before sending out new versions.
Sure, that works for me too. I've picked up a couple of -v2 patches
already.
Thanks,
Ingo
^ permalink raw reply
* Re: [PATCH] staging: fbtft: fb_ra8875: replace udelay with usleep_range
From: Andy Shevchenko @ 2026-07-16 18:37 UTC (permalink / raw)
To: mashikiselby
Cc: Andy Shevchenko, Greg Kroah-Hartman, dri-devel, linux-fbdev,
linux-staging, linux-kernel
In-Reply-To: <20260716-staging-testing-v1-1-2ebc0c6bb27a@gmail.com>
On Thu, Jul 16, 2026 at 01:08:33PM +0200, Selby Mashiki via B4 Relay wrote:
> udelay() busy-waits for the specified duration, consuming CPU cycles
> unnecessarily. usleep_range() is preferred for delays that can sleep,
> as it allows the scheduler to run other tasks during the wait.
>
> Replace two instances of udelay(100) with usleep_range(100, 200) to
> use the preferred sleeping delay function.
NAK. Read mailing list archives why.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH v2 3/8] Add Advantech EIO Hardware Monitor driver
From: Guenter Roeck @ 2026-07-16 11:36 UTC (permalink / raw)
To: Ramiro Manuel Silva Oliveira, Lee Jones, Linus Walleij,
Bartosz Golaszewski, Andi Shyti, Daniel Thompson, Jingoo Han,
Helge Deller, Wim Van Sebroeck, Rafael J. Wysocki, Daniel Lezcano,
Zhang Rui, Lukasz Luba
Cc: linux-kernel@vger.kernel.org, mfd@lists.linux.dev,
linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org,
linux-i2c@vger.kernel.org, dri-devel@lists.freedesktop.org,
linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org,
linux-pm@vger.kernel.org, Wenkai.Chung, Francisco Aragon-Trivino,
Hongzhi Wang, Mikhail Tsukerman, Thomas.Kastner
In-Reply-To: <PSAPR02MB4502BBD659D7C4FCDDCABB6294C72@PSAPR02MB4502.apcprd02.prod.outlook.com>
On 7/16/26 01:10, Ramiro Manuel Silva Oliveira wrote:
>> On 7/14/26 08:54, Ramiro Oliveira wrote:
>>> 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>
>>
>> Normally there would be a hwmon driver instantiating the thermal zone(s)
>> using HWMON_C_REGISTER_TZ. If the driver actively controls thermal zones
>> since it also handles fans, that would be instantiated from the hwmon driver.
>>
>> Since you have both thermal temperature and thermal fan drivers, I am very
>> concerned that there is functionality overlap andor even conflicts. You should
>> drop this driver and configure the thermal temperature driver to instantiate a
>> hwmon device from it if so desired.
>
> But this hwmon driver doesn't control any setting, it just displays values. Fan
> and Thermal Zone control is handled in the thermal fan and thermal
> temperature drivers.
>
> Also this hwmon shows values not related to temperature, like voltage,
> current and intrusion.
>
> Would it be ok if I kept the HW Monitor since it only displays values, and if
> not can I keep it to show the other non-temperature values?
>
All other similar drivers implement a hwmon driver and instantiate the thermal
subsystem from it. You decided to implement thermal drivers. You should only
have thermal or hwmon drivers, not both.
Having both would essentially implement two drivers with overlapping functionality.
Since you decided that you want separate thermal drivers, and two of them, you
should drop the hwmon driver.
Guenter
^ permalink raw reply
* [PATCH] staging: fbtft: fb_ra8875: replace udelay with usleep_range
From: Selby Mashiki via B4 Relay @ 2026-07-16 11:08 UTC (permalink / raw)
To: Andy Shevchenko, Greg Kroah-Hartman
Cc: dri-devel, linux-fbdev, linux-staging, linux-kernel,
Selby Mashiki
From: Selby Mashiki <mashikiselby@gmail.com>
udelay() busy-waits for the specified duration, consuming CPU cycles
unnecessarily. usleep_range() is preferred for delays that can sleep,
as it allows the scheduler to run other tasks during the wait.
Replace two instances of udelay(100) with usleep_range(100, 200) to
use the preferred sleeping delay function.
Signed-off-by: Selby Mashiki <mashikiselby@gmail.com>
---
drivers/staging/fbtft/fb_ra8875.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/staging/fbtft/fb_ra8875.c b/drivers/staging/fbtft/fb_ra8875.c
index 0ab1de664..d2400bb44 100644
--- a/drivers/staging/fbtft/fb_ra8875.c
+++ b/drivers/staging/fbtft/fb_ra8875.c
@@ -210,7 +210,7 @@ static void write_reg8_bus8(struct fbtft_par *par, int len, ...)
}
len--;
- udelay(100);
+ usleep_range(100, 200);
if (len) {
buf = (u8 *)par->buf;
@@ -231,7 +231,7 @@ static void write_reg8_bus8(struct fbtft_par *par, int len, ...)
/* restore user spi-speed */
par->fbtftops.write = fbtft_write_spi;
- udelay(100);
+ usleep_range(100, 200);
}
static int write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len)
---
base-commit: 64276d9bfe4d1fdd5cf2636f1065f7ea55c2defb
change-id: 20260716-staging-testing-b1b81d63d0d1
Best regards,
--
Selby Mashiki <mashikiselby@gmail.com>
^ permalink raw reply related
* RE: [PATCH v2 3/8] Add Advantech EIO Hardware Monitor driver
From: Ramiro Manuel Silva Oliveira @ 2026-07-16 8:10 UTC (permalink / raw)
To: Guenter Roeck, Lee Jones, Linus Walleij, Bartosz Golaszewski,
Andi Shyti, Daniel Thompson, Jingoo Han, Helge Deller,
Wim Van Sebroeck, Rafael J. Wysocki, Daniel Lezcano, Zhang Rui,
Lukasz Luba
Cc: linux-kernel@vger.kernel.org, mfd@lists.linux.dev,
linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org,
linux-i2c@vger.kernel.org, dri-devel@lists.freedesktop.org,
linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org,
linux-pm@vger.kernel.org, Wenkai.Chung, Francisco Aragon-Trivino,
Hongzhi Wang, Mikhail Tsukerman, Thomas.Kastner
In-Reply-To: <b7171173-aa64-4393-b9cf-77d92f2ec932@roeck-us.net>
> On 7/14/26 08:54, Ramiro Oliveira wrote:
> > 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>
>
> Normally there would be a hwmon driver instantiating the thermal zone(s)
> using HWMON_C_REGISTER_TZ. If the driver actively controls thermal zones
> since it also handles fans, that would be instantiated from the hwmon driver.
>
> Since you have both thermal temperature and thermal fan drivers, I am very
> concerned that there is functionality overlap andor even conflicts. You should
> drop this driver and configure the thermal temperature driver to instantiate a
> hwmon device from it if so desired.
But this hwmon driver doesn't control any setting, it just displays values. Fan
and Thermal Zone control is handled in the thermal fan and thermal
temperature drivers.
Also this hwmon shows values not related to temperature, like voltage,
current and intrusion.
Would it be ok if I kept the HW Monitor since it only displays values, and if
not can I keep it to show the other non-temperature values?
>
> Thanks,
> Guenter
^ permalink raw reply
* [PATCH] staging: sm750fb: remove unused set_all_eng_off field
From: Hungyu Lin @ 2026-07-16 4:46 UTC (permalink / raw)
To: Sudip Mukherjee, Teddy Wang, Greg Kroah-Hartman
Cc: Dan Carpenter, linux-fbdev, linux-staging, linux-kernel,
Hungyu Lin
The set_all_eng_off field is only initialized to 0 and is never set by
any caller.
Remove the unused field together with the dead cleanup path guarded by
it.
No functional change intended.
Signed-off-by: Hungyu Lin <dennylin0707@gmail.com>
---
drivers/staging/sm750fb/ddk750_chip.c | 27 ---------------------------
drivers/staging/sm750fb/ddk750_chip.h | 7 -------
drivers/staging/sm750fb/sm750.c | 1 -
3 files changed, 35 deletions(-)
diff --git a/drivers/staging/sm750fb/ddk750_chip.c b/drivers/staging/sm750fb/ddk750_chip.c
index 0bb56bbec43f..aa78d0bc1d87 100644
--- a/drivers/staging/sm750fb/ddk750_chip.c
+++ b/drivers/staging/sm750fb/ddk750_chip.c
@@ -263,33 +263,6 @@ int ddk750_init_hw(struct initchip_param *p_init_param)
poke32(MISC_CTRL, reg);
}
- if (p_init_param->set_all_eng_off == 1) {
- sm750_enable_2d_engine(0);
-
- /* Disable Overlay, if a former application left it on */
- reg = peek32(VIDEO_DISPLAY_CTRL);
- reg &= ~DISPLAY_CTRL_PLANE;
- poke32(VIDEO_DISPLAY_CTRL, reg);
-
- /* Disable video alpha, if a former application left it on */
- reg = peek32(VIDEO_ALPHA_DISPLAY_CTRL);
- reg &= ~DISPLAY_CTRL_PLANE;
- poke32(VIDEO_ALPHA_DISPLAY_CTRL, reg);
-
- /* Disable alpha plane, if a former application left it on */
- reg = peek32(ALPHA_DISPLAY_CTRL);
- reg &= ~DISPLAY_CTRL_PLANE;
- poke32(ALPHA_DISPLAY_CTRL, reg);
-
- /* Disable DMA Channel, if a former application left it on */
- reg = peek32(DMA_ABORT_INTERRUPT);
- reg |= DMA_ABORT_INTERRUPT_ABORT_1;
- poke32(DMA_ABORT_INTERRUPT, reg);
-
- /* Disable DMA Power, if a former application left it on */
- sm750_enable_dma(0);
- }
-
/* We can add more initialization as needed. */
return 0;
diff --git a/drivers/staging/sm750fb/ddk750_chip.h b/drivers/staging/sm750fb/ddk750_chip.h
index ee2e9d90f7dd..2a13debc179f 100644
--- a/drivers/staging/sm750fb/ddk750_chip.h
+++ b/drivers/staging/sm750fb/ddk750_chip.h
@@ -76,13 +76,6 @@ struct initchip_param {
*/
unsigned short master_clock;
- /*
- * 0 = leave all engine state untouched.
- * 1 = make sure they are off: 2D, Overlay,
- * video alpha, alpha, hardware cursors
- */
- unsigned short set_all_eng_off;
-
/*
* 0 = Do not reset the memory controller
* 1 = Reset the memory controller
diff --git a/drivers/staging/sm750fb/sm750.c b/drivers/staging/sm750fb/sm750.c
index a2f244878f85..039e2033f84e 100644
--- a/drivers/staging/sm750fb/sm750.c
+++ b/drivers/staging/sm750fb/sm750.c
@@ -837,7 +837,6 @@ static void sm750fb_setup(struct sm750_dev *sm750_dev, char *src)
sm750_dev->init_parm.mem_clock = 0;
sm750_dev->init_parm.master_clock = 0;
sm750_dev->init_parm.power_mode = 0;
- sm750_dev->init_parm.set_all_eng_off = 0;
sm750_dev->init_parm.reset_memory = 1;
/* defaultly turn g_hwcursor on for both view */
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v2 3/8] Add Advantech EIO Hardware Monitor driver
From: Guenter Roeck @ 2026-07-16 0:29 UTC (permalink / raw)
To: Ramiro Oliveira, Lee Jones, Linus Walleij, Bartosz Golaszewski,
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
In-Reply-To: <20260714-upstream-v2-v2-3-76e5e41026db@advantech.com>
On 7/14/26 08:54, Ramiro Oliveira wrote:
> 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>
Normally there would be a hwmon driver instantiating the thermal zone(s)
using HWMON_C_REGISTER_TZ. If the driver actively controls thermal zones
since it also handles fans, that would be instantiated from the hwmon driver.
Since you have both thermal temperature and thermal fan drivers, I am very
concerned that there is functionality overlap andor even conflicts. You
should drop this driver and configure the thermal temperature driver
to instantiate a hwmon device from it if so desired.
Thanks,
Guenter
^ permalink raw reply
* Re: [PATCH v2 2/8] Add Advantech EIO GPIO driver
From: Bartosz Golaszewski @ 2026-07-15 12:24 UTC (permalink / raw)
To: Ramiro Oliveira
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, 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
In-Reply-To: <20260714-upstream-v2-v2-2-76e5e41026db@advantech.com>
On Tue, 14 Jul 2026 17:54:16 +0200, Ramiro Oliveira
<ramiro.oliveira@advantech.com> said:
> This driver controls the GPIO component of the Advantech EIO chip.
>
The subject should be: "gpio: add a driver for the Advantech EIO GPIO
controller"
or similar.
> 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
Shouldn't this be part of the MFD entry?
> +
> 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[] = {
Please use a common prefix for all symbols. "eio_gpio" like above is fine.
> + { 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;
Why not put all ints on the same line? Same elsewhere.
> + 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)
This warrants an -ENODEV. We don't want to retry probe here, do we?
> + 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");
-ENODEV is used for silently bailing out of probe and telling the driver core
to not defer. Basically it means: this device cannot be enabled on this system
or is not present at all. This means: don't use dev_err_probe() and don't emit
any logs. Just return -ENODEV.
Though it makes me think - can this even happen at all? MFD cells won't get
created if parent's probe() failed.
> +
> + 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))
Can you use the ret = gpio_init() pattern for consistency with the rest of the
code?
> + 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
>
>
Bart
^ permalink raw reply
* Re: [PATCH 16/16] arm: omap2: remove remaining pdata-quirks for pandora legacy devices
From: Andreas Kemnade @ 2026-07-14 18:46 UTC (permalink / raw)
To: H. Nikolaus Schaller
Cc: Grond, Stefan Leichter, Grazvydas Ignotas, Tony Lindgren,
Ethan Nelson-Moore, Jarkko Nikula, Sascha Hauer, 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: <d380941287c655a3d205ee8b08516d364c0ddec9.1783749722.git.hns@goldelico.com>
On Sat, 11 Jul 2026 08:02:03 +0200
"H. Nikolaus Schaller" <hns@goldelico.com> wrote:
> After updating the pandora-backligt setup and removing the
> omap3pandora sound system and defining related gpios by device tree,
> we can remove omap3_pandora_legacy_init() and the related legacy
> devices completely.
>
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
regarding bisectabily: Do we create mess, if the platform devices via
quirks are disabled?
I am wondering how we handle this. If this patch goes together with the
removal of the legacy drivers, then there might be a situation where
neither the new one nor the old one is available? Split this all up?
If sound discussion takes longer, maybe do the backlight thing alone?
Regards,
Andreas
> ---
> arch/arm/mach-omap2/pdata-quirks.c | 23 -----------------------
> 1 file changed, 23 deletions(-)
>
> diff --git a/arch/arm/mach-omap2/pdata-quirks.c
> b/arch/arm/mach-omap2/pdata-quirks.c index
> b947bacf23a37..aca7097a692ed 100644 ---
> a/arch/arm/mach-omap2/pdata-quirks.c +++
> b/arch/arm/mach-omap2/pdata-quirks.c @@ -268,27 +268,6 @@ static void
> __init omap3_logicpd_torpedo_init(void) omap3_gpio126_127_129();
> }
>
> -/* omap3pandora legacy devices */
> -
> -static struct platform_device pandora_backlight = {
> - .name = "pandora-backlight",
> - .id = -1,
> -};
> -
> -static struct gpiod_lookup_table pandora_soc_audio_gpios = {
> - .dev_id = "soc-audio",
> - .table = {
> - GPIO_LOOKUP("gpio-112-127", 6, "dac",
> GPIO_ACTIVE_HIGH),
> - GPIO_LOOKUP("gpio-0-15", 14, "amp",
> GPIO_ACTIVE_HIGH),
> - { }
> - },
> -};
> -
> -static void __init omap3_pandora_legacy_init(void)
> -{
> - platform_device_register(&pandora_backlight);
> - gpiod_add_lookup_table(&pandora_soc_audio_gpios);
> -}
> #endif /* CONFIG_ARCH_OMAP3 */
>
> #ifdef CONFIG_SOC_DRA7XX
> @@ -514,8 +493,6 @@ static struct pdata_init pdata_quirks[]
> __initdata = { { "ti,omap3-evm-37xx", omap3_evm_legacy_init, },
> { "ti,am3517-evm", am3517_evm_legacy_init, },
> { "technexion,omap3-tao3530", omap3_tao3530_legacy_init, },
> - { "openpandora,omap3-pandora-600mhz",
> omap3_pandora_legacy_init, },
> - { "openpandora,omap3-pandora-1ghz",
> omap3_pandora_legacy_init, }, #endif
> { /* sentinel */ },
> };
^ permalink raw reply
* Re: [PATCH 06/16] ASoC: twl4030-codec: Allow setting APLL rate through the .set_sysclk() interface
From: Andreas Kemnade @ 2026-07-14 18:36 UTC (permalink / raw)
To: H. Nikolaus Schaller
Cc: Grond, Stefan Leichter, Grazvydas Ignotas, Tony Lindgren,
Ethan Nelson-Moore, Jarkko Nikula, Sascha Hauer, 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: <7ea589f951baf1e217c7169ae21c0bf510672ecf.1783749722.git.hns@goldelico.com>
On Sat, 11 Jul 2026 08:01:53 +0200
"H. Nikolaus Schaller" <hns@goldelico.com> wrote:
> From: Grond <grond66@riseup.net>
>
> On the Pandora, the APLL is actually what drives all of the audio
> clocks on the board. Since the TWL4030 is only used for capture, we
> need some way of setting the correct rate for playback streams, since
> they will not go through the TWL4030's .hw_params() code path (which
> is normally how APLL rate is set).
>
> Signed-off-by: Grond <grond66@riseup.net>
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
As a general comment: might we model this as clock provider (and maybe
that enables the usage of a generic audio-graph-card)?
Regards,
Andreas
^ permalink raw reply
* Re: [PATCH 0/3] staging/fbtft: Minor cleanups.
From: Dan Carpenter @ 2026-07-15 7:28 UTC (permalink / raw)
To: Panagiotis Gravias
Cc: andy, gregkh, namcao, tomasz.unger, dri-devel, linux-fbdev,
linux-staging, linux-kernel
In-Reply-To: <20260714190958.219437-2-graviaspanagiotis1@gmail.com>
On Tue, Jul 14, 2026 at 10:09:38PM +0300, Panagiotis Gravias wrote:
> Solved some style errors,
> parenthesis spacing, aligning arguments, replacing udelay() with
> usleep_range().
>
> I hope this makes future checks with the style script cleaner and more
> helpfull.
>
With old code you should search lore for why it still exists.
regards,
dan carpenter
^ permalink raw reply
* Re: [PATCH 0/3] staging/fbtft: Minor cleanups.
From: Andy Shevchenko @ 2026-07-14 19:18 UTC (permalink / raw)
To: Panagiotis Gravias
Cc: andy, gregkh, namcao, tomasz.unger, dri-devel, linux-fbdev,
linux-staging, linux-kernel
In-Reply-To: <20260714190958.219437-2-graviaspanagiotis1@gmail.com>
On Tue, Jul 14, 2026 at 10:09:38PM +0300, Panagiotis Gravias wrote:
> Solved some style errors,
> parenthesis spacing, aligning arguments, replacing udelay() with
> usleep_range().
>
> I hope this makes future checks with the style script cleaner and more
> helpfull.
To understand the resolution, read the mailing list archives for
the last ~6-9 months against this driver.
Also read and understand README in the folder of the driver.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH 3/3] staging: fbtft: Replace udelay with usleep_range
From: Andy Shevchenko @ 2026-07-14 19:17 UTC (permalink / raw)
To: Panagiotis Gravias
Cc: andy, gregkh, namcao, tomasz.unger, dri-devel, linux-fbdev,
linux-staging, linux-kernel
In-Reply-To: <20260714190958.219437-6-graviaspanagiotis1@gmail.com>
On Tue, Jul 14, 2026 at 10:09:42PM +0300, Panagiotis Gravias wrote:
> Fix checkpatch warnings preferring usleep_range over udelay.
> Replaced udelay() calls with usleep_range() providing a reasonable
> upper bound to allow the scheduler to coalesce timer interrupts.
NAK.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH 2/3] staging/fbtft: Align arguments to match open parenthesis
From: Andy Shevchenko @ 2026-07-14 19:16 UTC (permalink / raw)
To: Panagiotis Gravias
Cc: andy, gregkh, namcao, tomasz.unger, dri-devel, linux-fbdev,
linux-staging, linux-kernel
In-Reply-To: <20260714190958.219437-5-graviaspanagiotis1@gmail.com>
On Tue, Jul 14, 2026 at 10:09:41PM +0300, Panagiotis Gravias wrote:
> Fix checkpatch warning for alignment in write_reg() call to match
> the open parenthesis, improving code readability.
NAK.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH 1/3] staging/fbtft: Remove prohibited space before close parenthesis
From: Andy Shevchenko @ 2026-07-14 19:16 UTC (permalink / raw)
To: Panagiotis Gravias
Cc: andy, gregkh, namcao, tomasz.unger, dri-devel, linux-fbdev,
linux-staging, linux-kernel
In-Reply-To: <20260714190958.219437-4-graviaspanagiotis1@gmail.com>
On Tue, Jul 14, 2026 at 10:09:40PM +0300, Panagiotis Gravias wrote:
> Fix checkpatch error: "space prohibited before that close parenthesis"
> in the define_fbtft_write_reg macro.
NAK.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* [PATCH 3/3] staging: fbtft: Replace udelay with usleep_range
From: Panagiotis Gravias @ 2026-07-14 19:09 UTC (permalink / raw)
To: andy, gregkh
Cc: namcao, tomasz.unger, dri-devel, linux-fbdev, linux-staging,
linux-kernel, Panagiotis Gravias
In-Reply-To: <20260714190958.219437-2-graviaspanagiotis1@gmail.com>
Fix checkpatch warnings preferring usleep_range over udelay.
Replaced udelay() calls with usleep_range() providing a reasonable
upper bound to allow the scheduler to coalesce timer interrupts.
Signed-off-by: Panagiotis Gravias <graviaspanagiotis1@gmail.com>
---
drivers/staging/fbtft/fb_ra8875.c | 4 ++--
drivers/staging/fbtft/fb_upd161704.c | 18 +++++++++---------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/drivers/staging/fbtft/fb_ra8875.c b/drivers/staging/fbtft/fb_ra8875.c
index 0ab1de6647d0..d2400bb44f1c 100644
--- a/drivers/staging/fbtft/fb_ra8875.c
+++ b/drivers/staging/fbtft/fb_ra8875.c
@@ -210,7 +210,7 @@ static void write_reg8_bus8(struct fbtft_par *par, int len, ...)
}
len--;
- udelay(100);
+ usleep_range(100, 200);
if (len) {
buf = (u8 *)par->buf;
@@ -231,7 +231,7 @@ static void write_reg8_bus8(struct fbtft_par *par, int len, ...)
/* restore user spi-speed */
par->fbtftops.write = fbtft_write_spi;
- udelay(100);
+ usleep_range(100, 200);
}
static int write_vmem16_bus8(struct fbtft_par *par, size_t offset, size_t len)
diff --git a/drivers/staging/fbtft/fb_upd161704.c b/drivers/staging/fbtft/fb_upd161704.c
index c680160d6380..e26766f58a50 100644
--- a/drivers/staging/fbtft/fb_upd161704.c
+++ b/drivers/staging/fbtft/fb_upd161704.c
@@ -32,27 +32,27 @@ static int init_display(struct fbtft_par *par)
/* oscillator start */
write_reg(par, 0x003A, 0x0001); /*Oscillator 0: stop, 1: operation */
- udelay(100);
+ usleep_range(100, 200);
/* y-setting */
write_reg(par, 0x0024, 0x007B); /* amplitude setting */
- udelay(10);
+ usleep_range(10, 20);
write_reg(par, 0x0025, 0x003B); /* amplitude setting */
write_reg(par, 0x0026, 0x0034); /* amplitude setting */
- udelay(10);
+ usleep_range(10, 20);
write_reg(par, 0x0027, 0x0004); /* amplitude setting */
write_reg(par, 0x0052, 0x0025); /* circuit setting 1 */
- udelay(10);
+ usleep_range(10, 20);
write_reg(par, 0x0053, 0x0033); /* circuit setting 2 */
write_reg(par, 0x0061, 0x001C); /* adjustment V10 positive polarity */
- udelay(10);
+ usleep_range(10, 20);
write_reg(par, 0x0062, 0x002C); /* adjustment V9 negative polarity */
write_reg(par, 0x0063, 0x0022); /* adjustment V34 positive polarity */
- udelay(10);
+ usleep_range(10, 20);
write_reg(par, 0x0064, 0x0027); /* adjustment V31 negative polarity */
- udelay(10);
+ usleep_range(10, 20);
write_reg(par, 0x0065, 0x0014); /* adjustment V61 negative polarity */
- udelay(10);
+ usleep_range(10, 20);
write_reg(par, 0x0066, 0x0010); /* adjustment V61 negative polarity */
/* Basical clock for 1 line (BASECOUNT[7:0]) number specified */
@@ -60,7 +60,7 @@ static int init_display(struct fbtft_par *par)
/* Power supply setting */
write_reg(par, 0x0019, 0x0000); /* DC/DC output setting */
- udelay(200);
+ usleep_range(200, 300);
write_reg(par, 0x001A, 0x1000); /* DC/DC frequency setting */
write_reg(par, 0x001B, 0x0023); /* DC/DC rising setting */
write_reg(par, 0x001C, 0x0C01); /* Regulator voltage setting */
--
2.55.0
^ permalink raw reply related
* [PATCH 2/3] staging/fbtft: Align arguments to match open parenthesis
From: Panagiotis Gravias @ 2026-07-14 19:09 UTC (permalink / raw)
To: andy, gregkh
Cc: namcao, tomasz.unger, dri-devel, linux-fbdev, linux-staging,
linux-kernel, Panagiotis Gravias
In-Reply-To: <20260714190958.219437-2-graviaspanagiotis1@gmail.com>
Fix checkpatch warning for alignment in write_reg() call to match
the open parenthesis, improving code readability.
Signed-off-by: Panagiotis Gravias <graviaspanagiotis1@gmail.com>
---
drivers/staging/fbtft/fb_tinylcd.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/staging/fbtft/fb_tinylcd.c b/drivers/staging/fbtft/fb_tinylcd.c
index afa8f1c740c5..d58b1247262b 100644
--- a/drivers/staging/fbtft/fb_tinylcd.c
+++ b/drivers/staging/fbtft/fb_tinylcd.c
@@ -38,7 +38,7 @@ static int init_display(struct fbtft_par *par)
write_reg(par, 0xE5, 0x00);
write_reg(par, 0xF0, 0x36, 0xA5, 0x53);
write_reg(par, 0xE0, 0x00, 0x35, 0x33, 0x00, 0x00, 0x00,
- 0x00, 0x35, 0x33, 0x00, 0x00, 0x00);
+ 0x00, 0x35, 0x33, 0x00, 0x00, 0x00);
write_reg(par, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
write_reg(par, MIPI_DCS_EXIT_SLEEP_MODE);
fsleep(250);
--
2.55.0
^ permalink raw reply related
* [PATCH 1/3] staging/fbtft: Remove prohibited space before close parenthesis
From: Panagiotis Gravias @ 2026-07-14 19:09 UTC (permalink / raw)
To: andy, gregkh
Cc: namcao, tomasz.unger, dri-devel, linux-fbdev, linux-staging,
linux-kernel, Panagiotis Gravias
In-Reply-To: <20260714190958.219437-2-graviaspanagiotis1@gmail.com>
Fix checkpatch error: "space prohibited before that close parenthesis"
in the define_fbtft_write_reg macro.
Signed-off-by: Panagiotis Gravias <graviaspanagiotis1@gmail.com>
---
drivers/staging/fbtft/fbtft-bus.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/staging/fbtft/fbtft-bus.c b/drivers/staging/fbtft/fbtft-bus.c
index 30e436ff19e4..38697992f5e1 100644
--- a/drivers/staging/fbtft/fbtft-bus.c
+++ b/drivers/staging/fbtft/fbtft-bus.c
@@ -62,9 +62,9 @@ out: \
} \
EXPORT_SYMBOL(func);
-define_fbtft_write_reg(fbtft_write_reg8_bus8, u8, u8, )
+define_fbtft_write_reg(fbtft_write_reg8_bus8, u8, u8,)
define_fbtft_write_reg(fbtft_write_reg16_bus8, __be16, u16, cpu_to_be16)
-define_fbtft_write_reg(fbtft_write_reg16_bus16, u16, u16, )
+define_fbtft_write_reg(fbtft_write_reg16_bus16, u16, u16,)
void fbtft_write_reg8_bus9(struct fbtft_par *par, int len, ...)
{
--
2.55.0
^ permalink raw reply related
* [PATCH 0/3] staging/fbtft: Minor cleanups.
From: Panagiotis Gravias @ 2026-07-14 19:09 UTC (permalink / raw)
To: andy, gregkh
Cc: namcao, tomasz.unger, dri-devel, linux-fbdev, linux-staging,
linux-kernel, Panagiotis Gravias
Solved some style errors,
parenthesis spacing, aligning arguments, replacing udelay() with
usleep_range().
I hope this makes future checks with the style script cleaner and more
helpfull.
Panagiotis Gravias (3):
staging/fbtft: Remove prohibited space before close parenthesis
staging/fbtft: Align arguments to match open parenthesis
staging: fbtft: Replace udelay with usleep_range
drivers/staging/fbtft/fb_ra8875.c | 4 ++--
drivers/staging/fbtft/fb_tinylcd.c | 2 +-
drivers/staging/fbtft/fb_upd161704.c | 18 +++++++++---------
drivers/staging/fbtft/fbtft-bus.c | 4 ++--
4 files changed, 14 insertions(+), 14 deletions(-)
--
2.55.0
^ permalink raw reply
* [PATCH v2 8/8] Add Advantech EIO Fan 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 Fan block,
which is included in the Advantech EIO Embedded Controller.
Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
MAINTAINERS | 1 +
drivers/thermal/Kconfig | 8 +
drivers/thermal/Makefile | 1 +
drivers/thermal/eio_fan.c | 478 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 488 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index ded89e70d6cc..5d5d4ac54030 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/thermal/eio_fan.c
F: drivers/thermal/eio_thermal.c
F: drivers/video/backlight/eio_bl.c
F: drivers/watchdog/eio_wdt.c
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index fc078012a989..a52ec50c8cc5 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -436,6 +436,14 @@ config EIO_THERMAL
the Linux thermal framework. It communicates with the EC through the
EIO MFD core.
+config EIO_FAN
+ tristate "Advantech EIO Fan cooling device"
+ depends on MFD_EIO && THERMAL
+ help
+ Fan cooling device for the Advantech EIO. This driver exposes a
+ thermal cooling device with controllable states (e.g. Auto/Manual/PWM).
+ It communicates with the EC through the EIO MFD core.
+
menu "Mediatek thermal drivers"
depends on ARCH_MEDIATEK || COMPILE_TEST
source "drivers/thermal/mediatek/Kconfig"
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 6abce8933e34..872f733b6ad4 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_IMX91_THERMAL) += imx91_thermal.o
obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o
obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o
obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o
+obj-$(CONFIG_EIO_FAN) += eio_fan.o
obj-$(CONFIG_EIO_THERMAL) += eio_thermal.o
obj-y += intel/
obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
diff --git a/drivers/thermal/eio_fan.c b/drivers/thermal/eio_fan.c
new file mode 100644
index 000000000000..e07b18c2ec44
--- /dev/null
+++ b/drivers/thermal/eio_fan.c
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * eio_fan
+ * ============
+ * Thermal zone driver for Advantech EIO embedded controller's smart
+ * fan mechanism.
+ *
+ * We create a sysfs 'name' of the zone, point out where the fan is. Such as
+ * CPU0, SYS3, etc.
+ *
+ * The sysfs 'fan_mode' can be one of 'Stop', 'Full', 'Manual' or 'Auto'.
+ * If 'Manual'. You can control fan speed via sysfs 'PWM'.
+ * If it is 'Auto'. It enables the smart fan mechanism as below.
+ *
+ * In EIO chip. The smart fan has 3 trips. When the temperature is:
+ * - Over Temp High(trip0), the Fan runs at the fan PWM High.
+ * - Between Temp Low and Temp High(trip1 - trip0), the fan PWM value slopes
+ * from PWM Low to PWM High.
+ * - Between Temp Stop and Temp Low(trip2 - trip1), the fan PWM is PWM low.
+ * - Below Temp Stop, the fan stopped.
+ *
+ * (PWM)|
+ * |
+ * High |............................. ______________
+ * (Max)| /:
+ * | / :
+ * | / :
+ * | / :
+ * | / :
+ * | / :
+ * | / :
+ * | / :
+ * Low |.......... __________/ :
+ * | | : :
+ * | | : :
+ * 0 +===========+---------+--------+-------------
+ * 0 Stop Low High (Temp)
+ *
+ * Copyright (C) 2025 Advantech Corporation. All rights reserved.
+ */
+
+#include <linux/ctype.h>
+#include <linux/errno.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/sysfs.h>
+#include <linux/thermal.h>
+
+#define CMD_FAN_WRITE 0x24
+#define CMD_FAN_READ 0x25
+#define FAN_MAX 0x04
+
+#define CMD_THERM_WRITE 0x10
+#define CMD_THERM_READ 0x11
+#define THERM_MAX 0x04
+#define THERM_MULTI 100
+
+#define CTRL_STATE 0x00
+#define CTRL_TYPE 0x01
+#define CTRL_CTRL 0x02
+#define CTRL_ERROR 0x04
+#define CTRL_VALUE 0x10
+#define CTRL_INVERT 0x11
+#define CTRL_FREQ 0x12
+#define CTRL_THERM_HIGH 0x13
+#define CTRL_THERM_LOW 0x14
+#define CTRL_THERM_STOP 0x15
+#define CTRL_PWM_HIGH 0x16
+#define CTRL_PWM_LOW 0x17
+#define CTRL_THERM_SRC 0x20
+
+#define CTRLMODE_STOP 0x00
+#define CTRLMODE_FULL 0x01
+#define CTRLMODE_MANUAL 0x02
+#define CTRLMODE_AUTO 0x03
+
+#define DUTY_MAX 100
+#define UNIT_PER_TEMP 10
+#define NAME_SIZE 4
+
+#define TRIP_HIGH 0
+#define TRIP_LOW 1
+#define TRIP_STOP 2
+#define TRIP_NUM 3
+
+/* Bitfields inside CTRL_CTRL */
+#define FAN_MODE_MASK GENMASK(1, 0)
+#define FAN_SCM_BIT BIT(2)
+#define FAN_FRAME_BIT BIT(3)
+#define FAN_SRC_MASK GENMASK(7, 4)
+
+#define FAN_SRC(val) (((int)(val)) >> 4)
+
+#ifndef DECI_KELVIN_TO_MILLI_CELSIUS
+#define DECI_KELVIN_TO_MILLI_CELSIUS(t) ((((t) - 2731) * 100))
+#endif
+
+#ifndef MILLI_CELSIUS_TO_DECI_KELVIN
+#define MILLI_CELSIUS_TO_DECI_KELVIN(t) ((((t) / 100) + 2731))
+#endif
+
+static const u8 pmc_len[CTRL_THERM_SRC + 1] = {
+/* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */
+ 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 4, 2, 2, 2, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0,
+ 1,
+};
+
+static const char fan_name[0x20][NAME_SIZE + 1] = {
+ "CPU0", "CPU1", "CPU2", "CPU3", "SYS0", "SYS1", "SYS2", "SYS3",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "", "", "", "",
+ "", "", "", "", "OEM0", "OEM1", "OEM2", "OEM3",
+};
+
+struct eio_fan_trip {
+ u8 trip_ctl;
+};
+
+struct eio_fan_dev {
+ struct device *mfd;
+ struct device *dev;
+ u8 id;
+ struct thermal_zone_device *tzd;
+ struct eio_fan_trip trip_priv[TRIP_NUM];
+};
+
+static int pmc_write(struct device *mfd, u8 ctrl, u8 id, void *data)
+{
+ if (ctrl >= ARRAY_SIZE(pmc_len))
+ return -EINVAL;
+
+ struct pmc_op op = {
+ .cmd = CMD_FAN_WRITE,
+ .control = ctrl,
+ .device_id = id,
+ .size = pmc_len[ctrl],
+ .payload = (u8 *)data,
+ };
+ return eio_core_pmc_operation(mfd, &op);
+}
+
+static int pmc_read(struct device *mfd, u8 ctrl, u8 id, void *data)
+{
+ struct pmc_op op = {
+ .cmd = CMD_FAN_READ,
+ .control = ctrl,
+ .device_id = id,
+ .size = pmc_len[ctrl],
+ .payload = (u8 *)data,
+ };
+ return eio_core_pmc_operation(mfd, &op);
+}
+
+static int pmc_read_therm(struct device *mfd, u8 ctrl, u8 id, void *data)
+{
+ struct pmc_op op = {
+ .cmd = CMD_THERM_READ,
+ .control = ctrl,
+ .device_id = id,
+ .size = 2,
+ .payload = (u8 *)data,
+ };
+ return eio_core_pmc_operation(mfd, &op);
+}
+
+static int eio_fan_get_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ struct eio_fan_dev *fan = thermal_zone_device_priv(tzd);
+ struct device *mfd = fan->mfd;
+ u8 ch = fan->id;
+ int sensor = 0;
+ u16 val = 0;
+ int ret;
+
+ ret = pmc_read(mfd, CTRL_CTRL, ch, &sensor);
+ if (ret)
+ return ret;
+
+ ret = pmc_read_therm(mfd, CTRL_VALUE, (u8)FAN_SRC(sensor), &val);
+ if (ret)
+ return ret;
+
+ *temp = DECI_KELVIN_TO_MILLI_CELSIUS(val);
+ return 0;
+}
+
+static int eio_fan_set_trip_temp(struct thermal_zone_device *tzd,
+ const struct thermal_trip *trip, int temp)
+{
+ struct eio_fan_dev *fan = thermal_zone_device_priv(tzd);
+ const struct eio_fan_trip *fan_trip = trip->priv;
+ u8 ctl = CTRL_THERM_HIGH + fan_trip->trip_ctl;
+ u16 val;
+
+ if (temp < 1000)
+ return -EINVAL;
+
+ val = MILLI_CELSIUS_TO_DECI_KELVIN(temp);
+ return pmc_write(fan->mfd, ctl, fan->id, &val);
+}
+
+static bool eio_fan_should_bind(struct thermal_zone_device *tzd,
+ const struct thermal_trip *trip,
+ struct thermal_cooling_device *cdev,
+ struct cooling_spec *spec)
+{
+ struct eio_fan_dev *tz_fan = thermal_zone_device_priv(tzd);
+ struct eio_fan_dev *cd_fan = cdev->devdata;
+
+ if (!tz_fan || !cd_fan)
+ return false;
+
+ if (tz_fan->mfd != cd_fan->mfd || tz_fan->id != cd_fan->id)
+ return false;
+
+ return true;
+}
+
+static const struct thermal_zone_device_ops zone_ops = {
+ .get_temp = eio_fan_get_temp,
+ .set_trip_temp = eio_fan_set_trip_temp,
+ .should_bind = eio_fan_should_bind,
+};
+
+static int eio_fan_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ *state = 100;
+ return 0;
+}
+
+static int eio_fan_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct eio_fan_dev *fan = cdev->devdata;
+ int fan_mode = 0;
+ u8 duty = 0;
+ int ret = 0;
+
+ *state = 0;
+ ret = pmc_read(fan->mfd, CTRL_CTRL, fan->id, &fan_mode);
+ if (ret)
+ return ret;
+
+ switch (fan_mode & FAN_MODE_MASK) {
+ case CTRLMODE_STOP:
+ *state = 0;
+ break;
+ case CTRLMODE_FULL:
+ *state = 100;
+ break;
+ case CTRLMODE_AUTO:
+ *state = 0;
+ ret = 0;
+ break;
+ case CTRLMODE_MANUAL:
+ ret = pmc_read(fan->mfd, CTRL_VALUE, fan->id, &duty);
+ if (ret)
+ return ret;
+ duty = (u8)clamp_val(duty, 0, 100);
+ *state = duty;
+ break;
+ default:
+ *state = 0;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int eio_fan_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct eio_fan_dev *fan = cdev->devdata;
+ u8 ctrl = 0;
+ u8 duty;
+ int ret;
+
+ ret = pmc_read(fan->mfd, CTRL_CTRL, fan->id, &ctrl);
+ if (ret)
+ return ret;
+
+ if ((ctrl & FAN_MODE_MASK) != CTRLMODE_MANUAL)
+ return -EOPNOTSUPP;
+
+ duty = (u8)clamp_val(state, 0, 100);
+
+ ret = pmc_write(fan->mfd, CTRL_VALUE, fan->id, &duty);
+
+ return ret;
+}
+
+static const struct thermal_cooling_device_ops cooling_ops = {
+ .get_max_state = eio_fan_get_max_state,
+ .get_cur_state = eio_fan_get_cur_state,
+ .set_cur_state = eio_fan_set_cur_state,
+};
+
+static ssize_t fan_mode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ static const char * const names[] = { "Stop", "Full", "Manual", "Auto" };
+ struct thermal_zone_device *tzd = dev_get_drvdata(dev);
+ struct eio_fan_dev *fan = thermal_zone_device_priv(tzd);
+ u8 mode = 0;
+
+ int ret = pmc_read(fan->mfd, CTRL_CTRL, fan->id, &mode);
+
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", names[mode & 0x03]);
+}
+
+static ssize_t fan_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ static const char * const names[] = { "Stop", "Full", "Manual", "Auto" };
+ struct thermal_zone_device *tzd = dev_get_drvdata(dev);
+ struct eio_fan_dev *fan = thermal_zone_device_priv(tzd);
+ u8 ctrl, newc;
+ int mode_idx, ret;
+
+ for (mode_idx = 0; mode_idx < ARRAY_SIZE(names); mode_idx++) {
+ if (strncasecmp(buf, names[mode_idx], strlen(names[mode_idx])))
+ continue;
+
+ ret = pmc_read(fan->mfd, CTRL_CTRL, fan->id, &ctrl);
+ if (ret)
+ return -EIO;
+
+ newc = ctrl & FAN_SRC_MASK;
+
+ switch (mode_idx) {
+ case CTRLMODE_AUTO:
+ newc |= FAN_FRAME_BIT;
+ newc &= ~FAN_SCM_BIT;
+ newc |= CTRLMODE_AUTO;
+ break;
+ case CTRLMODE_MANUAL:
+ newc &= ~FAN_FRAME_BIT;
+ newc &= ~FAN_SCM_BIT;
+ newc |= CTRLMODE_MANUAL;
+ break;
+ case CTRLMODE_FULL:
+ newc &= ~FAN_FRAME_BIT;
+ newc &= ~FAN_SCM_BIT;
+ newc |= CTRLMODE_FULL;
+ break;
+ case CTRLMODE_STOP:
+ default:
+ newc &= ~FAN_FRAME_BIT;
+ newc &= ~FAN_SCM_BIT;
+ newc |= CTRLMODE_STOP;
+ break;
+ }
+
+ ret = pmc_write(fan->mfd, CTRL_CTRL, fan->id, &newc);
+ return ret ? ret : count;
+ }
+
+ return -EINVAL;
+}
+
+static DEVICE_ATTR_RW(fan_mode);
+
+static int eio_fan_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ unsigned int fan_id;
+ int ret;
+
+ if (!dev_get_drvdata(dev->parent)) {
+ dev_err(dev, "eio_core not present\n");
+ return -ENODEV;
+ }
+
+ for (fan_id = 0; fan_id < FAN_MAX; fan_id++) {
+ u8 state = 0, name = 0;
+ int trip_hi = 0, trip_lo = 0, trip_stop = 0;
+ int pwm_hi = 0, pwm_lo = 0;
+ struct eio_fan_dev *fan;
+ struct thermal_zone_device *tzd;
+ struct thermal_cooling_device *cdev;
+
+ if (pmc_read(dev->parent, CTRL_STATE, fan_id, &state) ||
+ pmc_read(dev->parent, CTRL_TYPE, fan_id, &name) ||
+ pmc_read(dev->parent, CTRL_THERM_HIGH, fan_id, &trip_hi) ||
+ pmc_read(dev->parent, CTRL_THERM_LOW, fan_id, &trip_lo) ||
+ pmc_read(dev->parent, CTRL_THERM_STOP, fan_id, &trip_stop) ||
+ pmc_read(dev->parent, CTRL_PWM_HIGH, fan_id, &pwm_hi) ||
+ pmc_read(dev->parent, CTRL_PWM_LOW, fan_id, &pwm_lo)) {
+ dev_info(dev, "fan%u: pmc read error, skipping\n", fan_id);
+ continue;
+ }
+
+ if (!(state & 0x1)) {
+ dev_info(dev, "fan%u: firmware reports disabled\n", fan_id);
+ continue;
+ }
+
+ if (!fan_name[name][0]) {
+ dev_info(dev, "fan%u: unknown name index %u\n", fan_id, name);
+ continue;
+ }
+
+ fan = devm_kzalloc(dev, sizeof(*fan), GFP_KERNEL);
+ if (!fan)
+ return -ENOMEM;
+
+ fan->mfd = dev->parent;
+ fan->id = (u8)fan_id;
+
+ fan->trip_priv[TRIP_HIGH].trip_ctl = CTRL_THERM_HIGH;
+ fan->trip_priv[TRIP_LOW].trip_ctl = CTRL_THERM_LOW;
+ fan->trip_priv[TRIP_STOP].trip_ctl = CTRL_THERM_STOP;
+
+ struct thermal_trip trips[TRIP_NUM] = {
+ [TRIP_HIGH] = {
+ .type = THERMAL_TRIP_ACTIVE,
+ .temperature = DECI_KELVIN_TO_MILLI_CELSIUS(trip_hi),
+ .flags = THERMAL_TRIP_FLAG_RW_TEMP,
+ .priv = &fan->trip_priv[TRIP_HIGH],
+ },
+ [TRIP_LOW] = {
+ .type = THERMAL_TRIP_ACTIVE,
+ .temperature = DECI_KELVIN_TO_MILLI_CELSIUS(trip_lo),
+ .flags = THERMAL_TRIP_FLAG_RW_TEMP,
+ .priv = &fan->trip_priv[TRIP_LOW],
+ },
+ [TRIP_STOP] = {
+ .type = THERMAL_TRIP_ACTIVE,
+ .temperature = DECI_KELVIN_TO_MILLI_CELSIUS(trip_stop),
+ .flags = THERMAL_TRIP_FLAG_RW_TEMP,
+ .priv = &fan->trip_priv[TRIP_STOP],
+ },
+ };
+
+ tzd = thermal_zone_device_register_with_trips(fan_name[name],
+ trips, TRIP_NUM,
+ fan,
+ &zone_ops,
+ NULL,
+ 0, 0);
+ if (IS_ERR(tzd))
+ return PTR_ERR(tzd);
+
+ cdev = thermal_cooling_device_register(fan_name[name], fan, &cooling_ops);
+ if (IS_ERR(cdev)) {
+ thermal_zone_device_unregister(tzd);
+ dev_err(dev, "fan%u: cdev register failed: %ld\n",
+ fan_id, PTR_ERR(cdev));
+ return PTR_ERR(cdev);
+ }
+
+ dev_set_drvdata(thermal_zone_device(tzd), tzd);
+ ret = device_create_file(thermal_zone_device(tzd), &dev_attr_fan_mode);
+ if (ret)
+ dev_warn(dev, "Error create thermal zone fan_mode sysfs\n");
+ }
+ return 0;
+}
+
+static struct platform_driver eio_fan_driver = {
+ .probe = eio_fan_probe,
+ .driver = {
+ .name = "eio_fan",
+ },
+};
+
+module_platform_driver(eio_fan_driver);
+
+MODULE_AUTHOR("Wenkai Chung <wenkai.chung@advantech.com.tw>");
+MODULE_AUTHOR("Ramiro Oliveira <ramiro.oliveira@advantech.com>");
+MODULE_DESCRIPTION("Fan driver for Advantech EIO embedded controller");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v2 7/8] Add Advantech EIO Thermal 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 Thermal block,
this block is included in the Advantech EIO Embedded Controller.
Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
MAINTAINERS | 1 +
drivers/thermal/Kconfig | 9 ++
drivers/thermal/Makefile | 1 +
drivers/thermal/eio_thermal.c | 346 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 357 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 8d35fd7c5599..ded89e70d6cc 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/thermal/eio_thermal.c
F: drivers/video/backlight/eio_bl.c
F: drivers/watchdog/eio_wdt.c
F: include/linux/mfd/eio.h
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 810eeccedfba..fc078012a989 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -427,6 +427,15 @@ config DA9062_THERMAL
zone.
Compatible with the DA9062 and DA9061 PMICs.
+config EIO_THERMAL
+ tristate "Advantech EIO Thermal zone"
+ depends on MFD_EIO && THERMAL
+ help
+ Thermal zone support for the Advantech EIO. This driver exposes
+ temperature readings, trip points and protection enable/disable via
+ the Linux thermal framework. It communicates with the EC through the
+ EIO MFD core.
+
menu "Mediatek thermal drivers"
depends on ARCH_MEDIATEK || COMPILE_TEST
source "drivers/thermal/mediatek/Kconfig"
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 3b249195c088..6abce8933e34 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_IMX91_THERMAL) += imx91_thermal.o
obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o
obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o
obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o
+obj-$(CONFIG_EIO_THERMAL) += eio_thermal.o
obj-y += intel/
obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
obj-y += st/
diff --git a/drivers/thermal/eio_thermal.c b/drivers/thermal/eio_thermal.c
new file mode 100644
index 000000000000..ad923acaf138
--- /dev/null
+++ b/drivers/thermal/eio_thermal.c
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * eio_thermal
+ * ================
+ * Thermal zone driver for Advantech EIO embedded controller's thermal
+ * protect mechanism.
+ *
+ * In EIO chip. The smart fan has 3 trips. While the temperature:
+ * - Touch Trip0: Shutdown --> Cut off the power.
+ * - Touch Trip1: Poweroff --> Send the power button signal.
+ * - between Trip2 and Trip1: Throttle --> Intermittently hold the CPU.
+ *
+ * PowerOff Shutdown
+ * ^ ^
+ * Throttle | |
+ * | | |
+ * +--------+------------+----------+---------
+ * 0 trip2 trip1 trip0 (Temp)
+ *
+ * Copyright (C) 2025 Advantech Corporation. All rights reserved.
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/thermal.h>
+
+#define CMD_THERM_WRITE 0x10
+#define CMD_THERM_READ 0x11
+#define THERM_NUM 0x04
+#define UNIT_PER_TEMP 100
+
+#define CTRL_STATE 0x00
+#define CTRL_TYPE 0x01
+#define CTRL_ERROR 0x04
+#define CTRL_VALUE 0x10
+#define CTRL_MAX 0x11
+#define CTRL_MIN 0x12
+#define CTRL_THROTTLE 0x20
+#define CTRL_THROTTLE_HI 0x21
+#define CTRL_THROTTLE_LO 0x22
+#define CTRL_THROTTLE_DEFAULT 0x28
+#define CTRL_THROTTLE_HI_DEFAULT 0x29
+#define CTRL_THROTTLE_LO_DEFAULT 0x2A
+#define CTRL_POWEROFF 0x30
+#define CTRL_POWEROFF_HI 0x31
+#define CTRL_POWEROFF_LO 0x32
+#define CTRL_POWEROFF_DEFAULT 0x38
+#define CTRL_POWEROFF_HI_DEFAULT 0x39
+#define CTRL_POWEROFF_LO_DEFAULT 0x3A
+#define CTRL_SHUTDOWN 0x40
+#define CTRL_SHUTDOWN_HI 0x41
+#define CTRL_SHUTDOWN_LO 0x42
+#define CTRL_SHUTDOWN_DEFAULT 0x48
+#define CTRL_SHUTDOWN_HI_DEFAULT 0x49
+#define CTRL_SHUTDOWN_LO_DEFAULT 0x4A
+#define CTRL_SB_TSI_STATUS 0x80
+#define CTRL_SB_TSI_ACCESS 0x81
+#define CTRL_WARN_STATUS 0x90
+#define CTRL_WARN_BEEP 0x91
+#define CTRL_WARN_TEMP 0x92
+
+#define THERM_ERR_NO 0x00
+#define THERM_ERR_CHANNEL 0x01
+#define THERM_ERR_HI 0x02
+#define THERM_ERR_LO 0x03
+
+#define NAME_SIZE 5
+
+#define TRIP_NUM 3
+#define TRIP_SHUTDOWN 0
+#define TRIP_POWEROFF 1
+#define TRIP_THROTTLE 2
+/* Beep mechanism no stable. Not supported, yet. */
+#define TRIP_BEEP 3
+
+#define THERMAL_POLLING_DELAY 2000 /* millisecond */
+#define THERMAL_PASSIVE_DELAY 1000
+
+#define DECI_KELVIN_TO_MILLI_CELSIUS(t) (((t) - 2731) * 100)
+#define MILLI_CELSIUS_TO_DECI_KELVIN(t) (((t) / 100) + 2731)
+
+#define THERM_STS_AVAIL BIT(0)
+#define THERM_STS_THROTTLE_AVAIL BIT(1)
+#define THERM_STS_POWEROFF_AVAIL BIT(2)
+#define THERM_STS_SHUTDOWN_AVAIL BIT(3)
+#define THERM_STS_THROTTLE_EVT BIT(4)
+#define THERM_STS_POWEROFF_EVT BIT(5)
+#define THERM_STS_SHUTDOWN_EVT BIT(6)
+/* BIT(7) reserved */
+#define THERM_STS_THROTTLE_ON BIT(8)
+#define THERM_STS_POWEROFF_ON BIT(9)
+#define THERM_STS_SHUTDOWN_ON BIT(10)
+/* BIT(11) reserved */
+#define THERM_STS_THROTTLE_LOG BIT(12)
+#define THERM_STS_POWEROFF_LOG BIT(13)
+#define THERM_STS_SHUTDOWN_LOG BIT(14)
+
+static u8 pmc_len[] = {
+/* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */
+/* 0 */ 2, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 1 */ 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 2 */ 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 3 */ 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 4 */ 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 5 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 6 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 7 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 8 */ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 9 */ 2, 1, 2,
+};
+
+static char therm_name[0x20][NAME_SIZE + 1] = {
+ "CPU0", "CPU1", "CPU2", "CPU3", "SYS0", "SYS1", "SYS2", "SYS3",
+ "AUX0", "AUX1", "AUX2", "AUX3", "DIMM0", "DIMM1", "DIMM2", "DIMM3",
+ "PCH", "VGA", "", "", "", "", "", "",
+ "", "", "", "", "OEM0", "OEM1", "OEM2", "OEM3",
+};
+
+static const u8 ctrl_map[] = {
+ CTRL_SHUTDOWN, CTRL_POWEROFF, CTRL_THROTTLE
+};
+
+struct eio_thermal_dev {
+ struct device *mfd;
+ struct device *dev;
+ u8 ch;
+ u8 name;
+};
+
+struct eio_trip_dev {
+ struct device *mfd;
+ u8 ch;
+ u8 idx;
+};
+
+static int pmc_write(struct device *mfd, u8 ctrl, u8 dev_id, void *data)
+{
+ if (ctrl >= ARRAY_SIZE(pmc_len))
+ return -EINVAL;
+
+ struct pmc_op op = {
+ .cmd = CMD_THERM_WRITE,
+ .control = ctrl,
+ .device_id = dev_id,
+ .payload = (u8 *)data,
+ .size = pmc_len[ctrl],
+ };
+
+ return eio_core_pmc_operation(mfd, &op);
+}
+
+static int pmc_read(struct device *mfd, u8 ctrl, u8 dev_id, void *data)
+{
+ if (ctrl >= ARRAY_SIZE(pmc_len))
+ return -EINVAL;
+
+ struct pmc_op op = {
+ .cmd = CMD_THERM_READ,
+ .control = ctrl,
+ .device_id = dev_id,
+ .payload = (u8 *)data,
+ .size = pmc_len[ctrl],
+ };
+
+ return eio_core_pmc_operation(mfd, &op);
+}
+
+static int eio_tz_get_temp(struct thermal_zone_device *tzd, int *temp)
+{
+ struct eio_thermal_dev *eio_thermal = thermal_zone_device_priv(tzd);
+ u16 val = 0;
+ int ret;
+
+ ret = pmc_read(eio_thermal->mfd, CTRL_VALUE, eio_thermal->ch, &val);
+ if (ret)
+ return ret;
+
+ *temp = DECI_KELVIN_TO_MILLI_CELSIUS(val);
+ return 0;
+}
+
+static int eio_tz_set_trip_temp(struct thermal_zone_device *tzd,
+ const struct thermal_trip *trip, int temp)
+{
+ struct eio_thermal_dev *eio_thermal = thermal_zone_device_priv(tzd);
+ const u8 ctl = (uintptr_t)trip->priv;
+ u16 val;
+
+ if (temp < 1000)
+ return -EINVAL;
+
+ val = MILLI_CELSIUS_TO_DECI_KELVIN(temp);
+ return pmc_write(eio_thermal->mfd, ctl, eio_thermal->ch, &val);
+}
+
+static int eio_tz_change_mode(struct thermal_zone_device *tzd,
+ enum thermal_device_mode mode)
+{
+ struct eio_thermal_dev *eio_thermal = thermal_zone_device_priv(tzd);
+ int trip;
+ int ret = 0;
+
+ for (trip = 0; trip < TRIP_NUM; trip++) {
+ ret = pmc_write(eio_thermal->mfd, ctrl_map[trip], eio_thermal->ch, &mode);
+ if (ret)
+ dev_err(eio_thermal->dev, "Error when %s trip num %d\n",
+ mode == THERMAL_DEVICE_ENABLED ? "enabling" : "disabling",
+ trip);
+ }
+
+ return ret;
+}
+
+static struct thermal_zone_device_ops zone_ops = {
+ .get_temp = eio_tz_get_temp,
+ .set_trip_temp = eio_tz_set_trip_temp,
+ .change_mode = eio_tz_change_mode,
+};
+
+static struct thermal_zone_params zone_params = {
+ .no_hwmon = true,
+};
+
+static int eio_thermal_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ch;
+
+ if (!dev_get_drvdata(dev->parent)) {
+ dev_err(dev, "eio_core not present\n");
+ return -ENODEV;
+ }
+
+ for (ch = 0; ch < THERM_NUM; ch++) {
+ u16 state = 0;
+ u8 name = 0;
+ u16 hi_shutdown = 0, hi_poweroff = 0, hi_throttle = 0;
+ int t_shutdown = 0, t_poweroff = 0, t_throttle = 0;
+ struct thermal_trip trips[TRIP_NUM];
+ int ntrips = 0;
+ struct eio_thermal_dev *eio_th;
+ struct thermal_zone_device *tzd;
+
+ if (pmc_read(dev->parent, CTRL_STATE, (u8)ch, &state) ||
+ pmc_read(dev->parent, CTRL_TYPE, (u8)ch, &name)) {
+ dev_info(dev, "thermal%d: PMC read error\n", ch);
+ continue;
+ }
+
+ if (!(state & THERM_STS_AVAIL) ||
+ !((state & THERM_STS_THROTTLE_AVAIL) ||
+ (state & THERM_STS_POWEROFF_AVAIL) ||
+ (state & THERM_STS_SHUTDOWN_AVAIL))) {
+ dev_info(dev, "thermal%d: firmware not activated\n", ch);
+ continue;
+ }
+
+ if (name >= ARRAY_SIZE(therm_name) || !therm_name[name][0]) {
+ dev_info(dev, "thermal%d: unknown sensor name idx=%u\n", ch, name);
+ continue;
+ }
+
+ /* Throttle starts a 1C increase it */
+ int throttle_temp = MILLI_CELSIUS_TO_DECI_KELVIN(60000);
+
+ pmc_write(dev->parent, CTRL_THROTTLE_HI, (u8)ch, &throttle_temp);
+
+ pmc_read(dev->parent, CTRL_SHUTDOWN_HI, (u8)ch, &hi_shutdown);
+ pmc_read(dev->parent, CTRL_POWEROFF_HI, (u8)ch, &hi_poweroff);
+ pmc_read(dev->parent, CTRL_THROTTLE_HI, (u8)ch, &hi_throttle);
+
+ t_shutdown = DECI_KELVIN_TO_MILLI_CELSIUS(hi_shutdown);
+ t_poweroff = DECI_KELVIN_TO_MILLI_CELSIUS(hi_poweroff);
+ t_throttle = DECI_KELVIN_TO_MILLI_CELSIUS(hi_throttle);
+
+ ntrips = 0;
+ if (hi_shutdown) {
+ trips[ntrips].type = THERMAL_TRIP_CRITICAL;
+ trips[ntrips].temperature = t_shutdown;
+ trips[ntrips].flags = THERMAL_TRIP_FLAG_RW_TEMP;
+ trips[ntrips].priv = THERMAL_INT_TO_TRIP_PRIV(TRIP_SHUTDOWN),
+ ntrips++;
+ }
+ if (hi_poweroff) {
+ trips[ntrips].type = THERMAL_TRIP_HOT;
+ trips[ntrips].temperature = t_poweroff;
+ trips[ntrips].flags = THERMAL_TRIP_FLAG_RW_TEMP;
+ trips[ntrips].priv = THERMAL_INT_TO_TRIP_PRIV(TRIP_POWEROFF),
+ ntrips++;
+ }
+ if (hi_throttle) {
+ trips[ntrips].type = THERMAL_TRIP_PASSIVE;
+ trips[ntrips].temperature = t_throttle;
+ trips[ntrips].flags = THERMAL_TRIP_FLAG_RW_TEMP;
+ trips[ntrips].priv = THERMAL_INT_TO_TRIP_PRIV(TRIP_THROTTLE),
+ ntrips++;
+ }
+ if (!ntrips) {
+ dev_info(dev, "thermal%d: no valid trips\n", ch);
+ continue;
+ }
+
+ eio_th = devm_kzalloc(dev, sizeof(*eio_th), GFP_KERNEL);
+ if (!eio_th)
+ return -ENOMEM;
+ eio_th->ch = (u8)ch;
+ eio_th->mfd = dev->parent;
+ eio_th->dev = dev;
+
+ tzd = thermal_zone_device_register_with_trips(therm_name[name],
+ trips,
+ ntrips,
+ eio_th,
+ &zone_ops,
+ &zone_params,
+ THERMAL_PASSIVE_DELAY,
+ THERMAL_POLLING_DELAY);
+ if (IS_ERR(tzd))
+ return PTR_ERR(tzd);
+ /* Make sure zones start disabled */
+ thermal_zone_device_disable(tzd);
+
+ dev_info(dev, "%s thermal up (ch=%d)\n", therm_name[name], ch);
+ }
+
+ return 0;
+}
+
+static struct platform_driver eio_thermal_driver = {
+ .probe = eio_thermal_probe,
+ .driver = {
+ .name = "eio_thermal",
+ },
+};
+module_platform_driver(eio_thermal_driver);
+
+MODULE_AUTHOR("Wenkai Chung <wenkai.chung@advantech.com.tw>");
+MODULE_AUTHOR("Ramiro Oliveira <ramiro.oliveira@advantech.com>");
+MODULE_DESCRIPTION("Thermal 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