diff options
Diffstat (limited to 'drivers/tty/serial/8250/8250_bcm2835aux.c')
-rw-r--r-- | drivers/tty/serial/8250/8250_bcm2835aux.c | 147 |
1 files changed, 93 insertions, 54 deletions
diff --git a/drivers/tty/serial/8250/8250_bcm2835aux.c b/drivers/tty/serial/8250/8250_bcm2835aux.c index beac6b340ace..0609582a62f7 100644 --- a/drivers/tty/serial/8250/8250_bcm2835aux.c +++ b/drivers/tty/serial/8250/8250_bcm2835aux.c @@ -13,6 +13,7 @@ */ #include <linux/clk.h> +#include <linux/console.h> #include <linux/io.h> #include <linux/module.h> #include <linux/of.h> @@ -45,11 +46,7 @@ struct bcm2835aux_data { u32 cntl; }; -struct bcm2835_aux_serial_driver_data { - resource_size_t offset; -}; - -static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up) +static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up, bool toggle_ier) { if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX)) { struct bcm2835aux_data *data = dev_get_drvdata(up->port.dev); @@ -68,7 +65,7 @@ static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up) serial8250_out_MCR(up, UART_MCR_RTS); } -static void bcm2835aux_rs485_stop_tx(struct uart_8250_port *up) +static void bcm2835aux_rs485_stop_tx(struct uart_8250_port *up, bool toggle_ier) { if (up->port.rs485.flags & SER_RS485_RTS_AFTER_SEND) serial8250_out_MCR(up, 0); @@ -85,10 +82,9 @@ static void bcm2835aux_rs485_stop_tx(struct uart_8250_port *up) static int bcm2835aux_serial_probe(struct platform_device *pdev) { - const struct bcm2835_aux_serial_driver_data *bcm_data; + const struct software_node *bcm2835_swnode; struct uart_8250_port up = { }; struct bcm2835aux_data *data; - resource_size_t offset = 0; struct resource *res; unsigned int uartclk; int ret; @@ -101,12 +97,8 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev) /* initialize data */ up.capabilities = UART_CAP_FIFO | UART_CAP_MINI; up.port.dev = &pdev->dev; - up.port.regshift = 2; up.port.type = PORT_16550; - up.port.iotype = UPIO_MEM; - up.port.fifosize = 8; - up.port.flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE | - UPF_SKIP_TEST | UPF_IOREMAP; + up.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SKIP_TEST | UPF_IOREMAP; up.port.rs485_config = serial8250_em485_config; up.port.rs485_supported = serial8250_em485_supported; up.rs485_start_tx = bcm2835aux_rs485_start_tx; @@ -122,12 +114,6 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev) if (IS_ERR(data->clk)) return dev_err_probe(&pdev->dev, PTR_ERR(data->clk), "could not get clk\n"); - /* get the interrupt */ - ret = platform_get_irq(pdev, 0); - if (ret < 0) - return ret; - up.port.irq = ret; - /* map the main registers */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { @@ -135,52 +121,40 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev) return -EINVAL; } - bcm_data = device_get_match_data(&pdev->dev); - - /* Some UEFI implementations (e.g. tianocore/edk2 for the Raspberry Pi) - * describe the miniuart with a base address that encompasses the auxiliary - * registers shared between the miniuart and spi. - * - * This is due to historical reasons, see discussion here : - * https://edk2.groups.io/g/devel/topic/87501357#84349 - * - * We need to add the offset between the miniuart and auxiliary - * registers to get the real miniuart base address. - */ - if (bcm_data) - offset = bcm_data->offset; + up.port.mapbase = res->start; + up.port.mapsize = resource_size(res); - up.port.mapbase = res->start + offset; - up.port.mapsize = resource_size(res) - offset; + bcm2835_swnode = device_get_match_data(&pdev->dev); + if (bcm2835_swnode) { + ret = device_add_software_node(&pdev->dev, bcm2835_swnode); + if (ret) + return ret; + } - /* Check for a fixed line number */ - ret = of_alias_get_id(pdev->dev.of_node, "serial"); - if (ret >= 0) - up.port.line = ret; + ret = uart_read_port_properties(&up.port); + if (ret) + goto rm_swnode; + + up.port.regshift = 2; + up.port.fifosize = 8; /* enable the clock as a last step */ ret = clk_prepare_enable(data->clk); if (ret) { - dev_err(&pdev->dev, "unable to enable uart clock - %d\n", - ret); - return ret; + dev_err_probe(&pdev->dev, ret, "unable to enable uart clock\n"); + goto rm_swnode; } uartclk = clk_get_rate(data->clk); - if (!uartclk) { - ret = device_property_read_u32(&pdev->dev, "clock-frequency", &uartclk); - if (ret) { - dev_err_probe(&pdev->dev, ret, "could not get clk rate\n"); - goto dis_clk; - } - } + if (uartclk) + up.port.uartclk = uartclk; /* the HW-clock divider for bcm2835aux is 8, * but 8250 expects a divider of 16, * so we have to multiply the actual clock by 2 * to get identical baudrates. */ - up.port.uartclk = uartclk * 2; + up.port.uartclk *= 2; /* register the port */ ret = serial8250_register_8250_port(&up); @@ -194,6 +168,8 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev) dis_clk: clk_disable_unprepare(data->clk); +rm_swnode: + device_remove_software_node(&pdev->dev); return ret; } @@ -203,10 +179,27 @@ static void bcm2835aux_serial_remove(struct platform_device *pdev) serial8250_unregister_port(data->line); clk_disable_unprepare(data->clk); + device_remove_software_node(&pdev->dev); } -static const struct bcm2835_aux_serial_driver_data bcm2835_acpi_data = { - .offset = 0x40, +/* + * Some UEFI implementations (e.g. tianocore/edk2 for the Raspberry Pi) + * describe the miniuart with a base address that encompasses the auxiliary + * registers shared between the miniuart and spi. + * + * This is due to historical reasons, see discussion here: + * https://edk2.groups.io/g/devel/topic/87501357#84349 + * + * We need to add the offset between the miniuart and auxiliary registers + * to get the real miniuart base address. + */ +static const struct property_entry bcm2835_acpi_properties[] = { + PROPERTY_ENTRY_U32("reg-offset", 0x40), + { } +}; + +static const struct software_node bcm2835_acpi_node = { + .properties = bcm2835_acpi_properties, }; static const struct of_device_id bcm2835aux_serial_match[] = { @@ -216,19 +209,65 @@ static const struct of_device_id bcm2835aux_serial_match[] = { MODULE_DEVICE_TABLE(of, bcm2835aux_serial_match); static const struct acpi_device_id bcm2835aux_serial_acpi_match[] = { - { "BCM2836", (kernel_ulong_t)&bcm2835_acpi_data }, + { "BCM2836", (kernel_ulong_t)&bcm2835_acpi_node }, { } }; MODULE_DEVICE_TABLE(acpi, bcm2835aux_serial_acpi_match); +static bool bcm2835aux_can_disable_clock(struct device *dev) +{ + struct bcm2835aux_data *data = dev_get_drvdata(dev); + struct uart_8250_port *up = serial8250_get_port(data->line); + + if (device_may_wakeup(dev)) + return false; + + if (uart_console(&up->port) && !console_suspend_enabled) + return false; + + return true; +} + +static int bcm2835aux_suspend(struct device *dev) +{ + struct bcm2835aux_data *data = dev_get_drvdata(dev); + + serial8250_suspend_port(data->line); + + if (!bcm2835aux_can_disable_clock(dev)) + return 0; + + clk_disable_unprepare(data->clk); + return 0; +} + +static int bcm2835aux_resume(struct device *dev) +{ + struct bcm2835aux_data *data = dev_get_drvdata(dev); + int ret; + + if (bcm2835aux_can_disable_clock(dev)) { + ret = clk_prepare_enable(data->clk); + if (ret) + return ret; + } + + serial8250_resume_port(data->line); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(bcm2835aux_dev_pm_ops, bcm2835aux_suspend, bcm2835aux_resume); + static struct platform_driver bcm2835aux_serial_driver = { .driver = { .name = "bcm2835-aux-uart", .of_match_table = bcm2835aux_serial_match, .acpi_match_table = bcm2835aux_serial_acpi_match, + .pm = pm_ptr(&bcm2835aux_dev_pm_ops), }, .probe = bcm2835aux_serial_probe, - .remove_new = bcm2835aux_serial_remove, + .remove = bcm2835aux_serial_remove, }; module_platform_driver(bcm2835aux_serial_driver); |