summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/extcon/Kconfig9
-rw-r--r--drivers/extcon/Makefile1
-rw-r--r--drivers/extcon/extcon-port.c588
-rw-r--r--include/linux/extcon/extcon-port.h46
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