162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Mellanox hotplug driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2016-2020 Mellanox Technologies 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/bitops.h> 962306a36Sopenharmony_ci#include <linux/device.h> 1062306a36Sopenharmony_ci#include <linux/hwmon.h> 1162306a36Sopenharmony_ci#include <linux/hwmon-sysfs.h> 1262306a36Sopenharmony_ci#include <linux/i2c.h> 1362306a36Sopenharmony_ci#include <linux/interrupt.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/platform_data/mlxreg.h> 1662306a36Sopenharmony_ci#include <linux/platform_device.h> 1762306a36Sopenharmony_ci#include <linux/spinlock.h> 1862306a36Sopenharmony_ci#include <linux/string_helpers.h> 1962306a36Sopenharmony_ci#include <linux/regmap.h> 2062306a36Sopenharmony_ci#include <linux/workqueue.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/* Offset of event and mask registers from status register. */ 2362306a36Sopenharmony_ci#define MLXREG_HOTPLUG_EVENT_OFF 1 2462306a36Sopenharmony_ci#define MLXREG_HOTPLUG_MASK_OFF 2 2562306a36Sopenharmony_ci#define MLXREG_HOTPLUG_AGGR_MASK_OFF 1 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci/* ASIC good health mask. */ 2862306a36Sopenharmony_ci#define MLXREG_HOTPLUG_GOOD_HEALTH_MASK 0x02 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define MLXREG_HOTPLUG_ATTRS_MAX 128 3162306a36Sopenharmony_ci#define MLXREG_HOTPLUG_NOT_ASSERT 3 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/** 3462306a36Sopenharmony_ci * struct mlxreg_hotplug_priv_data - platform private data: 3562306a36Sopenharmony_ci * @irq: platform device interrupt number; 3662306a36Sopenharmony_ci * @dev: basic device; 3762306a36Sopenharmony_ci * @pdev: platform device; 3862306a36Sopenharmony_ci * @plat: platform data; 3962306a36Sopenharmony_ci * @regmap: register map handle; 4062306a36Sopenharmony_ci * @dwork_irq: delayed work template; 4162306a36Sopenharmony_ci * @lock: spin lock; 4262306a36Sopenharmony_ci * @hwmon: hwmon device; 4362306a36Sopenharmony_ci * @mlxreg_hotplug_attr: sysfs attributes array; 4462306a36Sopenharmony_ci * @mlxreg_hotplug_dev_attr: sysfs sensor device attribute array; 4562306a36Sopenharmony_ci * @group: sysfs attribute group; 4662306a36Sopenharmony_ci * @groups: list of sysfs attribute group for hwmon registration; 4762306a36Sopenharmony_ci * @cell: location of top aggregation interrupt register; 4862306a36Sopenharmony_ci * @mask: top aggregation interrupt common mask; 4962306a36Sopenharmony_ci * @aggr_cache: last value of aggregation register status; 5062306a36Sopenharmony_ci * @after_probe: flag indication probing completion; 5162306a36Sopenharmony_ci * @not_asserted: number of entries in workqueue with no signal assertion; 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_cistruct mlxreg_hotplug_priv_data { 5462306a36Sopenharmony_ci int irq; 5562306a36Sopenharmony_ci struct device *dev; 5662306a36Sopenharmony_ci struct platform_device *pdev; 5762306a36Sopenharmony_ci struct mlxreg_hotplug_platform_data *plat; 5862306a36Sopenharmony_ci struct regmap *regmap; 5962306a36Sopenharmony_ci struct delayed_work dwork_irq; 6062306a36Sopenharmony_ci spinlock_t lock; /* sync with interrupt */ 6162306a36Sopenharmony_ci struct device *hwmon; 6262306a36Sopenharmony_ci struct attribute *mlxreg_hotplug_attr[MLXREG_HOTPLUG_ATTRS_MAX + 1]; 6362306a36Sopenharmony_ci struct sensor_device_attribute_2 6462306a36Sopenharmony_ci mlxreg_hotplug_dev_attr[MLXREG_HOTPLUG_ATTRS_MAX]; 6562306a36Sopenharmony_ci struct attribute_group group; 6662306a36Sopenharmony_ci const struct attribute_group *groups[2]; 6762306a36Sopenharmony_ci u32 cell; 6862306a36Sopenharmony_ci u32 mask; 6962306a36Sopenharmony_ci u32 aggr_cache; 7062306a36Sopenharmony_ci bool after_probe; 7162306a36Sopenharmony_ci u8 not_asserted; 7262306a36Sopenharmony_ci}; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci/* Environment variables array for udev. */ 7562306a36Sopenharmony_cistatic char *mlxreg_hotplug_udev_envp[] = { NULL, NULL }; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic int 7862306a36Sopenharmony_cimlxreg_hotplug_udev_event_send(struct kobject *kobj, 7962306a36Sopenharmony_ci struct mlxreg_core_data *data, bool action) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci char event_str[MLXREG_CORE_LABEL_MAX_SIZE + 2]; 8262306a36Sopenharmony_ci char label[MLXREG_CORE_LABEL_MAX_SIZE] = { 0 }; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci mlxreg_hotplug_udev_envp[0] = event_str; 8562306a36Sopenharmony_ci string_upper(label, data->label); 8662306a36Sopenharmony_ci snprintf(event_str, MLXREG_CORE_LABEL_MAX_SIZE, "%s=%d", label, !!action); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci return kobject_uevent_env(kobj, KOBJ_CHANGE, mlxreg_hotplug_udev_envp); 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic void 9262306a36Sopenharmony_cimlxreg_hotplug_pdata_export(void *pdata, void *regmap) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *dev_pdata = pdata; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci /* Export regmap to underlying device. */ 9762306a36Sopenharmony_ci dev_pdata->regmap = regmap; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic int mlxreg_hotplug_device_create(struct mlxreg_hotplug_priv_data *priv, 10162306a36Sopenharmony_ci struct mlxreg_core_data *data, 10262306a36Sopenharmony_ci enum mlxreg_hotplug_kind kind) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci struct i2c_board_info *brdinfo = data->hpdev.brdinfo; 10562306a36Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 10662306a36Sopenharmony_ci struct i2c_client *client; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci /* Notify user by sending hwmon uevent. */ 10962306a36Sopenharmony_ci mlxreg_hotplug_udev_event_send(&priv->hwmon->kobj, data, true); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci /* 11262306a36Sopenharmony_ci * Return if adapter number is negative. It could be in case hotplug 11362306a36Sopenharmony_ci * event is not associated with hotplug device. 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_ci if (data->hpdev.nr < 0 && data->hpdev.action != MLXREG_HOTPLUG_DEVICE_NO_ACTION) 11662306a36Sopenharmony_ci return 0; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 11962306a36Sopenharmony_ci switch (data->hpdev.action) { 12062306a36Sopenharmony_ci case MLXREG_HOTPLUG_DEVICE_DEFAULT_ACTION: 12162306a36Sopenharmony_ci data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr + 12262306a36Sopenharmony_ci pdata->shift_nr); 12362306a36Sopenharmony_ci if (!data->hpdev.adapter) { 12462306a36Sopenharmony_ci dev_err(priv->dev, "Failed to get adapter for bus %d\n", 12562306a36Sopenharmony_ci data->hpdev.nr + pdata->shift_nr); 12662306a36Sopenharmony_ci return -EFAULT; 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci /* Export platform data to underlying device. */ 13062306a36Sopenharmony_ci if (brdinfo->platform_data) 13162306a36Sopenharmony_ci mlxreg_hotplug_pdata_export(brdinfo->platform_data, pdata->regmap); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci client = i2c_new_client_device(data->hpdev.adapter, 13462306a36Sopenharmony_ci brdinfo); 13562306a36Sopenharmony_ci if (IS_ERR(client)) { 13662306a36Sopenharmony_ci dev_err(priv->dev, "Failed to create client %s at bus %d at addr 0x%02x\n", 13762306a36Sopenharmony_ci brdinfo->type, data->hpdev.nr + 13862306a36Sopenharmony_ci pdata->shift_nr, brdinfo->addr); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci i2c_put_adapter(data->hpdev.adapter); 14162306a36Sopenharmony_ci data->hpdev.adapter = NULL; 14262306a36Sopenharmony_ci return PTR_ERR(client); 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci data->hpdev.client = client; 14662306a36Sopenharmony_ci break; 14762306a36Sopenharmony_ci case MLXREG_HOTPLUG_DEVICE_PLATFORM_ACTION: 14862306a36Sopenharmony_ci /* Export platform data to underlying device. */ 14962306a36Sopenharmony_ci if (data->hpdev.brdinfo && data->hpdev.brdinfo->platform_data) 15062306a36Sopenharmony_ci mlxreg_hotplug_pdata_export(data->hpdev.brdinfo->platform_data, 15162306a36Sopenharmony_ci pdata->regmap); 15262306a36Sopenharmony_ci /* Pass parent hotplug device handle to underlying device. */ 15362306a36Sopenharmony_ci data->notifier = data->hpdev.notifier; 15462306a36Sopenharmony_ci data->hpdev.pdev = platform_device_register_resndata(&priv->pdev->dev, 15562306a36Sopenharmony_ci brdinfo->type, 15662306a36Sopenharmony_ci data->hpdev.nr, 15762306a36Sopenharmony_ci NULL, 0, data, 15862306a36Sopenharmony_ci sizeof(*data)); 15962306a36Sopenharmony_ci if (IS_ERR(data->hpdev.pdev)) 16062306a36Sopenharmony_ci return PTR_ERR(data->hpdev.pdev); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci break; 16362306a36Sopenharmony_ci default: 16462306a36Sopenharmony_ci break; 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci if (data->hpdev.notifier && data->hpdev.notifier->user_handler) 16862306a36Sopenharmony_ci return data->hpdev.notifier->user_handler(data->hpdev.notifier->handle, kind, 1); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci return 0; 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistatic void 17462306a36Sopenharmony_cimlxreg_hotplug_device_destroy(struct mlxreg_hotplug_priv_data *priv, 17562306a36Sopenharmony_ci struct mlxreg_core_data *data, 17662306a36Sopenharmony_ci enum mlxreg_hotplug_kind kind) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci /* Notify user by sending hwmon uevent. */ 17962306a36Sopenharmony_ci mlxreg_hotplug_udev_event_send(&priv->hwmon->kobj, data, false); 18062306a36Sopenharmony_ci if (data->hpdev.notifier && data->hpdev.notifier->user_handler) 18162306a36Sopenharmony_ci data->hpdev.notifier->user_handler(data->hpdev.notifier->handle, kind, 0); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci switch (data->hpdev.action) { 18462306a36Sopenharmony_ci case MLXREG_HOTPLUG_DEVICE_DEFAULT_ACTION: 18562306a36Sopenharmony_ci if (data->hpdev.client) { 18662306a36Sopenharmony_ci i2c_unregister_device(data->hpdev.client); 18762306a36Sopenharmony_ci data->hpdev.client = NULL; 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if (data->hpdev.adapter) { 19162306a36Sopenharmony_ci i2c_put_adapter(data->hpdev.adapter); 19262306a36Sopenharmony_ci data->hpdev.adapter = NULL; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci break; 19562306a36Sopenharmony_ci case MLXREG_HOTPLUG_DEVICE_PLATFORM_ACTION: 19662306a36Sopenharmony_ci if (data->hpdev.pdev) 19762306a36Sopenharmony_ci platform_device_unregister(data->hpdev.pdev); 19862306a36Sopenharmony_ci break; 19962306a36Sopenharmony_ci default: 20062306a36Sopenharmony_ci break; 20162306a36Sopenharmony_ci } 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic ssize_t mlxreg_hotplug_attr_show(struct device *dev, 20562306a36Sopenharmony_ci struct device_attribute *attr, 20662306a36Sopenharmony_ci char *buf) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(dev); 20962306a36Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 21062306a36Sopenharmony_ci int index = to_sensor_dev_attr_2(attr)->index; 21162306a36Sopenharmony_ci int nr = to_sensor_dev_attr_2(attr)->nr; 21262306a36Sopenharmony_ci struct mlxreg_core_item *item; 21362306a36Sopenharmony_ci struct mlxreg_core_data *data; 21462306a36Sopenharmony_ci u32 regval; 21562306a36Sopenharmony_ci int ret; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 21862306a36Sopenharmony_ci item = pdata->items + nr; 21962306a36Sopenharmony_ci data = item->data + index; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci ret = regmap_read(priv->regmap, data->reg, ®val); 22262306a36Sopenharmony_ci if (ret) 22362306a36Sopenharmony_ci return ret; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci if (item->health) { 22662306a36Sopenharmony_ci regval &= data->mask; 22762306a36Sopenharmony_ci } else { 22862306a36Sopenharmony_ci /* Bit = 0 : functional if item->inversed is true. */ 22962306a36Sopenharmony_ci if (item->inversed) 23062306a36Sopenharmony_ci regval = !(regval & data->mask); 23162306a36Sopenharmony_ci else 23262306a36Sopenharmony_ci regval = !!(regval & data->mask); 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci return sprintf(buf, "%u\n", regval); 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci#define PRIV_ATTR(i) priv->mlxreg_hotplug_attr[i] 23962306a36Sopenharmony_ci#define PRIV_DEV_ATTR(i) priv->mlxreg_hotplug_dev_attr[i] 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic int mlxreg_hotplug_item_label_index_get(u32 mask, u32 bit) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci int i, j; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci for (i = 0, j = -1; i <= bit; i++) { 24662306a36Sopenharmony_ci if (mask & BIT(i)) 24762306a36Sopenharmony_ci j++; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci return j; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cistatic int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 25562306a36Sopenharmony_ci struct mlxreg_core_item *item; 25662306a36Sopenharmony_ci struct mlxreg_core_data *data; 25762306a36Sopenharmony_ci unsigned long mask; 25862306a36Sopenharmony_ci u32 regval; 25962306a36Sopenharmony_ci int num_attrs = 0, id = 0, i, j, k, count, ret; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 26262306a36Sopenharmony_ci item = pdata->items; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci /* Go over all kinds of items - psu, pwr, fan. */ 26562306a36Sopenharmony_ci for (i = 0; i < pdata->counter; i++, item++) { 26662306a36Sopenharmony_ci if (item->capability) { 26762306a36Sopenharmony_ci /* 26862306a36Sopenharmony_ci * Read group capability register to get actual number 26962306a36Sopenharmony_ci * of interrupt capable components and set group mask 27062306a36Sopenharmony_ci * accordingly. 27162306a36Sopenharmony_ci */ 27262306a36Sopenharmony_ci ret = regmap_read(priv->regmap, item->capability, 27362306a36Sopenharmony_ci ®val); 27462306a36Sopenharmony_ci if (ret) 27562306a36Sopenharmony_ci return ret; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci item->mask = GENMASK((regval & item->mask) - 1, 0); 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci data = item->data; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* Go over all unmasked units within item. */ 28362306a36Sopenharmony_ci mask = item->mask; 28462306a36Sopenharmony_ci k = 0; 28562306a36Sopenharmony_ci count = item->ind ? item->ind : item->count; 28662306a36Sopenharmony_ci for_each_set_bit(j, &mask, count) { 28762306a36Sopenharmony_ci if (data->capability) { 28862306a36Sopenharmony_ci /* 28962306a36Sopenharmony_ci * Read capability register and skip non 29062306a36Sopenharmony_ci * relevant attributes. 29162306a36Sopenharmony_ci */ 29262306a36Sopenharmony_ci ret = regmap_read(priv->regmap, 29362306a36Sopenharmony_ci data->capability, ®val); 29462306a36Sopenharmony_ci if (ret) 29562306a36Sopenharmony_ci return ret; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci if (!(regval & data->bit)) { 29862306a36Sopenharmony_ci data++; 29962306a36Sopenharmony_ci continue; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr; 30462306a36Sopenharmony_ci PRIV_ATTR(id)->name = devm_kasprintf(&priv->pdev->dev, 30562306a36Sopenharmony_ci GFP_KERNEL, 30662306a36Sopenharmony_ci data->label); 30762306a36Sopenharmony_ci if (!PRIV_ATTR(id)->name) { 30862306a36Sopenharmony_ci dev_err(priv->dev, "Memory allocation failed for attr %d.\n", 30962306a36Sopenharmony_ci id); 31062306a36Sopenharmony_ci return -ENOMEM; 31162306a36Sopenharmony_ci } 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci PRIV_DEV_ATTR(id).dev_attr.attr.name = 31462306a36Sopenharmony_ci PRIV_ATTR(id)->name; 31562306a36Sopenharmony_ci PRIV_DEV_ATTR(id).dev_attr.attr.mode = 0444; 31662306a36Sopenharmony_ci PRIV_DEV_ATTR(id).dev_attr.show = 31762306a36Sopenharmony_ci mlxreg_hotplug_attr_show; 31862306a36Sopenharmony_ci PRIV_DEV_ATTR(id).nr = i; 31962306a36Sopenharmony_ci PRIV_DEV_ATTR(id).index = k; 32062306a36Sopenharmony_ci sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr); 32162306a36Sopenharmony_ci data++; 32262306a36Sopenharmony_ci id++; 32362306a36Sopenharmony_ci k++; 32462306a36Sopenharmony_ci } 32562306a36Sopenharmony_ci num_attrs += k; 32662306a36Sopenharmony_ci } 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci priv->group.attrs = devm_kcalloc(&priv->pdev->dev, 32962306a36Sopenharmony_ci num_attrs, 33062306a36Sopenharmony_ci sizeof(struct attribute *), 33162306a36Sopenharmony_ci GFP_KERNEL); 33262306a36Sopenharmony_ci if (!priv->group.attrs) 33362306a36Sopenharmony_ci return -ENOMEM; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci priv->group.attrs = priv->mlxreg_hotplug_attr; 33662306a36Sopenharmony_ci priv->groups[0] = &priv->group; 33762306a36Sopenharmony_ci priv->groups[1] = NULL; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci return 0; 34062306a36Sopenharmony_ci} 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_cistatic void 34362306a36Sopenharmony_cimlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv, 34462306a36Sopenharmony_ci struct mlxreg_core_item *item) 34562306a36Sopenharmony_ci{ 34662306a36Sopenharmony_ci struct mlxreg_core_data *data; 34762306a36Sopenharmony_ci unsigned long asserted; 34862306a36Sopenharmony_ci u32 regval, bit; 34962306a36Sopenharmony_ci int ret; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci /* 35262306a36Sopenharmony_ci * Validate if item related to received signal type is valid. 35362306a36Sopenharmony_ci * It should never happen, excepted the situation when some 35462306a36Sopenharmony_ci * piece of hardware is broken. In such situation just produce 35562306a36Sopenharmony_ci * error message and return. Caller must continue to handle the 35662306a36Sopenharmony_ci * signals from other devices if any. 35762306a36Sopenharmony_ci */ 35862306a36Sopenharmony_ci if (unlikely(!item)) { 35962306a36Sopenharmony_ci dev_err(priv->dev, "False signal: at offset:mask 0x%02x:0x%02x.\n", 36062306a36Sopenharmony_ci item->reg, item->mask); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci return; 36362306a36Sopenharmony_ci } 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci /* Mask event. */ 36662306a36Sopenharmony_ci ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, 36762306a36Sopenharmony_ci 0); 36862306a36Sopenharmony_ci if (ret) 36962306a36Sopenharmony_ci goto out; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci /* Read status. */ 37262306a36Sopenharmony_ci ret = regmap_read(priv->regmap, item->reg, ®val); 37362306a36Sopenharmony_ci if (ret) 37462306a36Sopenharmony_ci goto out; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci /* Set asserted bits and save last status. */ 37762306a36Sopenharmony_ci regval &= item->mask; 37862306a36Sopenharmony_ci asserted = item->cache ^ regval; 37962306a36Sopenharmony_ci item->cache = regval; 38062306a36Sopenharmony_ci for_each_set_bit(bit, &asserted, 8) { 38162306a36Sopenharmony_ci int pos; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci pos = mlxreg_hotplug_item_label_index_get(item->mask, bit); 38462306a36Sopenharmony_ci if (pos < 0) 38562306a36Sopenharmony_ci goto out; 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci data = item->data + pos; 38862306a36Sopenharmony_ci if (regval & BIT(bit)) { 38962306a36Sopenharmony_ci if (item->inversed) 39062306a36Sopenharmony_ci mlxreg_hotplug_device_destroy(priv, data, item->kind); 39162306a36Sopenharmony_ci else 39262306a36Sopenharmony_ci mlxreg_hotplug_device_create(priv, data, item->kind); 39362306a36Sopenharmony_ci } else { 39462306a36Sopenharmony_ci if (item->inversed) 39562306a36Sopenharmony_ci mlxreg_hotplug_device_create(priv, data, item->kind); 39662306a36Sopenharmony_ci else 39762306a36Sopenharmony_ci mlxreg_hotplug_device_destroy(priv, data, item->kind); 39862306a36Sopenharmony_ci } 39962306a36Sopenharmony_ci } 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci /* Acknowledge event. */ 40262306a36Sopenharmony_ci ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_EVENT_OFF, 40362306a36Sopenharmony_ci 0); 40462306a36Sopenharmony_ci if (ret) 40562306a36Sopenharmony_ci goto out; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci /* Unmask event. */ 40862306a36Sopenharmony_ci ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, 40962306a36Sopenharmony_ci item->mask); 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci out: 41262306a36Sopenharmony_ci if (ret) 41362306a36Sopenharmony_ci dev_err(priv->dev, "Failed to complete workqueue.\n"); 41462306a36Sopenharmony_ci} 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_cistatic void 41762306a36Sopenharmony_cimlxreg_hotplug_health_work_helper(struct mlxreg_hotplug_priv_data *priv, 41862306a36Sopenharmony_ci struct mlxreg_core_item *item) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci struct mlxreg_core_data *data = item->data; 42162306a36Sopenharmony_ci u32 regval; 42262306a36Sopenharmony_ci int i, ret = 0; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci for (i = 0; i < item->count; i++, data++) { 42562306a36Sopenharmony_ci /* Mask event. */ 42662306a36Sopenharmony_ci ret = regmap_write(priv->regmap, data->reg + 42762306a36Sopenharmony_ci MLXREG_HOTPLUG_MASK_OFF, 0); 42862306a36Sopenharmony_ci if (ret) 42962306a36Sopenharmony_ci goto out; 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci /* Read status. */ 43262306a36Sopenharmony_ci ret = regmap_read(priv->regmap, data->reg, ®val); 43362306a36Sopenharmony_ci if (ret) 43462306a36Sopenharmony_ci goto out; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci regval &= data->mask; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci if (item->cache == regval) 43962306a36Sopenharmony_ci goto ack_event; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci /* 44262306a36Sopenharmony_ci * ASIC health indication is provided through two bits. Bits 44362306a36Sopenharmony_ci * value 0x2 indicates that ASIC reached the good health, value 44462306a36Sopenharmony_ci * 0x0 indicates ASIC the bad health or dormant state and value 44562306a36Sopenharmony_ci * 0x3 indicates the booting state. During ASIC reset it should 44662306a36Sopenharmony_ci * pass the following states: dormant -> booting -> good. 44762306a36Sopenharmony_ci */ 44862306a36Sopenharmony_ci if (regval == MLXREG_HOTPLUG_GOOD_HEALTH_MASK) { 44962306a36Sopenharmony_ci if (!data->attached) { 45062306a36Sopenharmony_ci /* 45162306a36Sopenharmony_ci * ASIC is in steady state. Connect associated 45262306a36Sopenharmony_ci * device, if configured. 45362306a36Sopenharmony_ci */ 45462306a36Sopenharmony_ci mlxreg_hotplug_device_create(priv, data, item->kind); 45562306a36Sopenharmony_ci data->attached = true; 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci } else { 45862306a36Sopenharmony_ci if (data->attached) { 45962306a36Sopenharmony_ci /* 46062306a36Sopenharmony_ci * ASIC health is failed after ASIC has been 46162306a36Sopenharmony_ci * in steady state. Disconnect associated 46262306a36Sopenharmony_ci * device, if it has been connected. 46362306a36Sopenharmony_ci */ 46462306a36Sopenharmony_ci mlxreg_hotplug_device_destroy(priv, data, item->kind); 46562306a36Sopenharmony_ci data->attached = false; 46662306a36Sopenharmony_ci data->health_cntr = 0; 46762306a36Sopenharmony_ci } 46862306a36Sopenharmony_ci } 46962306a36Sopenharmony_ci item->cache = regval; 47062306a36Sopenharmony_ciack_event: 47162306a36Sopenharmony_ci /* Acknowledge event. */ 47262306a36Sopenharmony_ci ret = regmap_write(priv->regmap, data->reg + 47362306a36Sopenharmony_ci MLXREG_HOTPLUG_EVENT_OFF, 0); 47462306a36Sopenharmony_ci if (ret) 47562306a36Sopenharmony_ci goto out; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci /* Unmask event. */ 47862306a36Sopenharmony_ci ret = regmap_write(priv->regmap, data->reg + 47962306a36Sopenharmony_ci MLXREG_HOTPLUG_MASK_OFF, data->mask); 48062306a36Sopenharmony_ci if (ret) 48162306a36Sopenharmony_ci goto out; 48262306a36Sopenharmony_ci } 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci out: 48562306a36Sopenharmony_ci if (ret) 48662306a36Sopenharmony_ci dev_err(priv->dev, "Failed to complete workqueue.\n"); 48762306a36Sopenharmony_ci} 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci/* 49062306a36Sopenharmony_ci * mlxreg_hotplug_work_handler - performs traversing of device interrupt 49162306a36Sopenharmony_ci * registers according to the below hierarchy schema: 49262306a36Sopenharmony_ci * 49362306a36Sopenharmony_ci * Aggregation registers (status/mask) 49462306a36Sopenharmony_ci * PSU registers: *---* 49562306a36Sopenharmony_ci * *-----------------* | | 49662306a36Sopenharmony_ci * |status/event/mask|-----> | * | 49762306a36Sopenharmony_ci * *-----------------* | | 49862306a36Sopenharmony_ci * Power registers: | | 49962306a36Sopenharmony_ci * *-----------------* | | 50062306a36Sopenharmony_ci * |status/event/mask|-----> | * | 50162306a36Sopenharmony_ci * *-----------------* | | 50262306a36Sopenharmony_ci * FAN registers: | |--> CPU 50362306a36Sopenharmony_ci * *-----------------* | | 50462306a36Sopenharmony_ci * |status/event/mask|-----> | * | 50562306a36Sopenharmony_ci * *-----------------* | | 50662306a36Sopenharmony_ci * ASIC registers: | | 50762306a36Sopenharmony_ci * *-----------------* | | 50862306a36Sopenharmony_ci * |status/event/mask|-----> | * | 50962306a36Sopenharmony_ci * *-----------------* | | 51062306a36Sopenharmony_ci * *---* 51162306a36Sopenharmony_ci * 51262306a36Sopenharmony_ci * In case some system changed are detected: FAN in/out, PSU in/out, power 51362306a36Sopenharmony_ci * cable attached/detached, ASIC health good/bad, relevant device is created 51462306a36Sopenharmony_ci * or destroyed. 51562306a36Sopenharmony_ci */ 51662306a36Sopenharmony_cistatic void mlxreg_hotplug_work_handler(struct work_struct *work) 51762306a36Sopenharmony_ci{ 51862306a36Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 51962306a36Sopenharmony_ci struct mlxreg_hotplug_priv_data *priv; 52062306a36Sopenharmony_ci struct mlxreg_core_item *item; 52162306a36Sopenharmony_ci u32 regval, aggr_asserted; 52262306a36Sopenharmony_ci unsigned long flags; 52362306a36Sopenharmony_ci int i, ret; 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci priv = container_of(work, struct mlxreg_hotplug_priv_data, 52662306a36Sopenharmony_ci dwork_irq.work); 52762306a36Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 52862306a36Sopenharmony_ci item = pdata->items; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci /* Mask aggregation event. */ 53162306a36Sopenharmony_ci ret = regmap_write(priv->regmap, pdata->cell + 53262306a36Sopenharmony_ci MLXREG_HOTPLUG_AGGR_MASK_OFF, 0); 53362306a36Sopenharmony_ci if (ret < 0) 53462306a36Sopenharmony_ci goto out; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci /* Read aggregation status. */ 53762306a36Sopenharmony_ci ret = regmap_read(priv->regmap, pdata->cell, ®val); 53862306a36Sopenharmony_ci if (ret) 53962306a36Sopenharmony_ci goto out; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci regval &= pdata->mask; 54262306a36Sopenharmony_ci aggr_asserted = priv->aggr_cache ^ regval; 54362306a36Sopenharmony_ci priv->aggr_cache = regval; 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci /* 54662306a36Sopenharmony_ci * Handler is invoked, but no assertion is detected at top aggregation 54762306a36Sopenharmony_ci * status level. Set aggr_asserted to mask value to allow handler extra 54862306a36Sopenharmony_ci * run over all relevant signals to recover any missed signal. 54962306a36Sopenharmony_ci */ 55062306a36Sopenharmony_ci if (priv->not_asserted == MLXREG_HOTPLUG_NOT_ASSERT) { 55162306a36Sopenharmony_ci priv->not_asserted = 0; 55262306a36Sopenharmony_ci aggr_asserted = pdata->mask; 55362306a36Sopenharmony_ci } 55462306a36Sopenharmony_ci if (!aggr_asserted) 55562306a36Sopenharmony_ci goto unmask_event; 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci /* Handle topology and health configuration changes. */ 55862306a36Sopenharmony_ci for (i = 0; i < pdata->counter; i++, item++) { 55962306a36Sopenharmony_ci if (aggr_asserted & item->aggr_mask) { 56062306a36Sopenharmony_ci if (item->health) 56162306a36Sopenharmony_ci mlxreg_hotplug_health_work_helper(priv, item); 56262306a36Sopenharmony_ci else 56362306a36Sopenharmony_ci mlxreg_hotplug_work_helper(priv, item); 56462306a36Sopenharmony_ci } 56562306a36Sopenharmony_ci } 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci spin_lock_irqsave(&priv->lock, flags); 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci /* 57062306a36Sopenharmony_ci * It is possible, that some signals have been inserted, while 57162306a36Sopenharmony_ci * interrupt has been masked by mlxreg_hotplug_work_handler. In this 57262306a36Sopenharmony_ci * case such signals will be missed. In order to handle these signals 57362306a36Sopenharmony_ci * delayed work is canceled and work task re-scheduled for immediate 57462306a36Sopenharmony_ci * execution. It allows to handle missed signals, if any. In other case 57562306a36Sopenharmony_ci * work handler just validates that no new signals have been received 57662306a36Sopenharmony_ci * during masking. 57762306a36Sopenharmony_ci */ 57862306a36Sopenharmony_ci cancel_delayed_work(&priv->dwork_irq); 57962306a36Sopenharmony_ci schedule_delayed_work(&priv->dwork_irq, 0); 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci spin_unlock_irqrestore(&priv->lock, flags); 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci return; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ciunmask_event: 58662306a36Sopenharmony_ci priv->not_asserted++; 58762306a36Sopenharmony_ci /* Unmask aggregation event (no need acknowledge). */ 58862306a36Sopenharmony_ci ret = regmap_write(priv->regmap, pdata->cell + 58962306a36Sopenharmony_ci MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask); 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci out: 59262306a36Sopenharmony_ci if (ret) 59362306a36Sopenharmony_ci dev_err(priv->dev, "Failed to complete workqueue.\n"); 59462306a36Sopenharmony_ci} 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_cistatic int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) 59762306a36Sopenharmony_ci{ 59862306a36Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 59962306a36Sopenharmony_ci struct mlxreg_core_item *item; 60062306a36Sopenharmony_ci struct mlxreg_core_data *data; 60162306a36Sopenharmony_ci u32 regval; 60262306a36Sopenharmony_ci int i, j, ret; 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 60562306a36Sopenharmony_ci item = pdata->items; 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci for (i = 0; i < pdata->counter; i++, item++) { 60862306a36Sopenharmony_ci /* Clear group presense event. */ 60962306a36Sopenharmony_ci ret = regmap_write(priv->regmap, item->reg + 61062306a36Sopenharmony_ci MLXREG_HOTPLUG_EVENT_OFF, 0); 61162306a36Sopenharmony_ci if (ret) 61262306a36Sopenharmony_ci goto out; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci /* 61562306a36Sopenharmony_ci * Verify if hardware configuration requires to disable 61662306a36Sopenharmony_ci * interrupt capability for some of components. 61762306a36Sopenharmony_ci */ 61862306a36Sopenharmony_ci data = item->data; 61962306a36Sopenharmony_ci for (j = 0; j < item->count; j++, data++) { 62062306a36Sopenharmony_ci /* Verify if the attribute has capability register. */ 62162306a36Sopenharmony_ci if (data->capability) { 62262306a36Sopenharmony_ci /* Read capability register. */ 62362306a36Sopenharmony_ci ret = regmap_read(priv->regmap, 62462306a36Sopenharmony_ci data->capability, ®val); 62562306a36Sopenharmony_ci if (ret) 62662306a36Sopenharmony_ci goto out; 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci if (!(regval & data->bit)) 62962306a36Sopenharmony_ci item->mask &= ~BIT(j); 63062306a36Sopenharmony_ci } 63162306a36Sopenharmony_ci } 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci /* Set group initial status as mask and unmask group event. */ 63462306a36Sopenharmony_ci if (item->inversed) { 63562306a36Sopenharmony_ci item->cache = item->mask; 63662306a36Sopenharmony_ci ret = regmap_write(priv->regmap, item->reg + 63762306a36Sopenharmony_ci MLXREG_HOTPLUG_MASK_OFF, 63862306a36Sopenharmony_ci item->mask); 63962306a36Sopenharmony_ci if (ret) 64062306a36Sopenharmony_ci goto out; 64162306a36Sopenharmony_ci } 64262306a36Sopenharmony_ci } 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci /* Keep aggregation initial status as zero and unmask events. */ 64562306a36Sopenharmony_ci ret = regmap_write(priv->regmap, pdata->cell + 64662306a36Sopenharmony_ci MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask); 64762306a36Sopenharmony_ci if (ret) 64862306a36Sopenharmony_ci goto out; 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci /* Keep low aggregation initial status as zero and unmask events. */ 65162306a36Sopenharmony_ci if (pdata->cell_low) { 65262306a36Sopenharmony_ci ret = regmap_write(priv->regmap, pdata->cell_low + 65362306a36Sopenharmony_ci MLXREG_HOTPLUG_AGGR_MASK_OFF, 65462306a36Sopenharmony_ci pdata->mask_low); 65562306a36Sopenharmony_ci if (ret) 65662306a36Sopenharmony_ci goto out; 65762306a36Sopenharmony_ci } 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci /* Invoke work handler for initializing hot plug devices setting. */ 66062306a36Sopenharmony_ci mlxreg_hotplug_work_handler(&priv->dwork_irq.work); 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci out: 66362306a36Sopenharmony_ci if (ret) 66462306a36Sopenharmony_ci dev_err(priv->dev, "Failed to set interrupts.\n"); 66562306a36Sopenharmony_ci enable_irq(priv->irq); 66662306a36Sopenharmony_ci return ret; 66762306a36Sopenharmony_ci} 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_cistatic void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv) 67062306a36Sopenharmony_ci{ 67162306a36Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 67262306a36Sopenharmony_ci struct mlxreg_core_item *item; 67362306a36Sopenharmony_ci struct mlxreg_core_data *data; 67462306a36Sopenharmony_ci int count, i, j; 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci pdata = dev_get_platdata(&priv->pdev->dev); 67762306a36Sopenharmony_ci item = pdata->items; 67862306a36Sopenharmony_ci disable_irq(priv->irq); 67962306a36Sopenharmony_ci cancel_delayed_work_sync(&priv->dwork_irq); 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci /* Mask low aggregation event, if defined. */ 68262306a36Sopenharmony_ci if (pdata->cell_low) 68362306a36Sopenharmony_ci regmap_write(priv->regmap, pdata->cell_low + 68462306a36Sopenharmony_ci MLXREG_HOTPLUG_AGGR_MASK_OFF, 0); 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci /* Mask aggregation event. */ 68762306a36Sopenharmony_ci regmap_write(priv->regmap, pdata->cell + MLXREG_HOTPLUG_AGGR_MASK_OFF, 68862306a36Sopenharmony_ci 0); 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci /* Clear topology configurations. */ 69162306a36Sopenharmony_ci for (i = 0; i < pdata->counter; i++, item++) { 69262306a36Sopenharmony_ci data = item->data; 69362306a36Sopenharmony_ci /* Mask group presense event. */ 69462306a36Sopenharmony_ci regmap_write(priv->regmap, data->reg + MLXREG_HOTPLUG_MASK_OFF, 69562306a36Sopenharmony_ci 0); 69662306a36Sopenharmony_ci /* Clear group presense event. */ 69762306a36Sopenharmony_ci regmap_write(priv->regmap, data->reg + 69862306a36Sopenharmony_ci MLXREG_HOTPLUG_EVENT_OFF, 0); 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci /* Remove all the attached devices in group. */ 70162306a36Sopenharmony_ci count = item->count; 70262306a36Sopenharmony_ci for (j = 0; j < count; j++, data++) 70362306a36Sopenharmony_ci mlxreg_hotplug_device_destroy(priv, data, item->kind); 70462306a36Sopenharmony_ci } 70562306a36Sopenharmony_ci} 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_cistatic irqreturn_t mlxreg_hotplug_irq_handler(int irq, void *dev) 70862306a36Sopenharmony_ci{ 70962306a36Sopenharmony_ci struct mlxreg_hotplug_priv_data *priv; 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci priv = (struct mlxreg_hotplug_priv_data *)dev; 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ci /* Schedule work task for immediate execution.*/ 71462306a36Sopenharmony_ci schedule_delayed_work(&priv->dwork_irq, 0); 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci return IRQ_HANDLED; 71762306a36Sopenharmony_ci} 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_cistatic int mlxreg_hotplug_probe(struct platform_device *pdev) 72062306a36Sopenharmony_ci{ 72162306a36Sopenharmony_ci struct mlxreg_core_hotplug_platform_data *pdata; 72262306a36Sopenharmony_ci struct mlxreg_hotplug_priv_data *priv; 72362306a36Sopenharmony_ci struct i2c_adapter *deferred_adap; 72462306a36Sopenharmony_ci int err; 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci pdata = dev_get_platdata(&pdev->dev); 72762306a36Sopenharmony_ci if (!pdata) { 72862306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to get platform data.\n"); 72962306a36Sopenharmony_ci return -EINVAL; 73062306a36Sopenharmony_ci } 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci /* Defer probing if the necessary adapter is not configured yet. */ 73362306a36Sopenharmony_ci deferred_adap = i2c_get_adapter(pdata->deferred_nr); 73462306a36Sopenharmony_ci if (!deferred_adap) 73562306a36Sopenharmony_ci return -EPROBE_DEFER; 73662306a36Sopenharmony_ci i2c_put_adapter(deferred_adap); 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 73962306a36Sopenharmony_ci if (!priv) 74062306a36Sopenharmony_ci return -ENOMEM; 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci if (pdata->irq) { 74362306a36Sopenharmony_ci priv->irq = pdata->irq; 74462306a36Sopenharmony_ci } else { 74562306a36Sopenharmony_ci priv->irq = platform_get_irq(pdev, 0); 74662306a36Sopenharmony_ci if (priv->irq < 0) 74762306a36Sopenharmony_ci return priv->irq; 74862306a36Sopenharmony_ci } 74962306a36Sopenharmony_ci 75062306a36Sopenharmony_ci priv->regmap = pdata->regmap; 75162306a36Sopenharmony_ci priv->dev = pdev->dev.parent; 75262306a36Sopenharmony_ci priv->pdev = pdev; 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci err = devm_request_irq(&pdev->dev, priv->irq, 75562306a36Sopenharmony_ci mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING 75662306a36Sopenharmony_ci | IRQF_SHARED, "mlxreg-hotplug", priv); 75762306a36Sopenharmony_ci if (err) { 75862306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to request irq: %d\n", err); 75962306a36Sopenharmony_ci return err; 76062306a36Sopenharmony_ci } 76162306a36Sopenharmony_ci 76262306a36Sopenharmony_ci disable_irq(priv->irq); 76362306a36Sopenharmony_ci spin_lock_init(&priv->lock); 76462306a36Sopenharmony_ci INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler); 76562306a36Sopenharmony_ci dev_set_drvdata(&pdev->dev, priv); 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci err = mlxreg_hotplug_attr_init(priv); 76862306a36Sopenharmony_ci if (err) { 76962306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to allocate attributes: %d\n", 77062306a36Sopenharmony_ci err); 77162306a36Sopenharmony_ci return err; 77262306a36Sopenharmony_ci } 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_ci priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, 77562306a36Sopenharmony_ci "mlxreg_hotplug", priv, priv->groups); 77662306a36Sopenharmony_ci if (IS_ERR(priv->hwmon)) { 77762306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to register hwmon device %ld\n", 77862306a36Sopenharmony_ci PTR_ERR(priv->hwmon)); 77962306a36Sopenharmony_ci return PTR_ERR(priv->hwmon); 78062306a36Sopenharmony_ci } 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci /* Perform initial interrupts setup. */ 78362306a36Sopenharmony_ci mlxreg_hotplug_set_irq(priv); 78462306a36Sopenharmony_ci priv->after_probe = true; 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci return 0; 78762306a36Sopenharmony_ci} 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_cistatic int mlxreg_hotplug_remove(struct platform_device *pdev) 79062306a36Sopenharmony_ci{ 79162306a36Sopenharmony_ci struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(&pdev->dev); 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_ci /* Clean interrupts setup. */ 79462306a36Sopenharmony_ci mlxreg_hotplug_unset_irq(priv); 79562306a36Sopenharmony_ci devm_free_irq(&pdev->dev, priv->irq, priv); 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_ci return 0; 79862306a36Sopenharmony_ci} 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_cistatic struct platform_driver mlxreg_hotplug_driver = { 80162306a36Sopenharmony_ci .driver = { 80262306a36Sopenharmony_ci .name = "mlxreg-hotplug", 80362306a36Sopenharmony_ci }, 80462306a36Sopenharmony_ci .probe = mlxreg_hotplug_probe, 80562306a36Sopenharmony_ci .remove = mlxreg_hotplug_remove, 80662306a36Sopenharmony_ci}; 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_cimodule_platform_driver(mlxreg_hotplug_driver); 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ciMODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); 81162306a36Sopenharmony_ciMODULE_DESCRIPTION("Mellanox regmap hotplug platform driver"); 81262306a36Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 81362306a36Sopenharmony_ciMODULE_ALIAS("platform:mlxreg-hotplug"); 814