linuxOS_D21X/source/uboot-2021.10/drivers/usb/gadget/f_aicupgusb.c
2024-11-29 16:23:11 +08:00

453 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 ArtInChip Technology Co., Ltd
*/
#include <config.h>
#include <common.h>
#include <env.h>
#include <errno.h>
#include <malloc.h>
#include <memalign.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/composite.h>
#include <linux/compiler.h>
#include <version.h>
#include <g_dnl.h>
#include <artinchip/aicupg.h>
#ifdef CONFIG_ARTINCHIP_DEBUG_AICUPG
#undef debug
#define debug printf
#endif
#define USB_UPG_BUF_SIZE (64 * 1024)
#define USB_CBW_LENGTH 31
#define USB_CBW_SIGATURE 0x43425355 /* 'USBC' */
#define USB_DIRECT_IN_FLAG 0x80
#define USB_CSW_SIGNATURE 0x53425355 /* 'USBS' */
#define USB_CSW_LENGTH 13
#define USB_CSW_STATUS_OK 0
#define USB_CSW_STATUS_FAIL 1
#define AICUPG_USB_CMD_WRITE_PKT 0x01
#define AICUPG_USB_CMD_READ_PKT 0x02
struct f_aicupgusb {
struct usb_function usb_function;
struct usb_ep *in_ep, *out_ep;
struct usb_request *in_req, *out_req;
u32 tag;
};
/* Command Block Wrapper */
struct aic_cbw {
__le32 signature; /* Contains 'USBC' */
u32 tag; /* Unique per command id */
__le32 data_transfer_length; /* Size of the data */
u8 flags; /* Direction in bit 7 */
u8 lun; /* LUN (normally 0) */
u8 length; /* Of the CDB, 0x01 */
u8 cmd; /* Command Data Block */
u8 reserved[15];
};
/* Command status Wrapper */
struct aic_csw {
__le32 signature; /* Should = 'USBS' */
u32 tag; /* Same as original command */
__le32 residue; /* Amount not transferred */
u8 status; /* See below */
};
static struct f_aicupgusb *aicupg_func;
static struct usb_endpoint_descriptor fs_ep_in = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(64),
};
static struct usb_endpoint_descriptor fs_ep_out = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(64),
};
static struct usb_endpoint_descriptor hs_ep_in = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(512),
};
static struct usb_endpoint_descriptor hs_ep_out = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = cpu_to_le16(512),
};
static struct usb_interface_descriptor interface_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0x00,
.bAlternateSetting = 0x00,
.bNumEndpoints = 0x02,
.bInterfaceClass = 0xFF,
.bInterfaceSubClass = 0xFF,
.bInterfaceProtocol = 0xFF,
};
static struct usb_descriptor_header *aicupg_fs_function[] = {
(struct usb_descriptor_header *)&interface_desc,
(struct usb_descriptor_header *)&fs_ep_in,
(struct usb_descriptor_header *)&fs_ep_out,
};
static struct usb_descriptor_header *aicupg_hs_function[] = {
(struct usb_descriptor_header *)&interface_desc,
(struct usb_descriptor_header *)&hs_ep_in,
(struct usb_descriptor_header *)&hs_ep_out,
NULL,
};
static struct usb_endpoint_descriptor *
aicupg_ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *fs,
struct usb_endpoint_descriptor *hs)
{
if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
return hs;
return fs;
}
static const char aicupg_manu_name[] = "ArtInChip";
static const char aicupg_prod_name[] = "AIC Device";
static struct usb_string aicupg_string_defs[] = {
[0].s = aicupg_manu_name,
[1].s = aicupg_prod_name,
{ } /* end of list */
};
static struct usb_gadget_strings stringtab_aicupg = {
.language = 0x0409, /* en-us */
.strings = aicupg_string_defs,
};
static struct usb_gadget_strings *aicupg_strings[] = {
&stringtab_aicupg,
NULL,
};
static void aicupg_trans_layer_rx_cbw(struct usb_ep *ep,
struct usb_request *req);
static void aicupg_tx_complete(struct usb_ep *ep, struct usb_request *req);
static inline struct f_aicupgusb *func_to_aicupgusb(struct usb_function *f)
{
return container_of(f, struct f_aicupgusb, usb_function);
}
static int aicupg_bind(struct usb_configuration *c, struct usb_function *f)
{
struct f_aicupgusb *f_upg = func_to_aicupgusb(f);
struct usb_gadget *gadget = c->cdev->gadget;
const char *s;
int id;
id = usb_interface_id(c, f);
if (id < 0)
return id;
interface_desc.bInterfaceNumber = id;
id = usb_string_id(c->cdev);
if (id < 0)
return id;
aicupg_string_defs[0].id = id;
interface_desc.iInterface = id;
f_upg->in_ep = usb_ep_autoconfig(gadget, &fs_ep_in);
if (!f_upg->in_ep) {
pr_err("%s, in ep config failed.\n", __func__);
return -ENODEV;
}
f_upg->in_ep->driver_data = c->cdev;
f_upg->out_ep = usb_ep_autoconfig(gadget, &fs_ep_out);
if (!f_upg->out_ep) {
pr_err("%s, out ep config failed.\n", __func__);
return -ENODEV;
}
f_upg->out_ep->driver_data = c->cdev;
f->descriptors = aicupg_fs_function;
if (gadget_is_dualspeed(gadget)) {
hs_ep_in.bEndpointAddress = fs_ep_in.bEndpointAddress;
hs_ep_out.bEndpointAddress = fs_ep_out.bEndpointAddress;
f->hs_descriptors = aicupg_hs_function;
}
s = env_get("serial#");
if (s)
g_dnl_set_serialnumber((char *)s);
return 0;
}
static void aicupg_unbind(struct usb_configuration *c, struct usb_function *f)
{
memset(aicupg_func, 0, sizeof(*aicupg_func));
}
static void aicupg_disable(struct usb_function *f)
{
struct f_aicupgusb *f_upg = func_to_aicupgusb(f);
usb_ep_disable(f_upg->out_ep);
usb_ep_disable(f_upg->in_ep);
if (f_upg->out_req) {
free(f_upg->out_req->buf);
usb_ep_free_request(f_upg->out_ep, f_upg->out_req);
f_upg->out_req = NULL;
}
if (f_upg->in_req) {
free(f_upg->in_req->buf);
usb_ep_free_request(f_upg->in_ep, f_upg->in_req);
f_upg->in_req = NULL;
}
}
static struct usb_request *aicupg_start_ep(struct usb_ep *ep)
{
struct usb_request *req;
req = usb_ep_alloc_request(ep, 0);
if (!req)
return NULL;
req->length = USB_UPG_BUF_SIZE;
req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, USB_UPG_BUF_SIZE);
if (!req->buf) {
usb_ep_free_request(ep, req);
return NULL;
}
memset(req->buf, 0, req->length);
return req;
}
static int aicupg_set_alt(struct usb_function *f, unsigned interface,
unsigned alt)
{
struct f_aicupgusb *f_upg = func_to_aicupgusb(f);
struct usb_composite_dev *cdev = f->config->cdev;
struct usb_gadget *gadget = cdev->gadget;
const struct usb_endpoint_descriptor *d;
int ret;
debug("%s: func: %s intf: %d alt: %d\n",
__func__, f->name, interface, alt);
d = aicupg_ep_desc(gadget, &fs_ep_out, &hs_ep_out);
ret = usb_ep_enable(f_upg->out_ep, d);
if (ret) {
puts("failed to enable out ep\n");
return ret;
}
f_upg->out_req = aicupg_start_ep(f_upg->out_ep);
if (!f_upg->out_req) {
puts("failed to alloc out req\n");
ret = -EINVAL;
goto err;
}
f_upg->out_req->complete = aicupg_trans_layer_rx_cbw;
d = aicupg_ep_desc(gadget, &fs_ep_in, &hs_ep_in);
ret = usb_ep_enable(f_upg->in_ep, d);
if (ret) {
puts("failed to enable in ep\n");
goto err;
}
f_upg->in_req = aicupg_start_ep(f_upg->in_ep);
if (!f_upg->in_req) {
puts("failed alloc req in\n");
ret = -EINVAL;
goto err;
}
f_upg->in_req->complete = aicupg_tx_complete;
ret = usb_ep_queue(f_upg->out_ep, f_upg->out_req, 0);
if (ret)
goto err;
return 0;
err:
aicupg_disable(f);
return ret;
}
static void aicupg_tx_complete(struct usb_ep *in_ep, struct usb_request *in_req)
{
int status = in_req->status;
if (!status)
return;
debug("status: %d ep '%s' trans: %d\n", status, in_ep->name,
in_req->actual);
}
static int aicupg_trans_layer_send_csw(u32 tag, int residue, u8 status)
{
struct usb_request *in_req = aicupg_func->in_req;
struct usb_ep *in_ep = aicupg_func->in_ep;
struct aic_csw csw;
int ret;
debug("%s, tag = 0x%x, status = %d\n", __func__, tag, status);
csw.signature = cpu_to_le32(USB_CSW_SIGNATURE);
csw.tag = tag;
csw.residue = cpu_to_be32(residue);
csw.status = status;
memcpy(in_req->buf, &csw, USB_CSW_LENGTH);
in_req->length = USB_CSW_LENGTH;
in_req->complete = aicupg_tx_complete;
usb_ep_dequeue(in_ep, in_req);
ret = usb_ep_queue(in_ep, in_req, 0);
if (ret)
pr_err("Error %d on queue\n", ret);
return ret;
}
static void aicupg_trans_layer_tx_csw(struct usb_ep *in_ep,
struct usb_request *in_req)
{
aicupg_trans_layer_send_csw(aicupg_func->tag, 0, USB_CSW_STATUS_OK);
}
static void aicupg_trans_layer_write_pkt(struct usb_ep *out_ep,
struct usb_request *out_req)
{
debug("%s, actual %d\n", __func__, out_req->actual);
aicupg_data_packet_write(out_req->buf, out_req->actual);
/*
* Host write data packet is ok, now should send out csw.
*/
aicupg_trans_layer_send_csw(aicupg_func->tag, 0, USB_CSW_STATUS_OK);
/*
* Data packet seq is:
* {CBW} {DATA PKT} {CSW} ... {CBW} {DATA PKT} {CSW}
*
* Next rx data should be CBW again
*/
out_req->complete = aicupg_trans_layer_rx_cbw;
out_req->actual = 0;
usb_ep_queue(out_ep, out_req, 0);
}
static void aicupg_trans_layer_read_pkt(struct usb_ep *in_ep,
struct usb_request *in_req)
{
debug("%s, length %d\n", __func__, in_req->length);
aicupg_data_packet_read(in_req->buf, in_req->length);
/* Send data packet, and csw should be sent when data packet is done */
in_req->complete = aicupg_trans_layer_tx_csw;
usb_ep_dequeue(in_ep, in_req);
usb_ep_queue(in_ep, in_req, 0);
}
static void aicupg_trans_layer_rx_cbw(struct usb_ep *out_ep,
struct usb_request *out_req)
{
unsigned int maxpacket, rem;
struct aic_cbw cbw;
if (out_req->status || out_req->length == 0)
return;
memcpy(&cbw, out_req->buf, USB_CBW_LENGTH);
debug("\n%s, tag = 0x%x, data_transfer_length = %d\n", __func__, cbw.tag,
cbw.data_transfer_length);
if (cbw.cmd == AICUPG_USB_CMD_WRITE_PKT) {
/*
* Host write data packet to device, next is going to receive
* one data packet.
*/
if (cbw.data_transfer_length > 0) {
aicupg_func->tag = cbw.tag;
out_req->complete = aicupg_trans_layer_write_pkt;
}
} else if (cbw.cmd == AICUPG_USB_CMD_READ_PKT) {
/*
* Host read data packet from device, here read one pakcet
* data and setup to send out.
*/
if (cbw.data_transfer_length > 0) {
aicupg_func->tag = cbw.tag;
aicupg_func->in_req->length = cbw.data_transfer_length;
aicupg_trans_layer_read_pkt(aicupg_func->in_ep,
aicupg_func->in_req);
}
} else {
pr_err("%s() Invalid CBW.tag %#x, CBW.cmd %#x\n", __func__,
cbw.tag, cbw.cmd);
return;
}
out_req->actual = 0;
/*
* Out ep transfer always expect the length is integral multiple of
* maxpackets.
*/
maxpacket = usb_endpoint_maxp(out_ep->desc);
rem = cbw.data_transfer_length % maxpacket;
out_req->length = cbw.data_transfer_length;
if (rem)
out_req->length += (maxpacket - rem);
if (out_req->length > USB_UPG_BUF_SIZE)
out_req->length = USB_UPG_BUF_SIZE;
usb_ep_queue(out_ep, out_req, 0);
}
static int aicupg_add(struct usb_configuration *c)
{
struct f_aicupgusb *f_upg = aicupg_func;
int ret;
if (!f_upg) {
f_upg = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*f_upg));
if (!f_upg)
return -ENOMEM;
aicupg_func = f_upg;
memset(f_upg, 0, sizeof(*f_upg));
}
f_upg->usb_function.name = "f_aicupg";
f_upg->usb_function.bind = aicupg_bind;
f_upg->usb_function.unbind = aicupg_unbind;
f_upg->usb_function.set_alt = aicupg_set_alt;
f_upg->usb_function.disable = aicupg_disable;
f_upg->usb_function.strings = aicupg_strings;
ret = usb_add_function(c, &f_upg->usb_function);
if (ret) {
free(f_upg);
aicupg_func = NULL;
}
return ret;
}
DECLARE_GADGET_BIND_CALLBACK(usb_dnl_aicupg, aicupg_add);