summaryrefslogtreecommitdiff
path: root/drivers/serial/serial_mtk.c
blob: a84f39b3fa2ea1396b6a425590a54a93885ff113 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
// SPDX-License-Identifier: GPL-2.0
/*
 * MediaTek High-speed UART driver
 *
 * Copyright (C) 2018 MediaTek Inc.
 * Author: Weijie Gao <weijie.gao@mediatek.com>
 */

#include <clk.h>
#include <common.h>
#include <div64.h>
#include <dm.h>
#include <errno.h>
#include <log.h>
#include <serial.h>
#include <watchdog.h>
#include <asm/global_data.h>
#include <asm/io.h>
#include <asm/types.h>
#include <linux/err.h>

struct mtk_serial_regs {
	u32 rbr;
	u32 ier;
	u32 fcr;
	u32 lcr;
	u32 mcr;
	u32 lsr;
	u32 msr;
	u32 spr;
	u32 mdr1;
	u32 highspeed;
	u32 sample_count;
	u32 sample_point;
	u32 fracdiv_l;
	u32 fracdiv_m;
	u32 escape_en;
	u32 guard;
	u32 rx_sel;
};

#define thr rbr
#define iir fcr
#define dll rbr
#define dlm ier

#define UART_LCR_WLS_8	0x03		/* 8 bit character length */
#define UART_LCR_DLAB	0x80		/* Divisor latch access bit */

#define UART_LSR_DR	0x01		/* Data ready */
#define UART_LSR_THRE	0x20		/* Xmit holding register empty */
#define UART_LSR_TEMT	0x40		/* Xmitter empty */

#define UART_MCR_DTR	0x01		/* DTR   */
#define UART_MCR_RTS	0x02		/* RTS   */

#define UART_FCR_FIFO_EN	0x01	/* Fifo enable */
#define UART_FCR_RXSR		0x02	/* Receiver soft reset */
#define UART_FCR_TXSR		0x04	/* Transmitter soft reset */

#define UART_MCRVAL (UART_MCR_DTR | \
		     UART_MCR_RTS)

/* Clear & enable FIFOs */
#define UART_FCRVAL (UART_FCR_FIFO_EN | \
		     UART_FCR_RXSR |	\
		     UART_FCR_TXSR)

/* the data is correct if the real baud is within 3%. */
#define BAUD_ALLOW_MAX(baud)	((baud) + (baud) * 3 / 100)
#define BAUD_ALLOW_MIX(baud)	((baud) - (baud) * 3 / 100)

struct mtk_serial_priv {
	struct mtk_serial_regs __iomem *regs;
	u32 clock;
	bool force_highspeed;
};

static void _mtk_serial_setbrg(struct mtk_serial_priv *priv, int baud)
{
	u32 quot, realbaud, samplecount = 1;

	/* Special case for low baud clock */
	if (baud <= 115200 && priv->clock <= 12000000) {
		writel(3, &priv->regs->highspeed);

		quot = DIV_ROUND_CLOSEST(priv->clock, 256 * baud);
		if (quot == 0)
			quot = 1;

		samplecount = DIV_ROUND_CLOSEST(priv->clock, quot * baud);

		realbaud = priv->clock / samplecount / quot;
		if (realbaud > BAUD_ALLOW_MAX(baud) ||
		    realbaud < BAUD_ALLOW_MIX(baud)) {
			pr_info("baud %d can't be handled\n", baud);
		}

		goto set_baud;
	}

	if (priv->force_highspeed)
		goto use_hs3;

	if (baud <= 115200) {
		writel(0, &priv->regs->highspeed);
		quot = DIV_ROUND_CLOSEST(priv->clock, 16 * baud);
	} else if (baud <= 576000) {
		writel(2, &priv->regs->highspeed);

		/* Set to next lower baudrate supported */
		if ((baud == 500000) || (baud == 576000))
			baud = 460800;

		quot = DIV_ROUND_UP(priv->clock, 4 * baud);
	} else {
use_hs3:
		writel(3, &priv->regs->highspeed);

		quot = DIV_ROUND_UP(priv->clock, 256 * baud);
		samplecount = DIV_ROUND_CLOSEST(priv->clock, quot * baud);
	}

set_baud:
	/* set divisor */
	writel(UART_LCR_WLS_8 | UART_LCR_DLAB, &priv->regs->lcr);
	writel(quot & 0xff, &priv->regs->dll);
	writel((quot >> 8) & 0xff, &priv->regs->dlm);
	writel(UART_LCR_WLS_8, &priv->regs->lcr);

	/* set highspeed mode sample count & point */
	writel(samplecount - 1, &priv->regs->sample_count);
	writel((samplecount - 2) >> 1, &priv->regs->sample_point);
}

