AnsweredAssumed Answered

BF70x USB device driver

Question asked by blueskull on Oct 2, 2017

Hi all, 

 

After a very long battle, I was finally able to get the USB of BF70x to work without uC-OSiii. Here's the code to get it to enumerate. I didn't include and device classes, so it's just a device with a vendor designed interface with a vendor defined endpoint, very basic, but shows how to talk to BF70x USB module.

 

I didn't use ADI driver's buffer mechanism, not did any optimizations. This is just a starting point of a useful driver. I'm hereby providing the code to whoever needs this. It took me a long time to work out how ADI URB level USB driver works, and here's the outcome.

 

To use this code, add USB to your device driver Add-ins, and set sysclk to 100MHz+, here I tested at 200MHz sysclk and 400MHz cpuclk.

 

In your main function, call usb_start(), then repetitively call usb_loop().

 

USB user level driver code starts here:

 

#include <stdio.h>
#include <stdint.h>
#include <drivers/usb/controller/device/adi_usbd_dev.h>

 

#define MIN(a, b) ((a)>=(b)?(b):(a))

 

//TODO: replace ring buffer with a lock-less implementation and get rid of the following macro

#include <adi_osal.h>
#include <services/int/adi_int.h>
#define ADI_USB_ENTER_CR() do { adi_osal_EnterCriticalRegion(); \
adi_int_EnableInt(INTR_USB0_STAT, false); \
adi_int_EnableInt(INTR_USB0_DATA, false); \
adi_osal_ExitCriticalRegion(); \
} while(0)

 

#define ADI_USB_EXIT_CR() do { adi_osal_EnterCriticalRegion(); \
adi_int_EnableInt(INTR_USB0_STAT, true); \
adi_int_EnableInt(INTR_USB0_DATA, true); \
adi_osal_ExitCriticalRegion(); \
} while(0)

 

//Driver API interface
extern const ADI_USB_DRV_API ADI_MUSBMHDRC_DeviceDrvAPI;
ADI_USB_DRV_API *usb_drv=&ADI_MUSBMHDRC_DeviceDrvAPI;
ADI_USB_HANDLE usb_dev;
ADI_USB_URB urb_template={.CurReadIdx=0, .CurUrbCount=0, .CurWriteIdx=0, .DevAddr=0, .DevNum=0,
.EpDir=ADI_USB_EP_DIR_TX, .EpResult=ADI_USB_RESULT_SUCCESS, .EpType=ADI_USB_EP_TYPE_CONTROL,
.EpAddr=0x00, .ProcessedBytes=0, .TransferLen=0, .pData=NULL, .pHostSubmitURB=NULL
};

 

//URB request queue
typedef struct
{
ADI_USB_URB urb[32]; //URB buffer
int used; //Usage level
int wpos; //Current latest position
int rpos; //Current oldest position
}USB_QUEUE;

 

//EP configuration
typedef struct
{
uint8_t enabled;
uint8_t type;
int interval;
int maxsize;
uint8_t opened;
}EP_CONF;

 

//USB request queue
USB_QUEUE usb_queue;

//Enumeration statuses
static uint8_t configured=0;
static uint8_t features=0;
static uint8_t status[2]={0x00, 0x00};
static uint8_t setaddr=0;

 

//Descriptors
//Change 10th~13th bytes for custom PID/VID
static uint8_t desc_dev[]={0x12, 0x01, 0x00, 0x02, 0xff, 0x00, 0x00, 0x40, 0xcd, 0xab, 0x34, 0x12, 0x01, 0x00, 0x01, 0x02, 0x03, 0x01};
static uint8_t desc_cfg[]={0x09, 0x02, 0x19, 0x00, 0x01, 0x01, 0x00, 0x80, 0xfa, 0x09, 0x04, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x07, 0x05, 0x01, 0x02, 0x40, 0x00, 0x00};
static uint8_t desc_str_lang[]={0x04, 0x03, 0x09, 0x04};
//Change to your device strings, first byte being length of entire descriptor, second byte being 0x03, third bytes and after are Unicode 16 string
static uint8_t desc_str_mfg[]={0x12, 0x03, 'W', 0x00, 'h', 0x00, 'a', 0x00, 't', 0x00, 'e', 0x00, 'v', 0x00, 'e', 0x00, 'r', 0x00};
static uint8_t desc_str_dev[]={0x0e, 0x03, 'G', 0x00, 'a', 0x00, 'd', 0x00, 'g', 0x00, 'e', 0x00, 't', 0x00};
static uint8_t desc_str_ser[]={0x12, 0x03, 'U', 0x00, 'S', 0x00, 'B', 0x00, 'D', 0x00, '0', 0x00, '0', 0x00, '0', 0x00, '1', 0x00};

 

