15272d4e9SVadim Pasternak // SPDX-License-Identifier: GPL-2.0+ 230488704SVadim Pasternak /* 35272d4e9SVadim Pasternak * Mellanox hotplug driver 430488704SVadim Pasternak * 55272d4e9SVadim Pasternak * Copyright (C) 2016-2020 Mellanox Technologies 630488704SVadim Pasternak */ 730488704SVadim Pasternak 830488704SVadim Pasternak #include <linux/bitops.h> 930488704SVadim Pasternak #include <linux/device.h> 1030488704SVadim Pasternak #include <linux/hwmon.h> 1130488704SVadim Pasternak #include <linux/hwmon-sysfs.h> 1230488704SVadim Pasternak #include <linux/i2c.h> 1330488704SVadim Pasternak #include <linux/interrupt.h> 1430488704SVadim Pasternak #include <linux/module.h> 15c6acad68SVadim Pasternak #include <linux/of_device.h> 161f976f69SVadim Pasternak #include <linux/platform_data/mlxreg.h> 1730488704SVadim Pasternak #include <linux/platform_device.h> 1830488704SVadim Pasternak #include <linux/spinlock.h> 19*92d020f9SVadim Pasternak #include <linux/string_helpers.h> 20c6acad68SVadim Pasternak #include <linux/regmap.h> 2130488704SVadim Pasternak #include <linux/workqueue.h> 2230488704SVadim Pasternak 23c6acad68SVadim Pasternak /* Offset of event and mask registers from status register. */ 241f976f69SVadim Pasternak #define MLXREG_HOTPLUG_EVENT_OFF 1 251f976f69SVadim Pasternak #define MLXREG_HOTPLUG_MASK_OFF 2 261f976f69SVadim Pasternak #define MLXREG_HOTPLUG_AGGR_MASK_OFF 1 2730488704SVadim Pasternak 2866342d1cSVadim Pasternak /* ASIC good health mask. */ 2966342d1cSVadim Pasternak #define MLXREG_HOTPLUG_GOOD_HEALTH_MASK 0x02 3030488704SVadim Pasternak 31c6acad68SVadim Pasternak #define MLXREG_HOTPLUG_ATTRS_MAX 24 324b5e32dfSVadim Pasternak #define MLXREG_HOTPLUG_NOT_ASSERT 3 3330488704SVadim Pasternak 3430488704SVadim Pasternak /** 351f976f69SVadim Pasternak * struct mlxreg_hotplug_priv_data - platform private data: 36c6acad68SVadim Pasternak * @irq: platform device interrupt number; 37321089a4SVadim Pasternak * @dev: basic device; 3830488704SVadim Pasternak * @pdev: platform device; 3930488704SVadim Pasternak * @plat: platform data; 40321089a4SVadim Pasternak * @regmap: register map handle; 41321089a4SVadim Pasternak * @dwork_irq: delayed work template; 42c6acad68SVadim Pasternak * @lock: spin lock; 4330488704SVadim Pasternak * @hwmon: hwmon device; 441f976f69SVadim Pasternak * @mlxreg_hotplug_attr: sysfs attributes array; 451f976f69SVadim Pasternak * @mlxreg_hotplug_dev_attr: sysfs sensor device attribute array; 4630488704SVadim Pasternak * @group: sysfs attribute group; 4730488704SVadim Pasternak * @groups: list of sysfs attribute group for hwmon registration; 48c6acad68SVadim Pasternak * @cell: location of top aggregation interrupt register; 49c6acad68SVadim Pasternak * @mask: top aggregation interrupt common mask; 5030488704SVadim Pasternak * @aggr_cache: last value of aggregation register status; 51321089a4SVadim Pasternak * @after_probe: flag indication probing completion; 524b5e32dfSVadim Pasternak * @not_asserted: number of entries in workqueue with no signal assertion; 5330488704SVadim Pasternak */ 541f976f69SVadim Pasternak struct mlxreg_hotplug_priv_data { 5530488704SVadim Pasternak int irq; 56c6acad68SVadim Pasternak struct device *dev; 5730488704SVadim Pasternak struct platform_device *pdev; 581f976f69SVadim Pasternak struct mlxreg_hotplug_platform_data *plat; 59c6acad68SVadim Pasternak struct regmap *regmap; 60c6acad68SVadim Pasternak struct delayed_work dwork_irq; 61c6acad68SVadim Pasternak spinlock_t lock; /* sync with interrupt */ 6230488704SVadim Pasternak struct device *hwmon; 63c6acad68SVadim Pasternak struct attribute *mlxreg_hotplug_attr[MLXREG_HOTPLUG_ATTRS_MAX + 1]; 6430488704SVadim Pasternak struct sensor_device_attribute_2 65c6acad68SVadim Pasternak mlxreg_hotplug_dev_attr[MLXREG_HOTPLUG_ATTRS_MAX]; 6630488704SVadim Pasternak struct attribute_group group; 6730488704SVadim Pasternak const struct attribute_group *groups[2]; 68c6acad68SVadim Pasternak u32 cell; 69c6acad68SVadim Pasternak u32 mask; 70c6acad68SVadim Pasternak u32 aggr_cache; 71c6acad68SVadim Pasternak bool after_probe; 724b5e32dfSVadim Pasternak u8 not_asserted; 7330488704SVadim Pasternak }; 7430488704SVadim Pasternak 75*92d020f9SVadim Pasternak /* Environment variables array for udev. */ 76*92d020f9SVadim Pasternak static char *mlxreg_hotplug_udev_envp[] = { NULL, NULL }; 77*92d020f9SVadim Pasternak 78*92d020f9SVadim Pasternak static int 79*92d020f9SVadim Pasternak mlxreg_hotplug_udev_event_send(struct kobject *kobj, 80*92d020f9SVadim Pasternak struct mlxreg_core_data *data, bool action) 81*92d020f9SVadim Pasternak { 82*92d020f9SVadim Pasternak char event_str[MLXREG_CORE_LABEL_MAX_SIZE + 2]; 83*92d020f9SVadim Pasternak char label[MLXREG_CORE_LABEL_MAX_SIZE] = { 0 }; 84*92d020f9SVadim Pasternak 85*92d020f9SVadim Pasternak mlxreg_hotplug_udev_envp[0] = event_str; 86*92d020f9SVadim Pasternak string_upper(label, data->label); 87*92d020f9SVadim Pasternak snprintf(event_str, MLXREG_CORE_LABEL_MAX_SIZE, "%s=%d", label, !!action); 88*92d020f9SVadim Pasternak 89*92d020f9SVadim Pasternak return kobject_uevent_env(kobj, KOBJ_CHANGE, mlxreg_hotplug_udev_envp); 90*92d020f9SVadim Pasternak } 91*92d020f9SVadim Pasternak 92f709e1bfSVadim Pasternak static int mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data *priv, 93c6acad68SVadim Pasternak struct mlxreg_core_data *data) 94752849e6SVadim Pasternak { 95ef0f6226SVadim Pasternak struct mlxreg_core_hotplug_platform_data *pdata; 9684c0eb21SWolfram Sang struct i2c_client *client; 97ef0f6226SVadim Pasternak 989272d2d1SVadim Pasternak /* Notify user by sending hwmon uevent. */ 99*92d020f9SVadim Pasternak mlxreg_hotplug_udev_event_send(&priv->hwmon->kobj, data, true); 1009272d2d1SVadim Pasternak 1017805fa8dSVadim Pasternak /* 1027805fa8dSVadim Pasternak * Return if adapter number is negative. It could be in case hotplug 1037805fa8dSVadim Pasternak * event is not associated with hotplug device. 1047805fa8dSVadim Pasternak */ 1057805fa8dSVadim Pasternak if (data->hpdev.nr < 0) 1067805fa8dSVadim Pasternak return 0; 1077805fa8dSVadim Pasternak 108ef0f6226SVadim Pasternak pdata = dev_get_platdata(&priv->pdev->dev); 109ef0f6226SVadim Pasternak data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr + 110ef0f6226SVadim Pasternak pdata->shift_nr); 111c6acad68SVadim Pasternak if (!data->hpdev.adapter) { 112f709e1bfSVadim Pasternak dev_err(priv->dev, "Failed to get adapter for bus %d\n", 113ef0f6226SVadim Pasternak data->hpdev.nr + pdata->shift_nr); 114752849e6SVadim Pasternak return -EFAULT; 115752849e6SVadim Pasternak } 116752849e6SVadim Pasternak 11784c0eb21SWolfram Sang client = i2c_new_client_device(data->hpdev.adapter, 118c6acad68SVadim Pasternak data->hpdev.brdinfo); 11984c0eb21SWolfram Sang if (IS_ERR(client)) { 120f709e1bfSVadim Pasternak dev_err(priv->dev, "Failed to create client %s at bus %d at addr 0x%02x\n", 121ef0f6226SVadim Pasternak data->hpdev.brdinfo->type, data->hpdev.nr + 122ef0f6226SVadim Pasternak pdata->shift_nr, data->hpdev.brdinfo->addr); 123c6acad68SVadim Pasternak 124c6acad68SVadim Pasternak i2c_put_adapter(data->hpdev.adapter); 125c6acad68SVadim Pasternak data->hpdev.adapter = NULL; 12684c0eb21SWolfram Sang return PTR_ERR(client); 127752849e6SVadim Pasternak } 128752849e6SVadim Pasternak 12984c0eb21SWolfram Sang data->hpdev.client = client; 13084c0eb21SWolfram Sang 131752849e6SVadim Pasternak return 0; 132752849e6SVadim Pasternak } 133752849e6SVadim Pasternak 1349272d2d1SVadim Pasternak static void 1359272d2d1SVadim Pasternak mlxreg_hotplug_device_destroy(struct mlxreg_hotplug_priv_data *priv, 1369272d2d1SVadim Pasternak struct mlxreg_core_data *data) 137752849e6SVadim Pasternak { 1389272d2d1SVadim Pasternak /* Notify user by sending hwmon uevent. */ 139*92d020f9SVadim Pasternak mlxreg_hotplug_udev_event_send(&priv->hwmon->kobj, data, false); 1409272d2d1SVadim Pasternak 141c6acad68SVadim Pasternak if (data->hpdev.client) { 142c6acad68SVadim Pasternak i2c_unregister_device(data->hpdev.client); 143c6acad68SVadim Pasternak data->hpdev.client = NULL; 144752849e6SVadim Pasternak } 145752849e6SVadim Pasternak 146c6acad68SVadim Pasternak if (data->hpdev.adapter) { 147c6acad68SVadim Pasternak i2c_put_adapter(data->hpdev.adapter); 148c6acad68SVadim Pasternak data->hpdev.adapter = NULL; 149752849e6SVadim Pasternak } 150752849e6SVadim Pasternak } 151752849e6SVadim Pasternak 1521f976f69SVadim Pasternak static ssize_t mlxreg_hotplug_attr_show(struct device *dev, 15330488704SVadim Pasternak struct device_attribute *attr, 15430488704SVadim Pasternak char *buf) 15530488704SVadim Pasternak { 156c6acad68SVadim Pasternak struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(dev); 157c6acad68SVadim Pasternak struct mlxreg_core_hotplug_platform_data *pdata; 15830488704SVadim Pasternak int index = to_sensor_dev_attr_2(attr)->index; 15930488704SVadim Pasternak int nr = to_sensor_dev_attr_2(attr)->nr; 160c6acad68SVadim Pasternak struct mlxreg_core_item *item; 161c6acad68SVadim Pasternak struct mlxreg_core_data *data; 162c6acad68SVadim Pasternak u32 regval; 163c6acad68SVadim Pasternak int ret; 16430488704SVadim Pasternak 165c6acad68SVadim Pasternak pdata = dev_get_platdata(&priv->pdev->dev); 166c6acad68SVadim Pasternak item = pdata->items + nr; 167c6acad68SVadim Pasternak data = item->data + index; 16830488704SVadim Pasternak 169c6acad68SVadim Pasternak ret = regmap_read(priv->regmap, data->reg, ®val); 170c6acad68SVadim Pasternak if (ret) 171c6acad68SVadim Pasternak return ret; 17230488704SVadim Pasternak 173c6acad68SVadim Pasternak if (item->health) { 174c6acad68SVadim Pasternak regval &= data->mask; 175c6acad68SVadim Pasternak } else { 176c6acad68SVadim Pasternak /* Bit = 0 : functional if item->inversed is true. */ 177c6acad68SVadim Pasternak if (item->inversed) 178c6acad68SVadim Pasternak regval = !(regval & data->mask); 179c6acad68SVadim Pasternak else 180c6acad68SVadim Pasternak regval = !!(regval & data->mask); 18130488704SVadim Pasternak } 18230488704SVadim Pasternak 183c6acad68SVadim Pasternak return sprintf(buf, "%u\n", regval); 18430488704SVadim Pasternak } 18530488704SVadim Pasternak 1861f976f69SVadim Pasternak #define PRIV_ATTR(i) priv->mlxreg_hotplug_attr[i] 1871f976f69SVadim Pasternak #define PRIV_DEV_ATTR(i) priv->mlxreg_hotplug_dev_attr[i] 188c6acad68SVadim Pasternak 1891f976f69SVadim Pasternak static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) 19030488704SVadim Pasternak { 191c6acad68SVadim Pasternak struct mlxreg_core_hotplug_platform_data *pdata; 192c6acad68SVadim Pasternak struct mlxreg_core_item *item; 193c6acad68SVadim Pasternak struct mlxreg_core_data *data; 1940a43f7beSVadim Pasternak unsigned long mask; 1950a43f7beSVadim Pasternak u32 regval; 1960a43f7beSVadim Pasternak int num_attrs = 0, id = 0, i, j, k, ret; 197c6acad68SVadim Pasternak 198c6acad68SVadim Pasternak pdata = dev_get_platdata(&priv->pdev->dev); 199c6acad68SVadim Pasternak item = pdata->items; 200c6acad68SVadim Pasternak 201c6acad68SVadim Pasternak /* Go over all kinds of items - psu, pwr, fan. */ 202c6acad68SVadim Pasternak for (i = 0; i < pdata->counter; i++, item++) { 2030a43f7beSVadim Pasternak if (item->capability) { 2040a43f7beSVadim Pasternak /* 2050a43f7beSVadim Pasternak * Read group capability register to get actual number 2060a43f7beSVadim Pasternak * of interrupt capable components and set group mask 2070a43f7beSVadim Pasternak * accordingly. 2080a43f7beSVadim Pasternak */ 2090a43f7beSVadim Pasternak ret = regmap_read(priv->regmap, item->capability, 2100a43f7beSVadim Pasternak ®val); 2110a43f7beSVadim Pasternak if (ret) 2120a43f7beSVadim Pasternak return ret; 2130a43f7beSVadim Pasternak 2140a43f7beSVadim Pasternak item->mask = GENMASK((regval & item->mask) - 1, 0); 2150a43f7beSVadim Pasternak } 2160a43f7beSVadim Pasternak 217c6acad68SVadim Pasternak data = item->data; 2180a43f7beSVadim Pasternak 2190a43f7beSVadim Pasternak /* Go over all unmasked units within item. */ 2200a43f7beSVadim Pasternak mask = item->mask; 2210a43f7beSVadim Pasternak k = 0; 2220a43f7beSVadim Pasternak for_each_set_bit(j, &mask, item->count) { 2230a43f7beSVadim Pasternak if (data->capability) { 2240a43f7beSVadim Pasternak /* 2250a43f7beSVadim Pasternak * Read capability register and skip non 2260a43f7beSVadim Pasternak * relevant attributes. 2270a43f7beSVadim Pasternak */ 2280a43f7beSVadim Pasternak ret = regmap_read(priv->regmap, 2290a43f7beSVadim Pasternak data->capability, ®val); 2300a43f7beSVadim Pasternak if (ret) 2310a43f7beSVadim Pasternak return ret; 2320a43f7beSVadim Pasternak if (!(regval & data->bit)) { 2330a43f7beSVadim Pasternak data++; 2340a43f7beSVadim Pasternak continue; 2350a43f7beSVadim Pasternak } 2360a43f7beSVadim Pasternak } 237c6acad68SVadim Pasternak PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr; 238c6acad68SVadim Pasternak PRIV_ATTR(id)->name = devm_kasprintf(&priv->pdev->dev, 239c6acad68SVadim Pasternak GFP_KERNEL, 240c6acad68SVadim Pasternak data->label); 241c6acad68SVadim Pasternak 242c6acad68SVadim Pasternak if (!PRIV_ATTR(id)->name) { 243c6acad68SVadim Pasternak dev_err(priv->dev, "Memory allocation failed for attr %d.\n", 244c6acad68SVadim Pasternak id); 245c6acad68SVadim Pasternak return -ENOMEM; 246c6acad68SVadim Pasternak } 247c6acad68SVadim Pasternak 248c6acad68SVadim Pasternak PRIV_DEV_ATTR(id).dev_attr.attr.name = 249c6acad68SVadim Pasternak PRIV_ATTR(id)->name; 250c6acad68SVadim Pasternak PRIV_DEV_ATTR(id).dev_attr.attr.mode = 0444; 251c6acad68SVadim Pasternak PRIV_DEV_ATTR(id).dev_attr.show = 252c6acad68SVadim Pasternak mlxreg_hotplug_attr_show; 253c6acad68SVadim Pasternak PRIV_DEV_ATTR(id).nr = i; 2540a43f7beSVadim Pasternak PRIV_DEV_ATTR(id).index = k; 255c6acad68SVadim Pasternak sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr); 2560a43f7beSVadim Pasternak data++; 2570a43f7beSVadim Pasternak id++; 2580a43f7beSVadim Pasternak k++; 259c6acad68SVadim Pasternak } 2600a43f7beSVadim Pasternak num_attrs += k; 261c6acad68SVadim Pasternak } 26230488704SVadim Pasternak 263a86854d0SKees Cook priv->group.attrs = devm_kcalloc(&priv->pdev->dev, 264a86854d0SKees Cook num_attrs, 26530488704SVadim Pasternak sizeof(struct attribute *), 26630488704SVadim Pasternak GFP_KERNEL); 26730488704SVadim Pasternak if (!priv->group.attrs) 26830488704SVadim Pasternak return -ENOMEM; 26930488704SVadim Pasternak 2701f976f69SVadim Pasternak priv->group.attrs = priv->mlxreg_hotplug_attr; 27130488704SVadim Pasternak priv->groups[0] = &priv->group; 27230488704SVadim Pasternak priv->groups[1] = NULL; 27330488704SVadim Pasternak 27430488704SVadim Pasternak return 0; 27530488704SVadim Pasternak } 27630488704SVadim Pasternak 277c6acad68SVadim Pasternak static void 278c6acad68SVadim Pasternak mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv, 279c6acad68SVadim Pasternak struct mlxreg_core_item *item) 28030488704SVadim Pasternak { 281c6acad68SVadim Pasternak struct mlxreg_core_data *data; 282e4c275f7SVadim Pasternak unsigned long asserted; 283e4c275f7SVadim Pasternak u32 regval, bit; 284c6acad68SVadim Pasternak int ret; 28530488704SVadim Pasternak 28630488704SVadim Pasternak /* 28730488704SVadim Pasternak * Validate if item related to received signal type is valid. 28830488704SVadim Pasternak * It should never happen, excepted the situation when some 28930488704SVadim Pasternak * piece of hardware is broken. In such situation just produce 29030488704SVadim Pasternak * error message and return. Caller must continue to handle the 29130488704SVadim Pasternak * signals from other devices if any. 29230488704SVadim Pasternak */ 29330488704SVadim Pasternak if (unlikely(!item)) { 294c6acad68SVadim Pasternak dev_err(priv->dev, "False signal: at offset:mask 0x%02x:0x%02x.\n", 295c6acad68SVadim Pasternak item->reg, item->mask); 296c6acad68SVadim Pasternak 29730488704SVadim Pasternak return; 29830488704SVadim Pasternak } 29930488704SVadim Pasternak 300c6acad68SVadim Pasternak /* Mask event. */ 301c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, 302c6acad68SVadim Pasternak 0); 303c6acad68SVadim Pasternak if (ret) 304c6acad68SVadim Pasternak goto out; 305c6acad68SVadim Pasternak 306c6acad68SVadim Pasternak /* Read status. */ 307c6acad68SVadim Pasternak ret = regmap_read(priv->regmap, item->reg, ®val); 308c6acad68SVadim Pasternak if (ret) 309c6acad68SVadim Pasternak goto out; 310c6acad68SVadim Pasternak 311c6acad68SVadim Pasternak /* Set asserted bits and save last status. */ 312c6acad68SVadim Pasternak regval &= item->mask; 313c6acad68SVadim Pasternak asserted = item->cache ^ regval; 314c6acad68SVadim Pasternak item->cache = regval; 315c6acad68SVadim Pasternak 316e4c275f7SVadim Pasternak for_each_set_bit(bit, &asserted, 8) { 317c6acad68SVadim Pasternak data = item->data + bit; 318c6acad68SVadim Pasternak if (regval & BIT(bit)) { 319c6acad68SVadim Pasternak if (item->inversed) 3209272d2d1SVadim Pasternak mlxreg_hotplug_device_destroy(priv, data); 32130488704SVadim Pasternak else 322f709e1bfSVadim Pasternak mlxreg_hotplug_device_create(priv, data); 32330488704SVadim Pasternak } else { 324c6acad68SVadim Pasternak if (item->inversed) 325f709e1bfSVadim Pasternak mlxreg_hotplug_device_create(priv, data); 32630488704SVadim Pasternak else 3279272d2d1SVadim Pasternak mlxreg_hotplug_device_destroy(priv, data); 32830488704SVadim Pasternak } 32930488704SVadim Pasternak } 33030488704SVadim Pasternak 33130488704SVadim Pasternak /* Acknowledge event. */ 332c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_EVENT_OFF, 333c6acad68SVadim Pasternak 0); 334c6acad68SVadim Pasternak if (ret) 335c6acad68SVadim Pasternak goto out; 336c6acad68SVadim Pasternak 33730488704SVadim Pasternak /* Unmask event. */ 338c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, 339c6acad68SVadim Pasternak item->mask); 340c6acad68SVadim Pasternak 341c6acad68SVadim Pasternak out: 342c6acad68SVadim Pasternak if (ret) 343c6acad68SVadim Pasternak dev_err(priv->dev, "Failed to complete workqueue.\n"); 344c6acad68SVadim Pasternak } 345c6acad68SVadim Pasternak 346c6acad68SVadim Pasternak static void 347c6acad68SVadim Pasternak mlxreg_hotplug_health_work_helper(struct mlxreg_hotplug_priv_data *priv, 348c6acad68SVadim Pasternak struct mlxreg_core_item *item) 349c6acad68SVadim Pasternak { 350c6acad68SVadim Pasternak struct mlxreg_core_data *data = item->data; 351c6acad68SVadim Pasternak u32 regval; 352b81e830cSGeert Uytterhoeven int i, ret = 0; 353c6acad68SVadim Pasternak 354c6acad68SVadim Pasternak for (i = 0; i < item->count; i++, data++) { 355c6acad68SVadim Pasternak /* Mask event. */ 356c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, data->reg + 357c6acad68SVadim Pasternak MLXREG_HOTPLUG_MASK_OFF, 0); 358c6acad68SVadim Pasternak if (ret) 359c6acad68SVadim Pasternak goto out; 360c6acad68SVadim Pasternak 361c6acad68SVadim Pasternak /* Read status. */ 362c6acad68SVadim Pasternak ret = regmap_read(priv->regmap, data->reg, ®val); 363c6acad68SVadim Pasternak if (ret) 364c6acad68SVadim Pasternak goto out; 365c6acad68SVadim Pasternak 366c6acad68SVadim Pasternak regval &= data->mask; 36766342d1cSVadim Pasternak 36866342d1cSVadim Pasternak if (item->cache == regval) 36966342d1cSVadim Pasternak goto ack_event; 37066342d1cSVadim Pasternak 37166342d1cSVadim Pasternak /* 37266342d1cSVadim Pasternak * ASIC health indication is provided through two bits. Bits 37366342d1cSVadim Pasternak * value 0x2 indicates that ASIC reached the good health, value 37466342d1cSVadim Pasternak * 0x0 indicates ASIC the bad health or dormant state and value 37566342d1cSVadim Pasternak * 0x3 indicates the booting state. During ASIC reset it should 37666342d1cSVadim Pasternak * pass the following states: dormant -> booting -> good. 37766342d1cSVadim Pasternak */ 37866342d1cSVadim Pasternak if (regval == MLXREG_HOTPLUG_GOOD_HEALTH_MASK) { 37966342d1cSVadim Pasternak if (!data->attached) { 38066342d1cSVadim Pasternak /* 38166342d1cSVadim Pasternak * ASIC is in steady state. Connect associated 38266342d1cSVadim Pasternak * device, if configured. 38366342d1cSVadim Pasternak */ 384f709e1bfSVadim Pasternak mlxreg_hotplug_device_create(priv, data); 385c6acad68SVadim Pasternak data->attached = true; 386c6acad68SVadim Pasternak } 387c6acad68SVadim Pasternak } else { 388c6acad68SVadim Pasternak if (data->attached) { 38966342d1cSVadim Pasternak /* 39066342d1cSVadim Pasternak * ASIC health is failed after ASIC has been 39166342d1cSVadim Pasternak * in steady state. Disconnect associated 39266342d1cSVadim Pasternak * device, if it has been connected. 39366342d1cSVadim Pasternak */ 3949272d2d1SVadim Pasternak mlxreg_hotplug_device_destroy(priv, data); 395c6acad68SVadim Pasternak data->attached = false; 396c6acad68SVadim Pasternak data->health_cntr = 0; 397c6acad68SVadim Pasternak } 398c6acad68SVadim Pasternak } 39966342d1cSVadim Pasternak item->cache = regval; 40066342d1cSVadim Pasternak ack_event: 401c6acad68SVadim Pasternak /* Acknowledge event. */ 402c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, data->reg + 403c6acad68SVadim Pasternak MLXREG_HOTPLUG_EVENT_OFF, 0); 404c6acad68SVadim Pasternak if (ret) 405c6acad68SVadim Pasternak goto out; 406c6acad68SVadim Pasternak 407c6acad68SVadim Pasternak /* Unmask event. */ 408c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, data->reg + 409c6acad68SVadim Pasternak MLXREG_HOTPLUG_MASK_OFF, data->mask); 410c6acad68SVadim Pasternak if (ret) 411c6acad68SVadim Pasternak goto out; 412c6acad68SVadim Pasternak } 413c6acad68SVadim Pasternak 414c6acad68SVadim Pasternak out: 415c6acad68SVadim Pasternak if (ret) 416c6acad68SVadim Pasternak dev_err(priv->dev, "Failed to complete workqueue.\n"); 41730488704SVadim Pasternak } 41830488704SVadim Pasternak 41930488704SVadim Pasternak /* 420c6acad68SVadim Pasternak * mlxreg_hotplug_work_handler - performs traversing of device interrupt 42130488704SVadim Pasternak * registers according to the below hierarchy schema: 42230488704SVadim Pasternak * 42330488704SVadim Pasternak * Aggregation registers (status/mask) 42430488704SVadim Pasternak * PSU registers: *---* 42530488704SVadim Pasternak * *-----------------* | | 42630488704SVadim Pasternak * |status/event/mask|-----> | * | 42730488704SVadim Pasternak * *-----------------* | | 42830488704SVadim Pasternak * Power registers: | | 42930488704SVadim Pasternak * *-----------------* | | 430c6acad68SVadim Pasternak * |status/event/mask|-----> | * | 43130488704SVadim Pasternak * *-----------------* | | 432c6acad68SVadim Pasternak * FAN registers: | |--> CPU 433c6acad68SVadim Pasternak * *-----------------* | | 434c6acad68SVadim Pasternak * |status/event/mask|-----> | * | 435c6acad68SVadim Pasternak * *-----------------* | | 436c6acad68SVadim Pasternak * ASIC registers: | | 43730488704SVadim Pasternak * *-----------------* | | 43830488704SVadim Pasternak * |status/event/mask|-----> | * | 43930488704SVadim Pasternak * *-----------------* | | 44030488704SVadim Pasternak * *---* 441c6acad68SVadim Pasternak * 44230488704SVadim Pasternak * In case some system changed are detected: FAN in/out, PSU in/out, power 443c6acad68SVadim Pasternak * cable attached/detached, ASIC health good/bad, relevant device is created 444c6acad68SVadim Pasternak * or destroyed. 44530488704SVadim Pasternak */ 4461f976f69SVadim Pasternak static void mlxreg_hotplug_work_handler(struct work_struct *work) 44730488704SVadim Pasternak { 448c6acad68SVadim Pasternak struct mlxreg_core_hotplug_platform_data *pdata; 449c6acad68SVadim Pasternak struct mlxreg_hotplug_priv_data *priv; 450c6acad68SVadim Pasternak struct mlxreg_core_item *item; 451c6acad68SVadim Pasternak u32 regval, aggr_asserted; 45230488704SVadim Pasternak unsigned long flags; 453c6acad68SVadim Pasternak int i, ret; 454c6acad68SVadim Pasternak 455c6acad68SVadim Pasternak priv = container_of(work, struct mlxreg_hotplug_priv_data, 456c6acad68SVadim Pasternak dwork_irq.work); 457c6acad68SVadim Pasternak pdata = dev_get_platdata(&priv->pdev->dev); 458c6acad68SVadim Pasternak item = pdata->items; 45930488704SVadim Pasternak 46030488704SVadim Pasternak /* Mask aggregation event. */ 461c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, pdata->cell + 462c6acad68SVadim Pasternak MLXREG_HOTPLUG_AGGR_MASK_OFF, 0); 463c6acad68SVadim Pasternak if (ret < 0) 464c6acad68SVadim Pasternak goto out; 465c6acad68SVadim Pasternak 46630488704SVadim Pasternak /* Read aggregation status. */ 467c6acad68SVadim Pasternak ret = regmap_read(priv->regmap, pdata->cell, ®val); 468c6acad68SVadim Pasternak if (ret) 469c6acad68SVadim Pasternak goto out; 47030488704SVadim Pasternak 471c6acad68SVadim Pasternak regval &= pdata->mask; 472c6acad68SVadim Pasternak aggr_asserted = priv->aggr_cache ^ regval; 473c6acad68SVadim Pasternak priv->aggr_cache = regval; 47430488704SVadim Pasternak 4754b5e32dfSVadim Pasternak /* 4764b5e32dfSVadim Pasternak * Handler is invoked, but no assertion is detected at top aggregation 4774b5e32dfSVadim Pasternak * status level. Set aggr_asserted to mask value to allow handler extra 4784b5e32dfSVadim Pasternak * run over all relevant signals to recover any missed signal. 4794b5e32dfSVadim Pasternak */ 4804b5e32dfSVadim Pasternak if (priv->not_asserted == MLXREG_HOTPLUG_NOT_ASSERT) { 4814b5e32dfSVadim Pasternak priv->not_asserted = 0; 4824b5e32dfSVadim Pasternak aggr_asserted = pdata->mask; 4834b5e32dfSVadim Pasternak } 4844b5e32dfSVadim Pasternak if (!aggr_asserted) 4854b5e32dfSVadim Pasternak goto unmask_event; 4864b5e32dfSVadim Pasternak 487c6acad68SVadim Pasternak /* Handle topology and health configuration changes. */ 488c6acad68SVadim Pasternak for (i = 0; i < pdata->counter; i++, item++) { 489c6acad68SVadim Pasternak if (aggr_asserted & item->aggr_mask) { 490c6acad68SVadim Pasternak if (item->health) 491c6acad68SVadim Pasternak mlxreg_hotplug_health_work_helper(priv, item); 492c6acad68SVadim Pasternak else 493c6acad68SVadim Pasternak mlxreg_hotplug_work_helper(priv, item); 494c6acad68SVadim Pasternak } 495c6acad68SVadim Pasternak } 49630488704SVadim Pasternak 49730488704SVadim Pasternak spin_lock_irqsave(&priv->lock, flags); 49830488704SVadim Pasternak 49930488704SVadim Pasternak /* 50030488704SVadim Pasternak * It is possible, that some signals have been inserted, while 5014b5e32dfSVadim Pasternak * interrupt has been masked by mlxreg_hotplug_work_handler. In this 5024b5e32dfSVadim Pasternak * case such signals will be missed. In order to handle these signals 5034b5e32dfSVadim Pasternak * delayed work is canceled and work task re-scheduled for immediate 5044b5e32dfSVadim Pasternak * execution. It allows to handle missed signals, if any. In other case 5054b5e32dfSVadim Pasternak * work handler just validates that no new signals have been received 5064b5e32dfSVadim Pasternak * during masking. 50730488704SVadim Pasternak */ 508c6acad68SVadim Pasternak cancel_delayed_work(&priv->dwork_irq); 509c6acad68SVadim Pasternak schedule_delayed_work(&priv->dwork_irq, 0); 51030488704SVadim Pasternak 51130488704SVadim Pasternak spin_unlock_irqrestore(&priv->lock, flags); 51230488704SVadim Pasternak 51330488704SVadim Pasternak return; 51430488704SVadim Pasternak 5154b5e32dfSVadim Pasternak unmask_event: 5164b5e32dfSVadim Pasternak priv->not_asserted++; 51730488704SVadim Pasternak /* Unmask aggregation event (no need acknowledge). */ 518c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, pdata->cell + 519c6acad68SVadim Pasternak MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask); 520c6acad68SVadim Pasternak 521c6acad68SVadim Pasternak out: 522c6acad68SVadim Pasternak if (ret) 523c6acad68SVadim Pasternak dev_err(priv->dev, "Failed to complete workqueue.\n"); 52430488704SVadim Pasternak } 52530488704SVadim Pasternak 526c6acad68SVadim Pasternak static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) 52730488704SVadim Pasternak { 528c6acad68SVadim Pasternak struct mlxreg_core_hotplug_platform_data *pdata; 529c6acad68SVadim Pasternak struct mlxreg_core_item *item; 53083cdb2c1SVadim Pasternak struct mlxreg_core_data *data; 53183cdb2c1SVadim Pasternak u32 regval; 53283cdb2c1SVadim Pasternak int i, j, ret; 53330488704SVadim Pasternak 534c6acad68SVadim Pasternak pdata = dev_get_platdata(&priv->pdev->dev); 535c6acad68SVadim Pasternak item = pdata->items; 53630488704SVadim Pasternak 537c6acad68SVadim Pasternak for (i = 0; i < pdata->counter; i++, item++) { 538c6acad68SVadim Pasternak /* Clear group presense event. */ 539c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, item->reg + 540c6acad68SVadim Pasternak MLXREG_HOTPLUG_EVENT_OFF, 0); 541c6acad68SVadim Pasternak if (ret) 542c6acad68SVadim Pasternak goto out; 543c6acad68SVadim Pasternak 54483cdb2c1SVadim Pasternak /* 54583cdb2c1SVadim Pasternak * Verify if hardware configuration requires to disable 54683cdb2c1SVadim Pasternak * interrupt capability for some of components. 54783cdb2c1SVadim Pasternak */ 54883cdb2c1SVadim Pasternak data = item->data; 54983cdb2c1SVadim Pasternak for (j = 0; j < item->count; j++, data++) { 55083cdb2c1SVadim Pasternak /* Verify if the attribute has capability register. */ 55183cdb2c1SVadim Pasternak if (data->capability) { 55283cdb2c1SVadim Pasternak /* Read capability register. */ 55383cdb2c1SVadim Pasternak ret = regmap_read(priv->regmap, 55483cdb2c1SVadim Pasternak data->capability, ®val); 55583cdb2c1SVadim Pasternak if (ret) 55683cdb2c1SVadim Pasternak goto out; 55783cdb2c1SVadim Pasternak 55883cdb2c1SVadim Pasternak if (!(regval & data->bit)) 55983cdb2c1SVadim Pasternak item->mask &= ~BIT(j); 56083cdb2c1SVadim Pasternak } 56183cdb2c1SVadim Pasternak } 56283cdb2c1SVadim Pasternak 563c6acad68SVadim Pasternak /* Set group initial status as mask and unmask group event. */ 564c6acad68SVadim Pasternak if (item->inversed) { 565c6acad68SVadim Pasternak item->cache = item->mask; 566c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, item->reg + 567c6acad68SVadim Pasternak MLXREG_HOTPLUG_MASK_OFF, 568c6acad68SVadim Pasternak item->mask); 569c6acad68SVadim Pasternak if (ret) 570c6acad68SVadim Pasternak goto out; 571c6acad68SVadim Pasternak } 572c6acad68SVadim Pasternak } 57330488704SVadim Pasternak 57430488704SVadim Pasternak /* Keep aggregation initial status as zero and unmask events. */ 575c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, pdata->cell + 576c6acad68SVadim Pasternak MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask); 577c6acad68SVadim Pasternak if (ret) 578c6acad68SVadim Pasternak goto out; 579c6acad68SVadim Pasternak 580c6acad68SVadim Pasternak /* Keep low aggregation initial status as zero and unmask events. */ 581c6acad68SVadim Pasternak if (pdata->cell_low) { 582c6acad68SVadim Pasternak ret = regmap_write(priv->regmap, pdata->cell_low + 583c6acad68SVadim Pasternak MLXREG_HOTPLUG_AGGR_MASK_OFF, 584c6acad68SVadim Pasternak pdata->mask_low); 585c6acad68SVadim Pasternak if (ret) 586c6acad68SVadim Pasternak goto out; 587c6acad68SVadim Pasternak } 58830488704SVadim Pasternak 58930488704SVadim Pasternak /* Invoke work handler for initializing hot plug devices setting. */ 590c6acad68SVadim Pasternak mlxreg_hotplug_work_handler(&priv->dwork_irq.work); 59130488704SVadim Pasternak 592c6acad68SVadim Pasternak out: 593c6acad68SVadim Pasternak if (ret) 594c6acad68SVadim Pasternak dev_err(priv->dev, "Failed to set interrupts.\n"); 59530488704SVadim Pasternak enable_irq(priv->irq); 596c6acad68SVadim Pasternak return ret; 59730488704SVadim Pasternak } 59830488704SVadim Pasternak 5991f976f69SVadim Pasternak static void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv) 60030488704SVadim Pasternak { 601c6acad68SVadim Pasternak struct mlxreg_core_hotplug_platform_data *pdata; 602c6acad68SVadim Pasternak struct mlxreg_core_item *item; 603c6acad68SVadim Pasternak struct mlxreg_core_data *data; 604c6acad68SVadim Pasternak int count, i, j; 60530488704SVadim Pasternak 606c6acad68SVadim Pasternak pdata = dev_get_platdata(&priv->pdev->dev); 607c6acad68SVadim Pasternak item = pdata->items; 60830488704SVadim Pasternak disable_irq(priv->irq); 609c6acad68SVadim Pasternak cancel_delayed_work_sync(&priv->dwork_irq); 610c6acad68SVadim Pasternak 611c6acad68SVadim Pasternak /* Mask low aggregation event, if defined. */ 612c6acad68SVadim Pasternak if (pdata->cell_low) 613c6acad68SVadim Pasternak regmap_write(priv->regmap, pdata->cell_low + 614c6acad68SVadim Pasternak MLXREG_HOTPLUG_AGGR_MASK_OFF, 0); 61530488704SVadim Pasternak 61630488704SVadim Pasternak /* Mask aggregation event. */ 617c6acad68SVadim Pasternak regmap_write(priv->regmap, pdata->cell + MLXREG_HOTPLUG_AGGR_MASK_OFF, 618c6acad68SVadim Pasternak 0); 61930488704SVadim Pasternak 620c6acad68SVadim Pasternak /* Clear topology configurations. */ 621c6acad68SVadim Pasternak for (i = 0; i < pdata->counter; i++, item++) { 622c6acad68SVadim Pasternak data = item->data; 623c6acad68SVadim Pasternak /* Mask group presense event. */ 624c6acad68SVadim Pasternak regmap_write(priv->regmap, data->reg + MLXREG_HOTPLUG_MASK_OFF, 625c6acad68SVadim Pasternak 0); 626c6acad68SVadim Pasternak /* Clear group presense event. */ 627c6acad68SVadim Pasternak regmap_write(priv->regmap, data->reg + 628c6acad68SVadim Pasternak MLXREG_HOTPLUG_EVENT_OFF, 0); 62930488704SVadim Pasternak 630c6acad68SVadim Pasternak /* Remove all the attached devices in group. */ 631c6acad68SVadim Pasternak count = item->count; 632c6acad68SVadim Pasternak for (j = 0; j < count; j++, data++) 6339272d2d1SVadim Pasternak mlxreg_hotplug_device_destroy(priv, data); 634c6acad68SVadim Pasternak } 63530488704SVadim Pasternak } 63630488704SVadim Pasternak 6371f976f69SVadim Pasternak static irqreturn_t mlxreg_hotplug_irq_handler(int irq, void *dev) 63830488704SVadim Pasternak { 639c6acad68SVadim Pasternak struct mlxreg_hotplug_priv_data *priv; 640c6acad68SVadim Pasternak 641c6acad68SVadim Pasternak priv = (struct mlxreg_hotplug_priv_data *)dev; 64230488704SVadim Pasternak 64330488704SVadim Pasternak /* Schedule work task for immediate execution.*/ 644c6acad68SVadim Pasternak schedule_delayed_work(&priv->dwork_irq, 0); 64530488704SVadim Pasternak 64630488704SVadim Pasternak return IRQ_HANDLED; 64730488704SVadim Pasternak } 64830488704SVadim Pasternak 6491f976f69SVadim Pasternak static int mlxreg_hotplug_probe(struct platform_device *pdev) 65030488704SVadim Pasternak { 651c6acad68SVadim Pasternak struct mlxreg_core_hotplug_platform_data *pdata; 6521f976f69SVadim Pasternak struct mlxreg_hotplug_priv_data *priv; 653d726f6b1SVadim Pasternak struct i2c_adapter *deferred_adap; 65430488704SVadim Pasternak int err; 65530488704SVadim Pasternak 65630488704SVadim Pasternak pdata = dev_get_platdata(&pdev->dev); 65730488704SVadim Pasternak if (!pdata) { 65830488704SVadim Pasternak dev_err(&pdev->dev, "Failed to get platform data.\n"); 65930488704SVadim Pasternak return -EINVAL; 66030488704SVadim Pasternak } 66130488704SVadim Pasternak 662d726f6b1SVadim Pasternak /* Defer probing if the necessary adapter is not configured yet. */ 663d726f6b1SVadim Pasternak deferred_adap = i2c_get_adapter(pdata->deferred_nr); 664d726f6b1SVadim Pasternak if (!deferred_adap) 665d726f6b1SVadim Pasternak return -EPROBE_DEFER; 666d726f6b1SVadim Pasternak i2c_put_adapter(deferred_adap); 667d726f6b1SVadim Pasternak 66830488704SVadim Pasternak priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 66930488704SVadim Pasternak if (!priv) 67030488704SVadim Pasternak return -ENOMEM; 67130488704SVadim Pasternak 672c6acad68SVadim Pasternak if (pdata->irq) { 673c6acad68SVadim Pasternak priv->irq = pdata->irq; 674c6acad68SVadim Pasternak } else { 67530488704SVadim Pasternak priv->irq = platform_get_irq(pdev, 0); 676eaae882cSStephen Boyd if (priv->irq < 0) 67730488704SVadim Pasternak return priv->irq; 67830488704SVadim Pasternak } 679c6acad68SVadim Pasternak 680c6acad68SVadim Pasternak priv->regmap = pdata->regmap; 681c6acad68SVadim Pasternak priv->dev = pdev->dev.parent; 682c6acad68SVadim Pasternak priv->pdev = pdev; 68330488704SVadim Pasternak 68430488704SVadim Pasternak err = devm_request_irq(&pdev->dev, priv->irq, 685c6acad68SVadim Pasternak mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING 686c6acad68SVadim Pasternak | IRQF_SHARED, "mlxreg-hotplug", priv); 68730488704SVadim Pasternak if (err) { 68830488704SVadim Pasternak dev_err(&pdev->dev, "Failed to request irq: %d\n", err); 68930488704SVadim Pasternak return err; 69030488704SVadim Pasternak } 69130488704SVadim Pasternak 692c6acad68SVadim Pasternak disable_irq(priv->irq); 69330488704SVadim Pasternak spin_lock_init(&priv->lock); 694c6acad68SVadim Pasternak INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler); 695c6acad68SVadim Pasternak dev_set_drvdata(&pdev->dev, priv); 69630488704SVadim Pasternak 6971f976f69SVadim Pasternak err = mlxreg_hotplug_attr_init(priv); 69830488704SVadim Pasternak if (err) { 699c6acad68SVadim Pasternak dev_err(&pdev->dev, "Failed to allocate attributes: %d\n", 700c6acad68SVadim Pasternak err); 70130488704SVadim Pasternak return err; 70230488704SVadim Pasternak } 70330488704SVadim Pasternak 70430488704SVadim Pasternak priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, 7051f976f69SVadim Pasternak "mlxreg_hotplug", priv, priv->groups); 70630488704SVadim Pasternak if (IS_ERR(priv->hwmon)) { 70730488704SVadim Pasternak dev_err(&pdev->dev, "Failed to register hwmon device %ld\n", 70830488704SVadim Pasternak PTR_ERR(priv->hwmon)); 70930488704SVadim Pasternak return PTR_ERR(priv->hwmon); 71030488704SVadim Pasternak } 71130488704SVadim Pasternak 7129272d2d1SVadim Pasternak /* Perform initial interrupts setup. */ 7139272d2d1SVadim Pasternak mlxreg_hotplug_set_irq(priv); 7149272d2d1SVadim Pasternak priv->after_probe = true; 7159272d2d1SVadim Pasternak 71630488704SVadim Pasternak return 0; 71730488704SVadim Pasternak } 71830488704SVadim Pasternak 7191f976f69SVadim Pasternak static int mlxreg_hotplug_remove(struct platform_device *pdev) 72030488704SVadim Pasternak { 721c6acad68SVadim Pasternak struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(&pdev->dev); 72230488704SVadim Pasternak 72330488704SVadim Pasternak /* Clean interrupts setup. */ 7241f976f69SVadim Pasternak mlxreg_hotplug_unset_irq(priv); 7258c2eb7b6SVadim Pasternak devm_free_irq(&pdev->dev, priv->irq, priv); 72630488704SVadim Pasternak 72730488704SVadim Pasternak return 0; 72830488704SVadim Pasternak } 72930488704SVadim Pasternak 7301f976f69SVadim Pasternak static struct platform_driver mlxreg_hotplug_driver = { 73130488704SVadim Pasternak .driver = { 7321f976f69SVadim Pasternak .name = "mlxreg-hotplug", 73330488704SVadim Pasternak }, 7341f976f69SVadim Pasternak .probe = mlxreg_hotplug_probe, 7351f976f69SVadim Pasternak .remove = mlxreg_hotplug_remove, 73630488704SVadim Pasternak }; 73730488704SVadim Pasternak 7381f976f69SVadim Pasternak module_platform_driver(mlxreg_hotplug_driver); 73930488704SVadim Pasternak 74030488704SVadim Pasternak MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); 7411f976f69SVadim Pasternak MODULE_DESCRIPTION("Mellanox regmap hotplug platform driver"); 74230488704SVadim Pasternak MODULE_LICENSE("Dual BSD/GPL"); 7431f976f69SVadim Pasternak MODULE_ALIAS("platform:mlxreg-hotplug"); 744