xref: /linux/drivers/platform/x86/portwell-ec.c (revision a191224186ec16a4cb1775b2a647ea91f5c139e1)
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 (jesse.huang@portwell.com.tw)
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/pm.h>
34 #include <linux/sizes.h>
35 #include <linux/string.h>
36 #include <linux/watchdog.h>
37 
38 #define PORTWELL_EC_IOSPACE              0xe300
39 #define PORTWELL_EC_IOSPACE_LEN          SZ_256
40 
41 #define PORTWELL_GPIO_PINS               8
42 #define PORTWELL_GPIO_DIR_REG            0x2b
43 #define PORTWELL_GPIO_VAL_REG            0x2c
44 
45 #define PORTWELL_WDT_EC_CONFIG_ADDR      0x06
46 #define PORTWELL_WDT_CONFIG_ENABLE       0x1
47 #define PORTWELL_WDT_CONFIG_DISABLE      0x0
48 #define PORTWELL_WDT_EC_COUNT_MIN_ADDR   0x07
49 #define PORTWELL_WDT_EC_COUNT_SEC_ADDR   0x08
50 #define PORTWELL_WDT_EC_MAX_COUNT_SECOND (255 * 60)
51 
52 #define PORTWELL_EC_FW_VENDOR_ADDRESS    0x4d
53 #define PORTWELL_EC_FW_VENDOR_LENGTH     3
54 #define PORTWELL_EC_FW_VENDOR_NAME       "PWG"
55 
56 static bool force;
57 module_param(force, bool, 0444);
58 MODULE_PARM_DESC(force, "Force loading EC driver without checking DMI boardname");
59 
60 static const struct dmi_system_id pwec_dmi_table[] = {
61 	{
62 		.ident = "NANO-6064 series",
63 		.matches = {
64 			DMI_MATCH(DMI_BOARD_NAME, "NANO-6064"),
65 		},
66 	},
67 	{ }
68 };
69 MODULE_DEVICE_TABLE(dmi, pwec_dmi_table);
70 
71 /* Functions for access EC via IOSPACE */
72 
73 static void pwec_write(u8 index, u8 data)
74 {
75 	outb(data, PORTWELL_EC_IOSPACE + index);
76 }
77 
78 static u8 pwec_read(u8 address)
79 {
80 	return inb(PORTWELL_EC_IOSPACE + address);
81 }
82 
83 /* GPIO functions */
84 
85 static int pwec_gpio_get(struct gpio_chip *chip, unsigned int offset)
86 {
87 	return pwec_read(PORTWELL_GPIO_VAL_REG) & BIT(offset) ? 1 : 0;
88 }
89 
90 static int pwec_gpio_set(struct gpio_chip *chip, unsigned int offset, int val)
91 {
92 	u8 tmp = pwec_read(PORTWELL_GPIO_VAL_REG);
93 
94 	if (val)
95 		tmp |= BIT(offset);
96 	else
97 		tmp &= ~BIT(offset);
98 	pwec_write(PORTWELL_GPIO_VAL_REG, tmp);
99 
100 	return 0;
101 }
102 
103 static int pwec_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
104 {
105 	u8 direction = pwec_read(PORTWELL_GPIO_DIR_REG) & BIT(offset);
106 
107 	if (direction)
108 		return GPIO_LINE_DIRECTION_IN;
109 
110 	return GPIO_LINE_DIRECTION_OUT;
111 }
112 
113 /*
114  * Changing direction causes issues on some boards,
115  * so direction_input and direction_output are disabled for now.
116  */
117 
118 static int pwec_gpio_direction_input(struct gpio_chip *gc, unsigned int offset)
119 {
120 	return -EOPNOTSUPP;
121 }
122 
123 static int pwec_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value)
124 {
125 	return -EOPNOTSUPP;
126 }
127 
128 static struct gpio_chip pwec_gpio_chip = {
129 	.label = "portwell-ec-gpio",
130 	.get_direction = pwec_gpio_get_direction,
131 	.direction_input = pwec_gpio_direction_input,
132 	.direction_output = pwec_gpio_direction_output,
133 	.get = pwec_gpio_get,
134 	.set = pwec_gpio_set,
135 	.base = -1,
136 	.ngpio = PORTWELL_GPIO_PINS,
137 };
138 
139 /* Watchdog functions */
140 
141 static void pwec_wdt_write_timeout(unsigned int timeout)
142 {
143 	pwec_write(PORTWELL_WDT_EC_COUNT_MIN_ADDR, timeout / 60);
144 	pwec_write(PORTWELL_WDT_EC_COUNT_SEC_ADDR, timeout % 60);
145 }
146 
147 static int pwec_wdt_trigger(struct watchdog_device *wdd)
148 {
149 	pwec_wdt_write_timeout(wdd->timeout);
150 	pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_ENABLE);
151 
152 	return 0;
153 }
154 
155 static int pwec_wdt_start(struct watchdog_device *wdd)
156 {
157 	return pwec_wdt_trigger(wdd);
158 }
159 
160 static int pwec_wdt_stop(struct watchdog_device *wdd)
161 {
162 	pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_DISABLE);
163 	return 0;
164 }
165 
166 static int pwec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
167 {
168 	wdd->timeout = timeout;
169 	pwec_wdt_write_timeout(wdd->timeout);
170 
171 	return 0;
172 }
173 
174 /* Ensure consistent min/sec read in case of second rollover. */
175 static unsigned int pwec_wdt_get_timeleft(struct watchdog_device *wdd)
176 {
177 	u8 sec, min, old_min;
178 
179 	do {
180 		old_min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR);
181 		sec = pwec_read(PORTWELL_WDT_EC_COUNT_SEC_ADDR);
182 		min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR);
183 	} while (min != old_min);
184 
185 	return min * 60 + sec;
186 }
187 
188 static const struct watchdog_ops pwec_wdt_ops = {
189 	.owner = THIS_MODULE,
190 	.start = pwec_wdt_start,
191 	.stop = pwec_wdt_stop,
192 	.ping = pwec_wdt_trigger,
193 	.set_timeout = pwec_wdt_set_timeout,
194 	.get_timeleft = pwec_wdt_get_timeleft,
195 };
196 
197 static struct watchdog_device ec_wdt_dev = {
198 	.info = &(struct watchdog_info){
199 		.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
200 		.identity = "Portwell EC watchdog",
201 	},
202 	.ops = &pwec_wdt_ops,
203 	.timeout = 60,
204 	.min_timeout = 1,
205 	.max_timeout = PORTWELL_WDT_EC_MAX_COUNT_SECOND,
206 };
207 
208 static int pwec_firmware_vendor_check(void)
209 {
210 	u8 buf[PORTWELL_EC_FW_VENDOR_LENGTH + 1];
211 	u8 i;
212 
213 	for (i = 0; i < PORTWELL_EC_FW_VENDOR_LENGTH; i++)
214 		buf[i] = pwec_read(PORTWELL_EC_FW_VENDOR_ADDRESS + i);
215 	buf[PORTWELL_EC_FW_VENDOR_LENGTH] = '\0';
216 
217 	return !strcmp(PORTWELL_EC_FW_VENDOR_NAME, buf) ? 0 : -ENODEV;
218 }
219 
220 static int pwec_probe(struct platform_device *pdev)
221 {
222 	int ret;
223 
224 	if (!devm_request_region(&pdev->dev, PORTWELL_EC_IOSPACE,
225 				PORTWELL_EC_IOSPACE_LEN, dev_name(&pdev->dev))) {
226 		dev_err(&pdev->dev, "failed to get IO region\n");
227 		return -EBUSY;
228 	}
229 
230 	ret = pwec_firmware_vendor_check();
231 	if (ret < 0)
232 		return ret;
233 
234 	ret = devm_gpiochip_add_data(&pdev->dev, &pwec_gpio_chip, NULL);
235 	if (ret < 0) {
236 		dev_err(&pdev->dev, "failed to register Portwell EC GPIO\n");
237 		return ret;
238 	}
239 
240 	ec_wdt_dev.parent = &pdev->dev;
241 	return devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev);
242 }
243 
244 static int pwec_suspend(struct device *dev)
245 {
246 	if (watchdog_active(&ec_wdt_dev))
247 		return pwec_wdt_stop(&ec_wdt_dev);
248 
249 	return 0;
250 }
251 
252 static int pwec_resume(struct device *dev)
253 {
254 	if (watchdog_active(&ec_wdt_dev))
255 		return pwec_wdt_start(&ec_wdt_dev);
256 
257 	return 0;
258 }
259 
260 static DEFINE_SIMPLE_DEV_PM_OPS(pwec_dev_pm_ops, pwec_suspend, pwec_resume);
261 
262 static struct platform_driver pwec_driver = {
263 	.driver = {
264 		.name = "portwell-ec",
265 		.pm = pm_sleep_ptr(&pwec_dev_pm_ops),
266 	},
267 	.probe = pwec_probe,
268 };
269 
270 static struct platform_device *pwec_dev;
271 
272 static int __init pwec_init(void)
273 {
274 	int ret;
275 
276 	if (!dmi_check_system(pwec_dmi_table)) {
277 		if (!force)
278 			return -ENODEV;
279 		pr_warn("force load portwell-ec without DMI check\n");
280 	}
281 
282 	ret = platform_driver_register(&pwec_driver);
283 	if (ret)
284 		return ret;
285 
286 	pwec_dev = platform_device_register_simple("portwell-ec", -1, NULL, 0);
287 	if (IS_ERR(pwec_dev)) {
288 		platform_driver_unregister(&pwec_driver);
289 		return PTR_ERR(pwec_dev);
290 	}
291 
292 	return 0;
293 }
294 
295 static void __exit pwec_exit(void)
296 {
297 	platform_device_unregister(pwec_dev);
298 	platform_driver_unregister(&pwec_driver);
299 }
300 
301 module_init(pwec_init);
302 module_exit(pwec_exit);
303 
304 MODULE_AUTHOR("Yen-Chi Huang <jesse.huang@portwell.com.tw>");
305 MODULE_DESCRIPTION("Portwell EC Driver");
306 MODULE_LICENSE("GPL");
307