[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[bluetooth-dev] [PATCH] Brainbox 620 Pcmcia card at 921600 baud



	Hi,

	Thanks infinitely to Paul Barnett from Brainboxes, I've
eventually managed to get those nice Pcmcia cards to run at 921600
bauds with OpenBT. Sweet ;-)
	Attached is two patches that I wrote. The first patch is for
the serial driver in the kernel (I'm using 2.4.12-ac6). The second
patch is for the "setserial" utility (available at
http://serial.sourceforge.net/).

	Here is the deal : you recompile your serial module and
setserial, insert the card and do :
		> setserial /dev/ttyS2 baud_base 921600
		> setserial /dev/ttyS2 wmsr 0x30
	Then, you just launch the usual bti and you specify 921600,
and it should work. At least, it did for me ;-)

	And you may get a :
---------------------------------
BT SYS: ERROR: process_event: A hardware error with error code 0x1C occurred.
BT SYS: ERROR: process_event: Please refer to your Bluetooth module's manual.
---------------------------------
	Just ignore that and proceed anyway (for example with an
inquiry).

	Have fun...

	Jean
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;
diff -u -p setserial.brain.c setserial.c
--- setserial.brain.c	Mon Oct 29 16:49:10 2001
+++ setserial.c	Mon Oct 29 17:01:54 2001
@@ -77,6 +77,7 @@ struct serial_type_struct {
 #define CMD_FLOW_ON     16
 #define CMD_RX_TMOUT    17
 #define CMD_DMA_CHAN    18
+#define CMD_WMSR        19
 
 #define FLAG_CAN_INVERT	0x0001
 #define FLAG_NEED_ARG	0x0002
@@ -126,9 +127,15 @@ struct flag_type_table {
 	CMD_RX_TMOUT,   "rx_timeout",   0,              0,              0, FLAG_NEED_ARG,
 	CMD_DMA_CHAN,   "dma_channel",  0,              0,              0, FLAG_NEED_ARG,
 #endif
+	CMD_WMSR,	"wmsr",		0,		0,		0, FLAG_NEED_ARG,
 	0,		0,		0,		0,		0, 0,
 };
 	
+/* Kernel header will take ages before beeing updated in distributions */
+#ifndef TIOCSERSETWMSR
+#define TIOCSERSETWMSR  0x5461	/* Set custom write in Modem Status Register */
+#endif	/* TIOCSERSETWMSR */
+
 char *serial_type(int id)
 {
 	int i;
@@ -603,6 +610,15 @@ void set_serial(char *device, char ** ar
 			set_hayesesp(fd, p->cmd, atonum(*arg++));
 			break;
 #endif
+		case CMD_WMSR:
+			{
+				unsigned char wmsr = atonum(*arg++);
+				if (ioctl(fd, TIOCSERSETWMSR, &wmsr) < 0) {
+					perror("Cannot set wmsr");
+					exit(1);
+				}
+			}
+			break;
 		default:
 			fprintf(stderr, "Internal error: unhandled cmd #%d\n", p->cmd);
 			exit(1);