Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions drivers/usb/uhc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
# SPDX-License-Identifier: Apache-2.0

zephyr_library()
zephyr_library_include_directories(${ZEPHYR_BASE}/drivers/usb/common/)

zephyr_library_sources(uhc_common.c)
zephyr_library_sources_ifdef(CONFIG_UHC_DWC2 uhc_dwc2.c)
zephyr_library_sources_ifdef(CONFIG_UHC_MAX3421E uhc_max3421e.c)
zephyr_library_sources_ifdef(CONFIG_UHC_VIRTUAL uhc_virtual.c)
zephyr_library_sources_ifdef(CONFIG_UHC_NXP_EHCI uhc_mcux_common.c uhc_mcux_ehci.c)
Expand Down
1 change: 1 addition & 0 deletions drivers/usb/uhc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ module = UHC_DRIVER
module-str = uhc drv
source "subsys/logging/Kconfig.template.log_config"

source "drivers/usb/uhc/Kconfig.dwc2"
source "drivers/usb/uhc/Kconfig.max3421e"
source "drivers/usb/uhc/Kconfig.virtual"
source "drivers/usb/uhc/Kconfig.mcux"
Expand Down
26 changes: 26 additions & 0 deletions drivers/usb/uhc/Kconfig.dwc2
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright (c) 2026 Roman Leonov <jam_roma@yahoo.com>
# SPDX-License-Identifier: Apache-2.0

config UHC_DWC2
bool "DWC2 USB host controller driver"
depends on DT_HAS_SNPS_DWC2_ENABLED
default y
select EVENTS
help
DWC2 USB host controller driver.

if UHC_DWC2

config UHC_DWC2_STACK_SIZE
int "UHC DWC2 driver thread stack size"
default 512
help
DWC2 driver thread stack size.

config UHC_DWC2_THREAD_PRIORITY
int "UHC DWC2 driver thread priority"
default 8
help
DWC2 driver thread priority.

endif # UHC_DWC2
247 changes: 247 additions & 0 deletions drivers/usb/uhc/uhc_dwc2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/*
* Copyright (c) 2026 Roman Leonov <jam_roma@yahoo.com>
*
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT snps_dwc2

#include "uhc_common.h"
#include "uhc_dwc2.h"

#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/usb/uhc.h>
#include <zephyr/usb/usb_ch9.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL);

#define UHC_DWC2_CHAN_REG(base, chan_idx) \
((struct usb_dwc2_host_chan *)(((mem_addr_t)(base)) + USB_DWC2_HCCHAR(chan_idx)))

struct uhc_dwc2_data {
struct k_thread thread;
struct k_event event;
};

static void uhc_dwc2_isr_handler(const struct device *dev)
{
/* TODO: Interrupt handling */
}

static void uhc_dwc2_thread(void *arg0, void *arg1, void *arg2)
{
const struct device *const dev = (const struct device *)arg0;
struct uhc_dwc2_data *const priv = uhc_get_private(dev);
uint32_t event;

while (true) {
event = k_event_wait_safe(&priv->event, UINT32_MAX, false, K_FOREVER);

uhc_lock_internal(dev, K_FOREVER);

/* TODO: handle port and channel events */
(void) event;

uhc_unlock_internal(dev);
}
}

static int uhc_dwc2_lock(const struct device *const dev)
{
struct uhc_data *data = dev->data;

return k_mutex_lock(&data->mutex, K_FOREVER);
}

static int uhc_dwc2_unlock(const struct device *const dev)
{
struct uhc_data *data = dev->data;

return k_mutex_unlock(&data->mutex);
}

static int uhc_dwc2_sof_enable(const struct device *const dev)
{
LOG_WRN("%s has not been implemented", __func__);

return -ENOSYS;
}

static int uhc_dwc2_bus_suspend(const struct device *const dev)
{
LOG_WRN("%s has not been implemented", __func__);

return -ENOSYS;
}

static int uhc_dwc2_bus_reset(const struct device *const dev)
{
LOG_WRN("%s has not been implemented", __func__);

return -ENOSYS;
}

static int uhc_dwc2_bus_resume(const struct device *const dev)
{
LOG_WRN("%s has not been implemented", __func__);

return -ENOSYS;
}

static int uhc_dwc2_enqueue(const struct device *const dev, struct uhc_transfer *const xfer)
{
LOG_WRN("%s has not been implemented", __func__);

return -ENOSYS;
}

static int uhc_dwc2_dequeue(const struct device *const dev, struct uhc_transfer *const xfer)
{
LOG_WRN("%s has not been implemented", __func__);

return -ENOSYS;
}

