blob: c575b03cee208322762611b00922dfb60cb3ef77 [file] [log] [blame]
#pragma once
#pragma push_macro("CHERIOT_PLATFORM_CUSTOM_UART")
#define CHERIOT_PLATFORM_CUSTOM_UART
#include_next <platform-uart.hh>
#pragma pop_macro("CHERIOT_PLATFORM_CUSTOM_UART")
/**
* The Synopsis extended 16550 UART has two additional registers that are not
* part of a standard 16550, one of which allows for fractional multipliers.
*
* This is always instantiated with 32-bit device registers. The default baud
* rate is set as a template parameter and can be overridden in `init`.
*/
template<unsigned DefaultBaudRate = 115'200>
struct SynopsisExtended16550 : public Uart16550<uint32_t>
{
/**
* Loopback register. Unused.
*/
uint32_t loopback;
/**
* Divisor latch fraction. When the divisor latch is set (for writing the
* divisor), this is used for the fraction. RS-232 requires a 3% tolerance
* for the baud rate and an integer divisor over the clock frequency of the
* host may not get within this range. This register is used to provide a
* 4-bit correction that allows baud rates to stay within the required
* range.
*/
uint32_t divisorLatchFraction;
/**
* The divisor is calculated from 16 times the baud rate. The baud rate is
* in the range of KHz to hundreds of KHz, whereas the clock frequency is
* typically tens to hundreds of MHz. The divisor must fit in a 16-bit
* integer (written across two 8-bit device registers) and so is scaled to
* ensure that it fits (by a power of two, which makes it trivial to
* reverse the multiplication in the hardware).
*/
static constexpr uint32_t BaudScaleFactorShift = 4;
/**
* The value in `divisorLatchFraction` is truncated to this many bits.
*/
static constexpr uint32_t BaudFractionBits = 4;
/**
* Initialise the UART.
*/
void init(unsigned baudRate = DefaultBaudRate,
unsigned timerFrequency = CPU_TIMER_HZ) volatile
{
// The multiplier that we will scale the baud rate by.
constexpr uint32_t Multiplier = (1 << BaudScaleFactorShift);
// The scaled baud rate is used in all of the remaining calculations.
// See `BaudScaleFactorShift` for an explanation.
uint32_t scaledBaudRate = baudRate * Multiplier;
// The divisor to be programmed into the device.
uint32_t divisor = timerFrequency / scaledBaudRate;
// The remainder of the divisor.
uint32_t remainder = timerFrequency % scaledBaudRate;
// The remainder is scaled relative to the baud rate to give a
// correction factor.
remainder = (remainder << (BaudScaleFactorShift + 1)) / scaledBaudRate;
// The remainder is rounded up to give the value that can be programmed
// into the device register.
remainder = (remainder >> 1) + (remainder & 0x1);
// Set up the base UART
Uart16550<uint32_t>::init(divisor,
[&]() { divisorLatchFraction = remainder; });
// Write a character with the low bit set. Any value is fine here, but
// if we use 0x7 (ASCII bell character) then we should get a beep if it
// goes through to the receiver and so can tell whether this worked.
blocking_write(0x7);
}
};
#ifndef CHERIOT_PLATFORM_CUSTOM_UART
using Uart = SynopsisExtended16550<>;
#endif