/* * Copyright 2023, NXP * * SPDX-License-Identifier: Apache-2.0 */ /* Based on dsi_mcux.c, which is (c) 2022 NXP */ #define DT_DRV_COMPAT nxp_mipi_dsi_2l #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(dsi_mcux_host, CONFIG_MIPI_DSI_LOG_LEVEL); struct mcux_mipi_dsi_config { MIPI_DSI_HOST_Type *base; dsi_dpi_config_t dpi_config; bool auto_insert_eotp; bool noncontinuous_hs_clk; const struct device *bit_clk_dev; clock_control_subsys_t bit_clk_subsys; const struct device *esc_clk_dev; clock_control_subsys_t esc_clk_subsys; const struct device *pixel_clk_dev; clock_control_subsys_t pixel_clk_subsys; uint32_t dphy_ref_freq; #ifdef CONFIG_MIPI_DSI_MCUX_2L_SMARTDMA const struct device *smart_dma; #else void (*irq_config_func)(const struct device *dev); #endif }; struct mcux_mipi_dsi_data { dsi_handle_t mipi_handle; struct k_sem transfer_sem; #ifdef CONFIG_MIPI_DSI_MCUX_2L_SMARTDMA uint8_t dma_slot; #endif }; /* MAX DSI TX payload */ #define DSI_TX_MAX_PAYLOAD_BYTE (64U * 4U) #ifdef CONFIG_MIPI_DSI_MCUX_2L_SMARTDMA /* Callback for DSI DMA transfer completion, called in ISR context */ static void dsi_mcux_dma_cb(const struct device *dma_dev, void *user_data, uint32_t channel, int status) { const struct device *dev = user_data; const struct mcux_mipi_dsi_config *config = dev->config; struct mcux_mipi_dsi_data *data = dev->data; uint32_t int_flags1, int_flags2; if (status != 0) { LOG_ERR("SMARTDMA transfer failed"); } else { /* Disable DSI interrupts at transfer completion */ DSI_DisableInterrupts(config->base, kDSI_InterruptGroup1ApbTxDone | kDSI_InterruptGroup1HtxTo, 0U); DSI_GetAndClearInterruptStatus(config->base, &int_flags1, &int_flags2); k_sem_give(&data->transfer_sem); } } /* Helper function to transfer DSI color (DMA based implementation) */ static int dsi_mcux_tx_color(const struct device *dev, uint8_t channel, struct mipi_dsi_msg *msg) { /* * Color streams are a special case for this DSI peripheral, because * the SMARTDMA peripheral (if enabled) can be used to accelerate * the transfer of data to the DSI. The SMARTDMA has the additional * advantage over traditional DMA of being able to to automatically * byte swap color data. This is advantageous, as most graphical * frameworks store RGB data in little endian format, but many * MIPI displays expect color data in big endian format. */ const struct mcux_mipi_dsi_config *config = dev->config; struct mcux_mipi_dsi_data *data = dev->data; struct dma_config dma_cfg = {0}; struct dma_block_config block = {0}; int ret; if (channel != 0) { return -ENOTSUP; /* DMA can only transfer on virtual channel 0 */ } /* Configure smartDMA device, and run transfer */ block.source_address = (uint32_t)msg->tx_buf; block.block_size = msg->tx_len; dma_cfg.dma_callback = dsi_mcux_dma_cb; dma_cfg.user_data = (struct device *)dev; dma_cfg.head_block = █ dma_cfg.block_count = 1; dma_cfg.dma_slot = data->dma_slot; dma_cfg.channel_direction = MEMORY_TO_PERIPHERAL; ret = dma_config(config->smart_dma, 0, &dma_cfg); if (ret < 0) { LOG_ERR("Could not configure SMARTDMA"); return ret; } /* * SMARTDMA uses DSI interrupt line as input for the DMA * transfer trigger. Therefore, we need to enable DSI TX * interrupts in order to trigger the DMA engine. * Note that if the MIPI IRQ is enabled in * the NVIC, it will fire on every SMARTDMA transfer */ DSI_EnableInterrupts(config->base, kDSI_InterruptGroup1ApbTxDone | kDSI_InterruptGroup1HtxTo, 0U); /* Trigger DMA engine */ ret = dma_start(config->smart_dma, 0); if (ret < 0) { LOG_ERR("Could not start SMARTDMA"); return ret; } /* Wait for TX completion */ k_sem_take(&data->transfer_sem, K_FOREVER); return msg->tx_len; } #else /* CONFIG_MIPI_DSI_MCUX_2L_SMARTDMA is not set */ /* Callback for DSI transfer completion, called in ISR context */ static void dsi_transfer_complete(MIPI_DSI_HOST_Type *base, dsi_handle_t *handle, status_t status, void *userData) { struct mcux_mipi_dsi_data *data = userData; k_sem_give(&data->transfer_sem); } /* Helper function to transfer DSI color (Interrupt based implementation) */ static int dsi_mcux_tx_color(const struct device *dev, uint8_t channel, struct mipi_dsi_msg *msg) { const struct mcux_mipi_dsi_config *config = dev->config; struct mcux_mipi_dsi_data *data = dev->data; status_t status; dsi_transfer_t xfer = { .virtualChannel = channel, .txData = msg->tx_buf, .rxDataSize = (uint16_t)msg->rx_len, .rxData = msg->rx_buf, .sendDscCmd = true, .dscCmd = msg->cmd, .txDataType = kDSI_TxDataDcsLongWr, /* default to high speed unless told to use low power */ .flags = (msg->flags & MIPI_DSI_MSG_USE_LPM) ? 0 : kDSI_TransferUseHighSpeed, }; /* * Cap transfer size. Note that we subtract six bytes here, * one for the DSC command and five to insure that * transfers are still aligned on a pixel boundary * (two or three byte pixel sizes are supported). */ xfer.txDataSize = MIN(msg->tx_len, (DSI_TX_MAX_PAYLOAD_BYTE - 6)); if (IS_ENABLED(CONFIG_MIPI_DSI_MCUX_2L_SWAP16)) { /* Manually swap the 16 byte color data in software */ uint8_t *src = (uint8_t *)xfer.txData; uint8_t tmp; for (uint32_t i = 0; i < xfer.txDataSize; i += 2) { tmp = src[i]; src[i] = src[i + 1]; src[i + 1] = tmp; } } /* Send TX data using non-blocking DSI API */ status = DSI_TransferNonBlocking(config->base, &data->mipi_handle, &xfer); /* Wait for transfer completion */ k_sem_take(&data->transfer_sem, K_FOREVER); if (status != kStatus_Success) { LOG_ERR("Transmission failed"); return -EIO; } return xfer.txDataSize; } /* ISR is used for DSI interrupt based implementation, unnecessary if DMA is used */ static int mipi_dsi_isr(const struct device *dev) { const struct mcux_mipi_dsi_config *config = dev->config; struct mcux_mipi_dsi_data *data = dev->data; DSI_TransferHandleIRQ(config->base, &data->mipi_handle); return 0; } #endif static int dsi_mcux_attach(const struct device *dev, uint8_t channel, const struct mipi_dsi_device *mdev) { const struct mcux_mipi_dsi_config *config = dev->config; dsi_dphy_config_t dphy_config; dsi_config_t dsi_config; uint32_t dphy_bit_clk_freq; uint32_t dphy_esc_clk_freq; uint32_t dsi_pixel_clk_freq; uint32_t bit_width; DSI_GetDefaultConfig(&dsi_config); dsi_config.numLanes = mdev->data_lanes; dsi_config.autoInsertEoTp = config->auto_insert_eotp; dsi_config.enableNonContinuousHsClk = config->noncontinuous_hs_clk; imxrt_pre_init_display_interface(); /* Init the DSI module. */ DSI_Init(config->base, &dsi_config); #ifdef CONFIG_MIPI_DSI_MCUX_2L_SMARTDMA /* Connect DSI IRQ line to SMARTDMA trigger via * INPUTMUX. */ /* Attach INPUTMUX from MIPI to SMARTDMA */ INPUTMUX_Init(INPUTMUX); INPUTMUX_AttachSignal(INPUTMUX, 0, kINPUTMUX_MipiIrqToSmartDmaInput); /* Gate inputmux clock to save power */ INPUTMUX_Deinit(INPUTMUX); if (!device_is_ready(config->smart_dma)) { return -ENODEV; } struct mcux_mipi_dsi_data *data = dev->data; switch (mdev->pixfmt) { case MIPI_DSI_PIXFMT_RGB888: data->dma_slot = DMA_SMARTDMA_MIPI_RGB888_DMA; break; case MIPI_DSI_PIXFMT_RGB565: if (IS_ENABLED(CONFIG_MIPI_DSI_MCUX_2L_SWAP16)) { data->dma_slot = DMA_SMARTDMA_MIPI_RGB565_DMA_SWAP; } else { data->dma_slot = DMA_SMARTDMA_MIPI_RGB565_DMA; } break; default: LOG_ERR("SMARTDMA does not support pixel_format %u", mdev->pixfmt); return -ENODEV; } #else struct mcux_mipi_dsi_data *data = dev->data; /* Create transfer handle */ if (DSI_TransferCreateHandle(config->base, &data->mipi_handle, dsi_transfer_complete, data) != kStatus_Success) { return -ENODEV; } #endif /* Get the DPHY bit clock frequency */ if (clock_control_get_rate(config->bit_clk_dev, config->bit_clk_subsys, &dphy_bit_clk_freq)) { return -EINVAL; }; /* Get the DPHY ESC clock frequency */ if (clock_control_get_rate(config->esc_clk_dev, config->esc_clk_subsys, &dphy_esc_clk_freq)) { return -EINVAL; } /* Get the Pixel clock frequency */ if (clock_control_get_rate(config->pixel_clk_dev, config->pixel_clk_subsys, &dsi_pixel_clk_freq)) { return -EINVAL; } switch (config->dpi_config.pixelPacket) { case kDSI_PixelPacket16Bit: bit_width = 16; break; case kDSI_PixelPacket18Bit: __fallthrough; case kDSI_PixelPacket18BitLoosely: bit_width = 18; break; case kDSI_PixelPacket24Bit: bit_width = 24; break; default: return -EINVAL; /* Invalid bit width enum value? */ } /* 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 */ if (((dsi_pixel_clk_freq * bit_width) / mdev->data_lanes) > dphy_bit_clk_freq) { return -EINVAL; } DSI_GetDphyDefaultConfig(&dphy_config, dphy_bit_clk_freq, dphy_esc_clk_freq); if (config->dphy_ref_freq != 0) { dphy_bit_clk_freq = DSI_InitDphy(config->base, &dphy_config, config->dphy_ref_freq); } else { /* DPHY PLL is not present, ref clock is unused */ DSI_InitDphy(config->base, &dphy_config, 0); } /* * 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(config->base, &config->dpi_config, mdev->data_lanes, dsi_pixel_clk_freq, dphy_bit_clk_freq); } imxrt_post_init_display_interface(); return 0; } static int dsi_mcux_detach(const struct device *dev, uint8_t channel, const struct mipi_dsi_device *mdev) { const struct mcux_mipi_dsi_config *config = dev->config; /* Enable DPHY auto power down */ DSI_DeinitDphy(config->base); /* Fully power off DPHY */ config->base->PD_DPHY = 0x1; /* Deinit MIPI */ DSI_Deinit(config->base); /* Call IMX RT clock function to gate clocks and power at SOC level */ imxrt_deinit_display_interface(); return 0; } static ssize_t dsi_mcux_transfer(const struct device *dev, uint8_t channel, struct mipi_dsi_msg *msg) { const struct mcux_mipi_dsi_config *config = dev->config; dsi_transfer_t dsi_xfer = {0}; status_t status; int ret; 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; /* default to high speed unless told to use low power */ dsi_xfer.flags = (msg->flags & MIPI_DSI_MSG_USE_LPM) ? 0 : kDSI_TransferUseHighSpeed; 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.txDataType = kDSI_TxDataDcsLongWr; if (msg->flags & MCUX_DSI_2L_FB_DATA) { /* * Special case- transfer framebuffer data using * SMARTDMA or non blocking DSI API. The framebuffer * will also be color swapped, if enabled. */ ret = dsi_mcux_tx_color(dev, channel, msg); if (ret < 0) { LOG_ERR("Transmission failed"); return -EIO; } return ret; } 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 msg->rx_len; } /* Return tx_len on a write */ return msg->tx_len; } static struct mipi_dsi_driver_api dsi_mcux_api = { .attach = dsi_mcux_attach, .detach = dsi_mcux_detach, .transfer = dsi_mcux_transfer, }; static int mcux_mipi_dsi_init(const struct device *dev) { const struct mcux_mipi_dsi_config *config = dev->config; struct mcux_mipi_dsi_data *data = dev->data; #ifndef CONFIG_MIPI_DSI_MCUX_2L_SMARTDMA /* Enable IRQ */ config->irq_config_func(dev); #endif k_sem_init(&data->transfer_sem, 0, 1); if (!device_is_ready(config->bit_clk_dev) || !device_is_ready(config->esc_clk_dev) || !device_is_ready(config->pixel_clk_dev)) { return -ENODEV; } 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), vsync_active) ? \ kDSI_DpiVsyncActiveHigh : \ kDSI_DpiVsyncActiveLow) | \ (DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif), \ display_timings), hsync_active) ? \ kDSI_DpiHsyncActiveHigh : \ kDSI_DpiHsyncActiveLow), \ .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) \ COND_CODE_1(CONFIG_MIPI_DSI_MCUX_2L_SMARTDMA, \ (), (static void mipi_dsi_##n##_irq_config_func(const struct device *dev) \ { \ IRQ_CONNECT(DT_INST_IRQN(id), DT_INST_IRQ(id, priority), \ mipi_dsi_isr, DEVICE_DT_INST_GET(id), 0); \ irq_enable(DT_INST_IRQN(id)); \ })) \ \ static const struct mcux_mipi_dsi_config mipi_dsi_config_##id = { \ MCUX_DSI_DPI_CONFIG(id) \ COND_CODE_1(CONFIG_MIPI_DSI_MCUX_2L_SMARTDMA, \ (.smart_dma = DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(id, smartdma)),), \ (.irq_config_func = mipi_dsi_##n##_irq_config_func,)) \ .base = (MIPI_DSI_HOST_Type *)DT_INST_REG_ADDR(id), \ .auto_insert_eotp = DT_INST_PROP(id, autoinsert_eotp), \ .noncontinuous_hs_clk = DT_INST_PROP(id, noncontinuous_hs_clk), \ .dphy_ref_freq = DT_INST_PROP_OR(id, dphy_ref_frequency, 0), \ .bit_clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_NAME(id, dphy)), \ .bit_clk_subsys = \ (clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_NAME(id, dphy, name), \ .esc_clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_NAME(id, esc)), \ .esc_clk_subsys = \ (clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_NAME(id, esc, name), \ .pixel_clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_NAME(id, pixel)), \ .pixel_clk_subsys = \ (clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_NAME(id, pixel, name), \ }; \ \ static struct mcux_mipi_dsi_data mipi_dsi_data_##id; \ DEVICE_DT_INST_DEFINE(id, \ &mcux_mipi_dsi_init, \ NULL, \ &mipi_dsi_data_##id, \ &mipi_dsi_config_##id, \ POST_KERNEL, \ CONFIG_MIPI_DSI_INIT_PRIORITY, \ &dsi_mcux_api); DT_INST_FOREACH_STATUS_OKAY(MCUX_MIPI_DSI_DEVICE)