summaryrefslogblamecommitdiffstats
path: root/c/src/lib/libcpu/powerpc/mpc55xx/esci/esci.c
blob: f8101d7920e582d83925deff966f4e151a331a18 (plain) (tree)
1
2
3
4
5
6
7
8
9








                                                                             
                                                                      
  







                                                          
                                        






                                
                   







                          
                    






                           
                                                               


                                                                                           




                                                                               
                                            
                                 
                                                              
                            
                                                 

                                
                                            
                                 
                                                              
                            
                                                 






                                                            
                                                 





























                                                                           
                                                                                
 

                                                                           
                                    
                          
 


                                                              
 




















                                                       
 


                                         
 

                                                                      
 

                                            
                 
         
                                      
 
 



                                                                                
 


















                                                                                 















                                                                            
                   
                                                                          



                                                                                            
                                        


                         
                     

                               
                                
                                                   

         
                                                             
                        
 
                 









                                                                            


                                                   
                                        


                                
                                            














                                                                          




                                                          


                          





                         




                                                              
                                                                   
   
                                                                       
                                                     

                                                                          
                     








                                                     

         
                 




                                                            
                      



                                                                  
                                                                            
 

                                                                                  
 


                                               



                 


                                














                                                                                   
                                        





                           
                                                  
























































                                                 





                                                 
 








                                                 
 





                                      


                         



                                   





                                
                                                              





                                                                         
                                    














                                           
                                       
                             

                                        


















































                                                                                                                    
                                
                                        
                                            
 


                                                                        








                                                                                    






                                                           
                                                

                                                                       

                                                               
                                                                  


                                                                               



                                                                               

                         
                                                                                             







                                                                                                              



                                                                          
                                        


                             
                                        



                                                                                                             

         
                  







                                                                                                               
                                        














                                                                                                              
                                        






















                                                                                                               
                                        







                                                                          


                                                                  
                                                                    













                                                                                                                 
                                        











                                                 





                                             
                                                                                               

                                            


                                                  
                                       
                                           

 

                                                  


                                                                                                        




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

/*
 * Copyright (c) 2008-2011 embedded brains GmbH.  All rights reserved.
 *
 *  embedded brains GmbH
 *  Obere Lagerstr. 30
 *  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.org/license/LICENSE.
 */

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

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

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

#include <bspopts.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)

/**
 * @brief eSCI driver table.
 */
mpc55xx_esci_driver_entry mpc55xx_esci_driver_table [MPC55XX_ESCI_NUMBER] = { {
		.regs = &ESCI_A,
		.device_name = "/dev/ttyS0",
		.use_termios = 1,
		.use_interrupts = MPC55XX_ESCI_USE_INTERRUPTS,
		.tty = NULL,
		.irq_number = MPC55XX_IRQ_ESCI(0)
	}, {
		.regs = &ESCI_B,
		.device_name = "/dev/ttyS1",
		.use_termios = 1,
		.use_interrupts = MPC55XX_ESCI_USE_INTERRUPTS,
		.tty = NULL,
		.irq_number = MPC55XX_IRQ_ESCI(1)
	}
};

