1 | /* Broadcom NetXtreme-C/E network driver. |
2 | * |
3 | * Copyright (c) 2023 Broadcom Limited |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation. |
8 | */ |
9 | |
10 | #include <linux/dev_printk.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/hwmon.h> |
13 | #include <linux/hwmon-sysfs.h> |
14 | #include <linux/pci.h> |
15 | |
16 | #include "bnxt_hsi.h" |
17 | #include "bnxt.h" |
18 | #include "bnxt_hwrm.h" |
19 | #include "bnxt_hwmon.h" |
20 | |
21 | void bnxt_hwmon_notify_event(struct bnxt *bp) |
22 | { |
23 | u32 attr; |
24 | |
25 | if (!bp->hwmon_dev) |
26 | return; |
27 | |
28 | switch (bp->thermal_threshold_type) { |
29 | case ASYNC_EVENT_CMPL_ERROR_REPORT_THERMAL_EVENT_DATA1_THRESHOLD_TYPE_WARN: |
30 | attr = hwmon_temp_max_alarm; |
31 | break; |
32 | case ASYNC_EVENT_CMPL_ERROR_REPORT_THERMAL_EVENT_DATA1_THRESHOLD_TYPE_CRITICAL: |
33 | attr = hwmon_temp_crit_alarm; |
34 | break; |
35 | case ASYNC_EVENT_CMPL_ERROR_REPORT_THERMAL_EVENT_DATA1_THRESHOLD_TYPE_FATAL: |
36 | case ASYNC_EVENT_CMPL_ERROR_REPORT_THERMAL_EVENT_DATA1_THRESHOLD_TYPE_SHUTDOWN: |
37 | attr = hwmon_temp_emergency_alarm; |
38 | break; |
39 | default: |
40 | return; |
41 | } |
42 | |
43 | hwmon_notify_event(dev: &bp->pdev->dev, type: hwmon_temp, attr, channel: 0); |
44 | } |
45 | |
46 | static int bnxt_hwrm_temp_query(struct bnxt *bp, u8 *temp) |
47 | { |
48 | struct hwrm_temp_monitor_query_output *resp; |
49 | struct hwrm_temp_monitor_query_input *req; |
50 | int rc; |
51 | |
52 | rc = hwrm_req_init(bp, req, HWRM_TEMP_MONITOR_QUERY); |
53 | if (rc) |
54 | return rc; |
55 | resp = hwrm_req_hold(bp, req); |
56 | rc = hwrm_req_send_silent(bp, req); |
57 | if (rc) |
58 | goto drop_req; |
59 | |
60 | if (temp) { |
61 | *temp = resp->temp; |
62 | } else if (resp->flags & |
63 | TEMP_MONITOR_QUERY_RESP_FLAGS_THRESHOLD_VALUES_AVAILABLE) { |
64 | bp->fw_cap |= BNXT_FW_CAP_THRESHOLD_TEMP_SUPPORTED; |
65 | bp->warn_thresh_temp = resp->warn_threshold; |
66 | bp->crit_thresh_temp = resp->critical_threshold; |
67 | bp->fatal_thresh_temp = resp->fatal_threshold; |
68 | bp->shutdown_thresh_temp = resp->shutdown_threshold; |
69 | } |
70 | drop_req: |
71 | hwrm_req_drop(bp, req); |
72 | return rc; |
73 | } |
74 | |
75 | static umode_t bnxt_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, |
76 | u32 attr, int channel) |
77 | { |
78 | const struct bnxt *bp = _data; |
79 | |
80 | if (type != hwmon_temp) |
81 | return 0; |
82 | |
83 | switch (attr) { |
84 | case hwmon_temp_input: |
85 | return 0444; |
86 | case hwmon_temp_max: |
87 | case hwmon_temp_crit: |
88 | case hwmon_temp_emergency: |
89 | case hwmon_temp_max_alarm: |
90 | case hwmon_temp_crit_alarm: |
91 | case hwmon_temp_emergency_alarm: |
92 | if (!(bp->fw_cap & BNXT_FW_CAP_THRESHOLD_TEMP_SUPPORTED)) |
93 | return 0; |
94 | return 0444; |
95 | default: |
96 | return 0; |
97 | } |
98 | } |
99 | |
100 | static int bnxt_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
101 | int channel, long *val) |
102 | { |
103 | struct bnxt *bp = dev_get_drvdata(dev); |
104 | u8 temp = 0; |
105 | int rc; |
106 | |
107 | switch (attr) { |
108 | case hwmon_temp_input: |
109 | rc = bnxt_hwrm_temp_query(bp, temp: &temp); |
110 | if (!rc) |
111 | *val = temp * 1000; |
112 | return rc; |
113 | case hwmon_temp_max: |
114 | *val = bp->warn_thresh_temp * 1000; |
115 | return 0; |
116 | case hwmon_temp_crit: |
117 | *val = bp->crit_thresh_temp * 1000; |
118 | return 0; |
119 | case hwmon_temp_emergency: |
120 | *val = bp->fatal_thresh_temp * 1000; |
121 | return 0; |
122 | case hwmon_temp_max_alarm: |
123 | rc = bnxt_hwrm_temp_query(bp, temp: &temp); |
124 | if (!rc) |
125 | *val = temp >= bp->warn_thresh_temp; |
126 | return rc; |
127 | case hwmon_temp_crit_alarm: |
128 | rc = bnxt_hwrm_temp_query(bp, temp: &temp); |
129 | if (!rc) |
130 | *val = temp >= bp->crit_thresh_temp; |
131 | return rc; |
132 | case hwmon_temp_emergency_alarm: |
133 | rc = bnxt_hwrm_temp_query(bp, temp: &temp); |
134 | if (!rc) |
135 | *val = temp >= bp->fatal_thresh_temp; |
136 | return rc; |
137 | default: |
138 | return -EOPNOTSUPP; |
139 | } |
140 | } |
141 | |
142 | static const struct hwmon_channel_info *bnxt_hwmon_info[] = { |
143 | HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | |
144 | HWMON_T_EMERGENCY | HWMON_T_MAX_ALARM | |
145 | HWMON_T_CRIT_ALARM | HWMON_T_EMERGENCY_ALARM), |
146 | NULL |
147 | }; |
148 | |
149 | static const struct hwmon_ops bnxt_hwmon_ops = { |
150 | .is_visible = bnxt_hwmon_is_visible, |
151 | .read = bnxt_hwmon_read, |
152 | }; |
153 | |
154 | static const struct hwmon_chip_info bnxt_hwmon_chip_info = { |
155 | .ops = &bnxt_hwmon_ops, |
156 | .info = bnxt_hwmon_info, |
157 | }; |
158 | |
159 | static ssize_t temp1_shutdown_show(struct device *dev, |
160 | struct device_attribute *attr, char *buf) |
161 | { |
162 | struct bnxt *bp = dev_get_drvdata(dev); |
163 | |
164 | return sysfs_emit(buf, fmt: "%u\n" , bp->shutdown_thresh_temp * 1000); |
165 | } |
166 | |
167 | static ssize_t temp1_shutdown_alarm_show(struct device *dev, |
168 | struct device_attribute *attr, char *buf) |
169 | { |
170 | struct bnxt *bp = dev_get_drvdata(dev); |
171 | u8 temp; |
172 | int rc; |
173 | |
174 | rc = bnxt_hwrm_temp_query(bp, temp: &temp); |
175 | if (rc) |
176 | return -EIO; |
177 | |
178 | return sysfs_emit(buf, fmt: "%u\n" , temp >= bp->shutdown_thresh_temp); |
179 | } |
180 | |
181 | static DEVICE_ATTR_RO(temp1_shutdown); |
182 | static DEVICE_ATTR_RO(temp1_shutdown_alarm); |
183 | |
184 | static struct attribute *[] = { |
185 | &dev_attr_temp1_shutdown.attr, |
186 | &dev_attr_temp1_shutdown_alarm.attr, |
187 | NULL, |
188 | }; |
189 | |
190 | static umode_t (struct kobject *kobj, |
191 | struct attribute *attr, int index) |
192 | { |
193 | struct device *dev = kobj_to_dev(kobj); |
194 | struct bnxt *bp = dev_get_drvdata(dev); |
195 | |
196 | /* Shutdown temperature setting in NVM is optional */ |
197 | if (!(bp->fw_cap & BNXT_FW_CAP_THRESHOLD_TEMP_SUPPORTED) || |
198 | !bp->shutdown_thresh_temp) |
199 | return 0; |
200 | |
201 | return attr->mode; |
202 | } |
203 | |
204 | static const struct attribute_group = { |
205 | .attrs = bnxt_temp_extra_attrs, |
206 | .is_visible = bnxt_temp_extra_attrs_visible, |
207 | }; |
208 | __ATTRIBUTE_GROUPS(bnxt_temp_extra); |
209 | |
210 | void bnxt_hwmon_uninit(struct bnxt *bp) |
211 | { |
212 | if (bp->hwmon_dev) { |
213 | hwmon_device_unregister(dev: bp->hwmon_dev); |
214 | bp->hwmon_dev = NULL; |
215 | } |
216 | } |
217 | |
218 | void bnxt_hwmon_init(struct bnxt *bp) |
219 | { |
220 | struct pci_dev *pdev = bp->pdev; |
221 | int rc; |
222 | |
223 | /* temp1_xxx is only sensor, ensure not registered if it will fail */ |
224 | rc = bnxt_hwrm_temp_query(bp, NULL); |
225 | if (rc == -EACCES || rc == -EOPNOTSUPP) { |
226 | bnxt_hwmon_uninit(bp); |
227 | return; |
228 | } |
229 | |
230 | if (bp->hwmon_dev) |
231 | return; |
232 | |
233 | bp->hwmon_dev = hwmon_device_register_with_info(dev: &pdev->dev, |
234 | DRV_MODULE_NAME, drvdata: bp, |
235 | info: &bnxt_hwmon_chip_info, |
236 | extra_groups: bnxt_temp_extra_groups); |
237 | if (IS_ERR(ptr: bp->hwmon_dev)) { |
238 | bp->hwmon_dev = NULL; |
239 | dev_warn(&pdev->dev, "Cannot register hwmon device\n" ); |
240 | } |
241 | } |
242 | |