Linux Framebuffer Layer development
 help / color / mirror / Atom feed
* Re: [PATCH 0/3] fbdev: Guard sysfs interfaces under CONFIG_FB_DEVICE
From: Thomas Zimmermann @ 2025-12-09  7:27 UTC (permalink / raw)
  To: Chintan Patel, linux-fbdev, linux-staging, linux-omap
  Cc: linux-kernel, dri-devel, andy, deller, gregkh
In-Reply-To: <20251209042744.7875-1-chintanlike@gmail.com>

Hi

Am 09.12.25 um 05:27 schrieb Chintan Patel:
> Hi all,
>
> This small series makes several legacy fbdev drivers buildable with
> CONFIG_FB_DEVICE=n. Currently, multiple fbdev drivers rely on fb_info->dev
> and sysfs attribute registration unconditionally, which leads to build
> failures whenever FB_DEVICE is disabled.
>
> Thomas previously noted that FB_DEVICE should eventually become optional
> and that drivers should not depend on sysfs or fb_info->dev being present
> unless the Kconfig explicitly selects it. This series pushes in that
> direction by tightening the FB_DEVICE dependency boundary without changing
> any runtime behaviour when FB_DEVICE=y.
>
> What this series does *not* change
>
> - No functional behaviour changes when FB_DEVICE=y.
> - No removal of sysfs interfaces.
> - No changes to fbops, memory allocation, or display update paths.
>
> Build & test coverage
>
> Tested with the following combinations:
>
> 1. **FB=y, FB_DEVICE=y**
>     - Baseline configuration; no regressions expected.
>
> 2. **FB=y, FB_DEVICE=n**
>     - Drivers build successfully.
>     - No sysfs attributes are created.
>     - fbdev devices operate normally (where applicable).
>
> 3. **FB=n**
>     - Drivers depend on FB, so they properly do not build, unchanged.
>
> Motivation
>
> This moves fbdev closer to supporting FB_DEVICE as truly optional, helps
> reduce Kconfig entanglement, and clears several long-standing TODO items
> as suggested by Thomas Zimmermann around legacy sysfs usage inside fbdev
> drivers.
>
> Feedback is welcome, especially on whether the guard boundaries around
> sysfs are placed correctly or whether more logic should be pulled under
> CONFIG_FB_DEVICE.

I left a comment on the first patch. If things still build nicely, then

Acked-by: Thomas Zimmermann <tzimmermann@suse.de>

for the series.

Best regards
Thomas

>
> Thanks,
> Chintan
>
> Chintan Patel (3):
>    fbtft: Make sysfs and dev_*() logging conditional on FB_DEVICE
>    omapfb: Guard sysfs code under CONFIG_FB_DEVICE
>    sh_mobile_lcdc: Guard overlay sysfs interfaces under CONFIG_FB_DEVICE
>
>   drivers/staging/fbtft/fbtft-core.c            | 20 +++++++++++++++++--
>   drivers/staging/fbtft/fbtft-sysfs.c           |  8 ++++++++
>   drivers/video/fbdev/omap2/omapfb/Kconfig      |  2 +-
>   .../video/fbdev/omap2/omapfb/omapfb-sysfs.c   | 11 ++++++++++
>   drivers/video/fbdev/sh_mobile_lcdcfb.c        |  4 ++++
>   5 files changed, 42 insertions(+), 3 deletions(-)
>

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
GF: Jochen Jaser, Andrew McDonald, Werner Knoblich, (HRB 36809, AG Nürnberg)



^ permalink raw reply

* [PATCH v2 14/19] video/vga: Add VGA_IS0_R
From: Ville Syrjala @ 2025-12-09  7:55 UTC (permalink / raw)
  To: intel-gfx; +Cc: intel-xe, Helge Deller, linux-fbdev, dri-devel
In-Reply-To: <20251208182637.334-15-ville.syrjala@linux.intel.com>

From: Ville Syrjälä <ville.syrjala@linux.intel.com>

Add a proper name for the "Input status register 0" IO address.
Currently we have some code that does read addressed using the
aliasing VGA_MSR_W define, making it unclear what register we're
actually reading.

v2: Remove stray '?'

Cc: Helge Deller <deller@gmx.de>
Cc: linux-fbdev@vger.kernel.org
Cc: dri-devel@lists.freedesktop.org
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
---
 include/video/vga.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/video/vga.h b/include/video/vga.h
index 468764d6727a..2f13c371800b 100644
--- a/include/video/vga.h
+++ b/include/video/vga.h
@@ -46,6 +46,7 @@
 #define VGA_MIS_R   	0x3CC	/* Misc Output Read Register */
 #define VGA_MIS_W   	0x3C2	/* Misc Output Write Register */
 #define VGA_FTC_R	0x3CA	/* Feature Control Read Register */
+#define VGA_IS0_R	0x3C2	/* Input Status Register 0 */
 #define VGA_IS1_RC  	0x3DA	/* Input Status Register 1 - color emulation */
 #define VGA_IS1_RM  	0x3BA	/* Input Status Register 1 - mono emulation */
 #define VGA_PEL_D   	0x3C9	/* PEL Data Register */
-- 
2.51.2


^ permalink raw reply related

* Re: [PATCH 0/3] fbdev: Guard sysfs interfaces under CONFIG_FB_DEVICE
From: Helge Deller @ 2025-12-09  8:22 UTC (permalink / raw)
  To: Thomas Zimmermann, Chintan Patel, linux-fbdev, linux-staging,
	linux-omap
  Cc: linux-kernel, dri-devel, andy, gregkh
In-Reply-To: <19e8a1b0-75e3-4c8d-911a-15fd70f60bea@suse.de>

On 12/9/25 08:27, Thomas Zimmermann wrote:
> Hi
> 
> Am 09.12.25 um 05:27 schrieb Chintan Patel:
>> Hi all,
>>
>> This small series makes several legacy fbdev drivers buildable with
>> CONFIG_FB_DEVICE=n. Currently, multiple fbdev drivers rely on fb_info->dev
>> and sysfs attribute registration unconditionally, which leads to build
>> failures whenever FB_DEVICE is disabled.
>>
>> Thomas previously noted that FB_DEVICE should eventually become optional
>> and that drivers should not depend on sysfs or fb_info->dev being present
>> unless the Kconfig explicitly selects it. This series pushes in that
>> direction by tightening the FB_DEVICE dependency boundary without changing
>> any runtime behaviour when FB_DEVICE=y.
>>
>> What this series does *not* change
>>
>> - No functional behaviour changes when FB_DEVICE=y.
>> - No removal of sysfs interfaces.
>> - No changes to fbops, memory allocation, or display update paths.
>>
>> Build & test coverage
>>
>> Tested with the following combinations:
>>
>> 1. **FB=y, FB_DEVICE=y**
>>     - Baseline configuration; no regressions expected.
>>
>> 2. **FB=y, FB_DEVICE=n**
>>     - Drivers build successfully.
>>     - No sysfs attributes are created.
>>     - fbdev devices operate normally (where applicable).
>>
>> 3. **FB=n**
>>     - Drivers depend on FB, so they properly do not build, unchanged.
>>
>> Motivation
>>
>> This moves fbdev closer to supporting FB_DEVICE as truly optional, helps
>> reduce Kconfig entanglement, and clears several long-standing TODO items
>> as suggested by Thomas Zimmermann around legacy sysfs usage inside fbdev
>> drivers.
>>
>> Feedback is welcome, especially on whether the guard boundaries around
>> sysfs are placed correctly or whether more logic should be pulled under
>> CONFIG_FB_DEVICE.
> 
> I left a comment on the first patch. If things still build nicely, then
> 
> Acked-by: Thomas Zimmermann <tzimmermann@suse.de>
> 
> for the series.

This whole series adds a whole lot of ifdef'ery, which I think is the
worst approach. It makes the code less readable and leads to two code
paths, which may trigger different build errors depending on the config.

I'm sure it must be possible to do the same without adding more #ifdefs,
e.g. by introducing a function like   dev_of_fbinfo(fbinfo)  which
simply returns NULL for the FB_DEVICE=n case.  Then, that value can be tested
like
	if (dev_of_fbinfo(fbinfo))
		{...do-the-things...}
For the FB_DEVICE=n case this will then be optimized out by the compiler,
while you still have full compiler syntax checking.

Thoughts?

Helge

^ permalink raw reply

* Re: [PATCH 0/3] fbdev: Guard sysfs interfaces under CONFIG_FB_DEVICE
From: Thomas Zimmermann @ 2025-12-09  8:42 UTC (permalink / raw)
  To: Helge Deller, Chintan Patel, linux-fbdev, linux-staging,
	linux-omap
  Cc: linux-kernel, dri-devel, andy, gregkh
In-Reply-To: <f5d50007-5b48-47cb-8133-72fca274d562@gmx.de>

Hi

Am 09.12.25 um 09:22 schrieb Helge Deller:
> On 12/9/25 08:27, Thomas Zimmermann wrote:
>> Hi
>>
>> Am 09.12.25 um 05:27 schrieb Chintan Patel:
>>> Hi all,
>>>
>>> This small series makes several legacy fbdev drivers buildable with
>>> CONFIG_FB_DEVICE=n. Currently, multiple fbdev drivers rely on 
>>> fb_info->dev
>>> and sysfs attribute registration unconditionally, which leads to build
>>> failures whenever FB_DEVICE is disabled.
>>>
>>> Thomas previously noted that FB_DEVICE should eventually become 
>>> optional
>>> and that drivers should not depend on sysfs or fb_info->dev being 
>>> present
>>> unless the Kconfig explicitly selects it. This series pushes in that
>>> direction by tightening the FB_DEVICE dependency boundary without 
>>> changing
>>> any runtime behaviour when FB_DEVICE=y.
>>>
>>> What this series does *not* change
>>>
>>> - No functional behaviour changes when FB_DEVICE=y.
>>> - No removal of sysfs interfaces.
>>> - No changes to fbops, memory allocation, or display update paths.
>>>
>>> Build & test coverage
>>>
>>> Tested with the following combinations:
>>>
>>> 1. **FB=y, FB_DEVICE=y**
>>>     - Baseline configuration; no regressions expected.
>>>
>>> 2. **FB=y, FB_DEVICE=n**
>>>     - Drivers build successfully.
>>>     - No sysfs attributes are created.
>>>     - fbdev devices operate normally (where applicable).
>>>
>>> 3. **FB=n**
>>>     - Drivers depend on FB, so they properly do not build, unchanged.
>>>
>>> Motivation
>>>
>>> This moves fbdev closer to supporting FB_DEVICE as truly optional, 
>>> helps
>>> reduce Kconfig entanglement, and clears several long-standing TODO 
>>> items
>>> as suggested by Thomas Zimmermann around legacy sysfs usage inside 
>>> fbdev
>>> drivers.
>>>
>>> Feedback is welcome, especially on whether the guard boundaries around
>>> sysfs are placed correctly or whether more logic should be pulled under
>>> CONFIG_FB_DEVICE.
>>
>> I left a comment on the first patch. If things still build nicely, then
>>
>> Acked-by: Thomas Zimmermann <tzimmermann@suse.de>
>>
>> for the series.
>
> This whole series adds a whole lot of ifdef'ery, which I think is the
> worst approach. It makes the code less readable and leads to two code
> paths, which may trigger different build errors depending on the config.
>
> I'm sure it must be possible to do the same without adding more #ifdefs,
> e.g. by introducing a function like   dev_of_fbinfo(fbinfo)  which
> simply returns NULL for the FB_DEVICE=n case.  Then, that value can be 
> tested
> like
>     if (dev_of_fbinfo(fbinfo))
>         {...do-the-things...}
> For the FB_DEVICE=n case this will then be optimized out by the compiler,
> while you still have full compiler syntax checking.
>
> Thoughts?

Your choice. I don't see this as an important fix. The FB_DEVICE=n case 
is mostly useful for DRM-based systems that do not want to expose fbdev 
interfaces to user space. Those are the vast majority today. The very 
few special builds with fbdev drivers would likely use FB_DEVICE=y anyway.

Best regards
Thomas

>
> Helge
>

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
GF: Jochen Jaser, Andrew McDonald, Werner Knoblich, (HRB 36809, AG Nürnberg)



^ permalink raw reply

* Re: [PATCH] riva/fbdev: fix divide error in nv3_arb()
From: kernel test robot @ 2025-12-09  9:02 UTC (permalink / raw)
  To: Guangshuo Li, Antonino Daplas, Helge Deller, linux-fbdev,
	dri-devel, linux-kernel
  Cc: oe-kbuild-all, Guangshuo Li, stable
In-Reply-To: <20251207072532.518547-1-lgs201920130244@gmail.com>

Hi Guangshuo,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on drm-tip/drm-tip linus/master v6.18 next-20251209]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Guangshuo-Li/riva-fbdev-fix-divide-error-in-nv3_arb/20251207-152840
base:   https://gitlab.freedesktop.org/drm/misc/kernel.git drm-misc-next
patch link:    https://lore.kernel.org/r/20251207072532.518547-1-lgs201920130244%40gmail.com
patch subject: [PATCH] riva/fbdev: fix divide error in nv3_arb()
config: x86_64-randconfig-161-20251209 (https://download.01.org/0day-ci/archive/20251209/202512091641.0Nz2KxpF-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512091641.0Nz2KxpF-lkp@intel.com/

New smatch warnings:
drivers/video/fbdev/riva/riva_hw.c:439 nv3_arb() warn: inconsistent indenting

Old smatch warnings:
drivers/video/fbdev/riva/riva_hw.c:982 nv10CalcArbitration() warn: inconsistent indenting

vim +439 drivers/video/fbdev/riva/riva_hw.c

   154	
   155	typedef struct {
   156	  int gdrain_rate;
   157	  int vdrain_rate;
   158	  int mdrain_rate;
   159	  int gburst_size;
   160	  int vburst_size;
   161	  char vid_en;
   162	  char gr_en;
   163	  int wcmocc, wcgocc, wcvocc, wcvlwm, wcglwm;
   164	  int by_gfacc;
   165	  char vid_only_once;
   166	  char gr_only_once;
   167	  char first_vacc;
   168	  char first_gacc;
   169	  char first_macc;
   170	  int vocc;
   171	  int gocc;
   172	  int mocc;
   173	  char cur;
   174	  char engine_en;
   175	  char converged;
   176	  int priority;
   177	} nv3_arb_info;
   178	typedef struct {
   179	  int graphics_lwm;
   180	  int video_lwm;
   181	  int graphics_burst_size;
   182	  int video_burst_size;
   183	  int graphics_hi_priority;
   184	  int media_hi_priority;
   185	  int rtl_values;
   186	  int valid;
   187	} nv3_fifo_info;
   188	typedef struct {
   189	  char pix_bpp;
   190	  char enable_video;
   191	  char gr_during_vid;
   192	  char enable_mp;
   193	  int memory_width;
   194	  int video_scale;
   195	  int pclk_khz;
   196	  int mclk_khz;
   197	  int mem_page_miss;
   198	  int mem_latency;
   199	  char mem_aligned;
   200	} nv3_sim_state;
   201	typedef struct {
   202	  int graphics_lwm;
   203	  int video_lwm;
   204	  int graphics_burst_size;
   205	  int video_burst_size;
   206	  int valid;
   207	} nv4_fifo_info;
   208	typedef struct {
   209	  int pclk_khz;
   210	  int mclk_khz;
   211	  int nvclk_khz;
   212	  char mem_page_miss;
   213	  char mem_latency;
   214	  int memory_width;
   215	  char enable_video;
   216	  char gr_during_vid;
   217	  char pix_bpp;
   218	  char mem_aligned;
   219	  char enable_mp;
   220	} nv4_sim_state;
   221	typedef struct {
   222	  int graphics_lwm;
   223	  int video_lwm;
   224	  int graphics_burst_size;
   225	  int video_burst_size;
   226	  int valid;
   227	} nv10_fifo_info;
   228	typedef struct {
   229	  int pclk_khz;
   230	  int mclk_khz;
   231	  int nvclk_khz;
   232	  char mem_page_miss;
   233	  char mem_latency;
   234	  u32 memory_type;
   235	  int memory_width;
   236	  char enable_video;
   237	  char gr_during_vid;
   238	  char pix_bpp;
   239	  char mem_aligned;
   240	  char enable_mp;
   241	} nv10_sim_state;
   242	static int nv3_iterate(nv3_fifo_info *res_info, nv3_sim_state * state, nv3_arb_info *ainfo)
   243	{
   244	    int iter = 0;
   245	    int tmp;
   246	    int vfsize, mfsize, gfsize;
   247	    int mburst_size = 32;
   248	    int mmisses, gmisses, vmisses;
   249	    int misses;
   250	    int vlwm, glwm;
   251	    int last, next, cur;
   252	    int max_gfsize ;
   253	    long ns;
   254	
   255	    vlwm = 0;
   256	    glwm = 0;
   257	    vfsize = 0;
   258	    gfsize = 0;
   259	    cur = ainfo->cur;
   260	    mmisses = 2;
   261	    gmisses = 2;
   262	    vmisses = 2;
   263	    if (ainfo->gburst_size == 128) max_gfsize = GFIFO_SIZE_128;
   264	    else  max_gfsize = GFIFO_SIZE;
   265	    max_gfsize = GFIFO_SIZE;
   266	    while (1)
   267	    {
   268	        if (ainfo->vid_en)
   269	        {
   270	            if (ainfo->wcvocc > ainfo->vocc) ainfo->wcvocc = ainfo->vocc;
   271	            if (ainfo->wcvlwm > vlwm) ainfo->wcvlwm = vlwm ;
   272	            ns = 1000000 * ainfo->vburst_size/(state->memory_width/8)/state->mclk_khz;
   273	            vfsize = ns * ainfo->vdrain_rate / 1000000;
   274	            vfsize =  ainfo->wcvlwm - ainfo->vburst_size + vfsize;
   275	        }
   276	        if (state->enable_mp)
   277	        {
   278	            if (ainfo->wcmocc > ainfo->mocc) ainfo->wcmocc = ainfo->mocc;
   279	        }
   280	        if (ainfo->gr_en)
   281	        {
   282	            if (ainfo->wcglwm > glwm) ainfo->wcglwm = glwm ;
   283	            if (ainfo->wcgocc > ainfo->gocc) ainfo->wcgocc = ainfo->gocc;
   284	            ns = 1000000 * (ainfo->gburst_size/(state->memory_width/8))/state->mclk_khz;
   285	            gfsize = (ns * (long) ainfo->gdrain_rate)/1000000;
   286	            gfsize = ainfo->wcglwm - ainfo->gburst_size + gfsize;
   287	        }
   288	        mfsize = 0;
   289	        if (!state->gr_during_vid && ainfo->vid_en)
   290	            if (ainfo->vid_en && (ainfo->vocc < 0) && !ainfo->vid_only_once)
   291	                next = VIDEO;
   292	            else if (ainfo->mocc < 0)
   293	                next = MPORT;
   294	            else if (ainfo->gocc< ainfo->by_gfacc)
   295	                next = GRAPHICS;
   296	            else return (0);
   297	        else switch (ainfo->priority)
   298	            {
   299	                case VIDEO:
   300	                    if (ainfo->vid_en && ainfo->vocc<0 && !ainfo->vid_only_once)
   301	                        next = VIDEO;
   302	                    else if (ainfo->gr_en && ainfo->gocc<0 && !ainfo->gr_only_once)
   303	                        next = GRAPHICS;
   304	                    else if (ainfo->mocc<0)
   305	                        next = MPORT;
   306	                    else    return (0);
   307	                    break;
   308	                case GRAPHICS:
   309	                    if (ainfo->gr_en && ainfo->gocc<0 && !ainfo->gr_only_once)
   310	                        next = GRAPHICS;
   311	                    else if (ainfo->vid_en && ainfo->vocc<0 && !ainfo->vid_only_once)
   312	                        next = VIDEO;
   313	                    else if (ainfo->mocc<0)
   314	                        next = MPORT;
   315	                    else    return (0);
   316	                    break;
   317	                default:
   318	                    if (ainfo->mocc<0)
   319	                        next = MPORT;
   320	                    else if (ainfo->gr_en && ainfo->gocc<0 && !ainfo->gr_only_once)
   321	                        next = GRAPHICS;
   322	                    else if (ainfo->vid_en && ainfo->vocc<0 && !ainfo->vid_only_once)
   323	                        next = VIDEO;
   324	                    else    return (0);
   325	                    break;
   326	            }
   327	        last = cur;
   328	        cur = next;
   329	        iter++;
   330	        switch (cur)
   331	        {
   332	            case VIDEO:
   333	                if (last==cur)    misses = 0;
   334	                else if (ainfo->first_vacc)   misses = vmisses;
   335	                else    misses = 1;
   336	                ainfo->first_vacc = 0;
   337	                if (last!=cur)
   338	                {
   339	                    ns =  1000000 * (vmisses*state->mem_page_miss + state->mem_latency)/state->mclk_khz; 
   340	                    vlwm = ns * ainfo->vdrain_rate/ 1000000;
   341	                    vlwm = ainfo->vocc - vlwm;
   342	                }
   343	                ns = 1000000*(misses*state->mem_page_miss + ainfo->vburst_size)/(state->memory_width/8)/state->mclk_khz;
   344	                ainfo->vocc = ainfo->vocc + ainfo->vburst_size - ns*ainfo->vdrain_rate/1000000;
   345	                ainfo->gocc = ainfo->gocc - ns*ainfo->gdrain_rate/1000000;
   346	                ainfo->mocc = ainfo->mocc - ns*ainfo->mdrain_rate/1000000;
   347	                break;
   348	            case GRAPHICS:
   349	                if (last==cur)    misses = 0;
   350	                else if (ainfo->first_gacc)   misses = gmisses;
   351	                else    misses = 1;
   352	                ainfo->first_gacc = 0;
   353	                if (last!=cur)
   354	                {
   355	                    ns = 1000000*(gmisses*state->mem_page_miss + state->mem_latency)/state->mclk_khz ;
   356	                    glwm = ns * ainfo->gdrain_rate/1000000;
   357	                    glwm = ainfo->gocc - glwm;
   358	                }
   359	                ns = 1000000*(misses*state->mem_page_miss + ainfo->gburst_size/(state->memory_width/8))/state->mclk_khz;
   360	                ainfo->vocc = ainfo->vocc + 0 - ns*ainfo->vdrain_rate/1000000;
   361	                ainfo->gocc = ainfo->gocc + ainfo->gburst_size - ns*ainfo->gdrain_rate/1000000;
   362	                ainfo->mocc = ainfo->mocc + 0 - ns*ainfo->mdrain_rate/1000000;
   363	                break;
   364	            default:
   365	                if (last==cur)    misses = 0;
   366	                else if (ainfo->first_macc)   misses = mmisses;
   367	                else    misses = 1;
   368	                ainfo->first_macc = 0;
   369	                ns = 1000000*(misses*state->mem_page_miss + mburst_size/(state->memory_width/8))/state->mclk_khz;
   370	                ainfo->vocc = ainfo->vocc + 0 - ns*ainfo->vdrain_rate/1000000;
   371	                ainfo->gocc = ainfo->gocc + 0 - ns*ainfo->gdrain_rate/1000000;
   372	                ainfo->mocc = ainfo->mocc + mburst_size - ns*ainfo->mdrain_rate/1000000;
   373	                break;
   374	        }
   375	        if (iter>100)
   376	        {
   377	            ainfo->converged = 0;
   378	            return (1);
   379	        }
   380	        ns = 1000000*ainfo->gburst_size/(state->memory_width/8)/state->mclk_khz;
   381	        tmp = ns * ainfo->gdrain_rate/1000000;
   382	        if (abs(ainfo->gburst_size) + ((abs(ainfo->wcglwm) + 16 ) & ~0x7) - tmp > max_gfsize)
   383	        {
   384	            ainfo->converged = 0;
   385	            return (1);
   386	        }
   387	        ns = 1000000*ainfo->vburst_size/(state->memory_width/8)/state->mclk_khz;
   388	        tmp = ns * ainfo->vdrain_rate/1000000;
   389	        if (abs(ainfo->vburst_size) + (abs(ainfo->wcvlwm + 32) & ~0xf)  - tmp> VFIFO_SIZE)
   390	        {
   391	            ainfo->converged = 0;
   392	            return (1);
   393	        }
   394	        if (abs(ainfo->gocc) > max_gfsize)
   395	        {
   396	            ainfo->converged = 0;
   397	            return (1);
   398	        }
   399	        if (abs(ainfo->vocc) > VFIFO_SIZE)
   400	        {
   401	            ainfo->converged = 0;
   402	            return (1);
   403	        }
   404	        if (abs(ainfo->mocc) > MFIFO_SIZE)
   405	        {
   406	            ainfo->converged = 0;
   407	            return (1);
   408	        }
   409	        if (abs(vfsize) > VFIFO_SIZE)
   410	        {
   411	            ainfo->converged = 0;
   412	            return (1);
   413	        }
   414	        if (abs(gfsize) > max_gfsize)
   415	        {
   416	            ainfo->converged = 0;
   417	            return (1);
   418	        }
   419	        if (abs(mfsize) > MFIFO_SIZE)
   420	        {
   421	            ainfo->converged = 0;
   422	            return (1);
   423	        }
   424	    }
   425	}
   426	static char nv3_arb(nv3_fifo_info * res_info, nv3_sim_state * state,  nv3_arb_info *ainfo) 
   427	{
   428	    long ens, vns, mns, gns;
   429	    int mmisses, gmisses, vmisses, eburst_size, mburst_size;
   430	    int refresh_cycle;
   431	
   432	    refresh_cycle = 2*(state->mclk_khz/state->pclk_khz) + 5;
   433	    mmisses = 2;
   434	    if (state->mem_aligned) gmisses = 2;
   435	    else    gmisses = 3;
   436	    vmisses = 2;
   437	    eburst_size = state->memory_width * 1;
   438	    mburst_size = 32;
 > 439		if (!state->mclk_khz)
   440			return (0);
   441	
   442	    gns = 1000000 * (gmisses*state->mem_page_miss + state->mem_latency)/state->mclk_khz;
   443	    ainfo->by_gfacc = gns*ainfo->gdrain_rate/1000000;
   444	    ainfo->wcmocc = 0;
   445	    ainfo->wcgocc = 0;
   446	    ainfo->wcvocc = 0;
   447	    ainfo->wcvlwm = 0;
   448	    ainfo->wcglwm = 0;
   449	    ainfo->engine_en = 1;
   450	    ainfo->converged = 1;
   451	    if (ainfo->engine_en)
   452	    {
   453	        ens =  1000000*(state->mem_page_miss + eburst_size/(state->memory_width/8) +refresh_cycle)/state->mclk_khz;
   454	        ainfo->mocc = state->enable_mp ? 0-ens*ainfo->mdrain_rate/1000000 : 0;
   455	        ainfo->vocc = ainfo->vid_en ? 0-ens*ainfo->vdrain_rate/1000000 : 0;
   456	        ainfo->gocc = ainfo->gr_en ? 0-ens*ainfo->gdrain_rate/1000000 : 0;
   457	        ainfo->cur = ENGINE;
   458	        ainfo->first_vacc = 1;
   459	        ainfo->first_gacc = 1;
   460	        ainfo->first_macc = 1;
   461	        nv3_iterate(res_info, state,ainfo);
   462	    }
   463	    if (state->enable_mp)
   464	    {
   465	        mns = 1000000 * (mmisses*state->mem_page_miss + mburst_size/(state->memory_width/8) + refresh_cycle)/state->mclk_khz;
   466	        ainfo->mocc = state->enable_mp ? 0 : mburst_size - mns*ainfo->mdrain_rate/1000000;
   467	        ainfo->vocc = ainfo->vid_en ? 0 : 0- mns*ainfo->vdrain_rate/1000000;
   468	        ainfo->gocc = ainfo->gr_en ? 0: 0- mns*ainfo->gdrain_rate/1000000;
   469	        ainfo->cur = MPORT;
   470	        ainfo->first_vacc = 1;
   471	        ainfo->first_gacc = 1;
   472	        ainfo->first_macc = 0;
   473	        nv3_iterate(res_info, state,ainfo);
   474	    }
   475	    if (ainfo->gr_en)
   476	    {
   477	        ainfo->first_vacc = 1;
   478	        ainfo->first_gacc = 0;
   479	        ainfo->first_macc = 1;
   480	        gns = 1000000*(gmisses*state->mem_page_miss + ainfo->gburst_size/(state->memory_width/8) + refresh_cycle)/state->mclk_khz;
   481	        ainfo->gocc = ainfo->gburst_size - gns*ainfo->gdrain_rate/1000000;
   482	        ainfo->vocc = ainfo->vid_en? 0-gns*ainfo->vdrain_rate/1000000 : 0;
   483	        ainfo->mocc = state->enable_mp ?  0-gns*ainfo->mdrain_rate/1000000: 0;
   484	        ainfo->cur = GRAPHICS;
   485	        nv3_iterate(res_info, state,ainfo);
   486	    }
   487	    if (ainfo->vid_en)
   488	    {
   489	        ainfo->first_vacc = 0;
   490	        ainfo->first_gacc = 1;
   491	        ainfo->first_macc = 1;
   492	        vns = 1000000*(vmisses*state->mem_page_miss + ainfo->vburst_size/(state->memory_width/8) + refresh_cycle)/state->mclk_khz;
   493	        ainfo->vocc = ainfo->vburst_size - vns*ainfo->vdrain_rate/1000000;
   494	        ainfo->gocc = ainfo->gr_en? (0-vns*ainfo->gdrain_rate/1000000) : 0;
   495	        ainfo->mocc = state->enable_mp? 0-vns*ainfo->mdrain_rate/1000000 :0 ;
   496	        ainfo->cur = VIDEO;
   497	        nv3_iterate(res_info, state, ainfo);
   498	    }
   499	    if (ainfo->converged)
   500	    {
   501	        res_info->graphics_lwm = (int)abs(ainfo->wcglwm) + 16;
   502	        res_info->video_lwm = (int)abs(ainfo->wcvlwm) + 32;
   503	        res_info->graphics_burst_size = ainfo->gburst_size;
   504	        res_info->video_burst_size = ainfo->vburst_size;
   505	        res_info->graphics_hi_priority = (ainfo->priority == GRAPHICS);
   506	        res_info->media_hi_priority = (ainfo->priority == MPORT);
   507	        if (res_info->video_lwm > 160)
   508	        {
   509	            res_info->graphics_lwm = 256;
   510	            res_info->video_lwm = 128;
   511	            res_info->graphics_burst_size = 64;
   512	            res_info->video_burst_size = 64;
   513	            res_info->graphics_hi_priority = 0;
   514	            res_info->media_hi_priority = 0;
   515	            ainfo->converged = 0;
   516	            return (0);
   517	        }
   518	        if (res_info->video_lwm > 128)
   519	        {
   520	            res_info->video_lwm = 128;
   521	        }
   522	        return (1);
   523	    }
   524	    else
   525	    {
   526	        res_info->graphics_lwm = 256;
   527	        res_info->video_lwm = 128;
   528	        res_info->graphics_burst_size = 64;
   529	        res_info->video_burst_size = 64;
   530	        res_info->graphics_hi_priority = 0;
   531	        res_info->media_hi_priority = 0;
   532	        return (0);
   533	    }
   534	}
   535	static char nv3_get_param(nv3_fifo_info *res_info, nv3_sim_state * state, nv3_arb_info *ainfo)
   536	{
   537	    int done, g,v, p;
   538	    
   539	    done = 0;
   540	    for (p=0; p < 2; p++)
   541	    {
   542	        for (g=128 ; g > 32; g= g>> 1)
   543	        {
   544	            for (v=128; v >=32; v = v>> 1)
   545	            {
   546	                ainfo->priority = p;
   547	                ainfo->gburst_size = g;     
   548	                ainfo->vburst_size = v;
   549	                done = nv3_arb(res_info, state,ainfo);
   550	                if (done && (g==128))
   551	                    if ((res_info->graphics_lwm + g) > 256)
   552	                        done = 0;
   553	                if (done)
   554	                    goto Done;
   555	            }
   556	        }
   557	    }
   558	
   559	 Done:
   560	    return done;
   561	}
   562	static void nv3CalcArbitration 
   563	(
   564	    nv3_fifo_info * res_info,
   565	    nv3_sim_state * state
   566	)
   567	{
   568	    nv3_fifo_info save_info;
   569	    nv3_arb_info ainfo;
   570	    char   res_gr, res_vid;
   571	
   572	    ainfo.gr_en = 1;
   573	    ainfo.vid_en = state->enable_video;
   574	    ainfo.vid_only_once = 0;
   575	    ainfo.gr_only_once = 0;
   576	    ainfo.gdrain_rate = (int) state->pclk_khz * (state->pix_bpp/8);
   577	    ainfo.vdrain_rate = (int) state->pclk_khz * 2;
   578	    if (state->video_scale != 0)
   579	        ainfo.vdrain_rate = ainfo.vdrain_rate/state->video_scale;
   580	    ainfo.mdrain_rate = 33000;
   581	    res_info->rtl_values = 0;
   582	    if (!state->gr_during_vid && state->enable_video)
   583	    {
   584	        ainfo.gr_only_once = 1;
   585	        ainfo.gr_en = 1;
   586	        ainfo.gdrain_rate = 0;
   587	        res_vid = nv3_get_param(res_info, state,  &ainfo);
   588	        res_vid = ainfo.converged;
   589	        save_info.video_lwm = res_info->video_lwm;
   590	        save_info.video_burst_size = res_info->video_burst_size;
   591	        ainfo.vid_en = 1;
   592	        ainfo.vid_only_once = 1;
   593	        ainfo.gr_en = 1;
   594	        ainfo.gdrain_rate = (int) state->pclk_khz * (state->pix_bpp/8);
   595	        ainfo.vdrain_rate = 0;
   596	        res_gr = nv3_get_param(res_info, state,  &ainfo);
   597	        res_gr = ainfo.converged;
   598	        res_info->video_lwm = save_info.video_lwm;
   599	        res_info->video_burst_size = save_info.video_burst_size;
   600	        res_info->valid = res_gr & res_vid;
   601	    }
   602	    else
   603	    {
   604	        if (!ainfo.gr_en) ainfo.gdrain_rate = 0;
   605	        if (!ainfo.vid_en) ainfo.vdrain_rate = 0;
   606	        res_gr = nv3_get_param(res_info, state,  &ainfo);
   607	        res_info->valid = ainfo.converged;
   608	    }
   609	}
   610	static void nv3UpdateArbitrationSettings
   611	(
   612	    unsigned      VClk, 
   613	    unsigned      pixelDepth, 
   614	    unsigned     *burst,
   615	    unsigned     *lwm,
   616	    RIVA_HW_INST *chip
   617	)
   618	{
   619	    nv3_fifo_info fifo_data;
   620	    nv3_sim_state sim_data;
   621	    unsigned int M, N, P, pll, MClk;
   622	    
   623	    pll = NV_RD32(&chip->PRAMDAC0[0x00000504/4], 0);
   624	    M = (pll >> 0) & 0xFF; N = (pll >> 8) & 0xFF; P = (pll >> 16) & 0x0F;
   625	    MClk = (N * chip->CrystalFreqKHz / M) >> P;
   626	    sim_data.pix_bpp        = (char)pixelDepth;
   627	    sim_data.enable_video   = 0;
   628	    sim_data.enable_mp      = 0;
   629	    sim_data.video_scale    = 1;
   630	    sim_data.memory_width   = (NV_RD32(&chip->PEXTDEV[0x00000000/4], 0) & 0x10) ?
   631		128 : 64;
   632	    sim_data.memory_width   = 128;
   633	
   634	    sim_data.mem_latency    = 9;
   635	    sim_data.mem_aligned    = 1;
   636	    sim_data.mem_page_miss  = 11;
   637	    sim_data.gr_during_vid  = 0;
   638	    sim_data.pclk_khz       = VClk;
   639	    sim_data.mclk_khz       = MClk;
   640	    nv3CalcArbitration(&fifo_data, &sim_data);
   641	    if (fifo_data.valid)
   642	    {
   643	        int  b = fifo_data.graphics_burst_size >> 4;
   644	        *burst = 0;
   645	        while (b >>= 1)
   646		    (*burst)++;
   647	        *lwm   = fifo_data.graphics_lwm >> 3;
   648	    }
   649	    else
   650	    {
   651	        *lwm   = 0x24;
   652	        *burst = 0x2;
   653	    }
   654	}
   655	static void nv4CalcArbitration 
   656	(
   657	    nv4_fifo_info *fifo,
   658	    nv4_sim_state *arb
   659	)
   660	{
   661	    int data, pagemiss, cas,width, video_enable, bpp;
   662	    int nvclks, mclks, pclks, vpagemiss, crtpagemiss, vbs;
   663	    int found, mclk_extra, mclk_loop, cbs, m1, p1;
   664	    int mclk_freq, pclk_freq, nvclk_freq, mp_enable;
   665	    int us_m, us_n, us_p, video_drain_rate, crtc_drain_rate;
   666	    int vpm_us, us_video, vlwm, video_fill_us, cpm_us, us_crt,clwm;
   667	
   668	    fifo->valid = 1;
   669	    pclk_freq = arb->pclk_khz;
   670	    mclk_freq = arb->mclk_khz;
   671	    nvclk_freq = arb->nvclk_khz;
   672	    pagemiss = arb->mem_page_miss;
   673	    cas = arb->mem_latency;
   674	    width = arb->memory_width >> 6;
   675	    video_enable = arb->enable_video;
   676	    bpp = arb->pix_bpp;
   677	    mp_enable = arb->enable_mp;
   678	    clwm = 0;
   679	    vlwm = 0;
   680	    cbs = 128;
   681	    pclks = 2;
   682	    nvclks = 2;
   683	    nvclks += 2;
   684	    nvclks += 1;
   685	    mclks = 5;
   686	    mclks += 3;
   687	    mclks += 1;
   688	    mclks += cas;
   689	    mclks += 1;
   690	    mclks += 1;
   691	    mclks += 1;
   692	    mclks += 1;
   693	    mclk_extra = 3;
   694	    nvclks += 2;
   695	    nvclks += 1;
   696	    nvclks += 1;
   697	    nvclks += 1;
   698	    if (mp_enable)
   699	        mclks+=4;
   700	    nvclks += 0;
   701	    pclks += 0;
   702	    found = 0;
   703	    vbs = 0;
   704	    while (found != 1)
   705	    {
   706	        fifo->valid = 1;
   707	        found = 1;
   708	        mclk_loop = mclks+mclk_extra;
   709	        us_m = mclk_loop *1000*1000 / mclk_freq;
   710	        us_n = nvclks*1000*1000 / nvclk_freq;
   711	        us_p = nvclks*1000*1000 / pclk_freq;
   712	        if (video_enable)
   713	        {
   714	            video_drain_rate = pclk_freq * 2;
   715	            crtc_drain_rate = pclk_freq * bpp/8;
   716	            vpagemiss = 2;
   717	            vpagemiss += 1;
   718	            crtpagemiss = 2;
   719	            vpm_us = (vpagemiss * pagemiss)*1000*1000/mclk_freq;
   720	            if (nvclk_freq * 2 > mclk_freq * width)
   721	                video_fill_us = cbs*1000*1000 / 16 / nvclk_freq ;
   722	            else
   723	                video_fill_us = cbs*1000*1000 / (8 * width) / mclk_freq;
   724	            us_video = vpm_us + us_m + us_n + us_p + video_fill_us;
   725	            vlwm = us_video * video_drain_rate/(1000*1000);
   726	            vlwm++;
   727	            vbs = 128;
   728	            if (vlwm > 128) vbs = 64;
   729	            if (vlwm > (256-64)) vbs = 32;
   730	            if (nvclk_freq * 2 > mclk_freq * width)
   731	                video_fill_us = vbs *1000*1000/ 16 / nvclk_freq ;
   732	            else
   733	                video_fill_us = vbs*1000*1000 / (8 * width) / mclk_freq;
   734	            cpm_us = crtpagemiss  * pagemiss *1000*1000/ mclk_freq;
   735	            us_crt =
   736	            us_video
   737	            +video_fill_us
   738	            +cpm_us
   739	            +us_m + us_n +us_p
   740	            ;
   741	            clwm = us_crt * crtc_drain_rate/(1000*1000);
   742	            clwm++;
   743	        }
   744	        else
   745	        {
   746	            crtc_drain_rate = pclk_freq * bpp/8;
   747	            crtpagemiss = 2;
   748	            crtpagemiss += 1;
   749	            cpm_us = crtpagemiss  * pagemiss *1000*1000/ mclk_freq;
   750	            us_crt =  cpm_us + us_m + us_n + us_p ;
   751	            clwm = us_crt * crtc_drain_rate/(1000*1000);
   752	            clwm++;
   753	        }
   754	        m1 = clwm + cbs - 512;
   755	        p1 = m1 * pclk_freq / mclk_freq;
   756	        p1 = p1 * bpp / 8;
   757	        if ((p1 < m1) && (m1 > 0))
   758	        {
   759	            fifo->valid = 0;
   760	            found = 0;
   761	            if (mclk_extra ==0)   found = 1;
   762	            mclk_extra--;
   763	        }
   764	        else if (video_enable)
   765	        {
   766	            if ((clwm > 511) || (vlwm > 255))
   767	            {
   768	                fifo->valid = 0;
   769	                found = 0;
   770	                if (mclk_extra ==0)   found = 1;
   771	                mclk_extra--;
   772	            }
   773	        }
   774	        else
   775	        {
   776	            if (clwm > 519)
   777	            {
   778	                fifo->valid = 0;
   779	                found = 0;
   780	                if (mclk_extra ==0)   found = 1;
   781	                mclk_extra--;
   782	            }
   783	        }
   784	        if (clwm < 384) clwm = 384;
   785	        if (vlwm < 128) vlwm = 128;
   786	        data = (int)(clwm);
   787	        fifo->graphics_lwm = data;
   788	        fifo->graphics_burst_size = 128;
   789	        data = (int)((vlwm+15));
   790	        fifo->video_lwm = data;
   791	        fifo->video_burst_size = vbs;
   792	    }
   793	}
   794	static void nv4UpdateArbitrationSettings
   795	(
   796	    unsigned      VClk, 
   797	    unsigned      pixelDepth, 
   798	    unsigned     *burst,
   799	    unsigned     *lwm,
   800	    RIVA_HW_INST *chip
   801	)
   802	{
   803	    nv4_fifo_info fifo_data;
   804	    nv4_sim_state sim_data;
   805	    unsigned int M, N, P, pll, MClk, NVClk, cfg1;
   806	
   807	    pll = NV_RD32(&chip->PRAMDAC0[0x00000504/4], 0);
   808	    M = (pll >> 0)  & 0xFF; N = (pll >> 8)  & 0xFF; P = (pll >> 16) & 0x0F;
   809	    MClk  = (N * chip->CrystalFreqKHz / M) >> P;
   810	    pll = NV_RD32(&chip->PRAMDAC0[0x00000500/4], 0);
   811	    M = (pll >> 0)  & 0xFF; N = (pll >> 8)  & 0xFF; P = (pll >> 16) & 0x0F;
   812	    NVClk  = (N * chip->CrystalFreqKHz / M) >> P;
   813	    cfg1 = NV_RD32(&chip->PFB[0x00000204/4], 0);
   814	    sim_data.pix_bpp        = (char)pixelDepth;
   815	    sim_data.enable_video   = 0;
   816	    sim_data.enable_mp      = 0;
   817	    sim_data.memory_width   = (NV_RD32(&chip->PEXTDEV[0x00000000/4], 0) & 0x10) ?
   818		128 : 64;
   819	    sim_data.mem_latency    = (char)cfg1 & 0x0F;
   820	    sim_data.mem_aligned    = 1;
   821	    sim_data.mem_page_miss  = (char)(((cfg1 >> 4) &0x0F) + ((cfg1 >> 31) & 0x01));
   822	    sim_data.gr_during_vid  = 0;
   823	    sim_data.pclk_khz       = VClk;
   824	    sim_data.mclk_khz       = MClk;
   825	    sim_data.nvclk_khz      = NVClk;
   826	    nv4CalcArbitration(&fifo_data, &sim_data);
   827	    if (fifo_data.valid)
   828	    {
   829	        int  b = fifo_data.graphics_burst_size >> 4;
   830	        *burst = 0;
   831	        while (b >>= 1)
   832		    (*burst)++;
   833	        *lwm   = fifo_data.graphics_lwm >> 3;
   834	    }
   835	}
   836	static void nv10CalcArbitration 
   837	(
   838	    nv10_fifo_info *fifo,
   839	    nv10_sim_state *arb
   840	)
   841	{
   842	    int data, pagemiss, width, video_enable, bpp;
   843	    int nvclks, mclks, pclks, vpagemiss, crtpagemiss;
   844	    int nvclk_fill;
   845	    int found, mclk_extra, mclk_loop, cbs, m1;
   846	    int mclk_freq, pclk_freq, nvclk_freq, mp_enable;
   847	    int us_m, us_m_min, us_n, us_p, crtc_drain_rate;
   848	    int vus_m;
   849	    int vpm_us, us_video, cpm_us, us_crt,clwm;
   850	    int clwm_rnd_down;
   851	    int m2us, us_pipe_min, p1clk, p2;
   852	    int min_mclk_extra;
   853	    int us_min_mclk_extra;
   854	
   855	    fifo->valid = 1;
   856	    pclk_freq = arb->pclk_khz; /* freq in KHz */
   857	    mclk_freq = arb->mclk_khz;
   858	    nvclk_freq = arb->nvclk_khz;
   859	    pagemiss = arb->mem_page_miss;
   860	    width = arb->memory_width/64;
   861	    video_enable = arb->enable_video;
   862	    bpp = arb->pix_bpp;
   863	    mp_enable = arb->enable_mp;
   864	    clwm = 0;
   865	
   866	    cbs = 512;
   867	
   868	    pclks = 4; /* lwm detect. */
   869	
   870	    nvclks = 3; /* lwm -> sync. */
   871	    nvclks += 2; /* fbi bus cycles (1 req + 1 busy) */
   872	
   873	    mclks  = 1;   /* 2 edge sync.  may be very close to edge so just put one. */
   874	
   875	    mclks += 1;   /* arb_hp_req */
   876	    mclks += 5;   /* ap_hp_req   tiling pipeline */
   877	
   878	    mclks += 2;    /* tc_req     latency fifo */
   879	    mclks += 2;    /* fb_cas_n_  memory request to fbio block */
   880	    mclks += 7;    /* sm_d_rdv   data returned from fbio block */
   881	
   882	    /* fb.rd.d.Put_gc   need to accumulate 256 bits for read */
   883	    if (arb->memory_type == 0)
   884	      if (arb->memory_width == 64) /* 64 bit bus */
   885	        mclks += 4;
   886	      else
   887	        mclks += 2;
   888	    else
   889	      if (arb->memory_width == 64) /* 64 bit bus */
   890	        mclks += 2;
   891	      else
   892	        mclks += 1;
   893	
   894	    if ((!video_enable) && (arb->memory_width == 128))
   895	    {  
   896	      mclk_extra = (bpp == 32) ? 31 : 42; /* Margin of error */
   897	      min_mclk_extra = 17;
   898	    }
   899	    else
   900	    {
   901	      mclk_extra = (bpp == 32) ? 8 : 4; /* Margin of error */
   902	      /* mclk_extra = 4; */ /* Margin of error */
   903	      min_mclk_extra = 18;
   904	    }
   905	
   906	    nvclks += 1; /* 2 edge sync.  may be very close to edge so just put one. */
   907	    nvclks += 1; /* fbi_d_rdv_n */
   908	    nvclks += 1; /* Fbi_d_rdata */
   909	    nvclks += 1; /* crtfifo load */
   910	
   911	    if(mp_enable)
   912	      mclks+=4; /* Mp can get in with a burst of 8. */
   913	    /* Extra clocks determined by heuristics */
   914	
   915	    nvclks += 0;
   916	    pclks += 0;
   917	    found = 0;
   918	    while(found != 1) {
   919	      fifo->valid = 1;
   920	      found = 1;
   921	      mclk_loop = mclks+mclk_extra;
   922	      us_m = mclk_loop *1000*1000 / mclk_freq; /* Mclk latency in us */
   923	      us_m_min = mclks * 1000*1000 / mclk_freq; /* Minimum Mclk latency in us */
   924	      us_min_mclk_extra = min_mclk_extra *1000*1000 / mclk_freq;
   925	      us_n = nvclks*1000*1000 / nvclk_freq;/* nvclk latency in us */
   926	      us_p = pclks*1000*1000 / pclk_freq;/* nvclk latency in us */
   927	      us_pipe_min = us_m_min + us_n + us_p;
   928	
   929	      vus_m = mclk_loop *1000*1000 / mclk_freq; /* Mclk latency in us */
   930	
   931	      if(video_enable) {
   932	        crtc_drain_rate = pclk_freq * bpp/8; /* MB/s */
   933	
   934	        vpagemiss = 1; /* self generating page miss */
   935	        vpagemiss += 1; /* One higher priority before */
   936	
   937	        crtpagemiss = 2; /* self generating page miss */
   938	        if(mp_enable)
   939	            crtpagemiss += 1; /* if MA0 conflict */
   940	
   941	        vpm_us = (vpagemiss * pagemiss)*1000*1000/mclk_freq;
   942	
   943	        us_video = vpm_us + vus_m; /* Video has separate read return path */
   944	
   945	        cpm_us = crtpagemiss  * pagemiss *1000*1000/ mclk_freq;
   946	        us_crt =
   947	          us_video  /* Wait for video */
   948	          +cpm_us /* CRT Page miss */
   949	          +us_m + us_n +us_p /* other latency */
   950	          ;
   951	
   952	        clwm = us_crt * crtc_drain_rate/(1000*1000);
   953	        clwm++; /* fixed point <= float_point - 1.  Fixes that */
   954	      } else {
   955	        crtc_drain_rate = pclk_freq * bpp/8; /* bpp * pclk/8 */
   956	
   957	        crtpagemiss = 1; /* self generating page miss */
   958	        crtpagemiss += 1; /* MA0 page miss */
   959	        if(mp_enable)
   960	            crtpagemiss += 1; /* if MA0 conflict */
   961	        cpm_us = crtpagemiss  * pagemiss *1000*1000/ mclk_freq;
   962	        us_crt =  cpm_us + us_m + us_n + us_p ;
   963	        clwm = us_crt * crtc_drain_rate/(1000*1000);
   964	        clwm++; /* fixed point <= float_point - 1.  Fixes that */
   965	
   966	  /*
   967	          //
   968	          // Another concern, only for high pclks so don't do this
   969	          // with video:
   970	          // What happens if the latency to fetch the cbs is so large that
   971	          // fifo empties.  In that case we need to have an alternate clwm value
   972	          // based off the total burst fetch
   973	          //
   974	          us_crt = (cbs * 1000 * 1000)/ (8*width)/mclk_freq ;
   975	          us_crt = us_crt + us_m + us_n + us_p + (4 * 1000 * 1000)/mclk_freq;
   976	          clwm_mt = us_crt * crtc_drain_rate/(1000*1000);
   977	          clwm_mt ++;
   978	          if(clwm_mt > clwm)
   979	              clwm = clwm_mt;
   980	  */
   981	          /* Finally, a heuristic check when width == 64 bits */
   982	          if(width == 1){
   983	              nvclk_fill = nvclk_freq * 8;
   984	              if(crtc_drain_rate * 100 >= nvclk_fill * 102)
   985	                      clwm = 0xfff; /*Large number to fail */
   986	
   987	              else if(crtc_drain_rate * 100  >= nvclk_fill * 98) {
   988	                  clwm = 1024;
   989	                  cbs = 512;
   990	              }
   991	          }
   992	      }
   993	
   994	
   995	      /*
   996	        Overfill check:
   997	
   998	        */
   999	
  1000	      clwm_rnd_down = ((int)clwm/8)*8;
  1001	      if (clwm_rnd_down < clwm)
  1002	          clwm += 8;
  1003	
  1004	      m1 = clwm + cbs -  1024; /* Amount of overfill */
  1005	      m2us = us_pipe_min + us_min_mclk_extra;
  1006	
  1007	      /* pclk cycles to drain */
  1008	      p1clk = m2us * pclk_freq/(1000*1000); 
  1009	      p2 = p1clk * bpp / 8; /* bytes drained. */
  1010	
  1011	      if((p2 < m1) && (m1 > 0)) {
  1012	          fifo->valid = 0;
  1013	          found = 0;
  1014	          if(min_mclk_extra == 0)   {
  1015	            if(cbs <= 32) {
  1016	              found = 1; /* Can't adjust anymore! */
  1017	            } else {
  1018	              cbs = cbs/2;  /* reduce the burst size */
  1019	            }
  1020	          } else {
  1021	            min_mclk_extra--;
  1022	          }
  1023	      } else {
  1024	        if (clwm > 1023){ /* Have some margin */
  1025	          fifo->valid = 0;
  1026	          found = 0;
  1027	          if(min_mclk_extra == 0)   
  1028	              found = 1; /* Can't adjust anymore! */
  1029	          else 
  1030	              min_mclk_extra--;
  1031	        }
  1032	      }
  1033	
  1034	      if(clwm < (1024-cbs+8)) clwm = 1024-cbs+8;
  1035	      data = (int)(clwm);
  1036	      /*  printf("CRT LWM: %f bytes, prog: 0x%x, bs: 256\n", clwm, data ); */
  1037	      fifo->graphics_lwm = data;   fifo->graphics_burst_size = cbs;
  1038	
  1039	      /*  printf("VID LWM: %f bytes, prog: 0x%x, bs: %d\n, ", vlwm, data, vbs ); */
  1040	      fifo->video_lwm = 1024;  fifo->video_burst_size = 512;
  1041	    }
  1042	}
  1043	static void nv10UpdateArbitrationSettings
  1044	(
  1045	    unsigned      VClk, 
  1046	    unsigned      pixelDepth, 
  1047	    unsigned     *burst,
  1048	    unsigned     *lwm,
  1049	    RIVA_HW_INST *chip
  1050	)
  1051	{
  1052	    nv10_fifo_info fifo_data;
  1053	    nv10_sim_state sim_data;
  1054	    unsigned int M, N, P, pll, MClk, NVClk, cfg1;
  1055	
  1056	    pll = NV_RD32(&chip->PRAMDAC0[0x00000504/4], 0);
  1057	    M = (pll >> 0)  & 0xFF; N = (pll >> 8)  & 0xFF; P = (pll >> 16) & 0x0F;
  1058	    MClk  = (N * chip->CrystalFreqKHz / M) >> P;
  1059	    pll = NV_RD32(&chip->PRAMDAC0[0x00000500/4], 0);
  1060	    M = (pll >> 0)  & 0xFF; N = (pll >> 8)  & 0xFF; P = (pll >> 16) & 0x0F;
  1061	    NVClk  = (N * chip->CrystalFreqKHz / M) >> P;
  1062	    cfg1 = NV_RD32(&chip->PFB[0x00000204/4], 0);
  1063	    sim_data.pix_bpp        = (char)pixelDepth;
  1064	    sim_data.enable_video   = 0;
  1065	    sim_data.enable_mp      = 0;
  1066	    sim_data.memory_type    = (NV_RD32(&chip->PFB[0x00000200/4], 0) & 0x01) ?
  1067		1 : 0;
  1068	    sim_data.memory_width   = (NV_RD32(&chip->PEXTDEV[0x00000000/4], 0) & 0x10) ?
  1069		128 : 64;
  1070	    sim_data.mem_latency    = (char)cfg1 & 0x0F;
  1071	    sim_data.mem_aligned    = 1;
  1072	    sim_data.mem_page_miss  = (char)(((cfg1 >> 4) &0x0F) + ((cfg1 >> 31) & 0x01));
  1073	    sim_data.gr_during_vid  = 0;
  1074	    sim_data.pclk_khz       = VClk;
  1075	    sim_data.mclk_khz       = MClk;
  1076	    sim_data.nvclk_khz      = NVClk;
  1077	    nv10CalcArbitration(&fifo_data, &sim_data);
  1078	    if (fifo_data.valid)
  1079	    {
  1080	        int  b = fifo_data.graphics_burst_size >> 4;
  1081	        *burst = 0;
  1082	        while (b >>= 1)
  1083		    (*burst)++;
  1084	        *lwm   = fifo_data.graphics_lwm >> 3;
  1085	    }
  1086	}
  1087	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH v2 14/19] video/vga: Add VGA_IS0_R
