| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "bt-hci-mediatek.h" |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/driver.h> |
| #include <ddk/platform-defs.h> |
| #include <ddktl/pdev.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/unique_ptr.h> |
| #include <hw/sdio.h> |
| #include <lib/fzl/vmo-mapper.h> |
| #include <lib/zx/time.h> |
| #include <zircon/device/bt-hci.h> |
| |
| #include <algorithm> |
| |
| namespace { |
| |
| constexpr uint8_t kFunctionNumber = SDIO_FN_2; |
| constexpr size_t kBlockSize = 256; |
| |
| constexpr uint8_t kPacketTypeCmd = 1; |
| constexpr uint8_t kPacketTypeAcl = 2; |
| constexpr uint8_t kPacketTypeSco = 3; |
| constexpr uint8_t kPacketTypeEvent = 4; |
| |
| constexpr uint8_t kAclPacketHeaderSize = 4; |
| constexpr size_t kAclPacketSizeOffset = 2; |
| |
| constexpr uint8_t kEventPacketHeaderSize = 2; |
| constexpr size_t kEventPacketSizeOffset = 1; |
| |
| // From the Mediatek driver source. Round down to the block size for convenience. |
| constexpr size_t kMaxPacketSize = fbl::round_down<size_t, size_t>(2000, kBlockSize); |
| |
| constexpr uint32_t kSdioHeaderSize = 4; |
| constexpr uint32_t kHciPacketHeaderSize = kSdioHeaderSize + 1; |
| |
| constexpr size_t kFwHeaderSize = 30; |
| |
| constexpr size_t kFwPartHeaderSize = 14; |
| constexpr size_t kFwPartMaxSize = kMaxPacketSize - kFwPartHeaderSize; |
| |
| constexpr int kFirmwareReady = 1; |
| constexpr int kFirmwareNeedDownload = 2; |
| |
| // Card register addresses and bits. |
| constexpr uint32_t kChlpcrAddress = 0x04; |
| constexpr uint32_t kChlpcrFwIntSet = 0x00000001; |
| constexpr uint32_t kChlpcrFwIntClear = 0x00000002; |
| constexpr uint32_t kChlpcrFwOwn = 0x00000100; |
| constexpr uint32_t kChlpcrDriverOwn = 0x00000200; |
| |
| constexpr uint32_t kCsdioCsrAddress = 0x08; |
| constexpr uint32_t kCsdioCsrClockFix = 0x00000004; |
| |
| constexpr uint32_t kChcrAddress = 0x0c; |
| constexpr uint32_t kChcrWriteClear = 0x00000002; |
| |
| constexpr uint32_t kChisrAddress = 0x10; |
| constexpr uint32_t kChierAddress = 0x14; |
| constexpr uint32_t kIsrRxDone = 0x00000002; |
| constexpr uint32_t kIsrTxEmpty = 0x00000004; |
| constexpr uint32_t kIsrTxUnderThreshold = 0x00000008; |
| constexpr uint32_t kIsrTxCompleteCount = 0x00000070; |
| constexpr uint32_t kIsrFwInd = 0x00000080; |
| constexpr uint32_t kIsrTxFifoOverflow = 0x00000100; |
| constexpr uint32_t kIsrFw = 0x0000fe00; |
| constexpr uint32_t kIsrRxPacketSizeMask = 0xffff0000; |
| constexpr uint32_t kIsrRxPacketSizeShift = 16; |
| constexpr uint32_t kIsrAll = kIsrRxDone | kIsrTxEmpty | kIsrTxUnderThreshold | kIsrTxCompleteCount | |
| kIsrFwInd | kIsrTxFifoOverflow | kIsrFw; |
| |
| constexpr uint32_t kCtdrAddress = 0x18; |
| constexpr uint32_t kCrdrAddress = 0x1c; |
| |
| void SetSizeField(void* packet, size_t size) { |
| uint16_t size_le = htole16(static_cast<uint16_t>(size)); |
| memcpy(packet, &size_le, sizeof(size_le)); |
| } |
| |
| uint16_t GetSizeField(const uint8_t* packet) { |
| return static_cast<uint16_t>(packet[0] | (packet[1] << 8)); |
| } |
| |
| } // namespace |
| |
| namespace bluetooth { |
| |
| zx_status_t BtHciMediatek::OpenCommandChannel(void* ctx, zx_handle_t in_handle) { |
| return reinterpret_cast<BtHciMediatek*>(ctx)->BtHciOpenCommandChannel(in_handle); |
| } |
| |
| zx_status_t BtHciMediatek::OpenAclDataChannel(void* ctx, zx_handle_t in_handle) { |
| return reinterpret_cast<BtHciMediatek*>(ctx)->BtHciOpenAclDataChannel(in_handle); |
| } |
| |
| zx_status_t BtHciMediatek::OpenSnoopChannel(void* ctx, zx_handle_t in_handle) { |
| return reinterpret_cast<BtHciMediatek*>(ctx)->BtHciOpenSnoopChannel(in_handle); |
| } |
| |
| void BtHciMediatek::DdkRelease() { |
| bool join_thread = false; |
| |
| { |
| fbl::AutoLock lock(&thread_mutex_); |
| |
| if (thread_running_) { |
| join_thread = true; |
| |
| // Send a packet to port_ to wake up the thread, then release the lock and join. |
| zx_port_packet_t packet; |
| packet.key = kStopThreadKey; |
| packet.type = ZX_PKT_TYPE_USER; |
| if (port_.queue(&packet) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to queue packet\n", __FILE__); |
| } |
| } |
| } |
| |
| if (join_thread) { |
| thrd_join(thread_, nullptr); |
| } |
| |
| delete this; |
| } |
| |
| zx_status_t BtHciMediatek::Create(void* ctx, zx_device_t* parent) { |
| ddk::SdioProtocolClient sdio(parent); |
| if (!sdio.is_valid()) { |
| zxlogf(ERROR, "%s: Failed to get SDIO protocol\n", __FILE__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| zx::port port; |
| zx_status_t status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to create port\n", __FILE__); |
| return status; |
| } |
| |
| zx::vmo fw_vmo; |
| size_t fw_size; |
| status = |
| load_firmware(parent, "mt7668_patch_e2_hdr.bin", fw_vmo.reset_and_get_address(), &fw_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to load firmware\n", __FILE__); |
| return status; |
| } |
| |
| fbl::AllocChecker ac; |
| fbl::unique_ptr<BtHciMediatek> device( |
| new (&ac) BtHciMediatek(parent, sdio, std::move(port), kFwPartMaxSize)); |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: BtHciMediatek alloc failed\n", __FILE__); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if ((device->Init(fw_vmo, fw_size)) != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = device->DdkAdd("bt-hci-mediatek")) != ZX_OK) { |
| zxlogf(ERROR, "%s: DdkAdd failed\n", __FILE__); |
| return status; |
| } |
| |
| __UNUSED auto* dummy = device.release(); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciMediatek::Init(const zx::vmo& fw_vmo, size_t fw_size) { |
| zx_status_t status = sdio_.GetInBandIntr(&sdio_int_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to get SDIO interrupt\n", __FILE__); |
| return status; |
| } |
| |
| if ((status = sdio_int_.bind(port_, kSdioInterruptKey, 0)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to bind interrupt to port\n", __FILE__); |
| return status; |
| } |
| |
| if ((status = sdio_.EnableFn(kFunctionNumber)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to set function\n", __FILE__); |
| return status; |
| } |
| |
| if ((status = sdio_.EnableFnIntr(kFunctionNumber)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to enable function interrupt\n", __FILE__); |
| return status; |
| } |
| |
| if ((status = sdio_.UpdateBlockSize(kFunctionNumber, kBlockSize, false)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to update block size\n", __FILE__); |
| return status; |
| } |
| |
| if ((status = CardEnableInterrupt()) != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = CardSetOwn(true)) != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = CardWrite32(kChierAddress, kIsrAll)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write card register\n", __FILE__); |
| return status; |
| } |
| |
| if ((status = CardWrite32(kChlpcrAddress, kChlpcrFwIntSet)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write card register\n", __FILE__); |
| return status; |
| } |
| |
| int fw_status = CardGetFirmwareStatus(); |
| if (fw_status < 0) { |
| return fw_status; |
| } else if (fw_status == kFirmwareNeedDownload) { |
| if ((status = CardDownloadFirmware(fw_vmo, fw_size)) != ZX_OK) { |
| return status; |
| } else if (CardGetFirmwareStatus() != kFirmwareReady) { |
| zxlogf(ERROR, "%s: Firmware not ready after download\n", __FILE__); |
| return ZX_ERR_INTERNAL; |
| } |
| } |
| |
| if ((status = CardSetPower(true)) != ZX_OK) { |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciMediatek::DdkGetProtocol(uint32_t proto_id, void* out) { |
| if (proto_id == ZX_PROTOCOL_BT_HCI) { |
| bt_hci_protocol_t* proto = reinterpret_cast<bt_hci_protocol_t*>(out); |
| proto->ops = &protocol_ops_; |
| proto->ctx = this; |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t BtHciMediatek::BtHciOpenCommandChannel(zx_handle_t in) { |
| return OpenChannel(&cmd_channel_, in, kCommandChannelKey); |
| } |
| |
| zx_status_t BtHciMediatek::BtHciOpenAclDataChannel(zx_handle_t in) { |
| return OpenChannel(&acl_channel_, in, kAclChannelKey); |
| } |
| |
| zx_status_t BtHciMediatek::BtHciOpenSnoopChannel(zx_handle_t in) { |
| return OpenChannel(&snoop_channel_, in, kSnoopChannelKey); |
| } |
| |
| zx_status_t BtHciMediatek::OpenChannel(zx::channel* in_channel, zx_handle_t in, PacketKey key) { |
| fbl::AutoLock lock(&thread_mutex_); |
| |
| if (in_channel->is_valid()) { |
| return ZX_ERR_ALREADY_BOUND; |
| } |
| |
| *in_channel = zx::channel(in); |
| |
| zx_signals_t wait_signals = ZX_CHANNEL_PEER_CLOSED | ZX_SIGNAL_HANDLE_CLOSED; |
| if (key != kSnoopChannelKey) { |
| wait_signals |= ZX_CHANNEL_READABLE; |
| } |
| |
| zx_status_t status = ZX_OK; |
| if ((status = in_channel->wait_async(port_, key, wait_signals, ZX_WAIT_ASYNC_ONCE)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Channel object_wait_async failed\n", __FILE__); |
| return status; |
| } |
| |
| if (!thread_running_) { |
| thread_running_ = true; |
| |
| thrd_create_with_name(&thread_, [](void* ctx) { |
| return reinterpret_cast<BtHciMediatek*>(ctx)->Thread(); |
| }, this, "bt-hci-mediatek-thread"); |
| } |
| |
| return status; |
| } |
| |
| zx_status_t BtHciMediatek::CardEnableInterrupt() { |
| zx_status_t status = sdio_.DoRwByte(true, kFunctionNumber, kChlpcrAddress, |
| kChlpcrFwIntSet | kChlpcrFwIntClear, nullptr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to enable card interrupt\n", __FILE__); |
| return status; |
| } |
| |
| uint32_t csdiocsr; |
| if ((status = CardRead32(kCsdioCsrAddress, &csdiocsr)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read CSDIOCSR\n", __FILE__); |
| return status; |
| } |
| |
| if ((status = CardWrite32(kCsdioCsrAddress, csdiocsr | kCsdioCsrClockFix)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write CSDIOCSR\n", __FILE__); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciMediatek::CardDisableInterrupt() { |
| zx_status_t status = sdio_.DoRwByte(true, kFunctionNumber, kChlpcrAddress, 0, nullptr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to disable card interrupt\n", __FILE__); |
| } |
| |
| return status; |
| } |
| |
| zx_status_t BtHciMediatek::CardRead32(uint32_t address, uint32_t* value) { |
| sdio_rw_txn txn; |
| txn.addr = address; |
| txn.data_size = sizeof(*value); |
| txn.incr = true; |
| txn.write = false; |
| txn.use_dma = false; |
| txn.virt_buffer = value; |
| txn.virt_size = sizeof(*value); |
| txn.buf_offset = 0; |
| |
| zx_status_t status = sdio_.DoRwTxn(kFunctionNumber, &txn); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read card register\n", __FILE__); |
| } |
| |
| *value = letoh32(*value); |
| return status; |
| } |
| |
| zx_status_t BtHciMediatek::CardWrite32(uint32_t address, uint32_t value) { |
| value = htole32(value); |
| |
| sdio_rw_txn txn; |
| txn.addr = address; |
| txn.data_size = sizeof(value); |
| txn.incr = true; |
| txn.write = true; |
| txn.use_dma = false; |
| txn.virt_buffer = &value; |
| txn.virt_size = sizeof(value); |
| txn.buf_offset = 0; |
| |
| zx_status_t status = sdio_.DoRwTxn(kFunctionNumber, &txn); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write card register\n", __FILE__); |
| } |
| |
| return status; |
| } |
| |
| zx_status_t BtHciMediatek::CardRecvPacket(uint32_t* size) { |
| uint32_t chisr; |
| zx_status_t status = CardRead32(kChisrAddress, &chisr); |
| for (int i = 0; i < 5 && status == ZX_OK; i++) { |
| if ((chisr & kIsrRxDone) != 0 && (chisr & kIsrRxPacketSizeMask) != 0) { |
| break; |
| } |
| |
| status = CardRead32(kChisrAddress, &chisr); |
| zx::nanosleep(zx::deadline_after(zx::msec(3))); |
| } |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read card register\n", __FILE__); |
| return status; |
| } else if ((chisr & kIsrRxDone) == 0 || (chisr & kIsrRxPacketSizeMask) == 0) { |
| // This could be expected so don't print an error message. |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| *size = letoh16(chisr >> kIsrRxPacketSizeShift); |
| |
| chisr &= ~kIsrRxPacketSizeMask & ~kIsrTxEmpty; |
| if ((status = CardWrite32(kChisrAddress, chisr)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write card register\n", __FILE__); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciMediatek::CardReset() { |
| constexpr uint8_t kResetPacket[] = {0x01, 0x07, 0x01, 0x00, 0x04}; |
| constexpr uint8_t kResetResponsePacket[] = {0x04, 0xe4, 0x05, 0x02, 0x07, 0x01, 0x00, 0x00}; |
| |
| uint8_t packet[sizeof(kResetResponsePacket)]; |
| memcpy(packet, kResetPacket, sizeof(kResetPacket)); |
| size_t size = sizeof(kResetPacket); |
| zx_status_t status = CardSendVendorPacket(kPacketTypeCmd, 0x6f, packet, &size, sizeof(packet)); |
| if (status != ZX_OK) { |
| return status; |
| } else if (size != sizeof(kResetResponsePacket)) { |
| zxlogf(ERROR, "%s: Packet header doesn't match size\n", __FILE__); |
| return ZX_ERR_IO; |
| } else if (memcmp(packet, kResetResponsePacket, sizeof(kResetResponsePacket)) != 0) { |
| zxlogf(ERROR, "%s: Unexpected response to reset command\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciMediatek::CardSetOwn(bool driver) { |
| const uint32_t mask = driver ? kChlpcrDriverOwn : kChlpcrFwOwn; |
| |
| zx_status_t status = CardWrite32(kChlpcrAddress, mask); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to set card own\n", __FILE__); |
| return status; |
| } |
| |
| const uint32_t checkvalue = driver ? kChlpcrFwOwn : 0; |
| |
| uint32_t value = 0; |
| for (uint32_t i = 0; i < 1000 && (value & kChlpcrFwOwn) != checkvalue; i++) { |
| zx::nanosleep(zx::deadline_after(zx::usec(1))); |
| |
| if ((status = CardRead32(kChlpcrAddress, &value)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read card own\n", __FILE__); |
| return status; |
| } |
| } |
| |
| if ((value & kChlpcrFwOwn) != checkvalue) { |
| zxlogf(ERROR, "%s: Timed out waiting for card own\n", __FILE__); |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciMediatek::CardSetPower(bool on) { |
| constexpr uint8_t kCardSetPowerPacket[] = |
| {kPacketTypeCmd, 0x6f, 0xfc, 0x06, 0x01, 0x06, 0x02, 0x00, 0x00, 0x01}; |
| constexpr uint8_t kCardSetPowerResponsePacket[] = |
| {0x04, 0xe4, 0x05, 0x02, 0x06, 0x01, 0x00, 0x00}; |
| |
| uint8_t packet[fbl::round_up<size_t, size_t>(sizeof(kCardSetPowerPacket) + 4, 4)]; |
| SetSizeField(packet, sizeof(kCardSetPowerPacket) + 4); |
| memcpy(packet + 4, kCardSetPowerPacket, sizeof(kCardSetPowerPacket)); |
| |
| sdio_rw_txn txn; |
| txn.addr = kCtdrAddress; |
| txn.data_size = sizeof(packet); |
| txn.incr = false; |
| txn.write = true; |
| txn.use_dma = false; |
| txn.virt_buffer = packet; |
| txn.virt_size = sizeof(packet); |
| txn.buf_offset = 0; |
| zx_status_t status = sdio_.DoRwTxn(kFunctionNumber, &txn); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: SDIO transaction failed\n", __FILE__); |
| return status; |
| } |
| |
| const int tries = on ? 60 : 3; |
| |
| uint32_t recv_size; |
| status = CardRecvPacket(&recv_size); |
| for (int i = 0; i < tries && status == ZX_ERR_TIMED_OUT; i++) { |
| zx::nanosleep(zx::deadline_after(zx::msec(100))); |
| status = CardRecvPacket(&recv_size); |
| } |
| |
| if (status != ZX_OK) { |
| return status; |
| } else if (recv_size > sizeof(packet)) { |
| zxlogf(ERROR, "%s: Unexpected response to set power command\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| txn.addr = kCrdrAddress; |
| txn.data_size = recv_size; |
| txn.incr = false; |
| txn.write = false; |
| txn.use_dma = false; |
| txn.virt_buffer = packet; |
| txn.virt_size = recv_size; |
| txn.buf_offset = 0; |
| if ((status = sdio_.DoRwTxn(kFunctionNumber, &txn)) != ZX_OK) { |
| zxlogf(ERROR, "%s: SDIO transaction failed\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| if (GetSizeField(packet) != recv_size) { |
| zxlogf(ERROR, "%s: Packet header doesn't match size\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| if (memcmp(packet + kSdioHeaderSize, kCardSetPowerResponsePacket, |
| sizeof(kCardSetPowerResponsePacket)) != 0) { |
| zxlogf(ERROR, "%s: Unexpected response to set power command\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciMediatek::CardSendVendorPacket(uint8_t id, uint8_t ocf, uint8_t* packet, |
| size_t* size, size_t buffer_size) { |
| constexpr size_t kCmdPacketHeaderSize = 8; |
| constexpr size_t kAclPacketHeaderSize = 9; |
| |
| if (id != kPacketTypeCmd && id != kPacketTypeAcl) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| size_t total_size = *size; |
| total_size += (id == kPacketTypeCmd) ? kCmdPacketHeaderSize : kAclPacketHeaderSize; |
| |
| size_t vmo_size = fbl::round_up<size_t, size_t>(std::max(total_size, buffer_size), kBlockSize); |
| |
| fzl::VmoMapper mapper; |
| zx::vmo vmo; |
| zx_status_t status = |
| mapper.CreateAndMap(vmo_size, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr, &vmo); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to create and map VMO\n", __FILE__); |
| return status; |
| } |
| |
| uint8_t* packet_buf = reinterpret_cast<uint8_t*>(mapper.start()); |
| |
| // SDIO header |
| SetSizeField(packet_buf, total_size); |
| packet_buf += 2; |
| *packet_buf++ = 0x00; |
| *packet_buf++ = 0x00; |
| |
| // HCI header with vendor opcode |
| *packet_buf++ = id; |
| *packet_buf++ = ocf; |
| *packet_buf++ = 0xfc; |
| |
| if (id == kPacketTypeAcl) { |
| SetSizeField(packet_buf, *size); |
| memcpy(packet_buf + 2, packet, *size); |
| } else { |
| *packet_buf++ = static_cast<uint8_t>(*size); |
| memcpy(packet_buf, packet, *size); |
| } |
| |
| packet_buf = reinterpret_cast<uint8_t*>(mapper.start()); |
| |
| sdio_rw_txn txn; |
| txn.addr = kCtdrAddress; |
| txn.data_size = static_cast<uint32_t>(fbl::round_up<size_t, size_t>(total_size, kBlockSize)); |
| txn.incr = false; |
| txn.write = true; |
| txn.use_dma = true; |
| txn.dma_vmo = vmo.get(); |
| txn.buf_offset = 0; |
| if ((status = sdio_.DoRwTxn(kFunctionNumber, &txn)) != ZX_OK) { |
| zxlogf(ERROR, "%s: SDIO transaction failed\n", __FILE__); |
| return status; |
| } |
| |
| uint32_t recv_size; |
| if ((status = CardRecvPacket(&recv_size)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read packet from card\n", __FILE__); |
| return status; |
| } else if (recv_size < kSdioHeaderSize) { |
| zxlogf(ERROR, "%s: Short read from card\n", __FILE__); |
| return ZX_ERR_IO; |
| } else if (recv_size - kSdioHeaderSize > buffer_size) { |
| zxlogf(ERROR, "%s: Received packet too big for buffer\n", __FILE__); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| txn.addr = kCrdrAddress; |
| txn.data_size = recv_size; |
| txn.incr = false; |
| txn.write = false; |
| txn.use_dma = true; |
| txn.dma_vmo = vmo.get(); |
| txn.buf_offset = 0; |
| if ((status = sdio_.DoRwTxn(kFunctionNumber, &txn)) != ZX_OK) { |
| zxlogf(ERROR, "%s: SDIO transaction failed\n", __FILE__); |
| return status; |
| } else if (GetSizeField(packet_buf) != recv_size) { |
| zxlogf(ERROR, "%s: Packet size doesn't match register value\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| *size = recv_size - kSdioHeaderSize; |
| memcpy(packet, packet_buf + kSdioHeaderSize, *size); |
| |
| return ZX_OK; |
| } |
| |
| int BtHciMediatek::CardGetFirmwareStatus() { |
| constexpr uint8_t kFwStatusPacket[] = {0x01, 0x17, 0x01, 0x00, 0x01}; |
| constexpr uint8_t kFwStatusResponsePacket[] = {0x04, 0xe4, 0x05, 0x02, 0x17, 0x01, 0x00, 0x00}; |
| |
| uint8_t packet[sizeof(kFwStatusResponsePacket)]; |
| memcpy(packet, kFwStatusPacket, sizeof(kFwStatusPacket)); |
| size_t size = sizeof(kFwStatusPacket); |
| |
| zx_status_t status = CardSendVendorPacket(kPacketTypeCmd, 0x6f, packet, &size, sizeof(packet)); |
| if (status != ZX_OK) { |
| return status; |
| } else if (size != sizeof(kFwStatusResponsePacket)) { |
| zxlogf(ERROR, "%s: Packet header doesn't match size\n", __FILE__); |
| return ZX_ERR_IO; |
| } else if (memcmp(packet, kFwStatusResponsePacket, sizeof(kFwStatusResponsePacket) - 1) != 0) { |
| zxlogf(ERROR, "%s: Unexpected response to firmware status command\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| return packet[sizeof(kFwStatusResponsePacket) - 1]; |
| } |
| |
| zx_status_t BtHciMediatek::CardGetHwVersion(uint32_t* version) { |
| constexpr uint8_t kHwVersionPacket[] = { |
| 0x01, 0x08, 0x08, 0x00, // Vendor data |
| 0x02, 0x01, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x80 // Register address |
| }; |
| |
| constexpr uint8_t kHwVersionResponsePacket[] = { |
| 0x04, 0xe4, 0x10, // Vendor header |
| 0x02, 0x08, 0x0c, 0x00, // Vendor data |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x80, |
| 0x00, 0x00, 0x00, 0x00, // Register data goes here |
| 0x00 // Padding |
| }; |
| |
| constexpr size_t kRegValueOffset = sizeof(kHwVersionResponsePacket) - sizeof(*version) - 1; |
| |
| uint8_t packet[sizeof(kHwVersionResponsePacket)]; |
| memcpy(packet, kHwVersionPacket, sizeof(kHwVersionPacket)); |
| size_t size = sizeof(kHwVersionPacket); |
| |
| zx_status_t status = CardSendVendorPacket(kPacketTypeCmd, 0x6f, packet, &size, sizeof(packet)); |
| if (status != ZX_OK) { |
| return status; |
| } else if (size != sizeof(kHwVersionResponsePacket)) { |
| zxlogf(ERROR, "%s: Packet header doesn't match size\n", __FILE__); |
| return ZX_ERR_IO; |
| } else if (memcmp(packet, kHwVersionResponsePacket, kRegValueOffset)) { |
| zxlogf(ERROR, "%s: Unexpected response to hardware version command\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| memcpy(version, packet + kRegValueOffset, sizeof(*version)); |
| *version = letoh32(*version); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciMediatek::CardDownloadFirmware(const zx::vmo& fw_vmo, size_t fw_size) { |
| if (fw_size < kFwHeaderSize) { |
| zxlogf(ERROR, "%s: Invalid firmware size\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| uint32_t hw_version = 0; |
| zx_status_t status = CardGetHwVersion(&hw_version); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if (hw_version == 0x8a00) { |
| zxlogf(ERROR, "%s: No firmware for card version %04x\n", __FILE__, hw_version); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| fzl::VmoMapper fw_mapper; |
| if ((status = fw_mapper.Map(fw_vmo, 0, 0, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to map firmware VMO\n", __FILE__); |
| return status; |
| } |
| |
| fzl::VmoMapper fw_part_mapper; |
| zx::vmo fw_part_vmo; |
| status = fw_part_mapper.CreateAndMap(kFwPartHeaderSize + fw_part_max_size_, |
| ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr, &fw_part_vmo); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to create and map VMO\n", __FILE__); |
| return status; |
| } |
| |
| const uint8_t* fw_data = reinterpret_cast<const uint8_t*>(fw_mapper.start()) + kFwHeaderSize; |
| fw_size -= kFwHeaderSize; |
| |
| uint8_t* fw_part_buffer = reinterpret_cast<uint8_t*>(fw_part_mapper.start()); |
| |
| size_t fw_left = fw_size; |
| for (size_t send_size; fw_left > 0 && status == ZX_OK; fw_left -= send_size) { |
| send_size = std::min(fw_part_max_size_, fw_left); |
| |
| FirmwarePartMode mode = kFirmwarePartContinue; |
| if (fw_left == fw_size) { |
| mode = kFirmwarePartFirst; |
| } else if (fw_left == send_size) { |
| mode = kFirmwarePartLast; |
| } |
| |
| status = CardSendFirmwarePart(fw_part_vmo.get(), fw_part_buffer, fw_data, send_size, mode); |
| fw_data += send_size; |
| } |
| |
| if (fw_left != 0) { |
| return status; |
| } |
| |
| uint32_t chcr; |
| if ((status = CardRead32(kChcrAddress, &chcr)) != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = CardWrite32(kChcrAddress, chcr | kChcrWriteClear)) != ZX_OK) { |
| return status; |
| } |
| |
| return CardReset(); |
| } |
| |
| zx_status_t BtHciMediatek::CardSendFirmwarePart(zx_handle_t vmo, uint8_t* buffer, |
| const uint8_t* fw_data, size_t size, |
| FirmwarePartMode mode) { |
| constexpr uint8_t kFirmwarePartResponse[] = |
| {0x0c, 0x00, 0x00, 0x00, 0x04, 0xe4, 0x05, 0x02, 0x01, 0x01, 0x00, 0x00}; |
| |
| size_t total_size = fbl::round_up<size_t, size_t>(kFwPartHeaderSize + size, kBlockSize); |
| uint8_t* buffer_ptr = buffer; |
| |
| // SDIO header |
| SetSizeField(buffer_ptr, kFwPartHeaderSize + size); |
| buffer_ptr += 2; |
| *buffer_ptr++ = 0x00; |
| *buffer_ptr++ = 0x00; |
| |
| // Vendor header |
| *buffer_ptr++ = kPacketTypeAcl; |
| *buffer_ptr++ = 0x6f; |
| *buffer_ptr++ = 0xfc; |
| SetSizeField(buffer_ptr, size + 5); |
| buffer_ptr += 2; |
| |
| // STP header |
| *buffer_ptr++ = 0x01; |
| *buffer_ptr++ = 0x01; |
| SetSizeField(buffer_ptr, size + 1); |
| buffer_ptr += 2; |
| |
| *buffer_ptr++ = mode; |
| |
| memcpy(buffer_ptr, fw_data, size); |
| |
| sdio_rw_txn txn; |
| txn.addr = kCtdrAddress; |
| txn.data_size = static_cast<uint32_t>(total_size); |
| txn.incr = false; |
| txn.write = true; |
| txn.use_dma = true; |
| txn.dma_vmo = vmo; |
| txn.buf_offset = 0; |
| zx_status_t status = sdio_.DoRwTxn(kFunctionNumber, &txn); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: SDIO transaction failed\n", __FILE__); |
| return status; |
| } |
| |
| uint32_t recv_size; |
| if ((status = CardRecvPacket(&recv_size)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read packet from card\n", __FILE__); |
| return status; |
| } else if (recv_size != sizeof(kFirmwarePartResponse)) { |
| zxlogf(ERROR, "%s: Packet header doesn't match size\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| txn.addr = kCrdrAddress; |
| txn.data_size = recv_size; |
| txn.incr = false; |
| txn.write = false; |
| txn.use_dma = true; |
| txn.dma_vmo = vmo; |
| txn.buf_offset = 0; |
| if ((status = sdio_.DoRwTxn(kFunctionNumber, &txn)) != ZX_OK) { |
| zxlogf(ERROR, "%s: SDIO transaction failed\n", __FILE__); |
| return status; |
| } else if (memcmp(buffer, kFirmwarePartResponse, sizeof(kFirmwarePartResponse)) != 0) { |
| zxlogf(ERROR, "%s: Unexpected response to firmware packet\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciMediatek::HandleCardInterrupt() { |
| fzl::VmoMapper mapper; |
| zx::vmo vmo; |
| zx_status_t status = |
| mapper.CreateAndMap(kMaxPacketSize, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr, &vmo); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to create and map VMO\n", __FILE__); |
| return status; |
| } |
| |
| uint8_t* header_buf = reinterpret_cast<uint8_t*>(mapper.start()); |
| uint8_t* packet_buf = header_buf + kHciPacketHeaderSize; |
| uint8_t* snoop_buf = header_buf + kHciPacketHeaderSize - 1; |
| |
| uint32_t chisr; |
| if ((status = CardRead32(kChisrAddress, &chisr)) != ZX_OK) { |
| return status; |
| } |
| |
| if ((chisr & kIsrTxEmpty) != 0) { |
| if ((status = CardWrite32(kChisrAddress, kIsrTxEmpty | kIsrTxCompleteCount)) != ZX_OK) { |
| return status; |
| } |
| } |
| |
| uint32_t recv_size = 0; |
| if ((chisr & kIsrRxDone) != 0) { |
| if ((status = CardRecvPacket(&recv_size)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read packet from card\n", __FILE__); |
| return status; |
| } |
| } |
| |
| if (recv_size == 0) { |
| return ZX_OK; |
| } else if (recv_size > kMaxPacketSize) { |
| zxlogf(ERROR, "%s: Received packet too big for buffer\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| sdio_rw_txn txn; |
| txn.addr = kCrdrAddress; |
| txn.data_size = fbl::round_up<uint32_t, uint32_t>(recv_size, kBlockSize); |
| txn.incr = false; |
| txn.write = false; |
| txn.use_dma = true; |
| txn.dma_vmo = vmo.get(); |
| txn.buf_offset = 0; |
| if ((status = sdio_.DoRwTxn(kFunctionNumber, &txn)) != ZX_OK) { |
| zxlogf(ERROR, "%s: SDIO transaction failed\n", __FILE__); |
| return status; |
| } else if (GetSizeField(header_buf) != recv_size) { |
| zxlogf(ERROR, "%s: Packet header doesn't match size\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| recv_size -= kHciPacketHeaderSize; |
| |
| const zx::channel* channel; |
| uint32_t snoop_type; |
| |
| switch (header_buf[kHciPacketHeaderSize - 1]) { |
| case kPacketTypeAcl: |
| channel = &acl_channel_; |
| snoop_type = BT_HCI_SNOOP_TYPE_ACL; |
| |
| // The MT7668 rounds packets up to a multiple of four bytes, so decode the actual packet |
| // size and only send that much data over the channel. |
| if (recv_size < kAclPacketHeaderSize) { |
| zxlogf(ERROR, "%s: ACL packet from card is too short\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| if (GetSizeField(packet_buf + kAclPacketSizeOffset) > recv_size - kAclPacketHeaderSize) { |
| zxlogf(ERROR, "%s: ACL packet from card is too big\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| recv_size = GetSizeField(packet_buf + kAclPacketSizeOffset) + kAclPacketHeaderSize; |
| break; |
| case kPacketTypeEvent: |
| channel = &cmd_channel_; |
| snoop_type = BT_HCI_SNOOP_TYPE_EVT; |
| |
| if (recv_size < kEventPacketHeaderSize) { |
| zxlogf(ERROR, "%s: Event packet from card is too short\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| if (packet_buf[kEventPacketSizeOffset] > recv_size - kEventPacketHeaderSize) { |
| zxlogf(ERROR, "%s: Event packet from card is too big\n", __FILE__); |
| return ZX_ERR_IO; |
| } |
| |
| recv_size = packet_buf[kEventPacketSizeOffset] + kEventPacketHeaderSize; |
| break; |
| case kPacketTypeSco: |
| default: |
| zxlogf(ERROR, "%s: Unknown packet type %u received from card\n", __FILE__, |
| header_buf[kHciPacketHeaderSize - 1]); |
| return ZX_OK; |
| } |
| |
| uint32_t snoop_size = recv_size + 1; |
| |
| if (channel->is_valid()) { |
| if ((status = channel->write(0, packet_buf, recv_size, nullptr, 0)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write to channel\n", __FILE__); |
| return status; |
| } |
| } |
| |
| if (snoop_channel_.is_valid()) { |
| snoop_buf[0] = bt_hci_snoop_flags(snoop_type, true); |
| if ((status = snoop_channel_.write(0, snoop_buf, snoop_size, nullptr, 0)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write to snoop channel\n", __FILE__); |
| return status; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t BtHciMediatek::HostToCardPacket(const zx::channel& channel, uint8_t packet_type, |
| uint32_t snoop_type) { |
| fzl::VmoMapper mapper; |
| zx::vmo vmo; |
| zx_status_t status = |
| mapper.CreateAndMap(kMaxPacketSize, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr, &vmo); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to create and map VMO\n", __FILE__); |
| return status; |
| } |
| |
| uint8_t* header_buf = reinterpret_cast<uint8_t*>(mapper.start()); |
| |
| uint8_t* packet_buf = header_buf + kHciPacketHeaderSize; |
| const uint32_t packet_buf_size = static_cast<uint32_t>(mapper.size() - kHciPacketHeaderSize); |
| |
| uint8_t* snoop_buf = header_buf + kHciPacketHeaderSize - 1; |
| |
| uint32_t actual; |
| status = channel.read(0, packet_buf, nullptr, packet_buf_size, 0, &actual, nullptr); |
| while (status == ZX_OK) { |
| uint32_t snoop_size = actual + 1; |
| actual += kHciPacketHeaderSize; |
| |
| if (actual > kMaxPacketSize) { |
| zxlogf(ERROR, "%s: Host packet too big for card\n", __FILE__); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| SetSizeField(header_buf, actual); |
| header_buf[2] = 0; |
| header_buf[3] = 0; |
| header_buf[4] = packet_type; |
| |
| sdio_rw_txn txn; |
| txn.addr = kCtdrAddress; |
| txn.data_size = fbl::round_up<uint32_t, uint32_t>(actual, kBlockSize); |
| txn.incr = false; |
| txn.write = true; |
| txn.use_dma = true; |
| txn.dma_vmo = vmo.get(); |
| txn.buf_offset = 0; |
| if ((status = sdio_.DoRwTxn(kFunctionNumber, &txn)) != ZX_OK) { |
| zxlogf(ERROR, "%s: SDIO transaction failed\n", __FILE__); |
| return status; |
| } |
| |
| if (snoop_channel_.is_valid()) { |
| snoop_buf[0] = bt_hci_snoop_flags(snoop_type, false); |
| if ((status = snoop_channel_.write(0, snoop_buf, snoop_size, nullptr, 0)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to write to snoop channel\n", __FILE__); |
| return status; |
| } |
| } |
| |
| status = channel.read(0, packet_buf, nullptr, packet_buf_size, 0, &actual, nullptr); |
| } |
| |
| if (status != ZX_ERR_SHOULD_WAIT) { |
| zxlogf(ERROR, "%s: Failed to read from command channel\n", __FILE__); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| int BtHciMediatek::Thread() { |
| for (;;) { |
| zx_port_packet_t packet; |
| zx_status_t status = port_.wait(zx::deadline_after(zx::duration::infinite()), &packet); |
| |
| fbl::AutoLock lock(&thread_mutex_); |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Port wait failed\n", __FILE__); |
| thread_running_ = false; |
| return thrd_error; |
| } |
| |
| if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE && packet.key == kCommandChannelKey) { |
| bool rearm = true; |
| if (packet.signal.observed & ZX_CHANNEL_READABLE) { |
| status = HostToCardPacket(cmd_channel_, kPacketTypeCmd, BT_HCI_SNOOP_TYPE_CMD); |
| if (status != ZX_OK) { |
| thread_running_ = false; |
| return thrd_error; |
| } |
| } |
| |
| if (packet.signal.observed & (ZX_CHANNEL_PEER_CLOSED | ZX_SIGNAL_HANDLE_CLOSED)) { |
| port_.cancel(cmd_channel_, packet.key); |
| cmd_channel_.reset(); |
| rearm = false; |
| } |
| if (rearm) { |
| zx_signals_t wait_signals = ZX_CHANNEL_PEER_CLOSED | ZX_SIGNAL_HANDLE_CLOSED | |
| ZX_CHANNEL_READABLE; |
| status = cmd_channel_.wait_async(port_, packet.key, wait_signals, |
| ZX_WAIT_ASYNC_ONCE); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Channel object_wait_async failed: %d\n", |
| __FILE__, status); |
| } |
| } |
| } else if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE && packet.key == kAclChannelKey) { |
| bool rearm = true; |
| if (packet.signal.observed & ZX_CHANNEL_READABLE) { |
| status = HostToCardPacket(acl_channel_, kPacketTypeAcl, BT_HCI_SNOOP_TYPE_ACL); |
| if (status != ZX_OK) { |
| thread_running_ = false; |
| return thrd_error; |
| } |
| } |
| |
| if (packet.signal.observed & (ZX_CHANNEL_PEER_CLOSED | ZX_SIGNAL_HANDLE_CLOSED)) { |
| port_.cancel(acl_channel_, packet.key); |
| acl_channel_.reset(); |
| rearm = false; |
| } |
| if (rearm) { |
| zx_signals_t wait_signals = ZX_CHANNEL_PEER_CLOSED | ZX_SIGNAL_HANDLE_CLOSED | |
| ZX_CHANNEL_READABLE; |
| status = acl_channel_.wait_async(port_, packet.key, wait_signals, |
| ZX_WAIT_ASYNC_ONCE); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Channel object_wait_async failed: %d\n", |
| __FILE__, status); |
| } |
| } |
| } else if (packet.type == ZX_PKT_TYPE_SIGNAL_ONE && packet.key == kSnoopChannelKey) { |
| if (packet.signal.observed & (ZX_CHANNEL_PEER_CLOSED | ZX_SIGNAL_HANDLE_CLOSED)) { |
| port_.cancel(snoop_channel_, packet.key); |
| snoop_channel_.reset(); |
| } else { |
| zx_signals_t wait_signals = ZX_CHANNEL_PEER_CLOSED | ZX_SIGNAL_HANDLE_CLOSED; |
| status = acl_channel_.wait_async(port_, packet.key, wait_signals, |
| ZX_WAIT_ASYNC_ONCE); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Channel object_wait_async failed: %d\n", |
| __FILE__, status); |
| } |
| } |
| } else if (packet.type == ZX_PKT_TYPE_INTERRUPT && packet.key == kSdioInterruptKey) { |
| CardWrite32(kChlpcrAddress, kChlpcrFwIntClear); |
| status = HandleCardInterrupt(); |
| CardWrite32(kChlpcrAddress, kChlpcrFwIntSet); |
| |
| sdio_int_.ack(); |
| |
| if (status != ZX_OK) { |
| thread_running_ = false; |
| return thrd_error; |
| } |
| } else if (packet.type == ZX_PKT_TYPE_USER && packet.key == kStopThreadKey) { |
| thread_running_ = false; |
| break; |
| } else { |
| zxlogf(WARN, "%s: Unknown packet type %u or key %lu\n", __FILE__, packet.type, |
| packet.key); |
| } |
| |
| if (!cmd_channel_.is_valid() && !acl_channel_.is_valid() && !snoop_channel_.is_valid()) { |
| thread_running_ = false; |
| break; |
| } |
| } |
| |
| return thrd_success; |
| } |
| |
| } // namespace bluetooth |
| |
| static zx_driver_ops_t bt_hci_mediatek_driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = bluetooth::BtHciMediatek::Create; |
| return ops; |
| }(); |
| |
| ZIRCON_DRIVER_BEGIN(bt_hci_mediatek, bt_hci_mediatek_driver_ops, "zircon", "0.1", 2) |
| BI_ABORT_IF(NE, BIND_SDIO_VID, 0x037a), |
| BI_MATCH_IF(EQ, BIND_SDIO_PID, 0x7668), |
| ZIRCON_DRIVER_END(bt_hci_mediatek) |