diff options
Diffstat (limited to 'drivers/extcon/extcon.c')
-rw-r--r-- | drivers/extcon/extcon.c | 1038 |
1 files changed, 1038 insertions, 0 deletions
diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c new file mode 100644 index 000000000000..8319f25b7145 --- /dev/null +++ b/drivers/extcon/extcon.c | |||
@@ -0,0 +1,1038 @@ | |||
1 | /* | ||
2 | * drivers/extcon/extcon_class.c | ||
3 | * | ||
4 | * External connector (extcon) class driver | ||
5 | * | ||
6 | * Copyright (C) 2012 Samsung Electronics | ||
7 | * Author: Donggeun Kim <dg77.kim@samsung.com> | ||
8 | * Author: MyungJoo Ham <myungjoo.ham@samsung.com> | ||
9 | * | ||
10 | * based on android/drivers/switch/switch_class.c | ||
11 | * Copyright (C) 2008 Google, Inc. | ||
12 | * Author: Mike Lockwood <lockwood@android.com> | ||
13 | * | ||
14 | * This software is licensed under the terms of the GNU General Public | ||
15 | * License version 2, as published by the Free Software Foundation, and | ||
16 | * may be copied, distributed, and modified under those terms. | ||
17 | * | ||
18 | * This program is distributed in the hope that it will be useful, | ||
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
21 | * GNU General Public License for more details. | ||
22 | * | ||
23 | */ | ||
24 | |||
25 | #include <linux/module.h> | ||
26 | #include <linux/types.h> | ||
27 | #include <linux/init.h> | ||
28 | #include <linux/device.h> | ||
29 | #include <linux/fs.h> | ||
30 | #include <linux/err.h> | ||
31 | #include <linux/extcon.h> | ||
32 | #include <linux/of.h> | ||
33 | #include <linux/slab.h> | ||
34 | #include <linux/sysfs.h> | ||
35 | |||
36 | /* | ||
37 | * extcon_cable_name suggests the standard cable names for commonly used | ||
38 | * cable types. | ||
39 | * | ||
40 | * However, please do not use extcon_cable_name directly for extcon_dev | ||
41 | * struct's supported_cable pointer unless your device really supports | ||
42 | * every single port-type of the following cable names. Please choose cable | ||
43 | * names that are actually used in your extcon device. | ||
44 | */ | ||
45 | const char extcon_cable_name[][CABLE_NAME_MAX + 1] = { | ||
46 | [EXTCON_USB] = "USB", | ||
47 | [EXTCON_USB_HOST] = "USB-Host", | ||
48 | [EXTCON_TA] = "TA", | ||
49 | [EXTCON_FAST_CHARGER] = "Fast-charger", | ||
50 | [EXTCON_SLOW_CHARGER] = "Slow-charger", | ||
51 | [EXTCON_CHARGE_DOWNSTREAM] = "Charge-downstream", | ||
52 | [EXTCON_HDMI] = "HDMI", | ||
53 | [EXTCON_MHL] = "MHL", | ||
54 | [EXTCON_DVI] = "DVI", | ||
55 | [EXTCON_VGA] = "VGA", | ||
56 | [EXTCON_DOCK] = "Dock", | ||
57 | [EXTCON_LINE_IN] = "Line-in", | ||
58 | [EXTCON_LINE_OUT] = "Line-out", | ||
59 | [EXTCON_MIC_IN] = "Microphone", | ||
60 | [EXTCON_HEADPHONE_OUT] = "Headphone", | ||
61 | [EXTCON_SPDIF_IN] = "SPDIF-in", | ||
62 | [EXTCON_SPDIF_OUT] = "SPDIF-out", | ||
63 | [EXTCON_VIDEO_IN] = "Video-in", | ||
64 | [EXTCON_VIDEO_OUT] = "Video-out", | ||
65 | [EXTCON_MECHANICAL] = "Mechanical", | ||
66 | }; | ||
67 | |||
68 | static struct class *extcon_class; | ||
69 | #if defined(CONFIG_ANDROID) | ||
70 | static struct class_compat *switch_class; | ||
71 | #endif /* CONFIG_ANDROID */ | ||
72 | |||
73 | static LIST_HEAD(extcon_dev_list); | ||
74 | static DEFINE_MUTEX(extcon_dev_list_lock); | ||
75 | |||
76 | /** | ||
77 | * check_mutually_exclusive - Check if new_state violates mutually_exclusive | ||
78 | * condition. | ||
79 | * @edev: the extcon device | ||
80 | * @new_state: new cable attach status for @edev | ||
81 | * | ||
82 | * Returns 0 if nothing violates. Returns the index + 1 for the first | ||
83 | * violated condition. | ||
84 | */ | ||
85 | static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state) | ||
86 | { | ||
87 | int i = 0; | ||
88 | |||
89 | if (!edev->mutually_exclusive) | ||
90 | return 0; | ||
91 | |||
92 | for (i = 0; edev->mutually_exclusive[i]; i++) { | ||
93 | int weight; | ||
94 | u32 correspondants = new_state & edev->mutually_exclusive[i]; | ||
95 | |||
96 | /* calculate the total number of bits set */ | ||
97 | weight = hweight32(correspondants); | ||
98 | if (weight > 1) | ||
99 | return i + 1; | ||
100 | } | ||
101 | |||
102 | return 0; | ||
103 | } | ||
104 | |||
105 | static ssize_t state_show(struct device *dev, struct device_attribute *attr, | ||
106 | char *buf) | ||
107 | { | ||
108 | int i, count = 0; | ||
109 | struct extcon_dev *edev = dev_get_drvdata(dev); | ||
110 | |||
111 | if (edev->print_state) { | ||
112 | int ret = edev->print_state(edev, buf); | ||
113 | |||
114 | if (ret >= 0) | ||
115 | return ret; | ||
116 | /* Use default if failed */ | ||
117 | } | ||
118 | |||
119 | if (edev->max_supported == 0) | ||
120 | return sprintf(buf, "%u\n", edev->state); | ||
121 | |||
122 | for (i = 0; i < SUPPORTED_CABLE_MAX; i++) { | ||
123 | if (!edev->supported_cable[i]) | ||
124 | break; | ||
125 | count += sprintf(buf + count, "%s=%d\n", | ||
126 | edev->supported_cable[i], | ||
127 | !!(edev->state & (1 << i))); | ||
128 | } | ||
129 | |||
130 | return count; | ||
131 | } | ||
132 | |||
133 | static ssize_t state_store(struct device *dev, struct device_attribute *attr, | ||
134 | const char *buf, size_t count) | ||
135 | { | ||
136 | u32 state; | ||
137 | ssize_t ret = 0; | ||
138 | struct extcon_dev *edev = dev_get_drvdata(dev); | ||
139 | |||
140 | ret = sscanf(buf, "0x%x", &state); | ||
141 | if (ret == 0) | ||
142 | ret = -EINVAL; | ||
143 | else | ||
144 | ret = extcon_set_state(edev, state); | ||
145 | |||
146 | if (ret < 0) | ||
147 | return ret; | ||
148 | |||
149 | return count; | ||
150 | } | ||
151 | static DEVICE_ATTR_RW(state); | ||
152 | |||
153 | static ssize_t name_show(struct device *dev, struct device_attribute *attr, | ||
154 | char *buf) | ||
155 | { | ||
156 | struct extcon_dev *edev = dev_get_drvdata(dev); | ||
157 | |||
158 | /* Optional callback given by the user */ | ||
159 | if (edev->print_name) { | ||
160 | int ret = edev->print_name(edev, buf); | ||
161 | if (ret >= 0) | ||
162 | return ret; | ||
163 | } | ||
164 | |||
165 | return sprintf(buf, "%s\n", dev_name(&edev->dev)); | ||
166 | } | ||
167 | static DEVICE_ATTR_RO(name); | ||
168 | |||
169 | static ssize_t cable_name_show(struct device *dev, | ||
170 | struct device_attribute *attr, char *buf) | ||
171 | { | ||
172 | struct extcon_cable *cable = container_of(attr, struct extcon_cable, | ||
173 | attr_name); | ||
174 | |||
175 | return sprintf(buf, "%s\n", | ||
176 | cable->edev->supported_cable[cable->cable_index]); | ||
177 | } | ||
178 | |||
179 | static ssize_t cable_state_show(struct device *dev, | ||
180 | struct device_attribute *attr, char *buf) | ||
181 | { | ||
182 | struct extcon_cable *cable = container_of(attr, struct extcon_cable, | ||
183 | attr_state); | ||
184 | |||
185 | return sprintf(buf, "%d\n", | ||
186 | extcon_get_cable_state_(cable->edev, | ||
187 | cable->cable_index)); | ||
188 | } | ||
189 | |||
190 | /** | ||
191 | * extcon_update_state() - Update the cable attach states of the extcon device | ||
192 | * only for the masked bits. | ||
193 | * @edev: the extcon device | ||
194 | * @mask: the bit mask to designate updated bits. | ||
195 | * @state: new cable attach status for @edev | ||
196 | * | ||
197 | * Changing the state sends uevent with environment variable containing | ||
198 | * the name of extcon device (envp[0]) and the state output (envp[1]). | ||
199 | * Tizen uses this format for extcon device to get events from ports. | ||
200 | * Android uses this format as well. | ||
201 | * | ||
202 | * Note that the notifier provides which bits are changed in the state | ||
203 | * variable with the val parameter (second) to the callback. | ||
204 | */ | ||
205 | int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) | ||
206 | { | ||
207 | char name_buf[120]; | ||
208 | char state_buf[120]; | ||
209 | char *prop_buf; | ||
210 | char *envp[3]; | ||
211 | int env_offset = 0; | ||
212 | int length; | ||
213 | unsigned long flags; | ||
214 | |||
215 | spin_lock_irqsave(&edev->lock, flags); | ||
216 | |||
217 | if (edev->state != ((edev->state & ~mask) | (state & mask))) { | ||
218 | u32 old_state = edev->state; | ||
219 | |||
220 | if (check_mutually_exclusive(edev, (edev->state & ~mask) | | ||
221 | (state & mask))) { | ||
222 | spin_unlock_irqrestore(&edev->lock, flags); | ||
223 | return -EPERM; | ||
224 | } | ||
225 | |||
226 | edev->state &= ~mask; | ||
227 | edev->state |= state & mask; | ||
228 | |||
229 | raw_notifier_call_chain(&edev->nh, old_state, edev); | ||
230 | /* This could be in interrupt handler */ | ||
231 | prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); | ||
232 | if (prop_buf) { | ||
233 | length = name_show(&edev->dev, NULL, prop_buf); | ||
234 | if (length > 0) { | ||
235 | if (prop_buf[length - 1] == '\n') | ||
236 | prop_buf[length - 1] = 0; | ||
237 | snprintf(name_buf, sizeof(name_buf), | ||
238 | "NAME=%s", prop_buf); | ||
239 | envp[env_offset++] = name_buf; | ||
240 | } | ||
241 | length = state_show(&edev->dev, NULL, prop_buf); | ||
242 | if (length > 0) { | ||
243 | if (prop_buf[length - 1] == '\n') | ||
244 | prop_buf[length - 1] = 0; | ||
245 | snprintf(state_buf, sizeof(state_buf), | ||
246 | "STATE=%s", prop_buf); | ||
247 | envp[env_offset++] = state_buf; | ||
248 | } | ||
249 | envp[env_offset] = NULL; | ||
250 | /* Unlock early before uevent */ | ||
251 | spin_unlock_irqrestore(&edev->lock, flags); | ||
252 | |||
253 | kobject_uevent_env(&edev->dev.kobj, KOBJ_CHANGE, envp); | ||
254 | free_page((unsigned long)prop_buf); | ||
255 | } else { | ||
256 | /* Unlock early before uevent */ | ||
257 | spin_unlock_irqrestore(&edev->lock, flags); | ||
258 | |||
259 | dev_err(&edev->dev, "out of memory in extcon_set_state\n"); | ||
260 | kobject_uevent(&edev->dev.kobj, KOBJ_CHANGE); | ||
261 | } | ||
262 | } else { | ||
263 | /* No changes */ | ||
264 | spin_unlock_irqrestore(&edev->lock, flags); | ||
265 | } | ||
266 | |||
267 | return 0; | ||
268 | } | ||
269 | EXPORT_SYMBOL_GPL(extcon_update_state); | ||
270 | |||
271 | /** | ||
272 | * extcon_set_state() - Set the cable attach states of the extcon device. | ||
273 | * @edev: the extcon device | ||
274 | * @state: new cable attach status for @edev | ||
275 | * | ||
276 | * Note that notifier provides which bits are changed in the state | ||
277 | * variable with the val parameter (second) to the callback. | ||
278 | */ | ||
279 | int extcon_set_state(struct extcon_dev *edev, u32 state) | ||
280 | { | ||
281 | return extcon_update_state(edev, 0xffffffff, state); | ||
282 | } | ||
283 | EXPORT_SYMBOL_GPL(extcon_set_state); | ||
284 | |||
285 | /** | ||
286 | * extcon_find_cable_index() - Get the cable index based on the cable name. | ||
287 | * @edev: the extcon device that has the cable. | ||
288 | * @cable_name: cable name to be searched. | ||
289 | * | ||
290 | * Note that accessing a cable state based on cable_index is faster than | ||
291 | * cable_name because using cable_name induces a loop with strncmp(). | ||
292 | * Thus, when get/set_cable_state is repeatedly used, using cable_index | ||
293 | * is recommended. | ||
294 | */ | ||
295 | int extcon_find_cable_index(struct extcon_dev *edev, const char *cable_name) | ||
296 | { | ||
297 | int i; | ||
298 | |||
299 | if (edev->supported_cable) { | ||
300 | for (i = 0; edev->supported_cable[i]; i++) { | ||
301 | if (!strncmp(edev->supported_cable[i], | ||
302 | cable_name, CABLE_NAME_MAX)) | ||
303 | return i; | ||
304 | } | ||
305 | } | ||
306 | |||
307 | return -EINVAL; | ||
308 | } | ||
309 | EXPORT_SYMBOL_GPL(extcon_find_cable_index); | ||
310 | |||
311 | /** | ||
312 | * extcon_get_cable_state_() - Get the status of a specific cable. | ||
313 | * @edev: the extcon device that has the cable. | ||
314 | * @index: cable index that can be retrieved by extcon_find_cable_index(). | ||
315 | */ | ||
316 | int extcon_get_cable_state_(struct extcon_dev *edev, int index) | ||
317 | { | ||
318 | if (index < 0 || (edev->max_supported && edev->max_supported <= index)) | ||
319 | return -EINVAL; | ||
320 | |||
321 | return !!(edev->state & (1 << index)); | ||
322 | } | ||
323 | EXPORT_SYMBOL_GPL(extcon_get_cable_state_); | ||
324 | |||
325 | /** | ||
326 | * extcon_get_cable_state() - Get the status of a specific cable. | ||
327 | * @edev: the extcon device that has the cable. | ||
328 | * @cable_name: cable name. | ||
329 | * | ||
330 | * Note that this is slower than extcon_get_cable_state_. | ||
331 | */ | ||
332 | int extcon_get_cable_state(struct extcon_dev *edev, const char *cable_name) | ||
333 | { | ||
334 | return extcon_get_cable_state_(edev, extcon_find_cable_index | ||
335 | (edev, cable_name)); | ||
336 | } | ||
337 | EXPORT_SYMBOL_GPL(extcon_get_cable_state); | ||
338 | |||
339 | /** | ||
340 | * extcon_set_cable_state_() - Set the status of a specific cable. | ||
341 | * @edev: the extcon device that has the cable. | ||
342 | * @index: cable index that can be retrieved by | ||
343 | * extcon_find_cable_index(). | ||
344 | * @cable_state: the new cable status. The default semantics is | ||
345 | * true: attached / false: detached. | ||
346 | */ | ||
347 | int extcon_set_cable_state_(struct extcon_dev *edev, | ||
348 | int index, bool cable_state) | ||
349 | { | ||
350 | u32 state; | ||
351 | |||
352 | if (index < 0 || (edev->max_supported && edev->max_supported <= index)) | ||
353 | return -EINVAL; | ||
354 | |||
355 | state = cable_state ? (1 << index) : 0; | ||
356 | return extcon_update_state(edev, 1 << index, state); | ||
357 | } | ||
358 | EXPORT_SYMBOL_GPL(extcon_set_cable_state_); | ||
359 | |||
360 | /** | ||
361 | * extcon_set_cable_state() - Set the status of a specific cable. | ||
362 | * @edev: the extcon device that has the cable. | ||
363 | * @cable_name: cable name. | ||
364 | * @cable_state: the new cable status. The default semantics is | ||
365 | * true: attached / false: detached. | ||
366 | * | ||
367 | * Note that this is slower than extcon_set_cable_state_. | ||
368 | */ | ||
369 | int extcon_set_cable_state(struct extcon_dev *edev, | ||
370 | const char *cable_name, bool cable_state) | ||
371 | { | ||
372 | return extcon_set_cable_state_(edev, extcon_find_cable_index | ||
373 | (edev, cable_name), cable_state); | ||
374 | } | ||
375 | EXPORT_SYMBOL_GPL(extcon_set_cable_state); | ||
376 | |||
377 | /** | ||
378 | * extcon_get_extcon_dev() - Get the extcon device instance from the name | ||
379 | * @extcon_name: The extcon name provided with extcon_dev_register() | ||
380 | */ | ||
381 | struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name) | ||
382 | { | ||
383 | struct extcon_dev *sd; | ||
384 | |||
385 | mutex_lock(&extcon_dev_list_lock); | ||
386 | list_for_each_entry(sd, &extcon_dev_list, entry) { | ||
387 | if (!strcmp(sd->name, extcon_name)) | ||
388 | goto out; | ||
389 | } | ||
390 | sd = NULL; | ||
391 | out: | ||
392 | mutex_unlock(&extcon_dev_list_lock); | ||
393 | return sd; | ||
394 | } | ||
395 | EXPORT_SYMBOL_GPL(extcon_get_extcon_dev); | ||
396 | |||
397 | static int _call_per_cable(struct notifier_block *nb, unsigned long val, | ||
398 | void *ptr) | ||
399 | { | ||
400 | struct extcon_specific_cable_nb *obj = container_of(nb, | ||
401 | struct extcon_specific_cable_nb, internal_nb); | ||
402 | struct extcon_dev *edev = ptr; | ||
403 | |||
404 | if ((val & (1 << obj->cable_index)) != | ||
405 | (edev->state & (1 << obj->cable_index))) { | ||
406 | bool cable_state = true; | ||
407 | |||
408 | obj->previous_value = val; | ||
409 | |||
410 | if (val & (1 << obj->cable_index)) | ||
411 | cable_state = false; | ||
412 | |||
413 | return obj->user_nb->notifier_call(obj->user_nb, | ||
414 | cable_state, ptr); | ||
415 | } | ||
416 | |||
417 | return NOTIFY_OK; | ||
418 | } | ||
419 | |||
420 | /** | ||
421 | * extcon_register_interest() - Register a notifier for a state change of a | ||
422 | * specific cable, not an entier set of cables of a | ||
423 | * extcon device. | ||
424 | * @obj: an empty extcon_specific_cable_nb object to be returned. | ||
425 | * @extcon_name: the name of extcon device. | ||
426 | * if NULL, extcon_register_interest will register | ||
427 | * every cable with the target cable_name given. | ||
428 | * @cable_name: the target cable name. | ||
429 | * @nb: the notifier block to get notified. | ||
430 | * | ||
431 | * Provide an empty extcon_specific_cable_nb. extcon_register_interest() sets | ||
432 | * the struct for you. | ||
433 | * | ||
434 | * extcon_register_interest is a helper function for those who want to get | ||
435 | * notification for a single specific cable's status change. If a user wants | ||
436 | * to get notification for any changes of all cables of a extcon device, | ||
437 | * he/she should use the general extcon_register_notifier(). | ||
438 | * | ||
439 | * Note that the second parameter given to the callback of nb (val) is | ||
440 | * "old_state", not the current state. The current state can be retrieved | ||
441 | * by looking at the third pameter (edev pointer)'s state value. | ||
442 | */ | ||
443 | int extcon_register_interest(struct extcon_specific_cable_nb *obj, | ||
444 | const char *extcon_name, const char *cable_name, | ||
445 | struct notifier_block *nb) | ||
446 | { | ||
447 | if (!obj || !cable_name || !nb) | ||
448 | return -EINVAL; | ||
449 | |||
450 | if (extcon_name) { | ||
451 | obj->edev = extcon_get_extcon_dev(extcon_name); | ||
452 | if (!obj->edev) | ||
453 | return -ENODEV; | ||
454 | |||
455 | obj->cable_index = extcon_find_cable_index(obj->edev, | ||
456 | cable_name); | ||
457 | if (obj->cable_index < 0) | ||
458 | return obj->cable_index; | ||
459 | |||
460 | obj->user_nb = nb; | ||
461 | |||
462 | obj->internal_nb.notifier_call = _call_per_cable; | ||
463 | |||
464 | return raw_notifier_chain_register(&obj->edev->nh, | ||
465 | &obj->internal_nb); | ||
466 | } else { | ||
467 | struct class_dev_iter iter; | ||
468 | struct extcon_dev *extd; | ||
469 | struct device *dev; | ||
470 | |||
471 | if (!extcon_class) | ||
472 | return -ENODEV; | ||
473 | class_dev_iter_init(&iter, extcon_class, NULL, NULL); | ||
474 | while ((dev = class_dev_iter_next(&iter))) { | ||
475 | extd = dev_get_drvdata(dev); | ||
476 | |||
477 | if (extcon_find_cable_index(extd, cable_name) < 0) | ||
478 | continue; | ||
479 | |||
480 | class_dev_iter_exit(&iter); | ||
481 | return extcon_register_interest(obj, extd->name, | ||
482 | cable_name, nb); | ||
483 | } | ||
484 | |||
485 | return -ENODEV; | ||
486 | } | ||
487 | } | ||
488 | EXPORT_SYMBOL_GPL(extcon_register_interest); | ||
489 | |||
490 | /** | ||
491 | * extcon_unregister_interest() - Unregister the notifier registered by | ||
492 | * extcon_register_interest(). | ||
493 | * @obj: the extcon_specific_cable_nb object returned by | ||
494 | * extcon_register_interest(). | ||
495 | */ | ||
496 | int extcon_unregister_interest(struct extcon_specific_cable_nb *obj) | ||
497 | { | ||
498 | if (!obj) | ||
499 | return -EINVAL; | ||
500 | |||
501 | return raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb); | ||
502 | } | ||
503 | EXPORT_SYMBOL_GPL(extcon_unregister_interest); | ||
504 | |||
505 | /** | ||
506 | * extcon_register_notifier() - Register a notifiee to get notified by | ||
507 | * any attach status changes from the extcon. | ||
508 | * @edev: the extcon device. | ||
509 | * @nb: a notifier block to be registered. | ||
510 | * | ||
511 | * Note that the second parameter given to the callback of nb (val) is | ||
512 | * "old_state", not the current state. The current state can be retrieved | ||
513 | * by looking at the third pameter (edev pointer)'s state value. | ||
514 | */ | ||
515 | int extcon_register_notifier(struct extcon_dev *edev, | ||
516 | struct notifier_block *nb) | ||
517 | { | ||
518 | return raw_notifier_chain_register(&edev->nh, nb); | ||
519 | } | ||
520 | EXPORT_SYMBOL_GPL(extcon_register_notifier); | ||
521 | |||
522 | /** | ||
523 | * extcon_unregister_notifier() - Unregister a notifiee from the extcon device. | ||
524 | * @edev: the extcon device. | ||
525 | * @nb: a registered notifier block to be unregistered. | ||
526 | */ | ||
527 | int extcon_unregister_notifier(struct extcon_dev *edev, | ||
528 | struct notifier_block *nb) | ||
529 | { | ||
530 | return raw_notifier_chain_unregister(&edev->nh, nb); | ||
531 | } | ||
532 | EXPORT_SYMBOL_GPL(extcon_unregister_notifier); | ||
533 | |||
534 | static struct attribute *extcon_attrs[] = { | ||
535 | &dev_attr_state.attr, | ||
536 | &dev_attr_name.attr, | ||
537 | NULL, | ||
538 | }; | ||
539 | ATTRIBUTE_GROUPS(extcon); | ||
540 | |||
541 | static int create_extcon_class(void) | ||
542 | { | ||
543 | if (!extcon_class) { | ||
544 | extcon_class = class_create(THIS_MODULE, "extcon"); | ||
545 | if (IS_ERR(extcon_class)) | ||
546 | return PTR_ERR(extcon_class); | ||
547 | extcon_class->dev_groups = extcon_groups; | ||
548 | |||
549 | #if defined(CONFIG_ANDROID) | ||
550 | switch_class = class_compat_register("switch"); | ||
551 | if (WARN(!switch_class, "cannot allocate")) | ||
552 | return -ENOMEM; | ||
553 | #endif /* CONFIG_ANDROID */ | ||
554 | } | ||
555 | |||
556 | return 0; | ||
557 | } | ||
558 | |||
559 | static void extcon_dev_release(struct device *dev) | ||
560 | { | ||
561 | } | ||
562 | |||
563 | static const char *muex_name = "mutually_exclusive"; | ||
564 | static void dummy_sysfs_dev_release(struct device *dev) | ||
565 | { | ||
566 | } | ||
567 | |||
568 | /* | ||
569 | * extcon_dev_allocate() - Allocate the memory of extcon device. | ||
570 | * @supported_cable: Array of supported cable names ending with NULL. | ||
571 | * If supported_cable is NULL, cable name related APIs | ||
572 | * are disabled. | ||
573 | * | ||
574 | * This function allocates the memory for extcon device without allocating | ||
575 | * memory in each extcon provider driver and initialize default setting for | ||
576 | * extcon device. | ||
577 | * | ||
578 | * Return the pointer of extcon device if success or ERR_PTR(err) if fail | ||
579 | */ | ||
580 | struct extcon_dev *extcon_dev_allocate(const char **supported_cable) | ||
581 | { | ||
582 | struct extcon_dev *edev; | ||
583 | |||
584 | edev = kzalloc(sizeof(*edev), GFP_KERNEL); | ||
585 | if (!edev) | ||
586 | return ERR_PTR(-ENOMEM); | ||
587 | |||
588 | edev->max_supported = 0; | ||
589 | edev->supported_cable = supported_cable; | ||
590 | |||
591 | return edev; | ||
592 | } | ||
593 | |||
594 | /* | ||
595 | * extcon_dev_free() - Free the memory of extcon device. | ||
596 | * @edev: the extcon device to free | ||
597 | */ | ||
598 | void extcon_dev_free(struct extcon_dev *edev) | ||
599 | { | ||
600 | kfree(edev); | ||
601 | } | ||
602 | EXPORT_SYMBOL_GPL(extcon_dev_free); | ||
603 | |||
604 | static int devm_extcon_dev_match(struct device *dev, void *res, void *data) | ||
605 | { | ||
606 | struct extcon_dev **r = res; | ||
607 | |||
608 | if (WARN_ON(!r || !*r)) | ||
609 | return 0; | ||
610 | |||
611 | return *r == data; | ||
612 | } | ||
613 | |||
614 | static void devm_extcon_dev_release(struct device *dev, void *res) | ||
615 | { | ||
616 | extcon_dev_free(*(struct extcon_dev **)res); | ||
617 | } | ||
618 | |||
619 | /** | ||
620 | * devm_extcon_dev_allocate - Allocate managed extcon device | ||
621 | * @dev: device owning the extcon device being created | ||
622 | * @supported_cable: Array of supported cable names ending with NULL. | ||
623 | * If supported_cable is NULL, cable name related APIs | ||
624 | * are disabled. | ||
625 | * | ||
626 | * This function manages automatically the memory of extcon device using device | ||
627 | * resource management and simplify the control of freeing the memory of extcon | ||
628 | * device. | ||
629 | * | ||
630 | * Returns the pointer memory of allocated extcon_dev if success | ||
631 | * or ERR_PTR(err) if fail | ||
632 | */ | ||
633 | struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, | ||
634 | const char **supported_cable) | ||
635 | { | ||
636 | struct extcon_dev **ptr, *edev; | ||
637 | |||
638 | ptr = devres_alloc(devm_extcon_dev_release, sizeof(*ptr), GFP_KERNEL); | ||
639 | if (!ptr) | ||
640 | return ERR_PTR(-ENOMEM); | ||
641 | |||
642 | edev = extcon_dev_allocate(supported_cable); | ||
643 | if (IS_ERR(edev)) { | ||
644 | devres_free(ptr); | ||
645 | return edev; | ||
646 | } | ||
647 | |||
648 | edev->dev.parent = dev; | ||
649 | |||
650 | *ptr = edev; | ||
651 | devres_add(dev, ptr); | ||
652 | |||
653 | return edev; | ||
654 | } | ||
655 | EXPORT_SYMBOL_GPL(devm_extcon_dev_allocate); | ||
656 | |||
657 | void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev) | ||
658 | { | ||
659 | WARN_ON(devres_release(dev, devm_extcon_dev_release, | ||
660 | devm_extcon_dev_match, edev)); | ||
661 | } | ||
662 | EXPORT_SYMBOL_GPL(devm_extcon_dev_free); | ||
663 | |||
664 | /** | ||
665 | * extcon_dev_register() - Register a new extcon device | ||
666 | * @edev : the new extcon device (should be allocated before calling) | ||
667 | * | ||
668 | * Among the members of edev struct, please set the "user initializing data" | ||
669 | * in any case and set the "optional callbacks" if required. However, please | ||
670 | * do not set the values of "internal data", which are initialized by | ||
671 | * this function. | ||
672 | */ | ||
673 | int extcon_dev_register(struct extcon_dev *edev) | ||
674 | { | ||
675 | int ret, index = 0; | ||
676 | |||
677 | if (!extcon_class) { | ||
678 | ret = create_extcon_class(); | ||
679 | if (ret < 0) | ||
680 | return ret; | ||
681 | } | ||
682 | |||
683 | if (edev->supported_cable) { | ||
684 | /* Get size of array */ | ||
685 | for (index = 0; edev->supported_cable[index]; index++) | ||
686 | ; | ||
687 | edev->max_supported = index; | ||
688 | } else { | ||
689 | edev->max_supported = 0; | ||
690 | } | ||
691 | |||
692 | if (index > SUPPORTED_CABLE_MAX) { | ||
693 | dev_err(&edev->dev, "extcon: maximum number of supported cables exceeded.\n"); | ||
694 | return -EINVAL; | ||
695 | } | ||
696 | |||
697 | edev->dev.class = extcon_class; | ||
698 | edev->dev.release = extcon_dev_release; | ||
699 | |||
700 | edev->name = edev->name ? edev->name : dev_name(edev->dev.parent); | ||
701 | if (IS_ERR_OR_NULL(edev->name)) { | ||
702 | dev_err(&edev->dev, | ||
703 | "extcon device name is null\n"); | ||
704 | return -EINVAL; | ||
705 | } | ||
706 | dev_set_name(&edev->dev, "%s", edev->name); | ||
707 | |||
708 | if (edev->max_supported) { | ||
709 | char buf[10]; | ||
710 | char *str; | ||
711 | struct extcon_cable *cable; | ||
712 | |||
713 | edev->cables = kzalloc(sizeof(struct extcon_cable) * | ||
714 | edev->max_supported, GFP_KERNEL); | ||
715 | if (!edev->cables) { | ||
716 | ret = -ENOMEM; | ||
717 | goto err_sysfs_alloc; | ||
718 | } | ||
719 | for (index = 0; index < edev->max_supported; index++) { | ||
720 | cable = &edev->cables[index]; | ||
721 | |||
722 | snprintf(buf, 10, "cable.%d", index); | ||
723 | str = kzalloc(sizeof(char) * (strlen(buf) + 1), | ||
724 | GFP_KERNEL); | ||
725 | if (!str) { | ||
726 | for (index--; index >= 0; index--) { | ||
727 | cable = &edev->cables[index]; | ||
728 | kfree(cable->attr_g.name); | ||
729 | } | ||
730 | ret = -ENOMEM; | ||
731 | |||
732 | goto err_alloc_cables; | ||
733 | } | ||
734 | strcpy(str, buf); | ||
735 | |||
736 | cable->edev = edev; | ||
737 | cable->cable_index = index; | ||
738 | cable->attrs[0] = &cable->attr_name.attr; | ||
739 | cable->attrs[1] = &cable->attr_state.attr; | ||
740 | cable->attrs[2] = NULL; | ||
741 | cable->attr_g.name = str; | ||
742 | cable->attr_g.attrs = cable->attrs; | ||
743 | |||
744 | sysfs_attr_init(&cable->attr_name.attr); | ||
745 | cable->attr_name.attr.name = "name"; | ||
746 | cable->attr_name.attr.mode = 0444; | ||
747 | cable->attr_name.show = cable_name_show; | ||
748 | |||
749 | sysfs_attr_init(&cable->attr_state.attr); | ||
750 | cable->attr_state.attr.name = "state"; | ||
751 | cable->attr_state.attr.mode = 0444; | ||
752 | cable->attr_state.show = cable_state_show; | ||
753 | } | ||
754 | } | ||
755 | |||
756 | if (edev->max_supported && edev->mutually_exclusive) { | ||
757 | char buf[80]; | ||
758 | char *name; | ||
759 | |||
760 | /* Count the size of mutually_exclusive array */ | ||
761 | for (index = 0; edev->mutually_exclusive[index]; index++) | ||
762 | ; | ||
763 | |||
764 | edev->attrs_muex = kzalloc(sizeof(struct attribute *) * | ||
765 | (index + 1), GFP_KERNEL); | ||
766 | if (!edev->attrs_muex) { | ||
767 | ret = -ENOMEM; | ||
768 | goto err_muex; | ||
769 | } | ||
770 | |||
771 | edev->d_attrs_muex = kzalloc(sizeof(struct device_attribute) * | ||
772 | index, GFP_KERNEL); | ||
773 | if (!edev->d_attrs_muex) { | ||
774 | ret = -ENOMEM; | ||
775 | kfree(edev->attrs_muex); | ||
776 | goto err_muex; | ||
777 | } | ||
778 | |||
779 | for (index = 0; edev->mutually_exclusive[index]; index++) { | ||
780 | sprintf(buf, "0x%x", edev->mutually_exclusive[index]); | ||
781 | name = kzalloc(sizeof(char) * (strlen(buf) + 1), | ||
782 | GFP_KERNEL); | ||
783 | if (!name) { | ||
784 | for (index--; index >= 0; index--) { | ||
785 | kfree(edev->d_attrs_muex[index].attr. | ||
786 | name); | ||
787 | } | ||
788 | kfree(edev->d_attrs_muex); | ||
789 | kfree(edev->attrs_muex); | ||
790 | ret = -ENOMEM; | ||
791 | goto err_muex; | ||
792 | } | ||
793 | strcpy(name, buf); | ||
794 | sysfs_attr_init(&edev->d_attrs_muex[index].attr); | ||
795 | edev->d_attrs_muex[index].attr.name = name; | ||
796 | edev->d_attrs_muex[index].attr.mode = 0000; | ||
797 | edev->attrs_muex[index] = &edev->d_attrs_muex[index] | ||
798 | .attr; | ||
799 | } | ||
800 | edev->attr_g_muex.name = muex_name; | ||
801 | edev->attr_g_muex.attrs = edev->attrs_muex; | ||
802 | |||
803 | } | ||
804 | |||
805 | if (edev->max_supported) { | ||
806 | edev->extcon_dev_type.groups = | ||
807 | kzalloc(sizeof(struct attribute_group *) * | ||
808 | (edev->max_supported + 2), GFP_KERNEL); | ||
809 | if (!edev->extcon_dev_type.groups) { | ||
810 | ret = -ENOMEM; | ||
811 | goto err_alloc_groups; | ||
812 | } | ||
813 | |||
814 | edev->extcon_dev_type.name = dev_name(&edev->dev); | ||
815 | edev->extcon_dev_type.release = dummy_sysfs_dev_release; | ||
816 | |||
817 | for (index = 0; index < edev->max_supported; index++) | ||
818 | edev->extcon_dev_type.groups[index] = | ||
819 | &edev->cables[index].attr_g; | ||
820 | if (edev->mutually_exclusive) | ||
821 | edev->extcon_dev_type.groups[index] = | ||
822 | &edev->attr_g_muex; | ||
823 | |||
824 | edev->dev.type = &edev->extcon_dev_type; | ||
825 | } | ||
826 | |||
827 | ret = device_register(&edev->dev); | ||
828 | if (ret) { | ||
829 | put_device(&edev->dev); | ||
830 | goto err_dev; | ||
831 | } | ||
832 | #if defined(CONFIG_ANDROID) | ||
833 | if (switch_class) | ||
834 | ret = class_compat_create_link(switch_class, &edev->dev, NULL); | ||
835 | #endif /* CONFIG_ANDROID */ | ||
836 | |||
837 | spin_lock_init(&edev->lock); | ||
838 | |||
839 | RAW_INIT_NOTIFIER_HEAD(&edev->nh); | ||
840 | |||
841 | dev_set_drvdata(&edev->dev, edev); | ||
842 | edev->state = 0; | ||
843 | |||
844 | mutex_lock(&extcon_dev_list_lock); | ||
845 | list_add(&edev->entry, &extcon_dev_list); | ||
846 | mutex_unlock(&extcon_dev_list_lock); | ||
847 | |||
848 | return 0; | ||
849 | |||
850 | err_dev: | ||
851 | if (edev->max_supported) | ||
852 | kfree(edev->extcon_dev_type.groups); | ||
853 | err_alloc_groups: | ||
854 | if (edev->max_supported && edev->mutually_exclusive) { | ||
855 | for (index = 0; edev->mutually_exclusive[index]; index++) | ||
856 | kfree(edev->d_attrs_muex[index].attr.name); | ||
857 | kfree(edev->d_attrs_muex); | ||
858 | kfree(edev->attrs_muex); | ||
859 | } | ||
860 | err_muex: | ||
861 | for (index = 0; index < edev->max_supported; index++) | ||
862 | kfree(edev->cables[index].attr_g.name); | ||
863 | err_alloc_cables: | ||
864 | if (edev->max_supported) | ||
865 | kfree(edev->cables); | ||
866 | err_sysfs_alloc: | ||
867 | return ret; | ||
868 | } | ||
869 | EXPORT_SYMBOL_GPL(extcon_dev_register); | ||
870 | |||
871 | /** | ||
872 | * extcon_dev_unregister() - Unregister the extcon device. | ||
873 | * @edev: the extcon device instance to be unregistered. | ||
874 | * | ||
875 | * Note that this does not call kfree(edev) because edev was not allocated | ||
876 | * by this class. | ||
877 | */ | ||
878 | void extcon_dev_unregister(struct extcon_dev *edev) | ||
879 | { | ||
880 | int index; | ||
881 | |||
882 | mutex_lock(&extcon_dev_list_lock); | ||
883 | list_del(&edev->entry); | ||
884 | mutex_unlock(&extcon_dev_list_lock); | ||
885 | |||
886 | if (IS_ERR_OR_NULL(get_device(&edev->dev))) { | ||
887 | dev_err(&edev->dev, "Failed to unregister extcon_dev (%s)\n", | ||
888 | dev_name(&edev->dev)); | ||
889 | return; | ||
890 | } | ||
891 | |||
892 | device_unregister(&edev->dev); | ||
893 | |||
894 | if (edev->mutually_exclusive && edev->max_supported) { | ||
895 | for (index = 0; edev->mutually_exclusive[index]; | ||
896 | index++) | ||
897 | kfree(edev->d_attrs_muex[index].attr.name); | ||
898 | kfree(edev->d_attrs_muex); | ||
899 | kfree(edev->attrs_muex); | ||
900 | } | ||
901 | |||
902 | for (index = 0; index < edev->max_supported; index++) | ||
903 | kfree(edev->cables[index].attr_g.name); | ||
904 | |||
905 | if (edev->max_supported) { | ||
906 | kfree(edev->extcon_dev_type.groups); | ||
907 | kfree(edev->cables); | ||
908 | } | ||
909 | |||
910 | #if defined(CONFIG_ANDROID) | ||
911 | if (switch_class) | ||
912 | class_compat_remove_link(switch_class, &edev->dev, NULL); | ||
913 | #endif | ||
914 | put_device(&edev->dev); | ||
915 | } | ||
916 | EXPORT_SYMBOL_GPL(extcon_dev_unregister); | ||
917 | |||
918 | static void devm_extcon_dev_unreg(struct device *dev, void *res) | ||
919 | { | ||
920 | extcon_dev_unregister(*(struct extcon_dev **)res); | ||
921 | } | ||
922 | |||
923 | /** | ||
924 | * devm_extcon_dev_register() - Resource-managed extcon_dev_register() | ||
925 | * @dev: device to allocate extcon device | ||
926 | * @edev: the new extcon device to register | ||
927 | * | ||
928 | * Managed extcon_dev_register() function. If extcon device is attached with | ||
929 | * this function, that extcon device is automatically unregistered on driver | ||
930 | * detach. Internally this function calls extcon_dev_register() function. | ||
931 | * To get more information, refer that function. | ||
932 | * | ||
933 | * If extcon device is registered with this function and the device needs to be | ||
934 | * unregistered separately, devm_extcon_dev_unregister() should be used. | ||
935 | * | ||
936 | * Returns 0 if success or negaive error number if failure. | ||
937 | */ | ||
938 | int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev) | ||
939 | { | ||
940 | struct extcon_dev **ptr; | ||
941 | int ret; | ||
942 | |||
943 | ptr = devres_alloc(devm_extcon_dev_unreg, sizeof(*ptr), GFP_KERNEL); | ||
944 | if (!ptr) | ||
945 | return -ENOMEM; | ||
946 | |||
947 | ret = extcon_dev_register(edev); | ||
948 | if (ret) { | ||
949 | devres_free(ptr); | ||
950 | return ret; | ||
951 | } | ||
952 | |||
953 | *ptr = edev; | ||
954 | devres_add(dev, ptr); | ||
955 | |||
956 | return 0; | ||
957 | } | ||
958 | EXPORT_SYMBOL_GPL(devm_extcon_dev_register); | ||
959 | |||
960 | /** | ||
961 | * devm_extcon_dev_unregister() - Resource-managed extcon_dev_unregister() | ||
962 | * @dev: device the extcon belongs to | ||
963 | * @edev: the extcon device to unregister | ||
964 | * | ||
965 | * Unregister extcon device that is registered with devm_extcon_dev_register() | ||
966 | * function. | ||
967 | */ | ||
968 | void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev) | ||
969 | { | ||
970 | WARN_ON(devres_release(dev, devm_extcon_dev_unreg, | ||
971 | devm_extcon_dev_match, edev)); | ||
972 | } | ||
973 | EXPORT_SYMBOL_GPL(devm_extcon_dev_unregister); | ||
974 | |||
975 | #ifdef CONFIG_OF | ||
976 | /* | ||
977 | * extcon_get_edev_by_phandle - Get the extcon device from devicetree | ||
978 | * @dev - instance to the given device | ||
979 | * @index - index into list of extcon_dev | ||
980 | * | ||
981 | * return the instance of extcon device | ||
982 | */ | ||
983 | struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) | ||
984 | { | ||
985 | struct device_node *node; | ||
986 | struct extcon_dev *edev; | ||
987 | |||
988 | if (!dev->of_node) { | ||
989 | dev_err(dev, "device does not have a device node entry\n"); | ||
990 | return ERR_PTR(-EINVAL); | ||
991 | } | ||
992 | |||
993 | node = of_parse_phandle(dev->of_node, "extcon", index); | ||
994 | if (!node) { | ||
995 | dev_err(dev, "failed to get phandle in %s node\n", | ||
996 | dev->of_node->full_name); | ||
997 | return ERR_PTR(-ENODEV); | ||
998 | } | ||
999 | |||
1000 | mutex_lock(&extcon_dev_list_lock); | ||
1001 | list_for_each_entry(edev, &extcon_dev_list, entry) { | ||
1002 | if (edev->dev.parent && edev->dev.parent->of_node == node) { | ||
1003 | mutex_unlock(&extcon_dev_list_lock); | ||
1004 | return edev; | ||
1005 | } | ||
1006 | } | ||
1007 | mutex_unlock(&extcon_dev_list_lock); | ||
1008 | |||
1009 | return ERR_PTR(-EPROBE_DEFER); | ||
1010 | } | ||
1011 | #else | ||
1012 | struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) | ||
1013 | { | ||
1014 | return ERR_PTR(-ENOSYS); | ||
1015 | } | ||
1016 | #endif /* CONFIG_OF */ | ||
1017 | EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle); | ||
1018 | |||
1019 | static int __init extcon_class_init(void) | ||
1020 | { | ||
1021 | return create_extcon_class(); | ||
1022 | } | ||
1023 | module_init(extcon_class_init); | ||
1024 | |||
1025 | static void __exit extcon_class_exit(void) | ||
1026 | { | ||
1027 | #if defined(CONFIG_ANDROID) | ||
1028 | class_compat_unregister(switch_class); | ||
1029 | #endif | ||
1030 | class_destroy(extcon_class); | ||
1031 | } | ||
1032 | module_exit(extcon_class_exit); | ||
1033 | |||
1034 | MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); | ||
1035 | MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); | ||
1036 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); | ||
1037 | MODULE_DESCRIPTION("External connector (extcon) class driver"); | ||
1038 | MODULE_LICENSE("GPL"); | ||