summaryrefslogblamecommitdiffstats
path: root/c/src/lib/libcpu/powerpc/mpc55xx/esci/esci.c
blob: 8181c65ccac0c217d9b41dcd71aff9c4c54e3f99 (plain) (tree)










































                                                                             
                                                               






























                                                                                           
                                                 







































                                                                                    
                                    
 




















                                                       
         
 
 



                                                                                
 


















                                                                                 















                                                                            
                   
                                                                          











                                                                                            
                                                   

         


                                                             










                                                                            






                                                   
                                            














                                                                          




                                                          


                          





                         



















                                                                              






















                                                                          


                                















































































                                                                                   



































                                                 
                                                              










































































                                                                                                                    
                                        




                                                                        
                                                          

                                           
                                                                                          


                                                        
                                                                                                          

                                                                                            
                                                                          









                                                                       

                                                               
                                                                  


                                                                               
                                                                           

                         
                                                                                             





















                                                                                                              
                                            






























































































                                                                                                                 
                                              








                                                                                                   

                                            



                                                  
                                           








                                                  
                                   
                  
 

                                                            
                                             



                                                           
 











                                                                                                                    
/**
 * @file
 *
 * @ingroup mpc55xx_esci
 *
 * @brief Source file for the Enhanced Serial Communication Interface (eSCI).
 */

/*
 * Copyright (c) 2008
 * Embedded Brains GmbH
 * Obere Lagerstr. 30
 * D-82178 Puchheim
 * Germany
 * rtems@embedded-brains.de
 *
 * The license and distribution terms for this file may be found in the file
 * LICENSE in this distribution or at http://www.rtems.com/license/LICENSE.
 */

/* Include order is important */
#include <mpc55xx/regs.h>
#include <mpc55xx/esci.h>
#include <bsp/irq.h>

#include <unistd.h>
#include <termios.h>

#include <rtems.h>
#include <rtems/libio.h>
#include <rtems/console.h>
#include <rtems/bspIo.h>

#define RTEMS_STATUS_CHECKS_USE_PRINTK

#include <rtems/status-checks.h>

/* Evil define conflicts */
#define TERMIOS_CR1 CR1
#undef CR1
#define TERMIOS_CR2 CR2
#undef CR2

#define MPC55XX_ESCI_IRQ_PRIORITY MPC55XX_INTC_DEFAULT_PRIORITY

#define MPC55XX_ESCI_IS_MINOR_INVALD(minor) ((minor) < 0 || (minor) >= MPC55XX_ESCI_NUMBER)

#define MPC55XX_ESCI_USE_INTERRUPTS(e) (!(e)->console && (e)->use_interrupts)

/**
 * @brief eSCI driver table.
 */
mpc55xx_esci_driver_entry mpc55xx_esci_driver_table [MPC55XX_ESCI_NUMBER] = { {
		.regs = &ESCI_A,
		.device_name = "/dev/tty1",
		.use_termios = 1,
		.use_interrupts = 0,
		.console = 1,
		.tty = NULL,
		.irq_number = 146
	}, {
		.regs = &ESCI_B,
		.device_name = "/dev/tty2",
		.use_termios = 1,
		.use_interrupts = 0,
		.console = 0,
		.tty = NULL,
		.irq_number = 149
	}
};

/**
 * @brief Default termios configuration.
 */
static const struct termios mpc55xx_esci_termios_default = {
	.c_cflag = CS8 | CREAD | CLOCAL | B115200
};

/**
 * @name Low-Level
 * @{
 */

/**
 * @brief Reads one character from the receive register.
 *
 * @note Waits for the receive data register full flag.
 */
static inline uint8_t mpc55xx_esci_read_char( mpc55xx_esci_driver_entry *e)
{
	volatile union ESCI_SR_tag *status = &e->regs->SR;
	volatile union ESCI_DR_tag *data = &e->regs->DR;
	union ESCI_SR_tag sr = MPC55XX_ZERO_FLAGS;

	while (status->B.RDRF == 0) {
		/* Wait */
	}

	/* Clear flag */
	sr.B.RDRF = 1;
	status->R = sr.R;

	/* Read */
	return data->B.D;
}

/**
 * @brief Writes the character @a c to the transmit register.
 *
 * @note Waits for the transmit data register empty flag.
 */
