summaryrefslogtreecommitdiffstats
path: root/drivers/serial/serial_arc.c
blob: 7dbb49f81464c1dffbc949c91e88000da3bc0691 (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
/*
 * Copyright (C) 2013 Synopsys, Inc. (www.synopsys.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <common.h>
#include <dm.h>
#include <serial.h>

DECLARE_GLOBAL_DATA_PTR;

struct arc_serial_regs {
	unsigned int id0;
	unsigned int id1;
	unsigned int id2;
	unsigned int id3;
	unsigned int data;
	unsigned int status;
	unsigned int baudl;
	unsigned int baudh;
};


struct arc_serial_platdata {
	struct arc_serial_regs *reg;
	unsigned int uartclk;
};

/* Bit definitions of STATUS register */
#define UART_RXEMPTY		(1 << 5)
#define UART_OVERFLOW_ERR	(1 << 1)
#define UART_TXEMPTY		(1 << 7)

static int arc_serial_setbrg(struct udevice *dev, int baudrate)
{
	struct arc_serial_platdata *plat = dev->platdata;
	struct arc_serial_regs *const regs = plat->reg;
	int arc_console_baud = gd->cpu_clk / (baudrate * 4) - 1;

	writeb(arc_console_baud & 0xff, &regs->baudl);

#ifdef CONFIG_ARC
	/*
	 * UART ISS(Instruction Set simulator) emulation has a subtle bug:
	 * A existing value of Baudh = 0 is used as a indication to startup
	 * it's internal state machine.
	 * Thus if baudh is set to 0, 2 times, it chokes.
	 * This happens with BAUD=115200 and the formaula above
	 * Until that is fixed, when running on ISS, we will set baudh to !0
	 */
	if (gd->arch.running_on_hw)
		writeb((arc_console_baud & 0xff00) >> 8, &regs->baudh);
	else
		writeb(1, &regs->baudh);
#else
	writeb((arc_console_baud & 0xff00) >> 8, &regs->baudh);
#endif

	return 0;
}

static int arc_serial_putc(struct udevice *dev, const char c)
{
	struct arc_serial_platdata *plat = dev->platdata;
	struct arc_serial_regs *const regs = plat->reg;

	if (c == '\n')
		arc_serial_putc(dev, '\r');

	while (!(readb(&regs->status) & UART_TXEMPTY))
		;

	writeb(c, &regs->data);

	return 0;
}

static int arc_serial_tstc(struct arc_serial_regs *const regs)
{
	return !(readb(&regs->status) & UART_RXEMPTY);
}

static int arc_serial_pending(struct udevice *dev, bool input)
{
	struct arc_serial_platdata *plat = dev->platdata;
	struct arc_serial_regs *const regs = plat->reg;
	uint32_t status = readb(&regs->status);

	if (input)
		return status & UART_RXEMPTY ? 0 : 1;
	else
		return status & UART_TXEMPTY ? 0 : 1;
}

static int arc_serial_getc(struct udevice *dev)
{
	struct arc_serial_platdata *plat = dev->platdata;
	struct arc_serial_regs *const regs = plat->reg;

	while (!arc_serial_tstc(regs))
		;

	/* Check for overflow errors */
	if (readb(&regs->status) & UART_OVERFLOW_ERR)
		return 0;

	return readb(&regs->data) & 0xFF;
}

static int arc_serial_probe(struct udevice *dev)
{
	return 0;
}

static const struct dm_serial_ops arc_serial_ops = {
	.putc = arc_serial_putc,
	.pending = arc_serial_pending,
	.getc = arc_serial_getc,
	.setbrg = arc_serial_setbrg,
};

static const struct udevice_id arc_serial_ids[] = {
	{ .compatible = "snps,arc-uart" },
	{ }
};

static int arc_serial_ofdata_to_platdata(struct udevice *dev)
{
	struct arc_serial_platdata *plat = dev_get_platdata(dev);
	DECLARE_GLOBAL_DATA_PTR;

	plat->reg = (struct arc_serial_regs *)dev_get_addr(dev);
	plat->uartclk = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
				       "clock-frequency", 0);

	return 0;
}

U_BOOT_DRIVER(serial_arc) = {
	.name	= "serial_arc",
	.id	= UCLASS_SERIAL,
	.of_match = arc_serial_ids,
	.ofdata_to_platdata = arc_serial_ofdata_to_platdata,
	.probe = arc_serial_probe,
	.ops	= &arc_serial_ops,
	.flags = DM_FLAG_PRE_RELOC,
};
OpenPOWER on IntegriCloud