xref: /openbmc/linux/drivers/platform/mellanox/mlxreg-hotplug.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
15272d4e9SVadim Pasternak // SPDX-License-Identifier: GPL-2.0+
21f976f69SVadim Pasternak /*
35272d4e9SVadim Pasternak  * Mellanox hotplug driver
41f976f69SVadim Pasternak  *
55272d4e9SVadim Pasternak  * Copyright (C) 2016-2020 Mellanox Technologies
61f976f69SVadim Pasternak  */
71f976f69SVadim Pasternak 
81f976f69SVadim Pasternak #include <linux/bitops.h>
91f976f69SVadim Pasternak #include <linux/device.h>
101f976f69SVadim Pasternak #include <linux/hwmon.h>
111f976f69SVadim Pasternak #include <linux/hwmon-sysfs.h>
121f976f69SVadim Pasternak #include <linux/i2c.h>
131f976f69SVadim Pasternak #include <linux/interrupt.h>
141f976f69SVadim Pasternak #include <linux/module.h>
151f976f69SVadim Pasternak #include <linux/platform_data/mlxreg.h>
161f976f69SVadim Pasternak #include <linux/platform_device.h>
171f976f69SVadim Pasternak #include <linux/spinlock.h>
1892d020f9SVadim Pasternak #include <linux/string_helpers.h>
19c6acad68SVadim Pasternak #include <linux/regmap.h>
201f976f69SVadim Pasternak #include <linux/workqueue.h>
211f976f69SVadim Pasternak 
22c6acad68SVadim Pasternak /* Offset of event and mask registers from status register. */
231f976f69SVadim Pasternak #define MLXREG_HOTPLUG_EVENT_OFF	1
241f976f69SVadim Pasternak #define MLXREG_HOTPLUG_MASK_OFF		2
251f976f69SVadim Pasternak #define MLXREG_HOTPLUG_AGGR_MASK_OFF	1
261f976f69SVadim Pasternak 
2766342d1cSVadim Pasternak /* ASIC good health mask. */
2866342d1cSVadim Pasternak #define MLXREG_HOTPLUG_GOOD_HEALTH_MASK	0x02
291f976f69SVadim Pasternak 
30bb1023b6SVadim Pasternak #define MLXREG_HOTPLUG_ATTRS_MAX	128
314b5e32dfSVadim Pasternak #define MLXREG_HOTPLUG_NOT_ASSERT	3
321f976f69SVadim Pasternak 
331f976f69SVadim Pasternak /**
341f976f69SVadim Pasternak  * struct mlxreg_hotplug_priv_data - platform private data:
35c6acad68SVadim Pasternak  * @irq: platform device interrupt number;
36321089a4SVadim Pasternak  * @dev: basic device;
371f976f69SVadim Pasternak  * @pdev: platform device;
381f976f69SVadim Pasternak  * @plat: platform data;
39321089a4SVadim Pasternak  * @regmap: register map handle;
40321089a4SVadim Pasternak  * @dwork_irq: delayed work template;
41c6acad68SVadim Pasternak  * @lock: spin lock;
421f976f69SVadim Pasternak  * @hwmon: hwmon device;
431f976f69SVadim Pasternak  * @mlxreg_hotplug_attr: sysfs attributes array;
441f976f69SVadim Pasternak  * @mlxreg_hotplug_dev_attr: sysfs sensor device attribute array;
451f976f69SVadim Pasternak  * @group: sysfs attribute group;
461f976f69SVadim Pasternak  * @groups: list of sysfs attribute group for hwmon registration;
47c6acad68SVadim Pasternak  * @cell: location of top aggregation interrupt register;
48c6acad68SVadim Pasternak  * @mask: top aggregation interrupt common mask;
491f976f69SVadim Pasternak  * @aggr_cache: last value of aggregation register status;
50321089a4SVadim Pasternak  * @after_probe: flag indication probing completion;
514b5e32dfSVadim Pasternak  * @not_asserted: number of entries in workqueue with no signal assertion;
521f976f69SVadim Pasternak  */
531f976f69SVadim Pasternak struct mlxreg_hotplug_priv_data {
541f976f69SVadim Pasternak 	int irq;
55c6acad68SVadim Pasternak 	struct device *dev;
561f976f69SVadim Pasternak 	struct platform_device *pdev;
571f976f69SVadim Pasternak 	struct mlxreg_hotplug_platform_data *plat;
58c6acad68SVadim Pasternak 	struct regmap *regmap;
59c6acad68SVadim Pasternak 	struct delayed_work dwork_irq;
60c6acad68SVadim Pasternak 	spinlock_t lock; /* sync with interrupt */
611f976f69SVadim Pasternak 	struct device *hwmon;
62c6acad68SVadim Pasternak 	struct attribute *mlxreg_hotplug_attr[MLXREG_HOTPLUG_ATTRS_MAX + 1];
631f976f69SVadim Pasternak 	struct sensor_device_attribute_2
64c6acad68SVadim Pasternak 			mlxreg_hotplug_dev_attr[MLXREG_HOTPLUG_ATTRS_MAX];
651f976f69SVadim Pasternak 	struct attribute_group group;
661f976f69SVadim Pasternak 	const struct attribute_group *groups[2];
67c6acad68SVadim Pasternak 	u32 cell;
68c6acad68SVadim Pasternak 	u32 mask;
69c6acad68SVadim Pasternak 	u32 aggr_cache;
70c6acad68SVadim Pasternak 	bool after_probe;
714b5e32dfSVadim Pasternak 	u8 not_asserted;
721f976f69SVadim Pasternak };
731f976f69SVadim Pasternak 
7492d020f9SVadim Pasternak /* Environment variables array for udev. */
7592d020f9SVadim Pasternak static char *mlxreg_hotplug_udev_envp[] = { NULL, NULL };
7692d020f9SVadim Pasternak 
7792d020f9SVadim Pasternak static int
mlxreg_hotplug_udev_event_send(struct kobject * kobj,struct mlxreg_core_data * data,bool action)7892d020f9SVadim Pasternak mlxreg_hotplug_udev_event_send(struct kobject *kobj,
7992d020f9SVadim Pasternak 			       struct mlxreg_core_data *data, bool action)
8092d020f9SVadim Pasternak {
8192d020f9SVadim Pasternak 	char event_str[MLXREG_CORE_LABEL_MAX_SIZE + 2];
8292d020f9SVadim Pasternak 	char label[MLXREG_CORE_LABEL_MAX_SIZE] = { 0 };
8392d020f9SVadim Pasternak 
8492d020f9SVadim Pasternak 	mlxreg_hotplug_udev_envp[0] = event_str;
8592d020f9SVadim Pasternak 	string_upper(label, data->label);
8692d020f9SVadim Pasternak 	snprintf(event_str, MLXREG_CORE_LABEL_MAX_SIZE, "%s=%d", label, !!action);
8792d020f9SVadim Pasternak 
8892d020f9SVadim Pasternak 	return kobject_uevent_env(kobj, KOBJ_CHANGE, mlxreg_hotplug_udev_envp);
8992d020f9SVadim Pasternak }
9092d020f9SVadim Pasternak 
91bb1023b6SVadim Pasternak static void
mlxreg_hotplug_pdata_export(void * pdata,void * regmap)92bb1023b6SVadim Pasternak mlxreg_hotplug_pdata_export(void *pdata, void *regmap)
93752849e6SVadim Pasternak {
94bb1023b6SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *dev_pdata = pdata;
95bb1023b6SVadim Pasternak 
96bb1023b6SVadim Pasternak 	/* Export regmap to underlying device. */
97bb1023b6SVadim Pasternak 	dev_pdata->regmap = regmap;
98bb1023b6SVadim Pasternak }
99bb1023b6SVadim Pasternak 
mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data * priv,struct mlxreg_core_data * data,enum mlxreg_hotplug_kind kind)100bb1023b6SVadim Pasternak static int mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data *priv,
101bb1023b6SVadim Pasternak 					struct mlxreg_core_data *data,
102bb1023b6SVadim Pasternak 					enum mlxreg_hotplug_kind kind)
103bb1023b6SVadim Pasternak {
104bb1023b6SVadim Pasternak 	struct i2c_board_info *brdinfo = data->hpdev.brdinfo;
105ef0f6226SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
10684c0eb21SWolfram Sang 	struct i2c_client *client;
107ef0f6226SVadim Pasternak 
1089272d2d1SVadim Pasternak 	/* Notify user by sending hwmon uevent. */
10992d020f9SVadim Pasternak 	mlxreg_hotplug_udev_event_send(&priv->hwmon->kobj, data, true);
1109272d2d1SVadim Pasternak 
1117805fa8dSVadim Pasternak 	/*
1127805fa8dSVadim Pasternak 	 * Return if adapter number is negative. It could be in case hotplug
1137805fa8dSVadim Pasternak 	 * event is not associated with hotplug device.
1147805fa8dSVadim Pasternak 	 */
115*01a4cce8SVadim Pasternak 	if (data->hpdev.nr < 0 && data->hpdev.action != MLXREG_HOTPLUG_DEVICE_NO_ACTION)
1167805fa8dSVadim Pasternak 		return 0;
1177805fa8dSVadim Pasternak 
118ef0f6226SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
119bb1023b6SVadim Pasternak 	switch (data->hpdev.action) {
120bb1023b6SVadim Pasternak 	case MLXREG_HOTPLUG_DEVICE_DEFAULT_ACTION:
121ef0f6226SVadim Pasternak 		data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr +
122ef0f6226SVadim Pasternak 						      pdata->shift_nr);
123c6acad68SVadim Pasternak 		if (!data->hpdev.adapter) {
124f709e1bfSVadim Pasternak 			dev_err(priv->dev, "Failed to get adapter for bus %d\n",
125ef0f6226SVadim Pasternak 				data->hpdev.nr + pdata->shift_nr);
126752849e6SVadim Pasternak 			return -EFAULT;
127752849e6SVadim Pasternak 		}
128752849e6SVadim Pasternak 
129bb1023b6SVadim Pasternak 		/* Export platform data to underlying device. */
130bb1023b6SVadim Pasternak 		if (brdinfo->platform_data)
131bb1023b6SVadim Pasternak 			mlxreg_hotplug_pdata_export(brdinfo->platform_data, pdata->regmap);
132bb1023b6SVadim Pasternak 
13384c0eb21SWolfram Sang 		client = i2c_new_client_device(data->hpdev.adapter,
134bb1023b6SVadim Pasternak 					       brdinfo);
13584c0eb21SWolfram Sang 		if (IS_ERR(client)) {
136f709e1bfSVadim Pasternak 			dev_err(priv->dev, "Failed to create client %s at bus %d at addr 0x%02x\n",
137bb1023b6SVadim Pasternak 				brdinfo->type, data->hpdev.nr +
138bb1023b6SVadim Pasternak 				pdata->shift_nr, brdinfo->addr);
139c6acad68SVadim Pasternak 
140c6acad68SVadim Pasternak 			i2c_put_adapter(data->hpdev.adapter);
141c6acad68SVadim Pasternak 			data->hpdev.adapter = NULL;
14284c0eb21SWolfram Sang 			return PTR_ERR(client);
143752849e6SVadim Pasternak 		}
144752849e6SVadim Pasternak 
14584c0eb21SWolfram Sang 		data->hpdev.client = client;
146bb1023b6SVadim Pasternak 		break;
147bb1023b6SVadim Pasternak 	case MLXREG_HOTPLUG_DEVICE_PLATFORM_ACTION:
148bb1023b6SVadim Pasternak 		/* Export platform data to underlying device. */
149bb1023b6SVadim Pasternak 		if (data->hpdev.brdinfo && data->hpdev.brdinfo->platform_data)
150bb1023b6SVadim Pasternak 			mlxreg_hotplug_pdata_export(data->hpdev.brdinfo->platform_data,
151bb1023b6SVadim Pasternak 						    pdata->regmap);
152bb1023b6SVadim Pasternak 		/* Pass parent hotplug device handle to underlying device. */
153bb1023b6SVadim Pasternak 		data->notifier = data->hpdev.notifier;
154bb1023b6SVadim Pasternak 		data->hpdev.pdev = platform_device_register_resndata(&priv->pdev->dev,
155bb1023b6SVadim Pasternak 								     brdinfo->type,
156bb1023b6SVadim Pasternak 								     data->hpdev.nr,
157bb1023b6SVadim Pasternak 								     NULL, 0, data,
158bb1023b6SVadim Pasternak 								     sizeof(*data));
159bb1023b6SVadim Pasternak 		if (IS_ERR(data->hpdev.pdev))
160bb1023b6SVadim Pasternak 			return PTR_ERR(data->hpdev.pdev);
161bb1023b6SVadim Pasternak 
162bb1023b6SVadim Pasternak 		break;
163bb1023b6SVadim Pasternak 	default:
164bb1023b6SVadim Pasternak 		break;
165bb1023b6SVadim Pasternak 	}
166bb1023b6SVadim Pasternak 
167bb1023b6SVadim Pasternak 	if (data->hpdev.notifier && data->hpdev.notifier->user_handler)
168bb1023b6SVadim Pasternak 		return data->hpdev.notifier->user_handler(data->hpdev.notifier->handle, kind, 1);
16984c0eb21SWolfram Sang 
170752849e6SVadim Pasternak 	return 0;
171752849e6SVadim Pasternak }
172752849e6SVadim Pasternak 
1739272d2d1SVadim Pasternak static void
mlxreg_hotplug_device_destroy(struct mlxreg_hotplug_priv_data * priv,struct mlxreg_core_data * data,enum mlxreg_hotplug_kind kind)1749272d2d1SVadim Pasternak mlxreg_hotplug_device_destroy(struct mlxreg_hotplug_priv_data *priv,
175bb1023b6SVadim Pasternak 			      struct mlxreg_core_data *data,
176bb1023b6SVadim Pasternak 			      enum mlxreg_hotplug_kind kind)
177752849e6SVadim Pasternak {
1789272d2d1SVadim Pasternak 	/* Notify user by sending hwmon uevent. */
17992d020f9SVadim Pasternak 	mlxreg_hotplug_udev_event_send(&priv->hwmon->kobj, data, false);
180bb1023b6SVadim Pasternak 	if (data->hpdev.notifier && data->hpdev.notifier->user_handler)
181bb1023b6SVadim Pasternak 		data->hpdev.notifier->user_handler(data->hpdev.notifier->handle, kind, 0);
1829272d2d1SVadim Pasternak 
183bb1023b6SVadim Pasternak 	switch (data->hpdev.action) {
184bb1023b6SVadim Pasternak 	case MLXREG_HOTPLUG_DEVICE_DEFAULT_ACTION:
185c6acad68SVadim Pasternak 		if (data->hpdev.client) {
186c6acad68SVadim Pasternak 			i2c_unregister_device(data->hpdev.client);
187c6acad68SVadim Pasternak 			data->hpdev.client = NULL;
188752849e6SVadim Pasternak 		}
189752849e6SVadim Pasternak 
190c6acad68SVadim Pasternak 		if (data->hpdev.adapter) {
191c6acad68SVadim Pasternak 			i2c_put_adapter(data->hpdev.adapter);
192c6acad68SVadim Pasternak 			data->hpdev.adapter = NULL;
193752849e6SVadim Pasternak 		}
194bb1023b6SVadim Pasternak 		break;
195bb1023b6SVadim Pasternak 	case MLXREG_HOTPLUG_DEVICE_PLATFORM_ACTION:
196bb1023b6SVadim Pasternak 		if (data->hpdev.pdev)
197bb1023b6SVadim Pasternak 			platform_device_unregister(data->hpdev.pdev);
198bb1023b6SVadim Pasternak 		break;
199bb1023b6SVadim Pasternak 	default:
200bb1023b6SVadim Pasternak 		break;
201bb1023b6SVadim Pasternak 	}
202752849e6SVadim Pasternak }
203752849e6SVadim Pasternak 
mlxreg_hotplug_attr_show(struct device * dev,struct device_attribute * attr,char * buf)2041f976f69SVadim Pasternak static ssize_t mlxreg_hotplug_attr_show(struct device *dev,
2051f976f69SVadim Pasternak 					struct device_attribute *attr,
2061f976f69SVadim Pasternak 					char *buf)
2071f976f69SVadim Pasternak {
208c6acad68SVadim Pasternak 	struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(dev);
209c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
2101f976f69SVadim Pasternak 	int index = to_sensor_dev_attr_2(attr)->index;
2111f976f69SVadim Pasternak 	int nr = to_sensor_dev_attr_2(attr)->nr;
212c6acad68SVadim Pasternak 	struct mlxreg_core_item *item;
213c6acad68SVadim Pasternak 	struct mlxreg_core_data *data;
214c6acad68SVadim Pasternak 	u32 regval;
215c6acad68SVadim Pasternak 	int ret;
2161f976f69SVadim Pasternak 
217c6acad68SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
218c6acad68SVadim Pasternak 	item = pdata->items + nr;
219c6acad68SVadim Pasternak 	data = item->data + index;
2201f976f69SVadim Pasternak 
221c6acad68SVadim Pasternak 	ret = regmap_read(priv->regmap, data->reg, &regval);
222c6acad68SVadim Pasternak 	if (ret)
223c6acad68SVadim Pasternak 		return ret;
2241f976f69SVadim Pasternak 
225c6acad68SVadim Pasternak 	if (item->health) {
226c6acad68SVadim Pasternak 		regval &= data->mask;
227c6acad68SVadim Pasternak 	} else {
228c6acad68SVadim Pasternak 		/* Bit = 0 : functional if item->inversed is true. */
229c6acad68SVadim Pasternak 		if (item->inversed)
230c6acad68SVadim Pasternak 			regval = !(regval & data->mask);
231c6acad68SVadim Pasternak 		else
232c6acad68SVadim Pasternak 			regval = !!(regval & data->mask);
2331f976f69SVadim Pasternak 	}
2341f976f69SVadim Pasternak 
235c6acad68SVadim Pasternak 	return sprintf(buf, "%u\n", regval);
2361f976f69SVadim Pasternak }
2371f976f69SVadim Pasternak 
2381f976f69SVadim Pasternak #define PRIV_ATTR(i) priv->mlxreg_hotplug_attr[i]
2391f976f69SVadim Pasternak #define PRIV_DEV_ATTR(i) priv->mlxreg_hotplug_dev_attr[i]
240c6acad68SVadim Pasternak 
mlxreg_hotplug_item_label_index_get(u32 mask,u32 bit)24126e118eaSVadim Pasternak static int mlxreg_hotplug_item_label_index_get(u32 mask, u32 bit)
24226e118eaSVadim Pasternak {
24326e118eaSVadim Pasternak 	int i, j;
24426e118eaSVadim Pasternak 
24526e118eaSVadim Pasternak 	for (i = 0, j = -1; i <= bit; i++) {
24626e118eaSVadim Pasternak 		if (mask & BIT(i))
24726e118eaSVadim Pasternak 			j++;
24826e118eaSVadim Pasternak 	}
24926e118eaSVadim Pasternak 	return j;
25026e118eaSVadim Pasternak }
25126e118eaSVadim Pasternak 
mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data * priv)2521f976f69SVadim Pasternak static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv)
2531f976f69SVadim Pasternak {
254c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
255c6acad68SVadim Pasternak 	struct mlxreg_core_item *item;
256c6acad68SVadim Pasternak 	struct mlxreg_core_data *data;
2570a43f7beSVadim Pasternak 	unsigned long mask;
2580a43f7beSVadim Pasternak 	u32 regval;
25926e118eaSVadim Pasternak 	int num_attrs = 0, id = 0, i, j, k, count, ret;
260c6acad68SVadim Pasternak 
261c6acad68SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
262c6acad68SVadim Pasternak 	item = pdata->items;
263c6acad68SVadim Pasternak 
264c6acad68SVadim Pasternak 	/* Go over all kinds of items - psu, pwr, fan. */
265c6acad68SVadim Pasternak 	for (i = 0; i < pdata->counter; i++, item++) {
2660a43f7beSVadim Pasternak 		if (item->capability) {
2670a43f7beSVadim Pasternak 			/*
2680a43f7beSVadim Pasternak 			 * Read group capability register to get actual number
2690a43f7beSVadim Pasternak 			 * of interrupt capable components and set group mask
2700a43f7beSVadim Pasternak 			 * accordingly.
2710a43f7beSVadim Pasternak 			 */
2720a43f7beSVadim Pasternak 			ret = regmap_read(priv->regmap, item->capability,
2730a43f7beSVadim Pasternak 					  &regval);
2740a43f7beSVadim Pasternak 			if (ret)
2750a43f7beSVadim Pasternak 				return ret;
2760a43f7beSVadim Pasternak 
2770a43f7beSVadim Pasternak 			item->mask = GENMASK((regval & item->mask) - 1, 0);
2780a43f7beSVadim Pasternak 		}
2790a43f7beSVadim Pasternak 
280c6acad68SVadim Pasternak 		data = item->data;
2810a43f7beSVadim Pasternak 
2820a43f7beSVadim Pasternak 		/* Go over all unmasked units within item. */
2830a43f7beSVadim Pasternak 		mask = item->mask;
2840a43f7beSVadim Pasternak 		k = 0;
28526e118eaSVadim Pasternak 		count = item->ind ? item->ind : item->count;
28626e118eaSVadim Pasternak 		for_each_set_bit(j, &mask, count) {
2870a43f7beSVadim Pasternak 			if (data->capability) {
2880a43f7beSVadim Pasternak 				/*
2890a43f7beSVadim Pasternak 				 * Read capability register and skip non
2900a43f7beSVadim Pasternak 				 * relevant attributes.
2910a43f7beSVadim Pasternak 				 */
2920a43f7beSVadim Pasternak 				ret = regmap_read(priv->regmap,
2930a43f7beSVadim Pasternak 						  data->capability, &regval);
2940a43f7beSVadim Pasternak 				if (ret)
2950a43f7beSVadim Pasternak 					return ret;
29626e118eaSVadim Pasternak 
2970a43f7beSVadim Pasternak 				if (!(regval & data->bit)) {
2980a43f7beSVadim Pasternak 					data++;
2990a43f7beSVadim Pasternak 					continue;
3000a43f7beSVadim Pasternak 				}
3010a43f7beSVadim Pasternak 			}
30226e118eaSVadim Pasternak 
303c6acad68SVadim Pasternak 			PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr;
304c6acad68SVadim Pasternak 			PRIV_ATTR(id)->name = devm_kasprintf(&priv->pdev->dev,
305c6acad68SVadim Pasternak 							     GFP_KERNEL,
306c6acad68SVadim Pasternak 							     data->label);
307c6acad68SVadim Pasternak 			if (!PRIV_ATTR(id)->name) {
308c6acad68SVadim Pasternak 				dev_err(priv->dev, "Memory allocation failed for attr %d.\n",
309c6acad68SVadim Pasternak 					id);
310c6acad68SVadim Pasternak 				return -ENOMEM;
311c6acad68SVadim Pasternak 			}
312c6acad68SVadim Pasternak 
313c6acad68SVadim Pasternak 			PRIV_DEV_ATTR(id).dev_attr.attr.name =
314c6acad68SVadim Pasternak 							PRIV_ATTR(id)->name;
315c6acad68SVadim Pasternak 			PRIV_DEV_ATTR(id).dev_attr.attr.mode = 0444;
316c6acad68SVadim Pasternak 			PRIV_DEV_ATTR(id).dev_attr.show =
317c6acad68SVadim Pasternak 						mlxreg_hotplug_attr_show;
318c6acad68SVadim Pasternak 			PRIV_DEV_ATTR(id).nr = i;
3190a43f7beSVadim Pasternak 			PRIV_DEV_ATTR(id).index = k;
320c6acad68SVadim Pasternak 			sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr);
3210a43f7beSVadim Pasternak 			data++;
3220a43f7beSVadim Pasternak 			id++;
3230a43f7beSVadim Pasternak 			k++;
324c6acad68SVadim Pasternak 		}
3250a43f7beSVadim Pasternak 		num_attrs += k;
326c6acad68SVadim Pasternak 	}
3271f976f69SVadim Pasternak 
328a86854d0SKees Cook 	priv->group.attrs = devm_kcalloc(&priv->pdev->dev,
329a86854d0SKees Cook 					 num_attrs,
3301f976f69SVadim Pasternak 					 sizeof(struct attribute *),
3311f976f69SVadim Pasternak 					 GFP_KERNEL);
3321f976f69SVadim Pasternak 	if (!priv->group.attrs)
3331f976f69SVadim Pasternak 		return -ENOMEM;
3341f976f69SVadim Pasternak 
3351f976f69SVadim Pasternak 	priv->group.attrs = priv->mlxreg_hotplug_attr;
3361f976f69SVadim Pasternak 	priv->groups[0] = &priv->group;
3371f976f69SVadim Pasternak 	priv->groups[1] = NULL;
3381f976f69SVadim Pasternak 
3391f976f69SVadim Pasternak 	return 0;
3401f976f69SVadim Pasternak }
3411f976f69SVadim Pasternak 
342c6acad68SVadim Pasternak static void
mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data * priv,struct mlxreg_core_item * item)343c6acad68SVadim Pasternak mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv,
344c6acad68SVadim Pasternak 			   struct mlxreg_core_item *item)
3451f976f69SVadim Pasternak {
346c6acad68SVadim Pasternak 	struct mlxreg_core_data *data;
347e4c275f7SVadim Pasternak 	unsigned long asserted;
348e4c275f7SVadim Pasternak 	u32 regval, bit;
349c6acad68SVadim Pasternak 	int ret;
3501f976f69SVadim Pasternak 
3511f976f69SVadim Pasternak 	/*
3521f976f69SVadim Pasternak 	 * Validate if item related to received signal type is valid.
3531f976f69SVadim Pasternak 	 * It should never happen, excepted the situation when some
3541f976f69SVadim Pasternak 	 * piece of hardware is broken. In such situation just produce
3551f976f69SVadim Pasternak 	 * error message and return. Caller must continue to handle the
3561f976f69SVadim Pasternak 	 * signals from other devices if any.
3571f976f69SVadim Pasternak 	 */
3581f976f69SVadim Pasternak 	if (unlikely(!item)) {
359c6acad68SVadim Pasternak 		dev_err(priv->dev, "False signal: at offset:mask 0x%02x:0x%02x.\n",
360c6acad68SVadim Pasternak 			item->reg, item->mask);
361c6acad68SVadim Pasternak 
3621f976f69SVadim Pasternak 		return;
3631f976f69SVadim Pasternak 	}
3641f976f69SVadim Pasternak 
365c6acad68SVadim Pasternak 	/* Mask event. */
366c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF,
367c6acad68SVadim Pasternak 			   0);
368c6acad68SVadim Pasternak 	if (ret)
369c6acad68SVadim Pasternak 		goto out;
370c6acad68SVadim Pasternak 
371c6acad68SVadim Pasternak 	/* Read status. */
372c6acad68SVadim Pasternak 	ret = regmap_read(priv->regmap, item->reg, &regval);
373c6acad68SVadim Pasternak 	if (ret)
374c6acad68SVadim Pasternak 		goto out;
375c6acad68SVadim Pasternak 
376c6acad68SVadim Pasternak 	/* Set asserted bits and save last status. */
377c6acad68SVadim Pasternak 	regval &= item->mask;
378c6acad68SVadim Pasternak 	asserted = item->cache ^ regval;
379c6acad68SVadim Pasternak 	item->cache = regval;
380e4c275f7SVadim Pasternak 	for_each_set_bit(bit, &asserted, 8) {
38126e118eaSVadim Pasternak 		int pos;
38226e118eaSVadim Pasternak 
38326e118eaSVadim Pasternak 		pos = mlxreg_hotplug_item_label_index_get(item->mask, bit);
38426e118eaSVadim Pasternak 		if (pos < 0)
38526e118eaSVadim Pasternak 			goto out;
38626e118eaSVadim Pasternak 
38726e118eaSVadim Pasternak 		data = item->data + pos;
388c6acad68SVadim Pasternak 		if (regval & BIT(bit)) {
389c6acad68SVadim Pasternak 			if (item->inversed)
390bb1023b6SVadim Pasternak 				mlxreg_hotplug_device_destroy(priv, data, item->kind);
3911f976f69SVadim Pasternak 			else
392bb1023b6SVadim Pasternak 				mlxreg_hotplug_device_create(priv, data, item->kind);
3931f976f69SVadim Pasternak 		} else {
394c6acad68SVadim Pasternak 			if (item->inversed)
395bb1023b6SVadim Pasternak 				mlxreg_hotplug_device_create(priv, data, item->kind);
3961f976f69SVadim Pasternak 			else
397bb1023b6SVadim Pasternak 				mlxreg_hotplug_device_destroy(priv, data, item->kind);
3981f976f69SVadim Pasternak 		}
3991f976f69SVadim Pasternak 	}
4001f976f69SVadim Pasternak 
4011f976f69SVadim Pasternak 	/* Acknowledge event. */
402c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_EVENT_OFF,
403c6acad68SVadim Pasternak 			   0);
404c6acad68SVadim Pasternak 	if (ret)
405c6acad68SVadim Pasternak 		goto out;
406c6acad68SVadim Pasternak 
4071f976f69SVadim Pasternak 	/* Unmask event. */
408c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF,
409c6acad68SVadim Pasternak 			   item->mask);
410c6acad68SVadim Pasternak 
411c6acad68SVadim Pasternak  out:
412c6acad68SVadim Pasternak 	if (ret)
413c6acad68SVadim Pasternak 		dev_err(priv->dev, "Failed to complete workqueue.\n");
414c6acad68SVadim Pasternak }
415c6acad68SVadim Pasternak 
416c6acad68SVadim Pasternak static void
mlxreg_hotplug_health_work_helper(struct mlxreg_hotplug_priv_data * priv,struct mlxreg_core_item * item)417c6acad68SVadim Pasternak mlxreg_hotplug_health_work_helper(struct mlxreg_hotplug_priv_data *priv,
418c6acad68SVadim Pasternak 				  struct mlxreg_core_item *item)
419c6acad68SVadim Pasternak {
420c6acad68SVadim Pasternak 	struct mlxreg_core_data *data = item->data;
421c6acad68SVadim Pasternak 	u32 regval;
422b81e830cSGeert Uytterhoeven 	int i, ret = 0;
423c6acad68SVadim Pasternak 
424c6acad68SVadim Pasternak 	for (i = 0; i < item->count; i++, data++) {
425c6acad68SVadim Pasternak 		/* Mask event. */
426c6acad68SVadim Pasternak 		ret = regmap_write(priv->regmap, data->reg +
427c6acad68SVadim Pasternak 				   MLXREG_HOTPLUG_MASK_OFF, 0);
428c6acad68SVadim Pasternak 		if (ret)
429c6acad68SVadim Pasternak 			goto out;
430c6acad68SVadim Pasternak 
431c6acad68SVadim Pasternak 		/* Read status. */
432c6acad68SVadim Pasternak 		ret = regmap_read(priv->regmap, data->reg, &regval);
433c6acad68SVadim Pasternak 		if (ret)
434c6acad68SVadim Pasternak 			goto out;
435c6acad68SVadim Pasternak 
436c6acad68SVadim Pasternak 		regval &= data->mask;
43766342d1cSVadim Pasternak 
43866342d1cSVadim Pasternak 		if (item->cache == regval)
43966342d1cSVadim Pasternak 			goto ack_event;
44066342d1cSVadim Pasternak 
44166342d1cSVadim Pasternak 		/*
44266342d1cSVadim Pasternak 		 * ASIC health indication is provided through two bits. Bits
44366342d1cSVadim Pasternak 		 * value 0x2 indicates that ASIC reached the good health, value
44466342d1cSVadim Pasternak 		 * 0x0 indicates ASIC the bad health or dormant state and value
44566342d1cSVadim Pasternak 		 * 0x3 indicates the booting state. During ASIC reset it should
44666342d1cSVadim Pasternak 		 * pass the following states: dormant -> booting -> good.
44766342d1cSVadim Pasternak 		 */
44866342d1cSVadim Pasternak 		if (regval == MLXREG_HOTPLUG_GOOD_HEALTH_MASK) {
44966342d1cSVadim Pasternak 			if (!data->attached) {
45066342d1cSVadim Pasternak 				/*
45166342d1cSVadim Pasternak 				 * ASIC is in steady state. Connect associated
45266342d1cSVadim Pasternak 				 * device, if configured.
45366342d1cSVadim Pasternak 				 */
454bb1023b6SVadim Pasternak 				mlxreg_hotplug_device_create(priv, data, item->kind);
455c6acad68SVadim Pasternak 				data->attached = true;
456c6acad68SVadim Pasternak 			}
457c6acad68SVadim Pasternak 		} else {
458c6acad68SVadim Pasternak 			if (data->attached) {
45966342d1cSVadim Pasternak 				/*
46066342d1cSVadim Pasternak 				 * ASIC health is failed after ASIC has been
46166342d1cSVadim Pasternak 				 * in steady state. Disconnect associated
46266342d1cSVadim Pasternak 				 * device, if it has been connected.
46366342d1cSVadim Pasternak 				 */
464bb1023b6SVadim Pasternak 				mlxreg_hotplug_device_destroy(priv, data, item->kind);
465c6acad68SVadim Pasternak 				data->attached = false;
466c6acad68SVadim Pasternak 				data->health_cntr = 0;
467c6acad68SVadim Pasternak 			}
468c6acad68SVadim Pasternak 		}
46966342d1cSVadim Pasternak 		item->cache = regval;
47066342d1cSVadim Pasternak ack_event:
471c6acad68SVadim Pasternak 		/* Acknowledge event. */
472c6acad68SVadim Pasternak 		ret = regmap_write(priv->regmap, data->reg +
473c6acad68SVadim Pasternak 				   MLXREG_HOTPLUG_EVENT_OFF, 0);
474c6acad68SVadim Pasternak 		if (ret)
475c6acad68SVadim Pasternak 			goto out;
476c6acad68SVadim Pasternak 
477c6acad68SVadim Pasternak 		/* Unmask event. */
478c6acad68SVadim Pasternak 		ret = regmap_write(priv->regmap, data->reg +
479c6acad68SVadim Pasternak 				   MLXREG_HOTPLUG_MASK_OFF, data->mask);
480c6acad68SVadim Pasternak 		if (ret)
481c6acad68SVadim Pasternak 			goto out;
482c6acad68SVadim Pasternak 	}
483c6acad68SVadim Pasternak 
484c6acad68SVadim Pasternak  out:
485c6acad68SVadim Pasternak 	if (ret)
486c6acad68SVadim Pasternak 		dev_err(priv->dev, "Failed to complete workqueue.\n");
4871f976f69SVadim Pasternak }
4881f976f69SVadim Pasternak 
4891f976f69SVadim Pasternak /*
490c6acad68SVadim Pasternak  * mlxreg_hotplug_work_handler - performs traversing of device interrupt
4911f976f69SVadim Pasternak  * registers according to the below hierarchy schema:
4921f976f69SVadim Pasternak  *
4931f976f69SVadim Pasternak  *				Aggregation registers (status/mask)
4941f976f69SVadim Pasternak  * PSU registers:		*---*
4951f976f69SVadim Pasternak  * *-----------------*		|   |
4961f976f69SVadim Pasternak  * |status/event/mask|----->    | * |
4971f976f69SVadim Pasternak  * *-----------------*		|   |
4981f976f69SVadim Pasternak  * Power registers:		|   |
4991f976f69SVadim Pasternak  * *-----------------*		|   |
500c6acad68SVadim Pasternak  * |status/event/mask|----->    | * |
5011f976f69SVadim Pasternak  * *-----------------*		|   |
502c6acad68SVadim Pasternak  * FAN registers:		|   |--> CPU
503c6acad68SVadim Pasternak  * *-----------------*		|   |
504c6acad68SVadim Pasternak  * |status/event/mask|----->    | * |
505c6acad68SVadim Pasternak  * *-----------------*		|   |
506c6acad68SVadim Pasternak  * ASIC registers:		|   |
5071f976f69SVadim Pasternak  * *-----------------*		|   |
5081f976f69SVadim Pasternak  * |status/event/mask|----->    | * |
5091f976f69SVadim Pasternak  * *-----------------*		|   |
5101f976f69SVadim Pasternak  *				*---*
511c6acad68SVadim Pasternak  *
5121f976f69SVadim Pasternak  * In case some system changed are detected: FAN in/out, PSU in/out, power
513c6acad68SVadim Pasternak  * cable attached/detached, ASIC health good/bad, relevant device is created
514c6acad68SVadim Pasternak  * or destroyed.
5151f976f69SVadim Pasternak  */
mlxreg_hotplug_work_handler(struct work_struct * work)5161f976f69SVadim Pasternak static void mlxreg_hotplug_work_handler(struct work_struct *work)
5171f976f69SVadim Pasternak {
518c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
519c6acad68SVadim Pasternak 	struct mlxreg_hotplug_priv_data *priv;
520c6acad68SVadim Pasternak 	struct mlxreg_core_item *item;
521c6acad68SVadim Pasternak 	u32 regval, aggr_asserted;
5221f976f69SVadim Pasternak 	unsigned long flags;
523c6acad68SVadim Pasternak 	int i, ret;
524c6acad68SVadim Pasternak 
525c6acad68SVadim Pasternak 	priv = container_of(work, struct mlxreg_hotplug_priv_data,
526c6acad68SVadim Pasternak 			    dwork_irq.work);
527c6acad68SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
528c6acad68SVadim Pasternak 	item = pdata->items;
5291f976f69SVadim Pasternak 
5301f976f69SVadim Pasternak 	/* Mask aggregation event. */
531c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, pdata->cell +
532c6acad68SVadim Pasternak 			   MLXREG_HOTPLUG_AGGR_MASK_OFF, 0);
533c6acad68SVadim Pasternak 	if (ret < 0)
534c6acad68SVadim Pasternak 		goto out;
535c6acad68SVadim Pasternak 
5361f976f69SVadim Pasternak 	/* Read aggregation status. */
537c6acad68SVadim Pasternak 	ret = regmap_read(priv->regmap, pdata->cell, &regval);
538c6acad68SVadim Pasternak 	if (ret)
539c6acad68SVadim Pasternak 		goto out;
5401f976f69SVadim Pasternak 
541c6acad68SVadim Pasternak 	regval &= pdata->mask;
542c6acad68SVadim Pasternak 	aggr_asserted = priv->aggr_cache ^ regval;
543c6acad68SVadim Pasternak 	priv->aggr_cache = regval;
5441f976f69SVadim Pasternak 
5454b5e32dfSVadim Pasternak 	/*
5464b5e32dfSVadim Pasternak 	 * Handler is invoked, but no assertion is detected at top aggregation
5474b5e32dfSVadim Pasternak 	 * status level. Set aggr_asserted to mask value to allow handler extra
5484b5e32dfSVadim Pasternak 	 * run over all relevant signals to recover any missed signal.
5494b5e32dfSVadim Pasternak 	 */
5504b5e32dfSVadim Pasternak 	if (priv->not_asserted == MLXREG_HOTPLUG_NOT_ASSERT) {
5514b5e32dfSVadim Pasternak 		priv->not_asserted = 0;
5524b5e32dfSVadim Pasternak 		aggr_asserted = pdata->mask;
5534b5e32dfSVadim Pasternak 	}
5544b5e32dfSVadim Pasternak 	if (!aggr_asserted)
5554b5e32dfSVadim Pasternak 		goto unmask_event;
5564b5e32dfSVadim Pasternak 
557c6acad68SVadim Pasternak 	/* Handle topology and health configuration changes. */
558c6acad68SVadim Pasternak 	for (i = 0; i < pdata->counter; i++, item++) {
559c6acad68SVadim Pasternak 		if (aggr_asserted & item->aggr_mask) {
560c6acad68SVadim Pasternak 			if (item->health)
561c6acad68SVadim Pasternak 				mlxreg_hotplug_health_work_helper(priv, item);
562c6acad68SVadim Pasternak 			else
563c6acad68SVadim Pasternak 				mlxreg_hotplug_work_helper(priv, item);
564c6acad68SVadim Pasternak 		}
565c6acad68SVadim Pasternak 	}
5661f976f69SVadim Pasternak 
5671f976f69SVadim Pasternak 	spin_lock_irqsave(&priv->lock, flags);
5681f976f69SVadim Pasternak 
5691f976f69SVadim Pasternak 	/*
5701f976f69SVadim Pasternak 	 * It is possible, that some signals have been inserted, while
5714b5e32dfSVadim Pasternak 	 * interrupt has been masked by mlxreg_hotplug_work_handler. In this
5724b5e32dfSVadim Pasternak 	 * case such signals will be missed. In order to handle these signals
5734b5e32dfSVadim Pasternak 	 * delayed work is canceled and work task re-scheduled for immediate
5744b5e32dfSVadim Pasternak 	 * execution. It allows to handle missed signals, if any. In other case
5754b5e32dfSVadim Pasternak 	 * work handler just validates that no new signals have been received
5764b5e32dfSVadim Pasternak 	 * during masking.
5771f976f69SVadim Pasternak 	 */
578c6acad68SVadim Pasternak 	cancel_delayed_work(&priv->dwork_irq);
579c6acad68SVadim Pasternak 	schedule_delayed_work(&priv->dwork_irq, 0);
5801f976f69SVadim Pasternak 
5811f976f69SVadim Pasternak 	spin_unlock_irqrestore(&priv->lock, flags);
5821f976f69SVadim Pasternak 
5831f976f69SVadim Pasternak 	return;
5841f976f69SVadim Pasternak 
5854b5e32dfSVadim Pasternak unmask_event:
5864b5e32dfSVadim Pasternak 	priv->not_asserted++;
5871f976f69SVadim Pasternak 	/* Unmask aggregation event (no need acknowledge). */
588c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, pdata->cell +
589c6acad68SVadim Pasternak 			   MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask);
590c6acad68SVadim Pasternak 
591c6acad68SVadim Pasternak  out:
592c6acad68SVadim Pasternak 	if (ret)
593c6acad68SVadim Pasternak 		dev_err(priv->dev, "Failed to complete workqueue.\n");
5941f976f69SVadim Pasternak }
5951f976f69SVadim Pasternak 
mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data * priv)596c6acad68SVadim Pasternak static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv)
5971f976f69SVadim Pasternak {
598c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
599c6acad68SVadim Pasternak 	struct mlxreg_core_item *item;
60083cdb2c1SVadim Pasternak 	struct mlxreg_core_data *data;
60183cdb2c1SVadim Pasternak 	u32 regval;
60283cdb2c1SVadim Pasternak 	int i, j, ret;
6031f976f69SVadim Pasternak 
604c6acad68SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
605c6acad68SVadim Pasternak 	item = pdata->items;
6061f976f69SVadim Pasternak 
607c6acad68SVadim Pasternak 	for (i = 0; i < pdata->counter; i++, item++) {
608c6acad68SVadim Pasternak 		/* Clear group presense event. */
609c6acad68SVadim Pasternak 		ret = regmap_write(priv->regmap, item->reg +
610c6acad68SVadim Pasternak 				   MLXREG_HOTPLUG_EVENT_OFF, 0);
611c6acad68SVadim Pasternak 		if (ret)
612c6acad68SVadim Pasternak 			goto out;
613c6acad68SVadim Pasternak 
61483cdb2c1SVadim Pasternak 		/*
61583cdb2c1SVadim Pasternak 		 * Verify if hardware configuration requires to disable
61683cdb2c1SVadim Pasternak 		 * interrupt capability for some of components.
61783cdb2c1SVadim Pasternak 		 */
61883cdb2c1SVadim Pasternak 		data = item->data;
61983cdb2c1SVadim Pasternak 		for (j = 0; j < item->count; j++, data++) {
62083cdb2c1SVadim Pasternak 			/* Verify if the attribute has capability register. */
62183cdb2c1SVadim Pasternak 			if (data->capability) {
62283cdb2c1SVadim Pasternak 				/* Read capability register. */
62383cdb2c1SVadim Pasternak 				ret = regmap_read(priv->regmap,
62483cdb2c1SVadim Pasternak 						  data->capability, &regval);
62583cdb2c1SVadim Pasternak 				if (ret)
62683cdb2c1SVadim Pasternak 					goto out;
62783cdb2c1SVadim Pasternak 
62883cdb2c1SVadim Pasternak 				if (!(regval & data->bit))
62983cdb2c1SVadim Pasternak 					item->mask &= ~BIT(j);
63083cdb2c1SVadim Pasternak 			}
63183cdb2c1SVadim Pasternak 		}
63283cdb2c1SVadim Pasternak 
633c6acad68SVadim Pasternak 		/* Set group initial status as mask and unmask group event. */
634c6acad68SVadim Pasternak 		if (item->inversed) {
635c6acad68SVadim Pasternak 			item->cache = item->mask;
636c6acad68SVadim Pasternak 			ret = regmap_write(priv->regmap, item->reg +
637c6acad68SVadim Pasternak 					   MLXREG_HOTPLUG_MASK_OFF,
638c6acad68SVadim Pasternak 					   item->mask);
639c6acad68SVadim Pasternak 			if (ret)
640c6acad68SVadim Pasternak 				goto out;
641c6acad68SVadim Pasternak 		}
642c6acad68SVadim Pasternak 	}
6431f976f69SVadim Pasternak 
6441f976f69SVadim Pasternak 	/* Keep aggregation initial status as zero and unmask events. */
645c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, pdata->cell +
646c6acad68SVadim Pasternak 			   MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask);
647c6acad68SVadim Pasternak 	if (ret)
648c6acad68SVadim Pasternak 		goto out;
649c6acad68SVadim Pasternak 
650c6acad68SVadim Pasternak 	/* Keep low aggregation initial status as zero and unmask events. */
651c6acad68SVadim Pasternak 	if (pdata->cell_low) {
652c6acad68SVadim Pasternak 		ret = regmap_write(priv->regmap, pdata->cell_low +
653c6acad68SVadim Pasternak 				   MLXREG_HOTPLUG_AGGR_MASK_OFF,
654c6acad68SVadim Pasternak 				   pdata->mask_low);
655c6acad68SVadim Pasternak 		if (ret)
656c6acad68SVadim Pasternak 			goto out;
657c6acad68SVadim Pasternak 	}
6581f976f69SVadim Pasternak 
6591f976f69SVadim Pasternak 	/* Invoke work handler for initializing hot plug devices setting. */
660c6acad68SVadim Pasternak 	mlxreg_hotplug_work_handler(&priv->dwork_irq.work);
6611f976f69SVadim Pasternak 
662c6acad68SVadim Pasternak  out:
663c6acad68SVadim Pasternak 	if (ret)
664c6acad68SVadim Pasternak 		dev_err(priv->dev, "Failed to set interrupts.\n");
6651f976f69SVadim Pasternak 	enable_irq(priv->irq);
666c6acad68SVadim Pasternak 	return ret;
6671f976f69SVadim Pasternak }
6681f976f69SVadim Pasternak 
mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data * priv)6691f976f69SVadim Pasternak static void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv)
6701f976f69SVadim Pasternak {
671c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
672c6acad68SVadim Pasternak 	struct mlxreg_core_item *item;
673c6acad68SVadim Pasternak 	struct mlxreg_core_data *data;
674c6acad68SVadim Pasternak 	int count, i, j;
6751f976f69SVadim Pasternak 
676c6acad68SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
677c6acad68SVadim Pasternak 	item = pdata->items;
6781f976f69SVadim Pasternak 	disable_irq(priv->irq);
679c6acad68SVadim Pasternak 	cancel_delayed_work_sync(&priv->dwork_irq);
680c6acad68SVadim Pasternak 
681c6acad68SVadim Pasternak 	/* Mask low aggregation event, if defined. */
682c6acad68SVadim Pasternak 	if (pdata->cell_low)
683c6acad68SVadim Pasternak 		regmap_write(priv->regmap, pdata->cell_low +
684c6acad68SVadim Pasternak 			     MLXREG_HOTPLUG_AGGR_MASK_OFF, 0);
6851f976f69SVadim Pasternak 
6861f976f69SVadim Pasternak 	/* Mask aggregation event. */
687c6acad68SVadim Pasternak 	regmap_write(priv->regmap, pdata->cell + MLXREG_HOTPLUG_AGGR_MASK_OFF,
688c6acad68SVadim Pasternak 		     0);
6891f976f69SVadim Pasternak 
690c6acad68SVadim Pasternak 	/* Clear topology configurations. */
691c6acad68SVadim Pasternak 	for (i = 0; i < pdata->counter; i++, item++) {
692c6acad68SVadim Pasternak 		data = item->data;
693c6acad68SVadim Pasternak 		/* Mask group presense event. */
694c6acad68SVadim Pasternak 		regmap_write(priv->regmap, data->reg + MLXREG_HOTPLUG_MASK_OFF,
695c6acad68SVadim Pasternak 			     0);
696c6acad68SVadim Pasternak 		/* Clear group presense event. */
697c6acad68SVadim Pasternak 		regmap_write(priv->regmap, data->reg +
698c6acad68SVadim Pasternak 			     MLXREG_HOTPLUG_EVENT_OFF, 0);
6991f976f69SVadim Pasternak 
700c6acad68SVadim Pasternak 		/* Remove all the attached devices in group. */
701c6acad68SVadim Pasternak 		count = item->count;
702c6acad68SVadim Pasternak 		for (j = 0; j < count; j++, data++)
703bb1023b6SVadim Pasternak 			mlxreg_hotplug_device_destroy(priv, data, item->kind);
704c6acad68SVadim Pasternak 	}
7051f976f69SVadim Pasternak }
7061f976f69SVadim Pasternak 
mlxreg_hotplug_irq_handler(int irq,void * dev)7071f976f69SVadim Pasternak static irqreturn_t mlxreg_hotplug_irq_handler(int irq, void *dev)
7081f976f69SVadim Pasternak {
709c6acad68SVadim Pasternak 	struct mlxreg_hotplug_priv_data *priv;
710c6acad68SVadim Pasternak 
711c6acad68SVadim Pasternak 	priv = (struct mlxreg_hotplug_priv_data *)dev;
7121f976f69SVadim Pasternak 
7131f976f69SVadim Pasternak 	/* Schedule work task for immediate execution.*/
714c6acad68SVadim Pasternak 	schedule_delayed_work(&priv->dwork_irq, 0);
7151f976f69SVadim Pasternak 
7161f976f69SVadim Pasternak 	return IRQ_HANDLED;
7171f976f69SVadim Pasternak }
7181f976f69SVadim Pasternak 
mlxreg_hotplug_probe(struct platform_device * pdev)7191f976f69SVadim Pasternak static int mlxreg_hotplug_probe(struct platform_device *pdev)
7201f976f69SVadim Pasternak {
721c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
7221f976f69SVadim Pasternak 	struct mlxreg_hotplug_priv_data *priv;
723d726f6b1SVadim Pasternak 	struct i2c_adapter *deferred_adap;
7241f976f69SVadim Pasternak 	int err;
7251f976f69SVadim Pasternak 
7261f976f69SVadim Pasternak 	pdata = dev_get_platdata(&pdev->dev);
7271f976f69SVadim Pasternak 	if (!pdata) {
7281f976f69SVadim Pasternak 		dev_err(&pdev->dev, "Failed to get platform data.\n");
7291f976f69SVadim Pasternak 		return -EINVAL;
7301f976f69SVadim Pasternak 	}
7311f976f69SVadim Pasternak 
732d726f6b1SVadim Pasternak 	/* Defer probing if the necessary adapter is not configured yet. */
733d726f6b1SVadim Pasternak 	deferred_adap = i2c_get_adapter(pdata->deferred_nr);
734d726f6b1SVadim Pasternak 	if (!deferred_adap)
735d726f6b1SVadim Pasternak 		return -EPROBE_DEFER;
736d726f6b1SVadim Pasternak 	i2c_put_adapter(deferred_adap);
737d726f6b1SVadim Pasternak 
7381f976f69SVadim Pasternak 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
7391f976f69SVadim Pasternak 	if (!priv)
7401f976f69SVadim Pasternak 		return -ENOMEM;
7411f976f69SVadim Pasternak 
742c6acad68SVadim Pasternak 	if (pdata->irq) {
743c6acad68SVadim Pasternak 		priv->irq = pdata->irq;
744c6acad68SVadim Pasternak 	} else {
7451f976f69SVadim Pasternak 		priv->irq = platform_get_irq(pdev, 0);
746eaae882cSStephen Boyd 		if (priv->irq < 0)
7471f976f69SVadim Pasternak 			return priv->irq;
7481f976f69SVadim Pasternak 	}
749c6acad68SVadim Pasternak 
750c6acad68SVadim Pasternak 	priv->regmap = pdata->regmap;
751c6acad68SVadim Pasternak 	priv->dev = pdev->dev.parent;
752c6acad68SVadim Pasternak 	priv->pdev = pdev;
7531f976f69SVadim Pasternak 
7541f976f69SVadim Pasternak 	err = devm_request_irq(&pdev->dev, priv->irq,
755c6acad68SVadim Pasternak 			       mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING
756701b54bcSMykola Kostenok 			       | IRQF_SHARED, "mlxreg-hotplug", priv);
7571f976f69SVadim Pasternak 	if (err) {
7581f976f69SVadim Pasternak 		dev_err(&pdev->dev, "Failed to request irq: %d\n", err);
7591f976f69SVadim Pasternak 		return err;
7601f976f69SVadim Pasternak 	}
7611f976f69SVadim Pasternak 
762701b54bcSMykola Kostenok 	disable_irq(priv->irq);
7631f976f69SVadim Pasternak 	spin_lock_init(&priv->lock);
764c6acad68SVadim Pasternak 	INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler);
765c6acad68SVadim Pasternak 	dev_set_drvdata(&pdev->dev, priv);
7661f976f69SVadim Pasternak 
7671f976f69SVadim Pasternak 	err = mlxreg_hotplug_attr_init(priv);
7681f976f69SVadim Pasternak 	if (err) {
769c6acad68SVadim Pasternak 		dev_err(&pdev->dev, "Failed to allocate attributes: %d\n",
770c6acad68SVadim Pasternak 			err);
7711f976f69SVadim Pasternak 		return err;
7721f976f69SVadim Pasternak 	}
7731f976f69SVadim Pasternak 
7741f976f69SVadim Pasternak 	priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev,
7751f976f69SVadim Pasternak 					"mlxreg_hotplug", priv, priv->groups);
7761f976f69SVadim Pasternak 	if (IS_ERR(priv->hwmon)) {
7771f976f69SVadim Pasternak 		dev_err(&pdev->dev, "Failed to register hwmon device %ld\n",
7781f976f69SVadim Pasternak 			PTR_ERR(priv->hwmon));
7791f976f69SVadim Pasternak 		return PTR_ERR(priv->hwmon);
7801f976f69SVadim Pasternak 	}
7811f976f69SVadim Pasternak 
7829272d2d1SVadim Pasternak 	/* Perform initial interrupts setup. */
7839272d2d1SVadim Pasternak 	mlxreg_hotplug_set_irq(priv);
7849272d2d1SVadim Pasternak 	priv->after_probe = true;
7859272d2d1SVadim Pasternak 
7861f976f69SVadim Pasternak 	return 0;
7871f976f69SVadim Pasternak }
7881f976f69SVadim Pasternak 
mlxreg_hotplug_remove(struct platform_device * pdev)7891f976f69SVadim Pasternak static int mlxreg_hotplug_remove(struct platform_device *pdev)
7901f976f69SVadim Pasternak {
791c6acad68SVadim Pasternak 	struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(&pdev->dev);
7921f976f69SVadim Pasternak 
7931f976f69SVadim Pasternak 	/* Clean interrupts setup. */
7941f976f69SVadim Pasternak 	mlxreg_hotplug_unset_irq(priv);
7958c2eb7b6SVadim Pasternak 	devm_free_irq(&pdev->dev, priv->irq, priv);
7961f976f69SVadim Pasternak 
7971f976f69SVadim Pasternak 	return 0;
7981f976f69SVadim Pasternak }
7991f976f69SVadim Pasternak 
8001f976f69SVadim Pasternak static struct platform_driver mlxreg_hotplug_driver = {
8011f976f69SVadim Pasternak 	.driver = {
8021f976f69SVadim Pasternak 		.name = "mlxreg-hotplug",
8031f976f69SVadim Pasternak 	},
8041f976f69SVadim Pasternak 	.probe = mlxreg_hotplug_probe,
8051f976f69SVadim Pasternak 	.remove = mlxreg_hotplug_remove,
8061f976f69SVadim Pasternak };
8071f976f69SVadim Pasternak 
8081f976f69SVadim Pasternak module_platform_driver(mlxreg_hotplug_driver);
8091f976f69SVadim Pasternak 
8101f976f69SVadim Pasternak MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
8111f976f69SVadim Pasternak MODULE_DESCRIPTION("Mellanox regmap hotplug platform driver");
8121f976f69SVadim Pasternak MODULE_LICENSE("Dual BSD/GPL");
8131f976f69SVadim Pasternak MODULE_ALIAS("platform:mlxreg-hotplug");
814