cregit-Linux how code gets into the kernel

Release 4.11 drivers/platform/x86/mlxcpld-hotplug.c

/*
 * drivers/platform/x86/mlxcpld-hotplug.c
 * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
 * Copyright (c) 2016 Vadim Pasternak <vadimp@mellanox.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the names of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL") version 2 as published by the Free
 * Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_data/mlxcpld-hotplug.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/workqueue.h>

/* Offset of event and mask registers from status register */

#define MLXCPLD_HOTPLUG_EVENT_OFF	1

#define MLXCPLD_HOTPLUG_MASK_OFF	2

#define MLXCPLD_HOTPLUG_AGGR_MASK_OFF	1


#define MLXCPLD_HOTPLUG_ATTRS_NUM	8

/**
 * enum mlxcpld_hotplug_attr_type - sysfs attributes for hotplug events:
 * @MLXCPLD_HOTPLUG_ATTR_TYPE_PSU: power supply unit attribute;
 * @MLXCPLD_HOTPLUG_ATTR_TYPE_PWR: power cable attribute;
 * @MLXCPLD_HOTPLUG_ATTR_TYPE_FAN: FAN drawer attribute;
 */

enum mlxcpld_hotplug_attr_type {
	
MLXCPLD_HOTPLUG_ATTR_TYPE_PSU,
	
MLXCPLD_HOTPLUG_ATTR_TYPE_PWR,
	
MLXCPLD_HOTPLUG_ATTR_TYPE_FAN,
};

/**
 * struct mlxcpld_hotplug_priv_data - platform private data:
 * @irq: platform interrupt number;
 * @pdev: platform device;
 * @plat: platform data;
 * @hwmon: hwmon device;
 * @mlxcpld_hotplug_attr: sysfs attributes array;
 * @mlxcpld_hotplug_dev_attr: sysfs sensor device attribute array;
 * @group: sysfs attribute group;
 * @groups: list of sysfs attribute group for hwmon registration;
 * @dwork: delayed work template;
 * @lock: spin lock;
 * @aggr_cache: last value of aggregation register status;
 * @psu_cache: last value of PSU register status;
 * @pwr_cache: last value of power register status;
 * @fan_cache: last value of FAN register status;
 */

struct mlxcpld_hotplug_priv_data {
	
int irq;
	
struct platform_device *pdev;
	
struct mlxcpld_hotplug_platform_data *plat;
	
struct device *hwmon;
	
struct attribute *mlxcpld_hotplug_attr[MLXCPLD_HOTPLUG_ATTRS_NUM + 1];
	struct sensor_device_attribute_2
			
mlxcpld_hotplug_dev_attr[MLXCPLD_HOTPLUG_ATTRS_NUM];
	
struct attribute_group group;
	
const struct attribute_group *groups[2];
	
struct delayed_work dwork;
	
spinlock_t lock;
	
u8 aggr_cache;
	
u8 psu_cache;
	
u8 pwr_cache;
	
u8 fan_cache;
};


static ssize_t mlxcpld_hotplug_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct platform_device *pdev = to_platform_device(dev); struct mlxcpld_hotplug_priv_data *priv = platform_get_drvdata(pdev); int index = to_sensor_dev_attr_2(attr)->index; int nr = to_sensor_dev_attr_2(attr)->nr; u8 reg_val = 0; switch (nr) { case MLXCPLD_HOTPLUG_ATTR_TYPE_PSU: /* Bit = 0 : PSU is present. */ reg_val = !!!(inb(priv->plat->psu_reg_offset) & BIT(index)); break; case MLXCPLD_HOTPLUG_ATTR_TYPE_PWR: /* Bit = 1 : power cable is attached. */ reg_val = !!(inb(priv->plat->pwr_reg_offset) & BIT(index % priv->plat->pwr_count)); break; case MLXCPLD_HOTPLUG_ATTR_TYPE_FAN: /* Bit = 0 : FAN is present. */ reg_val = !!!(inb(priv->plat->fan_reg_offset) & BIT(index % priv->plat->fan_count)); break; } return sprintf(buf, "%u\n", reg_val); }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak170100.00%1100.00%
Total170100.00%1100.00%

