aboutsummaryrefslogtreecommitdiffstats
path: root/hal/src/hal_usb_device.c
diff options
context:
space:
mode:
Diffstat (limited to 'hal/src/hal_usb_device.c')
-rw-r--r--hal/src/hal_usb_device.c592
1 files changed, 592 insertions, 0 deletions
diff --git a/hal/src/hal_usb_device.c b/hal/src/hal_usb_device.c
new file mode 100644
index 0000000..aa80ede
--- /dev/null
+++ b/hal/src/hal_usb_device.c
@@ -0,0 +1,592 @@
+/**
+ * \file
+ *
+ * \brief SAM USB device HAL
+ *
+ * Copyright (c) 2015-2018 Microchip Technology Inc. and its subsidiaries.
+ *
+ * \asf_license_start
+ *
+ * \page License
+ *
+ * Subject to your compliance with these terms, you may use Microchip
+ * software and any derivatives exclusively with Microchip products.
+ * It is your responsibility to comply with third party license terms applicable
+ * to your use of third party software (including open source software) that
+ * may accompany Microchip software.
+ *
+ * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES,
+ * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
+ * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
+ * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE
+ * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL
+ * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
+ * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE
+ * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT
+ * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY
+ * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
+ * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
+ *
+ * \asf_license_stop
+ *
+ */
+
+#include "hal_usb_device.h"
+#include "hal_atomic.h"
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** USB device HAL driver version. */
+#define USB_D_VERSION 0x00000001u
+
+/**
+ * Endpoint callbacks for data transfer.
+ */
+struct usb_d_ep_callbacks {
+ /** Callback that is invoked when setup packet is received. */
+ usb_d_ep_cb_setup_t req;
+ /** Callback invoked when buffer is done, but last packet is full size
+ * packet without ZLP. Return \c true if new transfer has been submitted.
+ */
+ usb_d_ep_cb_more_t more;
+ /** Callback invoked when transfer is finished/halted/aborted or error
+ * occurs.
+ */
+ usb_d_ep_cb_xfer_t xfer;
+};
+
+/**
+ * Endpoint transfer descriptor header.
+ */
+struct usb_ep_xfer_hdr {
+ /** Transfer type, reuse \ref usb_ep_type. */
+ uint8_t type;
+ /** Endpoint address. */
+ uint8_t ep;
+ /** Endpoint state. */
+ uint8_t state;
+ /** Last status code. */
+ uint8_t status;
+};
+
+/**
+ * Transfer descriptor.
+ */
+struct usb_ep_xfer {
+ /** General transfer descriptor. */
+ struct usb_ep_xfer_hdr hdr;
+ /** Pointer to data buffer. */
+ uint8_t *buf;
+ /** Transfer size. */
+ uint32_t size;
+ /** Control request packet. */
+ uint8_t req[8];
+};
+
+/**
+ * USB device endpoint descriptor.
+ */
+struct usb_d_ep {
+ /** On-going transfer on the endpoint. */
+ struct usb_ep_xfer xfer;
+ /** Endpoint callbacks. */
+ struct usb_d_ep_callbacks callbacks;
+};
+
+/**
+ * USB device HAL driver descriptor.
+ */
+struct usb_d_descriptor {
+ /** USB device endpoints. */
+ struct usb_d_ep ep[CONF_USB_D_NUM_EP_SP];
+};
+
+/** The USB HAL driver descriptor instance. */
+static struct usb_d_descriptor usb_d_inst;
+
+/** \brief Find the endpoint.
+ * \param[in] ep Endpoint address.
+ * \return Index of endpoint descriptor.
+ * \retval >=0 The index.
+ * \retval <0 Not found (endpoint is not initialized).
+ */
+static int8_t _usb_d_find_ep(const uint8_t ep)
+{
+ int8_t i;
+ for (i = 0; i < CONF_USB_D_NUM_EP_SP; i++) {
+ if (usb_d_inst.ep[i].xfer.hdr.ep == ep) {
+ return i;
+ }
+ if (usb_d_inst.ep[i].xfer.hdr.type == USB_EP_XTYPE_CTRL
+ && (ep & USB_EP_N_MASK) == usb_d_inst.ep[i].xfer.hdr.ep) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/**
+ * \brief Start transactions
+ * \param[in] ep Endpoint address.
+ * \param[in] dir Endpoint transfer direction.
+ * \param[in] buf Pointer to transfer buffer.
+ * \param[in] size Transfer size.
+ * \param[in] zlp Auto append ZLP for IN, or wait ZLP for OUT.
+ */
+static inline int32_t _usb_d_trans(const uint8_t ep, const bool dir, const uint8_t *buf, const uint32_t size,
+ const uint8_t zlp)
+{
+ struct usb_d_transfer trans
+ = {(uint8_t *)buf, size, dir ? (uint8_t)(ep | USB_EP_DIR) : (uint8_t)(ep & USB_EP_N_MASK), zlp};
+
+ return _usb_d_dev_ep_trans(&trans);
+}
+
+/**
+ * \brief Dummy callback that returns false
+ * \param[in] unused0 Unused parameter.
+ * \param[in] unused1 Unused parameter.
+ * \param[in] unused2 Unused parameter.
+ * \return Always \c false.
+ */
+static bool usb_d_dummy_cb_false(uint32_t unused0, uint32_t unused1, uint32_t unused2)
+{
+ (void)unused0;
+ (void)unused1;
+ (void)unused2;
+ return false;
+}
+
+/**
+ * \brief Callback invoked when SETUP packet is ready
+ * \param[in] ep Endpoint number with transfer direction on bit 8.
+ */
+static void usb_d_cb_trans_setup(const uint8_t ep)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep *ept = &usb_d_inst.ep[ep_index];
+ uint8_t * req = ept->xfer.req;
+
+ uint8_t n = _usb_d_dev_ep_read_req(ep, req);
+ if (n != 8) {
+ _usb_d_dev_ep_stall(ep, USB_EP_STALL_SET);
+ _usb_d_dev_ep_stall(ep | USB_EP_DIR, USB_EP_STALL_SET);
+ return;
+ }
+
+ _usb_d_dev_ep_stall(ep, USB_EP_STALL_CLR);
+ _usb_d_dev_ep_stall(ep | USB_EP_DIR, USB_EP_STALL_CLR);
+ ept->xfer.hdr.state = USB_EP_S_IDLE;
+ if (!ept->callbacks.req(ep, req)) {
+ ept->xfer.hdr.state = USB_EP_S_HALTED;
+ _usb_d_dev_ep_stall(ep, USB_EP_STALL_SET);
+ _usb_d_dev_ep_stall(ep | USB_EP_DIR, USB_EP_STALL_SET);
+ }
+}
+
+/**
+ * \brief Callback invoked when request more data
+ * \param[in] ep Endpoint number with transfer direction on bit 8.
+ * \param[in] transfered Number of bytes transfered.
+ */
+static bool usb_d_cb_trans_more(const uint8_t ep, const uint32_t transfered)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep *ept = &usb_d_inst.ep[ep_index];
+ if (ept->xfer.hdr.state == USB_EP_S_X_DATA) {
+ return ept->callbacks.more(ep, transfered);
+ }
+ return false;
+}
+
+/**
+ * \brief Handles the case that control endpoint transactions are done
+ * \param[in,out] ept Pointer to endpoint information.
+ */
+static inline void usb_d_ctrl_trans_done(struct usb_d_ep *ept)
+{
+ uint8_t state = ept->xfer.hdr.state;
+ bool req_dir = USB_GET_bmRequestType(ept->xfer.req) & USB_REQ_TYPE_IN;
+
+ if (state == USB_EP_S_X_DATA) {
+ /* Data stage -> Status stage */
+ bool err = ept->callbacks.xfer(ept->xfer.hdr.ep, USB_XFER_DATA, ept->xfer.req);
+ if (err) {
+ ept->xfer.hdr.state = USB_EP_S_HALTED;
+ ept->xfer.hdr.status = USB_XFER_HALT;
+ _usb_d_dev_ep_stall(req_dir ? ept->xfer.hdr.ep : (ept->xfer.hdr.ep | USB_EP_DIR), USB_EP_STALL_SET);
+ } else {
+ ept->xfer.hdr.state = USB_EP_S_X_STATUS;
+ _usb_d_trans(ept->xfer.hdr.ep, !req_dir, NULL, 0, 1);
+ }
+ } else {
+ /* Status stage done */
+ ept->callbacks.xfer(ept->xfer.hdr.ep, USB_XFER_DONE, ept->xfer.req);
+ ept->xfer.hdr.state = USB_EP_S_X_SETUP;
+ }
+}
+
+/**
+ * Callback when USB transactions are finished.
+ */
+static void _usb_d_cb_trans_done(const uint8_t ep, const int32_t code, const uint32_t transferred)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep *ept = &usb_d_inst.ep[ep_index];
+
+ if (code == USB_TRANS_DONE) {
+ ept->xfer.hdr.status = USB_XFER_DONE;
+ if (ept->xfer.hdr.type == USB_EP_XTYPE_CTRL) {
+ usb_d_ctrl_trans_done(ept);
+ return;
+ }
+ ept->xfer.hdr.state = USB_EP_S_IDLE;
+ } else if (code == USB_TRANS_STALL) {
+ ept->xfer.hdr.status = USB_XFER_HALT;
+ if (ept->xfer.hdr.type == USB_EP_XTYPE_CTRL) {
+ ept->xfer.hdr.state = USB_EP_S_X_SETUP;
+ _usb_d_dev_ep_stall(ep, USB_EP_STALL_CLR);
+ } else {
+ ept->xfer.hdr.state = USB_EP_S_HALTED;
+ }
+ } else if (code == USB_TRANS_ABORT) {
+ ept->xfer.hdr.status = USB_XFER_ABORT;
+ if (ept->xfer.hdr.type == USB_EP_XTYPE_CTRL) {
+ ept->xfer.hdr.state = USB_EP_S_X_SETUP;
+ return;
+ }
+ ept->xfer.hdr.state = USB_EP_S_IDLE;
+ } else if (code == USB_TRANS_RESET) {
+ ept->xfer.hdr.state = USB_EP_S_DISABLED;
+ ept->xfer.hdr.status = USB_XFER_RESET;
+ } else {
+ ept->xfer.hdr.state = USB_EP_S_ERROR;
+ ept->xfer.hdr.status = USB_XFER_ERROR;
+ }
+
+ ept->callbacks.xfer(ep, (enum usb_xfer_code)ept->xfer.hdr.status, (void *)transferred);
+}
+
+int32_t usb_d_init(void)
+{
+ int32_t rc = _usb_d_dev_init();
+ uint8_t i;
+ if (rc < 0) {
+ return rc;
+ }
+ memset(usb_d_inst.ep, 0x00, sizeof(struct usb_d_ep) * CONF_USB_D_NUM_EP_SP);
+ for (i = 0; i < CONF_USB_D_NUM_EP_SP; i++) {
+ usb_d_inst.ep[i].xfer.hdr.ep = 0xFF;
+ usb_d_inst.ep[i].callbacks.req = (usb_d_ep_cb_setup_t)usb_d_dummy_cb_false;
+ usb_d_inst.ep[i].callbacks.more = (usb_d_ep_cb_more_t)usb_d_dummy_cb_false;
+ usb_d_inst.ep[i].callbacks.xfer = (usb_d_ep_cb_xfer_t)usb_d_dummy_cb_false;
+ }
+ /* Handles device driver endpoint callbacks to build transfer. */
+ _usb_d_dev_register_ep_callback(USB_D_DEV_EP_CB_SETUP, (FUNC_PTR)usb_d_cb_trans_setup);
+ _usb_d_dev_register_ep_callback(USB_D_DEV_EP_CB_MORE, (FUNC_PTR)usb_d_cb_trans_more);
+ _usb_d_dev_register_ep_callback(USB_D_DEV_EP_CB_DONE, (FUNC_PTR)_usb_d_cb_trans_done);
+ return ERR_NONE;
+}
+
+void usb_d_deinit(void)
+{
+ _usb_d_dev_deinit();
+}
+
+void usb_d_register_callback(const enum usb_d_cb_type type, const FUNC_PTR func)
+{
+ /* Directly uses device driver callback. */
+ _usb_d_dev_register_callback(type, func);
+}
+
+int32_t usb_d_enable(void)
+{
+ return _usb_d_dev_enable();
+}
+
+void usb_d_disable(void)
+{
+ _usb_d_dev_disable();
+}
+
+void usb_d_attach(void)
+{
+ _usb_d_dev_attach();
+}
+
+void usb_d_detach(void)
+{
+ _usb_d_dev_detach();
+}
+
+enum usb_speed usb_d_get_speed(void)
+{
+ return _usb_d_dev_get_speed();
+}
+
+uint16_t usb_d_get_frame_num(void)
+{
+ return _usb_d_dev_get_frame_n();
+}
+
+uint8_t usb_d_get_uframe_num(void)
+{
+ return _usb_d_dev_get_uframe_n();
+}
+
+void usb_d_set_address(const uint8_t addr)
+{
+ _usb_d_dev_set_address(addr);
+}
+
+void usb_d_send_remotewakeup(void)
+{
+ _usb_d_dev_send_remotewakeup();
+}
+
+int32_t usb_d_ep0_init(const uint8_t max_pkt_size)
+{
+ return usb_d_ep_init(0, USB_EP_XTYPE_CTRL, max_pkt_size);
+}
+
+int32_t usb_d_ep_init(const uint8_t ep, const uint8_t attr, const uint16_t max_pkt_size)
+{
+ int32_t rc;
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep *ept = &usb_d_inst.ep[ep_index];
+ if (ep_index >= 0) {
+ return -USB_ERR_REDO;
+ } else {
+ ep_index = _usb_d_find_ep(0xFF);
+ if (ep_index < 0) {
+ return -USB_ERR_ALLOC_FAIL;
+ }
+ ept = &usb_d_inst.ep[ep_index];
+ }
+ rc = _usb_d_dev_ep_init(ep, attr, max_pkt_size);
+ if (rc < 0) {
+ return rc;
+ }
+ ept->xfer.hdr.ep = ep;
+ ept->xfer.hdr.type = attr & USB_EP_XTYPE_MASK;
+ return ERR_NONE;
+}
+
+void usb_d_ep_deinit(const uint8_t ep)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep *ept = &usb_d_inst.ep[ep_index];
+ if (ep_index < 0) {
+ return;
+ }
+ _usb_d_dev_ep_deinit(ep);
+ ept->xfer.hdr.ep = 0xFF;
+}
+
+int32_t usb_d_ep_enable(const uint8_t ep)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep *ept = &usb_d_inst.ep[ep_index];
+ int32_t rc;
+ if (ep_index < 0) {
+ return -USB_ERR_PARAM;
+ }
+ ept->xfer.hdr.state = (ept->xfer.hdr.type == USB_EP_XTYPE_CTRL) ? USB_EP_S_X_SETUP : USB_EP_S_IDLE;
+ rc = _usb_d_dev_ep_enable(ep);
+ if (rc < 0) {
+ ept->xfer.hdr.state = USB_EP_S_DISABLED;
+ }
+ return rc;
+}
+
+void usb_d_ep_disable(const uint8_t ep)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep *ept = &usb_d_inst.ep[ep_index];
+ if (ep_index < 0) {
+ return;
+ }
+ _usb_d_dev_ep_disable(ep);
+ ept->xfer.hdr.state = USB_EP_S_DISABLED;
+}
+
+uint8_t *usb_d_ep_get_req(const uint8_t ep)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ if (ep_index < 0) {
+ return NULL;
+ }
+ return usb_d_inst.ep[ep_index].xfer.req;
+}
+
+int32_t usb_d_ep_transfer(const struct usb_d_transfer *xfer)
+{
+ int8_t ep_index = _usb_d_find_ep(xfer->ep);
+ struct usb_d_ep * ept = &usb_d_inst.ep[ep_index];
+ bool dir = USB_EP_GET_DIR(xfer->ep), zlp = xfer->zlp;
+ uint32_t len = xfer->size;
+ int32_t rc;
+ volatile uint8_t state;
+ volatile hal_atomic_t flags;
+
+ if (ep_index < 0) {
+ return -USB_ERR_PARAM;
+ }
+
+ atomic_enter_critical(&flags);
+ state = ept->xfer.hdr.state;
+ if (state == USB_EP_S_IDLE) {
+ ept->xfer.hdr.state = USB_EP_S_X_DATA;
+ atomic_leave_critical(&flags);
+ } else {
+ atomic_leave_critical(&flags);
+ switch (state) {
+ case USB_EP_S_HALTED:
+ return USB_HALTED;
+ case USB_EP_S_ERROR:
+ return -USB_ERROR;
+ case USB_EP_S_DISABLED:
+ return -USB_ERR_FUNC;
+ default: /* USB_EP_S_X_xxxx */
+ return USB_BUSY;
+ }
+ }
+
+ if (ept->xfer.hdr.type == USB_EP_XTYPE_CTRL) {
+ uint16_t req_len = USB_GET_wLength(ept->xfer.req);
+ /* SETUP without data: ZLP IN as status. */
+ if (req_len == 0) {
+ dir = true;
+ len = 0;
+ zlp = true;
+ ept->xfer.hdr.state = USB_EP_S_X_STATUS;
+ } else {
+ dir = (USB_GET_bmRequestType(ept->xfer.req) & USB_REQ_TYPE_IN);
+ /* Data length not exceed requested. */
+ if (len > req_len) {
+ len = req_len;
+ }
+ if (dir) {
+ /* Setup -> In */
+ zlp = (req_len > len);
+ } else {
+ zlp = false;
+ }
+ }
+ }
+
+ rc = _usb_d_trans(xfer->ep, dir, xfer->buf, len, zlp);
+ return rc;
+}
+
+void usb_d_ep_abort(const uint8_t ep)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep *ept = &usb_d_inst.ep[ep_index];
+ if (ep_index < 0) {
+ return;
+ }
+ _usb_d_dev_ep_abort(ep);
+ ept->xfer.hdr.state = USB_EP_S_IDLE;
+ ept->xfer.hdr.status = USB_XFER_ABORT;
+}
+
+int32_t usb_d_ep_get_status(const uint8_t ep, struct usb_d_ep_status *stat)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep * ept = &usb_d_inst.ep[ep_index];
+ struct usb_d_trans_status tmp;
+ uint8_t state = ept->xfer.hdr.state;
+ if (ep_index < 0) {
+ return -USB_ERR_PARAM;
+ }
+ if (stat) {
+ /* Check transaction status if transferring data. */
+ _usb_d_dev_ep_get_status(ep, &tmp);
+ stat->ep = ep;
+ stat->state = state;
+ stat->code = ept->xfer.hdr.status;
+ stat->count = tmp.count;
+ stat->size = tmp.size;
+ }
+ switch (state) {
+ case USB_EP_S_IDLE:
+ return USB_OK;
+ case USB_EP_S_HALTED:
+ return USB_HALTED;
+ case USB_EP_S_ERROR:
+ return -USB_ERROR;
+ case USB_EP_S_DISABLED:
+ return -USB_ERR_FUNC;
+ default:
+ /* Busy */
+ return USB_BUSY;
+ }
+}
+
+static inline int32_t _usb_d_ep_halt_clr(const uint8_t ep)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep *ept = &usb_d_inst.ep[ep_index];
+ int32_t rc;
+ if (ep_index < 0) {
+ return -USB_ERR_PARAM;
+ }
+ if (_usb_d_dev_ep_stall(ep, USB_EP_STALL_GET)) {
+ rc = _usb_d_dev_ep_stall(ep, USB_EP_STALL_CLR);
+ if (rc < 0) {
+ return rc;
+ }
+ ept->xfer.hdr.state = USB_EP_S_IDLE;
+ ept->xfer.hdr.status = USB_XFER_UNHALT;
+ ept->callbacks.xfer(ep, USB_XFER_UNHALT, NULL);
+ }
+ return ERR_NONE;
+}
+
+int32_t usb_d_ep_halt(const uint8_t ep, const enum usb_ep_halt_ctrl ctrl)
+{
+ if (ctrl == USB_EP_HALT_CLR) {
+ return _usb_d_ep_halt_clr(ep);
+ } else if (ctrl == USB_EP_HALT_SET) {
+ return _usb_d_dev_ep_stall(ep, USB_EP_STALL_SET);
+ } else {
+ return _usb_d_dev_ep_stall(ep, USB_EP_STALL_GET);
+ }
+}
+
+void usb_d_ep_register_callback(const uint8_t ep, const enum usb_d_ep_cb_type type, const FUNC_PTR func)
+{
+ int8_t ep_index = _usb_d_find_ep(ep);
+ struct usb_d_ep *ept = &usb_d_inst.ep[ep_index];
+ FUNC_PTR f = func ? (FUNC_PTR)func : (FUNC_PTR)usb_d_dummy_cb_false;
+ if (ep_index < 0) {
+ return;
+ }
+ switch (type) {
+ case USB_D_EP_CB_SETUP:
+ ept->callbacks.req = (usb_d_ep_cb_setup_t)f;
+ break;
+ case USB_D_EP_CB_MORE:
+ ept->callbacks.more = (usb_d_ep_cb_more_t)f;
+ break;
+ case USB_D_EP_CB_XFER:
+ ept->callbacks.xfer = (usb_d_ep_cb_xfer_t)f;
+ break;
+ default:
+ break;
+ }
+}
+
+uint32_t usb_d_get_version(void)
+{
+ return USB_D_VERSION;
+}
+
+#ifdef __cplusplus
+}
+#endif