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