Commit | Line | Data |
---|---|---|
136e0ab9 AS |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* Synopsys DesignWare 8250 library. */ | |
3 | ||
4 | #include <linux/bitops.h> | |
642aa760 | 5 | #include <linux/bitfield.h> |
f287f971 | 6 | #include <linux/delay.h> |
136e0ab9 | 7 | #include <linux/device.h> |
136e0ab9 | 8 | #include <linux/kernel.h> |
f287f971 | 9 | #include <linux/math.h> |
642aa760 | 10 | #include <linux/property.h> |
136e0ab9 AS |
11 | #include <linux/serial_8250.h> |
12 | #include <linux/serial_core.h> | |
13 | ||
14 | #include "8250_dwlib.h" | |
15 | ||
16 | /* Offsets for the DesignWare specific registers */ | |
642aa760 IJ |
17 | #define DW_UART_TCR 0xac /* Transceiver Control Register (RS485) */ |
18 | #define DW_UART_DE_EN 0xb0 /* Driver Output Enable Register */ | |
19 | #define DW_UART_RE_EN 0xb4 /* Receiver Output Enable Register */ | |
136e0ab9 | 20 | #define DW_UART_DLF 0xc0 /* Divisor Latch Fraction Register */ |
f287f971 IJ |
21 | #define DW_UART_RAR 0xc4 /* Receive Address Register */ |
22 | #define DW_UART_TAR 0xc8 /* Transmit Address Register */ | |
23 | #define DW_UART_LCR_EXT 0xcc /* Line Extended Control Register */ | |
136e0ab9 AS |
24 | #define DW_UART_CPR 0xf4 /* Component Parameter Register */ |
25 | #define DW_UART_UCV 0xf8 /* UART Component Version */ | |
26 | ||
f287f971 IJ |
27 | /* Receive / Transmit Address Register bits */ |
28 | #define DW_UART_ADDR_MASK GENMASK(7, 0) | |
29 | ||
30 | /* Line Status Register bits */ | |
31 | #define DW_UART_LSR_ADDR_RCVD BIT(8) | |
32 | ||
642aa760 IJ |
33 | /* Transceiver Control Register bits */ |
34 | #define DW_UART_TCR_RS485_EN BIT(0) | |
35 | #define DW_UART_TCR_RE_POL BIT(1) | |
36 | #define DW_UART_TCR_DE_POL BIT(2) | |
37 | #define DW_UART_TCR_XFER_MODE GENMASK(4, 3) | |
38 | #define DW_UART_TCR_XFER_MODE_DE_DURING_RE FIELD_PREP(DW_UART_TCR_XFER_MODE, 0) | |
39 | #define DW_UART_TCR_XFER_MODE_SW_DE_OR_RE FIELD_PREP(DW_UART_TCR_XFER_MODE, 1) | |
40 | #define DW_UART_TCR_XFER_MODE_DE_OR_RE FIELD_PREP(DW_UART_TCR_XFER_MODE, 2) | |
41 | ||
f287f971 IJ |
42 | /* Line Extended Control Register bits */ |
43 | #define DW_UART_LCR_EXT_DLS_E BIT(0) | |
44 | #define DW_UART_LCR_EXT_ADDR_MATCH BIT(1) | |
45 | #define DW_UART_LCR_EXT_SEND_ADDR BIT(2) | |
46 | #define DW_UART_LCR_EXT_TRANSMIT_MODE BIT(3) | |
47 | ||
136e0ab9 | 48 | /* Component Parameter Register bits */ |
e9f97366 IJ |
49 | #define DW_UART_CPR_ABP_DATA_WIDTH GENMASK(1, 0) |
50 | #define DW_UART_CPR_AFCE_MODE BIT(4) | |
51 | #define DW_UART_CPR_THRE_MODE BIT(5) | |
52 | #define DW_UART_CPR_SIR_MODE BIT(6) | |
53 | #define DW_UART_CPR_SIR_LP_MODE BIT(7) | |
54 | #define DW_UART_CPR_ADDITIONAL_FEATURES BIT(8) | |
55 | #define DW_UART_CPR_FIFO_ACCESS BIT(9) | |
56 | #define DW_UART_CPR_FIFO_STAT BIT(10) | |
57 | #define DW_UART_CPR_SHADOW BIT(11) | |
58 | #define DW_UART_CPR_ENCODED_PARMS BIT(12) | |
59 | #define DW_UART_CPR_DMA_EXTRA BIT(13) | |
60 | #define DW_UART_CPR_FIFO_MODE GENMASK(23, 16) | |
136e0ab9 AS |
61 | |
62 | /* Helper for FIFO size calculation */ | |
e9f97366 | 63 | #define DW_UART_CPR_FIFO_SIZE(a) (FIELD_GET(DW_UART_CPR_FIFO_MODE, (a)) * 16) |
136e0ab9 | 64 | |
136e0ab9 AS |
65 | /* |
66 | * divisor = div(I) + div(F) | |
67 | * "I" means integer, "F" means fractional | |
68 | * quot = div(I) = clk / (16 * baud) | |
69 | * frac = div(F) * 2^dlf_size | |
70 | * | |
71 | * let rem = clk % (16 * baud) | |
72 | * we have: div(F) * (16 * baud) = rem | |
73 | * so frac = 2^dlf_size * rem / (16 * baud) = (rem << dlf_size) / (16 * baud) | |
74 | */ | |
75 | static unsigned int dw8250_get_divisor(struct uart_port *p, unsigned int baud, | |
76 | unsigned int *frac) | |
77 | { | |
78 | unsigned int quot, rem, base_baud = baud * 16; | |
79 | struct dw8250_port_data *d = p->private_data; | |
80 | ||
81 | quot = p->uartclk / base_baud; | |
82 | rem = p->uartclk % base_baud; | |
83 | *frac = DIV_ROUND_CLOSEST(rem << d->dlf_size, base_baud); | |
84 | ||
85 | return quot; | |
86 | } | |
87 | ||
88 | static void dw8250_set_divisor(struct uart_port *p, unsigned int baud, | |
89 | unsigned int quot, unsigned int quot_frac) | |
90 | { | |
91 | dw8250_writel_ext(p, DW_UART_DLF, quot_frac); | |
92 | serial8250_do_set_divisor(p, baud, quot, quot_frac); | |
93 | } | |
94 | ||
bec5b814 IJ |
95 | void dw8250_do_set_termios(struct uart_port *p, struct ktermios *termios, |
96 | const struct ktermios *old) | |
7c4fc082 AS |
97 | { |
98 | p->status &= ~UPSTAT_AUTOCTS; | |
99 | if (termios->c_cflag & CRTSCTS) | |
100 | p->status |= UPSTAT_AUTOCTS; | |
101 | ||
102 | serial8250_do_set_termios(p, termios, old); | |
f287f971 IJ |
103 | |
104 | /* Filter addresses which have 9th bit set */ | |
105 | p->ignore_status_mask |= DW_UART_LSR_ADDR_RCVD; | |
106 | p->read_status_mask |= DW_UART_LSR_ADDR_RCVD; | |
7c4fc082 AS |
107 | } |
108 | EXPORT_SYMBOL_GPL(dw8250_do_set_termios); | |
109 | ||
f287f971 IJ |
110 | /* |
111 | * Wait until re is de-asserted for sure. An ongoing receive will keep | |
112 | * re asserted until end of frame. Without BUSY indication available, | |
113 | * only available course of action is to wait for the time it takes to | |
114 | * receive one frame (there might nothing to receive but w/o BUSY the | |
115 | * driver cannot know). | |
116 | */ | |
117 | static void dw8250_wait_re_deassert(struct uart_port *p) | |
118 | { | |
119 | ndelay(p->frame_time); | |
120 | } | |
121 | ||
122 | static void dw8250_update_rar(struct uart_port *p, u32 addr) | |
123 | { | |
124 | u32 re_en = dw8250_readl_ext(p, DW_UART_RE_EN); | |
125 | ||
126 | /* | |
127 | * RAR shouldn't be changed while receiving. Thus, de-assert RE_EN | |
128 | * if asserted and wait. | |
129 | */ | |
130 | if (re_en) | |
131 | dw8250_writel_ext(p, DW_UART_RE_EN, 0); | |
132 | dw8250_wait_re_deassert(p); | |
133 | dw8250_writel_ext(p, DW_UART_RAR, addr); | |
134 | if (re_en) | |
135 | dw8250_writel_ext(p, DW_UART_RE_EN, re_en); | |
136 | } | |
137 | ||
138 | static void dw8250_rs485_set_addr(struct uart_port *p, struct serial_rs485 *rs485, | |
139 | struct ktermios *termios) | |
140 | { | |
141 | u32 lcr = dw8250_readl_ext(p, DW_UART_LCR_EXT); | |
142 | ||
143 | if (rs485->flags & SER_RS485_ADDRB) { | |
144 | lcr |= DW_UART_LCR_EXT_DLS_E; | |
145 | if (termios) | |
146 | termios->c_cflag |= ADDRB; | |
147 | ||
148 | if (rs485->flags & SER_RS485_ADDR_RECV) { | |
149 | u32 delta = p->rs485.flags ^ rs485->flags; | |
150 | ||
151 | /* | |
152 | * rs485 (param) is equal to uart_port's rs485 only during init | |
153 | * (during init, delta is not yet applicable). | |
154 | */ | |
155 | if (unlikely(&p->rs485 == rs485)) | |
156 | delta = rs485->flags; | |
157 | ||
158 | if ((delta & SER_RS485_ADDR_RECV) || | |
159 | (p->rs485.addr_recv != rs485->addr_recv)) | |
160 | dw8250_update_rar(p, rs485->addr_recv); | |
161 | lcr |= DW_UART_LCR_EXT_ADDR_MATCH; | |
162 | } else { | |
163 | lcr &= ~DW_UART_LCR_EXT_ADDR_MATCH; | |
164 | } | |
165 | if (rs485->flags & SER_RS485_ADDR_DEST) { | |
166 | /* | |
167 | * Don't skip writes here as another endpoint could | |
168 | * have changed communication line's destination | |
169 | * address in between. | |
170 | */ | |
171 | dw8250_writel_ext(p, DW_UART_TAR, rs485->addr_dest); | |
172 | lcr |= DW_UART_LCR_EXT_SEND_ADDR; | |
173 | } | |
174 | } else { | |
175 | lcr = 0; | |
176 | } | |
177 | dw8250_writel_ext(p, DW_UART_LCR_EXT, lcr); | |
178 | } | |
179 | ||
ae50bb27 IJ |
180 | static int dw8250_rs485_config(struct uart_port *p, struct ktermios *termios, |
181 | struct serial_rs485 *rs485) | |
642aa760 IJ |
182 | { |
183 | u32 tcr; | |
184 | ||
185 | tcr = dw8250_readl_ext(p, DW_UART_TCR); | |
186 | tcr &= ~DW_UART_TCR_XFER_MODE; | |
187 | ||
188 | if (rs485->flags & SER_RS485_ENABLED) { | |
642aa760 IJ |
189 | tcr |= DW_UART_TCR_RS485_EN; |
190 | ||
c64e1758 | 191 | if (rs485->flags & SER_RS485_RX_DURING_TX) |
642aa760 | 192 | tcr |= DW_UART_TCR_XFER_MODE_DE_DURING_RE; |
c64e1758 | 193 | else |
642aa760 | 194 | tcr |= DW_UART_TCR_XFER_MODE_DE_OR_RE; |
642aa760 IJ |
195 | dw8250_writel_ext(p, DW_UART_DE_EN, 1); |
196 | dw8250_writel_ext(p, DW_UART_RE_EN, 1); | |
197 | } else { | |
f287f971 IJ |
198 | if (termios) |
199 | termios->c_cflag &= ~ADDRB; | |
200 | ||
642aa760 IJ |
201 | tcr &= ~DW_UART_TCR_RS485_EN; |
202 | } | |
203 | ||
204 | /* Reset to default polarity */ | |
205 | tcr |= DW_UART_TCR_DE_POL; | |
206 | tcr &= ~DW_UART_TCR_RE_POL; | |
207 | ||
208 | if (!(rs485->flags & SER_RS485_RTS_ON_SEND)) | |
209 | tcr &= ~DW_UART_TCR_DE_POL; | |
210 | if (device_property_read_bool(p->dev, "rs485-rx-active-high")) | |
211 | tcr |= DW_UART_TCR_RE_POL; | |
212 | ||
213 | dw8250_writel_ext(p, DW_UART_TCR, tcr); | |
214 | ||
f287f971 IJ |
215 | /* Addressing mode can only be set up after TCR */ |
216 | if (rs485->flags & SER_RS485_ENABLED) | |
217 | dw8250_rs485_set_addr(p, rs485, termios); | |
218 | ||
642aa760 IJ |
219 | return 0; |
220 | } | |
221 | ||
222 | /* | |
223 | * Tests if RE_EN register can have non-zero value to see if RS-485 HW support | |
224 | * is present. | |
225 | */ | |
226 | static bool dw8250_detect_rs485_hw(struct uart_port *p) | |
227 | { | |
228 | u32 reg; | |
229 | ||
230 | dw8250_writel_ext(p, DW_UART_RE_EN, 1); | |
231 | reg = dw8250_readl_ext(p, DW_UART_RE_EN); | |
232 | dw8250_writel_ext(p, DW_UART_RE_EN, 0); | |
233 | return reg; | |
234 | } | |
235 | ||
62a4b3d2 IJ |
236 | static const struct serial_rs485 dw8250_rs485_supported = { |
237 | .flags = SER_RS485_ENABLED | SER_RS485_RX_DURING_TX | SER_RS485_RTS_ON_SEND | | |
f287f971 IJ |
238 | SER_RS485_RTS_AFTER_SEND | SER_RS485_ADDRB | SER_RS485_ADDR_RECV | |
239 | SER_RS485_ADDR_DEST, | |
62a4b3d2 IJ |
240 | }; |
241 | ||
136e0ab9 AS |
242 | void dw8250_setup_port(struct uart_port *p) |
243 | { | |
593dea00 MR |
244 | struct dw8250_port_data *pd = p->private_data; |
245 | struct dw8250_data *data = to_dw8250_data(pd); | |
136e0ab9 | 246 | struct uart_8250_port *up = up_to_u8250p(p); |
748c5ea8 | 247 | u32 reg, old_dlf; |
136e0ab9 | 248 | |
642aa760 | 249 | pd->hw_rs485_support = dw8250_detect_rs485_hw(p); |
5ff33917 | 250 | if (pd->hw_rs485_support) { |
642aa760 | 251 | p->rs485_config = dw8250_rs485_config; |
f287f971 | 252 | up->lsr_save_mask = LSR_SAVE_FLAGS | DW_UART_LSR_ADDR_RCVD; |
0139da50 | 253 | p->rs485_supported = dw8250_rs485_supported; |
5ff33917 IJ |
254 | } else { |
255 | p->rs485_config = serial8250_em485_config; | |
0139da50 | 256 | p->rs485_supported = serial8250_em485_supported; |
5ff33917 IJ |
257 | up->rs485_start_tx = serial8250_em485_start_tx; |
258 | up->rs485_stop_tx = serial8250_em485_stop_tx; | |
259 | } | |
260 | up->capabilities |= UART_CAP_NOTEMT; | |
642aa760 | 261 | |
748c5ea8 RL |
262 | /* Preserve value written by firmware or bootloader */ |
263 | old_dlf = dw8250_readl_ext(p, DW_UART_DLF); | |
136e0ab9 AS |
264 | dw8250_writel_ext(p, DW_UART_DLF, ~0U); |
265 | reg = dw8250_readl_ext(p, DW_UART_DLF); | |
748c5ea8 | 266 | dw8250_writel_ext(p, DW_UART_DLF, old_dlf); |
136e0ab9 AS |
267 | |
268 | if (reg) { | |
593dea00 | 269 | pd->dlf_size = fls(reg); |
136e0ab9 AS |
270 | p->get_divisor = dw8250_get_divisor; |
271 | p->set_divisor = dw8250_set_divisor; | |
272 | } | |
273 | ||
d8049871 | 274 | reg = dw8250_readl_ext(p, DW_UART_UCV); |
d8a02844 VG |
275 | if (reg) |
276 | dev_dbg(p->dev, "Designware UART version %c.%c%c\n", | |
277 | (reg >> 24) & 0xff, (reg >> 16) & 0xff, (reg >> 8) & 0xff); | |
d8049871 | 278 | |
136e0ab9 | 279 | reg = dw8250_readl_ext(p, DW_UART_CPR); |
593dea00 MR |
280 | if (!reg) { |
281 | reg = data->pdata->cpr_val; | |
282 | dev_dbg(p->dev, "CPR is not available, using 0x%08x instead\n", reg); | |
283 | } | |
136e0ab9 AS |
284 | if (!reg) |
285 | return; | |
286 | ||
287 | /* Select the type based on FIFO */ | |
288 | if (reg & DW_UART_CPR_FIFO_MODE) { | |
289 | p->type = PORT_16550A; | |
290 | p->flags |= UPF_FIXED_TYPE; | |
291 | p->fifosize = DW_UART_CPR_FIFO_SIZE(reg); | |
5ff33917 | 292 | up->capabilities = UART_CAP_FIFO | UART_CAP_NOTEMT; |
136e0ab9 AS |
293 | } |
294 | ||
295 | if (reg & DW_UART_CPR_AFCE_MODE) | |
296 | up->capabilities |= UART_CAP_AFE; | |
297 | ||
298 | if (reg & DW_UART_CPR_SIR_MODE) | |
299 | up->capabilities |= UART_CAP_IRDA; | |
300 | } | |
301 | EXPORT_SYMBOL_GPL(dw8250_setup_port); |