* [PATCH v5 0/2] hwmon: add GPD devices sensor driver
@ 2025-02-11 7:01 Cryolitia PukNgae via B4 Relay
2025-02-11 7:01 ` [PATCH v5 1/2] " Cryolitia PukNgae via B4 Relay
2025-02-11 7:01 ` [PATCH v5 2/2] hwmon: document: add gpd-fan Cryolitia PukNgae via B4 Relay
0 siblings, 2 replies; 7+ messages in thread
From: Cryolitia PukNgae via B4 Relay @ 2025-02-11 7:01 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck, Cryolitia PukNgae, Jonathan Corbet
Cc: linux-kernel, linux-hwmon, linux-doc, Celeste Liu, Yao Zi,
Derek John Clark, Marcin Strągowski, someone5678,
Justin Weiss
Sensors driver for GPD Handhelds that expose fan reading and control via
hwmon sysfs.
Shenzhen GPD Technology Co., Ltd. manufactures a series of handheld
devices. This driver implements these functions through x86 port-mapped IO.
Tested-by: Marcin Strągowski <marcin@stragowski.com>
Tested-by: someone5678 <someone5678.dev@gmail.com>
Tested-by: Justin Weiss <justin@justinweiss.com>
Signed-off-by: Cryolitia PukNgae <Cryolitia@gmail.com>
---
Changes in v5:
- Rebase on kernel 6.13
- Remove all value-cache related code
- Clean up code
- Link to v4: https://lore.kernel.org/r/20240718-gpd_fan-v4-0-116e5431a9fe@gmail.com
Changes in v4:
- Apply suggest by Krzysztof Kozlowski, thanks!
- Link to v3: https://lore.kernel.org/r/20240717-gpd_fan-v3-0-8d7efb1263b7@gmail.com
Changes in v3:
- Re-arrange code, thanks to Krzysztof Kozlowski, Guenter Roeck, Yao Zi!
- Link to v2: https://lore.kernel.org/r/20240717-gpd_fan-v2-0-f7b7e6b9f21b@gmail.com
Changes in v2:
- Improved documentation, thanks to Randy Dunlap!
- Link to v1: https://lore.kernel.org/r/20240716-gpd_fan-v1-0-34051dd71a06@gmail.com
---
Cryolitia PukNgae (2):
hwmon: add GPD devices sensor driver
hwmon: document: add gpd-fan
Documentation/hwmon/gpd-fan.rst | 63 +++++
Documentation/hwmon/index.rst | 1 +
MAINTAINERS | 7 +
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/gpd-fan.c | 611 ++++++++++++++++++++++++++++++++++++++++
6 files changed, 693 insertions(+)
---
base-commit: ffd294d346d185b70e28b1a28abe367bbfe53c04
change-id: 20240716-gpd_fan-57f30923c884
Best regards,
--
Cryolitia PukNgae <Cryolitia@gmail.com>
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v5 1/2] hwmon: add GPD devices sensor driver 2025-02-11 7:01 [PATCH v5 0/2] hwmon: add GPD devices sensor driver Cryolitia PukNgae via B4 Relay @ 2025-02-11 7:01 ` Cryolitia PukNgae via B4 Relay 2025-02-13 19:32 ` kernel test robot ` (2 more replies) 2025-02-11 7:01 ` [PATCH v5 2/2] hwmon: document: add gpd-fan Cryolitia PukNgae via B4 Relay 1 sibling, 3 replies; 7+ messages in thread From: Cryolitia PukNgae via B4 Relay @ 2025-02-11 7:01 UTC (permalink / raw) To: Jean Delvare, Guenter Roeck, Cryolitia PukNgae, Jonathan Corbet Cc: linux-kernel, linux-hwmon, linux-doc, Celeste Liu, Yao Zi, Derek John Clark, Marcin Strągowski, someone5678, Justin Weiss From: Cryolitia PukNgae <Cryolitia@gmail.com> Sensors driver for GPD Handhelds that expose fan reading and control via hwmon sysfs. Shenzhen GPD Technology Co., Ltd. manufactures a series of handheld devices. This driver implements these functions through x86 port-mapped IO. Signed-off-by: Cryolitia PukNgae <Cryolitia@gmail.com> --- MAINTAINERS | 6 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/gpd-fan.c | 611 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 628 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 0fa7c5728f1e64d031f4a47b6fce1db484ce0fc2..777ba74ccb07ccc0840c3cd34e7b4d98d726f964 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9762,6 +9762,12 @@ F: drivers/phy/samsung/phy-gs101-ufs.c F: include/dt-bindings/clock/google,gs101.h K: [gG]oogle.?[tT]ensor +GPD FAN DRIVER +M: Cryolitia PukNgae <Cryolitia@gmail.com> +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/gpd-fan.c + GPD POCKET FAN DRIVER M: Hans de Goede <hdegoede@redhat.com> L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index dd376602f3f19c6f258651afeffbe1bb5d9b6b72..974b341c0bdaba147370de59f510140c0c937913 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -729,6 +729,16 @@ config SENSORS_GL520SM This driver can also be built as a module. If so, the module will be called gl520sm. +config SENSORS_GPD + tristate "GPD handhelds" + depends on X86 + help + If you say yes here you get support for fan readings and + control over GPD handheld devices. + + Can also be built as a module. In that case it will be + called gpd-fan. + config SENSORS_G760A tristate "GMT G760A" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index b827b92f2a7844418f3f3b6434a63b744b52c33d..cd512c19caa9737a2926a3d4860f65b65cd013c3 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_SENSORS_GIGABYTE_WATERFORCE) += gigabyte_waterforce.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o +obj-$(CONFIG_SENSORS_GPD) += gpd-fan.o obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o obj-$(CONFIG_SENSORS_GXP_FAN_CTRL) += gxp-fan-ctrl.o obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o diff --git a/drivers/hwmon/gpd-fan.c b/drivers/hwmon/gpd-fan.c new file mode 100644 index 0000000000000000000000000000000000000000..8d54ebf5defa45f4d01c0dd7786b1908bca55ec0 --- /dev/null +++ b/drivers/hwmon/gpd-fan.c @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* Platform driver for GPD devices that expose fan control via hwmon sysfs. + * + * Fan control is provided via pwm interface in the range [0-255]. + * Each model has a different range in the EC, the written value is scaled to accommodate for that. + * + * Based on this repo: + * https://github.com/Cryolitia/gpd-fan-driver + * + * Copyright (c) 2024 Cryolitia PukNgae + */ + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#define DRIVER_NAME "gpdfan" + +#define GPD_PWM_CTR_OFFSET 0x1841 + +// model param, see document +static char *gpd_fan_board = ""; +module_param(gpd_fan_board, charp, 0444); + +// EC read/write locker +// Should never access EC at the same time, otherwise system down. +static DEFINE_MUTEX(gpd_fan_lock); + +enum gpd_board { + win_mini, + win4_6800u, + win_max_2, +}; + +enum FAN_PWM_ENABLE { + DISABLE = 0, + MANUAL = 1, + AUTOMATIC = 2, +}; + +static struct { + enum FAN_PWM_ENABLE pwm_enable; + u8 pwm_value; + + const struct gpd_board_drvdata *drvdata; +} gpd_driver_priv; + +struct gpd_board_drvdata { + const char *board_name; /* Board name for module param comparison */ + const enum gpd_board board; + + const u8 addr_port; + const u8 data_port; + const u16 manual_control_enable; + const u16 rpm_read; + const u16 pwm_write; + const u16 pwm_max; +}; + +static struct gpd_board_drvdata gpd_win_mini_drvdata = { + .board_name = "win_mini", + .board = win_mini, + + .addr_port = 0x4E, + .data_port = 0x4F, + .manual_control_enable = 0x047A, + .rpm_read = 0x0478, + .pwm_write = 0x047A, + .pwm_max = 244, +}; + +static struct gpd_board_drvdata gpd_win4_drvdata = { + .board_name = "win4", + .board = win4_6800u, + + .addr_port = 0x2E, + .data_port = 0x2F, + .manual_control_enable = 0xC311, + .rpm_read = 0xC880, + .pwm_write = 0xC311, + .pwm_max = 127, +}; + +static struct gpd_board_drvdata gpd_wm2_drvdata = { + .board_name = "wm2", + .board = win_max_2, + + .addr_port = 0x4E, + .data_port = 0x4F, + .manual_control_enable = 0x0275, + .rpm_read = 0x0218, + .pwm_write = 0x1809, + .pwm_max = 184, +}; + +static const struct dmi_system_id dmi_table[] = { + { + // GPD Win Mini + // GPD Win Mini with AMD Ryzen 8840U + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1617-01") + }, + .driver_data = &gpd_win_mini_drvdata, + }, + { + // GPD Win 4 with AMD Ryzen 6800U + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"), + DMI_MATCH(DMI_BOARD_VERSION, "Default string"), + }, + .driver_data = &gpd_win4_drvdata, + }, + { + // GPD Win 4 with Ryzen 7840U + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"), + DMI_MATCH(DMI_BOARD_VERSION, "Ver. 1.0"), + }, + // Since 7840U, win4 uses the same drvdata as wm2 + .driver_data = &gpd_wm2_drvdata, + }, + { + // GPD Win Max 2 with Ryzen 6800U + // GPD Win Max 2 2023 with Ryzen 7840U + // GPD Win Max 2 2024 with Ryzen 8840U + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"), + }, + .driver_data = &gpd_wm2_drvdata, + }, + {} +}; + +static const struct gpd_board_drvdata *gpd_module_drvdata[] = { + &gpd_win_mini_drvdata, &gpd_win4_drvdata, &gpd_wm2_drvdata, NULL +}; + +/* Helper functions to handle EC read/write */ +static int gpd_ecram_read(const struct gpd_board_drvdata *drvdata, u16 offset, + u8 *val) +{ + int ret; + u16 addr_port = drvdata->addr_port; + u16 data_port = drvdata->data_port; + + ret = mutex_lock_interruptible(&gpd_fan_lock); + + if (ret) + return ret; + + outb(0x2E, addr_port); + outb(0x11, data_port); + outb(0x2F, addr_port); + outb((u8)((offset >> 8) & 0xFF), data_port); + + outb(0x2E, addr_port); + outb(0x10, data_port); + outb(0x2F, addr_port); + outb((u8)(offset & 0xFF), data_port); + + outb(0x2E, addr_port); + outb(0x12, data_port); + outb(0x2F, addr_port); + *val = inb(data_port); + + mutex_unlock(&gpd_fan_lock); + return 0; +} + +static int gpd_ecram_write(const struct gpd_board_drvdata *drvdata, u16 offset, + u8 value) +{ + int ret; + u16 addr_port = drvdata->addr_port; + u16 data_port = drvdata->data_port; + + ret = mutex_lock_interruptible(&gpd_fan_lock); + + if (ret) + return ret; + + outb(0x2E, addr_port); + outb(0x11, data_port); + outb(0x2F, addr_port); + outb((u8)((offset >> 8) & 0xFF), data_port); + + outb(0x2E, addr_port); + outb(0x10, data_port); + outb(0x2F, addr_port); + outb((u8)(offset & 0xFF), data_port); + + outb(0x2E, addr_port); + outb(0x12, data_port); + outb(0x2F, addr_port); + outb(value, data_port); + + mutex_unlock(&gpd_fan_lock); + return 0; +} + +static int gpd_generic_read_rpm_uncached(void) +{ + u8 high, low; + int ret; + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; + + ret = gpd_ecram_read(drvdata, drvdata->rpm_read, &high); + if (ret) + return ret; + ret = gpd_ecram_read(drvdata, drvdata->rpm_read + 1, &low); + if (ret) + return ret; + + return (u16)high << 8 | low; +} + +static int gpd_win4_read_rpm_uncached(void) +{ + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; + u8 PWMCTR; + int ret; + + gpd_ecram_read(drvdata, GPD_PWM_CTR_OFFSET, &PWMCTR); + if (PWMCTR != 0x7F) + gpd_ecram_write(drvdata, GPD_PWM_CTR_OFFSET, 0x7F); + + ret = gpd_generic_read_rpm_uncached(); + + if (ret < 0) + return ret; + + if (ret == 0) { + //re-init EC + u8 chip_id; + + gpd_ecram_read(drvdata, 0x2000, &chip_id); + if (chip_id == 0x55) { + u8 chip_ver; + + if (gpd_ecram_read(drvdata, 0x1060, &chip_ver)) + gpd_ecram_write(drvdata, 0x1060, chip_ver | 0x80); + } + } + return ret; +} + +static int gpd_wm2_read_rpm_uncached(void) +{ + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; + + for (u16 pwm_ctr_offset = GPD_PWM_CTR_OFFSET; pwm_ctr_offset <= GPD_PWM_CTR_OFFSET + 2; + pwm_ctr_offset++) { + u8 PWMCTR; + + gpd_ecram_read(drvdata, pwm_ctr_offset, &PWMCTR); + if (PWMCTR != 0xB8) + gpd_ecram_write(drvdata, pwm_ctr_offset, 0xB8); + } + return gpd_generic_read_rpm_uncached(); +} + +// Read value for fan1_input +static int gpd_read_rpm(void) +{ + switch (gpd_driver_priv.drvdata->board) { + case win_mini: { + return gpd_generic_read_rpm_uncached(); + } + case win4_6800u: { + return gpd_win4_read_rpm_uncached(); + } + case win_max_2: { + return gpd_wm2_read_rpm_uncached(); + } + } + return 0; +} + +static int gpd_wm2_read_pwm_uncached(void) +{ + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; + u8 var; + int ret = gpd_ecram_read(drvdata, drvdata->pwm_write, &var); + + if (ret < 0) + return ret; + + return var * 255 / drvdata->pwm_max; +} + +// Read value for pwm1 +static int gpd_read_pwm(void) +{ + switch (gpd_driver_priv.drvdata->board) { + case win_mini: + case win4_6800u: + return gpd_driver_priv.pwm_value; + case win_max_2: + return gpd_wm2_read_pwm_uncached(); + } + return 0; +} + +static int gpd_generic_write_pwm(u8 val) +{ + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; + u8 actual; + + // PWM value's range in EC is 1 - pwm_max, cast 0 - 255 to it. + actual = val * (drvdata->pwm_max - 1) / 255 + 1; + return gpd_ecram_write(drvdata, drvdata->pwm_write, actual); +} + +static int gpd_win_mini_write_pwm(u8 val) +{ + if (gpd_driver_priv.pwm_enable == MANUAL) + return gpd_generic_write_pwm(val); + else + return -EPERM; +} + +static int gpd_wm2_write_pwm(u8 val) +{ + if (gpd_driver_priv.pwm_enable != DISABLE) + return gpd_generic_write_pwm(val); + else + return -EPERM; +} + +// Write value for pwm1 +static int gpd_write_pwm(u8 val) +{ + switch (gpd_driver_priv.drvdata->board) { + case win_mini: + return gpd_win_mini_write_pwm(val); + case win4_6800u: + return gpd_generic_write_pwm(val); + case win_max_2: + return gpd_wm2_write_pwm(val); + } + return 0; +} + +static int gpd_win_mini_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable) +{ + switch (pwm_enable) { + case DISABLE: + return gpd_generic_write_pwm(255); + case MANUAL: + return gpd_generic_write_pwm(gpd_driver_priv.pwm_value); + case AUTOMATIC: + const struct gpd_board_drvdata *drvdata = gpd_driver_priv.drvdata; + + return gpd_ecram_write(drvdata, drvdata->pwm_write, 0); + } + return 0; +} + +static int gpd_wm2_set_pwm_enable(enum FAN_PWM_ENABLE enable) +{ + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; + int ret; + + switch (enable) { + case DISABLE: { + ret = gpd_generic_write_pwm(255); + + if (ret) + return ret; + + return gpd_ecram_write(drvdata, drvdata->manual_control_enable, 1); + } + case MANUAL: { + ret = gpd_generic_write_pwm(gpd_driver_priv.pwm_value); + + if (ret) + return ret; + + return gpd_ecram_write(drvdata, drvdata->manual_control_enable, 1); + } + case AUTOMATIC: { + ret = gpd_ecram_write(drvdata, drvdata->manual_control_enable, 0); + + return ret; + } + } + return 0; +} + +// Write value for pwm1_enable +static int gpd_set_pwm_enable(enum FAN_PWM_ENABLE enable) +{ + switch (gpd_driver_priv.drvdata->board) { + case win_mini: + case win4_6800u: + return gpd_win_mini_set_pwm_enable(enable); + case win_max_2: + return gpd_wm2_set_pwm_enable(enable); + } + return 0; +} + +static umode_t gpd_fan_hwmon_is_visible(__always_unused const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + __always_unused int channel) +{ + if (type == hwmon_fan && attr == hwmon_fan_input) { + return 0444; + } else if (type == hwmon_pwm) { + switch (attr) { + case hwmon_pwm_enable: + case hwmon_pwm_input: + return 0644; + default: + return 0; + } + } + return 0; +} + +static int gpd_fan_hwmon_read(__always_unused struct device *dev, + enum hwmon_sensor_types type, u32 attr, + __always_unused int channel, long *val) +{ + if (type == hwmon_fan) { + if (attr == hwmon_fan_input) { + int ret = gpd_read_rpm(); + + if (ret < 0) + return ret; + + *val = ret; + return 0; + } + return -EOPNOTSUPP; + } + if (type == hwmon_pwm) { + switch (attr) { + case hwmon_pwm_enable: + *val = gpd_driver_priv.pwm_enable; + return 0; + case hwmon_pwm_input: + int ret = gpd_read_pwm(); + + if (ret < 0) + return ret; + + *val = ret; + return 0; + default: + return -EOPNOTSUPP; + } + } + return -EOPNOTSUPP; +} + +static int gpd_fan_hwmon_write(__always_unused struct device *dev, + enum hwmon_sensor_types type, u32 attr, + __always_unused int channel, long val) +{ + if (type == hwmon_pwm) { + switch (attr) { + case hwmon_pwm_enable: + if (!in_range(val, 0, 3)) + return -EINVAL; + gpd_driver_priv.pwm_enable = val; + return gpd_set_pwm_enable(gpd_driver_priv.pwm_enable); + case hwmon_pwm_input: + u8 var = clamp_val(val, 0, 255); + + gpd_driver_priv.pwm_value = var; + return gpd_write_pwm(var); + default: + return -EOPNOTSUPP; + } + } + return -EOPNOTSUPP; +} + +static const struct hwmon_ops gpd_fan_ops = { + .is_visible = gpd_fan_hwmon_is_visible, + .read = gpd_fan_hwmon_read, + .write = gpd_fan_hwmon_write, +}; + +static const struct hwmon_channel_info *gpd_fan_hwmon_channel_info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE), NULL +}; + +static struct hwmon_chip_info gpd_fan_chip_info = { + .ops = &gpd_fan_ops, + .info = gpd_fan_hwmon_channel_info +}; + +static int gpd_fan_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpd_driver_priv *data; + const struct resource *plat_res; + const struct device *dev_reg; + const struct resource *region_res; + + data = dev_get_platdata(&pdev->dev); + if (IS_ERR(data)) + return -ENODEV; + + plat_res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (IS_ERR(plat_res)) + return dev_err_probe(dev, PTR_ERR(plat_res), + "Failed to get platform resource\n"); + + region_res = devm_request_region(dev, plat_res->start, + resource_size(plat_res), DRIVER_NAME); + if (IS_ERR(region_res)) + return dev_err_probe(dev, PTR_ERR(region_res), + "Failed to request region\n"); + + dev_reg = devm_hwmon_device_register_with_info(dev, + DRIVER_NAME, + data, + &gpd_fan_chip_info, + NULL); + if (IS_ERR(dev_reg)) + return dev_err_probe(dev, PTR_ERR(region_res), + "Failed to register hwmon device\n"); + + return 0; +} + +static void gpd_fan_remove(__always_unused struct platform_device *pdev) +{ + gpd_driver_priv.pwm_enable = AUTOMATIC; + gpd_set_pwm_enable(AUTOMATIC); +} + +static struct platform_driver gpd_fan_driver = { + .probe = gpd_fan_probe, + .remove = gpd_fan_remove, + .driver = { + .name = KBUILD_MODNAME, + }, +}; + +static struct platform_device *gpd_fan_platform_device; + +static int __init gpd_fan_init(void) +{ + const struct gpd_board_drvdata *match = NULL; + + for (const struct gpd_board_drvdata **p = gpd_module_drvdata; *p; p++) { + if (strcmp(gpd_fan_board, (*p)->board_name) == 0) { + match = *p; + break; + } + } + + if (!match) + match = dmi_first_match(dmi_table)->driver_data; + + if (!match) + return -ENODEV; + + gpd_driver_priv.pwm_enable = AUTOMATIC; + gpd_driver_priv.pwm_value = 255; + gpd_driver_priv.drvdata = match; + + struct resource gpd_fan_resources[] = { + { + .start = match->addr_port, + .end = match->data_port, + .flags = IORESOURCE_IO, + }, + }; + + gpd_fan_platform_device = platform_create_bundle(&gpd_fan_driver, + gpd_fan_probe, + gpd_fan_resources, + 1, NULL, 0); + + if (IS_ERR(gpd_fan_platform_device)) { + pr_warn("Failed to create platform device\n"); + return PTR_ERR(gpd_fan_platform_device); + } + + return 0; +} + +static void __exit gpd_fan_exit(void) +{ + platform_device_unregister(gpd_fan_platform_device); + platform_driver_unregister(&gpd_fan_driver); +} + +MODULE_DEVICE_TABLE(dmi, dmi_table); + +module_init(gpd_fan_init) +module_exit(gpd_fan_exit) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cryolitia <Cryolitia@gmail.com>"); +MODULE_DESCRIPTION("GPD Devices fan control driver"); -- 2.47.1 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v5 1/2] hwmon: add GPD devices sensor driver 2025-02-11 7:01 ` [PATCH v5 1/2] " Cryolitia PukNgae via B4 Relay @ 2025-02-13 19:32 ` kernel test robot 2025-02-17 5:03 ` kernel test robot 2025-03-02 17:31 ` Guenter Roeck 2 siblings, 0 replies; 7+ messages in thread From: kernel test robot @ 2025-02-13 19:32 UTC (permalink / raw) To: Cryolitia PukNgae via B4 Relay, Jean Delvare, Guenter Roeck, Cryolitia PukNgae, Jonathan Corbet Cc: llvm, oe-kbuild-all, linux-kernel, linux-hwmon, linux-doc, Celeste Liu, Yao Zi, Derek John Clark, Marcin Strągowski, someone5678, Justin Weiss Hi Cryolitia, kernel test robot noticed the following build warnings: [auto build test WARNING on ffd294d346d185b70e28b1a28abe367bbfe53c04] url: https://github.com/intel-lab-lkp/linux/commits/Cryolitia-PukNgae-via-B4-Relay/hwmon-add-GPD-devices-sensor-driver/20250211-150418 base: ffd294d346d185b70e28b1a28abe367bbfe53c04 patch link: https://lore.kernel.org/r/20250211-gpd_fan-v5-1-608f4255f0e1%40gmail.com patch subject: [PATCH v5 1/2] hwmon: add GPD devices sensor driver config: i386-randconfig-002-20250214 (https://download.01.org/0day-ci/archive/20250214/202502140302.IkW9UALU-lkp@intel.com/config) compiler: clang version 19.1.3 (https://github.com/llvm/llvm-project ab51eccf88f5321e7c60591c5546b254b6afab99) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250214/202502140302.IkW9UALU-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202502140302.IkW9UALU-lkp@intel.com/ All warnings (new ones prefixed by >>): >> drivers/hwmon/gpd-fan.c:361:3: warning: label followed by a declaration is a C23 extension [-Wc23-extensions] 361 | const struct gpd_board_drvdata *drvdata = gpd_driver_priv.drvdata; | ^ drivers/hwmon/gpd-fan.c:452:4: warning: label followed by a declaration is a C23 extension [-Wc23-extensions] 452 | int ret = gpd_read_pwm(); | ^ drivers/hwmon/gpd-fan.c:478:4: warning: label followed by a declaration is a C23 extension [-Wc23-extensions] 478 | u8 var = clamp_val(val, 0, 255); | ^ 3 warnings generated. vim +361 drivers/hwmon/gpd-fan.c 352 353 static int gpd_win_mini_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable) 354 { 355 switch (pwm_enable) { 356 case DISABLE: 357 return gpd_generic_write_pwm(255); 358 case MANUAL: 359 return gpd_generic_write_pwm(gpd_driver_priv.pwm_value); 360 case AUTOMATIC: > 361 const struct gpd_board_drvdata *drvdata = gpd_driver_priv.drvdata; 362 363 return gpd_ecram_write(drvdata, drvdata->pwm_write, 0); 364 } 365 return 0; 366 } 367 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 1/2] hwmon: add GPD devices sensor driver 2025-02-11 7:01 ` [PATCH v5 1/2] " Cryolitia PukNgae via B4 Relay 2025-02-13 19:32 ` kernel test robot @ 2025-02-17 5:03 ` kernel test robot 2025-03-02 17:31 ` Guenter Roeck 2 siblings, 0 replies; 7+ messages in thread From: kernel test robot @ 2025-02-17 5:03 UTC (permalink / raw) To: Cryolitia PukNgae via B4 Relay Cc: oe-lkp, lkp, linux-kernel, linux-hwmon, Jean Delvare, Guenter Roeck, Cryolitia PukNgae, Jonathan Corbet, linux-doc, Celeste Liu, Yao Zi, Derek John Clark, Marcin Strągowski, someone5678, Justin Weiss, oliver.sang Hello, kernel test robot noticed "Oops:general_protection_fault,probably_for_non-canonical_address#:#[##]PREEMPT_KASAN" on: commit: 143d683070d6cedc02b6406f98f21bd5ffed8a70 ("[PATCH v5 1/2] hwmon: add GPD devices sensor driver") url: https://github.com/intel-lab-lkp/linux/commits/Cryolitia-PukNgae-via-B4-Relay/hwmon-add-GPD-devices-sensor-driver/20250211-150418 patch link: https://lore.kernel.org/all/20250211-gpd_fan-v5-1-608f4255f0e1@gmail.com/ patch subject: [PATCH v5 1/2] hwmon: add GPD devices sensor driver in testcase: boot config: x86_64-randconfig-004-20250215 compiler: gcc-12 test machine: qemu-system-x86_64 -enable-kvm -cpu SandyBridge -smp 2 -m 16G (please refer to attached dmesg/kmsg for entire log/backtrace) +--------------------------------------------------------------------------------------+-------+------------+ | | v6.13 | 143d683070 | +--------------------------------------------------------------------------------------+-------+------------+ | boot_successes | 12 | 0 | | boot_failures | 0 | 12 | | Oops:general_protection_fault,probably_for_non-canonical_address#:#[##]PREEMPT_KASAN | 0 | 12 | | KASAN:null-ptr-deref_in_range[#-#] | 0 | 12 | | RIP:gpd_fan_init | 0 | 12 | | Kernel_panic-not_syncing:Fatal_exception | 0 | 12 | +--------------------------------------------------------------------------------------+-------+------------+ If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <oliver.sang@intel.com> | Closes: https://lore.kernel.org/oe-lkp/202502171113.9faaae-lkp@intel.com [ 24.206559][ T1] pps_ldisc: PPS line discipline registered [ 24.221748][ T1] Driver for 1-wire Dallas network protocol. [ 24.228882][ T1] usbcore: registered new interface driver DS9490R [ 24.241341][ T1] applesmc: supported laptop not found! [ 24.245457][ T1] applesmc: driver init failed (ret=-19)! [ 24.253994][ T1] Oops: general protection fault, probably for non-canonical address 0xdffffc000000002a: 0000 [#1] PREEMPT KASAN [ 24.255395][ T1] KASAN: null-ptr-deref in range [0x0000000000000150-0x0000000000000157] [ 24.255395][ T1] CPU: 0 UID: 0 PID: 1 Comm: swapper Tainted: G T 6.13.0-00001-g143d683070d6 #1 [ 24.255395][ T1] Tainted: [T]=RANDSTRUCT [ 24.255395][ T1] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.2-debian-1.16.2-1 04/01/2014 [ 24.255395][ T1] RIP: 0010:gpd_fan_init (kbuild/src/consumer/drivers/hwmon/gpd-fan.c:568) [ 24.255395][ T1] Code: c5 08 eb b2 48 c7 c7 40 16 90 87 e8 49 ea b6 f9 48 8d b8 50 01 00 00 49 89 c5 b8 ff ff 37 00 48 c1 e0 2a 48 89 fa 48 c1 ea 03 <80> 3c 02 00 74 05 e8 a7 5d e9 f5 4d 8b bd 50 01 00 00 b8 ed ff ff All code ======== 0: c5 08 eb (bad) 3: b2 48 mov $0x48,%dl 5: c7 c7 40 16 90 87 mov $0x87901640,%edi b: e8 49 ea b6 f9 call 0xfffffffff9b6ea59 10: 48 8d b8 50 01 00 00 lea 0x150(%rax),%rdi 17: 49 89 c5 mov %rax,%r13 1a: b8 ff ff 37 00 mov $0x37ffff,%eax 1f: 48 c1 e0 2a shl $0x2a,%rax 23: 48 89 fa mov %rdi,%rdx 26: 48 c1 ea 03 shr $0x3,%rdx 2a:* 80 3c 02 00 cmpb $0x0,(%rdx,%rax,1) <-- trapping instruction 2e: 74 05 je 0x35 30: e8 a7 5d e9 f5 call 0xfffffffff5e95ddc 35: 4d 8b bd 50 01 00 00 mov 0x150(%r13),%r15 3c: b8 .byte 0xb8 3d: ed in (%dx),%eax 3e: ff (bad) 3f: ff .byte 0xff Code starting with the faulting instruction =========================================== 0: 80 3c 02 00 cmpb $0x0,(%rdx,%rax,1) 4: 74 05 je 0xb 6: e8 a7 5d e9 f5 call 0xfffffffff5e95db2 b: 4d 8b bd 50 01 00 00 mov 0x150(%r13),%r15 12: b8 .byte 0xb8 13: ed in (%dx),%eax 14: ff (bad) 15: ff .byte 0xff [ 24.255395][ T1] RSP: 0018:ffffc9000001fcd8 EFLAGS: 00010202 [ 24.255395][ T1] RAX: dffffc0000000000 RBX: 1ffff92000003f9c RCX: ffffffff8c800034 [ 24.255395][ T1] RDX: 000000000000002a RSI: 0000000000000001 RDI: 0000000000000150 [ 24.255395][ T1] RBP: ffffc9000001fd88 R08: 0000000e56910ec6 R09: 0000000e56910ec6 [ 24.255395][ T1] R10: fffffbfff130e5ad R11: ffffffff8261d8e0 R12: ffffc9000001fd60 [ 24.255395][ T1] R13: 0000000000000000 R14: dffffc0000000000 R15: 0000000000000000 [ 24.255395][ T1] FS: 0000000000000000(0000) GS:ffffffff8890a000(0000) knlGS:0000000000000000 [ 24.255395][ T1] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 24.255395][ T1] CR2: 00007fd5127dc480 CR3: 00000000088d4000 CR4: 00000000000406b0 [ 24.255395][ T1] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 24.255395][ T1] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 [ 24.255395][ T1] Call Trace: [ 24.255395][ T1] <TASK> [ 24.255395][ T1] ? show_regs (kbuild/src/consumer/arch/x86/kernel/dumpstack.c:479) [ 24.255395][ T1] ? __die_body (kbuild/src/consumer/arch/x86/kernel/dumpstack.c:421) [ 24.255395][ T1] ? die_addr (kbuild/src/consumer/arch/x86/kernel/dumpstack.c:455) [ 24.255395][ T1] ? exc_general_protection (kbuild/src/consumer/arch/x86/kernel/traps.c:751 kbuild/src/consumer/arch/x86/kernel/traps.c:693) The kernel config and materials to reproduce are available at: https://download.01.org/0day-ci/archive/20250217/202502171113.9faaae-lkp@intel.com -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 1/2] hwmon: add GPD devices sensor driver 2025-02-11 7:01 ` [PATCH v5 1/2] " Cryolitia PukNgae via B4 Relay 2025-02-13 19:32 ` kernel test robot 2025-02-17 5:03 ` kernel test robot @ 2025-03-02 17:31 ` Guenter Roeck 2025-03-13 20:12 ` Cryolitia PukNgae 2 siblings, 1 reply; 7+ messages in thread From: Guenter Roeck @ 2025-03-02 17:31 UTC (permalink / raw) To: Cryolitia Cc: Jean Delvare, Jonathan Corbet, linux-kernel, linux-hwmon, linux-doc, Celeste Liu, Yao Zi, Derek John Clark, Marcin Strągowski, someone5678, Justin Weiss On Tue, Feb 11, 2025 at 03:01:17PM +0800, Cryolitia PukNgae via B4 Relay wrote: > From: Cryolitia PukNgae <Cryolitia@gmail.com> > > Sensors driver for GPD Handhelds that expose fan reading and control via > hwmon sysfs. > > Shenzhen GPD Technology Co., Ltd. manufactures a series of handheld > devices. This driver implements these functions through x86 port-mapped IO. > > Signed-off-by: Cryolitia PukNgae <Cryolitia@gmail.com> 0-day reported crashes with this driver applied. Please fix. Guenter > --- > MAINTAINERS | 6 + > drivers/hwmon/Kconfig | 10 + > drivers/hwmon/Makefile | 1 + > drivers/hwmon/gpd-fan.c | 611 ++++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 628 insertions(+) > > diff --git a/MAINTAINERS b/MAINTAINERS > index 0fa7c5728f1e64d031f4a47b6fce1db484ce0fc2..777ba74ccb07ccc0840c3cd34e7b4d98d726f964 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -9762,6 +9762,12 @@ F: drivers/phy/samsung/phy-gs101-ufs.c > F: include/dt-bindings/clock/google,gs101.h > K: [gG]oogle.?[tT]ensor > > +GPD FAN DRIVER > +M: Cryolitia PukNgae <Cryolitia@gmail.com> > +L: linux-hwmon@vger.kernel.org > +S: Maintained > +F: drivers/hwmon/gpd-fan.c > + > GPD POCKET FAN DRIVER > M: Hans de Goede <hdegoede@redhat.com> > L: platform-driver-x86@vger.kernel.org > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig > index dd376602f3f19c6f258651afeffbe1bb5d9b6b72..974b341c0bdaba147370de59f510140c0c937913 100644 > --- a/drivers/hwmon/Kconfig > +++ b/drivers/hwmon/Kconfig > @@ -729,6 +729,16 @@ config SENSORS_GL520SM > This driver can also be built as a module. If so, the module > will be called gl520sm. > > +config SENSORS_GPD > + tristate "GPD handhelds" > + depends on X86 > + help > + If you say yes here you get support for fan readings and > + control over GPD handheld devices. > + > + Can also be built as a module. In that case it will be > + called gpd-fan. > + > config SENSORS_G760A > tristate "GMT G760A" > depends on I2C > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile > index b827b92f2a7844418f3f3b6434a63b744b52c33d..cd512c19caa9737a2926a3d4860f65b65cd013c3 100644 > --- a/drivers/hwmon/Makefile > +++ b/drivers/hwmon/Makefile > @@ -87,6 +87,7 @@ obj-$(CONFIG_SENSORS_GIGABYTE_WATERFORCE) += gigabyte_waterforce.o > obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o > obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o > obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o > +obj-$(CONFIG_SENSORS_GPD) += gpd-fan.o > obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o > obj-$(CONFIG_SENSORS_GXP_FAN_CTRL) += gxp-fan-ctrl.o > obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o > diff --git a/drivers/hwmon/gpd-fan.c b/drivers/hwmon/gpd-fan.c > new file mode 100644 > index 0000000000000000000000000000000000000000..8d54ebf5defa45f4d01c0dd7786b1908bca55ec0 > --- /dev/null > +++ b/drivers/hwmon/gpd-fan.c > @@ -0,0 +1,611 @@ > +// SPDX-License-Identifier: GPL-2.0+ > + > +/* Platform driver for GPD devices that expose fan control via hwmon sysfs. > + * > + * Fan control is provided via pwm interface in the range [0-255]. > + * Each model has a different range in the EC, the written value is scaled to accommodate for that. > + * > + * Based on this repo: > + * https://github.com/Cryolitia/gpd-fan-driver > + * > + * Copyright (c) 2024 Cryolitia PukNgae > + */ > + > +#include <linux/acpi.h> > +#include <linux/dmi.h> > +#include <linux/hwmon.h> > +#include <linux/ioport.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > + > +#define DRIVER_NAME "gpdfan" > + > +#define GPD_PWM_CTR_OFFSET 0x1841 > + > +// model param, see document > +static char *gpd_fan_board = ""; > +module_param(gpd_fan_board, charp, 0444); > + > +// EC read/write locker > +// Should never access EC at the same time, otherwise system down. > +static DEFINE_MUTEX(gpd_fan_lock); > + > +enum gpd_board { > + win_mini, > + win4_6800u, > + win_max_2, > +}; > + > +enum FAN_PWM_ENABLE { > + DISABLE = 0, > + MANUAL = 1, > + AUTOMATIC = 2, > +}; > + > +static struct { > + enum FAN_PWM_ENABLE pwm_enable; > + u8 pwm_value; > + > + const struct gpd_board_drvdata *drvdata; > +} gpd_driver_priv; > + > +struct gpd_board_drvdata { > + const char *board_name; /* Board name for module param comparison */ > + const enum gpd_board board; > + > + const u8 addr_port; > + const u8 data_port; > + const u16 manual_control_enable; > + const u16 rpm_read; > + const u16 pwm_write; > + const u16 pwm_max; > +}; > + > +static struct gpd_board_drvdata gpd_win_mini_drvdata = { > + .board_name = "win_mini", > + .board = win_mini, > + > + .addr_port = 0x4E, > + .data_port = 0x4F, > + .manual_control_enable = 0x047A, > + .rpm_read = 0x0478, > + .pwm_write = 0x047A, > + .pwm_max = 244, > +}; > + > +static struct gpd_board_drvdata gpd_win4_drvdata = { > + .board_name = "win4", > + .board = win4_6800u, > + > + .addr_port = 0x2E, > + .data_port = 0x2F, > + .manual_control_enable = 0xC311, > + .rpm_read = 0xC880, > + .pwm_write = 0xC311, > + .pwm_max = 127, > +}; > + > +static struct gpd_board_drvdata gpd_wm2_drvdata = { > + .board_name = "wm2", > + .board = win_max_2, > + > + .addr_port = 0x4E, > + .data_port = 0x4F, > + .manual_control_enable = 0x0275, > + .rpm_read = 0x0218, > + .pwm_write = 0x1809, > + .pwm_max = 184, > +}; > + > +static const struct dmi_system_id dmi_table[] = { > + { > + // GPD Win Mini > + // GPD Win Mini with AMD Ryzen 8840U > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), > + DMI_MATCH(DMI_PRODUCT_NAME, "G1617-01") > + }, > + .driver_data = &gpd_win_mini_drvdata, > + }, > + { > + // GPD Win 4 with AMD Ryzen 6800U > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), > + DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"), > + DMI_MATCH(DMI_BOARD_VERSION, "Default string"), > + }, > + .driver_data = &gpd_win4_drvdata, > + }, > + { > + // GPD Win 4 with Ryzen 7840U > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), > + DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"), > + DMI_MATCH(DMI_BOARD_VERSION, "Ver. 1.0"), > + }, > + // Since 7840U, win4 uses the same drvdata as wm2 > + .driver_data = &gpd_wm2_drvdata, > + }, > + { > + // GPD Win Max 2 with Ryzen 6800U > + // GPD Win Max 2 2023 with Ryzen 7840U > + // GPD Win Max 2 2024 with Ryzen 8840U > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), > + DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"), > + }, > + .driver_data = &gpd_wm2_drvdata, > + }, > + {} > +}; > + > +static const struct gpd_board_drvdata *gpd_module_drvdata[] = { > + &gpd_win_mini_drvdata, &gpd_win4_drvdata, &gpd_wm2_drvdata, NULL > +}; > + > +/* Helper functions to handle EC read/write */ > +static int gpd_ecram_read(const struct gpd_board_drvdata *drvdata, u16 offset, > + u8 *val) > +{ > + int ret; > + u16 addr_port = drvdata->addr_port; > + u16 data_port = drvdata->data_port; > + > + ret = mutex_lock_interruptible(&gpd_fan_lock); > + > + if (ret) > + return ret; > + > + outb(0x2E, addr_port); > + outb(0x11, data_port); > + outb(0x2F, addr_port); > + outb((u8)((offset >> 8) & 0xFF), data_port); > + > + outb(0x2E, addr_port); > + outb(0x10, data_port); > + outb(0x2F, addr_port); > + outb((u8)(offset & 0xFF), data_port); > + > + outb(0x2E, addr_port); > + outb(0x12, data_port); > + outb(0x2F, addr_port); > + *val = inb(data_port); > + > + mutex_unlock(&gpd_fan_lock); > + return 0; > +} > + > +static int gpd_ecram_write(const struct gpd_board_drvdata *drvdata, u16 offset, > + u8 value) > +{ > + int ret; > + u16 addr_port = drvdata->addr_port; > + u16 data_port = drvdata->data_port; > + > + ret = mutex_lock_interruptible(&gpd_fan_lock); > + > + if (ret) > + return ret; > + > + outb(0x2E, addr_port); > + outb(0x11, data_port); > + outb(0x2F, addr_port); > + outb((u8)((offset >> 8) & 0xFF), data_port); > + > + outb(0x2E, addr_port); > + outb(0x10, data_port); > + outb(0x2F, addr_port); > + outb((u8)(offset & 0xFF), data_port); > + > + outb(0x2E, addr_port); > + outb(0x12, data_port); > + outb(0x2F, addr_port); > + outb(value, data_port); > + > + mutex_unlock(&gpd_fan_lock); > + return 0; > +} > + > +static int gpd_generic_read_rpm_uncached(void) > +{ > + u8 high, low; > + int ret; > + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; > + > + ret = gpd_ecram_read(drvdata, drvdata->rpm_read, &high); > + if (ret) > + return ret; > + ret = gpd_ecram_read(drvdata, drvdata->rpm_read + 1, &low); > + if (ret) > + return ret; > + > + return (u16)high << 8 | low; > +} > + > +static int gpd_win4_read_rpm_uncached(void) > +{ > + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; > + u8 PWMCTR; > + int ret; > + > + gpd_ecram_read(drvdata, GPD_PWM_CTR_OFFSET, &PWMCTR); > + if (PWMCTR != 0x7F) > + gpd_ecram_write(drvdata, GPD_PWM_CTR_OFFSET, 0x7F); > + > + ret = gpd_generic_read_rpm_uncached(); > + > + if (ret < 0) > + return ret; > + > + if (ret == 0) { > + //re-init EC > + u8 chip_id; > + > + gpd_ecram_read(drvdata, 0x2000, &chip_id); > + if (chip_id == 0x55) { > + u8 chip_ver; > + > + if (gpd_ecram_read(drvdata, 0x1060, &chip_ver)) > + gpd_ecram_write(drvdata, 0x1060, chip_ver | 0x80); > + } > + } > + return ret; > +} > + > +static int gpd_wm2_read_rpm_uncached(void) > +{ > + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; > + > + for (u16 pwm_ctr_offset = GPD_PWM_CTR_OFFSET; pwm_ctr_offset <= GPD_PWM_CTR_OFFSET + 2; > + pwm_ctr_offset++) { > + u8 PWMCTR; > + > + gpd_ecram_read(drvdata, pwm_ctr_offset, &PWMCTR); > + if (PWMCTR != 0xB8) > + gpd_ecram_write(drvdata, pwm_ctr_offset, 0xB8); > + } > + return gpd_generic_read_rpm_uncached(); > +} > + > +// Read value for fan1_input > +static int gpd_read_rpm(void) > +{ > + switch (gpd_driver_priv.drvdata->board) { > + case win_mini: { > + return gpd_generic_read_rpm_uncached(); > + } > + case win4_6800u: { > + return gpd_win4_read_rpm_uncached(); > + } > + case win_max_2: { > + return gpd_wm2_read_rpm_uncached(); > + } > + } > + return 0; > +} > + > +static int gpd_wm2_read_pwm_uncached(void) > +{ > + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; > + u8 var; > + int ret = gpd_ecram_read(drvdata, drvdata->pwm_write, &var); > + > + if (ret < 0) > + return ret; > + > + return var * 255 / drvdata->pwm_max; > +} > + > +// Read value for pwm1 > +static int gpd_read_pwm(void) > +{ > + switch (gpd_driver_priv.drvdata->board) { > + case win_mini: > + case win4_6800u: > + return gpd_driver_priv.pwm_value; > + case win_max_2: > + return gpd_wm2_read_pwm_uncached(); > + } > + return 0; > +} > + > +static int gpd_generic_write_pwm(u8 val) > +{ > + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; > + u8 actual; > + > + // PWM value's range in EC is 1 - pwm_max, cast 0 - 255 to it. > + actual = val * (drvdata->pwm_max - 1) / 255 + 1; > + return gpd_ecram_write(drvdata, drvdata->pwm_write, actual); > +} > + > +static int gpd_win_mini_write_pwm(u8 val) > +{ > + if (gpd_driver_priv.pwm_enable == MANUAL) > + return gpd_generic_write_pwm(val); > + else > + return -EPERM; > +} > + > +static int gpd_wm2_write_pwm(u8 val) > +{ > + if (gpd_driver_priv.pwm_enable != DISABLE) > + return gpd_generic_write_pwm(val); > + else > + return -EPERM; > +} > + > +// Write value for pwm1 > +static int gpd_write_pwm(u8 val) > +{ > + switch (gpd_driver_priv.drvdata->board) { > + case win_mini: > + return gpd_win_mini_write_pwm(val); > + case win4_6800u: > + return gpd_generic_write_pwm(val); > + case win_max_2: > + return gpd_wm2_write_pwm(val); > + } > + return 0; > +} > + > +static int gpd_win_mini_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable) > +{ > + switch (pwm_enable) { > + case DISABLE: > + return gpd_generic_write_pwm(255); > + case MANUAL: > + return gpd_generic_write_pwm(gpd_driver_priv.pwm_value); > + case AUTOMATIC: > + const struct gpd_board_drvdata *drvdata = gpd_driver_priv.drvdata; > + > + return gpd_ecram_write(drvdata, drvdata->pwm_write, 0); > + } > + return 0; > +} > + > +static int gpd_wm2_set_pwm_enable(enum FAN_PWM_ENABLE enable) > +{ > + const struct gpd_board_drvdata *const drvdata = gpd_driver_priv.drvdata; > + int ret; > + > + switch (enable) { > + case DISABLE: { > + ret = gpd_generic_write_pwm(255); > + > + if (ret) > + return ret; > + > + return gpd_ecram_write(drvdata, drvdata->manual_control_enable, 1); > + } > + case MANUAL: { > + ret = gpd_generic_write_pwm(gpd_driver_priv.pwm_value); > + > + if (ret) > + return ret; > + > + return gpd_ecram_write(drvdata, drvdata->manual_control_enable, 1); > + } > + case AUTOMATIC: { > + ret = gpd_ecram_write(drvdata, drvdata->manual_control_enable, 0); > + > + return ret; > + } > + } > + return 0; > +} > + > +// Write value for pwm1_enable > +static int gpd_set_pwm_enable(enum FAN_PWM_ENABLE enable) > +{ > + switch (gpd_driver_priv.drvdata->board) { > + case win_mini: > + case win4_6800u: > + return gpd_win_mini_set_pwm_enable(enable); > + case win_max_2: > + return gpd_wm2_set_pwm_enable(enable); > + } > + return 0; > +} > + > +static umode_t gpd_fan_hwmon_is_visible(__always_unused const void *drvdata, > + enum hwmon_sensor_types type, u32 attr, > + __always_unused int channel) > +{ > + if (type == hwmon_fan && attr == hwmon_fan_input) { > + return 0444; > + } else if (type == hwmon_pwm) { > + switch (attr) { > + case hwmon_pwm_enable: > + case hwmon_pwm_input: > + return 0644; > + default: > + return 0; > + } > + } > + return 0; > +} > + > +static int gpd_fan_hwmon_read(__always_unused struct device *dev, > + enum hwmon_sensor_types type, u32 attr, > + __always_unused int channel, long *val) > +{ > + if (type == hwmon_fan) { > + if (attr == hwmon_fan_input) { > + int ret = gpd_read_rpm(); > + > + if (ret < 0) > + return ret; > + > + *val = ret; > + return 0; > + } > + return -EOPNOTSUPP; > + } > + if (type == hwmon_pwm) { > + switch (attr) { > + case hwmon_pwm_enable: > + *val = gpd_driver_priv.pwm_enable; > + return 0; > + case hwmon_pwm_input: > + int ret = gpd_read_pwm(); > + > + if (ret < 0) > + return ret; > + > + *val = ret; > + return 0; > + default: > + return -EOPNOTSUPP; > + } > + } > + return -EOPNOTSUPP; > +} > + > +static int gpd_fan_hwmon_write(__always_unused struct device *dev, > + enum hwmon_sensor_types type, u32 attr, > + __always_unused int channel, long val) > +{ > + if (type == hwmon_pwm) { > + switch (attr) { > + case hwmon_pwm_enable: > + if (!in_range(val, 0, 3)) > + return -EINVAL; > + gpd_driver_priv.pwm_enable = val; > + return gpd_set_pwm_enable(gpd_driver_priv.pwm_enable); > + case hwmon_pwm_input: > + u8 var = clamp_val(val, 0, 255); > + > + gpd_driver_priv.pwm_value = var; > + return gpd_write_pwm(var); > + default: > + return -EOPNOTSUPP; > + } > + } > + return -EOPNOTSUPP; > +} > + > +static const struct hwmon_ops gpd_fan_ops = { > + .is_visible = gpd_fan_hwmon_is_visible, > + .read = gpd_fan_hwmon_read, > + .write = gpd_fan_hwmon_write, > +}; > + > +static const struct hwmon_channel_info *gpd_fan_hwmon_channel_info[] = { > + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), > + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE), NULL > +}; > + > +static struct hwmon_chip_info gpd_fan_chip_info = { > + .ops = &gpd_fan_ops, > + .info = gpd_fan_hwmon_channel_info > +}; > + > +static int gpd_fan_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct gpd_driver_priv *data; > + const struct resource *plat_res; > + const struct device *dev_reg; > + const struct resource *region_res; > + > + data = dev_get_platdata(&pdev->dev); > + if (IS_ERR(data)) > + return -ENODEV; > + > + plat_res = platform_get_resource(pdev, IORESOURCE_IO, 0); > + if (IS_ERR(plat_res)) > + return dev_err_probe(dev, PTR_ERR(plat_res), > + "Failed to get platform resource\n"); > + > + region_res = devm_request_region(dev, plat_res->start, > + resource_size(plat_res), DRIVER_NAME); > + if (IS_ERR(region_res)) > + return dev_err_probe(dev, PTR_ERR(region_res), > + "Failed to request region\n"); > + > + dev_reg = devm_hwmon_device_register_with_info(dev, > + DRIVER_NAME, > + data, > + &gpd_fan_chip_info, > + NULL); > + if (IS_ERR(dev_reg)) > + return dev_err_probe(dev, PTR_ERR(region_res), > + "Failed to register hwmon device\n"); > + > + return 0; > +} > + > +static void gpd_fan_remove(__always_unused struct platform_device *pdev) > +{ > + gpd_driver_priv.pwm_enable = AUTOMATIC; > + gpd_set_pwm_enable(AUTOMATIC); > +} > + > +static struct platform_driver gpd_fan_driver = { > + .probe = gpd_fan_probe, > + .remove = gpd_fan_remove, > + .driver = { > + .name = KBUILD_MODNAME, > + }, > +}; > + > +static struct platform_device *gpd_fan_platform_device; > + > +static int __init gpd_fan_init(void) > +{ > + const struct gpd_board_drvdata *match = NULL; > + > + for (const struct gpd_board_drvdata **p = gpd_module_drvdata; *p; p++) { > + if (strcmp(gpd_fan_board, (*p)->board_name) == 0) { > + match = *p; > + break; > + } > + } > + > + if (!match) > + match = dmi_first_match(dmi_table)->driver_data; > + > + if (!match) > + return -ENODEV; > + > + gpd_driver_priv.pwm_enable = AUTOMATIC; > + gpd_driver_priv.pwm_value = 255; > + gpd_driver_priv.drvdata = match; > + > + struct resource gpd_fan_resources[] = { > + { > + .start = match->addr_port, > + .end = match->data_port, > + .flags = IORESOURCE_IO, > + }, > + }; > + > + gpd_fan_platform_device = platform_create_bundle(&gpd_fan_driver, > + gpd_fan_probe, > + gpd_fan_resources, > + 1, NULL, 0); > + > + if (IS_ERR(gpd_fan_platform_device)) { > + pr_warn("Failed to create platform device\n"); > + return PTR_ERR(gpd_fan_platform_device); > + } > + > + return 0; > +} > + > +static void __exit gpd_fan_exit(void) > +{ > + platform_device_unregister(gpd_fan_platform_device); > + platform_driver_unregister(&gpd_fan_driver); > +} > + > +MODULE_DEVICE_TABLE(dmi, dmi_table); > + > +module_init(gpd_fan_init) > +module_exit(gpd_fan_exit) > + > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Cryolitia <Cryolitia@gmail.com>"); > +MODULE_DESCRIPTION("GPD Devices fan control driver"); ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 1/2] hwmon: add GPD devices sensor driver 2025-03-02 17:31 ` Guenter Roeck @ 2025-03-13 20:12 ` Cryolitia PukNgae 0 siblings, 0 replies; 7+ messages in thread From: Cryolitia PukNgae @ 2025-03-13 20:12 UTC (permalink / raw) To: Guenter Roeck Cc: Jean Delvare, Jonathan Corbet, linux-kernel, linux-hwmon, linux-doc, Celeste Liu, Yao Zi, Derek John Clark, Marcin Strągowski, someone5678, Justin Weiss > 0-day reported crashes with this driver applied. Please fix. Fixed and v6 sent. Thx for your review! 顺颂时祺 Cryolitia ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v5 2/2] hwmon: document: add gpd-fan 2025-02-11 7:01 [PATCH v5 0/2] hwmon: add GPD devices sensor driver Cryolitia PukNgae via B4 Relay 2025-02-11 7:01 ` [PATCH v5 1/2] " Cryolitia PukNgae via B4 Relay @ 2025-02-11 7:01 ` Cryolitia PukNgae via B4 Relay 1 sibling, 0 replies; 7+ messages in thread From: Cryolitia PukNgae via B4 Relay @ 2025-02-11 7:01 UTC (permalink / raw) To: Jean Delvare, Guenter Roeck, Cryolitia PukNgae, Jonathan Corbet Cc: linux-kernel, linux-hwmon, linux-doc, Celeste Liu, Yao Zi, Derek John Clark, Marcin Strągowski, someone5678, Justin Weiss From: Cryolitia PukNgae <Cryolitia@gmail.com> Add GPD fan driver document Signed-off-by: Cryolitia PukNgae <Cryolitia@gmail.com> --- Documentation/hwmon/gpd-fan.rst | 63 +++++++++++++++++++++++++++++++++++++++++ Documentation/hwmon/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 65 insertions(+) diff --git a/Documentation/hwmon/gpd-fan.rst b/Documentation/hwmon/gpd-fan.rst new file mode 100644 index 0000000000000000000000000000000000000000..9d478c7350b5fb88e43e54407503dac441328142 --- /dev/null +++ b/Documentation/hwmon/gpd-fan.rst @@ -0,0 +1,63 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver gpd-fan +========================= + +Author: + - Cryolitia PukNgae <Cryolitia@gmail.com> + +Description +------------ + +Handheld devices from Shenzhen GPD Technology Co., Ltd. provide fan readings and fan control through +their embedded controllers. + +Supported devices +----------------- + +Currently the driver supports the following handhelds: + + - GPD Win Mini (7840U) + - GPD Win Mini (8840U) + - GPD Win Max 2 + - GPD Win Max 2 2023 (7840U) + - GPD Win Max 2 2024 (8840U) + - GPD Win 4 (6800U) + - GPD Win 4 (7840U) + +Module parameters +----------------- + +gpd_fan_board + Force specific which module quirk should be used. + Use it like "gpd_fan_board=wm2". + + - wm2 + - GPD Win 4 (7840U) + - GPD Win Max 2 (6800U) + - GPD Win Max 2 2023 (7840U) + - GPD Win Max 2 2024 (8840U) + - win4 + - GPD Win 4 (6800U) + - win_mini + - GPD Win Mini (7840U) + - GPD Win Mini (8840U) + +Sysfs entries +------------- + +The following attributes are supported: + +fan1_input + Read Only. Reads current fan RPM. + +pwm1_enable + Read/Write. Enable manual fan control. Write "0" to disable control and run at + full speed. Write "1" to set to manual, write "2" to let the EC control decide + fan speed. Read this attribute to see current status. + +pwm1 + Read/Write. Read this attribute to see current duty cycle in the range [0-255]. + When pwm1_enable is set to "1" (manual) write any value in the range [0-255] + to set fan speed. + diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 55f1111594b2e9ada4a881e5d4d8884f33256d1f..d5c7cd0cfdeb7059b6cd83050ae98aa7cb1334e6 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -80,6 +80,7 @@ Hardware Monitoring Kernel Drivers gigabyte_waterforce gsc-hwmon gl518sm + gpd-fan gxp-fan-ctrl hih6130 hp-wmi-sensors diff --git a/MAINTAINERS b/MAINTAINERS index 777ba74ccb07ccc0840c3cd34e7b4d98d726f964..20faebeae981e4b7619fb10331c50525d98db944 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9766,6 +9766,7 @@ GPD FAN DRIVER M: Cryolitia PukNgae <Cryolitia@gmail.com> L: linux-hwmon@vger.kernel.org S: Maintained +F: Documentation/hwmon/gpd-fan.rst F: drivers/hwmon/gpd-fan.c GPD POCKET FAN DRIVER -- 2.47.1 ^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-03-13 20:13 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-02-11 7:01 [PATCH v5 0/2] hwmon: add GPD devices sensor driver Cryolitia PukNgae via B4 Relay 2025-02-11 7:01 ` [PATCH v5 1/2] " Cryolitia PukNgae via B4 Relay 2025-02-13 19:32 ` kernel test robot 2025-02-17 5:03 ` kernel test robot 2025-03-02 17:31 ` Guenter Roeck 2025-03-13 20:12 ` Cryolitia PukNgae 2025-02-11 7:01 ` [PATCH v5 2/2] hwmon: document: add gpd-fan Cryolitia PukNgae via B4 Relay
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox