diff -u -p linux/include/linux/serial_reg.brain.h linux/include/linux/serial_reg.h --- linux/include/linux/serial_reg.brain.h Mon Oct 29 15:55:04 2001 +++ linux/include/linux/serial_reg.h Fri Oct 26 11:35:39 2001 @@ -126,6 +126,12 @@ #define UART_MCR_OUT1 0x04 /* Out1 complement */ #define UART_MCR_RTS 0x02 /* RTS complement */ #define UART_MCR_DTR 0x01 /* DTR complement */ +/* + * Extra MCR bit for 16C950. + * Those bits are only available when the 950 mode is enable (EFR bit 4 = 1) + */ +#define UART_MCR_IRDA 0x40 /* Enable IrDA mode (SIR) */ +#define UART_MCR_PRESCALE 0x80 /* Enable clock prescaler */ /* * These are the definitions for the Modem Status Register @@ -163,7 +169,7 @@ /* The 16950 ICR registers */ #define UART_ACR 0x00 /* Additional Control Register */ #define UART_CPR 0x01 /* Clock Prescalar Register */ -#define UART_TCR 0x02 /* Times Clock Register */ +#define UART_TCR 0x02 /* Times Clock Register (overclock) */ #define UART_CKS 0x03 /* Clock Select Register */ #define UART_TTL 0x04 /* Transmitter Interrupt Trigger Level */ #define UART_RTL 0x05 /* Receiver Interrupt Trigger Level */ diff -u -p linux/include/linux/serialP.brain.h linux/include/linux/serialP.h --- linux/include/linux/serialP.brain.h Mon Oct 29 15:54:50 2001 +++ linux/include/linux/serialP.h Mon Oct 29 17:49:36 2001 @@ -74,7 +74,9 @@ struct async_struct { int IER; /* Interrupt Enable Register */ int MCR; /* Modem control register */ int LCR; /* Line control register */ - int ACR; /* 16950 Additional Control Reg. */ + int ACR; /* 16950 Additional Control Reg. */ + int CPR; /* 16950 Clock prescaler. */ + int WMSR; /* Custom write to MSR if >= 0x100 */ unsigned long event; unsigned long last_active; int line; diff -u -p linux/include/asm-i386/ioctls.brain.h linux/include/asm-i386/ioctls.h --- linux/include/asm-i386/ioctls.brain.h Mon Oct 29 15:54:10 2001 +++ linux/include/asm-i386/ioctls.h Mon Oct 29 16:14:41 2001 @@ -68,6 +68,7 @@ #define TIOCGHAYESESP 0x545E /* Get Hayes ESP configuration */ #define TIOCSHAYESESP 0x545F /* Set Hayes ESP configuration */ #define FIOQSIZE 0x5460 +#define TIOCSERSETWMSR 0x5461 /* Set custom write in Modem Status Register */ /* Used for packet mode */ #define TIOCPKT_DATA 0 diff -u -p linux/drivers/char/serial.brain.c linux/drivers/char/serial.c --- linux/drivers/char/serial.brain.c Mon Oct 29 15:55:33 2001 +++ linux/drivers/char/serial.c Tue Oct 30 13:25:09 2001 @@ -1268,6 +1268,36 @@ static int startup(struct async_struct * serial_outp(info, UART_LCR, 0xBF); serial_outp(info, UART_EFR, UART_EFR_ECB); serial_outp(info, UART_LCR, 0); + + /* If we don't have a value for CPR, try to guess one... + * The 16C950 can use non standard UART clock frequencies, + * the CPR register allow to convert this non standard clock + * to the standard clock, allowing us to generate the proper + * serial bit rate. + * The UART clock is known only to the board designer, so we + * can only attempt to guess the proper CPR value (see below). + * The user can "fix" CPR by setting baud_base with setserial. + * Jean II */ + if(info->CPR == 0) { + /* Spec says : After reset, MCR[7] is set to the + * complement of the value of pin CLKSEL. + * To be compatible with 16C550, either use 1.8432MHz + * clock with CLKSEL connected to VDD, or use + * 7.3728MHz with CLKSEL connected to GND. */ + if(serial_inp(info, UART_MCR) & UART_MCR_PRESCALE) { + /* Assume 7.3728MHz UART clock. + * CPR will contain 0x20 == Divide by 4 */ + info->CPR = serial_icr_read(info, UART_CPR); + state->baud_base = (115200 * info->CPR) / 0x8; + } else { + /* Assume standard 1.8432MHz UART clock */ + info->CPR = 0x08; /* Divide by 1 */ + state->baud_base = 115200; + } +#ifdef DEBUG_16C950_CPR + printk(KERN_DEBUG "16C950 read MCR = 0x%X, CPR = 0x%X ; assuming baud_base = %d, CPR = 0x%X\n", serial_inp(info, UART_MCR), serial_icr_read(info, UART_CPR), state->baud_base, info->CPR); +#endif + } } #ifdef CONFIG_SERIAL_RSA @@ -1284,6 +1314,14 @@ static int startup(struct async_struct * } #endif + /* Custom write to MSR (needed here because of UART soft reset) */ + if(info->WMSR >= 0x100) { +#ifdef DEBUG_16C950_WMSR + printk(KERN_DEBUG "16C950 Writing 0x%02X to MSR\n", info->WMSR & 0xFF); +#endif + serial_out(info, UART_MSR, info->WMSR & 0xFF); + } + /* * Clear the FIFO buffers and disable them * (they will be reenabled in change_speed()) @@ -1370,8 +1408,12 @@ static int startup(struct async_struct * serial_outp(info, UART_LCR, UART_LCR_WLEN8); /* reset DLAB */ info->MCR = 0; + if (state->type == PORT_16C950) + /* For the 16C950, always enable the clock prescaler + * it will be properly set in set_extra_speed_950() */ + info->MCR |= UART_MCR_PRESCALE; if (info->tty->termios->c_cflag & CBAUD) - info->MCR = UART_MCR_DTR | UART_MCR_RTS; + info->MCR |= UART_MCR_DTR | UART_MCR_RTS; #ifdef CONFIG_SERIAL_MANY_PORTS if (info->flags & ASYNC_FOURPORT) { if (state->irq == 0) @@ -1601,6 +1643,103 @@ static int tty_get_baud_rate(struct tty_ #endif /* + * Set the appropriate extra speed control register of the 16C950. + * Basically, we set the CPR prescaler register to match the assumed + * UART clock speed, and we set the TCR overclock register to enable + * to reach higher speed. + * We return the new apparent overclocked baud_base that will be used + * to calculate the proper divisor (quot). + * Jean II + */ +static int set_extra_speed_950(struct async_struct *info, int baud) +{ + /* baud_base need to be at least 25 bits to avoid overflow. */ + long baud_base = info->state->baud_base; + int current_tcr; + + /* Setting CPR (prescaler register) + * + * baud_base indicates by how much the present UART clock is faster + * than the standard UART clock. In other words, if the UART clock + * is 4 time faster than standard (7.3728MHz), you should set + * baud_base to 4*115200 = 460800 (which is the max baud rate + * with CPR=1 and TCR=16 mentionned in table 17 at page 30 of the + * OX16C950 rev B spec sheet). + * It seems that the prescaler must be greater than 1 (CPR >= 0x8). + * + * For now, we are pretty dumb in setting CPR. We just use the proper + * prescaler value when we need to do standard speeds (up to 115200) + * and we use no prescaler to reach higher speeds. + * Remember that on top of that we can be overclocked up to 4 times + * through TCR. + * We could be much more clever, as in theory the CPR allow us + * to generate any baud_base we want, so we can generate any + * serial speed we want. I don't really know how well it works + * in practice... + */ + if(baud > 115200) { + /* No prescaler - allow higher speeds */ + info->CPR = 0x8; + /* No need to fudge baud_rate, it's the proper one */ + } else { + /* Calculate prescaler - allow 550 compatible speeds */ + info->CPR = (baud_base * 0x8) / 115200; + /* Check boundaries */ + if(info->CPR < 8) { + info->CPR = 8; + printk(KERN_DEBUG "16C950 : Baud_base is to low (min 115200)\n"); + } + if(info->CPR > 0xFF) { + info->CPR = 0xFF; + printk(KERN_DEBUG "16C950 : Baud_base is to high (max 3672000)\n"); + } + /* Our apparent baud_base is now 550 compatible */ + baud_base = 115200; + } + serial_icr_write(info, UART_CPR, info->CPR); + + /* Setting TCR (overclock register) + * + * We try to minimise the use of overclocking to only when + * it's really necessary : only when we can't reach + * the speed through the prescaler. + * + * The 4 lowest bit of TCR represent the value of the clock + * sampling. 0x0 in fact mean 0x10 = 16, which is the default + * (550 compatible), and only values in the range 4 to 16 are + * supported (0x4 -> 0xF + 0x0). + * Moreover, it seems that only 0x4 and 0x0 are safe with older + * revisions of the 16C950. We don't really need to intermediate + * steppings because we could use the divisor & CPR to do it. + */ + if (baud <= baud_base) + current_tcr = 0x0; /* Sampling clock = 16 = standard */ + else if ((baud <= 2*baud_base) && + (info->state->revision != 0x5201) && + (info->state->revision != 0x5400)) { + current_tcr = 0x8; /* Sampling clock = 8 = 2x overclock */ + baud_base = baud_base * 2; + } else if (baud <= 4*baud_base) { + current_tcr = 0x4; /* Sampling clock = 4 = 4x overclock */ + baud_base = baud_base * 4; + } else { + /* Should do better ??? */ + printk(KERN_DEBUG "16C950 : Can't reach desired speed %d\n", + baud); + current_tcr = 0x0; /* Sampling clock = 16 = standard */ + } + serial_icr_write(info, UART_TCR, current_tcr); + +#ifdef DEBUG_16C950_CPR + printk(KERN_DEBUG "16C950 Set Extra speed : baud = %d, standard baud_base = %d\n", baud, info->state->baud_base); + printk(KERN_DEBUG "16C950 - apparent baud_base = %ld, CPR = 0x%X, TCR = 0x%X\n", baud_base, info->CPR, current_tcr); +#endif + + /* Return the aparent baud_base */ + return(baud_base); +} + +/* * This routine is called to set the UART divisor registers to match * the specified baud rate for a serial port. */ @@ -1654,16 +1793,8 @@ static void change_speed(struct async_st #endif baud_base = info->state->baud_base; if (info->state->type == PORT_16C950) { - if (baud <= baud_base) - serial_icr_write(info, UART_TCR, 0); - else if (baud <= 2*baud_base) { - serial_icr_write(info, UART_TCR, 0x8); - baud_base = baud_base * 2; - } else if (baud <= 4*baud_base) { - serial_icr_write(info, UART_TCR, 0x4); - baud_base = baud_base * 4; - } else - serial_icr_write(info, UART_TCR, 0); + /* Special processing for 16C950 */ + baud_base = set_extra_speed_950(info, baud); } if (baud == 38400 && ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) @@ -2339,6 +2470,44 @@ static int set_modem_info(struct async_s return 0; } +/* + * Some serial card take advantage that MSR is normally a read only register + * to define here an additional write only register (not part of the UART). + * In theory, writing to MSR is safe, but you never know... Also, in 650 + * mode, this register is XOFF1 (enabled after writing 0xBF to LCR). + * + * One example is the Brainboxes 620 (Pcmcia Bluetooth card using 16C950 + * UART) where writing to MSR set the UART clock frequency : + * MSR[5:4] set to 0x0 -> /8 -> 1.8432MHz UART clock + * MSR[5:4] set to 0x1 -> /4 -> 3.6864MHz UART clock + * MSR[5:4] set to 0x2 -> /2 -> 7.3728MHz UART clock + * MSR[5:4] set to 0x3 -> /1 -> 14.7456MHz UART clock + * Note : Most often, you want to set it to 0x30 (and baud_base to 921600) + * Jean II + */ +static int set_write_msr(struct async_struct * info, unsigned char *value) +{ + unsigned long flags; + unsigned char msr; + + if (copy_from_user(&msr, value, sizeof(unsigned char))) + return -EFAULT; + +#ifdef DEBUG_16C950_WMSR + printk(KERN_DEBUG "16C950 Setting WMSR to 0x%02X\n", msr); +#endif + + /* Save it for subsequent reset */ + info->WMSR = 0x100 | msr; + + /* Just do it ! */ + save_flags(flags); cli(); + serial_out(info, UART_MSR, info->WMSR & 0xFF); + restore_flags(flags); + + return 0; +} + static int do_autoconfig(struct async_struct * info) { int irq, retval; @@ -2678,6 +2847,8 @@ static int rs_ioctl(struct tty_struct *t /* "setserial -W" is called in Debian boot */ printk ("TIOCSER?WILD ioctl obsolete, ignored.\n"); return 0; + case TIOCSERSETWMSR: + return set_write_msr(info, (unsigned char *) arg); default: return -ENOIOCTLCMD;