]>
Commit | Line | Data |
---|---|---|
1 | /* | |
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 | */ | |
12 | ||
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
14 | ||
15 | #include <linux/module.h> | |
16 | #include <linux/device.h> | |
17 | #include <linux/err.h> | |
18 | #include <linux/slab.h> | |
19 | #include <linux/kdev_t.h> | |
20 | #include <linux/idr.h> | |
21 | #include <linux/hwmon.h> | |
22 | #include <linux/gfp.h> | |
23 | #include <linux/spinlock.h> | |
24 | #include <linux/pci.h> | |
25 | #include <linux/string.h> | |
26 | ||
27 | #define HWMON_ID_PREFIX "hwmon" | |
28 | #define HWMON_ID_FORMAT HWMON_ID_PREFIX "%d" | |
29 | ||
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 | }; | |
80 | ||
81 | static DEFINE_IDA(hwmon_ida); | |
82 | ||
83 | /** | |
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 | |
89 | * | |
90 | * hwmon_device_unregister() must be called when the device is no | |
91 | * longer needed. | |
92 | * | |
93 | * Returns the pointer to the new device. | |
94 | */ | |
95 | struct device * | |
96 | hwmon_device_register_with_groups(struct device *dev, const char *name, | |
97 | void *drvdata, | |
98 | const struct attribute_group **groups) | |
99 | { | |
100 | struct hwmon_device *hwdev; | |
101 | int err, id; | |
102 | ||
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 | ||
107 | id = ida_simple_get(&hwmon_ida, 0, 0, GFP_KERNEL); | |
108 | if (id < 0) | |
109 | return ERR_PTR(id); | |
110 | ||
111 | hwdev = kzalloc(sizeof(*hwdev), GFP_KERNEL); | |
112 | if (hwdev == NULL) { | |
113 | err = -ENOMEM; | |
114 | goto ida_remove; | |
115 | } | |
116 | ||
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); | |
137 | ||
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); | |
150 | } | |
151 | EXPORT_SYMBOL_GPL(hwmon_device_register); | |
152 | ||
153 | /** | |
154 | * hwmon_device_unregister - removes the previously registered class device | |
155 | * | |
156 | * @dev: the class device to destroy | |
157 | */ | |
158 | void hwmon_device_unregister(struct device *dev) | |
159 | { | |
160 | int id; | |
161 | ||
162 | if (likely(sscanf(dev_name(dev), HWMON_ID_FORMAT, &id) == 1)) { | |
163 | device_unregister(dev); | |
164 | ida_simple_remove(&hwmon_ida, id); | |
165 | } else | |
166 | dev_dbg(dev->parent, | |
167 | "hwmon_device_unregister() failed: bad class ID!\n"); | |
168 | } | |
169 | EXPORT_SYMBOL_GPL(hwmon_device_unregister); | |
170 | ||
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 | ||
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); | |
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 | } | |
256 | } | |
257 | pci_dev_put(sb); | |
258 | } | |
259 | #endif | |
260 | } | |
261 | ||
262 | static int __init hwmon_init(void) | |
263 | { | |
264 | int err; | |
265 | ||
266 | hwmon_pci_quirks(); | |
267 | ||
268 | err = class_register(&hwmon_class); | |
269 | if (err) { | |
270 | pr_err("couldn't register hwmon sysfs class\n"); | |
271 | return err; | |
272 | } | |
273 | return 0; | |
274 | } | |
275 | ||
276 | static void __exit hwmon_exit(void) | |
277 | { | |
278 | class_unregister(&hwmon_class); | |
279 | } | |
280 | ||
281 | subsys_initcall(hwmon_init); | |
282 | module_exit(hwmon_exit); | |
283 | ||
284 | MODULE_AUTHOR("Mark M. Hoffman <[email protected]>"); | |
285 | MODULE_DESCRIPTION("hardware monitoring sysfs/class support"); | |
286 | MODULE_LICENSE("GPL"); | |
287 |