18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Mellanox hotplug driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2016-2020 Mellanox Technologies 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/bitops.h> 98c2ecf20Sopenharmony_ci#include <linux/device.h> 108c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 118c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h> 128c2ecf20Sopenharmony_ci#include <linux/i2c.h> 138c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/of_device.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_data/mlxreg.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 198c2ecf20Sopenharmony_ci#include <linux/string_helpers.h> 208c2ecf20Sopenharmony_ci#include <linux/regmap.h> 218c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* Offset of event and mask registers from status register. */ 248c2ecf20Sopenharmony_ci#define MLXREG_HOTPLUG_EVENT_OFF 1 258c2ecf20Sopenharmony_ci#define MLXREG_HOTPLUG_MASK_OFF 2 268c2ecf20Sopenharmony_ci#define MLXREG_HOTPLUG_AGGR_MASK_OFF 1 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* ASIC good health mask. */ 298c2ecf20Sopenharmony_ci#define MLXREG_HOTPLUG_GOOD_HEALTH_MASK 0x02 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define MLXREG_HOTPLUG_ATTRS_MAX 24 328c2ecf20Sopenharmony_ci#define MLXREG_HOTPLUG_NOT_ASSERT 3 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci/** 358c2ecf20Sopenharmony_ci * struct mlxreg_hotplug_priv_data - platform private data: 368c2ecf20Sopenharmony_ci * @irq: platform device interrupt number; 378c2ecf20Sopenharmony_ci * @dev: basic device; 388c2ecf20Sopenharmony_ci * @pdev: platform device; 398c2ecf20Sopenharmony_ci * @plat: platform data; 408c2ecf20Sopenharmony_ci * @regmap: register map handle; 418c2ecf20Sopenharmony_ci * @dwork_irq: delayed work template; 428c2ecf20Sopenharmony_ci * @lock: spin lock; 438c2ecf20Sopenharmony_ci * @hwmon: hwmon device; 448c2ecf20Sopenharmony_ci * @mlxreg_hotplug_attr: sysfs attributes array; 458c2ecf20Sopenharmony_ci * @mlxreg_hotplug_dev_attr: sysfs sensor device attribute array; 468c2ecf20Sopenharmony_ci * @group: sysfs attribute group; 478c2ecf20Sopenharmony_ci * @groups: list of sysfs attribute group for hwmon registration; 488c2ecf20Sopenharmony_ci * @cell: location of top aggregation interrupt register; 498c2ecf20Sopenharmony_ci * @mask: top aggregation interrupt common mask; 508c2ecf20Sopenharmony_ci * @aggr_cache: last value of aggregation register status; 518c2ecf20Sopenharmony_ci * @after_probe: flag indication probing completion; 528c2ecf20Sopenharmony_ci * @not_asserted: number of entries in workqueue with no signal assertion; 538c2ecf20Sopenharmony_ci */ 548c2ecf20Sopenharmony_cistruct mlxreg_hotplug_priv_data { 558c2ecf20Sopenharmony_ci int irq; 568c2ecf20Sopenharmony_ci struct device *dev; 578c2ecf20Sopenharmony_ci struct platform_device *pdev; 588c2ecf20Sopenharmony_ci struct mlxreg_hotplug_platform_data *plat; 598c2ecf20Sopenharmony_ci struct regmap *regmap; 608c2ecf20Sopenharmony_ci struct delayed_work dwork_irq; 618c2ecf20Sopenharmony_ci spinlock_t lock; /* sync with interrupt */ 628c2ecf20Sopenharmony_ci struct device *hwmon; 638c2ecf20Sopenharmony_ci struct attribute *mlxreg_hotplug_attr[MLXREG_HOTPLUG_ATTRS_MAX + 1]; 648c2ecf20Sopenharmony_ci struct sensor_device_attribute_2 658c2ecf20Sopenharmony_ci mlxreg_hotplug_dev_attr[MLXREG_HOTPLUG_ATTRS_MAX]; 668c2ecf20Sopenharmony_ci struct attribute_group group; 678c2ecf20Sopenharmony_ci const struct attribute_group *groups[2]; 688c2ecf20Sopenharmony_ci u32 cell; 698c2ecf20Sopenharmony_ci u32 mask; 708c2ecf20Sopenharmony_ci u32 aggr_cache; 718c2ecf20Sopenharmony_ci bool after_probe; 728c2ecf20Sopenharmony_ci u8 not_asserted; 738c2ecf20Sopenharmony_ci}; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci/* Environment variables array for udev. */ 768c2ecf20Sopenharmony_cistatic char *mlxreg_hotplug_udev_envp[] = { NULL, NULL }; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic int 798c2ecf20Sopenharmony_cimlxreg_hotplug_udev_event_send(struct kobject *kobj, 808c2ecf20Sopenharmony_ci struct mlxreg_core_data *data, bool action) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci char event_str[MLXREG_CORE_LABEL_MAX_SIZE + 2]; 838c2ecf20Sopenharmony_ci char label[MLXREG_CORE_LABEL_MAX_SIZE] = { 0 }; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci mlxreg_hotplug_udev_envp[0] = event_str; 868c2ecf20Sopenharmony_ci string_upper(label, data->label); 878c2ecf20Sopenharmony_ci snprintf(event_str, MLXREG_CORE_LABEL_MAX_SIZE, "%s=%d", label, !!action); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci return kobject_uevent_env(kobj, KOBJ_CHANGE, mlxreg_hotplug_udev_envp); 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic int mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data *priv, 938c2ecf20Sopenharmony_ci struct mlxreg_core_data *data) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 968c2ecf20Sopenharmony_ci struct i2c_client *client; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci /* Notify user by sending hwmon uevent. */ 998c2ecf20Sopenharmony_ci mlxreg_hotplug_udev_event_send(&priv->hwmon->kobj, data, true); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci /* 1028c2ecf20Sopenharmony_ci * Return if adapter number is negative. It could be in case hotplug 1038c2ecf20Sopenharmony_ci * event is not associated with hotplug device. 1048c2ecf20Sopenharmony_ci */ 1058c2ecf20Sopenharmony_ci if (data->hpdev.nr < 0) 1068c2ecf20Sopenharmony_ci return 0; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 1098c2ecf20Sopenharmony_ci data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr + 1108c2ecf20Sopenharmony_ci pdata->shift_nr); 1118c2ecf20Sopenharmony_ci if (!data->hpdev.adapter) { 1128c2ecf20Sopenharmony_ci dev_err(priv->dev, "Failed to get adapter for bus %d\n", 1138c2ecf20Sopenharmony_ci data->hpdev.nr + pdata->shift_nr); 1148c2ecf20Sopenharmony_ci return -EFAULT; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci client = i2c_new_client_device(data->hpdev.adapter, 1188c2ecf20Sopenharmony_ci data->hpdev.brdinfo); 1198c2ecf20Sopenharmony_ci if (IS_ERR(client)) { 1208c2ecf20Sopenharmony_ci dev_err(priv->dev, "Failed to create client %s at bus %d at addr 0x%02x\n", 1218c2ecf20Sopenharmony_ci data->hpdev.brdinfo->type, data->hpdev.nr + 1228c2ecf20Sopenharmony_ci pdata->shift_nr, data->hpdev.brdinfo->addr); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci i2c_put_adapter(data->hpdev.adapter); 1258c2ecf20Sopenharmony_ci data->hpdev.adapter = NULL; 1268c2ecf20Sopenharmony_ci return PTR_ERR(client); 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci data->hpdev.client = client; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci return 0; 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic void 1358c2ecf20Sopenharmony_cimlxreg_hotplug_device_destroy(struct mlxreg_hotplug_priv_data *priv, 1368c2ecf20Sopenharmony_ci struct mlxreg_core_data *data) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci /* Notify user by sending hwmon uevent. */ 1398c2ecf20Sopenharmony_ci mlxreg_hotplug_udev_event_send(&priv->hwmon->kobj, data, false); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (data->hpdev.client) { 1428c2ecf20Sopenharmony_ci i2c_unregister_device(data->hpdev.client); 1438c2ecf20Sopenharmony_ci data->hpdev.client = NULL; 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci if (data->hpdev.adapter) { 1478c2ecf20Sopenharmony_ci i2c_put_adapter(data->hpdev.adapter); 1488c2ecf20Sopenharmony_ci data->hpdev.adapter = NULL; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic ssize_t mlxreg_hotplug_attr_show(struct device *dev, 1538c2ecf20Sopenharmony_ci struct device_attribute *attr, 1548c2ecf20Sopenharmony_ci char *buf) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(dev); 1578c2ecf20Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 1588c2ecf20Sopenharmony_ci int index = to_sensor_dev_attr_2(attr)->index; 1598c2ecf20Sopenharmony_ci int nr = to_sensor_dev_attr_2(attr)->nr; 1608c2ecf20Sopenharmony_ci struct mlxreg_core_item *item; 1618c2ecf20Sopenharmony_ci struct mlxreg_core_data *data; 1628c2ecf20Sopenharmony_ci u32 regval; 1638c2ecf20Sopenharmony_ci int ret; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 1668c2ecf20Sopenharmony_ci item = pdata->items + nr; 1678c2ecf20Sopenharmony_ci data = item->data + index; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci ret = regmap_read(priv->regmap, data->reg, ®val); 1708c2ecf20Sopenharmony_ci if (ret) 1718c2ecf20Sopenharmony_ci return ret; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci if (item->health) { 1748c2ecf20Sopenharmony_ci regval &= data->mask; 1758c2ecf20Sopenharmony_ci } else { 1768c2ecf20Sopenharmony_ci /* Bit = 0 : functional if item->inversed is true. */ 1778c2ecf20Sopenharmony_ci if (item->inversed) 1788c2ecf20Sopenharmony_ci regval = !(regval & data->mask); 1798c2ecf20Sopenharmony_ci else 1808c2ecf20Sopenharmony_ci regval = !!(regval & data->mask); 1818c2ecf20Sopenharmony_ci } 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci return sprintf(buf, "%u\n", regval); 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci#define PRIV_ATTR(i) priv->mlxreg_hotplug_attr[i] 1878c2ecf20Sopenharmony_ci#define PRIV_DEV_ATTR(i) priv->mlxreg_hotplug_dev_attr[i] 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_cistatic int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 1928c2ecf20Sopenharmony_ci struct mlxreg_core_item *item; 1938c2ecf20Sopenharmony_ci struct mlxreg_core_data *data; 1948c2ecf20Sopenharmony_ci unsigned long mask; 1958c2ecf20Sopenharmony_ci u32 regval; 1968c2ecf20Sopenharmony_ci int num_attrs = 0, id = 0, i, j, k, ret; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 1998c2ecf20Sopenharmony_ci item = pdata->items; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci /* Go over all kinds of items - psu, pwr, fan. */ 2028c2ecf20Sopenharmony_ci for (i = 0; i < pdata->counter; i++, item++) { 2038c2ecf20Sopenharmony_ci if (item->capability) { 2048c2ecf20Sopenharmony_ci /* 2058c2ecf20Sopenharmony_ci * Read group capability register to get actual number 2068c2ecf20Sopenharmony_ci * of interrupt capable components and set group mask 2078c2ecf20Sopenharmony_ci * accordingly. 2088c2ecf20Sopenharmony_ci */ 2098c2ecf20Sopenharmony_ci ret = regmap_read(priv->regmap, item->capability, 2108c2ecf20Sopenharmony_ci ®val); 2118c2ecf20Sopenharmony_ci if (ret) 2128c2ecf20Sopenharmony_ci return ret; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci item->mask = GENMASK((regval & item->mask) - 1, 0); 2158c2ecf20Sopenharmony_ci } 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci data = item->data; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci /* Go over all unmasked units within item. */ 2208c2ecf20Sopenharmony_ci mask = item->mask; 2218c2ecf20Sopenharmony_ci k = 0; 2228c2ecf20Sopenharmony_ci for_each_set_bit(j, &mask, item->count) { 2238c2ecf20Sopenharmony_ci if (data->capability) { 2248c2ecf20Sopenharmony_ci /* 2258c2ecf20Sopenharmony_ci * Read capability register and skip non 2268c2ecf20Sopenharmony_ci * relevant attributes. 2278c2ecf20Sopenharmony_ci */ 2288c2ecf20Sopenharmony_ci ret = regmap_read(priv->regmap, 2298c2ecf20Sopenharmony_ci data->capability, ®val); 2308c2ecf20Sopenharmony_ci if (ret) 2318c2ecf20Sopenharmony_ci return ret; 2328c2ecf20Sopenharmony_ci if (!(regval & data->bit)) { 2338c2ecf20Sopenharmony_ci data++; 2348c2ecf20Sopenharmony_ci continue; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr; 2388c2ecf20Sopenharmony_ci PRIV_ATTR(id)->name = devm_kasprintf(&priv->pdev->dev, 2398c2ecf20Sopenharmony_ci GFP_KERNEL, 2408c2ecf20Sopenharmony_ci data->label); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci if (!PRIV_ATTR(id)->name) { 2438c2ecf20Sopenharmony_ci dev_err(priv->dev, "Memory allocation failed for attr %d.\n", 2448c2ecf20Sopenharmony_ci id); 2458c2ecf20Sopenharmony_ci return -ENOMEM; 2468c2ecf20Sopenharmony_ci } 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci PRIV_DEV_ATTR(id).dev_attr.attr.name = 2498c2ecf20Sopenharmony_ci PRIV_ATTR(id)->name; 2508c2ecf20Sopenharmony_ci PRIV_DEV_ATTR(id).dev_attr.attr.mode = 0444; 2518c2ecf20Sopenharmony_ci PRIV_DEV_ATTR(id).dev_attr.show = 2528c2ecf20Sopenharmony_ci mlxreg_hotplug_attr_show; 2538c2ecf20Sopenharmony_ci PRIV_DEV_ATTR(id).nr = i; 2548c2ecf20Sopenharmony_ci PRIV_DEV_ATTR(id).index = k; 2558c2ecf20Sopenharmony_ci sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr); 2568c2ecf20Sopenharmony_ci data++; 2578c2ecf20Sopenharmony_ci id++; 2588c2ecf20Sopenharmony_ci k++; 2598c2ecf20Sopenharmony_ci } 2608c2ecf20Sopenharmony_ci num_attrs += k; 2618c2ecf20Sopenharmony_ci } 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci priv->group.attrs = devm_kcalloc(&priv->pdev->dev, 2648c2ecf20Sopenharmony_ci num_attrs, 2658c2ecf20Sopenharmony_ci sizeof(struct attribute *), 2668c2ecf20Sopenharmony_ci GFP_KERNEL); 2678c2ecf20Sopenharmony_ci if (!priv->group.attrs) 2688c2ecf20Sopenharmony_ci return -ENOMEM; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci priv->group.attrs = priv->mlxreg_hotplug_attr; 2718c2ecf20Sopenharmony_ci priv->groups[0] = &priv->group; 2728c2ecf20Sopenharmony_ci priv->groups[1] = NULL; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci return 0; 2758c2ecf20Sopenharmony_ci} 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_cistatic void 2788c2ecf20Sopenharmony_cimlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv, 2798c2ecf20Sopenharmony_ci struct mlxreg_core_item *item) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci struct mlxreg_core_data *data; 2828c2ecf20Sopenharmony_ci unsigned long asserted; 2838c2ecf20Sopenharmony_ci u32 regval, bit; 2848c2ecf20Sopenharmony_ci int ret; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci /* 2878c2ecf20Sopenharmony_ci * Validate if item related to received signal type is valid. 2888c2ecf20Sopenharmony_ci * It should never happen, excepted the situation when some 2898c2ecf20Sopenharmony_ci * piece of hardware is broken. In such situation just produce 2908c2ecf20Sopenharmony_ci * error message and return. Caller must continue to handle the 2918c2ecf20Sopenharmony_ci * signals from other devices if any. 2928c2ecf20Sopenharmony_ci */ 2938c2ecf20Sopenharmony_ci if (unlikely(!item)) { 2948c2ecf20Sopenharmony_ci dev_err(priv->dev, "False signal: at offset:mask 0x%02x:0x%02x.\n", 2958c2ecf20Sopenharmony_ci item->reg, item->mask); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci return; 2988c2ecf20Sopenharmony_ci } 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci /* Mask event. */ 3018c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, 3028c2ecf20Sopenharmony_ci 0); 3038c2ecf20Sopenharmony_ci if (ret) 3048c2ecf20Sopenharmony_ci goto out; 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci /* Read status. */ 3078c2ecf20Sopenharmony_ci ret = regmap_read(priv->regmap, item->reg, ®val); 3088c2ecf20Sopenharmony_ci if (ret) 3098c2ecf20Sopenharmony_ci goto out; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci /* Set asserted bits and save last status. */ 3128c2ecf20Sopenharmony_ci regval &= item->mask; 3138c2ecf20Sopenharmony_ci asserted = item->cache ^ regval; 3148c2ecf20Sopenharmony_ci item->cache = regval; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci for_each_set_bit(bit, &asserted, 8) { 3178c2ecf20Sopenharmony_ci data = item->data + bit; 3188c2ecf20Sopenharmony_ci if (regval & BIT(bit)) { 3198c2ecf20Sopenharmony_ci if (item->inversed) 3208c2ecf20Sopenharmony_ci mlxreg_hotplug_device_destroy(priv, data); 3218c2ecf20Sopenharmony_ci else 3228c2ecf20Sopenharmony_ci mlxreg_hotplug_device_create(priv, data); 3238c2ecf20Sopenharmony_ci } else { 3248c2ecf20Sopenharmony_ci if (item->inversed) 3258c2ecf20Sopenharmony_ci mlxreg_hotplug_device_create(priv, data); 3268c2ecf20Sopenharmony_ci else 3278c2ecf20Sopenharmony_ci mlxreg_hotplug_device_destroy(priv, data); 3288c2ecf20Sopenharmony_ci } 3298c2ecf20Sopenharmony_ci } 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci /* Acknowledge event. */ 3328c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_EVENT_OFF, 3338c2ecf20Sopenharmony_ci 0); 3348c2ecf20Sopenharmony_ci if (ret) 3358c2ecf20Sopenharmony_ci goto out; 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci /* Unmask event. */ 3388c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, 3398c2ecf20Sopenharmony_ci item->mask); 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci out: 3428c2ecf20Sopenharmony_ci if (ret) 3438c2ecf20Sopenharmony_ci dev_err(priv->dev, "Failed to complete workqueue.\n"); 3448c2ecf20Sopenharmony_ci} 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_cistatic void 3478c2ecf20Sopenharmony_cimlxreg_hotplug_health_work_helper(struct mlxreg_hotplug_priv_data *priv, 3488c2ecf20Sopenharmony_ci struct mlxreg_core_item *item) 3498c2ecf20Sopenharmony_ci{ 3508c2ecf20Sopenharmony_ci struct mlxreg_core_data *data = item->data; 3518c2ecf20Sopenharmony_ci u32 regval; 3528c2ecf20Sopenharmony_ci int i, ret = 0; 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_ci for (i = 0; i < item->count; i++, data++) { 3558c2ecf20Sopenharmony_ci /* Mask event. */ 3568c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, data->reg + 3578c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_MASK_OFF, 0); 3588c2ecf20Sopenharmony_ci if (ret) 3598c2ecf20Sopenharmony_ci goto out; 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci /* Read status. */ 3628c2ecf20Sopenharmony_ci ret = regmap_read(priv->regmap, data->reg, ®val); 3638c2ecf20Sopenharmony_ci if (ret) 3648c2ecf20Sopenharmony_ci goto out; 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci regval &= data->mask; 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci if (item->cache == regval) 3698c2ecf20Sopenharmony_ci goto ack_event; 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci /* 3728c2ecf20Sopenharmony_ci * ASIC health indication is provided through two bits. Bits 3738c2ecf20Sopenharmony_ci * value 0x2 indicates that ASIC reached the good health, value 3748c2ecf20Sopenharmony_ci * 0x0 indicates ASIC the bad health or dormant state and value 3758c2ecf20Sopenharmony_ci * 0x3 indicates the booting state. During ASIC reset it should 3768c2ecf20Sopenharmony_ci * pass the following states: dormant -> booting -> good. 3778c2ecf20Sopenharmony_ci */ 3788c2ecf20Sopenharmony_ci if (regval == MLXREG_HOTPLUG_GOOD_HEALTH_MASK) { 3798c2ecf20Sopenharmony_ci if (!data->attached) { 3808c2ecf20Sopenharmony_ci /* 3818c2ecf20Sopenharmony_ci * ASIC is in steady state. Connect associated 3828c2ecf20Sopenharmony_ci * device, if configured. 3838c2ecf20Sopenharmony_ci */ 3848c2ecf20Sopenharmony_ci mlxreg_hotplug_device_create(priv, data); 3858c2ecf20Sopenharmony_ci data->attached = true; 3868c2ecf20Sopenharmony_ci } 3878c2ecf20Sopenharmony_ci } else { 3888c2ecf20Sopenharmony_ci if (data->attached) { 3898c2ecf20Sopenharmony_ci /* 3908c2ecf20Sopenharmony_ci * ASIC health is failed after ASIC has been 3918c2ecf20Sopenharmony_ci * in steady state. Disconnect associated 3928c2ecf20Sopenharmony_ci * device, if it has been connected. 3938c2ecf20Sopenharmony_ci */ 3948c2ecf20Sopenharmony_ci mlxreg_hotplug_device_destroy(priv, data); 3958c2ecf20Sopenharmony_ci data->attached = false; 3968c2ecf20Sopenharmony_ci data->health_cntr = 0; 3978c2ecf20Sopenharmony_ci } 3988c2ecf20Sopenharmony_ci } 3998c2ecf20Sopenharmony_ci item->cache = regval; 4008c2ecf20Sopenharmony_ciack_event: 4018c2ecf20Sopenharmony_ci /* Acknowledge event. */ 4028c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, data->reg + 4038c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_EVENT_OFF, 0); 4048c2ecf20Sopenharmony_ci if (ret) 4058c2ecf20Sopenharmony_ci goto out; 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci /* Unmask event. */ 4088c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, data->reg + 4098c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_MASK_OFF, data->mask); 4108c2ecf20Sopenharmony_ci if (ret) 4118c2ecf20Sopenharmony_ci goto out; 4128c2ecf20Sopenharmony_ci } 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci out: 4158c2ecf20Sopenharmony_ci if (ret) 4168c2ecf20Sopenharmony_ci dev_err(priv->dev, "Failed to complete workqueue.\n"); 4178c2ecf20Sopenharmony_ci} 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_ci/* 4208c2ecf20Sopenharmony_ci * mlxreg_hotplug_work_handler - performs traversing of device interrupt 4218c2ecf20Sopenharmony_ci * registers according to the below hierarchy schema: 4228c2ecf20Sopenharmony_ci * 4238c2ecf20Sopenharmony_ci * Aggregation registers (status/mask) 4248c2ecf20Sopenharmony_ci * PSU registers: *---* 4258c2ecf20Sopenharmony_ci * *-----------------* | | 4268c2ecf20Sopenharmony_ci * |status/event/mask|-----> | * | 4278c2ecf20Sopenharmony_ci * *-----------------* | | 4288c2ecf20Sopenharmony_ci * Power registers: | | 4298c2ecf20Sopenharmony_ci * *-----------------* | | 4308c2ecf20Sopenharmony_ci * |status/event/mask|-----> | * | 4318c2ecf20Sopenharmony_ci * *-----------------* | | 4328c2ecf20Sopenharmony_ci * FAN registers: | |--> CPU 4338c2ecf20Sopenharmony_ci * *-----------------* | | 4348c2ecf20Sopenharmony_ci * |status/event/mask|-----> | * | 4358c2ecf20Sopenharmony_ci * *-----------------* | | 4368c2ecf20Sopenharmony_ci * ASIC registers: | | 4378c2ecf20Sopenharmony_ci * *-----------------* | | 4388c2ecf20Sopenharmony_ci * |status/event/mask|-----> | * | 4398c2ecf20Sopenharmony_ci * *-----------------* | | 4408c2ecf20Sopenharmony_ci * *---* 4418c2ecf20Sopenharmony_ci * 4428c2ecf20Sopenharmony_ci * In case some system changed are detected: FAN in/out, PSU in/out, power 4438c2ecf20Sopenharmony_ci * cable attached/detached, ASIC health good/bad, relevant device is created 4448c2ecf20Sopenharmony_ci * or destroyed. 4458c2ecf20Sopenharmony_ci */ 4468c2ecf20Sopenharmony_cistatic void mlxreg_hotplug_work_handler(struct work_struct *work) 4478c2ecf20Sopenharmony_ci{ 4488c2ecf20Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 4498c2ecf20Sopenharmony_ci struct mlxreg_hotplug_priv_data *priv; 4508c2ecf20Sopenharmony_ci struct mlxreg_core_item *item; 4518c2ecf20Sopenharmony_ci u32 regval, aggr_asserted; 4528c2ecf20Sopenharmony_ci unsigned long flags; 4538c2ecf20Sopenharmony_ci int i, ret; 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci priv = container_of(work, struct mlxreg_hotplug_priv_data, 4568c2ecf20Sopenharmony_ci dwork_irq.work); 4578c2ecf20Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 4588c2ecf20Sopenharmony_ci item = pdata->items; 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_ci /* Mask aggregation event. */ 4618c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, pdata->cell + 4628c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_AGGR_MASK_OFF, 0); 4638c2ecf20Sopenharmony_ci if (ret < 0) 4648c2ecf20Sopenharmony_ci goto out; 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci /* Read aggregation status. */ 4678c2ecf20Sopenharmony_ci ret = regmap_read(priv->regmap, pdata->cell, ®val); 4688c2ecf20Sopenharmony_ci if (ret) 4698c2ecf20Sopenharmony_ci goto out; 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_ci regval &= pdata->mask; 4728c2ecf20Sopenharmony_ci aggr_asserted = priv->aggr_cache ^ regval; 4738c2ecf20Sopenharmony_ci priv->aggr_cache = regval; 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci /* 4768c2ecf20Sopenharmony_ci * Handler is invoked, but no assertion is detected at top aggregation 4778c2ecf20Sopenharmony_ci * status level. Set aggr_asserted to mask value to allow handler extra 4788c2ecf20Sopenharmony_ci * run over all relevant signals to recover any missed signal. 4798c2ecf20Sopenharmony_ci */ 4808c2ecf20Sopenharmony_ci if (priv->not_asserted == MLXREG_HOTPLUG_NOT_ASSERT) { 4818c2ecf20Sopenharmony_ci priv->not_asserted = 0; 4828c2ecf20Sopenharmony_ci aggr_asserted = pdata->mask; 4838c2ecf20Sopenharmony_ci } 4848c2ecf20Sopenharmony_ci if (!aggr_asserted) 4858c2ecf20Sopenharmony_ci goto unmask_event; 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_ci /* Handle topology and health configuration changes. */ 4888c2ecf20Sopenharmony_ci for (i = 0; i < pdata->counter; i++, item++) { 4898c2ecf20Sopenharmony_ci if (aggr_asserted & item->aggr_mask) { 4908c2ecf20Sopenharmony_ci if (item->health) 4918c2ecf20Sopenharmony_ci mlxreg_hotplug_health_work_helper(priv, item); 4928c2ecf20Sopenharmony_ci else 4938c2ecf20Sopenharmony_ci mlxreg_hotplug_work_helper(priv, item); 4948c2ecf20Sopenharmony_ci } 4958c2ecf20Sopenharmony_ci } 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->lock, flags); 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci /* 5008c2ecf20Sopenharmony_ci * It is possible, that some signals have been inserted, while 5018c2ecf20Sopenharmony_ci * interrupt has been masked by mlxreg_hotplug_work_handler. In this 5028c2ecf20Sopenharmony_ci * case such signals will be missed. In order to handle these signals 5038c2ecf20Sopenharmony_ci * delayed work is canceled and work task re-scheduled for immediate 5048c2ecf20Sopenharmony_ci * execution. It allows to handle missed signals, if any. In other case 5058c2ecf20Sopenharmony_ci * work handler just validates that no new signals have been received 5068c2ecf20Sopenharmony_ci * during masking. 5078c2ecf20Sopenharmony_ci */ 5088c2ecf20Sopenharmony_ci cancel_delayed_work(&priv->dwork_irq); 5098c2ecf20Sopenharmony_ci schedule_delayed_work(&priv->dwork_irq, 0); 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->lock, flags); 5128c2ecf20Sopenharmony_ci 5138c2ecf20Sopenharmony_ci return; 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ciunmask_event: 5168c2ecf20Sopenharmony_ci priv->not_asserted++; 5178c2ecf20Sopenharmony_ci /* Unmask aggregation event (no need acknowledge). */ 5188c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, pdata->cell + 5198c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask); 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci out: 5228c2ecf20Sopenharmony_ci if (ret) 5238c2ecf20Sopenharmony_ci dev_err(priv->dev, "Failed to complete workqueue.\n"); 5248c2ecf20Sopenharmony_ci} 5258c2ecf20Sopenharmony_ci 5268c2ecf20Sopenharmony_cistatic int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) 5278c2ecf20Sopenharmony_ci{ 5288c2ecf20Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 5298c2ecf20Sopenharmony_ci struct mlxreg_core_item *item; 5308c2ecf20Sopenharmony_ci struct mlxreg_core_data *data; 5318c2ecf20Sopenharmony_ci u32 regval; 5328c2ecf20Sopenharmony_ci int i, j, ret; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 5358c2ecf20Sopenharmony_ci item = pdata->items; 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci for (i = 0; i < pdata->counter; i++, item++) { 5388c2ecf20Sopenharmony_ci /* Clear group presense event. */ 5398c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, item->reg + 5408c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_EVENT_OFF, 0); 5418c2ecf20Sopenharmony_ci if (ret) 5428c2ecf20Sopenharmony_ci goto out; 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci /* 5458c2ecf20Sopenharmony_ci * Verify if hardware configuration requires to disable 5468c2ecf20Sopenharmony_ci * interrupt capability for some of components. 5478c2ecf20Sopenharmony_ci */ 5488c2ecf20Sopenharmony_ci data = item->data; 5498c2ecf20Sopenharmony_ci for (j = 0; j < item->count; j++, data++) { 5508c2ecf20Sopenharmony_ci /* Verify if the attribute has capability register. */ 5518c2ecf20Sopenharmony_ci if (data->capability) { 5528c2ecf20Sopenharmony_ci /* Read capability register. */ 5538c2ecf20Sopenharmony_ci ret = regmap_read(priv->regmap, 5548c2ecf20Sopenharmony_ci data->capability, ®val); 5558c2ecf20Sopenharmony_ci if (ret) 5568c2ecf20Sopenharmony_ci goto out; 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci if (!(regval & data->bit)) 5598c2ecf20Sopenharmony_ci item->mask &= ~BIT(j); 5608c2ecf20Sopenharmony_ci } 5618c2ecf20Sopenharmony_ci } 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_ci /* Set group initial status as mask and unmask group event. */ 5648c2ecf20Sopenharmony_ci if (item->inversed) { 5658c2ecf20Sopenharmony_ci item->cache = item->mask; 5668c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, item->reg + 5678c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_MASK_OFF, 5688c2ecf20Sopenharmony_ci item->mask); 5698c2ecf20Sopenharmony_ci if (ret) 5708c2ecf20Sopenharmony_ci goto out; 5718c2ecf20Sopenharmony_ci } 5728c2ecf20Sopenharmony_ci } 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ci /* Keep aggregation initial status as zero and unmask events. */ 5758c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, pdata->cell + 5768c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask); 5778c2ecf20Sopenharmony_ci if (ret) 5788c2ecf20Sopenharmony_ci goto out; 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_ci /* Keep low aggregation initial status as zero and unmask events. */ 5818c2ecf20Sopenharmony_ci if (pdata->cell_low) { 5828c2ecf20Sopenharmony_ci ret = regmap_write(priv->regmap, pdata->cell_low + 5838c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_AGGR_MASK_OFF, 5848c2ecf20Sopenharmony_ci pdata->mask_low); 5858c2ecf20Sopenharmony_ci if (ret) 5868c2ecf20Sopenharmony_ci goto out; 5878c2ecf20Sopenharmony_ci } 5888c2ecf20Sopenharmony_ci 5898c2ecf20Sopenharmony_ci /* Invoke work handler for initializing hot plug devices setting. */ 5908c2ecf20Sopenharmony_ci mlxreg_hotplug_work_handler(&priv->dwork_irq.work); 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ci out: 5938c2ecf20Sopenharmony_ci if (ret) 5948c2ecf20Sopenharmony_ci dev_err(priv->dev, "Failed to set interrupts.\n"); 5958c2ecf20Sopenharmony_ci enable_irq(priv->irq); 5968c2ecf20Sopenharmony_ci return ret; 5978c2ecf20Sopenharmony_ci} 5988c2ecf20Sopenharmony_ci 5998c2ecf20Sopenharmony_cistatic void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv) 6008c2ecf20Sopenharmony_ci{ 6018c2ecf20Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 6028c2ecf20Sopenharmony_ci struct mlxreg_core_item *item; 6038c2ecf20Sopenharmony_ci struct mlxreg_core_data *data; 6048c2ecf20Sopenharmony_ci int count, i, j; 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 6078c2ecf20Sopenharmony_ci item = pdata->items; 6088c2ecf20Sopenharmony_ci disable_irq(priv->irq); 6098c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&priv->dwork_irq); 6108c2ecf20Sopenharmony_ci 6118c2ecf20Sopenharmony_ci /* Mask low aggregation event, if defined. */ 6128c2ecf20Sopenharmony_ci if (pdata->cell_low) 6138c2ecf20Sopenharmony_ci regmap_write(priv->regmap, pdata->cell_low + 6148c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_AGGR_MASK_OFF, 0); 6158c2ecf20Sopenharmony_ci 6168c2ecf20Sopenharmony_ci /* Mask aggregation event. */ 6178c2ecf20Sopenharmony_ci regmap_write(priv->regmap, pdata->cell + MLXREG_HOTPLUG_AGGR_MASK_OFF, 6188c2ecf20Sopenharmony_ci 0); 6198c2ecf20Sopenharmony_ci 6208c2ecf20Sopenharmony_ci /* Clear topology configurations. */ 6218c2ecf20Sopenharmony_ci for (i = 0; i < pdata->counter; i++, item++) { 6228c2ecf20Sopenharmony_ci data = item->data; 6238c2ecf20Sopenharmony_ci /* Mask group presense event. */ 6248c2ecf20Sopenharmony_ci regmap_write(priv->regmap, data->reg + MLXREG_HOTPLUG_MASK_OFF, 6258c2ecf20Sopenharmony_ci 0); 6268c2ecf20Sopenharmony_ci /* Clear group presense event. */ 6278c2ecf20Sopenharmony_ci regmap_write(priv->regmap, data->reg + 6288c2ecf20Sopenharmony_ci MLXREG_HOTPLUG_EVENT_OFF, 0); 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_ci /* Remove all the attached devices in group. */ 6318c2ecf20Sopenharmony_ci count = item->count; 6328c2ecf20Sopenharmony_ci for (j = 0; j < count; j++, data++) 6338c2ecf20Sopenharmony_ci mlxreg_hotplug_device_destroy(priv, data); 6348c2ecf20Sopenharmony_ci } 6358c2ecf20Sopenharmony_ci} 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_cistatic irqreturn_t mlxreg_hotplug_irq_handler(int irq, void *dev) 6388c2ecf20Sopenharmony_ci{ 6398c2ecf20Sopenharmony_ci struct mlxreg_hotplug_priv_data *priv; 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_ci priv = (struct mlxreg_hotplug_priv_data *)dev; 6428c2ecf20Sopenharmony_ci 6438c2ecf20Sopenharmony_ci /* Schedule work task for immediate execution.*/ 6448c2ecf20Sopenharmony_ci schedule_delayed_work(&priv->dwork_irq, 0); 6458c2ecf20Sopenharmony_ci 6468c2ecf20Sopenharmony_ci return IRQ_HANDLED; 6478c2ecf20Sopenharmony_ci} 6488c2ecf20Sopenharmony_ci 6498c2ecf20Sopenharmony_cistatic int mlxreg_hotplug_probe(struct platform_device *pdev) 6508c2ecf20Sopenharmony_ci{ 6518c2ecf20Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 6528c2ecf20Sopenharmony_ci struct mlxreg_hotplug_priv_data *priv; 6538c2ecf20Sopenharmony_ci struct i2c_adapter *deferred_adap; 6548c2ecf20Sopenharmony_ci int err; 6558c2ecf20Sopenharmony_ci 6568c2ecf20Sopenharmony_ci pdata = dev_get_platdata(&pdev->dev); 6578c2ecf20Sopenharmony_ci if (!pdata) { 6588c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to get platform data.\n"); 6598c2ecf20Sopenharmony_ci return -EINVAL; 6608c2ecf20Sopenharmony_ci } 6618c2ecf20Sopenharmony_ci 6628c2ecf20Sopenharmony_ci /* Defer probing if the necessary adapter is not configured yet. */ 6638c2ecf20Sopenharmony_ci deferred_adap = i2c_get_adapter(pdata->deferred_nr); 6648c2ecf20Sopenharmony_ci if (!deferred_adap) 6658c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 6668c2ecf20Sopenharmony_ci i2c_put_adapter(deferred_adap); 6678c2ecf20Sopenharmony_ci 6688c2ecf20Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 6698c2ecf20Sopenharmony_ci if (!priv) 6708c2ecf20Sopenharmony_ci return -ENOMEM; 6718c2ecf20Sopenharmony_ci 6728c2ecf20Sopenharmony_ci if (pdata->irq) { 6738c2ecf20Sopenharmony_ci priv->irq = pdata->irq; 6748c2ecf20Sopenharmony_ci } else { 6758c2ecf20Sopenharmony_ci priv->irq = platform_get_irq(pdev, 0); 6768c2ecf20Sopenharmony_ci if (priv->irq < 0) 6778c2ecf20Sopenharmony_ci return priv->irq; 6788c2ecf20Sopenharmony_ci } 6798c2ecf20Sopenharmony_ci 6808c2ecf20Sopenharmony_ci priv->regmap = pdata->regmap; 6818c2ecf20Sopenharmony_ci priv->dev = pdev->dev.parent; 6828c2ecf20Sopenharmony_ci priv->pdev = pdev; 6838c2ecf20Sopenharmony_ci 6848c2ecf20Sopenharmony_ci err = devm_request_irq(&pdev->dev, priv->irq, 6858c2ecf20Sopenharmony_ci mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING 6868c2ecf20Sopenharmony_ci | IRQF_SHARED, "mlxreg-hotplug", priv); 6878c2ecf20Sopenharmony_ci if (err) { 6888c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to request irq: %d\n", err); 6898c2ecf20Sopenharmony_ci return err; 6908c2ecf20Sopenharmony_ci } 6918c2ecf20Sopenharmony_ci 6928c2ecf20Sopenharmony_ci disable_irq(priv->irq); 6938c2ecf20Sopenharmony_ci spin_lock_init(&priv->lock); 6948c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler); 6958c2ecf20Sopenharmony_ci dev_set_drvdata(&pdev->dev, priv); 6968c2ecf20Sopenharmony_ci 6978c2ecf20Sopenharmony_ci err = mlxreg_hotplug_attr_init(priv); 6988c2ecf20Sopenharmony_ci if (err) { 6998c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to allocate attributes: %d\n", 7008c2ecf20Sopenharmony_ci err); 7018c2ecf20Sopenharmony_ci return err; 7028c2ecf20Sopenharmony_ci } 7038c2ecf20Sopenharmony_ci 7048c2ecf20Sopenharmony_ci priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, 7058c2ecf20Sopenharmony_ci "mlxreg_hotplug", priv, priv->groups); 7068c2ecf20Sopenharmony_ci if (IS_ERR(priv->hwmon)) { 7078c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to register hwmon device %ld\n", 7088c2ecf20Sopenharmony_ci PTR_ERR(priv->hwmon)); 7098c2ecf20Sopenharmony_ci return PTR_ERR(priv->hwmon); 7108c2ecf20Sopenharmony_ci } 7118c2ecf20Sopenharmony_ci 7128c2ecf20Sopenharmony_ci /* Perform initial interrupts setup. */ 7138c2ecf20Sopenharmony_ci mlxreg_hotplug_set_irq(priv); 7148c2ecf20Sopenharmony_ci priv->after_probe = true; 7158c2ecf20Sopenharmony_ci 7168c2ecf20Sopenharmony_ci return 0; 7178c2ecf20Sopenharmony_ci} 7188c2ecf20Sopenharmony_ci 7198c2ecf20Sopenharmony_cistatic int mlxreg_hotplug_remove(struct platform_device *pdev) 7208c2ecf20Sopenharmony_ci{ 7218c2ecf20Sopenharmony_ci struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(&pdev->dev); 7228c2ecf20Sopenharmony_ci 7238c2ecf20Sopenharmony_ci /* Clean interrupts setup. */ 7248c2ecf20Sopenharmony_ci mlxreg_hotplug_unset_irq(priv); 7258c2ecf20Sopenharmony_ci devm_free_irq(&pdev->dev, priv->irq, priv); 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_ci return 0; 7288c2ecf20Sopenharmony_ci} 7298c2ecf20Sopenharmony_ci 7308c2ecf20Sopenharmony_cistatic struct platform_driver mlxreg_hotplug_driver = { 7318c2ecf20Sopenharmony_ci .driver = { 7328c2ecf20Sopenharmony_ci .name = "mlxreg-hotplug", 7338c2ecf20Sopenharmony_ci }, 7348c2ecf20Sopenharmony_ci .probe = mlxreg_hotplug_probe, 7358c2ecf20Sopenharmony_ci .remove = mlxreg_hotplug_remove, 7368c2ecf20Sopenharmony_ci}; 7378c2ecf20Sopenharmony_ci 7388c2ecf20Sopenharmony_cimodule_platform_driver(mlxreg_hotplug_driver); 7398c2ecf20Sopenharmony_ci 7408c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); 7418c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Mellanox regmap hotplug platform driver"); 7428c2ecf20Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 7438c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:mlxreg-hotplug"); 744