summaryrefslogtreecommitdiff
path: root/src/superio/smsc/smscsuperio/superio.c
blob: dae720eb6bdf24721cf453709dae2d0fa9ef6493 (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
334
335
/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2007 Uwe Hermann <uwe@hermann-uwe.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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

/*
 * Generic driver for pretty much all known Standard Microsystems Corporation
 * (SMSC) Super I/O chips.
 *
 * Datasheets are available from: http://www.smsc.com/main/datasheet.html
 *
 * Most of the SMSC Super I/O chips seem to be similar enough (for our
 * purposes) so that we can handle them with a unified driver.
 *
 * So far only the ASUS A8000 has been tested on real hardware!
 *
 * The floppy disk controller, the parallel port, the serial ports, and the
 * keyboard controller should work with all the chips. For the more advanced
 * stuff (e.g. HWM, ACPI, SMBus) more work is probably required.
 */

#include <arch/io.h>
#include <device/device.h>
#include <device/pnp.h>
#include <console/console.h>
#include <uart8250.h>
#include <pc80/keyboard.h>
#include <stdlib.h>
#include "chip.h"

/* The following Super I/O chips are currently supported by this driver: */
#define FDC37B80X	0x42	/* Same ID: FDC37M70X (a.k.a. FDC37M707) */
#define FDC37B78X	0x44
#define FDC37B72X	0x4c
#define FDC37M81X	0x4d
#define FDC37M60X	0x47
#define LPC47B27X	0x51	/* a.k.a. LPC47B272 */
#define LPC47M10X	0x59	/* Same ID: LPC47M112, LPC47M13X */
#define LPC47M15X	0x60	/* Same ID: LPC47M192 */
#define LPC47S45X	0x62
#define LPC47B397	0x6f
#define A8000		0x77	/* ASUS A8000, a rebranded DME1737(?) */
#define DME1737		0x78
#define SCH3112		0x7c
#define SCH5307		0x81	/* Rebranded LPC47B397(?) */

/* Register defines */
#define DEVICE_ID_REG	0x20	/* Device ID register */
#define DEVICE_REV_REG	0x21	/* Device revision register */

/* Static variables for the Super I/O device ID and revision. */
static int first_time = 1;
static uint8_t superio_id = 0;
static uint8_t superio_rev = 0;

/**
 * A list of all possible logical devices which may be supported by at least
 * one of the Super I/O chips. These values are used as index into the
 * logical_device_table[i].devs array(s).
 *
 * If you change this enum, you must also adapt the logical_device_table[]
 * array and MAX_LOGICAL_DEVICES!
 */
enum {
	LD_FDC,		/* Floppy disk controller */
	LD_PP,		/* Parallel port */
	LD_SP1,		/* Serial port 1 (COM1) */
	LD_SP2,		/* Serial port 2 (COM2) */
	LD_RTC,		/* Real-time clock */
	LD_KBC,		/* Keyboard controller */
	LD_AUX,		/* Auxiliary I/O */
	LD_XBUS,	/* X-Bus */
	LD_HWM,		/* Hardware monitor */
	LD_GAME,	/* Game port */
	LD_PME,		/* Power management events */
	LD_MPU401,	/* MPU-401 MIDI UART */
	LD_RT,		/* Runtime registers / security key registers */
	LD_ACPI,	/* ACPI */
	LD_SMB,		/* SMBus */
};

/* Note: This value must match the number of items in the enum above! */
#define MAX_LOGICAL_DEVICES 15

/**
 * A table describing the logical devices which are present on the
 * supported Super I/O chips.
 *
 * The first entry (superio_id) is the device ID of the Super I/O chip
 * as stored in the (read-only) DEVICE_ID_REG register.
 *
 * The second entry (devs) is the list of logical device IDs which are
 * present on that particular Super I/O chip. A value of -1 means the
 * device is not present on that chip.
 *
 * Note: Do _not_ list chips with different name but same device ID twice!
 *       The result would be that the init code would be executed twice!
 */
static const struct logical_devices {
	uint8_t superio_id;
	int devs[MAX_LOGICAL_DEVICES];
} logical_device_table[] = {
	// Chip   FDC PP SP1 SP2 RTC KBC AUX XBUS HWM GAME PME MPU RT ACPI SMB
	{FDC37B80X,{0, 3, 4,  5, -1,  7,  8,  -1, -1,  -1, -1, -1, -1, -1, -1}},
	{FDC37B78X,{0, 3, 4,  5,  6,  7,  8,  -1, -1,  -1, -1, -1, -1, 10, -1}},
	{FDC37B72X,{0, 3, 4,  5, -1,  7,  8,  -1, -1,  -1, -1, -1, -1, 10, -1}},
	{FDC37M81X,{0, 3, 4,  5, -1,  7,  8,  -1, -1,  -1, -1, -1, -1, -1, -1}},
	{FDC37M60X,{0, 3, 4,  5, -1,  7,  8,  -1, -1,  -1, -1, -1, -1, -1, -1}},
	{LPC47B27X,{0, 3, 4,  5, -1,  7, -1,  -1, -1,   9, -1, 11, 10, -1, -1}},
	{LPC47M10X,{0, 3, 4,  5, -1,  7, -1,  -1, -1,   9, 10, 11, -1, -1, -1}},
	{LPC47M15X,{0, 3, 4,  5, -1,  7, -1,  -1, -1,   9, 10, 11, -1, -1, -1}},
	{LPC47S45X,{0, 3, 4,  5,  6,  7, -1,   8, -1,  -1, -1, -1, 10, -1, 11}},
	{LPC47B397,{0, 3, 4,  5, -1,  7, -1,  -1,  8,  -1, -1, -1, 10, -1, -1}},
	{A8000,    {0, 3, 4,  5, -1,  7, -1,  -1, -1,  -1, -1, -1, 10, -1, -1}},
	{DME1737,  {0, 3, 4,  5, -1,  7, -1,  -1, -1,  -1, -1, -1, 10, -1, -1}},
	{SCH3112,  {0, 3, 4,  5, -1,  7, -1,  -1, -1,  -1, -1, -1, 10, -1, -1}},
	{SCH5307,  {0, 3, 4,  5, -1,  7, -1,  -1,  8,  -1, -1, -1, 10, -1, -1}},
};

/**
 * Enter the configuration state by writing 0x55 to the config port.
 *
 * The Super I/O configuration registers can only be modified when the chip
 * is in the configuration state. Thus, to program the registers you have
 * to a) enter config mode, b) program the registers, c) exit config mode.
 *
 * @param dev The device to use.
 */
static inline void smsc_pnp_enter_conf_state(device_t dev)
{
	outb(0x55, dev->path.u.pnp.port);
}

/**
 * Exit the configuration state by writing 0xaa to the config port.
 *
 * This puts the chip into the 'run' state again.
 *
 * @param dev The device to use.
 */
static inline void smsc_pnp_exit_conf_state(device_t dev)
{
	outb(0xaa, dev->path.u.pnp.port);
}

/** Wrapper for pnp_set_resources(). */
static void smsc_pnp_set_resources(device_t dev)
{
	smsc_pnp_enter_conf_state(dev);
	pnp_set_resources(dev);
	smsc_pnp_exit_conf_state(dev);
}

/** Wrapper for pnp_enable_resources(). */
static void smsc_pnp_enable_resources(device_t dev)
{
	smsc_pnp_enter_conf_state(dev);
	pnp_enable_resources(dev);
	smsc_pnp_exit_conf_state(dev);
}

/**
 * If so configured, enable the specified device, otherwise
 * explicitly disable it.
 *
 * @param dev The device to use.
 */
static void smsc_pnp_enable(device_t dev)
{
	smsc_pnp_enter_conf_state(dev);
	pnp_set_logical_device(dev);
	(dev->enabled) ? pnp_set_enable(dev, 1) : pnp_set_enable(dev, 0);
	smsc_pnp_exit_conf_state(dev);
}

/**
 * Initialize those logical devices which need a special init.
 *
 * @param dev The device to use.
 */
static void smsc_init(device_t dev)
{
	struct superio_smsc_smscsuperio_config *conf = dev->chip_info;
	struct resource *res0, *res1;
	int i, ld;

	/* Do not initialize disabled devices. */
	if (!dev->enabled)
		return;

	/* Find the correct Super I/O. */
	for (i = 0; i < ARRAY_SIZE(logical_device_table); i++)
		if (logical_device_table[i].superio_id == superio_id)
			break;

	/* If no Super I/O was found, return. */
	if (i == ARRAY_SIZE(logical_device_table))
		return;

	/* A Super I/O was found, so initialize the respective device. */
	ld = dev->path.u.pnp.device;
	if (ld == logical_device_table[i].devs[LD_SP1]) {
		res0 = find_resource(dev, PNP_IDX_IO0);
		init_uart8250(res0->base, &conf->com1);
	} else if (ld == logical_device_table[i].devs[LD_SP2]) {
		res0 = find_resource(dev, PNP_IDX_IO0);
		init_uart8250(res0->base, &conf->com2);
	} else if (ld == logical_device_table[i].devs[LD_KBC]) {
		res0 = find_resource(dev, PNP_IDX_IO0);
		res1 = find_resource(dev, PNP_IDX_IO1);
		init_pc_keyboard(res0->base, res1->base, &conf->keyboard);
	}
}

/** Standard device operations. */
static struct device_operations ops = {
	.read_resources		= pnp_read_resources,
	.set_resources		= smsc_pnp_set_resources,
	.enable_resources	= smsc_pnp_enable_resources,
	.enable			= smsc_pnp_enable,
	.init			= smsc_init,
};

/**
 * TODO.
 *
 * This table should contain all possible entries for any of the supported
 * Super I/O chips, even if some of them don't have the respective logical
 * devices. That will be handled correctly by our code.
 *
 * The LD_FOO entries are device markers which tell you the type of the logical
 * device (e.g. whether it's a floppy disk controller or a serial port etc.).
 *
 * Before using pnp_dev_info[] in pnp_enable_devices() these markers have
 * to be replaced with the real logical device IDs of the respective
 * Super I/O chip. This is done in enable_dev().
 *
 * TODO: FDC, PP, SP1, SP2, and KBC should work, the rest probably not (yet).
 */
static struct pnp_info pnp_dev_info[] = {
	{ &ops, LD_FDC, PNP_IO0 | PNP_IRQ0 | PNP_DRQ0, { 0x07f8, 0 }, },
	{ &ops, LD_PP,  PNP_IO0 | PNP_IRQ0 | PNP_DRQ0, { 0x07f8, 0 }, },
	{ &ops, LD_SP1, PNP_IO0 | PNP_IRQ0, { 0x7f8, 0 }, },
	{ &ops, LD_SP2, PNP_IO0 | PNP_IRQ0, { 0x7f8, 0 }, },
	{ &ops, LD_RTC, },
	{ &ops, LD_KBC, PNP_IO0 | PNP_IO1 | PNP_IRQ0 | PNP_IRQ1, { 0x7ff, 0 },
								 { 0x7ff, 4 },},
	{ &ops, LD_AUX, },
	{ &ops, LD_XBUS, },
	{ &ops, LD_HWM, PNP_IO0, { 0x7f0, 0 }, },
	{ &ops, LD_GAME, },
	{ &ops, LD_PME, },
	{ &ops, LD_MPU401, },
	{ &ops, LD_RT,  PNP_IO0, { 0x780, 0 }, },
	{ &ops, LD_ACPI, },
	{ &ops, LD_SMB, },
};

/**
 * Enable the logical devices of the Super I/O chip.
 *
 * TODO: Think about how to handle the case when a mainboard has multiple
 *       Super I/O chips soldered on.
 * TODO: Can this code be simplified a bit?
 *
 * @param dev The device to use.
 */
static void enable_dev(device_t dev)
{
	int i, j, fn;
	int tmp[MAX_LOGICAL_DEVICES];

	if (first_time) {
		/* Read the device ID and revision of the Super I/O chip. */
		smsc_pnp_enter_conf_state(dev);
		superio_id = pnp_read_config(dev, DEVICE_ID_REG);
		superio_rev = pnp_read_config(dev, DEVICE_REV_REG);
		smsc_pnp_exit_conf_state(dev);

		/* TODO: Error handling? */

		printk_info("Found SMSC Super I/O (ID=0x%02x, rev=0x%02x)\n",
			    superio_id, superio_rev);
		first_time = 0;
	}

	/* Find the correct Super I/O. */
	for (i = 0; i < ARRAY_SIZE(logical_device_table); i++)
		if (logical_device_table[i].superio_id == superio_id)
			break;

	/* If no Super I/O was found, return. */
	if (i == ARRAY_SIZE(logical_device_table))
		return;

	/* Temporarily save the LD_FOO values. */
	for (j = 0; j < ARRAY_SIZE(pnp_dev_info); j++)
		tmp[j] = pnp_dev_info[j].function;

	/* Replace the LD_FOO markers in pnp_dev_info[] with
	 * the real logical device IDs of this Super I/O chip.
	 */
	for (j = 0; j < ARRAY_SIZE(pnp_dev_info); j++) {
		fn = pnp_dev_info[j].function;
		pnp_dev_info[j].function = logical_device_table[i].devs[fn];
	}

	/* Enable the specified devices (if present on the chip). */
	pnp_enable_devices(dev, &pnp_ops, ARRAY_SIZE(pnp_dev_info),
			   &pnp_dev_info);

	/* Restore LD_FOO values. */
	for (j = 0; j < ARRAY_SIZE(pnp_dev_info); j++)
		pnp_dev_info[j].function = tmp[j];
}

struct chip_operations superio_smsc_smscsuperio_ops = {
	CHIP_NAME("Various SMSC Super I/Os")
	.enable_dev = enable_dev
};