summaryrefslogtreecommitdiffstats
path: root/drivers/serial/serial_cadence.c
blob: 0501c400b12982cd69be50455daaebbb7a192816 (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
/*
 * (c) 2012 Steffen Trumtrar <s.trumtrar@pengutronix.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */
#include <common.h>
#include <driver.h>
#include <init.h>
#include <malloc.h>
#include <notifier.h>
#include <io.h>
#include <linux/err.h>
#include <linux/clk.h>

#define CADENCE_UART_CONTROL		0x00
#define CADENCE_UART_MODE		0x04
#define CADENCE_UART_BAUD_GEN		0x18
#define CADENCE_UART_CHANNEL_STS	0x2C
#define CADENCE_UART_RXTXFIFO		0x30
#define CADENCE_UART_BAUD_DIV		0x34

#define CADENCE_CTRL_RXRES		(1 << 0)
#define CADENCE_CTRL_TXRES		(1 << 1)
#define CADENCE_CTRL_RXEN		(1 << 2)
#define CADENCE_CTRL_RXDIS		(1 << 3)
#define CADENCE_CTRL_TXEN		(1 << 4)
#define CADENCE_CTRL_TXDIS		(1 << 5)
#define CADENCE_CTRL_RSTTO		(1 << 6)
#define CADENCE_CTRL_STTBRK		(1 << 7)
#define CADENCE_CTRL_STPBRK		(1 << 8)

#define CADENCE_MODE_CLK_REF		(0 << 0)
#define CADENCE_MODE_CLK_REF_DIV	(1 << 0)
#define CADENCE_MODE_CHRL_6		(3 << 1)
#define CADENCE_MODE_CHRL_7		(2 << 1)
#define CADENCE_MODE_CHRL_8		(0 << 1)
#define CADENCE_MODE_PAR_EVEN		(0 << 3)
#define CADENCE_MODE_PAR_ODD		(1 << 3)
#define CADENCE_MODE_PAR_SPACE		(2 << 3)
#define CADENCE_MODE_PAR_MARK		(3 << 3)
#define CADENCE_MODE_PAR_NONE		(4 << 3)

#define CADENCE_STS_REMPTY		(1 << 1)
#define CADENCE_STS_RFUL		(1 << 2)
#define CADENCE_STS_TEMPTY		(1 << 3)
#define CADENCE_STS_TFUL		(1 << 4)

/*
 * create default values for different platforms
 */
struct cadence_serial_devtype_data {
	u32 ctrl;
	u32 mode;
};

static struct cadence_serial_devtype_data cadence_r1p08_data = {
	.ctrl = CADENCE_CTRL_RXEN | CADENCE_CTRL_TXEN,
	.mode = CADENCE_MODE_CLK_REF | CADENCE_MODE_CHRL_8 | CADENCE_MODE_PAR_NONE,
};

struct cadence_serial_priv {
	struct console_device cdev;
	int baudrate;
	struct notifier_block notify;
	void __iomem *regs;
	struct clk *clk;
	struct cadence_serial_devtype_data *devtype;
};

static int cadence_serial_reset(struct console_device *cdev)
{
	struct cadence_serial_priv *priv = container_of(cdev,
					struct cadence_serial_priv, cdev);

	/* Soft-Reset Tx/Rx paths */
	writel(CADENCE_CTRL_RXRES | CADENCE_CTRL_TXRES, priv->regs +
		CADENCE_UART_CONTROL);

	while (readl(priv->regs + CADENCE_UART_CONTROL) &
		(CADENCE_CTRL_RXRES | CADENCE_CTRL_TXRES))
		;

	return 0;
}

static int cadence_serial_setbaudrate(struct console_device *cdev, int baudrate)
{
	struct cadence_serial_priv *priv = container_of(cdev,
					struct cadence_serial_priv, cdev);
	unsigned int gen, div;
	int calc_rate;
	unsigned long clk;
	int error;
	int val;

	clk = clk_get_rate(priv->clk);
	priv->baudrate = baudrate;

	/* disable transmitter and receiver */
	val = readl(priv->regs + CADENCE_UART_CONTROL);
	val &= ~CADENCE_CTRL_TXEN & ~CADENCE_CTRL_RXEN;
	writel(val, priv->regs + CADENCE_UART_CONTROL);

	/*
	 *	      clk
	 * rate = -----------
	 *	  gen*(div+1)
	 */

	for (div = 4; div < 256; div++) {
		gen = clk / (baudrate * (div + 1));

		if (gen < 1 || gen > 65535)
			continue;

		calc_rate = clk / (gen * (div + 1));
		error = baudrate - calc_rate;
		if (error < 0)
			error *= -1;
		if (((error * 100) / baudrate) < 3)
			break;
	}

	writel(gen, priv->regs + CADENCE_UART_BAUD_GEN);
	writel(div, priv->regs + CADENCE_UART_BAUD_DIV);

	/* Soft-Reset Tx/Rx paths */
	writel(CADENCE_CTRL_RXRES | CADENCE_CTRL_TXRES, priv->regs +
		CADENCE_UART_CONTROL);

	while (readl(priv->regs + CADENCE_UART_CONTROL) &
		(CADENCE_CTRL_RXRES | CADENCE_CTRL_TXRES))
		;

	/* Enable UART */
	writel(priv->devtype->ctrl, priv->regs + CADENCE_UART_CONTROL);

	return 0;
}

static int cadence_serial_init_port(struct console_device *cdev)
{
	struct cadence_serial_priv *priv = container_of(cdev,
					struct cadence_serial_priv, cdev);

	cadence_serial_reset(cdev);

	/* Enable UART */
	writel(priv->devtype->ctrl, priv->regs + CADENCE_UART_CONTROL);
	writel(priv->devtype->mode, priv->regs + CADENCE_UART_MODE);

	return 0;
}

static void cadence_serial_putc(struct console_device *cdev, char c)
{
	struct cadence_serial_priv *priv = container_of(cdev,
					struct cadence_serial_priv, cdev);

	while ((readl(priv->regs + CADENCE_UART_CHANNEL_STS) &
		CADENCE_STS_TFUL) != 0)
		;

	writel(c, priv->regs + CADENCE_UART_RXTXFIFO);
}

static int cadence_serial_tstc(struct console_device *cdev)
{
	struct cadence_serial_priv *priv = container_of(cdev,
					struct cadence_serial_priv, cdev);

	return ((readl(priv->regs + CADENCE_UART_CHANNEL_STS) &
		 CADENCE_STS_REMPTY) == 0);
}

static int cadence_serial_getc(struct console_device *cdev)
{
	struct cadence_serial_priv *priv = container_of(cdev,
					struct cadence_serial_priv, cdev);

	while (!cadence_serial_tstc(cdev))
		;

	return readl(priv->regs + CADENCE_UART_RXTXFIFO);
}

static void cadence_serial_flush(struct console_device *cdev)
{
	struct cadence_serial_priv *priv = container_of(cdev,
					struct cadence_serial_priv, cdev);

	while ((readl(priv->regs + CADENCE_UART_CHANNEL_STS) &
		CADENCE_STS_TEMPTY) != 0)
		;
}

static int cadence_clocksource_clock_change(struct notifier_block *nb,
			unsigned long event, void *data)
{
	struct cadence_serial_priv *priv = container_of(nb,
					struct cadence_serial_priv, notify);

	cadence_serial_setbaudrate(&priv->cdev, priv->baudrate);

	return 0;
}

static int cadence_serial_probe(struct device_d *dev)
{
	struct resource *iores;
	struct console_device *cdev;
	struct cadence_serial_priv *priv;
	struct cadence_serial_devtype_data *devtype;
	int ret;

	ret = dev_get_drvdata(dev, (const void **)&devtype);
	if (ret)
		return ret;

	priv = xzalloc(sizeof(*priv));
	priv->devtype = devtype;
	cdev = &priv->cdev;
	dev->priv = priv;

	priv->clk = clk_get(dev, NULL);
	if (IS_ERR(priv->clk)) {
		ret = -ENODEV;
		goto err_free;
	}

	if (devtype->mode & CADENCE_MODE_CLK_REF_DIV)
		clk_set_rate(priv->clk, clk_get_rate(priv->clk) / 8);

	iores = dev_request_mem_resource(dev, 0);
	if (IS_ERR(iores)) {
		ret = PTR_ERR(iores);
		goto err_free;
	}
	priv->regs = IOMEM(iores->start);

	cdev->dev = dev;
	cdev->tstc = cadence_serial_tstc;
	cdev->putc = cadence_serial_putc;
	cdev->getc = cadence_serial_getc;
	cdev->flush = cadence_serial_flush;
	cdev->setbrg = cadence_serial_setbaudrate;

	cadence_serial_init_port(cdev);

	console_register(cdev);
	priv->notify.notifier_call = cadence_clocksource_clock_change;
	clock_register_client(&priv->notify);

	return 0;

err_free:
	free(priv);
	return ret;
}

static __maybe_unused struct of_device_id cadence_serial_dt_ids[] = {
	{
		.compatible = "xlnx,xuartps",
		.data = &cadence_r1p08_data,
	}, {
		/* sentinel */
	}
};

static struct platform_device_id cadence_serial_ids[] = {
	{
		.name = "cadence-uart",
		.driver_data = (unsigned long)&cadence_r1p08_data,
	}, {
		/* sentinel */
	},
};

static struct driver_d cadence_serial_driver = {
	.name   = "cadence_serial",
	.probe  = cadence_serial_probe,
	.of_compatible = DRV_OF_COMPAT(cadence_serial_dt_ids),
	.id_table = cadence_serial_ids,
};

static int cadence_serial_init(void)
{
	return platform_driver_register(&cadence_serial_driver);
}
console_initcall(cadence_serial_init);