1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/console.h> |
7 | #include <linux/mailbox_client.h> |
8 | #include <linux/module.h> |
9 | #include <linux/of.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/serial.h> |
12 | #include <linux/serial_core.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/tty.h> |
15 | #include <linux/tty_flip.h> |
16 | |
17 | #define TCU_MBOX_BYTE(i, x) ((x) << (i * 8)) |
18 | #define TCU_MBOX_BYTE_V(x, i) (((x) >> (i * 8)) & 0xff) |
19 | #define TCU_MBOX_NUM_BYTES(x) ((x) << 24) |
20 | #define TCU_MBOX_NUM_BYTES_V(x) (((x) >> 24) & 0x3) |
21 | |
22 | struct tegra_tcu { |
23 | struct uart_driver driver; |
24 | #if IS_ENABLED(CONFIG_SERIAL_TEGRA_TCU_CONSOLE) |
25 | struct console console; |
26 | #endif |
27 | struct uart_port port; |
28 | |
29 | struct mbox_client tx_client, rx_client; |
30 | struct mbox_chan *tx, *rx; |
31 | }; |
32 | |
33 | static unsigned int tegra_tcu_uart_tx_empty(struct uart_port *port) |
34 | { |
35 | return TIOCSER_TEMT; |
36 | } |
37 | |
38 | static void tegra_tcu_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) |
39 | { |
40 | } |
41 | |
42 | static unsigned int tegra_tcu_uart_get_mctrl(struct uart_port *port) |
43 | { |
44 | return 0; |
45 | } |
46 | |
47 | static void tegra_tcu_uart_stop_tx(struct uart_port *port) |
48 | { |
49 | } |
50 | |
51 | static void tegra_tcu_write_one(struct tegra_tcu *tcu, u32 value, |
52 | unsigned int count) |
53 | { |
54 | void *msg; |
55 | |
56 | value |= TCU_MBOX_NUM_BYTES(count); |
57 | msg = (void *)(unsigned long)value; |
58 | mbox_send_message(chan: tcu->tx, mssg: msg); |
59 | mbox_flush(chan: tcu->tx, timeout: 1000); |
60 | } |
61 | |
62 | static void tegra_tcu_write(struct tegra_tcu *tcu, const char *s, |
63 | unsigned int count) |
64 | { |
65 | unsigned int written = 0, i = 0; |
66 | bool insert_nl = false; |
67 | u32 value = 0; |
68 | |
69 | while (i < count) { |
70 | if (insert_nl) { |
71 | value |= TCU_MBOX_BYTE(written++, '\n'); |
72 | insert_nl = false; |
73 | i++; |
74 | } else if (s[i] == '\n') { |
75 | value |= TCU_MBOX_BYTE(written++, '\r'); |
76 | insert_nl = true; |
77 | } else { |
78 | value |= TCU_MBOX_BYTE(written++, s[i++]); |
79 | } |
80 | |
81 | if (written == 3) { |
82 | tegra_tcu_write_one(tcu, value, count: 3); |
83 | value = written = 0; |
84 | } |
85 | } |
86 | |
87 | if (written) |
88 | tegra_tcu_write_one(tcu, value, count: written); |
89 | } |
90 | |
91 | static void tegra_tcu_uart_start_tx(struct uart_port *port) |
92 | { |
93 | struct tegra_tcu *tcu = port->private_data; |
94 | struct circ_buf *xmit = &port->state->xmit; |
95 | unsigned long count; |
96 | |
97 | for (;;) { |
98 | count = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE); |
99 | if (!count) |
100 | break; |
101 | |
102 | tegra_tcu_write(tcu, s: &xmit->buf[xmit->tail], count); |
103 | uart_xmit_advance(up: port, chars: count); |
104 | } |
105 | |
106 | uart_write_wakeup(port); |
107 | } |
108 | |
109 | static void tegra_tcu_uart_stop_rx(struct uart_port *port) |
110 | { |
111 | } |
112 | |
113 | static void tegra_tcu_uart_break_ctl(struct uart_port *port, int ctl) |
114 | { |
115 | } |
116 | |
117 | static int tegra_tcu_uart_startup(struct uart_port *port) |
118 | { |
119 | return 0; |
120 | } |
121 | |
122 | static void tegra_tcu_uart_shutdown(struct uart_port *port) |
123 | { |
124 | } |
125 | |
126 | static void tegra_tcu_uart_set_termios(struct uart_port *port, |
127 | struct ktermios *new, |
128 | const struct ktermios *old) |
129 | { |
130 | } |
131 | |
132 | static const struct uart_ops tegra_tcu_uart_ops = { |
133 | .tx_empty = tegra_tcu_uart_tx_empty, |
134 | .set_mctrl = tegra_tcu_uart_set_mctrl, |
135 | .get_mctrl = tegra_tcu_uart_get_mctrl, |
136 | .stop_tx = tegra_tcu_uart_stop_tx, |
137 | .start_tx = tegra_tcu_uart_start_tx, |
138 | .stop_rx = tegra_tcu_uart_stop_rx, |
139 | .break_ctl = tegra_tcu_uart_break_ctl, |
140 | .startup = tegra_tcu_uart_startup, |
141 | .shutdown = tegra_tcu_uart_shutdown, |
142 | .set_termios = tegra_tcu_uart_set_termios, |
143 | }; |
144 | |
145 | #if IS_ENABLED(CONFIG_SERIAL_TEGRA_TCU_CONSOLE) |
146 | static void tegra_tcu_console_write(struct console *cons, const char *s, |
147 | unsigned int count) |
148 | { |
149 | struct tegra_tcu *tcu = container_of(cons, struct tegra_tcu, console); |
150 | |
151 | tegra_tcu_write(tcu, s, count); |
152 | } |
153 | |
154 | static int tegra_tcu_console_setup(struct console *cons, char *options) |
155 | { |
156 | return 0; |
157 | } |
158 | #endif |
159 | |
160 | static void tegra_tcu_receive(struct mbox_client *cl, void *msg) |
161 | { |
162 | struct tegra_tcu *tcu = container_of(cl, struct tegra_tcu, rx_client); |
163 | struct tty_port *port = &tcu->port.state->port; |
164 | u32 value = (u32)(unsigned long)msg; |
165 | unsigned int num_bytes, i; |
166 | |
167 | num_bytes = TCU_MBOX_NUM_BYTES_V(value); |
168 | |
169 | for (i = 0; i < num_bytes; i++) |
170 | tty_insert_flip_char(port, TCU_MBOX_BYTE_V(value, i), |
171 | TTY_NORMAL); |
172 | |
173 | tty_flip_buffer_push(port); |
174 | } |
175 | |
176 | static int tegra_tcu_probe(struct platform_device *pdev) |
177 | { |
178 | struct uart_port *port; |
179 | struct tegra_tcu *tcu; |
180 | int err; |
181 | |
182 | tcu = devm_kzalloc(dev: &pdev->dev, size: sizeof(*tcu), GFP_KERNEL); |
183 | if (!tcu) |
184 | return -ENOMEM; |
185 | |
186 | tcu->tx_client.dev = &pdev->dev; |
187 | tcu->rx_client.dev = &pdev->dev; |
188 | tcu->rx_client.rx_callback = tegra_tcu_receive; |
189 | |
190 | tcu->tx = mbox_request_channel_byname(cl: &tcu->tx_client, name: "tx" ); |
191 | if (IS_ERR(ptr: tcu->tx)) { |
192 | err = PTR_ERR(ptr: tcu->tx); |
193 | dev_err(&pdev->dev, "failed to get tx mailbox: %d\n" , err); |
194 | return err; |
195 | } |
196 | |
197 | #if IS_ENABLED(CONFIG_SERIAL_TEGRA_TCU_CONSOLE) |
198 | /* setup the console */ |
199 | strcpy(p: tcu->console.name, q: "ttyTCU" ); |
200 | tcu->console.device = uart_console_device; |
201 | tcu->console.flags = CON_PRINTBUFFER | CON_ANYTIME; |
202 | tcu->console.index = -1; |
203 | tcu->console.write = tegra_tcu_console_write; |
204 | tcu->console.setup = tegra_tcu_console_setup; |
205 | tcu->console.data = &tcu->driver; |
206 | #endif |
207 | |
208 | /* setup the driver */ |
209 | tcu->driver.owner = THIS_MODULE; |
210 | tcu->driver.driver_name = "tegra-tcu" ; |
211 | tcu->driver.dev_name = "ttyTCU" ; |
212 | #if IS_ENABLED(CONFIG_SERIAL_TEGRA_TCU_CONSOLE) |
213 | tcu->driver.cons = &tcu->console; |
214 | #endif |
215 | tcu->driver.nr = 1; |
216 | |
217 | err = uart_register_driver(uart: &tcu->driver); |
218 | if (err) { |
219 | dev_err(&pdev->dev, "failed to register UART driver: %d\n" , |
220 | err); |
221 | goto free_tx; |
222 | } |
223 | |
224 | /* setup the port */ |
225 | port = &tcu->port; |
226 | spin_lock_init(&port->lock); |
227 | port->dev = &pdev->dev; |
228 | port->type = PORT_TEGRA_TCU; |
229 | port->ops = &tegra_tcu_uart_ops; |
230 | port->fifosize = 1; |
231 | port->iotype = UPIO_MEM; |
232 | port->flags = UPF_BOOT_AUTOCONF; |
233 | port->private_data = tcu; |
234 | |
235 | err = uart_add_one_port(reg: &tcu->driver, port); |
236 | if (err) { |
237 | dev_err(&pdev->dev, "failed to add UART port: %d\n" , err); |
238 | goto unregister_uart; |
239 | } |
240 | |
241 | /* |
242 | * Request RX channel after creating port to ensure tcu->port |
243 | * is ready for any immediate incoming bytes. |
244 | */ |
245 | tcu->rx = mbox_request_channel_byname(cl: &tcu->rx_client, name: "rx" ); |
246 | if (IS_ERR(ptr: tcu->rx)) { |
247 | err = PTR_ERR(ptr: tcu->rx); |
248 | dev_err(&pdev->dev, "failed to get rx mailbox: %d\n" , err); |
249 | goto remove_uart_port; |
250 | } |
251 | |
252 | platform_set_drvdata(pdev, data: tcu); |
253 | #if IS_ENABLED(CONFIG_SERIAL_TEGRA_TCU_CONSOLE) |
254 | register_console(&tcu->console); |
255 | #endif |
256 | |
257 | return 0; |
258 | |
259 | remove_uart_port: |
260 | uart_remove_one_port(reg: &tcu->driver, port: &tcu->port); |
261 | unregister_uart: |
262 | uart_unregister_driver(uart: &tcu->driver); |
263 | free_tx: |
264 | mbox_free_channel(chan: tcu->tx); |
265 | |
266 | return err; |
267 | } |
268 | |
269 | static int tegra_tcu_remove(struct platform_device *pdev) |
270 | { |
271 | struct tegra_tcu *tcu = platform_get_drvdata(pdev); |
272 | |
273 | #if IS_ENABLED(CONFIG_SERIAL_TEGRA_TCU_CONSOLE) |
274 | unregister_console(&tcu->console); |
275 | #endif |
276 | mbox_free_channel(chan: tcu->rx); |
277 | uart_remove_one_port(reg: &tcu->driver, port: &tcu->port); |
278 | uart_unregister_driver(uart: &tcu->driver); |
279 | mbox_free_channel(chan: tcu->tx); |
280 | |
281 | return 0; |
282 | } |
283 | |
284 | static const struct of_device_id tegra_tcu_match[] = { |
285 | { .compatible = "nvidia,tegra194-tcu" }, |
286 | { } |
287 | }; |
288 | MODULE_DEVICE_TABLE(of, tegra_tcu_match); |
289 | |
290 | static struct platform_driver tegra_tcu_driver = { |
291 | .driver = { |
292 | .name = "tegra-tcu" , |
293 | .of_match_table = tegra_tcu_match, |
294 | }, |
295 | .probe = tegra_tcu_probe, |
296 | .remove = tegra_tcu_remove, |
297 | }; |
298 | module_platform_driver(tegra_tcu_driver); |
299 | |
300 | MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>" ); |
301 | MODULE_LICENSE("GPL v2" ); |
302 | MODULE_DESCRIPTION("NVIDIA Tegra Combined UART driver" ); |
303 | |