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/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 <jesse.huang@portwell.com.tw>");
290 MODULE_DESCRIPTION("Portwell EC Driver");
291 MODULE_LICENSE("GPL");
292