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