Skip to content

File usbd_stm32f103_devfs.c

File List > fw > rbcx-coprocessor > lib > libusb_stm32 > src > usbd_stm32f103_devfs.c

Go to the documentation of this file.

/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers
 *
 * Copyright ©2016 Dmitry Filimonchuk <dmitrystu[at]gmail[dot]com>
 * Copyright ©2017 Max Chan <max[at]maxchan[dot]info>
 *
 * 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 <stdint.h>
#include <stdbool.h>
#include "stm32.h"
#include "usb.h"

#if defined(USBD_STM32F103)

#define USB_EP_SWBUF_TX     USB_EP_DTOG_RX
#define USB_EP_SWBUF_RX     USB_EP_DTOG_TX

#define EP_TOGGLE_SET(epr, bits, mask) *(epr) = (*(epr) ^ (bits)) & (USB_EPREG_MASK | (mask))

#define EP_TX_STALL(epr)    EP_TOGGLE_SET((epr), USB_EP_TX_STALL,                   USB_EPTX_STAT)
#define EP_RX_STALL(epr)    EP_TOGGLE_SET((epr), USB_EP_RX_STALL,                   USB_EPRX_STAT)
#define EP_TX_UNSTALL(epr)  EP_TOGGLE_SET((epr), USB_EP_TX_NAK,                     USB_EPTX_STAT | USB_EP_DTOG_TX)
#define EP_RX_UNSTALL(epr)  EP_TOGGLE_SET((epr), USB_EP_RX_VALID,                   USB_EPRX_STAT | USB_EP_DTOG_RX)
#define EP_DTX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_VALID,                   USB_EPTX_STAT | USB_EP_DTOG_TX | USB_EP_SWBUF_TX)
#define EP_DRX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID | USB_EP_SWBUF_RX, USB_EPRX_STAT | USB_EP_DTOG_RX | USB_EP_SWBUF_RX)
#define EP_TX_VALID(epr)    EP_TOGGLE_SET((epr), USB_EP_TX_VALID,                   USB_EPTX_STAT)
#define EP_RX_VALID(epr)    EP_TOGGLE_SET((epr), USB_EP_RX_VALID,                   USB_EPRX_STAT)

#define STATUS_VAL(x)       (x)

typedef union _pma_table pma_table;

#if defined(STM32F302x8) || defined(STM32F302xE) || defined(STM32F303xE)
    #if !defined(USB_PMASIZE)
    #warning PMA memory size is not defined. Use 768 bytes by default
    #define USB_PMASIZE 0x300
    #endif
    #define PMA_STEP    1

    typedef struct {
        uint16_t    addr;
        uint16_t    cnt;
    } pma_rec;

    inline static pma_table *EPT(uint8_t ep) {
        return (pma_table*)((ep & 0x07) * 8 + USB_PMAADDR);
    }

    inline static uint16_t *PMA(uint16_t addr) {
        return (uint16_t*)(USB_PMAADDR + addr);
    }

#else
    #if !defined(USB_PMASIZE)
    #warning PMA memory size is not defined. Use 512 bytes by default
    #define USB_PMASIZE 0x200
    #endif
    #define PMA_STEP    2

    typedef struct {
        uint16_t    addr;
        uint16_t    :16;
        uint16_t    cnt;
        uint16_t    :16;
    } pma_rec;

    inline static pma_table *EPT(uint8_t ep) {
        return (pma_table*)((ep & 0x07) * 16 + USB_PMAADDR);
    }

    inline static uint16_t *PMA(uint16_t addr) {
        return (uint16_t*)(USB_PMAADDR + 2 * addr);
    }
#endif

union _pma_table {
    struct {
    pma_rec     tx;
    pma_rec     rx;
    };
    struct {
    pma_rec     tx0;
    pma_rec     tx1;
    };
    struct {
    pma_rec     rx0;
    pma_rec     rx1;
    };
};

