* [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
* [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
* 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
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;
as well as URLs for NNTP newsgroup(s).