From: Jani Nikula @ 2025-12-09 10:55 UTC (permalink / raw)
  To: Ville Syrjala, intel-gfx; +Cc: intel-xe, Helge Deller, linux-fbdev, dri-devel
In-Reply-To: <20251209075549.14051-1-ville.syrjala@linux.intel.com>

On Tue, 09 Dec 2025, Ville Syrjala <ville.syrjala@linux.intel.com> wrote:
> From: Ville Syrjälä <ville.syrjala@linux.intel.com>
>
> Add a proper name for the "Input status register 0" IO address.
> Currently we have some code that does read addressed using the
> aliasing VGA_MSR_W define, making it unclear what register we're
> actually reading.
>
> v2: Remove stray '?'
>
> Cc: Helge Deller <deller@gmx.de>
> Cc: linux-fbdev@vger.kernel.org
> Cc: dri-devel@lists.freedesktop.org
> Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>

Reviewed-by: Jani Nikula <jani.nikula@intel.com>

> ---
>  include/video/vga.h | 1 +
>  1 file changed, 1 insertion(+)
>
> diff --git a/include/video/vga.h b/include/video/vga.h
> index 468764d6727a..2f13c371800b 100644
> --- a/include/video/vga.h
> +++ b/include/video/vga.h
> @@ -46,6 +46,7 @@
>  #define VGA_MIS_R   	0x3CC	/* Misc Output Read Register */
>  #define VGA_MIS_W   	0x3C2	/* Misc Output Write Register */
>  #define VGA_FTC_R	0x3CA	/* Feature Control Read Register */
> +#define VGA_IS0_R	0x3C2	/* Input Status Register 0 */
>  #define VGA_IS1_RC  	0x3DA	/* Input Status Register 1 - color emulation */
>  #define VGA_IS1_RM  	0x3BA	/* Input Status Register 1 - mono emulation */
>  #define VGA_PEL_D   	0x3C9	/* PEL Data Register */

-- 
Jani Nikula, Intel

^ permalink raw reply

* Re: [PATCH 0/3] fbdev: Guard sysfs interfaces under CONFIG_FB_DEVICE
From: Andy Shevchenko @ 2025-12-09 14:25 UTC (permalink / raw)
  To: Helge Deller
  Cc: Thomas Zimmermann, Chintan Patel, linux-fbdev, linux-staging,
	linux-omap, linux-kernel, dri-devel, andy, gregkh
In-Reply-To: <f5d50007-5b48-47cb-8133-72fca274d562@gmx.de>

On Tue, Dec 9, 2025 at 10:23 AM Helge Deller <deller@gmx.de> wrote:
> On 12/9/25 08:27, Thomas Zimmermann wrote:

...

> This whole series adds a whole lot of ifdef'ery, which I think is the
> worst approach. It makes the code less readable and leads to two code
> paths, which may trigger different build errors depending on the config.
>
> I'm sure it must be possible to do the same without adding more #ifdefs,
> e.g. by introducing a function like   dev_of_fbinfo(fbinfo)  which
> simply returns NULL for the FB_DEVICE=n case.  Then, that value can be tested
> like
>         if (dev_of_fbinfo(fbinfo))
>                 {...do-the-things...}
> For the FB_DEVICE=n case this will then be optimized out by the compiler,
> while you still have full compiler syntax checking.
>
> Thoughts?

I second you. I am also not a fan of ifdeffery when it can be avoided.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* Re: [PATCH v6 1/4] dt-bindings: backlight: Add max25014 support
From: Rob Herring @ 2025-12-09 19:07 UTC (permalink / raw)
  To: Maud Spierings
  Cc: Frank Li, Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek,
	Krzysztof Kozlowski, Conor Dooley, Helge Deller, Shawn Guo,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Liam Girdwood, Mark Brown, dri-devel, linux-leds, devicetree,
	linux-kernel, linux-fbdev, imx, linux-arm-kernel
In-Reply-To: <e428c3a9-49e2-4af5-b597-2cdfef7028f5@gocontroll.com>

On Mon, Dec 08, 2025 at 02:56:50PM +0100, Maud Spierings wrote:
> On 12/2/25 15:53, Frank Li wrote:
> > On Tue, Dec 02, 2025 at 08:46:21AM +0100, Maud Spierings wrote:
> > > On 12/1/25 17:52, Frank Li wrote:
> > > > On Mon, Dec 01, 2025 at 12:53:20PM +0100, Maud Spierings via B4 Relay wrote:
> > > > > From: Maud Spierings <maudspierings@gocontroll.com>
> > > > > 
> > > > > The Maxim MAX25014 is a 4-channel automotive grade backlight driver IC
> > > > > with integrated boost controller.
> > > > > 
> > > > > Signed-off-by: Maud Spierings <maudspierings@gocontroll.com>
> > > > > 
> > > > > ---
> > > > > 
> > > > > In the current implementation the control registers for channel 1,
> > > > > control all channels. So only one led subnode with led-sources is
> > > > > supported right now. If at some point the driver functionality is
> > > > > expanded the bindings can be easily extended with it.
> > > > > ---
> > > > >    .../bindings/leds/backlight/maxim,max25014.yaml    | 107 +++++++++++++++++++++
> > > > >    MAINTAINERS                                        |   5 +
> > > > >    2 files changed, 112 insertions(+)
> > > > > 
> > > > > diff --git a/Documentation/devicetree/bindings/leds/backlight/maxim,max25014.yaml b/Documentation/devicetree/bindings/leds/backlight/maxim,max25014.yaml
> > > > > new file mode 100644
> > > > > index 000000000000..e83723224b07
> > > > > --- /dev/null
> > > > > +++ b/Documentation/devicetree/bindings/leds/backlight/maxim,max25014.yaml
> > > > > @@ -0,0 +1,107 @@
> > > > > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > > > > +%YAML 1.2
> > > > > +---
> > > > > +$id: http://devicetree.org/schemas/leds/backlight/maxim,max25014.yaml#
> > > > > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > > > > +
> > > > > +title: Maxim max25014 backlight controller
> > > > > +
> > > > > +maintainers:
> > > > > +  - Maud Spierings <maudspierings@gocontroll.com>
> > > > > +
> > > > > +properties:
> > > > > +  compatible:
> > > > > +    enum:
> > > > > +      - maxim,max25014
> > > > > +
> > > > > +  reg:
> > > > > +    maxItems: 1
> > > > > +
> > > > > +  "#address-cells":
> > > > > +    const: 1
> > > > > +
> > > > > +  "#size-cells":
> > > > > +    const: 0
> > > > > +
> > > > > +  enable-gpios:
> > > > > +    maxItems: 1
> > > > > +
> > > > > +  interrupts:
> > > > > +    maxItems: 1
> > > > > +
> > > > > +  power-supply:
> > > > > +    description: Regulator which controls the boost converter input rail.
> > > > > +
> > > > > +  pwms:
> > > > > +    maxItems: 1
> > > > > +
> > > > > +  maxim,iset:
> > > > > +    $ref: /schemas/types.yaml#/definitions/uint32
> > > > > +    maximum: 15
> > > > > +    default: 11
> > > > > +    description:
> > > > > +      Value of the ISET field in the ISET register. This controls the current
> > > > > +      scale of the outputs, a higher number means more current.
> > > > > +
> > > > > +  led@0:
> > > > 
> > > > define whole binding, allow 0-3. binding is not related with driver's
> > > > implement.
> > > > 
> > > > it'd better put unders leds.
> > > > 
> > > 
> > > so like:
> > > 
> > > backlight: backlight@6f {
> > > 	compatible = "maxim,max25014";
> > > 	reg = <0x6f>;
> > > 	enable-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;
> > > 	pinctrl-names = "default";
> > > 	pinctrl-0 = <&pinctrl_backlight>;
> > > 	maxim,iset = <7>;
> > > 
> > > 	leds {
> > > 		#address-cells = <1>;
> > > 		#size-cells = <0>;
> > > 
> > > 		led@0 {
> > > 			reg = <0>;
> > > 			led-sources = <0 1 2>;
> > > 			default-brightness = <50>;
> > > 		};
> > > 
> > > 		optional led@#....
> > > 	};
> > > };
> > > 
> > > right?
> > 
> > yes.
> > 
> 
> I am feeling a bit weird about these led sub nodes, because it is not
> programmed as a led driver, it is programmed as a backlight. I am trying to
> figure out how this would be used later when the led strings are
> individually controllable.
> 
> it isn't possible to link the seperate strings to different displays because
> it is only one backlight device, so I don't seen any reason why it would
> ever be used in another way than what it is now, were all strings are
> programmed by one register.
> 
> The only way I can make sense of it is if instead I program this device as a
> led driver and then use the led_bl driver as the actual backlight.
> 
> Thats a pretty big step in a different direction, but then the led subnodes
> at least can be properly used I feel.

