1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * drivers/tty/serial/8250/8250_pxa.c -- driver for PXA on-board UARTS |
4 | * Copyright: (C) 2013 Sergei Ianovich <ynvich@gmail.com> |
5 | * |
6 | * replaces drivers/serial/pxa.c by Nicolas Pitre |
7 | * Created: Feb 20, 2003 |
8 | * Copyright: (C) 2003 Monta Vista Software, Inc. |
9 | * |
10 | * Based on drivers/serial/8250.c by Russell King. |
11 | */ |
12 | |
13 | #include <linux/device.h> |
14 | #include <linux/init.h> |
15 | #include <linux/io.h> |
16 | #include <linux/module.h> |
17 | #include <linux/serial_8250.h> |
18 | #include <linux/serial_core.h> |
19 | #include <linux/serial_reg.h> |
20 | #include <linux/of.h> |
21 | #include <linux/of_platform.h> |
22 | #include <linux/platform_device.h> |
23 | #include <linux/slab.h> |
24 | #include <linux/clk.h> |
25 | |
26 | #include "8250.h" |
27 | |
28 | struct pxa8250_data { |
29 | int line; |
30 | struct clk *clk; |
31 | }; |
32 | |
33 | static int __maybe_unused serial_pxa_suspend(struct device *dev) |
34 | { |
35 | struct pxa8250_data *data = dev_get_drvdata(dev); |
36 | |
37 | serial8250_suspend_port(line: data->line); |
38 | |
39 | return 0; |
40 | } |
41 | |
42 | static int __maybe_unused serial_pxa_resume(struct device *dev) |
43 | { |
44 | struct pxa8250_data *data = dev_get_drvdata(dev); |
45 | |
46 | serial8250_resume_port(line: data->line); |
47 | |
48 | return 0; |
49 | } |
50 | |
51 | static const struct dev_pm_ops serial_pxa_pm_ops = { |
52 | SET_SYSTEM_SLEEP_PM_OPS(serial_pxa_suspend, serial_pxa_resume) |
53 | }; |
54 | |
55 | static const struct of_device_id serial_pxa_dt_ids[] = { |
56 | { .compatible = "mrvl,pxa-uart" , }, |
57 | { .compatible = "mrvl,mmp-uart" , }, |
58 | {} |
59 | }; |
60 | MODULE_DEVICE_TABLE(of, serial_pxa_dt_ids); |
61 | |
62 | /* Uart divisor latch write */ |
63 | static void serial_pxa_dl_write(struct uart_8250_port *up, u32 value) |
64 | { |
65 | unsigned int dll; |
66 | |
67 | serial_out(up, UART_DLL, value: value & 0xff); |
68 | /* |
69 | * work around Erratum #74 according to Marvel(R) PXA270M Processor |
70 | * Specification Update (April 19, 2010) |
71 | */ |
72 | dll = serial_in(up, UART_DLL); |
73 | WARN_ON(dll != (value & 0xff)); |
74 | |
75 | serial_out(up, UART_DLM, value: value >> 8 & 0xff); |
76 | } |
77 | |
78 | |
79 | static void serial_pxa_pm(struct uart_port *port, unsigned int state, |
80 | unsigned int oldstate) |
81 | { |
82 | struct pxa8250_data *data = port->private_data; |
83 | |
84 | if (!state) |
85 | clk_prepare_enable(clk: data->clk); |
86 | else |
87 | clk_disable_unprepare(clk: data->clk); |
88 | } |
89 | |
90 | static int serial_pxa_probe(struct platform_device *pdev) |
91 | { |
92 | struct uart_8250_port uart = {}; |
93 | struct pxa8250_data *data; |
94 | struct resource *mmres; |
95 | int ret; |
96 | |
97 | mmres = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
98 | if (!mmres) |
99 | return -ENODEV; |
100 | |
101 | data = devm_kzalloc(dev: &pdev->dev, size: sizeof(*data), GFP_KERNEL); |
102 | if (!data) |
103 | return -ENOMEM; |
104 | |
105 | data->clk = devm_clk_get(dev: &pdev->dev, NULL); |
106 | if (IS_ERR(ptr: data->clk)) |
107 | return PTR_ERR(ptr: data->clk); |
108 | |
109 | ret = clk_prepare(clk: data->clk); |
110 | if (ret) |
111 | return ret; |
112 | |
113 | uart.port.type = PORT_XSCALE; |
114 | uart.port.mapbase = mmres->start; |
115 | uart.port.flags = UPF_IOREMAP | UPF_SKIP_TEST | UPF_FIXED_TYPE; |
116 | uart.port.dev = &pdev->dev; |
117 | uart.port.uartclk = clk_get_rate(clk: data->clk); |
118 | uart.port.pm = serial_pxa_pm; |
119 | uart.port.private_data = data; |
120 | |
121 | ret = uart_read_port_properties(port: &uart.port); |
122 | if (ret) |
123 | return ret; |
124 | |
125 | uart.port.iotype = UPIO_MEM32; |
126 | uart.port.regshift = 2; |
127 | uart.port.fifosize = 64; |
128 | uart.dl_write = serial_pxa_dl_write; |
129 | |
130 | ret = serial8250_register_8250_port(&uart); |
131 | if (ret < 0) |
132 | goto err_clk; |
133 | |
134 | data->line = ret; |
135 | |
136 | platform_set_drvdata(pdev, data); |
137 | |
138 | return 0; |
139 | |
140 | err_clk: |
141 | clk_unprepare(clk: data->clk); |
142 | return ret; |
143 | } |
144 | |
145 | static void serial_pxa_remove(struct platform_device *pdev) |
146 | { |
147 | struct pxa8250_data *data = platform_get_drvdata(pdev); |
148 | |
149 | serial8250_unregister_port(line: data->line); |
150 | |
151 | clk_unprepare(clk: data->clk); |
152 | } |
153 | |
154 | static struct platform_driver serial_pxa_driver = { |
155 | .probe = serial_pxa_probe, |
156 | .remove_new = serial_pxa_remove, |
157 | |
158 | .driver = { |
159 | .name = "pxa2xx-uart" , |
160 | .pm = &serial_pxa_pm_ops, |
161 | .of_match_table = serial_pxa_dt_ids, |
162 | }, |
163 | }; |
164 | |
165 | module_platform_driver(serial_pxa_driver); |
166 | |
167 | #ifdef CONFIG_SERIAL_8250_CONSOLE |
168 | static int __init early_serial_pxa_setup(struct earlycon_device *device, |
169 | const char *options) |
170 | { |
171 | struct uart_port *port = &device->port; |
172 | |
173 | if (!(device->port.membase || device->port.iobase)) |
174 | return -ENODEV; |
175 | |
176 | port->regshift = 2; |
177 | return early_serial8250_setup(device, NULL); |
178 | } |
179 | OF_EARLYCON_DECLARE(early_pxa, "mrvl,pxa-uart" , early_serial_pxa_setup); |
180 | OF_EARLYCON_DECLARE(mmp, "mrvl,mmp-uart" , early_serial_pxa_setup); |
181 | #endif |
182 | |
183 | MODULE_AUTHOR("Sergei Ianovich" ); |
184 | MODULE_LICENSE("GPL" ); |
185 | MODULE_ALIAS("platform:pxa2xx-uart" ); |
186 | |