]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blob - drivers/platform/x86/mlxcpld-hotplug.c
Merge tag 'v4.10' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux...
[mirror_ubuntu-artful-kernel.git] / drivers / platform / x86 / mlxcpld-hotplug.c
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");