| // Copyright 2024 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/lib/designware-dsi/dsi-host-controller.h" |
| |
| #include <fuchsia/hardware/dsiimpl/c/banjo.h> |
| #include <lib/mipi-dsi/mipi-dsi.h> |
| #include <lib/mmio/mmio-buffer.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <fbl/auto_lock.h> |
| #include <fbl/string_buffer.h> |
| |
| #include "src/graphics/display/lib/designware-dsi/dw-mipi-dsi-reg.h" |
| #include "src/graphics/display/lib/driver-framework-migration-utils/logging/zxlogf.h" |
| |
| // Header Creation Macros |
| #define GEN_HDR_WC_MSB(x) ((x & 0xFF) << 16) |
| #define GEN_HDR_WC_LSB(x) ((x & 0xFF) << 8) |
| #define GEN_HDR_VC(x) ((x & 0x03) << 6) |
| #define GEN_HDR_DT(x) ((x & 0x3F) << 0) |
| |
| namespace designware_dsi { |
| |
| namespace { |
| constexpr uint32_t kPowerReset = 0; |
| constexpr uint32_t kPowerOn = 1; |
| constexpr uint32_t kPhyTestCtrlSet = 0x2; |
| constexpr uint32_t kPhyTestCtrlClr = 0x0; |
| |
| constexpr uint32_t kDPhyTimeout = 200000; |
| constexpr uint32_t kPhyDelay = 6; |
| constexpr uint32_t kPhyStopWaitTime = 0x28; // value from vendor |
| |
| // Generic retry value used for BTA and FIFO related events |
| constexpr uint32_t kRetryMax = 20000; |
| |
| constexpr uint32_t kMaxPldFifoDepth = 200; |
| |
| constexpr uint32_t kBitPldRFull = 5; |
| constexpr uint32_t kBitPldREmpty = 4; |
| constexpr uint32_t kBitPldWFull = 3; |
| constexpr uint32_t kBitPldWEmpty = 2; |
| constexpr uint32_t kBitCmdFull = 1; |
| constexpr uint32_t kBitCmdEmpty = 0; |
| |
| // Computes the amount of time it takes to transmit a number of pixels across |
| // the DPI interface between the display engine frontend and the DesignWare DSI |
| // host controller. |
| // |
| // The result is expressed in D-PHY lane byte clock cycles, which is the |
| // number of bytes that can be transmitted on a D-PHY data lane in High Speed |
| // mode. |
| // |
| // `pixels` must be >= 0 and <= 2^23 - 1. |
| // |
| // The DPI pixel rate (Hz) must be divisble by the D-PHY data lane byte |
| // transmission rate (bytes per second), and the quotient must be >= 1 and |
| // <= 256. |
| constexpr int32_t DpiPixelToDphyLaneByteClockCycle(int32_t pixels, int64_t dpi_pixel_rate_hz, |
| int64_t dphy_data_lane_bytes_per_second) { |
| ZX_DEBUG_ASSERT(pixels >= 0); |
| ZX_DEBUG_ASSERT(pixels <= (1 << 23) - 1); |
| |
| ZX_DEBUG_ASSERT(dphy_data_lane_bytes_per_second % dpi_pixel_rate_hz == 0); |
| int64_t dphy_data_lane_byte_rate_to_dpi_pixel_rate_ratio_i64 = |
| dphy_data_lane_bytes_per_second / dpi_pixel_rate_hz; |
| ZX_DEBUG_ASSERT(dphy_data_lane_byte_rate_to_dpi_pixel_rate_ratio_i64 >= 1); |
| ZX_DEBUG_ASSERT(dphy_data_lane_byte_rate_to_dpi_pixel_rate_ratio_i64 <= 256); |
| |
| int32_t dphy_data_lane_byte_rate_to_dpi_pixel_rate_ratio = |
| static_cast<int32_t>(dphy_data_lane_byte_rate_to_dpi_pixel_rate_ratio_i64); |
| return pixels * dphy_data_lane_byte_rate_to_dpi_pixel_rate_ratio; |
| } |
| |
| void LogBytes(cpp20::span<const uint8_t> bytes) { |
| if (bytes.empty()) { |
| return; |
| } |
| |
| static constexpr size_t kByteCountPerPrint = 16; |
| // Stores up to `kByteCountPerPrint` bytes in "0x01,0x02,..." format |
| static constexpr size_t kStringBufferSize = kByteCountPerPrint * 5 + 1; |
| fbl::StringBuffer<kStringBufferSize> string_buffer; |
| for (size_t i = 0; i < bytes.size(); i++) { |
| if (i % kByteCountPerPrint == 0 && i != 0) { |
| zxlogf(INFO, "%s", string_buffer.c_str()); |
| string_buffer.Clear(); |
| } |
| string_buffer.AppendPrintf("0x%02x,", bytes[i]); |
| } |
| if (!string_buffer.empty()) { |
| zxlogf(INFO, "%s", string_buffer.c_str()); |
| } |
| } |
| |
| } // namespace |
| |
| DsiHostController::DsiHostController(fdf::MmioBuffer dsi_mmio) : dsi_mmio_(std::move(dsi_mmio)) {} |
| |
| zx_status_t DsiHostController::GetColorCode(color_code_t c, bool& packed, uint8_t& code) { |
| zx_status_t status = ZX_OK; |
| switch (c) { |
| case COLOR_CODE_PACKED_16BIT_565: |
| packed = true; |
| code = 0; |
| break; |
| case COLOR_CODE_PACKED_18BIT_666: |
| packed = true; |
| code = 3; |
| // Converts the duration of transmitting `pixels` on the DPI interface to |
| // lane byte clock cycles (number of lane byte clock periods), |
| break; |
| case COLOR_CODE_LOOSE_24BIT_666: |
| packed = false; |
| code = 3; |
| break; |
| case COLOR_CODE_PACKED_24BIT_888: |
| packed = true; |
| code = 5; |
| break; |
| default: |
| status = ZX_ERR_INVALID_ARGS; |
| break; |
| } |
| return status; |
| } |
| |
| zx_status_t DsiHostController::GetVideoMode(video_mode_t v, uint8_t& mode) { |
| zx_status_t status = ZX_OK; |
| switch (v) { |
| case VIDEO_MODE_NON_BURST_PULSE: |
| mode = 0; |
| break; |
| case VIDEO_MODE_NON_BURST_EVENT: |
| mode = 1; |
| break; |
| case VIDEO_MODE_BURST: |
| mode = 2; |
| break; |
| default: |
| status = ZX_ERR_INVALID_ARGS; |
| } |
| return status; |
| } |
| |
| void DsiHostController::PowerUp() { |
| DsiDwPwrUpReg::Get().ReadFrom(&dsi_mmio_).set_shutdown(kPowerOn).WriteTo(&dsi_mmio_); |
| } |
| |
| void DsiHostController::PowerDown() { |
| DsiDwPwrUpReg::Get().ReadFrom(&dsi_mmio_).set_shutdown(kPowerReset).WriteTo(&dsi_mmio_); |
| } |
| |
| void DsiHostController::PhySendCode(uint32_t code, uint32_t parameter) { |
| // Write code |
| DsiDwPhyTstCtrl1Reg::Get().FromValue(0).set_reg_value(code).WriteTo(&dsi_mmio_); |
| |
| // Toggle PhyTestClk |
| DsiDwPhyTstCtrl0Reg::Get().FromValue(0).set_reg_value(kPhyTestCtrlSet).WriteTo(&dsi_mmio_); |
| DsiDwPhyTstCtrl0Reg::Get().FromValue(0).set_reg_value(kPhyTestCtrlClr).WriteTo(&dsi_mmio_); |
| |
| // Write parameter |
| DsiDwPhyTstCtrl1Reg::Get().FromValue(0).set_reg_value(parameter).WriteTo(&dsi_mmio_); |
| |
| // Toggle PhyTestClk |
| DsiDwPhyTstCtrl0Reg::Get().FromValue(0).set_reg_value(kPhyTestCtrlSet).WriteTo(&dsi_mmio_); |
| DsiDwPhyTstCtrl0Reg::Get().FromValue(0).set_reg_value(kPhyTestCtrlClr).WriteTo(&dsi_mmio_); |
| } |
| |
| void DsiHostController::PhyPowerUp() { |
| DsiDwPhyRstzReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_phy_forcepll(1) |
| .set_phy_enableclk(1) |
| .set_phy_rstz(1) |
| .set_phy_shutdownz(1) |
| .WriteTo(&dsi_mmio_); |
| } |
| |
| void DsiHostController::PhyPowerDown() { |
| DsiDwPhyRstzReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_phy_rstz(0) |
| .set_phy_shutdownz(0) |
| .WriteTo(&dsi_mmio_); |
| } |
| |
| zx_status_t DsiHostController::PhyWaitForReady() { |
| int timeout = kDPhyTimeout; |
| while ((DsiDwPhyStatusReg::Get().ReadFrom(&dsi_mmio_).phy_lock() == 0) && timeout--) { |
| zx_nanosleep(zx_deadline_after(ZX_USEC(kPhyDelay))); |
| } |
| if (timeout <= 0) { |
| zxlogf(ERROR, "Timed out waiting for D-PHY lock"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| timeout = kDPhyTimeout; |
| while ((DsiDwPhyStatusReg::Get().ReadFrom(&dsi_mmio_).phy_stopstateclklane() == 0) && timeout--) { |
| zx_nanosleep(zx_deadline_after(ZX_USEC(kPhyDelay))); |
| } |
| if (timeout <= 0) { |
| zxlogf(ERROR, "Timed out waiting for D-PHY StopStateClk to be set"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| return ZX_OK; |
| } |
| |
| zx::result<> DsiHostController::IssueCommands( |
| cpp20::span<const mipi_dsi::DsiCommandAndResponse> commands) { |
| for (const mipi_dsi::DsiCommandAndResponse& command : commands) { |
| zx_status_t status = IssueCommand(command); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to issue a command: %s", zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| } |
| return zx::ok(); |
| } |
| |
| void DsiHostController::SetMode(dsi_mode_t mode) { |
| // Configure the operation mode (cmd or vid) |
| DsiDwModeCfgReg::Get().ReadFrom(&dsi_mmio_).set_cmd_video_mode(mode).WriteTo(&dsi_mmio_); |
| } |
| |
| zx_status_t DsiHostController::Config(const dsi_config_t* dsi_config, |
| int64_t dphy_data_lane_bits_per_second) { |
| const display_setting_t disp_setting = dsi_config->display_setting; |
| const designware_config_t dw_cfg = |
| *(reinterpret_cast<designware_config_t*>(dsi_config->vendor_config_buffer)); |
| |
| static constexpr int kBitsPerByte = 8; |
| const int64_t dphy_data_lane_bytes_per_second = dphy_data_lane_bits_per_second / kBitsPerByte; |
| |
| bool packed; |
| uint8_t code; |
| uint8_t video_mode; |
| zx_status_t status = GetColorCode(dsi_config->color_coding, packed, code); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Refusing config with invalid or unsupported color coding"); |
| return status; |
| } |
| |
| status = GetVideoMode(dsi_config->video_mode_type, video_mode); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Refusing config with invalid or unsupported video mode"); |
| return status; |
| } |
| |
| // Enable LP transmission in CMD Mode |
| DsiDwCmdModeCfgReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_max_rd_pkt_size(1) |
| .set_dcs_lw_tx(1) |
| .set_dcs_sr_0p_tx(1) |
| .set_dcs_sw_1p_tx(1) |
| .set_dcs_sw_0p_tx(1) |
| .set_gen_lw_tx(1) |
| .set_gen_sr_2p_tx(1) |
| .set_gen_sr_1p_tx(1) |
| .set_gen_sr_0p_tx(1) |
| .set_gen_sw_2p_tx(1) |
| .set_gen_sw_1p_tx(1) |
| .set_gen_sw_0p_tx(1) |
| .WriteTo(&dsi_mmio_); |
| |
| // Packet header settings - Enable CRC and ECC. BTA will be enabled based on CMD |
| DsiDwPckhdlCfgReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_crc_rx_en(1) |
| .set_ecc_rx_en(1) |
| .WriteTo(&dsi_mmio_); |
| |
| // DesignWare DSI Host Setup based on MIPI DSI Host Controller User Guide (Sec 3.1.1) |
| |
| // 1. Global configuration: Lane number and PHY stop wait time |
| DsiDwPhyIfCfgReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_phy_stop_wait_time(kPhyStopWaitTime) |
| .set_n_lanes(disp_setting.lane_num - 1) |
| .WriteTo(&dsi_mmio_); |
| |
| // 2.1 Configure virtual channel |
| DsiDwDpiVcidReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_dpi_vcid(kMipiDsiVirtualChanId) |
| .WriteTo(&dsi_mmio_); |
| |
| // 2.2, Configure Color format |
| DsiDwDpiColorCodingReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_loosely18_en(!packed) |
| .set_dpi_color_coding(code) |
| .WriteTo(&dsi_mmio_); |
| // 2.3 Configure Signal polarity - Keep as default |
| DsiDwDpiCfgPolReg::Get().FromValue(0).set_reg_value(0).WriteTo(&dsi_mmio_); |
| |
| // The following values are relevent for video mode |
| // 3.1 Configure low power transitions and video mode type |
| |
| // Note: If the lp_cmd_en bit of the VID_MODE_CFG register is 0, the commands are sent |
| // in high-speed in Video Mode. In this case, the DWC_mipi_dsi_host automatically determines |
| // the area where each command can be sent and no programming or calculation is required. |
| |
| DsiDwVidModeCfgReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_vpg_en(0) |
| .set_lp_cmd_en(0) |
| .set_frame_bta_ack_en(1) |
| .set_lp_hfp_en(1) |
| .set_lp_hbp_en(1) |
| .set_lp_vact_en(1) |
| .set_lp_vfp_en(1) |
| .set_lp_vbp_en(1) |
| .set_lp_vsa_en(1) |
| .set_vid_mode_type(video_mode) |
| .WriteTo(&dsi_mmio_); |
| |
| // Define the max pkt size during Low Power mode |
| DsiDwDpiLpCmdTimReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_outvact_lpcmd_time(dw_cfg.lp_cmd_pkt_size) |
| .set_invact_lpcmd_time(dw_cfg.lp_cmd_pkt_size) |
| .WriteTo(&dsi_mmio_); |
| |
| // 3.2 Configure video packet size settings |
| DsiDwVidPktSizeReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_vid_pkt_size(disp_setting.h_active) |
| .WriteTo(&dsi_mmio_); |
| |
| // Disable sending vid in chunk since they are ignored by DW host IP in burst mode |
| DsiDwVidNumChunksReg::Get().FromValue(0).set_reg_value(0).WriteTo(&dsi_mmio_); |
| DsiDwVidNullSizeReg::Get().FromValue(0).set_reg_value(0).WriteTo(&dsi_mmio_); |
| |
| // 4 Configure the video relative parameters according to the output type |
| const int64_t pixel_clock_frequency_hz = disp_setting.lcd_clock; |
| |
| const int32_t horizontal_sync_width_pixels = static_cast<int32_t>(disp_setting.hsync_width); |
| const int32_t horizontal_sync_width_duration_lane_byte_clock_cycles = |
| DpiPixelToDphyLaneByteClockCycle(horizontal_sync_width_pixels, pixel_clock_frequency_hz, |
| dphy_data_lane_bytes_per_second); |
| DsiDwVidHsaTimeReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_vid_hsa_time(horizontal_sync_width_duration_lane_byte_clock_cycles) |
| .WriteTo(&dsi_mmio_); |
| |
| const int32_t horizontal_back_porch_pixels = static_cast<int32_t>(disp_setting.hsync_bp); |
| const int32_t horizontal_back_porch_duration_lane_byte_clock_cycles = |
| DpiPixelToDphyLaneByteClockCycle(horizontal_back_porch_pixels, pixel_clock_frequency_hz, |
| dphy_data_lane_bytes_per_second); |
| DsiDwVidHbpTimeReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_vid_hbp_time(horizontal_back_porch_duration_lane_byte_clock_cycles) |
| .WriteTo(&dsi_mmio_); |
| |
| const int32_t horizontal_total_pixels = static_cast<int32_t>(disp_setting.h_period); |
| const int32_t horizontal_total_duration_lane_byte_clock_cycles = DpiPixelToDphyLaneByteClockCycle( |
| horizontal_total_pixels, pixel_clock_frequency_hz, dphy_data_lane_bytes_per_second); |
| DsiDwVidHlineTimeReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_vid_hline_time(horizontal_total_duration_lane_byte_clock_cycles) |
| .WriteTo(&dsi_mmio_); |
| |
| DsiDwVidVsaLinesReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_vsa_lines(disp_setting.vsync_width) |
| .WriteTo(&dsi_mmio_); |
| |
| DsiDwVidVbpLinesReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_vbp_lines(disp_setting.vsync_bp) |
| .WriteTo(&dsi_mmio_); |
| |
| DsiDwVidVactiveLinesReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_vactive_lines(disp_setting.v_active) |
| .WriteTo(&dsi_mmio_); |
| |
| DsiDwVidVfpLinesReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_vfp_lines(disp_setting.v_period - disp_setting.v_active - disp_setting.vsync_bp - |
| disp_setting.vsync_width) |
| .WriteTo(&dsi_mmio_); |
| |
| // Internal dividers to divide lanebyteclk for timeout purposes |
| DsiDwClkmgrCfgReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_to_clk_div(1) |
| .set_tx_esc_clk_div(dw_cfg.lp_escape_time) |
| .WriteTo(&dsi_mmio_); |
| |
| // Setup Phy Timers as provided by vendor |
| DsiDwPhyTmrLpclkCfgReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_phy_clkhs2lp_time(dw_cfg.phy_timer_clkhs_to_lp) |
| .set_phy_clklp2hs_time(dw_cfg.phy_timer_clklp_to_hs) |
| .WriteTo(&dsi_mmio_); |
| DsiDwPhyTmrCfgReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_phy_hs2lp_time(dw_cfg.phy_timer_hs_to_lp) |
| .set_phy_lp2hs_time(dw_cfg.phy_timer_lp_to_hs) |
| .WriteTo(&dsi_mmio_); |
| |
| DsiDwLpclkCtrlReg::Get() |
| .ReadFrom(&dsi_mmio_) |
| .set_auto_clklane_ctrl(dw_cfg.auto_clklane) |
| .set_phy_txrequestclkhs(1) |
| .WriteTo(&dsi_mmio_); |
| |
| return ZX_OK; |
| } |
| |
| inline bool DsiHostController::IsPldREmpty() { |
| return (DsiDwCmdPktStatusReg::Get().ReadFrom(&dsi_mmio_).gen_pld_r_empty() == 1); |
| } |
| |
| inline bool DsiHostController::IsPldRFull() { |
| return (DsiDwCmdPktStatusReg::Get().ReadFrom(&dsi_mmio_).gen_pld_r_full() == 1); |
| } |
| |
| inline bool DsiHostController::IsPldWEmpty() { |
| return (DsiDwCmdPktStatusReg::Get().ReadFrom(&dsi_mmio_).gen_pld_w_empty() == 1); |
| } |
| |
| inline bool DsiHostController::IsPldWFull() { |
| return (DsiDwCmdPktStatusReg::Get().ReadFrom(&dsi_mmio_).gen_pld_w_full() == 1); |
| } |
| |
| inline bool DsiHostController::IsCmdEmpty() { |
| return (DsiDwCmdPktStatusReg::Get().ReadFrom(&dsi_mmio_).gen_cmd_empty() == 1); |
| } |
| |
| inline bool DsiHostController::IsCmdFull() { |
| return (DsiDwCmdPktStatusReg::Get().ReadFrom(&dsi_mmio_).gen_cmd_full() == 1); |
| } |
| |
| zx_status_t DsiHostController::WaitforFifo(uint32_t bit, bool val) { |
| int retry = kRetryMax; |
| while (((DsiDwCmdPktStatusReg::Get().ReadFrom(&dsi_mmio_).reg_value() >> bit) & 1) != val && |
| retry--) { |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| } |
| if (retry <= 0) { |
| return ZX_ERR_TIMED_OUT; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t DsiHostController::WaitforPldWNotFull() { return WaitforFifo(kBitPldWFull, 0); } |
| |
| zx_status_t DsiHostController::WaitforPldWEmpty() { return WaitforFifo(kBitPldWEmpty, 1); } |
| |
| zx_status_t DsiHostController::WaitforPldRFull() { return WaitforFifo(kBitPldRFull, 1); } |
| |
| zx_status_t DsiHostController::WaitforPldRNotEmpty() { return WaitforFifo(kBitPldREmpty, 0); } |
| |
| zx_status_t DsiHostController::WaitforCmdNotFull() { return WaitforFifo(kBitCmdFull, 0); } |
| |
| zx_status_t DsiHostController::WaitforCmdEmpty() { return WaitforFifo(kBitCmdEmpty, 1); } |
| |
| void DsiHostController::LogCommand(const mipi_dsi::DsiCommandAndResponse& command) { |
| zxlogf(INFO, "MIPI DSI Outgoing Packet:"); |
| zxlogf(INFO, "Virtual Channel ID = 0x%x (%d)", command.virtual_channel_id, |
| command.virtual_channel_id); |
| zxlogf(INFO, "Data Type = 0x%x (%d)", command.data_type, command.data_type); |
| zxlogf(INFO, "Payload size = %zu", command.payload.size()); |
| zxlogf(INFO, "Payload Data: ["); |
| LogBytes(command.payload); |
| zxlogf(INFO, "]"); |
| zxlogf(INFO, "Response payload size = %zu", command.response_payload.size()); |
| } |
| |
| zx_status_t DsiHostController::GenericPayloadRead(uint32_t* data) { |
| // make sure there is something valid to read from payload fifo |
| if (WaitforPldRNotEmpty() != ZX_OK) { |
| zxlogf(ERROR, "Timed out waiting for data in PLD R FIFO"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| *data = DsiDwGenPldDataReg::Get().ReadFrom(&dsi_mmio_).reg_value(); |
| return ZX_OK; |
| } |
| |
| zx_status_t DsiHostController::GenericHdrWrite(uint32_t data) { |
| // make sure cmd fifo is not full before writing into it |
| if (WaitforCmdNotFull() != ZX_OK) { |
| zxlogf(ERROR, "Timed out waiting for CMD FIFO to not be full"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| DsiDwGenHdrReg::Get().FromValue(0).set_reg_value(data).WriteTo(&dsi_mmio_); |
| return ZX_OK; |
| } |
| |
| zx_status_t DsiHostController::GenericPayloadWrite(uint32_t data) { |
| // Make sure PLD_W is not full before writing into it |
| if (WaitforPldWNotFull() != ZX_OK) { |
| zxlogf(ERROR, "Timed out waiting for PLD W FIFO to not be full"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| DsiDwGenPldDataReg::Get().FromValue(0).set_reg_value(data).WriteTo(&dsi_mmio_); |
| return ZX_OK; |
| } |
| |
| void DsiHostController::EnableBta() { |
| // enable ack req after each packet transmission |
| DsiDwCmdModeCfgReg::Get().ReadFrom(&dsi_mmio_).set_ack_rqst_en(1).WriteTo(&dsi_mmio_); |
| // enable But Turn-Around request |
| DsiDwPckhdlCfgReg::Get().ReadFrom(&dsi_mmio_).set_bta_en(1).WriteTo(&dsi_mmio_); |
| } |
| |
| void DsiHostController::DisableBta() { |
| // disable ack req after each packet transmission |
| DsiDwCmdModeCfgReg::Get().ReadFrom(&dsi_mmio_).set_ack_rqst_en(0).WriteTo(&dsi_mmio_); |
| |
| // disable But Turn-Around request |
| DsiDwPckhdlCfgReg::Get().ReadFrom(&dsi_mmio_).set_bta_en(0).WriteTo(&dsi_mmio_); |
| } |
| |
| zx_status_t DsiHostController::WaitforBtaAck() { |
| int retry = kRetryMax; |
| while (DsiDwCmdPktStatusReg::Get().ReadFrom(&dsi_mmio_).gen_rd_cmd_busy() && retry--) { |
| zx_nanosleep(zx_deadline_after(ZX_USEC(10))); |
| } |
| if (retry <= 0) { |
| zxlogf(ERROR, "Timed out waiting for read completion"); |
| return ZX_ERR_TIMED_OUT; |
| } |
| return ZX_OK; |
| } |
| |
| // MIPI DSI Functions as implemented by DWC IP |
| zx_status_t DsiHostController::GenWriteShort(const mipi_dsi::DsiCommandAndResponse& command) { |
| if (command.payload.size() > 2) { |
| zxlogf(ERROR, "Invalid payload size (%zu) for a Generic Short Write command", |
| command.payload.size()); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (command.data_type != kMipiDsiDtGenShortWrite0 && |
| command.data_type != kMipiDsiDtGenShortWrite1 && |
| command.data_type != kMipiDsiDtGenShortWrite2 && |
| command.data_type != kMipiDsiDtSetMaxRetPkt) { |
| zxlogf(ERROR, "Invalid data type (%d) for Generic Short Write", command.data_type); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| uint32_t regVal = 0; |
| regVal |= GEN_HDR_DT(command.data_type); |
| regVal |= GEN_HDR_VC(command.virtual_channel_id); |
| if (command.payload.size() >= 1) { |
| regVal |= GEN_HDR_WC_LSB(command.payload[0]); |
| } |
| if (command.payload.size() == 2) { |
| regVal |= GEN_HDR_WC_MSB(command.payload[1]); |
| } |
| |
| return GenericHdrWrite(regVal); |
| } |
| |
| zx_status_t DsiHostController::DcsWriteShort(const mipi_dsi::DsiCommandAndResponse& command) { |
| // Check that the payload size and command match |
| if (command.payload.size() != 1 && command.payload.size() != 2) { |
| zxlogf(ERROR, "Invalid payload size (%zu) for a DCS Short Write command", |
| command.payload.size()); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (command.data_type != kMipiDsiDtDcsShortWrite0 && |
| command.data_type != kMipiDsiDtDcsShortWrite1) { |
| zxlogf(ERROR, "Invalid data type (%d) for Generic Short Write", command.data_type); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| uint32_t regVal = 0; |
| regVal |= GEN_HDR_DT(command.data_type); |
| regVal |= GEN_HDR_VC(command.virtual_channel_id); |
| regVal |= GEN_HDR_WC_LSB(command.payload[0]); |
| if (command.payload.size() == 2) { |
| regVal |= GEN_HDR_WC_MSB(command.payload[1]); |
| } |
| |
| return GenericHdrWrite(regVal); |
| } |
| |
| // This function writes a generic long command. We can only write a maximum of FIFO_DEPTH |
| // to the payload fifo. This value is implementation specific. |
| zx_status_t DsiHostController::GenWriteLong(const mipi_dsi::DsiCommandAndResponse& command) { |
| zx_status_t status = ZX_OK; |
| uint32_t pld_data_idx = 0; // payload data index |
| uint32_t regVal = 0; |
| |
| ZX_DEBUG_ASSERT(command.payload.size() < kMaxPldFifoDepth); |
| size_t ts = command.payload.size(); // initial transfer size |
| |
| // first write complete words |
| while (ts >= 4) { |
| regVal = command.payload[pld_data_idx + 0] << 0 | command.payload[pld_data_idx + 1] << 8 | |
| command.payload[pld_data_idx + 2] << 16 | command.payload[pld_data_idx + 3] << 24; |
| pld_data_idx += 4; |
| |
| status = GenericPayloadWrite(regVal); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Generic Payload write failed: %s", zx_status_get_string(status)); |
| return status; |
| } |
| ts -= 4; |
| } |
| |
| // Write remaining bytes |
| if (ts > 0) { |
| regVal = command.payload[pld_data_idx++] << 0; |
| if (ts > 1) { |
| regVal |= command.payload[pld_data_idx++] << 8; |
| } |
| if (ts > 2) { |
| regVal |= command.payload[pld_data_idx++] << 16; |
| } |
| status = GenericPayloadWrite(regVal); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Generic Payload write failed: %s", zx_status_get_string(status)); |
| return status; |
| } |
| } |
| |
| // At this point, we have written all of our mipi payload to FIFO. Let's transmit it |
| regVal = 0; |
| regVal |= GEN_HDR_DT(command.data_type); |
| regVal |= GEN_HDR_VC(command.virtual_channel_id); |
| regVal |= GEN_HDR_WC_LSB(static_cast<uint32_t>(command.payload.size()) & 0xFF); |
| regVal |= GEN_HDR_WC_MSB((command.payload.size() & 0xFF00) >> 16); |
| |
| return GenericHdrWrite(regVal); |
| } |
| |
| zx_status_t DsiHostController::GenRead(const mipi_dsi::DsiCommandAndResponse& command) { |
| uint32_t regVal = 0; |
| zx_status_t status = ZX_OK; |
| |
| // valid cmd packet |
| if (command.payload.size() > 2) { |
| zxlogf(ERROR, "Invalid payload size (%zu) for a Generic Read command", command.payload.size()); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (command.response_payload.empty()) { |
| zxlogf(ERROR, "Response payload buffer is empty for a Generic Read command"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| regVal = 0; |
| regVal |= GEN_HDR_DT(command.data_type); |
| regVal |= GEN_HDR_VC(command.virtual_channel_id); |
| if (command.payload.size() >= 1) { |
| regVal |= GEN_HDR_WC_LSB(command.payload[0]); |
| } |
| if (command.payload.size() == 2) { |
| regVal |= GEN_HDR_WC_MSB(command.payload[1]); |
| } |
| |
| // Packet is ready. Let's enable BTA first |
| EnableBta(); |
| |
| if ((status = GenericHdrWrite(regVal)) != ZX_OK) { |
| // no need to print extra error msg |
| return status; |
| } |
| |
| if ((status = WaitforBtaAck()) != ZX_OK) { |
| // bta never returned. no need to print extra error msg |
| return status; |
| } |
| |
| // Got ACK. Let's start reading |
| // We should only read rlen worth of DATA. Let's hope the device is not sending |
| // more than it should. |
| size_t ts = command.response_payload.size(); |
| uint32_t rsp_data_idx = 0; |
| uint32_t data; |
| while (ts >= 4) { |
| status = GenericPayloadRead(&data); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to read payload data: %s", zx_status_get_string(status)); |
| return status; |
| } |
| command.response_payload[rsp_data_idx++] = static_cast<uint8_t>((data >> 0) & 0xFF); |
| command.response_payload[rsp_data_idx++] = static_cast<uint8_t>((data >> 8) & 0xFF); |
| command.response_payload[rsp_data_idx++] = static_cast<uint8_t>((data >> 16) & 0xFF); |
| command.response_payload[rsp_data_idx++] = static_cast<uint8_t>((data >> 24) & 0xFF); |
| ts -= 4; |
| } |
| |
| // Read out remaining bytes |
| if (ts > 0) { |
| status = GenericPayloadRead(&data); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to read payload data: %s", zx_status_get_string(status)); |
| return status; |
| } |
| command.response_payload[rsp_data_idx++] = (data >> 0) & 0xFF; |
| if (ts > 1) { |
| command.response_payload[rsp_data_idx++] = (data >> 8) & 0xFF; |
| } |
| if (ts > 2) { |
| command.response_payload[rsp_data_idx++] = (data >> 16) & 0xFF; |
| } |
| } |
| |
| // we are done. Display BTA |
| DisableBta(); |
| return status; |
| } |
| |
| zx_status_t DsiHostController::IssueCommand(const mipi_dsi::DsiCommandAndResponse& command) { |
| fbl::AutoLock lock(&command_lock_); |
| zx_status_t status = ZX_OK; |
| |
| switch (command.data_type) { |
| case kMipiDsiDtGenShortWrite0: |
| case kMipiDsiDtGenShortWrite1: |
| case kMipiDsiDtGenShortWrite2: |
| case kMipiDsiDtSetMaxRetPkt: |
| status = GenWriteShort(command); |
| break; |
| case kMipiDsiDtGenLongWrite: |
| case kMipiDsiDtDcsLongWrite: |
| status = GenWriteLong(command); |
| break; |
| case kMipiDsiDtGenShortRead0: |
| case kMipiDsiDtGenShortRead1: |
| case kMipiDsiDtGenShortRead2: |
| case kMipiDsiDtDcsRead0: |
| status = GenRead(command); |
| break; |
| case kMipiDsiDtDcsShortWrite0: |
| case kMipiDsiDtDcsShortWrite1: |
| status = DcsWriteShort(command); |
| break; |
| default: |
| zxlogf(ERROR, "Unsupported/Invalid DSI command data type: %d", command.data_type); |
| status = ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to perform DSI command and/or response: %s", |
| zx_status_get_string(status)); |
| LogCommand(command); |
| } |
| |
| return status; |
| } |
| |
| } // namespace designware_dsi |