usb: gadget: udc: atmel: Update endpoint allocation scheme

This patch updates the usb endpoint allocation scheme for atmel usba
driver to make sure all endpoints are allocated in order. This
requirement comes from the datasheet of the controller.

The allocation scheme is decided by fifo_mode parameter. For fifo_mode =
0 the driver tries to autoconfigure the endpoints fifo size. All other
modes contain fixed configurations optimized for different purposes. The
idea is somehow similar with the approach used on musb driver.

Signed-off-by: Cristian Birsan <cristian.birsan@microchip.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
This commit is contained in:
Cristian Birsan 2017-01-23 16:45:59 +02:00 коммит произвёл Felipe Balbi
Родитель 41dc9ac163
Коммит 741d2558bf
3 изменённых файлов: 227 добавлений и 33 удалений

Просмотреть файл

@ -60,6 +60,20 @@ config USB_ATMEL_USBA
USBA is the integrated high-speed USB Device controller on
the AT32AP700x, some AT91SAM9 and AT91CAP9 processors from Atmel.
The fifo_mode parameter is used to select endpoint allocation mode.
fifo_mode = 0 is used to let the driver autoconfigure the endpoints.
In this case 2 banks are allocated for isochronous endpoints and
only one bank is allocated for the rest of the endpoints.
fifo_mode = 1 is a generic maximum fifo size (1024 bytes) configuration
allowing the usage of ep1 - ep6
fifo_mode = 2 is a generic performance maximum fifo size (1024 bytes)
configuration allowing the usage of ep1 - ep3
fifo_mode = 3 is a balanced performance configuration allowing the
the usage of ep1 - ep8
config USB_BCM63XX_UDC
tristate "Broadcom BCM63xx Peripheral Controller"
depends on BCM63XX

Просмотреть файл