static int _mtk_serial_putc(struct mtk_serial_priv *priv, const char ch)
{
	if (!(readl(&priv->regs->lsr) & UART_LSR_THRE))
		return -EAGAIN;

	writel(ch, &priv->regs->thr);

	if (ch == '\n')
		WATCHDOG_RESET();

	return 0;
}

static int _mtk_serial_getc(struct mtk_serial_priv *priv)
{
	if (!(readl(&priv->regs->lsr) & UART_LSR_DR))
		return -EAGAIN;

	return readl(&priv->regs->rbr);
}

static int _mtk_serial_pending(struct mtk_serial_priv *priv, bool input)
{
	if (input)
		return (readl(&priv->regs->lsr) & UART_LSR_DR) ? 1 : 0;
	else
		return (readl(&priv->regs->lsr) & UART_LSR_THRE) ? 0 : 1;
}

#if defined(CONFIG_DM_SERIAL) && \
	(!defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_DM))
static int mtk_serial_setbrg(struct udevice *dev, int baudrate)
{
	struct mtk_serial_priv *priv = dev_get_priv(dev);

	_mtk_serial_setbrg(priv, baudrate);

	return 0;
}

static int mtk_serial_putc(struct udevice *dev, const char ch)
{
	struct mtk_serial_priv *priv = dev_get_priv(dev);

	return _mtk_serial_putc(priv, ch);
}

static int mtk_serial_getc(struct udevice *dev)
{
	struct mtk_serial_priv *priv = dev_get_priv(dev);

	return _mtk_serial_getc(priv);
}

static int mtk_serial_pending(struct udevice *dev, bool input)
{
	struct mtk_serial_priv *priv = dev_get_priv(dev);

	return _mtk_serial_pending(priv, input);
}

static int mtk_serial_probe(struct udevice *dev)
{
	struct mtk_serial_priv *priv = dev_get_priv(dev);

	/* Disable interrupt */
	writel(0, &priv->regs->ier);

	writel(UART_MCRVAL, &priv->regs->mcr);
	writel(UART_FCRVAL, &priv->regs->fcr);

	return 0;
}

static int mtk_serial_of_to_plat(struct udevice *dev)
{
	struct mtk_serial_priv *priv = dev_get_priv(dev);
	fdt_addr_t addr;
	struct clk clk;
	int err;

	addr = dev_read_addr(dev);
	if (addr == FDT_ADDR_T_NONE)
		return -EINVAL;

	priv->regs = map_physmem(addr, 0, MAP_NOCACHE);

	err = clk_get_by_index(dev, 0, &clk);
	if (!err) {
		err = clk_get_rate(&clk);
		if (!IS_ERR_VALUE(err))
			priv->clock = err;
	} else if (err != -ENOENT && err != -ENODEV && err != -ENOSYS) {
		debug("mtk_serial: failed to get clock\n");
		return err;
	}

	if (!priv->clock)
		priv->clock = dev_read_u32_default(dev, "clock-frequency", 0);

	if (!priv->clock) {
		debug("mtk_serial: clock not defined\n");
		return -EINVAL;
	}

	priv->force_highspeed = dev_read_bool(dev, "mediatek,force-highspeed");

	return 0;
}

