summaryrefslogtreecommitdiff
path: root/drivers/tty/serial/8250/8250_men_mcb.c
blob: c27c52d18dfa57956d2af24d409b6d48718ca1e4 (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
// SPDX-License-Identifier: GPL-2.0
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/mcb.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/serial_8250.h>

#define MEN_UART_ID_Z025 0x19
#define MEN_UART_ID_Z057 0x39
#define MEN_UART_ID_Z125 0x7d

/*
 * IP Cores Z025 and Z057 can have up to 4 UART
 * The UARTs available are stored in a global
 * register saved in physical address + 0x40
 * Is saved as follows:
 *
 * 7                                                              0
 * +------+-------+-------+-------+-------+-------+-------+-------+
 * |UART4 | UART3 | UART2 | UART1 | U4irq | U3irq | U2irq | U1irq |
 * +------+-------+-------+-------+-------+-------+-------+-------+
 */
#define MEN_UART1_MASK	0x01
#define MEN_UART2_MASK	0x02
#define MEN_UART3_MASK	0x04
#define MEN_UART4_MASK	0x08

#define MEN_Z125_UARTS_AVAILABLE	0x01

#define MEN_Z025_MAX_UARTS		4
#define MEN_UART_MEM_SIZE		0x10
#define MEM_UART_REGISTER_SIZE		0x01
#define MEN_Z025_REGISTER_OFFSET	0x40

#define MEN_UART1_OFFSET	0
#define MEN_UART2_OFFSET	(MEN_UART1_OFFSET + MEN_UART_MEM_SIZE)
#define MEN_UART3_OFFSET	(MEN_UART2_OFFSET + MEN_UART_MEM_SIZE)
#define MEN_UART4_OFFSET	(MEN_UART3_OFFSET + MEN_UART_MEM_SIZE)

#define MEN_READ_REGISTER(addr)	readb((void *)addr)

#define MAX_PORTS	4

struct serial_8250_men_mcb_data {
	int num_ports;
	unsigned int line[MAX_PORTS];
	unsigned int offset[MAX_PORTS];
};

/*
 * The Z125 16550-compatible UART has no fixed base clock assigned
 * So, depending on the board we're on, we need to adjust the
 * parameter in order to really set the correct baudrate, and
 * do so if possible without user interaction
 */
static u32 men_lookup_uartclk(struct mcb_device *mdev)
{
	/* use default value if board is not available below */
	u32 clkval = 1041666;

	dev_info(&mdev->dev, "%s on board %s\n",
		dev_name(&mdev->dev),
		mdev->bus->name);
	if  (strncmp(mdev->bus->name, "F075", 4) == 0)
		clkval = 1041666;
	else if (strncmp(mdev->bus->name, "F216", 4) == 0)
		clkval = 1843200;
	else if (strncmp(mdev->bus->name, "F210", 4) == 0)
		clkval = 115200;
	else if (strstr(mdev->bus->name, "215"))
		clkval = 1843200;
	else
		dev_info(&mdev->dev,
			 "board not detected, using default uartclk\n");

	clkval = clkval  << 4;

	return clkval;
}

static int read_uarts_available_from_register(struct resource *mem_res,
					      u8 *uarts_available)
{
	void __iomem *mem;
	int reg_value;

	if (!request_mem_region(mem_res->start + MEN_Z025_REGISTER_OFFSET,
				MEM_UART_REGISTER_SIZE,  KBUILD_MODNAME)) {
		return -EBUSY;
	}

	mem = ioremap(mem_res->start + MEN_Z025_REGISTER_OFFSET,
		      MEM_UART_REGISTER_SIZE);
	if (!mem) {
		release_mem_region(mem_res->start + MEN_Z025_REGISTER_OFFSET,
				   MEM_UART_REGISTER_SIZE);
		return -ENOMEM;
	}

	reg_value = MEN_READ_REGISTER(mem);

	iounmap(mem);

	release_mem_region(mem_res->start + MEN_Z025_REGISTER_OFFSET,
			   MEM_UART_REGISTER_SIZE);

	*uarts_available = reg_value >> 4;

	return 0;
}

static int read_serial_data(struct mcb_device *mdev,
			    struct resource *mem_res,
			    struct serial_8250_men_mcb_data *serial_data)
{
	u8 uarts_available;
	int count = 0;
	int mask;
	int res;
	int i;

	res = read_uarts_available_from_register(mem_res, &uarts_available);
	if (res < 0)
		return res;

	for (i = 0; i < MAX_PORTS; i++) {
		mask = 0x1 << i;
		switch (uarts_available & mask) {
		case MEN_UART1_MASK:
			serial_data->offset[count] = MEN_UART1_OFFSET;
			count++;
			break;
		case MEN_UART2_MASK:
			serial_data->offset[count] = MEN_UART2_OFFSET;
			count++;
			break;
		case MEN_UART3_MASK:
			serial_data->offset[count] = MEN_UART3_OFFSET;
			count++;
			break;
		case MEN_UART4_MASK:
			serial_data->offset[count] = MEN_UART4_OFFSET;
			count++;
			break;
		default:
			return -EINVAL;
		}
	}

	if (count <= 0 || count > MAX_PORTS) {
		dev_err(&mdev->dev, "unexpected number of ports: %u\n",
			count);
		return -ENODEV;
	}

	serial_data->num_ports = count;

	return 0;
}

static int init_serial_data(struct mcb_device *mdev,
			    struct resource *mem_res,
			    struct serial_8250_men_mcb_data *serial_data)
{
	switch (mdev->id) {
	case MEN_UART_ID_Z125:
		serial_data->num_ports = 1;
		serial_data->offset[0] = 0;
		return 0;
	case MEN_UART_ID_Z025:
	case MEN_UART_ID_Z057:
		return read_serial_data(mdev, mem_res, serial_data);
	default:
		dev_err(&mdev->dev, "no supported device!\n");
		return -ENODEV;
	}
}

static int serial_8250_men_mcb_probe(struct mcb_device *mdev,
				     const struct mcb_device_id *id)
{
	struct uart_8250_port uart;
	struct serial_8250_men_mcb_data *data;
	struct resource *mem;
	int i;
	int res;

	mem = mcb_get_resource(mdev, IORESOURCE_MEM);
	if (mem == NULL)
		return -ENXIO;

	data = devm_kzalloc(&mdev->dev,
			    sizeof(struct serial_8250_men_mcb_data),
			    GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	res = init_serial_data(mdev, mem, data);
	if (res < 0)
		return res;

	dev_dbg(&mdev->dev, "found a 16z%03u with %u ports\n",
		mdev->id, data->num_ports);

	mcb_set_drvdata(mdev, data);

	for (i = 0; i < data->num_ports; i++) {
		memset(&uart, 0, sizeof(struct uart_8250_port));
		spin_lock_init(&uart.port.lock);

		uart.port.flags = UPF_SKIP_TEST |
				  UPF_SHARE_IRQ |
				  UPF_BOOT_AUTOCONF |
				  UPF_IOREMAP;
		uart.port.iotype = UPIO_MEM;
		uart.port.uartclk = men_lookup_uartclk(mdev);
		uart.port.irq = mcb_get_irq(mdev);
		uart.port.mapbase = (unsigned long) mem->start
					    + data->offset[i];

		/* ok, register the port */
		res = serial8250_register_8250_port(&uart);
		if (res < 0) {
			dev_err(&mdev->dev, "unable to register UART port\n");
			return res;
		}

		data->line[i] = res;
		dev_info(&mdev->dev, "found MCB UART: ttyS%d\n", data->line[i]);
	}

	return 0;
}

static void serial_8250_men_mcb_remove(struct mcb_device *mdev)
{
	int i;
	struct serial_8250_men_mcb_data *data = mcb_get_drvdata(mdev);

	if (!data)
		return;

	for (i = 0; i < data->num_ports; i++)
		serial8250_unregister_port(data->line[i]);
}

static const struct mcb_device_id serial_8250_men_mcb_ids[] = {
	{ .device = MEN_UART_ID_Z025 },
	{ .device = MEN_UART_ID_Z057 },
	{ .device = MEN_UART_ID_Z125 },
	{ }
};
MODULE_DEVICE_TABLE(mcb, serial_8250_men_mcb_ids);

static struct mcb_driver mcb_driver = {
	.driver = {
		.name = "8250_men_mcb",
		.owner = THIS_MODULE,
	},
	.probe = serial_8250_men_mcb_probe,
	.remove = serial_8250_men_mcb_remove,
	.id_table = serial_8250_men_mcb_ids,
};
module_mcb_driver(mcb_driver);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MEN 8250 UART driver");
MODULE_AUTHOR("Michael Moese <michael.moese@men.de");
MODULE_ALIAS("mcb:16z125");
MODULE_ALIAS("mcb:16z025");
MODULE_ALIAS("mcb:16z057");
MODULE_IMPORT_NS(MCB);