//EP configurations
static EP_CONF usb_ep_conf[23];

 

//Adds URB to queue
//TODO: replace current ring buffer implementation with lock-less implementation
//TODO: replace global ring buffer to per-EP buffer
//TODO: add code to allow rx/tx complete interrupt to automatically send next buffer in ADI_USB_URB structure
static int usb_queue_push(ADI_USB_URB *urb)
{
int newpos=(usb_queue.wpos+1)%32;
//URB queue is full if next next is not freed
if((usb_queue.used)&(1<<(newpos%32)))
return -1;
//Push URB to queue
usb_queue.urb[usb_queue.wpos]=*urb;
usb_queue.used|=1<<usb_queue.wpos;
usb_queue.wpos=newpos;
return 0;
}

 

//Flushes URB buffer
static void usb_queue_clear(void)
{
usb_queue.rpos=0;
usb_queue.wpos=0;
usb_queue.used=0;
}

 

//Opens all endpoints
static void usb_ep_open_all(void)
{
int i;
ADI_USB_EP_DESC desc;
for(i=0;i<23;i++)
if(usb_ep_conf[i].enabled)
{
desc.EndpointAddress=i;
desc.Attributes=usb_ep_conf[i].type;
desc.Interval=usb_ep_conf[i].interval;
desc.MaxPacketSize=usb_ep_conf[i].maxsize;
usb_drv->EP_Open(usb_dev, &desc, 0);
usb_ep_conf[i].opened=1;
}
}

 

//Closes all endpoints
static void usb_ep_close_all(void)
{
int i;
for(i=0;i<23;i++)
if(usb_ep_conf[i].opened)
{
usb_ep_conf[i].opened=0;
usb_drv->EP_Close(usb_dev, i);
}
configured=0;
}

 

//Sends a TX ZLP
static void usb_txzlp(uint8_t ep)
{
ADI_USB_URB urb;
urb=urb_template;
urb.EpAddr=ep;
urb.EpDir=ADI_USB_EP_DIR_TX;
usb_queue_push(&urb);
}

 

//Sends a RX ZLP
static void usb_rxzlp(uint8_t ep)
{
ADI_USB_URB urb;
urb=urb_template;
urb.EpAddr=ep;
urb.EpDir=ADI_USB_EP_DIR_RX;
usb_queue_push(&urb);
}

 

//Adds an endpoint to endpoint list
//Call before loop begins!
static void usb_ep_add(uint8_t ep, uint8_t type, int interval, int maxsize)
{
if((ep&~0x80)>11) return;
if(usb_ep_conf[ep].opened)
return;
usb_ep_conf[ep].enabled=1;
usb_ep_conf[ep].type=type;
usb_ep_conf[ep].interval=interval;
usb_ep_conf[ep].maxsize=maxsize;
}

 

