From 4f0be26a7939d8cae5d010b9de33550a366ceef4 Mon Sep 17 00:00:00 2001 From: Jaewon Kim Date: Thu, 29 Jan 2015 17:45:23 +0900 Subject: extcon: max77693: Fix cable name of MHL-TA This patch fixes extcon cable name of MHL-TA instead of MHL_TA to unify cable name style. Signed-off-by: Jaewon Kim Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max77693.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index af165fd0c6f5..9aca3b7719b9 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -228,7 +228,7 @@ static const char *max77693_extcon_cable[] = { [EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger", [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream", [EXTCON_CABLE_MHL] = "MHL", - [EXTCON_CABLE_MHL_TA] = "MHL_TA", + [EXTCON_CABLE_MHL_TA] = "MHL-TA", [EXTCON_CABLE_JIG_USB_ON] = "JIG-USB-ON", [EXTCON_CABLE_JIG_USB_OFF] = "JIG-USB-OFF", [EXTCON_CABLE_JIG_UART_OFF] = "JIG-UART-OFF", @@ -823,19 +823,19 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) case MAX77693_MUIC_GND_MHL: case MAX77693_MUIC_GND_MHL_VB: /* - * MHL cable with MHL_TA(USB/TA) cable + * MHL cable with MHL-TA(USB/TA) cable * - MHL cable include two port(HDMI line and separate * micro-usb port. When the target connect MHL cable, - * extcon driver check whether MHL_TA(USB/TA) cable is - * connected. If MHL_TA cable is connected, extcon + * extcon driver check whether MHL-TA(USB/TA) cable is + * connected. If MHL-TA cable is connected, extcon * driver notify state to notifiee for charging battery. * - * Features of 'MHL_TA(USB/TA) with MHL cable' + * Features of 'MHL-TA(USB/TA) with MHL cable' * - Support MHL * - Support charging through micro-usb port without * data connection */ - extcon_set_cable_state(info->edev, "MHL_TA", attached); + extcon_set_cable_state(info->edev, "MHL-TA", attached); if (!cable_attached) extcon_set_cable_state(info->edev, "MHL", cable_attached); -- cgit v1.2.3-59-g8ed1b From 4c883abee08b13d7fac5e672159575bb7a3365a6 Mon Sep 17 00:00:00 2001 From: Jaewon Kim Date: Thu, 29 Jan 2015 17:45:24 +0900 Subject: extcon: max77693: Use HOST term to express USB-HOST cable instead of OTG term This patch unifies the term called 'USB_OTG' and 'USB_HOST' into USB_HOST. OTG(On-The-Go) function supports USB host and this driver sents 'USB-Host event. So, unifies term to USB_HOST. Signed-off-by: Jaewon Kim [cw00.choi: Fix patch title to indicate the correct meaning of patch] Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max77693.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 9aca3b7719b9..dfcc5cba25f0 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -190,8 +190,8 @@ enum max77693_muic_acc_type { /* The below accessories have same ADC value so ADCLow and ADC1K bit is used to separate specific accessory */ /* ADC|VBVolot|ADCLow|ADC1K| */ - MAX77693_MUIC_GND_USB_OTG = 0x100, /* 0x0| 0| 0| 0| */ - MAX77693_MUIC_GND_USB_OTG_VB = 0x104, /* 0x0| 1| 0| 0| */ + MAX77693_MUIC_GND_USB_HOST = 0x100, /* 0x0| 0| 0| 0| */ + MAX77693_MUIC_GND_USB_HOST_VB = 0x104, /* 0x0| 1| 0| 0| */ MAX77693_MUIC_GND_AV_CABLE_LOAD = 0x102,/* 0x0| 0| 1| 0| */ MAX77693_MUIC_GND_MHL = 0x103, /* 0x0| 0| 1| 1| */ MAX77693_MUIC_GND_MHL_VB = 0x107, /* 0x0| 1| 1| 1| */ @@ -403,8 +403,8 @@ static int max77693_muic_get_cable_type(struct max77693_muic_info *info, /** * [0x1|VBVolt|ADCLow|ADC1K] - * [0x1| 0| 0| 0] USB_OTG - * [0x1| 1| 0| 0] USB_OTG_VB + * [0x1| 0| 0| 0] USB_HOST + * [0x1| 1| 0| 0] USB_HSOT_VB * [0x1| 0| 1| 0] Audio Video cable with load * [0x1| 0| 1| 1] MHL without charging cable * [0x1| 1| 1| 1] MHL with charging cable @@ -523,7 +523,7 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info, * - Support charging and data connection through micro-usb port * if USB cable is connected between target and host * device. - * - Support OTG device (Mouse/Keyboard) + * - Support OTG(On-The-Go) device (Ex: Mouse/Keyboard) */ ret = max77693_muic_set_path(info, info->path_usb, attached); if (ret < 0) @@ -609,9 +609,9 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info) MAX77693_CABLE_GROUP_ADC_GND, &attached); switch (cable_type_gnd) { - case MAX77693_MUIC_GND_USB_OTG: - case MAX77693_MUIC_GND_USB_OTG_VB: - /* USB_OTG, PATH: AP_USB */ + case MAX77693_MUIC_GND_USB_HOST: + case MAX77693_MUIC_GND_USB_HOST_VB: + /* USB_HOST, PATH: AP_USB */ ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); if (ret < 0) return ret; @@ -704,7 +704,7 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info) switch (cable_type) { case MAX77693_MUIC_ADC_GROUND: - /* USB_OTG/MHL/Audio */ + /* USB_HOST/MHL/Audio */ max77693_muic_adc_ground_handler(info); break; case MAX77693_MUIC_ADC_FACTORY_MODE_USB_OFF: @@ -886,7 +886,7 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) * - Support charging and data connection through micro- * usb port if USB cable is connected between target * and host device - * - Support OTG device (Mouse/Keyboard) + * - Support OTG(On-The-Go) device (Ex: Mouse/Keyboard) */ ret = max77693_muic_set_path(info, info->path_usb, attached); -- cgit v1.2.3-59-g8ed1b From e52817faae359ce95c93c2b6eb88b16d4b430181 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Mon, 2 Feb 2015 12:21:59 +0200 Subject: extcon: usb-gpio: Introduce gpio usb extcon driver This driver observes the USB ID pin connected over a GPIO and updates the USB cable extcon states accordingly. The existing GPIO extcon driver is not suitable for this purpose as it needs to be taught to understand USB cable states and it can't handle more than one cable per instance. For the USB case we need to handle 2 cable states. 1) USB (attach/detach) 2) USB-HOST (attach/detach) This driver can be easily updated in the future to handle VBUS events in case it happens to be available on GPIO for any platform. Signed-off-by: Roger Quadros Reviewed-by: Felipe Balbi Acked-by: Felipe Balbi Signed-off-by: Chanwoo Choi --- .../devicetree/bindings/extcon/extcon-usb-gpio.txt | 18 ++ drivers/extcon/Kconfig | 7 + drivers/extcon/Makefile | 1 + drivers/extcon/extcon-usb-gpio.c | 237 +++++++++++++++++++++ 4 files changed, 263 insertions(+) create mode 100644 Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt create mode 100644 drivers/extcon/extcon-usb-gpio.c (limited to 'drivers/extcon') diff --git a/Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt b/Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt new file mode 100644 index 000000000000..af0b903de293 --- /dev/null +++ b/Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt @@ -0,0 +1,18 @@ +USB GPIO Extcon device + +This is a virtual device used to generate USB cable states from the USB ID pin +connected to a GPIO pin. + +Required properties: +- compatible: Should be "linux,extcon-usb-gpio" +- id-gpio: gpio for USB ID pin. See gpio binding. + +Example: Examples of extcon-usb-gpio node in dra7-evm.dts as listed below: + extcon_usb1 { + compatible = "linux,extcon-usb-gpio"; + id-gpio = <&gpio6 1 GPIO_ACTIVE_HIGH>; + } + + &omap_dwc3_1 { + extcon = <&extcon_usb1>; + }; diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 6a1f7de6fa54..e4c01ab046f7 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -93,4 +93,11 @@ config EXTCON_SM5502 Silicon Mitus SM5502. The SM5502 is a USB port accessory detector and switch. +config EXTCON_USB_GPIO + tristate "USB GPIO extcon support" + depends on GPIOLIB + help + Say Y here to enable GPIO based USB cable detection extcon support. + Used typically if GPIO is used for USB ID pin detection. + endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 0370b42e5a27..6a08a98dc069 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o +obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c new file mode 100644 index 000000000000..3f0bad3ce8aa --- /dev/null +++ b/drivers/extcon/extcon-usb-gpio.c @@ -0,0 +1,237 @@ +/** + * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com + * Author: Roger Quadros + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 + +#define USB_GPIO_DEBOUNCE_MS 20 /* ms */ + +struct usb_extcon_info { + struct device *dev; + struct extcon_dev *edev; + + struct gpio_desc *id_gpiod; + int id_irq; + + unsigned long debounce_jiffies; + struct delayed_work wq_detcable; +}; + +/* List of detectable cables */ +enum { + EXTCON_CABLE_USB = 0, + EXTCON_CABLE_USB_HOST, + + EXTCON_CABLE_END, +}; + +static const char *usb_extcon_cable[] = { + [EXTCON_CABLE_USB] = "USB", + [EXTCON_CABLE_USB_HOST] = "USB-HOST", + NULL, +}; + +static void usb_extcon_detect_cable(struct work_struct *work) +{ + int id; + struct usb_extcon_info *info = container_of(to_delayed_work(work), + struct usb_extcon_info, + wq_detcable); + + /* check ID and update cable state */ + id = gpiod_get_value_cansleep(info->id_gpiod); + if (id) { + /* + * ID = 1 means USB HOST cable detached. + * As we don't have event for USB peripheral cable attached, + * we simulate USB peripheral attach here. + */ + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB_HOST], + false); + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB], + true); + } else { + /* + * ID = 0 means USB HOST cable attached. + * As we don't have event for USB peripheral cable detached, + * we simulate USB peripheral detach here. + */ + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB], + false); + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB_HOST], + true); + } +} + +static irqreturn_t usb_irq_handler(int irq, void *dev_id) +{ + struct usb_extcon_info *info = dev_id; + + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + info->debounce_jiffies); + + return IRQ_HANDLED; +} + +static int usb_extcon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct usb_extcon_info *info; + int ret; + + if (!np) + return -EINVAL; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = dev; + info->id_gpiod = devm_gpiod_get(&pdev->dev, "id"); + if (IS_ERR(info->id_gpiod)) { + dev_err(dev, "failed to get ID GPIO\n"); + return PTR_ERR(info->id_gpiod); + } + + ret = gpiod_set_debounce(info->id_gpiod, + USB_GPIO_DEBOUNCE_MS * 1000); + if (ret < 0) + info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); + + INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); + + info->id_irq = gpiod_to_irq(info->id_gpiod); + if (info->id_irq < 0) { + dev_err(dev, "failed to get ID IRQ\n"); + return info->id_irq; + } + + ret = devm_request_threaded_irq(dev, info->id_irq, NULL, + usb_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + pdev->name, info); + if (ret < 0) { + dev_err(dev, "failed to request handler for ID IRQ\n"); + return ret; + } + + info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); + if (IS_ERR(info->edev)) { + dev_err(dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + + ret = devm_extcon_dev_register(dev, info->edev); + if (ret < 0) { + dev_err(dev, "failed to register extcon device\n"); + return ret; + } + + platform_set_drvdata(pdev, info); + device_init_wakeup(dev, 1); + + /* Perform initial detection */ + usb_extcon_detect_cable(&info->wq_detcable.work); + + return 0; +} + +static int usb_extcon_remove(struct platform_device *pdev) +{ + struct usb_extcon_info *info = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&info->wq_detcable); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int usb_extcon_suspend(struct device *dev) +{ + struct usb_extcon_info *info = dev_get_drvdata(dev); + int ret = 0; + + if (device_may_wakeup(dev)) { + ret = enable_irq_wake(info->id_irq); + if (ret) + return ret; + } + + /* + * We don't want to process any IRQs after this point + * as GPIOs used behind I2C subsystem might not be + * accessible until resume completes. So disable IRQ. + */ + disable_irq(info->id_irq); + + return ret; +} + +static int usb_extcon_resume(struct device *dev) +{ + struct usb_extcon_info *info = dev_get_drvdata(dev); + int ret = 0; + + if (device_may_wakeup(dev)) { + ret = disable_irq_wake(info->id_irq); + if (ret) + return ret; + } + + enable_irq(info->id_irq); + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, + usb_extcon_suspend, usb_extcon_resume); + +static struct of_device_id usb_extcon_dt_match[] = { + { .compatible = "linux,extcon-usb-gpio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, usb_extcon_dt_match); + +static struct platform_driver usb_extcon_driver = { + .probe = usb_extcon_probe, + .remove = usb_extcon_remove, + .driver = { + .name = "extcon-usb-gpio", + .pm = &usb_extcon_pm_ops, + .of_match_table = usb_extcon_dt_match, + }, +}; + +module_platform_driver(usb_extcon_driver); + +MODULE_AUTHOR("Roger Quadros "); +MODULE_DESCRIPTION("USB GPIO extcon driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 27a28d32b4f22a4ae687837aeda6afb42116cca4 Mon Sep 17 00:00:00 2001 From: Jaewon Kim Date: Wed, 4 Feb 2015 13:56:07 +0900 Subject: extcon: max77843: Add max77843 MUIC driver This patch adds MAX77843 extcon driver to support for MUIC(Micro USB Interface Controller) device by using EXTCON subsystem to handle various external connectors. Signed-off-by: Jaewon Kim Signed-off-by: Chanwoo Choi --- drivers/extcon/Kconfig | 10 + drivers/extcon/Makefile | 1 + drivers/extcon/extcon-max77843.c | 881 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 892 insertions(+) create mode 100644 drivers/extcon/extcon-max77843.c (limited to 'drivers/extcon') diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index e4c01ab046f7..fdc0bf0543ce 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -55,6 +55,16 @@ config EXTCON_MAX77693 Maxim MAX77693 PMIC. The MAX77693 MUIC is a USB port accessory detector and switch. +config EXTCON_MAX77843 + tristate "MAX77843 EXTCON Support" + depends on MFD_MAX77843 + select IRQ_DOMAIN + select REGMAP_I2C + help + If you say yes here you get support for the MUIC device of + Maxim MAX77843. The MAX77843 MUIC is a USB port accessory + detector add switch. + config EXTCON_MAX8997 tristate "MAX8997 EXTCON Support" depends on MFD_MAX8997 && IRQ_DOMAIN diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 6a08a98dc069..e1eb2d50e72b 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o +obj-$(CONFIG_EXTCON_MAX77843) += extcon-max77843.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o diff --git a/drivers/extcon/extcon-max77843.c b/drivers/extcon/extcon-max77843.c new file mode 100644 index 000000000000..598a017cc91b --- /dev/null +++ b/drivers/extcon/extcon-max77843.c @@ -0,0 +1,881 @@ +/* + * extcon-max77843.c - Maxim MAX77843 extcon driver to support + * MUIC(Micro USB Interface Controller) + * + * Copyright (C) 2015 Samsung Electronics + * Author: Jaewon 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DELAY_MS_DEFAULT 15000 /* unit: millisecond */ + +enum max77843_muic_status { + MAX77843_MUIC_STATUS1 = 0, + MAX77843_MUIC_STATUS2, + MAX77843_MUIC_STATUS3, + + MAX77843_MUIC_STATUS_NUM, +}; + +struct max77843_muic_info { + struct device *dev; + struct max77843 *max77843; + struct extcon_dev *edev; + + struct mutex mutex; + struct work_struct irq_work; + struct delayed_work wq_detcable; + + u8 status[MAX77843_MUIC_STATUS_NUM]; + int prev_cable_type; + int prev_chg_type; + int prev_gnd_type; + + bool irq_adc; + bool irq_chg; +}; + +enum max77843_muic_cable_group { + MAX77843_CABLE_GROUP_ADC = 0, + MAX77843_CABLE_GROUP_ADC_GND, + MAX77843_CABLE_GROUP_CHG, +}; + +enum max77843_muic_adc_debounce_time { + MAX77843_DEBOUNCE_TIME_5MS = 0, + MAX77843_DEBOUNCE_TIME_10MS, + MAX77843_DEBOUNCE_TIME_25MS, + MAX77843_DEBOUNCE_TIME_38_62MS, +}; + +/* Define accessory cable type */ +enum max77843_muic_accessory_type { + MAX77843_MUIC_ADC_GROUND = 0, + MAX77843_MUIC_ADC_SEND_END_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S1_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S2_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S3_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S4_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S5_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S6_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S7_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S8_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S9_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S10_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S11_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S12_BUTTON, + MAX77843_MUIC_ADC_RESERVED_ACC_1, + MAX77843_MUIC_ADC_RESERVED_ACC_2, + MAX77843_MUIC_ADC_RESERVED_ACC_3, + MAX77843_MUIC_ADC_RESERVED_ACC_4, + MAX77843_MUIC_ADC_RESERVED_ACC_5, + MAX77843_MUIC_ADC_AUDIO_DEVICE_TYPE2, + MAX77843_MUIC_ADC_PHONE_POWERED_DEV, + MAX77843_MUIC_ADC_TTY_CONVERTER, + MAX77843_MUIC_ADC_UART_CABLE, + MAX77843_MUIC_ADC_CEA936A_TYPE1_CHG, + MAX77843_MUIC_ADC_FACTORY_MODE_USB_OFF, + MAX77843_MUIC_ADC_FACTORY_MODE_USB_ON, + MAX77843_MUIC_ADC_AV_CABLE_NOLOAD, + MAX77843_MUIC_ADC_CEA936A_TYPE2_CHG, + MAX77843_MUIC_ADC_FACTORY_MODE_UART_OFF, + MAX77843_MUIC_ADC_FACTORY_MODE_UART_ON, + MAX77843_MUIC_ADC_AUDIO_DEVICE_TYPE1, + MAX77843_MUIC_ADC_OPEN, + + /* The blow accessories should check + not only ADC value but also ADC1K and VBVolt value. */ + /* Offset|ADC1K|VBVolt| */ + MAX77843_MUIC_GND_USB_HOST = 0x100, /* 0x1| 0| 0| */ + MAX77843_MUIC_GND_USB_HOST_VB = 0x101, /* 0x1| 0| 1| */ + MAX77843_MUIC_GND_MHL = 0x102, /* 0x1| 1| 0| */ + MAX77843_MUIC_GND_MHL_VB = 0x103, /* 0x1| 1| 1| */ +}; + +/* Define charger cable type */ +enum max77843_muic_charger_type { + MAX77843_MUIC_CHG_NONE = 0, + MAX77843_MUIC_CHG_USB, + MAX77843_MUIC_CHG_DOWNSTREAM, + MAX77843_MUIC_CHG_DEDICATED, + MAX77843_MUIC_CHG_SPECIAL_500MA, + MAX77843_MUIC_CHG_SPECIAL_1A, + MAX77843_MUIC_CHG_SPECIAL_BIAS, + MAX77843_MUIC_CHG_RESERVED, + MAX77843_MUIC_CHG_GND, +}; + +enum { + MAX77843_CABLE_USB = 0, + MAX77843_CABLE_USB_HOST, + MAX77843_CABLE_TA, + MAX77843_CABLE_CHARGE_DOWNSTREAM, + MAX77843_CABLE_FAST_CHARGER, + MAX77843_CABLE_SLOW_CHARGER, + MAX77843_CABLE_MHL, + MAX77843_CABLE_MHL_TA, + MAX77843_CABLE_JIG_USB_ON, + MAX77843_CABLE_JIG_USB_OFF, + MAX77843_CABLE_JIG_UART_ON, + MAX77843_CABLE_JIG_UART_OFF, + + MAX77843_CABLE_NUM, +}; + +static const char *max77843_extcon_cable[] = { + [MAX77843_CABLE_USB] = "USB", + [MAX77843_CABLE_USB_HOST] = "USB-HOST", + [MAX77843_CABLE_TA] = "TA", + [MAX77843_CABLE_CHARGE_DOWNSTREAM] = "CHARGER-DOWNSTREAM", + [MAX77843_CABLE_FAST_CHARGER] = "FAST-CHARGER", + [MAX77843_CABLE_SLOW_CHARGER] = "SLOW-CHARGER", + [MAX77843_CABLE_MHL] = "MHL", + [MAX77843_CABLE_MHL_TA] = "MHL-TA", + [MAX77843_CABLE_JIG_USB_ON] = "JIG-USB-ON", + [MAX77843_CABLE_JIG_USB_OFF] = "JIG-USB-OFF", + [MAX77843_CABLE_JIG_UART_ON] = "JIG-UART-ON", + [MAX77843_CABLE_JIG_UART_OFF] = "JIG-UART-OFF", +}; + +struct max77843_muic_irq { + unsigned int irq; + const char *name; + unsigned int virq; +}; + +static struct max77843_muic_irq max77843_muic_irqs[] = { + { MAX77843_MUIC_IRQ_INT1_ADC, "MUIC-ADC" }, + { MAX77843_MUIC_IRQ_INT1_ADCERROR, "MUIC-ADC_ERROR" }, + { MAX77843_MUIC_IRQ_INT1_ADC1K, "MUIC-ADC1K" }, + { MAX77843_MUIC_IRQ_INT2_CHGTYP, "MUIC-CHGTYP" }, + { MAX77843_MUIC_IRQ_INT2_CHGDETRUN, "MUIC-CHGDETRUN" }, + { MAX77843_MUIC_IRQ_INT2_DCDTMR, "MUIC-DCDTMR" }, + { MAX77843_MUIC_IRQ_INT2_DXOVP, "MUIC-DXOVP" }, + { MAX77843_MUIC_IRQ_INT2_VBVOLT, "MUIC-VBVOLT" }, + { MAX77843_MUIC_IRQ_INT3_VBADC, "MUIC-VBADC" }, + { MAX77843_MUIC_IRQ_INT3_VDNMON, "MUIC-VDNMON" }, + { MAX77843_MUIC_IRQ_INT3_DNRES, "MUIC-DNRES" }, + { MAX77843_MUIC_IRQ_INT3_MPNACK, "MUIC-MPNACK"}, + { MAX77843_MUIC_IRQ_INT3_MRXBUFOW, "MUIC-MRXBUFOW"}, + { MAX77843_MUIC_IRQ_INT3_MRXTRF, "MUIC-MRXTRF"}, + { MAX77843_MUIC_IRQ_INT3_MRXPERR, "MUIC-MRXPERR"}, + { MAX77843_MUIC_IRQ_INT3_MRXRDY, "MUIC-MRXRDY"}, +}; + +static const struct regmap_config max77843_muic_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77843_MUIC_REG_END, +}; + +static const struct regmap_irq max77843_muic_irq[] = { + /* INT1 interrupt */ + { .reg_offset = 0, .mask = MAX77843_MUIC_ADC, }, + { .reg_offset = 0, .mask = MAX77843_MUIC_ADCERROR, }, + { .reg_offset = 0, .mask = MAX77843_MUIC_ADC1K, }, + + /* INT2 interrupt */ + { .reg_offset = 1, .mask = MAX77843_MUIC_CHGTYP, }, + { .reg_offset = 1, .mask = MAX77843_MUIC_CHGDETRUN, }, + { .reg_offset = 1, .mask = MAX77843_MUIC_DCDTMR, }, + { .reg_offset = 1, .mask = MAX77843_MUIC_DXOVP, }, + { .reg_offset = 1, .mask = MAX77843_MUIC_VBVOLT, }, + + /* INT3 interrupt */ + { .reg_offset = 2, .mask = MAX77843_MUIC_VBADC, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_VDNMON, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_DNRES, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_MPNACK, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_MRXBUFOW, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_MRXTRF, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_MRXPERR, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_MRXRDY, }, +}; + +static const struct regmap_irq_chip max77843_muic_irq_chip = { + .name = "max77843-muic", + .status_base = MAX77843_MUIC_REG_INT1, + .mask_base = MAX77843_MUIC_REG_INTMASK1, + .mask_invert = true, + .num_regs = 3, + .irqs = max77843_muic_irq, + .num_irqs = ARRAY_SIZE(max77843_muic_irq), +}; + +static int max77843_muic_set_path(struct max77843_muic_info *info, + u8 val, bool attached) +{ + struct max77843 *max77843 = info->max77843; + int ret = 0; + unsigned int ctrl1, ctrl2; + + if (attached) + ctrl1 = val; + else + ctrl1 = CONTROL1_SW_OPEN; + + ret = regmap_update_bits(max77843->regmap_muic, + MAX77843_MUIC_REG_CONTROL1, + CONTROL1_COM_SW, ctrl1); + if (ret < 0) { + dev_err(info->dev, "Cannot switch MUIC port\n"); + return ret; + } + + if (attached) + ctrl2 = MAX77843_MUIC_CONTROL2_CPEN_MASK; + else + ctrl2 = MAX77843_MUIC_CONTROL2_LOWPWR_MASK; + + ret = regmap_update_bits(max77843->regmap_muic, + MAX77843_MUIC_REG_CONTROL2, + MAX77843_MUIC_CONTROL2_LOWPWR_MASK | + MAX77843_MUIC_CONTROL2_CPEN_MASK, ctrl2); + if (ret < 0) { + dev_err(info->dev, "Cannot update lowpower mode\n"); + return ret; + } + + dev_dbg(info->dev, + "CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s\n", + ctrl1, ctrl2, attached ? "attached" : "detached"); + + return 0; +} + +static int max77843_muic_get_cable_type(struct max77843_muic_info *info, + enum max77843_muic_cable_group group, bool *attached) +{ + int adc, chg_type, cable_type, gnd_type; + + adc = info->status[MAX77843_MUIC_STATUS1] & + MAX77843_MUIC_STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + switch (group) { + case MAX77843_CABLE_GROUP_ADC: + if (adc == MAX77843_MUIC_ADC_OPEN) { + *attached = false; + cable_type = info->prev_cable_type; + info->prev_cable_type = MAX77843_MUIC_ADC_OPEN; + } else { + *attached = true; + cable_type = info->prev_cable_type = adc; + } + break; + case MAX77843_CABLE_GROUP_CHG: + chg_type = info->status[MAX77843_MUIC_STATUS2] & + MAX77843_MUIC_STATUS2_CHGTYP_MASK; + + /* Check GROUND accessory with charger cable */ + if (adc == MAX77843_MUIC_ADC_GROUND) { + if (chg_type == MAX77843_MUIC_CHG_NONE) { + /* The following state when charger cable is + * disconnected but the GROUND accessory still + * connected */ + *attached = false; + cable_type = info->prev_chg_type; + info->prev_chg_type = MAX77843_MUIC_CHG_NONE; + } else { + + /* The following state when charger cable is + * connected on the GROUND accessory */ + *attached = true; + cable_type = MAX77843_MUIC_CHG_GND; + info->prev_chg_type = MAX77843_MUIC_CHG_GND; + } + break; + } + + if (chg_type == MAX77843_MUIC_CHG_NONE) { + *attached = false; + cable_type = info->prev_chg_type; + info->prev_chg_type = MAX77843_MUIC_CHG_NONE; + } else { + *attached = true; + cable_type = info->prev_chg_type = chg_type; + } + break; + case MAX77843_CABLE_GROUP_ADC_GND: + if (adc == MAX77843_MUIC_ADC_OPEN) { + *attached = false; + cable_type = info->prev_gnd_type; + info->prev_gnd_type = MAX77843_MUIC_ADC_OPEN; + } else { + *attached = true; + + /* Offset|ADC1K|VBVolt| + * 0x1| 0| 0| USB-HOST + * 0x1| 0| 1| USB-HOST with VB + * 0x1| 1| 0| MHL + * 0x1| 1| 1| MHL with VB */ + /* Get ADC1K register bit */ + gnd_type = (info->status[MAX77843_MUIC_STATUS1] & + MAX77843_MUIC_STATUS1_ADC1K_MASK); + + /* Get VBVolt register bit */ + gnd_type |= (info->status[MAX77843_MUIC_STATUS2] & + MAX77843_MUIC_STATUS2_VBVOLT_MASK); + gnd_type >>= STATUS2_VBVOLT_SHIFT; + + /* Offset of GND cable */ + gnd_type |= MAX77843_MUIC_GND_USB_HOST; + cable_type = info->prev_gnd_type = gnd_type; + } + break; + default: + dev_err(info->dev, "Unknown cable group (%d)\n", group); + cable_type = -EINVAL; + break; + } + + return cable_type; +} + +static int max77843_muic_adc_gnd_handler(struct max77843_muic_info *info) +{ + int ret, gnd_cable_type; + bool attached; + + gnd_cable_type = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_ADC_GND, &attached); + dev_dbg(info->dev, "external connector is %s (gnd:0x%02x)\n", + attached ? "attached" : "detached", gnd_cable_type); + + switch (gnd_cable_type) { + case MAX77843_MUIC_GND_USB_HOST: + case MAX77843_MUIC_GND_USB_HOST_VB: + ret = max77843_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "USB-HOST", attached); + break; + case MAX77843_MUIC_GND_MHL_VB: + case MAX77843_MUIC_GND_MHL: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "MHL", attached); + break; + default: + dev_err(info->dev, "failed to detect %s accessory(gnd:0x%x)\n", + attached ? "attached" : "detached", gnd_cable_type); + return -EINVAL; + } + + return 0; +} + +static int max77843_muic_jig_handler(struct max77843_muic_info *info, + int cable_type, bool attached) +{ + int ret; + + dev_dbg(info->dev, "external connector is %s (adc:0x%02x)\n", + attached ? "attached" : "detached", cable_type); + + switch (cable_type) { + case MAX77843_MUIC_ADC_FACTORY_MODE_USB_OFF: + ret = max77843_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + return ret; + extcon_set_cable_state(info->edev, "JIG-USB-OFF", attached); + break; + case MAX77843_MUIC_ADC_FACTORY_MODE_USB_ON: + ret = max77843_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + return ret; + extcon_set_cable_state(info->edev, "JIG-USB-ON", attached); + break; + case MAX77843_MUIC_ADC_FACTORY_MODE_UART_OFF: + ret = max77843_muic_set_path(info, CONTROL1_SW_UART, attached); + if (ret < 0) + return ret; + extcon_set_cable_state(info->edev, "JIG-UART-OFF", attached); + break; + default: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + break; + } + + return 0; +} + +static int max77843_muic_adc_handler(struct max77843_muic_info *info) +{ + int ret, cable_type; + bool attached; + + cable_type = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_ADC, &attached); + + dev_dbg(info->dev, + "external connector is %s (adc:0x%02x, prev_adc:0x%x)\n", + attached ? "attached" : "detached", cable_type, + info->prev_cable_type); + + switch (cable_type) { + case MAX77843_MUIC_ADC_GROUND: + ret = max77843_muic_adc_gnd_handler(info); + if (ret < 0) + return ret; + break; + case MAX77843_MUIC_ADC_FACTORY_MODE_USB_OFF: + case MAX77843_MUIC_ADC_FACTORY_MODE_USB_ON: + case MAX77843_MUIC_ADC_FACTORY_MODE_UART_OFF: + ret = max77843_muic_jig_handler(info, cable_type, attached); + if (ret < 0) + return ret; + break; + case MAX77843_MUIC_ADC_SEND_END_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S1_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S2_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S3_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S4_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S5_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S6_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S7_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S8_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S9_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S10_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S11_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S12_BUTTON: + case MAX77843_MUIC_ADC_RESERVED_ACC_1: + case MAX77843_MUIC_ADC_RESERVED_ACC_2: + case MAX77843_MUIC_ADC_RESERVED_ACC_3: + case MAX77843_MUIC_ADC_RESERVED_ACC_4: + case MAX77843_MUIC_ADC_RESERVED_ACC_5: + case MAX77843_MUIC_ADC_AUDIO_DEVICE_TYPE2: + case MAX77843_MUIC_ADC_PHONE_POWERED_DEV: + case MAX77843_MUIC_ADC_TTY_CONVERTER: + case MAX77843_MUIC_ADC_UART_CABLE: + case MAX77843_MUIC_ADC_CEA936A_TYPE1_CHG: + case MAX77843_MUIC_ADC_AV_CABLE_NOLOAD: + case MAX77843_MUIC_ADC_CEA936A_TYPE2_CHG: + case MAX77843_MUIC_ADC_FACTORY_MODE_UART_ON: + case MAX77843_MUIC_ADC_AUDIO_DEVICE_TYPE1: + case MAX77843_MUIC_ADC_OPEN: + dev_err(info->dev, + "accessory is %s but it isn't used (adc:0x%x)\n", + attached ? "attached" : "detached", cable_type); + return -EAGAIN; + default: + dev_err(info->dev, + "failed to detect %s accessory (adc:0x%x)\n", + attached ? "attached" : "detached", cable_type); + return -EINVAL; + } + + return 0; +} + +static int max77843_muic_chg_handler(struct max77843_muic_info *info) +{ + int ret, chg_type, gnd_type; + bool attached; + + chg_type = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_CHG, &attached); + + dev_dbg(info->dev, + "external connector is %s(chg_type:0x%x, prev_chg_type:0x%x)\n", + attached ? "attached" : "detached", + chg_type, info->prev_chg_type); + + switch (chg_type) { + case MAX77843_MUIC_CHG_USB: + ret = max77843_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "USB", attached); + break; + case MAX77843_MUIC_CHG_DOWNSTREAM: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, + "CHARGER-DOWNSTREAM", attached); + break; + case MAX77843_MUIC_CHG_DEDICATED: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "TA", attached); + break; + case MAX77843_MUIC_CHG_SPECIAL_500MA: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "SLOW-CHAREGER", attached); + break; + case MAX77843_MUIC_CHG_SPECIAL_1A: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "FAST-CHARGER", attached); + break; + case MAX77843_MUIC_CHG_GND: + gnd_type = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_ADC_GND, &attached); + + /* Charger cable on MHL accessory is attach or detach */ + if (gnd_type == MAX77843_MUIC_GND_MHL_VB) + extcon_set_cable_state(info->edev, "MHL-TA", true); + else if (gnd_type == MAX77843_MUIC_GND_MHL) + extcon_set_cable_state(info->edev, "MHL-TA", false); + break; + case MAX77843_MUIC_CHG_NONE: + break; + default: + dev_err(info->dev, + "failed to detect %s accessory (chg_type:0x%x)\n", + attached ? "attached" : "detached", chg_type); + + max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + return -EINVAL; + } + + return 0; +} + +static void max77843_muic_irq_work(struct work_struct *work) +{ + struct max77843_muic_info *info = container_of(work, + struct max77843_muic_info, irq_work); + struct max77843 *max77843 = info->max77843; + int ret = 0; + + mutex_lock(&info->mutex); + + ret = regmap_bulk_read(max77843->regmap_muic, + MAX77843_MUIC_REG_STATUS1, info->status, + MAX77843_MUIC_STATUS_NUM); + if (ret) { + dev_err(info->dev, "Cannot read STATUS registers\n"); + mutex_unlock(&info->mutex); + return; + } + + if (info->irq_adc) { + ret = max77843_muic_adc_handler(info); + if (ret) + dev_err(info->dev, "Unknown cable type\n"); + info->irq_adc = false; + } + + if (info->irq_chg) { + ret = max77843_muic_chg_handler(info); + if (ret) + dev_err(info->dev, "Unknown charger type\n"); + info->irq_chg = false; + } + + mutex_unlock(&info->mutex); +} + +static irqreturn_t max77843_muic_irq_handler(int irq, void *data) +{ + struct max77843_muic_info *info = data; + int i, irq_type = -1; + + for (i = 0; i < ARRAY_SIZE(max77843_muic_irqs); i++) + if (irq == max77843_muic_irqs[i].virq) + irq_type = max77843_muic_irqs[i].irq; + + switch (irq_type) { + case MAX77843_MUIC_IRQ_INT1_ADC: + case MAX77843_MUIC_IRQ_INT1_ADCERROR: + case MAX77843_MUIC_IRQ_INT1_ADC1K: + info->irq_adc = true; + break; + case MAX77843_MUIC_IRQ_INT2_CHGTYP: + case MAX77843_MUIC_IRQ_INT2_CHGDETRUN: + case MAX77843_MUIC_IRQ_INT2_DCDTMR: + case MAX77843_MUIC_IRQ_INT2_DXOVP: + case MAX77843_MUIC_IRQ_INT2_VBVOLT: + info->irq_chg = true; + break; + case MAX77843_MUIC_IRQ_INT3_VBADC: + case MAX77843_MUIC_IRQ_INT3_VDNMON: + case MAX77843_MUIC_IRQ_INT3_DNRES: + case MAX77843_MUIC_IRQ_INT3_MPNACK: + case MAX77843_MUIC_IRQ_INT3_MRXBUFOW: + case MAX77843_MUIC_IRQ_INT3_MRXTRF: + case MAX77843_MUIC_IRQ_INT3_MRXPERR: + case MAX77843_MUIC_IRQ_INT3_MRXRDY: + break; + default: + dev_err(info->dev, "Cannot recognize IRQ(%d)\n", irq_type); + break; + } + + schedule_work(&info->irq_work); + + return IRQ_HANDLED; +} + +static void max77843_muic_detect_cable_wq(struct work_struct *work) +{ + struct max77843_muic_info *info = container_of(to_delayed_work(work), + struct max77843_muic_info, wq_detcable); + struct max77843 *max77843 = info->max77843; + int chg_type, adc, ret; + bool attached; + + mutex_lock(&info->mutex); + + ret = regmap_bulk_read(max77843->regmap_muic, + MAX77843_MUIC_REG_STATUS1, info->status, + MAX77843_MUIC_STATUS_NUM); + if (ret) { + dev_err(info->dev, "Cannot read STATUS registers\n"); + goto err_cable_wq; + } + + adc = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_ADC, &attached); + if (attached && adc != MAX77843_MUIC_ADC_OPEN) { + ret = max77843_muic_adc_handler(info); + if (ret < 0) { + dev_err(info->dev, "Cannot detect accessory\n"); + goto err_cable_wq; + } + } + + chg_type = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_CHG, &attached); + if (attached && chg_type != MAX77843_MUIC_CHG_NONE) { + ret = max77843_muic_chg_handler(info); + if (ret < 0) { + dev_err(info->dev, "Cannot detect charger accessory\n"); + goto err_cable_wq; + } + } + +err_cable_wq: + mutex_unlock(&info->mutex); +} + +static int max77843_muic_set_debounce_time(struct max77843_muic_info *info, + enum max77843_muic_adc_debounce_time time) +{ + struct max77843 *max77843 = info->max77843; + unsigned int ret; + + switch (time) { + case MAX77843_DEBOUNCE_TIME_5MS: + case MAX77843_DEBOUNCE_TIME_10MS: + case MAX77843_DEBOUNCE_TIME_25MS: + case MAX77843_DEBOUNCE_TIME_38_62MS: + ret = regmap_update_bits(max77843->regmap_muic, + MAX77843_MUIC_REG_CONTROL4, + MAX77843_MUIC_CONTROL4_ADCDBSET_MASK, + time << CONTROL4_ADCDBSET_SHIFT); + if (ret < 0) { + dev_err(info->dev, "Cannot write MUIC regmap\n"); + return ret; + } + break; + default: + dev_err(info->dev, "Invalid ADC debounce time\n"); + return -EINVAL; + } + + return 0; +} + +static int max77843_init_muic_regmap(struct max77843 *max77843) +{ + int ret; + + max77843->i2c_muic = i2c_new_dummy(max77843->i2c->adapter, + I2C_ADDR_MUIC); + if (!max77843->i2c_muic) { + dev_err(&max77843->i2c->dev, + "Cannot allocate I2C device for MUIC\n"); + return PTR_ERR(max77843->i2c_muic); + } + + i2c_set_clientdata(max77843->i2c_muic, max77843); + + max77843->regmap_muic = devm_regmap_init_i2c(max77843->i2c_muic, + &max77843_muic_regmap_config); + if (IS_ERR(max77843->regmap_muic)) { + ret = PTR_ERR(max77843->regmap_muic); + goto err_muic_i2c; + } + + ret = regmap_add_irq_chip(max77843->regmap_muic, max77843->irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED, + 0, &max77843_muic_irq_chip, &max77843->irq_data_muic); + if (ret < 0) { + dev_err(&max77843->i2c->dev, "Cannot add MUIC IRQ chip\n"); + goto err_muic_i2c; + } + + return 0; + +err_muic_i2c: + i2c_unregister_device(max77843->i2c_muic); + + return ret; +} + +static int max77843_muic_probe(struct platform_device *pdev) +{ + struct max77843 *max77843 = dev_get_drvdata(pdev->dev.parent); + struct max77843_muic_info *info; + unsigned int id; + int i, ret; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = &pdev->dev; + info->max77843 = max77843; + + platform_set_drvdata(pdev, info); + mutex_init(&info->mutex); + + /* Initialize i2c and regmap */ + ret = max77843_init_muic_regmap(max77843); + if (ret) { + dev_err(&pdev->dev, "Failed to init MUIC regmap\n"); + return ret; + } + + /* Turn off auto detection configuration */ + ret = regmap_update_bits(max77843->regmap_muic, + MAX77843_MUIC_REG_CONTROL4, + MAX77843_MUIC_CONTROL4_USBAUTO_MASK | + MAX77843_MUIC_CONTROL4_FCTAUTO_MASK, + CONTROL4_AUTO_DISABLE); + + /* Initialize extcon device */ + info->edev = devm_extcon_dev_allocate(&pdev->dev, + max77843_extcon_cable); + if (IS_ERR(info->edev)) { + dev_err(&pdev->dev, "Failed to allocate memory for extcon\n"); + ret = -ENODEV; + goto err_muic_irq; + } + + ret = devm_extcon_dev_register(&pdev->dev, info->edev); + if (ret) { + dev_err(&pdev->dev, "Failed to register extcon device\n"); + goto err_muic_irq; + } + + /* Set ADC debounce time */ + max77843_muic_set_debounce_time(info, MAX77843_DEBOUNCE_TIME_25MS); + + /* Set initial path for UART */ + max77843_muic_set_path(info, CONTROL1_SW_UART, true); + + /* Check revision number of MUIC device */ + ret = regmap_read(max77843->regmap_muic, MAX77843_MUIC_REG_ID, &id); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to read revision number\n"); + goto err_muic_irq; + } + dev_info(info->dev, "MUIC device ID : 0x%x\n", id); + + /* Support virtual irq domain for max77843 MUIC device */ + INIT_WORK(&info->irq_work, max77843_muic_irq_work); + + for (i = 0; i < ARRAY_SIZE(max77843_muic_irqs); i++) { + struct max77843_muic_irq *muic_irq = &max77843_muic_irqs[i]; + unsigned int virq = 0; + + virq = regmap_irq_get_virq(max77843->irq_data_muic, + muic_irq->irq); + if (virq <= 0) { + ret = -EINVAL; + goto err_muic_irq; + } + muic_irq->virq = virq; + + ret = devm_request_threaded_irq(&pdev->dev, virq, NULL, + max77843_muic_irq_handler, IRQF_NO_SUSPEND, + muic_irq->name, info); + if (ret) { + dev_err(&pdev->dev, + "Failed to request irq (IRQ: %d, error: %d)\n", + muic_irq->irq, ret); + goto err_muic_irq; + } + } + + /* Detect accessory after completing the initialization of platform */ + INIT_DELAYED_WORK(&info->wq_detcable, max77843_muic_detect_cable_wq); + queue_delayed_work(system_power_efficient_wq, + &info->wq_detcable, msecs_to_jiffies(DELAY_MS_DEFAULT)); + + return 0; + +err_muic_irq: + regmap_del_irq_chip(max77843->irq, max77843->irq_data_muic); + i2c_unregister_device(max77843->i2c_muic); + + return ret; +} + +static int max77843_muic_remove(struct platform_device *pdev) +{ + struct max77843_muic_info *info = platform_get_drvdata(pdev); + struct max77843 *max77843 = info->max77843; + + cancel_work_sync(&info->irq_work); + regmap_del_irq_chip(max77843->irq, max77843->irq_data_muic); + i2c_unregister_device(max77843->i2c_muic); + + return 0; +} + +static const struct platform_device_id max77843_muic_id[] = { + { "max77843-muic", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, max77843_muic_id); + +static struct platform_driver max77843_muic_driver = { + .driver = { + .name = "max77843-muic", + }, + .probe = max77843_muic_probe, + .remove = max77843_muic_remove, + .id_table = max77843_muic_id, +}; + +static int __init max77843_muic_init(void) +{ + return platform_driver_register(&max77843_muic_driver); +} +subsys_initcall(max77843_muic_init); + +MODULE_DESCRIPTION("Maxim MAX77843 Extcon driver"); +MODULE_AUTHOR("Jaewon Kim "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 112bdfaa525fd5993e17885861342893f15290b0 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 16 Feb 2015 15:41:02 +0000 Subject: extcon: arizona: Deobfuscate arizona_extcon_do_magic arizona_extcon_do_magic does not lend a lot of clarity to the purpose of the function, and as all the registers used are described in the datasheet there is no need to obfuscate the code. This patch renames the function to arizona_extcon_hp_clamp, as it controls clamping on the headphone output. Signed-off-by: Charles Keepax Acked-by: Lee Jones Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 36 ++++++++++++++++++++---------------- include/linux/mfd/arizona/core.h | 2 +- sound/soc/codecs/arizona.c | 4 ++-- 3 files changed, 23 insertions(+), 19 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 63f01c42aed4..95cf7f875bb3 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -136,18 +136,22 @@ static const char *arizona_cable[] = { static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info); -static void arizona_extcon_do_magic(struct arizona_extcon_info *info, - unsigned int magic) +static void arizona_extcon_hp_clamp(struct arizona_extcon_info *info, + bool clamp) { struct arizona *arizona = info->arizona; + unsigned int val = 0; int ret; + if (clamp) + val = ARIZONA_RMV_SHRT_HP1L; + mutex_lock(&arizona->dapm->card->dapm_mutex); - arizona->hpdet_magic = magic; + arizona->hpdet_clamp = clamp; - /* Keep the HP output stages disabled while doing the magic */ - if (magic) { + /* Keep the HP output stages disabled while doing the clamp */ + if (clamp) { ret = regmap_update_bits(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, ARIZONA_OUT1L_ENA | @@ -158,20 +162,20 @@ static void arizona_extcon_do_magic(struct arizona_extcon_info *info, ret); } - ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, - magic); + ret = regmap_update_bits(arizona->regmap, ARIZONA_HP_CTRL_1L, + ARIZONA_RMV_SHRT_HP1L, val); if (ret != 0) - dev_warn(arizona->dev, "Failed to do magic: %d\n", + dev_warn(arizona->dev, "Failed to do clamp: %d\n", ret); - ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, - magic); + ret = regmap_update_bits(arizona->regmap, ARIZONA_HP_CTRL_1R, + ARIZONA_RMV_SHRT_HP1R, val); if (ret != 0) - dev_warn(arizona->dev, "Failed to do magic: %d\n", + dev_warn(arizona->dev, "Failed to do clamp: %d\n", ret); - /* Restore the desired state while not doing the magic */ - if (!magic) { + /* Restore the desired state while not doing the clamp */ + if (!clamp) { ret = regmap_update_bits(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, ARIZONA_OUT1L_ENA | @@ -603,7 +607,7 @@ done: ARIZONA_HP_IMPEDANCE_RANGE_MASK | ARIZONA_HP_POLL, 0); - arizona_extcon_do_magic(info, 0); + arizona_extcon_hp_clamp(info, false); if (id_gpio) gpio_set_value_cansleep(id_gpio, 0); @@ -648,7 +652,7 @@ static void arizona_identify_headphone(struct arizona_extcon_info *info) if (info->mic) arizona_stop_mic(info); - arizona_extcon_do_magic(info, 0x4000); + arizona_extcon_hp_clamp(info, true); ret = regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, @@ -699,7 +703,7 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) info->hpdet_active = true; - arizona_extcon_do_magic(info, 0x4000); + arizona_extcon_hp_clamp(info, true); ret = regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, diff --git a/include/linux/mfd/arizona/core.h b/include/linux/mfd/arizona/core.h index 910e3aa1e965..4863548faff7 100644 --- a/include/linux/mfd/arizona/core.h +++ b/include/linux/mfd/arizona/core.h @@ -126,7 +126,7 @@ struct arizona { struct regmap_irq_chip_data *aod_irq_chip; struct regmap_irq_chip_data *irq_chip; - bool hpdet_magic; + bool hpdet_clamp; unsigned int hp_ena; struct mutex clk_lock; diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 29202610dd0d..fb58c7ee3780 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -840,8 +840,8 @@ int arizona_hp_ev(struct snd_soc_dapm_widget *w, priv->arizona->hp_ena &= ~mask; priv->arizona->hp_ena |= val; - /* Force off if HPDET magic is active */ - if (priv->arizona->hpdet_magic) + /* Force off if HPDET clamp is active */ + if (priv->arizona->hpdet_clamp) val = 0; regmap_update_bits_async(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, -- cgit v1.2.3-59-g8ed1b From 43f0acd96163754672cfb8c8015c54ec527a2cce Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 16 Feb 2015 15:41:03 +0000 Subject: extcon: arizona: Fix headphone clamping on wm5110 wm5110 requires slightly different configuration of the headphone clamps to other Arizona devices. Otherwise headphone detection accuracy will be way off. This patch adds the needed clamping. Signed-off-by: Charles Keepax Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 95cf7f875bb3..d9e763cddb50 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -140,11 +140,24 @@ static void arizona_extcon_hp_clamp(struct arizona_extcon_info *info, bool clamp) { struct arizona *arizona = info->arizona; - unsigned int val = 0; + unsigned int mask = 0, val = 0; int ret; - if (clamp) - val = ARIZONA_RMV_SHRT_HP1L; + switch (arizona->type) { + case WM5110: + mask = ARIZONA_HP1L_SHRTO | ARIZONA_HP1L_FLWR | + ARIZONA_HP1L_SHRTI; + if (clamp) + val = ARIZONA_HP1L_SHRTO; + else + val = ARIZONA_HP1L_FLWR | ARIZONA_HP1L_SHRTI; + break; + default: + mask = ARIZONA_RMV_SHRT_HP1L; + if (clamp) + val = ARIZONA_RMV_SHRT_HP1L; + break; + }; mutex_lock(&arizona->dapm->card->dapm_mutex); @@ -163,13 +176,13 @@ static void arizona_extcon_hp_clamp(struct arizona_extcon_info *info, } ret = regmap_update_bits(arizona->regmap, ARIZONA_HP_CTRL_1L, - ARIZONA_RMV_SHRT_HP1L, val); + mask, val); if (ret != 0) dev_warn(arizona->dev, "Failed to do clamp: %d\n", ret); ret = regmap_update_bits(arizona->regmap, ARIZONA_HP_CTRL_1R, - ARIZONA_RMV_SHRT_HP1R, val); + mask, val); if (ret != 0) dev_warn(arizona->dev, "Failed to do clamp: %d\n", ret); -- cgit v1.2.3-59-g8ed1b From f68a8342b1082d9a4b084cb085396e562899bc62 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Sat, 7 Mar 2015 22:58:10 +0900 Subject: extcon: Rename extcon core driver This patch renames the extcon core driver from extcon-class.c to extcon.c because '-class' postfix is not necessary. Signed-off-by: Chanwoo Choi --- drivers/extcon/Makefile | 2 +- drivers/extcon/extcon-class.c | 1038 ----------------------------------------- drivers/extcon/extcon.c | 1038 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1039 insertions(+), 1039 deletions(-) delete mode 100644 drivers/extcon/extcon-class.c create mode 100644 drivers/extcon/extcon.c (limited to 'drivers/extcon') diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index e1eb2d50e72b..9204114791a3 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -2,7 +2,7 @@ # Makefile for external connector class (extcon) devices # -obj-$(CONFIG_EXTCON) += extcon-class.o +obj-$(CONFIG_EXTCON) += extcon.o obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o diff --git a/drivers/extcon/extcon-class.c b/drivers/extcon/extcon-class.c deleted file mode 100644 index 8319f25b7145..000000000000 --- a/drivers/extcon/extcon-class.c +++ /dev/null @@ -1,1038 +0,0 @@ -/* - * 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 -#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[][CABLE_NAME_MAX + 1] = { - [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", - [EXTCON_MECHANICAL] = "Mechanical", -}; - -static struct class *extcon_class; -#if defined(CONFIG_ANDROID) -static struct class_compat *switch_class; -#endif /* CONFIG_ANDROID */ - -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 weight; - u32 correspondants = new_state & edev->mutually_exclusive[i]; - - /* calculate the total number of bits set */ - weight = hweight32(correspondants); - if (weight > 1) - return i + 1; - } - - return 0; -} - -static ssize_t state_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - int i, count = 0; - struct extcon_dev *edev = dev_get_drvdata(dev); - - if (edev->print_state) { - int ret = edev->print_state(edev, buf); - - if (ret >= 0) - return ret; - /* Use default if failed */ - } - - 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; -} - -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 = dev_get_drvdata(dev); - - ret = sscanf(buf, "0x%x", &state); - if (ret == 0) - ret = -EINVAL; - else - ret = extcon_set_state(edev, state); - - if (ret < 0) - return ret; - - return count; -} -static DEVICE_ATTR_RW(state); - -static ssize_t name_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct extcon_dev *edev = 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)); -} -static DEVICE_ATTR_RO(name); - -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)); -} - -/** - * 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 - * 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 the notifier provides which bits are changed in the state - * variable with the val parameter (second) to the callback. - */ -int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) -{ - char name_buf[120]; - char state_buf[120]; - char *prop_buf; - char *envp[3]; - int env_offset = 0; - int length; - unsigned long flags; - - spin_lock_irqsave(&edev->lock, flags); - - 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; - - 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) { - 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; - /* 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); - } - - return 0; -} -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. - */ -int extcon_set_state(struct extcon_dev *edev, u32 state) -{ - return 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_set_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; - return extcon_update_state(edev, 1 << index, state); -} -EXPORT_SYMBOL_GPL(extcon_set_cable_state_); - -/** - * extcon_set_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() - */ -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); - -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))) { - bool cable_state = true; - - obj->previous_value = val; - - if (val & (1 << obj->cable_index)) - cable_state = false; - - return obj->user_nb->notifier_call(obj->user_nb, - cable_state, ptr); - } - - return NOTIFY_OK; -} - -/** - * extcon_register_interest() - Register a notifier for a state change of a - * specific cable, not an 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. - * if NULL, extcon_register_interest will register - * every cable with the target cable_name given. - * @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 || !cable_name || !nb) - return -EINVAL; - - if (extcon_name) { - 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 obj->cable_index; - - obj->user_nb = nb; - - obj->internal_nb.notifier_call = _call_per_cable; - - return raw_notifier_chain_register(&obj->edev->nh, - &obj->internal_nb); - } else { - struct class_dev_iter iter; - struct extcon_dev *extd; - struct device *dev; - - if (!extcon_class) - return -ENODEV; - class_dev_iter_init(&iter, extcon_class, NULL, NULL); - while ((dev = class_dev_iter_next(&iter))) { - extd = dev_get_drvdata(dev); - - if (extcon_find_cable_index(extd, cable_name) < 0) - continue; - - class_dev_iter_exit(&iter); - return extcon_register_interest(obj, extd->name, - cable_name, nb); - } - - return -ENODEV; - } -} -EXPORT_SYMBOL_GPL(extcon_register_interest); - -/** - * 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); -} -EXPORT_SYMBOL_GPL(extcon_unregister_interest); - -/** - * extcon_register_notifier() - Register a notifiee 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) -{ - return raw_notifier_chain_register(&edev->nh, nb); -} -EXPORT_SYMBOL_GPL(extcon_register_notifier); - -/** - * extcon_unregister_notifier() - Unregister a notifiee 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 attribute *extcon_attrs[] = { - &dev_attr_state.attr, - &dev_attr_name.attr, - NULL, -}; -ATTRIBUTE_GROUPS(extcon); - -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_groups = extcon_groups; - -#if defined(CONFIG_ANDROID) - switch_class = class_compat_register("switch"); - if (WARN(!switch_class, "cannot allocate")) - return -ENOMEM; -#endif /* CONFIG_ANDROID */ - } - - return 0; -} - -static void extcon_dev_release(struct device *dev) -{ -} - -static const char *muex_name = "mutually_exclusive"; -static void dummy_sysfs_dev_release(struct device *dev) -{ -} - -/* - * extcon_dev_allocate() - Allocate the memory of extcon device. - * @supported_cable: Array of supported cable names ending with NULL. - * If supported_cable is NULL, cable name related APIs - * are disabled. - * - * This function allocates the memory for extcon device without allocating - * memory in each extcon provider driver and initialize default setting for - * extcon device. - * - * Return the pointer of extcon device if success or ERR_PTR(err) if fail - */ -struct extcon_dev *extcon_dev_allocate(const char **supported_cable) -{ - struct extcon_dev *edev; - - edev = kzalloc(sizeof(*edev), GFP_KERNEL); - if (!edev) - return ERR_PTR(-ENOMEM); - - edev->max_supported = 0; - edev->supported_cable = supported_cable; - - return edev; -} - -/* - * extcon_dev_free() - Free the memory of extcon device. - * @edev: the extcon device to free - */ -void extcon_dev_free(struct extcon_dev *edev) -{ - kfree(edev); -} -EXPORT_SYMBOL_GPL(extcon_dev_free); - -static int devm_extcon_dev_match(struct device *dev, void *res, void *data) -{ - struct extcon_dev **r = res; - - if (WARN_ON(!r || !*r)) - return 0; - - return *r == data; -} - -static void devm_extcon_dev_release(struct device *dev, void *res) -{ - extcon_dev_free(*(struct extcon_dev **)res); -} - -/** - * devm_extcon_dev_allocate - Allocate managed extcon device - * @dev: device owning the extcon device being created - * @supported_cable: Array of supported cable names ending with NULL. - * If supported_cable is NULL, cable name related APIs - * are disabled. - * - * This function manages automatically the memory of extcon device using device - * resource management and simplify the control of freeing the memory of extcon - * device. - * - * Returns the pointer memory of allocated extcon_dev if success - * or ERR_PTR(err) if fail - */ -struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, - const char **supported_cable) -{ - struct extcon_dev **ptr, *edev; - - ptr = devres_alloc(devm_extcon_dev_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return ERR_PTR(-ENOMEM); - - edev = extcon_dev_allocate(supported_cable); - if (IS_ERR(edev)) { - devres_free(ptr); - return edev; - } - - edev->dev.parent = dev; - - *ptr = edev; - devres_add(dev, ptr); - - return edev; -} -EXPORT_SYMBOL_GPL(devm_extcon_dev_allocate); - -void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev) -{ - WARN_ON(devres_release(dev, devm_extcon_dev_release, - devm_extcon_dev_match, edev)); -} -EXPORT_SYMBOL_GPL(devm_extcon_dev_free); - -/** - * extcon_dev_register() - Register a new extcon device - * @edev : the new extcon device (should be allocated before calling) - * - * 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) -{ - int ret, index = 0; - - if (!extcon_class) { - ret = create_extcon_class(); - if (ret < 0) - 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.class = extcon_class; - edev->dev.release = extcon_dev_release; - - edev->name = edev->name ? edev->name : dev_name(edev->dev.parent); - if (IS_ERR_OR_NULL(edev->name)) { - dev_err(&edev->dev, - "extcon device name is null\n"); - return -EINVAL; - } - dev_set_name(&edev->dev, "%s", edev->name); - - 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; - - sysfs_attr_init(&cable->attr_name.attr); - cable->attr_name.attr.name = "name"; - cable->attr_name.attr.mode = 0444; - cable->attr_name.show = cable_name_show; - - sysfs_attr_init(&cable->attr_state.attr); - cable->attr_state.attr.name = "state"; - cable->attr_state.attr.mode = 0444; - cable->attr_state.show = cable_state_show; - } - } - - 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); - sysfs_attr_init(&edev->d_attrs_muex[index].attr); - 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 + 2), 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; - if (edev->mutually_exclusive) - edev->extcon_dev_type.groups[index] = - &edev->attr_g_muex; - - edev->dev.type = &edev->extcon_dev_type; - } - - ret = device_register(&edev->dev); - if (ret) { - put_device(&edev->dev); - goto err_dev; - } -#if defined(CONFIG_ANDROID) - if (switch_class) - ret = class_compat_create_link(switch_class, &edev->dev, NULL); -#endif /* CONFIG_ANDROID */ - - spin_lock_init(&edev->lock); - - 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: - 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: - if (edev->max_supported) - kfree(edev->cables); -err_sysfs_alloc: - return ret; -} -EXPORT_SYMBOL_GPL(extcon_dev_register); - -/** - * extcon_dev_unregister() - Unregister the extcon device. - * @edev: the extcon device instance to be unregistered. - * - * Note that this does not call kfree(edev) because edev was not allocated - * by this class. - */ -void extcon_dev_unregister(struct extcon_dev *edev) -{ - int index; - - mutex_lock(&extcon_dev_list_lock); - list_del(&edev->entry); - mutex_unlock(&extcon_dev_list_lock); - - if (IS_ERR_OR_NULL(get_device(&edev->dev))) { - dev_err(&edev->dev, "Failed to unregister extcon_dev (%s)\n", - dev_name(&edev->dev)); - return; - } - - device_unregister(&edev->dev); - - 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); - - if (edev->max_supported) { - kfree(edev->extcon_dev_type.groups); - kfree(edev->cables); - } - -#if defined(CONFIG_ANDROID) - if (switch_class) - class_compat_remove_link(switch_class, &edev->dev, NULL); -#endif - put_device(&edev->dev); -} -EXPORT_SYMBOL_GPL(extcon_dev_unregister); - -static void devm_extcon_dev_unreg(struct device *dev, void *res) -{ - extcon_dev_unregister(*(struct extcon_dev **)res); -} - -/** - * devm_extcon_dev_register() - Resource-managed extcon_dev_register() - * @dev: device to allocate extcon device - * @edev: the new extcon device to register - * - * Managed extcon_dev_register() function. If extcon device is attached with - * this function, that extcon device is automatically unregistered on driver - * detach. Internally this function calls extcon_dev_register() function. - * To get more information, refer that function. - * - * If extcon device is registered with this function and the device needs to be - * unregistered separately, devm_extcon_dev_unregister() should be used. - * - * Returns 0 if success or negaive error number if failure. - */ -int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev) -{ - struct extcon_dev **ptr; - int ret; - - ptr = devres_alloc(devm_extcon_dev_unreg, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return -ENOMEM; - - ret = extcon_dev_register(edev); - if (ret) { - devres_free(ptr); - return ret; - } - - *ptr = edev; - devres_add(dev, ptr); - - return 0; -} -EXPORT_SYMBOL_GPL(devm_extcon_dev_register); - -/** - * devm_extcon_dev_unregister() - Resource-managed extcon_dev_unregister() - * @dev: device the extcon belongs to - * @edev: the extcon device to unregister - * - * Unregister extcon device that is registered with devm_extcon_dev_register() - * function. - */ -void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev) -{ - WARN_ON(devres_release(dev, devm_extcon_dev_unreg, - devm_extcon_dev_match, edev)); -} -EXPORT_SYMBOL_GPL(devm_extcon_dev_unregister); - -#ifdef CONFIG_OF -/* - * extcon_get_edev_by_phandle - Get the extcon device from devicetree - * @dev - instance to the given device - * @index - index into list of extcon_dev - * - * return the instance of extcon device - */ -struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) -{ - struct device_node *node; - struct extcon_dev *edev; - - if (!dev->of_node) { - dev_err(dev, "device does not have a device node entry\n"); - return ERR_PTR(-EINVAL); - } - - node = of_parse_phandle(dev->of_node, "extcon", index); - if (!node) { - dev_err(dev, "failed to get phandle in %s node\n", - dev->of_node->full_name); - return ERR_PTR(-ENODEV); - } - - mutex_lock(&extcon_dev_list_lock); - list_for_each_entry(edev, &extcon_dev_list, entry) { - if (edev->dev.parent && edev->dev.parent->of_node == node) { - mutex_unlock(&extcon_dev_list_lock); - return edev; - } - } - mutex_unlock(&extcon_dev_list_lock); - - return ERR_PTR(-EPROBE_DEFER); -} -#else -struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) -{ - return ERR_PTR(-ENOSYS); -} -#endif /* CONFIG_OF */ -EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle); - -static int __init extcon_class_init(void) -{ - return create_extcon_class(); -} -module_init(extcon_class_init); - -static void __exit extcon_class_exit(void) -{ -#if defined(CONFIG_ANDROID) - class_compat_unregister(switch_class); -#endif - 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"); diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c new file mode 100644 index 000000000000..8319f25b7145 --- /dev/null +++ b/drivers/extcon/extcon.c @@ -0,0 +1,1038 @@ +/* + * 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 +#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[][CABLE_NAME_MAX + 1] = { + [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", + [EXTCON_MECHANICAL] = "Mechanical", +}; + +static struct class *extcon_class; +#if defined(CONFIG_ANDROID) +static struct class_compat *switch_class; +#endif /* CONFIG_ANDROID */ + +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 weight; + u32 correspondants = new_state & edev->mutually_exclusive[i]; + + /* calculate the total number of bits set */ + weight = hweight32(correspondants); + if (weight > 1) + return i + 1; + } + + return 0; +} + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int i, count = 0; + struct extcon_dev *edev = dev_get_drvdata(dev); + + if (edev->print_state) { + int ret = edev->print_state(edev, buf); + + if (ret >= 0) + return ret; + /* Use default if failed */ + } + + 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; +} + +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 = dev_get_drvdata(dev); + + ret = sscanf(buf, "0x%x", &state); + if (ret == 0) + ret = -EINVAL; + else + ret = extcon_set_state(edev, state); + + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_RW(state); + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct extcon_dev *edev = 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)); +} +static DEVICE_ATTR_RO(name); + +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)); +} + +/** + * 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 + * 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 the notifier provides which bits are changed in the state + * variable with the val parameter (second) to the callback. + */ +int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) +{ + char name_buf[120]; + char state_buf[120]; + char *prop_buf; + char *envp[3]; + int env_offset = 0; + int length; + unsigned long flags; + + spin_lock_irqsave(&edev->lock, flags); + + 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; + + 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) { + 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; + /* 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); + } + + return 0; +} +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. + */ +int extcon_set_state(struct extcon_dev *edev, u32 state) +{ + return 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_set_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; + return extcon_update_state(edev, 1 << index, state); +} +EXPORT_SYMBOL_GPL(extcon_set_cable_state_); + +/** + * extcon_set_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() + */ +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); + +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))) { + bool cable_state = true; + + obj->previous_value = val; + + if (val & (1 << obj->cable_index)) + cable_state = false; + + return obj->user_nb->notifier_call(obj->user_nb, + cable_state, ptr); + } + + return NOTIFY_OK; +} + +/** + * extcon_register_interest() - Register a notifier for a state change of a + * specific cable, not an 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. + * if NULL, extcon_register_interest will register + * every cable with the target cable_name given. + * @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 || !cable_name || !nb) + return -EINVAL; + + if (extcon_name) { + 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 obj->cable_index; + + obj->user_nb = nb; + + obj->internal_nb.notifier_call = _call_per_cable; + + return raw_notifier_chain_register(&obj->edev->nh, + &obj->internal_nb); + } else { + struct class_dev_iter iter; + struct extcon_dev *extd; + struct device *dev; + + if (!extcon_class) + return -ENODEV; + class_dev_iter_init(&iter, extcon_class, NULL, NULL); + while ((dev = class_dev_iter_next(&iter))) { + extd = dev_get_drvdata(dev); + + if (extcon_find_cable_index(extd, cable_name) < 0) + continue; + + class_dev_iter_exit(&iter); + return extcon_register_interest(obj, extd->name, + cable_name, nb); + } + + return -ENODEV; + } +} +EXPORT_SYMBOL_GPL(extcon_register_interest); + +/** + * 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); +} +EXPORT_SYMBOL_GPL(extcon_unregister_interest); + +/** + * extcon_register_notifier() - Register a notifiee 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) +{ + return raw_notifier_chain_register(&edev->nh, nb); +} +EXPORT_SYMBOL_GPL(extcon_register_notifier); + +/** + * extcon_unregister_notifier() - Unregister a notifiee 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 attribute *extcon_attrs[] = { + &dev_attr_state.attr, + &dev_attr_name.attr, + NULL, +}; +ATTRIBUTE_GROUPS(extcon); + +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_groups = extcon_groups; + +#if defined(CONFIG_ANDROID) + switch_class = class_compat_register("switch"); + if (WARN(!switch_class, "cannot allocate")) + return -ENOMEM; +#endif /* CONFIG_ANDROID */ + } + + return 0; +} + +static void extcon_dev_release(struct device *dev) +{ +} + +static const char *muex_name = "mutually_exclusive"; +static void dummy_sysfs_dev_release(struct device *dev) +{ +} + +/* + * extcon_dev_allocate() - Allocate the memory of extcon device. + * @supported_cable: Array of supported cable names ending with NULL. + * If supported_cable is NULL, cable name related APIs + * are disabled. + * + * This function allocates the memory for extcon device without allocating + * memory in each extcon provider driver and initialize default setting for + * extcon device. + * + * Return the pointer of extcon device if success or ERR_PTR(err) if fail + */ +struct extcon_dev *extcon_dev_allocate(const char **supported_cable) +{ + struct extcon_dev *edev; + + edev = kzalloc(sizeof(*edev), GFP_KERNEL); + if (!edev) + return ERR_PTR(-ENOMEM); + + edev->max_supported = 0; + edev->supported_cable = supported_cable; + + return edev; +} + +/* + * extcon_dev_free() - Free the memory of extcon device. + * @edev: the extcon device to free + */ +void extcon_dev_free(struct extcon_dev *edev) +{ + kfree(edev); +} +EXPORT_SYMBOL_GPL(extcon_dev_free); + +static int devm_extcon_dev_match(struct device *dev, void *res, void *data) +{ + struct extcon_dev **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +static void devm_extcon_dev_release(struct device *dev, void *res) +{ + extcon_dev_free(*(struct extcon_dev **)res); +} + +/** + * devm_extcon_dev_allocate - Allocate managed extcon device + * @dev: device owning the extcon device being created + * @supported_cable: Array of supported cable names ending with NULL. + * If supported_cable is NULL, cable name related APIs + * are disabled. + * + * This function manages automatically the memory of extcon device using device + * resource management and simplify the control of freeing the memory of extcon + * device. + * + * Returns the pointer memory of allocated extcon_dev if success + * or ERR_PTR(err) if fail + */ +struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, + const char **supported_cable) +{ + struct extcon_dev **ptr, *edev; + + ptr = devres_alloc(devm_extcon_dev_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + edev = extcon_dev_allocate(supported_cable); + if (IS_ERR(edev)) { + devres_free(ptr); + return edev; + } + + edev->dev.parent = dev; + + *ptr = edev; + devres_add(dev, ptr); + + return edev; +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_allocate); + +void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev) +{ + WARN_ON(devres_release(dev, devm_extcon_dev_release, + devm_extcon_dev_match, edev)); +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_free); + +/** + * extcon_dev_register() - Register a new extcon device + * @edev : the new extcon device (should be allocated before calling) + * + * 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) +{ + int ret, index = 0; + + if (!extcon_class) { + ret = create_extcon_class(); + if (ret < 0) + 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.class = extcon_class; + edev->dev.release = extcon_dev_release; + + edev->name = edev->name ? edev->name : dev_name(edev->dev.parent); + if (IS_ERR_OR_NULL(edev->name)) { + dev_err(&edev->dev, + "extcon device name is null\n"); + return -EINVAL; + } + dev_set_name(&edev->dev, "%s", edev->name); + + 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; + + sysfs_attr_init(&cable->attr_name.attr); + cable->attr_name.attr.name = "name"; + cable->attr_name.attr.mode = 0444; + cable->attr_name.show = cable_name_show; + + sysfs_attr_init(&cable->attr_state.attr); + cable->attr_state.attr.name = "state"; + cable->attr_state.attr.mode = 0444; + cable->attr_state.show = cable_state_show; + } + } + + 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); + sysfs_attr_init(&edev->d_attrs_muex[index].attr); + 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 + 2), 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; + if (edev->mutually_exclusive) + edev->extcon_dev_type.groups[index] = + &edev->attr_g_muex; + + edev->dev.type = &edev->extcon_dev_type; + } + + ret = device_register(&edev->dev); + if (ret) { + put_device(&edev->dev); + goto err_dev; + } +#if defined(CONFIG_ANDROID) + if (switch_class) + ret = class_compat_create_link(switch_class, &edev->dev, NULL); +#endif /* CONFIG_ANDROID */ + + spin_lock_init(&edev->lock); + + 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: + 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: + if (edev->max_supported) + kfree(edev->cables); +err_sysfs_alloc: + return ret; +} +EXPORT_SYMBOL_GPL(extcon_dev_register); + +/** + * extcon_dev_unregister() - Unregister the extcon device. + * @edev: the extcon device instance to be unregistered. + * + * Note that this does not call kfree(edev) because edev was not allocated + * by this class. + */ +void extcon_dev_unregister(struct extcon_dev *edev) +{ + int index; + + mutex_lock(&extcon_dev_list_lock); + list_del(&edev->entry); + mutex_unlock(&extcon_dev_list_lock); + + if (IS_ERR_OR_NULL(get_device(&edev->dev))) { + dev_err(&edev->dev, "Failed to unregister extcon_dev (%s)\n", + dev_name(&edev->dev)); + return; + } + + device_unregister(&edev->dev); + + 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); + + if (edev->max_supported) { + kfree(edev->extcon_dev_type.groups); + kfree(edev->cables); + } + +#if defined(CONFIG_ANDROID) + if (switch_class) + class_compat_remove_link(switch_class, &edev->dev, NULL); +#endif + put_device(&edev->dev); +} +EXPORT_SYMBOL_GPL(extcon_dev_unregister); + +static void devm_extcon_dev_unreg(struct device *dev, void *res) +{ + extcon_dev_unregister(*(struct extcon_dev **)res); +} + +/** + * devm_extcon_dev_register() - Resource-managed extcon_dev_register() + * @dev: device to allocate extcon device + * @edev: the new extcon device to register + * + * Managed extcon_dev_register() function. If extcon device is attached with + * this function, that extcon device is automatically unregistered on driver + * detach. Internally this function calls extcon_dev_register() function. + * To get more information, refer that function. + * + * If extcon device is registered with this function and the device needs to be + * unregistered separately, devm_extcon_dev_unregister() should be used. + * + * Returns 0 if success or negaive error number if failure. + */ +int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev) +{ + struct extcon_dev **ptr; + int ret; + + ptr = devres_alloc(devm_extcon_dev_unreg, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = extcon_dev_register(edev); + if (ret) { + devres_free(ptr); + return ret; + } + + *ptr = edev; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_register); + +/** + * devm_extcon_dev_unregister() - Resource-managed extcon_dev_unregister() + * @dev: device the extcon belongs to + * @edev: the extcon device to unregister + * + * Unregister extcon device that is registered with devm_extcon_dev_register() + * function. + */ +void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev) +{ + WARN_ON(devres_release(dev, devm_extcon_dev_unreg, + devm_extcon_dev_match, edev)); +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_unregister); + +#ifdef CONFIG_OF +/* + * extcon_get_edev_by_phandle - Get the extcon device from devicetree + * @dev - instance to the given device + * @index - index into list of extcon_dev + * + * return the instance of extcon device + */ +struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) +{ + struct device_node *node; + struct extcon_dev *edev; + + if (!dev->of_node) { + dev_err(dev, "device does not have a device node entry\n"); + return ERR_PTR(-EINVAL); + } + + node = of_parse_phandle(dev->of_node, "extcon", index); + if (!node) { + dev_err(dev, "failed to get phandle in %s node\n", + dev->of_node->full_name); + return ERR_PTR(-ENODEV); + } + + mutex_lock(&extcon_dev_list_lock); + list_for_each_entry(edev, &extcon_dev_list, entry) { + if (edev->dev.parent && edev->dev.parent->of_node == node) { + mutex_unlock(&extcon_dev_list_lock); + return edev; + } + } + mutex_unlock(&extcon_dev_list_lock); + + return ERR_PTR(-EPROBE_DEFER); +} +#else +struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) +{ + return ERR_PTR(-ENOSYS); +} +#endif /* CONFIG_OF */ +EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle); + +static int __init extcon_class_init(void) +{ + return create_extcon_class(); +} +module_init(extcon_class_init); + +static void __exit extcon_class_exit(void) +{ +#if defined(CONFIG_ANDROID) + class_compat_unregister(switch_class); +#endif + 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-59-g8ed1b From 34825e511971e193db16a96350faeb60eff0d842 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Sat, 7 Mar 2015 01:41:36 +0900 Subject: extcon: Fix the checkpatch warning This patch fixes the checkpatch warning about coding style. Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max14577.c | 5 +---- drivers/extcon/extcon-max77693.c | 5 +---- drivers/extcon/extcon-max8997.c | 5 +---- drivers/extcon/extcon-rt8973a.c | 6 ++---- drivers/extcon/extcon-sm5502.c | 6 +++--- drivers/extcon/extcon-usb-gpio.c | 2 +- drivers/extcon/extcon.c | 1 + 7 files changed, 10 insertions(+), 20 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index c1bf0cf747b0..3823aa4a3a80 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -539,8 +539,6 @@ static void max14577_muic_irq_work(struct work_struct *work) dev_err(info->dev, "failed to handle MUIC interrupt\n"); mutex_unlock(&info->mutex); - - return; } /* @@ -730,8 +728,7 @@ static int max14577_muic_probe(struct platform_device *pdev) muic_irq->name, info); if (ret) { dev_err(&pdev->dev, - "failed: irq request (IRQ: %d," - " error :%d)\n", + "failed: irq request (IRQ: %d, error :%d)\n", muic_irq->irq, ret); return ret; } diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index dfcc5cba25f0..a66bec8f6252 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1019,8 +1019,6 @@ static void max77693_muic_irq_work(struct work_struct *work) dev_err(info->dev, "failed to handle MUIC interrupt\n"); mutex_unlock(&info->mutex); - - return; } static irqreturn_t max77693_muic_irq_handler(int irq, void *data) @@ -1171,8 +1169,7 @@ static int max77693_muic_probe(struct platform_device *pdev) muic_irq->name, info); if (ret) { dev_err(&pdev->dev, - "failed: irq request (IRQ: %d," - " error :%d)\n", + "failed: irq request (IRQ: %d, error :%d)\n", muic_irq->irq, ret); return ret; } diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index fc1678fa95c4..5774e56c6422 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -579,8 +579,6 @@ static void max8997_muic_irq_work(struct work_struct *work) dev_err(info->dev, "failed to handle MUIC interrupt\n"); mutex_unlock(&info->mutex); - - return; } static irqreturn_t max8997_muic_irq_handler(int irq, void *data) @@ -689,8 +687,7 @@ static int max8997_muic_probe(struct platform_device *pdev) muic_irq->name, info); if (ret) { dev_err(&pdev->dev, - "failed: irq request (IRQ: %d," - " error :%d)\n", + "failed: irq request (IRQ: %d, error :%d)\n", muic_irq->irq, ret); goto err_irq; } diff --git a/drivers/extcon/extcon-rt8973a.c b/drivers/extcon/extcon-rt8973a.c index a784b2d5ee72..9ccd5af89d1c 100644 --- a/drivers/extcon/extcon-rt8973a.c +++ b/drivers/extcon/extcon-rt8973a.c @@ -582,10 +582,8 @@ static int rt8973a_muic_i2c_probe(struct i2c_client *i2c, return -EINVAL; info = devm_kzalloc(&i2c->dev, sizeof(*info), GFP_KERNEL); - if (!info) { - dev_err(&i2c->dev, "failed to allocate memory\n"); + if (!info) return -ENOMEM; - } i2c_set_clientdata(i2c, info); info->dev = &i2c->dev; @@ -681,7 +679,7 @@ static int rt8973a_muic_i2c_remove(struct i2c_client *i2c) return 0; } -static struct of_device_id rt8973a_dt_match[] = { +static const struct of_device_id rt8973a_dt_match[] = { { .compatible = "richtek,rt8973a-muic" }, { }, }; diff --git a/drivers/extcon/extcon-sm5502.c b/drivers/extcon/extcon-sm5502.c index b0f7bd82af90..2f93cf307852 100644 --- a/drivers/extcon/extcon-sm5502.c +++ b/drivers/extcon/extcon-sm5502.c @@ -359,8 +359,8 @@ static unsigned int sm5502_muic_get_cable_type(struct sm5502_muic_info *info) break; default: dev_dbg(info->dev, - "cannot identify the cable type: adc(0x%x) " - "dev_type1(0x%x)\n", adc, dev_type1); + "cannot identify the cable type: adc(0x%x)\n", + adc); return -EINVAL; }; break; @@ -659,7 +659,7 @@ static int sm5502_muic_i2c_remove(struct i2c_client *i2c) return 0; } -static struct of_device_id sm5502_dt_match[] = { +static const struct of_device_id sm5502_dt_match[] = { { .compatible = "siliconmitus,sm5502-muic" }, { }, }; diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c index 3f0bad3ce8aa..de67fce18984 100644 --- a/drivers/extcon/extcon-usb-gpio.c +++ b/drivers/extcon/extcon-usb-gpio.c @@ -214,7 +214,7 @@ static int usb_extcon_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, usb_extcon_suspend, usb_extcon_resume); -static struct of_device_id usb_extcon_dt_match[] = { +static const struct of_device_id usb_extcon_dt_match[] = { { .compatible = "linux,extcon-usb-gpio", }, { /* sentinel */ } }; diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 8319f25b7145..752ce1208428 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -158,6 +158,7 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr, /* Optional callback given by the user */ if (edev->print_name) { int ret = edev->print_name(edev, buf); + if (ret >= 0) return ret; } -- cgit v1.2.3-59-g8ed1b From d927c59076f505a056e78a9fc9744069aa052cc8 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sun, 15 Mar 2015 13:49:47 +0300 Subject: extcon: max77843: Fix signedness bug in max77843_muic_set_debounce_time() This patch fixes the variable type of 'ret' from 'unsigned int' to 'int' type because the return type of regmap_update_bits() is 'int' type. Fixes: 27a28d32b4f2 ('extcon: max77843: Add max77843 MUIC driver') Signed-off-by: Dan Carpenter [cw00.choi: Fix the patch description] Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max77843.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-max77843.c b/drivers/extcon/extcon-max77843.c index 598a017cc91b..a3f80ca4a6e3 100644 --- a/drivers/extcon/extcon-max77843.c +++ b/drivers/extcon/extcon-max77843.c @@ -682,7 +682,7 @@ static int max77843_muic_set_debounce_time(struct max77843_muic_info *info, enum max77843_muic_adc_debounce_time time) { struct max77843 *max77843 = info->max77843; - unsigned int ret; + int ret; switch (time) { case MAX77843_DEBOUNCE_TIME_5MS: -- cgit v1.2.3-59-g8ed1b From b9b518f53996b67833b42ca7751b2085b0dc565a Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sun, 15 Mar 2015 13:56:04 +0300 Subject: extcon: max77843: Fix an error code in max77843_init_muic_regmap() The i2c_new_dummy() return the NULL if error happen. So, If i2c_new_dummy() return NULL, max77843_init_muic_regmap() return the proper error value (-ENOMEM); Signed-off-by: Dan Carpenter [cw00.choi: Use -ENOMEM instead of -ENODEV and modify patch description] Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max77843.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-max77843.c b/drivers/extcon/extcon-max77843.c index a3f80ca4a6e3..8db6a926ea07 100644 --- a/drivers/extcon/extcon-max77843.c +++ b/drivers/extcon/extcon-max77843.c @@ -715,7 +715,7 @@ static int max77843_init_muic_regmap(struct max77843 *max77843) if (!max77843->i2c_muic) { dev_err(&max77843->i2c->dev, "Cannot allocate I2C device for MUIC\n"); - return PTR_ERR(max77843->i2c_muic); + return -ENOMEM; } i2c_set_clientdata(max77843->i2c_muic, max77843); -- cgit v1.2.3-59-g8ed1b From 66bee35f29683fc4a9a530a1c56a0ec45e3f7d72 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 21 Mar 2015 17:26:24 +0100 Subject: extcon: Fix missing locking when [un]registering notifiers Since extcon.c is using raw_notifiers it must protect the notifier list itself when [un]registering notifiers to avoid the list changing while extcon_update_state is walking the list (through raw_notifier_call_chain). Signed-off-by: Hans de Goede [cw00.choi: Apply this patch to extcon.c driver instead of old extcon-class.c] Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon.c | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 752ce1208428..4c9f165e4a04 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -445,6 +445,9 @@ int extcon_register_interest(struct extcon_specific_cable_nb *obj, const char *extcon_name, const char *cable_name, struct notifier_block *nb) { + unsigned long flags; + int ret; + if (!obj || !cable_name || !nb) return -EINVAL; @@ -462,8 +465,11 @@ int extcon_register_interest(struct extcon_specific_cable_nb *obj, obj->internal_nb.notifier_call = _call_per_cable; - return raw_notifier_chain_register(&obj->edev->nh, + spin_lock_irqsave(&obj->edev->lock, flags); + ret = raw_notifier_chain_register(&obj->edev->nh, &obj->internal_nb); + spin_unlock_irqrestore(&obj->edev->lock, flags); + return ret; } else { struct class_dev_iter iter; struct extcon_dev *extd; @@ -496,10 +502,17 @@ EXPORT_SYMBOL_GPL(extcon_register_interest); */ int extcon_unregister_interest(struct extcon_specific_cable_nb *obj) { + unsigned long flags; + int ret; + if (!obj) return -EINVAL; - return raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb); + spin_lock_irqsave(&obj->edev->lock, flags); + ret = raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb); + spin_unlock_irqrestore(&obj->edev->lock, flags); + + return ret; } EXPORT_SYMBOL_GPL(extcon_unregister_interest); @@ -516,7 +529,14 @@ EXPORT_SYMBOL_GPL(extcon_unregister_interest); int extcon_register_notifier(struct extcon_dev *edev, struct notifier_block *nb) { - return raw_notifier_chain_register(&edev->nh, nb); + unsigned long flags; + int ret; + + spin_lock_irqsave(&edev->lock, flags); + ret = raw_notifier_chain_register(&edev->nh, nb); + spin_unlock_irqrestore(&edev->lock, flags); + + return ret; } EXPORT_SYMBOL_GPL(extcon_register_notifier); @@ -528,7 +548,14 @@ EXPORT_SYMBOL_GPL(extcon_register_notifier); int extcon_unregister_notifier(struct extcon_dev *edev, struct notifier_block *nb) { - return raw_notifier_chain_unregister(&edev->nh, nb); + unsigned long flags; + int ret; + + spin_lock_irqsave(&edev->lock, flags); + ret = raw_notifier_chain_unregister(&edev->nh, nb); + spin_unlock_irqrestore(&edev->lock, flags); + + return ret; } EXPORT_SYMBOL_GPL(extcon_unregister_notifier); -- cgit v1.2.3-59-g8ed1b