diff options
author | David S. Miller <davem@davemloft.net> | 2016-11-22 10:04:19 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-11-22 10:04:19 -0500 |
commit | 41f698b0f5fdaeff7dd76c3b218cf4da3c8ef70b (patch) | |
tree | 31ed2e98fb076db71e812bd915a731fa7643a39f | |
parent | 2903372695eb33568bc13a02b3e91da1fe6fe855 (diff) | |
parent | a50c1e35650b929500bd89be61c89d95a267ce56 (diff) |
Merge branch 'mlxsw-thermal-zone'
Jiri Pirko says:
====================
mlxsw: core: Implement thermal zone
Implement thermal zone for mlxsw based HW.
The first patch is just a register dependency for the second patch.
====================
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxsw/Kconfig | 9 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxsw/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxsw/core.c | 8 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxsw/core.h | 24 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 442 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxsw/reg.h | 49 |
6 files changed, 533 insertions, 0 deletions
diff --git a/drivers/net/ethernet/mellanox/mlxsw/Kconfig b/drivers/net/ethernet/mellanox/mlxsw/Kconfig index c9822e653b93..95ae4c0d3a18 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/Kconfig +++ b/drivers/net/ethernet/mellanox/mlxsw/Kconfig | |||
@@ -19,6 +19,15 @@ config MLXSW_CORE_HWMON | |||
19 | ---help--- | 19 | ---help--- |
20 | Say Y here if you want to expose HWMON interface on mlxsw devices. | 20 | Say Y here if you want to expose HWMON interface on mlxsw devices. |
21 | 21 | ||
22 | config MLXSW_CORE_THERMAL | ||
23 | bool "Thermal zone support for Mellanox Technologies Switch ASICs" | ||
24 | depends on MLXSW_CORE && THERMAL | ||
25 | depends on !(MLXSW_CORE=y && THERMAL=m) | ||
26 | default y | ||
27 | ---help--- | ||
28 | Say Y here if you want to automatically control fans speed according | ||
29 | ambient temperature reported by ASIC. | ||
30 | |||
22 | config MLXSW_PCI | 31 | config MLXSW_PCI |
23 | tristate "PCI bus implementation for Mellanox Technologies Switch ASICs" | 32 | tristate "PCI bus implementation for Mellanox Technologies Switch ASICs" |
24 | depends on PCI && HAS_DMA && HAS_IOMEM && MLXSW_CORE | 33 | depends on PCI && HAS_DMA && HAS_IOMEM && MLXSW_CORE |
diff --git a/drivers/net/ethernet/mellanox/mlxsw/Makefile b/drivers/net/ethernet/mellanox/mlxsw/Makefile index 272294244ab1..fe8dadba15ab 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/Makefile +++ b/drivers/net/ethernet/mellanox/mlxsw/Makefile | |||
@@ -1,6 +1,7 @@ | |||
1 | obj-$(CONFIG_MLXSW_CORE) += mlxsw_core.o | 1 | obj-$(CONFIG_MLXSW_CORE) += mlxsw_core.o |
2 | mlxsw_core-objs := core.o | 2 | mlxsw_core-objs := core.o |
3 | mlxsw_core-$(CONFIG_MLXSW_CORE_HWMON) += core_hwmon.o | 3 | mlxsw_core-$(CONFIG_MLXSW_CORE_HWMON) += core_hwmon.o |
4 | mlxsw_core-$(CONFIG_MLXSW_CORE_THERMAL) += core_thermal.o | ||
4 | obj-$(CONFIG_MLXSW_PCI) += mlxsw_pci.o | 5 | obj-$(CONFIG_MLXSW_PCI) += mlxsw_pci.o |
5 | mlxsw_pci-objs := pci.o | 6 | mlxsw_pci-objs := pci.o |
6 | obj-$(CONFIG_MLXSW_I2C) += mlxsw_i2c.o | 7 | obj-$(CONFIG_MLXSW_I2C) += mlxsw_i2c.o |
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.c b/drivers/net/ethernet/mellanox/mlxsw/core.c index 763752f1745d..bcd7251385e3 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/core.c +++ b/drivers/net/ethernet/mellanox/mlxsw/core.c | |||
@@ -131,6 +131,7 @@ struct mlxsw_core { | |||
131 | } lag; | 131 | } lag; |
132 | struct mlxsw_res res; | 132 | struct mlxsw_res res; |
133 | struct mlxsw_hwmon *hwmon; | 133 | struct mlxsw_hwmon *hwmon; |
134 | struct mlxsw_thermal *thermal; | ||
134 | struct mlxsw_core_port ports[MLXSW_PORT_MAX_PORTS]; | 135 | struct mlxsw_core_port ports[MLXSW_PORT_MAX_PORTS]; |
135 | unsigned long driver_priv[0]; | 136 | unsigned long driver_priv[0]; |
136 | /* driver_priv has to be always the last item */ | 137 | /* driver_priv has to be always the last item */ |
@@ -1162,6 +1163,11 @@ int mlxsw_core_bus_device_register(const struct mlxsw_bus_info *mlxsw_bus_info, | |||
1162 | if (err) | 1163 | if (err) |
1163 | goto err_hwmon_init; | 1164 | goto err_hwmon_init; |
1164 | 1165 | ||
1166 | err = mlxsw_thermal_init(mlxsw_core, mlxsw_bus_info, | ||
1167 | &mlxsw_core->thermal); | ||
1168 | if (err) | ||
1169 | goto err_thermal_init; | ||
1170 | |||
1165 | if (mlxsw_driver->init) { | 1171 | if (mlxsw_driver->init) { |
1166 | err = mlxsw_driver->init(mlxsw_core, mlxsw_bus_info); | 1172 | err = mlxsw_driver->init(mlxsw_core, mlxsw_bus_info); |
1167 | if (err) | 1173 | if (err) |
@@ -1178,6 +1184,7 @@ err_debugfs_init: | |||
1178 | if (mlxsw_core->driver->fini) | 1184 | if (mlxsw_core->driver->fini) |
1179 | mlxsw_core->driver->fini(mlxsw_core); | 1185 | mlxsw_core->driver->fini(mlxsw_core); |
1180 | err_driver_init: | 1186 | err_driver_init: |
1187 | err_thermal_init: | ||
1181 | err_hwmon_init: | 1188 | err_hwmon_init: |
1182 | devlink_unregister(devlink); | 1189 | devlink_unregister(devlink); |
1183 | err_devlink_register: | 1190 | err_devlink_register: |
@@ -1204,6 +1211,7 @@ void mlxsw_core_bus_device_unregister(struct mlxsw_core *mlxsw_core) | |||
1204 | mlxsw_core_debugfs_fini(mlxsw_core); | 1211 | mlxsw_core_debugfs_fini(mlxsw_core); |
1205 | if (mlxsw_core->driver->fini) | 1212 | if (mlxsw_core->driver->fini) |
1206 | mlxsw_core->driver->fini(mlxsw_core); | 1213 | mlxsw_core->driver->fini(mlxsw_core); |
1214 | mlxsw_thermal_fini(mlxsw_core->thermal); | ||
1207 | devlink_unregister(devlink); | 1215 | devlink_unregister(devlink); |
1208 | mlxsw_emad_fini(mlxsw_core); | 1216 | mlxsw_emad_fini(mlxsw_core); |
1209 | mlxsw_core->bus->fini(mlxsw_core->bus_priv); | 1217 | mlxsw_core->bus->fini(mlxsw_core->bus_priv); |
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.h b/drivers/net/ethernet/mellanox/mlxsw/core.h index f7a4d83801eb..3de8955a26fd 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/core.h +++ b/drivers/net/ethernet/mellanox/mlxsw/core.h | |||
@@ -321,4 +321,28 @@ static inline int mlxsw_hwmon_init(struct mlxsw_core *mlxsw_core, | |||
321 | 321 | ||
322 | #endif | 322 | #endif |
323 | 323 | ||
324 | struct mlxsw_thermal; | ||
325 | |||
326 | #ifdef CONFIG_MLXSW_CORE_THERMAL | ||
327 | |||
328 | int mlxsw_thermal_init(struct mlxsw_core *mlxsw_core, | ||
329 | const struct mlxsw_bus_info *mlxsw_bus_info, | ||
330 | struct mlxsw_thermal **p_thermal); | ||
331 | void mlxsw_thermal_fini(struct mlxsw_thermal *thermal); | ||
332 | |||
333 | #else | ||
334 | |||
335 | static inline int mlxsw_thermal_init(struct mlxsw_core *mlxsw_core, | ||
336 | const struct mlxsw_bus_info *mlxsw_bus_info, | ||
337 | struct mlxsw_thermal **p_thermal) | ||
338 | { | ||
339 | return 0; | ||
340 | } | ||
341 | |||
342 | static inline void mlxsw_thermal_fini(struct mlxsw_thermal *thermal) | ||
343 | { | ||
344 | } | ||
345 | |||
346 | #endif | ||
347 | |||
324 | #endif | 348 | #endif |
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c new file mode 100644 index 000000000000..d866c98c1a97 --- /dev/null +++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | |||
@@ -0,0 +1,442 @@ | |||
1 | /* | ||
2 | * drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | ||
3 | * Copyright (c) 2016 Ivan Vecera <cera@cera.cz> | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * | ||
8 | * 1. Redistributions of source code must retain the above copyright | ||
9 | * notice, this list of conditions and the following disclaimer. | ||
10 | * 2. Redistributions in binary form must reproduce the above copyright | ||
11 | * notice, this list of conditions and the following disclaimer in the | ||
12 | * documentation and/or other materials provided with the distribution. | ||
13 | * 3. Neither the names of the copyright holders nor the names of its | ||
14 | * contributors may be used to endorse or promote products derived from | ||
15 | * this software without specific prior written permission. | ||
16 | * | ||
17 | * Alternatively, this software may be distributed under the terms of the | ||
18 | * GNU General Public License ("GPL") version 2 as published by the Free | ||
19 | * Software Foundation. | ||
20 | * | ||
21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
22 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | ||
25 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
26 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
27 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
28 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
30 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
31 | * POSSIBILITY OF SUCH DAMAGE. | ||
32 | */ | ||
33 | |||
34 | #include <linux/kernel.h> | ||
35 | #include <linux/types.h> | ||
36 | #include <linux/device.h> | ||
37 | #include <linux/sysfs.h> | ||
38 | #include <linux/thermal.h> | ||
39 | #include <linux/err.h> | ||
40 | |||
41 | #include "core.h" | ||
42 | |||
43 | #define MLXSW_THERMAL_POLL_INT 1000 /* ms */ | ||
44 | #define MLXSW_THERMAL_MAX_TEMP 110000 /* 110C */ | ||
45 | #define MLXSW_THERMAL_MAX_STATE 10 | ||
46 | #define MLXSW_THERMAL_MAX_DUTY 255 | ||
47 | |||
48 | struct mlxsw_thermal_trip { | ||
49 | int type; | ||
50 | int temp; | ||
51 | int min_state; | ||
52 | int max_state; | ||
53 | }; | ||
54 | |||
55 | static const struct mlxsw_thermal_trip default_thermal_trips[] = { | ||
56 | { /* In range - 0-40% PWM */ | ||
57 | .type = THERMAL_TRIP_ACTIVE, | ||
58 | .temp = 75000, | ||
59 | .min_state = 0, | ||
60 | .max_state = (4 * MLXSW_THERMAL_MAX_STATE) / 10, | ||
61 | }, | ||
62 | { /* High - 40-100% PWM */ | ||
63 | .type = THERMAL_TRIP_ACTIVE, | ||
64 | .temp = 80000, | ||
65 | .min_state = (4 * MLXSW_THERMAL_MAX_STATE) / 10, | ||
66 | .max_state = MLXSW_THERMAL_MAX_STATE, | ||
67 | }, | ||
68 | { | ||
69 | /* Very high - 100% PWM */ | ||
70 | .type = THERMAL_TRIP_ACTIVE, | ||
71 | .temp = 85000, | ||
72 | .min_state = MLXSW_THERMAL_MAX_STATE, | ||
73 | .max_state = MLXSW_THERMAL_MAX_STATE, | ||
74 | }, | ||
75 | { /* Warning */ | ||
76 | .type = THERMAL_TRIP_HOT, | ||
77 | .temp = 105000, | ||
78 | .min_state = MLXSW_THERMAL_MAX_STATE, | ||
79 | .max_state = MLXSW_THERMAL_MAX_STATE, | ||
80 | }, | ||
81 | { /* Critical - soft poweroff */ | ||
82 | .type = THERMAL_TRIP_CRITICAL, | ||
83 | .temp = MLXSW_THERMAL_MAX_TEMP, | ||
84 | .min_state = MLXSW_THERMAL_MAX_STATE, | ||
85 | .max_state = MLXSW_THERMAL_MAX_STATE, | ||
86 | } | ||
87 | }; | ||
88 | |||
89 | #define MLXSW_THERMAL_NUM_TRIPS ARRAY_SIZE(default_thermal_trips) | ||
90 | |||
91 | /* Make sure all trips are writable */ | ||
92 | #define MLXSW_THERMAL_TRIP_MASK (BIT(MLXSW_THERMAL_NUM_TRIPS) - 1) | ||
93 | |||
94 | struct mlxsw_thermal { | ||
95 | struct mlxsw_core *core; | ||
96 | const struct mlxsw_bus_info *bus_info; | ||
97 | struct thermal_zone_device *tzdev; | ||
98 | struct thermal_cooling_device *cdevs[MLXSW_MFCR_PWMS_MAX]; | ||
99 | struct mlxsw_thermal_trip trips[MLXSW_THERMAL_NUM_TRIPS]; | ||
100 | enum thermal_device_mode mode; | ||
101 | }; | ||
102 | |||
103 | static inline u8 mlxsw_state_to_duty(int state) | ||
104 | { | ||
105 | return DIV_ROUND_CLOSEST(state * MLXSW_THERMAL_MAX_DUTY, | ||
106 | MLXSW_THERMAL_MAX_STATE); | ||
107 | } | ||
108 | |||
109 | static inline int mlxsw_duty_to_state(u8 duty) | ||
110 | { | ||
111 | return DIV_ROUND_CLOSEST(duty * MLXSW_THERMAL_MAX_STATE, | ||
112 | MLXSW_THERMAL_MAX_DUTY); | ||
113 | } | ||
114 | |||
115 | static int mlxsw_get_cooling_device_idx(struct mlxsw_thermal *thermal, | ||
116 | struct thermal_cooling_device *cdev) | ||
117 | { | ||
118 | int i; | ||
119 | |||
120 | for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) | ||
121 | if (thermal->cdevs[i] == cdev) | ||
122 | return i; | ||
123 | |||
124 | return -ENODEV; | ||
125 | } | ||
126 | |||
127 | static int mlxsw_thermal_bind(struct thermal_zone_device *tzdev, | ||
128 | struct thermal_cooling_device *cdev) | ||
129 | { | ||
130 | struct mlxsw_thermal *thermal = tzdev->devdata; | ||
131 | struct device *dev = thermal->bus_info->dev; | ||
132 | int i, err; | ||
133 | |||
134 | /* If the cooling device is one of ours bind it */ | ||
135 | if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) | ||
136 | return 0; | ||
137 | |||
138 | for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { | ||
139 | const struct mlxsw_thermal_trip *trip = &thermal->trips[i]; | ||
140 | |||
141 | err = thermal_zone_bind_cooling_device(tzdev, i, cdev, | ||
142 | trip->max_state, | ||
143 | trip->min_state, | ||
144 | THERMAL_WEIGHT_DEFAULT); | ||
145 | if (err < 0) { | ||
146 | dev_err(dev, "Failed to bind cooling device to trip %d\n", i); | ||
147 | return err; | ||
148 | } | ||
149 | } | ||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static int mlxsw_thermal_unbind(struct thermal_zone_device *tzdev, | ||
154 | struct thermal_cooling_device *cdev) | ||
155 | { | ||
156 | struct mlxsw_thermal *thermal = tzdev->devdata; | ||
157 | struct device *dev = thermal->bus_info->dev; | ||
158 | int i; | ||
159 | int err; | ||
160 | |||
161 | /* If the cooling device is our one unbind it */ | ||
162 | if (mlxsw_get_cooling_device_idx(thermal, cdev) < 0) | ||
163 | return 0; | ||
164 | |||
165 | for (i = 0; i < MLXSW_THERMAL_NUM_TRIPS; i++) { | ||
166 | err = thermal_zone_unbind_cooling_device(tzdev, i, cdev); | ||
167 | if (err < 0) { | ||
168 | dev_err(dev, "Failed to unbind cooling device\n"); | ||
169 | return err; | ||
170 | } | ||
171 | } | ||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | static int mlxsw_thermal_get_mode(struct thermal_zone_device *tzdev, | ||
176 | enum thermal_device_mode *mode) | ||
177 | { | ||
178 | struct mlxsw_thermal *thermal = tzdev->devdata; | ||
179 | |||
180 | *mode = thermal->mode; | ||
181 | |||
182 | return 0; | ||
183 | } | ||
184 | |||
185 | static int mlxsw_thermal_set_mode(struct thermal_zone_device *tzdev, | ||
186 | enum thermal_device_mode mode) | ||
187 | { | ||
188 | struct mlxsw_thermal *thermal = tzdev->devdata; | ||
189 | |||
190 | mutex_lock(&tzdev->lock); | ||
191 | |||
192 | if (mode == THERMAL_DEVICE_ENABLED) | ||
193 | tzdev->polling_delay = MLXSW_THERMAL_POLL_INT; | ||
194 | else | ||
195 | tzdev->polling_delay = 0; | ||
196 | |||
197 | mutex_unlock(&tzdev->lock); | ||
198 | |||
199 | thermal->mode = mode; | ||
200 | thermal_zone_device_update(tzdev, THERMAL_EVENT_UNSPECIFIED); | ||
201 | |||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | static int mlxsw_thermal_get_temp(struct thermal_zone_device *tzdev, | ||
206 | int *p_temp) | ||
207 | { | ||
208 | struct mlxsw_thermal *thermal = tzdev->devdata; | ||
209 | struct device *dev = thermal->bus_info->dev; | ||
210 | char mtmp_pl[MLXSW_REG_MTMP_LEN]; | ||
211 | unsigned int temp; | ||
212 | int err; | ||
213 | |||
214 | mlxsw_reg_mtmp_pack(mtmp_pl, 0, false, false); | ||
215 | |||
216 | err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtmp), mtmp_pl); | ||
217 | if (err) { | ||
218 | dev_err(dev, "Failed to query temp sensor\n"); | ||
219 | return err; | ||
220 | } | ||
221 | mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL); | ||
222 | |||
223 | *p_temp = (int) temp; | ||
224 | return 0; | ||
225 | } | ||
226 | |||
227 | static int mlxsw_thermal_get_trip_type(struct thermal_zone_device *tzdev, | ||
228 | int trip, | ||
229 | enum thermal_trip_type *p_type) | ||
230 | { | ||
231 | struct mlxsw_thermal *thermal = tzdev->devdata; | ||
232 | |||
233 | if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) | ||
234 | return -EINVAL; | ||
235 | |||
236 | *p_type = thermal->trips[trip].type; | ||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | static int mlxsw_thermal_get_trip_temp(struct thermal_zone_device *tzdev, | ||
241 | int trip, int *p_temp) | ||
242 | { | ||
243 | struct mlxsw_thermal *thermal = tzdev->devdata; | ||
244 | |||
245 | if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS) | ||
246 | return -EINVAL; | ||
247 | |||
248 | *p_temp = thermal->trips[trip].temp; | ||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | static int mlxsw_thermal_set_trip_temp(struct thermal_zone_device *tzdev, | ||
253 | int trip, int temp) | ||
254 | { | ||
255 | struct mlxsw_thermal *thermal = tzdev->devdata; | ||
256 | |||
257 | if (trip < 0 || trip >= MLXSW_THERMAL_NUM_TRIPS || | ||
258 | temp > MLXSW_THERMAL_MAX_TEMP) | ||
259 | return -EINVAL; | ||
260 | |||
261 | thermal->trips[trip].temp = temp; | ||
262 | return 0; | ||
263 | } | ||
264 | |||
265 | static struct thermal_zone_device_ops mlxsw_thermal_ops = { | ||
266 | .bind = mlxsw_thermal_bind, | ||
267 | .unbind = mlxsw_thermal_unbind, | ||
268 | .get_mode = mlxsw_thermal_get_mode, | ||
269 | .set_mode = mlxsw_thermal_set_mode, | ||
270 | .get_temp = mlxsw_thermal_get_temp, | ||
271 | .get_trip_type = mlxsw_thermal_get_trip_type, | ||
272 | .get_trip_temp = mlxsw_thermal_get_trip_temp, | ||
273 | .set_trip_temp = mlxsw_thermal_set_trip_temp, | ||
274 | }; | ||
275 | |||
276 | static int mlxsw_thermal_get_max_state(struct thermal_cooling_device *cdev, | ||
277 | unsigned long *p_state) | ||
278 | { | ||
279 | *p_state = MLXSW_THERMAL_MAX_STATE; | ||
280 | return 0; | ||
281 | } | ||
282 | |||
283 | static int mlxsw_thermal_get_cur_state(struct thermal_cooling_device *cdev, | ||
284 | unsigned long *p_state) | ||
285 | |||
286 | { | ||
287 | struct mlxsw_thermal *thermal = cdev->devdata; | ||
288 | struct device *dev = thermal->bus_info->dev; | ||
289 | char mfsc_pl[MLXSW_REG_MFSC_LEN]; | ||
290 | int err, idx; | ||
291 | u8 duty; | ||
292 | |||
293 | idx = mlxsw_get_cooling_device_idx(thermal, cdev); | ||
294 | if (idx < 0) | ||
295 | return idx; | ||
296 | |||
297 | mlxsw_reg_mfsc_pack(mfsc_pl, idx, 0); | ||
298 | err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfsc), mfsc_pl); | ||
299 | if (err) { | ||
300 | dev_err(dev, "Failed to query PWM duty\n"); | ||
301 | return err; | ||
302 | } | ||
303 | |||
304 | duty = mlxsw_reg_mfsc_pwm_duty_cycle_get(mfsc_pl); | ||
305 | *p_state = mlxsw_duty_to_state(duty); | ||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static int mlxsw_thermal_set_cur_state(struct thermal_cooling_device *cdev, | ||
310 | unsigned long state) | ||
311 | |||
312 | { | ||
313 | struct mlxsw_thermal *thermal = cdev->devdata; | ||
314 | struct device *dev = thermal->bus_info->dev; | ||
315 | char mfsc_pl[MLXSW_REG_MFSC_LEN]; | ||
316 | int err, idx; | ||
317 | |||
318 | idx = mlxsw_get_cooling_device_idx(thermal, cdev); | ||
319 | if (idx < 0) | ||
320 | return idx; | ||
321 | |||
322 | mlxsw_reg_mfsc_pack(mfsc_pl, idx, mlxsw_state_to_duty(state)); | ||
323 | err = mlxsw_reg_write(thermal->core, MLXSW_REG(mfsc), mfsc_pl); | ||
324 | if (err) { | ||
325 | dev_err(dev, "Failed to write PWM duty\n"); | ||
326 | return err; | ||
327 | } | ||
328 | return 0; | ||
329 | } | ||
330 | |||
331 | static const struct thermal_cooling_device_ops mlxsw_cooling_ops = { | ||
332 | .get_max_state = mlxsw_thermal_get_max_state, | ||
333 | .get_cur_state = mlxsw_thermal_get_cur_state, | ||
334 | .set_cur_state = mlxsw_thermal_set_cur_state, | ||
335 | }; | ||
336 | |||
337 | int mlxsw_thermal_init(struct mlxsw_core *core, | ||
338 | const struct mlxsw_bus_info *bus_info, | ||
339 | struct mlxsw_thermal **p_thermal) | ||
340 | { | ||
341 | char mfcr_pl[MLXSW_REG_MFCR_LEN] = { 0 }; | ||
342 | enum mlxsw_reg_mfcr_pwm_frequency freq; | ||
343 | struct device *dev = bus_info->dev; | ||
344 | struct mlxsw_thermal *thermal; | ||
345 | u16 tacho_active; | ||
346 | u8 pwm_active; | ||
347 | int err, i; | ||
348 | |||
349 | thermal = devm_kzalloc(dev, sizeof(*thermal), | ||
350 | GFP_KERNEL); | ||
351 | if (!thermal) | ||
352 | return -ENOMEM; | ||
353 | |||
354 | thermal->core = core; | ||
355 | thermal->bus_info = bus_info; | ||
356 | memcpy(thermal->trips, default_thermal_trips, sizeof(thermal->trips)); | ||
357 | |||
358 | err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfcr), mfcr_pl); | ||
359 | if (err) { | ||
360 | dev_err(dev, "Failed to probe PWMs\n"); | ||
361 | goto err_free_thermal; | ||
362 | } | ||
363 | mlxsw_reg_mfcr_unpack(mfcr_pl, &freq, &tacho_active, &pwm_active); | ||
364 | |||
365 | for (i = 0; i < MLXSW_MFCR_TACHOS_MAX; i++) { | ||
366 | if (tacho_active & BIT(i)) { | ||
367 | char mfsl_pl[MLXSW_REG_MFSL_LEN]; | ||
368 | |||
369 | mlxsw_reg_mfsl_pack(mfsl_pl, i, 0, 0); | ||
370 | |||
371 | /* We need to query the register to preserve maximum */ | ||
372 | err = mlxsw_reg_query(thermal->core, MLXSW_REG(mfsl), | ||
373 | mfsl_pl); | ||
374 | if (err) | ||
375 | goto err_free_thermal; | ||
376 | |||
377 | /* set the minimal RPMs to 0 */ | ||
378 | mlxsw_reg_mfsl_tach_min_set(mfsl_pl, 0); | ||
379 | err = mlxsw_reg_write(thermal->core, MLXSW_REG(mfsl), | ||
380 | mfsl_pl); | ||
381 | if (err) | ||
382 | goto err_free_thermal; | ||
383 | } | ||
384 | } | ||
385 | for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) { | ||
386 | if (pwm_active & BIT(i)) { | ||
387 | struct thermal_cooling_device *cdev; | ||
388 | |||
389 | cdev = thermal_cooling_device_register("Fan", thermal, | ||
390 | &mlxsw_cooling_ops); | ||
391 | if (IS_ERR(cdev)) { | ||
392 | err = PTR_ERR(cdev); | ||
393 | dev_err(dev, "Failed to register cooling device\n"); | ||
394 | goto err_unreg_cdevs; | ||
395 | } | ||
396 | thermal->cdevs[i] = cdev; | ||
397 | } | ||
398 | } | ||
399 | |||
400 | thermal->tzdev = thermal_zone_device_register("mlxsw", | ||
401 | MLXSW_THERMAL_NUM_TRIPS, | ||
402 | MLXSW_THERMAL_TRIP_MASK, | ||
403 | thermal, | ||
404 | &mlxsw_thermal_ops, | ||
405 | NULL, 0, | ||
406 | MLXSW_THERMAL_POLL_INT); | ||
407 | if (IS_ERR(thermal->tzdev)) { | ||
408 | err = PTR_ERR(thermal->tzdev); | ||
409 | dev_err(dev, "Failed to register thermal zone\n"); | ||
410 | goto err_unreg_cdevs; | ||
411 | } | ||
412 | |||
413 | thermal->mode = THERMAL_DEVICE_ENABLED; | ||
414 | *p_thermal = thermal; | ||
415 | return 0; | ||
416 | err_unreg_cdevs: | ||
417 | for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) | ||
418 | if (thermal->cdevs[i]) | ||
419 | thermal_cooling_device_unregister(thermal->cdevs[i]); | ||
420 | err_free_thermal: | ||
421 | devm_kfree(dev, thermal); | ||
422 | return err; | ||
423 | } | ||
424 | |||
425 | void mlxsw_thermal_fini(struct mlxsw_thermal *thermal) | ||
426 | { | ||
427 | int i; | ||
428 | |||
429 | if (thermal->tzdev) { | ||
430 | thermal_zone_device_unregister(thermal->tzdev); | ||
431 | thermal->tzdev = NULL; | ||
432 | } | ||
433 | |||
434 | for (i = 0; i < MLXSW_MFCR_PWMS_MAX; i++) { | ||
435 | if (thermal->cdevs[i]) { | ||
436 | thermal_cooling_device_unregister(thermal->cdevs[i]); | ||
437 | thermal->cdevs[i] = NULL; | ||
438 | } | ||
439 | } | ||
440 | |||
441 | devm_kfree(thermal->bus_info->dev, thermal); | ||
442 | } | ||
diff --git a/drivers/net/ethernet/mellanox/mlxsw/reg.h b/drivers/net/ethernet/mellanox/mlxsw/reg.h index edad7cb62475..2618e9cf3aab 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/reg.h +++ b/drivers/net/ethernet/mellanox/mlxsw/reg.h | |||
@@ -4518,6 +4518,54 @@ static inline void mlxsw_reg_mfsm_pack(char *payload, u8 tacho) | |||
4518 | mlxsw_reg_mfsm_tacho_set(payload, tacho); | 4518 | mlxsw_reg_mfsm_tacho_set(payload, tacho); |
4519 | } | 4519 | } |
4520 | 4520 | ||
4521 | /* MFSL - Management Fan Speed Limit Register | ||
4522 | * ------------------------------------------ | ||
4523 | * The Fan Speed Limit register is used to configure the fan speed | ||
4524 | * event / interrupt notification mechanism. Fan speed threshold are | ||
4525 | * defined for both under-speed and over-speed. | ||
4526 | */ | ||
4527 | #define MLXSW_REG_MFSL_ID 0x9004 | ||
4528 | #define MLXSW_REG_MFSL_LEN 0x0C | ||
4529 | |||
4530 | MLXSW_REG_DEFINE(mfsl, MLXSW_REG_MFSL_ID, MLXSW_REG_MFSL_LEN); | ||
4531 | |||
4532 | /* reg_mfsl_tacho | ||
4533 | * Fan tachometer index. | ||
4534 | * Access: Index | ||
4535 | */ | ||
4536 | MLXSW_ITEM32(reg, mfsl, tacho, 0x00, 24, 4); | ||
4537 | |||
4538 | /* reg_mfsl_tach_min | ||
4539 | * Tachometer minimum value (minimum RPM). | ||
4540 | * Access: RW | ||
4541 | */ | ||
4542 | MLXSW_ITEM32(reg, mfsl, tach_min, 0x04, 0, 16); | ||
4543 | |||
4544 | /* reg_mfsl_tach_max | ||
4545 | * Tachometer maximum value (maximum RPM). | ||
4546 | * Access: RW | ||
4547 | */ | ||
4548 | MLXSW_ITEM32(reg, mfsl, tach_max, 0x08, 0, 16); | ||
4549 | |||
4550 | static inline void mlxsw_reg_mfsl_pack(char *payload, u8 tacho, | ||
4551 | u16 tach_min, u16 tach_max) | ||
4552 | { | ||
4553 | MLXSW_REG_ZERO(mfsl, payload); | ||
4554 | mlxsw_reg_mfsl_tacho_set(payload, tacho); | ||
4555 | mlxsw_reg_mfsl_tach_min_set(payload, tach_min); | ||
4556 | mlxsw_reg_mfsl_tach_max_set(payload, tach_max); | ||
4557 | } | ||
4558 | |||
4559 | static inline void mlxsw_reg_mfsl_unpack(char *payload, u8 tacho, | ||
4560 | u16 *p_tach_min, u16 *p_tach_max) | ||
4561 | { | ||
4562 | if (p_tach_min) | ||
4563 | *p_tach_min = mlxsw_reg_mfsl_tach_min_get(payload); | ||
4564 | |||
4565 | if (p_tach_max) | ||
4566 | *p_tach_max = mlxsw_reg_mfsl_tach_max_get(payload); | ||
4567 | } | ||
4568 | |||
4521 | /* MTCAP - Management Temperature Capabilities | 4569 | /* MTCAP - Management Temperature Capabilities |
4522 | * ------------------------------------------- | 4570 | * ------------------------------------------- |
4523 | * This register exposes the capabilities of the device and | 4571 | * This register exposes the capabilities of the device and |
@@ -5228,6 +5276,7 @@ static const struct mlxsw_reg_info *mlxsw_reg_infos[] = { | |||
5228 | MLXSW_REG(mfcr), | 5276 | MLXSW_REG(mfcr), |
5229 | MLXSW_REG(mfsc), | 5277 | MLXSW_REG(mfsc), |
5230 | MLXSW_REG(mfsm), | 5278 | MLXSW_REG(mfsm), |
5279 | MLXSW_REG(mfsl), | ||
5231 | MLXSW_REG(mtcap), | 5280 | MLXSW_REG(mtcap), |
5232 | MLXSW_REG(mtmp), | 5281 | MLXSW_REG(mtmp), |
5233 | MLXSW_REG(mpat), | 5282 | MLXSW_REG(mpat), |