1665 lines · 40.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Serial driver for the amiga builtin port. * * This code was created by taking serial.c version 4.30 from kernel * release 2.3.22, replacing all hardware related stuff with the * corresponding amiga hardware actions, and removing all irrelevant * code. As a consequence, it uses many of the constants and names * associated with the registers and bits of 16550 compatible UARTS - * but only to keep track of status, etc in the state variables. It * was done this was to make it easier to keep the code in line with * (non hardware specific) changes to serial.c. * * The port is registered with the tty driver as minor device 64, and * therefore other ports should only use 65 upwards. * * Richard Lucock 28/12/99 * *  Copyright (C) 1991, 1992  Linus Torvalds *  Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997,  * 		1998, 1999  Theodore Ts'o * */ /* Set of debugging defines */ #undef SERIAL_DEBUG_INTR#undef SERIAL_DEBUG_OPEN#undef SERIAL_DEBUG_FLOW#undef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT /* * End of serial driver configuration section. */ #include <linux/bitops.h>#include <linux/circ_buf.h>#include <linux/console.h>#include <linux/delay.h>#include <linux/errno.h>#include <linux/fcntl.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/ioport.h>#include <linux/kernel.h>#include <linux/major.h>#include <linux/mm.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/ptrace.h>#include <linux/seq_file.h>#include <linux/serial.h>#include <linux/serial_reg.h>#include <linux/serial_core.h>#include <linux/sched.h>#include <linux/signal.h>#include <linux/slab.h>#include <linux/string.h>#include <linux/timer.h>#include <linux/tty_flip.h>#include <linux/tty.h>#include <linux/types.h>#include <linux/uaccess.h> #include <asm/amigahw.h>#include <asm/amigaints.h>#include <asm/irq.h>#include <asm/setup.h> struct serial_state {	struct tty_port		tport;	struct circ_buf		xmit;	struct async_icount	icount; 	unsigned long		port;	int			baud_base;	int			custom_divisor;	int			read_status_mask;	int			ignore_status_mask;	int			timeout;	int			quot;	int			IER; 	/* Interrupt Enable Register */	int			MCR; 	/* Modem control register */	u8			x_char;	/* xon/xoff character */}; static struct tty_driver *serial_driver; /* number of characters left in xmit buffer before we ask for more */#define WAKEUP_CHARS 256 #define XMIT_FIFO_SIZE 1 static unsigned char current_ctl_bits; static void change_speed(struct tty_struct *tty, struct serial_state *info,			 const struct ktermios *old);static void rs_wait_until_sent(struct tty_struct *tty, int timeout);  static struct serial_state serial_state; /* some serial hardware definitions */#define SDR_OVRUN   (1<<15)#define SDR_RBF     (1<<14)#define SDR_TBE     (1<<13)#define SDR_TSRE    (1<<12) #define SERPER_PARENB    (1<<15) #define AC_SETCLR   (1<<15)#define AC_UARTBRK  (1<<11) #define SER_DTR     (1<<7)#define SER_RTS     (1<<6)#define SER_DCD     (1<<5)#define SER_CTS     (1<<4)#define SER_DSR     (1<<3) static __inline__ void rtsdtr_ctrl(int bits){    ciab.pra = ((bits & (SER_RTS | SER_DTR)) ^ (SER_RTS | SER_DTR)) | (ciab.pra & ~(SER_RTS | SER_DTR));} /* * ------------------------------------------------------------ * rs_stop() and rs_start() * * This routines are called before setting or resetting tty->flow.stopped. * They enable or disable transmitter interrupts, as necessary. * ------------------------------------------------------------ */static void rs_stop(struct tty_struct *tty){	struct serial_state *info = tty->driver_data;	unsigned long flags; 	local_irq_save(flags);	if (info->IER & UART_IER_THRI) {		info->IER &= ~UART_IER_THRI;		/* disable Tx interrupt and remove any pending interrupts */		amiga_custom.intena = IF_TBE;		mb();		amiga_custom.intreq = IF_TBE;		mb();	}	local_irq_restore(flags);} static void rs_start(struct tty_struct *tty){	struct serial_state *info = tty->driver_data;	unsigned long flags; 	local_irq_save(flags);	if (info->xmit.head != info->xmit.tail	    && info->xmit.buf	    && !(info->IER & UART_IER_THRI)) {		info->IER |= UART_IER_THRI;		amiga_custom.intena = IF_SETCLR | IF_TBE;		mb();		/* set a pending Tx Interrupt, transmitter should restart now */		amiga_custom.intreq = IF_SETCLR | IF_TBE;		mb();	}	local_irq_restore(flags);} /* * ---------------------------------------------------------------------- * * Here start the interrupt handling routines. * * ----------------------------------------------------------------------- */ static void receive_chars(struct serial_state *info){        int status;	int serdatr;	u8 ch, flag;	struct	async_icount *icount;	bool overrun = false; 	icount = &info->icount; 	status = UART_LSR_DR; /* We obviously have a character! */	serdatr = amiga_custom.serdatr;	mb();	amiga_custom.intreq = IF_RBF;	mb(); 	if((serdatr & 0x1ff) == 0)	    status |= UART_LSR_BI;	if(serdatr & SDR_OVRUN)	    status |= UART_LSR_OE; 	ch = serdatr & 0xff;	icount->rx++; #ifdef SERIAL_DEBUG_INTR	printk("DR%02x:%02x...", ch, status);#endif	flag = TTY_NORMAL; 	/*	 * We don't handle parity or frame errors - but I have left	 * the code in, since I'm not sure that the errors can't be	 * detected.	 */ 	if (status & (UART_LSR_BI | UART_LSR_PE |		      UART_LSR_FE | UART_LSR_OE)) {	  /*	   * For statistics only	   */	  if (status & UART_LSR_BI) {	    status &= ~(UART_LSR_FE | UART_LSR_PE);	    icount->brk++;	  } else if (status & UART_LSR_PE)	    icount->parity++;	  else if (status & UART_LSR_FE)	    icount->frame++;	  if (status & UART_LSR_OE)	    icount->overrun++; 	  /*	   * Now check to see if character should be	   * ignored, and mask off conditions which	   * should be ignored.	   */	  if (status & info->ignore_status_mask)		  return; 	  status &= info->read_status_mask; 	  if (status & (UART_LSR_BI)) {#ifdef SERIAL_DEBUG_INTR	    printk("handling break....");#endif	    flag = TTY_BREAK;	    if (info->tport.flags & ASYNC_SAK)	      do_SAK(info->tport.tty);	  } else if (status & UART_LSR_PE)	    flag = TTY_PARITY;	  else if (status & UART_LSR_FE)	    flag = TTY_FRAME;	  if (status & UART_LSR_OE) {	    /*	     * Overrun is special, since it's	     * reported immediately, and doesn't	     * affect the current character	     */	     overrun = true;	  }	}	tty_insert_flip_char(&info->tport, ch, flag);	if (overrun)		tty_insert_flip_char(&info->tport, 0, TTY_OVERRUN);	tty_flip_buffer_push(&info->tport);} static void transmit_chars(struct serial_state *info){	amiga_custom.intreq = IF_TBE;	mb();	if (info->x_char) {	        amiga_custom.serdat = info->x_char | 0x100;		mb();		info->icount.tx++;		info->x_char = 0;		return;	}	if (info->xmit.head == info->xmit.tail	    || info->tport.tty->flow.stopped	    || info->tport.tty->hw_stopped) {		info->IER &= ~UART_IER_THRI;	        amiga_custom.intena = IF_TBE;		mb();		return;	} 	amiga_custom.serdat = info->xmit.buf[info->xmit.tail++] | 0x100;	mb();	info->xmit.tail = info->xmit.tail & (UART_XMIT_SIZE - 1);	info->icount.tx++; 	if (CIRC_CNT(info->xmit.head,		     info->xmit.tail,		     UART_XMIT_SIZE) < WAKEUP_CHARS)		tty_wakeup(info->tport.tty); #ifdef SERIAL_DEBUG_INTR	printk("THRE...");#endif	if (info->xmit.head == info->xmit.tail) {	        amiga_custom.intena = IF_TBE;		mb();		info->IER &= ~UART_IER_THRI;	}} static void check_modem_status(struct serial_state *info){	struct tty_port *port = &info->tport;	unsigned char status = ciab.pra & (SER_DCD | SER_CTS | SER_DSR);	unsigned char dstatus;	struct	async_icount *icount; 	/* Determine bits that have changed */	dstatus = status ^ current_ctl_bits;	current_ctl_bits = status; 	if (dstatus) {		icount = &info->icount;		/* update input line counters */		if (dstatus & SER_DSR)			icount->dsr++;		if (dstatus & SER_DCD) {			icount->dcd++;		}		if (dstatus & SER_CTS)			icount->cts++;		wake_up_interruptible(&port->delta_msr_wait);	} 	if (tty_port_check_carrier(port) && (dstatus & SER_DCD)) {#if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR))		printk("ttyS%d CD now %s...", info->line,		       (!(status & SER_DCD)) ? "on" : "off");#endif		if (!(status & SER_DCD))			wake_up_interruptible(&port->open_wait);		else {#ifdef SERIAL_DEBUG_OPEN			printk("doing serial hangup...");#endif			if (port->tty)				tty_hangup(port->tty);		}	}	if (tty_port_cts_enabled(port)) {		if (port->tty->hw_stopped) {			if (!(status & SER_CTS)) {#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW))				printk("CTS tx start...");#endif				port->tty->hw_stopped = false;				info->IER |= UART_IER_THRI;				amiga_custom.intena = IF_SETCLR | IF_TBE;				mb();				/* set a pending Tx Interrupt, transmitter should restart now */				amiga_custom.intreq = IF_SETCLR | IF_TBE;				mb();				tty_wakeup(port->tty);				return;			}		} else {			if ((status & SER_CTS)) {#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW))				printk("CTS tx stop...");#endif				port->tty->hw_stopped = true;				info->IER &= ~UART_IER_THRI;				/* disable Tx interrupt and remove any pending interrupts */				amiga_custom.intena = IF_TBE;				mb();				amiga_custom.intreq = IF_TBE;				mb();			}		}	}} static irqreturn_t ser_vbl_int( int irq, void *data){        /* vbl is just a periodic interrupt we tie into to update modem status */	struct serial_state *info = data;	/*	 * TBD - is it better to unregister from this interrupt or to	 * ignore it if MSI is clear ?	 */	if(info->IER & UART_IER_MSI)	  check_modem_status(info);	return IRQ_HANDLED;} static irqreturn_t ser_rx_int(int irq, void *dev_id){	struct serial_state *info = dev_id; #ifdef SERIAL_DEBUG_INTR	printk("ser_rx_int...");#endif 	if (!info->tport.tty)		return IRQ_NONE; 	receive_chars(info);#ifdef SERIAL_DEBUG_INTR	printk("end.\n");#endif	return IRQ_HANDLED;} static irqreturn_t ser_tx_int(int irq, void *dev_id){	struct serial_state *info = dev_id; 	if (amiga_custom.serdatr & SDR_TBE) {#ifdef SERIAL_DEBUG_INTR	  printk("ser_tx_int...");#endif 	  if (!info->tport.tty)		return IRQ_NONE; 	  transmit_chars(info);#ifdef SERIAL_DEBUG_INTR	  printk("end.\n");#endif	}	return IRQ_HANDLED;} /* * ------------------------------------------------------------------- * Here ends the serial interrupt routines. * ------------------------------------------------------------------- */ /* * --------------------------------------------------------------- * Low level utility subroutines for the serial driver:  routines to * figure out the appropriate timeout for an interrupt chain, routines * to initialize and startup a serial port, and routines to shutdown a * serial port.  Useful stuff like that. * --------------------------------------------------------------- */ static int rs_startup(struct tty_struct *tty, struct serial_state *info){	struct tty_port *port = &info->tport;	unsigned long flags;	int	retval=0;	void *buffer; 	buffer = kzalloc(PAGE_SIZE, GFP_KERNEL);	if (!buffer)		return -ENOMEM; 	local_irq_save(flags); 	if (tty_port_initialized(port)) {		kfree(buffer);		goto errout;	} 	if (info->xmit.buf)		kfree(buffer);	else		info->xmit.buf = buffer; #ifdef SERIAL_DEBUG_OPEN	printk("starting up ttys%d ...", info->line);#endif 	/* Clear anything in the input buffer */ 	amiga_custom.intreq = IF_RBF;	mb(); 	retval = request_irq(IRQ_AMIGA_VERTB, ser_vbl_int, 0, "serial status", info);	if (retval) {		if (capable(CAP_SYS_ADMIN)) {			set_bit(TTY_IO_ERROR, &tty->flags);			retval = 0;		}		goto errout;	} 	/* enable both Rx and Tx interrupts */	amiga_custom.intena = IF_SETCLR | IF_RBF | IF_TBE;	mb();	info->IER = UART_IER_MSI; 	/* remember current state of the DCD and CTS bits */	current_ctl_bits = ciab.pra & (SER_DCD | SER_CTS | SER_DSR); 	info->MCR = 0;	if (C_BAUD(tty))	  info->MCR = SER_DTR | SER_RTS;	rtsdtr_ctrl(info->MCR); 	clear_bit(TTY_IO_ERROR, &tty->flags);	info->xmit.head = info->xmit.tail = 0; 	/*	 * and set the speed of the serial port	 */

Lines 1–500 of 1665.