fix: Pi 4 Bluetooth - hold RTS deasserted across BT_ON power-on, require Read Local Version to sync, mini UART no flow control first

This commit is contained in:
daniel committed 2026-10-26 07:46:49 +01:00
1 parent 93d8becce3
commit 3310b3d6de
11 files changed
+210 -38

No files matched your search

+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 86 #define MONTAUK_BUILD_NUMBER 99
+8
View File
@@ -330,6 +330,14 @@ namespace Memory::VMM {
} }
} }
// The bootloader wrote the image through the data cache. Push it to
// the point of unification and drop every instruction-cache line
// before the kernel relies on fetching code it has not run yet: a
// line still dirty in L1D is invisible to instruction fetch, which
// then reads stale DRAM (an "unknown reason" exception on a valid
// instruction).
Arch::SyncInstructionCache((const void*)kernelBaseVirt, kernelSize);
Arch::ActivateKernelTranslation(); Arch::ActivateKernelTranslation();
Kt::KernelLogStream(Kt::OK, "VMM") << "Switched to kernel translation tables (HHDM limit 0x" Kt::KernelLogStream(Kt::OK, "VMM") << "Switched to kernel translation tables (HHDM limit 0x"
+8
View File
@@ -24,5 +24,13 @@ namespace Arch {
PrintBoxedHex(kerr, "PSTATE (SPSR)", frame->spsr, BoxWidth); PrintBoxedHex(kerr, "PSTATE (SPSR)", frame->spsr, BoxWidth);
PrintBoxedHex(kerr, "Stack Pointer", frame->sp, BoxWidth); PrintBoxedHex(kerr, "Stack Pointer", frame->sp, BoxWidth);
PrintBoxedHex(kerr, "Link Register (x30)", frame->x[30], BoxWidth); PrintBoxedHex(kerr, "Link Register (x30)", frame->x[30], BoxWidth);
// An "unknown reason" exception on a kernel PC: show the word the
// data side sees there. A valid instruction means instruction fetch
// saw something else (cache maintenance); garbage means the code
// itself was overwritten. The fetch succeeded, so the page is mapped.
if ((frame->esr >> 26) == 0 && frame->elr >= 0xFFFF000000000000ULL && (frame->elr & 3) == 0) {
PrintBoxedHex(kerr, "Instruction at PC", *(volatile uint32_t*)frame->elr, BoxWidth);
}
} }
} }
+31
View File
@@ -1837,6 +1837,36 @@ namespace Drivers::Bluetooth::A2dp {
// ConfigureStream // ConfigureStream
// ========================================================================= // =========================================================================
// A UART transport has a hard byte budget: at 460800 baud it carries
// 46 KB/s, and SBC at bitpool 53 / 48 kHz needs ~46 KB/s with its
// headers, so the stream fell behind whenever anything else crossed the
// link and the sink played concealment. Keep the stream within three
// quarters of the line rate by lowering the bitpool (any value in the
// negotiated range is valid per frame). Over USB nothing changes.
static void FitBitpoolToTransport(uint32_t sampleRate) {
uint32_t budget = Hci::TransportBytesPerSecond() / 4 * 3;
if (budget == 0) return;
uint32_t rate = sampleRate ? sampleRate : 48000;
uint8_t minBitpool = g_cfgSbc[2] < 2 ? 2 : g_cfgSbc[2];
uint8_t original = g_sbcEncoder.Bitpool;
auto bytesPerSecond = [&]() {
// H4 + ACL + L2CAP + RTP + SCMS-T + SBC headers (23 bytes) per
// packet, spread over the ~4-5 frames bundled into each.
uint32_t frameCost = Sbc::GetFrameSize(&g_sbcEncoder) + 6;
return (uint64_t)frameCost * rate / Sbc::GetSamplesPerFrame(&g_sbcEncoder);
};
while (g_sbcEncoder.Bitpool > minBitpool && bytesPerSecond() > budget) {
Sbc::Configure(&g_sbcEncoder, g_cfgSbc[0], g_cfgSbc[1],
(uint8_t)(g_sbcEncoder.Bitpool - 1));
}
if (g_sbcEncoder.Bitpool != original) {
KernelLogStream(INFO, "BT-A2DP") << "Bitpool " << (uint64_t)original << " -> "
<< (uint64_t)g_sbcEncoder.Bitpool << " to fit the transport ("
<< bytesPerSecond() << " of " << (uint64_t)Hci::TransportBytesPerSecond()
<< " bytes/s)";
}
}
bool ConfigureStream(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample) { bool ConfigureStream(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample) {
// Encoder configuration and PumpMedia both mutate the SBC encoder. // Encoder configuration and PumpMedia both mutate the SBC encoder.
// Device switching normally configures an Open stream, but explicitly // Device switching normally configures an Open stream, but explicitly
@@ -1851,6 +1881,7 @@ namespace Drivers::Bluetooth::A2dp {
// agreed to (Init's defaults may differ from the negotiated subset). // agreed to (Init's defaults may differ from the negotiated subset).
if (g_cfgSbc[0] || g_cfgSbc[1]) { if (g_cfgSbc[0] || g_cfgSbc[1]) {
Sbc::Configure(&g_sbcEncoder, g_cfgSbc[0], g_cfgSbc[1], g_cfgSbc[3]); Sbc::Configure(&g_sbcEncoder, g_cfgSbc[0], g_cfgSbc[1], g_cfgSbc[3]);
FitBitpoolToTransport(sampleRate);
} }
g_sbcInitialized = true; g_sbcInitialized = true;
g_seqNum = 0; g_seqNum = 0;
+84 -22
View File
@@ -38,9 +38,15 @@
// the speed again. // the speed again.
// //
// Raspberry Pi OS's btuart runs the controller at 460800 without flow // Raspberry Pi OS's btuart runs the controller at 460800 without flow
// control on the mini UART and at 3 Mbaud with it on the PL011. The mini // control on the mini UART and at 3 Mbaud with it on the PL011. 460800 is
// UART's FIFO is 8 bytes deep, so here its hardware RTS is used whenever // too slow for A2DP here: 8N1 carries 46 KB/s and SBC at bitpool 53 needs
// the controller is still reachable with it on, which allows 921600. // ~46 KB/s with its headers, so music broke up whenever anything else used
// the link. The mini UART's auto RTS/CTS allows 921600, so it is tried
// first; without flow control (460800) is the fallback, and A2DP lowers its
// bitpool to fit (Hci::TransportBytesPerSecond).
//
// Our RTS must be deasserted while the controller powers on: with it
// asserted the controller boots into autobaud mode (see PowerCycle).
using namespace Kt; using namespace Kt;
@@ -189,14 +195,19 @@ namespace Drivers::Bluetooth::Broadcom {
// did not). The PL011's reference clock is fixed (CLCK, 48 MHz). // did not). The PL011's reference clock is fixed (CLCK, 48 MHz).
uint64_t tableClock = 0; uint64_t tableClock = 0;
uint32_t clockHz = 0; uint32_t clockHz = 0;
const char* clockSource = "firmware";
if (mini) { if (mini) {
if (!RpiFirmware::GetClockRate(RpiFirmware::CLOCK_CORE, clockHz)) { if (!RpiFirmware::GetClockRate(RpiFirmware::CLOCK_CORE, clockHz)) {
if (ReadInteger(uart.NodeIndex, "MUCR", tableClock) && tableClock > 0x1000000) if (ReadInteger(uart.NodeIndex, "MUCR", tableClock) && tableClock > 0x1000000) {
clockHz = (uint32_t)tableClock; clockHz = (uint32_t)tableClock;
else clockSource = "ACPI MUCR";
} else {
clockHz = 500000000; clockHz = 500000000;
clockSource = "assumed";
}
} }
} else { } else {
clockSource = "PL011 reference";
clockHz = ReadInteger(uart.NodeIndex, "CLCK", tableClock) && tableClock != 0 clockHz = ReadInteger(uart.NodeIndex, "CLCK", tableClock) && tableClock != 0
? (uint32_t)tableClock : 48000000; ? (uint32_t)tableClock : 48000000;
} }
@@ -221,7 +232,7 @@ namespace Drivers::Bluetooth::Broadcom {
Hci::InitializeUart(); Hci::InitializeUart();
Hci::SetVendor(Hci::Vendor::Broadcom); Hci::SetVendor(Hci::Vendor::Broadcom);
KernelLogStream(OK, "BT-BCM") << "Bluetooth controller " << bt.Path << " on " KernelLogStream(OK, "BT-BCM") << "Bluetooth controller " << bt.Path << " on "
<< uart.Path << " (GSI " << (uint64_t)config.gsi << ")"; << uart.Path << " (GSI " << (uint64_t)config.gsi << ", clock " << clockSource << ")";
return true; return true;
} }
@@ -229,11 +240,31 @@ namespace Drivers::Bluetooth::Broadcom {
// Controller bring-up // Controller bring-up
// ========================================================================= // =========================================================================
// BT_ON through the firmware's expander. The line has to be an output
// first (hci_bcm's GPIOD_OUT_LOW request does that on Linux); plain
// SET_GPIO_STATE is the fallback for firmware without the config tag.
static bool SetBtOn(bool on) {
return RpiFirmware::SetGpioOutput(RpiFirmware::EXPGPIO_BT_ON, on)
|| RpiFirmware::SetGpioState(RpiFirmware::EXPGPIO_BT_ON, on);
}
// The controller samples its CTS (our RTS) as it leaves reset. Asserted,
// it boots into autobaud mode, where it answers HCI Reset and silently
// drops nearly everything else (on a Pi 400: Read Local Version, Read
// PIN Type, Write PIN Type, at any baud rate). hci_bcm deasserts it
// around power-on for the same reason; SyncAtInitialBaud asserts it
// again once the controller is up.
static void PowerCycle() { static void PowerCycle() {
if (g_powerSwitch) { if (g_powerSwitch) {
if (RpiFirmware::SetGpioState(RpiFirmware::EXPGPIO_BT_ON, false)) { BtUart::DeassertRts();
if (SetBtOn(false)) {
Timekeeping::BusyWaitMs(20); Timekeeping::BusyWaitMs(20);
RpiFirmware::SetGpioState(RpiFirmware::EXPGPIO_BT_ON, true); bool on = SetBtOn(true);
bool state = false;
bool readBack = RpiFirmware::GetGpioState(RpiFirmware::EXPGPIO_BT_ON, state);
KernelLogStream(on ? INFO : WARNING, "BT-BCM") << "BT_ON "
<< (on ? "switched on" : "did not switch on") << " (reads back "
<< (readBack ? (state ? "high" : "low") : "unknown") << ")";
// hci_bcm allows 100-120 ms to power on and leave reset. // hci_bcm allows 100-120 ms to power on and leave reset.
Timekeeping::BusyWaitMs(150); Timekeeping::BusyWaitMs(150);
} else { } else {
@@ -245,25 +276,58 @@ namespace Drivers::Bluetooth::Broadcom {
Hci::ResetUartReceiver(); Hci::ResetUartReceiver();
} }
static bool ResetController(uint32_t timeoutMs) { // Whether received bytes move by interrupt and whether any were lost:
if (!Hci::SendCommand(Hci::OP_RESET, nullptr, 0)) return false; // the first thing to know when the controller seems not to answer.
return Hci::WaitCommandComplete(Hci::OP_RESET, nullptr, 0, timeoutMs); static void LogUartStats(const char* when) {
BtUart::Stats stats{};
BtUart::GetStats(stats);
KernelLogStream(INFO, "BT-BCM") << when << ": UART rx " << stats.rxBytes << " bytes, tx "
<< stats.txBytes << ", " << stats.interrupts << " interrupts, " << stats.overruns
<< " overruns, " << stats.rxDropped << " dropped, " << (uint64_t)BtUart::GetBaud()
<< " baud, " << (BtUart::FlowControl() ? "RTS flow control" : "no flow control");
} }
// Reach the controller at its power-on 115200 baud. Hardware RTS is tried static bool ResetController(uint32_t timeoutMs) {
// first: if the RTS line is wired the wrong way round for it, the if (!Hci::SendCommand(Hci::OP_RESET, nullptr, 0)) return false;
// controller sees RTS deasserted and never answers, and the retry without if (!Hci::WaitCommandComplete(Hci::OP_RESET, nullptr, 0, timeoutMs)) return false;
// it tells the two cases apart. // The controller acknowledges the reset before it has finished it
static bool SyncAtInitialBaud() { // and ignores commands until then; btbcm_reset waits 100 ms.
Timekeeping::BusyWaitMs(100);
return true;
}
// Reach the controller at its power-on 115200 baud and read its ROM
// version. A flow-control mode counts only once a second command has
// been answered too: a controller in autobaud mode answers HCI Reset
// alone, and a flow-control polarity that holds either side back fails
// here rather than later.
static bool SyncAtInitialBaud(Hci::LocalVersion& rom) {
BtUart::SetBaud(InitialBaud); BtUart::SetBaud(InitialBaud);
static constexpr bool attempts[] = { true, true, false, false }; static constexpr bool attempts[] = { true, true, false, false };
for (bool flow : attempts) { for (bool flow : attempts) {
BtUart::SetFlowControl(flow); BtUart::SetFlowControl(flow);
// Events the controller held back while it saw RTS deasserted
// pour out once it is asserted: let them arrive and drop them,
// so they neither overrun the FIFO under the next reply nor
// leave the deframer mid-packet.
Timekeeping::BusyWaitMs(50);
Hci::ResetUartReceiver(); Hci::ResetUartReceiver();
if (ResetController(1000)) { if (!ResetController(1000)) {
LogUartStats(flow ? "No reset reply with flow control" : "No reset reply");
continue;
}
// A controller slow to finish its reset gets a second chance.
bool answered = Hci::ReadLocalVersion(&rom);
if (!answered) {
Timekeeping::BusyWaitMs(200);
answered = Hci::ReadLocalVersion(&rom);
}
if (answered) {
g_flowControl = flow; g_flowControl = flow;
return true; return true;
} }
LogUartStats(flow ? "Reset answered but not Read Local Version, with flow control"
: "Reset answered but not Read Local Version");
} }
return false; return false;
} }
@@ -402,14 +466,12 @@ namespace Drivers::Bluetooth::Broadcom {
// afterwards; without it everything stays at 115200. // afterwards; without it everything stays at 115200.
static bool Attempt(bool fast) { static bool Attempt(bool fast) {
PowerCycle(); PowerCycle();
if (!SyncAtInitialBaud()) { Hci::LocalVersion rom{};
KernelLogStream(ERROR, "BT-BCM") << "Controller does not answer HCI Reset" if (!SyncAtInitialBaud(rom)) {
KernelLogStream(ERROR, "BT-BCM") << "Controller does not answer HCI commands"
<< (g_powerSwitch ? "" : " (BT_ON was never switched on: it is likely unpowered)"); << (g_powerSwitch ? "" : " (BT_ON was never switched on: it is likely unpowered)");
return false; return false;
} }
Hci::LocalVersion rom{};
if (!Hci::ReadLocalVersion(&rom)) return false;
const char* chip = ChipFor(rom.LmpSubversion); const char* chip = ChipFor(rom.LmpSubversion);
KernelLogStream(INFO, "BT-BCM") << "ROM firmware: LMP subversion " << base::hex KernelLogStream(INFO, "BT-BCM") << "ROM firmware: LMP subversion " << base::hex
<< (uint64_t)rom.LmpSubversion << ", HCI revision " << (uint64_t)rom.HciRevision << (uint64_t)rom.LmpSubversion << ", HCI revision " << (uint64_t)rom.HciRevision
+4
View File
@@ -695,6 +695,10 @@ namespace Drivers::Bluetooth::Hci {
} }
Transport GetTransport() { return g_transport; } Transport GetTransport() { return g_transport; }
uint32_t TransportBytesPerSecond() {
return g_transport == Transport::Uart ? BtUart::GetBaud() / 10 : 0;
}
void SetVendor(Vendor vendor) { g_vendor = vendor; } void SetVendor(Vendor vendor) { g_vendor = vendor; }
Vendor GetVendor() { return g_vendor; } Vendor GetVendor() { return g_vendor; }
+5
View File
@@ -199,6 +199,11 @@ namespace Drivers::Bluetooth::Hci {
void InitializeUart(); void InitializeUart();
Transport GetTransport(); Transport GetTransport();
// Bytes per second the host-to-controller link can carry: the UART's
// line rate (8N1), or 0 when the transport is not the bottleneck (USB).
uint32_t TransportBytesPerSecond();
void SetVendor(Vendor vendor); void SetVendor(Vendor vendor);
Vendor GetVendor(); Vendor GetVendor();
+34 -9
View File
@@ -48,6 +48,8 @@ namespace Drivers::Platform::RpiFirmware {
static constexpr uint32_t TAG_GET_CLOCK_RATE = 0x00030002; static constexpr uint32_t TAG_GET_CLOCK_RATE = 0x00030002;
static constexpr uint32_t TAG_GET_GPIO_STATE = 0x00030041; static constexpr uint32_t TAG_GET_GPIO_STATE = 0x00030041;
static constexpr uint32_t TAG_SET_GPIO_STATE = 0x00038041; static constexpr uint32_t TAG_SET_GPIO_STATE = 0x00038041;
static constexpr uint32_t TAG_GET_GPIO_CONFIG = 0x00030043;
static constexpr uint32_t TAG_SET_GPIO_CONFIG = 0x00038043;
// The VideoCore sees the first GiB of SDRAM at bus address 0xC0000000 // The VideoCore sees the first GiB of SDRAM at bus address 0xC0000000
// (the uncached alias), and nothing above it, so the message buffer // (the uncached alias), and nothing above it, so the message buffer
@@ -199,18 +201,28 @@ namespace Drivers::Platform::RpiFirmware {
g_buffer[words++] = 0; // end tag g_buffer[words++] = 0; // end tag
g_buffer[0] = words * 4; g_buffer[0] = words * 4;
// The firmware does not set the response bit for every tag: the
// expander GPIO tags answer through value[0] alone and leave the
// status word 0. Linux checks only the message code, so do the same
// and take the whole value buffer back when the bit is missing; each
// caller validates the values it gets.
int result = -1; int result = -1;
if (Call()) { bool answered = Call();
uint32_t status = g_buffer[valueStart - 1]; uint32_t status = g_buffer[valueStart - 1];
if (status & TAG_RESPONSE) { if (answered) {
uint32_t length = status & ~TAG_RESPONSE; uint32_t length = (status & TAG_RESPONSE) ? status & ~TAG_RESPONSE : valueWords * 4;
uint32_t copy = (length + 3) / 4; uint32_t copy = (length + 3) / 4;
if (copy > valueWords) copy = valueWords; if (copy > valueWords) copy = valueWords;
for (uint32_t i = 0; i < copy; i++) values[i] = g_buffer[valueStart + i]; for (uint32_t i = 0; i < copy; i++) values[i] = g_buffer[valueStart + i];
result = (int)length; result = (int)length;
}
} }
uint32_t code = g_buffer[1];
g_lock.Release(); g_lock.Release();
if (result < 0) {
KernelLogStream(WARNING, "RpiFw") << "Property tag 0x" << base::hex << (uint64_t)tag
<< " failed: message code 0x" << (uint64_t)code << base::dec;
}
return result; return result;
} }
@@ -228,6 +240,19 @@ namespace Drivers::Platform::RpiFirmware {
return PropertyCall(TAG_SET_GPIO_STATE, v, 2) >= 4 && v[0] == 0; return PropertyCall(TAG_SET_GPIO_STATE, v, 2) >= 4 && v[0] == 0;
} }
// As Linux's rpi_exp_gpio_dir_out: keep the line's polarity, make it an
// output driving `high`. A line the firmware still has as an input
// refuses SET_GPIO_STATE.
bool SetGpioOutput(uint32_t gpio, bool high) {
uint32_t polarity = 0;
uint32_t cfg[5] = { gpio, 0, 0, 0, 0 }; // gpio, direction, polarity, term_en, term_pull_up
if (PropertyCall(TAG_GET_GPIO_CONFIG, cfg, 5) >= 12 && cfg[0] == 0) polarity = cfg[2];
uint32_t v[6] = { gpio, 1 /* output */, polarity, 0, 0, high ? 1u : 0u };
if (PropertyCall(TAG_SET_GPIO_CONFIG, v, 6) < 4 || v[0] != 0) return false;
return true;
}
bool GetGpioState(uint32_t gpio, bool& high) { bool GetGpioState(uint32_t gpio, bool& high) {
uint32_t v[2] = { gpio, 0 }; uint32_t v[2] = { gpio, 0 };
if (PropertyCall(TAG_GET_GPIO_STATE, v, 2) < 8 || v[0] != 0) return false; if (PropertyCall(TAG_GET_GPIO_STATE, v, 2) < 8 || v[0] != 0) return false;
@@ -38,6 +38,8 @@ namespace Drivers::Platform::RpiFirmware {
constexpr uint32_t EXPGPIO_WL_ON = EXPGPIO_BASE + 1; // Wi-Fi power (high = on) constexpr uint32_t EXPGPIO_WL_ON = EXPGPIO_BASE + 1; // Wi-Fi power (high = on)
bool SetGpioState(uint32_t gpio, bool high); bool SetGpioState(uint32_t gpio, bool high);
// Configure the line as an output, then drive it (SET_GPIO_CONFIG).
bool SetGpioOutput(uint32_t gpio, bool high);
bool GetGpioState(uint32_t gpio, bool& high); bool GetGpioState(uint32_t gpio, bool& high);
// The board's Ethernet MAC address from OTP, in transmission order // The board's Ethernet MAC address from OTP, in transmission order
+25 -3
View File
@@ -52,9 +52,16 @@ namespace Drivers::Serial::BtUart {
static constexpr uint32_t MU_LSR_TX_IDLE = 1u << 6; // FIFO empty, shifter idle static constexpr uint32_t MU_LSR_TX_IDLE = 1u << 6; // FIFO empty, shifter idle
static constexpr uint32_t MU_CNTL_RXEN = 1u << 0; static constexpr uint32_t MU_CNTL_RXEN = 1u << 0;
static constexpr uint32_t MU_CNTL_TXEN = 1u << 1; static constexpr uint32_t MU_CNTL_TXEN = 1u << 1;
// Auto RTS at the default level (deassert with 3 FIFO spaces left) and // Auto RTS at the default level (deassert with 3 FIFO spaces left).
// the default, active-low polarity (CNTL bit 6 would invert it). // Bit 6 picks the level auto RTS asserts: set = low, clear (the reset
// value) = high. A UART's RTS is active low, so it must be set: left
// clear, RTS reads "stop" to the controller the moment auto RTS is on.
// Bits 3 and 7 are the same for CTS: on a Pi 400, with bit 7 clear the
// transmitter held its bytes while the controller drove CTS low (ready).
static constexpr uint32_t MU_CNTL_AUTORTS = 1u << 2; static constexpr uint32_t MU_CNTL_AUTORTS = 1u << 2;
static constexpr uint32_t MU_CNTL_AUTOCTS = 1u << 3;
static constexpr uint32_t MU_CNTL_RTS_LOW = 1u << 6;
static constexpr uint32_t MU_CNTL_CTS_LOW = 1u << 7;
// PL011 (ARM PrimeCell UART TRM) // PL011 (ARM PrimeCell UART TRM)
static constexpr uint32_t PL_DR = 0x00; static constexpr uint32_t PL_DR = 0x00;
@@ -359,7 +366,9 @@ namespace Drivers::Serial::BtUart {
if (!g_initialized) return; if (!g_initialized) return;
g_hwLock.Acquire(); g_hwLock.Acquire();
if (g_kind == Kind::MiniUart) { if (g_kind == Kind::MiniUart) {
uint32_t cntl = MU_CNTL_RXEN | MU_CNTL_TXEN | (on ? MU_CNTL_AUTORTS : 0); uint32_t cntl = MU_CNTL_RXEN | MU_CNTL_TXEN
| (on ? MU_CNTL_AUTORTS | MU_CNTL_RTS_LOW
| MU_CNTL_AUTOCTS | MU_CNTL_CTS_LOW : 0);
Write32(MU_CNTL, cntl); Write32(MU_CNTL, cntl);
// With auto RTS off, hold RTS asserted by hand. // With auto RTS off, hold RTS asserted by hand.
Write32(MU_MCR, MU_MCR_RTS); Write32(MU_MCR, MU_MCR_RTS);
@@ -372,6 +381,19 @@ namespace Drivers::Serial::BtUart {
g_hwLock.Release(); g_hwLock.Release();
} }
void DeassertRts() {
if (!g_initialized) return;
g_hwLock.Acquire();
if (g_kind == Kind::MiniUart) {
Write32(MU_CNTL, MU_CNTL_RXEN | MU_CNTL_TXEN);
Write32(MU_MCR, 0);
} else {
Write32(PL_CR, Read32(PL_CR) & ~(PL_CR_RTSEN | PL_CR_CTSEN | PL_CR_RTS));
}
g_flowControl = false;
g_hwLock.Release();
}
int Read(uint8_t* buffer, int maxLength) { int Read(uint8_t* buffer, int maxLength) {
if (buffer == nullptr || maxLength <= 0) return 0; if (buffer == nullptr || maxLength <= 0) return 0;
uint32_t tail = g_rxTail.load(std::memory_order_relaxed); uint32_t tail = g_rxTail.load(std::memory_order_relaxed);
+8 -3
View File
@@ -52,12 +52,17 @@ namespace Drivers::Serial::BtUart {
uint32_t GetBaud(); uint32_t GetBaud();
Kind GetKind(); Kind GetKind();
// Hardware RTS: deassert RTS while the receive FIFO is nearly full so // Hardware flow control: deassert RTS while the receive FIFO is nearly
// the peer pauses instead of overrunning it. On the PL011 this also // full so the peer pauses instead of overrunning it, and hold our
// enables CTS (the peer's RTS gating our transmitter). // transmitter while the peer deasserts CTS.
void SetFlowControl(bool on); void SetFlowControl(bool on);
bool FlowControl(); bool FlowControl();
// Drive RTS deasserted (and stop auto RTS) until the next
// SetFlowControl. The peer may sample it at power-on: a Broadcom
// controller that sees its CTS asserted then boots into autobaud mode.
void DeassertRts();
// Receive ring (single consumer). Read returns the number of bytes copied. // Receive ring (single consumer). Read returns the number of bytes copied.
int Read(uint8_t* buffer, int maxLength); int Read(uint8_t* buffer, int maxLength);
bool HasRxData(); bool HasRxData();