//Setup packet received callback
void usb_setup_cb(ADI_USB_URB *urb)
{
//Bitfields
uint8_t typ, req, val[2], idx[2], len[2];
//To be pushed to queue
static ADI_USB_URB out;
out=urb_template; //Default to ZLP
out.EpAddr=0x0;
out.EpDir=ADI_USB_EP_DIR_TX;
//Invalid packet, return NACK
if(urb->TransferLen!=8) {usb_drv->EP_Stall(usb_dev, 0, 1); return;}
//Parse descriptors
typ=urb->pData[0];
req=urb->pData[1];
val[0]=urb->pData[2];
val[1]=urb->pData[3];
idx[0]=urb->pData[4];
idx[1]=urb->pData[5];
len[0]=urb->pData[6];
len[1]=urb->pData[7];
//Parse requests
if(typ==0x00 || typ==0x80) //Device requests
{
if(req==0x00) //Get status
{
out.pData=status;
out.TransferLen=2;
}
else if(req==0x01) //Clear feature
features&=~(1<<val[0]);
else if(req==0x03) //Set feature
features|=(1<<val[0]);
else if(req==0x05) //Set address
{
setaddr=val[0];
usb_rxzlp(0);
return;
}
else if(req==0x06) //Get descriptor
{
if(val[1]==0x01) //Device descriptor
{
out.pData=desc_dev;
out.TransferLen=MIN(len[0]+len[1]<<8, sizeof(desc_dev));
}
else if(val[1]==0x02) //Configuration descriptor
{
out.pData=desc_cfg;
out.TransferLen=MIN(len[0]+len[1]<<8, sizeof(desc_cfg));
}
else if(val[1]==0x03) //String descriptor
{
if(val[0]==0x00) //String language descriptor
{
out.pData=desc_str_lang;
out.TransferLen=MIN(len[0]+len[1]<<8, sizeof(desc_str_lang));
}
else if(val[0]==0x01) //Mfg. string
{
out.pData=desc_str_mfg;
out.TransferLen=MIN(len[0]+len[1]<<8, sizeof(desc_str_mfg));
}
else if(val[0]==0x02) //Device string
{
out.pData=desc_str_dev;
out.TransferLen=MIN(len[0]+len[1]<<8, sizeof(desc_str_dev));
}
else if(val[0]==0x03) //SN string
{
out.pData=desc_str_ser;
out.TransferLen=MIN(len[0]+len[1]<<8, sizeof(desc_str_ser));
}
else //Unsupported strings
{
usb_drv->EP_Stall(usb_dev, 0, 1);
return; //Don't send ZLP
}
}
else //Unsupported descriptors
{
usb_drv->EP_Stall(usb_dev, 0, 1);
return; //Don't send ZLP
}
}
else if(req==0x07) //Set descriptor, invalid
{
usb_drv->EP_Stall(usb_dev, 0, 1);
return; //Don't send ZLP
}
else if(req==0x08) //Get config
{
out.pData=&configured;
out.TransferLen=1;
}
else if(req==0x09) //Set config
{
if(val[1]!=1 || val[0]!=0)
{
usb_drv->EP_Stall(usb_dev, 0, 1);
return;
}
else
configured=val[1];
}
else //Unsupported requests
{
usb_drv->EP_Stall(usb_dev, 0, 1);
return;
}
}
else if(typ==0x01 || typ==0x81) //Interface requests
{
if(req==0x00) //Get status
{
out.pData=status;
out.TransferLen=2;
}
else if(req==0x01 || req==0x03) //Set/clear feature
{
//Do nothing
}
else //Unsupported requests
{
usb_drv->EP_Stall(usb_dev, 0, 1);
return;
}
}
else if(typ==0x02 || typ==0x82) //Endpoint requests
{
if(req==0x00) //Get status
{
out.pData=status; //Halt not supported
out.TransferLen=2;
}
else if(req==0x01) //Ignore clear feature
{
//Do nothing
}
else if(req==0x03) //Set feature
{
if(val[1]==0x00 && val[0]==0x00) //Stall
usb_drv->EP_Stall(usb_dev, 0, 1);
//Ignore other features
}
else //Unsupported requests
{
usb_drv->EP_Stall(usb_dev, 0, 1);
return;
}
}
else //Unsupported request types
{
usb_drv->EP_Stall(usb_dev, 0, 1);
return;
}
//Queue URBs
if(out.TransferLen)
{
usb_queue_push(&out);
if((len[0]+len[1]<<8)>out.TransferLen && out.TransferLen!=64)
usb_rxzlp(0);
}
else
usb_txzlp(0); //Send status phase
}

 

//Data transmitted callback
void usb_tx_cb(ADI_USB_URB *urb, int ep)
{
//printf("EP %d TX complete, len: %d, status: %d.\n", ep, urb->ProcessedBytes, usb_drv->EP_URB_GetState(usb_dev, ep));
}

 

