|  | /* | 
|  | * Copyright 2022, NXP | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT nxp_imx_mipi_dsi | 
|  |  | 
|  | #include <zephyr/drivers/mipi_dsi.h> | 
|  | #include <fsl_mipi_dsi.h> | 
|  | #include <fsl_clock.h> | 
|  | #include <zephyr/logging/log.h> | 
|  |  | 
|  | #include <soc.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(dsi_mcux, CONFIG_MIPI_DSI_LOG_LEVEL); | 
|  |  | 
|  | #define MIPI_DPHY_REF_CLK DT_INST_PROP(0, dphy_ref_frequency) | 
|  |  | 
|  | /* Max output frequency of DPHY bit clock */ | 
|  | #define MIPI_DPHY_MAX_FREQ MHZ(800) | 
|  |  | 
|  | /* PLL CN should be in the range of 1 to 32. */ | 
|  | #define DSI_DPHY_PLL_CN_MIN 1U | 
|  | #define DSI_DPHY_PLL_CN_MAX 32U | 
|  |  | 
|  | /* PLL refClk / CN should be in the range of 24M to 30M. */ | 
|  | #define DSI_DPHY_PLL_REFCLK_CN_MIN MHZ(24) | 
|  | #define DSI_DPHY_PLL_REFCLK_CN_MAX MHZ(30) | 
|  |  | 
|  | /* PLL CM should be in the range of 16 to 255. */ | 
|  | #define DSI_DPHY_PLL_CM_MIN 16U | 
|  | #define DSI_DPHY_PLL_CM_MAX 255U | 
|  |  | 
|  | #define DSI_DPHY_PLL_CO_MIN 0 | 
|  | #define DSI_DPHY_PLL_CO_MAX 3 | 
|  |  | 
|  | /* MAX DSI TX payload */ | 
|  | #define DSI_TX_MAX_PAYLOAD_BYTE (64U * 4U) | 
|  |  | 
|  | struct display_mcux_mipi_dsi_config { | 
|  | MIPI_DSI_Type base; | 
|  | dsi_dpi_config_t dpi_config; | 
|  | bool auto_insert_eotp; | 
|  | uint32_t phy_clock; | 
|  | }; | 
|  |  | 
|  | struct display_mcux_mipi_dsi_data { | 
|  | const struct device *dev; | 
|  | }; | 
|  |  | 
|  | static uint32_t dsi_mcux_best_clock(uint32_t ref_clk, uint32_t target_freq) | 
|  | { | 
|  | /* | 
|  | * This function is intended to find the closest realizable DPHY | 
|  | * bit clock for a given target frequency, such that the DPHY clock | 
|  | * is faster than the target frequency. MCUX SDK implements a similar | 
|  | * function with DSI_DphyGetPllDivider, but this function will | 
|  | * configure the DPHY to output the closest realizable clock frequency | 
|  | * to the requested value. This can cause dropped pixels if | 
|  | * the output frequency is less than the requested one. | 
|  | */ | 
|  | uint32_t co_shift, cn, cm; | 
|  | uint32_t cand_freq, vco_freq, refclk_cn_freq; | 
|  | uint32_t best_pll_freq = 0U; | 
|  | uint32_t best_diff = UINT32_MAX; | 
|  |  | 
|  | /* | 
|  | * The formula for the DPHY output frequency is: | 
|  | * ref_clk * (CM / (CN * (1 << CO))) | 
|  | */ | 
|  |  | 
|  | /* Test all available CO shifts (1x, 2x, 4x, 8x) */ | 
|  | for (co_shift = DSI_DPHY_PLL_CO_MIN; co_shift <= DSI_DPHY_PLL_CO_MAX; co_shift++) { | 
|  | /* Determine VCO output frequency before CO divider */ | 
|  | vco_freq = target_freq << co_shift; | 
|  |  | 
|  | /* If desired VCO output frequency is too low, try next CO shift */ | 
|  | if (vco_freq < DSI_DPHY_PLL_VCO_MIN) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* If desired VCO output frequency is too high, no point in | 
|  | * searching further | 
|  | */ | 
|  | if (vco_freq > DSI_DPHY_PLL_VCO_MAX) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Search the best CN and CM values for desired VCO frequency */ | 
|  | for (cn = DSI_DPHY_PLL_CN_MIN; cn <= DSI_DPHY_PLL_CN_MAX; cn++) { | 
|  | refclk_cn_freq = ref_clk / cn; | 
|  |  | 
|  | /* If the frequency after input divider is too high, | 
|  | * try next CN value | 
|  | */ | 
|  | if (refclk_cn_freq > DSI_DPHY_PLL_REFCLK_CN_MAX) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* If the frequency after input divider is too low, | 
|  | * no point in trying higher dividers. | 
|  | */ | 
|  | if (refclk_cn_freq < DSI_DPHY_PLL_REFCLK_CN_MIN) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Get the closest CM value for this vco frequency | 
|  | * and input divider. Round up, to bias towards higher | 
|  | * frequencies | 
|  | * NOTE: we differ from the SDK algorithm here, which | 
|  | * would round cm to the closest integer | 
|  | */ | 
|  | cm = (vco_freq + (refclk_cn_freq - 1)) / refclk_cn_freq; | 
|  |  | 
|  | /* If CM was rounded up to one over valid range, | 
|  | * round down | 
|  | */ | 
|  | if (cm == (DSI_DPHY_PLL_CM_MAX + 1)) { | 
|  | cm = DSI_DPHY_PLL_CM_MAX; | 
|  | } | 
|  |  | 
|  | /* If CM value is still out of range, CN/CO setting won't work */ | 
|  | if ((cm < DSI_DPHY_PLL_CM_MIN) || (cm > DSI_DPHY_PLL_CM_MAX)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* Calculate candidate frequency */ | 
|  | cand_freq = (refclk_cn_freq * cm) >> co_shift; | 
|  |  | 
|  | if (cand_freq < target_freq) { | 
|  | /* SKIP frequencies less than target frequency. | 
|  | * this is where the algorithm differs from the | 
|  | * SDK. | 
|  | */ | 
|  | continue; | 
|  | } else { | 
|  | if ((cand_freq - target_freq) < best_diff) { | 
|  | /* New best CN, CM, and CO found */ | 
|  | best_diff = (cand_freq - target_freq); | 
|  | best_pll_freq = cand_freq; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (best_diff == 0U) { | 
|  | /* We have found exact match for CN, CM, CO. | 
|  | * return now. | 
|  | */ | 
|  | return best_pll_freq; | 
|  | } | 
|  | } | 
|  | } | 
|  | return best_pll_freq; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int dsi_mcux_attach(const struct device *dev, | 
|  | uint8_t channel, | 
|  | const struct mipi_dsi_device *mdev) | 
|  | { | 
|  | const struct display_mcux_mipi_dsi_config *config = dev->config; | 
|  | dsi_dphy_config_t dphy_config; | 
|  | dsi_config_t dsi_config; | 
|  | uint32_t mipi_dsi_esc_clk_hz; | 
|  | uint32_t mipi_dsi_tx_esc_clk_hz; | 
|  | uint32_t mipi_dsi_dphy_ref_clk_hz = MIPI_DPHY_REF_CLK; | 
|  |  | 
|  | DSI_GetDefaultConfig(&dsi_config); | 
|  | dsi_config.numLanes = mdev->data_lanes; | 
|  | dsi_config.autoInsertEoTp = config->auto_insert_eotp; | 
|  |  | 
|  | /* Init the DSI module. */ | 
|  | DSI_Init((MIPI_DSI_Type *)&config->base, &dsi_config); | 
|  |  | 
|  | /* Init DPHY. | 
|  | * | 
|  | * The DPHY bit clock must be fast enough to send out the pixels, it should be | 
|  | * larger than: | 
|  | * | 
|  | *         (Pixel clock * bit per output pixel) / number of MIPI data lane | 
|  | */ | 
|  | uint32_t mipi_dsi_dpi_clk_hz = CLOCK_GetRootClockFreq(kCLOCK_Root_Lcdif); | 
|  | /* Find the best realizable clock value for the MIPI DSI */ | 
|  | uint32_t mipi_dsi_dphy_bit_clk_hz = | 
|  | dsi_mcux_best_clock(mipi_dsi_dphy_ref_clk_hz, config->phy_clock); | 
|  | if (mipi_dsi_dphy_bit_clk_hz == 0) { | 
|  | LOG_ERR("DPHY cannot support requested PHY clock"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  | /* Cap clock value to max frequency */ | 
|  | mipi_dsi_dphy_bit_clk_hz = MIN(mipi_dsi_dphy_bit_clk_hz, MIPI_DPHY_MAX_FREQ); | 
|  |  | 
|  | mipi_dsi_esc_clk_hz = CLOCK_GetRootClockFreq(kCLOCK_Root_Mipi_Esc); | 
|  | mipi_dsi_tx_esc_clk_hz = mipi_dsi_esc_clk_hz / 3; | 
|  |  | 
|  | DSI_GetDphyDefaultConfig(&dphy_config, mipi_dsi_dphy_bit_clk_hz, mipi_dsi_tx_esc_clk_hz); | 
|  |  | 
|  | mipi_dsi_dphy_bit_clk_hz = DSI_InitDphy((MIPI_DSI_Type *)&config->base, | 
|  | &dphy_config, mipi_dsi_dphy_ref_clk_hz); | 
|  |  | 
|  | LOG_DBG("DPHY clock set to %u", mipi_dsi_dphy_bit_clk_hz); | 
|  | /* | 
|  | * If nxp,lcdif node is present, then the MIPI DSI driver will | 
|  | * accept input on the DPI port from the LCDIF, and convert the output | 
|  | * to DSI data. This is useful for video mode, where the LCDIF can | 
|  | * constantly refresh the MIPI panel. | 
|  | */ | 
|  | if (mdev->mode_flags & MIPI_DSI_MODE_VIDEO) { | 
|  | /* Init DPI interface. */ | 
|  | DSI_SetDpiConfig((MIPI_DSI_Type *)&config->base, | 
|  | &config->dpi_config, mdev->data_lanes, | 
|  | mipi_dsi_dpi_clk_hz, mipi_dsi_dphy_bit_clk_hz); | 
|  | } | 
|  |  | 
|  | imxrt_post_init_display_interface(); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static ssize_t dsi_mcux_transfer(const struct device *dev, uint8_t channel, | 
|  | struct mipi_dsi_msg *msg) | 
|  | { | 
|  | const struct display_mcux_mipi_dsi_config *config = dev->config; | 
|  | dsi_transfer_t dsi_xfer = {0}; | 
|  | status_t status; | 
|  |  | 
|  | dsi_xfer.virtualChannel = channel; | 
|  | dsi_xfer.txDataSize = msg->tx_len; | 
|  | dsi_xfer.txData = msg->tx_buf; | 
|  | dsi_xfer.rxDataSize = msg->rx_len; | 
|  | dsi_xfer.rxData = msg->rx_buf; | 
|  |  | 
|  | switch (msg->type) { | 
|  |  | 
|  | case MIPI_DSI_DCS_READ: | 
|  | LOG_ERR("DCS Read not yet implemented or used"); | 
|  | return -ENOTSUP; | 
|  | case MIPI_DSI_DCS_SHORT_WRITE: | 
|  | dsi_xfer.sendDscCmd = true; | 
|  | dsi_xfer.dscCmd = msg->cmd; | 
|  | dsi_xfer.txDataType = kDSI_TxDataDcsShortWrNoParam; | 
|  | break; | 
|  | case MIPI_DSI_DCS_SHORT_WRITE_PARAM: | 
|  | dsi_xfer.sendDscCmd = true; | 
|  | dsi_xfer.dscCmd = msg->cmd; | 
|  | dsi_xfer.txDataType = kDSI_TxDataDcsShortWrOneParam; | 
|  | break; | 
|  | case MIPI_DSI_DCS_LONG_WRITE: | 
|  | dsi_xfer.sendDscCmd = true; | 
|  | dsi_xfer.dscCmd = msg->cmd; | 
|  | dsi_xfer.flags = kDSI_TransferUseHighSpeed; | 
|  | dsi_xfer.txDataType = kDSI_TxDataDcsLongWr; | 
|  | /* | 
|  | * Cap transfer size. Note that we subtract six bytes here, | 
|  | * one for the DSC command and one to insure that | 
|  | * transfers are still aligned on a pixel boundary | 
|  | * (two or three byte pixel sizes are supported). | 
|  | */ | 
|  | dsi_xfer.txDataSize = MIN(dsi_xfer.txDataSize, | 
|  | (DSI_TX_MAX_PAYLOAD_BYTE - 6)); | 
|  | break; | 
|  | case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM: | 
|  | dsi_xfer.txDataType = kDSI_TxDataGenShortWrNoParam; | 
|  | break; | 
|  | case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM: | 
|  | dsi_xfer.txDataType = kDSI_TxDataGenShortWrOneParam; | 
|  | break; | 
|  | case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM: | 
|  | dsi_xfer.txDataType = kDSI_TxDataGenShortWrTwoParam; | 
|  | break; | 
|  | case MIPI_DSI_GENERIC_LONG_WRITE: | 
|  | dsi_xfer.txDataType = kDSI_TxDataGenLongWr; | 
|  | break; | 
|  | case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM: | 
|  | __fallthrough; | 
|  | case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM: | 
|  | __fallthrough; | 
|  | case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM: | 
|  | LOG_ERR("Generic Read not yet implemented or used"); | 
|  | return -ENOTSUP; | 
|  | default: | 
|  | LOG_ERR("Unsupported message type (%d)", msg->type); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | status = DSI_TransferBlocking(&config->base, &dsi_xfer); | 
|  |  | 
|  | if (status != kStatus_Success) { | 
|  | LOG_ERR("Transmission failed"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (msg->rx_len != 0) { | 
|  | /* Return rx_len on a read */ | 
|  | return dsi_xfer.rxDataSize; | 
|  | } | 
|  |  | 
|  | /* Return tx_len on a write */ | 
|  | return dsi_xfer.txDataSize; | 
|  |  | 
|  | } | 
|  |  | 
|  | static DEVICE_API(mipi_dsi, dsi_mcux_api) = { | 
|  | .attach = dsi_mcux_attach, | 
|  | .transfer = dsi_mcux_transfer, | 
|  | }; | 
|  |  | 
|  | static int display_mcux_mipi_dsi_init(const struct device *dev) | 
|  | { | 
|  | imxrt_pre_init_display_interface(); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #define MCUX_DSI_DPI_CONFIG(id)									\ | 
|  | IF_ENABLED(DT_NODE_HAS_PROP(DT_DRV_INST(id), nxp_lcdif),				\ | 
|  | (.dpi_config = {									\ | 
|  | .dpiColorCoding = DT_INST_ENUM_IDX(id, dpi_color_coding),			\ | 
|  | .pixelPacket = DT_INST_ENUM_IDX(id, dpi_pixel_packet),				\ | 
|  | .videoMode = DT_INST_ENUM_IDX(id, dpi_video_mode),				\ | 
|  | .bllpMode = DT_INST_ENUM_IDX(id, dpi_bllp_mode),				\ | 
|  | .pixelPayloadSize = DT_INST_PROP_BY_PHANDLE(id, nxp_lcdif, width),		\ | 
|  | .panelHeight = DT_INST_PROP_BY_PHANDLE(id, nxp_lcdif, height),			\ | 
|  | .polarityFlags = (DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif),		\ | 
|  | display_timings), hsync_active) ?				\ | 
|  | kDSI_DpiHsyncActiveHigh : kDSI_DpiHsyncActiveLow) |		\ | 
|  | (DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif),		\ | 
|  | display_timings), vsync_active) ?				\ | 
|  | kDSI_DpiVsyncActiveHigh : kDSI_DpiVsyncActiveLow),		\ | 
|  | .hfp = DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif),				\ | 
|  | display_timings), hfront_porch),			\ | 
|  | .hbp = DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif),				\ | 
|  | display_timings), hback_porch),			\ | 
|  | .hsw = DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif),				\ | 
|  | display_timings), hsync_len),			\ | 
|  | .vfp = DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif),				\ | 
|  | display_timings), vfront_porch),			\ | 
|  | .vbp = DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif),				\ | 
|  | display_timings), vback_porch),			\ | 
|  | },)) | 
|  |  | 
|  | #define MCUX_MIPI_DSI_DEVICE(id)								\ | 
|  | static const struct display_mcux_mipi_dsi_config display_mcux_mipi_dsi_config_##id = {	\ | 
|  | .base = {									\ | 
|  | .host = (DSI_HOST_Type *)DT_INST_REG_ADDR_BY_IDX(id, 0),		\ | 
|  | .dpi = (DSI_HOST_DPI_INTFC_Type *)DT_INST_REG_ADDR_BY_IDX(id, 1),	\ | 
|  | .apb = (DSI_HOST_APB_PKT_IF_Type *)DT_INST_REG_ADDR_BY_IDX(id, 2),	\ | 
|  | .dphy = (DSI_HOST_NXP_FDSOI28_DPHY_INTFC_Type *)			\ | 
|  | DT_INST_REG_ADDR_BY_IDX(id, 3),					\ | 
|  | },										\ | 
|  | MCUX_DSI_DPI_CONFIG(id)								\ | 
|  | .auto_insert_eotp = DT_INST_PROP(id, autoinsert_eotp),				\ | 
|  | .phy_clock = DT_INST_PROP(id, phy_clock),					\ | 
|  | };											\ | 
|  | static struct display_mcux_mipi_dsi_data display_mcux_mipi_dsi_data_##id;		\ | 
|  | DEVICE_DT_INST_DEFINE(id,								\ | 
|  | &display_mcux_mipi_dsi_init,					\ | 
|  | NULL,								\ | 
|  | &display_mcux_mipi_dsi_data_##id,					\ | 
|  | &display_mcux_mipi_dsi_config_##id,					\ | 
|  | POST_KERNEL,							\ | 
|  | CONFIG_MIPI_DSI_INIT_PRIORITY,					\ | 
|  | &dsi_mcux_api); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(MCUX_MIPI_DSI_DEVICE) |