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