diff options
author | Rob Lippert <roblip@gmail.com> | 2016-05-15 12:15:09 +0000 |
---|---|---|
committer | Fabio Utzig <utzig@utzig.org> | 2016-05-15 12:15:09 +0000 |
commit | 06f9534388d942b2e94ae8b7deda6f60e2914fb1 (patch) | |
tree | c4332ff3c9c4ebb29cd1673155a1408c2f3bfdf9 /os | |
parent | 5928999fd50a6c2daa12eb7f2c183beffa2c0341 (diff) | |
download | ChibiOS-06f9534388d942b2e94ae8b7deda6f60e2914fb1.tar.gz ChibiOS-06f9534388d942b2e94ae8b7deda6f60e2914fb1.tar.bz2 ChibiOS-06f9534388d942b2e94ae8b7deda6f60e2914fb1.zip |
Adds support for USB device functionality for AT90USB and
ATU2/U4 series devices.
Support tested on a PJRC TEENSY2++ board with AT90USB1286.
Signed-off-by: Rob Lippert <roblip@gmail.com>
git-svn-id: svn://svn.code.sf.net/p/chibios/svn/trunk@9487 35acf78f-673a-0410-8e92-d51de3d6d3f4
Diffstat (limited to 'os')
-rw-r--r-- | os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.c | 75 | ||||
-rw-r--r-- | os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.h | 86 | ||||
-rw-r--r-- | os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.mk | 5 | ||||
-rw-r--r-- | os/hal/ports/AVR/hal_usb_lld.c | 841 | ||||
-rw-r--r-- | os/hal/ports/AVR/hal_usb_lld.h | 398 | ||||
-rw-r--r-- | os/hal/ports/AVR/platform.mk | 1 |
6 files changed, 1406 insertions, 0 deletions
diff --git a/os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.c b/os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.c new file mode 100644 index 000000000..a16f25305 --- /dev/null +++ b/os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.c @@ -0,0 +1,75 @@ +/* + Copyright (C) 2015 Robert Lippert + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "hal.h" + +/** + * @brief PAL setup. + * @details Digital I/O ports static configuration as defined in @p board.h. + * This variable is used by the HAL when initializing the PAL driver. + */ +#if HAL_USE_PAL || defined(__DOXYGEN__) +const PALConfig pal_default_config = +{ +#if defined(PORTA) + {VAL_PORTA, VAL_DDRA}, +#endif +#if defined(PORTB) + {VAL_PORTB, VAL_DDRB}, +#endif +#if defined(PORTC) + {VAL_PORTC, VAL_DDRC}, +#endif +#if defined(PORTD) + {VAL_PORTD, VAL_DDRD}, +#endif +#if defined(PORTE) + {VAL_PORTE, VAL_DDRE}, +#endif +#if defined(PORTF) + {VAL_PORTF, VAL_DDRF}, +#endif +#if defined(PORTG) + {VAL_PORTG, VAL_DDRG}, +#endif +#if defined(PORTH) + {VAL_PORTH, VAL_DDRH}, +#endif +#if defined(PORTJ) + {VAL_PORTJ, VAL_DDRJ}, +#endif +#if defined(PORTK) + {VAL_PORTK, VAL_DDRK}, +#endif +#if defined(PORTL) + {VAL_PORTL, VAL_DDRL}, +#endif +}; +#endif /* HAL_USE_PAL */ + +/** + * Board-specific initialization code. + */ +void boardInit(void) { + + /* + * External interrupts setup, all disabled initially. + */ + EICRA = 0x00; + EICRB = 0x00; + EIMSK = 0x00; + +} diff --git a/os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.h b/os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.h new file mode 100644 index 000000000..7b20330ee --- /dev/null +++ b/os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.h @@ -0,0 +1,86 @@ +/* + Copyright (C) 2015 Robert Lippert + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef BOARD_H_ +#define BOARD_H_ + +/* + * Setup for the PJRC Teensy2++ board. + */ + +/* + * Board identifier. + */ +#define BOARD_TEENSY_2PLUSPLUS +#define BOARD_NAME "PJRC Teensy 2++" + +/* All inputs with pull-ups */ +#define VAL_DDRA 0x00 +#define VAL_PORTA 0xFF + +/* All inputs with pull-ups */ +#define VAL_DDRB 0x00 +#define VAL_PORTB 0xFF + +/* All inputs with pull-ups */ +#define VAL_DDRC 0x00 +#define VAL_PORTC 0xFF + +/* All inputs with pull-ups, LED on D6, serial TX1 on D3 */ +#define VAL_DDRD 0x48 +#define VAL_PORTD 0xFF + +/* All inputs with pull-ups */ +#define VAL_DDRE 0x00 +#define VAL_PORTE 0xFF + +/* All inputs with pull-ups */ +#define VAL_DDRF 0x00 +#define VAL_PORTF 0xFF + +/* All inputs with pull-ups */ +#define VAL_DDRG 0x00 +#define VAL_PORTG 0xFF + +/* All inputs with pull-ups */ +#define VAL_DDRH 0x00 +#define VAL_PORTH 0xFF + +/* All inputs with pull-ups */ +#define VAL_DDRJ 0x00 +#define VAL_PORTJ 0xFF + +/* All inputs with pull-ups */ +#define VAL_DDRK 0x00 +#define VAL_PORTK 0xFF + +/* All inputs with pull-ups */ +#define VAL_DDRL 0x00 +#define VAL_PORTL 0xFF + +#define BOARD_LED1 6 + +#if !defined(_FROM_ASM_) +#ifdef __cplusplus +extern "C" { +#endif + void boardInit(void); +#ifdef __cplusplus +} +#endif +#endif /* _FROM_ASM_ */ + +#endif /* BOARD_H_ */ diff --git a/os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.mk b/os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.mk new file mode 100644 index 000000000..244324067 --- /dev/null +++ b/os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.mk @@ -0,0 +1,5 @@ +# List of all the board related files. +BOARDSRC = ${CHIBIOS}/os/hal/boards/PJRC_TEENSY_2PLUSPLUS/board.c + +# Required include directories +BOARDINC = ${CHIBIOS}/os/hal/boards/PJRC_TEENSY_2PLUSPLUS diff --git a/os/hal/ports/AVR/hal_usb_lld.c b/os/hal/ports/AVR/hal_usb_lld.c new file mode 100644 index 000000000..e6fb42f1e --- /dev/null +++ b/os/hal/ports/AVR/hal_usb_lld.c @@ -0,0 +1,841 @@ +/* + Copyright (C) 2015 Robert Lippert + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file usb_lld.c + * @brief AVR USB subsystem low level driver source. + * + * @addtogroup USB + * @{ + */ + +#include "hal.h" + +#if (HAL_USE_USB == TRUE) || defined(__DOXYGEN__) + +#ifndef F_USB +#define F_USB F_CPU +#endif + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/** + * @brief USB1 driver identifier. + */ +#if (AVR_USB_USE_USB1 == TRUE) || defined(__DOXYGEN__) +USBDriver USBD1; +#endif + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +/** + * @brief EP0 state. + * @note It is an union because IN and OUT endpoints are never used at the + * same time for EP0. + */ +static union { + /** + * @brief IN EP0 state. + */ + USBInEndpointState in; + /** + * @brief OUT EP0 state. + */ + USBOutEndpointState out; +} ep0_state; + +/** + * @brief EP0 initialization structure. + */ +static const USBEndpointConfig ep0config = { + USB_EP_MODE_TYPE_CTRL, + _usb_ep0setup, + _usb_ep0in, + _usb_ep0out, + 0x40, + 0x40, + &ep0_state.in, + &ep0_state.out +}; + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +#ifdef AVR_USB_PLL_OFF_IN_SUSPEND +static __attribute__((unused)) void usb_pll_off(void) { + PLLCSR = 0; +} +#endif + +static void usb_pll_on(void) { +#if (F_USB == 8000000) + #if (defined(__AVR_AT90USB82__) || defined(__AVR_AT90USB162__) || \ + defined(__AVR_ATmega8U2__) || defined(__AVR_ATmega16U2__) || \ + defined(__AVR_ATmega32U2__)) + #define PLL_VAL 0 + #elif (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) + #define PLL_VAL 0 + #elif (defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)) + #define PLL_VAL ((0 << PLLP2) | (1 << PLLP1) | (1 << PLLP0)) + #elif (defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1287__)) + #define PLL_VAL ((0 << PLLP2) | (1 << PLLP1) | (1 << PLLP0)) + #endif +#elif (F_USB == 16000000) + #if (defined(__AVR_AT90USB82__) || defined(__AVR_AT90USB162__) || \ + defined(__AVR_ATmega8U2__) || defined(__AVR_ATmega16U2__) || \ + defined(__AVR_ATmega32U2__)) + #define PLL_VAL ((0 << PLLP2) | (0 << PLLP1) | (1 << PLLP0)) + #elif (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) + #define PLL_VAL (1 << PINDIV) + #elif (defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__)) + #define PLL_VAL ((1 << PLLP2) | (1 << PLLP1) | (0 << PLLP0)) + #elif (defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__)) + #define PLL_VAL ((1 << PLLP2) | (0 << PLLP1) | (1 << PLLP0)) + #endif +#endif + +#ifndef PLL_VAL +#error Could not determine PLL value, unsupported AVR USB model type +#endif + +#ifdef PLLFRQ + /* This initializes PLL on supported devices for USB 48MHz *only* */ + PLLFRQ = (0 << PDIV3) | (1 << PDIV2) | (0 << PDIV1) | (0 << PDIV0); +#endif + + PLLCSR = PLL_VAL; + PLLCSR = PLL_VAL | (1 << PLLE); +} + +static int usb_pll_is_locked(void) { + return !!(PLLCSR & (1 << PLOCK)); +} + +/*===========================================================================*/ +/* Driver interrupt handlers and threads. */ +/*===========================================================================*/ + +/** + * @brief USB general/OTG/device management event interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(USB_GEN_vect) { + uint8_t usbint, udint; + USBDriver * const usbp = &USBD1; + + OSAL_IRQ_PROLOGUE(); + + usbint = USBINT; + udint = UDINT; + + if (usbint & (1 << VBUSTI)) { + /* Connected. */ +#ifdef AVR_USB_PLL_OFF_IN_SUSPEND + usb_pll_on(); + while (!usb_pll_is_locked()) {} +#endif /* AVR_USB_PLL_OFF_IN_SUSPEND */ + + /* Attach to bus */ + usb_lld_connect_bus(usbp); + USBINT &= ~(1 << VBUSTI); + } + + /* USB bus SUSPEND condition handling.*/ + if (udint & (1 << SUSPI)) { + /* Disable suspend interrupt, enable WAKEUP interrupt */ + UDIEN |= (1 << WAKEUPE); + UDINT &= ~(1 << WAKEUPI); + UDIEN &= ~(1 << SUSPE); + + /* Freeze the clock to reduce power consumption */ + USBCON |= (1 << FRZCLK); +#ifdef AVR_USB_PLL_OFF_IN_SUSPEND + usb_pll_off(); +#endif /* AVR_USB_PLL_OFF_IN_SUSPEND */ + + /* Clear the interrupt */ + UDINT &= ~(1 << SUSPI); + + _usb_isr_invoke_event_cb(usbp, USB_EVENT_SUSPEND); + } + + /* USB bus WAKEUP condition handling.*/ + if (udint & (1 << WAKEUPI)) { +#ifdef AVR_USB_PLL_OFF_IN_SUSPEND + usb_pll_on(); + while (!usb_pll_is_locked()) {} +#endif /* AVR_USB_PLL_OFF_IN_SUSPEND */ + + /* Unfreeze the clock */ + USBCON &= ~(1 << FRZCLK); + + /* Clear & disable wakeup interrupt, enable suspend interrupt */ + UDINT &= ~(1 << WAKEUPI); + UDIEN &= ~(1 << WAKEUPE); + UDIEN |= (1 << SUSPE); + + _usb_isr_invoke_event_cb(usbp, USB_EVENT_WAKEUP); + } + + /* USB bus RESUME condition handling.*/ + if (udint & (1 << EORSMI)) { + UDINT &= ~(1 << EORSMI); + UDIEN &= ~(1 << EORSME); + } + + /* USB bus reset condition handling.*/ + if (udint & (1 << EORSTI)) { + UDINT &= ~(1 << EORSTI); + + /* Clear & disable suspend interrupt, enable WAKEUP interrupt */ + UDINT &= ~(1 << SUSPI); + UDIEN &= ~(1 << SUSPE); + UDIEN |= (1 << WAKEUPE); + + /* Reinitialize EP0. This is not mentioned in the datasheet but + * apparently is required. */ + usb_lld_init_endpoint(usbp, 0); + + _usb_isr_invoke_event_cb(usbp, USB_EVENT_RESET); + } + + /* Start-Of-Frame handling, only if enabled */ + if ((UDIEN & (1 << SOFE)) && (udint & (1 << SOFI))) { + _usb_isr_invoke_sof_cb(usbp); + UDINT &= ~(1 << SOFI); + } + + OSAL_IRQ_EPILOGUE(); +} + +static void usb_fifo_write(USBDriver *usbp, usbep_t ep, size_t n) { + const USBEndpointConfig *epcp = usbp->epc[ep]; + USBInEndpointState *isp = epcp->in_state; + syssts_t sts; + if (n == 0) { + isp->last_tx_size = 0; + return; + } + + if (n > epcp->in_maxsize) + n = epcp->in_maxsize; + /* i is number of bytes remaining to transmit minus 1 (to handle 256b case) */ + uint8_t i = n - 1; + + /* Must lock for entire operation to ensure nothing changes the ENUM value */ + sts = osalSysGetStatusAndLockX(); + UENUM = ep & 0xf; + do { + UEDATX = *isp->txbuf++; + } while (i--); + isp->last_tx_size = n; + osalSysRestoreStatusX(sts); +} + +static void usb_fifo_read(USBDriver *usbp, usbep_t ep, size_t n) { + const USBEndpointConfig *epcp = usbp->epc[ep]; + USBOutEndpointState *osp = epcp->out_state; + syssts_t sts; + if (n == 0) + return; + if (n > epcp->out_maxsize) + n = epcp->out_maxsize; + // i is number of bytes remaining to receive minus 1 (to handle 256b case) + uint8_t i = n - 1; + + /* Must lock for entire operation to ensure nothing changes the ENUM value */ + sts = osalSysGetStatusAndLockX(); + UENUM = ep & 0xf; + do { + *osp->rxbuf++ = UEDATX; + } while (i--); + osalSysRestoreStatusX(sts); +} + +static void ep_isr(USBDriver *usbp, usbep_t ep) { + const USBEndpointConfig *epcp = usbp->epc[ep]; + size_t n; + UENUM = ep & 0xf; + + /* TODO: if stalling is needed/expected remove this check */ + osalDbgAssert(!(UEINTX & (1 << STALLEDI)), "Endpoint stalled!"); + + if ((UEIENX & (1 << TXINE)) && (UEINTX & (1 << TXINI))) { + /* Ready to accept more IN data to transmit to host */ + /* Update transaction counts to reflect newly transmitted bytes */ + epcp->in_state->txcnt += epcp->in_state->last_tx_size; + n = epcp->in_state->txsize - epcp->in_state->txcnt; + if (n > 0) { + /* Transfer not completed, there are more packets to send. */ + usb_fifo_write(usbp, ep, n); + + /* Clear FIFOCON to send the data in the FIFO and switch bank */ + UEINTX &= ~((1 << TXINI) | (1 << FIFOCON)); + /* Enable the TX complete interrupt */ + UEIENX |= (1 << TXINE); + } else { + /* Disable TXIN interrupt */ + UEIENX &= ~(1 << TXINE); + /* Handshake interrupt status */ + UEINTX &= ~(1 << TXINI); + _usb_isr_invoke_in_cb(usbp, ep); + } + } else if ((UEIENX & (1 << RXSTPE)) && (UEINTX & (1 << RXSTPI))) { + /* Received SETUP data */ + /* Reset transaction state for endpoint */ + epcp->in_state->txcnt = 0; + epcp->in_state->txsize = 0; + epcp->in_state->last_tx_size = 0; + /* Setup packets handling, setup packets are handled using a + specific callback.*/ + _usb_isr_invoke_setup_cb(usbp, ep); + } else if ((UEIENX & (1 << RXOUTE)) && (UEINTX & (1 << RXOUTI))) { + /* Received OUT data from host */ + if (ep == 0 && usbp->ep0state == USB_EP0_WAITING_STS) { + /* SETUP/control transaction complete, invoke the callback. */ + UEIENX &= ~(1 << RXOUTE); + UEINTX &= ~((1 << RXOUTI) | (1 << FIFOCON)); + _usb_isr_invoke_out_cb(usbp, ep); + } else { + /* Check the FIFO byte count to see how many bytes were received */ + n = UEBCX; + + usb_fifo_read(usbp, ep, n); + + /* Transaction state update */ + epcp->out_state->rxcnt += n; + epcp->out_state->rxsize -= n; + epcp->out_state->rxpkts -= 1; + if (n < epcp->out_maxsize || epcp->out_state->rxpkts == 0) { + /* Disable OUT interrupt */ + UEIENX &= ~(1 << RXOUTE); + /* Mark OUT FIFO processed to allow more data to be received */ + UEINTX &= ~((1 << RXOUTI) | (1 << FIFOCON)); + /* Transfer complete, invokes the callback.*/ + _usb_isr_invoke_out_cb(usbp, ep); + } else { + /* Mark OUT FIFO processed to allow more data to be received */ + UEINTX &= ~((1 << RXOUTI) | (1 << FIFOCON)); + } + } + } +} + +/** + * @brief USB communication event interrupt handler. + * + * @isr + */ +OSAL_IRQ_HANDLER(USB_COM_vect) { + USBDriver *usbp = &USBD1; + const uint8_t epnum_orig = UENUM; + uint8_t i; + + OSAL_IRQ_PROLOGUE(); + + /* Figure out which endpoint(s) are interrupting */ + for (i = 0; i < USB_MAX_ENDPOINTS; ++i) { + if (UEINT & (1 << i)) { + ep_isr(usbp, i); + } + } + + /* Restore endpoint selector to pre-interrupt state */ + UENUM = epnum_orig; + + OSAL_IRQ_EPILOGUE(); +} + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Low level USB driver initialization. + * + * @notapi + */ +void usb_lld_init(void) { +#if AVR_USB_USE_USB1 == TRUE + /* Driver initialization.*/ + usbObjectInit(&USBD1); + + /* Start and lock the USB 48MHz PLL (takes ~100ms) */ + usb_pll_on(); + while (!usb_pll_is_locked()) {} +#endif +} + +/** + * @brief Configures and activates the USB peripheral. + * + * @param[in] usbp pointer to the @p USBDriver object + * + * @notapi + */ +void usb_lld_start(USBDriver *usbp) { + if (usbp->state == USB_STOP) { + /* Enables the peripheral.*/ +#if AVR_USB_USE_USB1 == TRUE + if (&USBD1 == usbp) { + uint8_t i; + /* + * Workaround: disable pad drivers as first step in case bootloader left + * it on. Otherwise VBUS detection interrupt will not trigger later. + */ + USBCON &= ~(1 << OTGPADE); + + /* Enable the internal 3.3V pad regulator */ + UHWCON |= (1 << UVREGE); + + /* Reset and disable all endpoints */ + UERST = 0x7f; + UERST = 0; + for (i = 0; i < USB_MAX_ENDPOINTS; ++i){ + UENUM = i; + UEIENX = 0; + UEINTX = 0; + UECFG1X = 0; + UECONX &= ~(1 << EPEN); + } + } +#endif + /* Reset procedure enforced on driver start.*/ + _usb_reset(usbp); + } +} + +/** + * @brief Deactivates the USB peripheral. + * + * @param[in] usbp pointer to the @p USBDriver object + * + * @notapi + */ +void usb_lld_stop(USBDriver *usbp) { + if (usbp->state == USB_READY) { + /* Disables the peripheral.*/ +#if AVR_USB_USE_USB1 == TRUE + if (&USBD1 == usbp) { + /* Disable and clear transition interrupts */ + USBCON &= ~((1 << VBUSTE) | (1 << IDTE)); + USBINT = 0; + + /* Disable and clear device interrupts */ + UDIEN &= ~((1 << UPRSME) | (1 << EORSME) | (1 << WAKEUPE) | (1 << EORSTE) + | (1 << SOFE) | (1 << SUSPE)); + UDINT = 0; + + /* Freeze clock */ + USBCON |= (1 << FRZCLK); + + /* Disable USB logic */ + USBCON &= ~(1 << USBE); + } +#endif + } +} + +/** + * @brief USB low level reset routine. + * + * @param[in] usbp pointer to the @p USBDriver object + * + * @notapi + */ +void usb_lld_reset(USBDriver *usbp) { + /* Post-reset initialization.*/ + /* Reset and enable via toggling the USB macro logic overall enable bit */ + USBCON &= ~(1 << USBE); + USBCON |= (1 << USBE); + + /* Unfreeze clock */ + USBCON &= ~(1 << FRZCLK); + + /* Set Device mode */ + /* TODO: Support HOST/OTG mode if needed */ + UHWCON |= (1 << UIMOD); + + /* Set FULL 12mbps speed */ + UDCON &= ~(1 << LSM); + + /* Enable device pin interrupt */ + USBCON |= (1 << VBUSTE); + + /* EP0 initialization.*/ + UERST |= (1 << 0); + UERST &= ~(1 << 0); + usbp->epc[0] = &ep0config; + usb_lld_init_endpoint(usbp, 0); + + /* Enable device-level event interrupts */ + UDINT &= ~(1 << SUSPI); + UDIEN = (1 << UPRSME) | (1 << EORSME) | (1 << WAKEUPE) | (1 << EORSTE) + | (1 << SUSPE); + /* The SOF interrupt is only enabled if a callback is defined for + this service because it is a high rate source. */ + if (usbp->config->sof_cb != NULL) + UDIEN |= (1 << SOFE); + + /* Set OTG PAD to on which will trigger VBUS transition if plugged in. */ + USBCON |= (1 << OTGPADE); +} + +/** + * @brief Sets the USB address. + * + * @param[in] usbp pointer to the @p USBDriver object + * + * @notapi + */ +void usb_lld_set_address(USBDriver *usbp) { + UDADDR = (UDADDR & (1 << ADDEN)) | (usbp->address & 0x7F); + + UDADDR |= (1 << ADDEN); +} + +/** + * @brief Enables an endpoint. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * + * @notapi + */ +void usb_lld_init_endpoint(USBDriver *usbp, usbep_t ep) { + uint16_t size = 0; + const USBEndpointConfig *epcp = usbp->epc[ep]; + + /* Select this endpoint number for subsequent commands */ + UENUM = ep & 0xf; + + /* Enable endpoint to take out of reset */ + UECONX |= (1 << EPEN); + + UECFG1X = 0; + /* Set the endpoint type.*/ + switch (epcp->ep_mode & USB_EP_MODE_TYPE) { + case USB_EP_MODE_TYPE_ISOC: + UECFG0X = (0 << EPTYPE1) | (1 << EPTYPE0); + break; + case USB_EP_MODE_TYPE_BULK: + UECFG0X = (1 << EPTYPE1) | (0 << EPTYPE0); + break; + case USB_EP_MODE_TYPE_INTR: + UECFG0X = (1 << EPTYPE1) | (1 << EPTYPE0); + break; + default: + UECFG0X = (0 << EPTYPE1) | (0 << EPTYPE0); + } + if ((epcp->ep_mode & USB_EP_MODE_TYPE) == USB_EP_MODE_TYPE_CTRL) { + /* CTRL endpoint */ + osalDbgCheck(epcp->in_maxsize == epcp->out_maxsize); + size = epcp->in_maxsize; + } else { + osalDbgAssert(!(epcp->in_cb != NULL && epcp->out_cb != NULL), + "On AVR each endpoint can be IN or OUT not both"); + + /* IN endpoint? */ + if (epcp->in_cb != NULL) { + UECFG0X |= (1 << EPDIR); + size = epcp->in_maxsize; + } + + /* OUT endpoint? */ + if (epcp->out_cb != NULL) { + UECFG0X &= ~(1 << EPDIR); + size = epcp->out_maxsize; + } + } + + /* Endpoint size and address initialization. */ + switch (size) { + case 8: UECFG1X = (0 << EPSIZE0) | (1 << ALLOC); break; + case 16: UECFG1X = (1 << EPSIZE0) | (1 << ALLOC); break; + case 32: UECFG1X = (2 << EPSIZE0) | (1 << ALLOC); break; + case 64: UECFG1X = (3 << EPSIZE0) | (1 << ALLOC); break; + case 128: + osalDbgAssert(ep == 1, "Endpoint size of 128 bytes only valid for EP#1"); + UECFG1X = (4 << EPSIZE0) | (1 << ALLOC); break; + case 256: + osalDbgAssert(ep == 1, "Endpoint size of 256 bytes only valid for EP#1"); + UECFG1X = (5 << EPSIZE0) | (1 << ALLOC); break; + default: + osalDbgAssert(false, "Invalid size for USB endpoint"); + } + + UEIENX |= (1 << RXSTPE)/* | (1 << RXOUTE)*/ | (1 << STALLEDE) ; + + osalDbgAssert((UESTA0X & (1 << CFGOK)), + "Hardware reports endpoint config is INVALID"); +} + +/** + * @brief Disables all the active endpoints except the endpoint zero. + * + * @param[in] usbp pointer to the @p USBDriver object + * + * @notapi + */ +void usb_lld_disable_endpoints(USBDriver *usbp) { + uint8_t i; + for (i = 1; i <= USB_MAX_ENDPOINTS; ++i) { + UENUM = i; + UECFG1X &= ~(1 << ALLOC); + UECONX &= ~(1 << EPEN); + } +} + +/** + * @brief Returns the status of an OUT endpoint. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * @return The endpoint status. + * @retval EP_STATUS_DISABLED The endpoint is not active. + * @retval EP_STATUS_STALLED The endpoint is stalled. + * @retval EP_STATUS_ACTIVE The endpoint is active. + * + * @notapi + */ +usbepstatus_t usb_lld_get_status_out(USBDriver *usbp, usbep_t ep) { + /* Select this endpoint number for subsequent commands */ + UENUM = ep & 0xf; + + if (!(UECONX & (1 << EPEN))) + return EP_STATUS_DISABLED; + if (UECONX & (1 << STALLRQ)) + return EP_STATUS_STALLED; + return EP_STATUS_ACTIVE; +} + +/** + * @brief Returns the status of an IN endpoint. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * @return The endpoint status. + * @retval EP_STATUS_DISABLED The endpoint is not active. + * @retval EP_STATUS_STALLED The endpoint is stalled. + * @retval EP_STATUS_ACTIVE The endpoint is active. + * + * @notapi + */ +usbepstatus_t usb_lld_get_status_in(USBDriver *usbp, usbep_t ep) { + return usb_lld_get_status_out(usbp, ep); +} + +/** + * @brief Reads a setup packet from the dedicated packet buffer. + * @details This function must be invoked in the context of the @p setup_cb + * callback in order to read the received setup packet. + * @pre In order to use this function the endpoint must have been + * initialized as a control endpoint. + * @post The endpoint is ready to accept another packet. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * @param[out] buf buffer where to copy the packet data + * + * @notapi + */ +void usb_lld_read_setup(USBDriver *usbp, usbep_t ep, uint8_t *buf) { + uint8_t i; + /* Select this endpoint number for subsequent commands */ + UENUM = ep & 0xf; + + for (i = 0; i < 8; ++i) { + *buf++ = UEDATX; + } + /* Clear FIFOCON and RXSTPI to drain the setup packet data from the FIFO */ + UEINTX &= ~((1 << FIFOCON) | (1 << RXSTPI)); +} + +/** + * @brief Ends a SETUP transaction + * @details This function must be invoked in the context of the @p setup_cb + * callback in order to finish an entire setup packet. + * @pre In order to use this function the endpoint must have been + * initialized as a control endpoint. + * @post The endpoint is ready to accept another packet. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * + * @notapi + */ +void usb_lld_end_setup(USBDriver *usbp, usbep_t ep) { + /* Select this endpoint number for subsequent commands */ + UENUM = ep & 0xf; + + if ((usbp->setup[0] & USB_RTYPE_DIR_MASK) == USB_RTYPE_DIR_DEV2HOST) { + /* Enable interrupt and wait for OUT packet */ + usbp->epc[ep]->out_state->rxsize = 0; + usbp->epc[ep]->out_state->rxpkts = 1; + + UEINTX &= ~((1 << FIFOCON) | (1 << RXOUTI)); + UEIENX |= (1 << RXOUTE); + } else { + /* Enable interrupt and wait for IN packet */ + usbp->epc[ep]->in_state->last_tx_size = 0; + usbp->epc[ep]->in_state->txcnt = 0; + usbp->epc[ep]->in_state->txsize = 0; + + UEINTX &= ~((1 << FIFOCON) | (1 << TXINI)); + UEIENX |= (1 << TXINE); + } +} + +/** + * @brief Starts a receive operation on an OUT endpoint. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * + * @notapi + */ +void usb_lld_start_out(USBDriver *usbp, usbep_t ep) { + USBOutEndpointState *osp = usbp->epc[ep]->out_state; + syssts_t sts; + + /* Initialize transfer by recording how many packets we expect to receive. */ + if (osp->rxsize == 0) /* Special case for zero sized packets.*/ + osp->rxpkts = 1; + else + osp->rxpkts = (uint8_t)((osp->rxsize + usbp->epc[ep]->out_maxsize - 1) / + usbp->epc[ep]->out_maxsize); + + /* Select this endpoint number for subsequent commands */ + /* Must lock for entire operation to ensure nothing changes the ENUM value */ + sts = osalSysGetStatusAndLockX(); + UENUM = ep & 0xf; + + UEIENX |= (1 << RXOUTE); + osalSysRestoreStatusX(sts); +} + +/** + * @brief Starts a transmit operation on an IN endpoint. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * + * @notapi + */ +void usb_lld_start_in(USBDriver *usbp, usbep_t ep) { + USBInEndpointState *isp = usbp->epc[ep]->in_state; + syssts_t sts; + + /* Initialize transfer by filling FIFO with passed data. */ + usb_fifo_write(usbp, ep, isp->txsize); + + /* Select this endpoint number for subsequent commands */ + /* Must lock for entire operation to ensure nothing changes the ENUM value */ + sts = osalSysGetStatusAndLockX(); + UENUM = ep & 0xf; + + /* Clear FIFOCON to send the data in the FIFO and switch bank */ + UEINTX &= ~((1 << TXINI) | (1 << FIFOCON)); + + /* Enable the TX complete interrupt */ + UEIENX |= (1 << TXINE); + + osalSysRestoreStatusX(sts); +} + +/** + * @brief Brings an OUT endpoint in the stalled state. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * + * @notapi + */ +void usb_lld_stall_out(USBDriver *usbp, usbep_t ep) { + syssts_t sts; + (void)usbp; + + /* Select this endpoint number for subsequent commands */ + /* Must lock for entire operation to ensure nothing changes the ENUM value */ + sts = osalSysGetStatusAndLockX(); + UENUM = ep & 0xf; + + UECONX |= (1 << STALLRQ); + osalSysRestoreStatusX(sts); +} + +/** + * @brief Brings an IN endpoint in the stalled state. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * + * @notapi + */ +void usb_lld_stall_in(USBDriver *usbp, usbep_t ep) { + usb_lld_stall_out(usbp, ep); +} + +/** + * @brief Brings an OUT endpoint in the active state. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * + * @notapi + */ +void usb_lld_clear_out(USBDriver *usbp, usbep_t ep) { + syssts_t sts; + (void)usbp; + + /* Select this endpoint number for subsequent commands */ + /* Must lock for entire operation to ensure nothing changes the ENUM value */ + sts = osalSysGetStatusAndLockX(); + UENUM = ep & 0xf; + + UECONX |= (1 << STALLRQC); + osalSysRestoreStatusX(sts); +} + +/** + * @brief Brings an IN endpoint in the active state. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * + * @notapi + */ +void usb_lld_clear_in(USBDriver *usbp, usbep_t ep) { + usb_lld_clear_out(usbp, ep); +} + +#endif /* HAL_USE_USB == TRUE */ + +/** @} */ diff --git a/os/hal/ports/AVR/hal_usb_lld.h b/os/hal/ports/AVR/hal_usb_lld.h new file mode 100644 index 000000000..e47d38425 --- /dev/null +++ b/os/hal/ports/AVR/hal_usb_lld.h @@ -0,0 +1,398 @@ +/* + Copyright (C) 2015 Robert Lippert + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file usb_lld.h + * @brief AVR USB subsystem low level driver header. + * + * @addtogroup USB + * @{ + */ + +#ifndef _USB_LLD_H_ +#define _USB_LLD_H_ + +#if (HAL_USE_USB == TRUE) || defined(__DOXYGEN__) + +#include "hal_usb.h" + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/** + * @brief Maximum endpoint address. + */ +#define USB_MAX_ENDPOINTS 7 + +/** + * @brief Status stage handling method. + */ +#define USB_EP0_STATUS_STAGE USB_EP0_STATUS_STAGE_HW + +/** + * @brief The address is changed after IN packet is received. + */ +#define USB_SET_ADDRESS_MODE USB_LATE_SET_ADDRESS + +/** + * @brief Method for set address acknowledge. + */ +#define USB_SET_ADDRESS_ACK_HANDLING USB_SET_ADDRESS_ACK_SW + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @name AVR configuration options + * @{ + */ +/** + * @brief USB driver enable switch. + * @details If set to @p TRUE the support for USB1 is included. + * @note The default is @p FALSE. + */ +#if !defined(AVR_USB_USE_USB1) || defined(__DOXYGEN__) +#define AVR_USB_USE_USB1 FALSE +#endif +/** @} */ + +/* + * If compiler supports named address spaces + * (see https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html) + * then mark our TX buf pointer as able to cover flash or SRAM to allow + * for storing/transmitting constants like USB descriptors in flash to save + * previous RAM space. + */ +#if !defined(AVR_USB_USE_NAMED_ADDRESS_SPACES) || defined(__DOXYGEN__) +#define AVR_USB_USE_NAMED_ADDRESS_SPACES FALSE +#endif + +#if (AVR_USB_USE_NAMED_ADDRESS_SPACES == TRUE) && defined(__MEMX) +#define AVR_USB_TX_BUF_ADDRESS_SPACE volatile __memx +#else +#define AVR_USB_TX_BUF_ADDRESS_SPACE +#endif + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +typedef const AVR_USB_TX_BUF_ADDRESS_SPACE uint8_t *usbbufptr_t; + +/** + * @brief Type of an IN endpoint state structure. + */ +typedef struct { + /** + * @brief Requested transmit transfer size. + */ + size_t txsize; + /** + * @brief Transmitted bytes so far. + */ + size_t txcnt; + /** + * @brief Pointer to the transmission linear buffer. + */ + usbbufptr_t txbuf; +#if (USB_USE_WAIT == TRUE) || defined(__DOXYGEN__) + /** + * @brief Waiting thread. + */ + thread_reference_t thread; +#endif + /* End of the mandatory fields.*/ + /** + * @brief Number of expected bytes in the most recent transmission. + */ + size_t last_tx_size; +} USBInEndpointState; + +/** + * @brief Type of an OUT endpoint state structure. + */ +typedef struct { + /** + * @brief Requested receive transfer size. + */ + size_t rxsize; + /** + * @brief Received bytes so far. + */ + size_t rxcnt; + /** + * @brief Pointer to the receive linear buffer. + */ + uint8_t *rxbuf; +#if (USB_USE_WAIT == TRUE) || defined(__DOXYGEN__) + /** + * @brief Waiting thread. + */ + thread_reference_t thread; +#endif + /* End of the mandatory fields.*/ + uint8_t rxpkts; +} USBOutEndpointState; + +/** + * @brief Type of an USB endpoint configuration structure. + * @note Platform specific restrictions may apply to endpoints. + */ +typedef struct { + /** + * @brief Type and mode of the endpoint. + */ + uint32_t ep_mode; + /** + * @brief Setup packet notification callback. + * @details This callback is invoked when a setup packet has been + * received. + * @post The application must immediately call @p usbReadPacket() in + * order to access the received packet. + * @note This field is only valid for @p USB_EP_MODE_TYPE_CTRL + * endpoints, it should be set to @p NULL for other endpoint + * types. + */ + usbepcallback_t setup_cb; + /** + * @brief IN endpoint notification callback. + * @details This field must be set to @p NULL if the IN endpoint is not + * used. + */ + usbepcallback_t in_cb; + /** + * @brief OUT endpoint notification callback. + * @details This field must be set to @p NULL if the OUT endpoint is not + * used. + */ + usbepcallback_t out_cb; + /** + * @brief IN endpoint maximum packet size. + * @details This field must be set to zero if the IN endpoint is not + * used. + */ + uint16_t in_maxsize; + /** + * @brief OUT endpoint maximum packet size. + * @details This field must be set to zero if the OUT endpoint is not + * used. + */ + uint16_t out_maxsize; + /** + * @brief @p USBEndpointState associated to the IN endpoint. + * @details This structure maintains the state of the IN endpoint. + */ + USBInEndpointState *in_state; + /** + * @brief @p USBEndpointState associated to the OUT endpoint. + * @details This structure maintains the state of the OUT endpoint. + */ + USBOutEndpointState *out_state; + /* End of the mandatory fields.*/ +} USBEndpointConfig; + +/** + * @brief Type of an USB driver configuration structure. + */ +typedef struct { + /** + * @brief USB events callback. + * @details This callback is invoked when an USB driver event is registered. + */ + usbeventcb_t event_cb; + /** + * @brief Device GET_DESCRIPTOR request callback. + * @note This callback is mandatory and cannot be set to @p NULL. + */ + usbgetdescriptor_t get_descriptor_cb; + /** + * @brief Requests hook callback. + * @details This hook allows to be notified of standard requests or to + * handle non standard requests. + */ + usbreqhandler_t requests_hook_cb; + /** + * @brief Start Of Frame callback. + */ + usbcallback_t sof_cb; + /* End of the mandatory fields.*/ +} USBConfig; + +/** + * @brief Structure representing an USB driver. + */ +struct USBDriver { + /** + * @brief Driver state. + */ + usbstate_t state; + /** + * @brief Current configuration data. + */ + const USBConfig *config; + /** + * @brief Bit map of the transmitting IN endpoints. + */ + uint8_t transmitting; + /** + * @brief Bit map of the receiving OUT endpoints. + */ + uint8_t receiving; + /** + * @brief Active endpoints configurations. + */ + const USBEndpointConfig *epc[USB_MAX_ENDPOINTS + 1]; + /** + * @brief Fields available to user, it can be used to associate an + * application-defined handler to an IN endpoint. + * @note The base index is one, the endpoint zero does not have a + * reserved element in this array. + */ + void *in_params[USB_MAX_ENDPOINTS]; + /** + * @brief Fields available to user, it can be used to associate an + * application-defined handler to an OUT endpoint. + * @note The base index is one, the endpoint zero does not have a + * reserved element in this array. + */ + void *out_params[USB_MAX_ENDPOINTS]; + /** + * @brief Endpoint 0 state. + */ + usbep0state_t ep0state; + /** + * @brief Next position in the buffer to be transferred through endpoint 0. + */ + const AVR_USB_TX_BUF_ADDRESS_SPACE uint8_t *ep0next; + /** + * @brief Number of bytes yet to be transferred through endpoint 0. + */ + size_t ep0n; + /** + * @brief Endpoint 0 end transaction callback. + */ + usbcallback_t ep0endcb; + /** + * @brief Setup packet buffer. + */ + uint8_t setup[8]; + /** + * @brief Current USB device status. + */ + uint16_t status; + /** + * @brief Assigned USB address. + */ + uint8_t address; + /** + * @brief Current USB device configuration. + */ + uint8_t configuration; +#if defined(USB_DRIVER_EXT_FIELDS) + USB_DRIVER_EXT_FIELDS +#endif + /* End of the mandatory fields.*/ +}; + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/** + * @brief Returns the current frame number. + * + * @param[in] usbp pointer to the @p USBDriver object + * @return The current frame number. + * + * @notapi + */ +#define usb_lld_get_frame_number(usbp) (UDFNUM) + +/** + * @brief Returns the exact size of a receive transaction. + * @details The received size can be different from the size specified in + * @p usbStartReceiveI() because the last packet could have a size + * different from the expected one. + * @pre The OUT endpoint must have been configured in transaction mode + * in order to use this function. + * + * @param[in] usbp pointer to the @p USBDriver object + * @param[in] ep endpoint number + * @return Received data size. + * + * @notapi + */ +#define usb_lld_get_transaction_size(usbp, ep) \ + ((usbp)->epc[ep]->out_state->rxcnt) + +/** + * @brief Connects the USB device. + * + * @api + */ +#define usb_lld_connect_bus(usbp) (UDCON &= ~(1 << DETACH)) + +/** + * @brief Disconnect the USB device. + * + * @api + */ +#define usb_lld_disconnect_bus(usbp) (UDCON |= (1 << DETACH)) + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#if (AVR_USB_USE_USB1 == TRUE) && !defined(__DOXYGEN__) +extern USBDriver USBD1; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + void usb_lld_init(void); + void usb_lld_start(USBDriver *usbp); + void usb_lld_stop(USBDriver *usbp); + void usb_lld_reset(USBDriver *usbp); + void usb_lld_set_address(USBDriver *usbp); + void usb_lld_enable_address(USBDriver *usbp); + void usb_lld_init_endpoint(USBDriver *usbp, usbep_t ep); + void usb_lld_disable_endpoints(USBDriver *usbp); + usbepstatus_t usb_lld_get_status_in(USBDriver *usbp, usbep_t ep); + usbepstatus_t usb_lld_get_status_out(USBDriver *usbp, usbep_t ep); + void usb_lld_read_setup(USBDriver *usbp, usbep_t ep, uint8_t *buf); + void usb_lld_end_setup(USBDriver *usbp, usbep_t ep); + void usb_lld_start_out(USBDriver *usbp, usbep_t ep); + void usb_lld_start_in(USBDriver *usbp, usbep_t ep); + void usb_lld_stall_out(USBDriver *usbp, usbep_t ep); + void usb_lld_stall_in(USBDriver *usbp, usbep_t ep); + void usb_lld_clear_out(USBDriver *usbp, usbep_t ep); + void usb_lld_clear_in(USBDriver *usbp, usbep_t ep); +#ifdef __cplusplus +} +#endif + +#endif /* HAL_USE_USB == TRUE */ + +#endif /* _USB_LLD_H_ */ + +/** @} */ diff --git a/os/hal/ports/AVR/platform.mk b/os/hal/ports/AVR/platform.mk index 7181924bc..2b20b8a80 100644 --- a/os/hal/ports/AVR/platform.mk +++ b/os/hal/ports/AVR/platform.mk @@ -8,6 +8,7 @@ PLATFORMSRC = ${CHIBIOS}/os/hal/ports/AVR/hal_lld.c \ ${CHIBIOS}/os/hal/ports/AVR/hal_gpt_lld.c \
${CHIBIOS}/os/hal/ports/AVR/hal_pwm_lld.c \
${CHIBIOS}/os/hal/ports/AVR/hal_icu_lld.c \
+ ${CHIBIOS}/os/hal/ports/AVR/hal_usb_lld.c \
${CHIBIOS}/os/hal/ports/AVR/hal_st_lld.c
# Required include directories
|