1c87e859cSMarian Cichy // SPDX-License-Identifier: GPL-2.0-only 2c87e859cSMarian Cichy // SPDX-FileCopyrightText: 2020 Marian Cichy <M.Cichy@pengutronix.de> 3c87e859cSMarian Cichy 4*c6265f5cSDave Airlie #include <drm/drm_bridge.h> 5c87e859cSMarian Cichy #include <drm/drm_bridge_connector.h> 6c87e859cSMarian Cichy #include <drm/drm_damage_helper.h> 7c87e859cSMarian Cichy #include <drm/drm_drv.h> 8c87e859cSMarian Cichy #include <drm/drm_fbdev_generic.h> 9c87e859cSMarian Cichy #include <drm/drm_fb_dma_helper.h> 10c87e859cSMarian Cichy #include <drm/drm_fourcc.h> 11c87e859cSMarian Cichy #include <drm/drm_framebuffer.h> 12c87e859cSMarian Cichy #include <drm/drm_gem_atomic_helper.h> 13c87e859cSMarian Cichy #include <drm/drm_gem_dma_helper.h> 14c87e859cSMarian Cichy #include <drm/drm_gem_framebuffer_helper.h> 15c87e859cSMarian Cichy #include <drm/drm_of.h> 16c87e859cSMarian Cichy #include <drm/drm_probe_helper.h> 17c87e859cSMarian Cichy #include <drm/drm_simple_kms_helper.h> 18c87e859cSMarian Cichy #include <drm/drm_vblank.h> 19c87e859cSMarian Cichy #include <linux/bitfield.h> 20c87e859cSMarian Cichy #include <linux/clk.h> 21c87e859cSMarian Cichy #include <linux/dma-mapping.h> 22c87e859cSMarian Cichy #include <linux/module.h> 23c87e859cSMarian Cichy #include <linux/of_device.h> 24c87e859cSMarian Cichy #include <linux/platform_device.h> 25c87e859cSMarian Cichy 26c87e859cSMarian Cichy #define IMX21LCDC_LSSAR 0x0000 /* LCDC Screen Start Address Register */ 27c87e859cSMarian Cichy #define IMX21LCDC_LSR 0x0004 /* LCDC Size Register */ 28c87e859cSMarian Cichy #define IMX21LCDC_LVPWR 0x0008 /* LCDC Virtual Page Width Register */ 29c87e859cSMarian Cichy #define IMX21LCDC_LCPR 0x000C /* LCDC Cursor Position Register */ 30c87e859cSMarian Cichy #define IMX21LCDC_LCWHB 0x0010 /* LCDC Cursor Width Height and Blink Register*/ 31c87e859cSMarian Cichy #define IMX21LCDC_LCCMR 0x0014 /* LCDC Color Cursor Mapping Register */ 32c87e859cSMarian Cichy #define IMX21LCDC_LPCR 0x0018 /* LCDC Panel Configuration Register */ 33c87e859cSMarian Cichy #define IMX21LCDC_LHCR 0x001C /* LCDC Horizontal Configuration Register */ 34c87e859cSMarian Cichy #define IMX21LCDC_LVCR 0x0020 /* LCDC Vertical Configuration Register */ 35c87e859cSMarian Cichy #define IMX21LCDC_LPOR 0x0024 /* LCDC Panning Offset Register */ 36c87e859cSMarian Cichy #define IMX21LCDC_LSCR 0x0028 /* LCDC Sharp Configuration Register */ 37c87e859cSMarian Cichy #define IMX21LCDC_LPCCR 0x002C /* LCDC PWM Contrast Control Register */ 38c87e859cSMarian Cichy #define IMX21LCDC_LDCR 0x0030 /* LCDC DMA Control Register */ 39c87e859cSMarian Cichy #define IMX21LCDC_LRMCR 0x0034 /* LCDC Refresh Mode Control Register */ 40c87e859cSMarian Cichy #define IMX21LCDC_LICR 0x0038 /* LCDC Interrupt Configuration Register */ 41c87e859cSMarian Cichy #define IMX21LCDC_LIER 0x003C /* LCDC Interrupt Enable Register */ 42c87e859cSMarian Cichy #define IMX21LCDC_LISR 0x0040 /* LCDC Interrupt Status Register */ 43c87e859cSMarian Cichy #define IMX21LCDC_LGWSAR 0x0050 /* LCDC Graphic Window Start Address Register */ 44c87e859cSMarian Cichy #define IMX21LCDC_LGWSR 0x0054 /* LCDC Graph Window Size Register */ 45c87e859cSMarian Cichy #define IMX21LCDC_LGWVPWR 0x0058 /* LCDC Graphic Window Virtual Page Width Register */ 46c87e859cSMarian Cichy #define IMX21LCDC_LGWPOR 0x005C /* LCDC Graphic Window Panning Offset Register */ 47c87e859cSMarian Cichy #define IMX21LCDC_LGWPR 0x0060 /* LCDC Graphic Window Position Register */ 48c87e859cSMarian Cichy #define IMX21LCDC_LGWCR 0x0064 /* LCDC Graphic Window Control Register */ 49c87e859cSMarian Cichy #define IMX21LCDC_LGWDCR 0x0068 /* LCDC Graphic Window DMA Control Register */ 50c87e859cSMarian Cichy #define IMX21LCDC_LAUSCR 0x0080 /* LCDC AUS Mode Control Register */ 51c87e859cSMarian Cichy #define IMX21LCDC_LAUSCCR 0x0084 /* LCDC AUS Mode Cursor Control Register */ 52c87e859cSMarian Cichy #define IMX21LCDC_BGLUT 0x0800 /* Background Lookup Table */ 53c87e859cSMarian Cichy #define IMX21LCDC_GWLUT 0x0C00 /* Graphic Window Lookup Table */ 54c87e859cSMarian Cichy 55c87e859cSMarian Cichy #define IMX21LCDC_LCPR_CC0 BIT(30) /* Cursor Control Bit 0 */ 56c87e859cSMarian Cichy #define IMX21LCDC_LCPR_CC1 BIT(31) /* Cursor Control Bit 1 */ 57c87e859cSMarian Cichy 58c87e859cSMarian Cichy /* Values HSYNC, VSYNC and Framesize Register */ 59c87e859cSMarian Cichy #define IMX21LCDC_LHCR_HWIDTH GENMASK(31, 26) 60c87e859cSMarian Cichy #define IMX21LCDC_LHCR_HFPORCH GENMASK(15, 8) /* H_WAIT_1 in the i.MX25 Reference manual */ 61c87e859cSMarian Cichy #define IMX21LCDC_LHCR_HBPORCH GENMASK(7, 0) /* H_WAIT_2 in the i.MX25 Reference manual */ 62c87e859cSMarian Cichy 63c87e859cSMarian Cichy #define IMX21LCDC_LVCR_VWIDTH GENMASK(31, 26) 64c87e859cSMarian Cichy #define IMX21LCDC_LVCR_VFPORCH GENMASK(15, 8) /* V_WAIT_1 in the i.MX25 Reference manual */ 65c87e859cSMarian Cichy #define IMX21LCDC_LVCR_VBPORCH GENMASK(7, 0) /* V_WAIT_2 in the i.MX25 Reference manual */ 66c87e859cSMarian Cichy 67c87e859cSMarian Cichy #define IMX21LCDC_LSR_XMAX GENMASK(25, 20) 68c87e859cSMarian Cichy #define IMX21LCDC_LSR_YMAX GENMASK(9, 0) 69c87e859cSMarian Cichy 70c87e859cSMarian Cichy /* Values for LPCR Register */ 71c87e859cSMarian Cichy #define IMX21LCDC_LPCR_PCD GENMASK(5, 0) 72c87e859cSMarian Cichy #define IMX21LCDC_LPCR_SHARP BIT(6) 73c87e859cSMarian Cichy #define IMX21LCDC_LPCR_SCLKSEL BIT(7) 74c87e859cSMarian Cichy #define IMX21LCDC_LPCR_ACD GENMASK(14, 8) 75c87e859cSMarian Cichy #define IMX21LCDC_LPCR_ACDSEL BIT(15) 76c87e859cSMarian Cichy #define IMX21LCDC_LPCR_REV_VS BIT(16) 77c87e859cSMarian Cichy #define IMX21LCDC_LPCR_SWAP_SEL BIT(17) 78c87e859cSMarian Cichy #define IMX21LCDC_LPCR_END_SEL BIT(18) 79c87e859cSMarian Cichy #define IMX21LCDC_LPCR_SCLKIDLE BIT(19) 80c87e859cSMarian Cichy #define IMX21LCDC_LPCR_OEPOL BIT(20) 81c87e859cSMarian Cichy #define IMX21LCDC_LPCR_CLKPOL BIT(21) 82c87e859cSMarian Cichy #define IMX21LCDC_LPCR_LPPOL BIT(22) 83c87e859cSMarian Cichy #define IMX21LCDC_LPCR_FLMPOL BIT(23) 84c87e859cSMarian Cichy #define IMX21LCDC_LPCR_PIXPOL BIT(24) 85c87e859cSMarian Cichy #define IMX21LCDC_LPCR_BPIX GENMASK(27, 25) 86c87e859cSMarian Cichy #define IMX21LCDC_LPCR_PBSIZ GENMASK(29, 28) 87c87e859cSMarian Cichy #define IMX21LCDC_LPCR_COLOR BIT(30) 88c87e859cSMarian Cichy #define IMX21LCDC_LPCR_TFT BIT(31) 89c87e859cSMarian Cichy 90c87e859cSMarian Cichy #define INTR_EOF BIT(1) /* VBLANK Interrupt Bit */ 91c87e859cSMarian Cichy 92c87e859cSMarian Cichy #define BPP_RGB565 0x05 93c87e859cSMarian Cichy #define BPP_XRGB8888 0x07 94c87e859cSMarian Cichy 95c87e859cSMarian Cichy #define LCDC_MIN_XRES 64 96c87e859cSMarian Cichy #define LCDC_MIN_YRES 64 97c87e859cSMarian Cichy 98c87e859cSMarian Cichy #define LCDC_MAX_XRES 1024 99c87e859cSMarian Cichy #define LCDC_MAX_YRES 1024 100c87e859cSMarian Cichy 101c87e859cSMarian Cichy struct imx_lcdc { 102c87e859cSMarian Cichy struct drm_device drm; 103c87e859cSMarian Cichy struct drm_simple_display_pipe pipe; 104c87e859cSMarian Cichy struct drm_connector *connector; 105c87e859cSMarian Cichy void __iomem *base; 106c87e859cSMarian Cichy 107c87e859cSMarian Cichy struct clk *clk_ipg; 108c87e859cSMarian Cichy struct clk *clk_ahb; 109c87e859cSMarian Cichy struct clk *clk_per; 110c87e859cSMarian Cichy }; 111c87e859cSMarian Cichy 112c87e859cSMarian Cichy static const u32 imx_lcdc_formats[] = { 113c87e859cSMarian Cichy DRM_FORMAT_RGB565, DRM_FORMAT_XRGB8888, 114c87e859cSMarian Cichy }; 115c87e859cSMarian Cichy 116c87e859cSMarian Cichy static inline struct imx_lcdc *imx_lcdc_from_drmdev(struct drm_device *drm) 117c87e859cSMarian Cichy { 118c87e859cSMarian Cichy return container_of(drm, struct imx_lcdc, drm); 119c87e859cSMarian Cichy } 120c87e859cSMarian Cichy 121c87e859cSMarian Cichy static unsigned int imx_lcdc_get_format(unsigned int drm_format) 122c87e859cSMarian Cichy { 123c87e859cSMarian Cichy switch (drm_format) { 124c87e859cSMarian Cichy default: 125c87e859cSMarian Cichy DRM_WARN("Format not supported - fallback to XRGB8888\n"); 126c87e859cSMarian Cichy fallthrough; 127c87e859cSMarian Cichy 128c87e859cSMarian Cichy case DRM_FORMAT_XRGB8888: 129c87e859cSMarian Cichy return BPP_XRGB8888; 130c87e859cSMarian Cichy 131c87e859cSMarian Cichy case DRM_FORMAT_RGB565: 132c87e859cSMarian Cichy return BPP_RGB565; 133c87e859cSMarian Cichy } 134c87e859cSMarian Cichy } 135c87e859cSMarian Cichy 136c87e859cSMarian Cichy static void imx_lcdc_update_hw_registers(struct drm_simple_display_pipe *pipe, 137c87e859cSMarian Cichy struct drm_plane_state *old_state, 138c87e859cSMarian Cichy bool mode_set) 139c87e859cSMarian Cichy { 140c87e859cSMarian Cichy struct drm_crtc *crtc = &pipe->crtc; 141c87e859cSMarian Cichy struct drm_plane_state *new_state = pipe->plane.state; 142c87e859cSMarian Cichy struct drm_framebuffer *fb = new_state->fb; 143c87e859cSMarian Cichy struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(pipe->crtc.dev); 144c87e859cSMarian Cichy u32 lpcr, lvcr, lhcr; 145c87e859cSMarian Cichy u32 framesize; 146c87e859cSMarian Cichy dma_addr_t addr; 147c87e859cSMarian Cichy 148c87e859cSMarian Cichy addr = drm_fb_dma_get_gem_addr(fb, new_state, 0); 149c87e859cSMarian Cichy /* The LSSAR register specifies the LCD screen start address (SSA). */ 150c87e859cSMarian Cichy writel(addr, lcdc->base + IMX21LCDC_LSSAR); 151c87e859cSMarian Cichy 152c87e859cSMarian Cichy if (!mode_set) 153c87e859cSMarian Cichy return; 154c87e859cSMarian Cichy 155c87e859cSMarian Cichy /* Disable PER clock to make register write possible */ 156c87e859cSMarian Cichy if (old_state && old_state->crtc && old_state->crtc->enabled) 157c87e859cSMarian Cichy clk_disable_unprepare(lcdc->clk_per); 158c87e859cSMarian Cichy 159c87e859cSMarian Cichy /* Framesize */ 160c87e859cSMarian Cichy framesize = FIELD_PREP(IMX21LCDC_LSR_XMAX, crtc->mode.hdisplay >> 4) | 161c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LSR_YMAX, crtc->mode.vdisplay); 162c87e859cSMarian Cichy writel(framesize, lcdc->base + IMX21LCDC_LSR); 163c87e859cSMarian Cichy 164c87e859cSMarian Cichy /* HSYNC */ 165c87e859cSMarian Cichy lhcr = FIELD_PREP(IMX21LCDC_LHCR_HFPORCH, crtc->mode.hsync_start - crtc->mode.hdisplay - 1) | 166c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LHCR_HWIDTH, crtc->mode.hsync_end - crtc->mode.hsync_start - 1) | 167c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LHCR_HBPORCH, crtc->mode.htotal - crtc->mode.hsync_end - 3); 168c87e859cSMarian Cichy writel(lhcr, lcdc->base + IMX21LCDC_LHCR); 169c87e859cSMarian Cichy 170c87e859cSMarian Cichy /* VSYNC */ 171c87e859cSMarian Cichy lvcr = FIELD_PREP(IMX21LCDC_LVCR_VFPORCH, crtc->mode.vsync_start - crtc->mode.vdisplay) | 172c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LVCR_VWIDTH, crtc->mode.vsync_end - crtc->mode.vsync_start) | 173c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LVCR_VBPORCH, crtc->mode.vtotal - crtc->mode.vsync_end); 174c87e859cSMarian Cichy writel(lvcr, lcdc->base + IMX21LCDC_LVCR); 175c87e859cSMarian Cichy 176c87e859cSMarian Cichy lpcr = readl(lcdc->base + IMX21LCDC_LPCR); 177c87e859cSMarian Cichy lpcr &= ~IMX21LCDC_LPCR_BPIX; 178c87e859cSMarian Cichy lpcr |= FIELD_PREP(IMX21LCDC_LPCR_BPIX, imx_lcdc_get_format(fb->format->format)); 179c87e859cSMarian Cichy writel(lpcr, lcdc->base + IMX21LCDC_LPCR); 180c87e859cSMarian Cichy 181c87e859cSMarian Cichy /* Virtual Page Width */ 182c87e859cSMarian Cichy writel(new_state->fb->pitches[0] / 4, lcdc->base + IMX21LCDC_LVPWR); 183c87e859cSMarian Cichy 184c87e859cSMarian Cichy /* Enable PER clock */ 185c87e859cSMarian Cichy if (new_state->crtc->enabled) 186c87e859cSMarian Cichy clk_prepare_enable(lcdc->clk_per); 187c87e859cSMarian Cichy } 188c87e859cSMarian Cichy 189c87e859cSMarian Cichy static void imx_lcdc_pipe_enable(struct drm_simple_display_pipe *pipe, 190c87e859cSMarian Cichy struct drm_crtc_state *crtc_state, 191c87e859cSMarian Cichy struct drm_plane_state *plane_state) 192c87e859cSMarian Cichy { 193c87e859cSMarian Cichy int ret; 194c87e859cSMarian Cichy int clk_div; 195c87e859cSMarian Cichy int bpp; 196c87e859cSMarian Cichy struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(pipe->crtc.dev); 197c87e859cSMarian Cichy struct drm_display_mode *mode = &pipe->crtc.mode; 198c87e859cSMarian Cichy struct drm_display_info *disp_info = &lcdc->connector->display_info; 199c87e859cSMarian Cichy const int hsync_pol = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : 1; 200c87e859cSMarian Cichy const int vsync_pol = (mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : 1; 201c87e859cSMarian Cichy const int data_enable_pol = 202c87e859cSMarian Cichy (disp_info->bus_flags & DRM_BUS_FLAG_DE_HIGH) ? 0 : 1; 203c87e859cSMarian Cichy const int clk_pol = 204c87e859cSMarian Cichy (disp_info->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE) ? 0 : 1; 205c87e859cSMarian Cichy 206c87e859cSMarian Cichy clk_div = DIV_ROUND_CLOSEST_ULL(clk_get_rate(lcdc->clk_per), 207c87e859cSMarian Cichy mode->clock * 1000); 208c87e859cSMarian Cichy bpp = imx_lcdc_get_format(plane_state->fb->format->format); 209c87e859cSMarian Cichy 210c87e859cSMarian Cichy writel(FIELD_PREP(IMX21LCDC_LPCR_PCD, clk_div - 1) | 211c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LPCR_LPPOL, hsync_pol) | 212c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LPCR_FLMPOL, vsync_pol) | 213c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LPCR_OEPOL, data_enable_pol) | 214c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LPCR_TFT, 1) | 215c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LPCR_COLOR, 1) | 216c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LPCR_PBSIZ, 3) | 217c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LPCR_BPIX, bpp) | 218c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LPCR_SCLKSEL, 1) | 219c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LPCR_PIXPOL, 0) | 220c87e859cSMarian Cichy FIELD_PREP(IMX21LCDC_LPCR_CLKPOL, clk_pol), 221c87e859cSMarian Cichy lcdc->base + IMX21LCDC_LPCR); 222c87e859cSMarian Cichy 223c87e859cSMarian Cichy /* 0px panning offset */ 224c87e859cSMarian Cichy writel(0x00000000, lcdc->base + IMX21LCDC_LPOR); 225c87e859cSMarian Cichy 226c87e859cSMarian Cichy /* disable hardware cursor */ 227c87e859cSMarian Cichy writel(readl(lcdc->base + IMX21LCDC_LCPR) & ~(IMX21LCDC_LCPR_CC0 | IMX21LCDC_LCPR_CC1), 228c87e859cSMarian Cichy lcdc->base + IMX21LCDC_LCPR); 229c87e859cSMarian Cichy 230c87e859cSMarian Cichy ret = clk_prepare_enable(lcdc->clk_ipg); 231c87e859cSMarian Cichy if (ret) { 232c87e859cSMarian Cichy dev_err(pipe->crtc.dev->dev, "Cannot enable ipg clock: %pe\n", ERR_PTR(ret)); 233c87e859cSMarian Cichy return; 234c87e859cSMarian Cichy } 235c87e859cSMarian Cichy ret = clk_prepare_enable(lcdc->clk_ahb); 236c87e859cSMarian Cichy if (ret) { 237c87e859cSMarian Cichy dev_err(pipe->crtc.dev->dev, "Cannot enable ahb clock: %pe\n", ERR_PTR(ret)); 238c87e859cSMarian Cichy 239c87e859cSMarian Cichy clk_disable_unprepare(lcdc->clk_ipg); 240c87e859cSMarian Cichy 241c87e859cSMarian Cichy return; 242c87e859cSMarian Cichy } 243c87e859cSMarian Cichy 244c87e859cSMarian Cichy imx_lcdc_update_hw_registers(pipe, NULL, true); 245c87e859cSMarian Cichy 246c87e859cSMarian Cichy /* Enable VBLANK Interrupt */ 247c87e859cSMarian Cichy writel(INTR_EOF, lcdc->base + IMX21LCDC_LIER); 248c87e859cSMarian Cichy } 249c87e859cSMarian Cichy 250c87e859cSMarian Cichy static void imx_lcdc_pipe_disable(struct drm_simple_display_pipe *pipe) 251c87e859cSMarian Cichy { 252c87e859cSMarian Cichy struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(pipe->crtc.dev); 253c87e859cSMarian Cichy struct drm_crtc *crtc = &lcdc->pipe.crtc; 254c87e859cSMarian Cichy struct drm_pending_vblank_event *event; 255c87e859cSMarian Cichy 256c87e859cSMarian Cichy clk_disable_unprepare(lcdc->clk_ahb); 257c87e859cSMarian Cichy clk_disable_unprepare(lcdc->clk_ipg); 258c87e859cSMarian Cichy 259c87e859cSMarian Cichy if (pipe->crtc.enabled) 260c87e859cSMarian Cichy clk_disable_unprepare(lcdc->clk_per); 261c87e859cSMarian Cichy 262c87e859cSMarian Cichy spin_lock_irq(&lcdc->drm.event_lock); 263c87e859cSMarian Cichy event = crtc->state->event; 264c87e859cSMarian Cichy if (event) { 265c87e859cSMarian Cichy crtc->state->event = NULL; 266c87e859cSMarian Cichy drm_crtc_send_vblank_event(crtc, event); 267c87e859cSMarian Cichy } 268c87e859cSMarian Cichy spin_unlock_irq(&lcdc->drm.event_lock); 269c87e859cSMarian Cichy 270c87e859cSMarian Cichy /* Disable VBLANK Interrupt */ 271c87e859cSMarian Cichy writel(0, lcdc->base + IMX21LCDC_LIER); 272c87e859cSMarian Cichy } 273c87e859cSMarian Cichy 274c87e859cSMarian Cichy static int imx_lcdc_pipe_check(struct drm_simple_display_pipe *pipe, 275c87e859cSMarian Cichy struct drm_plane_state *plane_state, 276c87e859cSMarian Cichy struct drm_crtc_state *crtc_state) 277c87e859cSMarian Cichy { 278c87e859cSMarian Cichy const struct drm_display_mode *mode = &crtc_state->mode; 279c87e859cSMarian Cichy const struct drm_display_mode *old_mode = &pipe->crtc.state->mode; 280c87e859cSMarian Cichy 281c87e859cSMarian Cichy if (mode->hdisplay < LCDC_MIN_XRES || mode->hdisplay > LCDC_MAX_XRES || 282c87e859cSMarian Cichy mode->vdisplay < LCDC_MIN_YRES || mode->vdisplay > LCDC_MAX_YRES || 283c87e859cSMarian Cichy mode->hdisplay % 0x10) { /* must be multiple of 16 */ 284c87e859cSMarian Cichy drm_err(pipe->crtc.dev, "unsupported display mode (%u x %u)\n", 285c87e859cSMarian Cichy mode->hdisplay, mode->vdisplay); 286c87e859cSMarian Cichy return -EINVAL; 287c87e859cSMarian Cichy } 288c87e859cSMarian Cichy 289c87e859cSMarian Cichy crtc_state->mode_changed = 290c87e859cSMarian Cichy old_mode->hdisplay != mode->hdisplay || 291c87e859cSMarian Cichy old_mode->vdisplay != mode->vdisplay; 292c87e859cSMarian Cichy 293c87e859cSMarian Cichy return 0; 294c87e859cSMarian Cichy } 295c87e859cSMarian Cichy 296c87e859cSMarian Cichy static void imx_lcdc_pipe_update(struct drm_simple_display_pipe *pipe, 297c87e859cSMarian Cichy struct drm_plane_state *old_state) 298c87e859cSMarian Cichy { 299c87e859cSMarian Cichy struct drm_crtc *crtc = &pipe->crtc; 300c87e859cSMarian Cichy struct drm_pending_vblank_event *event = crtc->state->event; 301c87e859cSMarian Cichy struct drm_plane_state *new_state = pipe->plane.state; 302c87e859cSMarian Cichy struct drm_framebuffer *fb = new_state->fb; 303c87e859cSMarian Cichy struct drm_framebuffer *old_fb = old_state->fb; 304c87e859cSMarian Cichy struct drm_crtc *old_crtc = old_state->crtc; 305c87e859cSMarian Cichy bool mode_changed = false; 306c87e859cSMarian Cichy 307c87e859cSMarian Cichy if (old_fb && old_fb->format != fb->format) 308c87e859cSMarian Cichy mode_changed = true; 309c87e859cSMarian Cichy else if (old_crtc != crtc) 310c87e859cSMarian Cichy mode_changed = true; 311c87e859cSMarian Cichy 312c87e859cSMarian Cichy imx_lcdc_update_hw_registers(pipe, old_state, mode_changed); 313c87e859cSMarian Cichy 314c87e859cSMarian Cichy if (event) { 315c87e859cSMarian Cichy crtc->state->event = NULL; 316c87e859cSMarian Cichy 317c87e859cSMarian Cichy spin_lock_irq(&crtc->dev->event_lock); 318c87e859cSMarian Cichy 319c87e859cSMarian Cichy if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0) 320c87e859cSMarian Cichy drm_crtc_arm_vblank_event(crtc, event); 321c87e859cSMarian Cichy else 322c87e859cSMarian Cichy drm_crtc_send_vblank_event(crtc, event); 323c87e859cSMarian Cichy 324c87e859cSMarian Cichy spin_unlock_irq(&crtc->dev->event_lock); 325c87e859cSMarian Cichy } 326c87e859cSMarian Cichy } 327c87e859cSMarian Cichy 328c87e859cSMarian Cichy static const struct drm_simple_display_pipe_funcs imx_lcdc_pipe_funcs = { 329c87e859cSMarian Cichy .enable = imx_lcdc_pipe_enable, 330c87e859cSMarian Cichy .disable = imx_lcdc_pipe_disable, 331c87e859cSMarian Cichy .check = imx_lcdc_pipe_check, 332c87e859cSMarian Cichy .update = imx_lcdc_pipe_update, 333c87e859cSMarian Cichy }; 334c87e859cSMarian Cichy 335c87e859cSMarian Cichy static const struct drm_mode_config_funcs imx_lcdc_mode_config_funcs = { 336c87e859cSMarian Cichy .fb_create = drm_gem_fb_create_with_dirty, 337c87e859cSMarian Cichy .atomic_check = drm_atomic_helper_check, 338c87e859cSMarian Cichy .atomic_commit = drm_atomic_helper_commit, 339c87e859cSMarian Cichy }; 340c87e859cSMarian Cichy 341c87e859cSMarian Cichy static const struct drm_mode_config_helper_funcs imx_lcdc_mode_config_helpers = { 342c87e859cSMarian Cichy .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, 343c87e859cSMarian Cichy }; 344c87e859cSMarian Cichy 345c87e859cSMarian Cichy static void imx_lcdc_release(struct drm_device *drm) 346c87e859cSMarian Cichy { 347c87e859cSMarian Cichy struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(drm); 348c87e859cSMarian Cichy 349c87e859cSMarian Cichy drm_kms_helper_poll_fini(drm); 350c87e859cSMarian Cichy kfree(lcdc); 351c87e859cSMarian Cichy } 352c87e859cSMarian Cichy 353c87e859cSMarian Cichy DEFINE_DRM_GEM_DMA_FOPS(imx_lcdc_drm_fops); 354c87e859cSMarian Cichy 355c87e859cSMarian Cichy static struct drm_driver imx_lcdc_drm_driver = { 356c87e859cSMarian Cichy .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, 357c87e859cSMarian Cichy .fops = &imx_lcdc_drm_fops, 358c87e859cSMarian Cichy DRM_GEM_DMA_DRIVER_OPS_VMAP, 359c87e859cSMarian Cichy .release = imx_lcdc_release, 360c87e859cSMarian Cichy .name = "imx-lcdc", 361c87e859cSMarian Cichy .desc = "i.MX LCDC driver", 362c87e859cSMarian Cichy .date = "20200716", 363c87e859cSMarian Cichy }; 364c87e859cSMarian Cichy 365c87e859cSMarian Cichy static const struct of_device_id imx_lcdc_of_dev_id[] = { 366c87e859cSMarian Cichy { 367c87e859cSMarian Cichy .compatible = "fsl,imx21-lcdc", 368c87e859cSMarian Cichy }, 369c87e859cSMarian Cichy { 370c87e859cSMarian Cichy .compatible = "fsl,imx25-lcdc", 371c87e859cSMarian Cichy }, 372c87e859cSMarian Cichy { /* sentinel */ } 373c87e859cSMarian Cichy }; 374c87e859cSMarian Cichy MODULE_DEVICE_TABLE(of, imx_lcdc_of_dev_id); 375c87e859cSMarian Cichy 376c87e859cSMarian Cichy static irqreturn_t imx_lcdc_irq_handler(int irq, void *arg) 377c87e859cSMarian Cichy { 378c87e859cSMarian Cichy struct imx_lcdc *lcdc = arg; 379c87e859cSMarian Cichy struct drm_crtc *crtc = &lcdc->pipe.crtc; 380c87e859cSMarian Cichy unsigned int status; 381c87e859cSMarian Cichy 382c87e859cSMarian Cichy status = readl(lcdc->base + IMX21LCDC_LISR); 383c87e859cSMarian Cichy 384c87e859cSMarian Cichy if (status & INTR_EOF) { 385c87e859cSMarian Cichy drm_crtc_handle_vblank(crtc); 386c87e859cSMarian Cichy return IRQ_HANDLED; 387c87e859cSMarian Cichy } 388c87e859cSMarian Cichy 389c87e859cSMarian Cichy return IRQ_NONE; 390c87e859cSMarian Cichy } 391c87e859cSMarian Cichy 392c87e859cSMarian Cichy static int imx_lcdc_probe(struct platform_device *pdev) 393c87e859cSMarian Cichy { 394c87e859cSMarian Cichy struct imx_lcdc *lcdc; 395c87e859cSMarian Cichy struct drm_device *drm; 396c87e859cSMarian Cichy struct drm_bridge *bridge; 397c87e859cSMarian Cichy int irq; 398c87e859cSMarian Cichy int ret; 399c87e859cSMarian Cichy struct device *dev = &pdev->dev; 400c87e859cSMarian Cichy 401c87e859cSMarian Cichy lcdc = devm_drm_dev_alloc(dev, &imx_lcdc_drm_driver, 402c87e859cSMarian Cichy struct imx_lcdc, drm); 403c87e859cSMarian Cichy if (!lcdc) 404c87e859cSMarian Cichy return -ENOMEM; 405c87e859cSMarian Cichy 406c87e859cSMarian Cichy drm = &lcdc->drm; 407c87e859cSMarian Cichy 408c87e859cSMarian Cichy lcdc->base = devm_platform_ioremap_resource(pdev, 0); 409c87e859cSMarian Cichy if (IS_ERR(lcdc->base)) 410c87e859cSMarian Cichy return dev_err_probe(dev, PTR_ERR(lcdc->base), "Cannot get IO memory\n"); 411c87e859cSMarian Cichy 412c87e859cSMarian Cichy bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0); 413c87e859cSMarian Cichy if (IS_ERR(bridge)) 414c87e859cSMarian Cichy return dev_err_probe(dev, PTR_ERR(bridge), "Failed to find bridge\n"); 415c87e859cSMarian Cichy 416c87e859cSMarian Cichy /* Get Clocks */ 417c87e859cSMarian Cichy lcdc->clk_ipg = devm_clk_get(dev, "ipg"); 418c87e859cSMarian Cichy if (IS_ERR(lcdc->clk_ipg)) 419c87e859cSMarian Cichy return dev_err_probe(dev, PTR_ERR(lcdc->clk_ipg), "Failed to get %s clk\n", "ipg"); 420c87e859cSMarian Cichy 421c87e859cSMarian Cichy lcdc->clk_ahb = devm_clk_get(dev, "ahb"); 422c87e859cSMarian Cichy if (IS_ERR(lcdc->clk_ahb)) 423c87e859cSMarian Cichy return dev_err_probe(dev, PTR_ERR(lcdc->clk_ahb), "Failed to get %s clk\n", "ahb"); 424c87e859cSMarian Cichy 425c87e859cSMarian Cichy lcdc->clk_per = devm_clk_get(dev, "per"); 426c87e859cSMarian Cichy if (IS_ERR(lcdc->clk_per)) 427c87e859cSMarian Cichy return dev_err_probe(dev, PTR_ERR(lcdc->clk_per), "Failed to get %s clk\n", "per"); 428c87e859cSMarian Cichy 429c87e859cSMarian Cichy ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32)); 430c87e859cSMarian Cichy if (ret) 431c87e859cSMarian Cichy return dev_err_probe(dev, ret, "Cannot set DMA Mask\n"); 432c87e859cSMarian Cichy 433c87e859cSMarian Cichy /* Modeset init */ 434c87e859cSMarian Cichy ret = drmm_mode_config_init(drm); 435c87e859cSMarian Cichy if (ret) 436c87e859cSMarian Cichy return dev_err_probe(dev, ret, "Cannot initialize mode configuration structure\n"); 437c87e859cSMarian Cichy 438c87e859cSMarian Cichy /* CRTC, Plane, Encoder */ 439c87e859cSMarian Cichy ret = drm_simple_display_pipe_init(drm, &lcdc->pipe, 440c87e859cSMarian Cichy &imx_lcdc_pipe_funcs, 441c87e859cSMarian Cichy imx_lcdc_formats, 442c87e859cSMarian Cichy ARRAY_SIZE(imx_lcdc_formats), NULL, NULL); 443c87e859cSMarian Cichy if (ret < 0) 444c87e859cSMarian Cichy return dev_err_probe(drm->dev, ret, "Cannot setup simple display pipe\n"); 445c87e859cSMarian Cichy 446c87e859cSMarian Cichy ret = drm_vblank_init(drm, drm->mode_config.num_crtc); 447c87e859cSMarian Cichy if (ret < 0) 448c87e859cSMarian Cichy return dev_err_probe(drm->dev, ret, "Failed to initialize vblank\n"); 449c87e859cSMarian Cichy 450c87e859cSMarian Cichy ret = drm_bridge_attach(&lcdc->pipe.encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); 451c87e859cSMarian Cichy if (ret) 452c87e859cSMarian Cichy return dev_err_probe(drm->dev, ret, "Cannot attach bridge\n"); 453c87e859cSMarian Cichy 454c87e859cSMarian Cichy lcdc->connector = drm_bridge_connector_init(drm, &lcdc->pipe.encoder); 455c87e859cSMarian Cichy if (IS_ERR(lcdc->connector)) 456c87e859cSMarian Cichy return dev_err_probe(drm->dev, PTR_ERR(lcdc->connector), "Cannot init bridge connector\n"); 457c87e859cSMarian Cichy 458c87e859cSMarian Cichy drm_connector_attach_encoder(lcdc->connector, &lcdc->pipe.encoder); 459c87e859cSMarian Cichy 460c87e859cSMarian Cichy /* 461c87e859cSMarian Cichy * The LCDC controller does not have an enable bit. The 462c87e859cSMarian Cichy * controller starts directly when the clocks are enabled. 463c87e859cSMarian Cichy * If the clocks are enabled when the controller is not yet 464c87e859cSMarian Cichy * programmed with proper register values (enabled at the 465c87e859cSMarian Cichy * bootloader, for example) then it just goes into some undefined 466c87e859cSMarian Cichy * state. 467c87e859cSMarian Cichy * To avoid this issue, let's enable and disable LCDC IPG, 468c87e859cSMarian Cichy * PER and AHB clock so that we force some kind of 'reset' 469c87e859cSMarian Cichy * to the LCDC block. 470c87e859cSMarian Cichy */ 471c87e859cSMarian Cichy 472c87e859cSMarian Cichy ret = clk_prepare_enable(lcdc->clk_ipg); 473c87e859cSMarian Cichy if (ret) 474c87e859cSMarian Cichy return dev_err_probe(dev, ret, "Cannot enable ipg clock\n"); 475c87e859cSMarian Cichy clk_disable_unprepare(lcdc->clk_ipg); 476c87e859cSMarian Cichy 477c87e859cSMarian Cichy ret = clk_prepare_enable(lcdc->clk_per); 478c87e859cSMarian Cichy if (ret) 479c87e859cSMarian Cichy return dev_err_probe(dev, ret, "Cannot enable per clock\n"); 480c87e859cSMarian Cichy clk_disable_unprepare(lcdc->clk_per); 481c87e859cSMarian Cichy 482c87e859cSMarian Cichy ret = clk_prepare_enable(lcdc->clk_ahb); 483c87e859cSMarian Cichy if (ret) 484c87e859cSMarian Cichy return dev_err_probe(dev, ret, "Cannot enable ahb clock\n"); 485c87e859cSMarian Cichy clk_disable_unprepare(lcdc->clk_ahb); 486c87e859cSMarian Cichy 487c87e859cSMarian Cichy drm->mode_config.min_width = LCDC_MIN_XRES; 488c87e859cSMarian Cichy drm->mode_config.max_width = LCDC_MAX_XRES; 489c87e859cSMarian Cichy drm->mode_config.min_height = LCDC_MIN_YRES; 490c87e859cSMarian Cichy drm->mode_config.max_height = LCDC_MAX_YRES; 491c87e859cSMarian Cichy drm->mode_config.preferred_depth = 16; 492c87e859cSMarian Cichy drm->mode_config.funcs = &imx_lcdc_mode_config_funcs; 493c87e859cSMarian Cichy drm->mode_config.helper_private = &imx_lcdc_mode_config_helpers; 494c87e859cSMarian Cichy 495c87e859cSMarian Cichy drm_mode_config_reset(drm); 496c87e859cSMarian Cichy 497c87e859cSMarian Cichy irq = platform_get_irq(pdev, 0); 498c87e859cSMarian Cichy if (irq < 0) { 499c87e859cSMarian Cichy ret = irq; 500c87e859cSMarian Cichy return ret; 501c87e859cSMarian Cichy } 502c87e859cSMarian Cichy 503c87e859cSMarian Cichy ret = devm_request_irq(dev, irq, imx_lcdc_irq_handler, 0, "imx-lcdc", lcdc); 504c87e859cSMarian Cichy if (ret < 0) 505c87e859cSMarian Cichy return dev_err_probe(drm->dev, ret, "Failed to install IRQ handler\n"); 506c87e859cSMarian Cichy 507c87e859cSMarian Cichy platform_set_drvdata(pdev, drm); 508c87e859cSMarian Cichy 509c87e859cSMarian Cichy ret = drm_dev_register(&lcdc->drm, 0); 510c87e859cSMarian Cichy if (ret) 511c87e859cSMarian Cichy return dev_err_probe(dev, ret, "Cannot register device\n"); 512c87e859cSMarian Cichy 513c87e859cSMarian Cichy drm_fbdev_generic_setup(drm, 0); 514c87e859cSMarian Cichy 515c87e859cSMarian Cichy return 0; 516c87e859cSMarian Cichy } 517c87e859cSMarian Cichy 518c87e859cSMarian Cichy static int imx_lcdc_remove(struct platform_device *pdev) 519c87e859cSMarian Cichy { 520c87e859cSMarian Cichy struct drm_device *drm = platform_get_drvdata(pdev); 521c87e859cSMarian Cichy 522c87e859cSMarian Cichy drm_dev_unregister(drm); 523c87e859cSMarian Cichy drm_atomic_helper_shutdown(drm); 524c87e859cSMarian Cichy 525c87e859cSMarian Cichy return 0; 526c87e859cSMarian Cichy } 527c87e859cSMarian Cichy 528c87e859cSMarian Cichy static void imx_lcdc_shutdown(struct platform_device *pdev) 529c87e859cSMarian Cichy { 530c87e859cSMarian Cichy drm_atomic_helper_shutdown(platform_get_drvdata(pdev)); 531c87e859cSMarian Cichy } 532c87e859cSMarian Cichy 533c87e859cSMarian Cichy static struct platform_driver imx_lcdc_driver = { 534c87e859cSMarian Cichy .driver = { 535c87e859cSMarian Cichy .name = "imx-lcdc", 536c87e859cSMarian Cichy .of_match_table = imx_lcdc_of_dev_id, 537c87e859cSMarian Cichy }, 538c87e859cSMarian Cichy .probe = imx_lcdc_probe, 539c87e859cSMarian Cichy .remove = imx_lcdc_remove, 540c87e859cSMarian Cichy .shutdown = imx_lcdc_shutdown, 541c87e859cSMarian Cichy }; 542c87e859cSMarian Cichy module_platform_driver(imx_lcdc_driver); 543c87e859cSMarian Cichy 544c87e859cSMarian Cichy MODULE_AUTHOR("Marian Cichy <M.Cichy@pengutronix.de>"); 545c87e859cSMarian Cichy MODULE_DESCRIPTION("Freescale i.MX LCDC driver"); 546c87e859cSMarian Cichy MODULE_LICENSE("GPL"); 547