/**
 * \file
 *
 * \brief SAM USB Driver.
 *
 * Copyright (C) 2014-2016 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \asf_license_stop
 *
 */
/*
 * Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
 */

#define DEVICE_MODE_ONLY true
#define SAMD11 DEVICE_MODE_ONLY

#ifndef ARM_MATH_CM4
#    define ARM_MATH_CM4
#endif

#include "compiler.h"
#undef LITTLE_ENDIAN // redefined in samd51j18a.h
#include "samd51j18a.h"
#include <stdbool.h>
#include <string.h>
#include "arm_math.h"
#include "status_codes.h"
#include "usb.h"

/** Fields definition from a LPM TOKEN  */
#define USB_LPM_ATTRIBUT_BLINKSTATE_MASK (0xF << 0)
#define USB_LPM_ATTRIBUT_HIRD_MASK (0xF << 4)
#define USB_LPM_ATTRIBUT_REMOTEWAKE_MASK (1 << 8)
#define USB_LPM_ATTRIBUT_BLINKSTATE(value) ((value & 0xF) << 0)
#define USB_LPM_ATTRIBUT_HIRD(value) ((value & 0xF) << 4)
#define USB_LPM_ATTRIBUT_REMOTEWAKE(value) ((value & 1) << 8)
#define USB_LPM_ATTRIBUT_BLINKSTATE_L1 USB_LPM_ATTRIBUT_BLINKSTATE(1)

/**
 * \brief Mask selecting the index part of an endpoint address
 */
#define USB_EP_ADDR_MASK 0x0f

/**
 * \brief Endpoint transfer direction is IN
 */
#define USB_EP_DIR_IN 0x80

/**
 * \brief Endpoint transfer direction is OUT
 */
#define USB_EP_DIR_OUT 0x00

/**
 * \name USB SRAM data containing pipe descriptor table
 * The content of the USB SRAM can be :
 * - modified by USB hardware interface to update pipe status.
 *   Thereby, it is read by software.
 * - modified by USB software to control pipe.
 *   Thereby, it is read by hardware.
 * This data section is volatile.
 *
 * @{
 */
COMPILER_PACK_SET(1)
COMPILER_WORD_ALIGNED
union {
    UsbDeviceDescriptor usb_endpoint_table[USB_EPT_NUM];
} usb_descriptor_table;
COMPILER_PACK_RESET()
/** @} */

/**
 * \brief Local USB module instance
 */
static struct usb_module *_usb_instances;

/* Device LPM callback variable */
static uint32_t device_callback_lpm_wakeup_enable;

/**
 * \brief Device endpoint callback parameter variable, used to transfer info to UDD wrapper layer
 */
static struct usb_endpoint_callback_parameter ep_callback_para;

/**
 * \internal USB Device IRQ Mask Bits Map
 */
static const uint16_t _usb_device_irq_bits[USB_DEVICE_CALLBACK_N] = {
    USB_DEVICE_INTFLAG_SOF, USB_DEVICE_INTFLAG_EORST, USB_DEVICE_INTFLAG_WAKEUP | USB_DEVICE_INTFLAG_EORSM | USB_DEVICE_INTFLAG_UPRSM, USB_DEVICE_INTFLAG_RAMACER, USB_DEVICE_INTFLAG_SUSPEND, USB_DEVICE_INTFLAG_LPMNYET, USB_DEVICE_INTFLAG_LPMSUSP,
};

/**
 * \internal USB Device IRQ Mask Bits Map
 */
static const uint8_t _usb_endpoint_irq_bits[USB_DEVICE_EP_CALLBACK_N] = {USB_DEVICE_EPINTFLAG_TRCPT_Msk, USB_DEVICE_EPINTFLAG_TRFAIL_Msk, USB_DEVICE_EPINTFLAG_RXSTP, USB_DEVICE_EPINTFLAG_STALL_Msk};

/**
 * \brief Registers a USB device callback
 *
 * Registers a callback function which is implemented by the user.
 *
 * \note The callback must be enabled by \ref usb_device_enable_callback,
 * in order for the interrupt handler to call it when the conditions for the
 * callback type is met.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 * \param[in]     callback_func Pointer to callback function
 *
 * \return Status of the registration operation.
 * \retval STATUS_OK    The callback was registered successfully.
 */
enum status_code usb_device_register_callback(struct usb_module *module_inst, enum usb_device_callback callback_type, usb_device_callback_t callback_func) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(callback_func);

    /* Register callback function */
    module_inst->device_callback[callback_type] = callback_func;

    /* Set the bit corresponding to the callback_type */
    module_inst->device_registered_callback_mask |= _usb_device_irq_bits[callback_type];

    return STATUS_OK;
}

/**
 * \brief Unregisters a USB device callback
 *
 * Unregisters an asynchronous callback implemented by the user. Removing it
 * from the internal callback registration table.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the de-registration operation.
 * \retval STATUS_OK    The callback was unregistered successfully.
 */
