FILE
portwell-ec.c
File preview with syntax highlighting.
linux-6.19.14/drivers/platform/x86/portwell-ec.c
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 62static bool force; 63module_param(force, bool, 0444); 64MODULE_PARM_DESC(force, "Force loading EC driver without checking DMI boardname"); 65 66/* A sensor's metadata (label, scale, and register) */ 67struct pwec_sensor_prop { 68 const char *label; 69 u8 reg; 70 u32 scale; 71}; 72 73/* Master configuration with properties for all possible sensors */ 74static 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 92struct pwec_board_info { 93 u32 temp_mask; /* bit N = temperature channel N */ 94 u32 in_mask; /* bit N = voltage channel N */ 95}; 96 97static 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 102static const struct pwec_board_info pwec_board_info_nano = { 103 .temp_mask = BIT(0) | BIT(1), 104 .in_mask = GENMASK(4, 0), 105}; 106 107static 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}; 117MODULE_DEVICE_TABLE(dmi, pwec_dmi_table); 118 119/* Functions for access EC via IOSPACE */ 120 121static void pwec_write(u8 index, u8 data) 122{ 123 outb(data, PORTWELL_EC_IOSPACE + index); 124} 125 126static 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. */ 132static 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 147static 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 152static 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 165static 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 180static int pwec_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) 181{ 182 return -EOPNOTSUPP; 183} 184 185static int pwec_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value) 186{ 187 return -EOPNOTSUPP; 188} 189 190static 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 203static 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 209static 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 217static int pwec_wdt_start(struct watchdog_device *wdd) 218{ 219 return pwec_wdt_trigger(wdd); 220} 221 222static 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 228static 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. */ 237static 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 250static 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 259static 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 272static 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 287static 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 305static 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 320static 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 334static 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 340static const struct hwmon_chip_info pwec_chip_info = { 341 .ops = &pwec_hwmon_ops, 342 .info = pwec_hwmon_info, 343}; 344 345static 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 357static 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 391static 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 399static 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 407static DEFINE_SIMPLE_DEV_PM_OPS(pwec_dev_pm_ops, pwec_suspend, pwec_resume); 408 409static 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 417static struct platform_device *pwec_dev; 418 419static 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 449static void __exit pwec_exit(void) 450{ 451 platform_device_unregister(pwec_dev); 452 platform_driver_unregister(&pwec_driver); 453} 454 455module_init(pwec_init); 456module_exit(pwec_exit); 457 458MODULE_AUTHOR("Yen-Chi Huang <jesse.huang@portwell.com.tw>"); 459MODULE_DESCRIPTION("Portwell EC Driver"); 460MODULE_LICENSE("GPL");
~/
Read-only file preview.