Commit | Line | Data |
---|---|---|
c942fddf | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
45d59d70 MV |
2 | /* |
3 | * Copyright (C) 2016 Marek Vasut <marex@denx.de> | |
4 | * | |
5 | * This code is based on drivers/video/fbdev/mxsfb.c : | |
6 | * Copyright (C) 2010 Juergen Beisert, Pengutronix | |
7 | * Copyright (C) 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. | |
8 | * Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved. | |
45d59d70 MV |
9 | */ |
10 | ||
11 | #include <drm/drmP.h> | |
12 | #include <drm/drm_atomic_helper.h> | |
13 | #include <drm/drm_crtc.h> | |
45d59d70 MV |
14 | #include <drm/drm_fb_helper.h> |
15 | #include <drm/drm_fb_cma_helper.h> | |
16 | #include <drm/drm_gem_cma_helper.h> | |
17 | #include <drm/drm_of.h> | |
18 | #include <drm/drm_plane_helper.h> | |
fcd70cd3 | 19 | #include <drm/drm_probe_helper.h> |
45d59d70 MV |
20 | #include <drm/drm_simple_kms_helper.h> |
21 | #include <linux/clk.h> | |
22 | #include <linux/iopoll.h> | |
23 | #include <linux/of_graph.h> | |
24 | #include <linux/platform_data/simplefb.h> | |
25 | #include <video/videomode.h> | |
26 | ||
27 | #include "mxsfb_drv.h" | |
28 | #include "mxsfb_regs.h" | |
29 | ||
0f933328 FE |
30 | #define MXS_SET_ADDR 0x4 |
31 | #define MXS_CLR_ADDR 0x8 | |
32 | #define MODULE_CLKGATE BIT(30) | |
33 | #define MODULE_SFTRST BIT(31) | |
34 | /* 1 second delay should be plenty of time for block reset */ | |
35 | #define RESET_TIMEOUT 1000000 | |
36 | ||
45d59d70 MV |
37 | static u32 set_hsync_pulse_width(struct mxsfb_drm_private *mxsfb, u32 val) |
38 | { | |
39 | return (val & mxsfb->devdata->hs_wdth_mask) << | |
40 | mxsfb->devdata->hs_wdth_shift; | |
41 | } | |
42 | ||
43 | /* Setup the MXSFB registers for decoding the pixels out of the framebuffer */ | |
44 | static int mxsfb_set_pixel_fmt(struct mxsfb_drm_private *mxsfb) | |
45 | { | |
46 | struct drm_crtc *crtc = &mxsfb->pipe.crtc; | |
47 | struct drm_device *drm = crtc->dev; | |
438b74a5 | 48 | const u32 format = crtc->primary->state->fb->format->format; |
45d59d70 MV |
49 | u32 ctrl, ctrl1; |
50 | ||
51 | ctrl = CTRL_BYPASS_COUNT | CTRL_MASTER; | |
52 | ||
53 | /* | |
54 | * WARNING: The bus width, CTRL_SET_BUS_WIDTH(), is configured to | |
55 | * match the selected mode here. This differs from the original | |
56 | * MXSFB driver, which had the option to configure the bus width | |
57 | * to arbitrary value. This limitation should not pose an issue. | |
58 | */ | |
59 | ||
60 | /* CTRL1 contains IRQ config and status bits, preserve those. */ | |
61 | ctrl1 = readl(mxsfb->base + LCDC_CTRL1); | |
62 | ctrl1 &= CTRL1_CUR_FRAME_DONE_IRQ_EN | CTRL1_CUR_FRAME_DONE_IRQ; | |
63 | ||
64 | switch (format) { | |
65 | case DRM_FORMAT_RGB565: | |
66 | dev_dbg(drm->dev, "Setting up RGB565 mode\n"); | |
45d59d70 MV |
67 | ctrl |= CTRL_SET_WORD_LENGTH(0); |
68 | ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf); | |
69 | break; | |
70 | case DRM_FORMAT_XRGB8888: | |
71 | dev_dbg(drm->dev, "Setting up XRGB8888 mode\n"); | |
45d59d70 MV |
72 | ctrl |= CTRL_SET_WORD_LENGTH(3); |
73 | /* Do not use packed pixels = one pixel per word instead. */ | |
74 | ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0x7); | |
75 | break; | |
76 | default: | |
77 | dev_err(drm->dev, "Unhandled pixel format %08x\n", format); | |
78 | return -EINVAL; | |
79 | } | |
80 | ||
81 | writel(ctrl1, mxsfb->base + LCDC_CTRL1); | |
82 | writel(ctrl, mxsfb->base + LCDC_CTRL); | |
83 | ||
84 | return 0; | |
85 | } | |
86 | ||
10f2889b SA |
87 | static void mxsfb_set_bus_fmt(struct mxsfb_drm_private *mxsfb) |
88 | { | |
89 | struct drm_crtc *crtc = &mxsfb->pipe.crtc; | |
90 | struct drm_device *drm = crtc->dev; | |
91 | u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; | |
92 | u32 reg; | |
93 | ||
94 | reg = readl(mxsfb->base + LCDC_CTRL); | |
95 | ||
96 | if (mxsfb->connector.display_info.num_bus_formats) | |
97 | bus_format = mxsfb->connector.display_info.bus_formats[0]; | |
98 | ||
99 | reg &= ~CTRL_BUS_WIDTH_MASK; | |
100 | switch (bus_format) { | |
101 | case MEDIA_BUS_FMT_RGB565_1X16: | |
102 | reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_16BIT); | |
103 | break; | |
104 | case MEDIA_BUS_FMT_RGB666_1X18: | |
105 | reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_18BIT); | |
106 | break; | |
107 | case MEDIA_BUS_FMT_RGB888_1X24: | |
108 | reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_24BIT); | |
109 | break; | |
110 | default: | |
111 | dev_err(drm->dev, "Unknown media bus format %d\n", bus_format); | |
112 | break; | |
113 | } | |
114 | writel(reg, mxsfb->base + LCDC_CTRL); | |
115 | } | |
116 | ||
45d59d70 MV |
117 | static void mxsfb_enable_controller(struct mxsfb_drm_private *mxsfb) |
118 | { | |
119 | u32 reg; | |
120 | ||
121 | if (mxsfb->clk_disp_axi) | |
122 | clk_prepare_enable(mxsfb->clk_disp_axi); | |
123 | clk_prepare_enable(mxsfb->clk); | |
45d59d70 MV |
124 | |
125 | /* If it was disabled, re-enable the mode again */ | |
126 | writel(CTRL_DOTCLK_MODE, mxsfb->base + LCDC_CTRL + REG_SET); | |
127 | ||
128 | /* Enable the SYNC signals first, then the DMA engine */ | |
129 | reg = readl(mxsfb->base + LCDC_VDCTRL4); | |
130 | reg |= VDCTRL4_SYNC_SIGNALS_ON; | |
131 | writel(reg, mxsfb->base + LCDC_VDCTRL4); | |
132 | ||
133 | writel(CTRL_RUN, mxsfb->base + LCDC_CTRL + REG_SET); | |
134 | } | |
135 | ||
136 | static void mxsfb_disable_controller(struct mxsfb_drm_private *mxsfb) | |
137 | { | |
138 | u32 reg; | |
139 | ||
140 | /* | |
141 | * Even if we disable the controller here, it will still continue | |
142 | * until its FIFOs are running out of data | |
143 | */ | |
144 | writel(CTRL_DOTCLK_MODE, mxsfb->base + LCDC_CTRL + REG_CLR); | |
145 | ||
146 | readl_poll_timeout(mxsfb->base + LCDC_CTRL, reg, !(reg & CTRL_RUN), | |
147 | 0, 1000); | |
148 | ||
149 | reg = readl(mxsfb->base + LCDC_VDCTRL4); | |
150 | reg &= ~VDCTRL4_SYNC_SIGNALS_ON; | |
151 | writel(reg, mxsfb->base + LCDC_VDCTRL4); | |
152 | ||
45d59d70 MV |
153 | clk_disable_unprepare(mxsfb->clk); |
154 | if (mxsfb->clk_disp_axi) | |
155 | clk_disable_unprepare(mxsfb->clk_disp_axi); | |
156 | } | |
157 | ||
0f933328 FE |
158 | /* |
159 | * Clear the bit and poll it cleared. This is usually called with | |
160 | * a reset address and mask being either SFTRST(bit 31) or CLKGATE | |
161 | * (bit 30). | |
162 | */ | |
163 | static int clear_poll_bit(void __iomem *addr, u32 mask) | |
164 | { | |
165 | u32 reg; | |
166 | ||
167 | writel(mask, addr + MXS_CLR_ADDR); | |
168 | return readl_poll_timeout(addr, reg, !(reg & mask), 0, RESET_TIMEOUT); | |
169 | } | |
170 | ||
171 | static int mxsfb_reset_block(void __iomem *reset_addr) | |
172 | { | |
173 | int ret; | |
174 | ||
175 | ret = clear_poll_bit(reset_addr, MODULE_SFTRST); | |
176 | if (ret) | |
177 | return ret; | |
178 | ||
179 | writel(MODULE_CLKGATE, reset_addr + MXS_CLR_ADDR); | |
180 | ||
181 | ret = clear_poll_bit(reset_addr, MODULE_SFTRST); | |
182 | if (ret) | |
183 | return ret; | |
184 | ||
185 | return clear_poll_bit(reset_addr, MODULE_CLKGATE); | |
186 | } | |
187 | ||
2dc3620e LC |
188 | static dma_addr_t mxsfb_get_fb_paddr(struct mxsfb_drm_private *mxsfb) |
189 | { | |
190 | struct drm_framebuffer *fb = mxsfb->pipe.plane.state->fb; | |
191 | struct drm_gem_cma_object *gem; | |
192 | ||
193 | if (!fb) | |
194 | return 0; | |
195 | ||
196 | gem = drm_fb_cma_get_gem_obj(fb, 0); | |
197 | if (!gem) | |
198 | return 0; | |
199 | ||
200 | return gem->paddr; | |
201 | } | |
202 | ||
45d59d70 MV |
203 | static void mxsfb_crtc_mode_set_nofb(struct mxsfb_drm_private *mxsfb) |
204 | { | |
205 | struct drm_display_mode *m = &mxsfb->pipe.crtc.state->adjusted_mode; | |
206 | const u32 bus_flags = mxsfb->connector.display_info.bus_flags; | |
207 | u32 vdctrl0, vsync_pulse_len, hsync_pulse_len; | |
208 | int err; | |
209 | ||
210 | /* | |
211 | * It seems, you can't re-program the controller if it is still | |
212 | * running. This may lead to shifted pictures (FIFO issue?), so | |
213 | * first stop the controller and drain its FIFOs. | |
214 | */ | |
45d59d70 | 215 | |
0f933328 FE |
216 | /* Mandatory eLCDIF reset as per the Reference Manual */ |
217 | err = mxsfb_reset_block(mxsfb->base); | |
218 | if (err) | |
219 | return; | |
220 | ||
45d59d70 MV |
221 | /* Clear the FIFOs */ |
222 | writel(CTRL1_FIFO_CLEAR, mxsfb->base + LCDC_CTRL1 + REG_SET); | |
223 | ||
224 | err = mxsfb_set_pixel_fmt(mxsfb); | |
225 | if (err) | |
226 | return; | |
227 | ||
228 | clk_set_rate(mxsfb->clk, m->crtc_clock * 1000); | |
229 | ||
230 | writel(TRANSFER_COUNT_SET_VCOUNT(m->crtc_vdisplay) | | |
231 | TRANSFER_COUNT_SET_HCOUNT(m->crtc_hdisplay), | |
232 | mxsfb->base + mxsfb->devdata->transfer_count); | |
233 | ||
234 | vsync_pulse_len = m->crtc_vsync_end - m->crtc_vsync_start; | |
235 | ||
236 | vdctrl0 = VDCTRL0_ENABLE_PRESENT | /* Always in DOTCLOCK mode */ | |
237 | VDCTRL0_VSYNC_PERIOD_UNIT | | |
238 | VDCTRL0_VSYNC_PULSE_WIDTH_UNIT | | |
239 | VDCTRL0_SET_VSYNC_PULSE_WIDTH(vsync_pulse_len); | |
240 | if (m->flags & DRM_MODE_FLAG_PHSYNC) | |
241 | vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH; | |
242 | if (m->flags & DRM_MODE_FLAG_PVSYNC) | |
243 | vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH; | |
53990e41 SA |
244 | /* Make sure Data Enable is high active by default */ |
245 | if (!(bus_flags & DRM_BUS_FLAG_DE_LOW)) | |
45d59d70 | 246 | vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH; |
53990e41 | 247 | /* |
88bc4178 | 248 | * DRM_BUS_FLAG_PIXDATA_DRIVE_ defines are controller centric, |
53990e41 SA |
249 | * controllers VDCTRL0_DOTCLK is display centric. |
250 | * Drive on positive edge -> display samples on falling edge | |
88bc4178 | 251 | * DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE -> VDCTRL0_DOTCLK_ACT_FALLING |
53990e41 | 252 | */ |
88bc4178 | 253 | if (bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE) |
45d59d70 MV |
254 | vdctrl0 |= VDCTRL0_DOTCLK_ACT_FALLING; |
255 | ||
256 | writel(vdctrl0, mxsfb->base + LCDC_VDCTRL0); | |
257 | ||
10f2889b SA |
258 | mxsfb_set_bus_fmt(mxsfb); |
259 | ||
45d59d70 MV |
260 | /* Frame length in lines. */ |
261 | writel(m->crtc_vtotal, mxsfb->base + LCDC_VDCTRL1); | |
262 | ||
263 | /* Line length in units of clocks or pixels. */ | |
264 | hsync_pulse_len = m->crtc_hsync_end - m->crtc_hsync_start; | |
265 | writel(set_hsync_pulse_width(mxsfb, hsync_pulse_len) | | |
266 | VDCTRL2_SET_HSYNC_PERIOD(m->crtc_htotal), | |
267 | mxsfb->base + LCDC_VDCTRL2); | |
268 | ||
d42986b6 FE |
269 | writel(SET_HOR_WAIT_CNT(m->crtc_htotal - m->crtc_hsync_start) | |
270 | SET_VERT_WAIT_CNT(m->crtc_vtotal - m->crtc_vsync_start), | |
45d59d70 MV |
271 | mxsfb->base + LCDC_VDCTRL3); |
272 | ||
273 | writel(SET_DOTCLK_H_VALID_DATA_CNT(m->hdisplay), | |
274 | mxsfb->base + LCDC_VDCTRL4); | |
45d59d70 MV |
275 | } |
276 | ||
277 | void mxsfb_crtc_enable(struct mxsfb_drm_private *mxsfb) | |
278 | { | |
2dc3620e LC |
279 | dma_addr_t paddr; |
280 | ||
626a2c52 | 281 | mxsfb_enable_axi_clk(mxsfb); |
45d59d70 | 282 | mxsfb_crtc_mode_set_nofb(mxsfb); |
2dc3620e LC |
283 | |
284 | /* Write cur_buf as well to avoid an initial corrupt frame */ | |
285 | paddr = mxsfb_get_fb_paddr(mxsfb); | |
286 | if (paddr) { | |
287 | writel(paddr, mxsfb->base + mxsfb->devdata->cur_buf); | |
288 | writel(paddr, mxsfb->base + mxsfb->devdata->next_buf); | |
289 | } | |
290 | ||
45d59d70 MV |
291 | mxsfb_enable_controller(mxsfb); |
292 | } | |
293 | ||
294 | void mxsfb_crtc_disable(struct mxsfb_drm_private *mxsfb) | |
295 | { | |
296 | mxsfb_disable_controller(mxsfb); | |
626a2c52 | 297 | mxsfb_disable_axi_clk(mxsfb); |
45d59d70 MV |
298 | } |
299 | ||
300 | void mxsfb_plane_atomic_update(struct mxsfb_drm_private *mxsfb, | |
301 | struct drm_plane_state *state) | |
302 | { | |
303 | struct drm_simple_display_pipe *pipe = &mxsfb->pipe; | |
304 | struct drm_crtc *crtc = &pipe->crtc; | |
45d59d70 | 305 | struct drm_pending_vblank_event *event; |
2dc3620e | 306 | dma_addr_t paddr; |
45d59d70 MV |
307 | |
308 | spin_lock_irq(&crtc->dev->event_lock); | |
309 | event = crtc->state->event; | |
310 | if (event) { | |
311 | crtc->state->event = NULL; | |
312 | ||
313 | if (drm_crtc_vblank_get(crtc) == 0) { | |
314 | drm_crtc_arm_vblank_event(crtc, event); | |
315 | } else { | |
316 | drm_crtc_send_vblank_event(crtc, event); | |
317 | } | |
318 | } | |
319 | spin_unlock_irq(&crtc->dev->event_lock); | |
320 | ||
2dc3620e LC |
321 | paddr = mxsfb_get_fb_paddr(mxsfb); |
322 | if (paddr) { | |
323 | mxsfb_enable_axi_clk(mxsfb); | |
324 | writel(paddr, mxsfb->base + mxsfb->devdata->next_buf); | |
325 | mxsfb_disable_axi_clk(mxsfb); | |
326 | } | |
45d59d70 | 327 | } |