enum status_code usb_device_unregister_callback(struct usb_module *module_inst, enum usb_device_callback callback_type) {
    /* Sanity check arguments */
    Assert(module_inst);

    /* Unregister callback function */
    module_inst->device_callback[callback_type] = NULL;

    /* Clear the bit corresponding to the callback_type */
    module_inst->device_registered_callback_mask &= ~_usb_device_irq_bits[callback_type];

    return STATUS_OK;
}

/**
 * \brief Enables USB device callback generation for a given type.
 *
 * Enables asynchronous callbacks for a given logical type.
 * This must be called before USB device generate callback events.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback enable operation.
 * \retval STATUS_OK    The callback was enabled successfully.
 */
enum status_code usb_device_enable_callback(struct usb_module *module_inst, enum usb_device_callback callback_type) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(module_inst->hw);

    /* clear related flag */
    module_inst->hw->DEVICE.INTFLAG.reg = _usb_device_irq_bits[callback_type];

    /* Enable callback */
    module_inst->device_enabled_callback_mask |= _usb_device_irq_bits[callback_type];

    module_inst->hw->DEVICE.INTENSET.reg = _usb_device_irq_bits[callback_type];

    return STATUS_OK;
}

/**
 * \brief Disables USB device callback generation for a given type.
 *
 * Disables asynchronous callbacks for a given logical type.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback disable operation.
 * \retval STATUS_OK    The callback was disabled successfully.
 */
enum status_code usb_device_disable_callback(struct usb_module *module_inst, enum usb_device_callback callback_type) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(module_inst->hw);

    /* Disable callback */
    module_inst->device_enabled_callback_mask &= ~_usb_device_irq_bits[callback_type];

    module_inst->hw->DEVICE.INTENCLR.reg = _usb_device_irq_bits[callback_type];

    return STATUS_OK;
}

/**
 * \brief Registers a USB device endpoint callback
 *
 * Registers a callback function which is implemented by the user.
 *
 * \note The callback must be enabled by \ref usb_device_endpoint_enable_callback,
 * in order for the interrupt handler to call it when the conditions for the
 * callback type is met.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     ep_num        Endpoint to configure
 * \param[in]     callback_type Callback type given by an enum
 * \param[in]     callback_func Pointer to callback function
 *
 * \return Status of the registration operation.
 * \retval STATUS_OK    The callback was registered successfully.
 */
enum status_code usb_device_endpoint_register_callback(struct usb_module *module_inst, uint8_t ep_num, enum usb_device_endpoint_callback callback_type, usb_device_endpoint_callback_t callback_func) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(ep_num < USB_EPT_NUM);
    Assert(callback_func);

    /* Register callback function */
    module_inst->device_endpoint_callback[ep_num][callback_type] = callback_func;

    /* Set the bit corresponding to the callback_type */
    module_inst->device_endpoint_registered_callback_mask[ep_num] |= _usb_endpoint_irq_bits[callback_type];

    return STATUS_OK;
}

/**
 * \brief Unregisters a USB device endpoint callback
 *
 * Unregisters an callback implemented by the user. Removing it
 * from the internal callback registration table.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     ep_num        Endpoint to configure
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the de-registration operation.
 * \retval STATUS_OK    The callback was unregistered successfully.
 */
enum status_code usb_device_endpoint_unregister_callback(struct usb_module *module_inst, uint8_t ep_num, enum usb_device_endpoint_callback callback_type) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(ep_num < USB_EPT_NUM);

    /* Unregister callback function */
    module_inst->device_endpoint_callback[ep_num][callback_type] = NULL;

    /* Clear the bit corresponding to the callback_type */
    module_inst->device_endpoint_registered_callback_mask[ep_num] &= ~_usb_endpoint_irq_bits[callback_type];

    return STATUS_OK;
}

/**
 * \brief Enables USB device endpoint callback generation for a given type.
 *
 * Enables callbacks for a given logical type.
 * This must be called before USB device pipe generate callback events.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     ep            Endpoint to configure
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback enable operation.
 * \retval STATUS_OK    The callback was enabled successfully.
 */
enum status_code usb_device_endpoint_enable_callback(struct usb_module *module_inst, uint8_t ep, enum usb_device_endpoint_callback callback_type) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(module_inst->hw);

    uint8_t ep_num = ep & USB_EP_ADDR_MASK;
    Assert(ep_num < USB_EPT_NUM);

    /* Enable callback */
    module_inst->device_endpoint_enabled_callback_mask[ep_num] |= _usb_endpoint_irq_bits[callback_type];

    if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_TRCPT) {
        if (ep_num == 0) { // control endpoint
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 | USB_DEVICE_EPINTENSET_TRCPT1;
        } else if (ep & USB_EP_DIR_IN) {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT1;
        } else {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0;
        }
    }

    if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_TRFAIL) {
        if (ep_num == 0) { // control endpoint
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRFAIL0 | USB_DEVICE_EPINTENSET_TRFAIL1;
        } else if (ep & USB_EP_DIR_IN) {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRFAIL1;
        } else {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRFAIL0;
        }
    }

    if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_RXSTP) {
        module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_RXSTP;
    }

    if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_STALL) {
        if (ep & USB_EP_DIR_IN) {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_STALL1;
        } else {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENSET.reg = USB_DEVICE_EPINTENSET_STALL0;
        }
    }

    return STATUS_OK;
}

