From de55d8716ac50a356cea736c29bb7db5ac3d0190 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 20 Apr 2012 14:16:22 +0900 Subject: Extcon (external connector): import Android's switch class and modify. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit External connector class (extcon) is based on and an extension of Android kernel's switch class located at linux/drivers/switch/. This patch provides the before-extension switch class moved to the location where the extcon will be located (linux/drivers/extcon/) and updates to handle class properly. The before-extension class, switch class of Android kernel, commits imported are: switch: switch class and GPIO drivers. (splitted) Author: Mike Lockwood switch: Use device_create instead of device_create_drvdata. Author: Arve Hjønnevåg In this patch, upon the commits of Android kernel, we have added: - Relocated and renamed for extcon. - Comments, module name, and author information are updated - Code clean for successing patches - Bugfix: enabling write access without write functions - Class/device/sysfs create/remove handling - Added comments about uevents - Format changes for extcon_dev_register() to have a parent dev. Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Reviewed-by: Mark Brown -- Changes from v7 - Compiler error fixed when it is compiled as a module. - Removed out-of-date Kconfig entry Changes from v6 - Updated comment/strings - Revised "Android-compatible" mode. * Automatically activated if CONFIG_ANDROID && !CONFIG_ANDROID_SWITCH * Creates /sys/class/switch/*, which is a copy of /sys/class/extcon/* Changes from v5 - Split the patch - Style fixes - "Android-compatible" mode is enabled by Kconfig option. Changes from v2 - Updated name_show - Sysfs entries are handled by class itself. - Updated the method to add/remove devices for the class - Comments on uevent send - Able to become a module - Compatible with Android platform Changes from RFC - Renamed to extcon (external connector) from multistate switch - Added a seperated directory (drivers/extcon) - Added kerneldoc comments - Removed unused variables from extcon_gpio.c - Added ABI Documentation. Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/Kconfig | 17 +++ drivers/extcon/Makefile | 5 + drivers/extcon/extcon_class.c | 236 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 drivers/extcon/Kconfig create mode 100644 drivers/extcon/Makefile create mode 100644 drivers/extcon/extcon_class.c (limited to 'drivers/extcon') diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig new file mode 100644 index 000000000000..7932e1bd9b02 --- /dev/null +++ b/drivers/extcon/Kconfig @@ -0,0 +1,17 @@ +menuconfig EXTCON + tristate "External Connector Class (extcon) support" + help + Say Y here to enable external connector class (extcon) support. + This allows monitoring external connectors by userspace + via sysfs and uevent and supports external connectors with + multiple states; i.e., an extcon that may have multiple + cables attached. For example, an external connector of a device + may be used to connect an HDMI cable and a AC adaptor, and to + host USB ports. Many of 30-pin connectors including PDMI are + also good examples. + +if EXTCON + +comment "Extcon Device Drivers" + +endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile new file mode 100644 index 000000000000..6bc69214fcd7 --- /dev/null +++ b/drivers/extcon/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for external connector class (extcon) devices +# + +obj-$(CONFIG_EXTCON) += extcon_class.o diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c new file mode 100644 index 000000000000..3c46368c279a --- /dev/null +++ b/drivers/extcon/extcon_class.c @@ -0,0 +1,236 @@ +/* + * drivers/extcon/extcon_class.c + * + * External connector (extcon) class driver + * + * Copyright (C) 2012 Samsung Electronics + * Author: Donggeun Kim + * Author: MyungJoo Ham + * + * based on android/drivers/switch/switch_class.c + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct class *extcon_class; +#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) +static struct class_compat *switch_class; +#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); + + if (edev->print_state) { + int ret = edev->print_state(edev, buf); + + if (ret >= 0) + return ret; + /* Use default if failed */ + } + return sprintf(buf, "%u\n", edev->state); +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); + + /* Optional callback given by the user */ + if (edev->print_name) { + int ret = edev->print_name(edev, buf); + if (ret >= 0) + return ret; + } + + return sprintf(buf, "%s\n", dev_name(edev->dev)); +} + +/** + * extcon_set_state() - Set the cable attach states of the extcon device. + * @edev: the extcon device + * @state: new cable attach status for @edev + * + * Changing the state sends uevent with environment variable containing + * the name of extcon device (envp[0]) and the state output (envp[1]). + * Tizen uses this format for extcon device to get events from ports. + * Android uses this format as well. + */ +void extcon_set_state(struct extcon_dev *edev, u32 state) +{ + char name_buf[120]; + char state_buf[120]; + char *prop_buf; + char *envp[3]; + int env_offset = 0; + int length; + + if (edev->state != state) { + edev->state = state; + + prop_buf = (char *)get_zeroed_page(GFP_KERNEL); + if (prop_buf) { + length = name_show(edev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(name_buf, sizeof(name_buf), + "NAME=%s", prop_buf); + envp[env_offset++] = name_buf; + } + length = state_show(edev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(state_buf, sizeof(state_buf), + "STATE=%s", prop_buf); + envp[env_offset++] = state_buf; + } + envp[env_offset] = NULL; + kobject_uevent_env(&edev->dev->kobj, KOBJ_CHANGE, envp); + free_page((unsigned long)prop_buf); + } else { + dev_err(edev->dev, "out of memory in extcon_set_state\n"); + kobject_uevent(&edev->dev->kobj, KOBJ_CHANGE); + } + } +} +EXPORT_SYMBOL_GPL(extcon_set_state); + +static struct device_attribute extcon_attrs[] = { + __ATTR_RO(state), + __ATTR_RO(name), +}; + +static int create_extcon_class(void) +{ + if (!extcon_class) { + extcon_class = class_create(THIS_MODULE, "extcon"); + if (IS_ERR(extcon_class)) + return PTR_ERR(extcon_class); + extcon_class->dev_attrs = extcon_attrs; + +#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) + switch_class = class_compat_register("switch"); + if (WARN(!switch_class, "cannot allocate")) + return -ENOMEM; +#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ + } + + return 0; +} + +static void extcon_cleanup(struct extcon_dev *edev, bool skip) +{ + if (!skip && get_device(edev->dev)) { + device_unregister(edev->dev); + put_device(edev->dev); + } + + kfree(edev->dev); +} + +static void extcon_dev_release(struct device *dev) +{ + struct extcon_dev *edev = (struct extcon_dev *) dev_get_drvdata(dev); + + extcon_cleanup(edev, true); +} + +/** + * extcon_dev_register() - Register a new extcon device + * @edev : the new extcon device (should be allocated before calling) + * @dev : the parent device for this extcon device. + * + * Among the members of edev struct, please set the "user initializing data" + * in any case and set the "optional callbacks" if required. However, please + * do not set the values of "internal data", which are initialized by + * this function. + */ +int extcon_dev_register(struct extcon_dev *edev, struct device *dev) +{ + int ret; + + if (!extcon_class) { + ret = create_extcon_class(); + if (ret < 0) + return ret; + } + + 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)); + ret = device_register(edev->dev); + if (ret) { + put_device(edev->dev); + goto err_dev; + } +#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) + if (switch_class) + ret = class_compat_create_link(switch_class, edev->dev, + dev); +#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ + + dev_set_drvdata(edev->dev, edev); + edev->state = 0; + return 0; + +err_dev: + kfree(edev->dev); + return ret; +} +EXPORT_SYMBOL_GPL(extcon_dev_register); + +/** + * extcon_dev_unregister() - Unregister the extcon device. + * @edev: the extcon device instance to be unregitered. + * + * Note that this does not call kfree(edev) because edev was not allocated + * by this class. + */ +void extcon_dev_unregister(struct extcon_dev *edev) +{ + extcon_cleanup(edev, false); +} +EXPORT_SYMBOL_GPL(extcon_dev_unregister); + +static int __init extcon_class_init(void) +{ + return create_extcon_class(); +} +module_init(extcon_class_init); + +static void __exit extcon_class_exit(void) +{ + class_destroy(extcon_class); +} +module_exit(extcon_class_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_AUTHOR("Donggeun Kim "); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_DESCRIPTION("External connector (extcon) class driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From be48308a24c7651bf968b561dbd590edb8166d62 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 20 Apr 2012 14:16:23 +0900 Subject: Extcon: support generic GPIO extcon driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generic GPIO extcon driver (an external connector device based on GPIO control) and imported from Android kernel. switch: switch class and GPIO drivers. (splitted) Author: Mike Lockwood switch: gpio: Don't call request_irq with interrupts disabled Author: Arve Hjønnevåg switch_gpio: Add missing #include Author: Mike Lockwood Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Reviewed-by: Mark Brown -- Changed from v7: - Style updates mentioned by Stephen Boyd and Mark Brown Changed from v5: - Splitted at v5 from the main extcon patch. - Added debounce time for irq handlers. - Use request_any_context_irq instead of request_irq - User needs to specify irq flags for GPIO interrupts (was fixed to IRQF_TRIGGER_LOW before) - Use module_platform_driver(). Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/Kconfig | 7 ++ drivers/extcon/Makefile | 1 + drivers/extcon/extcon_gpio.c | 169 +++++++++++++++++++++++++++++++++++++ include/linux/extcon/extcon_gpio.h | 52 ++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 drivers/extcon/extcon_gpio.c create mode 100644 include/linux/extcon/extcon_gpio.h (limited to 'drivers/extcon') diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 7932e1bd9b02..85cecff36db3 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -14,4 +14,11 @@ if EXTCON comment "Extcon Device Drivers" +config EXTCON_GPIO + tristate "GPIO extcon support" + depends on GENERIC_GPIO + help + Say Y here to enable GPIO based extcon support. Note that GPIO + extcon supports single state per extcon instance. + endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 6bc69214fcd7..2c46d4176d18 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_EXTCON) += extcon_class.o +obj-$(CONFIG_EXTCON_GPIO) += extcon_gpio.o diff --git a/drivers/extcon/extcon_gpio.c b/drivers/extcon/extcon_gpio.c new file mode 100644 index 000000000000..5c0f08506f93 --- /dev/null +++ b/drivers/extcon/extcon_gpio.c @@ -0,0 +1,169 @@ +/* + * drivers/extcon/extcon_gpio.c + * + * Single-state GPIO extcon driver based on extcon class + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * Modified by MyungJoo Ham to support extcon + * (originally switch class is supported) + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpio_extcon_data { + struct extcon_dev edev; + unsigned gpio; + const char *state_on; + const char *state_off; + int irq; + struct delayed_work work; + unsigned long debounce_jiffies; +}; + +static void gpio_extcon_work(struct work_struct *work) +{ + int state; + struct gpio_extcon_data *data = + container_of(to_delayed_work(work), struct gpio_extcon_data, + work); + + state = gpio_get_value(data->gpio); + extcon_set_state(&data->edev, state); +} + +static irqreturn_t gpio_irq_handler(int irq, void *dev_id) +{ + struct gpio_extcon_data *extcon_data = dev_id; + + schedule_delayed_work(&extcon_data->work, + extcon_data->debounce_jiffies); + return IRQ_HANDLED; +} + +static ssize_t extcon_gpio_print_state(struct extcon_dev *edev, char *buf) +{ + struct gpio_extcon_data *extcon_data = + container_of(edev, struct gpio_extcon_data, edev); + const char *state; + if (extcon_get_state(edev)) + state = extcon_data->state_on; + else + state = extcon_data->state_off; + + if (state) + return sprintf(buf, "%s\n", state); + return -EINVAL; +} + +static int __devinit gpio_extcon_probe(struct platform_device *pdev) +{ + struct gpio_extcon_platform_data *pdata = pdev->dev.platform_data; + struct gpio_extcon_data *extcon_data; + int ret = 0; + + if (!pdata) + return -EBUSY; + if (!pdata->irq_flags) { + dev_err(&pdev->dev, "IRQ flag is not specified.\n"); + return -EINVAL; + } + + extcon_data = devm_kzalloc(&pdev->dev, sizeof(struct gpio_extcon_data), + GFP_KERNEL); + if (!extcon_data) + return -ENOMEM; + + extcon_data->edev.name = pdata->name; + extcon_data->gpio = pdata->gpio; + extcon_data->state_on = pdata->state_on; + extcon_data->state_off = pdata->state_off; + if (pdata->state_on && pdata->state_off) + extcon_data->edev.print_state = extcon_gpio_print_state; + extcon_data->debounce_jiffies = msecs_to_jiffies(pdata->debounce); + + ret = extcon_dev_register(&extcon_data->edev, &pdev->dev); + if (ret < 0) + goto err_extcon_dev_register; + + ret = gpio_request_one(extcon_data->gpio, GPIOF_DIR_IN, pdev->name); + if (ret < 0) + goto err_request_gpio; + + INIT_DELAYED_WORK(&extcon_data->work, gpio_extcon_work); + + extcon_data->irq = gpio_to_irq(extcon_data->gpio); + if (extcon_data->irq < 0) { + ret = extcon_data->irq; + goto err_detect_irq_num_failed; + } + + ret = request_any_context_irq(extcon_data->irq, gpio_irq_handler, + pdata->irq_flags, pdev->name, + extcon_data); + if (ret < 0) + goto err_request_irq; + + /* Perform initial detection */ + gpio_extcon_work(&extcon_data->work.work); + + return 0; + +err_request_irq: +err_detect_irq_num_failed: + gpio_free(extcon_data->gpio); +err_request_gpio: + extcon_dev_unregister(&extcon_data->edev); +err_extcon_dev_register: + devm_kfree(&pdev->dev, extcon_data); + + return ret; +} + +static int __devexit gpio_extcon_remove(struct platform_device *pdev) +{ + struct gpio_extcon_data *extcon_data = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&extcon_data->work); + gpio_free(extcon_data->gpio); + extcon_dev_unregister(&extcon_data->edev); + devm_kfree(&pdev->dev, extcon_data); + + return 0; +} + +static struct platform_driver gpio_extcon = { + .probe = gpio_extcon_probe, + .remove = __devexit_p(gpio_extcon_remove), + .driver = { + .name = "extcon-gpio", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(gpio_extcon); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("GPIO extcon driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/extcon/extcon_gpio.h b/include/linux/extcon/extcon_gpio.h new file mode 100644 index 000000000000..a2129b73dcb1 --- /dev/null +++ b/include/linux/extcon/extcon_gpio.h @@ -0,0 +1,52 @@ +/* + * External connector (extcon) class generic GPIO driver + * + * Copyright (C) 2012 Samsung Electronics + * Author: MyungJoo Ham + * + * based on switch class driver + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ +#ifndef __EXTCON_GPIO_H__ +#define __EXTCON_GPIO_H__ __FILE__ + +#include + +/** + * struct gpio_extcon_platform_data - A simple GPIO-controlled extcon device. + * @name The name of this GPIO extcon device. + * @gpio Corresponding GPIO. + * @debounce Debounce time for GPIO IRQ in ms. + * @irq_flags IRQ Flags (e.g., IRQF_TRIGGER_LOW). + * @state_on print_state is overriden with state_on if attached. If Null, + * default method of extcon class is used. + * @state_off print_state is overriden with state_on if dettached. If Null, + * default method of extcon class is used. + * + * Note that in order for state_on or state_off to be valid, both state_on + * and state_off should be not NULL. If at least one of them is NULL, + * the print_state is not overriden. + */ +struct gpio_extcon_platform_data { + const char *name; + unsigned gpio; + unsigned long debounce; + unsigned long irq_flags; + + /* if NULL, "0" or "1" will be printed */ + const char *state_on; + const char *state_off; +}; + +#endif /* __EXTCON_GPIO_H__ */ -- cgit v1.2.3-70-g09d2 From 74c5d09bd562edc220d6e076b8f1e118819c178f Mon Sep 17 00:00:00 2001 From: Donggeun Kim Date: Fri, 20 Apr 2012 14:16:24 +0900 Subject: Extcon: support notification based on the state changes. State changes of extcon devices have been notified via kobjet_uevent. This patch adds notifier interfaces in order to allow device drivers to get notified easily. Along with notifier interface, extcon_get_extcon_dev() function is added so that device drivers may discover a extcon_dev easily. Signed-off-by: Donggeun Kim Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Reviewed-by: Mark Brown -- Changes from RFC - Renamed switch to extcon - Bugfix: extcon_dev_unregister() - Bugfix: "edev->dev" is "internal" data. - Added kerneldoc comments. - Reworded comments. Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 66 +++++++++++++++++++++++++++++++++++++++++++ include/linux/extcon.h | 38 +++++++++++++++++++++++++ 2 files changed, 104 insertions(+) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 3c46368c279a..83a088f1edc4 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -36,6 +36,9 @@ struct class *extcon_class; static struct class_compat *switch_class; #endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ +static LIST_HEAD(extcon_dev_list); +static DEFINE_MUTEX(extcon_dev_list_lock); + static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -75,6 +78,9 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr, * the name of extcon device (envp[0]) and the state output (envp[1]). * 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. */ void extcon_set_state(struct extcon_dev *edev, u32 state) { @@ -84,10 +90,14 @@ void extcon_set_state(struct extcon_dev *edev, u32 state) char *envp[3]; int env_offset = 0; int length; + u32 old_state = edev->state; if (edev->state != state) { edev->state = state; + raw_notifier_call_chain(&edev->nh, old_state ^ edev->state, + edev); + prop_buf = (char *)get_zeroed_page(GFP_KERNEL); if (prop_buf) { length = name_show(edev->dev, NULL, prop_buf); @@ -117,6 +127,51 @@ void extcon_set_state(struct extcon_dev *edev, u32 state) } EXPORT_SYMBOL_GPL(extcon_set_state); +/** + * extcon_get_extcon_dev() - Get the extcon device instance from the name + * @extcon_name: The extcon name provided with extcon_dev_register() + */ +struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name) +{ + struct extcon_dev *sd; + + mutex_lock(&extcon_dev_list_lock); + list_for_each_entry(sd, &extcon_dev_list, entry) { + if (!strcmp(sd->name, extcon_name)) + goto out; + } + sd = NULL; +out: + mutex_unlock(&extcon_dev_list_lock); + return sd; +} +EXPORT_SYMBOL_GPL(extcon_get_extcon_dev); + +/** + * 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. + */ +int extcon_register_notifier(struct extcon_dev *edev, + struct notifier_block *nb) +{ + return raw_notifier_chain_register(&edev->nh, nb); +} +EXPORT_SYMBOL_GPL(extcon_register_notifier); + +/** + * extcon_unregister_notifier() - Unregister a notifee from the extcon device. + * @edev: the extcon device. + * @nb: a registered notifier block to be unregistered. + */ +int extcon_unregister_notifier(struct extcon_dev *edev, + struct notifier_block *nb) +{ + return raw_notifier_chain_unregister(&edev->nh, nb); +} +EXPORT_SYMBOL_GPL(extcon_unregister_notifier); + static struct device_attribute extcon_attrs[] = { __ATTR_RO(state), __ATTR_RO(name), @@ -142,6 +197,10 @@ static int create_extcon_class(void) static void extcon_cleanup(struct extcon_dev *edev, bool skip) { + mutex_lock(&extcon_dev_list_lock); + list_del(&edev->entry); + mutex_unlock(&extcon_dev_list_lock); + if (!skip && get_device(edev->dev)) { device_unregister(edev->dev); put_device(edev->dev); @@ -194,8 +253,15 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) dev); #endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ + RAW_INIT_NOTIFIER_HEAD(&edev->nh); + dev_set_drvdata(edev->dev, edev); edev->state = 0; + + mutex_lock(&extcon_dev_list_lock); + list_add(&edev->entry, &extcon_dev_list); + mutex_unlock(&extcon_dev_list_lock); + return 0; err_dev: diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 9cb3aaaf67f5..c9c9afe12b47 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -23,6 +23,7 @@ #ifndef __LINUX_EXTCON_H__ #define __LINUX_EXTCON_H__ +#include /** * struct extcon_dev - An extcon device represents one external connector. * @name The name of this extcon device. Parent device name is used @@ -34,6 +35,9 @@ * @dev Device of this extcon. Do not provide at register-time. * @state Attach/detach state of this extcon. Do not provide at * register-time + * @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. * * In most cases, users only need to provide "User initializing data" of * this struct when registering an extcon. In some exceptional cases, @@ -51,11 +55,19 @@ struct extcon_dev { /* --- Internal data. Please do not set. --- */ struct device *dev; u32 state; + struct raw_notifier_head nh; + struct list_head entry; }; #if IS_ENABLED(CONFIG_EXTCON) + +/* + * Following APIs are for notifiers or configurations. + * Notifiers are the external port and connection devices. + */ 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); static inline u32 extcon_get_state(struct extcon_dev *edev) { @@ -63,6 +75,15 @@ static inline u32 extcon_get_state(struct extcon_dev *edev) } extern void extcon_set_state(struct extcon_dev *edev, u32 state); + +/* + * Following APIs are to monitor every action of a notifier. + * Registerer gets notified for every external port of a connection device. + */ +extern int extcon_register_notifier(struct extcon_dev *edev, + struct notifier_block *nb); +extern int extcon_unregister_notifier(struct extcon_dev *edev, + struct notifier_block *nb); #else /* CONFIG_EXTCON */ static inline int extcon_dev_register(struct extcon_dev *edev, struct device *dev) @@ -78,5 +99,22 @@ static inline u32 extcon_get_state(struct extcon_dev *edev) } static inline void extcon_set_state(struct extcon_dev *edev, u32 state) { } +static inline struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name) +{ + return NULL; +} + +static inline int extcon_register_notifier(struct extcon_dev *edev, + struct notifier_block *nb) +{ + return 0; +} + +static inline int extcon_unregister_notifier(struct extcon_dev *edev, + struct notifier_block *nb) +{ + return 0; +} + #endif /* CONFIG_EXTCON */ #endif /* __LINUX_EXTCON_H__ */ -- cgit v1.2.3-70-g09d2 From 806d9dd71ff52ef09764585baaeec23afbb98560 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 20 Apr 2012 14:16:25 +0900 Subject: 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 Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park -- 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 --- Documentation/ABI/testing/sysfs-class-extcon | 63 +++- drivers/extcon/extcon_class.c | 439 ++++++++++++++++++++++++++- include/linux/extcon.h | 185 +++++++++++ 3 files changed, 667 insertions(+), 20 deletions(-) (limited to 'drivers/extcon') diff --git a/Documentation/ABI/testing/sysfs-class-extcon b/Documentation/ABI/testing/sysfs-class-extcon index 59a4b1c708d5..19b18e986a57 100644 --- a/Documentation/ABI/testing/sysfs-class-extcon +++ b/Documentation/ABI/testing/sysfs-class-extcon @@ -1,5 +1,5 @@ What: /sys/class/extcon/.../ -Date: December 2011 +Date: February 2012 Contact: MyungJoo Ham 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 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 +Description: + 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 +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 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/.../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). diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 83a088f1edc4..403933bc3e9c 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -31,6 +31,39 @@ #include #include +/* + * 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; } diff --git a/include/linux/extcon.h b/include/linux/extcon.h index c9c9afe12b47..20e24b32a17d 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -24,10 +24,60 @@ #define __LINUX_EXTCON_H__ #include + +#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__ */ -- cgit v1.2.3-70-g09d2 From bde68e60b18208978c50c6fb9bdf29826d2887f3 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 20 Apr 2012 14:16:26 +0900 Subject: 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 Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park -- Changes from V5: - Updated sysfs format Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-extcon | 22 +++++ drivers/extcon/extcon_class.c | 123 +++++++++++++++++++++++++-- include/linux/extcon.h | 28 ++++-- 3 files changed, 160 insertions(+), 13 deletions(-) (limited to 'drivers/extcon') diff --git a/Documentation/ABI/testing/sysfs-class-extcon b/Documentation/ABI/testing/sysfs-class-extcon index 19b18e986a57..20ab361bd8c6 100644 --- a/Documentation/ABI/testing/sysfs-class-extcon +++ b/Documentation/ABI/testing/sysfs-class-extcon @@ -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 @@ -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 +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. diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 403933bc3e9c..3bc4b8af46cf 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -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: diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 20e24b32a17d..6495f7731400 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -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) -- cgit v1.2.3-70-g09d2 From 449a2bf5e881b2a00d42a7c0baa67119c8cb5dce Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Mon, 23 Apr 2012 20:19:57 +0900 Subject: Remove "switch" class in drivers/staging/android/switch Because extcon can also be a switch class for legacy userspace (Android) and is a superset of switch class in drivers/staging/android/switch, switch class may be removed. - Remove switch class - Remove switch class consideration in extcon class Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 12 +- drivers/staging/android/Kconfig | 3 - drivers/staging/android/Makefile | 1 - drivers/staging/android/switch/Kconfig | 11 -- drivers/staging/android/switch/Makefile | 4 - drivers/staging/android/switch/switch.h | 53 -------- drivers/staging/android/switch/switch_class.c | 174 -------------------------- drivers/staging/android/switch/switch_gpio.c | 172 ------------------------- 8 files changed, 6 insertions(+), 424 deletions(-) delete mode 100644 drivers/staging/android/switch/Kconfig delete mode 100644 drivers/staging/android/switch/Makefile delete mode 100644 drivers/staging/android/switch/switch.h delete mode 100644 drivers/staging/android/switch/switch_class.c delete mode 100644 drivers/staging/android/switch/switch_gpio.c (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 3bc4b8af46cf..dbd3bfba42da 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -65,9 +65,9 @@ const char *extcon_cable_name[] = { }; struct class *extcon_class; -#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) +#if defined(CONFIG_ANDROID) static struct class_compat *switch_class; -#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ +#endif /* CONFIG_ANDROID */ static LIST_HEAD(extcon_dev_list); static DEFINE_MUTEX(extcon_dev_list_lock); @@ -532,11 +532,11 @@ static int create_extcon_class(void) return PTR_ERR(extcon_class); extcon_class->dev_attrs = extcon_attrs; -#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) +#if defined(CONFIG_ANDROID) switch_class = class_compat_register("switch"); if (WARN(!switch_class, "cannot allocate")) return -ENOMEM; -#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ +#endif /* CONFIG_ANDROID */ } return 0; @@ -749,11 +749,11 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) put_device(edev->dev); goto err_dev; } -#if defined(CONFIG_ANDROID) && !defined(CONFIG_ANDROID_SWITCH) +#if defined(CONFIG_ANDROID) if (switch_class) ret = class_compat_create_link(switch_class, edev->dev, dev); -#endif /* CONFIG_ANDROID && !defined(CONFIG_ANDROID_SWITCH) */ +#endif /* CONFIG_ANDROID */ spin_lock_init(&edev->lock); diff --git a/drivers/staging/android/Kconfig b/drivers/staging/android/Kconfig index 08a3b1133d29..60dc7108c8b3 100644 --- a/drivers/staging/android/Kconfig +++ b/drivers/staging/android/Kconfig @@ -52,8 +52,6 @@ config ANDROID_LOW_MEMORY_KILLER ---help--- Register processes to be killed when memory is low -source "drivers/staging/android/switch/Kconfig" - config ANDROID_INTF_ALARM bool "Android alarm driver" depends on RTC_CLASS @@ -79,7 +77,6 @@ config ANDROID_ALARM_OLDDRV_COMPAT Provides preprocessor alias to aid compatability with older out-of-tree drivers that use the Android Alarm in-kernel API. This will be removed eventually. - endif # if ANDROID endmenu diff --git a/drivers/staging/android/Makefile b/drivers/staging/android/Makefile index 9b6c9ed91f69..045d17bde524 100644 --- a/drivers/staging/android/Makefile +++ b/drivers/staging/android/Makefile @@ -6,6 +6,5 @@ obj-$(CONFIG_ANDROID_RAM_CONSOLE) += ram_console.o obj-$(CONFIG_ANDROID_TIMED_OUTPUT) += timed_output.o obj-$(CONFIG_ANDROID_TIMED_GPIO) += timed_gpio.o obj-$(CONFIG_ANDROID_LOW_MEMORY_KILLER) += lowmemorykiller.o -obj-$(CONFIG_ANDROID_SWITCH) += switch/ obj-$(CONFIG_ANDROID_INTF_ALARM) += alarm.o obj-$(CONFIG_ANDROID_INTF_ALARM_DEV) += alarm-dev.o diff --git a/drivers/staging/android/switch/Kconfig b/drivers/staging/android/switch/Kconfig deleted file mode 100644 index 36846f62f4bc..000000000000 --- a/drivers/staging/android/switch/Kconfig +++ /dev/null @@ -1,11 +0,0 @@ -menuconfig ANDROID_SWITCH - tristate "Android Switch class support" - help - Say Y here to enable Android switch class support. This allows - monitoring switches by userspace via sysfs and uevent. - -config ANDROID_SWITCH_GPIO - tristate "Android GPIO Switch support" - depends on GENERIC_GPIO && ANDROID_SWITCH - help - Say Y here to enable GPIO based switch support. diff --git a/drivers/staging/android/switch/Makefile b/drivers/staging/android/switch/Makefile deleted file mode 100644 index d76bfdcedfaf..000000000000 --- a/drivers/staging/android/switch/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -# Android Switch Class Driver -obj-$(CONFIG_ANDROID_SWITCH) += switch_class.o -obj-$(CONFIG_ANDROID_SWITCH_GPIO) += switch_gpio.o - diff --git a/drivers/staging/android/switch/switch.h b/drivers/staging/android/switch/switch.h deleted file mode 100644 index 4fcb3109875a..000000000000 --- a/drivers/staging/android/switch/switch.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Switch class driver - * - * Copyright (C) 2008 Google, Inc. - * Author: Mike Lockwood - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * -*/ - -#ifndef __LINUX_SWITCH_H__ -#define __LINUX_SWITCH_H__ - -struct switch_dev { - const char *name; - struct device *dev; - int index; - int state; - - ssize_t (*print_name)(struct switch_dev *sdev, char *buf); - ssize_t (*print_state)(struct switch_dev *sdev, char *buf); -}; - -struct gpio_switch_platform_data { - const char *name; - unsigned gpio; - - /* if NULL, switch_dev.name will be printed */ - const char *name_on; - const char *name_off; - /* if NULL, "0" or "1" will be printed */ - const char *state_on; - const char *state_off; -}; - -extern int switch_dev_register(struct switch_dev *sdev); -extern void switch_dev_unregister(struct switch_dev *sdev); - -static inline int switch_get_state(struct switch_dev *sdev) -{ - return sdev->state; -} - -extern void switch_set_state(struct switch_dev *sdev, int state); - -#endif /* __LINUX_SWITCH_H__ */ diff --git a/drivers/staging/android/switch/switch_class.c b/drivers/staging/android/switch/switch_class.c deleted file mode 100644 index 74680446fc66..000000000000 --- a/drivers/staging/android/switch/switch_class.c +++ /dev/null @@ -1,174 +0,0 @@ -/* - * switch_class.c - * - * Copyright (C) 2008 Google, Inc. - * Author: Mike Lockwood - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * -*/ - -#include -#include -#include -#include -#include -#include -#include "switch.h" - -struct class *switch_class; -static atomic_t device_count; - -static ssize_t state_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct switch_dev *sdev = (struct switch_dev *) - dev_get_drvdata(dev); - - if (sdev->print_state) { - int ret = sdev->print_state(sdev, buf); - if (ret >= 0) - return ret; - } - return sprintf(buf, "%d\n", sdev->state); -} - -static ssize_t name_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct switch_dev *sdev = (struct switch_dev *) - dev_get_drvdata(dev); - - if (sdev->print_name) { - int ret = sdev->print_name(sdev, buf); - if (ret >= 0) - return ret; - } - return sprintf(buf, "%s\n", sdev->name); -} - -static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, state_show, NULL); -static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, name_show, NULL); - -void switch_set_state(struct switch_dev *sdev, int state) -{ - char name_buf[120]; - char state_buf[120]; - char *prop_buf; - char *envp[3]; - int env_offset = 0; - int length; - - if (sdev->state != state) { - sdev->state = state; - - prop_buf = (char *)get_zeroed_page(GFP_KERNEL); - if (prop_buf) { - length = name_show(sdev->dev, NULL, prop_buf); - if (length > 0) { - if (prop_buf[length - 1] == '\n') - prop_buf[length - 1] = 0; - snprintf(name_buf, sizeof(name_buf), - "SWITCH_NAME=%s", prop_buf); - envp[env_offset++] = name_buf; - } - length = state_show(sdev->dev, NULL, prop_buf); - if (length > 0) { - if (prop_buf[length - 1] == '\n') - prop_buf[length - 1] = 0; - snprintf(state_buf, sizeof(state_buf), - "SWITCH_STATE=%s", prop_buf); - envp[env_offset++] = state_buf; - } - envp[env_offset] = NULL; - kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp); - free_page((unsigned long)prop_buf); - } else { - printk(KERN_ERR "out of memory in switch_set_state\n"); - kobject_uevent(&sdev->dev->kobj, KOBJ_CHANGE); - } - } -} -EXPORT_SYMBOL_GPL(switch_set_state); - -static int create_switch_class(void) -{ - if (!switch_class) { - switch_class = class_create(THIS_MODULE, "switch"); - if (IS_ERR(switch_class)) - return PTR_ERR(switch_class); - atomic_set(&device_count, 0); - } - - return 0; -} - -int switch_dev_register(struct switch_dev *sdev) -{ - int ret; - - if (!switch_class) { - ret = create_switch_class(); - if (ret < 0) - return ret; - } - - sdev->index = atomic_inc_return(&device_count); - sdev->dev = device_create(switch_class, NULL, - MKDEV(0, sdev->index), NULL, sdev->name); - if (IS_ERR(sdev->dev)) - return PTR_ERR(sdev->dev); - - ret = device_create_file(sdev->dev, &dev_attr_state); - if (ret < 0) - goto err_create_file_1; - ret = device_create_file(sdev->dev, &dev_attr_name); - if (ret < 0) - goto err_create_file_2; - - dev_set_drvdata(sdev->dev, sdev); - sdev->state = 0; - return 0; - -err_create_file_2: - device_remove_file(sdev->dev, &dev_attr_state); -err_create_file_1: - device_destroy(switch_class, MKDEV(0, sdev->index)); - printk(KERN_ERR "switch: Failed to register driver %s\n", sdev->name); - - return ret; -} -EXPORT_SYMBOL_GPL(switch_dev_register); - -void switch_dev_unregister(struct switch_dev *sdev) -{ - device_remove_file(sdev->dev, &dev_attr_name); - device_remove_file(sdev->dev, &dev_attr_state); - device_destroy(switch_class, MKDEV(0, sdev->index)); - dev_set_drvdata(sdev->dev, NULL); -} -EXPORT_SYMBOL_GPL(switch_dev_unregister); - -static int __init switch_class_init(void) -{ - return create_switch_class(); -} - -static void __exit switch_class_exit(void) -{ - class_destroy(switch_class); -} - -module_init(switch_class_init); -module_exit(switch_class_exit); - -MODULE_AUTHOR("Mike Lockwood "); -MODULE_DESCRIPTION("Switch class driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/staging/android/switch/switch_gpio.c b/drivers/staging/android/switch/switch_gpio.c deleted file mode 100644 index 38b2c2f6004e..000000000000 --- a/drivers/staging/android/switch/switch_gpio.c +++ /dev/null @@ -1,172 +0,0 @@ -/* - * switch_gpio.c - * - * Copyright (C) 2008 Google, Inc. - * Author: Mike Lockwood - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "switch.h" - -struct gpio_switch_data { - struct switch_dev sdev; - unsigned gpio; - const char *name_on; - const char *name_off; - const char *state_on; - const char *state_off; - int irq; - struct work_struct work; -}; - -static void gpio_switch_work(struct work_struct *work) -{ - int state; - struct gpio_switch_data *data = - container_of(work, struct gpio_switch_data, work); - - state = gpio_get_value(data->gpio); - switch_set_state(&data->sdev, state); -} - -static irqreturn_t gpio_irq_handler(int irq, void *dev_id) -{ - struct gpio_switch_data *switch_data = - (struct gpio_switch_data *)dev_id; - - schedule_work(&switch_data->work); - return IRQ_HANDLED; -} - -static ssize_t switch_gpio_print_state(struct switch_dev *sdev, char *buf) -{ - struct gpio_switch_data *switch_data = - container_of(sdev, struct gpio_switch_data, sdev); - const char *state; - if (switch_get_state(sdev)) - state = switch_data->state_on; - else - state = switch_data->state_off; - - if (state) - return sprintf(buf, "%s\n", state); - return -1; -} - -static int gpio_switch_probe(struct platform_device *pdev) -{ - struct gpio_switch_platform_data *pdata = pdev->dev.platform_data; - struct gpio_switch_data *switch_data; - int ret = 0; - - if (!pdata) - return -EBUSY; - - switch_data = kzalloc(sizeof(struct gpio_switch_data), GFP_KERNEL); - if (!switch_data) - return -ENOMEM; - - switch_data->sdev.name = pdata->name; - switch_data->gpio = pdata->gpio; - switch_data->name_on = pdata->name_on; - switch_data->name_off = pdata->name_off; - switch_data->state_on = pdata->state_on; - switch_data->state_off = pdata->state_off; - switch_data->sdev.print_state = switch_gpio_print_state; - - ret = switch_dev_register(&switch_data->sdev); - if (ret < 0) - goto err_switch_dev_register; - - ret = gpio_request(switch_data->gpio, pdev->name); - if (ret < 0) - goto err_request_gpio; - - ret = gpio_direction_input(switch_data->gpio); - if (ret < 0) - goto err_set_gpio_input; - - INIT_WORK(&switch_data->work, gpio_switch_work); - - switch_data->irq = gpio_to_irq(switch_data->gpio); - if (switch_data->irq < 0) { - ret = switch_data->irq; - goto err_detect_irq_num_failed; - } - - ret = request_irq(switch_data->irq, gpio_irq_handler, - IRQF_TRIGGER_LOW, pdev->name, switch_data); - if (ret < 0) - goto err_request_irq; - - /* Perform initial detection */ - gpio_switch_work(&switch_data->work); - - return 0; - -err_request_irq: -err_detect_irq_num_failed: -err_set_gpio_input: - gpio_free(switch_data->gpio); -err_request_gpio: - switch_dev_unregister(&switch_data->sdev); -err_switch_dev_register: - kfree(switch_data); - - return ret; -} - -static int __devexit gpio_switch_remove(struct platform_device *pdev) -{ - struct gpio_switch_data *switch_data = platform_get_drvdata(pdev); - - cancel_work_sync(&switch_data->work); - gpio_free(switch_data->gpio); - switch_dev_unregister(&switch_data->sdev); - kfree(switch_data); - - return 0; -} - -static struct platform_driver gpio_switch_driver = { - .probe = gpio_switch_probe, - .remove = __devexit_p(gpio_switch_remove), - .driver = { - .name = "switch-gpio", - .owner = THIS_MODULE, - }, -}; - -static int __init gpio_switch_init(void) -{ - return platform_driver_register(&gpio_switch_driver); -} - -static void __exit gpio_switch_exit(void) -{ - platform_driver_unregister(&gpio_switch_driver); -} - -module_init(gpio_switch_init); -module_exit(gpio_switch_exit); - -MODULE_AUTHOR("Mike Lockwood "); -MODULE_DESCRIPTION("GPIO Switch driver"); -MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From a1d26ac0ddc4ea561e17a75dd3b5f9d3c1812f16 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 25 Apr 2012 11:47:02 +0300 Subject: Extcon: check for allocation failure Return -ENOMEM if the kmalloc() fails. Signed-off-by: Dan Carpenter Acked-by: MyungJoo Ham Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index dbd3bfba42da..53c64a98b0be 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -621,6 +621,8 @@ int extcon_dev_register(struct extcon_dev *edev, struct device *dev) } edev->dev = kzalloc(sizeof(struct device), GFP_KERNEL); + if (!edev->dev) + return -ENOMEM; edev->dev->parent = dev; edev->dev->class = extcon_class; edev->dev->release = extcon_dev_release; -- cgit v1.2.3-70-g09d2 From f4cce69611ee941bac0729c6069795f106905ef9 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Fri, 27 Apr 2012 15:17:28 +0900 Subject: Extcon: Notify changed state for only one cable to notifee This patch inform the state of only one cable instead of previous data including the state of 32 cables to notifee which use extcon_register_interest() function to monitor whether the specific cable is attachd or detached. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham Signed-off-by: Kyungmin Park Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 53c64a98b0be..4657ad38164b 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -425,8 +425,15 @@ static int _call_per_cable(struct notifier_block *nb, unsigned long val, if ((val & (1 << obj->cable_index)) != (edev->state & (1 << obj->cable_index))) { + bool cable_state = true; + obj->previous_value = val; - return obj->user_nb->notifier_call(obj->user_nb, val, ptr); + + if (val & (1 << obj->cable_index)) + cable_state = false; + + return obj->user_nb->notifier_call(obj->user_nb, + cable_state, ptr); } return NOTIFY_OK; -- cgit v1.2.3-70-g09d2 From 2878bda864c53db599c4a3e3b136ed3e2af68ca0 Mon Sep 17 00:00:00 2001 From: H Hartley Sweeten Date: Wed, 2 May 2012 15:38:44 -0700 Subject: Extcon: fix section mismatch in extcon_gpio.c Fix the section mismatch be renaming the struct platform_driver variable. Signed-off-by: H Hartley Sweeten Cc: MyungJoo Ham Cc: Mark Brown Cc: Kyungmin Park Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_gpio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon_gpio.c b/drivers/extcon/extcon_gpio.c index 5c0f08506f93..fe7a07b47336 100644 --- a/drivers/extcon/extcon_gpio.c +++ b/drivers/extcon/extcon_gpio.c @@ -153,7 +153,7 @@ static int __devexit gpio_extcon_remove(struct platform_device *pdev) return 0; } -static struct platform_driver gpio_extcon = { +static struct platform_driver gpio_extcon_driver = { .probe = gpio_extcon_probe, .remove = __devexit_p(gpio_extcon_remove), .driver = { @@ -162,7 +162,7 @@ static struct platform_driver gpio_extcon = { }, }; -module_platform_driver(gpio_extcon); +module_platform_driver(gpio_extcon_driver); MODULE_AUTHOR("Mike Lockwood "); MODULE_DESCRIPTION("GPIO extcon driver"); -- cgit v1.2.3-70-g09d2 From 0e1507c8453081c9a6a515b92f89dd00b68f5c09 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 2 May 2012 10:38:51 +0100 Subject: extcon: Add EXTCON_MECHANICAL cable type for physical presence Some accessory detection mechanisms are able to detect that something is physically present in the socket separately to identifying what is present in the socket. This information can be useful to applications, for example allowing them to indicate that a potentially broken accessory is present, so provide a standard way to report it to userspace. Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon_class.c | 1 + include/linux/extcon.h | 1 + 2 files changed, 2 insertions(+) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon_class.c b/drivers/extcon/extcon_class.c index 4657ad38164b..f598a700ec15 100644 --- a/drivers/extcon/extcon_class.c +++ b/drivers/extcon/extcon_class.c @@ -60,6 +60,7 @@ const char *extcon_cable_name[] = { [EXTCON_SPDIF_OUT] = "SPDIF-out", [EXTCON_VIDEO_IN] = "Video-in", [EXTCON_VIDEO_OUT] = "Video-out", + [EXTCON_MECHANICAL] = "Mechanical", NULL, }; diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 6495f7731400..cdd401477656 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -66,6 +66,7 @@ enum extcon_cable_name { EXTCON_SPDIF_OUT, EXTCON_VIDEO_IN, EXTCON_VIDEO_OUT, + EXTCON_MECHANICAL, }; extern const char *extcon_cable_name[]; -- cgit v1.2.3-70-g09d2 From b76668ba8a7722f589af2e13a340f3629430a35a Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 9 May 2012 12:31:58 +0900 Subject: Extcon: add MAX8997 extcon driver This patch add extcon-max8997 driver to support the muic feature of Maxim max8997 by using Extcon framework. The extcon-max8997 driver is implemented based on 'drivers/misc/ max8997-muic.c' and then use Extcon interface instead of callback function in struct max8997_muic_platform_data to notify cable state of notifee which want to know always newly cable state when external connector(e.g., USB, TA, JIG) is attached or detached. v1 - Use Extcon interface to notify cable state of notifee instead of callback function when external connector is attached or detached. - Bug fix of getting platform_data for irq_base value. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham Signed-off-by: Kyungmin Park Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/Kconfig | 8 + drivers/extcon/Makefile | 1 + drivers/extcon/extcon-max8997.c | 537 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 546 insertions(+) create mode 100644 drivers/extcon/extcon-max8997.c (limited to 'drivers/extcon') diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 85cecff36db3..29c5cf852efc 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -21,4 +21,12 @@ config EXTCON_GPIO Say Y here to enable GPIO based extcon support. Note that GPIO extcon supports single state per extcon instance. +config EXTCON_MAX8997 + tristate "MAX8997 EXTCON Support" + depends on MFD_MAX8997 + help + If you say yes here you get support for the MUIC device of + Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory + detector and switch. + endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 2c46d4176d18..86020bdb6da0 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_EXTCON) += extcon_class.o obj-$(CONFIG_EXTCON_GPIO) += extcon_gpio.o +obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c new file mode 100644 index 000000000000..2b1aa04a27dd --- /dev/null +++ b/drivers/extcon/extcon-max8997.c @@ -0,0 +1,537 @@ +/* + * extcon-max8997.c - MAX8997 extcon driver to support MAX8997 MUIC + * + * Copyright (C) 2012 Samsung Electrnoics + * Donggeun Kim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEV_NAME "max8997-muic" + +/* MAX8997-MUIC STATUS1 register */ +#define STATUS1_ADC_SHIFT 0 +#define STATUS1_ADCLOW_SHIFT 5 +#define STATUS1_ADCERR_SHIFT 6 +#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) +#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) +#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) + +/* MAX8997-MUIC STATUS2 register */ +#define STATUS2_CHGTYP_SHIFT 0 +#define STATUS2_CHGDETRUN_SHIFT 3 +#define STATUS2_DCDTMR_SHIFT 4 +#define STATUS2_DBCHG_SHIFT 5 +#define STATUS2_VBVOLT_SHIFT 6 +#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) +#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) +#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) +#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT) +#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) + +/* MAX8997-MUIC STATUS3 register */ +#define STATUS3_OVP_SHIFT 2 +#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) + +/* MAX8997-MUIC CONTROL1 register */ +#define COMN1SW_SHIFT 0 +#define COMP2SW_SHIFT 3 +#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) +#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) +#define SW_MASK (COMP2SW_MASK | COMN1SW_MASK) + +#define MAX8997_SW_USB ((1 << COMP2SW_SHIFT) | (1 << COMN1SW_SHIFT)) +#define MAX8997_SW_AUDIO ((2 << COMP2SW_SHIFT) | (2 << COMN1SW_SHIFT)) +#define MAX8997_SW_UART ((3 << COMP2SW_SHIFT) | (3 << COMN1SW_SHIFT)) +#define MAX8997_SW_OPEN ((0 << COMP2SW_SHIFT) | (0 << COMN1SW_SHIFT)) + +#define MAX8997_ADC_GROUND 0x00 +#define MAX8997_ADC_MHL 0x01 +#define MAX8997_ADC_JIG_USB_1 0x18 +#define MAX8997_ADC_JIG_USB_2 0x19 +#define MAX8997_ADC_DESKDOCK 0x1a +#define MAX8997_ADC_JIG_UART 0x1c +#define MAX8997_ADC_CARDOCK 0x1d +#define MAX8997_ADC_OPEN 0x1f + +struct max8997_muic_irq { + unsigned int irq; + const char *name; +}; + +static struct max8997_muic_irq muic_irqs[] = { + { MAX8997_MUICIRQ_ADCError, "muic-ADC_error" }, + { MAX8997_MUICIRQ_ADCLow, "muic-ADC_low" }, + { MAX8997_MUICIRQ_ADC, "muic-ADC" }, + { MAX8997_MUICIRQ_VBVolt, "muic-VB_voltage" }, + { MAX8997_MUICIRQ_DBChg, "muic-DB_charger" }, + { MAX8997_MUICIRQ_DCDTmr, "muic-DCD_timer" }, + { MAX8997_MUICIRQ_ChgDetRun, "muic-CDR_status" }, + { MAX8997_MUICIRQ_ChgTyp, "muic-charger_type" }, + { MAX8997_MUICIRQ_OVP, "muic-over_voltage" }, +}; + +struct max8997_muic_info { + struct device *dev; + struct i2c_client *muic; + struct max8997_muic_platform_data *muic_pdata; + + int irq; + struct work_struct irq_work; + + enum max8997_muic_charger_type pre_charger_type; + int pre_adc; + + struct mutex mutex; + + struct extcon_dev *edev; +}; + +const char *max8997_extcon_cable[] = { + [0] = "USB", + [1] = "USB-Host", + [2] = "TA", + [3] = "Fast-charger", + [4] = "Slow-charger", + [5] = "Charge-downstream", + [6] = "MHL", + [7] = "Dock-desk", + [7] = "Dock-card", + [8] = "JIG", + + NULL, +}; + +static int max8997_muic_handle_usb(struct max8997_muic_info *info, + enum max8997_muic_usb_type usb_type, bool attached) +{ + int ret = 0; + + if (usb_type == MAX8997_USB_HOST) { + /* switch to USB */ + ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, + attached ? MAX8997_SW_USB : MAX8997_SW_OPEN, + SW_MASK); + if (ret) { + dev_err(info->dev, "failed to update muic register\n"); + goto out; + } + } + + switch (usb_type) { + case MAX8997_USB_HOST: + extcon_set_cable_state(info->edev, "USB-Host", attached); + break; + case MAX8997_USB_DEVICE: + extcon_set_cable_state(info->edev, "USB", attached); + break; + default: + ret = -EINVAL; + break; + } + +out: + return ret; +} + +static int max8997_muic_handle_dock(struct max8997_muic_info *info, + int adc, bool attached) +{ + int ret = 0; + + /* switch to AUDIO */ + ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, + attached ? MAX8997_SW_AUDIO : MAX8997_SW_OPEN, + SW_MASK); + if (ret) { + dev_err(info->dev, "failed to update muic register\n"); + goto out; + } + + switch (adc) { + case MAX8997_ADC_DESKDOCK: + extcon_set_cable_state(info->edev, "Dock-desk", attached); + break; + case MAX8997_ADC_CARDOCK: + extcon_set_cable_state(info->edev, "Dock-card", attached); + break; + default: + ret = -EINVAL; + break; + } +out: + return ret; +} + +static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, + bool attached) +{ + int ret = 0; + + /* switch to UART */ + ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, + attached ? MAX8997_SW_UART : MAX8997_SW_OPEN, + SW_MASK); + if (ret) { + dev_err(info->dev, "failed to update muic register\n"); + goto out; + } + + extcon_set_cable_state(info->edev, "JIG", attached); +out: + return ret; +} + +static int max8997_muic_handle_adc_detach(struct max8997_muic_info *info) +{ + int ret = 0; + + switch (info->pre_adc) { + case MAX8997_ADC_GROUND: + ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, false); + break; + case MAX8997_ADC_MHL: + extcon_set_cable_state(info->edev, "MHL", false); + break; + case MAX8997_ADC_JIG_USB_1: + case MAX8997_ADC_JIG_USB_2: + ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, false); + break; + case MAX8997_ADC_DESKDOCK: + case MAX8997_ADC_CARDOCK: + ret = max8997_muic_handle_dock(info, info->pre_adc, false); + break; + case MAX8997_ADC_JIG_UART: + ret = max8997_muic_handle_jig_uart(info, false); + break; + default: + break; + } + + return ret; +} + +static int max8997_muic_handle_adc(struct max8997_muic_info *info, int adc) +{ + int ret = 0; + + switch (adc) { + case MAX8997_ADC_GROUND: + ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, true); + break; + case MAX8997_ADC_MHL: + extcon_set_cable_state(info->edev, "MHL", true); + break; + case MAX8997_ADC_JIG_USB_1: + case MAX8997_ADC_JIG_USB_2: + ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, true); + break; + case MAX8997_ADC_DESKDOCK: + case MAX8997_ADC_CARDOCK: + ret = max8997_muic_handle_dock(info, adc, true); + break; + case MAX8997_ADC_JIG_UART: + ret = max8997_muic_handle_jig_uart(info, true); + break; + case MAX8997_ADC_OPEN: + ret = max8997_muic_handle_adc_detach(info); + break; + default: + ret = -EINVAL; + goto out; + } + + info->pre_adc = adc; +out: + return ret; +} + +static int max8997_muic_handle_charger_type_detach( + struct max8997_muic_info *info) +{ + int ret = 0; + + switch (info->pre_charger_type) { + case MAX8997_CHARGER_TYPE_USB: + extcon_set_cable_state(info->edev, "USB", false); + break; + case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: + extcon_set_cable_state(info->edev, "Charge-downstream", false); + break; + case MAX8997_CHARGER_TYPE_DEDICATED_CHG: + extcon_set_cable_state(info->edev, "TA", false); + break; + case MAX8997_CHARGER_TYPE_500MA: + extcon_set_cable_state(info->edev, "Slow-charger", false); + break; + case MAX8997_CHARGER_TYPE_1A: + extcon_set_cable_state(info->edev, "Fast-charger", false); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int max8997_muic_handle_charger_type(struct max8997_muic_info *info, + enum max8997_muic_charger_type charger_type) +{ + u8 adc; + int ret; + + ret = max8997_read_reg(info->muic, MAX8997_MUIC_REG_STATUS1, &adc); + if (ret) { + dev_err(info->dev, "failed to read muic register\n"); + goto out; + } + + switch (charger_type) { + case MAX8997_CHARGER_TYPE_NONE: + ret = max8997_muic_handle_charger_type_detach(info); + break; + case MAX8997_CHARGER_TYPE_USB: + if ((adc & STATUS1_ADC_MASK) == MAX8997_ADC_OPEN) { + max8997_muic_handle_usb(info, + MAX8997_USB_DEVICE, true); + } + break; + case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: + extcon_set_cable_state(info->edev, "Charge-downstream", true); + break; + case MAX8997_CHARGER_TYPE_DEDICATED_CHG: + extcon_set_cable_state(info->edev, "TA", true); + break; + case MAX8997_CHARGER_TYPE_500MA: + extcon_set_cable_state(info->edev, "Slow-charger", true); + break; + case MAX8997_CHARGER_TYPE_1A: + extcon_set_cable_state(info->edev, "Fast-charger", true); + break; + default: + ret = -EINVAL; + goto out; + } + + info->pre_charger_type = charger_type; +out: + return ret; +} + +static void max8997_muic_irq_work(struct work_struct *work) +{ + struct max8997_muic_info *info = container_of(work, + struct max8997_muic_info, irq_work); + struct max8997_dev *max8997 = i2c_get_clientdata(info->muic); + u8 status[2]; + u8 adc, chg_type; + + int irq_type = info->irq - max8997->irq_base; + int ret; + + mutex_lock(&info->mutex); + + ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, + 2, status); + if (ret) { + dev_err(info->dev, "failed to read muic register\n"); + mutex_unlock(&info->mutex); + return; + } + + dev_dbg(info->dev, "%s: STATUS1:0x%x, 2:0x%x\n", __func__, + status[0], status[1]); + + switch (irq_type) { + case MAX8997_MUICIRQ_ADC: + adc = status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + max8997_muic_handle_adc(info, adc); + break; + case MAX8997_MUICIRQ_ChgTyp: + chg_type = status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + max8997_muic_handle_charger_type(info, chg_type); + break; + default: + dev_info(info->dev, "misc interrupt: irq %d occurred\n", + irq_type); + break; + } + + mutex_unlock(&info->mutex); + + return; +} + +static irqreturn_t max8997_muic_irq_handler(int irq, void *data) +{ + struct max8997_muic_info *info = data; + + dev_dbg(info->dev, "irq:%d\n", irq); + info->irq = irq; + + schedule_work(&info->irq_work); + + return IRQ_HANDLED; +} + +static void max8997_muic_detect_dev(struct max8997_muic_info *info) +{ + int ret; + u8 status[2], adc, chg_type; + + ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, + 2, status); + if (ret) { + dev_err(info->dev, "failed to read muic register\n"); + return; + } + + dev_info(info->dev, "STATUS1:0x%x, STATUS2:0x%x\n", + status[0], status[1]); + + adc = status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + chg_type = status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + max8997_muic_handle_adc(info, adc); + max8997_muic_handle_charger_type(info, chg_type); +} + +static int __devinit max8997_muic_probe(struct platform_device *pdev) +{ + struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent); + struct max8997_platform_data *pdata = dev_get_platdata(max8997->dev); + struct max8997_muic_info *info; + int ret, i; + + info = kzalloc(sizeof(struct max8997_muic_info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + ret = -ENOMEM; + goto err_kfree; + } + + info->dev = &pdev->dev; + info->muic = max8997->muic; + + platform_set_drvdata(pdev, info); + mutex_init(&info->mutex); + + INIT_WORK(&info->irq_work, max8997_muic_irq_work); + + for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { + struct max8997_muic_irq *muic_irq = &muic_irqs[i]; + + ret = request_threaded_irq(pdata->irq_base + muic_irq->irq, + NULL, max8997_muic_irq_handler, + 0, muic_irq->name, + info); + if (ret) { + dev_err(&pdev->dev, + "failed: irq request (IRQ: %d," + " error :%d)\n", + muic_irq->irq, ret); + + for (i = i - 1; i >= 0; i--) + free_irq(muic_irq->irq, info); + + goto err_irq; + } + } + + /* External connector */ + info->edev = kzalloc(sizeof(struct extcon_dev), GFP_KERNEL); + if (!info->edev) { + dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); + ret = -ENOMEM; + goto err_irq; + } + info->edev->name = DEV_NAME; + info->edev->supported_cable = max8997_extcon_cable; + ret = extcon_dev_register(info->edev, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to register extcon device\n"); + goto err_extcon; + } + + /* Initialize registers according to platform data */ + if (pdata->muic_pdata) { + struct max8997_muic_platform_data *mdata = info->muic_pdata; + + for (i = 0; i < mdata->num_init_data; i++) { + max8997_write_reg(info->muic, mdata->init_data[i].addr, + mdata->init_data[i].data); + } + } + + /* Initial device detection */ + max8997_muic_detect_dev(info); + + return ret; + +err_extcon: + kfree(info->edev); +err_irq: + kfree(info); +err_kfree: + return ret; +} + +static int __devexit max8997_muic_remove(struct platform_device *pdev) +{ + struct max8997_muic_info *info = platform_get_drvdata(pdev); + struct max8997_dev *max8997 = i2c_get_clientdata(info->muic); + int i; + + for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) + free_irq(max8997->irq_base + muic_irqs[i].irq, info); + cancel_work_sync(&info->irq_work); + + extcon_dev_unregister(info->edev); + + kfree(info); + + return 0; +} + +static struct platform_driver max8997_muic_driver = { + .driver = { + .name = DEV_NAME, + .owner = THIS_MODULE, + }, + .probe = max8997_muic_probe, + .remove = __devexit_p(max8997_muic_remove), +}; + +module_platform_driver(max8997_muic_driver); + +MODULE_DESCRIPTION("Maxim MAX8997 Extcon driver"); +MODULE_AUTHOR("Donggeun Kim "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2