Extcon: support multiple states at a device.
One switch device (e.g., MUIC(MAX8997, MAX77686, ...), and some 30-pin devices) may have multiple cables attached. For example, one 30-pin port may inhabit a USB cable, an HDMI cable, and a mic. Thus, one switch device requires multiple state bits each representing a type of cable. For such purpose, we use the 32bit state variable; thus, up to 32 different type of cables may be defined for a switch device. The list of possible cables is defined by the array of cable names in the switch_dev struct given to the class. Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com> Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> -- Changes from V7 - Bugfixed in _call_per_cable() (incorrect nb) (Chanwoo Choi) - Compiler error in header for !CONFIG_EXTCON (Chanwoo Choi) Changes from V5 - Sysfs style reformed: subdirectory per cable. - Updated standard cable names - Removed unnecessary printf - Bugfixes after testing Changes from V4 - Bugfixes after more testing at Exynos4412 boards with userspace processses. Changes from V3 - Bugfixes after more testing at Exynos4412 boards. Changes from V2 - State can be stored by user - Documentation updated Changes from RFC - Switch is renamed to extcon - Added kerneldoc comments - Added APIs to support "standard" cable names - Added helper APIs to support notifier block registration with cable name. - Regrouped function list in the header file. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
74c5d09bd5
commit
806d9dd71f
|
@ -1,5 +1,5 @@
|
|||
What: /sys/class/extcon/.../
|
||||
Date: December 2011
|
||||
Date: February 2012
|
||||
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
Description:
|
||||
Provide a place in sysfs for the extcon objects.
|
||||
|
@ -7,8 +7,16 @@ Description:
|
|||
The name of extcon object denoted as ... is the name given
|
||||
with extcon_dev_register.
|
||||
|
||||
One extcon device denotes a single external connector
|
||||
port. An external connector may have multiple cables
|
||||
attached simultaneously. Many of docks, cradles, and
|
||||
accessory cables have such capability. For example,
|
||||
the 30-pin port of Nuri board (/arch/arm/mach-exynos)
|
||||
may have both HDMI and Charger attached, or analog audio,
|
||||
video, and USB cables attached simulteneously.
|
||||
|
||||
What: /sys/class/extcon/.../name
|
||||
Date: December 2011
|
||||
Date: February 2012
|
||||
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
Description:
|
||||
The /sys/class/extcon/.../name shows the name of the extcon
|
||||
|
@ -17,10 +25,51 @@ Description:
|
|||
this sysfs node.
|
||||
|
||||
What: /sys/class/extcon/.../state
|
||||
Date: December 2011
|
||||
Date: February 2012
|
||||
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
Description:
|
||||
The /sys/class/extcon/.../state shows the cable attach/detach
|
||||
information of the corresponding extcon object. If the extcon
|
||||
objecct has an optional callback "show_state" defined, the
|
||||
callback will provide the name with this sysfs node.
|
||||
The /sys/class/extcon/.../state shows and stores the cable
|
||||
attach/detach information of the corresponding extcon object.
|
||||
If the extcon object has an optional callback "show_state"
|
||||
defined, the showing function is overriden with the optional
|
||||
callback.
|
||||
|
||||
If the default callback for showing function is used, the
|
||||
format is like this:
|
||||
# cat state
|
||||
USB_OTG=1
|
||||
HDMI=0
|
||||
TA=1
|
||||
EAR_JACK=0
|
||||
#
|
||||
In this example, the extcon device have USB_OTG and TA
|
||||
cables attached and HDMI and EAR_JACK cables detached.
|
||||
|
||||
In order to update the state of an extcon device, enter a hex
|
||||
state number starting with 0x.
|
||||
echo 0xHEX > state
|
||||
|
||||
This updates the whole state of the extcon dev.
|
||||
Inputs of all the methods are required to meet the
|
||||
mutually_exclusive contidions if they exist.
|
||||
|
||||
It is recommended to use this "global" state interface if
|
||||
you need to enter the value atomically. The later state
|
||||
interface associated with each cable cannot update
|
||||
multiple cable states of an extcon device simultaneously.
|
||||
|
||||
What: /sys/class/extcon/.../cable.x/name
|
||||
Date: February 2012
|
||||
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
Description:
|
||||
The /sys/class/extcon/.../cable.x/name shows the name of cable
|
||||
"x" (integer between 0 and 31) of an extcon device.
|
||||
|
||||
What: /sys/class/extcon/.../cable.x/state
|
||||
Date: February 2012
|
||||
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
Description:
|
||||
The /sys/class/extcon/.../cable.x/name shows and stores the
|
||||
state of cable "x" (integer between 0 and 31) of an extcon
|
||||
device. The state value is either 0 (detached) or 1
|
||||
(attached).
|
||||
|
|
|
@ -31,6 +31,39 @@
|
|||
#include <linux/extcon.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/*
|
||||
* extcon_cable_name suggests the standard cable names for commonly used
|
||||
* cable types.
|
||||
*
|
||||
* However, please do not use extcon_cable_name directly for extcon_dev
|
||||
* struct's supported_cable pointer unless your device really supports
|
||||
* every single port-type of the following cable names. Please choose cable
|
||||
* names that are actually used in your extcon device.
|
||||
*/
|
||||
const char *extcon_cable_name[] = {
|
||||
[EXTCON_USB] = "USB",
|
||||
[EXTCON_USB_HOST] = "USB-Host",
|
||||
[EXTCON_TA] = "TA",
|
||||
[EXTCON_FAST_CHARGER] = "Fast-charger",
|
||||
[EXTCON_SLOW_CHARGER] = "Slow-charger",
|
||||
[EXTCON_CHARGE_DOWNSTREAM] = "Charge-downstream",
|
||||
[EXTCON_HDMI] = "HDMI",
|
||||
[EXTCON_MHL] = "MHL",
|
||||
[EXTCON_DVI] = "DVI",
|
||||
[EXTCON_VGA] = "VGA",
|
||||
[EXTCON_DOCK] = "Dock",
|
||||
[EXTCON_LINE_IN] = "Line-in",
|
||||
[EXTCON_LINE_OUT] = "Line-out",
|
||||
[EXTCON_MIC_IN] = "Microphone",
|
||||
[EXTCON_HEADPHONE_OUT] = "Headphone",
|
||||
[EXTCON_SPDIF_IN] = "SPDIF-in",
|
||||
[EXTCON_SPDIF_OUT] = "SPDIF-out",
|
||||
[EXTCON_VIDEO_IN] = "Video-in",
|
||||
[EXTCON_VIDEO_OUT] = "Video-out",
|
||||
|
||||
NULL,
|
||||
};
|
||||
|
||||
struct class *extcon_class;
|
||||
#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH)
|
||||
static struct class_compat *switch_class;
|
||||
|
@ -42,6 +75,7 @@ static DEFINE_MUTEX(extcon_dev_list_lock);
|
|||
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int i, count = 0;
|
||||
struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev);
|
||||
|
||||
if (edev->print_state) {
|
||||
|
@ -51,7 +85,39 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr,
|
|||
return ret;
|
||||
/* Use default if failed */
|
||||
}
|
||||
return sprintf(buf, "%u\n", edev->state);
|
||||
|
||||
if (edev->max_supported == 0)
|
||||
return sprintf(buf, "%u\n", edev->state);
|
||||
|
||||
for (i = 0; i < SUPPORTED_CABLE_MAX; i++) {
|
||||
if (!edev->supported_cable[i])
|
||||
break;
|
||||
count += sprintf(buf + count, "%s=%d\n",
|
||||
edev->supported_cable[i],
|
||||
!!(edev->state & (1 << i)));
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void 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)
|
||||
{
|
||||
u32 state;
|
||||
ssize_t ret = 0;
|
||||
struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev);
|
||||
|
||||
ret = sscanf(buf, "0x%x", &state);
|
||||
if (ret == 0)
|
||||
ret = -EINVAL;
|
||||
else
|
||||
extcon_set_state(edev, state);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
|
||||
|
@ -69,9 +135,52 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr,
|
|||
return sprintf(buf, "%s\n", dev_name(edev->dev));
|
||||
}
|
||||
|
||||
static ssize_t cable_name_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct extcon_cable *cable = container_of(attr, struct extcon_cable,
|
||||
attr_name);
|
||||
|
||||
return sprintf(buf, "%s\n",
|
||||
cable->edev->supported_cable[cable->cable_index]);
|
||||
}
|
||||
|
||||
static ssize_t cable_state_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct extcon_cable *cable = container_of(attr, struct extcon_cable,
|
||||
attr_state);
|
||||
|
||||
return sprintf(buf, "%d\n",
|
||||
extcon_get_cable_state_(cable->edev,
|
||||
cable->cable_index));
|
||||
}
|
||||
|
||||
static ssize_t cable_state_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct extcon_cable *cable = container_of(attr, struct extcon_cable,
|
||||
attr_state);
|
||||
int ret, state;
|
||||
|
||||
ret = sscanf(buf, "%d", &state);
|
||||
if (ret == 0)
|
||||
ret = -EINVAL;
|
||||
else
|
||||
ret = extcon_set_cable_state_(cable->edev, cable->cable_index,
|
||||
state);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* extcon_set_state() - Set the cable attach states of the extcon device.
|
||||
* extcon_update_state() - Update the cable attach states of the extcon device
|
||||
* only for the masked bits.
|
||||
* @edev: the extcon device
|
||||
* @mask: the bit mask to designate updated bits.
|
||||
* @state: new cable attach status for @edev
|
||||
*
|
||||
* Changing the state sends uevent with environment variable containing
|
||||
|
@ -79,10 +188,10 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr,
|
|||
* Tizen uses this format for extcon device to get events from ports.
|
||||
* Android uses this format as well.
|
||||
*
|
||||
* Note that notifier provides the which bits are changes in the state
|
||||
* variable with "val" to the callback.
|
||||
* Note that the 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)
|
||||
void extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state)
|
||||
{
|
||||
char name_buf[120];
|
||||
char state_buf[120];
|
||||
|
@ -90,15 +199,20 @@ void extcon_set_state(struct extcon_dev *edev, u32 state)
|
|||
char *envp[3];
|
||||
int env_offset = 0;
|
||||
int length;
|
||||
u32 old_state = edev->state;
|
||||
unsigned long flags;
|
||||
|
||||
if (edev->state != state) {
|
||||
edev->state = state;
|
||||
spin_lock_irqsave(&edev->lock, flags);
|
||||
|
||||
raw_notifier_call_chain(&edev->nh, old_state ^ edev->state,
|
||||
edev);
|
||||
if (edev->state != ((edev->state & ~mask) | (state & mask))) {
|
||||
u32 old_state = edev->state;
|
||||
|
||||
prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
|
||||
edev->state &= ~mask;
|
||||
edev->state |= state & mask;
|
||||
|
||||
raw_notifier_call_chain(&edev->nh, old_state, edev);
|
||||
|
||||
/* This could be in interrupt handler */
|
||||
prop_buf = (char *)get_zeroed_page(GFP_ATOMIC);
|
||||
if (prop_buf) {
|
||||
length = name_show(edev->dev, NULL, prop_buf);
|
||||
if (length > 0) {
|
||||
|
@ -117,16 +231,131 @@ void extcon_set_state(struct extcon_dev *edev, u32 state)
|
|||
envp[env_offset++] = state_buf;
|
||||
}
|
||||
envp[env_offset] = NULL;
|
||||
/* Unlock early before uevent */
|
||||
spin_unlock_irqrestore(&edev->lock, flags);
|
||||
|
||||
kobject_uevent_env(&edev->dev->kobj, KOBJ_CHANGE, envp);
|
||||
free_page((unsigned long)prop_buf);
|
||||
} else {
|
||||
/* Unlock early before uevent */
|
||||
spin_unlock_irqrestore(&edev->lock, flags);
|
||||
|
||||
dev_err(edev->dev, "out of memory in extcon_set_state\n");
|
||||
kobject_uevent(&edev->dev->kobj, KOBJ_CHANGE);
|
||||
}
|
||||
} else {
|
||||
/* No changes */
|
||||
spin_unlock_irqrestore(&edev->lock, flags);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(extcon_update_state);
|
||||
|
||||
/**
|
||||
* extcon_set_state() - Set the cable attach states of the extcon device.
|
||||
* @edev: the extcon device
|
||||
* @state: new cable attach status for @edev
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
extcon_update_state(edev, 0xffffffff, state);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(extcon_set_state);
|
||||
|
||||
/**
|
||||
* extcon_find_cable_index() - Get the cable index based on the cable name.
|
||||
* @edev: the extcon device that has the cable.
|
||||
* @cable_name: cable name to be searched.
|
||||
*
|
||||
* Note that accessing a cable state based on cable_index is faster than
|
||||
* cable_name because using cable_name induces a loop with strncmp().
|
||||
* Thus, when get/set_cable_state is repeatedly used, using cable_index
|
||||
* is recommended.
|
||||
*/
|
||||
int extcon_find_cable_index(struct extcon_dev *edev, const char *cable_name)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (edev->supported_cable) {
|
||||
for (i = 0; edev->supported_cable[i]; i++) {
|
||||
if (!strncmp(edev->supported_cable[i],
|
||||
cable_name, CABLE_NAME_MAX))
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(extcon_find_cable_index);
|
||||
|
||||
/**
|
||||
* extcon_get_cable_state_() - Get the status of a specific cable.
|
||||
* @edev: the extcon device that has the cable.
|
||||
* @index: cable index that can be retrieved by extcon_find_cable_index().
|
||||
*/
|
||||
int extcon_get_cable_state_(struct extcon_dev *edev, int index)
|
||||
{
|
||||
if (index < 0 || (edev->max_supported && edev->max_supported <= index))
|
||||
return -EINVAL;
|
||||
|
||||
return !!(edev->state & (1 << index));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(extcon_get_cable_state_);
|
||||
|
||||
/**
|
||||
* extcon_get_cable_state() - Get the status of a specific cable.
|
||||
* @edev: the extcon device that has the cable.
|
||||
* @cable_name: cable name.
|
||||
*
|
||||
* Note that this is slower than extcon_get_cable_state_.
|
||||
*/
|
||||
int extcon_get_cable_state(struct extcon_dev *edev, const char *cable_name)
|
||||
{
|
||||
return extcon_get_cable_state_(edev, extcon_find_cable_index
|
||||
(edev, cable_name));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(extcon_get_cable_state);
|
||||
|
||||
/**
|
||||
* extcon_get_cable_state_() - Set the status of a specific cable.
|
||||
* @edev: the extcon device that has the cable.
|
||||
* @index: cable index that can be retrieved by extcon_find_cable_index().
|
||||
* @cable_state: the new cable status. The default semantics is
|
||||
* true: attached / false: detached.
|
||||
*/
|
||||
int extcon_set_cable_state_(struct extcon_dev *edev,
|
||||
int index, bool cable_state)
|
||||
{
|
||||
u32 state;
|
||||
|
||||
if (index < 0 || (edev->max_supported && edev->max_supported <= index))
|
||||
return -EINVAL;
|
||||
|
||||
state = cable_state ? (1 << index) : 0;
|
||||
extcon_update_state(edev, 1 << index, state);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(extcon_set_cable_state_);
|
||||
|
||||
/**
|
||||
* extcon_get_cable_state() - Set the status of a specific cable.
|
||||
* @edev: the extcon device that has the cable.
|
||||
* @cable_name: cable name.
|
||||
* @cable_state: the new cable status. The default semantics is
|
||||
* true: attached / false: detached.
|
||||
*
|
||||
* Note that this is slower than extcon_set_cable_state_.
|
||||
*/
|
||||
int extcon_set_cable_state(struct extcon_dev *edev,
|
||||
const char *cable_name, bool cable_state)
|
||||
{
|
||||
return extcon_set_cable_state_(edev, extcon_find_cable_index
|
||||
(edev, cable_name), cable_state);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(extcon_set_cable_state);
|
||||
|
||||
/**
|
||||
* extcon_get_extcon_dev() - Get the extcon device instance from the name
|
||||
* @extcon_name: The extcon name provided with extcon_dev_register()
|
||||
|
@ -147,11 +376,88 @@ out:
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(extcon_get_extcon_dev);
|
||||
|
||||
static int _call_per_cable(struct notifier_block *nb, unsigned long val,
|
||||
void *ptr)
|
||||
{
|
||||
struct extcon_specific_cable_nb *obj = container_of(nb,
|
||||
struct extcon_specific_cable_nb, internal_nb);
|
||||
struct extcon_dev *edev = ptr;
|
||||
|
||||
if ((val & (1 << obj->cable_index)) !=
|
||||
(edev->state & (1 << obj->cable_index))) {
|
||||
obj->previous_value = val;
|
||||
return obj->user_nb->notifier_call(obj->user_nb, val, ptr);
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* extcon_register_interest() - Register a notifier for a state change of a
|
||||
* specific cable, not a entier set of cables of a
|
||||
* extcon device.
|
||||
* @obj: an empty extcon_specific_cable_nb object to be returned.
|
||||
* @extcon_name: the name of extcon device.
|
||||
* @cable_name: the target cable name.
|
||||
* @nb: the notifier block to get notified.
|
||||
*
|
||||
* Provide an empty extcon_specific_cable_nb. extcon_register_interest() sets
|
||||
* the struct for you.
|
||||
*
|
||||
* extcon_register_interest is a helper function for those who want to get
|
||||
* notification for a single specific cable's status change. If a user wants
|
||||
* to get notification for any changes of all cables of a extcon device,
|
||||
* he/she should use the general extcon_register_notifier().
|
||||
*
|
||||
* Note that the second parameter given to the callback of nb (val) is
|
||||
* "old_state", not the current state. The current state can be retrieved
|
||||
* by looking at the third pameter (edev pointer)'s state value.
|
||||
*/
|
||||
int extcon_register_interest(struct extcon_specific_cable_nb *obj,
|
||||
const char *extcon_name, const char *cable_name,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
if (!obj || !extcon_name || !cable_name || !nb)
|
||||
return -EINVAL;
|
||||
|
||||
obj->edev = extcon_get_extcon_dev(extcon_name);
|
||||
if (!obj->edev)
|
||||
return -ENODEV;
|
||||
|
||||
obj->cable_index = extcon_find_cable_index(obj->edev, cable_name);
|
||||
if (obj->cable_index < 0)
|
||||
return -ENODEV;
|
||||
|
||||
obj->user_nb = nb;
|
||||
|
||||
obj->internal_nb.notifier_call = _call_per_cable;
|
||||
|
||||
return raw_notifier_chain_register(&obj->edev->nh, &obj->internal_nb);
|
||||
}
|
||||
|
||||
/**
|
||||
* extcon_unregister_interest() - Unregister the notifier registered by
|
||||
* extcon_register_interest().
|
||||
* @obj: the extcon_specific_cable_nb object returned by
|
||||
* extcon_register_interest().
|
||||
*/
|
||||
int extcon_unregister_interest(struct extcon_specific_cable_nb *obj)
|
||||
{
|
||||
if (!obj)
|
||||
return -EINVAL;
|
||||
|
||||
return raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb);
|
||||
}
|
||||
|
||||
/**
|
||||
* extcon_register_notifier() - Register a notifee to get notified by
|
||||
* any attach status changes from the extcon.
|
||||
* @edev: the extcon device.
|
||||
* @nb: a notifier block to be registered.
|
||||
*
|
||||
* Note that the second parameter given to the callback of nb (val) is
|
||||
* "old_state", not the current state. The current state can be retrieved
|
||||
* by looking at the third pameter (edev pointer)'s state value.
|
||||
*/
|
||||
int extcon_register_notifier(struct extcon_dev *edev,
|
||||
struct notifier_block *nb)
|
||||
|
@ -173,8 +479,9 @@ int extcon_unregister_notifier(struct extcon_dev *edev,
|
|||
EXPORT_SYMBOL_GPL(extcon_unregister_notifier);
|
||||
|
||||
static struct device_attribute extcon_attrs[] = {
|
||||
__ATTR_RO(state),
|
||||
__ATTR(state, S_IRUGO | S_IWUSR, state_show, state_store),
|
||||
__ATTR_RO(name),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
static int create_extcon_class(void)
|
||||
|
@ -202,6 +509,16 @@ static void extcon_cleanup(struct extcon_dev *edev, bool skip)
|
|||
mutex_unlock(&extcon_dev_list_lock);
|
||||
|
||||
if (!skip && get_device(edev->dev)) {
|
||||
int index;
|
||||
|
||||
for (index = 0; index < edev->max_supported; index++)
|
||||
kfree(edev->cables[index].attr_g.name);
|
||||
|
||||
if (edev->max_supported) {
|
||||
kfree(edev->extcon_dev_type.groups);
|
||||
kfree(edev->cables);
|
||||
}
|
||||
|
||||
device_unregister(edev->dev);
|
||||
put_device(edev->dev);
|
||||
}
|
||||
|
@ -216,6 +533,10 @@ static void extcon_dev_release(struct device *dev)
|
|||
extcon_cleanup(edev, true);
|
||||
}
|
||||
|
||||
static void dummy_sysfs_dev_release(struct device *dev)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* extcon_dev_register() - Register a new extcon device
|
||||
* @edev : the new extcon device (should be allocated before calling)
|
||||
|
@ -228,7 +549,7 @@ static void extcon_dev_release(struct device *dev)
|
|||
*/
|
||||
int extcon_dev_register(struct extcon_dev *edev, struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
int ret, index = 0;
|
||||
|
||||
if (!extcon_class) {
|
||||
ret = create_extcon_class();
|
||||
|
@ -236,12 +557,93 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (edev->supported_cable) {
|
||||
/* Get size of array */
|
||||
for (index = 0; edev->supported_cable[index]; index++)
|
||||
;
|
||||
edev->max_supported = index;
|
||||
} else {
|
||||
edev->max_supported = 0;
|
||||
}
|
||||
|
||||
if (index > SUPPORTED_CABLE_MAX) {
|
||||
dev_err(edev->dev, "extcon: maximum number of supported cables exceeded.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
edev->dev = kzalloc(sizeof(struct device), GFP_KERNEL);
|
||||
edev->dev->parent = dev;
|
||||
edev->dev->class = extcon_class;
|
||||
edev->dev->release = extcon_dev_release;
|
||||
|
||||
dev_set_name(edev->dev, edev->name ? edev->name : dev_name(dev));
|
||||
|
||||
if (edev->max_supported) {
|
||||
char buf[10];
|
||||
char *str;
|
||||
struct extcon_cable *cable;
|
||||
|
||||
edev->cables = kzalloc(sizeof(struct extcon_cable) *
|
||||
edev->max_supported, GFP_KERNEL);
|
||||
if (!edev->cables) {
|
||||
ret = -ENOMEM;
|
||||
goto err_sysfs_alloc;
|
||||
}
|
||||
for (index = 0; index < edev->max_supported; index++) {
|
||||
cable = &edev->cables[index];
|
||||
|
||||
snprintf(buf, 10, "cable.%d", index);
|
||||
str = kzalloc(sizeof(char) * (strlen(buf) + 1),
|
||||
GFP_KERNEL);
|
||||
if (!str) {
|
||||
for (index--; index >= 0; index--) {
|
||||
cable = &edev->cables[index];
|
||||
kfree(cable->attr_g.name);
|
||||
}
|
||||
ret = -ENOMEM;
|
||||
|
||||
goto err_alloc_cables;
|
||||
}
|
||||
strcpy(str, buf);
|
||||
|
||||
cable->edev = edev;
|
||||
cable->cable_index = index;
|
||||
cable->attrs[0] = &cable->attr_name.attr;
|
||||
cable->attrs[1] = &cable->attr_state.attr;
|
||||
cable->attrs[2] = NULL;
|
||||
cable->attr_g.name = str;
|
||||
cable->attr_g.attrs = cable->attrs;
|
||||
|
||||
cable->attr_name.attr.name = "name";
|
||||
cable->attr_name.attr.mode = 0444;
|
||||
cable->attr_name.show = cable_name_show;
|
||||
|
||||
cable->attr_state.attr.name = "state";
|
||||
cable->attr_state.attr.mode = 0644;
|
||||
cable->attr_state.show = cable_state_show;
|
||||
cable->attr_state.store = cable_state_store;
|
||||
}
|
||||
}
|
||||
|
||||
if (edev->max_supported) {
|
||||
edev->extcon_dev_type.groups =
|
||||
kzalloc(sizeof(struct attribute_group *) *
|
||||
(edev->max_supported + 1), GFP_KERNEL);
|
||||
if (!edev->extcon_dev_type.groups) {
|
||||
ret = -ENOMEM;
|
||||
goto err_alloc_groups;
|
||||
}
|
||||
|
||||
edev->extcon_dev_type.name = dev_name(edev->dev);
|
||||
edev->extcon_dev_type.release = dummy_sysfs_dev_release;
|
||||
|
||||
for (index = 0; index < edev->max_supported; index++)
|
||||
edev->extcon_dev_type.groups[index] =
|
||||
&edev->cables[index].attr_g;
|
||||
|
||||
edev->dev->type = &edev->extcon_dev_type;
|
||||
}
|
||||
|
||||
ret = device_register(edev->dev);
|
||||
if (ret) {
|
||||
put_device(edev->dev);
|
||||
|
@ -253,6 +655,8 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev)
|
|||
dev);
|
||||
#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */
|
||||
|
||||
spin_lock_init(&edev->lock);
|
||||
|
||||
RAW_INIT_NOTIFIER_HEAD(&edev->nh);
|
||||
|
||||
dev_set_drvdata(edev->dev, edev);
|
||||
|
@ -265,6 +669,15 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev)
|
|||
return 0;
|
||||
|
||||
err_dev:
|
||||
if (edev->max_supported)
|
||||
kfree(edev->extcon_dev_type.groups);
|
||||
err_alloc_groups:
|
||||
for (index = 0; index < edev->max_supported; index++)
|
||||
kfree(edev->cables[index].attr_g.name);
|
||||
err_alloc_cables:
|
||||
if (edev->max_supported)
|
||||
kfree(edev->cables);
|
||||
err_sysfs_alloc:
|
||||
kfree(edev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -24,10 +24,60 @@
|
|||
#define __LINUX_EXTCON_H__
|
||||
|
||||
#include <linux/notifier.h>
|
||||
|
||||
#define SUPPORTED_CABLE_MAX 32
|
||||
#define CABLE_NAME_MAX 30
|
||||
|
||||
/*
|
||||
* The standard cable name is to help support general notifier
|
||||
* and notifee device drivers to share the common names.
|
||||
* Please use standard cable names unless your notifier device has
|
||||
* a very unique and abnormal cable or
|
||||
* the cable type is supposed to be used with only one unique
|
||||
* pair of notifier/notifee devices.
|
||||
*
|
||||
* Please add any other "standard" cables used with extcon dev.
|
||||
*
|
||||
* You may add a dot and number to specify version or specification
|
||||
* of the specific cable if it is required. (e.g., "Fast-charger.18"
|
||||
* and "Fast-charger.10" for 1.8A and 1.0A chargers)
|
||||
* However, the notifee and notifier should be able to handle such
|
||||
* string and if the notifee can negotiate the protocol or idenify,
|
||||
* you don't need such convention. This convention is helpful when
|
||||
* notifier can distinguish but notifiee cannot.
|
||||
*/
|
||||
enum extcon_cable_name {
|
||||
EXTCON_USB = 0,
|
||||
EXTCON_USB_HOST,
|
||||
EXTCON_TA, /* Travel Adaptor */
|
||||
EXTCON_FAST_CHARGER,
|
||||
EXTCON_SLOW_CHARGER,
|
||||
EXTCON_CHARGE_DOWNSTREAM, /* Charging an external device */
|
||||
EXTCON_HDMI,
|
||||
EXTCON_MHL,
|
||||
EXTCON_DVI,
|
||||
EXTCON_VGA,
|
||||
EXTCON_DOCK,
|
||||
EXTCON_LINE_IN,
|
||||
EXTCON_LINE_OUT,
|
||||
EXTCON_MIC_IN,
|
||||
EXTCON_HEADPHONE_OUT,
|
||||
EXTCON_SPDIF_IN,
|
||||
EXTCON_SPDIF_OUT,
|
||||
EXTCON_VIDEO_IN,
|
||||
EXTCON_VIDEO_OUT,
|
||||
};
|
||||
extern const char *extcon_cable_name[];
|
||||
|
||||
struct extcon_cable;
|
||||
|
||||
/**
|
||||
* struct extcon_dev - An extcon device represents one external connector.
|
||||
* @name The name of this extcon device. Parent device name is used
|
||||
* if NULL.
|
||||
* @supported_cable Array of supported cable name ending with NULL.
|
||||
* If supported_cable is NULL, cable name related APIs
|
||||
* are disabled.
|
||||
* @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
|
||||
|
@ -38,6 +88,11 @@
|
|||
* @nh Notifier for the state change events from this extcon
|
||||
* @entry To support list of extcon devices so that uses can search
|
||||
* for extcon devices based on the extcon name.
|
||||
* @lock
|
||||
* @max_supported Internal value to store the number of cables.
|
||||
* @extcon_dev_type Device_type struct to provide attribute_groups
|
||||
* customized for each extcon device.
|
||||
* @cables Sysfs subdirectories. Each represents one cable.
|
||||
*
|
||||
* In most cases, users only need to provide "User initializing data" of
|
||||
* this struct when registering an extcon. In some exceptional cases,
|
||||
|
@ -47,6 +102,7 @@
|
|||
struct extcon_dev {
|
||||
/* --- Optional user initializing data --- */
|
||||
const char *name;
|
||||
const char **supported_cable;
|
||||
|
||||
/* --- Optional callbacks to override class functions --- */
|
||||
ssize_t (*print_name)(struct extcon_dev *edev, char *buf);
|
||||
|
@ -57,6 +113,49 @@ struct extcon_dev {
|
|||
u32 state;
|
||||
struct raw_notifier_head nh;
|
||||
struct list_head entry;
|
||||
spinlock_t lock; /* could be called by irq handler */
|
||||
int max_supported;
|
||||
|
||||
/* /sys/class/extcon/.../cable.n/... */
|
||||
struct device_type extcon_dev_type;
|
||||
struct extcon_cable *cables;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct extcon_cable - An internal data for each cable of extcon device.
|
||||
* @edev The extcon device
|
||||
* @cable_index Index of this cable in the edev
|
||||
* @attr_g Attribute group for the cable
|
||||
* @attr_name "name" sysfs entry
|
||||
* @attr_state "state" sysfs entry
|
||||
* @attrs Array pointing to attr_name and attr_state for attr_g
|
||||
*/
|
||||
struct extcon_cable {
|
||||
struct extcon_dev *edev;
|
||||
int cable_index;
|
||||
|
||||
struct attribute_group attr_g;
|
||||
struct device_attribute attr_name;
|
||||
struct device_attribute attr_state;
|
||||
|
||||
struct attribute *attrs[3]; /* to be fed to attr_g.attrs */
|
||||
};
|
||||
|
||||
/**
|
||||
* struct extcon_specific_cable_nb - An internal data for
|
||||
* extcon_register_interest().
|
||||
* @internal_nb a notifier block bridging extcon notifier and cable notifier.
|
||||
* @user_nb user provided notifier block for events from a specific cable.
|
||||
* @cable_index the target cable.
|
||||
* @edev the target extcon device.
|
||||
* @previous_value the saved previous event value.
|
||||
*/
|
||||
struct extcon_specific_cable_nb {
|
||||
struct notifier_block internal_nb;
|
||||
struct notifier_block *user_nb;
|
||||
int cable_index;
|
||||
struct extcon_dev *edev;
|
||||
unsigned long previous_value;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_EXTCON)
|
||||
|
@ -69,16 +168,54 @@ extern int extcon_dev_register(struct extcon_dev *edev, struct device *dev);
|
|||
extern void extcon_dev_unregister(struct extcon_dev *edev);
|
||||
extern struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name);
|
||||
|
||||
/*
|
||||
* get/set/update_state access the 32b encoded state value, which represents
|
||||
* states of all possible cables of the multistate port. For example, if one
|
||||
* calls extcon_set_state(edev, 0x7), it may mean that all the three cables
|
||||
* are attached to the port.
|
||||
*/
|
||||
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);
|
||||
|
||||
/*
|
||||
* get/set_cable_state access each bit of the 32b encoded state value.
|
||||
* They are used to access the status of each cable based on the cable_name
|
||||
* or cable_index, which is retrived by extcon_find_cable_index
|
||||
*/
|
||||
extern int extcon_find_cable_index(struct extcon_dev *sdev,
|
||||
const char *cable_name);
|
||||
extern int extcon_get_cable_state_(struct extcon_dev *edev, int cable_index);
|
||||
extern int extcon_set_cable_state_(struct extcon_dev *edev, int cable_index,
|
||||
bool cable_state);
|
||||
|
||||
extern int extcon_get_cable_state(struct extcon_dev *edev,
|
||||
const char *cable_name);
|
||||
extern int extcon_set_cable_state(struct extcon_dev *edev,
|
||||
const char *cable_name, bool cable_state);
|
||||
|
||||
/*
|
||||
* Following APIs are for notifiees (those who want to be notified)
|
||||
* to register a callback for events from a specific cable of the extcon.
|
||||
* Notifiees are the connected device drivers wanting to get notified by
|
||||
* a specific external port of a connection device.
|
||||
*/
|
||||
extern int extcon_register_interest(struct extcon_specific_cable_nb *obj,
|
||||
const char *extcon_name,
|
||||
const char *cable_name,
|
||||
struct notifier_block *nb);
|
||||
extern int extcon_unregister_interest(struct extcon_specific_cable_nb *nb);
|
||||
|
||||
/*
|
||||
* Following APIs are to monitor every action of a notifier.
|
||||
* Registerer gets notified for every external port of a connection device.
|
||||
* Probably this could be used to debug an action of notifier; however,
|
||||
* we do not recommend to use this at normal 'notifiee' device drivers who
|
||||
* want to be notified by a specific external port of the notifier.
|
||||
*/
|
||||
extern int extcon_register_notifier(struct extcon_dev *edev,
|
||||
struct notifier_block *nb);
|
||||
|
@ -99,6 +236,41 @@ static inline u32 extcon_get_state(struct extcon_dev *edev)
|
|||
}
|
||||
|
||||
static inline void extcon_set_state(struct extcon_dev *edev, u32 state) { }
|
||||
|
||||
static inline void extcon_update_state(struct extcon_dev *edev, u32 mask,
|
||||
u32 state)
|
||||
{ }
|
||||
|
||||
static inline int extcon_find_cable_index(struct extcon_dev *edev,
|
||||
const char *cable_name)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int extcon_get_cable_state_(struct extcon_dev *edev,
|
||||
int cable_index)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int extcon_set_cable_state_(struct extcon_dev *edev,
|
||||
int cable_index, bool cable_state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int extcon_get_cable_state(struct extcon_dev *edev,
|
||||
const char *cable_name)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int extcon_set_cable_state(struct extcon_dev *edev,
|
||||
const char *cable_name, int state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name)
|
||||
{
|
||||
return NULL;
|
||||
|
@ -116,5 +288,18 @@ static inline int extcon_unregister_notifier(struct extcon_dev *edev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline int extcon_register_interest(struct extcon_specific_cable_nb *obj,
|
||||
const char *extcon_name,
|
||||
const char *cable_name,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int extcon_unregister_interest(struct extcon_specific_cable_nb
|
||||
*obj)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_EXTCON */
|
||||
#endif /* __LINUX_EXTCON_H__ */
|
||||
|
|
Loading…
Reference in New Issue