/**
 * \brief Disables USB device endpoint callback generation for a given type.
 *
 * Disables callbacks for a given logical type.
 *
 * \param[in]     module_inst   Pointer to USB software instance struct
 * \param[in]     ep            Endpoint to configure
 * \param[in]     callback_type Callback type given by an enum
 *
 * \return Status of the callback disable operation.
 * \retval STATUS_OK    The callback was disabled successfully.
 */
enum status_code usb_device_endpoint_disable_callback(struct usb_module *module_inst, uint8_t ep, enum usb_device_endpoint_callback callback_type) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(module_inst->hw);

    uint8_t ep_num = ep & USB_EP_ADDR_MASK;
    Assert(ep_num < USB_EPT_NUM);

    /* Enable callback */
    module_inst->device_endpoint_enabled_callback_mask[ep_num] &= ~_usb_endpoint_irq_bits[callback_type];

    if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_TRCPT) {
        if (ep_num == 0) { // control endpoint
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRCPT0 | USB_DEVICE_EPINTENCLR_TRCPT1;
        } else if (ep & USB_EP_DIR_IN) {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRCPT1;
        } else {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRCPT0;
        }
    }

    if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_TRFAIL) {
        if (ep_num == 0) { // control endpoint
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRFAIL0 | USB_DEVICE_EPINTENCLR_TRFAIL1;
        } else if (ep & USB_EP_DIR_IN) {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRFAIL1;
        } else {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRFAIL0;
        }
    }

    if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_RXSTP) {
        module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_RXSTP;
    }

    if (callback_type == USB_DEVICE_ENDPOINT_CALLBACK_STALL) {
        if (ep & USB_EP_DIR_IN) {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_STALL1;
        } else {
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_STALL0;
        }
    }

    return STATUS_OK;
}

/**
 * \brief Initializes an USB device endpoint configuration structure to defaults.
 *
 * Initializes a given USB device endpoint configuration structure to a
 * set of known default values. This function should be called on all new
 * instances of these configuration structures before being modified by the
 * user application.
 *
 * The default configuration is as follows:
 * \li endpoint address is 0
 * \li endpoint size is 8 bytes
 * \li auto_zlp is false
 * \li endpoint type is control
 *
 * \param[out] ep_config  Configuration structure to initialize to default values
 */
void usb_device_endpoint_get_config_defaults(struct usb_device_endpoint_config *ep_config) {
    /* Sanity check arguments */
    Assert(ep_config);

    /* Write default config to config struct */
    ep_config->ep_address = 0;
    ep_config->ep_size    = USB_ENDPOINT_8_BYTE;
    ep_config->auto_zlp   = false;
    ep_config->ep_type    = USB_DEVICE_ENDPOINT_TYPE_CONTROL;
}

/**
 * \brief Writes an USB device endpoint configuration to the hardware module.
 *
 * Writes out a given configuration of an USB device endpoint
 * configuration to the hardware module. If the pipe is already configured,
 * the new configuration will replace the existing one.
 *
 * \param[in] module_inst    Pointer to USB software instance struct
 * \param[in] ep_config      Configuration settings for the endpoint
 *
 * \return Status of the device endpoint configuration operation
 * \retval STATUS_OK         The device endpoint was configured successfully
 * \retval STATUS_ERR_DENIED The endpoint address is already configured
 */
