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:
committed by
Greg Kroah-Hartman
parent
806d9dd71f
commit
bde68e60b1
@@ -15,6 +15,10 @@ Description:
|
|||||||
may have both HDMI and Charger attached, or analog audio,
|
may have both HDMI and Charger attached, or analog audio,
|
||||||
video, and USB cables attached simulteneously.
|
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
|
What: /sys/class/extcon/.../name
|
||||||
Date: February 2012
|
Date: February 2012
|
||||||
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||||
@@ -73,3 +77,21 @@ Description:
|
|||||||
state of cable "x" (integer between 0 and 31) of an extcon
|
state of cable "x" (integer between 0 and 31) of an extcon
|
||||||
device. The state value is either 0 (detached) or 1
|
device. The state value is either 0 (detached) or 1
|
||||||
(attached).
|
(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.
|
||||||
|
@@ -72,6 +72,39 @@ static struct class_compat *switch_class;
|
|||||||
static LIST_HEAD(extcon_dev_list);
|
static LIST_HEAD(extcon_dev_list);
|
||||||
static DEFINE_MUTEX(extcon_dev_list_lock);
|
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,
|
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
@@ -100,7 +133,7 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr,
|
|||||||
return count;
|
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,
|
static ssize_t state_store(struct device *dev, struct device_attribute *attr,
|
||||||
const char *buf, size_t count)
|
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)
|
if (ret == 0)
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
else
|
else
|
||||||
extcon_set_state(edev, state);
|
ret = extcon_set_state(edev, state);
|
||||||
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
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
|
* Note that the notifier provides which bits are changed in the state
|
||||||
* variable with the val parameter (second) to the callback.
|
* 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 name_buf[120];
|
||||||
char state_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))) {
|
if (edev->state != ((edev->state & ~mask) | (state & mask))) {
|
||||||
u32 old_state = edev->state;
|
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 &= ~mask;
|
||||||
edev->state |= state & mask;
|
edev->state |= state & mask;
|
||||||
|
|
||||||
@@ -247,6 +286,8 @@ void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state)
|
|||||||
/* No changes */
|
/* No changes */
|
||||||
spin_unlock_irqrestore(&edev->lock, flags);
|
spin_unlock_irqrestore(&edev->lock, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(extcon_update_state);
|
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
|
* Note that notifier provides which bits are changed in the state
|
||||||
* variable with the val parameter (second) to the callback.
|
* 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);
|
EXPORT_SYMBOL_GPL(extcon_set_state);
|
||||||
|
|
||||||
@@ -334,8 +375,7 @@ int extcon_set_cable_state_(struct extcon_dev *edev,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
state = cable_state ? (1 << index) : 0;
|
state = cable_state ? (1 << index) : 0;
|
||||||
extcon_update_state(edev, 1 << index, state);
|
return extcon_update_state(edev, 1 << index, state);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(extcon_set_cable_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)) {
|
if (!skip && get_device(edev->dev)) {
|
||||||
int index;
|
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++)
|
for (index = 0; index < edev->max_supported; index++)
|
||||||
kfree(edev->cables[index].attr_g.name);
|
kfree(edev->cables[index].attr_g.name);
|
||||||
|
|
||||||
@@ -533,6 +581,7 @@ static void extcon_dev_release(struct device *dev)
|
|||||||
extcon_cleanup(edev, true);
|
extcon_cleanup(edev, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *muex_name = "mutually_exclusive";
|
||||||
static void dummy_sysfs_dev_release(struct device *dev)
|
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) {
|
if (edev->max_supported) {
|
||||||
edev->extcon_dev_type.groups =
|
edev->extcon_dev_type.groups =
|
||||||
kzalloc(sizeof(struct attribute_group *) *
|
kzalloc(sizeof(struct attribute_group *) *
|
||||||
(edev->max_supported + 1), GFP_KERNEL);
|
(edev->max_supported + 2), GFP_KERNEL);
|
||||||
if (!edev->extcon_dev_type.groups) {
|
if (!edev->extcon_dev_type.groups) {
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto err_alloc_groups;
|
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++)
|
for (index = 0; index < edev->max_supported; index++)
|
||||||
edev->extcon_dev_type.groups[index] =
|
edev->extcon_dev_type.groups[index] =
|
||||||
&edev->cables[index].attr_g;
|
&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;
|
edev->dev->type = &edev->extcon_dev_type;
|
||||||
}
|
}
|
||||||
@@ -672,6 +772,13 @@ err_dev:
|
|||||||
if (edev->max_supported)
|
if (edev->max_supported)
|
||||||
kfree(edev->extcon_dev_type.groups);
|
kfree(edev->extcon_dev_type.groups);
|
||||||
err_alloc_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++)
|
for (index = 0; index < edev->max_supported; index++)
|
||||||
kfree(edev->cables[index].attr_g.name);
|
kfree(edev->cables[index].attr_g.name);
|
||||||
err_alloc_cables:
|
err_alloc_cables:
|
||||||
|
@@ -78,6 +78,14 @@ struct extcon_cable;
|
|||||||
* @supported_cable Array of supported cable name ending with NULL.
|
* @supported_cable Array of supported cable name ending with NULL.
|
||||||
* If supported_cable is NULL, cable name related APIs
|
* If supported_cable is NULL, cable name related APIs
|
||||||
* are disabled.
|
* 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
|
* @print_name An optional callback to override the method to print the
|
||||||
* name of the extcon device.
|
* name of the extcon device.
|
||||||
* @print_state An optional callback to override the method to print the
|
* @print_state An optional callback to override the method to print the
|
||||||
@@ -103,6 +111,7 @@ struct extcon_dev {
|
|||||||
/* --- Optional user initializing data --- */
|
/* --- Optional user initializing data --- */
|
||||||
const char *name;
|
const char *name;
|
||||||
const char **supported_cable;
|
const char **supported_cable;
|
||||||
|
const u32 *mutually_exclusive;
|
||||||
|
|
||||||
/* --- Optional callbacks to override class functions --- */
|
/* --- Optional callbacks to override class functions --- */
|
||||||
ssize_t (*print_name)(struct extcon_dev *edev, char *buf);
|
ssize_t (*print_name)(struct extcon_dev *edev, char *buf);
|
||||||
@@ -119,6 +128,10 @@ struct extcon_dev {
|
|||||||
/* /sys/class/extcon/.../cable.n/... */
|
/* /sys/class/extcon/.../cable.n/... */
|
||||||
struct device_type extcon_dev_type;
|
struct device_type extcon_dev_type;
|
||||||
struct extcon_cable *cables;
|
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;
|
return edev->state;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void extcon_set_state(struct extcon_dev *edev, u32 state);
|
extern int 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_update_state(struct extcon_dev *edev, u32 mask, u32 state);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get/set_cable_state access each bit of the 32b encoded state value.
|
* 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;
|
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)
|
u32 state)
|
||||||
{ }
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static inline int extcon_find_cable_index(struct extcon_dev *edev,
|
static inline int extcon_find_cable_index(struct extcon_dev *edev,
|
||||||
const char *cable_name)
|
const char *cable_name)
|
||||||
|
Reference in New Issue
Block a user