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>
This commit is contained in:
MyungJoo Ham 2012-04-20 14:16:26 +09:00 committed by Greg Kroah-Hartman
parent 806d9dd71f
commit bde68e60b1
3 changed files with 160 additions and 13 deletions

View file

@ -15,6 +15,10 @@ Description:
may have both HDMI and Charger attached, or analog audio,
video, and USB cables attached simulteneously.
If there are cables mutually exclusive with each other,
such binary relations may be expressed with extcon_dev's
mutually_exclusive array.
What: /sys/class/extcon/.../name
Date: February 2012
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
@ -73,3 +77,21 @@ Description:
state of cable "x" (integer between 0 and 31) of an extcon
device. The state value is either 0 (detached) or 1
(attached).
What: /sys/class/extcon/.../mutually_exclusive/...
Date: December 2011
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
Description:
Shows the relations of mutually exclusiveness. For example,
if the mutually_exclusive array of extcon_dev is
{0x3, 0x5, 0xC, 0x0}, the, the output is:
# ls mutually_exclusive/
0x3
0x5
0xc
#
Note that mutually_exclusive is a sub-directory of the extcon
device and the file names under the mutually_exclusive
directory show the mutually-exclusive sets, not the contents
of the files.

View file

@ -72,6 +72,39 @@ static struct class_compat *switch_class;
static LIST_HEAD(extcon_dev_list);
static DEFINE_MUTEX(extcon_dev_list_lock);
/**
* check_mutually_exclusive - Check if new_state violates mutually_exclusive
* condition.
* @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++) {
int count = 0, j;
u32 correspondants = new_state & edev->mutually_exclusive[i];
u32 exp = 1;
for (j = 0; j < 32; j++) {
if (exp & correspondants)
count++;
if (count > 1)
return i + 1;
exp <<= 1;
}
}
return 0;
}
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@ -100,7 +133,7 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr,
return count;
}
void extcon_set_state(struct extcon_dev *edev, u32 state);
int extcon_set_state(struct extcon_dev *edev, u32 state);
static ssize_t state_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
@ -112,7 +145,7 @@ static ssize_t state_store(struct device *dev, struct device_attribute *attr,
if (ret == 0)
ret = -EINVAL;
else
extcon_set_state(edev, state);
ret = extcon_set_state(edev, state);
if (ret < 0)
return ret;
@ -191,7 +224,7 @@ static ssize_t cable_state_store(struct device *dev,
* Note that the notifier provides which bits are changed in the state
* variable with the val parameter (second) to the callback.
*/
void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state)
int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state)
{
char name_buf[120];
char state_buf[120];
@ -206,6 +239,12 @@ void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state)
if (edev->state != ((edev->state & ~mask) | (state & mask))) {
u32 old_state = edev->state;
if (check_mutually_exclusive(edev, (edev->state & ~mask) |
(state & mask))) {
spin_unlock_irqrestore(&edev->lock, flags);
return -EPERM;
}
edev->state &= ~mask;
edev->state |= state & mask;
@ -247,6 +286,8 @@ void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state)
/* No changes */
spin_unlock_irqrestore(&edev->lock, flags);
}
return 0;
}
EXPORT_SYMBOL_GPL(extcon_update_state);
@ -258,9 +299,9 @@ EXPORT_SYMBOL_GPL(extcon_update_state);
* Note that notifier provides which bits are changed in the state
* variable with the val parameter (second) to the callback.
*/
void extcon_set_state(struct extcon_dev *edev, u32 state)
int extcon_set_state(struct extcon_dev *edev, u32 state)
{
extcon_update_state(edev, 0xffffffff, state);
return extcon_update_state(edev, 0xffffffff, state);
}
EXPORT_SYMBOL_GPL(extcon_set_state);
@ -334,8 +375,7 @@ int extcon_set_cable_state_(struct extcon_dev *edev,
return -EINVAL;
state = cable_state ? (1 << index) : 0;
extcon_update_state(edev, 1 << index, state);
return 0;
return extcon_update_state(edev, 1 << index, state);
}
EXPORT_SYMBOL_GPL(extcon_set_cable_state_);
@ -511,6 +551,14 @@ static void extcon_cleanup(struct extcon_dev *edev, bool skip)
if (!skip && get_device(edev->dev)) {
int index;
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);
@ -533,6 +581,7 @@ static void extcon_dev_release(struct device *dev)
extcon_cleanup(edev, true);
}
static const char *muex_name = "mutually_exclusive";
static void dummy_sysfs_dev_release(struct device *dev)
{
}
@ -625,10 +674,58 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev)
}
}
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);
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;
}
if (edev->max_supported) {
edev->extcon_dev_type.groups =
kzalloc(sizeof(struct attribute_group *) *
(edev->max_supported + 1), GFP_KERNEL);
(edev->max_supported + 2), GFP_KERNEL);
if (!edev->extcon_dev_type.groups) {
ret = -ENOMEM;
goto err_alloc_groups;
@ -640,6 +737,9 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev)
for (index = 0; index < edev->max_supported; index++)
edev->extcon_dev_type.groups[index] =
&edev->cables[index].attr_g;
if (edev->mutually_exclusive)
edev->extcon_dev_type.groups[index] =
&edev->attr_g_muex;
edev->dev->type = &edev->extcon_dev_type;
}
@ -672,6 +772,13 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev)
if (edev->max_supported)
kfree(edev->extcon_dev_type.groups);
err_alloc_groups:
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:
for (index = 0; index < edev->max_supported; index++)
kfree(edev->cables[index].attr_g.name);
err_alloc_cables:

