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, ®val); 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, ®val); 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, ®val); 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, ®val); 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