]>
Commit | Line | Data |
---|---|---|
1236441f | 1 | /* |
5ed04880 GR |
2 | * hwmon.c - part of lm_sensors, Linux kernel modules for hardware monitoring |
3 | * | |
4 | * This file defines the sysfs class "hwmon", for use by sensors drivers. | |
5 | * | |
6 | * Copyright (C) 2005 Mark M. Hoffman <mhoffman@lightlink.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; version 2 of the License. | |
11 | */ | |
1236441f | 12 | |
c95df1ae JP |
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
14 | ||
1236441f MH |
15 | #include <linux/module.h> |
16 | #include <linux/device.h> | |
17 | #include <linux/err.h> | |
bab2243c | 18 | #include <linux/slab.h> |
1236441f MH |
19 | #include <linux/kdev_t.h> |
20 | #include <linux/idr.h> | |
21 | #include <linux/hwmon.h> | |
8c65b4a6 | 22 | #include <linux/gfp.h> |
ded2b666 | 23 | #include <linux/spinlock.h> |
2958b1ec | 24 | #include <linux/pci.h> |
1236441f MH |
25 | |
26 | #define HWMON_ID_PREFIX "hwmon" | |
27 | #define HWMON_ID_FORMAT HWMON_ID_PREFIX "%d" | |
28 | ||
bab2243c GR |
29 | struct hwmon_device { |
30 | const char *name; | |
31 | struct device dev; | |
32 | }; | |
33 | #define to_hwmon_device(d) container_of(d, struct hwmon_device, dev) | |
34 | ||
35 | static ssize_t | |
36 | show_name(struct device *dev, struct device_attribute *attr, char *buf) | |
37 | { | |
38 | return sprintf(buf, "%s\n", to_hwmon_device(dev)->name); | |
39 | } | |
40 | static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); | |
41 | ||
42 | static struct attribute *hwmon_dev_attrs[] = { | |
43 | &dev_attr_name.attr, | |
44 | NULL | |
45 | }; | |
46 | ||
47 | static umode_t hwmon_dev_name_is_visible(struct kobject *kobj, | |
48 | struct attribute *attr, int n) | |
49 | { | |
50 | struct device *dev = container_of(kobj, struct device, kobj); | |
51 | ||
52 | if (to_hwmon_device(dev)->name == NULL) | |
53 | return 0; | |
54 | ||
55 | return attr->mode; | |
56 | } | |
57 | ||
58 | static struct attribute_group hwmon_dev_attr_group = { | |
59 | .attrs = hwmon_dev_attrs, | |
60 | .is_visible = hwmon_dev_name_is_visible, | |
61 | }; | |
62 | ||
63 | static const struct attribute_group *hwmon_dev_attr_groups[] = { | |
64 | &hwmon_dev_attr_group, | |
65 | NULL | |
66 | }; | |
67 | ||
68 | static void hwmon_dev_release(struct device *dev) | |
69 | { | |
70 | kfree(to_hwmon_device(dev)); | |
71 | } | |
72 | ||
73 | static struct class hwmon_class = { | |
74 | .name = "hwmon", | |
75 | .owner = THIS_MODULE, | |
76 | .dev_groups = hwmon_dev_attr_groups, | |
77 | .dev_release = hwmon_dev_release, | |
78 | }; | |
1236441f | 79 | |
4ca5f468 | 80 | static DEFINE_IDA(hwmon_ida); |
1236441f MH |
81 | |
82 | /** | |
bab2243c GR |
83 | * hwmon_device_register_with_groups - register w/ hwmon |
84 | * @dev: the parent device | |
85 | * @name: hwmon name attribute | |
86 | * @drvdata: driver data to attach to created device | |
87 | * @groups: List of attribute groups to create | |
1236441f | 88 | * |
1beeffe4 | 89 | * hwmon_device_unregister() must be called when the device is no |
1236441f MH |
90 | * longer needed. |
91 | * | |
1beeffe4 | 92 | * Returns the pointer to the new device. |
1236441f | 93 | */ |
bab2243c GR |
94 | struct device * |
95 | hwmon_device_register_with_groups(struct device *dev, const char *name, | |
96 | void *drvdata, | |
97 | const struct attribute_group **groups) | |
1236441f | 98 | { |
bab2243c GR |
99 | struct hwmon_device *hwdev; |
100 | int err, id; | |
ded2b666 | 101 | |
4ca5f468 JC |
102 | id = ida_simple_get(&hwmon_ida, 0, 0, GFP_KERNEL); |
103 | if (id < 0) | |
104 | return ERR_PTR(id); | |
1236441f | 105 | |
bab2243c GR |
106 | hwdev = kzalloc(sizeof(*hwdev), GFP_KERNEL); |
107 | if (hwdev == NULL) { | |
108 | err = -ENOMEM; | |
109 | goto ida_remove; | |
110 | } | |
1236441f | 111 | |
bab2243c GR |
112 | hwdev->name = name; |
113 | hwdev->dev.class = &hwmon_class; | |
114 | hwdev->dev.parent = dev; | |
115 | hwdev->dev.groups = groups; | |
116 | hwdev->dev.of_node = dev ? dev->of_node : NULL; | |
117 | dev_set_drvdata(&hwdev->dev, drvdata); | |
118 | dev_set_name(&hwdev->dev, HWMON_ID_FORMAT, id); | |
119 | err = device_register(&hwdev->dev); | |
120 | if (err) | |
121 | goto free; | |
122 | ||
123 | return &hwdev->dev; | |
124 | ||
125 | free: | |
126 | kfree(hwdev); | |
127 | ida_remove: | |
128 | ida_simple_remove(&hwmon_ida, id); | |
129 | return ERR_PTR(err); | |
130 | } | |
131 | EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups); | |
1236441f | 132 | |
bab2243c GR |
133 | /** |
134 | * hwmon_device_register - register w/ hwmon | |
135 | * @dev: the device to register | |
136 | * | |
137 | * hwmon_device_unregister() must be called when the device is no | |
138 | * longer needed. | |
139 | * | |
140 | * Returns the pointer to the new device. | |
141 | */ | |
142 | struct device *hwmon_device_register(struct device *dev) | |
143 | { | |
144 | return hwmon_device_register_with_groups(dev, NULL, NULL, NULL); | |
1236441f | 145 | } |
839a9eef | 146 | EXPORT_SYMBOL_GPL(hwmon_device_register); |
1236441f MH |
147 | |
148 | /** | |
149 | * hwmon_device_unregister - removes the previously registered class device | |
150 | * | |
1beeffe4 | 151 | * @dev: the class device to destroy |
1236441f | 152 | */ |
1beeffe4 | 153 | void hwmon_device_unregister(struct device *dev) |
1236441f MH |
154 | { |
155 | int id; | |
156 | ||
739cf3a2 | 157 | if (likely(sscanf(dev_name(dev), HWMON_ID_FORMAT, &id) == 1)) { |
1beeffe4 | 158 | device_unregister(dev); |
4ca5f468 | 159 | ida_simple_remove(&hwmon_ida, id); |
1236441f | 160 | } else |
1beeffe4 | 161 | dev_dbg(dev->parent, |
1236441f MH |
162 | "hwmon_device_unregister() failed: bad class ID!\n"); |
163 | } | |
839a9eef | 164 | EXPORT_SYMBOL_GPL(hwmon_device_unregister); |
1236441f | 165 | |
74188cba GR |
166 | static void devm_hwmon_release(struct device *dev, void *res) |
167 | { | |
168 | struct device *hwdev = *(struct device **)res; | |
169 | ||
170 | hwmon_device_unregister(hwdev); | |
171 | } | |
172 | ||
173 | /** | |
174 | * devm_hwmon_device_register_with_groups - register w/ hwmon | |
175 | * @dev: the parent device | |
176 | * @name: hwmon name attribute | |
177 | * @drvdata: driver data to attach to created device | |
178 | * @groups: List of attribute groups to create | |
179 | * | |
180 | * Returns the pointer to the new device. The new device is automatically | |
181 | * unregistered with the parent device. | |
182 | */ | |
183 | struct device * | |
184 | devm_hwmon_device_register_with_groups(struct device *dev, const char *name, | |
185 | void *drvdata, | |
186 | const struct attribute_group **groups) | |
187 | { | |
188 | struct device **ptr, *hwdev; | |
189 | ||
190 | if (!dev) | |
191 | return ERR_PTR(-EINVAL); | |
192 | ||
193 | ptr = devres_alloc(devm_hwmon_release, sizeof(*ptr), GFP_KERNEL); | |
194 | if (!ptr) | |
195 | return ERR_PTR(-ENOMEM); | |
196 | ||
197 | hwdev = hwmon_device_register_with_groups(dev, name, drvdata, groups); | |
198 | if (IS_ERR(hwdev)) | |
199 | goto error; | |
200 | ||
201 | *ptr = hwdev; | |
202 | devres_add(dev, ptr); | |
203 | return hwdev; | |
204 | ||
205 | error: | |
206 | devres_free(ptr); | |
207 | return hwdev; | |
208 | } | |
209 | EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_groups); | |
210 | ||
211 | static int devm_hwmon_match(struct device *dev, void *res, void *data) | |
212 | { | |
213 | struct device **hwdev = res; | |
214 | ||
215 | return *hwdev == data; | |
216 | } | |
217 | ||
218 | /** | |
219 | * devm_hwmon_device_unregister - removes a previously registered hwmon device | |
220 | * | |
221 | * @dev: the parent device of the device to unregister | |
222 | */ | |
223 | void devm_hwmon_device_unregister(struct device *dev) | |
224 | { | |
225 | WARN_ON(devres_release(dev, devm_hwmon_release, devm_hwmon_match, dev)); | |
226 | } | |
227 | EXPORT_SYMBOL_GPL(devm_hwmon_device_unregister); | |
228 | ||
2958b1ec JD |
229 | static void __init hwmon_pci_quirks(void) |
230 | { | |
231 | #if defined CONFIG_X86 && defined CONFIG_PCI | |
232 | struct pci_dev *sb; | |
233 | u16 base; | |
234 | u8 enable; | |
235 | ||
236 | /* Open access to 0x295-0x296 on MSI MS-7031 */ | |
237 | sb = pci_get_device(PCI_VENDOR_ID_ATI, 0x436c, NULL); | |
d6dab7dd JD |
238 | if (sb) { |
239 | if (sb->subsystem_vendor == 0x1462 && /* MSI */ | |
240 | sb->subsystem_device == 0x0031) { /* MS-7031 */ | |
241 | pci_read_config_byte(sb, 0x48, &enable); | |
242 | pci_read_config_word(sb, 0x64, &base); | |
243 | ||
244 | if (base == 0 && !(enable & BIT(2))) { | |
245 | dev_info(&sb->dev, | |
246 | "Opening wide generic port at 0x295\n"); | |
247 | pci_write_config_word(sb, 0x64, 0x295); | |
248 | pci_write_config_byte(sb, 0x48, | |
249 | enable | BIT(2)); | |
250 | } | |
2958b1ec | 251 | } |
d6dab7dd | 252 | pci_dev_put(sb); |
2958b1ec JD |
253 | } |
254 | #endif | |
255 | } | |
256 | ||
1236441f MH |
257 | static int __init hwmon_init(void) |
258 | { | |
bab2243c GR |
259 | int err; |
260 | ||
2958b1ec JD |
261 | hwmon_pci_quirks(); |
262 | ||
bab2243c GR |
263 | err = class_register(&hwmon_class); |
264 | if (err) { | |
265 | pr_err("couldn't register hwmon sysfs class\n"); | |
266 | return err; | |
1236441f MH |
267 | } |
268 | return 0; | |
269 | } | |
270 | ||
271 | static void __exit hwmon_exit(void) | |
272 | { | |
bab2243c | 273 | class_unregister(&hwmon_class); |
1236441f MH |
274 | } |
275 | ||
37f54ee5 | 276 | subsys_initcall(hwmon_init); |
1236441f MH |
277 | module_exit(hwmon_exit); |
278 | ||
1236441f MH |
279 | MODULE_AUTHOR("Mark M. Hoffman <mhoffman@lightlink.com>"); |
280 | MODULE_DESCRIPTION("hardware monitoring sysfs/class support"); | |
281 | MODULE_LICENSE("GPL"); | |
282 |