/**
 * @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;
}

static inline void mpc55xx_esci_write_char(mpc55xx_esci_driver_entry *e, char c)
{
	static const union ESCI_SR_tag clear_tdre = { .B = { .TDRE = 1 } };
	volatile struct ESCI_tag *esci = e->regs;
	rtems_interrupt_level level;
	bool done = false;

	rtems_interrupt_disable(level);
	if (e->transmit_nest_level == 0) {
		union ESCI_CR1_tag cr1 = { .R = esci->CR1.R };

		if (cr1.B.TIE != 0) {
			cr1.B.TIE = 0;
			e->transmit_nest_level = 1;
			esci->CR1.R = cr1.R;
		}
	} else {
		++e->transmit_nest_level;
	}
	rtems_interrupt_enable(level);

	while (!done) {
		rtems_interrupt_disable(level);
		bool tx = e->transmit_in_progress;
		if (!tx || (tx && esci->SR.B.TDRE)) {
			esci->SR.R = clear_tdre.R;
			esci->DR.B.D = c;
			e->transmit_in_progress = true;
			done = true;
		}
		rtems_interrupt_enable(level);
	}

	rtems_interrupt_disable(level);
	if (e->transmit_nest_level > 0) {
		--e->transmit_nest_level;

		if (e->transmit_nest_level == 0) {
			union ESCI_CR1_tag cr1 = { .R = esci->CR1.R };

			cr1.B.TIE = 1;
			esci->CR1.R = cr1.R;
		}
	}
	rtems_interrupt_enable(level);
}

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_ID;
	}

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

	/* Enable interrupts */
	if (e->use_interrupts) {
		mpc55xx_esci_interrupts_enable( e);
	}

	rv = rtems_termios_set_initial_baud( e->tty, 115200);
	assert(rv == 0);

	return 0;
}

/**
 * @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_ID;
	}

	/* 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 number of chars sent on success or -1 otherwise.
 */
static int mpc55xx_esci_termios_poll_write( int minor, const char *out,
					    size_t n)
{
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];
	size_t 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 n;
}

/**
 * @brief Writes one character from @a out to port @a minor.
 *
 * @return (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, size_t n)
{
	if (n > 0) {
		mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];

		e->regs->DR.B.D = out [0];
		e->transmit_in_progress = true;
	}

	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_ID;
	}

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

	/* Interrupts */
	if (e->use_interrupts && 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;
	}

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

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

	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;
	rtems_interrupt_level level;

	/* 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 */
	rtems_interrupt_disable(level);
	status->R = active.R;
	e->transmit_in_progress = false;
	rtems_interrupt_enable(level);

	/* 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 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);
		if (sc != RTEMS_SUCCESSFUL) {
			/* FIXME */
			rtems_fatal_error_occurred(0xdeadbeef);
		}
		if (i == MPC55XX_ESCI_CONSOLE_MINOR) {
			sc = rtems_io_register_name( CONSOLE_DEVICE_NAME, major, i);
			if (sc != RTEMS_SUCCESSFUL) {
				/* FIXME */
				rtems_fatal_error_occurred(0xdeadbeef);
			}
		}
		if (e->use_termios && termios_do_init) {
			if (termios_do_init) {
				termios_do_init = 0;
				rtems_termios_initialize();
			}
			if (e->use_interrupts) {
				sc = mpc55xx_interrupt_handler_install(
					e->irq_number,
					"eSCI",
					RTEMS_INTERRUPT_UNIQUE,
					MPC55XX_ESCI_IRQ_PRIORITY,
					mpc55xx_esci_termios_interrupt_handler,
					e
				);
				if (sc != RTEMS_SUCCESSFUL) {
					/* FIXME */
					rtems_fatal_error_occurred(0xdeadbeef);
				}
			}
		}
		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;
	mpc55xx_esci_driver_entry *e = &mpc55xx_esci_driver_table [minor];

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

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

	return sc;
}

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_ID;
	}

	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_ID;
	}

	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_ID;
	}

	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) {
			if (rw->buffer [i] == '\n') {
				mpc55xx_esci_write_char( e, '\r');
			}
			mpc55xx_esci_write_char( e, rw->buffer [i]);
			++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_ID;
	}

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

	return RTEMS_NOT_DEFINED;
}

/** @} */

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

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

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

static void mpc55xx_esci_output_char_init( char c)
{
	mpc55xx_esci_termios_set_attributes( MPC55XX_ESCI_CONSOLE_MINOR, &mpc55xx_esci_termios_default);
	mpc55xx_esci_output_char( c);
	BSP_output_char = mpc55xx_esci_output_char;
}

/** @} */

BSP_output_char_function_type BSP_output_char = mpc55xx_esci_output_char_init;
BSP_polling_getchar_function_type BSP_poll_char = NULL;