enum status_code usb_device_endpoint_set_config(struct usb_module *module_inst, struct usb_device_endpoint_config *ep_config) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(ep_config);

    uint8_t ep_num  = ep_config->ep_address & USB_EP_ADDR_MASK;
    uint8_t ep_bank = (ep_config->ep_address & USB_EP_DIR_IN) ? 1 : 0;

    switch (ep_config->ep_type) {
        case USB_DEVICE_ENDPOINT_TYPE_DISABLE:
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE0(0) | USB_DEVICE_EPCFG_EPTYPE1(0);
            return STATUS_OK;

        case USB_DEVICE_ENDPOINT_TYPE_CONTROL:
            if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE0_Msk) == 0 && (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE1_Msk) == 0) {
                module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg       = USB_DEVICE_EPCFG_EPTYPE0(1) | USB_DEVICE_EPCFG_EPTYPE1(1);
                module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
                module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
            } else {
                return STATUS_ERR_DENIED;
            }
            if (true == ep_config->auto_zlp) {
                usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].PCKSIZE.reg |= USB_DEVICE_PCKSIZE_AUTO_ZLP;
                usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].PCKSIZE.reg |= USB_DEVICE_PCKSIZE_AUTO_ZLP;
            } else {
                usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].PCKSIZE.reg &= ~USB_DEVICE_PCKSIZE_AUTO_ZLP;
                usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].PCKSIZE.reg &= ~USB_DEVICE_PCKSIZE_AUTO_ZLP;
            }
            usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].PCKSIZE.bit.SIZE = ep_config->ep_size;
            usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].PCKSIZE.bit.SIZE = ep_config->ep_size;
            return STATUS_OK;

        case USB_DEVICE_ENDPOINT_TYPE_ISOCHRONOUS:
            if (ep_bank) {
                if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE1_Msk) == 0) {
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE1(2);
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
                } else {
                    return STATUS_ERR_DENIED;
                }
            } else {
                if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE0_Msk) == 0) {
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE0(2);
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
                } else {
                    return STATUS_ERR_DENIED;
                }
            }
            break;

        case USB_DEVICE_ENDPOINT_TYPE_BULK:
            if (ep_bank) {
                if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE1_Msk) == 0) {
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE1(3);
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
                } else {
                    return STATUS_ERR_DENIED;
                }
            } else {
                if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE0_Msk) == 0) {
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE0(3);
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
                } else {
                    return STATUS_ERR_DENIED;
                }
            }
            break;

        case USB_DEVICE_ENDPOINT_TYPE_INTERRUPT:
            if (ep_bank) {
                if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE1_Msk) == 0) {
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE1(4);
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
                } else {
                    return STATUS_ERR_DENIED;
                }
            } else {
                if ((module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg & USB_DEVICE_EPCFG_EPTYPE0_Msk) == 0) {
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.reg |= USB_DEVICE_EPCFG_EPTYPE0(4);
                    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
                } else {
                    return STATUS_ERR_DENIED;
                }
            }
            break;

        default:
            break;
    }

    usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[ep_bank].PCKSIZE.bit.SIZE = ep_config->ep_size;

    if (true == ep_config->auto_zlp) {
        usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[ep_bank].PCKSIZE.reg |= USB_DEVICE_PCKSIZE_AUTO_ZLP;
    } else {
        usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[ep_bank].PCKSIZE.reg &= ~USB_DEVICE_PCKSIZE_AUTO_ZLP;
    }

    return STATUS_OK;
}

/**
 * \brief Check if current endpoint is configured
 *
 * \param module_inst   Pointer to USB software instance struct
 * \param ep            Endpoint address (direction & number)
 *
 * \return \c true if endpoint is configured and ready to use
 */
bool usb_device_endpoint_is_configured(struct usb_module *module_inst, uint8_t ep) {
    uint8_t ep_num = ep & USB_EP_ADDR_MASK;
    uint8_t flag;

    if (ep & USB_EP_DIR_IN) {
        flag = (uint8_t)(module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.bit.EPTYPE1);
    } else {
        flag = (uint8_t)(module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.bit.EPTYPE0);
    }
    return ((enum usb_device_endpoint_type)(flag) != USB_DEVICE_ENDPOINT_TYPE_DISABLE);
}

/**
 * \brief Abort ongoing job on the endpoint
 *
 * \param module_inst Pointer to USB software instance struct
 * \param ep          Endpoint address
 */
void usb_device_endpoint_abort_job(struct usb_module *module_inst, uint8_t ep) {
    uint8_t ep_num;
    ep_num = ep & USB_EP_ADDR_MASK;

    // Stop transfer
    if (ep & USB_EP_DIR_IN) {
        module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
        // Eventually ack a transfer occur during abort
        module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT1;
    } else {
        module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
        // Eventually ack a transfer occur during abort
        module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT0;
    }
}

/**
 * \brief Check if endpoint is halted
 *
 * \param module_inst Pointer to USB software instance struct
 * \param ep          Endpoint address
 *
 * \return \c true if the endpoint is halted
 */
bool usb_device_endpoint_is_halted(struct usb_module *module_inst, uint8_t ep) {
    uint8_t ep_num = ep & USB_EP_ADDR_MASK;

    if (ep & USB_EP_DIR_IN) {
        return (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUS.reg & USB_DEVICE_EPSTATUSSET_STALLRQ1);
    } else {
        return (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUS.reg & USB_DEVICE_EPSTATUSSET_STALLRQ0);
    }
}

/**
 * \brief Halt the endpoint (send STALL)
 *
 * \param module_inst Pointer to USB software instance struct
 * \param ep          Endpoint address
 */
void usb_device_endpoint_set_halt(struct usb_module *module_inst, uint8_t ep) {
    uint8_t ep_num = ep & USB_EP_ADDR_MASK;

    // Stall endpoint
    if (ep & USB_EP_DIR_IN) {
        module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ1;
    } else {
        module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ0;
    }
}

