linux/drivers/platform/x86/mlxcpld-hotplug.c
<<
>>
Prefs
   1/*
   2 * drivers/platform/x86/mlxcpld-hotplug.c
   3 * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
   4 * Copyright (c) 2016 Vadim Pasternak <vadimp@mellanox.com>
   5 *
   6 * Redistribution and use in source and binary forms, with or without
   7 * modification, are permitted provided that the following conditions are met:
   8 *
   9 * 1. Redistributions of source code must retain the above copyright
  10 *    notice, this list of conditions and the following disclaimer.
  11 * 2. Redistributions in binary form must reproduce the above copyright
  12 *    notice, this list of conditions and the following disclaimer in the
  13 *    documentation and/or other materials provided with the distribution.
  14 * 3. Neither the names of the copyright holders nor the names of its
  15 *    contributors may be used to endorse or promote products derived from
  16 *    this software without specific prior written permission.
  17 *
  18 * Alternatively, this software may be distributed under the terms of the
  19 * GNU General Public License ("GPL") version 2 as published by the Free
  20 * Software Foundation.
  21 *
  22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  32 * POSSIBILITY OF SUCH DAMAGE.
  33 */
  34
  35#include <linux/bitops.h>
  36#include <linux/device.h>
  37#include <linux/hwmon.h>
  38#include <linux/hwmon-sysfs.h>
  39#include <linux/i2c.h>
  40#include <linux/interrupt.h>
  41#include <linux/io.h>
  42#include <linux/module.h>
  43#include <linux/platform_data/mlxcpld-hotplug.h>
  44#include <linux/platform_device.h>
  45#include <linux/spinlock.h>
  46#include <linux/wait.h>
  47#include <linux/workqueue.h>
  48
  49/* Offset of event and mask registers from status register */
  50#define MLXCPLD_HOTPLUG_EVENT_OFF       1
  51#define MLXCPLD_HOTPLUG_MASK_OFF        2
  52#define MLXCPLD_HOTPLUG_AGGR_MASK_OFF   1
  53
  54#define MLXCPLD_HOTPLUG_ATTRS_NUM       8
  55
  56/**
  57 * enum mlxcpld_hotplug_attr_type - sysfs attributes for hotplug events:
  58 * @MLXCPLD_HOTPLUG_ATTR_TYPE_PSU: power supply unit attribute;
  59 * @MLXCPLD_HOTPLUG_ATTR_TYPE_PWR: power cable attribute;
  60 * @MLXCPLD_HOTPLUG_ATTR_TYPE_FAN: FAN drawer attribute;
  61 */
  62enum mlxcpld_hotplug_attr_type {
  63        MLXCPLD_HOTPLUG_ATTR_TYPE_PSU,
  64        MLXCPLD_HOTPLUG_ATTR_TYPE_PWR,
  65        MLXCPLD_HOTPLUG_ATTR_TYPE_FAN,
  66};
  67
  68/**
  69 * struct mlxcpld_hotplug_priv_data - platform private data:
  70 * @irq: platform interrupt number;
  71 * @pdev: platform device;
  72 * @plat: platform data;
  73 * @hwmon: hwmon device;
  74 * @mlxcpld_hotplug_attr: sysfs attributes array;
  75 * @mlxcpld_hotplug_dev_attr: sysfs sensor device attribute array;
  76 * @group: sysfs attribute group;
  77 * @groups: list of sysfs attribute group for hwmon registration;
  78 * @dwork: delayed work template;
  79 * @lock: spin lock;
  80 * @aggr_cache: last value of aggregation register status;
  81 * @psu_cache: last value of PSU register status;
  82 * @pwr_cache: last value of power register status;
  83 * @fan_cache: last value of FAN register status;
  84 */
  85struct mlxcpld_hotplug_priv_data {
  86        int irq;
  87        struct platform_device *pdev;
  88        struct mlxcpld_hotplug_platform_data *plat;
  89        struct device *hwmon;
  90        struct attribute *mlxcpld_hotplug_attr[MLXCPLD_HOTPLUG_ATTRS_NUM + 1];
  91        struct sensor_device_attribute_2
  92                        mlxcpld_hotplug_dev_attr[MLXCPLD_HOTPLUG_ATTRS_NUM];
  93        struct attribute_group group;
  94        const struct attribute_group *groups[2];
  95        struct delayed_work dwork;
  96        spinlock_t lock;
  97        u8 aggr_cache;
  98        u8 psu_cache;
  99        u8 pwr_cache;
 100        u8 fan_cache;
 101};
 102
 103static ssize_t mlxcpld_hotplug_attr_show(struct device *dev,
 104                                         struct device_attribute *attr,
 105                                         char *buf)
 106{
 107        struct platform_device *pdev = to_platform_device(dev);
 108        struct mlxcpld_hotplug_priv_data *priv = platform_get_drvdata(pdev);
 109        int index = to_sensor_dev_attr_2(attr)->index;
 110        int nr = to_sensor_dev_attr_2(attr)->nr;
 111        u8 reg_val = 0;
 112
 113        switch (nr) {
 114        case MLXCPLD_HOTPLUG_ATTR_TYPE_PSU:
 115                /* Bit = 0 : PSU is present. */
 116                reg_val = !!!(inb(priv->plat->psu_reg_offset) & BIT(index));
 117                break;
 118
 119        case MLXCPLD_HOTPLUG_ATTR_TYPE_PWR:
 120                /* Bit = 1 : power cable is attached. */
 121                reg_val = !!(inb(priv->plat->pwr_reg_offset) & BIT(index %
 122                                                priv->plat->pwr_count));
 123                break;
 124
 125        case MLXCPLD_HOTPLUG_ATTR_TYPE_FAN:
 126                /* Bit = 0 : FAN is present. */
 127                reg_val = !!!(inb(priv->plat->fan_reg_offset) & BIT(index %
 128                                                priv->plat->fan_count));
 129                break;
 130        }
 131
 132        return sprintf(buf, "%u\n", reg_val);
 133}
 134
 135#define PRIV_ATTR(i) priv->mlxcpld_hotplug_attr[i]
 136#define PRIV_DEV_ATTR(i) priv->mlxcpld_hotplug_dev_attr[i]
 137static int mlxcpld_hotplug_attr_init(struct mlxcpld_hotplug_priv_data *priv)
 138{
 139        int num_attrs = priv->plat->psu_count + priv->plat->pwr_count +
 140                        priv->plat->fan_count;
 141        int i;
 142
 143        priv->group.attrs = devm_kzalloc(&priv->pdev->dev, num_attrs *
 144                                         sizeof(struct attribute *),
 145                                         GFP_KERNEL);
 146        if (!priv->group.attrs)
 147                return -ENOMEM;
 148
 149        for (i = 0; i < num_attrs; i++) {
 150                PRIV_ATTR(i) = &PRIV_DEV_ATTR(i).dev_attr.attr;
 151
 152                if (i < priv->plat->psu_count) {
 153                        PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev,
 154                                                GFP_KERNEL, "psu%u", i + 1);
 155                        PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_PSU;
 156                } else if (i < priv->plat->psu_count + priv->plat->pwr_count) {
 157                        PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev,
 158                                                GFP_KERNEL, "pwr%u", i %
 159                                                priv->plat->pwr_count + 1);
 160                        PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_PWR;
 161                } else {
 162                        PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev,
 163                                                GFP_KERNEL, "fan%u", i %
 164                                                priv->plat->fan_count + 1);
 165                        PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_FAN;
 166                }
 167
 168                if (!PRIV_ATTR(i)->name) {
 169                        dev_err(&priv->pdev->dev, "Memory allocation failed for sysfs attribute %d.\n",
 170                                i + 1);
 171                        return -ENOMEM;
 172                }
 173
 174                PRIV_DEV_ATTR(i).dev_attr.attr.name = PRIV_ATTR(i)->name;
 175                PRIV_DEV_ATTR(i).dev_attr.attr.mode = S_IRUGO;
 176                PRIV_DEV_ATTR(i).dev_attr.show = mlxcpld_hotplug_attr_show;
 177                PRIV_DEV_ATTR(i).index = i;
 178                sysfs_attr_init(&PRIV_DEV_ATTR(i).dev_attr.attr);
 179        }
 180
 181        priv->group.attrs = priv->mlxcpld_hotplug_attr;
 182        priv->groups[0] = &priv->group;
 183        priv->groups[1] = NULL;
 184
 185        return 0;
 186}
 187
 188static int mlxcpld_hotplug_device_create(struct device *dev,
 189                                         struct mlxcpld_hotplug_device *item)
 190{
 191        item->adapter = i2c_get_adapter(item->bus);
 192        if (!item->adapter) {
 193                dev_err(dev, "Failed to get adapter for bus %d\n",
 194                        item->bus);
 195                return -EFAULT;
 196        }
 197
 198        item->client = i2c_new_device(item->adapter, &item->brdinfo);
 199        if (!item->client) {
 200                dev_err(dev, "Failed to create client %s at bus %d at addr 0x%02x\n",
 201                        item->brdinfo.type, item->bus, item->brdinfo.addr);
 202                i2c_put_adapter(item->adapter);
 203                item->adapter = NULL;
 204                return -EFAULT;
 205        }
 206
 207        return 0;
 208}
 209
 210static void mlxcpld_hotplug_device_destroy(struct mlxcpld_hotplug_device *item)
 211{
 212        if (item->client) {
 213                i2c_unregister_device(item->client);
 214                item->client = NULL;
 215        }
 216
 217        if (item->adapter) {
 218                i2c_put_adapter(item->adapter);
 219                item->adapter = NULL;
 220        }
 221}
 222
 223static inline void
 224mlxcpld_hotplug_work_helper(struct device *dev,
 225                            struct mlxcpld_hotplug_device *item, u8 is_inverse,
 226                            u16 offset, u8 mask, u8 *cache)
 227{
 228        u8 val, asserted;
 229        int bit;
 230
 231        /* Mask event. */
 232        outb(0, offset + MLXCPLD_HOTPLUG_MASK_OFF);
 233        /* Read status. */
 234        val = inb(offset) & mask;
 235        asserted = *cache ^ val;
 236        *cache = val;
 237
 238        /*
 239         * Validate if item related to received signal type is valid.
 240         * It should never happen, excepted the situation when some
 241         * piece of hardware is broken. In such situation just produce
 242         * error message and return. Caller must continue to handle the
 243         * signals from other devices if any.
 244         */
 245        if (unlikely(!item)) {
 246                dev_err(dev, "False signal is received: register at offset 0x%02x, mask 0x%02x.\n",
 247                        offset, mask);
 248                return;
 249        }
 250
 251        for_each_set_bit(bit, (unsigned long *)&asserted, 8) {
 252                if (val & BIT(bit)) {
 253                        if (is_inverse)
 254                                mlxcpld_hotplug_device_destroy(item + bit);
 255                        else
 256                                mlxcpld_hotplug_device_create(dev, item + bit);
 257                } else {
 258                        if (is_inverse)
 259                                mlxcpld_hotplug_device_create(dev, item + bit);
 260                        else
 261                                mlxcpld_hotplug_device_destroy(item + bit);
 262                }
 263        }
 264
 265        /* Acknowledge event. */
 266        outb(0, offset + MLXCPLD_HOTPLUG_EVENT_OFF);
 267        /* Unmask event. */
 268        outb(mask, offset + MLXCPLD_HOTPLUG_MASK_OFF);
 269}
 270
 271/*
 272 * mlxcpld_hotplug_work_handler - performs traversing of CPLD interrupt
 273 * registers according to the below hierarchy schema:
 274 *
 275 *                   Aggregation registers (status/mask)
 276 * PSU registers:           *---*
 277 * *-----------------*      |   |
 278 * |status/event/mask|----->| * |
 279 * *-----------------*      |   |
 280 * Power registers:         |   |
 281 * *-----------------*      |   |
 282 * |status/event/mask|----->| * |---> CPU
 283 * *-----------------*      |   |
 284 * FAN registers:
 285 * *-----------------*      |   |
 286 * |status/event/mask|----->| * |
 287 * *-----------------*      |   |
 288 *                          *---*
 289 * In case some system changed are detected: FAN in/out, PSU in/out, power
 290 * cable attached/detached, relevant device is created or destroyed.
 291 */
 292static void mlxcpld_hotplug_work_handler(struct work_struct *work)
 293{
 294        struct mlxcpld_hotplug_priv_data *priv = container_of(work,
 295                                struct mlxcpld_hotplug_priv_data, dwork.work);
 296        u8 val, aggr_asserted;
 297        unsigned long flags;
 298
 299        /* Mask aggregation event. */
 300        outb(0, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF);
 301        /* Read aggregation status. */
 302        val = inb(priv->plat->top_aggr_offset) & priv->plat->top_aggr_mask;
 303        aggr_asserted = priv->aggr_cache ^ val;
 304        priv->aggr_cache = val;
 305
 306        /* Handle PSU configuration changes. */
 307        if (aggr_asserted & priv->plat->top_aggr_psu_mask)
 308                mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->psu,
 309                                            1, priv->plat->psu_reg_offset,
 310                                            priv->plat->psu_mask,
 311                                            &priv->psu_cache);
 312
 313        /* Handle power cable configuration changes. */
 314        if (aggr_asserted & priv->plat->top_aggr_pwr_mask)
 315                mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->pwr,
 316                                            0, priv->plat->pwr_reg_offset,
 317                                            priv->plat->pwr_mask,
 318                                            &priv->pwr_cache);
 319
 320        /* Handle FAN configuration changes. */
 321        if (aggr_asserted & priv->plat->top_aggr_fan_mask)
 322                mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->fan,
 323                                            1, priv->plat->fan_reg_offset,
 324                                            priv->plat->fan_mask,
 325                                            &priv->fan_cache);
 326
 327        if (aggr_asserted) {
 328                spin_lock_irqsave(&priv->lock, flags);
 329
 330                /*
 331                 * It is possible, that some signals have been inserted, while
 332                 * interrupt has been masked by mlxcpld_hotplug_work_handler.
 333                 * In this case such signals will be missed. In order to handle
 334                 * these signals delayed work is canceled and work task
 335                 * re-scheduled for immediate execution. It allows to handle
 336                 * missed signals, if any. In other case work handler just
 337                 * validates that no new signals have been received during
 338                 * masking.
 339                 */
 340                cancel_delayed_work(&priv->dwork);
 341                schedule_delayed_work(&priv->dwork, 0);
 342
 343                spin_unlock_irqrestore(&priv->lock, flags);
 344
 345                return;
 346        }
 347
 348        /* Unmask aggregation event (no need acknowledge). */
 349        outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset +
 350                                                MLXCPLD_HOTPLUG_AGGR_MASK_OFF);
 351}
 352
 353static void mlxcpld_hotplug_set_irq(struct mlxcpld_hotplug_priv_data *priv)
 354{
 355        /* Clear psu presense event. */
 356        outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
 357        /* Set psu initial status as mask and unmask psu event. */
 358        priv->psu_cache = priv->plat->psu_mask;
 359        outb(priv->plat->psu_mask, priv->plat->psu_reg_offset +
 360                                                MLXCPLD_HOTPLUG_MASK_OFF);
 361
 362        /* Clear power cable event. */
 363        outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
 364        /* Keep power initial status as zero and unmask power event. */
 365        outb(priv->plat->pwr_mask, priv->plat->pwr_reg_offset +
 366                                                MLXCPLD_HOTPLUG_MASK_OFF);
 367
 368        /* Clear fan presense event. */
 369        outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
 370        /* Set fan initial status as mask and unmask fan event. */
 371        priv->fan_cache = priv->plat->fan_mask;
 372        outb(priv->plat->fan_mask, priv->plat->fan_reg_offset +
 373                                                MLXCPLD_HOTPLUG_MASK_OFF);
 374
 375        /* Keep aggregation initial status as zero and unmask events. */
 376        outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset +
 377                                                MLXCPLD_HOTPLUG_AGGR_MASK_OFF);
 378
 379        /* Invoke work handler for initializing hot plug devices setting. */
 380        mlxcpld_hotplug_work_handler(&priv->dwork.work);
 381
 382        enable_irq(priv->irq);
 383}
 384
 385static void mlxcpld_hotplug_unset_irq(struct mlxcpld_hotplug_priv_data *priv)
 386{
 387        int i;
 388
 389        disable_irq(priv->irq);
 390        cancel_delayed_work_sync(&priv->dwork);
 391
 392        /* Mask aggregation event. */
 393        outb(0, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF);
 394
 395        /* Mask psu presense event. */
 396        outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF);
 397        /* Clear psu presense event. */
 398        outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
 399
 400        /* Mask power cable event. */
 401        outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF);
 402        /* Clear power cable event. */
 403        outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
 404
 405        /* Mask fan presense event. */
 406        outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF);
 407        /* Clear fan presense event. */
 408        outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
 409
 410        /* Remove all the attached devices. */
 411        for (i = 0; i < priv->plat->psu_count; i++)
 412                mlxcpld_hotplug_device_destroy(priv->plat->psu + i);
 413
 414        for (i = 0; i < priv->plat->pwr_count; i++)
 415                mlxcpld_hotplug_device_destroy(priv->plat->pwr + i);
 416
 417        for (i = 0; i < priv->plat->fan_count; i++)
 418                mlxcpld_hotplug_device_destroy(priv->plat->fan + i);
 419}
 420
 421static irqreturn_t mlxcpld_hotplug_irq_handler(int irq, void *dev)
 422{
 423        struct mlxcpld_hotplug_priv_data *priv =
 424                                (struct mlxcpld_hotplug_priv_data *)dev;
 425
 426        /* Schedule work task for immediate execution.*/
 427        schedule_delayed_work(&priv->dwork, 0);
 428
 429        return IRQ_HANDLED;
 430}
 431
 432static int mlxcpld_hotplug_probe(struct platform_device *pdev)
 433{
 434        struct mlxcpld_hotplug_platform_data *pdata;
 435        struct mlxcpld_hotplug_priv_data *priv;
 436        int err;
 437
 438        pdata = dev_get_platdata(&pdev->dev);
 439        if (!pdata) {
 440                dev_err(&pdev->dev, "Failed to get platform data.\n");
 441                return -EINVAL;
 442        }
 443
 444        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 445        if (!priv)
 446                return -ENOMEM;
 447
 448        priv->pdev = pdev;
 449        priv->plat = pdata;
 450
 451        priv->irq = platform_get_irq(pdev, 0);
 452        if (priv->irq < 0) {
 453                dev_err(&pdev->dev, "Failed to get platform irq: %d\n",
 454                        priv->irq);
 455                return priv->irq;
 456        }
 457
 458        err = devm_request_irq(&pdev->dev, priv->irq,
 459                                mlxcpld_hotplug_irq_handler, 0, pdev->name,
 460                                priv);
 461        if (err) {
 462                dev_err(&pdev->dev, "Failed to request irq: %d\n", err);
 463                return err;
 464        }
 465        disable_irq(priv->irq);
 466
 467        INIT_DELAYED_WORK(&priv->dwork, mlxcpld_hotplug_work_handler);
 468        spin_lock_init(&priv->lock);
 469
 470        err = mlxcpld_hotplug_attr_init(priv);
 471        if (err) {
 472                dev_err(&pdev->dev, "Failed to allocate attributes: %d\n", err);
 473                return err;
 474        }
 475
 476        priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev,
 477                                        "mlxcpld_hotplug", priv, priv->groups);
 478        if (IS_ERR(priv->hwmon)) {
 479                dev_err(&pdev->dev, "Failed to register hwmon device %ld\n",
 480                        PTR_ERR(priv->hwmon));
 481                return PTR_ERR(priv->hwmon);
 482        }
 483
 484        platform_set_drvdata(pdev, priv);
 485
 486        /* Perform initial interrupts setup. */
 487        mlxcpld_hotplug_set_irq(priv);
 488
 489        return 0;
 490}
 491
 492static int mlxcpld_hotplug_remove(struct platform_device *pdev)
 493{
 494        struct mlxcpld_hotplug_priv_data *priv = platform_get_drvdata(pdev);
 495
 496        /* Clean interrupts setup. */
 497        mlxcpld_hotplug_unset_irq(priv);
 498
 499        return 0;
 500}
 501
 502static struct platform_driver mlxcpld_hotplug_driver = {
 503        .driver = {
 504                .name = "mlxcpld-hotplug",
 505        },
 506        .probe = mlxcpld_hotplug_probe,
 507        .remove = mlxcpld_hotplug_remove,
 508};
 509
 510module_platform_driver(mlxcpld_hotplug_driver);
 511
 512MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
 513MODULE_DESCRIPTION("Mellanox CPLD hotplug platform driver");
 514MODULE_LICENSE("Dual BSD/GPL");
 515MODULE_ALIAS("platform:mlxcpld-hotplug");
 516