55 * Tested on:
66 * - Portwell NANO-6064
77 *
8- * This driver provides support for GPIO and Watchdog Timer
9- * functionalities of the Portwell boards with ITE embedded controller (EC).
8+ * This driver supports Portwell boards with an ITE embedded controller (EC).
109 * The EC is accessed through I/O ports and provides:
10+ * - Temperature and voltage readings (hwmon)
1111 * - 8 GPIO pins for control and monitoring
1212 * - Hardware watchdog with 1-15300 second timeout range
1313 *
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.
14+ * It integrates with the Linux hwmon, GPIO and Watchdog subsystems.
1715 *
1816 * (C) Copyright 2025 Portwell, Inc.
1917 * Author: Yen-Chi Huang (jesse.huang@portwell.com.tw)
2220#define pr_fmt (fmt ) KBUILD_MODNAME ": " fmt
2321
2422#include <linux/acpi.h>
23+ #include <linux/bits.h>
2524#include <linux/bitfield.h>
2625#include <linux/dmi.h>
2726#include <linux/gpio/driver.h>
27+ #include <linux/hwmon.h>
2828#include <linux/init.h>
2929#include <linux/io.h>
3030#include <linux/ioport.h>
3333#include <linux/pm.h>
3434#include <linux/sizes.h>
3535#include <linux/string.h>
36+ #include <linux/units.h>
3637#include <linux/watchdog.h>
3738
3839#define PORTWELL_EC_IOSPACE 0xe300
4243#define PORTWELL_GPIO_DIR_REG 0x2b
4344#define PORTWELL_GPIO_VAL_REG 0x2c
4445
46+ #define PORTWELL_HWMON_TEMP_NUM 3
47+ #define PORTWELL_HWMON_VOLT_NUM 5
48+
4549#define PORTWELL_WDT_EC_CONFIG_ADDR 0x06
4650#define PORTWELL_WDT_CONFIG_ENABLE 0x1
4751#define PORTWELL_WDT_CONFIG_DISABLE 0x0
5357#define PORTWELL_EC_FW_VENDOR_LENGTH 3
5458#define PORTWELL_EC_FW_VENDOR_NAME "PWG"
5559
60+ #define PORTWELL_EC_ADC_MAX 1023
61+
5662static bool force ;
5763module_param (force , bool , 0444 );
5864MODULE_PARM_DESC (force , "Force loading EC driver without checking DMI boardname" );
5965
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+
60107static const struct dmi_system_id pwec_dmi_table [] = {
61108 {
62109 .ident = "NANO-6064 series" ,
63110 .matches = {
64111 DMI_MATCH (DMI_BOARD_NAME , "NANO-6064" ),
65112 },
113+ .driver_data = (void * )& pwec_board_info_nano ,
66114 },
67115 { }
68116};
@@ -80,6 +128,20 @@ static u8 pwec_read(u8 address)
80128 return inb (PORTWELL_EC_IOSPACE + address );
81129}
82130
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+
83145/* GPIO functions */
84146
85147static int pwec_gpio_get (struct gpio_chip * chip , unsigned int offset )
@@ -205,6 +267,81 @@ static struct watchdog_device ec_wdt_dev = {
205267 .max_timeout = PORTWELL_WDT_EC_MAX_COUNT_SECOND ,
206268};
207269
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+
208345static int pwec_firmware_vendor_check (void )
209346{
210347 u8 buf [PORTWELL_EC_FW_VENDOR_LENGTH + 1 ];
@@ -219,6 +356,8 @@ static int pwec_firmware_vendor_check(void)
219356
220357static int pwec_probe (struct platform_device * pdev )
221358{
359+ struct device * hwmon_dev ;
360+ void * drvdata = dev_get_platdata (& pdev -> dev );
222361 int ret ;
223362
224363 if (!devm_request_region (& pdev -> dev , PORTWELL_EC_IOSPACE ,
@@ -237,6 +376,14 @@ static int pwec_probe(struct platform_device *pdev)
237376 return ret ;
238377 }
239378
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+
240387 ec_wdt_dev .parent = & pdev -> dev ;
241388 return devm_watchdog_register_device (& pdev -> dev , & ec_wdt_dev );
242389}
@@ -271,19 +418,26 @@ static struct platform_device *pwec_dev;
271418
272419static int __init pwec_init (void )
273420{
421+ const struct dmi_system_id * match ;
422+ const struct pwec_board_info * hwmon_data ;
274423 int ret ;
275424
276- if (!dmi_check_system (pwec_dmi_table )) {
425+ match = dmi_first_match (pwec_dmi_table );
426+ if (!match ) {
277427 if (!force )
278428 return - ENODEV ;
279- pr_warn ("force load portwell-ec without DMI check\n" );
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 ;
280433 }
281434
282435 ret = platform_driver_register (& pwec_driver );
283436 if (ret )
284437 return ret ;
285438
286- pwec_dev = platform_device_register_simple ("portwell-ec" , -1 , NULL , 0 );
439+ pwec_dev = platform_device_register_data (NULL , "portwell-ec" , PLATFORM_DEVID_NONE ,
440+ hwmon_data , sizeof (* hwmon_data ));
287441 if (IS_ERR (pwec_dev )) {
288442 platform_driver_unregister (& pwec_driver );
289443 return PTR_ERR (pwec_dev );
0 commit comments