/**
 * \brief Clear endpoint halt state
 *
 * \param module_inst Pointer to USB software instance struct
 * \param ep          Endpoint address
 */
void usb_device_endpoint_clear_halt(struct usb_module *module_inst, uint8_t ep) {
    uint8_t ep_num = ep & USB_EP_ADDR_MASK;

    if (ep & USB_EP_DIR_IN) {
        if (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUS.reg & USB_DEVICE_EPSTATUSSET_STALLRQ1) {
            // Remove stall request
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ1;
            if (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_STALL1) {
                module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL1;
                // The Stall has occurred, then reset data toggle
                module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSSET_DTGLIN;
            }
        }
    } else {
        if (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUS.reg & USB_DEVICE_EPSTATUSSET_STALLRQ0) {
            // Remove stall request
            module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ0;
            if (module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_STALL0) {
                module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL0;
                // The Stall has occurred, then reset data toggle
                module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSSET_DTGLOUT;
            }
        }
    }
}

/**
 * \brief Start write buffer job on a endpoint
 *
 * \param module_inst Pointer to USB module instance
 * \param ep_num      Endpoint number
 * \param pbuf        Pointer to buffer
 * \param buf_size    Size of buffer
 *
 * \return Status of procedure
 * \retval STATUS_OK Job started successfully
 * \retval STATUS_ERR_DENIED Endpoint is not ready
 */
enum status_code usb_device_endpoint_write_buffer_job(struct usb_module *module_inst, uint8_t ep_num, uint8_t *pbuf, uint32_t buf_size) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(module_inst->hw);
    Assert(ep_num < USB_EPT_NUM);

    uint8_t flag;
    flag = (uint8_t)(module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.bit.EPTYPE1);
    if ((enum usb_device_endpoint_type)(flag) == USB_DEVICE_ENDPOINT_TYPE_DISABLE) {
        return STATUS_ERR_DENIED;
    };

    /* get endpoint configuration from setting register */
    usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].ADDR.reg                      = (uint32_t)pbuf;
    usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].PCKSIZE.bit.MULTI_PACKET_SIZE = 0;
    usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[1].PCKSIZE.bit.BYTE_COUNT        = buf_size;
    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSSET.reg                                  = USB_DEVICE_EPSTATUSSET_BK1RDY;

    return STATUS_OK;
}

/**
 * \brief Start read buffer job on a endpoint
 *
 * \param module_inst Pointer to USB module instance
 * \param ep_num      Endpoint number
 * \param pbuf        Pointer to buffer
 * \param buf_size    Size of buffer
 *
 * \return Status of procedure
 * \retval STATUS_OK Job started successfully
 * \retval STATUS_ERR_DENIED Endpoint is not ready
 */
enum status_code usb_device_endpoint_read_buffer_job(struct usb_module *module_inst, uint8_t ep_num, uint8_t *pbuf, uint32_t buf_size) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(module_inst->hw);
    Assert(ep_num < USB_EPT_NUM);

    uint8_t flag;
    flag = (uint8_t)(module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPCFG.bit.EPTYPE0);
    if ((enum usb_device_endpoint_type)(flag) == USB_DEVICE_ENDPOINT_TYPE_DISABLE) {
        return STATUS_ERR_DENIED;
    };

    /* get endpoint configuration from setting register */
    usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].ADDR.reg                      = (uint32_t)pbuf;
    usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = buf_size;
    usb_descriptor_table.usb_endpoint_table[ep_num].DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT        = 0;
    module_inst->hw->DEVICE.DeviceEndpoint[ep_num].EPSTATUSCLR.reg                                  = USB_DEVICE_EPSTATUSCLR_BK0RDY;

    return STATUS_OK;
}

/**
 * \brief Start setup packet read job on a endpoint
 *
 * \param module_inst Pointer to USB device module instance
 * \param pbuf        Pointer to buffer
 *
 * \return Status of procedure
 * \retval STATUS_OK Job started successfully
 * \retval STATUS_ERR_DENIED Endpoint is not ready
 */
enum status_code usb_device_endpoint_setup_buffer_job(struct usb_module *module_inst, uint8_t *pbuf) {
    /* Sanity check arguments */
    Assert(module_inst);
    Assert(module_inst->hw);

    /* get endpoint configuration from setting register */
    usb_descriptor_table.usb_endpoint_table[0].DeviceDescBank[0].ADDR.reg                      = (uint32_t)pbuf;
    usb_descriptor_table.usb_endpoint_table[0].DeviceDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = 8;
    usb_descriptor_table.usb_endpoint_table[0].DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT        = 0;
    module_inst->hw->DEVICE.DeviceEndpoint[0].EPSTATUSCLR.reg                                  = USB_DEVICE_EPSTATUSCLR_BK0RDY;

