Commit | Line | Data |
---|---|---|
45d59d70 MV |
1 | /* |
2 | * Copyright (C) 2016 Marek Vasut <marex@denx.de> | |
3 | * | |
4 | * This code is based on drivers/video/fbdev/mxsfb.c : | |
5 | * Copyright (C) 2010 Juergen Beisert, Pengutronix | |
6 | * Copyright (C) 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. | |
7 | * Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved. | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU General Public License | |
11 | * as published by the Free Software Foundation; either version 2 | |
12 | * of the License, or (at your option) any later version. | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | */ | |
18 | ||
19 | #include <drm/drmP.h> | |
20 | #include <drm/drm_atomic_helper.h> | |
21 | #include <drm/drm_crtc.h> | |
22 | #include <drm/drm_crtc_helper.h> | |
23 | #include <drm/drm_fb_helper.h> | |
24 | #include <drm/drm_fb_cma_helper.h> | |
25 | #include <drm/drm_gem_cma_helper.h> | |
26 | #include <drm/drm_of.h> | |
27 | #include <drm/drm_plane_helper.h> | |
28 | #include <drm/drm_simple_kms_helper.h> | |
29 | #include <linux/clk.h> | |
30 | #include <linux/iopoll.h> | |
31 | #include <linux/of_graph.h> | |
32 | #include <linux/platform_data/simplefb.h> | |
33 | #include <video/videomode.h> | |
34 | ||
35 | #include "mxsfb_drv.h" | |
36 | #include "mxsfb_regs.h" | |
37 | ||
38 | static u32 set_hsync_pulse_width(struct mxsfb_drm_private *mxsfb, u32 val) | |
39 | { | |
40 | return (val & mxsfb->devdata->hs_wdth_mask) << | |
41 | mxsfb->devdata->hs_wdth_shift; | |
42 | } | |
43 | ||
44 | /* Setup the MXSFB registers for decoding the pixels out of the framebuffer */ | |
45 | static int mxsfb_set_pixel_fmt(struct mxsfb_drm_private *mxsfb) | |
46 | { | |
47 | struct drm_crtc *crtc = &mxsfb->pipe.crtc; | |
48 | struct drm_device *drm = crtc->dev; | |
438b74a5 | 49 | const u32 format = crtc->primary->state->fb->format->format; |
45d59d70 MV |
50 | u32 ctrl, ctrl1; |
51 | ||
52 | ctrl = CTRL_BYPASS_COUNT | CTRL_MASTER; | |
53 | ||
54 | /* | |
55 | * WARNING: The bus width, CTRL_SET_BUS_WIDTH(), is configured to | |
56 | * match the selected mode here. This differs from the original | |
57 | * MXSFB driver, which had the option to configure the bus width | |
58 | * to arbitrary value. This limitation should not pose an issue. | |
59 | */ | |
60 | ||
61 | /* CTRL1 contains IRQ config and status bits, preserve those. */ | |
62 | ctrl1 = readl(mxsfb->base + LCDC_CTRL1); | |
63 | ctrl1 &= CTRL1_CUR_FRAME_DONE_IRQ_EN | CTRL1_CUR_FRAME_DONE_IRQ; | |
64 | ||
65 | switch (format) { | |
66 | case DRM_FORMAT_RGB565: | |
67 | dev_dbg(drm->dev, "Setting up RGB565 mode\n"); | |
45d59d70 MV |
68 | ctrl |= CTRL_SET_WORD_LENGTH(0); |
69 | ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf); | |
70 | break; | |
71 | case DRM_FORMAT_XRGB8888: | |
72 | dev_dbg(drm->dev, "Setting up XRGB8888 mode\n"); | |
45d59d70 MV |
73 | ctrl |= CTRL_SET_WORD_LENGTH(3); |
74 | /* Do not use packed pixels = one pixel per word instead. */ | |
75 | ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0x7); | |
76 | break; | |
77 | default: | |
78 | dev_err(drm->dev, "Unhandled pixel format %08x\n", format); | |
79 | return -EINVAL; | |
80 | } | |
81 | ||
82 | writel(ctrl1, mxsfb->base + LCDC_CTRL1); | |
83 | writel(ctrl, mxsfb->base + LCDC_CTRL); | |
84 | ||
85 | return 0; | |
86 | } | |
87 | ||
10f2889b SA |
88 | static void mxsfb_set_bus_fmt(struct mxsfb_drm_private *mxsfb) |
89 | { | |
90 | struct drm_crtc *crtc = &mxsfb->pipe.crtc; | |
91 | struct drm_device *drm = crtc->dev; | |
92 | u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; | |
93 | u32 reg; | |
94 | ||
95 | reg = readl(mxsfb->base + LCDC_CTRL); | |
96 | ||
97 | if (mxsfb->connector.display_info.num_bus_formats) | |
98 | bus_format = mxsfb->connector.display_info.bus_formats[0]; | |
99 | ||
100 | reg &= ~CTRL_BUS_WIDTH_MASK; | |
101 | switch (bus_format) { | |
102 | case MEDIA_BUS_FMT_RGB565_1X16: | |
103 | reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_16BIT); | |
104 | break; | |
105 | case MEDIA_BUS_FMT_RGB666_1X18: | |
106 | reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_18BIT); | |
107 | break; | |
108 | case MEDIA_BUS_FMT_RGB888_1X24: | |
109 | reg |= CTRL_SET_BUS_WIDTH(STMLCDIF_24BIT); | |
110 | break; | |
111 | default: | |
112 | dev_err(drm->dev, "Unknown media bus format %d\n", bus_format); | |
113 | break; | |
114 | } | |
115 | writel(reg, mxsfb->base + LCDC_CTRL); | |
116 | } | |
117 | ||
45d59d70 MV |
118 | static void mxsfb_enable_controller(struct mxsfb_drm_private *mxsfb) |
119 | { | |
120 | u32 reg; | |
121 | ||
122 | if (mxsfb->clk_disp_axi) | |
123 | clk_prepare_enable(mxsfb->clk_disp_axi); | |
124 | clk_prepare_enable(mxsfb->clk); | |
125 | mxsfb_enable_axi_clk(mxsfb); | |
126 | ||
127 | /* If it was disabled, re-enable the mode again */ | |
128 | writel(CTRL_DOTCLK_MODE, mxsfb->base + LCDC_CTRL + REG_SET); | |
129 | ||
130 | /* Enable the SYNC signals first, then the DMA engine */ | |
131 | reg = readl(mxsfb->base + LCDC_VDCTRL4); | |
132 | reg |= VDCTRL4_SYNC_SIGNALS_ON; | |
133 | writel(reg, mxsfb->base + LCDC_VDCTRL4); | |
134 | ||
135 | writel(CTRL_RUN, mxsfb->base + LCDC_CTRL + REG_SET); | |
136 | } | |
137 | ||
138 | static void mxsfb_disable_controller(struct mxsfb_drm_private *mxsfb) | |
139 | { | |
140 | u32 reg; | |
141 | ||
142 | /* | |
143 | * Even if we disable the controller here, it will still continue | |
144 | * until its FIFOs are running out of data | |
145 | */ | |
146 | writel(CTRL_DOTCLK_MODE, mxsfb->base + LCDC_CTRL + REG_CLR); | |
147 | ||
148 | readl_poll_timeout(mxsfb->base + LCDC_CTRL, reg, !(reg & CTRL_RUN), | |
149 | 0, 1000); | |
150 | ||
151 | reg = readl(mxsfb->base + LCDC_VDCTRL4); | |
152 | reg &= ~VDCTRL4_SYNC_SIGNALS_ON; | |
153 | writel(reg, mxsfb->base + LCDC_VDCTRL4); | |
154 | ||
155 | mxsfb_disable_axi_clk(mxsfb); | |
156 | ||
157 | clk_disable_unprepare(mxsfb->clk); | |
158 | if (mxsfb->clk_disp_axi) | |
159 | clk_disable_unprepare(mxsfb->clk_disp_axi); | |
160 | } | |
161 | ||
162 | static void mxsfb_crtc_mode_set_nofb(struct mxsfb_drm_private *mxsfb) | |
163 | { | |
164 | struct drm_display_mode *m = &mxsfb->pipe.crtc.state->adjusted_mode; | |
165 | const u32 bus_flags = mxsfb->connector.display_info.bus_flags; | |
166 | u32 vdctrl0, vsync_pulse_len, hsync_pulse_len; | |
167 | int err; | |
168 | ||
169 | /* | |
170 | * It seems, you can't re-program the controller if it is still | |
171 | * running. This may lead to shifted pictures (FIFO issue?), so | |
172 | * first stop the controller and drain its FIFOs. | |
173 | */ | |
174 | mxsfb_enable_axi_clk(mxsfb); | |
175 | ||
176 | /* Clear the FIFOs */ | |
177 | writel(CTRL1_FIFO_CLEAR, mxsfb->base + LCDC_CTRL1 + REG_SET); | |
178 | ||
179 | err = mxsfb_set_pixel_fmt(mxsfb); | |
180 | if (err) | |
181 | return; | |
182 | ||
183 | clk_set_rate(mxsfb->clk, m->crtc_clock * 1000); | |
184 | ||
185 | writel(TRANSFER_COUNT_SET_VCOUNT(m->crtc_vdisplay) | | |
186 | TRANSFER_COUNT_SET_HCOUNT(m->crtc_hdisplay), | |
187 | mxsfb->base + mxsfb->devdata->transfer_count); | |
188 | ||
189 | vsync_pulse_len = m->crtc_vsync_end - m->crtc_vsync_start; | |
190 | ||
191 | vdctrl0 = VDCTRL0_ENABLE_PRESENT | /* Always in DOTCLOCK mode */ | |
192 | VDCTRL0_VSYNC_PERIOD_UNIT | | |
193 | VDCTRL0_VSYNC_PULSE_WIDTH_UNIT | | |
194 | VDCTRL0_SET_VSYNC_PULSE_WIDTH(vsync_pulse_len); | |
195 | if (m->flags & DRM_MODE_FLAG_PHSYNC) | |
196 | vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH; | |
197 | if (m->flags & DRM_MODE_FLAG_PVSYNC) | |
198 | vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH; | |
199 | if (bus_flags & DRM_BUS_FLAG_DE_HIGH) | |
200 | vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH; | |
201 | if (bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE) | |
202 | vdctrl0 |= VDCTRL0_DOTCLK_ACT_FALLING; | |
203 | ||
204 | writel(vdctrl0, mxsfb->base + LCDC_VDCTRL0); | |
205 | ||
10f2889b SA |
206 | mxsfb_set_bus_fmt(mxsfb); |
207 | ||
45d59d70 MV |
208 | /* Frame length in lines. */ |
209 | writel(m->crtc_vtotal, mxsfb->base + LCDC_VDCTRL1); | |
210 | ||
211 | /* Line length in units of clocks or pixels. */ | |
212 | hsync_pulse_len = m->crtc_hsync_end - m->crtc_hsync_start; | |
213 | writel(set_hsync_pulse_width(mxsfb, hsync_pulse_len) | | |
214 | VDCTRL2_SET_HSYNC_PERIOD(m->crtc_htotal), | |
215 | mxsfb->base + LCDC_VDCTRL2); | |
216 | ||
217 | writel(SET_HOR_WAIT_CNT(m->crtc_hblank_end - m->crtc_hsync_end) | | |
218 | SET_VERT_WAIT_CNT(m->crtc_vblank_end - m->crtc_vsync_end), | |
219 | mxsfb->base + LCDC_VDCTRL3); | |
220 | ||
221 | writel(SET_DOTCLK_H_VALID_DATA_CNT(m->hdisplay), | |
222 | mxsfb->base + LCDC_VDCTRL4); | |
223 | ||
224 | mxsfb_disable_axi_clk(mxsfb); | |
225 | } | |
226 | ||
227 | void mxsfb_crtc_enable(struct mxsfb_drm_private *mxsfb) | |
228 | { | |
229 | mxsfb_crtc_mode_set_nofb(mxsfb); | |
230 | mxsfb_enable_controller(mxsfb); | |
231 | } | |
232 | ||
233 | void mxsfb_crtc_disable(struct mxsfb_drm_private *mxsfb) | |
234 | { | |
235 | mxsfb_disable_controller(mxsfb); | |
236 | } | |
237 | ||
238 | void mxsfb_plane_atomic_update(struct mxsfb_drm_private *mxsfb, | |
239 | struct drm_plane_state *state) | |
240 | { | |
241 | struct drm_simple_display_pipe *pipe = &mxsfb->pipe; | |
242 | struct drm_crtc *crtc = &pipe->crtc; | |
243 | struct drm_framebuffer *fb = pipe->plane.state->fb; | |
244 | struct drm_pending_vblank_event *event; | |
245 | struct drm_gem_cma_object *gem; | |
246 | ||
247 | if (!crtc) | |
248 | return; | |
249 | ||
250 | spin_lock_irq(&crtc->dev->event_lock); | |
251 | event = crtc->state->event; | |
252 | if (event) { | |
253 | crtc->state->event = NULL; | |
254 | ||
255 | if (drm_crtc_vblank_get(crtc) == 0) { | |
256 | drm_crtc_arm_vblank_event(crtc, event); | |
257 | } else { | |
258 | drm_crtc_send_vblank_event(crtc, event); | |
259 | } | |
260 | } | |
261 | spin_unlock_irq(&crtc->dev->event_lock); | |
262 | ||
263 | if (!fb) | |
264 | return; | |
265 | ||
266 | gem = drm_fb_cma_get_gem_obj(fb, 0); | |
267 | ||
268 | mxsfb_enable_axi_clk(mxsfb); | |
269 | writel(gem->paddr, mxsfb->base + mxsfb->devdata->next_buf); | |
270 | mxsfb_disable_axi_clk(mxsfb); | |
271 | } |