summaryrefslogtreecommitdiff
path: root/drivers/gpu
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu')
-rw-r--r--drivers/gpu/drm/bridge/Kconfig8
-rw-r--r--drivers/gpu/drm/bridge/Makefile1
-rw-r--r--drivers/gpu/drm/bridge/dumb-vga-dac.c7
-rw-r--r--drivers/gpu/drm/bridge/lt8912.c688
-rw-r--r--drivers/gpu/drm/bridge/nwl-dsi.c2
-rw-r--r--drivers/gpu/drm/drm_atomic_helper.c2
-rw-r--r--drivers/gpu/drm/drm_modes.c15
-rw-r--r--drivers/gpu/drm/drm_vblank.c45
-rw-r--r--drivers/gpu/drm/imx/hdp/imx-hdp.c76
-rw-r--r--drivers/gpu/drm/imx/hdp/imx-hdp.h4
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_crtc.c8
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_drv.c80
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_drv.h2
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_out.c2
-rw-r--r--drivers/gpu/drm/panel/panel-lvds.c46
-rw-r--r--drivers/gpu/drm/panel/panel-simple.c90
-rw-r--r--drivers/gpu/imx/lcdif/lcdif-common.c2
17 files changed, 1026 insertions, 52 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 58db4bbc7ab8..4ef1bc1092fb 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -52,6 +52,14 @@ config DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW
to DP++. This is used with the i.MX6 imx-ldb
driver. You are likely to say N here.
+config DRM_LONTIUM_LT8912
+ tristate "Lontium LT8912 MIPI-DSI to LVDS and HDMI/MHL bridge"
+ depends on OF
+ select DRM_KMS_HELPER
+ select REGMAP_I2C
+ help
+ Lontium LT8912 MIPI-DSI to LVDS and HDMI/MHL bridge chip driver.
+
config DRM_NWL_DSI
tristate
select DRM_KMS_HELPER
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index d684dce13256..8119857c7ed4 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o
obj-$(CONFIG_DRM_LVDS_ENCODER) += lvds-encoder.o
obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
+obj-$(CONFIG_DRM_LONTIUM_LT8912) += lt8912.o
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
diff --git a/drivers/gpu/drm/bridge/dumb-vga-dac.c b/drivers/gpu/drm/bridge/dumb-vga-dac.c
index 2e6c61d9b8ea..499f8b1b20ab 100644
--- a/drivers/gpu/drm/bridge/dumb-vga-dac.c
+++ b/drivers/gpu/drm/bridge/dumb-vga-dac.c
@@ -82,6 +82,13 @@ dumb_vga_connector_detect(struct drm_connector *connector, bool force)
struct dumb_vga *vga = drm_connector_to_dumb_vga(connector);
/*
+ * If I2C bus for DDC is not defined, asume that the cable
+ * is always connected.
+ */
+ if (PTR_ERR(vga->ddc) == -ENODEV)
+ return connector_status_connected;
+
+ /*
* Even if we have an I2C bus, we can't assume that the cable
* is disconnected if drm_probe_ddc fails. Some cables don't
* wire the DDC pins, or the I2C bus might not be working at
diff --git a/drivers/gpu/drm/bridge/lt8912.c b/drivers/gpu/drm/bridge/lt8912.c
new file mode 100644
index 000000000000..3de3a0f8df4e
--- /dev/null
+++ b/drivers/gpu/drm/bridge/lt8912.c
@@ -0,0 +1,688 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018 Rockchip Electronics Co. Ltd.
+ * Copyright 2019 Toradex AG
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_of.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_mipi_dsi.h>
+
+struct lt8912 {
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+ struct drm_display_mode mode;
+ struct device *dev;
+ struct mipi_dsi_device *dsi;
+ struct device_node *host_node;
+ u8 num_dsi_lanes;
+ u8 channel_id;
+ unsigned int irq;
+ u8 sink_is_hdmi;
+ struct regmap *regmap[3];
+ struct gpio_desc *hpd_gpio;
+ struct gpio_desc *reset_n;
+ struct i2c_adapter *ddc; /* optional regular DDC I2C bus */
+};
+
+static int lt8912_attach_dsi(struct lt8912 *lt);
+
+static inline struct lt8912 *bridge_to_lt8912(struct drm_bridge *b)
+{
+ return container_of(b, struct lt8912, bridge);
+}
+
+static inline struct lt8912 *connector_to_lt8912(struct drm_connector *c)
+{
+ return container_of(c, struct lt8912, connector);
+}
+
+/* LT8912 MIPI to HDMI & LVDS REG setting - 20180115.txt */
+static void lt8912_init(struct lt8912 *lt)
+{
+ u8 lanes = lt->dsi->lanes;
+ const struct drm_display_mode *mode = &lt->mode;
+ u32 hactive, hfp, hsync, hbp, vfp, vsync, vbp, htotal, vtotal;
+ unsigned int hsync_activehigh, vsync_activehigh, reg;
+ unsigned int version[2];
+
+ dev_info(lt->dev, DRM_MODE_FMT "\n", DRM_MODE_ARG(mode));
+ /* TODO: lvds output init */
+
+ hactive = mode->hdisplay;
+ hfp = mode->hsync_start - mode->hdisplay;
+ hsync = mode->hsync_end - mode->hsync_start;
+ hsync_activehigh = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
+ hbp = mode->htotal - mode->hsync_end;
+ vfp = mode->vsync_start - mode->vdisplay;
+ vsync = mode->vsync_end - mode->vsync_start;
+ vsync_activehigh = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
+ vbp = mode->vtotal - mode->vsync_end;
+ htotal = mode->htotal;
+ vtotal = mode->vtotal;
+
+ regmap_read(lt->regmap[0], 0x00, &version[0]);
+ regmap_read(lt->regmap[0], 0x01, &version[1]);
+
+ dev_info(lt->dev, "LT8912 ID: %02x, %02x\n",
+ version[0], version[1]);
+
+ /* DigitalClockEn */
+ regmap_write(lt->regmap[0], 0x08, 0xff);
+ regmap_write(lt->regmap[0], 0x09, 0xff);
+ regmap_write(lt->regmap[0], 0x0a, 0xff);
+ regmap_write(lt->regmap[0], 0x0b, 0x7c);
+ regmap_write(lt->regmap[0], 0x0c, 0xff);
+
+ /* TxAnalog */
+ regmap_write(lt->regmap[0], 0x31, 0xa1);
+ regmap_write(lt->regmap[0], 0x32, 0xa1);
+ regmap_write(lt->regmap[0], 0x33, 0x03);
+ regmap_write(lt->regmap[0], 0x37, 0x00);
+ regmap_write(lt->regmap[0], 0x38, 0x22);
+ regmap_write(lt->regmap[0], 0x60, 0x82);
+
+ /* CbusAnalog */
+ regmap_write(lt->regmap[0], 0x39, 0x45);
+ regmap_write(lt->regmap[0], 0x3a, 0x00);
+ regmap_write(lt->regmap[0], 0x3b, 0x00);
+
+ /* HDMIPllAnalog */
+ regmap_write(lt->regmap[0], 0x44, 0x31);
+ regmap_write(lt->regmap[0], 0x55, 0x44);
+ regmap_write(lt->regmap[0], 0x57, 0x01);
+ regmap_write(lt->regmap[0], 0x5a, 0x02);
+
+ /* MIPIAnalog */
+ regmap_write(lt->regmap[0], 0x3e, 0xce);
+ regmap_write(lt->regmap[0], 0x3f, 0xd4);
+ regmap_write(lt->regmap[0], 0x41, 0x3c);
+
+ /* MipiBasicSet */
+ regmap_write(lt->regmap[1], 0x12, 0x04);
+ regmap_write(lt->regmap[1], 0x13, lanes % 4);
+ regmap_write(lt->regmap[1], 0x14, 0x00);
+
+ regmap_write(lt->regmap[1], 0x15, 0x00);
+ regmap_write(lt->regmap[1], 0x1a, 0x03);
+ regmap_write(lt->regmap[1], 0x1b, 0x03);
+
+ /* MIPIDig */
+ regmap_write(lt->regmap[1], 0x10, 0x01);
+ regmap_write(lt->regmap[1], 0x11, 0x0a);
+ regmap_write(lt->regmap[1], 0x18, hsync);
+ regmap_write(lt->regmap[1], 0x19, vsync);
+ regmap_write(lt->regmap[1], 0x1c, hactive % 0x100);
+ regmap_write(lt->regmap[1], 0x1d, hactive >> 8);
+
+ regmap_write(lt->regmap[1], 0x2f, 0x0c);
+
+ regmap_write(lt->regmap[1], 0x34, htotal % 0x100);
+ regmap_write(lt->regmap[1], 0x35, htotal >> 8);
+ regmap_write(lt->regmap[1], 0x36, vtotal % 0x100);
+ regmap_write(lt->regmap[1], 0x37, vtotal >> 8);
+ regmap_write(lt->regmap[1], 0x38, vbp % 0x100);
+ regmap_write(lt->regmap[1], 0x39, vbp >> 8);
+ regmap_write(lt->regmap[1], 0x3a, vfp % 0x100);
+ regmap_write(lt->regmap[1], 0x3b, vfp >> 8);
+ regmap_write(lt->regmap[1], 0x3c, hbp % 0x100);
+ regmap_write(lt->regmap[1], 0x3d, hbp >> 8);
+ regmap_write(lt->regmap[1], 0x3e, hfp % 0x100);
+ regmap_write(lt->regmap[1], 0x3f, hfp >> 8);
+ regmap_read(lt->regmap[0], 0xab, &reg);
+ reg &= 0xfc;
+ reg |= (hsync_activehigh < 1) | vsync_activehigh;
+ regmap_write(lt->regmap[0], 0xab, reg);
+
+ /* DDSConfig */
+ regmap_write(lt->regmap[1], 0x4e, 0x6a);
+ regmap_write(lt->regmap[1], 0x4f, 0xad);
+ regmap_write(lt->regmap[1], 0x50, 0xf3);
+ regmap_write(lt->regmap[1], 0x51, 0x80);
+
+ regmap_write(lt->regmap[1], 0x1f, 0x5e);
+ regmap_write(lt->regmap[1], 0x20, 0x01);
+ regmap_write(lt->regmap[1], 0x21, 0x2c);
+ regmap_write(lt->regmap[1], 0x22, 0x01);
+ regmap_write(lt->regmap[1], 0x23, 0xfa);
+ regmap_write(lt->regmap[1], 0x24, 0x00);
+ regmap_write(lt->regmap[1], 0x25, 0xc8);
+ regmap_write(lt->regmap[1], 0x26, 0x00);
+ regmap_write(lt->regmap[1], 0x27, 0x5e);
+ regmap_write(lt->regmap[1], 0x28, 0x01);
+ regmap_write(lt->regmap[1], 0x29, 0x2c);
+ regmap_write(lt->regmap[1], 0x2a, 0x01);
+ regmap_write(lt->regmap[1], 0x2b, 0xfa);
+ regmap_write(lt->regmap[1], 0x2c, 0x00);
+ regmap_write(lt->regmap[1], 0x2d, 0xc8);
+ regmap_write(lt->regmap[1], 0x2e, 0x00);
+ regmap_write(lt->regmap[1], 0x42, 0x64);
+ regmap_write(lt->regmap[1], 0x43, 0x00);
+ regmap_write(lt->regmap[1], 0x44, 0x04);
+ regmap_write(lt->regmap[1], 0x45, 0x00);
+ regmap_write(lt->regmap[1], 0x46, 0x59);
+ regmap_write(lt->regmap[1], 0x47, 0x00);
+ regmap_write(lt->regmap[1], 0x48, 0xf2);
+ regmap_write(lt->regmap[1], 0x49, 0x06);
+ regmap_write(lt->regmap[1], 0x4a, 0x00);
+ regmap_write(lt->regmap[1], 0x4b, 0x72);
+ regmap_write(lt->regmap[1], 0x4c, 0x45);
+ regmap_write(lt->regmap[1], 0x4d, 0x00);
+ regmap_write(lt->regmap[1], 0x52, 0x08);
+ regmap_write(lt->regmap[1], 0x53, 0x00);
+ regmap_write(lt->regmap[1], 0x54, 0xb2);
+ regmap_write(lt->regmap[1], 0x55, 0x00);
+ regmap_write(lt->regmap[1], 0x56, 0xe4);
+ regmap_write(lt->regmap[1], 0x57, 0x0d);
+ regmap_write(lt->regmap[1], 0x58, 0x00);
+ regmap_write(lt->regmap[1], 0x59, 0xe4);
+ regmap_write(lt->regmap[1], 0x5a, 0x8a);
+ regmap_write(lt->regmap[1], 0x5b, 0x00);
+ regmap_write(lt->regmap[1], 0x5c, 0x34);
+ regmap_write(lt->regmap[1], 0x1e, 0x4f);
+ regmap_write(lt->regmap[1], 0x51, 0x00);
+
+ regmap_write(lt->regmap[0], 0xb2, lt->sink_is_hdmi);
+
+ /* Audio Disable */
+ regmap_write(lt->regmap[2], 0x06, 0x00);
+ regmap_write(lt->regmap[2], 0x07, 0x00);
+
+ regmap_write(lt->regmap[2], 0x34, 0xd2);
+
+ regmap_write(lt->regmap[2], 0x3c, 0x41);
+
+ /* MIPIRxLogicRes */
+ regmap_write(lt->regmap[0], 0x03, 0x7f);
+ usleep_range(10000, 20000);
+ regmap_write(lt->regmap[0], 0x03, 0xff);
+
+ regmap_write(lt->regmap[1], 0x51, 0x80);
+ usleep_range(10000, 20000);
+ regmap_write(lt->regmap[1], 0x51, 0x00);
+}
+
+static void lt8912_wakeup(struct lt8912 *lt)
+{
+ gpiod_direction_output(lt->reset_n, 1);
+ msleep(120);
+ gpiod_direction_output(lt->reset_n, 0);
+
+ regmap_write(lt->regmap[0], 0x08,0xff); /* enable clk gating */
+ regmap_write(lt->regmap[0], 0x41,0x3c); /* MIPI Rx Power On */
+ regmap_write(lt->regmap[0], 0x05,0xfb); /* DDS logical reset */
+ regmap_write(lt->regmap[0], 0x05,0xff);
+ regmap_write(lt->regmap[0], 0x03,0x7f); /* MIPI RX logical reset */
+ usleep_range(10000, 20000);
+ regmap_write(lt->regmap[0], 0x03,0xff);
+ regmap_write(lt->regmap[0], 0x32,0xa1);
+ regmap_write(lt->regmap[0], 0x33,0x03);
+}
+
+static void lt8912_sleep(struct lt8912 *lt)
+{
+ regmap_write(lt->regmap[0], 0x32,0xa0);
+ regmap_write(lt->regmap[0], 0x33,0x00); /* Disable HDMI output. */
+ regmap_write(lt->regmap[0], 0x41,0x3d); /* MIPI Rx Power Down. */
+ regmap_write(lt->regmap[0], 0x08,0x00); /* diable DDS clk. */
+
+ gpiod_direction_output(lt->reset_n, 1);
+}
+
+static enum drm_connector_status
+lt8912_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct lt8912 *lt = connector_to_lt8912(connector);
+ enum drm_connector_status hpd, hpd_last;
+ int timeout = 0;
+
+ hpd = connector_status_unknown;
+ do {
+ hpd_last = hpd;
+ hpd = gpiod_get_value_cansleep(lt->hpd_gpio) ?
+ connector_status_connected : connector_status_disconnected;
+ msleep(20);
+ timeout += 20;
+ } while((hpd_last != hpd) && (timeout < 500));
+
+ return hpd;
+}
+
+static const struct drm_connector_funcs lt8912_connector_funcs = {
+ .detect = lt8912_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static irqreturn_t lt8912_hpd_irq_thread(int irq, void *arg)
+{
+ struct lt8912 *lt = arg;
+ struct drm_connector *connector = &lt->connector;
+
+ drm_helper_hpd_irq_event(connector->dev);
+
+ return IRQ_HANDLED;
+}
+
+static struct drm_encoder *
+lt8912_connector_best_encoder(struct drm_connector *connector)
+{
+ struct lt8912 *lt = connector_to_lt8912(connector);
+
+ return lt->bridge.encoder;
+}
+
+static int lt8912_connector_get_modes(struct drm_connector *connector)
+{
+ struct lt8912 *lt = connector_to_lt8912(connector);
+ struct edid *edid;
+ struct display_timings *timings;
+ u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ int i, ret, num_modes = 0;
+
+ /* Check if optional DDC I2C bus should be used. */
+ if (lt->ddc) {
+ edid = drm_get_edid(connector, lt->ddc);
+ if (edid) {
+ drm_mode_connector_update_edid_property(connector,
+ edid);
+ num_modes = drm_add_edid_modes(connector, edid);
+ lt->sink_is_hdmi = !!drm_detect_hdmi_monitor(edid);
+ kfree(edid);
+ }
+ if (num_modes == 0) {
+ dev_warn(lt->dev, "failed to get display timings from EDID\n");
+ return 0;
+ }
+ } else { /* if not EDID, use dtb timings */
+ timings = of_get_display_timings(lt->dev->of_node);
+
+ if (timings->num_timings == 0) {
+ dev_err(lt->dev, "failed to get display timings from dtb\n");
+ return 0;
+ }
+
+ for (i = 0; i < timings->num_timings; i++) {
+ struct drm_display_mode *mode;
+ struct videomode vm;
+
+ if (videomode_from_timings(timings, &vm, i)) {
+ continue;
+ }
+
+ mode = drm_mode_create(connector->dev);
+ drm_display_mode_from_videomode(&vm, mode);
+ mode->type = DRM_MODE_TYPE_DRIVER;
+
+ if (timings->native_mode == i)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(connector, mode);
+ num_modes++;
+ }
+ if (num_modes == 0) {
+ dev_err(lt->dev, "failed to get display modes from dtb\n");
+ return 0;
+ }
+ }
+
+ connector->display_info.bus_flags = DRM_BUS_FLAG_DE_LOW |
+ DRM_BUS_FLAG_PIXDATA_NEGEDGE;
+ ret = drm_display_info_set_bus_formats(&connector->display_info,
+ &bus_format, 1);
+
+ if (ret)
+ return ret;
+
+ return num_modes;
+}
+
+static enum drm_mode_status lt8912_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ if (mode->clock > 150000)
+ return MODE_CLOCK_HIGH;
+
+ if (mode->hdisplay > 1920)
+ return MODE_BAD_HVALUE;
+
+ if (mode->vdisplay > 1080)
+ return MODE_BAD_VVALUE;
+
+ return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs lt8912_connector_helper_funcs = {
+ .get_modes = lt8912_connector_get_modes,
+ .best_encoder = lt8912_connector_best_encoder,
+ .mode_valid = lt8912_connector_mode_valid,
+};
+
+static void lt8912_bridge_post_disable(struct drm_bridge *bridge)
+{
+ struct lt8912 *lt = bridge_to_lt8912(bridge);
+ lt8912_sleep(lt);
+}
+
+static void lt8912_bridge_enable(struct drm_bridge *bridge)
+{
+ struct lt8912 *lt = bridge_to_lt8912(bridge);
+ lt8912_init(lt);
+}
+
+static void lt8912_bridge_pre_enable(struct drm_bridge *bridge)
+{
+ struct lt8912 *lt = bridge_to_lt8912(bridge);
+ lt8912_wakeup(lt);
+}
+
+static void lt8912_bridge_mode_set(struct drm_bridge *bridge,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adj)
+{
+ struct lt8912 *lt = bridge_to_lt8912(bridge);
+
+ drm_mode_copy(&lt->mode, adj);
+}
+
+static int lt8912_bridge_attach(struct drm_bridge *bridge)
+{
+ struct lt8912 *lt = bridge_to_lt8912(bridge);
+ struct drm_connector *connector = &lt->connector;
+ int ret;
+
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+ ret = drm_connector_init(bridge->dev, connector,
+ &lt8912_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret) {
+ dev_err(lt->dev, "failed to initialize connector\n");
+ return ret;
+ }
+
+ drm_connector_helper_add(connector, &lt8912_connector_helper_funcs);
+ drm_mode_connector_attach_encoder(connector, bridge->encoder);
+
+ ret = lt8912_attach_dsi(lt);
+
+ enable_irq(lt->irq);
+
+ return ret;
+}
+
+static const struct drm_bridge_funcs lt8912_bridge_funcs = {
+ .attach = lt8912_bridge_attach,
+ .mode_set = lt8912_bridge_mode_set,
+ .pre_enable = lt8912_bridge_pre_enable,
+ .enable = lt8912_bridge_enable,
+ .post_disable = lt8912_bridge_post_disable,
+};
+
+static const struct regmap_config lt8912_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+};
+
+static int lt8912_i2c_init(struct lt8912 *lt,
+ struct i2c_client *client)
+{
+ struct i2c_board_info info[] = {
+ { I2C_BOARD_INFO("lt8912p0", 0x48), },
+ { I2C_BOARD_INFO("lt8912p1", 0x49), },
+ { I2C_BOARD_INFO("lt8912p2", 0x4a), }
+ };
+ struct regmap *regmap;
+ unsigned int i;
+ int ret;
+
+ if (!lt || !client)
+ return -ENODEV;
+
+ for (i = 0; i < ARRAY_SIZE(info); i++) {
+ if (i > 0 ) {
+ client = i2c_new_dummy(client->adapter, info[i].addr);
+ if (!client)
+ return -ENODEV;
+ }
+ regmap = devm_regmap_init_i2c(client, &lt8912_regmap_config);
+ if (IS_ERR(regmap)) {
+ ret = PTR_ERR(regmap);
+ dev_err(lt->dev,
+ "Failed to initialize regmap: %d\n", ret);
+ return ret;
+ }
+
+ lt->regmap[i] = regmap;
+ }
+
+ return 0;
+}
+
+int lt8912_attach_dsi(struct lt8912 *lt)
+{
+ struct device *dev = lt->dev;
+ struct mipi_dsi_host *host;
+ struct mipi_dsi_device *dsi;
+ int ret = 0;
+ const struct mipi_dsi_device_info info = { .type = "lt8912",
+ .channel = lt->channel_id,
+ .node = NULL,
+ };
+
+ host = of_find_mipi_dsi_host_by_node(lt->host_node);
+ if (!host) {
+ dev_err(dev, "failed to find dsi host\n");
+ return -EPROBE_DEFER;
+ }
+
+ dsi = mipi_dsi_device_register_full(host, &info);
+ if (IS_ERR(dsi)) {
+ dev_err(dev, "failed to create dsi device\n");
+ ret = PTR_ERR(dsi);
+ goto err_dsi_device;
+ }
+
+ lt->dsi = dsi;
+
+ dsi->lanes = lt->num_dsi_lanes;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_EOT_PACKET;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "failed to attach dsi to host\n");
+ goto err_dsi_attach;
+ }
+
+ return 0;
+
+err_dsi_attach:
+ mipi_dsi_device_unregister(dsi);
+err_dsi_device:
+ return ret;
+}
+
+void lt8912_detach_dsi(struct lt8912 *lt)
+{
+ mipi_dsi_detach(lt->dsi);
+ mipi_dsi_device_unregister(lt->dsi);
+}
+
+
+static int lt8912_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
+{
+ struct device *dev = &i2c->dev;
+ struct lt8912 *lt;
+ struct device_node *ddc_phandle;
+ struct device_node *endpoint;
+ unsigned int irq_flags;
+ int ret;
+
+ static int initialize_it = 1;
+
+ if(!initialize_it) {
+ initialize_it = 1;
+ return -EPROBE_DEFER;
+ }
+
+ lt = devm_kzalloc(dev, sizeof(*lt), GFP_KERNEL);
+ if (!lt)
+ return -ENOMEM;
+
+ lt->dev = dev;
+
+ /* get optional regular DDC I2C bus */
+ ddc_phandle = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0);
+ if (ddc_phandle) {
+ lt->ddc = of_get_i2c_adapter_by_node(ddc_phandle);
+ if (!(lt->ddc))
+ ret = -EPROBE_DEFER;
+ of_node_put(ddc_phandle);
+ }
+
+ lt->hpd_gpio = devm_gpiod_get(dev, "hpd", GPIOD_IN);
+ if (IS_ERR(lt->hpd_gpio)) {
+ dev_err(dev, "failed to get hpd gpio\n");
+ return ret;
+ }
+
+ lt->irq = gpiod_to_irq(lt->hpd_gpio);
+ if (lt->irq == -ENXIO) {
+ dev_err(dev, "failed to get hpd irq\n");
+ return -ENODEV;
+ }
+ if (lt->irq < 0) {
+ dev_err(dev, "failed to get hpd irq, %i\n", lt->irq);
+ return lt->irq;
+ }
+ irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+ ret = devm_request_threaded_irq(dev, lt->irq,
+ NULL,
+ lt8912_hpd_irq_thread,
+ irq_flags, "lt8912_hpd", lt);
+ if (ret) {
+ dev_err(dev, "failed to request irq\n");
+ return -ENODEV;
+ }
+
+ disable_irq(lt->irq);
+
+ lt->reset_n = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS);
+ if (IS_ERR(lt->reset_n)) {
+ ret = PTR_ERR(lt->reset_n);
+ dev_err(dev, "failed to request reset GPIO: %d\n", ret);
+ return ret;
+ }
+
+ ret = lt8912_i2c_init(lt, i2c);
+ if (ret)
+ return ret;
+
+ /* TODO: interrupt handing */
+
+ lt->num_dsi_lanes = 4;
+ lt->channel_id = 1;
+
+ endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
+ if (!endpoint)
+ return -ENODEV;
+
+ lt->host_node = of_graph_get_remote_port_parent(endpoint);
+ if (!lt->host_node) {
+ of_node_put(endpoint);
+ return -ENODEV;
+ }
+
+ of_node_put(endpoint);
+ of_node_put(lt->host_node);
+
+ lt->bridge.funcs = &lt8912_bridge_funcs;
+ lt->bridge.of_node = dev->of_node;
+ ret = drm_bridge_add(&lt->bridge);
+ if (ret) {
+ dev_err(dev, "failed to add bridge: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt8912_remove(struct i2c_client *i2c)
+{
+ struct lt8912 *lt = i2c_get_clientdata(i2c);
+
+ lt8912_sleep(lt);
+ mipi_dsi_detach(lt->dsi);
+ drm_bridge_remove(&lt->bridge);
+
+ return 0;
+}
+
+static const struct i2c_device_id lt8912_i2c_ids[] = {
+ { "lt8912", 0 },
+ { }
+};
+
+static const struct of_device_id lt8912_of_match[] = {
+ { .compatible = "lontium,lt8912" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, lt8912_of_match);
+
+static struct mipi_dsi_driver lt8912_driver = {
+ .driver.name = "lt8912",
+};
+
+static struct i2c_driver lt8912_i2c_driver = {
+ .driver = {
+ .name = "lt8912",
+ .of_match_table = lt8912_of_match,
+ },
+ .id_table = lt8912_i2c_ids,
+ .probe = lt8912_probe,
+ .remove = lt8912_remove,
+};
+
+static int __init lt8912_i2c_drv_init(void)
+{
+ mipi_dsi_driver_register(&lt8912_driver);
+
+ return i2c_add_driver(&lt8912_i2c_driver);
+}
+module_init(lt8912_i2c_drv_init);
+
+static void __exit lt8912_i2c_exit(void)
+{
+ i2c_del_driver(&lt8912_i2c_driver);
+
+ mipi_dsi_driver_unregister(&lt8912_driver);
+}
+module_exit(lt8912_i2c_exit);
+
+MODULE_AUTHOR("Wyon Bi <bivvy.bi@rock-chips.com>");
+MODULE_DESCRIPTION("Lontium LT8912 MIPI-DSI to LVDS and HDMI/MHL bridge");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c
index 403228eaddce..6b2fbc66ab68 100644
--- a/drivers/gpu/drm/bridge/nwl-dsi.c
+++ b/drivers/gpu/drm/bridge/nwl-dsi.c
@@ -1165,7 +1165,7 @@ static int nwl_dsi_bridge_attach(struct drm_bridge *bridge)
if (dsi->panel)
ret = nwl_dsi_create_connector(encoder->dev, dsi);
else if (!dsi->next_bridge)
- ret = -ENODEV;
+ ret = -EPROBE_DEFER;
return ret;
}
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 2147a602ecae..068cb0122c82 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -1216,7 +1216,7 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device *dev,
return;
for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
- if (!new_crtc_state->active || !new_crtc_state->planes_changed)
+ if (!new_crtc_state->active)
continue;
ret = drm_crtc_vblank_get(crtc);
diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c
index 3e3035c9e96b..333c3c4e8d4f 100644
--- a/drivers/gpu/drm/drm_modes.c
+++ b/drivers/gpu/drm/drm_modes.c
@@ -659,10 +659,12 @@ EXPORT_SYMBOL_GPL(drm_display_mode_to_videomode);
* drm_bus_flags_from_videomode - extract information about pixelclk and
* DE polarity from videomode and store it in a separate variable
* @vm: videomode structure to use
- * @bus_flags: information about pixelclk and DE polarity will be stored here
+ * @bus_flags: information about pixelclk, sync and DE polarity will be stored
+ * here
*
- * Sets DRM_BUS_FLAG_DE_(LOW|HIGH) and DRM_BUS_FLAG_PIXDATA_(POS|NEG)EDGE
- * in @bus_flags according to DISPLAY_FLAGS found in @vm
+ * Sets DRM_BUS_FLAG_DE_(LOW|HIGH), DRM_BUS_FLAG_PIXDATA_(POS|NEG)EDGE and
+ * DISPLAY_FLAGS_SYNC_(POS|NEG)EDGE in @bus_flags according to DISPLAY_FLAGS
+ * found in @vm
*/
void drm_bus_flags_from_videomode(const struct videomode *vm, u32 *bus_flags)
{
@@ -672,6 +674,11 @@ void drm_bus_flags_from_videomode(const struct videomode *vm, u32 *bus_flags)
if (vm->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
*bus_flags |= DRM_BUS_FLAG_PIXDATA_NEGEDGE;
+ if (vm->flags & DISPLAY_FLAGS_SYNC_POSEDGE)
+ *bus_flags |= DRM_BUS_FLAG_SYNC_POSEDGE;
+ if (vm->flags & DISPLAY_FLAGS_SYNC_NEGEDGE)
+ *bus_flags |= DRM_BUS_FLAG_SYNC_NEGEDGE;
+
if (vm->flags & DISPLAY_FLAGS_DE_LOW)
*bus_flags |= DRM_BUS_FLAG_DE_LOW;
if (vm->flags & DISPLAY_FLAGS_DE_HIGH)
@@ -684,7 +691,7 @@ EXPORT_SYMBOL_GPL(drm_bus_flags_from_videomode);
* of_get_drm_display_mode - get a drm_display_mode from devicetree
* @np: device_node with the timing specification
* @dmode: will be set to the return value
- * @bus_flags: information about pixelclk and DE polarity
+ * @bus_flags: information about pixelclk, sync and DE polarity
* @index: index into the list of display timings in devicetree
*
* This function is expensive and should only be used, if only one mode is to be
diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c
index c2f23a68da7f..e7f9fd5b479c 100644
--- a/drivers/gpu/drm/drm_vblank.c
+++ b/drivers/gpu/drm/drm_vblank.c
@@ -113,13 +113,20 @@ static void store_vblank(struct drm_device *dev, unsigned int pipe,
write_sequnlock(&vblank->seqlock);
}
+static u32 drm_max_vblank_count(struct drm_device *dev, unsigned int pipe)
+{
+ struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
+
+ return vblank->max_vblank_count ?: dev->max_vblank_count;
+}
+
/*
* "No hw counter" fallback implementation of .get_vblank_counter() hook,
* if there is no useable hardware frame counter available.
*/
static u32 drm_vblank_no_hw_counter(struct drm_device *dev, unsigned int pipe)
{
- WARN_ON_ONCE(dev->max_vblank_count != 0);
+ WARN_ON_ONCE(drm_max_vblank_count(dev, pipe) != 0);
return 0;
}
@@ -203,6 +210,7 @@ static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe,
struct timeval t_vblank;
int count = DRM_TIMESTAMP_MAXRETRIES;
int framedur_ns = vblank->framedur_ns;
+ u32 max_vblank_count = drm_max_vblank_count(dev, pipe);
/*
* Interrupts were disabled prior to this call, so deal with counter
@@ -221,9 +229,9 @@ static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe,
rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, in_vblank_irq);
} while (cur_vblank != __get_vblank_counter(dev, pipe) && --count > 0);
- if (dev->max_vblank_count != 0) {
+ if (max_vblank_count) {
/* trust the hw counter when it's around */
- diff = (cur_vblank - vblank->last) & dev->max_vblank_count;
+ diff = (cur_vblank - vblank->last) & max_vblank_count;
} else if (rc && framedur_ns) {
const struct timeval *t_old;
u64 diff_ns;
@@ -1186,6 +1194,37 @@ void drm_crtc_vblank_reset(struct drm_crtc *crtc)
EXPORT_SYMBOL(drm_crtc_vblank_reset);
/**
+ * drm_crtc_set_max_vblank_count - configure the hw max vblank counter value
+ * @crtc: CRTC in question
+ * @max_vblank_count: max hardware vblank counter value
+ *
+ * Update the maximum hardware vblank counter value for @crtc
+ * at runtime. Useful for hardware where the operation of the
+ * hardware vblank counter depends on the currently active
+ * display configuration.
+ *
+ * For example, if the hardware vblank counter does not work
+ * when a specific connector is active the maximum can be set
+ * to zero. And when that specific connector isn't active the
+ * maximum can again be set to the appropriate non-zero value.
+ *
+ * If used, must be called before drm_vblank_on().
+ */
+void drm_crtc_set_max_vblank_count(struct drm_crtc *crtc,
+ u32 max_vblank_count)
+{
+ struct drm_device *dev = crtc->dev;
+ unsigned int pipe = drm_crtc_index(crtc);
+ struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
+
+ WARN_ON(dev->max_vblank_count);
+ WARN_ON(!READ_ONCE(vblank->inmodeset));
+
+ vblank->max_vblank_count = max_vblank_count;
+}
+EXPORT_SYMBOL(drm_crtc_set_max_vblank_count);
+
+/**
* drm_crtc_vblank_on - enable vblank events on a CRTC
* @crtc: CRTC in question
*
diff --git a/drivers/gpu/drm/imx/hdp/imx-hdp.c b/drivers/gpu/drm/imx/hdp/imx-hdp.c
index bcfad522df43..0fd33ce1f047 100644
--- a/drivers/gpu/drm/imx/hdp/imx-hdp.c
+++ b/drivers/gpu/drm/imx/hdp/imx-hdp.c
@@ -21,6 +21,7 @@
#include <linux/of.h>
#include <linux/irq.h>
#include <linux/of_device.h>
+#include <linux/of_gpio.h>
#include "imx-hdp.h"
#include "imx-hdmi.h"
@@ -32,7 +33,9 @@
#define B0_SILICON_ID 0x11
struct drm_display_mode *g_mode;
-uint8_t g_default_mode = 3;
+uint8_t g_default_mode = 2;
+static const uint8_t g_4kp60_mode = 3;
+
static struct drm_display_mode edid_cea_modes[] = {
/* 3 - 720x480@60Hz */
{ DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736,
@@ -833,10 +836,17 @@ imx_hdp_connector_detect(struct drm_connector *connector, bool force)
struct imx_hdp, connector);
int ret;
u8 hpd = 0xf;
+ struct edid *edid;
ret = imx_hdp_call(hdp, get_hpd_state, &hdp->state, &hpd);
- if (ret > 0)
- return connector_status_unknown;
+ if (ret > 0) {
+ /* Check if optional regular DDC I2C bus should be used. */
+ if (hdp->ddc) {
+ edid = drm_get_edid(connector, hdp->ddc);
+ if (drm_edid_is_valid(edid))
+ hpd = 1;
+ }
+ }
if (hpd == 1)
/* Cable Connected */
@@ -846,7 +856,7 @@ imx_hdp_connector_detect(struct drm_connector *connector, bool force)
return connector_status_disconnected;
else {
/* Cable status unknown */
- DRM_INFO("Unknow cable status, hdp=%u\n", hpd);
+ DRM_INFO("Unknow cable status, hpd=%u\n", hpd);
return connector_status_unknown;
}
}
@@ -861,7 +871,9 @@ static int imx_hdp_default_video_modes(struct drm_connector *connector)
if (!mode)
return -EINVAL;
drm_mode_copy(mode, &edid_cea_modes[i]);
- mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ mode->type |= DRM_MODE_TYPE_DRIVER;
+ if(i == g_default_mode)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
}
return i;
@@ -875,8 +887,16 @@ static int imx_hdp_connector_get_modes(struct drm_connector *connector)
int num_modes = 0;
if (!hdp->no_edid) {
- edid = drm_do_get_edid(connector, hdp->ops->get_edid_block,
- &hdp->state);
+ /*
+ * Check if optional regular DDC I2C bus should be used.
+ * Fall-back to using IP/firmware integrated one.
+ */
+ if (hdp->ddc)
+ edid = drm_get_edid(connector, hdp->ddc);
+ else
+ edid = drm_do_get_edid(connector,
+ hdp->ops->get_edid_block,
+ &hdp->state);
if (edid) {
dev_info(hdp->dev, "%x,%x,%x,%x,%x,%x,%x,%x\n",
edid->header[0], edid->header[1],
@@ -1320,6 +1340,7 @@ static struct hdp_ops imx8qm_hdmi_ops = {
};
static struct hdp_devtype imx8qm_dp_devtype = {
+ .is_hdmi_level = false,
.audio_type = CDN_DPTX,
.ops = &imx8qm_dp_ops,
.rw = &imx8qm_rw,
@@ -1327,6 +1348,7 @@ static struct hdp_devtype imx8qm_dp_devtype = {
};
static struct hdp_devtype imx8qm_hdmi_devtype = {
+ .is_hdmi_level = true,
.audio_type = CDN_HDMITX_TYPHOON,
.ops = &imx8qm_hdmi_ops,
.rw = &imx8qm_rw,
@@ -1353,6 +1375,7 @@ static struct hdp_ops imx8mq_ops = {
};
static struct hdp_devtype imx8mq_hdmi_devtype = {
+ .is_hdmi_level = true,
.audio_type = CDN_HDMITX_KIRAN,
.ops = &imx8mq_ops,
.rw = &imx8mq_rw,
@@ -1399,7 +1422,7 @@ static void hotplug_work_func(struct work_struct *work)
/* For HDMI2.0 SCDC should setup again.
* So recovery pre video mode if it is 4Kp60 */
if (drm_mode_equal(&hdp->video.pre_mode,
- &edid_cea_modes[g_default_mode]))
+ &edid_cea_modes[g_4kp60_mode]))
imx_hdp_mode_setup(hdp, &hdp->video.pre_mode);
DRM_INFO("HDMI/DP Cable Plug In\n");
enable_irq(hdp->irq[HPD_IRQ_OUT]);
@@ -1428,6 +1451,7 @@ static int imx_hdp_imx_bind(struct device *dev, struct device *master,
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = data;
struct imx_hdp *hdp;
+ struct device_node *ddc_phandle;
const struct of_device_id *of_id =
of_match_device(imx_hdp_dt_ids, dev);
const struct hdp_devtype *devtype = of_id->data;
@@ -1495,6 +1519,17 @@ static int imx_hdp_imx_bind(struct device *dev, struct device *master,
if (cpu_is_imx8qm() && (imx8_get_soc_revision() < B0_SILICON_ID))
hdp->no_edid = true;
+ /* get optional regular DDC I2C bus */
+ ddc_phandle = of_parse_phandle(pdev->dev.of_node, "ddc-i2c-bus", 0);
+ if (ddc_phandle) {
+ hdp->ddc = of_get_i2c_adapter_by_node(ddc_phandle);
+ if (hdp->ddc)
+ dev_info(dev, "Connector's ddc i2c bus found\n");
+ else
+ ret = -EPROBE_DEFER;
+ of_node_put(ddc_phandle);
+ }
+
if (devtype->connector_type == DRM_MODE_CONNECTOR_DisplayPort) {
hdp->is_dp = true;
hdp->is_edp = of_property_read_bool(pdev->dev.of_node, "fsl,edp");
@@ -1634,6 +1669,18 @@ static int imx_hdp_imx_bind(struct device *dev, struct device *master,
INIT_DELAYED_WORK(&hdp->hotplug_work, hotplug_work_func);
+ hdp->hdmi_ctrl_gpio = of_get_named_gpio(dev->of_node, "hdmi-ctrl-gpios", 0);
+ if (gpio_is_valid(hdp->hdmi_ctrl_gpio)) {
+ ret = gpio_request(hdp->hdmi_ctrl_gpio, "HDMI_CTRL");
+ if (ret < 0) {
+ dev_err(dev, "request HDMI CTRL GPIO failed: %d\n", ret);
+ goto err_cleanup_encoder;
+ }
+
+ /* Set signals depending on HDP device type */
+ gpio_direction_output(hdp->hdmi_ctrl_gpio, devtype->is_hdmi_level);
+ }
+
/* Check cable states before enable irq */
imx_hdp_call(hdp, get_hpd_state, &hdp->state, &hpd);
@@ -1647,7 +1694,7 @@ static int imx_hdp_imx_bind(struct device *dev, struct device *master,
if (ret) {
dev_err(&pdev->dev, "can't claim irq %d\n",
hdp->irq[HPD_IRQ_IN]);
- goto err_irq;
+ goto err_free_hdmi_gpio;
}
/* Cable Disconnedted, enable Plug in IRQ */
if (hpd == 0)
@@ -1662,7 +1709,7 @@ static int imx_hdp_imx_bind(struct device *dev, struct device *master,
if (ret) {
dev_err(&pdev->dev, "can't claim irq %d\n",
hdp->irq[HPD_IRQ_OUT]);
- goto err_irq;
+ goto err_free_hdmi_gpio;
}
/* Cable Connected, enable Plug out IRQ */
if (hpd == 1)
@@ -1678,7 +1725,11 @@ static int imx_hdp_imx_bind(struct device *dev, struct device *master,
imx_hdp_register_audio_driver(dev);
return 0;
-err_irq:
+
+err_free_hdmi_gpio:
+ if (gpio_is_valid(hdp->hdmi_ctrl_gpio))
+ gpio_free(hdp->hdmi_ctrl_gpio);
+err_cleanup_encoder:
drm_encoder_cleanup(encoder);
return ret;
}
@@ -1693,6 +1744,9 @@ static void imx_hdp_imx_unbind(struct device *dev, struct device *master,
imx_cec_unregister(&hdp->cec);
#endif
imx_hdp_call(hdp, pixel_clock_disable, &hdp->clks);
+
+ if (gpio_is_valid(hdp->hdmi_ctrl_gpio))
+ gpio_free(hdp->hdmi_ctrl_gpio);
}
static const struct component_ops imx_hdp_imx_ops = {
diff --git a/drivers/gpu/drm/imx/hdp/imx-hdp.h b/drivers/gpu/drm/imx/hdp/imx-hdp.h
index 52678a0bc0e3..bec765b4c119 100644
--- a/drivers/gpu/drm/imx/hdp/imx-hdp.h
+++ b/drivers/gpu/drm/imx/hdp/imx-hdp.h
@@ -128,6 +128,7 @@ struct hdp_ops {
};
struct hdp_devtype {
+ u8 is_hdmi_level;
u8 audio_type;
struct hdp_ops *ops;
struct hdp_rw_func *rw;
@@ -214,6 +215,7 @@ struct imx_hdp {
struct edid *edid;
char cable_state;
+ int hdmi_ctrl_gpio;
struct hdp_mem mem;
struct imx_hdcp hdcp;
@@ -261,6 +263,8 @@ struct imx_hdp {
VIC_PXL_ENCODING_FORMAT format;
bool hdr_metadata_present;
bool hdr_mode;
+
+ struct i2c_adapter *ddc; /* optional regular DDC I2C bus */
};
void imx_hdp_register_audio_driver(struct device *dev);
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_crtc.c b/drivers/gpu/drm/mxsfb/mxsfb_crtc.c
index 94a6d16d469c..6c9a5efb3ab8 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_crtc.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_crtc.c
@@ -158,9 +158,9 @@ err:
return -EINVAL;
}
-static u32 get_bus_format_from_bpp(u32 bpp)
+static u32 get_bus_format_from_width(u32 width)
{
- switch (bpp) {
+ switch (width) {
case 16:
return MEDIA_BUS_FMT_RGB565_1X16;
case 18:
@@ -177,7 +177,7 @@ static void mxsfb_set_bus_fmt(struct mxsfb_drm_private *mxsfb)
struct drm_crtc *crtc = &mxsfb->pipe.crtc;
unsigned int bits_per_pixel = crtc->primary->state->fb->format->depth;
struct drm_device *drm = crtc->dev;
- u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ u32 bus_format = get_bus_format_from_width(mxsfb->bus_width);
int num_bus_formats = mxsfb->connector->display_info.num_bus_formats;
const u32 *bus_formats = mxsfb->connector->display_info.bus_formats;
u32 reg = 0;
@@ -185,7 +185,7 @@ static void mxsfb_set_bus_fmt(struct mxsfb_drm_private *mxsfb)
/* match the user requested bus_format to one supported by the panel */
if (num_bus_formats) {
- u32 user_bus_format = get_bus_format_from_bpp(bits_per_pixel);
+ u32 user_bus_format = get_bus_format_from_width(bits_per_pixel);
bus_format = bus_formats[0];
for (i = 0; i < num_bus_formats; i++) {
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
index 24bb09fdc5fd..183c3652100b 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
@@ -44,6 +44,27 @@
#include "mxsfb_drv.h"
#include "mxsfb_regs.h"
+/*
+ * There are non-atomic versions of clk_enable()/clk_disable() callbacks
+ * used in IMX8QM/IMX8QXP, so we can't manage axi clk in interrupt handlers
+ */
+#if defined(CONFIG_ARCH_FSL_IMX8QM) || defined(CONFIG_ARCH_FSL_IMX8QXP)
+# define mxsfb_enable_axi_clk(mxsfb)
+# define mxsfb_disable_axi_clk(mxsfb)
+#else
+static inline void mxsfb_enable_axi_clk(struct mxsfb_drm_private *mxsfb)
+{
+ if (mxsfb->clk_axi)
+ clk_prepare_enable(mxsfb->clk_axi);
+}
+
+static inline void mxsfb_disable_axi_clk(struct mxsfb_drm_private *mxsfb)
+{
+ if (mxsfb->clk_axi)
+ clk_disable_unprepare(mxsfb->clk_axi);
+}
+#endif
+
/* The eLCDIF max possible CRTCs */
#define MAX_CRTCS 1
@@ -56,6 +77,9 @@ enum mxsfb_devtype {
MXSFB_V4,
};
+/* default output bus width */
+#define MXSFB_DEFAULT_BUS_WIDTH 24
+
/*
* When adding new formats, make sure to update the num_formats from
* mxsfb_devdata below.
@@ -336,9 +360,17 @@ static void mxsfb_pipe_enable(struct drm_simple_display_pipe *pipe,
drm_crtc_vblank_on(&mxsfb->pipe.crtc);
pm_runtime_get_sync(drm->dev);
- drm_panel_prepare(mxsfb->panel);
- mxsfb_crtc_enable(mxsfb);
- drm_panel_enable(mxsfb->panel);
+ if (mxsfb->panel) {
+ drm_panel_prepare(mxsfb->panel);
+ mxsfb_crtc_enable(mxsfb);
+ drm_panel_enable(mxsfb->panel);
+ }
+
+ if (mxsfb->bridge) {
+ drm_bridge_pre_enable(mxsfb->bridge);
+ mxsfb_crtc_enable(mxsfb);
+ drm_bridge_enable(mxsfb->bridge);
+ }
}
static void mxsfb_pipe_disable(struct drm_simple_display_pipe *pipe)
@@ -348,9 +380,18 @@ static void mxsfb_pipe_disable(struct drm_simple_display_pipe *pipe)
struct drm_crtc *crtc = &pipe->crtc;
struct drm_pending_vblank_event *event;
- drm_panel_disable(mxsfb->panel);
- mxsfb_crtc_disable(mxsfb);
- drm_panel_unprepare(mxsfb->panel);
+ if (mxsfb->bridge) {
+ drm_bridge_disable(mxsfb->bridge);
+ mxsfb_crtc_disable(mxsfb);
+ drm_bridge_post_disable(mxsfb->bridge);
+ }
+
+ if (mxsfb->panel) {
+ drm_panel_disable(mxsfb->panel);
+ mxsfb_crtc_disable(mxsfb);
+ drm_panel_unprepare(mxsfb->panel);
+ }
+
pm_runtime_put_sync(drm->dev);
spin_lock_irq(&drm->event_lock);
@@ -398,6 +439,7 @@ static int mxsfb_load(struct drm_device *drm, unsigned long flags)
struct mxsfb_drm_private *mxsfb;
struct resource *res;
u32 max_res[2] = {0, 0};
+ u32 bus_width = MXSFB_DEFAULT_BUS_WIDTH;
int ret;
mxsfb = devm_kzalloc(&pdev->dev, sizeof(*mxsfb), GFP_KERNEL);
@@ -493,6 +535,10 @@ static int mxsfb_load(struct drm_device *drm, unsigned long flags)
}
}
+ /* bus width is needed to set up correct bus format */
+ of_property_read_u32(drm->dev->of_node, "bus-width", &bus_width);
+ mxsfb->bus_width = bus_width;
+
of_property_read_u32_array(drm->dev->of_node, "max-res",
&max_res[0], 2);
if (!max_res[0])
@@ -531,13 +577,15 @@ static int mxsfb_load(struct drm_device *drm, unsigned long flags)
drm_helper_hpd_irq_event(drm);
pm_runtime_enable(drm->dev);
+ pm_runtime_get_sync(drm->dev);
return 0;
err_cma:
drm_irq_uninstall(drm);
err_irq:
- drm_panel_detach(mxsfb->panel);
+ if (mxsfb->panel)
+ drm_panel_detach(mxsfb->panel);
return ret;
}
@@ -566,6 +614,7 @@ static void mxsfb_unload(struct drm_device *drm)
drm->dev_private = NULL;
+ pm_runtime_put_sync(drm->dev);
pm_runtime_disable(drm->dev);
}
@@ -581,14 +630,13 @@ static int mxsfb_enable_vblank(struct drm_device *drm, unsigned int crtc)
struct mxsfb_drm_private *mxsfb = drm->dev_private;
int ret = 0;
- ret = clk_prepare_enable(mxsfb->clk_axi);
- if (ret)
- return ret;
+ mxsfb_enable_axi_clk(mxsfb);
/* Clear and enable VBLANK IRQ */
writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR);
writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, mxsfb->base + LCDC_CTRL1 + REG_SET);
- clk_disable_unprepare(mxsfb->clk_axi);
+
+ mxsfb_disable_axi_clk(mxsfb);
return ret;
}
@@ -597,13 +645,13 @@ static void mxsfb_disable_vblank(struct drm_device *drm, unsigned int crtc)
{
struct mxsfb_drm_private *mxsfb = drm->dev_private;
- if (clk_prepare_enable(mxsfb->clk_axi))
- return;
+ mxsfb_enable_axi_clk(mxsfb);
/* Disable and clear VBLANK IRQ */
writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, mxsfb->base + LCDC_CTRL1 + REG_CLR);
writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR);
- clk_disable_unprepare(mxsfb->clk_axi);
+
+ mxsfb_disable_axi_clk(mxsfb);
}
static void mxsfb_irq_preinstall(struct drm_device *drm)
@@ -617,7 +665,7 @@ static irqreturn_t mxsfb_irq_handler(int irq, void *data)
struct mxsfb_drm_private *mxsfb = drm->dev_private;
u32 reg;
- clk_prepare_enable(mxsfb->clk_axi);
+ mxsfb_enable_axi_clk(mxsfb);
reg = readl(mxsfb->base + LCDC_CTRL1);
@@ -626,7 +674,7 @@ static irqreturn_t mxsfb_irq_handler(int irq, void *data)
writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR);
- clk_disable_unprepare(mxsfb->clk_axi);
+ mxsfb_disable_axi_clk(mxsfb);
return IRQ_HANDLED;
}
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.h b/drivers/gpu/drm/mxsfb/mxsfb_drv.h
index 24d573fe5aee..ead101bc4e17 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.h
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.h
@@ -56,6 +56,8 @@ struct mxsfb_drm_private {
struct drm_bridge *bridge;
struct drm_fbdev_cma *fbdev;
+ u32 bus_width;
+
struct list_head valid_modes;
};
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_out.c b/drivers/gpu/drm/mxsfb/mxsfb_out.c
index 044d5fbfbce5..66b1095f310b 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_out.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_out.c
@@ -100,7 +100,7 @@ int mxsfb_create_output(struct drm_device *drm)
&mxsfb_panel_connector_helper_funcs);
ret = drm_connector_init(drm, mxsfb->connector,
&mxsfb_panel_connector_funcs,
- DRM_MODE_CONNECTOR_Unknown);
+ DRM_MODE_CONNECTOR_DPI);
}
return ret;
diff --git a/drivers/gpu/drm/panel/panel-lvds.c b/drivers/gpu/drm/panel/panel-lvds.c
index e2d57c01200b..e18770be2070 100644
--- a/drivers/gpu/drm/panel/panel-lvds.c
+++ b/drivers/gpu/drm/panel/panel-lvds.c
@@ -27,6 +27,11 @@
#include <video/of_display_timing.h>
#include <video/videomode.h>
+enum panel_type {
+ PANEL_LVDS,
+ PANEL_DPI
+};
+
struct panel_lvds {
struct drm_panel panel;
struct device *dev;
@@ -113,7 +118,9 @@ static int panel_lvds_get_modes(struct drm_panel *panel)
connector->display_info.height_mm = lvds->height;
drm_display_info_set_bus_formats(&connector->display_info,
&lvds->bus_format, 1);
- connector->display_info.bus_flags = lvds->data_mirror
+ drm_bus_flags_from_videomode(&lvds->video_mode,
+ &connector->display_info.bus_flags);
+ connector->display_info.bus_flags |= lvds->data_mirror
? DRM_BUS_FLAG_DATA_LSB_TO_MSB
: DRM_BUS_FLAG_DATA_MSB_TO_LSB;
@@ -134,6 +141,7 @@ static int panel_lvds_parse_dt(struct panel_lvds *lvds)
struct display_timing timing;
const char *mapping;
int ret;
+ enum panel_type type;
ret = of_get_display_timing(np, "panel-timing", &timing);
if (ret < 0)
@@ -163,13 +171,30 @@ static int panel_lvds_parse_dt(struct panel_lvds *lvds)
return -ENODEV;
}
- if (!strcmp(mapping, "jeida-18")) {
- lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
- } else if (!strcmp(mapping, "jeida-24")) {
- lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
- } else if (!strcmp(mapping, "vesa-24")) {
- lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
- } else {
+ type = (enum panel_type)of_device_get_match_data(lvds->dev);
+ switch (type) {
+ case PANEL_LVDS:
+ if (!strcmp(mapping, "jeida-18")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
+ } else if (!strcmp(mapping, "jeida-24")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
+ } else if (!strcmp(mapping, "vesa-24")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
+ }
+ break;
+ case PANEL_DPI:
+ if (!strcmp(mapping, "rgb24")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ } else if (!strcmp(mapping, "rgb565")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB565_1X16;
+ } else if (!strcmp(mapping, "bgr666")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+ } else if (!strcmp(mapping, "lvds666")) {
+ lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X24_CPADHI;
+ }
+ };
+
+ if (!lvds->bus_format) {
dev_err(lvds->dev, "%pOF: invalid or missing %s DT property\n",
np, "data-mapping");
return -EINVAL;
@@ -264,7 +289,8 @@ static int panel_lvds_remove(struct platform_device *pdev)
}
static const struct of_device_id panel_lvds_of_table[] = {
- { .compatible = "panel-lvds", },
+ { .compatible = "panel-lvds", .data = (void *)PANEL_LVDS },
+ { .compatible = "panel-dpi", .data = (void *)PANEL_DPI },
{ /* Sentinel */ },
};
@@ -274,7 +300,7 @@ static struct platform_driver panel_lvds_driver = {
.probe = panel_lvds_probe,
.remove = panel_lvds_remove,
.driver = {
- .name = "panel-lvds",
+ .name = "panel-generic",
.of_match_table = panel_lvds_of_table,
},
};
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
index 2813933b666e..5e077cb87e35 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -1388,6 +1388,84 @@ static const struct panel_desc lg_lp129qe = {
},
};
+static const struct drm_display_mode lg_lp156wf1_mode = {
+ .clock = 138500,
+ .hdisplay = 1920,
+ .hsync_start = 1920 + 48,
+ .hsync_end = 1920 + 48 + 32,
+ .htotal = 1920 + 48 + 32 + 80,
+ .vdisplay = 1080,
+ .vsync_start = 1080 + 3,
+ .vsync_end = 1080 + 3 + 5,
+ .vtotal = 1080 + 3 + 5 + 23,
+ .vrefresh = 60,
+};
+
+static const struct panel_desc lg_lp156wf1 = {
+ .modes = &lg_lp156wf1_mode,
+ .num_modes = 1,
+ .size = {
+ .width = 345,
+ .height = 194,
+ },
+};
+
+static const struct display_timing logictechno_lt161010_2nh_timing = {
+ .pixelclock = { 26400000, 33300000, 46800000 },
+ .hactive = { 800, 800, 800 },
+ .hfront_porch = { 16, 210, 354 },
+ .hback_porch = { 46, 46, 46 },
+ .hsync_len = { 1, 20, 40 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 7, 22, 147 },
+ .vback_porch = { 23, 23, 23 },
+ .vsync_len = { 1, 10, 20 },
+ .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW |
+ DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE |
+ DISPLAY_FLAGS_SYNC_POSEDGE,
+};
+
+static const struct panel_desc logictechno_lt161010_2nh = {
+ .timings = &logictechno_lt161010_2nh_timing,
+ .num_timings = 1,
+ .size = {
+ .width = 154,
+ .height = 86,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB666_1X18,
+ .bus_flags = DRM_BUS_FLAG_DE_HIGH |
+ DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
+ DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE,
+};
+
+static const struct display_timing logictechno_lt170410_2whc_timing = {
+ .pixelclock = { 68900000, 71100000, 7340000 },
+ .hactive = { 1280, 1280, 1280 },
+ .hfront_porch = { 23, 60, 71 },
+ .hback_porch = { 23, 60, 71 },
+ .hsync_len = { 15, 40, 47 },
+ .vactive = { 800, 800, 800 },
+ .vfront_porch = { 5, 7, 10 },
+ .vback_porch = { 5, 7, 10 },
+ .vsync_len = { 6, 9, 12 },
+ .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW |
+ DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE |
+ DISPLAY_FLAGS_SYNC_POSEDGE,
+};
+
+static const struct panel_desc logictechno_lt170410_2whc = {
+ .timings = &logictechno_lt170410_2whc_timing,
+ .num_timings = 1,
+ .size = {
+ .width = 217,
+ .height = 136,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,
+ .bus_flags = DRM_BUS_FLAG_DE_HIGH |
+ DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
+ DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE,
+};
+
static const struct display_timing nec_nl12880bc20_05_timing = {
.pixelclock = { 67000000, 71000000, 75000000 },
.hactive = { 1280, 1280, 1280 },
@@ -2097,6 +2175,18 @@ static const struct of_device_id platform_of_match[] = {
.compatible = "lg,lp129qe",
.data = &lg_lp129qe,
}, {
+ .compatible = "lg,lp156wf1",
+ .data = &lg_lp156wf1,
+ }, {
+ .compatible = "logictechno,lt161010-2nhc",
+ .data = &logictechno_lt161010_2nh,
+ }, {
+ .compatible = "logictechno,lt161010-2nhr",
+ .data = &logictechno_lt161010_2nh,
+ }, {
+ .compatible = "logictechno,lt170410-2whc",
+ .data = &logictechno_lt170410_2whc,
+ }, {
.compatible = "nec,nl12880bc20-05",
.data = &nec_nl12880bc20_05,
}, {
diff --git a/drivers/gpu/imx/lcdif/lcdif-common.c b/drivers/gpu/imx/lcdif/lcdif-common.c
index 86b106956e65..4c2d0de1ae4d 100644
--- a/drivers/gpu/imx/lcdif/lcdif-common.c
+++ b/drivers/gpu/imx/lcdif/lcdif-common.c
@@ -85,7 +85,7 @@ static const struct of_device_id imx_lcdif_dt_ids[] = {
{ .compatible = "fsl,imx8mm-lcdif", .data = &imx8mm_pdata, },
{ /* sentinel */ }
};
-MODULE_DEVICE_TABLE(of, lcdif_dt_ids);
+MODULE_DEVICE_TABLE(of, imx_lcdif_dt_ids);
#ifdef CONFIG_PM
static int imx_lcdif_runtime_suspend(struct device *dev);