* 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
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox