Hi all,
I've been busy trying to clarify few things discussed here.
Please find attached a re-worked patch and comments interleaved here after.
Thank you very much for your help!
Regards,

Fabrice

On 20/01/2012 20:04, Wolfgang Grandegger wrote:
> On 01/20/2012 07:46 PM, Gilles Chanteperdrix wrote:
>> On 01/20/2012 07:03 PM, Wolfgang Grandegger wrote:
>>> Hi Fabrice and Manfred,
>>>
>>> On 01/19/2012 06:09 PM, Fabrice Gasnier wrote:
>>>> On 18/01/2012 17:32, Wolfgang Grandegger wrote:
>>>>>> Further investigation indicates that when writing, tx interrupt is not 
>>>>>> asserted as expected (rt_16550_write):
>>>>>>> /* unmask tx interrupt */
>>>>>>> ctx->ier_status |= IER_TX;
>>>>>>> rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx),
>>>>>>>                  ctx->base_addr, IER,
>>>>>>>                  ctx->ier_status,
>>>>>>>                  ctx->regshift);
>>>>>>>
>>>>>>> >From my understanding this should trigger an irq for data to be put in 
>>>>>>> >rt_16550_tx_interrupt().
>>>>>>> It seems this is not always the case. Moreover, TX interrupt seem to be 
>>>>>>> triggered by a new RX interrupt.
>>>>> The TX interrupt will be enabled as long as there are chars to send,
>>>>> IIRC. The isr the puts the chars into the FIFO and triggers the xfer.
>>>> Finally, I find out that UART was in sleep mode.
>>>> According to omap's reference manual, it enters this mode when conditions 
>>>> are met:
>>>> rx line is idle,
>>>> tx fifo and shift register are empty,
>>>> rx fifo is empty
>>>> no interrupts pending
>>>>
>>>> One solution that i've tested successfully is to disable sleep mode in 
>>>> rt_16550_open(). TX interrupts are then being triggered as expected.
Disable sleep seems definitely required (at least on my omap target):
int rt_16550_open( ... ) {
...
        /* disable sleep mode */
        rt_16550_disable_sleep(ctx);
...
}
>>>>>
>>>>>>>
>>>>>>> Would you have an explanation for such a behavior?
>>>>>>> I'm not sure how to solve this.
>>>>> Depending on the hardware/uart revision, you may need to take care of
>>>>> other quirks, see:
>>>>>
>>>>> http://lxr.linux.no/#linux+v3.2.1/arch/arm/mach-omap2/serial.c#L742
>>>> Thank you for this link! It helps handle the fifo full condition.
>>>> However, I noticed a strange value regarding version register. My omap3530 
>>>> reports 0x46?
>>>> Linux serial driver assume this bug is present on revision >= 0x52 ...
>>>> But my target freeze when I send more than 16 chars at once (Fifo full 
>>>> without errata handling).
I finally found out the cause was not as I thought it was.
I'm running a small test program that write data to serial and exits after 
closing the device using rt_dev_close.
I figured out that closing dev immediately after write makes the driver not to 
flush all data!
When closing the device, interrupts are disabled without waiting for fifo to 
empty.
I also made some testing on another (x86) platform to be sure: behavior is 
identical.
Next time I open the port, target freeze (omap only).
So, I added a small routine to wait for data being sent when closing:

int rt_16550_close(...) {
...
        /* wait for fifo tx to be empty */
        rt_16550_wait_tx_end(ctx);
...
}

>>>> It works when using errata handling.

Further analysis of both xeno_16550A and omap-serial driver lead me to believe 
there is no need to use errata handling to write to TX fifo:
from omap-serial 
http://lxr.linux.no/#linux+v3.2.1/arch/arm/mach-omap2/serial.c#L620
 624        status = __serial_read_reg(up, UART_LSR);
 625        while (!(status & UART_LSR_THRE)) {
 626                /* Wait up to 10ms for the character(s) to be sent. */
 627                if (--tmout == 0)
 628                        break;
 629                udelay(1);
 630                status = __serial_read_reg(up, UART_LSR);
 631        }

from 16550A.c:
static inline void rt_16550_tx_interrupt(struct rt_16550_context *ctx)
...
for (count = ctx->tx_fifo;
         (count > 0) && (ctx->out_npend > 0);
         count--, ctx->out_npend--) {
        c = ctx->out_buf[ctx->out_head++];
        rt_16550_reg_out(mode, base, THR, c);
        ctx->out_head &= (OUT_BUFFER_SIZE - 1);
}

THR is only written in rt_16550_tx_interrupt. It's only triggered when tx fifo 
is empty.
So, I think there is no need to use linux driver method.
Moreover, I believe this makes the driver to push data byte-per-byte, instead 
of using fifo.
I removed it from my fisrt patch.

However, I cannot test this on an omap that should be impacted by errata as my 
uart revision is 0x46 ( < 0x52 ).
All of this is ok on my side.