static int uhc_dwc2_preinit(const struct device *const dev)
{
const struct uhc_dwc2_config *const config = dev->config;
struct uhc_dwc2_data *const priv = uhc_get_private(dev);
struct uhc_data *const data = dev->data;

/* Initialize the private data structure */
memset(priv, 0, sizeof(struct uhc_dwc2_data));
k_mutex_init(&data->mutex);
k_event_init(&priv->event);

uhc_dwc2_quirk_caps(dev);

/*
* TODO:
* use devicetree to get GHWCFGn values and use them to determine the
* number and type of configured endpoints in the hardware as in udc?
*/

k_thread_create(&priv->thread,
config->stack,
config->stack_size,
uhc_dwc2_thread,
(void *)dev, NULL, NULL,
K_PRIO_COOP(CONFIG_UHC_DWC2_THREAD_PRIORITY),
K_ESSENTIAL,
K_NO_WAIT);
k_thread_name_set(&priv->thread, dev->name);

return 0;
}

static int uhc_dwc2_init(const struct device *const dev)
{
LOG_WRN("%s has not been implemented", __func__);

return -ENOSYS;
}

static int uhc_dwc2_enable(const struct device *const dev)
{
LOG_WRN("%s has not been implemented", __func__);

return -ENOSYS;
}

static int uhc_dwc2_disable(const struct device *const dev)
{
LOG_WRN("%s has not been implemented", __func__);

return -ENOSYS;
}

static int uhc_dwc2_shutdown(const struct device *const dev)
{
LOG_WRN("%s has not been implemented", __func__);
Comment thread
roma-jam marked this conversation as resolved.

return -ENOSYS;
}

/*
* Device Definition and Initialization
*/
static const struct uhc_api uhc_dwc2_api = {
/* Common */
.lock = uhc_dwc2_lock,
.unlock = uhc_dwc2_unlock,
.init = uhc_dwc2_init,
.enable = uhc_dwc2_enable,
.disable = uhc_dwc2_disable,
.shutdown = uhc_dwc2_shutdown,
/* Bus related */
.bus_reset = uhc_dwc2_bus_reset,
.sof_enable = uhc_dwc2_sof_enable,
.bus_suspend = uhc_dwc2_bus_suspend,
.bus_resume = uhc_dwc2_bus_resume,
/* EP related */
.ep_enqueue = uhc_dwc2_enqueue,
.ep_dequeue = uhc_dwc2_dequeue,
};

#define UHC_DWC2_DT_INST_REG_ADDR(n) \
COND_CODE_1(DT_NUM_REGS(DT_DRV_INST(n)), \
(DT_INST_REG_ADDR(n)), \
(DT_INST_REG_ADDR_BY_NAME(n, core)))

#if !defined(UHC_DWC2_IRQ_DT_INST_DEFINE)
#define UHC_DWC2_IRQ_FLAGS_TYPE0(n) 0
#define UHC_DWC2_IRQ_FLAGS_TYPE1(n) DT_INST_IRQ(n, type)
#define DW_IRQ_FLAGS(n) \
_CONCAT(UHC_DWC2_IRQ_FLAGS_TYPE, DT_INST_IRQ_HAS_CELL(n, type))(n)

#define UHC_DWC2_IRQ_DT_INST_DEFINE(n) \
static void uhc_dwc2_irq_enable_func_##n(const struct device *dev) \
{ \
IRQ_CONNECT(DT_INST_IRQN(n), \
DT_INST_IRQ(n, priority), \
uhc_dwc2_isr_handler, \
DEVICE_DT_INST_GET(n), \
DW_IRQ_FLAGS(n)); \
\
irq_enable(DT_INST_IRQN(n)); \
} \
\
static void uhc_dwc2_irq_disable_func_##n(const struct device *dev) \
{ \
irq_disable(DT_INST_IRQN(n)); \
}
#endif

/* Multi-instance device definition for DWC2 host controller */
#define UHC_DWC2_DEVICE_DEFINE(n) \
\
K_THREAD_STACK_DEFINE(uhc_dwc2_stack_##n, \
CONFIG_UHC_DWC2_STACK_SIZE); \
\
UHC_DWC2_IRQ_DT_INST_DEFINE(n) \
\
static struct uhc_dwc2_data uhc_dwc2_priv_##n = { 0 }; \
\
static struct uhc_data uhc_data_##n = { \
.mutex = Z_MUTEX_INITIALIZER(uhc_data_##n.mutex), \
.priv = &uhc_dwc2_priv_##n, \
}; \
\
static const struct uhc_dwc2_config uhc_dwc2_config_##n = { \
.base = (struct usb_dwc2_reg *)UHC_DWC2_DT_INST_REG_ADDR(n), \
.quirks = UHC_DWC2_VENDOR_QUIRK_GET(n), \
.stack = uhc_dwc2_stack_##n, \
.stack_size = K_THREAD_STACK_SIZEOF(uhc_dwc2_stack_##n), \
.irq_enable_func = uhc_dwc2_irq_enable_func_##n, \
.irq_disable_func = uhc_dwc2_irq_disable_func_##n, \
}; \
\
DEVICE_DT_INST_DEFINE(n, uhc_dwc2_preinit, NULL, \
&uhc_data_##n, &uhc_dwc2_config_##n, \
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&uhc_dwc2_api);

