diff options
author | MyungJoo Ham <myungjoo.ham@samsung.com> | 2012-04-20 01:16:26 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-04-20 12:24:03 -0400 |
commit | bde68e60b18208978c50c6fb9bdf29826d2887f3 (patch) | |
tree | 62b48807d48c2454c374bede680c9e9c6cf71a0d | |
parent | 806d9dd71ff52ef09764585baaeec23afbb98560 (diff) |
Extcon: support mutually exclusive relation between cables.
There could be cables that t recannot be attaches simulatenously. Extcon
device drivers may express such information via mutually_exclusive in
struct extcon_dev.
For example, for an extcon device with 16 cables (bits 0 to 15 are
available), if mutually_exclusive = { 0x7, 0xC0, 0x81, 0 }, then, the
following attachments are prohibitted.
{0, 1}
{0, 2}
{1, 2}
{6, 7}
{0, 7}
and every attachment set that are superset of one of the above.
For the detail, please refer to linux/include/linux/extcon.h.
The concept is suggested by NeilBrown <neilb@suse.de>
Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
--
Changes from V5:
- Updated sysfs format
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | Documentation/ABI/testing/sysfs-class-extcon | 22 | ||||
-rw-r--r-- | drivers/extcon/extcon_class.c | 123 | ||||
-rw-r--r-- | include/linux/extcon.h | 28 |
3 files changed, 160 insertions, 13 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-extcon b/Documentation/ABI/testing/sysfs-class-extcon index 19b18e986a57..20ab361bd8c6 100644 --- a/Documentation/ABI/testing/sysfs-class-extcon +++ b/Documentation/ABI/testing/sysfs-class-extcon | |||
@@ -15,6 +15,10 @@ Description: | |||
15 | may have both HDMI and Charger attached, or analog audio, | 15 | may have both HDMI and Charger attached, or analog audio, |
16 | video, and USB cables attached simulteneously. | 16 | video, and USB cables attached simulteneously. |
17 | 17 | ||
18 | If there are cables mutually exclusive with each other, | ||
19 | such binary relations may be expressed with extcon_dev's | ||
20 | mutually_exclusive array. | ||
21 | |||
18 | What: /sys/class/extcon/.../name | 22 | What: /sys/class/extcon/.../name |
19 | Date: February 2012 | 23 | Date: February 2012 |
20 | Contact: MyungJoo Ham <myungjoo.ham@samsung.com> | 24 | Contact: MyungJoo Ham <myungjoo.ham@samsung.com> |
@@ -73,3 +77,21 @@ Description: | |||
73 | state of cable "x" (integer between 0 and 31) of an extcon | 77 | state of cable "x" (integer between 0 and 31) of an extcon |
74 | device. The state value is either 0 (detached) or 1 | 78 | device. The state value is either 0 (detached) or 1 |
75 | (attached). | 79 | (attached). |
80 | |||
81 | What: /sys/class/extcon/.../mutually_exclusive/... | ||
82 | Date: December 2011 | ||
83 | Contact: MyungJoo Ham <myungjoo.ham@samsung.com> | ||
84 | Description: | ||
85 | Shows the relations of mutually exclusiveness. For example, | ||
86 | if the mutually_exclusive array of extcon_dev is | ||
87 | {0x3, 0x5, 0xC, 0x0}, the, the output is: | ||
88 | # ls mutually_exclusive/ | ||
89 | 0x3 | ||
90 | 0x5 | ||
91 | 0xc | ||
92 | # | ||
93 | |||
94 | Note that mutually_exclusive is a sub-directory of the extcon | ||
95 | device and the file names under the mutually_exclusive | ||
96 | directory show the mutually-exclusive sets, not the contents | ||
97 | of the files. | ||
diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 403933bc3e9c..3bc4b8af46cf 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c | |||
@@ -72,6 +72,39 @@ static struct class_compat *switch_class; | |||
72 | static LIST_HEAD(extcon_dev_list); | 72 | static LIST_HEAD(extcon_dev_list); |
73 | static DEFINE_MUTEX(extcon_dev_list_lock); | 73 | static DEFINE_MUTEX(extcon_dev_list_lock); |
74 | 74 | ||
75 | /** | ||
76 | * check_mutually_exclusive - Check if new_state violates mutually_exclusive | ||
77 | * condition. | ||
78 | * @edev: the extcon device | ||
79 | * @new_state: new cable attach status for @edev | ||
80 | * | ||
81 | * Returns 0 if nothing violates. Returns the index + 1 for the first | ||
82 | * violated condition. | ||
83 | */ | ||
84 | static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state) | ||
85 | { | ||
86 | int i = 0; | ||
87 | |||
88 | if (!edev->mutually_exclusive) | ||
89 | return 0; | ||
90 | |||
91 | for (i = 0; edev->mutually_exclusive[i]; i++) { | ||
92 | int count = 0, j; | ||
93 | u32 correspondants = new_state & edev->mutually_exclusive[i]; | ||
94 | u32 exp = 1; | ||
95 | |||
96 | for (j = 0; j < 32; j++) { | ||
97 | if (exp & correspondants) | ||
98 | count++; | ||
99 | if (count > 1) | ||
100 | return i + 1; | ||
101 | exp <<= 1; | ||
102 | } | ||
103 | } | ||
104 | |||
105 | return 0; | ||
106 | } | ||
107 | |||
75 | static ssize_t state_show(struct device *dev, struct device_attribute *attr, | 108 | static ssize_t state_show(struct device *dev, struct device_attribute *attr, |
76 | char *buf) | 109 | char *buf) |
77 | { | 110 | { |
@@ -100,7 +133,7 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, | |||
100 | return count; | 133 | return count; |
101 | } | 134 | } |
102 | 135 | ||
103 | void extcon_set_state(struct extcon_dev *edev, u32 state); | 136 | int extcon_set_state(struct extcon_dev *edev, u32 state); |
104 | static ssize_t state_store(struct device *dev, struct device_attribute *attr, | 137 | static ssize_t state_store(struct device *dev, struct device_attribute *attr, |
105 | const char *buf, size_t count) | 138 | const char *buf, size_t count) |
106 | { | 139 | { |
@@ -112,7 +145,7 @@ static ssize_t state_store(struct device *dev, struct device_attribute *attr, | |||
112 | if (ret == 0) | 145 | if (ret == 0) |
113 | ret = -EINVAL; | 146 | ret = -EINVAL; |
114 | else | 147 | else |
115 | extcon_set_state(edev, state); | 148 | ret = extcon_set_state(edev, state); |
116 | 149 | ||
117 | if (ret < 0) | 150 | if (ret < 0) |
118 | return ret; | 151 | return ret; |
@@ -191,7 +224,7 @@ static ssize_t cable_state_store(struct device *dev, | |||
191 | * Note that the notifier provides which bits are changed in the state | 224 | * Note that the notifier provides which bits are changed in the state |
192 | * variable with the val parameter (second) to the callback. | 225 | * variable with the val parameter (second) to the callback. |
193 | */ | 226 | */ |
194 | void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) | 227 | int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) |
195 | { | 228 | { |
196 | char name_buf[120]; | 229 | char name_buf[120]; |
197 | char state_buf[120]; | 230 | char state_buf[120]; |
@@ -206,6 +239,12 @@ void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) | |||
206 | if (edev->state != ((edev->state & ~mask) | (state & mask))) { | 239 | if (edev->state != ((edev->state & ~mask) | (state & mask))) { |
207 | u32 old_state = edev->state; | 240 | u32 old_state = edev->state; |
208 | 241 | ||
242 | if (check_mutually_exclusive(edev, (edev->state & ~mask) | | ||
243 | (state & mask))) { | ||
244 | spin_unlock_irqrestore(&edev->lock, flags); | ||
245 | return -EPERM; | ||
246 | } | ||
247 | |||
209 | edev->state &= ~mask; | 248 | edev->state &= ~mask; |
210 | edev->state |= state & mask; | 249 | edev->state |= state & mask; |
211 | 250 | ||
@@ -247,6 +286,8 @@ void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) | |||
247 | /* No changes */ | 286 | /* No changes */ |
248 | spin_unlock_irqrestore(&edev->lock, flags); | 287 | spin_unlock_irqrestore(&edev->lock, flags); |
249 | } | 288 | } |
289 | |||
290 | return 0; | ||
250 | } | 291 | } |
251 | EXPORT_SYMBOL_GPL(extcon_update_state); | 292 | EXPORT_SYMBOL_GPL(extcon_update_state); |
252 | 293 | ||
@@ -258,9 +299,9 @@ EXPORT_SYMBOL_GPL(extcon_update_state); | |||
258 | * Note that notifier provides which bits are changed in the state | 299 | * Note that notifier provides which bits are changed in the state |
259 | * variable with the val parameter (second) to the callback. | 300 | * variable with the val parameter (second) to the callback. |
260 | */ | 301 | */ |
261 | void extcon_set_state(struct extcon_dev *edev, u32 state) | 302 | int extcon_set_state(struct extcon_dev *edev, u32 state) |
262 | { | 303 | { |
263 | extcon_update_state(edev, 0xffffffff, state); | 304 | return extcon_update_state(edev, 0xffffffff, state); |
264 | } | 305 | } |
265 | EXPORT_SYMBOL_GPL(extcon_set_state); | 306 | EXPORT_SYMBOL_GPL(extcon_set_state); |
266 | 307 | ||
@@ -334,8 +375,7 @@ int extcon_set_cable_state_(struct extcon_dev *edev, | |||
334 | return -EINVAL; | 375 | return -EINVAL; |
335 | 376 | ||
336 | state = cable_state ? (1 << index) : 0; | 377 | state = cable_state ? (1 << index) : 0; |
337 | extcon_update_state(edev, 1 << index, state); | 378 | return extcon_update_state(edev, 1 << index, state); |
338 | return 0; | ||
339 | } | 379 | } |
340 | EXPORT_SYMBOL_GPL(extcon_set_cable_state_); | 380 | EXPORT_SYMBOL_GPL(extcon_set_cable_state_); |
341 | 381 | ||
@@ -511,6 +551,14 @@ static void extcon_cleanup(struct extcon_dev *edev, bool skip) | |||
511 | if (!skip && get_device(edev->dev)) { | 551 | if (!skip && get_device(edev->dev)) { |
512 | int index; | 552 | int index; |
513 | 553 | ||
554 | if (edev->mutually_exclusive && edev->max_supported) { | ||
555 | for (index = 0; edev->mutually_exclusive[index]; | ||
556 | index++) | ||
557 | kfree(edev->d_attrs_muex[index].attr.name); | ||
558 | kfree(edev->d_attrs_muex); | ||
559 | kfree(edev->attrs_muex); | ||
560 | } | ||
561 | |||
514 | for (index = 0; index < edev->max_supported; index++) | 562 | for (index = 0; index < edev->max_supported; index++) |
515 | kfree(edev->cables[index].attr_g.name); | 563 | kfree(edev->cables[index].attr_g.name); |
516 | 564 | ||
@@ -533,6 +581,7 @@ static void extcon_dev_release(struct device *dev) | |||
533 | extcon_cleanup(edev, true); | 581 | extcon_cleanup(edev, true); |
534 | } | 582 | } |
535 | 583 | ||
584 | static const char *muex_name = "mutually_exclusive"; | ||
536 | static void dummy_sysfs_dev_release(struct device *dev) | 585 | static void dummy_sysfs_dev_release(struct device *dev) |
537 | { | 586 | { |
538 | } | 587 | } |
@@ -625,10 +674,58 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) | |||
625 | } | 674 | } |
626 | } | 675 | } |
627 | 676 | ||
677 | if (edev->max_supported && edev->mutually_exclusive) { | ||
678 | char buf[80]; | ||
679 | char *name; | ||
680 | |||
681 | /* Count the size of mutually_exclusive array */ | ||
682 | for (index = 0; edev->mutually_exclusive[index]; index++) | ||
683 | ; | ||
684 | |||
685 | edev->attrs_muex = kzalloc(sizeof(struct attribute *) * | ||
686 | (index + 1), GFP_KERNEL); | ||
687 | if (!edev->attrs_muex) { | ||
688 | ret = -ENOMEM; | ||
689 | goto err_muex; | ||
690 | } | ||
691 | |||
692 | edev->d_attrs_muex = kzalloc(sizeof(struct device_attribute) * | ||
693 | index, GFP_KERNEL); | ||
694 | if (!edev->d_attrs_muex) { | ||
695 | ret = -ENOMEM; | ||
696 | kfree(edev->attrs_muex); | ||
697 | goto err_muex; | ||
698 | } | ||
699 | |||
700 | for (index = 0; edev->mutually_exclusive[index]; index++) { | ||
701 | sprintf(buf, "0x%x", edev->mutually_exclusive[index]); | ||
702 | name = kzalloc(sizeof(char) * (strlen(buf) + 1), | ||
703 | GFP_KERNEL); | ||
704 | if (!name) { | ||
705 | for (index--; index >= 0; index--) { | ||
706 | kfree(edev->d_attrs_muex[index].attr. | ||
707 | name); | ||
708 | } | ||
709 | kfree(edev->d_attrs_muex); | ||
710 | kfree(edev->attrs_muex); | ||
711 | ret = -ENOMEM; | ||
712 | goto err_muex; | ||
713 | } | ||
714 | strcpy(name, buf); | ||
715 | edev->d_attrs_muex[index].attr.name = name; | ||
716 | edev->d_attrs_muex[index].attr.mode = 0000; | ||
717 | edev->attrs_muex[index] = &edev->d_attrs_muex[index] | ||
718 | .attr; | ||
719 | } | ||
720 | edev->attr_g_muex.name = muex_name; | ||
721 | edev->attr_g_muex.attrs = edev->attrs_muex; | ||
722 | |||
723 | } | ||
724 | |||
628 | if (edev->max_supported) { | 725 | if (edev->max_supported) { |
629 | edev->extcon_dev_type.groups = | 726 | edev->extcon_dev_type.groups = |
630 | kzalloc(sizeof(struct attribute_group *) * | 727 | kzalloc(sizeof(struct attribute_group *) * |
631 | (edev->max_supported + 1), GFP_KERNEL); | 728 | (edev->max_supported + 2), GFP_KERNEL); |
632 | if (!edev->extcon_dev_type.groups) { | 729 | if (!edev->extcon_dev_type.groups) { |
633 | ret = -ENOMEM; | 730 | ret = -ENOMEM; |
634 | goto err_alloc_groups; | 731 | goto err_alloc_groups; |
@@ -640,6 +737,9 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) | |||
640 | for (index = 0; index < edev->max_supported; index++) | 737 | for (index = 0; index < edev->max_supported; index++) |
641 | edev->extcon_dev_type.groups[index] = | 738 | edev->extcon_dev_type.groups[index] = |
642 | &edev->cables[index].attr_g; | 739 | &edev->cables[index].attr_g; |
740 | if (edev->mutually_exclusive) | ||
741 | edev->extcon_dev_type.groups[index] = | ||
742 | &edev->attr_g_muex; | ||
643 | 743 | ||
644 | edev->dev->type = &edev->extcon_dev_type; | 744 | edev->dev->type = &edev->extcon_dev_type; |
645 | } | 745 | } |
@@ -672,6 +772,13 @@ err_dev: | |||
672 | if (edev->max_supported) | 772 | if (edev->max_supported) |
673 | kfree(edev->extcon_dev_type.groups); | 773 | kfree(edev->extcon_dev_type.groups); |
674 | err_alloc_groups: | 774 | err_alloc_groups: |
775 | if (edev->max_supported && edev->mutually_exclusive) { | ||
776 | for (index = 0; edev->mutually_exclusive[index]; index++) | ||
777 | kfree(edev->d_attrs_muex[index].attr.name); | ||
778 | kfree(edev->d_attrs_muex); | ||
779 | kfree(edev->attrs_muex); | ||
780 | } | ||
781 | err_muex: | ||
675 | for (index = 0; index < edev->max_supported; index++) | 782 | for (index = 0; index < edev->max_supported; index++) |
676 | kfree(edev->cables[index].attr_g.name); | 783 | kfree(edev->cables[index].attr_g.name); |
677 | err_alloc_cables: | 784 | err_alloc_cables: |
diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 20e24b32a17d..6495f7731400 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h | |||
@@ -78,6 +78,14 @@ struct extcon_cable; | |||
78 | * @supported_cable Array of supported cable name ending with NULL. | 78 | * @supported_cable Array of supported cable name ending with NULL. |
79 | * If supported_cable is NULL, cable name related APIs | 79 | * If supported_cable is NULL, cable name related APIs |
80 | * are disabled. | 80 | * are disabled. |
81 | * @mutually_exclusive Array of mutually exclusive set of cables that cannot | ||
82 | * be attached simultaneously. The array should be | ||
83 | * ending with NULL or be NULL (no mutually exclusive | ||
84 | * cables). For example, if it is { 0x7, 0x30, 0}, then, | ||
85 | * {0, 1}, {0, 1, 2}, {0, 2}, {1, 2}, or {4, 5} cannot | ||
86 | * be attached simulataneously. {0x7, 0} is equivalent to | ||
87 | * {0x3, 0x6, 0x5, 0}. If it is {0xFFFFFFFF, 0}, there | ||
88 | * can be no simultaneous connections. | ||
81 | * @print_name An optional callback to override the method to print the | 89 | * @print_name An optional callback to override the method to print the |
82 | * name of the extcon device. | 90 | * name of the extcon device. |
83 | * @print_state An optional callback to override the method to print the | 91 | * @print_state An optional callback to override the method to print the |
@@ -103,6 +111,7 @@ struct extcon_dev { | |||
103 | /* --- Optional user initializing data --- */ | 111 | /* --- Optional user initializing data --- */ |
104 | const char *name; | 112 | const char *name; |
105 | const char **supported_cable; | 113 | const char **supported_cable; |
114 | const u32 *mutually_exclusive; | ||
106 | 115 | ||
107 | /* --- Optional callbacks to override class functions --- */ | 116 | /* --- Optional callbacks to override class functions --- */ |
108 | ssize_t (*print_name)(struct extcon_dev *edev, char *buf); | 117 | ssize_t (*print_name)(struct extcon_dev *edev, char *buf); |
@@ -119,6 +128,10 @@ struct extcon_dev { | |||
119 | /* /sys/class/extcon/.../cable.n/... */ | 128 | /* /sys/class/extcon/.../cable.n/... */ |
120 | struct device_type extcon_dev_type; | 129 | struct device_type extcon_dev_type; |
121 | struct extcon_cable *cables; | 130 | struct extcon_cable *cables; |
131 | /* /sys/class/extcon/.../mutually_exclusive/... */ | ||
132 | struct attribute_group attr_g_muex; | ||
133 | struct attribute **attrs_muex; | ||
134 | struct device_attribute *d_attrs_muex; | ||
122 | }; | 135 | }; |
123 | 136 | ||
124 | /** | 137 | /** |
@@ -179,8 +192,8 @@ static inline u32 extcon_get_state(struct extcon_dev *edev) | |||
179 | return edev->state; | 192 | return edev->state; |
180 | } | 193 | } |
181 | 194 | ||
182 | extern void extcon_set_state(struct extcon_dev *edev, u32 state); | 195 | extern int extcon_set_state(struct extcon_dev *edev, u32 state); |
183 | extern void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state); | 196 | extern int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state); |
184 | 197 | ||
185 | /* | 198 | /* |
186 | * get/set_cable_state access each bit of the 32b encoded state value. | 199 | * get/set_cable_state access each bit of the 32b encoded state value. |
@@ -235,11 +248,16 @@ static inline u32 extcon_get_state(struct extcon_dev *edev) | |||
235 | return 0; | 248 | return 0; |
236 | } | 249 | } |
237 | 250 | ||
238 | static inline void extcon_set_state(struct extcon_dev *edev, u32 state) { } | 251 | static inline int extcon_set_state(struct extcon_dev *edev, u32 state) |
252 | { | ||
253 | return 0; | ||
254 | } | ||
239 | 255 | ||
240 | static inline void extcon_update_state(struct extcon_dev *edev, u32 mask, | 256 | static inline int extcon_update_state(struct extcon_dev *edev, u32 mask, |
241 | u32 state) | 257 | u32 state) |
242 | { } | 258 | { |
259 | return 0; | ||
260 | } | ||
243 | 261 | ||
244 | static inline int extcon_find_cable_index(struct extcon_dev *edev, | 262 | static inline int extcon_find_cable_index(struct extcon_dev *edev, |
245 | const char *cable_name) | 263 | const char *cable_name) |