static const struct dm_serial_ops mtk_serial_ops = {
	.putc = mtk_serial_putc,
	.pending = mtk_serial_pending,
	.getc = mtk_serial_getc,
	.setbrg = mtk_serial_setbrg,
};

static const struct udevice_id mtk_serial_ids[] = {
	{ .compatible = "mediatek,hsuart" },
	{ .compatible = "mediatek,mt6577-uart" },
	{ }
};

U_BOOT_DRIVER(serial_mtk) = {
	.name = "serial_mtk",
	.id = UCLASS_SERIAL,
	.of_match = mtk_serial_ids,
	.of_to_plat = mtk_serial_of_to_plat,
	.priv_auto	= sizeof(struct mtk_serial_priv),
	.probe = mtk_serial_probe,
	.ops = &mtk_serial_ops,
	.flags = DM_FLAG_PRE_RELOC,
};
#else

DECLARE_GLOBAL_DATA_PTR;

#define DECLARE_HSUART_PRIV(port) \
	static struct mtk_serial_priv mtk_hsuart##port = { \
	.regs = (struct mtk_serial_regs *)CONFIG_SYS_NS16550_COM##port, \
	.clock = CONFIG_SYS_NS16550_CLK \
};

#define DECLARE_HSUART_FUNCTIONS(port) \
	static int mtk_serial##port##_init(void) \
	{ \
		writel(0, &mtk_hsuart##port.regs->ier); \
		writel(UART_MCRVAL, &mtk_hsuart##port.regs->mcr); \
		writel(UART_FCRVAL, &mtk_hsuart##port.regs->fcr); \
		_mtk_serial_setbrg(&mtk_hsuart##port, gd->baudrate); \
		return 0 ; \
	} \
	static void mtk_serial##port##_setbrg(void) \
	{ \
		_mtk_serial_setbrg(&mtk_hsuart##port, gd->baudrate); \
	} \
	static int mtk_serial##port##_getc(void) \
	{ \
		int err; \
		do { \
			err = _mtk_serial_getc(&mtk_hsuart##port); \
			if (err == -EAGAIN) \
				WATCHDOG_RESET(); \
		} while (err == -EAGAIN); \
		return err >= 0 ? err : 0; \
	} \
	static int mtk_serial##port##_tstc(void) \
	{ \
		return _mtk_serial_pending(&mtk_hsuart##port, true); \
	} \
	static void mtk_serial##port##_putc(const char c) \
	{ \
		int err; \
		if (c == '\n') \
			mtk_serial##port##_putc('\r'); \
		do { \
			err = _mtk_serial_putc(&mtk_hsuart##port, c); \
		} while (err == -EAGAIN); \
	} \
	static void mtk_serial##port##_puts(const char *s) \
	{ \
		while (*s) { \
			mtk_serial##port##_putc(*s++); \
		} \
	}

/* Serial device descriptor */
#define INIT_HSUART_STRUCTURE(port, __name) {	\
	.name	= __name,			\
	.start	= mtk_serial##port##_init,	\
	.stop	= NULL,				\
	.setbrg	= mtk_serial##port##_setbrg,	\
	.getc	= mtk_serial##port##_getc,	\
	.tstc	= mtk_serial##port##_tstc,	\
	.putc	= mtk_serial##port##_putc,	\
	.puts	= mtk_serial##port##_puts,	\
}

#define DECLARE_HSUART(port, __name) \
	DECLARE_HSUART_PRIV(port); \
	DECLARE_HSUART_FUNCTIONS(port); \
	struct serial_device mtk_hsuart##port##_device = \
		INIT_HSUART_STRUCTURE(port, __name);

#if !defined(CONFIG_CONS_INDEX)
#elif (CONFIG_CONS_INDEX < 1) || (CONFIG_CONS_INDEX > 6)
#error	"Invalid console index value."
#endif

#if CONFIG_CONS_INDEX == 1 && !defined(CONFIG_SYS_NS16550_COM1)
#error	"Console port 1 defined but not configured."
#elif CONFIG_CONS_INDEX == 2 && !defined(CONFIG_SYS_NS16550_COM2)
#error	"Console port 2 defined but not configured."
#elif CONFIG_CONS_INDEX == 3 && !defined(CONFIG_SYS_NS16550_COM3)
#error	"Console port 3 defined but not configured."
#elif CONFIG_CONS_INDEX == 4 && !defined(CONFIG_SYS_NS16550_COM4)
#error	"Console port 4 defined but not configured."
#elif CONFIG_CONS_INDEX == 5 && !defined(CONFIG_SYS_NS16550_COM5)
#error	"Console port 5 defined but not configured."
#elif CONFIG_CONS_INDEX == 6 && !defined(CONFIG_SYS_NS16550_COM6)
#error	"Console port 6 defined but not configured."
#endif

#if defined(CONFIG_SYS_NS16550_COM1)
DECLARE_HSUART(1, "mtk-hsuart0");
#endif
#if defined(CONFIG_SYS_NS16550_COM2)
DECLARE_HSUART(2, "mtk-hsuart1");
#endif
#if defined(CONFIG_SYS_NS16550_COM3)
DECLARE_HSUART(3, "mtk-hsuart2");
#endif
#if defined(CONFIG_SYS_NS16550_COM4)
DECLARE_HSUART(4, "mtk-hsuart3");
#endif
#if defined(CONFIG_SYS_NS16550_COM5)
DECLARE_HSUART(5, "mtk-hsuart4");
#endif
#if defined(CONFIG_SYS_NS16550_COM6)
DECLARE_HSUART(6, "mtk-hsuart5");
#endif

__weak struct serial_device *default_serial_console(void)
{
#if CONFIG_CONS_INDEX == 1
	return &mtk_hsuart1_device;
#elif CONFIG_CONS_INDEX == 2
	return &mtk_hsuart2_device;
#elif CONFIG_CONS_INDEX == 3
	return &mtk_hsuart3_device;
#elif CONFIG_CONS_INDEX == 4
	return &mtk_hsuart4_device;
#elif CONFIG_CONS_INDEX == 5
	return &mtk_hsuart5_device;
#elif CONFIG_CONS_INDEX == 6
	return &mtk_hsuart6_device;
#else
#error "Bad CONFIG_CONS_INDEX."
#endif
}

void mtk_serial_initialize(void)
{
#if defined(CONFIG_SYS_NS16550_COM1)
	serial_register(&mtk_hsuart1_device);
#endif
#if defined(CONFIG_SYS_NS16550_COM2)
	serial_register(&mtk_hsuart2_device);
#endif
#if defined(CONFIG_SYS_NS16550_COM3)
	serial_register(&mtk_hsuart3_device);
#endif
#if defined(CONFIG_SYS_NS16550_COM4)
	serial_register(&mtk_hsuart4_device);
#endif
#if defined(CONFIG_SYS_NS16550_COM5)
	serial_register(&mtk_hsuart5_device);
#endif
#if defined(CONFIG_SYS_NS16550_COM6)
	serial_register(&mtk_hsuart6_device);
#endif
}

#endif

#ifdef CONFIG_DEBUG_UART_MTK

#include <debug_uart.h>

static inline void _debug_uart_init(void)
{
	struct mtk_serial_priv priv;

	priv.regs = (void *) CONFIG_VAL(DEBUG_UART_BASE);
	priv.clock = CONFIG_DEBUG_UART_CLOCK;

	writel(0, &priv.regs->ier);
	writel(UART_MCRVAL, &priv.regs->mcr);
	writel(UART_FCRVAL, &priv.regs->fcr);

	_mtk_serial_setbrg(&priv, CONFIG_BAUDRATE);
}

static inline void _debug_uart_putc(int ch)
{
	struct mtk_serial_regs __iomem *regs =
		(void *) CONFIG_VAL(DEBUG_UART_BASE);

	while (!(readl(&regs->lsr) & UART_LSR_THRE))
		;

	writel(ch, &regs->thr);
}

DEBUG_UART_FUNCS

#endif