xref: /linux/drivers/platform/mellanox/mlxreg-hotplug.c (revision c6acad68eb2dbffd0497f91b206de5c362f59ee4)
130488704SVadim Pasternak /*
21f976f69SVadim Pasternak  * Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved.
31f976f69SVadim Pasternak  * Copyright (c) 2016-2018 Vadim Pasternak <vadimp@mellanox.com>
430488704SVadim Pasternak  *
530488704SVadim Pasternak  * Redistribution and use in source and binary forms, with or without
630488704SVadim Pasternak  * modification, are permitted provided that the following conditions are met:
730488704SVadim Pasternak  *
830488704SVadim Pasternak  * 1. Redistributions of source code must retain the above copyright
930488704SVadim Pasternak  *    notice, this list of conditions and the following disclaimer.
1030488704SVadim Pasternak  * 2. Redistributions in binary form must reproduce the above copyright
1130488704SVadim Pasternak  *    notice, this list of conditions and the following disclaimer in the
1230488704SVadim Pasternak  *    documentation and/or other materials provided with the distribution.
1330488704SVadim Pasternak  * 3. Neither the names of the copyright holders nor the names of its
1430488704SVadim Pasternak  *    contributors may be used to endorse or promote products derived from
1530488704SVadim Pasternak  *    this software without specific prior written permission.
1630488704SVadim Pasternak  *
1730488704SVadim Pasternak  * Alternatively, this software may be distributed under the terms of the
1830488704SVadim Pasternak  * GNU General Public License ("GPL") version 2 as published by the Free
1930488704SVadim Pasternak  * Software Foundation.
2030488704SVadim Pasternak  *
2130488704SVadim Pasternak  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
2230488704SVadim Pasternak  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2330488704SVadim Pasternak  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2430488704SVadim Pasternak  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
2530488704SVadim Pasternak  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2630488704SVadim Pasternak  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2730488704SVadim Pasternak  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2830488704SVadim Pasternak  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2930488704SVadim Pasternak  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3030488704SVadim Pasternak  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3130488704SVadim Pasternak  * POSSIBILITY OF SUCH DAMAGE.
3230488704SVadim Pasternak  */
3330488704SVadim Pasternak 
3430488704SVadim Pasternak #include <linux/bitops.h>
3530488704SVadim Pasternak #include <linux/device.h>
3630488704SVadim Pasternak #include <linux/hwmon.h>
3730488704SVadim Pasternak #include <linux/hwmon-sysfs.h>
3830488704SVadim Pasternak #include <linux/i2c.h>
3930488704SVadim Pasternak #include <linux/interrupt.h>
4030488704SVadim Pasternak #include <linux/module.h>
41*c6acad68SVadim Pasternak #include <linux/of_device.h>
421f976f69SVadim Pasternak #include <linux/platform_data/mlxreg.h>
4330488704SVadim Pasternak #include <linux/platform_device.h>
4430488704SVadim Pasternak #include <linux/spinlock.h>
45*c6acad68SVadim Pasternak #include <linux/regmap.h>
4630488704SVadim Pasternak #include <linux/workqueue.h>
4730488704SVadim Pasternak 
48*c6acad68SVadim Pasternak /* Offset of event and mask registers from status register. */
491f976f69SVadim Pasternak #define MLXREG_HOTPLUG_EVENT_OFF	1
501f976f69SVadim Pasternak #define MLXREG_HOTPLUG_MASK_OFF		2
511f976f69SVadim Pasternak #define MLXREG_HOTPLUG_AGGR_MASK_OFF	1
5230488704SVadim Pasternak 
53*c6acad68SVadim Pasternak /* ASIC health parameters. */
54*c6acad68SVadim Pasternak #define MLXREG_HOTPLUG_HEALTH_MASK	0x02
55*c6acad68SVadim Pasternak #define MLXREG_HOTPLUG_RST_CNTR		3
5630488704SVadim Pasternak 
57*c6acad68SVadim Pasternak #define MLXREG_HOTPLUG_ATTRS_MAX	24
5830488704SVadim Pasternak 
5930488704SVadim Pasternak /**
601f976f69SVadim Pasternak  * struct mlxreg_hotplug_priv_data - platform private data:
61*c6acad68SVadim Pasternak  * @irq: platform device interrupt number;
6230488704SVadim Pasternak  * @pdev: platform device;
6330488704SVadim Pasternak  * @plat: platform data;
64*c6acad68SVadim Pasternak  * @dwork: delayed work template;
65*c6acad68SVadim Pasternak  * @lock: spin lock;
6630488704SVadim Pasternak  * @hwmon: hwmon device;
671f976f69SVadim Pasternak  * @mlxreg_hotplug_attr: sysfs attributes array;
681f976f69SVadim Pasternak  * @mlxreg_hotplug_dev_attr: sysfs sensor device attribute array;
6930488704SVadim Pasternak  * @group: sysfs attribute group;
7030488704SVadim Pasternak  * @groups: list of sysfs attribute group for hwmon registration;
71*c6acad68SVadim Pasternak  * @cell: location of top aggregation interrupt register;
72*c6acad68SVadim Pasternak  * @mask: top aggregation interrupt common mask;
7330488704SVadim Pasternak  * @aggr_cache: last value of aggregation register status;
7430488704SVadim Pasternak  */
751f976f69SVadim Pasternak struct mlxreg_hotplug_priv_data {
7630488704SVadim Pasternak 	int irq;
77*c6acad68SVadim Pasternak 	struct device *dev;
7830488704SVadim Pasternak 	struct platform_device *pdev;
791f976f69SVadim Pasternak 	struct mlxreg_hotplug_platform_data *plat;
80*c6acad68SVadim Pasternak 	struct regmap *regmap;
81*c6acad68SVadim Pasternak 	struct delayed_work dwork_irq;
82*c6acad68SVadim Pasternak 	struct delayed_work dwork;
83*c6acad68SVadim Pasternak 	spinlock_t lock; /* sync with interrupt */
8430488704SVadim Pasternak 	struct device *hwmon;
85*c6acad68SVadim Pasternak 	struct attribute *mlxreg_hotplug_attr[MLXREG_HOTPLUG_ATTRS_MAX + 1];
8630488704SVadim Pasternak 	struct sensor_device_attribute_2
87*c6acad68SVadim Pasternak 			mlxreg_hotplug_dev_attr[MLXREG_HOTPLUG_ATTRS_MAX];
8830488704SVadim Pasternak 	struct attribute_group group;
8930488704SVadim Pasternak 	const struct attribute_group *groups[2];
90*c6acad68SVadim Pasternak 	u32 cell;
91*c6acad68SVadim Pasternak 	u32 mask;
92*c6acad68SVadim Pasternak 	u32 aggr_cache;
93*c6acad68SVadim Pasternak 	bool after_probe;
9430488704SVadim Pasternak };
9530488704SVadim Pasternak 
96752849e6SVadim Pasternak static int mlxreg_hotplug_device_create(struct device *dev,
97*c6acad68SVadim Pasternak 					struct mlxreg_core_data *data)
98752849e6SVadim Pasternak {
99*c6acad68SVadim Pasternak 	data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr);
100*c6acad68SVadim Pasternak 	if (!data->hpdev.adapter) {
101752849e6SVadim Pasternak 		dev_err(dev, "Failed to get adapter for bus %d\n",
102*c6acad68SVadim Pasternak 			data->hpdev.nr);
103752849e6SVadim Pasternak 		return -EFAULT;
104752849e6SVadim Pasternak 	}
105752849e6SVadim Pasternak 
106*c6acad68SVadim Pasternak 	data->hpdev.client = i2c_new_device(data->hpdev.adapter,
107*c6acad68SVadim Pasternak 					    data->hpdev.brdinfo);
108*c6acad68SVadim Pasternak 	if (!data->hpdev.client) {
109752849e6SVadim Pasternak 		dev_err(dev, "Failed to create client %s at bus %d at addr 0x%02x\n",
110*c6acad68SVadim Pasternak 			data->hpdev.brdinfo->type, data->hpdev.nr,
111*c6acad68SVadim Pasternak 			data->hpdev.brdinfo->addr);
112*c6acad68SVadim Pasternak 
113*c6acad68SVadim Pasternak 		i2c_put_adapter(data->hpdev.adapter);
114*c6acad68SVadim Pasternak 		data->hpdev.adapter = NULL;
115752849e6SVadim Pasternak 		return -EFAULT;
116752849e6SVadim Pasternak 	}
117752849e6SVadim Pasternak 
118752849e6SVadim Pasternak 	return 0;
119752849e6SVadim Pasternak }
120752849e6SVadim Pasternak 
121*c6acad68SVadim Pasternak static void mlxreg_hotplug_device_destroy(struct mlxreg_core_data *data)
122752849e6SVadim Pasternak {
123*c6acad68SVadim Pasternak 	if (data->hpdev.client) {
124*c6acad68SVadim Pasternak 		i2c_unregister_device(data->hpdev.client);
125*c6acad68SVadim Pasternak 		data->hpdev.client = NULL;
126752849e6SVadim Pasternak 	}
127752849e6SVadim Pasternak 
128*c6acad68SVadim Pasternak 	if (data->hpdev.adapter) {
129*c6acad68SVadim Pasternak 		i2c_put_adapter(data->hpdev.adapter);
130*c6acad68SVadim Pasternak 		data->hpdev.adapter = NULL;
131752849e6SVadim Pasternak 	}
132752849e6SVadim Pasternak }
133752849e6SVadim Pasternak 
1341f976f69SVadim Pasternak static ssize_t mlxreg_hotplug_attr_show(struct device *dev,
13530488704SVadim Pasternak 					struct device_attribute *attr,
13630488704SVadim Pasternak 					char *buf)
13730488704SVadim Pasternak {
138*c6acad68SVadim Pasternak 	struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(dev);
139*c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
14030488704SVadim Pasternak 	int index = to_sensor_dev_attr_2(attr)->index;
14130488704SVadim Pasternak 	int nr = to_sensor_dev_attr_2(attr)->nr;
142*c6acad68SVadim Pasternak 	struct mlxreg_core_item *item;
143*c6acad68SVadim Pasternak 	struct mlxreg_core_data *data;
144*c6acad68SVadim Pasternak 	u32 regval;
145*c6acad68SVadim Pasternak 	int ret;
14630488704SVadim Pasternak 
147*c6acad68SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
148*c6acad68SVadim Pasternak 	item = pdata->items + nr;
149*c6acad68SVadim Pasternak 	data = item->data + index;
15030488704SVadim Pasternak 
151*c6acad68SVadim Pasternak 	ret = regmap_read(priv->regmap, data->reg, &regval);
152*c6acad68SVadim Pasternak 	if (ret)
153*c6acad68SVadim Pasternak 		return ret;
15430488704SVadim Pasternak 
155*c6acad68SVadim Pasternak 	if (item->health) {
156*c6acad68SVadim Pasternak 		regval &= data->mask;
157*c6acad68SVadim Pasternak 	} else {
158*c6acad68SVadim Pasternak 		/* Bit = 0 : functional if item->inversed is true. */
159*c6acad68SVadim Pasternak 		if (item->inversed)
160*c6acad68SVadim Pasternak 			regval = !(regval & data->mask);
161*c6acad68SVadim Pasternak 		else
162*c6acad68SVadim Pasternak 			regval = !!(regval & data->mask);
16330488704SVadim Pasternak 	}
16430488704SVadim Pasternak 
165*c6acad68SVadim Pasternak 	return sprintf(buf, "%u\n", regval);
16630488704SVadim Pasternak }
16730488704SVadim Pasternak 
1681f976f69SVadim Pasternak #define PRIV_ATTR(i) priv->mlxreg_hotplug_attr[i]
1691f976f69SVadim Pasternak #define PRIV_DEV_ATTR(i) priv->mlxreg_hotplug_dev_attr[i]
170*c6acad68SVadim Pasternak 
1711f976f69SVadim Pasternak static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv)
17230488704SVadim Pasternak {
173*c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
174*c6acad68SVadim Pasternak 	struct mlxreg_core_item *item;
175*c6acad68SVadim Pasternak 	struct mlxreg_core_data *data;
176*c6acad68SVadim Pasternak 	int num_attrs = 0, id = 0, i, j;
177*c6acad68SVadim Pasternak 
178*c6acad68SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
179*c6acad68SVadim Pasternak 	item = pdata->items;
180*c6acad68SVadim Pasternak 
181*c6acad68SVadim Pasternak 	/* Go over all kinds of items - psu, pwr, fan. */
182*c6acad68SVadim Pasternak 	for (i = 0; i < pdata->counter; i++, item++) {
183*c6acad68SVadim Pasternak 		num_attrs += item->count;
184*c6acad68SVadim Pasternak 		data = item->data;
185*c6acad68SVadim Pasternak 		/* Go over all units within the item. */
186*c6acad68SVadim Pasternak 		for (j = 0; j < item->count; j++, data++, id++) {
187*c6acad68SVadim Pasternak 			PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr;
188*c6acad68SVadim Pasternak 			PRIV_ATTR(id)->name = devm_kasprintf(&priv->pdev->dev,
189*c6acad68SVadim Pasternak 							     GFP_KERNEL,
190*c6acad68SVadim Pasternak 							     data->label);
191*c6acad68SVadim Pasternak 
192*c6acad68SVadim Pasternak 			if (!PRIV_ATTR(id)->name) {
193*c6acad68SVadim Pasternak 				dev_err(priv->dev, "Memory allocation failed for attr %d.\n",
194*c6acad68SVadim Pasternak 					id);
195*c6acad68SVadim Pasternak 				return -ENOMEM;
196*c6acad68SVadim Pasternak 			}
197*c6acad68SVadim Pasternak 
198*c6acad68SVadim Pasternak 			PRIV_DEV_ATTR(id).dev_attr.attr.name =
199*c6acad68SVadim Pasternak 							PRIV_ATTR(id)->name;
200*c6acad68SVadim Pasternak 			PRIV_DEV_ATTR(id).dev_attr.attr.mode = 0444;
201*c6acad68SVadim Pasternak 			PRIV_DEV_ATTR(id).dev_attr.show =
202*c6acad68SVadim Pasternak 						mlxreg_hotplug_attr_show;
203*c6acad68SVadim Pasternak 			PRIV_DEV_ATTR(id).nr = i;
204*c6acad68SVadim Pasternak 			PRIV_DEV_ATTR(id).index = j;
205*c6acad68SVadim Pasternak 			sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr);
206*c6acad68SVadim Pasternak 		}
207*c6acad68SVadim Pasternak 	}
20830488704SVadim Pasternak 
20930488704SVadim Pasternak 	priv->group.attrs = devm_kzalloc(&priv->pdev->dev, num_attrs *
21030488704SVadim Pasternak 					 sizeof(struct attribute *),
21130488704SVadim Pasternak 					 GFP_KERNEL);
21230488704SVadim Pasternak 	if (!priv->group.attrs)
21330488704SVadim Pasternak 		return -ENOMEM;
21430488704SVadim Pasternak 
2151f976f69SVadim Pasternak 	priv->group.attrs = priv->mlxreg_hotplug_attr;
21630488704SVadim Pasternak 	priv->groups[0] = &priv->group;
21730488704SVadim Pasternak 	priv->groups[1] = NULL;
21830488704SVadim Pasternak 
21930488704SVadim Pasternak 	return 0;
22030488704SVadim Pasternak }
22130488704SVadim Pasternak 
222*c6acad68SVadim Pasternak static void
223*c6acad68SVadim Pasternak mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv,
224*c6acad68SVadim Pasternak 			   struct mlxreg_core_item *item)
22530488704SVadim Pasternak {
226*c6acad68SVadim Pasternak 	struct mlxreg_core_data *data;
227*c6acad68SVadim Pasternak 	u32 asserted, regval, bit;
228*c6acad68SVadim Pasternak 	int ret;
22930488704SVadim Pasternak 
23030488704SVadim Pasternak 	/*
23130488704SVadim Pasternak 	 * Validate if item related to received signal type is valid.
23230488704SVadim Pasternak 	 * It should never happen, excepted the situation when some
23330488704SVadim Pasternak 	 * piece of hardware is broken. In such situation just produce
23430488704SVadim Pasternak 	 * error message and return. Caller must continue to handle the
23530488704SVadim Pasternak 	 * signals from other devices if any.
23630488704SVadim Pasternak 	 */
23730488704SVadim Pasternak 	if (unlikely(!item)) {
238*c6acad68SVadim Pasternak 		dev_err(priv->dev, "False signal: at offset:mask 0x%02x:0x%02x.\n",
239*c6acad68SVadim Pasternak 			item->reg, item->mask);
240*c6acad68SVadim Pasternak 
24130488704SVadim Pasternak 		return;
24230488704SVadim Pasternak 	}
24330488704SVadim Pasternak 
244*c6acad68SVadim Pasternak 	/* Mask event. */
245*c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF,
246*c6acad68SVadim Pasternak 			   0);
247*c6acad68SVadim Pasternak 	if (ret)
248*c6acad68SVadim Pasternak 		goto out;
249*c6acad68SVadim Pasternak 
250*c6acad68SVadim Pasternak 	/* Read status. */
251*c6acad68SVadim Pasternak 	ret = regmap_read(priv->regmap, item->reg, &regval);
252*c6acad68SVadim Pasternak 	if (ret)
253*c6acad68SVadim Pasternak 		goto out;
254*c6acad68SVadim Pasternak 
255*c6acad68SVadim Pasternak 	/* Set asserted bits and save last status. */
256*c6acad68SVadim Pasternak 	regval &= item->mask;
257*c6acad68SVadim Pasternak 	asserted = item->cache ^ regval;
258*c6acad68SVadim Pasternak 	item->cache = regval;
259*c6acad68SVadim Pasternak 
26030488704SVadim Pasternak 	for_each_set_bit(bit, (unsigned long *)&asserted, 8) {
261*c6acad68SVadim Pasternak 		data = item->data + bit;
262*c6acad68SVadim Pasternak 		if (regval & BIT(bit)) {
263*c6acad68SVadim Pasternak 			if (item->inversed)
264*c6acad68SVadim Pasternak 				mlxreg_hotplug_device_destroy(data);
26530488704SVadim Pasternak 			else
266*c6acad68SVadim Pasternak 				mlxreg_hotplug_device_create(priv->dev, data);
26730488704SVadim Pasternak 		} else {
268*c6acad68SVadim Pasternak 			if (item->inversed)
269*c6acad68SVadim Pasternak 				mlxreg_hotplug_device_create(priv->dev, data);
27030488704SVadim Pasternak 			else
271*c6acad68SVadim Pasternak 				mlxreg_hotplug_device_destroy(data);
27230488704SVadim Pasternak 		}
27330488704SVadim Pasternak 	}
27430488704SVadim Pasternak 
27530488704SVadim Pasternak 	/* Acknowledge event. */
276*c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_EVENT_OFF,
277*c6acad68SVadim Pasternak 			   0);
278*c6acad68SVadim Pasternak 	if (ret)
279*c6acad68SVadim Pasternak 		goto out;
280*c6acad68SVadim Pasternak 
28130488704SVadim Pasternak 	/* Unmask event. */
282*c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF,
283*c6acad68SVadim Pasternak 			   item->mask);
284*c6acad68SVadim Pasternak 
285*c6acad68SVadim Pasternak  out:
286*c6acad68SVadim Pasternak 	if (ret)
287*c6acad68SVadim Pasternak 		dev_err(priv->dev, "Failed to complete workqueue.\n");
288*c6acad68SVadim Pasternak }
289*c6acad68SVadim Pasternak 
290*c6acad68SVadim Pasternak static void
291*c6acad68SVadim Pasternak mlxreg_hotplug_health_work_helper(struct mlxreg_hotplug_priv_data *priv,
292*c6acad68SVadim Pasternak 				  struct mlxreg_core_item *item)
293*c6acad68SVadim Pasternak {
294*c6acad68SVadim Pasternak 	struct mlxreg_core_data *data = item->data;
295*c6acad68SVadim Pasternak 	u32 regval;
296*c6acad68SVadim Pasternak 	int i, ret;
297*c6acad68SVadim Pasternak 
298*c6acad68SVadim Pasternak 	for (i = 0; i < item->count; i++, data++) {
299*c6acad68SVadim Pasternak 		/* Mask event. */
300*c6acad68SVadim Pasternak 		ret = regmap_write(priv->regmap, data->reg +
301*c6acad68SVadim Pasternak 				   MLXREG_HOTPLUG_MASK_OFF, 0);
302*c6acad68SVadim Pasternak 		if (ret)
303*c6acad68SVadim Pasternak 			goto out;
304*c6acad68SVadim Pasternak 
305*c6acad68SVadim Pasternak 		/* Read status. */
306*c6acad68SVadim Pasternak 		ret = regmap_read(priv->regmap, data->reg, &regval);
307*c6acad68SVadim Pasternak 		if (ret)
308*c6acad68SVadim Pasternak 			goto out;
309*c6acad68SVadim Pasternak 
310*c6acad68SVadim Pasternak 		regval &= data->mask;
311*c6acad68SVadim Pasternak 		item->cache = regval;
312*c6acad68SVadim Pasternak 		if (regval == MLXREG_HOTPLUG_HEALTH_MASK) {
313*c6acad68SVadim Pasternak 			if ((data->health_cntr++ == MLXREG_HOTPLUG_RST_CNTR) ||
314*c6acad68SVadim Pasternak 			    !priv->after_probe) {
315*c6acad68SVadim Pasternak 				mlxreg_hotplug_device_create(priv->dev, data);
316*c6acad68SVadim Pasternak 				data->attached = true;
317*c6acad68SVadim Pasternak 			}
318*c6acad68SVadim Pasternak 		} else {
319*c6acad68SVadim Pasternak 			if (data->attached) {
320*c6acad68SVadim Pasternak 				mlxreg_hotplug_device_destroy(data);
321*c6acad68SVadim Pasternak 				data->attached = false;
322*c6acad68SVadim Pasternak 				data->health_cntr = 0;
323*c6acad68SVadim Pasternak 			}
324*c6acad68SVadim Pasternak 		}
325*c6acad68SVadim Pasternak 
326*c6acad68SVadim Pasternak 		/* Acknowledge event. */
327*c6acad68SVadim Pasternak 		ret = regmap_write(priv->regmap, data->reg +
328*c6acad68SVadim Pasternak 				   MLXREG_HOTPLUG_EVENT_OFF, 0);
329*c6acad68SVadim Pasternak 		if (ret)
330*c6acad68SVadim Pasternak 			goto out;
331*c6acad68SVadim Pasternak 
332*c6acad68SVadim Pasternak 		/* Unmask event. */
333*c6acad68SVadim Pasternak 		ret = regmap_write(priv->regmap, data->reg +
334*c6acad68SVadim Pasternak 				   MLXREG_HOTPLUG_MASK_OFF, data->mask);
335*c6acad68SVadim Pasternak 		if (ret)
336*c6acad68SVadim Pasternak 			goto out;
337*c6acad68SVadim Pasternak 	}
338*c6acad68SVadim Pasternak 
339*c6acad68SVadim Pasternak  out:
340*c6acad68SVadim Pasternak 	if (ret)
341*c6acad68SVadim Pasternak 		dev_err(priv->dev, "Failed to complete workqueue.\n");
34230488704SVadim Pasternak }
34330488704SVadim Pasternak 
34430488704SVadim Pasternak /*
345*c6acad68SVadim Pasternak  * mlxreg_hotplug_work_handler - performs traversing of device interrupt
34630488704SVadim Pasternak  * registers according to the below hierarchy schema:
34730488704SVadim Pasternak  *
34830488704SVadim Pasternak  *				Aggregation registers (status/mask)
34930488704SVadim Pasternak  * PSU registers:		*---*
35030488704SVadim Pasternak  * *-----------------*		|   |
35130488704SVadim Pasternak  * |status/event/mask|----->    | * |
35230488704SVadim Pasternak  * *-----------------*		|   |
35330488704SVadim Pasternak  * Power registers:		|   |
35430488704SVadim Pasternak  * *-----------------*		|   |
355*c6acad68SVadim Pasternak  * |status/event/mask|----->    | * |
35630488704SVadim Pasternak  * *-----------------*		|   |
357*c6acad68SVadim Pasternak  * FAN registers:		|   |--> CPU
358*c6acad68SVadim Pasternak  * *-----------------*		|   |
359*c6acad68SVadim Pasternak  * |status/event/mask|----->    | * |
360*c6acad68SVadim Pasternak  * *-----------------*		|   |
361*c6acad68SVadim Pasternak  * ASIC registers:		|   |
36230488704SVadim Pasternak  * *-----------------*		|   |
36330488704SVadim Pasternak  * |status/event/mask|----->    | * |
36430488704SVadim Pasternak  * *-----------------*		|   |
36530488704SVadim Pasternak  *				*---*
366*c6acad68SVadim Pasternak  *
36730488704SVadim Pasternak  * In case some system changed are detected: FAN in/out, PSU in/out, power
368*c6acad68SVadim Pasternak  * cable attached/detached, ASIC health good/bad, relevant device is created
369*c6acad68SVadim Pasternak  * or destroyed.
37030488704SVadim Pasternak  */
3711f976f69SVadim Pasternak static void mlxreg_hotplug_work_handler(struct work_struct *work)
37230488704SVadim Pasternak {
373*c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
374*c6acad68SVadim Pasternak 	struct mlxreg_hotplug_priv_data *priv;
375*c6acad68SVadim Pasternak 	struct mlxreg_core_item *item;
376*c6acad68SVadim Pasternak 	u32 regval, aggr_asserted;
37730488704SVadim Pasternak 	unsigned long flags;
378*c6acad68SVadim Pasternak 	int i, ret;
379*c6acad68SVadim Pasternak 
380*c6acad68SVadim Pasternak 	priv = container_of(work, struct mlxreg_hotplug_priv_data,
381*c6acad68SVadim Pasternak 			    dwork_irq.work);
382*c6acad68SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
383*c6acad68SVadim Pasternak 	item = pdata->items;
38430488704SVadim Pasternak 
38530488704SVadim Pasternak 	/* Mask aggregation event. */
386*c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, pdata->cell +
387*c6acad68SVadim Pasternak 			   MLXREG_HOTPLUG_AGGR_MASK_OFF, 0);
388*c6acad68SVadim Pasternak 	if (ret < 0)
389*c6acad68SVadim Pasternak 		goto out;
390*c6acad68SVadim Pasternak 
39130488704SVadim Pasternak 	/* Read aggregation status. */
392*c6acad68SVadim Pasternak 	ret = regmap_read(priv->regmap, pdata->cell, &regval);
393*c6acad68SVadim Pasternak 	if (ret)
394*c6acad68SVadim Pasternak 		goto out;
39530488704SVadim Pasternak 
396*c6acad68SVadim Pasternak 	regval &= pdata->mask;
397*c6acad68SVadim Pasternak 	aggr_asserted = priv->aggr_cache ^ regval;
398*c6acad68SVadim Pasternak 	priv->aggr_cache = regval;
39930488704SVadim Pasternak 
400*c6acad68SVadim Pasternak 	/* Handle topology and health configuration changes. */
401*c6acad68SVadim Pasternak 	for (i = 0; i < pdata->counter; i++, item++) {
402*c6acad68SVadim Pasternak 		if (aggr_asserted & item->aggr_mask) {
403*c6acad68SVadim Pasternak 			if (item->health)
404*c6acad68SVadim Pasternak 				mlxreg_hotplug_health_work_helper(priv, item);
405*c6acad68SVadim Pasternak 			else
406*c6acad68SVadim Pasternak 				mlxreg_hotplug_work_helper(priv, item);
407*c6acad68SVadim Pasternak 		}
408*c6acad68SVadim Pasternak 	}
40930488704SVadim Pasternak 
41030488704SVadim Pasternak 	if (aggr_asserted) {
41130488704SVadim Pasternak 		spin_lock_irqsave(&priv->lock, flags);
41230488704SVadim Pasternak 
41330488704SVadim Pasternak 		/*
41430488704SVadim Pasternak 		 * It is possible, that some signals have been inserted, while
4151f976f69SVadim Pasternak 		 * interrupt has been masked by mlxreg_hotplug_work_handler.
41630488704SVadim Pasternak 		 * In this case such signals will be missed. In order to handle
41730488704SVadim Pasternak 		 * these signals delayed work is canceled and work task
41830488704SVadim Pasternak 		 * re-scheduled for immediate execution. It allows to handle
41930488704SVadim Pasternak 		 * missed signals, if any. In other case work handler just
42030488704SVadim Pasternak 		 * validates that no new signals have been received during
42130488704SVadim Pasternak 		 * masking.
42230488704SVadim Pasternak 		 */
423*c6acad68SVadim Pasternak 		cancel_delayed_work(&priv->dwork_irq);
424*c6acad68SVadim Pasternak 		schedule_delayed_work(&priv->dwork_irq, 0);
42530488704SVadim Pasternak 
42630488704SVadim Pasternak 		spin_unlock_irqrestore(&priv->lock, flags);
42730488704SVadim Pasternak 
42830488704SVadim Pasternak 		return;
42930488704SVadim Pasternak 	}
43030488704SVadim Pasternak 
43130488704SVadim Pasternak 	/* Unmask aggregation event (no need acknowledge). */
432*c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, pdata->cell +
433*c6acad68SVadim Pasternak 			   MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask);
434*c6acad68SVadim Pasternak 
435*c6acad68SVadim Pasternak  out:
436*c6acad68SVadim Pasternak 	if (ret)
437*c6acad68SVadim Pasternak 		dev_err(priv->dev, "Failed to complete workqueue.\n");
43830488704SVadim Pasternak }
43930488704SVadim Pasternak 
440*c6acad68SVadim Pasternak static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv)
44130488704SVadim Pasternak {
442*c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
443*c6acad68SVadim Pasternak 	struct mlxreg_core_item *item;
444*c6acad68SVadim Pasternak 	int i, ret;
44530488704SVadim Pasternak 
446*c6acad68SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
447*c6acad68SVadim Pasternak 	item = pdata->items;
44830488704SVadim Pasternak 
449*c6acad68SVadim Pasternak 	for (i = 0; i < pdata->counter; i++, item++) {
450*c6acad68SVadim Pasternak 		/* Clear group presense event. */
451*c6acad68SVadim Pasternak 		ret = regmap_write(priv->regmap, item->reg +
452*c6acad68SVadim Pasternak 				   MLXREG_HOTPLUG_EVENT_OFF, 0);
453*c6acad68SVadim Pasternak 		if (ret)
454*c6acad68SVadim Pasternak 			goto out;
455*c6acad68SVadim Pasternak 
456*c6acad68SVadim Pasternak 		/* Set group initial status as mask and unmask group event. */
457*c6acad68SVadim Pasternak 		if (item->inversed) {
458*c6acad68SVadim Pasternak 			item->cache = item->mask;
459*c6acad68SVadim Pasternak 			ret = regmap_write(priv->regmap, item->reg +
460*c6acad68SVadim Pasternak 					   MLXREG_HOTPLUG_MASK_OFF,
461*c6acad68SVadim Pasternak 					   item->mask);
462*c6acad68SVadim Pasternak 			if (ret)
463*c6acad68SVadim Pasternak 				goto out;
464*c6acad68SVadim Pasternak 		}
465*c6acad68SVadim Pasternak 	}
46630488704SVadim Pasternak 
46730488704SVadim Pasternak 	/* Keep aggregation initial status as zero and unmask events. */
468*c6acad68SVadim Pasternak 	ret = regmap_write(priv->regmap, pdata->cell +
469*c6acad68SVadim Pasternak 			   MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask);
470*c6acad68SVadim Pasternak 	if (ret)
471*c6acad68SVadim Pasternak 		goto out;
472*c6acad68SVadim Pasternak 
473*c6acad68SVadim Pasternak 	/* Keep low aggregation initial status as zero and unmask events. */
474*c6acad68SVadim Pasternak 	if (pdata->cell_low) {
475*c6acad68SVadim Pasternak 		ret = regmap_write(priv->regmap, pdata->cell_low +
476*c6acad68SVadim Pasternak 				   MLXREG_HOTPLUG_AGGR_MASK_OFF,
477*c6acad68SVadim Pasternak 				   pdata->mask_low);
478*c6acad68SVadim Pasternak 		if (ret)
479*c6acad68SVadim Pasternak 			goto out;
480*c6acad68SVadim Pasternak 	}
48130488704SVadim Pasternak 
48230488704SVadim Pasternak 	/* Invoke work handler for initializing hot plug devices setting. */
483*c6acad68SVadim Pasternak 	mlxreg_hotplug_work_handler(&priv->dwork_irq.work);
48430488704SVadim Pasternak 
485*c6acad68SVadim Pasternak  out:
486*c6acad68SVadim Pasternak 	if (ret)
487*c6acad68SVadim Pasternak 		dev_err(priv->dev, "Failed to set interrupts.\n");
48830488704SVadim Pasternak 	enable_irq(priv->irq);
489*c6acad68SVadim Pasternak 	return ret;
49030488704SVadim Pasternak }
49130488704SVadim Pasternak 
4921f976f69SVadim Pasternak static void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv)
49330488704SVadim Pasternak {
494*c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
495*c6acad68SVadim Pasternak 	struct mlxreg_core_item *item;
496*c6acad68SVadim Pasternak 	struct mlxreg_core_data *data;
497*c6acad68SVadim Pasternak 	int count, i, j;
49830488704SVadim Pasternak 
499*c6acad68SVadim Pasternak 	pdata = dev_get_platdata(&priv->pdev->dev);
500*c6acad68SVadim Pasternak 	item = pdata->items;
50130488704SVadim Pasternak 	disable_irq(priv->irq);
502*c6acad68SVadim Pasternak 	cancel_delayed_work_sync(&priv->dwork_irq);
503*c6acad68SVadim Pasternak 
504*c6acad68SVadim Pasternak 	/* Mask low aggregation event, if defined. */
505*c6acad68SVadim Pasternak 	if (pdata->cell_low)
506*c6acad68SVadim Pasternak 		regmap_write(priv->regmap, pdata->cell_low +
507*c6acad68SVadim Pasternak 			     MLXREG_HOTPLUG_AGGR_MASK_OFF, 0);
50830488704SVadim Pasternak 
50930488704SVadim Pasternak 	/* Mask aggregation event. */
510*c6acad68SVadim Pasternak 	regmap_write(priv->regmap, pdata->cell + MLXREG_HOTPLUG_AGGR_MASK_OFF,
511*c6acad68SVadim Pasternak 		     0);
51230488704SVadim Pasternak 
513*c6acad68SVadim Pasternak 	/* Clear topology configurations. */
514*c6acad68SVadim Pasternak 	for (i = 0; i < pdata->counter; i++, item++) {
515*c6acad68SVadim Pasternak 		data = item->data;
516*c6acad68SVadim Pasternak 		/* Mask group presense event. */
517*c6acad68SVadim Pasternak 		regmap_write(priv->regmap, data->reg + MLXREG_HOTPLUG_MASK_OFF,
518*c6acad68SVadim Pasternak 			     0);
519*c6acad68SVadim Pasternak 		/* Clear group presense event. */
520*c6acad68SVadim Pasternak 		regmap_write(priv->regmap, data->reg +
521*c6acad68SVadim Pasternak 			     MLXREG_HOTPLUG_EVENT_OFF, 0);
52230488704SVadim Pasternak 
523*c6acad68SVadim Pasternak 		/* Remove all the attached devices in group. */
524*c6acad68SVadim Pasternak 		count = item->count;
525*c6acad68SVadim Pasternak 		for (j = 0; j < count; j++, data++)
526*c6acad68SVadim Pasternak 			mlxreg_hotplug_device_destroy(data);
527*c6acad68SVadim Pasternak 	}
52830488704SVadim Pasternak }
52930488704SVadim Pasternak 
5301f976f69SVadim Pasternak static irqreturn_t mlxreg_hotplug_irq_handler(int irq, void *dev)
53130488704SVadim Pasternak {
532*c6acad68SVadim Pasternak 	struct mlxreg_hotplug_priv_data *priv;
533*c6acad68SVadim Pasternak 
534*c6acad68SVadim Pasternak 	priv = (struct mlxreg_hotplug_priv_data *)dev;
53530488704SVadim Pasternak 
53630488704SVadim Pasternak 	/* Schedule work task for immediate execution.*/
537*c6acad68SVadim Pasternak 	schedule_delayed_work(&priv->dwork_irq, 0);
53830488704SVadim Pasternak 
53930488704SVadim Pasternak 	return IRQ_HANDLED;
54030488704SVadim Pasternak }
54130488704SVadim Pasternak 
5421f976f69SVadim Pasternak static int mlxreg_hotplug_probe(struct platform_device *pdev)
54330488704SVadim Pasternak {
544*c6acad68SVadim Pasternak 	struct mlxreg_core_hotplug_platform_data *pdata;
5451f976f69SVadim Pasternak 	struct mlxreg_hotplug_priv_data *priv;
54630488704SVadim Pasternak 	int err;
54730488704SVadim Pasternak 
54830488704SVadim Pasternak 	pdata = dev_get_platdata(&pdev->dev);
54930488704SVadim Pasternak 	if (!pdata) {
55030488704SVadim Pasternak 		dev_err(&pdev->dev, "Failed to get platform data.\n");
55130488704SVadim Pasternak 		return -EINVAL;
55230488704SVadim Pasternak 	}
55330488704SVadim Pasternak 
55430488704SVadim Pasternak 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
55530488704SVadim Pasternak 	if (!priv)
55630488704SVadim Pasternak 		return -ENOMEM;
55730488704SVadim Pasternak 
558*c6acad68SVadim Pasternak 	if (pdata->irq) {
559*c6acad68SVadim Pasternak 		priv->irq = pdata->irq;
560*c6acad68SVadim Pasternak 	} else {
56130488704SVadim Pasternak 		priv->irq = platform_get_irq(pdev, 0);
56230488704SVadim Pasternak 		if (priv->irq < 0) {
56330488704SVadim Pasternak 			dev_err(&pdev->dev, "Failed to get platform irq: %d\n",
56430488704SVadim Pasternak 				priv->irq);
56530488704SVadim Pasternak 			return priv->irq;
56630488704SVadim Pasternak 		}
567*c6acad68SVadim Pasternak 	}
568*c6acad68SVadim Pasternak 
569*c6acad68SVadim Pasternak 	priv->regmap = pdata->regmap;
570*c6acad68SVadim Pasternak 	priv->dev = pdev->dev.parent;
571*c6acad68SVadim Pasternak 	priv->pdev = pdev;
57230488704SVadim Pasternak 
57330488704SVadim Pasternak 	err = devm_request_irq(&pdev->dev, priv->irq,
574*c6acad68SVadim Pasternak 			       mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING
575*c6acad68SVadim Pasternak 			       | IRQF_SHARED, "mlxreg-hotplug", priv);
57630488704SVadim Pasternak 	if (err) {
57730488704SVadim Pasternak 		dev_err(&pdev->dev, "Failed to request irq: %d\n", err);
57830488704SVadim Pasternak 		return err;
57930488704SVadim Pasternak 	}
58030488704SVadim Pasternak 
581*c6acad68SVadim Pasternak 	disable_irq(priv->irq);
58230488704SVadim Pasternak 	spin_lock_init(&priv->lock);
583*c6acad68SVadim Pasternak 	INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler);
584*c6acad68SVadim Pasternak 	/* Perform initial interrupts setup. */
585*c6acad68SVadim Pasternak 	mlxreg_hotplug_set_irq(priv);
586*c6acad68SVadim Pasternak 
587*c6acad68SVadim Pasternak 	priv->after_probe = true;
588*c6acad68SVadim Pasternak 	dev_set_drvdata(&pdev->dev, priv);
58930488704SVadim Pasternak 
5901f976f69SVadim Pasternak 	err = mlxreg_hotplug_attr_init(priv);
59130488704SVadim Pasternak 	if (err) {
592*c6acad68SVadim Pasternak 		dev_err(&pdev->dev, "Failed to allocate attributes: %d\n",
593*c6acad68SVadim Pasternak 			err);
59430488704SVadim Pasternak 		return err;
59530488704SVadim Pasternak 	}
59630488704SVadim Pasternak 
59730488704SVadim Pasternak 	priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev,
5981f976f69SVadim Pasternak 					"mlxreg_hotplug", priv, priv->groups);
59930488704SVadim Pasternak 	if (IS_ERR(priv->hwmon)) {
60030488704SVadim Pasternak 		dev_err(&pdev->dev, "Failed to register hwmon device %ld\n",
60130488704SVadim Pasternak 			PTR_ERR(priv->hwmon));
60230488704SVadim Pasternak 		return PTR_ERR(priv->hwmon);
60330488704SVadim Pasternak 	}
60430488704SVadim Pasternak 
60530488704SVadim Pasternak 	return 0;
60630488704SVadim Pasternak }
60730488704SVadim Pasternak 
6081f976f69SVadim Pasternak static int mlxreg_hotplug_remove(struct platform_device *pdev)
60930488704SVadim Pasternak {
610*c6acad68SVadim Pasternak 	struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(&pdev->dev);
61130488704SVadim Pasternak 
61230488704SVadim Pasternak 	/* Clean interrupts setup. */
6131f976f69SVadim Pasternak 	mlxreg_hotplug_unset_irq(priv);
61430488704SVadim Pasternak 
61530488704SVadim Pasternak 	return 0;
61630488704SVadim Pasternak }
61730488704SVadim Pasternak 
6181f976f69SVadim Pasternak static struct platform_driver mlxreg_hotplug_driver = {
61930488704SVadim Pasternak 	.driver = {
6201f976f69SVadim Pasternak 		.name = "mlxreg-hotplug",
62130488704SVadim Pasternak 	},
6221f976f69SVadim Pasternak 	.probe = mlxreg_hotplug_probe,
6231f976f69SVadim Pasternak 	.remove = mlxreg_hotplug_remove,
62430488704SVadim Pasternak };
62530488704SVadim Pasternak 
6261f976f69SVadim Pasternak module_platform_driver(mlxreg_hotplug_driver);
62730488704SVadim Pasternak 
62830488704SVadim Pasternak MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
6291f976f69SVadim Pasternak MODULE_DESCRIPTION("Mellanox regmap hotplug platform driver");
63030488704SVadim Pasternak MODULE_LICENSE("Dual BSD/GPL");
6311f976f69SVadim Pasternak MODULE_ALIAS("platform:mlxreg-hotplug");
632