Skip to contentSkip to conversation

serial: add drivers for the ESP32xx serial devices

PATCH Superseded Max Filippov 4 participants 12 messages Sep 20, 2023 – Sep 28, 2023

Subsystems (2)

v2 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

v2 cover serial: add drivers for the ESP32xx serial devices

Hello,

this series adds drivers for the UART and ACM controllers found in the Espressif ESP32 and ESP32S3 SoCs.

Changes v1->v2:
- address review comments, listed in each patch
- add cleanup for the uart_get_baud_rate function

Max Filippov (5):
serial: core: tidy invalid baudrate handling in uart_get_baud_rate
dt-bindings: serial: document esp32-uart drivers/tty/serial: add driver for the ESP32 UART
dt-bindings: serial: document esp32s3-acm drivers/tty/serial: add ESP32S3 ACM device driver

 .../bindings/serial/esp,esp32-acm.yaml        |  39 +
 .../bindings/serial/esp,esp32-uart.yaml       |  48 ++
 drivers/tty/serial/Kconfig                    |  27 +
 drivers/tty/serial/Makefile                   |   2 +
 drivers/tty/serial/esp32_acm.c                | 458 +++++++++++
 drivers/tty/serial/esp32_uart.c               | 749 ++++++++++++++++++
 drivers/tty/serial/serial_core.c              |   5 +-
 include/uapi/linux/serial_core.h              |   6 +
 8 files changed, 1331 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/serial/esp,esp32-acm.yaml
 create mode 100644 Documentation/devicetree/bindings/serial/esp,esp32-uart.yaml
 create mode 100644 drivers/tty/serial/esp32_acm.c
 create mode 100644 drivers/tty/serial/esp32_uart.c
Signature · 1 line
2.30.2
#1 lore raw

PATCH v2 1/5 serial: core: tidy invalid baudrate handling in uart_get_baud_rate

uart_get_baud_rate has input parameters 'min' and 'max' limiting the range of acceptable baud rates from the caller's perspective. If neither current or old termios structures have acceptable baud rate setting and 9600 is not in the min/max range either the function returns 0 and issues a warning.
However for a UART that does not support speed of 9600 baud this is expected behavior.
Clarify that 0 can be (and always could be) returned from the uart_get_baud_rate. Don't issue a warning in that case.
Move the warinng to the uart_get_divisor instead, which is often called with the uart_get_baud_rate return value.

---
 drivers/tty/serial/serial_core.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
drivers/tty/serial/serial_core.c +2 -3
index 7bdc21d5e13b..a8e2915832e8 100644
@@ -431,7 +431,7 @@ EXPORT_SYMBOL(uart_update_timeout);
  * baud.
  *
  * If the new baud rate is invalid, try the @old termios setting. If it's still
- * invalid, we try 9600 baud.
+ * invalid, we try 9600 baud. If that is also invalid 0 is returned.
  *
  * The @termios structure is updated to reflect the baud rate we're actually
  * going to be using. Don't do this for the case where B0 is requested ("hang
@@ -515,8 +515,6 @@ uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,
 							max - 1, max - 1);
 		}
 	}
-	/* Should never happen */
-	WARN_ON(1);
 	return 0;
 }
 EXPORT_SYMBOL(uart_get_baud_rate);
@@ -539,6 +537,7 @@ uart_get_divisor(struct uart_port *port, unsigned int baud)
 {
 	unsigned int quot;
 
+	WARN_ON(baud == 0);
 	/*
 	 * Old custom speed handling.
 	 */
Signature · 1 line
2.30.2
#2 lore raw

Re: [PATCH v2 1/5] serial: core: tidy invalid baudrate handling in uart_get_baud_rate

Quoted 31 lines · Max Filippov

uart_get_baud_rate has input parameters 'min' and 'max' limiting the range of acceptable baud rates from the caller's perspective. If neither current or old termios structures have acceptable baud rate setting and 9600 is not in the min/max range either the function returns 0 and issues a warning.
However for a UART that does not support speed of 9600 baud this is expected behavior.
Clarify that 0 can be (and always could be) returned from the uart_get_baud_rate. Don't issue a warning in that case.
Move the warinng to the uart_get_divisor instead, which is often called with the uart_get_baud_rate return value.

---
 drivers/tty/serial/serial_core.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
drivers/tty/serial/serial_core.c +1 -1
index 7bdc21d5e13b..a8e2915832e8 100644
@@ -431,7 +431,7 @@ EXPORT_SYMBOL(uart_update_timeout);
  * baud.
  *
  * If the new baud rate is invalid, try the @old termios setting. If it's still
- * invalid, we try 9600 baud.
+ * invalid, we try 9600 baud. If that is also invalid 0 is returned.
  *
  * The @termios structure is updated to reflect the baud rate we're actually
  * going to be using. Don't do this for the case where B0 is requested ("hang

drivers/tty/serial/serial_core.c uart_get_baud_rate(struct uart_port *port, struct ktermios *termios, · line 519

Quoted 5 lines
drivers/tty/serial/serial_core.c +0 -2
@@ -515,8 +515,6 @@ uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,
 							max - 1, max - 1);
 		}
 	}
-	/* Should never happen */
-	WARN_ON(1);

I'm ok with this removal, but:

