fix: Pi 400 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
44739bf35d
11 files changed
+205
-33
No files matched your search
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 86
|
#define MONTAUK_BUILD_NUMBER 99
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in new issue
Block a user