/* * This file is part of the libopencm3 project. * * Copyright (C) 2010 Gareth McMullin * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . */ #include #include #include #include #include #include "usb_private.h" static usbd_device *stm32f103_usbd_init(void); static void stm32f103_set_address(usbd_device *usbd_dev, u8 addr); static void stm32f103_ep_setup(usbd_device *usbd_dev, u8 addr, u8 type, u16 max_size, void (*callback) (usbd_device *usbd_dev, u8 ep)); static void stm32f103_endpoints_reset(usbd_device *usbd_dev); static void stm32f103_ep_stall_set(usbd_device *usbd_dev, u8 addr, u8 stall); static u8 stm32f103_ep_stall_get(usbd_device *usbd_dev, u8 addr); static void stm32f103_ep_nak_set(usbd_device *usbd_dev, u8 addr, u8 nak); static u16 stm32f103_ep_write_packet(usbd_device *usbd_dev, u8 addr, const void *buf, u16 len); static u16 stm32f103_ep_read_packet(usbd_device *usbd_dev, u8 addr, void *buf, u16 len); static void stm32f103_poll(usbd_device *usbd_dev); static u8 force_nak[8]; static struct _usbd_device usbd_dev; const struct _usbd_driver stm32f103_usb_driver = { .init = stm32f103_usbd_init, .set_address = stm32f103_set_address, .ep_setup = stm32f103_ep_setup, .ep_reset = stm32f103_endpoints_reset, .ep_stall_set = stm32f103_ep_stall_set, .ep_stall_get = stm32f103_ep_stall_get, .ep_nak_set = stm32f103_ep_nak_set, .ep_write_packet = stm32f103_ep_write_packet, .ep_read_packet = stm32f103_ep_read_packet, .poll = stm32f103_poll, }; /** Initialize the USB device controller hardware of the STM32. */ static usbd_device *stm32f103_usbd_init(void) { rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_USBEN); SET_REG(USB_CNTR_REG, 0); SET_REG(USB_BTABLE_REG, 0); SET_REG(USB_ISTR_REG, 0); /* Enable RESET, SUSPEND, RESUME and CTR interrupts. */ SET_REG(USB_CNTR_REG, USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SUSPM | USB_CNTR_WKUPM); return &usbd_dev; } static void stm32f103_set_address(usbd_device *dev, u8 addr) { (void)dev; /* Set device address and enable. */ SET_REG(USB_DADDR_REG, (addr & USB_DADDR_ADDR) | USB_DADDR_ENABLE); } /** * Set the receive buffer size for a given USB endpoint. * * @param ep Index of endpoint to configure. * @param size Size in bytes of the RX buffer. */ static void usb_set_ep_rx_bufsize(usbd_device *dev, u8 ep, u32 size) { (void)dev; if (size > 62) { if (size & 0x1f) size -= 32; USB_SET_EP_RX_COUNT(ep, (size << 5) | 0x8000); } else { if (size & 1) size++; USB_SET_EP_RX_COUNT(ep, size << 10); } } static void stm32f103_ep_setup(usbd_device *dev, u8 addr, u8 type, u16 max_size, void (*callback) (usbd_device *usbd_dev, u8 ep)) { /* Translate USB standard type codes to STM32. */ const u16 typelookup[] = { [USB_ENDPOINT_ATTR_CONTROL] = USB_EP_TYPE_CONTROL, [USB_ENDPOINT_ATTR_ISOCHRONOUS] = USB_EP_TYPE_ISO, [USB_ENDPOINT_ATTR_BULK] = USB_EP_TYPE_BULK, [USB_ENDPOINT_ATTR_INTERRUPT] = USB_EP_TYPE_INTERRUPT, }; u8 dir = addr & 0x80; addr &= 0x7f; /* Assign address. */ USB_SET_EP_ADDR(addr, addr); USB_SET_EP_TYPE(addr, typelookup[type]); if (dir || (addr == 0)) { USB_SET_EP_TX_ADDR(addr, dev->pm_top); if (callback) { dev->user_callback_ctr[addr][USB_TRANSACTION_IN] = (void *)callback; } USB_CLR_EP_TX_DTOG(addr); USB_SET_EP_TX_STAT(addr, USB_EP_TX_STAT_NAK); dev->pm_top += max_size; } if (!dir) { USB_SET_EP_RX_ADDR(addr, dev->pm_top); usb_set_ep_rx_bufsize(dev, addr, max_size); if (callback) { dev->user_callback_ctr[addr][USB_TRANSACTION_OUT] = (void *)callback; } USB_CLR_EP_RX_DTOG(addr); USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_VALID); dev->pm_top += max_size; } } static void stm32f103_endpoints_reset(usbd_device *dev) { int i; /* Reset all endpoints. */ for (i = 1; i < 8; i++) { USB_SET_EP_TX_STAT(i, USB_EP_TX_STAT_DISABLED); USB_SET_EP_RX_STAT(i, USB_EP_RX_STAT_DISABLED); } dev->pm_top = 0x40 + (2 * dev->desc->bMaxPacketSize0); } static void stm32f103_ep_stall_set(usbd_device *dev, u8 addr, u8 stall) { (void)dev; if (addr == 0) USB_SET_EP_TX_STAT(addr, stall ? USB_EP_TX_STAT_STALL : USB_EP_TX_STAT_NAK); if (addr & 0x80) { addr &= 0x7F; USB_SET_EP_TX_STAT(addr, stall ? USB_EP_TX_STAT_STALL : USB_EP_TX_STAT_NAK); /* Reset to DATA0 if clearing stall condition. */ if (!stall) USB_CLR_EP_TX_DTOG(addr); } else { /* Reset to DATA0 if clearing stall condition. */ if (!stall) USB_CLR_EP_RX_DTOG(addr); USB_SET_EP_RX_STAT(addr, stall ? USB_EP_RX_STAT_STALL : USB_EP_RX_STAT_VALID); } } static u8 stm32f103_ep_stall_get(usbd_device *dev, u8 addr) { (void)dev; if (addr & 0x80) { if ((*USB_EP_REG(addr & 0x7F) & USB_EP_TX_STAT) == USB_EP_TX_STAT_STALL) return 1; } else { if ((*USB_EP_REG(addr) & USB_EP_RX_STAT) == USB_EP_RX_STAT_STALL) return 1; } return 0; } static void stm32f103_ep_nak_set(usbd_device *dev, u8 addr, u8 nak) { (void)dev; /* It does not make sence to force NAK on IN endpoints. */ if (addr & 0x80) return; force_nak[addr] = nak; if (nak) USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_NAK); else USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_VALID); } /** * Copy a data buffer to packet memory. * * @param vPM Destination pointer into packet memory. * @param buf Source pointer to data buffer. * @param len Number of bytes to copy. */ static void usb_copy_to_pm(volatile void *vPM, const void *buf, u16 len) { const u16 *lbuf = buf; volatile u16 *PM = vPM; for (len = (len + 1) >> 1; len; PM += 2, lbuf++, len--) *PM = *lbuf; } static u16 stm32f103_ep_write_packet(usbd_device *dev, u8 addr, const void *buf, u16 len) { (void)dev; addr &= 0x7F; if ((*USB_EP_REG(addr) & USB_EP_TX_STAT) == USB_EP_TX_STAT_VALID) return 0; usb_copy_to_pm(USB_GET_EP_TX_BUFF(addr), buf, len); USB_SET_EP_TX_COUNT(addr, len); USB_SET_EP_TX_STAT(addr, USB_EP_TX_STAT_VALID); return len; } /** * Copy a data buffer from packet memory. * * @param buf Source pointer to data buffer. * @param vPM Destination pointer into packet memory. * @param len Number of bytes to copy. */ static void usb_copy_from_pm(void *buf, const volatile void *vPM, u16 len) { u16 *lbuf = buf; const volatile u16 *PM = vPM; u8 odd = len & 1; for (len >>= 1; len; PM += 2, lbuf++, len--) *lbuf = *PM; if (odd) *(u8 *) lbuf = *(u8 *) PM; } static u16 stm32f103_ep_read_packet(usbd_device *dev, u8 addr, void *buf, u16 len) { (void)dev; if ((*USB_EP_REG(addr) & USB_EP_RX_STAT) == USB_EP_RX_STAT_VALID) return 0; len = MIN(USB_GET_EP_RX_COUNT(addr) & 0x3ff, len); usb_copy_from_pm(buf, USB_GET_EP_RX_BUFF(addr), len); USB_CLR_EP_RX_CTR(addr); if (!force_nak[addr]) USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_VALID); return len; } static void stm32f103_poll(usbd_device *dev) { u16 istr = *USB_ISTR_REG; if (istr & USB_ISTR_RESET) { dev->pm_top = 0x40; _usbd_reset(dev); USB_CLR_ISTR_RESET(); return; } if (istr & USB_ISTR_CTR) { u8 ep = istr & USB_ISTR_EP_ID; u8 type = (istr & USB_ISTR_DIR) ? 1 : 0; if (type) /* OUT or SETUP transaction */ type += (*USB_EP_REG(ep) & USB_EP_SETUP) ? 1 : 0; else /* IN transaction */ USB_CLR_EP_TX_CTR(ep); if (dev->user_callback_ctr[ep][type]) dev->user_callback_ctr[ep][type] (dev, ep); else USB_CLR_EP_RX_CTR(ep); } if (istr & USB_ISTR_SUSP) { USB_CLR_ISTR_SUSP(); if (dev->user_callback_suspend) dev->user_callback_suspend(); } if (istr & USB_ISTR_WKUP) { USB_CLR_ISTR_WKUP(); if (dev->user_callback_resume) dev->user_callback_resume(); } if (istr & USB_ISTR_SOF) { if (dev->user_callback_sof) dev->user_callback_sof(); USB_CLR_ISTR_SOF(); } }