Commit | Line | Data |
---|---|---|
30488704 VP |
1 | /* |
2 | * drivers/platform/x86/mlxcpld-hotplug.c | |
3 | * Copyright (c) 2016 Mellanox Technologies. All rights reserved. | |
4 | * Copyright (c) 2016 Vadim Pasternak <vadimp@mellanox.com> | |
5 | * | |
6 | * Redistribution and use in source and binary forms, with or without | |
7 | * modification, are permitted provided that the following conditions are met: | |
8 | * | |
9 | * 1. Redistributions of source code must retain the above copyright | |
10 | * notice, this list of conditions and the following disclaimer. | |
11 | * 2. Redistributions in binary form must reproduce the above copyright | |
12 | * notice, this list of conditions and the following disclaimer in the | |
13 | * documentation and/or other materials provided with the distribution. | |
14 | * 3. Neither the names of the copyright holders nor the names of its | |
15 | * contributors may be used to endorse or promote products derived from | |
16 | * this software without specific prior written permission. | |
17 | * | |
18 | * Alternatively, this software may be distributed under the terms of the | |
19 | * GNU General Public License ("GPL") version 2 as published by the Free | |
20 | * Software Foundation. | |
21 | * | |
22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
23 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
26 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
27 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
28 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
29 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
31 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
32 | * POSSIBILITY OF SUCH DAMAGE. | |
33 | */ | |
34 | ||
35 | #include <linux/bitops.h> | |
36 | #include <linux/device.h> | |
37 | #include <linux/hwmon.h> | |
38 | #include <linux/hwmon-sysfs.h> | |
39 | #include <linux/i2c.h> | |
40 | #include <linux/interrupt.h> | |
41 | #include <linux/io.h> | |
42 | #include <linux/module.h> | |
43 | #include <linux/platform_data/mlxcpld-hotplug.h> | |
44 | #include <linux/platform_device.h> | |
45 | #include <linux/spinlock.h> | |
46 | #include <linux/wait.h> | |
47 | #include <linux/workqueue.h> | |
48 | ||
49 | /* Offset of event and mask registers from status register */ | |
50 | #define MLXCPLD_HOTPLUG_EVENT_OFF 1 | |
51 | #define MLXCPLD_HOTPLUG_MASK_OFF 2 | |
52 | #define MLXCPLD_HOTPLUG_AGGR_MASK_OFF 1 | |
53 | ||
54 | #define MLXCPLD_HOTPLUG_ATTRS_NUM 8 | |
55 | ||
56 | /** | |
57 | * enum mlxcpld_hotplug_attr_type - sysfs attributes for hotplug events: | |
58 | * @MLXCPLD_HOTPLUG_ATTR_TYPE_PSU: power supply unit attribute; | |
59 | * @MLXCPLD_HOTPLUG_ATTR_TYPE_PWR: power cable attribute; | |
60 | * @MLXCPLD_HOTPLUG_ATTR_TYPE_FAN: FAN drawer attribute; | |
61 | */ | |
62 | enum mlxcpld_hotplug_attr_type { | |
63 | MLXCPLD_HOTPLUG_ATTR_TYPE_PSU, | |
64 | MLXCPLD_HOTPLUG_ATTR_TYPE_PWR, | |
65 | MLXCPLD_HOTPLUG_ATTR_TYPE_FAN, | |
66 | }; | |
67 | ||
68 | /** | |
69 | * struct mlxcpld_hotplug_priv_data - platform private data: | |
70 | * @irq: platform interrupt number; | |
71 | * @pdev: platform device; | |
72 | * @plat: platform data; | |
73 | * @hwmon: hwmon device; | |
74 | * @mlxcpld_hotplug_attr: sysfs attributes array; | |
75 | * @mlxcpld_hotplug_dev_attr: sysfs sensor device attribute array; | |
76 | * @group: sysfs attribute group; | |
77 | * @groups: list of sysfs attribute group for hwmon registration; | |
78 | * @dwork: delayed work template; | |
79 | * @lock: spin lock; | |
80 | * @aggr_cache: last value of aggregation register status; | |
81 | * @psu_cache: last value of PSU register status; | |
82 | * @pwr_cache: last value of power register status; | |
83 | * @fan_cache: last value of FAN register status; | |
84 | */ | |
85 | struct mlxcpld_hotplug_priv_data { | |
86 | int irq; | |
87 | struct platform_device *pdev; | |
88 | struct mlxcpld_hotplug_platform_data *plat; | |
89 | struct device *hwmon; | |
90 | struct attribute *mlxcpld_hotplug_attr[MLXCPLD_HOTPLUG_ATTRS_NUM + 1]; | |
91 | struct sensor_device_attribute_2 | |
92 | mlxcpld_hotplug_dev_attr[MLXCPLD_HOTPLUG_ATTRS_NUM]; | |
93 | struct attribute_group group; | |
94 | const struct attribute_group *groups[2]; | |
95 | struct delayed_work dwork; | |
96 | spinlock_t lock; | |
97 | u8 aggr_cache; | |
98 | u8 psu_cache; | |
99 | u8 pwr_cache; | |
100 | u8 fan_cache; | |
101 | }; | |
102 | ||
103 | static ssize_t mlxcpld_hotplug_attr_show(struct device *dev, | |
104 | struct device_attribute *attr, | |
105 | char *buf) | |
106 | { | |
107 | struct platform_device *pdev = to_platform_device(dev); | |
108 | struct mlxcpld_hotplug_priv_data *priv = platform_get_drvdata(pdev); | |
109 | int index = to_sensor_dev_attr_2(attr)->index; | |
110 | int nr = to_sensor_dev_attr_2(attr)->nr; | |
111 | u8 reg_val = 0; | |
112 | ||
113 | switch (nr) { | |
114 | case MLXCPLD_HOTPLUG_ATTR_TYPE_PSU: | |
115 | /* Bit = 0 : PSU is present. */ | |
116 | reg_val = !!!(inb(priv->plat->psu_reg_offset) & BIT(index)); | |
117 | break; | |
118 | ||
119 | case MLXCPLD_HOTPLUG_ATTR_TYPE_PWR: | |
120 | /* Bit = 1 : power cable is attached. */ | |
121 | reg_val = !!(inb(priv->plat->pwr_reg_offset) & BIT(index % | |
122 | priv->plat->pwr_count)); | |
123 | break; | |
124 | ||
125 | case MLXCPLD_HOTPLUG_ATTR_TYPE_FAN: | |
126 | /* Bit = 0 : FAN is present. */ | |
127 | reg_val = !!!(inb(priv->plat->fan_reg_offset) & BIT(index % | |
128 | priv->plat->fan_count)); | |
129 | break; | |
130 | } | |
131 | ||
132 | return sprintf(buf, "%u\n", reg_val); | |
133 | } | |
134 | ||
135 | #define PRIV_ATTR(i) priv->mlxcpld_hotplug_attr[i] | |
136 | #define PRIV_DEV_ATTR(i) priv->mlxcpld_hotplug_dev_attr[i] | |
137 | static int mlxcpld_hotplug_attr_init(struct mlxcpld_hotplug_priv_data *priv) | |
138 | { | |
139 | int num_attrs = priv->plat->psu_count + priv->plat->pwr_count + | |
140 | priv->plat->fan_count; | |
141 | int i; | |
142 | ||
143 | priv->group.attrs = devm_kzalloc(&priv->pdev->dev, num_attrs * | |
144 | sizeof(struct attribute *), | |
145 | GFP_KERNEL); | |
146 | if (!priv->group.attrs) | |
147 | return -ENOMEM; | |
148 | ||
149 | for (i = 0; i < num_attrs; i++) { | |
150 | PRIV_ATTR(i) = &PRIV_DEV_ATTR(i).dev_attr.attr; | |
151 | ||
152 | if (i < priv->plat->psu_count) { | |
153 | PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev, | |
154 | GFP_KERNEL, "psu%u", i + 1); | |
155 | PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_PSU; | |
156 | } else if (i < priv->plat->psu_count + priv->plat->pwr_count) { | |
157 | PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev, | |
158 | GFP_KERNEL, "pwr%u", i % | |
159 | priv->plat->pwr_count + 1); | |
160 | PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_PWR; | |
161 | } else { | |
162 | PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev, | |
163 | GFP_KERNEL, "fan%u", i % | |
164 | priv->plat->fan_count + 1); | |
165 | PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_FAN; | |
166 | } | |
167 | ||
168 | if (!PRIV_ATTR(i)->name) { | |
169 | dev_err(&priv->pdev->dev, "Memory allocation failed for sysfs attribute %d.\n", | |
170 | i + 1); | |
171 | return -ENOMEM; | |
172 | } | |
173 | ||
174 | PRIV_DEV_ATTR(i).dev_attr.attr.name = PRIV_ATTR(i)->name; | |
175 | PRIV_DEV_ATTR(i).dev_attr.attr.mode = S_IRUGO; | |
176 | PRIV_DEV_ATTR(i).dev_attr.show = mlxcpld_hotplug_attr_show; | |
177 | PRIV_DEV_ATTR(i).index = i; | |
178 | sysfs_attr_init(&PRIV_DEV_ATTR(i).dev_attr.attr); | |
179 | } | |
180 | ||
181 | priv->group.attrs = priv->mlxcpld_hotplug_attr; | |
182 | priv->groups[0] = &priv->group; | |
183 | priv->groups[1] = NULL; | |
184 | ||
185 | return 0; | |
186 | } | |
187 | ||
188 | static int mlxcpld_hotplug_device_create(struct device *dev, | |
189 | struct mlxcpld_hotplug_device *item) | |
190 | { | |
191 | item->adapter = i2c_get_adapter(item->bus); | |
192 | if (!item->adapter) { | |
193 | dev_err(dev, "Failed to get adapter for bus %d\n", | |
194 | item->bus); | |
195 | return -EFAULT; | |
196 | } | |
197 | ||
198 | item->client = i2c_new_device(item->adapter, &item->brdinfo); | |
199 | if (!item->client) { | |
200 | dev_err(dev, "Failed to create client %s at bus %d at addr 0x%02x\n", | |
201 | item->brdinfo.type, item->bus, item->brdinfo.addr); | |
202 | i2c_put_adapter(item->adapter); | |
203 | item->adapter = NULL; | |
204 | return -EFAULT; | |
205 | } | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
210 | static void mlxcpld_hotplug_device_destroy(struct mlxcpld_hotplug_device *item) | |
211 | { | |
212 | if (item->client) { | |
213 | i2c_unregister_device(item->client); | |
214 | item->client = NULL; | |
215 | } | |
216 | ||
217 | if (item->adapter) { | |
218 | i2c_put_adapter(item->adapter); | |
219 | item->adapter = NULL; | |
220 | } | |
221 | } | |
222 | ||
223 | static inline void | |
224 | mlxcpld_hotplug_work_helper(struct device *dev, | |
225 | struct mlxcpld_hotplug_device *item, u8 is_inverse, | |
226 | u16 offset, u8 mask, u8 *cache) | |
227 | { | |
228 | u8 val, asserted; | |
229 | int bit; | |
230 | ||
231 | /* Mask event. */ | |
232 | outb(0, offset + MLXCPLD_HOTPLUG_MASK_OFF); | |
233 | /* Read status. */ | |
234 | val = inb(offset) & mask; | |
235 | asserted = *cache ^ val; | |
236 | *cache = val; | |
237 | ||
238 | /* | |
239 | * Validate if item related to received signal type is valid. | |
240 | * It should never happen, excepted the situation when some | |
241 | * piece of hardware is broken. In such situation just produce | |
242 | * error message and return. Caller must continue to handle the | |
243 | * signals from other devices if any. | |
244 | */ | |
245 | if (unlikely(!item)) { | |
246 | dev_err(dev, "False signal is received: register at offset 0x%02x, mask 0x%02x.\n", | |
247 | offset, mask); | |
248 | return; | |
249 | } | |
250 | ||
251 | for_each_set_bit(bit, (unsigned long *)&asserted, 8) { | |
252 | if (val & BIT(bit)) { | |
253 | if (is_inverse) | |
254 | mlxcpld_hotplug_device_destroy(item + bit); | |
255 | else | |
256 | mlxcpld_hotplug_device_create(dev, item + bit); | |
257 | } else { | |
258 | if (is_inverse) | |
259 | mlxcpld_hotplug_device_create(dev, item + bit); | |
260 | else | |
261 | mlxcpld_hotplug_device_destroy(item + bit); | |
262 | } | |
263 | } | |
264 | ||
265 | /* Acknowledge event. */ | |
266 | outb(0, offset + MLXCPLD_HOTPLUG_EVENT_OFF); | |
267 | /* Unmask event. */ | |
268 | outb(mask, offset + MLXCPLD_HOTPLUG_MASK_OFF); | |
269 | } | |
270 | ||
271 | /* | |
272 | * mlxcpld_hotplug_work_handler - performs traversing of CPLD interrupt | |
273 | * registers according to the below hierarchy schema: | |
274 | * | |
275 | * Aggregation registers (status/mask) | |
276 | * PSU registers: *---* | |
277 | * *-----------------* | | | |
278 | * |status/event/mask|----->| * | | |
279 | * *-----------------* | | | |
280 | * Power registers: | | | |
281 | * *-----------------* | | | |
282 | * |status/event/mask|----->| * |---> CPU | |
283 | * *-----------------* | | | |
284 | * FAN registers: | |
285 | * *-----------------* | | | |
286 | * |status/event/mask|----->| * | | |
287 | * *-----------------* | | | |
288 | * *---* | |
289 | * In case some system changed are detected: FAN in/out, PSU in/out, power | |
290 | * cable attached/detached, relevant device is created or destroyed. | |
291 | */ | |
292 | static void mlxcpld_hotplug_work_handler(struct work_struct *work) | |
293 | { | |
294 | struct mlxcpld_hotplug_priv_data *priv = container_of(work, | |
295 | struct mlxcpld_hotplug_priv_data, dwork.work); | |
296 | u8 val, aggr_asserted; | |
297 | unsigned long flags; | |
298 | ||
299 | /* Mask aggregation event. */ | |
300 | outb(0, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF); | |
301 | /* Read aggregation status. */ | |
302 | val = inb(priv->plat->top_aggr_offset) & priv->plat->top_aggr_mask; | |
303 | aggr_asserted = priv->aggr_cache ^ val; | |
304 | priv->aggr_cache = val; | |
305 | ||
306 | /* Handle PSU configuration changes. */ | |
307 | if (aggr_asserted & priv->plat->top_aggr_psu_mask) | |
308 | mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->psu, | |
309 | 1, priv->plat->psu_reg_offset, | |
310 | priv->plat->psu_mask, | |
311 | &priv->psu_cache); | |
312 | ||
313 | /* Handle power cable configuration changes. */ | |
314 | if (aggr_asserted & priv->plat->top_aggr_pwr_mask) | |
315 | mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->pwr, | |
316 | 0, priv->plat->pwr_reg_offset, | |
317 | priv->plat->pwr_mask, | |
318 | &priv->pwr_cache); | |
319 | ||
320 | /* Handle FAN configuration changes. */ | |
321 | if (aggr_asserted & priv->plat->top_aggr_fan_mask) | |
322 | mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->fan, | |
323 | 1, priv->plat->fan_reg_offset, | |
324 | priv->plat->fan_mask, | |
325 | &priv->fan_cache); | |
326 | ||
327 | if (aggr_asserted) { | |
328 | spin_lock_irqsave(&priv->lock, flags); | |
329 | ||
330 | /* | |
331 | * It is possible, that some signals have been inserted, while | |
332 | * interrupt has been masked by mlxcpld_hotplug_work_handler. | |
333 | * In this case such signals will be missed. In order to handle | |
334 | * these signals delayed work is canceled and work task | |
335 | * re-scheduled for immediate execution. It allows to handle | |
336 | * missed signals, if any. In other case work handler just | |
337 | * validates that no new signals have been received during | |
338 | * masking. | |
339 | */ | |
340 | cancel_delayed_work(&priv->dwork); | |
341 | schedule_delayed_work(&priv->dwork, 0); | |
342 | ||
343 | spin_unlock_irqrestore(&priv->lock, flags); | |
344 | ||
345 | return; | |
346 | } | |
347 | ||
348 | /* Unmask aggregation event (no need acknowledge). */ | |
349 | outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset + | |
350 | MLXCPLD_HOTPLUG_AGGR_MASK_OFF); | |
351 | } | |
352 | ||
353 | static void mlxcpld_hotplug_set_irq(struct mlxcpld_hotplug_priv_data *priv) | |
354 | { | |
355 | /* Clear psu presense event. */ | |
356 | outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); | |
357 | /* Set psu initial status as mask and unmask psu event. */ | |
358 | priv->psu_cache = priv->plat->psu_mask; | |
359 | outb(priv->plat->psu_mask, priv->plat->psu_reg_offset + | |
360 | MLXCPLD_HOTPLUG_MASK_OFF); | |
361 | ||
362 | /* Clear power cable event. */ | |
363 | outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); | |
364 | /* Keep power initial status as zero and unmask power event. */ | |
365 | outb(priv->plat->pwr_mask, priv->plat->pwr_reg_offset + | |
366 | MLXCPLD_HOTPLUG_MASK_OFF); | |
367 | ||
368 | /* Clear fan presense event. */ | |
369 | outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); | |
370 | /* Set fan initial status as mask and unmask fan event. */ | |
371 | priv->fan_cache = priv->plat->fan_mask; | |
372 | outb(priv->plat->fan_mask, priv->plat->fan_reg_offset + | |
373 | MLXCPLD_HOTPLUG_MASK_OFF); | |
374 | ||
375 | /* Keep aggregation initial status as zero and unmask events. */ | |
376 | outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset + | |
377 | MLXCPLD_HOTPLUG_AGGR_MASK_OFF); | |
378 | ||
379 | /* Invoke work handler for initializing hot plug devices setting. */ | |
380 | mlxcpld_hotplug_work_handler(&priv->dwork.work); | |
381 | ||
382 | enable_irq(priv->irq); | |
383 | } | |
384 | ||
385 | static void mlxcpld_hotplug_unset_irq(struct mlxcpld_hotplug_priv_data *priv) | |
386 | { | |
387 | int i; | |
388 | ||
389 | disable_irq(priv->irq); | |
390 | cancel_delayed_work_sync(&priv->dwork); | |
391 | ||
392 | /* Mask aggregation event. */ | |
393 | outb(0, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF); | |
394 | ||
395 | /* Mask psu presense event. */ | |
396 | outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); | |
397 | /* Clear psu presense event. */ | |
398 | outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); | |
399 | ||
400 | /* Mask power cable event. */ | |
401 | outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); | |
402 | /* Clear power cable event. */ | |
403 | outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); | |
404 | ||
405 | /* Mask fan presense event. */ | |
406 | outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); | |
407 | /* Clear fan presense event. */ | |
408 | outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); | |
409 | ||
410 | /* Remove all the attached devices. */ | |
411 | for (i = 0; i < priv->plat->psu_count; i++) | |
412 | mlxcpld_hotplug_device_destroy(priv->plat->psu + i); | |
413 | ||
414 | for (i = 0; i < priv->plat->pwr_count; i++) | |
415 | mlxcpld_hotplug_device_destroy(priv->plat->pwr + i); | |
416 | ||
417 | for (i = 0; i < priv->plat->fan_count; i++) | |
418 | mlxcpld_hotplug_device_destroy(priv->plat->fan + i); | |
419 | } | |
420 | ||
421 | static irqreturn_t mlxcpld_hotplug_irq_handler(int irq, void *dev) | |
422 | { | |
423 | struct mlxcpld_hotplug_priv_data *priv = | |
424 | (struct mlxcpld_hotplug_priv_data *)dev; | |
425 | ||
426 | /* Schedule work task for immediate execution.*/ | |
427 | schedule_delayed_work(&priv->dwork, 0); | |
428 | ||
429 | return IRQ_HANDLED; | |
430 | } | |
431 | ||
432 | static int mlxcpld_hotplug_probe(struct platform_device *pdev) | |
433 | { | |
434 | struct mlxcpld_hotplug_platform_data *pdata; | |
435 | struct mlxcpld_hotplug_priv_data *priv; | |
436 | int err; | |
437 | ||
438 | pdata = dev_get_platdata(&pdev->dev); | |
439 | if (!pdata) { | |
440 | dev_err(&pdev->dev, "Failed to get platform data.\n"); | |
441 | return -EINVAL; | |
442 | } | |
443 | ||
444 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | |
445 | if (!priv) | |
446 | return -ENOMEM; | |
447 | ||
448 | priv->pdev = pdev; | |
449 | priv->plat = pdata; | |
450 | ||
451 | priv->irq = platform_get_irq(pdev, 0); | |
452 | if (priv->irq < 0) { | |
453 | dev_err(&pdev->dev, "Failed to get platform irq: %d\n", | |
454 | priv->irq); | |
455 | return priv->irq; | |
456 | } | |
457 | ||
458 | err = devm_request_irq(&pdev->dev, priv->irq, | |
459 | mlxcpld_hotplug_irq_handler, 0, pdev->name, | |
460 | priv); | |
461 | if (err) { | |
462 | dev_err(&pdev->dev, "Failed to request irq: %d\n", err); | |
463 | return err; | |
464 | } | |
465 | disable_irq(priv->irq); | |
466 | ||
467 | INIT_DELAYED_WORK(&priv->dwork, mlxcpld_hotplug_work_handler); | |
468 | spin_lock_init(&priv->lock); | |
469 | ||
470 | err = mlxcpld_hotplug_attr_init(priv); | |
471 | if (err) { | |
472 | dev_err(&pdev->dev, "Failed to allocate attributes: %d\n", err); | |
473 | return err; | |
474 | } | |
475 | ||
476 | priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, | |
477 | "mlxcpld_hotplug", priv, priv->groups); | |
478 | if (IS_ERR(priv->hwmon)) { | |
479 | dev_err(&pdev->dev, "Failed to register hwmon device %ld\n", | |
480 | PTR_ERR(priv->hwmon)); | |
481 | return PTR_ERR(priv->hwmon); | |
482 | } | |
483 | ||
484 | platform_set_drvdata(pdev, priv); | |
485 | ||
486 | /* Perform initial interrupts setup. */ | |
487 | mlxcpld_hotplug_set_irq(priv); | |
488 | ||
489 | return 0; | |
490 | } | |
491 | ||
492 | static int mlxcpld_hotplug_remove(struct platform_device *pdev) | |
493 | { | |
494 | struct mlxcpld_hotplug_priv_data *priv = platform_get_drvdata(pdev); | |
495 | ||
496 | /* Clean interrupts setup. */ | |
497 | mlxcpld_hotplug_unset_irq(priv); | |
498 | ||
499 | return 0; | |
500 | } | |
501 | ||
502 | static struct platform_driver mlxcpld_hotplug_driver = { | |
503 | .driver = { | |
504 | .name = "mlxcpld-hotplug", | |
505 | }, | |
506 | .probe = mlxcpld_hotplug_probe, | |
507 | .remove = mlxcpld_hotplug_remove, | |
508 | }; | |
509 | ||
510 | module_platform_driver(mlxcpld_hotplug_driver); | |
511 | ||
512 | MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); | |
513 | MODULE_DESCRIPTION("Mellanox CPLD hotplug platform driver"); | |
514 | MODULE_LICENSE("Dual BSD/GPL"); | |
515 | MODULE_ALIAS("platform:mlxcpld-hotplug"); |