drivers/tty/serial/serial_core.c uart_get_divisor(struct uart_port *port, unsigned int baud) · line 540

Quoted 8 lines
drivers/tty/serial/serial_core.c +1 -0
 	return 0;
 }
 EXPORT_SYMBOL(uart_get_baud_rate);
@@ -539,6 +537,7 @@ uart_get_divisor(struct uart_port *port, unsigned int baud)
 {
 	unsigned int quot;
 
+	WARN_ON(baud == 0);

Why is this needed? If this isn't happening today, then there's no need to check for this here. Or if it can happen, we should return an error, not cause a possible reboot of the system if panic-on-warn is enabled.

thanks,

greg k-h

#3 lore raw

Re: [PATCH v2 1/5] serial: core: tidy invalid baudrate handling in uart_get_baud_rate

Quoted 9 lines · Greg Kroah-Hartman

drivers/tty/serial/serial_core.c uart_get_divisor(struct uart_port *port, unsigned int baud) · line 540

Quoted 5 lines · Max Filippov
drivers/tty/serial/serial_core.c +1 -0
@@ -539,6 +537,7 @@ uart_get_divisor(struct uart_port *port, unsigned int baud)
 {
      unsigned int quot;
 
+     WARN_ON(baud == 0);

Why is this needed? If this isn't happening today, then there's no need to check for this here.

I'll drop it then.

Signature · 2 lines
Thanks.
-- Max
#4 lore raw

PATCH v2 2/5 dt-bindings: serial: document esp32-uart

In reply to Max Filippov

Add documentation for the ESP32xx UART controllers.

---
Changes v1->v2:
- drop '|' from description
- change 'compatible' property type to enum
- drop label from the example node
- fix example indentation

 .../bindings/serial/esp,esp32-uart.yaml       | 48 +++++++++++++++++++
 1 file changed, 48 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/esp,esp32-uart.yaml
Documentation/devicetree/bindings/serial/esp,esp32-uart.yaml new file +48 -0
new file mode 100644
index 000000000000..707c10eb093c
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/esp,esp32-uart.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ESP32xx UART controllers
+
+maintainers:
+  - Max Filippov <[email protected]>
+
+description:
+  ESP32 UART controller is a part of the ESP32 SoC.
+  ESP32S3 UART controller is a part of the ESP32S3 SoC.
+  Both SoCs are produced by Espressif Systems Co. Ltd.
+
+properties:
+  compatible:
+    enum:
+      - esp,esp32-uart
+      - esp,esp32s3-uart
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+
+additionalProperties: false
+
+examples:
+  - |
+    serial@60000000 {
+      compatible = "esp,esp32s3-uart";
+      reg = <0x60000000 0x80>;
+      interrupts = <27 1 0>;
+      clocks = <&serial_clk>;
+    };
Signature · 1 line
2.30.2
#5 lore raw

Re: [PATCH v2 2/5] dt-bindings: serial: document esp32-uart

Quoted 4 lines · Max Filippov

Add documentation for the ESP32xx UART controllers.

Thanks for the changes.

Quoted 10 lines
Documentation/devicetree/bindings/serial/esp,esp32-uart.yaml new file +10 -0
+properties:
+  compatible:
+    enum:
+      - esp,esp32-uart
+      - esp,esp32s3-uart
+
+  reg:
+    maxItems: 1
+
+  interrupts:

Documentation/devicetree/bindings/serial/esp,esp32-uart.yaml line 37

Quoted 10 lines
Documentation/devicetree/bindings/serial/esp,esp32-uart.yaml new file +10 -0
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks

You need allOf with $ref to serial.yaml# (local serial, not absolute path). I apologize, I missed this in my previous review.

Best regards,
Krzysztof

#6 lore raw

PATCH v2 3/5 drivers/tty/serial: add driver for the ESP32 UART

In reply to Max Filippov

Add driver for the UART controllers of the Espressif ESP32 and ESP32S3 SoCs. Hardware specification is available at the following URLs:

https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
(Chapter 13 UART Controller)
https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf
(Chapter 26 UART Controller)

---
Changes v1->v2:
- redefine register fields using BIT and GENMASK
- drop _MASK suffix from register field definitions
- drop *_SHIFT definitions where possible
- drop unused rxfifo_full_thrhd_mask and txfifo_empty_thrhd_mask
- split register reads/writes and bitwise operations into multiple lines
- use u8 instead of unsigned char in internal functions
- add timeout to esp32_uart_put_char_sync
- use uart_port_tx_limited in esp32_uart_transmit_buffer
- use IRQ_RETVAL in esp32_uart_int
- disable clock in esp32_uart_startup in case devm_request_irq fails
- rearrange devm_request_irq with enabling IRQs in the UART registers
- drop empty esp32_uart_release_port and esp32_uart_request_port
- simplify esp32_uart_tx_empty
- mask out unsupported CMSPAR flag in termios->c_cflag in esp32_uart_set_termios
- invoke uart_update_timeout in esp32_uart_set_termios
- drop MODULE_DESCRIPTION
- rearrange esp32_uart_set_baud: return bool indicating whether baud rate was set or not, use it in the esp32_uart_set_termios to set the default 115200
- turn 'locked' into bool in esp32_uart_console_write
- turn num_read into unsigned int in esp32_uart_earlycon_read

 drivers/tty/serial/Kconfig       |  13 +
 drivers/tty/serial/Makefile      |   1 +
 drivers/tty/serial/esp32_uart.c  | 749 +++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h |   3 +
 4 files changed, 766 insertions(+)
 create mode 100644 drivers/tty/serial/esp32_uart.c
drivers/tty/serial/Kconfig +13 -0
index bdc568a4ab66..d9ca6b268f01 100644
@@ -1578,6 +1578,19 @@ config SERIAL_NUVOTON_MA35D1_CONSOLE
 	  but you can alter that using a kernel command line option such as
 	  "console=ttyNVTx".
 
+config SERIAL_ESP32
+	tristate "Espressif ESP32 UART support"
+	depends on XTENSA_PLATFORM_ESP32 || (COMPILE_TEST && OF)
+	select SERIAL_CORE
+	select SERIAL_CORE_CONSOLE
+	select SERIAL_EARLYCON
+	help
+	  Driver for the UART controllers of the Espressif ESP32xx SoCs.
+	  When earlycon option is enabled the following kernel command line
+	  snippets may be used:
+	    earlycon=esp32s3uart,mmio32,0x60000000,115200n8,40000000
+	    earlycon=esp32uart,mmio32,0x3ff40000,115200n8
+
 endmenu
 
 config SERIAL_MCTRL_GPIO
drivers/tty/serial/Makefile +1 -0
index 138abbc89738..7b73137df7f3 100644
@@ -88,6 +88,7 @@ obj-$(CONFIG_SERIAL_MILBEAUT_USIO) += milbeaut_usio.o
 obj-$(CONFIG_SERIAL_SIFIVE)	+= sifive.o
 obj-$(CONFIG_SERIAL_LITEUART) += liteuart.o
 obj-$(CONFIG_SERIAL_SUNPLUS)	+= sunplus-uart.o
+obj-$(CONFIG_SERIAL_ESP32)	+= esp32_uart.o
 
 # GPIOLIB helpers for modem control lines
 obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
drivers/tty/serial/esp32_uart.c new file +749 -0

749 lines not on this page · raw message

include/uapi/linux/serial_core.h +3 -0
index add349889d0a..ff076d6be159 100644
@@ -245,4 +245,7 @@
 /* Sunplus UART */
 #define PORT_SUNPLUS	123
 
+/* Espressif ESP32 UART */
+#define PORT_ESP32UART	124
+
 #endif /* _UAPILINUX_SERIAL_CORE_H */
Signature · 1 line
2.30.2
maintainer
#7 lore raw

Re: [PATCH v2 3/5] drivers/tty/serial: add driver for the ESP32 UART

Quoted 2 lines · Max Filippov

Add driver for the UART controllers of the Espressif ESP32 and ESP32S3 SoCs. Hardware specification is available at the following URLs:

...

Quoted 7 lines
drivers/tty/serial/esp32_uart.c new file +7 -0
+static void esp32_uart_rxint(struct uart_port *port)
+{
+	struct tty_port *tty_port = &port->state->port;
+	u32 rx_fifo_cnt = esp32_uart_rx_fifo_cnt(port);
+	unsigned long flags;
+	u32 i;
+

drivers/tty/serial/esp32_uart.c line 207

Quoted 10 lines
drivers/tty/serial/esp32_uart.c new file +10 -0
+	if (!rx_fifo_cnt)
+		return;
+
+	spin_lock_irqsave(&port->lock, flags);
+
+	for (i = 0; i < rx_fifo_cnt; ++i) {
+		u32 rx = esp32_uart_read(port, UART_FIFO_REG);
+		u32 brk = 0;
+
+		++port->icount.rx;

Should yuou increment this only in case of insert_flip_char()?

drivers/tty/serial/esp32_uart.c line 221

Quoted 13 lines
drivers/tty/serial/esp32_uart.c new file +13 -0
+		if (!rx) {
+			brk = esp32_uart_read(port, UART_INT_ST_REG) &
+				UART_BRK_DET_INT;
+		}
+		if (brk) {
+			esp32_uart_write(port, UART_INT_CLR_REG, brk);
+			++port->icount.brk;
+			uart_handle_break(port);
+		} else {
+			if (uart_handle_sysrq_char(port, (unsigned char)rx))
+				continue;
+			tty_insert_flip_char(tty_port, rx, TTY_NORMAL);
+		}

This is heavy. Is it equivalent to:
if (!rx && esp32_uart_read(port, UART_INT_ST_REG) &

    UART_BRK_DET_INT) {

esp32_uart_write(port, UART_INT_CLR_REG, brk);
++port->icount.brk;
uart_handle_break(port);
continue;
}

if (uart_handle_sysrq_char(port, (unsigned char)rx))
continue;

tty_insert_flip_char(tty_port, rx, TTY_NORMAL);

?

Quoted 5 lines
drivers/tty/serial/esp32_uart.c new file +5 -0
+	}
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	tty_flip_buffer_push(tty_port);
+}

...

drivers/tty/serial/esp32_uart.c line 237

Quoted 5 lines
drivers/tty/serial/esp32_uart.c new file +5 -0
+static void esp32_uart_put_char_sync(struct uart_port *port, u8 c)
+{
+	unsigned long timeout;
+
+	timeout = jiffies + msecs_to_jiffies(1000);

I.e. plus HZ?

Quoted 5 lines
drivers/tty/serial/esp32_uart.c new file +5 -0
+	while (esp32_uart_tx_fifo_cnt(port) >= ESP32_UART_TX_FIFO_SIZE) {
+		if (time_after(jiffies, timeout)) {
+			dev_warn(port->dev, "timeout waiting for TX FIFO\n");
+			return;
+		}

drivers/tty/serial/esp32_uart.c line 252

Quoted 10 lines
drivers/tty/serial/esp32_uart.c new file +10 -0
+		cpu_relax();
+	}
+	esp32_uart_put_char(port, c);
+}
+
+static void esp32_uart_transmit_buffer(struct uart_port *port)
+{
+	u32 tx_fifo_used = esp32_uart_tx_fifo_cnt(port);
+
+	if (tx_fifo_used < ESP32_UART_TX_FIFO_SIZE) {

Invert the condition and return here?

Quoted 21 lines
drivers/tty/serial/esp32_uart.c new file +19 -0
+		unsigned int pending;
+		u8 ch;
+
+		pending = uart_port_tx_limited(port, ch,
+					       ESP32_UART_TX_FIFO_SIZE - tx_fifo_used,
+					       true, esp32_uart_put_char(port, ch),
+					       ({}));
+		if (pending) {
+			u32 int_ena;
+
+			int_ena = esp32_uart_read(port, UART_INT_ENA_REG);
+			int_ena |= UART_TXFIFO_EMPTY_INT;
+			esp32_uart_write(port, UART_INT_ENA_REG, int_ena);
+		}
+	}
+}
 
 
+static irqreturn_t esp32_uart_int(int irq, void *dev_id)
+{
+	struct uart_port *port = dev_id;

drivers/tty/serial/esp32_uart.c line 287

Quoted 10 lines
drivers/tty/serial/esp32_uart.c new file +10 -0
+	u32 status;
+
+	status = esp32_uart_read(port, UART_INT_ST_REG);
+
+	if (status & (UART_RXFIFO_FULL_INT | UART_BRK_DET_INT))
+		esp32_uart_rxint(port);
+	if (status & UART_TXFIFO_EMPTY_INT)
+		esp32_uart_txint(port);
+
+	esp32_uart_write(port, UART_INT_CLR_REG, status);

\n here please.

Quoted 13 lines
drivers/tty/serial/esp32_uart.c new file +12 -0
+	return IRQ_RETVAL(status);
+}
 
+static int esp32_uart_startup(struct uart_port *port)
+{
+	int ret = 0;
+	unsigned long flags;
+	struct esp32_port *sport = container_of(port, struct esp32_port, port);
+
+	ret = clk_prepare_enable(sport->clk);
+	if (ret)
+		return ret;
+

drivers/tty/serial/esp32_uart.c line 324

Quoted 10 lines
drivers/tty/serial/esp32_uart.c new file +10 -0
+	ret = devm_request_irq(port->dev, port->irq, esp32_uart_int, 0,
+			       DRIVER_NAME, port);
+	if (ret) {
+		clk_disable_unprepare(sport->clk);
+		return ret;
+	}
+
+	spin_lock_irqsave(&port->lock, flags);
+	esp32_uart_write(port, UART_CONF1_REG,
+			 (1 << UART_RXFIFO_FULL_THRHD_SHIFT) |

BIT() ?

drivers/tty/serial/esp32_uart.c line 325

Quoted 1 line
drivers/tty/serial/esp32_uart.c new file +1 -0
+			 (1 << port_variant(port)->txfifo_empty_thrhd_shift));

And here?

drivers/tty/serial/esp32_uart.c line 334

Quoted 9 lines
drivers/tty/serial/esp32_uart.c new file +9 -0
+	esp32_uart_write(port, UART_INT_CLR_REG, UART_RXFIFO_FULL_INT | UART_BRK_DET_INT);
+	esp32_uart_write(port, UART_INT_ENA_REG, UART_RXFIFO_FULL_INT | UART_BRK_DET_INT);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	pr_debug("%s, conf1 = %08x, int_st = %08x, status = %08x\n",
+		 __func__,
+		 esp32_uart_read(port, UART_CONF1_REG),
+		 esp32_uart_read(port, UART_INT_ST_REG),
+		 esp32_uart_read(port, UART_STATUS_REG));

\n here. Is this debug printout somehow useful?

drivers/tty/serial/esp32_uart.c line 343

Quoted 9 lines
drivers/tty/serial/esp32_uart.c new file +9 -0
+	return ret;
+}
+
+static void esp32_uart_shutdown(struct uart_port *port)
+{
+	struct esp32_port *sport = container_of(port, struct esp32_port, port);
+
+	esp32_uart_write(port, UART_INT_ENA_REG, 0);
+	devm_free_irq(port->dev, port->irq, port);

I wonder why to use devm_ after all?

drivers/tty/serial/esp32_uart.c line 356

Quoted 13 lines
drivers/tty/serial/esp32_uart.c new file +13 -0
+	clk_disable_unprepare(sport->clk);
+}
+
+static bool esp32_uart_set_baud(struct uart_port *port, u32 baud)
+{
+	u32 div = port->uartclk / baud;
+	u32 frag = (port->uartclk * 16) / baud - div * 16;
+
+	if (div <= port_variant(port)->clkdiv_mask) {
+		esp32_uart_write(port, UART_CLKDIV_REG,
+				 div | FIELD_PREP(UART_CLKDIV_FRAG, frag));
+		return true;
+	}

\n

Quoted 2 lines
(quoted diff) +2 -0
+	return false;
+}

...

drivers/tty/serial/esp32_uart.c line 628

Quoted 4 lines
drivers/tty/serial/esp32_uart.c new file +4 -0
+static int __init esp32s3_uart_early_console_setup(struct earlycon_device *device,
+						   const char *options)
+{
+	device->port.private_data = (void *)&esp32s3_variant;

No need to cast, right?

\n

Quoted 2 lines
(quoted diff) +2 -0
+	return esp32xx_uart_early_console_setup(device, options);
+}

...

Quoted 44 lines
drivers/tty/serial/esp32_uart.c new file +44 -0
+static int esp32_uart_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	static const struct of_device_id *match;
+	struct uart_port *port;
+	struct esp32_port *sport;
+	struct resource *res;
+	int ret;
+
+	match = of_match_device(esp32_uart_dt_ids, &pdev->dev);
+	if (!match)
+		return -ENODEV;
+
+	sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
+	if (!sport)
+		return -ENOMEM;
+
+	port = &sport->port;
+
+	ret = of_alias_get_id(np, "serial");
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret);
+		return ret;
+	}
+	if (ret >= UART_NR) {
+		dev_err(&pdev->dev, "driver limited to %d serial ports\n", UART_NR);
+		return -ENOMEM;
+	}
+
+	port->line = ret;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	port->mapbase = res->start;
+	port->membase = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(port->membase))
+		return PTR_ERR(port->membase);
+
+	sport->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(sport->clk))
+		return PTR_ERR(sport->clk);
+

