sam7fc/src/at91_udp.c

520 lines
14 KiB
C

/***************************************************************************
* Copyright (C) 01/2008 by Olaf Rempel *
* razzor@kopf-tisch.de *
* *
* 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; version 2 of the License *
* *
* 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., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <stdio.h>
#include "AT91SAM7S256.h"
#include "at91_pio.h"
#include "board.h"
#include "usb_ch9.h"
#include "usb_cdc.h"
#include "usb_dfu.h"
#define csr_clear_flags(csr, flags) \
while ((csr) & (flags)) \
(csr) &= ~(flags);
#define csr_set_flags(csr, flags) \
while (((csr) & (flags)) != (flags)) \
(csr) |= (flags);
// TODO: name?
struct udp_transfer {
uint16_t address;
uint16_t maxpkt;
// TODO: last bank used / ping pong
// TODO: direction (IN/OUT)
uint16_t length;
uint16_t curpos;
char *data;
void (*complete_cb)(void);
// TODO: privdata?
};
static uint16_t current_address;
static uint16_t current_config;
static uint16_t current_interface;
static struct udp_transfer ep_transfer[4];
static const struct usb_device_descriptor dev_descriptor = {
.bLength = sizeof(struct usb_device_descriptor),
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = 0x0110,
.bMaxPacketSize0 = 8,
.idVendor = USB_VENDOR_ID,
.idProduct = USB_PRODUCT_ID +1,
.bcdDevice = 0x0001,
.bNumConfigurations = 1,
};
struct my_config {
struct usb_config_descriptor cfg;
struct usb_interface_descriptor ctrl_iface;
struct usb_cdc_header_desc cdc_header;
struct usb_cdc_call_mgmt_descriptor cdc_call_mgmt;
struct usb_cdc_acm_descriptor cdc_acm;
struct usb_cdc_union_desc cdc_union;
struct usb_endpoint_descriptor notify_ep;
struct usb_interface_descriptor data_iface;
struct usb_endpoint_descriptor dataout_ep;
struct usb_endpoint_descriptor datain_ep;
struct usb_interface_descriptor dfu_iface;
struct usb_dfu_descriptor dfu;
} __attribute__ ((packed));
static const struct my_config cfg_descriptor = {
.cfg = {
.bLength = sizeof(struct usb_config_descriptor),
.bDescriptorType = USB_DT_CONFIG,
.wTotalLength = sizeof(struct my_config),
.bNumInterfaces = 3,
.bConfigurationValue = 1,
.bmAttributes = USB_CONFIG_ATT_SELFPOWER | USB_CONFIG_ATT_WAKEUP,
.bMaxPower = 50,
},
.ctrl_iface = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_COMM,
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
.bInterfaceProtocol = 1,
},
.cdc_header = {
.bLength = sizeof(struct usb_cdc_header_desc),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
.bcdCDC = 0x0110,
},
.cdc_call_mgmt = {
.bLength = sizeof(struct usb_cdc_call_mgmt_descriptor),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE,
.bmCapabilities = USB_CDC_CALL_MGMT_CAP_CALL_MGMT,
.bDataInterface = 1,
},
.cdc_acm = {
.bLength = sizeof(struct usb_cdc_acm_descriptor),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_CDC_ACM_TYPE,
.bmCapabilities = (USB_CDC_CAP_BRK | USB_CDC_CAP_LINE | USB_CDC_COMM_FEATURE),
},
.cdc_union = {
.bLength = sizeof(struct usb_cdc_union_desc),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_CDC_UNION_TYPE,
.bMasterInterface0 = 0,
.bSlaveInterface0 = 1,
},
.notify_ep = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN | 0x03,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = 64,
.bInterval = 10,
},
.data_iface = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 1,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_CDC_DATA,
},
.dataout_ep = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT | 0x01,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = 64,
},
.datain_ep = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN | 0x02,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = 64,
},
.dfu_iface = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 2,
.bNumEndpoints = 0,
.bInterfaceClass = USB_CLASS_APP_SPEC,
.bInterfaceSubClass = 0x01, /* DFU */
},
.dfu = {
.bLength = sizeof(struct usb_dfu_descriptor),
.bDescriptorType = USB_TYPE_DFU,
.bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_CAN_UPLOAD,
.wDetachTimeOut = 0xff00,
.wTransferSize = AT91C_IFLASH_PAGE_SIZE,
.bcdDFUVersion = 0x0100,
},
};
static struct dfu_status dfu_status = {
.bStatus = DFU_STATUS_OK,
.bwPollTimeout = {0x00, 0x04, 0x00},
.bState = DFU_STATE_appIDLE,
};
static void udp_configure_ep(const struct usb_endpoint_descriptor *desc)
{
/* get endpoint type (ctrl, iso, bulb, int) */
uint32_t eptype = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
if (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
eptype |= 0x04;
/* get endpoint address, set Max Packet Size */
uint32_t address = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
ep_transfer[address].maxpkt = desc->wMaxPacketSize;
/* configure UDP endpoint and enable interrupt */
AT91C_UDP_CSR[address] = AT91C_UDP_EPEDS | (eptype << 8);
*AT91C_UDP_IER = (1 << address);
}
static void udp_fill_fifo(struct udp_transfer *trans)
{
uint16_t ep = trans->address;
if (AT91C_UDP_CSR[ep] & AT91C_UDP_TXPKTRDY) {
printf("TX!RDY\n\r");
return;
}
/* fill fifo */
uint16_t max = trans->maxpkt;
while (trans->curpos < trans->length && max--)
AT91C_UDP_FDR[ep] = trans->data[trans->curpos++];
/* trigger transmission */
AT91C_UDP_CSR[ep] |= AT91C_UDP_TXPKTRDY;
}
static void udp_send_data(struct udp_transfer *trans, const char *data,
uint32_t length, void (*complete_cb)(void))
{
if (trans->length != trans->curpos) {
printf("TXOVF\n\r");
return;
}
/* setup data transmission */
trans->length = length;
trans->curpos = 0;
trans->data = (char *)data;
trans->complete_cb = complete_cb;
udp_fill_fifo(trans);
}
/* stalls the endpoint */
static void udp_send_stall(uint32_t ep)
{
printf("stall\n\r");
AT91C_UDP_CSR[ep] |= AT91C_UDP_FORCESTALL;
}
/*
* set local address
* (USB_REQ_SET_ADDRESS callback)
*/
static void udp_txcb_setaddress(void)
{
*AT91C_UDP_FADDR = (AT91C_UDP_FEN | current_address);
*AT91C_UDP_GLBSTATE = AT91C_UDP_FADDEN;
}
/*
* configure endpoints
* (USB_REQ_SET_CONFIGURATION callback)
*/
static void udp_txcb_setconfig(void)
{
udp_configure_ep(&cfg_descriptor.notify_ep);
udp_configure_ep(&cfg_descriptor.datain_ep);
udp_configure_ep(&cfg_descriptor.dataout_ep);
/* set UDP to "configured" */
*AT91C_UDP_GLBSTATE = AT91C_UDP_CONFG;
}
static void udp_txcb_setinterface(void)
{
printf("claim interface %d\n\r", current_interface);
}
static void udp_handle_ctrlrequest(struct usb_ctrlrequest *req)
{
printf("typ:0x%02x req:0x%02x val:0x%04x idx:0x%04x len:0x%04x\n\r",
req->bRequestType, req->bRequest, req->wValue, req->wIndex, req->wLength);
switch (req->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK)) {
case (USB_TYPE_STANDARD | USB_RECIP_DEVICE): /* 0x00/0x80 */
switch (req->bRequest) {
case USB_REQ_SET_ADDRESS: /* 0x05 */
current_address = req->wValue;
udp_send_data(&ep_transfer[0], NULL, 0, udp_txcb_setaddress);
break;
case USB_REQ_GET_DESCRIPTOR: /* 0x06 */
switch (req->wValue >> 8) {
case USB_DT_DEVICE: /* 0x01 */
udp_send_data(&ep_transfer[0], (const char *)&dev_descriptor,
MIN(sizeof(dev_descriptor), req->wLength), NULL);
break;
case USB_DT_CONFIG: /* 0x02 */
udp_send_data(&ep_transfer[0], (const char *)&cfg_descriptor,
MIN(sizeof(cfg_descriptor), req->wLength), NULL);
break;
default:
udp_send_stall(0);
break;
}
break;
case USB_REQ_SET_CONFIGURATION: /* 0x09 */
current_config = req->wValue;
udp_send_data(&ep_transfer[0], NULL, 0, udp_txcb_setconfig);
break;
default:
udp_send_stall(0);
break;
}
break;
case (USB_TYPE_STANDARD | USB_RECIP_INTERFACE): /* 0x01/0x81 */
// TODO: follow current_interface
switch (req->bRequest) {
case USB_REQ_SET_INTERFACE: /* 0x0b */
current_interface = req->wValue;
udp_send_data(&ep_transfer[0], NULL, 0, udp_txcb_setinterface);
break;
default:
udp_send_stall(0);
break;
}
break;
case (USB_TYPE_CLASS | USB_RECIP_INTERFACE): /* 0x21/0xA1 */
// TODO: follow current_interface
switch (req->bRequest) {
case USB_REQ_DFU_DETACH: /* 0x00 */
udp_send_data(&ep_transfer[0], NULL, 0, NULL);
break;
case USB_REQ_DFU_GETSTATUS: /* 0x03 */
udp_send_data(&ep_transfer[0], (const char *)&dfu_status,
MIN(sizeof(struct dfu_status), req->wLength), NULL);
break;
case USB_CDC_REQ_SET_LINE_CODING: /* 0x20 */
// TODO: read 7 data bytes
break;
case USB_CDC_REQ_SET_CONTROL_LINE_STATE: /* 0x22 */
udp_send_data(&ep_transfer[0], NULL, 0, NULL);
break;
default:
udp_send_stall(0);
break;
}
break;
default:
udp_send_stall(0);
break;
}
}
static void udp_handle_ep(uint32_t ep)
{
AT91_REG *csr = &AT91C_UDP_CSR[ep];
/* endpoint enabled? */
if (!(*csr & AT91C_UDP_EPEDS))
return;
/* ctrl request packet? */
if (*csr & AT91C_UDP_RXSETUP) {
struct usb_ctrlrequest req;
uint8_t *p;
for (p = (uint8_t *)&req; p < (uint8_t *)(&req +1); p++)
*p = AT91C_UDP_FDR[ep];
/* set data phase transfer direction */
if (req.bRequestType & USB_DIR_IN)
*csr |= AT91C_UDP_DIR;
/* clear interrupt (THIS MUST USE csr_clear_flags()!) */
csr_clear_flags(*csr, AT91C_UDP_RXSETUP);
udp_handle_ctrlrequest(&req);
}
/* transmit complete? */
if (*csr & AT91C_UDP_TXCOMP) {
struct udp_transfer *trans = &ep_transfer[ep];
/* refill fifo, if transfer is incomplete */
if (trans->length != trans->curpos)
udp_fill_fifo(trans);
/* execute callback when transfer is complete */
else if (trans->complete_cb)
trans->complete_cb();
/* clear interrupt */
*csr &= ~(AT91C_UDP_TXCOMP);
}
/* clear STALLSENT interrupt */
if (*csr & AT91C_UDP_STALLSENT)
csr_clear_flags(*csr, (AT91C_UDP_STALLSENT | AT91C_UDP_FORCESTALL));
/* data ready to read? */
if (*csr & (AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1)) {
uint16_t len = (*csr & AT91C_UDP_RXBYTECNT) >> 16;
if (len) {
printf("rx: ");
while (len--)
printf("0x%02x ", AT91C_UDP_FDR[ep]);
printf("\n\r");
} else {
/* clear a pending transfer! */
ep_transfer[ep].length = 0;
ep_transfer[ep].curpos = 0;
}
/* TODO: ping pong FIFOs */
if (*csr & AT91C_UDP_RX_DATA_BK0)
csr_clear_flags(*csr, AT91C_UDP_RX_DATA_BK0);
if (*csr & AT91C_UDP_RX_DATA_BK1)
csr_clear_flags(*csr, AT91C_UDP_RX_DATA_BK1);
}
}
static void udp_isr(void)
{
uint32_t isr = *AT91C_UDP_ISR;
if (isr & AT91C_UDP_ENDBUSRES) {
printf("usb reset\n\r");
AT91S_UDP *udp = AT91C_BASE_UDP;
/* reset all endpoints */
udp->UDP_RSTEP = (AT91C_UDP_EP0 | AT91C_UDP_EP1 |
AT91C_UDP_EP2 | AT91C_UDP_EP3) ;
udp->UDP_RSTEP = 0;
/* clear all transfers, set default values */
uint32_t i;
for (i = 0; i < 4; i++) {
ep_transfer[i].address = i;
ep_transfer[i].maxpkt = 64;
ep_transfer[i].length = 0;
ep_transfer[i].curpos = 0;
}
ep_transfer[0].maxpkt = 8;
/* Configure endpoint0 as Control EP */
udp->UDP_CSR[0] = (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_CTRL);
/* enable ep0 Interrupt, disable all others */
udp->UDP_IER = AT91C_UDP_EPINT0;
udp->UDP_IDR = AT91C_UDP_EPINT1 | AT91C_UDP_EPINT2 | AT91C_UDP_EPINT3 |
AT91C_UDP_RXSUSP | AT91C_UDP_RXRSM | AT91C_UDP_SOFINT |
AT91C_UDP_WAKEUP;
}
/* Handle Endpoint Interrupts */
uint32_t i;
for (i = 0; i < 4; i++) {
if (isr & (1<<i))
udp_handle_ep(i);
}
/* clear all unhandled interrupts */
*AT91C_UDP_ICR = isr & (AT91C_UDP_RXSUSP | AT91C_UDP_RXRSM |
AT91C_UDP_ENDBUSRES | AT91C_UDP_WAKEUP);
}
void at91_udp_init(void)
{
/* configure & disable Pullup, disable Pullup von VBUS */
AT91PS_PIO pio = AT91C_BASE_PIOA;
pio->PIO_CODR = UDP_PULLUP;
pio->PIO_PER = UDP_PULLUP;
pio->PIO_OER = UDP_PULLUP;
// TODO: needed?
pio->PIO_PPUDR = UDP_VBUS_MON;
/* UDPCK (48MHz) = PLLCK / 2 */
*AT91C_CKGR_PLLR |= AT91C_CKGR_USBDIV_1;
/* enable UDP clocks */
*AT91C_PMC_SCER = AT91C_PMC_UDP;
*AT91C_PMC_PCER = (1 << AT91C_ID_UDP);
/* enable transmitter */
*AT91C_UDP_TXVC &= ~AT91C_UDP_TXVDIS;
/* clear & disable all UDP interrupts */
*AT91C_UDP_IDR = AT91C_UDP_EPINT0 | AT91C_UDP_EPINT1 | AT91C_UDP_EPINT2 |
AT91C_UDP_EPINT3 | AT91C_UDP_RXSUSP | AT91C_UDP_RXRSM |
AT91C_UDP_SOFINT | AT91C_UDP_WAKEUP;
*AT91C_UDP_ICR = AT91C_UDP_RXSUSP | AT91C_UDP_RXRSM | AT91C_UDP_SOFINT |
AT91C_UDP_ENDBUSRES | AT91C_UDP_WAKEUP ;
/* level triggered, own vector */
AT91S_AIC *aic = AT91C_BASE_AIC;
aic->AIC_SMR[AT91C_ID_UDP] = IRQPRIO_UDP | AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL;
aic->AIC_SVR[AT91C_ID_UDP] = (uint32_t)udp_isr;
aic->AIC_IECR = (1 << AT91C_ID_UDP);
pio_trigger_isr(UDP_VBUS_MON);
}
static void udp_vbus_monitor(uint32_t status, uint32_t input)
{
if (input & UDP_VBUS_MON)
/* usb connected -> enable pullup */
*AT91C_PIOA_SODR = UDP_PULLUP;
else
/* usb got diconnected -> disable pullup */
*AT91C_PIOA_CODR = UDP_PULLUP;
}
PIO_PINCHANGE_ISR(UDP_VBUS_MON, udp_vbus_monitor);