Extcon: support mutually exclusive relation between cables.
authorMyungJoo Ham <myungjoo.ham@samsung.com>
Fri, 20 Apr 2012 05:16:26 +0000 (14:16 +0900)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 20 Apr 2012 16:24:03 +0000 (09:24 -0700)
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>
Documentation/ABI/testing/sysfs-class-extcon
drivers/extcon/extcon_class.c
include/linux/extcon.h

index 19b18e9..20ab361 100644 (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.
index 403933b..3bc4b8a 100644 (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 @@ err_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:
index 20e24b3..6495f77 100644 (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)