/*************************************************************************** * sam7fc - USB Device Port with logical Serial Port * * * * 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 "fifo.h" #include "telemetrie.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); struct ep_transfer { uint16_t length; uint16_t curpos; char *data; void (*complete_cb)(void); }; struct ep_ctx { uint16_t maxpktsize; uint16_t flags; union { struct ep_transfer *transfer; struct fifo *fifo; }; }; #define CTX_TRANSFER 0x01 /* ctx use ep_transfer struct */ #define CTX_FIFO 0x02 /* ctx use fifo */ #define CTX_IN 0x04 /* write to the host */ #define CTX_OUT 0x08 /* read from the host */ #define CTX_RXBANK0 0x10 #define CTX_RXBANK1 0x20 static struct ep_transfer ep0_transfer; static struct ep_ctx ep_ctx[4]; static uint16_t current_address; static uint16_t current_config; static uint16_t current_interface; static struct comm_device usb_comm; 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, .iProduct = 0x01, .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, .bInterfaceClass = USB_CLASS_APP_SPEC, .bInterfaceSubClass = 0x01, /* DFU */ .bInterfaceProtocol = 0x01, }, .dfu = { .bLength = sizeof(struct usb_dfu_descriptor), .bDescriptorType = USB_TYPE_DFU, .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_CAN_UPLOAD | USB_DFU_MANIFEST_TOL | USB_DFU_WILL_DETACH, .wDetachTimeOut = 0xff00, .wTransferSize = AT91C_IFLASH_PAGE_SIZE, .bcdDFUVersion = 0x0101, }, }; /* not const! */ static struct usb_cdc_line_coding cdc_line_coding = { .dwDTERate = 9600, .bCharFormat = USB_CDC_1_STOP_BITS, .bParityType = USB_CDC_NO_PARITY, .bDataBits = 8, }; /* not const! */ static struct dfu_status dfu_status = { .bStatus = DFU_STATUS_OK, .bState = DFU_STATE_appIDLE, }; static const struct usb_string_descriptor usb_string0 = { /* String 0 - Language */ .bLength = sizeof(struct usb_string_descriptor) + 1 * sizeof(uint16_t), .bDescriptorType = USB_DT_STRING, .wData = { 0x0409 /* English */ }, }; static const struct usb_string_descriptor usb_string1 = { /* String 1 "sam7fc" */ .bLength = sizeof(struct usb_string_descriptor) + 6 * sizeof(uint16_t), .bDescriptorType = USB_DT_STRING, .wData = { 0x0073, 0x0061, 0x006d, 0x0037, 0x0066, 0x0063, }, }; static const struct usb_string_descriptor *usb_strings[] = { &usb_string0, &usb_string1, }; void ep_transfer_send(uint32_t ep, char *data, uint32_t length, void (*complete_cb)(void)) { struct ep_ctx *ctx = &ep_ctx[ep]; // printf("ep_transfer_send(%ld) size=%ld flags=0x%x\n\r", ep, length, ctx->flags); if (!(ctx->flags & CTX_TRANSFER) || (ctx->flags & (CTX_IN | CTX_OUT))) return; /* from buffer to usb */ ctx->flags |= CTX_IN; struct ep_transfer *transfer = ctx->transfer; transfer->length = length; transfer->curpos = 0; transfer->data = data; transfer->complete_cb = complete_cb; uint32_t maxsize = ctx->maxpktsize; /* get data from transfer */ while (transfer->curpos < transfer->length && maxsize--) AT91C_UDP_FDR[ep] = transfer->data[transfer->curpos++]; /* trigger tx */ AT91C_UDP_CSR[ep] |= AT91C_UDP_TXPKTRDY; } void ep_transfer_receive(uint32_t ep, char *data, uint32_t length, void (*complete_cb)(void)) { struct ep_ctx *ctx = &ep_ctx[ep]; // printf("ep_transfer_receive(%ld) size=%ld flags=0x%x\n\r", ep, length, ctx->flags); if (!(ctx->flags & CTX_TRANSFER) || (ctx->flags & (CTX_IN | CTX_OUT))) return; /* from usb to buffer */ ctx->flags |= CTX_OUT; struct ep_transfer *transfer = ctx->transfer; transfer->length = length; transfer->curpos = 0; transfer->data = data; transfer->complete_cb = complete_cb; } /* stalls the endpoint */ static void ep_send_stall(uint32_t ep) { printf("usb stall\n\r"); AT91C_UDP_CSR[ep] |= AT91C_UDP_FORCESTALL; } static void udp_configure_ep(const struct usb_endpoint_descriptor *desc) { /* get endpoint address, set Max Packet Size */ uint32_t ep = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; ep_ctx[ep].maxpktsize = desc->wMaxPacketSize; /* 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; } else { ep_ctx[ep].flags |= CTX_RXBANK0; } /* configure UDP endpoint and enable interrupt */ AT91C_UDP_CSR[ep] = AT91C_UDP_EPEDS | (eptype << 8); *AT91C_UDP_IER = (1 << ep); } static void udp_print_config(void) { printf("usb: addr=%d cfg=%d if=%d\n\r", current_address, current_config, current_interface); } /* * 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; udp_print_config(); } /* * 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); ep_ctx[1].fifo = usb_comm.rxfifo; ep_ctx[1].flags |= CTX_FIFO; ep_ctx[2].fifo = usb_comm.txfifo; ep_ctx[2].flags |= CTX_FIFO; /* set UDP to "configured" */ *AT91C_UDP_GLBSTATE = AT91C_UDP_CONFG; udp_print_config(); } static void ep_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; ep_transfer_send(0, NULL, 0, udp_txcb_setaddress); break; case USB_REQ_GET_DESCRIPTOR: /* 0x06 */ switch (req->wValue >> 8) { case USB_DT_DEVICE: /* 0x01 */ ep_transfer_send(0, (char *)&dev_descriptor, MIN(sizeof(dev_descriptor), req->wLength), NULL); break; case USB_DT_CONFIG: /* 0x02 */ ep_transfer_send(0, (char *)&cfg_descriptor, MIN(sizeof(cfg_descriptor), req->wLength), NULL); break; case USB_DT_STRING: /* 0x03 */ ; uint8_t index = req->wValue & 0xFF; if (index < ARRAY_SIZE(usb_strings)) { ep_transfer_send(0, (char *)usb_strings[index], MIN(usb_strings[index]->bLength, req->wLength), NULL); } else { ep_send_stall(0); } break; case USB_DT_CS_DEVICE: /* 0x21 */ ep_transfer_send(0, (char *)&cfg_descriptor.dfu, MIN(sizeof(cfg_descriptor.dfu), req->wLength), NULL); break; default: ep_send_stall(0); break; } break; case USB_REQ_SET_CONFIGURATION: /* 0x09 */ current_config = req->wValue; ep_transfer_send(0, NULL, 0, udp_txcb_setconfig); break; default: ep_send_stall(0); break; } break; case (USB_TYPE_STANDARD | USB_RECIP_INTERFACE): /* 0x01/0x81 */ switch (req->bRequest) { case USB_REQ_SET_INTERFACE: /* 0x0b */ current_interface = req->wValue; ep_transfer_send(0, NULL, 0, udp_print_config); break; default: ep_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 */ dfu_status.bStatus = DFU_STATE_appDETACH; ep_transfer_send(0, NULL, 0, NULL); break; case USB_REQ_DFU_GETSTATUS: /* 0x03 */ ep_transfer_send(0, (char *)&dfu_status, sizeof(dfu_status), NULL); break; case USB_CDC_REQ_SET_LINE_CODING: /* 0x20 */ ep_transfer_receive(0, (char *)&cdc_line_coding, sizeof(cdc_line_coding), NULL); break; case USB_CDC_REQ_GET_LINE_CODING: /* 0x21 */ ep_transfer_send(0, (char *)&cdc_line_coding, sizeof(cdc_line_coding), NULL); break; case USB_CDC_REQ_SET_CONTROL_LINE_STATE: /* 0x22 */ ep_transfer_send(0, NULL, 0, NULL); break; default: ep_send_stall(0); break; } break; default: ep_send_stall(0); break; } } static void udp_handle_ep(uint32_t ep) { /* endpoint enabled? */ AT91_REG *csr = &AT91C_UDP_CSR[ep]; if (!(*csr & AT91C_UDP_EPEDS)) return; /* clear STALLSENT interrupt */ if (*csr & AT91C_UDP_STALLSENT) csr_clear_flags(*csr, (AT91C_UDP_STALLSENT | AT91C_UDP_FORCESTALL)); /* 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]; /* ack bank0 *now */ if (*csr & AT91C_UDP_RX_DATA_BK0) csr_clear_flags(*csr, AT91C_UDP_RX_DATA_BK0); /* set data phase transfer direction */ if (req.bRequestType & USB_DIR_IN) *csr |= AT91C_UDP_DIR; /* clear interrupt - *MUST* use csr_clear_flags() here */ csr_clear_flags(*csr, AT91C_UDP_RXSETUP); ep_handle_ctrlrequest(&req); } void (* transfer_cb)(void) = NULL; /* transmit complete? */ if (*csr & AT91C_UDP_TXCOMP) { struct ep_ctx *ctx = &ep_ctx[ep]; if (ctx->flags & CTX_FIFO) { /* get data from fifo */ if (fifo_txudp(ctx->fifo, ep, ctx->maxpktsize)) { AT91C_UDP_CSR[ep] |= AT91C_UDP_TXPKTRDY; } else { ctx->flags &= ~CTX_IN; } } else if ((ctx->flags & (CTX_TRANSFER | CTX_IN)) == (CTX_TRANSFER | CTX_IN)) { /* transfer not complete */ struct ep_transfer *transfer = ctx->transfer; if (transfer->length != transfer->curpos) { uint32_t maxsize = ctx->maxpktsize; /* get data from transfer */ while (transfer->curpos < transfer->length && maxsize--) AT91C_UDP_FDR[ep] = transfer->data[transfer->curpos++]; /* trigger tx */ AT91C_UDP_CSR[ep] |= AT91C_UDP_TXPKTRDY; /* transfer complete, execute callback */ } else { ctx->flags &= ~CTX_IN; transfer_cb = transfer->complete_cb; } } /* clear interrupt */ *csr &= ~(AT91C_UDP_TXCOMP); } /* data ready to read? */ if (*csr & (AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1)) { struct ep_ctx *ctx = &ep_ctx[ep]; uint16_t len = (*csr & AT91C_UDP_RXBYTECNT) >> 16; // TODO: only ep0 status OUT? if (!len && (ctx->flags & CTX_TRANSFER)) { ctx->flags &= ~(CTX_OUT | CTX_IN); ctx->transfer->length = 0; ctx->transfer->curpos = 0; } if (ctx->flags & CTX_FIFO) { fifo_rxudp(ctx->fifo, ep, len); } else if ((ctx->flags & (CTX_TRANSFER | CTX_OUT)) == (CTX_TRANSFER | CTX_OUT)) { /* transfer not complete */ struct ep_transfer *transfer = ctx->transfer; if (transfer->length != transfer->curpos) { /* get data from transfer */ while (transfer->curpos < transfer->length && len--) transfer->data[transfer->curpos++] = AT91C_UDP_FDR[ep]; } /* test again */ if (transfer->length == transfer->curpos) { ctx->flags &= ~CTX_OUT; transfer_cb = transfer->complete_cb; } } if (ctx->flags & CTX_RXBANK0) { if (*csr & AT91C_UDP_RX_DATA_BK0) csr_clear_flags(*csr, AT91C_UDP_RX_DATA_BK0); /* all but ep0 have ping pong buffers */ if (ep > 0) ctx->flags = (ctx->flags & ~CTX_RXBANK0) | CTX_RXBANK1; } else if (ctx->flags & CTX_RXBANK1) { if (*csr & AT91C_UDP_RX_DATA_BK1) csr_clear_flags(*csr, AT91C_UDP_RX_DATA_BK1); ctx->flags = (ctx->flags & ~CTX_RXBANK1) | CTX_RXBANK0; } } if (transfer_cb) transfer_cb(); } static void udp_isr(void) { uint32_t isr = *AT91C_UDP_ISR; if (isr & AT91C_UDP_ENDBUSRES) { 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; /* init ep0 */ struct ep_ctx *ctx = &ep_ctx[0]; ctx->maxpktsize = 8; ctx->flags = CTX_TRANSFER | CTX_RXBANK0; ctx->transfer = &ep0_transfer; ctx->transfer->length = 0; ctx->transfer->curpos = 0; /* 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; if (dfu_status.bStatus == DFU_STATE_appDETACH) { void (* bootloader)(void) = (void *)0x13c000; bootloader(); while (1); } } /* Handle Endpoint Interrupts */ uint32_t i; for (i = 0; i < 4; i++) { if (isr & *AT91C_UDP_IMR & (1<flags & CTX_IN) return; if (fifo_txudp(ctx->fifo, 2, ctx->maxpktsize)) { ctx->flags |= CTX_IN; AT91C_UDP_CSR[2] |= AT91C_UDP_TXPKTRDY; } } 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; /* 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); usb_comm.rxfifo = fifo_alloc(1024); usb_comm.txfifo = fifo_alloc(1024); usb_comm.trigger_tx = trigger_fifo_tx; tdc_register_device(0, &usb_comm); 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_CODR = UDP_PULLUP; else /* usb got diconnected -> disable pullup */ *AT91C_PIOA_SODR = UDP_PULLUP; } PIO_PINCHANGE_ISR(UDP_VBUS_MON, udp_vbus_monitor);