1499 lines
42 KiB
C
1499 lines
42 KiB
C
/*
|
|
* Kneron CMSIS Driver
|
|
*
|
|
* Copyright (C) 2019 Kneron, Inc. All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#include "cmsis_os2.h" // CMSIS RTOS header file
|
|
#include "Driver_USBH.h"
|
|
#include "kdrv_cmsis_core.h"
|
|
#include "regbase.h"
|
|
#include "io.h"
|
|
|
|
#include "kmdw_memory.h"
|
|
#include <string.h>
|
|
|
|
#include "base.h"
|
|
|
|
//#define USBH_DRV_DBG // turn on this can help trace code, a lot of code-size needed
|
|
//#define USBH_DRV_ERR // turn on this can add some more error checking, slightly code-size needed
|
|
#define USBH_DRV_FULL // turn on this to get full set of functions, some code-sie needed
|
|
|
|
#if defined(USBH_DRV_DBG) | defined(USBH_DRV_ERR)
|
|
#include "kdrv_uart.h"
|
|
#endif
|
|
|
|
/* Register read/write macros */
|
|
|
|
// FIXME: copied from kdp_usbd_reg.h
|
|
#define EHCI_USBCMD 0x10
|
|
/* USBSTS bit fields */
|
|
#define ASCH_EN BIT5
|
|
#define PSCH_EN BIT4
|
|
#define HC_RESET BIT1
|
|
#define RS BIT0
|
|
/* end */
|
|
|
|
#define EHCI_USBSTS 0x14
|
|
/* USBSTS bit fields */
|
|
#define ASCH_STS BIT15
|
|
#define PSCH_STS BIT14
|
|
#define HCHalted BIT12
|
|
#define INT_OAA BIT5
|
|
#define H_SYSERR BIT4
|
|
#define FRL_ROL BIT3
|
|
#define PO_CHG_DET BIT2
|
|
#define USBERR_INT BIT1
|
|
#define USB_INT BIT0
|
|
/* end */
|
|
|
|
#define EHCI_USBINTR 0x18
|
|
/* USBINTR bit fields */
|
|
#define H_SYSERR_EN BIT4
|
|
#define PO_CHG_INT_EN BIT2
|
|
#define USBERR_INT_EN BIT1
|
|
#define USB_INT_EN BIT0
|
|
/* end */
|
|
|
|
#define EHCI_FRINDEX 0x1C
|
|
|
|
#define EHCI_PERIODIC_LIST_ADDR 0x24
|
|
#define EHCI_ASYNC_LIST_ADDR 0x28
|
|
|
|
#define EHCI_PORTSC 0x30
|
|
/* USBINTR bit fields */
|
|
#define PO_RESET BIT8
|
|
#define PO_SUSP BIT7
|
|
#define F_PO_RESM BIT6
|
|
#define PO_EN BIT2
|
|
#define CONN_CHG BIT1
|
|
#define CONN_STS BIT0
|
|
/* end */
|
|
|
|
#define REG_OTG_CSR 0x80
|
|
#define A_BUS_DROP BIT5
|
|
#define A_BUS_REQ BIT4
|
|
|
|
#define REG_OTG_ISR 0x84
|
|
#define OTG_OVC_RW1C BIT10
|
|
|
|
#define REG_GLB_INT 0xC4
|
|
|
|
#define REG_DEV_CTL 0x100
|
|
|
|
// PHY Test Mode Selector Register (0x114)
|
|
#define REG_PHY_TST 0x114
|
|
#define TST_JSTA BIT0
|
|
|
|
#define UsbRegRead(reg_offset) inw(USB_FOTG210_PA_BASE + reg_offset)
|
|
#define UsbRegWrite(reg_offset, val) outw(USB_FOTG210_PA_BASE + reg_offset, val)
|
|
#define UsbRegMaskedSet(reg_offset, val) masked_outw(USB_FOTG210_PA_BASE + reg_offset, val, val)
|
|
#define UsbRegMaskedClr(reg_offset, val) masked_outw(USB_FOTG210_PA_BASE + reg_offset, 0, val)
|
|
|
|
/* USB Host Driver */
|
|
|
|
#define ARM_USBH_DRV_VERSION ARM_DRIVER_VERSION_MAJOR_MINOR(2, 0) /* driver version */
|
|
|
|
typedef enum
|
|
{
|
|
ASYNC_SCHEDULE = ASCH_EN,
|
|
PERIODIC_SCHEDULE = PSCH_EN,
|
|
} EHCI_SCHEDULE_TYPE;
|
|
|
|
/* Queue Head */
|
|
#define QH_NUM 10
|
|
#define QH_REAL_SIZE 48 // EHCI actual size
|
|
#define QH_ALLOC_IZE 64 // for 32-byte alignment and some meta-data usage
|
|
|
|
#define qTD_NUM 50
|
|
#define qTD_REAL_SIZE 32 // EHCI actual size
|
|
#define qTD_ALLOC_SIZE 64 // for 32-byte alignment and some meta-data usage
|
|
|
|
#define MAX_PIPES QH_NUM
|
|
|
|
#define FRAME_LIST_SIZE 1024
|
|
#define FRAME_LIST_ROLL_MASK (FRAME_LIST_SIZE - 1)
|
|
|
|
#define iTD_MAX_NUM 1024
|
|
#define iTD_SIZE 64
|
|
|
|
// for QH, qTD and iTD link pointer dwords
|
|
struct Link_Pointer_DWord
|
|
{
|
|
uint32_t terminate : 1;
|
|
uint32_t type : 2; // valid only for QH and iTD
|
|
uint32_t reserved : 2;
|
|
uint32_t ptr_address : 27;
|
|
};
|
|
|
|
// qTD DWORD 2
|
|
struct Status_DWord
|
|
{
|
|
uint32_t status : 8;
|
|
uint32_t PID : 2;
|
|
uint32_t CERR : 2;
|
|
uint32_t c_page : 3;
|
|
uint32_t ioc : 1;
|
|
uint32_t total_bytes_txfer : 15;
|
|
uint32_t dt : 1;
|
|
};
|
|
|
|
// qTD Buffer Pointer DWORD
|
|
struct Buffer_Pointer_DWord
|
|
{
|
|
uint32_t cur_offset : 12; // only valid for DWORD 3
|
|
uint32_t ptr_address : 20; // 4KBytes-aligned addrees
|
|
};
|
|
|
|
// qTD data structure
|
|
typedef struct __attribute__((__packed__))
|
|
{
|
|
// DWORD 0 : Next qTD Pointer
|
|
struct Link_Pointer_DWord next_qtd;
|
|
|
|
// DWORD 1 : Alternate Next qTD Pointer
|
|
struct Link_Pointer_DWord alt_next_qtd;
|
|
|
|
// DWORD 2 : Status, PID Code, Error Counter, Current Page, Interrupt On Complete, Total Bytes to Transfer, Data Toggle
|
|
struct Status_DWord status_dword;
|
|
|
|
// DWORD 3~7 : Buffer Pointer pages and current offset
|
|
struct Buffer_Pointer_DWord buf_ptr_dword[5];
|
|
|
|
// ======== below is for internal metada ========
|
|
uint32_t in_use;
|
|
uint32_t wanted_txfer_bytes; // record the wanted txfer bytes
|
|
// ==============================================
|
|
|
|
} Q_TD;
|
|
|
|
// iTD Transaction DWORD
|
|
struct Transaction_DWord
|
|
{
|
|
uint32_t offset : 12;
|
|
uint32_t pg : 3;
|
|
uint32_t ioc : 1;
|
|
uint32_t length : 12;
|
|
uint32_t status : 4;
|
|
};
|
|
|
|
// iTD Buffer Pointer Page DWORD (generic)
|
|
struct Buffer_Page_DWord
|
|
{
|
|
uint32_t misc : 12; // for page 0~2, use differnt structs
|
|
uint32_t ptr_address : 20; // 4KBytes-aligned addrees
|
|
};
|
|
|
|
// iTD Buffer Pointer Page 0 DWORD
|
|
struct Buffer_Page_0_DWord
|
|
{
|
|
uint32_t dev_addr : 7;
|
|
uint32_t reserved : 1;
|
|
uint32_t endpoint : 4;
|
|
uint32_t ptr_address : 20; // 4KBytes-aligned addrees
|
|
};
|
|
|
|
// iTD Buffer Pointer Page 1 DWORD
|
|
struct Buffer_Page_1_DWord
|
|
{
|
|
uint32_t max_packet_size : 11;
|
|
uint32_t direction : 1;
|
|
uint32_t ptr_address : 20; // 4KBytes-aligned addrees
|
|
};
|
|
|
|
// iTD Buffer Pointer Page 2 DWORD
|
|
struct Buffer_Page_2_DWord
|
|
{
|
|
uint32_t mult : 2; // valid values are 1~3, indidcates number of transaction per micro-frame
|
|
uint32_t reserved : 10;
|
|
uint32_t ptr_address : 20; // 4KBytes-aligned addrees
|
|
};
|
|
|
|
// iTD data structure
|
|
typedef struct __attribute__((__packed__))
|
|
{
|
|
// DWORD 0 : Next qTD Pointer
|
|
struct Link_Pointer_DWord next_link; // QH or iTD
|
|
|
|
// DWORD 1~8 : Transaction DWORDs
|
|
struct Transaction_DWord trans_dword[8];
|
|
|
|
// DWORD 9~15 : Buffer Pointer pages and others
|
|
struct Buffer_Page_DWord buf_page_dword[7];
|
|
|
|
} I_TD;
|
|
|
|
// QH DWORD 1
|
|
struct Device_Address_DWord
|
|
{
|
|
uint32_t dev_addr : 7;
|
|
uint32_t inactivate : 1;
|
|
uint32_t endpoint : 4;
|
|
uint32_t endpt_speed : 2;
|
|
uint32_t dtc : 1;
|
|
uint32_t H_reclamation : 1;
|
|
uint32_t max_packet_length : 11;
|
|
uint32_t C_endpt_flag : 1;
|
|
uint32_t RL_Nak_count : 4;
|
|
};
|
|
|
|
// QH DWORD 2
|
|
struct Mask_DWord
|
|
{
|
|
uint32_t S_mask : 8;
|
|
uint32_t C_mask : 8;
|
|
uint32_t hub_addr : 7;
|
|
uint32_t port_number : 7;
|
|
uint32_t mult : 2;
|
|
};
|
|
|
|
// Queue Head data structure
|
|
typedef struct __attribute__((__packed__))
|
|
{
|
|
// DWORD 0
|
|
struct Link_Pointer_DWord next_qh;
|
|
|
|
// DWORD 1
|
|
struct Device_Address_DWord device_dword;
|
|
|
|
// DWORD 2
|
|
struct Mask_DWord mask_dword;
|
|
|
|
// DWORD 3 : Current qTD Pointer
|
|
uint32_t cur_qtd_ptr;
|
|
|
|
// DWORD 4~11 : overlay of qTD
|
|
uint32_t overlay_next_qtd; // qTD dword 0
|
|
uint32_t overlay_alternate_qtd; // qTD dword 1
|
|
uint32_t overlay_dword_others[6]; // qTD dword 2~7, dont care
|
|
|
|
// ======== below is for internal metada ========
|
|
uint32_t prev_link_ptr; // make it double linked list
|
|
uint32_t all_txfered_bytes; // record all txfered bytes
|
|
uint8_t is_txfering; // mark it '1' if this QH is doing transfer, otherwise '0'
|
|
uint8_t in_use;
|
|
uint8_t endpoint_type; // control/bulk/interrupt/isochronous
|
|
// ==============================================
|
|
|
|
} Queue_Head;
|
|
|
|
static bool hw_initialized = false;
|
|
static uint32_t QH_addr[QH_NUM];
|
|
static uint32_t qTD_addr[qTD_NUM];
|
|
static uint32_t iTD_addr[iTD_MAX_NUM];
|
|
static uint32_t *periodic_frame_list = 0;
|
|
|
|
static ARM_USBH_SignalPortEvent_t notify_port_event_cb = 0;
|
|
static ARM_USBH_SignalPipeEvent_t notify_pipe_event_cb = 0;
|
|
|
|
#ifdef KNERON_USBH_MDW
|
|
|
|
typedef uint32_t (*ARM_USBH_ISOCH_ITD_WORK)();
|
|
static uint32_t usbh_handle_iTD_work(void);
|
|
|
|
static ARM_USBH_ISOCH_DATA_CALLBACK notify_isoch_data_cb = 0;
|
|
static ARM_USBH_ISOCH_ITD_WORK isoch_process_itd_work = usbh_handle_iTD_work; // it can be replaced by user bottom-half callback
|
|
|
|
// below are all for iTD management
|
|
static uint32_t isoch_running_flag = 0; // ISOCH periodic schelde running flag
|
|
static uint32_t isoch_next_insert_pos = 0; // next inserted pos for iTD
|
|
static uint32_t isoch_next_process_pos = 0; // next processing pos for iTD
|
|
static uint8_t isoch_trans_interval = 0; // transaction interval in a iTD
|
|
static uint32_t isoch_txfer_len = 0; // total transfer length
|
|
static uint8_t isoch_iTD_interval = 0; // iTD interval in the periodic frame list
|
|
static uint8_t isoch_IOC_pos = 0; // position of the IOC flag
|
|
static uint32_t isoch_iTD_num = 0; // number of iTD
|
|
|
|
#endif
|
|
|
|
/* Driver Version */
|
|
static const ARM_DRIVER_VERSION usbh_driver_version = {
|
|
ARM_USBH_API_VERSION,
|
|
ARM_USBH_DRV_VERSION};
|
|
|
|
/* Driver Capabilities */
|
|
static const ARM_USBH_CAPABILITIES usbh_driver_capabilities = {
|
|
0x0001U, /* Root HUB available Ports Mask */
|
|
0U, /* Automatic SPLIT packet handling */
|
|
1U, /* Signal Connect event */
|
|
1U, /* Signal Disconnect event */
|
|
0U, /* Signal Overcurrent event */
|
|
0U /* Reserved */
|
|
};
|
|
|
|
static void fotg210_ehci_USBCMD_schedule_set_enable(EHCI_SCHEDULE_TYPE schedule_type, bool enable)
|
|
{
|
|
|
|
uint32_t usbsts = UsbRegRead(EHCI_USBSTS);
|
|
|
|
uint32_t bit_check;
|
|
if (schedule_type == ASCH_EN)
|
|
bit_check = BIT15;
|
|
else
|
|
bit_check = BIT14;
|
|
|
|
if (enable)
|
|
{
|
|
if (usbsts & bit_check)
|
|
return;
|
|
|
|
UsbRegMaskedSet(EHCI_USBCMD, schedule_type);
|
|
|
|
// polling wait for status change
|
|
while ((UsbRegRead(EHCI_USBSTS) & bit_check) == 0)
|
|
;
|
|
}
|
|
else
|
|
{
|
|
if ((usbsts & bit_check) == 0)
|
|
return;
|
|
|
|
UsbRegMaskedClr(EHCI_USBCMD, schedule_type);
|
|
|
|
// polling wait for status change
|
|
while ((UsbRegRead(EHCI_USBSTS) & bit_check) != 0)
|
|
;
|
|
}
|
|
}
|
|
|
|
static void fotg210_ehci_USBCMD_HC_run()
|
|
{
|
|
// According to EHCI spec, doing USBCMD Run only if USBSTS is in HCHalted
|
|
if (UsbRegRead(EHCI_USBSTS) & HCHalted)
|
|
{
|
|
UsbRegMaskedSet(EHCI_USBCMD, RS);
|
|
|
|
// polling wait for !HCHalted
|
|
while ((UsbRegRead(EHCI_USBSTS) & HCHalted))
|
|
;
|
|
}
|
|
}
|
|
|
|
static void fotg210_ehci_USBCMD_HC_stop()
|
|
{
|
|
if (!(UsbRegRead(EHCI_USBSTS) & HCHalted))
|
|
{
|
|
// first disable all scheduling works
|
|
fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, false);
|
|
fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, false);
|
|
|
|
UsbRegMaskedClr(EHCI_USBCMD, RS);
|
|
|
|
// polling wait for HCHalted
|
|
while (!(UsbRegRead(EHCI_USBSTS) & HCHalted))
|
|
;
|
|
}
|
|
}
|
|
|
|
static void fotg210_ehci_data_struct_from_ddr()
|
|
{
|
|
// alloacte a pool of memory from DDR
|
|
|
|
uint32_t ehci_struct_ddr_addr = 0; // all data structures begin address in DDR
|
|
uint32_t needed_size = QH_NUM * QH_ALLOC_IZE + qTD_NUM * qTD_ALLOC_SIZE + iTD_MAX_NUM * iTD_SIZE + 128; // 128 just for more space
|
|
|
|
ehci_struct_ddr_addr = kmdw_ddr_reserve(needed_size) & (~0x1F);
|
|
|
|
memset((void *)ehci_struct_ddr_addr, 0, needed_size);
|
|
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s() allocate ddr mem %d bytes\n", __FUNCTION__, needed_size);
|
|
kmdw_printf("@@ Q_TD size %d, Queue Head size %d\n", sizeof(Q_TD), sizeof(Queue_Head));
|
|
#endif
|
|
|
|
QH_addr[0] = ehci_struct_ddr_addr;
|
|
for (int i = 1; i < QH_NUM; i++)
|
|
{
|
|
QH_addr[i] = QH_addr[i - 1] + QH_ALLOC_IZE;
|
|
}
|
|
|
|
qTD_addr[0] = QH_addr[QH_NUM - 1] + QH_ALLOC_IZE;
|
|
for (int i = 1; i < qTD_NUM; i++)
|
|
{
|
|
qTD_addr[i] = qTD_addr[i - 1] + qTD_ALLOC_SIZE;
|
|
}
|
|
|
|
iTD_addr[0] = qTD_addr[QH_NUM - 1] + qTD_ALLOC_SIZE;
|
|
for (int i = 1; i < iTD_MAX_NUM; i++)
|
|
{
|
|
iTD_addr[i] = iTD_addr[i - 1] + iTD_SIZE;
|
|
}
|
|
|
|
periodic_frame_list = (uint32_t *)kmdw_ddr_reserve(2 * sizeof(uint32_t) * FRAME_LIST_SIZE); // FIXME: double the allocated size due to 4-KB alignment
|
|
periodic_frame_list = (uint32_t *)(((uint32_t)periodic_frame_list & (~0xFFF)) + 0x1000); // now make periodic_frame_list begin from 4-KB boundray address
|
|
|
|
for (int i = 0; i < FRAME_LIST_SIZE; i++)
|
|
{
|
|
periodic_frame_list[i] = 0x1; // mark T-bit as 1 indicating invalid pointers
|
|
}
|
|
}
|
|
|
|
// USB host ISR
|
|
static void usbh_driver_isr(void)
|
|
{
|
|
// read the USB Status register
|
|
uint32_t usbsts = UsbRegRead(EHCI_USBSTS);
|
|
|
|
// USB transaction completeion
|
|
if (usbsts & USB_INT)
|
|
{
|
|
uint32_t txfer_complt = 0; // is any transfer completed ?
|
|
|
|
#ifdef KNERON_USBH_MDW
|
|
|
|
// CMSIS middleware does not support ISOCH transfer
|
|
if (isoch_running_flag)
|
|
{
|
|
txfer_complt = isoch_process_itd_work();
|
|
}
|
|
|
|
#endif
|
|
|
|
// scan all QHs
|
|
for (int i = 0; i < QH_NUM; i++)
|
|
{
|
|
Queue_Head *qh = (Queue_Head *)QH_addr[i];
|
|
if (qh->is_txfering)
|
|
{
|
|
Q_TD *qTD = (Q_TD *)(qh->cur_qtd_ptr & (~0x1F));
|
|
|
|
if (qTD && qTD->in_use == true)
|
|
{
|
|
uint8_t status_bits = qTD->status_dword.status;
|
|
if (status_bits <= 0x1) // 0 or 1
|
|
{
|
|
qh->is_txfering = false;
|
|
qTD->in_use = false;
|
|
qh->all_txfered_bytes += (qTD->wanted_txfer_bytes - qTD->status_dword.total_bytes_txfer);
|
|
|
|
// notify middleware of the completeion
|
|
notify_pipe_event_cb((uint32_t)qh, ARM_USBH_EVENT_TRANSFER_COMPLETE);
|
|
|
|
txfer_complt = 1;
|
|
}
|
|
else if (status_bits == 0x80) // still in Active ? guess DDR update slower ?
|
|
{
|
|
// A workaround, we skip this interrupt
|
|
// just do nothing, keep 'txfer_complt = 0'
|
|
}
|
|
else
|
|
{
|
|
#ifdef USBH_DRV_ERR
|
|
kmdw_printf("-- QH 0x%p Q_TD 0x%p error, status 0x%x, CERR 0x%x, in_use %d\n", qh, qTD, qTD->status_dword.status, qTD->status_dword.CERR, qTD->in_use);
|
|
#endif
|
|
txfer_complt = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (txfer_complt > 0) // a workaround for ZLP interrupt coming prior to status bit update in DDR
|
|
UsbRegMaskedSet(EHCI_USBSTS, USB_INT);
|
|
}
|
|
|
|
// USB transaction error
|
|
if (usbsts & USBERR_INT)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("-- transaction error\n");
|
|
#endif
|
|
UsbRegMaskedSet(EHCI_USBSTS, USBERR_INT);
|
|
// UsbRegMaskedSet(EHCI_USBCMD, HC_RESET);
|
|
}
|
|
|
|
// port change detected
|
|
if (usbsts & PO_CHG_DET)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("-- port change detected\n");
|
|
#endif
|
|
|
|
// read the PORTSC
|
|
uint32_t portsc = UsbRegRead(EHCI_PORTSC);
|
|
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s(), portsc: 0x%x\n", __FUNCTION__, portsc);
|
|
#endif
|
|
// check if connect status change in PORTSC
|
|
if (portsc & CONN_CHG)
|
|
{
|
|
// write 1 to clear bit
|
|
UsbRegMaskedSet(EHCI_PORTSC, CONN_CHG);
|
|
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("-- notify ARM_USBH_EVENT_CONNECT/DISCONNECT\n");
|
|
#endif
|
|
|
|
// call back for USB middleware
|
|
if (portsc & CONN_STS) {
|
|
notify_port_event_cb(0, ARM_USBH_EVENT_CONNECT);
|
|
}
|
|
else {
|
|
notify_port_event_cb(0, ARM_USBH_EVENT_DISCONNECT);
|
|
}
|
|
}
|
|
// Port changed in USBSTS but no connection change in PORTSC
|
|
// so guess it could be a port reset
|
|
else
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("-- notify ARM_USBH_EVENT_RESET\n");
|
|
#endif
|
|
notify_port_event_cb(0, ARM_USBH_EVENT_RESET);
|
|
}
|
|
|
|
UsbRegMaskedSet(EHCI_USBSTS, PO_CHG_DET);
|
|
}
|
|
|
|
// host system error
|
|
if (usbsts & H_SYSERR)
|
|
{
|
|
/* serious error here */
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("-- host system error\n");
|
|
#endif
|
|
|
|
UsbRegMaskedSet(EHCI_USBSTS, H_SYSERR);
|
|
}
|
|
|
|
// async schedule advanced
|
|
if (usbsts & INT_OAA)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("-- async schedule advanced\n");
|
|
#endif
|
|
|
|
UsbRegMaskedSet(EHCI_USBSTS, INT_OAA);
|
|
}
|
|
|
|
//NVIC_EnableIRQ(OTG_SBS_3_IRQ);
|
|
}
|
|
|
|
//
|
|
// Functions
|
|
//
|
|
|
|
// done
|
|
static ARM_DRIVER_VERSION usbh_driver_get_version(void)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
return usbh_driver_version;
|
|
}
|
|
|
|
// done
|
|
static ARM_USBH_CAPABILITIES usbh_driver_get_capabilities(void)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
return usbh_driver_capabilities;
|
|
}
|
|
|
|
static int32_t usbh_driver_initialize(ARM_USBH_SignalPortEvent_t cb_port_event, ARM_USBH_SignalPipeEvent_t cb_pipe_event)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
if (hw_initialized)
|
|
{
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
// ask for DDR memory for EHCI data structures
|
|
fotg210_ehci_data_struct_from_ddr();
|
|
|
|
notify_port_event_cb = cb_port_event;
|
|
notify_pipe_event_cb = cb_pipe_event;
|
|
|
|
NVIC_SetVector(OTG_SBS_3_IRQ, (uint32_t)usbh_driver_isr);
|
|
|
|
// Clear and Enable IRQ
|
|
NVIC_ClearPendingIRQ(OTG_SBS_3_IRQ);
|
|
NVIC_EnableIRQ(OTG_SBS_3_IRQ);
|
|
|
|
// set up only host interrupt
|
|
UsbRegWrite(REG_GLB_INT, 0x3);
|
|
|
|
// enable chip
|
|
UsbRegMaskedSet(REG_DEV_CTL, BIT5);
|
|
|
|
// USBCMD - reset host controller
|
|
UsbRegMaskedSet(EHCI_USBCMD, HC_RESET);
|
|
|
|
// USBCMD - polling to wait for the reset complete done
|
|
while ((UsbRegRead(EHCI_USBCMD) & HC_RESET) > 0)
|
|
;
|
|
|
|
// enable EHCI USBINTR
|
|
UsbRegWrite(EHCI_USBINTR, H_SYSERR_EN | PO_CHG_INT_EN | USBERR_INT_EN | USB_INT_EN);
|
|
|
|
// zero Async List Addr before first QH is inserted
|
|
UsbRegWrite(EHCI_ASYNC_LIST_ADDR, 0x0);
|
|
|
|
// init periodic Aist Addr
|
|
UsbRegWrite(EHCI_PERIODIC_LIST_ADDR, (uint32_t)&periodic_frame_list[0]);
|
|
|
|
hw_initialized = true;
|
|
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
static int32_t usbh_driver_uninitialize(void)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
// FIXME: cannot free DDR memory
|
|
|
|
return ARM_DRIVER_ERROR;
|
|
}
|
|
|
|
static int32_t usbh_driver_power_control(ARM_POWER_STATE state)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
switch (state)
|
|
{
|
|
case ARM_POWER_OFF:
|
|
break;
|
|
|
|
case ARM_POWER_LOW:
|
|
break;
|
|
|
|
case ARM_POWER_FULL:
|
|
|
|
// USBCMD - Run HC
|
|
fotg210_ehci_USBCMD_HC_run();
|
|
|
|
break;
|
|
|
|
default:
|
|
return ARM_DRIVER_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
static int32_t usbh_driver_vbus_on_off(uint8_t port, bool vbus)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
if (vbus == true)
|
|
{
|
|
// turn on VBUS
|
|
UsbRegMaskedClr(REG_OTG_CSR, A_BUS_DROP);
|
|
UsbRegMaskedSet(REG_OTG_CSR, A_BUS_REQ);
|
|
}
|
|
else
|
|
{
|
|
// turn off VBUS
|
|
UsbRegMaskedClr(REG_OTG_CSR, A_BUS_REQ);
|
|
UsbRegMaskedSet(REG_OTG_CSR, A_BUS_DROP);
|
|
}
|
|
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
static int32_t usbh_driver_port_reset(uint8_t port)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
//NVIC_DisableIRQ(OTG_SBS_3_IRQ);
|
|
|
|
// stop host controller
|
|
fotg210_ehci_USBCMD_HC_stop();
|
|
|
|
// port disable
|
|
UsbRegMaskedClr(EHCI_PORTSC, PO_EN);
|
|
|
|
// holding port reset for 55 ms
|
|
UsbRegMaskedSet(EHCI_PORTSC, PO_RESET);
|
|
osDelay(55);
|
|
UsbRegMaskedClr(EHCI_PORTSC, PO_RESET);
|
|
|
|
// run host controller again
|
|
fotg210_ehci_USBCMD_HC_run();
|
|
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
static int32_t usbh_driver_port_suspend(uint8_t port)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
// make sure HC is running
|
|
if (UsbRegRead(EHCI_USBSTS) & HCHalted)
|
|
return ARM_DRIVER_ERROR;
|
|
|
|
// make sure port is enabled
|
|
if (!(UsbRegRead(EHCI_PORTSC) & PO_EN))
|
|
return ARM_DRIVER_ERROR;
|
|
|
|
// disable all scheduling works
|
|
fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, false);
|
|
fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, false);
|
|
|
|
fotg210_ehci_USBCMD_HC_stop(); // due to Faraday
|
|
|
|
// suspend port according to EHCI spec
|
|
UsbRegMaskedSet(EHCI_PORTSC, PO_SUSP);
|
|
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
static int32_t usbh_driver_port_resume(uint8_t port)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
// make sure port is suspending
|
|
if (!(UsbRegRead(EHCI_PORTSC) & PO_SUSP))
|
|
return ARM_DRIVER_ERROR;
|
|
|
|
osDelay(10); // due to Faraday
|
|
|
|
// resume port according to EHCI spec
|
|
UsbRegMaskedSet(EHCI_PORTSC, F_PO_RESM);
|
|
|
|
osDelay(20); // due to Faraday
|
|
|
|
UsbRegMaskedClr(EHCI_PORTSC, F_PO_RESM); // due to Faraday ??
|
|
|
|
fotg210_ehci_USBCMD_HC_run(); // due to Faraday
|
|
|
|
// PORTSC - polling to wait for the exit from suspend status
|
|
while ((UsbRegRead(EHCI_PORTSC) & PO_SUSP))
|
|
;
|
|
|
|
fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, true);
|
|
fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, true);
|
|
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
static ARM_USBH_PORT_STATE usbh_driver_port_get_state(uint8_t port)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
ARM_USBH_PORT_STATE port_state;
|
|
|
|
port_state.connected = UsbRegRead(EHCI_PORTSC) & CONN_STS;
|
|
port_state.overcurrent = !!(UsbRegRead(REG_OTG_ISR) & OTG_OVC_RW1C);
|
|
|
|
// refer to the register OTG_CSR in FOTG210 bloack data sheet
|
|
uint32_t speed = (UsbRegRead(REG_OTG_CSR) >> 22) & 0x3;
|
|
switch (speed)
|
|
{
|
|
case 0x00:
|
|
port_state.speed = ARM_USB_SPEED_FULL;
|
|
break;
|
|
case 0x01:
|
|
port_state.speed = ARM_USB_SPEED_LOW;
|
|
break;
|
|
case 0x02:
|
|
port_state.speed = ARM_USB_SPEED_HIGH;
|
|
break;
|
|
}
|
|
|
|
return port_state;
|
|
}
|
|
|
|
#define HORI_LINK_TPYE_iTD 0x0
|
|
#define HORI_LINK_TPYE_QH 0x1
|
|
|
|
// this pipe create is only for Control/Bulk/Interrupt transfer
|
|
// so 'ep_type' can only be ARM_USB_ENDPOINT_CONTROL, ARM_USB_ENDPOINT_BULK or ARM_USB_ENDPOINT_INTERRUPT
|
|
static ARM_USBH_PIPE_HANDLE usbh_driver_pipe_create(uint8_t dev_addr, uint8_t dev_speed, uint8_t hub_addr,
|
|
uint8_t hub_port, uint8_t ep_addr, uint8_t ep_type,
|
|
uint16_t ep_max_packet_size, uint8_t ep_interval)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s() ep_addr 0x%x ep_type 0x%x ep_max_packet %u ep_interval %u\n", __FUNCTION__, ep_addr, ep_type, ep_max_packet_size, ep_interval);
|
|
#endif
|
|
|
|
// insert a new QH into the async scheudle list without Q_TD
|
|
|
|
Queue_Head *qh = 0;
|
|
uint32_t idx;
|
|
|
|
// find a free QH
|
|
for (idx = 0; idx < QH_NUM; idx++)
|
|
{
|
|
qh = (Queue_Head *)QH_addr[idx];
|
|
// check in-use or not
|
|
if (qh->in_use == false)
|
|
{
|
|
// fill up the QH
|
|
memset(qh, 0, QH_REAL_SIZE);
|
|
qh = (Queue_Head *)QH_addr[idx];
|
|
qh->in_use = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef USBH_DRV_ERR
|
|
if (idx >= QH_NUM)
|
|
{
|
|
kmdw_printf("@@ %s() no more QH !!!\n");
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
// DWORD 0
|
|
|
|
// DWORD 1
|
|
qh->device_dword.dev_addr = dev_addr;
|
|
qh->device_dword.endpoint = ep_addr & 0xF;
|
|
qh->device_dword.endpt_speed = dev_speed;
|
|
qh->device_dword.dtc = 1; // ????
|
|
qh->device_dword.H_reclamation = 0; // ????
|
|
qh->device_dword.max_packet_length = ep_max_packet_size;
|
|
|
|
if (ep_addr != 0x0)
|
|
qh->device_dword.dtc = 0; // test
|
|
|
|
// DWORD 2
|
|
qh->mask_dword.hub_addr = hub_addr;
|
|
qh->mask_dword.port_number = hub_port;
|
|
|
|
// Q_TD overlay
|
|
qh->overlay_next_qtd |= 0x1; // T-bit = 1
|
|
qh->overlay_alternate_qtd |= 1; // T-bit = 1
|
|
|
|
// internal use
|
|
qh->all_txfered_bytes = 0;
|
|
qh->endpoint_type = ep_type;
|
|
|
|
if (ep_type == ARM_USB_ENDPOINT_CONTROL || ep_type == ARM_USB_ENDPOINT_BULK)
|
|
{
|
|
|
|
// let's add this QH into the first position
|
|
if (UsbRegRead(EHCI_ASYNC_LIST_ADDR) == 0x0)
|
|
{
|
|
// the very first QH
|
|
qh->next_qh.type = HORI_LINK_TPYE_QH; // QH type
|
|
//qh->next_qh.terminate = 0; // default
|
|
qh->next_qh.ptr_address = ((uint32_t)qh >> 5);
|
|
qh->prev_link_ptr = (uint32_t)qh;
|
|
UsbRegWrite(EHCI_ASYNC_LIST_ADDR, (uint32_t)qh);
|
|
}
|
|
else
|
|
{
|
|
// Insert the new QH into the first position
|
|
Queue_Head *qh_cur = (Queue_Head *)UsbRegRead(EHCI_ASYNC_LIST_ADDR);
|
|
Queue_Head *qh_next = (Queue_Head *)(qh_cur->next_qh.ptr_address << 5);
|
|
|
|
qh->next_qh.ptr_address = ((uint32_t)qh_next >> 5);
|
|
qh->prev_link_ptr = (uint32_t)qh_cur;
|
|
|
|
qh_next->prev_link_ptr = (uint32_t)qh;
|
|
qh_cur->next_qh.ptr_address = ((uint32_t)qh >> 5);
|
|
}
|
|
|
|
// enable schedules only if dev_speed = High Speed (0x2), FIXME
|
|
if (dev_speed == ARM_USB_SPEED_HIGH)
|
|
fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, true);
|
|
}
|
|
else if (ep_type == ARM_USB_ENDPOINT_INTERRUPT) // FIXME: now it supports only one QH
|
|
{
|
|
if (ep_interval < 4)
|
|
ep_interval = 4; // FIXME, we support minimum interval is 1ms
|
|
|
|
uint32_t intv_per_ms = (0x1 << (ep_interval - 4));
|
|
|
|
qh->next_qh.type = HORI_LINK_TPYE_QH; // QH type
|
|
qh->next_qh.terminate = 1; // no other QH
|
|
qh->mask_dword.mult = 0x1;
|
|
qh->mask_dword.S_mask = 0x1; // indicate that trigger at Frindex 000b
|
|
|
|
for (int i = 0; i < FRAME_LIST_SIZE; i += intv_per_ms)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
// check T-bit
|
|
if (periodic_frame_list[i] != 0x1)
|
|
kmdw_printf("warning: this frame list link pointer is occupied!! idx = %u\n", i);
|
|
#endif
|
|
|
|
periodic_frame_list[i] = (uint32_t)qh | (HORI_LINK_TPYE_QH << 1); // setting Frame List Link Pointer T-bit as '0'
|
|
}
|
|
|
|
// enable schedules only if dev_speed = High Speed (0x2), FIXME
|
|
if (dev_speed == ARM_USB_SPEED_HIGH)
|
|
fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, true);
|
|
}
|
|
else // Isochronous transfer
|
|
{
|
|
}
|
|
|
|
return (uint32_t)qh;
|
|
}
|
|
|
|
static int32_t usbh_driver_pipe_modify(ARM_USBH_PIPE_HANDLE pipe_hndl, uint8_t dev_addr,
|
|
uint8_t dev_speed, uint8_t hub_addr, uint8_t hub_port, uint16_t ep_max_packet_size)
|
|
{
|
|
// assume only control transfer do this
|
|
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
Queue_Head *qh = (Queue_Head *)pipe_hndl;
|
|
|
|
// DWORD 0
|
|
qh->next_qh.type = 0x1; // QH type
|
|
|
|
// DWORD 1
|
|
qh->device_dword.dev_addr = dev_addr;
|
|
qh->device_dword.endpt_speed = dev_speed;
|
|
qh->device_dword.max_packet_length = ep_max_packet_size;
|
|
|
|
// DWORD 2
|
|
qh->mask_dword.hub_addr = hub_addr;
|
|
qh->mask_dword.port_number = hub_port;
|
|
|
|
// enable schedules only if dev_speed = High Speed (0x2), FIXME
|
|
if (dev_speed == ARM_USB_SPEED_HIGH)
|
|
{
|
|
fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, true);
|
|
fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, true);
|
|
}
|
|
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
// for data SRAM, the address must be remapped, FIXME!!!!!!
|
|
uint32_t scpu_remap_addr(uint32_t addr)
|
|
{
|
|
uint32_t tmp;
|
|
|
|
if ((addr & (SdRAM_MEM_BASE)) == SdRAM_MEM_BASE)
|
|
{
|
|
tmp = ((addr) & (~0x10000000)) | 0x20000000;
|
|
return tmp;
|
|
}
|
|
return addr;
|
|
}
|
|
|
|
static int32_t usbh_driver_pipe_transfer(ARM_USBH_PIPE_HANDLE pipe_hndl, uint32_t packet, uint8_t *data, uint32_t num)
|
|
{
|
|
|
|
Queue_Head *qh = (Queue_Head *)pipe_hndl;
|
|
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s() pipe_hndl %p packet 0x%x data %p num %u\n", __FUNCTION__, (void *)pipe_hndl, packet, data, num);
|
|
|
|
if (qh->is_txfering)
|
|
{
|
|
kmdw_printf("@@ error: pipe 0x%p is already doing transfer, %x\n", (void *)pipe_hndl, qh->is_txfering);
|
|
return ARM_DRIVER_ERROR;
|
|
}
|
|
#endif
|
|
|
|
Q_TD *qTD = 0;
|
|
uint32_t qtd_idx;
|
|
|
|
// find a free Q_TD
|
|
for (qtd_idx = 0; qtd_idx < qTD_NUM; qtd_idx++)
|
|
{
|
|
qTD = (Q_TD *)qTD_addr[qtd_idx];
|
|
// check in-use or not
|
|
if (qTD->in_use == false)
|
|
{
|
|
memset(qTD, 0, qTD_REAL_SIZE);
|
|
qTD->in_use = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// no more Q_TD available
|
|
if (qtd_idx >= qTD_NUM)
|
|
return ARM_DRIVER_ERROR;
|
|
|
|
qTD->next_qtd.terminate = 1;
|
|
qTD->alt_next_qtd.terminate = 1;
|
|
|
|
switch (packet & ARM_USBH_PACKET_TOKEN_Msk)
|
|
{
|
|
case ARM_USBH_PACKET_OUT:
|
|
qTD->status_dword.PID = 0x0;
|
|
break;
|
|
case ARM_USBH_PACKET_IN:
|
|
qTD->status_dword.PID = 0x1;
|
|
break;
|
|
case ARM_USBH_PACKET_SETUP:
|
|
qTD->status_dword.PID = 0x2;
|
|
break;
|
|
}
|
|
|
|
switch (packet & ARM_USBH_PACKET_DATA_Msk)
|
|
{
|
|
case ARM_USBH_PACKET_DATA0:
|
|
qTD->status_dword.dt = 0;
|
|
break;
|
|
case ARM_USBH_PACKET_DATA1:
|
|
qTD->status_dword.dt = 1;
|
|
break;
|
|
}
|
|
|
|
qTD->status_dword.CERR = 0x0; // FIXME: it 0x3 OK ?
|
|
qTD->status_dword.c_page = 0;
|
|
qTD->status_dword.ioc = 1;
|
|
qTD->status_dword.total_bytes_txfer = num;
|
|
|
|
// internal use, not part of EHCI
|
|
qTD->wanted_txfer_bytes = num;
|
|
|
|
// set up data 4K address
|
|
uint32_t data_addr = scpu_remap_addr((uint32_t)data);
|
|
|
|
// get buf 0 offset
|
|
qTD->buf_ptr_dword[0].cur_offset = data_addr & 0xFFF;
|
|
|
|
// make it as 4K page unit
|
|
data_addr >>= 12;
|
|
|
|
// whatever the txfer size is, we here fill up all buffer pointer fields
|
|
for (int i = 0; i < 5; i++)
|
|
qTD->buf_ptr_dword[i].ptr_address = data_addr + i;
|
|
|
|
// then insert this Q_TD into pipe QH
|
|
|
|
qh->overlay_next_qtd = (uint32_t)(qTD) & (~0x1F);
|
|
qh->overlay_alternate_qtd = (uint32_t)(qTD) & (~0x1F);
|
|
|
|
qh->all_txfered_bytes = 0; // FIXME: clear 0 here is ok ?
|
|
|
|
qh->is_txfering = true; // internal use to mark it is doing transfer
|
|
|
|
// set Activate bit, HC shall begin the transaction very soon
|
|
qTD->status_dword.status = 0x80;
|
|
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
static uint32_t usbh_driver_pipe_transfer_get_result(ARM_USBH_PIPE_HANDLE pipe_hndl)
|
|
{
|
|
Queue_Head *qh = (Queue_Head *)pipe_hndl;
|
|
|
|
uint32_t txfer_bytes = qh->all_txfered_bytes;
|
|
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s() bytes: %d\n", __FUNCTION__, txfer_bytes);
|
|
#endif
|
|
return txfer_bytes;
|
|
}
|
|
|
|
static int32_t usbh_driver_pipe_transfer_abort(ARM_USBH_PIPE_HANDLE pipe_hndl)
|
|
{
|
|
// FIXME: this function implementation is not complete
|
|
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
#ifdef USBH_DRV_FULL
|
|
|
|
Queue_Head *qh = (Queue_Head *)pipe_hndl;
|
|
|
|
if (qh->endpoint_type != ARM_USB_ENDPOINT_ISOCHRONOUS)
|
|
{
|
|
// handle QH and qTD
|
|
if (qh->is_txfering) // then there is a running qTD
|
|
{
|
|
Q_TD *qTD = (Q_TD *)(qh->overlay_next_qtd & (~0x1F));
|
|
|
|
// clear Active bit
|
|
qTD->status_dword.status &= (~0x80);
|
|
|
|
qh->is_txfering = false;
|
|
qTD->in_use = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// handle iTD
|
|
}
|
|
|
|
return ARM_DRIVER_OK;
|
|
#else
|
|
return ARM_DRIVER_ERROR;
|
|
#endif
|
|
}
|
|
|
|
static int32_t usbh_driver_pipe_reset(ARM_USBH_PIPE_HANDLE pipe_hndl)
|
|
{
|
|
// FIXME: do the same as usbh_driver_pipe_transfer_abort() ?
|
|
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
#ifdef USBH_DRV_FULL
|
|
return usbh_driver_pipe_transfer_abort(pipe_hndl);
|
|
#else
|
|
return ARM_DRIVER_ERROR;
|
|
#endif
|
|
}
|
|
|
|
static int32_t usbh_driver_pipe_delete(ARM_USBH_PIPE_HANDLE pipe_hndl)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
#ifdef USBH_DRV_FULL
|
|
|
|
Queue_Head *qh = (Queue_Head *)pipe_hndl;
|
|
|
|
// report error if this pipe is still doing transfers or it is already out of schedule
|
|
// user should invoke usbh_driver_pipe_transfer_abort() before this
|
|
if (qh->is_txfering || !qh->in_use)
|
|
return ARM_DRIVER_ERROR;
|
|
|
|
// first check the pipe (endpoint) is async or periodic
|
|
if (qh->endpoint_type == ARM_USB_ENDPOINT_CONTROL || ARM_USB_ENDPOINT_BULK)
|
|
{
|
|
// async schedule
|
|
|
|
// stop async schedule for safety, FIXME: really need this ?
|
|
fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, false);
|
|
|
|
Queue_Head *qh_next = (Queue_Head *)(qh->next_qh.ptr_address << 5);
|
|
|
|
if (qh_next == qh)
|
|
{
|
|
// in this case only it is the last QH in the scheudle
|
|
// clear the ASYNCLISTADDR register
|
|
UsbRegWrite(EHCI_ASYNC_LIST_ADDR, 0x0);
|
|
}
|
|
else
|
|
{
|
|
// remove it from schedule linked list
|
|
Queue_Head *qh_prev = (Queue_Head *)(qh->prev_link_ptr);
|
|
qh_prev->next_qh.ptr_address = ((uint32_t)qh_next) >> 5;
|
|
qh_next->prev_link_ptr = (uint32_t)qh_prev;
|
|
|
|
if (UsbRegRead(EHCI_ASYNC_LIST_ADDR) == (uint32_t)qh)
|
|
UsbRegWrite(EHCI_ASYNC_LIST_ADDR, (uint32_t)qh_next);
|
|
|
|
// re-enable the async schedule
|
|
fotg210_ehci_USBCMD_schedule_set_enable(ASYNC_SCHEDULE, true);
|
|
}
|
|
|
|
qh->in_use = false;
|
|
}
|
|
else
|
|
{
|
|
// periodic scheudle
|
|
// ARM_USB_ENDPOINT_ISOCHRONOUS or ARM_USB_ENDPOINT_INTERRUPT
|
|
|
|
// stop periodic schedule for safety, FIXME: really need this ?
|
|
fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, false);
|
|
|
|
// FIXME: for now only one endpoint in the periodic schedule is allowed
|
|
// FIXME: below way of handling is not enough
|
|
|
|
// search all existence of this QH from periodic scheudle
|
|
for (int i = 0; i < FRAME_LIST_SIZE; i++)
|
|
periodic_frame_list[i] = 0x1;
|
|
|
|
qh->in_use = false;
|
|
}
|
|
|
|
return ARM_DRIVER_OK;
|
|
#else
|
|
return ARM_DRIVER_ERROR;
|
|
#endif
|
|
}
|
|
|
|
static uint16_t usbh_driver_get_frame_number(void)
|
|
{
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s()\n", __FUNCTION__);
|
|
#endif
|
|
|
|
#ifdef USBH_DRV_FULL
|
|
return (UsbRegRead(EHCI_FRINDEX) & 0x1FFF >> 3);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
// code under this define is a extenion set only for Kneron's own USBH middlware
|
|
#ifdef KNERON_USBH_MDW
|
|
|
|
// use this for Isochronous endpoint
|
|
// 'ep_addr' should also include direction bit
|
|
// 'ep_max_packet_mult' is directly from the 'wMaxPacketSize' of an Isoch endpoint descriptor
|
|
// NOTE: this driver supports only one isoch endpoint
|
|
static ARM_USBH_PIPE_HANDLE usbh_driver_pipe_isoch_create(uint8_t dev_addr, uint8_t ep_addr,
|
|
uint16_t max_packet_size, uint8_t mult, uint8_t ep_interval,
|
|
uint8_t *buf, uint32_t buf_size)
|
|
{
|
|
|
|
// set up first iTD
|
|
I_TD *iTD_0 = (I_TD *)iTD_addr[0];
|
|
|
|
// clear all iTDs
|
|
memset(iTD_0, 0, sizeof(I_TD));
|
|
|
|
iTD_0->next_link.terminate = 1;
|
|
|
|
struct Buffer_Page_0_DWord *dev_dword = (struct Buffer_Page_0_DWord *)&(iTD_0->buf_page_dword[0]);
|
|
dev_dword->dev_addr = dev_addr;
|
|
dev_dword->endpoint = ep_addr & 0xF;
|
|
|
|
struct Buffer_Page_1_DWord *maxpack_dword = (struct Buffer_Page_1_DWord *)&(iTD_0->buf_page_dword[1]);
|
|
maxpack_dword->direction = !!(ep_addr & 0x80);
|
|
maxpack_dword->max_packet_size = max_packet_size; // USB 2.0 spec Table 9-13 and EHCI 1.0 spec Table 3-5
|
|
|
|
struct Buffer_Page_2_DWord *mult_dword = (struct Buffer_Page_2_DWord *)&(iTD_0->buf_page_dword[2]);
|
|
mult_dword->mult = mult; // USB 2.0 spec Table 9-13 and EHCI 1.0 spec Table 3-6
|
|
|
|
// calcualte intervals
|
|
isoch_trans_interval = (1 << (ep_interval - 1)); // SOF interval, USB 2.0 spec Table 9-13
|
|
isoch_iTD_interval = (isoch_trans_interval >> 4) + 1; // calculate interval of iTD, min is 1
|
|
|
|
// calculate needed buffer size for a iTD
|
|
uint8_t num_of_sof;
|
|
switch (isoch_trans_interval)
|
|
{
|
|
case 1:
|
|
num_of_sof = 8;
|
|
isoch_IOC_pos = 7;
|
|
break;
|
|
case 2:
|
|
num_of_sof = 4;
|
|
isoch_IOC_pos = 6;
|
|
break;
|
|
case 4:
|
|
num_of_sof = 2;
|
|
isoch_IOC_pos = 4;
|
|
break;
|
|
default:
|
|
num_of_sof = 1;
|
|
isoch_IOC_pos = 4;
|
|
break;
|
|
}
|
|
|
|
isoch_txfer_len = (max_packet_size * mult); // for example: 1020 x 3
|
|
uint32_t buf_per_iTD = (num_of_sof * isoch_txfer_len);
|
|
|
|
isoch_iTD_num = (buf_size / buf_per_iTD); // now we know how many iTDs needed
|
|
|
|
int i;
|
|
// let's use PG field to indicate if this transaction is needed according to isoch_trans_interval
|
|
for (i = 0; i < 8; i++)
|
|
iTD_0->trans_dword[i].pg = 0x7; // 0x7 to indicate NOT in-use
|
|
|
|
for (i = 0; i < 8; i += isoch_trans_interval)
|
|
{
|
|
iTD_0->trans_dword[i].length = isoch_txfer_len;
|
|
iTD_0->trans_dword[i].pg = 0;
|
|
iTD_0->trans_dword[i].status = 0x8; // pre-activate it
|
|
}
|
|
// only 1 ioc in a iTD
|
|
iTD_0->trans_dword[isoch_IOC_pos].ioc = 1; // set interrupt only for last transaction, interrtup per 1 ms
|
|
|
|
// error checking: isoch_iTD_num must <= iTD_MAX_NUM
|
|
|
|
uint32_t data_addr = (uint32_t)buf;
|
|
|
|
// memory copy to all other iTDs
|
|
for (i = 0; i < isoch_iTD_num; i++)
|
|
{
|
|
I_TD *iTD = (I_TD *)iTD_addr[i];
|
|
memcpy(iTD, iTD_0, sizeof(I_TD));
|
|
|
|
// layout memory
|
|
iTD->trans_dword[0].offset = data_addr & 0xFFF;
|
|
iTD->trans_dword[0].pg = 0;
|
|
iTD->buf_page_dword[0].ptr_address = (data_addr & (~0xFFF)) >> 12;
|
|
|
|
uint8_t cp = 0; // curent page looking at
|
|
|
|
for (int t = 1; t < 8; t += isoch_trans_interval)
|
|
{
|
|
data_addr += isoch_txfer_len;
|
|
|
|
iTD->trans_dword[t].offset = data_addr & 0xFFF;
|
|
|
|
uint32_t buf_ptr = (data_addr & (~0xFFF)) >> 12;
|
|
if (buf_ptr != iTD->buf_page_dword[cp].ptr_address)
|
|
{
|
|
cp++;
|
|
iTD->buf_page_dword[cp].ptr_address = buf_ptr;
|
|
}
|
|
iTD->trans_dword[t].pg = cp;
|
|
}
|
|
|
|
// below is kind of workaround, give a plus page
|
|
if (cp < 6)
|
|
iTD->buf_page_dword[cp + 1].ptr_address = 1 + iTD->buf_page_dword[cp].ptr_address;
|
|
|
|
data_addr += isoch_txfer_len;
|
|
}
|
|
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s() iTD_0 0x%p num %d buf 0x%p\n", __FUNCTION__, iTD_0, isoch_iTD_num, buf);
|
|
#endif
|
|
|
|
return 0x1000F; // just return a value to represent the handle
|
|
}
|
|
|
|
// FIXME, for now it has conflits with interrupt transfer
|
|
static int32_t usbh_driver_pipe_isoch_start(ARM_USBH_PIPE_HANDLE pipe_hndl, ARM_USBH_ISOCH_DATA_CALLBACK isoch_data_cb)
|
|
{
|
|
// pipe_hndl is not used because it supports only one ISOCH endpoint
|
|
|
|
fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, false);
|
|
|
|
isoch_next_process_pos = usbh_driver_get_frame_number() + 50; // insert iTD after 50 ms position
|
|
isoch_next_process_pos &= FRAME_LIST_ROLL_MASK;
|
|
|
|
isoch_next_insert_pos = isoch_next_process_pos;
|
|
|
|
for (int i = 0; i < isoch_iTD_num; i++)
|
|
{
|
|
periodic_frame_list[isoch_next_insert_pos] = (uint32_t)iTD_addr[i]; // FIXME: should also take care of other iTD or QH here
|
|
isoch_next_insert_pos += isoch_iTD_interval;
|
|
isoch_next_insert_pos &= FRAME_LIST_ROLL_MASK;
|
|
}
|
|
|
|
#ifdef USBH_DRV_DBG
|
|
kmdw_printf("@@ %s(),isoch_iTD_num %d isoch_next_process_pos %d isoch_next_insert_pos = %d\n", __FUNCTION__, isoch_iTD_num, isoch_next_process_pos, isoch_next_insert_pos);
|
|
#endif
|
|
|
|
fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, true);
|
|
|
|
isoch_running_flag = 1; // FIXME
|
|
|
|
// at least one of callback should not be NULL
|
|
notify_isoch_data_cb = isoch_data_cb;
|
|
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
static int32_t usbh_driver_pipe_isoch_stop(ARM_USBH_PIPE_HANDLE pipe_hndl)
|
|
{
|
|
// pipe_hndl is not used because it supports only one ISOCH endpoint
|
|
|
|
fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, false);
|
|
|
|
// just clear the frame link list
|
|
for (int i = 0; i < FRAME_LIST_SIZE; i++)
|
|
{
|
|
periodic_frame_list[i] = 0x1; // mark T-bit as 1 indicating invalid pointers
|
|
}
|
|
|
|
isoch_running_flag = 0;
|
|
|
|
// fotg210_ehci_USBCMD_schedule_set_enable(PERIODIC_SCHEDULE, true);
|
|
return ARM_DRIVER_OK;
|
|
}
|
|
|
|
uint32_t usbh_handle_iTD_work(void)
|
|
{
|
|
uint32_t handle_count = 0;
|
|
|
|
// scan some number of iTDs, FIXME: why 100
|
|
for (int32_t r = 0; r < 100; r++)
|
|
{
|
|
|
|
I_TD *iTD = (I_TD *)periodic_frame_list[isoch_next_process_pos];
|
|
|
|
// find the completed iTD
|
|
if ((periodic_frame_list[isoch_next_process_pos] & 0x3) == 0x0 &&
|
|
iTD->trans_dword[isoch_IOC_pos].ioc == 0x1 &&
|
|
iTD->trans_dword[isoch_IOC_pos].status == 0x0)
|
|
{
|
|
periodic_frame_list[isoch_next_process_pos] = 0x1; // set T-bit as 1 to dequeue this iTD
|
|
|
|
for (int t = 0; t < 8; t += isoch_trans_interval)
|
|
{
|
|
#ifdef USBH_DRV_ERR
|
|
if (iTD->trans_dword[t].status != 0)
|
|
kmdw_printf("iTD %d trans %d error status = 0x%x\n", isoch_next_process_pos, t, iTD->trans_dword[t].status);
|
|
#endif
|
|
// callback to uppper layer middleware
|
|
uint8_t pg = iTD->trans_dword[t].pg;
|
|
uint32_t ptr_base = iTD->buf_page_dword[pg].ptr_address;
|
|
uint32_t ptr_offset = iTD->trans_dword[t].offset;
|
|
uint32_t *ptr_buf = (uint32_t *)((ptr_base << 12) | ptr_offset);
|
|
|
|
notify_isoch_data_cb(ptr_buf, iTD->trans_dword[t].length);
|
|
|
|
iTD->trans_dword[t].length = isoch_txfer_len;
|
|
iTD->trans_dword[t].status = 0x8; // pre-activate it
|
|
}
|
|
|
|
// inser iTD in the back, FIXME: there should a better policy
|
|
periodic_frame_list[isoch_next_insert_pos] = (uint32_t)iTD;
|
|
isoch_next_insert_pos += isoch_iTD_interval;
|
|
isoch_next_insert_pos &= FRAME_LIST_ROLL_MASK;
|
|
|
|
handle_count++;
|
|
}
|
|
|
|
isoch_next_process_pos += isoch_iTD_interval;
|
|
isoch_next_process_pos &= FRAME_LIST_ROLL_MASK;
|
|
}
|
|
|
|
return handle_count;
|
|
}
|
|
|
|
// this function is optional, it is for registering bottom-half callback function
|
|
static ARM_USBH_ISOCH_ITD_WORK_FUNC usbh_driver_pipe_isoch_enable_bh(ARM_USBH_PIPE_HANDLE pipe_hndl, ARM_USBH_ISOCH_BH_CALLBACK isoch_bf_callback)
|
|
{
|
|
// pipe_hndl is not used because it supports only one ISOCH endpoint
|
|
|
|
isoch_process_itd_work = isoch_bf_callback;
|
|
return usbh_handle_iTD_work;
|
|
}
|
|
|
|
#endif
|
|
|
|
// Structure exported by driver
|
|
ARM_DRIVER_USBH Driver_USBH0 = {
|
|
usbh_driver_get_version,
|
|
usbh_driver_get_capabilities,
|
|
usbh_driver_initialize,
|
|
usbh_driver_uninitialize,
|
|
usbh_driver_power_control,
|
|
usbh_driver_vbus_on_off,
|
|
usbh_driver_port_reset,
|
|
usbh_driver_port_suspend,
|
|
usbh_driver_port_resume,
|
|
usbh_driver_port_get_state,
|
|
usbh_driver_pipe_create,
|
|
usbh_driver_pipe_modify,
|
|
usbh_driver_pipe_delete,
|
|
usbh_driver_pipe_reset,
|
|
usbh_driver_pipe_transfer,
|
|
usbh_driver_pipe_transfer_get_result,
|
|
usbh_driver_pipe_transfer_abort,
|
|
usbh_driver_get_frame_number,
|
|
#ifdef KNERON_USBH_MDW
|
|
usbh_driver_pipe_isoch_create,
|
|
usbh_driver_pipe_isoch_start,
|
|
usbh_driver_pipe_isoch_stop,
|
|
usbh_driver_pipe_isoch_enable_bh
|
|
#endif
|
|
};
|