blob: c6d6dfea755376cc703df983a08b708c5e1d3e85 [file] [log] [blame]
// 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 "dsi-mt.h"
#include "mt-dsi-reg.h"
#include <ddk/platform-defs.h>
#include <ddk/metadata.h>
#include <ddk/metadata/display.h>
#include <fbl/auto_call.h>
#include <fuchsia/sysmem/c/fidl.h>
#include <ddk/binding.h>
namespace dsi_mt {
#define MAX(a, b) (a > b ? a : b )
namespace {
constexpr uint32_t kWMemCommand = 0x3C;
constexpr uint32_t kBusyTimeout = 500000; // from vendor
constexpr uint32_t kReadTimeout = 20; // Unit: ms
constexpr uint32_t kMaxPayloadLength = 64;
constexpr uint32_t kMaxReadResponse = 12;
// MIPI-PHY Related constants based on spec
constexpr uint32_t kTrailOffset = 0xa;
constexpr uint32_t kHsTrailParam = 0x64;
constexpr uint32_t kHsPrepParam = 0x40;
constexpr uint32_t kHsPrepUiMultiplier = 0x5;
constexpr uint32_t kHsZeroParam = 0xC8;
constexpr uint32_t kHsZeroUiMultiplier = 0x0a;
constexpr uint32_t kLpxParam = 0x50;
constexpr uint32_t kHsExitParam = 0x3c;
constexpr uint32_t kHsExitUiMultiplier = 0x80;
constexpr uint32_t kTaGetLpxMultiplier = 0x5;
constexpr uint32_t kTaSureLpxMultiplier = 0x3;
constexpr uint32_t kTaSureLpxDivider = 0x2;
constexpr uint32_t kTaGoMultiplier = 0x4;
constexpr uint32_t kClkTrailParam = 0x64;
constexpr uint32_t kContDet = 0;
constexpr uint32_t kClkZeroParam = 0x190;
constexpr uint32_t kClkPrepParam = 0x40;
constexpr uint32_t kClkExitLpxMultiplier = 0x2;
constexpr uint32_t kClkPostParam = 0x3c;
constexpr uint32_t kClkPostUiMultiplier = 0x80;
enum {
TYPE_SHORT = 0,
TYPE_LONG = 2,
};
} // namespace
zx_status_t DsiMt::DsiImplWriteReg(uint32_t reg, uint32_t val) {
// TODO(payamm): Verify register offset is valid
dsi_mmio_->Write32(val, reg);
return ZX_OK;
}
zx_status_t DsiMt::DsiImplReadReg(uint32_t reg, uint32_t* val) {
// TODO(payamm): Verify register offset is valid
*val = dsi_mmio_->Read32(reg);
return ZX_OK;
}
zx_status_t DsiMt::DsiImplEnableBist(uint32_t pattern) {
// bist enable
DsiImplSetMode(DSI_MODE_VIDEO);
DSI_INFO("Enabling BIST\n");
DsiBistPatternReg::Get().FromValue(pattern).WriteTo(&(*dsi_mmio_));
DsiBistConReg::Get().ReadFrom(&(*dsi_mmio_))
.set_sel_pat_mode(1)
.WriteTo(&(*dsi_mmio_));
StartDsi();
return ZX_OK;
}
zx_status_t DsiMt::GetColorCode(color_code_t c, uint8_t& code) {
zx_status_t status = ZX_OK;
switch (c) {
case COLOR_CODE_PACKED_16BIT_565:
code = 0;
break;
case COLOR_CODE_PACKED_18BIT_666:
code = 1;
break;
case COLOR_CODE_LOOSE_24BIT_666:
code = 2;
break;
case COLOR_CODE_PACKED_24BIT_888:
code = 3;
break;
default:
status = ZX_ERR_INVALID_ARGS;
break;
}
return status;
}
zx_status_t DsiMt::GetVideoMode(video_mode_t v, uint8_t& mode) {
zx_status_t status = ZX_OK;
switch (v) {
case VIDEO_MODE_NON_BURST_PULSE:
mode = 1;
break;
case VIDEO_MODE_NON_BURST_EVENT:
mode = 2;
break;
case VIDEO_MODE_BURST:
mode = 3;
break;
default:
status = ZX_ERR_INVALID_ARGS;
}
return status;
}
zx_status_t DsiMt::DsiImplConfig(const dsi_config_t* dsi_config) {
const display_setting_t disp_setting = dsi_config->display_setting;
// Calculated ui and cycle_time needed for phy configuration
ui_ = 1000 / (disp_setting.lcd_clock * 2) + 0x01;
cycle_time_ = 8000 / (disp_setting.lcd_clock * 2) + 0x01;
// Make sure we support the color code
uint8_t code;
zx_status_t status = GetColorCode(dsi_config->color_coding, code);
if (status != ZX_OK) {
DSI_ERROR("Invalid/Unsupported color coding %d\n", status);
return status;
}
uint8_t video_mode;
status = GetVideoMode(dsi_config->video_mode_type, video_mode);
if (status != ZX_OK) {
DSI_ERROR("Invalid/Unsupported video mode\n");
return status;
}
// TODO(payamm): We only support sync-pulse mode.
if (dsi_config->video_mode_type != VIDEO_MODE_NON_BURST_PULSE) {
DSI_ERROR("Video Mode: Non-Burst pulse supported only\n");
// TODO(payamm): Add burst mode support
return status;
}
// enable highspeed mode in command mode
DsiPhyLcconReg::Get().ReadFrom(&(*dsi_mmio_))
.set_lc_hstx_en(1)
.WriteTo(&(*dsi_mmio_));
// Setup TXRX Control as follows:
// Set Virtual Channel to 0, disable end of transmission packet, disable null packet in bllp,
// set max_return_size to zero, disable hs clock lane non-continuous mode and configures the
// correct number of lanes.
DsiTxRxCtrlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_vc_num(0)
.set_hstx_dis_eot(0)
.set_hstx_bllp_en(0)
.set_hstx_cklp_en(0)
.set_lane_num((1 << disp_setting.lane_num) - 1)
.WriteTo(&(*dsi_mmio_));
// Set Read/Write memory continue command. This is used for Type-1 FrameBuffer Write
DsiMemContReg::Get().ReadFrom(&(*dsi_mmio_))
.set_rwmem_cont(kWMemCommand)
.WriteTo(&(*dsi_mmio_));
// Set pixel stream type
// TODO(payamm): Confirm width_ == h_active
uint8_t bpp = (dsi_config->color_coding == COLOR_CODE_PACKED_16BIT_565)? 2 : 3;
DsiPsCtrlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_ps_wc(disp_setting.h_active * bpp)
.set_ps_sel(code)
.WriteTo(&(*dsi_mmio_));
// Setup vertical parameters
DsiVsaNlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_vsa(disp_setting.vsync_width)
.WriteTo(&(*dsi_mmio_));
DsiVbpNlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_vbp(disp_setting.vsync_bp)
.WriteTo(&(*dsi_mmio_));
DsiVfpNlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_vfp(disp_setting.v_period - disp_setting.v_active -
disp_setting.vsync_bp -disp_setting.vsync_width)
.WriteTo(&(*dsi_mmio_));
DsiVactNlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_vact(disp_setting.v_active)
.WriteTo(&(*dsi_mmio_));
// The subtractions at the end of the calculations below are slight adjustments
// needed to leave some space for HS prep time due to non-continuous data lane transmission
// The numbers come from MT8167s spec
uint32_t h_fp = disp_setting.h_period -
disp_setting.h_active -
disp_setting.hsync_bp -
disp_setting.hsync_width;
uint32_t hsync_width_byte = ALIGN(disp_setting.hsync_width * bpp - 10, 4);
uint32_t h_bp_byte;
if (dsi_config->video_mode_type == VIDEO_MODE_BURST) {
h_bp_byte = ALIGN((disp_setting.hsync_bp + disp_setting.hsync_width) * bpp - 10, 4);
hsync_width_byte = ALIGN(disp_setting.hsync_width * bpp - 4, 4);
} else {
h_bp_byte = ALIGN(disp_setting.hsync_bp * bpp - 10, 4);
}
uint32_t h_fp_byte = ALIGN(h_fp * bpp - 12, 4);
DsiHsaWcReg::Get().ReadFrom(&(*dsi_mmio_))
.set_hsa(hsync_width_byte)
.WriteTo(&(*dsi_mmio_));
DsiHbpWcReg::Get().ReadFrom(&(*dsi_mmio_))
.set_hbp(h_bp_byte)
.WriteTo(&(*dsi_mmio_));
DsiHfpWcReg::Get().ReadFrom(&(*dsi_mmio_))
.set_hfp(h_fp_byte)
.WriteTo(&(*dsi_mmio_));
// Set horizontal blanking to 0 since we do not operate in burst mode
// TODO(payamm): Revisit if Burst mode is added
DsiBllpWcReg::Get().ReadFrom(&(*dsi_mmio_))
.set_bllp(0)
.WriteTo(&(*dsi_mmio_));
// Enable sending commands in video mode. We set this register up to only send commands
// (i.e. short) during VFP period. (TODO: try to really understand this feature)
DsiVmCmdConReg::Get().ReadFrom(&(*dsi_mmio_))
.set_ts_vfp_en(1)
.set_vm_cmd_en(1)
.WriteTo(&(*dsi_mmio_));
return ZX_OK;
}
void DsiMt::DsiImplPhyPowerUp() {
// Configure TimeCon0 Register which includes hs_trail, hs_zero, hs_prep and lpx
// hs_trail: Time that the transmitter drives the flipped differential state after
// last payload data bit of a HS transmission burst
// hs_prep: Time that hte transmisster drives the Data Lane LP-00 line state
// immediately before the HS-0 line state starting the HS transmission
// hs_zero: Time that the transmitter drives the hs-0 state prior to transmitting the
// sync sequence
// lpx: Transmitted length of any low-power state period
uint32_t hs_trail = MAX(NsToCycle(kHsTrailParam), 1) + kTrailOffset;
uint32_t hs_prep = MAX(NsToCycle(kHsPrepParam + kHsPrepUiMultiplier * ui_), 1);
uint32_t hs_zero = NsToCycle(kHsZeroParam + kHsZeroUiMultiplier * ui_);
// make sure hs_zero does not exceed hs_prep
if (hs_zero > hs_prep) {
hs_zero -= hs_prep;
}
uint32_t lpx = MAX(NsToCycle(kLpxParam), 1);
DsiPhyTimeCon0Reg::Get().ReadFrom(&(*dsi_mmio_))
.set_hs_trail(hs_trail)
.set_hs_zero(hs_zero)
.set_hs_prep(hs_prep)
.set_lpx(lpx)
.WriteTo(&(*dsi_mmio_));
// Configure TimeCon1 Register which includes hs_exit, ta_get, ta_sure and ta_go
// hs_exit: time that the transmitter drives LP-11 following a HS burst
// ta_get: Time that the new transmitter drives the bridge state (lp-00) after accepting
// control during a link turnaround
// ta_sure: Time that the new transmitter waits after the lp-10 state before transmitting
// the bridge state (lp-00) during a link turnaround
// ta_go: Time that the transmitter drives the bridge state (lp-00) before releasing control
// during a link turnaround
uint32_t ta_get = kTaGetLpxMultiplier * lpx;
uint32_t ta_sure = kTaSureLpxMultiplier * lpx / kTaSureLpxDivider;
uint32_t ta_go = kTaGoMultiplier * lpx;
uint32_t hs_exit = NsToCycle(kHsExitParam + kHsExitUiMultiplier * ui_);
DsiPhyTimeCon1Reg::Get().ReadFrom(&(*dsi_mmio_))
.set_hs_exit(hs_exit)
.set_ta_get(ta_get)
.set_ta_sure(ta_sure)
.set_ta_go(ta_go)
.WriteTo(&(*dsi_mmio_));
// Configure TimeCon2 Register which includes clk_trail, clk_zero and cont_det
// clk_trail: Time that the transmitter drives the hs-0 state after the last payload clock bit
// of a hs transmission burst
// clk_zero: Time that the transmitter drives the hs-0 state prior to starting the clock
// cont_det: Not sure. Set to 0
uint32_t clk_trail = NsToCycle(kClkTrailParam) + kTrailOffset;
uint32_t clk_zero = NsToCycle(kClkZeroParam);
DsiPhyTimeCon2Reg::Get().ReadFrom(&(*dsi_mmio_))
.set_clk_trail(clk_trail)
.set_clk_zero(clk_zero)
.set_cont_det(kContDet)
.WriteTo(&(*dsi_mmio_));
// Configure TimeCon3 Register which includes clk_exit, clk_post and clk_prep
// clk_post: Time that the transmitter continues to send HS clock after the last associated
// Data Lane has transitioned to LP mode
// clk_prep: Time that the transmitter drives the clock lane lp-00 line state immidiately
// before the hs-0 line state starting the hs transmission
uint32_t clk_prep = MAX(NsToCycle(kClkPrepParam), 1);
uint32_t clk_exit = kClkExitLpxMultiplier * lpx;
uint32_t clk_post = NsToCycle(kClkPostParam + kClkPostUiMultiplier * ui_);
DsiPhyTimeCon3Reg::Get().ReadFrom(&(*dsi_mmio_))
.set_clk_exit(clk_exit)
.set_clk_post(clk_post)
.set_clk_prep(clk_prep)
.WriteTo(&(*dsi_mmio_));
}
// MT Command Queue looks something like this: <Data1><Data0><Data ID><Config>
// where Config is: [7:6rsv][5TE][4 CL][3 HS][2 BTA][1:0 Type]
// Where Type is 00: Short read/write, 10: Generic Long and 01/03: Framebuffer R/W which
// are not supported in this driver
//
zx_status_t DsiMt::DsiImplSendCmd(const mipi_dsi_cmd_t* cmd_list, size_t cmd_count) {
zx_status_t status = ZX_OK;
for (size_t i = 0; i < cmd_count && status == ZX_OK; i++) {
mipi_dsi_cmd_t cmd = cmd_list[i];
switch (cmd.dsi_data_type) {
case MIPI_DSI_DT_GEN_SHORT_WRITE_0:
case MIPI_DSI_DT_GEN_SHORT_WRITE_1:
case MIPI_DSI_DT_GEN_SHORT_WRITE_2:
case MIPI_DSI_DT_GEN_LONG_WRITE:
case MIPI_DSI_DT_DCS_LONG_WRITE:
case MIPI_DSI_DT_DCS_SHORT_WRITE_0:
case MIPI_DSI_DT_DCS_SHORT_WRITE_1:
status = Write(cmd);
break;
case MIPI_DSI_DT_GEN_SHORT_READ_0:
case MIPI_DSI_DT_GEN_SHORT_READ_1:
case MIPI_DSI_DT_GEN_SHORT_READ_2:
case MIPI_DSI_DT_DCS_READ_0:
status = Read(cmd);
break;
default:
DSI_ERROR("Unsupported/Invalid DSI Command type %d\n", cmd.dsi_data_type);
status = ZX_ERR_INVALID_ARGS;
}
if (status != ZX_OK) {
DSI_ERROR("Something went wrong in sending command\n");
DsiImplPrintDsiRegisters();
break;
}
}
return status;
}
void DsiMt::DsiImplSetMode(dsi_mode_t mode) {
uint8_t dsi_mode = (mode == DSI_MODE_COMMAND) ? 0 : 1;
auto current_mode = DsiModeCtrlReg::Get().ReadFrom(&(*dsi_mmio_)).mode_con();
if (dsi_mode == current_mode) {
DSI_INFO("No need to change mode\n");
// return;
}
DsiModeCtrlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_mode_con(dsi_mode)
.WriteTo(&(*dsi_mmio_));
}
void DsiMt::DsiImplPowerUp() {
//TODO(payamm): Should we toggle reset here before powering up?
DsiComCtrlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_dsi_en(1)
.WriteTo(&(*dsi_mmio_));
}
void DsiMt::DsiImplPowerDown() {
DsiImplReset();
DsiComCtrlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_dsi_en(0)
.WriteTo(&(*dsi_mmio_));
}
bool DsiMt::DsiImplIsPoweredUp() {
return (DsiComCtrlReg::Get().ReadFrom(&(*dsi_mmio_)).dsi_en() == 1);
}
void DsiMt::DsiImplReset() {
DsiComCtrlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_dsi_reset(1)
.WriteTo(&(*dsi_mmio_));
zx_nanosleep(zx_deadline_after(ZX_USEC(50)));
DsiComCtrlReg::Get().ReadFrom(&(*dsi_mmio_))
.set_dsi_reset(0)
.WriteTo(&(*dsi_mmio_));
}
void DsiMt::DsiImplPrintDsiRegisters() {
zxlogf(INFO, "Dumping DSI MT Registers:\n");
zxlogf(INFO, "######################\n\n");
zxlogf(INFO, "DsiStartReg = 0x%x\n",
DsiStartReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStaReg = 0x%x\n",
DsiStaReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiIntEnReg = 0x%x\n",
DsiIntEnReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiIntStaReg = 0x%x\n",
DsiIntStaReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiComCtrlReg = 0x%x\n",
DsiComCtrlReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiModeCtrlReg = 0x%x\n",
DsiModeCtrlReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiTxRxCtrlReg = 0x%x\n",
DsiTxRxCtrlReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiPsCtrlReg = 0x%x\n",
DsiPsCtrlReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiVsaNlReg = 0x%x\n",
DsiVsaNlReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiVbpNlReg = 0x%x\n",
DsiVbpNlReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiVfpNlReg = 0x%x\n",
DsiVfpNlReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiVactNlReg = 0x%x\n",
DsiVactNlReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiHsaWcReg = 0x%x\n",
DsiHsaWcReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiHbpWcReg = 0x%x\n",
DsiHbpWcReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiHfpWcReg = 0x%x\n",
DsiHfpWcReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiBllpWcReg = 0x%x\n",
DsiBllpWcReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiCmdqSizeReg = 0x%x\n",
DsiCmdqSizeReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiHstxCklWcReg = 0x%x\n",
DsiHstxCklWcReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiRxData03Reg = 0x%x\n",
DsiRxData03Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiRxData47Reg = 0x%x\n",
DsiRxData47Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiRxData8bReg = 0x%x\n",
DsiRxData8bReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiRxDataCReg = 0x%x\n",
DsiRxDataCReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiRackReg = 0x%x\n",
DsiRackReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiTrigStaReg = 0x%x\n",
DsiTrigStaReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiMemContReg = 0x%x\n",
DsiMemContReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiFrmBcReg = 0x%x\n",
DsiFrmBcReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiPhyLcpatReg = 0x%x\n",
DsiPhyLcpatReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiPhyLcconReg = 0x%x\n",
DsiPhyLcconReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiPhyLd0ConReg = 0x%x\n",
DsiPhyLd0ConReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiPhyTimeCon0Reg = 0x%x\n",
DsiPhyTimeCon0Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiPhyTimeCon1Reg = 0x%x\n",
DsiPhyTimeCon1Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiPhyTimeCon2Reg = 0x%x\n",
DsiPhyTimeCon2Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiPhyTimeCon3Reg = 0x%x\n",
DsiPhyTimeCon3Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiPhyTimeCon4Reg = 0x%x\n",
DsiPhyTimeCon4Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiVmCmdConReg = 0x%x\n",
DsiVmCmdConReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiVmCmdData0Reg = 0x%x\n",
DsiVmCmdData0Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiVmCmdData4Reg = 0x%x\n",
DsiVmCmdData4Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiVmCmdData8Reg = 0x%x\n",
DsiVmCmdData8Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiVmCmdDataCReg = 0x%x\n",
DsiVmCmdDataCReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiCksmOutReg = 0x%x\n",
DsiCksmOutReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStateDbg0Reg = 0x%x\n",
DsiStateDbg0Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStateDbg1Reg = 0x%x\n",
DsiStateDbg1Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStateDbg2Reg = 0x%x\n",
DsiStateDbg2Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStateDbg3Reg = 0x%x\n",
DsiStateDbg3Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStateDbg4Reg = 0x%x\n",
DsiStateDbg4Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStateDbg5Reg = 0x%x\n",
DsiStateDbg5Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStateDbg6Reg = 0x%x\n",
DsiStateDbg6Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStateDbg7Reg = 0x%x\n",
DsiStateDbg7Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStateDbg8Reg = 0x%x\n",
DsiStateDbg8Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiStateDbg9Reg = 0x%x\n",
DsiStateDbg9Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiDebugSelReg = 0x%x\n",
DsiDebugSelReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiBistPatternReg = 0x%x\n",
DsiBistPatternReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "DsiBistConReg = 0x%x\n",
DsiBistConReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value());
zxlogf(INFO, "######################\n\n");
}
void DsiMt::StartDsi() {
// Toggle Start bit
DsiStartReg::Get().ReadFrom(&(*dsi_mmio_)).set_dsi_start(0).WriteTo(&(*dsi_mmio_));
DsiStartReg::Get().ReadFrom(&(*dsi_mmio_)).set_dsi_start(1).WriteTo(&(*dsi_mmio_));
}
zx_status_t DsiMt::WaitForIdle() {
int timeout = kBusyTimeout;
auto stat_reg = DsiIntStaReg::Get();
while (stat_reg.ReadFrom(&(*dsi_mmio_)).dsi_busy() && timeout--) {
zx_nanosleep(zx_deadline_after(ZX_USEC(1)));
}
if (timeout <= 0) {
DSI_ERROR("Timeout! DSI remains busy\n");
// TODO(payamm): perform reset and dump registers
return ZX_ERR_TIMED_OUT;
}
// clear register
stat_reg.FromValue(0).WriteTo(&(*dsi_mmio_));
return ZX_OK;
}
zx_status_t DsiMt::WaitForRxReady() {
int timeout = kReadTimeout;
auto stat_reg = DsiIntStaReg::Get();
while (!stat_reg.ReadFrom(&(*dsi_mmio_)).lprx_rd_rdy() && timeout--) {
zx_nanosleep(zx_deadline_after(ZX_MSEC(1)));
}
if (timeout <= 0) {
DSI_ERROR("Timeout! DSI remains busy\n");
// TODO(payamm): perform reset and dump registers
return ZX_ERR_TIMED_OUT;
}
// clear register
stat_reg.FromValue(0).WriteTo(&(*dsi_mmio_));
return ZX_OK;
}
zx_status_t DsiMt::Read(const mipi_dsi_cmd_t& cmd) {
if ((cmd.rsp_data_list == nullptr) || (cmd.pld_data_count > 2) ||
(cmd.pld_data_count > 0 && cmd.pld_data_list == nullptr)) {
DSI_ERROR("Invalid read command packet\n");
return ZX_ERR_INVALID_ARGS;
}
if (cmd.rsp_data_count > kMaxReadResponse) {
DSI_ERROR("Expected Read exceeds %d\n", kMaxReadResponse);
return ZX_ERR_OUT_OF_RANGE;
}
// Make sure DSI is not busy
zx_status_t status = WaitForIdle();
if (status != ZX_OK) {
DSI_ERROR("Could not send command (%d)\n", status);
return status;
}
auto cmdq_reg = CmdQReg::Get(0).FromValue(0);
// Check whether max return packet size should be set
if (cmd.flags & MIPI_DSI_CMD_FLAGS_SET_MAX) {
// We will set the max return size as rlen
cmdq_reg.set_data_id(MIPI_DSI_DT_SET_MAX_RET_PKT);
cmdq_reg.set_type(TYPE_SHORT);
cmdq_reg.set_data_0(static_cast<uint32_t>(cmd.rsp_data_count) & 0xFF);
cmdq_reg.set_data_1((static_cast<uint32_t>(cmd.rsp_data_count) >> 8) & 0xFF);
cmdq_reg.WriteTo(&(*dsi_mmio_));
DsiCmdqSizeReg::Get().FromValue(0).set_cmdq_reg_size(1).WriteTo(&(*dsi_mmio_));
StartDsi();
status = WaitForIdle();
if (status != ZX_OK) {
DSI_ERROR("Command did not complete (%d)\n", status);
return status;
}
}
// Make sure DSI is not busy
status = WaitForIdle();
if (status != ZX_OK) {
DSI_ERROR("Could not send command (%d)\n", status);
return status;
}
// Setup the read packet
cmdq_reg.set_reg_value(0);
cmdq_reg.set_type(TYPE_SHORT);
cmdq_reg.set_data_id(cmd.dsi_data_type);
cmdq_reg.set_bta(1);
if (cmd.pld_data_count >= 1) {
cmdq_reg.set_data_0(cmd.pld_data_list[0]);
}
if (cmd.pld_data_count == 2) {
cmdq_reg.set_data_1(cmd.pld_data_list[1]);
}
cmdq_reg.WriteTo(&(*dsi_mmio_));
DsiCmdqSizeReg::Get().FromValue(0).set_cmdq_reg_size(1).WriteTo(&(*dsi_mmio_));
DsiRackReg::Get().ReadFrom(&(*dsi_mmio_)).set_rack(1).WriteTo(&(*dsi_mmio_));
DsiIntStaReg::Get()
.ReadFrom(&(*dsi_mmio_))
.set_lprx_rd_rdy(1)
.set_cmd_done(1)
.WriteTo(&(*dsi_mmio_));
StartDsi();
// Wait for read to finish
status = WaitForRxReady();
if (status != ZX_OK) {
DSI_ERROR("Read not completed\n");
return status;
}
DsiRackReg::Get().ReadFrom(&(*dsi_mmio_)).set_rack(1).WriteTo(&(*dsi_mmio_));
// store a local copy of the response registers.
uint32_t read_buf[3];
read_buf[0] = DsiRxData47Reg::Get().ReadFrom(&(*dsi_mmio_)).reg_value();
read_buf[1] = DsiRxData8bReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value();
read_buf[2] = DsiRxDataCReg::Get().ReadFrom(&(*dsi_mmio_)).reg_value();
// Determine response type first
auto rx_data_reg03 = DsiRxData03Reg::Get().ReadFrom(&(*dsi_mmio_));
switch(rx_data_reg03.byte0()) {
case MIPI_DSI_RSP_GEN_SHORT_1:
case MIPI_DSI_RSP_GEN_SHORT_2:
case MIPI_DSI_RSP_DCS_SHORT_1:
case MIPI_DSI_RSP_DCS_SHORT_2:
// For short response, byte1 and 2 contain the returned value
if (cmd.rsp_data_count >= 1) {
cmd.rsp_data_list[0] = static_cast<uint8_t>(rx_data_reg03.byte1());
}
if (cmd.rsp_data_count == 2) {
cmd.rsp_data_list[1] = static_cast<uint8_t>(rx_data_reg03.byte2());
}
break;
case MIPI_DSI_RSP_GEN_LONG:
case MIPI_DSI_RSP_DCS_LONG:
{
// For long responses, <byte2><byte1> contains the response bytes
size_t rsp_count = rx_data_reg03.byte2() << 8 | rx_data_reg03.byte1();
size_t actual_read = (rsp_count < cmd.rsp_data_count)? rsp_count : cmd.rsp_data_count;
memcpy(cmd.rsp_data_list, read_buf, actual_read);
}
break;
default:
DSI_ERROR("Invalid Response Type\n");
break;
}
return ZX_OK;
}
zx_status_t DsiMt::Write(const mipi_dsi_cmd_t& cmd) {
if (cmd.pld_data_count > 0 && cmd.pld_data_list == nullptr) {
DSI_ERROR("Invalid write command packet\n");
return ZX_ERR_INVALID_ARGS;
}
if (cmd.pld_data_count > kMaxPayloadLength) {
DSI_ERROR("Payload length exceeds %d\n", kMaxPayloadLength);
return ZX_ERR_OUT_OF_RANGE;
}
// Make sure DSI is not busy
zx_status_t status = WaitForIdle();
if (status != ZX_OK) {
DSI_ERROR("Could not send command (%d)\n", status);
return status;
}
// Both short and long writes need the first command queue register to setup the
// outgoing packet. In case of short write, DATA0 and DATA1 contain actual data. In
// case of long write, DATA0 and DATA1 contain the word count
if (cmd.pld_data_count > 2) {
// Long write
auto cmdq_reg = CmdQReg::Get(0).FromValue(0);
cmdq_reg.set_type(TYPE_LONG);
cmdq_reg.set_data_0(static_cast<uint8_t>(cmd.pld_data_count));
cmdq_reg.set_data_1(0); // we only support 64Bytes, so WC1 will be zero
// Setup DataID
cmdq_reg.set_data_id(cmd.dsi_data_type);
// At this point, cmd packet is ready. Write it
cmdq_reg.WriteTo(&(*dsi_mmio_));
// Write remaining parameters if long write
uint32_t ts = static_cast<uint8_t>(cmd.pld_data_count);
uint32_t pld_data_idx = 0; // payload data index
uint32_t cmdq_index = 1; // start from the second queue (first one contains the command)
// write complete words first
while (ts >= 4) {
uint32_t qval = cmd.pld_data_list[pld_data_idx + 0] << 0 |
cmd.pld_data_list[pld_data_idx + 1] << 8 |
cmd.pld_data_list[pld_data_idx + 2] << 16 |
cmd.pld_data_list[pld_data_idx + 3] << 24;
auto cmdq_reg2 = CmdQReg::Get(cmdq_index).FromValue(0);
cmdq_reg2.set_reg_value(qval).WriteTo(&(*dsi_mmio_));
pld_data_idx += 4;
cmdq_index++;
ts -= 4;
}
// Write remaining bytes
if (ts > 0) {
uint32_t qval = cmd.pld_data_list[pld_data_idx++] << 0;
if (ts > 1) {
qval |= cmd.pld_data_list[pld_data_idx++] << 8;
}
if (ts > 2) {
qval |= cmd.pld_data_list[pld_data_idx++] << 16;
}
auto cmdq_reg2 = CmdQReg::Get(cmdq_index).FromValue(0);
cmdq_reg2.set_reg_value(qval).WriteTo(&(*dsi_mmio_));
cmdq_index++;
}
// set command queue size (only 1 entry)
DsiCmdqSizeReg::Get().FromValue(0).set_cmdq_reg_size(cmdq_index).WriteTo(&(*dsi_mmio_));
} else {
// Short write
auto cmdq_reg = CmdQReg::Get(0).FromValue(0);
cmdq_reg.set_data_id(cmd.dsi_data_type);
cmdq_reg.set_type(TYPE_SHORT);
if (cmd.pld_data_count >= 1) {
cmdq_reg.set_data_0(cmd.pld_data_list[0]);
}
if (cmd.pld_data_count == 2) {
cmdq_reg.set_data_1(cmd.pld_data_list[1]);
}
// At this point, cmd packet is ready. Write it
cmdq_reg.WriteTo(&(*dsi_mmio_));
// set command queue size (only 1 entry)
DsiCmdqSizeReg::Get().FromValue(0).set_cmdq_reg_size(1).WriteTo(&(*dsi_mmio_));
}
// At this point, we have written all data into the queue. Let's transmit now
// Start DSI Engine
StartDsi();
// Wait for command to complete
status = WaitForIdle();
if (status != ZX_OK) {
DSI_ERROR("Command did not complete (%d)\n", status);
return status;
}
return status;
}
zx_status_t DsiMt::Bind() {
zx_status_t status = device_get_protocol(parent_, ZX_PROTOCOL_PDEV, &pdev_proto_);
if (status != ZX_OK) {
DSI_ERROR("Could not get parent protocol (%d)\n", status);
return status;
}
// Map DSI registers
mmio_buffer_t mmio;
status = pdev_map_mmio_buffer(&pdev_proto_, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE, &mmio);
if (status != ZX_OK) {
DSI_ERROR("Could not map DSI mmio (%d)\n", status);
return status;
}
dsi_mmio_ = ddk::MmioBuffer(mmio);
// Obtain display metadata needed to load the proper display driver
display_driver_t display_info;
size_t actual;
status = device_get_metadata(parent_, DEVICE_METADATA_DISPLAY_DEVICE,
&display_info,
sizeof(display_driver_t),
&actual);
if (status != ZX_OK || actual != sizeof(display_driver_t)) {
DSI_ERROR("Could not get display driver metadata %d\n", status);
return status;
}
zx_device_prop_t props[] = {
{BIND_PLATFORM_DEV_VID, 0, display_info.vid},
{BIND_PLATFORM_DEV_PID, 0, display_info.pid},
{BIND_PLATFORM_DEV_DID, 0, display_info.did},
};
status = DdkAdd("mt-dsi", 0, props, countof(props));
if (status != ZX_OK) {
DSI_ERROR("could not add device %d\n", status);
}
return status;
}
// main bind function called from dev manager
zx_status_t dsi_mt_bind(void* ctx, zx_device_t* parent) {
fbl::AllocChecker ac;
auto dev = fbl::make_unique_checked<dsi_mt::DsiMt>(&ac, parent);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto status = dev->Bind();
if (status == ZX_OK) {
// devmgr is now in charge of the memory for dev
__UNUSED auto ptr = dev.release();
}
return status;
}
static zx_driver_ops_t dsi_mt_ops = []{
zx_driver_ops_t ops;
ops.version = DRIVER_OPS_VERSION;
ops.bind = dsi_mt_bind;
return ops;
}();
} // namespace dsi_mt
// clang-format off
ZIRCON_DRIVER_BEGIN(dsi_mt, dsi_mt::dsi_mt_ops, "zircon", "0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_MEDIATEK),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_MEDIATEK_DSI),
ZIRCON_DRIVER_END(dsi_mt)