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 supports Portwell boards with an ITE embedded controller (EC). 9 * The EC is accessed through I/O ports and provides: 10 * - Temperature and voltage readings (hwmon) 11 * - 8 GPIO pins for control and monitoring 12 * - Hardware watchdog with 1-15300 second timeout range 13 * 14 * It integrates with the Linux hwmon, GPIO and Watchdog subsystems. 15 * 16 * (C) Copyright 2025 Portwell, Inc. 17 * Author: Yen-Chi Huang (jesse.huang@portwell.com.tw) 18 */ 19 20 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 21 22 #include <linux/acpi.h> 23 #include <linux/bits.h> 24 #include <linux/bitfield.h> 25 #include <linux/dmi.h> 26 #include <linux/gpio/driver.h> 27 #include <linux/hwmon.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/units.h> 37 #include <linux/watchdog.h> 38 39 #define PORTWELL_EC_IOSPACE 0xe300 40 #define PORTWELL_EC_IOSPACE_LEN SZ_256 41 42 #define PORTWELL_GPIO_PINS 8 43 #define PORTWELL_GPIO_DIR_REG 0x2b 44 #define PORTWELL_GPIO_VAL_REG 0x2c 45 46 #define PORTWELL_HWMON_TEMP_NUM 3 47 #define PORTWELL_HWMON_VOLT_NUM 5 48 49 #define PORTWELL_WDT_EC_CONFIG_ADDR 0x06 50 #define PORTWELL_WDT_CONFIG_ENABLE 0x1 51 #define PORTWELL_WDT_CONFIG_DISABLE 0x0 52 #define PORTWELL_WDT_EC_COUNT_MIN_ADDR 0x07 53 #define PORTWELL_WDT_EC_COUNT_SEC_ADDR 0x08 54 #define PORTWELL_WDT_EC_MAX_COUNT_SECOND (255 * 60) 55 56 #define PORTWELL_EC_FW_VENDOR_ADDRESS 0x4d 57 #define PORTWELL_EC_FW_VENDOR_LENGTH 3 58 #define PORTWELL_EC_FW_VENDOR_NAME "PWG" 59 60 #define PORTWELL_EC_ADC_MAX 1023 61 62 static bool force; 63 module_param(force, bool, 0444); 64 MODULE_PARM_DESC(force, "Force loading EC driver without checking DMI boardname"); 65 66 /* A sensor's metadata (label, scale, and register) */ 67 struct pwec_sensor_prop { 68 const char *label; 69 u8 reg; 70 u32 scale; 71 }; 72 73 /* Master configuration with properties for all possible sensors */ 74 static const struct { 75 const struct pwec_sensor_prop temp_props[PORTWELL_HWMON_TEMP_NUM]; 76 const struct pwec_sensor_prop in_props[PORTWELL_HWMON_VOLT_NUM]; 77 } pwec_master_data = { 78 .temp_props = { 79 { "CPU Temperature", 0x00, 0 }, 80 { "System Temperature", 0x02, 0 }, 81 { "Aux Temperature", 0x04, 0 }, 82 }, 83 .in_props = { 84 { "Vcore", 0x20, 3000 }, 85 { "3.3V", 0x22, 6000 }, 86 { "5V", 0x24, 9600 }, 87 { "12V", 0x30, 19800 }, 88 { "VDIMM", 0x32, 3000 }, 89 }, 90 }; 91 92 struct pwec_board_info { 93 u32 temp_mask; /* bit N = temperature channel N */ 94 u32 in_mask; /* bit N = voltage channel N */ 95 }; 96 97 static const struct pwec_board_info pwec_board_info_default = { 98 .temp_mask = GENMASK(PORTWELL_HWMON_TEMP_NUM - 1, 0), 99 .in_mask = GENMASK(PORTWELL_HWMON_VOLT_NUM - 1, 0), 100 }; 101 102 static const struct pwec_board_info pwec_board_info_nano = { 103 .temp_mask = BIT(0) | BIT(1), 104 .in_mask = GENMASK(4, 0), 105 }; 106 107 static const struct dmi_system_id pwec_dmi_table[] = { 108 { 109 .ident = "NANO-6064 series", 110 .matches = { 111 DMI_MATCH(DMI_BOARD_NAME, "NANO-6064"), 112 }, 113 .driver_data = (void *)&pwec_board_info_nano, 114 }, 115 { } 116 }; 117 MODULE_DEVICE_TABLE(dmi, pwec_dmi_table); 118 119 /* Functions for access EC via IOSPACE */ 120 121 static void pwec_write(u8 index, u8 data) 122 { 123 outb(data, PORTWELL_EC_IOSPACE + index); 124 } 125 126 static u8 pwec_read(u8 address) 127 { 128 return inb(PORTWELL_EC_IOSPACE + address); 129 } 130 131 /* Ensure consistent 16-bit read across potential MSB rollover. */ 132 static u16 pwec_read16_stable(u8 lsb_reg) 133 { 134 u8 lsb, msb, old_msb; 135 136 do { 137 old_msb = pwec_read(lsb_reg + 1); 138 lsb = pwec_read(lsb_reg); 139 msb = pwec_read(lsb_reg + 1); 140 } while (msb != old_msb); 141 142 return (msb << 8) | lsb; 143 } 144 145 /* GPIO functions */ 146 147 static int pwec_gpio_get(struct gpio_chip *chip, unsigned int offset) 148 { 149 return pwec_read(PORTWELL_GPIO_VAL_REG) & BIT(offset) ? 1 : 0; 150 } 151 152 static int pwec_gpio_set(struct gpio_chip *chip, unsigned int offset, int val) 153 { 154 u8 tmp = pwec_read(PORTWELL_GPIO_VAL_REG); 155 156 if (val) 157 tmp |= BIT(offset); 158 else 159 tmp &= ~BIT(offset); 160 pwec_write(PORTWELL_GPIO_VAL_REG, tmp); 161 162 return 0; 163 } 164 165 static int pwec_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) 166 { 167 u8 direction = pwec_read(PORTWELL_GPIO_DIR_REG) & BIT(offset); 168 169 if (direction) 170 return GPIO_LINE_DIRECTION_IN; 171 172 return GPIO_LINE_DIRECTION_OUT; 173 } 174 175 /* 176 * Changing direction causes issues on some boards, 177 * so direction_input and direction_output are disabled for now. 178 */ 179 180 static int pwec_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) 181 { 182 return -EOPNOTSUPP; 183 } 184 185 static int pwec_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value) 186 { 187 return -EOPNOTSUPP; 188 } 189 190 static struct gpio_chip pwec_gpio_chip = { 191 .label = "portwell-ec-gpio", 192 .get_direction = pwec_gpio_get_direction, 193 .direction_input = pwec_gpio_direction_input, 194 .direction_output = pwec_gpio_direction_output, 195 .get = pwec_gpio_get, 196 .set = pwec_gpio_set, 197 .base = -1, 198 .ngpio = PORTWELL_GPIO_PINS, 199 }; 200 201 /* Watchdog functions */ 202 203 static void pwec_wdt_write_timeout(unsigned int timeout) 204 { 205 pwec_write(PORTWELL_WDT_EC_COUNT_MIN_ADDR, timeout / 60); 206 pwec_write(PORTWELL_WDT_EC_COUNT_SEC_ADDR, timeout % 60); 207 } 208 209 static int pwec_wdt_trigger(struct watchdog_device *wdd) 210 { 211 pwec_wdt_write_timeout(wdd->timeout); 212 pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_ENABLE); 213 214 return 0; 215 } 216 217 static int pwec_wdt_start(struct watchdog_device *wdd) 218 { 219 return pwec_wdt_trigger(wdd); 220 } 221 222 static int pwec_wdt_stop(struct watchdog_device *wdd) 223 { 224 pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_DISABLE); 225 return 0; 226 } 227 228 static int pwec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) 229 { 230 wdd->timeout = timeout; 231 pwec_wdt_write_timeout(wdd->timeout); 232 233 return 0; 234 } 235 236 /* Ensure consistent min/sec read in case of second rollover. */ 237 static unsigned int pwec_wdt_get_timeleft(struct watchdog_device *wdd) 238 { 239 u8 sec, min, old_min; 240 241 do { 242 old_min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR); 243 sec = pwec_read(PORTWELL_WDT_EC_COUNT_SEC_ADDR); 244 min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR); 245 } while (min != old_min); 246 247 return min * 60 + sec; 248 } 249 250 static const struct watchdog_ops pwec_wdt_ops = { 251 .owner = THIS_MODULE, 252 .start = pwec_wdt_start, 253 .stop = pwec_wdt_stop, 254 .ping = pwec_wdt_trigger, 255 .set_timeout = pwec_wdt_set_timeout, 256 .get_timeleft = pwec_wdt_get_timeleft, 257 }; 258 259 static struct watchdog_device ec_wdt_dev = { 260 .info = &(struct watchdog_info){ 261 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 262 .identity = "Portwell EC watchdog", 263 }, 264 .ops = &pwec_wdt_ops, 265 .timeout = 60, 266 .min_timeout = 1, 267 .max_timeout = PORTWELL_WDT_EC_MAX_COUNT_SECOND, 268 }; 269 270 /* HWMON functions */ 271 272 static umode_t pwec_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, 273 u32 attr, int channel) 274 { 275 const struct pwec_board_info *info = drvdata; 276 277 switch (type) { 278 case hwmon_temp: 279 return (info->temp_mask & BIT(channel)) ? 0444 : 0; 280 case hwmon_in: 281 return (info->in_mask & BIT(channel)) ? 0444 : 0; 282 default: 283 return 0; 284 } 285 } 286 287 static int pwec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 288 u32 attr, int channel, long *val) 289 { 290 u16 tmp16; 291 292 switch (type) { 293 case hwmon_temp: 294 *val = pwec_read(pwec_master_data.temp_props[channel].reg) * MILLIDEGREE_PER_DEGREE; 295 return 0; 296 case hwmon_in: 297 tmp16 = pwec_read16_stable(pwec_master_data.in_props[channel].reg); 298 *val = (tmp16 * pwec_master_data.in_props[channel].scale) / PORTWELL_EC_ADC_MAX; 299 return 0; 300 default: 301 return -EOPNOTSUPP; 302 } 303 } 304 305 static int pwec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, 306 u32 attr, int channel, const char **str) 307 { 308 switch (type) { 309 case hwmon_temp: 310 *str = pwec_master_data.temp_props[channel].label; 311 return 0; 312 case hwmon_in: 313 *str = pwec_master_data.in_props[channel].label; 314 return 0; 315 default: 316 return -EOPNOTSUPP; 317 } 318 } 319 320 static const struct hwmon_channel_info *pwec_hwmon_info[] = { 321 HWMON_CHANNEL_INFO(temp, 322 HWMON_T_INPUT | HWMON_T_LABEL, 323 HWMON_T_INPUT | HWMON_T_LABEL, 324 HWMON_T_INPUT | HWMON_T_LABEL), 325 HWMON_CHANNEL_INFO(in, 326 HWMON_I_INPUT | HWMON_I_LABEL, 327 HWMON_I_INPUT | HWMON_I_LABEL, 328 HWMON_I_INPUT | HWMON_I_LABEL, 329 HWMON_I_INPUT | HWMON_I_LABEL, 330 HWMON_I_INPUT | HWMON_I_LABEL), 331 NULL 332 }; 333 334 static const struct hwmon_ops pwec_hwmon_ops = { 335 .is_visible = pwec_hwmon_is_visible, 336 .read = pwec_hwmon_read, 337 .read_string = pwec_hwmon_read_string, 338 }; 339 340 static const struct hwmon_chip_info pwec_chip_info = { 341 .ops = &pwec_hwmon_ops, 342 .info = pwec_hwmon_info, 343 }; 344 345 static int pwec_firmware_vendor_check(void) 346 { 347 u8 buf[PORTWELL_EC_FW_VENDOR_LENGTH + 1]; 348 u8 i; 349 350 for (i = 0; i < PORTWELL_EC_FW_VENDOR_LENGTH; i++) 351 buf[i] = pwec_read(PORTWELL_EC_FW_VENDOR_ADDRESS + i); 352 buf[PORTWELL_EC_FW_VENDOR_LENGTH] = '\0'; 353 354 return !strcmp(PORTWELL_EC_FW_VENDOR_NAME, buf) ? 0 : -ENODEV; 355 } 356 357 static int pwec_probe(struct platform_device *pdev) 358 { 359 struct device *hwmon_dev; 360 void *drvdata = dev_get_platdata(&pdev->dev); 361 int ret; 362 363 if (!devm_request_region(&pdev->dev, PORTWELL_EC_IOSPACE, 364 PORTWELL_EC_IOSPACE_LEN, dev_name(&pdev->dev))) { 365 dev_err(&pdev->dev, "failed to get IO region\n"); 366 return -EBUSY; 367 } 368 369 ret = pwec_firmware_vendor_check(); 370 if (ret < 0) 371 return ret; 372 373 ret = devm_gpiochip_add_data(&pdev->dev, &pwec_gpio_chip, NULL); 374 if (ret < 0) { 375 dev_err(&pdev->dev, "failed to register Portwell EC GPIO\n"); 376 return ret; 377 } 378 379 if (IS_REACHABLE(CONFIG_HWMON)) { 380 hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, 381 "portwell_ec", drvdata, &pwec_chip_info, NULL); 382 ret = PTR_ERR_OR_ZERO(hwmon_dev); 383 if (ret) 384 return ret; 385 } 386 387 ec_wdt_dev.parent = &pdev->dev; 388 return devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev); 389 } 390 391 static int pwec_suspend(struct device *dev) 392 { 393 if (watchdog_active(&ec_wdt_dev)) 394 return pwec_wdt_stop(&ec_wdt_dev); 395 396 return 0; 397 } 398 399 static int pwec_resume(struct device *dev) 400 { 401 if (watchdog_active(&ec_wdt_dev)) 402 return pwec_wdt_start(&ec_wdt_dev); 403 404 return 0; 405 } 406 407 static DEFINE_SIMPLE_DEV_PM_OPS(pwec_dev_pm_ops, pwec_suspend, pwec_resume); 408 409 static struct platform_driver pwec_driver = { 410 .driver = { 411 .name = "portwell-ec", 412 .pm = pm_sleep_ptr(&pwec_dev_pm_ops), 413 }, 414 .probe = pwec_probe, 415 }; 416 417 static struct platform_device *pwec_dev; 418 419 static int __init pwec_init(void) 420 { 421 const struct dmi_system_id *match; 422 const struct pwec_board_info *hwmon_data; 423 int ret; 424 425 match = dmi_first_match(pwec_dmi_table); 426 if (!match) { 427 if (!force) 428 return -ENODEV; 429 hwmon_data = &pwec_board_info_default; 430 pr_warn("force load portwell-ec without DMI check, using full display config\n"); 431 } else { 432 hwmon_data = match->driver_data; 433 } 434 435 ret = platform_driver_register(&pwec_driver); 436 if (ret) 437 return ret; 438 439 pwec_dev = platform_device_register_data(NULL, "portwell-ec", PLATFORM_DEVID_NONE, 440 hwmon_data, sizeof(*hwmon_data)); 441 if (IS_ERR(pwec_dev)) { 442 platform_driver_unregister(&pwec_driver); 443 return PTR_ERR(pwec_dev); 444 } 445 446 return 0; 447 } 448 449 static void __exit pwec_exit(void) 450 { 451 platform_device_unregister(pwec_dev); 452 platform_driver_unregister(&pwec_driver); 453 } 454 455 module_init(pwec_init); 456 module_exit(pwec_exit); 457 458 MODULE_AUTHOR("Yen-Chi Huang <jesse.huang@portwell.com.tw>"); 459 MODULE_DESCRIPTION("Portwell EC Driver"); 460 MODULE_LICENSE("GPL"); 461