    return STATUS_OK;
}

static void _usb_device_interrupt_handler(void) {
    uint16_t ep_inst;
    uint16_t flags, flags_run;
    ep_inst = _usb_instances->hw->DEVICE.EPINTSMRY.reg;

    /* device interrupt */
    if (0 == ep_inst) {
        int i;

        /* get interrupt flags */
        flags     = _usb_instances->hw->DEVICE.INTFLAG.reg;
        flags_run = flags & _usb_instances->device_enabled_callback_mask & _usb_instances->device_registered_callback_mask;

        for (i = 0; i < USB_DEVICE_CALLBACK_N; i++) {
            if (flags & _usb_device_irq_bits[i]) {
                _usb_instances->hw->DEVICE.INTFLAG.reg = _usb_device_irq_bits[i];
            }
            if (flags_run & _usb_device_irq_bits[i]) {
                if (i == USB_DEVICE_CALLBACK_LPMSUSP) {
                    device_callback_lpm_wakeup_enable = usb_descriptor_table.usb_endpoint_table[0].DeviceDescBank[0].EXTREG.bit.VARIABLE & USB_LPM_ATTRIBUT_REMOTEWAKE_MASK;
                }
                (_usb_instances->device_callback[i])(_usb_instances, &device_callback_lpm_wakeup_enable);
            }
        }

    } else {
        /* endpoint interrupt */

        for (uint8_t i = 0; i < USB_EPT_NUM; i++) {
            if (ep_inst & (1 << i)) {
                flags     = _usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg;
                flags_run = flags & _usb_instances->device_endpoint_enabled_callback_mask[i] & _usb_instances->device_endpoint_registered_callback_mask[i];

                // endpoint transfer stall interrupt
                if (flags & USB_DEVICE_EPINTFLAG_STALL_Msk) {
                    if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_STALL1) {
                        _usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL1;
                        ep_callback_para.endpoint_address                          = USB_EP_DIR_IN | i;
                    } else if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_STALL0) {
                        _usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL0;
                        ep_callback_para.endpoint_address                          = USB_EP_DIR_OUT | i;
                    }

                    if (flags_run & USB_DEVICE_EPINTFLAG_STALL_Msk) {
                        (_usb_instances->device_endpoint_callback[i][USB_DEVICE_ENDPOINT_CALLBACK_STALL])(_usb_instances, &ep_callback_para);
                    }
                    return;
                }

                // endpoint received setup interrupt
                if (flags & USB_DEVICE_EPINTFLAG_RXSTP) {
                    _usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_RXSTP;
                    if (_usb_instances->device_endpoint_enabled_callback_mask[i] & _usb_endpoint_irq_bits[USB_DEVICE_ENDPOINT_CALLBACK_RXSTP]) {
                        ep_callback_para.received_bytes = (uint16_t)(usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT);
                        (_usb_instances->device_endpoint_callback[i][USB_DEVICE_ENDPOINT_CALLBACK_RXSTP])(_usb_instances, &ep_callback_para);
                    }
                    return;
                }

                // endpoint transfer complete interrupt
                if (flags & USB_DEVICE_EPINTFLAG_TRCPT_Msk) {
                    if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRCPT1) {
                        _usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT1;
                        ep_callback_para.endpoint_address                          = USB_EP_DIR_IN | i;
                        ep_callback_para.sent_bytes                                = (uint16_t)(usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[1].PCKSIZE.bit.BYTE_COUNT);

                    } else if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRCPT0) {
                        _usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT0;
                        ep_callback_para.endpoint_address                          = USB_EP_DIR_OUT | i;
                        ep_callback_para.received_bytes                            = (uint16_t)(usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT);
                        ep_callback_para.out_buffer_size                           = (uint16_t)(usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE);
                    }
                    if (flags_run & USB_DEVICE_EPINTFLAG_TRCPT_Msk) {
                        (_usb_instances->device_endpoint_callback[i][USB_DEVICE_ENDPOINT_CALLBACK_TRCPT])(_usb_instances, &ep_callback_para);
                    }
                    return;
                }

                // endpoint transfer fail interrupt
                if (flags & USB_DEVICE_EPINTFLAG_TRFAIL_Msk) {
                    if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRFAIL1) {
                        _usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRFAIL1;
                        if (usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[1].STATUS_BK.reg & USB_DEVICE_STATUS_BK_ERRORFLOW) {
                            usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[1].STATUS_BK.reg &= ~USB_DEVICE_STATUS_BK_ERRORFLOW;
                        }
                        ep_callback_para.endpoint_address = USB_EP_DIR_IN | i;
                        if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRCPT1) {
                            return;
                        }
                    } else if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRFAIL0) {
                        _usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRFAIL0;
                        if (usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[0].STATUS_BK.reg & USB_DEVICE_STATUS_BK_ERRORFLOW) {
                            usb_descriptor_table.usb_endpoint_table[i].DeviceDescBank[0].STATUS_BK.reg &= ~USB_DEVICE_STATUS_BK_ERRORFLOW;
                        }
                        ep_callback_para.endpoint_address = USB_EP_DIR_OUT | i;
                        if (_usb_instances->hw->DEVICE.DeviceEndpoint[i].EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRCPT0) {
                            return;
                        }
                    }

                    if (flags_run & USB_DEVICE_EPINTFLAG_TRFAIL_Msk) {
                        (_usb_instances->device_endpoint_callback[i][USB_DEVICE_ENDPOINT_CALLBACK_TRFAIL])(_usb_instances, &ep_callback_para);
                    }
                    return;
                }
            }
        }
    }
}

