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 ret = devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev); 242 if (ret < 0) { 243 dev_err(&pdev->dev, "failed to register Portwell EC Watchdog\n"); 244 return ret; 245 } 246 247 return 0; 248 } 249 250 static int pwec_suspend(struct device *dev) 251 { 252 if (watchdog_active(&ec_wdt_dev)) 253 return pwec_wdt_stop(&ec_wdt_dev); 254 255 return 0; 256 } 257 258 static int pwec_resume(struct device *dev) 259 { 260 if (watchdog_active(&ec_wdt_dev)) 261 return pwec_wdt_start(&ec_wdt_dev); 262 263 return 0; 264 } 265 266 static DEFINE_SIMPLE_DEV_PM_OPS(pwec_dev_pm_ops, pwec_suspend, pwec_resume); 267 268 static struct platform_driver pwec_driver = { 269 .driver = { 270 .name = "portwell-ec", 271 .pm = pm_sleep_ptr(&pwec_dev_pm_ops), 272 }, 273 .probe = pwec_probe, 274 }; 275 276 static struct platform_device *pwec_dev; 277 278 static int __init pwec_init(void) 279 { 280 int ret; 281 282 if (!dmi_check_system(pwec_dmi_table)) { 283 if (!force) 284 return -ENODEV; 285 pr_warn("force load portwell-ec without DMI check\n"); 286 } 287 288 ret = platform_driver_register(&pwec_driver); 289 if (ret) 290 return ret; 291 292 pwec_dev = platform_device_register_simple("portwell-ec", -1, NULL, 0); 293 if (IS_ERR(pwec_dev)) { 294 platform_driver_unregister(&pwec_driver); 295 return PTR_ERR(pwec_dev); 296 } 297 298 return 0; 299 } 300 301 static void __exit pwec_exit(void) 302 { 303 platform_device_unregister(pwec_dev); 304 platform_driver_unregister(&pwec_driver); 305 } 306 307 module_init(pwec_init); 308 module_exit(pwec_exit); 309 310 MODULE_AUTHOR("Yen-Chi Huang <jesse.huang@portwell.com.tw>"); 311 MODULE_DESCRIPTION("Portwell EC Driver"); 312 MODULE_LICENSE("GPL"); 313