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

+84 -22
View File
@@ -38,9 +38,15 @@
// the speed again.
//
// 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
// UART's FIFO is 8 bytes deep, so here its hardware RTS is used whenever
// the controller is still reachable with it on, which allows 921600.
// control on the mini UART and at 3 Mbaud with it on the PL011. 460800 is
// too slow for A2DP here: 8N1 carries 46 KB/s and SBC at bitpool 53 needs
// ~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;
@@ -189,14 +195,19 @@ namespace Drivers::Bluetooth::Broadcom {
// did not). The PL011's reference clock is fixed (CLCK, 48 MHz).
uint64_t tableClock = 0;
uint32_t clockHz = 0;
const char* clockSource = "firmware";
if (mini) {
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;
else
clockSource = "ACPI MUCR";
} else {
clockHz = 500000000;
clockSource = "assumed";
}
}
} else {
clockSource = "PL011 reference";
clockHz = ReadInteger(uart.NodeIndex, "CLCK", tableClock) && tableClock != 0
? (uint32_t)tableClock : 48000000;
}
@@ -221,7 +232,7 @@ namespace Drivers::Bluetooth::Broadcom {
Hci::InitializeUart();
Hci::SetVendor(Hci::Vendor::Broadcom);
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;
}
@@ -229,11 +240,31 @@ namespace Drivers::Bluetooth::Broadcom {
// 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() {
if (g_powerSwitch) {
if (RpiFirmware::SetGpioState(RpiFirmware::EXPGPIO_BT_ON, false)) {
BtUart::DeassertRts();
if (SetBtOn(false)) {
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.
Timekeeping::BusyWaitMs(150);
} else {
@@ -245,25 +276,58 @@ namespace Drivers::Bluetooth::Broadcom {
Hci::ResetUartReceiver();
}
static bool ResetController(uint32_t timeoutMs) {
if (!Hci::SendCommand(Hci::OP_RESET, nullptr, 0)) return false;
return Hci::WaitCommandComplete(Hci::OP_RESET, nullptr, 0, timeoutMs);
// Whether received bytes move by interrupt and whether any were lost:
// the first thing to know when the controller seems not to answer.
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
// first: if the RTS line is wired the wrong way round for it, the
// controller sees RTS deasserted and never answers, and the retry without
// it tells the two cases apart.
static bool SyncAtInitialBaud() {
static bool ResetController(uint32_t timeoutMs) {
if (!Hci::SendCommand(Hci::OP_RESET, nullptr, 0)) return false;
if (!Hci::WaitCommandComplete(Hci::OP_RESET, nullptr, 0, timeoutMs)) return false;
// The controller acknowledges the reset before it has finished it
// 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);
static constexpr bool attempts[] = { true, true, false, false };
for (bool flow : attempts) {
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();
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;
return true;
}
LogUartStats(flow ? "Reset answered but not Read Local Version, with flow control"
: "Reset answered but not Read Local Version");
}
return false;
}
@@ -402,14 +466,12 @@ namespace Drivers::Bluetooth::Broadcom {
// afterwards; without it everything stays at 115200.
static bool Attempt(bool fast) {
PowerCycle();
if (!SyncAtInitialBaud()) {
KernelLogStream(ERROR, "BT-BCM") << "Controller does not answer HCI Reset"
Hci::LocalVersion rom{};
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)");
return false;
}
Hci::LocalVersion rom{};
if (!Hci::ReadLocalVersion(&rom)) return false;
const char* chip = ChipFor(rom.LmpSubversion);
KernelLogStream(INFO, "BT-BCM") << "ROM firmware: LMP subversion " << base::hex
<< (uint64_t)rom.LmpSubversion << ", HCI revision " << (uint64_t)rom.HciRevision