rt-thread/bsp/nuvoton/libraries/m460/USBHostLib/src/ohci.c

1298 lines
42 KiB
C
Raw Normal View History

/**************************************************************************//**
* @file ohci.c
* @version V1.10
* @brief USB Host library OHCI (USB 1.1) host controller driver.
*
* SPDX-License-Identifier: Apache-2.0
*
* @copyright (C) 2017 Nuvoton Technology Corp. All rights reserved.
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "NuMicro.h"
#include "usb.h"
#include "hub.h"
#include "ohci.h"
/// @cond HIDDEN_SYMBOLS
//#define TD_debug printf
#define TD_debug(...)
//#define ED_debug printf
#define ED_debug(...)
#ifdef __ICCARM__
#pragma data_alignment=256
HCCA_T _hcca;
#else
HCCA_T _hcca __attribute__((aligned(256)));
#endif
ED_T *_Ied[6];
static ED_T *ed_remove_list;
static void add_to_ED_remove_list(ED_T *ed)
{
ED_T *p;
ED_debug("add_to_ED_remove_list - 0x%x (0x%x)\n", (int)ed, ed->Info);
DISABLE_OHCI_IRQ();
/* check if this ED found in ed_remove_list */
p = ed_remove_list;
while (p)
{
if (p == ed)
{
ENABLE_OHCI_IRQ(); /* This ED found in ed_remove_list */
return; /* do nothing */
}
p = p->next;
}
ed->Info |= ED_SKIP; /* ask OHCI controller skip this ED */
ed->next = ed_remove_list;
ed_remove_list = ed; /* insert to the head of ed_remove_list */
ENABLE_OHCI_IRQ();
_ohci->HcInterruptStatus = USBH_HcInterruptStatus_SF_Msk;
_ohci->HcInterruptEnable |= USBH_HcInterruptEnable_SF_Msk;
usbh_delay_ms(2); /* Full speed wait 2 ms is enough */
}
static int ohci_reset(void)
{
volatile int t0;
/* Disable HC interrupts */
_ohci->HcInterruptDisable = USBH_HcInterruptDisable_MIE_Msk;
/* HC Reset requires max 10 ms delay */
_ohci->HcControl = 0;
_ohci->HcCommandStatus = USBH_HcCommandStatus_HCR_Msk;
usbh_delay_ms(10);
/* Check if OHCI reset completed? */
if ((_ohci->HcCommandStatus & USBH_HcCommandStatus_HCR_Msk) != 0)
{
USB_error("Error! - USB OHCI reset timed out!\n");
return -1;
}
_ohci->HcRhStatus = USBH_HcRhStatus_OCI_Msk | USBH_HcRhStatus_LPS_Msk;
_ohci->HcControl = HCFS_RESET;
usbh_delay_ms(10);
/* Check if OHCI reset completed? */
if ((_ohci->HcCommandStatus & USBH_HcCommandStatus_HCR_Msk) != 0)
{
USB_error("Error! - USB HC reset timed out!\n");
return -1;
}
return 0;
}
static void init_hcca_int_table()
{
ED_T *ed_p;
int i, idx, interval;
memset(_hcca.int_table, 0, sizeof(_hcca.int_table));
for (i = 5; i >= 0; i--) /* interval = i^2 */
{
_Ied[i] = alloc_ohci_ED();
_Ied[i]->Info = ED_SKIP;
interval = 0x1 << i;
for (idx = interval - 1; idx < 32; idx += interval)
{
if (_hcca.int_table[idx] == 0) /* is empty list, insert directly */
{
_hcca.int_table[idx] = (uint32_t)_Ied[i];
}
else
{
ed_p = (ED_T *)_hcca.int_table[idx];
while (1)
{
if (ed_p == _Ied[i])
break; /* already chained by previous visit */
if (ed_p->NextED == 0) /* reach end of list? */
{
ed_p->NextED = (uint32_t)_Ied[i];
break;
}
ed_p = (ED_T *)ed_p->NextED;
}
}
}
}
}
static ED_T *get_int_tree_head_node(int interval)
{
int i;
for (i = 0; i < 5; i++)
{
interval >>= 1;
if (interval == 0)
return _Ied[i];
}
return _Ied[5]; /* for interval >= 32 */
}
static int get_ohci_interval(int interval)
{
int i, bInterval = 1;
for (i = 0; i < 5; i++)
{
interval >>= 1;
if (interval == 0)
return bInterval;
bInterval *= 2;
}
return 32; /* for interval >= 32 */
}
static int ohci_init(void)
{
uint32_t fminterval;
volatile int i;
if (ohci_reset() < 0)
return -1;
ed_remove_list = NULL;
init_hcca_int_table();
/* Tell the controller where the control and bulk lists are
* The lists are empty now. */
_ohci->HcControlHeadED = 0; /* control ED list head */
_ohci->HcBulkHeadED = 0; /* bulk ED list head */
_ohci->HcHCCA = (uint32_t)&_hcca; /* HCCA area */
/* periodic start 90% of frame interval */
fminterval = 0x2edf; /* 11,999 */
_ohci->HcPeriodicStart = (fminterval * 9) / 10;
/* set FSLargestDataPacket, 10,104 for 0x2edf frame interval */
fminterval |= ((((fminterval - 210) * 6) / 7) << 16);
_ohci->HcFmInterval = fminterval;
_ohci->HcLSThreshold = 0x628;
/* start controller operations */
_ohci->HcControl = HCFS_OPER | (0x3 << USBH_HcControl_CBSR_Pos);
#ifdef OHCI_PER_PORT_POWER
_ohci->HcRhDescriptorB = 0x60000;
_ohci->HcRhPortStatus[0] = USBH_HcRhPortStatus_PPS_Msk;
_ohci->HcRhPortStatus[1] = USBH_HcRhPortStatus_PPS_Msk;
#else
_ohci->HcRhDescriptorA = (_ohci->HcRhDescriptorA | (1 << 9)) & ~USBH_HcRhDescriptorA_PSM_Msk;
_ohci->HcRhStatus = USBH_HcRhStatus_LPSC_Msk;
#endif
_ohci->HcInterruptEnable = USBH_HcInterruptEnable_MIE_Msk | USBH_HcInterruptEnable_WDH_Msk | USBH_HcInterruptEnable_SF_Msk;
/* POTPGT delay is bits 24-31, in 20 ms units. */
usbh_delay_ms(20);
return 0;
}
static void ohci_suspend(void)
{
/* set port suspend if connected */
if (_ohci->HcRhPortStatus[0] & 0x1)
_ohci->HcRhPortStatus[0] = 0x4;
if (_ohci->HcRhPortStatus[1] & 0x1)
_ohci->HcRhPortStatus[1] = 0x4;
/* enable Device Remote Wakeup */
_ohci->HcRhStatus |= USBH_HcRhStatus_DRWE_Msk;
/* enable USBH RHSC interrupt for system wakeup */
_ohci->HcInterruptEnable |= USBH_HcInterruptEnable_RHSC_Msk | USBH_HcInterruptEnable_RD_Msk;
/* set Host Controller enter suspend state */
_ohci->HcControl = (_ohci->HcControl & ~USBH_HcControl_HCFS_Msk) | (3 << USBH_HcControl_HCFS_Pos);
}
static void ohci_resume(void)
{
_ohci->HcControl = (_ohci->HcControl & ~USBH_HcControl_HCFS_Msk) | (1 << USBH_HcControl_HCFS_Pos);
_ohci->HcControl = (_ohci->HcControl & ~USBH_HcControl_HCFS_Msk) | (2 << USBH_HcControl_HCFS_Pos);
if (_ohci->HcRhPortStatus[0] & 0x4)
_ohci->HcRhPortStatus[0] = 0x8;
if (_ohci->HcRhPortStatus[1] & 0x4)
_ohci->HcRhPortStatus[1] = 0x8;
}
static void ohci_shutdown(void)
{
ohci_suspend();
DISABLE_OHCI_IRQ();
#ifndef OHCI_PER_PORT_POWER
_ohci->HcRhStatus = USBH_HcRhStatus_LPS_Msk;
#endif
}
/*
* Quit current trasnfer via UTR or hardware EP.
*/
static int ohci_quit_xfer(UTR_T *utr, EP_INFO_T *ep)
{
ED_T *ed;
if (utr != NULL)
{
if (utr->ep == NULL)
return USBH_ERR_NOT_FOUND;
ed = (ED_T *)(utr->ep->hw_pipe);
if (!ed)
return USBH_ERR_NOT_FOUND;
/* add the endpoint to remove list, it will be removed on the next start of frame */
add_to_ED_remove_list(ed);
utr->ep->hw_pipe = NULL;
}
if ((ep != NULL) && (ep->hw_pipe != NULL))
{
ed = (ED_T *)(ep->hw_pipe);
/* add the endpoint to remove list, it will be removed on the next start of frame */
add_to_ED_remove_list(ed);
ep->hw_pipe = NULL;
}
return 0;
}
uint32_t ed_make_info(UDEV_T *udev, EP_INFO_T *ep)
{
uint32_t info;
if (ep == NULL) /* is a control endpoint */
{
/* control endpoint direction is from TD */
if (udev->descriptor.bMaxPacketSize0 == 0) /* is 0 if device descriptor still not obtained. */
{
if (udev->speed == SPEED_LOW) /* give a default maximum packet size */
udev->descriptor.bMaxPacketSize0 = 8;
else
udev->descriptor.bMaxPacketSize0 = 64;
}
info = (udev->descriptor.bMaxPacketSize0 << 16) /* Control endpoint Maximum Packet Size from device descriptor */
| ED_DIR_BY_TD /* Direction (Get direction From TD) */
| ED_FORMAT_GENERAL /* General format */
| (0 << ED_CTRL_EN_Pos); /* Endpoint address 0 */
}
else /* Other endpoint direction is from endpoint descriptor */
{
info = (ep->wMaxPacketSize << 16); /* Maximum Packet Size from endpoint */
info |= ((ep->bEndpointAddress & 0xf) << ED_CTRL_EN_Pos); /* Endpoint Number */
if ((ep->bEndpointAddress & EP_ADDR_DIR_MASK) == EP_ADDR_DIR_IN)
info |= ED_DIR_IN;
else
info |= ED_DIR_OUT;
if ((ep->bmAttributes & EP_ATTR_TT_MASK) == EP_ATTR_TT_ISO)
info |= ED_FORMAT_ISO;
else
info |= ED_FORMAT_GENERAL;
}
info |= ((udev->speed == SPEED_LOW) ? ED_SPEED_LOW : ED_SPEED_FULL); /* Speed */
info |= (udev->dev_num); /* Function Address */
return info;
}
static void write_td(TD_T *td, uint32_t info, uint8_t *buff, uint32_t data_len)
{
td->Info = info;
td->CBP = (uint32_t)((!buff || !data_len) ? 0 : buff);
td->BE = (uint32_t)((!buff || !data_len) ? 0 : (uint32_t)buff + data_len - 1);
td->buff_start = td->CBP;
// TD_debug("TD [0x%x]: 0x%x, 0x%x, 0x%x\n", (int)td, td->Info, td->CBP, td->BE);
}
static int ohci_ctrl_xfer(UTR_T *utr)
{
UDEV_T *udev;
ED_T *ed;
TD_T *td_setup, *td_data, *td_status;
uint32_t info;
udev = utr->udev;
/*------------------------------------------------------------------------------------*/
/* Allocate ED and TDs */
/*------------------------------------------------------------------------------------*/
td_setup = alloc_ohci_TD(utr);
if (utr->data_len > 0)
td_data = alloc_ohci_TD(utr);
else
td_data = NULL;
td_status = alloc_ohci_TD(utr);
if (td_status == NULL)
{
free_ohci_TD(td_setup);
if (utr->data_len > 0)
free_ohci_TD(td_data);
return USBH_ERR_MEMORY_OUT;
}
/* Check if there's any transfer pending on this endpoint... */
if (udev->ep0.hw_pipe == NULL)
{
ed = alloc_ohci_ED();
if (ed == NULL)
{
free_ohci_TD(td_setup);
free_ohci_TD(td_status);
if (utr->data_len > 0)
free_ohci_TD(td_data);
return USBH_ERR_MEMORY_OUT;
}
}
else
ed = (ED_T *)udev->ep0.hw_pipe;
/*------------------------------------------------------------------------------------*/
/* prepare SETUP stage TD */
/*------------------------------------------------------------------------------------*/
info = TD_CC | TD_T_DATA0 | TD_TYPE_CTRL;
write_td(td_setup, info, (uint8_t *)&utr->setup, 8);
td_setup->ed = ed;
/*------------------------------------------------------------------------------------*/
/* prepare DATA stage TD */
/*------------------------------------------------------------------------------------*/
if (utr->data_len > 0)
{
if ((utr->setup.bmRequestType & 0x80) == REQ_TYPE_OUT)
info = (TD_CC | TD_R | TD_DP_OUT | TD_T_DATA1 | TD_TYPE_CTRL | TD_CTRL_DATA);
else
info = (TD_CC | TD_R | TD_DP_IN | TD_T_DATA1 | TD_TYPE_CTRL | TD_CTRL_DATA);
write_td(td_data, info, utr->buff, utr->data_len);
td_data->ed = ed;
td_setup->NextTD = (uint32_t)td_data;
td_setup->next = td_data;
td_data->NextTD = (uint32_t)td_status;
td_data->next = td_status;
}
else
{
td_setup->NextTD = (uint32_t)td_status;
td_setup->next = td_status;
}
/*------------------------------------------------------------------------------------*/
/* prepare STATUS stage TD */
/*------------------------------------------------------------------------------------*/
ed->Info = ed_make_info(udev, NULL);
if ((utr->setup.bmRequestType & 0x80) == REQ_TYPE_OUT)
info = (TD_CC | TD_DP_IN | TD_T_DATA1 | TD_TYPE_CTRL);
else
info = (TD_CC | TD_DP_OUT | TD_T_DATA1 | TD_TYPE_CTRL);
write_td(td_status, info, NULL, 0);
td_status->ed = ed;
td_status->NextTD = 0;
td_status->next = 0;
/*------------------------------------------------------------------------------------*/
/* prepare ED */
/*------------------------------------------------------------------------------------*/
ed->TailP = 0;
ed->HeadP = (uint32_t)td_setup;
ed->Info = ed_make_info(udev, NULL);
ed->NextED = 0;
//TD_debug("TD SETUP [0x%x]: 0x%x, 0x%x, 0x%x, 0x%x\n", (int)td_setup, td_setup->Info, td_setup->CBP, td_setup->BE, td_setup->NextTD);
//if (td_data)
// TD_debug("TD DATA [0x%x]: 0x%x, 0x%x, 0x%x, 0x%x\n", (int)td_data, td_data->Info, td_data->CBP, td_data->BE, td_data->NextTD);
//TD_debug("TD STATUS [0x%x]: 0x%x, 0x%x, 0x%x, 0x%x\n", (int)td_status, td_status->Info, td_status->CBP, td_status->BE, td_status->NextTD);
ED_debug("Xfer ED 0x%x: 0x%x 0x%x 0x%x 0x%x\n", (int)ed, ed->Info, ed->TailP, ed->HeadP, ed->NextED);
if (utr->data_len > 0)
utr->td_cnt = 3;
else
utr->td_cnt = 2;
utr->ep = &udev->ep0; /* driver can find EP from UTR */
udev->ep0.hw_pipe = (void *)ed; /* driver can find ED from EP */
/*------------------------------------------------------------------------------------*/
/* Start transfer */
/*------------------------------------------------------------------------------------*/
DISABLE_OHCI_IRQ();
_ohci->HcControlHeadED = (uint32_t)ed; /* Link ED to OHCI */
_ohci->HcControl |= USBH_HcControl_CLE_Msk; /* enable control list */
ENABLE_OHCI_IRQ();
_ohci->HcCommandStatus = USBH_HcCommandStatus_CLF_Msk; /* start Control list */
return 0;
}
static int ohci_bulk_xfer(UTR_T *utr)
{
UDEV_T *udev = utr->udev;
EP_INFO_T *ep = utr->ep;
ED_T *ed;
TD_T *td, *td_p, *td_list = NULL;
uint32_t info;
uint32_t data_len, xfer_len;
int8_t bIsNewED = 0;
uint8_t *buff;
/*------------------------------------------------------------------------------------*/
/* Check if there's uncompleted transfer on this endpoint... */
/* Prepare ED */
/*------------------------------------------------------------------------------------*/
info = ed_make_info(udev, ep);
/* Check if there's any transfer pending on this endpoint... */
ed = (ED_T *)_ohci->HcBulkHeadED; /* get the head of bulk endpoint list */
while (ed != NULL)
{
if (ed->Info == info) /* have transfer of this EP not completed? */
{
if ((ed->HeadP & 0xFFFFFFF0) != (ed->TailP & 0xFFFFFFF0))
return USBH_ERR_OHCI_EP_BUSY; /* endpoint is busy */
else
break; /* ED already there... */
}
ed = (ED_T *)ed->NextED;
}
if (ed == NULL)
{
bIsNewED = 1;
ed = alloc_ohci_ED(); /* allocate an Endpoint Descriptor */
if (ed == NULL)
return USBH_ERR_MEMORY_OUT;
ed->Info = info;
ed->HeadP = 0;
ED_debug("Link BULK ED 0x%x: 0x%x 0x%x 0x%x 0x%x\n", (int)ed, ed->Info, ed->TailP, ed->HeadP, ed->NextED);
}
ep->hw_pipe = (void *)ed;
/*------------------------------------------------------------------------------------*/
/* Prepare TDs */
/*------------------------------------------------------------------------------------*/
utr->td_cnt = 0;
data_len = utr->data_len;
buff = utr->buff;
do
{
if ((ep->bEndpointAddress & EP_ADDR_DIR_MASK) == EP_ADDR_DIR_OUT)
info = (TD_CC | TD_R | TD_DP_OUT | TD_TYPE_BULK);
else
info = (TD_CC | TD_R | TD_DP_IN | TD_TYPE_BULK);
info &= ~(1 << 25); /* Data toggle from ED toggleCarry bit */
if (data_len > 4096) /* maximum transfer length is 4K for each TD */
xfer_len = 4096;
else
xfer_len = data_len; /* remaining data length < 4K */
td = alloc_ohci_TD(utr); /* allocate a TD */
if (td == NULL)
goto mem_out;
/* fill this TD */
write_td(td, info, buff, xfer_len);
td->ed = ed;
utr->td_cnt++; /* increase TD count, for recalim counter */
buff += xfer_len; /* advanced buffer pointer */
data_len -= xfer_len;
/* chain to end of TD list */
if (td_list == NULL)
{
td_list = td;
}
else
{
td_p = td_list;
while (td_p->NextTD != 0)
td_p = (TD_T *)td_p->NextTD;
td_p->NextTD = (uint32_t)td;
}
}
while (data_len > 0);
/*------------------------------------------------------------------------------------*/
/* Start transfer */
/*------------------------------------------------------------------------------------*/
utr->status = 0;
DISABLE_OHCI_IRQ();
ed->HeadP = (ed->HeadP & 0x2) | (uint32_t)td_list; /* keep toggleCarry bit */
if (bIsNewED)
{
ed->HeadP = (uint32_t)td_list;
/* Link ED to OHCI Bulk List */
ed->NextED = _ohci->HcBulkHeadED;
_ohci->HcBulkHeadED = (uint32_t)ed;
}
ENABLE_OHCI_IRQ();
_ohci->HcControl |= USBH_HcControl_BLE_Msk; /* enable bulk list */
_ohci->HcCommandStatus = USBH_HcCommandStatus_BLF_Msk; /* start bulk list */
return 0;
mem_out:
while (td_list != NULL)
{
td = td_list;
td_list = (TD_T *)td_list->NextTD;
free_ohci_TD(td);
}
free_ohci_ED(ed);
return USBH_ERR_MEMORY_OUT;
}
static int ohci_int_xfer(UTR_T *utr)
{
UDEV_T *udev = utr->udev;
EP_INFO_T *ep = utr->ep;
ED_T *ed, *ied;
TD_T *td, *td_new;
uint32_t info;
int8_t bIsNewED = 0;
if (utr->data_len > 64) /* USB 1.1 interrupt transfer maximum packet size is 64 */
return USBH_ERR_INVALID_PARAM;
td_new = alloc_ohci_TD(utr); /* allocate a TD for dummy TD */
if (td_new == NULL)
return USBH_ERR_MEMORY_OUT;
ied = get_int_tree_head_node(ep->bInterval); /* get head node of this interval */
/*------------------------------------------------------------------------------------*/
/* Find if this ED was already in the list */
/*------------------------------------------------------------------------------------*/
info = ed_make_info(udev, ep);
ed = ied;
while (ed != NULL)
{
if (ed->Info == info)
break; /* Endpoint found */
ed = (ED_T *)ed->NextED;
}
if (ed == NULL) /* ED not found, create it */
{
bIsNewED = 1;
ed = alloc_ohci_ED(); /* allocate an Endpoint Descriptor */
if (ed == NULL)
return USBH_ERR_MEMORY_OUT;
ed->Info = info;
ed->HeadP = 0;
ed->bInterval = ep->bInterval;
td = alloc_ohci_TD(NULL); /* allocate the initial dummy TD for ED */
if (td == NULL)
{
free_ohci_ED(ed);
free_ohci_TD(td_new);
return USBH_ERR_MEMORY_OUT;
}
ed->HeadP = (uint32_t)td; /* Let both HeadP and TailP point to dummy TD */
ed->TailP = ed->HeadP;
}
else
{
td = (TD_T *)(ed->TailP & ~0xf); /* TailP always point to the dummy TD */
}
ep->hw_pipe = (void *)ed;
/*------------------------------------------------------------------------------------*/
/* Prepare TD */
/*------------------------------------------------------------------------------------*/
if ((ep->bEndpointAddress & EP_ADDR_DIR_MASK) == EP_ADDR_DIR_OUT)
info = (TD_CC | TD_R | TD_DP_OUT | TD_TYPE_INT);
else
info = (TD_CC | TD_R | TD_DP_IN | TD_TYPE_INT);
/* Keep data toggle */
info = (info & ~(1 << 25)) | (td->Info & (1 << 25));
/* fill this TD */
write_td(td, info, utr->buff, utr->data_len);
td->ed = ed;
td->NextTD = (uint32_t)td_new;
td->utr = utr;
utr->td_cnt = 1; /* increase TD count, for recalim counter */
utr->status = 0;
/*------------------------------------------------------------------------------------*/
/* Hook ED and TD list to HCCA interrupt table */
/*------------------------------------------------------------------------------------*/
DISABLE_OHCI_IRQ();
ed->TailP = (uint32_t)td_new;
if (bIsNewED)
{
/* Add to list of the same interval */
ed->NextED = ied->NextED;
ied->NextED = (uint32_t)ed;
}
ENABLE_OHCI_IRQ();
//printf("Link INT ED 0x%x: 0x%x 0x%x 0x%x 0x%x\n", (int)ed, ed->Info, ed->TailP, ed->HeadP, ed->NextED);
_ohci->HcControl |= USBH_HcControl_PLE_Msk; /* periodic list enable */
return 0;
}
static int ohci_iso_xfer(UTR_T *utr)
{
UDEV_T *udev = utr->udev;
EP_INFO_T *ep = utr->ep;
ED_T *ed, *ied;
TD_T *td, *td_list, *last_td;
int i;
uint32_t info;
uint32_t buff_addr;
int8_t bIsNewED = 0;
ied = get_int_tree_head_node(ep->bInterval); /* get head node of this interval */
/*------------------------------------------------------------------------------------*/
/* Find if this ED was already in the list */
/*------------------------------------------------------------------------------------*/
info = ed_make_info(udev, ep);
ed = ied;
while (ed != NULL)
{
if (ed->Info == info)
break; /* Endpoint found */
ed = (ED_T *)ed->NextED;
}
if (ed == NULL) /* ED not found, create it */
{
bIsNewED = 1;
ed = alloc_ohci_ED(); /* allocate an Endpoint Descriptor */
if (ed == NULL)
return USBH_ERR_MEMORY_OUT;
ed->Info = info;
ed->HeadP = 0;
ed->bInterval = ep->bInterval;
}
else
ep->hw_pipe = (void *)ed;
/*------------------------------------------------------------------------------------*/
/* Prepare TDs */
/*------------------------------------------------------------------------------------*/
if (utr->bIsoNewSched) /* Is the starting of isochronous streaming? */
ed->next_sf = _hcca.frame_no + OHCI_ISO_DELAY;
utr->td_cnt = 0;
utr->iso_sf = ed->next_sf;
last_td = NULL;
td_list = NULL;
for (i = 0; i < IF_PER_UTR; i++)
{
utr->iso_status[i] = USBH_ERR_NOT_ACCESS1;
td = alloc_ohci_TD(utr); /* allocate a TD */
if (td == NULL)
goto mem_out;
/* fill this TD */
buff_addr = (uint32_t)(utr->iso_buff[i]);
td->Info = (TD_CC | TD_TYPE_ISO) | ed->next_sf;
ed->next_sf += get_ohci_interval(ed->bInterval);
td->CBP = buff_addr & ~0xFFF;
td->BE = buff_addr + utr->iso_xlen[i] - 1;
td->PSW[0] = 0xE000 | (buff_addr & 0xFFF);
td->ed = ed;
utr->td_cnt++; /* increase TD count, for reclaim counter */
/* chain to end of TD list */
if (td_list == NULL)
td_list = td;
else
last_td->NextTD = (uint32_t)td;
last_td = td;
};
/*------------------------------------------------------------------------------------*/
/* Hook ED and TD list to HCCA interrupt table */
/*------------------------------------------------------------------------------------*/
utr->status = 0;
DISABLE_OHCI_IRQ();
if ((ed->HeadP & ~0x3) == 0)
ed->HeadP = (ed->HeadP & 0x2) | (uint32_t)td_list; /* keep toggleCarry bit */
else
{
/* find the tail of TDs under this ED */
td = (TD_T *)(ed->HeadP & ~0x3);
while (td->NextTD != 0)
{
td = (TD_T *)td->NextTD;
}
td->NextTD = (uint32_t)td_list;
}
if (bIsNewED)
{
/* Add to list of the same interval */
ed->NextED = ied->NextED;
ied->NextED = (uint32_t)ed;
}
ENABLE_OHCI_IRQ();
ED_debug("Link ISO ED 0x%x: 0x%x 0x%x 0x%x 0x%x\n", (int)ed, ed->Info, ed->TailP, ed->HeadP, ed->NextED);
_ohci->HcControl |= USBH_HcControl_PLE_Msk | USBH_HcControl_IE_Msk; /* enable periodic list and isochronous transfer */
return 0;
mem_out:
while (td_list != NULL)
{
td = td_list;
td_list = (TD_T *)td_list->NextTD;
free_ohci_TD(td);
}
free_ohci_ED(ed);
return USBH_ERR_MEMORY_OUT;
}
static UDEV_T *ohci_find_device_by_port(int port)
{
UDEV_T *udev;
udev = g_udev_list;
while (udev != NULL)
{
if ((udev->parent == NULL) && (udev->port_num == port) &&
((udev->speed == SPEED_LOW) || (udev->speed == SPEED_FULL)))
return udev;
udev = udev->next;
}
return NULL;
}
static int ohci_rh_port_reset(int port)
{
int retry;
int reset_time;
uint32_t t0;
reset_time = usbh_tick_from_millisecond(PORT_RESET_TIME_MS);
for (retry = 0; retry < PORT_RESET_RETRY; retry++)
{
_ohci->HcRhPortStatus[port] = USBH_HcRhPortStatus_PRS_Msk;
t0 = usbh_get_ticks();
while (usbh_get_ticks() - t0 < (reset_time) + 1)
{
/*
* If device is disconnected or port enabled, we can stop port reset.
*/
if (((_ohci->HcRhPortStatus[port] & USBH_HcRhPortStatus_CCS_Msk) == 0) ||
((_ohci->HcRhPortStatus[port] & (USBH_HcRhPortStatus_PES_Msk | USBH_HcRhPortStatus_CCS_Msk)) == (USBH_HcRhPortStatus_PES_Msk | USBH_HcRhPortStatus_CCS_Msk)))
goto port_reset_done;
}
reset_time += PORT_RESET_RETRY_INC_MS;
}
USB_debug("OHCI port %d - port reset failed!\n", port + 1);
return USBH_ERR_PORT_RESET;
port_reset_done:
if ((_ohci->HcRhPortStatus[port] & USBH_HcRhPortStatus_CCS_Msk) == 0) /* check again if device disconnected */
{
_ohci->HcRhPortStatus[port] = USBH_HcRhPortStatus_CSC_Msk; /* clear CSC */
return USBH_ERR_DISCONNECTED;
}
return USBH_OK; /* port reset success */
}
static int ohci_rh_polling(void)
{
int i, change = 0;
UDEV_T *udev;
int ret;
for (i = 0; i < 2; i++)
{
/* M460LD OHCI has no root hub port 1 */
/* clear unwanted port change status */
_ohci->HcRhPortStatus[i] = USBH_HcRhPortStatus_OCIC_Msk | USBH_HcRhPortStatus_PRSC_Msk |
USBH_HcRhPortStatus_PSSC_Msk | USBH_HcRhPortStatus_PESC_Msk;
if ((_ohci->HcRhPortStatus[i] & USBH_HcRhPortStatus_CSC_Msk) == 0)
continue;
/*--------------------------------------------------------------------------------*/
/* connect status change */
/*--------------------------------------------------------------------------------*/
_ohci->HcRhPortStatus[i] = USBH_HcRhPortStatus_CSC_Msk; /* clear CSC */
if (_ohci->HcRhPortStatus[i] & USBH_HcRhPortStatus_CCS_Msk)
{
/*----------------------------------------------------------------------------*/
/* First of all, check if there's any previously connected device. */
/*----------------------------------------------------------------------------*/
while (1)
{
udev = ohci_find_device_by_port(i + 1);
if (udev == NULL)
break;
usbh_disconnect_device(udev);
}
if (ohci_rh_port_reset(i) != USBH_OK)
continue;
/*
* Port reset success...
*/
udev = alloc_device();
if (udev == NULL)
continue;
udev->parent = NULL;
udev->port_num = i + 1;
if (_ohci->HcRhPortStatus[i] & USBH_HcRhPortStatus_LSDA_Msk)
udev->speed = SPEED_LOW;
else
udev->speed = SPEED_FULL;
udev->hc_driver = &ohci_driver;
ret = usbh_connect_device(udev);
if (ret < 0)
{
USB_error("connect_device error! [%d]\n", ret);
free_device(udev);
}
change = 1;
}
else
{
/*
* Device disconnected
*/
while (1)
{
udev = ohci_find_device_by_port(i + 1);
if (udev == NULL)
break;
usbh_disconnect_device(udev);
}
change = 1;
}
}
return change;
}
void td_done(TD_T *td)
{
UTR_T *utr = td->utr;
uint32_t info;
int cc;
info = td->Info;
TD_debug("td_done: 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", (int)td, td->Info, td->CBP, td->NextTD, td->BE);
/* ISO ... drivers see per-TD length/status */
if ((info & TD_TYPE_Msk) == TD_TYPE_ISO)
{
uint16_t sf;
int idx;
sf = info & 0xFFFF;
idx = ((sf + 0x10000 - utr->iso_sf) & 0xFFFF) / get_ohci_interval(td->ed->bInterval);
if (idx >= IF_PER_UTR)
{
USB_error("ISO invalid index!! %d, %d\n", sf, utr->iso_sf);
goto td_out;
}
cc = (td->PSW[0] >> 12) & 0xF;
if (cc == 0xF) /* this frame was not transferred */
{
USB_debug("ISO F %d N/A!\n", sf);
utr->iso_status[idx] = USBH_ERR_SCH_OVERRUN;
goto td_out;
}
if ((cc != 0) && (cc != CC_DATA_UNDERRUN))
{
utr->iso_status[idx] = USBH_ERR_CC_NO_ERR - cc;
goto td_out;
}
utr->iso_status[idx] = 0;
utr->iso_xlen[idx] = td->PSW[0] & 0x7FF;
}
else
{
cc = TD_CC_GET(info);
/* short packet is fine */
if ((cc != CC_NOERROR) && (cc != CC_DATA_UNDERRUN))
{
USB_error("TD error, CC = 0x%x\n", cc);
if (cc == CC_STALL)
utr->status = USBH_ERR_STALL;
else
utr->status = USBH_ERR_TRANSFER;
}
switch (info & TD_TYPE_Msk)
{
case TD_TYPE_CTRL:
if (info & TD_CTRL_DATA)
{
if (td->CBP == 0)
utr->xfer_len += td->BE - td->buff_start + 1;
else
utr->xfer_len += td->CBP - td->buff_start;
}
break;
case TD_TYPE_BULK:
case TD_TYPE_INT:
if (td->CBP == 0)
utr->xfer_len += td->BE - td->buff_start + 1;
else
utr->xfer_len += td->CBP - td->buff_start;
break;
}
}
td_out:
utr->td_cnt--;
/* If all TDs are done, call-back to requester. */
if (utr->td_cnt == 0)
{
utr->bIsTransferDone = 1;
if (utr->func)
utr->func(utr);
}
}
/* in IRQ context */
static void remove_ed()
{
ED_T *ed, *ed_p, *ied;
TD_T *td, *td_next;
UTR_T *utr;
int found;
while (ed_remove_list != NULL)
{
ED_debug("Remove ED: 0x%x, %d\n", (int)ed_remove_list, ed_remove_list->bInterval);
ed_p = ed_remove_list;
found = 0;
/*--------------------------------------------------------------------------------*/
/* Remove endpoint from Control List if found */
/*--------------------------------------------------------------------------------*/
if ((ed_p->Info & ED_EP_ADDR_Msk) == 0)
{
if (_ohci->HcControlHeadED == (uint32_t)ed_p)
{
_ohci->HcControlHeadED = (uint32_t)ed_p->NextED;
found = 1;
}
else
{
ed = (ED_T *)_ohci->HcControlHeadED;
while (ed != NULL)
{
if (ed->NextED == (uint32_t)ed_p)
{
ed->NextED = ed_p->NextED;
found = 1;
}
ed = (ED_T *)ed->NextED;
}
}
}
/*--------------------------------------------------------------------------------*/
/* Remove INT or ISO endpoint from HCCA interrupt table */
/*--------------------------------------------------------------------------------*/
else if (ed_p->bInterval > 0)
{
ied = get_int_tree_head_node(ed_p->bInterval);
ed = ied;
while (ed != NULL)
{
if (ed->NextED == (uint32_t)ed_p)
{
ed->NextED = ed_p->NextED;
found = 1;
break;
}
ed = (ED_T *)ed->NextED;
}
}
/*--------------------------------------------------------------------------------*/
/* Remove endpoint from Bulk List if found */
/*--------------------------------------------------------------------------------*/
else
{
if (_ohci->HcBulkHeadED == (uint32_t)ed_p)
{
ed = (ED_T *)ed_p;
_ohci->HcBulkHeadED = ed_p->NextED;
found = 1;
}
else
{
ed = (ED_T *)_ohci->HcBulkHeadED;
while (ed != NULL)
{
if (ed->NextED == (uint32_t)ed_p)
{
ed->NextED = ed_p->NextED;
found = 1;
}
ed = (ED_T *)ed->NextED;
}
}
}
/*--------------------------------------------------------------------------------*/
/* Remove and free all TDs under this endpoint */
/*--------------------------------------------------------------------------------*/
if (found)
{
td = (TD_T *)(ed_p->HeadP & ~0x3);
if (td != NULL)
{
while (td != NULL)
{
utr = td->utr;
td_next = (TD_T *)td->NextTD;
free_ohci_TD(td);
td = td_next;
utr->td_cnt--;
if (utr->td_cnt == 0)
{
utr->status = USBH_ERR_ABORT;
utr->bIsTransferDone = 1;
if (utr->func)
utr->func(utr);
}
}
}
}
/*
* Done. Remove this ED from [ed_remove_list] and free it.
*/
ed_remove_list = ed_p->next;
free_ohci_ED(ed_p);
}
}
//static irqreturn_t ohci_irq (struct usb_hcd *hcd)
void OHCI_IRQHandler(void)
{
TD_T *td, *td_prev, *td_next;
uint32_t int_sts;
/* enter interrupt */
rt_interrupt_enter();
int_sts = _ohci->HcInterruptStatus;
//USB_debug("ohci int_sts = 0x%x\n", int_sts);
if ((_ohci->HcInterruptEnable & USBH_HcInterruptEnable_SF_Msk) &&
(int_sts & USBH_HcInterruptStatus_SF_Msk))
{
int_sts &= ~USBH_HcInterruptStatus_SF_Msk;
_ohci->HcInterruptDisable = USBH_HcInterruptDisable_SF_Msk;
remove_ed();
_ohci->HcInterruptStatus = USBH_HcInterruptStatus_SF_Msk;
}
if (int_sts & USBH_HcInterruptStatus_WDH_Msk)
{
//printf("!%02x\n", _ohci->HcFmNumber & 0xff);
int_sts &= ~USBH_HcInterruptStatus_WDH_Msk;
/*
* reverse done list
*/
td = (TD_T *)(_hcca.done_head & TD_ADDR_MASK);
_hcca.done_head = 0;
td_prev = NULL;
_ohci->HcInterruptStatus = USBH_HcInterruptStatus_WDH_Msk;
while (td != NULL)
{
//TD_debug("Done list TD 0x%x => 0x%x\n", (int)td, (int)td->NextTD);
td_next = (TD_T *)(td->NextTD & TD_ADDR_MASK);
td->NextTD = (uint32_t)td_prev;
td_prev = td;
td = td_next;
}
td = td_prev; /* first TD of the reversed done list */
/*
* reclaim TDs
*/
while (td != NULL)
{
TD_debug("Reclaim TD 0x%x, next 0x%x\n", (int)td, td->NextTD);
td_next = (TD_T *)td->NextTD;
td_done(td);
free_ohci_TD(td);
td = td_next;
}
}
if (int_sts & USBH_HcInterruptStatus_RHSC_Msk)
{
_ohci->HcInterruptDisable = USBH_HcInterruptDisable_RHSC_Msk;
}
_ohci->HcInterruptStatus = int_sts;
/* leave interrupt */
rt_interrupt_leave();
}
#ifdef ENABLE_DEBUG_MSG
void dump_ohci_int_table()
{
int i;
ED_T *ed;
for (i = 0; i < 32; i++)
// for (i = 0; i < 1; i++)
{
USB_debug("%02d: ", i);
ed = (ED_T *)_hcca.int_table[i];
while (ed != NULL)
{
USB_debug("0x%x (0x%x) => ", (int)ed, ed->HeadP);
ed = (ED_T *)ed->NextED;
}
rt_kprintf("0\n");
}
}
void dump_ohci_regs()
{
USB_debug("Dump OCHI registers:\n");
USB_debug(" HcRevision = 0x%x\n", _ohci->HcRevision);
USB_debug(" HcControl = 0x%x\n", _ohci->HcControl);
USB_debug(" HcCommandStatus = 0x%x\n", _ohci->HcCommandStatus);
USB_debug(" HcInterruptStatus = 0x%x\n", _ohci->HcInterruptStatus);
USB_debug(" HcInterruptEnable = 0x%x\n", _ohci->HcInterruptEnable);
USB_debug(" HcInterruptDisable = 0x%x\n", _ohci->HcInterruptDisable);
USB_debug(" HcHCCA = 0x%x\n", _ohci->HcHCCA);
USB_debug(" HcPeriodCurrentED = 0x%x\n", _ohci->HcPeriodCurrentED);
USB_debug(" HcControlHeadED = 0x%x\n", _ohci->HcControlHeadED);
USB_debug(" HcControlCurrentED = 0x%x\n", _ohci->HcControlCurrentED);
USB_debug(" HcBulkHeadED = 0x%x\n", _ohci->HcBulkHeadED);
USB_debug(" HcBulkCurrentED = 0x%x\n", _ohci->HcBulkCurrentED);
USB_debug(" HcDoneHead = 0x%x\n", _ohci->HcDoneHead);
USB_debug(" HcFmInterval = 0x%x\n", _ohci->HcFmInterval);
USB_debug(" HcFmRemaining = 0x%x\n", _ohci->HcFmRemaining);
USB_debug(" HcFmNumber = 0x%x\n", _ohci->HcFmNumber);
USB_debug(" HcPeriodicStart = 0x%x\n", _ohci->HcPeriodicStart);
USB_debug(" HcLSThreshold = 0x%x\n", _ohci->HcLSThreshold);
USB_debug(" HcRhDescriptorA = 0x%x\n", _ohci->HcRhDescriptorA);
USB_debug(" HcRhDescriptorB = 0x%x\n", _ohci->HcRhDescriptorB);
USB_debug(" HcRhStatus = 0x%x\n", _ohci->HcRhStatus);
USB_debug(" HcRhPortStatus0 = 0x%x\n", _ohci->HcRhPortStatus[0]);
USB_debug(" HcRhPortStatus1 = 0x%x\n", _ohci->HcRhPortStatus[1]);
USB_debug(" HcPhyControl = 0x%x\n", _ohci->HcPhyControl);
USB_debug(" HcMiscControl = 0x%x\n", _ohci->HcMiscControl);
}
void dump_ohci_ports()
{
USB_debug("_ohci port0=0x%x, port1=0x%x\n", _ohci->HcRhPortStatus[0], _ohci->HcRhPortStatus[1]);
}
#endif // ENABLE_DEBUG_MSG
HC_DRV_T ohci_driver =
{
ohci_init, /* init */
ohci_shutdown, /* shutdown */
ohci_suspend, /* suspend */
ohci_resume, /* resume */
ohci_ctrl_xfer, /* ctrl_xfer */
ohci_bulk_xfer, /* bulk_xfer */
ohci_int_xfer, /* int_xfer */
ohci_iso_xfer, /* iso_xfer */
ohci_quit_xfer, /* quit_xfer */
ohci_rh_port_reset, /* rthub_port_reset */
ohci_rh_polling /* rthub_polling */
};
/// @endcond HIDDEN_SYMBOLS
/*** (C) COPYRIGHT 2017 Nuvoton Technology Corp. ***/