diff options
Diffstat (limited to 'drivers/gpu')
21 files changed, 1917 insertions, 58 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 1a20991ba88e..9e3778cce1ed 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -74,6 +74,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_SEC_MIPI_DSIM tristate "Samsung MIPI DSIM Bridge" depends on OF @@ -183,6 +191,8 @@ source "drivers/gpu/drm/bridge/cadence/Kconfig" source "drivers/gpu/drm/bridge/synopsys/Kconfig" +source "drivers/gpu/drm/bridge/sn65dsi83/Kconfig" + config DRM_ITE_IT6263 tristate "ITE IT6263 LVDS/HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 103466b50b2a..9016ea806307 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o obj-$(CONFIG_DRM_FSL_IMX_LVDS_BRIDGE) += fsl-imx-ldb.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 @@ -23,3 +24,4 @@ obj-y += synopsys/ obj-$(CONFIG_DRM_ITE_IT6263) += it6263.o obj-$(CONFIG_DRM_SEC_MIPI_DSIM) += sec-dsim.o obj-$(CONFIG_DRM_NXP_SEIKO_43WVFIG) += nxp-seiko-43wvfig.o +obj-$(CONFIG_DRM_I2C_SN65DSI83) += sn65dsi83/ diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c index 07700e494857..b9677ef4c23d 100644 --- a/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c +++ b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c @@ -251,6 +251,7 @@ EXPORT_SYMBOL_GPL(cdns_hdmi_set_plugged_cb); static enum drm_connector_status cdns_hdmi_connector_detect(struct drm_connector *connector, bool force) { + struct edid *edid; struct cdns_mhdp_device *mhdp = container_of(connector, struct cdns_mhdp_device, connector.base); enum drm_connector_status result; @@ -259,15 +260,21 @@ cdns_hdmi_connector_detect(struct drm_connector *connector, bool force) hpd = cdns_mhdp_read_hpd(mhdp); - if (hpd == 1) + if (hpd == 1) { /* Cable Connected */ result = connector_status_connected; - else if (hpd == 0) + } else if (hpd == 0) { /* Cable Disconnedted */ result = connector_status_disconnected; - else { + } else if (mhdp->ddc) { + edid = drm_get_edid(connector, mhdp->ddc); + if (drm_edid_is_valid(edid)) + result = connector_status_connected; + else + result = 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); result = connector_status_unknown; } @@ -289,8 +296,15 @@ static int cdns_hdmi_connector_get_modes(struct drm_connector *connector) int num_modes = 0; struct edid *edid; - edid = drm_do_get_edid(&mhdp->connector.base, - cdns_hdmi_get_edid_block, mhdp); + /* + * Check if optional regular DDC I2C bus should be used. + * Fall-back to using IP/firmware integrated one. + */ + if (mhdp->ddc) + edid = drm_get_edid(&mhdp->connector.base, mhdp->ddc); + else + edid = drm_do_get_edid(&mhdp->connector.base, + cdns_hdmi_get_edid_block, mhdp); if (edid) { dev_info(mhdp->dev, "%x,%x,%x,%x,%x,%x,%x,%x\n", edid->header[0], edid->header[1], @@ -545,6 +559,7 @@ static irqreturn_t cdns_hdmi_irq_thread(int irq, void *data) static void cdns_hdmi_parse_dt(struct cdns_mhdp_device *mhdp) { struct device_node *of_node = mhdp->dev->of_node; + struct device_node *ddc_phandle; int ret; ret = of_property_read_u32(of_node, "lane-mapping", &mhdp->lane_mapping); @@ -553,6 +568,17 @@ static void cdns_hdmi_parse_dt(struct cdns_mhdp_device *mhdp) dev_warn(mhdp->dev, "Failed to get lane_mapping - using default 0xc6\n"); } dev_info(mhdp->dev, "lane-mapping 0x%02x\n", mhdp->lane_mapping); + + /* get optional regular DDC I2C bus */ + ddc_phandle = of_parse_phandle(of_node, "ddc-i2c-bus", 0); + if (ddc_phandle) { + mhdp->ddc = of_get_i2c_adapter_by_node(ddc_phandle); + if (mhdp->ddc) + dev_info(mhdp->dev, "Connector's ddc i2c bus found\n"); + else + ret = -EPROBE_DEFER; + of_node_put(ddc_phandle); + } } static int __cdns_hdmi_probe(struct platform_device *pdev, diff --git a/drivers/gpu/drm/bridge/dumb-vga-dac.c b/drivers/gpu/drm/bridge/dumb-vga-dac.c index 7aa789c35882..83ba7ba78f35 100644 --- a/drivers/gpu/drm/bridge/dumb-vga-dac.c +++ b/drivers/gpu/drm/bridge/dumb-vga-dac.c @@ -79,6 +79,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..af49911d447b --- /dev/null +++ b/drivers/gpu/drm/bridge/lt8912.c @@ -0,0 +1,730 @@ +// 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_atomic_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_panel.h> +#include <drm/drm_mipi_dsi.h> + +#define HOTPLUG_DEBOUNCE_MS 1100 + +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 */ + struct delayed_work hotplug_work; +}; + +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 = <->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 &= 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); /* disable 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 void lt8912_hotplug_work_func(struct work_struct *work) +{ + struct lt8912 *lt; + + lt = container_of(work, struct lt8912, hotplug_work.work); + + if (lt->bridge.dev) + drm_helper_hpd_irq_event(lt->bridge.dev); + +} + +static irqreturn_t lt8912_hpd_irq_thread(int irq, void *arg) +{ + struct lt8912 *lt = arg; + + mod_delayed_work(system_wq, <->hotplug_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); + + 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_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 || 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, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj) +{ + struct lt8912 *lt = bridge_to_lt8912(bridge); + + drm_mode_copy(<->mode, adj); +} + +static int lt8912_bridge_attach(struct drm_bridge *bridge) +{ + struct lt8912 *lt = bridge_to_lt8912(bridge); + struct drm_connector *connector = <->connector; + int ret; + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(bridge->dev, connector, + <8912_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + dev_err(lt->dev, "failed to initialize connector\n"); + return ret; + } + + drm_connector_helper_add(connector, <8912_connector_helper_funcs); + drm_connector_attach_encoder(connector, bridge->encoder); + + if (!bridge->encoder) { + dev_err(lt->dev, "Parent encoder object not found"); + return -ENODEV; + } + + ret = lt8912_attach_dsi(lt); + + if (!ret && irqd_irq_disabled(irq_get_irq_data(lt->irq))) + 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; + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(lt->dev, "Failed to access device %s at address %02x", + info[0].type, info[0].addr); + 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, <8912_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 = 0; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C)) { + DRM_ERROR("device doesn't support I2C\n"); + return -ENODEV; + } + + lt = devm_kzalloc(dev, sizeof(*lt), GFP_KERNEL); + if (!lt) + return -ENOMEM; + + dev_set_drvdata(dev, lt); + 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); + of_node_put(ddc_phandle); + if (!(lt->ddc)) + return -EPROBE_DEFER; + } + + lt->hpd_gpio = devm_gpiod_get(dev, "hpd", GPIOD_IN); + if (IS_ERR(lt->hpd_gpio)) { + ret = PTR_ERR(lt->hpd_gpio); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get hpd gpio: %d\n", ret); + goto put_i2c_ddc; + } + + lt->irq = gpiod_to_irq(lt->hpd_gpio); + if (lt->irq == -ENXIO) { + dev_err(dev, "failed to get hpd irq\n"); + ret = -ENODEV; + goto put_i2c_ddc; + } + if (lt->irq < 0) { + dev_err(dev, "failed to get hpd irq, %i\n", lt->irq); + ret = lt->irq; + goto put_i2c_ddc; + } + + INIT_DELAYED_WORK(<->hotplug_work, lt8912_hotplug_work_func); + + 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: %d\n", ret); + ret = -ENODEV; + goto put_i2c_ddc; + } + + 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); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to request reset GPIO: %d\n", ret); + goto put_i2c_ddc; + } + + ret = lt8912_i2c_init(lt, i2c); + if (ret) + goto put_i2c_ddc; + + /* TODO: interrupt handing */ + + lt->num_dsi_lanes = 4; + lt->channel_id = 1; + + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!endpoint) { + ret = -ENODEV; + goto put_i2c_ddc; + } + + lt->host_node = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!lt->host_node) { + ret = -ENODEV; + goto put_i2c_ddc; + } + + lt->bridge.funcs = <8912_bridge_funcs; + lt->bridge.of_node = dev->of_node; + drm_bridge_add(<->bridge); + + return 0; + +put_i2c_ddc: + i2c_put_adapter(lt->ddc); + return ret; +} + +static int lt8912_remove(struct i2c_client *i2c) +{ + struct lt8912 *lt = i2c_get_clientdata(i2c); + + cancel_delayed_work_sync(<->hotplug_work); + + lt8912_sleep(lt); + mipi_dsi_detach(lt->dsi); + drm_bridge_remove(<->bridge); + of_node_put(lt->host_node); + i2c_put_adapter(lt->ddc); + + 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(<8912_driver); + + return i2c_add_driver(<8912_i2c_driver); +} +module_init(lt8912_i2c_drv_init); + +static void __exit lt8912_i2c_exit(void) +{ + i2c_del_driver(<8912_i2c_driver); + + mipi_dsi_driver_unregister(<8912_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/sn65dsi83/Kconfig b/drivers/gpu/drm/bridge/sn65dsi83/Kconfig new file mode 100644 index 000000000000..1d8f37f689d3 --- /dev/null +++ b/drivers/gpu/drm/bridge/sn65dsi83/Kconfig @@ -0,0 +1,7 @@ +config DRM_I2C_SN65DSI83 + bool "SN65DSI83 mipi dsi to lvds bridge" + depends on OF + select DRM_MIPI_DSI + default y + help + Support for the sn65dsi83 MIPI DSI to LVDS bridge diff --git a/drivers/gpu/drm/bridge/sn65dsi83/Makefile b/drivers/gpu/drm/bridge/sn65dsi83/Makefile new file mode 100644 index 000000000000..dee7f493b323 --- /dev/null +++ b/drivers/gpu/drm/bridge/sn65dsi83/Makefile @@ -0,0 +1,2 @@ +sn65dsi83-objs := sn65dsi83_drv.o sn65dsi83_brg.o +obj-$(CONFIG_DRM_I2C_SN65DSI83) := sn65dsi83.o diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c new file mode 100644 index 000000000000..f4a7713635d6 --- /dev/null +++ b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2018 CopuLab Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/i2c.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/slab.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_connector.h> +#include <video/mipi_display.h> +#include <video/of_videomode.h> +#include <video/videomode.h> + +#include "sn65dsi83_brg.h" + +/* Register addresses */ + +#define SN65DSI83_SOFT_RESET 0x09 +#define SN65DSI83_CORE_PLL 0x0A + #define LVDS_CLK_RANGE_SHIFT 1 + #define HS_CLK_SRC_SHIFT 0 + +#define SN65DSI83_PLL_DIV 0x0B + #define DSI_CLK_DIV_SHIFT 3 + +#define SN65DSI83_PLL_EN 0x0D +#define SN65DSI83_DSI_CFG 0x10 + #define CHA_DSI_LANES_SHIFT 3 + +#define SN65DSI83_DSI_EQ 0x11 +#define SN65DSI83_CHA_DSI_CLK_RNG 0x12 +#define SN65DSI83_CHB_DSI_CLK_RNG 0x13 +#define SN65DSI83_LVDS_MODE 0x18 + #define DE_NEG_POLARITY_SHIFT 7 + #define HS_NEG_POLARITY_SHIFT 6 + #define VS_NEG_POLARITY_SHIFT 5 + #define LVDS_LINK_CFG_SHIFT 4 + #define CHA_24BPP_MODE_SHIFT 3 + #define CHA_24BPP_FMT1_SHIFT 1 + +#define SN65DSI83_LVDS_SIGN 0x19 +#define SN65DSI83_LVDS_TERM 0x1A +#define SN65DSI83_LVDS_CM_ADJ 0x1B +#define SN65DSI83_CHA_LINE_LEN_LO 0x20 +#define SN65DSI83_CHA_LINE_LEN_HI 0x21 +#define SN65DSI83_CHB_LINE_LEN_LO 0x22 +#define SN65DSI83_CHB_LINE_LEN_HI 0x23 +#define SN65DSI83_CHA_VERT_LINES_LO 0x24 +#define SN65DSI83_CHA_VERT_LINES_HI 0x25 +#define SN65DSI83_CHB_VERT_LINES_LO 0x26 +#define SN65DSI83_CHB_VERT_LINES_HI 0x27 +#define SN65DSI83_CHA_SYNC_DELAY_LO 0x28 +#define SN65DSI83_CHA_SYNC_DELAY_HI 0x29 +#define SN65DSI83_CHB_SYNC_DELAY_LO 0x2A +#define SN65DSI83_CHB_SYNC_DELAY_HI 0x2B +#define SN65DSI83_CHA_HSYNC_WIDTH_LO 0x2C +#define SN65DSI83_CHA_HSYNC_WIDTH_HI 0x2D +#define SN65DSI83_CHB_HSYNC_WIDTH_LO 0x2E +#define SN65DSI83_CHB_HSYNC_WIDTH_HI 0x2F +#define SN65DSI83_CHA_VSYNC_WIDTH_LO 0x30 +#define SN65DSI83_CHA_VSYNC_WIDTH_HI 0x31 +#define SN65DSI83_CHB_VSYNC_WIDTH_LO 0x32 +#define SN65DSI83_CHB_VSYNC_WIDTH_HI 0x33 +#define SN65DSI83_CHA_HORZ_BACKPORCH 0x34 +#define SN65DSI83_CHB_HORZ_BACKPORCH 0x35 +#define SN65DSI83_CHA_VERT_BACKPORCH 0x36 +#define SN65DSI83_CHB_VERT_BACKPORCH 0x37 +#define SN65DSI83_CHA_HORZ_FRONTPORCH 0x38 +#define SN65DSI83_CHB_HORZ_FRONTPORCH 0x39 +#define SN65DSI83_CHA_VERT_FRONTPORCH 0x3A +#define SN65DSI83_CHB_VERT_FRONTPORCH 0x3B +#define SN65DSI83_CHA_ERR 0xE5 +#define SN65DSI83_TEST_PATTERN 0x3C +#define SN65DSI83_REG_3D 0x3D +#define SN65DSI83_REG_3E 0x3E + +static int sn65dsi83_brg_power_on(struct sn65dsi83_brg *brg) +{ + dev_info(&brg->client->dev,"%s\n",__func__); + gpiod_set_value_cansleep(brg->gpio_enable, 1); + /* Wait for 1ms for the internal voltage regulator to stabilize */ + msleep(1); + + return 0; +} + +static void sn65dsi83_brg_power_off(struct sn65dsi83_brg *brg) +{ + dev_info(&brg->client->dev,"%s\n",__func__); + gpiod_set_value_cansleep(brg->gpio_enable, 0); + /* + * The EN pin must be held low for at least 10 ms + * before being asserted high + */ + msleep(10); +} + +static int sn65dsi83_write(struct i2c_client *client, u8 reg, u8 val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, val); + + if (ret) + dev_err(&client->dev, "failed to write at 0x%02x", reg); + + dev_dbg(&client->dev, "%s: write reg 0x%02x data 0x%02x", __func__, reg, val); + + return ret; +} +#define SN65DSI83_WRITE(reg,val) sn65dsi83_write(client, (reg) , (val)) + +static int sn65dsi83_read(struct i2c_client *client, u8 reg) +{ + int ret; + + dev_info(&client->dev, "client 0x%p", client); + ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) { + dev_err(&client->dev, "failed reading at 0x%02x", reg); + return ret; + } + + dev_dbg(&client->dev, "%s: read reg 0x%02x data 0x%02x", __func__, reg, ret); + + return ret; +} +#define SN65DSI83_READ(reg) sn65dsi83_read(client, (reg)) + +static int sn65dsi83_brg_start_stream(struct sn65dsi83_brg *brg) +{ + int regval; + struct i2c_client *client = I2C_CLIENT(brg); + + dev_info(&client->dev,"%s\n",__func__); + /* Set the PLL_EN bit (CSR 0x0D.0) */ + SN65DSI83_WRITE(SN65DSI83_PLL_EN, 0x1); + /* Wait for the PLL_LOCK bit to be set (CSR 0x0A.7) */ + msleep(200); + + /* Perform SW reset to apply changes */ + SN65DSI83_WRITE(SN65DSI83_SOFT_RESET, 0x01); + + /* Read CHA Error register */ + regval = SN65DSI83_READ(SN65DSI83_CHA_ERR); + dev_info(&client->dev, "CHA (0x%02x) = 0x%02x", + SN65DSI83_CHA_ERR, regval); + + return 0; +} + +static void sn65dsi83_brg_stop_stream(struct sn65dsi83_brg *brg) +{ + struct i2c_client *client = I2C_CLIENT(brg); + dev_info(&client->dev,"%s\n",__func__); + /* Clear the PLL_EN bit (CSR 0x0D.0) */ + SN65DSI83_WRITE(SN65DSI83_PLL_EN, 0x00); +} + +static int sn65dsi83_calk_clk_range(int min_regval, int max_regval, + unsigned long min_clk, unsigned long inc, + unsigned long target_clk) +{ + int regval = min_regval; + unsigned long clk = min_clk; + + while (regval <= max_regval) { + if ((clk <= target_clk) && (target_clk < (clk + inc))) + return regval; + + regval++; + clk += inc; + } + + return -1; +} + +#define ABS(X) ((X) < 0 ? (-1 * (X)) : (X)) +static int sn65dsi83_calk_div(int min_regval, int max_regval, int min_div, + int inc, unsigned long source_clk, + unsigned long target_clk) +{ + int regval = min_regval; + int div = min_div; + unsigned long curr_delta; + unsigned long prev_delta = ABS(DIV_ROUND_UP(source_clk, div) - + target_clk); + + while (regval <= max_regval) { + curr_delta = ABS(DIV_ROUND_UP(source_clk, div) - target_clk); + if (curr_delta > prev_delta) + return --regval; + + regval++; + div += inc; + } + + return -1; +} + +static int sn65dsi83_brg_configure(struct sn65dsi83_brg *brg) +{ + int regval = 0; + struct i2c_client *client = I2C_CLIENT(brg); + struct videomode *vm = VM(brg); + + u32 dsi_clk = (((PIXCLK * BPP(brg)) / DSI_LANES(brg)) >> 1); + + dev_info(&client->dev, "DSI clock [ %u ] Hz\n",dsi_clk); + dev_info(&client->dev, "GeoMetry [ %d x %d ] Hz\n",HACTIVE,VACTIVE); + + /* Reset PLL_EN and SOFT_RESET registers */ + SN65DSI83_WRITE(SN65DSI83_SOFT_RESET,0x00); + SN65DSI83_WRITE(SN65DSI83_PLL_EN,0x00); + + /* LVDS clock setup */ + if ((25000000 <= PIXCLK) && (PIXCLK < 37500000)) + regval = 0; + else + regval = sn65dsi83_calk_clk_range(0x01, 0x05, 37500000, 25000000, + PIXCLK); + + if (regval < 0) { + dev_err(&client->dev, "failed to configure LVDS clock"); + return -EINVAL; + } + + regval = (regval << LVDS_CLK_RANGE_SHIFT); + regval |= (1 << HS_CLK_SRC_SHIFT); /* Use DSI clock */ + SN65DSI83_WRITE(SN65DSI83_CORE_PLL,regval); + + /* DSI clock range */ + regval = sn65dsi83_calk_clk_range(0x08, 0x64, 40000000, 5000000, dsi_clk); + if (regval < 0) { + dev_err(&client->dev, "failed to configure DSI clock range\n"); + return -EINVAL; + } + SN65DSI83_WRITE(SN65DSI83_CHA_DSI_CLK_RNG,regval); + + /* DSI clock divider */ + regval = sn65dsi83_calk_div(0x0, 0x18, 1, 1, dsi_clk, PIXCLK); + if (regval < 0) { + dev_err(&client->dev, "failed to calculate DSI clock divider"); + return -EINVAL; + } + + regval = regval << DSI_CLK_DIV_SHIFT; + SN65DSI83_WRITE(SN65DSI83_PLL_DIV,regval); + + /* Configure DSI_LANES */ + regval = SN65DSI83_READ(SN65DSI83_DSI_CFG); + regval &= ~(3 << CHA_DSI_LANES_SHIFT); + regval |= ((4 - DSI_LANES(brg)) << CHA_DSI_LANES_SHIFT); + SN65DSI83_WRITE(SN65DSI83_DSI_CFG,regval); + + /* CHA_DSI_DATA_EQ - No Equalization */ + /* CHA_DSI_CLK_EQ - No Equalization */ + SN65DSI83_WRITE(SN65DSI83_DSI_EQ,0x00); + + /* Video formats */ + regval = 0; + if (FLAGS & DISPLAY_FLAGS_HSYNC_LOW) + regval |= (1 << HS_NEG_POLARITY_SHIFT); + + if (FLAGS & DISPLAY_FLAGS_VSYNC_LOW) + regval |= (1 << VS_NEG_POLARITY_SHIFT); + + if (FLAGS & DISPLAY_FLAGS_DE_LOW) + regval |= (1 << DE_NEG_POLARITY_SHIFT); + + if (BPP(brg) == 24) + regval |= (1 << CHA_24BPP_MODE_SHIFT); + + if (FORMAT(brg) == 1) + regval |= (1 << CHA_24BPP_FMT1_SHIFT); + + regval |= (1 << LVDS_LINK_CFG_SHIFT); + SN65DSI83_WRITE(SN65DSI83_LVDS_MODE,regval); + + /* Voltage and pins */ + SN65DSI83_WRITE(SN65DSI83_LVDS_SIGN,0x00); + SN65DSI83_WRITE(SN65DSI83_LVDS_TERM,0x03); + SN65DSI83_WRITE(SN65DSI83_LVDS_CM_ADJ,0x00); + + /* Configure sync delay to minimal allowed value */ + SN65DSI83_WRITE(SN65DSI83_CHA_SYNC_DELAY_LO,0x21); + SN65DSI83_WRITE(SN65DSI83_CHA_SYNC_DELAY_HI,0x00); + + /* Geometry */ + SN65DSI83_WRITE(SN65DSI83_CHA_LINE_LEN_LO,LOW(HACTIVE)); + SN65DSI83_WRITE(SN65DSI83_CHA_LINE_LEN_HI,HIGH(HACTIVE)); + + SN65DSI83_WRITE(SN65DSI83_CHA_VERT_LINES_LO,LOW(VACTIVE)); + SN65DSI83_WRITE(SN65DSI83_CHA_VERT_LINES_HI,HIGH(VACTIVE)); + + SN65DSI83_WRITE(SN65DSI83_CHA_HSYNC_WIDTH_LO,LOW(HPW)); + SN65DSI83_WRITE(SN65DSI83_CHA_HSYNC_WIDTH_HI,HIGH(HPW)); + + SN65DSI83_WRITE(SN65DSI83_CHA_VSYNC_WIDTH_LO,LOW(VPW)); + SN65DSI83_WRITE(SN65DSI83_CHA_VSYNC_WIDTH_HI,HIGH(VPW)); + + SN65DSI83_WRITE(SN65DSI83_CHA_HORZ_BACKPORCH,LOW(HBP)); + SN65DSI83_WRITE(SN65DSI83_CHA_VERT_BACKPORCH,LOW(VBP)); + + SN65DSI83_WRITE(SN65DSI83_CHA_HORZ_FRONTPORCH,LOW(HFP)); + SN65DSI83_WRITE(SN65DSI83_CHA_VERT_FRONTPORCH,LOW(VFP)); + + SN65DSI83_WRITE(SN65DSI83_TEST_PATTERN,0x00); + SN65DSI83_WRITE(SN65DSI83_REG_3D,0x00); + SN65DSI83_WRITE(SN65DSI83_REG_3E,0x00); + + /* mute channel B */ + SN65DSI83_WRITE(SN65DSI83_CHB_DSI_CLK_RNG, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_LINE_LEN_LO, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_LINE_LEN_HI, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_VERT_LINES_LO, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_VERT_LINES_HI, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_SYNC_DELAY_LO, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_SYNC_DELAY_HI, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_HSYNC_WIDTH_LO, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_HSYNC_WIDTH_HI, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_VSYNC_WIDTH_LO, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_VSYNC_WIDTH_HI, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_HORZ_BACKPORCH, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_VERT_BACKPORCH, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_HORZ_FRONTPORCH, 0x00); + SN65DSI83_WRITE(SN65DSI83_CHB_VERT_FRONTPORCH, 0x00); + return 0; +} + +static int sn65dsi83_brg_setup(struct sn65dsi83_brg *brg) +{ + struct i2c_client *client = I2C_CLIENT(brg); + dev_info(&client->dev,"%s\n",__func__); + sn65dsi83_brg_power_on(brg); + return sn65dsi83_brg_configure(brg); +} + +static int sn65dsi83_brg_reset(struct sn65dsi83_brg *brg) +{ + /* Soft Reset reg value at power on should be 0x00 */ + struct i2c_client *client = I2C_CLIENT(brg); + int ret = SN65DSI83_READ(SN65DSI83_SOFT_RESET); + dev_info(&client->dev,"%s\n",__func__); + if (ret != 0x00) { + dev_err(&client->dev,"Failed to reset the device"); + return -ENODEV; + } + return 0; +} + +static struct sn65dsi83_brg_funcs brg_func = { + .power_on = sn65dsi83_brg_power_on, + .power_off = sn65dsi83_brg_power_off, + .setup = sn65dsi83_brg_setup, + .reset = sn65dsi83_brg_reset, + .start_stream = sn65dsi83_brg_start_stream, + .stop_stream = sn65dsi83_brg_stop_stream, +}; + +static struct sn65dsi83_brg brg = { + .funcs = &brg_func, +}; + +struct sn65dsi83_brg *sn65dsi83_brg_get(void) { + return &brg; +} diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h new file mode 100644 index 000000000000..9f23df8afedc --- /dev/null +++ b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h @@ -0,0 +1,55 @@ +#ifndef _SN65DSI83_BRG_H__ +#define _SN65DSI83_BRG_H__ + +#include <linux/i2c.h> +#include <linux/gpio/consumer.h> +#include <video/videomode.h> + +struct sn65dsi83_brg; +struct sn65dsi83_brg_funcs { + int (*power_on)(struct sn65dsi83_brg *sn65dsi8383_brg); + void (*power_off)(struct sn65dsi83_brg *sn65dsi8383_brg); + int (*reset)(struct sn65dsi83_brg *sn65dsi8383_brg); + int (*setup)(struct sn65dsi83_brg *sn65dsi8383_brg); + int (*start_stream)(struct sn65dsi83_brg *sn65dsi8383_brg); + void (*stop_stream)(struct sn65dsi83_brg *sn65dsi8383_brg); +}; + +struct sn65dsi83_brg { + struct i2c_client *client; + struct gpio_desc *gpio_enable; + /* Bridge Panel Parameters */ + struct videomode vm; + u32 width_mm; + u32 height_mm; + u32 format; + u32 bpp; + + u8 num_dsi_lanes; + struct sn65dsi83_brg_funcs *funcs; +}; +struct sn65dsi83_brg *sn65dsi83_brg_get(void); + +#define I2C_DEVICE(A) &(A)->client->dev +#define I2C_CLIENT(A) (A)->client +#define VM(A) &(A)->vm +#define BPP(A) (A)->bpp +#define FORMAT(A) (A)->format +#define DSI_LANES(A) (A)->num_dsi_lanes + +/* The caller has to have a vm structure defined */ +#define PIXCLK vm->pixelclock +#define HACTIVE vm->hactive +#define HFP vm->hfront_porch +#define HBP vm->hback_porch +#define HPW vm->hsync_len +#define VACTIVE vm->vactive +#define VFP vm->vfront_porch +#define VBP vm->vback_porch +#define VPW vm->vsync_len +#define FLAGS vm->flags + +#define HIGH(A) (((A) >> 8) & 0xFF) +#define LOW(A) ((A) & 0xFF) + +#endif /* _SN65DSI83_BRG_H__ */ diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c new file mode 100644 index 000000000000..ec7d62cc3275 --- /dev/null +++ b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c @@ -0,0 +1,408 @@ +/* + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/slab.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_connector.h> +#include <drm/drm_crtc_helper.h> +#include <video/mipi_display.h> +#include <video/of_videomode.h> +#include <video/videomode.h> + +#include "sn65dsi83_timing.h" +#include "sn65dsi83_brg.h" + +struct sn65dsi83 { + u8 channel_id; + enum drm_connector_status status; + bool powered; + struct drm_display_mode curr_mode; + struct drm_bridge bridge; + struct drm_connector connector; + struct device_node *host_node; + struct mipi_dsi_device *dsi; + struct sn65dsi83_brg *brg; +}; + +static int sn65dsi83_attach_dsi(struct sn65dsi83 *sn65dsi83); +#define DRM_DEVICE(A) A->dev->dev +/* Connector funcs */ +static struct sn65dsi83 *connector_to_sn65dsi83(struct drm_connector *connector) +{ + return container_of(connector, struct sn65dsi83, connector); +} + +static int sn65dsi83_connector_get_modes(struct drm_connector *connector) +{ + struct sn65dsi83 *sn65dsi83 = connector_to_sn65dsi83(connector); + struct sn65dsi83_brg *brg = sn65dsi83->brg; + struct device *dev = connector->dev->dev; + struct drm_display_mode *mode; + u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + u32 *bus_flags = &connector->display_info.bus_flags; + int ret; + + dev_info(dev, "%s\n",__func__); + mode = drm_mode_create(connector->dev); + if (!mode) { + DRM_DEV_ERROR(dev, "Failed to create display mode!\n"); + return 0; + } + + drm_display_mode_from_videomode(&brg->vm, mode); + mode->width_mm = brg->width_mm; + mode->height_mm = brg->height_mm; + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + drm_mode_probed_add(connector, mode); + drm_connector_list_update(connector); + + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + + if (brg->vm.flags & DISPLAY_FLAGS_DE_HIGH) + *bus_flags |= DRM_BUS_FLAG_DE_HIGH; + if (brg->vm.flags & DISPLAY_FLAGS_DE_LOW) + *bus_flags |= DRM_BUS_FLAG_DE_LOW; + if (brg->vm.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) + *bus_flags |= DRM_BUS_FLAG_PIXDATA_NEGEDGE; + if (brg->vm.flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) + *bus_flags |= DRM_BUS_FLAG_PIXDATA_POSEDGE; + + ret = drm_display_info_set_bus_formats(&connector->display_info, + &bus_format, 1); + if (ret) + return ret; + + return 1; +} + +static enum drm_mode_status +sn65dsi83_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct sn65dsi83 *sn65dsi83 = connector_to_sn65dsi83(connector); + struct device *dev = connector->dev->dev; + if (mode->clock > ( sn65dsi83->brg->vm.pixelclock / 1000 )) + return MODE_CLOCK_HIGH; + + dev_info(dev, "%s: mode: %d*%d@%d is valid\n",__func__, + mode->hdisplay,mode->vdisplay,mode->clock); + return MODE_OK; +} + +static struct drm_connector_helper_funcs sn65dsi83_connector_helper_funcs = { + .get_modes = sn65dsi83_connector_get_modes, + .mode_valid = sn65dsi83_connector_mode_valid, +}; + +static enum drm_connector_status +sn65dsi83_connector_detect(struct drm_connector *connector, bool force) +{ + struct sn65dsi83 *sn65dsi83 = connector_to_sn65dsi83(connector); + struct device *dev = connector->dev->dev; + enum drm_connector_status status; + dev_info(dev, "%s\n",__func__); + + status = connector_status_connected; + sn65dsi83->status = status; + return status; +} + +int drm_helper_probe_single_connector_modes(struct drm_connector *connector, + uint32_t maxX, uint32_t maxY); + +static struct drm_connector_funcs sn65dsi83_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = sn65dsi83_connector_detect, + .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, +}; + +/* Bridge funcs */ +static struct sn65dsi83 *bridge_to_sn65dsi83(struct drm_bridge *bridge) +{ + return container_of(bridge, struct sn65dsi83, bridge); +} + +static void sn65dsi83_bridge_enable(struct drm_bridge *bridge) +{ + struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge); + dev_info(DRM_DEVICE(bridge),"%s\n",__func__); + sn65dsi83->brg->funcs->setup(sn65dsi83->brg); + sn65dsi83->brg->funcs->start_stream(sn65dsi83->brg); +} + +static void sn65dsi83_bridge_disable(struct drm_bridge *bridge) +{ + struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge); + dev_info(DRM_DEVICE(bridge),"%s\n",__func__); + sn65dsi83->brg->funcs->stop_stream(sn65dsi83->brg); + sn65dsi83->brg->funcs->power_off(sn65dsi83->brg); +} + +static void sn65dsi83_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj_mode) +{ + struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge); + dev_info(DRM_DEVICE(bridge), "%s: mode: %d*%d@%d\n",__func__, + mode->hdisplay,mode->vdisplay,mode->clock); + drm_mode_copy(&sn65dsi83->curr_mode, adj_mode); +} + +static int sn65dsi83_bridge_attach(struct drm_bridge *bridge) +{ + struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge); + int ret; + + dev_info(DRM_DEVICE(bridge),"%s\n",__func__); + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + sn65dsi83->connector.polled = DRM_CONNECTOR_POLL_CONNECT; + + ret = drm_connector_init(bridge->dev, &sn65dsi83->connector, + &sn65dsi83_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + drm_connector_helper_add(&sn65dsi83->connector, + &sn65dsi83_connector_helper_funcs); + drm_connector_attach_encoder(&sn65dsi83->connector, bridge->encoder); + + ret = sn65dsi83_attach_dsi(sn65dsi83); + + return ret; +} + +static struct drm_bridge_funcs sn65dsi83_bridge_funcs = { + .enable = sn65dsi83_bridge_enable, + .disable = sn65dsi83_bridge_disable, + .mode_set = sn65dsi83_bridge_mode_set, + .attach = sn65dsi83_bridge_attach, +}; + +static int sn65dsi83_parse_dt(struct device_node *np, + struct sn65dsi83 *sn65dsi83) +{ + struct device *dev = &sn65dsi83->brg->client->dev; + u32 num_lanes = 2, bpp = 24, format = 2, width = 149, height = 93; + struct device_node *endpoint; + + endpoint = of_graph_get_next_endpoint(np, NULL); + if (!endpoint) + return -ENODEV; + + sn65dsi83->host_node = of_graph_get_remote_port_parent(endpoint); + if (!sn65dsi83->host_node) { + of_node_put(endpoint); + return -ENODEV; + } + + of_property_read_u32(np, "ti,dsi-lanes", &num_lanes); + of_property_read_u32(np, "ti,lvds-format", &format); + of_property_read_u32(np, "ti,lvds-bpp", &bpp); + of_property_read_u32(np, "ti,width-mm", &width); + of_property_read_u32(np, "ti,height-mm", &height); + + if (num_lanes < 1 || num_lanes > 4) { + dev_err(dev, "Invalid dsi-lanes: %d\n", num_lanes); + return -EINVAL; + } + sn65dsi83->brg->num_dsi_lanes = num_lanes; + + sn65dsi83->brg->gpio_enable = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(sn65dsi83->brg->gpio_enable)) { + dev_err(dev, "failed to parse enable gpio"); + return PTR_ERR(sn65dsi83->brg->gpio_enable); + } + + sn65dsi83->brg->format = format; + sn65dsi83->brg->bpp = bpp; + + sn65dsi83->brg->width_mm = width; + sn65dsi83->brg->height_mm = height; + + /* Read default timing if there is not device tree node for */ + if ((of_get_videomode(np, &sn65dsi83->brg->vm, 0)) < 0) + videomode_from_timing(&panel_default_timing, &sn65dsi83->brg->vm); + + of_node_put(endpoint); + of_node_put(sn65dsi83->host_node); + + return 0; +} + +static int sn65dsi83_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct sn65dsi83 *sn65dsi83; + struct device *dev = &i2c->dev; + int ret; + + dev_info(dev,"%s\n",__func__); + if (!dev->of_node) + return -EINVAL; + + sn65dsi83 = devm_kzalloc(dev, sizeof(*sn65dsi83), GFP_KERNEL); + if (!sn65dsi83) + return -ENOMEM; + + /* Initialize it before DT parser */ + sn65dsi83->brg = sn65dsi83_brg_get(); + sn65dsi83->brg->client = i2c; + + sn65dsi83->powered = false; + sn65dsi83->status = connector_status_disconnected; + + i2c_set_clientdata(i2c, sn65dsi83); + + ret = sn65dsi83_parse_dt(dev->of_node, sn65dsi83); + if (ret) + return ret; + + sn65dsi83->brg->funcs->power_off(sn65dsi83->brg); + sn65dsi83->brg->funcs->power_on(sn65dsi83->brg); + ret = sn65dsi83->brg->funcs->reset(sn65dsi83->brg); + if (ret != 0x00) { + dev_err(dev, "Failed to reset the device"); + return -ENODEV; + } + sn65dsi83->brg->funcs->power_off(sn65dsi83->brg); + + + sn65dsi83->bridge.funcs = &sn65dsi83_bridge_funcs; + sn65dsi83->bridge.of_node = dev->of_node; + + drm_bridge_add(&sn65dsi83->bridge); + + return ret; +} + +static int sn65dsi83_attach_dsi(struct sn65dsi83 *sn65dsi83) +{ + struct device *dev = &sn65dsi83->brg->client->dev; + struct mipi_dsi_host *host; + struct mipi_dsi_device *dsi; + int ret = 0; + const struct mipi_dsi_device_info info = { .type = "sn65dsi83", + .channel = 0, + .node = NULL, + }; + + dev_info(dev, "%s\n",__func__); + host = of_find_mipi_dsi_host_by_node(sn65dsi83->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); + return -ENODEV; + } + + sn65dsi83->dsi = dsi; + + dsi->lanes = sn65dsi83->brg->num_dsi_lanes; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "failed to attach dsi to host\n"); + mipi_dsi_device_unregister(dsi); + } + + return ret; +} + +static void sn65dsi83_detach_dsi(struct sn65dsi83 *sn65dsi83) +{ + struct device *dev = &sn65dsi83->brg->client->dev; + dev_info(dev, "%s\n",__func__); + mipi_dsi_detach(sn65dsi83->dsi); + mipi_dsi_device_unregister(sn65dsi83->dsi); +} + +static int sn65dsi83_remove(struct i2c_client *i2c) +{ + struct sn65dsi83 *sn65dsi83 = i2c_get_clientdata(i2c); + struct device *dev = &sn65dsi83->brg->client->dev; + dev_info(dev, "%s\n",__func__); + + sn65dsi83_detach_dsi(sn65dsi83); + drm_bridge_remove(&sn65dsi83->bridge); + + return 0; +} + +static const struct i2c_device_id sn65dsi83_i2c_ids[] = { + { "sn65dsi83", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sn65dsi83_i2c_ids); + +static const struct of_device_id sn65dsi83_of_ids[] = { + { .compatible = "ti,sn65dsi83" }, + { } +}; +MODULE_DEVICE_TABLE(of, sn65dsi83_of_ids); + +static struct mipi_dsi_driver sn65dsi83_dsi_driver = { + .driver.name = "sn65dsi83", +}; + +static struct i2c_driver sn65dsi83_driver = { + .driver = { + .name = "sn65dsi83", + .of_match_table = sn65dsi83_of_ids, + }, + .id_table = sn65dsi83_i2c_ids, + .probe = sn65dsi83_probe, + .remove = sn65dsi83_remove, +}; + +static int __init sn65dsi83_init(void) +{ + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_register(&sn65dsi83_dsi_driver); + + return i2c_add_driver(&sn65dsi83_driver); +} +module_init(sn65dsi83_init); + +static void __exit sn65dsi83_exit(void) +{ + i2c_del_driver(&sn65dsi83_driver); + + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_unregister(&sn65dsi83_dsi_driver); +} +module_exit(sn65dsi83_exit); + +MODULE_AUTHOR("CompuLab <compulab@compula.co.il>"); +MODULE_DESCRIPTION("SN65DSI bridge driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h new file mode 100644 index 000000000000..e9bb6633c376 --- /dev/null +++ b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h @@ -0,0 +1,33 @@ +#ifndef __SN65DSI83_TIMING_H__ +#define __SN65DSI83_TIMING_H__ + +/* Default Video Parameters */ +#define PIXCLK_INIT 62500000 + +#define HACTIVE_INIT 1280 +#define HPW_INIT 2 +#define HBP_INIT 6 +#define HFP_INIT 5 + +#define VACTIVE_INIT 800 +#define VPW_INIT 1 +#define VBP_INIT 2 +#define VFP_INIT 3 + +static const struct display_timing panel_default_timing = { + .pixelclock = { PIXCLK_INIT, PIXCLK_INIT, PIXCLK_INIT }, + .hactive = { HACTIVE_INIT, HACTIVE_INIT, HACTIVE_INIT }, + .hfront_porch = { HFP_INIT, HFP_INIT, HFP_INIT }, + .hsync_len = { HPW_INIT, HPW_INIT, HPW_INIT }, + .hback_porch = { HBP_INIT, HBP_INIT, HBP_INIT }, + .vactive = { VACTIVE_INIT, VACTIVE_INIT, VACTIVE_INIT }, + .vfront_porch = { VFP_INIT, VFP_INIT, VFP_INIT }, + .vsync_len = { VPW_INIT, VPW_INIT, VPW_INIT }, + .vback_porch = { VBP_INIT, VBP_INIT, VBP_INIT }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | + DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_LOW | + DISPLAY_FLAGS_PIXDATA_NEGEDGE, +}; + +#endif /* __SN65DSI83_TIMING_H__ */ diff --git a/drivers/gpu/drm/imx/dw_hdmi-imx.c b/drivers/gpu/drm/imx/dw_hdmi-imx.c index 0c54acd52145..1a29c71228c9 100644 --- a/drivers/gpu/drm/imx/dw_hdmi-imx.c +++ b/drivers/gpu/drm/imx/dw_hdmi-imx.c @@ -212,11 +212,16 @@ imx6dl_hdmi_mode_valid(struct drm_connector *con, static bool imx8mp_hdmi_check_clk_rate(int rate_khz) { - int rate = rate_khz * 1000; + int rate; /* Check hdmi phy pixel clock support rate */ - if (rate != clk_round_rate(imx8mp_clocks[0].clk, rate)) + rate = clk_round_rate(imx8mp_clocks[0].clk, rate_khz * 1000); + /* Drop mode if pixelclk generated is more than 6% off */ + if ((rate < rate_khz * 940) || (rate > rate_khz * 1060)) { + pr_info("%s: mode with pixelclk %i kHz dropped\n", + __func__, rate_khz); return false; + } return true; } diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h index 87b5e366b7e6..88049a2e348c 100644 --- a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h +++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h @@ -52,6 +52,7 @@ struct imx_mhdp_device { struct imx_hdp_clks clks; const struct firmware *fw; const char *firmware_name; + int hdmi_ctrl_gpio; int bus_type; diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c index 9bfe5f08f4a3..9c425ac91863 100644 --- a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c +++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c @@ -12,6 +12,7 @@ #include <linux/clk.h> #include <drm/drm_vblank.h> #include <drm/drm_print.h> +#include <linux/delay.h> #include "cdns-mhdp-imx.h" @@ -571,13 +572,6 @@ int cdns_mhdp_firmware_write_section(struct imx_mhdp_device *imx_mhdp, return 0; } -static void cdns_mhdp_firmware_load_cont(const struct firmware *fw, void *context) -{ - struct imx_mhdp_device *imx_mhdp = context; - - imx_mhdp->fw = fw; -} - static int cdns_mhdp_firmware_load(struct imx_mhdp_device *imx_mhdp) { const u8 *iram; @@ -597,23 +591,25 @@ static int cdns_mhdp_firmware_load(struct imx_mhdp_device *imx_mhdp) goto out; if (!imx_mhdp->fw) { - ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, + ret = request_firmware_direct(&imx_mhdp->fw, imx_mhdp->firmware_name, - imx_mhdp->mhdp.dev, GFP_KERNEL, - imx_mhdp, - cdns_mhdp_firmware_load_cont); + imx_mhdp->mhdp.dev); if (ret < 0) { DRM_ERROR("failed to load firmware\n"); - return -ENOENT; + /* Maybe U-Boot loaded the firmware. Therefore, still try to + * reset the controller */ + goto out; } - } else { - iram = imx_mhdp->fw->data + FW_IRAM_OFFSET; - dram = iram + FW_IRAM_SIZE; - - cdns_mhdp_firmware_write_section(imx_mhdp, iram, FW_IRAM_SIZE, ADDR_IMEM); - cdns_mhdp_firmware_write_section(imx_mhdp, dram, FW_DRAM_SIZE, ADDR_DMEM); } + iram = imx_mhdp->fw->data + FW_IRAM_OFFSET; + dram = iram + FW_IRAM_SIZE; + + cdns_mhdp_firmware_write_section(imx_mhdp, iram, + FW_IRAM_SIZE, ADDR_IMEM); + cdns_mhdp_firmware_write_section(imx_mhdp, dram, + FW_DRAM_SIZE, ADDR_DMEM); + out: /* un-reset ucpu */ cdns_mhdp_bus_write(0, &imx_mhdp->mhdp, APB_CTRL); diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c index ead56bed3574..f422516aa354 100644 --- a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c +++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c @@ -8,6 +8,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/component.h> +#include <linux/of_gpio.h> #include <drm/drm_of.h> #include <drm/drm_vblank.h> #include <drm/drm_crtc_helper.h> @@ -161,6 +162,19 @@ static int cdns_mhdp_imx_bind(struct device *dev, struct device *master, match = of_match_node(cdns_mhdp_imx_dt_ids, pdev->dev.of_node); plat_data = match->data; + + imx_mhdp->hdmi_ctrl_gpio = of_get_named_gpio(dev->of_node, "hdmi-ctrl-gpios", 0); + if (gpio_is_valid(imx_mhdp->hdmi_ctrl_gpio)) { + ret = gpio_request(imx_mhdp->hdmi_ctrl_gpio, "HDMI_CTRL"); + if (ret < 0) { + dev_err(dev, "request HDMI CTRL GPIO failed: %d\n", ret); + return ret; + } + + /* Set signals depending on HDP device type, 0 DP, 1 HDMI */ + gpio_direction_output(imx_mhdp->hdmi_ctrl_gpio, !plat_data->is_dp); + } + encoder = &imx_mhdp->encoder; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); @@ -173,8 +187,10 @@ static int cdns_mhdp_imx_bind(struct device *dev, struct device *master, * not been registered yet. Defer probing, and hope that * the required CRTC is added later. */ - if (encoder->possible_crtcs == 0) - return -EPROBE_DEFER; + if (encoder->possible_crtcs == 0) { + ret = -EPROBE_DEFER; + goto err_free_hdmi_gpio; + } drm_encoder_helper_add(encoder, &cdns_mhdp_imx_encoder_helper_funcs); drm_encoder_init(drm, encoder, &cdns_mhdp_imx_encoder_funcs, @@ -190,9 +206,16 @@ static int cdns_mhdp_imx_bind(struct device *dev, struct device *master, * which would have called the encoder cleanup. Do it manually. */ if (ret < 0) - drm_encoder_cleanup(encoder); + goto err_cleanup_encoder; return ret; + +err_cleanup_encoder: + drm_encoder_cleanup(encoder); +err_free_hdmi_gpio: + if (gpio_is_valid(imx_mhdp->hdmi_ctrl_gpio)) + gpio_free(imx_mhdp->hdmi_ctrl_gpio); + return ret; } static void cdns_mhdp_imx_unbind(struct device *dev, struct device *master, diff --git a/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c b/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c index 9bd9b2c6b9f5..b58589e4959b 100644 --- a/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c +++ b/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c @@ -286,6 +286,7 @@ static int imx_sec_dsim_bind(struct device *dev, struct device *master, dev); const struct sec_mipi_dsim_plat_data *pdata; struct drm_encoder *encoder; + static int retry = 0; dev_dbg(dev, "%s: dsim bind begin\n", __func__); @@ -329,7 +330,7 @@ static int imx_sec_dsim_bind(struct device *dev, struct device *master, /* bind sec dsim bridge */ ret = sec_mipi_dsim_bind(dev, master, data, encoder, res, irq, pdata); if (ret) { - dev_err(dev, "failed to bind sec dsim bridge: %d\n", ret); + dev_err(dev, "failed to bind sec dsim bridge: %d, retry %d\n", ret, retry); pm_runtime_disable(dev); drm_encoder_cleanup(encoder); sec_dsim_of_put_resets(dsim_dev); @@ -341,7 +342,7 @@ static int imx_sec_dsim_bind(struct device *dev, struct device *master, * it follows 'one fails, all fail'. It is useful * when there exists multiple heads display. */ - if (ret == -ENODEV) + if ((ret == -ENODEV) || ((retry++ >= 3) && (ret == -EPROBE_DEFER))) return 0; return ret; diff --git a/drivers/gpu/drm/mxsfb/mxsfb_crtc.c b/drivers/gpu/drm/mxsfb/mxsfb_crtc.c index fb6664f84721..5e51924a9f5a 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_crtc.c +++ b/drivers/gpu/drm/mxsfb/mxsfb_crtc.c @@ -151,9 +151,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: @@ -170,7 +170,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; @@ -178,7 +178,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 aafc29a25a60..735c76299813 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c @@ -44,6 +44,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. @@ -88,6 +91,25 @@ static const struct mxsfb_devdata mxsfb_devdata[] = { }, }; +/* + * 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) 0 +# define mxsfb_disable_axi_clk(mxsfb) +#else +static inline int mxsfb_enable_axi_clk(struct mxsfb_drm_private *mxsfb) +{ + return clk_prepare_enable(mxsfb->clk_axi); +} + +static inline void mxsfb_disable_axi_clk(struct mxsfb_drm_private *mxsfb) +{ + clk_disable_unprepare(mxsfb->clk_axi); +} +#endif + static struct mxsfb_drm_private * drm_pipe_to_mxsfb_drm_private(struct drm_simple_display_pipe *pipe) { @@ -232,9 +254,17 @@ static void mxsfb_pipe_enable(struct drm_simple_display_pipe *pipe, } 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) @@ -244,9 +274,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); @@ -274,14 +313,14 @@ static int mxsfb_pipe_enable_vblank(struct drm_simple_display_pipe *pipe) struct mxsfb_drm_private *mxsfb = drm_pipe_to_mxsfb_drm_private(pipe); int ret = 0; - ret = clk_prepare_enable(mxsfb->clk_axi); + ret = mxsfb_enable_axi_clk(mxsfb); if (ret) return ret; /* 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; } @@ -290,13 +329,13 @@ static void mxsfb_pipe_disable_vblank(struct drm_simple_display_pipe *pipe) { struct mxsfb_drm_private *mxsfb = drm_pipe_to_mxsfb_drm_private(pipe); - if (clk_prepare_enable(mxsfb->clk_axi)) + if (mxsfb_enable_axi_clk(mxsfb)) return; /* 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 struct drm_simple_display_pipe_funcs mxsfb_funcs = { @@ -315,6 +354,7 @@ static int mxsfb_load(struct drm_device *drm, unsigned long flags) struct platform_device *pdev = to_platform_device(drm->dev); struct mxsfb_drm_private *mxsfb; struct resource *res; + u32 bus_width = MXSFB_DEFAULT_BUS_WIDTH; int ret; mxsfb = devm_kzalloc(&pdev->dev, sizeof(*mxsfb), GFP_KERNEL); @@ -349,6 +389,7 @@ static int mxsfb_load(struct drm_device *drm, unsigned long flags) return ret; pm_runtime_enable(drm->dev); + pm_runtime_get_sync(drm->dev); ret = drm_vblank_init(drm, drm->mode_config.num_crtc); if (ret < 0) { @@ -396,6 +437,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; + drm->mode_config.min_width = MXSFB_MIN_XRES; drm->mode_config.min_height = MXSFB_MIN_YRES; drm->mode_config.max_width = MXSFB_MAX_XRES; @@ -423,7 +468,8 @@ static int mxsfb_load(struct drm_device *drm, unsigned long flags) return 0; err_irq: - drm_panel_detach(mxsfb->panel); + if (mxsfb->panel) + drm_panel_detach(mxsfb->panel); err_vblank: pm_runtime_disable(drm->dev); @@ -441,6 +487,7 @@ static void mxsfb_unload(struct drm_device *drm) drm->dev_private = NULL; + pm_runtime_put_sync(drm->dev); pm_runtime_disable(drm->dev); } @@ -457,7 +504,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); @@ -466,7 +513,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 54c06445be96..dffe4b8f52f7 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.h +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.h @@ -34,6 +34,7 @@ struct mxsfb_drm_private { struct drm_bridge *bridge; u32 max_bw; + u32 bus_width; }; int mxsfb_setup_crtc(struct drm_device *dev); diff --git a/drivers/gpu/drm/panel/panel-lvds.c b/drivers/gpu/drm/panel/panel-lvds.c index bf5fcc3e5379..7e7655e002c4 100644 --- a/drivers/gpu/drm/panel/panel-lvds.c +++ b/drivers/gpu/drm/panel/panel-lvds.c @@ -23,6 +23,11 @@ #include <drm/drm_crtc.h> #include <drm/drm_panel.h> +enum panel_type { + PANEL_LVDS, + PANEL_DPI +}; + struct panel_lvds { struct drm_panel panel; struct device *dev; @@ -124,7 +129,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; @@ -145,6 +152,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) { @@ -177,13 +185,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; @@ -278,7 +303,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 */ }, }; @@ -288,7 +314,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 2a5214d2eb80..147e1fea4a5a 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -2070,6 +2070,85 @@ 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, + .bpc = 6, + .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, 73400000 }, + .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 drm_display_mode mitsubishi_aa070mc01_mode = { .clock = 30400, .hdisplay = 800, @@ -3338,9 +3417,21 @@ static const struct of_device_id platform_of_match[] = { .compatible = "lg,lp129qe", .data = &lg_lp129qe, }, { + .compatible = "lg,lp156wf1", + .data = &lg_lp156wf1, + }, { .compatible = "logicpd,type28", .data = &logicpd_type_28, }, { + .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 = "mitsubishi,aa070mc01-ca1", .data = &mitsubishi_aa070mc01, }, { |