view target-utils/libcommon/cmd_baud_switch.c @ 422:833e8fa93330

fc-loadtool -r (reattach) option implemented
author Michael Spacefalcon <msokolov@ivan.Harhan.ORG>
date Thu, 19 Jun 2014 00:57:02 +0000
parents 437f9365249c
children
line wrap: on
line source

/*
 * Baud rate switching command
 */

#include <stdlib.h>
#include "types.h"
#include "ns16550.h"

extern struct ns16550_regs *uart_base;
extern int serial_in_poll();

static const struct tab {
	int baud;
	int divisor;
} rate_table[] = {
	/*
	 * First support the rates and divisors implemented by the
	 * Calypso boot ROM.  Dividing 13 MHz by 7 gives an approximation
	 * of 115200 (x16); the divisors used by the boot ROM code for
	 * the slower baud rates are all 7x the usual PC value.
	 */
	{115200, 7},
	{57600, 7 * 2},
	{38400, 7 * 3},
	{28800, 7 * 4},
	{19200, 7 * 6},
	/*
	 * Going faster than ~115200 baud means using a divisor
	 * less than 7, resulting in a non-standard baud rate.
	 * The /1, /2 and /4 seem like reasonable choices.
	 */
	{812500, 1},
	{406250, 2},
	{203125, 4},
	/* that's all we really need to support */
	{0, 0}
};

/*
 * The following helper function actually switches the UART
 * baud rate divisor.  Call serial_flush() first.  It returns the
 * old divisor value.
 *
 * Note the u8 type for both the new and old divisor values.
 * All supported divisors are well below 255, so we don't bother
 * with the upper byte.
 */
static u8
actually_switch_baud(newdiv)
	u8 newdiv;
{
	volatile struct ns16550_regs *regs;
	u8 save_lcr, save_old_baud;

	regs = uart_base;
	save_lcr = regs->lcr;
	regs->lcr = save_lcr | NS16550_LCR_DLAB;
	save_old_baud = regs->datareg;
	regs->datareg = newdiv;
	regs->lcr = save_lcr;
	return(save_old_baud);
}

void
cmd_baud_switch(argbulk)
	char *argbulk;
{
	char *argv[2];
	int baudarg;
	struct tab *tp;
	u8 save_old_baud;
	int c;

	if (parse_args(argbulk, 1, 1, argv, 0) < 0)
		return;
	baudarg = atoi(argv[0]);
	for (tp = rate_table; tp->baud; tp++)
		if (tp->baud == baudarg)
			break;
	if (!tp->baud) {
		printf("ERROR: invalid/unimplemented baud rate argument\n");
		return;
	}

	/* do it */
	serial_flush();
	save_old_baud = actually_switch_baud(tp->divisor);

	/*
	 * After getting the echo of this command at the old baud rate
	 * (see the serial flush call just before switching the divisor),
	 * the line will go silent from the user's perspective.
	 * The user should wait just a little bit, then send us a 0x55 ('U')
	 * at the new baud rate - we should be in the below loop waiting
	 * for this character by then.  Receiving that character
	 * correctly (0x55 was chosen for the bit pattern - unlikely to
	 * be received if the sender is sending at a wrong baud rate)
	 * will cause us to conclude this command and return a new '='
	 * prompt at the new baud rate.
	 *
	 * If we get something else, we assume that someone messed up,
	 * switch back to the old baud rate, scribble an error message
	 * and return.
	 */
	do
		c = serial_in_poll();
	while (c < 0);
	if (c != 0x55) {
	  actually_switch_baud(save_old_baud);
	  printf("ERROR: no \'U\' received, switched back to old baud rate\n");
	}
}