If you don't have any use for anything other than driving a single 
backlight, then I'd just drop the led nodes completely.

Rob

^ permalink raw reply

* Re: [PATCH 1/3] fbtft: Make sysfs and dev_*() logging conditional on FB_DEVICE
From: Chintan Patel @ 2025-12-10  4:24 UTC (permalink / raw)
  To: Thomas Zimmermann, linux-fbdev, linux-staging, linux-omap
  Cc: linux-kernel, dri-devel, andy, deller, gregkh
In-Reply-To: <329423e8-d778-4f30-904a-825b1be72ce2@suse.de>

Hi Thomas,

On 12/8/25 23:25, Thomas Zimmermann wrote:
> Hi
> 
> Am 09.12.25 um 05:27 schrieb Chintan Patel:
>> The fbtft core and sysfs implementation unconditionally dereference
>> fb_info->dev and register sysfs attributes. When FB_DEVICE=n, these
>> fields are unavailable, leading to build failures.
>>
>> This patch wraps all sysfs attribute creation/removal and dev_dbg/ 
>> dev_info
>> logging in #ifdef CONFIG_FB_DEVICE, with pr_*() fallbacks for the
>> non-FB_DEVICE case. This makes fbtft fully buildable when FB_DEVICE is
>> disabled.
>>
>> Signed-off-by: Chintan Patel <chintanlike@gmail.com>
>> ---
>>   drivers/staging/fbtft/fbtft-core.c  | 20 ++++++++++++++++++--
>>   drivers/staging/fbtft/fbtft-sysfs.c |  8 ++++++++
>>   2 files changed, 26 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/ 
>> fbtft/fbtft-core.c
>> index 9e7b84071174..dc967bdeabe8 100644
>> --- a/drivers/staging/fbtft/fbtft-core.c
>> +++ b/drivers/staging/fbtft/fbtft-core.c
>> @@ -365,9 +365,14 @@ static int fbtft_fb_setcolreg(unsigned int regno, 
>> unsigned int red,
>>       unsigned int val;
>>       int ret = 1;
>> +#ifdef CONFIG_FB_DEVICE
>>       dev_dbg(info->dev,
> 
> Rather use fb_dbg() [1] and similar helpers for logging. They only need 
> the info pointer and do the correct output by themselves.
> 
> [1] https://elixir.bootlin.com/linux/v6.18/source/include/linux/fb.h#L895

Thank you for this pointer - I actually didnt now this existed. Will do v2.

^ permalink raw reply

* Re: [PATCH 0/3] fbdev: Guard sysfs interfaces under CONFIG_FB_DEVICE
From: Chintan Patel @ 2025-12-10  4:26 UTC (permalink / raw)
  To: Andy Shevchenko, Helge Deller
  Cc: Thomas Zimmermann, linux-fbdev, linux-staging, linux-omap,
	linux-kernel, dri-devel, andy, gregkh
In-Reply-To: <CAHp75Vds8GP+daMe9WcEbOaNT91kMHUjidzGUN-1_kVDuWBtLw@mail.gmail.com>



On 12/9/25 06:25, Andy Shevchenko wrote:
> On Tue, Dec 9, 2025 at 10:23 AM Helge Deller <deller@gmx.de> wrote:
>> On 12/9/25 08:27, Thomas Zimmermann wrote:
> 
> ...
> 
>> This whole series adds a whole lot of ifdef'ery, which I think is the
>> worst approach. It makes the code less readable and leads to two code
>> paths, which may trigger different build errors depending on the config.
>>
>> I'm sure it must be possible to do the same without adding more #ifdefs,
>> e.g. by introducing a function like   dev_of_fbinfo(fbinfo)  which
>> simply returns NULL for the FB_DEVICE=n case.  Then, that value can be tested
>> like
>>          if (dev_of_fbinfo(fbinfo))
>>                  {...do-the-things...}
>> For the FB_DEVICE=n case this will then be optimized out by the compiler,
>> while you still have full compiler syntax checking.
>>
>> Thoughts?
> 
> I second you. I am also not a fan of ifdeffery when it can be avoided.
> 

Thank you for the review! Will do the change.

^ permalink raw reply

* Re: [PATCH 14/19] video/vga: Add VGA_IS0_R
From: kernel test robot @ 2025-12-10 14:13 UTC (permalink / raw)
  To: Ville Syrjala, intel-gfx
  Cc: oe-kbuild-all, intel-xe, Helge Deller, linux-fbdev, dri-devel
In-Reply-To: <20251208182637.334-15-ville.syrjala@linux.intel.com>

Hi Ville,

kernel test robot noticed the following build errors:

[auto build test ERROR on drm-tip/drm-tip]
[cannot apply to drm-i915/for-linux-next drm-i915/for-linux-next-fixes drm-xe/drm-xe-next linus/master v6.18 next-20251210]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Ville-Syrjala/drm-i915-vga-Register-vgaarb-client-later/20251209-195929
base:   https://gitlab.freedesktop.org/drm/tip.git drm-tip
patch link:    https://lore.kernel.org/r/20251208182637.334-15-ville.syrjala%40linux.intel.com
patch subject: [PATCH 14/19] video/vga: Add VGA_IS0_R
config: parisc-randconfig-002-20251210 (https://download.01.org/0day-ci/archive/20251210/202512102159.xsvvXCXy-lkp@intel.com/config)
compiler: hppa-linux-gcc (GCC) 8.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251210/202512102159.xsvvXCXy-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512102159.xsvvXCXy-lkp@intel.com/

All errors (new ones prefixed by >>):

   In file included from include/linux/svga.h:6,
                    from drivers/video/fbdev/arkfb.c:24:
   include/video/vga.h:489:1: error: expected identifier or '(' before '?' token
    ?
    ^
   In file included from drivers/video/fbdev/arkfb.c:28:
   include/video/vga.h:489:1: error: expected identifier or '(' before '?' token
    ?
    ^
   drivers/video/fbdev/arkfb.c:68:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_h_total_regs[]        = {{0x00, 0, 7}, {0x41, 7, 7}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:69:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_h_display_regs[]      = {{0x01, 0, 7}, {0x41, 6, 6}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:70:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_h_blank_start_regs[]  = {{0x02, 0, 7}, {0x41, 5, 5}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:71:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_h_blank_end_regs[]    = {{0x03, 0, 4}, {0x05, 7, 7 }, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:72:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_h_sync_start_regs[]   = {{0x04, 0, 7}, {0x41, 4, 4}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:73:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_h_sync_end_regs[]     = {{0x05, 0, 4}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:75:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_v_total_regs[]        = {{0x06, 0, 7}, {0x07, 0, 0}, {0x07, 5, 5}, {0x40, 7, 7}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:76:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_v_display_regs[]      = {{0x12, 0, 7}, {0x07, 1, 1}, {0x07, 6, 6}, {0x40, 6, 6}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:77:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_v_blank_start_regs[]  = {{0x15, 0, 7}, {0x07, 3, 3}, {0x09, 5, 5}, {0x40, 5, 5}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:79:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_v_blank_end_regs[]    = {{0x16, 0, 7}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:80:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_v_sync_start_regs[]   = {{0x10, 0, 7}, {0x07, 2, 2}, {0x07, 7, 7}, {0x40, 4, 4}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:81:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_v_sync_end_regs[]     = {{0x11, 0, 3}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:83:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_line_compare_regs[]   = {{0x18, 0, 7}, {0x07, 4, 4}, {0x09, 6, 6}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:84:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_start_address_regs[]  = {{0x0d, 0, 7}, {0x0c, 0, 7}, {0x40, 0, 2}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:85:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset ark_offset_regs[]         = {{0x13, 0, 7}, {0x41, 3, 3}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c: In function 'arkfb_tilecursor':
>> drivers/video/fbdev/arkfb.c:152:21: error: dereferencing pointer to incomplete type 'struct arkfb_info'
     svga_tilecursor(par->state.vgabase, info, cursor);
                        ^~
   drivers/video/fbdev/arkfb.c: At top level:
   drivers/video/fbdev/arkfb.c:303:29: warning: 'struct dac_info' declared inside parameter list will not be visible outside of this definition or declaration
     int (*dac_get_mode)(struct dac_info *info);
                                ^~~~~~~~
   drivers/video/fbdev/arkfb.c:304:29: warning: 'struct dac_info' declared inside parameter list will not be visible outside of this definition or declaration
     int (*dac_set_mode)(struct dac_info *info, int mode);
                                ^~~~~~~~
   drivers/video/fbdev/arkfb.c:305:29: warning: 'struct dac_info' declared inside parameter list will not be visible outside of this definition or declaration
     int (*dac_get_freq)(struct dac_info *info, int channel);
                                ^~~~~~~~
   drivers/video/fbdev/arkfb.c:306:29: warning: 'struct dac_info' declared inside parameter list will not be visible outside of this definition or declaration
     int (*dac_set_freq)(struct dac_info *info, int channel, u32 freq);
                                ^~~~~~~~
   drivers/video/fbdev/arkfb.c:307:29: warning: 'struct dac_info' declared inside parameter list will not be visible outside of this definition or declaration
     void (*dac_release)(struct dac_info *info);
                                ^~~~~~~~
   drivers/video/fbdev/arkfb.c: In function 'dac_set_mode':
   drivers/video/fbdev/arkfb.c:339:36: error: passing argument 1 of 'info->dacops->dac_set_mode' from incompatible pointer type [-Werror=incompatible-pointer-types]
     return info->dacops->dac_set_mode(info, mode);
                                       ^~~~
   drivers/video/fbdev/arkfb.c:339:36: note: expected 'struct dac_info *' but argument is of type 'struct dac_info *'
   drivers/video/fbdev/arkfb.c: In function 'dac_set_freq':
   drivers/video/fbdev/arkfb.c:344:36: error: passing argument 1 of 'info->dacops->dac_set_freq' from incompatible pointer type [-Werror=incompatible-pointer-types]
     return info->dacops->dac_set_freq(info, channel, freq);
                                       ^~~~
   drivers/video/fbdev/arkfb.c:344:36: note: expected 'struct dac_info *' but argument is of type 'struct dac_info *'
   drivers/video/fbdev/arkfb.c: In function 'dac_release':
   drivers/video/fbdev/arkfb.c:349:28: error: passing argument 1 of 'info->dacops->dac_release' from incompatible pointer type [-Werror=incompatible-pointer-types]
     info->dacops->dac_release(info);
                               ^~~~
   drivers/video/fbdev/arkfb.c:349:28: note: expected 'struct dac_info *' but argument is of type 'struct dac_info *'
   drivers/video/fbdev/arkfb.c: At top level:
   drivers/video/fbdev/arkfb.c:426:18: error: initialization of 'int (*)(struct dac_info *, int)' from incompatible pointer type 'int (*)(struct dac_info *, int)' [-Werror=incompatible-pointer-types]
     .dac_set_mode = ics5342_set_mode,
                     ^~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:426:18: note: (near initialization for 'ics5342_ops.dac_set_mode')
   drivers/video/fbdev/arkfb.c:427:18: error: initialization of 'int (*)(struct dac_info *, int,  u32)' {aka 'int (*)(struct dac_info *, int,  unsigned int)'} from incompatible pointer type 'int (*)(struct dac_info *, int,  u32)' {aka 'int (*)(struct dac_info *, int,  unsigned int)'} [-Werror=incompatible-pointer-types]
     .dac_set_freq = ics5342_set_freq,
                     ^~~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:427:18: note: (near initialization for 'ics5342_ops.dac_set_freq')
   drivers/video/fbdev/arkfb.c:428:17: error: initialization of 'void (*)(struct dac_info *)' from incompatible pointer type 'void (*)(struct dac_info *)' [-Werror=incompatible-pointer-types]
     .dac_release = ics5342_release
                    ^~~~~~~~~~~~~~~
   drivers/video/fbdev/arkfb.c:428:17: note: (near initialization for 'ics5342_ops.dac_release')
   drivers/video/fbdev/arkfb.c: In function 'ark_dac_read_regs':
   drivers/video/fbdev/arkfb.c:461:23: error: dereferencing pointer to incomplete type 'struct arkfb_info'
     regval = vga_rseq(par->state.vgabase, 0x1C);
                          ^~
   drivers/video/fbdev/arkfb.c: In function 'ark_dac_write_regs':
   drivers/video/fbdev/arkfb.c:480:23: error: dereferencing pointer to incomplete type 'struct arkfb_info'
     regval = vga_rseq(par->state.vgabase, 0x1C);
                          ^~
   drivers/video/fbdev/arkfb.c: In function 'ark_set_pixclock':
   drivers/video/fbdev/arkfb.c:498:27: error: dereferencing pointer to incomplete type 'struct arkfb_info'
     int rv = dac_set_freq(par->dac, 0, 1000000000 / pixclock);
                              ^~
   In file included from include/linux/seqlock.h:19,
                    from include/linux/mmzone.h:17,
                    from include/linux/gfp.h:7,
                    from include/linux/umh.h:4,
                    from include/linux/kmod.h:9,
                    from include/linux/module.h:18,
                    from drivers/video/fbdev/arkfb.c:15:
   drivers/video/fbdev/arkfb.c: In function 'arkfb_open':
   drivers/video/fbdev/arkfb.c:516:18: error: dereferencing pointer to incomplete type 'struct arkfb_info'
     mutex_lock(&(par->open_lock));
                     ^~
   include/linux/mutex.h:168:44: note: in definition of macro 'mutex_lock'
    #define mutex_lock(lock) mutex_lock_nested(lock, 0)
                                               ^~~~
   drivers/video/fbdev/arkfb.c: In function 'arkfb_release':
   drivers/video/fbdev/arkfb.c:540:18: error: dereferencing pointer to incomplete type 'struct arkfb_info'
     mutex_lock(&(par->open_lock));
                     ^~
   include/linux/mutex.h:168:44: note: in definition of macro 'mutex_lock'
    #define mutex_lock(lock) mutex_lock_nested(lock, 0)
                                               ^~~~
   drivers/video/fbdev/arkfb.c: In function 'arkfb_set_par':
   drivers/video/fbdev/arkfb.c:658:20: error: dereferencing pointer to incomplete type 'struct arkfb_info'
     svga_wcrt_mask(par->state.vgabase, 0x11, 0x00, 0x80);
                       ^~
   drivers/video/fbdev/arkfb.c: In function 'arkfb_blank':
   drivers/video/fbdev/arkfb.c:881:21: error: dereferencing pointer to incomplete type 'struct arkfb_info'
      svga_wseq_mask(par->state.vgabase, 0x01, 0x00, 0x20);
                        ^~
   drivers/video/fbdev/arkfb.c: In function 'arkfb_pan_display':
   drivers/video/fbdev/arkfb.c:920:21: error: dereferencing pointer to incomplete type 'struct arkfb_info'
     svga_wcrt_multi(par->state.vgabase, ark_start_address_regs, offset);
                        ^~
   drivers/video/fbdev/arkfb.c: In function 'ark_pci_probe':
   drivers/video/fbdev/arkfb.c:973:34: error: invalid application of 'sizeof' to incomplete type 'struct arkfb_info'
     info = framebuffer_alloc(sizeof(struct arkfb_info), &(dev->dev));
                                     ^~~~~~
   In file included from include/linux/seqlock.h:19,
                    from include/linux/mmzone.h:17,
                    from include/linux/gfp.h:7,
                    from include/linux/umh.h:4,
                    from include/linux/kmod.h:9,
--
   In file included from include/linux/svga.h:6,
                    from drivers/video/fbdev/vt8623fb.c:24:
   include/video/vga.h:489:1: error: expected identifier or '(' before '?' token
    ?
    ^
   In file included from drivers/video/fbdev/vt8623fb.c:28:
   include/video/vga.h:489:1: error: expected identifier or '(' before '?' token
    ?
    ^
   drivers/video/fbdev/vt8623fb.c:66:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_h_total_regs[]       = {{0x00, 0, 7}, {0x36, 3, 3}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:67:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_h_display_regs[]     = {{0x01, 0, 7}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:68:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_h_blank_start_regs[] = {{0x02, 0, 7}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:69:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_h_blank_end_regs[]   = {{0x03, 0, 4}, {0x05, 7, 7}, {0x33, 5, 5}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:70:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_h_sync_start_regs[]  = {{0x04, 0, 7}, {0x33, 4, 4}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:71:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_h_sync_end_regs[]    = {{0x05, 0, 4}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:73:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_v_total_regs[]       = {{0x06, 0, 7}, {0x07, 0, 0}, {0x07, 5, 5}, {0x35, 0, 0}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:74:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_v_display_regs[]     = {{0x12, 0, 7}, {0x07, 1, 1}, {0x07, 6, 6}, {0x35, 2, 2}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:75:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_v_blank_start_regs[] = {{0x15, 0, 7}, {0x07, 3, 3}, {0x09, 5, 5}, {0x35, 3, 3}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:76:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_v_blank_end_regs[]   = {{0x16, 0, 7}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:77:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_v_sync_start_regs[]  = {{0x10, 0, 7}, {0x07, 2, 2}, {0x07, 7, 7}, {0x35, 1, 1}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:78:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_v_sync_end_regs[]    = {{0x11, 0, 3}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:80:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_offset_regs[]        = {{0x13, 0, 7}, {0x35, 5, 7}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:81:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_line_compare_regs[]  = {{0x18, 0, 7}, {0x07, 4, 4}, {0x09, 6, 6}, {0x33, 0, 2}, {0x35, 4, 4}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:82:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_fetch_count_regs[]   = {{0x1C, 0, 7}, {0x1D, 0, 1}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:83:32: error: array type has incomplete element type 'struct vga_regset'
    static const struct vga_regset vt8623_start_address_regs[] = {{0x0d, 0, 7}, {0x0c, 0, 7}, {0x34, 0, 7}, {0x48, 0, 1}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623fb_tilecursor':
>> drivers/video/fbdev/vt8623fb.c:119:21: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
     svga_tilecursor(par->state.vgabase, info, cursor);
                        ^~
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623_set_pixclock':
   drivers/video/fbdev/vt8623fb.c:265:20: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
     regval = vga_r(par->state.vgabase, VGA_MIS_R);
                       ^~
   In file included from include/linux/seqlock.h:19,
                    from include/linux/mmzone.h:17,
                    from include/linux/gfp.h:7,
                    from include/linux/umh.h:4,
                    from include/linux/kmod.h:9,
                    from include/linux/module.h:18,
                    from drivers/video/fbdev/vt8623fb.c:16:
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623fb_open':
   drivers/video/fbdev/vt8623fb.c:284:18: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
     mutex_lock(&(par->open_lock));
                     ^~
   include/linux/mutex.h:168:44: note: in definition of macro 'mutex_lock'
    #define mutex_lock(lock) mutex_lock_nested(lock, 0)
                                               ^~~~
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623fb_release':
   drivers/video/fbdev/vt8623fb.c:306:18: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
     mutex_lock(&(par->open_lock));
                     ^~
   include/linux/mutex.h:168:44: note: in definition of macro 'mutex_lock'
    #define mutex_lock(lock) mutex_lock_nested(lock, 0)
                                               ^~~~
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623fb_set_par':
   drivers/video/fbdev/vt8623fb.c:431:20: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
     svga_wseq_mask(par->state.vgabase, 0x10, 0x01, 0x01);
                       ^~
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623fb_blank':
   drivers/video/fbdev/vt8623fb.c:593:21: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
      svga_wcrt_mask(par->state.vgabase, 0x36, 0x00, 0x30);
                        ^~
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623fb_pan_display':
   drivers/video/fbdev/vt8623fb.c:639:21: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
     svga_wcrt_multi(par->state.vgabase, vt8623_start_address_regs, offset);
                        ^~
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623_pci_probe':
   drivers/video/fbdev/vt8623fb.c:690:34: error: invalid application of 'sizeof' to incomplete type 'struct vt8623fb_info'
     info = framebuffer_alloc(sizeof(struct vt8623fb_info), &(dev->dev));
                                     ^~~~~~
   In file included from include/linux/seqlock.h:19,
                    from include/linux/mmzone.h:17,
                    from include/linux/gfp.h:7,
                    from include/linux/umh.h:4,
                    from include/linux/kmod.h:9,
                    from include/linux/module.h:18,
                    from drivers/video/fbdev/vt8623fb.c:16:
   drivers/video/fbdev/vt8623fb.c:695:17: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
     mutex_init(&par->open_lock);
                    ^~
   include/linux/mutex.h:64:16: note: in definition of macro 'mutex_init'
     __mutex_init((mutex), #mutex, &__key);    \
                   ^~~~~
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623_pci_remove':
   drivers/video/fbdev/vt8623fb.c:823:23: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
      arch_phys_wc_del(par->wc_cookie);
                          ^~
   In file included from include/linux/seqlock.h:19,
                    from include/linux/mmzone.h:17,
                    from include/linux/gfp.h:7,
                    from include/linux/umh.h:4,
                    from include/linux/kmod.h:9,
                    from include/linux/module.h:18,
                    from drivers/video/fbdev/vt8623fb.c:16:
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623_pci_suspend':
   drivers/video/fbdev/vt8623fb.c:847:18: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
     mutex_lock(&(par->open_lock));
                     ^~
   include/linux/mutex.h:168:44: note: in definition of macro 'mutex_lock'
    #define mutex_lock(lock) mutex_lock_nested(lock, 0)
                                               ^~~~
   drivers/video/fbdev/vt8623fb.c: In function 'vt8623_pci_resume':
   drivers/video/fbdev/vt8623fb.c:874:18: error: dereferencing pointer to incomplete type 'struct vt8623fb_info'
     mutex_lock(&(par->open_lock));
                     ^~
   include/linux/mutex.h:168:44: note: in definition of macro 'mutex_lock'
    #define mutex_lock(lock) mutex_lock_nested(lock, 0)
                                               ^~~~
   At top level:
   drivers/video/fbdev/vt8623fb.c:83:32: warning: 'vt8623_start_address_regs' defined but not used [-Wunused-variable]
    static const struct vga_regset vt8623_start_address_regs[] = {{0x0d, 0, 7}, {0x0c, 0, 7}, {0x34, 0, 7}, {0x48, 0, 1}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:82:32: warning: 'vt8623_fetch_count_regs' defined but not used [-Wunused-variable]
    static const struct vga_regset vt8623_fetch_count_regs[]   = {{0x1C, 0, 7}, {0x1D, 0, 1}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:81:32: warning: 'vt8623_line_compare_regs' defined but not used [-Wunused-variable]
    static const struct vga_regset vt8623_line_compare_regs[]  = {{0x18, 0, 7}, {0x07, 4, 4}, {0x09, 6, 6}, {0x33, 0, 2}, {0x35, 4, 4}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:80:32: warning: 'vt8623_offset_regs' defined but not used [-Wunused-variable]
    static const struct vga_regset vt8623_offset_regs[]        = {{0x13, 0, 7}, {0x35, 5, 7}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:78:32: warning: 'vt8623_v_sync_end_regs' defined but not used [-Wunused-variable]
    static const struct vga_regset vt8623_v_sync_end_regs[]    = {{0x11, 0, 3}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~
   drivers/video/fbdev/vt8623fb.c:77:32: warning: 'vt8623_v_sync_start_regs' defined but not used [-Wunused-variable]
    static const struct vga_regset vt8623_v_sync_start_regs[]  = {{0x10, 0, 7}, {0x07, 2, 2}, {0x07, 7, 7}, {0x35, 1, 1}, VGA_REGSET_END};
                                   ^~~~~~~~~~~~~~~~~~~~~~~~
..


vim +152 drivers/video/fbdev/arkfb.c

681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   82  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   83  static const struct vga_regset ark_line_compare_regs[]   = {{0x18, 0, 7}, {0x07, 4, 4}, {0x09, 6, 6}, VGA_REGSET_END};
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  @84  static const struct vga_regset ark_start_address_regs[]  = {{0x0d, 0, 7}, {0x0c, 0, 7}, {0x40, 0, 2}, VGA_REGSET_END};
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   85  static const struct vga_regset ark_offset_regs[]         = {{0x13, 0, 7}, {0x41, 3, 3}, VGA_REGSET_END};
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   86  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   87  static const struct svga_timing_regs ark_timing_regs     = {
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   88  	ark_h_total_regs, ark_h_display_regs, ark_h_blank_start_regs,
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   89  	ark_h_blank_end_regs, ark_h_sync_start_regs, ark_h_sync_end_regs,
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   90  	ark_v_total_regs, ark_v_display_regs, ark_v_blank_start_regs,
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   91  	ark_v_blank_end_regs, ark_v_sync_start_regs, ark_v_sync_end_regs,
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   92  };
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   93  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   94  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   95  /* ------------------------------------------------------------------------- */
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   96  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   97  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   98  /* Module parameters */
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09   99  
48c68c4f1b5424 drivers/video/arkfb.c Greg Kroah-Hartman 2012-12-21  100  static char *mode_option = "640x480-8@60";
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  101  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  102  MODULE_AUTHOR("(c) 2007 Ondrej Zajicek <santiago@crfreenet.org>");
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  103  MODULE_LICENSE("GPL");
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  104  MODULE_DESCRIPTION("fbdev driver for ARK 2000PV");
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  105  
1abf91729faf2f drivers/video/arkfb.c Krzysztof Helt     2008-04-28  106  module_param(mode_option, charp, 0444);
1abf91729faf2f drivers/video/arkfb.c Krzysztof Helt     2008-04-28  107  MODULE_PARM_DESC(mode_option, "Default video mode ('640x480-8@60', etc)");
1abf91729faf2f drivers/video/arkfb.c Krzysztof Helt     2008-04-28  108  module_param_named(mode, mode_option, charp, 0444);
1abf91729faf2f drivers/video/arkfb.c Krzysztof Helt     2008-04-28  109  MODULE_PARM_DESC(mode, "Default video mode ('640x480-8@60', etc) (deprecated)");
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  110  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  111  static int threshold = 4;
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  112  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  113  module_param(threshold, int, 0644);
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  114  MODULE_PARM_DESC(threshold, "FIFO threshold");
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  115  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  116  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  117  /* ------------------------------------------------------------------------- */
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  118  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  119  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  120  static void arkfb_settile(struct fb_info *info, struct fb_tilemap *map)
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  121  {
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  122  	const u8 *font = map->data;
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  123  	u8 __iomem *fb = (u8 __iomem *)info->screen_base;
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  124  	int i, c;
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  125  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  126  	if ((map->width != 8) || (map->height != 16) ||
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  127  	    (map->depth != 1) || (map->length != 256)) {
31b6780c15a4e3 drivers/video/arkfb.c Joe Perches        2013-09-19  128  		fb_err(info, "unsupported font parameters: width %d, height %d, depth %d, length %d\n",
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  129  		       map->width, map->height, map->depth, map->length);
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  130  		return;
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  131  	}
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  132  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  133  	fb += 2;
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  134  	for (c = 0; c < map->length; c++) {
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  135  		for (i = 0; i < map->height; i++) {
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  136  			fb_writeb(font[i], &fb[i * 4]);
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  137  			fb_writeb(font[i], &fb[i * 4 + (128 * 8)]);
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  138  		}
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  139  		fb += 128;
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  140  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  141  		if ((c % 8) == 7)
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  142  			fb += 128*8;
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  143  
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  144  		font += map->height;
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  145  	}
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  146  }
681e14730c73cc drivers/video/arkfb.c Ondrej Zajicek     2007-05-09  147  
55db0923884554 drivers/video/arkfb.c David Miller       2011-01-11  148  static void arkfb_tilecursor(struct fb_info *info, struct fb_tilecursor *cursor)
55db0923884554 drivers/video/arkfb.c David Miller       2011-01-11  149  {
55db0923884554 drivers/video/arkfb.c David Miller       2011-01-11  150  	struct arkfb_info *par = info->par;
55db0923884554 drivers/video/arkfb.c David Miller       2011-01-11  151  
55db0923884554 drivers/video/arkfb.c David Miller       2011-01-11 @152  	svga_tilecursor(par->state.vgabase, info, cursor);
55db0923884554 drivers/video/arkfb.c David Miller       2011-01-11  153  }
55db0923884554 drivers/video/arkfb.c David Miller       2011-01-11  154  

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH 14/19] video/vga: Add VGA_IS0_R
From: kernel test robot @ 2025-12-10 14:24 UTC (permalink / raw)
  To: Ville Syrjala, intel-gfx
  Cc: oe-kbuild-all, intel-xe, Helge Deller, linux-fbdev, dri-devel
In-Reply-To: <20251208182637.334-15-ville.syrjala@linux.intel.com>

Hi Ville,

kernel test robot noticed the following build errors:

[auto build test ERROR on drm-tip/drm-tip]
[cannot apply to drm-i915/for-linux-next drm-i915/for-linux-next-fixes drm-xe/drm-xe-next linus/master v6.18 next-20251210]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Ville-Syrjala/drm-i915-vga-Register-vgaarb-client-later/20251209-195929
base:   https://gitlab.freedesktop.org/drm/tip.git drm-tip
patch link:    https://lore.kernel.org/r/20251208182637.334-15-ville.syrjala%40linux.intel.com
patch subject: [PATCH 14/19] video/vga: Add VGA_IS0_R
config: s390-randconfig-002-20251210 (https://download.01.org/0day-ci/archive/20251210/202512102200.KIAC3RLu-lkp@intel.com/config)
compiler: s390-linux-gcc (GCC) 9.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251210/202512102200.KIAC3RLu-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512102200.KIAC3RLu-lkp@intel.com/

All errors (new ones prefixed by >>):

   In file included from drivers/video/vgastate.c:20:
   include/video/vga.h:489:1: error: expected identifier or '(' before '?' token
     489 | ?
         | ^
   drivers/video/vgastate.c: In function 'save_vga_text':
>> drivers/video/vgastate.c:89:9: error: dereferencing pointer to incomplete type 'struct regstate'
      89 |    saved->vga_font0[i] = vga_r(fbbase, i);
         |         ^~
   drivers/video/vgastate.c: In function 'restore_vga_text':
   drivers/video/vgastate.c:175:26: error: dereferencing pointer to incomplete type 'struct regstate'
     175 |    vga_w(fbbase, i, saved->vga_font0[i]);
         |                          ^~
   drivers/video/vgastate.c: In function 'save_vga_mode':
   drivers/video/vgastate.c:232:7: error: dereferencing pointer to incomplete type 'struct regstate'
     232 |  saved->misc = vga_r(state->vgabase, VGA_MIS_R);
         |       ^~
   drivers/video/vgastate.c: In function 'restore_vga_mode':
   drivers/video/vgastate.c:263:40: error: dereferencing pointer to incomplete type 'struct regstate'
     263 |  vga_w(state->vgabase, VGA_MIS_W, saved->misc);
         |                                        ^~
   drivers/video/vgastate.c: In function 'save_vga_cmap':
   drivers/video/vgastate.c:319:8: error: dereferencing pointer to incomplete type 'struct regstate'
     319 |   saved->vga_cmap[i] = vga_r(state->vgabase, VGA_PEL_D);
         |        ^~
   drivers/video/vgastate.c: In function 'restore_vga_cmap':
   drivers/video/vgastate.c:332:41: error: dereferencing pointer to incomplete type 'struct regstate'
     332 |   vga_w(state->vgabase, VGA_PEL_D, saved->vga_cmap[i]);
         |                                         ^~
   drivers/video/vgastate.c: In function 'vga_cleanup':
   drivers/video/vgastate.c:340:14: error: dereferencing pointer to incomplete type 'struct regstate'
     340 |   vfree(saved->vga_font0);
         |              ^~
   In file included from include/linux/workqueue.h:9,
                    from include/linux/mm_types.h:19,
                    from include/linux/mmzone.h:22,
                    from include/linux/gfp.h:7,
                    from include/linux/umh.h:4,
                    from include/linux/kmod.h:9,
                    from include/linux/module.h:18,
                    from drivers/video/vgastate.c:16:
   drivers/video/vgastate.c: In function 'save_vga':
   drivers/video/vgastate.c:354:25: error: invalid application of 'sizeof' to incomplete type 'struct regstate'
     354 |  saved = kzalloc(sizeof(struct regstate), GFP_KERNEL);
         |                         ^~~~~~
   include/linux/alloc_tag.h:251:9: note: in definition of macro 'alloc_hooks_tag'
     251 |  typeof(_do_alloc) _res;      \
         |         ^~~~~~~~~
   include/linux/slab.h:1096:25: note: in expansion of macro 'alloc_hooks'
    1096 | #define kzalloc(...)    alloc_hooks(kzalloc_noprof(__VA_ARGS__))
         |                         ^~~~~~~~~~~
   drivers/video/vgastate.c:354:10: note: in expansion of macro 'kzalloc'
     354 |  saved = kzalloc(sizeof(struct regstate), GFP_KERNEL);
         |          ^~~~~~~
   drivers/video/vgastate.c:354:25: error: invalid application of 'sizeof' to incomplete type 'struct regstate'
     354 |  saved = kzalloc(sizeof(struct regstate), GFP_KERNEL);
         |                         ^~~~~~
   include/linux/alloc_tag.h:255:10: note: in definition of macro 'alloc_hooks_tag'
     255 |   _res = _do_alloc;     \
         |          ^~~~~~~~~
   include/linux/slab.h:1096:25: note: in expansion of macro 'alloc_hooks'
    1096 | #define kzalloc(...)    alloc_hooks(kzalloc_noprof(__VA_ARGS__))
         |                         ^~~~~~~~~~~
   drivers/video/vgastate.c:354:10: note: in expansion of macro 'kzalloc'
     354 |  saved = kzalloc(sizeof(struct regstate), GFP_KERNEL);
         |          ^~~~~~~
   drivers/video/vgastate.c:354:25: error: invalid application of 'sizeof' to incomplete type 'struct regstate'
     354 |  saved = kzalloc(sizeof(struct regstate), GFP_KERNEL);
         |                         ^~~~~~
   include/linux/alloc_tag.h:258:10: note: in definition of macro 'alloc_hooks_tag'
     258 |   _res = _do_alloc;     \
         |          ^~~~~~~~~
   include/linux/slab.h:1096:25: note: in expansion of macro 'alloc_hooks'
    1096 | #define kzalloc(...)    alloc_hooks(kzalloc_noprof(__VA_ARGS__))
         |                         ^~~~~~~~~~~
   drivers/video/vgastate.c:354:10: note: in expansion of macro 'kzalloc'
     354 |  saved = kzalloc(sizeof(struct regstate), GFP_KERNEL);
         |          ^~~~~~~
   drivers/video/vgastate.c:354:8: warning: assignment to 'struct regstate *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
     354 |  saved = kzalloc(sizeof(struct regstate), GFP_KERNEL);
         |        ^
   drivers/video/vgastate.c:362:8: error: dereferencing pointer to incomplete type 'struct regstate'
     362 |   saved->vga_cmap = vmalloc(768);
         |        ^~


vim +89 drivers/video/vgastate.c

^1da177e4c3f41 Linus Torvalds 2005-04-16   47  
^1da177e4c3f41 Linus Torvalds 2005-04-16   48  static void save_vga_text(struct vgastate *state, void __iomem *fbbase)
^1da177e4c3f41 Linus Torvalds 2005-04-16   49  {
^1da177e4c3f41 Linus Torvalds 2005-04-16   50  	struct regstate *saved = (struct regstate *) state->vidstate;
^1da177e4c3f41 Linus Torvalds 2005-04-16   51  	int i;
^1da177e4c3f41 Linus Torvalds 2005-04-16   52  	u8 misc, attr10, gr4, gr5, gr6, seq1, seq2, seq4;
0449359f053829 Ondrej Zajicek 2007-05-08   53  	unsigned short iobase;
^1da177e4c3f41 Linus Torvalds 2005-04-16   54  
^1da177e4c3f41 Linus Torvalds 2005-04-16   55  	/* if in graphics mode, no need to save */
0449359f053829 Ondrej Zajicek 2007-05-08   56  	misc = vga_r(state->vgabase, VGA_MIS_R);
0449359f053829 Ondrej Zajicek 2007-05-08   57  	iobase = (misc & 1) ? 0x3d0 : 0x3b0;
0449359f053829 Ondrej Zajicek 2007-05-08   58  
0449359f053829 Ondrej Zajicek 2007-05-08   59  	vga_r(state->vgabase, iobase + 0xa);
0449359f053829 Ondrej Zajicek 2007-05-08   60  	vga_w(state->vgabase, VGA_ATT_W, 0x00);
^1da177e4c3f41 Linus Torvalds 2005-04-16   61  	attr10 = vga_rattr(state->vgabase, 0x10);
0449359f053829 Ondrej Zajicek 2007-05-08   62  	vga_r(state->vgabase, iobase + 0xa);
0449359f053829 Ondrej Zajicek 2007-05-08   63  	vga_w(state->vgabase, VGA_ATT_W, 0x20);
0449359f053829 Ondrej Zajicek 2007-05-08   64  
^1da177e4c3f41 Linus Torvalds 2005-04-16   65  	if (attr10 & 1)
^1da177e4c3f41 Linus Torvalds 2005-04-16   66  		return;
^1da177e4c3f41 Linus Torvalds 2005-04-16   67  
^1da177e4c3f41 Linus Torvalds 2005-04-16   68  	/* save regs */
^1da177e4c3f41 Linus Torvalds 2005-04-16   69  	gr4 = vga_rgfx(state->vgabase, VGA_GFX_PLANE_READ);
^1da177e4c3f41 Linus Torvalds 2005-04-16   70  	gr5 = vga_rgfx(state->vgabase, VGA_GFX_MODE);
^1da177e4c3f41 Linus Torvalds 2005-04-16   71  	gr6 = vga_rgfx(state->vgabase, VGA_GFX_MISC);
^1da177e4c3f41 Linus Torvalds 2005-04-16   72  	seq2 = vga_rseq(state->vgabase, VGA_SEQ_PLANE_WRITE);
^1da177e4c3f41 Linus Torvalds 2005-04-16   73  	seq4 = vga_rseq(state->vgabase, VGA_SEQ_MEMORY_MODE);
^1da177e4c3f41 Linus Torvalds 2005-04-16   74  
^1da177e4c3f41 Linus Torvalds 2005-04-16   75  	/* blank screen */
^1da177e4c3f41 Linus Torvalds 2005-04-16   76  	seq1 = vga_rseq(state->vgabase, VGA_SEQ_CLOCK_MODE);
^1da177e4c3f41 Linus Torvalds 2005-04-16   77  	vga_wseq(state->vgabase, VGA_SEQ_RESET, 0x1);
^1da177e4c3f41 Linus Torvalds 2005-04-16   78  	vga_wseq(state->vgabase, VGA_SEQ_CLOCK_MODE, seq1 | 1 << 5);
^1da177e4c3f41 Linus Torvalds 2005-04-16   79  	vga_wseq(state->vgabase, VGA_SEQ_RESET, 0x3);
^1da177e4c3f41 Linus Torvalds 2005-04-16   80  
^1da177e4c3f41 Linus Torvalds 2005-04-16   81  	/* save font at plane 2 */
^1da177e4c3f41 Linus Torvalds 2005-04-16   82  	if (state->flags & VGA_SAVE_FONT0) {
^1da177e4c3f41 Linus Torvalds 2005-04-16   83  		vga_wseq(state->vgabase, VGA_SEQ_PLANE_WRITE, 0x4);
^1da177e4c3f41 Linus Torvalds 2005-04-16   84  		vga_wseq(state->vgabase, VGA_SEQ_MEMORY_MODE, 0x6);
^1da177e4c3f41 Linus Torvalds 2005-04-16   85  		vga_wgfx(state->vgabase, VGA_GFX_PLANE_READ, 0x2);
^1da177e4c3f41 Linus Torvalds 2005-04-16   86  		vga_wgfx(state->vgabase, VGA_GFX_MODE, 0x0);
^1da177e4c3f41 Linus Torvalds 2005-04-16   87  		vga_wgfx(state->vgabase, VGA_GFX_MISC, 0x5);
^1da177e4c3f41 Linus Torvalds 2005-04-16   88  		for (i = 0; i < 4 * 8192; i++)
^1da177e4c3f41 Linus Torvalds 2005-04-16  @89  			saved->vga_font0[i] = vga_r(fbbase, i);
^1da177e4c3f41 Linus Torvalds 2005-04-16   90  	}
^1da177e4c3f41 Linus Torvalds 2005-04-16   91  
^1da177e4c3f41 Linus Torvalds 2005-04-16   92  	/* save font at plane 3 */
^1da177e4c3f41 Linus Torvalds 2005-04-16   93  	if (state->flags & VGA_SAVE_FONT1) {
^1da177e4c3f41 Linus Torvalds 2005-04-16   94  		vga_wseq(state->vgabase, VGA_SEQ_PLANE_WRITE, 0x8);
^1da177e4c3f41 Linus Torvalds 2005-04-16   95  		vga_wseq(state->vgabase, VGA_SEQ_MEMORY_MODE, 0x6);
^1da177e4c3f41 Linus Torvalds 2005-04-16   96  		vga_wgfx(state->vgabase, VGA_GFX_PLANE_READ, 0x3);
^1da177e4c3f41 Linus Torvalds 2005-04-16   97  		vga_wgfx(state->vgabase, VGA_GFX_MODE, 0x0);
^1da177e4c3f41 Linus Torvalds 2005-04-16   98  		vga_wgfx(state->vgabase, VGA_GFX_MISC, 0x5);
^1da177e4c3f41 Linus Torvalds 2005-04-16   99  		for (i = 0; i < state->memsize; i++)
^1da177e4c3f41 Linus Torvalds 2005-04-16  100  			saved->vga_font1[i] = vga_r(fbbase, i);
^1da177e4c3f41 Linus Torvalds 2005-04-16  101  	}
^1da177e4c3f41 Linus Torvalds 2005-04-16  102  
^1da177e4c3f41 Linus Torvalds 2005-04-16  103  	/* save font at plane 0/1 */
^1da177e4c3f41 Linus Torvalds 2005-04-16  104  	if (state->flags & VGA_SAVE_TEXT) {
^1da177e4c3f41 Linus Torvalds 2005-04-16  105  		vga_wseq(state->vgabase, VGA_SEQ_PLANE_WRITE, 0x1);
^1da177e4c3f41 Linus Torvalds 2005-04-16  106  		vga_wseq(state->vgabase, VGA_SEQ_MEMORY_MODE, 0x6);
^1da177e4c3f41 Linus Torvalds 2005-04-16  107  		vga_wgfx(state->vgabase, VGA_GFX_PLANE_READ, 0x0);
^1da177e4c3f41 Linus Torvalds 2005-04-16  108  		vga_wgfx(state->vgabase, VGA_GFX_MODE, 0x0);
^1da177e4c3f41 Linus Torvalds 2005-04-16  109  		vga_wgfx(state->vgabase, VGA_GFX_MISC, 0x5);
^1da177e4c3f41 Linus Torvalds 2005-04-16  110  		for (i = 0; i < 8192; i++)
^1da177e4c3f41 Linus Torvalds 2005-04-16  111  			saved->vga_text[i] = vga_r(fbbase, i);
^1da177e4c3f41 Linus Torvalds 2005-04-16  112  
^1da177e4c3f41 Linus Torvalds 2005-04-16  113  		vga_wseq(state->vgabase, VGA_SEQ_PLANE_WRITE, 0x2);
^1da177e4c3f41 Linus Torvalds 2005-04-16  114  		vga_wseq(state->vgabase, VGA_SEQ_MEMORY_MODE, 0x6);
^1da177e4c3f41 Linus Torvalds 2005-04-16  115  		vga_wgfx(state->vgabase, VGA_GFX_PLANE_READ, 0x1);
^1da177e4c3f41 Linus Torvalds 2005-04-16  116  		vga_wgfx(state->vgabase, VGA_GFX_MODE, 0x0);
^1da177e4c3f41 Linus Torvalds 2005-04-16  117  		vga_wgfx(state->vgabase, VGA_GFX_MISC, 0x5);
^1da177e4c3f41 Linus Torvalds 2005-04-16  118  		for (i = 0; i < 8192; i++)
^1da177e4c3f41 Linus Torvalds 2005-04-16  119  			saved->vga_text[8192+i] = vga_r(fbbase + 2 * 8192, i);
^1da177e4c3f41 Linus Torvalds 2005-04-16  120  	}
^1da177e4c3f41 Linus Torvalds 2005-04-16  121  
^1da177e4c3f41 Linus Torvalds 2005-04-16  122  	/* restore regs */
^1da177e4c3f41 Linus Torvalds 2005-04-16  123  	vga_wseq(state->vgabase, VGA_SEQ_PLANE_WRITE, seq2);
^1da177e4c3f41 Linus Torvalds 2005-04-16  124  	vga_wseq(state->vgabase, VGA_SEQ_MEMORY_MODE, seq4);
^1da177e4c3f41 Linus Torvalds 2005-04-16  125  
^1da177e4c3f41 Linus Torvalds 2005-04-16  126  	vga_wgfx(state->vgabase, VGA_GFX_PLANE_READ, gr4);
^1da177e4c3f41 Linus Torvalds 2005-04-16  127  	vga_wgfx(state->vgabase, VGA_GFX_MODE, gr5);
^1da177e4c3f41 Linus Torvalds 2005-04-16  128  	vga_wgfx(state->vgabase, VGA_GFX_MISC, gr6);
^1da177e4c3f41 Linus Torvalds 2005-04-16  129  
^1da177e4c3f41 Linus Torvalds 2005-04-16  130  	/* unblank screen */
^1da177e4c3f41 Linus Torvalds 2005-04-16  131  	vga_wseq(state->vgabase, VGA_SEQ_RESET, 0x1);
^1da177e4c3f41 Linus Torvalds 2005-04-16  132  	vga_wseq(state->vgabase, VGA_SEQ_CLOCK_MODE, seq1 & ~(1 << 5));
^1da177e4c3f41 Linus Torvalds 2005-04-16  133  	vga_wseq(state->vgabase, VGA_SEQ_RESET, 0x3);
^1da177e4c3f41 Linus Torvalds 2005-04-16  134  
^1da177e4c3f41 Linus Torvalds 2005-04-16  135  	vga_wseq(state->vgabase, VGA_SEQ_CLOCK_MODE, seq1);
^1da177e4c3f41 Linus Torvalds 2005-04-16  136  }
^1da177e4c3f41 Linus Torvalds 2005-04-16  137  

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* [PATCH 0/8] Add support for Advantech EIO MFD series devices
From: Ramiro Oliveira @ 2025-12-12 16:40 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, 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.

Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
Ramiro Oliveira (8):
      Add Advantech EIO MFD 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                      |   13 +
 drivers/gpio/Kconfig             |    6 +
 drivers/gpio/Makefile            |    1 +
 drivers/gpio/gpio-eio.c          |  273 +++++++++
 drivers/hwmon/Kconfig            |   10 +
 drivers/hwmon/Makefile           |    1 +
 drivers/hwmon/eio-hwmon.c        |  344 ++++++++++++
 drivers/i2c/busses/Kconfig       |    6 +
 drivers/i2c/busses/Makefile      |    1 +
 drivers/i2c/busses/i2c-eio.c     | 1142 ++++++++++++++++++++++++++++++++++++++
 drivers/mfd/Kconfig              |   10 +
 drivers/mfd/Makefile             |    1 +
 drivers/mfd/eio_core.c           |  621 +++++++++++++++++++++
 drivers/thermal/Kconfig          |   17 +
 drivers/thermal/Makefile         |    2 +
 drivers/thermal/eio_fan.c        |  490 ++++++++++++++++
 drivers/thermal/eio_thermal.c    |  352 ++++++++++++
 drivers/video/backlight/Kconfig  |    6 +
 drivers/video/backlight/Makefile |    1 +
 drivers/video/backlight/eio_bl.c |  268 +++++++++
 drivers/watchdog/Kconfig         |    7 +
 drivers/watchdog/Makefile        |    1 +
 drivers/watchdog/eio_wdt.c       |  672 ++++++++++++++++++++++
 include/linux/mfd/eio.h          |  127 +++++
 24 files changed, 4372 insertions(+)
---
base-commit: d9771d0dbe18dd643760431870a6abf9b0866bb0
change-id: 20251212-upstream-v1-81338c603f94

Best regards,
-- 
Ramiro Oliveira <ramiro.oliveira@advantech.com>


^ permalink raw reply

* [PATCH 1/8] Add Advantech EIO MFD driver
From: Ramiro Oliveira @ 2025-12-12 16:40 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, 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: <20251212-upstream-v1-v1-0-d50d40ec8d8a@advantech.com>

Creating the MFD core driver for Advantech EIO, all other drivers (GPIO,
I2C, etc) depend on this core driver.

Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
 MAINTAINERS             |   6 +
 drivers/mfd/Kconfig     |  10 +
 drivers/mfd/Makefile    |   1 +
 drivers/mfd/eio_core.c  | 621 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/eio.h | 127 ++++++++++
 5 files changed, 765 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 663e86eb9ff1..bd9279796c2f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -616,6 +616,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 aace5766b38a..02a0b324eb6a 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -506,6 +506,16 @@ config MFD_DLN2
 	  etc. must be enabled in order to use the functionality of
 	  the device.
 
+config MFD_EIO
+	tristate "Advantech EIO MFD 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 e75e8045c28a..f8c53b55b679 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..7a58c62595a5
--- /dev/null
+++ b/drivers/mfd/eio_core.c
@@ -0,0 +1,621 @@
+// 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/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+#include <linux/time.h>
+#include <linux/uaccess.h>
+#include <linux/version.h>
+#include <linux/mfd/eio.h>
+
+#define TIMEOUT_MAX (10 * USEC_PER_SEC)
+#define TIMEOUT_MIN 200
+#define SLEEP_MAX 200
+#define DEFAULT_TIMEOUT 5000
+
+/**
+ * Timeout: Default timeout in microseconds when a PMC command's
+ * timeout is unspecified. PMC command responses typically range
+ * from 200us to 2ms. 5ms is quite a safe value for timeout. In
+ * In some cases, responses are longer. In such situations, please
+ * adding the timeout parameter loading related sub-drivers or
+ * this core driver (not recommended).
+ */
+static uint timeout = DEFAULT_TIMEOUT;
+module_param(timeout, uint, 0444);
+MODULE_PARM_DESC(timeout, "Default PMC command timeout in usec.\n");
+
+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 mfd_devs[] = {
+	{ .name = "eio_wdt" },
+	{ .name = "gpio_eio" },
+	{ .name = "eio_hwmon" },
+	{ .name = "i2c_eio" },
+	{ .name = "eio_thermal" },
+	{ .name = "eio_fan" },
+	{ .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 sprintf(buf, "%s\n", str);
+
+		val = *(u32 *)str;
+
+		if (attrs[i].type == HEX)
+			return sprintf(buf, "0x%08X\n", val);
+
+		if (attrs[i].type == NUMBER)
+			return sprintf(buf, "%d\n", val);
+
+		/* Should be pnp_id */
+		return sprintf(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;
+}
+
+#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)
+
+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 unsigned int eio_pnp_read(struct device *dev,
+				 struct eio_dev_port *port, u8 idx)
+{
+	struct eio_dev *eio = dev_get_drvdata(dev);
+	unsigned int val;
+
+	if (regmap_write(eio->map, port->idx_port, idx))
+		dev_err(dev, "Error port write 0x%X\n", port->idx_port);
+
+	if (regmap_read(eio->map, port->data_port, &val))
+		dev_err(dev, "Error port read 0x%X\n", port->data_port);
+
+	return val;
+}
+
+static void eio_pnp_write(struct device *dev, struct eio_dev_port *port,
+			  u8 idx, u8 data)
+{
+	struct eio_dev *eio = dev_get_drvdata(dev);
+
+	if (regmap_write(eio->map, port->idx_port, idx) ||
+	    regmap_write(eio->map, port->data_port, data))
+		dev_err(dev, "Error port write 0x%X %X\n", port->idx_port,
+			port->data_port);
+}
+
+static void eio_pnp_enter(struct device *dev, struct eio_dev_port *port)
+{
+	struct eio_dev *eio = dev_get_drvdata(dev);
+	/* Write 0x87 to index port twice to unlock IO port */
+	if (regmap_write(eio->map, port->idx_port,
+			 EIO_EXT_MODE_ENTER) ||
+	    regmap_write(eio->map, port->idx_port, EIO_EXT_MODE_ENTER))
+		dev_err(dev, "Error port write 0x%X\n", port->idx_port);
+}
+
+static void eio_pnp_leave(struct device *dev, struct eio_dev_port *port)
+{
+	struct eio_dev *eio = dev_get_drvdata(dev);
+	/* Write 0xAA to index port once to lock IO port */
+	if (regmap_write(eio->map, port->idx_port, EIO_EXT_MODE_EXIT))
+		dev_err(dev, "Error port write 0x%X\n", port->idx_port);
+}
+
+static int pmc_write_data(struct device *dev, int id, u8 value, u16 timeout)
+{
+	struct eio_dev *eio = dev_get_drvdata(dev);
+	int ret;
+
+	if (WAIT_IBF(dev, id, timeout))
+		return -ETIME;
+
+	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, u16 timeout)
+{
+	struct eio_dev *eio = dev_get_drvdata(dev);
+	int ret;
+
+	if (WAIT_IBF(dev, id, timeout))
+		return -ETIME;
+
+	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, u16 timeout)
+{
+	struct eio_dev *eio = dev_get_drvdata(dev);
+	unsigned int val;
+	int ret;
+
+	if (WAIT_OBF(dev, id, timeout))
+		return -ETIME;
+
+	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, uint max_duration)
+{
+	struct eio_dev *eio = dev_get_drvdata(dev);
+	uint val;
+	int new_timeout = max_duration ? max_duration : timeout;
+
+	if (new_timeout < TIMEOUT_MIN || new_timeout > TIMEOUT_MAX) {
+		dev_err(dev,
+			"Error timeout value: %dus. Timeout value should between %d and %ld\n",
+			new_timeout, TIMEOUT_MIN, TIMEOUT_MAX);
+		return -ETIME;
+	}
+
+	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, new_timeout);
+	return regmap_read_poll_timeout(eio->map,
+					eio->pmc[id].status, val,
+					(val & EIO_PMC_STATUS_OBF) != 0,
+					SLEEP_MAX, new_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 read_cmd = op->cmd & EIO_FLAG_PMC_READ;
+	ktime_t t = ktime_get();
+
+	mutex_lock(&eio->mutex);
+
+	pmc_clear(dev, op->chip);
+
+	ret = pmc_write_cmd(dev, op->chip, op->cmd, op->timeout);
+	if (ret)
+		goto err;
+
+	ret = pmc_write_data(dev, op->chip, op->control, op->timeout);
+	if (ret)
+		goto err;
+
+	ret = pmc_write_data(dev, op->chip, op->device_id, op->timeout);
+	if (ret)
+		goto err;
+
+	ret = pmc_write_data(dev, op->chip, op->size, op->timeout);
+	if (ret)
+		goto err;
+
+	for (i = 0; i < op->size; i++) {
+		if (read_cmd)
+			ret = pmc_read_data(dev, op->chip, &op->payload[i],
+					    op->timeout);
+		else
+			ret = pmc_write_data(dev, op->chip, op->payload[i],
+					     op->timeout);
+
+		if (ret)
+			goto err;
+	}
+
+	mutex_unlock(&eio->mutex);
+
+	return 0;
+
+err:
+	mutex_unlock(&eio->mutex);
+
+	dev_err(dev, "PMC error duration:%lldus",
+		ktime_to_us(ktime_sub(ktime_get(), t)));
+	dev_err(dev,
+		".cmd=0x%02X, .ctrl=0x%02X .id=0x%02X, .size=0x%02X .data=0x%02X%02X",
+		op->cmd, op->control, op->device_id, op->size, op->payload[0],
+		op->payload[1]);
+
+	return ret;
+}
+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];
+
+	eio_pnp_enter(dev, port);
+
+	/* Switch to PMC device page */
+	eio_pnp_write(dev, port, EIO_LDN, EIO_LDN_PMC1);
+
+	/* Active this device */
+	eio_pnp_write(dev, port, EIO_LDAR, EIO_LDAR_LDACT);
+
+	/* Get PMC cmd and data port */
+	pmc->data = eio_pnp_read(dev, port, EIO_IOBA0H) << 8;
+	pmc->data |= eio_pnp_read(dev, port, EIO_IOBA0L);
+	pmc->cmd = eio_pnp_read(dev, port, EIO_IOBA1H) << 8;
+	pmc->cmd |= eio_pnp_read(dev, port, EIO_IOBA1L);
+
+	/* Disable IRQ */
+	eio_pnp_write(dev, port, EIO_IRQCTRL, 0);
+
+	eio_pnp_leave(dev, port);
+
+	/* 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 = 0;
+	u8 tmp = 0;
+	int chip = 0;
+	int ret = -ENOMEM;
+
+	for (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;
+
+		eio_pnp_enter(dev, port);
+
+		chip_id = eio_pnp_read(dev, port, EIO_CHIPID1) << 8;
+		chip_id |= eio_pnp_read(dev, port, EIO_CHIPID2);
+
+		if (chip_id != EIO200_CHIPID && chip_id != EIO201_211_CHIPID)
+			continue;
+
+		/* Turn on the enable flag */
+		tmp = eio_pnp_read(dev, port, EIO_SIOCTRL);
+		tmp |= EIO_SIOCTRL_SIOEN;
+
+		eio_pnp_write(dev, port, EIO_SIOCTRL, tmp);
+
+		eio_pnp_leave(dev, port);
+
+		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 acpiram_access(struct device *dev, uint8_t offset)
+{
+	u8 val;
+	int ret;
+	int timeout = 0;
+	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, timeout);
+	if (ret)
+		goto err;
+
+	ret = pmc_write_data(dev, chip, offset, timeout);
+	if (ret)
+		goto err;
+
+	ret = pmc_write_data(dev, chip, sizeof(val), timeout);
+	if (ret)
+		goto err;
+
+	ret = pmc_read_data(dev, chip, &val, timeout);
+	if (ret)
+		goto err;
+
+err:
+	mutex_unlock(&eio->mutex);
+	return ret ? 0 : val;
+}
+
+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 = acpiram_access(dev, EIO_ACPIRAM_ICVENDOR);
+	ic_code = acpiram_access(dev, EIO_ACPIRAM_ICCODE);
+	code_base = acpiram_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, yet.");
+		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)) {
+		dev_dbg(dev, "No device found\n");
+		return -ENODEV;
+	}
+
+	ret = firmware_code_base(dev);
+	if (ret) {
+		dev_err(dev, "Chip code base check fail\n");
+		return ret; /* keep helper's return (e.g., -EIO) */
+	}
+
+	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+				   mfd_devs, ARRAY_SIZE(mfd_devs),
+				   NULL, 0, NULL);
+	if (ret)
+		dev_err(dev, "Cannot register child devices (error = %d)\n", ret);
+
+	dev_dbg(dev, "Module insert completed\n");
+	return 0;
+}
+
+static struct isa_driver eio_driver = {
+	.probe    = eio_probe,
+
+	.driver = {
+		.name = "eio_core",
+		.dev_groups = pmc_groups,
+	},
+};
+module_isa_driver(eio_driver, 1);
+
+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..b87614274201
--- /dev/null
+++ b/include/linux/mfd/eio.h
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Header for the Advantech EIO core driver and its sub-drivers
+ *
+ * Copyright (C) 2025 Advantech Co., Ltd.
+ */
+
+#ifndef _MFD_EIO_H_
+#define _MFD_EIO_H_
+#include <linux/io.h>
+#include <linux/regmap.h>
+
+/* Definition */
+#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;
+	u16 timeout;
+};
+
+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,
+		      uint timeout);
+
+#define WAIT_IBF(dev, id, timeout)	eio_core_pmc_wait(dev, id, PMC_WAIT_INPUT, timeout)
+#define WAIT_OBF(dev, id, timeout)	eio_core_pmc_wait(dev, id, PMC_WAIT_OUTPUT, timeout)
+
+#ifdef pr_fmt
+#undef pr_fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#endif
+
+#endif

-- 
2.43.0


^ permalink raw reply related

* [PATCH 2/8] Add Advantech EIO GPIO driver
From: Ramiro Oliveira @ 2025-12-12 16:40 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, 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: <20251212-upstream-v1-v1-0-d50d40ec8d8a@advantech.com>

This driver controls the GPIO component of the Advantech EIO chip.

Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
 MAINTAINERS             |   1 +
 drivers/gpio/Kconfig    |   6 ++
 drivers/gpio/Makefile   |   1 +
 drivers/gpio/gpio-eio.c | 273 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 281 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index bd9279796c2f..359d4a13f212 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -619,6 +619,7 @@ F:	drivers/platform/x86/adv_swbutton.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
 
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index bd185482a7fd..628a914842bd 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -277,6 +277,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 2421a8fd3733..ba3883d5e4a0 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..50f66a325e8f
--- /dev/null
+++ b/drivers/gpio/gpio-eio.c
@@ -0,0 +1,273 @@
+// 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.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+
+#define GPIO_MAX_PINS	48
+#define GPIO_WRITE	0x18
+#define GPIO_READ	0x19
+
+struct eio_gpio_dev {
+	u64 avail;
+	int max;
+	struct gpio_chip chip;
+	struct device *dev;
+};
+
+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_STATUS	 = 0,
+	GPIO_GROUP_AVAIL = 3,
+	GPIO_ERROR	 = 0x04,
+	GPIO_PIN_DIR	 = 0x10,
+	GPIO_PIN_LEVEL	 = 0x11,
+	GPIO_GROUP_DIR	 = 0x12,
+	GPIO_GROUP_LEVEL = 0x13,
+	GPIO_MAPPING	 = 0x14,
+	GPIO_NAME	 = 0x15
+} gpio_ctrl;
+
+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 timeout;
+module_param(timeout, int, 0444);
+MODULE_PARM_DESC(timeout, "Set PMC command timeout value.\n");
+
+static int pmc_write(struct device *mfd_dev, u8 ctrl, u8 dev_id, void *data)
+{
+	struct pmc_op op = {
+		 .cmd       = GPIO_WRITE,
+		 .control   = ctrl,
+		 .device_id = dev_id,
+		 .payload   = (u8 *)data,
+		 .timeout   = timeout,
+	};
+
+	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       = GPIO_READ,
+		 .control   = ctrl,
+		 .device_id = dev_id,
+		 .payload   = (u8 *)data,
+		 .timeout   = timeout,
+	};
+
+	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, 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, 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, GPIO_PIN_DIR, offset, &dir);
+
+	return pmc_write(chip->parent, 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, 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;
+
+	pmc_write(chip->parent, GPIO_PIN_LEVEL, offset, &val);
+
+	return 0;
+}
+
+static int check_support(struct device *dev)
+{
+	u8  data;
+	int ret;
+
+	ret = pmc_read(dev, 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, 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, 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;
+	int i;
+	char str[GPIO_MAX_PINS + 1];
+
+	memset(str, 0x30, sizeof(str));
+
+	ret = check_support(mfd);
+	if (ret) {
+		dev_err(eio_gpio->dev, "GPIO not supported (%d)\n", ret);
+		return ret;
+	}
+
+	eio_gpio->avail = 0;
+
+	for (i = 0 ; i <  GPIO_MAX_PINS ; i++) {
+		ret = check_pin(mfd, i);
+		if (ret)
+			continue;
+
+		eio_gpio->avail |= BIT(i);
+		eio_gpio->max = i + 1;
+		str[GPIO_MAX_PINS - i] = '1';
+	}
+
+	dev_info(eio_gpio->dev, "GPIO pins=%s\n", str);
+
+	return eio_gpio->max ? 0 : -EOPNOTSUPP;
+}
+
+static const struct gpio_chip eio_gpio_chip = {
+	.label		  = KBUILD_MODNAME,
+	.owner		  = THIS_MODULE,
+	.direction_input  = dir_input,
+	.get		  = gpio_get,
+	.direction_output = dir_output,
+	.set		  = gpio_set,
+	.get_direction	  = get_dir,
+	.base		  = -1,
+	.can_sleep	  = true,
+};
+
+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) {
+		dev_err(dev, "Error contact eio_core\n");
+		return -ENODEV;
+	}
+
+	eio_gpio = devm_kzalloc(dev, sizeof(*eio_gpio), GFP_KERNEL);
+	eio_gpio->dev = dev;
+
+	if (gpio_init(dev->parent, eio_gpio))
+		return -EIO;
+
+	eio_gpio->chip	   = eio_gpio_chip;
+	eio_gpio->chip.parent = dev->parent;
+	eio_gpio->chip.ngpio  = eio_gpio->max;
+
+	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 3/8] Add Advantech EIO Hardware Monitor driver
From: Ramiro Oliveira @ 2025-12-12 16:40 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, 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: <20251212-upstream-v1-v1-0-d50d40ec8d8a@advantech.com>

This driver controls the Hardware Monitor block of the Advantech EIO chip.

Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
---
 MAINTAINERS               |   1 +
 drivers/hwmon/Kconfig     |  10 ++
 drivers/hwmon/Makefile    |   1 +
 drivers/hwmon/eio-hwmon.c | 344 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 356 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 359d4a13f212..fdd39b152f41 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -620,6 +620,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 157678b821fc..08993b993596 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2043,6 +2043,16 @@ config SENSORS_DME1737
 	  This driver can also be built as a module. If so, the module
 	  will be called dme1737.
 
+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 eade8e3b1bde..e69f03b41fae 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o
 obj-$(CONFIG_SENSORS_DRIVETEMP)	+= drivetemp.o
 obj-$(CONFIG_SENSORS_DS620)	+= ds620.o
 obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
+obj-$(CONFIG_SENSORS_EIO)	+= eio-hwmon.o
 obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
 obj-$(CONFIG_SENSORS_EMC2103)	+= emc2103.o
 obj-$(CONFIG_SENSORS_EMC2305)	+= emc2305.o
diff --git a/drivers/hwmon/eio-hwmon.c b/drivers/hwmon/eio-hwmon.c
new file mode 100644
index 000000000000..164591aa31a7
--- /dev/null
+++ b/drivers/hwmon/eio-hwmon.c
@@ -0,0 +1,344 @@
+// 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/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+
+#define MAX_DEV 128
+#define MAX_NAME 32
+
+static uint timeout;
+module_param(timeout, uint, 0444);
+MODULE_PARM_DESC(timeout,
+		 "Default pmc command timeout in micro-seconds.\n");
+
+struct eio_hwmon_dev {
+	struct device *mfd;
+};
+
+enum _sen_type {
+	NONE,
+	VOLTAGE,
+	CURRENT,
+	TEMP,
+	PWM,
+	TACHO,
+	FAN,
+	CASEOPEN,
+};
+
+struct eio_key {
+	enum _sen_type type;
+	u8 chan;
+	u8 item;
+	u8 label_idx;
+};
+
+struct eio_attr {
+	struct sensor_device_attribute sda;
+	struct eio_key key;
+};
+
+static struct {
+	u8   cmd;
+	u8   max;
+	signed int shift;
+	char name[32];
+	u8   ctrl[16];
+	u16  multi[16];
+	char item[16][32];
+	char labels[32][32];
+
+} 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" },
+	},
+	{ 0x14, 0, 0, "pwm",
+		{ 0xFF, 0x11, 0x12 },
+		{ 1, 1, 1 },
+		{ "label", "polarity", "freq" },
+		{ "pwm0", "pwm0", "pwm0", "pwm0" },
+	},
+	{ 0x16, 2, 0, "tacho",
+		{ 0xFF, 0x10 },
+		{ 1, 1 },
+		{ "label", "input"},
+		{ "cpu0", "cpu1", "cpu2", "cpu3",
+		  "sys0", "sys1", "sys2", "sys3",
+		  "", "", "", "", "", "", "", "",
+		  "", "", "", "", "", "", "", "",
+		  "", "", "", "",
+		  "oem0", "oem1", "oem2", "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 },
+
+	{ PWM,      0x00, 1, false }, { PWM,	  0x10, 1, true  },
+	{ PWM,      0x11, 1, true  }, { PWM,	  0x12, 4, true  },
+
+	{ TACHO,    0x00, 1, false }, { TACHO,	  0x01, 1, false },
+	{ TACHO,    0x10, 4, true  },
+
+	{ 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,
+		 .timeout   = timeout,
+	};
+
+	ret = eio_core_pmc_operation(mfd, &op);
+	return ret;
+}
+
+static ssize_t eio_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct eio_hwmon_dev *eio_hwmon = dev_get_drvdata(dev);
+	struct eio_attr *eio_attr =
+		container_of(attr, struct eio_attr, sda.dev_attr);
+	const struct eio_key *eio_key = &eio_attr->key;
+	int ret;
+	u8 data[2];
+	u32 temp_val;
+	signed int final_val;
+
+	switch (eio_key->item) {
+	case 0:
+		return sysfs_emit(buf, "%s\n",
+				  sen_info[eio_key->type].labels[eio_key->label_idx]);
+
+	default:
+		ret = pmc_read(eio_hwmon->mfd, eio_key->type, eio_key->chan,
+			       sen_info[eio_key->type].ctrl[eio_key->item],
+			       &data);
+		if (ret)
+			return ret;
+
+		temp_val = data[0] | data[1] << 8;
+
+		final_val = (signed int)temp_val + (signed int)(sen_info[eio_key->type].shift);
+		final_val = final_val * (signed int)sen_info[eio_key->type].multi[eio_key->item];
+
+		return sysfs_emit(buf, "%d\n", final_val);
+	}
+
+	return -EINVAL;
+}
+
+static char devname[MAX_DEV][MAX_NAME];
+static struct eio_attr devattrs[MAX_DEV];
+static struct attribute *attrs[MAX_DEV];
+
+static struct attribute_group group = {
+	.attrs = attrs,
+};
+
+static const struct attribute_group *groups[] = {
+	&group,
+	NULL
+};
+
+static int hwmon_init(struct device *mfd, struct eio_hwmon_dev *eio_hwmon)
+{
+	enum _sen_type type;
+	u8 i, j, data[16];
+	int sum = 0;
+	int ret;
+
+	for (type = VOLTAGE ; type <= CASEOPEN ; type++) {
+		int cnt = 1;
+
+		for (i = 0 ; i < sen_info[type].max ; i++) {
+			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) {
+				dev_info(mfd, "read type id error\n");
+				continue;
+			}
+
+			for (j = 0 ; j < ARRAY_SIZE(sen_info->item) ; j++) {
+				struct eio_attr *eio_attr;
+
+				if (sen_info[type].item[j][0] == 0)
+					continue;
+
+				eio_attr = &devattrs[sum];
+
+				eio_attr->key.type      = type;
+				eio_attr->key.chan      = i;
+				eio_attr->key.item      = j;
+				eio_attr->key.label_idx = data[0];
+
+				snprintf(devname[sum], sizeof(devname[sum]),
+					 "%s%d_%s", sen_info[type].name, cnt,
+					 sen_info[type].item[j]);
+
+				eio_attr->sda.dev_attr.attr.name = devname[sum];
+				eio_attr->sda.dev_attr.attr.mode = 0444;
+				eio_attr->sda.dev_attr.show      = eio_show;
+
+				attrs[sum] = &eio_attr->sda.dev_attr.attr;
+
+				if (++sum >= MAX_DEV)
+					break;
+			}
+			cnt++;
+		}
+	}
+
+	return sum;
+}
+
+static int hwmon_probe(struct platform_device *pdev)
+{
+	struct device *dev =  &pdev->dev;
+	struct eio_hwmon_dev *eio_hwmon;
+	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_hwmon = devm_kzalloc(dev, sizeof(*eio_hwmon), GFP_KERNEL);
+	if (!eio_hwmon)
+		return -ENOMEM;
+
+	eio_hwmon->mfd = dev->parent;
+	platform_set_drvdata(pdev, eio_hwmon);
+
+	if (hwmon_init(dev->parent, eio_hwmon) <= 0)
+		return -ENODEV;
+
+	hwmon =	devm_hwmon_device_register_with_groups(dev, KBUILD_MODNAME,
+						       eio_hwmon,
+						       groups);
+	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 4/8] Add Advantech EIO I2C driver
From: Ramiro Oliveira @ 2025-12-12 16:40 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, 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: <20251212-upstream-v1-v1-0-d50d40ec8d8a@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 | 1142 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1150 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index fdd39b152f41..be9d3c4e1ce1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -621,6 +621,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 09ba55bae1fa..e597c08414e4 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -656,6 +656,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 fb985769f5ff..b65bb06b14c6 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..a867f24a4809
--- /dev/null
+++ b/drivers/i2c/busses/i2c-eio.c
@@ -0,0 +1,1142 @@
+// 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 timeout = I2C_TIMEOUT;
+module_param(timeout, int, 0444);
+MODULE_PARM_DESC(timeout, "Set IO timeout value.\n");
+
+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(), 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(), 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(), 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(), 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(), 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(), 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(), 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;
+	}
+
+	timeout = clamp_t(int, timeout, I2C_TIMEOUT / 100, I2C_TIMEOUT * 100);
+	dev_info(dev, "Timeout value %d\n", timeout);
+
+	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 5/8] Add Advantech EIO Backlight driver
From: Ramiro Oliveira @ 2025-12-12 16:40 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, 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: <20251212-upstream-v1-v1-0-d50d40ec8d8a@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 | 268 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 276 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index be9d3c4e1ce1..df4b4cc31257 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -623,6 +623,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
 
 ADXL313 THREE-AXIS DIGITAL ACCELEROMETER DRIVER
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index a1422ddd1c22..ddd3d6922553 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -496,6 +496,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 a5d62b018102..4601b644b6d4 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_BACKLIGHT_BD6107)		+= bd6107.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..2b9fd4d48d30
--- /dev/null
+++ b/drivers/video/backlight/eio_bl.c
@@ -0,0 +1,268 @@
+// 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
+
+#define BL_AVAIL		BIT(0)
+#define BL_PWM_DC		BIT(1)
+#define BL_PWM_SRC		BIT(2)
+#define BL_BRI_INVERT		BIT(3)
+#define BL_ENABLE_PIN_SUPP	BIT(4)
+#define BL_POWER_INVERT		BIT(5)
+#define BL_ENABLE_PIN_EN	BIT(6)
+#define BL_FIRMWARE_ERROR	BIT(7)
+
+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");
+
+static int timeout;
+module_param(timeout, int, 0444);
+MODULE_PARM_DESC(timeout, "Set PMC command timeout value.\n");
+
+struct eio_bl_dev {
+	struct device *mfd;
+	u8 id;
+	u8 max;
+};
+
+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,
+		.timeout   = timeout,
+	};
+
+	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,
+		.timeout   = timeout,
+	};
+
+	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);
+	u32 max  = bl->props.max_brightness;
+	u8 duty = clamp_val(bl->props.brightness, 0, max);
+	u8 sw = bl->props.power == BACKLIGHT_POWER_OFF;
+	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 -ENXIO;
+	}
+
+	ret = pmc_read(dev, BL_CTRL_DUTY, id, &props->brightness);
+	if (ret)
+		return ret;
+
+	/* Invert PWM */
+	dev_dbg(dev, "bri_invert=%d\n", bri_invert);
+	if (bri_invert > USE_DEFAULT) {
+		ret = pmc_write(dev, BL_CTRL_INVERT, id, &bri_invert);
+		if (ret)
+			return ret;
+	}
+
+	bri_invert = 0;
+	ret = pmc_read(dev, BL_CTRL_INVERT, id, &bri_invert);
+	if (ret)
+		return ret;
+
+	dev_dbg(dev, "bri_freq=%u\n", bri_freq);
+	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;
+
+	dev_dbg(dev, "bl_power_invert=%d\n", bl_power_invert);
+	if (bl_power_invert >= USE_DEFAULT) {
+		ret = pmc_write(dev, BL_CTRL_ENABLE_INVERT, id, &bl_power_invert);
+		if (ret)
+			return ret;
+	}
+
+	bl_power_invert = 0;
+	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;
+	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;
+		int ret;
+
+		memset(&props, 0, sizeof(props));
+		props.type           = BACKLIGHT_RAW;
+		props.max_brightness = THERMAL_MAX;
+		props.power          = BACKLIGHT_POWER_OFF;
+		props.brightness     = props.max_brightness;
+
+		eio_bl = devm_kzalloc(dev, sizeof(*eio_bl), GFP_KERNEL);
+		if (!eio_bl)
+			return -ENOMEM;
+
+		eio_bl->mfd = dev->parent;
+		eio_bl->id  = id;
+		eio_bl->max = props.max_brightness;
+
+		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;
+		}
+
+		dev_info(dev, "%s registered (max=%u)\n", name, props.max_brightness);
+	}
+
+	return 0;
+}
+
+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 6/8] Add Advantech EIO Watchdog driver
From: Ramiro Oliveira @ 2025-12-12 16:40 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, 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: <20251212-upstream-v1-v1-0-d50d40ec8d8a@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 | 672 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 681 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index df4b4cc31257..dfdf4f39c14b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -624,6 +624,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
 
 ADXL313 THREE-AXIS DIGITAL ACCELEROMETER DRIVER
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index d3b9df7d466b..2f8508e51634 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -248,6 +248,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 OF_GPIO
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index ba52099b1253..59b5ec0246d6 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -230,6 +230,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..a81f005d82d2
--- /dev/null
+++ b/drivers/watchdog/eio_wdt.c
@@ -0,0 +1,672 @@
+// 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/module.h>
+#include <linux/reboot.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <linux/mfd/eio.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)");
+
+/* Specify the IRQ number when the IRQ event is triggered */
+static int irq;
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "The IRQ number for IRQ event");
+
+static int timeout;
+module_param(timeout, int, 0444);
+MODULE_PARM_DESC(timeout, "Set PMC command timeout value.\n");
+
+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,
+		.timeout   = timeout,
+	};
+	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,
+		.timeout   = timeout,
+	};
+	return eio_core_pmc_operation(dev, &op);
+}
+
+static int wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
+{
+	struct eio_wdt_dev *eio_wdt = watchdog_get_drvdata(wdd);
+
+	wdd->timeout = timeout;
+	dev_info(eio_wdt->dev, "Set timeout: %u\n", timeout);
+
+	return 0;
+}
+
+static int wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int pretimeout)
+{
+	struct eio_wdt_dev *eio_wdt = watchdog_get_drvdata(wdd);
+
+	wdd->pretimeout = pretimeout;
+	dev_info(eio_wdt->dev, "Set pretimeout: %u\n", 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;
+			}
+
+			dev_info(eio_wdt->dev, "Trigger type is %d:%s\n",
+				 i, type_strs[i]);
+			eio_wdt->event_type = i;
+			return 0;
+		}
+	}
+
+	dev_info(eio_wdt->dev, "Event type: %s\n",
+		 type_strs[eio_wdt->event_type]);
+	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;
+
+	dev_dbg(eio_wdt->dev, "Pretimeout H/W disabled\n");
+	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 unsigned int wdt_get_timeleft(struct watchdog_device *wdd)
+{
+	struct eio_wdt_dev *eio_wdt = watchdog_get_drvdata(wdd);
+	unsigned int timeleft = 0;
+
+	if (eio_wdt->last_time && wdd->timeout) {
+		unsigned long delta   = jiffies - eio_wdt->last_time;
+		unsigned int  elapsed = (unsigned int)(delta / HZ);
+
+		if (elapsed < wdd->timeout)
+			timeleft = wdd->timeout - elapsed;
+	}
+	return timeleft;
+}
+
+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);
+	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);
+
+	/* Enable WDT */
+	ret |= regmap_write(map, idx,  IOREG_WDT_STATUS);
+	ret |= regmap_write(map, data, FLAG_WDT_ENABLED);
+
+	/* Set IRQ number */
+	ret |= regmap_write(map, idx,  IOREG_IRQ);
+	ret |= regmap_write(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_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;
+
+	if (irq) {
+		eio_wdt->irq = irq;
+	} else {
+		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,
+	.get_timeleft	= wdt_get_timeleft,
+	.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(dev, "IRQ %d request fail:%d. Disabled.\n",
+				eio_wdt->irq, ret);
+			return ret;
+		}
+	}
+
+	wdd = &eio_wdt->wdd;
+	wdd->info        = &wdinfo;
+	wdd->ops         = &wdt_ops;
+	wdd->parent      = dev;
+	wdd->min_timeout = 1;
+	wdd->max_timeout = 0x7FFF;
+
+	ret = watchdog_init_timeout(wdd, wdd->timeout, dev);
+	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

* [PATCH 7/8] Add Advantech EIO Thermal driver
From: Ramiro Oliveira @ 2025-12-12 16:40 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, 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: <20251212-upstream-v1-v1-0-d50d40ec8d8a@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 | 352 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 363 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index dfdf4f39c14b..770b2f82d01a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -623,6 +623,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 b10080d61860..7309f7e7a1c1 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 bb21e7ea7fc6..3740540d8a18 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..2d82bd9d7855
--- /dev/null
+++ b/drivers/thermal/eio_thermal.c
@@ -0,0 +1,352 @@
+// 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 timeout;
+module_param(timeout, int, 0444);
+MODULE_PARM_DESC(timeout, "Set PMC command timeout value.\n");
+
+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],
+		.timeout   = timeout,
+	};
+
+	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],
+		.timeout   = timeout,
+	};
+
+	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 8/8] Add Advantech EIO Fan driver
From: Ramiro Oliveira @ 2025-12-12 16:40 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, 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: <20251212-upstream-v1-v1-0-d50d40ec8d8a@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 | 490 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 500 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 770b2f82d01a..b227a9d28191 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -623,6 +623,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 7309f7e7a1c1..ba4958ff0962 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 3740540d8a18..2633e8ed9fdc 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..7f0529a1907e
--- /dev/null
+++ b/drivers/thermal/eio_fan.c
@@ -0,0 +1,490 @@
+// 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 timeout;
+module_param(timeout, int, 0444);
+MODULE_PARM_DESC(timeout, "Set PMC command timeout value.\n");
+
+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,
+		.timeout   = timeout,
+	};
+	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,
+		.timeout   = timeout,
+	};
+	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,
+		.timeout   = timeout,
+	};
+	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;
+		int temps_mc[TRIP_NUM];
+		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;
+		}
+
+		temps_mc[TRIP_HIGH] = DECI_KELVIN_TO_MILLI_CELSIUS(trip_hi);
+		temps_mc[TRIP_LOW]  = DECI_KELVIN_TO_MILLI_CELSIUS(trip_lo);
+		temps_mc[TRIP_STOP] = DECI_KELVIN_TO_MILLI_CELSIUS(trip_stop);
+
+		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

* Re: [PATCH 5/8] Add Advantech EIO Backlight driver
From: Daniel Thompson @ 2025-12-12 17:59 UTC (permalink / raw)
  To: Ramiro Oliveira
  Cc: 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, linux-kernel, 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: <20251212-upstream-v1-v1-5-d50d40ec8d8a@advantech.com>

On Fri, Dec 12, 2025 at 05:40:56PM +0100, Ramiro Oliveira wrote:
> This driver controls the Video Backlight block of the Advantech EIO chip.
>
> Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>

Thanks for the patch.

Review below...


> ---
>  MAINTAINERS                      |   1 +
>  drivers/video/backlight/Kconfig  |   6 +
>  drivers/video/backlight/Makefile |   1 +
>  drivers/video/backlight/eio_bl.c | 268 +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 276 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index be9d3c4e1ce1..df4b4cc31257 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -623,6 +623,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
>
>  ADXL313 THREE-AXIS DIGITAL ACCELEROMETER DRIVER
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index a1422ddd1c22..ddd3d6922553 100644
> --- a/drivers/video/backlight/Kconfig
> +++ b/drivers/video/backlight/Kconfig
> @@ -496,6 +496,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 a5d62b018102..4601b644b6d4 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -30,6 +30,7 @@ obj-$(CONFIG_BACKLIGHT_BD6107)		+= bd6107.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..2b9fd4d48d30
> --- /dev/null
> +++ b/drivers/video/backlight/eio_bl.c
> @@ -0,0 +1,268 @@
> +// 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
> +
> +#define BL_AVAIL		BIT(0)
> +#define BL_PWM_DC		BIT(1)
> +#define BL_PWM_SRC		BIT(2)
> +#define BL_BRI_INVERT		BIT(3)
> +#define BL_ENABLE_PIN_SUPP	BIT(4)
> +#define BL_POWER_INVERT		BIT(5)
> +#define BL_ENABLE_PIN_EN	BIT(6)
> +#define BL_FIRMWARE_ERROR	BIT(7)

These appear to be unused.

> +
> +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");
> +
> +static int timeout;
> +module_param(timeout, int, 0444);
> +MODULE_PARM_DESC(timeout, "Set PMC command timeout value.\n");

Module parameters are not really expected these days and are
pretty user hostile.

Are they really needed? AFAICT this is a firmware based device. Why
doesn't the firmware provide this information if the drivers need it
(either directly or via PNP ID and a lookup table)?


> +
> +struct eio_bl_dev {
> +	struct device *mfd;
> +	u8 id;
> +	u8 max;

The value in max is never read.


> +};
> +
> +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,
> +		.timeout   = timeout,
> +	};
> +
> +	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,
> +		.timeout   = timeout,
> +	};
> +
> +	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);
> +	u32 max  = bl->props.max_brightness;
> +	u8 duty = clamp_val(bl->props.brightness, 0, max);
> +	u8 sw = bl->props.power == BACKLIGHT_POWER_OFF;

