3910 lines
92 KiB
C
3910 lines
92 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
/*
|
|
* Copyright (c) 2021, Artinchip Technology Co., Ltd
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/usb/of.h>
|
|
|
|
#include <linux/usb/ch9.h>
|
|
#include <linux/usb/gadget.h>
|
|
#include <linux/usb/phy.h>
|
|
#include <linux/usb/composite.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include "aic_udc.h"
|
|
|
|
static inline u32 aic_readl(struct aic_usb_gadget *gg, u32 offset);
|
|
static inline void aic_writel(struct aic_usb_gadget *gg, u32 value,
|
|
u32 offset);
|
|
static void aic_ep0_enqueue_setup(struct aic_usb_gadget *gg);
|
|
static struct usb_request *aic_ep_alloc_request(struct usb_ep *ep,
|
|
gfp_t flags);
|
|
static void aic_ep_free_request(struct usb_ep *ep,
|
|
struct usb_request *req);
|
|
static void aic_ep_complete_request(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *a_ep,
|
|
struct aic_usb_req *a_req,
|
|
int result);
|
|
static int aic_ep_queue_request_nolock(struct usb_ep *ep,
|
|
struct usb_request *req,
|
|
gfp_t gfp_flags);
|
|
static int aic_ep_sethalt_nolock(struct usb_ep *ep, int value, bool now);
|
|
static void aic_ep_start_req(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *a_ep,
|
|
struct aic_usb_req *a_req,
|
|
bool continuing);
|
|
static void aic_ep_start_next_request(struct aic_usb_ep *a_ep);
|
|
static void aic_stall_ep0(struct aic_usb_gadget *gg);
|
|
static void aic_epint_handle_complete_in(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *a_ep);
|
|
static void aic_kill_ep_reqs(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *ep,
|
|
int result);
|
|
|
|
static int aic_ep_enable(struct usb_ep *ep,
|
|
const struct usb_endpoint_descriptor *desc);
|
|
static int aic_ep_disable(struct usb_ep *ep);
|
|
static int aic_ep_disable_nolock(struct usb_ep *ep);
|
|
static int aic_set_test_mode(struct aic_usb_gadget *gg, int testmode);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
|
|
#define print_param(_seq, _ptr, _param) \
|
|
seq_printf((_seq), "%-30s: %d\n", #_param, (_ptr)->_param)
|
|
|
|
#define print_param_hex(_seq, _ptr, _param) \
|
|
seq_printf((_seq), "%-30s: 0x%x\n", #_param, (_ptr)->_param)
|
|
|
|
#define dump_register(nm) \
|
|
{ \
|
|
.name = #nm, \
|
|
.offset = nm, \
|
|
}
|
|
|
|
static const struct debugfs_reg32 aic_udc_regs[] = {
|
|
dump_register(AHBBASIC),
|
|
dump_register(USBDEVINIT),
|
|
dump_register(USBPHYIF),
|
|
dump_register(USBINTSTS),
|
|
dump_register(USBINTMSK),
|
|
dump_register(RXFIFOSTS_DBG),
|
|
dump_register(RXFIFOSIZ),
|
|
dump_register(NPTXFIFOSIZ),
|
|
dump_register(NPTXFIFOSTS),
|
|
dump_register(USBULPIPHY),
|
|
dump_register(TXFIFOSIZ(1)),
|
|
dump_register(TXFIFOSIZ(2)),
|
|
dump_register(USBDEVCONF),
|
|
dump_register(USBDEVFUNC),
|
|
dump_register(USBLINESTS),
|
|
dump_register(INEPINTMSK),
|
|
dump_register(OUTEPINTMSK),
|
|
dump_register(USBEPINT),
|
|
dump_register(USBEPINTMSK),
|
|
dump_register(DTKNQR1),
|
|
dump_register(DTKNQR2),
|
|
dump_register(DTKNQR3),
|
|
dump_register(DTKNQR4),
|
|
dump_register(INEPCFG(0)),
|
|
dump_register(INEPCFG(1)),
|
|
dump_register(INEPCFG(2)),
|
|
dump_register(INEPCFG(3)),
|
|
dump_register(INEPCFG(4)),
|
|
dump_register(OUTEPCFG(0)),
|
|
dump_register(OUTEPCFG(1)),
|
|
dump_register(OUTEPCFG(2)),
|
|
dump_register(OUTEPCFG(3)),
|
|
dump_register(OUTEPCFG(4)),
|
|
dump_register(INEPINT(0)),
|
|
dump_register(INEPINT(1)),
|
|
dump_register(INEPINT(2)),
|
|
dump_register(INEPINT(3)),
|
|
dump_register(INEPINT(4)),
|
|
dump_register(OUTEPINT(0)),
|
|
dump_register(OUTEPINT(1)),
|
|
dump_register(OUTEPINT(2)),
|
|
dump_register(OUTEPINT(3)),
|
|
dump_register(OUTEPINT(4)),
|
|
dump_register(INEPTSFSIZ(0)),
|
|
dump_register(INEPTSFSIZ(1)),
|
|
dump_register(INEPTSFSIZ(2)),
|
|
dump_register(INEPTSFSIZ(3)),
|
|
dump_register(INEPTSFSIZ(4)),
|
|
dump_register(OUTEPTSFSIZ(0)),
|
|
dump_register(OUTEPTSFSIZ(1)),
|
|
dump_register(OUTEPTSFSIZ(2)),
|
|
dump_register(OUTEPTSFSIZ(3)),
|
|
dump_register(OUTEPTSFSIZ(4)),
|
|
dump_register(INEPDMAADDR(0)),
|
|
dump_register(INEPDMAADDR(1)),
|
|
dump_register(INEPDMAADDR(2)),
|
|
dump_register(INEPDMAADDR(3)),
|
|
dump_register(INEPDMAADDR(4)),
|
|
dump_register(OUTEPDMAADDR(0)),
|
|
dump_register(OUTEPDMAADDR(1)),
|
|
dump_register(OUTEPDMAADDR(2)),
|
|
dump_register(OUTEPDMAADDR(3)),
|
|
dump_register(OUTEPDMAADDR(4)),
|
|
dump_register(INEPTXSTS(0)),
|
|
dump_register(INEPTXSTS(1)),
|
|
dump_register(INEPTXSTS(2)),
|
|
dump_register(INEPTXSTS(3)),
|
|
dump_register(INEPTXSTS(4)),
|
|
dump_register(PCGCTL),
|
|
dump_register(UDCVERSION),
|
|
};
|
|
|
|
static ssize_t testmode_write(struct file *file, const char __user *ubuf, size_t
|
|
count, loff_t *ppos)
|
|
{
|
|
struct seq_file *s = file->private_data;
|
|
struct aic_usb_gadget *gg = s->private;
|
|
unsigned long flags;
|
|
u32 testmode = 0;
|
|
char buf[32];
|
|
|
|
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
|
|
return -EFAULT;
|
|
|
|
if (!strncmp(buf, "test_j", 6))
|
|
testmode = USB_TEST_J;
|
|
else if (!strncmp(buf, "test_k", 6))
|
|
testmode = USB_TEST_K;
|
|
else if (!strncmp(buf, "test_se0_nak", 12))
|
|
testmode = USB_TEST_SE0_NAK;
|
|
else if (!strncmp(buf, "test_packet", 11))
|
|
testmode = USB_TEST_PACKET;
|
|
else if (!strncmp(buf, "test_force_enable", 17))
|
|
testmode = USB_TEST_FORCE_ENABLE;
|
|
else
|
|
testmode = 0;
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
aic_set_test_mode(gg, testmode);
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
return count;
|
|
}
|
|
|
|
static int testmode_show(struct seq_file *s, void *unused)
|
|
{
|
|
struct aic_usb_gadget *gg = s->private;
|
|
unsigned long flags;
|
|
int dctl;
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
dctl = aic_readl(gg, USBDEVFUNC);
|
|
dctl &= USBDEVFUNC_TSTCTL_MASK;
|
|
dctl >>= USBDEVFUNC_TSTCTL_SHIFT;
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
|
|
switch (dctl) {
|
|
case 0:
|
|
seq_puts(s, "no test\n");
|
|
break;
|
|
case USB_TEST_J:
|
|
seq_puts(s, "test_j\n");
|
|
break;
|
|
case USB_TEST_K:
|
|
seq_puts(s, "test_k\n");
|
|
break;
|
|
case USB_TEST_SE0_NAK:
|
|
seq_puts(s, "test_se0_nak\n");
|
|
break;
|
|
case USB_TEST_PACKET:
|
|
seq_puts(s, "test_packet\n");
|
|
break;
|
|
case USB_TEST_FORCE_ENABLE:
|
|
seq_puts(s, "test_force_enable\n");
|
|
break;
|
|
default:
|
|
seq_printf(s, "UNKNOWN %d\n", dctl);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int testmode_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, testmode_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations testmode_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = testmode_open,
|
|
.write = testmode_write,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int ep_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct aic_usb_ep *ep = seq->private;
|
|
struct aic_usb_gadget *gg = ep->parent;
|
|
struct aic_usb_req *req = NULL;
|
|
int index = ep->index;
|
|
int show_limit = 15;
|
|
unsigned long flags;
|
|
|
|
seq_printf(seq, "Endpoint index %d, named %s, dir %s:\n",
|
|
ep->index, ep->ep.name, (ep->dir_in ? "in" : "out"));
|
|
|
|
/* first show the register state */
|
|
seq_printf(seq, "\tINEPCFG=0x%08x, OUTEPCFG=0x%08x\n",
|
|
aic_readl(gg, INEPCFG(index)),
|
|
aic_readl(gg, OUTEPCFG(index)));
|
|
|
|
seq_printf(seq, "\tINEPDMAADDR=0x%08x, OUTEPDMAADDR=0x%08x\n",
|
|
aic_readl(gg, INEPDMAADDR(index)),
|
|
aic_readl(gg, OUTEPDMAADDR(index)));
|
|
|
|
seq_printf(seq, "\tINEPINT=0x%08x, OUTEPINT=0x%08x\n",
|
|
aic_readl(gg, INEPINT(index)),
|
|
aic_readl(gg, OUTEPINT(index)));
|
|
|
|
seq_printf(seq, "\tINEPTSFSIZ=0x%08x, OUTEPTSFSIZ=0x%08x\n",
|
|
aic_readl(gg, INEPTSFSIZ(index)),
|
|
aic_readl(gg, OUTEPTSFSIZ(index)));
|
|
|
|
seq_puts(seq, "\n");
|
|
seq_printf(seq, "mps %d\n", ep->ep.maxpacket);
|
|
seq_printf(seq, "total_data=%ld\n", ep->total_data);
|
|
|
|
seq_printf(seq, "request list (%p,%p):\n",
|
|
ep->queue.next, ep->queue.prev);
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
|
|
list_for_each_entry(req, &ep->queue, queue) {
|
|
if (--show_limit < 0) {
|
|
seq_puts(seq, "not showing more requests...\n");
|
|
break;
|
|
}
|
|
|
|
seq_printf(seq, "%c req %p: %d bytes @%p, ",
|
|
req == ep->req ? '*' : ' ',
|
|
req, req->req.length, req->req.buf);
|
|
seq_printf(seq, "%d done, res %d\n",
|
|
req->req.actual, req->req.status);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SHOW_ATTRIBUTE(ep);
|
|
|
|
static int fifo_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct aic_usb_gadget *gg = seq->private;
|
|
u32 val;
|
|
int idx;
|
|
|
|
seq_puts(seq, "Non-periodic FIFOs:\n");
|
|
seq_printf(seq, "RXFIFO: Size %d\n", aic_readl(gg, RXFIFOSIZ));
|
|
|
|
val = aic_readl(gg, NPTXFIFOSIZ);
|
|
seq_printf(seq, "NPTXFIFO: Size %d, Start 0x%08x\n",
|
|
val >> FIFOSIZE_DEPTH_SHIFT,
|
|
val & FIFOSIZE_STARTADDR_MASK);
|
|
|
|
seq_puts(seq, "\nPeriodic TXFIFOs:\n");
|
|
|
|
for (idx = 1; idx <= gg->params.num_perio_in_ep; idx++) {
|
|
val = aic_readl(gg, TXFIFOSIZ(idx));
|
|
|
|
seq_printf(seq, "\tDPTXFIFO%2d: Size %d, Start 0x%08x\n", idx,
|
|
val >> FIFOSIZE_DEPTH_SHIFT,
|
|
val & FIFOSIZE_STARTADDR_MASK);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SHOW_ATTRIBUTE(fifo);
|
|
|
|
static int state_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct aic_usb_gadget *gg = seq->private;
|
|
int idx;
|
|
|
|
seq_printf(seq, "USBDEVCONF=0x%08x, USBDEVFUNC=0x%08x, USBLINESTS=0x%08x\n",
|
|
aic_readl(gg, USBDEVCONF),
|
|
aic_readl(gg, USBDEVFUNC),
|
|
aic_readl(gg, USBLINESTS));
|
|
|
|
seq_printf(seq, "INEPINTMSK=0x%08x, DOEPMASK=0x%08x\n",
|
|
aic_readl(gg, INEPINTMSK), aic_readl(gg, OUTEPINTMSK));
|
|
|
|
seq_printf(seq, "USBINTMSK=0x%08x, USBINTSTS=0x%08x\n",
|
|
aic_readl(gg, USBINTMSK),
|
|
aic_readl(gg, USBINTSTS));
|
|
|
|
seq_printf(seq, "USBEPINTMSK=0x%08x, USBEPINT=0x%08x\n",
|
|
aic_readl(gg, USBEPINTMSK),
|
|
aic_readl(gg, USBEPINT));
|
|
|
|
seq_printf(seq, "NPTXFIFOSTS=0x%08x, RXFIFOSTS_DBG=%08x\n",
|
|
aic_readl(gg, NPTXFIFOSTS),
|
|
aic_readl(gg, RXFIFOSTS_DBG));
|
|
|
|
seq_puts(seq, "\nEndpoint status:\n");
|
|
|
|
for (idx = 0; idx < gg->params.num_ep; idx++) {
|
|
u32 in, out;
|
|
|
|
in = aic_readl(gg, INEPCFG(idx));
|
|
out = aic_readl(gg, OUTEPCFG(idx));
|
|
|
|
seq_printf(seq, "ep%d: INEPCFG=0x%08x, OUTEPCFG=0x%08x",
|
|
idx, in, out);
|
|
|
|
in = aic_readl(gg, INEPTSFSIZ(idx));
|
|
out = aic_readl(gg, OUTEPTSFSIZ(idx));
|
|
|
|
seq_printf(seq, ", INEPTSFSIZ=0x%08x, OUTEPTSFSIZ=0x%08x",
|
|
in, out);
|
|
|
|
seq_puts(seq, "\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SHOW_ATTRIBUTE(state);
|
|
|
|
static int params_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct aic_usb_gadget *gg = seq->private;
|
|
struct aic_gadget_params *p = &gg->params;
|
|
int i;
|
|
|
|
print_param(seq, p, num_ep);
|
|
print_param(seq, p, num_perio_in_ep);
|
|
print_param(seq, p, total_fifo_size);
|
|
print_param(seq, p, rx_fifo_size);
|
|
print_param(seq, p, np_tx_fifo_size);
|
|
|
|
for (i = 1; i <= PERIOD_IN_EP_NUM; i++) {
|
|
char str[32];
|
|
|
|
snprintf(str, 32, "p_tx_fifo_size[%d]", i);
|
|
seq_printf(seq, "%-30s: %d\n", str, p->p_tx_fifo_size[i]);
|
|
}
|
|
|
|
print_param(seq, p, ep_dirs);
|
|
print_param(seq, p, speed);
|
|
print_param(seq, p, phy_type);
|
|
print_param(seq, p, phy_ulpi_ddr);
|
|
print_param(seq, p, phy_utmi_width);
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SHOW_ATTRIBUTE(params);
|
|
|
|
int aic_udc_debugfs_init(struct aic_usb_gadget *gg)
|
|
{
|
|
struct dentry *root = NULL;
|
|
int i = 0;
|
|
int ret = 0;
|
|
|
|
root = debugfs_create_dir(dev_name(gg->dev), NULL);
|
|
gg->debug_root = root;
|
|
|
|
debugfs_create_file("params", 0444, root, gg, ¶ms_fops);
|
|
/* create general state file */
|
|
debugfs_create_file("state", 0444, root, gg, &state_fops);
|
|
debugfs_create_file("fifo", 0444, root, gg, &fifo_fops);
|
|
debugfs_create_file("testmode", 0644, root, gg, &testmode_fops);
|
|
|
|
/* Create one file for each out endpoint */
|
|
for (i = 0; i < gg->params.num_ep; i++) {
|
|
struct aic_usb_ep *ep;
|
|
|
|
ep = gg->eps_out[i];
|
|
if (ep)
|
|
debugfs_create_file(ep->name, 0444, root, ep, &ep_fops);
|
|
}
|
|
|
|
/* Create one file for each in endpoint. EP0 is handled with out eps */
|
|
for (i = 1; i < gg->params.num_ep; i++) {
|
|
struct aic_usb_ep *ep;
|
|
|
|
ep = gg->eps_in[i];
|
|
if (ep)
|
|
debugfs_create_file(ep->name, 0444, root, ep, &ep_fops);
|
|
}
|
|
|
|
/* regdump */
|
|
gg->regset = devm_kzalloc(gg->dev, sizeof(*gg->regset),
|
|
GFP_KERNEL);
|
|
if (!gg->regset) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
gg->regset->regs = aic_udc_regs;
|
|
gg->regset->nregs = ARRAY_SIZE(aic_udc_regs);
|
|
gg->regset->base = gg->regs;
|
|
debugfs_create_regset32("regdump", 0444, root, gg->regset);
|
|
|
|
return 0;
|
|
err:
|
|
debugfs_remove_recursive(gg->debug_root);
|
|
return ret;
|
|
}
|
|
|
|
void aic_udc_debugfs_exit(struct aic_usb_gadget *gg)
|
|
{
|
|
debugfs_remove_recursive(gg->debug_root);
|
|
gg->debug_root = NULL;
|
|
}
|
|
#else
|
|
static inline int aic_udc_debugfs_init(struct aic_usb_gadget *gg)
|
|
{ return 0; }
|
|
static inline void aic_udc_debugfs_exit(struct aic_usb_gadget *gg)
|
|
{ }
|
|
#endif
|
|
|
|
static inline struct aic_usb_req *our_req(struct usb_request *req)
|
|
{
|
|
return container_of(req, struct aic_usb_req, req);
|
|
}
|
|
|
|
static inline struct aic_usb_ep *our_ep(struct usb_ep *ep)
|
|
{
|
|
return container_of(ep, struct aic_usb_ep, ep);
|
|
}
|
|
|
|
static inline struct aic_usb_gadget *our_gadget(struct usb_gadget *gadget)
|
|
{
|
|
return container_of(gadget, struct aic_usb_gadget, gadget);
|
|
}
|
|
|
|
static inline u32 aic_readl(struct aic_usb_gadget *gg, u32 offset)
|
|
{
|
|
return readl(gg->regs + offset);
|
|
}
|
|
|
|
static inline void aic_writel(struct aic_usb_gadget *gg, u32 value,
|
|
u32 offset)
|
|
{
|
|
writel(value, gg->regs + offset);
|
|
}
|
|
|
|
static inline void aic_readl_rep(struct aic_usb_gadget *gg, u32 offset,
|
|
void *buffer, unsigned int count)
|
|
{
|
|
if (count) {
|
|
u32 *buf = buffer;
|
|
|
|
do {
|
|
u32 x = aic_readl(gg, offset);
|
|
*buf++ = x;
|
|
} while (--count);
|
|
}
|
|
}
|
|
|
|
static inline void aic_writel_rep(struct aic_usb_gadget *gg, u32 offset,
|
|
const void *buffer, unsigned int count)
|
|
{
|
|
if (count) {
|
|
const u32 *buf = buffer;
|
|
|
|
do {
|
|
aic_writel(gg, *buf++, offset);
|
|
} while (--count);
|
|
}
|
|
}
|
|
|
|
static inline void aic_set_bit(struct aic_usb_gadget *gg, u32 offset, u32 val)
|
|
{
|
|
aic_writel(gg, aic_readl(gg, offset) | val, offset);
|
|
}
|
|
|
|
static inline void aic_clear_bit(struct aic_usb_gadget *gg, u32 offset, u32 val)
|
|
{
|
|
aic_writel(gg, aic_readl(gg, offset) & ~val, offset);
|
|
}
|
|
|
|
int aic_wait_bit_clear(struct aic_usb_gadget *gg, u32 offset, u32 mask,
|
|
u32 timeout)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < timeout; i++) {
|
|
if (!(aic_readl(gg, offset) & mask))
|
|
return 0;
|
|
udelay(1);
|
|
}
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
int aic_wait_bit_set(struct aic_usb_gadget *gg, u32 offset, u32 mask,
|
|
u32 timeout)
|
|
{
|
|
u32 i;
|
|
|
|
for (i = 0; i < timeout; i++) {
|
|
if (aic_readl(gg, offset) & mask)
|
|
return 0;
|
|
udelay(1);
|
|
}
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static void aic_en_gsint(struct aic_usb_gadget *gg, u32 ints)
|
|
{
|
|
u32 gsintmsk = aic_readl(gg, USBINTMSK);
|
|
u32 new_gsintmsk;
|
|
|
|
new_gsintmsk = gsintmsk | ints;
|
|
|
|
if (new_gsintmsk != gsintmsk) {
|
|
dev_dbg(gg->dev, "gsintmsk now 0x%08x\n", new_gsintmsk);
|
|
aic_writel(gg, new_gsintmsk, USBINTMSK);
|
|
}
|
|
}
|
|
|
|
static void aic_dis_gsint(struct aic_usb_gadget *gg, u32 ints)
|
|
{
|
|
u32 gsintmsk = aic_readl(gg, USBINTMSK);
|
|
u32 new_gsintmsk;
|
|
|
|
new_gsintmsk = gsintmsk & ~ints;
|
|
|
|
if (new_gsintmsk != gsintmsk)
|
|
aic_writel(gg, new_gsintmsk, USBINTMSK);
|
|
}
|
|
|
|
static void aic_ctrl_epint(struct aic_usb_gadget *gg,
|
|
unsigned int ep, unsigned int dir_in,
|
|
unsigned int en)
|
|
{
|
|
unsigned long flags;
|
|
u32 bit = 1 << ep;
|
|
u32 daint;
|
|
|
|
if (!dir_in)
|
|
bit <<= 16;
|
|
|
|
local_irq_save(flags);
|
|
daint = aic_readl(gg, USBEPINTMSK);
|
|
if (en)
|
|
daint |= bit;
|
|
else
|
|
daint &= ~bit;
|
|
aic_writel(gg, daint, USBEPINTMSK);
|
|
local_irq_restore(flags);
|
|
}
|
|
|
|
static int aic_low_hw_enable(struct aic_usb_gadget *gg)
|
|
{
|
|
int ret = 0;
|
|
int i = 0;
|
|
|
|
/* enable regulator */
|
|
/* enable clock */
|
|
for (i = 0; i < USB_MAX_CLKS_RSTS && gg->clks[i]; i++)
|
|
ret = clk_prepare_enable(gg->clks[i]);
|
|
/* wait USB-PHY work stable */
|
|
udelay(200);
|
|
|
|
/* reset deassert */
|
|
reset_control_deassert(gg->reset);
|
|
reset_control_deassert(gg->reset_ecc);
|
|
udelay(200);
|
|
|
|
/* enable phy */
|
|
if (gg->uphy) {
|
|
ret = usb_phy_init(gg->uphy);
|
|
} else {
|
|
ret = phy_power_on(gg->phy);
|
|
if (ret == 0)
|
|
ret = phy_init(gg->phy);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aic_low_hw_disable(struct aic_usb_gadget *gg)
|
|
{
|
|
int ret = 0;
|
|
int i = 0;
|
|
|
|
/* disable phy */
|
|
if (gg->uphy) {
|
|
usb_phy_shutdown(gg->uphy);
|
|
} else {
|
|
ret = phy_exit(gg->phy);
|
|
if (ret == 0)
|
|
ret = phy_power_off(gg->phy);
|
|
else
|
|
return ret;
|
|
}
|
|
|
|
/* reset assert */
|
|
reset_control_assert(gg->reset);
|
|
reset_control_assert(gg->reset_ecc);
|
|
|
|
/* disable clock */
|
|
for (i = 0; i < USB_MAX_CLKS_RSTS && gg->clks[i]; i++)
|
|
clk_disable_unprepare(gg->clks[i]);
|
|
|
|
/* disable regulator */
|
|
|
|
return ret;
|
|
}
|
|
|
|
static u32 aic_read_frameno(struct aic_usb_gadget *gg)
|
|
{
|
|
u32 reg = 0;
|
|
|
|
reg = aic_readl(gg, USBLINESTS);
|
|
reg &= USBLINESTS_SOFFN_MASK;
|
|
reg >>= USBLINESTS_SOFFN_SHIFT;
|
|
|
|
return reg;
|
|
}
|
|
|
|
static bool aic_target_frame_elapsed(struct aic_usb_ep *a_ep)
|
|
{
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
u32 target_frame = a_ep->target_frame;
|
|
u32 current_frame = gg->frame_number;
|
|
bool frame_overrun = a_ep->frame_overrun;
|
|
|
|
if (!frame_overrun && current_frame >= target_frame)
|
|
return true;
|
|
|
|
if (frame_overrun && current_frame >= target_frame &&
|
|
((current_frame - target_frame) < USBLINESTS_SOFFN_LIMIT / 2))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline void aic_incr_frame_num(struct aic_usb_ep *a_ep)
|
|
{
|
|
a_ep->target_frame += a_ep->interval;
|
|
if (a_ep->target_frame > USBLINESTS_SOFFN_LIMIT) {
|
|
a_ep->frame_overrun = true;
|
|
a_ep->target_frame &= USBLINESTS_SOFFN_LIMIT;
|
|
} else {
|
|
a_ep->frame_overrun = false;
|
|
}
|
|
}
|
|
|
|
static void aic_set_nextep(struct aic_usb_gadget *gg)
|
|
{
|
|
int i;
|
|
u32 reg = 0;
|
|
|
|
/* dma to set the next-endpoint pointer. */
|
|
for (i = 0; i < gg->params.num_ep; i++) {
|
|
u32 next = EPCTL_NEXTEP((i + 1) % 15);
|
|
|
|
if (gg->eps_in[i]) {
|
|
reg = aic_readl(gg, INEPCFG(i));
|
|
reg &= ~EPCTL_NEXTEP_MASK;
|
|
reg |= next;
|
|
aic_writel(gg, reg, INEPCFG(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
static int aic_core_rst(struct aic_usb_gadget *gg)
|
|
{
|
|
u32 reset;
|
|
|
|
/* Core Soft Reset */
|
|
reset = aic_readl(gg, USBDEVINIT);
|
|
reset |= USBDEVINIT_CSFTRST;
|
|
aic_writel(gg, reset, USBDEVINIT);
|
|
|
|
if (aic_wait_bit_clear(gg, USBDEVINIT, USBDEVINIT_CSFTRST, 10000)) {
|
|
dev_warn(gg->dev, "%s: HANG! Soft Reset timeout GRSTCTL GRSTCTL_CSFTRST\n",
|
|
__func__);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Wait for AHB master IDLE state */
|
|
if (aic_wait_bit_set(gg, AHBBASIC, AHBBASIC_AHBIDLE, 10000)) {
|
|
dev_warn(gg->dev, "%s: HANG! AHB Idle timeout GRSTCTL GRSTCTL_AHBIDLE\n",
|
|
__func__);
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_hs_phy_init(struct aic_usb_gadget *gg)
|
|
{
|
|
u32 phyif, phyif_old;
|
|
int ret = 0;
|
|
|
|
phyif = aic_readl(gg, USBPHYIF);
|
|
phyif_old = phyif;
|
|
|
|
switch (gg->params.phy_type) {
|
|
case AIC_PHY_TYPE_PARAM_ULPI:
|
|
/* ULPI interface */
|
|
dev_dbg(gg->dev, "HS ULPI PHY selected\n");
|
|
phyif |= USBPHYIF_ULPI_UTMI_SEL;
|
|
phyif &= ~(USBPHYIF_PHYIF16 | USBPHYIF_DDRSEL);
|
|
if (gg->params.phy_ulpi_ddr)
|
|
phyif |= USBPHYIF_DDRSEL;
|
|
break;
|
|
case AIC_PHY_TYPE_PARAM_UTMI:
|
|
/* UTMI+ interface */
|
|
dev_dbg(gg->dev, "HS UTMI+ PHY selected\n");
|
|
phyif &= ~(USBPHYIF_ULPI_UTMI_SEL | USBPHYIF_PHYIF16);
|
|
if (gg->params.phy_utmi_width == 16)
|
|
phyif |= USBPHYIF_PHYIF16;
|
|
|
|
phyif &= ~USBPHYIF_USBTRDTIM_MASK;
|
|
if (gg->params.phy_utmi_width == 16)
|
|
phyif |= 5 << USBPHYIF_USBTRDTIM_SHIFT;
|
|
else
|
|
phyif |= 9 << USBPHYIF_USBTRDTIM_SHIFT;
|
|
break;
|
|
default:
|
|
dev_err(gg->dev, "FS PHY selected at HS!\n");
|
|
break;
|
|
}
|
|
|
|
if (phyif != phyif_old) {
|
|
aic_writel(gg, phyif, USBPHYIF);
|
|
|
|
/* Reset after setting the PHY parameters */
|
|
ret = aic_core_rst(gg);
|
|
if (ret) {
|
|
dev_err(gg->dev,
|
|
"%s: Reset failed, aborting", __func__);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aic_init_fifo(struct aic_usb_gadget *gg)
|
|
{
|
|
u32 addr = 0;
|
|
u32 val = 0;
|
|
int i = 0;
|
|
|
|
/* reset fifo map of endpoints */
|
|
WARN_ON(gg->fifo_map);
|
|
gg->fifo_map = 0;
|
|
|
|
/* rx fifo */
|
|
aic_writel(gg, gg->params.rx_fifo_size, RXFIFOSIZ);
|
|
|
|
/* tx no-period fifo */
|
|
aic_writel(gg, (gg->params.rx_fifo_size <<
|
|
FIFOSIZE_STARTADDR_SHIFT) |
|
|
(gg->params.np_tx_fifo_size << FIFOSIZE_DEPTH_SHIFT),
|
|
NPTXFIFOSIZ);
|
|
|
|
/* tx period fifos */
|
|
addr = gg->params.rx_fifo_size + gg->params.np_tx_fifo_size;
|
|
for (i = 1; i <= gg->params.num_perio_in_ep; i++) {
|
|
val = addr;
|
|
val |= gg->params.p_tx_fifo_size[i] << FIFOSIZE_DEPTH_SHIFT;
|
|
aic_writel(gg, val, TXFIFOSIZ(i));
|
|
WARN_ONCE(addr + gg->params.p_tx_fifo_size[i] >
|
|
gg->params.total_fifo_size,
|
|
"insufficient fifo memory");
|
|
addr += gg->params.p_tx_fifo_size[i];
|
|
}
|
|
|
|
/* flush all fifo */
|
|
val = aic_readl(gg, USBDEVINIT);
|
|
val &= ~USBDEVINIT_TXFNUM_MASK;
|
|
val |= USBDEVINIT_TXFNUM(0x10);
|
|
val |= USBDEVINIT_TXFFLSH | USBDEVINIT_RXFFLSH;
|
|
aic_writel(gg, val, USBDEVINIT);
|
|
if (aic_wait_bit_clear(gg, USBDEVINIT,
|
|
(USBDEVINIT_TXFFLSH | USBDEVINIT_RXFFLSH),
|
|
100)) {
|
|
dev_warn(gg->dev, "%s: flush fifo timeout\n", __func__);
|
|
return -EBUSY;
|
|
}
|
|
|
|
dev_dbg(gg->dev, "FIFOs reset.\n");
|
|
return 0;
|
|
}
|
|
|
|
void aic_flush_tx_fifo(struct aic_usb_gadget *gg, const int num)
|
|
{
|
|
u32 rst;
|
|
|
|
dev_dbg(gg->dev, "Flush Tx FIFO %d\n", num);
|
|
|
|
/* Wait for AHB master IDLE state */
|
|
if (aic_wait_bit_set(gg, AHBBASIC, AHBBASIC_AHBIDLE, 10000))
|
|
dev_warn(gg->dev, "%s: HANG! AHB Idle GRSCTL\n",
|
|
__func__);
|
|
|
|
rst = aic_readl(gg, USBDEVINIT);
|
|
rst &= ~USBDEVINIT_TXFNUM_MASK;
|
|
rst |= (num << USBDEVINIT_TXFNUM_SHIFT) & USBDEVINIT_TXFNUM_MASK;
|
|
rst |= USBDEVINIT_TXFFLSH;
|
|
aic_writel(gg, rst, USBDEVINIT);
|
|
|
|
if (aic_wait_bit_clear(gg, USBDEVINIT, USBDEVINIT_TXFFLSH, 10000))
|
|
dev_warn(gg->dev, "%s: HANG! timeout GRSTCTL GRSTCTL_TXFFLSH\n",
|
|
__func__);
|
|
|
|
/* Wait for at least 3 PHY Clocks */
|
|
udelay(1);
|
|
}
|
|
|
|
void aic_flush_rx_fifo(struct aic_usb_gadget *gg)
|
|
{
|
|
u32 rst;
|
|
|
|
dev_dbg(gg->dev, "%s()\n", __func__);
|
|
|
|
/* Wait for AHB master IDLE state */
|
|
if (aic_wait_bit_set(gg, AHBBASIC, AHBBASIC_AHBIDLE, 10000))
|
|
dev_warn(gg->dev, "%s: HANG! AHB Idle GRSCTL\n",
|
|
__func__);
|
|
|
|
rst = aic_readl(gg, USBDEVINIT);
|
|
rst |= USBDEVINIT_RXFFLSH;
|
|
aic_writel(gg, rst, USBDEVINIT);
|
|
|
|
/* Wait for RxFIFO flush done */
|
|
if (aic_wait_bit_clear(gg, USBDEVINIT, USBDEVINIT_RXFFLSH, 10000))
|
|
dev_warn(gg->dev, "%s: HANG! timeout GRSTCTL GRSTCTL_RXFFLSH\n",
|
|
__func__);
|
|
|
|
/* Wait for at least 3 PHY Clocks */
|
|
udelay(1);
|
|
}
|
|
|
|
static void aic_soft_disconnect(struct aic_usb_gadget *gg)
|
|
{
|
|
/* set the soft-disconnect bit */
|
|
aic_set_bit(gg, USBDEVFUNC, USBDEVFUNC_SFTDISCON);
|
|
}
|
|
|
|
static void aic_soft_connect(struct aic_usb_gadget *gg)
|
|
{
|
|
/* remove the soft-disconnect and let's go */
|
|
aic_clear_bit(gg, USBDEVFUNC, USBDEVFUNC_SFTDISCON);
|
|
}
|
|
|
|
static u32 aic_read_ep_interrupts(struct aic_usb_gadget *gg,
|
|
unsigned int idx, int dir_in)
|
|
{
|
|
u32 msk_addr = dir_in ? INEPINTMSK : OUTEPINTMSK;
|
|
u32 int_addr = dir_in ? INEPINT(idx) : OUTEPINT(idx);
|
|
u32 ints;
|
|
u32 mask;
|
|
|
|
mask = aic_readl(gg, msk_addr);
|
|
mask |= EPINT_SETUP_RCVD;
|
|
|
|
ints = aic_readl(gg, int_addr);
|
|
ints &= mask;
|
|
|
|
return ints;
|
|
}
|
|
|
|
static int aic_set_test_mode(struct aic_usb_gadget *gg, int testmode)
|
|
{
|
|
int ctl = aic_readl(gg, USBDEVFUNC);
|
|
|
|
ctl &= ~USBDEVFUNC_TSTCTL_MASK;
|
|
switch (testmode) {
|
|
case USB_TEST_J:
|
|
case USB_TEST_K:
|
|
case USB_TEST_SE0_NAK:
|
|
case USB_TEST_PACKET:
|
|
case USB_TEST_FORCE_ENABLE:
|
|
ctl |= testmode << USBDEVFUNC_TSTCTL_SHIFT;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
aic_writel(gg, ctl, USBDEVFUNC);
|
|
return 0;
|
|
}
|
|
|
|
static u32 aic_ep0_mps(unsigned int mps)
|
|
{
|
|
switch (mps) {
|
|
case 64:
|
|
return EP0CTL_MPS_64;
|
|
case 32:
|
|
return EP0CTL_MPS_32;
|
|
case 16:
|
|
return EP0CTL_MPS_16;
|
|
case 8:
|
|
return EP0CTL_MPS_8;
|
|
}
|
|
|
|
/* bad max packet size, warn and return invalid result */
|
|
WARN_ON(1);
|
|
return (u32)-1;
|
|
}
|
|
|
|
static inline struct aic_usb_ep *index_to_ep(struct aic_usb_gadget *gg,
|
|
u32 index, u32 dir_in)
|
|
{
|
|
if (dir_in)
|
|
return gg->eps_in[index];
|
|
else
|
|
return gg->eps_out[index];
|
|
}
|
|
|
|
static struct aic_usb_ep *windex_to_ep(struct aic_usb_gadget *gg,
|
|
u32 windex)
|
|
{
|
|
struct aic_usb_ep *a_ep;
|
|
int dir = (windex & USB_DIR_IN) ? 1 : 0;
|
|
int idx = windex & 0x7F;
|
|
|
|
if (windex >= 0x100)
|
|
return NULL;
|
|
|
|
if (idx > gg->params.num_ep)
|
|
return NULL;
|
|
|
|
a_ep = index_to_ep(gg, idx, dir);
|
|
|
|
if (idx && a_ep->dir_in != dir)
|
|
return NULL;
|
|
|
|
return a_ep;
|
|
}
|
|
|
|
static bool on_list(struct aic_usb_ep *ep, struct aic_usb_req *test)
|
|
{
|
|
struct aic_usb_req *req = NULL, *treq;
|
|
|
|
list_for_each_entry_safe(req, treq, &ep->queue, queue) {
|
|
if (req == test)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static struct aic_usb_req *get_ep_head(struct aic_usb_ep *a_ep)
|
|
{
|
|
return list_first_entry_or_null(&a_ep->queue, struct aic_usb_req,
|
|
queue);
|
|
}
|
|
|
|
static unsigned int get_ep_limit(struct aic_usb_ep *a_ep)
|
|
{
|
|
int index = a_ep->index;
|
|
unsigned int maxsize;
|
|
unsigned int maxpkt;
|
|
|
|
if (index != 0) {
|
|
maxsize = EPTSIZ_XFERSIZE_LIMIT + 1;
|
|
maxpkt = EPTSIZ_PKTCNT_LIMIT + 1;
|
|
} else {
|
|
maxsize = 64 + 64;
|
|
if (a_ep->dir_in)
|
|
maxpkt = INEPTSFSIZ0_PKTCNT_LIMIT + 1;
|
|
else
|
|
maxpkt = 2;
|
|
}
|
|
|
|
/* we made the constant loading easier above by using +1 */
|
|
maxpkt--;
|
|
maxsize--;
|
|
|
|
/*
|
|
* constrain by packet count if maxpkts*pktsize is greater
|
|
* than the length register size.
|
|
*/
|
|
|
|
if ((maxpkt * a_ep->ep.maxpacket) < maxsize)
|
|
maxsize = maxpkt * a_ep->ep.maxpacket;
|
|
|
|
return maxsize;
|
|
}
|
|
|
|
static void aic_set_ep_maxpacket(struct aic_usb_gadget *gg,
|
|
unsigned int ep, unsigned int mps,
|
|
unsigned int mc, unsigned int dir_in)
|
|
{
|
|
struct aic_usb_ep *a_ep;
|
|
u32 reg;
|
|
|
|
a_ep = index_to_ep(gg, ep, dir_in);
|
|
if (!a_ep)
|
|
return;
|
|
|
|
if (ep == 0) {
|
|
u32 mps_bytes = mps;
|
|
|
|
/* EP0 is a special case */
|
|
mps = aic_ep0_mps(mps_bytes);
|
|
if (mps > 3)
|
|
goto bad_mps;
|
|
a_ep->ep.maxpacket = mps_bytes;
|
|
a_ep->mc = 1;
|
|
} else {
|
|
if (mps > 1024)
|
|
goto bad_mps;
|
|
a_ep->mc = mc;
|
|
if (mc > 3)
|
|
goto bad_mps;
|
|
a_ep->ep.maxpacket = mps;
|
|
}
|
|
|
|
if (dir_in) {
|
|
reg = aic_readl(gg, INEPCFG(ep));
|
|
reg &= ~EPCTL_MPS_MASK;
|
|
reg |= mps;
|
|
aic_writel(gg, reg, INEPCFG(ep));
|
|
} else {
|
|
reg = aic_readl(gg, OUTEPCFG(ep));
|
|
reg &= ~EPCTL_MPS_MASK;
|
|
reg |= mps;
|
|
aic_writel(gg, reg, OUTEPCFG(ep));
|
|
}
|
|
|
|
return;
|
|
|
|
bad_mps:
|
|
dev_err(gg->dev, "ep%d: bad mps of %d\n", ep, mps);
|
|
}
|
|
|
|
static void aic_gg_set_usb_res(void __iomem *ctl_reg, u32 resis)
|
|
{
|
|
u32 val;
|
|
|
|
if (ctl_reg == NULL)
|
|
return;
|
|
|
|
resis &= SYSCFG_USB_RES_CAL_VAL_MASK;
|
|
|
|
val = readl(ctl_reg);
|
|
val &= ~SYSCFG_USB_RES_CAL_VAL_MASK;
|
|
val |= resis << SYSCFG_USB_RES_CAL_VAL_SHIFT;
|
|
val |= 1 << SYSCFG_USB_RES_CAL_EN_SHIFT;
|
|
|
|
writel(val, ctl_reg);
|
|
}
|
|
|
|
static int aic_gg_get_res_cfg(struct device_node *np, struct aic_usb_res_cfg *cfg,
|
|
const char *property)
|
|
{
|
|
int len, index, offset, res;
|
|
const __be32 *prop;
|
|
struct device_node *child_np;
|
|
|
|
prop = of_get_property(np, property, &len);
|
|
if (!prop || len < 4 * sizeof(__be32))
|
|
return -ENODEV;
|
|
|
|
child_np = of_find_node_by_phandle(be32_to_cpup(prop++));
|
|
if (!child_np)
|
|
return -ENODEV;
|
|
|
|
index = be32_to_cpup(prop++);
|
|
offset = be32_to_cpup(prop++);
|
|
res = be32_to_cpup(prop);
|
|
|
|
cfg->addr = of_iomap(child_np, index);
|
|
if (!cfg->addr)
|
|
return -ENOMEM;
|
|
cfg->addr += offset;
|
|
cfg->resis = res;
|
|
|
|
pr_debug("device property : %s \n", property);
|
|
pr_debug("child_np : %s \n", child_np->name);
|
|
pr_debug("offset : %#x res : %#x \n", offset, res);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_core_hw_init1(struct aic_usb_gadget *gg, bool is_usb_reset)
|
|
{
|
|
u32 reg = 0;
|
|
int i = 0;
|
|
int ret = 0;
|
|
|
|
if (!is_usb_reset) {
|
|
/* unmask subset of endpoint interrupts */
|
|
aic_writel(gg, INEPINTMSK_TIMEOUTMSK | INEPINTMSK_AHBERRMSK |
|
|
INEPINTMSK_EPDISBLDMSK | INEPINTMSK_XFERCOMPLMSK,
|
|
INEPINTMSK);
|
|
aic_writel(gg, OUTEPINTMSK_SETUPMSK | OUTEPINTMSK_AHBERRMSK |
|
|
OUTEPINTMSK_EPDISBLDMSK | OUTEPINTMSK_XFERCOMPLMSK,
|
|
OUTEPINTMSK);
|
|
|
|
/* mask all endpoint interrupt */
|
|
aic_writel(gg, 0, USBEPINTMSK);
|
|
|
|
/* soft disconnect */
|
|
aic_writel(gg, USBDEVFUNC, USBDEVFUNC_SFTDISCON);
|
|
|
|
/* enable DMA */
|
|
aic_set_bit(gg, USBDEVINIT, USBDEVINIT_DMA_EN);
|
|
}
|
|
|
|
/* Kill any ep0 requests as controller will be reinitialized */
|
|
aic_kill_ep_reqs(gg, gg->eps_out[0], -ECONNRESET);
|
|
|
|
if (!is_usb_reset) {
|
|
/* core reset */
|
|
ret = aic_core_rst(gg);
|
|
if (ret) {
|
|
dev_err(gg->dev,
|
|
"%s: Reset failed, aborting", __func__);
|
|
return ret;
|
|
}
|
|
} else {
|
|
/* all endpoints should be shutdown */
|
|
for (i = 1; i < gg->params.num_ep; i++) {
|
|
if (gg->eps_in[i])
|
|
aic_ep_disable_nolock(&gg->eps_in[i]->ep);
|
|
if (gg->eps_out[i])
|
|
aic_ep_disable_nolock(&gg->eps_out[i]->ep);
|
|
}
|
|
}
|
|
|
|
/* HS/FS timeout calibration */
|
|
reg = aic_readl(gg, USBPHYIF);
|
|
reg &= ~USBPHYIF_TOUTCAL_MASK;
|
|
reg |= USBPHYIF_TOUTCAL(7);
|
|
aic_writel(gg, reg, USBPHYIF);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void aic_core_hw_init2(struct aic_usb_gadget *gg, bool is_usb_reset)
|
|
{
|
|
u32 reg = 0;
|
|
|
|
if (!is_usb_reset) {
|
|
/* soft disconnect */
|
|
aic_set_bit(gg, USBDEVFUNC, USBDEVFUNC_SFTDISCON);
|
|
}
|
|
|
|
/* device speed */
|
|
reg = USBDEVCONF_EPMISCNT(1);
|
|
switch (gg->params.speed) {
|
|
case AIC_SPEED_PARAM_LOW:
|
|
reg |= USBDEVCONF_DEVSPD_LS;
|
|
break;
|
|
case AIC_SPEED_PARAM_FULL:
|
|
if (gg->params.phy_type == AIC_PHY_TYPE_PARAM_FS)
|
|
reg |= USBDEVCONF_DEVSPD_FS48;
|
|
else
|
|
reg |= USBDEVCONF_DEVSPD_FS;
|
|
break;
|
|
default:
|
|
reg |= USBDEVCONF_DEVSPD_HS;
|
|
}
|
|
aic_writel(gg, reg, USBDEVCONF);
|
|
|
|
/* clear any pending interrupts */
|
|
aic_writel(gg, 0xffffffff, USBINTSTS);
|
|
|
|
/* set global interrupt mask */
|
|
reg = USBINTSTS_ERLYSUSP |
|
|
USBINTSTS_GOUTNAKEFF | USBINTSTS_GINNAKEFF |
|
|
USBINTSTS_USBRST | USBINTSTS_ENUMDONE |
|
|
USBINTSTS_USBSUSP | USBINTSTS_WKUPINT;
|
|
reg |= USBINTSTS_INCOMPL_SOIN | USBINTSTS_INCOMPL_SOOUT;
|
|
aic_writel(gg, reg, USBINTMSK);
|
|
|
|
/* AHB config */
|
|
reg = USBDEVINIT_GLBL_INTR_EN | USBDEVINIT_DMA_EN;
|
|
reg |= USBDEVINIT_HBSTLEN_INCR4 << USBDEVINIT_HBSTLEN_SHIFT;
|
|
aic_writel(gg, reg, USBDEVINIT);
|
|
|
|
/* set IN endpoint interrupt mask */
|
|
reg = INEPINTMSK_EPDISBLDMSK | INEPINTMSK_XFERCOMPLMSK |
|
|
INEPINTMSK_TIMEOUTMSK | INEPINTMSK_AHBERRMSK |
|
|
INEPINTMSK_INTKNEPMISMSK;
|
|
aic_writel(gg, reg, INEPINTMSK);
|
|
|
|
/* set OUT endpoint interrupt mask */
|
|
reg = OUTEPINTMSK_XFERCOMPLMSK | OUTEPINTMSK_STSPHSERCVDMSK |
|
|
OUTEPINTMSK_EPDISBLDMSK | OUTEPINTMSK_AHBERRMSK |
|
|
OUTEPINTMSK_SETUPMSK;
|
|
aic_writel(gg, reg, OUTEPINTMSK);
|
|
|
|
/* mask all endpoint interrupt */
|
|
aic_writel(gg, 0, USBEPINTMSK);
|
|
|
|
dev_dbg(gg->dev, "EP0: INEPCFG0=0x%08x, OUTEPCFG0=0x%08x\n",
|
|
aic_readl(gg, INEPCFG0),
|
|
aic_readl(gg, OUTEPCFG0));
|
|
|
|
/* enable in and out endpoint interrupts */
|
|
aic_en_gsint(gg, USBINTSTS_OEPINT | USBINTSTS_IEPINT);
|
|
|
|
/* Enable interrupts for EP0 in and out */
|
|
aic_ctrl_epint(gg, 0, 0, 1);
|
|
aic_ctrl_epint(gg, 0, 1, 1);
|
|
|
|
gg->tx_fifo_map |= 1;
|
|
|
|
if (!is_usb_reset) {
|
|
/* power-on programming done */
|
|
aic_set_bit(gg, USBDEVFUNC, USBDEVFUNC_PWRONPRGDONE);
|
|
udelay(10);
|
|
aic_clear_bit(gg, USBDEVFUNC, USBDEVFUNC_PWRONPRGDONE);
|
|
}
|
|
dev_dbg(gg->dev, "USBDEVFUNC=0x%08x\n", aic_readl(gg, USBDEVFUNC));
|
|
|
|
/* ep0 OUT: set to read 1 8byte packet */
|
|
aic_writel(gg, EPTSIZ_MC(1) | EPTSIZ_PKTCNT(1) |
|
|
EPTSIZ_XFERSIZE(8), OUTEPTSFSIZ0);
|
|
/* ep0 OUT: enable + active */
|
|
aic_writel(gg, aic_ep0_mps(gg->eps_out[0]->ep.maxpacket) |
|
|
EPCTL_CNAK | EPCTL_EPENA |
|
|
EPCTL_USBACTEP,
|
|
OUTEPCFG0);
|
|
/* ep0 IN: disable + active */
|
|
aic_writel(gg, aic_ep0_mps(gg->eps_out[0]->ep.maxpacket) |
|
|
EPCTL_USBACTEP, INEPCFG0);
|
|
|
|
/* clear global NAKs */
|
|
reg = USBDEVFUNC_CGOUTNAK | USBDEVFUNC_CGNPINNAK;
|
|
if (!is_usb_reset)
|
|
reg |= USBDEVFUNC_SFTDISCON;
|
|
aic_set_bit(gg, USBDEVFUNC, reg);
|
|
|
|
mdelay(3);
|
|
|
|
dev_dbg(gg->dev, "EP0: INEPCFG0=0x%08x, OUTEPCFG0=0x%08x\n",
|
|
aic_readl(gg, INEPCFG0),
|
|
aic_readl(gg, OUTEPCFG0));
|
|
}
|
|
|
|
static int aic_core_init(struct aic_usb_gadget *gg,
|
|
bool is_usb_reset)
|
|
{
|
|
int ret = 0;
|
|
|
|
aic_gg_set_usb_res(gg->params.usb_res_cfg.addr, gg->params.usb_res_cfg.resis);
|
|
|
|
ret = aic_core_hw_init1(gg, is_usb_reset);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = aic_hs_phy_init(gg);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = aic_init_fifo(gg);
|
|
if (ret)
|
|
return ret;
|
|
|
|
aic_core_hw_init2(gg, is_usb_reset);
|
|
|
|
aic_set_nextep(gg);
|
|
|
|
aic_ep0_enqueue_setup(gg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void aic_kill_ep_reqs(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *ep,
|
|
int result)
|
|
{
|
|
unsigned int size;
|
|
|
|
ep->req = NULL;
|
|
|
|
while (!list_empty(&ep->queue)) {
|
|
struct aic_usb_req *req = get_ep_head(ep);
|
|
|
|
aic_ep_complete_request(gg, ep, req, result);
|
|
}
|
|
|
|
size = (aic_readl(gg, INEPTXSTS(ep->fifo_index)) & 0xffff) * 4;
|
|
if (size < ep->fifo_size)
|
|
aic_flush_tx_fifo(gg, ep->fifo_index);
|
|
}
|
|
|
|
void aic_core_disconnect(struct aic_usb_gadget *gg)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (!gg->connected)
|
|
return;
|
|
|
|
gg->connected = 0;
|
|
gg->test_mode = 0;
|
|
|
|
/* all endpoints should be shutdown */
|
|
for (i = 0; i < gg->params.num_ep; i++) {
|
|
if (gg->eps_in[i])
|
|
aic_kill_ep_reqs(gg, gg->eps_in[i],
|
|
-ESHUTDOWN);
|
|
if (gg->eps_out[i])
|
|
aic_kill_ep_reqs(gg, gg->eps_out[i],
|
|
-ESHUTDOWN);
|
|
}
|
|
|
|
aic_gadget_driver_cb(gg, disconnect);
|
|
|
|
usb_gadget_set_state(&gg->gadget, USB_STATE_NOTATTACHED);
|
|
}
|
|
|
|
static void aic_ep_complete_empty(struct usb_ep *ep,
|
|
struct usb_request *req)
|
|
{
|
|
struct aic_usb_ep *a_ep = our_ep(ep);
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
|
|
dev_dbg(gg->dev, "%s: ep %p, req %p\n", __func__, ep, req);
|
|
|
|
aic_ep_free_request(ep, req);
|
|
}
|
|
|
|
static int aic_ep0_enqueue_reply(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *ep,
|
|
void *buff,
|
|
int length)
|
|
{
|
|
struct usb_request *req;
|
|
int ret;
|
|
|
|
dev_dbg(gg->dev, "%s: buff %p, len %d\n", __func__, buff, length);
|
|
|
|
req = aic_ep_alloc_request(&ep->ep, GFP_ATOMIC);
|
|
gg->ep0_reply = req;
|
|
if (!req) {
|
|
dev_warn(gg->dev, "%s: cannot alloc req\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
req->buf = gg->ep0_buff;
|
|
req->length = length;
|
|
/*
|
|
* zero flag is for sending zlp in DATA IN stage. It has no impact on
|
|
* STATUS stage.
|
|
*/
|
|
req->zero = 0;
|
|
req->complete = aic_ep_complete_empty;
|
|
|
|
if (length)
|
|
memcpy(req->buf, buff, length);
|
|
|
|
ret = aic_ep_queue_request_nolock(&ep->ep, req, GFP_ATOMIC);
|
|
if (ret) {
|
|
dev_warn(gg->dev, "%s: cannot queue req\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_ep0_process_req_feature(struct aic_usb_gadget *gg,
|
|
struct usb_ctrlrequest *ctrl)
|
|
{
|
|
struct aic_usb_ep *ep0 = gg->eps_out[0];
|
|
struct aic_usb_req *a_req;
|
|
bool set = (ctrl->bRequest == USB_REQ_SET_FEATURE);
|
|
struct aic_usb_ep *ep;
|
|
int ret;
|
|
bool halted;
|
|
u32 recip;
|
|
u32 wValue;
|
|
u32 wIndex;
|
|
|
|
dev_dbg(gg->dev, "%s: %s_FEATURE\n",
|
|
__func__, set ? "SET" : "CLEAR");
|
|
|
|
wValue = le16_to_cpu(ctrl->wValue);
|
|
wIndex = le16_to_cpu(ctrl->wIndex);
|
|
recip = ctrl->bRequestType & USB_RECIP_MASK;
|
|
|
|
switch (recip) {
|
|
case USB_RECIP_DEVICE:
|
|
switch (wValue) {
|
|
case USB_DEVICE_REMOTE_WAKEUP:
|
|
if (set)
|
|
gg->remote_wakeup_allowed = 1;
|
|
else
|
|
gg->remote_wakeup_allowed = 0;
|
|
break;
|
|
|
|
case USB_DEVICE_TEST_MODE:
|
|
if ((wIndex & 0xff) != 0)
|
|
return -EINVAL;
|
|
if (!set)
|
|
return -EINVAL;
|
|
|
|
gg->test_mode = wIndex >> 8;
|
|
break;
|
|
default:
|
|
return -ENOENT;
|
|
}
|
|
|
|
ret = aic_ep0_enqueue_reply(gg, ep0, NULL, 0);
|
|
if (ret) {
|
|
dev_err(gg->dev,
|
|
"%s: failed to send reply\n", __func__);
|
|
return ret;
|
|
}
|
|
break;
|
|
|
|
case USB_RECIP_ENDPOINT:
|
|
ep = windex_to_ep(gg, wIndex);
|
|
if (!ep) {
|
|
dev_dbg(gg->dev, "%s: no endpoint for 0x%04x\n",
|
|
__func__, wIndex);
|
|
return -ENOENT;
|
|
}
|
|
|
|
switch (wValue) {
|
|
case USB_ENDPOINT_HALT:
|
|
halted = ep->halted;
|
|
|
|
aic_ep_sethalt_nolock(&ep->ep, set, true);
|
|
|
|
ret = aic_ep0_enqueue_reply(gg, ep0, NULL, 0);
|
|
if (ret) {
|
|
dev_err(gg->dev,
|
|
"%s: failed to send reply\n", __func__);
|
|
return ret;
|
|
}
|
|
if (!set && halted) {
|
|
if (ep->req) {
|
|
a_req = ep->req;
|
|
ep->req = NULL;
|
|
list_del_init(&a_req->queue);
|
|
if (a_req->req.complete) {
|
|
spin_unlock(&gg->lock);
|
|
usb_gadget_giveback_request
|
|
(&ep->ep, &a_req->req);
|
|
spin_lock(&gg->lock);
|
|
}
|
|
}
|
|
|
|
/* If we have pending request, then start it */
|
|
if (!ep->req)
|
|
aic_ep_start_next_request(ep);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
return -ENOENT;
|
|
}
|
|
break;
|
|
default:
|
|
return -ENOENT;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int aic_ep0_process_get_status(struct aic_usb_gadget *gg,
|
|
struct usb_ctrlrequest *ctrl)
|
|
{
|
|
struct aic_usb_ep *ep0 = gg->eps_out[0];
|
|
struct aic_usb_ep *ep;
|
|
__le16 reply;
|
|
u16 status;
|
|
int ret;
|
|
|
|
dev_dbg(gg->dev, "%s: USB_REQ_GET_STATUS\n", __func__);
|
|
|
|
if (!ep0->dir_in) {
|
|
dev_warn(gg->dev, "%s: direction out?\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (ctrl->bRequestType & USB_RECIP_MASK) {
|
|
case USB_RECIP_DEVICE:
|
|
status = 1 << USB_DEVICE_SELF_POWERED;
|
|
status |= gg->remote_wakeup_allowed <<
|
|
USB_DEVICE_REMOTE_WAKEUP;
|
|
reply = cpu_to_le16(status);
|
|
break;
|
|
|
|
case USB_RECIP_INTERFACE:
|
|
/* currently, the data result should be zero */
|
|
reply = cpu_to_le16(0);
|
|
break;
|
|
|
|
case USB_RECIP_ENDPOINT:
|
|
ep = windex_to_ep(gg, le16_to_cpu(ctrl->wIndex));
|
|
if (!ep)
|
|
return -ENOENT;
|
|
reply = cpu_to_le16(ep->halted ? 1 : 0);
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (le16_to_cpu(ctrl->wLength) != 2)
|
|
return -EINVAL;
|
|
|
|
ret = aic_ep0_enqueue_reply(gg, ep0, &reply, 2);
|
|
if (ret) {
|
|
dev_err(gg->dev, "%s: failed to send reply\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void aic_ep0_process_control(struct aic_usb_gadget *gg,
|
|
struct usb_ctrlrequest *ctrl)
|
|
{
|
|
struct aic_usb_ep *ep0 = gg->eps_out[0];
|
|
u32 reg = 0;
|
|
int ret = 0;
|
|
|
|
dev_dbg(gg->dev,
|
|
"ctrl Type=%02x, Req=%02x, V=%04x, I=%04x, L=%04x\n",
|
|
ctrl->bRequestType, ctrl->bRequest, ctrl->wValue,
|
|
ctrl->wIndex, ctrl->wLength);
|
|
|
|
if (ctrl->wLength == 0) {
|
|
ep0->dir_in = 1;
|
|
gg->ep0_state = AIC_EP0_STATUS_IN;
|
|
} else if (ctrl->bRequestType & USB_DIR_IN) {
|
|
ep0->dir_in = 1;
|
|
gg->ep0_state = AIC_EP0_DATA_IN;
|
|
} else {
|
|
ep0->dir_in = 0;
|
|
gg->ep0_state = AIC_EP0_DATA_OUT;
|
|
}
|
|
|
|
if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
|
|
switch (ctrl->bRequest) {
|
|
case USB_REQ_SET_ADDRESS:
|
|
gg->connected = 1;
|
|
reg = aic_readl(gg, USBDEVCONF);
|
|
reg &= ~USBDEVCONF_DEVADDR_MASK;
|
|
reg |= (le16_to_cpu(ctrl->wValue) <<
|
|
USBDEVCONF_DEVADDR_SHIFT) &
|
|
USBDEVCONF_DEVADDR_MASK;
|
|
aic_writel(gg, reg, USBDEVCONF);
|
|
|
|
dev_info(gg->dev, "new address %d\n", ctrl->wValue);
|
|
|
|
ret = aic_ep0_enqueue_reply(gg, ep0, NULL, 0);
|
|
return;
|
|
|
|
case USB_REQ_GET_STATUS:
|
|
ret = aic_ep0_process_get_status(gg, ctrl);
|
|
break;
|
|
|
|
case USB_REQ_CLEAR_FEATURE:
|
|
case USB_REQ_SET_FEATURE:
|
|
ret = aic_ep0_process_req_feature(gg, ctrl);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* driver's setup() callback */
|
|
if (ret == 0 && gg->driver) {
|
|
spin_unlock(&gg->lock);
|
|
ret = gg->driver->setup(&gg->gadget, ctrl);
|
|
spin_lock(&gg->lock);
|
|
if (ret < 0)
|
|
dev_dbg(gg->dev, "driver->setup() ret %d\n", ret);
|
|
}
|
|
|
|
gg->delayed_status = false;
|
|
if (ret == USB_GADGET_DELAYED_STATUS)
|
|
gg->delayed_status = true;
|
|
|
|
if (ret < 0)
|
|
aic_stall_ep0(gg);
|
|
}
|
|
|
|
static void aic_ep0_complete_setup(struct usb_ep *ep,
|
|
struct usb_request *req)
|
|
{
|
|
struct aic_usb_ep *hs_ep = our_ep(ep);
|
|
struct aic_usb_gadget *gg = hs_ep->parent;
|
|
|
|
if (req->status < 0) {
|
|
dev_dbg(gg->dev, "%s: failed %d\n", __func__, req->status);
|
|
return;
|
|
}
|
|
|
|
spin_lock(&gg->lock);
|
|
if (req->actual == 0)
|
|
aic_ep0_enqueue_setup(gg);
|
|
else
|
|
aic_ep0_process_control(gg, req->buf);
|
|
spin_unlock(&gg->lock);
|
|
}
|
|
|
|
static void aic_ep0_enqueue_setup(struct aic_usb_gadget *gg)
|
|
{
|
|
struct usb_request *req = gg->ctrl_req;
|
|
struct aic_usb_req *a_req = our_req(req);
|
|
int ret;
|
|
|
|
dev_dbg(gg->dev, "%s: queueing setup request\n", __func__);
|
|
|
|
req->zero = 0;
|
|
req->length = 8;
|
|
req->buf = gg->ctrl_buff;
|
|
req->complete = aic_ep0_complete_setup;
|
|
|
|
if (!list_empty(&a_req->queue)) {
|
|
dev_dbg(gg->dev, "%s already queued???\n", __func__);
|
|
return;
|
|
}
|
|
|
|
gg->eps_out[0]->dir_in = 0;
|
|
gg->eps_out[0]->send_zlp = 0;
|
|
gg->ep0_state = AIC_EP0_SETUP;
|
|
|
|
ret = aic_ep_queue_request_nolock(&gg->eps_out[0]->ep, req, GFP_ATOMIC);
|
|
if (ret < 0)
|
|
dev_err(gg->dev, "%s: failed queue (%d)\n", __func__, ret);
|
|
}
|
|
|
|
static void aic_ep_program_zlp(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *a_ep)
|
|
{
|
|
u32 ctrl;
|
|
u8 index = a_ep->index;
|
|
u32 ctl_addr = a_ep->dir_in ? INEPCFG(index) : OUTEPCFG(index);
|
|
u32 siz_addr = a_ep->dir_in ? INEPTSFSIZ(index) : OUTEPTSFSIZ(index);
|
|
|
|
if (a_ep->dir_in)
|
|
dev_dbg(gg->dev, "Sending zero-length packet on ep%d\n",
|
|
index);
|
|
else
|
|
dev_dbg(gg->dev, "Receiving zero-length packet on ep%d\n",
|
|
index);
|
|
|
|
aic_writel(gg, EPTSIZ_MC(1) | EPTSIZ_PKTCNT(1) |
|
|
EPTSIZ_XFERSIZE(0),
|
|
siz_addr);
|
|
|
|
ctrl = aic_readl(gg, ctl_addr);
|
|
ctrl |= EPCTL_CNAK; /* clear NAK set by core */
|
|
ctrl |= EPCTL_EPENA; /* ensure ep enabled */
|
|
ctrl |= EPCTL_USBACTEP;
|
|
aic_writel(gg, ctrl, ctl_addr);
|
|
}
|
|
|
|
static void aic_ep0_program_zlp(struct aic_usb_gadget *gg, bool dir_in)
|
|
{
|
|
/* eps_out[0] is used in both directions */
|
|
gg->eps_out[0]->dir_in = dir_in;
|
|
gg->ep0_state = dir_in ? AIC_EP0_STATUS_IN : AIC_EP0_STATUS_OUT;
|
|
|
|
aic_ep_program_zlp(gg, gg->eps_out[0]);
|
|
}
|
|
|
|
static void aic_epint_handle_nak(struct aic_usb_ep *a_ep)
|
|
{
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
int dir_in = a_ep->dir_in;
|
|
|
|
if (!dir_in || !a_ep->isochronous)
|
|
return;
|
|
|
|
if (a_ep->target_frame == TARGET_FRAME_INITIAL) {
|
|
a_ep->target_frame = gg->frame_number;
|
|
if (a_ep->interval > 1) {
|
|
u32 ctrl = aic_readl(gg,
|
|
INEPCFG(a_ep->index));
|
|
if (a_ep->target_frame & 0x1)
|
|
ctrl |= EPCTL_SETODDFR;
|
|
else
|
|
ctrl |= EPCTL_SETEVENFR;
|
|
|
|
aic_writel(gg, ctrl, INEPCFG(a_ep->index));
|
|
}
|
|
aic_ep_complete_request(gg, a_ep,
|
|
get_ep_head(a_ep), 0);
|
|
}
|
|
|
|
aic_incr_frame_num(a_ep);
|
|
}
|
|
|
|
static void aic_epint_handle_outtoken_ep_disabled(struct aic_usb_ep *ep)
|
|
{
|
|
struct aic_usb_gadget *gg = ep->parent;
|
|
int dir_in = ep->dir_in;
|
|
u32 doepmsk;
|
|
|
|
if (dir_in || !ep->isochronous)
|
|
return;
|
|
|
|
if (ep->interval > 1 &&
|
|
ep->target_frame == TARGET_FRAME_INITIAL) {
|
|
u32 ctrl;
|
|
|
|
ep->target_frame = gg->frame_number;
|
|
aic_incr_frame_num(ep);
|
|
|
|
ctrl = aic_readl(gg, OUTEPCFG(ep->index));
|
|
if (ep->target_frame & 0x1)
|
|
ctrl |= EPCTL_SETODDFR;
|
|
else
|
|
ctrl |= EPCTL_SETEVENFR;
|
|
|
|
aic_writel(gg, ctrl, OUTEPCFG(ep->index));
|
|
}
|
|
|
|
aic_ep_start_next_request(ep);
|
|
doepmsk = aic_readl(gg, OUTEPINTMSK);
|
|
doepmsk &= ~OUTEPINTMSK_OUTTKNEPDISMSK;
|
|
aic_writel(gg, doepmsk, OUTEPINTMSK);
|
|
}
|
|
|
|
static void aic_epint_handle_ep_disabled(struct aic_usb_ep *a_ep)
|
|
{
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
struct aic_usb_req *a_req;
|
|
unsigned char idx = a_ep->index;
|
|
int dir_in = a_ep->dir_in;
|
|
u32 epctl_addr = dir_in ? INEPCFG(idx) : OUTEPCFG(idx);
|
|
int dctl = aic_readl(gg, USBDEVFUNC);
|
|
|
|
dev_dbg(gg->dev, "%s: EPDisbld\n", __func__);
|
|
|
|
if (dir_in) {
|
|
int epctl = aic_readl(gg, epctl_addr);
|
|
|
|
aic_flush_tx_fifo(gg, a_ep->fifo_index);
|
|
|
|
if (a_ep->isochronous) {
|
|
aic_epint_handle_complete_in(gg, a_ep);
|
|
return;
|
|
}
|
|
|
|
if ((epctl & EPCTL_STALL) && (epctl & EPCTL_EPTYPE_BULK)) {
|
|
int dctl = aic_readl(gg, USBDEVFUNC);
|
|
|
|
dctl |= USBDEVFUNC_CGNPINNAK;
|
|
aic_writel(gg, dctl, USBDEVFUNC);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (dctl & USBDEVFUNC_GOUTNAKSTS) {
|
|
dctl |= USBDEVFUNC_CGOUTNAK;
|
|
aic_writel(gg, dctl, USBDEVFUNC);
|
|
}
|
|
|
|
if (!a_ep->isochronous)
|
|
return;
|
|
|
|
if (list_empty(&a_ep->queue)) {
|
|
dev_dbg(gg->dev, "%s: complete_ep 0x%p, ep->queue empty!\n",
|
|
__func__, a_ep);
|
|
return;
|
|
}
|
|
|
|
do {
|
|
a_req = get_ep_head(a_ep);
|
|
if (a_req)
|
|
aic_ep_complete_request(gg, a_ep, a_req,
|
|
-ENODATA);
|
|
aic_incr_frame_num(a_ep);
|
|
/* Update current frame number value. */
|
|
gg->frame_number = aic_read_frameno(gg);
|
|
} while (aic_target_frame_elapsed(a_ep));
|
|
|
|
aic_ep_start_next_request(a_ep);
|
|
}
|
|
|
|
static void aic_epint_handle_complete_in(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *a_ep)
|
|
{
|
|
struct aic_usb_req *a_req = a_ep->req;
|
|
u32 epsize = aic_readl(gg, INEPTSFSIZ(a_ep->index));
|
|
int size_left, size_done;
|
|
|
|
if (!a_req) {
|
|
dev_dbg(gg->dev, "XferCompl but no req\n");
|
|
return;
|
|
}
|
|
|
|
/* Finish ZLP handling for IN EP0 transactions */
|
|
if (a_ep->index == 0 && gg->ep0_state == AIC_EP0_STATUS_IN) {
|
|
dev_dbg(gg->dev, "zlp packet sent\n");
|
|
|
|
/*
|
|
* While send zlp for DWC2_EP0_STATUS_IN EP direction was
|
|
* changed to IN. Change back to complete OUT transfer request
|
|
*/
|
|
a_ep->dir_in = 0;
|
|
|
|
aic_ep_complete_request(gg, a_ep, a_req, 0);
|
|
if (gg->test_mode) {
|
|
int ret;
|
|
|
|
ret = aic_set_test_mode(gg, gg->test_mode);
|
|
if (ret < 0) {
|
|
dev_dbg(gg->dev, "Invalid Test #%d\n",
|
|
gg->test_mode);
|
|
aic_stall_ep0(gg);
|
|
return;
|
|
}
|
|
}
|
|
aic_ep0_enqueue_setup(gg);
|
|
return;
|
|
}
|
|
|
|
/* calculate xfer length */
|
|
size_left = EPTSIZ_XFERSIZE_GET(epsize);
|
|
size_done = a_ep->size_loaded - size_left;
|
|
size_done += a_ep->last_load;
|
|
|
|
/* update req.actual */
|
|
if (a_req->req.actual != size_done)
|
|
dev_dbg(gg->dev, "%s: adjusting size done %d => %d\n",
|
|
__func__, a_req->req.actual, size_done);
|
|
a_req->req.actual = size_done;
|
|
dev_dbg(gg->dev, "req->length:%d req->actual:%d req->zero:%d\n",
|
|
a_req->req.length, a_req->req.actual, a_req->req.zero);
|
|
|
|
/* request remain data, continue xfer */
|
|
if (!size_left && a_req->req.actual < a_req->req.length) {
|
|
dev_dbg(gg->dev, "%s trying more for req...\n", __func__);
|
|
aic_ep_start_req(gg, a_ep, a_req, true);
|
|
return;
|
|
}
|
|
|
|
/* Zlp for all endpoints, for ep0 only in DATA IN stage */
|
|
if (a_ep->send_zlp) {
|
|
aic_ep_program_zlp(gg, a_ep);
|
|
a_ep->send_zlp = 0;
|
|
/* transfer will be completed on next complete interrupt */
|
|
return;
|
|
}
|
|
|
|
if (a_ep->index == 0 && gg->ep0_state == AIC_EP0_DATA_IN) {
|
|
/* Move to STATUS OUT */
|
|
aic_ep0_program_zlp(gg, false);
|
|
return;
|
|
}
|
|
|
|
aic_ep_complete_request(gg, a_ep, a_req, 0);
|
|
}
|
|
|
|
static void aic_epint_handle_outdone(struct aic_usb_gadget *gg, int epnum)
|
|
{
|
|
u32 epsize = aic_readl(gg, OUTEPTSFSIZ(epnum));
|
|
struct aic_usb_ep *a_ep = gg->eps_out[epnum];
|
|
struct aic_usb_req *a_req = a_ep->req;
|
|
struct usb_request *req = &a_req->req;
|
|
unsigned int size_left = EPTSIZ_XFERSIZE_GET(epsize);
|
|
unsigned int size_done = 0;
|
|
int ret = 0;
|
|
|
|
if (!a_req) {
|
|
dev_dbg(gg->dev, "%s: no request active\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (epnum == 0 && gg->ep0_state == AIC_EP0_STATUS_OUT) {
|
|
dev_dbg(gg->dev, "zlp packet received\n");
|
|
aic_ep_complete_request(gg, a_ep, a_req, 0);
|
|
aic_ep0_enqueue_setup(gg);
|
|
return;
|
|
}
|
|
|
|
size_done = a_ep->size_loaded - size_left;
|
|
size_done += a_ep->last_load;
|
|
req->actual = size_done;
|
|
|
|
/* request remain data, continue xfer */
|
|
if (req->actual < req->length && size_left == 0) {
|
|
aic_ep_start_req(gg, a_ep, a_req, true);
|
|
return;
|
|
}
|
|
|
|
if (req->actual < req->length && req->short_not_ok) {
|
|
dev_dbg(gg->dev, "%s: got %d/%d (short not ok) => error\n",
|
|
__func__, req->actual, req->length);
|
|
/*
|
|
* todo - what should we return here? there's no one else
|
|
* even bothering to check the status.
|
|
*/
|
|
}
|
|
|
|
if (epnum == 0 && gg->ep0_state == AIC_EP0_DATA_OUT) {
|
|
/* Move to STATUS IN */
|
|
if (!gg->delayed_status) {
|
|
aic_ep0_program_zlp(gg, true);
|
|
ret = -EISCONN;
|
|
}
|
|
}
|
|
|
|
/* Set actual frame number for completed transfers */
|
|
if (a_ep->isochronous)
|
|
req->frame_number = gg->frame_number;
|
|
|
|
aic_ep_complete_request(gg, a_ep, a_req, ret);
|
|
}
|
|
|
|
/* func: aic_inep0_open
|
|
* gg->eps_in/out[0]->ep.desc(ep0) dose not have usb_endpoint_descriptor,
|
|
* using aic_ep_enable will cause NULL point.
|
|
* So use this func to enable ep0 IN_EP0_CFG.
|
|
*/
|
|
static void aic_inep0_open(struct aic_usb_gadget *gg)
|
|
{
|
|
u32 reg = 0;
|
|
unsigned int mps;
|
|
|
|
/* mark tx_fifo_map */
|
|
gg->tx_fifo_map |= (1 << 0);
|
|
|
|
mps = aic_ep0_mps(gg->eps_in[0]->ep.maxpacket);
|
|
|
|
reg = aic_readl(gg, INEPCFG0);
|
|
reg |= mps | EPCTL_SETD0PID | EPCTL_USBACTEP;
|
|
aic_writel(gg, reg, INEPCFG0);
|
|
|
|
/* Enable interrupts for EP0 in */
|
|
aic_ctrl_epint(gg, 0, 1, 1);
|
|
}
|
|
|
|
static int aic_npinep_rewrite(struct aic_usb_gadget *gg, unsigned int idx)
|
|
{
|
|
unsigned int pending_map = 0;
|
|
int i = 0, j = 0, fail = 0;
|
|
u32 reg = 0;
|
|
|
|
for (i = 0U, j = 0U; i < gg->params.num_ep; i++) {
|
|
reg = aic_readl(gg, INEPCFG(i));
|
|
if (!((gg->tx_fifo_map & (1 << i)) && (reg & EPCTL_EPENA)))
|
|
continue;
|
|
/* mark the fifo which need to be rewrite */
|
|
j++;
|
|
pending_map |= (1 << i);
|
|
}
|
|
|
|
/* Mismatch must be caused by two or more ep */
|
|
if (j <= 1)
|
|
return 0;
|
|
|
|
dev_dbg(gg->dev, "mismatch ep pending_map:0x%x\n", pending_map);
|
|
|
|
/* (1) close all no-periodic ep */
|
|
/* (1.1) Set Global In NP NAK in Shared FIFO for non periodic ep */
|
|
reg = aic_readl(gg, USBINTSTS);
|
|
if (!(reg & USBINTSTS_GINNAKEFF)) {
|
|
reg = aic_readl(gg, USBDEVFUNC);
|
|
reg |= USBDEVFUNC_SGNPINNAK;
|
|
aic_writel(gg, reg, USBDEVFUNC);
|
|
} else {
|
|
dev_err(gg->dev,
|
|
"%s GNINAKEFF is not 0, reg:0x%x, check bit:0x%x\n",
|
|
__func__, reg, (unsigned int)(reg & USBINTSTS_GINNAKEFF));
|
|
}
|
|
|
|
for (i = 0; i < DIS_EP_TIMOUT; i++) {
|
|
reg = aic_readl(gg, USBINTSTS);
|
|
/* wait for SGNPINNAK to take effect, this bit will be set to 1 */
|
|
if (reg & USBINTSTS_GINNAKEFF)
|
|
break;
|
|
udelay(1);
|
|
}
|
|
dev_dbg(gg->dev, "USBINTSTS(0x%x) val:0x%x\n", (unsigned int)USBINTSTS, reg);
|
|
|
|
if (i == DIS_EP_TIMOUT)
|
|
dev_err(gg->dev, "%s timeout USBINTSTS.GOUTNAKEFF\n", __func__);
|
|
|
|
/* (1.2) Disable ep */
|
|
for (i = 0U; i < gg->params.num_ep; i++) {
|
|
reg = aic_readl(gg, INEPCFG(i));
|
|
if (!((gg->tx_fifo_map & (1 << i)) && (reg & EPCTL_EPENA)))
|
|
continue;
|
|
|
|
/* set NACK and disabled ep */
|
|
reg |= EPCTL_SNAK;
|
|
reg |= EPCTL_EPDIS;
|
|
aic_writel(gg, reg, INEPCFG(i));
|
|
}
|
|
|
|
/* check if ep disabled complete */
|
|
for (j = 0; j < DIS_EP_TIMOUT; j++) {
|
|
fail = 0;
|
|
for (i = 0U; i < gg->params.num_ep; i++) {
|
|
if (!(pending_map & (1 << i)))
|
|
continue;
|
|
/* wait for ep disabled finish */
|
|
reg = aic_readl(gg, INEPINT(i));
|
|
if (!(reg & EPINT_EPDISBLD)) {
|
|
fail = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!fail)
|
|
break;
|
|
udelay(1);
|
|
}
|
|
|
|
if (j == DIS_EP_TIMOUT)
|
|
dev_err(gg->dev,
|
|
"%s ep%d timeout OUTEPCFG.EPDisable\n", __func__, fail - 1);
|
|
|
|
/* Clear EPDISBLD interrupt */
|
|
for (i = 0U; i < gg->params.num_ep; i++) {
|
|
if (!(pending_map & (1 << i)))
|
|
continue;
|
|
reg = aic_readl(gg, INEPINT(i));
|
|
reg |= EPINT_EPDISBLD;
|
|
aic_writel(gg, reg, INEPINT(i));
|
|
}
|
|
|
|
/* (1.3) Flush TX FIFO0 */
|
|
aic_flush_tx_fifo(gg, 0);
|
|
|
|
/* (1.4) Clear Global In NP NAK in Shared FIFO for non periodic ep */
|
|
reg = aic_readl(gg, USBDEVFUNC);
|
|
reg |= USBDEVFUNC_CGNPINNAK;
|
|
aic_writel(gg, reg, USBDEVFUNC);
|
|
|
|
/* (2) reopen current ep */
|
|
if (idx == 0)
|
|
aic_inep0_open(gg);
|
|
else
|
|
aic_ep_enable(&gg->eps_in[idx]->ep, gg->eps_in[idx]->ep.desc);
|
|
|
|
/* (3) rewrite current ep */
|
|
dev_dbg(gg->dev, "start req: ep%d, req:%p\n", idx, gg->eps_in[idx]->req);
|
|
if (pending_map & (1 << idx))
|
|
aic_ep_start_req(gg, gg->eps_in[idx], gg->eps_in[idx]->req, false);
|
|
|
|
/* (4) reopen & rewrite other ep, let's receive ep mismtach interrupt */
|
|
for (i = 0U; i < gg->params.num_ep; i++) {
|
|
if (!(pending_map & (1 << i)) || i == idx)
|
|
continue;
|
|
dev_dbg(gg->dev, "deal with another ep%d\n", i);
|
|
if (i == 0)
|
|
aic_inep0_open(gg);
|
|
else
|
|
aic_ep_enable(&gg->eps_in[i]->ep, gg->eps_in[i]->ep.desc);
|
|
|
|
aic_ep_start_req(gg, gg->eps_in[i], gg->eps_in[i]->req, true);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void aic_epint_irq(struct aic_usb_gadget *gg, unsigned int idx,
|
|
int dir_in)
|
|
{
|
|
struct aic_usb_ep *a_ep = index_to_ep(gg, idx, dir_in);
|
|
u32 int_addr = dir_in ? INEPINT(idx) : OUTEPINT(idx);
|
|
u32 ctl_addr = dir_in ? INEPCFG(idx) : OUTEPCFG(idx);
|
|
u32 siz_addr = dir_in ? INEPTSFSIZ(idx) : OUTEPTSFSIZ(idx);
|
|
u32 ints;
|
|
u32 ctrl;
|
|
|
|
if (!a_ep) {
|
|
dev_err(gg->dev, "%s:Interrupt for unconfigured ep%d(%s)\n",
|
|
__func__, idx, dir_in ? "in" : "out");
|
|
return;
|
|
}
|
|
|
|
/* read interrupt status */
|
|
ints = aic_read_ep_interrupts(gg, idx, dir_in);
|
|
ctrl = aic_readl(gg, ctl_addr);
|
|
dev_dbg(gg->dev, "%s: ep%d(%s) DxEPINT=0x%08x\n",
|
|
__func__, idx, dir_in ? "in" : "out", ints);
|
|
|
|
/* clear interrupt status */
|
|
aic_writel(gg, ints, int_addr);
|
|
|
|
/* Don't process XferCompl interrupt if it is a setup packet */
|
|
if (idx == 0 && (ints & (EPINT_SETUP | EPINT_SETUP_RCVD)))
|
|
ints &= ~EPINT_XFERCOMPL;
|
|
|
|
if (ints & EPINT_XFERCOMPL) {
|
|
dev_dbg(gg->dev,
|
|
"%s: XferCompl: DxEPCTL=0x%08x, EPTSIZ=%08x\n",
|
|
__func__, aic_readl(gg, ctl_addr),
|
|
aic_readl(gg, siz_addr));
|
|
|
|
if (dir_in) {
|
|
/*
|
|
* We get OutDone from the FIFO, so we only
|
|
* need to look at completing IN requests here
|
|
* if operating slave mode
|
|
*/
|
|
if (a_ep->isochronous && a_ep->interval > 1)
|
|
aic_incr_frame_num(a_ep);
|
|
|
|
aic_epint_handle_complete_in(gg, a_ep);
|
|
if (ints & EPINT_NAKINTRPT)
|
|
ints &= ~EPINT_NAKINTRPT;
|
|
|
|
if (idx == 0 && !a_ep->req)
|
|
aic_ep0_enqueue_setup(gg);
|
|
} else {
|
|
/*
|
|
* We're using DMA, we need to fire an OutDone here
|
|
* as we ignore the RXFIFO.
|
|
*/
|
|
if (a_ep->isochronous && a_ep->interval > 1)
|
|
aic_incr_frame_num(a_ep);
|
|
|
|
aic_epint_handle_outdone(gg, idx);
|
|
}
|
|
}
|
|
|
|
if (ints & EPINT_EPDISBLD)
|
|
aic_epint_handle_ep_disabled(a_ep);
|
|
|
|
if (ints & EPINT_OUTTKNEPDIS)
|
|
aic_epint_handle_outtoken_ep_disabled(a_ep);
|
|
|
|
if (ints & EPINT_NAKINTRPT)
|
|
aic_epint_handle_nak(a_ep);
|
|
|
|
if (ints & EPINT_AHBERR)
|
|
dev_dbg(gg->dev, "%s: AHBErr\n", __func__);
|
|
|
|
if (ints & EPINT_SETUP) { /* Setup or Timeout */
|
|
dev_dbg(gg->dev, "%s: Setup/Timeout\n", __func__);
|
|
|
|
if (idx == 0) {
|
|
/*
|
|
* this is the notification we've received a
|
|
* setup packet. In non-DMA mode we'd get this
|
|
* from the RXFIFO, instead we need to process
|
|
* the setup here.
|
|
*/
|
|
|
|
if (dir_in)
|
|
WARN_ON_ONCE(1);
|
|
else
|
|
aic_epint_handle_outdone(gg, 0);
|
|
}
|
|
}
|
|
|
|
if (ints & EPINT_STSPHSERCVD)
|
|
dev_dbg(gg->dev, "%s: StsPhseRcvd\n", __func__);
|
|
|
|
if (ints & EPINT_BACK2BACKSETUP)
|
|
dev_dbg(gg->dev, "%s: B2BSetup/INEPNakEff\n", __func__);
|
|
|
|
if (dir_in && !a_ep->isochronous) {
|
|
/* not sure if this is important, but we'll clear it anyway */
|
|
if (ints & EPINT_INTKNTXFEMP) {
|
|
dev_dbg(gg->dev, "%s: ep%d: INTknTXFEmpMsk\n",
|
|
__func__, idx);
|
|
}
|
|
|
|
/* this probably means something bad is happening */
|
|
if (ints & EPINT_INTKNEPMIS) {
|
|
dev_warn(gg->dev, "%s: ep%d: INTknEP\n",
|
|
__func__, idx);
|
|
aic_npinep_rewrite(gg, idx);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
static void aic_irq_handle_epint(struct aic_usb_gadget *gg)
|
|
{
|
|
u32 int_val = aic_readl(gg, USBEPINT);
|
|
u32 mask = aic_readl(gg, USBEPINTMSK);
|
|
u32 int_out, int_in;
|
|
int i;
|
|
|
|
int_val &= mask;
|
|
int_out = int_val >> USBEPINT_OUTEP_SHIFT;
|
|
int_in = int_val & 0xFFFF;
|
|
|
|
dev_dbg(gg->dev, "%s: int=%08x\n", __func__, int_val);
|
|
|
|
for (i = 0; i < gg->params.num_ep && int_out;
|
|
i++, int_out >>= 1) {
|
|
if (int_out & 1)
|
|
aic_epint_irq(gg, i, 0);
|
|
}
|
|
|
|
for (i = 0; i < gg->params.num_ep && int_in;
|
|
i++, int_in >>= 1) {
|
|
if (int_in & 1)
|
|
aic_epint_irq(gg, i, 1);
|
|
}
|
|
}
|
|
|
|
static void aic_irq_handle_enumdone(struct aic_usb_gadget *gg)
|
|
{
|
|
u32 reg = 0;
|
|
int ep0_mps = 0;
|
|
int ep_mps = 8;
|
|
|
|
/* clear interrupt status */
|
|
aic_writel(gg, USBINTSTS_ENUMDONE, USBINTSTS);
|
|
|
|
/* read enumerated speed */
|
|
reg = aic_readl(gg, USBLINESTS);
|
|
dev_dbg(gg->dev, "EnumDone (USBLINESTS=0x%08x)\n", reg);
|
|
|
|
switch ((reg & USBLINESTS_ENUMSPD_MASK) >> USBLINESTS_ENUMSPD_SHIFT) {
|
|
case USBLINESTS_ENUMSPD_FS:
|
|
case USBLINESTS_ENUMSPD_FS48:
|
|
gg->gadget.speed = USB_SPEED_FULL;
|
|
ep0_mps = EP0_MPS_LIMIT;
|
|
ep_mps = 1023;
|
|
break;
|
|
|
|
case USBLINESTS_ENUMSPD_HS:
|
|
gg->gadget.speed = USB_SPEED_HIGH;
|
|
ep0_mps = EP0_MPS_LIMIT;
|
|
ep_mps = 1024;
|
|
break;
|
|
|
|
case USBLINESTS_ENUMSPD_LS:
|
|
gg->gadget.speed = USB_SPEED_LOW;
|
|
ep0_mps = 8;
|
|
ep_mps = 8;
|
|
break;
|
|
}
|
|
dev_info(gg->dev, "new device is %s\n",
|
|
usb_speed_string(gg->gadget.speed));
|
|
|
|
/* update endpoint maxpacket */
|
|
if (ep0_mps) {
|
|
int i;
|
|
/* Initialize ep0 for both in and out directions */
|
|
aic_set_ep_maxpacket(gg, 0, ep0_mps, 0, 1);
|
|
aic_set_ep_maxpacket(gg, 0, ep0_mps, 0, 0);
|
|
for (i = 1; i < gg->params.num_ep; i++) {
|
|
if (gg->eps_in[i])
|
|
aic_set_ep_maxpacket(gg, i, ep_mps,
|
|
0, 1);
|
|
if (gg->eps_out[i])
|
|
aic_set_ep_maxpacket(gg, i, ep_mps,
|
|
0, 0);
|
|
}
|
|
}
|
|
|
|
/* ep0 request */
|
|
aic_ep0_enqueue_setup(gg);
|
|
dev_dbg(gg->dev, "EP0: INEPCFG0=0x%08x, OUTEPCFG0=0x%08x\n",
|
|
aic_readl(gg, INEPCFG0),
|
|
aic_readl(gg, OUTEPCFG0));
|
|
}
|
|
|
|
static void aic_irq_handle_usbrst(struct aic_usb_gadget *gg)
|
|
{
|
|
/* clear interrupt status */
|
|
aic_writel(gg, USBINTSTS_USBRST, USBINTSTS);
|
|
|
|
dev_dbg(gg->dev, "%s: USBRst\n", __func__);
|
|
dev_dbg(gg->dev, "NPTXFIFOSTS=%08x\n",
|
|
aic_readl(gg, NPTXFIFOSTS));
|
|
|
|
/* Reset device address to zero */
|
|
aic_clear_bit(gg, USBDEVCONF, USBDEVCONF_DEVADDR_MASK);
|
|
|
|
if (gg->connected) {
|
|
/* cleanup endpoints request */
|
|
aic_core_disconnect(gg);
|
|
/* reinit hardware core register */
|
|
aic_core_init(gg, true);
|
|
}
|
|
}
|
|
|
|
static void aic_irq_handle_goutnak(struct aic_usb_gadget *gg)
|
|
{
|
|
u8 idx;
|
|
u32 epctrl;
|
|
u32 gintmsk;
|
|
u32 daintmsk;
|
|
struct aic_usb_ep *a_ep;
|
|
|
|
daintmsk = aic_readl(gg, USBEPINTMSK);
|
|
daintmsk >>= USBEPINT_OUTEP_SHIFT;
|
|
/* Mask this interrupt */
|
|
gintmsk = aic_readl(gg, USBINTMSK);
|
|
gintmsk &= ~USBINTSTS_GOUTNAKEFF;
|
|
aic_writel(gg, gintmsk, USBINTMSK);
|
|
|
|
dev_dbg(gg->dev, "GOUTNakEff triggered\n");
|
|
for (idx = 1; idx < gg->params.num_ep; idx++) {
|
|
a_ep = gg->eps_out[idx];
|
|
/* Proceed only unmasked ISOC EPs */
|
|
if ((BIT(idx) & ~daintmsk) || !a_ep->isochronous)
|
|
continue;
|
|
|
|
epctrl = aic_readl(gg, OUTEPCFG(idx));
|
|
|
|
if (epctrl & EPCTL_EPENA) {
|
|
epctrl |= EPCTL_SNAK;
|
|
epctrl |= EPCTL_EPDIS;
|
|
aic_writel(gg, epctrl, OUTEPCFG(idx));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void aic_irq_handle_incomplete_isoc_in(struct aic_usb_gadget *gg)
|
|
{
|
|
struct aic_usb_ep *a_ep;
|
|
u32 epctrl;
|
|
u32 daintmsk;
|
|
u32 idx;
|
|
|
|
dev_dbg(gg->dev, "Incomplete isoc in interrupt received:\n");
|
|
|
|
/* Clear interrupt */
|
|
aic_writel(gg, USBINTSTS_INCOMPL_SOIN, USBINTSTS);
|
|
|
|
daintmsk = aic_readl(gg, USBEPINTMSK);
|
|
|
|
for (idx = 1; idx < gg->params.num_ep; idx++) {
|
|
a_ep = gg->eps_in[idx];
|
|
/* Proceed only unmasked ISOC EPs */
|
|
if ((BIT(idx) & ~daintmsk) || !a_ep->isochronous)
|
|
continue;
|
|
|
|
epctrl = aic_readl(gg, INEPCFG(idx));
|
|
if ((epctrl & EPCTL_EPENA) &&
|
|
aic_target_frame_elapsed(a_ep)) {
|
|
epctrl |= EPCTL_SNAK;
|
|
epctrl |= EPCTL_EPDIS;
|
|
aic_writel(gg, epctrl, INEPCFG(idx));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void aic_irq_handle_incomplete_isoc_out(struct aic_usb_gadget *gg)
|
|
{
|
|
u32 gintsts;
|
|
u32 gintmsk;
|
|
u32 daintmsk;
|
|
u32 epctrl;
|
|
struct aic_usb_ep *a_ep;
|
|
int idx;
|
|
|
|
dev_dbg(gg->dev, "%s: USBINTSTS_INCOMPL_SOOUT\n", __func__);
|
|
|
|
/* Clear interrupt */
|
|
aic_writel(gg, USBINTSTS_INCOMPL_SOOUT, USBINTSTS);
|
|
|
|
daintmsk = aic_readl(gg, USBEPINTMSK);
|
|
daintmsk >>= USBEPINT_OUTEP_SHIFT;
|
|
|
|
for (idx = 1; idx < gg->params.num_ep; idx++) {
|
|
a_ep = gg->eps_out[idx];
|
|
/* Proceed only unmasked ISOC EPs */
|
|
if ((BIT(idx) & ~daintmsk) || !a_ep->isochronous)
|
|
continue;
|
|
|
|
epctrl = aic_readl(gg, OUTEPCFG(idx));
|
|
if ((epctrl & EPCTL_EPENA) &&
|
|
aic_target_frame_elapsed(a_ep)) {
|
|
/* Unmask GOUTNAKEFF interrupt */
|
|
gintmsk = aic_readl(gg, USBINTMSK);
|
|
gintmsk |= USBINTSTS_GOUTNAKEFF;
|
|
aic_writel(gg, gintmsk, USBINTMSK);
|
|
|
|
gintsts = aic_readl(gg, USBINTSTS);
|
|
if (!(gintsts & USBINTSTS_GOUTNAKEFF)) {
|
|
aic_set_bit(gg, USBDEVFUNC,
|
|
USBDEVFUNC_SGOUTNAK);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static irqreturn_t aic_udc_irq(int irq, void *data)
|
|
{
|
|
struct aic_usb_gadget *gg = data;
|
|
u32 intsts = 0;
|
|
u32 intmsk = 0;
|
|
int retry = 8;
|
|
|
|
spin_lock(&gg->lock);
|
|
|
|
intsts = aic_readl(gg, USBINTSTS);
|
|
intmsk = aic_readl(gg, USBINTMSK);
|
|
|
|
dev_dbg(gg->dev, "%s: %08x %08x (%08x) retry %d\n",
|
|
__func__, intsts, intsts & intmsk, intmsk, retry);
|
|
|
|
if (intsts & USBINTSTS_USBRST)
|
|
aic_irq_handle_usbrst(gg);
|
|
|
|
if (intsts & USBINTSTS_ENUMDONE)
|
|
aic_irq_handle_enumdone(gg);
|
|
|
|
if (intsts & (USBINTSTS_OEPINT | USBINTSTS_IEPINT))
|
|
aic_irq_handle_epint(gg);
|
|
|
|
if (intsts & USBINTSTS_NPTXFEMP) {
|
|
dev_dbg(gg->dev, "NPTxFEmp\n");
|
|
aic_dis_gsint(gg, USBINTSTS_NPTXFEMP);
|
|
}
|
|
|
|
if (intsts & USBINTSTS_RXFLVL)
|
|
dev_dbg(gg->dev, "RxFLVL\n");
|
|
|
|
if (intsts & USBINTSTS_ERLYSUSP) {
|
|
dev_dbg(gg->dev, "ErlySusp\n");
|
|
aic_writel(gg, USBINTSTS_ERLYSUSP, USBINTSTS);
|
|
}
|
|
|
|
if (intsts & USBINTSTS_GOUTNAKEFF)
|
|
aic_irq_handle_goutnak(gg);
|
|
|
|
if (intsts & USBINTSTS_GINNAKEFF) {
|
|
dev_info(gg->dev, "GINNakEff triggered\n");
|
|
aic_set_bit(gg, USBDEVFUNC, USBDEVFUNC_CGNPINNAK);
|
|
}
|
|
|
|
if (intsts & USBINTSTS_INCOMPL_SOIN)
|
|
aic_irq_handle_incomplete_isoc_in(gg);
|
|
|
|
if (intsts & USBINTSTS_INCOMPL_SOOUT)
|
|
aic_irq_handle_incomplete_isoc_out(gg);
|
|
|
|
if (intsts & USBINTSTS_WKUPINT) {
|
|
dev_dbg(gg->dev, "WKUP\n");
|
|
aic_writel(gg, USBINTSTS_WKUPINT, USBINTSTS);
|
|
}
|
|
|
|
if (intsts & USBINTSTS_USBSUSP) {
|
|
dev_dbg(gg->dev, "USB SUSPEND\n");
|
|
aic_writel(gg, USBINTSTS_USBSUSP, USBINTSTS);
|
|
}
|
|
|
|
spin_unlock(&gg->lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int aic_handle_unaligned_req(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *a_ep,
|
|
struct aic_usb_req *a_req)
|
|
{
|
|
void *req_buf = a_req->req.buf;
|
|
|
|
/* If buffer is aligned */
|
|
if ((!((long)req_buf % L1_CACHE_BYTES) &&
|
|
!(a_req->req.length % L1_CACHE_BYTES))
|
|
|| (a_ep->index == 0))
|
|
return 0;
|
|
|
|
WARN_ON(a_req->saved_req_buf);
|
|
|
|
dev_dbg(gg->dev, "%s: %s: buf=%p length=%d\n", __func__,
|
|
a_ep->ep.name, req_buf, a_req->req.length);
|
|
|
|
a_req->req.buf = kmalloc(ALIGN(a_req->req.length, L1_CACHE_BYTES),
|
|
GFP_ATOMIC);
|
|
if (!a_req->req.buf) {
|
|
a_req->req.buf = req_buf;
|
|
dev_err(gg->dev,
|
|
"%s: unable to allocate memory for bounce buffer\n",
|
|
__func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Save actual buffer */
|
|
a_req->saved_req_buf = req_buf;
|
|
|
|
if (a_ep->dir_in)
|
|
memcpy(a_req->req.buf, req_buf, a_req->req.length);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
aic_handle_unaligned_req_complete(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *a_ep,
|
|
struct aic_usb_req *a_req)
|
|
{
|
|
/* If buffer was aligned */
|
|
if (!a_req->saved_req_buf)
|
|
return;
|
|
|
|
dev_dbg(gg->dev, "%s: %s: status=%d actual-length=%d\n", __func__,
|
|
a_ep->ep.name, a_req->req.status, a_req->req.actual);
|
|
|
|
/* Copy data from bounce buffer on successful out transfer */
|
|
if (!a_ep->dir_in && !a_req->req.status)
|
|
memcpy(a_req->saved_req_buf, a_req->req.buf,
|
|
a_req->req.actual);
|
|
|
|
/* Free bounce buffer */
|
|
kfree(a_req->req.buf);
|
|
|
|
a_req->req.buf = a_req->saved_req_buf;
|
|
a_req->saved_req_buf = NULL;
|
|
}
|
|
|
|
static void aic_ep_stop_xfer(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *a_ep)
|
|
{
|
|
u32 ctrl_addr;
|
|
u32 int_addr;
|
|
|
|
ctrl_addr = a_ep->dir_in ? INEPCFG(a_ep->index) :
|
|
OUTEPCFG(a_ep->index);
|
|
int_addr = a_ep->dir_in ? INEPINT(a_ep->index) :
|
|
OUTEPINT(a_ep->index);
|
|
|
|
dev_dbg(gg->dev, "%s: stopping transfer on %s\n", __func__,
|
|
a_ep->name);
|
|
|
|
if (a_ep->dir_in) {
|
|
if (a_ep->periodic) {
|
|
aic_set_bit(gg, ctrl_addr, EPCTL_SNAK);
|
|
/* Wait for Nak effect */
|
|
if (aic_wait_bit_set(gg, int_addr,
|
|
EPINT_INEPNAKEFF, 100))
|
|
dev_warn(gg->dev,
|
|
"%s: timeout INEPINT.NAKEFF\n",
|
|
__func__);
|
|
} else {
|
|
aic_set_bit(gg, USBDEVFUNC, USBDEVFUNC_SGNPINNAK);
|
|
/* Wait for Nak effect */
|
|
if (aic_wait_bit_set(gg, USBINTSTS,
|
|
USBINTSTS_GINNAKEFF, 100))
|
|
dev_warn(gg->dev,
|
|
"%s: timeout USBINTSTS.GINNAKEFF\n",
|
|
__func__);
|
|
}
|
|
} else {
|
|
if (!(aic_readl(gg, USBINTSTS) & USBINTSTS_GOUTNAKEFF))
|
|
aic_set_bit(gg, USBDEVFUNC, USBDEVFUNC_SGOUTNAK);
|
|
|
|
/* Wait for global nak to take effect */
|
|
if (aic_wait_bit_set(gg, USBINTSTS,
|
|
USBINTSTS_GOUTNAKEFF, 100))
|
|
dev_warn(gg->dev, "%s: timeout USBINTSTS.GOUTNAKEFF\n",
|
|
__func__);
|
|
}
|
|
|
|
/* Disable ep */
|
|
aic_set_bit(gg, ctrl_addr, EPCTL_EPDIS | EPCTL_SNAK);
|
|
|
|
/* Wait for ep to be disabled */
|
|
if (aic_wait_bit_set(gg, int_addr, EPINT_EPDISBLD, 100))
|
|
dev_warn(gg->dev,
|
|
"%s: timeout OUTEPCFG.EPDisable\n", __func__);
|
|
|
|
/* Clear EPDISBLD interrupt */
|
|
aic_set_bit(gg, int_addr, EPINT_EPDISBLD);
|
|
|
|
if (a_ep->dir_in) {
|
|
unsigned short fifo_index;
|
|
|
|
if (a_ep->periodic)
|
|
fifo_index = a_ep->fifo_index;
|
|
else
|
|
fifo_index = 0;
|
|
|
|
/* Flush TX FIFO */
|
|
aic_flush_tx_fifo(gg, fifo_index);
|
|
|
|
/* Clear Global In NP NAK in Shared FIFO for non periodic ep */
|
|
if (!a_ep->periodic)
|
|
aic_set_bit(gg, USBDEVFUNC, USBDEVFUNC_CGNPINNAK);
|
|
|
|
} else {
|
|
/* Remove global NAKs */
|
|
aic_set_bit(gg, USBDEVFUNC, USBDEVFUNC_CGOUTNAK);
|
|
}
|
|
}
|
|
|
|
static void aic_ep_start_req(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *a_ep,
|
|
struct aic_usb_req *a_req,
|
|
bool continuing)
|
|
{
|
|
struct usb_request *ureq = &a_req->req;
|
|
int index = a_ep->index;
|
|
int dir_in = a_ep->dir_in;
|
|
u32 dma_reg;
|
|
u32 epctrl_reg;
|
|
u32 epsize_reg;
|
|
u32 epsize;
|
|
u32 ctrl;
|
|
unsigned int length;
|
|
unsigned int packets;
|
|
unsigned int maxreq;
|
|
|
|
if (index != 0) {
|
|
if (a_ep->req && !continuing) {
|
|
dev_err(gg->dev, "%s: active request\n", __func__);
|
|
WARN_ON(1);
|
|
return;
|
|
} else if (a_ep->req != a_req && continuing) {
|
|
dev_err(gg->dev,
|
|
"%s: continue different req\n", __func__);
|
|
WARN_ON(1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
dma_reg = dir_in ? INEPDMAADDR(index) : OUTEPDMAADDR(index);
|
|
epctrl_reg = dir_in ? INEPCFG(index) : OUTEPCFG(index);
|
|
epsize_reg = dir_in ? INEPTSFSIZ(index) : OUTEPTSFSIZ(index);
|
|
|
|
/* stall ignore */
|
|
ctrl = aic_readl(gg, epctrl_reg);
|
|
if (index && ctrl & EPCTL_STALL) {
|
|
dev_warn(gg->dev, "%s: ep%d is stalled\n", __func__, index);
|
|
return;
|
|
}
|
|
dev_dbg(gg->dev, "%s: DxEPCTL=0x%08x, ep %d, dir %s\n",
|
|
__func__, ctrl, index,
|
|
a_ep->dir_in ? "in" : "out");
|
|
|
|
/* length */
|
|
length = ureq->length - ureq->actual;
|
|
maxreq = get_ep_limit(a_ep);
|
|
dev_dbg(gg->dev, "ureq->length:%d ureq->actual:%d\n",
|
|
ureq->length, ureq->actual);
|
|
if (length > maxreq) {
|
|
int round = maxreq % a_ep->ep.maxpacket;
|
|
|
|
dev_dbg(gg->dev, "%s: length %d, max-req %d, r %d\n",
|
|
__func__, length, maxreq, round);
|
|
if (round)
|
|
maxreq -= round;
|
|
length = maxreq;
|
|
}
|
|
|
|
/* multi count */
|
|
if (length)
|
|
packets = DIV_ROUND_UP(length, a_ep->ep.maxpacket);
|
|
else
|
|
packets = 1; /* send one packet if length is zero. */
|
|
if (dir_in && index != 0)
|
|
if (a_ep->isochronous)
|
|
epsize = EPTSIZ_MC(packets);
|
|
else
|
|
epsize = EPTSIZ_MC(1);
|
|
else
|
|
epsize = 0;
|
|
|
|
/* zero length packet */
|
|
if (dir_in && ureq->zero && !continuing) {
|
|
/* Test if zlp is actually required. */
|
|
if ((ureq->length >= a_ep->ep.maxpacket) &&
|
|
!(ureq->length % a_ep->ep.maxpacket))
|
|
a_ep->send_zlp = 1;
|
|
}
|
|
|
|
/* update size & dma */
|
|
epsize |= EPTSIZ_PKTCNT(packets);
|
|
epsize |= EPTSIZ_XFERSIZE(length);
|
|
aic_writel(gg, epsize, epsize_reg);
|
|
dev_dbg(gg->dev, "%s: %d@%d/%d, 0x%08x => 0x%08x\n",
|
|
__func__, packets, length, ureq->length, epsize, epsize_reg);
|
|
if (!continuing && (length != 0)) {
|
|
aic_writel(gg, ureq->dma, dma_reg);
|
|
dev_dbg(gg->dev, "%s: %pad => 0x%08x\n",
|
|
__func__, &ureq->dma, dma_reg);
|
|
}
|
|
a_ep->req = a_req;
|
|
|
|
/* update ctrl */
|
|
if (a_ep->isochronous && a_ep->interval == 1) {
|
|
a_ep->target_frame = aic_read_frameno(gg);
|
|
aic_incr_frame_num(a_ep);
|
|
if (a_ep->target_frame & 0x1)
|
|
ctrl |= EPCTL_SETODDFR;
|
|
else
|
|
ctrl |= EPCTL_SETEVENFR;
|
|
}
|
|
dev_dbg(gg->dev, "ep0 state:%d\n", gg->ep0_state);
|
|
if (!(index == 0 && gg->ep0_state == AIC_EP0_SETUP))
|
|
ctrl |= EPCTL_CNAK; /* clear NAK set by core */
|
|
ctrl |= EPCTL_EPENA; /* ensure ep enabled */
|
|
dev_dbg(gg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl);
|
|
aic_writel(gg, ctrl, epctrl_reg);
|
|
|
|
/* update counter */
|
|
a_ep->size_loaded = length;
|
|
a_ep->last_load = ureq->actual;
|
|
|
|
/* check ep is enabled */
|
|
if (!(aic_readl(gg, epctrl_reg) & EPCTL_EPENA))
|
|
dev_dbg(gg->dev,
|
|
"ep%d: failed to become enabled (EPCTL=0x%08x)?\n",
|
|
index, aic_readl(gg, epctrl_reg));
|
|
dev_dbg(gg->dev, "%s: DxEPCTL=0x%08x\n",
|
|
__func__, aic_readl(gg, epctrl_reg));
|
|
|
|
/* enable ep interrupts */
|
|
aic_ctrl_epint(gg, a_ep->index, a_ep->dir_in, 1);
|
|
}
|
|
|
|
static void aic_ep_start_next_request(struct aic_usb_ep *a_ep)
|
|
{
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
struct aic_usb_req *a_req;
|
|
int dir_in = a_ep->dir_in;
|
|
u32 epmsk_reg = dir_in ? INEPINTMSK : OUTEPINTMSK;
|
|
u32 mask;
|
|
|
|
if (!list_empty(&a_ep->queue)) {
|
|
a_req = get_ep_head(a_ep);
|
|
aic_ep_start_req(gg, a_ep, a_req, false);
|
|
return;
|
|
}
|
|
if (!a_ep->isochronous)
|
|
return;
|
|
|
|
if (dir_in) {
|
|
dev_dbg(gg->dev, "%s: No more ISOC-IN requests\n",
|
|
__func__);
|
|
} else {
|
|
dev_dbg(gg->dev, "%s: No more ISOC-OUT requests\n",
|
|
__func__);
|
|
mask = aic_readl(gg, epmsk_reg);
|
|
mask |= OUTEPINTMSK_OUTTKNEPDISMSK;
|
|
aic_writel(gg, mask, epmsk_reg);
|
|
}
|
|
}
|
|
|
|
static void aic_ep_complete_request(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *a_ep,
|
|
struct aic_usb_req *a_req,
|
|
int result)
|
|
{
|
|
if (!a_req) {
|
|
dev_dbg(gg->dev, "%s: nothing to complete?\n", __func__);
|
|
return;
|
|
}
|
|
|
|
dev_dbg(gg->dev, "complete: ep %p %s, req %p, %d => %p\n",
|
|
a_ep, a_ep->ep.name, a_req, result, a_req->req.complete);
|
|
|
|
if (a_req->req.status == -EINPROGRESS)
|
|
a_req->req.status = result;
|
|
|
|
/* unmap dma */
|
|
usb_gadget_unmap_request(&gg->gadget, &a_req->req, a_ep->dir_in);
|
|
|
|
/* unalign clear */
|
|
aic_handle_unaligned_req_complete(gg, a_ep, a_req);
|
|
|
|
/* dequeue */
|
|
/* When the device receives a out-data packet, device needs to send
|
|
* an empty in-status packet. Due to using aic_ep0_program_zlp()
|
|
* instead of ep_queue() which will result in gg->eps_in/out[]->req
|
|
* being cleared. To proceed with the next transfer, it is necessary
|
|
* to alloc a new req.
|
|
* When handling mismatch function, aic_npinep_rewrite() use the old
|
|
* req instead of allocing a new req. When using aic_ep0_program_zlp()
|
|
* to send a in-status packet to host, aic_ep_start_req() which was
|
|
* called by aic_ep_start_req, can not send in-status packet bacause
|
|
* req is NULL.
|
|
* So, when not alloc req to send empty packet, do not clear req.
|
|
*/
|
|
if (result != -EISCONN) {
|
|
a_ep->req = NULL;
|
|
result = 0;
|
|
}
|
|
list_del_init(&a_req->queue);
|
|
|
|
/* call complete function */
|
|
if (a_req->req.complete) {
|
|
spin_unlock(&gg->lock);
|
|
usb_gadget_giveback_request(&a_ep->ep, &a_req->req);
|
|
spin_lock(&gg->lock);
|
|
}
|
|
|
|
/* restart request */
|
|
if (!a_ep->req && result >= 0)
|
|
aic_ep_start_next_request(a_ep);
|
|
}
|
|
|
|
static void aic_stall_ep0(struct aic_usb_gadget *gg)
|
|
{
|
|
struct aic_usb_ep *ep0 = gg->eps_out[0];
|
|
u32 reg;
|
|
u32 ctrl;
|
|
|
|
dev_dbg(gg->dev, "ep0 stall (dir=%d)\n", ep0->dir_in);
|
|
reg = (ep0->dir_in) ? INEPCFG0 : OUTEPCFG0;
|
|
|
|
/*
|
|
* DxEPCTL_Stall will be cleared by EP once it has
|
|
* taken effect, so no need to clear later.
|
|
*/
|
|
|
|
ctrl = aic_readl(gg, reg);
|
|
ctrl |= EPCTL_STALL;
|
|
ctrl |= EPCTL_CNAK;
|
|
aic_writel(gg, ctrl, reg);
|
|
|
|
dev_dbg(gg->dev,
|
|
"written EPCTL=0x%08x to %08x (EPCTL=0x%08x)\n",
|
|
ctrl, reg, aic_readl(gg, reg));
|
|
|
|
/* ep0 request */
|
|
aic_ep0_enqueue_setup(gg);
|
|
}
|
|
|
|
static int aic_ep_sethalt_nolock(struct usb_ep *ep, int value, bool now)
|
|
{
|
|
struct aic_usb_ep *a_ep = our_ep(ep);
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
int index = a_ep->index;
|
|
u32 epreg;
|
|
u32 epctl;
|
|
u32 xfertype;
|
|
|
|
dev_info(gg->dev, "%s(ep %p %s, %d)\n", __func__, ep, ep->name, value);
|
|
|
|
if (index == 0) {
|
|
if (value)
|
|
aic_stall_ep0(gg);
|
|
else
|
|
dev_warn(gg->dev,
|
|
"%s: can't clear halt on ep0\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (a_ep->isochronous) {
|
|
dev_err(gg->dev, "%s is Isochronous Endpoint\n", ep->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!now && value && !list_empty(&a_ep->queue)) {
|
|
dev_dbg(gg->dev, "%s request is pending, cannot halt\n",
|
|
ep->name);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (a_ep->dir_in) {
|
|
epreg = INEPCFG(index);
|
|
epctl = aic_readl(gg, epreg);
|
|
|
|
if (value) {
|
|
epctl |= EPCTL_STALL | EPCTL_SNAK;
|
|
if (epctl & EPCTL_EPENA)
|
|
epctl |= EPCTL_EPDIS;
|
|
} else {
|
|
epctl &= ~EPCTL_STALL;
|
|
xfertype = epctl & EPCTL_EPTYPE_MASK;
|
|
if (xfertype == EPCTL_EPTYPE_BULK ||
|
|
xfertype == EPCTL_EPTYPE_INTERRUPT)
|
|
epctl |= EPCTL_SETD0PID;
|
|
}
|
|
aic_writel(gg, epctl, epreg);
|
|
} else {
|
|
epreg = OUTEPCFG(index);
|
|
epctl = aic_readl(gg, epreg);
|
|
|
|
if (value) {
|
|
epctl |= EPCTL_STALL;
|
|
} else {
|
|
epctl &= ~EPCTL_STALL;
|
|
xfertype = epctl & EPCTL_EPTYPE_MASK;
|
|
if (xfertype == EPCTL_EPTYPE_BULK ||
|
|
xfertype == EPCTL_EPTYPE_INTERRUPT)
|
|
epctl |= EPCTL_SETD0PID;
|
|
}
|
|
aic_writel(gg, epctl, epreg);
|
|
}
|
|
|
|
a_ep->halted = value;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_ep_sethalt(struct usb_ep *ep, int value)
|
|
{
|
|
struct aic_usb_ep *a_ep = our_ep(ep);
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
/* Sometime ADBD set ep0 halt for unknown reason, and no release halt,
|
|
mask this operation temporary.
|
|
*/
|
|
return 0;
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
ret = aic_ep_sethalt_nolock(ep, value, false);
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aic_ep_dequeue_request(struct usb_ep *ep, struct usb_request *req)
|
|
{
|
|
struct aic_usb_req *a_req = our_req(req);
|
|
struct aic_usb_ep *a_ep = our_ep(ep);
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
unsigned long flags;
|
|
|
|
dev_dbg(gg->dev, "ep_dequeue(%p,%p)\n", ep, req);
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
|
|
if (!on_list(a_ep, a_req)) {
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Dequeue already started request */
|
|
if (req == &a_ep->req->req)
|
|
aic_ep_stop_xfer(gg, a_ep);
|
|
|
|
aic_ep_complete_request(gg, a_ep, a_req, -ECONNRESET);
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_ep_queue_request_nolock(struct usb_ep *ep,
|
|
struct usb_request *req,
|
|
gfp_t gfp_flags)
|
|
{
|
|
struct aic_usb_req *a_req = our_req(req);
|
|
struct aic_usb_ep *a_ep = our_ep(ep);
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
bool first;
|
|
int ret = 0;
|
|
|
|
/* Don't queue ISOC request if length greater than mps*mc */
|
|
if (a_ep->isochronous &&
|
|
req->length > (a_ep->mc * a_ep->ep.maxpacket)) {
|
|
dev_err(gg->dev, "req length > maxpacket*mc\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(gg->dev, "%s: req %p: %d@%p, noi=%d, zero=%d, snok=%d\n",
|
|
ep->name, req, req->length, req->buf, req->no_interrupt,
|
|
req->zero, req->short_not_ok);
|
|
|
|
/* init request */
|
|
INIT_LIST_HEAD(&a_req->queue);
|
|
req->actual = 0;
|
|
req->status = -EINPROGRESS;
|
|
|
|
/* unalign address */
|
|
ret = aic_handle_unaligned_req(gg, a_ep, a_req);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* map dma */
|
|
ret = usb_gadget_map_request(&gg->gadget, req, a_ep->dir_in);
|
|
if (ret) {
|
|
dev_err(gg->dev, "%s: failed to map buffer %p, %d bytes\n",
|
|
__func__, req->buf, req->length);
|
|
return ret;
|
|
}
|
|
|
|
/* enqueue */
|
|
first = list_empty(&a_ep->queue);
|
|
list_add_tail(&a_req->queue, &a_ep->queue);
|
|
|
|
/* Change EP0 direction if status phase request is after data out */
|
|
if (!a_ep->index && !req->length && !a_ep->dir_in &&
|
|
gg->ep0_state == AIC_EP0_DATA_OUT)
|
|
a_ep->dir_in = 1;
|
|
|
|
/* start transfer */
|
|
if (first) {
|
|
if (!a_ep->isochronous) {
|
|
aic_ep_start_req(gg, a_ep, a_req, false);
|
|
return 0;
|
|
}
|
|
|
|
/* Update current frame number value. */
|
|
gg->frame_number = aic_read_frameno(gg);
|
|
while (aic_target_frame_elapsed(a_ep)) {
|
|
aic_incr_frame_num(a_ep);
|
|
/* Update current frame number value once more as it
|
|
* changes here.
|
|
*/
|
|
gg->frame_number = aic_read_frameno(gg);
|
|
}
|
|
|
|
if (a_ep->target_frame != TARGET_FRAME_INITIAL)
|
|
aic_ep_start_req(gg, a_ep, a_req, false);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int aic_ep_queue_request(struct usb_ep *ep, struct usb_request *req,
|
|
gfp_t gfp_flags)
|
|
{
|
|
struct aic_usb_ep *a_ep = our_ep(ep);
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
ret = aic_ep_queue_request_nolock(ep, req, gfp_flags);
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aic_ep_disable_nolock(struct usb_ep *ep)
|
|
{
|
|
struct aic_usb_ep *a_ep = our_ep(ep);
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
unsigned int index = a_ep->index;
|
|
u32 dir_in = a_ep->dir_in;
|
|
u32 ctrl_addr;
|
|
u32 ctrl;
|
|
|
|
dev_dbg(gg->dev, "%s(ep %p)\n", __func__, ep);
|
|
|
|
/* ep0 don't use by app driver */
|
|
if (ep == &gg->eps_out[0]->ep) {
|
|
dev_err(gg->dev, "%s: called for ep0\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctrl_addr = dir_in ? INEPCFG(index) : OUTEPCFG(index);
|
|
ctrl = aic_readl(gg, ctrl_addr);
|
|
|
|
if (ctrl & EPCTL_EPENA)
|
|
aic_ep_stop_xfer(gg, a_ep);
|
|
|
|
ctrl &= ~EPCTL_EPENA;
|
|
ctrl &= ~EPCTL_USBACTEP;
|
|
ctrl |= EPCTL_SNAK;
|
|
aic_writel(gg, ctrl, ctrl_addr);
|
|
dev_dbg(gg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl);
|
|
|
|
/* disable endpoint interrupts */
|
|
aic_ctrl_epint(gg, a_ep->index, a_ep->dir_in, 0);
|
|
|
|
/* terminate all requests with shutdown */
|
|
aic_kill_ep_reqs(gg, a_ep, -ESHUTDOWN);
|
|
|
|
/* free fifo */
|
|
gg->fifo_map &= ~(1 << a_ep->fifo_index);
|
|
gg->tx_fifo_map &= ~(1 << a_ep->index);
|
|
a_ep->fifo_index = 0;
|
|
a_ep->fifo_size = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_ep_disable(struct usb_ep *ep)
|
|
{
|
|
struct aic_usb_ep *a_ep = our_ep(ep);
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
ret = aic_ep_disable_nolock(ep);
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
static int aic_ep_enable(struct usb_ep *ep,
|
|
const struct usb_endpoint_descriptor *desc)
|
|
{
|
|
struct aic_usb_ep *a_ep = our_ep(ep);
|
|
struct aic_usb_gadget *gg = a_ep->parent;
|
|
unsigned int index = a_ep->index;
|
|
unsigned long flags;
|
|
u32 dir_in;
|
|
u32 mps;
|
|
u32 mc;
|
|
u32 ep_type;
|
|
u32 ctrl_addr;
|
|
u32 ctrl;
|
|
u32 mask;
|
|
int i;
|
|
int ret = 0;
|
|
|
|
dev_dbg(gg->dev,
|
|
"%s: ep %s: a 0x%02x, attr 0x%02x, mps 0x%04x, intr %d\n",
|
|
__func__, ep->name, desc->bEndpointAddress, desc->bmAttributes,
|
|
desc->wMaxPacketSize, desc->bInterval);
|
|
|
|
/* ep0 don't use by app driver */
|
|
if (index == 0) {
|
|
dev_err(gg->dev, "%s: called for EP0\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dir_in = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? 1 : 0;
|
|
if (dir_in != a_ep->dir_in) {
|
|
dev_err(gg->dev, "%s: direction mismatch!\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
|
|
/* (0) read ep ctrl */
|
|
ctrl_addr = dir_in ? INEPCFG(index) : OUTEPCFG(index);
|
|
ctrl = aic_readl(gg, ctrl_addr);
|
|
dev_dbg(gg->dev, "%s: read DxEPCTL=0x%08x from 0x%08x\n",
|
|
__func__, ctrl, ctrl_addr);
|
|
|
|
/* (1) max packet */
|
|
mps = usb_endpoint_maxp(desc);
|
|
mc = usb_endpoint_maxp_mult(desc);
|
|
|
|
aic_set_ep_maxpacket(gg, a_ep->index, mps, mc, dir_in);
|
|
|
|
ctrl &= ~(EPCTL_EPTYPE_MASK | EPCTL_MPS_MASK);
|
|
ctrl |= EPCTL_MPS(mps);
|
|
ctrl |= EPCTL_USBACTEP;
|
|
|
|
/* (2) endpoint type */
|
|
ep_type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
|
|
a_ep->isochronous = 0;
|
|
a_ep->periodic = 0;
|
|
a_ep->halted = 0;
|
|
a_ep->interval = desc->bInterval;
|
|
switch (ep_type) {
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
ctrl |= EPCTL_EPTYPE_ISO;
|
|
ctrl |= EPCTL_SETEVENFR;
|
|
a_ep->isochronous = 1;
|
|
a_ep->interval = 1 << (desc->bInterval - 1);
|
|
a_ep->target_frame = TARGET_FRAME_INITIAL;
|
|
if (dir_in) {
|
|
a_ep->periodic = 1;
|
|
mask = aic_readl(gg, INEPINTMSK);
|
|
mask |= INEPINTMSK_NAKMSK;
|
|
aic_writel(gg, mask, INEPINTMSK);
|
|
} else {
|
|
mask = aic_readl(gg, OUTEPINTMSK);
|
|
mask |= OUTEPINTMSK_OUTTKNEPDISMSK;
|
|
aic_writel(gg, mask, OUTEPINTMSK);
|
|
}
|
|
break;
|
|
|
|
case USB_ENDPOINT_XFER_BULK:
|
|
ctrl |= EPCTL_EPTYPE_BULK;
|
|
break;
|
|
|
|
case USB_ENDPOINT_XFER_INT:
|
|
if (dir_in)
|
|
a_ep->periodic = 1;
|
|
|
|
if (gg->gadget.speed == USB_SPEED_HIGH)
|
|
a_ep->interval = 1 << (desc->bInterval - 1);
|
|
|
|
ctrl |= EPCTL_EPTYPE_INTERRUPT;
|
|
break;
|
|
|
|
case USB_ENDPOINT_XFER_CONTROL:
|
|
ctrl |= EPCTL_EPTYPE_CONTROL;
|
|
break;
|
|
}
|
|
|
|
/* (3) period IN ep alloc fifo */
|
|
if (dir_in && ((ep_type == USB_ENDPOINT_XFER_INT) ||
|
|
(ep_type == USB_ENDPOINT_XFER_ISOC))) {
|
|
u32 fifo_index = 0;
|
|
u32 fifo_size = UINT_MAX;
|
|
u32 val = 0;
|
|
u32 size = 0;
|
|
|
|
size = a_ep->ep.maxpacket * a_ep->mc;
|
|
for (i = 1; i <= gg->params.num_perio_in_ep; i++) {
|
|
if (gg->fifo_map & (1 << i))
|
|
continue;
|
|
val = aic_readl(gg, TXFIFOSIZ(i));
|
|
val = (val >> FIFOSIZE_DEPTH_SHIFT) * 4;
|
|
if (val < size)
|
|
continue;
|
|
/* Search for smallest acceptable fifo */
|
|
if (val < fifo_size) {
|
|
fifo_size = val;
|
|
fifo_index = i;
|
|
}
|
|
}
|
|
if (!fifo_index) {
|
|
dev_err(gg->dev,
|
|
"%s: No suitable fifo found\n", __func__);
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
ctrl &= ~(EPCTL_TXFNUM_LIMIT << EPCTL_TXFNUM_SHIFT);
|
|
ctrl |= EPCTL_TXFNUM(fifo_index);
|
|
gg->fifo_map |= 1 << fifo_index;
|
|
a_ep->fifo_index = fifo_index;
|
|
a_ep->fifo_size = fifo_size;
|
|
} else {
|
|
if (dir_in)
|
|
gg->tx_fifo_map |= 1 << index;
|
|
}
|
|
|
|
/* (4) for non iso endpoints, set PID to D0 */
|
|
if (index && !a_ep->isochronous)
|
|
ctrl |= EPCTL_SETD0PID;
|
|
|
|
/* (5) clear NAK */
|
|
if (gg->gadget.speed == USB_SPEED_FULL &&
|
|
a_ep->isochronous && dir_in) {
|
|
ctrl |= EPCTL_CNAK;
|
|
}
|
|
|
|
/* (6) write-back ep ctrl */
|
|
dev_dbg(gg->dev, "%s: write DxEPCTL=0x%08x\n",
|
|
__func__, ctrl);
|
|
aic_writel(gg, ctrl, ctrl_addr);
|
|
dev_dbg(gg->dev, "%s: read DxEPCTL=0x%08x\n",
|
|
__func__, aic_readl(gg, ctrl_addr));
|
|
|
|
/* (7) enable the endpoint interrupt */
|
|
aic_ctrl_epint(gg, index, dir_in, 1);
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct usb_request *aic_ep_alloc_request(struct usb_ep *ep,
|
|
gfp_t flags)
|
|
{
|
|
struct aic_usb_req *req;
|
|
|
|
req = kzalloc(sizeof(*req), flags);
|
|
if (!req)
|
|
return NULL;
|
|
|
|
INIT_LIST_HEAD(&req->queue);
|
|
|
|
return &req->req;
|
|
}
|
|
|
|
static void aic_ep_free_request(struct usb_ep *ep,
|
|
struct usb_request *req)
|
|
{
|
|
struct aic_usb_req *aic_req = our_req(req);
|
|
|
|
kfree(aic_req);
|
|
}
|
|
|
|
static const struct usb_ep_ops aic_usb_ep_ops = {
|
|
.enable = aic_ep_enable,
|
|
.disable = aic_ep_disable,
|
|
.alloc_request = aic_ep_alloc_request,
|
|
.free_request = aic_ep_free_request,
|
|
.queue = aic_ep_queue_request,
|
|
.dequeue = aic_ep_dequeue_request,
|
|
.set_halt = aic_ep_sethalt,
|
|
};
|
|
|
|
static int aic_gg_getframe(struct usb_gadget *gadget)
|
|
{
|
|
return aic_read_frameno(our_gadget(gadget));
|
|
}
|
|
|
|
static int aic_gg_vbus_draw(struct usb_gadget *gadget, unsigned int mA)
|
|
{
|
|
struct aic_usb_gadget *gg = our_gadget(gadget);
|
|
|
|
if (IS_ERR_OR_NULL(gg->uphy))
|
|
return -ENOTSUPP;
|
|
return usb_phy_set_power(gg->uphy, mA);
|
|
}
|
|
|
|
static int aic_gg_vbus_session(struct usb_gadget *gadget, int is_active)
|
|
{
|
|
struct aic_usb_gadget *gg = our_gadget(gadget);
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
if (!gg) {
|
|
pr_err("%s: called with no device\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
dev_dbg(gg->dev, "%s: is_active: %d\n", __func__, is_active);
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
|
|
if (is_active) {
|
|
ret = aic_core_init(gg, false);
|
|
if (ret) {
|
|
dev_err(gg->dev, "%s: aic_core_init %d\n", __func__, ret);
|
|
goto err;
|
|
}
|
|
if (gg->enabled)
|
|
aic_soft_connect(gg);
|
|
} else {
|
|
aic_soft_disconnect(gg);
|
|
aic_core_disconnect(gg);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
return 0;
|
|
err:
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
static int aic_gg_pullup(struct usb_gadget *gadget, int is_on)
|
|
{
|
|
struct aic_usb_gadget *gg = our_gadget(gadget);
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
if (!gg) {
|
|
pr_err("%s: called with no device\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
dev_dbg(gg->dev, "%s: is_on: %d\n", __func__, is_on);
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
|
|
if (is_on) {
|
|
gg->enabled = 1;
|
|
ret = aic_core_init(gg, false);
|
|
if (ret) {
|
|
dev_err(gg->dev, "%s: aic_core_init %d\n", __func__, ret);
|
|
goto err;
|
|
}
|
|
aic_soft_connect(gg);
|
|
} else {
|
|
aic_soft_disconnect(gg);
|
|
aic_core_disconnect(gg);
|
|
gg->enabled = 0;
|
|
}
|
|
gg->gadget.speed = USB_SPEED_UNKNOWN;
|
|
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
return 0;
|
|
err:
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
static int aic_gg_udc_stop(struct usb_gadget *gadget)
|
|
{
|
|
struct aic_usb_gadget *gg = our_gadget(gadget);
|
|
unsigned long flags = 0;
|
|
int i = 0;
|
|
int ret = 0;
|
|
|
|
if (!gg) {
|
|
pr_err("%s: called with no device\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* all endpoints should be shutdown */
|
|
for (i = 1; i < gg->params.num_ep; i++) {
|
|
if (gg->eps_in[i])
|
|
aic_ep_disable(&gg->eps_in[i]->ep);
|
|
if (gg->eps_out[i])
|
|
aic_ep_disable(&gg->eps_out[i]->ep);
|
|
}
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
|
|
gg->driver = NULL;
|
|
gg->gadget.speed = USB_SPEED_UNKNOWN;
|
|
gg->enabled = 0;
|
|
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
if (!IS_ERR_OR_NULL(gg->uphy))
|
|
otg_set_peripheral(gg->uphy->otg, NULL);
|
|
#endif
|
|
|
|
ret = aic_low_hw_disable(gg);
|
|
if (ret) {
|
|
dev_err(gg->dev, "%s: aic_low_hw_disable %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_gg_udc_start(struct usb_gadget *gadget,
|
|
struct usb_gadget_driver *driver)
|
|
{
|
|
struct aic_usb_gadget *gg = our_gadget(gadget);
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
if (!gg) {
|
|
pr_err("%s: called with no device\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!driver) {
|
|
dev_err(gg->dev, "%s: no driver\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (driver->max_speed < USB_SPEED_FULL)
|
|
dev_err(gg->dev, "%s: bad speed\n", __func__);
|
|
|
|
if (!driver->setup) {
|
|
dev_err(gg->dev, "%s: missing entry points\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
WARN_ON(gg->driver);
|
|
driver->driver.bus = NULL;
|
|
gg->driver = driver;
|
|
gg->gadget.dev.of_node = gg->dev->of_node;
|
|
gg->gadget.speed = USB_SPEED_UNKNOWN;
|
|
|
|
ret = aic_low_hw_enable(gg);
|
|
if (ret) {
|
|
dev_err(gg->dev, "%s: aic_low_hw_enable %d\n", __func__, ret);
|
|
goto err;
|
|
}
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
if (!IS_ERR_OR_NULL(gg->uphy))
|
|
otg_set_peripheral(gg->uphy->otg, &gg->gadget);
|
|
#endif
|
|
|
|
spin_lock_irqsave(&gg->lock, flags);
|
|
|
|
ret = aic_core_init(gg, false);
|
|
if (ret) {
|
|
dev_err(gg->dev, "%s: aic_core_init %d\n", __func__, ret);
|
|
goto err;
|
|
}
|
|
gg->enabled = 0;
|
|
|
|
spin_unlock_irqrestore(&gg->lock, flags);
|
|
|
|
dev_info(gg->dev, " bound driver %s\n", driver->driver.name);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
gg->driver = NULL;
|
|
return ret;
|
|
}
|
|
|
|
static struct usb_ep *aic_gg_match_ep(struct usb_gadget *gadget,
|
|
struct usb_endpoint_descriptor *desc,
|
|
struct usb_ss_ep_comp_descriptor *ep_comp)
|
|
{
|
|
struct aic_usb_gadget *gg = our_gadget(gadget);
|
|
int ep_num = usb_endpoint_num(desc);
|
|
struct usb_ep *ep;
|
|
|
|
if ((ep_num >= gg->params.num_ep) || (ep_num == 0))
|
|
return NULL;
|
|
|
|
if (usb_endpoint_dir_in(desc))
|
|
ep = &gg->eps_in[ep_num]->ep;
|
|
else
|
|
ep = &gg->eps_out[ep_num]->ep;
|
|
|
|
if (ep->claimed)
|
|
return NULL;
|
|
|
|
if (usb_gadget_ep_match_desc(gadget, ep, desc, ep_comp))
|
|
return ep;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const struct usb_gadget_ops aic_usb_gadget_ops = {
|
|
.get_frame = aic_gg_getframe,
|
|
.udc_start = aic_gg_udc_start,
|
|
.udc_stop = aic_gg_udc_stop,
|
|
.pullup = aic_gg_pullup,
|
|
.vbus_session = aic_gg_vbus_session,
|
|
.vbus_draw = aic_gg_vbus_draw,
|
|
.match_ep = aic_gg_match_ep,
|
|
};
|
|
|
|
static void aic_init_ep(struct aic_usb_gadget *gg,
|
|
struct aic_usb_ep *ep,
|
|
int epnum,
|
|
bool dir_in)
|
|
{
|
|
char *dir;
|
|
|
|
ep->dir_in = dir_in;
|
|
ep->index = epnum;
|
|
ep->parent = gg;
|
|
|
|
if (epnum == 0)
|
|
dir = "";
|
|
else if (dir_in)
|
|
dir = "in";
|
|
else
|
|
dir = "out";
|
|
snprintf(ep->name, sizeof(ep->name), "ep%d%s", epnum, dir);
|
|
ep->ep.name = ep->name;
|
|
|
|
INIT_LIST_HEAD(&ep->queue);
|
|
INIT_LIST_HEAD(&ep->ep.ep_list);
|
|
/* add ep1 ~ epN to the list of endpoints known by the gadget driver */
|
|
if (epnum)
|
|
list_add_tail(&ep->ep.ep_list, &gg->gadget.ep_list);
|
|
|
|
if (gg->params.speed == AIC_SPEED_PARAM_LOW)
|
|
usb_ep_set_maxpacket_limit(&ep->ep, 8);
|
|
else
|
|
usb_ep_set_maxpacket_limit(&ep->ep,
|
|
epnum ? 1024 : EP0_MPS_LIMIT);
|
|
ep->ep.ops = &aic_usb_ep_ops;
|
|
|
|
if (epnum == 0) {
|
|
ep->ep.caps.type_control = true;
|
|
} else {
|
|
if (gg->params.speed != AIC_SPEED_PARAM_LOW) {
|
|
ep->ep.caps.type_iso = true;
|
|
ep->ep.caps.type_bulk = true;
|
|
}
|
|
ep->ep.caps.type_int = true;
|
|
}
|
|
|
|
if (dir_in)
|
|
ep->ep.caps.dir_in = true;
|
|
else
|
|
ep->ep.caps.dir_out = true;
|
|
}
|
|
|
|
static int aic_gadget_core_init(struct aic_usb_gadget *gg)
|
|
{
|
|
struct device *dev = gg->dev;
|
|
u32 cfg = gg->params.ep_dirs;
|
|
u32 ep_type = 0;
|
|
int i = 0;
|
|
|
|
spin_lock_init(&gg->lock);
|
|
|
|
/* alloc ep0 */
|
|
gg->eps_in[0] = devm_kzalloc(gg->dev,
|
|
sizeof(struct aic_usb_ep),
|
|
GFP_KERNEL);
|
|
if (!gg->eps_in[0])
|
|
return -ENOMEM;
|
|
gg->eps_out[0] = gg->eps_in[0];
|
|
|
|
/* alloc ep0 buffer */
|
|
gg->ctrl_buff = devm_kzalloc(gg->dev,
|
|
ALIGN(CTRL_BUFF_SIZE, L1_CACHE_BYTES),
|
|
GFP_KERNEL);
|
|
if (!gg->ctrl_buff)
|
|
return -ENOMEM;
|
|
gg->ep0_buff = devm_kzalloc(gg->dev,
|
|
ALIGN(CTRL_BUFF_SIZE, L1_CACHE_BYTES),
|
|
GFP_KERNEL);
|
|
if (!gg->ep0_buff)
|
|
return -ENOMEM;
|
|
|
|
/* alloc ep0 request */
|
|
gg->ctrl_req = aic_ep_alloc_request(&gg->eps_out[0]->ep,
|
|
GFP_KERNEL);
|
|
if (!gg->ctrl_req) {
|
|
dev_err(dev, "failed to allocate ctrl req\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* alloc other ep */
|
|
for (i = 1, cfg >>= 2; i < gg->params.num_ep; i++, cfg >>= 2) {
|
|
ep_type = cfg & 3;
|
|
/* Direction in or both */
|
|
if (!(ep_type & 2)) {
|
|
gg->eps_in[i] = devm_kzalloc(gg->dev,
|
|
sizeof(struct aic_usb_ep),
|
|
GFP_KERNEL);
|
|
if (!gg->eps_in[i])
|
|
return -ENOMEM;
|
|
}
|
|
/* Direction out or both */
|
|
if (!(ep_type & 1)) {
|
|
gg->eps_out[i] = devm_kzalloc(gg->dev,
|
|
sizeof(struct aic_usb_ep),
|
|
GFP_KERNEL);
|
|
if (!gg->eps_out[i])
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
/* init eps */
|
|
INIT_LIST_HEAD(&gg->gadget.ep_list);
|
|
for (i = 0; i < gg->params.num_ep; i++) {
|
|
if (gg->eps_in[i])
|
|
aic_init_ep(gg, gg->eps_in[i], i, 1);
|
|
if (gg->eps_out[i])
|
|
aic_init_ep(gg, gg->eps_out[i], i, 0);
|
|
}
|
|
|
|
/* gadget member */
|
|
gg->gadget.max_speed = USB_SPEED_HIGH;
|
|
gg->gadget.ops = &aic_usb_gadget_ops;
|
|
gg->gadget.name = dev_name(dev);
|
|
gg->gadget.ep0 = &gg->eps_out[0]->ep;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_param_init(struct aic_usb_gadget *gg)
|
|
{
|
|
gg->params.num_ep = EPS_NUM;
|
|
gg->params.num_perio_in_ep = PERIOD_IN_EP_NUM;
|
|
gg->params.total_fifo_size = TOTAL_FIFO_SIZE;
|
|
|
|
gg->params.rx_fifo_size = RX_FIFO_SIZE;
|
|
gg->params.np_tx_fifo_size = NP_TX_FIFO_SIZE;
|
|
gg->params.p_tx_fifo_size[1] = PERIOD_TX_FIFO1_SIZE;
|
|
gg->params.p_tx_fifo_size[2] = PERIOD_TX_FIFO2_SIZE;
|
|
|
|
gg->params.ep_dirs = EP_DIRS;
|
|
#ifdef CONFIG_DEBUG_ON_FPGA_BOARD_ARTINCHIP
|
|
gg->params.phy_type = AIC_PHY_TYPE_PARAM_ULPI;
|
|
#else
|
|
gg->params.phy_type = AIC_PHY_TYPE_PARAM_UTMI;
|
|
#endif
|
|
gg->params.phy_ulpi_ddr = 0;
|
|
gg->params.speed = AIC_SPEED_PARAM_HIGH;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_gadget_init(struct aic_usb_gadget *gg)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = aic_param_init(gg);
|
|
if (ret) {
|
|
dev_err(gg->dev, "call aic_param_init fail: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = aic_gadget_core_init(gg);
|
|
if (ret) {
|
|
dev_err(gg->dev, "call aic_gadget_core_init fail: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aic_udc_remove(struct platform_device *dev)
|
|
{
|
|
struct aic_usb_gadget *gg = platform_get_drvdata(dev);
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
if (!IS_ERR_OR_NULL(gg->uphy))
|
|
usb_put_phy(gg->uphy);
|
|
#endif
|
|
|
|
aic_udc_debugfs_exit(gg);
|
|
|
|
usb_del_gadget_udc(&gg->gadget);
|
|
aic_ep_free_request(&gg->eps_out[0]->ep, gg->ctrl_req);
|
|
|
|
aic_low_hw_disable(gg);
|
|
|
|
reset_control_assert(gg->reset);
|
|
reset_control_assert(gg->reset_ecc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void aic_udc_shutdown(struct platform_device *dev)
|
|
{
|
|
struct aic_usb_gadget *gg = platform_get_drvdata(dev);
|
|
|
|
disable_irq(gg->irq);
|
|
}
|
|
|
|
static int aic_udc_probe(struct platform_device *dev)
|
|
{
|
|
struct aic_usb_gadget *gg = NULL;
|
|
struct resource *res = NULL;
|
|
int i, err;
|
|
int ret = 0;
|
|
|
|
if (of_property_read_bool(dev->dev.of_node, "aic,only-uboot-use")) {
|
|
dev_info(&dev->dev, "aic-udc only work in uboot.\n");
|
|
return -EPERM;
|
|
}
|
|
|
|
gg = devm_kzalloc(&dev->dev, sizeof(*gg), GFP_KERNEL);
|
|
if (!gg)
|
|
return -ENOMEM;
|
|
|
|
gg->dev = &dev->dev;
|
|
|
|
if (!dev->dev.dma_mask)
|
|
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
|
|
ret = dma_set_coherent_mask(&dev->dev, DMA_BIT_MASK(32));
|
|
if (ret) {
|
|
dev_err(&dev->dev, "can't set coherent DMA mask: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* register */
|
|
res = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
|
gg->regs = devm_ioremap_resource(&dev->dev, res);
|
|
if (IS_ERR(gg->regs)) {
|
|
dev_err(&dev->dev, "ioremap reg fail!\n");
|
|
return PTR_ERR(gg->regs);
|
|
}
|
|
dev_dbg(&dev->dev, "mapped physical addr %08lx to virtual addr %p\n",
|
|
(unsigned long)res->start, gg->regs);
|
|
|
|
/* reset */
|
|
gg->reset = devm_reset_control_get_optional(gg->dev, "aicudc");
|
|
if (IS_ERR(gg->reset)) {
|
|
ret = PTR_ERR(gg->reset);
|
|
dev_err(gg->dev, "error getting reset control %d\n", ret);
|
|
return ret;
|
|
}
|
|
gg->reset_ecc = devm_reset_control_get_optional_shared(gg->dev,
|
|
"aicudc-ecc");
|
|
if (IS_ERR(gg->reset_ecc)) {
|
|
ret = PTR_ERR(gg->reset_ecc);
|
|
dev_err(gg->dev, "error getting reset control for ecc %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
/* regulator */
|
|
aic_gg_get_res_cfg(dev->dev.of_node, &gg->params.usb_res_cfg, "aic,usbd-ext-resistance");
|
|
|
|
/* clock */
|
|
for (i = 0; i < USB_MAX_CLKS_RSTS; i++) {
|
|
gg->clks[i] = of_clk_get(gg->dev->of_node, i);
|
|
if (IS_ERR(gg->clks[i])) {
|
|
dev_err(gg->dev, "cannot get clock %d\n", i);
|
|
return PTR_ERR(gg->clks[i]);
|
|
|
|
err = PTR_ERR(gg->clks[i]);
|
|
if (err == -EPROBE_DEFER)
|
|
return err;
|
|
gg->clks[i] = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* phy */
|
|
gg->phy = devm_phy_get(gg->dev, "usb2-phy");
|
|
if (IS_ERR(gg->phy)) {
|
|
ret = PTR_ERR(gg->phy);
|
|
switch (ret) {
|
|
case -ENODEV:
|
|
case -ENOSYS:
|
|
gg->phy = NULL;
|
|
break;
|
|
case -EPROBE_DEFER:
|
|
return ret;
|
|
default:
|
|
dev_err(gg->dev, "error getting phy %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_USB_OTG
|
|
if (!gg->phy &&
|
|
(of_property_read_bool(dev->dev.of_node, "aic,otg-support"))) {
|
|
gg->uphy = devm_usb_get_phy(gg->dev, USB_PHY_TYPE_USB2);
|
|
if (IS_ERR(gg->uphy)) {
|
|
ret = PTR_ERR(gg->uphy);
|
|
switch (ret) {
|
|
case -ENODEV:
|
|
case -ENXIO:
|
|
gg->uphy = NULL;
|
|
break;
|
|
case -EPROBE_DEFER:
|
|
return ret;
|
|
default:
|
|
dev_err(gg->dev, "error getting usb phy %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* gadget init */
|
|
ret = aic_gadget_init(gg);
|
|
if (ret) {
|
|
dev_err(&dev->dev, "udc init fail: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* interrupt */
|
|
res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
|
|
if (!res) {
|
|
dev_err(&dev->dev, "No IRQ resource found!\n");
|
|
return -ENODEV;
|
|
}
|
|
gg->irq = res->start;
|
|
ret = devm_request_irq(gg->dev, gg->irq,
|
|
aic_udc_irq, IRQF_SHARED,
|
|
dev_name(gg->dev), gg);
|
|
if (ret) {
|
|
dev_err(&dev->dev, "request irq%d fail: %d\n", gg->irq, ret);
|
|
return ret;
|
|
}
|
|
dev_dbg(gg->dev, "registering interrupt handler for irq%d\n",
|
|
gg->irq);
|
|
|
|
/* udc add */
|
|
ret = usb_add_gadget_udc(gg->dev, &gg->gadget);
|
|
if (ret) {
|
|
dev_err(&dev->dev, "udc add fail: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* debugfs */
|
|
aic_udc_debugfs_init(gg);
|
|
|
|
platform_set_drvdata(dev, gg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct of_device_id aic_udc_match_table[] = {
|
|
{ .compatible = "artinchip,aic-udc-v1.0",},
|
|
{},
|
|
};
|
|
|
|
static int __maybe_unused aic_udc_suspend(struct device *dev)
|
|
{
|
|
struct aic_usb_gadget *gg = dev_get_drvdata(dev);
|
|
|
|
aic_low_hw_disable(gg);
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused aic_udc_resume(struct device *dev)
|
|
{
|
|
struct aic_usb_gadget *gg = dev_get_drvdata(dev);
|
|
|
|
aic_low_hw_enable(gg);
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops aic_udc_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(aic_udc_suspend, aic_udc_resume)
|
|
};
|
|
|
|
static struct platform_driver aic_udc_driver = {
|
|
.driver = {
|
|
.name = "aic_udc",
|
|
.of_match_table = aic_udc_match_table,
|
|
.pm = &aic_udc_pm_ops,
|
|
},
|
|
.probe = aic_udc_probe,
|
|
.remove = aic_udc_remove,
|
|
.shutdown = aic_udc_shutdown,
|
|
};
|
|
|
|
module_platform_driver(aic_udc_driver);
|
|
MODULE_DESCRIPTION("USB Device Controller driver for aic");
|
|
|