GNU Linux-libre 4.14.251-gnu1
[releases.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");