blob: acc16b9438f590305520a95a5dbcf82611ed48a8 [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/lcd.h"
#include <fuchsia/hardware/platform/device/c/banjo.h>
#include <lib/ddk/debug.h>
#include <lib/device-protocol/display-panel.h>
#include <lib/mipi-dsi/mipi-dsi.h>
#include <lib/zx/result.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <ddktl/device.h>
#include <fbl/alloc_checker.h>
#include "src/graphics/display/drivers/amlogic-display/common.h"
#include "src/graphics/display/drivers/amlogic-display/panel-config.h"
#define READ_DISPLAY_ID_CMD (0x04)
#define READ_DISPLAY_ID_LEN (0x03)
namespace amlogic_display {
namespace {
static zx_status_t CheckMipiReg(ddk::DsiImplProtocolClient* dsiimpl, uint8_t reg, size_t count) {
ZX_DEBUG_ASSERT(count > 0);
const uint8_t payload[3] = {kMipiDsiDtGenShortRead1, 1, reg};
uint8_t rsp[count];
mipi_dsi_cmd_t cmd{
.virt_chn_id = kMipiDsiVirtualChanId,
.dsi_data_type = kMipiDsiDtGenShortRead1,
.pld_data_list = payload,
.pld_data_count = 1,
.rsp_data_list = rsp,
.rsp_data_count = count,
.flags = MIPI_DSI_CMD_FLAGS_ACK | MIPI_DSI_CMD_FLAGS_SET_MAX,
};
zx_status_t status;
if ((status = dsiimpl->SendCmd(&cmd, 1)) != ZX_OK) {
zxlogf(ERROR, "Could not read register %c", reg);
return status;
}
if (cmd.rsp_data_count != count) {
zxlogf(ERROR, "MIPI-DSI register read was short: got %zu want %zu. Ignoring",
cmd.rsp_data_count, count);
}
return ZX_OK;
}
// Reads the display hardware ID from the MIPI-DSI interface.
//
// `dsiimpl` must be configured in DSI command mode.
zx::result<uint32_t> GetMipiDsiDisplayId(ddk::DsiImplProtocolClient dsiimpl) {
uint8_t txcmd = READ_DISPLAY_ID_CMD;
uint8_t response[READ_DISPLAY_ID_LEN];
zx_status_t status = ZX_OK;
// Create the command using mipi-dsi library
mipi_dsi_cmd_t cmd;
status =
mipi_dsi::MipiDsi::CreateCommand(&txcmd, 1, response, READ_DISPLAY_ID_LEN, COMMAND_GEN, &cmd);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to create READ_DISPLAY_ID command: %s", zx_status_get_string(status));
return zx::error(status);
}
status = dsiimpl.SendCmd(&cmd, 1);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to read out Display ID: %s", zx_status_get_string(status));
return zx::error(status);
}
const uint32_t display_id = response[0] << 16 | response[1] << 8 | response[2];
return zx::ok(display_id);
}
} // namespace
// This function write DSI commands based on the input buffer.
zx::result<> Lcd::PerformDisplayInitCommandSequence(cpp20::span<const uint8_t> encoded_commands) {
zx_status_t status = ZX_OK;
uint32_t delay_ms = 0;
constexpr size_t kMinCmdSize = 2;
for (size_t i = 0; i < encoded_commands.size() - kMinCmdSize;) {
const uint8_t cmd_type = encoded_commands[i];
const uint8_t payload_size = encoded_commands[i + 1];
bool is_dcs = false;
// This command has an implicit size=2, treat it specially.
if (cmd_type == kDsiOpSleep) {
if (payload_size == 0 || payload_size == 0xff) {
return zx::make_result(status);
}
zx::nanosleep(zx::deadline_after(zx::msec(payload_size)));
i += 2;
continue;
}
if (payload_size == 0) {
i += kMinCmdSize;
continue;
}
if ((i + payload_size + kMinCmdSize) > encoded_commands.size()) {
zxlogf(ERROR, "buffer[%lu] command 0x%x size=0x%x would overflow buffer size=%lu", i,
cmd_type, payload_size, encoded_commands.size());
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
switch (cmd_type) {
case kDsiOpDelay:
delay_ms = 0;
for (size_t j = 0; j < payload_size; j++) {
delay_ms += encoded_commands[i + 2 + j];
}
if (delay_ms > 0) {
zx::nanosleep(zx::deadline_after(zx::msec(delay_ms)));
}
break;
case kDsiOpGpio:
zxlogf(TRACE, "dsi_set_gpio size=%d value=%d", payload_size, encoded_commands[i + 3]);
if (encoded_commands[i + 2] != 0) {
zxlogf(ERROR, "Unrecognized GPIO pin (%d)", encoded_commands[i + 2]);
// We _should_ bail here, but this spec-violating behavior is present
// in the other drivers for this hardware.
//
// return ZX_ERR_UNKNOWN;
} else {
fidl::WireResult result = gpio_->ConfigOut(encoded_commands[i + 3]);
if (!result.ok()) {
zxlogf(ERROR, "Failed to send ConfigOut request to gpio: %s", result.status_string());
return zx::error(result.status());
}
if (result->is_error()) {
zxlogf(ERROR, "Failed to configure gpio to output: %s",
zx_status_get_string(result->error_value()));
return result->take_error();
}
}
if (payload_size > 2 && encoded_commands[i + 4]) {
zxlogf(TRACE, "dsi_set_gpio sleep %d", encoded_commands[i + 4]);
zx::nanosleep(zx::deadline_after(zx::msec(encoded_commands[i + 4])));
}
break;
case kDsiOpReadReg:
zxlogf(TRACE, "dsi_read size=%d reg=%d, count=%d", payload_size, encoded_commands[i + 2],
encoded_commands[i + 3]);
if (payload_size != 2) {
zxlogf(ERROR, "Invalid MIPI-DSI reg check, expected a register and a target value!");
}
status = CheckMipiReg(&dsiimpl_, /*reg=*/encoded_commands[i + 2],
/*count=*/encoded_commands[i + 3]);
if (status != ZX_OK) {
zxlogf(ERROR, "Error reading MIPI register 0x%x: %s", encoded_commands[i + 2],
zx_status_get_string(status));
return zx::error(status);
}
break;
case kDsiOpPhyPowerOn:
zxlogf(TRACE, "dsi_phy_power_on size=%d", payload_size);
set_signal_power_(/*on=*/true);
break;
case kDsiOpPhyPowerOff:
zxlogf(TRACE, "dsi_phy_power_off size=%d", payload_size);
set_signal_power_(/*on=*/false);
break;
// All other cmd_type bytes are real DSI commands
case kMipiDsiDtDcsShortWrite0:
case kMipiDsiDtDcsShortWrite1:
case /*kMipiDsiDtDcsShortWrite2*/ 0x25:
case kMipiDsiDtDcsLongWrite:
case kMipiDsiDtDcsRead0:
is_dcs = true;
__FALLTHROUGH;
default:
zxlogf(TRACE, "dsi_cmd op=0x%x size=%d is_dcs=%s", cmd_type, payload_size,
is_dcs ? "yes" : "no");
ZX_DEBUG_ASSERT(cmd_type != 0x37);
// Create the command using mipi-dsi library
mipi_dsi_cmd_t cmd;
status = mipi_dsi::MipiDsi::CreateCommand(&encoded_commands[i + 2], payload_size, NULL, 0,
is_dcs, &cmd);
if (status == ZX_OK) {
if ((status = dsiimpl_.SendCmd(&cmd, 1)) != ZX_OK) {
zxlogf(ERROR, "Error loading LCD init table. Aborting: %s",
zx_status_get_string(status));
return zx::error(status);
}
} else {
zxlogf(ERROR, "Invalid command at byte 0x%lx (%s). Skipping", i,
zx_status_get_string(status));
}
break;
}
// increment by payload length
i += payload_size + kMinCmdSize;
}
return zx::make_result(status);
}
zx::result<> Lcd::Disable() {
if (!enabled_) {
zxlogf(INFO, "LCD is already off, no work to do");
return zx::ok();
}
if (dsi_off_.size() == 0) {
zxlogf(ERROR, "Unsupported panel (%d) detected!", panel_type_);
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zxlogf(INFO, "Powering off the LCD [type=%d]", panel_type_);
zx::result<> power_off_result = PerformDisplayInitCommandSequence(dsi_off_);
if (!power_off_result.is_ok()) {
zxlogf(ERROR, "Failed to decode and execute panel off sequence: %s",
power_off_result.status_string());
return power_off_result.take_error();
}
enabled_ = false;
return zx::ok();
}
zx::result<> Lcd::Enable() {
if (enabled_) {
zxlogf(INFO, "LCD is already on, no work to do");
return zx::ok();
}
if (dsi_on_.size() == 0) {
zxlogf(ERROR, "Unsupported panel (%d) detected!", panel_type_);
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zxlogf(INFO, "Powering on the LCD [type=%d]", panel_type_);
zx::result<> power_on_result = PerformDisplayInitCommandSequence(dsi_on_);
if (!power_on_result.is_ok()) {
zxlogf(ERROR, "Failed to decode and execute panel init sequence: %s",
power_on_result.status_string());
return power_on_result.take_error();
}
// Check LCD initialization status by reading the display hardware ID.
zx::result<uint32_t> display_id_result = GetMipiDsiDisplayId(dsiimpl_);
if (!display_id_result.is_ok()) {
zxlogf(ERROR, "Failed to communicate with LCD Panel to get the display hardware ID: %s",
display_id_result.status_string());
return display_id_result.take_error();
}
zxlogf(INFO, "LCD MIPI DSI display hardware ID: 0x%08x", display_id_result.value());
zx_nanosleep(zx_deadline_after(ZX_USEC(10)));
// LCD is on now.
enabled_ = true;
return zx::ok();
}
// static
zx::result<std::unique_ptr<Lcd>> Lcd::Create(uint32_t panel_type, cpp20::span<const uint8_t> dsi_on,
cpp20::span<const uint8_t> dsi_off,
fit::function<void(bool)> set_signal_power,
ddk::DsiImplProtocolClient dsiimpl,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> gpio,
bool already_enabled) {
fbl::AllocChecker alloc_checker;
std::unique_ptr<Lcd> lcd =
fbl::make_unique_checked<Lcd>(&alloc_checker, panel_type, std::move(set_signal_power));
if (!alloc_checker.check()) {
return zx::error(ZX_ERR_NO_MEMORY);
}
lcd->dsi_on_ = dsi_on;
lcd->dsi_off_ = dsi_off;
lcd->dsiimpl_ = dsiimpl;
if (!gpio.is_valid()) {
zxlogf(ERROR, "Could not obtain GPIO protocol");
return zx::error(ZX_ERR_NO_RESOURCES);
}
lcd->gpio_.Bind(std::move(gpio));
lcd->enabled_ = already_enabled;
return zx::ok(std::move(lcd));
}
} // namespace amlogic_display