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