/**
 * \brief Enable the USB module peripheral
 *
 * \param module_inst pointer to USB module instance
 */
void usb_enable(struct usb_module *module_inst) {
    Assert(module_inst);
    Assert(module_inst->hw);

    module_inst->hw->DEVICE.CTRLA.reg |= USB_CTRLA_ENABLE;
    while (module_inst->hw->DEVICE.SYNCBUSY.reg == USB_SYNCBUSY_ENABLE)
        ;
}

/**
 * \brief Disable the USB module peripheral
 *
 * \param module_inst pointer to USB module instance
 */
void usb_disable(struct usb_module *module_inst) {
    Assert(module_inst);
    Assert(module_inst->hw);

    module_inst->hw->DEVICE.INTENCLR.reg = USB_DEVICE_INTENCLR_MASK;
    module_inst->hw->DEVICE.INTFLAG.reg  = USB_DEVICE_INTFLAG_MASK;
    module_inst->hw->DEVICE.CTRLA.reg &= ~USB_CTRLA_ENABLE;
    while (module_inst->hw->DEVICE.SYNCBUSY.reg == USB_SYNCBUSY_ENABLE)
        ;
}

/**
 * \brief Interrupt handler for the USB module.
 */
void USB_0_Handler(void) {
    if (_usb_instances->hw->DEVICE.CTRLA.bit.MODE) {
    } else {
        /*device mode ISR */
        _usb_device_interrupt_handler();
    }
}

void USB_1_Handler(void) {
    _usb_device_interrupt_handler();
}

void USB_2_Handler(void) {
    _usb_device_interrupt_handler();
}

void USB_3_Handler(void) {
    _usb_device_interrupt_handler();
}

/**
 * \brief Get the default USB module settings
 *
 * \param[out] module_config  Configuration structure to initialize to default values
 */
void usb_get_config_defaults(struct usb_config *module_config) {
    Assert(module_config);

    /* Sanity check arguments */
    Assert(module_config);
    /* Write default configuration to config struct */
    module_config->select_host_mode = 0;
    module_config->run_in_standby   = 1;
    module_config->source_generator = 0;
    module_config->speed_mode       = USB_SPEED_FULL;
}

#define NVM_USB_PAD_TRANSN_POS 45
#define NVM_USB_PAD_TRANSN_SIZE 5
#define NVM_USB_PAD_TRANSP_POS 50
#define NVM_USB_PAD_TRANSP_SIZE 5
#define NVM_USB_PAD_TRIM_POS 55
#define NVM_USB_PAD_TRIM_SIZE 3

/**
 * \brief Initializes USB module instance
 *
 * Enables the clock and initializes the USB module, based on the given
 * configuration values.
 *
 * \param[in,out] module_inst   Pointer to the software module instance struct
 * \param[in]     hw            Pointer to the USB hardware module
 * \param[in]     module_config Pointer to the USB configuration options struct
 *
 * \return Status of the initialization procedure.
 *
 * \retval STATUS_OK           The module was initialized successfully
 */

#define GCLK_USB 10

enum status_code usb_init(struct usb_module *module_inst, Usb *const hw, struct usb_config *module_config) {
    /* Sanity check arguments */
    Assert(hw);
    Assert(module_inst);
    Assert(module_config);

    uint32_t i, j;
    uint32_t pad_transn, pad_transp, pad_trim;

    Gclk *   pgclk = GCLK;
    Mclk *   pmclk = MCLK;
    Port *   pport = PORT;
    Oscctrl *posc  = OSCCTRL;

    _usb_instances = module_inst;

    /* Associate the software module instance with the hardware module */
    module_inst->hw = hw;

    // setup peripheral and synchronous bus clocks to USB
    pmclk->AHBMASK.bit.USB_  = 1;
    pmclk->APBBMASK.bit.USB_ = 1;

    /* Set up the USB DP/DN pins */
    pport->Group[0].PMUX[12].reg          = 0x77; // PA24, PA25, function column H for USB D-, D+
    pport->Group[0].PINCFG[24].bit.PMUXEN = 1;
    pport->Group[0].PINCFG[25].bit.PMUXEN = 1;
    pport->Group[1].PMUX[11].bit.PMUXE    = 7; // PB22, function column H for USB SOF_1KHz output
    pport->Group[1].PINCFG[22].bit.PMUXEN = 1;