drivers/tty/serial/esp32_uart.c line 696

Quoted 10 lines
drivers/tty/serial/esp32_uart.c new file +10 -0
+	port->uartclk = clk_get_rate(sport->clk);
+	port->dev = &pdev->dev;
+	port->type = PORT_ESP32UART;
+	port->iotype = UPIO_MEM;
+	port->irq = platform_get_irq(pdev, 0);
+	port->ops = &esp32_uart_pops;
+	port->flags = UPF_BOOT_AUTOCONF;
+	port->has_sysrq = 1;
+	port->fifosize = ESP32_UART_TX_FIFO_SIZE;
+	port->private_data = (void *)match->data;

No need to cast.

drivers/tty/serial/esp32_uart.c line 703

Quoted 7 lines
drivers/tty/serial/esp32_uart.c new file +7 -0
+
+	esp32_uart_ports[port->line] = sport;
+
+	platform_set_drvdata(pdev, port);
+
+	return uart_add_one_port(&esp32_uart_reg, port);
+}

regards,

Signature · 2 lines
js
suse labs
#8 lore raw

Re: [PATCH v2 3/5] drivers/tty/serial: add driver for the ESP32 UART

Quoted 16 lines · Jiri Slaby
Quoted 2 lines · Max Filippov

Add driver for the UART controllers of the Espressif ESP32 and ESP32S3 SoCs. Hardware specification is available at the following URLs:

