Skip to content

Commit 8357967

Browse files
Yen-Chi Huangij-intel
authored andcommitted
platform/x86: portwell-ec: Add GPIO and WDT driver for Portwell EC
Adds a driver for the ITE Embedded Controller (EC) on Portwell boards. It integrates with the Linux GPIO and watchdog subsystems to provide: - Control/monitoring of up to 8 EC GPIO pins. - Hardware watchdog timer with 1-255 second timeouts. The driver communicates with the EC via I/O port 0xe300 and identifies the hardware by the "PWG" firmware signature. This enables enhanced system management for Portwell embedded/industrial platforms. Signed-off-by: Yen-Chi Huang <[email protected]> Reviewed-by: Linus Walleij <[email protected]> Link: https://lore.kernel.org/r/[email protected] Reviewed-by: Ilpo Järvinen <[email protected]> Signed-off-by: Ilpo Järvinen <[email protected]>
1 parent d486002 commit 8357967

File tree

4 files changed

+315
-0
lines changed

4 files changed

+315
-0
lines changed

MAINTAINERS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19216,6 +19216,12 @@ S: Maintained
1921619216
F: drivers/pnp/
1921719217
F: include/linux/pnp.h
1921819218

19219+
PORTWELL EC DRIVER
19220+
M: Yen-Chi Huang <[email protected]>
19221+
19222+
S: Maintained
19223+
F: drivers/platform/x86/portwell-ec.c
19224+
1921919225
POSIX CLOCKS and TIMERS
1922019226
M: Anna-Maria Behnsen <[email protected]>
1922119227
M: Frederic Weisbecker <[email protected]>

drivers/platform/x86/Kconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,21 @@ config PCENGINES_APU2
779779
To compile this driver as a module, choose M here: the module
780780
will be called pcengines-apuv2.
781781

782+
config PORTWELL_EC
783+
tristate "Portwell Embedded Controller driver"
784+
depends on X86 && HAS_IOPORT && WATCHDOG && GPIOLIB
785+
select WATCHDOG_CORE
786+
help
787+
This driver provides support for the GPIO pins and watchdog timer
788+
embedded in Portwell's EC.
789+
790+
Theoretically, this driver should work on multiple Portwell platforms,
791+
but it has only been tested on the Portwell NANO-6064 board.
792+
If you encounter any issues on other boards, please report them.
793+
794+
To compile this driver as a module, choose M here: the module
795+
will be called portwell-ec.
796+
782797
config BARCO_P50_GPIO
783798
tristate "Barco P50 GPIO driver for identify LED/button"
784799
depends on GPIOLIB

drivers/platform/x86/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o
9292
# PC Engines
9393
obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o
9494

95+
# Portwell
96+
obj-$(CONFIG_PORTWELL_EC) += portwell-ec.o
97+
9598
# Barco
9699
obj-$(CONFIG_BARCO_P50_GPIO) += barco-p50-gpio.o
97100

