aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/notify
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/usb/notify
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2
samsung update 1
Diffstat (limited to 'drivers/usb/notify')
-rw-r--r--drivers/usb/notify/Kconfig11
-rw-r--r--drivers/usb/notify/Makefile4
-rw-r--r--drivers/usb/notify/host_notifier.c284
-rw-r--r--drivers/usb/notify/host_notify_class.c262
4 files changed, 561 insertions, 0 deletions
diff --git a/drivers/usb/notify/Kconfig b/drivers/usb/notify/Kconfig
new file mode 100644
index 0000000..fef8c88
--- /dev/null
+++ b/drivers/usb/notify/Kconfig
@@ -0,0 +1,11 @@
+#
+# USB Host notify configuration
+#
+
+config USB_HOST_NOTIFY
+ boolean "USB Host notify Driver"
+ depends on USB
+ help
+ Android framework needs uevents for usb host operation.
+ Host notify Driver serves uevent format
+ that is used by usb host or otg. \ No newline at end of file
diff --git a/drivers/usb/notify/Makefile b/drivers/usb/notify/Makefile
new file mode 100644
index 0000000..5fd4512
--- /dev/null
+++ b/drivers/usb/notify/Makefile
@@ -0,0 +1,4 @@
+
+# host notify driver
+obj-y += host_notify_class.o
+obj-y += host_notifier.o
diff --git a/drivers/usb/notify/host_notifier.c b/drivers/usb/notify/host_notifier.c
new file mode 100644
index 0000000..613e84b
--- /dev/null
+++ b/drivers/usb/notify/host_notifier.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics Co. Ltd.
+ * Hyuk Kang <hyuk78.kang@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/irq.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/kthread.h>
+#include <linux/wakelock.h>
+#include <linux/host_notify.h>
+
+struct host_notifier_info {
+ struct host_notifier_platform_data *pdata;
+ struct task_struct *th;
+ struct wake_lock wlock;
+ struct delayed_work current_dwork;
+ wait_queue_head_t delay_wait;
+ int thread_remove;
+ int currentlimit_irq;
+};
+
+static struct host_notifier_info ninfo;
+
+static int currentlimit_thread(void *data)
+{
+ struct host_notifier_info *ninfo = data;
+ struct host_notify_dev *ndev = &ninfo->pdata->ndev;
+ int gpio = ninfo->pdata->gpio;
+ int prev = ndev->booster;
+ int ret = 0;
+
+ pr_info("host_notifier usbhostd: start %d\n", prev);
+
+ while (!kthread_should_stop()) {
+ wait_event_interruptible_timeout(ninfo->delay_wait,
+ ninfo->thread_remove, 1 * HZ);
+
+ ret = gpio_get_value(gpio);
+ if (prev != ret) {
+ pr_info("host_notifier usbhostd: gpio %d = %s\n",
+ gpio, ret ? "HIGH" : "LOW");
+ ndev->booster = ret ?
+ NOTIFY_POWER_ON : NOTIFY_POWER_OFF;
+ prev = ret;
+
+ if (!ret && ndev->mode == NOTIFY_HOST_MODE) {
+ host_state_notify(ndev,
+ NOTIFY_HOST_OVERCURRENT);
+ pr_err("host_notifier usbhostd: overcurrent\n");
+ break;
+ }
+ }
+ }
+
+ ninfo->thread_remove = 1;
+
+ pr_info("host_notifier usbhostd: exit %d\n", ret);
+ return 0;
+}
+
+static int start_usbhostd_thread(void)
+{
+ if (!ninfo.th) {
+ pr_info("host_notifier: start usbhostd thread\n");
+
+ init_waitqueue_head(&ninfo.delay_wait);
+ ninfo.thread_remove = 0;
+ ninfo.th = kthread_run(currentlimit_thread,
+ &ninfo, "usbhostd");
+
+ if (IS_ERR(ninfo.th)) {
+ pr_err("host_notifier: Unable to start usbhostd\n");
+ ninfo.th = NULL;
+ ninfo.thread_remove = 1;
+ return -1;
+ }
+ host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_ADD);
+ wake_lock(&ninfo.wlock);
+
+ } else
+ pr_info("host_notifier: usbhostd already started!\n");
+
+ return 0;
+}
+
+static int stop_usbhostd_thread(void)
+{
+ if (ninfo.th) {
+ pr_info("host_notifier: stop thread\n");
+
+ if (!ninfo.thread_remove)
+ kthread_stop(ninfo.th);
+
+ ninfo.th = NULL;
+ host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_REMOVE);
+ wake_unlock(&ninfo.wlock);
+ } else
+ pr_info("host_notifier: no thread\n");
+
+ return 0;
+}
+
+static int start_usbhostd_notify(void)
+{
+ pr_info("host_notifier: start usbhostd notify\n");
+
+ host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_ADD);
+ wake_lock(&ninfo.wlock);
+
+ return 0;
+}
+
+static int stop_usbhostd_notify(void)
+{
+ pr_info("host_notifier: stop usbhostd notify\n");
+
+ host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_REMOVE);
+ wake_unlock(&ninfo.wlock);
+
+ return 0;
+}
+
+static void host_notifier_booster(int enable)
+{
+ pr_info("host_notifier: booster %s\n", enable ? "ON" : "OFF");
+
+ ninfo.pdata->booster(enable);
+
+ if (ninfo.pdata->thread_enable) {
+ if (enable)
+ start_usbhostd_thread();
+ else
+ stop_usbhostd_thread();
+ }
+}
+
+static irqreturn_t currentlimit_irq_thread(int irq, void *data)
+{
+ struct host_notifier_info *hostinfo = data;
+ struct host_notify_dev *ndev = &hostinfo->pdata->ndev;
+ int gpio = hostinfo->pdata->gpio;
+ int prev = ndev->booster;
+ int ret = 0;
+
+ ret = gpio_get_value(gpio);
+ pr_info("currentlimit_irq_thread gpio : %d, value : %d\n", gpio, ret);
+
+ if (prev != ret) {
+ pr_info("host_notifier currentlimit_irq_thread: gpio %d = %s\n",
+ gpio, ret ? "HIGH" : "LOW");
+ ndev->booster = ret ?
+ NOTIFY_POWER_ON : NOTIFY_POWER_OFF;
+ prev = ret;
+
+ if (!ret && ndev->mode == NOTIFY_HOST_MODE) {
+ host_state_notify(ndev,
+ NOTIFY_HOST_OVERCURRENT);
+ pr_err("host_notifier currentlimit_irq_thread: overcurrent\n");
+ }
+ }
+ return IRQ_HANDLED;
+}
+
+static int currentlimit_irq_init(struct host_notifier_info *hostinfo)
+{
+ int ret = 0;
+
+ hostinfo->currentlimit_irq = gpio_to_irq(hostinfo->pdata->gpio);
+
+ ret = request_threaded_irq(hostinfo->currentlimit_irq, NULL,
+ currentlimit_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "overcurrent_detect", hostinfo);
+ if (ret)
+ pr_info("host_notifier: %s return : %d\n", __func__, ret);
+
+ return ret;
+}
+
+static void currentlimit_irq_work(struct work_struct *work)
+{
+ int retval;
+ struct host_notifier_info *hostinfo = container_of(work,
+ struct host_notifier_info, current_dwork.work);
+
+ retval = currentlimit_irq_init(hostinfo);
+
+ if (retval)
+ pr_err("host_notifier: %s retval : %d\n", __func__, retval);
+ return;
+}
+
+static int host_notifier_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ if (pdev && pdev->dev.platform_data)
+ ninfo.pdata = pdev->dev.platform_data;
+ else {
+ pr_err("host_notifier: platform_data is null.\n");
+ return -ENODEV;
+ }
+
+ dev_info(&pdev->dev, "notifier_prove\n");
+
+ if (ninfo.pdata->thread_enable) {
+ ret = gpio_request(ninfo.pdata->gpio, "host_notifier");
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request %d\n",
+ ninfo.pdata->gpio);
+ return -EPERM;
+ }
+ gpio_direction_input(ninfo.pdata->gpio);
+ dev_info(&pdev->dev, "gpio = %d\n", ninfo.pdata->gpio);
+
+ ninfo.pdata->ndev.set_booster = host_notifier_booster;
+ ninfo.pdata->usbhostd_start = start_usbhostd_thread;
+ ninfo.pdata->usbhostd_stop = stop_usbhostd_thread;
+ } else if (ninfo.pdata->irq_enable) {
+ INIT_DELAYED_WORK(&ninfo.current_dwork, currentlimit_irq_work);
+ schedule_delayed_work(&ninfo.current_dwork,
+ msecs_to_jiffies(10000));
+ ninfo.pdata->ndev.set_booster = host_notifier_booster;
+ ninfo.pdata->usbhostd_start = start_usbhostd_notify;
+ ninfo.pdata->usbhostd_stop = stop_usbhostd_notify;
+ } else {
+ ninfo.pdata->ndev.set_booster = host_notifier_booster;
+ ninfo.pdata->usbhostd_start = start_usbhostd_notify;
+ ninfo.pdata->usbhostd_stop = stop_usbhostd_notify;
+ }
+
+ ret = host_notify_dev_register(&ninfo.pdata->ndev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to host_notify_dev_register\n");
+ return ret;
+ }
+ wake_lock_init(&ninfo.wlock, WAKE_LOCK_SUSPEND, "hostd");
+
+ return 0;
+}
+
+static int host_notifier_remove(struct platform_device *pdev)
+{
+ /* gpio_free(ninfo.pdata->gpio); */
+ host_notify_dev_unregister(&ninfo.pdata->ndev);
+ wake_lock_destroy(&ninfo.wlock);
+ return 0;
+}
+
+static struct platform_driver host_notifier_driver = {
+ .probe = host_notifier_probe,
+ .remove = host_notifier_remove,
+ .driver = {
+ .name = "host_notifier",
+ .owner = THIS_MODULE,
+ },
+};
+
+
+static int __init host_notifier_init(void)
+{
+ return platform_driver_register(&host_notifier_driver);
+}
+
+static void __init host_notifier_exit(void)
+{
+ platform_driver_unregister(&host_notifier_driver);
+}
+
+module_init(host_notifier_init);
+module_exit(host_notifier_exit);
+
+MODULE_AUTHOR("Hyuk Kang <hyuk78.kang@samsung.com>");
+MODULE_DESCRIPTION("USB Host notifier");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/notify/host_notify_class.c b/drivers/usb/notify/host_notify_class.c
new file mode 100644
index 0000000..9250568
--- /dev/null
+++ b/drivers/usb/notify/host_notify_class.c
@@ -0,0 +1,262 @@
+/*
+ * drivers/usb/notify/host_notify_class.c
+ *
+ * Copyright (C) 2011 Samsung, Inc.
+ * Author: Dongrak Shin <dongrak.shin@samsung.com>
+ *
+*/
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/err.h>
+#include <linux/host_notify.h>
+
+struct notify_data {
+ struct class *host_notify_class;
+ atomic_t device_count;
+};
+
+static struct notify_data host_notify;
+
+static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct host_notify_dev *ndev = (struct host_notify_dev *)
+ dev_get_drvdata(dev);
+ char *mode;
+
+ switch (ndev->mode) {
+ case NOTIFY_HOST_MODE:
+ mode = "HOST";
+ break;
+ case NOTIFY_PERIPHERAL_MODE:
+ mode = "PERIPHERAL";
+ break;
+ case NOTIFY_TEST_MODE:
+ mode = "TEST";
+ break;
+ case NOTIFY_NONE_MODE:
+ default:
+ mode = "NONE";
+ break;
+ }
+
+ printk(KERN_INFO "host_notify: read mode %s\n", mode);
+ return sprintf(buf, "%s\n", mode);
+}
+
+static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct host_notify_dev *ndev = (struct host_notify_dev *)
+ dev_get_drvdata(dev);
+
+ char *mode;
+ size_t ret = -ENOMEM;
+
+ mode = kzalloc(size + 1, GFP_KERNEL);
+ if (!mode)
+ goto error;
+
+ sscanf(buf, "%s", mode);
+
+ if (ndev->set_mode) {
+ printk(KERN_INFO "host_notify: set mode %s\n", mode);
+ if (!strcmp(mode, "HOST"))
+ ndev->set_mode(NOTIFY_SET_ON);
+ else if (!strcmp(mode, "NONE"))
+ ndev->set_mode(NOTIFY_SET_OFF);
+ }
+ ret = size;
+ kfree(mode);
+ error:
+ return ret;
+}
+
+static ssize_t booster_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct host_notify_dev *ndev = (struct host_notify_dev *)
+ dev_get_drvdata(dev);
+ char *booster;
+
+ switch (ndev->booster) {
+ case NOTIFY_POWER_ON:
+ booster = "ON";
+ break;
+ case NOTIFY_POWER_OFF:
+ default:
+ booster = "OFF";
+ break;
+ }
+
+ printk(KERN_INFO "host_notify: read booster %s\n", booster);
+ return sprintf(buf, "%s\n", booster);
+}
+
+static ssize_t booster_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct host_notify_dev *ndev = (struct host_notify_dev *)
+ dev_get_drvdata(dev);
+
+ char *booster;
+ size_t ret = -ENOMEM;
+
+ booster = kzalloc(size + 1, GFP_KERNEL);
+ if (!booster)
+ goto error;
+
+ sscanf(buf, "%s", booster);
+
+ if (ndev->set_booster) {
+ printk(KERN_INFO "host_notify: set booster %s\n", booster);
+ if (!strcmp(booster, "ON")) {
+ ndev->mode = NOTIFY_TEST_MODE;
+ ndev->set_booster(NOTIFY_SET_ON);
+ } else if (!strcmp(booster, "OFF")) {
+ ndev->mode = NOTIFY_NONE_MODE;
+ ndev->set_booster(NOTIFY_SET_OFF);
+ }
+ }
+ ret = size;
+ kfree(booster);
+ error:
+ return ret;
+}
+
+static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR | S_IWGRP, mode_show, mode_store);
+static DEVICE_ATTR(booster, S_IRUGO | S_IWUSR | S_IWGRP,
+ booster_show, booster_store);
+
+static struct attribute *host_notify_attrs[] = {
+ &dev_attr_mode.attr,
+ &dev_attr_booster.attr,
+ NULL,
+};
+
+static struct attribute_group host_notify_attr_grp = {
+ .attrs = host_notify_attrs,
+};
+
+void host_state_notify(struct host_notify_dev *ndev, int state)
+{
+ printk(KERN_INFO
+ "host_notify: ndev name=%s: from state=%d -> to state=%d\n",
+ ndev->name, ndev->state, state);
+ if (ndev->state != state) {
+ ndev->state = state;
+ kobject_uevent(&ndev->dev->kobj, KOBJ_CHANGE);
+ }
+}
+EXPORT_SYMBOL_GPL(host_state_notify);
+
+static int host_notify_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct host_notify_dev *ndev = (struct host_notify_dev *)
+ dev_get_drvdata(dev);
+ char *state;
+
+ if (!ndev) {
+ /* this happens when the device is first created */
+ return 0;
+ }
+ switch (ndev->state) {
+ case NOTIFY_HOST_ADD:
+ state = "ADD";
+ break;
+ case NOTIFY_HOST_REMOVE:
+ state = "REMOVE";
+ break;
+ case NOTIFY_HOST_OVERCURRENT:
+ state = "OVERCURRENT";
+ break;
+ case NOTIFY_HOST_LOWBATT:
+ state = "LOWBATT";
+ break;
+ case NOTIFY_HOST_UNKNOWN:
+ state = "UNKNOWN";
+ break;
+ case NOTIFY_HOST_NONE:
+ default:
+ return 0;
+ }
+ if (add_uevent_var(env, "DEVNAME=%s", ndev->dev->kobj.name))
+ return -ENOMEM;
+ if (add_uevent_var(env, "STATE=%s", state))
+ return -ENOMEM;
+ return 0;
+}
+
+static int create_notify_class(void)
+{
+ if (!host_notify.host_notify_class) {
+ host_notify.host_notify_class
+ = class_create(THIS_MODULE, "host_notify");
+ if (IS_ERR(host_notify.host_notify_class))
+ return PTR_ERR(host_notify.host_notify_class);
+ atomic_set(&host_notify.device_count, 0);
+ host_notify.host_notify_class->dev_uevent = host_notify_uevent;
+ }
+
+ return 0;
+}
+
+int host_notify_dev_register(struct host_notify_dev *ndev)
+{
+ int ret;
+
+ if (!host_notify.host_notify_class) {
+ ret = create_notify_class();
+ if (ret < 0)
+ return ret;
+ }
+
+ ndev->index = atomic_inc_return(&host_notify.device_count);
+ ndev->dev = device_create(host_notify.host_notify_class, NULL,
+ MKDEV(0, ndev->index), NULL, ndev->name);
+ if (IS_ERR(ndev->dev))
+ return PTR_ERR(ndev->dev);
+
+ ret = sysfs_create_group(&ndev->dev->kobj, &host_notify_attr_grp);
+ if (ret < 0) {
+ device_destroy(host_notify.host_notify_class,
+ MKDEV(0, ndev->index));
+ return ret;
+ }
+
+ dev_set_drvdata(ndev->dev, ndev);
+ ndev->state = 0;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(host_notify_dev_register);
+
+void host_notify_dev_unregister(struct host_notify_dev *ndev)
+{
+ ndev->state = NOTIFY_HOST_NONE;
+ sysfs_remove_group(&ndev->dev->kobj, &host_notify_attr_grp);
+ device_destroy(host_notify.host_notify_class, MKDEV(0, ndev->index));
+ dev_set_drvdata(ndev->dev, NULL);
+}
+EXPORT_SYMBOL_GPL(host_notify_dev_unregister);
+
+static int __init notify_class_init(void)
+{
+ return create_notify_class();
+}
+
+static void __exit notify_class_exit(void)
+{
+ class_destroy(host_notify.host_notify_class);
+}
+
+module_init(notify_class_init);
+module_exit(notify_class_exit);
+
+MODULE_AUTHOR("Dongrak Shin <dongrak.shin@samsung.com>");
+MODULE_DESCRIPTION("Usb host notify driver");
+MODULE_LICENSE("GPL");