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> 17*ad537fb2SChen-Yu Tsai #include <drm/drm_encoder.h> 189026e0d1SMaxime Ripard #include <drm/drm_modes.h> 19ebc94461SRob Herring #include <drm/drm_of.h> 209026e0d1SMaxime Ripard 21*ad537fb2SChen-Yu Tsai #include <uapi/drm/drm_mode.h> 22*ad537fb2SChen-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 115f8c73f4fSMaxime Ripard void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel, 116f8c73f4fSMaxime Ripard struct drm_encoder *encoder) 117f8c73f4fSMaxime Ripard { 118*ad537fb2SChen-Yu Tsai int ret = -ENOTSUPP; 119b7cb9b91SMaxime Ripard 120*ad537fb2SChen-Yu Tsai if (tcon->quirks->set_mux) 121*ad537fb2SChen-Yu Tsai ret = tcon->quirks->set_mux(tcon, encoder); 122f8c73f4fSMaxime Ripard 123*ad537fb2SChen-Yu Tsai DRM_DEBUG_DRIVER("Muxing encoder %s to CRTC %s: %d\n", 124*ad537fb2SChen-Yu Tsai encoder->name, encoder->crtc->name, ret); 125f8c73f4fSMaxime Ripard } 126f8c73f4fSMaxime Ripard EXPORT_SYMBOL(sun4i_tcon_set_mux); 127f8c73f4fSMaxime Ripard 1289026e0d1SMaxime Ripard static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, 1299026e0d1SMaxime Ripard int channel) 1309026e0d1SMaxime Ripard { 1319026e0d1SMaxime Ripard int delay = mode->vtotal - mode->vdisplay; 1329026e0d1SMaxime Ripard 1339026e0d1SMaxime Ripard if (mode->flags & DRM_MODE_FLAG_INTERLACE) 1349026e0d1SMaxime Ripard delay /= 2; 1359026e0d1SMaxime Ripard 1369026e0d1SMaxime Ripard if (channel == 1) 1379026e0d1SMaxime Ripard delay -= 2; 1389026e0d1SMaxime Ripard 1399026e0d1SMaxime Ripard delay = min(delay, 30); 1409026e0d1SMaxime Ripard 1419026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("TCON %d clock delay %u\n", channel, delay); 1429026e0d1SMaxime Ripard 1439026e0d1SMaxime Ripard return delay; 1449026e0d1SMaxime Ripard } 1459026e0d1SMaxime Ripard 1469026e0d1SMaxime Ripard void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, 1479026e0d1SMaxime Ripard struct drm_display_mode *mode) 1489026e0d1SMaxime Ripard { 1499026e0d1SMaxime Ripard unsigned int bp, hsync, vsync; 1509026e0d1SMaxime Ripard u8 clk_delay; 1519026e0d1SMaxime Ripard u32 val = 0; 1529026e0d1SMaxime Ripard 15386cf6788SChen-Yu Tsai /* Configure the dot clock */ 15486cf6788SChen-Yu Tsai clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); 15586cf6788SChen-Yu Tsai 1569026e0d1SMaxime Ripard /* Adjust clock delay */ 1579026e0d1SMaxime Ripard clk_delay = sun4i_tcon_get_clk_delay(mode, 0); 1589026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, 1599026e0d1SMaxime Ripard SUN4I_TCON0_CTL_CLK_DELAY_MASK, 1609026e0d1SMaxime Ripard SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); 1619026e0d1SMaxime Ripard 1629026e0d1SMaxime Ripard /* Set the resolution */ 1639026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, 1649026e0d1SMaxime Ripard SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | 1659026e0d1SMaxime Ripard SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); 1669026e0d1SMaxime Ripard 1679026e0d1SMaxime Ripard /* 1689026e0d1SMaxime Ripard * This is called a backporch in the register documentation, 16923a1cb11SChen-Yu Tsai * but it really is the back porch + hsync 1709026e0d1SMaxime Ripard */ 1719026e0d1SMaxime Ripard bp = mode->crtc_htotal - mode->crtc_hsync_start; 1729026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", 1739026e0d1SMaxime Ripard mode->crtc_htotal, bp); 1749026e0d1SMaxime Ripard 1759026e0d1SMaxime Ripard /* Set horizontal display timings */ 1769026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG, 1779026e0d1SMaxime Ripard SUN4I_TCON0_BASIC1_H_TOTAL(mode->crtc_htotal) | 1789026e0d1SMaxime Ripard SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); 1799026e0d1SMaxime Ripard 1809026e0d1SMaxime Ripard /* 1819026e0d1SMaxime Ripard * This is called a backporch in the register documentation, 18223a1cb11SChen-Yu Tsai * but it really is the back porch + hsync 1839026e0d1SMaxime Ripard */ 1849026e0d1SMaxime Ripard bp = mode->crtc_vtotal - mode->crtc_vsync_start; 1859026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", 1869026e0d1SMaxime Ripard mode->crtc_vtotal, bp); 1879026e0d1SMaxime Ripard 1889026e0d1SMaxime Ripard /* Set vertical display timings */ 1899026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, 190a88cbbd4SMaxime Ripard SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) | 1919026e0d1SMaxime Ripard SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); 1929026e0d1SMaxime Ripard 1939026e0d1SMaxime Ripard /* Set Hsync and Vsync length */ 1949026e0d1SMaxime Ripard hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; 1959026e0d1SMaxime Ripard vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; 1969026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); 1979026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_BASIC3_REG, 1989026e0d1SMaxime Ripard SUN4I_TCON0_BASIC3_V_SYNC(vsync) | 1999026e0d1SMaxime Ripard SUN4I_TCON0_BASIC3_H_SYNC(hsync)); 2009026e0d1SMaxime Ripard 2019026e0d1SMaxime Ripard /* Setup the polarity of the various signals */ 2029026e0d1SMaxime Ripard if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) 2039026e0d1SMaxime Ripard val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; 2049026e0d1SMaxime Ripard 2059026e0d1SMaxime Ripard if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) 2069026e0d1SMaxime Ripard val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; 2079026e0d1SMaxime Ripard 2089026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG, 2099026e0d1SMaxime Ripard SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE, 2109026e0d1SMaxime Ripard val); 2119026e0d1SMaxime Ripard 2129026e0d1SMaxime Ripard /* Map output pins to channel 0 */ 2139026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, 2149026e0d1SMaxime Ripard SUN4I_TCON_GCTL_IOMAP_MASK, 2159026e0d1SMaxime Ripard SUN4I_TCON_GCTL_IOMAP_TCON0); 2169026e0d1SMaxime Ripard 2179026e0d1SMaxime Ripard /* Enable the output on the pins */ 2189026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0); 2199026e0d1SMaxime Ripard } 2209026e0d1SMaxime Ripard EXPORT_SYMBOL(sun4i_tcon0_mode_set); 2219026e0d1SMaxime Ripard 2229026e0d1SMaxime Ripard void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, 2239026e0d1SMaxime Ripard struct drm_display_mode *mode) 2249026e0d1SMaxime Ripard { 225b8317a3dSMaxime Ripard unsigned int bp, hsync, vsync, vtotal; 2269026e0d1SMaxime Ripard u8 clk_delay; 2279026e0d1SMaxime Ripard u32 val; 2289026e0d1SMaxime Ripard 22991ea2f29SChen-Yu Tsai WARN_ON(!tcon->quirks->has_channel_1); 2308e924047SMaxime Ripard 23186cf6788SChen-Yu Tsai /* Configure the dot clock */ 23286cf6788SChen-Yu Tsai clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); 23386cf6788SChen-Yu Tsai 2349026e0d1SMaxime Ripard /* Adjust clock delay */ 2359026e0d1SMaxime Ripard clk_delay = sun4i_tcon_get_clk_delay(mode, 1); 2369026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, 2379026e0d1SMaxime Ripard SUN4I_TCON1_CTL_CLK_DELAY_MASK, 2389026e0d1SMaxime Ripard SUN4I_TCON1_CTL_CLK_DELAY(clk_delay)); 2399026e0d1SMaxime Ripard 2409026e0d1SMaxime Ripard /* Set interlaced mode */ 2419026e0d1SMaxime Ripard if (mode->flags & DRM_MODE_FLAG_INTERLACE) 2429026e0d1SMaxime Ripard val = SUN4I_TCON1_CTL_INTERLACE_ENABLE; 2439026e0d1SMaxime Ripard else 2449026e0d1SMaxime Ripard val = 0; 2459026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, 2469026e0d1SMaxime Ripard SUN4I_TCON1_CTL_INTERLACE_ENABLE, 2479026e0d1SMaxime Ripard val); 2489026e0d1SMaxime Ripard 2499026e0d1SMaxime Ripard /* Set the input resolution */ 2509026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC0_REG, 2519026e0d1SMaxime Ripard SUN4I_TCON1_BASIC0_X(mode->crtc_hdisplay) | 2529026e0d1SMaxime Ripard SUN4I_TCON1_BASIC0_Y(mode->crtc_vdisplay)); 2539026e0d1SMaxime Ripard 2549026e0d1SMaxime Ripard /* Set the upscaling resolution */ 2559026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC1_REG, 2569026e0d1SMaxime Ripard SUN4I_TCON1_BASIC1_X(mode->crtc_hdisplay) | 2579026e0d1SMaxime Ripard SUN4I_TCON1_BASIC1_Y(mode->crtc_vdisplay)); 2589026e0d1SMaxime Ripard 2599026e0d1SMaxime Ripard /* Set the output resolution */ 2609026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC2_REG, 2619026e0d1SMaxime Ripard SUN4I_TCON1_BASIC2_X(mode->crtc_hdisplay) | 2629026e0d1SMaxime Ripard SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); 2639026e0d1SMaxime Ripard 2649026e0d1SMaxime Ripard /* Set horizontal display timings */ 2653cb2f46bSMaxime Ripard bp = mode->crtc_htotal - mode->crtc_hsync_start; 2669026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", 2679026e0d1SMaxime Ripard mode->htotal, bp); 2689026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, 2699026e0d1SMaxime Ripard SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | 2709026e0d1SMaxime Ripard SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); 2719026e0d1SMaxime Ripard 2723cb2f46bSMaxime Ripard bp = mode->crtc_vtotal - mode->crtc_vsync_start; 2739026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", 274b8317a3dSMaxime Ripard mode->crtc_vtotal, bp); 275b8317a3dSMaxime Ripard 276b8317a3dSMaxime Ripard /* 277b8317a3dSMaxime Ripard * The vertical resolution needs to be doubled in all 278b8317a3dSMaxime Ripard * cases. We could use crtc_vtotal and always multiply by two, 279b8317a3dSMaxime Ripard * but that leads to a rounding error in interlace when vtotal 280b8317a3dSMaxime Ripard * is odd. 281b8317a3dSMaxime Ripard * 282b8317a3dSMaxime Ripard * This happens with TV's PAL for example, where vtotal will 283b8317a3dSMaxime Ripard * be 625, crtc_vtotal 312, and thus crtc_vtotal * 2 will be 284b8317a3dSMaxime Ripard * 624, which apparently confuses the hardware. 285b8317a3dSMaxime Ripard * 286b8317a3dSMaxime Ripard * To work around this, we will always use vtotal, and 287b8317a3dSMaxime Ripard * multiply by two only if we're not in interlace. 288b8317a3dSMaxime Ripard */ 289b8317a3dSMaxime Ripard vtotal = mode->vtotal; 290b8317a3dSMaxime Ripard if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) 291b8317a3dSMaxime Ripard vtotal = vtotal * 2; 292b8317a3dSMaxime Ripard 293b8317a3dSMaxime Ripard /* Set vertical display timings */ 2949026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, 295b8317a3dSMaxime Ripard SUN4I_TCON1_BASIC4_V_TOTAL(vtotal) | 2969026e0d1SMaxime Ripard SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); 2979026e0d1SMaxime Ripard 2989026e0d1SMaxime Ripard /* Set Hsync and Vsync length */ 2999026e0d1SMaxime Ripard hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; 3009026e0d1SMaxime Ripard vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; 3019026e0d1SMaxime Ripard DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); 3029026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_BASIC5_REG, 3039026e0d1SMaxime Ripard SUN4I_TCON1_BASIC5_V_SYNC(vsync) | 3049026e0d1SMaxime Ripard SUN4I_TCON1_BASIC5_H_SYNC(hsync)); 3059026e0d1SMaxime Ripard 3069026e0d1SMaxime Ripard /* Map output pins to channel 1 */ 3079026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, 3089026e0d1SMaxime Ripard SUN4I_TCON_GCTL_IOMAP_MASK, 3099026e0d1SMaxime Ripard SUN4I_TCON_GCTL_IOMAP_TCON1); 3109026e0d1SMaxime Ripard } 3119026e0d1SMaxime Ripard EXPORT_SYMBOL(sun4i_tcon1_mode_set); 3129026e0d1SMaxime Ripard 3139026e0d1SMaxime Ripard static void sun4i_tcon_finish_page_flip(struct drm_device *dev, 3149026e0d1SMaxime Ripard struct sun4i_crtc *scrtc) 3159026e0d1SMaxime Ripard { 3169026e0d1SMaxime Ripard unsigned long flags; 3179026e0d1SMaxime Ripard 3189026e0d1SMaxime Ripard spin_lock_irqsave(&dev->event_lock, flags); 3199026e0d1SMaxime Ripard if (scrtc->event) { 3209026e0d1SMaxime Ripard drm_crtc_send_vblank_event(&scrtc->crtc, scrtc->event); 3219026e0d1SMaxime Ripard drm_crtc_vblank_put(&scrtc->crtc); 3229026e0d1SMaxime Ripard scrtc->event = NULL; 3239026e0d1SMaxime Ripard } 3249026e0d1SMaxime Ripard spin_unlock_irqrestore(&dev->event_lock, flags); 3259026e0d1SMaxime Ripard } 3269026e0d1SMaxime Ripard 3279026e0d1SMaxime Ripard static irqreturn_t sun4i_tcon_handler(int irq, void *private) 3289026e0d1SMaxime Ripard { 3299026e0d1SMaxime Ripard struct sun4i_tcon *tcon = private; 3309026e0d1SMaxime Ripard struct drm_device *drm = tcon->drm; 33146cce6daSChen-Yu Tsai struct sun4i_crtc *scrtc = tcon->crtc; 3329026e0d1SMaxime Ripard unsigned int status; 3339026e0d1SMaxime Ripard 3349026e0d1SMaxime Ripard regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status); 3359026e0d1SMaxime Ripard 3369026e0d1SMaxime Ripard if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) | 3379026e0d1SMaxime Ripard SUN4I_TCON_GINT0_VBLANK_INT(1)))) 3389026e0d1SMaxime Ripard return IRQ_NONE; 3399026e0d1SMaxime Ripard 3409026e0d1SMaxime Ripard drm_crtc_handle_vblank(&scrtc->crtc); 3419026e0d1SMaxime Ripard sun4i_tcon_finish_page_flip(drm, scrtc); 3429026e0d1SMaxime Ripard 3439026e0d1SMaxime Ripard /* Acknowledge the interrupt */ 3449026e0d1SMaxime Ripard regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, 3459026e0d1SMaxime Ripard SUN4I_TCON_GINT0_VBLANK_INT(0) | 3469026e0d1SMaxime Ripard SUN4I_TCON_GINT0_VBLANK_INT(1), 3479026e0d1SMaxime Ripard 0); 3489026e0d1SMaxime Ripard 3499026e0d1SMaxime Ripard return IRQ_HANDLED; 3509026e0d1SMaxime Ripard } 3519026e0d1SMaxime Ripard 3529026e0d1SMaxime Ripard static int sun4i_tcon_init_clocks(struct device *dev, 3539026e0d1SMaxime Ripard struct sun4i_tcon *tcon) 3549026e0d1SMaxime Ripard { 3559026e0d1SMaxime Ripard tcon->clk = devm_clk_get(dev, "ahb"); 3569026e0d1SMaxime Ripard if (IS_ERR(tcon->clk)) { 3579026e0d1SMaxime Ripard dev_err(dev, "Couldn't get the TCON bus clock\n"); 3589026e0d1SMaxime Ripard return PTR_ERR(tcon->clk); 3599026e0d1SMaxime Ripard } 3609026e0d1SMaxime Ripard clk_prepare_enable(tcon->clk); 3619026e0d1SMaxime Ripard 3629026e0d1SMaxime Ripard tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); 3639026e0d1SMaxime Ripard if (IS_ERR(tcon->sclk0)) { 3649026e0d1SMaxime Ripard dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); 3659026e0d1SMaxime Ripard return PTR_ERR(tcon->sclk0); 3669026e0d1SMaxime Ripard } 3679026e0d1SMaxime Ripard 36891ea2f29SChen-Yu Tsai if (tcon->quirks->has_channel_1) { 3699026e0d1SMaxime Ripard tcon->sclk1 = devm_clk_get(dev, "tcon-ch1"); 3709026e0d1SMaxime Ripard if (IS_ERR(tcon->sclk1)) { 3719026e0d1SMaxime Ripard dev_err(dev, "Couldn't get the TCON channel 1 clock\n"); 3729026e0d1SMaxime Ripard return PTR_ERR(tcon->sclk1); 3739026e0d1SMaxime Ripard } 3748e924047SMaxime Ripard } 3759026e0d1SMaxime Ripard 3764c7f16d1SChen-Yu Tsai return 0; 3779026e0d1SMaxime Ripard } 3789026e0d1SMaxime Ripard 3799026e0d1SMaxime Ripard static void sun4i_tcon_free_clocks(struct sun4i_tcon *tcon) 3809026e0d1SMaxime Ripard { 3819026e0d1SMaxime Ripard clk_disable_unprepare(tcon->clk); 3829026e0d1SMaxime Ripard } 3839026e0d1SMaxime Ripard 3849026e0d1SMaxime Ripard static int sun4i_tcon_init_irq(struct device *dev, 3859026e0d1SMaxime Ripard struct sun4i_tcon *tcon) 3869026e0d1SMaxime Ripard { 3879026e0d1SMaxime Ripard struct platform_device *pdev = to_platform_device(dev); 3889026e0d1SMaxime Ripard int irq, ret; 3899026e0d1SMaxime Ripard 3909026e0d1SMaxime Ripard irq = platform_get_irq(pdev, 0); 3919026e0d1SMaxime Ripard if (irq < 0) { 3929026e0d1SMaxime Ripard dev_err(dev, "Couldn't retrieve the TCON interrupt\n"); 3939026e0d1SMaxime Ripard return irq; 3949026e0d1SMaxime Ripard } 3959026e0d1SMaxime Ripard 3969026e0d1SMaxime Ripard ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0, 3979026e0d1SMaxime Ripard dev_name(dev), tcon); 3989026e0d1SMaxime Ripard if (ret) { 3999026e0d1SMaxime Ripard dev_err(dev, "Couldn't request the IRQ\n"); 4009026e0d1SMaxime Ripard return ret; 4019026e0d1SMaxime Ripard } 4029026e0d1SMaxime Ripard 4039026e0d1SMaxime Ripard return 0; 4049026e0d1SMaxime Ripard } 4059026e0d1SMaxime Ripard 4069026e0d1SMaxime Ripard static struct regmap_config sun4i_tcon_regmap_config = { 4079026e0d1SMaxime Ripard .reg_bits = 32, 4089026e0d1SMaxime Ripard .val_bits = 32, 4099026e0d1SMaxime Ripard .reg_stride = 4, 4109026e0d1SMaxime Ripard .max_register = 0x800, 4119026e0d1SMaxime Ripard }; 4129026e0d1SMaxime Ripard 4139026e0d1SMaxime Ripard static int sun4i_tcon_init_regmap(struct device *dev, 4149026e0d1SMaxime Ripard struct sun4i_tcon *tcon) 4159026e0d1SMaxime Ripard { 4169026e0d1SMaxime Ripard struct platform_device *pdev = to_platform_device(dev); 4179026e0d1SMaxime Ripard struct resource *res; 4189026e0d1SMaxime Ripard void __iomem *regs; 4199026e0d1SMaxime Ripard 4209026e0d1SMaxime Ripard res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 4219026e0d1SMaxime Ripard regs = devm_ioremap_resource(dev, res); 422af346f55SWei Yongjun if (IS_ERR(regs)) 4239026e0d1SMaxime Ripard return PTR_ERR(regs); 4249026e0d1SMaxime Ripard 4259026e0d1SMaxime Ripard tcon->regs = devm_regmap_init_mmio(dev, regs, 4269026e0d1SMaxime Ripard &sun4i_tcon_regmap_config); 4279026e0d1SMaxime Ripard if (IS_ERR(tcon->regs)) { 4289026e0d1SMaxime Ripard dev_err(dev, "Couldn't create the TCON regmap\n"); 4299026e0d1SMaxime Ripard return PTR_ERR(tcon->regs); 4309026e0d1SMaxime Ripard } 4319026e0d1SMaxime Ripard 4329026e0d1SMaxime Ripard /* Make sure the TCON is disabled and all IRQs are off */ 4339026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, 0); 4349026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0); 4359026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON_GINT1_REG, 0); 4369026e0d1SMaxime Ripard 4379026e0d1SMaxime Ripard /* Disable IO lines and set them to tristate */ 4389026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, ~0); 4399026e0d1SMaxime Ripard regmap_write(tcon->regs, SUN4I_TCON1_IO_TRI_REG, ~0); 4409026e0d1SMaxime Ripard 4419026e0d1SMaxime Ripard return 0; 4429026e0d1SMaxime Ripard } 4439026e0d1SMaxime Ripard 444b317fa3bSChen-Yu Tsai /* 445b317fa3bSChen-Yu Tsai * On SoCs with the old display pipeline design (Display Engine 1.0), 446b317fa3bSChen-Yu Tsai * the TCON is always tied to just one backend. Hence we can traverse 447b317fa3bSChen-Yu Tsai * the of_graph upwards to find the backend our tcon is connected to, 448b317fa3bSChen-Yu Tsai * and take its ID as our own. 449b317fa3bSChen-Yu Tsai * 450b317fa3bSChen-Yu Tsai * We can either identify backends from their compatible strings, which 451b317fa3bSChen-Yu Tsai * means maintaining a large list of them. Or, since the backend is 452b317fa3bSChen-Yu Tsai * registered and binded before the TCON, we can just go through the 453b317fa3bSChen-Yu Tsai * list of registered backends and compare the device node. 45487969338SIcenowy Zheng * 45587969338SIcenowy Zheng * As the structures now store engines instead of backends, here this 45687969338SIcenowy Zheng * function in fact searches the corresponding engine, and the ID is 45787969338SIcenowy Zheng * requested via the get_id function of the engine. 458b317fa3bSChen-Yu Tsai */ 459e8d5bbf7SChen-Yu Tsai static struct sunxi_engine * 460e8d5bbf7SChen-Yu Tsai sun4i_tcon_find_engine_traverse(struct sun4i_drv *drv, 461b317fa3bSChen-Yu Tsai struct device_node *node) 462b317fa3bSChen-Yu Tsai { 463b317fa3bSChen-Yu Tsai struct device_node *port, *ep, *remote; 464be3fe0f9SChen-Yu Tsai struct sunxi_engine *engine = ERR_PTR(-EINVAL); 465b317fa3bSChen-Yu Tsai 466b317fa3bSChen-Yu Tsai port = of_graph_get_port_by_id(node, 0); 467b317fa3bSChen-Yu Tsai if (!port) 468b317fa3bSChen-Yu Tsai return ERR_PTR(-EINVAL); 469b317fa3bSChen-Yu Tsai 4701469619dSChen-Yu Tsai /* 4711469619dSChen-Yu Tsai * This only works if there is only one path from the TCON 4721469619dSChen-Yu Tsai * to any display engine. Otherwise the probe order of the 4731469619dSChen-Yu Tsai * TCONs and display engines is not guaranteed. They may 4741469619dSChen-Yu Tsai * either bind to the wrong one, or worse, bind to the same 4751469619dSChen-Yu Tsai * one if additional checks are not done. 4761469619dSChen-Yu Tsai * 4771469619dSChen-Yu Tsai * Bail out if there are multiple input connections. 4781469619dSChen-Yu Tsai */ 479be3fe0f9SChen-Yu Tsai if (of_get_available_child_count(port) != 1) 480be3fe0f9SChen-Yu Tsai goto out_put_port; 4811469619dSChen-Yu Tsai 482be3fe0f9SChen-Yu Tsai /* Get the first connection without specifying an ID */ 483be3fe0f9SChen-Yu Tsai ep = of_get_next_available_child(port, NULL); 484be3fe0f9SChen-Yu Tsai if (!ep) 485be3fe0f9SChen-Yu Tsai goto out_put_port; 486be3fe0f9SChen-Yu Tsai 487b317fa3bSChen-Yu Tsai remote = of_graph_get_remote_port_parent(ep); 488b317fa3bSChen-Yu Tsai if (!remote) 489be3fe0f9SChen-Yu Tsai goto out_put_ep; 490b317fa3bSChen-Yu Tsai 49187969338SIcenowy Zheng /* does this node match any registered engines? */ 492be3fe0f9SChen-Yu Tsai list_for_each_entry(engine, &drv->engine_list, list) 493be3fe0f9SChen-Yu Tsai if (remote == engine->node) 494be3fe0f9SChen-Yu Tsai goto out_put_remote; 495b317fa3bSChen-Yu Tsai 496b317fa3bSChen-Yu Tsai /* keep looking through upstream ports */ 497e8d5bbf7SChen-Yu Tsai engine = sun4i_tcon_find_engine_traverse(drv, remote); 498b317fa3bSChen-Yu Tsai 499be3fe0f9SChen-Yu Tsai out_put_remote: 500be3fe0f9SChen-Yu Tsai of_node_put(remote); 501be3fe0f9SChen-Yu Tsai out_put_ep: 502be3fe0f9SChen-Yu Tsai of_node_put(ep); 503be3fe0f9SChen-Yu Tsai out_put_port: 504be3fe0f9SChen-Yu Tsai of_node_put(port); 505be3fe0f9SChen-Yu Tsai 506be3fe0f9SChen-Yu Tsai return engine; 507b317fa3bSChen-Yu Tsai } 508b317fa3bSChen-Yu Tsai 509e8d5bbf7SChen-Yu Tsai /* 510e8d5bbf7SChen-Yu Tsai * The device tree binding says that the remote endpoint ID of any 511e8d5bbf7SChen-Yu Tsai * connection between components, up to and including the TCON, of 512e8d5bbf7SChen-Yu Tsai * the display pipeline should be equal to the actual ID of the local 513e8d5bbf7SChen-Yu Tsai * component. Thus we can look at any one of the input connections of 514e8d5bbf7SChen-Yu Tsai * the TCONs, and use that connection's remote endpoint ID as our own. 515e8d5bbf7SChen-Yu Tsai * 516e8d5bbf7SChen-Yu Tsai * Since the user of this function already finds the input port, 517e8d5bbf7SChen-Yu Tsai * the port is passed in directly without further checks. 518e8d5bbf7SChen-Yu Tsai */ 519e8d5bbf7SChen-Yu Tsai static int sun4i_tcon_of_get_id_from_port(struct device_node *port) 520e8d5bbf7SChen-Yu Tsai { 521e8d5bbf7SChen-Yu Tsai struct device_node *ep; 522e8d5bbf7SChen-Yu Tsai int ret = -EINVAL; 523e8d5bbf7SChen-Yu Tsai 524e8d5bbf7SChen-Yu Tsai /* try finding an upstream endpoint */ 525e8d5bbf7SChen-Yu Tsai for_each_available_child_of_node(port, ep) { 526e8d5bbf7SChen-Yu Tsai struct device_node *remote; 527e8d5bbf7SChen-Yu Tsai u32 reg; 528e8d5bbf7SChen-Yu Tsai 529e8d5bbf7SChen-Yu Tsai remote = of_graph_get_remote_endpoint(ep); 530e8d5bbf7SChen-Yu Tsai if (!remote) 531e8d5bbf7SChen-Yu Tsai continue; 532e8d5bbf7SChen-Yu Tsai 533e8d5bbf7SChen-Yu Tsai ret = of_property_read_u32(remote, "reg", ®); 534e8d5bbf7SChen-Yu Tsai if (ret) 535e8d5bbf7SChen-Yu Tsai continue; 536e8d5bbf7SChen-Yu Tsai 537e8d5bbf7SChen-Yu Tsai ret = reg; 538e8d5bbf7SChen-Yu Tsai } 539e8d5bbf7SChen-Yu Tsai 540e8d5bbf7SChen-Yu Tsai return ret; 541e8d5bbf7SChen-Yu Tsai } 542e8d5bbf7SChen-Yu Tsai 543e8d5bbf7SChen-Yu Tsai /* 544e8d5bbf7SChen-Yu Tsai * Once we know the TCON's id, we can look through the list of 545e8d5bbf7SChen-Yu Tsai * engines to find a matching one. We assume all engines have 546e8d5bbf7SChen-Yu Tsai * been probed and added to the list. 547e8d5bbf7SChen-Yu Tsai */ 548e8d5bbf7SChen-Yu Tsai static struct sunxi_engine *sun4i_tcon_get_engine_by_id(struct sun4i_drv *drv, 549e8d5bbf7SChen-Yu Tsai int id) 550e8d5bbf7SChen-Yu Tsai { 551e8d5bbf7SChen-Yu Tsai struct sunxi_engine *engine; 552e8d5bbf7SChen-Yu Tsai 553e8d5bbf7SChen-Yu Tsai list_for_each_entry(engine, &drv->engine_list, list) 554e8d5bbf7SChen-Yu Tsai if (engine->id == id) 555e8d5bbf7SChen-Yu Tsai return engine; 556e8d5bbf7SChen-Yu Tsai 557e8d5bbf7SChen-Yu Tsai return ERR_PTR(-EINVAL); 558e8d5bbf7SChen-Yu Tsai } 559e8d5bbf7SChen-Yu Tsai 560e8d5bbf7SChen-Yu Tsai /* 561e8d5bbf7SChen-Yu Tsai * On SoCs with the old display pipeline design (Display Engine 1.0), 562e8d5bbf7SChen-Yu Tsai * we assumed the TCON was always tied to just one backend. However 563e8d5bbf7SChen-Yu Tsai * this proved not to be the case. On the A31, the TCON can select 564e8d5bbf7SChen-Yu Tsai * either backend as its source. On the A20 (and likely on the A10), 565e8d5bbf7SChen-Yu Tsai * the backend can choose which TCON to output to. 566e8d5bbf7SChen-Yu Tsai * 567e8d5bbf7SChen-Yu Tsai * The device tree binding says that the remote endpoint ID of any 568e8d5bbf7SChen-Yu Tsai * connection between components, up to and including the TCON, of 569e8d5bbf7SChen-Yu Tsai * the display pipeline should be equal to the actual ID of the local 570e8d5bbf7SChen-Yu Tsai * component. Thus we should be able to look at any one of the input 571e8d5bbf7SChen-Yu Tsai * connections of the TCONs, and use that connection's remote endpoint 572e8d5bbf7SChen-Yu Tsai * ID as our own. 573e8d5bbf7SChen-Yu Tsai * 574e8d5bbf7SChen-Yu Tsai * However the connections between the backend and TCON were assumed 575e8d5bbf7SChen-Yu Tsai * to be always singular, and their endpoit IDs were all incorrectly 576e8d5bbf7SChen-Yu Tsai * set to 0. This means for these old device trees, we cannot just look 577e8d5bbf7SChen-Yu Tsai * up the remote endpoint ID of a TCON input endpoint. TCON1 would be 578e8d5bbf7SChen-Yu Tsai * incorrectly identified as TCON0. 579e8d5bbf7SChen-Yu Tsai * 580e8d5bbf7SChen-Yu Tsai * This function first checks if the TCON node has 2 input endpoints. 581e8d5bbf7SChen-Yu Tsai * If so, then the device tree is a corrected version, and it will use 582e8d5bbf7SChen-Yu Tsai * sun4i_tcon_of_get_id() and sun4i_tcon_get_engine_by_id() from above 583e8d5bbf7SChen-Yu Tsai * to fetch the ID and engine directly. If not, then it is likely an 584e8d5bbf7SChen-Yu Tsai * old device trees, where the endpoint IDs were incorrect, but did not 585e8d5bbf7SChen-Yu Tsai * have endpoint connections between the backend and TCON across 586e8d5bbf7SChen-Yu Tsai * different display pipelines. It will fall back to the old method of 587e8d5bbf7SChen-Yu Tsai * traversing the of_graph to try and find a matching engine by device 588e8d5bbf7SChen-Yu Tsai * node. 589e8d5bbf7SChen-Yu Tsai * 590e8d5bbf7SChen-Yu Tsai * In the case of single display pipeline device trees, either method 591e8d5bbf7SChen-Yu Tsai * works. 592e8d5bbf7SChen-Yu Tsai */ 593e8d5bbf7SChen-Yu Tsai static struct sunxi_engine *sun4i_tcon_find_engine(struct sun4i_drv *drv, 594e8d5bbf7SChen-Yu Tsai struct device_node *node) 595e8d5bbf7SChen-Yu Tsai { 596e8d5bbf7SChen-Yu Tsai struct device_node *port; 597e8d5bbf7SChen-Yu Tsai struct sunxi_engine *engine; 598e8d5bbf7SChen-Yu Tsai 599e8d5bbf7SChen-Yu Tsai port = of_graph_get_port_by_id(node, 0); 600e8d5bbf7SChen-Yu Tsai if (!port) 601e8d5bbf7SChen-Yu Tsai return ERR_PTR(-EINVAL); 602e8d5bbf7SChen-Yu Tsai 603e8d5bbf7SChen-Yu Tsai /* 604e8d5bbf7SChen-Yu Tsai * Is this a corrected device tree with cross pipeline 605e8d5bbf7SChen-Yu Tsai * connections between the backend and TCON? 606e8d5bbf7SChen-Yu Tsai */ 607e8d5bbf7SChen-Yu Tsai if (of_get_child_count(port) > 1) { 608e8d5bbf7SChen-Yu Tsai /* Get our ID directly from an upstream endpoint */ 609e8d5bbf7SChen-Yu Tsai int id = sun4i_tcon_of_get_id_from_port(port); 610e8d5bbf7SChen-Yu Tsai 611e8d5bbf7SChen-Yu Tsai /* Get our engine by matching our ID */ 612e8d5bbf7SChen-Yu Tsai engine = sun4i_tcon_get_engine_by_id(drv, id); 613e8d5bbf7SChen-Yu Tsai 614e8d5bbf7SChen-Yu Tsai of_node_put(port); 615e8d5bbf7SChen-Yu Tsai return engine; 616e8d5bbf7SChen-Yu Tsai } 617e8d5bbf7SChen-Yu Tsai 618e8d5bbf7SChen-Yu Tsai /* Fallback to old method by traversing input endpoints */ 619e8d5bbf7SChen-Yu Tsai of_node_put(port); 620e8d5bbf7SChen-Yu Tsai return sun4i_tcon_find_engine_traverse(drv, node); 621e8d5bbf7SChen-Yu Tsai } 622e8d5bbf7SChen-Yu Tsai 6239026e0d1SMaxime Ripard static int sun4i_tcon_bind(struct device *dev, struct device *master, 6249026e0d1SMaxime Ripard void *data) 6259026e0d1SMaxime Ripard { 6269026e0d1SMaxime Ripard struct drm_device *drm = data; 6279026e0d1SMaxime Ripard struct sun4i_drv *drv = drm->dev_private; 62887969338SIcenowy Zheng struct sunxi_engine *engine; 6299026e0d1SMaxime Ripard struct sun4i_tcon *tcon; 6309026e0d1SMaxime Ripard int ret; 6319026e0d1SMaxime Ripard 63287969338SIcenowy Zheng engine = sun4i_tcon_find_engine(drv, dev->of_node); 63387969338SIcenowy Zheng if (IS_ERR(engine)) { 63487969338SIcenowy Zheng dev_err(dev, "Couldn't find matching engine\n"); 63580a58240SChen-Yu Tsai return -EPROBE_DEFER; 636b317fa3bSChen-Yu Tsai } 63780a58240SChen-Yu Tsai 6389026e0d1SMaxime Ripard tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); 6399026e0d1SMaxime Ripard if (!tcon) 6409026e0d1SMaxime Ripard return -ENOMEM; 6419026e0d1SMaxime Ripard dev_set_drvdata(dev, tcon); 6429026e0d1SMaxime Ripard tcon->drm = drm; 643ae558110SMaxime Ripard tcon->dev = dev; 64487969338SIcenowy Zheng tcon->id = engine->id; 64591ea2f29SChen-Yu Tsai tcon->quirks = of_device_get_match_data(dev); 6469026e0d1SMaxime Ripard 6479026e0d1SMaxime Ripard tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); 6489026e0d1SMaxime Ripard if (IS_ERR(tcon->lcd_rst)) { 6499026e0d1SMaxime Ripard dev_err(dev, "Couldn't get our reset line\n"); 6509026e0d1SMaxime Ripard return PTR_ERR(tcon->lcd_rst); 6519026e0d1SMaxime Ripard } 6529026e0d1SMaxime Ripard 6539026e0d1SMaxime Ripard /* Make sure our TCON is reset */ 654d57294c1SChen-Yu Tsai ret = reset_control_reset(tcon->lcd_rst); 6559026e0d1SMaxime Ripard if (ret) { 6569026e0d1SMaxime Ripard dev_err(dev, "Couldn't deassert our reset line\n"); 6579026e0d1SMaxime Ripard return ret; 6589026e0d1SMaxime Ripard } 6599026e0d1SMaxime Ripard 6609026e0d1SMaxime Ripard ret = sun4i_tcon_init_clocks(dev, tcon); 6619026e0d1SMaxime Ripard if (ret) { 6629026e0d1SMaxime Ripard dev_err(dev, "Couldn't init our TCON clocks\n"); 6639026e0d1SMaxime Ripard goto err_assert_reset; 6649026e0d1SMaxime Ripard } 6659026e0d1SMaxime Ripard 6664c7f16d1SChen-Yu Tsai ret = sun4i_tcon_init_regmap(dev, tcon); 6679026e0d1SMaxime Ripard if (ret) { 6684c7f16d1SChen-Yu Tsai dev_err(dev, "Couldn't init our TCON regmap\n"); 6699026e0d1SMaxime Ripard goto err_free_clocks; 6709026e0d1SMaxime Ripard } 6719026e0d1SMaxime Ripard 6724c7f16d1SChen-Yu Tsai ret = sun4i_dclk_create(dev, tcon); 6734c7f16d1SChen-Yu Tsai if (ret) { 6744c7f16d1SChen-Yu Tsai dev_err(dev, "Couldn't create our TCON dot clock\n"); 6754c7f16d1SChen-Yu Tsai goto err_free_clocks; 6764c7f16d1SChen-Yu Tsai } 6774c7f16d1SChen-Yu Tsai 6789026e0d1SMaxime Ripard ret = sun4i_tcon_init_irq(dev, tcon); 6799026e0d1SMaxime Ripard if (ret) { 6809026e0d1SMaxime Ripard dev_err(dev, "Couldn't init our TCON interrupts\n"); 6814c7f16d1SChen-Yu Tsai goto err_free_dotclock; 6829026e0d1SMaxime Ripard } 6839026e0d1SMaxime Ripard 68487969338SIcenowy Zheng tcon->crtc = sun4i_crtc_init(drm, engine, tcon); 68546cce6daSChen-Yu Tsai if (IS_ERR(tcon->crtc)) { 68646cce6daSChen-Yu Tsai dev_err(dev, "Couldn't create our CRTC\n"); 68746cce6daSChen-Yu Tsai ret = PTR_ERR(tcon->crtc); 68846cce6daSChen-Yu Tsai goto err_free_clocks; 68946cce6daSChen-Yu Tsai } 69046cce6daSChen-Yu Tsai 691b9c8506cSChen-Yu Tsai ret = sun4i_rgb_init(drm, tcon); 69213fef095SChen-Yu Tsai if (ret < 0) 69313fef095SChen-Yu Tsai goto err_free_clocks; 69413fef095SChen-Yu Tsai 69527e18de7SChen-Yu Tsai if (tcon->quirks->needs_de_be_mux) { 69627e18de7SChen-Yu Tsai /* 69727e18de7SChen-Yu Tsai * We assume there is no dynamic muxing of backends 69827e18de7SChen-Yu Tsai * and TCONs, so we select the backend with same ID. 69927e18de7SChen-Yu Tsai * 70027e18de7SChen-Yu Tsai * While dynamic selection might be interesting, since 70127e18de7SChen-Yu Tsai * the CRTC is tied to the TCON, while the layers are 70227e18de7SChen-Yu Tsai * tied to the backends, this means, we will need to 70327e18de7SChen-Yu Tsai * switch between groups of layers. There might not be 70427e18de7SChen-Yu Tsai * a way to represent this constraint in DRM. 70527e18de7SChen-Yu Tsai */ 70627e18de7SChen-Yu Tsai regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, 70727e18de7SChen-Yu Tsai SUN4I_TCON0_CTL_SRC_SEL_MASK, 70827e18de7SChen-Yu Tsai tcon->id); 70927e18de7SChen-Yu Tsai regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, 71027e18de7SChen-Yu Tsai SUN4I_TCON1_CTL_SRC_SEL_MASK, 71127e18de7SChen-Yu Tsai tcon->id); 71227e18de7SChen-Yu Tsai } 71327e18de7SChen-Yu Tsai 71480a58240SChen-Yu Tsai list_add_tail(&tcon->list, &drv->tcon_list); 71580a58240SChen-Yu Tsai 71613fef095SChen-Yu Tsai return 0; 7179026e0d1SMaxime Ripard 7184c7f16d1SChen-Yu Tsai err_free_dotclock: 7194c7f16d1SChen-Yu Tsai sun4i_dclk_free(tcon); 7209026e0d1SMaxime Ripard err_free_clocks: 7219026e0d1SMaxime Ripard sun4i_tcon_free_clocks(tcon); 7229026e0d1SMaxime Ripard err_assert_reset: 7239026e0d1SMaxime Ripard reset_control_assert(tcon->lcd_rst); 7249026e0d1SMaxime Ripard return ret; 7259026e0d1SMaxime Ripard } 7269026e0d1SMaxime Ripard 7279026e0d1SMaxime Ripard static void sun4i_tcon_unbind(struct device *dev, struct device *master, 7289026e0d1SMaxime Ripard void *data) 7299026e0d1SMaxime Ripard { 7309026e0d1SMaxime Ripard struct sun4i_tcon *tcon = dev_get_drvdata(dev); 7319026e0d1SMaxime Ripard 73280a58240SChen-Yu Tsai list_del(&tcon->list); 7334c7f16d1SChen-Yu Tsai sun4i_dclk_free(tcon); 7349026e0d1SMaxime Ripard sun4i_tcon_free_clocks(tcon); 7359026e0d1SMaxime Ripard } 7369026e0d1SMaxime Ripard 737dfeb693dSJulia Lawall static const struct component_ops sun4i_tcon_ops = { 7389026e0d1SMaxime Ripard .bind = sun4i_tcon_bind, 7399026e0d1SMaxime Ripard .unbind = sun4i_tcon_unbind, 7409026e0d1SMaxime Ripard }; 7419026e0d1SMaxime Ripard 7429026e0d1SMaxime Ripard static int sun4i_tcon_probe(struct platform_device *pdev) 7439026e0d1SMaxime Ripard { 74429e57fabSMaxime Ripard struct device_node *node = pdev->dev.of_node; 745894f5a9fSMaxime Ripard struct drm_bridge *bridge; 74629e57fabSMaxime Ripard struct drm_panel *panel; 747ebc94461SRob Herring int ret; 74829e57fabSMaxime Ripard 749ebc94461SRob Herring ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge); 750ebc94461SRob Herring if (ret == -EPROBE_DEFER) 751ebc94461SRob Herring return ret; 75229e57fabSMaxime Ripard 7539026e0d1SMaxime Ripard return component_add(&pdev->dev, &sun4i_tcon_ops); 7549026e0d1SMaxime Ripard } 7559026e0d1SMaxime Ripard 7569026e0d1SMaxime Ripard static int sun4i_tcon_remove(struct platform_device *pdev) 7579026e0d1SMaxime Ripard { 7589026e0d1SMaxime Ripard component_del(&pdev->dev, &sun4i_tcon_ops); 7599026e0d1SMaxime Ripard 7609026e0d1SMaxime Ripard return 0; 7619026e0d1SMaxime Ripard } 7629026e0d1SMaxime Ripard 763*ad537fb2SChen-Yu Tsai /* platform specific TCON muxing callbacks */ 764*ad537fb2SChen-Yu Tsai static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon, 765*ad537fb2SChen-Yu Tsai struct drm_encoder *encoder) 766*ad537fb2SChen-Yu Tsai { 767*ad537fb2SChen-Yu Tsai u32 val; 768*ad537fb2SChen-Yu Tsai 769*ad537fb2SChen-Yu Tsai if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC) 770*ad537fb2SChen-Yu Tsai val = 1; 771*ad537fb2SChen-Yu Tsai else 772*ad537fb2SChen-Yu Tsai val = 0; 773*ad537fb2SChen-Yu Tsai 774*ad537fb2SChen-Yu Tsai /* 775*ad537fb2SChen-Yu Tsai * FIXME: Undocumented bits 776*ad537fb2SChen-Yu Tsai */ 777*ad537fb2SChen-Yu Tsai return regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val); 778*ad537fb2SChen-Yu Tsai } 779*ad537fb2SChen-Yu Tsai 78091ea2f29SChen-Yu Tsai static const struct sun4i_tcon_quirks sun5i_a13_quirks = { 78191ea2f29SChen-Yu Tsai .has_channel_1 = true, 782*ad537fb2SChen-Yu Tsai .set_mux = sun5i_a13_tcon_set_mux, 78391ea2f29SChen-Yu Tsai }; 78491ea2f29SChen-Yu Tsai 78593a5ec14SChen-Yu Tsai static const struct sun4i_tcon_quirks sun6i_a31_quirks = { 78693a5ec14SChen-Yu Tsai .has_channel_1 = true, 78727e18de7SChen-Yu Tsai .needs_de_be_mux = true, 78893a5ec14SChen-Yu Tsai }; 78993a5ec14SChen-Yu Tsai 79093a5ec14SChen-Yu Tsai static const struct sun4i_tcon_quirks sun6i_a31s_quirks = { 79193a5ec14SChen-Yu Tsai .has_channel_1 = true, 79227e18de7SChen-Yu Tsai .needs_de_be_mux = true, 79393a5ec14SChen-Yu Tsai }; 79493a5ec14SChen-Yu Tsai 79591ea2f29SChen-Yu Tsai static const struct sun4i_tcon_quirks sun8i_a33_quirks = { 79691ea2f29SChen-Yu Tsai /* nothing is supported */ 79791ea2f29SChen-Yu Tsai }; 79891ea2f29SChen-Yu Tsai 7991a0edb3fSIcenowy Zheng static const struct sun4i_tcon_quirks sun8i_v3s_quirks = { 8001a0edb3fSIcenowy Zheng /* nothing is supported */ 8011a0edb3fSIcenowy Zheng }; 8021a0edb3fSIcenowy Zheng 8039026e0d1SMaxime Ripard static const struct of_device_id sun4i_tcon_of_table[] = { 80491ea2f29SChen-Yu Tsai { .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks }, 80593a5ec14SChen-Yu Tsai { .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks }, 80693a5ec14SChen-Yu Tsai { .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks }, 80791ea2f29SChen-Yu Tsai { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks }, 8081a0edb3fSIcenowy Zheng { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks }, 8099026e0d1SMaxime Ripard { } 8109026e0d1SMaxime Ripard }; 8119026e0d1SMaxime Ripard MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); 8129026e0d1SMaxime Ripard 8139026e0d1SMaxime Ripard static struct platform_driver sun4i_tcon_platform_driver = { 8149026e0d1SMaxime Ripard .probe = sun4i_tcon_probe, 8159026e0d1SMaxime Ripard .remove = sun4i_tcon_remove, 8169026e0d1SMaxime Ripard .driver = { 8179026e0d1SMaxime Ripard .name = "sun4i-tcon", 8189026e0d1SMaxime Ripard .of_match_table = sun4i_tcon_of_table, 8199026e0d1SMaxime Ripard }, 8209026e0d1SMaxime Ripard }; 8219026e0d1SMaxime Ripard module_platform_driver(sun4i_tcon_platform_driver); 8229026e0d1SMaxime Ripard 8239026e0d1SMaxime Ripard MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); 8249026e0d1SMaxime Ripard MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver"); 8259026e0d1SMaxime Ripard MODULE_LICENSE("GPL"); 826