1119482Sobrien/*-
238465Smsmith * Copyright (c) 1998 Michael Smith (msmith@freebsd.org)
338465Smsmith *
438465Smsmith * Redistribution and use in source and binary forms, with or without
538465Smsmith * modification, are permitted provided that the following conditions
638465Smsmith * are met:
738465Smsmith * 1. Redistributions of source code must retain the above copyright
838465Smsmith *    notice, this list of conditions and the following disclaimer.
938465Smsmith * 2. Redistributions in binary form must reproduce the above copyright
1038465Smsmith *    notice, this list of conditions and the following disclaimer in the
1138465Smsmith *    documentation and/or other materials provided with the distribution.
1238465Smsmith *
1338465Smsmith * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1438465Smsmith * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1538465Smsmith * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1638465Smsmith * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1738465Smsmith * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1838465Smsmith * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
1938465Smsmith * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2038465Smsmith * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2138465Smsmith * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2238465Smsmith * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2338465Smsmith * SUCH DAMAGE.
2438465Smsmith */
2538465Smsmith
26119482Sobrien#include <sys/cdefs.h>
27119482Sobrien__FBSDID("$FreeBSD$");
28119482Sobrien
2938465Smsmith#include <stand.h>
3039441Smsmith#include <bootstrap.h>
3141285Srnordier#include <machine/cpufunc.h>
32120118Sbde#include <dev/ic/ns16550.h>
33230257Skib#include <dev/pci/pcireg.h>
3439441Smsmith#include "libi386.h"
3538465Smsmith
3641285Srnordier#define COMC_FMT	0x3		/* 8N1 */
3741285Srnordier#define COMC_TXWAIT	0x40000		/* transmit timeout */
3841285Srnordier#define COMC_BPS(x)	(115200 / (x))	/* speed to DLAB divisor */
39149213Siedowse#define COMC_DIV2BPS(x)	(115200 / (x))	/* DLAB divisor to speed */
4041285Srnordier
4142480Srnordier#ifndef	COMPORT
4241285Srnordier#define COMPORT		0x3f8
4342480Srnordier#endif
4442480Srnordier#ifndef	COMSPEED
4541285Srnordier#define COMSPEED	9600
4642480Srnordier#endif
4741285Srnordier
4838465Smsmithstatic void	comc_probe(struct console *cp);
4938465Smsmithstatic int	comc_init(int arg);
5039441Smsmithstatic void	comc_putchar(int c);
5139441Smsmithstatic int	comc_getchar(void);
52149213Siedowsestatic int	comc_getspeed(void);
5339441Smsmithstatic int	comc_ischar(void);
54230257Skibstatic int	comc_parseint(const char *string);
55230257Skibstatic uint32_t comc_parse_pcidev(const char *string);
56230257Skibstatic int	comc_pcidev_set(struct env_var *ev, int flags,
57230257Skib		    const void *value);
58230257Skibstatic int	comc_pcidev_handle(uint32_t locator);
59230257Skibstatic int	comc_port_set(struct env_var *ev, int flags,
60230257Skib		    const void *value);
61230257Skibstatic void	comc_setup(int speed, int port);
62149213Siedowsestatic int	comc_speed_set(struct env_var *ev, int flags,
63149213Siedowse		    const void *value);
6438465Smsmith
65149213Siedowsestatic int	comc_curspeed;
66230257Skibstatic int	comc_port = COMPORT;
67230257Skibstatic uint32_t	comc_locator;
6840211Speter
6938465Smsmithstruct console comconsole = {
7038465Smsmith    "comconsole",
7141285Srnordier    "serial port",
7238465Smsmith    0,
7338465Smsmith    comc_probe,
7438465Smsmith    comc_init,
7539441Smsmith    comc_putchar,
7639441Smsmith    comc_getchar,
7739441Smsmith    comc_ischar
7838465Smsmith};
7938465Smsmith
8038465Smsmithstatic void
8138465Smsmithcomc_probe(struct console *cp)
8238465Smsmith{
83230257Skib    char intbuf[16];
84230257Skib    char *cons, *env;
85230257Skib    int speed, port;
86230257Skib    uint32_t locator;
87149213Siedowse
88149213Siedowse    if (comc_curspeed == 0) {
89149213Siedowse	comc_curspeed = COMSPEED;
90149213Siedowse	/*
91149213Siedowse	 * Assume that the speed was set by an earlier boot loader if
92149213Siedowse	 * comconsole is already the preferred console.
93149213Siedowse	 */
94149213Siedowse	cons = getenv("console");
95149213Siedowse	if ((cons != NULL && strcmp(cons, comconsole.c_name) == 0) ||
96149213Siedowse	    getenv("boot_multicons") != NULL) {
97149213Siedowse		comc_curspeed = comc_getspeed();
98149213Siedowse	}
99230257Skib
100230257Skib	env = getenv("comconsole_speed");
101230257Skib	if (env != NULL) {
102230257Skib	    speed = comc_parseint(env);
103149213Siedowse	    if (speed > 0)
104149213Siedowse		comc_curspeed = speed;
105149213Siedowse	}
106149213Siedowse
107230257Skib	sprintf(intbuf, "%d", comc_curspeed);
108149213Siedowse	unsetenv("comconsole_speed");
109230257Skib	env_setenv("comconsole_speed", EV_VOLATILE, intbuf, comc_speed_set,
110149213Siedowse	    env_nounset);
111230257Skib
112230257Skib	env = getenv("comconsole_port");
113230257Skib	if (env != NULL) {
114230257Skib	    port = comc_parseint(env);
115230257Skib	    if (port > 0)
116230257Skib		comc_port = port;
117230257Skib	}
118230257Skib
119230257Skib	sprintf(intbuf, "%d", comc_port);
120230257Skib	unsetenv("comconsole_port");
121230257Skib	env_setenv("comconsole_port", EV_VOLATILE, intbuf, comc_port_set,
122230257Skib	    env_nounset);
123230257Skib
124230257Skib	env = getenv("comconsole_pcidev");
125230257Skib	if (env != NULL) {
126230257Skib	    locator = comc_parse_pcidev(env);
127230257Skib	    if (locator != 0)
128230257Skib		    comc_pcidev_handle(locator);
129230257Skib	}
130230257Skib
131230257Skib	unsetenv("comconsole_pcidev");
132230257Skib	env_setenv("comconsole_pcidev", EV_VOLATILE, env, comc_pcidev_set,
133230257Skib	    env_nounset);
134149213Siedowse    }
135242561Savg    comc_setup(comc_curspeed, comc_port);
13638465Smsmith}
13738465Smsmith
13838465Smsmithstatic int
13938465Smsmithcomc_init(int arg)
14038465Smsmith{
14140211Speter
142230257Skib    comc_setup(comc_curspeed, comc_port);
14340211Speter
144242561Savg    if ((comconsole.c_flags & (C_PRESENTIN | C_PRESENTOUT)) ==
145242561Savg	(C_PRESENTIN | C_PRESENTOUT))
146242561Savg	return (CMD_OK);
147242561Savg    return (CMD_ERROR);
14838465Smsmith}
14938465Smsmith
15039441Smsmithstatic void
15139441Smsmithcomc_putchar(int c)
15239441Smsmith{
15341285Srnordier    int wait;
15441285Srnordier
15541285Srnordier    for (wait = COMC_TXWAIT; wait > 0; wait--)
156230257Skib        if (inb(comc_port + com_lsr) & LSR_TXRDY) {
157230257Skib	    outb(comc_port + com_data, (u_char)c);
15841285Srnordier	    break;
15941285Srnordier	}
16039441Smsmith}
16139441Smsmith
16238465Smsmithstatic int
16339441Smsmithcomc_getchar(void)
16438465Smsmith{
165242561Savg    return (comc_ischar() ? inb(comc_port + com_data) : -1);
16638465Smsmith}
16739441Smsmith
16839441Smsmithstatic int
16939441Smsmithcomc_ischar(void)
17039441Smsmith{
171242561Savg    return (inb(comc_port + com_lsr) & LSR_RXRDY);
17239441Smsmith}
173149213Siedowse
174149213Siedowsestatic int
175149213Siedowsecomc_speed_set(struct env_var *ev, int flags, const void *value)
176149213Siedowse{
177149213Siedowse    int speed;
178149213Siedowse
179230257Skib    if (value == NULL || (speed = comc_parseint(value)) <= 0) {
180149213Siedowse	printf("Invalid speed\n");
181149213Siedowse	return (CMD_ERROR);
182149213Siedowse    }
183149213Siedowse
184261574Smav    if (comc_curspeed != speed)
185230257Skib	comc_setup(speed, comc_port);
186149213Siedowse
187149213Siedowse    env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
188149213Siedowse
189149213Siedowse    return (CMD_OK);
190149213Siedowse}
191149213Siedowse
192230257Skibstatic int
193230257Skibcomc_port_set(struct env_var *ev, int flags, const void *value)
194230257Skib{
195230257Skib    int port;
196230257Skib
197230257Skib    if (value == NULL || (port = comc_parseint(value)) <= 0) {
198230257Skib	printf("Invalid port\n");
199230257Skib	return (CMD_ERROR);
200230257Skib    }
201230257Skib
202261574Smav    if (comc_port != port)
203230257Skib	comc_setup(comc_curspeed, port);
204230257Skib
205230257Skib    env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
206230257Skib
207230257Skib    return (CMD_OK);
208230257Skib}
209230257Skib
210230257Skib/*
211230257Skib * Input: bus:dev:func[:bar]. If bar is not specified, it is 0x10.
212230257Skib * Output: bar[24:16] bus[15:8] dev[7:3] func[2:0]
213230257Skib */
214230257Skibstatic uint32_t
215230257Skibcomc_parse_pcidev(const char *string)
216230257Skib{
217230257Skib	char *p, *p1;
218230257Skib	uint8_t bus, dev, func, bar;
219230257Skib	uint32_t locator;
220230257Skib	int pres;
221230257Skib
222230257Skib	pres = strtol(string, &p, 0);
223230257Skib	if (p == string || *p != ':' || pres < 0 )
224230257Skib		return (0);
225230257Skib	bus = pres;
226230257Skib	p1 = ++p;
227230257Skib
228230257Skib	pres = strtol(p1, &p, 0);
229230257Skib	if (p == string || *p != ':' || pres < 0 )
230230257Skib		return (0);
231230257Skib	dev = pres;
232230257Skib	p1 = ++p;
233230257Skib
234230257Skib	pres = strtol(p1, &p, 0);
235230257Skib	if (p == string || (*p != ':' && *p != '\0') || pres < 0 )
236230257Skib		return (0);
237230257Skib	func = pres;
238230257Skib
239230257Skib	if (*p == ':') {
240230257Skib		p1 = ++p;
241230257Skib		pres = strtol(p1, &p, 0);
242230257Skib		if (p == string || *p != '\0' || pres <= 0 )
243230257Skib			return (0);
244230257Skib		bar = pres;
245230257Skib	} else
246230257Skib		bar = 0x10;
247230257Skib
248230257Skib	locator = (bar << 16) | biospci_locator(bus, dev, func);
249230257Skib	return (locator);
250230257Skib}
251230257Skib
252230257Skibstatic int
253230257Skibcomc_pcidev_handle(uint32_t locator)
254230257Skib{
255230257Skib	char intbuf[64];
256230257Skib	uint32_t port;
257230257Skib
258230257Skib	if (biospci_read_config(locator & 0xffff,
259230257Skib				(locator & 0xff0000) >> 16, 2, &port) == -1) {
260230257Skib		printf("Cannot read bar at 0x%x\n", locator);
261230257Skib		return (CMD_ERROR);
262230257Skib	}
263230257Skib	if (!PCI_BAR_IO(port)) {
264230257Skib		printf("Memory bar at 0x%x\n", locator);
265230257Skib		return (CMD_ERROR);
266230257Skib	}
267230257Skib        port &= PCIM_BAR_IO_BASE;
268230257Skib
269230257Skib	sprintf(intbuf, "%d", port);
270230257Skib	unsetenv("comconsole_port");
271230257Skib	env_setenv("comconsole_port", EV_VOLATILE, intbuf,
272230257Skib		   comc_port_set, env_nounset);
273230257Skib
274230257Skib	comc_setup(comc_curspeed, port);
275230257Skib	comc_locator = locator;
276230257Skib
277230257Skib	return (CMD_OK);
278230257Skib}
279230257Skib
280230257Skibstatic int
281230257Skibcomc_pcidev_set(struct env_var *ev, int flags, const void *value)
282230257Skib{
283230257Skib	uint32_t locator;
284230257Skib	int error;
285230257Skib
286230257Skib	if (value == NULL || (locator = comc_parse_pcidev(value)) <= 0) {
287230257Skib		printf("Invalid pcidev\n");
288230257Skib		return (CMD_ERROR);
289230257Skib	}
290242561Savg	if ((comconsole.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) != 0 &&
291242561Savg	    comc_locator != locator) {
292230257Skib		error = comc_pcidev_handle(locator);
293230257Skib		if (error != CMD_OK)
294230257Skib			return (error);
295230257Skib	}
296230257Skib	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
297230257Skib	return (CMD_OK);
298230257Skib}
299230257Skib
300230257Skibstatic void
301230257Skibcomc_setup(int speed, int port)
302230257Skib{
303242561Savg    static int TRY_COUNT = 1000000;
304260868Smav    char intbuf[64];
305242561Savg    int tries;
306230257Skib
307260868Smav    unsetenv("hw.uart.console");
308149213Siedowse    comc_curspeed = speed;
309230257Skib    comc_port = port;
310261574Smav    if ((comconsole.c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) == 0)
311261574Smav	return;
312149213Siedowse
313230257Skib    outb(comc_port + com_cfcr, CFCR_DLAB | COMC_FMT);
314230257Skib    outb(comc_port + com_dlbl, COMC_BPS(speed) & 0xff);
315230257Skib    outb(comc_port + com_dlbh, COMC_BPS(speed) >> 8);
316230257Skib    outb(comc_port + com_cfcr, COMC_FMT);
317230257Skib    outb(comc_port + com_mcr, MCR_RTS | MCR_DTR);
318149213Siedowse
319242561Savg    tries = 0;
320149213Siedowse    do
321230257Skib        inb(comc_port + com_data);
322242561Savg    while (inb(comc_port + com_lsr) & LSR_RXRDY && ++tries < TRY_COUNT);
323242561Savg
324260868Smav    if (tries < TRY_COUNT) {
325242561Savg	comconsole.c_flags |= (C_PRESENTIN | C_PRESENTOUT);
326260868Smav	sprintf(intbuf, "io:%d,br:%d", comc_port, comc_curspeed);
327260868Smav	env_setenv("hw.uart.console", EV_VOLATILE, intbuf, NULL, NULL);
328260868Smav    } else
329242561Savg	comconsole.c_flags &= ~(C_PRESENTIN | C_PRESENTOUT);
330149213Siedowse}
331149213Siedowse
332149213Siedowsestatic int
333230257Skibcomc_parseint(const char *speedstr)
334149213Siedowse{
335149213Siedowse    char *p;
336149213Siedowse    int speed;
337149213Siedowse
338149213Siedowse    speed = strtol(speedstr, &p, 0);
339149213Siedowse    if (p == speedstr || *p != '\0' || speed <= 0)
340149213Siedowse	return (-1);
341149213Siedowse
342149213Siedowse    return (speed);
343149213Siedowse}
344149213Siedowse
345149213Siedowsestatic int
346149213Siedowsecomc_getspeed(void)
347149213Siedowse{
348149213Siedowse	u_int	divisor;
349149213Siedowse	u_char	dlbh;
350149213Siedowse	u_char	dlbl;
351149213Siedowse	u_char	cfcr;
352149213Siedowse
353230257Skib	cfcr = inb(comc_port + com_cfcr);
354230257Skib	outb(comc_port + com_cfcr, CFCR_DLAB | cfcr);
355149213Siedowse
356230257Skib	dlbl = inb(comc_port + com_dlbl);
357230257Skib	dlbh = inb(comc_port + com_dlbh);
358149213Siedowse
359230257Skib	outb(comc_port + com_cfcr, cfcr);
360149213Siedowse
361149213Siedowse	divisor = dlbh << 8 | dlbl;
362149213Siedowse
363149213Siedowse	/* XXX there should be more sanity checking. */
364149213Siedowse	if (divisor == 0)
365149213Siedowse		return (COMSPEED);
366149213Siedowse	return (COMC_DIV2BPS(divisor));
367149213Siedowse}
368