Subject: [PATCH v4 10/11] drivers: PL011: add support for the ARM SBSA generic UART

From: [email-masked] (Mark Langsdorf)
Date: Wed, 13 May 2015 09:32:54 -0500

On 05/12/2015 09:14 AM, Andre Przywara wrote:
> The ARM Server Base System Architecture[1] document describes a
> generic UART which is a subset of the PL011 UART.
> It lacks DMA support, baud rate control and modem status line
> control, among other things.
> The idea is to move the UART initialization and setup into the
> firmware (which does this job today already) and let the kernel just
> use the UART for sending and receiving characters.
>
> We use the recent refactoring to build a new struct uart_ops
> variable which points to some new functions avoiding access to the
> missing registers. We reuse as much existing PL011 code as possible.
>
> In contrast to the PL011 the SBSA UART does not define any AMBA or
> PrimeCell relations, so we go with a pretty generic probe function
> which only uses platform device functions.
> A DT binding is provided with this patch, ACPI support is added in a
> separate one.
>
> Signed-off-by: Andre Przywara <[email-masked]>
> ---
>   .../devicetree/bindings/serial/arm_sbsa_uart.txt   |  10 ++
>   drivers/tty/serial/amba-pl011.c                    | 168 +++++++++++++++++++++
>   2 files changed, 178 insertions(+)
>   create mode 100644 Documentation/devicetree/bindings/serial/arm_sbsa_uart.txt
>
> diff --git a/Documentation/devicetree/bindings/serial/arm_sbsa_uart.txt b/Documentation/devicetree/bindings/serial/arm_sbsa_uart.txt
> new file mode 100644
> index 0000000..4163e7e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serial/arm_sbsa_uart.txt
> @@ -0,0 +1,10 @@
> +* ARM SBSA defined generic UART
> +This UART uses a subset of the PL011 registers and consequently lives
> +in the PL011 driver. It's baudrate and other communication parameters
> +cannot be adjusted at runtime, so it lacks a clock specifier here.
> +
> +Required properties:
> +- compatible: must be "arm,sbsa-uart"
> +- reg: exactly one register range
> +- interrupts: exactly one interrupt specifier
> +- current-speed: the (fixed) baud rate set by the firmware
> diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c
> index 70e2958..cca93d9 100644
> --- a/drivers/tty/serial/amba-pl011.c
> +++ b/drivers/tty/serial/amba-pl011.c
> @@ -101,6 +101,14 @@ static struct vendor_data vendor_arm = {
>   	.get_fifosize		= get_fifosize_arm,
>   };
>
> +static struct vendor_data vendor_sbsa = {
> +	.oversampling		= false,
> +	.dma_threshold		= false,
> +	.cts_event_workaround	= false,
> +	.always_enabled		= true,
> +	.fixed_options		= true,
> +};
> +
>   static unsigned int get_fifosize_st(struct amba_device *dev)
>   {
>   	return 64;
> @@ -1641,6 +1649,28 @@ static int pl011_startup(struct uart_port *port)
>   	return retval;
>   }
>
> +static int sbsa_uart_startup(struct uart_port *port)
> +{
> +	struct uart_amba_port *uap =
> +		container_of(port, struct uart_amba_port, port);
> +	int retval;
> +
> +	retval = pl011_hwinit(port);
> +	if (retval)
> +		return retval;
> +
> +	retval = pl011_allocate_irq(uap);
> +	if (retval)
> +		return retval;
> +
> +	/* The SBSA UART does not support any modem status lines. */
> +	uap->old_status = 0;
> +
> +	pl011_enable_interrupts(uap);
> +
> +	return 0;
> +}
> +
>   static void pl011_shutdown_channel(struct uart_amba_port *uap,
>   					unsigned int lcrh)
>   {
> @@ -1721,6 +1751,19 @@ static void pl011_shutdown(struct uart_port *port)
>   		uap->port.ops->flush_buffer(port);
>   }
>
> +static void sbsa_uart_shutdown(struct uart_port *port)
> +{
> +	struct uart_amba_port *uap =
> +		container_of(port, struct uart_amba_port, port);
> +
> +	pl011_disable_interrupts(uap);
> +
> +	free_irq(uap->port.irq, uap);
> +
> +	if (uap->port.ops->flush_buffer)
> +		uap->port.ops->flush_buffer(port);
> +}
> +
>   static void
>   pl011_setup_status_masks(struct uart_port *port, struct ktermios *termios)
>   {
> @@ -1872,6 +1915,24 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios,
>   	spin_unlock_irqrestore(&port->lock, flags);
>   }
>
> +static void
> +sbsa_uart_set_termios(struct uart_port *port, struct ktermios *termios,
> +		      struct ktermios *old)
> +{
> +	struct uart_amba_port *uap =
> +	    container_of(port, struct uart_amba_port, port);
> +	unsigned long flags;
> +
> +	if (old)
> +		tty_termios_copy_hw(termios, old);

This code prevented login via the serial console on our test hardware.

Mark Salter suggested the following patch:

-       if (old)
+       /*
+        * The first call to set_termios() comes when the console is
+        * registered via uart_add_one_port(). The serial core will
+        * pass in a dummy old termios rather than NULL. Check to make
+        * sure the old termios has reasonable info before copying from
+        * it.
+        */
+       if (old && old->c_cflag)
                 tty_termios_copy_hw(termios, old);

which fixed the issue.

> +	tty_termios_encode_baud_rate(termios, uap->fixed_baud, uap->fixed_baud);
> +
> +	spin_lock_irqsave(&port->lock, flags);
> +	uart_update_timeout(port, CS8, uap->fixed_baud);
> +	pl011_setup_status_masks(port, termios);
> +	spin_unlock_irqrestore(&port->lock, flags);
> +}
> +
>   static const char *pl011_type(struct uart_port *port)
>   {
>   	struct uart_amba_port *uap =
> @@ -1947,6 +2008,37 @@ static struct uart_ops amba_pl011_pops = {
>   #endif
>   };
>
> +static void sbsa_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
> +{
> +}
> +
> +static unsigned int sbsa_uart_get_mctrl(struct uart_port *port)
> +{
> +	return 0;
> +}
> +
> +static const struct uart_ops sbsa_uart_pops = {
> +	.tx_empty	= pl011_tx_empty,
> +	.set_mctrl	= sbsa_uart_set_mctrl,
> +	.get_mctrl	= sbsa_uart_get_mctrl,
> +	.stop_tx	= pl011_stop_tx,
> +	.start_tx	= pl011_start_tx,
> +	.stop_rx	= pl011_stop_rx,
> +	.startup	= sbsa_uart_startup,
> +	.shutdown	= sbsa_uart_shutdown,
> +	.set_termios	= sbsa_uart_set_termios,
> +	.type		= pl011_type,
> +	.release_port	= pl011_release_port,
> +	.request_port	= pl011_request_port,
> +	.config_port	= pl011_config_port,
> +	.verify_port	= pl011_verify_port,
> +#ifdef CONFIG_CONSOLE_POLL
> +	.poll_init     = pl011_hwinit,
> +	.poll_get_char = pl011_get_poll_char,
> +	.poll_put_char = pl011_put_poll_char,
> +#endif
> +};
> +
>   static struct uart_amba_port *amba_ports[UART_NR];
>
>   #ifdef CONFIG_SERIAL_AMBA_PL011_CONSOLE
> @@ -2327,6 +2419,79 @@ static int pl011_resume(struct device *dev)
>
>   static SIMPLE_DEV_PM_OPS(pl011_dev_pm_ops, pl011_suspend, pl011_resume);
>
> +static int sbsa_uart_probe(struct platform_device *pdev)
> +{
> +	struct uart_amba_port *uap;
> +	struct resource *r;
> +	int portnr, ret;
> +	int baudrate;
> +
> +	/*
> +	 * Check the mandatory baud rate parameter in the DT node early
> +	 * so that we can easily exit with the error.
> +	 */
> +	if (pdev->dev.of_node) {
> +		struct device_node *np = pdev->dev.of_node;
> +
> +		ret = of_property_read_u32(np, "current-speed", &baudrate);
> +		if (ret)
> +			return ret;
> +	} else {
> +		baudrate = 115200;
> +	}
> +
> +	portnr = pl011_find_free_port();
> +	if (portnr < 0)
> +		return portnr;
> +
> +	uap = devm_kzalloc(&pdev->dev, sizeof(struct uart_amba_port),
> +			   GFP_KERNEL);
> +	if (uap == NULL)
> +		return -ENOMEM;
> +
> +	uap->vendor	= &vendor_sbsa;
> +	uap->fifosize	= 32;
> +	uap->port.irq	= platform_get_irq(pdev, 0);
> +	uap->port.ops	= &sbsa_uart_pops;
> +	uap->fixed_baud = baudrate;
> +
> +	snprintf(uap->type, sizeof(uap->type), "SBSA");
> +
> +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +	ret = pl011_setup_port(&pdev->dev, uap, r, portnr);
> +	if (ret)
> +		return ret;
> +
> +	platform_set_drvdata(pdev, uap);
> +
> +	return pl011_register_port(uap);
> +}
> +
> +static int sbsa_uart_remove(struct platform_device *pdev)
> +{
> +	struct uart_amba_port *uap = platform_get_drvdata(pdev);
> +
> +	uart_remove_one_port(&amba_reg, &uap->port);
> +	pl011_unregister_port(uap);
> +	return 0;
> +}
> +
> +static const struct of_device_id sbsa_uart_of_match[] = {
> +	{ .compatible = "arm,sbsa-uart", },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, sbsa_uart_of_match);
> +
> +static struct platform_driver arm_sbsa_uart_platform_driver = {
> +	.probe		= sbsa_uart_probe,
> +	.remove		= sbsa_uart_remove,
> +	.driver	= {
> +		.name	= "sbsa-uart",
> +		.of_match_table = of_match_ptr(sbsa_uart_of_match),
> +	},
> +};
> +
>   static struct amba_id pl011_ids[] = {
>   	{
>   		.id	= 0x00041011,
> @@ -2357,11 +2522,14 @@ static int __init pl011_init(void)
>   {
>   	printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");
>
> +	if (platform_driver_register(&arm_sbsa_uart_platform_driver))
> +		pr_warn("could not register SBSA UART platform driver\n");
>   	return amba_driver_register(&pl011_driver);
>   }
>
>   static void __exit pl011_exit(void)
>   {
> +	platform_driver_unregister(&arm_sbsa_uart_platform_driver);
>   	amba_driver_unregister(&pl011_driver);
>   }

--Mark Langsdorf