#define PRIV_ATTR(i) priv->mlxcpld_hotplug_attr[i] #define PRIV_DEV_ATTR(i) priv->mlxcpld_hotplug_dev_attr[i]
static int mlxcpld_hotplug_attr_init(struct mlxcpld_hotplug_priv_data *priv) { int num_attrs = priv->plat->psu_count + priv->plat->pwr_count + priv->plat->fan_count; int i; priv->group.attrs = devm_kzalloc(&priv->pdev->dev, num_attrs * sizeof(struct attribute *), GFP_KERNEL); if (!priv->group.attrs) return -ENOMEM; for (i = 0; i < num_attrs; i++) { PRIV_ATTR(i) = &PRIV_DEV_ATTR(i).dev_attr.attr; if (i < priv->plat->psu_count) { PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL, "psu%u", i + 1); PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_PSU; } else if (i < priv->plat->psu_count + priv->plat->pwr_count) { PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL, "pwr%u", i % priv->plat->pwr_count + 1); PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_PWR; } else { PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL, "fan%u", i % priv->plat->fan_count + 1); PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_FAN; } if (!PRIV_ATTR(i)->name) { dev_err(&priv->pdev->dev, "Memory allocation failed for sysfs attribute %d.\n", i + 1); return -ENOMEM; } PRIV_DEV_ATTR(i).dev_attr.attr.name = PRIV_ATTR(i)->name; PRIV_DEV_ATTR(i).dev_attr.attr.mode = S_IRUGO; PRIV_DEV_ATTR(i).dev_attr.show = mlxcpld_hotplug_attr_show; PRIV_DEV_ATTR(i).index = i; sysfs_attr_init(&PRIV_DEV_ATTR(i).dev_attr.attr); } priv->group.attrs = priv->mlxcpld_hotplug_attr; priv->groups[0] = &priv->group; priv->groups[1] = NULL; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak383100.00%1100.00%
Total383100.00%1100.00%


