1146011Snyan/*-
243561Skato * Copyright (c) 1998 Michael Smith (msmith@freebsd.org)
343561Skato *
443561Skato * Redistribution and use in source and binary forms, with or without
543561Skato * modification, are permitted provided that the following conditions
643561Skato * are met:
743561Skato * 1. Redistributions of source code must retain the above copyright
843561Skato *    notice, this list of conditions and the following disclaimer.
943561Skato * 2. Redistributions in binary form must reproduce the above copyright
1043561Skato *    notice, this list of conditions and the following disclaimer in the
1143561Skato *    documentation and/or other materials provided with the distribution.
1243561Skato *
1343561Skato * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1443561Skato * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1543561Skato * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1643561Skato * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1743561Skato * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1843561Skato * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
1943561Skato * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2043561Skato * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2143561Skato * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2243561Skato * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2343561Skato * SUCH DAMAGE.
2443561Skato */
2543561Skato
26119880Sobrien#include <sys/cdefs.h>
27119880Sobrien__FBSDID("$FreeBSD$");
28119880Sobrien
2943561Skato#include <stand.h>
3043561Skato#include <bootstrap.h>
3143561Skato#include <machine/cpufunc.h>
32120118Sbde#include <dev/ic/ns16550.h>
33229463Snyan#include <dev/pci/pcireg.h>
3443561Skato#include "libi386.h"
3543561Skato
3643561Skato#define COMC_FMT	0x3		/* 8N1 */
3743561Skato#define COMC_TXWAIT	0x40000		/* transmit timeout */
3843561Skato#define COMC_BPS(x)	(115200 / (x))	/* speed to DLAB divisor */
39150073Snyan#define COMC_DIV2BPS(x)	(115200 / (x))	/* DLAB divisor to speed */
4043561Skato
4143561Skato#ifndef	COMPORT
4243561Skato#define COMPORT		0x238
4343561Skato#endif
4443561Skato#ifndef	COMSPEED
4543561Skato#define COMSPEED	9600
4643561Skato#endif
4743561Skato
4843561Skatostatic void	comc_probe(struct console *cp);
4943561Skatostatic int	comc_init(int arg);
5043561Skatostatic void	comc_putchar(int c);
5143561Skatostatic int	comc_getchar(void);
52150073Snyanstatic int	comc_getspeed(void);
5343561Skatostatic int	comc_ischar(void);
54229463Snyanstatic int	comc_parseint(const char *string);
55229463Snyanstatic uint32_t comc_parse_pcidev(const char *string);
56229463Snyanstatic int	comc_pcidev_set(struct env_var *ev, int flags,
57229463Snyan		    const void *value);
58229463Snyanstatic int	comc_pcidev_handle(uint32_t locator);
59229463Snyanstatic int	comc_port_set(struct env_var *ev, int flags,
60229463Snyan		    const void *value);
61229463Snyanstatic void	comc_setup(int speed, int port);
62150073Snyanstatic int	comc_speed_set(struct env_var *ev, int flags,
63150073Snyan		    const void *value);
6443561Skato
65150073Snyanstatic int	comc_curspeed;
66229463Snyanstatic int	comc_port = COMPORT;
67229463Snyanstatic uint32_t	comc_locator;
6843561Skato
6943561Skatostruct console comconsole = {
7043561Skato    "comconsole",
7143561Skato    "serial port",
7243561Skato    0,
7343561Skato    comc_probe,
7443561Skato    comc_init,
7543561Skato    comc_putchar,
7643561Skato    comc_getchar,
7743561Skato    comc_ischar
7843561Skato};
7943561Skato
8043561Skatostatic void
8143561Skatocomc_probe(struct console *cp)
8243561Skato{
83229463Snyan    char intbuf[16];
84229463Snyan    char *cons, *env;
85229463Snyan    int speed, port;
86229463Snyan    uint32_t locator;
87150073Snyan
88150073Snyan    if (comc_curspeed == 0) {
89150073Snyan	comc_curspeed = COMSPEED;
90150073Snyan	/*
91150073Snyan	 * Assume that the speed was set by an earlier boot loader if
92150073Snyan	 * comconsole is already the preferred console.
93150073Snyan	 */
94150073Snyan	cons = getenv("console");
95150073Snyan	if ((cons != NULL && strcmp(cons, comconsole.c_name) == 0) ||
96150073Snyan	    getenv("boot_multicons") != NULL) {
97150073Snyan		comc_curspeed = comc_getspeed();
98150073Snyan	}
99229463Snyan
100229463Snyan	env = getenv("comconsole_speed");
101229463Snyan	if (env != NULL) {
102229463Snyan	    speed = comc_parseint(env);
103150073Snyan	    if (speed > 0)
104150073Snyan		comc_curspeed = speed;
105150073Snyan	}
106150073Snyan
107229463Snyan	sprintf(intbuf, "%d", comc_curspeed);
108150073Snyan	unsetenv("comconsole_speed");
109229463Snyan	env_setenv("comconsole_speed", EV_VOLATILE, intbuf, comc_speed_set,
110150073Snyan	    env_nounset);
111229463Snyan
112229463Snyan	env = getenv("comconsole_port");
113229463Snyan	if (env != NULL) {
114229463Snyan	    port = comc_parseint(env);
115229463Snyan	    if (port > 0)
116229463Snyan		comc_port = port;
117229463Snyan	}
118229463Snyan
119229463Snyan	sprintf(intbuf, "%d", comc_port);
120229463Snyan	unsetenv("comconsole_port");
121229463Snyan	env_setenv("comconsole_port", EV_VOLATILE, intbuf, comc_port_set,
122229463Snyan	    env_nounset);
123229463Snyan
124229463Snyan	env = getenv("comconsole_pcidev");
125229463Snyan	if (env != NULL) {
126229463Snyan	    locator = comc_parse_pcidev(env);
127229463Snyan	    if (locator != 0)
128229463Snyan		    comc_pcidev_handle(locator);
129229463Snyan	}
130229463Snyan
131229463Snyan	unsetenv("comconsole_pcidev");
132229463Snyan	env_setenv("comconsole_pcidev", EV_VOLATILE, env, comc_pcidev_set,
133229463Snyan	    env_nounset);
134150073Snyan    }
135242864Snyan    comc_setup(comc_curspeed, comc_port);
13643561Skato}
13743561Skato
13843561Skatostatic int
13943561Skatocomc_init(int arg)
14043561Skato{
14143561Skato
142229463Snyan    comc_setup(comc_curspeed, comc_port);
14343561Skato
144242864Snyan    if ((comconsole.c_flags & (C_PRESENTIN | C_PRESENTOUT)) ==
145242864Snyan	(C_PRESENTIN | C_PRESENTOUT))
146242864Snyan	return (CMD_OK);
147242864Snyan    return (CMD_ERROR);
14843561Skato}
14943561Skato
15043561Skatostatic void
15143561Skatocomc_putchar(int c)
15243561Skato{
15343561Skato    int wait;
15443561Skato
15543561Skato    for (wait = COMC_TXWAIT; wait > 0; wait--)
156229463Snyan        if (inb(comc_port + com_lsr) & LSR_TXRDY) {
157229463Snyan	    outb(comc_port + com_data, (u_char)c);
15843561Skato	    break;
15943561Skato	}
16043561Skato}
16143561Skato
16243561Skatostatic int
16343561Skatocomc_getchar(void)
16443561Skato{
165242864Snyan    return (comc_ischar() ? inb(comc_port + com_data) : -1);
16643561Skato}
16743561Skato
16843561Skatostatic int
16943561Skatocomc_ischar(void)
17043561Skato{
171242864Snyan    return (inb(comc_port + com_lsr) & LSR_RXRDY);
17243561Skato}
173150073Snyan
174150073Snyanstatic int
175150073Snyancomc_speed_set(struct env_var *ev, int flags, const void *value)
176150073Snyan{
177150073Snyan    int speed;
178150073Snyan
179229463Snyan    if (value == NULL || (speed = comc_parseint(value)) <= 0) {
180150073Snyan	printf("Invalid speed\n");
181150073Snyan	return (CMD_ERROR);
182150073Snyan    }
183150073Snyan
184261573Smav    if (comc_curspeed != speed)
185229463Snyan	comc_setup(speed, comc_port);
186150073Snyan
187150073Snyan    env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
188150073Snyan
189150073Snyan    return (CMD_OK);
190150073Snyan}
191150073Snyan
192229463Snyanstatic int
193229463Snyancomc_port_set(struct env_var *ev, int flags, const void *value)
194229463Snyan{
195229463Snyan    int port;
196229463Snyan
197229463Snyan    if (value == NULL || (port = comc_parseint(value)) <= 0) {
198229463Snyan	printf("Invalid port\n");
199229463Snyan	return (CMD_ERROR);
200229463Snyan    }
201229463Snyan
202261573Smav    if (comc_port != port)
203229463Snyan	comc_setup(comc_curspeed, port);
204229463Snyan
205229463Snyan    env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
206229463Snyan
207229463Snyan    return (CMD_OK);
208229463Snyan}
209229463Snyan
210229463Snyan/*
211229463Snyan * Input: bus:dev:func[:bar]. If bar is not specified, it is 0x10.
212229463Snyan * Output: bar[24:16] bus[15:8] dev[7:3] func[2:0]
213229463Snyan */
214229463Snyanstatic uint32_t
215229463Snyancomc_parse_pcidev(const char *string)
216229463Snyan{
217229463Snyan	char *p, *p1;
218229463Snyan	uint8_t bus, dev, func, bar;
219229463Snyan	uint32_t locator;
220229463Snyan	int pres;
221229463Snyan
222229463Snyan	pres = strtol(string, &p, 0);
223229463Snyan	if (p == string || *p != ':' || pres < 0 )
224229463Snyan		return (0);
225229463Snyan	bus = pres;
226229463Snyan	p1 = ++p;
227229463Snyan
228229463Snyan	pres = strtol(p1, &p, 0);
229229463Snyan	if (p == string || *p != ':' || pres < 0 )
230229463Snyan		return (0);
231229463Snyan	dev = pres;
232229463Snyan	p1 = ++p;
233229463Snyan
234229463Snyan	pres = strtol(p1, &p, 0);
235229463Snyan	if (p == string || (*p != ':' && *p != '\0') || pres < 0 )
236229463Snyan		return (0);
237229463Snyan	func = pres;
238229463Snyan
239229463Snyan	if (*p == ':') {
240229463Snyan		p1 = ++p;
241229463Snyan		pres = strtol(p1, &p, 0);
242229463Snyan		if (p == string || *p != '\0' || pres <= 0 )
243229463Snyan			return (0);
244229463Snyan		bar = pres;
245229463Snyan	} else
246229463Snyan		bar = 0x10;
247229463Snyan
248229463Snyan	locator = (bar << 16) | biospci_locator(bus, dev, func);
249229463Snyan	return (locator);
250229463Snyan}
251229463Snyan
252229463Snyanstatic int
253229463Snyancomc_pcidev_handle(uint32_t locator)
254229463Snyan{
255229463Snyan	char intbuf[64];
256229463Snyan	uint32_t port;
257229463Snyan
258229463Snyan	if (biospci_read_config(locator & 0xffff,
259229463Snyan				(locator & 0xff0000) >> 16, 2, &port) == -1) {
260229463Snyan		printf("Cannot read bar at 0x%x\n", locator);
261229463Snyan		return (CMD_ERROR);
262229463Snyan	}
263229463Snyan	if (!PCI_BAR_IO(port)) {
264229463Snyan		printf("Memory bar at 0x%x\n", locator);
265229463Snyan		return (CMD_ERROR);
266229463Snyan	}
267229463Snyan        port &= PCIM_BAR_IO_BASE;
268229463Snyan
269229463Snyan	sprintf(intbuf, "%d", port);
270229463Snyan	unsetenv("comconsole_port");
271229463Snyan	env_setenv("comconsole_port", EV_VOLATILE, intbuf,
272229463Snyan		   comc_port_set, env_nounset);
273229463Snyan
274229463Snyan	comc_setup(comc_curspeed, port);
275229463Snyan	comc_locator = locator;
276229463Snyan
277229463Snyan	return (CMD_OK);
278229463Snyan}
279229463Snyan
280229463Snyanstatic int
281229463Snyancomc_pcidev_set(struct env_var *ev, int flags, const void *value)
282229463Snyan{
283229463Snyan	uint32_t locator;
284229463Snyan	int error;
285229463Snyan
286229463Snyan	if (value == NULL || (locator = comc_parse_pcidev(value)) <= 0) {
287229463Snyan		printf("Invalid pcidev\n");
288229463Snyan		return (CMD_ERROR);
289229463Snyan	}
290242864Snyan	if ((comconsole.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) != 0 &&
291242864Snyan	    comc_locator != locator) {
292229463Snyan		error = comc_pcidev_handle(locator);
293229463Snyan		if (error != CMD_OK)
294229463Snyan			return (error);
295229463Snyan	}
296229463Snyan	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
297229463Snyan	return (CMD_OK);
298229463Snyan}
299229463Snyan
300229463Snyanstatic void
301229463Snyancomc_setup(int speed, int port)
302229463Snyan{
303242864Snyan    static int TRY_COUNT = 1000000;
304251223Snyan    char intbuf[64];
305242864Snyan    int tries;
306229463Snyan
307251223Snyan    unsetenv("hw.uart.console");
308150073Snyan    comc_curspeed = speed;
309229463Snyan    comc_port = port;
310261573Smav    if ((comconsole.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0)
311261573Smav	return;
312150073Snyan
313229463Snyan    outb(comc_port + com_cfcr, CFCR_DLAB | COMC_FMT);
314229463Snyan    outb(comc_port + com_dlbl, COMC_BPS(speed) & 0xff);
315229463Snyan    outb(comc_port + com_dlbh, COMC_BPS(speed) >> 8);
316229463Snyan    outb(comc_port + com_cfcr, COMC_FMT);
317229463Snyan    outb(comc_port + com_mcr, MCR_RTS | MCR_DTR);
318150073Snyan
319242864Snyan    tries = 0;
320150073Snyan    do
321229463Snyan        inb(comc_port + com_data);
322242864Snyan    while (inb(comc_port + com_lsr) & LSR_RXRDY && ++tries < TRY_COUNT);
323242864Snyan
324251223Snyan    if (tries < TRY_COUNT) {
325242864Snyan	comconsole.c_flags |= (C_PRESENTIN | C_PRESENTOUT);
326251223Snyan	sprintf(intbuf, "io:%d,br:%d", comc_port, comc_curspeed);
327251223Snyan	env_setenv("hw.uart.console", EV_VOLATILE, intbuf, NULL, NULL);
328251223Snyan    } else
329242864Snyan	comconsole.c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
330150073Snyan}
331150073Snyan
332150073Snyanstatic int
333229463Snyancomc_parseint(const char *speedstr)
334150073Snyan{
335150073Snyan    char *p;
336150073Snyan    int speed;
337150073Snyan
338150073Snyan    speed = strtol(speedstr, &p, 0);
339150073Snyan    if (p == speedstr || *p != '\0' || speed <= 0)
340150073Snyan	return (-1);
341150073Snyan
342150073Snyan    return (speed);
343150073Snyan}
344150073Snyan
345150073Snyanstatic int
346150073Snyancomc_getspeed(void)
347150073Snyan{
348150073Snyan	u_int	divisor;
349150073Snyan	u_char	dlbh;
350150073Snyan	u_char	dlbl;
351150073Snyan	u_char	cfcr;
352150073Snyan
353229463Snyan	cfcr = inb(comc_port + com_cfcr);
354229463Snyan	outb(comc_port + com_cfcr, CFCR_DLAB | cfcr);
355150073Snyan
356229463Snyan	dlbl = inb(comc_port + com_dlbl);
357229463Snyan	dlbh = inb(comc_port + com_dlbh);
358150073Snyan
359229463Snyan	outb(comc_port + com_cfcr, cfcr);
360150073Snyan
361150073Snyan	divisor = dlbh << 8 | dlbl;
362150073Snyan
363150073Snyan	/* XXX there should be more sanity checking. */
364150073Snyan	if (divisor == 0)
365150073Snyan		return (COMSPEED);
366150073Snyan	return (COMC_DIV2BPS(divisor));
367150073Snyan}
368