...

Quoted 11 lines
drivers/tty/serial/esp32_uart.c new file +11 -0
+static void esp32_uart_rxint(struct uart_port *port)
+{
+     struct tty_port *tty_port = &port->state->port;
+     u32 rx_fifo_cnt = esp32_uart_rx_fifo_cnt(port);
+     unsigned long flags;
+     u32 i;
+
+     if (!rx_fifo_cnt)
+             return;
+
+     spin_lock_irqsave(&port->lock, flags);
Quoted 8 lines

drivers/tty/serial/esp32_uart.c line 207

Quoted 6 lines
drivers/tty/serial/esp32_uart.c new file +6 -0
+
+     for (i = 0; i < rx_fifo_cnt; ++i) {
+             u32 rx = esp32_uart_read(port, UART_FIFO_REG);
+             u32 brk = 0;
+
+             ++port->icount.rx;

Should yuou increment this only in case of insert_flip_char()?

I don't know. Does port->icount.rx have clearly defined semantics? I've looked through multiple other serial drivers and there's a lot of examples of similar pattern.

Quoted 23 lines

drivers/tty/serial/esp32_uart.c line 221

Quoted 13 lines
drivers/tty/serial/esp32_uart.c new file +13 -0
+             if (!rx) {
+                     brk = esp32_uart_read(port, UART_INT_ST_REG) &
+                             UART_BRK_DET_INT;
+             }
+             if (brk) {
+                     esp32_uart_write(port, UART_INT_CLR_REG, brk);
+                     ++port->icount.brk;
+                     uart_handle_break(port);
+             } else {
+                     if (uart_handle_sysrq_char(port, (unsigned char)rx))
+                             continue;
+                     tty_insert_flip_char(tty_port, rx, TTY_NORMAL);
+             }

This is heavy. Is it equivalent to:
if (!rx && esp32_uart_read(port, UART_INT_ST_REG) &

     UART_BRK_DET_INT) {
   esp32_uart_write(port, UART_INT_CLR_REG, brk);
   ++port->icount.brk;
   uart_handle_break(port);
   continue;
}
Quoted 6 lines

if (uart_handle_sysrq_char(port, (unsigned char)rx))
continue;

tty_insert_flip_char(tty_port, rx, TTY_NORMAL);

?

It is equivalent, but I find the version that I used somewhat easier to read. Maybe this:

               if (!rx &&
                   (esp32_uart_read(port, UART_INT_ST_REG) &
UART_BRK_DET_INT)) {
                       esp32_uart_write(port, UART_INT_CLR_REG, brk);
                       ++port->icount.brk;
                       uart_handle_break(port);
               } else {
                       if (uart_handle_sysrq_char(port, (unsigned char)rx))
                               continue;
                       tty_insert_flip_char(tty_port, rx, TTY_NORMAL);
               }

?

Quoted 6 lines
Quoted 5 lines
drivers/tty/serial/esp32_uart.c new file +5 -0
+     }
+     spin_unlock_irqrestore(&port->lock, flags);
+
+     tty_flip_buffer_push(tty_port);
+}

