diff options
Diffstat (limited to 'drivers/extcon')
| -rw-r--r-- | drivers/extcon/Kconfig | 14 | ||||
| -rw-r--r-- | drivers/extcon/Makefile | 3 | ||||
| -rw-r--r-- | drivers/extcon/extcon-adc-jack.c | 49 | ||||
| -rw-r--r-- | drivers/extcon/extcon-arizona.c | 123 | ||||
| -rw-r--r-- | drivers/extcon/extcon-class.c | 193 | ||||
| -rw-r--r-- | drivers/extcon/extcon-gpio.c | 67 | ||||
| -rw-r--r-- | drivers/extcon/extcon-max14577.c | 823 | ||||
| -rw-r--r-- | drivers/extcon/extcon-max77693.c | 23 | ||||
| -rw-r--r-- | drivers/extcon/extcon-max8997.c | 16 | ||||
| -rw-r--r-- | drivers/extcon/extcon-palmas.c | 61 | ||||
| -rw-r--r-- | drivers/extcon/of_extcon.c | 64 |
11 files changed, 1216 insertions, 220 deletions
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index f1d54a3985b..aebde489c29 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -14,10 +14,6 @@ if EXTCON comment "Extcon Device Drivers" -config OF_EXTCON - def_tristate y - depends on OF - config EXTCON_GPIO tristate "GPIO extcon support" depends on GPIOLIB @@ -31,6 +27,16 @@ config EXTCON_ADC_JACK help Say Y here to enable extcon device driver based on ADC values. +config EXTCON_MAX14577 + tristate "MAX14577/77836 EXTCON Support" + depends on MFD_MAX14577 + select IRQ_DOMAIN + select REGMAP_I2C + help + If you say yes here you get support for the MUIC device of + Maxim MAX14577/77836. The MAX14577/77836 MUIC is a USB port accessory + detector and switch. + config EXTCON_MAX77693 tristate "MAX77693 EXTCON Support" depends on MFD_MAX77693 && INPUT diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 759fdae46f9..bf7861ec090 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -2,11 +2,10 @@ # Makefile for external connector class (extcon) devices # -obj-$(CONFIG_OF_EXTCON) += of_extcon.o - obj-$(CONFIG_EXTCON) += extcon-class.o obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o +obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c index e23f1c2e505..e18f95be373 100644 --- a/drivers/extcon/extcon-adc-jack.c +++ b/drivers/extcon/extcon-adc-jack.c @@ -39,7 +39,7 @@ * @chan: iio channel being queried. */ struct adc_jack_data { - struct extcon_dev edev; + struct extcon_dev *edev; const char **cable_names; int num_cables; @@ -64,7 +64,7 @@ static void adc_jack_handler(struct work_struct *work) ret = iio_read_channel_raw(data->chan, &adc_val); if (ret < 0) { - dev_err(&data->edev.dev, "read channel() error: %d\n", ret); + dev_err(&data->edev->dev, "read channel() error: %d\n", ret); return; } @@ -80,7 +80,7 @@ static void adc_jack_handler(struct work_struct *work) } /* if no def has met, it means state = 0 (no cables attached) */ - extcon_set_state(&data->edev, state); + extcon_set_state(data->edev, state); } static irqreturn_t adc_jack_irq_thread(int irq, void *_data) @@ -102,33 +102,33 @@ static int adc_jack_probe(struct platform_device *pdev) if (!data) return -ENOMEM; - data->edev.name = pdata->name; - if (!pdata->cable_names) { - err = -EINVAL; dev_err(&pdev->dev, "error: cable_names not defined.\n"); - goto out; + return -EINVAL; } - data->edev.dev.parent = &pdev->dev; - data->edev.supported_cable = pdata->cable_names; + data->edev = devm_extcon_dev_allocate(&pdev->dev, pdata->cable_names); + if (IS_ERR(data->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + data->edev->dev.parent = &pdev->dev; + data->edev->name = pdata->name; /* Check the length of array and set num_cables */ - for (i = 0; data->edev.supported_cable[i]; i++) + for (i = 0; data->edev->supported_cable[i]; i++) ; if (i == 0 || i > SUPPORTED_CABLE_MAX) { - err = -EINVAL; dev_err(&pdev->dev, "error: pdata->cable_names size = %d\n", i - 1); - goto out; + return -EINVAL; } data->num_cables = i; if (!pdata->adc_conditions || !pdata->adc_conditions[0].state) { - err = -EINVAL; dev_err(&pdev->dev, "error: adc_conditions not defined.\n"); - goto out; + return -EINVAL; } data->adc_conditions = pdata->adc_conditions; @@ -138,10 +138,8 @@ static int adc_jack_probe(struct platform_device *pdev) data->num_conditions = i; data->chan = iio_channel_get(&pdev->dev, pdata->consumer_channel); - if (IS_ERR(data->chan)) { - err = PTR_ERR(data->chan); - goto out; - } + if (IS_ERR(data->chan)) + return PTR_ERR(data->chan); data->handling_delay = msecs_to_jiffies(pdata->handling_delay_ms); @@ -149,15 +147,14 @@ static int adc_jack_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); - err = extcon_dev_register(&data->edev); + err = devm_extcon_dev_register(&pdev->dev, data->edev); if (err) - goto out; + return err; data->irq = platform_get_irq(pdev, 0); if (!data->irq) { dev_err(&pdev->dev, "platform_get_irq failed\n"); - err = -ENODEV; - goto err_irq; + return -ENODEV; } err = request_any_context_irq(data->irq, adc_jack_irq_thread, @@ -165,15 +162,10 @@ static int adc_jack_probe(struct platform_device *pdev) if (err < 0) { dev_err(&pdev->dev, "error: irq %d\n", data->irq); - goto err_irq; + return err; } return 0; - -err_irq: - extcon_dev_unregister(&data->edev); -out: - return err; } static int adc_jack_remove(struct platform_device *pdev) @@ -182,7 +174,6 @@ static int adc_jack_remove(struct platform_device *pdev) free_irq(data->irq, data); cancel_work_sync(&data->handler.work); - extcon_dev_unregister(&data->edev); return 0; } diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index a287cece059..6c84e3d1204 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -44,6 +44,15 @@ #define HPDET_DEBOUNCE 500 #define DEFAULT_MICD_TIMEOUT 2000 +#define MICD_LVL_1_TO_7 (ARIZONA_MICD_LVL_1 | ARIZONA_MICD_LVL_2 | \ + ARIZONA_MICD_LVL_3 | ARIZONA_MICD_LVL_4 | \ + ARIZONA_MICD_LVL_5 | ARIZONA_MICD_LVL_6 | \ + ARIZONA_MICD_LVL_7) + +#define MICD_LVL_0_TO_7 (ARIZONA_MICD_LVL_0 | MICD_LVL_1_TO_7) + +#define MICD_LVL_0_TO_8 (MICD_LVL_0_TO_7 | ARIZONA_MICD_LVL_8) + struct arizona_extcon_info { struct device *dev; struct arizona *arizona; @@ -82,7 +91,7 @@ struct arizona_extcon_info { int hpdet_ip; - struct extcon_dev edev; + struct extcon_dev *edev; }; static const struct arizona_micd_config micd_default_modes[] = { @@ -213,27 +222,19 @@ static void arizona_extcon_pulse_micbias(struct arizona_extcon_info *info) struct snd_soc_dapm_context *dapm = arizona->dapm; int ret; - mutex_lock(&dapm->card->dapm_mutex); - ret = snd_soc_dapm_force_enable_pin(dapm, widget); if (ret != 0) dev_warn(arizona->dev, "Failed to enable %s: %d\n", widget, ret); - mutex_unlock(&dapm->card->dapm_mutex); - snd_soc_dapm_sync(dapm); if (!arizona->pdata.micd_force_micbias) { - mutex_lock(&dapm->card->dapm_mutex); - ret = snd_soc_dapm_disable_pin(arizona->dapm, widget); if (ret != 0) dev_warn(arizona->dev, "Failed to disable %s: %d\n", widget, ret); - mutex_unlock(&dapm->card->dapm_mutex); - snd_soc_dapm_sync(dapm); } } @@ -295,16 +296,12 @@ static void arizona_stop_mic(struct arizona_extcon_info *info) ARIZONA_MICD_ENA, 0, &change); - mutex_lock(&dapm->card->dapm_mutex); - ret = snd_soc_dapm_disable_pin(dapm, widget); if (ret != 0) dev_warn(arizona->dev, "Failed to disable %s: %d\n", widget, ret); - mutex_unlock(&dapm->card->dapm_mutex); - snd_soc_dapm_sync(dapm); if (info->micd_reva) { @@ -426,26 +423,15 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) } val &= ARIZONA_HP_LVL_B_MASK; + /* Convert to ohms, the value is in 0.5 ohm increments */ + val /= 2; regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, &range); range = (range & ARIZONA_HP_IMPEDANCE_RANGE_MASK) >> ARIZONA_HP_IMPEDANCE_RANGE_SHIFT; - /* Skip up or down a range? */ - if (range && (val < arizona_hpdet_c_ranges[range].min)) { - range--; - dev_dbg(arizona->dev, "Moving to HPDET range %d-%d\n", - arizona_hpdet_c_ranges[range].min, - arizona_hpdet_c_ranges[range].max); - regmap_update_bits(arizona->regmap, - ARIZONA_HEADPHONE_DETECT_1, - ARIZONA_HP_IMPEDANCE_RANGE_MASK, - range << - ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); - return -EAGAIN; - } - + /* Skip up a range, or report? */ if (range < ARRAY_SIZE(arizona_hpdet_c_ranges) - 1 && (val >= arizona_hpdet_c_ranges[range].max)) { range++; @@ -459,6 +445,12 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); return -EAGAIN; } + + if (range && (val < arizona_hpdet_c_ranges[range].min)) { + dev_dbg(arizona->dev, "Reporting range boundary %d\n", + arizona_hpdet_c_ranges[range].min); + val = arizona_hpdet_c_ranges[range].min; + } } dev_dbg(arizona->dev, "HP impedance %d ohms\n", val); @@ -554,7 +546,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) } /* If the cable was removed while measuring ignore the result */ - ret = extcon_get_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL); + ret = extcon_get_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL); if (ret < 0) { dev_err(arizona->dev, "Failed to check cable state: %d\n", ret); @@ -589,14 +581,20 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) else report = ARIZONA_CABLE_HEADPHONE; - ret = extcon_set_cable_state_(&info->edev, report, true); + ret = extcon_set_cable_state_(info->edev, report, true); if (ret != 0) dev_err(arizona->dev, "Failed to report HP/line: %d\n", ret); +done: + /* Reset back to starting range */ + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_IMPEDANCE_RANGE_MASK | ARIZONA_HP_POLL, + 0); + arizona_extcon_do_magic(info, 0); -done: if (id_gpio) gpio_set_value_cansleep(id_gpio, 0); @@ -666,7 +664,7 @@ err: ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); /* Just report headphone */ - ret = extcon_update_state(&info->edev, + ret = extcon_update_state(info->edev, 1 << ARIZONA_CABLE_HEADPHONE, 1 << ARIZONA_CABLE_HEADPHONE); if (ret != 0) @@ -725,7 +723,7 @@ err: ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); /* Just report headphone */ - ret = extcon_update_state(&info->edev, + ret = extcon_update_state(info->edev, 1 << ARIZONA_CABLE_HEADPHONE, 1 << ARIZONA_CABLE_HEADPHONE); if (ret != 0) @@ -765,7 +763,20 @@ static void arizona_micd_detect(struct work_struct *work) mutex_lock(&info->lock); - for (i = 0; i < 10 && !(val & 0x7fc); i++) { + /* If the cable was removed while measuring ignore the result */ + ret = extcon_get_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL); + if (ret < 0) { + dev_err(arizona->dev, "Failed to check cable state: %d\n", + ret); + mutex_unlock(&info->lock); + return; + } else if (!ret) { + dev_dbg(arizona->dev, "Ignoring MICDET for removed cable\n"); + mutex_unlock(&info->lock); + return; + } + + for (i = 0; i < 10 && !(val & MICD_LVL_0_TO_8); i++) { ret = regmap_read(arizona->regmap, ARIZONA_MIC_DETECT_3, &val); if (ret != 0) { dev_err(arizona->dev, @@ -784,7 +795,7 @@ static void arizona_micd_detect(struct work_struct *work) } } - if (i == 10 && !(val & 0x7fc)) { + if (i == 10 && !(val & MICD_LVL_0_TO_8)) { dev_err(arizona->dev, "Failed to get valid MICDET value\n"); mutex_unlock(&info->lock); return; @@ -798,10 +809,10 @@ static void arizona_micd_detect(struct work_struct *work) } /* If we got a high impedence we should have a headset, report it. */ - if (info->detecting && (val & 0x400)) { + if (info->detecting && (val & ARIZONA_MICD_LVL_8)) { arizona_identify_headphone(info); - ret = extcon_update_state(&info->edev, + ret = extcon_update_state(info->edev, 1 << ARIZONA_CABLE_MICROPHONE, 1 << ARIZONA_CABLE_MICROPHONE); @@ -827,7 +838,7 @@ static void arizona_micd_detect(struct work_struct *work) * plain headphones. If both polarities report a low * impedence then give up and report headphones. */ - if (info->detecting && (val & 0x3f8)) { + if (info->detecting && (val & MICD_LVL_1_TO_7)) { if (info->jack_flips >= info->micd_num_modes * 10) { dev_dbg(arizona->dev, "Detected HP/line\n"); arizona_identify_headphone(info); @@ -851,7 +862,7 @@ static void arizona_micd_detect(struct work_struct *work) * If we're still detecting and we detect a short then we've * got a headphone. Otherwise it's a button press. */ - if (val & 0x3fc) { + if (val & MICD_LVL_0_TO_7) { if (info->mic) { dev_dbg(arizona->dev, "Mic button detected\n"); @@ -988,7 +999,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) if (info->last_jackdet == present) { dev_dbg(arizona->dev, "Detected jack\n"); - ret = extcon_set_cable_state_(&info->edev, + ret = extcon_set_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL, true); if (ret != 0) @@ -1027,7 +1038,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) info->micd_ranges[i].key, 0); input_sync(info->input); - ret = extcon_update_state(&info->edev, 0xffffffff, 0); + ret = extcon_update_state(info->edev, 0xffffffff, 0); if (ret != 0) dev_err(arizona->dev, "Removal report failed: %d\n", ret); @@ -1094,15 +1105,14 @@ static int arizona_extcon_probe(struct platform_device *pdev) info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) { dev_err(&pdev->dev, "Failed to allocate memory\n"); - ret = -ENOMEM; - goto err; + return -ENOMEM; } info->micvdd = devm_regulator_get(arizona->dev, "MICVDD"); if (IS_ERR(info->micvdd)) { ret = PTR_ERR(info->micvdd); dev_err(arizona->dev, "Failed to get MICVDD: %d\n", ret); - goto err; + return ret; } mutex_init(&info->lock); @@ -1126,19 +1136,33 @@ static int arizona_extcon_probe(struct platform_device *pdev) break; } break; + case WM5110: + switch (arizona->rev) { + case 0 ... 2: + break; + default: + info->micd_clamp = true; + info->hpdet_ip = 2; + break; + } + break; default: break; } - info->edev.name = "Headset Jack"; - info->edev.dev.parent = arizona->dev; - info->edev.supported_cable = arizona_cable; + info->edev = devm_extcon_dev_allocate(&pdev->dev, arizona_cable); + if (IS_ERR(info->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + info->edev->name = "Headset Jack"; + info->edev->dev.parent = arizona->dev; - ret = extcon_dev_register(&info->edev); + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret < 0) { dev_err(arizona->dev, "extcon_dev_register() failed: %d\n", ret); - goto err; + return ret; } info->input = devm_input_allocate_device(&pdev->dev); @@ -1389,8 +1413,6 @@ err_rise: err_input: err_register: pm_runtime_disable(&pdev->dev); - extcon_dev_unregister(&info->edev); -err: return ret; } @@ -1424,7 +1446,6 @@ static int arizona_extcon_remove(struct platform_device *pdev) regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, 0); arizona_clk32k_disable(arizona); - extcon_dev_unregister(&info->edev); return 0; } diff --git a/drivers/extcon/extcon-class.c b/drivers/extcon/extcon-class.c index 76322330cbd..18d42c0e458 100644 --- a/drivers/extcon/extcon-class.c +++ b/drivers/extcon/extcon-class.c @@ -31,6 +31,7 @@ #include <linux/extcon.h> #include <linux/slab.h> #include <linux/sysfs.h> +#include <linux/of.h> /* * extcon_cable_name suggests the standard cable names for commonly used @@ -564,6 +565,100 @@ 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; + } + + *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) @@ -818,6 +913,104 @@ void extcon_dev_unregister(struct extcon_dev *edev) } 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); + } + + edev = extcon_get_extcon_dev(node->name); + if (!edev) { + dev_err(dev, "unable to get extcon device : %s\n", node->name); + return ERR_PTR(-ENODEV); + } + + return edev; +} +#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(); diff --git a/drivers/extcon/extcon-gpio.c b/drivers/extcon/extcon-gpio.c index 7e0dff58e49..645b2835681 100644 --- a/drivers/extcon/extcon-gpio.c +++ b/drivers/extcon/extcon-gpio.c @@ -32,7 +32,7 @@ #include <linux/extcon/extcon-gpio.h> struct gpio_extcon_data { - struct extcon_dev edev; + struct extcon_dev *edev; unsigned gpio; bool gpio_active_low; const char *state_on; @@ -40,6 +40,7 @@ struct gpio_extcon_data { int irq; struct delayed_work work; unsigned long debounce_jiffies; + bool check_on_resume; }; static void gpio_extcon_work(struct work_struct *work) @@ -52,7 +53,7 @@ static void gpio_extcon_work(struct work_struct *work) state = gpio_get_value(data->gpio); if (data->gpio_active_low) state = !state; - extcon_set_state(&data->edev, state); + extcon_set_state(data->edev, state); } static irqreturn_t gpio_irq_handler(int irq, void *dev_id) @@ -66,9 +67,10 @@ static irqreturn_t gpio_irq_handler(int irq, void *dev_id) static ssize_t extcon_gpio_print_state(struct extcon_dev *edev, char *buf) { - struct gpio_extcon_data *extcon_data = - container_of(edev, struct gpio_extcon_data, edev); + struct device *dev = edev->dev.parent; + struct gpio_extcon_data *extcon_data = dev_get_drvdata(dev); const char *state; + if (extcon_get_state(edev)) state = extcon_data->state_on; else @@ -97,14 +99,27 @@ static int gpio_extcon_probe(struct platform_device *pdev) if (!extcon_data) return -ENOMEM; - extcon_data->edev.name = pdata->name; - extcon_data->edev.dev.parent = &pdev->dev; + extcon_data->edev = devm_extcon_dev_allocate(&pdev->dev, NULL); + if (IS_ERR(extcon_data->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + extcon_data->edev->name = pdata->name; + extcon_data->edev->dev.parent = &pdev->dev; + extcon_data->gpio = pdata->gpio; extcon_data->gpio_active_low = pdata->gpio_active_low; extcon_data->state_on = pdata->state_on; extcon_data->state_off = pdata->state_off; + extcon_data->check_on_resume = pdata->check_on_resume; if (pdata->state_on && pdata->state_off) - extcon_data->edev.print_state = extcon_gpio_print_state; + extcon_data->edev->print_state = extcon_gpio_print_state; + + ret = devm_gpio_request_one(&pdev->dev, extcon_data->gpio, GPIOF_DIR_IN, + pdev->name); + if (ret < 0) + return ret; + if (pdata->debounce) { ret = gpio_set_debounce(extcon_data->gpio, pdata->debounce * 1000); @@ -113,39 +128,27 @@ static int gpio_extcon_probe(struct platform_device *pdev) msecs_to_jiffies(pdata->debounce); } - ret = extcon_dev_register(&extcon_data->edev); + ret = devm_extcon_dev_register(&pdev->dev, extcon_data->edev); if (ret < 0) return ret; - ret = devm_gpio_request_one(&pdev->dev, extcon_data->gpio, GPIOF_DIR_IN, - pdev->name); - if (ret < 0) - goto err; - INIT_DELAYED_WORK(&extcon_data->work, gpio_extcon_work); extcon_data->irq = gpio_to_irq(extcon_data->gpio); - if (extcon_data->irq < 0) { - ret = extcon_data->irq; - goto err; - } + if (extcon_data->irq < 0) + return extcon_data->irq; ret = request_any_context_irq(extcon_data->irq, gpio_irq_handler, pdata->irq_flags, pdev->name, extcon_data); if (ret < 0) - goto err; + return ret; platform_set_drvdata(pdev, extcon_data); /* Perform initial detection */ gpio_extcon_work(&extcon_data->work.work); return 0; - -err: - extcon_dev_unregister(&extcon_data->edev); - - return ret; } static int gpio_extcon_remove(struct platform_device *pdev) @@ -154,17 +157,33 @@ static int gpio_extcon_remove(struct platform_device *pdev) cancel_delayed_work_sync(&extcon_data->work); free_irq(extcon_data->irq, extcon_data); - extcon_dev_unregister(&extcon_data->edev); return 0; } +#ifdef CONFIG_PM_SLEEP +static int gpio_extcon_resume(struct device *dev) +{ + struct gpio_extcon_data *extcon_data; + + extcon_data = dev_get_drvdata(dev); + if (extcon_data->check_on_resume) + queue_delayed_work(system_power_efficient_wq, + &extcon_data->work, extcon_data->debounce_jiffies); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(gpio_extcon_pm_ops, NULL, gpio_extcon_resume); + static struct platform_driver gpio_extcon_driver = { .probe = gpio_extcon_probe, .remove = gpio_extcon_remove, .driver = { .name = "extcon-gpio", .owner = THIS_MODULE, + .pm = &gpio_extcon_pm_ops, }, }; diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c new file mode 100644 index 00000000000..d49e891b567 --- /dev/null +++ b/drivers/extcon/extcon-max14577.c @@ -0,0 +1,823 @@ +/* + * extcon-max14577.c - MAX14577/77836 extcon driver to support MUIC + * + * Copyright (C) 2013,2014 Samsung Electrnoics + * Chanwoo Choi <cw00.choi@samsung.com> + * Krzysztof Kozlowski <k.kozlowski@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/mfd/max14577.h> +#include <linux/mfd/max14577-private.h> +#include <linux/extcon.h> + +#define DELAY_MS_DEFAULT 17000 /* unit: millisecond */ + +enum max14577_muic_adc_debounce_time { + ADC_DEBOUNCE_TIME_5MS = 0, + ADC_DEBOUNCE_TIME_10MS, + ADC_DEBOUNCE_TIME_25MS, + ADC_DEBOUNCE_TIME_38_62MS, +}; + +enum max14577_muic_status { + MAX14577_MUIC_STATUS1 = 0, + MAX14577_MUIC_STATUS2 = 1, + MAX14577_MUIC_STATUS_END, +}; + +/** + * struct max14577_muic_irq + * @irq: the index of irq list of MUIC device. + * @name: the name of irq. + * @virq: the virtual irq to use irq domain + */ +struct max14577_muic_irq { + unsigned int irq; + const char *name; + unsigned int virq; +}; + +static struct max14577_muic_irq max14577_muic_irqs[] = { + { MAX14577_IRQ_INT1_ADC, "muic-ADC" }, + { MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" }, + { MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" }, + { MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" }, + { MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" }, + { MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" }, + { MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" }, + { MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" }, +}; + +static struct max14577_muic_irq max77836_muic_irqs[] = { + { MAX14577_IRQ_INT1_ADC, "muic-ADC" }, + { MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" }, + { MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" }, + { MAX77836_IRQ_INT1_ADC1K, "muic-ADC1K" }, + { MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" }, + { MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" }, + { MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" }, + { MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" }, + { MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" }, + { MAX77836_IRQ_INT2_VIDRM, "muic-VIDRM" }, +}; + +struct max14577_muic_info { + struct device *dev; + struct max14577 *max14577; + struct extcon_dev *edev; + int prev_cable_type; + int prev_chg_type; + u8 status[MAX14577_MUIC_STATUS_END]; + + struct max14577_muic_irq *muic_irqs; + unsigned int muic_irqs_num; + bool irq_adc; + bool irq_chg; + struct work_struct irq_work; + struct mutex mutex; + + /* + * Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + struct delayed_work wq_detcable; + + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + int path_usb; + int path_uart; +}; + +enum max14577_muic_cable_group { + MAX14577_CABLE_GROUP_ADC = 0, + MAX14577_CABLE_GROUP_CHG, +}; + +/* Define supported accessory type */ +enum max14577_muic_acc_type { + MAX14577_MUIC_ADC_GROUND = 0x0, + MAX14577_MUIC_ADC_SEND_END_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S1_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S2_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S3_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S4_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S5_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S6_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S7_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S8_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S9_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S10_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S11_BUTTON, + MAX14577_MUIC_ADC_REMOTE_S12_BUTTON, + MAX14577_MUIC_ADC_RESERVED_ACC_1, + MAX14577_MUIC_ADC_RESERVED_ACC_2, + MAX14577_MUIC_ADC_RESERVED_ACC_3, + MAX14577_MUIC_ADC_RESERVED_ACC_4, + MAX14577_MUIC_ADC_RESERVED_ACC_5, + MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE2, + MAX14577_MUIC_ADC_PHONE_POWERED_DEV, + MAX14577_MUIC_ADC_TTY_CONVERTER, + MAX14577_MUIC_ADC_UART_CABLE, + MAX14577_MUIC_ADC_CEA936A_TYPE1_CHG, + MAX14577_MUIC_ADC_FACTORY_MODE_USB_OFF, + MAX14577_MUIC_ADC_FACTORY_MODE_USB_ON, + MAX14577_MUIC_ADC_AV_CABLE_NOLOAD, + MAX14577_MUIC_ADC_CEA936A_TYPE2_CHG, + MAX14577_MUIC_ADC_FACTORY_MODE_UART_OFF, + MAX14577_MUIC_ADC_FACTORY_MODE_UART_ON, + MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE1, /* with Remote and Simple Ctrl */ + MAX14577_MUIC_ADC_OPEN, +}; + +/* max14577 MUIC device support below list of accessories(external connector) */ +enum { + EXTCON_CABLE_USB = 0, + EXTCON_CABLE_TA, + EXTCON_CABLE_FAST_CHARGER, + EXTCON_CABLE_SLOW_CHARGER, + EXTCON_CABLE_CHARGE_DOWNSTREAM, + EXTCON_CABLE_JIG_USB_ON, + EXTCON_CABLE_JIG_USB_OFF, + EXTCON_CABLE_JIG_UART_OFF, + EXTCON_CABLE_JIG_UART_ON, + + _EXTCON_CABLE_NUM, +}; + +static const char *max14577_extcon_cable[] = { + [EXTCON_CABLE_USB] = "USB", + [EXTCON_CABLE_TA] = "TA", + [EXTCON_CABLE_FAST_CHARGER] = "Fast-charger", + [EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger", + [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream", + [EXTCON_CABLE_JIG_USB_ON] = "JIG-USB-ON", + [EXTCON_CABLE_JIG_USB_OFF] = "JIG-USB-OFF", + [EXTCON_CABLE_JIG_UART_OFF] = "JIG-UART-OFF", + [EXTCON_CABLE_JIG_UART_ON] = "JIG-UART-ON", + + NULL, +}; + +/* + * max14577_muic_set_debounce_time - Set the debounce time of ADC + * @info: the instance including private data of max14577 MUIC + * @time: the debounce time of ADC + */ +static int max14577_muic_set_debounce_time(struct max14577_muic_info *info, + enum max14577_muic_adc_debounce_time time) +{ + u8 ret; + + switch (time) { + case ADC_DEBOUNCE_TIME_5MS: + case ADC_DEBOUNCE_TIME_10MS: + case ADC_DEBOUNCE_TIME_25MS: + case ADC_DEBOUNCE_TIME_38_62MS: + ret = max14577_update_reg(info->max14577->regmap, + MAX14577_MUIC_REG_CONTROL3, + CTRL3_ADCDBSET_MASK, + time << CTRL3_ADCDBSET_SHIFT); + if (ret) { + dev_err(info->dev, "failed to set ADC debounce time\n"); + return ret; + } + break; + default: + dev_err(info->dev, "invalid ADC debounce time\n"); + return -EINVAL; + } + + return 0; +}; + +/* + * max14577_muic_set_path - Set hardware line according to attached cable + * @info: the instance including private data of max14577 MUIC + * @value: the path according to attached cable + * @attached: the state of cable (true:attached, false:detached) + * + * The max14577 MUIC device share outside H/W line among a varity of cables + * so, this function set internal path of H/W line according to the type of + * attached cable. + */ +static int max14577_muic_set_path(struct max14577_muic_info *info, + u8 val, bool attached) +{ + int ret = 0; + u8 ctrl1, ctrl2 = 0; + + /* Set open state to path before changing hw path */ + ret = max14577_update_reg(info->max14577->regmap, + MAX14577_MUIC_REG_CONTROL1, + CLEAR_IDBEN_MICEN_MASK, CTRL1_SW_OPEN); + if (ret < 0) { + dev_err(info->dev, "failed to update MUIC register\n"); + return ret; + } + + if (attached) + ctrl1 = val; + else + ctrl1 = CTRL1_SW_OPEN; + + ret = max14577_update_reg(info->max14577->regmap, + MAX14577_MUIC_REG_CONTROL1, + CLEAR_IDBEN_MICEN_MASK, ctrl1); + if (ret < 0) { + dev_err(info->dev, "failed to update MUIC register\n"); + return ret; + } + + if (attached) + ctrl2 |= CTRL2_CPEN_MASK; /* LowPwr=0, CPEn=1 */ + else + ctrl2 |= CTRL2_LOWPWR_MASK; /* LowPwr=1, CPEn=0 */ + + ret = max14577_update_reg(info->max14577->regmap, + MAX14577_REG_CONTROL2, + CTRL2_LOWPWR_MASK | CTRL2_CPEN_MASK, ctrl2); + if (ret < 0) { + dev_err(info->dev, "failed to update MUIC register\n"); + return ret; + } + + dev_dbg(info->dev, + "CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s\n", + ctrl1, ctrl2, attached ? "attached" : "detached"); + + return 0; +} + +/* + * max14577_muic_get_cable_type - Return cable type and check cable state + * @info: the instance including private data of max14577 MUIC + * @group: the path according to attached cable + * @attached: store cable state and return + * + * This function check the cable state either attached or detached, + * and then divide precise type of cable according to cable group. + * - max14577_CABLE_GROUP_ADC + * - max14577_CABLE_GROUP_CHG + */ +static int max14577_muic_get_cable_type(struct max14577_muic_info *info, + enum max14577_muic_cable_group group, bool *attached) +{ + int cable_type = 0; + int adc; + int chg_type; + + switch (group) { + case MAX14577_CABLE_GROUP_ADC: + /* + * Read ADC value to check cable type and decide cable state + * according to cable type + */ + adc = info->status[MAX14577_MUIC_STATUS1] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + /* + * Check current cable state/cable type and store cable type + * (info->prev_cable_type) for handling cable when cable is + * detached. + */ + if (adc == MAX14577_MUIC_ADC_OPEN) { + *attached = false; + + cable_type = info->prev_cable_type; + info->prev_cable_type = MAX14577_MUIC_ADC_OPEN; + } else { + *attached = true; + + cable_type = info->prev_cable_type = adc; + } + break; + case MAX14577_CABLE_GROUP_CHG: + /* + * Read charger type to check cable type and decide cable state + * according to type of charger cable. + */ + chg_type = info->status[MAX14577_MUIC_STATUS2] & + STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + if (chg_type == MAX14577_CHARGER_TYPE_NONE) { + *attached = false; + + cable_type = info->prev_chg_type; + info->prev_chg_type = MAX14577_CHARGER_TYPE_NONE; + } else { + *attached = true; + + /* + * Check current cable state/cable type and store cable + * type(info->prev_chg_type) for handling cable when + * charger cable is detached. + */ + cable_type = info->prev_chg_type = chg_type; + } + + break; + default: + dev_err(info->dev, "Unknown cable group (%d)\n", group); + cable_type = -EINVAL; + break; + } + + return cable_type; +} + +static int max14577_muic_jig_handler(struct max14577_muic_info *info, + int cable_type, bool attached) +{ + char cable_name[32]; + int ret = 0; + u8 path = CTRL1_SW_OPEN; + + dev_dbg(info->dev, + "external connector is %s (adc:0x%02x)\n", + attached ? "attached" : "detached", cable_type); + + switch (cable_type) { + case MAX14577_MUIC_ADC_FACTORY_MODE_USB_OFF: /* ADC_JIG_USB_OFF */ + /* PATH:AP_USB */ + strcpy(cable_name, "JIG-USB-OFF"); + path = CTRL1_SW_USB; + break; + case MAX14577_MUIC_ADC_FACTORY_MODE_USB_ON: /* ADC_JIG_USB_ON */ + /* PATH:AP_USB */ + strcpy(cable_name, "JIG-USB-ON"); + path = CTRL1_SW_USB; + break; + case MAX14577_MUIC_ADC_FACTORY_MODE_UART_OFF: /* ADC_JIG_UART_OFF */ + /* PATH:AP_UART */ + strcpy(cable_name, "JIG-UART-OFF"); + path = CTRL1_SW_UART; + break; + default: + dev_err(info->dev, "failed to detect %s jig cable\n", + attached ? "attached" : "detached"); + return -EINVAL; + } + + ret = max14577_muic_set_path(info, path, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, cable_name, attached); + + return 0; +} + +static int max14577_muic_adc_handler(struct max14577_muic_info *info) +{ + int cable_type; + bool attached; + int ret = 0; + + /* Check accessory state which is either detached or attached */ + cable_type = max14577_muic_get_cable_type(info, + MAX14577_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 MAX14577_MUIC_ADC_FACTORY_MODE_USB_OFF: + case MAX14577_MUIC_ADC_FACTORY_MODE_USB_ON: + case MAX14577_MUIC_ADC_FACTORY_MODE_UART_OFF: + /* JIG */ + ret = max14577_muic_jig_handler(info, cable_type, attached); + if (ret < 0) + return ret; + break; + case MAX14577_MUIC_ADC_GROUND: + case MAX14577_MUIC_ADC_SEND_END_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S1_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S2_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S3_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S4_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S5_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S6_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S7_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S8_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S9_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S10_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S11_BUTTON: + case MAX14577_MUIC_ADC_REMOTE_S12_BUTTON: + case MAX14577_MUIC_ADC_RESERVED_ACC_1: + case MAX14577_MUIC_ADC_RESERVED_ACC_2: + case MAX14577_MUIC_ADC_RESERVED_ACC_3: + case MAX14577_MUIC_ADC_RESERVED_ACC_4: + case MAX14577_MUIC_ADC_RESERVED_ACC_5: + case MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE2: + case MAX14577_MUIC_ADC_PHONE_POWERED_DEV: + case MAX14577_MUIC_ADC_TTY_CONVERTER: + case MAX14577_MUIC_ADC_UART_CABLE: + case MAX14577_MUIC_ADC_CEA936A_TYPE1_CHG: + case MAX14577_MUIC_ADC_AV_CABLE_NOLOAD: + case MAX14577_MUIC_ADC_CEA936A_TYPE2_CHG: + case MAX14577_MUIC_ADC_FACTORY_MODE_UART_ON: + case MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE1: + /* + * This accessory isn't used in general case if it is specially + * needed to detect additional accessory, should implement + * proper operation when this accessory is attached/detached. + */ + dev_info(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 max14577_muic_chg_handler(struct max14577_muic_info *info) +{ + int chg_type; + bool attached; + int ret = 0; + + chg_type = max14577_muic_get_cable_type(info, + MAX14577_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 MAX14577_CHARGER_TYPE_USB: + /* PATH:AP_USB */ + ret = max14577_muic_set_path(info, info->path_usb, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "USB", attached); + break; + case MAX14577_CHARGER_TYPE_DEDICATED_CHG: + extcon_set_cable_state(info->edev, "TA", attached); + break; + case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: + extcon_set_cable_state(info->edev, + "Charge-downstream", attached); + break; + case MAX14577_CHARGER_TYPE_SPECIAL_500MA: + extcon_set_cable_state(info->edev, "Slow-charger", attached); + break; + case MAX14577_CHARGER_TYPE_SPECIAL_1A: + extcon_set_cable_state(info->edev, "Fast-charger", attached); + break; + case MAX14577_CHARGER_TYPE_NONE: + case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + break; + default: + dev_err(info->dev, + "failed to detect %s accessory (chg_type:0x%x)\n", + attached ? "attached" : "detached", chg_type); + return -EINVAL; + } + + return 0; +} + +static void max14577_muic_irq_work(struct work_struct *work) +{ + struct max14577_muic_info *info = container_of(work, + struct max14577_muic_info, irq_work); + int ret = 0; + + if (!info->edev) + return; + + mutex_lock(&info->mutex); + + ret = max14577_bulk_read(info->max14577->regmap, + MAX14577_MUIC_REG_STATUS1, info->status, 2); + if (ret) { + dev_err(info->dev, "failed to read MUIC register\n"); + mutex_unlock(&info->mutex); + return; + } + + if (info->irq_adc) { + ret = max14577_muic_adc_handler(info); + info->irq_adc = false; + } + if (info->irq_chg) { + ret = max14577_muic_chg_handler(info); + info->irq_chg = false; + } + + if (ret < 0) + dev_err(info->dev, "failed to handle MUIC interrupt\n"); + + mutex_unlock(&info->mutex); + + return; +} + +/* + * Sets irq_adc or irq_chg in max14577_muic_info and returns 1. + * Returns 0 if irq_type does not match registered IRQ for this device type. + */ +static int max14577_parse_irq(struct max14577_muic_info *info, int irq_type) +{ + switch (irq_type) { + case MAX14577_IRQ_INT1_ADC: + case MAX14577_IRQ_INT1_ADCLOW: + case MAX14577_IRQ_INT1_ADCERR: + /* Handle all of accessory except for + type of charger accessory */ + info->irq_adc = true; + return 1; + case MAX14577_IRQ_INT2_CHGTYP: + case MAX14577_IRQ_INT2_CHGDETRUN: + case MAX14577_IRQ_INT2_DCDTMR: + case MAX14577_IRQ_INT2_DBCHG: + case MAX14577_IRQ_INT2_VBVOLT: + /* Handle charger accessory */ + info->irq_chg = true; + return 1; + default: + return 0; + } +} + +/* + * Sets irq_adc or irq_chg in max14577_muic_info and returns 1. + * Returns 0 if irq_type does not match registered IRQ for this device type. + */ +static int max77836_parse_irq(struct max14577_muic_info *info, int irq_type) +{ + /* First check common max14577 interrupts */ + if (max14577_parse_irq(info, irq_type)) + return 1; + + switch (irq_type) { + case MAX77836_IRQ_INT1_ADC1K: + info->irq_adc = true; + return 1; + case MAX77836_IRQ_INT2_VIDRM: + /* Handle charger accessory */ + info->irq_chg = true; + return 1; + default: + return 0; + } +} + +static irqreturn_t max14577_muic_irq_handler(int irq, void *data) +{ + struct max14577_muic_info *info = data; + int i, irq_type = -1; + bool irq_parsed; + + /* + * We may be called multiple times for different nested IRQ-s. + * Including changes in INT1_ADC and INT2_CGHTYP at once. + * However we only need to know whether it was ADC, charger + * or both interrupts so decode IRQ and turn on proper flags. + */ + for (i = 0; i < info->muic_irqs_num; i++) + if (irq == info->muic_irqs[i].virq) + irq_type = info->muic_irqs[i].irq; + + switch (info->max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + irq_parsed = max77836_parse_irq(info, irq_type); + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + irq_parsed = max14577_parse_irq(info, irq_type); + break; + } + + if (!irq_parsed) { + dev_err(info->dev, "muic interrupt: irq %d occurred, skipped\n", + irq_type); + return IRQ_HANDLED; + } + schedule_work(&info->irq_work); + + return IRQ_HANDLED; +} + +static int max14577_muic_detect_accessory(struct max14577_muic_info *info) +{ + int ret = 0; + int adc; + int chg_type; + bool attached; + + mutex_lock(&info->mutex); + + /* Read STATUSx register to detect accessory */ + ret = max14577_bulk_read(info->max14577->regmap, + MAX14577_MUIC_REG_STATUS1, info->status, 2); + if (ret) { + dev_err(info->dev, "failed to read MUIC register\n"); + mutex_unlock(&info->mutex); + return ret; + } + + adc = max14577_muic_get_cable_type(info, MAX14577_CABLE_GROUP_ADC, + &attached); + if (attached && adc != MAX14577_MUIC_ADC_OPEN) { + ret = max14577_muic_adc_handler(info); + if (ret < 0) { + dev_err(info->dev, "Cannot detect accessory\n"); + mutex_unlock(&info->mutex); + return ret; + } + } + + chg_type = max14577_muic_get_cable_type(info, MAX14577_CABLE_GROUP_CHG, + &attached); + if (attached && chg_type != MAX14577_CHARGER_TYPE_NONE) { + ret = max14577_muic_chg_handler(info); + if (ret < 0) { + dev_err(info->dev, "Cannot detect charger accessory\n"); + mutex_unlock(&info->mutex); + return ret; + } + } + + mutex_unlock(&info->mutex); + + return 0; +} + +static void max14577_muic_detect_cable_wq(struct work_struct *work) +{ + struct max14577_muic_info *info = container_of(to_delayed_work(work), + struct max14577_muic_info, wq_detcable); + + max14577_muic_detect_accessory(info); +} + +static int max14577_muic_probe(struct platform_device *pdev) +{ + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent); + struct max14577_muic_info *info; + int delay_jiffies; + int ret; + int i; + u8 id; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + info->dev = &pdev->dev; + info->max14577 = max14577; + + platform_set_drvdata(pdev, info); + mutex_init(&info->mutex); + + INIT_WORK(&info->irq_work, max14577_muic_irq_work); + + switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + info->muic_irqs = max77836_muic_irqs; + info->muic_irqs_num = ARRAY_SIZE(max77836_muic_irqs); + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + info->muic_irqs = max14577_muic_irqs; + info->muic_irqs_num = ARRAY_SIZE(max14577_muic_irqs); + } + + /* Support irq domain for max14577 MUIC device */ + for (i = 0; i < info->muic_irqs_num; i++) { + struct max14577_muic_irq *muic_irq = &info->muic_irqs[i]; + unsigned int virq = 0; + + virq = regmap_irq_get_virq(max14577->irq_data, muic_irq->irq); + if (virq <= 0) + return -EINVAL; + muic_irq->virq = virq; + + ret = devm_request_threaded_irq(&pdev->dev, virq, NULL, + max14577_muic_irq_handler, + IRQF_NO_SUSPEND, + muic_irq->name, info); + if (ret) { + dev_err(&pdev->dev, + "failed: irq request (IRQ: %d," + " error :%d)\n", + muic_irq->irq, ret); + return ret; + } + } + + /* Initialize extcon device */ + info->edev = devm_extcon_dev_allocate(&pdev->dev, + max14577_extcon_cable); + if (IS_ERR(info->edev)) { + dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); + return -ENOMEM; + } + + info->edev->name = dev_name(&pdev->dev); + + ret = devm_extcon_dev_register(&pdev->dev, info->edev); + if (ret) { + dev_err(&pdev->dev, "failed to register extcon device\n"); + return ret; + } + + /* Default h/w line path */ + info->path_usb = CTRL1_SW_USB; + info->path_uart = CTRL1_SW_UART; + delay_jiffies = msecs_to_jiffies(DELAY_MS_DEFAULT); + + /* Set initial path for UART */ + max14577_muic_set_path(info, info->path_uart, true); + + /* Check revision number of MUIC device*/ + ret = max14577_read_reg(info->max14577->regmap, + MAX14577_REG_DEVICEID, &id); + if (ret < 0) { + dev_err(&pdev->dev, "failed to read revision number\n"); + return ret; + } + dev_info(info->dev, "device ID : 0x%x\n", id); + + /* Set ADC debounce time */ + max14577_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); + + /* + * Detect accessory after completing the initialization of platform + * + * - Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + INIT_DELAYED_WORK(&info->wq_detcable, max14577_muic_detect_cable_wq); + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + delay_jiffies); + + return ret; +} + +static int max14577_muic_remove(struct platform_device *pdev) +{ + struct max14577_muic_info *info = platform_get_drvdata(pdev); + + cancel_work_sync(&info->irq_work); + + return 0; +} + +static const struct platform_device_id max14577_muic_id[] = { + { "max14577-muic", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836-muic", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max14577_muic_id); + +static struct platform_driver max14577_muic_driver = { + .driver = { + .name = "max14577-muic", + .owner = THIS_MODULE, + }, + .probe = max14577_muic_probe, + .remove = max14577_muic_remove, + .id_table = max14577_muic_id, +}; + +module_platform_driver(max14577_muic_driver); + +MODULE_DESCRIPTION("Maxim 14577/77836 Extcon driver"); +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>, Krzysztof Kozlowski <k.kozlowski@samsung.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:extcon-max14577"); diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index da268fbc901..2c7c3e19159 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1175,25 +1175,24 @@ static int max77693_muic_probe(struct platform_device *pdev) } /* Initialize extcon device */ - info->edev = devm_kzalloc(&pdev->dev, sizeof(struct extcon_dev), - GFP_KERNEL); - if (!info->edev) { + info->edev = devm_extcon_dev_allocate(&pdev->dev, + max77693_extcon_cable); + if (IS_ERR(info->edev)) { dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); ret = -ENOMEM; goto err_irq; } info->edev->name = DEV_NAME; info->edev->dev.parent = &pdev->dev; - info->edev->supported_cable = max77693_extcon_cable; - ret = extcon_dev_register(info->edev); + + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); goto err_irq; } - /* Initialize MUIC register by using platform data or default data */ - if (pdata->muic_data) { + if (pdata && pdata->muic_data) { init_data = pdata->muic_data->init_data; num_init_data = pdata->muic_data->num_init_data; } else { @@ -1226,7 +1225,7 @@ static int max77693_muic_probe(struct platform_device *pdev) = init_data[i].data; } - if (pdata->muic_data) { + if (pdata && pdata->muic_data) { struct max77693_muic_platform_data *muic_pdata = pdata->muic_data; @@ -1267,7 +1266,7 @@ static int max77693_muic_probe(struct platform_device *pdev) MAX77693_MUIC_REG_ID, &id); if (ret < 0) { dev_err(&pdev->dev, "failed to read revision number\n"); - goto err_extcon; + goto err_irq; } dev_info(info->dev, "device ID : 0x%x\n", id); @@ -1283,12 +1282,11 @@ static int max77693_muic_probe(struct platform_device *pdev) * driver should notify cable state to upper layer. */ INIT_DELAYED_WORK(&info->wq_detcable, max77693_muic_detect_cable_wq); - schedule_delayed_work(&info->wq_detcable, delay_jiffies); + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + delay_jiffies); return ret; -err_extcon: - extcon_dev_unregister(info->edev); err_irq: while (--i >= 0) free_irq(muic_irqs[i].virq, info); @@ -1304,7 +1302,6 @@ static int max77693_muic_remove(struct platform_device *pdev) free_irq(muic_irqs[i].virq, info); cancel_work_sync(&info->irq_work); input_unregister_device(info->dock); - extcon_dev_unregister(info->edev); return 0; } diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 6a00464658c..d9f7f1baaa0 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -699,23 +699,22 @@ static int max8997_muic_probe(struct platform_device *pdev) } /* External connector */ - info->edev = devm_kzalloc(&pdev->dev, sizeof(struct extcon_dev), - GFP_KERNEL); - if (!info->edev) { + info->edev = devm_extcon_dev_allocate(&pdev->dev, max8997_extcon_cable); + if (IS_ERR(info->edev)) { dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); ret = -ENOMEM; goto err_irq; } info->edev->name = DEV_NAME; info->edev->dev.parent = &pdev->dev; - info->edev->supported_cable = max8997_extcon_cable; - ret = extcon_dev_register(info->edev); + + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); goto err_irq; } - if (pdata->muic_pdata) { + if (pdata && pdata->muic_pdata) { struct max8997_muic_platform_data *muic_pdata = pdata->muic_pdata; @@ -770,7 +769,8 @@ static int max8997_muic_probe(struct platform_device *pdev) * driver should notify cable state to upper layer. */ INIT_DELAYED_WORK(&info->wq_detcable, max8997_muic_detect_cable_wq); - schedule_delayed_work(&info->wq_detcable, delay_jiffies); + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + delay_jiffies); return 0; @@ -789,8 +789,6 @@ static int max8997_muic_remove(struct platform_device *pdev) free_irq(muic_irqs[i].virq, info); cancel_work_sync(&info->irq_work); - extcon_dev_unregister(info->edev); - return 0; } diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index 6c91976dd82..7417ce84eb2 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -23,6 +23,7 @@ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/platform_device.h> +#include <linux/slab.h> #include <linux/err.h> #include <linux/mfd/palmas.h> #include <linux/of.h> @@ -56,7 +57,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) { if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) { palmas_usb->linkstat = PALMAS_USB_STATE_VBUS; - extcon_set_cable_state(&palmas_usb->edev, "USB", true); + extcon_set_cable_state(palmas_usb->edev, "USB", true); dev_info(palmas_usb->dev, "USB cable is attached\n"); } else { dev_dbg(palmas_usb->dev, @@ -65,7 +66,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) } else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) { if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) { palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; - extcon_set_cable_state(&palmas_usb->edev, "USB", false); + extcon_set_cable_state(palmas_usb->edev, "USB", false); dev_info(palmas_usb->dev, "USB cable is detached\n"); } else { dev_dbg(palmas_usb->dev, @@ -78,31 +79,40 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb) { - unsigned int set; + unsigned int set, id_src; struct palmas_usb *palmas_usb = _palmas_usb; palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_ID_INT_LATCH_SET, &set); + palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE, + PALMAS_USB_ID_INT_SRC, &id_src); - if (set & PALMAS_USB_ID_INT_SRC_ID_GND) { + if ((set & PALMAS_USB_ID_INT_SRC_ID_GND) && + (id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) { palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_ID_INT_LATCH_CLR, PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND); palmas_usb->linkstat = PALMAS_USB_STATE_ID; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", true); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true); dev_info(palmas_usb->dev, "USB-HOST cable is attached\n"); - } else if (set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) { + } else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) && + (id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) { palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE, PALMAS_USB_ID_INT_LATCH_CLR, PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT); palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false); dev_info(palmas_usb->dev, "USB-HOST cable is detached\n"); } else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) && (!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) { palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false); dev_info(palmas_usb->dev, "USB-HOST cable is detached\n"); + } else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) && + (id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) { + palmas_usb->linkstat = PALMAS_USB_STATE_ID; + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true); + dev_info(palmas_usb->dev, " USB-HOST cable is attached\n"); } return IRQ_HANDLED; @@ -177,13 +187,20 @@ static int palmas_usb_probe(struct platform_device *pdev) platform_set_drvdata(pdev, palmas_usb); - palmas_usb->edev.supported_cable = palmas_extcon_cable; - palmas_usb->edev.dev.parent = palmas_usb->dev; - palmas_usb->edev.mutually_exclusive = mutually_exclusive; + palmas_usb->edev = devm_extcon_dev_allocate(&pdev->dev, + palmas_extcon_cable); + if (IS_ERR(palmas_usb->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + palmas_usb->edev->name = kstrdup(node->name, GFP_KERNEL); + palmas_usb->edev->dev.parent = palmas_usb->dev; + palmas_usb->edev->mutually_exclusive = mutually_exclusive; - status = extcon_dev_register(&palmas_usb->edev); + status = devm_extcon_dev_register(&pdev->dev, palmas_usb->edev); if (status) { dev_err(&pdev->dev, "failed to register extcon device\n"); + kfree(palmas_usb->edev->name); return status; } @@ -197,7 +214,8 @@ static int palmas_usb_probe(struct platform_device *pdev) if (status < 0) { dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", palmas_usb->id_irq, status); - goto fail_extcon; + kfree(palmas_usb->edev->name); + return status; } } @@ -211,25 +229,21 @@ static int palmas_usb_probe(struct platform_device *pdev) if (status < 0) { dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", palmas_usb->vbus_irq, status); - goto fail_extcon; + kfree(palmas_usb->edev->name); + return status; } } palmas_enable_irq(palmas_usb); device_set_wakeup_capable(&pdev->dev, true); return 0; - -fail_extcon: - extcon_dev_unregister(&palmas_usb->edev); - - return status; } static int palmas_usb_remove(struct platform_device *pdev) { struct palmas_usb *palmas_usb = platform_get_drvdata(pdev); - extcon_dev_unregister(&palmas_usb->edev); + kfree(palmas_usb->edev->name); return 0; } @@ -262,14 +276,13 @@ static int palmas_usb_resume(struct device *dev) }; #endif -static const struct dev_pm_ops palmas_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(palmas_usb_suspend, - palmas_usb_resume) -}; +static SIMPLE_DEV_PM_OPS(palmas_pm_ops, palmas_usb_suspend, palmas_usb_resume); static struct of_device_id of_palmas_match_tbl[] = { { .compatible = "ti,palmas-usb", }, + { .compatible = "ti,palmas-usb-vid", }, { .compatible = "ti,twl6035-usb", }, + { .compatible = "ti,twl6035-usb-vid", }, { /* end */ } }; diff --git a/drivers/extcon/of_extcon.c b/drivers/extcon/of_extcon.c deleted file mode 100644 index 72173ecbb31..00000000000 --- a/drivers/extcon/of_extcon.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * OF helpers for External connector (extcon) framework - * - * Copyright (C) 2013 Texas Instruments, Inc. - * Kishon Vijay Abraham I <kishon@ti.com> - * - * Copyright (C) 2013 Samsung Electronics - * Chanwoo Choi <cw00.choi@samsung.com> - * - * 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 <linux/module.h> -#include <linux/slab.h> -#include <linux/err.h> -#include <linux/extcon.h> -#include <linux/of.h> -#include <linux/of_platform.h> -#include <linux/extcon/of_extcon.h> - -/* - * of_extcon_get_extcon_dev - Get the name of 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 *of_extcon_get_extcon_dev(struct device *dev, int index) -{ - struct device_node *node; - struct extcon_dev *edev; - struct platform_device *extcon_parent_dev; - - if (!dev->of_node) { - dev_dbg(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_dbg(dev, "failed to get phandle in %s node\n", - dev->of_node->full_name); - return ERR_PTR(-ENODEV); - } - - extcon_parent_dev = of_find_device_by_node(node); - if (!extcon_parent_dev) { - dev_dbg(dev, "unable to find device by node\n"); - return ERR_PTR(-EPROBE_DEFER); - } - - edev = extcon_get_extcon_dev(dev_name(&extcon_parent_dev->dev)); - if (!edev) { - dev_dbg(dev, "unable to get extcon device : %s\n", - dev_name(&extcon_parent_dev->dev)); - return ERR_PTR(-ENODEV); - } - - return edev; -} -EXPORT_SYMBOL_GPL(of_extcon_get_extcon_dev); |
