diff options
-rw-r--r-- | drivers/extcon/Kconfig | 9 | ||||
-rw-r--r-- | drivers/extcon/Makefile | 1 | ||||
-rw-r--r-- | drivers/extcon/extcon-port.c | 588 | ||||
-rw-r--r-- | include/linux/extcon/extcon-port.h | 46 |
4 files changed, 644 insertions, 0 deletions
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 3297301a42d..90b3ad038b4 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -53,4 +53,13 @@ config EXTCON_ARIZONA with Wolfson Arizona devices. These are audio CODECs with advanced audio accessory detection support. +config EXTCON_PORT + tristate "MUIC/Jack compatible interface support with EXTCON" + depends on !JACK_MON + help + If you say yes here you get support compatibility of interface + between old JACK driver and EXTCON. EXTCON_PORT provide same + interface as old JACK driver to remove confusion of applying + EXTCON to platform. + endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index f98a3c4d46e..034bb95404b 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o +obj-$(CONFIG_EXTCON_PORT) += extcon-port.o diff --git a/drivers/extcon/extcon-port.c b/drivers/extcon/extcon-port.c new file mode 100644 index 00000000000..c3121f5ce48 --- /dev/null +++ b/drivers/extcon/extcon-port.c @@ -0,0 +1,588 @@ +/* + * drivers/extcon/extcon-port.c + * + * MUIC/JACK compatible extcon driver with MUIC/JACK Extcon device driver + * + * Copyright (C) 2012 Samsung Electronics + * Chanwoo Choi <cw00.choi@samsung.com> + * + * based on drivers/misc/jack.c + * Copyright (C) 2009 Samsung Electronics + * Minkyu Kang <mk7.samsung.com> + * + * 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. + */ + +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/extcon.h> +#include <linux/extcon/extcon-port.h> +#include <linux/mfd/max77693.h> + +struct extcon_cable_block { + char extcon_name[32]; + char name[32]; + + struct extcon_specific_cable_nb obj; + struct work_struct wq; + struct notifier_block nb; + + bool prev_attached; + bool attached; + + void (*function)(struct extcon_cable_block *cable); +}; + +struct extcon_port { + char extcon_name_muic[CABLE_NAME_MAX]; + struct extcon_dev *edev_muic; + struct extcon_cable_block *cables_muic; + + char extcon_name_jack[CABLE_NAME_MAX]; + struct extcon_dev *edev_jack; + struct extcon_cable_block *cables_jack; + + char extcon_name_hdmi[CABLE_NAME_MAX]; + struct extcon_dev *edev_hdmi; + struct extcon_cable_block *cables_hdmi; + + struct jack_platform_data *pdata; +}; + +static struct platform_device *extcon_port_pdev; + +/* Dummy event handler */ +void jack_event_handler(char *name, int value) {} +EXPORT_SYMBOL_GPL(jack_event_handler); + +int jack_get_data(const char *name) +{ + struct extcon_port *extcon_port + = platform_get_drvdata(extcon_port_pdev); + + if (!strcmp(name, "usb")) + return extcon_port->pdata->usb_online; + else if (!strcmp(name, "charger")) + return extcon_port->pdata->charger_online; + else if (!strcmp(name, "hdmi")) + return extcon_port->pdata->hdmi_online; + else if (!strcmp(name, "earjack")) + return extcon_port->pdata->earjack_online; + else if (!strcmp(name, "earkey")) + return extcon_port->pdata->earkey_online; + else if (!strcmp(name, "ums")) + return extcon_port->pdata->ums_online; + else if (!strcmp(name, "cdrom")) + return extcon_port->pdata->cdrom_online; + else if (!strcmp(name, "jig")) + return extcon_port->pdata->jig_online; + else if (!strcmp(name, "host")) + return extcon_port->pdata->host_online; + else if (!strcmp(name, "cradle")) + return extcon_port->pdata->cradle_online; + + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(jack_get_data); + +#define EXTCON_PORT_ENTRY(name) \ +static ssize_t extcon_port_show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct extcon_port *extcon_port = dev_get_drvdata(dev); \ + return sprintf(buf, "%d\n", extcon_port->pdata->name); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, extcon_port_show_##name, NULL); + +EXTCON_PORT_ENTRY(usb_online); +EXTCON_PORT_ENTRY(charger_online); +EXTCON_PORT_ENTRY(hdmi_online); +EXTCON_PORT_ENTRY(earjack_online); +EXTCON_PORT_ENTRY(earkey_online); +EXTCON_PORT_ENTRY(jig_online); +EXTCON_PORT_ENTRY(host_online); +EXTCON_PORT_ENTRY(cradle_online); + +static int extcon_port_create_sysfs(struct extcon_port *extcon_port) +{ + struct jack_platform_data *pdata = extcon_port->pdata; + int ret; + + if (pdata->usb_online != -1) + ret = device_create_file(&extcon_port_pdev->dev, + &dev_attr_usb_online); + if (pdata->charger_online != -1) + ret = device_create_file(&extcon_port_pdev->dev, + &dev_attr_charger_online); + if (pdata->hdmi_online != -1) + ret = device_create_file(&extcon_port_pdev->dev, + &dev_attr_hdmi_online); + if (pdata->earjack_online != -1) + ret = device_create_file(&extcon_port_pdev->dev, + &dev_attr_earjack_online); + if (pdata->earkey_online != -1) + ret = device_create_file(&extcon_port_pdev->dev, + &dev_attr_earkey_online); + if (pdata->jig_online != -1) + ret = device_create_file(&extcon_port_pdev->dev, + &dev_attr_jig_online); + if (pdata->host_online != -1) + ret = device_create_file(&extcon_port_pdev->dev, + &dev_attr_host_online); + if (pdata->cradle_online != -1) + ret = device_create_file(&extcon_port_pdev->dev, + &dev_attr_cradle_online); + + return 0; +} + +static void extcon_port_set_data(struct jack_platform_data *pdata, + const char *name, int value) +{ + if (!strcmp(name, "usb")) + pdata->usb_online = value; + else if (!strcmp(name, "charger")) + pdata->charger_online = value; + else if (!strcmp(name, "hdmi")) + pdata->hdmi_online = value; + else if (!strcmp(name, "earjack")) + pdata->earjack_online = value; + else if (!strcmp(name, "earkey")) + pdata->earkey_online = value; + else if (!strcmp(name, "ums")) + pdata->ums_online = value; + else if (!strcmp(name, "cdrom")) + pdata->cdrom_online = value; + else if (!strcmp(name, "jig")) + pdata->jig_online = value; + else if (!strcmp(name, "host")) + pdata->host_online = value; + else if (!strcmp(name, "cradle")) + pdata->cradle_online = value; +} + +static int extcon_port_event_handler(char *name, int value) +{ + struct extcon_port *extcon_port; + char env_str[16]; + char *envp[] = { env_str, NULL }; + int ret = 0; + + if (!extcon_port_pdev) { + printk(KERN_ERR "jack device is not allocated\n"); + return -ENODEV; + } + + extcon_port = platform_get_drvdata(extcon_port_pdev); + extcon_port_set_data(extcon_port->pdata, name, value); + sprintf(env_str, "CHGDET=%s", name); + dev_info(&extcon_port_pdev->dev, "jack event %s\n", env_str); + ret = kobject_uevent_env(&extcon_port_pdev->dev.kobj, + KOBJ_CHANGE, envp); + if (ret < 0) + pr_err("Failed to send uevent to user-space : extcon-port\n"); + + return ret; +} + +static void extcon_muic_function(struct extcon_cable_block *cable) +{ + int attached = cable->attached; + int ret; + char muic_name[10]; + + if (!strcmp(cable->name, "USB")) { + strcpy(muic_name, "usb"); + } else if (!strcmp(cable->name, "USB-Host")) { + strcpy(muic_name, "host"); + } else if (!strcmp(cable->name, "TA") + || !strcmp(cable->name, "Fast-charger") + || !strcmp(cable->name, "Slow-charger") + || !strcmp(cable->name, "Charger-downstream") + || !strcmp(cable->name, "MHL_TA")) { + strcpy(muic_name, "charger"); + } else if (!strcmp(cable->name, "JIG-USB-ON") + || !strcmp(cable->name, "JIG-USB-OFF") + || !strcmp(cable->name, "JIG-UART-OFF")) { + strcpy(muic_name, "jig"); + } else if (!strcmp(cable->name, "Dock-Smart")) { + strcpy(muic_name, "cradle"); + + if (attached) + attached = MAX77693_MUIC_DOCK_SMARTDOCK; + } else if (!strcmp(cable->name, "Dock-Car")) { + strcpy(muic_name, "cradle"); + + if (attached) + attached = MAX77693_MUIC_DOCK_CARDOCK; + } else if (!strcmp(cable->name, "Dock-Desk") + || !strcmp(cable->name, "Dock-Audio")) { + strcpy(muic_name, "cradle"); + + if (attached) + attached = MAX77693_MUIC_DOCK_DESKDOCK; + } else { + pr_err("Cannot detect unknown cable\n"); + goto out; + } + + ret = extcon_port_event_handler(muic_name, attached); + if (ret < 0) { + pr_info("Faild to set jack event handler(%s)\n", + muic_name); + goto out; + } + + if (!strcmp(cable->name, "USB")) { + ret = extcon_port_event_handler("charger", attached); + if (ret < 0) { + pr_info("Faild to set jack event handler(%s)\n", + muic_name); + goto out; + } + } +out: + return; +} + +static void extcon_jack_function(struct extcon_cable_block *cable) +{ + int state = 0; + int ret; + char jack_name[10]; + + if (!strcmp(cable->name, "Headset")) { + strcpy(jack_name, "earjack"); + + if (cable->attached) + state = 3; + + } else if (!strcmp(cable->name, "Headphone")) { + strcpy(jack_name, "earjack"); + + if (cable->attached) + state = 1; + + } else if (!strcmp(cable->name, "Microphone")) { + strcpy(jack_name, "earkey"); + state = cable->attached; + + } else { + pr_err("Cannot detect unknown cable\n"); + goto out; + } + + ret = extcon_port_event_handler(jack_name, state); + if (ret < 0) { + pr_info("Faild to set jack event handler(%s)\n", + jack_name); + } +out: + return; +} + +static void extcon_hdmi_function(struct extcon_cable_block *cable) +{ + if (!strcmp(cable->name, "HDMI")) { + int ret; + + ret = extcon_port_event_handler("hdmi", cable->attached); + if (ret < 0) { + pr_info("Faild to set hdmi event handler(%s)\n", + cable->name); + } + } + + return; +} + +static void extcon_work(struct work_struct *work) +{ + struct extcon_cable_block *cable = + container_of(work, struct extcon_cable_block, wq); + + if (cable->function) + cable->function(cable); +} + +static int extcon_notifier(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct extcon_cable_block *cable = + container_of(self, struct extcon_cable_block, nb); + + /* + * The newly state of charger cable. + * If cable is attached, cable->attached is true. + */ + cable->prev_attached = cable->attached; + cable->attached = event; + + /* + * Setup work for controlling charger(regulator) + * according to charger cable. + */ + schedule_work(&cable->wq); + + return NOTIFY_DONE; +} + +int extcon_port_register( + char *extcon_name, + struct extcon_dev *edev, + struct extcon_cable_block *cables, + void (*fn)(struct extcon_cable_block *)) +{ + struct extcon_cable_block *cable; + int ret = 0, num_cables; + int i; + + if (!extcon_name) + return -EINVAL; + + edev = extcon_get_extcon_dev(extcon_name); + if (!edev) + return -ENODEV; + + num_cables = edev->max_supported; + + cables = kzalloc(sizeof(struct extcon_cable_block) * num_cables, + GFP_KERNEL); + if (!cables) { + pr_err("failed to allocate array of extcon_cable_block(%s)\n", + extcon_name); + return -ENOMEM; + } + + for (i = 0 ; i < num_cables ; i++) { + cable = &cables[i]; + + strcpy(cable->extcon_name, extcon_name); + strcpy(cable->name, edev->supported_cable[i]); + cable->function = fn; + + INIT_WORK(&cable->wq, extcon_work); + cable->nb.notifier_call = extcon_notifier; + ret = extcon_register_interest(&cable->obj, + cable->extcon_name, cable->name, &cable->nb); + if (ret < 0) { + pr_err("Cannot register extcon_dev for %s(cable: %s)\n", + cable->extcon_name, cable->name); + goto err_extcon; + } + } + + return 0; + +err_extcon: + for (i = 0 ; i < num_cables ; i++) { + cable = &cables[i]; + + /* Unregister only extcon device which is initialized */ + if (cable->nb.notifier_call) { + ret = extcon_unregister_interest(&cable->obj); + + pr_err("Unregister extcon_dev for %s(cable: %s)\n", + cable->extcon_name, cable->name); + } + } + kfree(cables); + + return ret; +} + +int extcon_port_unregister(struct extcon_dev *edev, + struct extcon_cable_block *cables) +{ + struct extcon_cable_block *cable; + int num_cables; + int ret = 0; + int i; + + if (!edev) + return -ENODEV; + + num_cables = edev->max_supported; + + for (i = 0 ; i < num_cables ; i++) { + cable = &cables[i]; + + /* Unregister only extcon device which is initialized */ + if (cable->nb.notifier_call) { + ret = extcon_unregister_interest(&cable->obj); + + pr_err("Unregister extcon_dev for %s(cable: %s)\n", + cable->extcon_name, cable->name); + } + } + kfree(cables); + + return ret; +} + +static int extcon_port_probe(struct platform_device *pdev) +{ + struct jack_platform_data *pdata = pdev->dev.platform_data; + struct extcon_port *extcon_port; + int ret; + + extcon_port = kzalloc(sizeof(struct extcon_port), GFP_KERNEL); + if (!extcon_port) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + ret = -ENOMEM; + goto err; + } + platform_set_drvdata(pdev, extcon_port); + extcon_port->pdata = pdata; + extcon_port_pdev = pdev; + + ret = extcon_port_create_sysfs(extcon_port); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to create sysfs\n"); + goto err_sysfs; + } + + if (!pdata->extcon_name_muic) { + dev_err(&pdev->dev, + "Cannot set name of muic device by platform data\n"); + } else { + strcpy(extcon_port->extcon_name_muic, pdata->extcon_name_muic); + ret = extcon_port_register(extcon_port->extcon_name_muic, + extcon_port->edev_muic, + extcon_port->cables_muic, + extcon_muic_function); + if (ret < 0) { + dev_err(&pdev->dev, + "Failed to register extcon port (%s:%d)\n", + pdata->extcon_name_muic, ret); + goto err_extcon_muic; + } + } + + if (!pdata->extcon_name_jack) { + dev_err(&pdev->dev, + "Cannot set name of jack device by platform data\n"); + } else { + strcpy(extcon_port->extcon_name_jack, pdata->extcon_name_jack); + ret = extcon_port_register(extcon_port->extcon_name_jack, + extcon_port->edev_jack, + extcon_port->cables_jack, + extcon_jack_function); + if (ret < 0) { + dev_err(&pdev->dev, + "Failed to register extcon port (%s:%d)\n", + pdata->extcon_name_jack, ret); + goto err_extcon_jack; + } + } + + if (!pdata->extcon_name_hdmi) { + dev_err(&pdev->dev, + "Cannot set name of hdmi device by platform data\n"); + } else { + strcpy(extcon_port->extcon_name_hdmi, pdata->extcon_name_hdmi); + ret = extcon_port_register(extcon_port->extcon_name_hdmi, + extcon_port->edev_hdmi, + extcon_port->cables_hdmi, + extcon_hdmi_function); + if (ret < 0) { + dev_err(&pdev->dev, + "Failed to register extcon port (%s:%d)\n", + pdata->extcon_name_hdmi, ret); + goto err_extcon_hdmi; + } + } + + return ret; + +err_extcon_hdmi: + ret = extcon_port_unregister(extcon_port->edev_hdmi, + extcon_port->cables_hdmi); +err_extcon_jack: + ret = extcon_port_unregister(extcon_port->edev_jack, + extcon_port->cables_jack); +err_extcon_muic: + ret = extcon_port_unregister(extcon_port->edev_muic, + extcon_port->cables_muic); +err_sysfs: + kfree(extcon_port); +err: + return ret; +} + +static int extcon_port_remove(struct platform_device *pdev) +{ + struct jack_platform_data *pdata = pdev->dev.platform_data; + struct extcon_port *extcon_port = platform_get_drvdata(pdev); + + if (pdata->usb_online != -1) + device_remove_file(&extcon_port_pdev->dev, + &dev_attr_usb_online); + if (pdata->charger_online != -1) + device_remove_file(&extcon_port_pdev->dev, + &dev_attr_charger_online); + if (pdata->hdmi_online != -1) + device_remove_file(&extcon_port_pdev->dev, + &dev_attr_hdmi_online); + if (pdata->earjack_online != -1) + device_remove_file(&extcon_port_pdev->dev, + &dev_attr_earjack_online); + if (pdata->earkey_online != -1) + device_remove_file(&extcon_port_pdev->dev, + &dev_attr_earkey_online); + if (pdata->jig_online != -1) + device_remove_file(&extcon_port_pdev->dev, + &dev_attr_jig_online); + if (pdata->host_online != -1) + device_remove_file(&extcon_port_pdev->dev, + &dev_attr_host_online); + if (pdata->cradle_online != -1) + device_remove_file(&extcon_port_pdev->dev, + &dev_attr_cradle_online); + + platform_set_drvdata(pdev, NULL); + + extcon_port_unregister(extcon_port->edev_hdmi, + extcon_port->cables_hdmi); + extcon_port_unregister(extcon_port->edev_muic, + extcon_port->cables_jack); + extcon_port_unregister(extcon_port->edev_jack, + extcon_port->cables_muic); + + kfree(extcon_port); + + return 0; +} + +static struct platform_driver extcon_port_driver = { + .probe = extcon_port_probe, + .remove = extcon_port_remove, + .driver = { + .name = "jack", + .owner = THIS_MODULE, + }, +}; + +static int __init extcon_port_init(void) +{ + return platform_driver_register(&extcon_port_driver); +} +late_initcall(extcon_port_init); + +static void __exit extcon_port_exit(void) +{ + platform_driver_unregister(&extcon_port_driver); +} +module_exit(extcon_port_exit); + +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); +MODULE_DESCRIPTION("MUIC/JACK compatible extcon driver with MUIC/JACK device"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/extcon/extcon-port.h b/include/linux/extcon/extcon-port.h new file mode 100644 index 00000000000..6438ec4fc2e --- /dev/null +++ b/include/linux/extcon/extcon-port.h @@ -0,0 +1,46 @@ +/* + * include/linux/extcon/extcon-port.h + * + * Copyright (C) 2012 Samsung Electronics + * Chanwoo Choi <cw00.choi@samsung.com> + * + * based on include/linux/jack.h + * Copyright (C) 2009 Samsung Electronics + * Minkyu Kang <mk7.samsung.com> + * + * 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. + */ + +#ifndef __EXTCON_PORT_H_ +#define __EXTCON_PORT_H_ + +struct jack_platform_data { + int usb_online; + int charger_online; + int hdmi_online; + int earjack_online; + int earkey_online; + int ums_online; + int cdrom_online; + int jig_online; + int host_online; + int cradle_online; + + char *extcon_name_muic; + char *extcon_name_jack; + char *extcon_name_hdmi; +}; + +#ifdef CONFIG_EXTCON_PORT +extern int jack_get_data(const char *name); +extern void jack_event_handler(char *name, int value); +#else +static int jack_get_data(const char *name) +{ + return 0; +} +static void jack_event_handler(char *name, int value) {} +#endif +#endif |