summaryrefslogtreecommitdiff
path: root/drivers/tty/serial/aspeed-vuart.c
blob: 020c8159d25da0c417da70127f518b1935c7c3f1 (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
/*
 *  Serial Port driver for Aspeed VUART device
 *
 *    Copyright (C) 2016 Jeremy Kerr <jk@ozlabs.org>, IBM Corp.
 *    Copyright (C) 2006 Arnd Bergmann <arnd@arndb.de>, IBM Corp.
 *
 *  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.
 *
 */
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/clk.h>

#include "8250/8250.h"

#define AST_VUART_GCRA		0x20
#define AST_VUART_GCRA_VUART_EN		0x01
#define AST_VUART_GCRA_HOST_TX_DISCARD	0x20
#define AST_VUART_GCRB		0x24
#define AST_VUART_GCRB_HOST_SIRQ_MASK	0xf0
#define AST_VUART_GCRB_HOST_SIRQ_SHIFT	4
#define AST_VUART_ADDRL		0x28
#define AST_VUART_ADDRH		0x2c

struct ast_vuart {
	struct platform_device *pdev;
	void __iomem		*regs;
	struct clk		*clk;
	int			line;
};

static ssize_t ast_vuart_show_addr(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct ast_vuart *vuart = dev_get_drvdata(dev);
	u16 addr;

	addr = (readb(vuart->regs + AST_VUART_ADDRH) << 8) |
		(readb(vuart->regs + AST_VUART_ADDRL));

	return snprintf(buf, PAGE_SIZE - 1, "0x%x\n", addr);
}

static ssize_t ast_vuart_set_addr(struct device *dev,
		struct device_attribute *attr,
		const char *buf, size_t count)
{
	struct ast_vuart *vuart = dev_get_drvdata(dev);
	unsigned long val;
	int err;

	err = kstrtoul(buf, 0, &val);
	if (err)
		return err;

	writeb((val >> 8) & 0xff, vuart->regs + AST_VUART_ADDRH);
	writeb((val >> 0) & 0xff, vuart->regs + AST_VUART_ADDRL);

	return count;
}

static DEVICE_ATTR(lpc_address, S_IWUSR | S_IRUGO,
		ast_vuart_show_addr, ast_vuart_set_addr);

static ssize_t ast_vuart_show_sirq(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct ast_vuart *vuart = dev_get_drvdata(dev);
	u8 reg;

	reg = readb(vuart->regs + AST_VUART_GCRB);
	reg &= AST_VUART_GCRB_HOST_SIRQ_MASK;
	reg >>= AST_VUART_GCRB_HOST_SIRQ_SHIFT;

	return snprintf(buf, PAGE_SIZE - 1, "%u\n", reg);
}

static ssize_t ast_vuart_set_sirq(struct device *dev,
		struct device_attribute *attr,
		const char *buf, size_t count)
{
	struct ast_vuart *vuart = dev_get_drvdata(dev);
	unsigned long val;
	int err;
	u8 reg;

	err = kstrtoul(buf, 0, &val);
	if (err)
		return err;

	val <<= AST_VUART_GCRB_HOST_SIRQ_SHIFT;
	val &= AST_VUART_GCRB_HOST_SIRQ_MASK;

	reg = readb(vuart->regs + AST_VUART_GCRB);
	reg &= ~AST_VUART_GCRB_HOST_SIRQ_MASK;
	reg |= val;
	writeb(reg, vuart->regs + AST_VUART_GCRB);

	return count;
}

static DEVICE_ATTR(sirq, S_IWUSR | S_IRUGO,
		ast_vuart_show_sirq, ast_vuart_set_sirq);

static void ast_vuart_set_enabled(struct ast_vuart *vuart, bool enabled)
{
	u8 reg;

	reg = readb(vuart->regs + AST_VUART_GCRA);
	reg &= ~AST_VUART_GCRA_VUART_EN;
	if (enabled)
		reg |= AST_VUART_GCRA_VUART_EN;
	writeb(reg, vuart->regs + AST_VUART_GCRA);
}

static void ast_vuart_set_host_tx_discard(struct ast_vuart *vuart, bool discard)
{
	u8 reg;

	reg = readb(vuart->regs + AST_VUART_GCRA);

	/* if the HOST_TX_DISCARD bit is set, discard is *disabled* */
	reg &= ~AST_VUART_GCRA_HOST_TX_DISCARD;
	if (!discard)
		reg |= AST_VUART_GCRA_HOST_TX_DISCARD;

	writeb(reg, vuart->regs + AST_VUART_GCRA);
}

static int ast_vuart_startup(struct uart_port *uart_port)
{
	struct uart_8250_port *uart_8250_port = up_to_u8250p(uart_port);
	struct ast_vuart *vuart = uart_8250_port->port.private_data;
	int rc;

	rc = serial8250_do_startup(uart_port);
	if (rc)
		return rc;

	ast_vuart_set_host_tx_discard(vuart, false);

	return 0;
}

static void ast_vuart_shutdown(struct uart_port *uart_port)
{
	struct uart_8250_port *uart_8250_port = up_to_u8250p(uart_port);
	struct ast_vuart *vuart = uart_8250_port->port.private_data;

	ast_vuart_set_host_tx_discard(vuart, true);

	serial8250_do_shutdown(uart_port);
}


/**
 * The device tree parsing code here is heavily based on that of the of_serial
 * driver, but we have a few core differences, as we need to use our own
 * ioremapping for extra register support
 */
static int ast_vuart_probe(struct platform_device *pdev)
{
	struct uart_8250_port port;
	struct resource resource;
	struct ast_vuart *vuart;
	struct device_node *np;
	u32 clk, prop;
	int rc;

	np = pdev->dev.of_node;

	vuart = devm_kzalloc(&pdev->dev, sizeof(*vuart), GFP_KERNEL);
	if (!vuart)
		return -ENOMEM;

	vuart->pdev = pdev;
	rc = of_address_to_resource(np, 0, &resource);
	if (rc) {
		dev_warn(&pdev->dev, "invalid address\n");
		return rc;
	}

	/* create our own mapping for VUART-specific registers */
	vuart->regs = devm_ioremap_resource(&pdev->dev, &resource);
	if (IS_ERR(vuart->regs)) {
		dev_warn(&pdev->dev, "failed to map registers\n");
		return PTR_ERR(vuart->regs);
	}

	memset(&port, 0, sizeof(port));
	port.port.private_data = vuart;
	port.port.membase = vuart->regs;
	port.port.mapbase = resource.start;
	port.port.mapsize = resource_size(&resource);
	port.port.startup = ast_vuart_startup;
	port.port.shutdown = ast_vuart_shutdown;

	if (of_property_read_u32(np, "clock-frequency", &clk)) {
		vuart->clk = devm_clk_get(&pdev->dev, NULL);
		if (IS_ERR(vuart->clk)) {
			dev_warn(&pdev->dev,
				"clk or clock-frequency not defined\n");
			return PTR_ERR(vuart->clk);
		}

		rc = clk_prepare_enable(vuart->clk);
		if (rc < 0)
			return rc;

		clk = clk_get_rate(vuart->clk);
	}

	/* If current-speed was set, then try not to change it. */
	if (of_property_read_u32(np, "current-speed", &prop) == 0)
		port.port.custom_divisor = clk / (16 * prop);

	/* Check for shifted address mapping */
	if (of_property_read_u32(np, "reg-offset", &prop) == 0)
		port.port.mapbase += prop;

	/* Check for registers offset within the devices address range */
	if (of_property_read_u32(np, "reg-shift", &prop) == 0)
		port.port.regshift = prop;

	/* Check for fifo size */
	if (of_property_read_u32(np, "fifo-size", &prop) == 0)
		port.port.fifosize = prop;

	/* Check for a fixed line number */
	rc = of_alias_get_id(np, "serial");
	if (rc >= 0)
		port.port.line = rc;

	port.port.irq = irq_of_parse_and_map(np, 0);
	port.port.iotype = UPIO_MEM;
	if (of_property_read_u32(np, "reg-io-width", &prop) == 0) {
		switch (prop) {
		case 1:
			port.port.iotype = UPIO_MEM;
			break;
		case 4:
			port.port.iotype = of_device_is_big_endian(np) ?
				       UPIO_MEM32BE : UPIO_MEM32;
			break;
		default:
			dev_warn(&pdev->dev, "unsupported reg-io-width (%d)\n",
				 prop);
			rc = -EINVAL;
			goto err_clk_disable;
		}
	}

	port.port.type = PORT_16550A;
	port.port.uartclk = clk;
	port.port.flags = UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF
		| UPF_FIXED_PORT | UPF_FIXED_TYPE;

	if (of_find_property(np, "no-loopback-test", NULL))
		port.port.flags |= UPF_SKIP_TEST;

	port.port.dev = &pdev->dev;

	if (port.port.fifosize)
		port.capabilities = UART_CAP_FIFO;

	if (of_property_read_bool(pdev->dev.of_node,
				  "auto-flow-control"))
		port.capabilities |= UART_CAP_AFE;

	rc = serial8250_register_8250_port(&port);
	if (rc < 0)
		goto err_clk_disable;


	vuart->line = rc;
	ast_vuart_set_enabled(vuart, true);
	ast_vuart_set_host_tx_discard(vuart, true);
	platform_set_drvdata(pdev, vuart);

	/* extra sysfs control */
	rc = device_create_file(&pdev->dev, &dev_attr_lpc_address);
	if (rc)
		dev_warn(&pdev->dev, "can't create lpc_address file\n");
	rc = device_create_file(&pdev->dev, &dev_attr_sirq);
	if (rc)
		dev_warn(&pdev->dev, "can't create sirq file\n");

	return 0;

err_clk_disable:
	if (vuart->clk)
		clk_disable_unprepare(vuart->clk);

	irq_dispose_mapping(port.port.irq);
	return rc;
}

static int ast_vuart_remove(struct platform_device *pdev)
{
	struct ast_vuart *vuart = platform_get_drvdata(pdev);

	ast_vuart_set_enabled(vuart, false);

	if (vuart->clk)
		clk_disable_unprepare(vuart->clk);
	return 0;
}

static const struct of_device_id ast_vuart_table[] = {
	{ .compatible = "aspeed,vuart" },
	{ },
};

static struct platform_driver ast_vuart_driver = {
	.driver = {
		.name = "aspeed-vuart",
		.of_match_table = ast_vuart_table,
	},
	.probe = ast_vuart_probe,
	.remove = ast_vuart_remove,
};

module_platform_driver(ast_vuart_driver);

MODULE_AUTHOR("Jeremy Kerr <jk@ozlabs.org>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Driver for Aspeed VUART device");