Commit | Line | Data |
---|---|---|
9c92ab61 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
de55d871 | 2 | /* |
6ab6094f | 3 | * drivers/extcon/extcon.c - External Connector (extcon) framework. |
de55d871 | 4 | * |
2a9de9c0 CC |
5 | * Copyright (C) 2015 Samsung Electronics |
6 | * Author: Chanwoo Choi <cw00.choi@samsung.com> | |
7 | * | |
de55d871 MH |
8 | * Copyright (C) 2012 Samsung Electronics |
9 | * Author: Donggeun Kim <dg77.kim@samsung.com> | |
10 | * Author: MyungJoo Ham <myungjoo.ham@samsung.com> | |
11 | * | |
12 | * based on android/drivers/switch/switch_class.c | |
13 | * Copyright (C) 2008 Google, Inc. | |
14 | * Author: Mike Lockwood <lockwood@android.com> | |
b9ec23c0 | 15 | */ |
de55d871 MH |
16 | |
17 | #include <linux/module.h> | |
18 | #include <linux/types.h> | |
19 | #include <linux/init.h> | |
20 | #include <linux/device.h> | |
21 | #include <linux/fs.h> | |
22 | #include <linux/err.h> | |
f841afb1 | 23 | #include <linux/of.h> |
de55d871 | 24 | #include <linux/slab.h> |
9baf3220 | 25 | #include <linux/sysfs.h> |
de55d871 | 26 | |
e6cf0465 CC |
27 | #include "extcon.h" |
28 | ||
2a9de9c0 | 29 | #define SUPPORTED_CABLE_MAX 32 |
2a9de9c0 | 30 | |
5183240c | 31 | static const struct __extcon_info { |
55e4e2f1 CC |
32 | unsigned int type; |
33 | unsigned int id; | |
34 | const char *name; | |
35 | ||
36 | } extcon_info[] = { | |
37 | [EXTCON_NONE] = { | |
38 | .type = EXTCON_TYPE_MISC, | |
39 | .id = EXTCON_NONE, | |
40 | .name = "NONE", | |
41 | }, | |
73b6ecdb | 42 | |
8e9bc36d | 43 | /* USB external connector */ |
55e4e2f1 CC |
44 | [EXTCON_USB] = { |
45 | .type = EXTCON_TYPE_USB, | |
46 | .id = EXTCON_USB, | |
47 | .name = "USB", | |
48 | }, | |
49 | [EXTCON_USB_HOST] = { | |
50 | .type = EXTCON_TYPE_USB, | |
51 | .id = EXTCON_USB_HOST, | |
86d6cda6 | 52 | .name = "USB-HOST", |
55e4e2f1 | 53 | }, |
8e9bc36d | 54 | |
11eecf91 | 55 | /* Charging external connector */ |
55e4e2f1 CC |
56 | [EXTCON_CHG_USB_SDP] = { |
57 | .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, | |
58 | .id = EXTCON_CHG_USB_SDP, | |
59 | .name = "SDP", | |
60 | }, | |
61 | [EXTCON_CHG_USB_DCP] = { | |
62 | .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, | |
63 | .id = EXTCON_CHG_USB_DCP, | |
64 | .name = "DCP", | |
65 | }, | |
66 | [EXTCON_CHG_USB_CDP] = { | |
67 | .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, | |
68 | .id = EXTCON_CHG_USB_CDP, | |
69 | .name = "CDP", | |
70 | }, | |
71 | [EXTCON_CHG_USB_ACA] = { | |
72 | .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, | |
73 | .id = EXTCON_CHG_USB_ACA, | |
74 | .name = "ACA", | |
75 | }, | |
76 | [EXTCON_CHG_USB_FAST] = { | |
77 | .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, | |
78 | .id = EXTCON_CHG_USB_FAST, | |
79 | .name = "FAST-CHARGER", | |
80 | }, | |
81 | [EXTCON_CHG_USB_SLOW] = { | |
82 | .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, | |
83 | .id = EXTCON_CHG_USB_SLOW, | |
84 | .name = "SLOW-CHARGER", | |
85 | }, | |
7fe95fb8 CC |
86 | [EXTCON_CHG_WPT] = { |
87 | .type = EXTCON_TYPE_CHG, | |
88 | .id = EXTCON_CHG_WPT, | |
89 | .name = "WPT", | |
90 | }, | |
3c5f0e07 CC |
91 | [EXTCON_CHG_USB_PD] = { |
92 | .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, | |
93 | .id = EXTCON_CHG_USB_PD, | |
94 | .name = "PD", | |
95 | }, | |
8e9bc36d | 96 | |
11eecf91 | 97 | /* Jack external connector */ |
55e4e2f1 CC |
98 | [EXTCON_JACK_MICROPHONE] = { |
99 | .type = EXTCON_TYPE_JACK, | |
100 | .id = EXTCON_JACK_MICROPHONE, | |
101 | .name = "MICROPHONE", | |
102 | }, | |
103 | [EXTCON_JACK_HEADPHONE] = { | |
104 | .type = EXTCON_TYPE_JACK, | |
105 | .id = EXTCON_JACK_HEADPHONE, | |
106 | .name = "HEADPHONE", | |
107 | }, | |
108 | [EXTCON_JACK_LINE_IN] = { | |
109 | .type = EXTCON_TYPE_JACK, | |
110 | .id = EXTCON_JACK_LINE_IN, | |
111 | .name = "LINE-IN", | |
112 | }, | |
113 | [EXTCON_JACK_LINE_OUT] = { | |
114 | .type = EXTCON_TYPE_JACK, | |
115 | .id = EXTCON_JACK_LINE_OUT, | |
116 | .name = "LINE-OUT", | |
117 | }, | |
118 | [EXTCON_JACK_VIDEO_IN] = { | |
119 | .type = EXTCON_TYPE_JACK, | |
120 | .id = EXTCON_JACK_VIDEO_IN, | |
121 | .name = "VIDEO-IN", | |
122 | }, | |
123 | [EXTCON_JACK_VIDEO_OUT] = { | |
124 | .type = EXTCON_TYPE_JACK, | |
125 | .id = EXTCON_JACK_VIDEO_OUT, | |
126 | .name = "VIDEO-OUT", | |
127 | }, | |
128 | [EXTCON_JACK_SPDIF_IN] = { | |
129 | .type = EXTCON_TYPE_JACK, | |
130 | .id = EXTCON_JACK_SPDIF_IN, | |
131 | .name = "SPDIF-IN", | |
132 | }, | |
133 | [EXTCON_JACK_SPDIF_OUT] = { | |
134 | .type = EXTCON_TYPE_JACK, | |
135 | .id = EXTCON_JACK_SPDIF_OUT, | |
136 | .name = "SPDIF-OUT", | |
137 | }, | |
8e9bc36d | 138 | |
11eecf91 | 139 | /* Display external connector */ |
55e4e2f1 CC |
140 | [EXTCON_DISP_HDMI] = { |
141 | .type = EXTCON_TYPE_DISP, | |
142 | .id = EXTCON_DISP_HDMI, | |
143 | .name = "HDMI", | |
144 | }, | |
145 | [EXTCON_DISP_MHL] = { | |
146 | .type = EXTCON_TYPE_DISP, | |
147 | .id = EXTCON_DISP_MHL, | |
148 | .name = "MHL", | |
149 | }, | |
150 | [EXTCON_DISP_DVI] = { | |
151 | .type = EXTCON_TYPE_DISP, | |
152 | .id = EXTCON_DISP_DVI, | |
153 | .name = "DVI", | |
154 | }, | |
155 | [EXTCON_DISP_VGA] = { | |
156 | .type = EXTCON_TYPE_DISP, | |
157 | .id = EXTCON_DISP_VGA, | |
158 | .name = "VGA", | |
159 | }, | |
2164188d CZ |
160 | [EXTCON_DISP_DP] = { |
161 | .type = EXTCON_TYPE_DISP | EXTCON_TYPE_USB, | |
162 | .id = EXTCON_DISP_DP, | |
163 | .name = "DP", | |
164 | }, | |
9c0595d6 CC |
165 | [EXTCON_DISP_HMD] = { |
166 | .type = EXTCON_TYPE_DISP | EXTCON_TYPE_USB, | |
167 | .id = EXTCON_DISP_HMD, | |
168 | .name = "HMD", | |
169 | }, | |
3a06ed80 MW |
170 | [EXTCON_DISP_CVBS] = { |
171 | .type = EXTCON_TYPE_DISP, | |
172 | .id = EXTCON_DISP_CVBS, | |
173 | .name = "CVBS", | |
174 | }, | |
175 | [EXTCON_DISP_EDP] = { | |
176 | .type = EXTCON_TYPE_DISP, | |
177 | .id = EXTCON_DISP_EDP, | |
178 | .name = "EDP", | |
179 | }, | |
8e9bc36d | 180 | |
11eecf91 | 181 | /* Miscellaneous external connector */ |
55e4e2f1 CC |
182 | [EXTCON_DOCK] = { |
183 | .type = EXTCON_TYPE_MISC, | |
184 | .id = EXTCON_DOCK, | |
185 | .name = "DOCK", | |
186 | }, | |
187 | [EXTCON_JIG] = { | |
188 | .type = EXTCON_TYPE_MISC, | |
189 | .id = EXTCON_JIG, | |
190 | .name = "JIG", | |
191 | }, | |
192 | [EXTCON_MECHANICAL] = { | |
193 | .type = EXTCON_TYPE_MISC, | |
194 | .id = EXTCON_MECHANICAL, | |
195 | .name = "MECHANICAL", | |
196 | }, | |
197 | ||
198 | { /* sentinel */ } | |
806d9dd7 MH |
199 | }; |
200 | ||
20f7b53d | 201 | /** |
6ab6094f CC |
202 | * struct extcon_cable - An internal data for an external connector. |
203 | * @edev: the extcon device | |
204 | * @cable_index: the index of this cable in the edev | |
205 | * @attr_g: the attribute group for the cable | |
20f7b53d CC |
206 | * @attr_name: "name" sysfs entry |
207 | * @attr_state: "state" sysfs entry | |
6ab6094f | 208 | * @attrs: the array pointing to attr_name and attr_state for attr_g |
20f7b53d CC |
209 | */ |
210 | struct extcon_cable { | |
211 | struct extcon_dev *edev; | |
212 | int cable_index; | |
213 | ||
214 | struct attribute_group attr_g; | |
215 | struct device_attribute attr_name; | |
216 | struct device_attribute attr_state; | |
217 | ||
218 | struct attribute *attrs[3]; /* to be fed to attr_g.attrs */ | |
792e7e9e CC |
219 | |
220 | union extcon_property_value usb_propval[EXTCON_PROP_USB_CNT]; | |
221 | union extcon_property_value chg_propval[EXTCON_PROP_CHG_CNT]; | |
222 | union extcon_property_value jack_propval[EXTCON_PROP_JACK_CNT]; | |
223 | union extcon_property_value disp_propval[EXTCON_PROP_DISP_CNT]; | |
7f2a0a16 CC |
224 | |
225 | unsigned long usb_bits[BITS_TO_LONGS(EXTCON_PROP_USB_CNT)]; | |
226 | unsigned long chg_bits[BITS_TO_LONGS(EXTCON_PROP_CHG_CNT)]; | |
227 | unsigned long jack_bits[BITS_TO_LONGS(EXTCON_PROP_JACK_CNT)]; | |
228 | unsigned long disp_bits[BITS_TO_LONGS(EXTCON_PROP_DISP_CNT)]; | |
20f7b53d CC |
229 | }; |
230 | ||
be3a07f7 | 231 | static struct class *extcon_class; |
de55d871 | 232 | |
74c5d09b DK |
233 | static LIST_HEAD(extcon_dev_list); |
234 | static DEFINE_MUTEX(extcon_dev_list_lock); | |
235 | ||
bde68e60 MH |
236 | static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state) |
237 | { | |
238 | int i = 0; | |
239 | ||
240 | if (!edev->mutually_exclusive) | |
241 | return 0; | |
242 | ||
243 | for (i = 0; edev->mutually_exclusive[i]; i++) { | |
28c0ada6 | 244 | int weight; |
bde68e60 | 245 | u32 correspondants = new_state & edev->mutually_exclusive[i]; |
bde68e60 | 246 | |
28c0ada6 | 247 | /* calculate the total number of bits set */ |
248 | weight = hweight32(correspondants); | |
249 | if (weight > 1) | |
250 | return i + 1; | |
bde68e60 MH |
251 | } |
252 | ||
253 | return 0; | |
254 | } | |
255 | ||
73b6ecdb | 256 | static int find_cable_index_by_id(struct extcon_dev *edev, const unsigned int id) |
2a9de9c0 CC |
257 | { |
258 | int i; | |
259 | ||
97e1bb93 | 260 | /* Find the index of extcon cable in edev->supported_cable */ |
2a9de9c0 CC |
261 | for (i = 0; i < edev->max_supported; i++) { |
262 | if (edev->supported_cable[i] == id) | |
263 | return i; | |
264 | } | |
265 | ||
266 | return -EINVAL; | |
267 | } | |
268 | ||
792e7e9e CC |
269 | static int get_extcon_type(unsigned int prop) |
270 | { | |
271 | switch (prop) { | |
272 | case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX: | |
273 | return EXTCON_TYPE_USB; | |
274 | case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX: | |
275 | return EXTCON_TYPE_CHG; | |
276 | case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX: | |
277 | return EXTCON_TYPE_JACK; | |
278 | case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX: | |
279 | return EXTCON_TYPE_DISP; | |
280 | default: | |
281 | return -EINVAL; | |
282 | } | |
283 | } | |
284 | ||
285 | static bool is_extcon_attached(struct extcon_dev *edev, unsigned int index) | |
286 | { | |
287 | return !!(edev->state & BIT(index)); | |
288 | } | |
289 | ||
ab11af04 CC |
290 | static bool is_extcon_changed(struct extcon_dev *edev, int index, |
291 | bool new_state) | |
046050f6 | 292 | { |
ab11af04 CC |
293 | int state = !!(edev->state & BIT(index)); |
294 | return (state != new_state); | |
046050f6 CC |
295 | } |
296 | ||
792e7e9e CC |
297 | static bool is_extcon_property_supported(unsigned int id, unsigned int prop) |
298 | { | |
299 | int type; | |
300 | ||
301 | /* Check whether the property is supported or not. */ | |
302 | type = get_extcon_type(prop); | |
303 | if (type < 0) | |
304 | return false; | |
305 | ||
306 | /* Check whether a specific extcon id supports the property or not. */ | |
307 | return !!(extcon_info[id].type & type); | |
308 | } | |
309 | ||
7f2a0a16 CC |
310 | static int is_extcon_property_capability(struct extcon_dev *edev, |
311 | unsigned int id, int index,unsigned int prop) | |
312 | { | |
313 | struct extcon_cable *cable; | |
314 | int type, ret; | |
315 | ||
316 | /* Check whether the property is supported or not. */ | |
317 | type = get_extcon_type(prop); | |
318 | if (type < 0) | |
319 | return type; | |
320 | ||
321 | cable = &edev->cables[index]; | |
322 | ||
323 | switch (type) { | |
324 | case EXTCON_TYPE_USB: | |
325 | ret = test_bit(prop - EXTCON_PROP_USB_MIN, cable->usb_bits); | |
326 | break; | |
327 | case EXTCON_TYPE_CHG: | |
328 | ret = test_bit(prop - EXTCON_PROP_CHG_MIN, cable->chg_bits); | |
329 | break; | |
330 | case EXTCON_TYPE_JACK: | |
331 | ret = test_bit(prop - EXTCON_PROP_JACK_MIN, cable->jack_bits); | |
332 | break; | |
333 | case EXTCON_TYPE_DISP: | |
334 | ret = test_bit(prop - EXTCON_PROP_DISP_MIN, cable->disp_bits); | |
335 | break; | |
336 | default: | |
337 | ret = -EINVAL; | |
338 | } | |
339 | ||
340 | return ret; | |
341 | } | |
342 | ||
792e7e9e CC |
343 | static void init_property(struct extcon_dev *edev, unsigned int id, int index) |
344 | { | |
345 | unsigned int type = extcon_info[id].type; | |
346 | struct extcon_cable *cable = &edev->cables[index]; | |
347 | ||
348 | if (EXTCON_TYPE_USB & type) | |
349 | memset(cable->usb_propval, 0, sizeof(cable->usb_propval)); | |
350 | if (EXTCON_TYPE_CHG & type) | |
351 | memset(cable->chg_propval, 0, sizeof(cable->chg_propval)); | |
352 | if (EXTCON_TYPE_JACK & type) | |
353 | memset(cable->jack_propval, 0, sizeof(cable->jack_propval)); | |
354 | if (EXTCON_TYPE_DISP & type) | |
355 | memset(cable->disp_propval, 0, sizeof(cable->disp_propval)); | |
356 | } | |
357 | ||
de55d871 MH |
358 | static ssize_t state_show(struct device *dev, struct device_attribute *attr, |
359 | char *buf) | |
360 | { | |
806d9dd7 | 361 | int i, count = 0; |
cb8bb3a7 | 362 | struct extcon_dev *edev = dev_get_drvdata(dev); |
de55d871 | 363 | |
806d9dd7 MH |
364 | if (edev->max_supported == 0) |
365 | return sprintf(buf, "%u\n", edev->state); | |
366 | ||
2a9de9c0 | 367 | for (i = 0; i < edev->max_supported; i++) { |
806d9dd7 | 368 | count += sprintf(buf + count, "%s=%d\n", |
55e4e2f1 | 369 | extcon_info[edev->supported_cable[i]].name, |
70641a0a | 370 | !!(edev->state & BIT(i))); |
806d9dd7 MH |
371 | } |
372 | ||
373 | return count; | |
374 | } | |
5d5321e9 | 375 | static DEVICE_ATTR_RO(state); |
de55d871 MH |
376 | |
377 | static ssize_t name_show(struct device *dev, struct device_attribute *attr, | |
378 | char *buf) | |
379 | { | |
cb8bb3a7 | 380 | struct extcon_dev *edev = dev_get_drvdata(dev); |
de55d871 | 381 | |
71c3ffa5 | 382 | return sprintf(buf, "%s\n", edev->name); |
de55d871 | 383 | } |
af01da0e | 384 | static DEVICE_ATTR_RO(name); |
de55d871 | 385 | |
806d9dd7 MH |
386 | static ssize_t cable_name_show(struct device *dev, |
387 | struct device_attribute *attr, char *buf) | |
388 | { | |
389 | struct extcon_cable *cable = container_of(attr, struct extcon_cable, | |
390 | attr_name); | |
2a9de9c0 | 391 | int i = cable->cable_index; |
806d9dd7 MH |
392 | |
393 | return sprintf(buf, "%s\n", | |
55e4e2f1 | 394 | extcon_info[cable->edev->supported_cable[i]].name); |
806d9dd7 MH |
395 | } |
396 | ||
397 | static ssize_t cable_state_show(struct device *dev, | |
398 | struct device_attribute *attr, char *buf) | |
399 | { | |
400 | struct extcon_cable *cable = container_of(attr, struct extcon_cable, | |
401 | attr_state); | |
402 | ||
be052cc8 RQ |
403 | int i = cable->cable_index; |
404 | ||
806d9dd7 | 405 | return sprintf(buf, "%d\n", |
575c2b86 | 406 | extcon_get_state(cable->edev, cable->edev->supported_cable[i])); |
806d9dd7 MH |
407 | } |
408 | ||
de55d871 | 409 | /** |
6ab6094f CC |
410 | * extcon_sync() - Synchronize the state for an external connector. |
411 | * @edev: the extcon device | |
6506f6a0 | 412 | * @id: the unique id indicating an external connector |
6ab6094f CC |
413 | * |
414 | * Note that this function send a notification in order to synchronize | |
415 | * the state and property of an external connector. | |
74c5d09b | 416 | * |
6ab6094f | 417 | * Returns 0 if success or error number if fail. |
de55d871 | 418 | */ |
ab11af04 | 419 | int extcon_sync(struct extcon_dev *edev, unsigned int id) |
de55d871 MH |
420 | { |
421 | char name_buf[120]; | |
422 | char state_buf[120]; | |
423 | char *prop_buf; | |
424 | char *envp[3]; | |
425 | int env_offset = 0; | |
426 | int length; | |
046050f6 | 427 | int index; |
ab11af04 | 428 | int state; |
806d9dd7 | 429 | unsigned long flags; |
de55d871 | 430 | |
7eae43ae CC |
431 | if (!edev) |
432 | return -EINVAL; | |
433 | ||
ab11af04 CC |
434 | index = find_cable_index_by_id(edev, id); |
435 | if (index < 0) | |
436 | return index; | |
437 | ||
806d9dd7 | 438 | spin_lock_irqsave(&edev->lock, flags); |
ab11af04 | 439 | state = !!(edev->state & BIT(index)); |
8a9dbb77 | 440 | spin_unlock_irqrestore(&edev->lock, flags); |
815429b3 CC |
441 | |
442 | /* | |
443 | * Call functions in a raw notifier chain for the specific one | |
444 | * external connector. | |
445 | */ | |
ab11af04 CC |
446 | raw_notifier_call_chain(&edev->nh[index], state, edev); |
447 | ||
815429b3 CC |
448 | /* |
449 | * Call functions in a raw notifier chain for the all supported | |
450 | * external connectors. | |
451 | */ | |
452 | raw_notifier_call_chain(&edev->nh_all, state, edev); | |
453 | ||
8a9dbb77 | 454 | spin_lock_irqsave(&edev->lock, flags); |
ab11af04 CC |
455 | /* This could be in interrupt handler */ |
456 | prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); | |
457 | if (!prop_buf) { | |
458 | /* Unlock early before uevent */ | |
806d9dd7 | 459 | spin_unlock_irqrestore(&edev->lock, flags); |
ab11af04 CC |
460 | |
461 | dev_err(&edev->dev, "out of memory in extcon_set_state\n"); | |
462 | kobject_uevent(&edev->dev.kobj, KOBJ_CHANGE); | |
463 | ||
e7d9dd5a | 464 | return -ENOMEM; |
ab11af04 CC |
465 | } |
466 | ||
467 | length = name_show(&edev->dev, NULL, prop_buf); | |
468 | if (length > 0) { | |
469 | if (prop_buf[length - 1] == '\n') | |
470 | prop_buf[length - 1] = 0; | |
471 | snprintf(name_buf, sizeof(name_buf), "NAME=%s", prop_buf); | |
472 | envp[env_offset++] = name_buf; | |
473 | } | |
474 | ||
475 | length = state_show(&edev->dev, NULL, prop_buf); | |
476 | if (length > 0) { | |
477 | if (prop_buf[length - 1] == '\n') | |
478 | prop_buf[length - 1] = 0; | |
479 | snprintf(state_buf, sizeof(state_buf), "STATE=%s", prop_buf); | |
480 | envp[env_offset++] = state_buf; | |
de55d871 | 481 | } |
ab11af04 CC |
482 | envp[env_offset] = NULL; |
483 | ||
484 | /* Unlock early before uevent */ | |
485 | spin_unlock_irqrestore(&edev->lock, flags); | |
486 | kobject_uevent_env(&edev->dev.kobj, KOBJ_CHANGE, envp); | |
487 | free_page((unsigned long)prop_buf); | |
bde68e60 MH |
488 | |
489 | return 0; | |
de55d871 | 490 | } |
ab11af04 | 491 | EXPORT_SYMBOL_GPL(extcon_sync); |
de55d871 | 492 | |
806d9dd7 | 493 | /** |
6ab6094f CC |
494 | * extcon_get_state() - Get the state of an external connector. |
495 | * @edev: the extcon device | |
496 | * @id: the unique id indicating an external connector | |
497 | * | |
498 | * Returns 0 if success or error number if fail. | |
806d9dd7 | 499 | */ |
575c2b86 | 500 | int extcon_get_state(struct extcon_dev *edev, const unsigned int id) |
806d9dd7 | 501 | { |
575c2b86 CC |
502 | int index, state; |
503 | unsigned long flags; | |
806d9dd7 | 504 | |
7eae43ae CC |
505 | if (!edev) |
506 | return -EINVAL; | |
507 | ||
2a9de9c0 CC |
508 | index = find_cable_index_by_id(edev, id); |
509 | if (index < 0) | |
510 | return index; | |
806d9dd7 | 511 | |
575c2b86 CC |
512 | spin_lock_irqsave(&edev->lock, flags); |
513 | state = is_extcon_attached(edev, index); | |
514 | spin_unlock_irqrestore(&edev->lock, flags); | |
806d9dd7 | 515 | |
575c2b86 | 516 | return state; |
806d9dd7 | 517 | } |
575c2b86 | 518 | EXPORT_SYMBOL_GPL(extcon_get_state); |
806d9dd7 | 519 | |
806d9dd7 | 520 | /** |
6ab6094f CC |
521 | * extcon_set_state() - Set the state of an external connector. |
522 | * @edev: the extcon device | |
523 | * @id: the unique id indicating an external connector | |
524 | * @state: the new state of an external connector. | |
525 | * the default semantics is true: attached / false: detached. | |
526 | * | |
527 | * Note that this function set the state of an external connector without | |
528 | * a notification. To synchronize the state of an external connector, | |
529 | * have to use extcon_set_state_sync() and extcon_sync(). | |
ab11af04 | 530 | * |
6ab6094f | 531 | * Returns 0 if success or error number if fail. |
806d9dd7 | 532 | */ |
6ab6094f | 533 | int extcon_set_state(struct extcon_dev *edev, unsigned int id, bool state) |
806d9dd7 | 534 | { |
ab11af04 CC |
535 | unsigned long flags; |
536 | int index, ret = 0; | |
806d9dd7 | 537 | |
7eae43ae CC |
538 | if (!edev) |
539 | return -EINVAL; | |
540 | ||
2a9de9c0 CC |
541 | index = find_cable_index_by_id(edev, id); |
542 | if (index < 0) | |
543 | return index; | |
544 | ||
ab11af04 CC |
545 | spin_lock_irqsave(&edev->lock, flags); |
546 | ||
547 | /* Check whether the external connector's state is changed. */ | |
6ab6094f | 548 | if (!is_extcon_changed(edev, index, state)) |
ab11af04 CC |
549 | goto out; |
550 | ||
551 | if (check_mutually_exclusive(edev, | |
6ab6094f | 552 | (edev->state & ~BIT(index)) | (state & BIT(index)))) { |
ab11af04 CC |
553 | ret = -EPERM; |
554 | goto out; | |
555 | } | |
556 | ||
792e7e9e CC |
557 | /* |
558 | * Initialize the value of extcon property before setting | |
559 | * the detached state for an external connector. | |
560 | */ | |
6ab6094f | 561 | if (!state) |
792e7e9e CC |
562 | init_property(edev, id, index); |
563 | ||
6ab6094f CC |
564 | /* Update the state for an external connector. */ |
565 | if (state) | |
ab11af04 CC |
566 | edev->state |= BIT(index); |
567 | else | |
568 | edev->state &= ~(BIT(index)); | |
569 | out: | |
570 | spin_unlock_irqrestore(&edev->lock, flags); | |
571 | ||
572 | return ret; | |
806d9dd7 | 573 | } |
575c2b86 | 574 | EXPORT_SYMBOL_GPL(extcon_set_state); |
806d9dd7 | 575 | |
ab11af04 | 576 | /** |
6ab6094f CC |
577 | * extcon_set_state_sync() - Set the state of an external connector with sync. |
578 | * @edev: the extcon device | |
579 | * @id: the unique id indicating an external connector | |
580 | * @state: the new state of external connector. | |
581 | * the default semantics is true: attached / false: detached. | |
582 | * | |
583 | * Note that this function set the state of external connector | |
584 | * and synchronize the state by sending a notification. | |
ab11af04 | 585 | * |
6ab6094f | 586 | * Returns 0 if success or error number if fail. |
ab11af04 | 587 | */ |
6ab6094f | 588 | int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, bool state) |
ab11af04 | 589 | { |
2da3db7f | 590 | int ret; |
ab11af04 | 591 | |
6ab6094f | 592 | ret = extcon_set_state(edev, id, state); |
ab11af04 CC |
593 | if (ret < 0) |
594 | return ret; | |
595 | ||
596 | return extcon_sync(edev, id); | |
597 | } | |
598 | EXPORT_SYMBOL_GPL(extcon_set_state_sync); | |
599 | ||
792e7e9e | 600 | /** |
6ab6094f CC |
601 | * extcon_get_property() - Get the property value of an external connector. |
602 | * @edev: the extcon device | |
603 | * @id: the unique id indicating an external connector | |
604 | * @prop: the property id indicating an extcon property | |
605 | * @prop_val: the pointer which store the value of extcon property | |
792e7e9e | 606 | * |
6ab6094f CC |
607 | * Note that when getting the property value of external connector, |
608 | * the external connector should be attached. If detached state, function | |
609 | * return 0 without property value. Also, the each property should be | |
610 | * included in the list of supported properties according to extcon type. | |
792e7e9e | 611 | * |
6ab6094f | 612 | * Returns 0 if success or error number if fail. |
792e7e9e CC |
613 | */ |
614 | int extcon_get_property(struct extcon_dev *edev, unsigned int id, | |
615 | unsigned int prop, | |
616 | union extcon_property_value *prop_val) | |
617 | { | |
618 | struct extcon_cable *cable; | |
619 | unsigned long flags; | |
620 | int index, ret = 0; | |
621 | ||
cff7499d | 622 | *prop_val = (union extcon_property_value){0}; |
792e7e9e CC |
623 | |
624 | if (!edev) | |
625 | return -EINVAL; | |
626 | ||
627 | /* Check whether the property is supported or not */ | |
628 | if (!is_extcon_property_supported(id, prop)) | |
629 | return -EINVAL; | |
630 | ||
631 | /* Find the cable index of external connector by using id */ | |
632 | index = find_cable_index_by_id(edev, id); | |
633 | if (index < 0) | |
634 | return index; | |
635 | ||
636 | spin_lock_irqsave(&edev->lock, flags); | |
637 | ||
7f2a0a16 CC |
638 | /* Check whether the property is available or not. */ |
639 | if (!is_extcon_property_capability(edev, id, index, prop)) { | |
640 | spin_unlock_irqrestore(&edev->lock, flags); | |
641 | return -EPERM; | |
642 | } | |
643 | ||
792e7e9e CC |
644 | /* |
645 | * Check whether the external connector is attached. | |
646 | * If external connector is detached, the user can not | |
647 | * get the property value. | |
648 | */ | |
649 | if (!is_extcon_attached(edev, index)) { | |
650 | spin_unlock_irqrestore(&edev->lock, flags); | |
651 | return 0; | |
652 | } | |
653 | ||
654 | cable = &edev->cables[index]; | |
655 | ||
656 | /* Get the property value according to extcon type */ | |
657 | switch (prop) { | |
658 | case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX: | |
659 | *prop_val = cable->usb_propval[prop - EXTCON_PROP_USB_MIN]; | |
660 | break; | |
661 | case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX: | |
662 | *prop_val = cable->chg_propval[prop - EXTCON_PROP_CHG_MIN]; | |
663 | break; | |
664 | case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX: | |
665 | *prop_val = cable->jack_propval[prop - EXTCON_PROP_JACK_MIN]; | |
666 | break; | |
667 | case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX: | |
668 | *prop_val = cable->disp_propval[prop - EXTCON_PROP_DISP_MIN]; | |
669 | break; | |
670 | default: | |
671 | ret = -EINVAL; | |
672 | break; | |
673 | } | |
674 | ||
675 | spin_unlock_irqrestore(&edev->lock, flags); | |
676 | ||
677 | return ret; | |
678 | } | |
679 | EXPORT_SYMBOL_GPL(extcon_get_property); | |
680 | ||
681 | /** | |
6ab6094f CC |
682 | * extcon_set_property() - Set the property value of an external connector. |
683 | * @edev: the extcon device | |
684 | * @id: the unique id indicating an external connector | |
685 | * @prop: the property id indicating an extcon property | |
686 | * @prop_val: the pointer including the new value of extcon property | |
792e7e9e | 687 | * |
6ab6094f CC |
688 | * Note that each property should be included in the list of supported |
689 | * properties according to the extcon type. | |
792e7e9e | 690 | * |
6ab6094f | 691 | * Returns 0 if success or error number if fail. |
792e7e9e CC |
692 | */ |
693 | int extcon_set_property(struct extcon_dev *edev, unsigned int id, | |
694 | unsigned int prop, | |
695 | union extcon_property_value prop_val) | |
696 | { | |
697 | struct extcon_cable *cable; | |
698 | unsigned long flags; | |
699 | int index, ret = 0; | |
700 | ||
701 | if (!edev) | |
702 | return -EINVAL; | |
703 | ||
704 | /* Check whether the property is supported or not */ | |
705 | if (!is_extcon_property_supported(id, prop)) | |
706 | return -EINVAL; | |
707 | ||
708 | /* Find the cable index of external connector by using id */ | |
709 | index = find_cable_index_by_id(edev, id); | |
710 | if (index < 0) | |
711 | return index; | |
712 | ||
713 | spin_lock_irqsave(&edev->lock, flags); | |
714 | ||
7f2a0a16 CC |
715 | /* Check whether the property is available or not. */ |
716 | if (!is_extcon_property_capability(edev, id, index, prop)) { | |
717 | spin_unlock_irqrestore(&edev->lock, flags); | |
718 | return -EPERM; | |
719 | } | |
720 | ||
792e7e9e CC |
721 | cable = &edev->cables[index]; |
722 | ||
723 | /* Set the property value according to extcon type */ | |
724 | switch (prop) { | |
725 | case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX: | |
726 | cable->usb_propval[prop - EXTCON_PROP_USB_MIN] = prop_val; | |
727 | break; | |
728 | case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX: | |
729 | cable->chg_propval[prop - EXTCON_PROP_CHG_MIN] = prop_val; | |
730 | break; | |
731 | case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX: | |
732 | cable->jack_propval[prop - EXTCON_PROP_JACK_MIN] = prop_val; | |
733 | break; | |
734 | case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX: | |
735 | cable->disp_propval[prop - EXTCON_PROP_DISP_MIN] = prop_val; | |
736 | break; | |
737 | default: | |
738 | ret = -EINVAL; | |
739 | break; | |
740 | } | |
741 | ||
742 | spin_unlock_irqrestore(&edev->lock, flags); | |
743 | ||
744 | return ret; | |
745 | } | |
746 | EXPORT_SYMBOL_GPL(extcon_set_property); | |
747 | ||
ab11af04 | 748 | /** |
6ab6094f | 749 | * extcon_set_property_sync() - Set property of an external connector with sync. |
6506f6a0 YL |
750 | * @edev: the extcon device |
751 | * @id: the unique id indicating an external connector | |
752 | * @prop: the property id indicating an extcon property | |
6ab6094f | 753 | * @prop_val: the pointer including the new value of extcon property |
ab11af04 | 754 | * |
6ab6094f CC |
755 | * Note that when setting the property value of external connector, |
756 | * the external connector should be attached. The each property should | |
757 | * be included in the list of supported properties according to extcon type. | |
ab11af04 | 758 | * |
6ab6094f | 759 | * Returns 0 if success or error number if fail. |
ab11af04 CC |
760 | */ |
761 | int extcon_set_property_sync(struct extcon_dev *edev, unsigned int id, | |
762 | unsigned int prop, | |
763 | union extcon_property_value prop_val) | |
764 | { | |
765 | int ret; | |
766 | ||
767 | ret = extcon_set_property(edev, id, prop, prop_val); | |
768 | if (ret < 0) | |
769 | return ret; | |
770 | ||
771 | return extcon_sync(edev, id); | |
772 | } | |
773 | EXPORT_SYMBOL_GPL(extcon_set_property_sync); | |
774 | ||
7f2a0a16 | 775 | /** |
6ab6094f CC |
776 | * extcon_get_property_capability() - Get the capability of the property |
777 | * for an external connector. | |
778 | * @edev: the extcon device | |
779 | * @id: the unique id indicating an external connector | |
780 | * @prop: the property id indicating an extcon property | |
7f2a0a16 CC |
781 | * |
782 | * Returns 1 if the property is available or 0 if not available. | |
783 | */ | |
784 | int extcon_get_property_capability(struct extcon_dev *edev, unsigned int id, | |
785 | unsigned int prop) | |
786 | { | |
787 | int index; | |
788 | ||
789 | if (!edev) | |
790 | return -EINVAL; | |
791 | ||
792 | /* Check whether the property is supported or not */ | |
793 | if (!is_extcon_property_supported(id, prop)) | |
794 | return -EINVAL; | |
795 | ||
796 | /* Find the cable index of external connector by using id */ | |
797 | index = find_cable_index_by_id(edev, id); | |
798 | if (index < 0) | |
799 | return index; | |
800 | ||
801 | return is_extcon_property_capability(edev, id, index, prop); | |
802 | } | |
803 | EXPORT_SYMBOL_GPL(extcon_get_property_capability); | |
804 | ||
805 | /** | |
6ab6094f CC |
806 | * extcon_set_property_capability() - Set the capability of the property |
807 | * for an external connector. | |
808 | * @edev: the extcon device | |
809 | * @id: the unique id indicating an external connector | |
810 | * @prop: the property id indicating an extcon property | |
7f2a0a16 | 811 | * |
6ab6094f CC |
812 | * Note that this function set the capability of the property |
813 | * for an external connector in order to mark the bit in capability | |
814 | * bitmap which mean the available state of the property. | |
7f2a0a16 | 815 | * |
6ab6094f | 816 | * Returns 0 if success or error number if fail. |
7f2a0a16 CC |
817 | */ |
818 | int extcon_set_property_capability(struct extcon_dev *edev, unsigned int id, | |
819 | unsigned int prop) | |
820 | { | |
821 | struct extcon_cable *cable; | |
822 | int index, type, ret = 0; | |
823 | ||
824 | if (!edev) | |
825 | return -EINVAL; | |
826 | ||
827 | /* Check whether the property is supported or not. */ | |
828 | if (!is_extcon_property_supported(id, prop)) | |
829 | return -EINVAL; | |
830 | ||
831 | /* Find the cable index of external connector by using id. */ | |
832 | index = find_cable_index_by_id(edev, id); | |
833 | if (index < 0) | |
834 | return index; | |
835 | ||
836 | type = get_extcon_type(prop); | |
837 | if (type < 0) | |
838 | return type; | |
839 | ||
840 | cable = &edev->cables[index]; | |
841 | ||
842 | switch (type) { | |
843 | case EXTCON_TYPE_USB: | |
844 | __set_bit(prop - EXTCON_PROP_USB_MIN, cable->usb_bits); | |
845 | break; | |
846 | case EXTCON_TYPE_CHG: | |
847 | __set_bit(prop - EXTCON_PROP_CHG_MIN, cable->chg_bits); | |
848 | break; | |
849 | case EXTCON_TYPE_JACK: | |
850 | __set_bit(prop - EXTCON_PROP_JACK_MIN, cable->jack_bits); | |
851 | break; | |
852 | case EXTCON_TYPE_DISP: | |
853 | __set_bit(prop - EXTCON_PROP_DISP_MIN, cable->disp_bits); | |
854 | break; | |
855 | default: | |
856 | ret = -EINVAL; | |
857 | } | |
858 | ||
859 | return ret; | |
860 | } | |
861 | EXPORT_SYMBOL_GPL(extcon_set_property_capability); | |
862 | ||
74c5d09b | 863 | /** |
6ab6094f CC |
864 | * extcon_get_extcon_dev() - Get the extcon device instance from the name. |
865 | * @extcon_name: the extcon name provided with extcon_dev_register() | |
866 | * | |
867 | * Return the pointer of extcon device if success or ERR_PTR(err) if fail. | |
58e4a2d2 DC |
868 | * NOTE: This function returns -EPROBE_DEFER so it may only be called from |
869 | * probe() functions. | |
74c5d09b DK |
870 | */ |
871 | struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name) | |
872 | { | |
873 | struct extcon_dev *sd; | |
874 | ||
7eae43ae CC |
875 | if (!extcon_name) |
876 | return ERR_PTR(-EINVAL); | |
877 | ||
74c5d09b DK |
878 | mutex_lock(&extcon_dev_list_lock); |
879 | list_for_each_entry(sd, &extcon_dev_list, entry) { | |
880 | if (!strcmp(sd->name, extcon_name)) | |
881 | goto out; | |
882 | } | |
58e4a2d2 | 883 | sd = ERR_PTR(-EPROBE_DEFER); |
74c5d09b DK |
884 | out: |
885 | mutex_unlock(&extcon_dev_list_lock); | |
886 | return sd; | |
887 | } | |
888 | EXPORT_SYMBOL_GPL(extcon_get_extcon_dev); | |
889 | ||
890 | /** | |
6ab6094f CC |
891 | * extcon_register_notifier() - Register a notifier block to get notified by |
892 | * any state changes from the extcon. | |
893 | * @edev: the extcon device | |
894 | * @id: the unique id indicating an external connector | |
895 | * @nb: a notifier block to be registered | |
806d9dd7 MH |
896 | * |
897 | * Note that the second parameter given to the callback of nb (val) is | |
6ab6094f CC |
898 | * the current state of an external connector and the third pameter |
899 | * is the pointer of extcon device. | |
900 | * | |
901 | * Returns 0 if success or error number if fail. | |
74c5d09b | 902 | */ |
73b6ecdb | 903 | int extcon_register_notifier(struct extcon_dev *edev, unsigned int id, |
046050f6 | 904 | struct notifier_block *nb) |
74c5d09b | 905 | { |
66bee35f | 906 | unsigned long flags; |
1fa80f18 | 907 | int ret, idx; |
046050f6 | 908 | |
01b4c9a1 | 909 | if (!edev || !nb) |
7eae43ae CC |
910 | return -EINVAL; |
911 | ||
01b4c9a1 CC |
912 | idx = find_cable_index_by_id(edev, id); |
913 | if (idx < 0) | |
914 | return idx; | |
66bee35f | 915 | |
01b4c9a1 CC |
916 | spin_lock_irqsave(&edev->lock, flags); |
917 | ret = raw_notifier_chain_register(&edev->nh[idx], nb); | |
918 | spin_unlock_irqrestore(&edev->lock, flags); | |
66bee35f HG |
919 | |
920 | return ret; | |
74c5d09b DK |
921 | } |
922 | EXPORT_SYMBOL_GPL(extcon_register_notifier); | |
923 | ||
924 | /** | |
6ab6094f CC |
925 | * extcon_unregister_notifier() - Unregister a notifier block from the extcon. |
926 | * @edev: the extcon device | |
927 | * @id: the unique id indicating an external connector | |
928 | * @nb: a notifier block to be registered | |
929 | * | |
930 | * Returns 0 if success or error number if fail. | |
74c5d09b | 931 | */ |
73b6ecdb | 932 | int extcon_unregister_notifier(struct extcon_dev *edev, unsigned int id, |
046050f6 | 933 | struct notifier_block *nb) |
74c5d09b | 934 | { |
66bee35f | 935 | unsigned long flags; |
046050f6 CC |
936 | int ret, idx; |
937 | ||
7eae43ae CC |
938 | if (!edev || !nb) |
939 | return -EINVAL; | |
940 | ||
046050f6 | 941 | idx = find_cable_index_by_id(edev, id); |
a05f44c8 SB |
942 | if (idx < 0) |
943 | return idx; | |
66bee35f HG |
944 | |
945 | spin_lock_irqsave(&edev->lock, flags); | |
046050f6 | 946 | ret = raw_notifier_chain_unregister(&edev->nh[idx], nb); |
66bee35f HG |
947 | spin_unlock_irqrestore(&edev->lock, flags); |
948 | ||
949 | return ret; | |
74c5d09b DK |
950 | } |
951 | EXPORT_SYMBOL_GPL(extcon_unregister_notifier); | |
952 | ||
815429b3 | 953 | /** |
6ab6094f CC |
954 | * extcon_register_notifier_all() - Register a notifier block for all connectors. |
955 | * @edev: the extcon device | |
956 | * @nb: a notifier block to be registered | |
815429b3 | 957 | * |
6ab6094f CC |
958 | * Note that this function registers a notifier block in order to receive |
959 | * the state change of all supported external connectors from extcon device. | |
826a47e9 | 960 | * And the second parameter given to the callback of nb (val) is |
6ab6094f | 961 | * the current state and the third pameter is the pointer of extcon device. |
815429b3 | 962 | * |
6ab6094f | 963 | * Returns 0 if success or error number if fail. |
815429b3 CC |
964 | */ |
965 | int extcon_register_notifier_all(struct extcon_dev *edev, | |
966 | struct notifier_block *nb) | |
967 | { | |
968 | unsigned long flags; | |
969 | int ret; | |
970 | ||
971 | if (!edev || !nb) | |
972 | return -EINVAL; | |
973 | ||
974 | spin_lock_irqsave(&edev->lock, flags); | |
975 | ret = raw_notifier_chain_register(&edev->nh_all, nb); | |
976 | spin_unlock_irqrestore(&edev->lock, flags); | |
977 | ||
978 | return ret; | |
979 | } | |
980 | EXPORT_SYMBOL_GPL(extcon_register_notifier_all); | |
981 | ||
982 | /** | |
983 | * extcon_unregister_notifier_all() - Unregister a notifier block from extcon. | |
6ab6094f CC |
984 | * @edev: the extcon device |
985 | * @nb: a notifier block to be registered | |
815429b3 | 986 | * |
6ab6094f | 987 | * Returns 0 if success or error number if fail. |
815429b3 CC |
988 | */ |
989 | int extcon_unregister_notifier_all(struct extcon_dev *edev, | |
990 | struct notifier_block *nb) | |
991 | { | |
992 | unsigned long flags; | |
993 | int ret; | |
994 | ||
995 | if (!edev || !nb) | |
996 | return -EINVAL; | |
997 | ||
998 | spin_lock_irqsave(&edev->lock, flags); | |
999 | ret = raw_notifier_chain_unregister(&edev->nh_all, nb); | |
1000 | spin_unlock_irqrestore(&edev->lock, flags); | |
1001 | ||
1002 | return ret; | |
1003 | } | |
1004 | EXPORT_SYMBOL_GPL(extcon_unregister_notifier_all); | |
1005 | ||
af01da0e GKH |
1006 | static struct attribute *extcon_attrs[] = { |
1007 | &dev_attr_state.attr, | |
1008 | &dev_attr_name.attr, | |
1009 | NULL, | |
de55d871 | 1010 | }; |
af01da0e | 1011 | ATTRIBUTE_GROUPS(extcon); |
de55d871 MH |
1012 | |
1013 | static int create_extcon_class(void) | |
1014 | { | |
1015 | if (!extcon_class) { | |
1aaba11d | 1016 | extcon_class = class_create("extcon"); |
de55d871 MH |
1017 | if (IS_ERR(extcon_class)) |
1018 | return PTR_ERR(extcon_class); | |
af01da0e | 1019 | extcon_class->dev_groups = extcon_groups; |
de55d871 MH |
1020 | } |
1021 | ||
1022 | return 0; | |
1023 | } | |
1024 | ||
de55d871 MH |
1025 | static void extcon_dev_release(struct device *dev) |
1026 | { | |
de55d871 MH |
1027 | } |
1028 | ||
bde68e60 | 1029 | static const char *muex_name = "mutually_exclusive"; |
806d9dd7 MH |
1030 | static void dummy_sysfs_dev_release(struct device *dev) |
1031 | { | |
1032 | } | |
1033 | ||
a9af6522 CC |
1034 | /* |
1035 | * extcon_dev_allocate() - Allocate the memory of extcon device. | |
6ab6094f CC |
1036 | * @supported_cable: the array of the supported external connectors |
1037 | * ending with EXTCON_NONE. | |
a9af6522 | 1038 | * |
6ab6094f CC |
1039 | * Note that this function allocates the memory for extcon device |
1040 | * and initialize default setting for the extcon device. | |
a9af6522 | 1041 | * |
6ab6094f CC |
1042 | * Returns the pointer memory of allocated extcon_dev if success |
1043 | * or ERR_PTR(err) if fail. | |
a9af6522 | 1044 | */ |
73b6ecdb | 1045 | struct extcon_dev *extcon_dev_allocate(const unsigned int *supported_cable) |
a9af6522 CC |
1046 | { |
1047 | struct extcon_dev *edev; | |
1048 | ||
7eae43ae CC |
1049 | if (!supported_cable) |
1050 | return ERR_PTR(-EINVAL); | |
1051 | ||
a9af6522 CC |
1052 | edev = kzalloc(sizeof(*edev), GFP_KERNEL); |
1053 | if (!edev) | |
1054 | return ERR_PTR(-ENOMEM); | |
1055 | ||
1056 | edev->max_supported = 0; | |
1057 | edev->supported_cable = supported_cable; | |
1058 | ||
1059 | return edev; | |
1060 | } | |
1061 | ||
1062 | /* | |
1063 | * extcon_dev_free() - Free the memory of extcon device. | |
6ab6094f | 1064 | * @edev: the extcon device |
a9af6522 CC |
1065 | */ |
1066 | void extcon_dev_free(struct extcon_dev *edev) | |
1067 | { | |
1068 | kfree(edev); | |
1069 | } | |
1070 | EXPORT_SYMBOL_GPL(extcon_dev_free); | |
1071 | ||
de55d871 | 1072 | /** |
6ab6094f CC |
1073 | * extcon_dev_register() - Register an new extcon device |
1074 | * @edev: the extcon device to be registered | |
de55d871 MH |
1075 | * |
1076 | * Among the members of edev struct, please set the "user initializing data" | |
de55d871 MH |
1077 | * do not set the values of "internal data", which are initialized by |
1078 | * this function. | |
6ab6094f CC |
1079 | * |
1080 | * Note that before calling this funciton, have to allocate the memory | |
1081 | * of an extcon device by using the extcon_dev_allocate(). And the extcon | |
1082 | * dev should include the supported_cable information. | |
1083 | * | |
1084 | * Returns 0 if success or error number if fail. | |
de55d871 | 1085 | */ |
42d7d753 | 1086 | int extcon_dev_register(struct extcon_dev *edev) |
de55d871 | 1087 | { |
806d9dd7 | 1088 | int ret, index = 0; |
71c3ffa5 | 1089 | static atomic_t edev_no = ATOMIC_INIT(-1); |
de55d871 MH |
1090 | |
1091 | if (!extcon_class) { | |
1092 | ret = create_extcon_class(); | |
1093 | if (ret < 0) | |
1094 | return ret; | |
1095 | } | |
1096 | ||
7eae43ae | 1097 | if (!edev || !edev->supported_cable) |
2a9de9c0 CC |
1098 | return -EINVAL; |
1099 | ||
1100 | for (; edev->supported_cable[index] != EXTCON_NONE; index++); | |
806d9dd7 | 1101 | |
2a9de9c0 | 1102 | edev->max_supported = index; |
806d9dd7 | 1103 | if (index > SUPPORTED_CABLE_MAX) { |
2a9de9c0 CC |
1104 | dev_err(&edev->dev, |
1105 | "exceed the maximum number of supported cables\n"); | |
806d9dd7 MH |
1106 | return -EINVAL; |
1107 | } | |
1108 | ||
dae61651 CC |
1109 | edev->dev.class = extcon_class; |
1110 | edev->dev.release = extcon_dev_release; | |
de55d871 | 1111 | |
71c3ffa5 | 1112 | edev->name = dev_name(edev->dev.parent); |
42d7d753 CC |
1113 | if (IS_ERR_OR_NULL(edev->name)) { |
1114 | dev_err(&edev->dev, | |
1115 | "extcon device name is null\n"); | |
1116 | return -EINVAL; | |
1117 | } | |
71c3ffa5 CC |
1118 | dev_set_name(&edev->dev, "extcon%lu", |
1119 | (unsigned long)atomic_inc_return(&edev_no)); | |
806d9dd7 MH |
1120 | |
1121 | if (edev->max_supported) { | |
806d9dd7 MH |
1122 | char *str; |
1123 | struct extcon_cable *cable; | |
1124 | ||
6396bb22 KC |
1125 | edev->cables = kcalloc(edev->max_supported, |
1126 | sizeof(struct extcon_cable), | |
1127 | GFP_KERNEL); | |
806d9dd7 MH |
1128 | if (!edev->cables) { |
1129 | ret = -ENOMEM; | |
1130 | goto err_sysfs_alloc; | |
1131 | } | |
1132 | for (index = 0; index < edev->max_supported; index++) { | |
1133 | cable = &edev->cables[index]; | |
1134 | ||
69f75a4f | 1135 | str = kasprintf(GFP_KERNEL, "cable.%d", index); |
806d9dd7 MH |
1136 | if (!str) { |
1137 | for (index--; index >= 0; index--) { | |
1138 | cable = &edev->cables[index]; | |
1139 | kfree(cable->attr_g.name); | |
1140 | } | |
1141 | ret = -ENOMEM; | |
1142 | ||
1143 | goto err_alloc_cables; | |
1144 | } | |
806d9dd7 MH |
1145 | |
1146 | cable->edev = edev; | |
1147 | cable->cable_index = index; | |
1148 | cable->attrs[0] = &cable->attr_name.attr; | |
1149 | cable->attrs[1] = &cable->attr_state.attr; | |
1150 | cable->attrs[2] = NULL; | |
1151 | cable->attr_g.name = str; | |
1152 | cable->attr_g.attrs = cable->attrs; | |
1153 | ||
9baf3220 | 1154 | sysfs_attr_init(&cable->attr_name.attr); |
806d9dd7 MH |
1155 | cable->attr_name.attr.name = "name"; |
1156 | cable->attr_name.attr.mode = 0444; | |
1157 | cable->attr_name.show = cable_name_show; | |
1158 | ||
9baf3220 | 1159 | sysfs_attr_init(&cable->attr_state.attr); |
806d9dd7 | 1160 | cable->attr_state.attr.name = "state"; |
ea9dd9d6 | 1161 | cable->attr_state.attr.mode = 0444; |
806d9dd7 | 1162 | cable->attr_state.show = cable_state_show; |
806d9dd7 MH |
1163 | } |
1164 | } | |
1165 | ||
bde68e60 | 1166 | if (edev->max_supported && edev->mutually_exclusive) { |
bde68e60 MH |
1167 | char *name; |
1168 | ||
1169 | /* Count the size of mutually_exclusive array */ | |
1170 | for (index = 0; edev->mutually_exclusive[index]; index++) | |
1171 | ; | |
1172 | ||
6396bb22 KC |
1173 | edev->attrs_muex = kcalloc(index + 1, |
1174 | sizeof(struct attribute *), | |
1175 | GFP_KERNEL); | |
bde68e60 MH |
1176 | if (!edev->attrs_muex) { |
1177 | ret = -ENOMEM; | |
1178 | goto err_muex; | |
1179 | } | |
1180 | ||
6396bb22 KC |
1181 | edev->d_attrs_muex = kcalloc(index, |
1182 | sizeof(struct device_attribute), | |
1183 | GFP_KERNEL); | |
bde68e60 MH |
1184 | if (!edev->d_attrs_muex) { |
1185 | ret = -ENOMEM; | |
1186 | kfree(edev->attrs_muex); | |
1187 | goto err_muex; | |
1188 | } | |
1189 | ||
1190 | for (index = 0; edev->mutually_exclusive[index]; index++) { | |
69f75a4f AS |
1191 | name = kasprintf(GFP_KERNEL, "0x%x", |
1192 | edev->mutually_exclusive[index]); | |
bde68e60 MH |
1193 | if (!name) { |
1194 | for (index--; index >= 0; index--) { | |
1195 | kfree(edev->d_attrs_muex[index].attr. | |
1196 | name); | |
1197 | } | |
1198 | kfree(edev->d_attrs_muex); | |
1199 | kfree(edev->attrs_muex); | |
1200 | ret = -ENOMEM; | |
1201 | goto err_muex; | |
1202 | } | |
9baf3220 | 1203 | sysfs_attr_init(&edev->d_attrs_muex[index].attr); |
bde68e60 MH |
1204 | edev->d_attrs_muex[index].attr.name = name; |
1205 | edev->d_attrs_muex[index].attr.mode = 0000; | |
1206 | edev->attrs_muex[index] = &edev->d_attrs_muex[index] | |
1207 | .attr; | |
1208 | } | |
1209 | edev->attr_g_muex.name = muex_name; | |
1210 | edev->attr_g_muex.attrs = edev->attrs_muex; | |
1211 | ||
1212 | } | |
1213 | ||
806d9dd7 MH |
1214 | if (edev->max_supported) { |
1215 | edev->extcon_dev_type.groups = | |
6396bb22 KC |
1216 | kcalloc(edev->max_supported + 2, |
1217 | sizeof(struct attribute_group *), | |
1218 | GFP_KERNEL); | |
806d9dd7 MH |
1219 | if (!edev->extcon_dev_type.groups) { |
1220 | ret = -ENOMEM; | |
1221 | goto err_alloc_groups; | |
1222 | } | |
1223 | ||
dae61651 | 1224 | edev->extcon_dev_type.name = dev_name(&edev->dev); |
806d9dd7 MH |
1225 | edev->extcon_dev_type.release = dummy_sysfs_dev_release; |
1226 | ||
1227 | for (index = 0; index < edev->max_supported; index++) | |
1228 | edev->extcon_dev_type.groups[index] = | |
1229 | &edev->cables[index].attr_g; | |
bde68e60 MH |
1230 | if (edev->mutually_exclusive) |
1231 | edev->extcon_dev_type.groups[index] = | |
1232 | &edev->attr_g_muex; | |
806d9dd7 | 1233 | |
dae61651 | 1234 | edev->dev.type = &edev->extcon_dev_type; |
806d9dd7 MH |
1235 | } |
1236 | ||
806d9dd7 | 1237 | spin_lock_init(&edev->lock); |
5dcc2afe | 1238 | if (edev->max_supported) { |
1239 | edev->nh = kcalloc(edev->max_supported, sizeof(*edev->nh), | |
1240 | GFP_KERNEL); | |
1241 | if (!edev->nh) { | |
1242 | ret = -ENOMEM; | |
1243 | goto err_alloc_nh; | |
1244 | } | |
046050f6 CC |
1245 | } |
1246 | ||
1247 | for (index = 0; index < edev->max_supported; index++) | |
1248 | RAW_INIT_NOTIFIER_HEAD(&edev->nh[index]); | |
74c5d09b | 1249 | |
815429b3 CC |
1250 | RAW_INIT_NOTIFIER_HEAD(&edev->nh_all); |
1251 | ||
dae61651 | 1252 | dev_set_drvdata(&edev->dev, edev); |
de55d871 | 1253 | edev->state = 0; |
74c5d09b | 1254 | |
5dcc2afe | 1255 | ret = device_register(&edev->dev); |
1256 | if (ret) { | |
1257 | put_device(&edev->dev); | |
1258 | goto err_dev; | |
1259 | } | |
1260 | ||
74c5d09b DK |
1261 | mutex_lock(&extcon_dev_list_lock); |
1262 | list_add(&edev->entry, &extcon_dev_list); | |
1263 | mutex_unlock(&extcon_dev_list_lock); | |
1264 | ||
de55d871 MH |
1265 | return 0; |
1266 | ||
1267 | err_dev: | |
5dcc2afe | 1268 | if (edev->max_supported) |
1269 | kfree(edev->nh); | |
1270 | err_alloc_nh: | |
806d9dd7 MH |
1271 | if (edev->max_supported) |
1272 | kfree(edev->extcon_dev_type.groups); | |
1273 | err_alloc_groups: | |
bde68e60 MH |
1274 | if (edev->max_supported && edev->mutually_exclusive) { |
1275 | for (index = 0; edev->mutually_exclusive[index]; index++) | |
1276 | kfree(edev->d_attrs_muex[index].attr.name); | |
1277 | kfree(edev->d_attrs_muex); | |
1278 | kfree(edev->attrs_muex); | |
1279 | } | |
1280 | err_muex: | |
806d9dd7 MH |
1281 | for (index = 0; index < edev->max_supported; index++) |
1282 | kfree(edev->cables[index].attr_g.name); | |
1283 | err_alloc_cables: | |
1284 | if (edev->max_supported) | |
1285 | kfree(edev->cables); | |
1286 | err_sysfs_alloc: | |
de55d871 MH |
1287 | return ret; |
1288 | } | |
1289 | EXPORT_SYMBOL_GPL(extcon_dev_register); | |
1290 | ||
1291 | /** | |
1292 | * extcon_dev_unregister() - Unregister the extcon device. | |
6ab6094f | 1293 | * @edev: the extcon device to be unregistered. |
de55d871 MH |
1294 | * |
1295 | * Note that this does not call kfree(edev) because edev was not allocated | |
1296 | * by this class. | |
1297 | */ | |
1298 | void extcon_dev_unregister(struct extcon_dev *edev) | |
1299 | { | |
57e7cd37 | 1300 | int index; |
1301 | ||
7eae43ae CC |
1302 | if (!edev) |
1303 | return; | |
1304 | ||
57e7cd37 | 1305 | mutex_lock(&extcon_dev_list_lock); |
1306 | list_del(&edev->entry); | |
1307 | mutex_unlock(&extcon_dev_list_lock); | |
1308 | ||
dae61651 CC |
1309 | if (IS_ERR_OR_NULL(get_device(&edev->dev))) { |
1310 | dev_err(&edev->dev, "Failed to unregister extcon_dev (%s)\n", | |
1311 | dev_name(&edev->dev)); | |
57e7cd37 | 1312 | return; |
1313 | } | |
1314 | ||
7585ca0d WX |
1315 | device_unregister(&edev->dev); |
1316 | ||
57e7cd37 | 1317 | if (edev->mutually_exclusive && edev->max_supported) { |
1318 | for (index = 0; edev->mutually_exclusive[index]; | |
1319 | index++) | |
1320 | kfree(edev->d_attrs_muex[index].attr.name); | |
1321 | kfree(edev->d_attrs_muex); | |
1322 | kfree(edev->attrs_muex); | |
1323 | } | |
1324 | ||
1325 | for (index = 0; index < edev->max_supported; index++) | |
1326 | kfree(edev->cables[index].attr_g.name); | |
1327 | ||
1328 | if (edev->max_supported) { | |
1329 | kfree(edev->extcon_dev_type.groups); | |
1330 | kfree(edev->cables); | |
5dcc2afe | 1331 | kfree(edev->nh); |
57e7cd37 | 1332 | } |
1333 | ||
dae61651 | 1334 | put_device(&edev->dev); |
de55d871 MH |
1335 | } |
1336 | EXPORT_SYMBOL_GPL(extcon_dev_unregister); | |
1337 | ||
1ad94ffe | 1338 | #ifdef CONFIG_OF |
370ed7a9 AH |
1339 | |
1340 | /* | |
1341 | * extcon_find_edev_by_node - Find the extcon device from devicetree. | |
1342 | * @node : OF node identifying edev | |
1343 | * | |
1344 | * Return the pointer of extcon device if success or ERR_PTR(err) if fail. | |
1345 | */ | |
1346 | struct extcon_dev *extcon_find_edev_by_node(struct device_node *node) | |
1347 | { | |
1348 | struct extcon_dev *edev; | |
1349 | ||
1350 | mutex_lock(&extcon_dev_list_lock); | |
1351 | list_for_each_entry(edev, &extcon_dev_list, entry) | |
1352 | if (edev->dev.parent && edev->dev.parent->of_node == node) | |
1353 | goto out; | |
1354 | edev = ERR_PTR(-EPROBE_DEFER); | |
1355 | out: | |
1356 | mutex_unlock(&extcon_dev_list_lock); | |
1357 | ||
1358 | return edev; | |
1359 | } | |
1360 | ||
1ad94ffe | 1361 | /* |
6ab6094f CC |
1362 | * extcon_get_edev_by_phandle - Get the extcon device from devicetree. |
1363 | * @dev : the instance to the given device | |
1364 | * @index : the index into list of extcon_dev | |
1ad94ffe | 1365 | * |
6ab6094f | 1366 | * Return the pointer of extcon device if success or ERR_PTR(err) if fail. |
1ad94ffe CC |
1367 | */ |
1368 | struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) | |
1369 | { | |
1370 | struct device_node *node; | |
1371 | struct extcon_dev *edev; | |
1372 | ||
7eae43ae CC |
1373 | if (!dev) |
1374 | return ERR_PTR(-EINVAL); | |
1375 | ||
1ad94ffe | 1376 | if (!dev->of_node) { |
e8752b7a | 1377 | dev_dbg(dev, "device does not have a device node entry\n"); |
1ad94ffe CC |
1378 | return ERR_PTR(-EINVAL); |
1379 | } | |
1380 | ||
1381 | node = of_parse_phandle(dev->of_node, "extcon", index); | |
1382 | if (!node) { | |
5c27036d RH |
1383 | dev_dbg(dev, "failed to get phandle in %pOF node\n", |
1384 | dev->of_node); | |
1ad94ffe CC |
1385 | return ERR_PTR(-ENODEV); |
1386 | } | |
1387 | ||
370ed7a9 | 1388 | edev = extcon_find_edev_by_node(node); |
5d5c4c13 | 1389 | of_node_put(node); |
1ad94ffe | 1390 | |
370ed7a9 | 1391 | return edev; |
1ad94ffe | 1392 | } |
370ed7a9 | 1393 | |
1ad94ffe | 1394 | #else |
370ed7a9 AH |
1395 | |
1396 | struct extcon_dev *extcon_find_edev_by_node(struct device_node *node) | |
1397 | { | |
1398 | return ERR_PTR(-ENOSYS); | |
1399 | } | |
1400 | ||
1ad94ffe CC |
1401 | struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) |
1402 | { | |
1403 | return ERR_PTR(-ENOSYS); | |
1404 | } | |
370ed7a9 | 1405 | |
1ad94ffe | 1406 | #endif /* CONFIG_OF */ |
370ed7a9 AH |
1407 | |
1408 | EXPORT_SYMBOL_GPL(extcon_find_edev_by_node); | |
1ad94ffe CC |
1409 | EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle); |
1410 | ||
707d7550 CC |
1411 | /** |
1412 | * extcon_get_edev_name() - Get the name of the extcon device. | |
1413 | * @edev: the extcon device | |
1414 | */ | |
1415 | const char *extcon_get_edev_name(struct extcon_dev *edev) | |
1416 | { | |
1417 | return !edev ? NULL : edev->name; | |
1418 | } | |
995bb109 | 1419 | EXPORT_SYMBOL_GPL(extcon_get_edev_name); |
707d7550 | 1420 | |
de55d871 MH |
1421 | static int __init extcon_class_init(void) |
1422 | { | |
1423 | return create_extcon_class(); | |
1424 | } | |
1425 | module_init(extcon_class_init); | |
1426 | ||
1427 | static void __exit extcon_class_exit(void) | |
1428 | { | |
1429 | class_destroy(extcon_class); | |
1430 | } | |
1431 | module_exit(extcon_class_exit); | |
1432 | ||
2a9de9c0 | 1433 | MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); |
de55d871 | 1434 | MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); |
6ab6094f CC |
1435 | MODULE_DESCRIPTION("External Connector (extcon) framework"); |
1436 | MODULE_LICENSE("GPL v2"); |