| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_mipi_dsi_mcuxpresso/device.h" |
| |
| #include <cstring> |
| |
| #include "board.h" |
| #include "common.h" |
| #include "fsl_gpio.h" |
| #include "fsl_inputmux.h" |
| #include "fsl_mipi_dsi.h" |
| #include "fsl_mipi_dsi_smartdma.h" |
| #include "fsl_power.h" |
| #include "pin_mux.h" |
| #include "pw_assert/assert.h" |
| |
| using pw::framebuffer::Framebuffer; |
| using pw::framebuffer_pool::FramebufferPool; |
| |
| namespace pw::mipi::dsi { |
| |
| namespace { |
| |
| constexpr uint8_t kMipiDsiLaneNum = 1; |
| constexpr IRQn_Type kMipiDsiIrqn = MIPI_IRQn; |
| constexpr int kVideoLayer = 0; |
| |
| // This class is currently a singleton because the some callbacks and IRQ |
| // handlers do not have a user-data param. |
| MCUXpressoDevice* s_device; |
| |
| pw::mipi::dsi::Device::WriteCallback s_write_callback; |
| Framebuffer s_write_framebuffer; |
| |
| extern "C" { |
| void GPIO_INTA_DriverIRQHandler(void) { |
| uint32_t intStat = GPIO_PortGetInterruptStatus(GPIO, BOARD_MIPI_TE_PORT, 0); |
| |
| GPIO_PortClearInterruptFlags(GPIO, BOARD_MIPI_TE_PORT, 0, intStat); |
| |
| if (s_device && intStat & (1U << BOARD_MIPI_TE_PIN)) { |
| s_device->DisplayTEPinHandler(); |
| } |
| } |
| |
| void SDMA_DriverIRQHandler(void) { SMARTDMA_HandleIRQ(); } |
| } // extern "C" |
| |
| } // namespace |
| |
| MCUXpressoDevice::MCUXpressoDevice(const FramebufferPool& framebuffer_pool, |
| const pw::math::Size<uint16_t>& panel_size, |
| video_pixel_format_t pixel_format) |
| : framebuffer_pool_(framebuffer_pool), |
| fbdev_(kVideoLayer), |
| dsi_device_({ |
| .virtualChannel = 0, |
| .xferFunc = MCUXpressoDevice::DSI_Transfer, |
| .memWriteFunc = MCUXpressoDevice::DSI_MemWrite, |
| .callback = nullptr, |
| .userData = nullptr, |
| }), |
| rm67162_resource_({ |
| .dsiDevice = &dsi_device_, |
| .pullResetPin = MCUXpressoDevice::PullPanelResetPin, |
| .pullPowerPin = MCUXpressoDevice::PullPanelPowerPin, |
| }), |
| display_handle_({ |
| .resource = &rm67162_resource_, |
| .ops = &rm67162_ops, |
| .width = panel_size.width, |
| .height = panel_size.height, |
| .pixelFormat = pixel_format, |
| }), |
| dc_fb_dsi_cmd_handle_({ |
| .dsiDevice = &dsi_device_, |
| .panelHandle = &display_handle_, |
| .initTimes = 0, |
| .enabledLayerCount = 0, |
| .layers = {}, |
| .useTEPin = true, |
| }), |
| panel_config_({ |
| .commonConfig = |
| { |
| .resolution = |
| FSL_VIDEO_RESOLUTION(panel_size.width, panel_size.height), |
| .hsw = 0, // Unused. |
| .hfp = 0, // Unused. |
| .hbp = 0, // Unused. |
| .vsw = 0, // Unused. |
| .vfp = 0, // Unused. |
| .vbp = 0, // Unused. |
| .controlFlags = 0, |
| .dsiLanes = kMipiDsiLaneNum, |
| .pixelClock_Hz = 0, // Unsure of correct value. |
| .pixelFormat = pixel_format, |
| }, |
| .useTEPin = true, |
| }), |
| dc_({ |
| .ops = &g_dcFbOpsDsiCmd, |
| .prvData = &dc_fb_dsi_cmd_handle_, |
| .config = &panel_config_, |
| }) { |
| PW_ASSERT(s_device == nullptr); |
| s_device = this; |
| } |
| |
| MCUXpressoDevice::~MCUXpressoDevice() = default; |
| |
| Status MCUXpressoDevice::Init() { |
| const FramebufferPool::BufferArray& buffers = |
| framebuffer_pool_.GetBuffersForInit(); |
| if (buffers.empty()) |
| return Status::InvalidArgument(); |
| |
| Status s = PrepareDisplayController(); |
| if (!s.ok()) |
| return s; |
| |
| s = fbdev_.Init(&dc_, framebuffer_pool_); |
| if (!s.ok()) |
| return s; |
| |
| // Clear buffer to black - it is shown once screen is enabled. |
| void* buffer = fbdev_.GetFramebuffer(); |
| if (!buffer) { |
| return Status::Internal(); |
| } |
| std::memset( |
| buffer, |
| 0, |
| framebuffer_pool_.row_bytes() * framebuffer_pool_.dimensions().height); |
| fbdev_.WriteFramebuffer(buffer, [](void*, Status) {}); |
| |
| return fbdev_.Enable(); |
| } |
| |
| Framebuffer MCUXpressoDevice::GetFramebuffer() { |
| return Framebuffer(fbdev_.GetFramebuffer(), |
| framebuffer_pool_.pixel_format(), |
| framebuffer_pool_.dimensions(), |
| framebuffer_pool_.row_bytes()); |
| } |
| |
| void MCUXpressoDevice::WriteFramebuffer(Framebuffer framebuffer, |
| WriteCallback write_callback) { |
| PW_ASSERT(framebuffer.is_valid()); |
| PW_ASSERT(!s_write_callback); |
| s_write_callback = std::move(write_callback); |
| s_write_framebuffer = std::move(framebuffer); |
| fbdev_.WriteFramebuffer(s_write_framebuffer.data(), |
| [](void* buffer, Status s) { |
| PW_ASSERT(s_write_callback); |
| s_write_callback(std::move(s_write_framebuffer), s); |
| }); |
| } |
| |
| Status MCUXpressoDevice::PrepareDisplayController(void) { |
| Status status = InitDisplayInterface(); |
| if (!status.ok()) |
| return status; |
| |
| #if USE_DSI_SMARTDMA |
| InitSmartDMA(); |
| status_t s = DSI_TransferCreateHandleSMARTDMA( |
| MIPI_DSI_HOST, |
| &dsi_smartdma_driver_handle_, |
| MCUXpressoDevice::DsiSmartDMAMemWriteCallback, |
| this); |
| |
| return MCUXpressoToPigweedStatus(s); |
| |
| #else |
| |
| NVIC_SetPriority(kMipiDsiIrqn, 6); |
| |
| memset(&dsi_mem_write_ctx_, 0, sizeof(DSIMemWriteContext)); |
| |
| return MCUXpressoToPigweedStatus( |
| DSI_TransferCreateHandle(MIPI_DSI_HOST, |
| &dsi_driver_handle_, |
| MCUXpressoDevice::DsiMemWriteCallback, |
| this)); |
| #endif |
| } |
| |
| // static |
| Status MCUXpressoDevice::InitDisplayInterface() { |
| RESET_SetPeripheralReset(kMIPI_DSI_PHY_RST_SHIFT_RSTn); |
| InitMipiDsiClock(); |
| RESET_ClearPeripheralReset(kMIPI_DSI_CTRL_RST_SHIFT_RSTn); |
| SetMipiDsiConfig(); |
| RESET_ClearPeripheralReset(kMIPI_DSI_PHY_RST_SHIFT_RSTn); |
| return InitLcdPanel(); |
| } |
| |
| Status MCUXpressoDevice::InitLcdPanel() { |
| const gpio_pin_config_t pinConfig = { |
| .pinDirection = kGPIO_DigitalOutput, |
| .outputLogic = 0, |
| }; |
| |
| GPIO_PinInit(GPIO, BOARD_MIPI_POWER_PORT, BOARD_MIPI_POWER_PIN, &pinConfig); |
| GPIO_PinInit(GPIO, BOARD_MIPI_RST_PORT, BOARD_MIPI_RST_PIN, &pinConfig); |
| |
| InitMipiPanelTEPin(); |
| |
| return OkStatus(); |
| } |
| |
| // static |
| void MCUXpressoDevice::InitMipiDsiClock(void) { |
| POWER_DisablePD(kPDRUNCFG_APD_MIPIDSI_SRAM); |
| POWER_DisablePD(kPDRUNCFG_PPD_MIPIDSI_SRAM); |
| POWER_DisablePD(kPDRUNCFG_PD_MIPIDSI); |
| POWER_ApplyPD(); |
| |
| CLOCK_AttachClk(kFRO_DIV1_to_MIPI_DPHYESC_CLK); |
| CLOCK_SetClkDiv(kCLOCK_DivDphyEscRxClk, 4); |
| CLOCK_SetClkDiv(kCLOCK_DivDphyEscTxClk, 3); |
| mipi_dsi_tx_esc_clk_freq_hz_ = CLOCK_GetMipiDphyEscTxClkFreq(); |
| |
| CLOCK_AttachClk(kAUX1_PLL_to_MIPI_DPHY_CLK); |
| #if (DEMO_RM67162_BUFFER_FORMAT == PIXEL_FORMAT_RGB565) |
| CLOCK_InitSysPfd(kCLOCK_Pfd3, 30); |
| #else |
| CLOCK_InitSysPfd(kCLOCK_Pfd3, 19); |
| #endif |
| CLOCK_SetClkDiv(kCLOCK_DivDphyClk, 1); |
| mipi_dsi_dphy_bit_clk_freq_hz_ = CLOCK_GetMipiDphyClkFreq(); |
| } |
| |
| // static |
| void MCUXpressoDevice::InitMipiPanelTEPin(void) { |
| const gpio_pin_config_t tePinConfig = { |
| .pinDirection = kGPIO_DigitalInput, |
| .outputLogic = 0, |
| }; |
| |
| gpio_interrupt_config_t te_pin_int_config = {kGPIO_PinIntEnableEdge, |
| kGPIO_PinIntEnableHighOrRise}; |
| |
| GPIO_PinInit(GPIO, BOARD_MIPI_TE_PORT, BOARD_MIPI_TE_PIN, &tePinConfig); |
| |
| GPIO_SetPinInterruptConfig( |
| GPIO, BOARD_MIPI_TE_PORT, BOARD_MIPI_TE_PIN, &te_pin_int_config); |
| |
| GPIO_PinEnableInterrupt(GPIO, BOARD_MIPI_TE_PORT, BOARD_MIPI_TE_PIN, 0); |
| |
| NVIC_SetPriority(GPIO_INTA_IRQn, 3); |
| |
| NVIC_EnableIRQ(GPIO_INTA_IRQn); |
| } |
| |
| // static |
| void MCUXpressoDevice::SetMipiDsiConfig() { |
| dsi_config_t dsiConfig; |
| dsi_dphy_config_t dphyConfig; |
| |
| DSI_GetDefaultConfig(&dsiConfig); |
| dsiConfig.numLanes = kMipiDsiLaneNum; |
| dsiConfig.autoInsertEoTp = true; |
| |
| DSI_GetDphyDefaultConfig(&dphyConfig, |
| mipi_dsi_dphy_bit_clk_freq_hz_, |
| mipi_dsi_tx_esc_clk_freq_hz_); |
| |
| DSI_Init(MIPI_DSI_HOST, &dsiConfig); |
| |
| DSI_InitDphy(MIPI_DSI_HOST, &dphyConfig, 0); |
| } |
| |
| #if USE_DSI_SMARTDMA |
| // static |
| void MCUXpressoDevice::InitSmartDMA() { |
| RESET_ClearPeripheralReset(kINPUTMUX_RST_SHIFT_RSTn); |
| |
| INPUTMUX_Init(INPUTMUX); |
| INPUTMUX_AttachSignal(INPUTMUX, 0, kINPUTMUX_MipiIrqToSmartDmaInput); |
| |
| INPUTMUX_Deinit(INPUTMUX); |
| |
| POWER_DisablePD(kPDRUNCFG_APD_SMARTDMA_SRAM); |
| POWER_DisablePD(kPDRUNCFG_PPD_SMARTDMA_SRAM); |
| POWER_ApplyPD(); |
| |
| RESET_ClearPeripheralReset(kSMART_DMA_RST_SHIFT_RSTn); |
| CLOCK_EnableClock(kCLOCK_Smartdma); |
| |
| SMARTDMA_InitWithoutFirmware(); |
| NVIC_EnableIRQ(SDMA_IRQn); |
| |
| NVIC_SetPriority(SDMA_IRQn, 3); |
| } |
| #endif |
| |
| // static |
| status_t MCUXpressoDevice::DSI_Transfer(dsi_transfer_t* xfer) { |
| return DSI_TransferBlocking(MIPI_DSI_HOST, xfer); |
| } |
| |
| // static |
| status_t MCUXpressoDevice::DSI_MemWrite(uint8_t virtualChannel, |
| const uint8_t* data, |
| uint32_t length) { |
| #if USE_DSI_SMARTDMA |
| dsi_smartdma_write_mem_transfer_t xfer = { |
| #if (DEMO_RM67162_BUFFER_FORMAT == PIXEL_FORMAT_RGB565) |
| .inputFormat = kDSI_SMARTDMA_InputPixelFormatRGB565, |
| .outputFormat = kDSI_SMARTDMA_OutputPixelFormatRGB565, |
| #elif (DEMO_RM67162_BUFFER_FORMAT == PIXEL_FORMAT_RGB888) |
| .inputFormat = kDSI_SMARTDMA_InputPixelFormatRGB888, |
| .outputFormat = kDSI_SMARTDMA_OutputPixelFormatRGB888, |
| #else |
| .inputFormat = kDSI_SMARTDMA_InputPixelFormatXRGB8888, |
| .outputFormat = kDSI_SMARTDMA_OutputPixelFormatRGB888, |
| #endif /* DEMO_RM67162_BUFFER_FORMAT */ |
| .data = data, |
| .dataSize = length, |
| |
| .virtualChannel = virtualChannel, |
| .disablePixelByteSwap = false, |
| }; |
| |
| return DSI_TransferWriteMemorySMARTDMA( |
| MIPI_DSI_HOST, &s_device->dsi_smartdma_driver_handle_, &xfer); |
| |
| #else /* USE_DSI_SMARTDMA */ |
| |
| status_t status; |
| |
| if (s_device->dsi_mem_write_ctx_.ongoing) { |
| return kStatus_Fail; |
| } |
| |
| s_device->dsi_mem_write_xfer_.virtualChannel = virtualChannel; |
| s_device->dsi_mem_write_xfer_.flags = kDSI_TransferUseHighSpeed; |
| s_device->dsi_mem_write_xfer_.sendDscCmd = true; |
| |
| s_device->dsi_mem_write_ctx_.ongoing = true; |
| s_device->dsi_mem_write_ctx_.tx_data = data; |
| s_device->dsi_mem_write_ctx_.num_bytes_remaining = length; |
| s_device->dsi_mem_write_ctx_.dsc_cmd = kMIPI_DCS_WriteMemoryStart; |
| |
| status = s_device->DsiMemWriteSendChunck(); |
| |
| if (status != kStatus_Success) { |
| /* Memory write does not start actually. */ |
| s_device->dsi_mem_write_ctx_.ongoing = false; |
| } |
| |
| return status; |
| #endif |
| } |
| |
| // static |
| void MCUXpressoDevice::PullPanelResetPin(bool pullUp) { |
| if (pullUp) { |
| GPIO_PinWrite(GPIO, BOARD_MIPI_RST_PORT, BOARD_MIPI_RST_PIN, 1); |
| } else { |
| GPIO_PinWrite(GPIO, BOARD_MIPI_RST_PORT, BOARD_MIPI_RST_PIN, 0); |
| } |
| } |
| |
| // static |
| void MCUXpressoDevice::PullPanelPowerPin(bool pullUp) { |
| if (pullUp) { |
| GPIO_PinWrite(GPIO, BOARD_MIPI_POWER_PORT, BOARD_MIPI_POWER_PIN, 1); |
| } else { |
| GPIO_PinWrite(GPIO, BOARD_MIPI_POWER_PORT, BOARD_MIPI_POWER_PIN, 0); |
| } |
| } |
| |
| void MCUXpressoDevice::DisplayTEPinHandler() { |
| DC_FB_DSI_CMD_TE_IRQHandler(&dc_); |
| } |
| |
| // static |
| status_t MCUXpressoDevice::DsiMemWriteSendChunck(void) { |
| uint32_t curSendLen; |
| uint32_t i; |
| |
| curSendLen = kMaxDSITxArraySize > dsi_mem_write_ctx_.num_bytes_remaining |
| ? dsi_mem_write_ctx_.num_bytes_remaining |
| : kMaxDSITxArraySize; |
| |
| dsi_mem_write_xfer_.txDataType = kDSI_TxDataDcsLongWr; |
| dsi_mem_write_xfer_.dscCmd = dsi_mem_write_ctx_.dsc_cmd; |
| dsi_mem_write_xfer_.txData = dsi_mem_write_tmp_array_; |
| dsi_mem_write_xfer_.txDataSize = curSendLen; |
| |
| #if (DEMO_RM67162_BUFFER_FORMAT == PIXEL_FORMAT_RGB565) |
| for (i = 0; i < curSendLen; i += 2) { |
| dsi_mem_write_tmp_array_[i] = *(dsi_mem_write_ctx_.tx_data + 1); |
| dsi_mem_write_tmp_array_[i + 1] = *(dsi_mem_write_ctx_.tx_data); |
| |
| dsi_mem_write_ctx_.tx_data += 2; |
| } |
| #else |
| for (i = 0; i < curSendLen; i += 3) { |
| dsi_mem_write_tmp_array_[i] = *(dsi_mem_write_ctx_.tx_data + 2); |
| dsi_mem_write_tmp_array_[i + 1] = *(dsi_mem_write_ctx_.tx_data + 1); |
| dsi_mem_write_tmp_array_[i + 2] = *(dsi_mem_write_ctx_.tx_data); |
| |
| dsi_mem_write_ctx_.tx_data += 3; |
| } |
| #endif |
| |
| dsi_mem_write_ctx_.num_bytes_remaining -= curSendLen; |
| dsi_mem_write_ctx_.dsc_cmd = kMIPI_DCS_WriteMemoryContinue; |
| |
| return DSI_TransferNonBlocking( |
| MIPI_DSI_HOST, &dsi_driver_handle_, &dsi_mem_write_xfer_); |
| } |
| |
| // static |
| void MCUXpressoDevice::DsiMemWriteCallback(MIPI_DSI_HOST_Type* base, |
| dsi_handle_t* handle, |
| status_t status, |
| void* userData) { |
| MCUXpressoDevice* device = static_cast<MCUXpressoDevice*>(userData); |
| if ((kStatus_Success == status) && |
| (device->dsi_mem_write_ctx_.num_bytes_remaining > 0)) { |
| status = device->DsiMemWriteSendChunck(); |
| if (kStatus_Success == status) { |
| return; |
| } |
| } |
| |
| device->dsi_mem_write_ctx_.ongoing = false; |
| MIPI_DSI_MemoryDoneDriverCallback(status, &device->dsi_device_); |
| } |
| |
| // static |
| void MCUXpressoDevice::DsiSmartDMAMemWriteCallback( |
| MIPI_DSI_HOST_Type* base, |
| dsi_smartdma_handle_t* handle, |
| status_t status, |
| void* userData) { |
| MCUXpressoDevice* device = static_cast<MCUXpressoDevice*>(userData); |
| MIPI_DSI_MemoryDoneDriverCallback(status, &device->dsi_device_); |
| } |
| |
| } // namespace pw::mipi::dsi |