// SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2020 The Linux Foundation. All rights reserved. */ #include #include #include #include #include #include "core.h" #include "debug.h" static int ath11k_thermal_get_max_throttle_state(struct thermal_cooling_device *cdev, unsigned long *state) { *state = ATH11K_THERMAL_THROTTLE_MAX; return 0; } static int ath11k_thermal_get_cur_throttle_state(struct thermal_cooling_device *cdev, unsigned long *state) { struct ath11k *ar = cdev->devdata; mutex_lock(&ar->conf_mutex); *state = ar->thermal.throttle_state; mutex_unlock(&ar->conf_mutex); return 0; } static int ath11k_thermal_set_cur_throttle_state(struct thermal_cooling_device *cdev, unsigned long throttle_state) { struct ath11k *ar = cdev->devdata; int ret; if (throttle_state > ATH11K_THERMAL_THROTTLE_MAX) { ath11k_warn(ar->ab, "throttle state %ld is exceeding the limit %d\n", throttle_state, ATH11K_THERMAL_THROTTLE_MAX); return -EINVAL; } mutex_lock(&ar->conf_mutex); ret = ath11k_thermal_set_throttling(ar, throttle_state); if (ret == 0) ar->thermal.throttle_state = throttle_state; mutex_unlock(&ar->conf_mutex); return ret; } static const struct thermal_cooling_device_ops ath11k_thermal_ops = { .get_max_state = ath11k_thermal_get_max_throttle_state, .get_cur_state = ath11k_thermal_get_cur_throttle_state, .set_cur_state = ath11k_thermal_set_cur_throttle_state, }; static ssize_t ath11k_thermal_show_temp(struct device *dev, struct device_attribute *attr, char *buf) { struct ath11k *ar = dev_get_drvdata(dev); int ret, temperature; unsigned long time_left; mutex_lock(&ar->conf_mutex); /* Can't get temperature when the card is off */ if (ar->state != ATH11K_STATE_ON) { ret = -ENETDOWN; goto out; } reinit_completion(&ar->thermal.wmi_sync); ret = ath11k_wmi_send_pdev_temperature_cmd(ar); if (ret) { ath11k_warn(ar->ab, "failed to read temperature %d\n", ret); goto out; } if (test_bit(ATH11K_FLAG_CRASH_FLUSH, &ar->ab->dev_flags)) { ret = -ESHUTDOWN; goto out; } time_left = wait_for_completion_timeout(&ar->thermal.wmi_sync, ATH11K_THERMAL_SYNC_TIMEOUT_HZ); if (!time_left) { ath11k_warn(ar->ab, "failed to synchronize thermal read\n"); ret = -ETIMEDOUT; goto out; } spin_lock_bh(&ar->data_lock); temperature = ar->thermal.temperature; spin_unlock_bh(&ar->data_lock); /* display in millidegree celcius */ ret = snprintf(buf, PAGE_SIZE, "%d\n", temperature * 1000); out: mutex_unlock(&ar->conf_mutex); return ret; } void ath11k_thermal_event_temperature(struct ath11k *ar, int temperature) { spin_lock_bh(&ar->data_lock); ar->thermal.temperature = temperature; spin_unlock_bh(&ar->data_lock); complete(&ar->thermal.wmi_sync); } static SENSOR_DEVICE_ATTR(temp1_input, 0444, ath11k_thermal_show_temp, NULL, 0); static struct attribute *ath11k_hwmon_attrs[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, NULL, }; ATTRIBUTE_GROUPS(ath11k_hwmon); int ath11k_thermal_set_throttling(struct ath11k *ar, u32 throttle_state) { struct ath11k_base *sc = ar->ab; struct thermal_mitigation_params param; int ret = 0; lockdep_assert_held(&ar->conf_mutex); if (ar->state != ATH11K_STATE_ON) return 0; memset(¶m, 0, sizeof(param)); param.pdev_id = ar->pdev->pdev_id; param.enable = throttle_state ? 1 : 0; param.dc = ATH11K_THERMAL_DEFAULT_DUTY_CYCLE; param.dc_per_event = 0xFFFFFFFF; param.levelconf[0].tmplwm = ATH11K_THERMAL_TEMP_LOW_MARK; param.levelconf[0].tmphwm = ATH11K_THERMAL_TEMP_HIGH_MARK; param.levelconf[0].dcoffpercent = throttle_state; param.levelconf[0].priority = 0; /* disable all data tx queues */ ret = ath11k_wmi_send_thermal_mitigation_param_cmd(ar, ¶m); if (ret) { ath11k_warn(sc, "failed to send thermal mitigation duty cycle %u ret %d\n", throttle_state, ret); } return ret; } int ath11k_thermal_register(struct ath11k_base *sc) { struct thermal_cooling_device *cdev; struct device *hwmon_dev; struct ath11k *ar; struct ath11k_pdev *pdev; int i, ret; for (i = 0; i < sc->num_radios; i++) { pdev = &sc->pdevs[i]; ar = pdev->ar; if (!ar) continue; cdev = thermal_cooling_device_register("ath11k_thermal", ar, &ath11k_thermal_ops); if (IS_ERR(cdev)) { ath11k_err(sc, "failed to setup thermal device result: %ld\n", PTR_ERR(cdev)); ret = -EINVAL; goto err_thermal_destroy; } ar->thermal.cdev = cdev; ret = sysfs_create_link(&ar->hw->wiphy->dev.kobj, &cdev->device.kobj, "cooling_device"); if (ret) { ath11k_err(sc, "failed to create cooling device symlink\n"); goto err_thermal_destroy; } if (!IS_REACHABLE(CONFIG_HWMON)) return 0; hwmon_dev = devm_hwmon_device_register_with_groups(&ar->hw->wiphy->dev, "ath11k_hwmon", ar, ath11k_hwmon_groups); if (IS_ERR(hwmon_dev)) { ath11k_err(ar->ab, "failed to register hwmon device: %ld\n", PTR_ERR(hwmon_dev)); ret = -EINVAL; goto err_thermal_destroy; } } return 0; err_thermal_destroy: ath11k_thermal_unregister(sc); return ret; } void ath11k_thermal_unregister(struct ath11k_base *sc) { struct ath11k *ar; struct ath11k_pdev *pdev; int i; for (i = 0; i < sc->num_radios; i++) { pdev = &sc->pdevs[i]; ar = pdev->ar; if (!ar) continue; sysfs_remove_link(&ar->hw->wiphy->dev.kobj, "cooling_device"); thermal_cooling_device_unregister(ar->thermal.cdev); } }