View file

@ -78,6 +78,14 @@ struct extcon_cable;
* @supported_cable Array of supported cable name ending with NULL.
* If supported_cable is NULL, cable name related APIs
* are disabled.
* @mutually_exclusive Array of mutually exclusive set of cables that cannot
* be attached simultaneously. The array should be
* ending with NULL or be NULL (no mutually exclusive
* cables). For example, if it is { 0x7, 0x30, 0}, then,
* {0, 1}, {0, 1, 2}, {0, 2}, {1, 2}, or {4, 5} cannot
* be attached simulataneously. {0x7, 0} is equivalent to
* {0x3, 0x6, 0x5, 0}. If it is {0xFFFFFFFF, 0}, there
* can be no simultaneous connections.
* @print_name An optional callback to override the method to print the
* name of the extcon device.
* @print_state An optional callback to override the method to print the
@ -103,6 +111,7 @@ struct extcon_dev {
/* --- Optional user initializing data --- */
const char *name;
const char **supported_cable;
const u32 *mutually_exclusive;
/* --- Optional callbacks to override class functions --- */
ssize_t (*print_name)(struct extcon_dev *edev, char *buf);
@ -119,6 +128,10 @@ struct extcon_dev {
/* /sys/class/extcon/.../cable.n/... */
struct device_type extcon_dev_type;
struct extcon_cable *cables;
/* /sys/class/extcon/.../mutually_exclusive/... */
struct attribute_group attr_g_muex;
struct attribute **attrs_muex;
struct device_attribute *d_attrs_muex;
};
/**
@ -179,8 +192,8 @@ static inline u32 extcon_get_state(struct extcon_dev *edev)
return edev->state;
}
extern void extcon_set_state(struct extcon_dev *edev, u32 state);
extern void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state);
extern int extcon_set_state(struct extcon_dev *edev, u32 state);
extern int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state);
/*
* 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)
return 0;
}
static inline void extcon_set_state(struct extcon_dev *edev, u32 state) { }
static inline int extcon_set_state(struct extcon_dev *edev, u32 state)
{
return 0;
}
static inline void extcon_update_state(struct extcon_dev *edev, u32 mask,
static inline int extcon_update_state(struct extcon_dev *edev, u32 mask,
u32 state)
{ }
{
return 0;
}
static inline int extcon_find_cable_index(struct extcon_dev *edev,
const char *cable_name)