blob: 58a80baebfa5d9a1c9f23b79f46f6d6e17351094 [file] [log] [blame] [edit]
// Copyright 2018 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 "src/graphics/display/drivers/amlogic-display/dsi-host.h"
#include <lib/ddk/debug.h>
#include <lib/device-protocol/display-panel.h>
#include <zircon/status.h>
#include <fbl/alloc_checker.h>
#include "src/graphics/display/drivers/amlogic-display/common.h"
#include "src/graphics/display/drivers/amlogic-display/initcodes-inl.h"
namespace amlogic_display {
#define READ32_MIPI_DSI_REG(a) mipi_dsi_mmio_->Read32(a)
#define WRITE32_MIPI_DSI_REG(a, v) mipi_dsi_mmio_->Write32(v, a)
#define READ32_HHI_REG(a) hhi_mmio_->Read32(a)
#define WRITE32_HHI_REG(a, v) hhi_mmio_->Write32(v, a)
namespace {
constexpr uint8_t kEmptySequence[] = {};
constexpr cpp20::span<const uint8_t> kEmptySequenceSpan = {kEmptySequence, 0};
// Convenience function for building PanelConfigs. Most op sequences are shared
// between panel types.
constexpr PanelConfig MakeConfigForAstroSherlockNelson(const char* name,
cpp20::span<const uint8_t> init_seq) {
return {.name = name,
.dsi_on = init_seq,
.dsi_off = {lcd_shutdown_sequence, std::size(lcd_shutdown_sequence)},
.power_on = {kLcdPowerOnSequenceForAstroSherlockNelson,
std::size(kLcdPowerOnSequenceForAstroSherlockNelson)},
.power_off = {kLcdPowerOffSequenceForAstroSherlockNelson,
std::size(kLcdPowerOffSequenceForAstroSherlockNelson)}};
}
constexpr PanelConfig kVim3Ts050PanelConfig = {
.name = "MTF050FHDI_03",
.dsi_on = {lcd_init_sequence_MTF050FHDI_03, std::size(lcd_init_sequence_MTF050FHDI_03)},
.dsi_off = {lcd_shutdown_sequence, std::size(lcd_shutdown_sequence)},
.power_on = {kLcdPowerOnSequenceForVim3Ts050, std::size(kLcdPowerOnSequenceForVim3Ts050)},
.power_off = {kLcdPowerOffSequenceForVim3Ts050, std::size(kLcdPowerOffSequenceForVim3Ts050)},
};
// LINT.IfChange
/// Panel type IDs are compact. This array should be updated when
/// <lib/device-protocol/display-panel.h> is.
constexpr PanelConfig kPanelConfig[] = {
MakeConfigForAstroSherlockNelson(
"TV070WSM_FT", {lcd_init_sequence_TV070WSM_FT, std::size(lcd_init_sequence_TV070WSM_FT)}),
MakeConfigForAstroSherlockNelson(
"P070ACB_FT", {lcd_init_sequence_P070ACB_FT, std::size(lcd_init_sequence_P070ACB_FT)}),
MakeConfigForAstroSherlockNelson(
"TV101WXM_FT", {lcd_init_sequence_TV101WXM_FT, std::size(lcd_init_sequence_TV101WXM_FT)}),
MakeConfigForAstroSherlockNelson(
"G101B158_FT", {lcd_init_sequence_G101B158_FT, std::size(lcd_init_sequence_G101B158_FT)}),
// ILI9881C & ST7701S are not supported
MakeConfigForAstroSherlockNelson("ILI9881C", kEmptySequenceSpan),
MakeConfigForAstroSherlockNelson("ST7701S", kEmptySequenceSpan),
MakeConfigForAstroSherlockNelson(
"TV080WXM_FT", {lcd_init_sequence_TV080WXM_FT, std::size(lcd_init_sequence_TV080WXM_FT)}),
MakeConfigForAstroSherlockNelson(
"TV101WXM_FT_9365",
{lcd_init_sequence_TV101WXM_FT_9365, std::size(lcd_init_sequence_TV101WXM_FT_9365)}),
MakeConfigForAstroSherlockNelson(
"TV070WSM_FT_9365",
{lcd_init_sequence_TV070WSM_FT_9365, std::size(lcd_init_sequence_TV070WSM_FT_9365)}),
MakeConfigForAstroSherlockNelson(
"KD070D82_FT", {lcd_init_sequence_KD070D82_FT, std::size(lcd_init_sequence_KD070D82_FT)}),
MakeConfigForAstroSherlockNelson(
"KD070D82_FT_9365",
{lcd_init_sequence_KD070D82_FT_9365, std::size(lcd_init_sequence_KD070D82_FT_9365)}),
MakeConfigForAstroSherlockNelson(
"TV070WSM_ST7703I",
{lcd_init_sequence_TV070WSM_ST7703I, std::size(lcd_init_sequence_TV070WSM_ST7703I)}),
kVim3Ts050PanelConfig,
};
// LINT.ThenChange(//src/graphics/display/lib/device-protocol-display/include/lib/device-protocol/display-panel.h)
const PanelConfig* GetPanelConfig(uint32_t panel_type) {
ZX_DEBUG_ASSERT(panel_type <= PANEL_MTF050FHDI_03);
ZX_DEBUG_ASSERT(panel_type != PANEL_ILI9881C);
ZX_DEBUG_ASSERT(panel_type != PANEL_ST7701S);
if (panel_type == PANEL_ILI9881C || panel_type == PANEL_ST7701S) {
return nullptr;
}
return &(kPanelConfig[panel_type]);
}
} // namespace
DsiHost::DsiHost(zx_device_t* parent, uint32_t panel_type,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> lcd_gpio)
: pdev_(ddk::PDevFidl::FromFragment(parent)),
dsiimpl_(parent, "dsi"),
lcd_gpio_(std::move(lcd_gpio)),
panel_type_(panel_type) {}
// static
zx::result<std::unique_ptr<DsiHost>> DsiHost::Create(zx_device_t* parent, uint32_t panel_type) {
fbl::AllocChecker ac;
const char* kLcdGpioFragmentName = "gpio-lcd-reset";
zx::result lcd_gpio =
ddk::Device<void>::DdkConnectFragmentFidlProtocol<fuchsia_hardware_gpio::Service::Device>(
parent, kLcdGpioFragmentName);
if (lcd_gpio.is_error()) {
zxlogf(ERROR, "Failed to get gpio protocol from fragment: %s", kLcdGpioFragmentName);
return lcd_gpio.take_error();
}
std::unique_ptr<DsiHost> self = fbl::make_unique_checked<DsiHost>(
&ac, DsiHost(parent, panel_type, std::move(lcd_gpio.value())));
if (!ac.check()) {
zxlogf(ERROR, "No memory to allocate a DSI host");
return zx::error(ZX_ERR_NO_MEMORY);
}
self->panel_config_ = GetPanelConfig(panel_type);
if (self->panel_config_ == nullptr) {
zxlogf(ERROR, "Unrecognized panel type %d", panel_type);
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (!self->pdev_.is_valid()) {
zxlogf(ERROR, "DsiHost: Could not get ZX_PROTOCOL_PDEV protocol");
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Map MIPI DSI and HHI registers
zx_status_t status = self->pdev_.MapMmio(MMIO_MPI_DSI, &(self->mipi_dsi_mmio_));
if (status != ZX_OK) {
zxlogf(ERROR, "Could not map MIPI DSI mmio: %s", zx_status_get_string(status));
return zx::error(status);
}
status = self->pdev_.MapMmio(MMIO_HHI, &(self->hhi_mmio_));
if (status != ZX_OK) {
zxlogf(ERROR, "Could not map HHI mmio: %s", zx_status_get_string(status));
return zx::error(status);
}
// panel_type_ is now canonical.
lcd_gpio =
ddk::Device<void>::DdkConnectFragmentFidlProtocol<fuchsia_hardware_gpio::Service::Device>(
parent, kLcdGpioFragmentName);
if (lcd_gpio.is_error()) {
zxlogf(ERROR, "Failed to get gpio protocol from fragment: %s", kLcdGpioFragmentName);
return lcd_gpio.take_error();
}
zx::result<std::unique_ptr<Lcd>> lcd_or_status =
Lcd::Create(self->panel_type_, self->panel_config_->dsi_on, self->panel_config_->dsi_off,
fit::bind_member(self.get(), &DsiHost::SetSignalPower), self->dsiimpl_,
std::move(lcd_gpio.value()), kBootloaderDisplayEnabled);
if (lcd_or_status.is_error()) {
zxlogf(ERROR, "Failed to create LCD object: %s", lcd_or_status.status_string());
return zx::error(lcd_or_status.error_value());
}
self->lcd_ = std::move(lcd_or_status).value();
auto phy_or_status = MipiPhy::Create(self->pdev_, self->dsiimpl_, kBootloaderDisplayEnabled);
if (phy_or_status.is_error()) {
zxlogf(ERROR, "Failed to create PHY object: %s", phy_or_status.status_string());
return zx::error(phy_or_status.error_value());
}
self->phy_ = std::move(phy_or_status.value());
self->enabled_ = kBootloaderDisplayEnabled;
return zx::ok(std::move(self));
}
zx::result<> DsiHost::PerformPowerOpSequence(cpp20::span<const PowerOp> commands,
fit::callback<zx::result<>()> power_on) {
if (commands.size() == 0) {
zxlogf(ERROR, "No power commands to execute");
return zx::ok();
}
uint8_t wait_count = 0;
for (const auto op : commands) {
zxlogf(TRACE, "power_op %d index=%d value=%d sleep_ms=%d", op.op, op.index, op.value,
op.sleep_ms);
switch (op.op) {
case kPowerOpExit:
zxlogf(TRACE, "power_exit");
return zx::ok();
case kPowerOpGpio: {
zxlogf(TRACE, "power_set_gpio pin #%d value=%d", op.index, op.value);
if (op.index != 0) {
zxlogf(ERROR, "Unrecognized GPIO pin #%d, ignoring", op.index);
break;
}
fidl::WireResult result = lcd_gpio_->Write(op.value);
if (!result.ok()) {
zxlogf(ERROR, "Failed to send Write request to lcd gpio: %s", result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
zxlogf(ERROR, "Failed to write to lcd gpio: %s",
zx_status_get_string(result->error_value()));
return result->take_error();
}
break;
}
case kPowerOpSignal: {
zxlogf(TRACE, "power_signal dsi_init");
zx::result<> power_on_result = power_on();
if (!power_on_result.is_ok()) {
zxlogf(ERROR, "Failed to power on MIPI DSI display: %s", power_on_result.status_string());
return power_on_result.take_error();
}
break;
}
case kPowerOpAwaitGpio:
zxlogf(TRACE, "power_await_gpio pin #%d value=%d timeout=%d msec", op.index, op.value,
op.sleep_ms);
if (op.index != 0) {
zxlogf(ERROR, "Unrecognized GPIO pin #%d, ignoring", op.index);
break;
}
{
fidl::WireResult result =
lcd_gpio_->ConfigIn(fuchsia_hardware_gpio::GpioFlags::kPullDown);
if (!result.ok()) {
zxlogf(ERROR, "Failed to send ConfigIn request to lcd gpio: %s",
result.status_string());
return zx::error(result.status());
}
auto& response = result.value();
if (response.is_error()) {
zxlogf(ERROR, "Failed to configure lcd gpio to input: %s",
zx_status_get_string(response.error_value()));
return response.take_error();
}
}
for (wait_count = 0; wait_count < op.sleep_ms; wait_count++) {
fidl::WireResult read_result = lcd_gpio_->Read();
if (!read_result.ok()) {
zxlogf(ERROR, "Failed to send Read request to lcd gpio: %s",
read_result.status_string());
return zx::error(read_result.status());
}
auto& read_response = read_result.value();
if (read_response.is_error()) {
zxlogf(ERROR, "Failed to read lcd gpio: %s",
zx_status_get_string(read_response.error_value()));
return read_response.take_error();
}
if (read_result.value()->value == op.value) {
break;
}
zx::nanosleep(zx::deadline_after(zx::msec(1)));
}
if (wait_count == op.sleep_ms) {
zxlogf(ERROR, "Timed out waiting for GPIO value=%d", op.value);
}
break;
default:
zxlogf(ERROR, "Unrecognized power op %d", op.op);
break;
}
if (op.op != kPowerOpAwaitGpio && op.sleep_ms != 0) {
zxlogf(TRACE, "power_sleep %d msec", op.sleep_ms);
zx::nanosleep(zx::deadline_after(zx::msec(op.sleep_ms)));
}
}
return zx::ok();
}
zx::result<> DsiHost::ConfigureDsiHostController(const display_setting_t& disp_setting) {
// Setup relevant TOP_CNTL register -- Undocumented --
SET_BIT32(MIPI_DSI, MIPI_DSI_TOP_CNTL, SUPPORTED_DPI_FORMAT, TOP_CNTL_DPI_CLR_MODE_START,
TOP_CNTL_DPI_CLR_MODE_BITS);
SET_BIT32(MIPI_DSI, MIPI_DSI_TOP_CNTL, SUPPORTED_VENC_DATA_WIDTH, TOP_CNTL_IN_CLR_MODE_START,
TOP_CNTL_IN_CLR_MODE_BITS);
SET_BIT32(MIPI_DSI, MIPI_DSI_TOP_CNTL, 0, TOP_CNTL_CHROMA_SUBSAMPLE_START,
TOP_CNTL_CHROMA_SUBSAMPLE_BITS);
// setup dsi config
dsi_config_t dsi_cfg;
dsi_cfg.display_setting = disp_setting;
dsi_cfg.video_mode_type = VIDEO_MODE_BURST;
dsi_cfg.color_coding = COLOR_CODE_PACKED_24BIT_888;
designware_config_t dw_cfg;
dw_cfg.lp_escape_time = phy_->GetLowPowerEscaseTime();
dw_cfg.lp_cmd_pkt_size = LPCMD_PKT_SIZE;
dw_cfg.phy_timer_clkhs_to_lp = PHY_TMR_LPCLK_CLKHS_TO_LP;
dw_cfg.phy_timer_clklp_to_hs = PHY_TMR_LPCLK_CLKLP_TO_HS;
dw_cfg.phy_timer_hs_to_lp = PHY_TMR_HS_TO_LP;
dw_cfg.phy_timer_lp_to_hs = PHY_TMR_LP_TO_HS;
dw_cfg.auto_clklane = 1;
dsi_cfg.vendor_config_buffer = reinterpret_cast<uint8_t*>(&dw_cfg);
dsiimpl_.Config(&dsi_cfg);
return zx::ok();
}
void DsiHost::PhyEnable() {
WRITE32_REG(HHI, HHI_MIPI_CNTL0,
MIPI_CNTL0_CMN_REF_GEN_CTRL(0x29) | MIPI_CNTL0_VREF_SEL(VREF_SEL_VR) |
MIPI_CNTL0_LREF_SEL(LREF_SEL_L_ROUT) | MIPI_CNTL0_LBG_EN |
MIPI_CNTL0_VR_TRIM_CNTL(0x7) | MIPI_CNTL0_VR_GEN_FROM_LGB_EN);
WRITE32_REG(HHI, HHI_MIPI_CNTL1, MIPI_CNTL1_DSI_VBG_EN | MIPI_CNTL1_CTL);
WRITE32_REG(HHI, HHI_MIPI_CNTL2, MIPI_CNTL2_DEFAULT_VAL); // 4 lane
}
void DsiHost::PhyDisable() {
WRITE32_REG(HHI, HHI_MIPI_CNTL0, 0);
WRITE32_REG(HHI, HHI_MIPI_CNTL1, 0);
WRITE32_REG(HHI, HHI_MIPI_CNTL2, 0);
}
void DsiHost::SetSignalPower(bool on) {
// These bits latch after vsync.
if (on) {
SET_BIT32(MIPI_DSI, MIPI_DSI_TOP_CNTL, 1, 2, 1);
zx::nanosleep(zx::deadline_after(zx::msec(20)));
SET_BIT32(MIPI_DSI, MIPI_DSI_TOP_CNTL, 0, 2, 1);
zx::nanosleep(zx::deadline_after(zx::msec(20)));
} else {
SET_BIT32(MIPI_DSI, MIPI_DSI_TOP_CNTL, 0, 2, 1);
zx::nanosleep(zx::deadline_after(zx::msec(20)));
}
}
void DsiHost::Disable(const display_setting_t& disp_setting) {
// turn host off only if it's been fully turned on
if (!enabled_) {
return;
}
// Place dsi in command mode first
dsiimpl_.SetMode(DSI_MODE_COMMAND);
fit::callback<zx::result<>()> power_off = [this]() -> zx::result<> {
zx::result<> result = lcd_->Disable();
if (!result.is_ok()) {
return result.take_error();
}
PhyDisable();
phy_->Shutdown();
return zx::ok();
};
zx::result<> power_off_result =
PerformPowerOpSequence(panel_config_->power_off, std::move(power_off));
if (!power_off_result.is_ok()) {
zxlogf(ERROR, "Failed to power off the DSI display: %s", power_off_result.status_string());
}
enabled_ = false;
}
zx::result<> DsiHost::Enable(const display_setting_t& disp_setting, uint32_t bitrate) {
if (enabled_) {
return zx::ok();
}
fit::callback<zx::result<>()> power_on = [&]() -> zx::result<> {
// Enable MIPI PHY
PhyEnable();
// Load Phy configuration
zx::result<> phy_config_load_result = phy_->PhyCfgLoad(bitrate);
if (!phy_config_load_result.is_ok()) {
zxlogf(ERROR, "Error during phy config calculations: %s",
phy_config_load_result.status_string());
return phy_config_load_result.take_error();
}
// Enable dwc mipi_dsi_host's clock
SET_BIT32(MIPI_DSI, MIPI_DSI_TOP_CNTL, 0x3, 4, 2);
// mipi_dsi_host's reset
SET_BIT32(MIPI_DSI, MIPI_DSI_TOP_SW_RESET, 0xf, 0, 4);
// Release mipi_dsi_host's reset
SET_BIT32(MIPI_DSI, MIPI_DSI_TOP_SW_RESET, 0x0, 0, 4);
// Enable dwc mipi_dsi_host's clock
SET_BIT32(MIPI_DSI, MIPI_DSI_TOP_CLK_CNTL, 0x3, 0, 2);
WRITE32_REG(MIPI_DSI, MIPI_DSI_TOP_MEM_PD, 0);
zx::nanosleep(zx::deadline_after(zx::msec(10)));
// Initialize host in command mode first
dsiimpl_.SetMode(DSI_MODE_COMMAND);
zx::result<> dsi_host_config_result = ConfigureDsiHostController(disp_setting);
if (!dsi_host_config_result.is_ok()) {
zxlogf(ERROR, "Failed to configure the MIPI DSI Host Controller: %s",
dsi_host_config_result.status_string());
return dsi_host_config_result.take_error();
}
// Initialize mipi dsi D-phy
zx::result<> phy_startup_result = phy_->Startup();
if (!phy_startup_result.is_ok()) {
zxlogf(ERROR, "Error during MIPI D-PHY Initialization: %s",
phy_startup_result.status_string());
return phy_startup_result.take_error();
}
// Load LCD Init values while in command mode
zx::result<> lcd_enable_result = lcd_->Enable();
if (!lcd_enable_result.is_ok()) {
zxlogf(ERROR, "Failed to enable LCD: %s", lcd_enable_result.status_string());
}
// switch to video mode
dsiimpl_.SetMode(DSI_MODE_VIDEO);
return zx::ok();
};
zx::result<> power_on_result =
PerformPowerOpSequence(panel_config_->power_on, std::move(power_on));
if (!power_on_result.is_ok()) {
zxlogf(ERROR, "Failed to power on the DSI Display: %s", power_on_result.status_string());
return power_on_result.take_error();
}
// Host is On and Active at this point
enabled_ = true;
return zx::ok();
}
void DsiHost::Dump() {
zxlogf(INFO, "MIPI_DSI_TOP_SW_RESET = 0x%x", READ32_REG(MIPI_DSI, MIPI_DSI_TOP_SW_RESET));
zxlogf(INFO, "MIPI_DSI_TOP_CLK_CNTL = 0x%x", READ32_REG(MIPI_DSI, MIPI_DSI_TOP_CLK_CNTL));
zxlogf(INFO, "MIPI_DSI_TOP_CNTL = 0x%x", READ32_REG(MIPI_DSI, MIPI_DSI_TOP_CNTL));
zxlogf(INFO, "MIPI_DSI_TOP_SUSPEND_CNTL = 0x%x", READ32_REG(MIPI_DSI, MIPI_DSI_TOP_SUSPEND_CNTL));
zxlogf(INFO, "MIPI_DSI_TOP_SUSPEND_LINE = 0x%x", READ32_REG(MIPI_DSI, MIPI_DSI_TOP_SUSPEND_LINE));
zxlogf(INFO, "MIPI_DSI_TOP_SUSPEND_PIX = 0x%x", READ32_REG(MIPI_DSI, MIPI_DSI_TOP_SUSPEND_PIX));
zxlogf(INFO, "MIPI_DSI_TOP_MEAS_CNTL = 0x%x", READ32_REG(MIPI_DSI, MIPI_DSI_TOP_MEAS_CNTL));
zxlogf(INFO, "MIPI_DSI_TOP_STAT = 0x%x", READ32_REG(MIPI_DSI, MIPI_DSI_TOP_STAT));
zxlogf(INFO, "MIPI_DSI_TOP_MEAS_STAT_TE0 = 0x%x",
READ32_REG(MIPI_DSI, MIPI_DSI_TOP_MEAS_STAT_TE0));
zxlogf(INFO, "MIPI_DSI_TOP_MEAS_STAT_TE1 = 0x%x",
READ32_REG(MIPI_DSI, MIPI_DSI_TOP_MEAS_STAT_TE1));
zxlogf(INFO, "MIPI_DSI_TOP_MEAS_STAT_VS0 = 0x%x",
READ32_REG(MIPI_DSI, MIPI_DSI_TOP_MEAS_STAT_VS0));
zxlogf(INFO, "MIPI_DSI_TOP_MEAS_STAT_VS1 = 0x%x",
READ32_REG(MIPI_DSI, MIPI_DSI_TOP_MEAS_STAT_VS1));
zxlogf(INFO, "MIPI_DSI_TOP_INTR_CNTL_STAT = 0x%x",
READ32_REG(MIPI_DSI, MIPI_DSI_TOP_INTR_CNTL_STAT));
zxlogf(INFO, "MIPI_DSI_TOP_MEM_PD = 0x%x", READ32_REG(MIPI_DSI, MIPI_DSI_TOP_MEM_PD));
}
} // namespace amlogic_display