1 | // SPDX-License-Identifier: ISC |
2 | /* |
3 | * Copyright (c) 2014-2015 Qualcomm Atheros, Inc. |
4 | * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. |
5 | */ |
6 | |
7 | #include <linux/device.h> |
8 | #include <linux/sysfs.h> |
9 | #include <linux/thermal.h> |
10 | #include <linux/hwmon.h> |
11 | #include <linux/hwmon-sysfs.h> |
12 | #include "core.h" |
13 | #include "debug.h" |
14 | #include "wmi-ops.h" |
15 | |
16 | static int |
17 | ath10k_thermal_get_max_throttle_state(struct thermal_cooling_device *cdev, |
18 | unsigned long *state) |
19 | { |
20 | *state = ATH10K_THERMAL_THROTTLE_MAX; |
21 | |
22 | return 0; |
23 | } |
24 | |
25 | static int |
26 | ath10k_thermal_get_cur_throttle_state(struct thermal_cooling_device *cdev, |
27 | unsigned long *state) |
28 | { |
29 | struct ath10k *ar = cdev->devdata; |
30 | |
31 | mutex_lock(&ar->conf_mutex); |
32 | *state = ar->thermal.throttle_state; |
33 | mutex_unlock(lock: &ar->conf_mutex); |
34 | |
35 | return 0; |
36 | } |
37 | |
38 | static int |
39 | ath10k_thermal_set_cur_throttle_state(struct thermal_cooling_device *cdev, |
40 | unsigned long throttle_state) |
41 | { |
42 | struct ath10k *ar = cdev->devdata; |
43 | |
44 | if (throttle_state > ATH10K_THERMAL_THROTTLE_MAX) { |
45 | ath10k_warn(ar, fmt: "throttle state %ld is exceeding the limit %d\n" , |
46 | throttle_state, ATH10K_THERMAL_THROTTLE_MAX); |
47 | return -EINVAL; |
48 | } |
49 | mutex_lock(&ar->conf_mutex); |
50 | ar->thermal.throttle_state = throttle_state; |
51 | ath10k_thermal_set_throttling(ar); |
52 | mutex_unlock(lock: &ar->conf_mutex); |
53 | return 0; |
54 | } |
55 | |
56 | static const struct thermal_cooling_device_ops ath10k_thermal_ops = { |
57 | .get_max_state = ath10k_thermal_get_max_throttle_state, |
58 | .get_cur_state = ath10k_thermal_get_cur_throttle_state, |
59 | .set_cur_state = ath10k_thermal_set_cur_throttle_state, |
60 | }; |
61 | |
62 | static ssize_t ath10k_thermal_show_temp(struct device *dev, |
63 | struct device_attribute *attr, |
64 | char *buf) |
65 | { |
66 | struct ath10k *ar = dev_get_drvdata(dev); |
67 | int ret, temperature; |
68 | unsigned long time_left; |
69 | |
70 | mutex_lock(&ar->conf_mutex); |
71 | |
72 | /* Can't get temperature when the card is off */ |
73 | if (ar->state != ATH10K_STATE_ON) { |
74 | ret = -ENETDOWN; |
75 | goto out; |
76 | } |
77 | |
78 | reinit_completion(x: &ar->thermal.wmi_sync); |
79 | ret = ath10k_wmi_pdev_get_temperature(ar); |
80 | if (ret) { |
81 | ath10k_warn(ar, fmt: "failed to read temperature %d\n" , ret); |
82 | goto out; |
83 | } |
84 | |
85 | if (test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) { |
86 | ret = -ESHUTDOWN; |
87 | goto out; |
88 | } |
89 | |
90 | time_left = wait_for_completion_timeout(x: &ar->thermal.wmi_sync, |
91 | ATH10K_THERMAL_SYNC_TIMEOUT_HZ); |
92 | if (!time_left) { |
93 | ath10k_warn(ar, fmt: "failed to synchronize thermal read\n" ); |
94 | ret = -ETIMEDOUT; |
95 | goto out; |
96 | } |
97 | |
98 | spin_lock_bh(lock: &ar->data_lock); |
99 | temperature = ar->thermal.temperature; |
100 | spin_unlock_bh(lock: &ar->data_lock); |
101 | |
102 | /* display in millidegree celsius */ |
103 | ret = snprintf(buf, PAGE_SIZE, fmt: "%d\n" , temperature * 1000); |
104 | out: |
105 | mutex_unlock(lock: &ar->conf_mutex); |
106 | return ret; |
107 | } |
108 | |
109 | void ath10k_thermal_event_temperature(struct ath10k *ar, int temperature) |
110 | { |
111 | spin_lock_bh(lock: &ar->data_lock); |
112 | ar->thermal.temperature = temperature; |
113 | spin_unlock_bh(lock: &ar->data_lock); |
114 | complete(&ar->thermal.wmi_sync); |
115 | } |
116 | |
117 | static SENSOR_DEVICE_ATTR(temp1_input, 0444, ath10k_thermal_show_temp, |
118 | NULL, 0); |
119 | |
120 | static struct attribute *ath10k_hwmon_attrs[] = { |
121 | &sensor_dev_attr_temp1_input.dev_attr.attr, |
122 | NULL, |
123 | }; |
124 | ATTRIBUTE_GROUPS(ath10k_hwmon); |
125 | |
126 | void ath10k_thermal_set_throttling(struct ath10k *ar) |
127 | { |
128 | u32 period, duration, enabled; |
129 | int ret; |
130 | |
131 | lockdep_assert_held(&ar->conf_mutex); |
132 | |
133 | if (!test_bit(WMI_SERVICE_THERM_THROT, ar->wmi.svc_map)) |
134 | return; |
135 | |
136 | if (!ar->wmi.ops->gen_pdev_set_quiet_mode) |
137 | return; |
138 | |
139 | if (ar->state != ATH10K_STATE_ON) |
140 | return; |
141 | |
142 | period = ar->thermal.quiet_period; |
143 | duration = (period * ar->thermal.throttle_state) / 100; |
144 | enabled = duration ? 1 : 0; |
145 | |
146 | ret = ath10k_wmi_pdev_set_quiet_mode(ar, period, duration, |
147 | ATH10K_QUIET_START_OFFSET, |
148 | enabled); |
149 | if (ret) { |
150 | ath10k_warn(ar, fmt: "failed to set quiet mode period %u duarion %u enabled %u ret %d\n" , |
151 | period, duration, enabled, ret); |
152 | } |
153 | } |
154 | |
155 | int ath10k_thermal_register(struct ath10k *ar) |
156 | { |
157 | struct thermal_cooling_device *cdev; |
158 | struct device *hwmon_dev; |
159 | int ret; |
160 | |
161 | if (!test_bit(WMI_SERVICE_THERM_THROT, ar->wmi.svc_map)) |
162 | return 0; |
163 | |
164 | cdev = thermal_cooling_device_register("ath10k_thermal" , ar, |
165 | &ath10k_thermal_ops); |
166 | |
167 | if (IS_ERR(ptr: cdev)) { |
168 | ath10k_err(ar, fmt: "failed to setup thermal device result: %ld\n" , |
169 | PTR_ERR(ptr: cdev)); |
170 | return -EINVAL; |
171 | } |
172 | |
173 | ret = sysfs_create_link(kobj: &ar->dev->kobj, target: &cdev->device.kobj, |
174 | name: "cooling_device" ); |
175 | if (ret) { |
176 | ath10k_err(ar, fmt: "failed to create cooling device symlink\n" ); |
177 | goto err_cooling_destroy; |
178 | } |
179 | |
180 | ar->thermal.cdev = cdev; |
181 | ar->thermal.quiet_period = ATH10K_QUIET_PERIOD_DEFAULT; |
182 | |
183 | /* Do not register hwmon device when temperature reading is not |
184 | * supported by firmware |
185 | */ |
186 | if (!(ar->wmi.ops->gen_pdev_get_temperature)) |
187 | return 0; |
188 | |
189 | /* Avoid linking error on devm_hwmon_device_register_with_groups, I |
190 | * guess linux/hwmon.h is missing proper stubs. |
191 | */ |
192 | if (!IS_REACHABLE(CONFIG_HWMON)) |
193 | return 0; |
194 | |
195 | hwmon_dev = devm_hwmon_device_register_with_groups(dev: ar->dev, |
196 | name: "ath10k_hwmon" , drvdata: ar, |
197 | groups: ath10k_hwmon_groups); |
198 | if (IS_ERR(ptr: hwmon_dev)) { |
199 | ath10k_err(ar, fmt: "failed to register hwmon device: %ld\n" , |
200 | PTR_ERR(ptr: hwmon_dev)); |
201 | ret = -EINVAL; |
202 | goto err_remove_link; |
203 | } |
204 | return 0; |
205 | |
206 | err_remove_link: |
207 | sysfs_remove_link(kobj: &ar->dev->kobj, name: "cooling_device" ); |
208 | err_cooling_destroy: |
209 | thermal_cooling_device_unregister(cdev); |
210 | return ret; |
211 | } |
212 | |
213 | void ath10k_thermal_unregister(struct ath10k *ar) |
214 | { |
215 | if (!test_bit(WMI_SERVICE_THERM_THROT, ar->wmi.svc_map)) |
216 | return; |
217 | |
218 | sysfs_remove_link(kobj: &ar->dev->kobj, name: "cooling_device" ); |
219 | thermal_cooling_device_unregister(ar->thermal.cdev); |
220 | } |
221 | |