static inline void mpc55xx_esci_write_char( mpc55xx_esci_driver_entry *e, uint8_t c)
{
	volatile union ESCI_SR_tag *status = &e->regs->SR;
	volatile union ESCI_DR_tag *data = &e->regs->DR;
	union ESCI_SR_tag sr = MPC55XX_ZERO_FLAGS;
	rtems_interrupt_level level;

	/* Set clear flag */
	sr.B.TDRE = 1;

	while (true) {
		rtems_interrupt_disable( level);
		if (status->B.TDRE != 0) {
			/* Clear flag */
			status->R = sr.R;

			/* Write */
			data->B.D = c;

			/* Done */
			rtems_interrupt_enable( level);
			break;
		}
		rtems_interrupt_enable( level);

		while (status->B.TDRE == 0) {
			/* Wait */
		}
	}
}

static inline void mpc55xx_esci_interrupts_enable( mpc55xx_esci_driver_entry *e)
{
	union ESCI_CR1_tag cr1 = MPC55XX_ZERO_FLAGS;
	rtems_interrupt_level level;

	rtems_interrupt_disable( level);
	cr1.R = e->regs->CR1.R;
	cr1.B.RIE = 1;
	cr1.B.TIE = 1;
	e->regs->CR1.R = cr1.R;
	rtems_interrupt_enable( level);
}

static inline void mpc55xx_esci_interrupts_disable( mpc55xx_esci_driver_entry *e)
{
	union ESCI_CR1_tag cr1 = MPC55XX_ZERO_FLAGS;
	rtems_interrupt_level level;

	rtems_interrupt_disable( level);
	cr1.R = e->regs->CR1.R;
	cr1.B.RIE = 0;
	cr1.B.TIE = 0;
	e->regs->CR1.R = cr1.R;
	rtems_interrupt_enable( level);
}

/** @} */

/**
 * @name Termios Support
 * @{
 */

/**
 * @brief Opens port @a minor.
 *
 * @return Status code.
 */
static int mpc55xx_esci_termios_first_open( int major, int minor, void *arg)
{
	int rv = 0;
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];
	struct rtems_termios_tty *tty = ((rtems_libio_open_close_args_t *) arg)->iop->data1;

	/* Check minor number */
	if (MPC55XX_ESCI_IS_MINOR_INVALD( minor)) {
		return RTEMS_INVALID_NUMBER;
	}

	/* Connect TTY */
	e->tty = tty;	

	/* Enable interrupts */
	if (MPC55XX_ESCI_USE_INTERRUPTS( e)) {
		mpc55xx_esci_interrupts_enable( e);
	}

	rv = rtems_termios_set_initial_baud( e->tty, 115200);
	RTEMS_CHECK_RV_SC( rv, "Set initial baud");

	return RTEMS_SUCCESSFUL;
}

/**
 * @brief Closes port @a minor.
 *
 * @return Status code.
 */
static int mpc55xx_esci_termios_last_close( int major, int minor, void* arg)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];

	/* Check minor number */
	if (MPC55XX_ESCI_IS_MINOR_INVALD( minor)) {
		return RTEMS_INVALID_NUMBER;
	}

	/* Disable interrupts */
	mpc55xx_esci_interrupts_disable( e);

	/* Disconnect TTY */
	e->tty = NULL;

	return RTEMS_SUCCESSFUL;
}

/**
 * @brief Reads one character from port @a minor.
 *
 * @return Returns the character or -1 on error.
 */
static int mpc55xx_esci_termios_poll_read( int minor)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];
	volatile union ESCI_SR_tag *status = &e->regs->SR;
	volatile union ESCI_DR_tag *data = &e->regs->DR;
	union ESCI_SR_tag sr = MPC55XX_ZERO_FLAGS;

	if (status->B.RDRF == 0) {
		return -1;
	}

	/* Clear flag */
	sr.B.RDRF = 1;
	status->R = sr.R;

	/* Read */
	return data->B.D;
}

/**
 * @brief Writes @a n characters from @a out to port @a minor.
 *
 * @return Returns 0 on success or -1 otherwise.
 */
static int mpc55xx_esci_termios_poll_write( int minor, const char *out, int n)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];
	int i = 0;

	/* Check minor number */
	if (MPC55XX_ESCI_IS_MINOR_INVALD( minor)) {
		return -1;
	}

	/* Write */
	for (i = 0; i < n; ++i) {
		mpc55xx_esci_write_char( e, out [i]);
	}

	return 0;
}

