diff options
author | Jiri Kosina <jkosina@suse.cz> | 2015-09-01 09:35:24 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2015-09-01 09:35:24 -0400 |
commit | 067e2601d3c076abbf45db91261f9065eaa879b2 (patch) | |
tree | 86c8d4b913873dbd3b4ff23562a3a8597984b4df /drivers/extcon/extcon.c | |
parent | 3e097d1271ecdff2f251a54ddfc5eaa1f9821e96 (diff) | |
parent | 931830aa5c251e0803523213428f777a48bde254 (diff) |
Merge branch 'for-4.3/gembird' into for-linus
Diffstat (limited to 'drivers/extcon/extcon.c')
-rw-r--r-- | drivers/extcon/extcon.c | 347 |
1 files changed, 205 insertions, 142 deletions
diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 4c9f165e4a04..43b57b02d050 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c | |||
@@ -1,8 +1,11 @@ | |||
1 | /* | 1 | /* |
2 | * drivers/extcon/extcon_class.c | 2 | * drivers/extcon/extcon.c - External Connector (extcon) framework. |
3 | * | 3 | * |
4 | * External connector (extcon) class driver | 4 | * External connector (extcon) class driver |
5 | * | 5 | * |
6 | * Copyright (C) 2015 Samsung Electronics | ||
7 | * Author: Chanwoo Choi <cw00.choi@samsung.com> | ||
8 | * | ||
6 | * Copyright (C) 2012 Samsung Electronics | 9 | * Copyright (C) 2012 Samsung Electronics |
7 | * Author: Donggeun Kim <dg77.kim@samsung.com> | 10 | * Author: Donggeun Kim <dg77.kim@samsung.com> |
8 | * Author: MyungJoo Ham <myungjoo.ham@samsung.com> | 11 | * Author: MyungJoo Ham <myungjoo.ham@samsung.com> |
@@ -19,8 +22,7 @@ | |||
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
21 | * GNU General Public License for more details. | 24 | * GNU General Public License for more details. |
22 | * | 25 | */ |
23 | */ | ||
24 | 26 | ||
25 | #include <linux/module.h> | 27 | #include <linux/module.h> |
26 | #include <linux/types.h> | 28 | #include <linux/types.h> |
@@ -33,36 +35,43 @@ | |||
33 | #include <linux/slab.h> | 35 | #include <linux/slab.h> |
34 | #include <linux/sysfs.h> | 36 | #include <linux/sysfs.h> |
35 | 37 | ||
36 | /* | 38 | #define SUPPORTED_CABLE_MAX 32 |
37 | * extcon_cable_name suggests the standard cable names for commonly used | 39 | #define CABLE_NAME_MAX 30 |
38 | * cable types. | 40 | |
39 | * | 41 | static const char *extcon_name[] = { |
40 | * However, please do not use extcon_cable_name directly for extcon_dev | 42 | [EXTCON_NONE] = "NONE", |
41 | * struct's supported_cable pointer unless your device really supports | 43 | |
42 | * every single port-type of the following cable names. Please choose cable | 44 | /* USB external connector */ |
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", | 45 | [EXTCON_USB] = "USB", |
47 | [EXTCON_USB_HOST] = "USB-Host", | 46 | [EXTCON_USB_HOST] = "USB-HOST", |
47 | |||
48 | /* Charger external connector */ | ||
48 | [EXTCON_TA] = "TA", | 49 | [EXTCON_TA] = "TA", |
49 | [EXTCON_FAST_CHARGER] = "Fast-charger", | 50 | [EXTCON_FAST_CHARGER] = "FAST-CHARGER", |
50 | [EXTCON_SLOW_CHARGER] = "Slow-charger", | 51 | [EXTCON_SLOW_CHARGER] = "SLOW-CHARGER", |
51 | [EXTCON_CHARGE_DOWNSTREAM] = "Charge-downstream", | 52 | [EXTCON_CHARGE_DOWNSTREAM] = "CHARGE-DOWNSTREAM", |
53 | |||
54 | /* Audio/Video external connector */ | ||
55 | [EXTCON_LINE_IN] = "LINE-IN", | ||
56 | [EXTCON_LINE_OUT] = "LINE-OUT", | ||
57 | [EXTCON_MICROPHONE] = "MICROPHONE", | ||
58 | [EXTCON_HEADPHONE] = "HEADPHONE", | ||
59 | |||
52 | [EXTCON_HDMI] = "HDMI", | 60 | [EXTCON_HDMI] = "HDMI", |
53 | [EXTCON_MHL] = "MHL", | 61 | [EXTCON_MHL] = "MHL", |
54 | [EXTCON_DVI] = "DVI", | 62 | [EXTCON_DVI] = "DVI", |
55 | [EXTCON_VGA] = "VGA", | 63 | [EXTCON_VGA] = "VGA", |
56 | [EXTCON_DOCK] = "Dock", | 64 | [EXTCON_SPDIF_IN] = "SPDIF-IN", |
57 | [EXTCON_LINE_IN] = "Line-in", | 65 | [EXTCON_SPDIF_OUT] = "SPDIF-OUT", |
58 | [EXTCON_LINE_OUT] = "Line-out", | 66 | [EXTCON_VIDEO_IN] = "VIDEO-IN", |
59 | [EXTCON_MIC_IN] = "Microphone", | 67 | [EXTCON_VIDEO_OUT] = "VIDEO-OUT", |
60 | [EXTCON_HEADPHONE_OUT] = "Headphone", | 68 | |
61 | [EXTCON_SPDIF_IN] = "SPDIF-in", | 69 | /* Etc external connector */ |
62 | [EXTCON_SPDIF_OUT] = "SPDIF-out", | 70 | [EXTCON_DOCK] = "DOCK", |
63 | [EXTCON_VIDEO_IN] = "Video-in", | 71 | [EXTCON_JIG] = "JIG", |
64 | [EXTCON_VIDEO_OUT] = "Video-out", | 72 | [EXTCON_MECHANICAL] = "MECHANICAL", |
65 | [EXTCON_MECHANICAL] = "Mechanical", | 73 | |
74 | NULL, | ||
66 | }; | 75 | }; |
67 | 76 | ||
68 | static struct class *extcon_class; | 77 | static struct class *extcon_class; |
@@ -102,6 +111,61 @@ static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state) | |||
102 | return 0; | 111 | return 0; |
103 | } | 112 | } |
104 | 113 | ||
114 | static int find_cable_index_by_id(struct extcon_dev *edev, const unsigned int id) | ||
115 | { | ||
116 | int i; | ||
117 | |||
118 | /* Find the the index of extcon cable in edev->supported_cable */ | ||
119 | for (i = 0; i < edev->max_supported; i++) { | ||
120 | if (edev->supported_cable[i] == id) | ||
121 | return i; | ||
122 | } | ||
123 | |||
124 | return -EINVAL; | ||
125 | } | ||
126 | |||
127 | static int find_cable_id_by_name(struct extcon_dev *edev, const char *name) | ||
128 | { | ||
129 | unsigned int id = -EINVAL; | ||
130 | int i = 0; | ||
131 | |||
132 | /* Find the id of extcon cable */ | ||
133 | while (extcon_name[i]) { | ||
134 | if (!strncmp(extcon_name[i], name, CABLE_NAME_MAX)) { | ||
135 | id = i; | ||
136 | break; | ||
137 | } | ||
138 | i++; | ||
139 | } | ||
140 | |||
141 | return id; | ||
142 | } | ||
143 | |||
144 | static int find_cable_index_by_name(struct extcon_dev *edev, const char *name) | ||
145 | { | ||
146 | unsigned int id; | ||
147 | |||
148 | if (edev->max_supported == 0) | ||
149 | return -EINVAL; | ||
150 | |||
151 | /* Find the the number of extcon cable */ | ||
152 | id = find_cable_id_by_name(edev, name); | ||
153 | if (id < 0) | ||
154 | return id; | ||
155 | |||
156 | return find_cable_index_by_id(edev, id); | ||
157 | } | ||
158 | |||
159 | static bool is_extcon_changed(u32 prev, u32 new, int idx, bool *attached) | ||
160 | { | ||
161 | if (((prev >> idx) & 0x1) != ((new >> idx) & 0x1)) { | ||
162 | *attached = new ? true : false; | ||
163 | return true; | ||
164 | } | ||
165 | |||
166 | return false; | ||
167 | } | ||
168 | |||
105 | static ssize_t state_show(struct device *dev, struct device_attribute *attr, | 169 | static ssize_t state_show(struct device *dev, struct device_attribute *attr, |
106 | char *buf) | 170 | char *buf) |
107 | { | 171 | { |
@@ -119,11 +183,9 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, | |||
119 | if (edev->max_supported == 0) | 183 | if (edev->max_supported == 0) |
120 | return sprintf(buf, "%u\n", edev->state); | 184 | return sprintf(buf, "%u\n", edev->state); |
121 | 185 | ||
122 | for (i = 0; i < SUPPORTED_CABLE_MAX; i++) { | 186 | for (i = 0; i < edev->max_supported; i++) { |
123 | if (!edev->supported_cable[i]) | ||
124 | break; | ||
125 | count += sprintf(buf + count, "%s=%d\n", | 187 | count += sprintf(buf + count, "%s=%d\n", |
126 | edev->supported_cable[i], | 188 | extcon_name[edev->supported_cable[i]], |
127 | !!(edev->state & (1 << i))); | 189 | !!(edev->state & (1 << i))); |
128 | } | 190 | } |
129 | 191 | ||
@@ -155,15 +217,7 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr, | |||
155 | { | 217 | { |
156 | struct extcon_dev *edev = dev_get_drvdata(dev); | 218 | struct extcon_dev *edev = dev_get_drvdata(dev); |
157 | 219 | ||
158 | /* Optional callback given by the user */ | 220 | return sprintf(buf, "%s\n", edev->name); |
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 | } | 221 | } |
168 | static DEVICE_ATTR_RO(name); | 222 | static DEVICE_ATTR_RO(name); |
169 | 223 | ||
@@ -172,9 +226,10 @@ static ssize_t cable_name_show(struct device *dev, | |||
172 | { | 226 | { |
173 | struct extcon_cable *cable = container_of(attr, struct extcon_cable, | 227 | struct extcon_cable *cable = container_of(attr, struct extcon_cable, |
174 | attr_name); | 228 | attr_name); |
229 | int i = cable->cable_index; | ||
175 | 230 | ||
176 | return sprintf(buf, "%s\n", | 231 | return sprintf(buf, "%s\n", |
177 | cable->edev->supported_cable[cable->cable_index]); | 232 | extcon_name[cable->edev->supported_cable[i]]); |
178 | } | 233 | } |
179 | 234 | ||
180 | static ssize_t cable_state_show(struct device *dev, | 235 | static ssize_t cable_state_show(struct device *dev, |
@@ -183,9 +238,11 @@ static ssize_t cable_state_show(struct device *dev, | |||
183 | struct extcon_cable *cable = container_of(attr, struct extcon_cable, | 238 | struct extcon_cable *cable = container_of(attr, struct extcon_cable, |
184 | attr_state); | 239 | attr_state); |
185 | 240 | ||
241 | int i = cable->cable_index; | ||
242 | |||
186 | return sprintf(buf, "%d\n", | 243 | return sprintf(buf, "%d\n", |
187 | extcon_get_cable_state_(cable->edev, | 244 | extcon_get_cable_state_(cable->edev, |
188 | cable->cable_index)); | 245 | cable->edev->supported_cable[i])); |
189 | } | 246 | } |
190 | 247 | ||
191 | /** | 248 | /** |
@@ -211,12 +268,14 @@ int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) | |||
211 | char *envp[3]; | 268 | char *envp[3]; |
212 | int env_offset = 0; | 269 | int env_offset = 0; |
213 | int length; | 270 | int length; |
271 | int index; | ||
214 | unsigned long flags; | 272 | unsigned long flags; |
273 | bool attached; | ||
215 | 274 | ||
216 | spin_lock_irqsave(&edev->lock, flags); | 275 | spin_lock_irqsave(&edev->lock, flags); |
217 | 276 | ||
218 | if (edev->state != ((edev->state & ~mask) | (state & mask))) { | 277 | if (edev->state != ((edev->state & ~mask) | (state & mask))) { |
219 | u32 old_state = edev->state; | 278 | u32 old_state; |
220 | 279 | ||
221 | if (check_mutually_exclusive(edev, (edev->state & ~mask) | | 280 | if (check_mutually_exclusive(edev, (edev->state & ~mask) | |
222 | (state & mask))) { | 281 | (state & mask))) { |
@@ -224,10 +283,17 @@ int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) | |||
224 | return -EPERM; | 283 | return -EPERM; |
225 | } | 284 | } |
226 | 285 | ||
286 | old_state = edev->state; | ||
227 | edev->state &= ~mask; | 287 | edev->state &= ~mask; |
228 | edev->state |= state & mask; | 288 | edev->state |= state & mask; |
229 | 289 | ||
230 | raw_notifier_call_chain(&edev->nh, old_state, edev); | 290 | for (index = 0; index < edev->max_supported; index++) { |
291 | if (is_extcon_changed(old_state, edev->state, index, | ||
292 | &attached)) | ||
293 | raw_notifier_call_chain(&edev->nh[index], | ||
294 | attached, edev); | ||
295 | } | ||
296 | |||
231 | /* This could be in interrupt handler */ | 297 | /* This could be in interrupt handler */ |
232 | prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); | 298 | prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); |
233 | if (prop_buf) { | 299 | if (prop_buf) { |
@@ -284,39 +350,19 @@ int extcon_set_state(struct extcon_dev *edev, u32 state) | |||
284 | EXPORT_SYMBOL_GPL(extcon_set_state); | 350 | EXPORT_SYMBOL_GPL(extcon_set_state); |
285 | 351 | ||
286 | /** | 352 | /** |
287 | * extcon_find_cable_index() - Get the cable index based on the cable name. | 353 | * extcon_get_cable_state_() - Get the status of a specific cable. |
288 | * @edev: the extcon device that has the cable. | 354 | * @edev: the extcon device that has the cable. |
289 | * @cable_name: cable name to be searched. | 355 | * @id: the unique id of each external connector in extcon enumeration. |
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 | */ | 356 | */ |
296 | int extcon_find_cable_index(struct extcon_dev *edev, const char *cable_name) | 357 | int extcon_get_cable_state_(struct extcon_dev *edev, const unsigned int id) |
297 | { | 358 | { |
298 | int i; | 359 | int index; |
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 | 360 | ||
308 | return -EINVAL; | 361 | index = find_cable_index_by_id(edev, id); |
309 | } | 362 | if (index < 0) |
310 | EXPORT_SYMBOL_GPL(extcon_find_cable_index); | 363 | return index; |
311 | 364 | ||
312 | /** | 365 | if (edev->max_supported && edev->max_supported <= index) |
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; | 366 | return -EINVAL; |
321 | 367 | ||
322 | return !!(edev->state & (1 << index)); | 368 | return !!(edev->state & (1 << index)); |
@@ -332,25 +378,35 @@ EXPORT_SYMBOL_GPL(extcon_get_cable_state_); | |||
332 | */ | 378 | */ |
333 | int extcon_get_cable_state(struct extcon_dev *edev, const char *cable_name) | 379 | int extcon_get_cable_state(struct extcon_dev *edev, const char *cable_name) |
334 | { | 380 | { |
335 | return extcon_get_cable_state_(edev, extcon_find_cable_index | 381 | unsigned int id; |
336 | (edev, cable_name)); | 382 | |
383 | id = find_cable_id_by_name(edev, cable_name); | ||
384 | if (id < 0) | ||
385 | return id; | ||
386 | |||
387 | return extcon_get_cable_state_(edev, id); | ||
337 | } | 388 | } |
338 | EXPORT_SYMBOL_GPL(extcon_get_cable_state); | 389 | EXPORT_SYMBOL_GPL(extcon_get_cable_state); |
339 | 390 | ||
340 | /** | 391 | /** |
341 | * extcon_set_cable_state_() - Set the status of a specific cable. | 392 | * extcon_set_cable_state_() - Set the status of a specific cable. |
342 | * @edev: the extcon device that has the cable. | 393 | * @edev: the extcon device that has the cable. |
343 | * @index: cable index that can be retrieved by | 394 | * @id: the unique id of each external connector |
344 | * extcon_find_cable_index(). | 395 | * in extcon enumeration. |
345 | * @cable_state: the new cable status. The default semantics is | 396 | * @state: the new cable status. The default semantics is |
346 | * true: attached / false: detached. | 397 | * true: attached / false: detached. |
347 | */ | 398 | */ |
348 | int extcon_set_cable_state_(struct extcon_dev *edev, | 399 | int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id, |
349 | int index, bool cable_state) | 400 | bool cable_state) |
350 | { | 401 | { |
351 | u32 state; | 402 | u32 state; |
403 | int index; | ||
404 | |||
405 | index = find_cable_index_by_id(edev, id); | ||
406 | if (index < 0) | ||
407 | return index; | ||
352 | 408 | ||
353 | if (index < 0 || (edev->max_supported && edev->max_supported <= index)) | 409 | if (edev->max_supported && edev->max_supported <= index) |
354 | return -EINVAL; | 410 | return -EINVAL; |
355 | 411 | ||
356 | state = cable_state ? (1 << index) : 0; | 412 | state = cable_state ? (1 << index) : 0; |
@@ -370,8 +426,13 @@ EXPORT_SYMBOL_GPL(extcon_set_cable_state_); | |||
370 | int extcon_set_cable_state(struct extcon_dev *edev, | 426 | int extcon_set_cable_state(struct extcon_dev *edev, |
371 | const char *cable_name, bool cable_state) | 427 | const char *cable_name, bool cable_state) |
372 | { | 428 | { |
373 | return extcon_set_cable_state_(edev, extcon_find_cable_index | 429 | unsigned int id; |
374 | (edev, cable_name), cable_state); | 430 | |
431 | id = find_cable_id_by_name(edev, cable_name); | ||
432 | if (id < 0) | ||
433 | return id; | ||
434 | |||
435 | return extcon_set_cable_state_(edev, id, cable_state); | ||
375 | } | 436 | } |
376 | EXPORT_SYMBOL_GPL(extcon_set_cable_state); | 437 | EXPORT_SYMBOL_GPL(extcon_set_cable_state); |
377 | 438 | ||
@@ -395,29 +456,6 @@ out: | |||
395 | } | 456 | } |
396 | EXPORT_SYMBOL_GPL(extcon_get_extcon_dev); | 457 | EXPORT_SYMBOL_GPL(extcon_get_extcon_dev); |
397 | 458 | ||
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 | /** | 459 | /** |
422 | * extcon_register_interest() - Register a notifier for a state change of a | 460 | * extcon_register_interest() - Register a notifier for a state change of a |
423 | * specific cable, not an entier set of cables of a | 461 | * specific cable, not an entier set of cables of a |
@@ -456,20 +494,18 @@ int extcon_register_interest(struct extcon_specific_cable_nb *obj, | |||
456 | if (!obj->edev) | 494 | if (!obj->edev) |
457 | return -ENODEV; | 495 | return -ENODEV; |
458 | 496 | ||
459 | obj->cable_index = extcon_find_cable_index(obj->edev, | 497 | obj->cable_index = find_cable_index_by_name(obj->edev, |
460 | cable_name); | 498 | cable_name); |
461 | if (obj->cable_index < 0) | 499 | if (obj->cable_index < 0) |
462 | return obj->cable_index; | 500 | return obj->cable_index; |
463 | 501 | ||
464 | obj->user_nb = nb; | 502 | obj->user_nb = nb; |
465 | 503 | ||
466 | obj->internal_nb.notifier_call = _call_per_cable; | ||
467 | |||
468 | spin_lock_irqsave(&obj->edev->lock, flags); | 504 | spin_lock_irqsave(&obj->edev->lock, flags); |
469 | ret = raw_notifier_chain_register(&obj->edev->nh, | 505 | ret = raw_notifier_chain_register( |
470 | &obj->internal_nb); | 506 | &obj->edev->nh[obj->cable_index], |
507 | obj->user_nb); | ||
471 | spin_unlock_irqrestore(&obj->edev->lock, flags); | 508 | spin_unlock_irqrestore(&obj->edev->lock, flags); |
472 | return ret; | ||
473 | } else { | 509 | } else { |
474 | struct class_dev_iter iter; | 510 | struct class_dev_iter iter; |
475 | struct extcon_dev *extd; | 511 | struct extcon_dev *extd; |
@@ -481,7 +517,7 @@ int extcon_register_interest(struct extcon_specific_cable_nb *obj, | |||
481 | while ((dev = class_dev_iter_next(&iter))) { | 517 | while ((dev = class_dev_iter_next(&iter))) { |
482 | extd = dev_get_drvdata(dev); | 518 | extd = dev_get_drvdata(dev); |
483 | 519 | ||
484 | if (extcon_find_cable_index(extd, cable_name) < 0) | 520 | if (find_cable_index_by_name(extd, cable_name) < 0) |
485 | continue; | 521 | continue; |
486 | 522 | ||
487 | class_dev_iter_exit(&iter); | 523 | class_dev_iter_exit(&iter); |
@@ -489,8 +525,10 @@ int extcon_register_interest(struct extcon_specific_cable_nb *obj, | |||
489 | cable_name, nb); | 525 | cable_name, nb); |
490 | } | 526 | } |
491 | 527 | ||
492 | return -ENODEV; | 528 | ret = -ENODEV; |
493 | } | 529 | } |
530 | |||
531 | return ret; | ||
494 | } | 532 | } |
495 | EXPORT_SYMBOL_GPL(extcon_register_interest); | 533 | EXPORT_SYMBOL_GPL(extcon_register_interest); |
496 | 534 | ||
@@ -509,7 +547,8 @@ int extcon_unregister_interest(struct extcon_specific_cable_nb *obj) | |||
509 | return -EINVAL; | 547 | return -EINVAL; |
510 | 548 | ||
511 | spin_lock_irqsave(&obj->edev->lock, flags); | 549 | spin_lock_irqsave(&obj->edev->lock, flags); |
512 | ret = raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb); | 550 | ret = raw_notifier_chain_unregister( |
551 | &obj->edev->nh[obj->cable_index], obj->user_nb); | ||
513 | spin_unlock_irqrestore(&obj->edev->lock, flags); | 552 | spin_unlock_irqrestore(&obj->edev->lock, flags); |
514 | 553 | ||
515 | return ret; | 554 | return ret; |
@@ -519,21 +558,24 @@ EXPORT_SYMBOL_GPL(extcon_unregister_interest); | |||
519 | /** | 558 | /** |
520 | * extcon_register_notifier() - Register a notifiee to get notified by | 559 | * extcon_register_notifier() - Register a notifiee to get notified by |
521 | * any attach status changes from the extcon. | 560 | * any attach status changes from the extcon. |
522 | * @edev: the extcon device. | 561 | * @edev: the extcon device that has the external connecotr. |
562 | * @id: the unique id of each external connector in extcon enumeration. | ||
523 | * @nb: a notifier block to be registered. | 563 | * @nb: a notifier block to be registered. |
524 | * | 564 | * |
525 | * Note that the second parameter given to the callback of nb (val) is | 565 | * 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 | 566 | * "old_state", not the current state. The current state can be retrieved |
527 | * by looking at the third pameter (edev pointer)'s state value. | 567 | * by looking at the third pameter (edev pointer)'s state value. |
528 | */ | 568 | */ |
529 | int extcon_register_notifier(struct extcon_dev *edev, | 569 | int extcon_register_notifier(struct extcon_dev *edev, unsigned int id, |
530 | struct notifier_block *nb) | 570 | struct notifier_block *nb) |
531 | { | 571 | { |
532 | unsigned long flags; | 572 | unsigned long flags; |
533 | int ret; | 573 | int ret, idx; |
574 | |||
575 | idx = find_cable_index_by_id(edev, id); | ||
534 | 576 | ||
535 | spin_lock_irqsave(&edev->lock, flags); | 577 | spin_lock_irqsave(&edev->lock, flags); |
536 | ret = raw_notifier_chain_register(&edev->nh, nb); | 578 | ret = raw_notifier_chain_register(&edev->nh[idx], nb); |
537 | spin_unlock_irqrestore(&edev->lock, flags); | 579 | spin_unlock_irqrestore(&edev->lock, flags); |
538 | 580 | ||
539 | return ret; | 581 | return ret; |
@@ -542,17 +584,20 @@ EXPORT_SYMBOL_GPL(extcon_register_notifier); | |||
542 | 584 | ||
543 | /** | 585 | /** |
544 | * extcon_unregister_notifier() - Unregister a notifiee from the extcon device. | 586 | * extcon_unregister_notifier() - Unregister a notifiee from the extcon device. |
545 | * @edev: the extcon device. | 587 | * @edev: the extcon device that has the external connecotr. |
546 | * @nb: a registered notifier block to be unregistered. | 588 | * @id: the unique id of each external connector in extcon enumeration. |
589 | * @nb: a notifier block to be registered. | ||
547 | */ | 590 | */ |
548 | int extcon_unregister_notifier(struct extcon_dev *edev, | 591 | int extcon_unregister_notifier(struct extcon_dev *edev, unsigned int id, |
549 | struct notifier_block *nb) | 592 | struct notifier_block *nb) |
550 | { | 593 | { |
551 | unsigned long flags; | 594 | unsigned long flags; |
552 | int ret; | 595 | int ret, idx; |
596 | |||
597 | idx = find_cable_index_by_id(edev, id); | ||
553 | 598 | ||
554 | spin_lock_irqsave(&edev->lock, flags); | 599 | spin_lock_irqsave(&edev->lock, flags); |
555 | ret = raw_notifier_chain_unregister(&edev->nh, nb); | 600 | ret = raw_notifier_chain_unregister(&edev->nh[idx], nb); |
556 | spin_unlock_irqrestore(&edev->lock, flags); | 601 | spin_unlock_irqrestore(&edev->lock, flags); |
557 | 602 | ||
558 | return ret; | 603 | return ret; |
@@ -595,7 +640,7 @@ static void dummy_sysfs_dev_release(struct device *dev) | |||
595 | 640 | ||
596 | /* | 641 | /* |
597 | * extcon_dev_allocate() - Allocate the memory of extcon device. | 642 | * extcon_dev_allocate() - Allocate the memory of extcon device. |
598 | * @supported_cable: Array of supported cable names ending with NULL. | 643 | * @supported_cable: Array of supported extcon ending with EXTCON_NONE. |
599 | * If supported_cable is NULL, cable name related APIs | 644 | * If supported_cable is NULL, cable name related APIs |
600 | * are disabled. | 645 | * are disabled. |
601 | * | 646 | * |
@@ -605,7 +650,7 @@ static void dummy_sysfs_dev_release(struct device *dev) | |||
605 | * | 650 | * |
606 | * Return the pointer of extcon device if success or ERR_PTR(err) if fail | 651 | * Return the pointer of extcon device if success or ERR_PTR(err) if fail |
607 | */ | 652 | */ |
608 | struct extcon_dev *extcon_dev_allocate(const char **supported_cable) | 653 | struct extcon_dev *extcon_dev_allocate(const unsigned int *supported_cable) |
609 | { | 654 | { |
610 | struct extcon_dev *edev; | 655 | struct extcon_dev *edev; |
611 | 656 | ||
@@ -647,7 +692,7 @@ static void devm_extcon_dev_release(struct device *dev, void *res) | |||
647 | /** | 692 | /** |
648 | * devm_extcon_dev_allocate - Allocate managed extcon device | 693 | * devm_extcon_dev_allocate - Allocate managed extcon device |
649 | * @dev: device owning the extcon device being created | 694 | * @dev: device owning the extcon device being created |
650 | * @supported_cable: Array of supported cable names ending with NULL. | 695 | * @supported_cable: Array of supported extcon ending with EXTCON_NONE. |
651 | * If supported_cable is NULL, cable name related APIs | 696 | * If supported_cable is NULL, cable name related APIs |
652 | * are disabled. | 697 | * are disabled. |
653 | * | 698 | * |
@@ -659,7 +704,7 @@ static void devm_extcon_dev_release(struct device *dev, void *res) | |||
659 | * or ERR_PTR(err) if fail | 704 | * or ERR_PTR(err) if fail |
660 | */ | 705 | */ |
661 | struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, | 706 | struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, |
662 | const char **supported_cable) | 707 | const unsigned int *supported_cable) |
663 | { | 708 | { |
664 | struct extcon_dev **ptr, *edev; | 709 | struct extcon_dev **ptr, *edev; |
665 | 710 | ||
@@ -701,6 +746,7 @@ EXPORT_SYMBOL_GPL(devm_extcon_dev_free); | |||
701 | int extcon_dev_register(struct extcon_dev *edev) | 746 | int extcon_dev_register(struct extcon_dev *edev) |
702 | { | 747 | { |
703 | int ret, index = 0; | 748 | int ret, index = 0; |
749 | static atomic_t edev_no = ATOMIC_INIT(-1); | ||
704 | 750 | ||
705 | if (!extcon_class) { | 751 | if (!extcon_class) { |
706 | ret = create_extcon_class(); | 752 | ret = create_extcon_class(); |
@@ -708,30 +754,29 @@ int extcon_dev_register(struct extcon_dev *edev) | |||
708 | return ret; | 754 | return ret; |
709 | } | 755 | } |
710 | 756 | ||
711 | if (edev->supported_cable) { | 757 | if (!edev->supported_cable) |
712 | /* Get size of array */ | 758 | return -EINVAL; |
713 | for (index = 0; edev->supported_cable[index]; index++) | 759 | |
714 | ; | 760 | for (; edev->supported_cable[index] != EXTCON_NONE; index++); |
715 | edev->max_supported = index; | ||
716 | } else { | ||
717 | edev->max_supported = 0; | ||
718 | } | ||
719 | 761 | ||
762 | edev->max_supported = index; | ||
720 | if (index > SUPPORTED_CABLE_MAX) { | 763 | if (index > SUPPORTED_CABLE_MAX) { |
721 | dev_err(&edev->dev, "extcon: maximum number of supported cables exceeded.\n"); | 764 | dev_err(&edev->dev, |
765 | "exceed the maximum number of supported cables\n"); | ||
722 | return -EINVAL; | 766 | return -EINVAL; |
723 | } | 767 | } |
724 | 768 | ||
725 | edev->dev.class = extcon_class; | 769 | edev->dev.class = extcon_class; |
726 | edev->dev.release = extcon_dev_release; | 770 | edev->dev.release = extcon_dev_release; |
727 | 771 | ||
728 | edev->name = edev->name ? edev->name : dev_name(edev->dev.parent); | 772 | edev->name = dev_name(edev->dev.parent); |
729 | if (IS_ERR_OR_NULL(edev->name)) { | 773 | if (IS_ERR_OR_NULL(edev->name)) { |
730 | dev_err(&edev->dev, | 774 | dev_err(&edev->dev, |
731 | "extcon device name is null\n"); | 775 | "extcon device name is null\n"); |
732 | return -EINVAL; | 776 | return -EINVAL; |
733 | } | 777 | } |
734 | dev_set_name(&edev->dev, "%s", edev->name); | 778 | dev_set_name(&edev->dev, "extcon%lu", |
779 | (unsigned long)atomic_inc_return(&edev_no)); | ||
735 | 780 | ||
736 | if (edev->max_supported) { | 781 | if (edev->max_supported) { |
737 | char buf[10]; | 782 | char buf[10]; |
@@ -864,7 +909,15 @@ int extcon_dev_register(struct extcon_dev *edev) | |||
864 | 909 | ||
865 | spin_lock_init(&edev->lock); | 910 | spin_lock_init(&edev->lock); |
866 | 911 | ||
867 | RAW_INIT_NOTIFIER_HEAD(&edev->nh); | 912 | edev->nh = devm_kzalloc(&edev->dev, |
913 | sizeof(*edev->nh) * edev->max_supported, GFP_KERNEL); | ||
914 | if (!edev->nh) { | ||
915 | ret = -ENOMEM; | ||
916 | goto err_dev; | ||
917 | } | ||
918 | |||
919 | for (index = 0; index < edev->max_supported; index++) | ||
920 | RAW_INIT_NOTIFIER_HEAD(&edev->nh[index]); | ||
868 | 921 | ||
869 | dev_set_drvdata(&edev->dev, edev); | 922 | dev_set_drvdata(&edev->dev, edev); |
870 | edev->state = 0; | 923 | edev->state = 0; |
@@ -1044,6 +1097,15 @@ struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) | |||
1044 | #endif /* CONFIG_OF */ | 1097 | #endif /* CONFIG_OF */ |
1045 | EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle); | 1098 | EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle); |
1046 | 1099 | ||
1100 | /** | ||
1101 | * extcon_get_edev_name() - Get the name of the extcon device. | ||
1102 | * @edev: the extcon device | ||
1103 | */ | ||
1104 | const char *extcon_get_edev_name(struct extcon_dev *edev) | ||
1105 | { | ||
1106 | return !edev ? NULL : edev->name; | ||
1107 | } | ||
1108 | |||
1047 | static int __init extcon_class_init(void) | 1109 | static int __init extcon_class_init(void) |
1048 | { | 1110 | { |
1049 | return create_extcon_class(); | 1111 | return create_extcon_class(); |
@@ -1059,6 +1121,7 @@ static void __exit extcon_class_exit(void) | |||
1059 | } | 1121 | } |
1060 | module_exit(extcon_class_exit); | 1122 | module_exit(extcon_class_exit); |
1061 | 1123 | ||
1124 | MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); | ||
1062 | MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); | 1125 | MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); |
1063 | MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); | 1126 | MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); |
1064 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); | 1127 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); |