inline static void set_gpiox() {
#if defined(STM32F1) && defined(USBD_DP_PORT)
    if (USBD_DP_PORT == GPIOA) {RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; return;}
    if (USBD_DP_PORT == GPIOB) {RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; return;}
    if (USBD_DP_PORT == GPIOC) {RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; return;}
    if (USBD_DP_PORT == GPIOD) {RCC->APB2ENR |= RCC_APB2ENR_IOPDEN; return;}
    #if defined(GPIOE)
    if (USBD_DP_PORT == GPIOE) {RCC->APB2ENR |= RCC_APB2ENR_IOPEEN; return;}
    #endif
    #if defined(GPIOF)
    if (USBD_DP_PORT == GPIOF) {RCC->APB2ENR |= RCC_APB2ENR_IOPFEN; return;}
    #endif
#elif defined(STM32F3) && defined(USBD_DP_PORT)
    if (USBD_DP_PORT == GPIOA) {RCC->AHBENR |= RCC_AHBENR_GPIOAEN; return;}
    if (USBD_DP_PORT == GPIOB) {RCC->AHBENR |= RCC_AHBENR_GPIOBEN; return;}
    if (USBD_DP_PORT == GPIOC) {RCC->AHBENR |= RCC_AHBENR_GPIOCEN; return;}
    if (USBD_DP_PORT == GPIOD) {RCC->AHBENR |= RCC_AHBENR_GPIODEN; return;}
    #if defined(GPIOE)
    if (USBD_DP_PORT == GPIOE) {RCC->AHBENR |= RCC_AHBENR_GPIOEEN; return;}
    #endif
    #if defined(GPIOF)
    if (USBD_DP_PORT == GPIOF) {RCC->AHBENR |= RCC_AHBENR_GPIOFEN; return;}
    #endif
    #if defined(GPIOG)
    if (USBD_DP_PORT == GPIOG) {RCC->AHBENR |= RCC_AHBENR_GPIOGEN; return;}
    #endif
    #if defined(GPIOH)
    if (USBD_DP_PORT == GPIOH) {RCC->AHBENR |= RCC_AHBENR_GPIOHEN; return;}
    #endif
#endif
    return;
}

inline static volatile uint16_t *EPR(uint8_t ep) {
    return (uint16_t*)((ep & 0x07) * 4 + USB_BASE);
}

static uint16_t get_next_pma(uint16_t sz) {
    unsigned _result = USB_PMASIZE;
    for (int i = 0; i < 8; i++) {
        pma_table *tbl = EPT(i);
        if ((tbl->tx.addr) && (tbl->tx.addr < _result)) _result = tbl->tx.addr;
        if ((tbl->rx.addr) && (tbl->rx.addr < _result)) _result = tbl->rx.addr;
    }
    return (_result < (0x020 + sz)) ? 0 : (_result - sz);
}

uint32_t getinfo(void) {
    if (!(RCC->APB1ENR & RCC_APB1ENR_USBEN)) return STATUS_VAL(0);
#if defined(USBD_DP_PORT) && defined(USBD_DP_PIN)
    if (USBD_DP_PORT->IDR & _BV(USBD_DP_PIN)) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS);
    return STATUS_VAL(USBD_HW_ENABLED);
#else
    return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS);
#endif
}

void ep_setstall(uint8_t ep, bool stall) {
    volatile uint16_t *reg = EPR(ep);
    /* ISOCHRONOUS endpoint can't be stalled or unstalled */
    if (USB_EP_ISOCHRONOUS == (*reg & USB_EP_T_FIELD)) return;
    /* If it's an IN endpoint */
    if (ep & 0x80) {
        /* DISABLED endpoint can't be stalled or unstalled */
        if (USB_EP_TX_DIS == (*reg & USB_EPTX_STAT)) return;
        if (stall) {
            EP_TX_STALL(reg);
        } else {
            /* if it's a doublebuffered endpoint */
            if ((USB_EP_KIND | USB_EP_BULK) == (*reg & (USB_EP_T_FIELD | USB_EP_KIND))) {
                /* set endpoint to VALID and clear DTOG_TX & SWBUF_TX */
                EP_DTX_UNSTALL(reg);
            } else {
                /* set endpoint to NAKED and clear DTOG_TX */
                EP_TX_UNSTALL(reg);
            }
        }
    } else {
        if (USB_EP_RX_DIS == (*reg & USB_EPRX_STAT)) return;
        if (stall) {
            EP_RX_STALL(reg);
        } else {
            /* if it's a doublebuffered endpoint */
            if ((USB_EP_KIND | USB_EP_BULK) == (*reg & (USB_EP_T_FIELD | USB_EP_KIND))) {
                /* set endpoint to VALID, clear DTOG_RX, set SWBUF_RX */
                EP_DRX_UNSTALL(reg);
            } else {
                /* set endpoint to VALID and clear DTOG_RX */
                EP_RX_UNSTALL(reg);
            }
        }
    }
}