//Data received callback
void usb_rx_cb(ADI_USB_URB *urb, int ep)
{
if(setaddr)
{
usb_drv->SetDevAddr(usb_dev, setaddr);
setaddr=0;
}
//printf("EP %d RX complete, len: %d, status: %d.\n", ep, urb->ProcessedBytes, usb_drv->EP_URB_GetState(usb_dev, ep));
}

 

//Endpoint 0 callback functions
void usb_ep0_cb(ADI_USB_URB *urb, ADI_USB_RESULT res, void *buf)
{
//if(usb_drv->SpdGet(usb_dev)!=ADI_USB_SPD_HIGH) return;
if(res==ADI_USB_RESULT_URB_COMPLETE)
{
if(urb->EpDir==ADI_USB_EP_DIR_SETUP)
usb_setup_cb(urb);
else if(urb->EpDir==ADI_USB_EP_DIR_TX)
usb_tx_cb(urb, urb->EpAddr);
else if(urb->EpDir==ADI_USB_EP_DIR_RX)
usb_rx_cb(urb, urb->EpAddr);
}
}

 

//Endpoint N callback functions
void usb_epd_cb(ADI_USB_URB *urb, ADI_USB_RESULT res, void *buf)
{
//printf("Recv data.\n");
//if(usb_drv->SpdGet(usb_dev)!=ADI_USB_SPD_HIGH) return;
if(res==ADI_USB_RESULT_URB_COMPLETE)
{
if(urb->EpDir==ADI_USB_EP_DIR_TX)
usb_tx_cb(urb, urb->EpAddr);
else if(urb->EpDir==ADI_USB_EP_DIR_RX)
usb_rx_cb(urb, urb->EpAddr);
}
}

 

//USB bus event callback functions
void usb_bus_cb(ADI_USB_HANDLE dev, ADI_USB_BUS_EVENT evt, uint8_t *id)
{
if(evt==ADI_USB_BUS_EVENT_DEV_DISCON)
usb_ep_close_all();
else if(evt==ADI_USB_BUS_EVENT_RESET)
{
usb_ep_close_all();
usb_drv->SetDevAddr(usb_dev, 0);
usb_ep_open_all();
}
}

 

//Initializes and starts USB driver
void usb_start(void)
{
ADI_USB_INIT_DATA init;
init.DevNum=0;
init.DevMode=ADI_USB_MODE_DEVICE;
usb_drv->Init(&usb_dev, &init);
usb_drv->RegBusEventCallback(usb_dev, (ADI_CALLBACK)usb_bus_cb);
usb_drv->RegEpZeroCallback(usb_dev, (ADI_CALLBACK)usb_ep0_cb);
usb_drv->RegEpDataCallback(usb_dev, (ADI_CALLBACK)usb_epd_cb);
usb_ep_add(0, 0, 0, 64);
usb_ep_open_all();
usb_queue_clear();
usb_drv->Start(usb_dev);
}

 

//Returns 1 if USB is configured in high speed mode
int usb_ready(void)
{
if(usb_drv->SpdGet(usb_dev)==ADI_USB_SPD_HIGH && configured==1) return 1;
return 0;
}

 

//USB loop function called by main loop
void usb_loop(void)
{
int lastpos;
ADI_USB_URB *urb;
if(!(usb_queue.used&(1<<usb_queue.rpos)))
return;
//Pop oldest URB
urb=&usb_queue.urb[usb_queue.rpos];
if(!usb_drv->EP_URB_GetState(usb_dev, urb->EpAddr)==ADI_USB_URB_STATE_FREE)
return;
usb_drv->URB_Submit(usb_dev, urb);
//Write off last processed URB
lastpos=usb_queue.rpos-1;
if(lastpos<0) lastpos=31;
ADI_USB_ENTER_CR();
usb_queue.used&=~(1<<lastpos);
ADI_USB_EXIT_CR();
usb_queue.rpos=(usb_queue.rpos+1)%32;
}

 

---------- Enjoy! ----------

Outcomes