summaryrefslogtreecommitdiffstats
path: root/drivers/net/can/sja1000/tscan1.c
blob: 79572457a2d65f09f73bb9b6c55dc3e2d9beb800 (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
/*
 * tscan1.c: driver for Technologic Systems TS-CAN1 PC104 boards
 *
 * Copyright 2010 Andre B. Oliveira
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * References:
 * - Getting started with TS-CAN1, Technologic Systems, Jun 2009
 *	http://www.embeddedarm.com/documentation/ts-can1-manual.pdf
 */

#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/isa.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include "sja1000.h"

MODULE_DESCRIPTION("Driver for Technologic Systems TS-CAN1 PC104 boards");
MODULE_AUTHOR("Andre B. Oliveira <anbadeol@gmail.com>");
MODULE_LICENSE("GPL");

/* Maximum number of boards (one in each JP1:JP2 setting of IO address) */
#define TSCAN1_MAXDEV 4

/* PLD registers address offsets */
#define TSCAN1_ID1	0
#define TSCAN1_ID2	1
#define TSCAN1_VERSION	2
#define TSCAN1_LED	3
#define TSCAN1_PAGE	4
#define TSCAN1_MODE	5
#define TSCAN1_JUMPERS	6

/* PLD board identifier registers magic values */
#define TSCAN1_ID1_VALUE 0xf6
#define TSCAN1_ID2_VALUE 0xb9

/* PLD mode register SJA1000 IO enable bit */
#define TSCAN1_MODE_ENABLE 0x40

/* PLD jumpers register bits */
#define TSCAN1_JP4 0x10
#define TSCAN1_JP5 0x20

/* PLD IO base addresses start */
#define TSCAN1_PLD_ADDRESS 0x150

/* PLD register space size */
#define TSCAN1_PLD_SIZE 8

/* SJA1000 register space size */
#define TSCAN1_SJA1000_SIZE 32

/* SJA1000 crystal frequency (16MHz) */
#define TSCAN1_SJA1000_XTAL 16000000

/* SJA1000 IO base addresses */
static const unsigned short tscan1_sja1000_addresses[] = {
	0x100, 0x120, 0x180, 0x1a0, 0x200, 0x240, 0x280, 0x320
};

/* Read SJA1000 register */
static u8 tscan1_read(const struct sja1000_priv *priv, int reg)
{
	return inb((unsigned long)priv->reg_base + reg);
}

/* Write SJA1000 register */
static void tscan1_write(const struct sja1000_priv *priv, int reg, u8 val)
{
	outb(val, (unsigned long)priv->reg_base + reg);
}

/* Probe for a TS-CAN1 board with JP2:JP1 jumper setting ID */
static int tscan1_probe(struct device *dev, unsigned id)
{
	struct net_device *netdev;
	struct sja1000_priv *priv;
	unsigned long pld_base, sja1000_base;
	int irq, i;

	pld_base = TSCAN1_PLD_ADDRESS + id * TSCAN1_PLD_SIZE;
	if (!request_region(pld_base, TSCAN1_PLD_SIZE, dev_name(dev)))
		return -EBUSY;

	if (inb(pld_base + TSCAN1_ID1) != TSCAN1_ID1_VALUE ||
	    inb(pld_base + TSCAN1_ID2) != TSCAN1_ID2_VALUE) {
		release_region(pld_base, TSCAN1_PLD_SIZE);
		return -ENODEV;
	}

	switch (inb(pld_base + TSCAN1_JUMPERS) & (TSCAN1_JP4 | TSCAN1_JP5)) {
	case TSCAN1_JP4:
		irq = 6;
		break;
	case TSCAN1_JP5:
		irq = 7;
		break;
	case TSCAN1_JP4 | TSCAN1_JP5:
		irq = 5;
		break;
	default:
		dev_err(dev, "invalid JP4:JP5 setting (no IRQ)\n");
		release_region(pld_base, TSCAN1_PLD_SIZE);
		return -EINVAL;
	}

	netdev = alloc_sja1000dev(0);
	if (!netdev) {
		release_region(pld_base, TSCAN1_PLD_SIZE);
		return -ENOMEM;
	}

	dev_set_drvdata(dev, netdev);
	SET_NETDEV_DEV(netdev, dev);

	netdev->base_addr = pld_base;
	netdev->irq = irq;

	priv = netdev_priv(netdev);
	priv->read_reg = tscan1_read;
	priv->write_reg = tscan1_write;
	priv->can.clock.freq = TSCAN1_SJA1000_XTAL / 2;
	priv->cdr = CDR_CBP | CDR_CLK_OFF;
	priv->ocr = OCR_TX0_PUSHPULL;

	/* Select the first SJA1000 IO address that is free and that works */
	for (i = 0; i < ARRAY_SIZE(tscan1_sja1000_addresses); i++) {
		sja1000_base = tscan1_sja1000_addresses[i];
		if (!request_region(sja1000_base, TSCAN1_SJA1000_SIZE,
								dev_name(dev)))
			continue;

		/* Set SJA1000 IO base address and enable it */
		outb(TSCAN1_MODE_ENABLE | i, pld_base + TSCAN1_MODE);

		priv->reg_base = (void __iomem *)sja1000_base;
		if (!register_sja1000dev(netdev)) {
			/* SJA1000 probe succeeded; turn LED off and return */
			outb(0, pld_base + TSCAN1_LED);
			netdev_info(netdev, "TS-CAN1 at 0x%lx 0x%lx irq %d\n",
						pld_base, sja1000_base, irq);
			return 0;
		}

		/* SJA1000 probe failed; release and try next address */
		outb(0, pld_base + TSCAN1_MODE);
		release_region(sja1000_base, TSCAN1_SJA1000_SIZE);
	}

	dev_err(dev, "failed to assign SJA1000 IO address\n");
	dev_set_drvdata(dev, NULL);
	free_sja1000dev(netdev);
	release_region(pld_base, TSCAN1_PLD_SIZE);
	return -ENXIO;
}

static int tscan1_remove(struct device *dev, unsigned id /*unused*/)
{
	struct net_device *netdev;
	struct sja1000_priv *priv;
	unsigned long pld_base, sja1000_base;

	netdev = dev_get_drvdata(dev);
	unregister_sja1000dev(netdev);
	dev_set_drvdata(dev, NULL);

	priv = netdev_priv(netdev);
	pld_base = netdev->base_addr;
	sja1000_base = (unsigned long)priv->reg_base;

	outb(0, pld_base + TSCAN1_MODE);	/* disable SJA1000 IO space */

	release_region(sja1000_base, TSCAN1_SJA1000_SIZE);
	release_region(pld_base, TSCAN1_PLD_SIZE);

	free_sja1000dev(netdev);

	return 0;
}

static struct isa_driver tscan1_isa_driver = {
	.probe = tscan1_probe,
	.remove = tscan1_remove,
	.driver = {
		.name = "tscan1",
	},
};

module_isa_driver(tscan1_isa_driver, TSCAN1_MAXDEV);
OpenPOWER on IntegriCloud