From a9b43dbf68a4b5650dd98332243d4a2951717eb5 Mon Sep 17 00:00:00 2001 From: Jae Hyun Yoo Date: Mon, 27 Apr 2020 12:11:06 -0700 Subject: [PATCH] Add a workaround to cover UART interrupt bug in AST2600 A0 This commit adds a workaround to cover UART interrupt bug in AST2600 A0 revision. It makes infinite reading on the UART +0x7c register for clearing abnormal interrupts in every milli-second. Signed-off-by: Jae Hyun Yoo --- .../arm/boot/dts/aspeed-bmc-intel-ast2600.dts | 4 ++ drivers/tty/serial/8250/8250_aspeed.c | 56 ++++++++++++++++- drivers/tty/serial/8250/8250_early.c | 1 + drivers/tty/serial/8250/8250_of.c | 63 +++++++++++++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) diff --git a/arch/arm/boot/dts/aspeed-bmc-intel-ast2600.dts b/arch/arm/boot/dts/aspeed-bmc-intel-ast2600.dts index c648c123c315..bf375634082c 100644 --- a/arch/arm/boot/dts/aspeed-bmc-intel-ast2600.dts +++ b/arch/arm/boot/dts/aspeed-bmc-intel-ast2600.dts @@ -392,6 +392,7 @@ }; &uart1 { + reg = <0x1e783000 0x20>, <0x1e6e2014 0x4>, <0x1e78307c 0x4>; status = "okay"; pinctrl-0 = <&pinctrl_txd1_default &pinctrl_rxd1_default @@ -404,6 +405,7 @@ }; &uart2 { + reg = <0x1e78d000 0x20>, <0x1e6e2014 0x4>, <0x1e78d07c 0x4>; status = "okay"; pinctrl-0 = <&pinctrl_txd2_default &pinctrl_rxd2_default @@ -416,11 +418,13 @@ }; &uart3 { + reg = <0x1e78e000 0x20>, <0x1e6e2014 0x4>, <0x1e78e07c 0x4>; status = "okay"; pinctrl-0 = <>; }; &uart4 { + reg = <0x1e78f000 0x20>, <0x1e6e2014 0x4>, <0x1e78f07c 0x4>; status = "okay"; pinctrl-0 = <>; }; diff --git a/drivers/tty/serial/8250/8250_aspeed.c b/drivers/tty/serial/8250/8250_aspeed.c index bb3955bb8c24..1ba4423d5e2f 100644 --- a/drivers/tty/serial/8250/8250_aspeed.c +++ b/drivers/tty/serial/8250/8250_aspeed.c @@ -74,6 +74,10 @@ struct ast8250_data { struct ast8250_vuart vuart; struct ast8250_udma dma; + + struct workqueue_struct *work_queue; + struct delayed_work work_handler; + void __iomem *wa_base; }; static void ast8250_dma_tx_complete(int tx_rb_rptr, void *id) @@ -336,12 +340,25 @@ static int __maybe_unused ast8250_resume(struct device *dev) return 0; } +#define WA_DELAY_JIFFIES msecs_to_jiffies(1) +static void ast8250_clear_abnormal_int_flags(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct ast8250_data *data = container_of(dwork, struct ast8250_data, + work_handler); + + (void) readl(data->wa_base); + queue_delayed_work(data->work_queue, &data->work_handler, + WA_DELAY_JIFFIES); +} + static int ast8250_probe(struct platform_device *pdev) { int rc; struct uart_8250_port uart = {}; struct uart_port *port = &uart.port; struct device *dev = &pdev->dev; + void __iomem *chip_id_base; struct ast8250_data *data; struct resource *res; @@ -454,6 +471,37 @@ static int ast8250_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); platform_set_drvdata(pdev, data); + + #define REV_ID_AST2600A0 0x05000303 + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res || resource_size(res) < 2) + return 0; + + data->wa_base = devm_platform_ioremap_resource(pdev, 2); + if (IS_ERR(data->wa_base)) + return 0; + + chip_id_base = devm_ioremap_resource(dev, res); + if (IS_ERR(chip_id_base)) + return 0; + + if (readl(chip_id_base) == REV_ID_AST2600A0) { + data->work_queue = alloc_ordered_workqueue(pdev->name, 0); + if (data->work_queue) { + INIT_DELAYED_WORK(&data->work_handler, + ast8250_clear_abnormal_int_flags); + queue_delayed_work(data->work_queue, + &data->work_handler, + WA_DELAY_JIFFIES); + dev_info(dev, "AST2600 A0 WA initiated\n"); + } else { + dev_err(dev, "Can't enable AST2600 A0 UART WA\n"); + } + } + + devm_iounmap(dev, chip_id_base); + devm_release_mem_region(dev, res->start, resource_size(res)); + return 0; } @@ -464,7 +512,13 @@ static int ast8250_remove(struct platform_device *pdev) if (data->is_vuart) ast8250_vuart_set_enable(data, false); - serial8250_unregister_port(data->line); + if (data->work_queue) { + cancel_delayed_work_sync(&data->work_handler); + destroy_workqueue(data->work_queue); + } + + serial8250_unregister_port(data->line); + return 0; } diff --git a/drivers/tty/serial/8250/8250_early.c b/drivers/tty/serial/8250/8250_early.c index c171ce6db691..6d7bb63fe0c6 100644 --- a/drivers/tty/serial/8250/8250_early.c +++ b/drivers/tty/serial/8250/8250_early.c @@ -180,6 +180,7 @@ OF_EARLYCON_DECLARE(ns16550, "ns16550", early_serial8250_setup); OF_EARLYCON_DECLARE(ns16550a, "ns16550a", early_serial8250_setup); OF_EARLYCON_DECLARE(uart, "nvidia,tegra20-uart", early_serial8250_setup); OF_EARLYCON_DECLARE(uart, "snps,dw-apb-uart", early_serial8250_setup); +OF_EARLYCON_DECLARE(uart, "aspeed,ast2600-uart", early_serial8250_setup); #ifdef CONFIG_SERIAL_8250_OMAP diff --git a/drivers/tty/serial/8250/8250_of.c b/drivers/tty/serial/8250/8250_of.c index bce28729dd7b..6159d8af6fef 100644 --- a/drivers/tty/serial/8250/8250_of.c +++ b/drivers/tty/serial/8250/8250_of.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "8250.h" @@ -23,6 +24,9 @@ struct of_serial_info { struct reset_control *rst; int type; int line; + struct workqueue_struct *work_queue; + struct delayed_work work_handler; + void __iomem *wa_base; }; /* @@ -181,6 +185,18 @@ static int of_platform_serial_setup(struct platform_device *ofdev, return ret; } +#define WA_DELAY_JIFFIES msecs_to_jiffies(1) +static void clear_abnormal_int_flags(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct of_serial_info *info = container_of(dwork, struct of_serial_info, + work_handler); + + (void) readl(info->wa_base); + queue_delayed_work(info->work_queue, &info->work_handler, + WA_DELAY_JIFFIES); +} + /* * Try to register a serial port */ @@ -233,6 +249,47 @@ static int of_platform_serial_probe(struct platform_device *ofdev) if (ret < 0) goto err_dispose; + if (of_device_is_compatible(ofdev->dev.of_node, + "aspeed,ast2600-uart")) { + #define REV_ID_AST2600A0 0x05000303 + void __iomem *chip_id_base; + struct resource *res = platform_get_resource(ofdev, + IORESOURCE_MEM, 1); + + if (!res || resource_size(res) < 2) + goto skip_wa; + + info->wa_base = devm_platform_ioremap_resource(ofdev, 2); + if (IS_ERR(info->wa_base)) + goto skip_wa; + + chip_id_base = devm_ioremap_resource(&ofdev->dev, res); + if (IS_ERR(chip_id_base)) + goto skip_wa; + + if (readl(chip_id_base) == REV_ID_AST2600A0) { + info->work_queue = alloc_ordered_workqueue(ofdev->name, + 0); + if (info->work_queue) { + INIT_DELAYED_WORK(&info->work_handler, + clear_abnormal_int_flags); + queue_delayed_work(info->work_queue, + &info->work_handler, + WA_DELAY_JIFFIES); + dev_info(&ofdev->dev, + "AST2600 A0 WA initiated\n"); + } else { + dev_err(&ofdev->dev, + "Can't enable AST2600 A0 UART WA\n"); + } + } + + devm_iounmap(&ofdev->dev, chip_id_base); + devm_release_mem_region(&ofdev->dev, res->start, + resource_size(res)); + } + +skip_wa: info->type = port_type; info->line = ret; platform_set_drvdata(ofdev, info); @@ -254,6 +311,11 @@ static int of_platform_serial_remove(struct platform_device *ofdev) { struct of_serial_info *info = platform_get_drvdata(ofdev); + if (info->work_queue) { + cancel_delayed_work_sync(&info->work_handler); + destroy_workqueue(info->work_queue); + } + serial8250_unregister_port(info->line); reset_control_assert(info->rst); @@ -324,6 +386,7 @@ static const struct of_device_id of_platform_serial_table[] = { { .compatible = "ti,da830-uart", .data = (void *)PORT_DA830, }, { .compatible = "nuvoton,wpcm450-uart", .data = (void *)PORT_NPCM, }, { .compatible = "nuvoton,npcm750-uart", .data = (void *)PORT_NPCM, }, + { .compatible = "aspeed,ast2600-uart", .data = (void *)PORT_16550A, }, { /* end of list */ }, }; MODULE_DEVICE_TABLE(of, of_platform_serial_table); -- 2.17.1