linux/drivers/hwmon/i5k_amb.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * A hwmon driver for the Intel 5000 series chipset FB-DIMM AMB
   4 * temperature sensors
   5 * Copyright (C) 2007 IBM
   6 *
   7 * Author: Darrick J. Wong <darrick.wong@oracle.com>
   8 */
   9
  10#include <linux/module.h>
  11#include <linux/hwmon.h>
  12#include <linux/hwmon-sysfs.h>
  13#include <linux/err.h>
  14#include <linux/mutex.h>
  15#include <linux/log2.h>
  16#include <linux/pci.h>
  17#include <linux/platform_device.h>
  18#include <linux/slab.h>
  19
  20#define DRVNAME "i5k_amb"
  21
  22#define I5K_REG_AMB_BASE_ADDR           0x48
  23#define I5K_REG_AMB_LEN_ADDR            0x50
  24#define I5K_REG_CHAN0_PRESENCE_ADDR     0x64
  25#define I5K_REG_CHAN1_PRESENCE_ADDR     0x66
  26
  27#define AMB_REG_TEMP_MIN_ADDR           0x80
  28#define AMB_REG_TEMP_MID_ADDR           0x81
  29#define AMB_REG_TEMP_MAX_ADDR           0x82
  30#define AMB_REG_TEMP_STATUS_ADDR        0x84
  31#define AMB_REG_TEMP_ADDR               0x85
  32
  33#define AMB_CONFIG_SIZE                 2048
  34#define AMB_FUNC_3_OFFSET               768
  35
  36static unsigned long amb_reg_temp_status(unsigned int amb)
  37{
  38        return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_STATUS_ADDR +
  39               AMB_CONFIG_SIZE * amb;
  40}
  41
  42static unsigned long amb_reg_temp_min(unsigned int amb)
  43{
  44        return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MIN_ADDR +
  45               AMB_CONFIG_SIZE * amb;
  46}
  47
  48static unsigned long amb_reg_temp_mid(unsigned int amb)
  49{
  50        return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MID_ADDR +
  51               AMB_CONFIG_SIZE * amb;
  52}
  53
  54static unsigned long amb_reg_temp_max(unsigned int amb)
  55{
  56        return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_MAX_ADDR +
  57               AMB_CONFIG_SIZE * amb;
  58}
  59
  60static unsigned long amb_reg_temp(unsigned int amb)
  61{
  62        return AMB_FUNC_3_OFFSET + AMB_REG_TEMP_ADDR +
  63               AMB_CONFIG_SIZE * amb;
  64}
  65
  66#define MAX_MEM_CHANNELS                4
  67#define MAX_AMBS_PER_CHANNEL            16
  68#define MAX_AMBS                        (MAX_MEM_CHANNELS * \
  69                                         MAX_AMBS_PER_CHANNEL)
  70#define CHANNEL_SHIFT                   4
  71#define DIMM_MASK                       0xF
  72/*
  73 * Ugly hack: For some reason the highest bit is set if there
  74 * are _any_ DIMMs in the channel.  Attempting to read from
  75 * this "high-order" AMB results in a memory bus error, so
  76 * for now we'll just ignore that top bit, even though that
  77 * might prevent us from seeing the 16th DIMM in the channel.
  78 */
  79#define REAL_MAX_AMBS_PER_CHANNEL       15
  80#define KNOBS_PER_AMB                   6
  81
  82static unsigned long amb_num_from_reg(unsigned int byte_num, unsigned int bit)
  83{
  84        return byte_num * MAX_AMBS_PER_CHANNEL + bit;
  85}
  86
  87#define AMB_SYSFS_NAME_LEN              16
  88struct i5k_device_attribute {
  89        struct sensor_device_attribute s_attr;
  90        char name[AMB_SYSFS_NAME_LEN];
  91};
  92
  93struct i5k_amb_data {
  94        struct device *hwmon_dev;
  95
  96        unsigned long amb_base;
  97        unsigned long amb_len;
  98        u16 amb_present[MAX_MEM_CHANNELS];
  99        void __iomem *amb_mmio;
 100        struct i5k_device_attribute *attrs;
 101        unsigned int num_attrs;
 102};
 103
 104static ssize_t name_show(struct device *dev, struct device_attribute *devattr,
 105                         char *buf)
 106{
 107        return sprintf(buf, "%s\n", DRVNAME);
 108}
 109
 110
 111static DEVICE_ATTR_RO(name);
 112
 113static struct platform_device *amb_pdev;
 114
 115static u8 amb_read_byte(struct i5k_amb_data *data, unsigned long offset)
 116{
 117        return ioread8(data->amb_mmio + offset);
 118}
 119
 120static void amb_write_byte(struct i5k_amb_data *data, unsigned long offset,
 121                           u8 val)
 122{
 123        iowrite8(val, data->amb_mmio + offset);
 124}
 125
 126static ssize_t show_amb_alarm(struct device *dev,
 127                             struct device_attribute *devattr,
 128                             char *buf)
 129{
 130        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 131        struct i5k_amb_data *data = dev_get_drvdata(dev);
 132
 133        if (!(amb_read_byte(data, amb_reg_temp_status(attr->index)) & 0x20) &&
 134             (amb_read_byte(data, amb_reg_temp_status(attr->index)) & 0x8))
 135                return sprintf(buf, "1\n");
 136        else
 137                return sprintf(buf, "0\n");
 138}
 139
 140static ssize_t store_amb_min(struct device *dev,
 141                             struct device_attribute *devattr,
 142                             const char *buf,
 143                             size_t count)
 144{
 145        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 146        struct i5k_amb_data *data = dev_get_drvdata(dev);
 147        unsigned long temp;
 148        int ret = kstrtoul(buf, 10, &temp);
 149        if (ret < 0)
 150                return ret;
 151
 152        temp = temp / 500;
 153        if (temp > 255)
 154                temp = 255;
 155
 156        amb_write_byte(data, amb_reg_temp_min(attr->index), temp);
 157        return count;
 158}
 159
 160static ssize_t store_amb_mid(struct device *dev,
 161                             struct device_attribute *devattr,
 162                             const char *buf,
 163                             size_t count)
 164{
 165        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 166        struct i5k_amb_data *data = dev_get_drvdata(dev);
 167        unsigned long temp;
 168        int ret = kstrtoul(buf, 10, &temp);
 169        if (ret < 0)
 170                return ret;
 171
 172        temp = temp / 500;
 173        if (temp > 255)
 174                temp = 255;
 175
 176        amb_write_byte(data, amb_reg_temp_mid(attr->index), temp);
 177        return count;
 178}
 179
 180static ssize_t store_amb_max(struct device *dev,
 181                             struct device_attribute *devattr,
 182                             const char *buf,
 183                             size_t count)
 184{
 185        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 186        struct i5k_amb_data *data = dev_get_drvdata(dev);
 187        unsigned long temp;
 188        int ret = kstrtoul(buf, 10, &temp);
 189        if (ret < 0)
 190                return ret;
 191
 192        temp = temp / 500;
 193        if (temp > 255)
 194                temp = 255;
 195
 196        amb_write_byte(data, amb_reg_temp_max(attr->index), temp);
 197        return count;
 198}
 199
 200static ssize_t show_amb_min(struct device *dev,
 201                             struct device_attribute *devattr,
 202                             char *buf)
 203{
 204        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 205        struct i5k_amb_data *data = dev_get_drvdata(dev);
 206        return sprintf(buf, "%d\n",
 207                500 * amb_read_byte(data, amb_reg_temp_min(attr->index)));
 208}
 209
 210static ssize_t show_amb_mid(struct device *dev,
 211                             struct device_attribute *devattr,
 212                             char *buf)
 213{
 214        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 215        struct i5k_amb_data *data = dev_get_drvdata(dev);
 216        return sprintf(buf, "%d\n",
 217                500 * amb_read_byte(data, amb_reg_temp_mid(attr->index)));
 218}
 219
 220static ssize_t show_amb_max(struct device *dev,
 221                             struct device_attribute *devattr,
 222                             char *buf)
 223{
 224        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 225        struct i5k_amb_data *data = dev_get_drvdata(dev);
 226        return sprintf(buf, "%d\n",
 227                500 * amb_read_byte(data, amb_reg_temp_max(attr->index)));
 228}
 229
 230static ssize_t show_amb_temp(struct device *dev,
 231                             struct device_attribute *devattr,
 232                             char *buf)
 233{
 234        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 235        struct i5k_amb_data *data = dev_get_drvdata(dev);
 236        return sprintf(buf, "%d\n",
 237                500 * amb_read_byte(data, amb_reg_temp(attr->index)));
 238}
 239
 240static ssize_t show_label(struct device *dev,
 241                          struct device_attribute *devattr,
 242                          char *buf)
 243{
 244        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
 245
 246        return sprintf(buf, "Ch. %d DIMM %d\n", attr->index >> CHANNEL_SHIFT,
 247                       attr->index & DIMM_MASK);
 248}
 249
 250static int i5k_amb_hwmon_init(struct platform_device *pdev)
 251{
 252        int i, j, k, d = 0;
 253        u16 c;
 254        int res = 0;
 255        int num_ambs = 0;
 256        struct i5k_amb_data *data = platform_get_drvdata(pdev);
 257
 258        /* Count the number of AMBs found */
 259        /* ignore the high-order bit, see "Ugly hack" comment above */
 260        for (i = 0; i < MAX_MEM_CHANNELS; i++)
 261                num_ambs += hweight16(data->amb_present[i] & 0x7fff);
 262
 263        /* Set up sysfs stuff */
 264        data->attrs = kzalloc(array3_size(num_ambs, KNOBS_PER_AMB,
 265                                          sizeof(*data->attrs)),
 266                              GFP_KERNEL);
 267        if (!data->attrs)
 268                return -ENOMEM;
 269        data->num_attrs = 0;
 270
 271        for (i = 0; i < MAX_MEM_CHANNELS; i++) {
 272                c = data->amb_present[i];
 273                for (j = 0; j < REAL_MAX_AMBS_PER_CHANNEL; j++, c >>= 1) {
 274                        struct i5k_device_attribute *iattr;
 275
 276                        k = amb_num_from_reg(i, j);
 277                        if (!(c & 0x1))
 278                                continue;
 279                        d++;
 280
 281                        /* sysfs label */
 282                        iattr = data->attrs + data->num_attrs;
 283                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 284                                 "temp%d_label", d);
 285                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 286                        iattr->s_attr.dev_attr.attr.mode = 0444;
 287                        iattr->s_attr.dev_attr.show = show_label;
 288                        iattr->s_attr.index = k;
 289                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 290                        res = device_create_file(&pdev->dev,
 291                                                 &iattr->s_attr.dev_attr);
 292                        if (res)
 293                                goto exit_remove;
 294                        data->num_attrs++;
 295
 296                        /* Temperature sysfs knob */
 297                        iattr = data->attrs + data->num_attrs;
 298                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 299                                 "temp%d_input", d);
 300                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 301                        iattr->s_attr.dev_attr.attr.mode = 0444;
 302                        iattr->s_attr.dev_attr.show = show_amb_temp;
 303                        iattr->s_attr.index = k;
 304                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 305                        res = device_create_file(&pdev->dev,
 306                                                 &iattr->s_attr.dev_attr);
 307                        if (res)
 308                                goto exit_remove;
 309                        data->num_attrs++;
 310
 311                        /* Temperature min sysfs knob */
 312                        iattr = data->attrs + data->num_attrs;
 313                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 314                                 "temp%d_min", d);
 315                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 316                        iattr->s_attr.dev_attr.attr.mode = 0644;
 317                        iattr->s_attr.dev_attr.show = show_amb_min;
 318                        iattr->s_attr.dev_attr.store = store_amb_min;
 319                        iattr->s_attr.index = k;
 320                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 321                        res = device_create_file(&pdev->dev,
 322                                                 &iattr->s_attr.dev_attr);
 323                        if (res)
 324                                goto exit_remove;
 325                        data->num_attrs++;
 326
 327                        /* Temperature mid sysfs knob */
 328                        iattr = data->attrs + data->num_attrs;
 329                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 330                                 "temp%d_mid", d);
 331                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 332                        iattr->s_attr.dev_attr.attr.mode = 0644;
 333                        iattr->s_attr.dev_attr.show = show_amb_mid;
 334                        iattr->s_attr.dev_attr.store = store_amb_mid;
 335                        iattr->s_attr.index = k;
 336                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 337                        res = device_create_file(&pdev->dev,
 338                                                 &iattr->s_attr.dev_attr);
 339                        if (res)
 340                                goto exit_remove;
 341                        data->num_attrs++;
 342
 343                        /* Temperature max sysfs knob */
 344                        iattr = data->attrs + data->num_attrs;
 345                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 346                                 "temp%d_max", d);
 347                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 348                        iattr->s_attr.dev_attr.attr.mode = 0644;
 349                        iattr->s_attr.dev_attr.show = show_amb_max;
 350                        iattr->s_attr.dev_attr.store = store_amb_max;
 351                        iattr->s_attr.index = k;
 352                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 353                        res = device_create_file(&pdev->dev,
 354                                                 &iattr->s_attr.dev_attr);
 355                        if (res)
 356                                goto exit_remove;
 357                        data->num_attrs++;
 358
 359                        /* Temperature alarm sysfs knob */
 360                        iattr = data->attrs + data->num_attrs;
 361                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 362                                 "temp%d_alarm", d);
 363                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 364                        iattr->s_attr.dev_attr.attr.mode = 0444;
 365                        iattr->s_attr.dev_attr.show = show_amb_alarm;
 366                        iattr->s_attr.index = k;
 367                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 368                        res = device_create_file(&pdev->dev,
 369                                                 &iattr->s_attr.dev_attr);
 370                        if (res)
 371                                goto exit_remove;
 372                        data->num_attrs++;
 373                }
 374        }
 375
 376        res = device_create_file(&pdev->dev, &dev_attr_name);
 377        if (res)
 378                goto exit_remove;
 379
 380        data->hwmon_dev = hwmon_device_register(&pdev->dev);
 381        if (IS_ERR(data->hwmon_dev)) {
 382                res = PTR_ERR(data->hwmon_dev);
 383                goto exit_remove;
 384        }
 385
 386        return res;
 387
 388exit_remove:
 389        device_remove_file(&pdev->dev, &dev_attr_name);
 390        for (i = 0; i < data->num_attrs; i++)
 391                device_remove_file(&pdev->dev, &data->attrs[i].s_attr.dev_attr);
 392        kfree(data->attrs);
 393
 394        return res;
 395}
 396
 397static int i5k_amb_add(void)
 398{
 399        int res;
 400
 401        /* only ever going to be one of these */
 402        amb_pdev = platform_device_alloc(DRVNAME, 0);
 403        if (!amb_pdev)
 404                return -ENOMEM;
 405
 406        res = platform_device_add(amb_pdev);
 407        if (res)
 408                goto err;
 409        return 0;
 410
 411err:
 412        platform_device_put(amb_pdev);
 413        return res;
 414}
 415
 416static int i5k_find_amb_registers(struct i5k_amb_data *data,
 417                                            unsigned long devid)
 418{
 419        struct pci_dev *pcidev;
 420        u32 val32;
 421        int res = -ENODEV;
 422
 423        /* Find AMB register memory space */
 424        pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
 425                                devid,
 426                                NULL);
 427        if (!pcidev)
 428                return -ENODEV;
 429
 430        pci_read_config_dword(pcidev, I5K_REG_AMB_BASE_ADDR, &val32);
 431        if (val32 == (u32)~0)
 432                goto out;
 433        data->amb_base = val32;
 434
 435        pci_read_config_dword(pcidev, I5K_REG_AMB_LEN_ADDR, &val32);
 436        if (val32 == (u32)~0)
 437                goto out;
 438        data->amb_len = val32;
 439
 440        /* Is it big enough? */
 441        if (data->amb_len < AMB_CONFIG_SIZE * MAX_AMBS) {
 442                dev_err(&pcidev->dev, "AMB region too small!\n");
 443                goto out;
 444        }
 445
 446        res = 0;
 447out:
 448        pci_dev_put(pcidev);
 449        return res;
 450}
 451
 452static int i5k_channel_probe(u16 *amb_present, unsigned long dev_id)
 453{
 454        struct pci_dev *pcidev;
 455        u16 val16;
 456        int res = -ENODEV;
 457
 458        /* Copy the DIMM presence map for these two channels */
 459        pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, dev_id, NULL);
 460        if (!pcidev)
 461                return -ENODEV;
 462
 463        pci_read_config_word(pcidev, I5K_REG_CHAN0_PRESENCE_ADDR, &val16);
 464        if (val16 == (u16)~0)
 465                goto out;
 466        amb_present[0] = val16;
 467
 468        pci_read_config_word(pcidev, I5K_REG_CHAN1_PRESENCE_ADDR, &val16);
 469        if (val16 == (u16)~0)
 470                goto out;
 471        amb_present[1] = val16;
 472
 473        res = 0;
 474
 475out:
 476        pci_dev_put(pcidev);
 477        return res;
 478}
 479
 480static struct {
 481        unsigned long err;
 482        unsigned long fbd0;
 483} chipset_ids[]  = {
 484        { PCI_DEVICE_ID_INTEL_5000_ERR, PCI_DEVICE_ID_INTEL_5000_FBD0 },
 485        { PCI_DEVICE_ID_INTEL_5400_ERR, PCI_DEVICE_ID_INTEL_5400_FBD0 },
 486        { 0, 0 }
 487};
 488
 489#ifdef MODULE
 490static const struct pci_device_id i5k_amb_ids[] = {
 491        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5000_ERR) },
 492        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5400_ERR) },
 493        { 0, }
 494};
 495MODULE_DEVICE_TABLE(pci, i5k_amb_ids);
 496#endif
 497
 498static int i5k_amb_probe(struct platform_device *pdev)
 499{
 500        struct i5k_amb_data *data;
 501        struct resource *reso;
 502        int i, res;
 503
 504        data = kzalloc(sizeof(*data), GFP_KERNEL);
 505        if (!data)
 506                return -ENOMEM;
 507
 508        /* Figure out where the AMB registers live */
 509        i = 0;
 510        do {
 511                res = i5k_find_amb_registers(data, chipset_ids[i].err);
 512                if (res == 0)
 513                        break;
 514                i++;
 515        } while (chipset_ids[i].err);
 516
 517        if (res)
 518                goto err;
 519
 520        /* Copy the DIMM presence map for the first two channels */
 521        res = i5k_channel_probe(&data->amb_present[0], chipset_ids[i].fbd0);
 522        if (res)
 523                goto err;
 524
 525        /* Copy the DIMM presence map for the optional second two channels */
 526        i5k_channel_probe(&data->amb_present[2], chipset_ids[i].fbd0 + 1);
 527
 528        /* Set up resource regions */
 529        reso = request_mem_region(data->amb_base, data->amb_len, DRVNAME);
 530        if (!reso) {
 531                res = -EBUSY;
 532                goto err;
 533        }
 534
 535        data->amb_mmio = ioremap(data->amb_base, data->amb_len);
 536        if (!data->amb_mmio) {
 537                res = -EBUSY;
 538                goto err_map_failed;
 539        }
 540
 541        platform_set_drvdata(pdev, data);
 542
 543        res = i5k_amb_hwmon_init(pdev);
 544        if (res)
 545                goto err_init_failed;
 546
 547        return res;
 548
 549err_init_failed:
 550        iounmap(data->amb_mmio);
 551err_map_failed:
 552        release_mem_region(data->amb_base, data->amb_len);
 553err:
 554        kfree(data);
 555        return res;
 556}
 557
 558static int i5k_amb_remove(struct platform_device *pdev)
 559{
 560        int i;
 561        struct i5k_amb_data *data = platform_get_drvdata(pdev);
 562
 563        hwmon_device_unregister(data->hwmon_dev);
 564        device_remove_file(&pdev->dev, &dev_attr_name);
 565        for (i = 0; i < data->num_attrs; i++)
 566                device_remove_file(&pdev->dev, &data->attrs[i].s_attr.dev_attr);
 567        kfree(data->attrs);
 568        iounmap(data->amb_mmio);
 569        release_mem_region(data->amb_base, data->amb_len);
 570        kfree(data);
 571        return 0;
 572}
 573
 574static struct platform_driver i5k_amb_driver = {
 575        .driver = {
 576                .name = DRVNAME,
 577        },
 578        .probe = i5k_amb_probe,
 579        .remove = i5k_amb_remove,
 580};
 581
 582static int __init i5k_amb_init(void)
 583{
 584        int res;
 585
 586        res = platform_driver_register(&i5k_amb_driver);
 587        if (res)
 588                return res;
 589
 590        res = i5k_amb_add();
 591        if (res)
 592                platform_driver_unregister(&i5k_amb_driver);
 593
 594        return res;
 595}
 596
 597static void __exit i5k_amb_exit(void)
 598{
 599        platform_device_unregister(amb_pdev);
 600        platform_driver_unregister(&i5k_amb_driver);
 601}
 602
 603MODULE_AUTHOR("Darrick J. Wong <darrick.wong@oracle.com>");
 604MODULE_DESCRIPTION("Intel 5000 chipset FB-DIMM AMB temperature sensor");
 605MODULE_LICENSE("GPL");
 606
 607module_init(i5k_amb_init);
 608module_exit(i5k_amb_exit);
 609