1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * arch/arm/mach-lpc32xx/serial.c |
4 | * |
5 | * Author: Kevin Wells <kevin.wells@nxp.com> |
6 | * |
7 | * Copyright (C) 2010 NXP Semiconductors |
8 | */ |
9 | |
10 | #include <linux/kernel.h> |
11 | #include <linux/types.h> |
12 | #include <linux/serial.h> |
13 | #include <linux/serial_core.h> |
14 | #include <linux/serial_reg.h> |
15 | #include <linux/serial_8250.h> |
16 | #include <linux/clk.h> |
17 | #include <linux/io.h> |
18 | #include <linux/soc/nxp/lpc32xx-misc.h> |
19 | |
20 | #include "lpc32xx.h" |
21 | #include "common.h" |
22 | |
23 | #define LPC32XX_SUART_FIFO_SIZE 64 |
24 | |
25 | struct uartinit { |
26 | char *uart_ck_name; |
27 | u32 ck_mode_mask; |
28 | void __iomem *pdiv_clk_reg; |
29 | resource_size_t mapbase; |
30 | }; |
31 | |
32 | static struct uartinit uartinit_data[] __initdata = { |
33 | { |
34 | .uart_ck_name = "uart5_ck" , |
35 | .ck_mode_mask = |
36 | LPC32XX_UART_CLKMODE_LOAD(LPC32XX_UART_CLKMODE_ON, 5), |
37 | .pdiv_clk_reg = LPC32XX_CLKPWR_UART5_CLK_CTRL, |
38 | .mapbase = LPC32XX_UART5_BASE, |
39 | }, |
40 | { |
41 | .uart_ck_name = "uart3_ck" , |
42 | .ck_mode_mask = |
43 | LPC32XX_UART_CLKMODE_LOAD(LPC32XX_UART_CLKMODE_ON, 3), |
44 | .pdiv_clk_reg = LPC32XX_CLKPWR_UART3_CLK_CTRL, |
45 | .mapbase = LPC32XX_UART3_BASE, |
46 | }, |
47 | { |
48 | .uart_ck_name = "uart4_ck" , |
49 | .ck_mode_mask = |
50 | LPC32XX_UART_CLKMODE_LOAD(LPC32XX_UART_CLKMODE_ON, 4), |
51 | .pdiv_clk_reg = LPC32XX_CLKPWR_UART4_CLK_CTRL, |
52 | .mapbase = LPC32XX_UART4_BASE, |
53 | }, |
54 | { |
55 | .uart_ck_name = "uart6_ck" , |
56 | .ck_mode_mask = |
57 | LPC32XX_UART_CLKMODE_LOAD(LPC32XX_UART_CLKMODE_ON, 6), |
58 | .pdiv_clk_reg = LPC32XX_CLKPWR_UART6_CLK_CTRL, |
59 | .mapbase = LPC32XX_UART6_BASE, |
60 | }, |
61 | }; |
62 | |
63 | /* LPC3250 Errata HSUART.1: Hang workaround via loopback mode on inactivity */ |
64 | void lpc32xx_loopback_set(resource_size_t mapbase, int state) |
65 | { |
66 | int bit; |
67 | u32 tmp; |
68 | |
69 | switch (mapbase) { |
70 | case LPC32XX_HS_UART1_BASE: |
71 | bit = 0; |
72 | break; |
73 | case LPC32XX_HS_UART2_BASE: |
74 | bit = 1; |
75 | break; |
76 | case LPC32XX_HS_UART7_BASE: |
77 | bit = 6; |
78 | break; |
79 | default: |
80 | WARN(1, "lpc32xx_hs: Warning: Unknown port at %08x\n" , mapbase); |
81 | return; |
82 | } |
83 | |
84 | tmp = readl(LPC32XX_UARTCTL_CLOOP); |
85 | if (state) |
86 | tmp |= (1 << bit); |
87 | else |
88 | tmp &= ~(1 << bit); |
89 | writel(val: tmp, LPC32XX_UARTCTL_CLOOP); |
90 | } |
91 | EXPORT_SYMBOL_GPL(lpc32xx_loopback_set); |
92 | |
93 | void __init lpc32xx_serial_init(void) |
94 | { |
95 | u32 tmp, clkmodes = 0; |
96 | struct clk *clk; |
97 | unsigned int puart; |
98 | int i, j; |
99 | |
100 | for (i = 0; i < ARRAY_SIZE(uartinit_data); i++) { |
101 | clk = clk_get(NULL, id: uartinit_data[i].uart_ck_name); |
102 | if (!IS_ERR(ptr: clk)) { |
103 | clk_enable(clk); |
104 | } |
105 | |
106 | /* Setup UART clock modes for all UARTs, disable autoclock */ |
107 | clkmodes |= uartinit_data[i].ck_mode_mask; |
108 | |
109 | /* pre-UART clock divider set to 1 */ |
110 | __raw_writel(val: 0x0101, addr: uartinit_data[i].pdiv_clk_reg); |
111 | |
112 | /* |
113 | * Force a flush of the RX FIFOs to work around a |
114 | * HW bug |
115 | */ |
116 | puart = uartinit_data[i].mapbase; |
117 | __raw_writel(val: 0xC1, LPC32XX_UART_IIR_FCR(puart)); |
118 | __raw_writel(val: 0x00, LPC32XX_UART_DLL_FIFO(puart)); |
119 | j = LPC32XX_SUART_FIFO_SIZE; |
120 | while (j--) |
121 | tmp = __raw_readl( |
122 | LPC32XX_UART_DLL_FIFO(puart)); |
123 | __raw_writel(val: 0, LPC32XX_UART_IIR_FCR(puart)); |
124 | } |
125 | |
126 | /* This needs to be done after all UART clocks are setup */ |
127 | __raw_writel(val: clkmodes, LPC32XX_UARTCTL_CLKMODE); |
128 | for (i = 0; i < ARRAY_SIZE(uartinit_data); i++) { |
129 | /* Force a flush of the RX FIFOs to work around a HW bug */ |
130 | puart = uartinit_data[i].mapbase; |
131 | __raw_writel(val: 0xC1, LPC32XX_UART_IIR_FCR(puart)); |
132 | __raw_writel(val: 0x00, LPC32XX_UART_DLL_FIFO(puart)); |
133 | j = LPC32XX_SUART_FIFO_SIZE; |
134 | while (j--) |
135 | tmp = __raw_readl(LPC32XX_UART_DLL_FIFO(puart)); |
136 | __raw_writel(val: 0, LPC32XX_UART_IIR_FCR(puart)); |
137 | } |
138 | |
139 | /* Disable IrDA pulsing support on UART6 */ |
140 | tmp = __raw_readl(LPC32XX_UARTCTL_CTRL); |
141 | tmp |= LPC32XX_UART_UART6_IRDAMOD_BYPASS; |
142 | __raw_writel(val: tmp, LPC32XX_UARTCTL_CTRL); |
143 | |
144 | /* Disable UART5->USB transparent mode or USB won't work */ |
145 | tmp = __raw_readl(LPC32XX_UARTCTL_CTRL); |
146 | tmp &= ~LPC32XX_UART_U5_ROUTE_TO_USB; |
147 | __raw_writel(val: tmp, LPC32XX_UARTCTL_CTRL); |
148 | } |
149 | |