Skip to content

Commit

Permalink
Extcon: support mutually exclusive relation between cables.
Browse files Browse the repository at this point in the history
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 <[email protected]>

Signed-off-by: MyungJoo Ham <[email protected]>
Signed-off-by: Kyungmin Park <[email protected]>

--
Changes from V5:
- Updated sysfs format
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
myungjoo authored and gregkh committed Apr 20, 2012
1 parent 806d9dd commit bde68e6
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 13 deletions.
22 changes: 22 additions & 0 deletions Documentation/ABI/testing/sysfs-class-extcon
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>
Expand Down Expand Up @@ -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 <[email protected]>
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.
123 changes: 115 additions & 8 deletions drivers/extcon/extcon_class.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down Expand Up @@ -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];
Expand All @@ -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;

Expand Down Expand Up @@ -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);

Expand All @@ -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);

Expand Down Expand Up @@ -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_);

Expand Down Expand Up @@ -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);

Expand All @@ -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)
{
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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:
Expand Down
28 changes: 23 additions & 5 deletions include/linux/extcon.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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;
};

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit bde68e6

Please sign in to comment.