1/*
2 * Copyright (c) 2003 Marcel Moolenaar
3 * Copyright (c) 2007-2009 Andrew Turner
4 * Copyright (c) 2013 Ruslan Bukin <br@bsdpad.com>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD: stable/11/sys/arm/samsung/exynos/exynos_uart.c 356020 2019-12-22 19:06:45Z kevans $");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/bus.h>
35#include <sys/conf.h>
36#include <sys/cons.h>
37#include <sys/rman.h>
38#include <machine/bus.h>
39#include <machine/intr.h>
40
41#include <dev/uart/uart.h>
42#include <dev/uart/uart_cpu.h>
43#include <dev/uart/uart_cpu_fdt.h>
44#include <dev/uart/uart_bus.h>
45
46#include <arm/samsung/exynos/exynos_uart.h>
47
48#include "uart_if.h"
49
50#define	DEF_CLK		100000000
51
52static int sscomspeed(long, long);
53static int exynos4210_uart_param(struct uart_bas *, int, int, int, int);
54
55/*
56 * Low-level UART interface.
57 */
58static int exynos4210_probe(struct uart_bas *bas);
59static void exynos4210_init(struct uart_bas *bas, int, int, int, int);
60static void exynos4210_term(struct uart_bas *bas);
61static void exynos4210_putc(struct uart_bas *bas, int);
62static int exynos4210_rxready(struct uart_bas *bas);
63static int exynos4210_getc(struct uart_bas *bas, struct mtx *mtx);
64
65extern SLIST_HEAD(uart_devinfo_list, uart_devinfo) uart_sysdevs;
66
67static int
68sscomspeed(long speed, long frequency)
69{
70	int x;
71
72	if (speed <= 0 || frequency <= 0)
73		return (-1);
74	x = (frequency / 16) / speed;
75	return (x-1);
76}
77
78static int
79exynos4210_uart_param(struct uart_bas *bas, int baudrate, int databits,
80    int stopbits, int parity)
81{
82	int brd, ulcon;
83
84	ulcon = 0;
85
86	switch(databits) {
87	case 5:
88		ulcon |= ULCON_LENGTH_5;
89		break;
90	case 6:
91		ulcon |= ULCON_LENGTH_6;
92		break;
93	case 7:
94		ulcon |= ULCON_LENGTH_7;
95		break;
96	case 8:
97		ulcon |= ULCON_LENGTH_8;
98		break;
99	default:
100		return (EINVAL);
101	}
102
103	switch (parity) {
104	case UART_PARITY_NONE:
105		ulcon |= ULCON_PARITY_NONE;
106		break;
107	case UART_PARITY_ODD:
108		ulcon |= ULCON_PARITY_ODD;
109		break;
110	case UART_PARITY_EVEN:
111		ulcon |= ULCON_PARITY_EVEN;
112		break;
113	case UART_PARITY_MARK:
114	case UART_PARITY_SPACE:
115	default:
116		return (EINVAL);
117	}
118
119	if (stopbits == 2)
120		ulcon |= ULCON_STOP;
121
122	uart_setreg(bas, SSCOM_ULCON, ulcon);
123
124	brd = sscomspeed(baudrate, bas->rclk);
125	uart_setreg(bas, SSCOM_UBRDIV, brd);
126
127	return (0);
128}
129
130struct uart_ops uart_exynos4210_ops = {
131	.probe = exynos4210_probe,
132	.init = exynos4210_init,
133	.term = exynos4210_term,
134	.putc = exynos4210_putc,
135	.rxready = exynos4210_rxready,
136	.getc = exynos4210_getc,
137};
138
139static int
140exynos4210_probe(struct uart_bas *bas)
141{
142
143	return (0);
144}
145
146static void
147exynos4210_init(struct uart_bas *bas, int baudrate, int databits, int stopbits,
148    int parity)
149{
150
151	if (bas->rclk == 0)
152		bas->rclk = DEF_CLK;
153
154	KASSERT(bas->rclk != 0, ("exynos4210_init: Invalid rclk"));
155
156	uart_setreg(bas, SSCOM_UCON, 0);
157	uart_setreg(bas, SSCOM_UFCON,
158	    UFCON_TXTRIGGER_8 | UFCON_RXTRIGGER_8 |
159	    UFCON_TXFIFO_RESET | UFCON_RXFIFO_RESET |
160	    UFCON_FIFO_ENABLE);
161	exynos4210_uart_param(bas, baudrate, databits, stopbits, parity);
162
163	/* Enable UART. */
164	uart_setreg(bas, SSCOM_UCON, UCON_TXMODE_INT | UCON_RXMODE_INT |
165	    UCON_TOINT);
166	uart_setreg(bas, SSCOM_UMCON, UMCON_RTS);
167}
168
169static void
170exynos4210_term(struct uart_bas *bas)
171{
172	/* XXX */
173}
174
175static void
176exynos4210_putc(struct uart_bas *bas, int c)
177{
178
179	while ((bus_space_read_4(bas->bst, bas->bsh, SSCOM_UFSTAT) &
180		UFSTAT_TXFULL) == UFSTAT_TXFULL)
181		continue;
182
183	uart_setreg(bas, SSCOM_UTXH, c);
184}
185
186static int
187exynos4210_rxready(struct uart_bas *bas)
188{
189
190	return ((uart_getreg(bas, SSCOM_UTRSTAT) & UTRSTAT_RXREADY) ==
191	    UTRSTAT_RXREADY);
192}
193
194static int
195exynos4210_getc(struct uart_bas *bas, struct mtx *mtx)
196{
197	int utrstat;
198
199	utrstat = bus_space_read_1(bas->bst, bas->bsh, SSCOM_UTRSTAT);
200	while (!(utrstat & UTRSTAT_RXREADY)) {
201		utrstat = bus_space_read_1(bas->bst, bas->bsh, SSCOM_UTRSTAT);
202		continue;
203	}
204
205	return (bus_space_read_1(bas->bst, bas->bsh, SSCOM_URXH));
206}
207
208static int exynos4210_bus_probe(struct uart_softc *sc);
209static int exynos4210_bus_attach(struct uart_softc *sc);
210static int exynos4210_bus_flush(struct uart_softc *, int);
211static int exynos4210_bus_getsig(struct uart_softc *);
212static int exynos4210_bus_ioctl(struct uart_softc *, int, intptr_t);
213static int exynos4210_bus_ipend(struct uart_softc *);
214static int exynos4210_bus_param(struct uart_softc *, int, int, int, int);
215static int exynos4210_bus_receive(struct uart_softc *);
216static int exynos4210_bus_setsig(struct uart_softc *, int);
217static int exynos4210_bus_transmit(struct uart_softc *);
218
219static kobj_method_t exynos4210_methods[] = {
220	KOBJMETHOD(uart_probe,		exynos4210_bus_probe),
221	KOBJMETHOD(uart_attach, 	exynos4210_bus_attach),
222	KOBJMETHOD(uart_flush,		exynos4210_bus_flush),
223	KOBJMETHOD(uart_getsig,		exynos4210_bus_getsig),
224	KOBJMETHOD(uart_ioctl,		exynos4210_bus_ioctl),
225	KOBJMETHOD(uart_ipend,		exynos4210_bus_ipend),
226	KOBJMETHOD(uart_param,		exynos4210_bus_param),
227	KOBJMETHOD(uart_receive,	exynos4210_bus_receive),
228	KOBJMETHOD(uart_setsig,		exynos4210_bus_setsig),
229	KOBJMETHOD(uart_transmit,	exynos4210_bus_transmit),
230
231	{0, 0 }
232};
233
234int
235exynos4210_bus_probe(struct uart_softc *sc)
236{
237
238	sc->sc_txfifosz = 16;
239	sc->sc_rxfifosz = 16;
240
241	return (0);
242}
243
244static int
245exynos4210_bus_attach(struct uart_softc *sc)
246{
247
248	sc->sc_hwiflow = 0;
249	sc->sc_hwoflow = 0;
250
251	return (0);
252}
253
254static int
255exynos4210_bus_transmit(struct uart_softc *sc)
256{
257	int i;
258	int reg;
259
260	uart_lock(sc->sc_hwmtx);
261
262	for (i = 0; i < sc->sc_txdatasz; i++) {
263		exynos4210_putc(&sc->sc_bas, sc->sc_txbuf[i]);
264		uart_barrier(&sc->sc_bas);
265	}
266
267	sc->sc_txbusy = 1;
268
269	uart_unlock(sc->sc_hwmtx);
270
271	/* unmask TX interrupt */
272	reg = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UINTM);
273	reg &= ~(1 << 2);
274	bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UINTM, reg);
275
276	return (0);
277}
278
279static int
280exynos4210_bus_setsig(struct uart_softc *sc, int sig)
281{
282
283	return (0);
284}
285
286static int
287exynos4210_bus_receive(struct uart_softc *sc)
288{
289	struct uart_bas *bas;
290
291	bas = &sc->sc_bas;
292	while (bus_space_read_4(bas->bst, bas->bsh,
293		SSCOM_UFSTAT) & UFSTAT_RXCOUNT)
294		uart_rx_put(sc, uart_getreg(&sc->sc_bas, SSCOM_URXH));
295
296	return (0);
297}
298
299static int
300exynos4210_bus_param(struct uart_softc *sc, int baudrate, int databits,
301    int stopbits, int parity)
302{
303	int error;
304
305	if (sc->sc_bas.rclk == 0)
306		sc->sc_bas.rclk = DEF_CLK;
307
308	KASSERT(sc->sc_bas.rclk != 0, ("exynos4210_init: Invalid rclk"));
309
310	uart_lock(sc->sc_hwmtx);
311	error = exynos4210_uart_param(&sc->sc_bas, baudrate, databits, stopbits,
312	    parity);
313	uart_unlock(sc->sc_hwmtx);
314
315	return (error);
316}
317
318static int
319exynos4210_bus_ipend(struct uart_softc *sc)
320{
321	uint32_t ints;
322	uint32_t txempty, rxready;
323	int reg;
324	int ipend;
325
326	uart_lock(sc->sc_hwmtx);
327	ints = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UINTP);
328	bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh, SSCOM_UINTP, ints);
329
330	txempty = (1 << 2);
331	rxready = (1 << 0);
332
333	ipend = 0;
334	if ((ints & txempty) > 0) {
335		if (sc->sc_txbusy != 0)
336			ipend |= SER_INT_TXIDLE;
337
338		/* mask TX interrupt */
339		reg = bus_space_read_4(sc->sc_bas.bst, sc->sc_bas.bsh,
340		    SSCOM_UINTM);
341		reg |= (1 << 2);
342		bus_space_write_4(sc->sc_bas.bst, sc->sc_bas.bsh,
343		    SSCOM_UINTM, reg);
344	}
345
346	if ((ints & rxready) > 0) {
347		ipend |= SER_INT_RXREADY;
348	}
349
350	uart_unlock(sc->sc_hwmtx);
351	return (ipend);
352}
353
354static int
355exynos4210_bus_flush(struct uart_softc *sc, int what)
356{
357
358	return (0);
359}
360
361static int
362exynos4210_bus_getsig(struct uart_softc *sc)
363{
364
365	return (0);
366}
367
368static int
369exynos4210_bus_ioctl(struct uart_softc *sc, int request, intptr_t data)
370{
371
372	return (EINVAL);
373}
374
375static struct uart_class uart_exynos4210_class = {
376	"exynos4210 class",
377	exynos4210_methods,
378	1,
379	.uc_ops = &uart_exynos4210_ops,
380	.uc_range = 8,
381	.uc_rclk = 0,
382	.uc_rshift = 0
383};
384
385static struct ofw_compat_data compat_data[] = {
386	{"exynos",		(uintptr_t)&uart_exynos4210_class},
387	{NULL,			(uintptr_t)NULL},
388};
389UART_FDT_CLASS_AND_DEVICE(compat_data);
390