...

Quoted 7 lines

drivers/tty/serial/esp32_uart.c line 237

Quoted 5 lines
drivers/tty/serial/esp32_uart.c new file +5 -0
+static void esp32_uart_put_char_sync(struct uart_port *port, u8 c)
+{
+     unsigned long timeout;
+
+     timeout = jiffies + msecs_to_jiffies(1000);

I.e. plus HZ?

Yes, ok.

Quoted 9 lines
Quoted 9 lines
drivers/tty/serial/esp32_uart.c new file +9 -0
+     while (esp32_uart_tx_fifo_cnt(port) >= ESP32_UART_TX_FIFO_SIZE) {
+             if (time_after(jiffies, timeout)) {
+                     dev_warn(port->dev, "timeout waiting for TX FIFO\n");
+                     return;
+             }
+             cpu_relax();
+     }
+     esp32_uart_put_char(port, c);
+}
Quoted 8 lines

drivers/tty/serial/esp32_uart.c line 252

Quoted 6 lines
drivers/tty/serial/esp32_uart.c new file +6 -0
+
+static void esp32_uart_transmit_buffer(struct uart_port *port)
+{
+     u32 tx_fifo_used = esp32_uart_tx_fifo_cnt(port);
+
+     if (tx_fifo_used < ESP32_UART_TX_FIFO_SIZE) {

Invert the condition and return here?

Ok.

Quoted 18 lines
Quoted 16 lines
drivers/tty/serial/esp32_uart.c new file +16 -0
+             unsigned int pending;
+             u8 ch;
+
+             pending = uart_port_tx_limited(port, ch,
+                                            ESP32_UART_TX_FIFO_SIZE - tx_fifo_used,
+                                            true, esp32_uart_put_char(port, ch),
+                                            ({}));
+             if (pending) {
+                     u32 int_ena;
+
+                     int_ena = esp32_uart_read(port, UART_INT_ENA_REG);
+                     int_ena |= UART_TXFIFO_EMPTY_INT;
+                     esp32_uart_write(port, UART_INT_ENA_REG, int_ena);
+             }
+     }
+}
Quoted 15 lines

