/*************************************************************************** * 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 #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<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);