drivers: serial: esp32_usb: Add USB serial / JTAG interface

This peripheral combines a hardware-based USB CDC ACM serial interface
and a JTAG interface.

It is present in the ESP32-C3.

Signed-off-by: Martin Jäger <martin@libre.solar>
diff --git a/drivers/serial/CMakeLists.txt b/drivers/serial/CMakeLists.txt
index bca08a5..9f1dba6 100644
--- a/drivers/serial/CMakeLists.txt
+++ b/drivers/serial/CMakeLists.txt
@@ -9,6 +9,7 @@
 zephyr_library_sources_ifdef(CONFIG_UART_CC32XX uart_cc32xx.c)
 zephyr_library_sources_ifdef(CONFIG_UART_CMSDK_APB uart_cmsdk_apb.c)
 zephyr_library_sources_ifdef(CONFIG_UART_ESP32 uart_esp32.c)
+zephyr_library_sources_ifdef(CONFIG_SERIAL_ESP32_USB serial_esp32_usb.c)
 zephyr_library_sources_ifdef(CONFIG_UART_SIFIVE uart_sifive.c)
 zephyr_library_sources_ifdef(CONFIG_UART_GECKO uart_gecko.c)
 zephyr_library_sources_ifdef(CONFIG_LEUART_GECKO leuart_gecko.c)
diff --git a/drivers/serial/Kconfig.esp32 b/drivers/serial/Kconfig.esp32
index cce0e35..edc8de5 100644
--- a/drivers/serial/Kconfig.esp32
+++ b/drivers/serial/Kconfig.esp32
@@ -12,3 +12,15 @@
 	depends on SOC_ESP32 || SOC_ESP32S2 || SOC_ESP32C3
 	help
 	  Enable the ESP32 UART.