drivers/tty/serial/esp32_uart.c line 287

Quoted 13 lines
drivers/tty/serial/esp32_uart.c new file +13 -0
+static irqreturn_t esp32_uart_int(int irq, void *dev_id)
+{
+     struct uart_port *port = dev_id;
+     u32 status;
+
+     status = esp32_uart_read(port, UART_INT_ST_REG);
+
+     if (status & (UART_RXFIFO_FULL_INT | UART_BRK_DET_INT))
+             esp32_uart_rxint(port);
+     if (status & UART_TXFIFO_EMPTY_INT)
+             esp32_uart_txint(port);
+
+     esp32_uart_write(port, UART_INT_CLR_REG, status);

\n here please.

Ok

Quoted 26 lines
Quoted 13 lines
drivers/tty/serial/esp32_uart.c new file +12 -0
+     return IRQ_RETVAL(status);
+}
 
+static int esp32_uart_startup(struct uart_port *port)
+{
+     int ret = 0;
+     unsigned long flags;
+     struct esp32_port *sport = container_of(port, struct esp32_port, port);
+
+     ret = clk_prepare_enable(sport->clk);
+     if (ret)
+             return ret;
+

drivers/tty/serial/esp32_uart.c line 324

Quoted 10 lines
drivers/tty/serial/esp32_uart.c new file +10 -0
+     ret = devm_request_irq(port->dev, port->irq, esp32_uart_int, 0,
+                            DRIVER_NAME, port);
+     if (ret) {
+             clk_disable_unprepare(sport->clk);
+             return ret;
+     }
+
+     spin_lock_irqsave(&port->lock, flags);
+     esp32_uart_write(port, UART_CONF1_REG,
+                      (1 << UART_RXFIFO_FULL_THRHD_SHIFT) |

BIT() ?

Quoted 3 lines
Quoted 1 line

+ (1 << port_variant(port)->txfifo_empty_thrhd_shift));

And here?

These are not logically bits, in both cases I put value 1 into multiple-bit fields.

Quoted 11 lines

drivers/tty/serial/esp32_uart.c line 334

Quoted 9 lines
drivers/tty/serial/esp32_uart.c new file +9 -0
+     esp32_uart_write(port, UART_INT_CLR_REG, UART_RXFIFO_FULL_INT | UART_BRK_DET_INT);
+     esp32_uart_write(port, UART_INT_ENA_REG, UART_RXFIFO_FULL_INT | UART_BRK_DET_INT);
+     spin_unlock_irqrestore(&port->lock, flags);
+
+     pr_debug("%s, conf1 = %08x, int_st = %08x, status = %08x\n",
+              __func__,
+              esp32_uart_read(port, UART_CONF1_REG),
+              esp32_uart_read(port, UART_INT_ST_REG),
+              esp32_uart_read(port, UART_STATUS_REG));

\n here. Is this debug printout somehow useful?

I'll drop it.

Quoted 11 lines

drivers/tty/serial/esp32_uart.c line 343

Quoted 9 lines
drivers/tty/serial/esp32_uart.c new file +9 -0
+     return ret;
+}
+
+static void esp32_uart_shutdown(struct uart_port *port)
+{
+     struct esp32_port *sport = container_of(port, struct esp32_port, port);
+
+     esp32_uart_write(port, UART_INT_ENA_REG, 0);
+     devm_free_irq(port->dev, port->irq, port);

I wonder why to use devm_ after all?

I'll switch to non-devm_ version.

Quoted 7 lines
Quoted 7 lines
drivers/tty/serial/esp32_uart.c new file +7 -0
+     clk_disable_unprepare(sport->clk);
+}
+
+static bool esp32_uart_set_baud(struct uart_port *port, u32 baud)
+{
+     u32 div = port->uartclk / baud;
+     u32 frag = (port->uartclk * 16) / baud - div * 16;
Quoted 8 lines

drivers/tty/serial/esp32_uart.c line 356

Quoted 6 lines
drivers/tty/serial/esp32_uart.c new file +6 -0
+
+     if (div <= port_variant(port)->clkdiv_mask) {
+             esp32_uart_write(port, UART_CLKDIV_REG,
+                              div | FIELD_PREP(UART_CLKDIV_FRAG, frag));
+             return true;
+     }

\n

Ok.

Quoted 9 lines
Quoted 2 lines
(quoted diff) +2 -0
+     return false;
+}

