usb: imx usb buffers in iram work-around

Some i.MX parts (MX31, MX37) need to have usb host queue descriptors
and/or buffers in on-chip IRAM in cases of heavily loaded memory
system (i.e. large LCDs).

Signed-off-by: Dinh Nguyen <r00091@freescale.com>
This commit is contained in:
Dinh Nguyen
2009-09-16 12:56:38 -05:00
committed by Matt Sealey
parent c428ef3836
commit 88beb29fd0
4 changed files with 1871 additions and 0 deletions

View File

@@ -290,8 +290,13 @@ static void end_unlink_async(struct ehci_hcd *ehci);
static void ehci_work(struct ehci_hcd *ehci);
#include "ehci-hub.c"
#ifdef CONFIG_USB_STATIC_IRAM
#include "ehci-mem-iram.c"
#include "ehci-q-iram.c"
#else
#include "ehci-mem.c"
#include "ehci-q.c"
#endif
#include "ehci-sched.c"
/*-------------------------------------------------------------------------*/

View File

@@ -0,0 +1,506 @@
/*
* Copyright (c) 2001 by David Brownell
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* this file is part of ehci-hcd.c */
/*-------------------------------------------------------------------------*/
/*
* There's basically three types of memory:
* - data used only by the HCD ... kmalloc is fine
* - async and periodic schedules, shared by HC and HCD ... these
* need to use dma_pool or dma_alloc_coherent
* - driver buffers, read/written by HC ... single shot DMA mapped
*
* There's also "register" data (e.g. PCI or SOC), which is memory mapped.
* No memory seen by this driver is pageable.
*/
/*-------------------------------------------------------------------------*/
/* Allocate the key transfer structures from the previously allocated pool */
#include <linux/smp_lock.h>
bool use_iram_qtd;
struct memDesc {
u32 start;
u32 end;
struct memDesc *next;
} ;
static u32 g_usb_pool_start;
static s32 g_usb_pool_count;
static u32 g_total_pages;
static u32 g_alignment = 32;
struct memDesc *g_allocated_desc;
static spinlock_t g_usb_sema;
static u32 g_debug_qtd_allocated;
static u32 g_debug_qH_allocated;
static int g_alloc_map;
/*!
* usb_pool_initialize
*
* @param memPool start address of the pool
* @param poolSize memory pool size
* @param alignment alignment for example page alignmnet will be 4K
*
* @return 0 for success -1 for errors
*/
static int usb_pool_initialize(u32 memPool, u32 poolSize, u32 alignment)
{
if (g_usb_pool_count) {
printk(KERN_INFO "usb_pool_initialize : already initialzed.\n");
return 0;
}
g_alignment = alignment;
if (g_alignment == 0) {
printk(KERN_INFO
"usb_pool_initialize : g_alignment can not be zero.\n");
g_alignment = 32;
}
g_total_pages = poolSize / g_alignment;
g_usb_pool_start = (u32) memPool;
g_allocated_desc = kmalloc(sizeof(struct memDesc), GFP_KERNEL);
if (!g_allocated_desc) {
printk(KERN_ALERT "usb_pool_initialize : kmalloc failed \n");
return (-1);
}
g_allocated_desc->start = 0;
g_allocated_desc->end = 0;
g_allocated_desc->next = NULL;
spin_lock_init(&g_usb_sema);
g_usb_pool_count++;
return (0);
}
static void usb_pool_deinit()
{
if (--g_usb_pool_count < 0)
g_usb_pool_count = 0;
}
/*!
* usb_malloc
*
* @param size memory pool size
*
* @return physical address, 0 for error
*/
static u32 usb_malloc(u32 size, gfp_t mem_flags)
{
unsigned long flags;
struct memDesc *prevDesc = NULL;
struct memDesc *nextDesc = NULL;
struct memDesc *currentDesc = NULL;
u32 pages = (size + g_alignment - 1) / g_alignment;
if ((size == 0) || (pages > g_total_pages))
return 0;
currentDesc = kmalloc(sizeof(struct memDesc), mem_flags);
if (!currentDesc) {
printk(KERN_ALERT "usb_malloc: kmalloc failed \n");
return 0;
}
spin_lock_irqsave(&g_usb_sema, flags);
/* Create the first Allocated descriptor */
if (!g_allocated_desc->next) {
g_allocated_desc->next = currentDesc;
currentDesc->start = 0;
currentDesc->end = pages;
currentDesc->next = NULL;
spin_unlock_irqrestore(&g_usb_sema, flags);
return (g_usb_pool_start + currentDesc->start * g_alignment);
}
/* Find the free spot */
prevDesc = g_allocated_desc;
while (prevDesc->next) {
nextDesc = prevDesc->next;
if (pages <= nextDesc->start - prevDesc->end) {
currentDesc->start = prevDesc->end;
currentDesc->end = currentDesc->start + pages;
currentDesc->next = nextDesc;
prevDesc->next = currentDesc;
break;
}
prevDesc = nextDesc;
}
/* Do not find the free spot inside the chain, append to the end */
if (!prevDesc->next) {
if (pages > (g_total_pages - prevDesc->end)) {
spin_unlock_irqrestore(&g_usb_sema, flags);
kfree(currentDesc);
return 0;
} else {
currentDesc->start = prevDesc->end;
currentDesc->end = currentDesc->start + pages;
currentDesc->next = NULL;
prevDesc->next = currentDesc;
}
}
spin_unlock_irqrestore(&g_usb_sema, flags);
return (g_usb_pool_start + currentDesc->start * g_alignment);
}
/*!
* usb_free
*
* @param physical physical address try to free
*
*/
static void usb_free(u32 physical)
{
unsigned long flags;
struct memDesc *prevDesc = NULL;
struct memDesc *nextDesc = NULL;
u32 pages = (physical - g_usb_pool_start) / g_alignment;
/* Protect the memory pool data structures. */
spin_lock_irqsave(&g_usb_sema, flags);
prevDesc = g_allocated_desc;
while (prevDesc->next) {
nextDesc = prevDesc->next;
if (nextDesc->start == pages) {
prevDesc->next = nextDesc->next;
kfree(nextDesc);
break;
}
prevDesc = prevDesc->next;
}
/* All done with memory pool data structures. */
spin_unlock_irqrestore(&g_usb_sema, flags);
}
static int address_to_buffer(struct ehci_hcd *ehci, int address)
{
int i;
for (i = 0; i < IRAM_NTD; i++) {
if (ehci->usb_address[i] == address)
return i;
}
return IRAM_NTD;
}
static void use_buffer(struct ehci_hcd *ehci, int address)
{
int i;
for (i = 0; i < IRAM_NTD; i++) {
if (ehci->usb_address[i] == address)
return;
}
if (ehci->usb_address[0] == 0) {
ehci->usb_address[0] = address;
printk(KERN_INFO "usb_address[0] %x\n", address);
return;
} else if (ehci->usb_address[1] == 0) {
ehci->usb_address[1] = address;
printk(KERN_INFO "usb_address[1] %x\n", address);
return;
} else
printk(KERN_ALERT "qh_make run out of iRAM, already be used");
}
static u32 alloc_iram_buf(void)
{
int i;
for (i = 0; i < IRAM_NTD; i++) {
if (!(g_alloc_map & (1 << i))) {
g_alloc_map |= (1 << i);
return USB_IRAM_BASE_ADDR + i * (IRAM_TD_SIZE * 2);
}
}
panic("Out of IRAM buffers\n");
}
void free_iram_buf(u32 buf)
{
int i = (buf - USB_IRAM_BASE_ADDR) / (IRAM_TD_SIZE * 2);
g_alloc_map &= ~(1 << i);
}
static inline void ehci_qtd_init(struct ehci_hcd *ehci, struct ehci_qtd *qtd,
dma_addr_t dma)
{
memset(qtd, 0, sizeof *qtd);
qtd->qtd_dma = dma;
qtd->hw_token = cpu_to_le32(QTD_STS_HALT);
qtd->hw_next = EHCI_LIST_END(ehci);
qtd->hw_alt_next = EHCI_LIST_END(ehci);
INIT_LIST_HEAD(&qtd->qtd_list);
}
static struct ehci_qtd *ehci_qtd_alloc(struct ehci_hcd *ehci, gfp_t flags)
{
struct ehci_qtd *qtd;
dma_addr_t dma;
if (use_iram_qtd) {
dma = usb_malloc(sizeof(struct ehci_qtd), flags);
if (dma != 0)
qtd = (struct ehci_qtd *)IO_ADDRESS(dma);
else
qtd = dma_pool_alloc(ehci->qtd_pool, flags, &dma);
}
else
qtd = dma_pool_alloc(ehci->qtd_pool, flags, &dma);
if (qtd != NULL) {
ehci_qtd_init(ehci, qtd, dma);
++g_debug_qtd_allocated;
} else {
panic
("out of i-ram for qtd allocation g_debug_qtd_allocated %d \
size%d \n", g_debug_qtd_allocated,
sizeof(struct ehci_qtd));
}
return qtd;
}
static inline void ehci_qtd_free(struct ehci_hcd *ehci, struct ehci_qtd *qtd)
{
if ((qtd->qtd_dma & (USB_IRAM_BASE_ADDR & 0xFFF00000)) ==
(USB_IRAM_BASE_ADDR & 0xFFF00000))
usb_free(qtd->qtd_dma);
else
dma_pool_free(ehci->qtd_pool, qtd, qtd->qtd_dma);
--g_debug_qtd_allocated;
}
static void qh_destroy(struct ehci_qh *qh)
{
struct ehci_hcd *ehci = qh->ehci;
/* clean qtds first, and know this is not linked */
if (!list_empty(&qh->qtd_list) || qh->qh_next.ptr) {
ehci_dbg(ehci, "unused qh not empty!\n");
BUG();
}
if (qh->dummy)
ehci_qtd_free(ehci, qh->dummy);
int i;
for (i = 0; i < IRAM_NTD; i++) {
if (ehci->usb_address[i] == (qh->hw_info1 & 0x7F))
ehci->usb_address[i] = 0;
}
if ((qh->qh_dma & (USB_IRAM_BASE_ADDR & 0xFFF00000)) ==
(USB_IRAM_BASE_ADDR & 0xFFF00000))
usb_free(qh->qh_dma);
else
dma_pool_free(ehci->qh_pool, qh, qh->qh_dma);
--g_debug_qH_allocated;
}
static struct ehci_qh *ehci_qh_alloc(struct ehci_hcd *ehci, gfp_t flags)
{
struct ehci_qh *qh;
dma_addr_t dma;
dma = usb_malloc(sizeof(struct ehci_qh), flags);
if (dma != 0)
qh = (struct ehci_qh *)IO_ADDRESS(dma);
else
qh = (struct ehci_qh *)
dma_pool_alloc(ehci->qh_pool, flags, &dma);
++g_debug_qH_allocated;
if (qh == NULL) {
panic("run out of i-ram for qH allocation\n");
return qh;
}
memset(qh, 0, sizeof *qh);
qh->refcount = 1;
qh->ehci = ehci;
qh->qh_dma = dma;
INIT_LIST_HEAD(&qh->qtd_list);
/* dummy td enables safe urb queuing */
qh->dummy = ehci_qtd_alloc(ehci, flags);
if (qh->dummy == NULL) {
ehci_dbg(ehci, "no dummy td\n");
dma_pool_free(ehci->qh_pool, qh, qh->qh_dma);
qh = NULL;
}
return qh;
}
/* to share a qh (cpu threads, or hc) */
static inline struct ehci_qh *qh_get(struct ehci_qh *qh)
{
WARN_ON(!qh->refcount);
qh->refcount++;
return qh;
}
static inline void qh_put(struct ehci_qh *qh)
{
if (!--qh->refcount)
qh_destroy(qh);
}
/*-------------------------------------------------------------------------*/
/* The queue heads and transfer descriptors are managed from pools tied
* to each of the "per device" structures.
* This is the initialisation and cleanup code.
*/
static void ehci_mem_cleanup(struct ehci_hcd *ehci)
{
if (ehci->async)
qh_put(ehci->async);
ehci->async = NULL;
/* DMA consistent memory and pools */
if (ehci->qtd_pool)
dma_pool_destroy(ehci->qtd_pool);
ehci->qtd_pool = NULL;
if (ehci->qh_pool) {
dma_pool_destroy(ehci->qh_pool);
ehci->qh_pool = NULL;
}
if (ehci->itd_pool)
dma_pool_destroy(ehci->itd_pool);
ehci->itd_pool = NULL;
if (ehci->sitd_pool)
dma_pool_destroy(ehci->sitd_pool);
ehci->sitd_pool = NULL;
if (ehci->periodic)
dma_free_coherent(ehci_to_hcd(ehci)->self.controller,
ehci->periodic_size * sizeof(u32),
ehci->periodic, ehci->periodic_dma);
ehci->periodic = NULL;
if (ehci->iram_buffer[0])
free_iram_buf(ehci->iram_buffer[0]);
if (ehci->iram_buffer[1])
free_iram_buf(ehci->iram_buffer[1]);
/* shadow periodic table */
kfree(ehci->pshadow);
ehci->pshadow = NULL;
usb_pool_deinit();
}
/* remember to add cleanup code (above) if you add anything here */
static int ehci_mem_init(struct ehci_hcd *ehci, gfp_t flags)
{
int i;
g_usb_pool_count = 0;
g_debug_qtd_allocated = 0;
g_debug_qH_allocated = 0;
g_alloc_map = 0;
if (cpu_is_mx37())
use_iram_qtd = 0;
else
use_iram_qtd = 1;
usb_pool_initialize(USB_IRAM_BASE_ADDR + IRAM_TD_SIZE * IRAM_NTD * 2,
USB_IRAM_SIZE - IRAM_TD_SIZE * IRAM_NTD * 2, 32);
if (!ehci->iram_buffer[0]) {
ehci->iram_buffer[0] = alloc_iram_buf();
ehci->iram_buffer_v[0] = IO_ADDRESS(ehci->iram_buffer[0]);
ehci->iram_buffer[1] = alloc_iram_buf();
ehci->iram_buffer_v[1] = IO_ADDRESS(ehci->iram_buffer[1]);
}
/* QTDs for control/bulk/intr transfers */
ehci->qtd_pool = dma_pool_create("ehci_qtd",
ehci_to_hcd(ehci)->self.controller,
sizeof(struct ehci_qtd),
32/* byte alignment (for hw parts) */
, 4096 /* can't cross 4K */);
if (!ehci->qtd_pool)
goto fail;
/* QHs for control/bulk/intr transfers */
ehci->qh_pool = dma_pool_create("ehci_qh",
ehci_to_hcd(ehci)->self.controller,
sizeof(struct ehci_qh),
32 /* byte alignment (for hw parts) */ ,
4096 /* can't cross 4K */);
if (!ehci->qh_pool)
goto fail;
ehci->async = ehci_qh_alloc(ehci, flags);
if (!ehci->async)
goto fail;
/* ITD for high speed ISO transfers */
ehci->itd_pool = dma_pool_create("ehci_itd",
ehci_to_hcd(ehci)->self.controller,
sizeof(struct ehci_itd),
32/* byte alignment (for hw parts) */
, 4096 /* can't cross 4K */);
if (!ehci->itd_pool)
goto fail;
/* SITD for full/low speed split ISO transfers */
ehci->sitd_pool = dma_pool_create("ehci_sitd",
ehci_to_hcd(ehci)->self.controller,
sizeof(struct ehci_sitd),
32/* byte alignment (for hw parts) */
, 4096 /* can't cross 4K */);
if (!ehci->sitd_pool)
goto fail;
ehci->periodic = (__le32 *)
dma_alloc_coherent(ehci_to_hcd(ehci)->self.controller,
ehci->periodic_size * sizeof(__le32),
&ehci->periodic_dma, 0);
if (ehci->periodic == NULL)
goto fail;
for (i = 0; i < ehci->periodic_size; i++)
ehci->periodic[i] = EHCI_LIST_END(ehci);
/* software shadow of hardware table */
ehci->pshadow = kcalloc(ehci->periodic_size, sizeof(void *), flags);
if (ehci->pshadow != NULL)
return 0;
fail:
ehci_dbg(ehci, "couldn't init memory\n");
ehci_mem_cleanup(ehci);
return -ENOMEM;
}

