1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2010 Lars-Peter Clausen <lars@metafoo.de> |
4 | * Copyright (C) 2015 Imagination Technologies |
5 | * |
6 | * Ingenic SoC UART support |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/console.h> |
11 | #include <linux/io.h> |
12 | #include <linux/libfdt.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_fdt.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/serial_8250.h> |
18 | #include <linux/serial_core.h> |
19 | #include <linux/serial_reg.h> |
20 | |
21 | #include "8250.h" |
22 | |
23 | /** ingenic_uart_config: SOC specific config data. */ |
24 | struct ingenic_uart_config { |
25 | int tx_loadsz; |
26 | int fifosize; |
27 | }; |
28 | |
29 | struct ingenic_uart_data { |
30 | struct clk *clk_module; |
31 | struct clk *clk_baud; |
32 | int line; |
33 | }; |
34 | |
35 | static const struct of_device_id of_match[]; |
36 | |
37 | #define UART_FCR_UME BIT(4) |
38 | |
39 | #define UART_MCR_MDCE BIT(7) |
40 | #define UART_MCR_FCM BIT(6) |
41 | |
42 | static struct earlycon_device *early_device; |
43 | |
44 | static uint8_t early_in(struct uart_port *port, int offset) |
45 | { |
46 | return readl(addr: port->membase + (offset << 2)); |
47 | } |
48 | |
49 | static void early_out(struct uart_port *port, int offset, uint8_t value) |
50 | { |
51 | writel(val: value, addr: port->membase + (offset << 2)); |
52 | } |
53 | |
54 | static void ingenic_early_console_putc(struct uart_port *port, unsigned char c) |
55 | { |
56 | u16 lsr; |
57 | |
58 | do { |
59 | lsr = early_in(port, UART_LSR); |
60 | } while ((lsr & UART_LSR_TEMT) == 0); |
61 | |
62 | early_out(port, UART_TX, value: c); |
63 | } |
64 | |
65 | static void ingenic_early_console_write(struct console *console, |
66 | const char *s, unsigned int count) |
67 | { |
68 | uart_console_write(port: &early_device->port, s, count, |
69 | putchar: ingenic_early_console_putc); |
70 | } |
71 | |
72 | static void __init ingenic_early_console_setup_clock(struct earlycon_device *dev) |
73 | { |
74 | void *fdt = initial_boot_params; |
75 | const __be32 *prop; |
76 | int offset; |
77 | |
78 | offset = fdt_path_offset(fdt, path: "/ext" ); |
79 | if (offset < 0) |
80 | return; |
81 | |
82 | prop = fdt_getprop(fdt, nodeoffset: offset, name: "clock-frequency" , NULL); |
83 | if (!prop) |
84 | return; |
85 | |
86 | dev->port.uartclk = be32_to_cpup(p: prop); |
87 | } |
88 | |
89 | static int __init ingenic_earlycon_setup_tail(struct earlycon_device *dev, |
90 | const char *opt) |
91 | { |
92 | struct uart_port *port = &dev->port; |
93 | unsigned int divisor; |
94 | int baud = 115200; |
95 | |
96 | if (!dev->port.membase) |
97 | return -ENODEV; |
98 | |
99 | if (opt) { |
100 | unsigned int parity, bits, flow; /* unused for now */ |
101 | |
102 | uart_parse_options(options: opt, baud: &baud, parity: &parity, bits: &bits, flow: &flow); |
103 | } |
104 | |
105 | if (dev->baud) |
106 | baud = dev->baud; |
107 | divisor = DIV_ROUND_CLOSEST(port->uartclk, 16 * baud); |
108 | |
109 | early_out(port, UART_IER, value: 0); |
110 | early_out(port, UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8); |
111 | early_out(port, UART_DLL, value: 0); |
112 | early_out(port, UART_DLM, value: 0); |
113 | early_out(port, UART_LCR, UART_LCR_WLEN8); |
114 | early_out(port, UART_FCR, UART_FCR_UME | UART_FCR_CLEAR_XMIT | |
115 | UART_FCR_CLEAR_RCVR | UART_FCR_ENABLE_FIFO); |
116 | early_out(port, UART_MCR, UART_MCR_RTS | UART_MCR_DTR); |
117 | |
118 | early_out(port, UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8); |
119 | early_out(port, UART_DLL, value: divisor & 0xff); |
120 | early_out(port, UART_DLM, value: (divisor >> 8) & 0xff); |
121 | early_out(port, UART_LCR, UART_LCR_WLEN8); |
122 | |
123 | early_device = dev; |
124 | dev->con->write = ingenic_early_console_write; |
125 | |
126 | return 0; |
127 | } |
128 | |
129 | static int __init ingenic_early_console_setup(struct earlycon_device *dev, |
130 | const char *opt) |
131 | { |
132 | ingenic_early_console_setup_clock(dev); |
133 | |
134 | return ingenic_earlycon_setup_tail(dev, opt); |
135 | } |
136 | |
137 | static int __init jz4750_early_console_setup(struct earlycon_device *dev, |
138 | const char *opt) |
139 | { |
140 | /* |
141 | * JZ4750/55/60 have an optional /2 divider between the EXT |
142 | * oscillator and some peripherals including UART, which will |
143 | * be enabled if using a 24 MHz oscillator, and disabled when |
144 | * using a 12 MHz oscillator. |
145 | */ |
146 | ingenic_early_console_setup_clock(dev); |
147 | if (dev->port.uartclk >= 16000000) |
148 | dev->port.uartclk /= 2; |
149 | |
150 | return ingenic_earlycon_setup_tail(dev, opt); |
151 | } |
152 | |
153 | OF_EARLYCON_DECLARE(jz4740_uart, "ingenic,jz4740-uart" , |
154 | ingenic_early_console_setup); |
155 | |
156 | OF_EARLYCON_DECLARE(jz4750_uart, "ingenic,jz4750-uart" , |
157 | jz4750_early_console_setup); |
158 | |
159 | OF_EARLYCON_DECLARE(jz4770_uart, "ingenic,jz4770-uart" , |
160 | ingenic_early_console_setup); |
161 | |
162 | OF_EARLYCON_DECLARE(jz4775_uart, "ingenic,jz4775-uart" , |
163 | ingenic_early_console_setup); |
164 | |
165 | OF_EARLYCON_DECLARE(jz4780_uart, "ingenic,jz4780-uart" , |
166 | ingenic_early_console_setup); |
167 | |
168 | OF_EARLYCON_DECLARE(x1000_uart, "ingenic,x1000-uart" , |
169 | ingenic_early_console_setup); |
170 | |
171 | static void ingenic_uart_serial_out(struct uart_port *p, int offset, int value) |
172 | { |
173 | int ier; |
174 | |
175 | switch (offset) { |
176 | case UART_FCR: |
177 | /* UART module enable */ |
178 | value |= UART_FCR_UME; |
179 | break; |
180 | |
181 | case UART_IER: |
182 | /* |
183 | * Enable receive timeout interrupt with the receive line |
184 | * status interrupt. |
185 | */ |
186 | value |= (value & 0x4) << 2; |
187 | break; |
188 | |
189 | case UART_MCR: |
190 | /* |
191 | * If we have enabled modem status IRQs we should enable |
192 | * modem mode. |
193 | */ |
194 | ier = p->serial_in(p, UART_IER); |
195 | |
196 | if (ier & UART_IER_MSI) |
197 | value |= UART_MCR_MDCE | UART_MCR_FCM; |
198 | else |
199 | value &= ~(UART_MCR_MDCE | UART_MCR_FCM); |
200 | break; |
201 | |
202 | default: |
203 | break; |
204 | } |
205 | |
206 | writeb(val: value, addr: p->membase + (offset << p->regshift)); |
207 | } |
208 | |
209 | static unsigned int ingenic_uart_serial_in(struct uart_port *p, int offset) |
210 | { |
211 | unsigned int value; |
212 | |
213 | value = readb(addr: p->membase + (offset << p->regshift)); |
214 | |
215 | /* Hide non-16550 compliant bits from higher levels */ |
216 | switch (offset) { |
217 | case UART_FCR: |
218 | value &= ~UART_FCR_UME; |
219 | break; |
220 | |
221 | case UART_MCR: |
222 | value &= ~(UART_MCR_MDCE | UART_MCR_FCM); |
223 | break; |
224 | |
225 | default: |
226 | break; |
227 | } |
228 | return value; |
229 | } |
230 | |
231 | static int ingenic_uart_probe(struct platform_device *pdev) |
232 | { |
233 | struct uart_8250_port uart = {}; |
234 | struct ingenic_uart_data *data; |
235 | const struct ingenic_uart_config *cdata; |
236 | struct resource *regs; |
237 | int err; |
238 | |
239 | cdata = of_device_get_match_data(dev: &pdev->dev); |
240 | if (!cdata) { |
241 | dev_err(&pdev->dev, "Error: No device match found\n" ); |
242 | return -ENODEV; |
243 | } |
244 | |
245 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
246 | if (!regs) { |
247 | dev_err(&pdev->dev, "no registers defined\n" ); |
248 | return -EINVAL; |
249 | } |
250 | |
251 | data = devm_kzalloc(dev: &pdev->dev, size: sizeof(*data), GFP_KERNEL); |
252 | if (!data) |
253 | return -ENOMEM; |
254 | |
255 | spin_lock_init(&uart.port.lock); |
256 | uart.port.type = PORT_16550A; |
257 | uart.port.flags = UPF_SKIP_TEST | UPF_IOREMAP | UPF_FIXED_TYPE; |
258 | uart.port.mapbase = regs->start; |
259 | uart.port.serial_out = ingenic_uart_serial_out; |
260 | uart.port.serial_in = ingenic_uart_serial_in; |
261 | uart.port.dev = &pdev->dev; |
262 | uart.tx_loadsz = cdata->tx_loadsz; |
263 | uart.capabilities = UART_CAP_FIFO | UART_CAP_RTOIE; |
264 | |
265 | err = uart_read_port_properties(port: &uart.port); |
266 | if (err) |
267 | return err; |
268 | |
269 | uart.port.regshift = 2; |
270 | uart.port.fifosize = cdata->fifosize; |
271 | |
272 | uart.port.membase = devm_ioremap(dev: &pdev->dev, offset: regs->start, |
273 | size: resource_size(res: regs)); |
274 | if (!uart.port.membase) |
275 | return -ENOMEM; |
276 | |
277 | data->clk_module = devm_clk_get(dev: &pdev->dev, id: "module" ); |
278 | if (IS_ERR(ptr: data->clk_module)) |
279 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: data->clk_module), |
280 | fmt: "unable to get module clock\n" ); |
281 | |
282 | data->clk_baud = devm_clk_get(dev: &pdev->dev, id: "baud" ); |
283 | if (IS_ERR(ptr: data->clk_baud)) |
284 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: data->clk_baud), |
285 | fmt: "unable to get baud clock\n" ); |
286 | |
287 | err = clk_prepare_enable(clk: data->clk_module); |
288 | if (err) { |
289 | dev_err(&pdev->dev, "could not enable module clock: %d\n" , err); |
290 | goto out; |
291 | } |
292 | |
293 | err = clk_prepare_enable(clk: data->clk_baud); |
294 | if (err) { |
295 | dev_err(&pdev->dev, "could not enable baud clock: %d\n" , err); |
296 | goto out_disable_moduleclk; |
297 | } |
298 | uart.port.uartclk = clk_get_rate(clk: data->clk_baud); |
299 | |
300 | data->line = serial8250_register_8250_port(&uart); |
301 | if (data->line < 0) { |
302 | err = data->line; |
303 | goto out_disable_baudclk; |
304 | } |
305 | |
306 | platform_set_drvdata(pdev, data); |
307 | return 0; |
308 | |
309 | out_disable_baudclk: |
310 | clk_disable_unprepare(clk: data->clk_baud); |
311 | out_disable_moduleclk: |
312 | clk_disable_unprepare(clk: data->clk_module); |
313 | out: |
314 | return err; |
315 | } |
316 | |
317 | static void ingenic_uart_remove(struct platform_device *pdev) |
318 | { |
319 | struct ingenic_uart_data *data = platform_get_drvdata(pdev); |
320 | |
321 | serial8250_unregister_port(line: data->line); |
322 | clk_disable_unprepare(clk: data->clk_module); |
323 | clk_disable_unprepare(clk: data->clk_baud); |
324 | } |
325 | |
326 | static const struct ingenic_uart_config jz4740_uart_config = { |
327 | .tx_loadsz = 8, |
328 | .fifosize = 16, |
329 | }; |
330 | |
331 | static const struct ingenic_uart_config jz4760_uart_config = { |
332 | .tx_loadsz = 16, |
333 | .fifosize = 32, |
334 | }; |
335 | |
336 | static const struct ingenic_uart_config jz4780_uart_config = { |
337 | .tx_loadsz = 32, |
338 | .fifosize = 64, |
339 | }; |
340 | |
341 | static const struct ingenic_uart_config x1000_uart_config = { |
342 | .tx_loadsz = 32, |
343 | .fifosize = 64, |
344 | }; |
345 | |
346 | static const struct of_device_id of_match[] = { |
347 | { .compatible = "ingenic,jz4740-uart" , .data = &jz4740_uart_config }, |
348 | { .compatible = "ingenic,jz4750-uart" , .data = &jz4760_uart_config }, |
349 | { .compatible = "ingenic,jz4760-uart" , .data = &jz4760_uart_config }, |
350 | { .compatible = "ingenic,jz4770-uart" , .data = &jz4760_uart_config }, |
351 | { .compatible = "ingenic,jz4775-uart" , .data = &jz4760_uart_config }, |
352 | { .compatible = "ingenic,jz4780-uart" , .data = &jz4780_uart_config }, |
353 | { .compatible = "ingenic,x1000-uart" , .data = &x1000_uart_config }, |
354 | { /* sentinel */ } |
355 | }; |
356 | MODULE_DEVICE_TABLE(of, of_match); |
357 | |
358 | static struct platform_driver ingenic_uart_platform_driver = { |
359 | .driver = { |
360 | .name = "ingenic-uart" , |
361 | .of_match_table = of_match, |
362 | }, |
363 | .probe = ingenic_uart_probe, |
364 | .remove_new = ingenic_uart_remove, |
365 | }; |
366 | |
367 | module_platform_driver(ingenic_uart_platform_driver); |
368 | |
369 | MODULE_AUTHOR("Paul Burton" ); |
370 | MODULE_LICENSE("GPL" ); |
371 | MODULE_DESCRIPTION("Ingenic SoC UART driver" ); |
372 | |