@ -20,6 +20,7 @@
#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/ctype.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/atmel_usba_udc.h>
@ -318,6 +319,91 @@ static inline void usba_cleanup_debugfs(struct usba_udc *udc)
}
#endif
static ushort fifo_mode;
/* "modprobe ... fifo_mode=1" etc */
module_param(fifo_mode, ushort, 0x0);
MODULE_PARM_DESC(fifo_mode, "Endpoint configuration mode");
/* mode 0 - uses autoconfig */
/* mode 1 - fits in 8KB, generic max fifo configuration */
static struct usba_fifo_cfg mode_1_cfg[] = {
{ .hw_ep_num = 0, .fifo_size = 64, .nr_banks = 1, },
{ .hw_ep_num = 1, .fifo_size = 1024, .nr_banks = 2, },
{ .hw_ep_num = 2, .fifo_size = 1024, .nr_banks = 1, },
{ .hw_ep_num = 3, .fifo_size = 1024, .nr_banks = 1, },
{ .hw_ep_num = 4, .fifo_size = 1024, .nr_banks = 1, },
{ .hw_ep_num = 5, .fifo_size = 1024, .nr_banks = 1, },
{ .hw_ep_num = 6, .fifo_size = 1024, .nr_banks = 1, },
};
/* mode 2 - fits in 8KB, performance max fifo configuration */
static struct usba_fifo_cfg mode_2_cfg[] = {
{ .hw_ep_num = 0, .fifo_size = 64, .nr_banks = 1, },
{ .hw_ep_num = 1, .fifo_size = 1024, .nr_banks = 3, },
{ .hw_ep_num = 2, .fifo_size = 1024, .nr_banks = 2, },
{ .hw_ep_num = 3, .fifo_size = 1024, .nr_banks = 2, },
};
/* mode 3 - fits in 8KB, mixed fifo configuration */
static struct usba_fifo_cfg mode_3_cfg[] = {
{ .hw_ep_num = 0, .fifo_size = 64, .nr_banks = 1, },
{ .hw_ep_num = 1, .fifo_size = 1024, .nr_banks = 2, },
{ .hw_ep_num = 2, .fifo_size = 512, .nr_banks = 2, },
{ .hw_ep_num = 3, .fifo_size = 512, .nr_banks = 2, },
{ .hw_ep_num = 4, .fifo_size = 512, .nr_banks = 2, },
{ .hw_ep_num = 5, .fifo_size = 512, .nr_banks = 2, },
{ .hw_ep_num = 6, .fifo_size = 512, .nr_banks = 2, },
};
/* mode 4 - fits in 8KB, custom fifo configuration */
static struct usba_fifo_cfg mode_4_cfg[] = {
{ .hw_ep_num = 0, .fifo_size = 64, .nr_banks = 1, },
{ .hw_ep_num = 1, .fifo_size = 512, .nr_banks = 2, },
{ .hw_ep_num = 2, .fifo_size = 512, .nr_banks = 2, },
{ .hw_ep_num = 3, .fifo_size = 8, .nr_banks = 2, },
{ .hw_ep_num = 4, .fifo_size = 512, .nr_banks = 2, },
{ .hw_ep_num = 5, .fifo_size = 512, .nr_banks = 2, },
{ .hw_ep_num = 6, .fifo_size = 16, .nr_banks = 2, },
{ .hw_ep_num = 7, .fifo_size = 8, .nr_banks = 2, },
{ .hw_ep_num = 8, .fifo_size = 8, .nr_banks = 2, },
};
/* Add additional configurations here */
int usba_config_fifo_table(struct usba_udc *udc)
{
int n;
switch (fifo_mode) {
default:
fifo_mode = 0;
case 0:
udc->fifo_cfg = NULL;
n = 0;
break;
case 1:
udc->fifo_cfg = mode_1_cfg;
n = ARRAY_SIZE(mode_1_cfg);
break;
case 2:
udc->fifo_cfg = mode_2_cfg;
n = ARRAY_SIZE(mode_2_cfg);
break;
case 3:
udc->fifo_cfg = mode_3_cfg;
n = ARRAY_SIZE(mode_3_cfg);
break;
case 4:
udc->fifo_cfg = mode_4_cfg;
n = ARRAY_SIZE(mode_4_cfg);
break;
}
DBG(DBG_HW, "Setup fifo_mode %d\n", fifo_mode);
return n;
}
static inline u32 usba_int_enb_get(struct usba_udc *udc)
{
return udc->int_enb_cache;
@ -543,24 +629,17 @@ usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc)
ep->is_isoc = 0;
ep->is_in = 0;
if (maxpacket <= 8)
ept_cfg = USBA_BF(EPT_SIZE, USBA_EPT_SIZE_8);
else
/* LSB is bit 1, not 0 */
ept_cfg = USBA_BF(EPT_SIZE, fls(maxpacket - 1) - 3);
DBG(DBG_HW, "%s: EPT_SIZE = %lu (maxpacket = %lu)\n",
DBG(DBG_ERR, "%s: EPT_CFG = 0x%lx (maxpacket = %lu)\n",
ep->ep.name, ept_cfg, maxpacket);
if (usb_endpoint_dir_in(desc)) {
ep->is_in = 1;
ept_cfg |= USBA_EPT_DIR_IN;
ep->ept_cfg |= USBA_EPT_DIR_IN;
}
switch (usb_endpoint_type(desc)) {
case USB_ENDPOINT_XFER_CONTROL:
ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL);
ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE);
ep->ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_CONTROL);
break;
case USB_ENDPOINT_XFER_ISOC:
if (!ep->can_isoc) {
@ -578,24 +657,15 @@ usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc)
return -EINVAL;
ep->is_isoc = 1;
ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_ISO);
ep->ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_ISO);
ep->ept_cfg |= USBA_BF(NB_TRANS, nr_trans);
/*
* Do triple-buffering on high-bandwidth iso endpoints.
*/
if (nr_trans > 1 && ep->nr_banks == 3)
ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_TRIPLE);
else
ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_DOUBLE);
ept_cfg |= USBA_BF(NB_TRANS, nr_trans);
break;
case USB_ENDPOINT_XFER_BULK:
ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_BULK);
ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_DOUBLE);
ep->ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_BULK);
break;
case USB_ENDPOINT_XFER_INT:
ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_INT);
ept_cfg |= USBA_BF(BK_NUMBER, USBA_BK_NUMBER_DOUBLE);
ep->ept_cfg |= USBA_BF(EPT_TYPE, USBA_EPT_TYPE_INT);
break;
}
@ -604,7 +674,7 @@ usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc)
ep->ep.desc = desc;
ep->ep.maxpacket = maxpacket;
usba_ep_writel(ep, CFG, ept_cfg);
usba_ep_writel(ep, CFG, ep->ept_cfg);
usba_ep_writel(ep, CTL_ENB, USBA_EPT_ENABLE);
if (ep->can_dma) {
@ -1006,12 +1076,81 @@ static int atmel_usba_start(struct usb_gadget *gadget,
struct usb_gadget_driver *driver);
static int atmel_usba_stop(struct usb_gadget *gadget);
static struct usb_ep *atmel_usba_match_ep(
struct usb_gadget *gadget,
struct usb_endpoint_descriptor *desc,
struct usb_ss_ep_comp_descriptor *ep_comp
)
{
struct usb_ep *_ep;
struct usba_ep *ep;
/* Look at endpoints until an unclaimed one looks usable */
list_for_each_entry(_ep, &gadget->ep_list, ep_list) {
if (usb_gadget_ep_match_desc(gadget, _ep, desc, ep_comp))
goto found_ep;
}
/* Fail */
return NULL;
found_ep:
if (fifo_mode == 0) {
/* Optimize hw fifo size based on ep type and other info */
ep = to_usba_ep(_ep);
switch (usb_endpoint_type(desc)) {
case USB_ENDPOINT_XFER_CONTROL:
break;
case USB_ENDPOINT_XFER_ISOC:
ep->fifo_size = 1024;
ep->nr_banks = 2;
break;
case USB_ENDPOINT_XFER_BULK:
ep->fifo_size = 512;
ep->nr_banks = 1;
break;
case USB_ENDPOINT_XFER_INT:
if (desc->wMaxPacketSize == 0)
ep->fifo_size =
roundup_pow_of_two(_ep->maxpacket_limit);
else
ep->fifo_size =
roundup_pow_of_two(le16_to_cpu(desc->wMaxPacketSize));
ep->nr_banks = 1;
break;
}
/* It might be a little bit late to set this */
usb_ep_set_maxpacket_limit(&ep->ep, ep->fifo_size);
/* Generate ept_cfg basd on FIFO size and number of banks */
if (ep->fifo_size <= 8)
ep->ept_cfg = USBA_BF(EPT_SIZE, USBA_EPT_SIZE_8);
else
/* LSB is bit 1, not 0 */
ep->ept_cfg =
USBA_BF(EPT_SIZE, fls(ep->fifo_size - 1) - 3);
ep->ept_cfg |= USBA_BF(BK_NUMBER, ep->nr_banks);
ep->udc->configured_ep++;
}
return _ep;
}
static const struct usb_gadget_ops usba_udc_ops = {
.get_frame = usba_udc_get_frame,
.wakeup = usba_udc_wakeup,
.set_selfpowered = usba_udc_set_selfpowered,
.udc_start = atmel_usba_start,
.udc_stop = atmel_usba_stop,
.match_ep = atmel_usba_match_ep,
};
static struct usb_endpoint_descriptor usba_ep0_desc = {
@ -1678,7 +1817,8 @@ static irqreturn_t usba_udc_irq(int irq, void *devid)
}
if (status & USBA_END_OF_RESET) {
struct usba_ep *ep0;
struct usba_ep *ep0, *ep;
int i, n;
usba_writel(udc, INT_CLR, USBA_END_OF_RESET);
generate_bias_pulse(udc);
@ -1717,6 +1857,16 @@ static irqreturn_t usba_udc_irq(int irq, void *devid)
if (!(usba_ep_readl(ep0, CFG) & USBA_EPT_MAPPED))
dev_dbg(&udc->pdev->dev,
"ODD: EP0 configuration is invalid!\n");
/* Preallocate other endpoints */
n = fifo_mode ? udc->num_ep : udc->configured_ep;
for (i = 1; i < n; i++) {
ep = &udc->usba_ep[i];
usba_ep_writel(ep, CFG, ep->ept_cfg);
if (!(usba_ep_readl(ep, CFG) & USBA_EPT_MAPPED))
dev_dbg(&udc->pdev->dev,
"ODD: EP%d configuration is invalid!\n", i);
}
}
spin_unlock(&udc->lock);
@ -1864,6 +2014,9 @@ static int atmel_usba_stop(struct usb_gadget *gadget)
if (gpio_is_valid(udc->vbus_pin))
disable_irq(gpio_to_irq(udc->vbus_pin));
if (fifo_mode == 0)
udc->configured_ep = 1;
usba_stop(udc);
udc->driver = NULL;
@ -1931,9 +2084,13 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev,
&flags);
udc->vbus_pin_inverted = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0;
pp = NULL;
while ((pp = of_get_next_child(np, pp)))
udc->num_ep++;
if (fifo_mode == 0) {
pp = NULL;
while ((pp = of_get_next_child(np, pp)))
udc->num_ep++;
udc->configured_ep = 1;
} else
udc->num_ep = usba_config_fifo_table(udc);
eps = devm_kzalloc(&pdev->dev, sizeof(struct usba_ep) * udc->num_ep,
GFP_KERNEL);
@ -1946,7 +2103,7 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev,
pp = NULL;
i = 0;
while ((pp = of_get_next_child(np, pp))) {
while ((pp = of_get_next_child(np, pp)) && i < udc->num_ep) {
ep = &eps[i];
ret = of_property_read_u32(pp, "reg", &val);
@ -1954,21 +2111,21 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev,
dev_err(&pdev->dev, "of_probe: reg error(%d)\n", ret);
goto err;
}
ep->index = val;
ep->index = fifo_mode ? udc->fifo_cfg[i].hw_ep_num : val;
ret = of_property_read_u32(pp, "atmel,fifo-size", &val);
if (ret) {
dev_err(&pdev->dev, "of_probe: fifo-size error(%d)\n", ret);
goto err;
}
ep->fifo_size = val;
ep->fifo_size = fifo_mode ? udc->fifo_cfg[i].fifo_size : val;
ret = of_property_read_u32(pp, "atmel,nb-banks", &val);
if (ret) {
dev_err(&pdev->dev, "of_probe: nb-banks error(%d)\n", ret);
goto err;
}
ep->nr_banks = val;
ep->nr_banks = fifo_mode ? udc->fifo_cfg[i].nr_banks : val;
ep->can_dma = of_property_read_bool(pp, "atmel,can-dma");
ep->can_isoc = of_property_read_bool(pp, "atmel,can-isoc");
@ -1999,6 +2156,21 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev,
ep->ep.caps.dir_in = true;
ep->ep.caps.dir_out = true;
if (fifo_mode != 0) {
/*
* Generate ept_cfg based on FIFO size and
* banks number
*/
if (ep->fifo_size <= 8)
ep->ept_cfg = USBA_BF(EPT_SIZE, USBA_EPT_SIZE_8);
else
/* LSB is bit 1, not 0 */
ep->ept_cfg =
USBA_BF(EPT_SIZE, fls(ep->fifo_size - 1) - 3);
ep->ept_cfg |= USBA_BF(BK_NUMBER, ep->nr_banks);
}
if (i)
list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list);

Просмотреть файл

@ -275,6 +275,12 @@ struct usba_dma_desc {
u32 ctrl;
};
struct usba_fifo_cfg {
u8 hw_ep_num;
u16 fifo_size;
u8 nr_banks;
};
struct usba_ep {
int state;
void __iomem *ep_regs;
@ -292,7 +298,7 @@ struct usba_ep {
unsigned int can_isoc:1;
unsigned int is_isoc:1;
unsigned int is_in:1;
unsigned long ept_cfg;
#ifdef CONFIG_USB_GADGET_DEBUG_FS
u32 last_dma_status;
struct dentry *debugfs_dir;
@ -337,6 +343,8 @@ struct usba_udc {
int vbus_pin;
int vbus_pin_inverted;
int num_ep;
int configured_ep;
struct usba_fifo_cfg *fifo_cfg;
struct clk *pclk;
struct clk *hclk;
struct usba_ep *usba_ep;