static int mlxcpld_hotplug_device_create(struct device *dev, struct mlxcpld_hotplug_device *item) { item->adapter = i2c_get_adapter(item->bus); if (!item->adapter) { dev_err(dev, "Failed to get adapter for bus %d\n", item->bus); return -EFAULT; } item->client = i2c_new_device(item->adapter, &item->brdinfo); if (!item->client) { dev_err(dev, "Failed to create client %s at bus %d at addr 0x%02x\n", item->brdinfo.type, item->bus, item->brdinfo.addr); i2c_put_adapter(item->adapter); item->adapter = NULL; return -EFAULT; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak119100.00%1100.00%
Total119100.00%1100.00%


static void mlxcpld_hotplug_device_destroy(struct mlxcpld_hotplug_device *item) { if (item->client) { i2c_unregister_device(item->client); item->client = NULL; } if (item->adapter) { i2c_put_adapter(item->adapter); item->adapter = NULL; } }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak53100.00%1100.00%
Total53100.00%1100.00%


static inline void mlxcpld_hotplug_work_helper(struct device *dev, struct mlxcpld_hotplug_device *item, u8 is_inverse, u16 offset, u8 mask, u8 *cache) { u8 val, asserted; int bit; /* Mask event. */ outb(0, offset + MLXCPLD_HOTPLUG_MASK_OFF); /* Read status. */ val = inb(offset) & mask; asserted = *cache ^ val; *cache = val; /* * Validate if item related to received signal type is valid. * It should never happen, excepted the situation when some * piece of hardware is broken. In such situation just produce * error message and return. Caller must continue to handle the * signals from other devices if any. */ if (unlikely(!item)) { dev_err(dev, "False signal is received: register at offset 0x%02x, mask 0x%02x.\n", offset, mask); return; } for_each_set_bit(bit, (unsigned long *)&asserted, 8) { if (val & BIT(bit)) { if (is_inverse) mlxcpld_hotplug_device_destroy(item + bit); else mlxcpld_hotplug_device_create(dev, item + bit); } else { if (is_inverse) mlxcpld_hotplug_device_create(dev, item + bit); else mlxcpld_hotplug_device_destroy(item + bit); } } /* Acknowledge event. */ outb(0, offset + MLXCPLD_HOTPLUG_EVENT_OFF); /* Unmask event. */ outb(mask, offset + MLXCPLD_HOTPLUG_MASK_OFF); }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak179100.00%1100.00%
Total179100.00%1100.00%

/* * mlxcpld_hotplug_work_handler - performs traversing of CPLD interrupt * registers according to the below hierarchy schema: * * Aggregation registers (status/mask) * PSU registers: *---* * *-----------------* | | * |status/event/mask|----->| * | * *-----------------* | | * Power registers: | | * *-----------------* | | * |status/event/mask|----->| * |---> CPU * *-----------------* | | * FAN registers: * *-----------------* | | * |status/event/mask|----->| * | * *-----------------* | | * *---* * In case some system changed are detected: FAN in/out, PSU in/out, power * cable attached/detached, relevant device is created or destroyed. */
static void mlxcpld_hotplug_work_handler(struct work_struct *work) { struct mlxcpld_hotplug_priv_data *priv = container_of(work, struct mlxcpld_hotplug_priv_data, dwork.work); u8 val, aggr_asserted; unsigned long flags; /* Mask aggregation event. */ outb(0, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF); /* Read aggregation status. */ val = inb(priv->plat->top_aggr_offset) & priv->plat->top_aggr_mask; aggr_asserted = priv->aggr_cache ^ val; priv->aggr_cache = val; /* Handle PSU configuration changes. */ if (aggr_asserted & priv->plat->top_aggr_psu_mask) mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->psu, 1, priv->plat->psu_reg_offset, priv->plat->psu_mask, &priv->psu_cache); /* Handle power cable configuration changes. */ if (aggr_asserted & priv->plat->top_aggr_pwr_mask) mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->pwr, 0, priv->plat->pwr_reg_offset, priv->plat->pwr_mask, &priv->pwr_cache); /* Handle FAN configuration changes. */ if (aggr_asserted & priv->plat->top_aggr_fan_mask) mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->fan, 1, priv->plat->fan_reg_offset, priv->plat->fan_mask, &priv->fan_cache); if (aggr_asserted) { spin_lock_irqsave(&priv->lock, flags); /* * It is possible, that some signals have been inserted, while * interrupt has been masked by mlxcpld_hotplug_work_handler. * In this case such signals will be missed. In order to handle * these signals delayed work is canceled and work task * re-scheduled for immediate execution. It allows to handle * missed signals, if any. In other case work handler just * validates that no new signals have been received during * masking. */ cancel_delayed_work(&priv->dwork); schedule_delayed_work(&priv->dwork, 0); spin_unlock_irqrestore(&priv->lock, flags); return; } /* Unmask aggregation event (no need acknowledge). */ outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF); }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak285100.00%1100.00%
Total285100.00%1100.00%


static void mlxcpld_hotplug_set_irq(struct mlxcpld_hotplug_priv_data *priv) { /* Clear psu presense event. */ outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); /* Set psu initial status as mask and unmask psu event. */ priv->psu_cache = priv->plat->psu_mask; outb(priv->plat->psu_mask, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); /* Clear power cable event. */ outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); /* Keep power initial status as zero and unmask power event. */ outb(priv->plat->pwr_mask, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); /* Clear fan presense event. */ outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); /* Set fan initial status as mask and unmask fan event. */ priv->fan_cache = priv->plat->fan_mask; outb(priv->plat->fan_mask, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); /* Keep aggregation initial status as zero and unmask events. */ outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF); /* Invoke work handler for initializing hot plug devices setting. */ mlxcpld_hotplug_work_handler(&priv->dwork.work); enable_irq(priv->irq); }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak163100.00%1100.00%
Total163100.00%1100.00%


