summaryrefslogtreecommitdiff
path: root/drivers/tty/serial/8250/8250_acorn.c
blob: 857498312a9a84146b6c4c3493c224491fc1213d (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
/*
 *  linux/drivers/serial/acorn.c
 *
 *  Copyright (C) 1996-2003 Russell King.
 *
 * 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 <linux/module.h>
#include <linux/types.h>
#include <linux/tty.h>
#include <linux/serial_core.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/init.h>

#include <asm/io.h>
#include <asm/ecard.h>
#include <asm/string.h>

#include "8250.h"

#define MAX_PORTS	3

struct serial_card_type {
	unsigned int	num_ports;
	unsigned int	uartclk;
	unsigned int	type;
	unsigned int	offset[MAX_PORTS];
};

struct serial_card_info {
	unsigned int	num_ports;
	int		ports[MAX_PORTS];
	void __iomem *vaddr;
};

static int __devinit
serial_card_probe(struct expansion_card *ec, const struct ecard_id *id)
{
	struct serial_card_info *info;
	struct serial_card_type *type = id->data;
	struct uart_8250_port uart;
	unsigned long bus_addr;
	unsigned int i;

	info = kzalloc(sizeof(struct serial_card_info), GFP_KERNEL);
	if (!info)
		return -ENOMEM;

	info->num_ports = type->num_ports;

	bus_addr = ecard_resource_start(ec, type->type);
	info->vaddr = ecardm_iomap(ec, type->type, 0, 0);
	if (!info->vaddr) {
		kfree(info);
		return -ENOMEM;
	}

	ecard_set_drvdata(ec, info);

	memset(&uart, 0, sizeof(struct uart_8250_port));
	uart.port.irq	= ec->irq;
	uart.port.flags	= UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ;
	uart.port.uartclk	= type->uartclk;
	uart.port.iotype	= UPIO_MEM;
	uart.port.regshift	= 2;
	uart.port.dev	= &ec->dev;

	for (i = 0; i < info->num_ports; i ++) {
		uart.port.membase = info->vaddr + type->offset[i];
		uart.port.mapbase = bus_addr + type->offset[i];

		info->ports[i] = serial8250_register_8250_port(&uart);
	}

	return 0;
}

static void __devexit serial_card_remove(struct expansion_card *ec)
{
	struct serial_card_info *info = ecard_get_drvdata(ec);
	int i;

	ecard_set_drvdata(ec, NULL);

	for (i = 0; i < info->num_ports; i++)
		if (info->ports[i] > 0)
			serial8250_unregister_port(info->ports[i]);

	kfree(info);
}

static struct serial_card_type atomwide_type = {
	.num_ports	= 3,
	.uartclk	= 7372800,
	.type		= ECARD_RES_IOCSLOW,
	.offset		= { 0x2800, 0x2400, 0x2000 },
};

static struct serial_card_type serport_type = {
	.num_ports	= 2,
	.uartclk	= 3686400,
	.type		= ECARD_RES_IOCSLOW,
	.offset		= { 0x2000, 0x2020 },
};

static const struct ecard_id serial_cids[] = {
	{ MANU_ATOMWIDE,	PROD_ATOMWIDE_3PSERIAL,	&atomwide_type	},
	{ MANU_SERPORT,		PROD_SERPORT_DSPORT,	&serport_type	},
	{ 0xffff, 0xffff }
};

static struct ecard_driver serial_card_driver = {
	.probe		= serial_card_probe,
	.remove 	= __devexit_p(serial_card_remove),
	.id_table	= serial_cids,
	.drv = {
		.name	= "8250_acorn",
	},
};

static int __init serial_card_init(void)
{
	return ecard_register_driver(&serial_card_driver);
}

static void __exit serial_card_exit(void)
{
	ecard_remove_driver(&serial_card_driver);
}

MODULE_AUTHOR("Russell King");
MODULE_DESCRIPTION("Acorn 8250-compatible serial port expansion card driver");
MODULE_LICENSE("GPL");

module_init(serial_card_init);
module_exit(serial_card_exit);