File diff suppressed because it is too large Load Diff

View File

@@ -145,6 +145,13 @@ struct ehci_hcd { /* one per controller */
* other external transceivers should be software-transparent
*/
struct otg_transceiver *transceiver;
#ifdef CONFIG_USB_STATIC_IRAM
u32 iram_buffer[2];
u32 iram_buffer_v[2];
int iram_in_use[2];
int usb_address[2];
#endif
/* irq statistics */
#ifdef EHCI_STATS
struct ehci_stats stats;
@@ -247,6 +254,10 @@ struct ehci_qtd {
struct list_head qtd_list; /* sw qtd list */
struct urb *urb; /* qtd's urb */
size_t length; /* length of buffer */
#ifdef CONFIG_USB_STATIC_IRAM
size_t buffer_offset;
int last_one;
#endif
} __attribute__ ((aligned (32)));
/* mask NakCnt+T in qh->hw_alt_next */
@@ -717,6 +728,10 @@ static inline u32 hc32_to_cpup (const struct ehci_hcd *ehci, const __hc32 *x)
#define STUB_DEBUG_FILES
#endif /* DEBUG */
#ifdef CONFIG_USB_STATIC_IRAM
#define IRAM_TD_SIZE 1024 /* size of 1 qTD's buffer */
#define IRAM_NTD 2 /* number of TDs in IRAM */
#endif
/*-------------------------------------------------------------------------*/
#endif /* __LINUX_EHCI_HCD_H */