19026e0d1SMaxime Ripard /* 29026e0d1SMaxime Ripard * Copyright (C) 2015 Free Electrons 39026e0d1SMaxime Ripard * Copyright (C) 2015 NextThing Co 49026e0d1SMaxime Ripard * 59026e0d1SMaxime Ripard * Maxime Ripard <maxime.ripard@free-electrons.com> 69026e0d1SMaxime Ripard * 79026e0d1SMaxime Ripard * This program is free software; you can redistribute it and/or 89026e0d1SMaxime Ripard * modify it under the terms of the GNU General Public License as 99026e0d1SMaxime Ripard * published by the Free Software Foundation; either version 2 of 109026e0d1SMaxime Ripard * the License, or (at your option) any later version. 119026e0d1SMaxime Ripard */ 129026e0d1SMaxime Ripard 139026e0d1SMaxime Ripard #include <drm/drmP.h> 149026e0d1SMaxime Ripard #include <drm/drm_atomic_helper.h> 159026e0d1SMaxime Ripard #include <drm/drm_crtc.h> 169026e0d1SMaxime Ripard #include <drm/drm_crtc_helper.h> 17ad537fb2SChen-Yu Tsai #include <drm/drm_encoder.h> 189026e0d1SMaxime Ripard #include <drm/drm_modes.h> 19ebc94461SRob Herring #include <drm/drm_of.h> 209026e0d1SMaxime Ripard 21ad537fb2SChen-Yu Tsai #include <uapi/drm/drm_mode.h> 22ad537fb2SChen-Yu Tsai 239026e0d1SMaxime Ripard #include <linux/component.h> 249026e0d1SMaxime Ripard #include <linux/ioport.h> 259026e0d1SMaxime Ripard #include <linux/of_address.h> 2691ea2f29SChen-Yu Tsai #include <linux/of_device.h> 279026e0d1SMaxime Ripard #include <linux/of_irq.h> 289026e0d1SMaxime Ripard #include <linux/regmap.h> 299026e0d1SMaxime Ripard #include <linux/reset.h> 309026e0d1SMaxime Ripard 319026e0d1SMaxime Ripard #include "sun4i_crtc.h" 329026e0d1SMaxime Ripard #include "sun4i_dotclock.h" 339026e0d1SMaxime Ripard #include "sun4i_drv.h" 3429e57fabSMaxime Ripard #include "sun4i_rgb.h" 359026e0d1SMaxime Ripard #include "sun4i_tcon.h" 3687969338SIcenowy Zheng #include "sunxi_engine.h" 379026e0d1SMaxime Ripard 389026e0d1SMaxime Ripard void sun4i_tcon_disable(struct sun4i_tcon *tcon) 399026e0d1SMaxime Ripard { 409026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Disabling TCON\n"); 419026e0d1SMaxime Ripard 429026e0d1SMaxime Ripard /* Disable the TCON */ 439026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, 449026e0d1SMaxime Ripard SUN4I_TCON_GCTL_TCON_ENABLE, 0); 459026e0d1SMaxime Ripard } 469026e0d1SMaxime Ripard EXPORT_SYMBOL(sun4i_tcon_disable); 479026e0d1SMaxime Ripard 489026e0d1SMaxime Ripard void sun4i_tcon_enable(struct sun4i_tcon *tcon) 499026e0d1SMaxime Ripard { 509026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Enabling TCON\n"); 519026e0d1SMaxime Ripard 529026e0d1SMaxime Ripard /* Enable the TCON */ 539026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, 549026e0d1SMaxime Ripard SUN4I_TCON_GCTL_TCON_ENABLE, 559026e0d1SMaxime Ripard SUN4I_TCON_GCTL_TCON_ENABLE); 569026e0d1SMaxime Ripard } 579026e0d1SMaxime Ripard EXPORT_SYMBOL(sun4i_tcon_enable); 589026e0d1SMaxime Ripard 599026e0d1SMaxime Ripard void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) 609026e0d1SMaxime Ripard { 611a075426SMaxime Ripard DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel); 621a075426SMaxime Ripard 639026e0d1SMaxime Ripard /* Disable the TCON's channel */ 649026e0d1SMaxime Ripard if (channel == 0) { 659026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, 669026e0d1SMaxime Ripard SUN4I_TCON0_CTL_TCON_ENABLE, 0); 679026e0d1SMaxime Ripard clk_disable_unprepare(tcon->dclk); 688e924047SMaxime Ripard return; 698e924047SMaxime Ripard } 708e924047SMaxime Ripard 7191ea2f29SChen-Yu Tsai WARN_ON(!tcon->quirks->has_channel_1); 729026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, 739026e0d1SMaxime Ripard SUN4I_TCON1_CTL_TCON_ENABLE, 0); 749026e0d1SMaxime Ripard clk_disable_unprepare(tcon->sclk1); 759026e0d1SMaxime Ripard } 769026e0d1SMaxime Ripard EXPORT_SYMBOL(sun4i_tcon_channel_disable); 779026e0d1SMaxime Ripard 789026e0d1SMaxime Ripard void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) 799026e0d1SMaxime Ripard { 801a075426SMaxime Ripard DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel); 811a075426SMaxime Ripard 829026e0d1SMaxime Ripard /* Enable the TCON's channel */ 839026e0d1SMaxime Ripard if (channel == 0) { 849026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, 859026e0d1SMaxime Ripard SUN4I_TCON0_CTL_TCON_ENABLE, 869026e0d1SMaxime Ripard SUN4I_TCON0_CTL_TCON_ENABLE); 879026e0d1SMaxime Ripard clk_prepare_enable(tcon->dclk); 888e924047SMaxime Ripard return; 898e924047SMaxime Ripard } 908e924047SMaxime Ripard 9191ea2f29SChen-Yu Tsai WARN_ON(!tcon->quirks->has_channel_1); 929026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, 939026e0d1SMaxime Ripard SUN4I_TCON1_CTL_TCON_ENABLE, 949026e0d1SMaxime Ripard SUN4I_TCON1_CTL_TCON_ENABLE); 959026e0d1SMaxime Ripard clk_prepare_enable(tcon->sclk1); 969026e0d1SMaxime Ripard } 979026e0d1SMaxime Ripard EXPORT_SYMBOL(sun4i_tcon_channel_enable); 989026e0d1SMaxime Ripard 999026e0d1SMaxime Ripard void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) 1009026e0d1SMaxime Ripard { 1019026e0d1SMaxime Ripard u32 mask, val = 0; 1029026e0d1SMaxime Ripard 1039026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n", enable ? "En" : "Dis"); 1049026e0d1SMaxime Ripard 1059026e0d1SMaxime Ripard mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) | 1069026e0d1SMaxime Ripard SUN4I_TCON_GINT0_VBLANK_ENABLE(1); 1079026e0d1SMaxime Ripard 1089026e0d1SMaxime Ripard if (enable) 1099026e0d1SMaxime Ripard val = mask; 1109026e0d1SMaxime Ripard 1119026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, mask, val); 1129026e0d1SMaxime Ripard } 1139026e0d1SMaxime Ripard EXPORT_SYMBOL(sun4i_tcon_enable_vblank); 1149026e0d1SMaxime Ripard 115*67e32645SChen-Yu Tsai /* 116*67e32645SChen-Yu Tsai * This function is a helper for TCON output muxing. The TCON output 117*67e32645SChen-Yu Tsai * muxing control register in earlier SoCs (without the TCON TOP block) 118*67e32645SChen-Yu Tsai * are located in TCON0. This helper returns a pointer to TCON0's 119*67e32645SChen-Yu Tsai * sun4i_tcon structure, or NULL if not found. 120*67e32645SChen-Yu Tsai */ 121*67e32645SChen-Yu Tsai static struct sun4i_tcon *sun4i_get_tcon0(struct drm_device *drm) 122*67e32645SChen-Yu Tsai { 123*67e32645SChen-Yu Tsai struct sun4i_drv *drv = drm->dev_private; 124*67e32645SChen-Yu Tsai struct sun4i_tcon *tcon; 125*67e32645SChen-Yu Tsai 126*67e32645SChen-Yu Tsai list_for_each_entry(tcon, &drv->tcon_list, list) 127*67e32645SChen-Yu Tsai if (tcon->id == 0) 128*67e32645SChen-Yu Tsai return tcon; 129*67e32645SChen-Yu Tsai 130*67e32645SChen-Yu Tsai dev_warn(drm->dev, 131*67e32645SChen-Yu Tsai "TCON0 not found, display output muxing may not work\n"); 132*67e32645SChen-Yu Tsai 133*67e32645SChen-Yu Tsai return NULL; 134*67e32645SChen-Yu Tsai } 135*67e32645SChen-Yu Tsai 136f8c73f4fSMaxime Ripard void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, 137f8c73f4fSMaxime Ripard struct drm_encoder *encoder) 138f8c73f4fSMaxime Ripard { 139ad537fb2SChen-Yu Tsai int ret = -ENOTSUPP; 140b7cb9b91SMaxime Ripard 141ad537fb2SChen-Yu Tsai if (tcon->quirks->set_mux) 142ad537fb2SChen-Yu Tsai ret = tcon->quirks->set_mux(tcon, encoder); 143f8c73f4fSMaxime Ripard 144ad537fb2SChen-Yu Tsai DRM_DEBUG_DRIVER("Muxing encoder %s to CRTC %s: %d\n", 145ad537fb2SChen-Yu Tsai encoder->name, encoder->crtc->name, ret); 146f8c73f4fSMaxime Ripard } 147f8c73f4fSMaxime Ripard EXPORT_SYMBOL(sun4i_tcon_set_mux); 148f8c73f4fSMaxime Ripard 1499026e0d1SMaxime Ripard static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, 1509026e0d1SMaxime Ripard int channel) 1519026e0d1SMaxime Ripard { 1529026e0d1SMaxime Ripard int delay = mode->vtotal - mode->vdisplay; 1539026e0d1SMaxime Ripard 1549026e0d1SMaxime Ripard if (mode->flags & DRM_MODE_FLAG_INTERLACE) 1559026e0d1SMaxime Ripard delay /= 2; 1569026e0d1SMaxime Ripard 1579026e0d1SMaxime Ripard if (channel == 1) 1589026e0d1SMaxime Ripard delay -= 2; 1599026e0d1SMaxime Ripard 1609026e0d1SMaxime Ripard delay = min(delay, 30); 1619026e0d1SMaxime Ripard 1629026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("TCON %d clock delay %u\n", channel, delay); 1639026e0d1SMaxime Ripard 1649026e0d1SMaxime Ripard return delay; 1659026e0d1SMaxime Ripard } 1669026e0d1SMaxime Ripard 1679026e0d1SMaxime Ripard void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, 1689026e0d1SMaxime Ripard struct drm_display_mode *mode) 1699026e0d1SMaxime Ripard { 1709026e0d1SMaxime Ripard unsigned int bp, hsync, vsync; 1719026e0d1SMaxime Ripard u8 clk_delay; 1729026e0d1SMaxime Ripard u32 val = 0; 1739026e0d1SMaxime Ripard 17486cf6788SChen-Yu Tsai /* Configure the dot clock */ 17586cf6788SChen-Yu Tsai clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); 17686cf6788SChen-Yu Tsai 1779026e0d1SMaxime Ripard /* Adjust clock delay */ 1789026e0d1SMaxime Ripard clk_delay = sun4i_tcon_get_clk_delay(mode, 0); 1799026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, 1809026e0d1SMaxime Ripard SUN4I_TCON0_CTL_CLK_DELAY_MASK, 1819026e0d1SMaxime Ripard SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); 1829026e0d1SMaxime Ripard 1839026e0d1SMaxime Ripard /* Set the resolution */ 1849026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, 1859026e0d1SMaxime Ripard SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | 1869026e0d1SMaxime Ripard SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); 1879026e0d1SMaxime Ripard 1889026e0d1SMaxime Ripard /* 1899026e0d1SMaxime Ripard * This is called a backporch in the register documentation, 19023a1cb11SChen-Yu Tsai * but it really is the back porch + hsync 1919026e0d1SMaxime Ripard */ 1929026e0d1SMaxime Ripard bp = mode->crtc_htotal - mode->crtc_hsync_start; 1939026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", 1949026e0d1SMaxime Ripard mode->crtc_htotal, bp); 1959026e0d1SMaxime Ripard 1969026e0d1SMaxime Ripard /* Set horizontal display timings */ 1979026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG, 1989026e0d1SMaxime Ripard SUN4I_TCON0_BASIC1_H_TOTAL(mode->crtc_htotal) | 1999026e0d1SMaxime Ripard SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); 2009026e0d1SMaxime Ripard 2019026e0d1SMaxime Ripard /* 2029026e0d1SMaxime Ripard * This is called a backporch in the register documentation, 20323a1cb11SChen-Yu Tsai * but it really is the back porch + hsync 2049026e0d1SMaxime Ripard */ 2059026e0d1SMaxime Ripard bp = mode->crtc_vtotal - mode->crtc_vsync_start; 2069026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", 2079026e0d1SMaxime Ripard mode->crtc_vtotal, bp); 2089026e0d1SMaxime Ripard 2099026e0d1SMaxime Ripard /* Set vertical display timings */ 2109026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, 211a88cbbd4SMaxime Ripard SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | 2129026e0d1SMaxime Ripard SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); 2139026e0d1SMaxime Ripard 2149026e0d1SMaxime Ripard /* Set Hsync and Vsync length */ 2159026e0d1SMaxime Ripard hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; 2169026e0d1SMaxime Ripard vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; 2179026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); 2189026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_BASIC3_REG, 2199026e0d1SMaxime Ripard SUN4I_TCON0_BASIC3_V_SYNC(vsync) | 2209026e0d1SMaxime Ripard SUN4I_TCON0_BASIC3_H_SYNC(hsync)); 2219026e0d1SMaxime Ripard 2229026e0d1SMaxime Ripard /* Setup the polarity of the various signals */ 2239026e0d1SMaxime Ripard if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) 2249026e0d1SMaxime Ripard val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; 2259026e0d1SMaxime Ripard 2269026e0d1SMaxime Ripard if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) 2279026e0d1SMaxime Ripard val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; 2289026e0d1SMaxime Ripard 2299026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG, 2309026e0d1SMaxime Ripard SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE, 2319026e0d1SMaxime Ripard val); 2329026e0d1SMaxime Ripard 2339026e0d1SMaxime Ripard /* Map output pins to channel 0 */ 2349026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, 2359026e0d1SMaxime Ripard SUN4I_TCON_GCTL_IOMAP_MASK, 2369026e0d1SMaxime Ripard SUN4I_TCON_GCTL_IOMAP_TCON0); 2379026e0d1SMaxime Ripard 2389026e0d1SMaxime Ripard /* Enable the output on the pins */ 2399026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0); 2409026e0d1SMaxime Ripard } 2419026e0d1SMaxime Ripard EXPORT_SYMBOL(sun4i_tcon0_mode_set); 2429026e0d1SMaxime Ripard 2439026e0d1SMaxime Ripard void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, 2449026e0d1SMaxime Ripard struct drm_display_mode *mode) 2459026e0d1SMaxime Ripard { 246b8317a3dSMaxime Ripard unsigned int bp, hsync, vsync, vtotal; 2479026e0d1SMaxime Ripard u8 clk_delay; 2489026e0d1SMaxime Ripard u32 val; 2499026e0d1SMaxime Ripard 25091ea2f29SChen-Yu Tsai WARN_ON(!tcon->quirks->has_channel_1); 2518e924047SMaxime Ripard 25286cf6788SChen-Yu Tsai /* Configure the dot clock */ 25386cf6788SChen-Yu Tsai clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); 25486cf6788SChen-Yu Tsai 2559026e0d1SMaxime Ripard /* Adjust clock delay */ 2569026e0d1SMaxime Ripard clk_delay = sun4i_tcon_get_clk_delay(mode, 1); 2579026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, 2589026e0d1SMaxime Ripard SUN4I_TCON1_CTL_CLK_DELAY_MASK, 2599026e0d1SMaxime Ripard SUN4I_TCON1_CTL_CLK_DELAY(clk_delay)); 2609026e0d1SMaxime Ripard 2619026e0d1SMaxime Ripard /* Set interlaced mode */ 2629026e0d1SMaxime Ripard if (mode->flags & DRM_MODE_FLAG_INTERLACE) 2639026e0d1SMaxime Ripard val = SUN4I_TCON1_CTL_INTERLACE_ENABLE; 2649026e0d1SMaxime Ripard else 2659026e0d1SMaxime Ripard val = 0; 2669026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, 2679026e0d1SMaxime Ripard SUN4I_TCON1_CTL_INTERLACE_ENABLE, 2689026e0d1SMaxime Ripard val); 2699026e0d1SMaxime Ripard 2709026e0d1SMaxime Ripard /* Set the input resolution */ 2719026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC0_REG, 2729026e0d1SMaxime Ripard SUN4I_TCON1_BASIC0_X(mode->crtc_hdisplay) | 2739026e0d1SMaxime Ripard SUN4I_TCON1_BASIC0_Y(mode->crtc_vdisplay)); 2749026e0d1SMaxime Ripard 2759026e0d1SMaxime Ripard /* Set the upscaling resolution */ 2769026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC1_REG, 2779026e0d1SMaxime Ripard SUN4I_TCON1_BASIC1_X(mode->crtc_hdisplay) | 2789026e0d1SMaxime Ripard SUN4I_TCON1_BASIC1_Y(mode->crtc_vdisplay)); 2799026e0d1SMaxime Ripard 2809026e0d1SMaxime Ripard /* Set the output resolution */ 2819026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC2_REG, 2829026e0d1SMaxime Ripard SUN4I_TCON1_BASIC2_X(mode->crtc_hdisplay) | 2839026e0d1SMaxime Ripard SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); 2849026e0d1SMaxime Ripard 2859026e0d1SMaxime Ripard /* Set horizontal display timings */ 2863cb2f46bSMaxime Ripard bp = mode->crtc_htotal - mode->crtc_hsync_start; 2879026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", 2889026e0d1SMaxime Ripard mode->htotal, bp); 2899026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, 2909026e0d1SMaxime Ripard SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | 2919026e0d1SMaxime Ripard SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); 2929026e0d1SMaxime Ripard 2933cb2f46bSMaxime Ripard bp = mode->crtc_vtotal - mode->crtc_vsync_start; 2949026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", 295b8317a3dSMaxime Ripard mode->crtc_vtotal, bp); 296b8317a3dSMaxime Ripard 297b8317a3dSMaxime Ripard /* 298b8317a3dSMaxime Ripard * The vertical resolution needs to be doubled in all 299b8317a3dSMaxime Ripard * cases. We could use crtc_vtotal and always multiply by two, 300b8317a3dSMaxime Ripard * but that leads to a rounding error in interlace when vtotal 301b8317a3dSMaxime Ripard * is odd. 302b8317a3dSMaxime Ripard * 303b8317a3dSMaxime Ripard * This happens with TV's PAL for example, where vtotal will 304b8317a3dSMaxime Ripard * be 625, crtc_vtotal 312, and thus crtc_vtotal * 2 will be 305b8317a3dSMaxime Ripard * 624, which apparently confuses the hardware. 306b8317a3dSMaxime Ripard * 307b8317a3dSMaxime Ripard * To work around this, we will always use vtotal, and 308b8317a3dSMaxime Ripard * multiply by two only if we're not in interlace. 309b8317a3dSMaxime Ripard */ 310b8317a3dSMaxime Ripard vtotal = mode->vtotal; 311b8317a3dSMaxime Ripard if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) 312b8317a3dSMaxime Ripard vtotal = vtotal * 2; 313b8317a3dSMaxime Ripard 314b8317a3dSMaxime Ripard /* Set vertical display timings */ 3159026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, 316b8317a3dSMaxime Ripard SUN4I_TCON1_BASIC4_V_TOTAL(vtotal) | 3179026e0d1SMaxime Ripard SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); 3189026e0d1SMaxime Ripard 3199026e0d1SMaxime Ripard /* Set Hsync and Vsync length */ 3209026e0d1SMaxime Ripard hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; 3219026e0d1SMaxime Ripard vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; 3229026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); 3239026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC5_REG, 3249026e0d1SMaxime Ripard SUN4I_TCON1_BASIC5_V_SYNC(vsync) | 3259026e0d1SMaxime Ripard SUN4I_TCON1_BASIC5_H_SYNC(hsync)); 3269026e0d1SMaxime Ripard 3279026e0d1SMaxime Ripard /* Map output pins to channel 1 */ 3289026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, 3299026e0d1SMaxime Ripard SUN4I_TCON_GCTL_IOMAP_MASK, 3309026e0d1SMaxime Ripard SUN4I_TCON_GCTL_IOMAP_TCON1); 3319026e0d1SMaxime Ripard } 3329026e0d1SMaxime Ripard EXPORT_SYMBOL(sun4i_tcon1_mode_set); 3339026e0d1SMaxime Ripard 3349026e0d1SMaxime Ripard static void sun4i_tcon_finish_page_flip(struct drm_device *dev, 3359026e0d1SMaxime Ripard struct sun4i_crtc *scrtc) 3369026e0d1SMaxime Ripard { 3379026e0d1SMaxime Ripard unsigned long flags; 3389026e0d1SMaxime Ripard 3399026e0d1SMaxime Ripard spin_lock_irqsave(&dev->event_lock, flags); 3409026e0d1SMaxime Ripard if (scrtc->event) { 3419026e0d1SMaxime Ripard drm_crtc_send_vblank_event(&scrtc->crtc, scrtc->event); 3429026e0d1SMaxime Ripard drm_crtc_vblank_put(&scrtc->crtc); 3439026e0d1SMaxime Ripard scrtc->event = NULL; 3449026e0d1SMaxime Ripard } 3459026e0d1SMaxime Ripard spin_unlock_irqrestore(&dev->event_lock, flags); 3469026e0d1SMaxime Ripard } 3479026e0d1SMaxime Ripard 3489026e0d1SMaxime Ripard static irqreturn_t sun4i_tcon_handler(int irq, void *private) 3499026e0d1SMaxime Ripard { 3509026e0d1SMaxime Ripard struct sun4i_tcon *tcon = private; 3519026e0d1SMaxime Ripard struct drm_device *drm = tcon->drm; 35246cce6daSChen-Yu Tsai struct sun4i_crtc *scrtc = tcon->crtc; 3539026e0d1SMaxime Ripard unsigned int status; 3549026e0d1SMaxime Ripard 3559026e0d1SMaxime Ripard regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status); 3569026e0d1SMaxime Ripard 3579026e0d1SMaxime Ripard if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) | 3589026e0d1SMaxime Ripard SUN4I_TCON_GINT0_VBLANK_INT(1)))) 3599026e0d1SMaxime Ripard return IRQ_NONE; 3609026e0d1SMaxime Ripard 3619026e0d1SMaxime Ripard drm_crtc_handle_vblank(&scrtc->crtc); 3629026e0d1SMaxime Ripard sun4i_tcon_finish_page_flip(drm, scrtc); 3639026e0d1SMaxime Ripard 3649026e0d1SMaxime Ripard /* Acknowledge the interrupt */ 3659026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, 3669026e0d1SMaxime Ripard SUN4I_TCON_GINT0_VBLANK_INT(0) | 3679026e0d1SMaxime Ripard SUN4I_TCON_GINT0_VBLANK_INT(1), 3689026e0d1SMaxime Ripard 0); 3699026e0d1SMaxime Ripard 3709026e0d1SMaxime Ripard return IRQ_HANDLED; 3719026e0d1SMaxime Ripard } 3729026e0d1SMaxime Ripard 3739026e0d1SMaxime Ripard static int sun4i_tcon_init_clocks(struct device *dev, 3749026e0d1SMaxime Ripard struct sun4i_tcon *tcon) 3759026e0d1SMaxime Ripard { 3769026e0d1SMaxime Ripard tcon->clk = devm_clk_get(dev, "ahb"); 3779026e0d1SMaxime Ripard if (IS_ERR(tcon->clk)) { 3789026e0d1SMaxime Ripard dev_err(dev, "Couldn't get the TCON bus clock\n"); 3799026e0d1SMaxime Ripard return PTR_ERR(tcon->clk); 3809026e0d1SMaxime Ripard } 3819026e0d1SMaxime Ripard clk_prepare_enable(tcon->clk); 3829026e0d1SMaxime Ripard 3839026e0d1SMaxime Ripard tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); 3849026e0d1SMaxime Ripard if (IS_ERR(tcon->sclk0)) { 3859026e0d1SMaxime Ripard dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); 3869026e0d1SMaxime Ripard return PTR_ERR(tcon->sclk0); 3879026e0d1SMaxime Ripard } 3889026e0d1SMaxime Ripard 38991ea2f29SChen-Yu Tsai if (tcon->quirks->has_channel_1) { 3909026e0d1SMaxime Ripard tcon->sclk1 = devm_clk_get(dev, "tcon-ch1"); 3919026e0d1SMaxime Ripard if (IS_ERR(tcon->sclk1)) { 3929026e0d1SMaxime Ripard dev_err(dev, "Couldn't get the TCON channel 1 clock\n"); 3939026e0d1SMaxime Ripard return PTR_ERR(tcon->sclk1); 3949026e0d1SMaxime Ripard } 3958e924047SMaxime Ripard } 3969026e0d1SMaxime Ripard 3974c7f16d1SChen-Yu Tsai return 0; 3989026e0d1SMaxime Ripard } 3999026e0d1SMaxime Ripard 4009026e0d1SMaxime Ripard static void sun4i_tcon_free_clocks(struct sun4i_tcon *tcon) 4019026e0d1SMaxime Ripard { 4029026e0d1SMaxime Ripard clk_disable_unprepare(tcon->clk); 4039026e0d1SMaxime Ripard } 4049026e0d1SMaxime Ripard 4059026e0d1SMaxime Ripard static int sun4i_tcon_init_irq(struct device *dev, 4069026e0d1SMaxime Ripard struct sun4i_tcon *tcon) 4079026e0d1SMaxime Ripard { 4089026e0d1SMaxime Ripard struct platform_device *pdev = to_platform_device(dev); 4099026e0d1SMaxime Ripard int irq, ret; 4109026e0d1SMaxime Ripard 4119026e0d1SMaxime Ripard irq = platform_get_irq(pdev, 0); 4129026e0d1SMaxime Ripard if (irq < 0) { 4139026e0d1SMaxime Ripard dev_err(dev, "Couldn't retrieve the TCON interrupt\n"); 4149026e0d1SMaxime Ripard return irq; 4159026e0d1SMaxime Ripard } 4169026e0d1SMaxime Ripard 4179026e0d1SMaxime Ripard ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0, 4189026e0d1SMaxime Ripard dev_name(dev), tcon); 4199026e0d1SMaxime Ripard if (ret) { 4209026e0d1SMaxime Ripard dev_err(dev, "Couldn't request the IRQ\n"); 4219026e0d1SMaxime Ripard return ret; 4229026e0d1SMaxime Ripard } 4239026e0d1SMaxime Ripard 4249026e0d1SMaxime Ripard return 0; 4259026e0d1SMaxime Ripard } 4269026e0d1SMaxime Ripard 4279026e0d1SMaxime Ripard static struct regmap_config sun4i_tcon_regmap_config = { 4289026e0d1SMaxime Ripard .reg_bits = 32, 4299026e0d1SMaxime Ripard .val_bits = 32, 4309026e0d1SMaxime Ripard .reg_stride = 4, 4319026e0d1SMaxime Ripard .max_register = 0x800, 4329026e0d1SMaxime Ripard }; 4339026e0d1SMaxime Ripard 4349026e0d1SMaxime Ripard static int sun4i_tcon_init_regmap(struct device *dev, 4359026e0d1SMaxime Ripard struct sun4i_tcon *tcon) 4369026e0d1SMaxime Ripard { 4379026e0d1SMaxime Ripard struct platform_device *pdev = to_platform_device(dev); 4389026e0d1SMaxime Ripard struct resource *res; 4399026e0d1SMaxime Ripard void __iomem *regs; 4409026e0d1SMaxime Ripard 4419026e0d1SMaxime Ripard res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 4429026e0d1SMaxime Ripard regs = devm_ioremap_resource(dev, res); 443af346f55SWei Yongjun if (IS_ERR(regs)) 4449026e0d1SMaxime Ripard return PTR_ERR(regs); 4459026e0d1SMaxime Ripard 4469026e0d1SMaxime Ripard tcon->regs = devm_regmap_init_mmio(dev, regs, 4479026e0d1SMaxime Ripard &sun4i_tcon_regmap_config); 4489026e0d1SMaxime Ripard if (IS_ERR(tcon->regs)) { 4499026e0d1SMaxime Ripard dev_err(dev, "Couldn't create the TCON regmap\n"); 4509026e0d1SMaxime Ripard return PTR_ERR(tcon->regs); 4519026e0d1SMaxime Ripard } 4529026e0d1SMaxime Ripard 4539026e0d1SMaxime Ripard /* Make sure the TCON is disabled and all IRQs are off */ 4549026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, 0); 4559026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0); 4569026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON_GINT1_REG, 0); 4579026e0d1SMaxime Ripard 4589026e0d1SMaxime Ripard /* Disable IO lines and set them to tristate */ 4599026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, ~0); 4609026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_IO_TRI_REG, ~0); 4619026e0d1SMaxime Ripard 4629026e0d1SMaxime Ripard return 0; 4639026e0d1SMaxime Ripard } 4649026e0d1SMaxime Ripard 465b317fa3bSChen-Yu Tsai /* 466b317fa3bSChen-Yu Tsai * On SoCs with the old display pipeline design (Display Engine 1.0), 467b317fa3bSChen-Yu Tsai * the TCON is always tied to just one backend. Hence we can traverse 468b317fa3bSChen-Yu Tsai * the of_graph upwards to find the backend our tcon is connected to, 469b317fa3bSChen-Yu Tsai * and take its ID as our own. 470b317fa3bSChen-Yu Tsai * 471b317fa3bSChen-Yu Tsai * We can either identify backends from their compatible strings, which 472b317fa3bSChen-Yu Tsai * means maintaining a large list of them. Or, since the backend is 473b317fa3bSChen-Yu Tsai * registered and binded before the TCON, we can just go through the 474b317fa3bSChen-Yu Tsai * list of registered backends and compare the device node. 47587969338SIcenowy Zheng * 47687969338SIcenowy Zheng * As the structures now store engines instead of backends, here this 47787969338SIcenowy Zheng * function in fact searches the corresponding engine, and the ID is 47887969338SIcenowy Zheng * requested via the get_id function of the engine. 479b317fa3bSChen-Yu Tsai */ 480e8d5bbf7SChen-Yu Tsai static struct sunxi_engine * 481e8d5bbf7SChen-Yu Tsai sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv, 482b317fa3bSChen-Yu Tsai struct device_node *node) 483b317fa3bSChen-Yu Tsai { 484b317fa3bSChen-Yu Tsai struct device_node *port, *ep, *remote; 485be3fe0f9SChen-Yu Tsai struct sunxi_engine *engine = ERR_PTR(-EINVAL); 486b317fa3bSChen-Yu Tsai 487b317fa3bSChen-Yu Tsai port = of_graph_get_port_by_id(node, 0); 488b317fa3bSChen-Yu Tsai if (!port) 489b317fa3bSChen-Yu Tsai return ERR_PTR(-EINVAL); 490b317fa3bSChen-Yu Tsai 4911469619dSChen-Yu Tsai /* 4921469619dSChen-Yu Tsai * This only works if there is only one path from the TCON 4931469619dSChen-Yu Tsai * to any display engine. Otherwise the probe order of the 4941469619dSChen-Yu Tsai * TCONs and display engines is not guaranteed. They may 4951469619dSChen-Yu Tsai * either bind to the wrong one, or worse, bind to the same 4961469619dSChen-Yu Tsai * one if additional checks are not done. 4971469619dSChen-Yu Tsai * 4981469619dSChen-Yu Tsai * Bail out if there are multiple input connections. 4991469619dSChen-Yu Tsai */ 500be3fe0f9SChen-Yu Tsai if (of_get_available_child_count(port) != 1) 501be3fe0f9SChen-Yu Tsai goto out_put_port; 5021469619dSChen-Yu Tsai 503be3fe0f9SChen-Yu Tsai /* Get the first connection without specifying an ID */ 504be3fe0f9SChen-Yu Tsai ep = of_get_next_available_child(port, NULL); 505be3fe0f9SChen-Yu Tsai if (!ep) 506be3fe0f9SChen-Yu Tsai goto out_put_port; 507be3fe0f9SChen-Yu Tsai 508b317fa3bSChen-Yu Tsai remote = of_graph_get_remote_port_parent(ep); 509b317fa3bSChen-Yu Tsai if (!remote) 510be3fe0f9SChen-Yu Tsai goto out_put_ep; 511b317fa3bSChen-Yu Tsai 51287969338SIcenowy Zheng /* does this node match any registered engines? */ 513be3fe0f9SChen-Yu Tsai list_for_each_entry(engine, &drv->engine_list, list) 514be3fe0f9SChen-Yu Tsai if (remote == engine->node) 515be3fe0f9SChen-Yu Tsai goto out_put_remote; 516b317fa3bSChen-Yu Tsai 517b317fa3bSChen-Yu Tsai /* keep looking through upstream ports */ 518e8d5bbf7SChen-Yu Tsai engine = sun4i_tcon_find_engine_traverse(drv, remote); 519b317fa3bSChen-Yu Tsai 520be3fe0f9SChen-Yu Tsai out_put_remote: 521be3fe0f9SChen-Yu Tsai of_node_put(remote); 522be3fe0f9SChen-Yu Tsai out_put_ep: 523be3fe0f9SChen-Yu Tsai of_node_put(ep); 524be3fe0f9SChen-Yu Tsai out_put_port: 525be3fe0f9SChen-Yu Tsai of_node_put(port); 526be3fe0f9SChen-Yu Tsai 527be3fe0f9SChen-Yu Tsai return engine; 528b317fa3bSChen-Yu Tsai } 529b317fa3bSChen-Yu Tsai 530e8d5bbf7SChen-Yu Tsai /* 531e8d5bbf7SChen-Yu Tsai * The device tree binding says that the remote endpoint ID of any 532e8d5bbf7SChen-Yu Tsai * connection between components, up to and including the TCON, of 533e8d5bbf7SChen-Yu Tsai * the display pipeline should be equal to the actual ID of the local 534e8d5bbf7SChen-Yu Tsai * component. Thus we can look at any one of the input connections of 535e8d5bbf7SChen-Yu Tsai * the TCONs, and use that connection's remote endpoint ID as our own. 536e8d5bbf7SChen-Yu Tsai * 537e8d5bbf7SChen-Yu Tsai * Since the user of this function already finds the input port, 538e8d5bbf7SChen-Yu Tsai * the port is passed in directly without further checks. 539e8d5bbf7SChen-Yu Tsai */ 540e8d5bbf7SChen-Yu Tsai static int sun4i_tcon_of_get_id_from_port(struct device_node *port) 541e8d5bbf7SChen-Yu Tsai { 542e8d5bbf7SChen-Yu Tsai struct device_node *ep; 543e8d5bbf7SChen-Yu Tsai int ret = -EINVAL; 544e8d5bbf7SChen-Yu Tsai 545e8d5bbf7SChen-Yu Tsai /* try finding an upstream endpoint */ 546e8d5bbf7SChen-Yu Tsai for_each_available_child_of_node(port, ep) { 547e8d5bbf7SChen-Yu Tsai struct device_node *remote; 548e8d5bbf7SChen-Yu Tsai u32 reg; 549e8d5bbf7SChen-Yu Tsai 550e8d5bbf7SChen-Yu Tsai remote = of_graph_get_remote_endpoint(ep); 551e8d5bbf7SChen-Yu Tsai if (!remote) 552e8d5bbf7SChen-Yu Tsai continue; 553e8d5bbf7SChen-Yu Tsai 554e8d5bbf7SChen-Yu Tsai ret = of_property_read_u32(remote, "reg", ®); 555e8d5bbf7SChen-Yu Tsai if (ret) 556e8d5bbf7SChen-Yu Tsai continue; 557e8d5bbf7SChen-Yu Tsai 558e8d5bbf7SChen-Yu Tsai ret = reg; 559e8d5bbf7SChen-Yu Tsai } 560e8d5bbf7SChen-Yu Tsai 561e8d5bbf7SChen-Yu Tsai return ret; 562e8d5bbf7SChen-Yu Tsai } 563e8d5bbf7SChen-Yu Tsai 564e8d5bbf7SChen-Yu Tsai /* 565e8d5bbf7SChen-Yu Tsai * Once we know the TCON's id, we can look through the list of 566e8d5bbf7SChen-Yu Tsai * engines to find a matching one. We assume all engines have 567e8d5bbf7SChen-Yu Tsai * been probed and added to the list. 568e8d5bbf7SChen-Yu Tsai */ 569e8d5bbf7SChen-Yu Tsai static struct sunxi_engine *sun4i_tcon_get_engine_by_id(struct sun4i_drv *drv, 570e8d5bbf7SChen-Yu Tsai int id) 571e8d5bbf7SChen-Yu Tsai { 572e8d5bbf7SChen-Yu Tsai struct sunxi_engine *engine; 573e8d5bbf7SChen-Yu Tsai 574e8d5bbf7SChen-Yu Tsai list_for_each_entry(engine, &drv->engine_list, list) 575e8d5bbf7SChen-Yu Tsai if (engine->id == id) 576e8d5bbf7SChen-Yu Tsai return engine; 577e8d5bbf7SChen-Yu Tsai 578e8d5bbf7SChen-Yu Tsai return ERR_PTR(-EINVAL); 579e8d5bbf7SChen-Yu Tsai } 580e8d5bbf7SChen-Yu Tsai 581e8d5bbf7SChen-Yu Tsai /* 582e8d5bbf7SChen-Yu Tsai * On SoCs with the old display pipeline design (Display Engine 1.0), 583e8d5bbf7SChen-Yu Tsai * we assumed the TCON was always tied to just one backend. However 584e8d5bbf7SChen-Yu Tsai * this proved not to be the case. On the A31, the TCON can select 585e8d5bbf7SChen-Yu Tsai * either backend as its source. On the A20 (and likely on the A10), 586e8d5bbf7SChen-Yu Tsai * the backend can choose which TCON to output to. 587e8d5bbf7SChen-Yu Tsai * 588e8d5bbf7SChen-Yu Tsai * The device tree binding says that the remote endpoint ID of any 589e8d5bbf7SChen-Yu Tsai * connection between components, up to and including the TCON, of 590e8d5bbf7SChen-Yu Tsai * the display pipeline should be equal to the actual ID of the local 591e8d5bbf7SChen-Yu Tsai * component. Thus we should be able to look at any one of the input 592e8d5bbf7SChen-Yu Tsai * connections of the TCONs, and use that connection's remote endpoint 593e8d5bbf7SChen-Yu Tsai * ID as our own. 594e8d5bbf7SChen-Yu Tsai * 595e8d5bbf7SChen-Yu Tsai * However the connections between the backend and TCON were assumed 596e8d5bbf7SChen-Yu Tsai * to be always singular, and their endpoit IDs were all incorrectly 597e8d5bbf7SChen-Yu Tsai * set to 0. This means for these old device trees, we cannot just look 598e8d5bbf7SChen-Yu Tsai * up the remote endpoint ID of a TCON input endpoint. TCON1 would be 599e8d5bbf7SChen-Yu Tsai * incorrectly identified as TCON0. 600e8d5bbf7SChen-Yu Tsai * 601e8d5bbf7SChen-Yu Tsai * This function first checks if the TCON node has 2 input endpoints. 602e8d5bbf7SChen-Yu Tsai * If so, then the device tree is a corrected version, and it will use 603e8d5bbf7SChen-Yu Tsai * sun4i_tcon_of_get_id() and sun4i_tcon_get_engine_by_id() from above 604e8d5bbf7SChen-Yu Tsai * to fetch the ID and engine directly. If not, then it is likely an 605e8d5bbf7SChen-Yu Tsai * old device trees, where the endpoint IDs were incorrect, but did not 606e8d5bbf7SChen-Yu Tsai * have endpoint connections between the backend and TCON across 607e8d5bbf7SChen-Yu Tsai * different display pipelines. It will fall back to the old method of 608e8d5bbf7SChen-Yu Tsai * traversing the of_graph to try and find a matching engine by device 609e8d5bbf7SChen-Yu Tsai * node. 610e8d5bbf7SChen-Yu Tsai * 611e8d5bbf7SChen-Yu Tsai * In the case of single display pipeline device trees, either method 612e8d5bbf7SChen-Yu Tsai * works. 613e8d5bbf7SChen-Yu Tsai */ 614e8d5bbf7SChen-Yu Tsai static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, 615e8d5bbf7SChen-Yu Tsai struct device_node *node) 616e8d5bbf7SChen-Yu Tsai { 617e8d5bbf7SChen-Yu Tsai struct device_node *port; 618e8d5bbf7SChen-Yu Tsai struct sunxi_engine *engine; 619e8d5bbf7SChen-Yu Tsai 620e8d5bbf7SChen-Yu Tsai port = of_graph_get_port_by_id(node, 0); 621e8d5bbf7SChen-Yu Tsai if (!port) 622e8d5bbf7SChen-Yu Tsai return ERR_PTR(-EINVAL); 623e8d5bbf7SChen-Yu Tsai 624e8d5bbf7SChen-Yu Tsai /* 625e8d5bbf7SChen-Yu Tsai * Is this a corrected device tree with cross pipeline 626e8d5bbf7SChen-Yu Tsai * connections between the backend and TCON? 627e8d5bbf7SChen-Yu Tsai */ 628e8d5bbf7SChen-Yu Tsai if (of_get_child_count(port) > 1) { 629e8d5bbf7SChen-Yu Tsai /* Get our ID directly from an upstream endpoint */ 630e8d5bbf7SChen-Yu Tsai int id = sun4i_tcon_of_get_id_from_port(port); 631e8d5bbf7SChen-Yu Tsai 632e8d5bbf7SChen-Yu Tsai /* Get our engine by matching our ID */ 633e8d5bbf7SChen-Yu Tsai engine = sun4i_tcon_get_engine_by_id(drv, id); 634e8d5bbf7SChen-Yu Tsai 635e8d5bbf7SChen-Yu Tsai of_node_put(port); 636e8d5bbf7SChen-Yu Tsai return engine; 637e8d5bbf7SChen-Yu Tsai } 638e8d5bbf7SChen-Yu Tsai 639e8d5bbf7SChen-Yu Tsai /* Fallback to old method by traversing input endpoints */ 640e8d5bbf7SChen-Yu Tsai of_node_put(port); 641e8d5bbf7SChen-Yu Tsai return sun4i_tcon_find_engine_traverse(drv, node); 642e8d5bbf7SChen-Yu Tsai } 643e8d5bbf7SChen-Yu Tsai 6449026e0d1SMaxime Ripard static int sun4i_tcon_bind(struct device *dev, struct device *master, 6459026e0d1SMaxime Ripard void *data) 6469026e0d1SMaxime Ripard { 6479026e0d1SMaxime Ripard struct drm_device *drm = data; 6489026e0d1SMaxime Ripard struct sun4i_drv *drv = drm->dev_private; 64987969338SIcenowy Zheng struct sunxi_engine *engine; 6509026e0d1SMaxime Ripard struct sun4i_tcon *tcon; 6519026e0d1SMaxime Ripard int ret; 6529026e0d1SMaxime Ripard 65387969338SIcenowy Zheng engine = sun4i_tcon_find_engine(drv, dev->of_node); 65487969338SIcenowy Zheng if (IS_ERR(engine)) { 65587969338SIcenowy Zheng dev_err(dev, "Couldn't find matching engine\n"); 65680a58240SChen-Yu Tsai return -EPROBE_DEFER; 657b317fa3bSChen-Yu Tsai } 65880a58240SChen-Yu Tsai 6599026e0d1SMaxime Ripard tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); 6609026e0d1SMaxime Ripard if (!tcon) 6619026e0d1SMaxime Ripard return -ENOMEM; 6629026e0d1SMaxime Ripard dev_set_drvdata(dev, tcon); 6639026e0d1SMaxime Ripard tcon->drm = drm; 664ae558110SMaxime Ripard tcon->dev = dev; 66587969338SIcenowy Zheng tcon->id = engine->id; 66691ea2f29SChen-Yu Tsai tcon->quirks = of_device_get_match_data(dev); 6679026e0d1SMaxime Ripard 6689026e0d1SMaxime Ripard tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); 6699026e0d1SMaxime Ripard if (IS_ERR(tcon->lcd_rst)) { 6709026e0d1SMaxime Ripard dev_err(dev, "Couldn't get our reset line\n"); 6719026e0d1SMaxime Ripard return PTR_ERR(tcon->lcd_rst); 6729026e0d1SMaxime Ripard } 6739026e0d1SMaxime Ripard 6749026e0d1SMaxime Ripard /* Make sure our TCON is reset */ 675d57294c1SChen-Yu Tsai ret = reset_control_reset(tcon->lcd_rst); 6769026e0d1SMaxime Ripard if (ret) { 6779026e0d1SMaxime Ripard dev_err(dev, "Couldn't deassert our reset line\n"); 6789026e0d1SMaxime Ripard return ret; 6799026e0d1SMaxime Ripard } 6809026e0d1SMaxime Ripard 6819026e0d1SMaxime Ripard ret = sun4i_tcon_init_clocks(dev, tcon); 6829026e0d1SMaxime Ripard if (ret) { 6839026e0d1SMaxime Ripard dev_err(dev, "Couldn't init our TCON clocks\n"); 6849026e0d1SMaxime Ripard goto err_assert_reset; 6859026e0d1SMaxime Ripard } 6869026e0d1SMaxime Ripard 6874c7f16d1SChen-Yu Tsai ret = sun4i_tcon_init_regmap(dev, tcon); 6889026e0d1SMaxime Ripard if (ret) { 6894c7f16d1SChen-Yu Tsai dev_err(dev, "Couldn't init our TCON regmap\n"); 6909026e0d1SMaxime Ripard goto err_free_clocks; 6919026e0d1SMaxime Ripard } 6929026e0d1SMaxime Ripard 6934c7f16d1SChen-Yu Tsai ret = sun4i_dclk_create(dev, tcon); 6944c7f16d1SChen-Yu Tsai if (ret) { 6954c7f16d1SChen-Yu Tsai dev_err(dev, "Couldn't create our TCON dot clock\n"); 6964c7f16d1SChen-Yu Tsai goto err_free_clocks; 6974c7f16d1SChen-Yu Tsai } 6984c7f16d1SChen-Yu Tsai 6999026e0d1SMaxime Ripard ret = sun4i_tcon_init_irq(dev, tcon); 7009026e0d1SMaxime Ripard if (ret) { 7019026e0d1SMaxime Ripard dev_err(dev, "Couldn't init our TCON interrupts\n"); 7024c7f16d1SChen-Yu Tsai goto err_free_dotclock; 7039026e0d1SMaxime Ripard } 7049026e0d1SMaxime Ripard 70587969338SIcenowy Zheng tcon->crtc = sun4i_crtc_init(drm, engine, tcon); 70646cce6daSChen-Yu Tsai if (IS_ERR(tcon->crtc)) { 70746cce6daSChen-Yu Tsai dev_err(dev, "Couldn't create our CRTC\n"); 70846cce6daSChen-Yu Tsai ret = PTR_ERR(tcon->crtc); 70946cce6daSChen-Yu Tsai goto err_free_clocks; 71046cce6daSChen-Yu Tsai } 71146cce6daSChen-Yu Tsai 712b9c8506cSChen-Yu Tsai ret = sun4i_rgb_init(drm, tcon); 71313fef095SChen-Yu Tsai if (ret < 0) 71413fef095SChen-Yu Tsai goto err_free_clocks; 71513fef095SChen-Yu Tsai 71627e18de7SChen-Yu Tsai if (tcon->quirks->needs_de_be_mux) { 71727e18de7SChen-Yu Tsai /* 71827e18de7SChen-Yu Tsai * We assume there is no dynamic muxing of backends 71927e18de7SChen-Yu Tsai * and TCONs, so we select the backend with same ID. 72027e18de7SChen-Yu Tsai * 72127e18de7SChen-Yu Tsai * While dynamic selection might be interesting, since 72227e18de7SChen-Yu Tsai * the CRTC is tied to the TCON, while the layers are 72327e18de7SChen-Yu Tsai * tied to the backends, this means, we will need to 72427e18de7SChen-Yu Tsai * switch between groups of layers. There might not be 72527e18de7SChen-Yu Tsai * a way to represent this constraint in DRM. 72627e18de7SChen-Yu Tsai */ 72727e18de7SChen-Yu Tsai regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, 72827e18de7SChen-Yu Tsai SUN4I_TCON0_CTL_SRC_SEL_MASK, 72927e18de7SChen-Yu Tsai tcon->id); 73027e18de7SChen-Yu Tsai regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, 73127e18de7SChen-Yu Tsai SUN4I_TCON1_CTL_SRC_SEL_MASK, 73227e18de7SChen-Yu Tsai tcon->id); 73327e18de7SChen-Yu Tsai } 73427e18de7SChen-Yu Tsai 73580a58240SChen-Yu Tsai list_add_tail(&tcon->list, &drv->tcon_list); 73680a58240SChen-Yu Tsai 73713fef095SChen-Yu Tsai return 0; 7389026e0d1SMaxime Ripard 7394c7f16d1SChen-Yu Tsai err_free_dotclock: 7404c7f16d1SChen-Yu Tsai sun4i_dclk_free(tcon); 7419026e0d1SMaxime Ripard err_free_clocks: 7429026e0d1SMaxime Ripard sun4i_tcon_free_clocks(tcon); 7439026e0d1SMaxime Ripard err_assert_reset: 7449026e0d1SMaxime Ripard reset_control_assert(tcon->lcd_rst); 7459026e0d1SMaxime Ripard return ret; 7469026e0d1SMaxime Ripard } 7479026e0d1SMaxime Ripard 7489026e0d1SMaxime Ripard static void sun4i_tcon_unbind(struct device *dev, struct device *master, 7499026e0d1SMaxime Ripard void *data) 7509026e0d1SMaxime Ripard { 7519026e0d1SMaxime Ripard struct sun4i_tcon *tcon = dev_get_drvdata(dev); 7529026e0d1SMaxime Ripard 75380a58240SChen-Yu Tsai list_del(&tcon->list); 7544c7f16d1SChen-Yu Tsai sun4i_dclk_free(tcon); 7559026e0d1SMaxime Ripard sun4i_tcon_free_clocks(tcon); 7569026e0d1SMaxime Ripard } 7579026e0d1SMaxime Ripard 758dfeb693dSJulia Lawall static const struct component_ops sun4i_tcon_ops = { 7599026e0d1SMaxime Ripard .bind = sun4i_tcon_bind, 7609026e0d1SMaxime Ripard .unbind = sun4i_tcon_unbind, 7619026e0d1SMaxime Ripard }; 7629026e0d1SMaxime Ripard 7639026e0d1SMaxime Ripard static int sun4i_tcon_probe(struct platform_device *pdev) 7649026e0d1SMaxime Ripard { 76529e57fabSMaxime Ripard struct device_node *node = pdev->dev.of_node; 766894f5a9fSMaxime Ripard struct drm_bridge *bridge; 76729e57fabSMaxime Ripard struct drm_panel *panel; 768ebc94461SRob Herring int ret; 76929e57fabSMaxime Ripard 770ebc94461SRob Herring ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge); 771ebc94461SRob Herring if (ret == -EPROBE_DEFER) 772ebc94461SRob Herring return ret; 77329e57fabSMaxime Ripard 7749026e0d1SMaxime Ripard return component_add(&pdev->dev, &sun4i_tcon_ops); 7759026e0d1SMaxime Ripard } 7769026e0d1SMaxime Ripard 7779026e0d1SMaxime Ripard static int sun4i_tcon_remove(struct platform_device *pdev) 7789026e0d1SMaxime Ripard { 7799026e0d1SMaxime Ripard component_del(&pdev->dev, &sun4i_tcon_ops); 7809026e0d1SMaxime Ripard 7819026e0d1SMaxime Ripard return 0; 7829026e0d1SMaxime Ripard } 7839026e0d1SMaxime Ripard 784ad537fb2SChen-Yu Tsai /* platform specific TCON muxing callbacks */ 785ad537fb2SChen-Yu Tsai static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon, 786ad537fb2SChen-Yu Tsai struct drm_encoder *encoder) 787ad537fb2SChen-Yu Tsai { 788ad537fb2SChen-Yu Tsai u32 val; 789ad537fb2SChen-Yu Tsai 790ad537fb2SChen-Yu Tsai if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) 791ad537fb2SChen-Yu Tsai val = 1; 792ad537fb2SChen-Yu Tsai else 793ad537fb2SChen-Yu Tsai val = 0; 794ad537fb2SChen-Yu Tsai 795ad537fb2SChen-Yu Tsai /* 796ad537fb2SChen-Yu Tsai * FIXME: Undocumented bits 797ad537fb2SChen-Yu Tsai */ 798ad537fb2SChen-Yu Tsai return regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val); 799ad537fb2SChen-Yu Tsai } 800ad537fb2SChen-Yu Tsai 801*67e32645SChen-Yu Tsai static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon, 802*67e32645SChen-Yu Tsai struct drm_encoder *encoder) 803*67e32645SChen-Yu Tsai { 804*67e32645SChen-Yu Tsai struct sun4i_tcon *tcon0 = sun4i_get_tcon0(encoder->dev); 805*67e32645SChen-Yu Tsai u32 shift; 806*67e32645SChen-Yu Tsai 807*67e32645SChen-Yu Tsai if (!tcon0) 808*67e32645SChen-Yu Tsai return -EINVAL; 809*67e32645SChen-Yu Tsai 810*67e32645SChen-Yu Tsai switch (encoder->encoder_type) { 811*67e32645SChen-Yu Tsai case DRM_MODE_ENCODER_TMDS: 812*67e32645SChen-Yu Tsai /* HDMI */ 813*67e32645SChen-Yu Tsai shift = 8; 814*67e32645SChen-Yu Tsai break; 815*67e32645SChen-Yu Tsai default: 816*67e32645SChen-Yu Tsai /* TODO A31 has MIPI DSI but A31s does not */ 817*67e32645SChen-Yu Tsai return -EINVAL; 818*67e32645SChen-Yu Tsai } 819*67e32645SChen-Yu Tsai 820*67e32645SChen-Yu Tsai regmap_update_bits(tcon0->regs, SUN4I_TCON_MUX_CTRL_REG, 821*67e32645SChen-Yu Tsai 0x3 << shift, tcon->id << shift); 822*67e32645SChen-Yu Tsai 823*67e32645SChen-Yu Tsai return 0; 824*67e32645SChen-Yu Tsai } 825*67e32645SChen-Yu Tsai 82691ea2f29SChen-Yu Tsai static const struct sun4i_tcon_quirks sun5i_a13_quirks = { 82791ea2f29SChen-Yu Tsai .has_channel_1 = true, 828ad537fb2SChen-Yu Tsai .set_mux = sun5i_a13_tcon_set_mux, 82991ea2f29SChen-Yu Tsai }; 83091ea2f29SChen-Yu Tsai 83193a5ec14SChen-Yu Tsai static const struct sun4i_tcon_quirks sun6i_a31_quirks = { 83293a5ec14SChen-Yu Tsai .has_channel_1 = true, 83327e18de7SChen-Yu Tsai .needs_de_be_mux = true, 834*67e32645SChen-Yu Tsai .set_mux = sun6i_tcon_set_mux, 83593a5ec14SChen-Yu Tsai }; 83693a5ec14SChen-Yu Tsai 83793a5ec14SChen-Yu Tsai static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { 83893a5ec14SChen-Yu Tsai .has_channel_1 = true, 83927e18de7SChen-Yu Tsai .needs_de_be_mux = true, 84093a5ec14SChen-Yu Tsai }; 84193a5ec14SChen-Yu Tsai 84291ea2f29SChen-Yu Tsai static const struct sun4i_tcon_quirks sun8i_a33_quirks = { 84391ea2f29SChen-Yu Tsai /* nothing is supported */ 84491ea2f29SChen-Yu Tsai }; 84591ea2f29SChen-Yu Tsai 8461a0edb3fSIcenowy Zheng static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { 8471a0edb3fSIcenowy Zheng /* nothing is supported */ 8481a0edb3fSIcenowy Zheng }; 8491a0edb3fSIcenowy Zheng 8509026e0d1SMaxime Ripard static const struct of_device_id sun4i_tcon_of_table[] = { 85191ea2f29SChen-Yu Tsai { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, 85293a5ec14SChen-Yu Tsai { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, 85393a5ec14SChen-Yu Tsai { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, 85491ea2f29SChen-Yu Tsai { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, 8551a0edb3fSIcenowy Zheng { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, 8569026e0d1SMaxime Ripard { } 8579026e0d1SMaxime Ripard }; 8589026e0d1SMaxime Ripard MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); 8599026e0d1SMaxime Ripard 8609026e0d1SMaxime Ripard static struct platform_driver sun4i_tcon_platform_driver = { 8619026e0d1SMaxime Ripard .probe = sun4i_tcon_probe, 8629026e0d1SMaxime Ripard .remove = sun4i_tcon_remove, 8639026e0d1SMaxime Ripard .driver = { 8649026e0d1SMaxime Ripard .name = "sun4i-tcon", 8659026e0d1SMaxime Ripard .of_match_table = sun4i_tcon_of_table, 8669026e0d1SMaxime Ripard }, 8679026e0d1SMaxime Ripard }; 8689026e0d1SMaxime Ripard module_platform_driver(sun4i_tcon_platform_driver); 8699026e0d1SMaxime Ripard 8709026e0d1SMaxime Ripard MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); 8719026e0d1SMaxime Ripard MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver"); 8729026e0d1SMaxime Ripard MODULE_LICENSE("GPL"); 873