blob: 26b3c4e7ecb7d0f03de02aec719f525d3ab343e5 [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_dev.h"
#include "sys/alt_irq.h"
#include "sys/ioctl.h"
#include "sys/alt_errno.h"
#include "altera_avalon_uart_regs.h"
#include "altera_avalon_uart.h"
#if defined(ALT_USE_SMALL_DRIVERS) || defined(ALTERA_AVALON_UART_SMALL)
/* ----------------------------------------------------------- */
/* ------------------------ SMALL DRIVER --------------------- */
/* ----------------------------------------------------------- */
/*
* altera_avalon_uart_write() is called by the system write() function in
* order to write a block of data to the UART.
* "len" is the length of the data to write,
* and "ptr" indicates the source address. "fd" is the file descriptor for the
* device to be read from.
*
* Permission checks are made before the call to altera_avalon_uart_write(), 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 written.
*
* This function will block on the devices transmit register, until all
* characters have been transmitted. This is unless the device is being
* accessed in non-blocking mode. In this case this function will return as
* soon as the device reports that it is not ready to transmit.
*
* Since this is the small footprint version of the UART driver, the value of
* CTS is ignored.
*/
int
altera_avalon_uart_write(altera_avalon_uart_state* sp, const char* ptr, int len,
int flags)
{
int block;
unsigned int status;
int count;
block = !(flags & O_NONBLOCK);
count = len;
do
{
status = IORD_ALTERA_AVALON_UART_STATUS(sp->base);
if (status & ALTERA_AVALON_UART_STATUS_TRDY_MSK)
{
IOWR_ALTERA_AVALON_UART_TXDATA(sp->base, *ptr++);
count--;
}
}
while (block && count);
if (count)
{
ALT_ERRNO = EWOULDBLOCK;
}
return (len - count);
}
#else /* Using the "fast" version of the driver */
/* ----------------------------------------------------------- */
/* ------------------------- FAST DRIVER --------------------- */
/* ----------------------------------------------------------- */
/*
* altera_avalon_uart_write() is called by the system write() function in order
* to write a block of data to the UART. "len" is the length of the data to
* write, and "ptr" indicates the source address. "sp" is the state pointer
* for the device to be written to.
*
* Permission checks are made before the call to altera_avalon_uart_write(), 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 written.
*
* This function does not communicate with the device directly. Instead data is
* transfered to a circular buffer. The interrupt handler is then responsible
* for copying data from this buffer into the device.
*/
int
altera_avalon_uart_write(altera_avalon_uart_state* sp, const char* ptr, int len,
int flags)
{
alt_irq_context context;
int no_block;
alt_u32 next;
int count = len;
/*
* Construct a flag to indicate whether the device is being accessed in
* blocking or non-blocking mode.
*/
no_block = (flags & O_NONBLOCK);
/*
* When running in a multi threaded environment, obtain the "write_lock"
* semaphore. This ensures that writing to the device is thread-safe.
*/
ALT_SEM_PEND (sp->write_lock, 0);
/*
* Loop transferring data from the input buffer to the transmit circular
* buffer. The loop is terminated once all the data has been transferred,
* or, (if in non-blocking mode) the buffer becomes full.
*/
while (count)
{
/* Determine the next slot in the buffer to access */
next = (sp->tx_end + 1) & ALT_AVALON_UART_BUF_MSK;
/* block waiting for space if necessary */
if (next == sp->tx_start)
{
if (no_block)
{
/* Set errno to indicate why this function returned early */
ALT_ERRNO = EWOULDBLOCK;
break;
}
else
{
/* Block waiting for space in the circular buffer */
/* First, ensure transmit interrupts are enabled to avoid deadlock */
context = alt_irq_disable_all ();
sp->ctrl |= (ALTERA_AVALON_UART_CONTROL_TRDY_MSK |
ALTERA_AVALON_UART_CONTROL_DCTS_MSK);
IOWR_ALTERA_AVALON_UART_CONTROL(sp->base, sp->ctrl);
alt_irq_enable_all (context);
/* wait for space to come free */
do
{
/*
* When running in a multi-threaded mode, we pend on the write 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_WRITE_RDY,
OS_FLAG_WAIT_SET_ANY + OS_FLAG_CONSUME,
0);
}
while ((next == sp->tx_start));
}
}
count--;
/* Add the next character to the transmit buffer */
sp->tx_buf[sp->tx_end] = *ptr++;
sp->tx_end = next;
}
/*
* Now that access to the circular buffer is complete, release the write
* semaphore so that other threads can access the buffer.
*/
ALT_SEM_POST (sp->write_lock);
/*
* Ensure that interrupts are enabled, so that the circular buffer can
* drain.
*/
context = alt_irq_disable_all ();
sp->ctrl |= ALTERA_AVALON_UART_CONTROL_TRDY_MSK |
ALTERA_AVALON_UART_CONTROL_DCTS_MSK;
IOWR_ALTERA_AVALON_UART_CONTROL(sp->base, sp->ctrl);
alt_irq_enable_all (context);
/* return the number of bytes written */
return (len - count);
}
#endif /* fast driver */