You shouldn't need to read anything from bl->props directly.
Please use the helper functions to get the duty value and power
state.

> +	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 -ENXIO;

Is -ENODEV more appropriate here?


> +	}
> +
> +	ret = pmc_read(dev, BL_CTRL_DUTY, id, &props->brightness);
> +	if (ret)
> +		return ret;
> +
> +	/* Invert PWM */
> +	dev_dbg(dev, "bri_invert=%d\n", bri_invert);

Let's drop the dev_dbg() messages, printing module parameter values
isn't very useful.


> +	if (bri_invert > USE_DEFAULT) {
> +		ret = pmc_write(dev, BL_CTRL_INVERT, id, &bri_invert);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	bri_invert = 0;
> +	ret = pmc_read(dev, BL_CTRL_INVERT, id, &bri_invert);
> +	if (ret)
> +		return ret;

Writing back to module parameters during probe is rather unusual. Is it
really needed?


> +
> +	dev_dbg(dev, "bri_freq=%u\n", bri_freq);
> +	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;
> +
> +	dev_dbg(dev, "bl_power_invert=%d\n", bl_power_invert);
> +	if (bl_power_invert >= USE_DEFAULT) {
> +		ret = pmc_write(dev, BL_CTRL_ENABLE_INVERT, id, &bl_power_invert);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	bl_power_invert = 0;
> +	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;
> +	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;
> +		int ret;
> +
> +		memset(&props, 0, sizeof(props));
> +		props.type           = BACKLIGHT_RAW;
> +		props.max_brightness = THERMAL_MAX;
> +		props.power          = BACKLIGHT_POWER_OFF;
> +		props.brightness     = props.max_brightness;

New drivers should not initialize props.scale as UNKNOWN. Please
set to match the hardware behaviour (if brightness 50% looks roughly
half as bright as 100% then the scale is 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;
> +		eio_bl->max = props.max_brightness;
> +
> +		ret = bl_init(eio_bl->mfd, id, &props);
> +		if (ret) {
> +			dev_info(dev, "%d No Backlight %u enabled!\n", ret, id);
> +			continue;
> +		}

If neither backlight is enabled if would be good to propagate the return
value (to prevent the probe from spuriously succeeding).


> +
> +		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;
> +		}
> +
> +		dev_info(dev, "%s registered (max=%u)\n", name, props.max_brightness);

Silence (on success) is golden. Please remove this.


> +	}
> +
> +	return 0;
> +}
> +
> +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");


