meta-st-stm32mp/recipes-kernel/linux/linux-stm32mp/4.19/4.19.9/0018-ARM-stm32mp1-r0-rc2-DR...

1796 lines
52 KiB
Diff

From fd965c7b7da4a0a21bd7750be8c3851be80a576f Mon Sep 17 00:00:00 2001
From: Christophe Priouzeau <christophe.priouzeau@st.com>
Date: Tue, 27 Nov 2018 09:39:21 +0100
Subject: [PATCH 18/52] ARM-stm32mp1-r0-rc2-DRM-KMS
---
.../devicetree/bindings/display/bridge/sii902x.txt | 9 +
drivers/gpu/drm/bridge/Kconfig | 1 +
drivers/gpu/drm/bridge/sii902x.c | 844 +++++++++++++++++++--
drivers/gpu/drm/drm_modes.c | 19 +-
drivers/gpu/drm/panel/panel-orisetech-otm8009a.c | 12 +-
drivers/gpu/drm/panel/panel-raydium-rm68200.c | 12 +-
drivers/gpu/drm/stm/drv.c | 46 +-
drivers/gpu/drm/stm/dw_mipi_dsi-stm.c | 53 ++
drivers/gpu/drm/stm/ltdc.c | 191 +++--
drivers/gpu/drm/stm/ltdc.h | 6 +
include/uapi/drm/drm_mode.h | 6 +
11 files changed, 1060 insertions(+), 139 deletions(-)
diff --git a/Documentation/devicetree/bindings/display/bridge/sii902x.txt b/Documentation/devicetree/bindings/display/bridge/sii902x.txt
index 72d2dc6..00e9e88 100644
--- a/Documentation/devicetree/bindings/display/bridge/sii902x.txt
+++ b/Documentation/devicetree/bindings/display/bridge/sii902x.txt
@@ -13,6 +13,8 @@ Optional subnodes:
- video input: this subnode can contain a video input port node
to connect the bridge to a display controller output (See this
documentation [1]).
+ - audio input: this subnode can contain an audio input port node
+ to connect the bridge to an audio controller output.
[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
@@ -31,5 +33,12 @@ Example:
remote-endpoint = <&dc_out>;
};
};
+
+ port@1 {
+ reg = <1>;
+ codec_endpoint: endpoint {
+ remote-endpoint = <&i2s0_cpu_endpoint>;
+ };
+ };
};
};
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index bf6cad6..fe91c20 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -95,6 +95,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 e59a135..512eb03 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 <voice.shen@atmel.com>
*
@@ -20,16 +22,23 @@
* GNU General Public License for more details.
*/
+#include <linux/bitfield.h>
#include <linux/gpio/consumer.h>
+#include <linux/i2c-mux.h>
#include <linux/i2c.h>
#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
+#include <sound/hdmi-codec.h>
+
#define SII902X_TPI_VIDEO_DATA 0x0
#define SII902X_TPI_PIXEL_REPETITION 0x8
@@ -71,23 +80,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 = &reg,
+ }, {
+ .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 +350,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 +369,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;
}
@@ -240,17 +397,29 @@ static void sii902x_bridge_disable(struct drm_bridge *bridge)
regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
SII902X_SYS_CTRL_PWR_DWN,
SII902X_SYS_CTRL_PWR_DWN);
+ pinctrl_pm_select_sleep_state(&sii902x->i2c->dev);
}
static void sii902x_bridge_enable(struct drm_bridge *bridge)
{
struct sii902x *sii902x = bridge_to_sii902x(bridge);
+ bool hdmi_mode;
+ pinctrl_pm_select_default_state(&sii902x->i2c->dev);
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,
@@ -329,6 +498,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(&params->cea);
+ if (ret) {
+ DRM_ERROR("Failed to init audio infoframe\n");
+ return ret;
+ }
+
+ ret = hdmi_audio_infoframe_pack(&params->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,
@@ -348,10 +778,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;
@@ -366,15 +825,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;
@@ -392,39 +970,67 @@ 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;
+ }
+
+ pinctrl_pm_select_sleep_state(&sii902x->i2c->dev);
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;
@@ -433,7 +1039,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)
@@ -441,11 +1071,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", },
{ }
@@ -464,6 +1159,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/drm_modes.c b/drivers/gpu/drm/drm_modes.c
index 02db9ac..d9d9ad9 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 87fa316..a76d03a 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 = 33000,
.hdisplay = 480,
.hsync_start = 480 + 120,
- .hsync_end = 480 + 120 + 63,
- .htotal = 480 + 120 + 63 + 120,
+ .hsync_end = 480 + 120 + 64,
+ .htotal = 480 + 120 + 64 + 120,
.vdisplay = 800,
- .vsync_start = 800 + 12,
- .vsync_end = 800 + 12 + 12,
- .vtotal = 800 + 12 + 12 + 12,
+ .vsync_start = 800 + 14,
+ .vsync_end = 800 + 14 + 14,
+ .vtotal = 800 + 14 + 14 + 14,
.vrefresh = 50,
.flags = 0,
.width_mm = 52,
diff --git a/drivers/gpu/drm/panel/panel-raydium-rm68200.c b/drivers/gpu/drm/panel/panel-raydium-rm68200.c
index 7759353..94436ea 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,
diff --git a/drivers/gpu/drm/stm/drv.c b/drivers/gpu/drm/stm/drv.c
index f2021b2..cf61875 100644
--- a/drivers/gpu/drm/stm/drv.c
+++ b/drivers/gpu/drm/stm/drv.c
@@ -26,7 +26,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 +51,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 +70,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 +108,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 +120,43 @@ 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;
+
+ drm_kms_helper_poll_disable(ddev);
+ state = drm_atomic_helper_suspend(ddev);
+ if (IS_ERR(state)) {
+ drm_kms_helper_poll_enable(ddev);
+ return PTR_ERR(state);
+ }
+ ldev->suspend_state = state;
+
+ 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;
+
+ drm_atomic_helper_resume(ddev, ldev->suspend_state);
+ drm_kms_helper_poll_enable(ddev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops drv_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(drv_suspend, drv_resume)
+};
+
static int stm_drm_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -154,6 +179,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 +214,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..a6edd86 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 <linux/clk.h>
#include <linux/iopoll.h>
#include <linux/module.h>
+#include <linux/regulator/consumer.h>
#include <drm/drmP.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/bridge/dw_mipi_dsi.h>
@@ -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)
@@ -318,16 +320,30 @@ static int dw_mipi_dsi_stm_probe(struct platform_device *pdev)
return PTR_ERR(dsi->base);
}
+ dsi->vdd_supply = devm_regulator_get(dev, "phy-dsi");
+ if (IS_ERR(dsi->vdd_supply)) {
+ DRM_ERROR("can't get power supply\n");
+ return PTR_ERR(dsi->vdd_supply);
+ }
+
+ ret = regulator_enable(dsi->vdd_supply);
+ if (ret) {
+ DRM_ERROR("can't enable power supply\n");
+ 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);
+ regulator_disable(dsi->vdd_supply);
return ret;
}
ret = clk_prepare_enable(dsi->pllref_clk);
if (ret) {
dev_err(dev, "%s: Failed to enable pllref_clk\n", __func__);
+ regulator_disable(dsi->vdd_supply);
return ret;
}
@@ -339,6 +355,7 @@ 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)) {
DRM_ERROR("Failed to initialize mipi dsi host\n");
+ regulator_disable(dsi->vdd_supply);
clk_disable_unprepare(dsi->pllref_clk);
return PTR_ERR(dsi->dsi);
}
@@ -351,17 +368,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 808d9fb..599d2f8 100644
--- a/drivers/gpu/drm/stm/ltdc.c
+++ b/drivers/gpu/drm/stm/ltdc.c
@@ -148,6 +148,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 */
@@ -348,6 +350,33 @@ static inline u32 get_pixelformat_without_alpha(u32 drm)
}
}
+static int ltdc_power_up(struct ltdc_device *ldev)
+{
+ int ret;
+
+ DRM_DEBUG_DRIVER("\n");
+
+ if (!ldev->power_on) {
+ ret = clk_prepare_enable(ldev->pixel_clk);
+ if (ret) {
+ DRM_ERROR("failed to enable pixel clock (%d)\n",
+ ret);
+ return ret;
+ }
+ ldev->power_on = true;
+ }
+
+ return 0;
+}
+
+static void ltdc_power_down(struct ltdc_device *ldev)
+{
+ DRM_DEBUG_DRIVER("\n");
+
+ clk_disable_unprepare(ldev->pixel_clk);
+ ldev->power_on = false;
+}
+
static irqreturn_t ltdc_irq_thread(int irq, void *arg)
{
struct drm_device *ddev = arg;
@@ -408,9 +437,14 @@ static void ltdc_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
struct ltdc_device *ldev = crtc_to_ltdc(crtc);
+ int ret;
DRM_DEBUG_DRIVER("\n");
+ ret = ltdc_power_up(ldev);
+ if (ret)
+ return;
+
/* Sets the background color value */
reg_write(ldev->regs, LTDC_BCCR, BCCR_BCBLACK);
@@ -443,6 +477,8 @@ static void ltdc_crtc_atomic_disable(struct drm_crtc *crtc,
/* immediately commit disable of layers before switching off LTDC */
reg_set(ldev->regs, LTDC_SRCR, SRCR_IMR);
+
+ ltdc_power_down(ldev);
}
#define CLK_TOLERANCE_HZ 50
@@ -497,13 +533,10 @@ static bool ltdc_crtc_mode_fixup(struct drm_crtc *crtc,
* TODO clk_round_rate() does not work yet. When ready, it can
* be used instead of clk_set_rate() then clk_get_rate().
*/
-
- 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;
@@ -518,6 +551,11 @@ static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc)
u32 hsync, vsync, accum_hbp, accum_vbp, accum_act_w, accum_act_h;
u32 total_width, total_height;
u32 val;
+ int ret;
+
+ ret = ltdc_power_up(ldev);
+ if (ret)
+ return;
drm_display_mode_to_videomode(mode, &vm);
@@ -546,10 +584,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,
@@ -611,8 +649,14 @@ static const struct drm_crtc_helper_funcs ltdc_crtc_helper_funcs = {
static int ltdc_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct ltdc_device *ldev = crtc_to_ltdc(crtc);
+ int ret;
DRM_DEBUG_DRIVER("\n");
+
+ ret = ltdc_power_up(ldev);
+ if (ret)
+ return ret;
+
reg_set(ldev->regs, LTDC_IER, IER_LIE);
return 0;
@@ -623,9 +667,58 @@ static void ltdc_crtc_disable_vblank(struct drm_crtc *crtc)
struct ltdc_device *ldev = crtc_to_ltdc(crtc);
DRM_DEBUG_DRIVER("\n");
+
+ if (!ldev->power_on) {
+ DRM_WARN("power is already down\n");
+ return;
+ }
+
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
+ */
+ 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;
+
+ *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,
@@ -646,24 +739,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;
}
@@ -673,44 +786,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);
@@ -730,7 +835,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);
@@ -753,7 +858,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 */
@@ -772,11 +877,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);
@@ -1055,10 +1160,10 @@ int ltdc_load(struct drm_device *ddev)
return -ENODEV;
}
- if (clk_prepare_enable(ldev->pixel_clk)) {
- DRM_ERROR("Unable to prepare pixel clock\n");
- return -ENODEV;
- }
+ ldev->power_on = false;
+ ret = ltdc_power_up(ldev);
+ if (ret)
+ return ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ldev->regs = devm_ioremap_resource(dev, res);
@@ -1150,7 +1255,7 @@ int ltdc_load(struct drm_device *ddev)
for (i = 0; i < MAX_ENDPOINTS; i++)
drm_panel_bridge_remove(bridge[i]);
- clk_disable_unprepare(ldev->pixel_clk);
+ ltdc_power_down(ldev);
return ret;
}
@@ -1165,7 +1270,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);
+ ltdc_power_down(ldev);
}
MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h
index d5afb89..08bd69d 100644
--- a/drivers/gpu/drm/stm/ltdc.h
+++ b/drivers/gpu/drm/stm/ltdc.h
@@ -36,8 +36,14 @@ struct ltdc_device {
u32 error_status;
u32 irq_status;
struct fps_info plane_fpsi[LTDC_MAX_LAYER];
+ struct drm_atomic_state *suspend_state;
+ bool power_on;
};
+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);
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