DT_INST_FOREACH_STATUS_OKAY(UHC_DWC2_DEVICE_DEFINE)
95 changes: 95 additions & 0 deletions drivers/usb/uhc/uhc_dwc2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2026 Roman Leonov <jam_roma@yahoo.com>
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_DRIVERS_USB_UHC_DWC2_H
#define ZEPHYR_DRIVERS_USB_UHC_DWC2_H

#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/drivers/usb/uhc.h>
#include <usb_dwc2_hw.h>

/* Vendor quirks per driver instance */
struct uhc_dwc2_vendor_quirks {
/* Called at the beginning of uhc_dwc2_init() */
int (*init)(const struct device *dev);
/* Called on uhc_dwc2_enable() before the controller is initialized */
int (*pre_enable)(const struct device *dev);
/* Called on uhc_dwc2_enable() after the controller is initialized */
int (*post_enable)(const struct device *dev);
/* Called at the end of uhc_dwc2_disable() */
int (*disable)(const struct device *dev);
/* Called at the end of uhc_dwc2_shutdown() */
int (*shutdown)(const struct device *dev);
/* Called at the end of IRQ handling */
int (*irq_clear)(const struct device *dev);
/* Called on driver pre-init */
int (*caps)(const struct device *dev);
/* Called on PHY pre-select */
int (*phy_pre_select)(const struct device *dev);
/* Called on PHY post-select and core reset */
int (*phy_post_select)(const struct device *dev);
/* Called while waiting for bits that require PHY to be clocked */
int (*is_phy_clk_off)(const struct device *dev);
/* PHY get clock */
int (*get_phy_clk)(const struct device *dev);
/* Called after hibernation entry sequence */
int (*post_hibernation_entry)(const struct device *dev);
/* Called before hibernation exit sequence */
int (*pre_hibernation_exit)(const struct device *dev);
};

/* Driver configuration per instance */
struct uhc_dwc2_config {
/* Pointer to base address of DWC_OTG registers */
struct usb_dwc2_reg *const base;
/* Pointer to vendor quirks or NULL */
const struct uhc_dwc2_vendor_quirks *const quirks;
/* Pointer to the stack used by the driver thread */
k_thread_stack_t *stack;
/* Size of the stack used by the driver thread */
size_t stack_size;

void (*irq_enable_func)(const struct device *dev);
void (*irq_disable_func)(const struct device *dev);
};

#include "uhc_dwc2_vendor_quirks.h"

#define UHC_DWC2_VENDOR_QUIRK_GET(n) \
COND_CODE_1(DT_NODE_VENDOR_HAS_IDX(DT_DRV_INST(n), 1), \
(&uhc_dwc2_vendor_quirks_##n), \
(NULL))

#define DWC2_QUIRK_FUNC_DEFINE(fname) \
static inline int uhc_dwc2_quirk_##fname(const struct device *dev) \
{ \
const struct uhc_dwc2_config *const config = dev->config; \
const struct uhc_dwc2_vendor_quirks *const quirks = \
COND_CODE_1(IS_EQ(DT_NUM_INST_STATUS_OKAY(snps_dwc2), 1), \
(UHC_DWC2_VENDOR_QUIRK_GET(0); ARG_UNUSED(config);), \
(config->quirks;)) \
if (quirks != NULL && quirks->fname != NULL) { \
return quirks->fname(dev); \
} \
return 0; \
}

DWC2_QUIRK_FUNC_DEFINE(init)
DWC2_QUIRK_FUNC_DEFINE(pre_enable)
DWC2_QUIRK_FUNC_DEFINE(post_enable)
DWC2_QUIRK_FUNC_DEFINE(disable)
DWC2_QUIRK_FUNC_DEFINE(shutdown)
DWC2_QUIRK_FUNC_DEFINE(irq_clear)
DWC2_QUIRK_FUNC_DEFINE(caps)
DWC2_QUIRK_FUNC_DEFINE(phy_pre_select)
DWC2_QUIRK_FUNC_DEFINE(phy_post_select)
DWC2_QUIRK_FUNC_DEFINE(is_phy_clk_off)
DWC2_QUIRK_FUNC_DEFINE(get_phy_clk)
DWC2_QUIRK_FUNC_DEFINE(post_hibernation_entry)
DWC2_QUIRK_FUNC_DEFINE(pre_hibernation_exit)

#endif /* ZEPHYR_DRIVERS_USB_UHC_DWC2_H */
Loading