From: "Mikael Starvik" Changes to serial port driver. * Use I/O and DMA allocator. * Replaced calls to restore_flags etc. * Added CRISv32 driver. * Remove the console shortcut hack. Signed-off-by: Mikael Starvik rmk: I'm going to say no outright to this because it introduces another driver which doesn't use the serial core _and_ the comments do not explain why. Is cris SMP? If yes, converting save_flags()+cli() to local_irq_save() doesn't provide the required locking to ensure correct operation. Signed-off-by: Andrew Morton --- drivers/serial/Makefile | 1 drivers/serial/crisv10.c | 540 +++---- drivers/serial/crisv10.h | 15 drivers/serial/crisv32.c | 3568 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/serial/crisv32.h | 126 + 5 files changed, 4001 insertions(+), 249 deletions(-) diff -puN drivers/serial/crisv10.c~cris-update-12-17-serial-port-driver drivers/serial/crisv10.c --- 25/drivers/serial/crisv10.c~cris-update-12-17-serial-port-driver 2005-06-27 18:32:02.000000000 -0700 +++ 25-akpm/drivers/serial/crisv10.c 2005-06-27 18:32:02.000000000 -0700 @@ -2,7 +2,7 @@ * * Serial port driver for the ETRAX 100LX chip * - * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 Axis Communications AB + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004 Axis Communications AB * * Many, many authors. Based once upon a time on serial.c for 16x50. * @@ -447,6 +447,7 @@ static char *serial_version = "$Revision #include #include +#include #include #include #include @@ -457,8 +458,9 @@ static char *serial_version = "$Revision /* non-arch dependent serial structures are in linux/serial.h */ #include /* while we keep our own stuff (struct e100_serial) in a local .h file */ -#include "serial.h" +#include "crisv10.h" #include +#include #ifdef CONFIG_ETRAX_SERIAL_FAST_TIMER #ifndef CONFIG_ETRAX_FAST_TIMER @@ -591,13 +593,12 @@ unsigned long timer_data_to_ns(unsigned static void change_speed(struct e100_serial *info); static void rs_throttle(struct tty_struct * tty); static void rs_wait_until_sent(struct tty_struct *tty, int timeout); -static int rs_write(struct tty_struct * tty, int from_user, +static int rs_write(struct tty_struct * tty, const unsigned char *buf, int count); -extern _INLINE_ int rs_raw_write(struct tty_struct * tty, int from_user, +extern _INLINE_ int rs_raw_write(struct tty_struct * tty, const unsigned char *buf, int count); #ifdef CONFIG_ETRAX_RS485 -static int e100_write_rs485(struct tty_struct * tty, int from_user, - const unsigned char *buf, int count); +static int e100_write_rs485(struct tty_struct * tty, const unsigned char *buf, int count); #endif static int get_lsr_info(struct e100_serial * info, unsigned int *value); @@ -684,20 +685,39 @@ static struct e100_serial rs_table[] = { .rx_ctrl = DEF_RX, .tx_ctrl = DEF_TX, .iseteop = 2, + .dma_owner = dma_ser0, + .io_if = if_serial_0, #ifdef CONFIG_ETRAX_SERIAL_PORT0 .enabled = 1, #ifdef CONFIG_ETRAX_SERIAL_PORT0_DMA6_OUT .dma_out_enabled = 1, + .dma_out_nbr = SER0_TX_DMA_NBR, + .dma_out_irq_nbr = SER0_DMA_TX_IRQ_NBR, + .dma_out_irq_flags = SA_INTERRUPT, + .dma_out_irq_description = "serial 0 dma tr", #else .dma_out_enabled = 0, + .dma_out_nbr = UINT_MAX, + .dma_out_irq_nbr = 0, + .dma_out_irq_flags = 0, + .dma_out_irq_description = NULL, #endif #ifdef CONFIG_ETRAX_SERIAL_PORT0_DMA7_IN .dma_in_enabled = 1, + .dma_in_nbr = SER0_RX_DMA_NBR, + .dma_in_irq_nbr = SER0_DMA_RX_IRQ_NBR, + .dma_in_irq_flags = SA_INTERRUPT, + .dma_in_irq_description = "serial 0 dma rec", #else - .dma_in_enabled = 0 + .dma_in_enabled = 0, + .dma_in_nbr = UINT_MAX, + .dma_in_irq_nbr = 0, + .dma_in_irq_flags = 0, + .dma_in_irq_description = NULL, #endif #else .enabled = 0, + .io_if_description = NULL, .dma_out_enabled = 0, .dma_in_enabled = 0 #endif @@ -719,20 +739,42 @@ static struct e100_serial rs_table[] = { .rx_ctrl = DEF_RX, .tx_ctrl = DEF_TX, .iseteop = 3, + .dma_owner = dma_ser1, + .io_if = if_serial_1, #ifdef CONFIG_ETRAX_SERIAL_PORT1 .enabled = 1, + .io_if_description = "ser1", #ifdef CONFIG_ETRAX_SERIAL_PORT1_DMA8_OUT .dma_out_enabled = 1, + .dma_out_nbr = SER1_TX_DMA_NBR, + .dma_out_irq_nbr = SER1_DMA_TX_IRQ_NBR, + .dma_out_irq_flags = SA_INTERRUPT, + .dma_out_irq_description = "serial 1 dma tr", #else .dma_out_enabled = 0, + .dma_out_nbr = UINT_MAX, + .dma_out_irq_nbr = 0, + .dma_out_irq_flags = 0, + .dma_out_irq_description = NULL, #endif #ifdef CONFIG_ETRAX_SERIAL_PORT1_DMA9_IN .dma_in_enabled = 1, + .dma_in_nbr = SER1_RX_DMA_NBR, + .dma_in_irq_nbr = SER1_DMA_RX_IRQ_NBR, + .dma_in_irq_flags = SA_INTERRUPT, + .dma_in_irq_description = "serial 1 dma rec", #else - .dma_in_enabled = 0 + .dma_in_enabled = 0, + .dma_in_enabled = 0, + .dma_in_nbr = UINT_MAX, + .dma_in_irq_nbr = 0, + .dma_in_irq_flags = 0, + .dma_in_irq_description = NULL, #endif #else .enabled = 0, + .io_if_description = NULL, + .dma_in_irq_nbr = 0, .dma_out_enabled = 0, .dma_in_enabled = 0 #endif @@ -753,20 +795,40 @@ static struct e100_serial rs_table[] = { .rx_ctrl = DEF_RX, .tx_ctrl = DEF_TX, .iseteop = 0, + .dma_owner = dma_ser2, + .io_if = if_serial_2, #ifdef CONFIG_ETRAX_SERIAL_PORT2 .enabled = 1, + .io_if_description = "ser2", #ifdef CONFIG_ETRAX_SERIAL_PORT2_DMA2_OUT .dma_out_enabled = 1, + .dma_out_nbr = SER2_TX_DMA_NBR, + .dma_out_irq_nbr = SER2_DMA_TX_IRQ_NBR, + .dma_out_irq_flags = SA_INTERRUPT, + .dma_out_irq_description = "serial 2 dma tr", #else .dma_out_enabled = 0, + .dma_in_nbr = UINT_MAX, + .dma_in_irq_nbr = 0, + .dma_in_irq_flags = 0, + .dma_in_irq_description = NULL, #endif #ifdef CONFIG_ETRAX_SERIAL_PORT2_DMA3_IN .dma_in_enabled = 1, + .dma_in_nbr = SER2_RX_DMA_NBR, + .dma_in_irq_nbr = SER2_DMA_RX_IRQ_NBR, + .dma_in_irq_flags = SA_INTERRUPT, + .dma_in_irq_description = "serial 2 dma rec", #else - .dma_in_enabled = 0 + .dma_in_enabled = 0, + .dma_in_nbr = UINT_MAX, + .dma_in_irq_nbr = 0, + .dma_in_irq_flags = 0, + .dma_in_irq_description = NULL, #endif #else .enabled = 0, + .io_if_description = NULL, .dma_out_enabled = 0, .dma_in_enabled = 0 #endif @@ -787,20 +849,40 @@ static struct e100_serial rs_table[] = { .rx_ctrl = DEF_RX, .tx_ctrl = DEF_TX, .iseteop = 1, + .dma_owner = dma_ser3, + .io_if = if_serial_3, #ifdef CONFIG_ETRAX_SERIAL_PORT3 .enabled = 1, + .io_if_description = "ser3", #ifdef CONFIG_ETRAX_SERIAL_PORT3_DMA4_OUT .dma_out_enabled = 1, + .dma_out_nbr = SER3_TX_DMA_NBR, + .dma_out_irq_nbr = SER3_DMA_TX_IRQ_NBR, + .dma_out_irq_flags = SA_INTERRUPT, + .dma_out_irq_description = "serial 3 dma tr", #else .dma_out_enabled = 0, + .dma_out_nbr = UINT_MAX, + .dma_out_irq_nbr = 0, + .dma_out_irq_flags = 0, + .dma_out_irq_description = NULL, #endif #ifdef CONFIG_ETRAX_SERIAL_PORT3_DMA5_IN .dma_in_enabled = 1, + .dma_in_nbr = SER3_RX_DMA_NBR, + .dma_in_irq_nbr = SER3_DMA_RX_IRQ_NBR, + .dma_in_irq_flags = SA_INTERRUPT, + .dma_in_irq_description = "serial 3 dma rec", #else - .dma_in_enabled = 0 + .dma_in_enabled = 0, + .dma_in_nbr = UINT_MAX, + .dma_in_irq_nbr = 0, + .dma_in_irq_flags = 0, + .dma_in_irq_description = NULL #endif #else .enabled = 0, + .io_if_description = NULL, .dma_out_enabled = 0, .dma_in_enabled = 0 #endif @@ -1425,12 +1507,11 @@ e100_dtr(struct e100_serial *info, int s { unsigned long flags; - save_flags(flags); - cli(); + local_irq_save(flags); *e100_modem_pins[info->line].dtr_shadow &= ~mask; *e100_modem_pins[info->line].dtr_shadow |= (set ? 0 : mask); *e100_modem_pins[info->line].dtr_port = *e100_modem_pins[info->line].dtr_shadow; - restore_flags(flags); + local_irq_restore(flags); } #ifdef SERIAL_DEBUG_IO @@ -1449,12 +1530,11 @@ e100_rts(struct e100_serial *info, int s { #ifndef CONFIG_SVINTO_SIM unsigned long flags; - save_flags(flags); - cli(); + local_irq_save(flags); info->rx_ctrl &= ~E100_RTS_MASK; info->rx_ctrl |= (set ? 0 : E100_RTS_MASK); /* RTS is active low */ info->port[REG_REC_CTRL] = info->rx_ctrl; - restore_flags(flags); + local_irq_restore(flags); #ifdef SERIAL_DEBUG_IO printk("ser%i rts %i\n", info->line, set); #endif @@ -1472,12 +1552,11 @@ e100_ri_out(struct e100_serial *info, in unsigned char mask = e100_modem_pins[info->line].ri_mask; unsigned long flags; - save_flags(flags); - cli(); + local_irq_save(flags); *e100_modem_pins[info->line].ri_shadow &= ~mask; *e100_modem_pins[info->line].ri_shadow |= (set ? 0 : mask); *e100_modem_pins[info->line].ri_port = *e100_modem_pins[info->line].ri_shadow; - restore_flags(flags); + local_irq_restore(flags); } #endif } @@ -1490,12 +1569,11 @@ e100_cd_out(struct e100_serial *info, in unsigned char mask = e100_modem_pins[info->line].cd_mask; unsigned long flags; - save_flags(flags); - cli(); + local_irq_save(flags); *e100_modem_pins[info->line].cd_shadow &= ~mask; *e100_modem_pins[info->line].cd_shadow |= (set ? 0 : mask); *e100_modem_pins[info->line].cd_port = *e100_modem_pins[info->line].cd_shadow; - restore_flags(flags); + local_irq_restore(flags); } #endif } @@ -1572,8 +1650,7 @@ e100_disable_txdma_channel(struct e100_s /* Disable output DMA channel for the serial port in question * ( set to something other then serialX) */ - save_flags(flags); - cli(); + local_irq_save(flags); DFLOW(DEBUG_LOG(info->line, "disable_txdma_channel %i\n", info->line)); if (info->line == 0) { if ((genconfig_shadow & IO_MASK(R_GEN_CONFIG, dma6)) == @@ -1601,7 +1678,7 @@ e100_disable_txdma_channel(struct e100_s } } *R_GEN_CONFIG = genconfig_shadow; - restore_flags(flags); + local_irq_restore(flags); } @@ -1610,8 +1687,7 @@ e100_enable_txdma_channel(struct e100_se { unsigned long flags; - save_flags(flags); - cli(); + local_irq_save(flags); DFLOW(DEBUG_LOG(info->line, "enable_txdma_channel %i\n", info->line)); /* Enable output DMA channel for the serial port in question */ if (info->line == 0) { @@ -1628,7 +1704,7 @@ e100_enable_txdma_channel(struct e100_se genconfig_shadow |= IO_STATE(R_GEN_CONFIG, dma4, serial3); } *R_GEN_CONFIG = genconfig_shadow; - restore_flags(flags); + local_irq_restore(flags); } static _INLINE_ void @@ -1639,8 +1715,7 @@ e100_disable_rxdma_channel(struct e100_s /* Disable input DMA channel for the serial port in question * ( set to something other then serialX) */ - save_flags(flags); - cli(); + local_irq_save(flags); if (info->line == 0) { if ((genconfig_shadow & IO_MASK(R_GEN_CONFIG, dma7)) == IO_STATE(R_GEN_CONFIG, dma7, serial0)) { @@ -1667,7 +1742,7 @@ e100_disable_rxdma_channel(struct e100_s } } *R_GEN_CONFIG = genconfig_shadow; - restore_flags(flags); + local_irq_restore(flags); } @@ -1676,8 +1751,7 @@ e100_enable_rxdma_channel(struct e100_se { unsigned long flags; - save_flags(flags); - cli(); + local_irq_save(flags); /* Enable input DMA channel for the serial port in question */ if (info->line == 0) { genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG, dma7); @@ -1693,7 +1767,7 @@ e100_enable_rxdma_channel(struct e100_se genconfig_shadow |= IO_STATE(R_GEN_CONFIG, dma5, serial3); } *R_GEN_CONFIG = genconfig_shadow; - restore_flags(flags); + local_irq_restore(flags); } #ifdef SERIAL_HANDLE_EARLY_ERRORS @@ -1800,7 +1874,7 @@ e100_enable_rs485(struct tty_struct *tty } static int -e100_write_rs485(struct tty_struct *tty, int from_user, +e100_write_rs485(struct tty_struct *tty, const unsigned char *buf, int count) { struct e100_serial * info = (struct e100_serial *)tty->driver_data; @@ -1813,7 +1887,7 @@ e100_write_rs485(struct tty_struct *tty, */ info->rs485.enabled = 1; /* rs_write now deals with RS485 if enabled */ - count = rs_write(tty, from_user, buf, count); + count = rs_write(tty, buf, count); info->rs485.enabled = old_enabled; return count; } @@ -1851,7 +1925,7 @@ rs_stop(struct tty_struct *tty) unsigned long flags; unsigned long xoff; - save_flags(flags); cli(); + local_irq_save(flags); DFLOW(DEBUG_LOG(info->line, "XOFF rs_stop xmit %i\n", CIRC_CNT(info->xmit.head, info->xmit.tail,SERIAL_XMIT_SIZE))); @@ -1863,7 +1937,7 @@ rs_stop(struct tty_struct *tty) } *((unsigned long *)&info->port[REG_XOFF]) = xoff; - restore_flags(flags); + local_irq_restore(flags); } } @@ -1875,7 +1949,7 @@ rs_start(struct tty_struct *tty) unsigned long flags; unsigned long xoff; - save_flags(flags); cli(); + local_irq_save(flags); DFLOW(DEBUG_LOG(info->line, "XOFF rs_start xmit %i\n", CIRC_CNT(info->xmit.head, info->xmit.tail,SERIAL_XMIT_SIZE))); @@ -1890,7 +1964,7 @@ rs_start(struct tty_struct *tty) info->xmit.head != info->xmit.tail && info->xmit.buf) e100_enable_serial_tx_ready_irq(info); - restore_flags(flags); + local_irq_restore(flags); } } @@ -2072,8 +2146,7 @@ static int serial_fast_timer_expired = 0 static void flush_timeout_function(unsigned long data); #define START_FLUSH_FAST_TIMER_TIME(info, string, usec) {\ unsigned long timer_flags; \ - save_flags(timer_flags); \ - cli(); \ + local_irq_save(timer_flags); \ if (fast_timers[info->line].function == NULL) { \ serial_fast_timer_started++; \ TIMERD(DEBUG_LOG(info->line, "start_timer %i ", info->line)); \ @@ -2087,7 +2160,7 @@ static void flush_timeout_function(unsig else { \ TIMERD(DEBUG_LOG(info->line, "timer %i already running\n", info->line)); \ } \ - restore_flags(timer_flags); \ + local_irq_restore(timer_flags); \ } #define START_FLUSH_FAST_TIMER(info, string) START_FLUSH_FAST_TIMER_TIME(info, string, info->flush_time_usec) @@ -2116,8 +2189,7 @@ append_recv_buffer(struct e100_serial *i { unsigned long flags; - save_flags(flags); - cli(); + local_irq_save(flags); if (!info->first_recv_buffer) info->first_recv_buffer = buffer; @@ -2130,7 +2202,7 @@ append_recv_buffer(struct e100_serial *i if (info->recv_cnt > info->max_recv_cnt) info->max_recv_cnt = info->recv_cnt; - restore_flags(flags); + local_irq_restore(flags); } static int @@ -2517,11 +2589,10 @@ flush_to_flip_buffer(struct e100_serial if (!info->first_recv_buffer) return; - save_flags(flags); - cli(); + local_irq_save(flags); if (!(tty = info->tty)) { - restore_flags(flags); + local_irq_restore(flags); return; } @@ -2596,7 +2667,7 @@ flush_to_flip_buffer(struct e100_serial tty->flip.count); } ); - restore_flags(flags); + local_irq_restore(flags); DFLIP( if (1) { @@ -3008,7 +3079,7 @@ extern _INLINE_ void handle_ser_tx_inter if (info->x_char) { unsigned char rstat; DFLOW(DEBUG_LOG(info->line, "tx_int: xchar 0x%02X\n", info->x_char)); - save_flags(flags); cli(); + local_irq_save(flags); rstat = info->port[REG_STATUS]; DFLOW(DEBUG_LOG(info->line, "stat %x\n", rstat)); @@ -3017,7 +3088,7 @@ extern _INLINE_ void handle_ser_tx_inter info->x_char = 0; /* We must enable since it is disabled in ser_interrupt */ e100_enable_serial_tx_ready_irq(info); - restore_flags(flags); + local_irq_restore(flags); return; } if (info->uses_dma_out) { @@ -3025,7 +3096,7 @@ extern _INLINE_ void handle_ser_tx_inter int i; /* We only use normal tx interrupt when sending x_char */ DFLOW(DEBUG_LOG(info->line, "tx_int: xchar sent\n", 0)); - save_flags(flags); cli(); + local_irq_save(flags); rstat = info->port[REG_STATUS]; DFLOW(DEBUG_LOG(info->line, "stat %x\n", rstat)); e100_disable_serial_tx_ready_irq(info); @@ -3038,7 +3109,7 @@ extern _INLINE_ void handle_ser_tx_inter nop(); *info->ocmdadr = IO_STATE(R_DMA_CH6_CMD, cmd, continue); - restore_flags(flags); + local_irq_restore(flags); return; } /* Normal char-by-char interrupt */ @@ -3052,7 +3123,7 @@ extern _INLINE_ void handle_ser_tx_inter } DINTR2(DEBUG_LOG(info->line, "tx_int %c\n", info->xmit.buf[info->xmit.tail])); /* Send a byte, rs485 timing is critical so turn of ints */ - save_flags(flags); cli(); + local_irq_save(flags); info->port[REG_TR_DATA] = info->xmit.buf[info->xmit.tail]; info->xmit.tail = (info->xmit.tail + 1) & (SERIAL_XMIT_SIZE-1); info->icount.tx++; @@ -3076,7 +3147,7 @@ extern _INLINE_ void handle_ser_tx_inter /* We must enable since it is disabled in ser_interrupt */ e100_enable_serial_tx_ready_irq(info); } - restore_flags(flags); + local_irq_restore(flags); if (CIRC_CNT(info->xmit.head, info->xmit.tail, @@ -3101,7 +3172,7 @@ ser_interrupt(int irq, void *dev_id, str int handled = 0; static volatile unsigned long reentered_ready_mask = 0; - save_flags(flags); cli(); + local_irq_save(flags); irq_mask1_rd = *R_IRQ_MASK1_RD; /* First handle all rx interrupts with ints disabled */ info = rs_table; @@ -3146,7 +3217,7 @@ ser_interrupt(int irq, void *dev_id, str /* Unblock the serial interrupt */ *R_VECT_MASK_SET = IO_STATE(R_VECT_MASK_SET, serial, set); - sti(); + local_irq_enable(); ready_mask = (1 << (8+1+2*0)); /* ser0 tr_ready */ info = rs_table; for (i = 0; i < NR_PORTS; i++) { @@ -3159,11 +3230,11 @@ ser_interrupt(int irq, void *dev_id, str ready_mask <<= 2; } /* handle_ser_tx_interrupt enables tr_ready interrupts */ - cli(); + local_irq_disable(); /* Handle reentered TX interrupt */ irq_mask1_rd = reentered_ready_mask; } - cli(); + local_irq_disable(); tx_started = 0; } else { unsigned long ready_mask; @@ -3179,7 +3250,7 @@ ser_interrupt(int irq, void *dev_id, str } } - restore_flags(flags); + local_irq_restore(flags); return IRQ_RETVAL(handled); } /* ser_interrupt */ #endif @@ -3228,13 +3299,12 @@ startup(struct e100_serial * info) if (!xmit_page) return -ENOMEM; - save_flags(flags); - cli(); + local_irq_save(flags); /* if it was already initialized, skip this */ if (info->flags & ASYNC_INITIALIZED) { - restore_flags(flags); + local_irq_restore(flags); free_page(xmit_page); return 0; } @@ -3360,7 +3430,7 @@ startup(struct e100_serial * info) info->flags |= ASYNC_INITIALIZED; - restore_flags(flags); + local_irq_restore(flags); return 0; } @@ -3411,8 +3481,7 @@ shutdown(struct e100_serial * info) info->irq); #endif - save_flags(flags); - cli(); /* Disable interrupts */ + local_irq_save(flags); if (info->xmit.buf) { free_page((unsigned long)info->xmit.buf); @@ -3436,7 +3505,7 @@ shutdown(struct e100_serial * info) set_bit(TTY_IO_ERROR, &info->tty->flags); info->flags &= ~ASYNC_INITIALIZED; - restore_flags(flags); + local_irq_restore(flags); } @@ -3528,8 +3597,7 @@ change_speed(struct e100_serial *info) #ifndef CONFIG_SVINTO_SIM /* start with default settings and then fill in changes */ - save_flags(flags); - cli(); + local_irq_save(flags); /* 8 bit, no/even parity */ info->rx_ctrl &= ~(IO_MASK(R_SERIAL0_REC_CTRL, rec_bitnr) | IO_MASK(R_SERIAL0_REC_CTRL, rec_par_en) | @@ -3593,7 +3661,7 @@ change_speed(struct e100_serial *info) } *((unsigned long *)&info->port[REG_XOFF]) = xoff; - restore_flags(flags); + local_irq_restore(flags); #endif /* !CONFIG_SVINTO_SIM */ update_char_time(info); @@ -3621,14 +3689,13 @@ rs_flush_chars(struct tty_struct *tty) /* this protection might not exactly be necessary here */ - save_flags(flags); - cli(); + local_irq_save(flags); start_transmit(info); - restore_flags(flags); + local_irq_restore(flags); } extern _INLINE_ int -rs_raw_write(struct tty_struct * tty, int from_user, +rs_raw_write(struct tty_struct * tty, const unsigned char *buf, int count) { int c, ret = 0; @@ -3651,72 +3718,37 @@ rs_raw_write(struct tty_struct * tty, in SIMCOUT(buf, count); return count; #endif - save_flags(flags); + local_save_flags(flags); DFLOW(DEBUG_LOG(info->line, "write count %i ", count)); DFLOW(DEBUG_LOG(info->line, "ldisc %i\n", tty->ldisc.chars_in_buffer(tty))); - /* the cli/restore_flags pairs below are needed because the + /* the local_irq_disable/restore_flags pairs below are needed because the * DMA interrupt handler moves the info->xmit values. the memcpy * needs to be in the critical region unfortunately, because we * need to read xmit values, memcpy, write xmit values in one * atomic operation... this could perhaps be avoided by more clever * design. */ - if (from_user) { - down(&tmp_buf_sem); - while (1) { - int c1; - c = CIRC_SPACE_TO_END(info->xmit.head, - info->xmit.tail, - SERIAL_XMIT_SIZE); - if (count < c) - c = count; - if (c <= 0) - break; - - c -= copy_from_user(tmp_buf, buf, c); - if (!c) { - if (!ret) - ret = -EFAULT; - break; - } - cli(); - c1 = CIRC_SPACE_TO_END(info->xmit.head, - info->xmit.tail, - SERIAL_XMIT_SIZE); - if (c1 < c) - c = c1; - memcpy(info->xmit.buf + info->xmit.head, tmp_buf, c); - info->xmit.head = ((info->xmit.head + c) & - (SERIAL_XMIT_SIZE-1)); - restore_flags(flags); - buf += c; - count -= c; - ret += c; - } - up(&tmp_buf_sem); - } else { - cli(); - while (count) { - c = CIRC_SPACE_TO_END(info->xmit.head, - info->xmit.tail, - SERIAL_XMIT_SIZE); - - if (count < c) - c = count; - if (c <= 0) - break; - - memcpy(info->xmit.buf + info->xmit.head, buf, c); - info->xmit.head = (info->xmit.head + c) & - (SERIAL_XMIT_SIZE-1); - buf += c; - count -= c; - ret += c; - } - restore_flags(flags); + local_irq_disable(); + while (count) { + c = CIRC_SPACE_TO_END(info->xmit.head, + info->xmit.tail, + SERIAL_XMIT_SIZE); + + if (count < c) + c = count; + if (c <= 0) + break; + + memcpy(info->xmit.buf + info->xmit.head, buf, c); + info->xmit.head = (info->xmit.head + c) & + (SERIAL_XMIT_SIZE-1); + buf += c; + count -= c; + ret += c; } + local_irq_restore(flags); /* enable transmitter if not running, unless the tty is stopped * this does not need IRQ protection since if tr_running == 0 @@ -3735,7 +3767,7 @@ rs_raw_write(struct tty_struct * tty, in } /* raw_raw_write() */ static int -rs_write(struct tty_struct * tty, int from_user, +rs_write(struct tty_struct * tty, const unsigned char *buf, int count) { #if defined(CONFIG_ETRAX_RS485) @@ -3762,7 +3794,7 @@ rs_write(struct tty_struct * tty, int fr } #endif /* CONFIG_ETRAX_RS485 */ - count = rs_raw_write(tty, from_user, buf, count); + count = rs_raw_write(tty, buf, count); #if defined(CONFIG_ETRAX_RS485) if (info->rs485.enabled) @@ -3830,10 +3862,9 @@ rs_flush_buffer(struct tty_struct *tty) struct e100_serial *info = (struct e100_serial *)tty->driver_data; unsigned long flags; - save_flags(flags); - cli(); + local_irq_save(flags); info->xmit.head = info->xmit.tail = 0; - restore_flags(flags); + local_irq_restore(flags); wake_up_interruptible(&tty->write_wait); @@ -3855,7 +3886,7 @@ static void rs_send_xchar(struct tty_str { struct e100_serial *info = (struct e100_serial *)tty->driver_data; unsigned long flags; - save_flags(flags); cli(); + local_irq_save(flags); if (info->uses_dma_out) { /* Put the DMA on hold and disable the channel */ *info->ocmdadr = IO_STATE(R_DMA_CH6_CMD, cmd, hold); @@ -3872,7 +3903,7 @@ static void rs_send_xchar(struct tty_str DFLOW(DEBUG_LOG(info->line, "rs_send_xchar 0x%02X\n", ch)); info->x_char = ch; e100_enable_serial_tx_ready_irq(info); - restore_flags(flags); + local_irq_restore(flags); } /* @@ -4186,8 +4217,7 @@ rs_break(struct tty_struct *tty, int bre if (!info->port) return; - save_flags(flags); - cli(); + local_irq_save(flags); if (break_state == -1) { /* Go to manual mode and set the txd pin to 0 */ info->tx_ctrl &= 0x3F; /* Clear bit 7 (txd) and 6 (tr_enable) */ @@ -4195,7 +4225,7 @@ rs_break(struct tty_struct *tty, int bre info->tx_ctrl |= (0x80 | 0x40); /* Set bit 7 (txd) and 6 (tr_enable) */ } info->port[REG_TR_CTRL] = info->tx_ctrl; - restore_flags(flags); + local_irq_restore(flags); } static int @@ -4249,7 +4279,7 @@ rs_ioctl(struct tty_struct *tty, struct if (copy_from_user(&rs485wr, (struct rs485_write*)arg, sizeof(rs485wr))) return -EFAULT; - return e100_write_rs485(tty, 1, rs485wr.outc, rs485wr.outc_size); + return e100_write_rs485(tty, rs485wr.outc, rs485wr.outc_size); } #endif @@ -4279,46 +4309,6 @@ rs_set_termios(struct tty_struct *tty, s } -/* In debugport.c - register a console write function that uses the normal - * serial driver - */ -typedef int (*debugport_write_function)(int i, const char *buf, unsigned int len); - -extern debugport_write_function debug_write_function; - -static int rs_debug_write_function(int i, const char *buf, unsigned int len) -{ - int cnt; - int written = 0; - struct tty_struct *tty; - static int recurse_cnt = 0; - - tty = rs_table[i].tty; - if (tty) { - unsigned long flags; - if (recurse_cnt > 5) /* We skip this debug output */ - return 1; - - local_irq_save(flags); - recurse_cnt++; - local_irq_restore(flags); - do { - cnt = rs_write(tty, 0, buf + written, len); - if (cnt >= 0) { - written += cnt; - buf += cnt; - len -= cnt; - } else - len = cnt; - } while(len > 0); - local_irq_save(flags); - recurse_cnt--; - local_irq_restore(flags); - return 1; - } - return 0; -} - /* * ------------------------------------------------------------ * rs_close() @@ -4340,11 +4330,10 @@ rs_close(struct tty_struct *tty, struct /* interrupts are disabled for this entire function */ - save_flags(flags); - cli(); + local_irq_save(flags); if (tty_hung_up_p(filp)) { - restore_flags(flags); + local_irq_restore(flags); return; } @@ -4371,7 +4360,7 @@ rs_close(struct tty_struct *tty, struct info->count = 0; } if (info->count) { - restore_flags(flags); + local_irq_restore(flags); return; } info->flags |= ASYNC_CLOSING; @@ -4427,7 +4416,7 @@ rs_close(struct tty_struct *tty, struct } info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING); wake_up_interruptible(&info->close_wait); - restore_flags(flags); + local_irq_restore(flags); /* port closed */ @@ -4525,7 +4514,7 @@ block_til_ready(struct tty_struct *tty, if (tty_hung_up_p(filp) || (info->flags & ASYNC_CLOSING)) { if (info->flags & ASYNC_CLOSING) - interruptible_sleep_on(&info->close_wait); + wait_event_interruptible(info->close_wait, 0); #ifdef SERIAL_DO_RESTART if (info->flags & ASYNC_HUP_NOTIFY) return -EAGAIN; @@ -4563,21 +4552,19 @@ block_til_ready(struct tty_struct *tty, printk("block_til_ready before block: ttyS%d, count = %d\n", info->line, info->count); #endif - save_flags(flags); - cli(); + local_irq_save(flags); if (!tty_hung_up_p(filp)) { extra_count++; info->count--; } - restore_flags(flags); + local_irq_restore(flags); info->blocked_open++; while (1) { - save_flags(flags); - cli(); + local_irq_save(flags); /* assert RTS and DTR */ e100_rts(info, 1); e100_dtr(info, 1); - restore_flags(flags); + local_irq_restore(flags); set_current_state(TASK_INTERRUPTIBLE); if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)) { @@ -4629,9 +4616,9 @@ rs_open(struct tty_struct *tty, struct f struct e100_serial *info; int retval, line; unsigned long page; + int allocated_resources = 0; /* find which port we want to open */ - line = tty->index; if (line < 0 || line >= NR_PORTS) @@ -4672,7 +4659,7 @@ rs_open(struct tty_struct *tty, struct f if (tty_hung_up_p(filp) || (info->flags & ASYNC_CLOSING)) { if (info->flags & ASYNC_CLOSING) - interruptible_sleep_on(&info->close_wait); + wait_event_interruptible(info->close_wait, 0); #ifdef SERIAL_DO_RESTART return ((info->flags & ASYNC_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS); @@ -4682,12 +4669,79 @@ rs_open(struct tty_struct *tty, struct f } /* + * If DMA is enabled try to allocate the irq's. + */ + if (info->count == 1) { + allocated_resources = 1; + if (info->dma_in_enabled) { + if (request_irq(info->dma_in_irq_nbr, + rec_interrupt, + info->dma_in_irq_flags, + info->dma_in_irq_description, + info)) { + printk(KERN_WARNING "DMA irq '%s' busy; falling back to non-DMA mode\n", info->dma_in_irq_description); + /* Make sure we never try to use DMA in for the port again. */ + info->dma_in_enabled = 0; + } else if (cris_request_dma(info->dma_in_nbr, + info->dma_in_irq_description, + DMA_VERBOSE_ON_ERROR, + info->dma_owner)) { + free_irq(info->dma_in_irq_nbr, info); + printk(KERN_WARNING "DMA '%s' busy; falling back to non-DMA mode\n", info->dma_in_irq_description); + /* Make sure we never try to use DMA in for the port again. */ + info->dma_in_enabled = 0; + } +#ifdef SERIAL_DEBUG_OPEN + else printk("DMA irq '%s' allocated\n", info->dma_in_irq_description); +#endif + } + if (info->dma_out_enabled) { + if (request_irq(info->dma_out_irq_nbr, + tr_interrupt, + info->dma_out_irq_flags, + info->dma_out_irq_description, + info)) { + printk(KERN_WARNING "DMA irq '%s' busy; falling back to non-DMA mode\n", info->dma_out_irq_description); + /* Make sure we never try to use DMA out for the port again. */ + info->dma_out_enabled = 0; + } else if (cris_request_dma(info->dma_out_nbr, + info->dma_out_irq_description, + DMA_VERBOSE_ON_ERROR, + info->dma_owner)) { + free_irq(info->dma_out_irq_nbr, info); + printk(KERN_WARNING "DMA '%s' busy; falling back to non-DMA mode\n", info->dma_out_irq_description); + /* Make sure we never try to use DMA in for the port again. */ + info->dma_out_enabled = 0; + } +#ifdef SERIAL_DEBUG_OPEN + else printk("DMA irq '%s' allocated\n", info->dma_out_irq_description); +#endif + } + } + + /* * Start up the serial port */ retval = startup(info); - if (retval) - return retval; + if (retval) { + if (allocated_resources) { + if (info->dma_out_enabled) { + cris_free_dma(info->dma_out_nbr, info->dma_out_irq_description); + free_irq(info->dma_out_irq_nbr, + info); + } + if (info->dma_in_enabled) { + cris_free_dma(info->dma_in_nbr, info->dma_in_irq_description); + free_irq(info->dma_in_irq_nbr, + info); + } + } + /* FIXME Decrease count info->count here too? */ + return retval; + + } + retval = block_til_ready(tty, filp, info); if (retval) { @@ -4695,6 +4749,19 @@ rs_open(struct tty_struct *tty, struct f printk("rs_open returning after block_til_ready with %d\n", retval); #endif + if (allocated_resources) { + if (info->dma_out_enabled) { + cris_free_dma(info->dma_out_nbr, info->dma_out_irq_description); + free_irq(info->dma_out_irq_nbr, + info); + } + if (info->dma_in_enabled) { + cris_free_dma(info->dma_in_nbr, info->dma_in_irq_description); + free_irq(info->dma_in_irq_nbr, + info); + } + } + return retval; } @@ -4903,7 +4970,22 @@ rs_init(void) #if !defined(CONFIG_ETRAX_SERIAL_FAST_TIMER) init_timer(&flush_timer); flush_timer.function = timed_flush_handler; - mod_timer(&flush_timer, jiffies + CONFIG_ETRAX_SERIAL_RX_TIMEOUT_TICKS); + mod_timer(&flush_timer, jiffies + 5); +#endif + +#if defined(CONFIG_ETRAX_RS485) +#if defined(CONFIG_ETRAX_RS485_ON_PA) + if (cris_io_interface_allocate_pins(if_ser0, 'a', rs485_pa_bit, rs485_pa_bit)) { + printk(KERN_CRIT "ETRAX100LX serial: Could not allocate RS485 pin\n"); + return -EBUSY; + } +#endif +#if defined(CONFIG_ETRAX_RS485_ON_PORT_G) + if (cris_io_interface_allocate_pins(if_ser0, 'g', rs485_pa_bit, rs485_port_g_bit)) { + printk(KERN_CRIT "ETRAX100LX serial: Could not allocate RS485 pin\n"); + return -EBUSY; + } +#endif #endif /* Initialize the tty_driver structure */ @@ -4928,6 +5010,14 @@ rs_init(void) /* do some initializing for the separate ports */ for (i = 0, info = rs_table; i < NR_PORTS; i++,info++) { + if (info->enabled) { + if (cris_request_io_interface(info->io_if, info->io_if_description)) { + printk(KERN_CRIT "ETRAX100LX async serial: Could not allocate IO pins for %s, port %d\n", + info->io_if_description, + i); + info->enabled = 0; + } + } info->uses_dma_in = 0; info->uses_dma_out = 0; info->line = i; @@ -4979,64 +5069,16 @@ rs_init(void) #endif #ifndef CONFIG_SVINTO_SIM +#ifndef CONFIG_ETRAX_KGDB /* Not needed in simulator. May only complicate stuff. */ /* hook the irq's for DMA channel 6 and 7, serial output and input, and some more... */ - if (request_irq(SERIAL_IRQ_NBR, ser_interrupt, SA_SHIRQ | SA_INTERRUPT, "serial ", NULL)) + if (request_irq(SERIAL_IRQ_NBR, ser_interrupt, SA_SHIRQ | SA_INTERRUPT, "serial ", rs_table)) panic("irq8"); -#ifdef CONFIG_ETRAX_SERIAL_PORT0 -#ifdef CONFIG_ETRAX_SERIAL_PORT0_DMA6_OUT - if (request_irq(SER0_DMA_TX_IRQ_NBR, tr_interrupt, SA_INTERRUPT, "serial 0 dma tr", NULL)) - panic("irq22"); -#endif -#ifdef CONFIG_ETRAX_SERIAL_PORT0_DMA7_IN - if (request_irq(SER0_DMA_RX_IRQ_NBR, rec_interrupt, SA_INTERRUPT, "serial 0 dma rec", NULL)) - panic("irq23"); -#endif -#endif - -#ifdef CONFIG_ETRAX_SERIAL_PORT1 -#ifdef CONFIG_ETRAX_SERIAL_PORT1_DMA8_OUT - if (request_irq(SER1_DMA_TX_IRQ_NBR, tr_interrupt, SA_INTERRUPT, "serial 1 dma tr", NULL)) - panic("irq24"); -#endif -#ifdef CONFIG_ETRAX_SERIAL_PORT1_DMA9_IN - if (request_irq(SER1_DMA_RX_IRQ_NBR, rec_interrupt, SA_INTERRUPT, "serial 1 dma rec", NULL)) - panic("irq25"); -#endif -#endif -#ifdef CONFIG_ETRAX_SERIAL_PORT2 - /* DMA Shared with par0 (and SCSI0 and ATA) */ -#ifdef CONFIG_ETRAX_SERIAL_PORT2_DMA2_OUT - if (request_irq(SER2_DMA_TX_IRQ_NBR, tr_interrupt, SA_SHIRQ | SA_INTERRUPT, "serial 2 dma tr", NULL)) - panic("irq18"); -#endif -#ifdef CONFIG_ETRAX_SERIAL_PORT2_DMA3_IN - if (request_irq(SER2_DMA_RX_IRQ_NBR, rec_interrupt, SA_SHIRQ | SA_INTERRUPT, "serial 2 dma rec", NULL)) - panic("irq19"); -#endif -#endif -#ifdef CONFIG_ETRAX_SERIAL_PORT3 - /* DMA Shared with par1 (and SCSI1 and Extern DMA 0) */ -#ifdef CONFIG_ETRAX_SERIAL_PORT3_DMA4_OUT - if (request_irq(SER3_DMA_TX_IRQ_NBR, tr_interrupt, SA_SHIRQ | SA_INTERRUPT, "serial 3 dma tr", NULL)) - panic("irq20"); -#endif -#ifdef CONFIG_ETRAX_SERIAL_PORT3_DMA5_IN - if (request_irq(SER3_DMA_RX_IRQ_NBR, rec_interrupt, SA_SHIRQ | SA_INTERRUPT, "serial 3 dma rec", NULL)) - panic("irq21"); -#endif -#endif - -#ifdef CONFIG_ETRAX_SERIAL_FLUSH_DMA_FAST - if (request_irq(TIMER1_IRQ_NBR, timeout_interrupt, SA_SHIRQ | SA_INTERRUPT, - "fast serial dma timeout", NULL)) { - printk(KERN_CRIT "err: timer1 irq\n"); - } #endif #endif /* CONFIG_SVINTO_SIM */ - debug_write_function = rs_debug_write_function; + return 0; } diff -puN drivers/serial/crisv10.h~cris-update-12-17-serial-port-driver drivers/serial/crisv10.h --- 25/drivers/serial/crisv10.h~cris-update-12-17-serial-port-driver 2005-06-27 18:32:02.000000000 -0700 +++ 25-akpm/drivers/serial/crisv10.h 2005-06-27 18:32:02.000000000 -0700 @@ -10,6 +10,8 @@ #include #include #include +#include +#include /* Software state per channel */ @@ -62,6 +64,19 @@ struct e100_serial { u8 dma_in_enabled:1; /* Set to 1 if DMA should be used */ /* end of fields defined in rs_table[] in .c-file */ + int dma_owner; + unsigned int dma_in_nbr; + unsigned int dma_out_nbr; + unsigned int dma_in_irq_nbr; + unsigned int dma_out_irq_nbr; + unsigned long dma_in_irq_flags; + unsigned long dma_out_irq_flags; + char *dma_in_irq_description; + char *dma_out_irq_description; + + enum cris_io_interface io_if; + char *io_if_description; + u8 uses_dma_in; /* Set to 1 if DMA is used */ u8 uses_dma_out; /* Set to 1 if DMA is used */ u8 forced_eop; /* a fifo eop has been forced */ diff -puN /dev/null drivers/serial/crisv32.c --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/drivers/serial/crisv32.c 2005-06-27 18:32:02.000000000 -0700 @@ -0,0 +1,3568 @@ +/* $Id: crisv32.c,v 1.50 2005/06/19 17:09:11 starvik Exp $ + * + * Serial port driver for the ETRAX FS chip + * + * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 Axis Communications AB + * + * Many, many authors. Based once upon a time on serial.c for 16x50. + * + * Johan Adolfsson - port to ETRAX FS + * $Log: crisv32.c,v $ + * Revision 1.50 2005/06/19 17:09:11 starvik + * Merge of Linux 2.6.12. + * + * Revision 1.49 2005/06/10 10:22:04 starvik + * The debug_write_function hack is not needed anymore. + * + * Revision 1.48 2005/05/03 09:36:03 starvik + * Mark transmitter as stopped in rs_close. This makes it possbile send + * data after port has been closed and reopened. + * + * Revision 1.47 2005/04/26 09:37:55 starvik + * Make it compile again. + * + * Revision 1.46 2005/04/24 18:43:16 starvik + * Updated with latest register headers. + * + * Revision 1.45 2005/04/15 09:30:43 starvik + * Corrected DMA receiver path. + * Removed warnings. + * + * Revision 1.44 2005/04/15 08:35:53 starvik + * Use a small list of transmit descriptors because there is no way to tell + * the DMA to issue the descriptor interrupt after the characters have been + * drained from the FIFO. + * + * Revision 1.43 2005/04/14 12:11:22 starvik + * Removed garbage on serial console: + * Only set registers in change_speed if value has been modified. + * Don't disable serial port in change_speed. + * + * Revision 1.42 2005/04/08 11:03:11 starvik + * Removed missleading comment. + * + * Revision 1.41 2005/04/08 10:51:26 starvik + * Some corrections in DMA transmit code to make it work on real silicon. + * + * Revision 1.40 2005/04/04 13:55:54 orjanf + * * Added check for missing tty in info struct in receive_chars_no_dma + * (corresponding check already exists in other functions). + * + * Revision 1.39 2005/01/26 07:14:19 starvik + * Applied diff from kernel janitors (Nish Aravamudan). + * + * Revision 1.38 2005/01/05 06:13:03 starvik + * local_save_flags -> local_irq_save + * + * Revision 1.37 2005/01/04 12:10:13 starvik + * Serial driver moved here. Modified include. + * + * Revision 1.36 2005/01/04 11:15:18 starvik + * Modified arguments to rs_write and friends. + * + * Revision 1.35 2004/11/16 10:17:54 starvik + * Use the new GPIO helper functions to reduce configuration complexity. + * + * Revision 1.34 2004/09/29 10:33:51 starvik + * Resolved a dealock when printing debug from kernel. + * + * Revision 1.33 2004/09/27 07:41:45 starvik + * Let the DMA allocator set up the strmux + * + * Revision 1.32 2004/09/27 06:18:43 starvik + * Use pin allocator + * + * Revision 1.31 2004/08/27 23:46:31 johana + * rs_set_termios() must call change_speed() if c_iflag has changed or + * automatic XOFF handling will not be configured correctly. + * + * Revision 1.30 2004/06/08 08:05:36 starvik + * ETRAX 200 -> ETRAX FS + * + * Revision 1.28 2004/05/14 10:54:21 orjanf + * * Fixed a couple of typos. + * + * Revision 1.27 2004/05/14 07:58:02 starvik + * Merge of changes from 2.4 + * + * Revision 1.26 2004/01/13 12:14:12 starvik + * Don't set EOP in out channel + * + * Revision 1.25 2004/01/09 12:48:09 starvik + * The previous change was completely bogus. The IRQ must be acked after + * the write and there is no race condition (both because the tr_ready + * IRQ is issued continously while the transmitter is ready). + * + * Revision 1.24 2004/01/07 14:49:50 starvik + * Ack IRQ before sending data to avoid potential race + * + * Revision 1.23 2003/10/07 08:19:12 starvik + * Enable tx data IRQ when waiting for data to be sent. + * Report IRQ as handled for tr_ready IRQ. + * Implemented modem_pins for ser2 and ser3. + * + * Revision 1.22 2003/09/30 13:23:33 johana + * Added options argument to crisv32_request_dma() to be verbose and + * possibly panic. + * Store user in dma.c and assign variables when it is safe. + * + * Revision 1.21 2003/09/30 12:11:21 johana + * Removed PORT0_DMAs that doesn't exist anymore. + * Corrected and added missing crisv32_request_dma(). + * + * Revision 1.20 2003/09/30 11:49:06 starvik + * Allocated DMA channels + * + * Revision 1.19 2003/09/30 09:29:33 johana + * Serial DMA channels moved and not that flexible anymore. + * Added DMA channels for ser2 and ser3. + * Fixed typo in etraxfs_write_rs485. + * Added rs_debug_write_function and register it with debugport.c + * + * Revision 1.18 2003/09/25 14:10:34 starvik + * Updated for latest serial port implementation + * + * Revision 1.17 2003/09/22 08:49:24 johana + * Corrected mark/space parity. PARODD gives mark. + * + * Revision 1.16 2003/09/22 08:43:26 johana + * Compile with debug enabled. + * + * Revision 1.15 2003/09/11 13:44:03 starvik + * Don't reset DMAs after they have been started (reset clears all settings). + * Flush received characters to flip buffer. + * + * Revision 1.14 2003/09/08 05:28:56 starvik + * Corrected DMA context handling. + * Enable DMA after each reset. + * + * Revision 1.13 2003/09/01 07:05:56 starvik + * Updated for latest DMA + * + * Revision 1.12 2003/08/18 08:12:05 johana + * Clear tr_running when stopping transmission. + * + * Revision 1.11 2003/07/10 13:25:46 starvik + * Compiles for 2.5.74 + * Lindented ethernet.c + * + * Revision 1.10 2003/07/04 08:27:46 starvik + * Merge of Linux 2.5.74 + * + * Revision 1.9 2003/06/10 12:38:09 johana + * Adapted to new register description. + * r_status -> r_status_data, + * rs_data_in -> rs_status_data. + * Unified variable naming to rstat. + * Removed errorcode. + * + * Revision 1.8 2003/06/10 08:56:10 johana + * Use stop field to stop/start transmission instead of enable/disable port. + * + * Revision 1.7 2003/06/10 08:28:38 johana + * Added ack_intr. + * Added missing status read. + * Read masked_intr before using it. + * + * Revision 1.6 2003/06/06 12:48:11 johana + * Check masked_intr instead of status register in ser_interrupt. + * Disable/enable tr_ready interrupt in rs_break if dmaout isn't used. + * + * Revision 1.5 2003/06/06 12:03:21 johana + * local_irq_disable() not needed after local_irq_save(flags). + * Implemented enable/disable of dma interrupt. + * Corrected rs_break() so it handles if transmission is in progress. + * + * Revision 1.4 2003/06/05 14:52:08 johana + * Support for CONFIG_ETRAX_RS485_DISABLE_RECEIVER + * + * Revision 1.3 2003/06/04 12:45:27 johana + * Corrected DMA channel usage for serial ports. + * Make them configurable. + * + * Revision 1.2 2003/06/04 10:36:39 johana + * Initial CRIS v32 version. + * Timer stuff removed (DMA has timeout). + * Support separate input baudrate using CIBAUD flag. + * Removed support for RS485_ON_PA. + * Using hardware auto_rts support for RS485. + * DMA usage is configurable for in and out seperatly. + * save_flags(flags) -> local_irq_save(flags), + * cli() -> local_irq_disable(), + * restore_flags(flags) -> local_irq_restore(flags). + * + * Revision 1.1 2003/06/04 08:08:17 johana + * Copy of the arch-v10 driver version 1.14. + */ + +static char *serial_version = "$Revision: 1.50 $"; + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* non-arch dependent serial structures are in linux/serial.h */ +#include + +/* while we keep our own stuff (struct etraxfs_serial) in a local .h file */ +#include "crisv32.h" +#include +#include +#include +#include +#include +#include +#include +#include + + + +/* + * All of the compatibilty code so we can compile serial.c against + * older kernels is hidden in serial_compat.h + */ +#if defined(LOCAL_HEADERS) +#include "serial_compat.h" +#endif + +#define DMA_WAIT_UNTIL_RESET(inst) do { reg_dma_rw_stat r; do { r = REG_RD(dma, (inst), rw_stat); }while(r.mode != regk_dma_rst); }while(0) + +#define _INLINE_ inline + +struct tty_driver *serial_driver; + +/* serial subtype definitions */ +#ifndef SERIAL_TYPE_NORMAL +#define SERIAL_TYPE_NORMAL 1 +#endif + +#define ETRAX_SER_FIFO_SIZE 1 /* Or perhaps 2 */ + +/* number of characters left in xmit buffer before we ask for more */ +#define WAKEUP_CHARS 256 + +//#define SERIAL_DEBUG_INTR +//#define SERIAL_DEBUG_OPEN +//#define SERIAL_DEBUG_FLOW +//#define SERIAL_DEBUG_DATA +//#define SERIAL_DEBUG_THROTTLE +//#define SERIAL_DEBUG_IO /* Debug for Extra control and status pins */ +#define SERIAL_DEBUG_LINE 0 /* What serport we want to debug */ + +#define DEBUG_BAUD(x) do { x; }while(0) + +#define TTY_THROTTLE_LIMIT (TTY_FLIPBUF_SIZE/10) + +#define SERIAL_DESCR_BUF_SIZE 256 + + +#if 0 +#define DEBUG_LOG(line, string, value) printk(string, value) +#else +#define DEBUG_LOG(line, string, value) +#endif + +/* Macro to set up control lines for a port. */ +#define SETUP_PINS(port) \ + if (strcmp(CONFIG_ETRAX_SER##port##_DTR_BIT, "")) \ + crisv32_io_get_name(&rs_table[port].dtr_pin, CONFIG_ETRAX_SER##port##_DTR_BIT); \ + else \ + rs_table[port].dtr_pin = dummy_pin; \ + if (strcmp(CONFIG_ETRAX_SER##port##_DSR_BIT, "")) \ + crisv32_io_get_name(&rs_table[port].dsr_pin, CONFIG_ETRAX_SER##port##_DSR_BIT); \ + else \ + rs_table[port].dsr_pin = dummy_pin; \ + if (strcmp(CONFIG_ETRAX_SER##port##_RI_BIT, "")) \ + crisv32_io_get_name(&rs_table[port].ri_pin, CONFIG_ETRAX_SER##port##_RI_BIT); \ + else \ + rs_table[port].ri_pin = dummy_pin; \ + if (strcmp(CONFIG_ETRAX_SER##port##_CD_BIT, "")) \ + crisv32_io_get_name(&rs_table[port].cd_pin, CONFIG_ETRAX_SER##port##_CD_BIT); \ + else \ + rs_table[port].cd_pin = dummy_pin; + +static void change_speed(struct etraxfs_serial *info); +static void rs_wait_until_sent(struct tty_struct *tty, int timeout); +static int rs_write(struct tty_struct * tty, + const unsigned char *buf, int count); +static inline int raw_write(struct tty_struct * tty, + const unsigned char *buf, int count); +#ifdef CONFIG_ETRAX_RS485 +static int etraxfs_write_rs485(struct tty_struct * tty, + const unsigned char *buf, int count); +#endif +static int get_lsr_info(struct etraxfs_serial * info, unsigned int *value); +static void flush_to_flip_buffer(struct etraxfs_serial *info); + +#define STD_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST) + + +/* Dummy pin used for unused CD, DSR, DTR and RI signals */ +static unsigned long io_dummy; +static struct crisv32_ioport dummy_port = +{ + &io_dummy, + &io_dummy, + &io_dummy, + 18 +}; +static struct crisv32_iopin dummy_pin = +{ + &dummy_port, + 0 +}; + +/* this is the data for the two fixed serial ports in the ETRAX FS */ +/* ser0 can use dma4 or dma6 for tx and dma5 or dma7 for rx */ +/* ser1 can use dma2 for tx and dma3 for rx */ + +#define regi_NULL 0 + +static struct etraxfs_serial rs_table[] = { +{ +#ifdef CONFIG_ETRAX_SERIAL_PORT0 + .regi_ser = regi_ser0, + /* We initilise the dma stuff like this to get a compiler error + * if a CONFIG is missing + */ + .regi_dmain = +# ifdef CONFIG_ETRAX_SERIAL_PORT0_DMA7_IN + regi_dma7, +# endif +# ifdef CONFIG_ETRAX_SERIAL_PORT0_NO_DMA_IN + regi_NULL, +# endif + + .regi_dmaout = +# ifdef CONFIG_ETRAX_SERIAL_PORT0_DMA6_OUT + regi_dma6, +# endif +# ifdef CONFIG_ETRAX_SERIAL_PORT0_NO_DMA_OUT + regi_NULL, +# endif +#else + .regi_ser = regi_NULL, + .regi_dmain = regi_NULL, + .regi_dmaout = regi_NULL, +#endif + + .flags = STD_FLAGS, +}, /* ttyS0 */ +{ +#ifdef CONFIG_ETRAX_SERIAL_PORT1 + .regi_ser = regi_ser1, + .regi_dmain = +# ifdef CONFIG_ETRAX_SERIAL_PORT1_DMA5_IN + regi_dma5, +# endif +# ifdef CONFIG_ETRAX_SERIAL_PORT1_NO_DMA_IN + regi_NULL, +# endif + + .regi_dmaout = +# ifdef CONFIG_ETRAX_SERIAL_PORT1_DMA4_OUT + regi_dma4, +# endif +# ifdef CONFIG_ETRAX_SERIAL_PORT1_NO_DMA_OUT + regi_NULL, +# endif +#else + .regi_ser = regi_NULL, + .regi_dmain = regi_NULL, + .regi_dmaout = regi_NULL, +#endif + .flags = STD_FLAGS, +}, /* ttyS1 */ +{ +#ifdef CONFIG_ETRAX_SERIAL_PORT2 + .regi_ser = regi_ser2, + .regi_dmain = +# ifdef CONFIG_ETRAX_SERIAL_PORT2_DMA3_IN + regi_dma3, +# endif +# ifdef CONFIG_ETRAX_SERIAL_PORT2_NO_DMA_IN + regi_NULL, +# endif + + .regi_dmaout = +# ifdef CONFIG_ETRAX_SERIAL_PORT2_DMA2_OUT + regi_dma2, +# endif +# ifdef CONFIG_ETRAX_SERIAL_PORT2_NO_DMA_OUT + regi_NULL, +# endif +#else + .regi_ser = regi_NULL, + .regi_dmain = regi_NULL, + .regi_dmaout = regi_NULL, +#endif + .flags = STD_FLAGS, +}, /* ttyS2 */ +{ +#ifdef CONFIG_ETRAX_SERIAL_PORT3 + .regi_ser = regi_ser3, + .regi_dmain = +# ifdef CONFIG_ETRAX_SERIAL_PORT3_DMA9_IN + regi_dma9, +# endif +# ifdef CONFIG_ETRAX_SERIAL_PORT3_NO_DMA_IN + regi_NULL, +# endif + + .regi_dmaout = +# ifdef CONFIG_ETRAX_SERIAL_PORT3_DMA8_OUT + regi_dma8, +# endif +# ifdef CONFIG_ETRAX_SERIAL_PORT3_NO_DMA_OUT + regi_NULL, +# endif +#else + .regi_ser = regi_NULL, + .regi_dmain = regi_NULL, + .regi_dmaout = regi_NULL, +#endif + .flags = STD_FLAGS, +}, /* ttyS3 */ +}; + + + +#define NR_PORTS (sizeof(rs_table)/sizeof(struct etraxfs_serial)) + +#ifdef CONFIG_ETRAX_SERIAL_PROC_ENTRY +#define PROCSTAT(x) x +struct ser_statistics_type { + int overrun_cnt; + int early_errors_cnt; + int ser_ints_ok_cnt; + int errors_cnt; + unsigned long int processing_flip; + unsigned long processing_flip_still_room; + unsigned long int timeout_flush_cnt; + int rx_dma_ints; + int tx_dma_ints; + int rx_tot; + int tx_tot; +}; + +static struct ser_statistics_type ser_stat[NR_PORTS]; + +#else + +#define PROCSTAT(x) + +#endif /* CONFIG_ETRAX_SERIAL_PROC_ENTRY */ + +/* All serial port signals are active low: + * active = 0 -> 3.3V to RS-232 driver -> -12V on RS-232 level + * inactive = 1 -> 0V to RS-232 driver -> +12V on RS-232 level + * + * These macros returns the pin value: 0=0V, >=1 = 3.3V on ETRAX chip + */ + +/* Output */ +#define ETRAXFS_RTS_GET(info) ser_hw_rts_get(info) +static int inline ser_hw_rts_get(struct etraxfs_serial * info) +{ + reg_scope_instances regi_ser = info->regi_ser; + if (regi_ser) { + /* Return what the user has controlled rts to or + * what the pin is? (if auto_rts is used it differs during tx) + */ + reg_ser_r_stat_din rstat; + rstat = REG_RD(ser, regi_ser, r_stat_din); + return !(rstat.rts_n == regk_ser_active); + } + return 1; +} + +/* Input */ +#define ETRAXFS_CTS_GET(info) ser_hw_cts_get(info) +static int inline ser_hw_cts_get(struct etraxfs_serial * info) +{ + reg_scope_instances regi_ser = info->regi_ser; + if (regi_ser) { + reg_ser_r_stat_din rstat; + rstat = REG_RD(ser, regi_ser, r_stat_din); + return !(rstat.cts_n == regk_ser_active); + } + return 1; +} + +/* 0 means 0V, 1 means 3.3V */ +/* Is an output - read input anyway */ +#define ETRAXFS_DTR_GET(info) crisv32_io_rd(&info->dtr_pin) + +/* Normally inputs */ +#define ETRAXFS_RI_GET(info) crisv32_io_rd(&info->ri_pin) +#define ETRAXFS_CD_GET(info) crisv32_io_rd(&info->cd_pin) + +/* Input */ +#define ETRAXFS_DSR_GET(info) crisv32_io_rd(&info->dsr_pin) + +/* + * tmp_buf is used as a temporary buffer by serial_write. We need to + * lock it in case the memcpy_fromfs blocks while swapping in a page, + * and some other program tries to do a serial write at the same time. + * Since the lock will only come under contention when the system is + * swapping and available memory is low, it makes sense to share one + * buffer across all the serial ports, since it significantly saves + * memory if large numbers of serial ports are open. + */ +static unsigned char *tmp_buf; +#ifdef DECLARE_MUTEX +static DECLARE_MUTEX(tmp_buf_sem); +#else +static struct semaphore tmp_buf_sem = MUTEX; +#endif + +/* Calculate the chartime depending on baudrate, numbor of bits etc. */ +static void update_char_time(struct etraxfs_serial * info) +{ + tcflag_t cflags = info->tty->termios->c_cflag; + int bits; + + /* calc. number of bits / data byte */ + /* databits + startbit and 1 stopbit */ + if ((cflags & CSIZE) == CS7) + bits = 9; + else + bits = 10; + + if (cflags & CSTOPB) /* 2 stopbits ? */ + bits++; + + if (cflags & PARENB) /* parity bit ? */ + bits++; + + /* calc timeout */ + info->char_time_usec = ((bits * 1000000) / info->obaud) + 1; +} + +/* + * This function maps from the Bxxxx defines in asm/termbits.h into real + * baud rates. + */ + +static int +cflag_to_baud(unsigned int cflag) +{ + static const int baud_table[] = { + 0, 0, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, + 4800, 9600, 19200, 38400 }; + + static const int ext_baud_table[] = { + 0, 57600, 115200, 230400, 460800, 921600, 1843200, 6250000, + 12500000, 0, 0, 0, 0, 0, 0, 0 }; + + if (cflag & CBAUDEX) + return ext_baud_table[(cflag & CBAUD) & ~CBAUDEX]; + else + return baud_table[cflag & CBAUD]; +} + +/* and this maps to an ETRAX FS hardware baud divisor */ +#define F29_493 29493000 +#define BAUD_DIV(baud) ((F29_493/8)/(baud)) +#define BAUD_DIV100(baud) ((100000000/8)/(baud)) + + +static const unsigned short baud_div_table[] = { + 0, 0, BAUD_DIV(75), BAUD_DIV(110), + BAUD_DIV(134), BAUD_DIV(150), BAUD_DIV(200), BAUD_DIV(300), + BAUD_DIV(600), BAUD_DIV(1200), BAUD_DIV(1800), BAUD_DIV(2400), + BAUD_DIV(4800), BAUD_DIV(9600), BAUD_DIV(19200), BAUD_DIV(38400) +}; + +static const unsigned short ext_baud_div_table[] = { + 0, BAUD_DIV(57600), BAUD_DIV(115200), BAUD_DIV(230400), + BAUD_DIV(460800), BAUD_DIV(921600), BAUD_DIV(1843200), BAUD_DIV100(6250000), + BAUD_DIV100(12500000), 0, 0, 0, + 0, 0, 0, 0, +}; + +static unsigned short +cflag_to_etrax_divisor(unsigned int cflag, int *base_freq_p) +{ + unsigned short retval; + + *base_freq_p = regk_ser_f29_493; + if (cflag & CBAUDEX) { + int ix = (cflag & CBAUD) & ~CBAUDEX; + + retval = ext_baud_div_table[ix]; + if (ix == 7 || ix == 8) + *base_freq_p = regk_ser_f100; + } else { + retval = baud_div_table[cflag & CBAUD]; + } + + + if (retval == 0) { + printk("serdriver tried setting invalid baud rate, flags %x.\n", cflag); + retval = BAUD_DIV(9600); /* choose default 9600 instead */ + } + return retval; +} + +#if 0 +/* returns best divisor and base_freq (in regk_ser_fxxx) for an + input baudrate */ +static int +special_baud_to_etrax_registers(struct etraxfs_serial *info, + int baud, + int *base_freq_p) +{ + int base_freq; + unsigned int baud_res; + int baud_diff, best_baud_diff; + int divisor, best_divisor; + + + + /* baud_clock = base_freq / (divisor*8) + * divisor = base_freq / (baud_clock * 8) + * base_freq is either: + * off, ext, 29.493MHz, 32.000 MHz, 32.768 MHz or 100 MHz + * 29.493MHz is used for standard baudrates + */ + if (baud > 32768000/8) { + divisor = (100000000/8)/baud; + baud_res = (100000000/8)/divisor; + DEBUG_BAUD(printk("100.000MHz baud %i divisor %i baud_res %i diff %i\n", baud, divisor, baud_res, baud_res-baud)); + *base_freq_p = regk_ser_f100; + return divisor; + } else { + + divisor = (29493000/8)/baud; + baud_res = (29493000/8)/divisor; + DEBUG_BAUD(printk(" 29.493MHz baud %i divisor %i baud_res %i diff %i\n", baud, divisor, baud_res, baud_res-baud)); + if (divisor >65535) { + DEBUG_BAUD(printk("baudrate to low!\n")); + } + + baud_diff = baud_res-baud; + if (baud_diff == 0) { + *base_freq_p = regk_ser_f29_493; + return divisor; + } + best_divisor = divisor; + best_baud_diff = baud_diff; + base_freq = regk_ser_f29_493; + + divisor = (32000000/8)/baud; + baud_res = (32000000/8)/divisor; + DEBUG_BAUD(printk(" 32.000MHz baud %i divisor %i baud_res %i diff %i\n", baud, divisor, baud_res, baud_res-baud)); + if (baud_diff == 0) { + *base_freq_p = regk_ser_f32; + return divisor; + } + if (baud_diff < best_baud_diff) { + best_divisor = divisor; + best_baud_diff = baud_diff; + base_freq = regk_ser_f32; + } + + divisor = (32768000/8)/baud; + baud_res = (32768000/8)/divisor; + DEBUG_BAUD(printk(" 32.768MHz baud %i divisor %i baud_res %i diff %i\n", baud, divisor, baud_res, baud_res-baud)); + if (baud_diff == 0) { + *base_freq_p = regk_ser_f32_768; + return divisor; + } + if (baud_diff < best_baud_diff) { + best_divisor = divisor; + best_baud_diff = baud_diff; + base_freq = regk_ser_f32_768; + } + + divisor = (100000000/8)/baud; + baud_res = (100000000/8)/divisor; + DEBUG_BAUD(printk("100.000MHz baud %i divisor %i baud_res %i diff %i\n", baud, divisor, baud_res, baud_res-baud)); + if (baud_diff == 0) { + *base_freq_p = regk_ser_f100; + return divisor; + } + if (baud_diff < best_baud_diff) { + best_divisor = divisor; + best_baud_diff = baud_diff; + base_freq = regk_ser_f100; + } + *base_freq_p = base_freq; + return best_divisor; + } +} +#endif + +/* Various static support functions */ + +/* Functions to set or clear DTR/RTS on the requested line */ +/* It is complicated by the fact that RTS is a serial port register, while + * DTR might not be implemented in the HW at all, and if it is, it can be on + * any general port. + */ + + +static inline void +etraxfs_dtr(struct etraxfs_serial *info, int set) +{ +#ifdef SERIAL_DEBUG_IO + printk("ser%i dtr %i\n", info->line, set); +#endif + /* DTR is active low */ + { + unsigned long flags; + + local_irq_save(flags); + crisv32_io_set(&info->dtr_pin, set ? 0 : 1); + local_irq_restore(flags); + } +} + +/* set = 0 means 3.3V on the pin, bitvalue: 0=active, 1=inactive + * 0=0V , 1=3.3V + */ +static inline void +etraxfs_rts(struct etraxfs_serial *info, int set) +{ + reg_scope_instances regi_ser = info->regi_ser; + if (regi_ser) { + unsigned long flags; + reg_ser_rw_rec_ctrl rec_ctrl; + local_irq_save(flags); + rec_ctrl = REG_RD(ser, regi_ser, rw_rec_ctrl); + if (set) + rec_ctrl.rts_n = regk_ser_active; + else + rec_ctrl.rts_n = regk_ser_inactive; + REG_WR(ser, regi_ser, rw_rec_ctrl, rec_ctrl); + local_irq_restore(flags); + } + +#ifdef SERIAL_DEBUG_IO + printk("ser%i rts %i\n", info->line, set); +#endif +} + + +/* If this behaves as a modem, RI and CD is an output */ +static inline void +etraxfs_ri_out(struct etraxfs_serial *info, int set) +{ + /* RI is active low */ + { + unsigned long flags; + + local_irq_save(flags); + crisv32_io_set(&info->ri_pin, set ? 0 : 1); + local_irq_restore(flags); + } +} +static inline void +etraxfs_cd_out(struct etraxfs_serial *info, int set) +{ + /* CD is active low */ + { + unsigned long flags; + + local_irq_save(flags); + crisv32_io_set(&info->cd_pin, set ? 0 : 1); + local_irq_restore(flags); + } +} + +static inline void +etraxfs_disable_rx(struct etraxfs_serial *info) +{ + /* disable the receiver */ + reg_scope_instances regi_ser = info->regi_ser; + if (regi_ser) { + unsigned long flags; + reg_ser_rw_rec_ctrl rec_ctrl; + local_irq_save(flags); + rec_ctrl = REG_RD(ser, regi_ser, rw_rec_ctrl); + rec_ctrl.en = regk_ser_no; + REG_WR(ser, regi_ser, rw_rec_ctrl, rec_ctrl); + local_irq_restore(flags); + } +} + +static inline void +etraxfs_enable_rx(struct etraxfs_serial *info) +{ + /* enable the receiver */ + reg_scope_instances regi_ser = info->regi_ser; + if (regi_ser) { + unsigned long flags; + reg_ser_rw_rec_ctrl rec_ctrl; + local_irq_save(flags); + rec_ctrl = REG_RD(ser, regi_ser, rw_rec_ctrl); + rec_ctrl.en = regk_ser_yes; + REG_WR(ser, regi_ser, rw_rec_ctrl, rec_ctrl); + local_irq_restore(flags); + } +} + +/* the rx DMA uses both the dma_descr and the dma_eop interrupts */ + +static inline void +etraxfs_disable_rxdma_irq(struct etraxfs_serial *info) +{ + reg_scope_instances regi_dmain = info->regi_dmain; +#ifdef SERIAL_DEBUG_INTR + printk("rxdma_irq(%d): 0\n",info->line); +#endif + if (regi_dmain) { + reg_dma_rw_intr_mask intr_mask = { 0 }; + REG_WR(dma, regi_dmain, rw_intr_mask, intr_mask); + } +} + +static inline void +etraxfs_enable_rxdma_irq(struct etraxfs_serial *info) +{ + reg_scope_instances regi_dmain = info->regi_dmain; +#ifdef SERIAL_DEBUG_INTR + printk("rxdma_irq(%d): 1\n",info->line); +#endif + if (regi_dmain) { + reg_dma_rw_intr_mask intr_mask = { 0 }; + intr_mask.data = regk_dma_yes; + intr_mask.in_eop = regk_dma_yes; + REG_WR(dma, regi_dmain, rw_intr_mask, intr_mask); + } +} + +/* the tx DMA uses only dma_descr interrupt */ + +static inline void +etraxfs_disable_txdma_irq(struct etraxfs_serial *info) +{ + reg_scope_instances regi_dmaout = info->regi_dmaout; +#ifdef SERIAL_DEBUG_INTR + printk("txdma_irq(%d): 0\n",info->line); +#endif + if (regi_dmaout) { + reg_dma_rw_intr_mask intr_mask = { 0 }; + REG_WR(dma, regi_dmaout, rw_intr_mask, intr_mask); + } +} + +static inline void +etraxfs_enable_txdma_irq(struct etraxfs_serial *info) +{ + reg_scope_instances regi_dmaout = info->regi_dmaout; +#ifdef SERIAL_DEBUG_INTR + printk("txdma_irq(%d): 1\n",info->line); +#endif + if (regi_dmaout) { + reg_dma_rw_intr_mask intr_mask = { 0 }; + intr_mask.data = regk_dma_yes; + REG_WR(dma, regi_dmaout, rw_intr_mask, intr_mask); + } +} + + +static inline void +etraxfs_disable_serial_data_rx_irq(struct etraxfs_serial *info) +{ + reg_scope_instances regi_ser = info->regi_ser; +#ifdef SERIAL_DEBUG_INTR + printk("ser_irq(%d): 0\n",info->line); +#endif + if (regi_ser) { + unsigned long flags; + reg_ser_rw_intr_mask intr_mask; + local_irq_save(flags); + intr_mask = REG_RD(ser, regi_ser, rw_intr_mask); + intr_mask.dav = regk_ser_no; + REG_WR(ser, regi_ser, rw_intr_mask, intr_mask); + local_irq_restore(flags); + } +} + +static inline void +etraxfs_enable_serial_data_rx_irq(struct etraxfs_serial *info) +{ + reg_scope_instances regi_ser = info->regi_ser; +#ifdef SERIAL_DEBUG_INTR + printk("ser_irq(%d): 1\n",info->line); +#endif + if (regi_ser) { + unsigned long flags; + reg_ser_rw_intr_mask intr_mask; + local_irq_save(flags); + intr_mask = REG_RD(ser, regi_ser, rw_intr_mask); + intr_mask.dav= regk_ser_yes; + REG_WR(ser, regi_ser, rw_intr_mask, intr_mask); + local_irq_restore(flags); + } +} + + +static inline void +etraxfs_disable_serial_data_tx_irq(struct etraxfs_serial *info) +{ + reg_scope_instances regi_ser = info->regi_ser; +#ifdef SERIAL_DEBUG_INTR + printk("ser_irq(%d): 0\n",info->line); +#endif + if (regi_ser) { + unsigned long flags; + reg_ser_rw_intr_mask intr_mask; + local_irq_save(flags); + intr_mask = REG_RD(ser, regi_ser, rw_intr_mask); + intr_mask.tr_rdy = regk_ser_no; + REG_WR(ser, regi_ser, rw_intr_mask, intr_mask); + local_irq_restore(flags); + } +} + +static inline void +etraxfs_enable_serial_data_tx_irq(struct etraxfs_serial *info) +{ + reg_scope_instances regi_ser = info->regi_ser; +#ifdef SERIAL_DEBUG_INTR + printk("ser_irq(%d): 1\n",info->line); +#endif + if (regi_ser) { + unsigned long flags; + reg_ser_rw_intr_mask intr_mask; + local_irq_save(flags); + intr_mask = REG_RD(ser, regi_ser, rw_intr_mask); + intr_mask.tr_rdy = regk_ser_yes; + REG_WR(ser, regi_ser, rw_intr_mask, intr_mask); + local_irq_restore(flags); + } +} + + + +#if defined(CONFIG_ETRAX_RS485) +/* Enable RS-485 mode on selected port. This is UGLY. */ +static int +etraxfs_enable_rs485(struct tty_struct *tty,struct rs485_control *r) +{ + struct etraxfs_serial * info = (struct etraxfs_serial *)tty->driver_data; + reg_scope_instances regi_ser = info->regi_ser; + + info->rs485.rts_on_send = 0x01 & r->rts_on_send; + info->rs485.rts_after_sent = 0x01 & r->rts_after_sent; + info->rs485.delay_rts_before_send = r->delay_rts_before_send; + info->rs485.enabled = r->enabled; + if (regi_ser) { + unsigned long flags; + reg_ser_rw_tr_ctrl tr_ctrl; + reg_ser_rw_rec_ctrl rec_ctrl; + + local_irq_save(flags); + tr_ctrl = REG_RD(ser, regi_ser, rw_tr_ctrl); + rec_ctrl = REG_RD(ser, regi_ser, rw_rec_ctrl); + if (r->enabled) { + tr_ctrl.auto_rts = regk_ser_yes; + rec_ctrl.rts_n = r->rts_after_sent ? + regk_ser_active : regk_ser_inactive; + + } else { + tr_ctrl.auto_rts = regk_ser_no; + rec_ctrl.rts_n = r->rts_after_sent ? + regk_ser_active : regk_ser_inactive; + } + + REG_WR(ser, regi_ser, rw_tr_ctrl, tr_ctrl); + REG_WR(ser, regi_ser, rw_rec_ctrl, rec_ctrl); +#ifdef CONFIG_ETRAX_RS485_DISABLE_RECEIVER + { + reg_ser_rw_rec_ctrl rec_ctrl; + rec_ctrl = REG_RD(ser, regi_ser, rw_rec_ctrl); + rec_ctrl.half_duplex = r->enabled; + REG_WR(ser, regi_ser, rw_rec_ctrl, rec_ctrl); + } +#endif /* CONFIG_ETRAX_RS485_DISABLE_RECEIVER */ + local_irq_restore(flags); + } +/* printk("rts: on send = %i, after = %i, enabled = %i", + info->rs485.rts_on_send, + info->rs485.rts_after_sent, + info->rs485.enabled + ); +*/ + return 0; +} + +static int +etraxfs_write_rs485(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct etraxfs_serial * info = (struct etraxfs_serial *)tty->driver_data; + int old_enabled = info->rs485.enabled; + reg_scope_instances regi_ser = info->regi_ser; + + /* rs485 is always implicitly enabled if we're using the ioctl() + * but it doesn't have to be set in the rs485_control + * (to be backward compatible with old apps) + * So we store, set and restore it. + */ + info->rs485.enabled = 1; + /* rs_write now deals with RS485 if enabled */ + if (regi_ser) { + unsigned long flags; + reg_ser_rw_tr_ctrl tr_ctrl; + reg_ser_rw_rec_ctrl rec_ctrl; + + local_irq_save(flags); + tr_ctrl = REG_RD(ser, regi_ser, rw_tr_ctrl); + rec_ctrl = REG_RD(ser, regi_ser, rw_rec_ctrl); + tr_ctrl.auto_rts = regk_ser_yes; + rec_ctrl.rts_n = info->rs485.rts_after_sent ? + regk_ser_active : regk_ser_inactive; + +#ifdef CONFIG_ETRAX_RS485_DISABLE_RECEIVER + rec_ctrl.half_duplex = regk_ser_yes; +#endif /* CONFIG_ETRAX_RS485_DISABLE_RECEIVER */ + REG_WR(ser, regi_ser, rw_tr_ctrl, tr_ctrl); + REG_WR(ser, regi_ser, rw_rec_ctrl, rec_ctrl); + local_irq_restore(flags); + } + count = rs_write(tty, buf, count); + info->rs485.enabled = old_enabled; + if (regi_ser && !old_enabled) { + unsigned long flags; + reg_ser_rw_tr_ctrl tr_ctrl; + local_irq_save(flags); + tr_ctrl = REG_RD(ser, regi_ser, rw_tr_ctrl); + tr_ctrl.auto_rts = regk_ser_no; + REG_WR(ser, regi_ser, rw_tr_ctrl, tr_ctrl); +#ifdef CONFIG_ETRAX_RS485_DISABLE_RECEIVER + { + reg_ser_rw_rec_ctrl rec_ctrl; + rec_ctrl = REG_RD(ser, regi_ser, rw_rec_ctrl); + rec_ctrl.half_duplex = regk_ser_no; + REG_WR(ser, regi_ser, rw_rec_ctrl, rec_ctrl); + } +#endif /* CONFIG_ETRAX_RS485_DISABLE_RECEIVER */ + local_irq_restore(flags); + } + return count; +} + +#endif /* CONFIG_ETRAX_RS485 */ + +/* + * ------------------------------------------------------------ + * rs_stop() and rs_start() + * + * This routines are called before setting or resetting tty->stopped. + * They enable or disable transmitter using rw_tr_ctrl register. + * ------------------------------------------------------------ + */ + +static void +rs_stop(struct tty_struct *tty) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; + if (info) { + unsigned long flags; + reg_scope_instances regi_ser = info->regi_ser; + reg_ser_rw_tr_ctrl tr_ctrl; + if (regi_ser) { + local_irq_save(flags); + tr_ctrl = REG_RD(ser, regi_ser, rw_tr_ctrl); + tr_ctrl.en = regk_ser_no; + REG_WR(ser, regi_ser, rw_tr_ctrl, tr_ctrl); + local_irq_restore(flags); + } + } +} + +static void +rs_start(struct tty_struct *tty) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; + if (info) { + unsigned long flags; + reg_scope_instances regi_ser = info->regi_ser; + reg_ser_rw_tr_ctrl tr_ctrl; + if (regi_ser) { + local_irq_save(flags); + tr_ctrl = REG_RD(ser, regi_ser, rw_tr_ctrl); + tr_ctrl.en = regk_ser_yes; + REG_WR(ser, regi_ser, rw_tr_ctrl, tr_ctrl); + local_irq_restore(flags); + } + } +} + +/* + * ---------------------------------------------------------------------- + * + * Here starts the interrupt handling routines. All of the following + * subroutines are declared as inline and are folded into + * rs_interrupt(). They were separated out for readability's sake. + * + * Note: rs_interrupt() is a "fast" interrupt, which means that it + * runs with interrupts turned off. People who may want to modify + * rs_interrupt() should try to keep the interrupt handler as fast as + * possible. After you are done making modifications, it is not a bad + * idea to do: + * + * gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c + * + * and look at the resulting assemble code in serial.s. + * + * - Ted Ts'o (tytso@mit.edu), 7-Mar-93 + * ----------------------------------------------------------------------- + */ + +/* + * This routine is used by the interrupt handler to schedule + * processing in the software interrupt portion of the driver. + */ +static _INLINE_ void +rs_sched_event(struct etraxfs_serial *info, + int event) +{ + info->event |= 1 << event; + schedule_work(&info->work); +} + +/* The output DMA channel is free - use it to send as many chars as possible + * NOTES: + * We don't pay attention to info->x_char, which means if the TTY wants to + * use XON/XOFF it will set info->x_char but we won't send any X char! + * + * To implement this, we'd just start a DMA send of 1 byte pointing at a + * buffer containing the X char, and skip updating xmit. We'd also have to + * check if the last sent char was the X char when we enter this function + * the next time, to avoid updating xmit with the sent X value. + */ + + +static void inline +transmit_chars_dma(struct etraxfs_serial *info, + reg_scope_instances regi_dmaout) +{ + struct dma_descr_data *descr, *prev, *dmapos; + unsigned int c, sentl = 0; + reg_dma_rw_ack_intr ack_intr = { 0 }; + reg_dma_rw_stat status; + ack_intr.data = regk_dma_yes; + /* acknowledge dma data descriptor irq */ + REG_WR(dma, regi_dmaout, rw_ack_intr, ack_intr); + if (!info->tr_running) { + /* weirdo... we shouldn't get here! */ + printk("Achtung: transmit_chars with !tr_running\n"); + return; + } + + /* first get the amount of bytes sent during the last DMA transfer, + and update xmit accordingly */ + status = REG_RD(dma, regi_dmaout, rw_stat); + if (status.list_state == regk_dma_data_at_eol || !info->tx_started) + dmapos = phys_to_virt((int)info->last_tx_descr->next); + else + dmapos = phys_to_virt(REG_RD_INT(dma, regi_dmaout, rw_data)); + + descr = info->first_tx_descr; + while (descr != dmapos) { + sentl += descr->after - descr->buf; + descr->after = descr->buf = NULL; + descr = phys_to_virt((int)descr->next); + } + + info->first_tx_descr = descr; + descr = info->last_tx_descr; + + /* update stats */ + info->icount.tx += sentl; + + /* update xmit buffer */ + info->xmit.tail = (info->xmit.tail + sentl) & (SERIAL_XMIT_SIZE - 1); + + /* if there is only a few chars left in the buf, wake up the blocked + write if any */ + if (CIRC_CNT(info->xmit.head, + info->xmit.tail, + SERIAL_XMIT_SIZE) < WAKEUP_CHARS) + rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); + + /* find out the largest amount of consecutive bytes we want to send now */ + + c = CIRC_CNT_TO_END(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); + + if (c <= 0) { + /* our job here is done, don't schedule any new DMA transfer */ + info->tr_running = 0; + return; + } + + /* ok we can schedule a dma send of c chars starting at info->xmit.tail */ + /* set up the descriptor correctly for output */ + prev = descr; + descr = phys_to_virt((int)descr->next); + info->last_tx_descr = descr; + descr->buf = (void*)virt_to_phys(info->xmit.buf + info->xmit.tail); + descr->after = descr->buf + c; + descr->eol = 1; + descr->out_eop = 0; + descr->intr = 1; + descr->wait = 0; + descr->in_eop = 0; + descr->md = 0; + prev->eol = 0; + if (!info->tx_started) + { + info->tx_started = 1; + info->tr_context_descr.next = 0; + info->tr_context_descr.saved_data = (dma_descr_data*)virt_to_phys(descr); + info->tr_context_descr.saved_data_buf = descr->buf; + DMA_START_CONTEXT(regi_dmaout, virt_to_phys(&info->tr_context_descr)); + } + else + { + reg_dma_rw_cmd cmd = {.cont_data = regk_dma_yes}; + REG_WR(dma, regi_dmaout, rw_cmd, cmd); + } + + /* DMA is now running (hopefully) */ +} + +static void inline +transmit_chars_no_dma(struct etraxfs_serial *info, + reg_scope_instances regi_ser) +{ + int count; + + reg_ser_r_stat_din rstat; + reg_ser_rw_ack_intr ack_intr = { 0 }; + + ack_intr.tr_rdy = 1; + + if (info->x_char) { + reg_ser_rw_dout dout = { .data = info->x_char }; + REG_WR(ser, regi_ser, rw_dout, dout); + REG_WR(ser, regi_ser, rw_ack_intr, ack_intr); + info->icount.tx++; + info->x_char = 0; + return; + } + if (info->xmit.head == info->xmit.tail + || info->tty->stopped + || info->tty->hw_stopped) { + /* No more to send, disable int */ + reg_ser_rw_intr_mask intr_mask; + intr_mask = REG_RD(ser, regi_ser, rw_intr_mask); + intr_mask.tr_rdy = 0; + intr_mask.tr_empty = 0; + REG_WR(ser, regi_ser, rw_intr_mask, intr_mask); + info->tr_running = 0; + return; + } + + count = ETRAX_SER_FIFO_SIZE; + do { + reg_ser_rw_dout dout = { .data = info->xmit.buf[info->xmit.tail] }; + REG_WR(ser, regi_ser, rw_dout, dout); + REG_WR(ser, regi_ser, rw_ack_intr, ack_intr); + info->xmit.tail = (info->xmit.tail + 1) & (SERIAL_XMIT_SIZE-1); + info->icount.tx++; + if (info->xmit.head == info->xmit.tail) + break; + rstat = REG_RD(ser, regi_ser, r_stat_din); + } while ((--count > 0) && rstat.tr_rdy); + + etraxfs_enable_serial_data_tx_irq(info); + + if (CIRC_CNT(info->xmit.head, + info->xmit.tail, + SERIAL_XMIT_SIZE) < WAKEUP_CHARS) + rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); +} /* transmit_chars_no_dma */ + +static void +transmit_chars(struct etraxfs_serial *info) +{ + reg_scope_instances regi_dmaout = info->regi_dmaout; +#ifdef SERIAL_DEBUG_INTR + if (info->line == SERIAL_DEBUG_LINE) + printk("tc\n"); +#endif + if (regi_dmaout) { + /* DMA is used for transmit */ + transmit_chars_dma(info, regi_dmaout); + } else { + /* Use char-by-char transmit */ + reg_scope_instances regi_ser = info->regi_ser; + if (regi_ser) { + transmit_chars_no_dma(info, regi_ser); + } + } +} /* transmit_chars */ + +static void +start_transmit(struct etraxfs_serial *info) +{ + info->tr_running = 1; + transmit_chars(info); +} /* start_transmit */ + + +static struct etrax_recv_buffer * +alloc_recv_buffer(unsigned int size) +{ + struct etrax_recv_buffer *buffer; + + if (!(buffer = kmalloc(sizeof *buffer + size, GFP_ATOMIC))) + return NULL; + + buffer->next = NULL; + buffer->length = 0; + buffer->error = TTY_NORMAL; + + return buffer; +} + +static void +append_recv_buffer(struct etraxfs_serial *info, struct etrax_recv_buffer *buffer) +{ + unsigned long flags; + + local_irq_save(flags); + + if (!info->first_recv_buffer) + info->first_recv_buffer = buffer; + else + info->last_recv_buffer->next = buffer; + + info->last_recv_buffer = buffer; + + info->recv_cnt += buffer->length; + if (info->recv_cnt > info->max_recv_cnt) + info->max_recv_cnt = info->recv_cnt; + + local_irq_restore(flags); +} + +static int +add_char_and_flag(struct etraxfs_serial *info, unsigned char data, unsigned char flag) +{ + struct etrax_recv_buffer *buffer; + + if (!(buffer = alloc_recv_buffer(4))) + return 0; + + buffer->length = 1; + buffer->error = flag; + buffer->buffer[0] = data; + + append_recv_buffer(info, buffer); + + info->icount.rx++; + + return 1; +} + +extern _INLINE_ unsigned int +handle_descr_data(struct etraxfs_serial *info, struct dma_descr_data *descr, unsigned int recvl) +{ + struct etrax_recv_buffer *buffer = phys_to_virt((unsigned long)descr->buf) - sizeof *buffer; + + if (info->recv_cnt + recvl > 65536) { + printk("%s Too much pending incoming serial data! Dropping %u bytes.\n", __FUNCTION__, recvl); + return 0; + } + + buffer->length = recvl; + + append_recv_buffer(info, buffer); + flush_to_flip_buffer(info); + + if (!(buffer = alloc_recv_buffer(SERIAL_DESCR_BUF_SIZE))) + panic("%s: Failed to allocate memory for receive buffer!\n", __FUNCTION__); + + descr->buf = (void*)virt_to_phys(buffer->buffer); + + return recvl; +} + +static _INLINE_ unsigned int +handle_all_descr_data(struct etraxfs_serial *info) +{ + struct dma_descr_data *descr; + unsigned int recvl; + unsigned int ret = 0; + reg_scope_instances regi_dmain = info->regi_dmain; + + while (1) + { + descr = &info->rec_descr[info->cur_rec_descr]; + + if (descr == phys_to_virt(REG_RD(dma, regi_dmain, rw_data))) + break; + + if (++info->cur_rec_descr == SERIAL_RECV_DESCRIPTORS) + info->cur_rec_descr = 0; + + /* find out how many bytes were read */ + recvl = descr->after - descr->buf; + + DEBUG_LOG(info->line, "recvl %d\n", recvl); + + /* update stats */ + info->icount.rx += recvl; + + ret += handle_descr_data(info, descr, recvl); + } + + return ret; +} + +static _INLINE_ void +receive_chars_dma(struct etraxfs_serial *info) +{ + struct tty_struct *tty; + reg_ser_r_stat_din rstat; + reg_dma_rw_ack_intr ack_intr; + + /* Acknowledge both dma_descr and dma_eop irq in R_DMA_CHx_CLR_INTR */ + ack_intr.data = 1; + ack_intr.in_eop = 1; + REG_WR(dma, info->regi_dmain, rw_ack_intr, ack_intr); + + tty = info->tty; + if (!tty) /* Something wrong... */ + return; + + handle_all_descr_data(info); + + /* Read the status register to detect errors */ + rstat = REG_RD(ser, info->regi_ser, r_stat_din); + + if (rstat.framing_err | rstat.par_err | rstat.orun) { + /* If we got an error, we must reset it by reading the + * rs_stat_din register and put the data in buffer manually. + */ + reg_ser_rs_stat_din stat_din; + stat_din = REG_RD(ser, info->regi_ser, rs_stat_din); + + PROCSTAT(ser_stat[info->line].errors_cnt++); + DEBUG_LOG(info->line, "#dERR: s d 0x%05X\n", + ((stat_din.framing_err << 16 | + stat_din.par_err << 12 | + stat_din.orun << 8) | + stat_din.data)); + + if (stat_din.par_err) + add_char_and_flag(info, stat_din.data, TTY_PARITY); + else if (stat_din.orun) + add_char_and_flag(info, stat_din.data, TTY_OVERRUN); + else if (stat_din.framing_err) + add_char_and_flag(info, stat_din.data, TTY_FRAME); + } + + + /* Restart the receiving DMA */ + DMA_CONTINUE_DATA(info->regi_dmain); // CHECK +} + +static _INLINE_ int +start_recv_dma(struct etraxfs_serial *info) +{ + struct dma_descr_data *descr = info->rec_descr; + struct etrax_recv_buffer *buffer; + int i; + + /* Set up the receiving descriptors */ + for (i = 0; i < SERIAL_RECV_DESCRIPTORS; i++) { + if (!(buffer = alloc_recv_buffer(SERIAL_DESCR_BUF_SIZE))) + panic("%s: Failed to allocate memory for receive buffer!\n", __FUNCTION__ ); + descr[i].next = (void*)virt_to_phys(&descr[i+1]); + descr[i].buf = (void*)virt_to_phys(buffer->buffer); + descr[i].after = descr[i].buf + SERIAL_DESCR_BUF_SIZE; + descr[i].eol = 0; + descr[i].out_eop = 0; + descr[i].intr = 1; + descr[i].wait = 0; + descr[i].in_eop = 0; + descr[i].md = 0; + + } + + /* Link the last descriptor to the first */ + descr[i-1].next = (void*)virt_to_phys(&descr[0]); + + /* Start with the first descriptor in the list */ + info->cur_rec_descr = 0; + info->rec_context_descr.next = 0; + info->rec_context_descr.saved_data = (dma_descr_data *)virt_to_phys(&descr[info->cur_rec_descr]); + info->rec_context_descr.saved_data_buf = descr[info->cur_rec_descr].buf; + + /* Start the DMA */ + DMA_START_CONTEXT(info->regi_dmain, virt_to_phys(&info->rec_context_descr)); + + /* Input DMA should be running now */ + return 1; +} + + +static void +start_receive(struct etraxfs_serial *info) +{ + reg_scope_instances regi_dmain = info->regi_dmain; + if (regi_dmain) { + info->tty->flip.count = 0; + start_recv_dma(info); + } +} + + +static void +start_transmitter(struct etraxfs_serial *info) +{ + int i; + reg_scope_instances regi_dmaout = info->regi_dmaout; + if (regi_dmaout) { + for (i = 0; i < SERIAL_TX_DESCRIPTORS; i++) + { + memset(&info->tr_descr[i], 0, sizeof(info->tr_descr[i])); + info->tr_descr[i].eol = 1; + info->tr_descr[i].intr = 1; + info->tr_descr[i].next = (dma_descr_data*)virt_to_phys(&info->tr_descr[i+1]); + } + info->tr_descr[i-1].next = (dma_descr_data*)virt_to_phys(&info->tr_descr[0]); + info->first_tx_descr = info->last_tx_descr = &info->tr_descr[0]; + } +} + + +static _INLINE_ void +status_handle(struct etraxfs_serial *info, unsigned short status) +{ +} + +static _INLINE_ void check_modem_status(struct etraxfs_serial *info) +{ + /* TODO: Check modem status change */ +} + +/* dma output channel interrupt handler + this interrupt is called from DMA2(ser2), DMA4(ser3), DMA6(ser0) or + DMA8(ser1) when they have finished a descriptor with the intr flag set. +*/ + +static irqreturn_t +dma_tr_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct etraxfs_serial *info; + int i; + int handled = 0; + reg_scope_instances regi_dmaout; + /* find out the line that caused this irq and get it from rs_table */ + + for (i = 0; i < NR_PORTS; i++) { + reg_dma_r_masked_intr masked_intr; + info = rs_table + i; + regi_dmaout = info->regi_dmaout; + if (!regi_dmaout) + continue; + /* check for dma_descr (don't need to check for dma_eop in + output dma for serial */ + masked_intr = REG_RD(dma, regi_dmaout, r_masked_intr); + + if (masked_intr.data) { + /* we can send a new dma bunch. make it so. */ + DEBUG_LOG(info->line, "tr_interrupt %i\n", i); + /* Read jiffies_usec first, + * we want this time to be as late as possible + */ + PROCSTAT(ser_stat[info->line].tx_dma_ints++); + info->last_tx_active_usec = GET_JIFFIES_USEC(); + info->last_tx_active = jiffies; + transmit_chars_dma(info, regi_dmaout); + handled = 1; + } + check_modem_status(info); + } + return IRQ_RETVAL(handled); +} + +/* dma input channel interrupt handler */ + +static irqreturn_t +dma_rec_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct etraxfs_serial *info; + int i; + reg_scope_instances regi_dmain; + int handled = 0; + /* find out the line that caused this irq and get it from rs_table */ + + /* TODO: use irq argument to get correct info? */ + for (i = 0; i < NR_PORTS; i++) { + reg_dma_r_masked_intr masked_intr; + info = rs_table + i; + regi_dmain = info->regi_dmain; + if (!regi_dmain) + continue; + /* check for both dma_eop and dma_descr for the input dma channel */ + masked_intr = REG_RD(dma, regi_dmain, r_masked_intr); + if (masked_intr.data || masked_intr.in_eop) { + /* we have received something */ + receive_chars_dma(info); + handled = 1; + } + check_modem_status(info); + } + return IRQ_RETVAL(handled); +} + +#if 0 +static _INLINE_ int +force_eop_if_needed(struct etraxfs_serial *info) +{ + /* We check dav bit to determine if data has + * arrived since last time + */ + unsigned char rstat = info->port[REG_STATUS]; + + /* error or datavail? */ + if (rstat & SER_ERROR_MASK) { + /* Some error has occurred. If there has been valid data, an + * EOP interrupt will be made automatically. If no data, the + * normal ser_interrupt should be enabled and handle it. + * So do nothing! + */ + DEBUG_LOG(info->line, "timeout err: rstat 0x%03X\n", + rstat | (info->line << 8)); + return 0; + } + + if (rstat & SER_DATA_AVAIL_MASK) { + /* Ok data, no error, count it */ + TIMERD(DEBUG_LOG(info->line, "timeout: rstat 0x%03X\n", + rstat | (info->line << 8))); + /* Read data to clear status flags */ + (void)info->port[REG_DATA]; + + info->forced_eop = 0; + START_FLUSH_FAST_TIMER(info, "magic"); + return 0; + } + + /* hit the timeout, force an EOP for the input + * dma channel if we haven't already + */ + if (!info->forced_eop) { + info->forced_eop = 1; + PROCSTAT(ser_stat[info->line].timeout_flush_cnt++); + DEBUG_LOG(info->line, "timeout EOP %i\n", info->line); + FORCE_EOP(info); + } + + return 1; +} +#endif + +static void +flush_to_flip_buffer(struct etraxfs_serial *info) +{ + struct tty_struct *tty; + struct etrax_recv_buffer *buffer; + unsigned int length; + unsigned long flags; + + if (!info->first_recv_buffer) + return; + + local_irq_save(flags); + + if (!(tty = info->tty)) { + local_irq_restore(flags); + return; + } + + length = tty->flip.count; + + while ((buffer = info->first_recv_buffer) && length < TTY_FLIPBUF_SIZE) { + unsigned int count = buffer->length; + + if (length + count > TTY_FLIPBUF_SIZE) + count = TTY_FLIPBUF_SIZE - length; + + memcpy(tty->flip.char_buf_ptr + length, buffer->buffer, count); + memset(tty->flip.flag_buf_ptr + length, TTY_NORMAL, count); + tty->flip.flag_buf_ptr[length] = buffer->error; + + length += count; + info->recv_cnt -= count; + + if (count == buffer->length) { + info->first_recv_buffer = buffer->next; + kfree(buffer); + } else { + buffer->length -= count; + memmove(buffer->buffer, buffer->buffer + count, buffer->length); + buffer->error = TTY_NORMAL; + } + } + + if (!info->first_recv_buffer) + info->last_recv_buffer = NULL; + + tty->flip.count = length; + + local_irq_restore(flags); + + /* this includes a check for low-latency */ + tty_flip_buffer_push(tty); +} + +#if 0 +static _INLINE_ void +check_flush_timeout(struct etraxfs_serial *info) +{ + force_eop_if_needed(info); + + flush_to_flip_buffer(info); + + if (info->first_recv_buffer) + START_FLUSH_FAST_TIMER(info, "flip"); +} +#endif + +extern void _INLINE_ receive_chars_no_dma(struct etraxfs_serial *info) + +{ + reg_ser_rs_stat_din stat_din; + reg_ser_r_stat_din rstat; + struct tty_struct *tty; + struct async_icount *icount; + int max_count = 16; + reg_ser_rw_ack_intr ack_intr = { 0 }; + + rstat = REG_RD(ser, info->regi_ser, r_stat_din); + info->last_rx_active_usec = GET_JIFFIES_USEC(); + info->last_rx_active = jiffies; + tty = info->tty; + if (!tty) { + /* Something wrong... */ + /* One specific example where this happens is when the KGDB port + is in interrupt mode and we send lots of ctrl-C. */ + return; + } + icount = &info->icount; +#ifdef SERIAL_DEBUG_INTR + printk("Int from ser%d\n", info->line); +#endif +/*DEBUG_LOG(info->line, "ser_interrupt stat %03X\n", rstat | (i << 8)); */ + do { + if (tty->flip.count >= TTY_FLIPBUF_SIZE) { +#if 0 + tty->flip.tqueue.routine((void *) tty); +#endif + if (tty->flip.count >= TTY_FLIPBUF_SIZE) + return; // if TTY_DONT_FLIP is set + } + stat_din = REG_RD(ser, info->regi_ser, rs_stat_din); + + ack_intr.dav = 1; + REG_WR(ser, info->regi_ser, rw_ack_intr, ack_intr); + + *tty->flip.char_buf_ptr = stat_din.data; + icount->rx++; +#ifdef SERIAL_DEBUG_INTR + printk("DR%02x fe%i pe%i oe%i", stat_din.data, + stat_din.framing_err, stat_din.par_err, stat_din.orun); +#endif + + + + if (stat_din.framing_err | + stat_din.par_err | + stat_din.orun) { + if (stat_din.data == 0x00 && + stat_din.framing_err) { + /* Most likely a break. */ + *tty->flip.flag_buf_ptr = TTY_BREAK; + icount->brk++; + DEBUG_LOG(info->line, "# BL BRK %i\n", icount->brk); + } else if (stat_din.par_err) { + *tty->flip.flag_buf_ptr = TTY_PARITY; + icount->parity++; + } else if (stat_din.orun) { + *tty->flip.flag_buf_ptr = TTY_OVERRUN; + icount->overrun++; + } else if (stat_din.framing_err) { + *tty->flip.flag_buf_ptr = TTY_FRAME; + icount->frame++; + } + + DEBUG_LOG(info->line, "#iERR s d %04X\n", + ((stat_din.framing_err << 16 | + stat_din.par_err << 12 | + stat_din.orun << 8) | + stat_din.data)); + } else { + *tty->flip.flag_buf_ptr = 0; + } + tty->flip.flag_buf_ptr++; + tty->flip.char_buf_ptr++; + tty->flip.count++; + rstat = REG_RD(ser, info->regi_ser, r_stat_din); + } while (rstat.dav && (max_count-- > 0)); + tty_flip_buffer_push(tty); +} /* receive_chars_no_dma */ + + +/* "Normal" serial port interrupt handler - both rx and tx */ +static irqreturn_t +ser_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct etraxfs_serial *info; + reg_scope_instances regi_ser; + int i; + int handled = 0; + + for (i = 0; i < NR_PORTS; i++) { + info = rs_table + i; + if (info->regi_dmain && info->regi_dmaout) + continue; /* we use DMA for both in and out - skip */ + + regi_ser = info->regi_ser; + + if (regi_ser) { + reg_ser_r_masked_intr masked_intr; + masked_intr = REG_RD(ser, regi_ser, r_masked_intr); + /* Check what interrupts are active before taking + * actions. If DMA is used the interrupt shouldn't + * be enabled. + */ + if (masked_intr.dav) { + receive_chars_no_dma(info); + handled = 1; + } + check_modem_status(info); + + if (masked_intr.tr_rdy) { + transmit_chars_no_dma(info, regi_ser); + handled = 1; + } + } + } + return IRQ_RETVAL(handled); +} /* ser_interrupt */ + + +/* + * ------------------------------------------------------------------- + * Here ends the serial interrupt routines. + * ------------------------------------------------------------------- + */ + +/* + * This routine is used to handle the "bottom half" processing for the + * serial driver, known also the "software interrupt" processing. + * This processing is done at the kernel interrupt level, after the + * rs_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON. This + * is where time-consuming activities which can not be done in the + * interrupt driver proper are done; the interrupt driver schedules + * them using rs_sched_event(), and they get done here. + */ +static void +do_softint(void *private_) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *) private_; + struct tty_struct *tty; + + tty = info->tty; + if (!tty) + return; + + if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) { + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); + wake_up_interruptible(&tty->write_wait); + } +} + +static int +startup(struct etraxfs_serial * info) +{ + unsigned long flags; + unsigned long xmit_page; + reg_scope_instances regi_dma; + reg_scope_instances regi_ser; + reg_dma_rw_cfg cfg = {.en = 1}; + int i; + + xmit_page = get_zeroed_page(GFP_KERNEL); + if (!xmit_page) + return -ENOMEM; + + local_irq_save(flags); + + /* if it was already initialized, skip this */ + + if (info->flags & ASYNC_INITIALIZED) { + local_irq_restore(flags); + free_page(xmit_page); + return 0; + } + + if (info->xmit.buf) + free_page(xmit_page); + else + info->xmit.buf = (unsigned char *) xmit_page; + +#ifdef SERIAL_DEBUG_OPEN + printk("starting up ttyS%d (xmit_buf 0x%p)\n", info->line, info->xmit.buf); +#endif + + /* + * Clear the FIFO buffers and disable them + * (they will be reenabled in change_speed()) + */ + + /* + * Reset the DMA channels and make sure their interrupts are cleared + */ + + regi_dma = info->regi_dmain; + if (regi_dma) { + reg_dma_rw_ack_intr ack_intr = { 0 }; + DMA_RESET(regi_dma); + /* Wait until reset cycle is complete */ + DMA_WAIT_UNTIL_RESET(regi_dma); + REG_WR(dma, regi_dma, rw_cfg, cfg); + /* Make sure the irqs are cleared */ + ack_intr.group = 1; + ack_intr.ctxt = 1; + ack_intr.data = 1; + ack_intr.in_eop = 1; + ack_intr.stream_cmd = 1; + REG_WR(dma, regi_dma, rw_ack_intr, ack_intr); + } + regi_dma = info->regi_dmaout; + if (regi_dma) { + reg_dma_rw_ack_intr ack_intr = { 0 }; + DMA_RESET(regi_dma); + /* Wait until reset cycle is complete */ + DMA_WAIT_UNTIL_RESET(regi_dma); + REG_WR(dma, regi_dma, rw_cfg, cfg); + /* Make sure the irqs are cleared */ + ack_intr.group = 1; + ack_intr.ctxt = 1; + ack_intr.data = 1; + ack_intr.in_eop = 1; + ack_intr.stream_cmd = 1; + REG_WR(dma, regi_dma, rw_ack_intr, ack_intr); + } + + if (info->tty) + clear_bit(TTY_IO_ERROR, &info->tty->flags); + + info->xmit.head = info->xmit.tail = 0; + info->first_recv_buffer = info->last_recv_buffer = NULL; + info->recv_cnt = info->max_recv_cnt = 0; + + for (i = 0; i < SERIAL_RECV_DESCRIPTORS; i++) + info->rec_descr[i].buf = 0; + + /* + * and set the speed and other flags of the serial port + * this will start the rx/tx as well + */ +#ifdef SERIAL_HANDLE_EARLY_ERRORS + etraxfs_enable_serial_data_irq(info); +#endif + change_speed(info); + + /* dummy read to reset any serial errors */ + + /* dummy read to reset any serial errors */ + regi_ser = info->regi_ser; + if (regi_ser) { + (void)REG_RD(ser, regi_ser, rs_stat_din); + } + + /* enable the interrupts */ + if (info->regi_dmain) + { + etraxfs_enable_rxdma_irq(info); + } else { + /* No DMA used */ + /* enable the interrupts */ + etraxfs_enable_serial_data_rx_irq(info); + } + if (info->regi_dmaout) + { + etraxfs_enable_txdma_irq(info); + } + + info->tr_running = 0; /* to be sure we don't lock up the transmitter */ + + /* setup the dma input descriptor and start dma */ + + start_receive(info); + start_transmitter(info); + + /* enable RTS/DTR last */ + + etraxfs_rts(info, 1); + etraxfs_dtr(info, 1); + + info->flags |= ASYNC_INITIALIZED; + + local_irq_restore(flags); + return 0; +} + +/* + * This routine will shutdown a serial port; interrupts are disabled, and + * DTR is dropped if the hangup on close termio flag is on. + */ +static void +shutdown(struct etraxfs_serial * info) +{ + unsigned long flags; + struct dma_descr_data *descr = info->rec_descr; + struct etrax_recv_buffer *buffer; + int i; + reg_ser_rw_tr_ctrl tr_ctrl; + + /* shut down the transmitter and receiver */ + + etraxfs_disable_rx(info); + tr_ctrl = REG_RD(ser, info->regi_ser, rw_tr_ctrl); + tr_ctrl.en = 0; + REG_WR(ser, info->regi_ser, rw_tr_ctrl, tr_ctrl); + + info->tr_running = 0; + + /* reset both dma channels */ + /* reset both dma channels */ + if (info->regi_dmain) { + etraxfs_disable_rxdma_irq(info); + DMA_RESET(info->regi_dmain); + } + if (info->regi_dmaout) { + etraxfs_disable_txdma_irq(info); + DMA_RESET(info->regi_dmaout); + } + + if (!(info->flags & ASYNC_INITIALIZED)) + return; + +#ifdef SERIAL_DEBUG_OPEN + printk("Shutting down serial port %d (irq %d)....\n", info->line, + info->irq); +#endif + + local_irq_save(flags); /* Disable interrupts */ + + if (info->xmit.buf) { + free_page((unsigned long)info->xmit.buf); + info->xmit.buf = NULL; + } + + for (i = 0; i < SERIAL_RECV_DESCRIPTORS; i++) + if (descr[i].buf) { + buffer = phys_to_virt((unsigned long)descr[i].buf) - sizeof *buffer; + kfree(buffer); + descr[i].buf = 0; + } + + if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) { + /* hang up DTR and RTS if HUPCL is enabled */ + etraxfs_dtr(info, 0); + etraxfs_rts(info, 0); /* could check CRTSCTS before doing this */ + } + + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + + info->flags &= ~ASYNC_INITIALIZED; + local_irq_restore(flags); +} + +/* Set a serial port register if anything has changed. */ +#define MODIFY_REG(instance, reg, var) \ + if (REG_RD_INT(ser, instance, reg) != REG_TYPE_CONV(int, reg_ser_##reg, var)) \ + REG_WR(ser, instance, reg, var); + + +/* change baud rate and other assorted parameters */ + +static void +change_speed(struct etraxfs_serial *info) +{ + unsigned int cflag; + reg_ser_rw_xoff xoff; + reg_ser_rw_xoff_clr xoff_clr = {0}; + reg_ser_rw_tr_ctrl tx_ctrl = {0}; + reg_ser_rw_tr_dma_en tx_dma_en = {0}; + reg_ser_rw_rec_ctrl rx_ctrl = {0}; + reg_ser_rw_tr_baud_div tx_baud_div = {0}; + reg_ser_rw_rec_baud_div rx_baud_div = {0}; + int base_freq; + + /* first some safety checks */ + + if (!info->tty || !info->tty->termios) + return; + if (!info->regi_ser) + return; + + cflag = info->tty->termios->c_cflag; + + /* start with default settings and then fill in changes */ + + /* tx: 8 bit, no/even parity, 1 stop bit, no cts */ + tx_ctrl.base_freq = regk_ser_f29_493; + tx_ctrl.en = 0; + if (info->regi_dmaout) + tx_dma_en.en = 1; + tx_ctrl.stop = 0; + tx_ctrl.auto_rts = 0; + tx_ctrl.txd = 1; + tx_ctrl.auto_cts = 0; + /* rx: 8 bit, no/even parity */ + if (info->regi_dmain) { + rx_ctrl.dma_mode = 1; + rx_ctrl.auto_eop = 1; + } + rx_ctrl.dma_err = regk_ser_stop; + rx_ctrl.sampling = regk_ser_majority; + rx_ctrl.timeout = 1; + rx_ctrl.rts_n = regk_ser_inactive; + + /* Common for tx and rx: 8N1 */ + tx_ctrl.data_bits = regk_ser_bits8; + rx_ctrl.data_bits = regk_ser_bits8; + tx_ctrl.par = regk_ser_even; + rx_ctrl.par = regk_ser_even; + tx_ctrl.par_en = regk_ser_no; + rx_ctrl.par_en = regk_ser_no; + + tx_ctrl.stop_bits = regk_ser_bits1; + + + /* change baud-rate and write it to the hardware */ + + info->obaud = cflag_to_baud(cflag); + info->ibaud = info->obaud; + /* baud_clock = base_freq / (divisor*8) + * divisor = base_freq / (baud_clock * 8) + * base_freq is either: + * off, ext, 29.493MHz, 32.000 MHz, 32.768 MHz or 100 MHz + * 20.493MHz is used for standard baudrates + */ + + + tx_baud_div.div = cflag_to_etrax_divisor(cflag, &base_freq); + tx_ctrl.base_freq = base_freq; + /* ETRAX supports different baudrates on in and out */ + if (cflag & CIBAUD) { + info->ibaud = cflag_to_baud(cflag >> IBSHIFT); + rx_baud_div.div = cflag_to_etrax_divisor(cflag >> IBSHIFT, &base_freq); + rx_ctrl.base_freq = base_freq; + } else { + /* RX uses same as TX */ + rx_baud_div.div = tx_baud_div.div; + rx_ctrl.base_freq = tx_ctrl.base_freq; + } + + if ((cflag & CSIZE) == CS7) { + /* set 7 bit mode */ + tx_ctrl.data_bits = regk_ser_bits7; + rx_ctrl.data_bits = regk_ser_bits7; + } + + if (cflag & CSTOPB) { + /* set 2 stop bit mode */ + tx_ctrl.stop_bits = regk_ser_bits2; + } + + if (cflag & PARENB) { + /* enable parity */ + tx_ctrl.par_en = regk_ser_yes; + rx_ctrl.par_en = regk_ser_yes; + } + + if (cflag & CMSPAR) { + if (cflag & PARODD) { + /* set mark parity if PARODD and CMSPAR */ + tx_ctrl.par = regk_ser_mark; + rx_ctrl.par = regk_ser_mark; + } else { + tx_ctrl.par = regk_ser_space; + rx_ctrl.par = regk_ser_space; + } + } else { + if (cflag & PARODD) { + /* set odd parity */ + tx_ctrl.par = regk_ser_odd; + rx_ctrl.par = regk_ser_odd; + } + } + + if (cflag & CRTSCTS) { + /* enable automatic CTS handling */ + tx_ctrl.auto_cts = regk_ser_yes; + } + + /* make sure the tx and rx are enabled */ + tx_ctrl.en = regk_ser_yes; + rx_ctrl.en = regk_ser_yes; + + /* actually write the control regs (if modified) to the hardware */ + + MODIFY_REG(info->regi_ser, rw_rec_baud_div, rx_baud_div); + MODIFY_REG(info->regi_ser, rw_rec_ctrl, rx_ctrl); + + MODIFY_REG(info->regi_ser, rw_tr_baud_div, tx_baud_div); + MODIFY_REG(info->regi_ser, rw_tr_ctrl, tx_ctrl); + MODIFY_REG(info->regi_ser, rw_tr_dma_en, tx_dma_en); + + xoff = REG_RD(ser, info->regi_ser, rw_xoff); + xoff.chr = STOP_CHAR(info->tty); + + xoff_clr.clr = regk_ser_yes; + if (info->tty->termios->c_iflag & IXON ) { + xoff.automatic = regk_ser_yes; + } else { + xoff.automatic = regk_ser_no; + } + MODIFY_REG(info->regi_ser, rw_xoff_clr, xoff_clr); + MODIFY_REG(info->regi_ser, rw_xoff, xoff); + + update_char_time(info); +} /* change_speed */ + +/* start transmitting chars NOW */ + +static void +rs_flush_chars(struct tty_struct *tty) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; + unsigned long flags; + + if (info->tr_running || + info->xmit.head == info->xmit.tail || + tty->stopped || + tty->hw_stopped || + !info->xmit.buf) + return; + +#ifdef SERIAL_DEBUG_FLOW + printk("rs_flush_chars\n"); +#endif + + /* this protection might not exactly be necessary here */ + + local_irq_save(flags); + start_transmit(info); + local_irq_restore(flags); +} + +extern inline int +raw_write(struct tty_struct * tty, + const unsigned char *buf, int count) +{ + int c, ret = 0; + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; + unsigned long flags; + + /* first some sanity checks */ + + if (!tty || !info->xmit.buf || !tmp_buf) + return 0; + +#ifdef SERIAL_DEBUG_DATA + if (info->line == SERIAL_DEBUG_LINE) + printk("raw_write (%d)\n", count); +#endif + + local_irq_save(flags); + + /* the loca_irq_save/local_irq_restore pairs below are needed because + * the DMA interrupt handler moves the info->xmit values. the memcpy + * needs to be in the critical region unfortunately, because we + * need to read xmit values, memcpy, write xmit values in one + * atomic operation... this could perhaps be avoided by more clever + * design. + */ + while (count) { + c = CIRC_SPACE_TO_END(info->xmit.head, + info->xmit.tail, + SERIAL_XMIT_SIZE); + + if (count < c) + c = count; + if (c <= 0) + break; + + memcpy(info->xmit.buf + info->xmit.head, buf, c); + info->xmit.head = (info->xmit.head + c) & + (SERIAL_XMIT_SIZE-1); + buf += c; + count -= c; + ret += c; + } + local_irq_restore(flags); + + /* enable transmitter if not running, unless the tty is stopped + * this does not need IRQ protection since if tr_running == 0 + * the IRQ's are not running anyway for this port. + */ + + if (info->xmit.head != info->xmit.tail && + !tty->stopped && + !tty->hw_stopped && + !info->tr_running) { + start_transmit(info); + } + + return ret; +} /* raw_write() */ + +static int +rs_write(struct tty_struct * tty, + const unsigned char *buf, int count) +{ + /* RS-485 is taken care of by hardware. + * but we don't support waiting before we transmit. + */ + count = raw_write(tty, buf, count); + return count; +} /* rs_write */ + + +/* how much space is available in the xmit buffer? */ + +static int +rs_write_room(struct tty_struct *tty) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; + + return CIRC_SPACE(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); +} + +/* How many chars are in the xmit buffer? + * This does not include any chars in the transmitter FIFO. + * Use wait_until_sent for waiting for FIFO drain. + */ + +static int +rs_chars_in_buffer(struct tty_struct *tty) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; + + return CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); +} + +/* discard everything in the xmit buffer */ + +static void +rs_flush_buffer(struct tty_struct *tty) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; + unsigned long flags; + + local_irq_save(flags); + info->xmit.head = info->xmit.tail = 0; + local_irq_restore(flags); + + wake_up_interruptible(&tty->write_wait); + + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); +} + +/* + * This function is used to send a high-priority XON/XOFF character to + * the device + * + * Since we don't bother to check for info->x_char in transmit_chars yet, + * we don't really implement this function yet. + */ +static void rs_send_xchar(struct tty_struct *tty, char ch) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; + + info->x_char = ch; + if (ch) { + if (info->regi_dmaout) { + /* DMA is used, just write the char manually + * and it will be sent after any ongoing char + */ + reg_ser_rw_dout dout = { .data = info->x_char }; + REG_WR(ser, info->regi_ser, rw_dout, dout); + info->icount.tx++; + info->x_char = 0; + } else { + /* No DMA - Make sure transmit interrupts are on */ + etraxfs_enable_serial_data_tx_irq(info); + } + } +} + +/* + * ------------------------------------------------------------ + * rs_throttle() + * + * This routine is called by the upper-layer tty layer to signal that + * incoming characters should be throttled. + * ------------------------------------------------------------ + */ +static void +rs_throttle(struct tty_struct * tty) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; +#ifdef SERIAL_DEBUG_THROTTLE + char buf[64]; + + printk("throttle %s: %d....\n", tty_name(tty, buf), + (int)tty->ldisc.chars_in_buffer(tty)); +#endif + + if (I_IXOFF(tty)) + rs_send_xchar(tty, STOP_CHAR(tty)); + + /* Turn off RTS line */ + if (tty->termios->c_cflag & CRTSCTS) + etraxfs_rts(info, 0); +} + +static void +rs_unthrottle(struct tty_struct * tty) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; +#ifdef SERIAL_DEBUG_THROTTLE + char buf[64]; + + printk("unthrottle %s: %d....\n", tty_name(tty, buf), + (int)tty->ldisc.chars_in_buffer(tty)); +#endif + + if (I_IXOFF(tty)) { + if (info->x_char) + info->x_char = 0; + else + rs_send_xchar(tty, START_CHAR(tty)); + } + + /* Assert RTS line (do this atomic) */ + if (tty->termios->c_cflag & CRTSCTS) + etraxfs_rts(info, 1); +} + +/* + * ------------------------------------------------------------ + * rs_ioctl() and friends + * ------------------------------------------------------------ + */ + +static int +get_serial_info(struct etraxfs_serial * info, + struct serial_struct * retinfo) +{ + struct serial_struct tmp; + + /* this is all probably wrong, there are a lot of fields + * here that we don't have in etraxfs_serial and maybe we + * should set them to something else than 0. + */ + + if (!retinfo) + return -EFAULT; + memset(&tmp, 0, sizeof(tmp)); + tmp.type = info->type; + tmp.line = info->line; + tmp.port = (int)info->regi_ser; + tmp.irq = info->irq; + tmp.flags = info->flags; + tmp.close_delay = info->close_delay; + tmp.closing_wait = info->closing_wait; + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int +set_serial_info(struct etraxfs_serial *info, + struct serial_struct *new_info) +{ + struct serial_struct new_serial; + struct etraxfs_serial old_info; + int retval = 0; + + if (copy_from_user(&new_serial, new_info, sizeof(new_serial))) + return -EFAULT; + + old_info = *info; + + if (!capable(CAP_SYS_ADMIN)) { + if ((new_serial.type != info->type) || + (new_serial.close_delay != info->close_delay) || + ((new_serial.flags & ~ASYNC_USR_MASK) != + (info->flags & ~ASYNC_USR_MASK))) + return -EPERM; + info->flags = ((info->flags & ~ASYNC_USR_MASK) | + (new_serial.flags & ASYNC_USR_MASK)); + goto check_and_exit; + } + + if (info->count > 1) + return -EBUSY; + + /* + * OK, past this point, all the error checking has been done. + * At this point, we start making changes..... + */ + + info->flags = ((info->flags & ~ASYNC_FLAGS) | + (new_serial.flags & ASYNC_FLAGS)); + info->type = new_serial.type; + info->close_delay = new_serial.close_delay; + info->closing_wait = new_serial.closing_wait; + info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0; + + check_and_exit: + if (info->flags & ASYNC_INITIALIZED) { + change_speed(info); + } else + retval = startup(info); + return retval; +} + +/* + * get_lsr_info - get line status register info + * + * Purpose: Let user call ioctl() to get info when the UART physically + * is emptied. On bus types like RS485, the transmitter must + * release the bus after transmitting. This must be done when + * the transmit shift register is empty, not be done when the + * transmit holding register is empty. This functionality + * allows an RS485 driver to be written in user space. + */ +static int +get_lsr_info(struct etraxfs_serial * info, unsigned int *value) +{ + unsigned int result = TIOCSER_TEMT; + reg_ser_r_stat_din rstat; + rstat = REG_RD(ser, info->regi_ser, r_stat_din); + if (info->xmit.head != info->xmit.tail || !rstat.tr_empty) + result = 0; + + if (copy_to_user(value, &result, sizeof(int))) + return -EFAULT; + return 0; +} + +#ifdef SERIAL_DEBUG_IO +struct state_str +{ + int state; + const char *str; +}; + +const struct state_str control_state_str[] = { + {TIOCM_DTR, "DTR" }, + {TIOCM_RTS, "RTS"}, + {TIOCM_ST, "ST?" }, + {TIOCM_SR, "SR?" }, + {TIOCM_CTS, "CTS" }, + {TIOCM_CD, "CD" }, + {TIOCM_RI, "RI" }, + {TIOCM_DSR, "DSR" }, + {0, NULL } +}; + +char *get_control_state_str(int MLines, char *s) +{ + int i = 0; + + s[0]='\0'; + while (control_state_str[i].str != NULL) { + if (MLines & control_state_str[i].state) { + if (s[0] != '\0') { + strcat(s, ", "); + } + strcat(s, control_state_str[i].str); + } + i++; + } + return s; +} +#endif + +static int +get_modem_info(struct etraxfs_serial * info, unsigned int *value) +{ + unsigned int result; + /* Polarity isn't verified */ +#if 0 /*def SERIAL_DEBUG_IO */ + + printk("get_modem_info: RTS: %i DTR: %i CD: %i RI: %i DSR: %i CTS: %i\n", + ETRAXFS_RTS_GET(info), + ETRAXFS_DTR_GET(info), + ETRAXFS_CD_GET(info), + ETRAXFS_RI_GET(info), + ETRAXFS_DSR_GET(info), + ETRAXFS_CTS_GET(info)); +#endif + + result = + (!ETRAXFS_RTS_GET(info) ? TIOCM_RTS : 0) + | (!ETRAXFS_DTR_GET(info) ? TIOCM_DTR : 0) + | (!ETRAXFS_RI_GET(info) ? TIOCM_RNG : 0) + | (!ETRAXFS_DSR_GET(info) ? TIOCM_DSR : 0) + | (!ETRAXFS_CD_GET(info) ? TIOCM_CAR : 0) + | (!ETRAXFS_CTS_GET(info) ? TIOCM_CTS : 0); + +#ifdef SERIAL_DEBUG_IO + printk("etraxfsser: modem state: %i 0x%08X\n", result, result); + { + char s[100]; + + get_control_state_str(result, s); + printk("state: %s\n", s); + } +#endif + if (copy_to_user(value, &result, sizeof(int))) + return -EFAULT; + return 0; +} + + +static int +set_modem_info(struct etraxfs_serial * info, unsigned int cmd, + unsigned int *value) +{ + unsigned int arg; + + if (copy_from_user(&arg, value, sizeof(int))) + return -EFAULT; + + switch (cmd) { + case TIOCMBIS: + if (arg & TIOCM_RTS) { + etraxfs_rts(info, 1); + } + if (arg & TIOCM_DTR) { + etraxfs_dtr(info, 1); + } + /* Handle FEMALE behaviour */ + if (arg & TIOCM_RI) { + etraxfs_ri_out(info, 1); + } + if (arg & TIOCM_CD) { + etraxfs_cd_out(info, 1); + } + break; + case TIOCMBIC: + if (arg & TIOCM_RTS) { + etraxfs_rts(info, 0); + } + if (arg & TIOCM_DTR) { + etraxfs_dtr(info, 0); + } + /* Handle FEMALE behaviour */ + if (arg & TIOCM_RI) { + etraxfs_ri_out(info, 0); + } + if (arg & TIOCM_CD) { + etraxfs_cd_out(info, 0); + } + break; + case TIOCMSET: + etraxfs_rts(info, arg & TIOCM_RTS); + etraxfs_dtr(info, arg & TIOCM_DTR); + /* Handle FEMALE behaviour */ + etraxfs_ri_out(info, arg & TIOCM_RI); + etraxfs_cd_out(info, arg & TIOCM_CD); + break; + default: + return -EINVAL; + } + return 0; +} + + +static void +rs_break(struct tty_struct *tty, int break_state) +{ + struct etraxfs_serial * info = (struct etraxfs_serial *)tty->driver_data; + unsigned long flags; + reg_ser_rw_tr_ctrl tr_ctrl; + reg_ser_rw_tr_dma_en tr_dma_en; + reg_ser_r_stat_din rstat; + + if (!info->regi_ser) + return; + + if (break_state == -1) { /* Send break */ + /* Stop transmission */ + local_irq_save(flags); + tr_ctrl = REG_RD(ser, info->regi_ser, rw_tr_ctrl); + tr_dma_en = REG_RD(ser, info->regi_ser, rw_tr_dma_en); + tr_ctrl.stop = 1; + /* Disable DMA if used or tr_rdy interrupts if no DMA */ + if (info->regi_dmaout) { + tr_dma_en.en = 0; + } else{ + /* Disable tr_rdy interrupt so we don't have + * to wait until all sent + */ + etraxfs_disable_serial_data_tx_irq(info); + } + REG_WR(ser, info->regi_ser, rw_tr_ctrl, tr_ctrl); + REG_WR(ser, info->regi_ser, rw_tr_dma_en, tr_dma_en); + local_irq_restore(flags); + + /* Wait for current data sent so we don't cripple it */ + do { + rstat = REG_RD(ser, info->regi_ser, r_stat_din); + }while(!rstat.tr_empty); + + /* Go to manual mode and set the txd pin to 0 */ + local_irq_save(flags); + tr_ctrl = REG_RD(ser, info->regi_ser, rw_tr_ctrl); + tr_ctrl.stop = 1; + tr_ctrl.txd = 0; + } else { + local_irq_save(flags); + tr_ctrl = REG_RD(ser, info->regi_ser, rw_tr_ctrl); + tr_dma_en = REG_RD(ser, info->regi_ser, rw_tr_dma_en); + if (info->regi_dmaout) { + tr_dma_en.en = 1; + } else { + etraxfs_enable_serial_data_tx_irq(info); + } + + tr_ctrl.stop = 0; + tr_ctrl.txd = 1; + } + REG_WR(ser, info->regi_ser, rw_tr_ctrl, tr_ctrl); + REG_WR(ser, info->regi_ser, rw_tr_dma_en, tr_dma_en); + local_irq_restore(flags); +} + +static int +rs_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + struct etraxfs_serial * info = (struct etraxfs_serial *)tty->driver_data; + + if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && + (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGWILD) && + (cmd != TIOCSERSWILD) && (cmd != TIOCSERGSTRUCT)) { + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + } + + switch (cmd) { + case TIOCMGET: + return get_modem_info(info, (unsigned int *) arg); + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + return set_modem_info(info, cmd, (unsigned int *) arg); + case TIOCGSERIAL: + return get_serial_info(info, + (struct serial_struct *) arg); + case TIOCSSERIAL: + return set_serial_info(info, + (struct serial_struct *) arg); + case TIOCSERGETLSR: /* Get line status register */ + return get_lsr_info(info, (unsigned int *) arg); + + case TIOCSERGSTRUCT: + if (copy_to_user((struct etraxfs_serial *) arg, + info, sizeof(struct etraxfs_serial))) + return -EFAULT; + return 0; + +#if defined(CONFIG_ETRAX_RS485) + case TIOCSERSETRS485: + { + struct rs485_control rs485ctrl; + if (copy_from_user(&rs485ctrl, (struct rs485_control*)arg, sizeof(rs485ctrl))) + return -EFAULT; + + return etraxfs_enable_rs485(tty, &rs485ctrl); + } + + case TIOCSERWRRS485: + { + struct rs485_write rs485wr; + if (copy_from_user(&rs485wr, (struct rs485_write*)arg, sizeof(rs485wr))) + return -EFAULT; + + return etraxfs_write_rs485(tty, 1, rs485wr.outc, rs485wr.outc_size); + } +#endif + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static void +rs_set_termios(struct tty_struct *tty, struct termios *old_termios) +{ + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; + + if (tty->termios->c_cflag == old_termios->c_cflag && + tty->termios->c_iflag == old_termios->c_iflag) + return; + + change_speed(info); + + if ((old_termios->c_cflag & CRTSCTS) && + !(tty->termios->c_cflag & CRTSCTS)) { + tty->hw_stopped = 0; + rs_start(tty); + } + +} + +/* + * ------------------------------------------------------------ + * rs_close() + * + * This routine is called when the serial port gets closed. First, we + * wait for the last remaining data to be sent. Then, we unlink its + * S structure from the interrupt chain if necessary, and we free + * that IRQ if nothing is left in the chain. + * ------------------------------------------------------------ + */ +static void +rs_close(struct tty_struct *tty, struct file * filp) +{ + struct etraxfs_serial * info = (struct etraxfs_serial *)tty->driver_data; + unsigned long flags; + + if (!info) + return; + + /* interrupts are disabled for this entire function */ + + local_irq_save(flags); + + if (tty_hung_up_p(filp)) { + local_irq_restore(flags); + return; + } + +#ifdef SERIAL_DEBUG_OPEN + printk("[%d] rs_close ttyS%d, count = %d\n", current->pid, + info->line, info->count); +#endif + if ((tty->count == 1) && (info->count != 1)) { + /* + * Uh, oh. tty->count is 1, which means that the tty + * structure will be freed. Info->count should always + * be one in these conditions. If it's greater than + * one, we've got real problems, since it means the + * serial port won't be shutdown. + */ + printk("rs_close: bad serial port count; tty->count is 1, " + "info->count is %d\n", info->count); + info->count = 1; + } + if (--info->count < 0) { + printk("rs_close: bad serial port count for ttyS%d: %d\n", + info->line, info->count); + info->count = 0; + } + if (info->count) { + local_irq_restore(flags); + return; + } + info->flags |= ASYNC_CLOSING; + /* + * Now we wait for the transmit buffer to clear; and we notify + * the line discipline to only process XON/XOFF characters. + */ + tty->closing = 1; + if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) + tty_wait_until_sent(tty, info->closing_wait); + /* + * At this point we stop accepting input. To do this, we + * disable the serial receiver and the DMA receive interrupt. + */ + + etraxfs_disable_serial_data_rx_irq(info); + etraxfs_disable_rx(info); + etraxfs_disable_rxdma_irq(info); + info->tx_started = 0; + + if (info->flags & ASYNC_INITIALIZED) { + /* + * Before we drop DTR, make sure the UART transmitter + * has completely drained; this is especially + * important as we have a transmit FIFO! + */ + rs_wait_until_sent(tty, HZ); + } + + shutdown(info); + if (tty->driver->flush_buffer) + tty->driver->flush_buffer(tty); + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(tty); + tty->closing = 0; + info->event = 0; + info->tty = 0; + if (info->blocked_open) { + if (info->close_delay) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(info->close_delay); + } + wake_up_interruptible(&info->open_wait); + } + info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING); + wake_up_interruptible(&info->close_wait); + local_irq_restore(flags); + + /* port closed */ + +#if defined(CONFIG_ETRAX_RS485) + if (info->rs485.enabled) { + info->rs485.enabled = 0; + } +#endif +} + +/* + * rs_wait_until_sent() --- wait until the transmitter is empty + */ +static void rs_wait_until_sent(struct tty_struct *tty, int timeout) +{ + unsigned long orig_jiffies; + struct etraxfs_serial *info = (struct etraxfs_serial *)tty->driver_data; + reg_ser_r_stat_din rstat; + + orig_jiffies = jiffies; + rstat = REG_RD(ser, info->regi_ser, r_stat_din); + while (info->xmit.head != info->xmit.tail || /* More in send queue */ + (!rstat.tr_empty)) { /* transmitter not empty */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + if (signal_pending(current)) + break; + if (timeout && time_after(jiffies, orig_jiffies + timeout)) + break; + rstat = REG_RD(ser, info->regi_ser, r_stat_din); + } + set_current_state(TASK_RUNNING); +} + +/* + * rs_hangup() --- called by tty_hangup() when a hangup is signaled. + */ +void +rs_hangup(struct tty_struct *tty) +{ + struct etraxfs_serial * info = (struct etraxfs_serial *)tty->driver_data; + + rs_flush_buffer(tty); + shutdown(info); + info->event = 0; + info->count = 0; + info->flags &= ~ASYNC_NORMAL_ACTIVE; + info->tty = 0; + wake_up_interruptible(&info->open_wait); +} + +/* + * ------------------------------------------------------------ + * rs_open() and friends + * ------------------------------------------------------------ + */ +static int +block_til_ready(struct tty_struct *tty, struct file * filp, + struct etraxfs_serial *info) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int retval; + int do_clocal = 0, extra_count = 0; + + /* + * If the device is in the middle of being closed, then block + * until it's done, and then try again. + */ + if (tty_hung_up_p(filp) || + (info->flags & ASYNC_CLOSING)) { + if (info->flags & ASYNC_CLOSING) + wait_event_interruptible(info->close_wait, 0); +#ifdef SERIAL_DO_RESTART + if (info->flags & ASYNC_HUP_NOTIFY) + return -EAGAIN; + else + return -ERESTARTSYS; +#else + return -EAGAIN; +#endif + } + + /* + * If non-blocking mode is set, or the port is not enabled, + * then make the check up front and then exit. + */ + if ((filp->f_flags & O_NONBLOCK) || + (tty->flags & (1 << TTY_IO_ERROR))) { + info->flags |= ASYNC_NORMAL_ACTIVE; + return 0; + } + + if (tty->termios->c_cflag & CLOCAL) + do_clocal = 1; + + /* + * Block waiting for the carrier detect and the line to become + * free (i.e., not in use by the callout). While we are in + * this loop, info->count is dropped by one, so that + * rs_close() knows when to free things. We restore it upon + * exit, either normal or abnormal. + */ + retval = 0; + add_wait_queue(&info->open_wait, &wait); +#ifdef SERIAL_DEBUG_OPEN + printk("block_til_ready before block: ttyS%d, count = %d\n", + info->line, info->count); +#endif + local_irq_save(flags); + if (!tty_hung_up_p(filp)) { + extra_count++; + info->count--; + } + local_irq_restore(flags); + info->blocked_open++; + while (1) { + /* assert RTS and DTR */ + etraxfs_rts(info, 1); + etraxfs_dtr(info, 1); + local_irq_restore(flags); + set_current_state(TASK_INTERRUPTIBLE); + if (tty_hung_up_p(filp) || + !(info->flags & ASYNC_INITIALIZED)) { +#ifdef SERIAL_DO_RESTART + if (info->flags & ASYNC_HUP_NOTIFY) + retval = -EAGAIN; + else + retval = -ERESTARTSYS; +#else + retval = -EAGAIN; +#endif + break; + } + if (!(info->flags & ASYNC_CLOSING) && do_clocal) + /* && (do_clocal || DCD_IS_ASSERTED) */ + break; + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } +#ifdef SERIAL_DEBUG_OPEN + printk("block_til_ready blocking: ttyS%d, count = %d\n", + info->line, info->count); +#endif + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&info->open_wait, &wait); + if (extra_count) + info->count++; + info->blocked_open--; +#ifdef SERIAL_DEBUG_OPEN + printk("block_til_ready after blocking: ttyS%d, count = %d\n", + info->line, info->count); +#endif + if (retval) + return retval; + info->flags |= ASYNC_NORMAL_ACTIVE; + return 0; +} + +/* + * This routine is called whenever a serial port is opened. + * It performs the serial-specific initialization for the tty structure. + */ +static int +rs_open(struct tty_struct *tty, struct file * filp) +{ + struct etraxfs_serial *info; + int retval, line; + unsigned long page; + + /* find which port we want to open */ + + line = tty->index; + + if (line < 0 || line >= NR_PORTS) + return -ENODEV; + + /* find the corresponding etraxfs_serial struct in the table */ + info = rs_table + line; + + /* don't allow the opening of ports that are not enabled in the HW config */ + if (!info->regi_ser) + return -ENODEV; + +#ifdef SERIAL_DEBUG_OPEN + printk("[%d] rs_open %s, count = %d\n", current->pid, tty->name, + info->count); +#endif + + info->count++; + tty->driver_data = info; + info->tty = tty; + + info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0; + + if (!tmp_buf) { + page = get_zeroed_page(GFP_KERNEL); + if (!page) { + return -ENOMEM; + } + if (tmp_buf) + free_page(page); + else + tmp_buf = (unsigned char *) page; + } + + /* + * If the port is in the middle of closing, bail out now + */ + if (tty_hung_up_p(filp) || + (info->flags & ASYNC_CLOSING)) { + if (info->flags & ASYNC_CLOSING) + wait_event_interruptible(info->close_wait, 0); +#ifdef SERIAL_DO_RESTART + return ((info->flags & ASYNC_HUP_NOTIFY) ? + -EAGAIN : -ERESTARTSYS); +#else + return -EAGAIN; +#endif + } + + /* + * Start up the serial port + */ + + retval = startup(info); + if (retval) + return retval; + + retval = block_til_ready(tty, filp, info); + if (retval) { +#ifdef SERIAL_DEBUG_OPEN + printk("rs_open returning after block_til_ready with %d\n", + retval); +#endif + return retval; + } + +#ifdef SERIAL_DEBUG_OPEN + printk("rs_open ttyS%d successful...\n", info->line); +#endif + return 0; +} + +/* + * /proc fs routines.... + */ + +extern inline int line_info(char *buf, struct etraxfs_serial *info) +{ + char stat_buf[30]; + int ret; + + ret = sprintf(buf, "%d: uart:ETRAXFS port:%lX irq:%d", + info->line, (unsigned long)info->regi_ser, info->irq); + + if (!info->regi_ser || (info->type == PORT_UNKNOWN)) { + ret += sprintf(buf+ret, "\n"); + return ret; + } + + stat_buf[0] = 0; + stat_buf[1] = 0; + if (!ETRAXFS_RTS_GET(info)) + strcat(stat_buf, "|RTS"); + if (!ETRAXFS_CTS_GET(info)) + strcat(stat_buf, "|CTS"); + if (!ETRAXFS_DTR_GET(info)) + strcat(stat_buf, "|DTR"); + if (!ETRAXFS_DSR_GET(info)) + strcat(stat_buf, "|DSR"); + if (!ETRAXFS_CD_GET(info)) + strcat(stat_buf, "|CD"); + if (!ETRAXFS_RI_GET(info)) + strcat(stat_buf, "|RI"); + + ret += sprintf(buf+ret, " baud:%d", info->obaud); + if (info->ibaud != info->obaud) + ret += sprintf(buf+ret, " ibaud:%d", info->ibaud); + + ret += sprintf(buf+ret, " tx:%lu rx:%lu", + (unsigned long)info->icount.tx, + (unsigned long)info->icount.rx); + + ret += sprintf(buf+ret, " rx_pend:%lu/%lu", + (unsigned long)info->recv_cnt, + (unsigned long)info->max_recv_cnt); + + if (info->icount.frame) + ret += sprintf(buf+ret, " fe:%lu", + (unsigned long)info->icount.frame); + + if (info->icount.parity) + ret += sprintf(buf+ret, " pe:%lu", + (unsigned long)info->icount.parity); + + if (info->icount.brk) + ret += sprintf(buf+ret, " brk:%lu", + (unsigned long)info->icount.brk); + + if (info->icount.overrun) + ret += sprintf(buf+ret, " oe:%lu", + (unsigned long)info->icount.overrun); + + /* + * Last thing is the RS-232 status lines + */ + ret += sprintf(buf+ret, " %s\n", stat_buf+1); + return ret; +} + +int rs_read_proc(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + int i, len = 0, l; + off_t begin = 0; + + len += sprintf(page, "serinfo:1.0 driver:%s\n", + serial_version); + for (i = 0; i < NR_PORTS && len < 4000; i++) { + if (!rs_table[i].regi_ser) + continue; + l = line_info(page + len, &rs_table[i]); + len += l; + if (len+begin > off+count) + goto done; + if (len+begin < off) { + begin += len; + len = 0; + } + } + *eof = 1; +done: + if (off >= len+begin) + return 0; + *start = page + (off-begin); + return ((count < begin+len-off) ? count : begin+len-off); +} + +/* Finally, routines used to initialize the serial driver. */ + +static void +show_serial_version(void) +{ + printk("ETRAX FS serial-driver %s, (c) 2003 Axis Communications AB\r\n", + &serial_version[11]); /* "$Revision: x.yy" */ +} + +/* rs_init inits the driver at boot (using the module_init chain) */ + +static struct tty_operations rs_ops = { + .open = rs_open, + .close = rs_close, + .write = rs_write, + .flush_chars = rs_flush_chars, + .write_room = rs_write_room, + .chars_in_buffer = rs_chars_in_buffer, + .flush_buffer = rs_flush_buffer, + .ioctl = rs_ioctl, + .throttle = rs_throttle, + .unthrottle = rs_unthrottle, + .set_termios = rs_set_termios, + .stop = rs_stop, + .start = rs_start, + .hangup = rs_hangup, + .break_ctl = rs_break, + .send_xchar = rs_send_xchar, + .wait_until_sent = rs_wait_until_sent, + .read_proc = rs_read_proc, +}; + +static int __init +rs_init(void) +{ + int i; + struct etraxfs_serial *info; + struct tty_driver *driver = alloc_tty_driver(NR_PORTS); + reg_intr_vect_rw_mask intr_mask; + unsigned long flags; + + if (!driver) + return -ENOMEM; + + show_serial_version(); + + /* Setup the timed flush handler system */ + + /* Initialize the tty_driver structure */ + + driver->driver_name = "serial"; + driver->name = "ttyS"; + driver->major = TTY_MAJOR; + driver->minor_start = 64; + driver->type = TTY_DRIVER_TYPE_SERIAL; + driver->subtype = SERIAL_TYPE_NORMAL; + driver->init_termios = tty_std_termios; + driver->init_termios.c_cflag = + B115200 | CS8 | CREAD | HUPCL | CLOCAL; /* is normally B9600 default... */ + driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS; + tty_set_operations(driver, &rs_ops); + if (tty_register_driver(driver)) + panic("Couldn't register serial driver\n"); + serial_driver = driver; + + /* do some initializing for the separate ports */ + + for (i = 0, info = rs_table; i < NR_PORTS; i++,info++) { + info->line = i; + info->tty = 0; + info->type = PORT_ETRAX; + info->tr_running = 0; + info->forced_eop = 0; + info->flags = 0; + info->close_delay = 5*HZ/10; + info->closing_wait = 30*HZ; + info->x_char = 0; + info->event = 0; + info->count = 0; + info->blocked_open = 0; + init_waitqueue_head(&info->open_wait); + init_waitqueue_head(&info->close_wait); + info->xmit.buf = NULL; + info->xmit.tail = info->xmit.head = 0; + info->first_recv_buffer = info->last_recv_buffer = NULL; + info->recv_cnt = info->max_recv_cnt = 0; + info->last_tx_active_usec = 0; + info->last_tx_active = 0; + +#if defined(CONFIG_ETRAX_RS485) + /* Set sane defaults */ + info->rs485.rts_on_send = 0; + info->rs485.rts_after_sent = 1; + info->rs485.delay_rts_before_send = 0; + info->rs485.enabled = 0; +#endif + INIT_WORK(&info->work, do_softint, info); + + if (info->regi_ser) { + printk(KERN_INFO "%s%d at 0x%x is a builtin UART %s %s\n", + serial_driver->name, info->line, + (unsigned int)info->regi_ser, + info->regi_dmain?"DMA IN":"", + info->regi_dmaout?"DMA OUT":""); + } + } +#ifdef CONFIG_ETRAX_SERIAL_PORT0 + if (request_irq(SER0_INTR_VECT, ser_interrupt, SA_SHIRQ | SA_INTERRUPT, "ser0", &rs_table[0])) + panic("irq ser0"); + local_irq_save(flags); + /* enable the ser0 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.ser0 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); + SETUP_PINS(0); +#endif +#ifdef CONFIG_ETRAX_SERIAL_PORT1 + if (crisv32_pinmux_alloc_fixed(pinmux_ser1)) + panic("pinmux ser1"); + if (request_irq(SER1_INTR_VECT, ser_interrupt, SA_SHIRQ | SA_INTERRUPT, "ser1", &rs_table[1])) + panic("irq ser1"); + local_irq_save(flags); + /* enable the ser1 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.ser1 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); + SETUP_PINS(1); +#endif +#ifdef CONFIG_ETRAX_SERIAL_PORT2 + if (crisv32_pinmux_alloc_fixed(pinmux_ser2)) + panic("pinmux ser1"); + if (request_irq(SER2_INTR_VECT, ser_interrupt, SA_SHIRQ | SA_INTERRUPT, "ser2", &rs_table[2])) + panic("irq ser2"); + local_irq_save(flags); + /* enable the ser2 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.ser2 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); + SETUP_PINS(2); +#endif +#ifdef CONFIG_ETRAX_SERIAL_PORT3 + if (crisv32_pinmux_alloc_fixed(pinmux_ser3)) + panic("pinmux ser1"); + if (request_irq(SER3_INTR_VECT, ser_interrupt, SA_SHIRQ | SA_INTERRUPT, "ser3", &rs_table[3])) + panic("irq ser3" ); + local_irq_save(flags); + /* enable the ser3 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.ser3 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); + SETUP_PINS(3); +#endif + + /* ser0 can use dma6 for tx and dma7 for rx */ +#ifdef CONFIG_ETRAX_SERIAL_PORT0_DMA6_OUT + if (request_irq(DMA6_INTR_VECT, dma_tr_interrupt, SA_INTERRUPT, "serial 0 dma tr", NULL)) + panic("irq ser0txdma"); + crisv32_request_dma(6, "ser0", DMA_PANIC_ON_ERROR, 0, dma_ser0); + local_irq_save(flags); + /* enable the dma6 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.dma6 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); +#endif +#ifdef CONFIG_ETRAX_SERIAL_PORT0_DMA7_IN + if (request_irq(DMA7_INTR_VECT, dma_rec_interrupt, SA_INTERRUPT, "serial 0 dma rec", NULL)) + panic("irq ser0rxdma"); + crisv32_request_dma(7, "ser0", DMA_PANIC_ON_ERROR, 0, dma_ser0); + local_irq_save(flags); + /* enable the dma7 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.dma7 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); +#endif + + /* ser1 can use dma4 for tx and dma5 for rx */ +#ifdef CONFIG_ETRAX_SERIAL_PORT1_DMA4_OUT + if (request_irq(DMA4_INTR_VECT, dma_tr_interrupt, SA_INTERRUPT, "serial 1 dma tr", NULL)) + panic("irq ser1txdma"); + crisv32_request_dma(4, "ser1", DMA_PANIC_ON_ERROR, 0, dma_ser1); + local_irq_save(flags); + /* enable the dma4 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.dma4 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); +#endif +#ifdef CONFIG_ETRAX_SERIAL_PORT1_DMA5_IN + if (request_irq(DMA5_INTR_VECT, dma_rec_interrupt, SA_INTERRUPT, "serial 1 dma rec", NULL)) + panic("irq ser1rxdma"); + crisv32_request_dma(5, "ser1", DMA_PANIC_ON_ERROR, 0, dma_ser1); + local_irq_save(flags); + /* enable the dma5 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.dma5 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); +#endif + /* ser2 can use dma2 for tx and dma3 for rx */ +#ifdef CONFIG_ETRAX_SERIAL_PORT2_DMA2_OUT + if (request_irq(DMA2_INTR_VECT, dma_tr_interrupt, SA_INTERRUPT, "serial 2 dma tr", NULL)) + panic("irq ser2txdma"); + crisv32_request_dma(2, "ser2", DMA_PANIC_ON_ERROR, 0, dma_ser2); + local_irq_save(flags); + /* enable the dma2 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.dma2 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); +#endif +#ifdef CONFIG_ETRAX_SERIAL_PORT2_DMA3_IN + if (request_irq(DMA3_INTR_VECT, dma_rec_interrupt, SA_INTERRUPT, "serial 2 dma rec", NULL)) + panic("irq ser2rxdma"); + crisv32_request_dma(3, "ser2", DMA_PANIC_ON_ERROR, 0, dma_ser2); + local_irq_save(flags); + /* enable the dma3 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.dma3 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); +#endif + + /* ser3 can use dma8 for tx and dma9 for rx */ +#ifdef CONFIG_ETRAX_SERIAL_PORT3_DMA8_OUT + if (request_irq(DMA8_INTR_VECT, dma_tr_interrupt, SA_INTERRUPT, "serial 3 dma tr", NULL)) + panic("irq ser3txdma"); + crisv32_request_dma(8, "ser3", DMA_PANIC_ON_ERROR, 0, dma_ser3); + local_irq_save(flags); + /* enable the dma2 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.dma8 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); +#endif +#ifdef CONFIG_ETRAX_SERIAL_PORT3_DMA9_IN + if (request_irq(DMA9_INTR_VECT, dma_rec_interrupt, SA_INTERRUPT, "serial 3 dma rec", NULL)) + panic("irq ser3rxdma"); + crisv32_request_dma(9, "ser3", DMA_PANIC_ON_ERROR, 0, dma_ser3); + local_irq_save(flags); + /* enable the dma3 irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.dma9 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + local_irq_restore(flags); +#endif + + return 0; +} + +/* this makes sure that rs_init is called during kernel boot */ + +module_init(rs_init); + +/* + * register_serial and unregister_serial allows for serial ports to be + * configured at run-time, to support PCMCIA modems. + */ +int +register_serial(struct serial_struct *req) +{ + return -1; +} + +void unregister_serial(int line) +{ +} diff -puN /dev/null drivers/serial/crisv32.h --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/drivers/serial/crisv32.h 2005-06-27 18:32:02.000000000 -0700 @@ -0,0 +1,126 @@ +/* + * serial.h: Arch-dep definitions for the ETRAX FS serial driver. + * + * Copyright (C) 2003 Axis Communications AB + */ + +#ifndef _ETRAX_SERIAL_H +#define _ETRAX_SERIAL_H + +#include +#include +#include + +#include +#include +#include + +/* Software state per channel */ + +#ifdef __KERNEL__ +/* + * This is our internal structure for each serial port's state. + * + * Many fields are paralleled by the structure used by the serial_struct + * structure. + * + * For definitions of the flags field, see tty.h + */ + +#define SERIAL_RECV_DESCRIPTORS 8 +#define SERIAL_TX_DESCRIPTORS 8 + +struct etrax_recv_buffer { + struct etrax_recv_buffer *next; + unsigned short length; + unsigned char error; + unsigned char pad; + + unsigned char buffer[0]; +}; + +struct etraxfs_serial { + reg_scope_instances regi_ser; /* Used to check if port enabled as well */ + reg_scope_instances regi_dmain; + reg_scope_instances regi_dmaout; + + int flags; /* defined in tty.h */ + /* end of fields defined in rs_table[] in .c-file */ + int forced_eop; /* a fifo eop has been forced */ + + struct crisv32_iopin dtr_pin; + struct crisv32_iopin dsr_pin; + struct crisv32_iopin ri_pin; + struct crisv32_iopin cd_pin; + + struct dma_descr_context tr_context_descr __attribute__ ((__aligned__(32))); + struct dma_descr_data tr_descr[SERIAL_TX_DESCRIPTORS] __attribute__ ((__aligned__(32))); + struct dma_descr_context rec_context_descr __attribute__ ((__aligned__(32))); + struct dma_descr_data rec_descr[SERIAL_RECV_DESCRIPTORS] __attribute__ ((__aligned__(32))); + struct dma_descr_data* first_tx_descr; + struct dma_descr_data* last_tx_descr; + int tx_started; + int cur_rec_descr; + + volatile int tr_running; /* 1 if output is running */ + + struct tty_struct *tty; + int read_status_mask; + int ignore_status_mask; + int x_char; /* xon/xoff character */ + int obaud; + int ibaud; + int close_delay; + unsigned short closing_wait; + unsigned short closing_wait2; + unsigned long event; + unsigned long last_active; + int line; + u32 irq; + int type; /* PORT_ETRAX */ + int count; /* # of fd on device */ + int blocked_open; /* # of blocked opens */ + struct circ_buf xmit; + struct etrax_recv_buffer *first_recv_buffer; + struct etrax_recv_buffer *last_recv_buffer; + unsigned int recv_cnt; + unsigned int max_recv_cnt; + + struct work_struct work; + struct async_icount icount; /* error-statistics etc.*/ +#ifdef DECLARE_WAITQUEUE + wait_queue_head_t open_wait; + wait_queue_head_t close_wait; +#else + struct wait_queue *open_wait; + struct wait_queue *close_wait; +#endif + + unsigned long char_time_usec; /* The time for 1 char, in usecs */ + unsigned long last_tx_active_usec; /* Last tx usec in the jiffies */ + unsigned long last_tx_active; /* Last tx time in jiffies */ + unsigned long last_rx_active_usec; /* Last rx usec in the jiffies */ + unsigned long last_rx_active; /* Last rx time in jiffies */ + + +#ifdef CONFIG_ETRAX_RS485 + struct rs485_control rs485; /* RS-485 support */ +#endif +}; + +/* this PORT is not in the standard serial.h. it's not actually used for + * anything since we only have one type of async serial-port anyway in this + * system. + */ + +#define PORT_ETRAX 1 + +/* + * Events are used to schedule things to happen at timer-interrupt + * time, instead of at rs interrupt time. + */ +#define RS_EVENT_WRITE_WAKEUP 0 + +#endif /* __KERNEL__ */ + +#endif /* !_ETRAX_SERIAL_H */ diff -puN drivers/serial/Makefile~cris-update-12-17-serial-port-driver drivers/serial/Makefile --- 25/drivers/serial/Makefile~cris-update-12-17-serial-port-driver 2005-06-27 18:32:02.000000000 -0700 +++ 25-akpm/drivers/serial/Makefile 2005-06-27 18:32:02.000000000 -0700 @@ -53,6 +53,7 @@ obj-$(CONFIG_SERIAL_ICOM) += icom.o obj-$(CONFIG_SERIAL_M32R_SIO) += m32r_sio.o obj-$(CONFIG_SERIAL_MPSC) += mpsc.o obj-$(CONFIG_ETRAX_SERIAL) += crisv10.o +obj-$(CONFIG_ETRAXFS_SERIAL) += crisv32.o obj-$(CONFIG_SERIAL_JSM) += jsm/ obj-$(CONFIG_SERIAL_TXX9) += serial_txx9.o obj-$(CONFIG_SERIAL_VR41XX) += vr41xx_siu.o _