6db4831e98
Android 14
1401 lines
33 KiB
C
1401 lines
33 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* MTK FASTMETA Gadget Driver for Android
|
|
*
|
|
* Copyright (c) 2008 Google, Inc.
|
|
* Copyright (c) 2015 MediaTek Inc.
|
|
* Author: Mike Lockwood <lockwood@android.com>
|
|
* Benoit Goby <benoit@android.com>
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/utsname.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/usb/ch9.h>
|
|
#include <linux/usb/composite.h>
|
|
#include <linux/usb/gadget.h>
|
|
|
|
/* Add for HW/SW connect */
|
|
#include "mtk_gadget.h"
|
|
|
|
#include "function/u_fs.h"
|
|
#include "function/f_mass_storage.h"
|
|
|
|
MODULE_AUTHOR("Mike Lockwood");
|
|
MODULE_DESCRIPTION("Android Composite USB Driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION("2.0");
|
|
|
|
static const char longname[] = "Gadget Android";
|
|
|
|
/* Default vendor and product IDs, overridden by userspace */
|
|
#define VENDOR_ID 0x0E8D
|
|
#define PRODUCT_ID 0x0001
|
|
|
|
#ifdef CONFIG_MTPROF
|
|
#include <linux/bootprof.h>
|
|
#endif
|
|
|
|
static int quick_vcom_num;
|
|
|
|
struct android_usb_function {
|
|
char *name;
|
|
void *config;
|
|
|
|
struct device *dev;
|
|
char *dev_name;
|
|
struct device_attribute **attributes;
|
|
|
|
/* for android_dev.enabled_functions */
|
|
struct list_head enabled_list;
|
|
|
|
/* Optional: initialization during gadget bind */
|
|
int (*init)(struct android_usb_function *f,
|
|
struct usb_composite_dev *cdev);
|
|
/* Optional: cleanup during gadget unbind */
|
|
void (*cleanup)(struct android_usb_function *f);
|
|
/* Optional: called when the function is added the list of
|
|
* enabled functions
|
|
*/
|
|
void (*enable)(struct android_usb_function *f);
|
|
/* Optional: called when it is removed */
|
|
void (*disable)(struct android_usb_function *f);
|
|
|
|
int (*bind_config)(struct android_usb_function *f,
|
|
struct usb_configuration *c);
|
|
|
|
/* Optional: called when the configuration is removed */
|
|
void (*unbind_config)(struct android_usb_function *f,
|
|
struct usb_configuration *c);
|
|
/* Optional: handle ctrl requests before the device is configured */
|
|
int (*ctrlrequest)(struct android_usb_function *f,
|
|
struct usb_composite_dev *cdev,
|
|
const struct usb_ctrlrequest *ctrl_req);
|
|
};
|
|
|
|
struct android_dev {
|
|
struct android_usb_function **functions;
|
|
struct list_head enabled_functions;
|
|
struct usb_composite_dev *cdev;
|
|
struct device *dev;
|
|
|
|
void (*setup_complete)(struct usb_ep *ep,
|
|
struct usb_request *req);
|
|
|
|
bool enabled;
|
|
int disable_depth;
|
|
struct mutex mutex;
|
|
bool connected;
|
|
bool sw_connected;
|
|
struct work_struct work;
|
|
char ffs_aliases[256];
|
|
};
|
|
|
|
static struct class *android_class;
|
|
static struct android_dev *_android_dev;
|
|
static int android_bind_config(struct usb_configuration *c);
|
|
static void android_unbind_config(struct usb_configuration *c);
|
|
static int android_setup_config(struct usb_configuration *c,
|
|
const struct usb_ctrlrequest *ctrl);
|
|
|
|
/* string IDs are assigned dynamically */
|
|
#define STRING_MANUFACTURER_IDX 0
|
|
#define STRING_PRODUCT_IDX 1
|
|
#define STRING_SERIAL_IDX 2
|
|
|
|
static char manufacturer_string[256];
|
|
static char product_string[256];
|
|
static char serial_str[256];
|
|
|
|
/* String Table */
|
|
static struct usb_string strings_dev[] = {
|
|
[STRING_MANUFACTURER_IDX].s = manufacturer_string,
|
|
[STRING_PRODUCT_IDX].s = product_string,
|
|
[STRING_SERIAL_IDX].s = serial_str,
|
|
{ } /* end of list */
|
|
};
|
|
|
|
static struct usb_gadget_strings stringtab_dev = {
|
|
.language = 0x0409, /* en-us */
|
|
.strings = strings_dev,
|
|
};
|
|
|
|
static struct usb_gadget_strings *dev_strings[] = {
|
|
&stringtab_dev,
|
|
NULL,
|
|
};
|
|
|
|
static struct usb_device_descriptor device_desc = {
|
|
.bLength = sizeof(device_desc),
|
|
.bDescriptorType = USB_DT_DEVICE,
|
|
#ifdef CONFIG_USB_MU3D_DRV
|
|
.bcdUSB = cpu_to_le16(0x0300),
|
|
#else
|
|
.bcdUSB = cpu_to_le16(0x0200),
|
|
#endif
|
|
.bDeviceClass = USB_CLASS_PER_INTERFACE,
|
|
.idVendor = cpu_to_le16(VENDOR_ID),
|
|
.idProduct = cpu_to_le16(PRODUCT_ID),
|
|
.bcdDevice = cpu_to_le16(0xffff),
|
|
.bNumConfigurations = 1,
|
|
};
|
|
|
|
static struct usb_configuration android_config_driver = {
|
|
.label = "android",
|
|
.setup = android_setup_config,
|
|
.unbind = android_unbind_config,
|
|
.bConfigurationValue = 1,
|
|
.bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
|
|
#ifdef CONFIG_USB_MU3D_DRV
|
|
/* for passing USB30CV Descriptor Test [USB3.0 devices]*/
|
|
.MaxPower = 192,
|
|
#else
|
|
.MaxPower = 500, /* 500ma */
|
|
#endif
|
|
};
|
|
|
|
#ifdef CONFIG_MTPROF
|
|
static void bootprof_log(char *str)
|
|
{
|
|
static int first_shot = 1;
|
|
|
|
if (first_shot) {
|
|
bootprof_log_boot(str);
|
|
first_shot = 0;
|
|
}
|
|
}
|
|
#else
|
|
static void bootprof_log(char *str) {}
|
|
#endif
|
|
|
|
static void android_work(struct work_struct *data)
|
|
{
|
|
struct android_dev *dev = container_of(data, struct android_dev, work);
|
|
struct usb_composite_dev *cdev = dev->cdev;
|
|
char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL };
|
|
char *connected[2] = { "USB_STATE=CONNECTED", NULL };
|
|
char *configured[2] = { "USB_STATE=CONFIGURED", NULL };
|
|
/* Add for HW/SW connect */
|
|
char **uevent_envp = NULL;
|
|
unsigned long flags;
|
|
|
|
if (!cdev) {
|
|
pr_notice("%s, !cdev\n", __func__);
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&cdev->lock, flags);
|
|
if (cdev->config)
|
|
uevent_envp = configured;
|
|
else if (dev->connected != dev->sw_connected)
|
|
uevent_envp = dev->connected ? connected : disconnected;
|
|
dev->sw_connected = dev->connected;
|
|
spin_unlock_irqrestore(&cdev->lock, flags);
|
|
|
|
if (uevent_envp) {
|
|
kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp);
|
|
pr_notice("%s: sent uevent %s\n", __func__, uevent_envp[0]);
|
|
|
|
if (IS_ENABLED(CONFIG_MTPROF))
|
|
if (uevent_envp == configured)
|
|
bootprof_log("USB configured");
|
|
|
|
} else {
|
|
pr_notice("%s: did not send uevent (%d %d %p)\n", __func__,
|
|
dev->connected, dev->sw_connected, cdev->config);
|
|
}
|
|
}
|
|
|
|
static void android_enable(struct android_dev *dev)
|
|
{
|
|
struct usb_composite_dev *cdev = dev->cdev;
|
|
|
|
|
|
if (WARN_ON(!dev->disable_depth))
|
|
return;
|
|
|
|
if (--dev->disable_depth == 0) {
|
|
usb_add_config(cdev, &android_config_driver,
|
|
android_bind_config);
|
|
usb_gadget_connect(cdev->gadget);
|
|
}
|
|
}
|
|
|
|
static void android_disable(struct android_dev *dev)
|
|
{
|
|
struct usb_composite_dev *cdev = dev->cdev;
|
|
|
|
|
|
if (dev->disable_depth++ == 0) {
|
|
usb_gadget_disconnect(cdev->gadget);
|
|
/* Cancel pending control requests */
|
|
usb_ep_dequeue(cdev->gadget->ep0, cdev->req);
|
|
usb_remove_config(cdev, &android_config_driver);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* Supported functions initialization */
|
|
|
|
/* note all serial port number could not exceed MAX_U_SERIAL_PORTS */
|
|
#define MAX_ACM_INSTANCES 4
|
|
struct acm_function_config {
|
|
int instances;
|
|
int instances_on;
|
|
struct usb_function *f_acm[MAX_ACM_INSTANCES];
|
|
struct usb_function_instance *f_acm_inst[MAX_ACM_INSTANCES];
|
|
int port_index[MAX_ACM_INSTANCES];
|
|
int port_index_on[MAX_ACM_INSTANCES];
|
|
};
|
|
|
|
static int
|
|
acm_function_init(struct android_usb_function *f,
|
|
struct usb_composite_dev *cdev)
|
|
{
|
|
int i;
|
|
int ret;
|
|
struct acm_function_config *config;
|
|
|
|
config = kzalloc(sizeof(struct acm_function_config), GFP_KERNEL);
|
|
if (!config)
|
|
return -ENOMEM;
|
|
f->config = config;
|
|
|
|
for (i = 0; i < MAX_ACM_INSTANCES; i++) {
|
|
config->f_acm_inst[i] = usb_get_function_instance("acm");
|
|
if (IS_ERR(config->f_acm_inst[i])) {
|
|
ret = PTR_ERR(config->f_acm_inst[i]);
|
|
goto err_usb_get_function_instance;
|
|
}
|
|
config->f_acm[i] = usb_get_function(config->f_acm_inst[i]);
|
|
if (IS_ERR(config->f_acm[i])) {
|
|
ret = PTR_ERR(config->f_acm[i]);
|
|
goto err_usb_get_function;
|
|
}
|
|
}
|
|
return 0;
|
|
err_usb_get_function_instance:
|
|
pr_info("Could not usb_get_function_instance() %d\n", i);
|
|
while (i-- > 0) {
|
|
usb_put_function(config->f_acm[i]);
|
|
err_usb_get_function:
|
|
pr_info("Could not usb_get_function() %d\n", i);
|
|
usb_put_function_instance(config->f_acm_inst[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void acm_function_cleanup(struct android_usb_function *f)
|
|
{
|
|
int i;
|
|
struct acm_function_config *config = f->config;
|
|
|
|
for (i = 0; i < MAX_ACM_INSTANCES; i++) {
|
|
usb_put_function(config->f_acm[i]);
|
|
usb_put_function_instance(config->f_acm_inst[i]);
|
|
}
|
|
kfree(f->config);
|
|
f->config = NULL;
|
|
}
|
|
|
|
static int
|
|
acm_function_bind_config(struct android_usb_function *f,
|
|
struct usb_configuration *c)
|
|
{
|
|
int i;
|
|
int ret = 0;
|
|
struct acm_function_config *config = f->config;
|
|
/*1st:Modem, 2nd:Modem, 3rd:BT, 4th:MD logger*/
|
|
for (i = 0; i < MAX_ACM_INSTANCES; i++) {
|
|
if (config->port_index[i] != 0
|
|
|| (quick_vcom_num & (0x1 << i))) {
|
|
ret = usb_add_function(c, config->f_acm[i]);
|
|
if (ret) {
|
|
pr_info("Could not bind acm%u config\n", i);
|
|
goto err_usb_add_function;
|
|
}
|
|
pr_notice("%s Open /dev/ttyGS%d\n", __func__, i);
|
|
config->port_index[i] = 0;
|
|
config->port_index_on[i] = 1;
|
|
config->instances = 0;
|
|
}
|
|
}
|
|
|
|
quick_vcom_num = 0;
|
|
|
|
config->instances_on = config->instances;
|
|
for (i = 0; i < config->instances_on; i++) {
|
|
ret = usb_add_function(c, config->f_acm[i]);
|
|
if (ret) {
|
|
pr_info("Could not bind acm%u config\n", i);
|
|
goto err_usb_add_function;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_usb_add_function:
|
|
while (i-- > 0)
|
|
usb_remove_function(c, config->f_acm[i]);
|
|
return ret;
|
|
}
|
|
|
|
static void acm_function_unbind_config(struct android_usb_function *f,
|
|
struct usb_configuration *c)
|
|
{
|
|
struct acm_function_config *config = f->config;
|
|
|
|
config->instances_on = 0;
|
|
}
|
|
|
|
static struct android_usb_function acm_function = {
|
|
.name = "acm",
|
|
.init = acm_function_init,
|
|
.cleanup = acm_function_cleanup,
|
|
.bind_config = acm_function_bind_config,
|
|
.unbind_config = acm_function_unbind_config,
|
|
};
|
|
|
|
struct mass_storage_function_config {
|
|
struct usb_function *f_ms;
|
|
struct usb_function_instance *f_ms_inst;
|
|
};
|
|
#define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS
|
|
static struct fsg_module_parameters fsg_mod_data;
|
|
FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data);
|
|
|
|
static int mass_storage_function_init(struct android_usb_function *f,
|
|
struct usb_composite_dev *cdev)
|
|
{
|
|
struct mass_storage_function_config *config;
|
|
int ret, i;
|
|
struct fsg_opts *fsg_opts;
|
|
struct fsg_config m_config;
|
|
|
|
|
|
pr_debug("%s(): Inside\n", __func__);
|
|
config = kzalloc(sizeof(struct mass_storage_function_config),
|
|
GFP_KERNEL);
|
|
if (!config)
|
|
return -ENOMEM;
|
|
f->config = config;
|
|
|
|
config->f_ms_inst = usb_get_function_instance("mass_storage");
|
|
if (IS_ERR(config->f_ms_inst)) {
|
|
ret = PTR_ERR(config->f_ms_inst);
|
|
goto err_usb_get_function_instance;
|
|
}
|
|
|
|
config->f_ms = usb_get_function(config->f_ms_inst);
|
|
if (IS_ERR(config->f_ms)) {
|
|
ret = PTR_ERR(config->f_ms);
|
|
goto err_usb_get_function;
|
|
}
|
|
|
|
fsg_mod_data.file_count = 1;
|
|
for (i = 0 ; i < fsg_mod_data.file_count; i++) {
|
|
fsg_mod_data.file[i] = "";
|
|
fsg_mod_data.removable[i] = true;
|
|
fsg_mod_data.nofua[i] = true;
|
|
}
|
|
|
|
fsg_config_from_params(&m_config, &fsg_mod_data, fsg_num_buffers);
|
|
fsg_opts = fsg_opts_from_func_inst(config->f_ms_inst);
|
|
|
|
ret = fsg_common_set_cdev(fsg_opts->common, cdev,
|
|
m_config.can_stall);
|
|
if (ret) {
|
|
pr_info("%s(): error(%d) for fsg_common_set_cdev\n",
|
|
__func__, ret);
|
|
}
|
|
|
|
/* this will affect lun create name */
|
|
fsg_common_set_sysfs(fsg_opts->common, true);
|
|
ret = fsg_common_create_luns(fsg_opts->common, &m_config);
|
|
if (ret) {
|
|
pr_info("%s(): error(%d) for fsg_common_create_luns\n",
|
|
__func__, ret);
|
|
}
|
|
|
|
/* use default one currently */
|
|
fsg_common_set_inquiry_string(fsg_opts->common, m_config.vendor_name,
|
|
m_config.product_name);
|
|
|
|
/* SYSFS create */
|
|
fsg_sysfs_update(fsg_opts->common, f->dev, true);
|
|
|
|
/* invoke thread */
|
|
/* ret = fsg_common_run_thread(fsg_opts->common); */
|
|
/*if (ret) */
|
|
/* return ret; */
|
|
|
|
/* setup this to avoid create fsg thread in fsg_bind again */
|
|
fsg_opts->no_configfs = false;
|
|
|
|
return 0;
|
|
|
|
|
|
err_usb_get_function:
|
|
usb_put_function_instance(config->f_ms_inst);
|
|
|
|
err_usb_get_function_instance:
|
|
return ret;
|
|
}
|
|
|
|
static void mass_storage_function_cleanup(struct android_usb_function *f)
|
|
{
|
|
struct mass_storage_function_config *config = f->config;
|
|
|
|
/* release what we required */
|
|
struct fsg_opts *fsg_opts;
|
|
|
|
|
|
fsg_opts = fsg_opts_from_func_inst(config->f_ms_inst);
|
|
fsg_sysfs_update(fsg_opts->common, f->dev, false);
|
|
|
|
usb_put_function(config->f_ms);
|
|
usb_put_function_instance(config->f_ms_inst);
|
|
|
|
kfree(f->config);
|
|
f->config = NULL;
|
|
}
|
|
|
|
static int mass_storage_function_bind_config(struct android_usb_function *f,
|
|
struct usb_configuration *c)
|
|
{
|
|
struct mass_storage_function_config *config = f->config;
|
|
int ret = 0;
|
|
|
|
|
|
/* no_configfs :true, make fsg_bind skip for creating fsg thread */
|
|
ret = usb_add_function(c, config->f_ms);
|
|
if (ret)
|
|
pr_info("Could not bind config\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* mass_storage: storage_inquiry_show */
|
|
static ssize_t inquiry_string_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct fsg_opts *fsg_opts;
|
|
struct android_usb_function *f = dev_get_drvdata(dev);
|
|
struct mass_storage_function_config *config = f->config;
|
|
|
|
|
|
fsg_opts = fsg_opts_from_func_inst(config->f_ms_inst);
|
|
|
|
return fsg_inquiry_show(fsg_opts->common, buf);
|
|
}
|
|
|
|
/* mass_storage: storage_inquiry_store */
|
|
static ssize_t inquiry_string_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t size)
|
|
{
|
|
struct fsg_opts *fsg_opts;
|
|
struct android_usb_function *f = dev_get_drvdata(dev);
|
|
struct mass_storage_function_config *config = f->config;
|
|
|
|
|
|
fsg_opts = fsg_opts_from_func_inst(config->f_ms_inst);
|
|
|
|
return fsg_inquiry_store(fsg_opts->common, buf, size);
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(inquiry_string);
|
|
|
|
static struct device_attribute *mass_storage_function_attributes[] = {
|
|
&dev_attr_inquiry_string,
|
|
NULL
|
|
};
|
|
|
|
static struct android_usb_function mass_storage_function = {
|
|
.name = "mass_storage",
|
|
.init = mass_storage_function_init,
|
|
.cleanup = mass_storage_function_cleanup,
|
|
.bind_config = mass_storage_function_bind_config,
|
|
.attributes = mass_storage_function_attributes,
|
|
};
|
|
|
|
static struct android_usb_function *supported_functions[] = {
|
|
&acm_function,
|
|
&mass_storage_function,
|
|
NULL
|
|
};
|
|
|
|
static int android_init_functions(struct android_usb_function **functions,
|
|
struct usb_composite_dev *cdev)
|
|
{
|
|
struct android_dev *dev = _android_dev;
|
|
struct android_usb_function *f;
|
|
struct device_attribute **attrs;
|
|
struct device_attribute *attr;
|
|
int err = 0;
|
|
int index = 0;
|
|
|
|
for (; (f = *functions++); index++) {
|
|
f->dev_name = kasprintf(GFP_KERNEL, "f_%s", f->name);
|
|
pr_notice("[USB]%s: f->dev_name = %s, f->name = %s\n",
|
|
__func__, f->dev_name, f->name);
|
|
f->dev = device_create(android_class, dev->dev,
|
|
MKDEV(0, index), f, f->dev_name);
|
|
if (IS_ERR(f->dev)) {
|
|
pr_info("%s: Failed to create dev %s", __func__,
|
|
f->dev_name);
|
|
err = PTR_ERR(f->dev);
|
|
goto err_create;
|
|
}
|
|
|
|
if (f->init) {
|
|
err = f->init(f, cdev);
|
|
if (err) {
|
|
pr_info("%s: Failed to init %s", __func__,
|
|
f->name);
|
|
goto err_out;
|
|
} else
|
|
pr_notice("[USB]%s: init %s success!!\n",
|
|
__func__, f->name);
|
|
}
|
|
|
|
attrs = f->attributes;
|
|
if (attrs) {
|
|
while ((attr = *attrs++) && !err)
|
|
err = device_create_file(f->dev, attr);
|
|
}
|
|
if (err) {
|
|
pr_info("%s: Failed to create function %s attributes",
|
|
__func__, f->name);
|
|
goto err_out;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
err_out:
|
|
device_destroy(android_class, f->dev->devt);
|
|
err_create:
|
|
kfree(f->dev_name);
|
|
return err;
|
|
}
|
|
|
|
static void android_cleanup_functions(struct android_usb_function **functions)
|
|
{
|
|
struct android_usb_function *f;
|
|
|
|
|
|
while (*functions) {
|
|
f = *functions++;
|
|
|
|
if (f->dev) {
|
|
device_destroy(android_class, f->dev->devt);
|
|
kfree(f->dev_name);
|
|
}
|
|
|
|
if (f->cleanup)
|
|
f->cleanup(f);
|
|
}
|
|
}
|
|
|
|
static int
|
|
android_bind_enabled_functions(struct android_dev *dev,
|
|
struct usb_configuration *c)
|
|
{
|
|
struct android_usb_function *f;
|
|
int ret;
|
|
|
|
|
|
list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
|
|
pr_notice("[USB]bind_config function '%s'/%p\n", f->name, f);
|
|
ret = f->bind_config(f, c);
|
|
if (ret) {
|
|
pr_info("%s: %s failed", __func__, f->name);
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
android_unbind_enabled_functions(struct android_dev *dev,
|
|
struct usb_configuration *c)
|
|
{
|
|
struct android_usb_function *f;
|
|
|
|
|
|
list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
|
|
if (f->unbind_config)
|
|
f->unbind_config(f, c);
|
|
}
|
|
}
|
|
|
|
static int android_enable_function(struct android_dev *dev, const char *name)
|
|
{
|
|
struct android_usb_function **functions = dev->functions;
|
|
struct android_usb_function *f;
|
|
|
|
|
|
while ((f = *functions++)) {
|
|
if (!strcmp(name, f->name)) {
|
|
list_add_tail(&f->enabled_list,
|
|
&dev->enabled_functions);
|
|
return 0;
|
|
}
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* /sys/class/android_usb/android%d/ interface */
|
|
|
|
static ssize_t
|
|
functions_show(struct device *pdev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct android_dev *dev = dev_get_drvdata(pdev);
|
|
struct android_usb_function *f;
|
|
char *buff = buf;
|
|
|
|
pr_notice("[USB]%s: ", __func__);
|
|
mutex_lock(&dev->mutex);
|
|
|
|
list_for_each_entry(f, &dev->enabled_functions, enabled_list)
|
|
buff += sprintf(buff, "%s,", f->name);
|
|
|
|
mutex_unlock(&dev->mutex);
|
|
|
|
if (buff != buf)
|
|
*(buff-1) = '\n';
|
|
return buff - buf;
|
|
}
|
|
|
|
static ssize_t
|
|
functions_store(struct device *pdev, struct device_attribute *attr,
|
|
const char *buff, size_t size)
|
|
{
|
|
struct android_dev *dev = dev_get_drvdata(pdev);
|
|
|
|
char *name;
|
|
char buf[256], *b;
|
|
char aliases[256], *a;
|
|
int err;
|
|
int is_ffs;
|
|
int ffs_enabled = 0;
|
|
|
|
mutex_lock(&dev->mutex);
|
|
|
|
if (dev->enabled) {
|
|
mutex_unlock(&dev->mutex);
|
|
return -EBUSY;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&dev->enabled_functions);
|
|
strlcpy(buf, buff, sizeof(buf));
|
|
b = strim(buf);
|
|
|
|
while (b) {
|
|
name = strsep(&b, ",");
|
|
pr_notice("[USB]%s: name = %s\n", __func__, name);
|
|
if (!name)
|
|
continue;
|
|
|
|
is_ffs = 0;
|
|
strlcpy(aliases, dev->ffs_aliases, sizeof(aliases));
|
|
a = aliases;
|
|
|
|
while (a) {
|
|
char *alias = strsep(&a, ",");
|
|
|
|
if (alias && !strcmp(name, alias)) {
|
|
is_ffs = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_ffs) {
|
|
if (ffs_enabled)
|
|
continue;
|
|
err = android_enable_function(dev, "ffs");
|
|
if (err)
|
|
pr_info("android_usb: Cannot enable ffs (%d)",
|
|
err);
|
|
else
|
|
ffs_enabled = 1;
|
|
continue;
|
|
}
|
|
|
|
err = android_enable_function(dev, name);
|
|
if (err)
|
|
pr_info("android_usb: Cannot enable '%s' (%d)",
|
|
name, err);
|
|
}
|
|
|
|
mutex_unlock(&dev->mutex);
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t enable_show(struct device *pdev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct android_dev *dev = dev_get_drvdata(pdev);
|
|
|
|
|
|
return sprintf(buf, "%d\n", dev->enabled);
|
|
}
|
|
|
|
static ssize_t enable_store(struct device *pdev, struct device_attribute *attr,
|
|
const char *buff, size_t size)
|
|
{
|
|
struct android_dev *dev = dev_get_drvdata(pdev);
|
|
|
|
struct usb_composite_dev *cdev = dev->cdev;
|
|
struct android_usb_function *f;
|
|
int enabled = 0;
|
|
int ret;
|
|
|
|
if (!cdev)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&dev->mutex);
|
|
|
|
pr_notice("[USB]%s: %d %d\n", __func__, enabled, dev->enabled);
|
|
|
|
ret = kstrtoint(buff, 0, &enabled);
|
|
|
|
if (enabled && !dev->enabled) {
|
|
/* ALPS01770952
|
|
* Reset next_string_id to 0 before enabling the gadget driver.
|
|
* Otherwise, after a large number of enable/disable cycles,
|
|
* function bind will fail because
|
|
* we cannot allocate new string ids.
|
|
* String ids cannot be larger than 254 per USB spec.
|
|
* 0~15 are reserved for usb device descriptor
|
|
* 16~254 are for functions.
|
|
*/
|
|
cdev->next_string_id = 0x10;
|
|
|
|
/*
|
|
* Update values in composite driver's copy of
|
|
* device descriptor.
|
|
*/
|
|
cdev->desc.idVendor = device_desc.idVendor;
|
|
cdev->desc.idProduct = device_desc.idProduct;
|
|
cdev->desc.bcdDevice = device_desc.bcdDevice;
|
|
cdev->desc.bDeviceClass = device_desc.bDeviceClass;
|
|
cdev->desc.bDeviceSubClass = device_desc.bDeviceSubClass;
|
|
cdev->desc.bDeviceProtocol = device_desc.bDeviceProtocol;
|
|
|
|
/* special case for meta mode */
|
|
if (strlen(serial_str) == 0)
|
|
cdev->desc.iSerialNumber = 0;
|
|
else
|
|
cdev->desc.iSerialNumber = device_desc.iSerialNumber;
|
|
|
|
list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
|
|
if (f->enable)
|
|
f->enable(f);
|
|
}
|
|
android_enable(dev);
|
|
dev->enabled = true;
|
|
pr_notice("[USB]%s: enable 0->1 case, idVendor=0x%x, idProduct=0x%x\n",
|
|
__func__,
|
|
device_desc.idVendor, device_desc.idProduct);
|
|
|
|
if (IS_ENABLED(CONFIG_MTPROF))
|
|
bootprof_log("USB ready");
|
|
|
|
} else if (!enabled && dev->enabled) {
|
|
pr_notice("[USB]%s: enable 1->0 case, idVendor=0x%x, idProduct=0x%x\n",
|
|
__func__,
|
|
device_desc.idVendor, device_desc.idProduct);
|
|
android_disable(dev);
|
|
list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
|
|
pr_notice("[USB]disable function '%s'/%p\n",
|
|
f->name, f);
|
|
if (f->disable)
|
|
f->disable(f);
|
|
}
|
|
dev->enabled = false;
|
|
} else {
|
|
pr_info("android_usb: already %s\n",
|
|
dev->enabled ? "enabled" : "disabled");
|
|
}
|
|
|
|
mutex_unlock(&dev->mutex);
|
|
return size;
|
|
}
|
|
|
|
static ssize_t state_show(struct device *pdev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct android_dev *dev = dev_get_drvdata(pdev);
|
|
struct usb_composite_dev *cdev = dev->cdev;
|
|
char *state = "DISCONNECTED";
|
|
unsigned long flags;
|
|
|
|
if (!cdev)
|
|
goto out;
|
|
|
|
spin_lock_irqsave(&cdev->lock, flags);
|
|
if (cdev->config)
|
|
state = "CONFIGURED";
|
|
else if (dev->connected)
|
|
state = "CONNECTED";
|
|
pr_info("[USB]%s, state:%s\n", __func__, state);
|
|
spin_unlock_irqrestore(&cdev->lock, flags);
|
|
out:
|
|
return sprintf(buf, "%s\n", state);
|
|
}
|
|
|
|
#define LOG_BUG_SZ 2048
|
|
static char log_buf[LOG_BUG_SZ];
|
|
static int log_buf_idx;
|
|
static ssize_t
|
|
log_show(struct device *pdev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct android_dev *dev = dev_get_drvdata(pdev);
|
|
|
|
mutex_lock(&dev->mutex);
|
|
|
|
memcpy(buf, log_buf, log_buf_idx);
|
|
|
|
mutex_unlock(&dev->mutex);
|
|
return log_buf_idx;
|
|
}
|
|
|
|
static ssize_t
|
|
log_store(struct device *pdev, struct device_attribute *attr,
|
|
const char *buff, size_t size)
|
|
{
|
|
struct android_dev *dev = dev_get_drvdata(pdev);
|
|
char buf[256], n;
|
|
|
|
mutex_lock(&dev->mutex);
|
|
|
|
n = strlcpy(buf, buff, sizeof(buf));
|
|
|
|
if ((log_buf_idx + (n + 1)) > LOG_BUG_SZ)
|
|
log_buf_idx = 0;
|
|
|
|
memcpy(log_buf + log_buf_idx, buf, n);
|
|
log_buf_idx += n;
|
|
log_buf[log_buf_idx++] = ' ';
|
|
pr_info("[USB]%s, <%s>, n:%d, log_buf_idx:%d\n",
|
|
__func__, buf, n, log_buf_idx);
|
|
|
|
mutex_unlock(&dev->mutex);
|
|
return size;
|
|
}
|
|
|
|
#define DESCRIPTOR_ATTR(field, format_string) \
|
|
static ssize_t \
|
|
field ## _show(struct device *dev, struct device_attribute *attr, \
|
|
char *buf) \
|
|
{ \
|
|
return sprintf(buf, format_string, device_desc.field); \
|
|
} \
|
|
static ssize_t \
|
|
field ## _store(struct device *dev, struct device_attribute *attr, \
|
|
const char *buf, size_t size) \
|
|
{ \
|
|
int value; \
|
|
if (sscanf(buf, format_string, &value) == 1) { \
|
|
device_desc.field = value; \
|
|
return size; \
|
|
} \
|
|
return -1; \
|
|
} \
|
|
static DEVICE_ATTR(field, 0444, field ## _show, field ## _store)
|
|
|
|
#define DESCRIPTOR_STRING_ATTR(field, buffer) \
|
|
static ssize_t \
|
|
field ## _show(struct device *dev, struct device_attribute *attr, \
|
|
char *buf) \
|
|
{ \
|
|
return sprintf(buf, "%s", buffer); \
|
|
} \
|
|
static ssize_t \
|
|
field ## _store(struct device *dev, struct device_attribute *attr, \
|
|
const char *buf, size_t size) \
|
|
{ \
|
|
if (size >= sizeof(buffer)) \
|
|
return -EINVAL; \
|
|
return strlcpy(buffer, buf, sizeof(buffer)); \
|
|
} \
|
|
static DEVICE_ATTR(field, 0444, field ## _show, field ## _store)
|
|
|
|
|
|
DESCRIPTOR_ATTR(idVendor, "%04x\n");
|
|
DESCRIPTOR_ATTR(idProduct, "%04x\n");
|
|
DESCRIPTOR_ATTR(bcdDevice, "%04x\n");
|
|
DESCRIPTOR_ATTR(bDeviceClass, "%d\n");
|
|
DESCRIPTOR_ATTR(bDeviceSubClass, "%d\n");
|
|
DESCRIPTOR_ATTR(bDeviceProtocol, "%d\n");
|
|
DESCRIPTOR_STRING_ATTR(iManufacturer, manufacturer_string);
|
|
DESCRIPTOR_STRING_ATTR(iProduct, product_string);
|
|
DESCRIPTOR_STRING_ATTR(iSerial, serial_str);
|
|
|
|
static DEVICE_ATTR_RW(functions);
|
|
static DEVICE_ATTR_RW(enable);
|
|
static DEVICE_ATTR_RO(state);
|
|
static DEVICE_ATTR_RW(log);
|
|
|
|
static struct device_attribute *android_usb_attributes[] = {
|
|
&dev_attr_idVendor,
|
|
&dev_attr_idProduct,
|
|
&dev_attr_bcdDevice,
|
|
&dev_attr_bDeviceClass,
|
|
&dev_attr_bDeviceSubClass,
|
|
&dev_attr_bDeviceProtocol,
|
|
&dev_attr_iManufacturer,
|
|
&dev_attr_iProduct,
|
|
&dev_attr_iSerial,
|
|
&dev_attr_functions,
|
|
&dev_attr_enable,
|
|
&dev_attr_state,
|
|
&dev_attr_log,
|
|
NULL
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* Composite driver */
|
|
|
|
static int android_bind_config(struct usb_configuration *c)
|
|
{
|
|
struct android_dev *dev = _android_dev;
|
|
int ret = 0;
|
|
|
|
ret = android_bind_enabled_functions(dev, c);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void android_unbind_config(struct usb_configuration *c)
|
|
{
|
|
struct android_dev *dev = _android_dev;
|
|
|
|
|
|
android_unbind_enabled_functions(dev, c);
|
|
}
|
|
|
|
static int android_setup_config(struct usb_configuration *c,
|
|
const struct usb_ctrlrequest *ctrl)
|
|
{
|
|
int handled = -EINVAL;
|
|
const u8 recip = ctrl->bRequestType & USB_RECIP_MASK;
|
|
|
|
|
|
pr_notice("%s bRequestType=%x, bRequest=%x, recip=%x\n",
|
|
__func__, ctrl->bRequestType, ctrl->bRequest, recip);
|
|
|
|
if (!((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD))
|
|
return handled;
|
|
|
|
switch (ctrl->bRequest) {
|
|
|
|
case USB_REQ_CLEAR_FEATURE:
|
|
switch (recip) {
|
|
case USB_RECIP_DEVICE:
|
|
switch (ctrl->wValue) {
|
|
case USB_DEVICE_U1_ENABLE:
|
|
handled = 1;
|
|
pr_notice("Clear Feature->U1 Enable\n");
|
|
break;
|
|
|
|
case USB_DEVICE_U2_ENABLE:
|
|
handled = 1;
|
|
pr_notice("Clear Feature->U2 Enable\n");
|
|
break;
|
|
|
|
default:
|
|
handled = -EINVAL;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
handled = -EINVAL;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_SET_FEATURE:
|
|
switch (recip) {
|
|
case USB_RECIP_DEVICE:
|
|
switch (ctrl->wValue) {
|
|
case USB_DEVICE_U1_ENABLE:
|
|
pr_notice("Set Feature->U1 Enable\n");
|
|
handled = 1;
|
|
break;
|
|
case USB_DEVICE_U2_ENABLE:
|
|
pr_notice("Set Feature->U2 Enable\n");
|
|
handled = 1;
|
|
break;
|
|
default:
|
|
handled = -EINVAL;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
handled = -EINVAL;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
handled = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
static int android_bind(struct usb_composite_dev *cdev)
|
|
{
|
|
struct android_dev *dev = _android_dev;
|
|
struct usb_gadget *gadget = cdev->gadget;
|
|
int id, ret;
|
|
|
|
/* Save the default handler */
|
|
dev->setup_complete = cdev->req->complete;
|
|
|
|
/*
|
|
* Start disconnected. Userspace will connect the gadget once
|
|
* it is done configuring the functions.
|
|
*/
|
|
usb_gadget_disconnect(gadget);
|
|
|
|
ret = android_init_functions(dev->functions, cdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Allocate string descriptor numbers ... note that string
|
|
* contents can be overridden by the composite_dev glue.
|
|
*/
|
|
id = usb_string_id(cdev);
|
|
if (id < 0)
|
|
return id;
|
|
strings_dev[STRING_MANUFACTURER_IDX].id = id;
|
|
device_desc.iManufacturer = id;
|
|
|
|
id = usb_string_id(cdev);
|
|
if (id < 0)
|
|
return id;
|
|
strings_dev[STRING_PRODUCT_IDX].id = id;
|
|
device_desc.iProduct = id;
|
|
|
|
/* Default strings - should be updated by userspace */
|
|
strncpy(manufacturer_string, "Android", sizeof(manufacturer_string)-1);
|
|
strncpy(product_string, "Android", sizeof(product_string) - 1);
|
|
strncpy(serial_str, "0123456789ABCDEF", sizeof(serial_str) - 1);
|
|
|
|
id = usb_string_id(cdev);
|
|
if (id < 0)
|
|
return id;
|
|
strings_dev[STRING_SERIAL_IDX].id = id;
|
|
device_desc.iSerialNumber = id;
|
|
|
|
usb_gadget_set_selfpowered(gadget);
|
|
|
|
dev->cdev = cdev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int android_usb_unbind(struct usb_composite_dev *cdev)
|
|
{
|
|
struct android_dev *dev = _android_dev;
|
|
|
|
|
|
cancel_work_sync(&dev->work);
|
|
android_cleanup_functions(dev->functions);
|
|
return 0;
|
|
}
|
|
|
|
/* HACK: android needs to override setup for accessory to work */
|
|
static int (*composite_setup_func)(struct usb_gadget *gadget,
|
|
const struct usb_ctrlrequest *c);
|
|
|
|
static int
|
|
android_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *c)
|
|
{
|
|
struct android_dev *dev = _android_dev;
|
|
struct usb_composite_dev *cdev = get_gadget_data(gadget);
|
|
struct usb_request *req = cdev->req;
|
|
struct android_usb_function *f;
|
|
int value = -EOPNOTSUPP;
|
|
unsigned long flags;
|
|
|
|
|
|
req->zero = 0;
|
|
req->length = 0;
|
|
/* no need for FASTMETA */
|
|
/* req->complete = dev->setup_complete; */
|
|
/* req->complete = composite_setup_complete; */
|
|
gadget->ep0->driver_data = cdev;
|
|
|
|
list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
|
|
if (f->ctrlrequest) {
|
|
value = f->ctrlrequest(f, cdev, c);
|
|
if (value >= 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (value < 0)
|
|
value = composite_setup_func(gadget, c);
|
|
|
|
spin_lock_irqsave(&cdev->lock, flags);
|
|
if (!dev->connected) {
|
|
dev->connected = 1;
|
|
schedule_work(&dev->work);
|
|
} else if (c->bRequest == USB_REQ_SET_CONFIGURATION &&
|
|
cdev->config) {
|
|
schedule_work(&dev->work);
|
|
}
|
|
spin_unlock_irqrestore(&cdev->lock, flags);
|
|
|
|
return value;
|
|
}
|
|
|
|
static void android_disconnect(struct usb_composite_dev *cdev)
|
|
{
|
|
struct android_dev *dev = _android_dev;
|
|
|
|
dev->connected = 0;
|
|
schedule_work(&dev->work);
|
|
pr_notice("[USB]%s: dev->connected = %d\n", __func__, dev->connected);
|
|
}
|
|
|
|
static struct usb_composite_driver android_usb_driver = {
|
|
.name = "android_usb",
|
|
.dev = &device_desc,
|
|
.strings = dev_strings,
|
|
.bind = android_bind,
|
|
.unbind = android_usb_unbind,
|
|
.disconnect = android_disconnect,
|
|
#ifdef CONFIG_USB_MU3D_DRV
|
|
.max_speed = USB_SPEED_SUPER
|
|
#else
|
|
.max_speed = USB_SPEED_HIGH
|
|
#endif
|
|
};
|
|
|
|
#define USB_STATE_MONITOR_DELAY 3000
|
|
static struct delayed_work android_usb_state_monitor_work;
|
|
static void do_android_usb_state_monitor_work(struct work_struct *work)
|
|
{
|
|
struct android_dev *dev = _android_dev;
|
|
char *usb_state = "NO-DEV";
|
|
|
|
if (dev && dev->cdev)
|
|
usb_state = "DISCONNECTED";
|
|
|
|
if (dev && dev->cdev && dev->cdev->config)
|
|
usb_state = "CONFIGURED";
|
|
|
|
pr_info("usb_state<%s>\n", usb_state);
|
|
schedule_delayed_work(&android_usb_state_monitor_work,
|
|
msecs_to_jiffies(USB_STATE_MONITOR_DELAY));
|
|
}
|
|
void trigger_android_usb_state_monitor_work(void)
|
|
{
|
|
static int inited;
|
|
|
|
#ifdef CONFIG_FPGA_EARLY_PORTING
|
|
pr_info("SKIP %s\n", __func__);
|
|
return;
|
|
#endif
|
|
if (!inited) {
|
|
/* TIMER_DEFERRABLE for not interfering with deep idle */
|
|
INIT_DEFERRABLE_WORK(&android_usb_state_monitor_work,
|
|
do_android_usb_state_monitor_work);
|
|
inited = 1;
|
|
}
|
|
schedule_delayed_work(&android_usb_state_monitor_work,
|
|
msecs_to_jiffies(USB_STATE_MONITOR_DELAY));
|
|
|
|
};
|
|
|
|
static int android_create_device(struct android_dev *dev)
|
|
{
|
|
struct device_attribute **attrs = android_usb_attributes;
|
|
struct device_attribute *attr;
|
|
int err;
|
|
|
|
dev->dev = device_create(android_class, NULL,
|
|
MKDEV(0, 0), NULL, "android0");
|
|
if (IS_ERR(dev->dev))
|
|
return PTR_ERR(dev->dev);
|
|
|
|
dev_set_drvdata(dev->dev, dev);
|
|
|
|
while ((attr = *attrs++)) {
|
|
err = device_create_file(dev->dev, attr);
|
|
if (err) {
|
|
device_destroy(android_class, dev->dev->devt);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
trigger_android_usb_state_monitor_work();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void enable_meta_vcom(int mode)
|
|
{
|
|
struct android_dev *dev = _android_dev;
|
|
struct device *pdev = dev->dev;
|
|
|
|
enable_store(pdev, NULL, "0", 1);
|
|
|
|
if (mode == 1) {
|
|
strncpy(serial_str, "", sizeof(serial_str) - 1);
|
|
device_desc.idVendor = 0x0e8d;
|
|
device_desc.idProduct = 0x2007;
|
|
device_desc.bDeviceClass = 0x02;
|
|
|
|
/*ttyGS0*/
|
|
quick_vcom_num = (1 << 0);
|
|
|
|
functions_store(pdev, NULL, "acm", 3);
|
|
} else if (mode == 2) {
|
|
|
|
|
|
strncpy(serial_str, "", sizeof(serial_str) - 1);
|
|
device_desc.idVendor = 0x0e8d;
|
|
device_desc.idProduct = 0x202d;
|
|
|
|
/*ttyGS0 + ttyGS3*/
|
|
quick_vcom_num = (1 << 0) + (1 << 3);
|
|
|
|
functions_store(pdev, NULL, "mass_storage,acm", 16);
|
|
}
|
|
|
|
enable_store(pdev, NULL, "1", 1);
|
|
}
|
|
|
|
static const char TAG_NAME[] = "androidboot.usbconfig=";
|
|
|
|
int meta_dt_get_mboot_params(void)
|
|
{
|
|
struct device_node *np_chosen;
|
|
struct tag_bootmode *tag = NULL;
|
|
char *ptr;
|
|
char mode;
|
|
|
|
np_chosen = of_find_node_by_path("/chosen");
|
|
if (!np_chosen)
|
|
np_chosen = of_find_node_by_path("/chosen@0");
|
|
|
|
tag = (struct tag_bootmode *)of_get_property(np_chosen, "atag,boot",
|
|
NULL);
|
|
if (!tag) {
|
|
pr_notice("%s: fail to get atag,boot\n", __func__);
|
|
} else {
|
|
pr_notice("bootmode: 0x%x boottype: 0x%x\n",
|
|
tag->bootmode, tag->boottype);
|
|
|
|
/* check androidboot.usbconfig mode */
|
|
ptr = strstr(saved_command_line, TAG_NAME);
|
|
if (ptr) {
|
|
mode = *(ptr + strlen(TAG_NAME));
|
|
} else {
|
|
pr_notice("cannot find \"androidboot.usbconfig=\"\n");
|
|
return 0;
|
|
}
|
|
|
|
if (mode == '1' || mode == '2') {
|
|
pr_notice("Mediatek FASTMETA mode [%c]\n", mode);
|
|
|
|
if (mode == '1')
|
|
return 1;
|
|
else if (mode == '2')
|
|
return 2;
|
|
}
|
|
pr_notice("not FASTMETA mode [%c]\n", mode);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __init meta_usb_init(void)
|
|
{
|
|
struct android_dev *dev;
|
|
int err;
|
|
int config = 0;
|
|
|
|
config = meta_dt_get_mboot_params();
|
|
|
|
if (!config)
|
|
return 0;
|
|
|
|
pr_info("%s: config %d", __func__, config);
|
|
|
|
android_class = class_create(THIS_MODULE, "android_usb");
|
|
if (IS_ERR(android_class))
|
|
return PTR_ERR(android_class);
|
|
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
|
if (!dev) {
|
|
err = -ENOMEM;
|
|
goto err_dev;
|
|
}
|
|
|
|
dev->disable_depth = 1;
|
|
dev->functions = supported_functions;
|
|
INIT_LIST_HEAD(&dev->enabled_functions);
|
|
INIT_WORK(&dev->work, android_work);
|
|
mutex_init(&dev->mutex);
|
|
|
|
err = android_create_device(dev);
|
|
if (err) {
|
|
pr_info("%s: failed to create android device %d\n",
|
|
__func__, err);
|
|
goto err_create;
|
|
}
|
|
|
|
_android_dev = dev;
|
|
|
|
err = usb_composite_probe(&android_usb_driver);
|
|
if (err) {
|
|
pr_info("%s: failed to probe driver %d\n",
|
|
__func__, err);
|
|
_android_dev = NULL;
|
|
goto err_probe;
|
|
}
|
|
|
|
/* HACK: exchange composite's setup with ours */
|
|
composite_setup_func = android_usb_driver.gadget_driver.setup;
|
|
android_usb_driver.gadget_driver.setup = android_setup;
|
|
|
|
enable_meta_vcom(config);
|
|
|
|
return 0;
|
|
|
|
err_probe:
|
|
device_destroy(android_class, dev->dev->devt);
|
|
err_create:
|
|
kfree(dev);
|
|
err_dev:
|
|
class_destroy(android_class);
|
|
return err;
|
|
}
|
|
late_initcall(meta_usb_init);
|
|
|
|
static void __exit cleanup(void)
|
|
{
|
|
usb_composite_unregister(&android_usb_driver);
|
|
class_destroy(android_class);
|
|
kfree(_android_dev);
|
|
_android_dev = NULL;
|
|
}
|
|
module_exit(cleanup);
|
|
|