android_kernel_samsung_hero.../drivers/usb/gadget/function/f_charger.c

378 lines
9.7 KiB
C
Raw Permalink Normal View History

2016-08-17 10:41:52 +02:00
/*
* f_charger.c -- USB HID function driver
*
* Copyright (C) 2010 Fabien Chouteau <fabien.chouteau@barco.com>
* Copyright (C) 2014, The Linux Foundation. All rights reserved.
*
* 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.
*
* The Linux Foundation chooses to take subject only to the GPLv2 license
* terms, and distributes only under these terms.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/wait.h>
#include <linux/hid.h>
struct f_charger {
struct usb_ep *in_ep;
struct usb_function func;
};
static inline struct f_charger *func_to_charger(struct usb_function *f)
{
return container_of(f, struct f_charger, func);
}
static const uint8_t the_report_descriptor[] = {
0x06, 0xA0, 0xFF, 0x09, 0xA5, 0xA1, 0x01, 0x09,
0xA6, 0x09, 0xA7, 0x15, 0x80, 0x25, 0x7F, 0x75,
0x08, 0x95, 0x02, 0x81, 0x02, 0x09, 0xA9, 0x15,
0x80, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x02, 0x91,
0x02, 0xC0,
};
static struct usb_interface_descriptor charger_interface_desc = {
.bLength = sizeof(charger_interface_desc),
.bDescriptorType = USB_DT_INTERFACE,
/* .bInterfaceNumber = DYNAMIC */
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_HID,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
/* .iInterface = DYNAMIC */
};
static struct hid_descriptor charger_hid_desc = {
.bLength = sizeof(charger_hid_desc),
.bDescriptorType = 0x21,
.bcdHID = 0x0111,
.bCountryCode = 0x00,
.bNumDescriptors = 0x1,
.desc[0].bDescriptorType = 0x22,
.desc[0].wDescriptorLength = sizeof(the_report_descriptor),
};
/* Super-Speed Support */
static struct usb_endpoint_descriptor charger_ss_in_ep_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = 1 ,
.bInterval = 16,
};
static struct usb_ss_ep_comp_descriptor charger_ss_intr_comp_desc = {
.bLength = sizeof(charger_ss_intr_comp_desc),
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
/* the following 2 values can be tweaked if necessary */
/* .bMaxBurst = 0, */
/* .bmAttributes = 0, */
};
static struct usb_descriptor_header *charger_ss_descriptors[] = {
(struct usb_descriptor_header *)&charger_interface_desc,
(struct usb_descriptor_header *)&charger_hid_desc,
(struct usb_descriptor_header *)&charger_ss_in_ep_desc,
(struct usb_descriptor_header *)&charger_ss_intr_comp_desc,
NULL,
};
/* High-Speed Support */
static struct usb_endpoint_descriptor charger_hs_in_ep_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = 1 ,
.bInterval = 16,
};
static struct usb_descriptor_header *charger_hs_descriptors[] = {
(struct usb_descriptor_header *)&charger_interface_desc,
(struct usb_descriptor_header *)&charger_hid_desc,
(struct usb_descriptor_header *)&charger_hs_in_ep_desc,
NULL,
};
/* Full-Speed Support */
static struct usb_endpoint_descriptor charger_fs_in_ep_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = 1,
.bInterval = 16,
};
static struct usb_descriptor_header *charger_fs_descriptors[] = {
(struct usb_descriptor_header *)&charger_interface_desc,
(struct usb_descriptor_header *)&charger_hid_desc,
(struct usb_descriptor_header *)&charger_fs_in_ep_desc,
NULL,
};
/* Strings */
#define CT_FUNC_HID_IDX 0
static struct usb_string ct_func_string_defs[] = {
[CT_FUNC_HID_IDX].s = "HID Interface",
{}, /* end of list */
};
static struct usb_gadget_strings ct_func_string_table = {
.language = 0x0409, /* en-US */
.strings = ct_func_string_defs,
};
static struct usb_gadget_strings *ct_func_strings[] = {
&ct_func_string_table,
NULL,
};
static void charger_disable(struct usb_function *f)
{
struct f_charger *charger = func_to_charger(f);
usb_ep_disable(charger->in_ep);
charger->in_ep->driver_data = NULL;
}
static int hid_setup(struct usb_function *f,
const struct usb_ctrlrequest *ctrl)
{
struct usb_composite_dev *cdev = f->config->cdev;
struct usb_request *req = cdev->req;
int status = 0;
__u16 value, length;
bool resp_stall = false;
value = le16_to_cpu(ctrl->wValue);
length = le16_to_cpu(ctrl->wLength);
VDBG(cdev,
"hid_setup crtl_request : bRequestType:0x%x bRequest:0x%x Value:0x%x\n",
ctrl->bRequestType, ctrl->bRequest, value);
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
| HID_REQ_GET_REPORT):
VDBG(cdev, "get_report\n");
/* send an empty report */
length = min_t(unsigned, length,
charger_hid_desc.desc[0].wDescriptorLength);
memset(req->buf, 0x0, length);
break;
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
| HID_REQ_GET_PROTOCOL):
VDBG(cdev, "get_protocol\n");
resp_stall = true;
break;
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
| HID_REQ_SET_REPORT):
VDBG(cdev, "set_report | wLenght=%d\n", ctrl->wLength);
resp_stall = true;
break;
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
| HID_REQ_SET_PROTOCOL):
VDBG(cdev, "set_protocol\n");
resp_stall = true;
break;
case ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8
| USB_REQ_GET_DESCRIPTOR):
switch (value >> 8) {
case HID_DT_HID:
VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: HID\n");
length = min_t(unsigned short, length,
charger_hid_desc.bLength);
memcpy(req->buf, &charger_hid_desc, length);
break;
case HID_DT_REPORT:
VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: REPORT\n");
length = min_t(unsigned short, length,
charger_hid_desc.desc[0].wDescriptorLength);
memcpy(req->buf, &the_report_descriptor, length);
break;
default:
VDBG(cdev, "Unknown descriptor request 0x%x\n",
value >> 8);
resp_stall = true;
break;
}
break;
default:
VDBG(cdev, "Unknown request 0x%x\n",
ctrl->bRequest);
resp_stall = true;
break;
}
if (resp_stall) {
ERROR(cdev, "usb ep stall\n");
return -EOPNOTSUPP;
}
req->zero = 0;
req->length = length;
status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
if (status < 0)
ERROR(cdev, "usb_ep_queue error on ep0 %d\n", value);
return status;
}
static int charger_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
{
struct usb_composite_dev *cdev = f->config->cdev;
struct f_charger *charger = func_to_charger(f);
int status = 0;
VDBG(cdev, "charger_set_alt intf:%d alt:%d\n", intf, alt);
if (charger->in_ep != NULL) {
/* restart endpoint */
if (charger->in_ep->driver_data != NULL)
usb_ep_disable(charger->in_ep);
status = config_ep_by_speed(f->config->cdev->gadget, f,
charger->in_ep);
if (status) {
charger->in_ep->desc = NULL;
ERROR(cdev, "config_ep_by_speed FAILED!\n");
goto fail;
}
status = usb_ep_enable(charger->in_ep);
if (status < 0) {
ERROR(cdev, "Enable IN endpoint FAILED!\n");
goto fail;
}
charger->in_ep->driver_data = charger;
}
fail:
return status;
}
static int charger_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_ep *ep;
struct f_charger *charger = func_to_charger(f);
int status;
/* allocate instance-specific interface IDs, and patch descriptors */
status = usb_interface_id(c, f);
if (status < 0)
goto fail;
charger_interface_desc.bInterfaceNumber = status;
/* allocate instance-specific endpoints */
status = -ENODEV;
ep = usb_ep_autoconfig(c->cdev->gadget, &charger_fs_in_ep_desc);
if (!ep)
goto fail;
ep->driver_data = c->cdev; /* claim */
charger->in_ep = ep;
/* copy descriptors */
f->fs_descriptors = usb_copy_descriptors(charger_fs_descriptors);
if (!f->fs_descriptors)
goto fail;
if (gadget_is_dualspeed(c->cdev->gadget)) {
charger_hs_in_ep_desc.bEndpointAddress =
charger_fs_in_ep_desc.bEndpointAddress;
f->hs_descriptors =
usb_copy_descriptors(charger_hs_descriptors);
if (!f->hs_descriptors)
goto fail;
}
if (gadget_is_superspeed(c->cdev->gadget)) {
charger_ss_in_ep_desc.bEndpointAddress =
charger_fs_in_ep_desc.bEndpointAddress;
f->ss_descriptors =
usb_copy_descriptors(charger_ss_descriptors);
if (!f->ss_descriptors)
goto fail;
}
return 0;
fail:
ERROR(f->config->cdev, "charger_bind FAILED\n");
if (f->ss_descriptors)
usb_free_descriptors(f->ss_descriptors);
if (f->hs_descriptors)
usb_free_descriptors(f->hs_descriptors);
if (f->fs_descriptors)
usb_free_descriptors(f->fs_descriptors);
return status;
}
static void charger_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct f_charger *charger = func_to_charger(f);
/* disable/free request and end point */
usb_ep_disable(charger->in_ep);
/* free descriptors copies */
if (gadget_is_superspeed(c->cdev->gadget))
usb_free_descriptors(f->ss_descriptors);
if (gadget_is_dualspeed(c->cdev->gadget))
usb_free_descriptors(f->hs_descriptors);
usb_free_descriptors(f->fs_descriptors);
kfree(charger);
}
static int charger_bind_config(struct usb_configuration *c)
{
struct f_charger *charger;
int status;
/* allocate and initialize one new instance */
charger = kzalloc(sizeof(*charger), GFP_KERNEL);
if (!charger)
return -ENOMEM;
charger->func.name = "charging";
charger->func.strings = ct_func_strings;
charger->func.bind = charger_bind;
charger->func.unbind = charger_unbind;
charger->func.set_alt = charger_set_alt;
charger->func.disable = charger_disable;
charger->func.setup = hid_setup;
status = usb_add_function(c, &charger->func);
if (status)
kfree(charger);
return status;
}