diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-03-25 03:53:42 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-03-25 03:53:42 -0300 |
commit | 03dd4cb26d967f9588437b0fc9cc0e8353322bb7 (patch) | |
tree | fa581f6dc1c0596391690d1f67eceef3af8246dc /drivers/gpu/drm/rockchip | |
parent | d4e493caf788ef44982e131ff9c786546904d934 (diff) |
Linux-libre 4.5-gnu
Diffstat (limited to 'drivers/gpu/drm/rockchip')
-rw-r--r-- | drivers/gpu/drm/rockchip/Kconfig | 10 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/Makefile | 8 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/dw-mipi-dsi.c | 1195 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 20 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 29 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 20 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 151 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h | 11 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 1210 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 230 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_vop_reg.c | 316 | ||||
-rw-r--r-- | drivers/gpu/drm/rockchip/rockchip_vop_reg.h | 169 |
14 files changed, 2431 insertions, 949 deletions
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 35215f686..85739859d 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -25,3 +25,13 @@ config ROCKCHIP_DW_HDMI for the Synopsys DesignWare HDMI driver. If you want to enable HDMI on RK3288 based SoC, you should selet this option. + +config ROCKCHIP_DW_MIPI_DSI + tristate "Rockchip specific extensions for Synopsys DW MIPI DSI" + depends on DRM_ROCKCHIP + select DRM_MIPI_DSI + help + This selects support for Rockchip SoC specific extensions + for the Synopsys DesignWare HDMI driver. If you want to + enable MIPI DSI on RK3288 based SoC, you should selet this + option. diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index f3d8a19c6..f6a809afc 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -2,9 +2,11 @@ # Makefile for the drm device driver. This driver provides support for the # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. -rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o rockchip_drm_fbdev.o \ - rockchip_drm_gem.o +rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \ + rockchip_drm_gem.o rockchip_drm_vop.o +rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o +obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o -obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o rockchip_drm_vop.o +obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o rockchip_vop_reg.o diff --git a/drivers/gpu/drm/rockchip/dw-mipi-dsi.c b/drivers/gpu/drm/rockchip/dw-mipi-dsi.c new file mode 100644 index 000000000..f8f8f29fb --- /dev/null +++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi.c @@ -0,0 +1,1195 @@ +/* + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/iopoll.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drmP.h> +#include <video/mipi_display.h> + +#include "rockchip_drm_drv.h" +#include "rockchip_drm_vop.h" + +#define DRIVER_NAME "dw-mipi-dsi" + +#define GRF_SOC_CON6 0x025c +#define DSI0_SEL_VOP_LIT (1 << 6) +#define DSI1_SEL_VOP_LIT (1 << 9) + +#define DSI_VERSION 0x00 +#define DSI_PWR_UP 0x04 +#define RESET 0 +#define POWERUP BIT(0) + +#define DSI_CLKMGR_CFG 0x08 +#define TO_CLK_DIVIDSION(div) (((div) & 0xff) << 8) +#define TX_ESC_CLK_DIVIDSION(div) (((div) & 0xff) << 0) + +#define DSI_DPI_VCID 0x0c +#define DPI_VID(vid) (((vid) & 0x3) << 0) + +#define DSI_DPI_COLOR_CODING 0x10 +#define EN18_LOOSELY BIT(8) +#define DPI_COLOR_CODING_16BIT_1 0x0 +#define DPI_COLOR_CODING_16BIT_2 0x1 +#define DPI_COLOR_CODING_16BIT_3 0x2 +#define DPI_COLOR_CODING_18BIT_1 0x3 +#define DPI_COLOR_CODING_18BIT_2 0x4 +#define DPI_COLOR_CODING_24BIT 0x5 + +#define DSI_DPI_CFG_POL 0x14 +#define COLORM_ACTIVE_LOW BIT(4) +#define SHUTD_ACTIVE_LOW BIT(3) +#define HSYNC_ACTIVE_LOW BIT(2) +#define VSYNC_ACTIVE_LOW BIT(1) +#define DATAEN_ACTIVE_LOW BIT(0) + +#define DSI_DPI_LP_CMD_TIM 0x18 +#define OUTVACT_LPCMD_TIME(p) (((p) & 0xff) << 16) +#define INVACT_LPCMD_TIME(p) ((p) & 0xff) + +#define DSI_DBI_CFG 0x20 +#define DSI_DBI_CMDSIZE 0x28 + +#define DSI_PCKHDL_CFG 0x2c +#define EN_CRC_RX BIT(4) +#define EN_ECC_RX BIT(3) +#define EN_BTA BIT(2) +#define EN_EOTP_RX BIT(1) +#define EN_EOTP_TX BIT(0) + +#define DSI_MODE_CFG 0x34 +#define ENABLE_VIDEO_MODE 0 +#define ENABLE_CMD_MODE BIT(0) + +#define DSI_VID_MODE_CFG 0x38 +#define FRAME_BTA_ACK BIT(14) +#define ENABLE_LOW_POWER (0x3f << 8) +#define ENABLE_LOW_POWER_MASK (0x3f << 8) +#define VID_MODE_TYPE_BURST_SYNC_PULSES 0x2 +#define VID_MODE_TYPE_MASK 0x3 + +#define DSI_VID_PKT_SIZE 0x3c +#define VID_PKT_SIZE(p) (((p) & 0x3fff) << 0) +#define VID_PKT_MAX_SIZE 0x3fff + +#define DSI_VID_HSA_TIME 0x48 +#define DSI_VID_HBP_TIME 0x4c +#define DSI_VID_HLINE_TIME 0x50 +#define DSI_VID_VSA_LINES 0x54 +#define DSI_VID_VBP_LINES 0x58 +#define DSI_VID_VFP_LINES 0x5c +#define DSI_VID_VACTIVE_LINES 0x60 +#define DSI_CMD_MODE_CFG 0x68 +#define MAX_RD_PKT_SIZE_LP BIT(24) +#define DCS_LW_TX_LP BIT(19) +#define DCS_SR_0P_TX_LP BIT(18) +#define DCS_SW_1P_TX_LP BIT(17) +#define DCS_SW_0P_TX_LP BIT(16) +#define GEN_LW_TX_LP BIT(14) +#define GEN_SR_2P_TX_LP BIT(13) +#define GEN_SR_1P_TX_LP BIT(12) +#define GEN_SR_0P_TX_LP BIT(11) +#define GEN_SW_2P_TX_LP BIT(10) +#define GEN_SW_1P_TX_LP BIT(9) +#define GEN_SW_0P_TX_LP BIT(8) +#define EN_ACK_RQST BIT(1) +#define EN_TEAR_FX BIT(0) + +#define CMD_MODE_ALL_LP (MAX_RD_PKT_SIZE_LP | \ + DCS_LW_TX_LP | \ + DCS_SR_0P_TX_LP | \ + DCS_SW_1P_TX_LP | \ + DCS_SW_0P_TX_LP | \ + GEN_LW_TX_LP | \ + GEN_SR_2P_TX_LP | \ + GEN_SR_1P_TX_LP | \ + GEN_SR_0P_TX_LP | \ + GEN_SW_2P_TX_LP | \ + GEN_SW_1P_TX_LP | \ + GEN_SW_0P_TX_LP) + +#define DSI_GEN_HDR 0x6c +#define GEN_HDATA(data) (((data) & 0xffff) << 8) +#define GEN_HDATA_MASK (0xffff << 8) +#define GEN_HTYPE(type) (((type) & 0xff) << 0) +#define GEN_HTYPE_MASK 0xff + +#define DSI_GEN_PLD_DATA 0x70 + +#define DSI_CMD_PKT_STATUS 0x74 +#define GEN_CMD_EMPTY BIT(0) +#define GEN_CMD_FULL BIT(1) +#define GEN_PLD_W_EMPTY BIT(2) +#define GEN_PLD_W_FULL BIT(3) +#define GEN_PLD_R_EMPTY BIT(4) +#define GEN_PLD_R_FULL BIT(5) +#define GEN_RD_CMD_BUSY BIT(6) + +#define DSI_TO_CNT_CFG 0x78 +#define HSTX_TO_CNT(p) (((p) & 0xffff) << 16) +#define LPRX_TO_CNT(p) ((p) & 0xffff) + +#define DSI_BTA_TO_CNT 0x8c + +#define DSI_LPCLK_CTRL 0x94 +#define AUTO_CLKLANE_CTRL BIT(1) +#define PHY_TXREQUESTCLKHS BIT(0) + +#define DSI_PHY_TMR_LPCLK_CFG 0x98 +#define PHY_CLKHS2LP_TIME(lbcc) (((lbcc) & 0x3ff) << 16) +#define PHY_CLKLP2HS_TIME(lbcc) ((lbcc) & 0x3ff) + +#define DSI_PHY_TMR_CFG 0x9c +#define PHY_HS2LP_TIME(lbcc) (((lbcc) & 0xff) << 24) +#define PHY_LP2HS_TIME(lbcc) (((lbcc) & 0xff) << 16) +#define MAX_RD_TIME(lbcc) ((lbcc) & 0x7fff) + +#define DSI_PHY_RSTZ 0xa0 +#define PHY_DISFORCEPLL 0 +#define PHY_ENFORCEPLL BIT(3) +#define PHY_DISABLECLK 0 +#define PHY_ENABLECLK BIT(2) +#define PHY_RSTZ 0 +#define PHY_UNRSTZ BIT(1) +#define PHY_SHUTDOWNZ 0 +#define PHY_UNSHUTDOWNZ BIT(0) + +#define DSI_PHY_IF_CFG 0xa4 +#define N_LANES(n) ((((n) - 1) & 0x3) << 0) +#define PHY_STOP_WAIT_TIME(cycle) (((cycle) & 0xff) << 8) + +#define DSI_PHY_STATUS 0xb0 +#define LOCK BIT(0) +#define STOP_STATE_CLK_LANE BIT(2) + +#define DSI_PHY_TST_CTRL0 0xb4 +#define PHY_TESTCLK BIT(1) +#define PHY_UNTESTCLK 0 +#define PHY_TESTCLR BIT(0) +#define PHY_UNTESTCLR 0 + +#define DSI_PHY_TST_CTRL1 0xb8 +#define PHY_TESTEN BIT(16) +#define PHY_UNTESTEN 0 +#define PHY_TESTDOUT(n) (((n) & 0xff) << 8) +#define PHY_TESTDIN(n) (((n) & 0xff) << 0) + +#define DSI_INT_ST0 0xbc +#define DSI_INT_ST1 0xc0 +#define DSI_INT_MSK0 0xc4 +#define DSI_INT_MSK1 0xc8 + +#define PHY_STATUS_TIMEOUT_US 10000 +#define CMD_PKT_STATUS_TIMEOUT_US 20000 + +#define BYPASS_VCO_RANGE BIT(7) +#define VCO_RANGE_CON_SEL(val) (((val) & 0x7) << 3) +#define VCO_IN_CAP_CON_DEFAULT (0x0 << 1) +#define VCO_IN_CAP_CON_LOW (0x1 << 1) +#define VCO_IN_CAP_CON_HIGH (0x2 << 1) +#define REF_BIAS_CUR_SEL BIT(0) + +#define CP_CURRENT_3MA BIT(3) +#define CP_PROGRAM_EN BIT(7) +#define LPF_PROGRAM_EN BIT(6) +#define LPF_RESISTORS_20_KOHM 0 + +#define HSFREQRANGE_SEL(val) (((val) & 0x3f) << 1) + +#define INPUT_DIVIDER(val) ((val - 1) & 0x7f) +#define LOW_PROGRAM_EN 0 +#define HIGH_PROGRAM_EN BIT(7) +#define LOOP_DIV_LOW_SEL(val) ((val - 1) & 0x1f) +#define LOOP_DIV_HIGH_SEL(val) (((val - 1) >> 5) & 0x1f) +#define PLL_LOOP_DIV_EN BIT(5) +#define PLL_INPUT_DIV_EN BIT(4) + +#define POWER_CONTROL BIT(6) +#define INTERNAL_REG_CURRENT BIT(3) +#define BIAS_BLOCK_ON BIT(2) +#define BANDGAP_ON BIT(0) + +#define TER_RESISTOR_HIGH BIT(7) +#define TER_RESISTOR_LOW 0 +#define LEVEL_SHIFTERS_ON BIT(6) +#define TER_CAL_DONE BIT(5) +#define SETRD_MAX (0x7 << 2) +#define POWER_MANAGE BIT(1) +#define TER_RESISTORS_ON BIT(0) + +#define BIASEXTR_SEL(val) ((val) & 0x7) +#define BANDGAP_SEL(val) ((val) & 0x7) +#define TLP_PROGRAM_EN BIT(7) +#define THS_PRE_PROGRAM_EN BIT(7) +#define THS_ZERO_PROGRAM_EN BIT(6) + +enum { + BANDGAP_97_07, + BANDGAP_98_05, + BANDGAP_99_02, + BANDGAP_100_00, + BANDGAP_93_17, + BANDGAP_94_15, + BANDGAP_95_12, + BANDGAP_96_10, +}; + +enum { + BIASEXTR_87_1, + BIASEXTR_91_5, + BIASEXTR_95_9, + BIASEXTR_100, + BIASEXTR_105_94, + BIASEXTR_111_88, + BIASEXTR_118_8, + BIASEXTR_127_7, +}; + +struct dw_mipi_dsi_plat_data { + unsigned int max_data_lanes; + enum drm_mode_status (*mode_valid)(struct drm_connector *connector, + struct drm_display_mode *mode); +}; + +struct dw_mipi_dsi { + struct drm_encoder encoder; + struct drm_connector connector; + struct mipi_dsi_host dsi_host; + struct drm_panel *panel; + struct device *dev; + struct regmap *grf_regmap; + void __iomem *base; + + struct clk *pllref_clk; + struct clk *pclk; + + unsigned int lane_mbps; /* per lane */ + u32 channel; + u32 lanes; + u32 format; + u16 input_div; + u16 feedback_div; + struct drm_display_mode *mode; + + const struct dw_mipi_dsi_plat_data *pdata; +}; + +enum dw_mipi_dsi_mode { + DW_MIPI_DSI_CMD_MODE, + DW_MIPI_DSI_VID_MODE, +}; + +struct dphy_pll_testdin_map { + unsigned int max_mbps; + u8 testdin; +}; + +/* The table is based on 27MHz DPHY pll reference clock. */ +static const struct dphy_pll_testdin_map dptdin_map[] = { + { 90, 0x00}, { 100, 0x10}, { 110, 0x20}, { 130, 0x01}, + { 140, 0x11}, { 150, 0x21}, { 170, 0x02}, { 180, 0x12}, + { 200, 0x22}, { 220, 0x03}, { 240, 0x13}, { 250, 0x23}, + { 270, 0x04}, { 300, 0x14}, { 330, 0x05}, { 360, 0x15}, + { 400, 0x25}, { 450, 0x06}, { 500, 0x16}, { 550, 0x07}, + { 600, 0x17}, { 650, 0x08}, { 700, 0x18}, { 750, 0x09}, + { 800, 0x19}, { 850, 0x29}, { 900, 0x39}, { 950, 0x0a}, + {1000, 0x1a}, {1050, 0x2a}, {1100, 0x3a}, {1150, 0x0b}, + {1200, 0x1b}, {1250, 0x2b}, {1300, 0x3b}, {1350, 0x0c}, + {1400, 0x1c}, {1450, 0x2c}, {1500, 0x3c} +}; + +static int max_mbps_to_testdin(unsigned int max_mbps) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dptdin_map); i++) + if (dptdin_map[i].max_mbps > max_mbps) + return dptdin_map[i].testdin; + + return -EINVAL; +} + +/* + * The controller should generate 2 frames before + * preparing the peripheral. + */ +static void dw_mipi_dsi_wait_for_two_frames(struct dw_mipi_dsi *dsi) +{ + int refresh, two_frames; + + refresh = drm_mode_vrefresh(dsi->mode); + two_frames = DIV_ROUND_UP(MSEC_PER_SEC, refresh) * 2; + msleep(two_frames); +} + +static inline struct dw_mipi_dsi *host_to_dsi(struct mipi_dsi_host *host) +{ + return container_of(host, struct dw_mipi_dsi, dsi_host); +} + +static inline struct dw_mipi_dsi *con_to_dsi(struct drm_connector *con) +{ + return container_of(con, struct dw_mipi_dsi, connector); +} + +static inline struct dw_mipi_dsi *encoder_to_dsi(struct drm_encoder *encoder) +{ + return container_of(encoder, struct dw_mipi_dsi, encoder); +} +static inline void dsi_write(struct dw_mipi_dsi *dsi, u32 reg, u32 val) +{ + writel(val, dsi->base + reg); +} + +static inline u32 dsi_read(struct dw_mipi_dsi *dsi, u32 reg) +{ + return readl(dsi->base + reg); +} + +static void dw_mipi_dsi_phy_write(struct dw_mipi_dsi *dsi, u8 test_code, + u8 test_data) +{ + /* + * With the falling edge on TESTCLK, the TESTDIN[7:0] signal content + * is latched internally as the current test code. Test data is + * programmed internally by rising edge on TESTCLK. + */ + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR); + + dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_TESTEN | PHY_TESTDOUT(0) | + PHY_TESTDIN(test_code)); + + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLK | PHY_UNTESTCLR); + + dsi_write(dsi, DSI_PHY_TST_CTRL1, PHY_UNTESTEN | PHY_TESTDOUT(0) | + PHY_TESTDIN(test_data)); + + dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLK | PHY_UNTESTCLR); +} + +static int dw_mipi_dsi_phy_init(struct dw_mipi_dsi *dsi) +{ + int ret, testdin, vco, val; + + vco = (dsi->lane_mbps < 200) ? 0 : (dsi->lane_mbps + 100) / 200; + + testdin = max_mbps_to_testdin(dsi->lane_mbps); + if (testdin < 0) { + dev_err(dsi->dev, + "failed to get testdin for %dmbps lane clock\n", + dsi->lane_mbps); + return testdin; + } + + dsi_write(dsi, DSI_PWR_UP, POWERUP); + + dw_mipi_dsi_phy_write(dsi, 0x10, BYPASS_VCO_RANGE | + VCO_RANGE_CON_SEL(vco) | + VCO_IN_CAP_CON_LOW | + REF_BIAS_CUR_SEL); + + dw_mipi_dsi_phy_write(dsi, 0x11, CP_CURRENT_3MA); + dw_mipi_dsi_phy_write(dsi, 0x12, CP_PROGRAM_EN | LPF_PROGRAM_EN | + LPF_RESISTORS_20_KOHM); + + dw_mipi_dsi_phy_write(dsi, 0x44, HSFREQRANGE_SEL(testdin)); + + dw_mipi_dsi_phy_write(dsi, 0x19, PLL_LOOP_DIV_EN | PLL_INPUT_DIV_EN); + dw_mipi_dsi_phy_write(dsi, 0x17, INPUT_DIVIDER(dsi->input_div)); + dw_mipi_dsi_phy_write(dsi, 0x18, LOOP_DIV_LOW_SEL(dsi->feedback_div) | + LOW_PROGRAM_EN); + dw_mipi_dsi_phy_write(dsi, 0x18, LOOP_DIV_HIGH_SEL(dsi->feedback_div) | + HIGH_PROGRAM_EN); + + dw_mipi_dsi_phy_write(dsi, 0x20, POWER_CONTROL | INTERNAL_REG_CURRENT | + BIAS_BLOCK_ON | BANDGAP_ON); + + dw_mipi_dsi_phy_write(dsi, 0x21, TER_RESISTOR_LOW | TER_CAL_DONE | + SETRD_MAX | TER_RESISTORS_ON); + dw_mipi_dsi_phy_write(dsi, 0x21, TER_RESISTOR_HIGH | LEVEL_SHIFTERS_ON | + SETRD_MAX | POWER_MANAGE | + TER_RESISTORS_ON); + + dw_mipi_dsi_phy_write(dsi, 0x22, LOW_PROGRAM_EN | + BIASEXTR_SEL(BIASEXTR_127_7)); + dw_mipi_dsi_phy_write(dsi, 0x22, HIGH_PROGRAM_EN | + BANDGAP_SEL(BANDGAP_96_10)); + + dw_mipi_dsi_phy_write(dsi, 0x70, TLP_PROGRAM_EN | 0xf); + dw_mipi_dsi_phy_write(dsi, 0x71, THS_PRE_PROGRAM_EN | 0x55); + dw_mipi_dsi_phy_write(dsi, 0x72, THS_ZERO_PROGRAM_EN | 0xa); + + dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENFORCEPLL | PHY_ENABLECLK | + PHY_UNRSTZ | PHY_UNSHUTDOWNZ); + + + ret = readx_poll_timeout(readl, dsi->base + DSI_PHY_STATUS, + val, val & LOCK, 1000, PHY_STATUS_TIMEOUT_US); + if (ret < 0) { + dev_err(dsi->dev, "failed to wait for phy lock state\n"); + return ret; + } + + ret = readx_poll_timeout(readl, dsi->base + DSI_PHY_STATUS, + val, val & STOP_STATE_CLK_LANE, 1000, + PHY_STATUS_TIMEOUT_US); + if (ret < 0) { + dev_err(dsi->dev, + "failed to wait for phy clk lane stop state\n"); + return ret; + } + + return ret; +} + +static int dw_mipi_dsi_get_lane_bps(struct dw_mipi_dsi *dsi) +{ + unsigned int i, pre; + unsigned long mpclk, pllref, tmp; + unsigned int m = 1, n = 1, target_mbps = 1000; + unsigned int max_mbps = dptdin_map[ARRAY_SIZE(dptdin_map) - 1].max_mbps; + int bpp; + + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + if (bpp < 0) { + dev_err(dsi->dev, "failed to get bpp for pixel format %d\n", + dsi->format); + return bpp; + } + + mpclk = DIV_ROUND_UP(dsi->mode->clock, MSEC_PER_SEC); + if (mpclk) { + /* take 1 / 0.9, since mbps must big than bandwidth of RGB */ + tmp = mpclk * (bpp / dsi->lanes) * 10 / 9; + if (tmp < max_mbps) + target_mbps = tmp; + else + dev_err(dsi->dev, "DPHY clock frequency is out of range\n"); + } + + pllref = DIV_ROUND_UP(clk_get_rate(dsi->pllref_clk), USEC_PER_SEC); + tmp = pllref; + + for (i = 1; i < 6; i++) { + pre = pllref / i; + if ((tmp > (target_mbps % pre)) && (target_mbps / pre < 512)) { + tmp = target_mbps % pre; + n = i; + m = target_mbps / pre; + } + if (tmp == 0) + break; + } + + dsi->lane_mbps = pllref / n * m; + dsi->input_div = n; + dsi->feedback_div = m; + + return 0; +} + +static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct dw_mipi_dsi *dsi = host_to_dsi(host); + + if (device->lanes > dsi->pdata->max_data_lanes) { + dev_err(dsi->dev, "the number of data lanes(%u) is too many\n", + device->lanes); + return -EINVAL; + } + + if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) || + !(device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)) { + dev_err(dsi->dev, "device mode is unsupported\n"); + return -EINVAL; + } + + dsi->lanes = device->lanes; + dsi->channel = device->channel; + dsi->format = device->format; + dsi->panel = of_drm_find_panel(device->dev.of_node); + if (dsi->panel) + return drm_panel_attach(dsi->panel, &dsi->connector); + + return -EINVAL; +} + +static int dw_mipi_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct dw_mipi_dsi *dsi = host_to_dsi(host); + + drm_panel_detach(dsi->panel); + + return 0; +} + +static int dw_mipi_dsi_gen_pkt_hdr_write(struct dw_mipi_dsi *dsi, u32 val) +{ + int ret; + + ret = readx_poll_timeout(readl, dsi->base + DSI_CMD_PKT_STATUS, + val, !(val & GEN_CMD_FULL), 1000, + CMD_PKT_STATUS_TIMEOUT_US); + if (ret < 0) { + dev_err(dsi->dev, "failed to get available command FIFO\n"); + return ret; + } + + dsi_write(dsi, DSI_GEN_HDR, val); + + ret = readx_poll_timeout(readl, dsi->base + DSI_CMD_PKT_STATUS, + val, val & (GEN_CMD_EMPTY | GEN_PLD_W_EMPTY), + 1000, CMD_PKT_STATUS_TIMEOUT_US); + if (ret < 0) { + dev_err(dsi->dev, "failed to write command FIFO\n"); + return ret; + } + + return 0; +} + +static int dw_mipi_dsi_dcs_short_write(struct dw_mipi_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + const u16 *tx_buf = msg->tx_buf; + u32 val = GEN_HDATA(*tx_buf) | GEN_HTYPE(msg->type); + + if (msg->tx_len > 2) { + dev_err(dsi->dev, "too long tx buf length %zu for short write\n", + msg->tx_len); + return -EINVAL; + } + + return dw_mipi_dsi_gen_pkt_hdr_write(dsi, val); +} + +static int dw_mipi_dsi_dcs_long_write(struct dw_mipi_dsi *dsi, + const struct mipi_dsi_msg *msg) +{ + const u32 *tx_buf = msg->tx_buf; + int len = msg->tx_len, pld_data_bytes = sizeof(*tx_buf), ret; + u32 val = GEN_HDATA(msg->tx_len) | GEN_HTYPE(msg->type); + u32 remainder = 0; + + if (msg->tx_len < 3) { + dev_err(dsi->dev, "wrong tx buf length %zu for long write\n", + msg->tx_len); + return -EINVAL; + } + + while (DIV_ROUND_UP(len, pld_data_bytes)) { + if (len < pld_data_bytes) { + memcpy(&remainder, tx_buf, len); + dsi_write(dsi, DSI_GEN_PLD_DATA, remainder); + len = 0; + } else { + dsi_write(dsi, DSI_GEN_PLD_DATA, *tx_buf); + tx_buf++; + len -= pld_data_bytes; + } + + ret = readx_poll_timeout(readl, dsi->base + DSI_CMD_PKT_STATUS, + val, !(val & GEN_PLD_W_FULL), 1000, + CMD_PKT_STATUS_TIMEOUT_US); + if (ret < 0) { + dev_err(dsi->dev, + "failed to get available write payload FIFO\n"); + return ret; + } + } + + return dw_mipi_dsi_gen_pkt_hdr_write(dsi, val); +} + +static ssize_t dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct dw_mipi_dsi *dsi = host_to_dsi(host); + int ret; + + switch (msg->type) { + case MIPI_DSI_DCS_SHORT_WRITE: + case MIPI_DSI_DCS_SHORT_WRITE_PARAM: + case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE: + ret = dw_mipi_dsi_dcs_short_write(dsi, msg); + break; + case MIPI_DSI_DCS_LONG_WRITE: + ret = dw_mipi_dsi_dcs_long_write(dsi, msg); + break; + default: + dev_err(dsi->dev, "unsupported message type\n"); + ret = -EINVAL; + } + + return ret; +} + +static const struct mipi_dsi_host_ops dw_mipi_dsi_host_ops = { + .attach = dw_mipi_dsi_host_attach, + .detach = dw_mipi_dsi_host_detach, + .transfer = dw_mipi_dsi_host_transfer, +}; + +static void dw_mipi_dsi_video_mode_config(struct dw_mipi_dsi *dsi) +{ + u32 val; + + val = VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER; + + dsi_write(dsi, DSI_VID_MODE_CFG, val); +} + +static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi, + enum dw_mipi_dsi_mode mode) +{ + if (mode == DW_MIPI_DSI_CMD_MODE) { + dsi_write(dsi, DSI_PWR_UP, RESET); + dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE); + dsi_write(dsi, DSI_PWR_UP, POWERUP); + } else { + dsi_write(dsi, DSI_PWR_UP, RESET); + dsi_write(dsi, DSI_MODE_CFG, ENABLE_VIDEO_MODE); + dw_mipi_dsi_video_mode_config(dsi); + dsi_write(dsi, DSI_PWR_UP, POWERUP); + } +} + +static void dw_mipi_dsi_disable(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PWR_UP, RESET); + dsi_write(dsi, DSI_PHY_RSTZ, PHY_RSTZ); +} + +static void dw_mipi_dsi_init(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PWR_UP, RESET); + dsi_write(dsi, DSI_PHY_RSTZ, PHY_DISFORCEPLL | PHY_DISABLECLK + | PHY_RSTZ | PHY_SHUTDOWNZ); + dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVIDSION(10) | + TX_ESC_CLK_DIVIDSION(7)); + dsi_write(dsi, DSI_LPCLK_CTRL, PHY_TXREQUESTCLKHS); +} + +static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi, + struct drm_display_mode *mode) +{ + u32 val = 0, color = 0; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + color = DPI_COLOR_CODING_24BIT; + break; + case MIPI_DSI_FMT_RGB666: + color = DPI_COLOR_CODING_18BIT_2 | EN18_LOOSELY; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + color = DPI_COLOR_CODING_18BIT_1; + break; + case MIPI_DSI_FMT_RGB565: + color = DPI_COLOR_CODING_16BIT_1; + break; + } + + if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + val |= VSYNC_ACTIVE_LOW; + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + val |= HSYNC_ACTIVE_LOW; + + dsi_write(dsi, DSI_DPI_VCID, DPI_VID(dsi->channel)); + dsi_write(dsi, DSI_DPI_COLOR_CODING, color); + dsi_write(dsi, DSI_DPI_CFG_POL, val); + dsi_write(dsi, DSI_DPI_LP_CMD_TIM, OUTVACT_LPCMD_TIME(4) + | INVACT_LPCMD_TIME(4)); +} + +static void dw_mipi_dsi_packet_handler_config(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PCKHDL_CFG, EN_CRC_RX | EN_ECC_RX | EN_BTA); +} + +static void dw_mipi_dsi_video_packet_config(struct dw_mipi_dsi *dsi, + struct drm_display_mode *mode) +{ + dsi_write(dsi, DSI_VID_PKT_SIZE, VID_PKT_SIZE(mode->hdisplay)); +} + +static void dw_mipi_dsi_command_mode_config(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_TO_CNT_CFG, HSTX_TO_CNT(1000) | LPRX_TO_CNT(1000)); + dsi_write(dsi, DSI_BTA_TO_CNT, 0xd00); + dsi_write(dsi, DSI_CMD_MODE_CFG, CMD_MODE_ALL_LP); + dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE); +} + +/* Get lane byte clock cycles. */ +static u32 dw_mipi_dsi_get_hcomponent_lbcc(struct dw_mipi_dsi *dsi, + u32 hcomponent) +{ + u32 frac, lbcc; + + lbcc = hcomponent * dsi->lane_mbps * MSEC_PER_SEC / 8; + + frac = lbcc % dsi->mode->clock; + lbcc = lbcc / dsi->mode->clock; + if (frac) + lbcc++; + + return lbcc; +} + +static void dw_mipi_dsi_line_timer_config(struct dw_mipi_dsi *dsi) +{ + u32 htotal, hsa, hbp, lbcc; + struct drm_display_mode *mode = dsi->mode; + + htotal = mode->htotal; + hsa = mode->hsync_end - mode->hsync_start; + hbp = mode->htotal - mode->hsync_end; + + lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, htotal); + dsi_write(dsi, DSI_VID_HLINE_TIME, lbcc); + + lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, hsa); + dsi_write(dsi, DSI_VID_HSA_TIME, lbcc); + + lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, hbp); + dsi_write(dsi, DSI_VID_HBP_TIME, lbcc); +} + +static void dw_mipi_dsi_vertical_timing_config(struct dw_mipi_dsi *dsi) +{ + u32 vactive, vsa, vfp, vbp; + struct drm_display_mode *mode = dsi->mode; + + vactive = mode->vdisplay; + vsa = mode->vsync_end - mode->vsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + dsi_write(dsi, DSI_VID_VACTIVE_LINES, vactive); + dsi_write(dsi, DSI_VID_VSA_LINES, vsa); + dsi_write(dsi, DSI_VID_VFP_LINES, vfp); + dsi_write(dsi, DSI_VID_VBP_LINES, vbp); +} + +static void dw_mipi_dsi_dphy_timing_config(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PHY_TMR_CFG, PHY_HS2LP_TIME(0x40) + | PHY_LP2HS_TIME(0x40) | MAX_RD_TIME(10000)); + + dsi_write(dsi, DSI_PHY_TMR_LPCLK_CFG, PHY_CLKHS2LP_TIME(0x40) + | PHY_CLKLP2HS_TIME(0x40)); +} + +static void dw_mipi_dsi_dphy_interface_config(struct dw_mipi_dsi *dsi) +{ + dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) | + N_LANES(dsi->lanes)); +} + +static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi) +{ + dsi_read(dsi, DSI_INT_ST0); + dsi_read(dsi, DSI_INT_ST1); + dsi_write(dsi, DSI_INT_MSK0, 0); + dsi_write(dsi, DSI_INT_MSK1, 0); +} + +static void dw_mipi_dsi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct dw_mipi_dsi *dsi = encoder_to_dsi(encoder); + int ret; + + dsi->mode = adjusted_mode; + + ret = dw_mipi_dsi_get_lane_bps(dsi); + if (ret < 0) + return; + + if (clk_prepare_enable(dsi->pclk)) { + dev_err(dsi->dev, "%s: Failed to enable pclk\n", __func__); + return; + } + + dw_mipi_dsi_init(dsi); + dw_mipi_dsi_dpi_config(dsi, mode); + dw_mipi_dsi_packet_handler_config(dsi); + dw_mipi_dsi_video_mode_config(dsi); + dw_mipi_dsi_video_packet_config(dsi, mode); + dw_mipi_dsi_command_mode_config(dsi); + dw_mipi_dsi_line_timer_config(dsi); + dw_mipi_dsi_vertical_timing_config(dsi); + dw_mipi_dsi_dphy_timing_config(dsi); + dw_mipi_dsi_dphy_interface_config(dsi); + dw_mipi_dsi_clear_err(dsi); + if (drm_panel_prepare(dsi->panel)) + dev_err(dsi->dev, "failed to prepare panel\n"); + + clk_disable_unprepare(dsi->pclk); +} + +static void dw_mipi_dsi_encoder_disable(struct drm_encoder *encoder) +{ + struct dw_mipi_dsi *dsi = encoder_to_dsi(encoder); + + drm_panel_disable(dsi->panel); + + if (clk_prepare_enable(dsi->pclk)) { + dev_err(dsi->dev, "%s: Failed to enable pclk\n", __func__); + return; + } + + dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE); + drm_panel_unprepare(dsi->panel); + dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE); + + /* + * This is necessary to make sure the peripheral will be driven + * normally when the display is enabled again later. + */ + msleep(120); + + dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_CMD_MODE); + dw_mipi_dsi_disable(dsi); + clk_disable_unprepare(dsi->pclk); +} + +static bool dw_mipi_dsi_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void dw_mipi_dsi_encoder_commit(struct drm_encoder *encoder) +{ + struct dw_mipi_dsi *dsi = encoder_to_dsi(encoder); + int mux = rockchip_drm_encoder_get_mux_id(dsi->dev->of_node, encoder); + u32 interface_pix_fmt; + u32 val; + + if (clk_prepare_enable(dsi->pclk)) { + dev_err(dsi->dev, "%s: Failed to enable pclk\n", __func__); + return; + } + + dw_mipi_dsi_phy_init(dsi); + dw_mipi_dsi_wait_for_two_frames(dsi); + + dw_mipi_dsi_set_mode(dsi, DW_MIPI_DSI_VID_MODE); + drm_panel_enable(dsi->panel); + + clk_disable_unprepare(dsi->pclk); + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + interface_pix_fmt = ROCKCHIP_OUT_MODE_P888; + break; + case MIPI_DSI_FMT_RGB666: + interface_pix_fmt = ROCKCHIP_OUT_MODE_P666; + break; + case MIPI_DSI_FMT_RGB565: + interface_pix_fmt = ROCKCHIP_OUT_MODE_P565; + break; + default: + WARN_ON(1); + return; + } + + rockchip_drm_crtc_mode_config(encoder->crtc, DRM_MODE_CONNECTOR_DSI, + interface_pix_fmt); + + if (mux) + val = DSI0_SEL_VOP_LIT | (DSI0_SEL_VOP_LIT << 16); + else + val = DSI0_SEL_VOP_LIT << 16; + + regmap_write(dsi->grf_regmap, GRF_SOC_CON6, val); + dev_dbg(dsi->dev, "vop %s output to dsi0\n", (mux) ? "LIT" : "BIG"); +} + +static struct drm_encoder_helper_funcs +dw_mipi_dsi_encoder_helper_funcs = { + .mode_fixup = dw_mipi_dsi_encoder_mode_fixup, + .commit = dw_mipi_dsi_encoder_commit, + .mode_set = dw_mipi_dsi_encoder_mode_set, + .disable = dw_mipi_dsi_encoder_disable, +}; + +static struct drm_encoder_funcs dw_mipi_dsi_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int dw_mipi_dsi_connector_get_modes(struct drm_connector *connector) +{ + struct dw_mipi_dsi *dsi = con_to_dsi(connector); + + return drm_panel_get_modes(dsi->panel); +} + +static enum drm_mode_status dw_mipi_dsi_mode_valid( + struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct dw_mipi_dsi *dsi = con_to_dsi(connector); + + enum drm_mode_status mode_status = MODE_OK; + + if (dsi->pdata->mode_valid) + mode_status = dsi->pdata->mode_valid(connector, mode); + + return mode_status; +} + +static struct drm_encoder *dw_mipi_dsi_connector_best_encoder( + struct drm_connector *connector) +{ + struct dw_mipi_dsi *dsi = con_to_dsi(connector); + + return &dsi->encoder; +} + +static struct drm_connector_helper_funcs dw_mipi_dsi_connector_helper_funcs = { + .get_modes = dw_mipi_dsi_connector_get_modes, + .mode_valid = dw_mipi_dsi_mode_valid, + .best_encoder = dw_mipi_dsi_connector_best_encoder, +}; + +static enum drm_connector_status +dw_mipi_dsi_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void dw_mipi_dsi_drm_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static struct drm_connector_funcs dw_mipi_dsi_atomic_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = dw_mipi_dsi_detect, + .destroy = dw_mipi_dsi_drm_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int dw_mipi_dsi_register(struct drm_device *drm, + struct dw_mipi_dsi *dsi) +{ + struct drm_encoder *encoder = &dsi->encoder; + struct drm_connector *connector = &dsi->connector; + struct device *dev = dsi->dev; + int ret; + + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, + dev->of_node); + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + drm_encoder_helper_add(&dsi->encoder, + &dw_mipi_dsi_encoder_helper_funcs); + ret = drm_encoder_init(drm, &dsi->encoder, &dw_mipi_dsi_encoder_funcs, + DRM_MODE_ENCODER_DSI, NULL); + if (ret) { + dev_err(dev, "Failed to initialize encoder with drm\n"); + return ret; + } + + drm_connector_helper_add(connector, + &dw_mipi_dsi_connector_helper_funcs); + + drm_connector_init(drm, &dsi->connector, + &dw_mipi_dsi_atomic_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + + drm_mode_connector_attach_encoder(connector, encoder); + + return 0; +} + +static int rockchip_mipi_parse_dt(struct dw_mipi_dsi *dsi) +{ + struct device_node *np = dsi->dev->of_node; + + dsi->grf_regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(dsi->grf_regmap)) { + dev_err(dsi->dev, "Unable to get rockchip,grf\n"); + return PTR_ERR(dsi->grf_regmap); + } + + return 0; +} + +static enum drm_mode_status rk3288_mipi_dsi_mode_valid( + struct drm_connector *connector, + struct drm_display_mode *mode) +{ + /* + * The VID_PKT_SIZE field in the DSI_VID_PKT_CFG + * register is 11-bit. + */ + if (mode->hdisplay > 0x7ff) + return MODE_BAD_HVALUE; + + /* + * The V_ACTIVE_LINES field in the DSI_VTIMING_CFG + * register is 11-bit. + */ + if (mode->vdisplay > 0x7ff) + return MODE_BAD_VVALUE; + + return MODE_OK; +} + +static struct dw_mipi_dsi_plat_data rk3288_mipi_dsi_drv_data = { + .max_data_lanes = 4, + .mode_valid = rk3288_mipi_dsi_mode_valid, +}; + +static const struct of_device_id dw_mipi_dsi_dt_ids[] = { + { + .compatible = "rockchip,rk3288-mipi-dsi", + .data = &rk3288_mipi_dsi_drv_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dw_mipi_dsi_dt_ids); + +static int dw_mipi_dsi_bind(struct device *dev, struct device *master, + void *data) +{ + const struct of_device_id *of_id = + of_match_device(dw_mipi_dsi_dt_ids, dev); + const struct dw_mipi_dsi_plat_data *pdata = of_id->data; + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct dw_mipi_dsi *dsi; + struct resource *res; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + dsi->dev = dev; + dsi->pdata = pdata; + + ret = rockchip_mipi_parse_dt(dsi); + if (ret) + return ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + dsi->base = devm_ioremap_resource(dev, res); + if (IS_ERR(dsi->base)) + return PTR_ERR(dsi->base); + + dsi->pllref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(dsi->pllref_clk)) { + ret = PTR_ERR(dsi->pllref_clk); + dev_err(dev, "Unable to get pll reference clock: %d\n", ret); + return ret; + } + + dsi->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(dsi->pclk)) { + ret = PTR_ERR(dsi->pclk); + dev_err(dev, "Unable to get pclk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(dsi->pllref_clk); + if (ret) { + dev_err(dev, "%s: Failed to enable pllref_clk\n", __func__); + return ret; + } + + ret = dw_mipi_dsi_register(drm, dsi); + if (ret) { + dev_err(dev, "Failed to register mipi_dsi: %d\n", ret); + goto err_pllref; + } + + dev_set_drvdata(dev, dsi); + + dsi->dsi_host.ops = &dw_mipi_dsi_host_ops; + dsi->dsi_host.dev = dev; + return mipi_dsi_host_register(&dsi->dsi_host); + +err_pllref: + clk_disable_unprepare(dsi->pllref_clk); + return ret; +} + +static void dw_mipi_dsi_unbind(struct device *dev, struct device *master, + void *data) +{ + struct dw_mipi_dsi *dsi = dev_get_drvdata(dev); + + mipi_dsi_host_unregister(&dsi->dsi_host); + clk_disable_unprepare(dsi->pllref_clk); +} + +static const struct component_ops dw_mipi_dsi_ops = { + .bind = dw_mipi_dsi_bind, + .unbind = dw_mipi_dsi_unbind, +}; + +static int dw_mipi_dsi_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &dw_mipi_dsi_ops); +} + +static int dw_mipi_dsi_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dw_mipi_dsi_ops); + return 0; +} + +static struct platform_driver dw_mipi_dsi_driver = { + .probe = dw_mipi_dsi_probe, + .remove = dw_mipi_dsi_remove, + .driver = { + .of_match_table = dw_mipi_dsi_dt_ids, + .name = DRIVER_NAME, + }, +}; +module_platform_driver(dw_mipi_dsi_driver); + +MODULE_DESCRIPTION("ROCKCHIP MIPI DSI host controller driver"); +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 80d6fc8a5..c65ce8cb3 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -173,7 +173,7 @@ dw_hdmi_rockchip_mode_valid(struct drm_connector *connector, return (valid) ? MODE_OK : MODE_BAD; } -static struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = { +static const struct drm_encoder_funcs dw_hdmi_rockchip_encoder_funcs = { .destroy = drm_encoder_cleanup, }; @@ -195,12 +195,15 @@ static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder, { } -static void dw_hdmi_rockchip_encoder_commit(struct drm_encoder *encoder) +static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder) { struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); u32 val; int mux; + rockchip_drm_crtc_mode_config(encoder->crtc, DRM_MODE_CONNECTOR_HDMIA, + ROCKCHIP_OUT_MODE_AAAA); + mux = rockchip_drm_encoder_get_mux_id(hdmi->dev->of_node, encoder); if (mux) val = HDMI_SEL_VOP_LIT | (HDMI_SEL_VOP_LIT << 16); @@ -212,17 +215,10 @@ static void dw_hdmi_rockchip_encoder_commit(struct drm_encoder *encoder) (mux) ? "LIT" : "BIG"); } -static void dw_hdmi_rockchip_encoder_prepare(struct drm_encoder *encoder) -{ - rockchip_drm_crtc_mode_config(encoder->crtc, DRM_MODE_CONNECTOR_HDMIA, - ROCKCHIP_OUT_MODE_AAAA); -} - -static struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = { +static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = { .mode_fixup = dw_hdmi_rockchip_encoder_mode_fixup, .mode_set = dw_hdmi_rockchip_encoder_mode_set, - .prepare = dw_hdmi_rockchip_encoder_prepare, - .commit = dw_hdmi_rockchip_encoder_commit, + .enable = dw_hdmi_rockchip_encoder_enable, .disable = dw_hdmi_rockchip_encoder_disable, }; @@ -295,7 +291,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs); drm_encoder_init(drm, encoder, &dw_hdmi_rockchip_encoder_funcs, - DRM_MODE_ENCODER_TMDS); + DRM_MODE_ENCODER_TMDS, NULL); return dw_hdmi_bind(dev, master, data, encoder, iores, irq, plat_data); } diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index f22e1e1ee..a0d51ccb6 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -55,20 +55,18 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, return arm_iommu_attach_device(dev, mapping); } -EXPORT_SYMBOL_GPL(rockchip_drm_dma_attach_device); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev) { arm_iommu_detach_device(dev); } -EXPORT_SYMBOL_GPL(rockchip_drm_dma_detach_device); -int rockchip_register_crtc_funcs(struct drm_device *dev, - const struct rockchip_crtc_funcs *crtc_funcs, - int pipe) +int rockchip_register_crtc_funcs(struct drm_crtc *crtc, + const struct rockchip_crtc_funcs *crtc_funcs) { - struct rockchip_drm_private *priv = dev->dev_private; + int pipe = drm_crtc_index(crtc); + struct rockchip_drm_private *priv = crtc->dev->dev_private; if (pipe > ROCKCHIP_MAX_CRTC) return -EINVAL; @@ -77,18 +75,17 @@ int rockchip_register_crtc_funcs(struct drm_device *dev, return 0; } -EXPORT_SYMBOL_GPL(rockchip_register_crtc_funcs); -void rockchip_unregister_crtc_funcs(struct drm_device *dev, int pipe) +void rockchip_unregister_crtc_funcs(struct drm_crtc *crtc) { - struct rockchip_drm_private *priv = dev->dev_private; + int pipe = drm_crtc_index(crtc); + struct rockchip_drm_private *priv = crtc->dev->dev_private; if (pipe > ROCKCHIP_MAX_CRTC) return; priv->crtc_funcs[pipe] = NULL; } -EXPORT_SYMBOL_GPL(rockchip_unregister_crtc_funcs); static struct drm_crtc *rockchip_crtc_from_pipe(struct drm_device *drm, int pipe) @@ -139,6 +136,9 @@ static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags) if (!private) return -ENOMEM; + mutex_init(&private->commit.lock); + INIT_WORK(&private->commit.work, rockchip_drm_atomic_work); + drm_dev->dev_private = private; drm_mode_config_init(drm_dev); @@ -212,6 +212,8 @@ static int rockchip_drm_load(struct drm_device *drm_dev, unsigned long flags) */ drm_dev->vblank_disable_allowed = true; + drm_mode_config_reset(drm_dev); + ret = rockchip_drm_fbdev_init(drm_dev); if (ret) goto err_vblank_cleanup; @@ -275,7 +277,8 @@ const struct vm_operations_struct rockchip_drm_vm_ops = { }; static struct drm_driver rockchip_drm_driver = { - .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, + .driver_features = DRIVER_MODESET | DRIVER_GEM | + DRIVER_PRIME | DRIVER_ATOMIC, .load = rockchip_drm_load, .unload = rockchip_drm_unload, .lastclose = rockchip_drm_lastclose, @@ -450,10 +453,6 @@ static int rockchip_drm_bind(struct device *dev) if (!drm) return -ENOMEM; - ret = drm_dev_set_unique(drm, "%s", dev_name(dev)); - if (ret) - goto err_free; - ret = drm_dev_register(drm, 0); if (ret) goto err_free; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index dc4e5f03a..bb8b076f1 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -18,6 +18,7 @@ #define _ROCKCHIP_DRM_DRV_H #include <drm/drm_fb_helper.h> +#include <drm/drm_atomic_helper.h> #include <drm/drm_gem.h> #include <linux/module.h> @@ -38,6 +39,14 @@ struct drm_connector; struct rockchip_crtc_funcs { int (*enable_vblank)(struct drm_crtc *crtc); void (*disable_vblank)(struct drm_crtc *crtc); + void (*wait_for_update)(struct drm_crtc *crtc); +}; + +struct rockchip_atomic_commit { + struct work_struct work; + struct drm_atomic_state *state; + struct drm_device *dev; + struct mutex lock; }; /* @@ -50,12 +59,14 @@ struct rockchip_drm_private { struct drm_fb_helper fbdev_helper; struct drm_gem_object *fbdev_bo; const struct rockchip_crtc_funcs *crtc_funcs[ROCKCHIP_MAX_CRTC]; + + struct rockchip_atomic_commit commit; }; -int rockchip_register_crtc_funcs(struct drm_device *dev, - const struct rockchip_crtc_funcs *crtc_funcs, - int pipe); -void rockchip_unregister_crtc_funcs(struct drm_device *dev, int pipe); +void rockchip_drm_atomic_work(struct work_struct *work); +int rockchip_register_crtc_funcs(struct drm_crtc *crtc, + const struct rockchip_crtc_funcs *crtc_funcs); +void rockchip_unregister_crtc_funcs(struct drm_crtc *crtc); int rockchip_drm_encoder_get_mux_id(struct device_node *node, struct drm_encoder *encoder); int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc, int connector_type, @@ -64,5 +75,4 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct device *dev); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev); - #endif /* _ROCKCHIP_DRM_DRV_H_ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c index 002645bb5..3b8f65269 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c @@ -15,6 +15,7 @@ #include <linux/kernel.h> #include <drm/drm.h> #include <drm/drmP.h> +#include <drm/drm_atomic.h> #include <drm/drm_fb_helper.h> #include <drm/drm_crtc_helper.h> @@ -38,7 +39,6 @@ struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb, return rk_fb->obj[plane]; } -EXPORT_SYMBOL_GPL(rockchip_fb_get_gem_obj); static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb) { @@ -66,13 +66,13 @@ static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb, rockchip_fb->obj[0], handle); } -static struct drm_framebuffer_funcs rockchip_drm_fb_funcs = { +static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = { .destroy = rockchip_drm_fb_destroy, .create_handle = rockchip_drm_fb_create_handle, }; static struct rockchip_drm_fb * -rockchip_fb_alloc(struct drm_device *dev, struct drm_mode_fb_cmd2 *mode_cmd, +rockchip_fb_alloc(struct drm_device *dev, const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **obj, unsigned int num_planes) { struct rockchip_drm_fb *rockchip_fb; @@ -102,7 +102,7 @@ rockchip_fb_alloc(struct drm_device *dev, struct drm_mode_fb_cmd2 *mode_cmd, static struct drm_framebuffer * rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv, - struct drm_mode_fb_cmd2 *mode_cmd) + const struct drm_mode_fb_cmd2 *mode_cmd) { struct rockchip_drm_fb *rockchip_fb; struct drm_gem_object *objs[ROCKCHIP_MAX_FB_BUFFER]; @@ -166,14 +166,155 @@ static void rockchip_drm_output_poll_changed(struct drm_device *dev) drm_fb_helper_hotplug_event(fb_helper); } +static void rockchip_crtc_wait_for_update(struct drm_crtc *crtc) +{ + struct rockchip_drm_private *priv = crtc->dev->dev_private; + int pipe = drm_crtc_index(crtc); + const struct rockchip_crtc_funcs *crtc_funcs = priv->crtc_funcs[pipe]; + + if (crtc_funcs && crtc_funcs->wait_for_update) + crtc_funcs->wait_for_update(crtc); +} + +/* + * We can't use drm_atomic_helper_wait_for_vblanks() because rk3288 and rk3066 + * have hardware counters for neither vblanks nor scanlines, which results in + * a race where: + * | <-- HW vsync irq and reg take effect + * plane_commit --> | + * get_vblank and wait --> | + * | <-- handle_vblank, vblank->count + 1 + * cleanup_fb --> | + * iommu crash --> | + * | <-- HW vsync irq and reg take effect + * + * This function is equivalent but uses rockchip_crtc_wait_for_update() instead + * of waiting for vblank_count to change. + */ +static void +rockchip_atomic_wait_for_complete(struct drm_device *dev, struct drm_atomic_state *old_state) +{ + struct drm_crtc_state *old_crtc_state; + struct drm_crtc *crtc; + int i, ret; + + for_each_crtc_in_state(old_state, crtc, old_crtc_state, i) { + /* No one cares about the old state, so abuse it for tracking + * and store whether we hold a vblank reference (and should do a + * vblank wait) in the ->enable boolean. + */ + old_crtc_state->enable = false; + + if (!crtc->state->active) + continue; + + if (!drm_atomic_helper_framebuffer_changed(dev, + old_state, crtc)) + continue; + + ret = drm_crtc_vblank_get(crtc); + if (ret != 0) + continue; + + old_crtc_state->enable = true; + } + + for_each_crtc_in_state(old_state, crtc, old_crtc_state, i) { + if (!old_crtc_state->enable) + continue; + + rockchip_crtc_wait_for_update(crtc); + drm_crtc_vblank_put(crtc); + } +} + +static void +rockchip_atomic_commit_complete(struct rockchip_atomic_commit *commit) +{ + struct drm_atomic_state *state = commit->state; + struct drm_device *dev = commit->dev; + + /* + * TODO: do fence wait here. + */ + + /* + * Rockchip crtc support runtime PM, can't update display planes + * when crtc is disabled. + * + * drm_atomic_helper_commit comments detail that: + * For drivers supporting runtime PM the recommended sequence is + * + * drm_atomic_helper_commit_modeset_disables(dev, state); + * + * drm_atomic_helper_commit_modeset_enables(dev, state); + * + * drm_atomic_helper_commit_planes(dev, state, true); + * + * See the kerneldoc entries for these three functions for more details. + */ + drm_atomic_helper_commit_modeset_disables(dev, state); + + drm_atomic_helper_commit_modeset_enables(dev, state); + + drm_atomic_helper_commit_planes(dev, state, true); + + rockchip_atomic_wait_for_complete(dev, state); + + drm_atomic_helper_cleanup_planes(dev, state); + + drm_atomic_state_free(state); +} + +void rockchip_drm_atomic_work(struct work_struct *work) +{ + struct rockchip_atomic_commit *commit = container_of(work, + struct rockchip_atomic_commit, work); + + rockchip_atomic_commit_complete(commit); +} + +int rockchip_drm_atomic_commit(struct drm_device *dev, + struct drm_atomic_state *state, + bool async) +{ + struct rockchip_drm_private *private = dev->dev_private; + struct rockchip_atomic_commit *commit = &private->commit; + int ret; + + ret = drm_atomic_helper_prepare_planes(dev, state); + if (ret) + return ret; + + /* serialize outstanding asynchronous commits */ + mutex_lock(&commit->lock); + flush_work(&commit->work); + + drm_atomic_helper_swap_state(dev, state); + + commit->dev = dev; + commit->state = state; + + if (async) + schedule_work(&commit->work); + else + rockchip_atomic_commit_complete(commit); + + mutex_unlock(&commit->lock); + + return 0; +} + static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = { .fb_create = rockchip_user_fb_create, .output_poll_changed = rockchip_drm_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = rockchip_drm_atomic_commit, }; struct drm_framebuffer * rockchip_drm_framebuffer_init(struct drm_device *dev, - struct drm_mode_fb_cmd2 *mode_cmd, + const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object *obj) { struct rockchip_drm_fb *rockchip_fb; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h index 09574d482..2fe47f1ee 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h @@ -17,7 +17,7 @@ struct drm_framebuffer * rockchip_drm_framebuffer_init(struct drm_device *dev, - struct drm_mode_fb_cmd2 *mode_cmd, + const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object *obj); void rockchip_drm_framebuffer_fini(struct drm_framebuffer *fb); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h index 50432e9b5..73718c5f5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h @@ -15,7 +15,18 @@ #ifndef _ROCKCHIP_DRM_FBDEV_H #define _ROCKCHIP_DRM_FBDEV_H +#ifdef CONFIG_DRM_FBDEV_EMULATION int rockchip_drm_fbdev_init(struct drm_device *dev); void rockchip_drm_fbdev_fini(struct drm_device *dev); +#else +static inline int rockchip_drm_fbdev_init(struct drm_device *dev) +{ + return 0; +} + +static inline void rockchip_drm_fbdev_fini(struct drm_device *dev) +{ +} +#endif #endif /* _ROCKCHIP_DRM_FBDEV_H */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c index d908321b9..18e07338c 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c @@ -234,13 +234,8 @@ int rockchip_gem_dumb_create(struct drm_file *file_priv, /* * align to 64 bytes since Mali requires it. */ - min_pitch = ALIGN(min_pitch, 64); - - if (args->pitch < min_pitch) - args->pitch = min_pitch; - - if (args->size < args->pitch * args->height) - args->size = args->pitch * args->height; + args->pitch = ALIGN(min_pitch, 64); + args->size = args->pitch * args->height; rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size, &args->handle); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index 03c47eead..fd370548d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -14,6 +14,7 @@ #include <drm/drm.h> #include <drm/drmP.h> +#include <drm/drm_atomic.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_plane_helper.h> @@ -35,11 +36,6 @@ #include "rockchip_drm_fb.h" #include "rockchip_drm_vop.h" -#define VOP_REG(off, _mask, s) \ - {.offset = off, \ - .mask = _mask, \ - .shift = s,} - #define __REG_SET_RELAXED(x, off, mask, shift, v) \ vop_mask_write_relaxed(x, off, (mask) << shift, (v) << shift) #define __REG_SET_NORMAL(x, off, mask, shift, v) \ @@ -47,14 +43,37 @@ #define REG_SET(x, base, reg, v, mode) \ __REG_SET_##mode(x, base + reg.offset, reg.mask, reg.shift, v) +#define REG_SET_MASK(x, base, reg, mask, v, mode) \ + __REG_SET_##mode(x, base + reg.offset, mask, reg.shift, v) #define VOP_WIN_SET(x, win, name, v) \ REG_SET(x, win->base, win->phy->name, v, RELAXED) #define VOP_SCL_SET(x, win, name, v) \ REG_SET(x, win->base, win->phy->scl->name, v, RELAXED) +#define VOP_SCL_SET_EXT(x, win, name, v) \ + REG_SET(x, win->base, win->phy->scl->ext->name, v, RELAXED) #define VOP_CTRL_SET(x, name, v) \ REG_SET(x, 0, (x)->data->ctrl->name, v, NORMAL) +#define VOP_INTR_GET(vop, name) \ + vop_read_reg(vop, 0, &vop->data->ctrl->name) + +#define VOP_INTR_SET(vop, name, mask, v) \ + REG_SET_MASK(vop, 0, vop->data->intr->name, mask, v, NORMAL) +#define VOP_INTR_SET_TYPE(vop, name, type, v) \ + do { \ + int i, reg = 0, mask = 0; \ + for (i = 0; i < vop->data->intr->nintrs; i++) { \ + if (vop->data->intr->intrs[i] & type) { \ + reg |= (v) << i; \ + mask |= 1 << i; \ + } \ + } \ + VOP_INTR_SET(vop, name, mask, reg); \ + } while (0) +#define VOP_INTR_GET_TYPE(vop, name, type) \ + vop_get_intr_type(vop, &vop->data->intr->name, type) + #define VOP_WIN_GET(x, win, name) \ vop_read_reg(x, win->base, &win->phy->name) @@ -63,12 +82,15 @@ #define to_vop(x) container_of(x, struct vop, crtc) #define to_vop_win(x) container_of(x, struct vop_win, base) +#define to_vop_plane_state(x) container_of(x, struct vop_plane_state, base) -struct vop_win_state { - struct list_head head; - struct drm_framebuffer *fb; +struct vop_plane_state { + struct drm_plane_state base; + int format; + struct drm_rect src; + struct drm_rect dest; dma_addr_t yrgb_mst; - struct drm_pending_vblank_event *event; + bool enable; }; struct vop_win { @@ -76,8 +98,7 @@ struct vop_win { const struct vop_win_data *data; struct vop *vop; - struct list_head pending; - struct vop_win_state *active; + struct vop_plane_state state; }; struct vop { @@ -86,13 +107,12 @@ struct vop { struct drm_device *drm_dev; bool is_enabled; - int connector_type; - int connector_out_mode; - /* mutex vsync_ work */ struct mutex vsync_mutex; bool vsync_work_pending; struct completion dsp_hold_completion; + struct completion wait_update_complete; + struct drm_pending_vblank_event *event; const struct vop_data *data; @@ -119,263 +139,9 @@ struct vop { /* vop dclk reset */ struct reset_control *dclk_rst; - int pipe; - struct vop_win win[]; }; -enum vop_data_format { - VOP_FMT_ARGB8888 = 0, - VOP_FMT_RGB888, - VOP_FMT_RGB565, - VOP_FMT_YUV420SP = 4, - VOP_FMT_YUV422SP, - VOP_FMT_YUV444SP, -}; - -struct vop_reg_data { - uint32_t offset; - uint32_t value; -}; - -struct vop_reg { - uint32_t offset; - uint32_t shift; - uint32_t mask; -}; - -struct vop_ctrl { - struct vop_reg standby; - struct vop_reg data_blank; - struct vop_reg gate_en; - struct vop_reg mmu_en; - struct vop_reg rgb_en; - struct vop_reg edp_en; - struct vop_reg hdmi_en; - struct vop_reg mipi_en; - struct vop_reg out_mode; - struct vop_reg dither_down; - struct vop_reg dither_up; - struct vop_reg pin_pol; - - struct vop_reg htotal_pw; - struct vop_reg hact_st_end; - struct vop_reg vtotal_pw; - struct vop_reg vact_st_end; - struct vop_reg hpost_st_end; - struct vop_reg vpost_st_end; -}; - -struct vop_scl_regs { - struct vop_reg cbcr_vsd_mode; - struct vop_reg cbcr_vsu_mode; - struct vop_reg cbcr_hsd_mode; - struct vop_reg cbcr_ver_scl_mode; - struct vop_reg cbcr_hor_scl_mode; - struct vop_reg yrgb_vsd_mode; - struct vop_reg yrgb_vsu_mode; - struct vop_reg yrgb_hsd_mode; - struct vop_reg yrgb_ver_scl_mode; - struct vop_reg yrgb_hor_scl_mode; - struct vop_reg line_load_mode; - struct vop_reg cbcr_axi_gather_num; - struct vop_reg yrgb_axi_gather_num; - struct vop_reg vsd_cbcr_gt2; - struct vop_reg vsd_cbcr_gt4; - struct vop_reg vsd_yrgb_gt2; - struct vop_reg vsd_yrgb_gt4; - struct vop_reg bic_coe_sel; - struct vop_reg cbcr_axi_gather_en; - struct vop_reg yrgb_axi_gather_en; - - struct vop_reg lb_mode; - struct vop_reg scale_yrgb_x; - struct vop_reg scale_yrgb_y; - struct vop_reg scale_cbcr_x; - struct vop_reg scale_cbcr_y; -}; - -struct vop_win_phy { - const struct vop_scl_regs *scl; - const uint32_t *data_formats; - uint32_t nformats; - - struct vop_reg enable; - struct vop_reg format; - struct vop_reg rb_swap; - struct vop_reg act_info; - struct vop_reg dsp_info; - struct vop_reg dsp_st; - struct vop_reg yrgb_mst; - struct vop_reg uv_mst; - struct vop_reg yrgb_vir; - struct vop_reg uv_vir; - - struct vop_reg dst_alpha_ctl; - struct vop_reg src_alpha_ctl; -}; - -struct vop_win_data { - uint32_t base; - const struct vop_win_phy *phy; - enum drm_plane_type type; -}; - -struct vop_data { - const struct vop_reg_data *init_table; - unsigned int table_size; - const struct vop_ctrl *ctrl; - const struct vop_win_data *win; - unsigned int win_size; -}; - -static const uint32_t formats_01[] = { - DRM_FORMAT_XRGB8888, - DRM_FORMAT_ARGB8888, - DRM_FORMAT_XBGR8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_RGB888, - DRM_FORMAT_BGR888, - DRM_FORMAT_RGB565, - DRM_FORMAT_BGR565, - DRM_FORMAT_NV12, - DRM_FORMAT_NV16, - DRM_FORMAT_NV24, -}; - -static const uint32_t formats_234[] = { - DRM_FORMAT_XRGB8888, - DRM_FORMAT_ARGB8888, - DRM_FORMAT_XBGR8888, - DRM_FORMAT_ABGR8888, - DRM_FORMAT_RGB888, - DRM_FORMAT_BGR888, - DRM_FORMAT_RGB565, - DRM_FORMAT_BGR565, -}; - -static const struct vop_scl_regs win_full_scl = { - .cbcr_vsd_mode = VOP_REG(WIN0_CTRL1, 0x1, 31), - .cbcr_vsu_mode = VOP_REG(WIN0_CTRL1, 0x1, 30), - .cbcr_hsd_mode = VOP_REG(WIN0_CTRL1, 0x3, 28), - .cbcr_ver_scl_mode = VOP_REG(WIN0_CTRL1, 0x3, 26), - .cbcr_hor_scl_mode = VOP_REG(WIN0_CTRL1, 0x3, 24), - .yrgb_vsd_mode = VOP_REG(WIN0_CTRL1, 0x1, 23), - .yrgb_vsu_mode = VOP_REG(WIN0_CTRL1, 0x1, 22), - .yrgb_hsd_mode = VOP_REG(WIN0_CTRL1, 0x3, 20), - .yrgb_ver_scl_mode = VOP_REG(WIN0_CTRL1, 0x3, 18), - .yrgb_hor_scl_mode = VOP_REG(WIN0_CTRL1, 0x3, 16), - .line_load_mode = VOP_REG(WIN0_CTRL1, 0x1, 15), - .cbcr_axi_gather_num = VOP_REG(WIN0_CTRL1, 0x7, 12), - .yrgb_axi_gather_num = VOP_REG(WIN0_CTRL1, 0xf, 8), - .vsd_cbcr_gt2 = VOP_REG(WIN0_CTRL1, 0x1, 7), - .vsd_cbcr_gt4 = VOP_REG(WIN0_CTRL1, 0x1, 6), - .vsd_yrgb_gt2 = VOP_REG(WIN0_CTRL1, 0x1, 5), - .vsd_yrgb_gt4 = VOP_REG(WIN0_CTRL1, 0x1, 4), - .bic_coe_sel = VOP_REG(WIN0_CTRL1, 0x3, 2), - .cbcr_axi_gather_en = VOP_REG(WIN0_CTRL1, 0x1, 1), - .yrgb_axi_gather_en = VOP_REG(WIN0_CTRL1, 0x1, 0), - .lb_mode = VOP_REG(WIN0_CTRL0, 0x7, 5), - .scale_yrgb_x = VOP_REG(WIN0_SCL_FACTOR_YRGB, 0xffff, 0x0), - .scale_yrgb_y = VOP_REG(WIN0_SCL_FACTOR_YRGB, 0xffff, 16), - .scale_cbcr_x = VOP_REG(WIN0_SCL_FACTOR_CBR, 0xffff, 0x0), - .scale_cbcr_y = VOP_REG(WIN0_SCL_FACTOR_CBR, 0xffff, 16), -}; - -static const struct vop_win_phy win01_data = { - .scl = &win_full_scl, - .data_formats = formats_01, - .nformats = ARRAY_SIZE(formats_01), - .enable = VOP_REG(WIN0_CTRL0, 0x1, 0), - .format = VOP_REG(WIN0_CTRL0, 0x7, 1), - .rb_swap = VOP_REG(WIN0_CTRL0, 0x1, 12), - .act_info = VOP_REG(WIN0_ACT_INFO, 0x1fff1fff, 0), - .dsp_info = VOP_REG(WIN0_DSP_INFO, 0x0fff0fff, 0), - .dsp_st = VOP_REG(WIN0_DSP_ST, 0x1fff1fff, 0), - .yrgb_mst = VOP_REG(WIN0_YRGB_MST, 0xffffffff, 0), - .uv_mst = VOP_REG(WIN0_CBR_MST, 0xffffffff, 0), - .yrgb_vir = VOP_REG(WIN0_VIR, 0x3fff, 0), - .uv_vir = VOP_REG(WIN0_VIR, 0x3fff, 16), - .src_alpha_ctl = VOP_REG(WIN0_SRC_ALPHA_CTRL, 0xff, 0), - .dst_alpha_ctl = VOP_REG(WIN0_DST_ALPHA_CTRL, 0xff, 0), -}; - -static const struct vop_win_phy win23_data = { - .data_formats = formats_234, - .nformats = ARRAY_SIZE(formats_234), - .enable = VOP_REG(WIN2_CTRL0, 0x1, 0), - .format = VOP_REG(WIN2_CTRL0, 0x7, 1), - .rb_swap = VOP_REG(WIN2_CTRL0, 0x1, 12), - .dsp_info = VOP_REG(WIN2_DSP_INFO0, 0x0fff0fff, 0), - .dsp_st = VOP_REG(WIN2_DSP_ST0, 0x1fff1fff, 0), - .yrgb_mst = VOP_REG(WIN2_MST0, 0xffffffff, 0), - .yrgb_vir = VOP_REG(WIN2_VIR0_1, 0x1fff, 0), - .src_alpha_ctl = VOP_REG(WIN2_SRC_ALPHA_CTRL, 0xff, 0), - .dst_alpha_ctl = VOP_REG(WIN2_DST_ALPHA_CTRL, 0xff, 0), -}; - -static const struct vop_ctrl ctrl_data = { - .standby = VOP_REG(SYS_CTRL, 0x1, 22), - .gate_en = VOP_REG(SYS_CTRL, 0x1, 23), - .mmu_en = VOP_REG(SYS_CTRL, 0x1, 20), - .rgb_en = VOP_REG(SYS_CTRL, 0x1, 12), - .hdmi_en = VOP_REG(SYS_CTRL, 0x1, 13), - .edp_en = VOP_REG(SYS_CTRL, 0x1, 14), - .mipi_en = VOP_REG(SYS_CTRL, 0x1, 15), - .dither_down = VOP_REG(DSP_CTRL1, 0xf, 1), - .dither_up = VOP_REG(DSP_CTRL1, 0x1, 6), - .data_blank = VOP_REG(DSP_CTRL0, 0x1, 19), - .out_mode = VOP_REG(DSP_CTRL0, 0xf, 0), - .pin_pol = VOP_REG(DSP_CTRL0, 0xf, 4), - .htotal_pw = VOP_REG(DSP_HTOTAL_HS_END, 0x1fff1fff, 0), - .hact_st_end = VOP_REG(DSP_HACT_ST_END, 0x1fff1fff, 0), - .vtotal_pw = VOP_REG(DSP_VTOTAL_VS_END, 0x1fff1fff, 0), - .vact_st_end = VOP_REG(DSP_VACT_ST_END, 0x1fff1fff, 0), - .hpost_st_end = VOP_REG(POST_DSP_HACT_INFO, 0x1fff1fff, 0), - .vpost_st_end = VOP_REG(POST_DSP_VACT_INFO, 0x1fff1fff, 0), -}; - -static const struct vop_reg_data vop_init_reg_table[] = { - {SYS_CTRL, 0x00c00000}, - {DSP_CTRL0, 0x00000000}, - {WIN0_CTRL0, 0x00000080}, - {WIN1_CTRL0, 0x00000080}, - /* TODO: Win2/3 support multiple area function, but we haven't found - * a suitable way to use it yet, so let's just use them as other windows - * with only area 0 enabled. - */ - {WIN2_CTRL0, 0x00000010}, - {WIN3_CTRL0, 0x00000010}, -}; - -/* - * Note: rk3288 has a dedicated 'cursor' window, however, that window requires - * special support to get alpha blending working. For now, just use overlay - * window 3 for the drm cursor. - * - */ -static const struct vop_win_data rk3288_vop_win_data[] = { - { .base = 0x00, .phy = &win01_data, .type = DRM_PLANE_TYPE_PRIMARY }, - { .base = 0x40, .phy = &win01_data, .type = DRM_PLANE_TYPE_OVERLAY }, - { .base = 0x00, .phy = &win23_data, .type = DRM_PLANE_TYPE_OVERLAY }, - { .base = 0x50, .phy = &win23_data, .type = DRM_PLANE_TYPE_CURSOR }, -}; - -static const struct vop_data rk3288_vop = { - .init_table = vop_init_reg_table, - .table_size = ARRAY_SIZE(vop_init_reg_table), - .ctrl = &ctrl_data, - .win = rk3288_vop_win_data, - .win_size = ARRAY_SIZE(rk3288_vop_win_data), -}; - -static const struct of_device_id vop_driver_dt_match[] = { - { .compatible = "rockchip,rk3288-vop", - .data = &rk3288_vop }, - {}, -}; -MODULE_DEVICE_TABLE(of, vop_driver_dt_match); - static inline void vop_writel(struct vop *vop, uint32_t offset, uint32_t v) { writel(v, vop->regs + offset); @@ -393,11 +159,6 @@ static inline uint32_t vop_read_reg(struct vop *vop, uint32_t base, return (vop_readl(vop, base + reg->offset) >> reg->shift) & reg->mask; } -static inline void vop_cfg_done(struct vop *vop) -{ - writel(0x01, vop->regs + REG_CFG_DONE); -} - static inline void vop_mask_write(struct vop *vop, uint32_t offset, uint32_t mask, uint32_t v) { @@ -422,6 +183,25 @@ static inline void vop_mask_write_relaxed(struct vop *vop, uint32_t offset, } } +static inline uint32_t vop_get_intr_type(struct vop *vop, + const struct vop_reg *reg, int type) +{ + uint32_t i, ret = 0; + uint32_t regs = vop_read_reg(vop, 0, reg); + + for (i = 0; i < vop->data->intr->nintrs; i++) { + if ((type & vop->data->intr->intrs[i]) && (regs & 1 << i)) + ret |= vop->data->intr->intrs[i]; + } + + return ret; +} + +static inline void vop_cfg_done(struct vop *vop) +{ + VOP_CTRL_SET(vop, cfg_done, 1); +} + static bool has_rb_swapped(uint32_t format) { switch (format) { @@ -537,6 +317,20 @@ static void scl_vop_cal_scl_fac(struct vop *vop, const struct vop_win_data *win, return; } + if (!win->phy->scl->ext) { + VOP_SCL_SET(vop, win, scale_yrgb_x, + scl_cal_scale2(src_w, dst_w)); + VOP_SCL_SET(vop, win, scale_yrgb_y, + scl_cal_scale2(src_h, dst_h)); + if (is_yuv) { + VOP_SCL_SET(vop, win, scale_cbcr_x, + scl_cal_scale2(src_w, dst_w)); + VOP_SCL_SET(vop, win, scale_cbcr_y, + scl_cal_scale2(src_h, dst_h)); + } + return; + } + yrgb_hor_scl_mode = scl_get_scl_mode(src_w, dst_w); yrgb_ver_scl_mode = scl_get_scl_mode(src_h, dst_h); @@ -554,7 +348,7 @@ static void scl_vop_cal_scl_fac(struct vop *vop, const struct vop_win_data *win, lb_mode = scl_vop_cal_lb_mode(src_w, false); } - VOP_SCL_SET(vop, win, lb_mode, lb_mode); + VOP_SCL_SET_EXT(vop, win, lb_mode, lb_mode); if (lb_mode == LB_RGB_3840X2) { if (yrgb_ver_scl_mode != SCALE_NONE) { DRM_ERROR("ERROR : not allow yrgb ver scale\n"); @@ -578,14 +372,14 @@ static void scl_vop_cal_scl_fac(struct vop *vop, const struct vop_win_data *win, false, vsu_mode, &vskiplines); VOP_SCL_SET(vop, win, scale_yrgb_y, val); - VOP_SCL_SET(vop, win, vsd_yrgb_gt4, vskiplines == 4); - VOP_SCL_SET(vop, win, vsd_yrgb_gt2, vskiplines == 2); + VOP_SCL_SET_EXT(vop, win, vsd_yrgb_gt4, vskiplines == 4); + VOP_SCL_SET_EXT(vop, win, vsd_yrgb_gt2, vskiplines == 2); - VOP_SCL_SET(vop, win, yrgb_hor_scl_mode, yrgb_hor_scl_mode); - VOP_SCL_SET(vop, win, yrgb_ver_scl_mode, yrgb_ver_scl_mode); - VOP_SCL_SET(vop, win, yrgb_hsd_mode, SCALE_DOWN_BIL); - VOP_SCL_SET(vop, win, yrgb_vsd_mode, SCALE_DOWN_BIL); - VOP_SCL_SET(vop, win, yrgb_vsu_mode, vsu_mode); + VOP_SCL_SET_EXT(vop, win, yrgb_hor_scl_mode, yrgb_hor_scl_mode); + VOP_SCL_SET_EXT(vop, win, yrgb_ver_scl_mode, yrgb_ver_scl_mode); + VOP_SCL_SET_EXT(vop, win, yrgb_hsd_mode, SCALE_DOWN_BIL); + VOP_SCL_SET_EXT(vop, win, yrgb_vsd_mode, SCALE_DOWN_BIL); + VOP_SCL_SET_EXT(vop, win, yrgb_vsu_mode, vsu_mode); if (is_yuv) { val = scl_vop_cal_scale(cbcr_hor_scl_mode, cbcr_src_w, dst_w, true, 0, NULL); @@ -594,13 +388,13 @@ static void scl_vop_cal_scl_fac(struct vop *vop, const struct vop_win_data *win, dst_h, false, vsu_mode, &vskiplines); VOP_SCL_SET(vop, win, scale_cbcr_y, val); - VOP_SCL_SET(vop, win, vsd_cbcr_gt4, vskiplines == 4); - VOP_SCL_SET(vop, win, vsd_cbcr_gt2, vskiplines == 2); - VOP_SCL_SET(vop, win, cbcr_hor_scl_mode, cbcr_hor_scl_mode); - VOP_SCL_SET(vop, win, cbcr_ver_scl_mode, cbcr_ver_scl_mode); - VOP_SCL_SET(vop, win, cbcr_hsd_mode, SCALE_DOWN_BIL); - VOP_SCL_SET(vop, win, cbcr_vsd_mode, SCALE_DOWN_BIL); - VOP_SCL_SET(vop, win, cbcr_vsu_mode, vsu_mode); + VOP_SCL_SET_EXT(vop, win, vsd_cbcr_gt4, vskiplines == 4); + VOP_SCL_SET_EXT(vop, win, vsd_cbcr_gt2, vskiplines == 2); + VOP_SCL_SET_EXT(vop, win, cbcr_hor_scl_mode, cbcr_hor_scl_mode); + VOP_SCL_SET_EXT(vop, win, cbcr_ver_scl_mode, cbcr_ver_scl_mode); + VOP_SCL_SET_EXT(vop, win, cbcr_hsd_mode, SCALE_DOWN_BIL); + VOP_SCL_SET_EXT(vop, win, cbcr_vsd_mode, SCALE_DOWN_BIL); + VOP_SCL_SET_EXT(vop, win, cbcr_vsu_mode, vsu_mode); } } @@ -613,8 +407,7 @@ static void vop_dsp_hold_valid_irq_enable(struct vop *vop) spin_lock_irqsave(&vop->irq_lock, flags); - vop_mask_write(vop, INTR_CTRL0, DSP_HOLD_VALID_INTR_MASK, - DSP_HOLD_VALID_INTR_EN(1)); + VOP_INTR_SET_TYPE(vop, enable, DSP_HOLD_VALID_INTR, 1); spin_unlock_irqrestore(&vop->irq_lock, flags); } @@ -628,8 +421,7 @@ static void vop_dsp_hold_valid_irq_disable(struct vop *vop) spin_lock_irqsave(&vop->irq_lock, flags); - vop_mask_write(vop, INTR_CTRL0, DSP_HOLD_VALID_INTR_MASK, - DSP_HOLD_VALID_INTR_EN(0)); + VOP_INTR_SET_TYPE(vop, enable, DSP_HOLD_VALID_INTR, 0); spin_unlock_irqrestore(&vop->irq_lock, flags); } @@ -692,7 +484,7 @@ static void vop_enable(struct drm_crtc *crtc) enable_irq(vop->irq); - drm_vblank_on(vop->drm_dev, vop->pipe); + drm_crtc_vblank_on(crtc); return; @@ -704,14 +496,14 @@ err_disable_hclk: clk_disable(vop->hclk); } -static void vop_disable(struct drm_crtc *crtc) +static void vop_crtc_disable(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); if (!vop->is_enabled) return; - drm_vblank_off(crtc->dev, vop->pipe); + drm_crtc_vblank_off(crtc); /* * Vop standby will take effect at end of current frame, @@ -748,224 +540,188 @@ static void vop_disable(struct drm_crtc *crtc) pm_runtime_put(vop->dev); } -/* - * Caller must hold vsync_mutex. - */ -static struct drm_framebuffer *vop_win_last_pending_fb(struct vop_win *vop_win) -{ - struct vop_win_state *last; - struct vop_win_state *active = vop_win->active; - - if (list_empty(&vop_win->pending)) - return active ? active->fb : NULL; - - last = list_last_entry(&vop_win->pending, struct vop_win_state, head); - return last ? last->fb : NULL; -} - -/* - * Caller must hold vsync_mutex. - */ -static int vop_win_queue_fb(struct vop_win *vop_win, - struct drm_framebuffer *fb, dma_addr_t yrgb_mst, - struct drm_pending_vblank_event *event) +static void vop_plane_destroy(struct drm_plane *plane) { - struct vop_win_state *state; - - state = kzalloc(sizeof(*state), GFP_KERNEL); - if (!state) - return -ENOMEM; - - state->fb = fb; - state->yrgb_mst = yrgb_mst; - state->event = event; - - list_add_tail(&state->head, &vop_win->pending); - - return 0; + drm_plane_cleanup(plane); } -static int vop_update_plane_event(struct drm_plane *plane, - struct drm_crtc *crtc, - struct drm_framebuffer *fb, int crtc_x, - int crtc_y, unsigned int crtc_w, - unsigned int crtc_h, uint32_t src_x, - uint32_t src_y, uint32_t src_w, - uint32_t src_h, - struct drm_pending_vblank_event *event) +static int vop_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) { + struct drm_crtc *crtc = state->crtc; + struct drm_framebuffer *fb = state->fb; struct vop_win *vop_win = to_vop_win(plane); + struct vop_plane_state *vop_plane_state = to_vop_plane_state(state); const struct vop_win_data *win = vop_win->data; - struct vop *vop = to_vop(crtc); - struct drm_gem_object *obj; - struct rockchip_gem_object *rk_obj; - struct drm_gem_object *uv_obj; - struct rockchip_gem_object *rk_uv_obj; - unsigned long offset; - unsigned int actual_w; - unsigned int actual_h; - unsigned int dsp_stx; - unsigned int dsp_sty; - unsigned int y_vir_stride; - unsigned int uv_vir_stride = 0; - dma_addr_t yrgb_mst; - dma_addr_t uv_mst = 0; - enum vop_data_format format; - uint32_t val; - bool is_alpha; - bool rb_swap; - bool is_yuv; bool visible; int ret; - struct drm_rect dest = { - .x1 = crtc_x, - .y1 = crtc_y, - .x2 = crtc_x + crtc_w, - .y2 = crtc_y + crtc_h, - }; - struct drm_rect src = { - /* 16.16 fixed point */ - .x1 = src_x, - .y1 = src_y, - .x2 = src_x + src_w, - .y2 = src_y + src_h, - }; - const struct drm_rect clip = { - .x2 = crtc->mode.hdisplay, - .y2 = crtc->mode.vdisplay, - }; - bool can_position = plane->type != DRM_PLANE_TYPE_PRIMARY; + struct drm_rect *dest = &vop_plane_state->dest; + struct drm_rect *src = &vop_plane_state->src; + struct drm_rect clip; int min_scale = win->phy->scl ? FRAC_16_16(1, 8) : DRM_PLANE_HELPER_NO_SCALING; int max_scale = win->phy->scl ? FRAC_16_16(8, 1) : DRM_PLANE_HELPER_NO_SCALING; - ret = drm_plane_helper_check_update(plane, crtc, fb, - &src, &dest, &clip, + crtc = crtc ? crtc : plane->state->crtc; + /* + * Both crtc or plane->state->crtc can be null. + */ + if (!crtc || !fb) + goto out_disable; + src->x1 = state->src_x; + src->y1 = state->src_y; + src->x2 = state->src_x + state->src_w; + src->y2 = state->src_y + state->src_h; + dest->x1 = state->crtc_x; + dest->y1 = state->crtc_y; + dest->x2 = state->crtc_x + state->crtc_w; + dest->y2 = state->crtc_y + state->crtc_h; + + clip.x1 = 0; + clip.y1 = 0; + clip.x2 = crtc->mode.hdisplay; + clip.y2 = crtc->mode.vdisplay; + + ret = drm_plane_helper_check_update(plane, crtc, state->fb, + src, dest, &clip, min_scale, max_scale, - can_position, false, &visible); + true, true, &visible); if (ret) return ret; if (!visible) - return 0; - - is_alpha = is_alpha_support(fb->pixel_format); - rb_swap = has_rb_swapped(fb->pixel_format); - is_yuv = is_yuv_support(fb->pixel_format); + goto out_disable; - format = vop_convert_format(fb->pixel_format); - if (format < 0) - return format; + vop_plane_state->format = vop_convert_format(fb->pixel_format); + if (vop_plane_state->format < 0) + return vop_plane_state->format; - obj = rockchip_fb_get_gem_obj(fb, 0); - if (!obj) { - DRM_ERROR("fail to get rockchip gem object from framebuffer\n"); + /* + * Src.x1 can be odd when do clip, but yuv plane start point + * need align with 2 pixel. + */ + if (is_yuv_support(fb->pixel_format) && ((src->x1 >> 16) % 2)) return -EINVAL; - } - rk_obj = to_rockchip_obj(obj); + vop_plane_state->enable = true; - if (is_yuv) { - /* - * Src.x1 can be odd when do clip, but yuv plane start point - * need align with 2 pixel. - */ - val = (src.x1 >> 16) % 2; - src.x1 += val << 16; - src.x2 += val << 16; - } + return 0; - actual_w = (src.x2 - src.x1) >> 16; - actual_h = (src.y2 - src.y1) >> 16; +out_disable: + vop_plane_state->enable = false; + return 0; +} - dsp_stx = dest.x1 + crtc->mode.htotal - crtc->mode.hsync_start; - dsp_sty = dest.y1 + crtc->mode.vtotal - crtc->mode.vsync_start; +static void vop_plane_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct vop_plane_state *vop_plane_state = to_vop_plane_state(old_state); + struct vop_win *vop_win = to_vop_win(plane); + const struct vop_win_data *win = vop_win->data; + struct vop *vop = to_vop(old_state->crtc); - offset = (src.x1 >> 16) * drm_format_plane_cpp(fb->pixel_format, 0); - offset += (src.y1 >> 16) * fb->pitches[0]; + if (!old_state->crtc) + return; - yrgb_mst = rk_obj->dma_addr + offset + fb->offsets[0]; - y_vir_stride = fb->pitches[0] >> 2; + spin_lock(&vop->reg_lock); - if (is_yuv) { - int hsub = drm_format_horz_chroma_subsampling(fb->pixel_format); - int vsub = drm_format_vert_chroma_subsampling(fb->pixel_format); - int bpp = drm_format_plane_cpp(fb->pixel_format, 1); + VOP_WIN_SET(vop, win, enable, 0); - uv_obj = rockchip_fb_get_gem_obj(fb, 1); - if (!uv_obj) { - DRM_ERROR("fail to get uv object from framebuffer\n"); - return -EINVAL; - } - rk_uv_obj = to_rockchip_obj(uv_obj); - uv_vir_stride = fb->pitches[1] >> 2; + spin_unlock(&vop->reg_lock); - offset = (src.x1 >> 16) * bpp / hsub; - offset += (src.y1 >> 16) * fb->pitches[1] / vsub; + vop_plane_state->enable = false; +} - uv_mst = rk_uv_obj->dma_addr + offset + fb->offsets[1]; - } +static void vop_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct drm_plane_state *state = plane->state; + struct drm_crtc *crtc = state->crtc; + struct vop_win *vop_win = to_vop_win(plane); + struct vop_plane_state *vop_plane_state = to_vop_plane_state(state); + const struct vop_win_data *win = vop_win->data; + struct vop *vop = to_vop(state->crtc); + struct drm_framebuffer *fb = state->fb; + unsigned int actual_w, actual_h; + unsigned int dsp_stx, dsp_sty; + uint32_t act_info, dsp_info, dsp_st; + struct drm_rect *src = &vop_plane_state->src; + struct drm_rect *dest = &vop_plane_state->dest; + struct drm_gem_object *obj, *uv_obj; + struct rockchip_gem_object *rk_obj, *rk_uv_obj; + unsigned long offset; + dma_addr_t dma_addr; + uint32_t val; + bool rb_swap; /* - * If this plane update changes the plane's framebuffer, (or more - * precisely, if this update has a different framebuffer than the last - * update), enqueue it so we can track when it completes. - * - * Only when we discover that this update has completed, can we - * unreference any previous framebuffers. + * can't update plane when vop is disabled. */ - mutex_lock(&vop->vsync_mutex); - if (fb != vop_win_last_pending_fb(vop_win)) { - ret = drm_vblank_get(plane->dev, vop->pipe); - if (ret) { - DRM_ERROR("failed to get vblank, %d\n", ret); - mutex_unlock(&vop->vsync_mutex); - return ret; - } - - drm_framebuffer_reference(fb); + if (!crtc) + return; - ret = vop_win_queue_fb(vop_win, fb, yrgb_mst, event); - if (ret) { - drm_vblank_put(plane->dev, vop->pipe); - mutex_unlock(&vop->vsync_mutex); - return ret; - } + if (WARN_ON(!vop->is_enabled)) + return; - vop->vsync_work_pending = true; + if (!vop_plane_state->enable) { + vop_plane_atomic_disable(plane, old_state); + return; } - mutex_unlock(&vop->vsync_mutex); + + obj = rockchip_fb_get_gem_obj(fb, 0); + rk_obj = to_rockchip_obj(obj); + + actual_w = drm_rect_width(src) >> 16; + actual_h = drm_rect_height(src) >> 16; + act_info = (actual_h - 1) << 16 | ((actual_w - 1) & 0xffff); + + dsp_info = (drm_rect_height(dest) - 1) << 16; + dsp_info |= (drm_rect_width(dest) - 1) & 0xffff; + + dsp_stx = dest->x1 + crtc->mode.htotal - crtc->mode.hsync_start; + dsp_sty = dest->y1 + crtc->mode.vtotal - crtc->mode.vsync_start; + dsp_st = dsp_sty << 16 | (dsp_stx & 0xffff); + + offset = (src->x1 >> 16) * drm_format_plane_cpp(fb->pixel_format, 0); + offset += (src->y1 >> 16) * fb->pitches[0]; + vop_plane_state->yrgb_mst = rk_obj->dma_addr + offset + fb->offsets[0]; spin_lock(&vop->reg_lock); - VOP_WIN_SET(vop, win, format, format); - VOP_WIN_SET(vop, win, yrgb_vir, y_vir_stride); - VOP_WIN_SET(vop, win, yrgb_mst, yrgb_mst); - if (is_yuv) { - VOP_WIN_SET(vop, win, uv_vir, uv_vir_stride); - VOP_WIN_SET(vop, win, uv_mst, uv_mst); + VOP_WIN_SET(vop, win, format, vop_plane_state->format); + VOP_WIN_SET(vop, win, yrgb_vir, fb->pitches[0] >> 2); + VOP_WIN_SET(vop, win, yrgb_mst, vop_plane_state->yrgb_mst); + if (is_yuv_support(fb->pixel_format)) { + int hsub = drm_format_horz_chroma_subsampling(fb->pixel_format); + int vsub = drm_format_vert_chroma_subsampling(fb->pixel_format); + int bpp = drm_format_plane_cpp(fb->pixel_format, 1); + + uv_obj = rockchip_fb_get_gem_obj(fb, 1); + rk_uv_obj = to_rockchip_obj(uv_obj); + + offset = (src->x1 >> 16) * bpp / hsub; + offset += (src->y1 >> 16) * fb->pitches[1] / vsub; + + dma_addr = rk_uv_obj->dma_addr + offset + fb->offsets[1]; + VOP_WIN_SET(vop, win, uv_vir, fb->pitches[1] >> 2); + VOP_WIN_SET(vop, win, uv_mst, dma_addr); } if (win->phy->scl) scl_vop_cal_scl_fac(vop, win, actual_w, actual_h, - dest.x2 - dest.x1, dest.y2 - dest.y1, + drm_rect_width(dest), drm_rect_height(dest), fb->pixel_format); - val = (actual_h - 1) << 16; - val |= (actual_w - 1) & 0xffff; - VOP_WIN_SET(vop, win, act_info, val); + VOP_WIN_SET(vop, win, act_info, act_info); + VOP_WIN_SET(vop, win, dsp_info, dsp_info); + VOP_WIN_SET(vop, win, dsp_st, dsp_st); - val = (dest.y2 - dest.y1 - 1) << 16; - val |= (dest.x2 - dest.x1 - 1) & 0xffff; - VOP_WIN_SET(vop, win, dsp_info, val); - val = dsp_sty << 16; - val |= dsp_stx & 0xffff; - VOP_WIN_SET(vop, win, dsp_st, val); + rb_swap = has_rb_swapped(fb->pixel_format); VOP_WIN_SET(vop, win, rb_swap, rb_swap); - if (is_alpha) { + if (is_alpha_support(fb->pixel_format)) { VOP_WIN_SET(vop, win, dst_alpha_ctl, DST_FACTOR_M0(ALPHA_SRC_INVERSE)); val = SRC_ALPHA_EN(1) | SRC_COLOR_M0(ALPHA_SRC_PRE_MUL) | @@ -979,86 +735,70 @@ static int vop_update_plane_event(struct drm_plane *plane, } VOP_WIN_SET(vop, win, enable, 1); - - vop_cfg_done(vop); spin_unlock(&vop->reg_lock); - - return 0; } -static int vop_update_plane(struct drm_plane *plane, struct drm_crtc *crtc, - struct drm_framebuffer *fb, int crtc_x, int crtc_y, - unsigned int crtc_w, unsigned int crtc_h, - uint32_t src_x, uint32_t src_y, uint32_t src_w, - uint32_t src_h) -{ - return vop_update_plane_event(plane, crtc, fb, crtc_x, crtc_y, crtc_w, - crtc_h, src_x, src_y, src_w, src_h, - NULL); -} +static const struct drm_plane_helper_funcs plane_helper_funcs = { + .atomic_check = vop_plane_atomic_check, + .atomic_update = vop_plane_atomic_update, + .atomic_disable = vop_plane_atomic_disable, +}; -static int vop_update_primary_plane(struct drm_crtc *crtc, - struct drm_pending_vblank_event *event) +void vop_atomic_plane_reset(struct drm_plane *plane) { - unsigned int crtc_w, crtc_h; + struct vop_plane_state *vop_plane_state = + to_vop_plane_state(plane->state); - crtc_w = crtc->primary->fb->width - crtc->x; - crtc_h = crtc->primary->fb->height - crtc->y; + if (plane->state && plane->state->fb) + drm_framebuffer_unreference(plane->state->fb); - return vop_update_plane_event(crtc->primary, crtc, crtc->primary->fb, - 0, 0, crtc_w, crtc_h, crtc->x << 16, - crtc->y << 16, crtc_w << 16, - crtc_h << 16, event); + kfree(vop_plane_state); + vop_plane_state = kzalloc(sizeof(*vop_plane_state), GFP_KERNEL); + if (!vop_plane_state) + return; + + plane->state = &vop_plane_state->base; + plane->state->plane = plane; } -static int vop_disable_plane(struct drm_plane *plane) +struct drm_plane_state * +vop_atomic_plane_duplicate_state(struct drm_plane *plane) { - struct vop_win *vop_win = to_vop_win(plane); - const struct vop_win_data *win = vop_win->data; - struct vop *vop; - int ret; + struct vop_plane_state *old_vop_plane_state; + struct vop_plane_state *vop_plane_state; - if (!plane->crtc) - return 0; + if (WARN_ON(!plane->state)) + return NULL; - vop = to_vop(plane->crtc); + old_vop_plane_state = to_vop_plane_state(plane->state); + vop_plane_state = kmemdup(old_vop_plane_state, + sizeof(*vop_plane_state), GFP_KERNEL); + if (!vop_plane_state) + return NULL; - ret = drm_vblank_get(plane->dev, vop->pipe); - if (ret) { - DRM_ERROR("failed to get vblank, %d\n", ret); - return ret; - } + __drm_atomic_helper_plane_duplicate_state(plane, + &vop_plane_state->base); - mutex_lock(&vop->vsync_mutex); - - ret = vop_win_queue_fb(vop_win, NULL, 0, NULL); - if (ret) { - drm_vblank_put(plane->dev, vop->pipe); - mutex_unlock(&vop->vsync_mutex); - return ret; - } - - vop->vsync_work_pending = true; - mutex_unlock(&vop->vsync_mutex); - - spin_lock(&vop->reg_lock); - VOP_WIN_SET(vop, win, enable, 0); - vop_cfg_done(vop); - spin_unlock(&vop->reg_lock); - - return 0; + return &vop_plane_state->base; } -static void vop_plane_destroy(struct drm_plane *plane) +static void vop_atomic_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) { - vop_disable_plane(plane); - drm_plane_cleanup(plane); + struct vop_plane_state *vop_state = to_vop_plane_state(state); + + __drm_atomic_helper_plane_destroy_state(plane, state); + + kfree(vop_state); } static const struct drm_plane_funcs vop_plane_funcs = { - .update_plane = vop_update_plane, - .disable_plane = vop_disable_plane, + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, .destroy = vop_plane_destroy, + .reset = vop_atomic_plane_reset, + .atomic_duplicate_state = vop_atomic_plane_duplicate_state, + .atomic_destroy_state = vop_atomic_plane_destroy_state, }; int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc, @@ -1067,8 +807,27 @@ int rockchip_drm_crtc_mode_config(struct drm_crtc *crtc, { struct vop *vop = to_vop(crtc); - vop->connector_type = connector_type; - vop->connector_out_mode = out_mode; + if (WARN_ON(!vop->is_enabled)) + return -EINVAL; + + switch (connector_type) { + case DRM_MODE_CONNECTOR_LVDS: + VOP_CTRL_SET(vop, rgb_en, 1); + break; + case DRM_MODE_CONNECTOR_eDP: + VOP_CTRL_SET(vop, edp_en, 1); + break; + case DRM_MODE_CONNECTOR_HDMIA: + VOP_CTRL_SET(vop, hdmi_en, 1); + break; + case DRM_MODE_CONNECTOR_DSI: + VOP_CTRL_SET(vop, mipi_en, 1); + break; + default: + DRM_ERROR("unsupport connector_type[%d]\n", connector_type); + return -EINVAL; + }; + VOP_CTRL_SET(vop, out_mode, out_mode); return 0; } @@ -1079,12 +838,12 @@ static int vop_crtc_enable_vblank(struct drm_crtc *crtc) struct vop *vop = to_vop(crtc); unsigned long flags; - if (!vop->is_enabled) + if (WARN_ON(!vop->is_enabled)) return -EPERM; spin_lock_irqsave(&vop->irq_lock, flags); - vop_mask_write(vop, INTR_CTRL0, FS_INTR_MASK, FS_INTR_EN(1)); + VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 1); spin_unlock_irqrestore(&vop->irq_lock, flags); @@ -1096,76 +855,49 @@ static void vop_crtc_disable_vblank(struct drm_crtc *crtc) struct vop *vop = to_vop(crtc); unsigned long flags; - if (!vop->is_enabled) + if (WARN_ON(!vop->is_enabled)) return; spin_lock_irqsave(&vop->irq_lock, flags); - vop_mask_write(vop, INTR_CTRL0, FS_INTR_MASK, FS_INTR_EN(0)); + + VOP_INTR_SET_TYPE(vop, enable, FS_INTR, 0); + spin_unlock_irqrestore(&vop->irq_lock, flags); } -static const struct rockchip_crtc_funcs private_crtc_funcs = { - .enable_vblank = vop_crtc_enable_vblank, - .disable_vblank = vop_crtc_disable_vblank, -}; - -static void vop_crtc_dpms(struct drm_crtc *crtc, int mode) +static void vop_crtc_wait_for_update(struct drm_crtc *crtc) { - DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode); + struct vop *vop = to_vop(crtc); - switch (mode) { - case DRM_MODE_DPMS_ON: - vop_enable(crtc); - break; - case DRM_MODE_DPMS_STANDBY: - case DRM_MODE_DPMS_SUSPEND: - case DRM_MODE_DPMS_OFF: - vop_disable(crtc); - break; - default: - DRM_DEBUG_KMS("unspecified mode %d\n", mode); - break; - } + reinit_completion(&vop->wait_update_complete); + WARN_ON(!wait_for_completion_timeout(&vop->wait_update_complete, 100)); } -static void vop_crtc_prepare(struct drm_crtc *crtc) -{ - vop_crtc_dpms(crtc, DRM_MODE_DPMS_ON); -} +static const struct rockchip_crtc_funcs private_crtc_funcs = { + .enable_vblank = vop_crtc_enable_vblank, + .disable_vblank = vop_crtc_disable_vblank, + .wait_for_update = vop_crtc_wait_for_update, +}; static bool vop_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { + struct vop *vop = to_vop(crtc); + if (adjusted_mode->htotal == 0 || adjusted_mode->vtotal == 0) return false; - return true; -} - -static int vop_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, - struct drm_framebuffer *old_fb) -{ - int ret; - - crtc->x = x; - crtc->y = y; + adjusted_mode->clock = + clk_round_rate(vop->dclk, mode->clock * 1000) / 1000; - ret = vop_update_primary_plane(crtc, NULL); - if (ret < 0) { - DRM_ERROR("fail to update plane\n"); - return ret; - } - - return 0; + return true; } -static int vop_crtc_mode_set(struct drm_crtc *crtc, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode, - int x, int y, struct drm_framebuffer *fb) +static void vop_crtc_enable(struct drm_crtc *crtc) { struct vop *vop = to_vop(crtc); + struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode; u16 hsync_len = adjusted_mode->hsync_end - adjusted_mode->hsync_start; u16 hdisplay = adjusted_mode->hdisplay; u16 htotal = adjusted_mode->htotal; @@ -1176,32 +908,44 @@ static int vop_crtc_mode_set(struct drm_crtc *crtc, u16 vsync_len = adjusted_mode->vsync_end - adjusted_mode->vsync_start; u16 vact_st = adjusted_mode->vtotal - adjusted_mode->vsync_start; u16 vact_end = vact_st + vdisplay; - int ret, ret_clk; uint32_t val; + vop_enable(crtc); /* - * disable dclk to stop frame scan, so that we can safe config mode and - * enable iommu. + * If dclk rate is zero, mean that scanout is stop, + * we don't need wait any more. */ - clk_disable(vop->dclk); + if (clk_get_rate(vop->dclk)) { + /* + * Rk3288 vop timing register is immediately, when configure + * display timing on display time, may cause tearing. + * + * Vop standby will take effect at end of current frame, + * if dsp hold valid irq happen, it means standby complete. + * + * mode set: + * standby and wait complete --> |---- + * | display time + * |---- + * |---> dsp hold irq + * configure display timing --> | + * standby exit | + * | new frame start. + */ - switch (vop->connector_type) { - case DRM_MODE_CONNECTOR_LVDS: - VOP_CTRL_SET(vop, rgb_en, 1); - break; - case DRM_MODE_CONNECTOR_eDP: - VOP_CTRL_SET(vop, edp_en, 1); - break; - case DRM_MODE_CONNECTOR_HDMIA: - VOP_CTRL_SET(vop, hdmi_en, 1); - break; - default: - DRM_ERROR("unsupport connector_type[%d]\n", - vop->connector_type); - ret = -EINVAL; - goto out; - }; - VOP_CTRL_SET(vop, out_mode, vop->connector_out_mode); + reinit_completion(&vop->dsp_hold_completion); + vop_dsp_hold_valid_irq_enable(vop); + + spin_lock(&vop->reg_lock); + + VOP_CTRL_SET(vop, standby, 1); + + spin_unlock(&vop->reg_lock); + + wait_for_completion(&vop->dsp_hold_completion); + + vop_dsp_hold_valid_irq_disable(vop); + } val = 0x8; val |= (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1; @@ -1220,211 +964,119 @@ static int vop_crtc_mode_set(struct drm_crtc *crtc, VOP_CTRL_SET(vop, vact_st_end, val); VOP_CTRL_SET(vop, vpost_st_end, val); - ret = vop_crtc_mode_set_base(crtc, x, y, fb); - if (ret) - goto out; - - /* - * reset dclk, take all mode config affect, so the clk would run in - * correct frame. - */ - reset_control_assert(vop->dclk_rst); - usleep_range(10, 20); - reset_control_deassert(vop->dclk_rst); - clk_set_rate(vop->dclk, adjusted_mode->clock * 1000); -out: - ret_clk = clk_enable(vop->dclk); - if (ret_clk < 0) { - dev_err(vop->dev, "failed to enable dclk - %d\n", ret_clk); - return ret_clk; - } - return ret; -} - -static void vop_crtc_commit(struct drm_crtc *crtc) -{ + VOP_CTRL_SET(vop, standby, 0); } -static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = { - .dpms = vop_crtc_dpms, - .prepare = vop_crtc_prepare, - .mode_fixup = vop_crtc_mode_fixup, - .mode_set = vop_crtc_mode_set, - .mode_set_base = vop_crtc_mode_set_base, - .commit = vop_crtc_commit, -}; - -static int vop_crtc_page_flip(struct drm_crtc *crtc, - struct drm_framebuffer *fb, - struct drm_pending_vblank_event *event, - uint32_t page_flip_flags) +static void vop_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) { struct vop *vop = to_vop(crtc); - struct drm_framebuffer *old_fb = crtc->primary->fb; - int ret; - /* when the page flip is requested, crtc should be on */ - if (!vop->is_enabled) { - DRM_DEBUG("page flip request rejected because crtc is off.\n"); - return 0; - } + if (WARN_ON(!vop->is_enabled)) + return; - crtc->primary->fb = fb; + spin_lock(&vop->reg_lock); - ret = vop_update_primary_plane(crtc, event); - if (ret) - crtc->primary->fb = old_fb; + vop_cfg_done(vop); - return ret; + spin_unlock(&vop->reg_lock); } -static void vop_win_state_complete(struct vop_win *vop_win, - struct vop_win_state *state) +static void vop_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) { - struct vop *vop = vop_win->vop; - struct drm_crtc *crtc = &vop->crtc; - struct drm_device *drm = crtc->dev; - unsigned long flags; + struct vop *vop = to_vop(crtc); - if (state->event) { - spin_lock_irqsave(&drm->event_lock, flags); - drm_crtc_send_vblank_event(crtc, state->event); - spin_unlock_irqrestore(&drm->event_lock, flags); - } + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); - list_del(&state->head); - drm_vblank_put(crtc->dev, vop->pipe); + vop->event = crtc->state->event; + crtc->state->event = NULL; + } } +static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = { + .enable = vop_crtc_enable, + .disable = vop_crtc_disable, + .mode_fixup = vop_crtc_mode_fixup, + .atomic_flush = vop_crtc_atomic_flush, + .atomic_begin = vop_crtc_atomic_begin, +}; + static void vop_crtc_destroy(struct drm_crtc *crtc) { drm_crtc_cleanup(crtc); } static const struct drm_crtc_funcs vop_crtc_funcs = { - .set_config = drm_crtc_helper_set_config, - .page_flip = vop_crtc_page_flip, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, .destroy = vop_crtc_destroy, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, }; -static bool vop_win_state_is_active(struct vop_win *vop_win, - struct vop_win_state *state) +static bool vop_win_pending_is_complete(struct vop_win *vop_win) { - bool active = false; - - if (state->fb) { - dma_addr_t yrgb_mst; - - /* check yrgb_mst to tell if pending_fb is now front */ - yrgb_mst = VOP_WIN_GET_YRGBADDR(vop_win->vop, vop_win->data); - - active = (yrgb_mst == state->yrgb_mst); - } else { - bool enabled; - - /* if enable bit is clear, plane is now disabled */ - enabled = VOP_WIN_GET(vop_win->vop, vop_win->data, enable); - - active = (enabled == 0); - } + struct drm_plane *plane = &vop_win->base; + struct vop_plane_state *state = to_vop_plane_state(plane->state); + dma_addr_t yrgb_mst; - return active; -} + if (!state->enable) + return VOP_WIN_GET(vop_win->vop, vop_win->data, enable) == 0; -static void vop_win_state_destroy(struct vop_win_state *state) -{ - struct drm_framebuffer *fb = state->fb; + yrgb_mst = VOP_WIN_GET_YRGBADDR(vop_win->vop, vop_win->data); - if (fb) - drm_framebuffer_unreference(fb); - - kfree(state); + return yrgb_mst == state->yrgb_mst; } -static void vop_win_update_state(struct vop_win *vop_win) +static void vop_handle_vblank(struct vop *vop) { - struct vop_win_state *state, *n, *new_active = NULL; - - /* Check if any pending states are now active */ - list_for_each_entry(state, &vop_win->pending, head) - if (vop_win_state_is_active(vop_win, state)) { - new_active = state; - break; - } - - if (!new_active) - return; + struct drm_device *drm = vop->drm_dev; + struct drm_crtc *crtc = &vop->crtc; + unsigned long flags; + int i; - /* - * Destroy any 'skipped' pending states - states that were queued - * before the newly active state. - */ - list_for_each_entry_safe(state, n, &vop_win->pending, head) { - if (state == new_active) - break; - vop_win_state_complete(vop_win, state); - vop_win_state_destroy(state); + for (i = 0; i < vop->data->win_size; i++) { + if (!vop_win_pending_is_complete(&vop->win[i])) + return; } - vop_win_state_complete(vop_win, new_active); - - if (vop_win->active) - vop_win_state_destroy(vop_win->active); - vop_win->active = new_active; -} - -static bool vop_win_has_pending_state(struct vop_win *vop_win) -{ - return !list_empty(&vop_win->pending); -} - -static irqreturn_t vop_isr_thread(int irq, void *data) -{ - struct vop *vop = data; - const struct vop_data *vop_data = vop->data; - unsigned int i; - - mutex_lock(&vop->vsync_mutex); - - if (!vop->vsync_work_pending) - goto done; + if (vop->event) { + spin_lock_irqsave(&drm->event_lock, flags); - vop->vsync_work_pending = false; + drm_crtc_send_vblank_event(crtc, vop->event); + drm_crtc_vblank_put(crtc); + vop->event = NULL; - for (i = 0; i < vop_data->win_size; i++) { - struct vop_win *vop_win = &vop->win[i]; - - vop_win_update_state(vop_win); - if (vop_win_has_pending_state(vop_win)) - vop->vsync_work_pending = true; + spin_unlock_irqrestore(&drm->event_lock, flags); } - -done: - mutex_unlock(&vop->vsync_mutex); - - return IRQ_HANDLED; + if (!completion_done(&vop->wait_update_complete)) + complete(&vop->wait_update_complete); } static irqreturn_t vop_isr(int irq, void *data) { struct vop *vop = data; - uint32_t intr0_reg, active_irqs; + struct drm_crtc *crtc = &vop->crtc; + uint32_t active_irqs; unsigned long flags; int ret = IRQ_NONE; /* - * INTR_CTRL0 register has interrupt status, enable and clear bits, we + * interrupt register has interrupt status, enable and clear bits, we * must hold irq_lock to avoid a race with enable/disable_vblank(). */ spin_lock_irqsave(&vop->irq_lock, flags); - intr0_reg = vop_readl(vop, INTR_CTRL0); - active_irqs = intr0_reg & INTR_MASK; + + active_irqs = VOP_INTR_GET_TYPE(vop, status, INTR_MASK); /* Clear all active interrupt sources */ if (active_irqs) - vop_writel(vop, INTR_CTRL0, - intr0_reg | (active_irqs << INTR_CLR_SHIFT)); + VOP_INTR_SET_TYPE(vop, clear, active_irqs, 1); + spin_unlock_irqrestore(&vop->irq_lock, flags); /* This is expected for vop iommu irqs, since the irq is shared */ @@ -1438,9 +1090,10 @@ static irqreturn_t vop_isr(int irq, void *data) } if (active_irqs & FS_INTR) { - drm_handle_vblank(vop->drm_dev, vop->pipe); + drm_crtc_handle_vblank(crtc); + vop_handle_vblank(vop); active_irqs &= ~FS_INTR; - ret = (vop->vsync_work_pending) ? IRQ_WAKE_THREAD : IRQ_HANDLED; + ret = IRQ_HANDLED; } /* Unhandled irqs are spurious. */ @@ -1478,13 +1131,14 @@ static int vop_create_crtc(struct vop *vop) 0, &vop_plane_funcs, win_data->phy->data_formats, win_data->phy->nformats, - win_data->type); + win_data->type, NULL); if (ret) { DRM_ERROR("failed to initialize plane\n"); goto err_cleanup_planes; } plane = &vop_win->base; + drm_plane_helper_add(plane, &plane_helper_funcs); if (plane->type == DRM_PLANE_TYPE_PRIMARY) primary = plane; else if (plane->type == DRM_PLANE_TYPE_CURSOR) @@ -1492,7 +1146,7 @@ static int vop_create_crtc(struct vop *vop) } ret = drm_crtc_init_with_planes(drm_dev, crtc, primary, cursor, - &vop_crtc_funcs); + &vop_crtc_funcs, NULL); if (ret) return ret; @@ -1515,11 +1169,12 @@ static int vop_create_crtc(struct vop *vop) &vop_plane_funcs, win_data->phy->data_formats, win_data->phy->nformats, - win_data->type); + win_data->type, NULL); if (ret) { DRM_ERROR("failed to initialize overlay plane\n"); goto err_cleanup_crtc; } + drm_plane_helper_add(&vop_win->base, &plane_helper_funcs); } port = of_get_child_by_name(dev->of_node, "port"); @@ -1530,9 +1185,9 @@ static int vop_create_crtc(struct vop *vop) } init_completion(&vop->dsp_hold_completion); + init_completion(&vop->wait_update_complete); crtc->port = port; - vop->pipe = drm_crtc_index(crtc); - rockchip_register_crtc_funcs(drm_dev, &private_crtc_funcs, vop->pipe); + rockchip_register_crtc_funcs(crtc, &private_crtc_funcs); return 0; @@ -1548,7 +1203,7 @@ static void vop_destroy_crtc(struct vop *vop) { struct drm_crtc *crtc = &vop->crtc; - rockchip_unregister_crtc_funcs(vop->drm_dev, vop->pipe); + rockchip_unregister_crtc_funcs(crtc); of_node_put(crtc->port); drm_crtc_cleanup(crtc); } @@ -1664,14 +1319,12 @@ static void vop_win_init(struct vop *vop) vop_win->data = win_data; vop_win->vop = vop; - INIT_LIST_HEAD(&vop_win->pending); } } static int vop_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); - const struct of_device_id *of_id; const struct vop_data *vop_data; struct drm_device *drm_dev = data; struct vop *vop; @@ -1679,8 +1332,7 @@ static int vop_bind(struct device *dev, struct device *master, void *data) size_t alloc_size; int ret, irq; - of_id = of_match_device(vop_driver_dt_match, dev); - vop_data = of_id->data; + vop_data = of_device_get_match_data(dev); if (!vop_data) return -ENODEV; @@ -1725,8 +1377,8 @@ static int vop_bind(struct device *dev, struct device *master, void *data) mutex_init(&vop->vsync_mutex); - ret = devm_request_threaded_irq(dev, vop->irq, vop_isr, vop_isr_thread, - IRQF_SHARED, dev_name(dev), vop); + ret = devm_request_irq(dev, vop->irq, vop_isr, + IRQF_SHARED, dev_name(dev), vop); if (ret) return ret; @@ -1749,42 +1401,8 @@ static void vop_unbind(struct device *dev, struct device *master, void *data) vop_destroy_crtc(vop); } -static const struct component_ops vop_component_ops = { +const struct component_ops vop_component_ops = { .bind = vop_bind, .unbind = vop_unbind, }; - -static int vop_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - - if (!dev->of_node) { - dev_err(dev, "can't find vop devices\n"); - return -ENODEV; - } - - return component_add(dev, &vop_component_ops); -} - -static int vop_remove(struct platform_device *pdev) -{ - component_del(&pdev->dev, &vop_component_ops); - - return 0; -} - -struct platform_driver vop_platform_driver = { - .probe = vop_probe, - .remove = vop_remove, - .driver = { - .name = "rockchip-vop", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(vop_driver_dt_match), - }, -}; - -module_platform_driver(vop_platform_driver); - -MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>"); -MODULE_DESCRIPTION("ROCKCHIP VOP Driver"); -MODULE_LICENSE("GPL v2"); +EXPORT_SYMBOL_GPL(vop_component_ops); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index a2d4ddb89..071ff0be7 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -15,111 +15,125 @@ #ifndef _ROCKCHIP_DRM_VOP_H #define _ROCKCHIP_DRM_VOP_H -/* register definition */ -#define REG_CFG_DONE 0x0000 -#define VERSION_INFO 0x0004 -#define SYS_CTRL 0x0008 -#define SYS_CTRL1 0x000c -#define DSP_CTRL0 0x0010 -#define DSP_CTRL1 0x0014 -#define DSP_BG 0x0018 -#define MCU_CTRL 0x001c -#define INTR_CTRL0 0x0020 -#define INTR_CTRL1 0x0024 -#define WIN0_CTRL0 0x0030 -#define WIN0_CTRL1 0x0034 -#define WIN0_COLOR_KEY 0x0038 -#define WIN0_VIR 0x003c -#define WIN0_YRGB_MST 0x0040 -#define WIN0_CBR_MST 0x0044 -#define WIN0_ACT_INFO 0x0048 -#define WIN0_DSP_INFO 0x004c -#define WIN0_DSP_ST 0x0050 -#define WIN0_SCL_FACTOR_YRGB 0x0054 -#define WIN0_SCL_FACTOR_CBR 0x0058 -#define WIN0_SCL_OFFSET 0x005c -#define WIN0_SRC_ALPHA_CTRL 0x0060 -#define WIN0_DST_ALPHA_CTRL 0x0064 -#define WIN0_FADING_CTRL 0x0068 -/* win1 register */ -#define WIN1_CTRL0 0x0070 -#define WIN1_CTRL1 0x0074 -#define WIN1_COLOR_KEY 0x0078 -#define WIN1_VIR 0x007c -#define WIN1_YRGB_MST 0x0080 -#define WIN1_CBR_MST 0x0084 -#define WIN1_ACT_INFO 0x0088 -#define WIN1_DSP_INFO 0x008c -#define WIN1_DSP_ST 0x0090 -#define WIN1_SCL_FACTOR_YRGB 0x0094 -#define WIN1_SCL_FACTOR_CBR 0x0098 -#define WIN1_SCL_OFFSET 0x009c -#define WIN1_SRC_ALPHA_CTRL 0x00a0 -#define WIN1_DST_ALPHA_CTRL 0x00a4 -#define WIN1_FADING_CTRL 0x00a8 -/* win2 register */ -#define WIN2_CTRL0 0x00b0 -#define WIN2_CTRL1 0x00b4 -#define WIN2_VIR0_1 0x00b8 -#define WIN2_VIR2_3 0x00bc -#define WIN2_MST0 0x00c0 -#define WIN2_DSP_INFO0 0x00c4 -#define WIN2_DSP_ST0 0x00c8 -#define WIN2_COLOR_KEY 0x00cc -#define WIN2_MST1 0x00d0 -#define WIN2_DSP_INFO1 0x00d4 -#define WIN2_DSP_ST1 0x00d8 -#define WIN2_SRC_ALPHA_CTRL 0x00dc -#define WIN2_MST2 0x00e0 -#define WIN2_DSP_INFO2 0x00e4 -#define WIN2_DSP_ST2 0x00e8 -#define WIN2_DST_ALPHA_CTRL 0x00ec -#define WIN2_MST3 0x00f0 -#define WIN2_DSP_INFO3 0x00f4 -#define WIN2_DSP_ST3 0x00f8 -#define WIN2_FADING_CTRL 0x00fc -/* win3 register */ -#define WIN3_CTRL0 0x0100 -#define WIN3_CTRL1 0x0104 -#define WIN3_VIR0_1 0x0108 -#define WIN3_VIR2_3 0x010c -#define WIN3_MST0 0x0110 -#define WIN3_DSP_INFO0 0x0114 -#define WIN3_DSP_ST0 0x0118 -#define WIN3_COLOR_KEY 0x011c -#define WIN3_MST1 0x0120 -#define WIN3_DSP_INFO1 0x0124 -#define WIN3_DSP_ST1 0x0128 -#define WIN3_SRC_ALPHA_CTRL 0x012c -#define WIN3_MST2 0x0130 -#define WIN3_DSP_INFO2 0x0134 -#define WIN3_DSP_ST2 0x0138 -#define WIN3_DST_ALPHA_CTRL 0x013c -#define WIN3_MST3 0x0140 -#define WIN3_DSP_INFO3 0x0144 -#define WIN3_DSP_ST3 0x0148 -#define WIN3_FADING_CTRL 0x014c -/* hwc register */ -#define HWC_CTRL0 0x0150 -#define HWC_CTRL1 0x0154 -#define HWC_MST 0x0158 -#define HWC_DSP_ST 0x015c -#define HWC_SRC_ALPHA_CTRL 0x0160 -#define HWC_DST_ALPHA_CTRL 0x0164 -#define HWC_FADING_CTRL 0x0168 -/* post process register */ -#define POST_DSP_HACT_INFO 0x0170 -#define POST_DSP_VACT_INFO 0x0174 -#define POST_SCL_FACTOR_YRGB 0x0178 -#define POST_SCL_CTRL 0x0180 -#define POST_DSP_VACT_INFO_F1 0x0184 -#define DSP_HTOTAL_HS_END 0x0188 -#define DSP_HACT_ST_END 0x018c -#define DSP_VTOTAL_VS_END 0x0190 -#define DSP_VACT_ST_END 0x0194 -#define DSP_VS_ST_END_F1 0x0198 -#define DSP_VACT_ST_END_F1 0x019c -/* register definition end */ +enum vop_data_format { + VOP_FMT_ARGB8888 = 0, + VOP_FMT_RGB888, + VOP_FMT_RGB565, + VOP_FMT_YUV420SP = 4, + VOP_FMT_YUV422SP, + VOP_FMT_YUV444SP, +}; + +struct vop_reg_data { + uint32_t offset; + uint32_t value; +}; + +struct vop_reg { + uint32_t offset; + uint32_t shift; + uint32_t mask; +}; + +struct vop_ctrl { + struct vop_reg standby; + struct vop_reg data_blank; + struct vop_reg gate_en; + struct vop_reg mmu_en; + struct vop_reg rgb_en; + struct vop_reg edp_en; + struct vop_reg hdmi_en; + struct vop_reg mipi_en; + struct vop_reg out_mode; + struct vop_reg dither_down; + struct vop_reg dither_up; + struct vop_reg pin_pol; + + struct vop_reg htotal_pw; + struct vop_reg hact_st_end; + struct vop_reg vtotal_pw; + struct vop_reg vact_st_end; + struct vop_reg hpost_st_end; + struct vop_reg vpost_st_end; + + struct vop_reg cfg_done; +}; + +struct vop_intr { + const int *intrs; + uint32_t nintrs; + struct vop_reg enable; + struct vop_reg clear; + struct vop_reg status; +}; + +struct vop_scl_extension { + struct vop_reg cbcr_vsd_mode; + struct vop_reg cbcr_vsu_mode; + struct vop_reg cbcr_hsd_mode; + struct vop_reg cbcr_ver_scl_mode; + struct vop_reg cbcr_hor_scl_mode; + struct vop_reg yrgb_vsd_mode; + struct vop_reg yrgb_vsu_mode; + struct vop_reg yrgb_hsd_mode; + struct vop_reg yrgb_ver_scl_mode; + struct vop_reg yrgb_hor_scl_mode; + struct vop_reg line_load_mode; + struct vop_reg cbcr_axi_gather_num; + struct vop_reg yrgb_axi_gather_num; + struct vop_reg vsd_cbcr_gt2; + struct vop_reg vsd_cbcr_gt4; + struct vop_reg vsd_yrgb_gt2; + struct vop_reg vsd_yrgb_gt4; + struct vop_reg bic_coe_sel; + struct vop_reg cbcr_axi_gather_en; + struct vop_reg yrgb_axi_gather_en; + struct vop_reg lb_mode; +}; + +struct vop_scl_regs { + const struct vop_scl_extension *ext; + + struct vop_reg scale_yrgb_x; + struct vop_reg scale_yrgb_y; + struct vop_reg scale_cbcr_x; + struct vop_reg scale_cbcr_y; +}; + +struct vop_win_phy { + const struct vop_scl_regs *scl; + const uint32_t *data_formats; + uint32_t nformats; + + struct vop_reg enable; + struct vop_reg format; + struct vop_reg rb_swap; + struct vop_reg act_info; + struct vop_reg dsp_info; + struct vop_reg dsp_st; + struct vop_reg yrgb_mst; + struct vop_reg uv_mst; + struct vop_reg yrgb_vir; + struct vop_reg uv_vir; + + struct vop_reg dst_alpha_ctl; + struct vop_reg src_alpha_ctl; +}; + +struct vop_win_data { + uint32_t base; + const struct vop_win_phy *phy; + enum drm_plane_type type; +}; + +struct vop_data { + const struct vop_reg_data *init_table; + unsigned int table_size; + const struct vop_ctrl *ctrl; + const struct vop_intr *intr; + const struct vop_win_data *win; + unsigned int win_size; +}; /* interrupt define */ #define DSP_HOLD_VALID_INTR (1 << 0) @@ -233,6 +247,11 @@ static inline uint16_t scl_cal_scale(int src, int dst, int shift) return ((src * 2 - 3) << (shift - 1)) / (dst - 1); } +static inline uint16_t scl_cal_scale2(int src, int dst) +{ + return ((src - 1) << 12) / (dst - 1); +} + #define GET_SCL_FT_BILI_DN(src, dst) scl_cal_scale(src, dst, 12) #define GET_SCL_FT_BILI_UP(src, dst) scl_cal_scale(src, dst, 16) #define GET_SCL_FT_BIC(src, dst) scl_cal_scale(src, dst, 16) @@ -286,4 +305,5 @@ static inline int scl_vop_cal_lb_mode(int width, bool is_yuv) return lb_mode; } +extern const struct component_ops vop_component_ops; #endif /* _ROCKCHIP_DRM_VOP_H */ diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c new file mode 100644 index 000000000..3166b46a5 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c @@ -0,0 +1,316 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:Mark Yao <mark.yao@rock-chips.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> + +#include <linux/kernel.h> +#include <linux/component.h> + +#include "rockchip_drm_vop.h" +#include "rockchip_vop_reg.h" + +#define VOP_REG(off, _mask, s) \ + {.offset = off, \ + .mask = _mask, \ + .shift = s,} + +static const uint32_t formats_win_full[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_NV12, + DRM_FORMAT_NV16, + DRM_FORMAT_NV24, +}; + +static const uint32_t formats_win_lite[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, +}; + +static const struct vop_scl_extension rk3288_win_full_scl_ext = { + .cbcr_vsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 31), + .cbcr_vsu_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 30), + .cbcr_hsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 28), + .cbcr_ver_scl_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 26), + .cbcr_hor_scl_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 24), + .yrgb_vsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 23), + .yrgb_vsu_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 22), + .yrgb_hsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 20), + .yrgb_ver_scl_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 18), + .yrgb_hor_scl_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 16), + .line_load_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 15), + .cbcr_axi_gather_num = VOP_REG(RK3288_WIN0_CTRL1, 0x7, 12), + .yrgb_axi_gather_num = VOP_REG(RK3288_WIN0_CTRL1, 0xf, 8), + .vsd_cbcr_gt2 = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 7), + .vsd_cbcr_gt4 = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 6), + .vsd_yrgb_gt2 = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 5), + .vsd_yrgb_gt4 = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 4), + .bic_coe_sel = VOP_REG(RK3288_WIN0_CTRL1, 0x3, 2), + .cbcr_axi_gather_en = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 1), + .yrgb_axi_gather_en = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 0), + .lb_mode = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 5), +}; + +static const struct vop_scl_regs rk3288_win_full_scl = { + .ext = &rk3288_win_full_scl_ext, + .scale_yrgb_x = VOP_REG(RK3288_WIN0_SCL_FACTOR_YRGB, 0xffff, 0x0), + .scale_yrgb_y = VOP_REG(RK3288_WIN0_SCL_FACTOR_YRGB, 0xffff, 16), + .scale_cbcr_x = VOP_REG(RK3288_WIN0_SCL_FACTOR_CBR, 0xffff, 0x0), + .scale_cbcr_y = VOP_REG(RK3288_WIN0_SCL_FACTOR_CBR, 0xffff, 16), +}; + +static const struct vop_win_phy rk3288_win01_data = { + .scl = &rk3288_win_full_scl, + .data_formats = formats_win_full, + .nformats = ARRAY_SIZE(formats_win_full), + .enable = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 0), + .format = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 1), + .rb_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 12), + .act_info = VOP_REG(RK3288_WIN0_ACT_INFO, 0x1fff1fff, 0), + .dsp_info = VOP_REG(RK3288_WIN0_DSP_INFO, 0x0fff0fff, 0), + .dsp_st = VOP_REG(RK3288_WIN0_DSP_ST, 0x1fff1fff, 0), + .yrgb_mst = VOP_REG(RK3288_WIN0_YRGB_MST, 0xffffffff, 0), + .uv_mst = VOP_REG(RK3288_WIN0_CBR_MST, 0xffffffff, 0), + .yrgb_vir = VOP_REG(RK3288_WIN0_VIR, 0x3fff, 0), + .uv_vir = VOP_REG(RK3288_WIN0_VIR, 0x3fff, 16), + .src_alpha_ctl = VOP_REG(RK3288_WIN0_SRC_ALPHA_CTRL, 0xff, 0), + .dst_alpha_ctl = VOP_REG(RK3288_WIN0_DST_ALPHA_CTRL, 0xff, 0), +}; + +static const struct vop_win_phy rk3288_win23_data = { + .data_formats = formats_win_lite, + .nformats = ARRAY_SIZE(formats_win_lite), + .enable = VOP_REG(RK3288_WIN2_CTRL0, 0x1, 0), + .format = VOP_REG(RK3288_WIN2_CTRL0, 0x7, 1), + .rb_swap = VOP_REG(RK3288_WIN2_CTRL0, 0x1, 12), + .dsp_info = VOP_REG(RK3288_WIN2_DSP_INFO0, 0x0fff0fff, 0), + .dsp_st = VOP_REG(RK3288_WIN2_DSP_ST0, 0x1fff1fff, 0), + .yrgb_mst = VOP_REG(RK3288_WIN2_MST0, 0xffffffff, 0), + .yrgb_vir = VOP_REG(RK3288_WIN2_VIR0_1, 0x1fff, 0), + .src_alpha_ctl = VOP_REG(RK3288_WIN2_SRC_ALPHA_CTRL, 0xff, 0), + .dst_alpha_ctl = VOP_REG(RK3288_WIN2_DST_ALPHA_CTRL, 0xff, 0), +}; + +static const struct vop_ctrl rk3288_ctrl_data = { + .standby = VOP_REG(RK3288_SYS_CTRL, 0x1, 22), + .gate_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 23), + .mmu_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 20), + .rgb_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 12), + .hdmi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 13), + .edp_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 14), + .mipi_en = VOP_REG(RK3288_SYS_CTRL, 0x1, 15), + .dither_down = VOP_REG(RK3288_DSP_CTRL1, 0xf, 1), + .dither_up = VOP_REG(RK3288_DSP_CTRL1, 0x1, 6), + .data_blank = VOP_REG(RK3288_DSP_CTRL0, 0x1, 19), + .out_mode = VOP_REG(RK3288_DSP_CTRL0, 0xf, 0), + .pin_pol = VOP_REG(RK3288_DSP_CTRL0, 0xf, 4), + .htotal_pw = VOP_REG(RK3288_DSP_HTOTAL_HS_END, 0x1fff1fff, 0), + .hact_st_end = VOP_REG(RK3288_DSP_HACT_ST_END, 0x1fff1fff, 0), + .vtotal_pw = VOP_REG(RK3288_DSP_VTOTAL_VS_END, 0x1fff1fff, 0), + .vact_st_end = VOP_REG(RK3288_DSP_VACT_ST_END, 0x1fff1fff, 0), + .hpost_st_end = VOP_REG(RK3288_POST_DSP_HACT_INFO, 0x1fff1fff, 0), + .vpost_st_end = VOP_REG(RK3288_POST_DSP_VACT_INFO, 0x1fff1fff, 0), + .cfg_done = VOP_REG(RK3288_REG_CFG_DONE, 0x1, 0), +}; + +static const struct vop_reg_data rk3288_init_reg_table[] = { + {RK3288_SYS_CTRL, 0x00c00000}, + {RK3288_DSP_CTRL0, 0x00000000}, + {RK3288_WIN0_CTRL0, 0x00000080}, + {RK3288_WIN1_CTRL0, 0x00000080}, + /* TODO: Win2/3 support multiple area function, but we haven't found + * a suitable way to use it yet, so let's just use them as other windows + * with only area 0 enabled. + */ + {RK3288_WIN2_CTRL0, 0x00000010}, + {RK3288_WIN3_CTRL0, 0x00000010}, +}; + +/* + * Note: rk3288 has a dedicated 'cursor' window, however, that window requires + * special support to get alpha blending working. For now, just use overlay + * window 3 for the drm cursor. + * + */ +static const struct vop_win_data rk3288_vop_win_data[] = { + { .base = 0x00, .phy = &rk3288_win01_data, + .type = DRM_PLANE_TYPE_PRIMARY }, + { .base = 0x40, .phy = &rk3288_win01_data, + .type = DRM_PLANE_TYPE_OVERLAY }, + { .base = 0x00, .phy = &rk3288_win23_data, + .type = DRM_PLANE_TYPE_OVERLAY }, + { .base = 0x50, .phy = &rk3288_win23_data, + .type = DRM_PLANE_TYPE_CURSOR }, +}; + +static const int rk3288_vop_intrs[] = { + DSP_HOLD_VALID_INTR, + FS_INTR, + LINE_FLAG_INTR, + BUS_ERROR_INTR, +}; + +static const struct vop_intr rk3288_vop_intr = { + .intrs = rk3288_vop_intrs, + .nintrs = ARRAY_SIZE(rk3288_vop_intrs), + .status = VOP_REG(RK3288_INTR_CTRL0, 0xf, 0), + .enable = VOP_REG(RK3288_INTR_CTRL0, 0xf, 4), + .clear = VOP_REG(RK3288_INTR_CTRL0, 0xf, 8), +}; + +static const struct vop_data rk3288_vop = { + .init_table = rk3288_init_reg_table, + .table_size = ARRAY_SIZE(rk3288_init_reg_table), + .intr = &rk3288_vop_intr, + .ctrl = &rk3288_ctrl_data, + .win = rk3288_vop_win_data, + .win_size = ARRAY_SIZE(rk3288_vop_win_data), +}; + +static const struct vop_scl_regs rk3066_win_scl = { + .scale_yrgb_x = VOP_REG(RK3036_WIN0_SCL_FACTOR_YRGB, 0xffff, 0x0), + .scale_yrgb_y = VOP_REG(RK3036_WIN0_SCL_FACTOR_YRGB, 0xffff, 16), + .scale_cbcr_x = VOP_REG(RK3036_WIN0_SCL_FACTOR_CBR, 0xffff, 0x0), + .scale_cbcr_y = VOP_REG(RK3036_WIN0_SCL_FACTOR_CBR, 0xffff, 16), +}; + +static const struct vop_win_phy rk3036_win0_data = { + .scl = &rk3066_win_scl, + .data_formats = formats_win_full, + .nformats = ARRAY_SIZE(formats_win_full), + .enable = VOP_REG(RK3036_SYS_CTRL, 0x1, 0), + .format = VOP_REG(RK3036_SYS_CTRL, 0x7, 3), + .rb_swap = VOP_REG(RK3036_SYS_CTRL, 0x1, 15), + .act_info = VOP_REG(RK3036_WIN0_ACT_INFO, 0x1fff1fff, 0), + .dsp_info = VOP_REG(RK3036_WIN0_DSP_INFO, 0x0fff0fff, 0), + .dsp_st = VOP_REG(RK3036_WIN0_DSP_ST, 0x1fff1fff, 0), + .yrgb_mst = VOP_REG(RK3036_WIN0_YRGB_MST, 0xffffffff, 0), + .uv_mst = VOP_REG(RK3036_WIN0_CBR_MST, 0xffffffff, 0), + .yrgb_vir = VOP_REG(RK3036_WIN0_VIR, 0xffff, 0), +}; + +static const struct vop_win_phy rk3036_win1_data = { + .data_formats = formats_win_lite, + .nformats = ARRAY_SIZE(formats_win_lite), + .enable = VOP_REG(RK3036_SYS_CTRL, 0x1, 1), + .format = VOP_REG(RK3036_SYS_CTRL, 0x7, 6), + .rb_swap = VOP_REG(RK3036_SYS_CTRL, 0x1, 19), + .act_info = VOP_REG(RK3036_WIN1_ACT_INFO, 0x1fff1fff, 0), + .dsp_info = VOP_REG(RK3036_WIN1_DSP_INFO, 0x0fff0fff, 0), + .dsp_st = VOP_REG(RK3036_WIN1_DSP_ST, 0x1fff1fff, 0), + .yrgb_mst = VOP_REG(RK3036_WIN1_MST, 0xffffffff, 0), + .yrgb_vir = VOP_REG(RK3036_WIN1_VIR, 0xffff, 0), +}; + +static const struct vop_win_data rk3036_vop_win_data[] = { + { .base = 0x00, .phy = &rk3036_win0_data, + .type = DRM_PLANE_TYPE_PRIMARY }, + { .base = 0x00, .phy = &rk3036_win1_data, + .type = DRM_PLANE_TYPE_CURSOR }, +}; + +static const int rk3036_vop_intrs[] = { + DSP_HOLD_VALID_INTR, + FS_INTR, + LINE_FLAG_INTR, + BUS_ERROR_INTR, +}; + +static const struct vop_intr rk3036_intr = { + .intrs = rk3036_vop_intrs, + .nintrs = ARRAY_SIZE(rk3036_vop_intrs), + .status = VOP_REG(RK3036_INT_STATUS, 0xf, 0), + .enable = VOP_REG(RK3036_INT_STATUS, 0xf, 4), + .clear = VOP_REG(RK3036_INT_STATUS, 0xf, 8), +}; + +static const struct vop_ctrl rk3036_ctrl_data = { + .standby = VOP_REG(RK3036_SYS_CTRL, 0x1, 30), + .out_mode = VOP_REG(RK3036_DSP_CTRL0, 0xf, 0), + .pin_pol = VOP_REG(RK3036_DSP_CTRL0, 0xf, 4), + .htotal_pw = VOP_REG(RK3036_DSP_HTOTAL_HS_END, 0x1fff1fff, 0), + .hact_st_end = VOP_REG(RK3036_DSP_HACT_ST_END, 0x1fff1fff, 0), + .vtotal_pw = VOP_REG(RK3036_DSP_VTOTAL_VS_END, 0x1fff1fff, 0), + .vact_st_end = VOP_REG(RK3036_DSP_VACT_ST_END, 0x1fff1fff, 0), + .cfg_done = VOP_REG(RK3036_REG_CFG_DONE, 0x1, 0), +}; + +static const struct vop_reg_data rk3036_vop_init_reg_table[] = { + {RK3036_DSP_CTRL1, 0x00000000}, +}; + +static const struct vop_data rk3036_vop = { + .init_table = rk3036_vop_init_reg_table, + .table_size = ARRAY_SIZE(rk3036_vop_init_reg_table), + .ctrl = &rk3036_ctrl_data, + .intr = &rk3036_intr, + .win = rk3036_vop_win_data, + .win_size = ARRAY_SIZE(rk3036_vop_win_data), +}; + +static const struct of_device_id vop_driver_dt_match[] = { + { .compatible = "rockchip,rk3288-vop", + .data = &rk3288_vop }, + { .compatible = "rockchip,rk3036-vop", + .data = &rk3036_vop }, + {}, +}; +MODULE_DEVICE_TABLE(of, vop_driver_dt_match); + +static int vop_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + if (!dev->of_node) { + dev_err(dev, "can't find vop devices\n"); + return -ENODEV; + } + + return component_add(dev, &vop_component_ops); +} + +static int vop_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vop_component_ops); + + return 0; +} + +struct platform_driver vop_platform_driver = { + .probe = vop_probe, + .remove = vop_remove, + .driver = { + .name = "rockchip-vop", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(vop_driver_dt_match), + }, +}; + +module_platform_driver(vop_platform_driver); + +MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>"); +MODULE_DESCRIPTION("ROCKCHIP VOP Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.h b/drivers/gpu/drm/rockchip/rockchip_vop_reg.h new file mode 100644 index 000000000..d4b46cba2 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:Mark Yao <mark.yao@rock-chips.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _ROCKCHIP_VOP_REG_H +#define _ROCKCHIP_VOP_REG_H + +/* rk3288 register definition */ +#define RK3288_REG_CFG_DONE 0x0000 +#define RK3288_VERSION_INFO 0x0004 +#define RK3288_SYS_CTRL 0x0008 +#define RK3288_SYS_CTRL1 0x000c +#define RK3288_DSP_CTRL0 0x0010 +#define RK3288_DSP_CTRL1 0x0014 +#define RK3288_DSP_BG 0x0018 +#define RK3288_MCU_CTRL 0x001c +#define RK3288_INTR_CTRL0 0x0020 +#define RK3288_INTR_CTRL1 0x0024 +#define RK3288_WIN0_CTRL0 0x0030 +#define RK3288_WIN0_CTRL1 0x0034 +#define RK3288_WIN0_COLOR_KEY 0x0038 +#define RK3288_WIN0_VIR 0x003c +#define RK3288_WIN0_YRGB_MST 0x0040 +#define RK3288_WIN0_CBR_MST 0x0044 +#define RK3288_WIN0_ACT_INFO 0x0048 +#define RK3288_WIN0_DSP_INFO 0x004c +#define RK3288_WIN0_DSP_ST 0x0050 +#define RK3288_WIN0_SCL_FACTOR_YRGB 0x0054 +#define RK3288_WIN0_SCL_FACTOR_CBR 0x0058 +#define RK3288_WIN0_SCL_OFFSET 0x005c +#define RK3288_WIN0_SRC_ALPHA_CTRL 0x0060 +#define RK3288_WIN0_DST_ALPHA_CTRL 0x0064 +#define RK3288_WIN0_FADING_CTRL 0x0068 + +/* win1 register */ +#define RK3288_WIN1_CTRL0 0x0070 +#define RK3288_WIN1_CTRL1 0x0074 +#define RK3288_WIN1_COLOR_KEY 0x0078 +#define RK3288_WIN1_VIR 0x007c +#define RK3288_WIN1_YRGB_MST 0x0080 +#define RK3288_WIN1_CBR_MST 0x0084 +#define RK3288_WIN1_ACT_INFO 0x0088 +#define RK3288_WIN1_DSP_INFO 0x008c +#define RK3288_WIN1_DSP_ST 0x0090 +#define RK3288_WIN1_SCL_FACTOR_YRGB 0x0094 +#define RK3288_WIN1_SCL_FACTOR_CBR 0x0098 +#define RK3288_WIN1_SCL_OFFSET 0x009c +#define RK3288_WIN1_SRC_ALPHA_CTRL 0x00a0 +#define RK3288_WIN1_DST_ALPHA_CTRL 0x00a4 +#define RK3288_WIN1_FADING_CTRL 0x00a8 +/* win2 register */ +#define RK3288_WIN2_CTRL0 0x00b0 +#define RK3288_WIN2_CTRL1 0x00b4 +#define RK3288_WIN2_VIR0_1 0x00b8 +#define RK3288_WIN2_VIR2_3 0x00bc +#define RK3288_WIN2_MST0 0x00c0 +#define RK3288_WIN2_DSP_INFO0 0x00c4 +#define RK3288_WIN2_DSP_ST0 0x00c8 +#define RK3288_WIN2_COLOR_KEY 0x00cc +#define RK3288_WIN2_MST1 0x00d0 +#define RK3288_WIN2_DSP_INFO1 0x00d4 +#define RK3288_WIN2_DSP_ST1 0x00d8 +#define RK3288_WIN2_SRC_ALPHA_CTRL 0x00dc +#define RK3288_WIN2_MST2 0x00e0 +#define RK3288_WIN2_DSP_INFO2 0x00e4 +#define RK3288_WIN2_DSP_ST2 0x00e8 +#define RK3288_WIN2_DST_ALPHA_CTRL 0x00ec +#define RK3288_WIN2_MST3 0x00f0 +#define RK3288_WIN2_DSP_INFO3 0x00f4 +#define RK3288_WIN2_DSP_ST3 0x00f8 +#define RK3288_WIN2_FADING_CTRL 0x00fc +/* win3 register */ +#define RK3288_WIN3_CTRL0 0x0100 +#define RK3288_WIN3_CTRL1 0x0104 +#define RK3288_WIN3_VIR0_1 0x0108 +#define RK3288_WIN3_VIR2_3 0x010c +#define RK3288_WIN3_MST0 0x0110 +#define RK3288_WIN3_DSP_INFO0 0x0114 +#define RK3288_WIN3_DSP_ST0 0x0118 +#define RK3288_WIN3_COLOR_KEY 0x011c +#define RK3288_WIN3_MST1 0x0120 +#define RK3288_WIN3_DSP_INFO1 0x0124 +#define RK3288_WIN3_DSP_ST1 0x0128 +#define RK3288_WIN3_SRC_ALPHA_CTRL 0x012c +#define RK3288_WIN3_MST2 0x0130 +#define RK3288_WIN3_DSP_INFO2 0x0134 +#define RK3288_WIN3_DSP_ST2 0x0138 +#define RK3288_WIN3_DST_ALPHA_CTRL 0x013c +#define RK3288_WIN3_MST3 0x0140 +#define RK3288_WIN3_DSP_INFO3 0x0144 +#define RK3288_WIN3_DSP_ST3 0x0148 +#define RK3288_WIN3_FADING_CTRL 0x014c +/* hwc register */ +#define RK3288_HWC_CTRL0 0x0150 +#define RK3288_HWC_CTRL1 0x0154 +#define RK3288_HWC_MST 0x0158 +#define RK3288_HWC_DSP_ST 0x015c +#define RK3288_HWC_SRC_ALPHA_CTRL 0x0160 +#define RK3288_HWC_DST_ALPHA_CTRL 0x0164 +#define RK3288_HWC_FADING_CTRL 0x0168 +/* post process register */ +#define RK3288_POST_DSP_HACT_INFO 0x0170 +#define RK3288_POST_DSP_VACT_INFO 0x0174 +#define RK3288_POST_SCL_FACTOR_YRGB 0x0178 +#define RK3288_POST_SCL_CTRL 0x0180 +#define RK3288_POST_DSP_VACT_INFO_F1 0x0184 +#define RK3288_DSP_HTOTAL_HS_END 0x0188 +#define RK3288_DSP_HACT_ST_END 0x018c +#define RK3288_DSP_VTOTAL_VS_END 0x0190 +#define RK3288_DSP_VACT_ST_END 0x0194 +#define RK3288_DSP_VS_ST_END_F1 0x0198 +#define RK3288_DSP_VACT_ST_END_F1 0x019c +/* register definition end */ + +/* rk3036 register definition */ +#define RK3036_SYS_CTRL 0x00 +#define RK3036_DSP_CTRL0 0x04 +#define RK3036_DSP_CTRL1 0x08 +#define RK3036_INT_STATUS 0x10 +#define RK3036_ALPHA_CTRL 0x14 +#define RK3036_WIN0_COLOR_KEY 0x18 +#define RK3036_WIN1_COLOR_KEY 0x1c +#define RK3036_WIN0_YRGB_MST 0x20 +#define RK3036_WIN0_CBR_MST 0x24 +#define RK3036_WIN1_VIR 0x28 +#define RK3036_AXI_BUS_CTRL 0x2c +#define RK3036_WIN0_VIR 0x30 +#define RK3036_WIN0_ACT_INFO 0x34 +#define RK3036_WIN0_DSP_INFO 0x38 +#define RK3036_WIN0_DSP_ST 0x3c +#define RK3036_WIN0_SCL_FACTOR_YRGB 0x40 +#define RK3036_WIN0_SCL_FACTOR_CBR 0x44 +#define RK3036_WIN0_SCL_OFFSET 0x48 +#define RK3036_HWC_MST 0x58 +#define RK3036_HWC_DSP_ST 0x5c +#define RK3036_DSP_HTOTAL_HS_END 0x6c +#define RK3036_DSP_HACT_ST_END 0x70 +#define RK3036_DSP_VTOTAL_VS_END 0x74 +#define RK3036_DSP_VACT_ST_END 0x78 +#define RK3036_DSP_VS_ST_END_F1 0x7c +#define RK3036_DSP_VACT_ST_END_F1 0x80 +#define RK3036_GATHER_TRANSFER 0x84 +#define RK3036_VERSION_INFO 0x94 +#define RK3036_REG_CFG_DONE 0x90 +#define RK3036_WIN1_MST 0xa0 +#define RK3036_WIN1_ACT_INFO 0xb4 +#define RK3036_WIN1_DSP_INFO 0xb8 +#define RK3036_WIN1_DSP_ST 0xbc +#define RK3036_WIN1_SCL_FACTOR_YRGB 0xc0 +#define RK3036_WIN1_SCL_OFFSET 0xc8 +#define RK3036_BCSH_CTRL 0xd0 +#define RK3036_BCSH_COLOR_BAR 0xd4 +#define RK3036_BCSH_BCS 0xd8 +#define RK3036_BCSH_H 0xdc +#define RK3036_WIN1_LUT_ADDR 0x400 +#define RK3036_HWC_LUT_ADDR 0x800 +/* rk3036 register definition end */ + +#endif /* _ROCKCHIP_VOP_REG_H */ |