blob: ada5e3d055c414ffd6c6ea32835f4b77ea2ba47e [file] [log] [blame]
// Copyright 2021 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 <dirent.h>
#include <fidl/fuchsia.device/cpp/wire.h>
#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
#include <fidl/fuchsia.hardware.sdio/cpp/wire.h>
#include <fuchsia/hardware/sdio/c/banjo.h>
#include <lib/fdio/fdio.h>
#include <lib/fidl/llcpp/arena.h>
#include <lib/zx/clock.h>
#include <lib/zx/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <vector>
#include <fbl/unique_fd.h>
#include <zxtest/zxtest.h>
#include "sdmmc-test-device-controller-regs.h"
#include "sdmmc-test-device-controller.h"
namespace sdmmc {
class SdmmcHardwareTest : public zxtest::Test {
public:
void SetUp() override {
fidl::WireSyncClient i2c =
GetFidlClient<fuchsia_hardware_i2c::Device2>(kControllerI2cDevicePath);
controller_ = SdmmcTestDeviceController(std::move(i2c));
ASSERT_TRUE(controller_.is_valid(), "Failed to connect to I2C device");
// Reset the test device controller. This will probably fail because the controller doesn't ack
// on I2C before resetting.
CoreControl::Get().FromValue(0).set_por_reset(1).WriteTo(controller_);
const zx::time start = zx::clock::get_monotonic();
while (CoreControl::Get().FromValue(0).ReadFrom(controller_) != ZX_OK) {
}
printf("Took %luus for core to reset\n", (zx::clock::get_monotonic() - start).to_usecs());
EXPECT_OK(CoreControl::Get().FromValue(0).set_core_enable(1).WriteTo(controller_));
// 0x0000 is reserved, set RCA to 0x0001 instead.
EXPECT_OK(Rca0::Get().FromValue(1).WriteTo(controller_));
EXPECT_OK(Rca1::Get().FromValue(0).WriteTo(controller_));
// Support the entire voltage range.
EXPECT_OK(Ocr0::Get().FromValue(0).WriteTo(controller_));
EXPECT_OK(Ocr1::Get().FromValue(0b1111'1111).WriteTo(controller_));
EXPECT_OK(Ocr2::Get().FromValue(0b1111'1111).WriteTo(controller_));
SetupCis();
}
protected:
// TODO: Extract these into a device-specific config
static constexpr char kControllerI2cDevicePath[] =
"/dev/sys/platform/05:00:2/aml-i2c/i2c/i2c-1-50";
static constexpr char kSdmmcDevicePath[] = "/dev/aml_sd";
void SetupCis() {
// CCCR
EXPECT_OK(controller_.WriteFunction(
0, 0x0000,
{
// clang-format disable
/* [000] */ 0x43, 0x03, 0x00, 0x06, // SDIO spec 3.00, functions 1 and 2 ready
/* [004] */ 0x00, 0x00, 0x00, 0x00,
// ----- BUG ----- Must set 4BLS for core driver to register four-bit bus capability.
/* [008] */ 0x83, 0x00, 0x10, 0x00, // cmd52/cmd53 supported, CIS pointer (0x1000)
/* [00c] */ 0x00, 0x00, 0x00, 0x00,
/* [010] */ 0x00, 0x00, 0x00, 0x01, // No function 0 block ops, high speed supported
/* [014] */ 0x07, 0x00, 0x00, 0x00, // SDR50/SDR104/DDR50 supported
// clang-format enable
}));
// FBR function 1
EXPECT_OK(controller_.WriteFunction(
0, 0x0100,
{
// clang-format disable
/* [100] */ 0x00, 0x00, 0x00, 0x00,
/* [104] */ 0x00, 0x00, 0x00, 0x00,
/* [108] */ 0x00, 0x0d, 0x10, 0x00, // CIS pointer (0x100d)
/* [10c] */ 0x00, 0x00, 0x00, 0x00,
/* [110] */ 0x00, 0x00, 0x00, 0x00, // Block size set to zero initially
// clang-format enable
}));
// FBR function 2
EXPECT_OK(controller_.WriteFunction(
0, 0x0200,
{
// clang-format disable
/* [200] */ 0x00, 0x00, 0x00, 0x00,
/* [204] */ 0x00, 0x00, 0x00, 0x00,
/* [208] */ 0x00, 0x0d, 0x10, 0x00, // CIS pointer (0x100d)
/* [20c] */ 0x00, 0x00, 0x00, 0x00,
/* [210] */ 0x00, 0x00, 0x00, 0x00, // Block size set to zero initially
// clang-format enable
}));
EXPECT_OK(controller_.WriteFunction(
0, 0x1000,
{
// clang-format disable
// CISTPL_FUNCE for function 0: 4 bytes, block size 256, max transfer rate 200Mbit/s.
0x22, 0x04, 0x00, 0x00, 0x01, 0b101'011,
// CISTPL_MANFID for function 0: 4 bytes, manufacturer ID 0x0000, product ID 0x0000.
0x20, 0x04, 0x00, 0x00, 0x00, 0x00,
// CISTPL_END
0xff,
// CISTPL_FUNCE for I/O functions: 42 bytes, block size 512.
0x22, 0x2a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
// CISTPL_MANFID for function 0: 4 bytes, manufacturer ID 0x0000, product ID 0x0000.
0x20, 0x04, 0x00, 0x00, 0x00, 0x00,
// CISTPL_END
0xff,
// clang-format enable
}));
}
static void RestartSdmmcDriver() {
fidl::WireSyncClient sdmmc_device = GetFidlClient<fuchsia_device::Controller>(kSdmmcDevicePath);
ASSERT_TRUE(sdmmc_device.is_valid());
const auto response = sdmmc_device->Rebind(fidl::StringView());
ASSERT_TRUE(response.ok());
EXPECT_TRUE(response->result.is_response());
}
std::vector<fidl::WireSyncClient<fuchsia_hardware_sdio::Device>> GetTestSdioFidlClients(
const uint8_t max_function) {
if (max_function == 0 || max_function > 7) {
return {};
}
std::vector<fidl::WireSyncClient<fuchsia_hardware_sdio::Device>> clients(max_function);
for (uint8_t i = 1; i <= max_function; i++) {
// Loop indefinitely trying to get the client. We have to wait for SDIO intialization to
// complete for the devices to be created, which may take some time. If initialization errors
// occur let the test runner time out and fail the test.
while (!clients[i - 1].is_valid()) {
clients[i - 1] = GetTestSdioFidlClient(i);
}
}
return clients;
}
SdmmcTestDeviceController controller_;
private:
// Attempts to get a client of the specified function device for the SDIO test rig. Returns an
// invalid client on any errors.
static fidl::WireSyncClient<fuchsia_hardware_sdio::Device> GetTestSdioFidlClient(
const uint8_t function) {
#if 0
constexpr uint32_t kTestManufacturerId = 0x02d0;
constexpr uint32_t kTestProductId = 0x4359;
#endif
constexpr uint32_t kTestManufacturerId = 0x0000;
constexpr uint32_t kTestProductId = 0x0000;
if (function == 0) {
return {};
}
DIR* const sdio_dir = opendir("/dev/class/sdio");
if (sdio_dir == nullptr) {
return {};
}
fidl::WireSyncClient<fuchsia_hardware_sdio::Device> function_client = {};
struct dirent* entry = readdir(sdio_dir);
for (; entry != nullptr; entry = readdir(sdio_dir)) {
fbl::unique_fd device(openat(dirfd(sdio_dir), entry->d_name, O_RDWR));
if (!device.is_valid()) {
continue;
}
fidl::WireSyncClient client = GetFidlClient<fuchsia_hardware_sdio::Device>(std::move(device));
if (!client.is_valid()) {
continue;
}
const auto response = client->GetDevHwInfo();
if (response->result.is_err()) {
continue;
}
if (response->result.response().function != function) {
continue;
}
const auto& hw_info = response->result.response().hw_info;
if (hw_info.dev_hw_info.num_funcs <= function) {
continue;
}
const auto& function_info = hw_info.funcs_hw_info[function];
if (function_info.manufacturer_id == kTestManufacturerId &&
function_info.product_id == kTestProductId) {
function_client = std::move(client);
break;
}
}
closedir(sdio_dir);
return function_client;
}
};
TEST_F(SdmmcHardwareTest, InitSuccess) {
// Re-bind the SDMMC driver to initialize with the new settings.
printf("Restarting SDMMC driver\n");
RestartSdmmcDriver();
printf("Done, waiting for FPGA init success\n");
EXPECT_OK(CoreStatus::WaitForInitSuccess(controller_));
printf("Done, waiting for SDIO clients\n");
auto clients = GetTestSdioFidlClients(2);
ASSERT_EQ(clients.size(), 2);
printf("Done\n");
// ----- BUG ----- Core driver doesn't set SDIO_CARD_DIRECT_COMMAND capability.
constexpr uint32_t kExpectedSdioCaps =
SDIO_CARD_MULTI_BLOCK | /* SDIO_CARD_DIRECT_COMMAND | */ SDIO_CARD_HIGH_SPEED |
SDIO_CARD_FOUR_BIT_BUS | SDIO_CARD_UHS_SDR50 | SDIO_CARD_UHS_SDR104 | SDIO_CARD_UHS_DDR50;
for (const auto& client : clients) {
// TODO: Read CCCR and make sure all registers were set correctly
const auto response = client->GetDevHwInfo();
ASSERT_TRUE(response.ok());
ASSERT_TRUE(response->result.is_response());
const auto& hw_info = response->result.response().hw_info;
EXPECT_EQ(hw_info.dev_hw_info.num_funcs, 3); // Includes function 0
EXPECT_EQ(hw_info.dev_hw_info.sdio_vsn, 4); // Version 3.00
EXPECT_EQ(hw_info.dev_hw_info.cccr_vsn, 3); // Version 3.00
EXPECT_EQ(hw_info.dev_hw_info.caps, kExpectedSdioCaps);
for (uint32_t i = 0; i < 3; i++) {
EXPECT_EQ(hw_info.funcs_hw_info[i].manufacturer_id, 0);
EXPECT_EQ(hw_info.funcs_hw_info[i].product_id, 0);
if (i == 0) {
EXPECT_EQ(hw_info.funcs_hw_info[i].max_blk_size, 256);
EXPECT_EQ(hw_info.funcs_hw_info[i].max_tran_speed, 200'000);
} else {
EXPECT_EQ(hw_info.funcs_hw_info[i].max_blk_size, 512);
}
EXPECT_EQ(hw_info.funcs_hw_info[i].fn_intf_code, 0);
}
}
}
TEST_F(SdmmcHardwareTest, InitSuccessWithCmd52Retries) {
auto control = CoreControl::Get().FromValue(0);
EXPECT_OK(control.ReadFrom(controller_));
EXPECT_OK(control.set_error_injection_enable(1).WriteTo(controller_));
EXPECT_OK(CrcErrorControl::Get().FromValue(0).set_cmd52_crc_error_enable(1).WriteTo(controller_));
EXPECT_OK(
Cmd52ErrorControl::Get().FromValue(0).set_transfers_until_crc_error(5).WriteTo(controller_));
// Re-bind the SDMMC driver to initialize with the new settings.
printf("Restarting SDMMC driver\n");
RestartSdmmcDriver();
printf("Done, waiting for FPGA init success\n");
EXPECT_OK(CoreStatus::WaitForInitSuccess(controller_));
printf("Done, waiting for SDIO clients\n");
auto clients = GetTestSdioFidlClients(2);
ASSERT_EQ(clients.size(), 2);
printf("Done\n");
}
TEST_F(SdmmcHardwareTest, InitFailureCmd52Errors) {
auto control = CoreControl::Get().FromValue(0);
EXPECT_OK(control.ReadFrom(controller_));
EXPECT_OK(control.set_error_injection_enable(1).WriteTo(controller_));
EXPECT_OK(CrcErrorControl::Get().FromValue(0).set_cmd52_crc_error_enable(1).WriteTo(controller_));
EXPECT_OK(Cmd52ErrorControl::Get().FromValue(0).WriteTo(controller_));
// Re-bind the SDMMC driver to initialize with the new settings.
printf("Restarting SDMMC driver\n");
RestartSdmmcDriver();
printf("Done, waiting for FPGA init failure\n");
EXPECT_OK(CoreStatus::WaitForInitFailure(controller_));
printf("Done\n");
}
TEST_F(SdmmcHardwareTest, ReadCccr) {
// Re-bind the SDMMC driver to initialize with the new settings.
printf("Restarting SDMMC driver\n");
RestartSdmmcDriver();
printf("Done, waiting for FPGA init success\n");
EXPECT_OK(CoreStatus::WaitForInitSuccess(controller_));
printf("Done, waiting for SDIO clients\n");
auto clients = GetTestSdioFidlClients(2);
ASSERT_EQ(clients.size(), 2);
printf("Done\n");
zx::status<uint8_t> io_enable = controller_.ReadFunction(0, 0x2);
ASSERT_TRUE(io_enable.is_ok());
EXPECT_EQ(io_enable.value(), 0b110); // Functions 1 and 2 should be enabled.
}
TEST_F(SdmmcHardwareTest, ReadWriteCccr) {
// Write a reserved bus width value, and make sure it changes after init.
EXPECT_OK(controller_.WriteFunction(0, 0x7, {0b01}));
// Re-bind the SDMMC driver to initialize with the new settings.
printf("Restarting SDMMC driver\n");
RestartSdmmcDriver();
printf("Done, waiting for FPGA init success\n");
EXPECT_OK(CoreStatus::WaitForInitSuccess(controller_));
printf("Done, waiting for SDIO clients\n");
auto clients = GetTestSdioFidlClients(2);
ASSERT_EQ(clients.size(), 2);
printf("Done\n");
zx::status<uint8_t> bus_width = controller_.ReadFunction(0, 0x7);
ASSERT_TRUE(bus_width.is_ok());
EXPECT_EQ(bus_width.value(), 0b10);
}
} // namespace sdmmc