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:
1 parent
93d8becce3
commit
3310b3d6de
11 files changed
+210
-38
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user