+
+DT_COMPAT_ESP32_USB_SERIAL := espressif,esp32-usb-serial
+
+config SERIAL_ESP32_USB
+	bool "ESP32 USB serial driver"
+	default $(dt_compat_enabled,$(DT_COMPAT_ESP32_USB_SERIAL))
+	select SERIAL_HAS_DRIVER
+	select SERIAL_SUPPORT_INTERRUPT
+	depends on SOC_ESP32C3
+	help
+	  Enable the ESP32 USB serial driver integrated in the JTAG
+	  interface.
diff --git a/drivers/serial/serial_esp32_usb.c b/drivers/serial/serial_esp32_usb.c
new file mode 100644
index 0000000..a56076f
--- /dev/null
+++ b/drivers/serial/serial_esp32_usb.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2022 Libre Solar Technologies GmbH
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define DT_DRV_COMPAT espressif_esp32_usb_serial
+
+#include "stubs.h"
+#include <hal/usb_serial_jtag_ll.h>
+
+#include <soc/uart_reg.h>
+#include <device.h>
+#include <soc.h>
+#include <zephyr/drivers/uart.h>
+#include <zephyr/drivers/interrupt_controller/intc_esp32c3.h>
+#include <zephyr/drivers/clock_control.h>
+#include <errno.h>
+#include <zephyr/sys/util.h>
+#include <esp_attr.h>
+
+#define ISR_HANDLER isr_handler_t
+
+struct serial_esp32_usb_pin {
+	const char *gpio_name;
+	int pin;
+};
+
+struct serial_esp32_usb_config {
+	const struct device *clock_dev;
+	const clock_control_subsys_t clock_subsys;
+	uint8_t uart_num;
+	int irq_source;
+};
+
+struct serial_esp32_usb_data {
+	struct uart_config serial_config;
+#ifdef CONFIG_UART_INTERRUPT_DRIVEN
+	uart_irq_callback_user_data_t irq_cb;
+	void *irq_cb_data;
+#endif
+	int irq_line;
+};
+
+#define SERIAL_FIFO_LIMIT (USB_SERIAL_JTAG_PACKET_SZ_BYTES)
+
+#ifdef CONFIG_UART_INTERRUPT_DRIVEN
+static void serial_esp32_usb_isr(void *arg);
+#endif
+
+static int serial_esp32_usb_poll_in(const struct device *dev, unsigned char *p_char)
+{
+	struct serial_esp32_usb_data *data = dev->data;
+
+	if (!usb_serial_jtag_ll_rxfifo_data_available()) {
+		return -1;
+	}
+
+	usb_serial_jtag_ll_read_rxfifo(p_char, 1);
+
+	return 0;
+}
+
+static void serial_esp32_usb_poll_out(const struct device *dev, unsigned char c)
+{
+	ARG_UNUSED(dev);
+
+	/* Wait for space in FIFO */
+	while (usb_serial_jtag_ll_txfifo_writable() == 0) {
+		;
+	}
+
+	/* Send a character */
+	usb_serial_jtag_ll_write_txfifo(&c, 1);
+
+	usb_serial_jtag_ll_txfifo_flush();
+}
+
+static int serial_esp32_usb_err_check(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+
+	return 0;
+}
+
+#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
+static int serial_esp32_usb_config_get(const struct device *dev, struct uart_config *cfg)
+{
+	return -ENOTSUP;
+}
+#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */
+
+static int serial_esp32_usb_configure(const struct device *dev, const struct uart_config *cfg)
+{
+	const struct serial_esp32_usb_config *config = dev->config;
+	struct serial_esp32_usb_data *data = dev->data;
+
+	clock_control_on(config->clock_dev, config->clock_subsys);
+
+	return 0;
+}
+
+static int serial_esp32_usb_init(const struct device *dev)
+{
+	struct serial_esp32_usb_data *data = dev->data;
+	int ret = serial_esp32_usb_configure(dev, &data->serial_config);
+
+#ifdef CONFIG_UART_INTERRUPT_DRIVEN
+	const struct serial_esp32_usb_config *config = dev->config;
+
+	data->irq_line = esp_intr_alloc(config->irq_source, 0, (ISR_HANDLER)serial_esp32_usb_isr,
+					(void *)dev, NULL);
+#endif
+	return ret;
+}
+
+#ifdef CONFIG_UART_INTERRUPT_DRIVEN
+
+static int serial_esp32_usb_fifo_fill(const struct device *dev, const uint8_t *tx_data, int len)
+{
+	ARG_UNUSED(dev);
+
+	int ret = usb_serial_jtag_ll_write_txfifo(tx_data, len);
+
+	usb_serial_jtag_ll_txfifo_flush();
+
+	return ret;
+}
+
+static int serial_esp32_usb_fifo_read(const struct device *dev, uint8_t *rx_data, const int len)
+{
+	ARG_UNUSED(dev);
+
+	return usb_serial_jtag_ll_read_rxfifo(rx_data, len);
+}
+
+static void serial_esp32_usb_irq_tx_enable(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+
+	usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
+	usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
+}
+
+static void serial_esp32_usb_irq_tx_disable(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+
+	usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
+}
+
+static int serial_esp32_usb_irq_tx_ready(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+
+	return (usb_serial_jtag_ll_txfifo_writable() &&
+		usb_serial_jtag_ll_get_intr_ena_status() & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
+}
+
+static void serial_esp32_usb_irq_rx_enable(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+
+	usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT);
+	usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT);
+}
+
+static void serial_esp32_usb_irq_rx_disable(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+
+	usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT);
+}
+
+static int serial_esp32_usb_irq_tx_complete(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+
+	return usb_serial_jtag_ll_txfifo_writable();
+}
+
+static int serial_esp32_usb_irq_rx_ready(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+
+	return usb_serial_jtag_ll_rxfifo_data_available();
+}
+
+static void serial_esp32_usb_irq_err_enable(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+}
+
+static void serial_esp32_usb_irq_err_disable(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+}
+
+static int serial_esp32_usb_irq_is_pending(const struct device *dev)
+{
+	return serial_esp32_usb_irq_rx_ready(dev) || serial_esp32_usb_irq_tx_ready(dev);
+}
+
+static int serial_esp32_usb_irq_update(const struct device *dev)
+{
+	ARG_UNUSED(dev);
+
+	usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT);
+	usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
+
+	return 1;
+}
+
+static void serial_esp32_usb_irq_callback_set(const struct device *dev,
+					      uart_irq_callback_user_data_t cb, void *cb_data)
+{
+	struct serial_esp32_usb_data *data = dev->data;
+
+	data->irq_cb = cb;
+	data->irq_cb_data = cb_data;
+}
+
+static void serial_esp32_usb_isr(void *arg)
+{
+	const struct device *dev = (const struct device *)arg;
+	struct serial_esp32_usb_data *data = dev->data;
+	uint32_t uart_intr_status = usb_serial_jtag_ll_get_intsts_mask();
+
+	if (uart_intr_status == 0) {
+		return;
+	}
+	usb_serial_jtag_ll_clr_intsts_mask(uart_intr_status);
+
+	if (data->irq_cb != NULL) {
+		data->irq_cb(dev, data->irq_cb_data);
+	}
+}
+
+#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
+
+static const DRAM_ATTR struct uart_driver_api serial_esp32_usb_api = {
+	.poll_in = serial_esp32_usb_poll_in,
+	.poll_out = serial_esp32_usb_poll_out,
+	.err_check = serial_esp32_usb_err_check,
+#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
+	.configure = serial_esp32_usb_configure,
+	.config_get = serial_esp32_usb_config_get,
+#endif
+#ifdef CONFIG_UART_INTERRUPT_DRIVEN
+	.fifo_fill = serial_esp32_usb_fifo_fill,
+	.fifo_read = serial_esp32_usb_fifo_read,
+	.irq_tx_enable = serial_esp32_usb_irq_tx_enable,
+	.irq_tx_disable = serial_esp32_usb_irq_tx_disable,
+	.irq_tx_ready = serial_esp32_usb_irq_tx_ready,
+	.irq_rx_enable = serial_esp32_usb_irq_rx_enable,
+	.irq_rx_disable = serial_esp32_usb_irq_rx_disable,
+	.irq_tx_complete = serial_esp32_usb_irq_tx_complete,
+	.irq_rx_ready = serial_esp32_usb_irq_rx_ready,
+	.irq_err_enable = serial_esp32_usb_irq_err_enable,
+	.irq_err_disable = serial_esp32_usb_irq_err_disable,
+	.irq_is_pending = serial_esp32_usb_irq_is_pending,
+	.irq_update = serial_esp32_usb_irq_update,
+	.irq_callback_set = serial_esp32_usb_irq_callback_set,
+#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
+};
+
+#define ESP32_UART_INIT(idx)                                                                       \
+	static const DRAM_ATTR struct serial_esp32_usb_config serial_esp32_usb_cfg_port_##idx = {  \
+		.uart_num = DT_INST_PROP(idx, peripheral),                                         \
+		.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(idx)),                              \
+		.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(idx, offset),          \
+		.irq_source = DT_INST_IRQN(idx)                                                    \
+	};                                                                                         \
+												   \
+	static struct serial_esp32_usb_data serial_esp32_usb_data_##idx = {                        \
+		.serial_config = { .baudrate = DT_INST_PROP(idx, current_speed),                   \
+				   .parity = UART_CFG_PARITY_NONE,                                 \
+				   .stop_bits = UART_CFG_STOP_BITS_1,                              \
+				   .data_bits = UART_CFG_DATA_BITS_8,                              \
+				   .flow_ctrl =                                                    \
+					   COND_CODE_1(DT_NODE_HAS_PROP(idx, hw_flow_control),     \
+						       (UART_CFG_FLOW_CTRL_RTS_CTS),               \
+						       (UART_CFG_FLOW_CTRL_NONE)) },               \
+	};                                                                                         \
+												   \
+	DEVICE_DT_INST_DEFINE(idx, &serial_esp32_usb_init, NULL, &serial_esp32_usb_data_##idx,     \
+			      &serial_esp32_usb_cfg_port_##idx, PRE_KERNEL_1,                      \
+			      CONFIG_SERIAL_INIT_PRIORITY, &serial_esp32_usb_api);
+
+DT_INST_FOREACH_STATUS_OKAY(ESP32_UART_INIT);
diff --git a/dts/bindings/serial/espressif,esp32-usb-serial.yaml b/dts/bindings/serial/espressif,esp32-usb-serial.yaml
new file mode 100644
index 0000000..6d2acd0
--- /dev/null
+++ b/dts/bindings/serial/espressif,esp32-usb-serial.yaml
@@ -0,0 +1,18 @@
+description: ESP32 UART
+
+compatible: "espressif,esp32-usb-serial"
+
+include: uart-controller.yaml
+
+properties:
+  reg:
+    required: true
+
+  interrupts:
+    required: false
+
+  peripheral:
+    type: int
+    required: true
+    description: |
+      The UART peripheral number