From ab64dbf5f0965a425f9ae1ae3929f261f21990da Mon Sep 17 00:00:00 2001 From: Shaohan Yao Date: Mon, 19 Sep 2022 10:28:12 +0800 Subject: [PATCH] hwmon: (pwm-fan) Add system monitor notifier Add support to change fan speed according to temperature, and force to update pwm state when suspend and resume as the configuration of pwm is lost. Signed-off-by: Shaohan Yao Signed-off-by: Finley Xiao Change-Id: I05b286d1373dd4ace2fb519f4598008b851e4eff --- .../devicetree/bindings/hwmon/pwm-fan.txt | 5 + drivers/hwmon/pwm-fan.c | 110 +++++++++++++++++- 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/pwm-fan.txt b/Documentation/devicetree/bindings/hwmon/pwm-fan.txt index 41b76762953a..457ce1663d6a 100644 --- a/Documentation/devicetree/bindings/hwmon/pwm-fan.txt +++ b/Documentation/devicetree/bindings/hwmon/pwm-fan.txt @@ -17,6 +17,11 @@ Optional properties: - pulses-per-revolution : define the tachometer pulses per fan revolution as an integer (default is 2 interrupts per revolution). The value must be greater than zero. +- rockchip,temp-trips : The property is an array of 2-tuples items, and + each item consists of temperature in millicelsius and + pwm cooling state. This depends on CONFIG_ROCKCHIP_SYSTEM_MONITOR. + If add the property the fan cooling state will be changed + by system monitor. Otherwise, use the default thermal governor. Example: fan0: pwm-fan { diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index ec171f2b684a..6298da53ef14 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -19,9 +19,15 @@ #include #include #include +#include #define MAX_PWM 255 +struct thermal_trips { + int temp; + int state; +}; + struct pwm_fan_ctx { struct mutex lock; struct pwm_device *pwm; @@ -39,6 +45,9 @@ struct pwm_fan_ctx { unsigned int pwm_fan_max_state; unsigned int *pwm_fan_cooling_levels; struct thermal_cooling_device *cdev; + struct notifier_block thermal_nb; + struct thermal_trips *thermal_trips; + bool thermal_notifier_is_ok; }; /* This handler assumes self resetting edge triggered interrupt. */ @@ -278,6 +287,94 @@ static void pwm_fan_pwm_disable(void *__ctx) del_timer_sync(&ctx->rpm_timer); } +static int pwm_fan_get_thermal_trips(struct device *dev, char *porp_name, + struct thermal_trips **trips) +{ + struct device_node *np = dev->of_node; + struct thermal_trips *thermal_trips; + const struct property *prop; + int count, i; + + prop = of_find_property(np, porp_name, NULL); + if (!prop) + return -EINVAL; + if (!prop->value) + return -ENODATA; + count = of_property_count_u32_elems(np, porp_name); + if (count < 0) + return -EINVAL; + if (count % 2) + return -EINVAL; + thermal_trips = devm_kzalloc(dev, + sizeof(*thermal_trips) * (count / 2 + 1), + GFP_KERNEL); + if (!thermal_trips) + return -ENOMEM; + + for (i = 0; i < count / 2; i++) { + of_property_read_u32_index(np, porp_name, 2 * i, + &thermal_trips[i].temp); + of_property_read_u32_index(np, porp_name, 2 * i + 1, + &thermal_trips[i].state); + } + thermal_trips[i].temp = 0; + thermal_trips[i].state = INT_MAX; + + *trips = thermal_trips; + + return 0; +} + +static int pwm_fan_temp_to_state(struct pwm_fan_ctx *ctx, int temp) +{ + struct thermal_trips *trips = ctx->thermal_trips; + int i, state = 0; + + for (i = 0; trips[i].state != INT_MAX; i++) { + if (temp >= trips[i].temp) + state = trips[i].state; + } + + return state; +} + +static int pwm_fan_thermal_notifier_call(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct pwm_fan_ctx *ctx = container_of(nb, struct pwm_fan_ctx, thermal_nb); + struct system_monitor_event_data *event_data = data; + int state, ret; + + if (event != SYSTEM_MONITOR_CHANGE_TEMP) + return NOTIFY_OK; + + state = pwm_fan_temp_to_state(ctx, event_data->temp); + if (state > ctx->pwm_fan_max_state) + return NOTIFY_BAD; + if (state == ctx->pwm_fan_state) + return NOTIFY_OK; + + ret = __set_pwm(ctx, ctx->pwm_fan_cooling_levels[state]); + if (ret) + return NOTIFY_BAD; + + ctx->pwm_fan_state = state; + + return NOTIFY_OK; +} + +static int pwm_fan_register_thermal_notifier(struct device *dev, + struct pwm_fan_ctx *ctx) +{ + if (pwm_fan_get_thermal_trips(dev, "rockchip,temp-trips", + &ctx->thermal_trips)) + return -EINVAL; + + ctx->thermal_nb.notifier_call = pwm_fan_thermal_notifier_call; + + return rockchip_system_monitor_register_notifier(&ctx->thermal_nb); +} + static int pwm_fan_probe(struct platform_device *pdev) { struct thermal_cooling_device *cdev; @@ -379,6 +476,15 @@ static int pwm_fan_probe(struct platform_device *pdev) return ret; ctx->pwm_fan_state = ctx->pwm_fan_max_state; + if (IS_REACHABLE(CONFIG_ROCKCHIP_SYSTEM_MONITOR) && + of_find_property(dev->of_node, "rockchip,temp-trips", NULL)) { + ret = pwm_fan_register_thermal_notifier(dev, ctx); + if (ret) + dev_err(dev, "Failed to register thermal notifier: %d\n", ret); + else + ctx->thermal_notifier_is_ok = true; + return 0; + } if (IS_ENABLED(CONFIG_THERMAL)) { cdev = devm_thermal_of_cooling_device_register(dev, dev->of_node, "pwm-fan", ctx, &pwm_fan_cooling_ops); @@ -404,7 +510,7 @@ static int pwm_fan_disable(struct device *dev) pwm_get_args(ctx->pwm, &args); - if (ctx->pwm_value) { + if (ctx->pwm_value || ctx->thermal_notifier_is_ok) { ret = pwm_config(ctx->pwm, 0, args.period); if (ret < 0) return ret; @@ -449,7 +555,7 @@ static int pwm_fan_resume(struct device *dev) } } - if (ctx->pwm_value == 0) + if (ctx->pwm_value == 0 && !ctx->thermal_notifier_is_ok) return 0; pwm_get_args(ctx->pwm, &pargs);