root/drivers/hwmon/i5k_amb.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. amb_reg_temp_status
  2. amb_reg_temp_min
  3. amb_reg_temp_mid
  4. amb_reg_temp_max
  5. amb_reg_temp
  6. amb_num_from_reg
  7. name_show
  8. amb_read_byte
  9. amb_write_byte
  10. show_amb_alarm
  11. store_amb_min
  12. store_amb_mid
  13. store_amb_max
  14. show_amb_min
  15. show_amb_mid
  16. show_amb_max
  17. show_amb_temp
  18. show_label
  19. i5k_amb_hwmon_init
  20. i5k_amb_add
  21. i5k_find_amb_registers
  22. i5k_channel_probe
  23. i5k_amb_probe
  24. i5k_amb_remove
  25. i5k_amb_init
  26. i5k_amb_exit

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

/* [<][>][^][v][top][bottom][index][help] */