6db4831e98
Android 14
3018 lines
72 KiB
C
3018 lines
72 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2014-2022 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.
|
|
*/
|
|
|
|
/* usb notify layer v3.7 */
|
|
#define NOTIFY_VERSION "3.7"
|
|
|
|
#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/kthread.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/usb_notify.h>
|
|
#include <sound/core.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/usb/audio.h>
|
|
#include <linux/ratelimit.h>
|
|
#include "host_notify_class.h"
|
|
#include "dock_notify.h"
|
|
#include "usb_notify_sysfs.h"
|
|
|
|
#define DEFAULT_OVC_POLL_SEC 3
|
|
|
|
struct ovc {
|
|
struct otg_notify *o_notify;
|
|
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 *data);
|
|
};
|
|
|
|
struct vbus_gpio {
|
|
spinlock_t lock;
|
|
int gpio_status;
|
|
};
|
|
|
|
struct otg_state_work {
|
|
struct otg_notify *o_notify;
|
|
struct work_struct otg_work;
|
|
unsigned long event;
|
|
int enable;
|
|
};
|
|
|
|
struct otg_booting_delay {
|
|
struct delayed_work booting_work;
|
|
unsigned long reserve_state;
|
|
};
|
|
|
|
struct typec_info {
|
|
int data_role;
|
|
int power_role;
|
|
int pd;
|
|
int doing_drswap;
|
|
int doing_prswap;
|
|
};
|
|
|
|
struct usb_gadget_info {
|
|
int bus_state;
|
|
int usb_cable_connect;
|
|
};
|
|
|
|
struct usb_notify {
|
|
struct otg_notify *o_notify;
|
|
struct atomic_notifier_head otg_notifier;
|
|
struct blocking_notifier_head extra_notifier;
|
|
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 wakeup_source ws;
|
|
struct otg_booster *booster;
|
|
struct ovc ovc_info;
|
|
struct otg_booting_delay b_delay;
|
|
struct delayed_work check_work;
|
|
struct typec_info typec_status;
|
|
struct usb_gadget_info gadget_status;
|
|
struct mutex state_lock;
|
|
spinlock_t event_spin_lock;
|
|
int is_device;
|
|
int cond_max_speed;
|
|
int check_work_complete;
|
|
int oc_noti;
|
|
int disable_v_drive;
|
|
unsigned long c_type;
|
|
int c_status;
|
|
int sec_whitelist_enable;
|
|
int reserve_vbus_booster;
|
|
int disable_state;
|
|
#if defined(CONFIG_USB_HW_PARAM)
|
|
unsigned long long hw_param[USB_CCIC_HW_PARAM_MAX];
|
|
#endif
|
|
};
|
|
|
|
struct usb_notify_core {
|
|
struct otg_notify *o_notify;
|
|
};
|
|
|
|
static struct usb_notify_core *u_notify_core;
|
|
|
|
/*
|
|
* 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_HMD_EXT_CURRENT:
|
|
case NOTIFY_EVENT_DEVICE_CONNECT:
|
|
case NOTIFY_EVENT_GAMEPAD_CONNECT:
|
|
case NOTIFY_EVENT_LANHUB_CONNECT:
|
|
case NOTIFY_EVENT_POWER_SOURCE:
|
|
case NOTIFY_EVENT_PD_CONTRACT:
|
|
case NOTIFY_EVENT_VBUS_RESET:
|
|
case NOTIFY_EVENT_RESERVE_BOOSTER:
|
|
case NOTIFY_EVENT_USB_CABLE:
|
|
case NOTIFY_EVENT_USBD_SUSPENDED:
|
|
case NOTIFY_EVENT_USBD_UNCONFIGURED:
|
|
case NOTIFY_EVENT_USBD_CONFIGURED:
|
|
case NOTIFY_EVENT_DR_SWAP:
|
|
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_POGO:
|
|
ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_DELAY
|
|
| NOTIFY_EVENT_NEED_HOST);
|
|
break;
|
|
case NOTIFY_EVENT_HOST_RELOAD:
|
|
ret |= (NOTIFY_EVENT_STATE | NOTIFY_EVENT_NEED_HOST
|
|
| NOTIFY_EVENT_NOSAVE);
|
|
break;
|
|
case NOTIFY_EVENT_ALL_DISABLE:
|
|
case NOTIFY_EVENT_HOST_DISABLE:
|
|
case NOTIFY_EVENT_CLIENT_DISABLE:
|
|
case NOTIFY_EVENT_MDM_ON_OFF:
|
|
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_DELAY | NOTIFY_EVENT_NEED_HOST);
|
|
case NOTIFY_EVENT_CHARGER:
|
|
case NOTIFY_EVENT_NONE:
|
|
default:
|
|
ret |= NOTIFY_EVENT_STATE;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int check_same_event_type(enum otg_notify_events event1,
|
|
enum otg_notify_events event2)
|
|
{
|
|
return (check_event_type(event1)
|
|
== check_event_type(event2));
|
|
}
|
|
|
|
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_POGO:
|
|
return virt ? "pogo(virtual)" : "pogo";
|
|
case NOTIFY_EVENT_HOST_RELOAD:
|
|
return virt ? "host_reload(virtual)" : "host_reload";
|
|
case NOTIFY_EVENT_DRIVE_VBUS:
|
|
return "drive_vbus";
|
|
case NOTIFY_EVENT_ALL_DISABLE:
|
|
return "disable_all_notify";
|
|
case NOTIFY_EVENT_HOST_DISABLE:
|
|
return "disable_host_notify";
|
|
case NOTIFY_EVENT_CLIENT_DISABLE:
|
|
return "disable_client_notify";
|
|
case NOTIFY_EVENT_MDM_ON_OFF:
|
|
return "mdm control_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_HMD_EXT_CURRENT:
|
|
return "hmd_ext_current";
|
|
case NOTIFY_EVENT_DEVICE_CONNECT:
|
|
return "device_connect";
|
|
case NOTIFY_EVENT_GAMEPAD_CONNECT:
|
|
return "gamepad_connect";
|
|
case NOTIFY_EVENT_LANHUB_CONNECT:
|
|
return "lanhub_connect";
|
|
case NOTIFY_EVENT_POWER_SOURCE:
|
|
return "power_role_source";
|
|
case NOTIFY_EVENT_PD_CONTRACT:
|
|
return "pd_contract";
|
|
case NOTIFY_EVENT_VBUS_RESET:
|
|
return "host_accessory_restart";
|
|
case NOTIFY_EVENT_RESERVE_BOOSTER:
|
|
return "reserve_booster";
|
|
case NOTIFY_EVENT_USB_CABLE:
|
|
return "usb_cable";
|
|
case NOTIFY_EVENT_USBD_SUSPENDED:
|
|
return "usb_d_suspended";
|
|
case NOTIFY_EVENT_USBD_UNCONFIGURED:
|
|
return "usb_d_unconfigured";
|
|
case NOTIFY_EVENT_USBD_CONFIGURED:
|
|
return "usb_d_configured";
|
|
case NOTIFY_EVENT_DR_SWAP:
|
|
return "dr_swap";
|
|
default:
|
|
return "undefined";
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(event_string);
|
|
|
|
const char *status_string(enum otg_notify_event_status status)
|
|
{
|
|
switch (status) {
|
|
case NOTIFY_EVENT_DISABLED:
|
|
return "disabled";
|
|
case NOTIFY_EVENT_DISABLING:
|
|
return "disabling";
|
|
case NOTIFY_EVENT_ENABLED:
|
|
return "enabled";
|
|
case NOTIFY_EVENT_ENABLING:
|
|
return "enabling";
|
|
case NOTIFY_EVENT_BLOCKED:
|
|
return "blocked";
|
|
case NOTIFY_EVENT_BLOCKING:
|
|
return "blocking";
|
|
default:
|
|
return "undefined";
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(status_string);
|
|
|
|
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 int create_usb_notify(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (u_notify_core)
|
|
goto err;
|
|
|
|
u_notify_core = kzalloc(sizeof(struct usb_notify_core), GFP_KERNEL);
|
|
if (!u_notify_core) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
ret = notify_class_init();
|
|
if (ret) {
|
|
pr_err("unable to do host_notify_class_init\n");
|
|
goto err1;
|
|
}
|
|
|
|
ret = usb_notify_class_init();
|
|
if (ret) {
|
|
pr_err("unable to do usb_notify_class_init\n");
|
|
goto err2;
|
|
}
|
|
external_notifier_init();
|
|
|
|
return 0;
|
|
err2:
|
|
notify_class_exit();
|
|
err1:
|
|
kfree(u_notify_core);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static bool is_host_cable_block(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
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(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
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(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
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(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
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(struct otg_notify *n, unsigned long event)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
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 notify_event_lock_init(struct usb_notify *u_noti)
|
|
{
|
|
spin_lock_init(&u_noti->event_spin_lock);
|
|
}
|
|
|
|
static void notify_event_lock(struct usb_notify *u_noti, int type)
|
|
{
|
|
if (type & NOTIFY_EVENT_STATE)
|
|
spin_lock(&u_noti->event_spin_lock);
|
|
}
|
|
|
|
static void notify_event_unlock(struct usb_notify *u_noti, int type)
|
|
{
|
|
if (type & NOTIFY_EVENT_STATE)
|
|
spin_unlock(&u_noti->event_spin_lock);
|
|
}
|
|
|
|
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 = (struct ovc *)data;
|
|
struct otg_notify *o_notify = ovcinfo->o_notify;
|
|
struct usb_notify *u_notify = (struct usb_notify *)(o_notify->u_notify);
|
|
int state = 0, event = 0;
|
|
|
|
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(&ovcinfo->ovc_lock);
|
|
if (ovcinfo->check_state
|
|
&& ovcinfo->data
|
|
&& ovcinfo->can_ovc) {
|
|
|
|
state = ovcinfo->check_state(ovcinfo->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);
|
|
event
|
|
= NOTIFY_EXTRA_USBHOST_OVERCURRENT;
|
|
store_usblog_notify(NOTIFY_EXTRA,
|
|
(void *)&event, NULL);
|
|
} 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(&ovcinfo->ovc_lock);
|
|
if (!ovcinfo->can_ovc)
|
|
ovcinfo->thread_remove = 1;
|
|
}
|
|
|
|
pr_info("%s exit\n", __func__);
|
|
complete_and_exit(&ovcinfo->scanning_done, 0);
|
|
return 0;
|
|
}
|
|
|
|
void ovc_start(struct usb_notify *u_noti)
|
|
{
|
|
struct otg_notify *o_notify = u_noti->o_notify;
|
|
|
|
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, "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;
|
|
u_noti->ovc_info.o_notify = u_noti->o_notify;
|
|
pr_info("%s\n", __func__);
|
|
}
|
|
|
|
static irqreturn_t vbus_irq_isr(int irq, void *data)
|
|
{
|
|
struct otg_notify *notify = (struct otg_notify *)(data);
|
|
struct usb_notify *u_notify = (struct usb_notify *)(notify->u_notify);
|
|
unsigned long flags = 0;
|
|
int gpio_value = 0;
|
|
irqreturn_t ret = IRQ_NONE;
|
|
|
|
spin_lock_irqsave(&u_notify->v_gpio.lock, flags);
|
|
gpio_value = gpio_get_value(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 = (struct otg_notify *)(data);
|
|
struct usb_notify *u_notify = (struct usb_notify *)(notify->u_notify);
|
|
unsigned long flags = 0;
|
|
int gpio_value = 0, event = 0;
|
|
|
|
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);
|
|
event = NOTIFY_EXTRA_USBHOST_OVERCURRENT;
|
|
store_usblog_notify(NOTIFY_EXTRA,
|
|
(void *)&event, NULL);
|
|
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)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int ret = 0;
|
|
int vbus_irq = 0;
|
|
int vbus_gpio = -1;
|
|
int redriver_gpio = -1;
|
|
|
|
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",
|
|
n);
|
|
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 *n, unsigned long event,
|
|
int type, int enable)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int ret = 0;
|
|
|
|
switch (event) {
|
|
case NOTIFY_EVENT_NONE:
|
|
case NOTIFY_EVENT_CHARGER:
|
|
break;
|
|
case NOTIFY_EVENT_SMARTDOCK_USB:
|
|
case NOTIFY_EVENT_VBUS:
|
|
if (enable)
|
|
if (n->set_chg_current)
|
|
n->set_chg_current(NOTIFY_USB_CONFIGURED);
|
|
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:
|
|
case NOTIFY_EVENT_POGO:
|
|
if (n->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 *n, unsigned long event,
|
|
int virtual, int enable, int start)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
if (enable) {
|
|
u_notify->c_type = event;
|
|
if (check_block_event(n, event) ||
|
|
(check_event_type(u_notify->c_type)
|
|
& NOTIFY_EVENT_NEED_HOST &&
|
|
n->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 reserve_state_check(struct work_struct *work)
|
|
{
|
|
struct otg_booting_delay *o_b_d =
|
|
container_of(to_delayed_work(work),
|
|
struct otg_booting_delay, booting_work);
|
|
struct usb_notify *u_noti = container_of(o_b_d,
|
|
struct usb_notify, b_delay);
|
|
int enable = 1, type = 0;
|
|
unsigned long state = 0;
|
|
|
|
if (u_noti->o_notify->booting_delay_sync_usb) {
|
|
pr_info("%s wait dwc3 probe done\n", __func__);
|
|
return;
|
|
}
|
|
|
|
notify_event_lock(u_noti, NOTIFY_EVENT_STATE);
|
|
|
|
u_noti->o_notify->booting_delay_sec = 0;
|
|
|
|
state = u_noti->b_delay.reserve_state;
|
|
type = check_event_type(state);
|
|
|
|
u_noti->b_delay.reserve_state = NOTIFY_EVENT_NONE;
|
|
pr_info("%s booting delay finished\n", __func__);
|
|
|
|
if (state != NOTIFY_EVENT_NONE) {
|
|
pr_info("%s event=%s(%lu) enable=%d\n", __func__,
|
|
event_string(state), state, enable);
|
|
|
|
if (type & NOTIFY_EVENT_STATE)
|
|
atomic_notifier_call_chain
|
|
(&u_noti->otg_notifier,
|
|
state, &enable);
|
|
else
|
|
;
|
|
}
|
|
|
|
notify_event_unlock(u_noti, NOTIFY_EVENT_STATE);
|
|
|
|
send_external_notify(EXTERNAL_NOTIFY_POSSIBLE_USB, 1);
|
|
}
|
|
|
|
static void device_connect_check(struct work_struct *work)
|
|
{
|
|
struct usb_notify *u_notify = container_of(to_delayed_work(work),
|
|
struct usb_notify, check_work);
|
|
|
|
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 (u_notify->o_notify->vbus_drive)
|
|
u_notify->o_notify->vbus_drive(0);
|
|
u_notify->typec_status.power_role = HNOTIFY_SINK;
|
|
}
|
|
u_notify->check_work_complete = 1;
|
|
pr_info("%s finished\n", __func__);
|
|
}
|
|
|
|
static int set_notify_disable(struct usb_notify_dev *udev, int disable)
|
|
{
|
|
struct otg_notify *n = udev->o_notify;
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
unsigned long usb_notify;
|
|
int usb_notify_state;
|
|
|
|
if (!n->disable_control) {
|
|
pr_err("%s disable_control is not supported\n", __func__);
|
|
goto skip;
|
|
}
|
|
|
|
pr_info("%s prev=%s(%d) => disable=%s(%d)\n", __func__,
|
|
block_string(u_notify->disable_state), u_notify->disable_state,
|
|
block_string(disable), disable);
|
|
|
|
if (u_notify->disable_state == disable) {
|
|
pr_err("%s duplicated state\n", __func__);
|
|
goto skip;
|
|
}
|
|
|
|
u_notify->disable_state = disable;
|
|
|
|
switch (disable) {
|
|
case NOTIFY_BLOCK_TYPE_ALL:
|
|
send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_EARLY, 1);
|
|
if (is_host_cable_enable(n) ||
|
|
is_client_cable_enable(n)) {
|
|
pr_info("%s event=%s(%lu) disable\n", __func__,
|
|
event_string(VIRT_EVENT(u_notify->c_type)),
|
|
VIRT_EVENT(u_notify->c_type));
|
|
if (is_host_cable_enable(n)) {
|
|
if (!n->auto_drive_vbus &&
|
|
(u_notify->typec_status.power_role
|
|
== HNOTIFY_SOURCE) &&
|
|
check_event_type(u_notify->c_type)
|
|
& NOTIFY_EVENT_NEED_VBUSDRIVE)
|
|
send_otg_notify(n,
|
|
NOTIFY_EVENT_DRIVE_VBUS, 0);
|
|
} else {
|
|
if (u_notify->typec_status.power_role
|
|
== HNOTIFY_SOURCE)
|
|
send_otg_notify(n,
|
|
NOTIFY_EVENT_DRIVE_VBUS, 0);
|
|
}
|
|
send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 0);
|
|
} else {
|
|
if (u_notify->typec_status.power_role
|
|
== HNOTIFY_SOURCE)
|
|
send_otg_notify(n,
|
|
NOTIFY_EVENT_DRIVE_VBUS, 0);
|
|
}
|
|
send_otg_notify(n, NOTIFY_EVENT_ALL_DISABLE, 1);
|
|
|
|
usb_notify = NOTIFY_EVENT_ALL_DISABLE;
|
|
usb_notify_state = NOTIFY_EVENT_BLOCKED;
|
|
store_usblog_notify(NOTIFY_EVENT,
|
|
(void *)&usb_notify, (void *)&usb_notify_state);
|
|
break;
|
|
case NOTIFY_BLOCK_TYPE_HOST:
|
|
send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_EARLY, 1);
|
|
if (is_host_cable_enable(n)) {
|
|
|
|
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 &&
|
|
(u_notify->typec_status.power_role
|
|
== HNOTIFY_SOURCE) &&
|
|
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);
|
|
} else {
|
|
if (u_notify->typec_status.power_role
|
|
== HNOTIFY_SOURCE)
|
|
send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 0);
|
|
}
|
|
send_otg_notify(n, NOTIFY_EVENT_HOST_DISABLE, 1);
|
|
|
|
usb_notify = NOTIFY_EVENT_HOST_DISABLE;
|
|
usb_notify_state = NOTIFY_EVENT_BLOCKED;
|
|
store_usblog_notify(NOTIFY_EVENT,
|
|
(void *)&usb_notify, (void *)&usb_notify_state);
|
|
|
|
if (!is_client_cable_block(n))
|
|
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(n)) {
|
|
|
|
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_CLIENT_DISABLE, 1);
|
|
|
|
usb_notify = NOTIFY_EVENT_CLIENT_DISABLE;
|
|
usb_notify_state = NOTIFY_EVENT_BLOCKED;
|
|
store_usblog_notify(NOTIFY_EVENT,
|
|
(void *)&usb_notify, (void *)&usb_notify_state);
|
|
|
|
if (!is_host_cable_block(n))
|
|
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 &&
|
|
(u_notify->typec_status.power_role
|
|
== HNOTIFY_SOURCE) &&
|
|
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_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_EARLY, 0);
|
|
send_otg_notify(n, NOTIFY_EVENT_ALL_DISABLE, 0);
|
|
|
|
usb_notify = NOTIFY_EVENT_ALL_DISABLE;
|
|
usb_notify_state = NOTIFY_EVENT_DISABLED;
|
|
store_usblog_notify(NOTIFY_EVENT,
|
|
(void *)&usb_notify, (void *)&usb_notify_state);
|
|
|
|
if (!is_host_cable_block(n) && !is_client_cable_block(n)) {
|
|
if (u_notify->typec_status.power_role
|
|
== HNOTIFY_SOURCE)
|
|
send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1);
|
|
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 (is_host_cable_block(n)) {
|
|
if (!n->auto_drive_vbus &&
|
|
(u_notify->typec_status.power_role
|
|
== HNOTIFY_SOURCE) &&
|
|
check_event_type(u_notify->c_type)
|
|
& NOTIFY_EVENT_NEED_VBUSDRIVE)
|
|
send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1);
|
|
} else {
|
|
if (u_notify->typec_status.power_role
|
|
== HNOTIFY_SOURCE)
|
|
send_otg_notify(n, NOTIFY_EVENT_DRIVE_VBUS, 1);
|
|
}
|
|
send_otg_notify(n, VIRT_EVENT(u_notify->c_type), 1);
|
|
break;
|
|
}
|
|
skip:
|
|
return 0;
|
|
}
|
|
|
|
static void set_notify_mdm(struct usb_notify_dev *udev, int disable)
|
|
{
|
|
struct otg_notify *n = udev->o_notify;
|
|
|
|
switch (disable) {
|
|
case NOTIFY_MDM_TYPE_ON:
|
|
send_otg_notify(n, NOTIFY_EVENT_MDM_ON_OFF, 1);
|
|
if (is_host_cable_enable(n)) {
|
|
pr_info("%s event=%s(%d)\n", __func__,
|
|
event_string(
|
|
VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD)),
|
|
VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD));
|
|
send_otg_notify(n,
|
|
VIRT_EVENT(NOTIFY_EVENT_HOST_RELOAD), 1);
|
|
}
|
|
break;
|
|
case NOTIFY_MDM_TYPE_OFF:
|
|
send_otg_notify(n, NOTIFY_EVENT_MDM_ON_OFF, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int control_usb_maximum_speed(struct usb_notify_dev *udev, int speed)
|
|
{
|
|
struct otg_notify *n = udev->o_notify;
|
|
int ret = 0;
|
|
|
|
if (n->usb_maximum_speed)
|
|
ret = n->usb_maximum_speed(speed);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void send_usb_mdm_uevent(void)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
char *envp[4];
|
|
char *type = {"TYPE=usbmdm"};
|
|
char *state = {"STATE=ADD"};
|
|
char *words = {"WORDS=no_whitelist"};
|
|
int index = 0;
|
|
|
|
if (!o_notify) {
|
|
pr_err("%s o_notify is null\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
envp[index++] = type;
|
|
envp[index++] = state;
|
|
|
|
envp[index++] = words;
|
|
|
|
envp[index++] = NULL;
|
|
|
|
if (send_usb_notify_uevent(o_notify, envp)) {
|
|
pr_err("%s error\n", __func__);
|
|
goto err;
|
|
}
|
|
pr_info("%s\n", __func__);
|
|
err:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(send_usb_mdm_uevent);
|
|
|
|
void send_usb_certi_uevent(int usb_certi)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
char *envp[4];
|
|
char *type = {"TYPE=usbcerti"};
|
|
char *state = {"STATE=ADD"};
|
|
char *words;
|
|
int index = 0;
|
|
static DEFINE_RATELIMIT_STATE(rs_warm_reset, 5 * HZ, 1);
|
|
|
|
if (!o_notify) {
|
|
pr_err("%s o_notify is null\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
envp[index++] = type;
|
|
envp[index++] = state;
|
|
|
|
switch (usb_certi) {
|
|
case USB_CERTI_UNSUPPORT_ACCESSORY:
|
|
words = "WORDS=unsupport_accessory";
|
|
break;
|
|
case USB_CERTI_NO_RESPONSE:
|
|
words = "WORDS=no_response";
|
|
break;
|
|
case USB_CERTI_HUB_DEPTH_EXCEED:
|
|
words = "WORDS=hub_depth_exceed";
|
|
break;
|
|
case USB_CERTI_HUB_POWER_EXCEED:
|
|
words = "WORDS=hub_power_exceed";
|
|
break;
|
|
case USB_CERTI_HOST_RESOURCE_EXCEED:
|
|
words = "WORDS=host_resource_exceed";
|
|
break;
|
|
case USB_CERTI_WARM_RESET:
|
|
if (!__ratelimit(&rs_warm_reset))
|
|
goto err;
|
|
words = "WORDS=no_response";
|
|
break;
|
|
default:
|
|
pr_err("%s invalid input\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
envp[index++] = words;
|
|
|
|
envp[index++] = NULL;
|
|
|
|
if (send_usb_notify_uevent(o_notify, envp)) {
|
|
pr_err("%s error\n", __func__);
|
|
goto err;
|
|
}
|
|
pr_info("%s: %s(%d)\n", __func__, words, usb_certi);
|
|
err:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(send_usb_certi_uevent);
|
|
|
|
void send_usb_err_uevent(int err_type, int mode)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
char *envp[4];
|
|
char *type = {"TYPE=usberr"};
|
|
char *state;
|
|
char *words;
|
|
int index = 0;
|
|
|
|
if (!o_notify) {
|
|
pr_err("%s o_notify is null\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
if (mode)
|
|
state = "STATE=ADD";
|
|
else
|
|
state = "STATE=REMOVE";
|
|
|
|
envp[index++] = type;
|
|
envp[index++] = state;
|
|
|
|
switch (err_type) {
|
|
case USB_ERR_ABNORMAL_RESET:
|
|
words = "WORDS=abnormal_reset";
|
|
#if defined(CONFIG_USB_HW_PARAM)
|
|
if (mode)
|
|
inc_hw_param(o_notify,
|
|
USB_CLIENT_ANDROID_AUTO_RESET_POPUP_COUNT);
|
|
#endif
|
|
break;
|
|
default:
|
|
pr_err("%s invalid input\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
envp[index++] = words;
|
|
envp[index++] = NULL;
|
|
|
|
if (send_usb_notify_uevent(o_notify, envp)) {
|
|
pr_err("%s error\n", __func__);
|
|
goto err;
|
|
}
|
|
pr_info("%s: %s\n", __func__, words);
|
|
err:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(send_usb_err_uevent);
|
|
|
|
void send_usb_itracker_uevent(int err_type)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
char *envp[4];
|
|
char *type = {"TYPE=usbtracker"};
|
|
char *state = {"STATE=ADD"};
|
|
char *words;
|
|
int index = 0;
|
|
|
|
if (!o_notify) {
|
|
pr_err("%s o_notify is null\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
envp[index++] = type;
|
|
envp[index++] = state;
|
|
|
|
switch (err_type) {
|
|
case NOTIFY_USB_CC_REPEAT:
|
|
words = "WORDS=repeat_ccirq";
|
|
break;
|
|
default:
|
|
pr_err("%s invalid input\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
envp[index++] = words;
|
|
envp[index++] = NULL;
|
|
|
|
if (send_usb_notify_uevent(o_notify, envp)) {
|
|
pr_err("%s error\n", __func__);
|
|
goto err;
|
|
}
|
|
pr_info("%s: %s\n", __func__, words);
|
|
err:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(send_usb_itracker_uevent);
|
|
|
|
int get_class_index(int ch9_class_num)
|
|
{
|
|
int internal_class_index;
|
|
|
|
switch (ch9_class_num) {
|
|
case USB_CLASS_PER_INTERFACE:
|
|
internal_class_index = U_CLASS_PER_INTERFACE;
|
|
break;
|
|
case USB_CLASS_AUDIO:
|
|
internal_class_index = U_CLASS_AUDIO;
|
|
break;
|
|
case USB_CLASS_COMM:
|
|
internal_class_index = U_CLASS_COMM;
|
|
break;
|
|
case USB_CLASS_HID:
|
|
internal_class_index = U_CLASS_HID;
|
|
break;
|
|
case USB_CLASS_PHYSICAL:
|
|
internal_class_index = U_CLASS_PHYSICAL;
|
|
break;
|
|
case USB_CLASS_STILL_IMAGE:
|
|
internal_class_index = U_CLASS_STILL_IMAGE;
|
|
break;
|
|
case USB_CLASS_PRINTER:
|
|
internal_class_index = U_CLASS_PRINTER;
|
|
break;
|
|
case USB_CLASS_MASS_STORAGE:
|
|
internal_class_index = U_CLASS_MASS_STORAGE;
|
|
break;
|
|
case USB_CLASS_HUB:
|
|
internal_class_index = U_CLASS_HUB;
|
|
break;
|
|
case USB_CLASS_CDC_DATA:
|
|
internal_class_index = U_CLASS_CDC_DATA;
|
|
break;
|
|
case USB_CLASS_CSCID:
|
|
internal_class_index = U_CLASS_CSCID;
|
|
break;
|
|
case USB_CLASS_CONTENT_SEC:
|
|
internal_class_index = U_CLASS_CONTENT_SEC;
|
|
break;
|
|
case USB_CLASS_VIDEO:
|
|
internal_class_index = U_CLASS_VIDEO;
|
|
break;
|
|
case USB_CLASS_WIRELESS_CONTROLLER:
|
|
internal_class_index = U_CLASS_WIRELESS_CONTROLLER;
|
|
break;
|
|
case USB_CLASS_MISC:
|
|
internal_class_index = U_CLASS_MISC;
|
|
break;
|
|
case USB_CLASS_APP_SPEC:
|
|
internal_class_index = U_CLASS_APP_SPEC;
|
|
break;
|
|
case USB_CLASS_VENDOR_SPEC:
|
|
internal_class_index = U_CLASS_VENDOR_SPEC;
|
|
break;
|
|
default:
|
|
internal_class_index = 0;
|
|
break;
|
|
}
|
|
return internal_class_index;
|
|
}
|
|
|
|
static bool usb_match_any_interface_for_mdm(struct usb_device *udev,
|
|
int *whitelist_array)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < udev->descriptor.bNumConfigurations; ++i) {
|
|
struct usb_host_config *cfg = &udev->config[i];
|
|
unsigned int j;
|
|
|
|
for (j = 0; j < cfg->desc.bNumInterfaces; ++j) {
|
|
struct usb_interface_cache *cache;
|
|
struct usb_host_interface *intf;
|
|
int intf_class;
|
|
|
|
cache = cfg->intf_cache[j];
|
|
if (cache->num_altsetting == 0)
|
|
continue;
|
|
|
|
intf = &cache->altsetting[0];
|
|
intf_class = intf->desc.bInterfaceClass;
|
|
if (!whitelist_array[get_class_index(intf_class)]) {
|
|
pr_info("%s : FAIL,%x interface, it's not in whitelist\n",
|
|
__func__, intf_class);
|
|
store_usblog_notify(NOTIFY_PORT_CLASS_BLOCK,
|
|
(void *)&udev->descriptor.bDeviceClass,
|
|
(void *)&intf_class);
|
|
return false;
|
|
}
|
|
pr_info("%s : SUCCESS,%x interface, it's in whitelist\n",
|
|
__func__, intf_class);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int usb_check_whitelist_for_mdm(struct usb_device *dev)
|
|
{
|
|
int *whitelist_array;
|
|
struct otg_notify *o_notify;
|
|
struct usb_notify *u_notify;
|
|
/* return 1 if the enumeration will be going . */
|
|
/* return 0 if the enumeration will be skept . */
|
|
o_notify = get_otg_notify();
|
|
if (o_notify == NULL) {
|
|
pr_err("o_notify is NULL\n");
|
|
return 1;
|
|
}
|
|
|
|
u_notify = (struct usb_notify *)(o_notify->u_notify);
|
|
if (u_notify == NULL) {
|
|
pr_err("u_notify is NULL\n");
|
|
return 1;
|
|
}
|
|
|
|
if (u_notify->sec_whitelist_enable) {
|
|
whitelist_array = u_notify->udev.whitelist_array_for_mdm;
|
|
if (usb_match_any_interface_for_mdm(dev, whitelist_array)) {
|
|
dev_info(&dev->dev, "the device is matched with whitelist!\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
EXPORT_SYMBOL(usb_check_whitelist_for_mdm);
|
|
|
|
int usb_otg_restart_accessory(struct usb_device *dev)
|
|
{
|
|
struct otg_notify *o_notify;
|
|
int res = 0;
|
|
|
|
pr_info("%s\n", __func__);
|
|
o_notify = get_otg_notify();
|
|
if (o_notify == NULL) {
|
|
pr_err("o_notify is NULL\n");
|
|
return -1;
|
|
}
|
|
|
|
send_otg_notify(o_notify, NOTIFY_EVENT_VBUS_RESET, 0);
|
|
return res;
|
|
}
|
|
EXPORT_SYMBOL(usb_otg_restart_accessory);
|
|
|
|
static void otg_notify_state(struct otg_notify *n,
|
|
unsigned long event, int enable)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int type = 0;
|
|
int virtual = 0;
|
|
int status = 0;
|
|
unsigned long prev_c_type = 0;
|
|
|
|
pr_info("%s+ event=%s(%lu), enable=%s\n", __func__,
|
|
event_string(event), event, enable == 0 ? "off" : "on");
|
|
|
|
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(n, event, virtual, enable, 1);
|
|
|
|
store_usblog_notify(NOTIFY_EVENT,
|
|
(void *)&event, (void *)&u_notify->c_status);
|
|
|
|
} else {
|
|
if (enable)
|
|
status = NOTIFY_EVENT_ENABLING;
|
|
else
|
|
status = NOTIFY_EVENT_DISABLING;
|
|
store_usblog_notify(NOTIFY_EVENT,
|
|
(void *)&event, (void *)&status);
|
|
}
|
|
|
|
if (check_block_event(n, event) &&
|
|
!(type & NOTIFY_EVENT_NOBLOCKING)) {
|
|
pr_err("%s usb notify is blocked. cause %s\n", __func__,
|
|
u_notify->udev.disable_state_cmd);
|
|
if (do_notify_blockstate(n, event, type, enable))
|
|
goto no_save_event;
|
|
else
|
|
goto err;
|
|
}
|
|
|
|
switch (event) {
|
|
case NOTIFY_EVENT_NONE:
|
|
break;
|
|
case NOTIFY_EVENT_SMARTDOCK_USB:
|
|
case NOTIFY_EVENT_VBUS:
|
|
if (enable) {
|
|
mutex_lock(&u_notify->state_lock);
|
|
u_notify->ndev.mode = NOTIFY_PERIPHERAL_MODE;
|
|
u_notify->typec_status.doing_drswap = 0;
|
|
mutex_unlock(&u_notify->state_lock);
|
|
if (n->is_wakelock)
|
|
__pm_stay_awake(&u_notify->ws);
|
|
if (gpio_is_valid(n->redriver_en_gpio))
|
|
gpio_direction_output
|
|
(n->redriver_en_gpio, 1);
|
|
if (n->pre_peri_delay_us)
|
|
usleep_range(n->pre_peri_delay_us * 1000,
|
|
n->pre_peri_delay_us * 1000);
|
|
if (n->set_peripheral)
|
|
n->set_peripheral(true);
|
|
} else {
|
|
mutex_lock(&u_notify->state_lock);
|
|
u_notify->ndev.mode = NOTIFY_NONE_MODE;
|
|
u_notify->gadget_status.bus_state
|
|
= NOTIFY_USB_UNCONFIGURED;
|
|
mutex_unlock(&u_notify->state_lock);
|
|
if (n->set_peripheral)
|
|
n->set_peripheral(false);
|
|
if (gpio_is_valid(n->redriver_en_gpio))
|
|
gpio_direction_output
|
|
(n->redriver_en_gpio, 0);
|
|
if (n->is_wakelock)
|
|
__pm_relax(&u_notify->ws);
|
|
}
|
|
break;
|
|
case NOTIFY_EVENT_LANHUB_TA:
|
|
u_notify->disable_v_drive = enable;
|
|
if (enable)
|
|
u_notify->oc_noti = 0;
|
|
if (n->set_lanhubta)
|
|
n->set_lanhubta(enable);
|
|
break;
|
|
case NOTIFY_EVENT_LANHUB:
|
|
if (n->unsupport_host) {
|
|
pr_err("This model doesn't support usb host\n");
|
|
goto err;
|
|
}
|
|
u_notify->disable_v_drive = enable;
|
|
if (enable) {
|
|
u_notify->oc_noti = 0;
|
|
u_notify->ndev.mode = NOTIFY_HOST_MODE;
|
|
host_state_notify(&u_notify->ndev, NOTIFY_HOST_ADD);
|
|
if (gpio_is_valid(n->redriver_en_gpio))
|
|
gpio_direction_output
|
|
(n->redriver_en_gpio, 1);
|
|
if (n->set_host)
|
|
n->set_host(true);
|
|
} else {
|
|
u_notify->ndev.mode = NOTIFY_NONE_MODE;
|
|
if (n->set_host)
|
|
n->set_host(false);
|
|
if (gpio_is_valid(n->redriver_en_gpio))
|
|
gpio_direction_output
|
|
(n->redriver_en_gpio, 0);
|
|
host_state_notify(&u_notify->ndev, NOTIFY_HOST_REMOVE);
|
|
}
|
|
break;
|
|
case NOTIFY_EVENT_HMT:
|
|
case NOTIFY_EVENT_HOST:
|
|
case NOTIFY_EVENT_GAMEPAD:
|
|
if (n->unsupport_host) {
|
|
pr_err("This model doesn't support usb host\n");
|
|
goto err;
|
|
}
|
|
u_notify->disable_v_drive = 0;
|
|
if (enable) {
|
|
if (check_same_event_type(prev_c_type, event)
|
|
&& !virtual) {
|
|
pr_err("now host mode, skip this command\n");
|
|
goto err;
|
|
}
|
|
mutex_lock(&u_notify->state_lock);
|
|
u_notify->ndev.mode = NOTIFY_HOST_MODE;
|
|
u_notify->typec_status.doing_drswap = 0;
|
|
mutex_unlock(&u_notify->state_lock);
|
|
host_state_notify(&u_notify->ndev, NOTIFY_HOST_ADD);
|
|
if (gpio_is_valid(n->redriver_en_gpio))
|
|
gpio_direction_output
|
|
(n->redriver_en_gpio, 1);
|
|
if (n->auto_drive_vbus == NOTIFY_OP_PRE) {
|
|
u_notify->oc_noti = 1;
|
|
if (n->vbus_drive)
|
|
n->vbus_drive(1);
|
|
u_notify->typec_status.power_role
|
|
= HNOTIFY_SOURCE;
|
|
}
|
|
if (n->set_host)
|
|
n->set_host(true);
|
|
if (n->auto_drive_vbus == NOTIFY_OP_POST) {
|
|
u_notify->oc_noti = 1;
|
|
if (n->vbus_drive)
|
|
n->vbus_drive(1);
|
|
u_notify->typec_status.power_role
|
|
= HNOTIFY_SOURCE;
|
|
}
|
|
if (n->auto_drive_vbus == NOTIFY_OP_OFF) {
|
|
mutex_lock(&u_notify->state_lock);
|
|
if ((u_notify->typec_status.power_role
|
|
== HNOTIFY_SOURCE)
|
|
&& u_notify->reserve_vbus_booster
|
|
&& !is_blocked(n,
|
|
NOTIFY_BLOCK_TYPE_HOST)) {
|
|
pr_info("reserved vbus turn on\n");
|
|
if (n->vbus_drive)
|
|
n->vbus_drive(1);
|
|
u_notify->reserve_vbus_booster = 0;
|
|
}
|
|
mutex_unlock(&u_notify->state_lock);
|
|
}
|
|
} else { /* disable */
|
|
u_notify->ndev.mode = NOTIFY_NONE_MODE;
|
|
if (n->auto_drive_vbus == NOTIFY_OP_POST) {
|
|
u_notify->oc_noti = 0;
|
|
if (n->vbus_drive)
|
|
n->vbus_drive(0);
|
|
u_notify->typec_status.power_role
|
|
= HNOTIFY_SINK;
|
|
}
|
|
if (n->set_host)
|
|
n->set_host(false);
|
|
if (n->auto_drive_vbus == NOTIFY_OP_PRE) {
|
|
u_notify->oc_noti = 0;
|
|
if (n->vbus_drive)
|
|
n->vbus_drive(0);
|
|
u_notify->typec_status.power_role
|
|
= HNOTIFY_SINK;
|
|
}
|
|
if (gpio_is_valid(n->redriver_en_gpio))
|
|
gpio_direction_output
|
|
(n->redriver_en_gpio, 0);
|
|
host_state_notify(&u_notify->ndev, NOTIFY_HOST_REMOVE);
|
|
}
|
|
break;
|
|
case NOTIFY_EVENT_CHARGER:
|
|
if (n->set_charger)
|
|
n->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_POGO:
|
|
case NOTIFY_EVENT_SMARTDOCK_TA:
|
|
case NOTIFY_EVENT_AUDIODOCK:
|
|
if (n->unsupport_host) {
|
|
pr_err("This model doesn't support usb host\n");
|
|
goto err;
|
|
}
|
|
u_notify->disable_v_drive = enable;
|
|
if (enable) {
|
|
u_notify->ndev.mode = NOTIFY_HOST_MODE;
|
|
if (n->set_host)
|
|
n->set_host(true);
|
|
} else {
|
|
u_notify->ndev.mode = NOTIFY_NONE_MODE;
|
|
if (n->set_host)
|
|
n->set_host(false);
|
|
}
|
|
break;
|
|
case NOTIFY_EVENT_HOST_RELOAD:
|
|
if (u_notify->ndev.mode != NOTIFY_HOST_MODE) {
|
|
pr_err("mode is not host. skip host reload.\n");
|
|
goto no_save_event;
|
|
}
|
|
if (n->unsupport_host) {
|
|
pr_err("This model doesn't support usb host\n");
|
|
goto no_save_event;
|
|
}
|
|
if (n->set_host) {
|
|
n->set_host(false);
|
|
msleep(100);
|
|
n->set_host(true);
|
|
}
|
|
goto no_save_event;
|
|
case NOTIFY_EVENT_DRIVE_VBUS:
|
|
if (n->unsupport_host) {
|
|
pr_err("This model doesn't support usb host\n");
|
|
goto no_save_event;
|
|
}
|
|
if (u_notify->disable_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 (n->vbus_drive)
|
|
n->vbus_drive((bool)enable);
|
|
goto no_save_event;
|
|
case NOTIFY_EVENT_ALL_DISABLE:
|
|
if (!n->disable_control) {
|
|
pr_err("This model doesn't support disable_control\n");
|
|
goto no_save_event;
|
|
}
|
|
if (enable) {
|
|
send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_PRE, 1);
|
|
|
|
set_bit(NOTIFY_BLOCK_TYPE_HOST,
|
|
&u_notify->udev.disable_state);
|
|
set_bit(NOTIFY_BLOCK_TYPE_CLIENT,
|
|
&u_notify->udev.disable_state);
|
|
|
|
send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_POST, 1);
|
|
} else {
|
|
send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_PRE, 0);
|
|
|
|
clear_bit(NOTIFY_BLOCK_TYPE_HOST,
|
|
&u_notify->udev.disable_state);
|
|
clear_bit(NOTIFY_BLOCK_TYPE_CLIENT,
|
|
&u_notify->udev.disable_state);
|
|
|
|
send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_POST, 0);
|
|
}
|
|
goto no_save_event;
|
|
case NOTIFY_EVENT_HOST_DISABLE:
|
|
if (!n->disable_control) {
|
|
pr_err("This model doesn't support disable_control\n");
|
|
goto no_save_event;
|
|
}
|
|
if (enable) {
|
|
send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_PRE, 1);
|
|
|
|
clear_bit(NOTIFY_BLOCK_TYPE_CLIENT,
|
|
&u_notify->udev.disable_state);
|
|
set_bit(NOTIFY_BLOCK_TYPE_HOST,
|
|
&u_notify->udev.disable_state);
|
|
|
|
send_external_notify(EXTERNAL_NOTIFY_HOSTBLOCK_POST, 1);
|
|
}
|
|
goto no_save_event;
|
|
case NOTIFY_EVENT_CLIENT_DISABLE:
|
|
if (!n->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;
|
|
case NOTIFY_EVENT_MDM_ON_OFF:
|
|
pr_info("%s : mdm block enable for usb whiltelist = %d\n",
|
|
__func__, enable);
|
|
if (enable) {
|
|
send_external_notify(EXTERNAL_NOTIFY_MDMBLOCK_PRE, 1);
|
|
/*whilte list start*/
|
|
u_notify->sec_whitelist_enable = 1;
|
|
send_external_notify(EXTERNAL_NOTIFY_MDMBLOCK_POST, 1);
|
|
} else {
|
|
/*whilte list end*/
|
|
u_notify->sec_whitelist_enable = 0;
|
|
}
|
|
goto no_save_event;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (((type & NOTIFY_EVENT_NEED_VBUSDRIVE)
|
|
&& event != NOTIFY_EVENT_HOST)
|
|
|| event == NOTIFY_EVENT_POGO) {
|
|
if (enable) {
|
|
if (n->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,
|
|
n->device_check_sec*HZ);
|
|
pr_info("%s check work start\n", __func__);
|
|
}
|
|
} else {
|
|
if (n->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;
|
|
}
|
|
}
|
|
if (type & NOTIFY_EVENT_NEED_HOST) {
|
|
if (!enable) {
|
|
u_notify->is_device = 0;
|
|
pr_info("%s end host\n", __func__);
|
|
send_external_notify(EXTERNAL_NOTIFY_DEVICEADD, 0);
|
|
}
|
|
}
|
|
err:
|
|
update_cable_status(n, 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));
|
|
}
|
|
|
|
static void extra_notify_state(struct otg_notify *n,
|
|
unsigned long event, int enable)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int status = 0;
|
|
|
|
pr_info("%s+ event=%s(%lu), enable=%s\n", __func__,
|
|
event_string(event), event, enable == 0 ? "off" : "on");
|
|
|
|
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");
|
|
status = NOTIFY_EXTRA_USBHOST_OVERCURRENT;
|
|
store_usblog_notify(NOTIFY_EXTRA,
|
|
(void *)&status, NULL);
|
|
break;
|
|
case NOTIFY_EVENT_VBUSPOWER:
|
|
if (enable) {
|
|
u_notify->ndev.booster = NOTIFY_POWER_ON;
|
|
status = NOTIFY_EVENT_ENABLED;
|
|
} else {
|
|
u_notify->ndev.booster = NOTIFY_POWER_OFF;
|
|
status = NOTIFY_EVENT_DISABLED;
|
|
}
|
|
|
|
store_usblog_notify(NOTIFY_EVENT,
|
|
(void *)&event, (void *)&status);
|
|
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 (n->set_battcall)
|
|
n->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 (n->set_battcall)
|
|
n->set_battcall
|
|
(NOTIFY_EVENT_MMD_EXT_CURRENT, enable);
|
|
break;
|
|
case NOTIFY_EVENT_HMD_EXT_CURRENT:
|
|
if (n->set_battcall)
|
|
n->set_battcall
|
|
(NOTIFY_EVENT_HMD_EXT_CURRENT, enable);
|
|
break;
|
|
case NOTIFY_EVENT_DEVICE_CONNECT:
|
|
if (!u_notify->is_device) {
|
|
u_notify->is_device = 1;
|
|
send_external_notify(EXTERNAL_NOTIFY_DEVICEADD, 1);
|
|
}
|
|
break;
|
|
case NOTIFY_EVENT_GAMEPAD_CONNECT:
|
|
if (u_notify->c_type == NOTIFY_EVENT_HOST ||
|
|
u_notify->c_type == NOTIFY_EVENT_GAMEPAD)
|
|
send_external_notify(EXTERNAL_NOTIFY_DEVICE_CONNECT,
|
|
EXTERNAL_NOTIFY_GPAD);
|
|
break;
|
|
case NOTIFY_EVENT_LANHUB_CONNECT:
|
|
if (u_notify->c_type == NOTIFY_EVENT_HOST ||
|
|
u_notify->c_type == NOTIFY_EVENT_LANHUB)
|
|
send_external_notify(EXTERNAL_NOTIFY_DEVICE_CONNECT,
|
|
EXTERNAL_NOTIFY_LANHUB);
|
|
break;
|
|
case NOTIFY_EVENT_POWER_SOURCE:
|
|
if (enable) {
|
|
u_notify->typec_status.power_role = HNOTIFY_SOURCE;
|
|
host_state_notify(&u_notify->ndev,
|
|
NOTIFY_HOST_SOURCE);
|
|
} else {
|
|
u_notify->typec_status.power_role = HNOTIFY_SINK;
|
|
host_state_notify(&u_notify->ndev,
|
|
NOTIFY_HOST_SINK);
|
|
}
|
|
send_external_notify(EXTERNAL_NOTIFY_POWERROLE,
|
|
u_notify->typec_status.power_role);
|
|
break;
|
|
case NOTIFY_EVENT_PD_CONTRACT:
|
|
if (enable)
|
|
u_notify->typec_status.pd = enable;
|
|
else
|
|
u_notify->typec_status.pd = 0;
|
|
break;
|
|
case NOTIFY_EVENT_VBUS_RESET:
|
|
send_external_notify(EXTERNAL_NOTIFY_VBUS_RESET, 0);
|
|
break;
|
|
case NOTIFY_EVENT_RESERVE_BOOSTER:
|
|
mutex_lock(&u_notify->state_lock);
|
|
if (enable)
|
|
u_notify->reserve_vbus_booster = 1;
|
|
else
|
|
u_notify->reserve_vbus_booster = 0;
|
|
mutex_unlock(&u_notify->state_lock);
|
|
break;
|
|
case NOTIFY_EVENT_USB_CABLE:
|
|
mutex_lock(&u_notify->state_lock);
|
|
if (enable)
|
|
u_notify->gadget_status.usb_cable_connect = 1;
|
|
else
|
|
u_notify->gadget_status.usb_cable_connect = 0;
|
|
|
|
if (u_notify->ndev.mode == NOTIFY_PERIPHERAL_MODE
|
|
&& !u_notify->typec_status.doing_drswap) {
|
|
if ((u_notify->gadget_status.bus_state
|
|
== NOTIFY_USB_SUSPENDED)
|
|
&& u_notify->gadget_status.usb_cable_connect) {
|
|
if (n->set_chg_current)
|
|
n->set_chg_current
|
|
(NOTIFY_USB_SUSPENDED);
|
|
}
|
|
}
|
|
mutex_unlock(&u_notify->state_lock);
|
|
break;
|
|
case NOTIFY_EVENT_USBD_SUSPENDED:
|
|
mutex_lock(&u_notify->state_lock);
|
|
if (u_notify->ndev.mode == NOTIFY_PERIPHERAL_MODE
|
|
&& !u_notify->typec_status.doing_drswap) {
|
|
u_notify->gadget_status.bus_state
|
|
= NOTIFY_USB_SUSPENDED;
|
|
if (u_notify->gadget_status.usb_cable_connect) {
|
|
if (n->set_chg_current)
|
|
n->set_chg_current
|
|
(NOTIFY_USB_SUSPENDED);
|
|
}
|
|
}
|
|
mutex_unlock(&u_notify->state_lock);
|
|
break;
|
|
case NOTIFY_EVENT_USBD_UNCONFIGURED:
|
|
mutex_lock(&u_notify->state_lock);
|
|
if (u_notify->ndev.mode == NOTIFY_PERIPHERAL_MODE)
|
|
u_notify->gadget_status.bus_state
|
|
= NOTIFY_USB_UNCONFIGURED;
|
|
mutex_unlock(&u_notify->state_lock);
|
|
break;
|
|
case NOTIFY_EVENT_USBD_CONFIGURED:
|
|
mutex_lock(&u_notify->state_lock);
|
|
if (u_notify->ndev.mode == NOTIFY_PERIPHERAL_MODE)
|
|
u_notify->gadget_status.bus_state
|
|
= NOTIFY_USB_CONFIGURED;
|
|
mutex_unlock(&u_notify->state_lock);
|
|
break;
|
|
case NOTIFY_EVENT_DR_SWAP:
|
|
mutex_lock(&u_notify->state_lock);
|
|
if (enable)
|
|
u_notify->typec_status.doing_drswap = 1;
|
|
else
|
|
u_notify->typec_status.doing_drswap = 0;
|
|
mutex_unlock(&u_notify->state_lock);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
pr_info("%s- event=%s(%lu), cable=%s\n", __func__,
|
|
event_string(event), event,
|
|
event_string(u_notify->c_type));
|
|
}
|
|
|
|
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->o_notify,
|
|
state_work->event, state_work->enable);
|
|
|
|
kfree(state_work);
|
|
}
|
|
|
|
static int otg_notifier_callback(struct notifier_block *nb,
|
|
unsigned long event, void *param)
|
|
{
|
|
struct usb_notify *u_noti = container_of(nb,
|
|
struct usb_notify, otg_nb);
|
|
struct otg_notify *n = NULL;
|
|
struct otg_state_work *state_work = NULL;
|
|
|
|
n = u_noti->o_notify;
|
|
|
|
pr_info("%s event=%s(%lu)\n", __func__,
|
|
event_string(event), event);
|
|
|
|
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->o_notify = n;
|
|
state_work->event = event;
|
|
state_work->enable = *(int *)param;
|
|
queue_work(u_noti->notifier_wq, &state_work->otg_work);
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int extra_notifier_callback(struct notifier_block *nb,
|
|
unsigned long event, void *param)
|
|
{
|
|
struct usb_notify *u_noti = container_of(nb,
|
|
struct usb_notify, extra_nb);
|
|
struct otg_notify *n = NULL;
|
|
|
|
n = u_noti->o_notify;
|
|
|
|
pr_info("%s event=%s(%lu)\n", __func__,
|
|
event_string(event), event);
|
|
|
|
if (event > VIRT_EVENT(NOTIFY_EVENT_VBUSPOWER)) {
|
|
pr_err("%s event is invalid\n", __func__);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
extra_notify_state(n, event, *(int *)param);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
void send_otg_notify(struct otg_notify *n,
|
|
unsigned long event, int enable)
|
|
{
|
|
struct usb_notify *u_notify = NULL;
|
|
int type = 0;
|
|
|
|
if (!n) {
|
|
pr_err("%s otg_notify is null\n", __func__);
|
|
goto end;
|
|
}
|
|
|
|
u_notify = (struct usb_notify *)(n->u_notify);
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
goto end;
|
|
}
|
|
pr_info("%s event=%s(%lu) enable=%d\n", __func__,
|
|
event_string(event), event, enable);
|
|
|
|
type = check_event_type(event);
|
|
|
|
notify_event_lock(u_notify, type);
|
|
|
|
if ((type & NOTIFY_EVENT_DELAY) && (type & NOTIFY_EVENT_STATE)) {
|
|
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 before_unlock;
|
|
}
|
|
}
|
|
|
|
if (type & NOTIFY_EVENT_EXTRA)
|
|
blocking_notifier_call_chain
|
|
(&u_notify->extra_notifier, event, &enable);
|
|
else if (type & NOTIFY_EVENT_STATE)
|
|
atomic_notifier_call_chain
|
|
(&u_notify->otg_notifier, event, &enable);
|
|
else
|
|
goto before_unlock;
|
|
|
|
before_unlock:
|
|
notify_event_unlock(u_notify, type);
|
|
end:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(send_otg_notify);
|
|
|
|
int get_typec_status(struct otg_notify *n, int event)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int ret = -ENODEV;
|
|
|
|
if (u_notify == NULL) {
|
|
pr_err("u_notify is NULL\n");
|
|
goto end;
|
|
}
|
|
|
|
if (event == NOTIFY_EVENT_POWER_SOURCE) {
|
|
/* SINK == 0, SOURCE == 1 */
|
|
ret = u_notify->typec_status.power_role;
|
|
} else
|
|
ret = u_notify->typec_status.pd;
|
|
end:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(get_typec_status);
|
|
|
|
struct otg_booster *find_get_booster(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int ret = 0;
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
if (!u_notify_core) {
|
|
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_notify *n, struct otg_booster *b)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int ret = 0;
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
u_notify->booster = b;
|
|
err:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(register_booster);
|
|
|
|
int register_ovc_func(struct otg_notify *n,
|
|
int (*check_state)(void *), void *data)
|
|
{
|
|
struct usb_notify *u_notify;
|
|
int ret = 0;
|
|
|
|
if (!n) {
|
|
pr_err("%s otg_notify is null\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
u_notify = (struct usb_notify *)(n->u_notify);
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\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_booster(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify;
|
|
int ret = 0;
|
|
|
|
if (!n) {
|
|
pr_err("%s otg_notify is null\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
u_notify = (struct usb_notify *)(n->u_notify);
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
return NOTIFY_NONE_MODE;
|
|
}
|
|
|
|
if (!u_notify_core) {
|
|
ret = create_usb_notify();
|
|
if (ret) {
|
|
pr_err("unable create_usb_notify\n");
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
pr_info("%s usb booster=%d\n", __func__, u_notify->ndev.booster);
|
|
ret = u_notify->ndev.booster;
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(get_booster);
|
|
|
|
int get_usb_mode(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify;
|
|
int ret = 0;
|
|
|
|
if (!n) {
|
|
pr_err("%s otg_notify is null\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
u_notify = (struct usb_notify *)(n->u_notify);
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
return NOTIFY_NONE_MODE;
|
|
}
|
|
|
|
if (!u_notify_core) {
|
|
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(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
unsigned long ret = 0;
|
|
int noti_ret = 0;
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
return NOTIFY_EVENT_NONE;
|
|
}
|
|
|
|
if (!u_notify_core) {
|
|
noti_ret = create_usb_notify();
|
|
if (noti_ret) {
|
|
pr_err("unable create_usb_notify\n");
|
|
return NOTIFY_EVENT_NONE;
|
|
}
|
|
}
|
|
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 is_usb_host(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int ret = 0;
|
|
int noti_ret = 0;
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
return NOTIFY_EVENT_NONE;
|
|
}
|
|
|
|
if (!u_notify_core) {
|
|
noti_ret = create_usb_notify();
|
|
if (noti_ret) {
|
|
pr_err("unable create_usb_notify\n");
|
|
return NOTIFY_EVENT_NONE;
|
|
}
|
|
}
|
|
|
|
if (n->unsupport_host || !IS_ENABLED(CONFIG_USB_HOST_NOTIFY))
|
|
ret = 0;
|
|
else
|
|
ret = 1;
|
|
|
|
pr_info("%s %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(is_usb_host);
|
|
|
|
bool is_blocked(struct otg_notify *n, int type)
|
|
{
|
|
struct usb_notify *u_notify = NULL;
|
|
int ret = 0;
|
|
|
|
if (!n) {
|
|
pr_err("%s otg_notify is null\n", __func__);
|
|
goto end;
|
|
}
|
|
u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
goto end;
|
|
}
|
|
|
|
if (!u_notify_core) {
|
|
ret = create_usb_notify();
|
|
if (ret) {
|
|
pr_err("unable create_usb_notify\n");
|
|
goto end;
|
|
}
|
|
}
|
|
pr_info("%s type=%d, disable_state=%lu\n",
|
|
__func__, type, u_notify->udev.disable_state);
|
|
|
|
if (type == NOTIFY_BLOCK_TYPE_HOST) {
|
|
if (test_bit(NOTIFY_BLOCK_TYPE_HOST,
|
|
&u_notify->udev.disable_state))
|
|
goto end2;
|
|
} else if (type == NOTIFY_BLOCK_TYPE_CLIENT) {
|
|
if (test_bit(NOTIFY_BLOCK_TYPE_CLIENT,
|
|
&u_notify->udev.disable_state))
|
|
goto end2;
|
|
} else if (type == NOTIFY_BLOCK_TYPE_ALL) {
|
|
if (test_bit(NOTIFY_BLOCK_TYPE_HOST,
|
|
&u_notify->udev.disable_state) &&
|
|
test_bit(NOTIFY_BLOCK_TYPE_CLIENT,
|
|
&u_notify->udev.disable_state))
|
|
goto end2;
|
|
}
|
|
|
|
end:
|
|
return false;
|
|
end2:
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL(is_blocked);
|
|
|
|
bool is_snkdfp_usb_device_connected(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify = NULL;
|
|
|
|
if (!n) {
|
|
pr_err("%s otg_notify is null\n", __func__);
|
|
return false;
|
|
}
|
|
u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
pr_info("%s is_device = %d, power_role = %d\n",
|
|
__func__, u_notify->is_device,
|
|
u_notify->typec_status.power_role);
|
|
if (u_notify->is_device &&
|
|
u_notify->typec_status.power_role == HNOTIFY_SINK) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL(is_snkdfp_usb_device_connected);
|
|
|
|
int get_con_dev_max_speed(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify = NULL;
|
|
|
|
if (!n) {
|
|
pr_err("%s otg_notify is null\n", __func__);
|
|
return false;
|
|
}
|
|
u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
pr_info("%s device max speed=%s\n", __func__,
|
|
usb_speed_string(u_notify->cond_max_speed));
|
|
return u_notify->cond_max_speed;
|
|
}
|
|
EXPORT_SYMBOL(get_con_dev_max_speed);
|
|
|
|
void set_con_dev_max_speed(struct otg_notify *n, int speed)
|
|
{
|
|
struct usb_notify *u_notify = NULL;
|
|
|
|
if (!n) {
|
|
pr_err("%s otg_notify is null\n", __func__);
|
|
return;
|
|
}
|
|
u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
return;
|
|
}
|
|
|
|
u_notify->cond_max_speed = speed;
|
|
|
|
pr_info("%s device max speed=%s\n", __func__,
|
|
usb_speed_string(speed));
|
|
}
|
|
EXPORT_SYMBOL(set_con_dev_max_speed);
|
|
|
|
void set_request_action(struct otg_notify *n, unsigned int request_action)
|
|
{
|
|
struct usb_notify *u_notify = NULL;
|
|
|
|
if (!n) {
|
|
pr_err("%s o_notify is null\n", __func__);
|
|
goto err;
|
|
}
|
|
u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n",
|
|
__func__);
|
|
goto err;
|
|
}
|
|
|
|
pr_info("%s prev action = %u set action as=%u\n",
|
|
__func__, u_notify->udev.request_action, request_action);
|
|
|
|
u_notify->udev.request_action = request_action;
|
|
err:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(set_request_action);
|
|
|
|
struct dev_table {
|
|
struct usb_device_id dev;
|
|
int index;
|
|
};
|
|
|
|
static struct dev_table known_usbaudio_device_table[] = {
|
|
{ .dev = { USB_DEVICE(0x04e8, 0xa051), },
|
|
},
|
|
{ .dev = { USB_DEVICE(0x04e8, 0xa054), },
|
|
},
|
|
{ .dev = { USB_DEVICE(0x04e8, 0xa05b), },
|
|
},
|
|
{ .dev = { USB_DEVICE(0x04e8, 0xa058), },
|
|
},
|
|
{ .dev = { USB_DEVICE(0x04e8, 0xa057), },
|
|
},
|
|
{ .dev = { USB_DEVICE(0x04e8, 0xa059), },
|
|
},
|
|
{ .dev = { USB_DEVICE(0x04e8, 0xa05e), },
|
|
},
|
|
{}
|
|
};
|
|
|
|
static int check_audio_id(struct usb_device *dev)
|
|
{
|
|
struct dev_table *id;
|
|
int ret = 0;
|
|
|
|
/* check VID, PID */
|
|
for (id = known_usbaudio_device_table; id->dev.match_flags; id++) {
|
|
if ((id->dev.match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
|
|
(id->dev.match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
|
|
id->dev.idVendor == le16_to_cpu(dev->descriptor.idVendor) &&
|
|
id->dev.idProduct == le16_to_cpu(dev->descriptor.idProduct)) {
|
|
ret = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (ret)
|
|
pr_info("%s find\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int check_audio_descriptor(struct usb_device *dev)
|
|
{
|
|
struct usb_interface *intf;
|
|
struct usb_host_interface *alts;
|
|
struct usb_endpoint_descriptor *endpt;
|
|
unsigned int i, j;
|
|
int ret = 0;
|
|
__u8 play_intf = 0, cap_intf = 0;
|
|
__u8 aud_con_cnt = 0, out_ep = 0, in_ep = 0;
|
|
|
|
/* 1. check samsung vid */
|
|
if (le16_to_cpu(dev->descriptor.idVendor) != 0x04e8)
|
|
goto done;
|
|
|
|
/* 2. If set config is not execute, return false */
|
|
if (!dev->actconfig) {
|
|
pr_info("%s no set config\n", __func__);
|
|
goto done;
|
|
}
|
|
|
|
for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) {
|
|
intf = dev->actconfig->interface[i];
|
|
alts = intf->cur_altsetting;
|
|
|
|
if (alts->desc.bInterfaceClass == USB_CLASS_AUDIO) {
|
|
if (alts->desc.bInterfaceSubClass
|
|
== USB_SUBCLASS_AUDIOCONTROL)
|
|
aud_con_cnt++;
|
|
if (alts->desc.bInterfaceSubClass
|
|
!= USB_SUBCLASS_AUDIOSTREAMING &&
|
|
alts->desc.bInterfaceSubClass
|
|
!= USB_CLASS_VENDOR_SPEC)
|
|
continue;
|
|
|
|
out_ep = 0;
|
|
in_ep = 0;
|
|
for (j = 0; j < intf->num_altsetting; j++) {
|
|
alts = &intf->altsetting[j];
|
|
|
|
if (alts->desc.bNumEndpoints < 1)
|
|
continue;
|
|
|
|
endpt = &alts->endpoint[0].desc;
|
|
/*
|
|
* If there is endpoint[1],
|
|
* it will be sync endpoint(feedback).
|
|
*/
|
|
|
|
if (!endpt)
|
|
continue;
|
|
|
|
if (endpt->bEndpointAddress & USB_DIR_IN) {
|
|
if (!in_ep)
|
|
in_ep = endpt->bEndpointAddress;
|
|
else if (in_ep !=
|
|
endpt->bEndpointAddress) {
|
|
pr_info("%s in_ep 2 or more\n",
|
|
__func__);
|
|
goto done;
|
|
} else
|
|
continue;
|
|
} else {
|
|
if (!out_ep)
|
|
out_ep =
|
|
endpt->bEndpointAddress;
|
|
else if (out_ep !=
|
|
endpt->bEndpointAddress) {
|
|
pr_info("%s out_ep 2 or more\n",
|
|
__func__);
|
|
goto done;
|
|
} else
|
|
continue;
|
|
}
|
|
}
|
|
if (out_ep)
|
|
play_intf++;
|
|
else if (in_ep)
|
|
cap_intf++;
|
|
else {
|
|
pr_err("%s no ep\n", __func__);
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
/* 3. final check. AUDIOCONTROL 1. playback 1. capture 1 */
|
|
if (aud_con_cnt == 1 && play_intf == 1 && cap_intf == 1)
|
|
ret = 1;
|
|
done:
|
|
if (aud_con_cnt)
|
|
pr_info("%s ret=%d,aud_con_cnt=%d,play_intf=%d,cap_intf=%d\n",
|
|
__func__, ret, aud_con_cnt, play_intf, cap_intf);
|
|
return ret;
|
|
}
|
|
|
|
int is_known_usbaudio(struct usb_device *dev)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = check_audio_id(dev);
|
|
if (ret)
|
|
goto done;
|
|
|
|
ret = check_audio_descriptor(dev);
|
|
if (ret)
|
|
goto done;
|
|
|
|
done:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(is_known_usbaudio);
|
|
|
|
void set_usb_audio_cardnum(int card_num, int bundle, int attach)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
struct usb_notify *u_notify = NULL;
|
|
|
|
if (!o_notify) {
|
|
pr_err("%s o_notify is null\n", __func__);
|
|
goto err;
|
|
}
|
|
u_notify = (struct usb_notify *)(o_notify->u_notify);
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n",
|
|
__func__);
|
|
goto err;
|
|
}
|
|
|
|
pr_info("%s card=%d attach=%d\n", __func__, card_num, attach);
|
|
|
|
if (attach) {
|
|
u_notify->udev.usb_audio_cards[card_num].cards = 1;
|
|
if (bundle)
|
|
u_notify->udev.usb_audio_cards[card_num].bundle = 1;
|
|
} else {
|
|
u_notify->udev.usb_audio_cards[card_num].cards = 0;
|
|
u_notify->udev.usb_audio_cards[card_num].bundle = 0;
|
|
}
|
|
err:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(set_usb_audio_cardnum);
|
|
|
|
#ifdef CONFIG_USB_AUDIO_ENHANCED_DETECT_TIME
|
|
int __weak get_next_snd_card_number(struct module *module)
|
|
{
|
|
int idx = 0;
|
|
|
|
pr_info("%s call weak function\n", __func__);
|
|
return idx;
|
|
}
|
|
#endif
|
|
|
|
void send_usb_audio_uevent(struct usb_device *dev,
|
|
int card_num, int attach)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
char *envp[6];
|
|
char *type = {"TYPE=usbaudio"};
|
|
char *state_add = {"STATE=ADD"};
|
|
char *state_remove = {"STATE=REMOVE"};
|
|
char vidpid_vuf[15];
|
|
char path_buf[50];
|
|
int index = 0;
|
|
#ifdef CONFIG_USB_AUDIO_ENHANCED_DETECT_TIME
|
|
char cardnum_buf[10];
|
|
int cardnum = 0;
|
|
#endif
|
|
|
|
if (!o_notify) {
|
|
pr_err("%s o_notify is null\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
if (!is_known_usbaudio(dev))
|
|
goto err;
|
|
|
|
envp[index++] = type;
|
|
|
|
if (attach)
|
|
envp[index++] = state_add;
|
|
else
|
|
envp[index++] = state_remove;
|
|
|
|
snprintf(vidpid_vuf, sizeof(vidpid_vuf),
|
|
"ID=%04X/%04X", le16_to_cpu(dev->descriptor.idVendor),
|
|
le16_to_cpu(dev->descriptor.idProduct));
|
|
|
|
envp[index++] = vidpid_vuf;
|
|
|
|
snprintf(path_buf, sizeof(path_buf),
|
|
"PATH=/dev/bus/usb/%03d/%03d", dev->bus->busnum, dev->devnum);
|
|
|
|
envp[index++] = path_buf;
|
|
|
|
#ifdef CONFIG_USB_AUDIO_ENHANCED_DETECT_TIME
|
|
if (attach && !card_num) {
|
|
cardnum = get_next_snd_card_number(THIS_MODULE);
|
|
if (cardnum < 0) {
|
|
pr_err("%s cardnum error\n", __func__);
|
|
goto err;
|
|
}
|
|
} else
|
|
cardnum = card_num;
|
|
|
|
set_usb_audio_cardnum(cardnum, 1, attach);
|
|
|
|
snprintf(cardnum_buf, sizeof(cardnum_buf),
|
|
"CARDNUM=%d", cardnum);
|
|
|
|
envp[index++] = cardnum_buf;
|
|
#endif
|
|
|
|
envp[index++] = NULL;
|
|
|
|
if (send_usb_notify_uevent(o_notify, envp)) {
|
|
pr_err("%s error\n", __func__);
|
|
goto err;
|
|
}
|
|
pr_info("%s\n", __func__);
|
|
err:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(send_usb_audio_uevent);
|
|
|
|
int send_usb_notify_uevent(struct otg_notify *n, char *envp_ext[])
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int ret = 0;
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify is null\n", __func__);
|
|
ret = -EFAULT;
|
|
goto err;
|
|
}
|
|
|
|
ret = usb_notify_dev_uevent(&u_notify->udev, envp_ext);
|
|
err:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(send_usb_notify_uevent);
|
|
|
|
#if defined(CONFIG_USB_HW_PARAM)
|
|
unsigned long long *get_hw_param(struct otg_notify *n,
|
|
enum usb_hw_param index)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int ret = 0;
|
|
|
|
if (index < 0 || index >= USB_CCIC_HW_PARAM_MAX) {
|
|
pr_err("%s usb_hw_param is out of bound\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
if (!u_notify_core) {
|
|
ret = create_usb_notify();
|
|
if (ret) {
|
|
pr_err("unable create_usb_notify\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
return &(u_notify->hw_param[index]);
|
|
}
|
|
EXPORT_SYMBOL(get_hw_param);
|
|
|
|
int inc_hw_param(struct otg_notify *n,
|
|
enum usb_hw_param index)
|
|
{
|
|
struct usb_notify *u_notify;
|
|
int ret = 0;
|
|
|
|
if (!n) {
|
|
pr_err("%s otg_notify is null\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
if (index < 0 || index >= USB_CCIC_HW_PARAM_MAX) {
|
|
pr_err("%s usb_hw_param is out of bound\n", __func__);
|
|
ret = -ENOMEM;
|
|
return ret;
|
|
}
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
ret = -ENOENT;
|
|
return ret;
|
|
}
|
|
|
|
if (!u_notify_core) {
|
|
ret = create_usb_notify();
|
|
if (ret) {
|
|
pr_err("unable create_usb_notify\n");
|
|
return ret;
|
|
}
|
|
}
|
|
u_notify->hw_param[index]++;
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(inc_hw_param);
|
|
|
|
int inc_hw_param_host(struct host_notify_dev *dev,
|
|
enum usb_hw_param index)
|
|
{
|
|
struct usb_notify *u_notify = container_of(dev,
|
|
struct usb_notify, ndev);
|
|
int ret = 0;
|
|
|
|
if (index < 0 || index >= USB_CCIC_HW_PARAM_MAX) {
|
|
pr_err("%s usb_hw_param is out of bound\n", __func__);
|
|
ret = -ENOMEM;
|
|
return ret;
|
|
}
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
ret = -ENOENT;
|
|
return ret;
|
|
}
|
|
|
|
if (!u_notify_core) {
|
|
ret = create_usb_notify();
|
|
if (ret) {
|
|
pr_err("unable create_usb_notify\n");
|
|
return ret;
|
|
}
|
|
}
|
|
u_notify->hw_param[index]++;
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(inc_hw_param_host);
|
|
|
|
int register_hw_param_manager(struct otg_notify *n, unsigned long (*fptr)(int))
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
int ret = 0;
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
ret = -ENOENT;
|
|
goto err;
|
|
}
|
|
|
|
if (!u_notify_core) {
|
|
ret = create_usb_notify();
|
|
if (ret) {
|
|
pr_err("unable create_usb_notify\n");
|
|
goto err;
|
|
}
|
|
}
|
|
u_notify->udev.fp_hw_param_manager = fptr;
|
|
pr_info("%s\n", __func__);
|
|
err:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(register_hw_param_manager);
|
|
#endif
|
|
|
|
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_core)
|
|
return NULL;
|
|
if (!u_notify_core->o_notify)
|
|
return NULL;
|
|
return u_notify_core->o_notify;
|
|
}
|
|
EXPORT_SYMBOL(get_otg_notify);
|
|
|
|
void enable_usb_notify(void)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
struct usb_notify *u_notify = NULL;
|
|
|
|
if (!o_notify) {
|
|
pr_err("%s o_notify is null\n", __func__);
|
|
return;
|
|
}
|
|
u_notify = (struct usb_notify *)(o_notify->u_notify);
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
if (!o_notify->booting_delay_sync_usb) {
|
|
pr_err("%s booting_delay_sync_usb is not setting\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
o_notify->booting_delay_sync_usb = 0;
|
|
if (!u_notify->o_notify->booting_delay_sec)
|
|
schedule_delayed_work(&u_notify->b_delay.booting_work, 0);
|
|
}
|
|
EXPORT_SYMBOL(enable_usb_notify);
|
|
|
|
static int otg_notify_reboot(struct notifier_block *nb,
|
|
unsigned long event, void *cmd)
|
|
{
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
struct usb_notify *u_notify = NULL;
|
|
|
|
if (!o_notify) {
|
|
pr_err("%s o_notify is null\n", __func__);
|
|
goto err;
|
|
}
|
|
u_notify = (struct usb_notify *)(o_notify->u_notify);
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n",
|
|
__func__);
|
|
goto err;
|
|
}
|
|
|
|
if (is_host_cable_enable(o_notify))
|
|
send_otg_notify(o_notify,
|
|
VIRT_EVENT(u_notify->c_type), 0);
|
|
err:
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block otg_notify_reboot_nb = {
|
|
.notifier_call = otg_notify_reboot,
|
|
};
|
|
|
|
int set_otg_notify(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify;
|
|
int ret = 0;
|
|
|
|
if (!u_notify_core) {
|
|
ret = create_usb_notify();
|
|
if (ret) {
|
|
pr_err("unable create_usb_notify\n");
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
if (u_notify_core->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_core->o_notify = n;
|
|
|
|
u_notify = kzalloc(sizeof(struct usb_notify), GFP_KERNEL);
|
|
if (!u_notify) {
|
|
ret = -ENOMEM;
|
|
goto err1;
|
|
}
|
|
|
|
u_notify->o_notify = n;
|
|
|
|
n->u_notify = (void *)u_notify;
|
|
|
|
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 err2;
|
|
}
|
|
|
|
ovc_init(u_notify);
|
|
notify_event_lock_init(u_notify);
|
|
mutex_init(&u_notify->state_lock);
|
|
|
|
ATOMIC_INIT_NOTIFIER_HEAD(&u_notify->otg_notifier);
|
|
u_notify->otg_nb.notifier_call = otg_notifier_callback;
|
|
ret = atomic_notifier_chain_register(&u_notify->otg_notifier,
|
|
&u_notify->otg_nb);
|
|
if (ret < 0) {
|
|
pr_err("atomic_notifier_chain_register failed\n");
|
|
goto err3;
|
|
}
|
|
|
|
BLOCKING_INIT_NOTIFIER_HEAD(&u_notify->extra_notifier);
|
|
u_notify->extra_nb.notifier_call = extra_notifier_callback;
|
|
ret = blocking_notifier_chain_register
|
|
(&u_notify->extra_notifier, &u_notify->extra_nb);
|
|
if (ret < 0) {
|
|
pr_err("blocking_notifier_chain_register failed\n");
|
|
goto err4;
|
|
}
|
|
|
|
if (!n->unsupport_host) {
|
|
u_notify->ndev.name = "usb_otg";
|
|
u_notify->ndev.set_booster = n->vbus_drive;
|
|
u_notify->ndev.set_mode = n->set_host;
|
|
ret = host_notify_dev_register(&u_notify->ndev);
|
|
if (ret < 0) {
|
|
pr_err("host_notify_dev_register is failed\n");
|
|
goto err5;
|
|
}
|
|
|
|
if (!n->vbus_drive) {
|
|
pr_err("vbus_drive is null\n");
|
|
goto err6;
|
|
}
|
|
}
|
|
|
|
u_notify->udev.name = "usb_control";
|
|
u_notify->udev.set_disable = set_notify_disable;
|
|
u_notify->udev.set_mdm = set_notify_mdm;
|
|
u_notify->udev.control_usb_max_speed = control_usb_maximum_speed;
|
|
u_notify->udev.fp_hw_param_manager = NULL;
|
|
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 err6;
|
|
}
|
|
|
|
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 err7;
|
|
}
|
|
}
|
|
|
|
if (n->is_wakelock) {
|
|
u_notify->ws.name = "usb_notify";
|
|
wakeup_source_add(&u_notify->ws);
|
|
}
|
|
|
|
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();
|
|
|
|
register_usblog_proc();
|
|
|
|
register_reboot_notifier(&otg_notify_reboot_nb);
|
|
|
|
pr_info("registered otg_notify -\n");
|
|
return 0;
|
|
err7:
|
|
usb_notify_dev_unregister(&u_notify->udev);
|
|
err6:
|
|
if (!n->unsupport_host)
|
|
host_notify_dev_unregister(&u_notify->ndev);
|
|
err5:
|
|
blocking_notifier_chain_unregister(&u_notify->extra_notifier,
|
|
&u_notify->extra_nb);
|
|
err4:
|
|
atomic_notifier_chain_unregister(&u_notify->otg_notifier,
|
|
&u_notify->otg_nb);
|
|
err3:
|
|
flush_workqueue(u_notify->notifier_wq);
|
|
destroy_workqueue(u_notify->notifier_wq);
|
|
err2:
|
|
kfree(u_notify);
|
|
err1:
|
|
u_notify_core->o_notify = NULL;
|
|
err:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(set_otg_notify);
|
|
|
|
void put_otg_notify(struct otg_notify *n)
|
|
{
|
|
struct usb_notify *u_notify = (struct usb_notify *)(n->u_notify);
|
|
|
|
if (!u_notify) {
|
|
pr_err("%s u_notify structure is null\n", __func__);
|
|
return;
|
|
}
|
|
unregister_reboot_notifier(&otg_notify_reboot_nb);
|
|
unregister_usblog_proc();
|
|
unregister_usbdev_notify();
|
|
if (n->booting_delay_sec)
|
|
cancel_delayed_work_sync(&u_notify->b_delay.booting_work);
|
|
if (n->is_wakelock)
|
|
wakeup_source_remove(&u_notify->ws);
|
|
|
|
if (gpio_is_valid(n->vbus_detect_gpio))
|
|
free_irq(gpio_to_irq(n->vbus_detect_gpio), NULL);
|
|
usb_notify_dev_unregister(&u_notify->udev);
|
|
if (!n->unsupport_host)
|
|
host_notify_dev_unregister(&u_notify->ndev);
|
|
blocking_notifier_chain_unregister(&u_notify->extra_notifier,
|
|
&u_notify->extra_nb);
|
|
atomic_notifier_chain_unregister(&u_notify->otg_notifier,
|
|
&u_notify->otg_nb);
|
|
flush_workqueue(u_notify->notifier_wq);
|
|
destroy_workqueue(u_notify->notifier_wq);
|
|
u_notify->o_notify = NULL;
|
|
kfree(u_notify);
|
|
}
|
|
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_core)
|
|
return;
|
|
usb_notify_class_exit();
|
|
notify_class_exit();
|
|
kfree(u_notify_core);
|
|
}
|
|
|
|
module_init(usb_notify_init);
|
|
module_exit(usb_notify_exit);
|
|
|
|
MODULE_AUTHOR("Samsung USB Team");
|
|
MODULE_DESCRIPTION("USB Notify Layer");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION(NOTIFY_VERSION);
|