Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bde68e60 authored by MyungJoo Ham's avatar MyungJoo Ham Committed by Greg Kroah-Hartman
Browse files

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: default avatarMyungJoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: default avatarKyungmin Park <kyungmin.park@samsung.com>

--
Changes from V5:
- Updated sysfs format
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 806d9dd7
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -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.
+115 −8
Original line number Diff line number Diff line
@@ -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:
+23 −5
Original line number Diff line number Diff line
@@ -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)