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