/**************************************************************************** ** ** Lemcusb - Firmware USB driver for EFM32 Microcontroller ** Copyright (C) 2014 http://lemcu.org ** ** This library is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License version 3.0 as ** published by the Free Software Foundation and appearing in the file ** LICENSE.txt included in the packaging of this file. ** ** In addition, as a special exception, http://lemcu.org gives you certain ** additional rights. These rights are described in the lemcu.org GPL ** Exception version 1.0, included in the file GPL_EXCEPTION.txt in this ** package. ** ** This library 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. ** ****************************************************************************/ #include "usb.h" //#include "em_device.h" //#include "em_cmu.h" //#include "em_int.h" //#include "em_timer.h" #include "gpio.h" #include "usb_internal_ll.h" #include "usb_stack.h" #include "usb_helperfunctions.h" void usb_reset_received(void) { uint32_t cnt; uint32_t *ptr; GPIOA->PSOR = (1<<1); /* zero intitialize endpoint buffers and len/status fields */ ptr = (uint32_t *)usb_ep_buffers; for (cnt=0; cnt < (sizeof(usb_ep_buffers)>>2); cnt++) { *ptr++ = 0; } /* preinitialize DATA PIDS for IN endpoints */ usb_ep_buffers[USB_EPBUF_OFFSET_EP0IN_BUF] = 0x4B; usb_ep_buffers[USB_EPBUF_OFFSET_EP1IN_BUF] = 0x4B; /* reset usb device address to 0 (will be changed during enumeration) */ usbstack_init(); GPIOA->PSOR = (1<<1); } /* * Pinout information: * PA2 = D- * PA3 = D+ * PA1 = debug output (to scope) */ /* * Pre-condition: GPIO clock enabled * */ void usb_init(void) { usb_reset_received(); /* Pinout: PA1 = USB connect line (1k5 resistor to PC0) PA2 = D- PA3 = D+ */ /* set PA0 low (disconnected) Will enable the pulldown */ GPIOA->PCOR = (1 << 2); /* Pin PA0 is configured to Input enabled with pull-down */ //GPIO->P[0].MODEL = (GPIO->P[0].MODEL & ~_GPIO_P_MODEL_MODE0_MASK) | GPIO_P_MODEL_MODE0_INPUTPULL; /* Pin PC0 is configured to Input enabled */ //GPIO->P[2].MODEL = (GPIO->P[2].MODEL & ~_GPIO_P_MODEL_MODE0_MASK) | GPIO_P_MODEL_MODE0_INPUT; /* Pin PC1 is configured to Input enabled */ // GPIO->P[2].MODEL = (GPIO->P[2].MODEL & ~_GPIO_P_MODEL_MODE1_MASK) | GPIO_P_MODEL_MODE1_INPUT; /* set output state of PC0 PC1 (D- D+) to 1 0 */ GPIOA->PSOR = (1<<2); /* D- = 1 */ GPIOA->PCOR = (1<<3); /* D+ = 0 */ /* Pin PE13 is configured to Push-pull */ //GPIO->P[4].MODEH = (GPIO->P[4].MODEH & ~_GPIO_P_MODEH_MODE13_MASK) | GPIO_P_MODEH_MODE13_PUSHPULL; /* setup PC1 interrupt on rising edge (D+ first SYNC bit) */ /* Configure PC1 interrupt on rising edge */ // GPIO_IntConfig(gpioPortC, 1, true, false, true); /* Set Interrupt priority to highest */ // NVIC_SetPriority(GPIO_ODD_IRQn, 0); /* Enable GPIO_EVEN interrupt vector in NVIC */ // NVIC_EnableIRQ(GPIO_ODD_IRQn); } void usb_connect(void) { GPIOA->PSOR = (1 << 3); //GPIO->P[0].MODEL = (GPIO->P[0].MODEL & ~_GPIO_P_MODEL_MODE0_MASK) | GPIO_P_MODEL_MODE0_PUSHPULL; } void usb_disconnect(void) { /* Pin PA0 is configured to Input enabled with pull-down */ // GPIO->P[0].MODEL = (GPIO->P[0].MODEL & ~_GPIO_P_MODEL_MODE0_MASK) | GPIO_P_MODEL_MODE0_INPUTPULL; /* set PA0 low (disconnected) Will enable the pulldown */ GPIOA->PCOR = (1 << 3); } bool usb_check_resetcondition(void) { return ( (GPIOA->PDIR & 0x00000006) == 0x00 ); } /* check if a setup packet was received */ bool usb_setup_available(void) { return (usb_ep_buffers[USB_EPBUF_OFFSET_SETUP_BUF + USB_EPBUF_SUBOFFSET_STAT] & (1 << 0)) != 0x00; } /* IDEA: The reordering / Bitreversal can be done in place to save some RAM */ void usb_setup_get_data(setupData_t *psetupdata) { uint32_t i, j; uint8_t dat; uint8_t setupdat[8]; volatile uint8_t *SETUP_BUF; SETUP_BUF = &usb_ep_buffers[USB_EPBUF_OFFSET_SETUP_BUF]; for (i=0; i<8; i++) /* walk through all bytes */ { j = i+1; j = (j & 0xfC) | (3-(j & 0x03)); /* reverse byteorder => TODO: This can be done in the .s file using the REV instruction (single cycle) */ dat = SETUP_BUF[j]; dat = bitreverse(dat); /* reverse bits*/ setupdat[i] = dat; /* store data */ } usb_ep_buffers[USB_EPBUF_OFFSET_SETUP_BUF + USB_EPBUF_SUBOFFSET_STAT] &= ~(1 << 0); /* flag setup data as empty */ psetupdata->bmRequestType = setupdat[0]; psetupdata->bRequest = setupdat[1]; psetupdata->wValue = setupdat[2]; psetupdata->wValue |= setupdat[3] << 8; psetupdata->wIndex = setupdat[4]; psetupdata->wIndex |= setupdat[5] << 8; psetupdata->wLength = setupdat[6]; psetupdata->wLength |= setupdat[7] << 8; } bool usb_ep_in_buf_empty(uint32_t epnum) { if (epnum) { return (usb_ep_buffers[USB_EPBUF_OFFSET_EP1IN_BUF + USB_EPBUF_SUBOFFSET_STAT] & (1 << 0)) == 0x00; } else { return (usb_ep_buffers[USB_EPBUF_OFFSET_EP0IN_BUF + USB_EPBUF_SUBOFFSET_STAT] & (1 << 0)) == 0x00; } } /* TODO/IDEA: check parameters like len. This can be done e.g. with ASSERTIONS, so * that checks can be disabled later to save time & space */ void usb_ep_in_commit_pkt(uint32_t epnum, bool firstpkt, const uint8_t *pbuf, uint32_t len) { uint32_t i; uint8_t dat; uint16_t crc_value; volatile uint8_t *EP0IN_BUF; if (!len) { GPIOA->PSOR = (1<<8); GPIOA->PCOR = (1<<8); } if (epnum) { EP0IN_BUF = &usb_ep_buffers[USB_EPBUF_OFFSET_EP1IN_BUF]; } else { EP0IN_BUF = &usb_ep_buffers[USB_EPBUF_OFFSET_EP0IN_BUF]; } if (firstpkt) { EP0IN_BUF[0] = 0x4B; /* first IN packet is always transmitted with DATA1 PID */ } else { EP0IN_BUF[0] ^= 0x88; /* Following packets toggle between DATA0 and DATA1 PID */ } crc_value = 0xffff; for (i=0; i> 8; /* CRC high */ /* set packet length */ EP0IN_BUF[USB_EPBUF_SUBOFFSET_LEN] = len + 1 +2; /* DATAx PID + CRC16 */ /* Flag endpoint as "ready" for transmission. This needs to be done as the last step */ EP0IN_BUF[USB_EPBUF_SUBOFFSET_STAT] |= (1 << 0); } bool usb_ep_out_data_available(uint32_t epnum) { if (epnum == 0x00) { return (usb_ep_buffers[USB_EPBUF_OFFSET_EP0OUT_BUF + USB_EPBUF_SUBOFFSET_STAT] & (1 << 0)) != 0x00; } else { return (usb_ep_buffers[USB_EPBUF_OFFSET_EP1OUT_BUF + USB_EPBUF_SUBOFFSET_STAT] & (1 << 0)) != 0x00; } } uint32_t usb_ep_out_get_data(uint32_t epnum, uint8_t *pbuf) { uint32_t i, j, len; uint8_t dat, bytecount; volatile uint8_t *EP0OUT_BUF; if (epnum == 0) { EP0OUT_BUF = &usb_ep_buffers[USB_EPBUF_OFFSET_EP0OUT_BUF]; } else { EP0OUT_BUF = &usb_ep_buffers[USB_EPBUF_OFFSET_EP1OUT_BUF]; } len = EP0OUT_BUF[USB_EPBUF_SUBOFFSET_LEN]; bytecount = ((len + 7) >> 3) - 1 - 2; /* -PID -CRC16 */ for (i=0; i TODO: This can be done in the .s file using the REV instruction (single cycle) */ dat = EP0OUT_BUF[j]; dat = bitreverse(dat); /* reverse bits*/ *pbuf++ = dat; /* store data */ } //bytecount = 0; if (bytecount > 0) { volatile int i; i=0; } /* flag endpoint as empty. Needs to be done as last step */ EP0OUT_BUF[USB_EPBUF_SUBOFFSET_STAT] &= ~(1 << 0); //usb_ep_buffers[USB_EPBUF_OFFSET_EP0OUT_BUF + USB_EPBUF_SUBOFFSET_STAT] &= ~(1 << 0); return bytecount; } void usb_ep_stall(uint32_t epnum) { usb_ep_buffers[epnum + USB_EPBUF_SUBOFFSET_STAT] |= (1 << 1); } void usb_ep_unstall(uint32_t epnum) { usb_ep_buffers[epnum + USB_EPBUF_SUBOFFSET_STAT] &= ~(1 << 1); } bool usb_ep_is_stalled(uint32_t epnum) { return (usb_ep_buffers[epnum + USB_EPBUF_SUBOFFSET_STAT] & (1 << 1)); } /* send a package via EP0IN which is longer as 7 bytes */ void usb_ep_in_commit_pkt_long(uint32_t epnum, const uint8_t *pbuf, uint8_t len, bool sendshortpkt) { bool first; uint8_t curlen; first = (epnum == 0x00); /* EP0IN packets always start with DATA1 PID, while EP1IN PID toggles between DATA0/1 */ curlen = 0; while (len) { curlen = len; if (curlen > 8) { curlen = 8; } while (!usb_ep_in_buf_empty(epnum)); /* wait until endpoint buffer is free */ usb_ep_in_commit_pkt(epnum, first, pbuf, curlen); len -= curlen; pbuf += curlen; first = false; } if (!sendshortpkt) { return; } if ((curlen == 8) || (curlen == 0)) /* in case the last packet has 8 bytes a short packet needs to be sent to signalize the host, that this was the last packet*/ { while (!usb_ep_in_buf_empty(epnum)); /* wait until endpoint buffer is free */ usb_ep_in_commit_pkt(epnum, false, pbuf, 0); } } /* receive a package via EP0OUT. It is allowed to be longer than 8 bytes */ /* important: ensure, that pbuf has a buffer size with a multiple of 8 Bytes */ /* returns received length */ uint8_t usb_ep_out_get_data_long(uint8_t *pbuf, uint8_t maxlen) { uint8_t curlen, len; curlen = 8; len = 0; do { if (usb_ep_out_data_available(0)) { curlen = usb_ep_out_get_data(0, pbuf); /* TODO: Handle this within usb_stack.c. It is part of control transfers. setup */ pbuf += curlen; if (len >= maxlen) /* at least a little bit of protection against buffer overruns */ { pbuf -= curlen; } len += curlen; } } while ( (curlen == 8) && (len < maxlen) ); /* a short packet indicates the last packet */ return len; } /* Handle a Control transfer without data stage => transmits a ACK in status stage */ void usb_control_acknowledge(void) { uint8_t response[8]; while (!usb_ep_in_buf_empty(0x00)) ; usb_ep_in_commit_pkt(0, true, response, 0); } /* Handle a Control IN transfer */ bool usb_control_dataIn(const uint8_t *pbuf, uint8_t len) { usb_ep_in_commit_pkt_long(0, pbuf, len, false);//true); /* wait for status stage - acknowledge from HOST*/ while ( !usb_ep_out_data_available(0) ) { } usb_ep_buffers[USB_EPBUF_OFFSET_EP0OUT_BUF + USB_EPBUF_SUBOFFSET_STAT] &= ~(1 << 0); /* ignore ACK (OUT packet) */ return true; } /* Handle a Control OUT transfer */ uint8_t usb_control_dataOut(uint8_t *pbuf, uint8_t maxlen) { maxlen = usb_ep_out_get_data_long(pbuf, maxlen); usb_control_acknowledge(); return maxlen; } /* TODO: Combine the functions */