1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Mellanox hotplug driver |
4 | * |
5 | * Copyright (C) 2016-2020 Mellanox Technologies |
6 | */ |
7 | |
8 | #include <linux/bitops.h> |
9 | #include <linux/device.h> |
10 | #include <linux/hwmon.h> |
11 | #include <linux/hwmon-sysfs.h> |
12 | #include <linux/i2c.h> |
13 | #include <linux/interrupt.h> |
14 | #include <linux/module.h> |
15 | #include <linux/platform_data/mlxreg.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/spinlock.h> |
18 | #include <linux/string_helpers.h> |
19 | #include <linux/regmap.h> |
20 | #include <linux/workqueue.h> |
21 | |
22 | /* Offset of event and mask registers from status register. */ |
23 | #define MLXREG_HOTPLUG_EVENT_OFF 1 |
24 | #define MLXREG_HOTPLUG_MASK_OFF 2 |
25 | #define MLXREG_HOTPLUG_AGGR_MASK_OFF 1 |
26 | |
27 | /* ASIC good health mask. */ |
28 | #define MLXREG_HOTPLUG_GOOD_HEALTH_MASK 0x02 |
29 | |
30 | #define MLXREG_HOTPLUG_ATTRS_MAX 128 |
31 | #define MLXREG_HOTPLUG_NOT_ASSERT 3 |
32 | |
33 | /** |
34 | * struct mlxreg_hotplug_priv_data - platform private data: |
35 | * @irq: platform device interrupt number; |
36 | * @dev: basic device; |
37 | * @pdev: platform device; |
38 | * @plat: platform data; |
39 | * @regmap: register map handle; |
40 | * @dwork_irq: delayed work template; |
41 | * @lock: spin lock; |
42 | * @hwmon: hwmon device; |
43 | * @mlxreg_hotplug_attr: sysfs attributes array; |
44 | * @mlxreg_hotplug_dev_attr: sysfs sensor device attribute array; |
45 | * @group: sysfs attribute group; |
46 | * @groups: list of sysfs attribute group for hwmon registration; |
47 | * @cell: location of top aggregation interrupt register; |
48 | * @mask: top aggregation interrupt common mask; |
49 | * @aggr_cache: last value of aggregation register status; |
50 | * @after_probe: flag indication probing completion; |
51 | * @not_asserted: number of entries in workqueue with no signal assertion; |
52 | */ |
53 | struct mlxreg_hotplug_priv_data { |
54 | int irq; |
55 | struct device *dev; |
56 | struct platform_device *pdev; |
57 | struct mlxreg_hotplug_platform_data *plat; |
58 | struct regmap *regmap; |
59 | struct delayed_work dwork_irq; |
60 | spinlock_t lock; /* sync with interrupt */ |
61 | struct device *hwmon; |
62 | struct attribute *mlxreg_hotplug_attr[MLXREG_HOTPLUG_ATTRS_MAX + 1]; |
63 | struct sensor_device_attribute_2 |
64 | mlxreg_hotplug_dev_attr[MLXREG_HOTPLUG_ATTRS_MAX]; |
65 | struct attribute_group group; |
66 | const struct attribute_group *groups[2]; |
67 | u32 cell; |
68 | u32 mask; |
69 | u32 aggr_cache; |
70 | bool after_probe; |
71 | u8 not_asserted; |
72 | }; |
73 | |
74 | /* Environment variables array for udev. */ |
75 | static char *mlxreg_hotplug_udev_envp[] = { NULL, NULL }; |
76 | |
77 | static int |
78 | mlxreg_hotplug_udev_event_send(struct kobject *kobj, |
79 | struct mlxreg_core_data *data, bool action) |
80 | { |
81 | char event_str[MLXREG_CORE_LABEL_MAX_SIZE + 2]; |
82 | char label[MLXREG_CORE_LABEL_MAX_SIZE] = { 0 }; |
83 | |
84 | mlxreg_hotplug_udev_envp[0] = event_str; |
85 | string_upper(dst: label, src: data->label); |
86 | snprintf(buf: event_str, MLXREG_CORE_LABEL_MAX_SIZE, fmt: "%s=%d" , label, !!action); |
87 | |
88 | return kobject_uevent_env(kobj, action: KOBJ_CHANGE, envp: mlxreg_hotplug_udev_envp); |
89 | } |
90 | |
91 | static void |
92 | mlxreg_hotplug_pdata_export(void *pdata, void *regmap) |
93 | { |
94 | struct mlxreg_core_hotplug_platform_data *dev_pdata = pdata; |
95 | |
96 | /* Export regmap to underlying device. */ |
97 | dev_pdata->regmap = regmap; |
98 | } |
99 | |
100 | static int mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data *priv, |
101 | struct mlxreg_core_data *data, |
102 | enum mlxreg_hotplug_kind kind) |
103 | { |
104 | struct i2c_board_info *brdinfo = data->hpdev.brdinfo; |
105 | struct mlxreg_core_hotplug_platform_data *pdata; |
106 | struct i2c_client *client; |
107 | |
108 | /* Notify user by sending hwmon uevent. */ |
109 | mlxreg_hotplug_udev_event_send(kobj: &priv->hwmon->kobj, data, action: true); |
110 | |
111 | /* |
112 | * Return if adapter number is negative. It could be in case hotplug |
113 | * event is not associated with hotplug device. |
114 | */ |
115 | if (data->hpdev.nr < 0 && data->hpdev.action != MLXREG_HOTPLUG_DEVICE_NO_ACTION) |
116 | return 0; |
117 | |
118 | pdata = dev_get_platdata(dev: &priv->pdev->dev); |
119 | switch (data->hpdev.action) { |
120 | case MLXREG_HOTPLUG_DEVICE_DEFAULT_ACTION: |
121 | data->hpdev.adapter = i2c_get_adapter(nr: data->hpdev.nr + |
122 | pdata->shift_nr); |
123 | if (!data->hpdev.adapter) { |
124 | dev_err(priv->dev, "Failed to get adapter for bus %d\n" , |
125 | data->hpdev.nr + pdata->shift_nr); |
126 | return -EFAULT; |
127 | } |
128 | |
129 | /* Export platform data to underlying device. */ |
130 | if (brdinfo->platform_data) |
131 | mlxreg_hotplug_pdata_export(pdata: brdinfo->platform_data, regmap: pdata->regmap); |
132 | |
133 | client = i2c_new_client_device(adap: data->hpdev.adapter, |
134 | info: brdinfo); |
135 | if (IS_ERR(ptr: client)) { |
136 | dev_err(priv->dev, "Failed to create client %s at bus %d at addr 0x%02x\n" , |
137 | brdinfo->type, data->hpdev.nr + |
138 | pdata->shift_nr, brdinfo->addr); |
139 | |
140 | i2c_put_adapter(adap: data->hpdev.adapter); |
141 | data->hpdev.adapter = NULL; |
142 | return PTR_ERR(ptr: client); |
143 | } |
144 | |
145 | data->hpdev.client = client; |
146 | break; |
147 | case MLXREG_HOTPLUG_DEVICE_PLATFORM_ACTION: |
148 | /* Export platform data to underlying device. */ |
149 | if (data->hpdev.brdinfo && data->hpdev.brdinfo->platform_data) |
150 | mlxreg_hotplug_pdata_export(pdata: data->hpdev.brdinfo->platform_data, |
151 | regmap: pdata->regmap); |
152 | /* Pass parent hotplug device handle to underlying device. */ |
153 | data->notifier = data->hpdev.notifier; |
154 | data->hpdev.pdev = platform_device_register_resndata(parent: &priv->pdev->dev, |
155 | name: brdinfo->type, |
156 | id: data->hpdev.nr, |
157 | NULL, num: 0, data, |
158 | size: sizeof(*data)); |
159 | if (IS_ERR(ptr: data->hpdev.pdev)) |
160 | return PTR_ERR(ptr: data->hpdev.pdev); |
161 | |
162 | break; |
163 | default: |
164 | break; |
165 | } |
166 | |
167 | if (data->hpdev.notifier && data->hpdev.notifier->user_handler) |
168 | return data->hpdev.notifier->user_handler(data->hpdev.notifier->handle, kind, 1); |
169 | |
170 | return 0; |
171 | } |
172 | |
173 | static void |
174 | mlxreg_hotplug_device_destroy(struct mlxreg_hotplug_priv_data *priv, |
175 | struct mlxreg_core_data *data, |
176 | enum mlxreg_hotplug_kind kind) |
177 | { |
178 | /* Notify user by sending hwmon uevent. */ |
179 | mlxreg_hotplug_udev_event_send(kobj: &priv->hwmon->kobj, data, action: false); |
180 | if (data->hpdev.notifier && data->hpdev.notifier->user_handler) |
181 | data->hpdev.notifier->user_handler(data->hpdev.notifier->handle, kind, 0); |
182 | |
183 | switch (data->hpdev.action) { |
184 | case MLXREG_HOTPLUG_DEVICE_DEFAULT_ACTION: |
185 | if (data->hpdev.client) { |
186 | i2c_unregister_device(client: data->hpdev.client); |
187 | data->hpdev.client = NULL; |
188 | } |
189 | |
190 | if (data->hpdev.adapter) { |
191 | i2c_put_adapter(adap: data->hpdev.adapter); |
192 | data->hpdev.adapter = NULL; |
193 | } |
194 | break; |
195 | case MLXREG_HOTPLUG_DEVICE_PLATFORM_ACTION: |
196 | if (data->hpdev.pdev) |
197 | platform_device_unregister(data->hpdev.pdev); |
198 | break; |
199 | default: |
200 | break; |
201 | } |
202 | } |
203 | |
204 | static ssize_t mlxreg_hotplug_attr_show(struct device *dev, |
205 | struct device_attribute *attr, |
206 | char *buf) |
207 | { |
208 | struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(dev); |
209 | struct mlxreg_core_hotplug_platform_data *pdata; |
210 | int index = to_sensor_dev_attr_2(attr)->index; |
211 | int nr = to_sensor_dev_attr_2(attr)->nr; |
212 | struct mlxreg_core_item *item; |
213 | struct mlxreg_core_data *data; |
214 | u32 regval; |
215 | int ret; |
216 | |
217 | pdata = dev_get_platdata(dev: &priv->pdev->dev); |
218 | item = pdata->items + nr; |
219 | data = item->data + index; |
220 | |
221 | ret = regmap_read(map: priv->regmap, reg: data->reg, val: ®val); |
222 | if (ret) |
223 | return ret; |
224 | |
225 | if (item->health) { |
226 | regval &= data->mask; |
227 | } else { |
228 | /* Bit = 0 : functional if item->inversed is true. */ |
229 | if (item->inversed) |
230 | regval = !(regval & data->mask); |
231 | else |
232 | regval = !!(regval & data->mask); |
233 | } |
234 | |
235 | return sprintf(buf, fmt: "%u\n" , regval); |
236 | } |
237 | |
238 | #define PRIV_ATTR(i) priv->mlxreg_hotplug_attr[i] |
239 | #define PRIV_DEV_ATTR(i) priv->mlxreg_hotplug_dev_attr[i] |
240 | |
241 | static int mlxreg_hotplug_item_label_index_get(u32 mask, u32 bit) |
242 | { |
243 | int i, j; |
244 | |
245 | for (i = 0, j = -1; i <= bit; i++) { |
246 | if (mask & BIT(i)) |
247 | j++; |
248 | } |
249 | return j; |
250 | } |
251 | |
252 | static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) |
253 | { |
254 | struct mlxreg_core_hotplug_platform_data *pdata; |
255 | struct mlxreg_core_item *item; |
256 | struct mlxreg_core_data *data; |
257 | unsigned long mask; |
258 | u32 regval; |
259 | int num_attrs = 0, id = 0, i, j, k, count, ret; |
260 | |
261 | pdata = dev_get_platdata(dev: &priv->pdev->dev); |
262 | item = pdata->items; |
263 | |
264 | /* Go over all kinds of items - psu, pwr, fan. */ |
265 | for (i = 0; i < pdata->counter; i++, item++) { |
266 | if (item->capability) { |
267 | /* |
268 | * Read group capability register to get actual number |
269 | * of interrupt capable components and set group mask |
270 | * accordingly. |
271 | */ |
272 | ret = regmap_read(map: priv->regmap, reg: item->capability, |
273 | val: ®val); |
274 | if (ret) |
275 | return ret; |
276 | |
277 | item->mask = GENMASK((regval & item->mask) - 1, 0); |
278 | } |
279 | |
280 | data = item->data; |
281 | |
282 | /* Go over all unmasked units within item. */ |
283 | mask = item->mask; |
284 | k = 0; |
285 | count = item->ind ? item->ind : item->count; |
286 | for_each_set_bit(j, &mask, count) { |
287 | if (data->capability) { |
288 | /* |
289 | * Read capability register and skip non |
290 | * relevant attributes. |
291 | */ |
292 | ret = regmap_read(map: priv->regmap, |
293 | reg: data->capability, val: ®val); |
294 | if (ret) |
295 | return ret; |
296 | |
297 | if (!(regval & data->bit)) { |
298 | data++; |
299 | continue; |
300 | } |
301 | } |
302 | |
303 | PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr; |
304 | PRIV_ATTR(id)->name = devm_kasprintf(dev: &priv->pdev->dev, |
305 | GFP_KERNEL, |
306 | fmt: data->label); |
307 | if (!PRIV_ATTR(id)->name) { |
308 | dev_err(priv->dev, "Memory allocation failed for attr %d.\n" , |
309 | id); |
310 | return -ENOMEM; |
311 | } |
312 | |
313 | PRIV_DEV_ATTR(id).dev_attr.attr.name = |
314 | PRIV_ATTR(id)->name; |
315 | PRIV_DEV_ATTR(id).dev_attr.attr.mode = 0444; |
316 | PRIV_DEV_ATTR(id).dev_attr.show = |
317 | mlxreg_hotplug_attr_show; |
318 | PRIV_DEV_ATTR(id).nr = i; |
319 | PRIV_DEV_ATTR(id).index = k; |
320 | sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr); |
321 | data++; |
322 | id++; |
323 | k++; |
324 | } |
325 | num_attrs += k; |
326 | } |
327 | |
328 | priv->group.attrs = devm_kcalloc(dev: &priv->pdev->dev, |
329 | n: num_attrs, |
330 | size: sizeof(struct attribute *), |
331 | GFP_KERNEL); |
332 | if (!priv->group.attrs) |
333 | return -ENOMEM; |
334 | |
335 | priv->group.attrs = priv->mlxreg_hotplug_attr; |
336 | priv->groups[0] = &priv->group; |
337 | priv->groups[1] = NULL; |
338 | |
339 | return 0; |
340 | } |
341 | |
342 | static void |
343 | mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv, |
344 | struct mlxreg_core_item *item) |
345 | { |
346 | struct mlxreg_core_data *data; |
347 | unsigned long asserted; |
348 | u32 regval, bit; |
349 | int ret; |
350 | |
351 | /* Mask event. */ |
352 | ret = regmap_write(map: priv->regmap, reg: item->reg + MLXREG_HOTPLUG_MASK_OFF, |
353 | val: 0); |
354 | if (ret) |
355 | goto out; |
356 | |
357 | /* Read status. */ |
358 | ret = regmap_read(map: priv->regmap, reg: item->reg, val: ®val); |
359 | if (ret) |
360 | goto out; |
361 | |
362 | /* Set asserted bits and save last status. */ |
363 | regval &= item->mask; |
364 | asserted = item->cache ^ regval; |
365 | item->cache = regval; |
366 | for_each_set_bit(bit, &asserted, 8) { |
367 | int pos; |
368 | |
369 | pos = mlxreg_hotplug_item_label_index_get(mask: item->mask, bit); |
370 | if (pos < 0) |
371 | goto out; |
372 | |
373 | data = item->data + pos; |
374 | if (regval & BIT(bit)) { |
375 | if (item->inversed) |
376 | mlxreg_hotplug_device_destroy(priv, data, kind: item->kind); |
377 | else |
378 | mlxreg_hotplug_device_create(priv, data, kind: item->kind); |
379 | } else { |
380 | if (item->inversed) |
381 | mlxreg_hotplug_device_create(priv, data, kind: item->kind); |
382 | else |
383 | mlxreg_hotplug_device_destroy(priv, data, kind: item->kind); |
384 | } |
385 | } |
386 | |
387 | /* Acknowledge event. */ |
388 | ret = regmap_write(map: priv->regmap, reg: item->reg + MLXREG_HOTPLUG_EVENT_OFF, |
389 | val: 0); |
390 | if (ret) |
391 | goto out; |
392 | |
393 | /* Unmask event. */ |
394 | ret = regmap_write(map: priv->regmap, reg: item->reg + MLXREG_HOTPLUG_MASK_OFF, |
395 | val: item->mask); |
396 | |
397 | out: |
398 | if (ret) |
399 | dev_err(priv->dev, "Failed to complete workqueue.\n" ); |
400 | } |
401 | |
402 | static void |
403 | mlxreg_hotplug_health_work_helper(struct mlxreg_hotplug_priv_data *priv, |
404 | struct mlxreg_core_item *item) |
405 | { |
406 | struct mlxreg_core_data *data = item->data; |
407 | u32 regval; |
408 | int i, ret = 0; |
409 | |
410 | for (i = 0; i < item->count; i++, data++) { |
411 | /* Mask event. */ |
412 | ret = regmap_write(map: priv->regmap, reg: data->reg + |
413 | MLXREG_HOTPLUG_MASK_OFF, val: 0); |
414 | if (ret) |
415 | goto out; |
416 | |
417 | /* Read status. */ |
418 | ret = regmap_read(map: priv->regmap, reg: data->reg, val: ®val); |
419 | if (ret) |
420 | goto out; |
421 | |
422 | regval &= data->mask; |
423 | |
424 | if (item->cache == regval) |
425 | goto ack_event; |
426 | |
427 | /* |
428 | * ASIC health indication is provided through two bits. Bits |
429 | * value 0x2 indicates that ASIC reached the good health, value |
430 | * 0x0 indicates ASIC the bad health or dormant state and value |
431 | * 0x3 indicates the booting state. During ASIC reset it should |
432 | * pass the following states: dormant -> booting -> good. |
433 | */ |
434 | if (regval == MLXREG_HOTPLUG_GOOD_HEALTH_MASK) { |
435 | if (!data->attached) { |
436 | /* |
437 | * ASIC is in steady state. Connect associated |
438 | * device, if configured. |
439 | */ |
440 | mlxreg_hotplug_device_create(priv, data, kind: item->kind); |
441 | data->attached = true; |
442 | } |
443 | } else { |
444 | if (data->attached) { |
445 | /* |
446 | * ASIC health is failed after ASIC has been |
447 | * in steady state. Disconnect associated |
448 | * device, if it has been connected. |
449 | */ |
450 | mlxreg_hotplug_device_destroy(priv, data, kind: item->kind); |
451 | data->attached = false; |
452 | data->health_cntr = 0; |
453 | } |
454 | } |
455 | item->cache = regval; |
456 | ack_event: |
457 | /* Acknowledge event. */ |
458 | ret = regmap_write(map: priv->regmap, reg: data->reg + |
459 | MLXREG_HOTPLUG_EVENT_OFF, val: 0); |
460 | if (ret) |
461 | goto out; |
462 | |
463 | /* Unmask event. */ |
464 | ret = regmap_write(map: priv->regmap, reg: data->reg + |
465 | MLXREG_HOTPLUG_MASK_OFF, val: data->mask); |
466 | if (ret) |
467 | goto out; |
468 | } |
469 | |
470 | out: |
471 | if (ret) |
472 | dev_err(priv->dev, "Failed to complete workqueue.\n" ); |
473 | } |
474 | |
475 | /* |
476 | * mlxreg_hotplug_work_handler - performs traversing of device interrupt |
477 | * registers according to the below hierarchy schema: |
478 | * |
479 | * Aggregation registers (status/mask) |
480 | * PSU registers: *---* |
481 | * *-----------------* | | |
482 | * |status/event/mask|-----> | * | |
483 | * *-----------------* | | |
484 | * Power registers: | | |
485 | * *-----------------* | | |
486 | * |status/event/mask|-----> | * | |
487 | * *-----------------* | | |
488 | * FAN registers: | |--> CPU |
489 | * *-----------------* | | |
490 | * |status/event/mask|-----> | * | |
491 | * *-----------------* | | |
492 | * ASIC registers: | | |
493 | * *-----------------* | | |
494 | * |status/event/mask|-----> | * | |
495 | * *-----------------* | | |
496 | * *---* |
497 | * |
498 | * In case some system changed are detected: FAN in/out, PSU in/out, power |
499 | * cable attached/detached, ASIC health good/bad, relevant device is created |
500 | * or destroyed. |
501 | */ |
502 | static void mlxreg_hotplug_work_handler(struct work_struct *work) |
503 | { |
504 | struct mlxreg_core_hotplug_platform_data *pdata; |
505 | struct mlxreg_hotplug_priv_data *priv; |
506 | struct mlxreg_core_item *item; |
507 | u32 regval, aggr_asserted; |
508 | unsigned long flags; |
509 | int i, ret; |
510 | |
511 | priv = container_of(work, struct mlxreg_hotplug_priv_data, |
512 | dwork_irq.work); |
513 | pdata = dev_get_platdata(dev: &priv->pdev->dev); |
514 | item = pdata->items; |
515 | |
516 | /* Mask aggregation event. */ |
517 | ret = regmap_write(map: priv->regmap, reg: pdata->cell + |
518 | MLXREG_HOTPLUG_AGGR_MASK_OFF, val: 0); |
519 | if (ret < 0) |
520 | goto out; |
521 | |
522 | /* Read aggregation status. */ |
523 | ret = regmap_read(map: priv->regmap, reg: pdata->cell, val: ®val); |
524 | if (ret) |
525 | goto out; |
526 | |
527 | regval &= pdata->mask; |
528 | aggr_asserted = priv->aggr_cache ^ regval; |
529 | priv->aggr_cache = regval; |
530 | |
531 | /* |
532 | * Handler is invoked, but no assertion is detected at top aggregation |
533 | * status level. Set aggr_asserted to mask value to allow handler extra |
534 | * run over all relevant signals to recover any missed signal. |
535 | */ |
536 | if (priv->not_asserted == MLXREG_HOTPLUG_NOT_ASSERT) { |
537 | priv->not_asserted = 0; |
538 | aggr_asserted = pdata->mask; |
539 | } |
540 | if (!aggr_asserted) |
541 | goto unmask_event; |
542 | |
543 | /* Handle topology and health configuration changes. */ |
544 | for (i = 0; i < pdata->counter; i++, item++) { |
545 | if (aggr_asserted & item->aggr_mask) { |
546 | if (item->health) |
547 | mlxreg_hotplug_health_work_helper(priv, item); |
548 | else |
549 | mlxreg_hotplug_work_helper(priv, item); |
550 | } |
551 | } |
552 | |
553 | spin_lock_irqsave(&priv->lock, flags); |
554 | |
555 | /* |
556 | * It is possible, that some signals have been inserted, while |
557 | * interrupt has been masked by mlxreg_hotplug_work_handler. In this |
558 | * case such signals will be missed. In order to handle these signals |
559 | * delayed work is canceled and work task re-scheduled for immediate |
560 | * execution. It allows to handle missed signals, if any. In other case |
561 | * work handler just validates that no new signals have been received |
562 | * during masking. |
563 | */ |
564 | cancel_delayed_work(dwork: &priv->dwork_irq); |
565 | schedule_delayed_work(dwork: &priv->dwork_irq, delay: 0); |
566 | |
567 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
568 | |
569 | return; |
570 | |
571 | unmask_event: |
572 | priv->not_asserted++; |
573 | /* Unmask aggregation event (no need acknowledge). */ |
574 | ret = regmap_write(map: priv->regmap, reg: pdata->cell + |
575 | MLXREG_HOTPLUG_AGGR_MASK_OFF, val: pdata->mask); |
576 | |
577 | out: |
578 | if (ret) |
579 | dev_err(priv->dev, "Failed to complete workqueue.\n" ); |
580 | } |
581 | |
582 | static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) |
583 | { |
584 | struct mlxreg_core_hotplug_platform_data *pdata; |
585 | struct mlxreg_core_item *item; |
586 | struct mlxreg_core_data *data; |
587 | u32 regval; |
588 | int i, j, ret; |
589 | |
590 | pdata = dev_get_platdata(dev: &priv->pdev->dev); |
591 | item = pdata->items; |
592 | |
593 | for (i = 0; i < pdata->counter; i++, item++) { |
594 | /* Clear group presense event. */ |
595 | ret = regmap_write(map: priv->regmap, reg: item->reg + |
596 | MLXREG_HOTPLUG_EVENT_OFF, val: 0); |
597 | if (ret) |
598 | goto out; |
599 | |
600 | /* |
601 | * Verify if hardware configuration requires to disable |
602 | * interrupt capability for some of components. |
603 | */ |
604 | data = item->data; |
605 | for (j = 0; j < item->count; j++, data++) { |
606 | /* Verify if the attribute has capability register. */ |
607 | if (data->capability) { |
608 | /* Read capability register. */ |
609 | ret = regmap_read(map: priv->regmap, |
610 | reg: data->capability, val: ®val); |
611 | if (ret) |
612 | goto out; |
613 | |
614 | if (!(regval & data->bit)) |
615 | item->mask &= ~BIT(j); |
616 | } |
617 | } |
618 | |
619 | /* Set group initial status as mask and unmask group event. */ |
620 | if (item->inversed) { |
621 | item->cache = item->mask; |
622 | ret = regmap_write(map: priv->regmap, reg: item->reg + |
623 | MLXREG_HOTPLUG_MASK_OFF, |
624 | val: item->mask); |
625 | if (ret) |
626 | goto out; |
627 | } |
628 | } |
629 | |
630 | /* Keep aggregation initial status as zero and unmask events. */ |
631 | ret = regmap_write(map: priv->regmap, reg: pdata->cell + |
632 | MLXREG_HOTPLUG_AGGR_MASK_OFF, val: pdata->mask); |
633 | if (ret) |
634 | goto out; |
635 | |
636 | /* Keep low aggregation initial status as zero and unmask events. */ |
637 | if (pdata->cell_low) { |
638 | ret = regmap_write(map: priv->regmap, reg: pdata->cell_low + |
639 | MLXREG_HOTPLUG_AGGR_MASK_OFF, |
640 | val: pdata->mask_low); |
641 | if (ret) |
642 | goto out; |
643 | } |
644 | |
645 | /* Invoke work handler for initializing hot plug devices setting. */ |
646 | mlxreg_hotplug_work_handler(work: &priv->dwork_irq.work); |
647 | |
648 | out: |
649 | if (ret) |
650 | dev_err(priv->dev, "Failed to set interrupts.\n" ); |
651 | enable_irq(irq: priv->irq); |
652 | return ret; |
653 | } |
654 | |
655 | static void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv) |
656 | { |
657 | struct mlxreg_core_hotplug_platform_data *pdata; |
658 | struct mlxreg_core_item *item; |
659 | struct mlxreg_core_data *data; |
660 | int count, i, j; |
661 | |
662 | pdata = dev_get_platdata(dev: &priv->pdev->dev); |
663 | item = pdata->items; |
664 | disable_irq(irq: priv->irq); |
665 | cancel_delayed_work_sync(dwork: &priv->dwork_irq); |
666 | |
667 | /* Mask low aggregation event, if defined. */ |
668 | if (pdata->cell_low) |
669 | regmap_write(map: priv->regmap, reg: pdata->cell_low + |
670 | MLXREG_HOTPLUG_AGGR_MASK_OFF, val: 0); |
671 | |
672 | /* Mask aggregation event. */ |
673 | regmap_write(map: priv->regmap, reg: pdata->cell + MLXREG_HOTPLUG_AGGR_MASK_OFF, |
674 | val: 0); |
675 | |
676 | /* Clear topology configurations. */ |
677 | for (i = 0; i < pdata->counter; i++, item++) { |
678 | data = item->data; |
679 | /* Mask group presense event. */ |
680 | regmap_write(map: priv->regmap, reg: data->reg + MLXREG_HOTPLUG_MASK_OFF, |
681 | val: 0); |
682 | /* Clear group presense event. */ |
683 | regmap_write(map: priv->regmap, reg: data->reg + |
684 | MLXREG_HOTPLUG_EVENT_OFF, val: 0); |
685 | |
686 | /* Remove all the attached devices in group. */ |
687 | count = item->count; |
688 | for (j = 0; j < count; j++, data++) |
689 | mlxreg_hotplug_device_destroy(priv, data, kind: item->kind); |
690 | } |
691 | } |
692 | |
693 | static irqreturn_t mlxreg_hotplug_irq_handler(int irq, void *dev) |
694 | { |
695 | struct mlxreg_hotplug_priv_data *priv; |
696 | |
697 | priv = (struct mlxreg_hotplug_priv_data *)dev; |
698 | |
699 | /* Schedule work task for immediate execution.*/ |
700 | schedule_delayed_work(dwork: &priv->dwork_irq, delay: 0); |
701 | |
702 | return IRQ_HANDLED; |
703 | } |
704 | |
705 | static int mlxreg_hotplug_probe(struct platform_device *pdev) |
706 | { |
707 | struct mlxreg_core_hotplug_platform_data *pdata; |
708 | struct mlxreg_hotplug_priv_data *priv; |
709 | struct i2c_adapter *deferred_adap; |
710 | int err; |
711 | |
712 | pdata = dev_get_platdata(dev: &pdev->dev); |
713 | if (!pdata) { |
714 | dev_err(&pdev->dev, "Failed to get platform data.\n" ); |
715 | return -EINVAL; |
716 | } |
717 | |
718 | /* Defer probing if the necessary adapter is not configured yet. */ |
719 | deferred_adap = i2c_get_adapter(nr: pdata->deferred_nr); |
720 | if (!deferred_adap) |
721 | return -EPROBE_DEFER; |
722 | i2c_put_adapter(adap: deferred_adap); |
723 | |
724 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
725 | if (!priv) |
726 | return -ENOMEM; |
727 | |
728 | if (pdata->irq) { |
729 | priv->irq = pdata->irq; |
730 | } else { |
731 | priv->irq = platform_get_irq(pdev, 0); |
732 | if (priv->irq < 0) |
733 | return priv->irq; |
734 | } |
735 | |
736 | priv->regmap = pdata->regmap; |
737 | priv->dev = pdev->dev.parent; |
738 | priv->pdev = pdev; |
739 | |
740 | err = devm_request_irq(dev: &pdev->dev, irq: priv->irq, |
741 | handler: mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING |
742 | | IRQF_SHARED, devname: "mlxreg-hotplug" , dev_id: priv); |
743 | if (err) { |
744 | dev_err(&pdev->dev, "Failed to request irq: %d\n" , err); |
745 | return err; |
746 | } |
747 | |
748 | disable_irq(irq: priv->irq); |
749 | spin_lock_init(&priv->lock); |
750 | INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler); |
751 | dev_set_drvdata(dev: &pdev->dev, data: priv); |
752 | |
753 | err = mlxreg_hotplug_attr_init(priv); |
754 | if (err) { |
755 | dev_err(&pdev->dev, "Failed to allocate attributes: %d\n" , |
756 | err); |
757 | return err; |
758 | } |
759 | |
760 | priv->hwmon = devm_hwmon_device_register_with_groups(dev: &pdev->dev, |
761 | name: "mlxreg_hotplug" , drvdata: priv, groups: priv->groups); |
762 | if (IS_ERR(ptr: priv->hwmon)) { |
763 | dev_err(&pdev->dev, "Failed to register hwmon device %ld\n" , |
764 | PTR_ERR(priv->hwmon)); |
765 | return PTR_ERR(ptr: priv->hwmon); |
766 | } |
767 | |
768 | /* Perform initial interrupts setup. */ |
769 | mlxreg_hotplug_set_irq(priv); |
770 | priv->after_probe = true; |
771 | |
772 | return 0; |
773 | } |
774 | |
775 | static void mlxreg_hotplug_remove(struct platform_device *pdev) |
776 | { |
777 | struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(dev: &pdev->dev); |
778 | |
779 | /* Clean interrupts setup. */ |
780 | mlxreg_hotplug_unset_irq(priv); |
781 | devm_free_irq(dev: &pdev->dev, irq: priv->irq, dev_id: priv); |
782 | } |
783 | |
784 | static struct platform_driver mlxreg_hotplug_driver = { |
785 | .driver = { |
786 | .name = "mlxreg-hotplug" , |
787 | }, |
788 | .probe = mlxreg_hotplug_probe, |
789 | .remove_new = mlxreg_hotplug_remove, |
790 | }; |
791 | |
792 | module_platform_driver(mlxreg_hotplug_driver); |
793 | |
794 | MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>" ); |
795 | MODULE_DESCRIPTION("Mellanox regmap hotplug platform driver" ); |
796 | MODULE_LICENSE("Dual BSD/GPL" ); |
797 | MODULE_ALIAS("platform:mlxreg-hotplug" ); |
798 | |