/**
 * @brief Writes one character from @a out to port @a minor.
 *
 * @return Returns always 0.
 *
 * @note The buffer @a out has to provide at least one character.
 * This function assumes that the transmit data register is empty.
 */
static int mpc55xx_esci_termios_write( int minor, const char *out, int n)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];

	/* Write */
	e->regs->DR.B.D = out [0];

	return 0;
}

/* FIXME, TODO */
extern uint32_t bsp_clock_speed;

/**
 * @brief Sets attributes of port @a minor according to termios attributes @a t.
 *
 * @return Status code.
 */
static int mpc55xx_esci_termios_set_attributes( int minor, const struct termios *t)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];
	volatile struct ESCI_tag *regs = e->regs;
	union ESCI_CR1_tag cr1 = MPC55XX_ZERO_FLAGS;
	union ESCI_CR2_tag cr2 = MPC55XX_ZERO_FLAGS;
	unsigned br = 0;

	/* Check minor number */
	if (MPC55XX_ESCI_IS_MINOR_INVALD( minor)) {
		return RTEMS_INVALID_NUMBER;
	}

	/* Enable module */
	cr2.B.MDIS = 0;

	/* Interrupts */
	if (MPC55XX_ESCI_USE_INTERRUPTS( e) && e->tty != NULL) {
		cr1.B.RIE = 1;
		cr1.B.TIE = 1;
	} else {
		cr1.B.RIE = 0;
		cr1.B.TIE = 0;
	}
	cr1.B.TCIE = 0;
	cr1.B.ILIE = 0;
	cr2.B.IEBERR = 0;
	cr2.B.ORIE = 0;
	cr2.B.NFIE = 0;
	cr2.B.FEIE = 0;
	cr2.B.PFIE = 0;

	/* Disable receiver wake-up standby */
	cr1.B.RWU = 0;

	/* Disable DMA channels */
	cr2.B.RXDMA = 0;
	cr2.B.TXDMA = 0;

	/* Idle line type */
	cr1.B.ILT = 0;

	/* Disable loops */
	cr1.B.LOOPS = 0;

	/* Enable or disable receiver */
	cr1.B.RE = (t->c_cflag & CREAD) ? 1 : 0;

	/* Enable transmitter */
	cr1.B.TE = 1;

	/* Baud rate */
	switch (t->c_cflag & CBAUD) {
		case B50: br = 50; break;
		case B75: br = 75; break;
		case B110: br = 110; break;
		case B134: br = 134; break;
		case B150: br = 150; break;
		case B200: br = 200; break;
		case B300: br = 300; break;
		case B600: br = 600; break;
		case B1200: br = 1200; break;
		case B1800: br = 1800; break;
		case B2400: br = 2400; break;
		case B4800: br = 4800; break;
		case B9600: br = 9600; break;
		case B19200: br = 19200; break;
		case B38400: br = 38400; break;
		case B57600: br = 57600; break;
		case B115200: br = 115200; break;
		case B230400: br = 230400; break;
		case B460800: br = 460800; break;
		default: br = 0; break;
	}
	if (br > 0) {
		br = bsp_clock_speed / (16 * br);
		br = (br > 8191) ? 8191 : br;
	} else {
		br = 0;
	}
	cr1.B.SBR = br;
		 
	/* Number of data bits */
	if ((t->c_cflag & CSIZE) != CS8) {
		return RTEMS_IO_ERROR;
	}
	cr1.B.M = 0;

	/* Parity */
	cr1.B.PE = (t->c_cflag & PARENB) ? 1 : 0;
	cr1.B.PT = (t->c_cflag & PARODD) ? 1 : 0;
	
	/* Stop bits */
	if ( t->c_cflag & CSTOPB ) {
		/* Two stop bits */
		return RTEMS_IO_ERROR;
	}

	/* Set control registers */
	regs->CR1.R = cr1.R;
	regs->CR2.R = cr2.R;

	/* Disable LIN */
	regs->LCR.R = 0;

	return RTEMS_SUCCESSFUL;
}

/**
 * @brief Interrupt handler.
 */
