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 <darrick.wong@oracle.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 name_show(struct device *dev, struct device_attribute *devattr,
 118                         char *buf)
 119{
 120        return sprintf(buf, "%s\n", DRVNAME);
 121}
 122
 123
 124static DEVICE_ATTR_RO(name);
 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(array3_size(num_ambs, KNOBS_PER_AMB,
 278                                          sizeof(*data->attrs)),
 279                              GFP_KERNEL);
 280        if (!data->attrs)
 281                return -ENOMEM;
 282        data->num_attrs = 0;
 283
 284        for (i = 0; i < MAX_MEM_CHANNELS; i++) {
 285                c = data->amb_present[i];
 286                for (j = 0; j < REAL_MAX_AMBS_PER_CHANNEL; j++, c >>= 1) {
 287                        struct i5k_device_attribute *iattr;
 288
 289                        k = amb_num_from_reg(i, j);
 290                        if (!(c & 0x1))
 291                                continue;
 292                        d++;
 293
 294                        /* sysfs label */
 295                        iattr = data->attrs + data->num_attrs;
 296                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 297                                 "temp%d_label", d);
 298                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 299                        iattr->s_attr.dev_attr.attr.mode = S_IRUGO;
 300                        iattr->s_attr.dev_attr.show = show_label;
 301                        iattr->s_attr.index = k;
 302                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 303                        res = device_create_file(&pdev->dev,
 304                                                 &iattr->s_attr.dev_attr);
 305                        if (res)
 306                                goto exit_remove;
 307                        data->num_attrs++;
 308
 309                        /* Temperature sysfs knob */
 310                        iattr = data->attrs + data->num_attrs;
 311                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 312                                 "temp%d_input", d);
 313                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 314                        iattr->s_attr.dev_attr.attr.mode = S_IRUGO;
 315                        iattr->s_attr.dev_attr.show = show_amb_temp;
 316                        iattr->s_attr.index = k;
 317                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 318                        res = device_create_file(&pdev->dev,
 319                                                 &iattr->s_attr.dev_attr);
 320                        if (res)
 321                                goto exit_remove;
 322                        data->num_attrs++;
 323
 324                        /* Temperature min sysfs knob */
 325                        iattr = data->attrs + data->num_attrs;
 326                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 327                                 "temp%d_min", d);
 328                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 329                        iattr->s_attr.dev_attr.attr.mode = S_IWUSR | S_IRUGO;
 330                        iattr->s_attr.dev_attr.show = show_amb_min;
 331                        iattr->s_attr.dev_attr.store = store_amb_min;
 332                        iattr->s_attr.index = k;
 333                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 334                        res = device_create_file(&pdev->dev,
 335                                                 &iattr->s_attr.dev_attr);
 336                        if (res)
 337                                goto exit_remove;
 338                        data->num_attrs++;
 339
 340                        /* Temperature mid sysfs knob */
 341                        iattr = data->attrs + data->num_attrs;
 342                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 343                                 "temp%d_mid", d);
 344                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 345                        iattr->s_attr.dev_attr.attr.mode = S_IWUSR | S_IRUGO;
 346                        iattr->s_attr.dev_attr.show = show_amb_mid;
 347                        iattr->s_attr.dev_attr.store = store_amb_mid;
 348                        iattr->s_attr.index = k;
 349                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 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 max sysfs knob */
 357                        iattr = data->attrs + data->num_attrs;
 358                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 359                                 "temp%d_max", d);
 360                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 361                        iattr->s_attr.dev_attr.attr.mode = S_IWUSR | S_IRUGO;
 362                        iattr->s_attr.dev_attr.show = show_amb_max;
 363                        iattr->s_attr.dev_attr.store = store_amb_max;
 364                        iattr->s_attr.index = k;
 365                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 366                        res = device_create_file(&pdev->dev,
 367                                                 &iattr->s_attr.dev_attr);
 368                        if (res)
 369                                goto exit_remove;
 370                        data->num_attrs++;
 371
 372                        /* Temperature alarm sysfs knob */
 373                        iattr = data->attrs + data->num_attrs;
 374                        snprintf(iattr->name, AMB_SYSFS_NAME_LEN,
 375                                 "temp%d_alarm", d);
 376                        iattr->s_attr.dev_attr.attr.name = iattr->name;
 377                        iattr->s_attr.dev_attr.attr.mode = S_IRUGO;
 378                        iattr->s_attr.dev_attr.show = show_amb_alarm;
 379                        iattr->s_attr.index = k;
 380                        sysfs_attr_init(&iattr->s_attr.dev_attr.attr);
 381                        res = device_create_file(&pdev->dev,
 382                                                 &iattr->s_attr.dev_attr);
 383                        if (res)
 384                                goto exit_remove;
 385                        data->num_attrs++;
 386                }
 387        }
 388
 389        res = device_create_file(&pdev->dev, &dev_attr_name);
 390        if (res)
 391                goto exit_remove;
 392
 393        data->hwmon_dev = hwmon_device_register(&pdev->dev);
 394        if (IS_ERR(data->hwmon_dev)) {
 395                res = PTR_ERR(data->hwmon_dev);
 396                goto exit_remove;
 397        }
 398
 399        return res;
 400
 401exit_remove:
 402        device_remove_file(&pdev->dev, &dev_attr_name);
 403        for (i = 0; i < data->num_attrs; i++)
 404                device_remove_file(&pdev->dev, &data->attrs[i].s_attr.dev_attr);
 405        kfree(data->attrs);
 406
 407        return res;
 408}
 409
 410static int i5k_amb_add(void)
 411{
 412        int res = -ENODEV;
 413
 414        /* only ever going to be one of these */
 415        amb_pdev = platform_device_alloc(DRVNAME, 0);
 416        if (!amb_pdev)
 417                return -ENOMEM;
 418
 419        res = platform_device_add(amb_pdev);
 420        if (res)
 421                goto err;
 422        return 0;
 423
 424err:
 425        platform_device_put(amb_pdev);
 426        return res;
 427}
 428
 429static int i5k_find_amb_registers(struct i5k_amb_data *data,
 430                                            unsigned long devid)
 431{
 432        struct pci_dev *pcidev;
 433        u32 val32;
 434        int res = -ENODEV;
 435
 436        /* Find AMB register memory space */
 437        pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
 438                                devid,
 439                                NULL);
 440        if (!pcidev)
 441                return -ENODEV;
 442
 443        if (pci_read_config_dword(pcidev, I5K_REG_AMB_BASE_ADDR, &val32))
 444                goto out;
 445        data->amb_base = val32;
 446
 447        if (pci_read_config_dword(pcidev, I5K_REG_AMB_LEN_ADDR, &val32))
 448                goto out;
 449        data->amb_len = val32;
 450
 451        /* Is it big enough? */
 452        if (data->amb_len < AMB_CONFIG_SIZE * MAX_AMBS) {
 453                dev_err(&pcidev->dev, "AMB region too small!\n");
 454                goto out;
 455        }
 456
 457        res = 0;
 458out:
 459        pci_dev_put(pcidev);
 460        return res;
 461}
 462
 463static int i5k_channel_probe(u16 *amb_present, unsigned long dev_id)
 464{
 465        struct pci_dev *pcidev;
 466        u16 val16;
 467        int res = -ENODEV;
 468
 469        /* Copy the DIMM presence map for these two channels */
 470        pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, dev_id, NULL);
 471        if (!pcidev)
 472                return -ENODEV;
 473
 474        if (pci_read_config_word(pcidev, I5K_REG_CHAN0_PRESENCE_ADDR, &val16))
 475                goto out;
 476        amb_present[0] = val16;
 477
 478        if (pci_read_config_word(pcidev, I5K_REG_CHAN1_PRESENCE_ADDR, &val16))
 479                goto out;
 480        amb_present[1] = val16;
 481
 482        res = 0;
 483
 484out:
 485        pci_dev_put(pcidev);
 486        return res;
 487}
 488
 489static struct {
 490        unsigned long err;
 491        unsigned long fbd0;
 492} chipset_ids[]  = {
 493        { PCI_DEVICE_ID_INTEL_5000_ERR, PCI_DEVICE_ID_INTEL_5000_FBD0 },
 494        { PCI_DEVICE_ID_INTEL_5400_ERR, PCI_DEVICE_ID_INTEL_5400_FBD0 },
 495        { 0, 0 }
 496};
 497
 498#ifdef MODULE
 499static const struct pci_device_id i5k_amb_ids[] = {
 500        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5000_ERR) },
 501        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5400_ERR) },
 502        { 0, }
 503};
 504MODULE_DEVICE_TABLE(pci, i5k_amb_ids);
 505#endif
 506
 507static int i5k_amb_probe(struct platform_device *pdev)
 508{
 509        struct i5k_amb_data *data;
 510        struct resource *reso;
 511        int i, res;
 512
 513        data = kzalloc(sizeof(*data), GFP_KERNEL);
 514        if (!data)
 515                return -ENOMEM;
 516
 517        /* Figure out where the AMB registers live */
 518        i = 0;
 519        do {
 520                res = i5k_find_amb_registers(data, chipset_ids[i].err);
 521                if (res == 0)
 522                        break;
 523                i++;
 524        } while (chipset_ids[i].err);
 525
 526        if (res)
 527                goto err;
 528
 529        /* Copy the DIMM presence map for the first two channels */
 530        res = i5k_channel_probe(&data->amb_present[0], chipset_ids[i].fbd0);
 531        if (res)
 532                goto err;
 533
 534        /* Copy the DIMM presence map for the optional second two channels */
 535        i5k_channel_probe(&data->amb_present[2], chipset_ids[i].fbd0 + 1);
 536
 537        /* Set up resource regions */
 538        reso = request_mem_region(data->amb_base, data->amb_len, DRVNAME);
 539        if (!reso) {
 540                res = -EBUSY;
 541                goto err;
 542        }
 543
 544        data->amb_mmio = ioremap_nocache(data->amb_base, data->amb_len);
 545        if (!data->amb_mmio) {
 546                res = -EBUSY;
 547                goto err_map_failed;
 548        }
 549
 550        platform_set_drvdata(pdev, data);
 551
 552        res = i5k_amb_hwmon_init(pdev);
 553        if (res)
 554                goto err_init_failed;
 555
 556        return res;
 557
 558err_init_failed:
 559        iounmap(data->amb_mmio);
 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        kfree(data);
 580        return 0;
 581}
 582
 583static struct platform_driver i5k_amb_driver = {
 584        .driver = {
 585                .name = DRVNAME,
 586        },
 587        .probe = i5k_amb_probe,
 588        .remove = i5k_amb_remove,
 589};
 590
 591static int __init i5k_amb_init(void)
 592{
 593        int res;
 594
 595        res = platform_driver_register(&i5k_amb_driver);
 596        if (res)
 597                return res;
 598
 599        res = i5k_amb_add();
 600        if (res)
 601                platform_driver_unregister(&i5k_amb_driver);
 602
 603        return res;
 604}
 605
 606static void __exit i5k_amb_exit(void)
 607{
 608        platform_device_unregister(amb_pdev);
 609        platform_driver_unregister(&i5k_amb_driver);
 610}
 611
 612MODULE_AUTHOR("Darrick J. Wong <darrick.wong@oracle.com>");
 613MODULE_DESCRIPTION("Intel 5000 chipset FB-DIMM AMB temperature sensor");
 614MODULE_LICENSE("GPL");
 615
 616module_init(i5k_amb_init);
 617module_exit(i5k_amb_exit);
 618