Blame view

drivers/extcon/extcon.c 35.1 KB
de55d8716   MyungJoo Ham   Extcon (external ...
1
  /*
b9ec23c08   Chanwoo Choi   extcon: Fix the c...
2
   *  drivers/extcon/extcon.c - External Connector (extcon) framework.
de55d8716   MyungJoo Ham   Extcon (external ...
3
4
5
   *
   *  External connector (extcon) class driver
   *
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
6
7
8
   * Copyright (C) 2015 Samsung Electronics
   * Author: Chanwoo Choi <cw00.choi@samsung.com>
   *
de55d8716   MyungJoo Ham   Extcon (external ...
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
   * Copyright (C) 2012 Samsung Electronics
   * Author: Donggeun Kim <dg77.kim@samsung.com>
   * Author: MyungJoo Ham <myungjoo.ham@samsung.com>
   *
   * based on android/drivers/switch/switch_class.c
   * Copyright (C) 2008 Google, Inc.
   * Author: Mike Lockwood <lockwood@android.com>
   *
   * This software is licensed under the terms of the GNU General Public
   * License version 2, as published by the Free Software Foundation, and
   * may be copied, distributed, and modified under those terms.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
b9ec23c08   Chanwoo Choi   extcon: Fix the c...
25
   */
de55d8716   MyungJoo Ham   Extcon (external ...
26
27
28
29
30
31
32
33
  
  #include <linux/module.h>
  #include <linux/types.h>
  #include <linux/init.h>
  #include <linux/device.h>
  #include <linux/fs.h>
  #include <linux/err.h>
  #include <linux/extcon.h>
f841afb17   Tomasz Figa   extcon: Implement...
34
  #include <linux/of.h>
de55d8716   MyungJoo Ham   Extcon (external ...
35
  #include <linux/slab.h>
9baf3220a   Mark Brown   extcon: Ensure dy...
36
  #include <linux/sysfs.h>
de55d8716   MyungJoo Ham   Extcon (external ...
37

2a9de9c0f   Chanwoo Choi   extcon: Use the u...
38
39
  #define SUPPORTED_CABLE_MAX	32
  #define CABLE_NAME_MAX		30
55e4e2f12   Chanwoo Choi   extcon: Add the e...
40
41
42
43
44
45
46
47
48
49
50
  struct __extcon_info {
  	unsigned int type;
  	unsigned int id;
  	const char *name;
  
  } extcon_info[] = {
  	[EXTCON_NONE] = {
  		.type = EXTCON_TYPE_MISC,
  		.id = EXTCON_NONE,
  		.name = "NONE",
  	},
73b6ecdb9   Chanwoo Choi   extcon: Redefine ...
51

8e9bc36df   Chanwoo Choi   extcon: Use capit...
52
  	/* USB external connector */
55e4e2f12   Chanwoo Choi   extcon: Add the e...
53
54
55
56
57
58
59
60
61
62
  	[EXTCON_USB] = {
  		.type = EXTCON_TYPE_USB,
  		.id = EXTCON_USB,
  		.name = "USB",
  	},
  	[EXTCON_USB_HOST] = {
  		.type = EXTCON_TYPE_USB,
  		.id = EXTCON_USB_HOST,
  		.name = "USB_HOST",
  	},
8e9bc36df   Chanwoo Choi   extcon: Use capit...
63

11eecf910   Chanwoo Choi   extcon: Modify th...
64
  	/* Charging external connector */
55e4e2f12   Chanwoo Choi   extcon: Add the e...
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
  	[EXTCON_CHG_USB_SDP] = {
  		.type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,
  		.id = EXTCON_CHG_USB_SDP,
  		.name = "SDP",
  	},
  	[EXTCON_CHG_USB_DCP] = {
  		.type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,
  		.id = EXTCON_CHG_USB_DCP,
  		.name = "DCP",
  	},
  	[EXTCON_CHG_USB_CDP] = {
  		.type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,
  		.id = EXTCON_CHG_USB_CDP,
  		.name = "CDP",
  	},
  	[EXTCON_CHG_USB_ACA] = {
  		.type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,
  		.id = EXTCON_CHG_USB_ACA,
  		.name = "ACA",
  	},
  	[EXTCON_CHG_USB_FAST] = {
  		.type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,
  		.id = EXTCON_CHG_USB_FAST,
  		.name = "FAST-CHARGER",
  	},
  	[EXTCON_CHG_USB_SLOW] = {
  		.type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,
  		.id = EXTCON_CHG_USB_SLOW,
  		.name = "SLOW-CHARGER",
  	},
7fe95fb88   Chanwoo Choi   extcon: Add new E...
95
96
97
98
99
  	[EXTCON_CHG_WPT] = {
  		.type = EXTCON_TYPE_CHG,
  		.id = EXTCON_CHG_WPT,
  		.name = "WPT",
  	},
8e9bc36df   Chanwoo Choi   extcon: Use capit...
100

11eecf910   Chanwoo Choi   extcon: Modify th...
101
  	/* Jack external connector */
55e4e2f12   Chanwoo Choi   extcon: Add the e...
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
  	[EXTCON_JACK_MICROPHONE] = {
  		.type = EXTCON_TYPE_JACK,
  		.id = EXTCON_JACK_MICROPHONE,
  		.name = "MICROPHONE",
  	},
  	[EXTCON_JACK_HEADPHONE] = {
  		.type = EXTCON_TYPE_JACK,
  		.id = EXTCON_JACK_HEADPHONE,
  		.name = "HEADPHONE",
  	},
  	[EXTCON_JACK_LINE_IN] = {
  		.type = EXTCON_TYPE_JACK,
  		.id = EXTCON_JACK_LINE_IN,
  		.name = "LINE-IN",
  	},
  	[EXTCON_JACK_LINE_OUT] = {
  		.type = EXTCON_TYPE_JACK,
  		.id = EXTCON_JACK_LINE_OUT,
  		.name = "LINE-OUT",
  	},
  	[EXTCON_JACK_VIDEO_IN] = {
  		.type = EXTCON_TYPE_JACK,
  		.id = EXTCON_JACK_VIDEO_IN,
  		.name = "VIDEO-IN",
  	},
  	[EXTCON_JACK_VIDEO_OUT] = {
  		.type = EXTCON_TYPE_JACK,
  		.id = EXTCON_JACK_VIDEO_OUT,
  		.name = "VIDEO-OUT",
  	},
  	[EXTCON_JACK_SPDIF_IN] = {
  		.type = EXTCON_TYPE_JACK,
  		.id = EXTCON_JACK_SPDIF_IN,
  		.name = "SPDIF-IN",
  	},
  	[EXTCON_JACK_SPDIF_OUT] = {
  		.type = EXTCON_TYPE_JACK,
  		.id = EXTCON_JACK_SPDIF_OUT,
  		.name = "SPDIF-OUT",
  	},
8e9bc36df   Chanwoo Choi   extcon: Use capit...
142

11eecf910   Chanwoo Choi   extcon: Modify th...
143
  	/* Display external connector */
55e4e2f12   Chanwoo Choi   extcon: Add the e...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
  	[EXTCON_DISP_HDMI] = {
  		.type = EXTCON_TYPE_DISP,
  		.id = EXTCON_DISP_HDMI,
  		.name = "HDMI",
  	},
  	[EXTCON_DISP_MHL] = {
  		.type = EXTCON_TYPE_DISP,
  		.id = EXTCON_DISP_MHL,
  		.name = "MHL",
  	},
  	[EXTCON_DISP_DVI] = {
  		.type = EXTCON_TYPE_DISP,
  		.id = EXTCON_DISP_DVI,
  		.name = "DVI",
  	},
  	[EXTCON_DISP_VGA] = {
  		.type = EXTCON_TYPE_DISP,
  		.id = EXTCON_DISP_VGA,
  		.name = "VGA",
  	},
2164188d5   Chris Zhong   extcon: Add EXTCO...
164
165
166
167
168
  	[EXTCON_DISP_DP] = {
  		.type = EXTCON_TYPE_DISP | EXTCON_TYPE_USB,
  		.id = EXTCON_DISP_DP,
  		.name = "DP",
  	},
9c0595d68   Chanwoo Choi   extcon: Add new E...
169
170
171
172
173
  	[EXTCON_DISP_HMD] = {
  		.type = EXTCON_TYPE_DISP | EXTCON_TYPE_USB,
  		.id = EXTCON_DISP_HMD,
  		.name = "HMD",
  	},
8e9bc36df   Chanwoo Choi   extcon: Use capit...
174

11eecf910   Chanwoo Choi   extcon: Modify th...
175
  	/* Miscellaneous external connector */
55e4e2f12   Chanwoo Choi   extcon: Add the e...
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
  	[EXTCON_DOCK] = {
  		.type = EXTCON_TYPE_MISC,
  		.id = EXTCON_DOCK,
  		.name = "DOCK",
  	},
  	[EXTCON_JIG] = {
  		.type = EXTCON_TYPE_MISC,
  		.id = EXTCON_JIG,
  		.name = "JIG",
  	},
  	[EXTCON_MECHANICAL] = {
  		.type = EXTCON_TYPE_MISC,
  		.id = EXTCON_MECHANICAL,
  		.name = "MECHANICAL",
  	},
  
  	{ /* sentinel */ }
806d9dd71   MyungJoo Ham   Extcon: support m...
193
  };
20f7b53df   Chanwoo Choi   extcon: Move stru...
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
  /**
   * struct extcon_cable - An internal data for each cable of extcon device.
   * @edev:		The extcon device
   * @cable_index:	Index of this cable in the edev
   * @attr_g:		Attribute group for the cable
   * @attr_name:		"name" sysfs entry
   * @attr_state:		"state" sysfs entry
   * @attrs:		Array pointing to attr_name and attr_state for attr_g
   */
  struct extcon_cable {
  	struct extcon_dev *edev;
  	int cable_index;
  
  	struct attribute_group attr_g;
  	struct device_attribute attr_name;
  	struct device_attribute attr_state;
  
  	struct attribute *attrs[3]; /* to be fed to attr_g.attrs */
792e7e9e5   Chanwoo Choi   extcon: Add the s...
212
213
214
215
216
  
  	union extcon_property_value usb_propval[EXTCON_PROP_USB_CNT];
  	union extcon_property_value chg_propval[EXTCON_PROP_CHG_CNT];
  	union extcon_property_value jack_propval[EXTCON_PROP_JACK_CNT];
  	union extcon_property_value disp_propval[EXTCON_PROP_DISP_CNT];
7f2a0a169   Chanwoo Choi   extcon: Add the s...
217
218
219
220
221
  
  	unsigned long usb_bits[BITS_TO_LONGS(EXTCON_PROP_USB_CNT)];
  	unsigned long chg_bits[BITS_TO_LONGS(EXTCON_PROP_CHG_CNT)];
  	unsigned long jack_bits[BITS_TO_LONGS(EXTCON_PROP_JACK_CNT)];
  	unsigned long disp_bits[BITS_TO_LONGS(EXTCON_PROP_DISP_CNT)];
20f7b53df   Chanwoo Choi   extcon: Move stru...
222
  };
be3a07f71   Mark Brown   Extcon: Staticise...
223
  static struct class *extcon_class;
449a2bf5e   MyungJoo Ham   Remove "switch" c...
224
  #if defined(CONFIG_ANDROID)
de55d8716   MyungJoo Ham   Extcon (external ...
225
  static struct class_compat *switch_class;
449a2bf5e   MyungJoo Ham   Remove "switch" c...
226
  #endif /* CONFIG_ANDROID */
de55d8716   MyungJoo Ham   Extcon (external ...
227

74c5d09bd   Donggeun Kim   Extcon: support n...
228
229
  static LIST_HEAD(extcon_dev_list);
  static DEFINE_MUTEX(extcon_dev_list_lock);
bde68e60b   MyungJoo Ham   Extcon: support m...
230
231
  /**
   * check_mutually_exclusive - Check if new_state violates mutually_exclusive
a75e1c73a   Chanwoo Choi   extcon: Fix inden...
232
   *			      condition.
bde68e60b   MyungJoo Ham   Extcon: support m...
233
234
235
236
237
238
239
240
241
242
243
244
245
246
   * @edev:	the extcon device
   * @new_state:	new cable attach status for @edev
   *
   * Returns 0 if nothing violates. Returns the index + 1 for the first
   * violated condition.
   */
  static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state)
  {
  	int i = 0;
  
  	if (!edev->mutually_exclusive)
  		return 0;
  
  	for (i = 0; edev->mutually_exclusive[i]; i++) {
28c0ada62   anish kumar   extcon: optimisin...
247
  		int weight;
bde68e60b   MyungJoo Ham   Extcon: support m...
248
  		u32 correspondants = new_state & edev->mutually_exclusive[i];
bde68e60b   MyungJoo Ham   Extcon: support m...
249

28c0ada62   anish kumar   extcon: optimisin...
250
251
252
253
  		/* calculate the total number of bits set */
  		weight = hweight32(correspondants);
  		if (weight > 1)
  			return i + 1;
bde68e60b   MyungJoo Ham   Extcon: support m...
254
255
256
257
  	}
  
  	return 0;
  }
73b6ecdb9   Chanwoo Choi   extcon: Redefine ...
258
  static int find_cable_index_by_id(struct extcon_dev *edev, const unsigned int id)
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
259
260
261
262
263
264
265
266
267
268
269
  {
  	int i;
  
  	/* Find the the index of extcon cable in edev->supported_cable */
  	for (i = 0; i < edev->max_supported; i++) {
  		if (edev->supported_cable[i] == id)
  			return i;
  	}
  
  	return -EINVAL;
  }
792e7e9e5   Chanwoo Choi   extcon: Add the s...
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
  static int get_extcon_type(unsigned int prop)
  {
  	switch (prop) {
  	case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX:
  		return EXTCON_TYPE_USB;
  	case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX:
  		return EXTCON_TYPE_CHG;
  	case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX:
  		return EXTCON_TYPE_JACK;
  	case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX:
  		return EXTCON_TYPE_DISP;
  	default:
  		return -EINVAL;
  	}
  }
  
  static bool is_extcon_attached(struct extcon_dev *edev, unsigned int index)
  {
  	return !!(edev->state & BIT(index));
  }
ab11af049   Chanwoo Choi   extcon: Add the s...
290
291
  static bool is_extcon_changed(struct extcon_dev *edev, int index,
  				bool new_state)
046050f6e   Chanwoo Choi   extcon: Update th...
292
  {
ab11af049   Chanwoo Choi   extcon: Add the s...
293
294
  	int state = !!(edev->state & BIT(index));
  	return (state != new_state);
046050f6e   Chanwoo Choi   extcon: Update th...
295
  }
792e7e9e5   Chanwoo Choi   extcon: Add the s...
296
297
298
299
300
301
302
303
304
305
306
307
  static bool is_extcon_property_supported(unsigned int id, unsigned int prop)
  {
  	int type;
  
  	/* Check whether the property is supported or not. */
  	type = get_extcon_type(prop);
  	if (type < 0)
  		return false;
  
  	/* Check whether a specific extcon id supports the property or not. */
  	return !!(extcon_info[id].type & type);
  }
7f2a0a169   Chanwoo Choi   extcon: Add the s...
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
  static int is_extcon_property_capability(struct extcon_dev *edev,
  				unsigned int id, int index,unsigned int prop)
  {
  	struct extcon_cable *cable;
  	int type, ret;
  
  	/* Check whether the property is supported or not. */
  	type = get_extcon_type(prop);
  	if (type < 0)
  		return type;
  
  	cable = &edev->cables[index];
  
  	switch (type) {
  	case EXTCON_TYPE_USB:
  		ret = test_bit(prop - EXTCON_PROP_USB_MIN, cable->usb_bits);
  		break;
  	case EXTCON_TYPE_CHG:
  		ret = test_bit(prop - EXTCON_PROP_CHG_MIN, cable->chg_bits);
  		break;
  	case EXTCON_TYPE_JACK:
  		ret = test_bit(prop - EXTCON_PROP_JACK_MIN, cable->jack_bits);
  		break;
  	case EXTCON_TYPE_DISP:
  		ret = test_bit(prop - EXTCON_PROP_DISP_MIN, cable->disp_bits);
  		break;
  	default:
  		ret = -EINVAL;
  	}
  
  	return ret;
  }
792e7e9e5   Chanwoo Choi   extcon: Add the s...
340
341
342
343
344
345
346
347
348
349
350
351
352
353
  static void init_property(struct extcon_dev *edev, unsigned int id, int index)
  {
  	unsigned int type = extcon_info[id].type;
  	struct extcon_cable *cable = &edev->cables[index];
  
  	if (EXTCON_TYPE_USB & type)
  		memset(cable->usb_propval, 0, sizeof(cable->usb_propval));
  	if (EXTCON_TYPE_CHG & type)
  		memset(cable->chg_propval, 0, sizeof(cable->chg_propval));
  	if (EXTCON_TYPE_JACK & type)
  		memset(cable->jack_propval, 0, sizeof(cable->jack_propval));
  	if (EXTCON_TYPE_DISP & type)
  		memset(cable->disp_propval, 0, sizeof(cable->disp_propval));
  }
de55d8716   MyungJoo Ham   Extcon (external ...
354
355
356
  static ssize_t state_show(struct device *dev, struct device_attribute *attr,
  			  char *buf)
  {
806d9dd71   MyungJoo Ham   Extcon: support m...
357
  	int i, count = 0;
cb8bb3a77   Jingoo Han   extcon: Remove ca...
358
  	struct extcon_dev *edev = dev_get_drvdata(dev);
de55d8716   MyungJoo Ham   Extcon (external ...
359

806d9dd71   MyungJoo Ham   Extcon: support m...
360
361
362
  	if (edev->max_supported == 0)
  		return sprintf(buf, "%u
  ", edev->state);
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
363
  	for (i = 0; i < edev->max_supported; i++) {
806d9dd71   MyungJoo Ham   Extcon: support m...
364
365
  		count += sprintf(buf + count, "%s=%d
  ",
55e4e2f12   Chanwoo Choi   extcon: Add the e...
366
  				extcon_info[edev->supported_cable[i]].name,
806d9dd71   MyungJoo Ham   Extcon: support m...
367
368
369
370
371
  				 !!(edev->state & (1 << i)));
  	}
  
  	return count;
  }
5d5321e90   Chanwoo Choi   extcon: Remove th...
372
  static DEVICE_ATTR_RO(state);
de55d8716   MyungJoo Ham   Extcon (external ...
373
374
375
376
  
  static ssize_t name_show(struct device *dev, struct device_attribute *attr,
  		char *buf)
  {
cb8bb3a77   Jingoo Han   extcon: Remove ca...
377
  	struct extcon_dev *edev = dev_get_drvdata(dev);
de55d8716   MyungJoo Ham   Extcon (external ...
378

71c3ffa5d   Chanwoo Choi   extcon: Modify th...
379
380
  	return sprintf(buf, "%s
  ", edev->name);
de55d8716   MyungJoo Ham   Extcon (external ...
381
  }
af01da0e0   Greg Kroah-Hartman   extcon: convert e...
382
  static DEVICE_ATTR_RO(name);
de55d8716   MyungJoo Ham   Extcon (external ...
383

806d9dd71   MyungJoo Ham   Extcon: support m...
384
385
386
387
388
  static ssize_t cable_name_show(struct device *dev,
  			       struct device_attribute *attr, char *buf)
  {
  	struct extcon_cable *cable = container_of(attr, struct extcon_cable,
  						  attr_name);
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
389
  	int i = cable->cable_index;
806d9dd71   MyungJoo Ham   Extcon: support m...
390
391
392
  
  	return sprintf(buf, "%s
  ",
55e4e2f12   Chanwoo Choi   extcon: Add the e...
393
  			extcon_info[cable->edev->supported_cable[i]].name);
806d9dd71   MyungJoo Ham   Extcon: support m...
394
395
396
397
398
399
400
  }
  
  static ssize_t cable_state_show(struct device *dev,
  				struct device_attribute *attr, char *buf)
  {
  	struct extcon_cable *cable = container_of(attr, struct extcon_cable,
  						  attr_state);
be052cc87   Roger Quadros   extcon: Fix hang ...
401
  	int i = cable->cable_index;
806d9dd71   MyungJoo Ham   Extcon: support m...
402
403
  	return sprintf(buf, "%d
  ",
575c2b867   Chanwoo Choi   extcon: Rename th...
404
  		extcon_get_state(cable->edev, cable->edev->supported_cable[i]));
806d9dd71   MyungJoo Ham   Extcon: support m...
405
  }
de55d8716   MyungJoo Ham   Extcon (external ...
406
  /**
ab11af049   Chanwoo Choi   extcon: Add the s...
407
408
   * extcon_sync()	- Synchronize the states for both the attached/detached
   * @edev:		the extcon device that has the cable.
74c5d09bd   Donggeun Kim   Extcon: support n...
409
   *
ab11af049   Chanwoo Choi   extcon: Add the s...
410
411
   * This function send a notification to synchronize the all states of a
   * specific external connector
de55d8716   MyungJoo Ham   Extcon (external ...
412
   */
ab11af049   Chanwoo Choi   extcon: Add the s...
413
  int extcon_sync(struct extcon_dev *edev, unsigned int id)
de55d8716   MyungJoo Ham   Extcon (external ...
414
415
416
417
418
419
420
  {
  	char name_buf[120];
  	char state_buf[120];
  	char *prop_buf;
  	char *envp[3];
  	int env_offset = 0;
  	int length;
046050f6e   Chanwoo Choi   extcon: Update th...
421
  	int index;
ab11af049   Chanwoo Choi   extcon: Add the s...
422
  	int state;
806d9dd71   MyungJoo Ham   Extcon: support m...
423
  	unsigned long flags;
de55d8716   MyungJoo Ham   Extcon (external ...
424

7eae43aea   Chanwoo Choi   extcon: Add excep...
425
426
  	if (!edev)
  		return -EINVAL;
ab11af049   Chanwoo Choi   extcon: Add the s...
427
428
429
  	index = find_cable_index_by_id(edev, id);
  	if (index < 0)
  		return index;
806d9dd71   MyungJoo Ham   Extcon: support m...
430
  	spin_lock_irqsave(&edev->lock, flags);
de55d8716   MyungJoo Ham   Extcon (external ...
431

ab11af049   Chanwoo Choi   extcon: Add the s...
432
433
434
435
436
437
438
  	state = !!(edev->state & BIT(index));
  	raw_notifier_call_chain(&edev->nh[index], state, edev);
  
  	/* This could be in interrupt handler */
  	prop_buf = (char *)get_zeroed_page(GFP_ATOMIC);
  	if (!prop_buf) {
  		/* Unlock early before uevent */
806d9dd71   MyungJoo Ham   Extcon: support m...
439
  		spin_unlock_irqrestore(&edev->lock, flags);
ab11af049   Chanwoo Choi   extcon: Add the s...
440
441
442
443
  
  		dev_err(&edev->dev, "out of memory in extcon_set_state
  ");
  		kobject_uevent(&edev->dev.kobj, KOBJ_CHANGE);
86820a103   Pan Bian   extcon: return er...
444
  		return -ENOMEM;
ab11af049   Chanwoo Choi   extcon: Add the s...
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
  	}
  
  	length = name_show(&edev->dev, NULL, prop_buf);
  	if (length > 0) {
  		if (prop_buf[length - 1] == '
  ')
  			prop_buf[length - 1] = 0;
  		snprintf(name_buf, sizeof(name_buf), "NAME=%s", prop_buf);
  		envp[env_offset++] = name_buf;
  	}
  
  	length = state_show(&edev->dev, NULL, prop_buf);
  	if (length > 0) {
  		if (prop_buf[length - 1] == '
  ')
  			prop_buf[length - 1] = 0;
  		snprintf(state_buf, sizeof(state_buf), "STATE=%s", prop_buf);
  		envp[env_offset++] = state_buf;
de55d8716   MyungJoo Ham   Extcon (external ...
463
  	}
ab11af049   Chanwoo Choi   extcon: Add the s...
464
465
466
467
468
469
  	envp[env_offset] = NULL;
  
  	/* Unlock early before uevent */
  	spin_unlock_irqrestore(&edev->lock, flags);
  	kobject_uevent_env(&edev->dev.kobj, KOBJ_CHANGE, envp);
  	free_page((unsigned long)prop_buf);
bde68e60b   MyungJoo Ham   Extcon: support m...
470
471
  
  	return 0;
de55d8716   MyungJoo Ham   Extcon (external ...
472
  }
ab11af049   Chanwoo Choi   extcon: Add the s...
473
  EXPORT_SYMBOL_GPL(extcon_sync);
de55d8716   MyungJoo Ham   Extcon (external ...
474

74c5d09bd   Donggeun Kim   Extcon: support n...
475
  /**
575c2b867   Chanwoo Choi   extcon: Rename th...
476
   * extcon_get_state() - Get the state of a external connector.
806d9dd71   MyungJoo Ham   Extcon: support m...
477
   * @edev:	the extcon device that has the cable.
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
478
   * @id:		the unique id of each external connector in extcon enumeration.
806d9dd71   MyungJoo Ham   Extcon: support m...
479
   */
575c2b867   Chanwoo Choi   extcon: Rename th...
480
  int extcon_get_state(struct extcon_dev *edev, const unsigned int id)
806d9dd71   MyungJoo Ham   Extcon: support m...
481
  {
575c2b867   Chanwoo Choi   extcon: Rename th...
482
483
  	int index, state;
  	unsigned long flags;
806d9dd71   MyungJoo Ham   Extcon: support m...
484

7eae43aea   Chanwoo Choi   extcon: Add excep...
485
486
  	if (!edev)
  		return -EINVAL;
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
487
488
489
  	index = find_cable_index_by_id(edev, id);
  	if (index < 0)
  		return index;
806d9dd71   MyungJoo Ham   Extcon: support m...
490

575c2b867   Chanwoo Choi   extcon: Rename th...
491
492
493
  	spin_lock_irqsave(&edev->lock, flags);
  	state = is_extcon_attached(edev, index);
  	spin_unlock_irqrestore(&edev->lock, flags);
806d9dd71   MyungJoo Ham   Extcon: support m...
494

575c2b867   Chanwoo Choi   extcon: Rename th...
495
  	return state;
806d9dd71   MyungJoo Ham   Extcon: support m...
496
  }
575c2b867   Chanwoo Choi   extcon: Rename th...
497
  EXPORT_SYMBOL_GPL(extcon_get_state);
806d9dd71   MyungJoo Ham   Extcon: support m...
498
499
  
  /**
575c2b867   Chanwoo Choi   extcon: Rename th...
500
   * extcon_set_state() - Set the state of a external connector.
ab11af049   Chanwoo Choi   extcon: Add the s...
501
   *			without a notification.
a75e1c73a   Chanwoo Choi   extcon: Fix inden...
502
   * @edev:		the extcon device that has the cable.
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
503
504
505
   * @id:			the unique id of each external connector
   *			in extcon enumeration.
   * @state:		the new cable status. The default semantics is
806d9dd71   MyungJoo Ham   Extcon: support m...
506
   *			true: attached / false: detached.
ab11af049   Chanwoo Choi   extcon: Add the s...
507
508
509
510
   *
   * This function only set the state of a external connector without
   * a notification. To synchronize the data of a external connector,
   * use extcon_set_state_sync() and extcon_sync().
806d9dd71   MyungJoo Ham   Extcon: support m...
511
   */
575c2b867   Chanwoo Choi   extcon: Rename th...
512
  int extcon_set_state(struct extcon_dev *edev, unsigned int id,
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
513
  				bool cable_state)
806d9dd71   MyungJoo Ham   Extcon: support m...
514
  {
ab11af049   Chanwoo Choi   extcon: Add the s...
515
516
  	unsigned long flags;
  	int index, ret = 0;
806d9dd71   MyungJoo Ham   Extcon: support m...
517

7eae43aea   Chanwoo Choi   extcon: Add excep...
518
519
  	if (!edev)
  		return -EINVAL;
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
520
521
522
  	index = find_cable_index_by_id(edev, id);
  	if (index < 0)
  		return index;
ab11af049   Chanwoo Choi   extcon: Add the s...
523
524
525
526
527
528
529
530
531
532
533
  	spin_lock_irqsave(&edev->lock, flags);
  
  	/* Check whether the external connector's state is changed. */
  	if (!is_extcon_changed(edev, index, cable_state))
  		goto out;
  
  	if (check_mutually_exclusive(edev,
  		(edev->state & ~BIT(index)) | (cable_state & BIT(index)))) {
  		ret = -EPERM;
  		goto out;
  	}
792e7e9e5   Chanwoo Choi   extcon: Add the s...
534
535
536
537
538
539
  	/*
  	 * Initialize the value of extcon property before setting
  	 * the detached state for an external connector.
  	 */
  	if (!cable_state)
  		init_property(edev, id, index);
ab11af049   Chanwoo Choi   extcon: Add the s...
540
541
542
543
544
545
546
547
548
  	/* Update the state for a external connector. */
  	if (cable_state)
  		edev->state |= BIT(index);
  	else
  		edev->state &= ~(BIT(index));
  out:
  	spin_unlock_irqrestore(&edev->lock, flags);
  
  	return ret;
806d9dd71   MyungJoo Ham   Extcon: support m...
549
  }
575c2b867   Chanwoo Choi   extcon: Rename th...
550
  EXPORT_SYMBOL_GPL(extcon_set_state);
806d9dd71   MyungJoo Ham   Extcon: support m...
551
552
  
  /**
ab11af049   Chanwoo Choi   extcon: Add the s...
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
   * extcon_set_state_sync() - Set the state of a external connector
   *			with a notification.
   * @edev:		the extcon device that has the cable.
   * @id:			the unique id of each external connector
   *			in extcon enumeration.
   * @state:		the new cable status. The default semantics is
   *			true: attached / false: detached.
   *
   * This function set the state of external connector and synchronize the data
   * by usning a notification.
   */
  int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id,
  				bool cable_state)
  {
  	int ret, index;
  	unsigned long flags;
  
  	index = find_cable_index_by_id(edev, id);
  	if (index < 0)
  		return index;
  
  	/* Check whether the external connector's state is changed. */
  	spin_lock_irqsave(&edev->lock, flags);
  	ret = is_extcon_changed(edev, index, cable_state);
  	spin_unlock_irqrestore(&edev->lock, flags);
  	if (!ret)
  		return 0;
  
  	ret = extcon_set_state(edev, id, cable_state);
  	if (ret < 0)
  		return ret;
  
  	return extcon_sync(edev, id);
  }
  EXPORT_SYMBOL_GPL(extcon_set_state_sync);
  
  /**
792e7e9e5   Chanwoo Choi   extcon: Add the s...
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
   * extcon_get_property() - Get the property value of a specific cable.
   * @edev:		the extcon device that has the cable.
   * @id:			the unique id of each external connector
   *			in extcon enumeration.
   * @prop:		the property id among enum extcon_property.
   * @prop_val:		the pointer which store the value of property.
   *
   * When getting the property value of external connector, the external connector
   * should be attached. If detached state, function just return 0 without
   * property value. Also, the each property should be included in the list of
   * supported properties according to the type of external connectors.
   *
   * Returns 0 if success or error number if fail
   */
  int extcon_get_property(struct extcon_dev *edev, unsigned int id,
  				unsigned int prop,
  				union extcon_property_value *prop_val)
  {
  	struct extcon_cable *cable;
  	unsigned long flags;
  	int index, ret = 0;
  
  	*prop_val = (union extcon_property_value)(0);
  
  	if (!edev)
  		return -EINVAL;
  
  	/* Check whether the property is supported or not */
  	if (!is_extcon_property_supported(id, prop))
  		return -EINVAL;
  
  	/* Find the cable index of external connector by using id */
  	index = find_cable_index_by_id(edev, id);
  	if (index < 0)
  		return index;
  
  	spin_lock_irqsave(&edev->lock, flags);
7f2a0a169   Chanwoo Choi   extcon: Add the s...
627
628
629
630
631
  	/* Check whether the property is available or not. */
  	if (!is_extcon_property_capability(edev, id, index, prop)) {
  		spin_unlock_irqrestore(&edev->lock, flags);
  		return -EPERM;
  	}
792e7e9e5   Chanwoo Choi   extcon: Add the s...
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
  	/*
  	 * Check whether the external connector is attached.
  	 * If external connector is detached, the user can not
  	 * get the property value.
  	 */
  	if (!is_extcon_attached(edev, index)) {
  		spin_unlock_irqrestore(&edev->lock, flags);
  		return 0;
  	}
  
  	cable = &edev->cables[index];
  
  	/* Get the property value according to extcon type */
  	switch (prop) {
  	case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX:
  		*prop_val = cable->usb_propval[prop - EXTCON_PROP_USB_MIN];
  		break;
  	case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX:
  		*prop_val = cable->chg_propval[prop - EXTCON_PROP_CHG_MIN];
  		break;
  	case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX:
  		*prop_val = cable->jack_propval[prop - EXTCON_PROP_JACK_MIN];
  		break;
  	case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX:
  		*prop_val = cable->disp_propval[prop - EXTCON_PROP_DISP_MIN];
  		break;
  	default:
  		ret = -EINVAL;
  		break;
  	}
  
  	spin_unlock_irqrestore(&edev->lock, flags);
  
  	return ret;
  }
  EXPORT_SYMBOL_GPL(extcon_get_property);
  
  /**
   * extcon_set_property() - Set the property value of a specific cable.
   * @edev:		the extcon device that has the cable.
   * @id:			the unique id of each external connector
   *			in extcon enumeration.
   * @prop:		the property id among enum extcon_property.
   * @prop_val:		the pointer including the new value of property.
   *
   * The each property should be included in the list of supported properties
   * according to the type of external connectors.
   *
   * Returns 0 if success or error number if fail
   */
  int extcon_set_property(struct extcon_dev *edev, unsigned int id,
  				unsigned int prop,
  				union extcon_property_value prop_val)
  {
  	struct extcon_cable *cable;
  	unsigned long flags;
  	int index, ret = 0;
  
  	if (!edev)
  		return -EINVAL;
  
  	/* Check whether the property is supported or not */
  	if (!is_extcon_property_supported(id, prop))
  		return -EINVAL;
  
  	/* Find the cable index of external connector by using id */
  	index = find_cable_index_by_id(edev, id);
  	if (index < 0)
  		return index;
  
  	spin_lock_irqsave(&edev->lock, flags);
7f2a0a169   Chanwoo Choi   extcon: Add the s...
703
704
705
706
707
  	/* Check whether the property is available or not. */
  	if (!is_extcon_property_capability(edev, id, index, prop)) {
  		spin_unlock_irqrestore(&edev->lock, flags);
  		return -EPERM;
  	}
792e7e9e5   Chanwoo Choi   extcon: Add the s...
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
  	cable = &edev->cables[index];
  
  	/* Set the property value according to extcon type */
  	switch (prop) {
  	case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX:
  		cable->usb_propval[prop - EXTCON_PROP_USB_MIN] = prop_val;
  		break;
  	case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX:
  		cable->chg_propval[prop - EXTCON_PROP_CHG_MIN] = prop_val;
  		break;
  	case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX:
  		cable->jack_propval[prop - EXTCON_PROP_JACK_MIN] = prop_val;
  		break;
  	case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX:
  		cable->disp_propval[prop - EXTCON_PROP_DISP_MIN] = prop_val;
  		break;
  	default:
  		ret = -EINVAL;
  		break;
  	}
  
  	spin_unlock_irqrestore(&edev->lock, flags);
  
  	return ret;
  }
  EXPORT_SYMBOL_GPL(extcon_set_property);
  
  /**
ab11af049   Chanwoo Choi   extcon: Add the s...
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
   * extcon_set_property_sync() - Set the property value of a specific cable
  			with a notification.
   * @prop_val:		the pointer including the new value of property.
   *
   * When setting the property value of external connector, the external connector
   * should be attached. The each property should be included in the list of
   * supported properties according to the type of external connectors.
   *
   * Returns 0 if success or error number if fail
   */
  int extcon_set_property_sync(struct extcon_dev *edev, unsigned int id,
  				unsigned int prop,
  				union extcon_property_value prop_val)
  {
  	int ret;
  
  	ret = extcon_set_property(edev, id, prop, prop_val);
  	if (ret < 0)
  		return ret;
  
  	return extcon_sync(edev, id);
  }
  EXPORT_SYMBOL_GPL(extcon_set_property_sync);
  
  /**
7f2a0a169   Chanwoo Choi   extcon: Add the s...
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
   * extcon_get_property_capability() - Get the capability of property
   *			of an external connector.
   * @edev:		the extcon device that has the cable.
   * @id:			the unique id of each external connector
   *			in extcon enumeration.
   * @prop:		the property id among enum extcon_property.
   *
   * Returns 1 if the property is available or 0 if not available.
   */
  int extcon_get_property_capability(struct extcon_dev *edev, unsigned int id,
  					unsigned int prop)
  {
  	int index;
  
  	if (!edev)
  		return -EINVAL;
  
  	/* Check whether the property is supported or not */
  	if (!is_extcon_property_supported(id, prop))
  		return -EINVAL;
  
  	/* Find the cable index of external connector by using id */
  	index = find_cable_index_by_id(edev, id);
  	if (index < 0)
  		return index;
  
  	return is_extcon_property_capability(edev, id, index, prop);
  }
  EXPORT_SYMBOL_GPL(extcon_get_property_capability);
  
  /**
   * extcon_set_property_capability() - Set the capability of a property
   *			of an external connector.
   * @edev:		the extcon device that has the cable.
   * @id:			the unique id of each external connector
   *			in extcon enumeration.
   * @prop:		the property id among enum extcon_property.
   *
   * This function set the capability of a property for an external connector
   * to mark the bit in capability bitmap which mean the available state of
   * a property.
   *
   * Returns 0 if success or error number if fail
   */
  int extcon_set_property_capability(struct extcon_dev *edev, unsigned int id,
  					unsigned int prop)
  {
  	struct extcon_cable *cable;
  	int index, type, ret = 0;
  
  	if (!edev)
  		return -EINVAL;
  
  	/* Check whether the property is supported or not. */
  	if (!is_extcon_property_supported(id, prop))
  		return -EINVAL;
  
  	/* Find the cable index of external connector by using id. */
  	index = find_cable_index_by_id(edev, id);
  	if (index < 0)
  		return index;
  
  	type = get_extcon_type(prop);
  	if (type < 0)
  		return type;
  
  	cable = &edev->cables[index];
  
  	switch (type) {
  	case EXTCON_TYPE_USB:
  		__set_bit(prop - EXTCON_PROP_USB_MIN, cable->usb_bits);
  		break;
  	case EXTCON_TYPE_CHG:
  		__set_bit(prop - EXTCON_PROP_CHG_MIN, cable->chg_bits);
  		break;
  	case EXTCON_TYPE_JACK:
  		__set_bit(prop - EXTCON_PROP_JACK_MIN, cable->jack_bits);
  		break;
  	case EXTCON_TYPE_DISP:
  		__set_bit(prop - EXTCON_PROP_DISP_MIN, cable->disp_bits);
  		break;
  	default:
  		ret = -EINVAL;
  	}
  
  	return ret;
  }
  EXPORT_SYMBOL_GPL(extcon_set_property_capability);
  
  /**
74c5d09bd   Donggeun Kim   Extcon: support n...
851
852
853
854
855
856
   * extcon_get_extcon_dev() - Get the extcon device instance from the name
   * @extcon_name:	The extcon name provided with extcon_dev_register()
   */
  struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name)
  {
  	struct extcon_dev *sd;
7eae43aea   Chanwoo Choi   extcon: Add excep...
857
858
  	if (!extcon_name)
  		return ERR_PTR(-EINVAL);
74c5d09bd   Donggeun Kim   Extcon: support n...
859
860
861
862
863
864
865
866
867
868
869
  	mutex_lock(&extcon_dev_list_lock);
  	list_for_each_entry(sd, &extcon_dev_list, entry) {
  		if (!strcmp(sd->name, extcon_name))
  			goto out;
  	}
  	sd = NULL;
  out:
  	mutex_unlock(&extcon_dev_list_lock);
  	return sd;
  }
  EXPORT_SYMBOL_GPL(extcon_get_extcon_dev);
806d9dd71   MyungJoo Ham   Extcon: support m...
870
  /**
c338bb038   Peter Meerwald   extcon: fixing typos
871
   * extcon_register_notifier() - Register a notifiee to get notified by
a75e1c73a   Chanwoo Choi   extcon: Fix inden...
872
   *				any attach status changes from the extcon.
046050f6e   Chanwoo Choi   extcon: Update th...
873
874
   * @edev:	the extcon device that has the external connecotr.
   * @id:		the unique id of each external connector in extcon enumeration.
74c5d09bd   Donggeun Kim   Extcon: support n...
875
   * @nb:		a notifier block to be registered.
806d9dd71   MyungJoo Ham   Extcon: support m...
876
877
878
879
   *
   * Note that the second parameter given to the callback of nb (val) is
   * "old_state", not the current state. The current state can be retrieved
   * by looking at the third pameter (edev pointer)'s state value.
74c5d09bd   Donggeun Kim   Extcon: support n...
880
   */
73b6ecdb9   Chanwoo Choi   extcon: Redefine ...
881
  int extcon_register_notifier(struct extcon_dev *edev, unsigned int id,
046050f6e   Chanwoo Choi   extcon: Update th...
882
  			     struct notifier_block *nb)
74c5d09bd   Donggeun Kim   Extcon: support n...
883
  {
66bee35f2   Hans de Goede   extcon: Fix missi...
884
  	unsigned long flags;
2c8116a11   Maninder Singh   extcon: Fix compi...
885
  	int ret, idx = -EINVAL;
046050f6e   Chanwoo Choi   extcon: Update th...
886

830ae4422   Chanwoo Choi   extcon: Remove th...
887
  	if (!nb)
7eae43aea   Chanwoo Choi   extcon: Add excep...
888
  		return -EINVAL;
830ae4422   Chanwoo Choi   extcon: Remove th...
889
890
  	if (edev) {
  		idx = find_cable_index_by_id(edev, id);
a05f44c89   Stephen Boyd   extcon: Check for...
891
892
  		if (idx < 0)
  			return idx;
66bee35f2   Hans de Goede   extcon: Fix missi...
893

830ae4422   Chanwoo Choi   extcon: Remove th...
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
  		spin_lock_irqsave(&edev->lock, flags);
  		ret = raw_notifier_chain_register(&edev->nh[idx], nb);
  		spin_unlock_irqrestore(&edev->lock, flags);
  	} else {
  		struct extcon_dev *extd;
  
  		mutex_lock(&extcon_dev_list_lock);
  		list_for_each_entry(extd, &extcon_dev_list, entry) {
  			idx = find_cable_index_by_id(extd, id);
  			if (idx >= 0)
  				break;
  		}
  		mutex_unlock(&extcon_dev_list_lock);
  
  		if (idx >= 0) {
  			edev = extd;
  			return extcon_register_notifier(extd, id, nb);
  		} else {
  			ret = -ENODEV;
  		}
  	}
66bee35f2   Hans de Goede   extcon: Fix missi...
915
916
  
  	return ret;
74c5d09bd   Donggeun Kim   Extcon: support n...
917
918
919
920
  }
  EXPORT_SYMBOL_GPL(extcon_register_notifier);
  
  /**
c338bb038   Peter Meerwald   extcon: fixing typos
921
   * extcon_unregister_notifier() - Unregister a notifiee from the extcon device.
046050f6e   Chanwoo Choi   extcon: Update th...
922
923
924
   * @edev:	the extcon device that has the external connecotr.
   * @id:		the unique id of each external connector in extcon enumeration.
   * @nb:		a notifier block to be registered.
74c5d09bd   Donggeun Kim   Extcon: support n...
925
   */
73b6ecdb9   Chanwoo Choi   extcon: Redefine ...
926
  int extcon_unregister_notifier(struct extcon_dev *edev, unsigned int id,
046050f6e   Chanwoo Choi   extcon: Update th...
927
  				struct notifier_block *nb)
74c5d09bd   Donggeun Kim   Extcon: support n...
928
  {
66bee35f2   Hans de Goede   extcon: Fix missi...
929
  	unsigned long flags;
046050f6e   Chanwoo Choi   extcon: Update th...
930
  	int ret, idx;
7eae43aea   Chanwoo Choi   extcon: Add excep...
931
932
  	if (!edev || !nb)
  		return -EINVAL;
046050f6e   Chanwoo Choi   extcon: Update th...
933
  	idx = find_cable_index_by_id(edev, id);
a05f44c89   Stephen Boyd   extcon: Check for...
934
935
  	if (idx < 0)
  		return idx;
66bee35f2   Hans de Goede   extcon: Fix missi...
936
937
  
  	spin_lock_irqsave(&edev->lock, flags);
046050f6e   Chanwoo Choi   extcon: Update th...
938
  	ret = raw_notifier_chain_unregister(&edev->nh[idx], nb);
66bee35f2   Hans de Goede   extcon: Fix missi...
939
940
941
  	spin_unlock_irqrestore(&edev->lock, flags);
  
  	return ret;
74c5d09bd   Donggeun Kim   Extcon: support n...
942
943
  }
  EXPORT_SYMBOL_GPL(extcon_unregister_notifier);
af01da0e0   Greg Kroah-Hartman   extcon: convert e...
944
945
946
947
  static struct attribute *extcon_attrs[] = {
  	&dev_attr_state.attr,
  	&dev_attr_name.attr,
  	NULL,
de55d8716   MyungJoo Ham   Extcon (external ...
948
  };
af01da0e0   Greg Kroah-Hartman   extcon: convert e...
949
  ATTRIBUTE_GROUPS(extcon);
de55d8716   MyungJoo Ham   Extcon (external ...
950
951
952
953
954
955
956
  
  static int create_extcon_class(void)
  {
  	if (!extcon_class) {
  		extcon_class = class_create(THIS_MODULE, "extcon");
  		if (IS_ERR(extcon_class))
  			return PTR_ERR(extcon_class);
af01da0e0   Greg Kroah-Hartman   extcon: convert e...
957
  		extcon_class->dev_groups = extcon_groups;
de55d8716   MyungJoo Ham   Extcon (external ...
958

449a2bf5e   MyungJoo Ham   Remove "switch" c...
959
  #if defined(CONFIG_ANDROID)
de55d8716   MyungJoo Ham   Extcon (external ...
960
961
962
  		switch_class = class_compat_register("switch");
  		if (WARN(!switch_class, "cannot allocate"))
  			return -ENOMEM;
449a2bf5e   MyungJoo Ham   Remove "switch" c...
963
  #endif /* CONFIG_ANDROID */
de55d8716   MyungJoo Ham   Extcon (external ...
964
965
966
967
  	}
  
  	return 0;
  }
de55d8716   MyungJoo Ham   Extcon (external ...
968
969
  static void extcon_dev_release(struct device *dev)
  {
de55d8716   MyungJoo Ham   Extcon (external ...
970
  }
bde68e60b   MyungJoo Ham   Extcon: support m...
971
  static const char *muex_name = "mutually_exclusive";
806d9dd71   MyungJoo Ham   Extcon: support m...
972
973
974
  static void dummy_sysfs_dev_release(struct device *dev)
  {
  }
a9af65223   Chanwoo Choi   extcon: Add extco...
975
976
  /*
   * extcon_dev_allocate() - Allocate the memory of extcon device.
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
977
   * @supported_cable:	Array of supported extcon ending with EXTCON_NONE.
a9af65223   Chanwoo Choi   extcon: Add extco...
978
979
980
981
982
983
984
985
986
   *			If supported_cable is NULL, cable name related APIs
   *			are disabled.
   *
   * This function allocates the memory for extcon device without allocating
   * memory in each extcon provider driver and initialize default setting for
   * extcon device.
   *
   * Return the pointer of extcon device if success or ERR_PTR(err) if fail
   */
73b6ecdb9   Chanwoo Choi   extcon: Redefine ...
987
  struct extcon_dev *extcon_dev_allocate(const unsigned int *supported_cable)
a9af65223   Chanwoo Choi   extcon: Add extco...
988
989
  {
  	struct extcon_dev *edev;
7eae43aea   Chanwoo Choi   extcon: Add excep...
990
991
  	if (!supported_cable)
  		return ERR_PTR(-EINVAL);
a9af65223   Chanwoo Choi   extcon: Add extco...
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
  	edev = kzalloc(sizeof(*edev), GFP_KERNEL);
  	if (!edev)
  		return ERR_PTR(-ENOMEM);
  
  	edev->max_supported = 0;
  	edev->supported_cable = supported_cable;
  
  	return edev;
  }
  
  /*
   * extcon_dev_free() - Free the memory of extcon device.
   * @edev:	the extcon device to free
   */
  void extcon_dev_free(struct extcon_dev *edev)
  {
  	kfree(edev);
  }
  EXPORT_SYMBOL_GPL(extcon_dev_free);
de55d8716   MyungJoo Ham   Extcon (external ...
1011
1012
1013
  /**
   * extcon_dev_register() - Register a new extcon device
   * @edev	: the new extcon device (should be allocated before calling)
de55d8716   MyungJoo Ham   Extcon (external ...
1014
1015
1016
1017
1018
1019
   *
   * Among the members of edev struct, please set the "user initializing data"
   * in any case and set the "optional callbacks" if required. However, please
   * do not set the values of "internal data", which are initialized by
   * this function.
   */
42d7d7539   Chanwoo Choi   extcon: Simplify ...
1020
  int extcon_dev_register(struct extcon_dev *edev)
de55d8716   MyungJoo Ham   Extcon (external ...
1021
  {
806d9dd71   MyungJoo Ham   Extcon: support m...
1022
  	int ret, index = 0;
71c3ffa5d   Chanwoo Choi   extcon: Modify th...
1023
  	static atomic_t edev_no = ATOMIC_INIT(-1);
de55d8716   MyungJoo Ham   Extcon (external ...
1024
1025
1026
1027
1028
1029
  
  	if (!extcon_class) {
  		ret = create_extcon_class();
  		if (ret < 0)
  			return ret;
  	}
7eae43aea   Chanwoo Choi   extcon: Add excep...
1030
  	if (!edev || !edev->supported_cable)
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
1031
1032
1033
  		return -EINVAL;
  
  	for (; edev->supported_cable[index] != EXTCON_NONE; index++);
806d9dd71   MyungJoo Ham   Extcon: support m...
1034

2a9de9c0f   Chanwoo Choi   extcon: Use the u...
1035
  	edev->max_supported = index;
806d9dd71   MyungJoo Ham   Extcon: support m...
1036
  	if (index > SUPPORTED_CABLE_MAX) {
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
1037
1038
1039
  		dev_err(&edev->dev,
  			"exceed the maximum number of supported cables
  ");
806d9dd71   MyungJoo Ham   Extcon: support m...
1040
1041
  		return -EINVAL;
  	}
dae616512   Chanwoo Choi   extcon: Change fi...
1042
1043
  	edev->dev.class = extcon_class;
  	edev->dev.release = extcon_dev_release;
de55d8716   MyungJoo Ham   Extcon (external ...
1044

71c3ffa5d   Chanwoo Choi   extcon: Modify th...
1045
  	edev->name = dev_name(edev->dev.parent);
42d7d7539   Chanwoo Choi   extcon: Simplify ...
1046
1047
1048
1049
1050
1051
  	if (IS_ERR_OR_NULL(edev->name)) {
  		dev_err(&edev->dev,
  			"extcon device name is null
  ");
  		return -EINVAL;
  	}
71c3ffa5d   Chanwoo Choi   extcon: Modify th...
1052
1053
  	dev_set_name(&edev->dev, "extcon%lu",
  			(unsigned long)atomic_inc_return(&edev_no));
806d9dd71   MyungJoo Ham   Extcon: support m...
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
  
  	if (edev->max_supported) {
  		char buf[10];
  		char *str;
  		struct extcon_cable *cable;
  
  		edev->cables = kzalloc(sizeof(struct extcon_cable) *
  				       edev->max_supported, GFP_KERNEL);
  		if (!edev->cables) {
  			ret = -ENOMEM;
  			goto err_sysfs_alloc;
  		}
  		for (index = 0; index < edev->max_supported; index++) {
  			cable = &edev->cables[index];
  
  			snprintf(buf, 10, "cable.%d", index);
  			str = kzalloc(sizeof(char) * (strlen(buf) + 1),
  				      GFP_KERNEL);
  			if (!str) {
  				for (index--; index >= 0; index--) {
  					cable = &edev->cables[index];
  					kfree(cable->attr_g.name);
  				}
  				ret = -ENOMEM;
  
  				goto err_alloc_cables;
  			}
  			strcpy(str, buf);
  
  			cable->edev = edev;
  			cable->cable_index = index;
  			cable->attrs[0] = &cable->attr_name.attr;
  			cable->attrs[1] = &cable->attr_state.attr;
  			cable->attrs[2] = NULL;
  			cable->attr_g.name = str;
  			cable->attr_g.attrs = cable->attrs;
9baf3220a   Mark Brown   extcon: Ensure dy...
1090
  			sysfs_attr_init(&cable->attr_name.attr);
806d9dd71   MyungJoo Ham   Extcon: support m...
1091
1092
1093
  			cable->attr_name.attr.name = "name";
  			cable->attr_name.attr.mode = 0444;
  			cable->attr_name.show = cable_name_show;
9baf3220a   Mark Brown   extcon: Ensure dy...
1094
  			sysfs_attr_init(&cable->attr_state.attr);
806d9dd71   MyungJoo Ham   Extcon: support m...
1095
  			cable->attr_state.attr.name = "state";
ea9dd9d65   Chanwoo Choi   extcon: Change pe...
1096
  			cable->attr_state.attr.mode = 0444;
806d9dd71   MyungJoo Ham   Extcon: support m...
1097
  			cable->attr_state.show = cable_state_show;
806d9dd71   MyungJoo Ham   Extcon: support m...
1098
1099
  		}
  	}
bde68e60b   MyungJoo Ham   Extcon: support m...
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
  	if (edev->max_supported && edev->mutually_exclusive) {
  		char buf[80];
  		char *name;
  
  		/* Count the size of mutually_exclusive array */
  		for (index = 0; edev->mutually_exclusive[index]; index++)
  			;
  
  		edev->attrs_muex = kzalloc(sizeof(struct attribute *) *
  					   (index + 1), GFP_KERNEL);
  		if (!edev->attrs_muex) {
  			ret = -ENOMEM;
  			goto err_muex;
  		}
  
  		edev->d_attrs_muex = kzalloc(sizeof(struct device_attribute) *
  					     index, GFP_KERNEL);
  		if (!edev->d_attrs_muex) {
  			ret = -ENOMEM;
  			kfree(edev->attrs_muex);
  			goto err_muex;
  		}
  
  		for (index = 0; edev->mutually_exclusive[index]; index++) {
  			sprintf(buf, "0x%x", edev->mutually_exclusive[index]);
  			name = kzalloc(sizeof(char) * (strlen(buf) + 1),
  				       GFP_KERNEL);
  			if (!name) {
  				for (index--; index >= 0; index--) {
  					kfree(edev->d_attrs_muex[index].attr.
  					      name);
  				}
  				kfree(edev->d_attrs_muex);
  				kfree(edev->attrs_muex);
  				ret = -ENOMEM;
  				goto err_muex;
  			}
  			strcpy(name, buf);
9baf3220a   Mark Brown   extcon: Ensure dy...
1138
  			sysfs_attr_init(&edev->d_attrs_muex[index].attr);
bde68e60b   MyungJoo Ham   Extcon: support m...
1139
1140
1141
1142
1143
1144
1145
1146
1147
  			edev->d_attrs_muex[index].attr.name = name;
  			edev->d_attrs_muex[index].attr.mode = 0000;
  			edev->attrs_muex[index] = &edev->d_attrs_muex[index]
  							.attr;
  		}
  		edev->attr_g_muex.name = muex_name;
  		edev->attr_g_muex.attrs = edev->attrs_muex;
  
  	}
806d9dd71   MyungJoo Ham   Extcon: support m...
1148
1149
1150
  	if (edev->max_supported) {
  		edev->extcon_dev_type.groups =
  			kzalloc(sizeof(struct attribute_group *) *
bde68e60b   MyungJoo Ham   Extcon: support m...
1151
  				(edev->max_supported + 2), GFP_KERNEL);
806d9dd71   MyungJoo Ham   Extcon: support m...
1152
1153
1154
1155
  		if (!edev->extcon_dev_type.groups) {
  			ret = -ENOMEM;
  			goto err_alloc_groups;
  		}
dae616512   Chanwoo Choi   extcon: Change fi...
1156
  		edev->extcon_dev_type.name = dev_name(&edev->dev);
806d9dd71   MyungJoo Ham   Extcon: support m...
1157
1158
1159
1160
1161
  		edev->extcon_dev_type.release = dummy_sysfs_dev_release;
  
  		for (index = 0; index < edev->max_supported; index++)
  			edev->extcon_dev_type.groups[index] =
  				&edev->cables[index].attr_g;
bde68e60b   MyungJoo Ham   Extcon: support m...
1162
1163
1164
  		if (edev->mutually_exclusive)
  			edev->extcon_dev_type.groups[index] =
  				&edev->attr_g_muex;
806d9dd71   MyungJoo Ham   Extcon: support m...
1165

dae616512   Chanwoo Choi   extcon: Change fi...
1166
  		edev->dev.type = &edev->extcon_dev_type;
806d9dd71   MyungJoo Ham   Extcon: support m...
1167
  	}
dae616512   Chanwoo Choi   extcon: Change fi...
1168
  	ret = device_register(&edev->dev);
de55d8716   MyungJoo Ham   Extcon (external ...
1169
  	if (ret) {
dae616512   Chanwoo Choi   extcon: Change fi...
1170
  		put_device(&edev->dev);
de55d8716   MyungJoo Ham   Extcon (external ...
1171
1172
  		goto err_dev;
  	}
449a2bf5e   MyungJoo Ham   Remove "switch" c...
1173
  #if defined(CONFIG_ANDROID)
de55d8716   MyungJoo Ham   Extcon (external ...
1174
  	if (switch_class)
dae616512   Chanwoo Choi   extcon: Change fi...
1175
  		ret = class_compat_create_link(switch_class, &edev->dev, NULL);
449a2bf5e   MyungJoo Ham   Remove "switch" c...
1176
  #endif /* CONFIG_ANDROID */
de55d8716   MyungJoo Ham   Extcon (external ...
1177

806d9dd71   MyungJoo Ham   Extcon: support m...
1178
  	spin_lock_init(&edev->lock);
046050f6e   Chanwoo Choi   extcon: Update th...
1179
1180
1181
1182
1183
1184
1185
1186
1187
  	edev->nh = devm_kzalloc(&edev->dev,
  			sizeof(*edev->nh) * edev->max_supported, GFP_KERNEL);
  	if (!edev->nh) {
  		ret = -ENOMEM;
  		goto err_dev;
  	}
  
  	for (index = 0; index < edev->max_supported; index++)
  		RAW_INIT_NOTIFIER_HEAD(&edev->nh[index]);
74c5d09bd   Donggeun Kim   Extcon: support n...
1188

dae616512   Chanwoo Choi   extcon: Change fi...
1189
  	dev_set_drvdata(&edev->dev, edev);
de55d8716   MyungJoo Ham   Extcon (external ...
1190
  	edev->state = 0;
74c5d09bd   Donggeun Kim   Extcon: support n...
1191
1192
1193
1194
  
  	mutex_lock(&extcon_dev_list_lock);
  	list_add(&edev->entry, &extcon_dev_list);
  	mutex_unlock(&extcon_dev_list_lock);
de55d8716   MyungJoo Ham   Extcon (external ...
1195
1196
1197
  	return 0;
  
  err_dev:
806d9dd71   MyungJoo Ham   Extcon: support m...
1198
1199
1200
  	if (edev->max_supported)
  		kfree(edev->extcon_dev_type.groups);
  err_alloc_groups:
bde68e60b   MyungJoo Ham   Extcon: support m...
1201
1202
1203
1204
1205
1206
1207
  	if (edev->max_supported && edev->mutually_exclusive) {
  		for (index = 0; edev->mutually_exclusive[index]; index++)
  			kfree(edev->d_attrs_muex[index].attr.name);
  		kfree(edev->d_attrs_muex);
  		kfree(edev->attrs_muex);
  	}
  err_muex:
806d9dd71   MyungJoo Ham   Extcon: support m...
1208
1209
1210
1211
1212
1213
  	for (index = 0; index < edev->max_supported; index++)
  		kfree(edev->cables[index].attr_g.name);
  err_alloc_cables:
  	if (edev->max_supported)
  		kfree(edev->cables);
  err_sysfs_alloc:
de55d8716   MyungJoo Ham   Extcon (external ...
1214
1215
1216
1217
1218
1219
  	return ret;
  }
  EXPORT_SYMBOL_GPL(extcon_dev_register);
  
  /**
   * extcon_dev_unregister() - Unregister the extcon device.
c338bb038   Peter Meerwald   extcon: fixing typos
1220
   * @edev:	the extcon device instance to be unregistered.
de55d8716   MyungJoo Ham   Extcon (external ...
1221
1222
1223
1224
1225
1226
   *
   * Note that this does not call kfree(edev) because edev was not allocated
   * by this class.
   */
  void extcon_dev_unregister(struct extcon_dev *edev)
  {
57e7cd370   anish kumar   extcon: driver mo...
1227
  	int index;
7eae43aea   Chanwoo Choi   extcon: Add excep...
1228
1229
  	if (!edev)
  		return;
57e7cd370   anish kumar   extcon: driver mo...
1230
1231
1232
  	mutex_lock(&extcon_dev_list_lock);
  	list_del(&edev->entry);
  	mutex_unlock(&extcon_dev_list_lock);
dae616512   Chanwoo Choi   extcon: Change fi...
1233
1234
1235
1236
  	if (IS_ERR_OR_NULL(get_device(&edev->dev))) {
  		dev_err(&edev->dev, "Failed to unregister extcon_dev (%s)
  ",
  				dev_name(&edev->dev));
57e7cd370   anish kumar   extcon: driver mo...
1237
1238
  		return;
  	}
7585ca0dc   Wang, Xiaoming   extcon: remove fr...
1239
  	device_unregister(&edev->dev);
57e7cd370   anish kumar   extcon: driver mo...
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
  	if (edev->mutually_exclusive && edev->max_supported) {
  		for (index = 0; edev->mutually_exclusive[index];
  				index++)
  			kfree(edev->d_attrs_muex[index].attr.name);
  		kfree(edev->d_attrs_muex);
  		kfree(edev->attrs_muex);
  	}
  
  	for (index = 0; index < edev->max_supported; index++)
  		kfree(edev->cables[index].attr_g.name);
  
  	if (edev->max_supported) {
  		kfree(edev->extcon_dev_type.groups);
  		kfree(edev->cables);
  	}
  
  #if defined(CONFIG_ANDROID)
  	if (switch_class)
dae616512   Chanwoo Choi   extcon: Change fi...
1258
  		class_compat_remove_link(switch_class, &edev->dev, NULL);
57e7cd370   anish kumar   extcon: driver mo...
1259
  #endif
dae616512   Chanwoo Choi   extcon: Change fi...
1260
  	put_device(&edev->dev);
de55d8716   MyungJoo Ham   Extcon (external ...
1261
1262
  }
  EXPORT_SYMBOL_GPL(extcon_dev_unregister);
1ad94ffef   Chanwoo Choi   extcon: Move OF h...
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
  #ifdef CONFIG_OF
  /*
   * extcon_get_edev_by_phandle - Get the extcon device from devicetree
   * @dev - instance to the given device
   * @index - index into list of extcon_dev
   *
   * return the instance of extcon device
   */
  struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index)
  {
  	struct device_node *node;
  	struct extcon_dev *edev;
7eae43aea   Chanwoo Choi   extcon: Add excep...
1275
1276
  	if (!dev)
  		return ERR_PTR(-EINVAL);
1ad94ffef   Chanwoo Choi   extcon: Move OF h...
1277
  	if (!dev->of_node) {
e8752b7a7   Stephen Boyd   extcon: Move extc...
1278
1279
  		dev_dbg(dev, "device does not have a device node entry
  ");
1ad94ffef   Chanwoo Choi   extcon: Move OF h...
1280
1281
1282
1283
1284
  		return ERR_PTR(-EINVAL);
  	}
  
  	node = of_parse_phandle(dev->of_node, "extcon", index);
  	if (!node) {
e8752b7a7   Stephen Boyd   extcon: Move extc...
1285
1286
  		dev_dbg(dev, "failed to get phandle in %s node
  ",
1ad94ffef   Chanwoo Choi   extcon: Move OF h...
1287
1288
1289
  			dev->of_node->full_name);
  		return ERR_PTR(-ENODEV);
  	}
f841afb17   Tomasz Figa   extcon: Implement...
1290
1291
1292
1293
  	mutex_lock(&extcon_dev_list_lock);
  	list_for_each_entry(edev, &extcon_dev_list, entry) {
  		if (edev->dev.parent && edev->dev.parent->of_node == node) {
  			mutex_unlock(&extcon_dev_list_lock);
5d5c4c139   Peter Chen   extcon: add missi...
1294
  			of_node_put(node);
f841afb17   Tomasz Figa   extcon: Implement...
1295
1296
  			return edev;
  		}
1ad94ffef   Chanwoo Choi   extcon: Move OF h...
1297
  	}
f841afb17   Tomasz Figa   extcon: Implement...
1298
  	mutex_unlock(&extcon_dev_list_lock);
5d5c4c139   Peter Chen   extcon: add missi...
1299
  	of_node_put(node);
1ad94ffef   Chanwoo Choi   extcon: Move OF h...
1300

f841afb17   Tomasz Figa   extcon: Implement...
1301
  	return ERR_PTR(-EPROBE_DEFER);
1ad94ffef   Chanwoo Choi   extcon: Move OF h...
1302
1303
1304
1305
1306
1307
1308
1309
  }
  #else
  struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index)
  {
  	return ERR_PTR(-ENOSYS);
  }
  #endif /* CONFIG_OF */
  EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle);
707d75508   Chanwoo Choi   extcon: Add extco...
1310
1311
1312
1313
1314
1315
1316
1317
  /**
   * extcon_get_edev_name() - Get the name of the extcon device.
   * @edev:	the extcon device
   */
  const char *extcon_get_edev_name(struct extcon_dev *edev)
  {
  	return !edev ? NULL : edev->name;
  }
de55d8716   MyungJoo Ham   Extcon (external ...
1318
1319
1320
1321
1322
1323
1324
1325
  static int __init extcon_class_init(void)
  {
  	return create_extcon_class();
  }
  module_init(extcon_class_init);
  
  static void __exit extcon_class_exit(void)
  {
0dc77b6da   Peter Huewe   extcon: Unregiste...
1326
1327
1328
  #if defined(CONFIG_ANDROID)
  	class_compat_unregister(switch_class);
  #endif
de55d8716   MyungJoo Ham   Extcon (external ...
1329
1330
1331
  	class_destroy(extcon_class);
  }
  module_exit(extcon_class_exit);
2a9de9c0f   Chanwoo Choi   extcon: Use the u...
1332
  MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
de55d8716   MyungJoo Ham   Extcon (external ...
1333
1334
1335
1336
1337
  MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
  MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
  MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
  MODULE_DESCRIPTION("External connector (extcon) class driver");
  MODULE_LICENSE("GPL");