...

drivers/tty/serial/esp32_uart.c line 628

Quoted 4 lines
drivers/tty/serial/esp32_uart.c new file +4 -0
+static int __init esp32s3_uart_early_console_setup(struct earlycon_device *device,
+                                                const char *options)
+{
+     device->port.private_data = (void *)&esp32s3_variant;

No need to cast, right?

private_data is void *, esp32s3_variant is a constant.

Quoted 1 line

\n

Ok.

Quoted 51 lines
Quoted 2 lines
(quoted diff) +2 -0
+     return esp32xx_uart_early_console_setup(device, options);
+}

...

Quoted 48 lines
drivers/tty/serial/esp32_uart.c new file +48 -0
+static int esp32_uart_probe(struct platform_device *pdev)
+{
+     struct device_node *np = pdev->dev.of_node;
+     static const struct of_device_id *match;
+     struct uart_port *port;
+     struct esp32_port *sport;
+     struct resource *res;
+     int ret;
+
+     match = of_match_device(esp32_uart_dt_ids, &pdev->dev);
+     if (!match)
+             return -ENODEV;
+
+     sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
+     if (!sport)
+             return -ENOMEM;
+
+     port = &sport->port;
+
+     ret = of_alias_get_id(np, "serial");
+     if (ret < 0) {
+             dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret);
+             return ret;
+     }
+     if (ret >= UART_NR) {
+             dev_err(&pdev->dev, "driver limited to %d serial ports\n", UART_NR);
+             return -ENOMEM;
+     }
+
+     port->line = ret;
+
+     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+     if (!res)
+             return -ENODEV;
+
+     port->mapbase = res->start;
+     port->membase = devm_ioremap_resource(&pdev->dev, res);
+     if (IS_ERR(port->membase))
+             return PTR_ERR(port->membase);
+
+     sport->clk = devm_clk_get(&pdev->dev, NULL);
+     if (IS_ERR(sport->clk))
+             return PTR_ERR(sport->clk);
+
+     port->uartclk = clk_get_rate(sport->clk);
+     port->dev = &pdev->dev;
+     port->type = PORT_ESP32UART;
+     port->iotype = UPIO_MEM;
Quoted 8 lines

drivers/tty/serial/esp32_uart.c line 696

Quoted 6 lines
drivers/tty/serial/esp32_uart.c new file +6 -0
+     port->irq = platform_get_irq(pdev, 0);
+     port->ops = &esp32_uart_pops;
+     port->flags = UPF_BOOT_AUTOCONF;
+     port->has_sysrq = 1;
+     port->fifosize = ESP32_UART_TX_FIFO_SIZE;
+     port->private_data = (void *)match->data;

No need to cast.

This is again a const cast.

Quoted 13 lines

drivers/tty/serial/esp32_uart.c line 703

Quoted 7 lines
drivers/tty/serial/esp32_uart.c new file +7 -0
+
+     esp32_uart_ports[port->line] = sport;
+
+     platform_set_drvdata(pdev, port);
+
+     return uart_add_one_port(&esp32_uart_reg, port);
+}

regards,
--
js
suse labs

