Subject: uart.c: avoid changing parameter RAM on-the-fly for the console

From: Steffen Rumler <[email-masked]>
Date: Thu, 03 Feb 2005 16:17:30 +0100

Hi,

We are using the 2.4.20 PPC kernel running on the TQM859T module.

A lot of this kind of modules always hangs during the boot,
something like this:

      Linux version 2.4.20-rthal5 (ru@styx) (gcc version 2.95.3 20010111 (prerelease/franzo/20010111))
      #31 Thu Feb 3 15:07:0
      1 CET 2005
      On node 0 totalpages: 8128
      zone(0): 8128 pages.
      zone(1): 0 pages.
      zone(2): 0 pages.
      ...
      CPM UART driver version 0.03+
      ttyS00 at 0x0280 is a SMC
                           ^^^^^
                             THE SYSTEM HANGS HERE :-(

Deeper inspecting the uart.c, we recognized that the SMC (or SCC)
hardware related to the console will be (re-)configured multiple times in:

    (1) serial_console_setup():  early setup with small BD tables

    (2) rs_8xx_init(): bigger BD tables; without interrupts

    (3) startup(): with interrupts

The re-configuring (1->2, 2->3) will be performed on-the-fly,
without disabling the hardware.

According to Motorola this is _NOT_ allowed and dangerous.

For the SMC, it is suggested to run a 'STOP TX' CPCR command followed
by disabling RX/TX in SMCMR (TEN/REN), in order to stop the controller
correctly.

I suggest to do this inside rs_8xx_init() and startup() as shown
in the patch below.
The SCC is not addressed by the patch.

With this fix, the modules hanging before can boot now.

Steffen

--

diff -Naur old/arch/ppc/8xx_io/uart.c new/arch/ppc/8xx_io/uart.c
--- old/arch/ppc/8xx_io/uart.c	Thu Feb  3 15:54:21 2005
+++ new/arch/ppc/8xx_io/uart.c	Thu Feb  3 15:52:44 2005
@@ -119,6 +119,10 @@
 static int  uart_buf_read_proc    (char *, char **, off_t, int, int *, void *);
 #endif
 
+#ifdef CONFIG_SERIAL_CONSOLE
+static void full_seq_smc_stop (int port);
+#endif
+
 /*
  * Serial driver configuration section.  Here are the various options:
  */
@@ -831,10 +835,17 @@
 	else {
 		smcp = &cpmp->cp_smc[idx];
 
+#ifdef CONFIG_SERIAL_CONSOLE
+                /*  stop SMC in the correct way, before re-configuring it
+                 */
+		if (((state - rs_table) == CONFIG_SERIAL_CONSOLE_PORT)){
+                    full_seq_smc_stop(PORT_NUM(info->state->smc_scc_num));
+                }
+#endif
+
 		/* Enable interrupts and I/O.
 		*/
 		smcp->smc_smcm |= (SMCM_RX | SMCM_TX);
-		smcp->smc_smcmr |= (SMCMR_REN | SMCMR_TEN);
 
 		/* We can tune the buffer length and idle characters
 		 * to take advantage of the entire incoming buffer size.
@@ -848,6 +859,8 @@
 		up->smc_mrblr = RX_BUF_SIZE;
 		up->smc_maxidl = RX_BUF_SIZE;
 		up->smc_brkcr = 1;	/* number of break chars */
+
+		smcp->smc_smcmr |= (SMCMR_REN | SMCMR_TEN);
 	}
 
 	info->flags |= ASYNC_INITIALIZED;
@@ -2694,6 +2707,30 @@
   
 } /* end uart_removeProcEntries() */
 
+
+#ifdef CONFIG_SERIAL_CONSOLE
+static void full_seq_smc_stop (int port)
+{
+    volatile cpm8xx_t *cp=cpmp;
+    volatile smc_t *sp;
+    ushort chan;
+    unsigned long flags;
+
+    sp   = &cp->cp_smc[port];
+    chan = smc_chan_map[port];
+
+    local_irq_save(flags);
+
+    while (cp->cp_cpcr & CPM_CR_FLG);
+    cp->cp_cpcr = mk_cr_cmd(chan, CPM_CR_STOP_TX) | CPM_CR_FLG;
+    while (cp->cp_cpcr & CPM_CR_FLG);
+    sp->smc_smcmr &= ~(SMCMR_REN | SMCMR_TEN);
+
+    local_irq_restore(flags);
+
+}  /* full_seq_smc_stop */ 
+#endif  /* CONFIG_SERIAL_CONSOLE */
+
 /*
  * The serial driver boot-time initialization code!
  */
@@ -2905,6 +2942,15 @@
 			}
 			else {
 				sp = &cp->cp_smc[idx];
+
+#ifdef CONFIG_SERIAL_CONSOLE
+                                /*  stop SMC in the correct way, before re-configuring it
+                                 */
+                                if (i == CONFIG_SERIAL_CONSOLE_PORT){
+                                    full_seq_smc_stop(idx);
+                                }
+#endif
+
 				up = (smc_uart_t *)&cp->cp_dparam[state->port];
 				up->smc_rbase = dp_addr;
 			}