android_kernel_samsung_hero.../drivers/usb/notify/usb_notify.c

1494 lines
36 KiB
C
Raw Normal View History

2016-08-17 10:41:52 +02:00
/*
* Copyright (C) 2011-2013 Samsung Electronics Co. Ltd.
*
* 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.
*/
#define pr_fmt(fmt) "usb_notify: " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/err.h>
#include <linux/wakelock.h>
#include <linux/kthread.h>
#include <linux/usb_notify.h>
#include "dock_notify.h"
#include "usb_notify_sysfs.h"
#define DEFAULT_OVC_POLL_SEC 3
struct ovc {
wait_queue_head_t delay_wait;
struct completion scanning_done;
struct task_struct *th;
struct mutex ovc_lock;
int thread_remove;
int can_ovc;
int poll_period;
int prev_state;
void *data;
int (*check_state)(void *);
};
struct vbus_gpio {
spinlock_t lock;
int gpio_status;
};
struct otg_state_work {
struct work_struct otg_work;
unsigned long event;
int enable;
};
struct otg_booting_delay {
struct delayed_work booting_work;
unsigned long reserve_state;
};
struct usb_notify {
struct otg_notify *o_notify;
struct notifier_block otg_nb;
struct notifier_block extra_nb;
struct vbus_gpio v_gpio;
struct host_notify_dev ndev;
struct usb_notify_dev udev;
struct workqueue_struct *notifier_wq;
struct wake_lock wlock;
struct otg_booster *booster;
struct ovc ovc_info;
struct otg_booting_delay b_delay;
struct delayed_work check_work;
int is_device;
int check_work_complete;
int oc_noti;
int diable_v_drive;
unsigned long c_type;
int c_status;
};
static struct usb_notify *u_notify;
/*
* Define event types.
* NOTIFY_EVENT_STATE can be called in both interrupt context
* and process context. But it executes queue_work.
* NOTIFY_EVENT_EXTRA can be called directly without queue_work.
* But it must be called in process context.
* NOTIFY_EVENT_DELAY events can not run inner booting delay.
* NOTIFY_EVENT_NEED_VBUSDRIVE events need to drive 5v out
* from phone charger ic
* NOTIFY_EVENT_NOBLOCKING events are not blocked by disable sysfs.
* NOTIFY_EVENT_NOSAVE events are not saved in cable type.
*/
static int check_event_type(enum otg_notify_events event)
{
int ret = 0;
switch (event) {
case NOTIFY_EVENT_OVERCURRENT:
case NOTIFY_EVENT_VBUSPOWER:
case NOTIFY_EVENT_SMSC_OVC:
case NOTIFY_EVENT_SMTD_EXT_CURRENT:
case NOTIFY_EVENT_MMD_EXT_CURRENT:
case NOTIFY_EVENT_DEVICE_CONNECT:
case NOTIFY_EVENT_GAMEPAD_CONNECT:
ret |= NOTIFY_EVENT_EXTRA;
break;
case NOTIFY_EVENT_VBUS:
case NOTIFY_EVENT_SMARTDOCK_USB:
ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_DELAY
| NOTIFY_EVENT_NEED_CLIENT);
break;
case NOTIFY_EVENT_HOST:
case NOTIFY_EVENT_HMT:
case NOTIFY_EVENT_GAMEPAD:
ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_NEED_VBUSDRIVE
| NOTIFY_EVENT_DELAY | NOTIFY_EVENT_NEED_HOST);
break;
case NOTIFY_EVENT_ALLDISABLE_NOTIFY:
case NOTIFY_EVENT_HOSTDISABLE_NOTIFY:
case NOTIFY_EVENT_CLIENTDISABLE_NOTIFY:
ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_NOBLOCKING
| NOTIFY_EVENT_NOSAVE);
break;
case NOTIFY_EVENT_DRIVE_VBUS:
case NOTIFY_EVENT_LANHUB_TA:
ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_NOSAVE
| NOTIFY_EVENT_NEED_HOST);
break;
case NOTIFY_EVENT_SMARTDOCK_TA:
case NOTIFY_EVENT_AUDIODOCK:
case NOTIFY_EVENT_LANHUB:
case NOTIFY_EVENT_MMDOCK:
ret |= NOTIFY_EVENT_NEED_HOST;
case NOTIFY_EVENT_CHARGER:
case NOTIFY_EVENT_NONE:
default:
ret |= NOTIFY_EVENT_STATE;
break;
}
return ret;
}
static const char *event_string(enum otg_notify_events event)
{
int virt;
virt = IS_VIRTUAL(event);
event = PHY_EVENT(event);
switch (event) {
case NOTIFY_EVENT_NONE:
return "none";
case NOTIFY_EVENT_VBUS:
return virt ? "vbus(virtual)" : "vbus";
case NOTIFY_EVENT_HOST:
return virt ? "host_id(virtual)" : "host_id";
case NOTIFY_EVENT_CHARGER:
return virt ? "charger(virtual)" : "charger";
case NOTIFY_EVENT_SMARTDOCK_TA:
return virt ? "smartdock_ta(virtual)" : "smartdock_ta";
case NOTIFY_EVENT_SMARTDOCK_USB:
return virt ? "smartdock_usb(virtual)" : "smartdock_usb";
case NOTIFY_EVENT_AUDIODOCK:
return virt ? "audiodock(virtual)" : "audiodock";
case NOTIFY_EVENT_LANHUB:
return virt ? "lanhub(virtual)" : "lanhub";
case NOTIFY_EVENT_LANHUB_TA:
return virt ? "lanhub_ta(virtual)" : "lanhub_ta";
case NOTIFY_EVENT_MMDOCK:
return virt ? "mmdock(virtual)" : "mmdock";
case NOTIFY_EVENT_HMT:
return virt ? "hmt(virtual)" : "hmt";
case NOTIFY_EVENT_GAMEPAD:
return virt ? "gamepad(virtual)" : "gamepad";
case NOTIFY_EVENT_DRIVE_VBUS:
return "drive_vbus";
case NOTIFY_EVENT_ALLDISABLE_NOTIFY:
return "disable_all_notify";
case NOTIFY_EVENT_HOSTDISABLE_NOTIFY:
return "disable_host_notify";
case NOTIFY_EVENT_CLIENTDISABLE_NOTIFY:
return "disable_client_notify";
case NOTIFY_EVENT_OVERCURRENT:
return "overcurrent";
case NOTIFY_EVENT_VBUSPOWER:
return "vbus_power";
case NOTIFY_EVENT_SMSC_OVC:
return "smsc_ovc";
case NOTIFY_EVENT_SMTD_EXT_CURRENT:
return "smtd_ext_current";
case NOTIFY_EVENT_MMD_EXT_CURRENT:
return "mmd_ext_current";
case NOTIFY_EVENT_DEVICE_CONNECT:
return "device_connect";
case NOTIFY_EVENT_GAMEPAD_CONNECT:
return "gamepad_connect";
default:
return "undefined";
}
}
static const char *block_string(enum otg_notify_block_type type)
{
switch (type) {
case NOTIFY_BLOCK_TYPE_NONE:
return "block_off";
case NOTIFY_BLOCK_TYPE_HOST:
return "block_host";
case NOTIFY_BLOCK_TYPE_CLIENT:
return "block_client";
case NOTIFY_BLOCK_TYPE_ALL:
return "block_all";
default:
return "undefined";
}
}
static bool is_host_cable_block(void)
{
if ((check_event_type(u_notify->c_type)
& NOTIFY_EVENT_NEED_HOST) &&
(u_notify->c_status == NOTIFY_EVENT_BLOCKED
|| u_notify->c_status == NOTIFY_EVENT_BLOCKING))
return true;
else
return false;
}
static bool is_host_cable_enable(void)
{
if ((check_event_type(u_notify->c_type)
& NOTIFY_EVENT_NEED_HOST) &&
(u_notify->c_status == NOTIFY_EVENT_ENABLED
|| u_notify->c_status == NOTIFY_EVENT_ENABLING))
return true;
else
return false;
}
static bool is_client_cable_block(void)
{
if ((check_event_type(u_notify->c_type)
& NOTIFY_EVENT_NEED_CLIENT) &&
(u_notify->c_status == NOTIFY_EVENT_BLOCKED
|| u_notify->c_status == NOTIFY_EVENT_BLOCKING))
return true;
else
return false;
}
static bool is_client_cable_enable(void)
{
if ((check_event_type(u_notify->c_type)
& NOTIFY_EVENT_NEED_CLIENT) &&
(u_notify->c_status == NOTIFY_EVENT_ENABLED
|| u_notify->c_status == NOTIFY_EVENT_ENABLING))
return true;
else
return false;
}
static bool check_block_event(unsigned long event)
{
if ((test_bit(NOTIFY_BLOCK_TYPE_HOST, &u_notify->udev.disable_state)
&& (check_event_type(event) & NOTIFY_EVENT_NEED_HOST))
|| (test_bit(NOTIFY_BLOCK_TYPE_CLIENT,
&u_notify->udev.disable_state)
&& (check_event_type(event) & NOTIFY_EVENT_NEED_CLIENT)))
return true;
else
return false;
}
static void enable_ovc(struct usb_notify *u_noti, int enable)
{
u_noti->ovc_info.can_ovc = enable;
}
static int ovc_scan_thread(void *data)
{
struct ovc *ovcinfo = NULL;
int state;
if (!u_notify) {
pr_err("%s u_notify is NULL\n", __func__);
return 0;
}
ovcinfo = &u_notify->ovc_info;
while (!kthread_should_stop()) {
wait_event_interruptible_timeout(ovcinfo->delay_wait,
ovcinfo->thread_remove, (ovcinfo->poll_period)*HZ);
if (ovcinfo->thread_remove)
break;
mutex_lock(&u_notify->ovc_info.ovc_lock);
if (ovcinfo->check_state
&& ovcinfo->data
&& ovcinfo->can_ovc) {
state = ovcinfo->check_state(data);
if (ovcinfo->prev_state != state) {
if (state == HNOTIFY_LOW) {
pr_err("%s overcurrent detected\n",
__func__);
host_state_notify(&u_notify->ndev,
NOTIFY_HOST_OVERCURRENT);
} else if (state == HNOTIFY_HIGH) {
pr_info("%s vbus draw detected\n",
__func__);
host_state_notify(&u_notify->ndev,
NOTIFY_HOST_NONE);
}
}
ovcinfo->prev_state = state;
}
mutex_unlock(&u_notify->ovc_info.ovc_lock);
if (!ovcinfo->can_ovc)
ovcinfo->thread_remove = 1;
}
pr_info("ovc_scan_thread exit\n");
complete_and_exit(&ovcinfo->scanning_done, 0);
return 0;
}
void ovc_start(struct usb_notify *u_noti)
{
struct otg_notify *o_notify = get_otg_notify();
if (!o_notify) {
pr_err("%s o_notify is NULL\n", __func__);
goto skip;
}
if (!u_noti->ovc_info.can_ovc)
goto skip;
u_noti->ovc_info.prev_state = HNOTIFY_INITIAL;
u_noti->ovc_info.poll_period = (o_notify->smsc_ovc_poll_sec) ?
o_notify->smsc_ovc_poll_sec : DEFAULT_OVC_POLL_SEC;
reinit_completion(&u_noti->ovc_info.scanning_done);
u_noti->ovc_info.thread_remove = 0;
u_noti->ovc_info.th = kthread_run(ovc_scan_thread,
u_noti->ovc_info.data, "ovc-scan-thread");
if (IS_ERR(u_noti->ovc_info.th)) {
pr_err("Unable to start the ovc-scanning thread\n");
complete(&u_noti->ovc_info.scanning_done);
}
pr_info("%s on\n", __func__);
return;
skip:
complete(&u_noti->ovc_info.scanning_done);
pr_info("%s skip\n", __func__);
}
void ovc_stop(struct usb_notify *u_noti)
{
u_noti->ovc_info.thread_remove = 1;
wake_up_interruptible(&u_noti->ovc_info.delay_wait);
wait_for_completion(&u_noti->ovc_info.scanning_done);
mutex_lock(&u_noti->ovc_info.ovc_lock);
u_noti->ovc_info.check_state = NULL;
u_noti->ovc_info.data = 0;
mutex_unlock(&u_noti->ovc_info.ovc_lock);
pr_info("%s\n", __func__);
}
static void ovc_init(struct usb_notify *u_noti)
{
init_waitqueue_head(&u_noti->ovc_info.delay_wait);
init_completion(&u_noti->ovc_info.scanning_done);
mutex_init(&u_noti->ovc_info.ovc_lock);
u_noti->ovc_info.prev_state = HNOTIFY_INITIAL;
pr_info("%s\n", __func__);
}
static irqreturn_t vbus_irq_isr(int irq, void *data)
{
struct otg_notify *otg_notify;
unsigned long flags = 0;
int gpio_value = 0;
irqreturn_t ret = IRQ_NONE;
if (!u_notify) {
pr_err("u_notify is NULL\n");
return ret;
}
otg_notify = u_notify->o_notify;
if (!otg_notify) {
pr_err("otg_notify is NULL\n");
return ret;
}
spin_lock_irqsave(&u_notify->v_gpio.lock, flags);
gpio_value = gpio_get_value(otg_notify->vbus_detect_gpio);
if (u_notify->v_gpio.gpio_status != gpio_value) {
u_notify->v_gpio.gpio_status = gpio_value;
ret = IRQ_WAKE_THREAD;
} else
ret = IRQ_HANDLED;
spin_unlock_irqrestore(&u_notify->v_gpio.lock, flags);
return ret;
}
static irqreturn_t vbus_irq_thread(int irq, void *data)
{
struct otg_notify *notify = NULL;
unsigned long flags = 0;
int gpio_value = 0;
if (!u_notify) {
pr_err("u_notify is NULL\n");
return IRQ_HANDLED;
}
notify = u_notify->o_notify;
spin_lock_irqsave(&u_notify->v_gpio.lock, flags);
gpio_value = u_notify->v_gpio.gpio_status;
spin_unlock_irqrestore(&u_notify->v_gpio.lock, flags);
if (gpio_value) {
u_notify->ndev.booster = NOTIFY_POWER_ON;
pr_info("vbus on detect\n");
if (notify->post_vbus_detect)
notify->post_vbus_detect(NOTIFY_POWER_ON);
} else {
if ((u_notify->ndev.mode == NOTIFY_HOST_MODE)
&& (u_notify->ndev.booster == NOTIFY_POWER_ON
&& u_notify->oc_noti)) {
host_state_notify(&u_notify->ndev,
NOTIFY_HOST_OVERCURRENT);
pr_err("OTG overcurrent!!!!!!\n");
} else {
pr_info("vbus off detect\n");
if (notify->post_vbus_detect)
notify->post_vbus_detect(NOTIFY_POWER_OFF);
}
u_notify->ndev.booster = NOTIFY_POWER_OFF;
}
return IRQ_HANDLED;
}
int register_gpios(struct otg_notify *n)
{
int ret = 0;
int vbus_irq = 0;
int vbus_gpio = -1;
int redriver_gpio = -1;
if (!u_notify) {
pr_err("u_notify is NULL\n");
return ret;
}
if (!gpio_is_valid(n->vbus_detect_gpio))
goto redriver_en_gpio_phase;
vbus_gpio = n->vbus_detect_gpio;
spin_lock_init(&u_notify->v_gpio.lock);
if (n->pre_gpio)
n->pre_gpio(vbus_gpio, NOTIFY_VBUS);
ret = gpio_request(vbus_gpio, "vbus_detect_notify");
if (ret) {
pr_err("failed to request %d\n", vbus_gpio);
goto err;
}
gpio_direction_input(vbus_gpio);
u_notify->v_gpio.gpio_status
= gpio_get_value(vbus_gpio);
vbus_irq = gpio_to_irq(vbus_gpio);
ret = request_threaded_irq(vbus_irq,
vbus_irq_isr,
vbus_irq_thread,
(IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING |
IRQF_ONESHOT),
"vbus_irq_notify",
NULL);
if (ret) {
pr_err("Failed to register IRQ\n");
goto err;
}
if (n->post_gpio)
n->post_gpio(vbus_gpio, NOTIFY_VBUS);
pr_info("vbus detect gpio %d is registered.\n", vbus_gpio);
redriver_en_gpio_phase:
if (!gpio_is_valid(n->redriver_en_gpio))
goto err;
redriver_gpio = n->redriver_en_gpio;
if (n->pre_gpio)
n->pre_gpio(redriver_gpio, NOTIFY_REDRIVER);
ret = gpio_request(redriver_gpio, "usb30_redriver_en");
if (ret) {
pr_err("failed to request %d\n", redriver_gpio);
goto err;
}
gpio_direction_output(redriver_gpio, 0);
if (n->post_gpio)
n->post_gpio(redriver_gpio, NOTIFY_REDRIVER);
pr_info("redriver en gpio %d is registered.\n", redriver_gpio);
err:
return ret;
}
int do_notify_blockstate(struct otg_notify *notify, unsigned long event,
int type, int enable)
{
int ret = 0;
if (!u_notify) {
pr_err("%s u_notify is NULL\n", __func__);
goto skip;
}
switch (event) {
case NOTIFY_EVENT_NONE:
case NOTIFY_EVENT_CHARGER:
break;
case NOTIFY_EVENT_SMARTDOCK_USB:
case NOTIFY_EVENT_VBUS:
break;
case NOTIFY_EVENT_LANHUB:
case NOTIFY_EVENT_HMT:
case NOTIFY_EVENT_HOST:
case NOTIFY_EVENT_MMDOCK:
case NOTIFY_EVENT_SMARTDOCK_TA:
case NOTIFY_EVENT_AUDIODOCK:
case NOTIFY_EVENT_GAMEPAD:
if (notify->unsupport_host) {
pr_err("This model doesn't support usb host\n");
goto skip;
}
if (enable)
host_state_notify(&u_notify->ndev, NOTIFY_HOST_BLOCK);
else
host_state_notify(&u_notify->ndev, NOTIFY_HOST_NONE);
break;
case NOTIFY_EVENT_DRIVE_VBUS:
ret = -ESRCH;
break;
default:
break;
}
skip:
return ret;
}
static void update_cable_status(struct otg_notify *notify, unsigned long event,
int virtual, int enable, int start)
{
if (enable) {
u_notify->c_type = event;
if (check_block_event(event) ||
(check_event_type(u_notify->c_type)
& NOTIFY_EVENT_NEED_HOST &&
notify->unsupport_host))
u_notify->c_status = (start) ?
NOTIFY_EVENT_BLOCKING : NOTIFY_EVENT_BLOCKED;
else
u_notify->c_status = (start) ?
NOTIFY_EVENT_ENABLING : NOTIFY_EVENT_ENABLED;
} else {
if (virtual)
u_notify->c_status = (start) ?
NOTIFY_EVENT_BLOCKING : NOTIFY_EVENT_BLOCKED;
else {
u_notify->c_type = NOTIFY_EVENT_NONE;
u_notify->c_status = (start) ?
NOTIFY_EVENT_DISABLING : NOTIFY_EVENT_DISABLED;
}
}
}
static void otg_notify_state(unsigned long event, int enable)
{
struct otg_notify *notify = NULL;
int type = 0;
int virtual = 0;
unsigned long prev_c_type = 0;
if (!u_notify) {
pr_err("u_notify is NULL\n");
goto err;
}
pr_info("%s+ event=%s(%lu), enable=%s\n", __func__,
event_string(event), event, enable == 0 ? "off" : "on");
notify = get_otg_notify();
if (!notify) {
pr_err("notify is NULL\n");
goto no_save_event;
}
prev_c_type = u_notify->c_type;
virtual = IS_VIRTUAL(event);
event = PHY_EVENT(event);
type = check_event_type(event);
if (!(type & NOTIFY_EVENT_NOSAVE))
update_cable_status(notify, event, virtual, enable, 1);
if (check_block_event(event) &&
!(type & NOTIFY_EVENT_NOBLOCKING)) {
pr_err("%s usb notify is blocked\n", __func__);
if (do_notify_blockstate(notify, event, type, enable))
goto no_save_event;
else
goto err2;
}
switch (event) {
case NOTIFY_EVENT_NONE:
break;
case NOTIFY_EVENT_SMARTDOCK_USB:
case NOTIFY_EVENT_VBUS:
if (enable) {
u_notify->ndev.mode = NOTIFY_PERIPHERAL_MODE;
if (notify->is_wakelock)
wake_lock(&u_notify->wlock);
if (gpio_is_valid(notify->redriver_en_gpio))
gpio_direction_output
(notify->redriver_en_gpio, 1);
if (notify->set_peripheral)
notify->set_peripheral(true);
} else {
u_notify->ndev.mode = NOTIFY_NONE_MODE;
if (notify->set_peripheral)
notify->set_peripheral(false);
if (gpio_is_valid(notify->redriver_en_gpio))
gpio_direction_output
(notify->redriver_en_gpio, 0);
if (notify->is_wakelock)
wake_unlock(&u_notify->wlock);
}
break;
case NOTIFY_EVENT_LANHUB_TA:
u_notify->diable_v_drive = enable;
if (enable)
u_notify->oc_noti = 0;
if (notify->set_lanhubta)
notify->set_lanhubta(enable);
break;
case NOTIFY_EVENT_LANHUB:
if (notify->unsupport_host) {
pr_err("This model doesn't support usb host\n");
goto err2;
}
u_notify->diable_v_drive = enable;
if (enable) {
u_notify->oc_noti = 0;
u_notify->ndev.mode = NOTIFY_HOST_MODE;
if (notify->is_wakelock)
wake_lock(&u_notify->wlock);
host_state_notify(&u_notify->ndev, NOTIFY_HOST_ADD);
if (gpio_is_valid(notify->redriver_en_gpio))
gpio_direction_output
(notify->redriver_en_gpio, 1);
if (notify->set_host)
notify->set_host(true);
} else {
u_notify->ndev.mode = NOTIFY_NONE_MODE;
if (notify->set_host)
notify->set_host(false);
if (gpio_is_valid(notify->redriver_en_gpio))
gpio_direction_output
(notify->redriver_en_gpio, 0);
host_state_notify(&u_notify->ndev, NOTIFY_HOST_REMOVE);
if (notify->is_wakelock)
wake_unlock(&u_notify->wlock);
}
break;
case NOTIFY_EVENT_HMT:
case NOTIFY_EVENT_HOST:
case NOTIFY_EVENT_GAMEPAD:
if (notify->unsupport_host) {
pr_err("This model doesn't support usb host\n");
goto err2;
}
u_notify->diable_v_drive = 0;
if (enable) {
if (prev_c_type == NOTIFY_EVENT_HMT ||
prev_c_type == NOTIFY_EVENT_HOST ||
prev_c_type == NOTIFY_EVENT_GAMEPAD) {
pr_err("now host mode, skip this command\n");
goto err2;
}
u_notify->ndev.mode = NOTIFY_HOST_MODE;
if (notify->is_wakelock)
wake_lock(&u_notify->wlock);
host_state_notify(&u_notify->ndev, NOTIFY_HOST_ADD);
if (gpio_is_valid(notify->redriver_en_gpio))
gpio_direction_output
(notify->redriver_en_gpio, 1);
if (notify->auto_drive_vbus) {
u_notify->oc_noti = 1;
if (notify->vbus_drive)
notify->vbus_drive(1);
}
if (notify->set_host)
notify->set_host(true);
} else {
u_notify->ndev.mode = NOTIFY_NONE_MODE;
if (notify->set_host)
notify->set_host(false);
if (notify->auto_drive_vbus) {
u_notify->oc_noti = 0;
if (notify->vbus_drive)
notify->vbus_drive(0);
}
if (gpio_is_valid(notify->redriver_en_gpio))
gpio_direction_output
(notify->redriver_en_gpio, 0);
host_state_notify(&u_notify->ndev, NOTIFY_HOST_REMOVE);
if (notify->is_wakelock)
wake_unlock(&u_notify->wlock);
}
break;
case NOTIFY_EVENT_CHARGER:
if (notify->set_charger)
notify->set_charger(enable);
break;
case NOTIFY_EVENT_MMDOCK:
enable_ovc(u_notify, enable);
/* To detect overcurrent, ndev state is initialized */
if (enable)
host_state_notify(&u_notify->ndev,
NOTIFY_HOST_NONE);
case NOTIFY_EVENT_SMARTDOCK_TA:
case NOTIFY_EVENT_AUDIODOCK:
if (notify->unsupport_host) {
pr_err("This model doesn't support usb host\n");
goto err2;
}
u_notify->diable_v_drive = enable;
if (enable) {
u_notify->ndev.mode = NOTIFY_HOST_MODE;
if (notify->is_wakelock)
wake_lock(&u_notify->wlock);
if (notify->set_host)
notify->set_host(true);
} else {
u_notify->ndev.mode = NOTIFY_NONE_MODE;
if (notify->set_host)
notify->set_host(false);
if (notify->is_wakelock)
wake_unlock(&u_notify->wlock);
}
break;
case NOTIFY_EVENT_DRIVE_VBUS:
if (notify->unsupport_host) {
pr_err("This model doesn't support usb host\n");
goto no_save_event;
}
if (u_notify->diable_v_drive) {
pr_info("cable type=%s disable vbus draw\n",
event_string(u_notify->c_type));
goto no_save_event;
}
u_notify->oc_noti = enable;
if (notify->vbus_drive)
notify->vbus_drive((bool)enable);
goto no_save_event;
case NOTIFY_EVENT_ALLDISABLE_NOTIFY:
if (!notify->disable_control) {
pr_err("This model doesn't support disable_control\n");
goto no_save_event;
}
if (enable) {
set_bit(NOTIFY_BLOCK_TYPE_HOST,
&u_notify->udev.disable_state);
set_bit(NOTIFY_BLOCK_TYPE_CLIENT,
&u_notify->udev.disable_state);
} else {
clear_bit(NOTIFY_BLOCK_TYPE_HOST,
&u_notify->udev.disable_state);
clear_bit(NOTIFY_BLOCK_TYPE_CLIENT,
&u_notify->udev.disable_state);
}
goto no_save_event;
case NOTIFY_EVENT_HOSTDISABLE_NOTIFY:
if (!notify->disable_control) {
pr_err("This model doesn't support disable_control\n");
goto no_save_event;
}
if (enable) {
clear_bit(NOTIFY_BLOCK_TYPE_CLIENT,
&u_notify->udev.disable_state);
set_bit(NOTIFY_BLOCK_TYPE_HOST,
&u_notify->udev.disable_state);
}
goto no_save_event;
case NOTIFY_EVENT_CLIENTDISABLE_NOTIFY:
if (!notify->disable_control) {
pr_err("This model doesn't support disable_control\n");
goto no_save_event;
}
if (enable) {
clear_bit(NOTIFY_BLOCK_TYPE_HOST,
&u_notify->udev.disable_state);
set_bit(NOTIFY_BLOCK_TYPE_CLIENT,
&u_notify->udev.disable_state);
}
goto no_save_event;
default:
break;
}
if ((type & NOTIFY_EVENT_NEED_VBUSDRIVE)
&& event != NOTIFY_EVENT_HOST) {
if (enable) {
if (notify->device_check_sec) {
if(prev_c_type != NOTIFY_EVENT_HOST)
u_notify->is_device = 0;
u_notify->check_work_complete = 0;
schedule_delayed_work(&u_notify->check_work,
notify->device_check_sec*HZ);
pr_info("%s check work start\n", __func__);
}
} else {
if (notify->device_check_sec && !u_notify->check_work_complete) {
pr_info("%s check work cancel\n", __func__);
cancel_delayed_work_sync(&u_notify->check_work);
}
u_notify->is_device = 0;
}
}
err2:
update_cable_status(notify, event, virtual, enable, 0);
no_save_event:
pr_info("%s- event=%s, cable=%s\n", __func__,
event_string(event),
event_string(u_notify->c_type));
err:
return;
}
static void extra_notify_state(unsigned long event, int enable)
{
struct otg_notify *notify = NULL;
if (!u_notify) {
pr_err("u_notify is NULL\n");
goto err;
}
pr_info("%s+ event=%s(%lu), enable=%s\n", __func__,
event_string(event), event, enable == 0 ? "off" : "on");
notify = get_otg_notify();
if (!notify) {
pr_err("notify is NULL\n");
goto err1;
}
switch (event) {
case NOTIFY_EVENT_NONE:
break;
case NOTIFY_EVENT_OVERCURRENT:
if (!u_notify->ndev.dev) {
pr_err("ndev is NULL. Maybe usb host is not supported.\n");
break;
}
host_state_notify(&u_notify->ndev,
NOTIFY_HOST_OVERCURRENT);
pr_err("OTG overcurrent!!!!!!\n");
break;
case NOTIFY_EVENT_VBUSPOWER:
if (enable)
u_notify->ndev.booster = NOTIFY_POWER_ON;
else
u_notify->ndev.booster = NOTIFY_POWER_OFF;
break;
case NOTIFY_EVENT_SMSC_OVC:
if (enable)
ovc_start(u_notify);
else
ovc_stop(u_notify);
break;
case NOTIFY_EVENT_SMTD_EXT_CURRENT:
if (u_notify->c_type != NOTIFY_EVENT_SMARTDOCK_TA) {
pr_err("No smart dock!!!!!!\n");
break;
}
if (notify->set_battcall)
notify->set_battcall
(NOTIFY_EVENT_SMTD_EXT_CURRENT, enable);
break;
case NOTIFY_EVENT_MMD_EXT_CURRENT:
if (u_notify->c_type != NOTIFY_EVENT_MMDOCK) {
pr_err("No mmdock!!!!!!\n");
break;
}
if (notify->set_battcall)
notify->set_battcall
(NOTIFY_EVENT_MMD_EXT_CURRENT, enable);
break;
case NOTIFY_EVENT_DEVICE_CONNECT:
u_notify->is_device = 1;
pr_info("%s device connect\n", __func__);
break;
case NOTIFY_EVENT_GAMEPAD_CONNECT:
pr_info("%s gamepad usb device connected\n", __func__);
if (u_notify->c_type == NOTIFY_EVENT_HOST ||
u_notify->c_type == NOTIFY_EVENT_GAMEPAD)
send_external_notify(EXTERNAL_NOTIFY_DEVICE_CONNECT, 1);
break;
default:
break;
}
err1:
pr_info("%s- event=%s(%lu), cable=%s\n", __func__,
event_string(event), event,
event_string(u_notify->c_type));
err:
return;
}
static void otg_notify_work(struct work_struct *data)
{
struct otg_state_work *state_work =
container_of(data, struct otg_state_work, otg_work);
otg_notify_state(state_work->event, state_work->enable);
kfree(state_work);
}
static int otg_notifier_callback(struct notifier_block *nb,
unsigned long event, void *param)
{
struct otg_state_work *state_work;
pr_info("%s event=%s(%lu)\n", __func__,
event_string(event), event);
if (!u_notify) {
pr_err("u_notify is NULL\n");
return NOTIFY_DONE;
}
if (event > VIRT_EVENT(NOTIFY_EVENT_VBUSPOWER)) {
pr_err("%s event is invalid\n", __func__);
return NOTIFY_DONE;
}
state_work = kmalloc(sizeof(struct otg_state_work), GFP_ATOMIC);
if (!state_work)
return notifier_from_errno(-ENOMEM);
INIT_WORK(&state_work->otg_work, otg_notify_work);
state_work->event = event;
state_work->enable = *(int *)param;
queue_work(u_notify->notifier_wq, &state_work->otg_work);
return NOTIFY_OK;
}
static int extra_notifier_callback(struct notifier_block *nb,
unsigned long event, void *param)
{
pr_info("%s event=%s(%lu)\n", __func__,
event_string(event), event);
if (!u_notify) {
pr_err("u_notify is NULL\n");
return NOTIFY_DONE;
}
if (event > VIRT_EVENT(NOTIFY_EVENT_VBUSPOWER)) {
pr_err("%s event is invalid\n", __func__);
return NOTIFY_DONE;
}
extra_notify_state(event, *(int *)param);
return NOTIFY_OK;
}
static int create_usb_notify(void)
{
int ret = 0;
if (u_notify)
goto err;
u_notify = kzalloc(sizeof(struct usb_notify), GFP_KERNEL);
if (!u_notify) {
ret = -ENOMEM;
goto err;
}
u_notify->notifier_wq
= create_singlethread_workqueue("usb_notify");
if (!u_notify->notifier_wq) {
pr_err("%s failed to create work queue\n", __func__);
ret = -ENOMEM;
goto err1;
}
ret = usb_notify_class_init();
if (ret) {
pr_err("unable to do usb_notify_class_init\n");
goto err2;
}
ovc_init(u_notify);
return 0;
err2:
flush_workqueue(u_notify->notifier_wq);
destroy_workqueue(u_notify->notifier_wq);
err1:
kfree(u_notify);
err:
return ret;
}
static void reserve_state_check(struct work_struct *work)
{
struct otg_booting_delay *o_b_d = container_of(work,
struct otg_booting_delay, booting_work.work);
struct usb_notify *u_noti = container_of(o_b_d,
struct usb_notify, b_delay);
int enable = 1;
unsigned long state = 0;
state = u_noti->b_delay.reserve_state;
u_noti->o_notify->booting_delay_sec = 0;
pr_info("%s booting delay finished\n", __func__);
if (u_noti->b_delay.reserve_state != NOTIFY_EVENT_NONE) {
pr_info("%s event=%s(%lu) enable=%d\n", __func__,
event_string(state), state, enable);
if (check_event_type(state) & NOTIFY_EVENT_EXTRA)
blocking_notifier_call_chain
(&u_noti->o_notify->extra_notifier,
state, &enable);
else
atomic_notifier_call_chain
(&u_noti->o_notify->otg_notifier,
state, &enable);
}
}
static void device_connect_check(struct work_struct *work)
{
struct otg_notify *n = get_otg_notify();
pr_info("%s start. is_device=%d\n", __func__, u_notify->is_device);
if (!u_notify->is_device) {
send_external_notify(EXTERNAL_NOTIFY_3S_NODEVICE, 1);
if (n->vbus_drive)
n->vbus_drive(0);
}
u_notify->check_work_complete = 1;
pr_info("%s finished\n", __func__);
}
int set_notify_disable(struct usb_notify_dev *udev, int disable)
{
struct otg_notify *n = udev->o_notify;
if (!n) {
pr_err("%s otg_notify is null\n", __func__);
goto skip;
}
if (!n->disable_control) {
pr_err("%s disable_control is not supported\n", __func__);
goto skip;
}
pr_info("%s disable=%s(%d)\n", __func__,
block_string(disable), disable);
switch (disable) {
case NOTIFY_BLOCK_TYPE_ALL:
if (is_host_cable_enable() ||
is_client_cable_enable()) {
pr_info("%s event=%s(%lu) disable\n", __func__,
event_string(VIRT_EVENT(u_notify->c_type)),
VIRT_EVENT(u_notify->c_type));
if (!n->auto_drive_vbus &&
check_event_type(u_notify->c_type)
& NOTIFY_EVENT_NEED_VBUSDRIVE)
send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 0);
send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 0);
}
send_otg_notify(n, NOTIFY_EVENT_ALLDISABLE_NOTIFY, 1);
break;
case NOTIFY_BLOCK_TYPE_HOST:
if (is_host_cable_enable()) {
pr_info("%s event=%s(%lu) disable\n", __func__,
event_string(VIRT_EVENT(u_notify->c_type)),
VIRT_EVENT(u_notify->c_type));
if (!n->auto_drive_vbus &&
check_event_type(u_notify->c_type)
& NOTIFY_EVENT_NEED_VBUSDRIVE)
send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 0);
send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 0);
}
send_otg_notify(n, NOTIFY_EVENT_HOSTDISABLE_NOTIFY, 1);
if (!is_client_cable_block())
goto skip;
pr_info("%s event=%s(%lu) enable\n", __func__,
event_string(VIRT_EVENT(u_notify->c_type)),
VIRT_EVENT(u_notify->c_type));
send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1);
break;
case NOTIFY_BLOCK_TYPE_CLIENT:
if (is_client_cable_enable()) {
pr_info("%s event=%s(%lu) disable\n", __func__,
event_string(VIRT_EVENT(u_notify->c_type)),
VIRT_EVENT(u_notify->c_type));
send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 0);
}
send_otg_notify(n, NOTIFY_EVENT_CLIENTDISABLE_NOTIFY, 1);
if (!is_host_cable_block())
goto skip;
if (n->unsupport_host)
goto skip;
pr_info("%s event=%s(%lu) enable\n", __func__,
event_string(VIRT_EVENT(u_notify->c_type)),
VIRT_EVENT(u_notify->c_type));
if (!n->auto_drive_vbus &&
check_event_type(u_notify->c_type)
& NOTIFY_EVENT_NEED_VBUSDRIVE)
send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1);
send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1);
break;
case NOTIFY_BLOCK_TYPE_NONE:
send_otg_notify(n, NOTIFY_EVENT_ALLDISABLE_NOTIFY, 0);
if (!is_host_cable_block() && !is_client_cable_block())
goto skip;
if (check_event_type(u_notify->c_type)
& NOTIFY_EVENT_NEED_HOST && n->unsupport_host)
goto skip;
pr_info("%s event=%s(%lu) enable\n", __func__,
event_string(VIRT_EVENT(u_notify->c_type)),
VIRT_EVENT(u_notify->c_type));
if (!n->auto_drive_vbus &&
check_event_type(u_notify->c_type)
& NOTIFY_EVENT_NEED_VBUSDRIVE)
send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1);
send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1);
break;
}
skip:
return 0;
}
void send_otg_notify(struct otg_notify *n,
unsigned long event, int enable)
{
int type = 0;
if (!n) {
pr_err("%s otg_notify is null\n", __func__);
return;
}
pr_info("%s event=%s(%lu) enable=%d\n", __func__,
event_string(event), event, enable);
type = check_event_type(event);
if (type & NOTIFY_EVENT_DELAY) {
if (n->booting_delay_sec) {
if (u_notify) {
u_notify->b_delay.reserve_state =
(enable) ? event : NOTIFY_EVENT_NONE;
pr_info("%s reserve event\n", __func__);
} else
pr_err("%s u_notify is null\n", __func__);
goto end;
}
}
if (type & NOTIFY_EVENT_EXTRA)
blocking_notifier_call_chain
(&n->extra_notifier, event, &enable);
else if (type & NOTIFY_EVENT_STATE)
atomic_notifier_call_chain
(&n->otg_notifier, event, &enable);
else
goto end;
end:
return;
}
EXPORT_SYMBOL(send_otg_notify);
void *get_notify_data(struct otg_notify *n)
{
if (n)
return n->o_data;
else
return NULL;
}
EXPORT_SYMBOL(get_notify_data);
void set_notify_data(struct otg_notify *n, void *data)
{
n->o_data = data;
}
EXPORT_SYMBOL(set_notify_data);
struct otg_notify *get_otg_notify(void)
{
if (!u_notify)
return NULL;
if (!u_notify->o_notify)
return NULL;
return u_notify->o_notify;
}
EXPORT_SYMBOL(get_otg_notify);
struct otg_booster *find_get_booster(void)
{
int ret = 0;
if (!u_notify) {
ret = create_usb_notify();
if (ret) {
pr_err("unable create_usb_notify\n");
goto err;
}
}
if (!u_notify->booster) {
pr_err("error. No matching booster\n");
goto err;
}
return u_notify->booster;
err:
return NULL;
}
EXPORT_SYMBOL(find_get_booster);
int register_booster(struct otg_booster *b)
{
int ret = 0;
if (!u_notify) {
ret = create_usb_notify();
if (ret) {
pr_err("unable create_usb_notify\n");
goto err;
}
}
u_notify->booster = b;
err:
return ret;
}
EXPORT_SYMBOL(register_booster);
int register_ovc_func(int (*check_state)(void *), void *data)
{
int ret = 0;
if (!u_notify) {
ret = create_usb_notify();
if (ret) {
pr_err("%s unable create_usb_notify\n", __func__);
return -EFAULT;
}
}
mutex_lock(&u_notify->ovc_info.ovc_lock);
u_notify->ovc_info.check_state = check_state;
u_notify->ovc_info.data = data;
mutex_unlock(&u_notify->ovc_info.ovc_lock);
pr_info("%s\n", __func__);
return ret;
}
EXPORT_SYMBOL(register_ovc_func);
int get_usb_mode(void)
{
int ret = 0;
if (!u_notify) {
ret = create_usb_notify();
if (ret) {
pr_err("unable create_usb_notify\n");
return -EFAULT;
}
}
pr_info("%s usb mode=%d\n", __func__, u_notify->ndev.mode);
ret = u_notify->ndev.mode;
return ret;
}
EXPORT_SYMBOL(get_usb_mode);
unsigned long get_cable_type(void)
{
unsigned long ret = 0;
if (!u_notify) {
ret = create_usb_notify();
if (ret) {
pr_err("unable create_usb_notify\n");
return -EFAULT;
}
}
pr_info("%s cable type =%s\n", __func__,
event_string(u_notify->c_type));
ret = u_notify->c_type;
return ret;
}
EXPORT_SYMBOL(get_cable_type);
int set_otg_notify(struct otg_notify *n)
{
int ret = 0;
if (!u_notify) {
ret = create_usb_notify();
if (ret) {
pr_err("unable create_usb_notify\n");
goto err;
}
}
if (u_notify->o_notify && n) {
pr_err("error : already set o_notify\n");
goto err;
}
pr_info("registered otg_notify +\n");
if (!n) {
pr_err("otg notify structure is null\n");
ret = -EFAULT;
goto err1;
}
u_notify->o_notify = n;
ATOMIC_INIT_NOTIFIER_HEAD(&u_notify->o_notify->otg_notifier);
u_notify->otg_nb.notifier_call = otg_notifier_callback;
ret = atomic_notifier_chain_register(&u_notify->o_notify->otg_notifier,
&u_notify->otg_nb);
if (ret < 0) {
pr_err("atomic_notifier_chain_register failed\n");
goto err1;
}
BLOCKING_INIT_NOTIFIER_HEAD(&u_notify->o_notify->extra_notifier);
u_notify->extra_nb.notifier_call = extra_notifier_callback;
ret = blocking_notifier_chain_register
(&u_notify->o_notify->extra_notifier, &u_notify->extra_nb);
if (ret < 0) {
pr_err("blocking_notifier_chain_register failed\n");
goto err2;
}
if (!n->unsupport_host) {
u_notify->ndev.name = "usb_otg";
u_notify->ndev.set_booster = n->vbus_drive;
ret = host_notify_dev_register(&u_notify->ndev);
if (ret < 0) {
pr_err("host_notify_dev_register is failed\n");
goto err3;
}
if (!n->vbus_drive) {
pr_err("vbus_drive is null\n");
goto err4;
}
}
u_notify->udev.name = "usb_control";
u_notify->udev.set_disable = set_notify_disable;
u_notify->udev.o_notify = n;
ret = usb_notify_dev_register(&u_notify->udev);
if (ret < 0) {
pr_err("usb_notify_dev_register is failed\n");
goto err4;
}
if (gpio_is_valid(n->vbus_detect_gpio) ||
gpio_is_valid(n->redriver_en_gpio)) {
ret = register_gpios(n);
if (ret < 0) {
pr_err("register_gpios is failed\n");
goto err5;
}
}
if (n->is_wakelock)
wake_lock_init(&u_notify->wlock,
WAKE_LOCK_SUSPEND, "usb_notify");
if (n->booting_delay_sec) {
INIT_DELAYED_WORK(&u_notify->b_delay.booting_work,
reserve_state_check);
schedule_delayed_work(&u_notify->b_delay.booting_work,
n->booting_delay_sec*HZ);
}
if (n->device_check_sec)
INIT_DELAYED_WORK(&u_notify->check_work,
device_connect_check);
register_usbdev_notify();
pr_info("registered otg_notify -\n");
return 0;
err5:
usb_notify_dev_unregister(&u_notify->udev);
err4:
if (!n->unsupport_host)
host_notify_dev_unregister(&u_notify->ndev);
err3:
blocking_notifier_chain_unregister(&u_notify->o_notify->extra_notifier,
&u_notify->extra_nb);
err2:
atomic_notifier_chain_unregister(&u_notify->o_notify->otg_notifier,
&u_notify->otg_nb);
err1:
u_notify->o_notify = NULL;
err:
return ret;
}
EXPORT_SYMBOL(set_otg_notify);
void put_otg_notify(struct otg_notify *n)
{
if (n->booting_delay_sec)
cancel_delayed_work_sync(&u_notify->b_delay.booting_work);
if (n->is_wakelock)
wake_lock_destroy(&u_notify->wlock);
if (gpio_is_valid(n->vbus_detect_gpio))
free_irq(gpio_to_irq(n->vbus_detect_gpio), NULL);
host_notify_dev_unregister(&u_notify->ndev);
blocking_notifier_chain_unregister(&u_notify->o_notify->extra_notifier,
&u_notify->extra_nb);
atomic_notifier_chain_unregister(&u_notify->o_notify->otg_notifier,
&u_notify->otg_nb);
u_notify->o_notify = NULL;
}
EXPORT_SYMBOL(put_otg_notify);
static int __init usb_notify_init(void)
{
return create_usb_notify();
}
static void __exit usb_notify_exit(void)
{
if (!u_notify)
return;
usb_notify_class_exit();
flush_workqueue(u_notify->notifier_wq);
destroy_workqueue(u_notify->notifier_wq);
kfree(u_notify);
}
module_init(usb_notify_init);
module_exit(usb_notify_exit);
MODULE_AUTHOR("Samsung USB Team");
MODULE_DESCRIPTION("USB Notify Layer");
MODULE_LICENSE("GPL");