@Manfred: maybe you can help, make some more tests ?
>>>
>>> It seems the 16550-compatible UARTs on the OMAP processor are special
>>> and also buggy requiring more or less heavy workarounds, unfortunately.
>>> I can't comment on that as I do not have experience with OMAP processors.
>>>
>>>> You'll find attached a patch that works for me.
>>>> Please advise. Maybe we can enhance it and push it?
>>>
>>> To handle hardware-specific initialization I/O properly, I think we need
>>> first a more flexible interface using callback functions. The existing
>>> interface with
>>>
>>>     base = ctx->base_addr;
>>>     mode = rt_16550_io_mode_from_ctx(ctx);
>>>     regshift = ctx->regshift;
>>>     rt_16550_reg_in(mode, base, regshift, offset)",
I reworked my patch to follow this guideline:

struct rt_16550_context {
...
        u8 (*rt_16550_reg_in)(struct rt_16550_context *, int);
        void (*rt_16550_reg_out)(struct rt_16550_context *, int, u8);
...
}
#define rt_16550_serial_in(ctx, offset) \
                        (ctx->rt_16550_reg_in(ctx, offset))
#define rt_16550_serial_out(ctx, offset, value) \
                        (ctx->rt_16550_reg_out(ctx, offset, value))
This is very close to :
http://lxr.linux.no/#linux+v3.2.1/drivers/tty/serial/8250.c#L519

>>>
>>> #ifdef's and switch statements in the I/O functions is really horrible.
>>> A more elegant solution would make integration of the OMAP specialities
>>> much easier.
Removed from all routines in 16550.c but these are still required in 16550_io.h
>>
>> I will wait for Wolfgang's ack before merging anything, then.
> 
> Well, I'm actually not the maintainer. Jan?
> 
> Wolfgang.
> 
> 
> 
>From bdb16585ee67e746c0f747dfb93a3d8193103594 Mon Sep 17 00:00:00 2001
From: Fabrice Gasnier <[email protected]>
Date: Thu, 26 Jan 2012 10:13:08 +0100
Subject: [PATCH] Add xeno_16550A omap support
 Add regshift module parameter.
 Add omap specific "fifo full" errata handling from linux-3.2 omap-serial
 Fix closing when tx fifo isn't empty
 Disable sleep mode on omap uart


Signed-off-by: Fabrice Gasnier <[email protected]>
---
 ksrc/drivers/serial/16550A.c    |  148 +++++++++++++++++---------------
 ksrc/drivers/serial/16550A_io.h |  184 ++++++++++++++++++++++++++++++++++++--
 2 files changed, 253 insertions(+), 79 deletions(-)

diff --git a/ksrc/drivers/serial/16550A.c b/ksrc/drivers/serial/16550A.c
index 3672539..945929e 100644
--- a/ksrc/drivers/serial/16550A.c
+++ b/ksrc/drivers/serial/16550A.c
@@ -70,6 +70,23 @@
 #define LSR			5	/* Line Status Register */
 #define MSR			6	/* Modem Status Register */
 
