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