mirror of
https://github.com/physwizz/a155-U-u1.git
synced 2025-09-05 12:50:27 +00:00
1025 lines
28 KiB
C
1025 lines
28 KiB
C
/*
|
|
* f_ss_mon_gadget.c - generic USB serial function driver
|
|
*
|
|
* Copyright (C) 2021 by samsung Corporation
|
|
*
|
|
* This software is distributed under the terms of the GNU General
|
|
* Public License ("GPL") as published by the Free Software Foundation,
|
|
* either version 2 of that License or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/configfs.h>
|
|
#include <linux/usb/composite.h>
|
|
|
|
#include "../configfs.h"
|
|
#include <linux/usb/f_accessory.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER)
|
|
#include <linux/usb_notify.h>
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
|
|
#include <linux/usb/typec/manager/usb_typec_manager_notifier.h>
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
#include <linux/battery/sec_battery_common.h>
|
|
#endif
|
|
|
|
#define MAX_INST_NAME_LEN 40
|
|
#define MAX_NAME_LEN 40
|
|
|
|
#define DRIVER_NAME "usb_mtp_gadget"
|
|
#define MAX_GUID_SIZE 0x28
|
|
static const char mtpg_longname[] = "mtp";
|
|
static const char shortname[] = DRIVER_NAME;
|
|
|
|
static char guid_info[MAX_GUID_SIZE+1];
|
|
/* ID for Microsoft MTP OS String */
|
|
#define MTPG_OS_STRING_ID 0xEE
|
|
#define GADGET_MTP 0x01
|
|
#define GADGET_RNDIS 0x02
|
|
#define GADGET_ACCESSORY 0x04
|
|
#define GADGET_ADB 0x08
|
|
#define GADGET_ACM 0x10
|
|
#define GADGET_DM 0x20
|
|
#define GADGET_MIDI 0x40
|
|
#define GADGET_CONN_GADGET 0x80
|
|
|
|
struct usb_mtp_avd_descriptor {
|
|
__u8 bLength;
|
|
__u8 bDescriptorType;
|
|
__u8 bDescriptorSubType;
|
|
|
|
__u16 bDAU1_Type;
|
|
__u16 bDAU1_Length;
|
|
__u8 bDAU1_Value;
|
|
} __attribute__ ((packed));
|
|
|
|
static struct usb_mtp_avd_descriptor mtp_avd_descriptor = {
|
|
.bLength = 0x08,
|
|
.bDescriptorType = 0x24,
|
|
.bDescriptorSubType = 0x80,
|
|
|
|
/* First DAU = MTU Size */
|
|
.bDAU1_Type = 0x000C,
|
|
.bDAU1_Length = 0x0001,
|
|
.bDAU1_Value = 0x01,
|
|
};
|
|
|
|
static struct usb_descriptor_header *log_fs_function[] = {
|
|
(struct usb_descriptor_header *) &mtp_avd_descriptor,
|
|
NULL,
|
|
};
|
|
|
|
/* high speed support: */
|
|
|
|
static struct usb_descriptor_header *log_hs_function[] = {
|
|
(struct usb_descriptor_header *) &mtp_avd_descriptor,
|
|
NULL,
|
|
};
|
|
|
|
/* super speed support: */
|
|
static struct usb_descriptor_header *log_ss_function[] = {
|
|
(struct usb_descriptor_header *) &mtp_avd_descriptor,
|
|
NULL,
|
|
};
|
|
|
|
#define ACCESSORY_STRING_MASK ( \
|
|
1 << ACCESSORY_STRING_MANUFACTURER | \
|
|
1 << ACCESSORY_STRING_MODEL | \
|
|
1 << ACCESSORY_STRING_VERSION)
|
|
|
|
enum {
|
|
RELEASE = 0,
|
|
NOTIFY = 1,
|
|
};
|
|
struct f_ss_monitor {
|
|
struct usb_function function;
|
|
int current_usb_mode;
|
|
u8 intreface_id;
|
|
int accessory_string;
|
|
u8 aoa_start_cmd;
|
|
int usb_function_info;
|
|
char usb_mode[50];
|
|
bool is_bind;
|
|
struct ss_monitor_instance *func_inst;
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
int vbus_current;
|
|
struct work_struct set_vbus_current_work;
|
|
#endif
|
|
};
|
|
|
|
static inline struct f_ss_monitor *func_to_ss_monitor(struct usb_function *f)
|
|
{
|
|
return container_of(f, struct f_ss_monitor, function);
|
|
}
|
|
|
|
struct aoa_connect_status {
|
|
struct delayed_work usb_reset_event_work;
|
|
ktime_t rst_time_before;
|
|
ktime_t rst_time_first;
|
|
int rst_err_cnt;
|
|
bool rst_err_noti;
|
|
bool event_state;
|
|
bool acc_dev_status;
|
|
bool acc_online;
|
|
};
|
|
struct ss_monitor_instance {
|
|
struct usb_function_instance func_inst;
|
|
char *name;
|
|
struct f_ss_monitor *ss_monitor;
|
|
struct aoa_connect_status aoa_reset;
|
|
bool vbus_session_called;
|
|
};
|
|
|
|
static inline struct ss_monitor_instance *to_fi_ss_monitor(struct usb_function_instance *fi)
|
|
{
|
|
return container_of(fi, struct ss_monitor_instance, func_inst);
|
|
}
|
|
static struct ss_monitor_instance *g_ss_monitor;
|
|
#define ERR_RESET_CNT 3
|
|
|
|
static inline int _lock(atomic_t *excl)
|
|
{
|
|
pr_info("[%s] \tline = [%d]\n", __func__, __LINE__);
|
|
|
|
if (atomic_inc_return(excl) == 1)
|
|
return 0;
|
|
|
|
atomic_dec(excl);
|
|
return -1;
|
|
}
|
|
|
|
static inline void _unlock(atomic_t *excl)
|
|
{
|
|
atomic_dec(excl);
|
|
}
|
|
|
|
struct mtpg_dev {
|
|
int error;
|
|
atomic_t open_excl;
|
|
};
|
|
static struct mtpg_dev *the_mtpg;
|
|
|
|
static int mtpg_open(struct inode *ip, struct file *fp)
|
|
{
|
|
pr_info("usb: [%s]\tline = [%d]\n", __func__, __LINE__);
|
|
|
|
if (_lock(&the_mtpg->open_excl)) {
|
|
pr_err("usb: %s fn mtpg device busy\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
|
|
fp->private_data = the_mtpg;
|
|
|
|
/* clear the error latch */
|
|
the_mtpg->error = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mtpg_release_device(struct inode *ip, struct file *fp)
|
|
{
|
|
pr_info("usb: [%s]\tline = [%d]\n", __func__, __LINE__);
|
|
if (the_mtpg != NULL)
|
|
_unlock(&the_mtpg->open_excl);
|
|
return 0;
|
|
}
|
|
|
|
/* file operations for MTP device /dev/usb_mtp_gadget */
|
|
static const struct file_operations mtpg_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = mtpg_open,
|
|
.release = mtpg_release_device,
|
|
};
|
|
|
|
static struct miscdevice mtpg_device = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = shortname,
|
|
.fops = &mtpg_fops,
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static int ss_monitor_set_alt(struct usb_function *f,
|
|
unsigned int intf, unsigned int alt)
|
|
{
|
|
struct f_ss_monitor *ss_monitor;
|
|
|
|
/* We can add usb charing event */
|
|
ss_monitor = func_to_ss_monitor(f);
|
|
g_ss_monitor->aoa_reset.acc_dev_status = false;
|
|
if (ss_monitor->usb_function_info & GADGET_ACCESSORY) {
|
|
pr_info("usb: %s: aoa connect\n", __func__);
|
|
g_ss_monitor->aoa_reset.acc_online = true;
|
|
g_ss_monitor->aoa_reset.rst_err_cnt = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ss_monitor_disable(struct usb_function *f)
|
|
{
|
|
struct f_ss_monitor *ss_monitor;
|
|
char aoa_check[12] = {0,};
|
|
|
|
ss_monitor = func_to_ss_monitor(f);
|
|
if (!ss_monitor)
|
|
goto ret;
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
ss_monitor->vbus_current = USB_CURRENT_UNCONFIGURED;
|
|
#endif
|
|
if (ss_monitor->accessory_string && !ss_monitor->aoa_start_cmd) {
|
|
snprintf(aoa_check, sizeof(aoa_check), "AOA_ERR_%x", ss_monitor->accessory_string);
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
store_usblog_notify(NOTIFY_USBMODE_EXTRA, (void *)aoa_check, NULL);
|
|
#endif
|
|
}
|
|
ss_monitor->accessory_string = 0;
|
|
ss_monitor->aoa_start_cmd = 0;
|
|
ret:
|
|
if (g_ss_monitor->aoa_reset.acc_online) {
|
|
g_ss_monitor->aoa_reset.acc_online = false;
|
|
g_ss_monitor->aoa_reset.acc_dev_status = true;
|
|
}
|
|
memset(guid_info, 0, sizeof(guid_info));
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
static int set_vbus_current(int state)
|
|
{
|
|
struct power_supply *psy;
|
|
union power_supply_propval pval = {0};
|
|
|
|
pr_info("usb: %s : %dmA\n", __func__, state);
|
|
psy = power_supply_get_by_name("battery");
|
|
if (!psy) {
|
|
pr_err("%s: fail to get battery power_supply\n", __func__);
|
|
return -1;
|
|
}
|
|
pval.intval = state;
|
|
psy_do_property("battery", set, POWER_SUPPLY_EXT_PROP_USB_CONFIGURE, pval);
|
|
power_supply_put(psy);
|
|
return 0;
|
|
}
|
|
|
|
static void set_vbus_current_work(struct work_struct *w)
|
|
{
|
|
struct f_ss_monitor *ss_monitor;
|
|
#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER)
|
|
struct otg_notify *o_notify = get_otg_notify();
|
|
#endif
|
|
|
|
ss_monitor = container_of(w, struct f_ss_monitor, set_vbus_current_work);
|
|
#if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER)
|
|
switch (ss_monitor->vbus_current) {
|
|
case USB_CURRENT_SUSPENDED:
|
|
send_otg_notify(o_notify, NOTIFY_EVENT_USBD_SUSPENDED, 1);
|
|
goto skip;
|
|
case USB_CURRENT_UNCONFIGURED:
|
|
send_otg_notify(o_notify, NOTIFY_EVENT_USBD_UNCONFIGURED, 1);
|
|
break;
|
|
case USB_CURRENT_HIGH_SPEED:
|
|
case USB_CURRENT_SUPER_SPEED:
|
|
send_otg_notify(o_notify, NOTIFY_EVENT_USBD_CONFIGURED, 1);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#else
|
|
if (ss_monitor->vbus_current == USB_CURRENT_SUSPENDED)
|
|
goto skip;
|
|
#endif
|
|
|
|
set_vbus_current(ss_monitor->vbus_current);
|
|
skip:
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
mtp_complete_get_guid(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
int size;
|
|
if (the_mtpg)
|
|
pr_info("usb: [%s]\tline = [%d]\n", __func__, __LINE__);
|
|
else
|
|
pr_info("usb: [%s]\tline = [%d] / mtpg misc driver load fails\n", __func__, __LINE__);
|
|
|
|
if (req->status != 0) {
|
|
pr_info("usb: [%s]req->status !=0\tline = [%d]\n", __func__, __LINE__);
|
|
return;
|
|
}
|
|
|
|
if (req->actual >= sizeof(guid_info))
|
|
size = sizeof(guid_info)-1;
|
|
else
|
|
size = req->actual;
|
|
memset(guid_info, 0, sizeof(guid_info));
|
|
memcpy(guid_info, req->buf, size);
|
|
}
|
|
|
|
static ssize_t guid_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
pr_info("usb: mtp: [%s]\tline = [%d]\n", __func__, __LINE__);
|
|
memcpy(buf, guid_info, MAX_GUID_SIZE);
|
|
return MAX_GUID_SIZE;
|
|
}
|
|
|
|
static ssize_t guid_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
int value;
|
|
|
|
pr_info("usb: mtp: [%s]\tline = [%d]\n", __func__, __LINE__);
|
|
if (size > MAX_GUID_SIZE)
|
|
return -EINVAL;
|
|
|
|
value = strlcpy(guid_info, buf, size);
|
|
return value;
|
|
}
|
|
|
|
DEVICE_ATTR_RW(guid);
|
|
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
static bool is_aoa_string_come(int string_index)
|
|
{
|
|
if ((string_index & (1<<ACCESSORY_STRING_VERSION)) &&
|
|
(string_index & (1<<ACCESSORY_STRING_MODEL)) &&
|
|
(string_index & (1<<ACCESSORY_STRING_MANUFACTURER)))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static char *get_usb_speed(enum usb_device_speed speed)
|
|
{
|
|
switch (speed) {
|
|
case USB_SPEED_HIGH:
|
|
return "HS";
|
|
case USB_SPEED_SUPER:
|
|
return "SS";
|
|
case USB_SPEED_SUPER_PLUS:
|
|
return "PSS";
|
|
case USB_SPEED_FULL:
|
|
return "FS";
|
|
case USB_SPEED_UNKNOWN:
|
|
return "UN";
|
|
case USB_SPEED_LOW:
|
|
return "LS";
|
|
case USB_SPEED_WIRELESS:
|
|
return "WS";
|
|
default:
|
|
return "ERR";
|
|
}
|
|
}
|
|
|
|
static void ss_mon_usb_event_work(struct work_struct *work)
|
|
{
|
|
pr_info("%s, event_state: %d\n", __func__, g_ss_monitor->aoa_reset.event_state);
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
if (g_ss_monitor->aoa_reset.event_state)
|
|
send_usb_err_uevent(USB_ERR_ABNORMAL_RESET, NOTIFY);
|
|
else
|
|
send_usb_err_uevent(USB_ERR_ABNORMAL_RESET, RELEASE);
|
|
#endif
|
|
}
|
|
void vbus_session_notify(struct usb_gadget *gadget, int is_active, int ret)
|
|
{
|
|
if (!g_ss_monitor)
|
|
return;
|
|
if (ret == EAGAIN) {
|
|
g_ss_monitor->vbus_session_called = true;
|
|
return;
|
|
} else if (g_ss_monitor->vbus_session_called) {
|
|
pr_info("usb: %s : vbus_session %s run_stop(%s)\n",
|
|
__func__, is_active ? "On" : "Off", ret ? "fail" : "success");
|
|
g_ss_monitor->vbus_session_called = false;
|
|
if (is_active) {
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
if (!ret)
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=VBUS:EN:SUCCESS", NULL);
|
|
else
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=VBUS:EN:FAIL", NULL);
|
|
#endif
|
|
;
|
|
} else {
|
|
if (g_ss_monitor->aoa_reset.rst_err_noti) {
|
|
g_ss_monitor->aoa_reset.event_state = RELEASE;
|
|
g_ss_monitor->aoa_reset.rst_err_noti = false;
|
|
schedule_delayed_work(&g_ss_monitor->aoa_reset.usb_reset_event_work,
|
|
msecs_to_jiffies(0));
|
|
}
|
|
g_ss_monitor->aoa_reset.rst_err_cnt = 0;
|
|
g_ss_monitor->aoa_reset.acc_dev_status = 0;
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
if (!ret)
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=VBUS:DIS:SUCCESS", NULL);
|
|
else
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=VBUS:DIS:FAIL", NULL);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(vbus_session_notify);
|
|
void usb_reset_notify(struct usb_gadget *gadget)
|
|
{
|
|
ktime_t current_time;
|
|
|
|
if (!g_ss_monitor)
|
|
return;
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
switch (gadget->speed) {
|
|
case USB_SPEED_SUPER:
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=RESET:SUPER", NULL);
|
|
break;
|
|
case USB_SPEED_HIGH:
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=RESET:HIGH", NULL);
|
|
break;
|
|
case USB_SPEED_FULL:
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=RESET:FULL", NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
if (g_ss_monitor->ss_monitor == NULL)
|
|
return;
|
|
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
g_ss_monitor->ss_monitor->vbus_current = USB_CURRENT_UNCONFIGURED;
|
|
schedule_work(&g_ss_monitor->ss_monitor->set_vbus_current_work);
|
|
#endif
|
|
|
|
if (g_ss_monitor->aoa_reset.acc_dev_status && (g_ss_monitor->aoa_reset.rst_err_noti == false)) {
|
|
current_time = ktime_to_ms(ktime_get_boottime());
|
|
if (g_ss_monitor->aoa_reset.rst_err_cnt == 0) {
|
|
if ((current_time - g_ss_monitor->aoa_reset.rst_time_before) < 1000) {
|
|
g_ss_monitor->aoa_reset.rst_err_cnt++;
|
|
g_ss_monitor->aoa_reset.rst_time_first = g_ss_monitor->aoa_reset.rst_time_before;
|
|
}
|
|
} else {
|
|
if ((current_time - g_ss_monitor->aoa_reset.rst_time_first) < 1000)
|
|
g_ss_monitor->aoa_reset.rst_err_cnt++;
|
|
else
|
|
g_ss_monitor->aoa_reset.rst_err_cnt = 0;
|
|
}
|
|
if (g_ss_monitor->aoa_reset.rst_err_cnt >= ERR_RESET_CNT) {
|
|
g_ss_monitor->aoa_reset.event_state = NOTIFY;
|
|
schedule_delayed_work(&g_ss_monitor->aoa_reset.usb_reset_event_work, msecs_to_jiffies(0));
|
|
g_ss_monitor->aoa_reset.rst_err_noti = true;
|
|
}
|
|
pr_info("usb: %s rst_err_cnt: %d, time_current: %lld, time_before: %lld\n",
|
|
__func__, g_ss_monitor->aoa_reset.rst_err_cnt, current_time,
|
|
g_ss_monitor->aoa_reset.rst_time_before);
|
|
g_ss_monitor->aoa_reset.rst_time_before = current_time;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(usb_reset_notify);
|
|
static int ss_monitor_setup(struct usb_function *f,
|
|
const struct usb_ctrlrequest *ctrl)
|
|
{
|
|
struct f_ss_monitor *ss_monitor;
|
|
struct usb_composite_dev *cdev;
|
|
struct usb_request *req;
|
|
int value = -EOPNOTSUPP;
|
|
u16 w_index = le16_to_cpu(ctrl->wIndex);
|
|
u16 w_value = le16_to_cpu(ctrl->wValue);
|
|
u16 w_length = le16_to_cpu(ctrl->wLength);
|
|
char *usb_speed = NULL;
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
char log_buf[30] = {0};
|
|
#endif
|
|
|
|
ss_monitor = func_to_ss_monitor(f);
|
|
|
|
if (f->config != NULL && f->config->cdev != NULL) {
|
|
cdev = f->config->cdev;
|
|
req = cdev->req;
|
|
} else
|
|
goto unknown;
|
|
|
|
if (ctrl->bRequestType ==
|
|
(USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE)
|
|
&& ctrl->bRequest == USB_REQ_GET_DESCRIPTOR
|
|
&& (w_value >> 8) == USB_DT_STRING
|
|
&& (w_value & 0xFF) == MTPG_OS_STRING_ID) {
|
|
|
|
pr_info("usb: %s: MS OS String Descriptor\n", __func__);
|
|
} else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) {
|
|
/* Usb EP0 Vendor Requset */
|
|
if (ctrl->bRequestType & USB_DIR_IN) {
|
|
pr_info("usb: vendor requset : %02x.%02x v%04x i%04x l%u\n",
|
|
ctrl->bRequestType, ctrl->bRequest,
|
|
w_value, w_index, w_length);
|
|
} else {
|
|
/* samsung AVD operation: GUID for MTP */
|
|
if (ctrl->bRequest == 0xA3) {
|
|
pr_info("usb: [%s] RECEIVE PC GUID / line[%d]\n",
|
|
__func__, __LINE__);
|
|
if (w_length > MAX_GUID_SIZE) {
|
|
pr_info("usb: [%s] Invalid GUID size / line[%d]\n",
|
|
__func__, __LINE__);
|
|
goto unknown;
|
|
}
|
|
|
|
value = w_length;
|
|
req->complete = mtp_complete_get_guid;
|
|
req->zero = 0;
|
|
req->length = value;
|
|
value = usb_ep_queue(cdev->gadget->ep0,
|
|
req, GFP_ATOMIC);
|
|
|
|
if (value < 0) {
|
|
pr_err("usb: [%s:%d]Error usb_ep_queue\n",
|
|
__func__, __LINE__);
|
|
}
|
|
} else {
|
|
if (ctrl->bRequest == ACCESSORY_START) {
|
|
ss_monitor->aoa_start_cmd = 1;
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
if (!is_aoa_string_come(ss_monitor->accessory_string))
|
|
store_usblog_notify(NOTIFY_USBMODE_EXTRA,
|
|
(void *)"AOA_STR_ERR", NULL);
|
|
else
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"ACCESSORY=START", NULL);
|
|
#endif
|
|
} else if (ctrl->bRequest == ACCESSORY_SEND_STRING) {
|
|
ss_monitor->accessory_string |= 0x1 << w_index;
|
|
}
|
|
|
|
pr_info("usb: vendor requset : %02x.%02x v%04x i%04x l%u\n",
|
|
ctrl->bRequestType, ctrl->bRequest,
|
|
w_value, w_index, w_length);
|
|
}
|
|
}
|
|
|
|
} else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
|
|
switch (ctrl->bRequest) {
|
|
case USB_REQ_GET_DESCRIPTOR:
|
|
if (ctrl->bRequestType != USB_DIR_IN)
|
|
goto unknown;
|
|
switch (w_value >> 8) {
|
|
case USB_DT_DEVICE:
|
|
if (w_length == USB_DT_DEVICE_SIZE) {
|
|
usb_speed = get_usb_speed(cdev->gadget->speed);
|
|
pr_info("usb: GET_DES(%s)\n", usb_speed);
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
sprintf(log_buf, "USB_STATE=ENUM:GET:DES:%s", usb_speed);
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=ENUM:GET:DES", NULL);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
|
|
set_usb_enumeration_state(cdev->gadget->speed);
|
|
#endif
|
|
}
|
|
break;
|
|
case USB_DT_CONFIG:
|
|
pr_info("usb: GET_CONFIG (%d)\n", w_index);
|
|
break;
|
|
}
|
|
break;
|
|
case USB_REQ_SET_CONFIGURATION:
|
|
pr_info("usb: SET_CONFIG (%d)\n", w_value);
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
if (cdev->gadget->speed >= USB_SPEED_SUPER)
|
|
ss_monitor->vbus_current = USB_CURRENT_SUPER_SPEED;
|
|
else
|
|
ss_monitor->vbus_current = USB_CURRENT_HIGH_SPEED;
|
|
schedule_work(&ss_monitor->set_vbus_current_work);
|
|
#endif
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=ENUM:SET:CON", NULL);
|
|
#endif
|
|
if (ss_monitor->usb_function_info & GADGET_ACCESSORY) {
|
|
g_ss_monitor->aoa_reset.acc_online = true;
|
|
g_ss_monitor->aoa_reset.rst_err_cnt = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
unknown:
|
|
return value;
|
|
}
|
|
|
|
void make_suspend_current_event(void)
|
|
{
|
|
if (!g_ss_monitor)
|
|
return;
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
if (g_ss_monitor->ss_monitor && g_ss_monitor->ss_monitor->is_bind) {
|
|
g_ss_monitor->ss_monitor->vbus_current = USB_CURRENT_SUSPENDED;
|
|
schedule_work(&g_ss_monitor->ss_monitor->set_vbus_current_work);
|
|
}
|
|
#else
|
|
pr_info("usb: %s : usb suspend irq\n", __func__);
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL(make_suspend_current_event);
|
|
static void ss_monitor_suspend(struct usb_function *f)
|
|
{
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
struct f_ss_monitor *ss_monitor;
|
|
|
|
pr_info("usb: %s : usb suspend enter\n", __func__);
|
|
ss_monitor = func_to_ss_monitor(f);
|
|
ss_monitor->vbus_current = USB_CURRENT_SUSPENDED;
|
|
schedule_work(&ss_monitor->set_vbus_current_work);
|
|
#else
|
|
pr_info("usb: %s : usb suspend enter--\n", __func__);
|
|
#endif
|
|
}
|
|
static void ss_monitor_resume(struct usb_function *f)
|
|
{
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
struct f_ss_monitor *ss_monitor;
|
|
struct usb_composite_dev *cdev = f->config->cdev;
|
|
|
|
pr_info("usb: %s : usb suspend exit\n", __func__);
|
|
ss_monitor = func_to_ss_monitor(f);
|
|
if (cdev->gadget->speed >= USB_SPEED_SUPER)
|
|
ss_monitor->vbus_current = USB_CURRENT_SUPER_SPEED;
|
|
else
|
|
ss_monitor->vbus_current = USB_CURRENT_HIGH_SPEED;
|
|
schedule_work(&ss_monitor->set_vbus_current_work);
|
|
#else
|
|
pr_info("usb: %s : usb suspend exit --\n", __func__);
|
|
#endif
|
|
}
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
static void update_usb_gadet_function(char *f_name, struct f_ss_monitor *p_monitor)
|
|
{
|
|
if (!strcmp(f_name, "mtp")) {
|
|
p_monitor->usb_function_info |= GADGET_MTP;
|
|
goto ret;
|
|
}
|
|
if (!strcmp(f_name, "acm")) {
|
|
p_monitor->usb_function_info |= GADGET_ACM;
|
|
goto ret;
|
|
}
|
|
if (!strcmp(f_name, "conn_gadget")) {
|
|
p_monitor->usb_function_info |= GADGET_CONN_GADGET;
|
|
goto ret;
|
|
}
|
|
|
|
if (!strcmp(f_name, "Function FS Gadget") || !strcmp(f_name, "adb")) {
|
|
p_monitor->usb_function_info |= GADGET_ADB;
|
|
goto ret;
|
|
}
|
|
if (!strcmp(f_name, "rndis")) {
|
|
p_monitor->usb_function_info |= GADGET_RNDIS;
|
|
goto ret;
|
|
}
|
|
if (!strcmp(f_name, "accessory")) {
|
|
p_monitor->usb_function_info |= GADGET_ACCESSORY;
|
|
goto ret;
|
|
}
|
|
if (!strcmp(f_name, "gmidi function")) {
|
|
p_monitor->usb_function_info |= GADGET_MIDI;
|
|
goto ret;
|
|
}
|
|
if (!strcmp(f_name, "dm")) {
|
|
p_monitor->usb_function_info |= GADGET_DM;
|
|
goto ret;
|
|
}
|
|
ret:
|
|
return;
|
|
}
|
|
|
|
static int copy_usb_mode(char *f_name, char *usb_mode)
|
|
{
|
|
int length;
|
|
|
|
length = strlen(f_name);
|
|
if (length > 3) {
|
|
length = 3;
|
|
strncat(usb_mode, f_name, 3);
|
|
} else {
|
|
strncat(usb_mode, f_name, length);
|
|
}
|
|
length += 1;
|
|
strcat(usb_mode, ",");
|
|
|
|
return length;
|
|
}
|
|
|
|
static int usb_configuration_name(struct usb_configuration *config, struct usb_function *f_ss)
|
|
{
|
|
struct usb_function *f;
|
|
struct ss_monitor_instance *opts;
|
|
int length = 0;
|
|
char *f_name;
|
|
int name_len;
|
|
int use_ffs_mtp = 0;
|
|
struct f_ss_monitor *ss_monitor = func_to_ss_monitor(f_ss);
|
|
|
|
opts = container_of(f_ss->fi, struct ss_monitor_instance, func_inst);
|
|
|
|
if (!strcmp(opts->name, "mtp") || !strcmp(opts->name, "ptp"))
|
|
use_ffs_mtp = 1;
|
|
|
|
list_for_each_entry(f, &config->functions, list) {
|
|
if (!strcmp(f->name, "ss_mon")) {
|
|
break;
|
|
} else if (!strcmp(f->name, "Function FS Gadget")) {
|
|
if (use_ffs_mtp) {
|
|
use_ffs_mtp = 0;
|
|
name_len = strlen(opts->name);
|
|
f_name = kstrndup(opts->name, name_len, GFP_KERNEL);
|
|
if (!f_name)
|
|
return -ENOMEM;
|
|
} else {
|
|
name_len = 3;
|
|
f_name = kmemdup_nul("adb", name_len, GFP_KERNEL);
|
|
if (!f_name)
|
|
return -ENOMEM;
|
|
}
|
|
} else {
|
|
if (!strcmp(f->name, "mtp") || !strcmp(f->name, "ptp"))
|
|
use_ffs_mtp = 0;
|
|
name_len = strlen(f->name);
|
|
if (name_len > MAX_INST_NAME_LEN)
|
|
return -ENAMETOOLONG;
|
|
|
|
f_name = kstrndup(f->name, name_len, GFP_KERNEL);
|
|
if (!f_name)
|
|
return -ENOMEM;
|
|
}
|
|
update_usb_gadet_function(f_name, ss_monitor);
|
|
if (name_len > 3)
|
|
name_len = 3;
|
|
if (length + name_len > sizeof(ss_monitor->usb_mode)) {
|
|
pr_info("usb: %s : overflow usb mode buffer\n", __func__);
|
|
break;
|
|
}
|
|
length += copy_usb_mode(f_name, ss_monitor->usb_mode);
|
|
kfree(f_name);
|
|
}
|
|
|
|
if (length)
|
|
ss_monitor->usb_mode[length-1] = 0;
|
|
else
|
|
strncat(ss_monitor->usb_mode, "func:empty", 11);
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
store_usblog_notify(NOTIFY_USBMODE_EXTRA, (void *)ss_monitor->usb_mode, NULL);
|
|
#endif
|
|
pr_info("usb: %s : ss_mon: usb_mode: %s\n", __func__, ss_monitor->usb_mode);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
ss_monitor_bind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
struct f_ss_monitor *ss_monitor = func_to_ss_monitor(f);
|
|
struct ss_monitor_instance *opts;
|
|
int rc = -1;
|
|
|
|
opts = container_of(f->fi, struct ss_monitor_instance, func_inst);
|
|
if (!strcmp(opts->name, "mtp") || !strcmp(opts->name, "ptp")) {
|
|
/* copy descriptors, and track endpoint copies */
|
|
f->fs_descriptors = usb_copy_descriptors(log_fs_function);
|
|
|
|
/* support all relevant hardware speeds... we expect that when
|
|
* hardware is dual speed, all bulk-capable endpoints work at
|
|
* both speeds
|
|
*/
|
|
if (gadget_is_dualspeed(c->cdev->gadget)) {
|
|
/* copy descriptors, and track endpoint copies */
|
|
f->hs_descriptors = usb_copy_descriptors(log_hs_function);
|
|
}
|
|
|
|
if (gadget_is_superspeed(c->cdev->gadget)) {
|
|
/* copy descriptors, and track endpoint copies */
|
|
f->ss_descriptors = usb_copy_descriptors(log_ss_function);
|
|
if (!f->ss_descriptors)
|
|
goto fail;
|
|
|
|
/* copy descriptors, and track endpoint copies for SSP */
|
|
f->ssp_descriptors = usb_copy_descriptors(log_ss_function);
|
|
if (!f->ssp_descriptors)
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
/* save usb mode information */
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
usb_configuration_name(c, f);
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=PULLUP:EN:SUCCESS", NULL);
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
INIT_WORK(&ss_monitor->set_vbus_current_work, set_vbus_current_work);
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER)
|
|
set_usb_enable_state();
|
|
#endif
|
|
f->setup = ss_monitor_setup;
|
|
ss_monitor->is_bind = 1;
|
|
if (!strcmp(opts->name, "mtp") || !strcmp(opts->name, "ptp"))
|
|
pr_info("usb: [%s] ss_mon.%s bind\n", __func__, opts->name);
|
|
else
|
|
pr_info("usb: [%s] ss_mon.%s bind: skip descriptor\n",
|
|
__func__, opts->name);
|
|
return 0;
|
|
|
|
fail:
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=PULLUP:EN:FAIL", NULL);
|
|
#endif
|
|
pr_err("usb: [%s] ss_mon.%s bind fail\n", __func__, opts->name);
|
|
return rc;
|
|
|
|
}
|
|
|
|
static void
|
|
ss_monitor_unbind(struct usb_configuration *c, struct usb_function *f)
|
|
{
|
|
struct f_ss_monitor *ss_monitor = func_to_ss_monitor(f);
|
|
struct ss_monitor_instance *opts;
|
|
|
|
opts = container_of(f->fi, struct ss_monitor_instance, func_inst);
|
|
f->setup = NULL;
|
|
#if defined(CONFIG_USB_NOTIFY_PROC_LOG)
|
|
store_usblog_notify(NOTIFY_USBSTATE,
|
|
(void *)"USB_STATE=PULLUP:DIS", NULL);
|
|
#endif
|
|
opts->aoa_reset.rst_err_cnt = 0;
|
|
ss_monitor->usb_function_info = 0;
|
|
ss_monitor->is_bind = 0;
|
|
|
|
cancel_delayed_work_sync(&opts->aoa_reset.usb_reset_event_work);
|
|
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
|
|
flush_work(&ss_monitor->set_vbus_current_work);
|
|
#endif
|
|
pr_info("usb: %s: ss_mon.%s unbind\n", __func__, opts->name);
|
|
}
|
|
|
|
static struct ss_monitor_instance *to_ss_monitor_instance(struct config_item *item)
|
|
{
|
|
return container_of(to_config_group(item), struct ss_monitor_instance,
|
|
func_inst.group);
|
|
}
|
|
|
|
static void ss_monitor_attr_release(struct config_item *item)
|
|
{
|
|
struct ss_monitor_instance *fi_ss_monitor = to_ss_monitor_instance(item);
|
|
|
|
usb_put_function_instance(&fi_ss_monitor->func_inst);
|
|
}
|
|
|
|
static struct configfs_item_operations ss_monitor_item_ops = {
|
|
.release = ss_monitor_attr_release,
|
|
};
|
|
|
|
static struct config_item_type ss_monitor_func_type = {
|
|
.ct_item_ops = &ss_monitor_item_ops,
|
|
.ct_owner = THIS_MODULE,
|
|
};
|
|
|
|
static int ss_monitor_set_inst_name(struct usb_function_instance *fi, const char *name)
|
|
{
|
|
struct ss_monitor_instance *fi_ss_monitor;
|
|
char *ptr;
|
|
int name_len;
|
|
|
|
name_len = strlen(name) + 1;
|
|
if (name_len > MAX_INST_NAME_LEN)
|
|
return -ENAMETOOLONG;
|
|
|
|
ptr = kstrndup(name, name_len, GFP_KERNEL);
|
|
if (!ptr)
|
|
return -ENOMEM;
|
|
|
|
fi_ss_monitor = to_fi_ss_monitor(fi);
|
|
fi_ss_monitor->name = ptr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ss_monitor_free_inst(struct usb_function_instance *fi)
|
|
{
|
|
struct ss_monitor_instance *fi_ss_monitor;
|
|
|
|
fi_ss_monitor = to_fi_ss_monitor(fi);
|
|
if (the_mtpg) {
|
|
device_remove_file(mtpg_device.this_device, &dev_attr_guid);
|
|
misc_deregister(&mtpg_device);
|
|
kfree(the_mtpg);
|
|
the_mtpg = NULL;
|
|
}
|
|
kfree(fi_ss_monitor->name);
|
|
kfree(fi_ss_monitor);
|
|
}
|
|
|
|
static struct usb_function_instance *ss_mon_alloc_inst(void)
|
|
{
|
|
struct ss_monitor_instance *fi_ss_monitor;
|
|
int rc = -ENODEV;
|
|
|
|
/* scenario for MTP */
|
|
if (the_mtpg == NULL) {
|
|
rc = misc_register(&mtpg_device);
|
|
if (rc != 0) {
|
|
pr_err("usb: %s misc_register of mtpg Failed(%d)\n", __func__, rc);
|
|
} else {
|
|
rc = device_create_file(mtpg_device.this_device, &dev_attr_guid);
|
|
if (rc) {
|
|
pr_err("usb: %s failed to create guid attr\n", __func__);
|
|
misc_deregister(&mtpg_device);
|
|
} else {
|
|
the_mtpg = kzalloc(sizeof(*the_mtpg), GFP_KERNEL);
|
|
if (!the_mtpg) {
|
|
device_remove_file(mtpg_device.this_device, &dev_attr_guid);
|
|
misc_deregister(&mtpg_device);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fi_ss_monitor = kzalloc(sizeof(*fi_ss_monitor), GFP_KERNEL);
|
|
if (!fi_ss_monitor)
|
|
goto err_misc_deregister;
|
|
|
|
fi_ss_monitor->func_inst.set_inst_name = ss_monitor_set_inst_name;
|
|
fi_ss_monitor->func_inst.free_func_inst = ss_monitor_free_inst;
|
|
|
|
config_group_init_type_name(&fi_ss_monitor->func_inst.group,
|
|
"", &ss_monitor_func_type);
|
|
|
|
INIT_DELAYED_WORK(&fi_ss_monitor->aoa_reset.usb_reset_event_work, ss_mon_usb_event_work);
|
|
g_ss_monitor = fi_ss_monitor;
|
|
return &fi_ss_monitor->func_inst;
|
|
|
|
err_misc_deregister:
|
|
if (the_mtpg) {
|
|
device_remove_file(mtpg_device.this_device, &dev_attr_guid);
|
|
misc_deregister(&mtpg_device);
|
|
kfree(the_mtpg);
|
|
the_mtpg = NULL;
|
|
}
|
|
pr_err("usb: [%s] ss_mon_alloc_inst fail\n", __func__);
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
}
|
|
|
|
static void ss_monitor_free(struct usb_function *f)
|
|
{
|
|
struct f_ss_monitor *ss_monitor = func_to_ss_monitor(f);
|
|
struct ss_monitor_instance *opts = container_of(f->fi, struct ss_monitor_instance, func_inst);
|
|
|
|
opts->func_inst.f = NULL;
|
|
g_ss_monitor->ss_monitor = NULL;
|
|
kfree(ss_monitor);
|
|
}
|
|
|
|
static struct usb_function *ss_mon_alloc(struct usb_function_instance *fi)
|
|
{
|
|
struct ss_monitor_instance *fi_ss_monitor = to_fi_ss_monitor(fi);
|
|
struct f_ss_monitor *ss_monitor;
|
|
|
|
/* allocate and initialize one new instance */
|
|
ss_monitor = kzalloc(sizeof(*ss_monitor), GFP_KERNEL);
|
|
if (!ss_monitor)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
ss_monitor->function.name = "ss_mon";
|
|
ss_monitor->function.bind = ss_monitor_bind;
|
|
ss_monitor->function.unbind = ss_monitor_unbind;
|
|
ss_monitor->function.set_alt = ss_monitor_set_alt;
|
|
ss_monitor->function.disable = ss_monitor_disable;
|
|
ss_monitor->function.free_func = ss_monitor_free;
|
|
// ss_monitor->function.setup = ss_monitor_setup;
|
|
ss_monitor->function.suspend = ss_monitor_suspend;
|
|
ss_monitor->function.resume = ss_monitor_resume;
|
|
fi_ss_monitor->ss_monitor = ss_monitor;
|
|
fi->f = &ss_monitor->function;
|
|
ss_monitor->func_inst = fi_ss_monitor;
|
|
g_ss_monitor->ss_monitor = ss_monitor;
|
|
|
|
return &ss_monitor->function;
|
|
}
|
|
|
|
DECLARE_USB_FUNCTION_INIT(ss_mon, ss_mon_alloc_inst, ss_mon_alloc);
|
|
MODULE_AUTHOR("Samsung USB Team");
|
|
MODULE_DESCRIPTION("usb device monitor gadget");
|
|
MODULE_LICENSE("GPL"); |