From nobody Mon Feb 9 21:38:45 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 018EB27BF78; Tue, 29 Apr 2025 08:14:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745914467; cv=none; b=cRotBPNyKSvuJ+LXRDrCLZckdi51Xf+ztCqIhm6VOw1r6EHjR1E+9ICRbeFekaHr1CeNqatLXRS0/CVb1Q3WuBeCRQMffDeaPE8rayUYvzILIoY9UA1qvSnuIjeyzNcpnpWCz3NqFgI7ady1lkAqifDWvUgARHzWog3XxpHOuXM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745914467; c=relaxed/simple; bh=+bsictYrrfhDR0FKWAoqDa8atKjZ5xdocNz94lWjZiw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Vl/x+H9vb3LEA8rguRib083McmmnddaeYJmIalH4zE9imDMqoYjlrVH1Hk+2PPMprtQ1o0m06gFP9OkyK6Ncg0yg6Th9puPiZi67f2f+ubDeEXUPPIe/mt1q08THonIRI1SD6KwfGbI9h+kUEMSP9k/w3nWZRCuo2X6wlG7UX3c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=uNyX7eur; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="uNyX7eur" Received: by smtp.kernel.org (Postfix) with ESMTPS id 8ED96C4CEED; Tue, 29 Apr 2025 08:14:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1745914466; bh=+bsictYrrfhDR0FKWAoqDa8atKjZ5xdocNz94lWjZiw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=uNyX7eurN8SBwazI6RFmCkacUTfg3TqhlU+Pys6FbkX7wETVSysIUEyc55eXGHJZc Gx5Jnf7cDq4NO8FdUoI+S4B/ZWE0biNfqSylVMgHlQ5MbyvsQktqqqaZgB3cl1lQs1 FfayBCJC/hws2sGXiQSDTod7Gx8F09wurtR6tZ+DKAyMwkm+d0pd8qw4A/auGJFAN5 Lvsb+bzqaSyuYw8S9Y1TcNrdho7P6mHYKkVogCEEry4LLfr781EPI/I/Noo8/9diyY 6GrtIjFvr2UtUn7L/PNWoTeumVVYHWDNFI88XoeR8l2G4uq4Cz1MiOLIdP08xyusyo 5IF2GrzJWLEIg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7E979C3ABA9; Tue, 29 Apr 2025 08:14:26 +0000 (UTC) From: Sung-Chi Li via B4 Relay Date: Tue, 29 Apr 2025 16:14:21 +0800 Subject: [PATCH 1/3] platform/chrome: update pwm fan control host commands Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250429-cros_ec_fan-v1-1-a8d9e3efbb1a@chromium.org> References: <20250429-cros_ec_fan-v1-0-a8d9e3efbb1a@chromium.org> In-Reply-To: <20250429-cros_ec_fan-v1-0-a8d9e3efbb1a@chromium.org> To: Benson Leung , Guenter Roeck , =?utf-8?q?Thomas_Wei=C3=9Fschuh?= , Jean Delvare , Guenter Roeck , Jonathan Corbet Cc: chrome-platform@lists.linux.dev, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-doc@vger.kernel.org, Sung-Chi Li , Sung-Chi Li X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1745914465; l=2190; i=lschyi@chromium.org; s=20250429; h=from:subject:message-id; bh=O1PYKv+vMUowWjF4t+HpncYFKGunVlMm5UFqi3GIm7U=; b=POpBecDQV242cR2D1M+nJ6eAN/ptTmLgHvrXu4FoPHKD/zv+w5KHkNdZ+4APJ3pE5EKXkUrce EAHZCIJF5wCCAlIjcRQBMzH5Dt2d996+4OlIbi5DdC2FzSnphbWFwZh X-Developer-Key: i=lschyi@chromium.org; a=ed25519; pk=9gCZPRJmYyHDt6VN9FV2UreFcUr73JFrwYvmsltW9Y8= X-Endpoint-Received: by B4 Relay for lschyi@chromium.org/20250429 with auth_id=392 X-Original-From: Sung-Chi Li Reply-To: lschyi@chromium.org From: Sung-Chi Li Update cros_ec_commands.h to include definitions for getting PWM fan duty, getting and setting the fan control mode. Signed-off-by: Sung-Chi Li Acked-by: Tzung-Bi Shih --- include/linux/platform_data/cros_ec_commands.h | 29 ++++++++++++++++++++++= +++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux= /platform_data/cros_ec_commands.h index 1f4e4f2b89bb936b4b1c3f4162fec203b196cbc8..2ac1a30f9a3195bfc9dffc72fe5= c5b92d83f1ef2 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -1825,6 +1825,16 @@ struct ec_response_pwm_get_duty { uint16_t duty; /* Duty cycle, EC_PWM_MAX_DUTY =3D 100% */ } __ec_align2; =20 +#define EC_CMD_PWM_GET_FAN_DUTY 0x0027 + +struct ec_params_pwm_get_fan_duty { + uint8_t fan_idx; +} __ec_align1; + +struct ec_response_pwm_get_fan_duty { + uint32_t percent; /* Percentage of duty cycle, ranging from 0 ~ 100 */ +} __ec_align4; + /*************************************************************************= ****/ /* * Lightbar commands. This looks worse than it is. Since we only use one H= OST @@ -3105,14 +3115,31 @@ struct ec_params_thermal_set_threshold_v1 { =20 /*************************************************************************= ***/ =20 -/* Toggle automatic fan control */ +/* Set or get fan control mode */ #define EC_CMD_THERMAL_AUTO_FAN_CTRL 0x0052 =20 +enum ec_auto_fan_ctrl_cmd { + EC_AUTO_FAN_CONTROL_CMD_SET =3D 0, + EC_AUTO_FAN_CONTROL_CMD_GET, +}; + /* Version 1 of input params */ struct ec_params_auto_fan_ctrl_v1 { uint8_t fan_idx; } __ec_align1; =20 +/* Version 2 of input params */ +struct ec_params_auto_fan_ctrl_v2 { + uint8_t fan_idx; + uint8_t cmd; /* enum ec_auto_fan_ctrl_cmd */ + uint8_t set_auto; /* only used with EC_AUTO_FAN_CONTROL_CMD_SET - bool + */ +} __ec_align4; + +struct ec_response_auto_fan_control { + uint8_t is_auto; /* bool */ +} __ec_align1; + /* Get/Set TMP006 calibration data */ #define EC_CMD_TMP006_GET_CALIBRATION 0x0053 #define EC_CMD_TMP006_SET_CALIBRATION 0x0054 --=20 2.49.0.901.g37484f566f-goog From nobody Mon Feb 9 21:38:45 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 018082777ED; Tue, 29 Apr 2025 08:14:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745914467; cv=none; b=DLpFGuDb1dmN9Ijvs2JswdMyB0cbdyoErQMF2rbQAft1YWB4vJMKEE36WJwMTfWbalZ18REDLQBhrK3Rixbw4NDGJ0U0pQfDoH/YBRdCCCznnFKH3g1slkNOHOe95TIUq71WRJ+X+kcPZTXw2s4b66C1dx+E5/E13TJl+V+czAI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745914467; c=relaxed/simple; bh=MXCgg9Id2kReDLgXWtewe/Upj7AbX9Sg7GJUBOz5i7U=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Ul5CQn3xGrlqUGs5OOizJN4kIEKkFxYLliQk7IOxeWlAOGeltppXuxoT3LsMq4ZDwhFRrFjB31JeOwM06iJgpoc8UN+78JH3o6jyCdOCQjQPsW2sLoVDW2ZhaJKKmVZnnL4Uv9jyCioJgdnAKxBxkNQM2aygIhZpFhxzbNT3d3g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=fpH+RKnp; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="fpH+RKnp" Received: by smtp.kernel.org (Postfix) with ESMTPS id 97DA2C4CEEA; Tue, 29 Apr 2025 08:14:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1745914466; bh=MXCgg9Id2kReDLgXWtewe/Upj7AbX9Sg7GJUBOz5i7U=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=fpH+RKnp76ANzPQM/lnNM5jgXMjMM5sDgqf48jgFkgZEOjNmfGC5YG8SOBAF7+wfp cM2JTpHQefGDJSEC/Q4+2YxPWQ6V8d/1yMzHL4EemJaBOsLRkka5R+gX3g7HtowM6k X+VZa3FwTX9jRBq2igDJWqL9UCjojDLoPm14yhYGA06261uMxN05Cwdf73wUM3VvBA 7nJfzR8NN+ufEVtbdXrLpjuQ8wQ5J9aCKRtoJG/hGXI9/emPMpQ34ynepcHYzrWTfs lYih14pF1rS7j/34RYmdFTvX0E3Byx9Y+YVb0XO32bbLVv4pexmjujQ/X0tHMUVFn8 DDzM9JL4Xym9A== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9013FC3ABAA; Tue, 29 Apr 2025 08:14:26 +0000 (UTC) From: Sung-Chi Li via B4 Relay Date: Tue, 29 Apr 2025 16:14:22 +0800 Subject: [PATCH 2/3] hwmon: (cros_ec) add PWM control over fans Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250429-cros_ec_fan-v1-2-a8d9e3efbb1a@chromium.org> References: <20250429-cros_ec_fan-v1-0-a8d9e3efbb1a@chromium.org> In-Reply-To: <20250429-cros_ec_fan-v1-0-a8d9e3efbb1a@chromium.org> To: Benson Leung , Guenter Roeck , =?utf-8?q?Thomas_Wei=C3=9Fschuh?= , Jean Delvare , Guenter Roeck , Jonathan Corbet Cc: chrome-platform@lists.linux.dev, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-doc@vger.kernel.org, Sung-Chi Li , Sung-Chi Li X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1745914465; l=12358; i=lschyi@chromium.org; s=20250429; h=from:subject:message-id; bh=4PCkSeWQAdaLAbrCAAHRvD/qTzf/L9llle707SU1Nak=; b=bNvY13jjmgoYCBdpc4WQB/RpDf5YARzOG6jGvjexTcqH0YrJyn6XNXC0tjFd3HkgiVCaGIJt4 5CZ0D370iqyBGHQkCqYWm3Lqln56+zfoBS+kTUVrWGJvWX3Kbxap6Vs X-Developer-Key: i=lschyi@chromium.org; a=ed25519; pk=9gCZPRJmYyHDt6VN9FV2UreFcUr73JFrwYvmsltW9Y8= X-Endpoint-Received: by B4 Relay for lschyi@chromium.org/20250429 with auth_id=392 X-Original-From: Sung-Chi Li Reply-To: lschyi@chromium.org From: Sung-Chi Li Newer EC firmware supports controlling fans through host commands, so adding corresponding implementations for controlling these fans in the driver for other kernel services and userspace to control them. The driver will first probe the supported host command versions (get and set of fan PWM values, get and set of fan control mode) to see if the connected EC fulfills the requirements of controlling the fan, then exposes corresponding sysfs nodes for userspace to control the fan with corresponding read and write implementations. As EC will automatically change the fan mode to auto when the device is suspended, the power management hooks are added as well to keep the fan control mode and fan PWM value consistent during suspend and resume. As we need to access the hwmon device in the power management hook, update the driver by storing the hwmon device in the driver data as well. Signed-off-by: Sung-Chi Li --- Documentation/hwmon/cros_ec_hwmon.rst | 5 +- drivers/hwmon/cros_ec_hwmon.c | 237 ++++++++++++++++++++++++++++++= +++- 2 files changed, 237 insertions(+), 5 deletions(-) diff --git a/Documentation/hwmon/cros_ec_hwmon.rst b/Documentation/hwmon/cr= os_ec_hwmon.rst index 47ecae983bdbef4bfcafc5dd2fff3de039f77f8e..5b802be120438732529c3d25b1a= fa8b4ee353305 100644 --- a/Documentation/hwmon/cros_ec_hwmon.rst +++ b/Documentation/hwmon/cros_ec_hwmon.rst @@ -23,4 +23,7 @@ ChromeOS embedded controller used in Chromebooks and othe= r devices. =20 The channel labels exposed via hwmon are retrieved from the EC itself. =20 -Fan and temperature readings are supported. +Fan and temperature readings are supported. PWM fan control is also suppor= ted if +the EC also supports setting fan PWM values and fan mode. Note that EC will +switch fan control mode back to auto when suspended. This driver will rest= ore +the fan state before suspended. diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c index 9991c3fa020ac859cbbff29dfb669e53248df885..1139074d3eb003ee72bbe54a954= 647ced40f6d21 100644 --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -17,10 +17,17 @@ =20 #define DRV_NAME "cros-ec-hwmon" =20 +struct cros_ec_hwmon_platform_priv { + struct device *hwmon_dev; +}; + struct cros_ec_hwmon_priv { struct cros_ec_device *cros_ec; const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_E= NTRIES]; u8 usable_fans; + bool fan_control_supported; + u8 manual_fans; /* bits to indicate whether the fan is set to manual */ + u8 manual_fan_pwm_values[EC_FAN_SPEED_ENTRIES]; }; =20 static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8= index, u16 *speed) @@ -36,6 +43,51 @@ static int cros_ec_hwmon_read_fan_speed(struct cros_ec_d= evice *cros_ec, u8 index return 0; } =20 +static int cros_ec_hwmon_read_pwm_raw_value(struct cros_ec_device *cros_ec, + u8 index, u8 *pwm_value) +{ + struct ec_params_pwm_get_fan_duty req =3D { + .fan_idx =3D index, + }; + struct ec_response_pwm_get_fan_duty resp; + int ret =3D cros_ec_cmd(cros_ec, 0, EC_CMD_PWM_GET_FAN_DUTY, &req, + sizeof(req), &resp, sizeof(resp)); + + if (ret < 0) + return ret; + + *pwm_value =3D (u8)(le32_to_cpu(resp.percent)); + return 0; +} + +static int cros_ec_hwmon_read_pwm_value(struct cros_ec_device *cros_ec, + u8 index, u8 *pwm_value) +{ + int ret =3D cros_ec_hwmon_read_pwm_raw_value(cros_ec, index, pwm_value); + + if (ret =3D=3D 0) + *pwm_value =3D *pwm_value * 255 / 100; + return ret; +} + +static int cros_ec_hwmon_read_pwm_enable(struct cros_ec_device *cros_ec, + u8 index, u8 *control_method) +{ + struct ec_params_auto_fan_ctrl_v2 req =3D { + .fan_idx =3D index, + .cmd =3D EC_AUTO_FAN_CONTROL_CMD_GET, + }; + struct ec_response_auto_fan_control resp; + int ret =3D cros_ec_cmd(cros_ec, 2, EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, + sizeof(req), &resp, sizeof(resp)); + + if (ret < 0) + return ret; + + *control_method =3D (resp.is_auto) ? 2 : 1; + return 0; +} + static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 inde= x, u8 *temp) { unsigned int offset; @@ -76,6 +128,8 @@ static int cros_ec_hwmon_read(struct device *dev, enum h= wmon_sensor_types type, struct cros_ec_hwmon_priv *priv =3D dev_get_drvdata(dev); int ret =3D -EOPNOTSUPP; u16 speed; + u8 pwm_value; + u8 control_method; u8 temp; =20 if (type =3D=3D hwmon_fan) { @@ -92,6 +146,18 @@ static int cros_ec_hwmon_read(struct device *dev, enum = hwmon_sensor_types type, if (ret =3D=3D 0) *val =3D cros_ec_hwmon_is_error_fan(speed); } + } else if (type =3D=3D hwmon_pwm) { + if (attr =3D=3D hwmon_pwm_enable) { + ret =3D cros_ec_hwmon_read_pwm_enable( + priv->cros_ec, channel, &control_method); + if (ret =3D=3D 0) + *val =3D control_method; + } else if (attr =3D=3D hwmon_pwm_input) { + ret =3D cros_ec_hwmon_read_pwm_value(priv->cros_ec, + channel, &pwm_value); + if (ret =3D=3D 0) + *val =3D pwm_value; + } } else if (type =3D=3D hwmon_temp) { if (attr =3D=3D hwmon_temp_input) { ret =3D cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp); @@ -124,6 +190,97 @@ static int cros_ec_hwmon_read_string(struct device *de= v, enum hwmon_sensor_types return -EOPNOTSUPP; } =20 +static int cros_ec_hwmon_write_pwm_value(struct cros_ec_device *cros_ec, + u8 index, u8 val) +{ + struct ec_params_pwm_set_fan_duty_v1 req =3D { + .percent =3D val, + .fan_idx =3D index, + }; + int ret =3D cros_ec_cmd(cros_ec, 1, EC_CMD_PWM_SET_FAN_DUTY, &req, + sizeof(req), NULL, 0); + + if (ret < 0) + return ret; + return 0; +} + +static int cros_ec_hwmon_set_pwm_raw_value(struct cros_ec_hwmon_priv *priv, + u8 index, u8 val) +{ + int ret; + + if (!(priv->manual_fans & BIT(index))) + return -ECANCELED; + + ret =3D cros_ec_hwmon_write_pwm_value(priv->cros_ec, index, val); + if (ret =3D=3D 0) + priv->manual_fan_pwm_values[index] =3D val; + return ret; +} + +static int cros_ec_hwmon_set_pwm_value(struct cros_ec_hwmon_priv *priv, + u8 index, u8 val) +{ + return cros_ec_hwmon_set_pwm_raw_value(priv, index, + (((uint32_t)val) * 100 / 255)); +} + +static int cros_ec_hwmon_write_pwm_enable(struct cros_ec_device *cros_ec, + u8 index, u8 val) +{ + struct ec_params_auto_fan_ctrl_v2 req =3D { + .fan_idx =3D index, + .cmd =3D EC_AUTO_FAN_CONTROL_CMD_SET, + }; + int ret; + + /* No CROS EC supports no fan speed control */ + if (val =3D=3D 0) + return -EOPNOTSUPP; + + req.set_auto =3D (val !=3D 1) ? true : false; + ret =3D cros_ec_cmd(cros_ec, 2, EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, + sizeof(req), NULL, 0); + if (ret < 0) + return ret; + return 0; +} + +static int cros_ec_hwmon_set_pwm_control_method(struct cros_ec_hwmon_priv = *priv, + u8 index, u8 val) +{ + int ret =3D cros_ec_hwmon_write_pwm_enable(priv->cros_ec, index, val); + + if (ret =3D=3D 0) { + if (val =3D=3D 1) + priv->manual_fans |=3D BIT(index); + else + priv->manual_fans &=3D ~BIT(index); + } + return ret; +} + +static int cros_ec_hwmon_write(struct device *dev, enum hwmon_sensor_types= type, + u32 attr, int channel, long val) +{ + struct cros_ec_hwmon_priv *priv =3D dev_get_drvdata(dev); + + if (type =3D=3D hwmon_pwm) { + switch (attr) { + case hwmon_pwm_input: + return cros_ec_hwmon_set_pwm_value(priv, channel, val); + case hwmon_pwm_enable: + return cros_ec_hwmon_set_pwm_control_method( + priv, channel, val); + default: + return -EOPNOTSUPP; + } + } + + return -EOPNOTSUPP; +} + static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_senso= r_types type, u32 attr, int channel) { @@ -132,6 +289,9 @@ static umode_t cros_ec_hwmon_is_visible(const void *dat= a, enum hwmon_sensor_type if (type =3D=3D hwmon_fan) { if (priv->usable_fans & BIT(channel)) return 0444; + } else if (type =3D=3D hwmon_pwm && priv->fan_control_supported) { + if (priv->usable_fans & BIT(channel)) + return 0644; } else if (type =3D=3D hwmon_temp) { if (priv->temp_sensor_names[channel]) return 0444; @@ -147,6 +307,11 @@ static const struct hwmon_channel_info * const cros_ec= _hwmon_info[] =3D { HWMON_F_INPUT | HWMON_F_FAULT, HWMON_F_INPUT | HWMON_F_FAULT, HWMON_F_INPUT | HWMON_F_FAULT), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL, @@ -178,6 +343,7 @@ static const struct hwmon_channel_info * const cros_ec_= hwmon_info[] =3D { static const struct hwmon_ops cros_ec_hwmon_ops =3D { .read =3D cros_ec_hwmon_read, .read_string =3D cros_ec_hwmon_read_string, + .write =3D cros_ec_hwmon_write, .is_visible =3D cros_ec_hwmon_is_visible, }; =20 @@ -233,13 +399,35 @@ static void cros_ec_hwmon_probe_fans(struct cros_ec_h= wmon_priv *priv) } } =20 +static void +cros_ec_hwmon_probe_fan_control_supported(struct cros_ec_hwmon_priv *priv) +{ + int ret; + + priv->fan_control_supported =3D false; + + ret =3D cros_ec_get_cmd_versions(priv->cros_ec, EC_CMD_PWM_GET_FAN_DUTY); + if (ret < 0 || !(ret & EC_VER_MASK(0))) + return; + + ret =3D cros_ec_get_cmd_versions(priv->cros_ec, EC_CMD_PWM_SET_FAN_DUTY); + if (ret < 0 || !(ret & EC_VER_MASK(1))) + return; + + ret =3D cros_ec_get_cmd_versions(priv->cros_ec, EC_CMD_THERMAL_AUTO_FAN_C= TRL); + if (ret < 0 || !(ret & EC_VER_MASK(2))) + return; + + priv->fan_control_supported =3D true; +} + static int cros_ec_hwmon_probe(struct platform_device *pdev) { struct device *dev =3D &pdev->dev; struct cros_ec_dev *ec_dev =3D dev_get_drvdata(dev->parent); struct cros_ec_device *cros_ec =3D ec_dev->ec_dev; + struct cros_ec_hwmon_platform_priv *platform_priv; struct cros_ec_hwmon_priv *priv; - struct device *hwmon_dev; u8 thermal_version; int ret; =20 @@ -251,6 +439,10 @@ static int cros_ec_hwmon_probe(struct platform_device = *pdev) if (thermal_version =3D=3D 0) return -ENODEV; =20 + platform_priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!platform_priv) + return -ENOMEM; + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -259,11 +451,47 @@ static int cros_ec_hwmon_probe(struct platform_device= *pdev) =20 cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version); cros_ec_hwmon_probe_fans(priv); + cros_ec_hwmon_probe_fan_control_supported(priv); =20 - hwmon_dev =3D devm_hwmon_device_register_with_info(dev, "cros_ec", priv, - &cros_ec_hwmon_chip_info, NULL); + platform_priv->hwmon_dev =3D devm_hwmon_device_register_with_info( + dev, "cros_ec", priv, &cros_ec_hwmon_chip_info, NULL); + dev_set_drvdata(dev, platform_priv); =20 - return PTR_ERR_OR_ZERO(hwmon_dev); + return PTR_ERR_OR_ZERO(platform_priv->hwmon_dev); +} + +static int cros_ec_hwmon_resume(struct platform_device *pdev) +{ + const struct cros_ec_hwmon_platform_priv *platform_priv =3D + dev_get_drvdata(&pdev->dev); + const struct cros_ec_hwmon_priv *priv =3D + dev_get_drvdata(platform_priv->hwmon_dev); + size_t i; + int ret; + + if (!priv->fan_control_supported) + return 0; + + /* + * EC sets fan control to auto after suspended, restore settings to + * before suspended. + */ + for (i =3D 0; i < EC_FAN_SPEED_ENTRIES; i++) { + if (!(priv->manual_fans & BIT(i))) + continue; + + /* + * Setting fan PWM value to EC will change the mode to manual + * for that fan in EC as well, so we do not need to issue a + * separate fan mode to manual call. + */ + ret =3D cros_ec_hwmon_write_pwm_value( + priv->cros_ec, i, priv->manual_fan_pwm_values[i]); + if (ret) + return ret; + } + + return 0; } =20 static const struct platform_device_id cros_ec_hwmon_id[] =3D { @@ -274,6 +502,7 @@ static const struct platform_device_id cros_ec_hwmon_id= [] =3D { static struct platform_driver cros_ec_hwmon_driver =3D { .driver.name =3D DRV_NAME, .probe =3D cros_ec_hwmon_probe, + .resume =3D cros_ec_hwmon_resume, .id_table =3D cros_ec_hwmon_id, }; module_platform_driver(cros_ec_hwmon_driver); --=20 2.49.0.901.g37484f566f-goog From nobody Mon Feb 9 21:38:45 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 17B6327C150; Tue, 29 Apr 2025 08:14:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745914467; cv=none; b=jSXWnkm1skNsoP8EhjW5fLK6SK0U4LMXODYrZlBTtffkJr6dbtqS2z4glLwBIRC8e3hHv/NancEExLhEp5KBsccvf8oV8ThrM2sD7XOzTQYdLrmrQ+p1j+iX01GeyT/fHhguDb3WQbJvgHVPWDC5LHcNfr6SkkgR7eLPGumerqY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745914467; c=relaxed/simple; bh=1+DCRbeGHM7PQfOjrqPCPY/9L/UOCvZq4UXq4SRoJPs=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ZKXpK7NsAqU1Qb89n0NyE/yAvx5pJY2IXkXb+2PP7ej/UFU8CVXnOZi9hGJVi1ccsxEPC7y78Sh2bg4Rg4mBlFGXlf/qr/55XFNPuuETigugqZEyhe+vchiWMtuqXcXrUDDo9yWNrk7JrPRgPz/rcRhDGJ/8S7cfa7vn6cgnzmc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=b1qucDkz; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="b1qucDkz" Received: by smtp.kernel.org (Postfix) with ESMTPS id A86A2C4CEEF; Tue, 29 Apr 2025 08:14:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1745914466; bh=1+DCRbeGHM7PQfOjrqPCPY/9L/UOCvZq4UXq4SRoJPs=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=b1qucDkz7iOyfooAgJMp4MXEx3oS1KevmoImMsbj45NpWetaVOTmiXIAzKbFH9v7S j2PPA3Bk1CupMxDuQpuooa7JJRhJ/Bcd/oQqB37oY5SzVGfLQZI7qybnnnoyG0LiFW 9tLc6wB3H5zl1qqtL7CwBCH1QWJfJ0LYhGJ4HhyuShr+FWqfm1tEw6nB77WUw1Ufeo o2lMWjUWODwN8jK98EDf4XZfsPBi4jIigoFp3ITchgMTKguuEoZYS/PUX7uqAU/mu/ J8tleYtJYBFwk1SxMVoLc8MT2nkmE6jL6QZuBiUUbX3jtNmx8ruHs0K+CaUDkUWdRj B8r0hR+JEySOg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9FE79C369DC; Tue, 29 Apr 2025 08:14:26 +0000 (UTC) From: Sung-Chi Li via B4 Relay Date: Tue, 29 Apr 2025 16:14:23 +0800 Subject: [PATCH 3/3] hwmon: (cros_ec) register fans into thermal framework cooling devices Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250429-cros_ec_fan-v1-3-a8d9e3efbb1a@chromium.org> References: <20250429-cros_ec_fan-v1-0-a8d9e3efbb1a@chromium.org> In-Reply-To: <20250429-cros_ec_fan-v1-0-a8d9e3efbb1a@chromium.org> To: Benson Leung , Guenter Roeck , =?utf-8?q?Thomas_Wei=C3=9Fschuh?= , Jean Delvare , Guenter Roeck , Jonathan Corbet Cc: chrome-platform@lists.linux.dev, linux-kernel@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-doc@vger.kernel.org, Sung-Chi Li , Sung-Chi Li X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1745914465; l=5122; i=lschyi@chromium.org; s=20250429; h=from:subject:message-id; bh=CzzdhK1tpMOhvzur2FZyTlxUzTR8H+S2R1Q7+/4BMLA=; b=8m4Hke9PBnFU82gso9aL9qCn+SYp3bKudu0m/mK3sHYP/lWXlxINLK68OhfWnHS5ZCDfKzj4l iFhG6t6mxhvB7/9mEPWNu8IE8W7tsB45j2t+IqlZeSb7T1Cb8nGaqp0 X-Developer-Key: i=lschyi@chromium.org; a=ed25519; pk=9gCZPRJmYyHDt6VN9FV2UreFcUr73JFrwYvmsltW9Y8= X-Endpoint-Received: by B4 Relay for lschyi@chromium.org/20250429 with auth_id=392 X-Original-From: Sung-Chi Li Reply-To: lschyi@chromium.org From: Sung-Chi Li Register fans connected under EC as thermal cooling devices as well, so these fans can then work with the thermal framework. During the driver probing phase, we will also try to register each fan as a thermal cooling device based on previous probe result (whether the there are fans connected on that channel, and whether EC supports fan control). The basic get max state, get current state, and set current state methods are then implemented as well. Signed-off-by: Sung-Chi Li --- Documentation/hwmon/cros_ec_hwmon.rst | 2 + drivers/hwmon/cros_ec_hwmon.c | 72 +++++++++++++++++++++++++++++++= ++++ 2 files changed, 74 insertions(+) diff --git a/Documentation/hwmon/cros_ec_hwmon.rst b/Documentation/hwmon/cr= os_ec_hwmon.rst index 5b802be120438732529c3d25b1afa8b4ee353305..82c75bdaf912a116eaafa3149dc= 1252b3f7007d2 100644 --- a/Documentation/hwmon/cros_ec_hwmon.rst +++ b/Documentation/hwmon/cros_ec_hwmon.rst @@ -27,3 +27,5 @@ Fan and temperature readings are supported. PWM fan contr= ol is also supported if the EC also supports setting fan PWM values and fan mode. Note that EC will switch fan control mode back to auto when suspended. This driver will rest= ore the fan state before suspended. +If a fan is controllable, this driver will register that fan as a cooling = device +in the thermal framework as well. diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c index 1139074d3eb003ee72bbe54a954647ced40f6d21..c38c61bba431fe25322793f7dd5= db59fcc95daaf 100644 --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include =20 @@ -30,6 +31,11 @@ struct cros_ec_hwmon_priv { u8 manual_fan_pwm_values[EC_FAN_SPEED_ENTRIES]; }; =20 +struct cros_ec_hwmon_cooling_priv { + struct cros_ec_hwmon_priv *hwmon_priv; + u8 index; +}; + static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8= index, u16 *speed) { int ret; @@ -340,6 +346,38 @@ static const struct hwmon_channel_info * const cros_ec= _hwmon_info[] =3D { NULL }; =20 +static int +cros_ec_hwmon_cooling_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *val) +{ + *val =3D 100; + return 0; +} + +static int +cros_ec_hwmon_cooling_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *val) +{ + const struct cros_ec_hwmon_cooling_priv *priv =3D cdev->devdata; + u8 raw_val; + int ret =3D cros_ec_hwmon_read_pwm_raw_value(priv->hwmon_priv->cros_ec, + priv->index, &raw_val); + + if (ret =3D=3D 0) + *val =3D raw_val; + return ret; +} + +static int +cros_ec_hwmon_cooling_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long val) +{ + const struct cros_ec_hwmon_cooling_priv *priv =3D cdev->devdata; + + return cros_ec_hwmon_set_pwm_raw_value(priv->hwmon_priv, priv->index, + val); +} + static const struct hwmon_ops cros_ec_hwmon_ops =3D { .read =3D cros_ec_hwmon_read, .read_string =3D cros_ec_hwmon_read_string, @@ -347,6 +385,12 @@ static const struct hwmon_ops cros_ec_hwmon_ops =3D { .is_visible =3D cros_ec_hwmon_is_visible, }; =20 +static const struct thermal_cooling_device_ops cros_ec_thermal_cooling_ops= =3D { + .get_max_state =3D cros_ec_hwmon_cooling_get_max_state, + .get_cur_state =3D cros_ec_hwmon_cooling_get_cur_state, + .set_cur_state =3D cros_ec_hwmon_cooling_set_cur_state, +}; + static const struct hwmon_chip_info cros_ec_hwmon_chip_info =3D { .ops =3D &cros_ec_hwmon_ops, .info =3D cros_ec_hwmon_info, @@ -421,6 +465,33 @@ cros_ec_hwmon_probe_fan_control_supported(struct cros_= ec_hwmon_priv *priv) priv->fan_control_supported =3D true; } =20 +static void +cros_ec_hwmon_register_fan_cooling_devices(struct device *dev, + struct cros_ec_hwmon_priv *priv) +{ + struct cros_ec_hwmon_cooling_priv *cpriv; + size_t i; + + if (!priv->fan_control_supported) + return; + + for (i =3D 0; i < EC_FAN_SPEED_ENTRIES; i++) { + if (!(priv->usable_fans & BIT(i))) + continue; + + cpriv =3D devm_kzalloc(dev, sizeof(*cpriv), GFP_KERNEL); + if (!cpriv) + return; + + cpriv->hwmon_priv =3D priv; + cpriv->index =3D i; + devm_thermal_of_cooling_device_register( + dev, NULL, + devm_kasprintf(dev, GFP_KERNEL, "cros-ec-fan%zu", i), + cpriv, &cros_ec_thermal_cooling_ops); + } +} + static int cros_ec_hwmon_probe(struct platform_device *pdev) { struct device *dev =3D &pdev->dev; @@ -452,6 +523,7 @@ static int cros_ec_hwmon_probe(struct platform_device *= pdev) cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version); cros_ec_hwmon_probe_fans(priv); cros_ec_hwmon_probe_fan_control_supported(priv); + cros_ec_hwmon_register_fan_cooling_devices(dev, priv); =20 platform_priv->hwmon_dev =3D devm_hwmon_device_register_with_info( dev, "cros_ec", priv, &cros_ec_hwmon_chip_info, NULL); --=20 2.49.0.901.g37484f566f-goog