Thanks

Daniel.

^ permalink raw reply

* Re: [PATCH 3/8] Add Advantech EIO Hardware Monitor driver
From: Guenter Roeck @ 2025-12-12 18:21 UTC (permalink / raw)
  To: Ramiro Oliveira
  Cc: 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,
	linux-kernel, 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: <20251212-upstream-v1-v1-3-d50d40ec8d8a@advantech.com>

On Fri, Dec 12, 2025 at 05:40:54PM +0100, Ramiro Oliveira wrote:
> This driver controls the Hardware Monitor block of the Advantech EIO chip.
> 
> Signed-off-by: Ramiro Oliveira <ramiro.oliveira@advantech.com>
> ---
>  MAINTAINERS               |   1 +
>  drivers/hwmon/Kconfig     |  10 ++
>  drivers/hwmon/Makefile    |   1 +
>  drivers/hwmon/eio-hwmon.c | 344 ++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 356 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 359d4a13f212..fdd39b152f41 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -620,6 +620,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 157678b821fc..08993b993596 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -2043,6 +2043,16 @@ config SENSORS_DME1737
>  	  This driver can also be built as a module. If so, the module
>  	  will be called dme1737.
>  
> +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 eade8e3b1bde..e69f03b41fae 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -72,6 +72,7 @@ obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o
>  obj-$(CONFIG_SENSORS_DRIVETEMP)	+= drivetemp.o
>  obj-$(CONFIG_SENSORS_DS620)	+= ds620.o
>  obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
> +obj-$(CONFIG_SENSORS_EIO)	+= eio-hwmon.o
>  obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
>  obj-$(CONFIG_SENSORS_EMC2103)	+= emc2103.o
>  obj-$(CONFIG_SENSORS_EMC2305)	+= emc2305.o
> diff --git a/drivers/hwmon/eio-hwmon.c b/drivers/hwmon/eio-hwmon.c
> new file mode 100644
> index 000000000000..164591aa31a7
> --- /dev/null
> +++ b/drivers/hwmon/eio-hwmon.c
> @@ -0,0 +1,344 @@
> +// 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/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/eio.h>
> +#include <linux/module.h>
> +
> +#define MAX_DEV 128
> +#define MAX_NAME 32
> +
> +static uint timeout;
> +module_param(timeout, uint, 0444);
> +MODULE_PARM_DESC(timeout,
> +		 "Default pmc command timeout in micro-seconds.\n");
> +

