blob: ad3e9808f8a039c28c06ad51ecc96099f2725342 [file] [log] [blame]
// 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/dsiimpl/c/banjo.h>
#include <lib/mipi-dsi/mipi-dsi.h>
#include <lib/stdcompat/span.h>
#include <lib/zx/result.h>
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <array>
#include <cstdint>
#include <fbl/alloc_checker.h>
#include "src/graphics/display/drivers/amlogic-display/panel-config.h"
#include "src/graphics/display/lib/designware-dsi/dsi-host-controller.h"
#include "src/graphics/display/lib/driver-framework-migration-utils/logging/zxlogf.h"
#include "src/graphics/display/lib/driver-framework-migration-utils/namespace/namespace.h"
namespace amlogic_display {
namespace {
constexpr int IsDsiCommandPayloadSizeValid(uint8_t cmd_type, int payload_size) {
switch (cmd_type) {
case kMipiDsiDtDcsShortWrite0:
return payload_size == 1;
case kMipiDsiDtDcsShortWrite1:
return payload_size == 2;
case kMipiDsiDtDcsLongWrite:
return payload_size >= 1;
case kMipiDsiDtGenShortWrite0:
return payload_size == 0;
case kMipiDsiDtGenShortWrite1:
return payload_size == 1;
case kMipiDsiDtGenShortWrite2:
return payload_size == 2;
case kMipiDsiDtGenLongWrite:
return payload_size >= 0;
}
ZX_ASSERT_MSG(false, "Unsupported command type: 0x%02x", cmd_type);
}
constexpr int kReadRegisterMaximumValueCount = 4;
zx_status_t CheckDsiDeviceRegister(
designware_dsi::DsiHostController& designware_dsi_host_controller, uint8_t reg, size_t count) {
ZX_DEBUG_ASSERT(count > 0);
ZX_DEBUG_ASSERT(count <= kReadRegisterMaximumValueCount);
const std::array<uint8_t, 2> set_maximum_return_packet_size_payload = {
// `count` must not exceed kReadRegisterMaximumValueCount = 4, so the
// cast won't overflow which causes undefined behavior.
static_cast<uint8_t>(count),
0,
};
const std::array<uint8_t, 1> read_payload = {
reg,
};
std::array<uint8_t, kReadRegisterMaximumValueCount> response_buffer = {};
cpp20::span<uint8_t> response_payload(response_buffer.data(), count);
const mipi_dsi::DsiCommandAndResponse commands[] = {
// Sets the maximum return packet size to avoid read buffer overflow on the
// DSI host controller.
{
.virtual_channel_id = kMipiDsiVirtualChanId,
.data_type = kMipiDsiDtSetMaxRetPkt,
.payload = set_maximum_return_packet_size_payload,
.response_payload = {},
},
{
.virtual_channel_id = kMipiDsiVirtualChanId,
.data_type = kMipiDsiDtGenShortRead1,
.payload = read_payload,
.response_payload = response_payload,
},
};
zx::result<> result = designware_dsi_host_controller.IssueCommands(commands);
if (result.is_error()) {
zxlogf(ERROR, "Could not read register %d: %s", reg, result.status_string());
return result.status_value();
}
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(
designware_dsi::DsiHostController& designware_dsi_host_controller) {
// TODO(https://fxbug.dev/322450952): The Read Display Identification
// Information (0x04) command is not guaranteed to be available on all
// display driver ICs. The response size and the actual meaning of the
// response may vary, depending on the DDIC models. Do not hardcode the
// command address and the response size.
//
// The following command address and response size are specified on:
// - JD9364 datasheet, Section 10.2.3 "RDDIDIF", page 146
// - JD9365D datasheet, Section 10.2.3 "RDDIDIF", page 130
// - NT35596 datasheet, Section 6.1 "User Command Set (Command 1)", page 158
constexpr uint8_t kCommandReadDisplayIdentificationInformation = 0x04;
constexpr int kCommandReadDisplayIdentificationInformationResponseSize = 3;
const std::array<uint8_t, 2> set_maximum_return_packet_size_payload = {
kCommandReadDisplayIdentificationInformationResponseSize & 0xff,
kCommandReadDisplayIdentificationInformationResponseSize >> 8,
};
const std::array<uint8_t, 1> read_display_identification_payload = {
kCommandReadDisplayIdentificationInformation,
};
std::array<uint8_t, kCommandReadDisplayIdentificationInformationResponseSize> response_payload;
const mipi_dsi::DsiCommandAndResponse commands[] = {
// Sets the maximum return packet size to avoid read buffer overflow on the
// DSI host controller.
{
.virtual_channel_id = kMipiDsiVirtualChanId,
.data_type = kMipiDsiDtSetMaxRetPkt,
.payload = set_maximum_return_packet_size_payload,
.response_payload = {},
},
{
.virtual_channel_id = kMipiDsiVirtualChanId,
.data_type = kMipiDsiDtGenShortRead1,
.payload = read_display_identification_payload,
.response_payload = response_payload,
},
};
zx::result<> result = designware_dsi_host_controller.IssueCommands(commands);
if (result.is_error()) {
zxlogf(ERROR, "Failed to read out Display ID: %s", result.status_string());
return result.take_error();
}
const uint32_t display_id =
response_payload[0] << 16 | response_payload[1] << 8 | response_payload[2];
return zx::ok(display_id);
}
} // namespace
// static
zx::result<std::unique_ptr<Lcd>> Lcd::Create(
display::Namespace& incoming, uint32_t panel_type, const PanelConfig* panel_config,
designware_dsi::DsiHostController* designware_dsi_host_controller, bool enabled) {
ZX_DEBUG_ASSERT(panel_config != nullptr);
ZX_DEBUG_ASSERT(designware_dsi_host_controller != nullptr);
static constexpr char kLcdGpioFragmentName[] = "gpio-lcd-reset";
zx::result<fidl::ClientEnd<fuchsia_hardware_gpio::Gpio>> lcd_reset_gpio_result =
incoming.Connect<fuchsia_hardware_gpio::Service::Device>(kLcdGpioFragmentName);
if (lcd_reset_gpio_result.is_error()) {
zxlogf(ERROR, "Failed to get gpio protocol from fragment: %s", kLcdGpioFragmentName);
return lcd_reset_gpio_result.take_error();
}
fbl::AllocChecker alloc_checker;
auto lcd = fbl::make_unique_checked<Lcd>(&alloc_checker, panel_type, panel_config,
designware_dsi_host_controller,
std::move(lcd_reset_gpio_result).value(), enabled);
if (!alloc_checker.check()) {
zxlogf(ERROR, "Failed to allocate memory for Lcd");
return zx::error(ZX_ERR_NO_MEMORY);
}
return zx::ok(std::move(lcd));
}
Lcd::Lcd(uint32_t panel_type, const PanelConfig* panel_config,
designware_dsi::DsiHostController* designware_dsi_host_controller,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> lcd_reset_gpio, bool enabled)
: panel_type_(panel_type),
panel_config_(*panel_config),
designware_dsi_host_controller_(*designware_dsi_host_controller),
lcd_reset_gpio_(std::move(lcd_reset_gpio)) {
ZX_DEBUG_ASSERT(panel_config != nullptr);
ZX_DEBUG_ASSERT(designware_dsi_host_controller != nullptr);
ZX_DEBUG_ASSERT(lcd_reset_gpio_.is_valid());
}
// 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];
// 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 = lcd_reset_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: {
if (payload_size != 2) {
zxlogf(ERROR,
"Invalid MIPI-DSI read register payload size: "
"expected 2 (register address and count), actual %d",
payload_size);
return zx::error(ZX_ERR_INVALID_ARGS);
}
uint8_t address = encoded_commands[i + 2];
int count = encoded_commands[i + 3];
if (count <= 0 || count > kReadRegisterMaximumValueCount) {
zxlogf(ERROR,
"Invalid MIPI-DSI read register value count: %d. "
"It must be positive and no more than %d",
count, kReadRegisterMaximumValueCount);
return zx::error(ZX_ERR_INVALID_ARGS);
}
zxlogf(TRACE, "Read MIPI-DSI register: address=0x%02x count=%d", address, count);
status = CheckDsiDeviceRegister(designware_dsi_host_controller_, address, count);
if (status != ZX_OK) {
zxlogf(ERROR, "Error reading MIPI-DSI register 0x%02x: %s", address,
zx_status_get_string(status));
return zx::error(status);
}
break;
}
// All other cmd_type bytes are real DSI commands
case kMipiDsiDtDcsShortWrite0:
case kMipiDsiDtDcsShortWrite1:
case kMipiDsiDtDcsLongWrite:
case kMipiDsiDtGenShortWrite0:
case kMipiDsiDtGenShortWrite1:
case kMipiDsiDtGenShortWrite2:
case kMipiDsiDtGenLongWrite: {
zxlogf(TRACE, "DSI command type: 0x%02x payload size: %d", cmd_type, payload_size);
if (!IsDsiCommandPayloadSizeValid(cmd_type, payload_size)) {
zxlogf(ERROR,
"Invalid payload size for MIPI-DSI command 0x%02x: "
"actual size %d",
cmd_type, payload_size);
return zx::error(ZX_ERR_INVALID_ARGS);
}
const cpp20::span<const uint8_t> payload = encoded_commands.subspan(i + 2, payload_size);
const mipi_dsi::DsiCommandAndResponse commands[] = {{
.virtual_channel_id = kMipiDsiVirtualChanId,
.data_type = cmd_type,
.payload = payload,
.response_payload = {},
}};
zx::result<> result = designware_dsi_host_controller_.IssueCommands(commands);
if (result.is_error()) {
zxlogf(ERROR, "Failed to send command to the MIPI-DSI peripheral: %s",
result.status_string());
return result.take_error();
}
break;
}
case kMipiDsiDtDcsRead0:
case kMipiDsiDtGenShortRead0:
case kMipiDsiDtGenShortRead1:
case kMipiDsiDtGenShortRead2:
// TODO(https://fxbug.dev/322438328): Support MIPI-DSI read commands.
zxlogf(ERROR, "MIPI-DSI read command 0x%02x is not supported", cmd_type);
return zx::error(ZX_ERR_NOT_SUPPORTED);
default:
zxlogf(ERROR, "MIPI-DSI / panel initialization command 0x%02x is not supported", cmd_type);
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// 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();
}
zxlogf(INFO, "Powering off the LCD [type=%d]", panel_type_);
zx::result<> power_off_result = PerformDisplayInitCommandSequence(panel_config_.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();
}
zxlogf(INFO, "Powering on the LCD [type=%d]", panel_type_);
zx::result<> power_on_result = PerformDisplayInitCommandSequence(panel_config_.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(designware_dsi_host_controller_);
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();
}
} // namespace amlogic_display