From e2fff57ea5561d1fef1ca923bffe34985ebf3e8c Mon Sep 17 00:00:00 2001 From: Lionel VITTE Date: Fri, 8 Nov 2019 16:52:39 +0100 Subject: [PATCH 07/31] ARM stm32mp1 r3 DRM --- drivers/gpu/drm/bridge/Kconfig | 1 + drivers/gpu/drm/bridge/sii902x.c | 840 +++++++++++++++++++++-- drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 102 ++- drivers/gpu/drm/drm_gem.c | 6 - drivers/gpu/drm/drm_modes.c | 19 +- drivers/gpu/drm/panel/panel-orisetech-otm8009a.c | 78 ++- drivers/gpu/drm/panel/panel-raydium-rm68200.c | 20 +- drivers/gpu/drm/stm/drv.c | 76 +- drivers/gpu/drm/stm/dw_mipi_dsi-stm.c | 114 ++- drivers/gpu/drm/stm/ltdc.c | 282 ++++++-- drivers/gpu/drm/stm/ltdc.h | 7 + include/drm/bridge/dw_mipi_dsi.h | 1 + include/uapi/drm/drm_mode.h | 6 + 13 files changed, 1352 insertions(+), 200 deletions(-) diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 7a3e5a8..7695fdf 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -96,6 +96,7 @@ config DRM_SII902X depends on OF select DRM_KMS_HELPER select REGMAP_I2C + select SND_SOC_HDMI_CODEC if SND_SOC ---help--- Silicon Image sii902x bridge chip driver. diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c index 0cc6dbb..b41dd44 100644 --- a/drivers/gpu/drm/bridge/sii902x.c +++ b/drivers/gpu/drm/bridge/sii902x.c @@ -1,4 +1,6 @@ /* + * Copyright (C) 2018 Renesas Electronics + * * Copyright (C) 2016 Atmel * Bo Shen * @@ -20,16 +22,22 @@ * GNU General Public License for more details. */ +#include #include +#include #include #include +#include #include +#include #include #include #include #include +#include + #define SII902X_TPI_VIDEO_DATA 0x0 #define SII902X_TPI_PIXEL_REPETITION 0x8 @@ -71,23 +79,229 @@ #define SII902X_AVI_POWER_STATE_MSK GENMASK(1, 0) #define SII902X_AVI_POWER_STATE_D(l) ((l) & SII902X_AVI_POWER_STATE_MSK) +#define SII902X_I2S_MAP 0x1f +#define SII902X_I2S_MAP_SWITCH BIT(7) +#define SII902X_I2S_MAP_SD_MSK GENMASK(5, 4) +#define SII902X_I2S_MAP_SD(v) \ + FIELD_PREP(SII902X_I2S_MAP_SD_MSK, v) +#define SII902X_I2S_MAP_DS BIT(3) +#define SII902X_I2S_MAP_SWAP BIT(2) +#define SII902X_I2S_MAP_FIFO_MSK GENMASK(1, 0) +#define SII902X_I2S_MAP_FIFO(v) \ + FIELD_PREP(SII902X_I2S_MAP_FIFO_MSK, v) + +#define SII902X_I2S_CONF 0x20 +#define SII902X_I2S_CONF_SCK_STROBING BIT(7) +#define SII902X_I2S_CONF_MCLK_RATIO_MSK GENMASK(6, 4) +#define SII902X_I2S_CONF_WS_STROBING BIT(3) +#define SII902X_I2S_CONF_JUSTIFY BIT(2) +#define SII902X_I2S_CONF_LSB_DIR BIT(1) +#define SII902X_I2S_CONF_NO_OFFSET BIT(0) + +#define SII902X_I2S_CS0 0x21 + +#define SII902X_I2S_CS1 0x22 + +#define SII902X_I2S_CS2 0x23 +#define SII902X_I2S_CS2_SRC_MSK GENMASK(3, 0) +#define SII902X_I2S_CS2_CHAN_MSK GENMASK(7, 4) + +#define SII902X_I2S_CS3 0x24 +#define SII902X_I2S_CS3_FS_MSK GENMASK(3, 0) +#define SII902X_I2S_CS3_FS(v) \ + FIELD_PREP(SII902X_I2S_CS3_FS_MSK, v) +#define SII902X_I2S_CS3_ACC_MSK GENMASK(7, 4) + +#define SII902X_I2S_CS4 0x25 +#define SII902X_I2S_CS4_WL_MSK GENMASK(3, 0) +#define SII902X_I2S_CS4_WL(v) \ + FIELD_PREP(SII902X_I2S_CS4_WL_MSK, v) + +#define SII902X_AIF 0x26 +#define SII902X_AIF_FMT_MSK GENMASK(7, 6) +#define SII902X_AIF_FMT(v) \ + FIELD_PREP(SII902X_AIF_FMT_MSK, v) +#define SII902X_AIF_LAYOUT BIT(5) +#define SII902X_AIF_MUTE BIT(4) +#define SII902X_AIF_CODING_MSK GENMASK(3, 0) +#define SII902X_AIF_CODING(v) \ + FIELD_PREP(SII902X_AIF_CODING_MSK, v) + +#define SII902X_I2S_AUD_FMT 0x27 +#define SII902X_I2S_AUD_FMT_SZ_MSK GENMASK(7, 6) +#define SII902X_I2S_AUD_FMT_SZ(v) \ + FIELD_PREP(SII902X_I2S_AUD_FMT_SZ_MSK, v) +#define SII902X_I2S_AUD_FMT_FS_MSK GENMASK(5, 3) +#define SII902X_I2S_AUD_FMT_FS(v) \ + FIELD_PREP(SII902X_I2S_AUD_FMT_FS_MSK, v) +#define SII902X_I2S_AUD_FMT_FS_HBR BIT(2) + #define SII902X_INT_ENABLE 0x3c #define SII902X_INT_STATUS 0x3d #define SII902X_HOTPLUG_EVENT BIT(0) #define SII902X_PLUGGED_STATUS BIT(2) +#define SII902X_PLL_R1 0x82 +#define SII902X_PLL_R1_TCLKSEL_MSK GENMASK(6, 5) +#define SII902X_PLL_R1_TCLKSEL(v) \ + FIELD_PREP(SII902X_PLL_R1_TCLKSEL_MSK, v) +#define SII902X_PLL_R2 0x83 +#define SII902X_PLL_R2_CLKMUTLCTL_MSK GENMASK(5, 4) +#define SII902X_PLL_R2_CLKMUTLCTL(v) \ + FIELD_PREP(SII902X_PLL_R2_CLKMUTLCTL_MSK, v) +#define SII902X_PLL_R3 0x84 +#define SII902X_PLL_R3_ACLKCNT_MSK GENMASK(5, 4) +#define SII902X_PLL_R3_ACLKCNT(v) \ + FIELD_PREP(SII902X_PLL_R3_ACLKCNT_MSK, v) +#define SII902X_PLL_R3_HLFCLKEN BIT(1) + +#define SII902X_INDEXED_REG_PAGE 0xbc +#define SII902X_INDEXED_REG_IDX 0xbd +#define SII902X_INDEXED_REG_ACCESS 0xbe + +#define SII902X_OTHER_IF 0xbf +#define SII902X_OTHER_IF_SEL_MSK GENMASK(2, 0) +#define SII902X_OTHER_IF_SEL(v) \ + FIELD_PREP(SII902X_OTHER_IF_SEL_MSK, v) +#define SII902X_OTHER_IF_REPEAT BIT(6) +#define SII902X_OTHER_IF_ENABLE BIT(7) + #define SII902X_REG_TPI_RQB 0xc7 #define SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS 500 +#define SII902X_IF_AUDIO 2 + +/* CEC device */ +#define SII902X_CEC_I2C_ADDR 0x30 + +#define SII902X_CEC_SETUP 0x8e + +enum sii902x_i2s_map_sd { + SII902X_I2S_MAP_SD0, + SII902X_I2S_MAP_SD1, + SII902X_I2S_MAP_SD2, + SII902X_I2S_MAP_SD3, +}; + +enum sii902x_i2s_map_fifo { + SII902X_I2S_MAP_FIFO0, + SII902X_I2S_MAP_FIFO1, + SII902X_I2S_MAP_FIFO2, + SII902X_I2S_MAP_FIFO3, +}; + +enum sii902x_aif_format { + SII902X_AIF_FORMAT_SPDIF = 1, + SII902X_AIF_FORMAT_I2S, + SII902X_AIF_FORMAT_DSD, +}; + +enum sii902x_aif_coding { + SII902X_AIF_CODING_STREAM_HEADER, + SII902X_AIF_CODING_PCM, + SII902X_AIF_CODING_AC3, + SII902X_AIF_CODING_MPEG1, + SII902X_AIF_CODING_MP3, + SII902X_AIF_CODING_MPEG2, + SII902X_AIF_CODING_AAC, + SII902X_AIF_CODING_DTS, + SII902X_AIF_CODING_ATRAC, +}; + +enum sii902x_sample_rate { + SII902X_SAMPLE_RATE_32000 = 1, + SII902X_SAMPLE_RATE_44100, + SII902X_SAMPLE_RATE_48000, + SII902X_SAMPLE_RATE_88200, + SII902X_SAMPLE_RATE_96000, + SII902X_SAMPLE_RATE_176400, + SII902X_SAMPLE_RATE_192000, +}; + +enum sii902x_sample_width { + SII902X_SAMPLE_RATE_SIZE_16 = 1, + SII902X_SAMPLE_RATE_SIZE_20, + SII902X_SAMPLE_RATE_SIZE_24, +}; + +struct sii902x_audio_params { + unsigned int aes_size; + unsigned int aes_rate; +}; + struct sii902x { struct i2c_client *i2c; struct regmap *regmap; struct drm_bridge bridge; struct drm_connector connector; struct gpio_desc *reset_gpio; + struct i2c_mux_core *i2cmux; + struct regulator_bulk_data supplies[2]; + struct platform_device *audio_pdev; + struct sii902x_audio_params audio; + struct edid *edid; }; +static int sii902x_read_unlocked(struct i2c_client *i2c, u8 reg, u8 *val) +{ + struct i2c_msg xfer[] = { + { + .addr = i2c->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, { + .addr = i2c->addr, + .flags = I2C_M_RD, + .len = 1, + .buf = val, + } + }; + unsigned char xfers = ARRAY_SIZE(xfer); + int ret, retries = 5; + + do { + ret = __i2c_transfer(i2c->adapter, xfer, xfers); + if (ret < 0) + return ret; + } while (ret != xfers && --retries); + return ret == xfers ? 0 : -1; +} + +static int sii902x_write_unlocked(struct i2c_client *i2c, u8 reg, u8 val) +{ + u8 data[2] = {reg, val}; + struct i2c_msg xfer = { + .addr = i2c->addr, + .flags = 0, + .len = sizeof(data), + .buf = data, + }; + int ret, retries = 5; + + do { + ret = __i2c_transfer(i2c->adapter, &xfer, 1); + if (ret < 0) + return ret; + } while (!ret && --retries); + return !ret ? -1 : 0; +} + +static int sii902x_update_bits_unlocked(struct i2c_client *i2c, u8 reg, u8 mask, + u8 val) +{ + int ret; + u8 status; + + ret = sii902x_read_unlocked(i2c, reg, &status); + if (ret) + return ret; + status &= ~mask; + status |= val & mask; + return sii902x_write_unlocked(i2c, reg, status); +} + static inline struct sii902x *bridge_to_sii902x(struct drm_bridge *bridge) { return container_of(bridge, struct sii902x, bridge); @@ -135,45 +349,18 @@ static const struct drm_connector_funcs sii902x_connector_funcs = { static int sii902x_get_modes(struct drm_connector *connector) { struct sii902x *sii902x = connector_to_sii902x(connector); - struct regmap *regmap = sii902x->regmap; u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; - struct device *dev = &sii902x->i2c->dev; - unsigned long timeout; - unsigned int retries; - unsigned int status; struct edid *edid; - int num = 0; - int ret; - - ret = regmap_update_bits(regmap, SII902X_SYS_CTRL_DATA, - SII902X_SYS_CTRL_DDC_BUS_REQ, - SII902X_SYS_CTRL_DDC_BUS_REQ); - if (ret) - return ret; - - timeout = jiffies + - msecs_to_jiffies(SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS); - do { - ret = regmap_read(regmap, SII902X_SYS_CTRL_DATA, &status); - if (ret) - return ret; - } while (!(status & SII902X_SYS_CTRL_DDC_BUS_GRTD) && - time_before(jiffies, timeout)); + bool hdmi_mode = false; + int num = 0, ret; - if (!(status & SII902X_SYS_CTRL_DDC_BUS_GRTD)) { - dev_err(dev, "failed to acquire the i2c bus\n"); - return -ETIMEDOUT; - } - - ret = regmap_write(regmap, SII902X_SYS_CTRL_DATA, status); - if (ret) - return ret; - - edid = drm_get_edid(connector, sii902x->i2c->adapter); + edid = drm_get_edid(connector, sii902x->i2cmux->adapter[0]); drm_connector_update_edid_property(connector, edid); if (edid) { num = drm_add_edid_modes(connector, edid); - kfree(edid); + hdmi_mode = drm_detect_hdmi_monitor(edid); + kfree(sii902x->edid); + sii902x->edid = edid; } ret = drm_display_info_set_bus_formats(&connector->display_info, @@ -181,41 +368,10 @@ static int sii902x_get_modes(struct drm_connector *connector) if (ret) return ret; - /* - * Sometimes the I2C bus can stall after failure to use the - * EDID channel. Retry a few times to see if things clear - * up, else continue anyway. - */ - retries = 5; - do { - ret = regmap_read(regmap, SII902X_SYS_CTRL_DATA, - &status); - retries--; - } while (ret && retries); - if (ret) - dev_err(dev, "failed to read status (%d)\n", ret); - - ret = regmap_update_bits(regmap, SII902X_SYS_CTRL_DATA, - SII902X_SYS_CTRL_DDC_BUS_REQ | - SII902X_SYS_CTRL_DDC_BUS_GRTD, 0); - if (ret) - return ret; - - timeout = jiffies + - msecs_to_jiffies(SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS); - do { - ret = regmap_read(regmap, SII902X_SYS_CTRL_DATA, &status); - if (ret) - return ret; - } while (status & (SII902X_SYS_CTRL_DDC_BUS_REQ | - SII902X_SYS_CTRL_DDC_BUS_GRTD) && - time_before(jiffies, timeout)); - - if (status & (SII902X_SYS_CTRL_DDC_BUS_REQ | - SII902X_SYS_CTRL_DDC_BUS_GRTD)) { - dev_err(dev, "failed to release the i2c bus\n"); - return -ETIMEDOUT; - } + if (hdmi_mode) + regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_OUTPUT_MODE, + SII902X_SYS_CTRL_OUTPUT_HDMI); return num; } @@ -245,12 +401,22 @@ static void sii902x_bridge_disable(struct drm_bridge *bridge) static void sii902x_bridge_enable(struct drm_bridge *bridge) { struct sii902x *sii902x = bridge_to_sii902x(bridge); + bool hdmi_mode; regmap_update_bits(sii902x->regmap, SII902X_PWR_STATE_CTRL, SII902X_AVI_POWER_STATE_MSK, SII902X_AVI_POWER_STATE_D(0)); regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA, SII902X_SYS_CTRL_PWR_DWN, 0); + + if(sii902x->edid) { + hdmi_mode = drm_detect_hdmi_monitor(sii902x->edid); + if (hdmi_mode) + regmap_update_bits(sii902x->regmap, + SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_OUTPUT_MODE, + SII902X_SYS_CTRL_OUTPUT_HDMI); + } } static void sii902x_bridge_mode_set(struct drm_bridge *bridge, @@ -330,6 +496,267 @@ static int sii902x_bridge_attach(struct drm_bridge *bridge) return 0; } +static int sii902x_audio_infoframe_config(struct sii902x *sii902x, + struct hdmi_codec_params *params) +{ + u8 buf[HDMI_INFOFRAME_SIZE(AUDIO)]; + int ret; + + ret = hdmi_audio_infoframe_init(¶ms->cea); + if (ret) { + DRM_ERROR("Failed to init audio infoframe\n"); + return ret; + } + + ret = hdmi_audio_infoframe_pack(¶ms->cea, buf, sizeof(buf)); + if (ret < 0) { + DRM_ERROR("failed to pack audio infoframe: %d\n", ret); + return ret; + } + + regmap_update_bits(sii902x->regmap, + SII902X_OTHER_IF, + SII902X_OTHER_IF_SEL_MSK | + SII902X_OTHER_IF_REPEAT | + SII902X_OTHER_IF_ENABLE, + SII902X_OTHER_IF_SEL(SII902X_IF_AUDIO) | + SII902X_OTHER_IF_REPEAT | + SII902X_OTHER_IF_ENABLE); + + return regmap_bulk_write(sii902x->regmap, SII902X_OTHER_IF + 1, buf, + HDMI_INFOFRAME_SIZE(AUDIO) + + HDMI_INFOFRAME_HEADER_SIZE - 1); +} + +static int sii902x_audio_iec60958_config(struct sii902x *sii902x) +{ + /* Bytes 0,1,2 are let to default setting. Configure bytes 3&4 */ + regmap_update_bits(sii902x->regmap, + SII902X_I2S_CS3, + SII902X_I2S_CS3_FS_MSK, + SII902X_I2S_CS3_FS(sii902x->audio.aes_rate)); + + return regmap_update_bits(sii902x->regmap, + SII902X_I2S_CS4, + SII902X_I2S_CS4_WL_MSK, + SII902X_I2S_CS4_WL(sii902x->audio.aes_size)); +} + +static int sii902x_i2s_configure(struct sii902x *sii902x, + struct hdmi_codec_params *params, + struct hdmi_codec_daifmt *fmt) +{ + unsigned int rate, size, val, mask; + + /* Configure audio interface */ + regmap_update_bits(sii902x->regmap, SII902X_AIF, + SII902X_AIF_FMT_MSK | + SII902X_AIF_LAYOUT | + SII902X_AIF_MUTE | + SII902X_AIF_CODING_MSK, + SII902X_AIF_FMT(SII902X_AIF_FORMAT_I2S) | + SII902X_AIF_MUTE | + SII902X_AIF_CODING(SII902X_AIF_CODING_PCM)); + + switch (fmt->fmt) { + case HDMI_I2S: + val = SII902X_I2S_CONF_SCK_STROBING; + break; + case HDMI_LEFT_J: + val = SII902X_I2S_CONF_SCK_STROBING | + SII902X_I2S_CONF_WS_STROBING | + SII902X_I2S_CONF_NO_OFFSET; + break; + case HDMI_RIGHT_J: + val = SII902X_I2S_CONF_SCK_STROBING | + SII902X_I2S_CONF_WS_STROBING | + SII902X_I2S_CONF_NO_OFFSET | + SII902X_I2S_CONF_JUSTIFY; + break; + default: + DRM_ERROR("Unknown protocol %#x\n", fmt->fmt); + return -EINVAL; + } + mask = SII902X_I2S_CONF_NO_OFFSET | SII902X_I2S_CONF_WS_STROBING | + SII902X_I2S_CONF_JUSTIFY | SII902X_I2S_CONF_LSB_DIR | + SII902X_I2S_CONF_SCK_STROBING; + + if (fmt->frame_clk_inv) + val ^= SII902X_I2S_CONF_WS_STROBING; + + if (fmt->bit_clk_inv) + val ^= SII902X_I2S_CONF_SCK_STROBING; + + /* Configure i2s interface */ + regmap_update_bits(sii902x->regmap, + SII902X_I2S_CONF, mask, val); + + /* + * Configure i2s interface mapping + * Assume that only SD0 is used and connected to FIFO0 + */ + regmap_update_bits(sii902x->regmap, + SII902X_I2S_MAP, + SII902X_I2S_MAP_SWITCH | + SII902X_I2S_MAP_SD_MSK | SII902X_I2S_MAP_FIFO_MSK, + SII902X_I2S_MAP_SWITCH | + SII902X_I2S_MAP_SD0 | SII902X_I2S_MAP_FIFO0); + + switch (params->sample_rate) { + case 32000: + rate = SII902X_SAMPLE_RATE_32000; + sii902x->audio.aes_rate = IEC958_AES3_CON_FS_32000; + break; + case 44100: + rate = SII902X_SAMPLE_RATE_44100; + sii902x->audio.aes_rate = IEC958_AES3_CON_FS_44100; + break; + case 48000: + rate = SII902X_SAMPLE_RATE_48000; + sii902x->audio.aes_rate = IEC958_AES3_CON_FS_48000; + break; + case 88200: + rate = SII902X_SAMPLE_RATE_88200; + sii902x->audio.aes_rate = IEC958_AES3_CON_FS_88200; + break; + case 96000: + rate = SII902X_SAMPLE_RATE_96000; + sii902x->audio.aes_rate = IEC958_AES3_CON_FS_96000; + break; + case 176400: + rate = SII902X_SAMPLE_RATE_176400; + sii902x->audio.aes_rate = IEC958_AES3_CON_FS_176400; + break; + case 192000: + rate = SII902X_SAMPLE_RATE_192000; + sii902x->audio.aes_rate = IEC958_AES3_CON_FS_192000; + break; + default: + DRM_ERROR("Unknown sampling rate %d\n", params->sample_rate); + return -EINVAL; + } + + switch (params->sample_width) { + case 16: + size = SII902X_SAMPLE_RATE_SIZE_16; + sii902x->audio.aes_size = IEC958_AES4_CON_WORDLEN_20_16; + break; + case 20: + size = SII902X_SAMPLE_RATE_SIZE_20; + sii902x->audio.aes_size = IEC958_AES4_CON_WORDLEN_24_20; + break; + case 24: + case 32: + size = SII902X_SAMPLE_RATE_SIZE_24; + sii902x->audio.aes_size = IEC958_AES4_CON_WORDLEN_24_20 | + IEC958_AES4_CON_MAX_WORDLEN_24; + break; + default: + DRM_ERROR("Unknown sample width %d\n", params->sample_width); + return -EINVAL; + } + + /* Configure i2s interface rate and input/output word length */ + regmap_update_bits(sii902x->regmap, + SII902X_INDEXED_REG_PAGE, 0xff, 0x2); + regmap_update_bits(sii902x->regmap, + SII902X_INDEXED_REG_IDX, 0xff, 0x24); + regmap_update_bits(sii902x->regmap, + SII902X_INDEXED_REG_ACCESS, 0x0f, + sii902x->audio.aes_size); + + return regmap_update_bits(sii902x->regmap, + SII902X_I2S_AUD_FMT, + SII902X_I2S_AUD_FMT_FS_MSK | + SII902X_I2S_AUD_FMT_SZ_MSK, + SII902X_I2S_AUD_FMT_FS(rate) | + SII902X_I2S_AUD_FMT_SZ(size)); +} + +static int sii902x_audio_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *params) +{ + struct sii902x *sii902x = dev_get_drvdata(dev); + int ret; + + if (fmt->bit_clk_master || fmt->frame_clk_master) { + DRM_ERROR("Master mode not supported\n"); + return -EINVAL; + } + + if (fmt->fmt == HDMI_I2S || fmt->fmt == HDMI_RIGHT_J || + fmt->fmt == HDMI_LEFT_J) { + /* Configure i2s interface */ + ret = sii902x_i2s_configure(sii902x, params, fmt); + if (ret) + return ret; + + /* Configure iec958 channel status */ + ret = sii902x_audio_iec60958_config(sii902x); + if (ret) + return ret; + } else { + DRM_ERROR("Unsupported format 0x%x\n", fmt->fmt); + return -EINVAL; + } + + /* Configure audio infoframes */ + return sii902x_audio_infoframe_config(sii902x, params); +} + +static int sii902x_audio_digital_mute(struct device *dev, + void *data, bool enable) +{ + struct sii902x *sii902x = dev_get_drvdata(dev); + int ret; + + DRM_DEBUG("%s audio\n", enable ? "mute" : "unmute"); + + if (enable) + ret = regmap_update_bits(sii902x->regmap, SII902X_AIF, + SII902X_AIF_MUTE, SII902X_AIF_MUTE); + else + ret = regmap_update_bits(sii902x->regmap, SII902X_AIF, + SII902X_AIF_MUTE, 0); + + return ret; +} + +static int sii902x_audio_get_eld(struct device *dev, void *data, + u8 *buf, size_t len) +{ + struct sii902x *sii902x = dev_get_drvdata(dev); + struct drm_connector *connector = &sii902x->connector; + + if (!sii902x->edid) + return -ENODEV; + + memcpy(buf, connector->eld, min(sizeof(connector->eld), len)); + + return 0; +} + +static void sii902x_audio_shutdown(struct device *dev, void *data) +{} + +static int sii902x_audio_get_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + struct of_endpoint of_ep; + int ret; + + ret = of_graph_parse_endpoint(endpoint, &of_ep); + if (ret < 0) + return ret; + + /* HDMI sound should be located at reg = <1> */ + if (of_ep.port == 1) + return 0; + + return -EINVAL; +} + static const struct drm_bridge_funcs sii902x_bridge_funcs = { .attach = sii902x_bridge_attach, .mode_set = sii902x_bridge_mode_set, @@ -349,10 +776,39 @@ static const struct regmap_access_table sii902x_volatile_table = { static const struct regmap_config sii902x_regmap_config = { .reg_bits = 8, .val_bits = 8, + /* map up to infoframe data registers. 0xbf-0xde */ + .max_register = 0xde, .volatile_table = &sii902x_volatile_table, .cache_type = REGCACHE_NONE, }; +static const struct hdmi_codec_ops sii902x_codec_ops = { + .hw_params = sii902x_audio_hw_params, + .audio_shutdown = sii902x_audio_shutdown, + .get_dai_id = sii902x_audio_get_dai_id, + .digital_mute = sii902x_audio_digital_mute, + .get_eld = sii902x_audio_get_eld, +}; + +static int sii902x_register_audio_driver(struct device *dev, + struct sii902x *sii902x) +{ + struct hdmi_codec_pdata codec_data = { + .ops = &sii902x_codec_ops, + .max_i2s_channels = 2, + .i2s = 1, + }; + + sii902x->audio_pdev = platform_device_register_data( + dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + + if (IS_ERR(sii902x->audio_pdev)) + return PTR_ERR(sii902x->audio_pdev); + + return 0; +} + static irqreturn_t sii902x_interrupt(int irq, void *data) { struct sii902x *sii902x = data; @@ -367,15 +823,134 @@ static irqreturn_t sii902x_interrupt(int irq, void *data) return IRQ_HANDLED; } +/* + * The purpose of sii902x_i2c_bypass_select is to enable the pass through + * mode of the HDMI transmitter. Do not use regmap from within this function, + * only use sii902x_*_unlocked functions to read/modify/write registers. + * We are holding the parent adapter lock here, keep this in mind before + * adding more i2c transactions. + */ +static int sii902x_i2c_bypass_select(struct i2c_mux_core *mux, u32 chan_id) +{ + struct sii902x *sii902x = i2c_mux_priv(mux); + struct device *dev = &sii902x->i2c->dev; + unsigned long timeout; + u8 status; + int ret; + + ret = sii902x_update_bits_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_DDC_BUS_REQ, + SII902X_SYS_CTRL_DDC_BUS_REQ); + + timeout = jiffies + + msecs_to_jiffies(SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS); + do { + ret = sii902x_read_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + &status); + if (ret) + return ret; + } while (!(status & SII902X_SYS_CTRL_DDC_BUS_GRTD) && + time_before(jiffies, timeout)); + + if (!(status & SII902X_SYS_CTRL_DDC_BUS_GRTD)) { + dev_err(dev, "Failed to acquire the i2c bus\n"); + return -ETIMEDOUT; + } + + ret = sii902x_write_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + status); + if (ret) + return ret; + return 0; +} + +/* + * The purpose of sii902x_i2c_bypass_deselect is to disable the pass through + * mode of the HDMI transmitter. Do not use regmap from within this function, + * only use sii902x_*_unlocked functions to read/modify/write registers. + * We are holding the parent adapter lock here, keep this in mind before + * adding more i2c transactions. + */ +static int sii902x_i2c_bypass_deselect(struct i2c_mux_core *mux, u32 chan_id) +{ + struct sii902x *sii902x = i2c_mux_priv(mux); + struct device *dev = &sii902x->i2c->dev; + unsigned long timeout; + unsigned int retries; + u8 status; + int ret; + + /* + * When the HDMI transmitter is in pass through mode, we need an + * (undocumented) additional delay between STOP and START conditions + * to guarantee the bus won't get stuck. + */ + udelay(30); + + /* + * Sometimes the I2C bus can stall after failure to use the + * EDID channel. Retry a few times to see if things clear + * up, else continue anyway. + */ + retries = 5; + do { + ret = sii902x_read_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + &status); + retries--; + } while (ret && retries); + if (ret) { + dev_err(dev, "failed to read status (%d)\n", ret); + return ret; + } + + ret = sii902x_update_bits_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + SII902X_SYS_CTRL_DDC_BUS_REQ | + SII902X_SYS_CTRL_DDC_BUS_GRTD, 0); + if (ret) + return ret; + + timeout = jiffies + + msecs_to_jiffies(SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS); + do { + ret = sii902x_read_unlocked(sii902x->i2c, SII902X_SYS_CTRL_DATA, + &status); + if (ret) + return ret; + } while (status & (SII902X_SYS_CTRL_DDC_BUS_REQ | + SII902X_SYS_CTRL_DDC_BUS_GRTD) && + time_before(jiffies, timeout)); + + if (status & (SII902X_SYS_CTRL_DDC_BUS_REQ | + SII902X_SYS_CTRL_DDC_BUS_GRTD)) { + dev_err(dev, "failed to release the i2c bus\n"); + return -ETIMEDOUT; + } + + return 0; +} + static int sii902x_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; unsigned int status = 0; struct sii902x *sii902x; + unsigned char data[2] = { SII902X_CEC_SETUP, 0}; + struct i2c_msg msg = { + .addr = SII902X_CEC_I2C_ADDR << 1, + .flags = 0, + .len = 2, + .buf = data, + }; u8 chipid[4]; int ret; + ret = i2c_check_functionality(client->adapter, I2C_FUNC_I2C); + if (!ret) { + dev_err(dev, "I2C adapter not suitable\n"); + return -EIO; + } + sii902x = devm_kzalloc(dev, sizeof(*sii902x), GFP_KERNEL); if (!sii902x) return -ENOMEM; @@ -393,39 +968,66 @@ static int sii902x_probe(struct i2c_client *client, return PTR_ERR(sii902x->reset_gpio); } + sii902x->supplies[0].supply = "iovcc"; + sii902x->supplies[1].supply = "cvcc12"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + if (ret) { + if(ret != -EPROBE_DEFER) + dev_err(dev, "regulator_bulk_get failed\n"); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + if (ret) { + dev_err(dev, "regulator_bulk_enable failed\n"); + return ret; + } + sii902x_reset(sii902x); ret = regmap_write(sii902x->regmap, SII902X_REG_TPI_RQB, 0x0); if (ret) - return ret; + goto err_disable_regulator; ret = regmap_bulk_read(sii902x->regmap, SII902X_REG_CHIPID(0), &chipid, 4); if (ret) { dev_err(dev, "regmap_read failed %d\n", ret); - return ret; + goto err_disable_regulator; } if (chipid[0] != 0xb0) { dev_err(dev, "Invalid chipid: %02x (expecting 0xb0)\n", chipid[0]); - return -EINVAL; + ret = -EINVAL; + goto err_disable_regulator; } + /* + * By default, CEC must be disabled to allow other CEC devives + * to bypass the bridge. + */ + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + dev_warn(&client->dev, "Failed to disable CEC device!\n"); + /* Clear all pending interrupts */ regmap_read(sii902x->regmap, SII902X_INT_STATUS, &status); regmap_write(sii902x->regmap, SII902X_INT_STATUS, status); if (client->irq > 0) { - regmap_write(sii902x->regmap, SII902X_INT_ENABLE, - SII902X_HOTPLUG_EVENT); + regmap_update_bits(sii902x->regmap, SII902X_INT_ENABLE, + SII902X_HOTPLUG_EVENT, + SII902X_HOTPLUG_EVENT); ret = devm_request_threaded_irq(dev, client->irq, NULL, sii902x_interrupt, IRQF_ONESHOT, dev_name(dev), sii902x); if (ret) - return ret; + goto err_disable_regulator; } sii902x->bridge.funcs = &sii902x_bridge_funcs; @@ -434,7 +1036,31 @@ static int sii902x_probe(struct i2c_client *client, i2c_set_clientdata(client, sii902x); + sii902x->i2cmux = i2c_mux_alloc(client->adapter, dev, + 1, 0, I2C_MUX_GATE, + sii902x_i2c_bypass_select, + sii902x_i2c_bypass_deselect); + if (!sii902x->i2cmux) { + dev_err(dev, "failed to allocate I2C mux\n"); + return -ENOMEM; + } + + sii902x->i2cmux->priv = sii902x; + ret = i2c_mux_add_adapter(sii902x->i2cmux, 0, 0, 0); + if (ret) { + dev_err(dev, "Couldn't add i2c mux adapter\n"); + return ret; + } + + sii902x_register_audio_driver(dev, sii902x); + return 0; + +err_disable_regulator: + regulator_bulk_disable(ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + + return ret; } static int sii902x_remove(struct i2c_client *client) @@ -442,11 +1068,76 @@ static int sii902x_remove(struct i2c_client *client) { struct sii902x *sii902x = i2c_get_clientdata(client); + if (sii902x->i2cmux) + i2c_mux_del_adapters(sii902x->i2cmux); + drm_bridge_remove(&sii902x->bridge); + regulator_bulk_disable(ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + + return 0; +} + +static int sii902x_pm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sii902x *sii902x = i2c_get_clientdata(client); + + DRM_DEBUG_DRIVER("\n"); + + if (sii902x->reset_gpio) + gpiod_set_value(sii902x->reset_gpio, 1); + + regulator_bulk_disable(ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + return 0; } +static int sii902x_pm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sii902x *sii902x = i2c_get_clientdata(client); + unsigned char data[2] = { SII902X_CEC_SETUP, 0}; + struct i2c_msg msg = { + .addr = SII902X_CEC_I2C_ADDR << 1, + .flags = 0, + .len = 2, + .buf = data, + }; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + ret = regulator_bulk_enable(ARRAY_SIZE(sii902x->supplies), + sii902x->supplies); + if (ret) { + DRM_ERROR("regulator_bulk_enable failed\n"); + return ret; + } + + if (sii902x->reset_gpio) + gpiod_set_value(sii902x->reset_gpio, 0); + + regmap_write(sii902x->regmap, SII902X_REG_TPI_RQB, 0x00); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + DRM_ERROR("Failed to disable CEC device!\n"); + + if (client->irq > 0) + regmap_update_bits(sii902x->regmap, SII902X_INT_ENABLE, + SII902X_HOTPLUG_EVENT, + SII902X_HOTPLUG_EVENT); + + return 0; +} + +static const struct dev_pm_ops sii902x_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sii902x_pm_suspend, sii902x_pm_resume) +}; + static const struct of_device_id sii902x_dt_ids[] = { { .compatible = "sil,sii9022", }, { } @@ -465,6 +1156,7 @@ static struct i2c_driver sii902x_driver = { .driver = { .name = "sii902x", .of_match_table = sii902x_dt_ids, + .pm = &sii902x_pm_ops, }, .id_table = sii902x_i2c_ids, }; diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c index fd79996..2e4334f 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c @@ -206,6 +206,20 @@ #define DSI_INT_ST0 0xbc #define DSI_INT_ST1 0xc0 +#define GPRXE BIT(12) +#define GPRDE BIT(11) +#define GPTXE BIT(10) +#define GPWRE BIT(9) +#define GCWRE BIT(8) +#define DPIPLDWE BIT(7) +#define EOTPE BIT(6) +#define PSE BIT(5) +#define CRCE BIT(4) +#define ECCME BIT(3) +#define ECCSE BIT(2) +#define TOLPRX BIT(1) +#define TOHSTX BIT(0) + #define DSI_INT_MSK0 0xc4 #define DSI_INT_MSK1 0xc8 @@ -357,6 +371,42 @@ static int dw_mipi_dsi_gen_pkt_hdr_write(struct dw_mipi_dsi *dsi, u32 hdr_val) return 0; } +static int dw_mipi_dsi_read_status(struct dw_mipi_dsi *dsi) +{ + u32 val; + + val = dsi_read(dsi, DSI_INT_ST1); + + if (val & GPRXE) + DRM_DEBUG_DRIVER("DSI Generic payload receive error\n"); + if (val & GPRDE) + DRM_DEBUG_DRIVER("DSI Generic payload read error\n"); + if (val & GPTXE) + DRM_DEBUG_DRIVER("DSI Generic payload transmit error\n"); + if (val & GPWRE) + DRM_DEBUG_DRIVER("DSI Generic payload write error\n"); + if (val & GCWRE) + DRM_DEBUG_DRIVER("DSI Generic command write error\n"); + if (val & DPIPLDWE) + DRM_DEBUG_DRIVER("DSI DPI payload write error\n"); + if (val & EOTPE) + DRM_DEBUG_DRIVER("DSI EoTp error\n"); + if (val & PSE) + DRM_DEBUG_DRIVER("DSI Packet size error\n"); + if (val & CRCE) + DRM_DEBUG_DRIVER("DSI CRC error\n"); + if (val & ECCME) + DRM_DEBUG_DRIVER("DSI ECC multi-bit error\n"); + if (val & ECCSE) + DRM_DEBUG_DRIVER("DSI ECC single-bit error\n"); + if (val & TOLPRX) + DRM_DEBUG_DRIVER("DSI Timeout low-power reception\n"); + if (val & TOHSTX) + DRM_DEBUG_DRIVER("DSI Timeout high-speed transmission\n"); + + return val; +} + static int dw_mipi_dsi_write(struct dw_mipi_dsi *dsi, const struct mipi_dsi_packet *packet) { @@ -386,6 +436,12 @@ static int dw_mipi_dsi_write(struct dw_mipi_dsi *dsi, "failed to get available write payload FIFO\n"); return ret; } + + val = dw_mipi_dsi_read_status(dsi); + if (val) { + dev_err(dsi->dev, "dsi status error 0x%0x\n", val); + return -EINVAL; + } } word = 0; @@ -419,6 +475,12 @@ static int dw_mipi_dsi_read(struct dw_mipi_dsi *dsi, return ret; } + val = dw_mipi_dsi_read_status(dsi); + if (val) { + dev_err(dsi->dev, "dsi status error 0x%0x\n", val); + return -EINVAL; + } + val = dsi_read(dsi, DSI_GEN_PLD_DATA); for (j = 0; j < 4 && j + i < len; j++) buf[i + j] = val >> (8 * j); @@ -433,6 +495,7 @@ static ssize_t dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host, struct dw_mipi_dsi *dsi = host_to_dsi(host); struct mipi_dsi_packet packet; int ret, nb_bytes; + int retry = 3; ret = mipi_dsi_create_packet(&packet, msg); if (ret) { @@ -442,19 +505,26 @@ static ssize_t dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host, dw_mipi_message_config(dsi, msg); - ret = dw_mipi_dsi_write(dsi, &packet); - if (ret) - return ret; - - if (msg->rx_buf && msg->rx_len) { - ret = dw_mipi_dsi_read(dsi, msg); + while (retry--) { + ret = dw_mipi_dsi_write(dsi, &packet); if (ret) - return ret; - nb_bytes = msg->rx_len; - } else { - nb_bytes = packet.size; + continue; + + if (msg->rx_buf && msg->rx_len) { + ret = dw_mipi_dsi_read(dsi, msg); + if (ret) + continue; + nb_bytes = msg->rx_len; + break; + } else { + nb_bytes = packet.size; + break; + } } + if (ret) + return ret; + return nb_bytes; } @@ -488,6 +558,9 @@ static void dw_mipi_dsi_video_mode_config(struct dw_mipi_dsi *dsi) static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi, unsigned long mode_flags) { + const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; + void *priv_data = dsi->plat_data->priv_data; + dsi_write(dsi, DSI_PWR_UP, RESET); if (mode_flags & MIPI_DSI_MODE_VIDEO) { @@ -498,6 +571,9 @@ static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi, dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE); } + if (phy_ops->post_set_mode) + phy_ops->post_set_mode(priv_data, mode_flags); + dsi_write(dsi, DSI_PWR_UP, POWERUP); } @@ -588,6 +664,9 @@ static void dw_mipi_dsi_video_packet_config(struct dw_mipi_dsi *dsi, static void dw_mipi_dsi_command_mode_config(struct dw_mipi_dsi *dsi) { + const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; + void *priv_data = dsi->plat_data->priv_data; + /* * TODO dw drv improvements * compute high speed transmission counter timeout according @@ -601,6 +680,9 @@ static void dw_mipi_dsi_command_mode_config(struct dw_mipi_dsi *dsi) */ dsi_write(dsi, DSI_BTA_TO_CNT, 0xd00); dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE); + + if (phy_ops->post_set_mode) + phy_ops->post_set_mode(priv_data, 0); } /* Get lane byte clock cycles. */ diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c index bf90625..c7217b1 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -326,12 +326,6 @@ int drm_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, if (!obj) return -ENOENT; - /* Don't allow imported objects to be mapped */ - if (obj->import_attach) { - ret = -EINVAL; - goto out; - } - ret = drm_gem_create_mmap_offset(obj); if (ret) goto out; diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index a3104d7..f1600e4 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -130,7 +130,7 @@ EXPORT_SYMBOL(drm_mode_probed_add); * according to the hdisplay, vdisplay, vrefresh. * It is based from the VESA(TM) Coordinated Video Timing Generator by * Graham Loveridge April 9, 2003 available at - * http://www.elo.utfsm.cl/~elo212/docs/CVTd6r1.xls + * http://www.elo.utfsm.cl/~elo212/docs/CVTd6r1.xls * * And it is copied from xf86CVTmode in xserver/hw/xfree86/modes/xf86cvt.c. * What I have done is to translate it by using integer calculation. @@ -611,6 +611,15 @@ void drm_display_mode_from_videomode(const struct videomode *vm, dmode->flags |= DRM_MODE_FLAG_DBLSCAN; if (vm->flags & DISPLAY_FLAGS_DOUBLECLK) dmode->flags |= DRM_MODE_FLAG_DBLCLK; + if (vm->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) + dmode->flags |= DRM_MODE_FLAG_PPIXDATA; + else if (vm->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) + dmode->flags |= DRM_MODE_FLAG_NPIXDATA; + if (vm->flags & DISPLAY_FLAGS_DE_HIGH) + dmode->flags |= DRM_MODE_FLAG_PDE; + else if (vm->flags & DISPLAY_FLAGS_DE_LOW) + dmode->flags |= DRM_MODE_FLAG_NDE; + drm_mode_set_name(dmode); } EXPORT_SYMBOL_GPL(drm_display_mode_from_videomode); @@ -652,6 +661,14 @@ void drm_display_mode_to_videomode(const struct drm_display_mode *dmode, vm->flags |= DISPLAY_FLAGS_DOUBLESCAN; if (dmode->flags & DRM_MODE_FLAG_DBLCLK) vm->flags |= DISPLAY_FLAGS_DOUBLECLK; + if (dmode->flags & DRM_MODE_FLAG_PPIXDATA) + vm->flags |= DISPLAY_FLAGS_PIXDATA_POSEDGE; + else if (dmode->flags & DRM_MODE_FLAG_NPIXDATA) + vm->flags |= DISPLAY_FLAGS_PIXDATA_NEGEDGE; + if (dmode->flags & DRM_MODE_FLAG_PDE) + vm->flags |= DISPLAY_FLAGS_DE_HIGH; + else if (dmode->flags & DRM_MODE_FLAG_NDE) + vm->flags |= DISPLAY_FLAGS_DE_LOW; } EXPORT_SYMBOL_GPL(drm_display_mode_to_videomode); diff --git a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c index 58ccf64..afcae08 100644 --- a/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c +++ b/drivers/gpu/drm/panel/panel-orisetech-otm8009a.c @@ -67,15 +67,15 @@ struct otm8009a { }; static const struct drm_display_mode default_mode = { - .clock = 32729, + .clock = 29700, .hdisplay = 480, - .hsync_start = 480 + 120, - .hsync_end = 480 + 120 + 63, - .htotal = 480 + 120 + 63 + 120, + .hsync_start = 480 + 98, + .hsync_end = 480 + 98 + 32, + .htotal = 480 + 98 + 32 + 98, .vdisplay = 800, - .vsync_start = 800 + 12, - .vsync_end = 800 + 12 + 12, - .vtotal = 800 + 12 + 12 + 12, + .vsync_start = 800 + 15, + .vsync_end = 800 + 15 + 10, + .vtotal = 800 + 15 + 10 + 14, .vrefresh = 50, .flags = 0, .width_mm = 52, @@ -257,24 +257,12 @@ static int otm8009a_init_sequence(struct otm8009a *ctx) static int otm8009a_disable(struct drm_panel *panel) { struct otm8009a *ctx = panel_to_otm8009a(panel); - struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); - int ret; if (!ctx->enabled) return 0; /* This is not an issue so we return 0 here */ backlight_disable(ctx->bl_dev); - ret = mipi_dsi_dcs_set_display_off(dsi); - if (ret) - return ret; - - ret = mipi_dsi_dcs_enter_sleep_mode(dsi); - if (ret) - return ret; - - msleep(120); - ctx->enabled = false; return 0; @@ -283,14 +271,23 @@ static int otm8009a_disable(struct drm_panel *panel) static int otm8009a_unprepare(struct drm_panel *panel) { struct otm8009a *ctx = panel_to_otm8009a(panel); + struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); + int ret; if (!ctx->prepared) return 0; - if (ctx->reset_gpio) { - gpiod_set_value_cansleep(ctx->reset_gpio, 1); - msleep(20); - } + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret) + return ret; + + mdelay(10); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret) + return ret; + + mdelay(10); regulator_disable(ctx->supply); @@ -307,18 +304,24 @@ static int otm8009a_prepare(struct drm_panel *panel) if (ctx->prepared) return 0; + if (ctx->reset_gpio) { + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + } + + mdelay(20); + ret = regulator_enable(ctx->supply); if (ret < 0) { DRM_ERROR("failed to enable supply: %d\n", ret); return ret; } + mdelay(120); + if (ctx->reset_gpio) { gpiod_set_value_cansleep(ctx->reset_gpio, 0); - gpiod_set_value_cansleep(ctx->reset_gpio, 1); - msleep(20); - gpiod_set_value_cansleep(ctx->reset_gpio, 0); - msleep(100); + mdelay(20); } ret = otm8009a_init_sequence(ctx); @@ -433,10 +436,22 @@ static int otm8009a_probe(struct mipi_dsi_device *dsi) return PTR_ERR(ctx->reset_gpio); } + /* + * Due to a common reset between panel & touchscreen, the reset pin + * must be set to low level first and leave at high level at the + * end of probe + */ + if (ctx->reset_gpio) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + mdelay(1); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + } + ctx->supply = devm_regulator_get(dev, "power"); if (IS_ERR(ctx->supply)) { ret = PTR_ERR(ctx->supply); - dev_err(dev, "failed to request regulator: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to request regulator: %d\n", ret); return ret; } @@ -488,6 +503,13 @@ static int otm8009a_remove(struct mipi_dsi_device *dsi) mipi_dsi_detach(dsi); drm_panel_remove(&ctx->panel); + if (ctx->reset_gpio) { + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + mdelay(20); + } + + regulator_disable(ctx->supply); + return 0; } diff --git a/drivers/gpu/drm/panel/panel-raydium-rm68200.c b/drivers/gpu/drm/panel/panel-raydium-rm68200.c index 7759353..9fe15a4 100644 --- a/drivers/gpu/drm/panel/panel-raydium-rm68200.c +++ b/drivers/gpu/drm/panel/panel-raydium-rm68200.c @@ -81,15 +81,15 @@ struct rm68200 { }; static const struct drm_display_mode default_mode = { - .clock = 52582, + .clock = 54000, .hdisplay = 720, - .hsync_start = 720 + 38, - .hsync_end = 720 + 38 + 8, - .htotal = 720 + 38 + 8 + 38, + .hsync_start = 720 + 48, + .hsync_end = 720 + 48 + 9, + .htotal = 720 + 48 + 9 + 48, .vdisplay = 1280, .vsync_start = 1280 + 12, - .vsync_end = 1280 + 12 + 4, - .vtotal = 1280 + 12 + 4 + 12, + .vsync_end = 1280 + 12 + 5, + .vtotal = 1280 + 12 + 5 + 12, .vrefresh = 50, .flags = 0, .width_mm = 68, @@ -265,11 +265,6 @@ static int rm68200_unprepare(struct drm_panel *panel) msleep(120); - if (ctx->reset_gpio) { - gpiod_set_value_cansleep(ctx->reset_gpio, 1); - msleep(20); - } - regulator_disable(ctx->supply); ctx->prepared = false; @@ -383,7 +378,8 @@ static int rm68200_probe(struct mipi_dsi_device *dsi) ctx->supply = devm_regulator_get(dev, "power"); if (IS_ERR(ctx->supply)) { ret = PTR_ERR(ctx->supply); - dev_err(dev, "cannot get regulator: %d\n", ret); + if (ret != -EPROBE_DEFER) + dev_err(dev, "cannot get regulator: %d\n", ret); return ret; } diff --git a/drivers/gpu/drm/stm/drv.c b/drivers/gpu/drm/stm/drv.c index f2021b2..f578da4 100644 --- a/drivers/gpu/drm/stm/drv.c +++ b/drivers/gpu/drm/stm/drv.c @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -26,7 +27,6 @@ static const struct drm_mode_config_funcs drv_mode_config_funcs = { .fb_create = drm_gem_fb_create, - .output_poll_changed = drm_fb_helper_output_poll_changed, .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, }; @@ -52,7 +52,6 @@ DEFINE_DRM_GEM_CMA_FOPS(drv_driver_fops); static struct drm_driver drv_driver = { .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | DRIVER_ATOMIC, - .lastclose = drm_fb_helper_lastclose, .name = "stm", .desc = "STMicroelectronics SoC DRM", .date = "20170330", @@ -72,6 +71,8 @@ static struct drm_driver drv_driver = { .gem_prime_vmap = drm_gem_cma_prime_vmap, .gem_prime_vunmap = drm_gem_cma_prime_vunmap, .gem_prime_mmap = drm_gem_cma_prime_mmap, + .get_scanout_position = ltdc_crtc_scanoutpos, + .get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos, }; static int drv_load(struct drm_device *ddev) @@ -108,12 +109,6 @@ static int drv_load(struct drm_device *ddev) drm_mode_config_reset(ddev); drm_kms_helper_poll_init(ddev); - if (ddev->mode_config.num_connector) { - ret = drm_fb_cma_fbdev_init(ddev, 16, 0); - if (ret) - DRM_DEBUG("Warning: fails to create fbdev\n"); - } - platform_set_drvdata(pdev, ddev); return 0; @@ -126,12 +121,72 @@ static void drv_unload(struct drm_device *ddev) { DRM_DEBUG("%s\n", __func__); - drm_fb_cma_fbdev_fini(ddev); drm_kms_helper_poll_fini(ddev); ltdc_unload(ddev); drm_mode_config_cleanup(ddev); } +static __maybe_unused int drv_suspend(struct device *dev) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct ltdc_device *ldev = ddev->dev_private; + struct drm_atomic_state *state; + + WARN_ON(ldev->suspend_state); + + state = drm_atomic_helper_suspend(ddev); + if (IS_ERR(state)) + return PTR_ERR(state); + + ldev->suspend_state = state; + pm_runtime_force_suspend(dev); + + return 0; +} + +static __maybe_unused int drv_resume(struct device *dev) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + struct ltdc_device *ldev = ddev->dev_private; + int ret; + + if (WARN_ON(!ldev->suspend_state)) + return -ENOENT; + + pm_runtime_force_resume(dev); + ret = drm_atomic_helper_resume(ddev, ldev->suspend_state); + if (ret) + pm_runtime_force_suspend(dev); + + ldev->suspend_state = NULL; + + return ret; +} + +static __maybe_unused int drv_runtime_suspend(struct device *dev) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + + DRM_DEBUG_DRIVER("\n"); + ltdc_suspend(ddev); + + return 0; +} + +static __maybe_unused int drv_runtime_resume(struct device *dev) +{ + struct drm_device *ddev = dev_get_drvdata(dev); + + DRM_DEBUG_DRIVER("\n"); + return ltdc_resume(ddev); +} + +static const struct dev_pm_ops drv_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(drv_suspend, drv_resume) + SET_RUNTIME_PM_OPS(drv_runtime_suspend, + drv_runtime_resume, NULL) +}; + static int stm_drm_platform_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -154,6 +209,8 @@ static int stm_drm_platform_probe(struct platform_device *pdev) if (ret) goto err_put; + drm_fbdev_generic_setup(ddev, 16); + return 0; err_put: @@ -187,6 +244,7 @@ static struct platform_driver stm_drm_platform_driver = { .driver = { .name = "stm32-display", .of_match_table = drv_dt_ids, + .pm = &drv_pm_ops, }, }; diff --git a/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c b/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c index a514b59..44e29af 100644 --- a/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c +++ b/drivers/gpu/drm/stm/dw_mipi_dsi-stm.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,7 @@ struct dw_mipi_dsi_stm { u32 hw_version; int lane_min_kbps; int lane_max_kbps; + struct regulator *vdd_supply; }; static inline void dsi_write(struct dw_mipi_dsi_stm *dsi, u32 reg, u32 val) @@ -208,12 +210,26 @@ static int dw_mipi_dsi_phy_init(void *priv_data) if (ret) DRM_DEBUG_DRIVER("!TIMEOUT! waiting PLL, let's continue\n"); - /* Enable the DSI wrapper */ - dsi_set(dsi, DSI_WCR, WCR_DSIEN); - return 0; } +static void dw_mipi_dsi_phy_post_set_mode(void *priv_data, unsigned long mode_flags) +{ + struct dw_mipi_dsi_stm *dsi = priv_data; + + DRM_DEBUG_DRIVER("\n"); + + /* + * DSI wrapper must be enabled in video mode & disabled in command mode. + * If wrapper is enabled in command mode, the display controller + * register access will hang. + */ + if (mode_flags & MIPI_DSI_MODE_VIDEO) + dsi_set(dsi, DSI_WCR, WCR_DSIEN); + else + dsi_clear(dsi, DSI_WCR, WCR_DSIEN); +} + static int dw_mipi_dsi_get_lane_mbps(void *priv_data, struct drm_display_mode *mode, unsigned long mode_flags, u32 lanes, u32 format, @@ -225,7 +241,6 @@ dw_mipi_dsi_get_lane_mbps(void *priv_data, struct drm_display_mode *mode, u32 val; /* Update lane capabilities according to hw version */ - dsi->hw_version = dsi_read(dsi, DSI_VERSION) & VERSION; dsi->lane_min_kbps = LANE_MIN_KBPS; dsi->lane_max_kbps = LANE_MAX_KBPS; if (dsi->hw_version == HWVER_131) { @@ -287,6 +302,7 @@ dw_mipi_dsi_get_lane_mbps(void *priv_data, struct drm_display_mode *mode, static const struct dw_mipi_dsi_phy_ops dw_mipi_dsi_stm_phy_ops = { .init = dw_mipi_dsi_phy_init, .get_lane_mbps = dw_mipi_dsi_get_lane_mbps, + .post_set_mode = dw_mipi_dsi_phy_post_set_mode, }; static struct dw_mipi_dsi_plat_data dw_mipi_dsi_stm_plat_data = { @@ -304,6 +320,7 @@ static int dw_mipi_dsi_stm_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct dw_mipi_dsi_stm *dsi; + struct clk *pclk; struct resource *res; int ret; @@ -318,17 +335,53 @@ static int dw_mipi_dsi_stm_probe(struct platform_device *pdev) return PTR_ERR(dsi->base); } + dsi->vdd_supply = devm_regulator_get_optional(dev, "phy-dsi"); + if (IS_ERR(dsi->vdd_supply)) { + ret = PTR_ERR(dsi->vdd_supply); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to request regulator: %d\n", ret); + return ret; + } + + ret = regulator_enable(dsi->vdd_supply); + if (ret) { + dev_err(dev, "failed to enable regulator: %d\n", ret); + return ret; + } + 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; + goto err_clk_get; } ret = clk_prepare_enable(dsi->pllref_clk); if (ret) { dev_err(dev, "%s: Failed to enable pllref_clk\n", __func__); - return ret; + goto err_clk_get; + } + + pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(pclk)) { + ret = PTR_ERR(pclk); + dev_err(dev, "Unable to get peripheral clock: %d\n", ret); + goto err_pclk_get; + } + + ret = clk_prepare_enable(pclk); + if (ret) { + dev_err(dev, "%s: Failed to enable peripheral clk\n", __func__); + goto err_pclk_get; + } + + dsi->hw_version = dsi_read(dsi, DSI_VERSION) & VERSION; + clk_disable_unprepare(pclk); + + if (dsi->hw_version != HWVER_130 && dsi->hw_version != HWVER_131) { + ret = -ENODEV; + dev_err(dev, "bad dsi hardware version\n"); + goto err_pclk_get; } dw_mipi_dsi_stm_plat_data.base = dsi->base; @@ -338,12 +391,21 @@ static int dw_mipi_dsi_stm_probe(struct platform_device *pdev) dsi->dsi = dw_mipi_dsi_probe(pdev, &dw_mipi_dsi_stm_plat_data); if (IS_ERR(dsi->dsi)) { + ret = PTR_ERR(dsi->dsi); DRM_ERROR("Failed to initialize mipi dsi host\n"); - clk_disable_unprepare(dsi->pllref_clk); - return PTR_ERR(dsi->dsi); + goto err_pclk_get; } return 0; + +err_pclk_get: + clk_disable_unprepare(dsi->pllref_clk); + +err_clk_get: + regulator_disable(dsi->vdd_supply); + + return ret; + } static int dw_mipi_dsi_stm_remove(struct platform_device *pdev) @@ -351,17 +413,53 @@ static int dw_mipi_dsi_stm_remove(struct platform_device *pdev) struct dw_mipi_dsi_stm *dsi = platform_get_drvdata(pdev); clk_disable_unprepare(dsi->pllref_clk); + regulator_disable(dsi->vdd_supply); dw_mipi_dsi_remove(dsi->dsi); return 0; } +static int __maybe_unused dw_mipi_dsi_stm_suspend(struct device *dev) +{ + struct dw_mipi_dsi_stm *dsi = dw_mipi_dsi_stm_plat_data.priv_data; + + DRM_DEBUG_DRIVER("\n"); + + clk_disable_unprepare(dsi->pllref_clk); + regulator_disable(dsi->vdd_supply); + + return 0; +} + +static int __maybe_unused dw_mipi_dsi_stm_resume(struct device *dev) +{ + struct dw_mipi_dsi_stm *dsi = dw_mipi_dsi_stm_plat_data.priv_data; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + ret = regulator_enable(dsi->vdd_supply); + if (ret) { + DRM_ERROR("can't enable power supply\n"); + return ret; + } + clk_prepare_enable(dsi->pllref_clk); + + return 0; +} + +static const struct dev_pm_ops dw_mipi_dsi_stm_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dw_mipi_dsi_stm_suspend, + dw_mipi_dsi_stm_resume) +}; + static struct platform_driver dw_mipi_dsi_stm_driver = { .probe = dw_mipi_dsi_stm_probe, .remove = dw_mipi_dsi_stm_remove, .driver = { .of_match_table = dw_mipi_dsi_stm_dt_ids, .name = "stm32-display-dsi", + .pm = &dw_mipi_dsi_stm_pm_ops, }, }; diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index 477d0a2..013b9f9 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include @@ -149,6 +151,8 @@ #define IER_TERRIE BIT(2) /* Transfer ERRor Interrupt Enable */ #define IER_RRIE BIT(3) /* Register Reload Interrupt enable */ +#define CPSR_CYPOS GENMASK(15, 0) /* Current Y position */ + #define ISR_LIF BIT(0) /* Line Interrupt Flag */ #define ISR_FUIF BIT(1) /* Fifo Underrun Interrupt Flag */ #define ISR_TERRIF BIT(2) /* Transfer ERRor Interrupt Flag */ @@ -421,9 +425,6 @@ static void ltdc_crtc_atomic_enable(struct drm_crtc *crtc, /* Immediately commit the planes */ reg_set(ldev->regs, LTDC_SRCR, SRCR_IMR); - /* Enable LTDC */ - reg_set(ldev->regs, LTDC_GCR, GCR_LTDCEN); - drm_crtc_vblank_on(crtc); } @@ -431,19 +432,19 @@ static void ltdc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { struct ltdc_device *ldev = crtc_to_ltdc(crtc); + struct drm_device *ddev = crtc->dev; DRM_DEBUG_DRIVER("\n"); drm_crtc_vblank_off(crtc); - /* disable LTDC */ - reg_clear(ldev->regs, LTDC_GCR, GCR_LTDCEN); - /* disable IRQ */ reg_clear(ldev->regs, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); /* immediately commit disable of layers before switching off LTDC */ reg_set(ldev->regs, LTDC_SRCR, SRCR_IMR); + + pm_runtime_put_sync(ddev->dev); } #define CLK_TOLERANCE_HZ 50 @@ -492,33 +493,55 @@ static bool ltdc_crtc_mode_fixup(struct drm_crtc *crtc, struct drm_display_mode *adjusted_mode) { struct ltdc_device *ldev = crtc_to_ltdc(crtc); + struct drm_device *ddev = crtc->dev; int rate = mode->clock * 1000; + bool runtime_active; + int ret; - /* - * TODO clk_round_rate() does not work yet. When ready, it can - * be used instead of clk_set_rate() then clk_get_rate(). - */ + runtime_active = pm_runtime_active(ddev->dev); + + if (runtime_active) + pm_runtime_put_sync(ddev->dev); - clk_disable(ldev->pixel_clk); if (clk_set_rate(ldev->pixel_clk, rate) < 0) { DRM_ERROR("Cannot set rate (%dHz) for pixel clk\n", rate); return false; } - clk_enable(ldev->pixel_clk); adjusted_mode->clock = clk_get_rate(ldev->pixel_clk) / 1000; + if (runtime_active) { + ret = pm_runtime_get_sync(ddev->dev); + if (ret) { + DRM_ERROR("Failed to fixup mode, cannot get sync\n"); + return false; + } + } + + DRM_DEBUG_DRIVER("requested clock %dkHz, adjusted clock %dkHz\n", + mode->clock, adjusted_mode->clock); + return true; } static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc) { struct ltdc_device *ldev = crtc_to_ltdc(crtc); + struct drm_device *ddev = crtc->dev; struct drm_display_mode *mode = &crtc->state->adjusted_mode; struct videomode vm; u32 hsync, vsync, accum_hbp, accum_vbp, accum_act_w, accum_act_h; u32 total_width, total_height; u32 val; + int ret; + + if (!pm_runtime_active(ddev->dev)) { + ret = pm_runtime_get_sync(ddev->dev); + if (ret) { + DRM_ERROR("Failed to set mode, cannot get sync\n"); + return; + } + } drm_display_mode_to_videomode(mode, &vm); @@ -547,10 +570,10 @@ static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc) if (vm.flags & DISPLAY_FLAGS_VSYNC_HIGH) val |= GCR_VSPOL; - if (vm.flags & DISPLAY_FLAGS_DE_HIGH) + if (vm.flags & DISPLAY_FLAGS_DE_LOW) val |= GCR_DEPOL; - if (vm.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) + if (vm.flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) val |= GCR_PCPOL; reg_update_bits(ldev->regs, LTDC_GCR, @@ -627,6 +650,53 @@ static void ltdc_crtc_disable_vblank(struct drm_crtc *crtc) reg_clear(ldev->regs, LTDC_IER, IER_LIE); } +bool ltdc_crtc_scanoutpos(struct drm_device *ddev, unsigned int pipe, + bool in_vblank_irq, int *vpos, int *hpos, + ktime_t *stime, ktime_t *etime, + const struct drm_display_mode *mode) +{ + struct ltdc_device *ldev = ddev->dev_private; + int line, vactive_start, vactive_end, vtotal; + + if (stime) + *stime = ktime_get(); + + /* The active area starts after vsync + front porch and ends + * at vsync + front porc + display size. + * The total height also include back porch. + * We have 3 possible cases to handle: + * - line < vactive_start: vpos = line - vactive_start and will be + * negative + * - vactive_start < line < vactive_end: vpos = line - vactive_start + * and will be positive + * - line > vactive_end: vpos = line - vtotal - vactive_start + * and will negative + * + * Computation for the two first cases are identical so we can + * simplify the code and only test if line > vactive_end + */ + if (pm_runtime_active(ddev->dev)) { + line = reg_read(ldev->regs, LTDC_CPSR) & CPSR_CYPOS; + vactive_start = reg_read(ldev->regs, LTDC_BPCR) & BPCR_AVBP; + vactive_end = reg_read(ldev->regs, LTDC_AWCR) & AWCR_AAH; + vtotal = reg_read(ldev->regs, LTDC_TWCR) & TWCR_TOTALH; + + if (line > vactive_end) + *vpos = line - vtotal - vactive_start; + else + *vpos = line - vactive_start; + } else { + *vpos = 0; + } + + *hpos = 0; + + if (etime) + *etime = ktime_get(); + + return true; +} + static const struct drm_crtc_funcs ltdc_crtc_funcs = { .destroy = drm_crtc_cleanup, .set_config = drm_atomic_helper_set_config, @@ -647,24 +717,44 @@ static int ltdc_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) { struct drm_framebuffer *fb = state->fb; - u32 src_x, src_y, src_w, src_h; + struct drm_crtc_state *crtc_state; + struct drm_rect *src = &state->src; + struct drm_rect *dst = &state->dst; DRM_DEBUG_DRIVER("\n"); if (!fb) return 0; - /* convert src_ from 16:16 format */ - src_x = state->src_x >> 16; - src_y = state->src_y >> 16; - src_w = state->src_w >> 16; - src_h = state->src_h >> 16; + /* convert src from 16:16 format */ + src->x1 = state->src_x >> 16; + src->y1 = state->src_y >> 16; + src->x2 = (state->src_w >> 16) + src->x1 - 1; + src->y2 = (state->src_h >> 16) + src->y1 - 1; + + dst->x1 = state->crtc_x; + dst->y1 = state->crtc_y; + dst->x2 = state->crtc_w + dst->x1 - 1; + dst->y2 = state->crtc_h + dst->y1 - 1; - /* Reject scaling */ - if (src_w != state->crtc_w || src_h != state->crtc_h) { - DRM_ERROR("Scaling is not supported"); + DRM_DEBUG_DRIVER("plane:%d fb:%d (%dx%d)@(%d,%d) -> (%dx%d)@(%d,%d)\n", + plane->base.id, fb->base.id, + src->x2 - src->x1 + 1, src->y2 - src->y1 + 1, + src->x1, src->y1, + dst->x2 - dst->x1 + 1, dst->y2 - dst->y1 + 1, + dst->x1, dst->y1); + + crtc_state = drm_atomic_get_existing_crtc_state(state->state, + state->crtc); + /* destination coordinates do not have to exceed display sizes */ + if (crtc_state && (crtc_state->mode.hdisplay <= dst->x2 || + crtc_state->mode.vdisplay <= dst->y2)) + return -EINVAL; + + /* source sizes do not have to exceed destination sizes */ + if (dst->x2 - dst->x1 < src->x2 - src->x1 || + dst->y2 - dst->y1 < src->y2 - src->y1) return -EINVAL; - } return 0; } @@ -674,44 +764,36 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, { struct ltdc_device *ldev = plane_to_ltdc(plane); struct drm_plane_state *state = plane->state; + struct drm_rect *src = &state->src; + struct drm_rect *dst = &state->dst; struct drm_framebuffer *fb = state->fb; u32 lofs = plane->index * LAY_OFS; - u32 x0 = state->crtc_x; - u32 x1 = state->crtc_x + state->crtc_w - 1; - u32 y0 = state->crtc_y; - u32 y1 = state->crtc_y + state->crtc_h - 1; - u32 src_x, src_y, src_w, src_h; u32 val, pitch_in_bytes, line_length, paddr, ahbp, avbp, bpcr; enum ltdc_pix_fmt pf; + struct drm_rect dr; if (!state->crtc || !fb) { DRM_DEBUG_DRIVER("fb or crtc NULL"); return; } - /* convert src_ from 16:16 format */ - src_x = state->src_x >> 16; - src_y = state->src_y >> 16; - src_w = state->src_w >> 16; - src_h = state->src_h >> 16; - - DRM_DEBUG_DRIVER("plane:%d fb:%d (%dx%d)@(%d,%d) -> (%dx%d)@(%d,%d)\n", - plane->base.id, fb->base.id, - src_w, src_h, src_x, src_y, - state->crtc_w, state->crtc_h, - state->crtc_x, state->crtc_y); + /* compute final coordinates of frame buffer */ + dr.x1 = src->x1 + dst->x1; + dr.y1 = src->y1 + dst->y1; + dr.x2 = src->x2 + dst->x1; + dr.y2 = src->y2 + dst->y1; bpcr = reg_read(ldev->regs, LTDC_BPCR); ahbp = (bpcr & BPCR_AHBP) >> 16; avbp = bpcr & BPCR_AVBP; /* Configures the horizontal start and stop position */ - val = ((x1 + 1 + ahbp) << 16) + (x0 + 1 + ahbp); + val = ((dr.x2 + 1 + ahbp) << 16) + (dr.x1 + 1 + ahbp); reg_update_bits(ldev->regs, LTDC_L1WHPCR + lofs, LXWHPCR_WHSTPOS | LXWHPCR_WHSPPOS, val); /* Configures the vertical start and stop position */ - val = ((y1 + 1 + avbp) << 16) + (y0 + 1 + avbp); + val = ((dr.y2 + 1 + avbp) << 16) + (dr.y1 + 1 + avbp); reg_update_bits(ldev->regs, LTDC_L1WVPCR + lofs, LXWVPCR_WVSTPOS | LXWVPCR_WVSPPOS, val); @@ -731,7 +813,7 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, /* Configures the color frame buffer pitch in bytes & line length */ pitch_in_bytes = fb->pitches[0]; line_length = drm_format_plane_cpp(fb->format->format, 0) * - (x1 - x0 + 1) + (ldev->caps.bus_width >> 3) - 1; + (dr.x2 - dr.x1 + 1) + (ldev->caps.bus_width >> 3) - 1; val = ((pitch_in_bytes << 16) | line_length); reg_update_bits(ldev->regs, LTDC_L1CFBLR + lofs, LXCFBLR_CFBLL | LXCFBLR_CFBP, val); @@ -754,7 +836,7 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, LXBFCR_BF2 | LXBFCR_BF1, val); /* Configures the frame buffer line number */ - val = y1 - y0 + 1; + val = dr.y2 - dr.y1 + 1; reg_update_bits(ldev->regs, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, val); /* Sets the FB address */ @@ -773,11 +855,11 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, mutex_lock(&ldev->err_lock); if (ldev->error_status & ISR_FUIF) { - DRM_DEBUG_DRIVER("Fifo underrun\n"); + DRM_WARN("ltdc fifo underrun: please verify display mode\n"); ldev->error_status &= ~ISR_FUIF; } if (ldev->error_status & ISR_TERRIF) { - DRM_DEBUG_DRIVER("Transfer error\n"); + DRM_WARN("ltdc transfer error\n"); ldev->error_status &= ~ISR_TERRIF; } mutex_unlock(&ldev->err_lock); @@ -815,6 +897,13 @@ static void ltdc_plane_atomic_print_state(struct drm_printer *p, fpsi->counter = 0; } +static bool ltdc_plane_format_mod_supported(struct drm_plane *plane, + u32 format, + u64 modifier) +{ + return modifier == DRM_FORMAT_MOD_LINEAR; +} + static const struct drm_plane_funcs ltdc_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, @@ -823,6 +912,7 @@ static const struct drm_plane_funcs ltdc_plane_funcs = { .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, .atomic_print_state = ltdc_plane_atomic_print_state, + .format_mod_supported = ltdc_plane_format_mod_supported, }; static const struct drm_plane_helper_funcs ltdc_plane_helper_funcs = { @@ -843,6 +933,10 @@ static struct drm_plane *ltdc_plane_create(struct drm_device *ddev, u32 formats[NB_PF * 2]; u32 drm_fmt, drm_fmt_no_alpha; int ret; + const u64 modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID + }; /* Get supported pixel formats */ for (i = 0; i < NB_PF; i++) { @@ -870,7 +964,7 @@ static struct drm_plane *ltdc_plane_create(struct drm_device *ddev, ret = drm_universal_plane_init(ddev, plane, possible_crtcs, <dc_plane_funcs, formats, nb_fmt, - NULL, type, NULL); + modifiers, type, NULL); if (ret < 0) return NULL; @@ -942,6 +1036,54 @@ static const struct drm_encoder_funcs ltdc_encoder_funcs = { .destroy = drm_encoder_cleanup, }; +static void ltdc_encoder_disable(struct drm_encoder *encoder) +{ + struct drm_device *ddev = encoder->dev; + struct ltdc_device *ldev = ddev->dev_private; + + DRM_DEBUG_DRIVER("\n"); + + /* Disable LTDC */ + reg_clear(ldev->regs, LTDC_GCR, GCR_LTDCEN); + + /* Set to sleep state the pinctrl whatever type of encoder */ + pinctrl_pm_select_sleep_state(ddev->dev); +} + +static void ltdc_encoder_enable(struct drm_encoder *encoder) +{ + struct drm_device *ddev = encoder->dev; + struct ltdc_device *ldev = ddev->dev_private; + + DRM_DEBUG_DRIVER("\n"); + + /* Enable LTDC */ + reg_set(ldev->regs, LTDC_GCR, GCR_LTDCEN); +} + +static void ltdc_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *ddev = encoder->dev; + + DRM_DEBUG_DRIVER("\n"); + + /* + * Set to default state the pinctrl only with DPI type. + * Others types like DSI, don't need pinctrl due to + * internal bridge (the signals do not come out of the chipset). + */ + if (encoder->encoder_type == DRM_MODE_ENCODER_DPI) + pinctrl_pm_select_default_state(ddev->dev); +} + +static const struct drm_encoder_helper_funcs ltdc_encoder_helper_funcs = { + .disable = ltdc_encoder_disable, + .enable = ltdc_encoder_enable, + .mode_set = ltdc_encoder_mode_set, +}; + static int ltdc_encoder_init(struct drm_device *ddev, struct drm_bridge *bridge) { struct drm_encoder *encoder; @@ -957,6 +1099,8 @@ static int ltdc_encoder_init(struct drm_device *ddev, struct drm_bridge *bridge) drm_encoder_init(ddev, encoder, <dc_encoder_funcs, DRM_MODE_ENCODER_DPI, NULL); + drm_encoder_helper_add(encoder, <dc_encoder_helper_funcs); + ret = drm_bridge_attach(encoder, bridge, NULL); if (ret) { drm_encoder_cleanup(encoder); @@ -1014,6 +1158,30 @@ static int ltdc_get_caps(struct drm_device *ddev) return 0; } +void ltdc_suspend(struct drm_device *ddev) +{ + struct ltdc_device *ldev = ddev->dev_private; + + DRM_DEBUG_DRIVER("\n"); + clk_disable_unprepare(ldev->pixel_clk); +} + +int ltdc_resume(struct drm_device *ddev) +{ + struct ltdc_device *ldev = ddev->dev_private; + int ret; + + DRM_DEBUG_DRIVER("\n"); + + ret = clk_prepare_enable(ldev->pixel_clk); + if (ret) { + DRM_ERROR("failed to enable pixel clock (%d)\n", ret); + return ret; + } + + return 0; +} + int ltdc_load(struct drm_device *ddev) { struct platform_device *pdev = to_platform_device(ddev->dev); @@ -1053,8 +1221,9 @@ int ltdc_load(struct drm_device *ddev) ldev->pixel_clk = devm_clk_get(dev, "lcd"); if (IS_ERR(ldev->pixel_clk)) { - DRM_ERROR("Unable to get lcd clock\n"); - return -ENODEV; + if (PTR_ERR(ldev->pixel_clk) != -EPROBE_DEFER) + DRM_ERROR("Unable to get lcd clock\n"); + return PTR_ERR(ldev->pixel_clk); } if (clk_prepare_enable(ldev->pixel_clk)) { @@ -1072,6 +1241,9 @@ int ltdc_load(struct drm_device *ddev) for (i = 0; i < MAX_IRQ; i++) { irq = platform_get_irq(pdev, i); + if (irq == -EPROBE_DEFER) + goto err; + if (irq < 0) continue; @@ -1131,6 +1303,8 @@ int ltdc_load(struct drm_device *ddev) goto err; } + ddev->mode_config.allow_fb_modifiers = true; + ret = ltdc_crtc_init(ddev, crtc); if (ret) { DRM_ERROR("Failed to init crtc\n"); @@ -1146,8 +1320,13 @@ int ltdc_load(struct drm_device *ddev) /* Allow usage of vblank without having to call drm_irq_install */ ddev->irq_enabled = 1; - return 0; + clk_disable_unprepare(ldev->pixel_clk); + + pinctrl_pm_select_sleep_state(ddev->dev); + + pm_runtime_enable(ddev->dev); + return 0; err: for (i = 0; i < MAX_ENDPOINTS; i++) drm_panel_bridge_remove(bridge[i]); @@ -1159,7 +1338,6 @@ int ltdc_load(struct drm_device *ddev) void ltdc_unload(struct drm_device *ddev) { - struct ltdc_device *ldev = ddev->dev_private; int i; DRM_DEBUG_DRIVER("\n"); @@ -1167,7 +1345,7 @@ void ltdc_unload(struct drm_device *ddev) for (i = 0; i < MAX_ENDPOINTS; i++) drm_of_panel_bridge_remove(ddev->dev->of_node, 0, i); - clk_disable_unprepare(ldev->pixel_clk); + pm_runtime_disable(ddev->dev); } MODULE_AUTHOR("Philippe Cornu "); diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h index d5afb89..9b410c6 100644 --- a/drivers/gpu/drm/stm/ltdc.h +++ b/drivers/gpu/drm/stm/ltdc.h @@ -36,9 +36,16 @@ struct ltdc_device { u32 error_status; u32 irq_status; struct fps_info plane_fpsi[LTDC_MAX_LAYER]; + struct drm_atomic_state *suspend_state; }; +bool ltdc_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe, + bool in_vblank_irq, int *vpos, int *hpos, + ktime_t *stime, ktime_t *etime, + const struct drm_display_mode *mode); int ltdc_load(struct drm_device *ddev); void ltdc_unload(struct drm_device *ddev); +void ltdc_suspend(struct drm_device *ddev); +int ltdc_resume(struct drm_device *ddev); #endif diff --git a/include/drm/bridge/dw_mipi_dsi.h b/include/drm/bridge/dw_mipi_dsi.h index d9c6d54..0fdc550 100644 --- a/include/drm/bridge/dw_mipi_dsi.h +++ b/include/drm/bridge/dw_mipi_dsi.h @@ -17,6 +17,7 @@ struct dw_mipi_dsi_phy_ops { int (*get_lane_mbps)(void *priv_data, struct drm_display_mode *mode, unsigned long mode_flags, u32 lanes, u32 format, unsigned int *lane_mbps); + void (*post_set_mode)(void *priv_data, unsigned long mode_flags); }; struct dw_mipi_dsi_plat_data { diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index 8d67243..c20c4c3 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -89,6 +89,12 @@ extern "C" { #define DRM_MODE_FLAG_3D_TOP_AND_BOTTOM (7<<14) #define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF (8<<14) +/* flags for polarity clock & data enable polarities */ +#define DRM_MODE_FLAG_PPIXDATA (1 << 19) +#define DRM_MODE_FLAG_NPIXDATA (1 << 20) +#define DRM_MODE_FLAG_PDE (1 << 21) +#define DRM_MODE_FLAG_NDE (1 << 22) + /* Picture aspect ratio options */ #define DRM_MODE_PICTURE_ASPECT_NONE 0 #define DRM_MODE_PICTURE_ASPECT_4_3 1 -- 2.7.4