blob: 692966f9da98924db64093afc4b724b51d339c1e [file] [log] [blame]
/******************************************************************************
* *
* License Agreement *
* *
* Copyright (c) 2006 Altera Corporation, San Jose, California, USA. *
* All rights reserved. *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
* DEALINGS IN THE SOFTWARE. *
* *
* *
******************************************************************************/
#include <fcntl.h>
#include "sys/alt_irq.h"
#include "sys/ioctl.h"
#include "sys/alt_errno.h"
#include "altera_avalon_uart.h"
#include "altera_avalon_uart_regs.h"
#if defined(ALT_USE_SMALL_DRIVERS) || defined(ALTERA_AVALON_UART_SMALL)
/* ----------------------------------------------------------- */
/* ----------------------- SMALL DRIVER ---------------------- */
/* ----------------------------------------------------------- */
/*
* altera_avalon_uart_read() is called by the system read() function in order to
* read a block of data from the UART. "len" is the maximum length of the data
* to read, and "ptr" indicates the destination address. "fd" is the file
* descriptor for the device to be read from.
*
* Permission checks are made before the call to altera_avalon_uart_read(), so
* we know that the file descriptor has been opened with the correct permissions
* for this operation.
*
* The return value is the number of bytes actually read.
*
* This implementation polls the device waiting for characters. At most it can
* only return one character, regardless of how many are requested. If the
* device is being accessed in non-blocking mode then it is possible for this
* function to return without reading any characters. In this case errno is
* set to EWOULDBLOCK.
*/
int
altera_avalon_uart_read(altera_avalon_uart_state* sp, char* ptr, int len,
int flags)
{
int block;
unsigned int status;
block = !(flags & O_NONBLOCK);
do
{
status = IORD_ALTERA_AVALON_UART_STATUS(sp->base);
/* clear any error flags */
IOWR_ALTERA_AVALON_UART_STATUS(sp->base, 0);
if (status & ALTERA_AVALON_UART_CONTROL_RRDY_MSK)
{
ptr[0] = IORD_ALTERA_AVALON_UART_RXDATA(sp->base);
if (!(status & (ALTERA_AVALON_UART_STATUS_PE_MSK |
ALTERA_AVALON_UART_STATUS_FE_MSK)))
{
return 1;
}
}
}
while (block);
ALT_ERRNO = EWOULDBLOCK;
return 0;
}
#else
/* ----------------------------------------------------------- */
/* ----------------------- FAST DRIVER ----------------------- */
/* ----------------------------------------------------------- */
/*
* altera_avalon_uart_read() is called by the system read() function in order to
* read a block of data from the UART. "len" is the maximum length of the data
* to read, and "ptr" indicates the destination address. "sp" is the state
* pointer for the device to be read from.
*
* Permission checks are made before the call to altera_avalon_uart_read(), so
* we know that the file descriptor has been opened with the correct permissions
* for this operation.
*
* The return value is the number of bytes actually read.
*
* This function does not communicate with the device directly. Instead data is
* transfered from a circular buffer. The interrupt handler is then responsible
* for copying data from the device into this buffer.
*/
int
altera_avalon_uart_read(altera_avalon_uart_state* sp, char* ptr, int len,
int flags)
{
alt_irq_context context;
int block;
alt_u8 read_would_block = 0;
int count = 0;
/*
* Construct a flag to indicate whether the device is being accessed in
* blocking or non-blocking mode.
*/
block = !(flags & O_NONBLOCK);
/*
* When running in a multi threaded environment, obtain the "read_lock"
* semaphore. This ensures that reading from the device is thread-safe.
*/
ALT_SEM_PEND (sp->read_lock, 0);
/*
* Loop, copying data from the circular buffer to the destination address
* supplied in "ptr". This loop is terminated when the required number of
* bytes have been read. If the circular buffer is empty, and no data has
* been read, then the loop will block (when in blocking mode).
*
* If the circular buffer is empty, and some data has already been
* transferred, or the device is being accessed in non-blocking mode, then
* the loop terminates without necessarily reading all the requested data.
*/
do
{
/*
* Read the required amount of data, until the circular buffer runs
* empty
*/
while ((count < len) && (sp->rx_start != sp->rx_end))
{
count++;
*ptr++ = sp->rx_buf[sp->rx_start];
sp->rx_start = (sp->rx_start+1) & ALT_AVALON_UART_BUF_MSK;
}
/*
* If no data has been transferred, the circular buffer is empty, and
* this is not a non-blocking access, block waiting for data to arrive.
*/
if (!count && (sp->rx_start == sp->rx_end))
{
if (!block)
{
/* Set errno to indicate the reason we're not returning any data */
ALT_ERRNO = EWOULDBLOCK;
read_would_block = 1;
break;
}
else
{
/* Block waiting for some data to arrive */
/* First, ensure read interrupts are enabled to avoid deadlock */
context = alt_irq_disable_all ();
sp->ctrl |= ALTERA_AVALON_UART_CONTROL_RRDY_MSK;
IOWR_ALTERA_AVALON_UART_CONTROL(sp->base, sp->ctrl);
alt_irq_enable_all (context);
/*
* When running in a multi-threaded mode, we pend on the read event
* flag set in the interrupt service routine. This avoids wasting CPU
* cycles waiting in this thread, when we could be doing something more
* profitable elsewhere.
*/
ALT_FLAG_PEND (sp->events,
ALT_UART_READ_RDY,
OS_FLAG_WAIT_SET_ANY + OS_FLAG_CONSUME,
0);
}
}
}
while (!count && len);
/*
* Now that access to the circular buffer is complete, release the read
* semaphore so that other threads can access the buffer.
*/
ALT_SEM_POST (sp->read_lock);
/*
* Ensure that interrupts are enabled, so that the circular buffer can
* re-fill.
*/
context = alt_irq_disable_all ();
sp->ctrl |= ALTERA_AVALON_UART_CONTROL_RRDY_MSK;
IOWR_ALTERA_AVALON_UART_CONTROL(sp->base, sp->ctrl);
alt_irq_enable_all (context);
/* Return the number of bytes read */
if(read_would_block) {
return -EWOULDBLOCK;
}
else {
return count;
}
}
#endif /* fast driver */