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