Signature · 2 lines
Thanks.
-- Max
#9 lore raw

PATCH v2 4/5 dt-bindings: serial: document esp32s3-acm

In reply to Max Filippov

Add documentation for the ESP32S3 USB CDC-ACM controller.

---
Changes v1->v2:
- fix description
- rename node in the example
- fix example indentation

 .../bindings/serial/esp,esp32-acm.yaml        | 39 +++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serial/esp,esp32-acm.yaml
Documentation/devicetree/bindings/serial/esp,esp32-acm.yaml new file +39 -0
new file mode 100644
index 000000000000..6ec836db694a
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/esp,esp32-acm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ESP32S3 ACM controller
+
+maintainers:
+  - Max Filippov <[email protected]>
+
+description:
+  Fixed function USB CDC-ACM device controller of the Espressif ESP32S3 SoC.
+
+properties:
+  compatible:
+    const: esp,esp32s3-acm
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - interrupts
+
+additionalProperties: false
+
+examples:
+  - |
+    serial@60038000 {
+      compatible = "esp,esp32s3-acm";
+      reg = <0x60038000 0x1000>;
+      interrupts = <96 3 0>;
+    };
Signature · 1 line
2.30.2
#10 lore raw

Re: [PATCH v2 4/5] dt-bindings: serial: document esp32s3-acm

Documentation/devicetree/bindings/serial/esp,esp32-acm.yaml line 30

Quoted 11 lines · Max Filippov
Documentation/devicetree/bindings/serial/esp,esp32-acm.yaml new file +11 -0
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - interrupts
+

Same comment here - missing $ref to serial.yaml. Sorry for no bringing it up earlier.

Best regards,
Krzysztof

#11 lore raw

PATCH v2 5/5 drivers/tty/serial: add ESP32S3 ACM device driver

In reply to Max Filippov

Add driver for the ACM controller of the Espressif ESP32S3 Soc. Hardware specification is available at the following URL:

https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf
(Chapter 33 USB Serial/JTAG Controller)

---
Changes v1->v2:
- redefine register fields using BIT and GENMASK
- drop _MASK suffix from register field definitions
- drop *_SHIFT definitions where possible
- split register reads/writes and bitwise operations into multiple lines
- use u8 instead of unsigned char in internal functions
- add timeout to esp32_acm_put_char_sync
- use uart_port_tx_limited in esp32_acm_transmit_buffer
- use IRQ_RETVAL in esp32_acm_int
- drop esp32s3_acm_console_putchar and esp32s3_acm_earlycon_putchar
- turn num_read into unsigned int in esp32_acm_earlycon_read
- drop MODULE_DESCRIPTION

 drivers/tty/serial/Kconfig       |  14 +
 drivers/tty/serial/Makefile      |   1 +
 drivers/tty/serial/esp32_acm.c   | 458 +++++++++++++++++++++++++++++++
 include/uapi/linux/serial_core.h |   3 +
 4 files changed, 476 insertions(+)
 create mode 100644 drivers/tty/serial/esp32_acm.c
drivers/tty/serial/Kconfig +14 -0
index d9ca6b268f01..85807db8f7ce 100644
@@ -1591,6 +1591,20 @@ config SERIAL_ESP32
 	    earlycon=esp32s3uart,mmio32,0x60000000,115200n8,40000000
 	    earlycon=esp32uart,mmio32,0x3ff40000,115200n8
 
+config SERIAL_ESP32_ACM
+	tristate "Espressif ESP32 USB ACM support"
+	depends on XTENSA_PLATFORM_ESP32 || (COMPILE_TEST && OF)
+	select SERIAL_CORE
+	select SERIAL_CORE_CONSOLE
+	select SERIAL_EARLYCON
+	help
+	  Driver for the CDC ACM controllers of the Espressif ESP32S3 SoCs
+	  that share separate USB controller with the JTAG adapter.
+	  The device name used for this controller is ttyACM.
+	  When earlycon option is enabled the following kernel command line
+	  snippet may be used:
+	    earlycon=esp32s3acm,mmio32,0x60038000
+
 endmenu
 
 config SERIAL_MCTRL_GPIO
drivers/tty/serial/Makefile +1 -0
index 7b73137df7f3..970a292ca418 100644
@@ -89,6 +89,7 @@ obj-$(CONFIG_SERIAL_SIFIVE)	+= sifive.o
 obj-$(CONFIG_SERIAL_LITEUART) += liteuart.o
 obj-$(CONFIG_SERIAL_SUNPLUS)	+= sunplus-uart.o
 obj-$(CONFIG_SERIAL_ESP32)	+= esp32_uart.o
+obj-$(CONFIG_SERIAL_ESP32_ACM)	+= esp32_acm.o
 
 # GPIOLIB helpers for modem control lines
 obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
drivers/tty/serial/esp32_acm.c new file +458 -0

458 lines not on this page · raw message

include/uapi/linux/serial_core.h +3 -0
index ff076d6be159..1045bf096837 100644
@@ -248,4 +248,7 @@
 /* Espressif ESP32 UART */
 #define PORT_ESP32UART	124
 
+/* Espressif ESP32 ACM */
+#define PORT_ESP32ACM	125
+
 #endif /* _UAPILINUX_SERIAL_CORE_H */
Signature · 1 line
2.30.2