It does not make sense to me to have this as module parameter for each of
the drivers.

> +struct eio_hwmon_dev {
> +	struct device *mfd;
> +};
> +
> +enum _sen_type {
> +	NONE,
> +	VOLTAGE,
> +	CURRENT,
> +	TEMP,
> +	PWM,
> +	TACHO,
> +	FAN,
> +	CASEOPEN,
> +};
> +
> +struct eio_key {
> +	enum _sen_type type;
> +	u8 chan;
> +	u8 item;
> +	u8 label_idx;
> +};
> +
> +struct eio_attr {
> +	struct sensor_device_attribute sda;
> +	struct eio_key key;
> +};
> +
> +static struct {
> +	u8   cmd;
> +	u8   max;
> +	signed int shift;
> +	char name[32];
> +	u8   ctrl[16];
> +	u16  multi[16];
> +	char item[16][32];
> +	char labels[32][32];
> +
> +} 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" },
> +	},
> +	{ 0x14, 0, 0, "pwm",
> +		{ 0xFF, 0x11, 0x12 },
> +		{ 1, 1, 1 },
> +		{ "label", "polarity", "freq" },
> +		{ "pwm0", "pwm0", "pwm0", "pwm0" },
> +	},
> +	{ 0x16, 2, 0, "tacho",
> +		{ 0xFF, 0x10 },
> +		{ 1, 1 },
> +		{ "label", "input"},
> +		{ "cpu0", "cpu1", "cpu2", "cpu3",
> +		  "sys0", "sys1", "sys2", "sys3",
> +		  "", "", "", "", "", "", "", "",
> +		  "", "", "", "", "", "", "", "",
> +		  "", "", "", "",
> +		  "oem0", "oem1", "oem2", "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 },
> +
> +	{ PWM,      0x00, 1, false }, { PWM,	  0x10, 1, true  },
> +	{ PWM,      0x11, 1, true  }, { PWM,	  0x12, 4, true  },
> +
> +	{ TACHO,    0x00, 1, false }, { TACHO,	  0x01, 1, false },
> +	{ TACHO,    0x10, 4, true  },
> +
> +	{ 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,
> +		 .timeout   = timeout,
> +	};
> +
> +	ret = eio_core_pmc_operation(mfd, &op);
> +	return ret;
> +}
> +
> +static ssize_t eio_show(struct device *dev, struct device_attribute *attr,
> +			char *buf)
> +{
> +	struct eio_hwmon_dev *eio_hwmon = dev_get_drvdata(dev);
> +	struct eio_attr *eio_attr =
> +		container_of(attr, struct eio_attr, sda.dev_attr);
> +	const struct eio_key *eio_key = &eio_attr->key;
> +	int ret;
> +	u8 data[2];
> +	u32 temp_val;
> +	signed int final_val;
> +
> +	switch (eio_key->item) {
> +	case 0:
> +		return sysfs_emit(buf, "%s\n",
> +				  sen_info[eio_key->type].labels[eio_key->label_idx]);
> +
> +	default:
> +		ret = pmc_read(eio_hwmon->mfd, eio_key->type, eio_key->chan,
> +			       sen_info[eio_key->type].ctrl[eio_key->item],
> +			       &data);
> +		if (ret)
> +			return ret;
> +
> +		temp_val = data[0] | data[1] << 8;
> +
> +		final_val = (signed int)temp_val + (signed int)(sen_info[eio_key->type].shift);
> +		final_val = final_val * (signed int)sen_info[eio_key->type].multi[eio_key->item];
> +
> +		return sysfs_emit(buf, "%d\n", final_val);
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static char devname[MAX_DEV][MAX_NAME];
> +static struct eio_attr devattrs[MAX_DEV];
> +static struct attribute *attrs[MAX_DEV];
> +
> +static struct attribute_group group = {
> +	.attrs = attrs,
> +};
> +
> +static const struct attribute_group *groups[] = {
> +	&group,
> +	NULL
> +};
> +
> +static int hwmon_init(struct device *mfd, struct eio_hwmon_dev *eio_hwmon)
> +{
> +	enum _sen_type type;
> +	u8 i, j, data[16];
> +	int sum = 0;
> +	int ret;
> +
> +	for (type = VOLTAGE ; type <= CASEOPEN ; type++) {
> +		int cnt = 1;
> +
> +		for (i = 0 ; i < sen_info[type].max ; i++) {
> +			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) {
> +				dev_info(mfd, "read type id error\n");
> +				continue;
> +			}
> +
> +			for (j = 0 ; j < ARRAY_SIZE(sen_info->item) ; j++) {
> +				struct eio_attr *eio_attr;
> +
> +				if (sen_info[type].item[j][0] == 0)
> +					continue;
> +
> +				eio_attr = &devattrs[sum];
> +
> +				eio_attr->key.type      = type;
> +				eio_attr->key.chan      = i;
> +				eio_attr->key.item      = j;
> +				eio_attr->key.label_idx = data[0];
> +
> +				snprintf(devname[sum], sizeof(devname[sum]),
> +					 "%s%d_%s", sen_info[type].name, cnt,
> +					 sen_info[type].item[j]);
> +
> +				eio_attr->sda.dev_attr.attr.name = devname[sum];
> +				eio_attr->sda.dev_attr.attr.mode = 0444;
> +				eio_attr->sda.dev_attr.show      = eio_show;
> +
> +				attrs[sum] = &eio_attr->sda.dev_attr.attr;
> +
> +				if (++sum >= MAX_DEV)
> +					break;
> +			}
> +			cnt++;
> +		}
> +	}
> +
> +	return sum;
> +}
> +
> +static int hwmon_probe(struct platform_device *pdev)
> +{
> +	struct device *dev =  &pdev->dev;
> +	struct eio_hwmon_dev *eio_hwmon;
> +	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_hwmon = devm_kzalloc(dev, sizeof(*eio_hwmon), GFP_KERNEL);
> +	if (!eio_hwmon)
> +		return -ENOMEM;
> +
> +	eio_hwmon->mfd = dev->parent;
> +	platform_set_drvdata(pdev, eio_hwmon);
> +
> +	if (hwmon_init(dev->parent, eio_hwmon) <= 0)
> +		return -ENODEV;
> +
> +	hwmon =	devm_hwmon_device_register_with_groups(dev, KBUILD_MODNAME,
> +						       eio_hwmon,
> +						       groups);

This API is deprecated. Please rework to use
devm_hwmon_device_register_with_info().

Also, it is highly unusual to have both a hardware monitoring driver
and a thermal driver (instead of instantiating the thermal device
from the hardware monitoring driver). This warrants a detailed
explanation.

Thanks,
Guenter

> +	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

* Re: [PATCH 6/8] Add Advantech EIO Watchdog driver
From: Guenter Roeck @ 2025-12-12 18:43 UTC (permalink / raw)
  To: Ramiro Oliveira
  Cc: 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,
	linux-kernel, 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: <20251212-upstream-v1-v1-6-d50d40ec8d8a@advantech.com>

On Fri, Dec 12, 2025 at 05:40:57PM +0100, Ramiro Oliveira wrote:
> 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 | 672 +++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 681 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index df4b4cc31257..dfdf4f39c14b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -624,6 +624,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
>  
>  ADXL313 THREE-AXIS DIGITAL ACCELEROMETER DRIVER
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index d3b9df7d466b..2f8508e51634 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -248,6 +248,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 OF_GPIO
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index ba52099b1253..59b5ec0246d6 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -230,6 +230,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..a81f005d82d2
> --- /dev/null
> +++ b/drivers/watchdog/eio_wdt.c
> @@ -0,0 +1,672 @@
> +// 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/module.h>
> +#include <linux/reboot.h>
> +#include <linux/uaccess.h>
> +#include <linux/watchdog.h>
> +#include <linux/mfd/eio.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)");
> +
> +/* Specify the IRQ number when the IRQ event is triggered */
> +static int irq;
> +module_param(irq, int, 0);
> +MODULE_PARM_DESC(irq, "The IRQ number for IRQ event");
> +
> +static int timeout;
> +module_param(timeout, int, 0444);
> +MODULE_PARM_DESC(timeout, "Set PMC command timeout value.\n");
> +

Those module parameters are unusual and (for timeout) even misleading.
The "timeout" module parameter in a watchdog driver, if it exists,
is usually the watchdog timeout. This is likely to be confused.
On top of that, expecting the user to know the PMC timeout or an irq
number is just not reasonable, much less giving the user an opportunity
to write such values into the chip.

> +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,
> +		.timeout   = timeout,
> +	};
> +	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,
> +		.timeout   = timeout,
> +	};
> +	return eio_core_pmc_operation(dev, &op);
> +}