static void mpc55xx_esci_termios_interrupt_handler( void *arg)
{
	mpc55xx_esci_driver_entry *e = (mpc55xx_esci_driver_entry *) arg;
	volatile union ESCI_SR_tag *status = &e->regs->SR;
	volatile union ESCI_DR_tag *data = &e->regs->DR;
	union ESCI_SR_tag sr = MPC55XX_ZERO_FLAGS;
	union ESCI_SR_tag active = MPC55XX_ZERO_FLAGS;

	/* Status */
	sr.R = status->R;

	/* Receive data register full? */
	if (sr.B.RDRF != 0) {
		active.B.RDRF = 1;
	}

	/* Transmit data register empty? */
	if (sr.B.TDRE != 0) {
		active.B.TDRE = 1;
	}

	/* Clear flags */
	status->R = active.R;

	/* Enqueue */
	if (active.B.RDRF != 0) {
		char c = data->B.D;
		rtems_termios_enqueue_raw_characters( e->tty, &c, 1);
	}

	/* Dequeue */
	if (active.B.TDRE != 0) {
		rtems_termios_dequeue_characters( e->tty, 1);
	}
}

/** @} */

/**
 * @brief Termios callbacks with interrupt support.
 */
static const rtems_termios_callbacks mpc55xx_esci_termios_callbacks = {
	.firstOpen = mpc55xx_esci_termios_first_open,
	.lastClose = mpc55xx_esci_termios_last_close,
	.pollRead = NULL,
	.write = mpc55xx_esci_termios_write,
	.setAttributes = mpc55xx_esci_termios_set_attributes,
	.stopRemoteTx = NULL,
	.startRemoteTx = NULL,
	.outputUsesInterrupts = TERMIOS_IRQ_DRIVEN
};

/**
 * @brief Termios callbacks with polling functions (no interrupts).
 */
static const rtems_termios_callbacks mpc55xx_esci_termios_callbacks_polled = {
	.firstOpen = mpc55xx_esci_termios_first_open,
	.lastClose = mpc55xx_esci_termios_last_close,
	.pollRead = mpc55xx_esci_termios_poll_read,
	.write = mpc55xx_esci_termios_poll_write,
	.setAttributes = mpc55xx_esci_termios_set_attributes,
	.stopRemoteTx = NULL,
	.startRemoteTx = NULL,
	.outputUsesInterrupts = TERMIOS_POLLED
};

/**
 * @name Console Driver
 * @{
 */

rtems_device_driver console_initialize( rtems_device_major_number major, rtems_device_minor_number minor, void *arg)
{
	rtems_status_code sc = RTEMS_SUCCESSFUL;
	int console_done = 0; 
	int termios_do_init = 1; 
	rtems_device_minor_number i = 0;
	mpc55xx_esci_driver_entry *e = NULL;
	
	for (i = 0; i < MPC55XX_ESCI_NUMBER; ++i) {
		e = &mpc55xx_esci_driver_table [i];
		sc = rtems_io_register_name ( e->device_name, major, i);
		RTEMS_CHECK_SC( sc, "Register IO device");
		if (e->console) {
			if (console_done) {
				RTEMS_SYSLOG_WARNING( "Multiple console ports defined\n");
			} else {
				console_done = 1;
				if (e->use_interrupts) {
					RTEMS_SYSLOG_WARNING( "Cannot use interrupts for console port\n");
				}
				sc = rtems_io_register_name( CONSOLE_DEVICE_NAME, major, i);
				RTEMS_CHECK_SC( sc, "Register IO device");
			}
		}
		if (e->use_termios && termios_do_init) {
			if (termios_do_init) {
				termios_do_init = 0;
				rtems_termios_initialize();
			}
			if (MPC55XX_ESCI_USE_INTERRUPTS( e)) {
				sc = mpc55xx_interrupt_handler_install(
					e->irq_number,
					"eSCI",
					RTEMS_INTERRUPT_UNIQUE,
					MPC55XX_ESCI_IRQ_PRIORITY,
					mpc55xx_esci_termios_interrupt_handler,
					e
				);
				RTEMS_CHECK_SC( sc, "Install IRQ handler");
			}
		}
		mpc55xx_esci_termios_set_attributes( (int) i, &mpc55xx_esci_termios_default);
	}

	return RTEMS_SUCCESSFUL;
}

