1 /*
2 * Copyright 2022, NXP
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT nxp_imx_mipi_dsi
8
9 #include <zephyr/drivers/mipi_dsi.h>
10 #include <fsl_mipi_dsi.h>
11 #include <fsl_clock.h>
12 #include <zephyr/logging/log.h>
13
14 #include <soc.h>
15
16 LOG_MODULE_REGISTER(dsi_mcux, CONFIG_MIPI_DSI_LOG_LEVEL);
17
18 #define MIPI_DPHY_REF_CLK DT_INST_PROP(0, dphy_ref_frequency)
19
20 /* Max output frequency of DPHY bit clock */
21 #define MIPI_DPHY_MAX_FREQ MHZ(800)
22
23 /* PLL CN should be in the range of 1 to 32. */
24 #define DSI_DPHY_PLL_CN_MIN 1U
25 #define DSI_DPHY_PLL_CN_MAX 32U
26
27 /* PLL refClk / CN should be in the range of 24M to 30M. */
28 #define DSI_DPHY_PLL_REFCLK_CN_MIN MHZ(24)
29 #define DSI_DPHY_PLL_REFCLK_CN_MAX MHZ(30)
30
31 /* PLL CM should be in the range of 16 to 255. */
32 #define DSI_DPHY_PLL_CM_MIN 16U
33 #define DSI_DPHY_PLL_CM_MAX 255U
34
35 #define DSI_DPHY_PLL_CO_MIN 0
36 #define DSI_DPHY_PLL_CO_MAX 3
37
38 /* MAX DSI TX payload */
39 #define DSI_TX_MAX_PAYLOAD_BYTE (64U * 4U)
40
41 struct display_mcux_mipi_dsi_config {
42 MIPI_DSI_Type base;
43 dsi_dpi_config_t dpi_config;
44 bool auto_insert_eotp;
45 uint32_t phy_clock;
46 };
47
48 struct display_mcux_mipi_dsi_data {
49 const struct device *dev;
50 };
51
dsi_mcux_best_clock(uint32_t ref_clk,uint32_t target_freq)52 static uint32_t dsi_mcux_best_clock(uint32_t ref_clk, uint32_t target_freq)
53 {
54 /*
55 * This function is intended to find the closest realizable DPHY
56 * bit clock for a given target frequency, such that the DPHY clock
57 * is faster than the target frequency. MCUX SDK implements a similar
58 * function with DSI_DphyGetPllDivider, but this function will
59 * configure the DPHY to output the closest realizable clock frequency
60 * to the requested value. This can cause dropped pixels if
61 * the output frequency is less than the requested one.
62 */
63 uint32_t co_shift, cn, cm;
64 uint32_t cand_freq, vco_freq, refclk_cn_freq;
65 uint32_t best_pll_freq = 0U;
66 uint32_t best_diff = UINT32_MAX;
67
68 /*
69 * The formula for the DPHY output frequency is:
70 * ref_clk * (CM / (CN * (1 << CO)))
71 */
72
73 /* Test all available CO shifts (1x, 2x, 4x, 8x) */
74 for (co_shift = DSI_DPHY_PLL_CO_MIN; co_shift <= DSI_DPHY_PLL_CO_MAX; co_shift++) {
75 /* Determine VCO output frequency before CO divider */
76 vco_freq = target_freq << co_shift;
77
78 /* If desired VCO output frequency is too low, try next CO shift */
79 if (vco_freq < DSI_DPHY_PLL_VCO_MIN) {
80 continue;
81 }
82
83 /* If desired VCO output frequency is too high, no point in
84 * searching further
85 */
86 if (vco_freq > DSI_DPHY_PLL_VCO_MAX) {
87 break;
88 }
89
90 /* Search the best CN and CM values for desired VCO frequency */
91 for (cn = DSI_DPHY_PLL_CN_MIN; cn <= DSI_DPHY_PLL_CN_MAX; cn++) {
92 refclk_cn_freq = ref_clk / cn;
93
94 /* If the frequency after input divider is too high,
95 * try next CN value
96 */
97 if (refclk_cn_freq > DSI_DPHY_PLL_REFCLK_CN_MAX) {
98 continue;
99 }
100
101 /* If the frequency after input divider is too low,
102 * no point in trying higher dividers.
103 */
104 if (refclk_cn_freq < DSI_DPHY_PLL_REFCLK_CN_MIN) {
105 break;
106 }
107
108 /* Get the closest CM value for this vco frequency
109 * and input divider. Round up, to bias towards higher
110 * frequencies
111 * NOTE: we differ from the SDK algorithm here, which
112 * would round cm to the closest integer
113 */
114 cm = (vco_freq + (refclk_cn_freq - 1)) / refclk_cn_freq;
115
116 /* If CM was rounded up to one over valid range,
117 * round down
118 */
119 if (cm == (DSI_DPHY_PLL_CM_MAX + 1)) {
120 cm = DSI_DPHY_PLL_CM_MAX;
121 }
122
123 /* If CM value is still out of range, CN/CO setting won't work */
124 if ((cm < DSI_DPHY_PLL_CM_MIN) || (cm > DSI_DPHY_PLL_CM_MAX)) {
125 continue;
126 }
127
128 /* Calculate candidate frequency */
129 cand_freq = (refclk_cn_freq * cm) >> co_shift;
130
131 if (cand_freq < target_freq) {
132 /* SKIP frequencies less than target frequency.
133 * this is where the algorithm differs from the
134 * SDK.
135 */
136 continue;
137 } else {
138 if ((cand_freq - target_freq) < best_diff) {
139 /* New best CN, CM, and CO found */
140 best_diff = (cand_freq - target_freq);
141 best_pll_freq = cand_freq;
142 }
143 }
144
145 if (best_diff == 0U) {
146 /* We have found exact match for CN, CM, CO.
147 * return now.
148 */
149 return best_pll_freq;
150 }
151 }
152 }
153 return best_pll_freq;
154 }
155
156
dsi_mcux_attach(const struct device * dev,uint8_t channel,const struct mipi_dsi_device * mdev)157 static int dsi_mcux_attach(const struct device *dev,
158 uint8_t channel,
159 const struct mipi_dsi_device *mdev)
160 {
161 const struct display_mcux_mipi_dsi_config *config = dev->config;
162 dsi_dphy_config_t dphy_config;
163 dsi_config_t dsi_config;
164 uint32_t mipi_dsi_esc_clk_hz;
165 uint32_t mipi_dsi_tx_esc_clk_hz;
166 uint32_t mipi_dsi_dphy_ref_clk_hz = MIPI_DPHY_REF_CLK;
167
168 DSI_GetDefaultConfig(&dsi_config);
169 dsi_config.numLanes = mdev->data_lanes;
170 dsi_config.autoInsertEoTp = config->auto_insert_eotp;
171
172 /* Init the DSI module. */
173 DSI_Init((MIPI_DSI_Type *)&config->base, &dsi_config);
174
175 /* Init DPHY.
176 *
177 * The DPHY bit clock must be fast enough to send out the pixels, it should be
178 * larger than:
179 *
180 * (Pixel clock * bit per output pixel) / number of MIPI data lane
181 */
182 uint32_t mipi_dsi_dpi_clk_hz = CLOCK_GetRootClockFreq(kCLOCK_Root_Lcdif);
183 /* Find the best realizable clock value for the MIPI DSI */
184 uint32_t mipi_dsi_dphy_bit_clk_hz =
185 dsi_mcux_best_clock(mipi_dsi_dphy_ref_clk_hz, config->phy_clock);
186 if (mipi_dsi_dphy_bit_clk_hz == 0) {
187 LOG_ERR("DPHY cannot support requested PHY clock");
188 return -ENOTSUP;
189 }
190 /* Cap clock value to max frequency */
191 mipi_dsi_dphy_bit_clk_hz = MIN(mipi_dsi_dphy_bit_clk_hz, MIPI_DPHY_MAX_FREQ);
192
193 mipi_dsi_esc_clk_hz = CLOCK_GetRootClockFreq(kCLOCK_Root_Mipi_Esc);
194 mipi_dsi_tx_esc_clk_hz = mipi_dsi_esc_clk_hz / 3;
195
196 DSI_GetDphyDefaultConfig(&dphy_config, mipi_dsi_dphy_bit_clk_hz, mipi_dsi_tx_esc_clk_hz);
197
198 mipi_dsi_dphy_bit_clk_hz = DSI_InitDphy((MIPI_DSI_Type *)&config->base,
199 &dphy_config, mipi_dsi_dphy_ref_clk_hz);
200
201 LOG_DBG("DPHY clock set to %u", mipi_dsi_dphy_bit_clk_hz);
202 /*
203 * If nxp,lcdif node is present, then the MIPI DSI driver will
204 * accept input on the DPI port from the LCDIF, and convert the output
205 * to DSI data. This is useful for video mode, where the LCDIF can
206 * constantly refresh the MIPI panel.
207 */
208 if (mdev->mode_flags & MIPI_DSI_MODE_VIDEO) {
209 /* Init DPI interface. */
210 DSI_SetDpiConfig((MIPI_DSI_Type *)&config->base,
211 &config->dpi_config, mdev->data_lanes,
212 mipi_dsi_dpi_clk_hz, mipi_dsi_dphy_bit_clk_hz);
213 }
214
215 imxrt_post_init_display_interface();
216
217 return 0;
218 }
219
dsi_mcux_transfer(const struct device * dev,uint8_t channel,struct mipi_dsi_msg * msg)220 static ssize_t dsi_mcux_transfer(const struct device *dev, uint8_t channel,
221 struct mipi_dsi_msg *msg)
222 {
223 const struct display_mcux_mipi_dsi_config *config = dev->config;
224 dsi_transfer_t dsi_xfer = {0};
225 status_t status;
226
227 dsi_xfer.virtualChannel = channel;
228 dsi_xfer.txDataSize = msg->tx_len;
229 dsi_xfer.txData = msg->tx_buf;
230 dsi_xfer.rxDataSize = msg->rx_len;
231 dsi_xfer.rxData = msg->rx_buf;
232
233 switch (msg->type) {
234
235 case MIPI_DSI_DCS_READ:
236 LOG_ERR("DCS Read not yet implemented or used");
237 return -ENOTSUP;
238 case MIPI_DSI_DCS_SHORT_WRITE:
239 dsi_xfer.sendDscCmd = true;
240 dsi_xfer.dscCmd = msg->cmd;
241 dsi_xfer.txDataType = kDSI_TxDataDcsShortWrNoParam;
242 break;
243 case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
244 dsi_xfer.sendDscCmd = true;
245 dsi_xfer.dscCmd = msg->cmd;
246 dsi_xfer.txDataType = kDSI_TxDataDcsShortWrOneParam;
247 break;
248 case MIPI_DSI_DCS_LONG_WRITE:
249 dsi_xfer.sendDscCmd = true;
250 dsi_xfer.dscCmd = msg->cmd;
251 dsi_xfer.flags = kDSI_TransferUseHighSpeed;
252 dsi_xfer.txDataType = kDSI_TxDataDcsLongWr;
253 /*
254 * Cap transfer size. Note that we subtract six bytes here,
255 * one for the DSC command and one to insure that
256 * transfers are still aligned on a pixel boundary
257 * (two or three byte pixel sizes are supported).
258 */
259 dsi_xfer.txDataSize = MIN(dsi_xfer.txDataSize,
260 (DSI_TX_MAX_PAYLOAD_BYTE - 6));
261 break;
262 case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
263 dsi_xfer.txDataType = kDSI_TxDataGenShortWrNoParam;
264 break;
265 case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
266 dsi_xfer.txDataType = kDSI_TxDataGenShortWrOneParam;
267 break;
268 case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
269 dsi_xfer.txDataType = kDSI_TxDataGenShortWrTwoParam;
270 break;
271 case MIPI_DSI_GENERIC_LONG_WRITE:
272 dsi_xfer.txDataType = kDSI_TxDataGenLongWr;
273 break;
274 case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM:
275 __fallthrough;
276 case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM:
277 __fallthrough;
278 case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM:
279 LOG_ERR("Generic Read not yet implemented or used");
280 return -ENOTSUP;
281 default:
282 LOG_ERR("Unsupported message type (%d)", msg->type);
283 return -ENOTSUP;
284 }
285
286 status = DSI_TransferBlocking(&config->base, &dsi_xfer);
287
288 if (status != kStatus_Success) {
289 LOG_ERR("Transmission failed");
290 return -EIO;
291 }
292
293 if (msg->rx_len != 0) {
294 /* Return rx_len on a read */
295 return dsi_xfer.rxDataSize;
296 }
297
298 /* Return tx_len on a write */
299 return dsi_xfer.txDataSize;
300
301 }
302
303 static DEVICE_API(mipi_dsi, dsi_mcux_api) = {
304 .attach = dsi_mcux_attach,
305 .transfer = dsi_mcux_transfer,
306 };
307
display_mcux_mipi_dsi_init(const struct device * dev)308 static int display_mcux_mipi_dsi_init(const struct device *dev)
309 {
310 imxrt_pre_init_display_interface();
311
312 return 0;
313 }
314
315 #define MCUX_DSI_DPI_CONFIG(id) \
316 IF_ENABLED(DT_NODE_HAS_PROP(DT_DRV_INST(id), nxp_lcdif), \
317 (.dpi_config = { \
318 .dpiColorCoding = DT_INST_ENUM_IDX(id, dpi_color_coding), \
319 .pixelPacket = DT_INST_ENUM_IDX(id, dpi_pixel_packet), \
320 .videoMode = DT_INST_ENUM_IDX(id, dpi_video_mode), \
321 .bllpMode = DT_INST_ENUM_IDX(id, dpi_bllp_mode), \
322 .pixelPayloadSize = DT_INST_PROP_BY_PHANDLE(id, nxp_lcdif, width), \
323 .panelHeight = DT_INST_PROP_BY_PHANDLE(id, nxp_lcdif, height), \
324 .polarityFlags = (DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif), \
325 display_timings), hsync_active) ? \
326 kDSI_DpiHsyncActiveHigh : kDSI_DpiHsyncActiveLow) | \
327 (DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif), \
328 display_timings), vsync_active) ? \
329 kDSI_DpiVsyncActiveHigh : kDSI_DpiVsyncActiveLow), \
330 .hfp = DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif), \
331 display_timings), hfront_porch), \
332 .hbp = DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif), \
333 display_timings), hback_porch), \
334 .hsw = DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif), \
335 display_timings), hsync_len), \
336 .vfp = DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif), \
337 display_timings), vfront_porch), \
338 .vbp = DT_PROP(DT_CHILD(DT_INST_PHANDLE(id, nxp_lcdif), \
339 display_timings), vback_porch), \
340 },))
341
342 #define MCUX_MIPI_DSI_DEVICE(id) \
343 static const struct display_mcux_mipi_dsi_config display_mcux_mipi_dsi_config_##id = { \
344 .base = { \
345 .host = (DSI_HOST_Type *)DT_INST_REG_ADDR_BY_IDX(id, 0), \
346 .dpi = (DSI_HOST_DPI_INTFC_Type *)DT_INST_REG_ADDR_BY_IDX(id, 1), \
347 .apb = (DSI_HOST_APB_PKT_IF_Type *)DT_INST_REG_ADDR_BY_IDX(id, 2), \
348 .dphy = (DSI_HOST_NXP_FDSOI28_DPHY_INTFC_Type *) \
349 DT_INST_REG_ADDR_BY_IDX(id, 3), \
350 }, \
351 MCUX_DSI_DPI_CONFIG(id) \
352 .auto_insert_eotp = DT_INST_PROP(id, autoinsert_eotp), \
353 .phy_clock = DT_INST_PROP(id, phy_clock), \
354 }; \
355 static struct display_mcux_mipi_dsi_data display_mcux_mipi_dsi_data_##id; \
356 DEVICE_DT_INST_DEFINE(id, \
357 &display_mcux_mipi_dsi_init, \
358 NULL, \
359 &display_mcux_mipi_dsi_data_##id, \
360 &display_mcux_mipi_dsi_config_##id, \
361 POST_KERNEL, \
362 CONFIG_MIPI_DSI_INIT_PRIORITY, \
363 &dsi_mcux_api);
364
365 DT_INST_FOREACH_STATUS_OKAY(MCUX_MIPI_DSI_DEVICE)
366