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>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
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.
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.
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.
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>
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>
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
54 #define MLXCPLD_HOTPLUG_ATTRS_NUM 8
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;
62 enum mlxcpld_hotplug_attr_type
{
63 MLXCPLD_HOTPLUG_ATTR_TYPE_PSU
,
64 MLXCPLD_HOTPLUG_ATTR_TYPE_PWR
,
65 MLXCPLD_HOTPLUG_ATTR_TYPE_FAN
,
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;
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;
85 struct mlxcpld_hotplug_priv_data
{
87 struct platform_device
*pdev
;
88 struct mlxcpld_hotplug_platform_data
*plat
;
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
;
103 static ssize_t
mlxcpld_hotplug_attr_show(struct device
*dev
,
104 struct device_attribute
*attr
,
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
;
114 case MLXCPLD_HOTPLUG_ATTR_TYPE_PSU
:
115 /* Bit = 0 : PSU is present. */
116 reg_val
= !!!(inb(priv
->plat
->psu_reg_offset
) & BIT(index
));
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
));
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
));
132 return sprintf(buf
, "%u\n", reg_val
);
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
)
139 int num_attrs
= priv
->plat
->psu_count
+ priv
->plat
->pwr_count
+
140 priv
->plat
->fan_count
;
143 priv
->group
.attrs
= devm_kzalloc(&priv
->pdev
->dev
, num_attrs
*
144 sizeof(struct attribute
*),
146 if (!priv
->group
.attrs
)
149 for (i
= 0; i
< num_attrs
; i
++) {
150 PRIV_ATTR(i
) = &PRIV_DEV_ATTR(i
).dev_attr
.attr
;
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
;
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
;
168 if (!PRIV_ATTR(i
)->name
) {
169 dev_err(&priv
->pdev
->dev
, "Memory allocation failed for sysfs attribute %d.\n",
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
);
181 priv
->group
.attrs
= priv
->mlxcpld_hotplug_attr
;
182 priv
->groups
[0] = &priv
->group
;
183 priv
->groups
[1] = NULL
;
188 static int mlxcpld_hotplug_device_create(struct device
*dev
,
189 struct mlxcpld_hotplug_device
*item
)
191 item
->adapter
= i2c_get_adapter(item
->bus
);
192 if (!item
->adapter
) {
193 dev_err(dev
, "Failed to get adapter for bus %d\n",
198 item
->client
= i2c_new_device(item
->adapter
, &item
->brdinfo
);
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
;
210 static void mlxcpld_hotplug_device_destroy(struct mlxcpld_hotplug_device
*item
)
213 i2c_unregister_device(item
->client
);
218 i2c_put_adapter(item
->adapter
);
219 item
->adapter
= NULL
;
224 mlxcpld_hotplug_work_helper(struct device
*dev
,
225 struct mlxcpld_hotplug_device
*item
, u8 is_inverse
,
226 u16 offset
, u8 mask
, u8
*cache
)
232 outb(0, offset
+ MLXCPLD_HOTPLUG_MASK_OFF
);
234 val
= inb(offset
) & mask
;
235 asserted
= *cache
^ val
;
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.
245 if (unlikely(!item
)) {
246 dev_err(dev
, "False signal is received: register at offset 0x%02x, mask 0x%02x.\n",
251 for_each_set_bit(bit
, (unsigned long *)&asserted
, 8) {
252 if (val
& BIT(bit
)) {
254 mlxcpld_hotplug_device_destroy(item
+ bit
);
256 mlxcpld_hotplug_device_create(dev
, item
+ bit
);
259 mlxcpld_hotplug_device_create(dev
, item
+ bit
);
261 mlxcpld_hotplug_device_destroy(item
+ bit
);
265 /* Acknowledge event. */
266 outb(0, offset
+ MLXCPLD_HOTPLUG_EVENT_OFF
);
268 outb(mask
, offset
+ MLXCPLD_HOTPLUG_MASK_OFF
);
272 * mlxcpld_hotplug_work_handler - performs traversing of CPLD interrupt
273 * registers according to the below hierarchy schema:
275 * Aggregation registers (status/mask)
276 * PSU registers: *---*
277 * *-----------------* | |
278 * |status/event/mask|----->| * |
279 * *-----------------* | |
280 * Power registers: | |
281 * *-----------------* | |
282 * |status/event/mask|----->| * |---> CPU
283 * *-----------------* | |
285 * *-----------------* | |
286 * |status/event/mask|----->| * |
287 * *-----------------* | |
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.
292 static void mlxcpld_hotplug_work_handler(struct work_struct
*work
)
294 struct mlxcpld_hotplug_priv_data
*priv
= container_of(work
,
295 struct mlxcpld_hotplug_priv_data
, dwork
.work
);
296 u8 val
, aggr_asserted
;
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
;
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
,
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
,
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
,
328 spin_lock_irqsave(&priv
->lock
, flags
);
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
340 cancel_delayed_work(&priv
->dwork
);
341 schedule_delayed_work(&priv
->dwork
, 0);
343 spin_unlock_irqrestore(&priv
->lock
, flags
);
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
);
353 static void mlxcpld_hotplug_set_irq(struct mlxcpld_hotplug_priv_data
*priv
)
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
);
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
);
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
);
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
);
379 /* Invoke work handler for initializing hot plug devices setting. */
380 mlxcpld_hotplug_work_handler(&priv
->dwork
.work
);
382 enable_irq(priv
->irq
);
385 static void mlxcpld_hotplug_unset_irq(struct mlxcpld_hotplug_priv_data
*priv
)
389 disable_irq(priv
->irq
);
390 cancel_delayed_work_sync(&priv
->dwork
);
392 /* Mask aggregation event. */
393 outb(0, priv
->plat
->top_aggr_offset
+ MLXCPLD_HOTPLUG_AGGR_MASK_OFF
);
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
);
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
);
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
);
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
);
414 for (i
= 0; i
< priv
->plat
->pwr_count
; i
++)
415 mlxcpld_hotplug_device_destroy(priv
->plat
->pwr
+ i
);
417 for (i
= 0; i
< priv
->plat
->fan_count
; i
++)
418 mlxcpld_hotplug_device_destroy(priv
->plat
->fan
+ i
);
421 static irqreturn_t
mlxcpld_hotplug_irq_handler(int irq
, void *dev
)
423 struct mlxcpld_hotplug_priv_data
*priv
=
424 (struct mlxcpld_hotplug_priv_data
*)dev
;
426 /* Schedule work task for immediate execution.*/
427 schedule_delayed_work(&priv
->dwork
, 0);
432 static int mlxcpld_hotplug_probe(struct platform_device
*pdev
)
434 struct mlxcpld_hotplug_platform_data
*pdata
;
435 struct mlxcpld_hotplug_priv_data
*priv
;
438 pdata
= dev_get_platdata(&pdev
->dev
);
440 dev_err(&pdev
->dev
, "Failed to get platform data.\n");
444 priv
= devm_kzalloc(&pdev
->dev
, sizeof(*priv
), GFP_KERNEL
);
451 priv
->irq
= platform_get_irq(pdev
, 0);
453 dev_err(&pdev
->dev
, "Failed to get platform irq: %d\n",
458 err
= devm_request_irq(&pdev
->dev
, priv
->irq
,
459 mlxcpld_hotplug_irq_handler
, 0, pdev
->name
,
462 dev_err(&pdev
->dev
, "Failed to request irq: %d\n", err
);
465 disable_irq(priv
->irq
);
467 INIT_DELAYED_WORK(&priv
->dwork
, mlxcpld_hotplug_work_handler
);
468 spin_lock_init(&priv
->lock
);
470 err
= mlxcpld_hotplug_attr_init(priv
);
472 dev_err(&pdev
->dev
, "Failed to allocate attributes: %d\n", err
);
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
);
484 platform_set_drvdata(pdev
, priv
);
486 /* Perform initial interrupts setup. */
487 mlxcpld_hotplug_set_irq(priv
);
492 static int mlxcpld_hotplug_remove(struct platform_device
*pdev
)
494 struct mlxcpld_hotplug_priv_data
*priv
= platform_get_drvdata(pdev
);
496 /* Clean interrupts setup. */
497 mlxcpld_hotplug_unset_irq(priv
);
502 static struct platform_driver mlxcpld_hotplug_driver
= {
504 .name
= "mlxcpld-hotplug",
506 .probe
= mlxcpld_hotplug_probe
,
507 .remove
= mlxcpld_hotplug_remove
,
510 module_platform_driver(mlxcpld_hotplug_driver
);
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");