rtems_device_driver console_open( rtems_device_major_number major, rtems_device_minor_number minor, void *arg)
{
	rtems_status_code sc = RTEMS_SUCCESSFUL;
	int rv = 0;
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];

	/* Check minor number */
	if (MPC55XX_ESCI_IS_MINOR_INVALD( minor)) {
		return RTEMS_INVALID_NUMBER;
	}

	if (e->use_termios) {
		if (MPC55XX_ESCI_USE_INTERRUPTS( e)) {
			sc =  rtems_termios_open( major, minor, arg, &mpc55xx_esci_termios_callbacks);
		} else {
			sc =  rtems_termios_open( major, minor, arg, &mpc55xx_esci_termios_callbacks_polled);
		}
		RTEMS_CHECK_SC( sc, "Open");
	}

	return RTEMS_SUCCESSFUL;
}

rtems_device_driver console_close( rtems_device_major_number major, rtems_device_minor_number minor, void *arg)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];

	/* Check minor number */
	if (MPC55XX_ESCI_IS_MINOR_INVALD( minor)) {
		return RTEMS_INVALID_NUMBER;
	}

	if (e->use_termios) {
		return rtems_termios_close( arg);
	}

	return RTEMS_SUCCESSFUL;
}

rtems_device_driver console_read( rtems_device_major_number major, rtems_device_minor_number minor, void *arg)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];

	/* Check minor number */
	if (MPC55XX_ESCI_IS_MINOR_INVALD( minor)) {
		return RTEMS_INVALID_NUMBER;
	}

	if (e->use_termios) {
		return rtems_termios_read( arg);
	} else {
		rtems_libio_rw_args_t *rw = (rtems_libio_rw_args_t *) arg;
		uint32_t i = 0;
		while (i < rw->count) {
			rw->buffer [i] = mpc55xx_esci_read_char( e);
			++i;
		}
		rw->bytes_moved = i;
	}

	return RTEMS_SUCCESSFUL;
}

rtems_device_driver console_write( rtems_device_major_number major, rtems_device_minor_number minor, void *arg)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];

	/* Check minor number */
	if (MPC55XX_ESCI_IS_MINOR_INVALD( minor)) {
		return RTEMS_INVALID_NUMBER;
	}

	if (e->use_termios) {
		return rtems_termios_write( arg);
	} else {
		rtems_libio_rw_args_t *rw = (rtems_libio_rw_args_t *) arg;
		uint32_t i = 0;
		while (i < rw->count) {
			mpc55xx_esci_write_char( e, rw->buffer [i]);
			if (rw->buffer [i] == '\n') {
				mpc55xx_esci_write_char( e, '\r');
			}
			++i;
		}
		rw->bytes_moved = i;
	}

	return RTEMS_SUCCESSFUL;
}

rtems_device_driver console_control( rtems_device_major_number major, rtems_device_minor_number minor, void *arg)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];

	/* Check minor number */
	if (MPC55XX_ESCI_IS_MINOR_INVALD( minor)) {
		return RTEMS_INVALID_NUMBER;
	}

	if (e->use_termios) {
		return rtems_termios_ioctl( arg);
	}

	return RTEMS_NOT_DEFINED;
}

/** @} */

/**
 * @brief Port number for the BSP character output function.
 *
 * The correct value will be set by mpc55xx_esci_output_char_init().
 */
static int mpc55xx_esci_output_char_minor = 0;

/**
 * @name BSP Character Output
 * @{
 */

static void mpc55xx_esci_output_char( char c)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [mpc55xx_esci_output_char_minor];

	mpc55xx_esci_interrupts_disable( e);
	mpc55xx_esci_write_char( e, c);
	if (c == '\n') {
		mpc55xx_esci_write_char( e, '\r');
	}
	mpc55xx_esci_interrupts_enable( e);
}

static void mpc55xx_esci_output_char_nop( char c)
{
	/* Do nothing */
}

static void mpc55xx_esci_output_char_init( char c)
{
	bool console_found = false;
	int i = 0;

	for (i = 0; i < MPC55XX_ESCI_NUMBER; ++i) {
		if (mpc55xx_esci_driver_table [i].console) {
			console_found = true;
			mpc55xx_esci_output_char_minor = i;
			break;
		}
	}

	if (console_found) {
		BSP_output_char = mpc55xx_esci_output_char;
		mpc55xx_esci_termios_set_attributes( mpc55xx_esci_output_char_minor, &mpc55xx_esci_termios_default);
		mpc55xx_esci_output_char( c);
	} else {
		BSP_output_char = mpc55xx_esci_output_char_nop;
	}
}

/** @} */

BSP_output_char_function_type BSP_output_char = mpc55xx_esci_output_char_init;