diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/usb/notify | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_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/Kconfig | 11 | ||||
-rw-r--r-- | drivers/usb/notify/Makefile | 4 | ||||
-rw-r--r-- | drivers/usb/notify/host_notifier.c | 284 | ||||
-rw-r--r-- | drivers/usb/notify/host_notify_class.c | 262 |
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"); |