static void mlxcpld_hotplug_unset_irq(struct mlxcpld_hotplug_priv_data *priv) { int i; disable_irq(priv->irq); cancel_delayed_work_sync(&priv->dwork); /* Mask aggregation event. */ outb(0, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF); /* Mask psu presense event. */ outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); /* Clear psu presense event. */ outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); /* Mask power cable event. */ outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); /* Clear power cable event. */ outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); /* Mask fan presense event. */ outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); /* Clear fan presense event. */ outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); /* Remove all the attached devices. */ for (i = 0; i < priv->plat->psu_count; i++) mlxcpld_hotplug_device_destroy(priv->plat->psu + i); for (i = 0; i < priv->plat->pwr_count; i++) mlxcpld_hotplug_device_destroy(priv->plat->pwr + i); for (i = 0; i < priv->plat->fan_count; i++) mlxcpld_hotplug_device_destroy(priv->plat->fan + i); }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak212100.00%1100.00%
Total212100.00%1100.00%


static irqreturn_t mlxcpld_hotplug_irq_handler(int irq, void *dev) { struct mlxcpld_hotplug_priv_data *priv = (struct mlxcpld_hotplug_priv_data *)dev; /* Schedule work task for immediate execution.*/ schedule_delayed_work(&priv->dwork, 0); return IRQ_HANDLED; }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak39100.00%1100.00%
Total39100.00%1100.00%


static int mlxcpld_hotplug_probe(struct platform_device *pdev) { struct mlxcpld_hotplug_platform_data *pdata; struct mlxcpld_hotplug_priv_data *priv; int err; pdata = dev_get_platdata(&pdev->dev); if (!pdata) { dev_err(&pdev->dev, "Failed to get platform data.\n"); return -EINVAL; } priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->pdev = pdev; priv->plat = pdata; priv->irq = platform_get_irq(pdev, 0); if (priv->irq < 0) { dev_err(&pdev->dev, "Failed to get platform irq: %d\n", priv->irq); return priv->irq; } err = devm_request_irq(&pdev->dev, priv->irq, mlxcpld_hotplug_irq_handler, 0, pdev->name, priv); if (err) { dev_err(&pdev->dev, "Failed to request irq: %d\n", err); return err; } disable_irq(priv->irq); INIT_DELAYED_WORK(&priv->dwork, mlxcpld_hotplug_work_handler); spin_lock_init(&priv->lock); err = mlxcpld_hotplug_attr_init(priv); if (err) { dev_err(&pdev->dev, "Failed to allocate attributes: %d\n", err); return err; } priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, "mlxcpld_hotplug", priv, priv->groups); if (IS_ERR(priv->hwmon)) { dev_err(&pdev->dev, "Failed to register hwmon device %ld\n", PTR_ERR(priv->hwmon)); return PTR_ERR(priv->hwmon); } platform_set_drvdata(pdev, priv); /* Perform initial interrupts setup. */ mlxcpld_hotplug_set_irq(priv); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak304100.00%1100.00%
Total304100.00%1100.00%


static int mlxcpld_hotplug_remove(struct platform_device *pdev) { struct mlxcpld_hotplug_priv_data *priv = platform_get_drvdata(pdev); /* Clean interrupts setup. */ mlxcpld_hotplug_unset_irq(priv); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak30100.00%1100.00%
Total30100.00%1100.00%

static struct platform_driver mlxcpld_hotplug_driver = { .driver = { .name = "mlxcpld-hotplug", }, .probe = mlxcpld_hotplug_probe, .remove = mlxcpld_hotplug_remove, }; module_platform_driver(mlxcpld_hotplug_driver); MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); MODULE_DESCRIPTION("Mellanox CPLD hotplug platform driver"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_ALIAS("platform:mlxcpld-hotplug");

Overall Contributors

PersonTokensPropCommitsCommitProp
Vadim Pasternak2145100.00%1100.00%
Total2145100.00%1100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.