Those functions are almost the same. Only CMD_WDT_READ/CMD_WDT_WRITE
is different. Please fold into a single function.

> +
> +static int wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
> +{
> +	struct eio_wdt_dev *eio_wdt = watchdog_get_drvdata(wdd);
> +
> +	wdd->timeout = timeout;
> +	dev_info(eio_wdt->dev, "Set timeout: %u\n", timeout);
> +
> +	return 0;
> +}
> +
> +static int wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int pretimeout)
> +{
> +	struct eio_wdt_dev *eio_wdt = watchdog_get_drvdata(wdd);
> +
> +	wdd->pretimeout = pretimeout;
> +	dev_info(eio_wdt->dev, "Set pretimeout: %u\n", pretimeout);
> +
> +	return 0;
> +}

The above two functions to nothing that isn't already done by the watchdog
core, except to add logging noise. That is not acceptable.

> +
> +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;
> +			}
> +
> +			dev_info(eio_wdt->dev, "Trigger type is %d:%s\n",
> +				 i, type_strs[i]);
> +			eio_wdt->event_type = i;
> +			return 0;
> +		}
> +	}
> +
> +	dev_info(eio_wdt->dev, "Event type: %s\n",
> +		 type_strs[eio_wdt->event_type]);

Drop all that logging noise.

> +	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;
> +
> +	dev_dbg(eio_wdt->dev, "Pretimeout H/W disabled\n");

Is it ? Then why set it to a value != 0 above ?

> +	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 unsigned int wdt_get_timeleft(struct watchdog_device *wdd)
> +{
> +	struct eio_wdt_dev *eio_wdt = watchdog_get_drvdata(wdd);
> +	unsigned int timeleft = 0;
> +
> +	if (eio_wdt->last_time && wdd->timeout) {
> +		unsigned long delta   = jiffies - eio_wdt->last_time;
> +		unsigned int  elapsed = (unsigned int)(delta / HZ);
> +
> +		if (elapsed < wdd->timeout)
> +			timeleft = wdd->timeout - elapsed;
> +	}
> +	return timeleft;

No, that is not what the timeleft function is supposed to do. It is supposed
to read the remaining time from the chip. If the chip does not support reading
the remaining time, do not simulate it in software.

> +}
> +
> +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);
> +	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);
> +
> +	/* Enable WDT */
> +	ret |= regmap_write(map, idx,  IOREG_WDT_STATUS);
> +	ret |= regmap_write(map, data, FLAG_WDT_ENABLED);
> +
> +	/* Set IRQ number */
> +	ret |= regmap_write(map, idx,  IOREG_IRQ);
> +	ret |= regmap_write(map, data, eio_wdt->irq);
> +
> +	/* Lock back */
> +	ret |= regmap_write(map, idx, IOREG_LOCK);

While it may be convenient, it is unacceptable to bundle return codes
this way. The behavior of this code is completely undefined if any
of the accesses above fails.

> +
> +	mutex_unlock(&eio_wdt->core->mutex);
> +
> +	return ret ? -EIO : 0;
> +}
> +
> +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;
> +
> +	if (irq) {
> +		eio_wdt->irq = irq;
> +	} else {
> +		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,
> +	.get_timeleft	= wdt_get_timeleft,
> +	.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,

This would end up requesting an irq provided through the module parameter.
This is completely unacceptable.

> +						wdt_isr, wdt_threaded_isr,
> +						IRQF_SHARED | IRQF_ONESHOT, pdev->name,
> +						eio_wdt);
> +		if (ret) {
> +			dev_err(dev, "IRQ %d request fail:%d. Disabled.\n",
> +				eio_wdt->irq, ret);

You might want to consider using dev_err_probe().

> +			return ret;
> +		}
> +	}
> +
> +	wdd = &eio_wdt->wdd;
> +	wdd->info        = &wdinfo;
> +	wdd->ops         = &wdt_ops;
> +	wdd->parent      = dev;
> +	wdd->min_timeout = 1;
> +	wdd->max_timeout = 0x7FFF;
> +
> +	ret = watchdog_init_timeout(wdd, wdd->timeout, dev);

This is pretty pointless. The function is normally used to overwrite
the default timeout (which is already in wdd->timeout) with a module
parameter or a timeout from devicetree. Calling it with the already
configured timeout does not add any value. Either call it with "0"
as timeout parameter to let it read any timeout from devicetree if
provided, or drop the call.

> +	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

* [PATCH] fbdev: arkfb: Request legacy VGA I/O region
From: Swaraj Gaikwad @ 2025-12-13 15:49 UTC (permalink / raw)
  To: Helge Deller, Andrew Morton, Hans Verkuil, Zi Yan, Frank Li,
	Bjorn Andersson, Laurent Pinchart, Qianfeng Rong, Vivek Kasireddy,
	Konrad Dybcio, Bartosz Golaszewski, Kees Cook,
	open list:FRAMEBUFFER LAYER, open list:FRAMEBUFFER LAYER,
	open list
  Cc: skhan, david.hunter.linux, Swaraj Gaikwad

The arkfb driver uses the legacy VGA I/O range (0x3c0+) but does not
request it. This can cause conflicts with other drivers that try to
reserve these ports.

Fix this by using devm_request_region() during the probe function.
This ensures the region is properly reserved and automatically released
on driver detach.

Signed-off-by: Swaraj Gaikwad <swarajgaikwad1925@gmail.com>
---
Compile-tested only on x86_64.

 drivers/video/fbdev/arkfb.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/video/fbdev/arkfb.c b/drivers/video/fbdev/arkfb.c
index ec084323115f..24e4c20d1a32 100644
--- a/drivers/video/fbdev/arkfb.c
+++ b/drivers/video/fbdev/arkfb.c
@@ -1018,6 +1018,12 @@ static int ark_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)

 	pcibios_bus_to_resource(dev->bus, &vga_res, &bus_reg);

+	if (!devm_request_region(&dev->dev, vga_res.start, 64 * 1024, "arkfb-vga")) {
+		dev_err(info->device, "cannot reserve legacy VGA ports\n");
+		rc = -EBUSY;
+		goto err_find_mode;
+	}
+
 	par->state.vgabase = (void __iomem *) (unsigned long) vga_res.start;

 	/* FIXME get memsize */

base-commit: a859eca0e4cc96f63ff125dbe5388d961558b0e9
--
2.52.0


^ permalink raw reply related


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