bool ep_isstalled(uint8_t ep) {
    if (ep & 0x80) {
        return (USB_EP_TX_STALL == (USB_EPTX_STAT & *EPR(ep)));
    } else {
        return (USB_EP_RX_STALL == (USB_EPRX_STAT & *EPR(ep)));
    }
}

uint8_t connect(bool connect) {
#if defined(USBD_DP_PORT) && defined(USBD_DP_PIN) && defined(STM32F3)
    uint32_t _t = USBD_DP_PORT->MODER & ~(0x03 << (2 * USBD_DP_PIN));
    if (connect) {
        _t |= (0x01 << (2 * USBD_DP_PIN));
        USBD_DP_PORT->BSRR = (0x0001 << USBD_DP_PIN);
    }
    USBD_DP_PORT->MODER = _t;
#elif defined(USBD_DP_PORT) && defined(USBD_DP_PIN) && defined(STM32F1)
#if (USBD_DP_PIN < 8)
    uint32_t _t = USBD_DP_PORT->CRL & ~(0x0F << (4 * USBD_DP_PIN));
    if (connect) {
        _t |= (0x02 << (4 * USBD_DP_PIN));
        USBD_DP_PORT->BSRR = (0x0001 << USBD_DP_PIN);
    } else {
        _t |= (0x04 << (4 * USBD_DP_PIN));
    }
    USBD_DP_PORT->CRL = _t;
#else
    uint32_t _t = USBD_DP_PORT->CRH & ~(0x0F << (4 * (USBD_DP_PIN - 8)));
    if (connect) {
        _t |= (0x02 << (4 * (USBD_DP_PIN - 8)));
        USBD_DP_PORT->BSRR = (0x0001 << USBD_DP_PIN);
    } else {
       _t |= (0x04 << (4 * (USBD_DP_PIN - 8)));
    }
    USBD_DP_PORT->CRH = _t;
#endif
#endif
    return usbd_lane_unk;
}

void enable(bool enable) {
    if (enable) {
        set_gpiox();
        RCC->APB1ENR  |= RCC_APB1ENR_USBEN;
        RCC->APB1RSTR |= RCC_APB1RSTR_USBRST;
        RCC->APB1RSTR &= ~RCC_APB1RSTR_USBRST;
        USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM | USB_CNTR_ERRM |
#if !defined(USBD_SOF_DISABLED)
        USB_CNTR_SOFM |
#endif
        USB_CNTR_SUSPM | USB_CNTR_WKUPM;
    } else if (RCC->APB1ENR & RCC_APB1ENR_USBEN) {
        RCC->APB1RSTR |= RCC_APB1RSTR_USBRST;
        RCC->APB1ENR &= ~RCC_APB1ENR_USBEN;
        /* disconnecting DP if configured */
        connect(0);
    }
}

void setaddr (uint8_t addr) {
    USB->DADDR = USB_DADDR_EF | addr;
}

bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) {
    volatile uint16_t *reg = EPR(ep);
    pma_table *tbl = EPT(ep);
    /* epsize should be 16-bit aligned */
    if (epsize & 0x01) epsize++;

    switch (eptype) {
    case USB_EPTYPE_CONTROL:
        *reg = USB_EP_CONTROL | (ep & 0x07);
        break;
    case USB_EPTYPE_ISOCHRONUS:
        *reg = USB_EP_ISOCHRONOUS | (ep & 0x07);
        break;
    case USB_EPTYPE_BULK:
        *reg = USB_EP_BULK | (ep & 0x07);
        break;
    case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF:
        *reg = USB_EP_BULK | USB_EP_KIND | (ep & 0x07);
        break;
    default:
        *reg = USB_EP_INTERRUPT | (ep & 0x07);
        break;
    }
    /* if it TX or CONTROL endpoint */
    if ((ep & 0x80) || (eptype == USB_EPTYPE_CONTROL)) {
        uint16_t _pma;
        _pma = get_next_pma(epsize);
        if (_pma == 0) return false;
        tbl->tx.addr = _pma;
        tbl->tx.cnt  = 0;
        if ((eptype == USB_EPTYPE_ISOCHRONUS) ||
            (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) {
            _pma = get_next_pma(epsize);
            if (_pma == 0) return false;
            tbl->tx1.addr = _pma;
            tbl->tx1.cnt  = 0;
            EP_DTX_UNSTALL(reg);
        } else {
            EP_TX_UNSTALL(reg);
        }
    }
    if (!(ep & 0x80)) {
        uint16_t _rxcnt;
        uint16_t _pma;
        if (epsize > 62) {
            if (epsize & 0x1F) {
                epsize &= ~0x1F;
                epsize += 0x20;
            }
            _rxcnt = 0x8000 - 0x20 + (epsize << 5);
        } else {
            _rxcnt = epsize << 9;
        }
        _pma = get_next_pma(epsize);
        if (_pma == 0) return false;
        tbl->rx.addr = _pma;
        tbl->rx.cnt  = _rxcnt;
        if ((eptype == USB_EPTYPE_ISOCHRONUS) ||
            (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) {
            _pma = get_next_pma(epsize);
            if (_pma == 0) return false;
            tbl->rx0.addr = _pma;
            tbl->rx0.cnt  = _rxcnt;
            EP_DRX_UNSTALL(reg);
        } else {
            EP_RX_UNSTALL(reg);
        }
    }
    return true;
}

void ep_deconfig(uint8_t ep) {
    pma_table *ept = EPT(ep);
    *EPR(ep) &= ~USB_EPREG_MASK;
    ept->rx.addr = 0;
    ept->rx.cnt  = 0;
    ept->tx.addr = 0;
    ept->tx.cnt  = 0;
}

static uint16_t pma_read (uint8_t *buf, uint16_t blen, pma_rec *rx) {
    uint16_t *pma = PMA(rx->addr);
    uint16_t rxcnt = rx->cnt & 0x03FF;
    rx->cnt &= ~0x3FF;
    if (blen > rxcnt) {
        blen = rxcnt;
    }
    rxcnt = blen;
    while (blen) {
        uint16_t _t = *pma;
        *buf++ = _t & 0xFF;
        if (--blen) {
            *buf++ = _t >> 8;
            pma += PMA_STEP;
            blen--;
        } else break;
    }
    return rxcnt;
}

int32_t ep_read(uint8_t ep, void *buf, uint16_t blen) {
    pma_table *tbl = EPT(ep);
    volatile uint16_t *reg = EPR(ep);
    switch (*reg & (USB_EPRX_STAT | USB_EP_T_FIELD | USB_EP_KIND)) {
    /* doublebuffered bulk endpoint */
    case (USB_EP_RX_VALID | USB_EP_BULK | USB_EP_KIND):
        /* switching SWBUF if EP is NAKED */
        switch (*reg & (USB_EP_DTOG_RX | USB_EP_SWBUF_RX)) {
        case 0:
        case (USB_EP_DTOG_RX | USB_EP_SWBUF_RX):
            *reg = (*reg & USB_EPREG_MASK) | USB_EP_SWBUF_RX;
            break;
        default:
            break;
        }
        if (*reg & USB_EP_SWBUF_RX) {
            return pma_read(buf, blen, &(tbl->rx1));
        } else {
            return pma_read(buf, blen, &(tbl->rx0));
        }
    /* isochronous endpoint */
    case (USB_EP_RX_VALID | USB_EP_ISOCHRONOUS):
        if (*reg & USB_EP_DTOG_RX) {
            return pma_read(buf, blen, &(tbl->rx1));
        } else {
            return pma_read(buf, blen, &(tbl->rx0));
        }
    /* regular endpoint */
    case (USB_EP_RX_NAK | USB_EP_BULK):
    case (USB_EP_RX_NAK | USB_EP_CONTROL):
    case (USB_EP_RX_NAK | USB_EP_INTERRUPT):
        {
        int32_t res = pma_read(buf, blen, &(tbl->rx));
        /* setting endpoint to VALID state */
        EP_RX_VALID(reg);
        return res;
        }
    /* invalid or not ready */
    default:
        return -1;
    }
}

static void pma_write(const uint8_t *buf, uint16_t blen, pma_rec *tx) {
    uint16_t *pma = PMA(tx->addr);
    tx->cnt = blen;
    while (blen > 1) {
        *pma = buf[1] << 8 | buf[0];
        pma += PMA_STEP;
        buf += 2;
        blen -= 2;
    }
    if (blen) *pma = *buf;
}

int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) {
    pma_table *tbl = EPT(ep);
    volatile uint16_t *reg = EPR(ep);
    switch (*reg & (USB_EPTX_STAT | USB_EP_T_FIELD | USB_EP_KIND)) {
    /* doublebuffered bulk endpoint */
    case (USB_EP_TX_NAK   | USB_EP_BULK | USB_EP_KIND):
        if (*reg & USB_EP_SWBUF_TX) {
            pma_write(buf, blen, &(tbl->tx1));
        } else {
            pma_write(buf, blen, &(tbl->tx0));
        }
        *reg = (*reg & USB_EPREG_MASK) | USB_EP_SWBUF_TX;
        break;
    /* isochronous endpoint */
    case (USB_EP_TX_VALID | USB_EP_ISOCHRONOUS):
        if (!(*reg & USB_EP_DTOG_TX)) {
            pma_write(buf, blen, &(tbl->tx1));
        } else {
            pma_write(buf, blen, &(tbl->tx0));
        }
        break;
    /* regular endpoint */
    case (USB_EP_TX_NAK | USB_EP_BULK):
    case (USB_EP_TX_NAK | USB_EP_CONTROL):
    case (USB_EP_TX_NAK | USB_EP_INTERRUPT):
        pma_write(buf, blen, &(tbl->tx));
        EP_TX_VALID(reg);
        break;
    /* invalid or not ready */
    default:
        return -1;
    }
    return blen;
}

uint16_t get_frame (void) {
    return USB->FNR & USB_FNR_FN;
}

void evt_poll(usbd_device *dev, usbd_evt_callback callback) {
    uint8_t _ev, _ep;
    uint16_t _istr = USB->ISTR;
    _ep = _istr & USB_ISTR_EP_ID;

    if (_istr & USB_ISTR_CTR) {
        volatile uint16_t *reg = EPR(_ep);
        if (*reg & USB_EP_CTR_TX) {
            *reg &= (USB_EPREG_MASK ^ USB_EP_CTR_TX);
            _ep |= 0x80;
            _ev = usbd_evt_eptx;
        } else {
            *reg &= (USB_EPREG_MASK ^ USB_EP_CTR_RX);
            _ev = (*reg & USB_EP_SETUP) ? usbd_evt_epsetup : usbd_evt_eprx;
        }
    } else if (_istr & USB_ISTR_RESET) {
        USB->ISTR &= ~USB_ISTR_RESET;
        USB->BTABLE = 0;
        for (int i = 0; i < 8; i++) {
            ep_deconfig(i);
        }
        _ev = usbd_evt_reset;
#if !defined(USBD_SOF_DISABLED)
    } else if (_istr & USB_ISTR_SOF) {
        _ev = usbd_evt_sof;
        USB->ISTR &= ~USB_ISTR_SOF;
#endif
    } else if (_istr & USB_ISTR_WKUP) {
        _ev = usbd_evt_wkup;
        USB->CNTR &= ~USB_CNTR_FSUSP;
        USB->ISTR &= ~USB_ISTR_WKUP;
    } else if (_istr & USB_ISTR_SUSP) {
        _ev = usbd_evt_susp;
        USB->CNTR |= USB_CNTR_FSUSP;
        USB->ISTR &= ~USB_ISTR_SUSP;
    } else if (_istr & USB_ISTR_ERR) {
        USB->ISTR &= ~USB_ISTR_ERR;
        _ev = usbd_evt_error;
    } else {
        return;
    }
    callback(dev, _ev, _ep);
}

static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) {
    for (int i = 0; i < 4 ; i++) {
        fnv ^= (data & 0xFF);
        fnv *= 16777619;
        data >>= 8;
    }
    return fnv;
}

uint16_t get_serialno_desc(void *buffer) {
    struct  usb_string_descriptor *dsc = buffer;
    uint16_t *str = dsc->wString;
    uint32_t fnv = 2166136261;
    fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00));
    fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04));
    fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x08));
    for (int i = 28; i >= 0; i -= 4 ) {
        uint16_t c = (fnv >> i) & 0x0F;
        c += (c < 10) ? '0' : ('A' - 10);
        *str++ = c;
    }
    dsc->bDescriptorType = USB_DTYPE_STRING;
    dsc->bLength = 18;
    return 18;
}

 __attribute__((externally_visible)) const struct usbd_driver usbd_devfs = {
    getinfo,
    enable,
    connect,
    setaddr,
    ep_config,
    ep_deconfig,
    ep_read,
    ep_write,
    ep_setstall,
    ep_isstalled,
    evt_poll,
    get_frame,
    get_serialno_desc,
};

#endif //USBD_STM32F103