+#if defined (CONFIG_ARCH_OMAP3) || \
+	defined (CONFIG_ARCH_OMAP4)
+#define OMAP_SCR	0x10	/* Supplementary control register */
+#define OMAP_MVR	0x14	/* Module Version Register */
+#define OMAP_SYSC	0x15	/* System configuration register */
+#define OMAP_WER	0x17	/* Wake-up enable register */
+#define REGION_MAX	OMAP_WER	/* end of io/memory region */
+
+#define LCR_CONF_MODE_A		LCR_DLAB	/* Configutation mode A */
+#define LCR_CONF_MODE_B		0xBF		/* Configutation mode B */
+#define EFR            		2			/* Enhanced feature register (when LCR_CONF_MODE_B) */
+#define EFR_ECB        		0x10 		/* Enhanced control bit */
+#define IERX_SLEEP     		0x10 		/* Enable sleep mode */
+#else
+#define REGION_MAX			MSR			/* end of io/memory region   */
+#endif
+
 struct rt_16550_context {
 	struct rtser_config config;	/* current device configuration */
 
@@ -80,6 +97,8 @@ struct rt_16550_context {
 #ifdef CONFIG_XENO_DRIVERS_16550A_ANY
 	int io_mode;			/* hardware IO-access mode */
 #endif
+	unsigned char regshift;		/* register shift */
+
 	int tx_fifo;			/* cached global tx_fifo[<device>] */
 
 	int in_head;			/* RX ring buffer, head pointer */
@@ -107,8 +126,16 @@ struct rt_16550_context {
 	int mcr_status;			/* MCR cache */
 	int status;			/* cache for LSR + soft-states */
 	int saved_errors;		/* error cache for RTIOC_GET_STATUS */
+
+	u8 (*rt_16550_reg_in)(struct rt_16550_context *, int);
+	void (*rt_16550_reg_out)(struct rt_16550_context *, int, u8);
 };
 
+#define rt_16550_serial_in(ctx, offset) \
+			(ctx->rt_16550_reg_in(ctx, offset))
+#define rt_16550_serial_out(ctx, offset, value) \
+			(ctx->rt_16550_reg_out(ctx, offset, value))
+
 static const struct rtser_config default_config = {
 	0xFFFF, RTSER_DEF_BAUD, RTSER_DEF_PARITY, RTSER_DEF_BITS,
 	RTSER_DEF_STOPB, RTSER_DEF_HAND, RTSER_DEF_FIFO_DEPTH, 0,
@@ -148,14 +175,12 @@ MODULE_AUTHOR("[email protected]");
 static inline int rt_16550_rx_interrupt(struct rt_16550_context *ctx,
 					uint64_t * timestamp)
 {
-	unsigned long base = ctx->base_addr;
-	int mode = rt_16550_io_mode_from_ctx(ctx);
 	int rbytes = 0;
 	int lsr = 0;
 	int c;
 
 	do {
-		c = rt_16550_reg_in(mode, base, RHR);	/* read input char */
+		c = rt_16550_serial_in(ctx, RHR);	/* read input char */
 
 		ctx->in_buf[ctx->in_tail] = c;
 		if (ctx->in_history)
@@ -169,7 +194,7 @@ static inline int rt_16550_rx_interrupt(struct rt_16550_context *ctx,
 
 		rbytes++;
 		lsr &= ~RTSER_LSR_DATA;
-		lsr |= (rt_16550_reg_in(mode, base, LSR) &
+		lsr |= (rt_16550_serial_in(ctx, LSR) &
 			(RTSER_LSR_DATA | RTSER_LSR_OVERRUN_ERR |
 			 RTSER_LSR_PARITY_ERR | RTSER_LSR_FRAMING_ERR |
 			 RTSER_LSR_BREAK_IND));
@@ -195,8 +220,6 @@ static inline void rt_16550_tx_interrupt(struct rt_16550_context *ctx)
 {
 	int c;
 	int count;
-	unsigned long base = ctx->base_addr;
-	int mode = rt_16550_io_mode_from_ctx(ctx);
 
 /*	if (uart->modem & MSR_CTS)*/
 	{
@@ -204,7 +227,7 @@ static inline void rt_16550_tx_interrupt(struct rt_16550_context *ctx)
 		     (count > 0) && (ctx->out_npend > 0);
 		     count--, ctx->out_npend--) {
 			c = ctx->out_buf[ctx->out_head++];
-			rt_16550_reg_out(mode, base, THR, c);
+			rt_16550_serial_out(ctx, THR, c);
 			ctx->out_head &= (OUT_BUFFER_SIZE - 1);
 		}
 	}
@@ -212,10 +235,7 @@ static inline void rt_16550_tx_interrupt(struct rt_16550_context *ctx)
 
 static inline void rt_16550_stat_interrupt(struct rt_16550_context *ctx)
 {
-	unsigned long base = ctx->base_addr;
-	int mode = rt_16550_io_mode_from_ctx(ctx);
-
-	ctx->status |= (rt_16550_reg_in(mode, base, LSR) &
+	ctx->status |= (rt_16550_serial_in(ctx, LSR) &
 			(RTSER_LSR_OVERRUN_ERR | RTSER_LSR_PARITY_ERR |
 			 RTSER_LSR_FRAMING_ERR | RTSER_LSR_BREAK_IND));
 }
@@ -223,8 +243,6 @@ static inline void rt_16550_stat_interrupt(struct rt_16550_context *ctx)
 static int rt_16550_interrupt(rtdm_irq_t * irq_context)
 {
 	struct rt_16550_context *ctx;
-	unsigned long base;
-	int mode;
 	int iir;
 	uint64_t timestamp = rtdm_clock_read();
 	int rbytes = 0;
@@ -233,13 +251,11 @@ static int rt_16550_interrupt(rtdm_irq_t * irq_context)
 	int ret = RTDM_IRQ_NONE;
 
 	ctx = rtdm_irq_get_arg(irq_context, struct rt_16550_context);
-	base = ctx->base_addr;
-	mode = rt_16550_io_mode_from_ctx(ctx);
 
 	rtdm_lock_get(&ctx->lock);
 
 	while (1) {
-		iir = rt_16550_reg_in(mode, base, IIR) & IIR_MASK;
+		iir = rt_16550_serial_in(ctx, IIR) & IIR_MASK;
 		if (testbits(iir, IIR_PIRQ))
 			break;
 
@@ -251,7 +267,7 @@ static int rt_16550_interrupt(rtdm_irq_t * irq_context)
 		else if (iir == IIR_TX)
 			rt_16550_tx_interrupt(ctx);
 		else if (iir == IIR_MODEM) {
-			modem = rt_16550_reg_in(mode, base, MSR);
+			modem = rt_16550_serial_in(ctx, MSR);
 			if (modem & (modem << 4))
 				events |= RTSER_EVENT_MODEMHI;
 			if ((modem ^ 0xF0) & (modem << 4))
@@ -292,7 +308,7 @@ static int rt_16550_interrupt(rtdm_irq_t * irq_context)
 	}
 
 	/* update interrupt mask */
-	rt_16550_reg_out(mode, base, IER, ctx->ier_status);
+	rt_16550_serial_out(ctx, IER, ctx->ier_status);
 
 	rtdm_lock_put(&ctx->lock);
 
@@ -304,8 +320,6 @@ static int rt_16550_set_config(struct rt_16550_context *ctx,
 			       uint64_t **in_history_ptr)
 {
 	rtdm_lockctx_t lock_ctx;
-	unsigned long base = ctx->base_addr;
-	int mode = rt_16550_io_mode_from_ctx(ctx);
 	int err = 0;
 
 	/* make line configuration atomic and IRQ-safe */
@@ -320,9 +334,9 @@ static int rt_16550_set_config(struct rt_16550_context *ctx,
 		ctx->config.baud_rate = config->baud_rate;
 		baud_div = (baud_base[dev_id] + (ctx->config.baud_rate>>1)) /
 			ctx->config.baud_rate;
-		rt_16550_reg_out(mode, base, LCR, LCR_DLAB);
-		rt_16550_reg_out(mode, base, DLL, baud_div & 0xff);
-		rt_16550_reg_out(mode, base, DLM, baud_div >> 8);
+		rt_16550_serial_out(ctx, LCR, LCR_DLAB);
+		rt_16550_serial_out(ctx, DLL, baud_div & 0xff);
+		rt_16550_serial_out(ctx, DLM, baud_div >> 8);
 	}
 
 	if (testbits(config->config_mask, RTSER_SET_PARITY))
@@ -336,7 +350,7 @@ static int rt_16550_set_config(struct rt_16550_context *ctx,
 					  RTSER_SET_DATA_BITS |
 					  RTSER_SET_STOP_BITS |
 					  RTSER_SET_BAUD)) {
-		rt_16550_reg_out(mode, base, LCR,
+		rt_16550_serial_out(ctx, LCR,
 				 (ctx->config.parity << 3) |
 				 (ctx->config.stop_bits << 2) |
 				 ctx->config.data_bits);
@@ -346,9 +360,9 @@ static int rt_16550_set_config(struct rt_16550_context *ctx,
 
 	if (testbits(config->config_mask, RTSER_SET_FIFO_DEPTH)) {
 		ctx->config.fifo_depth = config->fifo_depth & FIFO_MASK;
-		rt_16550_reg_out(mode, base, FCR,
+		rt_16550_serial_out(ctx, FCR,
 				 FCR_FIFO | FCR_RESET_RX | FCR_RESET_TX);
-		rt_16550_reg_out(mode, base, FCR,
+		rt_16550_serial_out(ctx, FCR,
 				 FCR_FIFO | ctx->config.fifo_depth);
 	}
 
@@ -405,7 +419,7 @@ static int rt_16550_set_config(struct rt_16550_context *ctx,
 		else
 			/* disable modem status interrupt */
 			ctx->ier_status &= ~IER_MODEM;
-		rt_16550_reg_out(mode, base, IER, ctx->ier_status);
+		rt_16550_serial_out(ctx, IER, ctx->ier_status);
 
 		rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
 	}
@@ -425,7 +439,7 @@ static int rt_16550_set_config(struct rt_16550_context *ctx,
 			    RTSER_MCR_DTR | RTSER_MCR_RTS | RTSER_MCR_OUT2;
 			break;
 		}
-		rt_16550_reg_out(mode, base, MCR, ctx->mcr_status);
+		rt_16550_serial_out(ctx, MCR, ctx->mcr_status);
 
 		rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
 	}
@@ -486,20 +500,21 @@ int rt_16550_open(struct rtdm_dev_context *context,
 			       context->device->proc_name, ctx);
 	if (err) {
 		/* reset DTR and RTS */
-		rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx), ctx->base_addr,
-				 MCR, 0);
+		rt_16550_serial_out(ctx, MCR, 0);
 
 		rt_16550_cleanup_ctx(ctx);
 
 		return err;
 	}
 
+	/* disable sleep mode */
+	rt_16550_disable_sleep(ctx);
+
 	rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
 
 	/* enable interrupts */
 	ctx->ier_status = IER_RX;
-	rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx), ctx->base_addr, IER,
-			 IER_RX);
+	rt_16550_serial_out(ctx, IER, IER_RX);
 
 	rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
 
@@ -510,26 +525,25 @@ int rt_16550_close(struct rtdm_dev_context *context,
 		   rtdm_user_info_t * user_info)
 {
 	struct rt_16550_context *ctx;
-	unsigned long base;
-	int mode;
 	uint64_t *in_history;
 	rtdm_lockctx_t lock_ctx;
 
 	ctx = (struct rt_16550_context *)context->dev_private;
-	base = ctx->base_addr;
-	mode = rt_16550_io_mode_from_ctx(ctx);
+
+	/* wait for fifo tx to be empty */
+	rt_16550_wait_tx_end(ctx);
 
 	rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
 
 	/* reset DTR and RTS */
-	rt_16550_reg_out(mode, base, MCR, 0);
+	rt_16550_serial_out(ctx, MCR, 0);
 
 	/* mask all UART interrupts and clear pending ones. */
-	rt_16550_reg_out(mode, base, IER, 0);
-	rt_16550_reg_in(mode, base, IIR);
-	rt_16550_reg_in(mode, base, LSR);
-	rt_16550_reg_in(mode, base, RHR);
-	rt_16550_reg_in(mode, base, MSR);
+	rt_16550_serial_out(ctx, IER, 0);
+	rt_16550_serial_in(ctx, IIR);
+	rt_16550_serial_in(ctx, LSR);
+	rt_16550_serial_in(ctx, RHR);
+	rt_16550_serial_in(ctx, MSR);
 
 	in_history = ctx->in_history;
 	ctx->in_history = NULL;
@@ -552,12 +566,8 @@ int rt_16550_ioctl(struct rtdm_dev_context *context,
 	rtdm_lockctx_t lock_ctx;
 	struct rt_16550_context *ctx;
 	int err = 0;
-	unsigned long base;
-	int mode;
 
 	ctx = (struct rt_16550_context *)context->dev_private;
-	base = ctx->base_addr;
-	mode = rt_16550_io_mode_from_ctx(ctx);
 
 	switch (request) {
 	case RTSER_RTIOC_GET_CONFIG:
@@ -638,9 +648,9 @@ int rt_16550_ioctl(struct rtdm_dev_context *context,
 			struct rtser_status status_buf;
 
 			status_buf.line_status =
-			    rt_16550_reg_in(mode, base, LSR) | status;
+			    rt_16550_serial_in(ctx, LSR) | status;
 			status_buf.modem_status =
-			    rt_16550_reg_in(mode, base, MSR);
+			    rt_16550_serial_in(ctx, MSR);
 
 			err =
 			    rtdm_safe_copy_to_user(user_info, arg,
@@ -649,9 +659,9 @@ int rt_16550_ioctl(struct rtdm_dev_context *context,
 							  rtser_status));
 		} else {
 			((struct rtser_status *)arg)->line_status =
-			    rt_16550_reg_in(mode, base, LSR) | status;
+			    rt_16550_serial_in(ctx, LSR) | status;
 			((struct rtser_status *)arg)->modem_status =
-			    rt_16550_reg_in(mode, base, MSR);
+			    rt_16550_serial_in(ctx, MSR);
 		}
 		break;
 	}
@@ -672,7 +682,7 @@ int rt_16550_ioctl(struct rtdm_dev_context *context,
 
 		rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
 		ctx->mcr_status = new_mcr;
-		rt_16550_reg_out(mode, base, MCR, new_mcr);
+		rt_16550_serial_out(ctx, MCR, new_mcr);
 		rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
 		break;
 	}
@@ -698,7 +708,7 @@ int rt_16550_ioctl(struct rtdm_dev_context *context,
 			if (testbits(ctx->config.event_mask,
 				     RTSER_EVENT_ERRPEND)) {
 				ctx->ier_status |= IER_STAT;
-				rt_16550_reg_out(mode, base, IER,
+				rt_16550_serial_out(ctx, IER,
 						 ctx->ier_status);
 			}
 
@@ -752,7 +762,7 @@ int rt_16550_ioctl(struct rtdm_dev_context *context,
 		    (ctx->config.parity << 3) | (ctx->config.stop_bits << 2) |
 		    ctx->config.data_bits;
 
-		rt_16550_reg_out(mode, base, LCR, lcr);
+		rt_16550_serial_out(ctx, LCR, lcr);
 
 		rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
 		break;
@@ -768,7 +778,7 @@ int rt_16550_ioctl(struct rtdm_dev_context *context,
 			ctx->in_npend = 0;
 			ctx->status = 0;
 			fcr |= FCR_FIFO | FCR_RESET_RX;
-			rt_16550_reg_in(mode, base, RHR);
+			rt_16550_serial_in(ctx, RHR);
 		}
 		if ((long)arg & RTDM_PURGE_TX_BUFFER) {
 			ctx->out_head = 0;
@@ -777,8 +787,8 @@ int rt_16550_ioctl(struct rtdm_dev_context *context,
 			fcr |= FCR_FIFO | FCR_RESET_TX;
 		}
 		if (fcr) {
-			rt_16550_reg_out(mode, base, FCR, fcr);
-			rt_16550_reg_out(mode, base, FCR,
+			rt_16550_serial_out(ctx, FCR, fcr);
+			rt_16550_serial_out(ctx, FCR,
 					 FCR_FIFO | ctx->config.fifo_depth);
 		}
 		rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
@@ -830,8 +840,7 @@ ssize_t rt_16550_read(struct rtdm_dev_context * context,
 		/* switch on error interrupt - the user is ready to listen */
 		if (!testbits(ctx->ier_status, IER_STAT)) {
 			ctx->ier_status |= IER_STAT;
-			rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx),
-					 ctx->base_addr, IER,
+			rt_16550_serial_out(ctx, IER,
 					 ctx->ier_status);
 		}
 
@@ -1045,8 +1054,7 @@ ssize_t rt_16550_write(struct rtdm_dev_context * context,
 
 			/* unmask tx interrupt */
 			ctx->ier_status |= IER_TX;
-			rt_16550_reg_out(rt_16550_io_mode_from_ctx(ctx),
-					 ctx->base_addr, IER,
+			rt_16550_serial_out(ctx, IER,
 					 ctx->ier_status);
 
 			rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
@@ -1116,8 +1124,7 @@ void rt_16550_exit(void);
 int __init rt_16550_init(void)
 {
 	struct rtdm_device *dev;
-	unsigned long base;
-	int mode;
+	struct rt_16550_context ctx;
 	int err;
 	int i;
 
@@ -1155,13 +1162,16 @@ int __init rt_16550_init(void)
 			tx_fifo[i] = DEFAULT_TX_FIFO;
 
 		/* Mask all UART interrupts and clear pending ones. */
-		base = rt_16550_base_addr(i);
-		mode = rt_16550_io_mode(i);
-		rt_16550_reg_out(mode, base, IER, 0);
-		rt_16550_reg_in(mode, base, IIR);
-		rt_16550_reg_in(mode, base, LSR);
-		rt_16550_reg_in(mode, base, RHR);
-		rt_16550_reg_in(mode, base, MSR);
+		ctx.base_addr = rt_16550_base_addr(i);
+#ifdef CONFIG_XENO_DRIVERS_16550A_ANY
+		ctx.mode = rt_16550_io_mode(i);
+#endif
+		ctx.regshift = rt_16550_regshift(i);
+		rt_16550_reg_out(&ctx, IER, 0);
+		rt_16550_reg_in(&ctx, IIR);
+		rt_16550_reg_in(&ctx, LSR);
+		rt_16550_reg_in(&ctx, RHR);
+		rt_16550_reg_in(&ctx, MSR);
 
 		err = rtdm_dev_register(dev);
 
diff --git a/ksrc/drivers/serial/16550A_io.h b/ksrc/drivers/serial/16550A_io.h
index 92d21a5..ff55f46 100644
--- a/ksrc/drivers/serial/16550A_io.h
+++ b/ksrc/drivers/serial/16550A_io.h
@@ -31,15 +31,34 @@ MODULE_PARM_DESC(io, "I/O port addresses of the serial devices");
     defined(CONFIG_XENO_DRIVERS_16550A_ANY)
 static unsigned long mem[MAX_DEVICES];
 static void *mapped_io[MAX_DEVICES];
+static unsigned char regshift[MAX_DEVICES];
 compat_module_param_array(mem, ulong, MAX_DEVICES, 0400);
+compat_module_param_array(regshift, byte, MAX_DEVICES, 0400);
 MODULE_PARM_DESC(mem, "I/O memory addresses of the serial devices");
+MODULE_PARM_DESC(regshift, "register shift (ex: on some omap, use regshift=2)");
 #endif /* CONFIG_XENO_DRIVERS_16550A_MMIO || CONFIG_XENO_DRIVERS_16550A_ANY */
 
+#if defined (CONFIG_ARCH_OMAP3) || \
+	defined (CONFIG_ARCH_OMAP4)
+#define UART_OMAP_NO_EMPTY_FIFO_READ_IP_REV     0x52
+#define UART_ERRATA_FIFO_FULL_ABORT     		(0x1 << 0)
+#endif
+
 #ifdef CONFIG_XENO_DRIVERS_16550A_PIO
 
 #define RT_16550_IO_INLINE inline
 
 extern void *mapped_io[]; /* dummy */
+static unsigned char regshift[]; /* dummy */
+
+/* prototypes */
+static RT_16550_IO_INLINE int
+rt_16550_handle_errata(struct rt_16550_context *ctx);
+static RT_16550_IO_INLINE u8
+rt_16550_reg_in(struct rt_16550_context *ctx, int off);
+static RT_16550_IO_INLINE void
+rt_16550_reg_out(struct rt_16550_context *ctx, int off, u8 val);
+
 
 static inline unsigned long rt_16550_addr_param(int dev_id)
 {
@@ -61,6 +80,11 @@ static inline io_mode_t rt_16550_io_mode(int dev_id)
 	return MODE_PIO;
 }
 
+static inline unsigned char rt_16550_regshift(int dev_id)
+{
+	return 0;
+}
+
 static inline io_mode_t
 rt_16550_io_mode_from_ctx(struct rt_16550_context *ctx)
 {
@@ -71,6 +95,9 @@ static inline void
 rt_16550_init_io_ctx(int dev_id, struct rt_16550_context *ctx)
 {
 	ctx->base_addr = io[dev_id];
+	ctx->rt_16550_reg_in = rt_16550_reg_in;
+	ctx->rt_16550_reg_out = rt_16550_reg_out;
+	rt_16550_handle_errata(ctx);
 }
 
 #elif defined(CONFIG_XENO_DRIVERS_16550A_MMIO)
@@ -79,6 +106,14 @@ rt_16550_init_io_ctx(int dev_id, struct rt_16550_context *ctx)
 
 extern unsigned long io[]; /* dummy */
 
+/* prototypes */
+static RT_16550_IO_INLINE int
+rt_16550_handle_errata(struct rt_16550_context *ctx);
+static RT_16550_IO_INLINE u8
+rt_16550_reg_in(struct rt_16550_context *ctx, int off);
+static RT_16550_IO_INLINE void
+rt_16550_reg_out(struct rt_16550_context *ctx, int off, u8 val);
+
 static inline unsigned long rt_16550_addr_param(int dev_id)
 {
 	return mem[dev_id];
@@ -99,6 +134,11 @@ static inline io_mode_t rt_16550_io_mode(int dev_id)
 	return MODE_MMIO;
 }
 
+static inline unsigned char rt_16550_regshift(int dev_id)
+{
+	return (unsigned char)regshift[dev_id];
+}
+
 static inline io_mode_t
 rt_16550_io_mode_from_ctx(struct rt_16550_context *ctx)
 {
@@ -109,12 +149,24 @@ static inline void
 rt_16550_init_io_ctx(int dev_id, struct rt_16550_context *ctx)
 {
 	ctx->base_addr = (unsigned long)mapped_io[dev_id];
+	ctx->regshift = (unsigned char)regshift[dev_id];
+	ctx->rt_16550_reg_in = rt_16550_reg_in;
+	ctx->rt_16550_reg_out = rt_16550_reg_out;
+	rt_16550_handle_errata(ctx);
 }
 
 #elif defined(CONFIG_XENO_DRIVERS_16550A_ANY)
 
 #define RT_16550_IO_INLINE /* uninline */
 
+/* prototypes */
+static RT_16550_IO_INLINE int
+rt_16550_handle_errata(struct rt_16550_context *ctx);
+static RT_16550_IO_INLINE u8
+rt_16550_reg_in(struct rt_16550_context *ctx, int off);
+static RT_16550_IO_INLINE void
+rt_16550_reg_out(struct rt_16550_context *ctx, int off, u8 val);
+
 static inline unsigned long rt_16550_addr_param(int dev_id)
 {
 	return (io[dev_id]) ? io[dev_id] : mem[dev_id];
@@ -135,6 +187,11 @@ static inline io_mode_t rt_16550_io_mode(int dev_id)
 	return (io[dev_id]) ? MODE_PIO : MODE_MMIO;
 }
 
+static inline unsigned char rt_16550_regshift(int dev_id)
+{
+	return (io[dev_id]) ? 0 : (unsigned char)regshift[dev_id];
+}
+
 static inline io_mode_t
 rt_16550_io_mode_from_ctx(struct rt_16550_context *ctx)
 {
@@ -151,6 +208,11 @@ rt_16550_init_io_ctx(int dev_id, struct rt_16550_context *ctx)
 		ctx->base_addr = (unsigned long)mapped_io[dev_id];
 		ctx->io_mode   = MODE_MMIO;
 	}
+	ctx->regshift = (unsigned char)regshift[dev_id];
+	ctx->rt_16550_reg_in = rt_16550_reg_in;
+	ctx->rt_16550_reg_out = rt_16550_reg_out;
+	/* handle errata */
+	rt_16550_handle_errata (ctx);
 }
 
 #else
@@ -158,25 +220,27 @@ rt_16550_init_io_ctx(int dev_id, struct rt_16550_context *ctx)
 #endif
 
 static RT_16550_IO_INLINE u8
-rt_16550_reg_in(io_mode_t io_mode, unsigned long base, int off)
+rt_16550_reg_in(struct rt_16550_context *ctx, int off)
 {
-	switch (io_mode) {
+	off <<= ctx->regshift;
+	switch (rt_16550_io_mode_from_ctx(ctx)) {
 	case MODE_PIO:
-		return inb(base + off);
+		return inb(ctx->base_addr + off);
 	default: /* MODE_MMIO */
-		return readb((void *)base + off);
+		return readb((void *)ctx->base_addr + off);
 	}
 }
 
 static RT_16550_IO_INLINE void
-rt_16550_reg_out(io_mode_t io_mode, unsigned long base, int off, u8 val)
+rt_16550_reg_out(struct rt_16550_context *ctx, int off, u8 val)
 {
-	switch (io_mode) {
+	off <<= ctx->regshift;		/* regshift */
+	switch (rt_16550_io_mode_from_ctx(ctx)) {
 	case MODE_PIO:
-		outb(val, base + off);
+		outb(val, ctx->base_addr + off);
 		break;
 	case MODE_MMIO:
-		writeb(val, (void *)base + off);
+		writeb(val, (void *)ctx->base_addr + off);
 		break;
 	}
 }
@@ -185,11 +249,11 @@ static int rt_16550_init_io(int dev_id, char* name)
 {
 	switch (rt_16550_io_mode(dev_id)) {
 	case MODE_PIO:
-		if (!request_region(rt_16550_addr_param(dev_id), 8, name))
+		if (!request_region(rt_16550_addr_param(dev_id), REGION_MAX, name))
 			return -EBUSY;
 		break;
 	case MODE_MMIO:
-		mapped_io[dev_id] = ioremap(rt_16550_addr_param(dev_id), 8);
+		mapped_io[dev_id] = ioremap(rt_16550_addr_param(dev_id), REGION_MAX << regshift[dev_id]);
 		if (!mapped_io[dev_id])
 			return -EBUSY;
 		break;
@@ -208,3 +272,103 @@ static void rt_16550_release_io(int dev_id)
 		break;
 	}
 }
+
+static RT_16550_IO_INLINE void
+rt_16550_wait_tx_end(struct rt_16550_context *ctx)
+{
+	unsigned char lsr;
+	unsigned char nbits;
+	unsigned int tmout;
+
+	nbits = 5 + ctx->config.data_bits; // 5..8 data bits
+	nbits += 1 + ctx->config.stop_bits + 3; // 1..2 stop bits
+	nbits += 2; // start + parity;
+	nbits += 2; // overhead (round up, so tmout > tx duration);
+
+	tmout = (nbits * ctx->out_npend * 1000000) / ctx->config.baud_rate;
+
+	lsr = rt_16550_reg_in(ctx, LSR);
+	while (ctx->out_npend) {
+		/* Wait for the character(s) to be sent. */
+		if(--tmout == 0)
+			break;
+		rtdm_task_busy_sleep(1000);
+		lsr = rt_16550_reg_in(ctx, LSR);
+	}
+	return;
+}
+
+#if defined (CONFIG_ARCH_OMAP3) || \
+	defined (CONFIG_ARCH_OMAP4)
+
+static RT_16550_IO_INLINE u8
+rt_16550_omapsafe_reg_in(struct rt_16550_context *ctx, int off)
+{
+	if (RHR == off) {
+		unsigned char lsr;
+		lsr = rt_16550_reg_in(ctx, LSR);
+		if (!(lsr & RTSER_LSR_DATA)) /* Receiver data ready */
+			return 0; /* FIXME: -EPERM should be returned as error */
+	}
+	return rt_16550_reg_in(ctx, off);
+}
+
+static RT_16550_IO_INLINE void
+rt_16550_disable_sleep(struct rt_16550_context *ctx)
+{
+	unsigned char lcr, efr, ier;
+
+	ier = rt_16550_reg_in(ctx, IER);
+	if (ier & IERX_SLEEP)
+	{
+		lcr = rt_16550_reg_in(ctx, LCR);
+		rt_16550_reg_out(ctx, LCR, LCR_CONF_MODE_B);
+		efr = rt_16550_reg_in(ctx, EFR);
+		rt_16550_reg_out(ctx, EFR, EFR_ECB);
+		rt_16550_reg_out(ctx, LCR, 0x0); /* Operational mode */
+
+		ier = rt_16550_reg_in(ctx, IER);
+		ier &= ~IERX_SLEEP;	/* disable sleep */
+
+		rt_16550_reg_out(ctx, IER, ier);
+		rt_16550_reg_out(ctx, LCR, LCR_CONF_MODE_B);
+		rt_16550_reg_out(ctx, EFR, efr);
+		rt_16550_reg_out(ctx, LCR, lcr);
+	}
+}
+
+static RT_16550_IO_INLINE int
+rt_16550_handle_errata(struct rt_16550_context *ctx)
+{
+	int errata = 0, rev;
+	/*
+	 * omap44xx, ti816x: Never read empty UART fifo
+	 * omap3xxx: Never read empty UART fifo on UARTs
+	 * with IP rev >=0x52
+	 */
+	if (cpu_is_omap44xx() /* FIXME: || cpu_is_ti816x() */)
+		errata |= UART_ERRATA_FIFO_FULL_ABORT;
+	else if ((rev = rt_16550_reg_in(ctx, OMAP_MVR))
+			>= UART_OMAP_NO_EMPTY_FIFO_READ_IP_REV)
+		errata |= UART_ERRATA_FIFO_FULL_ABORT;
+
+	if (errata)
+		ctx->rt_16550_reg_in = rt_16550_omapsafe_reg_in;
+
+	return errata;
+}
+#else
+
+static RT_16550_IO_INLINE void
+rt_16550_disable_sleep(struct rt_16550_context *ctx)
+{
+	return;
+}
+
+static RT_16550_IO_INLINE int
+rt_16550_handle_errata(struct rt_16550_context *ctx)
+{
+	return 0;
+}
+
+#endif //CONFIG_ARCH_OMAP3/4
-- 
1.7.0.4

_______________________________________________
Xenomai-help mailing list
[email protected]
https://mail.gna.org/listinfo/xenomai-help

Reply via email to