    // configure and enable DFLL for USB clock recovery mode at 48MHz
    posc->DFLLCTRLA.bit.ENABLE = 0;
    while (posc->DFLLSYNC.bit.ENABLE)
        ;
    while (posc->DFLLSYNC.bit.DFLLCTRLB)
        ;
    posc->DFLLCTRLB.bit.USBCRM = 1;
    while (posc->DFLLSYNC.bit.DFLLCTRLB)
        ;
    posc->DFLLCTRLB.bit.MODE = 1;
    while (posc->DFLLSYNC.bit.DFLLCTRLB)
        ;
    posc->DFLLCTRLB.bit.QLDIS = 0;
    while (posc->DFLLSYNC.bit.DFLLCTRLB)
        ;
    posc->DFLLCTRLB.bit.CCDIS = 1;
    posc->DFLLMUL.bit.MUL     = 0xbb80; // 4800 x 1KHz
    while (posc->DFLLSYNC.bit.DFLLMUL)
        ;
    posc->DFLLCTRLA.bit.ENABLE = 1;
    while (posc->DFLLSYNC.bit.ENABLE)
        ;

    /* Setup clock for module */
    pgclk->PCHCTRL[GCLK_USB].bit.GEN  = 0;
    pgclk->PCHCTRL[GCLK_USB].bit.CHEN = 1;

    /* Reset */
    hw->DEVICE.CTRLA.bit.SWRST = 1;
    while (hw->DEVICE.SYNCBUSY.bit.SWRST) {
        /* Sync wait */
    }

    /* Change QOS values to have the best performance and correct USB behaviour */
    USB->DEVICE.QOSCTRL.bit.CQOS = 2;
    USB->DEVICE.QOSCTRL.bit.DQOS = 2;

    /* Load Pad Calibration */

    pad_transn = (USB_FUSES_TRANSN_ADDR >> USB_FUSES_TRANSN_Pos) & USB_FUSES_TRANSN_Msk;
    if (pad_transn == 0x1F) {
        pad_transn = 5;
    }

    hw->DEVICE.PADCAL.bit.TRANSN = pad_transn;

    pad_transp = (USB_FUSES_TRANSP_ADDR >> USB_FUSES_TRANSP_Pos) & USB_FUSES_TRANSP_Msk;
    if (pad_transp == 0x1F) {
        pad_transp = 29;
    }

    hw->DEVICE.PADCAL.bit.TRANSP = pad_transp;

    pad_trim = (USB_FUSES_TRIM_ADDR >> USB_FUSES_TRIM_Pos) & USB_FUSES_TRIM_Msk;
    if (pad_trim == 0x07) {
        pad_trim = 3;
    }

    hw->DEVICE.PADCAL.bit.TRIM = pad_trim;

    /* Set the configuration */
    hw->DEVICE.CTRLA.bit.MODE     = module_config->select_host_mode;
    hw->DEVICE.CTRLA.bit.RUNSTDBY = module_config->run_in_standby;
    hw->DEVICE.DESCADD.reg        = (uint32_t)(&usb_descriptor_table.usb_endpoint_table[0]);
    if (USB_SPEED_FULL == module_config->speed_mode) {
        module_inst->hw->DEVICE.CTRLB.bit.SPDCONF = USB_DEVICE_CTRLB_SPDCONF_FS_Val;
    } else if (USB_SPEED_LOW == module_config->speed_mode) {
        module_inst->hw->DEVICE.CTRLB.bit.SPDCONF = USB_DEVICE_CTRLB_SPDCONF_LS_Val;
    }

    memset((uint8_t *)(&usb_descriptor_table.usb_endpoint_table[0]), 0, sizeof(usb_descriptor_table.usb_endpoint_table));

    /*  device callback related */
    for (i = 0; i < USB_DEVICE_CALLBACK_N; i++) {
        module_inst->device_callback[i] = NULL;
    }
    for (i = 0; i < USB_EPT_NUM; i++) {
        for (j = 0; j < USB_DEVICE_EP_CALLBACK_N; j++) {
            module_inst->device_endpoint_callback[i][j] = NULL;
        }
    }
    module_inst->device_registered_callback_mask = 0;
    module_inst->device_enabled_callback_mask    = 0;
    for (j = 0; j < USB_EPT_NUM; j++) {
        module_inst->device_endpoint_registered_callback_mask[j] = 0;
        module_inst->device_endpoint_enabled_callback_mask[j]    = 0;
    }

    /* Enable interrupts for this USB module */
    NVIC_EnableIRQ(USB_0_IRQn);
    NVIC_EnableIRQ(USB_2_IRQn);
    NVIC_EnableIRQ(USB_3_IRQn);

    return STATUS_OK;
}
