blob: 19d369c9ee8403fc62afdf85a5077d35cf0ed4b3 [file] [log] [blame]
/****************************************************************************
* arch/arm/src/mx8mp/mx8mp_lowputc.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
#include <assert.h>
#include <fixedmath.h>
#include "arm_internal.h"
#include "mx8mp_config.h"
#include "mx8mp_lowputc.h"
#include "mx8mp_iomuxc.h"
#include "mx8mp_ccm.h"
#include "hardware/mx8mp_pinmux.h"
#include "hardware/mx8mp_uart.h"
#include <arch/board/board.h> /* Include last: has dependencies */
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#if defined(HAVE_UART_CONSOLE)
# if defined(CONFIG_UART1_SERIAL_CONSOLE)
# define CONSOLE_BASE MX8M_UART1
# define CONSOLE_FREQ UART1_CLK_ROOT
# define CONSOLE_BAUD CONFIG_UART1_BAUD
# define CONSOLE_BITS CONFIG_UART1_BITS
# define CONSOLE_2STOP CONFIG_UART1_2STOP
# define CONSOLE_PARITY CONFIG_UART1_PARITY
# elif defined(CONFIG_UART2_SERIAL_CONSOLE)
# define CONSOLE_BASE MX8M_UART2
# define CONSOLE_FREQ UART2_CLK_ROOT
# define CONSOLE_BAUD CONFIG_UART2_BAUD
# define CONSOLE_BITS CONFIG_UART2_BITS
# define CONSOLE_2STOP CONFIG_UART2_2STOP
# define CONSOLE_PARITY CONFIG_UART2_PARITY
# elif defined(CONFIG_UART3_SERIAL_CONSOLE)
# define CONSOLE_BASE MX8M_UART3
# define CONSOLE_FREQ UART3_CLK_ROOT
# define CONSOLE_BAUD CONFIG_UART3_BAUD
# define CONSOLE_BITS CONFIG_UART3_BITS
# define CONSOLE_2STOP CONFIG_UART3_2STOP
# define CONSOLE_PARITY CONFIG_UART3_PARITY
# elif defined(CONFIG_UART4_SERIAL_CONSOLE)
# define CONSOLE_BASE MX8M_UART4
# define CONSOLE_CLK UART4_CLK_ROOT
# define CONSOLE_BAUD CONFIG_UART4_BAUD
# define CONSOLE_BITS CONFIG_UART4_BITS
# define CONSOLE_2STOP CONFIG_UART4_2STOP
# define CONSOLE_PARITY CONFIG_UART4_PARITY
# elif defined(HAVE_UART_CONSOLE)
# error "No CONFIG_UARTn_SERIAL_CONSOLE Setting"
# endif
#endif /* HAVE_UART_CONSOLE */
/****************************************************************************
* Private Data
****************************************************************************/
#ifdef HAVE_UART_CONSOLE
static const struct uart_config_s g_console_config =
{
.baud = CONSOLE_BAUD,
.parity = CONSOLE_PARITY,
.bits = CONSOLE_BITS,
.stopbits2 = CONSOLE_2STOP
};
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
int mx8mp_uart_configure(uint32_t base,
uint32_t clk_frequency,
const struct uart_config_s *config)
{
#ifndef CONFIG_SUPPRESS_UART_CONFIG
uint64_t tmp;
uint32_t regval;
uint32_t ucr2;
uint32_t refclk;
uint32_t div;
uint32_t num;
uint32_t den;
b16_t ratio;
/* Disable the UART */
putreg32(0, base + UART_UCR1_OFFSET);
putreg32(0, base + UART_UCR2_OFFSET);
putreg32(0, base + UART_UCR3_OFFSET);
putreg32(0, base + UART_UCR4_OFFSET);
/* Wait for the UART to come out of reset */
while ((getreg32(base + UART_UCR2_OFFSET) & UART_UCR2_SRST) == 0);
/* Set up UCR2, Clearing all bits that will be configured below. */
ucr2 = getreg32(base + UART_UCR2_OFFSET);
ucr2 &= ~(UART_UCR2_WS | UART_UCR2_STPB | UART_UCR2_PREN |
UART_UCR2_PROE | UART_UCR2_IRTS | UART_UCR2_CTSC);
/* Select the number of data bits */
DEBUGASSERT(config->bits == 7 || config->bits == 8);
if (config->bits == 8)
{
ucr2 |= UART_UCR2_WS;
}
/* Select the number of stop bits */
if (config->stopbits2)
{
ucr2 |= UART_UCR2_STPB;
}
/* Select even/odd parity */
if (config->parity != 0)
{
DEBUGASSERT(config->parity == 1 || config->parity == 2);
ucr2 |= UART_UCR2_PREN;
if (config->parity == 1)
{
ucr2 |= UART_UCR2_PROE;
}
}
/* Setup hardware flow control */
regval = 0;
#if 0
if (config->hwfc)
{
/* CTS controlled by Rx FIFO */
ucr2 |= UART_UCR2_CTSC;
/* Set CTS trigger level */
regval |= 30 << UART_UCR4_CTSTL_SHIFT;
/* REVISIT: There are other relevant bits that must be managed in
* UCR1 and UCR3.
*/
}
else
#endif
{
/* Ignore RTS */
ucr2 |= UART_UCR2_IRTS;
}
putreg32(regval, base + UART_UCR4_OFFSET);
/* Setup the new UART configuration */
putreg32(ucr2, base + UART_UCR2_OFFSET);
/* Select a reference clock divider.
* REVISIT: For now we just use a divider of 2. That might not be
* optimal for very high or very low baud settings.
*/
div = 2;
refclk = (clk_frequency >> 1);
/* Set the baud.
*
* baud = REFFREQ / (16 * NUM/DEN)
* baud = REFFREQ / 16 / RATIO
* RATIO = REFREQ / 16 / baud;
*
* NUM = SCALE * RATIO
* DEN = SCALE
*
* UMBR = NUM-1
* UBIR = DEN-1;
*/
tmp = ((uint64_t)refclk << (16 - 4)) / config->baud;
DEBUGASSERT(tmp < 0x0000000100000000ll);
ratio = (b16_t)tmp;
/* Pick a scale factor that gives us about 14 bits of accuracy.
* REVISIT: Why not go all the way to 16-bits?
*/
if (ratio < b16HALF)
{
den = (1 << 15);
num = b16toi(ratio << 15);
DEBUGASSERT(num > 0);
}
else if (ratio < b16ONE)
{
den = (1 << 14);
num = b16toi(ratio << 14);
}
else if (ratio < itob16(2))
{
den = (1 << 13);
num = b16toi(ratio << 13);
}
else if (ratio < itob16(4))
{
den = (1 << 12);
num = b16toi(ratio << 12);
}
else if (ratio < itob16(8))
{
den = (1 << 11);
num = b16toi(ratio << 11);
}
else if (ratio < itob16(16))
{
den = (1 << 10);
num = b16toi(ratio << 10);
}
else if (ratio < itob16(32))
{
den = (1 << 9);
num = b16toi(ratio << 9);
}
else if (ratio < itob16(64))
{
den = (1 << 8);
num = b16toi(ratio << 8);
}
else if (ratio < itob16(128))
{
den = (1 << 7);
num = b16toi(ratio << 7);
}
else if (ratio < itob16(256))
{
den = (1 << 6);
num = b16toi(ratio << 6);
}
else if (ratio < itob16(512))
{
den = (1 << 5);
num = b16toi(ratio << 5);
}
else if (ratio < itob16(1024))
{
den = (1 << 4);
num = b16toi(ratio << 4);
}
else if (ratio < itob16(2048))
{
den = (1 << 3);
num = b16toi(ratio << 3);
}
else if (ratio < itob16(4096))
{
den = (1 << 2);
num = b16toi(ratio << 2);
}
else if (ratio < itob16(8192))
{
den = (1 << 1);
num = b16toi(ratio << 1);
}
else /* if (ratio < itob16(16384)) */
{
DEBUGASSERT(ratio < itob16(16384));
den = (1 << 0);
num = b16toi(ratio);
}
/* Reduce if possible without losing accuracy. */
while ((num & 1) == 0 && (den & 1) == 0)
{
num >>= 1;
den >>= 1;
}
/* The actual values are we write to the registers need to be
* decremented by 1. NOTE that the UBIR must be set before
* the UBMR.
*/
putreg32(den - 1, base + UART_UBIR_OFFSET);
putreg32(num - 1, base + UART_UBMR_OFFSET);
/* Fixup the divisor, the value in the UFCR register is
*
* 000 = Divide input clock by 6
* 001 = Divide input clock by 5
* 010 = Divide input clock by 4
* 011 = Divide input clock by 3
* 100 = Divide input clock by 2
* 101 = Divide input clock by 1
* 110 = Divide input clock by 7
*/
if (div == 7)
{
div = 6;
}
else
{
div = 6 - div;
}
regval = div << UART_UFCR_RFDIV_SHIFT;
/* Set the TX trigger level to interrupt when the TxFIFO has 2 or fewer
* characters. Set the RX trigger level to interrupt when the RxFIFO has
* 1 character.
*/
regval |= ((2 << UART_UFCR_TXTL_SHIFT) | (1 << UART_UFCR_RXTL_SHIFT));
putreg32(regval, base + UART_UFCR_OFFSET);
/* Selected. Selects proper input pins for serial and Infrared input
* signal. NOTE: In this chip, UARTs are used in MUXED mode, so that this
* bit should always be set.
*/
putreg32(UART_UCR3_RXDMUXSEL, base + UART_UCR3_OFFSET);
/* Enable the TX and RX */
ucr2 |= (UART_UCR2_TXEN | UART_UCR2_RXEN);
putreg32(ucr2, base + UART_UCR2_OFFSET);
/* Enable the UART */
regval = getreg32(base + UART_UCR1_OFFSET);
regval |= UART_UCR1_UARTEN;
putreg32(regval, base + UART_UCR1_OFFSET);
#endif
return OK;
}
/****************************************************************************
* Name: mx8mp_lowsetup
*
* Description:
* Called at the very beginning of _start. Performs low level
* initialization including setup of the console UART. This UART done
* early so that the serial console is available for debugging very early
* in the boot sequence.
*
****************************************************************************/
void mx8mp_lowsetup(void)
{
#ifdef HAVE_UART_CONSOLE
mx8mp_iomuxc_config(IOMUX_CONSOLE_UART_RX);
mx8mp_iomuxc_config(IOMUX_CONSOLE_UART_TX);
uint32_t clk = mx8mp_ccm_get_clock(CONSOLE_CLK);
mx8mp_uart_configure(CONSOLE_BASE, clk, &g_console_config);
#endif
}
/****************************************************************************
* Name: arm_lowputc
*
* Description:
* Output a byte with as few system dependencies as possible. This will
* even work BEFORE the console is initialized if we are booting from
* U-Boot (and the same UART is used for the console, of course.)
*
****************************************************************************/
void arm_lowputc(char ch)
{
#ifdef HAVE_UART_CONSOLE
/* Poll the TX fifo trigger level bit of the UART status register. When the
* TXFE bit is non-zero, the TX Buffer FIFO is empty.
*/
while ((getreg32(CONSOLE_BASE + UART_USR2_OFFSET) & UART_USR2_TXFE) == 0);
/* Send the character by writing it into the UART_TXD register. */
putreg32((uint32_t)ch, CONSOLE_BASE + UART_TXD_OFFSET);
#endif
}