drivers/platform/x86/portwell-ec.c

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* portwell-ec.c: Portwell embedded controller driver.
4+
*
5+
* Tested on:
6+
* - Portwell NANO-6064
7+
*
8+
* This driver provides support for GPIO and Watchdog Timer
9+
* functionalities of the Portwell boards with ITE embedded controller (EC).
10+
* The EC is accessed through I/O ports and provides:
11+
* - 8 GPIO pins for control and monitoring
12+
* - Hardware watchdog with 1-15300 second timeout range
13+
*
14+
* It integrates with the Linux GPIO and Watchdog subsystems, allowing
15+
* userspace interaction with EC GPIO pins and watchdog control,
16+
* ensuring system stability and configurability.
17+
*
18+
* (C) Copyright 2025 Portwell, Inc.
19+
* Author: Yen-Chi Huang ([email protected])
20+
*/
21+
22+
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
23+
24+
#include <linux/acpi.h>
25+
#include <linux/bitfield.h>
26+
#include <linux/dmi.h>
27+
#include <linux/gpio/driver.h>
28+
#include <linux/init.h>
29+
#include <linux/io.h>
30+
#include <linux/ioport.h>
31+
#include <linux/module.h>
32+
#include <linux/platform_device.h>
33+
#include <linux/sizes.h>
34+
#include <linux/string.h>
35+
#include <linux/watchdog.h>
36+
37+
#define PORTWELL_EC_IOSPACE 0xe300
38+
#define PORTWELL_EC_IOSPACE_LEN SZ_256
39+
40+
#define PORTWELL_GPIO_PINS 8
41+
#define PORTWELL_GPIO_DIR_REG 0x2b
42+
#define PORTWELL_GPIO_VAL_REG 0x2c
43+
44+
#define PORTWELL_WDT_EC_CONFIG_ADDR 0x06
45+
#define PORTWELL_WDT_CONFIG_ENABLE 0x1
46+
#define PORTWELL_WDT_CONFIG_DISABLE 0x0
47+
#define PORTWELL_WDT_EC_COUNT_MIN_ADDR 0x07
48+
#define PORTWELL_WDT_EC_COUNT_SEC_ADDR 0x08
49+
#define PORTWELL_WDT_EC_MAX_COUNT_SECOND (255 * 60)
50+
51+
#define PORTWELL_EC_FW_VENDOR_ADDRESS 0x4d
52+
#define PORTWELL_EC_FW_VENDOR_LENGTH 3
53+
#define PORTWELL_EC_FW_VENDOR_NAME "PWG"
54+
55+
static bool force;
56+
module_param(force, bool, 0444);
57+
MODULE_PARM_DESC(force, "Force loading EC driver without checking DMI boardname");
58+
59+
static const struct dmi_system_id pwec_dmi_table[] = {
60+
{
61+
.ident = "NANO-6064 series",
62+
.matches = {
63+
DMI_MATCH(DMI_BOARD_NAME, "NANO-6064"),
64+
},
65+
},
66+
{ }
67+
};
68+
MODULE_DEVICE_TABLE(dmi, pwec_dmi_table);
69+
70+
/* Functions for access EC via IOSPACE */
71+
72+
static void pwec_write(u8 index, u8 data)
73+
{
74+
outb(data, PORTWELL_EC_IOSPACE + index);
75+
}
76+
77+
static u8 pwec_read(u8 address)
78+
{
79+
return inb(PORTWELL_EC_IOSPACE + address);
80+
}
81+
82+
/* GPIO functions */
83+
84+
static int pwec_gpio_get(struct gpio_chip *chip, unsigned int offset)
85+
{
86+
return pwec_read(PORTWELL_GPIO_VAL_REG) & BIT(offset) ? 1 : 0;
87+
}
88+
89+
static int pwec_gpio_set_rv(struct gpio_chip *chip, unsigned int offset, int val)
90+
{
91+
u8 tmp = pwec_read(PORTWELL_GPIO_VAL_REG);
92+
93+
if (val)
94+
tmp |= BIT(offset);
95+
else
96+
tmp &= ~BIT(offset);
97+
pwec_write(PORTWELL_GPIO_VAL_REG, tmp);
98+
99+
return 0;
100+
}
101+
102+
static int pwec_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
103+
{
104+
u8 direction = pwec_read(PORTWELL_GPIO_DIR_REG) & BIT(offset);
105+
106+
if (direction)
107+
return GPIO_LINE_DIRECTION_IN;
108+
109+
return GPIO_LINE_DIRECTION_OUT;
110+
}
111+
112+
/*
113+
* Changing direction causes issues on some boards,
114+
* so direction_input and direction_output are disabled for now.
115+
*/
116+
117+
static int pwec_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
118+
{
119+
return -EOPNOTSUPP;
120+
}
121+
122+
static int pwec_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value)
123+
{
124+
return -EOPNOTSUPP;
125+
}
126+
127+
static struct gpio_chip pwec_gpio_chip = {
128+
.label = "portwell-ec-gpio",
129+
.get_direction = pwec_gpio_get_direction,
130+
.direction_input = pwec_gpio_direction_input,
131+
.direction_output = pwec_gpio_direction_output,
132+
.get = pwec_gpio_get,
133+
.set_rv = pwec_gpio_set_rv,
134+
.base = -1,
135+
.ngpio = PORTWELL_GPIO_PINS,
136+
};
137+
138+
/* Watchdog functions */
139+
140+
static void pwec_wdt_write_timeout(unsigned int timeout)
141+
{
142+
pwec_write(PORTWELL_WDT_EC_COUNT_MIN_ADDR, timeout / 60);
143+
pwec_write(PORTWELL_WDT_EC_COUNT_SEC_ADDR, timeout % 60);
144+
}
145+
146+
static int pwec_wdt_trigger(struct watchdog_device *wdd)
147+
{
148+
pwec_wdt_write_timeout(wdd->timeout);
149+
pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_ENABLE);
150+
151+
return 0;
152+
}
153+
154+
static int pwec_wdt_start(struct watchdog_device *wdd)
155+
{
156+
return pwec_wdt_trigger(wdd);
157+
}
158+
159+
static int pwec_wdt_stop(struct watchdog_device *wdd)
160+
{
161+
pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_DISABLE);
162+
return 0;
163+
}
164+
165+
static int pwec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
166+
{
167+
wdd->timeout = timeout;
168+
pwec_wdt_write_timeout(wdd->timeout);
169+
170+
return 0;
171+
}
172+
173+
/* Ensure consistent min/sec read in case of second rollover. */
174+
static unsigned int pwec_wdt_get_timeleft(struct watchdog_device *wdd)
175+
{
176+
u8 sec, min, old_min;
177+
178+
do {
179+
old_min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR);
180+
sec = pwec_read(PORTWELL_WDT_EC_COUNT_SEC_ADDR);
181+
min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR);
182+
} while (min != old_min);
183+
184+
return min * 60 + sec;
185+
}
186+
187+
static const struct watchdog_ops pwec_wdt_ops = {
188+
.owner = THIS_MODULE,
189+
.start = pwec_wdt_start,
190+
.stop = pwec_wdt_stop,
191+
.ping = pwec_wdt_trigger,
192+
.set_timeout = pwec_wdt_set_timeout,
193+
.get_timeleft = pwec_wdt_get_timeleft,
194+
};
195+
196+
static struct watchdog_device ec_wdt_dev = {
197+
.info = &(struct watchdog_info){
198+
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
199+
.identity = "Portwell EC watchdog",
200+
},
201+
.ops = &pwec_wdt_ops,
202+
.timeout = 60,
203+
.min_timeout = 1,
204+
.max_timeout = PORTWELL_WDT_EC_MAX_COUNT_SECOND,
205+
};
206+
207+
static int pwec_firmware_vendor_check(void)
208+
{
209+
u8 buf[PORTWELL_EC_FW_VENDOR_LENGTH + 1];
210+
u8 i;
211+
212+
for (i = 0; i < PORTWELL_EC_FW_VENDOR_LENGTH; i++)
213+
buf[i] = pwec_read(PORTWELL_EC_FW_VENDOR_ADDRESS + i);
214+
buf[PORTWELL_EC_FW_VENDOR_LENGTH] = '\0';
215+
216+
return !strcmp(PORTWELL_EC_FW_VENDOR_NAME, buf) ? 0 : -ENODEV;
217+
}
218+
219+
static int pwec_probe(struct platform_device *pdev)
220+
{
221+
int ret;
222+
223+
if (!devm_request_region(&pdev->dev, PORTWELL_EC_IOSPACE,
224+
PORTWELL_EC_IOSPACE_LEN, dev_name(&pdev->dev))) {
225+
dev_err(&pdev->dev, "failed to get IO region\n");
226+
return -EBUSY;
227+
}
228+
229+
ret = pwec_firmware_vendor_check();
230+
if (ret < 0)
231+
return ret;
232+
233+
ret = devm_gpiochip_add_data(&pdev->dev, &pwec_gpio_chip, NULL);
234+
if (ret < 0) {
235+
dev_err(&pdev->dev, "failed to register Portwell EC GPIO\n");
236+
return ret;
237+
}
238+
239+
ret = devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev);
240+
if (ret < 0) {
241+
dev_err(&pdev->dev, "failed to register Portwell EC Watchdog\n");
242+
return ret;
243+
}
244+
245+
return 0;
246+
}
247+
248+
static struct platform_driver pwec_driver = {
249+
.driver = {
250+
.name = "portwell-ec",
251+
},
252+
.probe = pwec_probe,
253+
};
254+
255+
static struct platform_device *pwec_dev;
256+
257+
static int __init pwec_init(void)
258+
{
259+
int ret;
260+
261+
if (!dmi_check_system(pwec_dmi_table)) {
262+
if (!force)
263+
return -ENODEV;
264+
pr_warn("force load portwell-ec without DMI check\n");
265+
}
266+
267+
ret = platform_driver_register(&pwec_driver);
268+
if (ret)
269+
return ret;
270+
271+
pwec_dev = platform_device_register_simple("portwell-ec", -1, NULL, 0);
272+
if (IS_ERR(pwec_dev)) {
273+
platform_driver_unregister(&pwec_driver);
274+
return PTR_ERR(pwec_dev);
275+
}
276+
277+
return 0;
278+
}
279+
280+
static void __exit pwec_exit(void)
281+
{
282+
platform_device_unregister(pwec_dev);
283+
platform_driver_unregister(&pwec_driver);
284+
}
285+
286+
module_init(pwec_init);
287+
module_exit(pwec_exit);
288+
289+
MODULE_AUTHOR("Yen-Chi Huang <[email protected]>");
290+
MODULE_DESCRIPTION("Portwell EC Driver");
291+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)