diff options
Diffstat (limited to 'drivers/gpu/drm/bridge')
-rw-r--r-- | drivers/gpu/drm/bridge/Kconfig | 26 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/adv7511/Kconfig | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/adv7511/adv7511_audio.c | 26 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/chrontel-ch7033.c | 620 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/nwl-dsi.c | 1213 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/nwl-dsi.h | 144 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/panel.c | 7 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/parade-ps8640.c | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/sii9234.c | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 86 | ||||
-rw-r--r-- | drivers/gpu/drm/bridge/tc358768.c | 4 |
13 files changed, 2099 insertions, 38 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index aaed2347ace9..04f876e985de 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -27,6 +27,16 @@ config DRM_CDNS_DSI Support Cadence DPI to DSI bridge. This is an internal bridge and is meant to be directly embedded in a SoC. +config DRM_CHRONTEL_CH7033 + tristate "Chrontel CH7033 Video Encoder" + depends on OF + select DRM_KMS_HELPER + help + Enable support for the Chrontel CH7033 VGA/DVI/HDMI Encoder, as + found in the Dell Wyse 3020 thin client. + + If in doubt, say "N". + config DRM_DISPLAY_CONNECTOR tristate "Display connector support" depends on OF @@ -58,6 +68,22 @@ 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_NWL_MIPI_DSI + tristate "Northwest Logic MIPI DSI Host controller" + depends on DRM + depends on COMMON_CLK + depends on OF && HAS_IOMEM + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + select GENERIC_PHY_MIPI_DPHY + select MFD_SYSCON + select MULTIPLEXER + select REGMAP_MMIO + help + This enables the Northwest Logic MIPI DSI Host controller as + for example found on NXP's i.MX8 Processors. + config DRM_NXP_PTN3460 tristate "NXP PTN3460 DP/LVDS bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 6fb062b5b0f0..d63d4b7e4347 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o +obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o @@ -18,6 +19,7 @@ obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi.o obj-y += analogix/ obj-y += synopsys/ diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig index 47d4eb9e845d..f46a5e26b5dd 100644 --- a/drivers/gpu/drm/bridge/adv7511/Kconfig +++ b/drivers/gpu/drm/bridge/adv7511/Kconfig @@ -6,7 +6,7 @@ config DRM_I2C_ADV7511 select REGMAP_I2C select DRM_MIPI_DSI help - Support for the Analog Device ADV7511(W)/13/33/35 HDMI encoders. + Support for the Analog Devices ADV7511(W)/13/33/35 HDMI encoders. config DRM_I2C_ADV7511_AUDIO bool "ADV7511 HDMI Audio driver" diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_audio.c b/drivers/gpu/drm/bridge/adv7511/adv7511_audio.c index a428185be2c1..f101dd2819b5 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_audio.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_audio.c @@ -19,13 +19,15 @@ static void adv7511_calc_cts_n(unsigned int f_tmds, unsigned int fs, { switch (fs) { case 32000: - *n = 4096; + case 48000: + case 96000: + case 192000: + *n = fs * 128 / 1000; break; case 44100: - *n = 6272; - break; - case 48000: - *n = 6144; + case 88200: + case 176400: + *n = fs * 128 / 900; break; } @@ -119,6 +121,9 @@ int adv7511_hdmi_hw_params(struct device *dev, void *data, audio_source = ADV7511_AUDIO_SOURCE_I2S; i2s_format = ADV7511_I2S_FORMAT_LEFT_J; break; + case HDMI_SPDIF: + audio_source = ADV7511_AUDIO_SOURCE_SPDIF; + break; default: return -EINVAL; } @@ -175,11 +180,21 @@ static int audio_startup(struct device *dev, void *data) /* use Audio infoframe updated info */ regmap_update_bits(adv7511->regmap, ADV7511_REG_GC(1), BIT(5), 0); + /* enable SPDIF receiver */ + if (adv7511->audio_source == ADV7511_AUDIO_SOURCE_SPDIF) + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, + BIT(7), BIT(7)); + return 0; } static void audio_shutdown(struct device *dev, void *data) { + struct adv7511 *adv7511 = dev_get_drvdata(dev); + + if (adv7511->audio_source == ADV7511_AUDIO_SOURCE_SPDIF) + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, + BIT(7), 0); } static int adv7511_hdmi_i2s_get_dai_id(struct snd_soc_component *component, @@ -213,6 +228,7 @@ static const struct hdmi_codec_pdata codec_data = { .ops = &adv7511_codec_ops, .max_i2s_channels = 2, .i2s = 1, + .spdif = 1, }; int adv7511_audio_init(struct device *dev, struct adv7511 *adv7511) diff --git a/drivers/gpu/drm/bridge/chrontel-ch7033.c b/drivers/gpu/drm/bridge/chrontel-ch7033.c new file mode 100644 index 000000000000..f8675d82974b --- /dev/null +++ b/drivers/gpu/drm/bridge/chrontel-ch7033.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Chrontel CH7033 Video Encoder Driver + * + * Copyright (C) 2019,2020 Lubomir Rintel + */ + +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +/* Page 0, Register 0x07 */ +enum { + DRI_PD = BIT(3), + IO_PD = BIT(5), +}; + +/* Page 0, Register 0x08 */ +enum { + DRI_PDDRI = GENMASK(7, 4), + PDDAC = GENMASK(3, 1), + PANEN = BIT(0), +}; + +/* Page 0, Register 0x09 */ +enum { + DPD = BIT(7), + GCKOFF = BIT(6), + TV_BP = BIT(5), + SCLPD = BIT(4), + SDPD = BIT(3), + VGA_PD = BIT(2), + HDBKPD = BIT(1), + HDMI_PD = BIT(0), +}; + +/* Page 0, Register 0x0a */ +enum { + MEMINIT = BIT(7), + MEMIDLE = BIT(6), + MEMPD = BIT(5), + STOP = BIT(4), + LVDS_PD = BIT(3), + HD_DVIB = BIT(2), + HDCP_PD = BIT(1), + MCU_PD = BIT(0), +}; + +/* Page 0, Register 0x18 */ +enum { + IDF = GENMASK(7, 4), + INTEN = BIT(3), + SWAP = GENMASK(2, 0), +}; + +enum { + BYTE_SWAP_RGB = 0, + BYTE_SWAP_RBG = 1, + BYTE_SWAP_GRB = 2, + BYTE_SWAP_GBR = 3, + BYTE_SWAP_BRG = 4, + BYTE_SWAP_BGR = 5, +}; + +/* Page 0, Register 0x19 */ +enum { + HPO_I = BIT(5), + VPO_I = BIT(4), + DEPO_I = BIT(3), + CRYS_EN = BIT(2), + GCLKFREQ = GENMASK(2, 0), +}; + +/* Page 0, Register 0x2e */ +enum { + HFLIP = BIT(7), + VFLIP = BIT(6), + DEPO_O = BIT(5), + HPO_O = BIT(4), + VPO_O = BIT(3), + TE = GENMASK(2, 0), +}; + +/* Page 0, Register 0x2b */ +enum { + SWAPS = GENMASK(7, 4), + VFMT = GENMASK(3, 0), +}; + +/* Page 0, Register 0x54 */ +enum { + COMP_BP = BIT(7), + DAC_EN_T = BIT(6), + HWO_HDMI_HI = GENMASK(5, 3), + HOO_HDMI_HI = GENMASK(2, 0), +}; + +/* Page 0, Register 0x57 */ +enum { + FLDSEN = BIT(7), + VWO_HDMI_HI = GENMASK(5, 3), + VOO_HDMI_HI = GENMASK(2, 0), +}; + +/* Page 0, Register 0x7e */ +enum { + HDMI_LVDS_SEL = BIT(7), + DE_GEN = BIT(6), + PWM_INDEX_HI = BIT(5), + USE_DE = BIT(4), + R_INT = GENMASK(3, 0), +}; + +/* Page 1, Register 0x07 */ +enum { + BPCKSEL = BIT(7), + DRI_CMFB_EN = BIT(6), + CEC_PUEN = BIT(5), + CEC_T = BIT(3), + CKINV = BIT(2), + CK_TVINV = BIT(1), + DRI_CKS2 = BIT(0), +}; + +/* Page 1, Register 0x08 */ +enum { + DACG = BIT(6), + DACKTST = BIT(5), + DEDGEB = BIT(4), + SYO = BIT(3), + DRI_IT_LVDS = GENMASK(2, 1), + DISPON = BIT(0), +}; + +/* Page 1, Register 0x0c */ +enum { + DRI_PLL_CP = GENMASK(7, 6), + DRI_PLL_DIVSEL = BIT(5), + DRI_PLL_N1_1 = BIT(4), + DRI_PLL_N1_0 = BIT(3), + DRI_PLL_N3_1 = BIT(2), + DRI_PLL_N3_0 = BIT(1), + DRI_PLL_CKTSTEN = BIT(0), +}; + +/* Page 1, Register 0x6b */ +enum { + VCO3CS = GENMASK(7, 6), + ICPGBK2_0 = GENMASK(5, 3), + DRI_VCO357SC = BIT(2), + PDPLL2 = BIT(1), + DRI_PD_SER = BIT(0), +}; + +/* Page 1, Register 0x6c */ +enum { + PLL2N11 = GENMASK(7, 4), + PLL2N5_4 = BIT(3), + PLL2N5_TOP = BIT(2), + DRI_PLL_PD = BIT(1), + PD_I2CM = BIT(0), +}; + +/* Page 3, Register 0x28 */ +enum { + DIFF_EN = GENMASK(7, 6), + CORREC_EN = GENMASK(5, 4), + VGACLK_BP = BIT(3), + HM_LV_SEL = BIT(2), + HD_VGA_SEL = BIT(1), +}; + +/* Page 3, Register 0x2a */ +enum { + LVDSCLK_BP = BIT(7), + HDTVCLK_BP = BIT(6), + HDMICLK_BP = BIT(5), + HDTV_BP = BIT(4), + HDMI_BP = BIT(3), + THRWL = GENMASK(2, 0), +}; + +/* Page 4, Register 0x52 */ +enum { + PGM_ARSTB = BIT(7), + MCU_ARSTB = BIT(6), + MCU_RETB = BIT(2), + RESETIB = BIT(1), + RESETDB = BIT(0), +}; + +struct ch7033_priv { + struct regmap *regmap; + struct drm_bridge *next_bridge; + struct drm_bridge bridge; + struct drm_connector connector; +}; + +#define conn_to_ch7033_priv(x) \ + container_of(x, struct ch7033_priv, connector) +#define bridge_to_ch7033_priv(x) \ + container_of(x, struct ch7033_priv, bridge) + + +static enum drm_connector_status ch7033_connector_detect( + struct drm_connector *connector, bool force) +{ + struct ch7033_priv *priv = conn_to_ch7033_priv(connector); + + return drm_bridge_detect(priv->next_bridge); +} + +static const struct drm_connector_funcs ch7033_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ch7033_connector_detect, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int ch7033_connector_get_modes(struct drm_connector *connector) +{ + struct ch7033_priv *priv = conn_to_ch7033_priv(connector); + struct edid *edid; + int ret; + + edid = drm_bridge_get_edid(priv->next_bridge, connector); + drm_connector_update_edid_property(connector, edid); + if (edid) { + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + } else { + ret = drm_add_modes_noedid(connector, 1920, 1080); + drm_set_preferred_mode(connector, 1024, 768); + } + + return ret; +} + +static struct drm_encoder *ch7033_connector_best_encoder( + struct drm_connector *connector) +{ + struct ch7033_priv *priv = conn_to_ch7033_priv(connector); + + return priv->bridge.encoder; +} + +static const struct drm_connector_helper_funcs ch7033_connector_helper_funcs = { + .get_modes = ch7033_connector_get_modes, + .best_encoder = ch7033_connector_best_encoder, +}; + +static void ch7033_hpd_event(void *arg, enum drm_connector_status status) +{ + struct ch7033_priv *priv = arg; + + if (priv->bridge.dev) + drm_helper_hpd_irq_event(priv->connector.dev); +} + +static int ch7033_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); + struct drm_connector *connector = &priv->connector; + int ret; + + ret = drm_bridge_attach(bridge->encoder, priv->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return 0; + + if (priv->next_bridge->ops & DRM_BRIDGE_OP_DETECT) { + connector->polled = DRM_CONNECTOR_POLL_HPD; + } else { + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + } + + if (priv->next_bridge->ops & DRM_BRIDGE_OP_HPD) { + drm_bridge_hpd_enable(priv->next_bridge, ch7033_hpd_event, + priv); + } + + drm_connector_helper_add(connector, + &ch7033_connector_helper_funcs); + ret = drm_connector_init_with_ddc(bridge->dev, &priv->connector, + &ch7033_connector_funcs, + priv->next_bridge->type, + priv->next_bridge->ddc); + if (ret) { + DRM_ERROR("Failed to initialize connector\n"); + return ret; + } + + return drm_connector_attach_encoder(&priv->connector, bridge->encoder); +} + +static void ch7033_bridge_detach(struct drm_bridge *bridge) +{ + struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); + + if (priv->next_bridge->ops & DRM_BRIDGE_OP_HPD) + drm_bridge_hpd_disable(priv->next_bridge); + drm_connector_cleanup(&priv->connector); +} + +static enum drm_mode_status ch7033_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_mode *mode) +{ + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + if (mode->hdisplay >= 1920) + return MODE_BAD_HVALUE; + if (mode->vdisplay >= 1080) + return MODE_BAD_VVALUE; + return MODE_OK; +} + +static void ch7033_bridge_disable(struct drm_bridge *bridge) +{ + struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); + + regmap_write(priv->regmap, 0x03, 0x04); + regmap_update_bits(priv->regmap, 0x52, RESETDB, 0x00); +} + +static void ch7033_bridge_enable(struct drm_bridge *bridge) +{ + struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); + + regmap_write(priv->regmap, 0x03, 0x04); + regmap_update_bits(priv->regmap, 0x52, RESETDB, RESETDB); +} + +static void ch7033_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct ch7033_priv *priv = bridge_to_ch7033_priv(bridge); + int hbporch = mode->hsync_start - mode->hdisplay; + int hsynclen = mode->hsync_end - mode->hsync_start; + int vbporch = mode->vsync_start - mode->vdisplay; + int vsynclen = mode->vsync_end - mode->vsync_start; + + /* + * Page 4 + */ + regmap_write(priv->regmap, 0x03, 0x04); + + /* Turn everything off to set all the registers to their defaults. */ + regmap_write(priv->regmap, 0x52, 0x00); + /* Bring I/O block up. */ + regmap_write(priv->regmap, 0x52, RESETIB); + + /* + * Page 0 + */ + regmap_write(priv->regmap, 0x03, 0x00); + + /* Bring up parts we need from the power down. */ + regmap_update_bits(priv->regmap, 0x07, DRI_PD | IO_PD, 0); + regmap_update_bits(priv->regmap, 0x08, DRI_PDDRI | PDDAC | PANEN, 0); + regmap_update_bits(priv->regmap, 0x09, DPD | GCKOFF | + HDMI_PD | VGA_PD, 0); + regmap_update_bits(priv->regmap, 0x0a, HD_DVIB, 0); + + /* Horizontal input timing. */ + regmap_write(priv->regmap, 0x0b, (mode->htotal >> 8) << 3 | + (mode->hdisplay >> 8)); + regmap_write(priv->regmap, 0x0c, mode->hdisplay); + regmap_write(priv->regmap, 0x0d, mode->htotal); + regmap_write(priv->regmap, 0x0e, (hsynclen >> 8) << 3 | + (hbporch >> 8)); + regmap_write(priv->regmap, 0x0f, hbporch); + regmap_write(priv->regmap, 0x10, hsynclen); + + /* Vertical input timing. */ + regmap_write(priv->regmap, 0x11, (mode->vtotal >> 8) << 3 | + (mode->vdisplay >> 8)); + regmap_write(priv->regmap, 0x12, mode->vdisplay); + regmap_write(priv->regmap, 0x13, mode->vtotal); + regmap_write(priv->regmap, 0x14, ((vsynclen >> 8) << 3) | + (vbporch >> 8)); + regmap_write(priv->regmap, 0x15, vbporch); + regmap_write(priv->regmap, 0x16, vsynclen); + + /* Input color swap. */ + regmap_update_bits(priv->regmap, 0x18, SWAP, BYTE_SWAP_BGR); + + /* Input clock and sync polarity. */ + regmap_update_bits(priv->regmap, 0x19, 0x1, mode->clock >> 16); + regmap_update_bits(priv->regmap, 0x19, HPO_I | VPO_I | GCLKFREQ, + (mode->flags & DRM_MODE_FLAG_PHSYNC) ? HPO_I : 0 | + (mode->flags & DRM_MODE_FLAG_PVSYNC) ? VPO_I : 0 | + mode->clock >> 16); + regmap_write(priv->regmap, 0x1a, mode->clock >> 8); + regmap_write(priv->regmap, 0x1b, mode->clock); + + /* Horizontal output timing. */ + regmap_write(priv->regmap, 0x1f, (mode->htotal >> 8) << 3 | + (mode->hdisplay >> 8)); + regmap_write(priv->regmap, 0x20, mode->hdisplay); + regmap_write(priv->regmap, 0x21, mode->htotal); + + /* Vertical output timing. */ + regmap_write(priv->regmap, 0x25, (mode->vtotal >> 8) << 3 | + (mode->vdisplay >> 8)); + regmap_write(priv->regmap, 0x26, mode->vdisplay); + regmap_write(priv->regmap, 0x27, mode->vtotal); + + /* VGA channel bypass */ + regmap_update_bits(priv->regmap, 0x2b, VFMT, 9); + + /* Output sync polarity. */ + regmap_update_bits(priv->regmap, 0x2e, HPO_O | VPO_O, + (mode->flags & DRM_MODE_FLAG_PHSYNC) ? HPO_O : 0 | + (mode->flags & DRM_MODE_FLAG_PVSYNC) ? VPO_O : 0); + + /* HDMI horizontal output timing. */ + regmap_update_bits(priv->regmap, 0x54, HWO_HDMI_HI | HOO_HDMI_HI, + (hsynclen >> 8) << 3 | + (hbporch >> 8)); + regmap_write(priv->regmap, 0x55, hbporch); + regmap_write(priv->regmap, 0x56, hsynclen); + + /* HDMI vertical output timing. */ + regmap_update_bits(priv->regmap, 0x57, VWO_HDMI_HI | VOO_HDMI_HI, + (vsynclen >> 8) << 3 | + (vbporch >> 8)); + regmap_write(priv->regmap, 0x58, vbporch); + regmap_write(priv->regmap, 0x59, vsynclen); + + /* Pick HDMI, not LVDS. */ + regmap_update_bits(priv->regmap, 0x7e, HDMI_LVDS_SEL, HDMI_LVDS_SEL); + + /* + * Page 1 + */ + regmap_write(priv->regmap, 0x03, 0x01); + + /* No idea what these do, but VGA is wobbly and blinky without them. */ + regmap_update_bits(priv->regmap, 0x07, CKINV, CKINV); + regmap_update_bits(priv->regmap, 0x08, DISPON, DISPON); + + /* DRI PLL */ + regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_DIVSEL, DRI_PLL_DIVSEL); + if (mode->clock <= 40000) { + regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | + DRI_PLL_N1_0 | + DRI_PLL_N3_1 | + DRI_PLL_N3_0, + 0); + } else if (mode->clock < 80000) { + regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | + DRI_PLL_N1_0 | + DRI_PLL_N3_1 | + DRI_PLL_N3_0, + DRI_PLL_N3_0 | + DRI_PLL_N1_0); + } else { + regmap_update_bits(priv->regmap, 0x0c, DRI_PLL_N1_1 | + DRI_PLL_N1_0 | + DRI_PLL_N3_1 | + DRI_PLL_N3_0, + DRI_PLL_N3_1 | + DRI_PLL_N1_1); + } + + /* This seems to be color calibration for VGA. */ + regmap_write(priv->regmap, 0x64, 0x29); /* LSB Blue */ + regmap_write(priv->regmap, 0x65, 0x29); /* LSB Green */ + regmap_write(priv->regmap, 0x66, 0x29); /* LSB Red */ + regmap_write(priv->regmap, 0x67, 0x00); /* MSB Blue */ + regmap_write(priv->regmap, 0x68, 0x00); /* MSB Green */ + regmap_write(priv->regmap, 0x69, 0x00); /* MSB Red */ + + regmap_update_bits(priv->regmap, 0x6b, DRI_PD_SER, 0x00); + regmap_update_bits(priv->regmap, 0x6c, DRI_PLL_PD, 0x00); + + /* + * Page 3 + */ + regmap_write(priv->regmap, 0x03, 0x03); + + /* More bypasses and apparently another HDMI/LVDS selector. */ + regmap_update_bits(priv->regmap, 0x28, VGACLK_BP | HM_LV_SEL, + VGACLK_BP | HM_LV_SEL); + regmap_update_bits(priv->regmap, 0x2a, HDMICLK_BP | HDMI_BP, + HDMICLK_BP | HDMI_BP); + + /* + * Page 4 + */ + regmap_write(priv->regmap, 0x03, 0x04); + + /* Output clock. */ + regmap_write(priv->regmap, 0x10, mode->clock >> 16); + regmap_write(priv->regmap, 0x11, mode->clock >> 8); + regmap_write(priv->regmap, 0x12, mode->clock); +} + +static const struct drm_bridge_funcs ch7033_bridge_funcs = { + .attach = ch7033_bridge_attach, + .detach = ch7033_bridge_detach, + .mode_valid = ch7033_bridge_mode_valid, + .disable = ch7033_bridge_disable, + .enable = ch7033_bridge_enable, + .mode_set = ch7033_bridge_mode_set, +}; + +static const struct regmap_config ch7033_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x7f, +}; + +static int ch7033_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ch7033_priv *priv; + unsigned int val; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + ret = drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, + &priv->next_bridge); + if (ret) + return ret; + + priv->regmap = devm_regmap_init_i2c(client, &ch7033_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&client->dev, "regmap init failed\n"); + return PTR_ERR(priv->regmap); + } + + ret = regmap_read(priv->regmap, 0x00, &val); + if (ret < 0) { + dev_err(&client->dev, "error reading the model id: %d\n", ret); + return ret; + } + if ((val & 0xf7) != 0x56) { + dev_err(&client->dev, "the device is not a ch7033\n"); + return -ENODEV; + } + + regmap_write(priv->regmap, 0x03, 0x04); + ret = regmap_read(priv->regmap, 0x51, &val); + if (ret < 0) { + dev_err(&client->dev, "error reading the model id: %d\n", ret); + return ret; + } + if ((val & 0x0f) != 3) { + dev_err(&client->dev, "unknown revision %u\n", val); + return -ENODEV; + } + + INIT_LIST_HEAD(&priv->bridge.list); + priv->bridge.funcs = &ch7033_bridge_funcs; + priv->bridge.of_node = dev->of_node; + drm_bridge_add(&priv->bridge); + + dev_info(dev, "Chrontel CH7033 Video Encoder\n"); + return 0; +} + +static int ch7033_remove(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ch7033_priv *priv = dev_get_drvdata(dev); + + drm_bridge_remove(&priv->bridge); + + return 0; +} + +static const struct of_device_id ch7033_dt_ids[] = { + { .compatible = "chrontel,ch7033", }, + { } +}; +MODULE_DEVICE_TABLE(of, ch7033_dt_ids); + +static const struct i2c_device_id ch7033_ids[] = { + { "ch7033", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ch7033_ids); + +static struct i2c_driver ch7033_driver = { + .probe = ch7033_probe, + .remove = ch7033_remove, + .driver = { + .name = "ch7033", + .of_match_table = of_match_ptr(ch7033_dt_ids), + }, + .id_table = ch7033_ids, +}; + +module_i2c_driver(ch7033_driver); + +MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); +MODULE_DESCRIPTION("Chrontel CH7033 Video Encoder Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c new file mode 100644 index 000000000000..b14d725bf609 --- /dev/null +++ b/drivers/gpu/drm/bridge/nwl-dsi.c @@ -0,0 +1,1213 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * i.MX8 NWL MIPI DSI host driver + * + * Copyright (C) 2017 NXP + * Copyright (C) 2020 Purism SPC + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/math64.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/mux/consumer.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/sys_soc.h> +#include <linux/time64.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> + +#include <video/mipi_display.h> + +#include "nwl-dsi.h" + +#define DRV_NAME "nwl-dsi" + +/* i.MX8 NWL quirks */ +/* i.MX8MQ errata E11418 */ +#define E11418_HS_MODE_QUIRK BIT(0) + +#define NWL_DSI_MIPI_FIFO_TIMEOUT msecs_to_jiffies(500) + +enum transfer_direction { + DSI_PACKET_SEND, + DSI_PACKET_RECEIVE, +}; + +#define NWL_DSI_ENDPOINT_LCDIF 0 +#define NWL_DSI_ENDPOINT_DCSS 1 + +struct nwl_dsi_plat_clk_config { + const char *id; + struct clk *clk; + bool present; +}; + +struct nwl_dsi_transfer { + const struct mipi_dsi_msg *msg; + struct mipi_dsi_packet packet; + struct completion completed; + + int status; /* status of transmission */ + enum transfer_direction direction; + bool need_bta; + u8 cmd; + u16 rx_word_count; + size_t tx_len; /* in bytes */ + size_t rx_len; /* in bytes */ +}; + +struct nwl_dsi { + struct drm_bridge bridge; + struct mipi_dsi_host dsi_host; + struct drm_bridge *panel_bridge; + struct device *dev; + struct phy *phy; + union phy_configure_opts phy_cfg; + unsigned int quirks; + + struct regmap *regmap; + int irq; + /* + * The DSI host controller needs this reset sequence according to NWL: + * 1. Deassert pclk reset to get access to DSI regs + * 2. Configure DSI Host and DPHY and enable DPHY + * 3. Deassert ESC and BYTE resets to allow host TX operations) + * 4. Send DSI cmds to configure peripheral (handled by panel drv) + * 5. Deassert DPI reset so DPI receives pixels and starts sending + * DSI data + * + * TODO: Since panel_bridges do their DSI setup in enable we + * currently have 4. and 5. swapped. + */ + struct reset_control *rst_byte; + struct reset_control *rst_esc; + struct reset_control *rst_dpi; + struct reset_control *rst_pclk; + struct mux_control *mux; + + /* DSI clocks */ + struct clk *phy_ref_clk; + struct clk *rx_esc_clk; + struct clk *tx_esc_clk; + struct clk *core_clk; + /* + * hardware bug: the i.MX8MQ needs this clock on during reset + * even when not using LCDIF. + */ + struct clk *lcdif_clk; + + /* dsi lanes */ + u32 lanes; + enum mipi_dsi_pixel_format format; + struct drm_display_mode mode; + unsigned long dsi_mode_flags; + int error; + + struct nwl_dsi_transfer *xfer; +}; + +static const struct regmap_config nwl_dsi_regmap_config = { + .reg_bits = 16, + .val_bits = 32, + .reg_stride = 4, + .max_register = NWL_DSI_IRQ_MASK2, + .name = DRV_NAME, +}; + +static inline struct nwl_dsi *bridge_to_dsi(struct drm_bridge *bridge) +{ + return container_of(bridge, struct nwl_dsi, bridge); +} + +static int nwl_dsi_clear_error(struct nwl_dsi *dsi) +{ + int ret = dsi->error; + + dsi->error = 0; + return ret; +} + +static void nwl_dsi_write(struct nwl_dsi *dsi, unsigned int reg, u32 val) +{ + int ret; + + if (dsi->error) + return; + + ret = regmap_write(dsi->regmap, reg, val); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, + "Failed to write NWL DSI reg 0x%x: %d\n", reg, + ret); + dsi->error = ret; + } +} + +static u32 nwl_dsi_read(struct nwl_dsi *dsi, u32 reg) +{ + unsigned int val; + int ret; + + if (dsi->error) + return 0; + + ret = regmap_read(dsi->regmap, reg, &val); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to read NWL DSI reg 0x%x: %d\n", + reg, ret); + dsi->error = ret; + } + return val; +} + +static int nwl_dsi_get_dpi_pixel_format(enum mipi_dsi_pixel_format format) +{ + switch (format) { + case MIPI_DSI_FMT_RGB565: + return NWL_DSI_PIXEL_FORMAT_16; + case MIPI_DSI_FMT_RGB666: + return NWL_DSI_PIXEL_FORMAT_18L; + case MIPI_DSI_FMT_RGB666_PACKED: + return NWL_DSI_PIXEL_FORMAT_18; + case MIPI_DSI_FMT_RGB888: + return NWL_DSI_PIXEL_FORMAT_24; + default: + return -EINVAL; + } +} + +/* + * ps2bc - Picoseconds to byte clock cycles + */ +static u32 ps2bc(struct nwl_dsi *dsi, unsigned long long ps) +{ + u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + + return DIV64_U64_ROUND_UP(ps * dsi->mode.clock * bpp, + dsi->lanes * 8 * NSEC_PER_SEC); +} + +/* + * ui2bc - UI time periods to byte clock cycles + */ +static u32 ui2bc(struct nwl_dsi *dsi, unsigned long long ui) +{ + u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + + return DIV64_U64_ROUND_UP(ui * dsi->lanes, + dsi->mode.clock * 1000 * bpp); +} + +/* + * us2bc - micro seconds to lp clock cycles + */ +static u32 us2lp(u32 lp_clk_rate, unsigned long us) +{ + return DIV_ROUND_UP(us * lp_clk_rate, USEC_PER_SEC); +} + +static int nwl_dsi_config_host(struct nwl_dsi *dsi) +{ + u32 cycles; + struct phy_configure_opts_mipi_dphy *cfg = &dsi->phy_cfg.mipi_dphy; + + if (dsi->lanes < 1 || dsi->lanes > 4) + return -EINVAL; + + DRM_DEV_DEBUG_DRIVER(dsi->dev, "DSI Lanes %d\n", dsi->lanes); + nwl_dsi_write(dsi, NWL_DSI_CFG_NUM_LANES, dsi->lanes - 1); + + if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) { + nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x01); + nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x01); + } else { + nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x00); + nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x00); + } + + /* values in byte clock cycles */ + cycles = ui2bc(dsi, cfg->clk_pre); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_pre: 0x%x\n", cycles); + nwl_dsi_write(dsi, NWL_DSI_CFG_T_PRE, cycles); + cycles = ps2bc(dsi, cfg->lpx + cfg->clk_prepare + cfg->clk_zero); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap (pre): 0x%x\n", cycles); + cycles += ui2bc(dsi, cfg->clk_pre); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_post: 0x%x\n", cycles); + nwl_dsi_write(dsi, NWL_DSI_CFG_T_POST, cycles); + cycles = ps2bc(dsi, cfg->hs_exit); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap: 0x%x\n", cycles); + nwl_dsi_write(dsi, NWL_DSI_CFG_TX_GAP, cycles); + + nwl_dsi_write(dsi, NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP, 0x01); + nwl_dsi_write(dsi, NWL_DSI_CFG_HTX_TO_COUNT, 0x00); + nwl_dsi_write(dsi, NWL_DSI_CFG_LRX_H_TO_COUNT, 0x00); + nwl_dsi_write(dsi, NWL_DSI_CFG_BTA_H_TO_COUNT, 0x00); + /* In LP clock cycles */ + cycles = us2lp(cfg->lp_clk_rate, cfg->wakeup); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_twakeup: 0x%x\n", cycles); + nwl_dsi_write(dsi, NWL_DSI_CFG_TWAKEUP, cycles); + + return nwl_dsi_clear_error(dsi); +} + +static int nwl_dsi_config_dpi(struct nwl_dsi *dsi) +{ + u32 mode; + int color_format; + bool burst_mode; + int hfront_porch, hback_porch, vfront_porch, vback_porch; + int hsync_len, vsync_len; + + hfront_porch = dsi->mode.hsync_start - dsi->mode.hdisplay; + hsync_len = dsi->mode.hsync_end - dsi->mode.hsync_start; + hback_porch = dsi->mode.htotal - dsi->mode.hsync_end; + + vfront_porch = dsi->mode.vsync_start - dsi->mode.vdisplay; + vsync_len = dsi->mode.vsync_end - dsi->mode.vsync_start; + vback_porch = dsi->mode.vtotal - dsi->mode.vsync_end; + + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hfront_porch = %d\n", hfront_porch); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hback_porch = %d\n", hback_porch); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hsync_len = %d\n", hsync_len); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "hdisplay = %d\n", dsi->mode.hdisplay); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vfront_porch = %d\n", vfront_porch); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vback_porch = %d\n", vback_porch); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vsync_len = %d\n", vsync_len); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "vactive = %d\n", dsi->mode.vdisplay); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "clock = %d kHz\n", dsi->mode.clock); + + color_format = nwl_dsi_get_dpi_pixel_format(dsi->format); + if (color_format < 0) { + DRM_DEV_ERROR(dsi->dev, "Invalid color format 0x%x\n", + dsi->format); + return color_format; + } + DRM_DEV_DEBUG_DRIVER(dsi->dev, "pixel fmt = %d\n", dsi->format); + + nwl_dsi_write(dsi, NWL_DSI_INTERFACE_COLOR_CODING, NWL_DSI_DPI_24_BIT); + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FORMAT, color_format); + /* + * Adjusting input polarity based on the video mode results in + * a black screen so always pick active low: + */ + nwl_dsi_write(dsi, NWL_DSI_VSYNC_POLARITY, + NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW); + nwl_dsi_write(dsi, NWL_DSI_HSYNC_POLARITY, + NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW); + + burst_mode = (dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_BURST) && + !(dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE); + + if (burst_mode) { + nwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE, NWL_DSI_VM_BURST_MODE); + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL, 256); + } else { + mode = ((dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) ? + NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSES : + NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_EVENTS); + nwl_dsi_write(dsi, NWL_DSI_VIDEO_MODE, mode); + nwl_dsi_write(dsi, NWL_DSI_PIXEL_FIFO_SEND_LEVEL, + dsi->mode.hdisplay); + } + + nwl_dsi_write(dsi, NWL_DSI_HFP, hfront_porch); + nwl_dsi_write(dsi, NWL_DSI_HBP, hback_porch); + nwl_dsi_write(dsi, NWL_DSI_HSA, hsync_len); + + nwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0); + nwl_dsi_write(dsi, NWL_DSI_BLLP_MODE, 0x1); + nwl_dsi_write(dsi, NWL_DSI_USE_NULL_PKT_BLLP, 0x0); + nwl_dsi_write(dsi, NWL_DSI_VC, 0x0); + + nwl_dsi_write(dsi, NWL_DSI_PIXEL_PAYLOAD_SIZE, dsi->mode.hdisplay); + nwl_dsi_write(dsi, NWL_DSI_VACTIVE, dsi->mode.vdisplay - 1); + nwl_dsi_write(dsi, NWL_DSI_VBP, vback_porch); + nwl_dsi_write(dsi, NWL_DSI_VFP, vfront_porch); + + return nwl_dsi_clear_error(dsi); +} + +static int nwl_dsi_init_interrupts(struct nwl_dsi *dsi) +{ + u32 irq_enable; + + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, 0xffffffff); + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK2, 0x7); + + irq_enable = ~(u32)(NWL_DSI_TX_PKT_DONE_MASK | + NWL_DSI_RX_PKT_HDR_RCVD_MASK | + NWL_DSI_TX_FIFO_OVFLW_MASK | + NWL_DSI_HS_TX_TIMEOUT_MASK); + + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, irq_enable); + + return nwl_dsi_clear_error(dsi); +} + +static int nwl_dsi_host_attach(struct mipi_dsi_host *dsi_host, + struct mipi_dsi_device *device) +{ + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host); + struct device *dev = dsi->dev; + + DRM_DEV_INFO(dev, "lanes=%u, format=0x%x flags=0x%lx\n", device->lanes, + device->format, device->mode_flags); + + if (device->lanes < 1 || device->lanes > 4) + return -EINVAL; + + dsi->lanes = device->lanes; + dsi->format = device->format; + dsi->dsi_mode_flags = device->mode_flags; + + return 0; +} + +static bool nwl_dsi_read_packet(struct nwl_dsi *dsi, u32 status) +{ + struct device *dev = dsi->dev; + struct nwl_dsi_transfer *xfer = dsi->xfer; + int err; + u8 *payload = xfer->msg->rx_buf; + u32 val; + u16 word_count; + u8 channel; + u8 data_type; + + xfer->status = 0; + + if (xfer->rx_word_count == 0) { + if (!(status & NWL_DSI_RX_PKT_HDR_RCVD)) + return false; + /* Get the RX header and parse it */ + val = nwl_dsi_read(dsi, NWL_DSI_RX_PKT_HEADER); + err = nwl_dsi_clear_error(dsi); + if (err) + xfer->status = err; + word_count = NWL_DSI_WC(val); + channel = NWL_DSI_RX_VC(val); + data_type = NWL_DSI_RX_DT(val); + + if (channel != xfer->msg->channel) { + DRM_DEV_ERROR(dev, + "[%02X] Channel mismatch (%u != %u)\n", + xfer->cmd, channel, xfer->msg->channel); + xfer->status = -EINVAL; + return true; + } + + switch (data_type) { + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + fallthrough; + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + if (xfer->msg->rx_len > 1) { + /* read second byte */ + payload[1] = word_count >> 8; + ++xfer->rx_len; + } + fallthrough; + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + fallthrough; + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + if (xfer->msg->rx_len > 0) { + /* read first byte */ + payload[0] = word_count & 0xff; + ++xfer->rx_len; + } + xfer->status = xfer->rx_len; + return true; + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + word_count &= 0xff; + DRM_DEV_ERROR(dev, "[%02X] DSI error report: 0x%02x\n", + xfer->cmd, word_count); + xfer->status = -EPROTO; + return true; + } + + if (word_count > xfer->msg->rx_len) { + DRM_DEV_ERROR(dev, + "[%02X] Receive buffer too small: %zu (< %u)\n", + xfer->cmd, xfer->msg->rx_len, word_count); + xfer->status = -EINVAL; + return true; + } + + xfer->rx_word_count = word_count; + } else { + /* Set word_count from previous header read */ + word_count = xfer->rx_word_count; + } + + /* If RX payload is not yet received, wait for it */ + if (!(status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)) + return false; + + /* Read the RX payload */ + while (word_count >= 4) { + val = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD); + payload[0] = (val >> 0) & 0xff; + payload[1] = (val >> 8) & 0xff; + payload[2] = (val >> 16) & 0xff; + payload[3] = (val >> 24) & 0xff; + payload += 4; + xfer->rx_len += 4; + word_count -= 4; + } + + if (word_count > 0) { + val = nwl_dsi_read(dsi, NWL_DSI_RX_PAYLOAD); + switch (word_count) { + case 3: + payload[2] = (val >> 16) & 0xff; + ++xfer->rx_len; + fallthrough; + case 2: + payload[1] = (val >> 8) & 0xff; + ++xfer->rx_len; + fallthrough; + case 1: + payload[0] = (val >> 0) & 0xff; + ++xfer->rx_len; + break; + } + } + + xfer->status = xfer->rx_len; + err = nwl_dsi_clear_error(dsi); + if (err) + xfer->status = err; + + return true; +} + +static void nwl_dsi_finish_transmission(struct nwl_dsi *dsi, u32 status) +{ + struct nwl_dsi_transfer *xfer = dsi->xfer; + bool end_packet = false; + + if (!xfer) + return; + + if (xfer->direction == DSI_PACKET_SEND && + status & NWL_DSI_TX_PKT_DONE) { + xfer->status = xfer->tx_len; + end_packet = true; + } else if (status & NWL_DSI_DPHY_DIRECTION && + ((status & (NWL_DSI_RX_PKT_HDR_RCVD | + NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD)))) { + end_packet = nwl_dsi_read_packet(dsi, status); + } + + if (end_packet) + complete(&xfer->completed); +} + +static void nwl_dsi_begin_transmission(struct nwl_dsi *dsi) +{ + struct nwl_dsi_transfer *xfer = dsi->xfer; + struct mipi_dsi_packet *pkt = &xfer->packet; + const u8 *payload; + size_t length; + u16 word_count; + u8 hs_mode; + u32 val; + u32 hs_workaround = 0; + + /* Send the payload, if any */ + length = pkt->payload_length; + payload = pkt->payload; + + while (length >= 4) { + val = *(u32 *)payload; + hs_workaround |= !(val & 0xFFFF00); + nwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val); + payload += 4; + length -= 4; + } + /* Send the rest of the payload */ + val = 0; + switch (length) { + case 3: + val |= payload[2] << 16; + fallthrough; + case 2: + val |= payload[1] << 8; + hs_workaround |= !(val & 0xFFFF00); + fallthrough; + case 1: + val |= payload[0]; + nwl_dsi_write(dsi, NWL_DSI_TX_PAYLOAD, val); + break; + } + xfer->tx_len = pkt->payload_length; + + /* + * Send the header + * header[0] = Virtual Channel + Data Type + * header[1] = Word Count LSB (LP) or first param (SP) + * header[2] = Word Count MSB (LP) or second param (SP) + */ + word_count = pkt->header[1] | (pkt->header[2] << 8); + if (hs_workaround && (dsi->quirks & E11418_HS_MODE_QUIRK)) { + DRM_DEV_DEBUG_DRIVER(dsi->dev, + "Using hs mode workaround for cmd 0x%x\n", + xfer->cmd); + hs_mode = 1; + } else { + hs_mode = (xfer->msg->flags & MIPI_DSI_MSG_USE_LPM) ? 0 : 1; + } + val = NWL_DSI_WC(word_count) | NWL_DSI_TX_VC(xfer->msg->channel) | + NWL_DSI_TX_DT(xfer->msg->type) | NWL_DSI_HS_SEL(hs_mode) | + NWL_DSI_BTA_TX(xfer->need_bta); + nwl_dsi_write(dsi, NWL_DSI_PKT_CONTROL, val); + + /* Send packet command */ + nwl_dsi_write(dsi, NWL_DSI_SEND_PACKET, 0x1); +} + +static ssize_t nwl_dsi_host_transfer(struct mipi_dsi_host *dsi_host, + const struct mipi_dsi_msg *msg) +{ + struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host); + struct nwl_dsi_transfer xfer; + ssize_t ret = 0; + + /* Create packet to be sent */ + dsi->xfer = &xfer; + ret = mipi_dsi_create_packet(&xfer.packet, msg); + if (ret < 0) { + dsi->xfer = NULL; + return ret; + } + + if ((msg->type & MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM || + msg->type & MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM || + msg->type & MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM || + msg->type & MIPI_DSI_DCS_READ) && + msg->rx_len > 0 && msg->rx_buf) + xfer.direction = DSI_PACKET_RECEIVE; + else + xfer.direction = DSI_PACKET_SEND; + + xfer.need_bta = (xfer.direction == DSI_PACKET_RECEIVE); + xfer.need_bta |= (msg->flags & MIPI_DSI_MSG_REQ_ACK) ? 1 : 0; + xfer.msg = msg; + xfer.status = -ETIMEDOUT; + xfer.rx_word_count = 0; + xfer.rx_len = 0; + xfer.cmd = 0x00; + if (msg->tx_len > 0) + xfer.cmd = ((u8 *)(msg->tx_buf))[0]; + init_completion(&xfer.completed); + + ret = clk_prepare_enable(dsi->rx_esc_clk); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to enable rx_esc clk: %zd\n", + ret); + return ret; + } + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled rx_esc clk @%lu Hz\n", + clk_get_rate(dsi->rx_esc_clk)); + + /* Initiate the DSI packet transmision */ + nwl_dsi_begin_transmission(dsi); + + if (!wait_for_completion_timeout(&xfer.completed, + NWL_DSI_MIPI_FIFO_TIMEOUT)) { + DRM_DEV_ERROR(dsi_host->dev, "[%02X] DSI transfer timed out\n", + xfer.cmd); + ret = -ETIMEDOUT; + } else { + ret = xfer.status; + } + + clk_disable_unprepare(dsi->rx_esc_clk); + + return ret; +} + +static const struct mipi_dsi_host_ops nwl_dsi_host_ops = { + .attach = nwl_dsi_host_attach, + .transfer = nwl_dsi_host_transfer, +}; + +static irqreturn_t nwl_dsi_irq_handler(int irq, void *data) +{ + u32 irq_status; + struct nwl_dsi *dsi = data; + + irq_status = nwl_dsi_read(dsi, NWL_DSI_IRQ_STATUS); + + if (irq_status & NWL_DSI_TX_FIFO_OVFLW) + DRM_DEV_ERROR_RATELIMITED(dsi->dev, "tx fifo overflow\n"); + + if (irq_status & NWL_DSI_HS_TX_TIMEOUT) + DRM_DEV_ERROR_RATELIMITED(dsi->dev, "HS tx timeout\n"); + + if (irq_status & NWL_DSI_TX_PKT_DONE || + irq_status & NWL_DSI_RX_PKT_HDR_RCVD || + irq_status & NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD) + nwl_dsi_finish_transmission(dsi, irq_status); + + return IRQ_HANDLED; +} + +static int nwl_dsi_enable(struct nwl_dsi *dsi) +{ + struct device *dev = dsi->dev; + union phy_configure_opts *phy_cfg = &dsi->phy_cfg; + int ret; + + if (!dsi->lanes) { + DRM_DEV_ERROR(dev, "Need DSI lanes: %d\n", dsi->lanes); + return -EINVAL; + } + + ret = phy_init(dsi->phy); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to init DSI phy: %d\n", ret); + return ret; + } + + ret = phy_configure(dsi->phy, phy_cfg); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to configure DSI phy: %d\n", ret); + goto uninit_phy; + } + + ret = clk_prepare_enable(dsi->tx_esc_clk); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to enable tx_esc clk: %d\n", + ret); + goto uninit_phy; + } + DRM_DEV_DEBUG_DRIVER(dsi->dev, "Enabled tx_esc clk @%lu Hz\n", + clk_get_rate(dsi->tx_esc_clk)); + + ret = nwl_dsi_config_host(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set up DSI: %d", ret); + goto disable_clock; + } + + ret = nwl_dsi_config_dpi(dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set up DPI: %d", ret); + goto disable_clock; + } + + ret = phy_power_on(dsi->phy); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to power on DPHY (%d)\n", ret); + goto disable_clock; + } + + ret = nwl_dsi_init_interrupts(dsi); + if (ret < 0) + goto power_off_phy; + + return ret; + +power_off_phy: + phy_power_off(dsi->phy); +disable_clock: + clk_disable_unprepare(dsi->tx_esc_clk); +uninit_phy: + phy_exit(dsi->phy); + + return ret; +} + +static int nwl_dsi_disable(struct nwl_dsi *dsi) +{ + struct device *dev = dsi->dev; + + DRM_DEV_DEBUG_DRIVER(dev, "Disabling clocks and phy\n"); + + phy_power_off(dsi->phy); + phy_exit(dsi->phy); + + /* Disabling the clock before the phy breaks enabling dsi again */ + clk_disable_unprepare(dsi->tx_esc_clk); + + return 0; +} + +static void nwl_dsi_bridge_disable(struct drm_bridge *bridge) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + int ret; + + nwl_dsi_disable(dsi); + + ret = reset_control_assert(dsi->rst_dpi); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to assert DPI: %d\n", ret); + return; + } + ret = reset_control_assert(dsi->rst_byte); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to assert ESC: %d\n", ret); + return; + } + ret = reset_control_assert(dsi->rst_esc); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to assert BYTE: %d\n", ret); + return; + } + ret = reset_control_assert(dsi->rst_pclk); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to assert PCLK: %d\n", ret); + return; + } + + clk_disable_unprepare(dsi->core_clk); + clk_disable_unprepare(dsi->lcdif_clk); + + pm_runtime_put(dsi->dev); +} + +static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi, + const struct drm_display_mode *mode, + union phy_configure_opts *phy_opts) +{ + unsigned long rate; + int ret; + + if (dsi->lanes < 1 || dsi->lanes > 4) + return -EINVAL; + + /* + * So far the DPHY spec minimal timings work for both mixel + * dphy and nwl dsi host + */ + ret = phy_mipi_dphy_get_default_config(mode->clock * 1000, + mipi_dsi_pixel_format_to_bpp(dsi->format), dsi->lanes, + &phy_opts->mipi_dphy); + if (ret < 0) + return ret; + + rate = clk_get_rate(dsi->tx_esc_clk); + DRM_DEV_DEBUG_DRIVER(dsi->dev, "LP clk is @%lu Hz\n", rate); + phy_opts->mipi_dphy.lp_clk_rate = rate; + + return 0; +} + +static bool nwl_dsi_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* At least LCDIF + NWL needs active high sync */ + adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); + adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC); + + return true; +} + +static enum drm_mode_status +nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_mode *mode) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + + if (mode->clock * bpp > 15000000 * dsi->lanes) + return MODE_CLOCK_HIGH; + + if (mode->clock * bpp < 80000 * dsi->lanes) + return MODE_CLOCK_LOW; + + return MODE_OK; +} + +static void +nwl_dsi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + struct device *dev = dsi->dev; + union phy_configure_opts new_cfg; + unsigned long phy_ref_rate; + int ret; + + ret = nwl_dsi_get_dphy_params(dsi, adjusted_mode, &new_cfg); + if (ret < 0) + return; + + /* + * If hs clock is unchanged, we're all good - all parameters are + * derived from it atm. + */ + if (new_cfg.mipi_dphy.hs_clk_rate == dsi->phy_cfg.mipi_dphy.hs_clk_rate) + return; + + phy_ref_rate = clk_get_rate(dsi->phy_ref_clk); + DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n", phy_ref_rate); + /* Save the new desired phy config */ + memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg)); + + memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode)); + drm_mode_debug_printmodeline(adjusted_mode); +} + +static void nwl_dsi_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + int ret; + + pm_runtime_get_sync(dsi->dev); + + if (clk_prepare_enable(dsi->lcdif_clk) < 0) + return; + if (clk_prepare_enable(dsi->core_clk) < 0) + return; + + /* Step 1 from DSI reset-out instructions */ + ret = reset_control_deassert(dsi->rst_pclk); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to deassert PCLK: %d\n", ret); + return; + } + + /* Step 2 from DSI reset-out instructions */ + nwl_dsi_enable(dsi); + + /* Step 3 from DSI reset-out instructions */ + ret = reset_control_deassert(dsi->rst_esc); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to deassert ESC: %d\n", ret); + return; + } + ret = reset_control_deassert(dsi->rst_byte); + if (ret < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to deassert BYTE: %d\n", ret); + return; + } +} + +static void nwl_dsi_bridge_enable(struct drm_bridge *bridge) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + int ret; + + /* Step 5 from DSI reset-out instructions */ + ret = reset_control_deassert(dsi->rst_dpi); + if (ret < 0) + DRM_DEV_ERROR(dsi->dev, "Failed to deassert DPI: %d\n", ret); +} + +static int nwl_dsi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct nwl_dsi *dsi = bridge_to_dsi(bridge); + struct drm_bridge *panel_bridge; + struct drm_panel *panel; + int ret; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + ret = drm_of_find_panel_or_bridge(dsi->dev->of_node, 1, 0, &panel, + &panel_bridge); + if (ret) + return ret; + + if (panel) { + panel_bridge = drm_panel_bridge_add(panel); + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); + } + dsi->panel_bridge = panel_bridge; + + if (!dsi->panel_bridge) + return -EPROBE_DEFER; + + return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge, + flags); +} + +static void nwl_dsi_bridge_detach(struct drm_bridge *bridge) +{ struct nwl_dsi *dsi = bridge_to_dsi(bridge); + + drm_of_panel_bridge_remove(dsi->dev->of_node, 1, 0); +} + +static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = { + .pre_enable = nwl_dsi_bridge_pre_enable, + .enable = nwl_dsi_bridge_enable, + .disable = nwl_dsi_bridge_disable, + .mode_fixup = nwl_dsi_bridge_mode_fixup, + .mode_set = nwl_dsi_bridge_mode_set, + .mode_valid = nwl_dsi_bridge_mode_valid, + .attach = nwl_dsi_bridge_attach, + .detach = nwl_dsi_bridge_detach, +}; + +static int nwl_dsi_parse_dt(struct nwl_dsi *dsi) +{ + struct platform_device *pdev = to_platform_device(dsi->dev); + struct clk *clk; + void __iomem *base; + int ret; + + dsi->phy = devm_phy_get(dsi->dev, "dphy"); + if (IS_ERR(dsi->phy)) { + ret = PTR_ERR(dsi->phy); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dsi->dev, "Could not get PHY: %d\n", ret); + return ret; + } + + clk = devm_clk_get(dsi->dev, "lcdif"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, "Failed to get lcdif clock: %d\n", + ret); + return ret; + } + dsi->lcdif_clk = clk; + + clk = devm_clk_get(dsi->dev, "core"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, "Failed to get core clock: %d\n", + ret); + return ret; + } + dsi->core_clk = clk; + + clk = devm_clk_get(dsi->dev, "phy_ref"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, "Failed to get phy_ref clock: %d\n", + ret); + return ret; + } + dsi->phy_ref_clk = clk; + + clk = devm_clk_get(dsi->dev, "rx_esc"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, "Failed to get rx_esc clock: %d\n", + ret); + return ret; + } + dsi->rx_esc_clk = clk; + + clk = devm_clk_get(dsi->dev, "tx_esc"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + DRM_DEV_ERROR(dsi->dev, "Failed to get tx_esc clock: %d\n", + ret); + return ret; + } + dsi->tx_esc_clk = clk; + + dsi->mux = devm_mux_control_get(dsi->dev, NULL); + if (IS_ERR(dsi->mux)) { + ret = PTR_ERR(dsi->mux); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dsi->dev, "Failed to get mux: %d\n", ret); + return ret; + } + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + dsi->regmap = + devm_regmap_init_mmio(dsi->dev, base, &nwl_dsi_regmap_config); + if (IS_ERR(dsi->regmap)) { + ret = PTR_ERR(dsi->regmap); + DRM_DEV_ERROR(dsi->dev, "Failed to create NWL DSI regmap: %d\n", + ret); + return ret; + } + + dsi->irq = platform_get_irq(pdev, 0); + if (dsi->irq < 0) { + DRM_DEV_ERROR(dsi->dev, "Failed to get device IRQ: %d\n", + dsi->irq); + return dsi->irq; + } + + dsi->rst_pclk = devm_reset_control_get_exclusive(dsi->dev, "pclk"); + if (IS_ERR(dsi->rst_pclk)) { + DRM_DEV_ERROR(dsi->dev, "Failed to get pclk reset: %ld\n", + PTR_ERR(dsi->rst_pclk)); + return PTR_ERR(dsi->rst_pclk); + } + dsi->rst_byte = devm_reset_control_get_exclusive(dsi->dev, "byte"); + if (IS_ERR(dsi->rst_byte)) { + DRM_DEV_ERROR(dsi->dev, "Failed to get byte reset: %ld\n", + PTR_ERR(dsi->rst_byte)); + return PTR_ERR(dsi->rst_byte); + } + dsi->rst_esc = devm_reset_control_get_exclusive(dsi->dev, "esc"); + if (IS_ERR(dsi->rst_esc)) { + DRM_DEV_ERROR(dsi->dev, "Failed to get esc reset: %ld\n", + PTR_ERR(dsi->rst_esc)); + return PTR_ERR(dsi->rst_esc); + } + dsi->rst_dpi = devm_reset_control_get_exclusive(dsi->dev, "dpi"); + if (IS_ERR(dsi->rst_dpi)) { + DRM_DEV_ERROR(dsi->dev, "Failed to get dpi reset: %ld\n", + PTR_ERR(dsi->rst_dpi)); + return PTR_ERR(dsi->rst_dpi); + } + return 0; +} + +static int nwl_dsi_select_input(struct nwl_dsi *dsi) +{ + struct device_node *remote; + u32 use_dcss = 1; + int ret; + + remote = of_graph_get_remote_node(dsi->dev->of_node, 0, + NWL_DSI_ENDPOINT_LCDIF); + if (remote) { + use_dcss = 0; + } else { + remote = of_graph_get_remote_node(dsi->dev->of_node, 0, + NWL_DSI_ENDPOINT_DCSS); + if (!remote) { + DRM_DEV_ERROR(dsi->dev, + "No valid input endpoint found\n"); + return -EINVAL; + } + } + + DRM_DEV_INFO(dsi->dev, "Using %s as input source\n", + (use_dcss) ? "DCSS" : "LCDIF"); + ret = mux_control_try_select(dsi->mux, use_dcss); + if (ret < 0) + DRM_DEV_ERROR(dsi->dev, "Failed to select input: %d\n", ret); + + of_node_put(remote); + return ret; +} + +static int nwl_dsi_deselect_input(struct nwl_dsi *dsi) +{ + int ret; + + ret = mux_control_deselect(dsi->mux); + if (ret < 0) + DRM_DEV_ERROR(dsi->dev, "Failed to deselect input: %d\n", ret); + + return ret; +} + +static const struct drm_bridge_timings nwl_dsi_timings = { + .input_bus_flags = DRM_BUS_FLAG_DE_LOW, +}; + +static const struct of_device_id nwl_dsi_dt_ids[] = { + { .compatible = "fsl,imx8mq-nwl-dsi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids); + +static const struct soc_device_attribute nwl_dsi_quirks_match[] = { + { .soc_id = "i.MX8MQ", .revision = "2.0", + .data = (void *)E11418_HS_MODE_QUIRK }, + { /* sentinel. */ }, +}; + +static int nwl_dsi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct soc_device_attribute *attr; + struct nwl_dsi *dsi; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + dsi->dev = dev; + + ret = nwl_dsi_parse_dt(dsi); + if (ret) + return ret; + + ret = devm_request_irq(dev, dsi->irq, nwl_dsi_irq_handler, 0, + dev_name(dev), dsi); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to request IRQ %d: %d\n", dsi->irq, + ret); + return ret; + } + + dsi->dsi_host.ops = &nwl_dsi_host_ops; + dsi->dsi_host.dev = dev; + ret = mipi_dsi_host_register(&dsi->dsi_host); + if (ret) { + DRM_DEV_ERROR(dev, "Failed to register MIPI host: %d\n", ret); + return ret; + } + + attr = soc_device_match(nwl_dsi_quirks_match); + if (attr) + dsi->quirks = (uintptr_t)attr->data; + + dsi->bridge.driver_private = dsi; + dsi->bridge.funcs = &nwl_dsi_bridge_funcs; + dsi->bridge.of_node = dev->of_node; + dsi->bridge.timings = &nwl_dsi_timings; + + dev_set_drvdata(dev, dsi); + pm_runtime_enable(dev); + + ret = nwl_dsi_select_input(dsi); + if (ret < 0) { + mipi_dsi_host_unregister(&dsi->dsi_host); + return ret; + } + + drm_bridge_add(&dsi->bridge); + return 0; +} + +static int nwl_dsi_remove(struct platform_device *pdev) +{ + struct nwl_dsi *dsi = platform_get_drvdata(pdev); + + nwl_dsi_deselect_input(dsi); + mipi_dsi_host_unregister(&dsi->dsi_host); + drm_bridge_remove(&dsi->bridge); + pm_runtime_disable(&pdev->dev); + return 0; +} + +static struct platform_driver nwl_dsi_driver = { + .probe = nwl_dsi_probe, + .remove = nwl_dsi_remove, + .driver = { + .of_match_table = nwl_dsi_dt_ids, + .name = DRV_NAME, + }, +}; + +module_platform_driver(nwl_dsi_driver); + +MODULE_AUTHOR("NXP Semiconductor"); +MODULE_AUTHOR("Purism SPC"); +MODULE_DESCRIPTION("Northwest Logic MIPI-DSI driver"); +MODULE_LICENSE("GPL"); /* GPLv2 or later */ diff --git a/drivers/gpu/drm/bridge/nwl-dsi.h b/drivers/gpu/drm/bridge/nwl-dsi.h new file mode 100644 index 000000000000..a247a8a11c7c --- /dev/null +++ b/drivers/gpu/drm/bridge/nwl-dsi.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * NWL MIPI DSI host driver + * + * Copyright (C) 2017 NXP + * Copyright (C) 2019 Purism SPC + */ +#ifndef __NWL_DSI_H__ +#define __NWL_DSI_H__ + +/* DSI HOST registers */ +#define NWL_DSI_CFG_NUM_LANES 0x0 +#define NWL_DSI_CFG_NONCONTINUOUS_CLK 0x4 +#define NWL_DSI_CFG_T_PRE 0x8 +#define NWL_DSI_CFG_T_POST 0xc +#define NWL_DSI_CFG_TX_GAP 0x10 +#define NWL_DSI_CFG_AUTOINSERT_EOTP 0x14 +#define NWL_DSI_CFG_EXTRA_CMDS_AFTER_EOTP 0x18 +#define NWL_DSI_CFG_HTX_TO_COUNT 0x1c +#define NWL_DSI_CFG_LRX_H_TO_COUNT 0x20 +#define NWL_DSI_CFG_BTA_H_TO_COUNT 0x24 +#define NWL_DSI_CFG_TWAKEUP 0x28 +#define NWL_DSI_CFG_STATUS_OUT 0x2c +#define NWL_DSI_RX_ERROR_STATUS 0x30 + +/* DSI DPI registers */ +#define NWL_DSI_PIXEL_PAYLOAD_SIZE 0x200 +#define NWL_DSI_PIXEL_FIFO_SEND_LEVEL 0x204 +#define NWL_DSI_INTERFACE_COLOR_CODING 0x208 +#define NWL_DSI_PIXEL_FORMAT 0x20c +#define NWL_DSI_VSYNC_POLARITY 0x210 +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW 0 +#define NWL_DSI_VSYNC_POLARITY_ACTIVE_HIGH BIT(1) + +#define NWL_DSI_HSYNC_POLARITY 0x214 +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW 0 +#define NWL_DSI_HSYNC_POLARITY_ACTIVE_HIGH BIT(1) + +#define NWL_DSI_VIDEO_MODE 0x218 +#define NWL_DSI_HFP 0x21c +#define NWL_DSI_HBP 0x220 +#define NWL_DSI_HSA 0x224 +#define NWL_DSI_ENABLE_MULT_PKTS 0x228 +#define NWL_DSI_VBP 0x22c +#define NWL_DSI_VFP 0x230 +#define NWL_DSI_BLLP_MODE 0x234 +#define NWL_DSI_USE_NULL_PKT_BLLP 0x238 +#define NWL_DSI_VACTIVE 0x23c +#define NWL_DSI_VC 0x240 + +/* DSI APB PKT control */ +#define NWL_DSI_TX_PAYLOAD 0x280 +#define NWL_DSI_PKT_CONTROL 0x284 +#define NWL_DSI_SEND_PACKET 0x288 +#define NWL_DSI_PKT_STATUS 0x28c +#define NWL_DSI_PKT_FIFO_WR_LEVEL 0x290 +#define NWL_DSI_PKT_FIFO_RD_LEVEL 0x294 +#define NWL_DSI_RX_PAYLOAD 0x298 +#define NWL_DSI_RX_PKT_HEADER 0x29c + +/* DSI IRQ handling */ +#define NWL_DSI_IRQ_STATUS 0x2a0 +#define NWL_DSI_SM_NOT_IDLE BIT(0) +#define NWL_DSI_TX_PKT_DONE BIT(1) +#define NWL_DSI_DPHY_DIRECTION BIT(2) +#define NWL_DSI_TX_FIFO_OVFLW BIT(3) +#define NWL_DSI_TX_FIFO_UDFLW BIT(4) +#define NWL_DSI_RX_FIFO_OVFLW BIT(5) +#define NWL_DSI_RX_FIFO_UDFLW BIT(6) +#define NWL_DSI_RX_PKT_HDR_RCVD BIT(7) +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD BIT(8) +#define NWL_DSI_BTA_TIMEOUT BIT(29) +#define NWL_DSI_LP_RX_TIMEOUT BIT(30) +#define NWL_DSI_HS_TX_TIMEOUT BIT(31) + +#define NWL_DSI_IRQ_STATUS2 0x2a4 +#define NWL_DSI_SINGLE_BIT_ECC_ERR BIT(0) +#define NWL_DSI_MULTI_BIT_ECC_ERR BIT(1) +#define NWL_DSI_CRC_ERR BIT(2) + +#define NWL_DSI_IRQ_MASK 0x2a8 +#define NWL_DSI_SM_NOT_IDLE_MASK BIT(0) +#define NWL_DSI_TX_PKT_DONE_MASK BIT(1) +#define NWL_DSI_DPHY_DIRECTION_MASK BIT(2) +#define NWL_DSI_TX_FIFO_OVFLW_MASK BIT(3) +#define NWL_DSI_TX_FIFO_UDFLW_MASK BIT(4) +#define NWL_DSI_RX_FIFO_OVFLW_MASK BIT(5) +#define NWL_DSI_RX_FIFO_UDFLW_MASK BIT(6) +#define NWL_DSI_RX_PKT_HDR_RCVD_MASK BIT(7) +#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD_MASK BIT(8) +#define NWL_DSI_BTA_TIMEOUT_MASK BIT(29) +#define NWL_DSI_LP_RX_TIMEOUT_MASK BIT(30) +#define NWL_DSI_HS_TX_TIMEOUT_MASK BIT(31) + +#define NWL_DSI_IRQ_MASK2 0x2ac +#define NWL_DSI_SINGLE_BIT_ECC_ERR_MASK BIT(0) +#define NWL_DSI_MULTI_BIT_ECC_ERR_MASK BIT(1) +#define NWL_DSI_CRC_ERR_MASK BIT(2) + +/* + * PKT_CONTROL format: + * [15: 0] - word count + * [17:16] - virtual channel + * [23:18] - data type + * [24] - LP or HS select (0 - LP, 1 - HS) + * [25] - perform BTA after packet is sent + * [26] - perform BTA only, no packet tx + */ +#define NWL_DSI_WC(x) FIELD_PREP(GENMASK(15, 0), (x)) +#define NWL_DSI_TX_VC(x) FIELD_PREP(GENMASK(17, 16), (x)) +#define NWL_DSI_TX_DT(x) FIELD_PREP(GENMASK(23, 18), (x)) +#define NWL_DSI_HS_SEL(x) FIELD_PREP(GENMASK(24, 24), (x)) +#define NWL_DSI_BTA_TX(x) FIELD_PREP(GENMASK(25, 25), (x)) +#define NWL_DSI_BTA_NO_TX(x) FIELD_PREP(GENMASK(26, 26), (x)) + +/* + * RX_PKT_HEADER format: + * [15: 0] - word count + * [21:16] - data type + * [23:22] - virtual channel + */ +#define NWL_DSI_RX_DT(x) FIELD_GET(GENMASK(21, 16), (x)) +#define NWL_DSI_RX_VC(x) FIELD_GET(GENMASK(23, 22), (x)) + +/* DSI Video mode */ +#define NWL_DSI_VM_BURST_MODE_WITH_SYNC_PULSES 0 +#define NWL_DSI_VM_NON_BURST_MODE_WITH_SYNC_EVENTS BIT(0) +#define NWL_DSI_VM_BURST_MODE BIT(1) + +/* * DPI color coding */ +#define NWL_DSI_DPI_16_BIT_565_PACKED 0 +#define NWL_DSI_DPI_16_BIT_565_ALIGNED 1 +#define NWL_DSI_DPI_16_BIT_565_SHIFTED 2 +#define NWL_DSI_DPI_18_BIT_PACKED 3 +#define NWL_DSI_DPI_18_BIT_ALIGNED 4 +#define NWL_DSI_DPI_24_BIT 5 + +/* * DPI Pixel format */ +#define NWL_DSI_PIXEL_FORMAT_16 0 +#define NWL_DSI_PIXEL_FORMAT_18 BIT(0) +#define NWL_DSI_PIXEL_FORMAT_18L BIT(1) +#define NWL_DSI_PIXEL_FORMAT_24 (BIT(0) | BIT(1)) + +#endif /* __NWL_DSI_H__ */ diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index 8461ee8304ba..1e63ed6b18aa 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -166,7 +166,7 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = { * * The connector type is set to @panel->connector_type, which must be set to a * known type. Calling this function with a panel whose connector type is - * DRM_MODE_CONNECTOR_Unknown will return NULL. + * DRM_MODE_CONNECTOR_Unknown will return ERR_PTR(-EINVAL). * * See devm_drm_panel_bridge_add() for an automatically managed version of this * function. @@ -174,7 +174,7 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = { struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel) { if (WARN_ON(panel->connector_type == DRM_MODE_CONNECTOR_Unknown)) - return NULL; + return ERR_PTR(-EINVAL); return drm_panel_bridge_add_typed(panel, panel->connector_type); } @@ -265,7 +265,7 @@ struct drm_bridge *devm_drm_panel_bridge_add(struct device *dev, struct drm_panel *panel) { if (WARN_ON(panel->connector_type == DRM_MODE_CONNECTOR_Unknown)) - return NULL; + return ERR_PTR(-EINVAL); return devm_drm_panel_bridge_add_typed(dev, panel, panel->connector_type); @@ -311,6 +311,7 @@ EXPORT_SYMBOL(devm_drm_panel_bridge_add_typed); /** * drm_panel_bridge_connector - return the connector for the panel bridge + * @bridge: The drm_bridge. * * drm_panel_bridge creates the connector. * This function gives external access to the connector. diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c index d3a53442d449..4b099196afeb 100644 --- a/drivers/gpu/drm/bridge/parade-ps8640.c +++ b/drivers/gpu/drm/bridge/parade-ps8640.c @@ -268,8 +268,6 @@ static int ps8640_probe(struct i2c_client *client) if (!panel) return -ENODEV; - panel->connector_type = DRM_MODE_CONNECTOR_eDP; - ps_bridge->panel_bridge = devm_drm_panel_bridge_add(dev, panel); if (IS_ERR(ps_bridge->panel_bridge)) return PTR_ERR(ps_bridge->panel_bridge); diff --git a/drivers/gpu/drm/bridge/sii9234.c b/drivers/gpu/drm/bridge/sii9234.c index f81f81b7051f..b1258f0ed205 100644 --- a/drivers/gpu/drm/bridge/sii9234.c +++ b/drivers/gpu/drm/bridge/sii9234.c @@ -836,7 +836,8 @@ static int sii9234_init_resources(struct sii9234 *ctx, ctx->supplies[3].supply = "cvcc12"; ret = devm_regulator_bulk_get(ctx->dev, 4, ctx->supplies); if (ret) { - dev_err(ctx->dev, "regulator_bulk failed\n"); + if (ret != -EPROBE_DEFER) + dev_err(ctx->dev, "regulator_bulk failed\n"); return ret; } diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c index dd56996fe9c7..d0db1acf11d7 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c @@ -630,7 +630,7 @@ static struct platform_driver snd_dw_hdmi_driver = { module_platform_driver(snd_dw_hdmi_driver); -MODULE_AUTHOR("Russell King <rmk+kernel@arm.linux.org.uk>"); +MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>"); MODULE_DESCRIPTION("Synopsis Designware HDMI AHB ALSA interface"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 383b1073d7de..30681398cfb0 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -92,6 +92,12 @@ static const u16 csc_coeff_rgb_in_eitu709[3][4] = { { 0x6756, 0x78ab, 0x2000, 0x0200 } }; +static const u16 csc_coeff_rgb_full_to_rgb_limited[3][4] = { + { 0x1b7c, 0x0000, 0x0000, 0x0020 }, + { 0x0000, 0x1b7c, 0x0000, 0x0020 }, + { 0x0000, 0x0000, 0x1b7c, 0x0020 } +}; + struct hdmi_vmode { bool mdataenablepolarity; @@ -109,6 +115,7 @@ struct hdmi_data_info { unsigned int pix_repet_factor; unsigned int hdcp_enable; struct hdmi_vmode video_mode; + bool rgb_limited_range; }; struct dw_hdmi_i2c { @@ -956,7 +963,14 @@ static void hdmi_video_sample(struct dw_hdmi *hdmi) static int is_color_space_conversion(struct dw_hdmi *hdmi) { - return hdmi->hdmi_data.enc_in_bus_format != hdmi->hdmi_data.enc_out_bus_format; + struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; + bool is_input_rgb, is_output_rgb; + + is_input_rgb = hdmi_bus_fmt_is_rgb(hdmi_data->enc_in_bus_format); + is_output_rgb = hdmi_bus_fmt_is_rgb(hdmi_data->enc_out_bus_format); + + return (is_input_rgb != is_output_rgb) || + (is_input_rgb && is_output_rgb && hdmi_data->rgb_limited_range); } static int is_color_space_decimation(struct dw_hdmi *hdmi) @@ -983,28 +997,37 @@ static int is_color_space_interpolation(struct dw_hdmi *hdmi) return 0; } +static bool is_csc_needed(struct dw_hdmi *hdmi) +{ + return is_color_space_conversion(hdmi) || + is_color_space_decimation(hdmi) || + is_color_space_interpolation(hdmi); +} + static void dw_hdmi_update_csc_coeffs(struct dw_hdmi *hdmi) { const u16 (*csc_coeff)[3][4] = &csc_coeff_default; + bool is_input_rgb, is_output_rgb; unsigned i; u32 csc_scale = 1; - if (is_color_space_conversion(hdmi)) { - if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { - if (hdmi->hdmi_data.enc_out_encoding == - V4L2_YCBCR_ENC_601) - csc_coeff = &csc_coeff_rgb_out_eitu601; - else - csc_coeff = &csc_coeff_rgb_out_eitu709; - } else if (hdmi_bus_fmt_is_rgb( - hdmi->hdmi_data.enc_in_bus_format)) { - if (hdmi->hdmi_data.enc_out_encoding == - V4L2_YCBCR_ENC_601) - csc_coeff = &csc_coeff_rgb_in_eitu601; - else - csc_coeff = &csc_coeff_rgb_in_eitu709; - csc_scale = 0; - } + is_input_rgb = hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_in_bus_format); + is_output_rgb = hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format); + + if (!is_input_rgb && is_output_rgb) { + if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_601) + csc_coeff = &csc_coeff_rgb_out_eitu601; + else + csc_coeff = &csc_coeff_rgb_out_eitu709; + } else if (is_input_rgb && !is_output_rgb) { + if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_601) + csc_coeff = &csc_coeff_rgb_in_eitu601; + else + csc_coeff = &csc_coeff_rgb_in_eitu709; + csc_scale = 0; + } else if (is_input_rgb && is_output_rgb && + hdmi->hdmi_data.rgb_limited_range) { + csc_coeff = &csc_coeff_rgb_full_to_rgb_limited; } /* The CSC registers are sequential, alternating MSB then LSB */ @@ -1614,6 +1637,18 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) drm_hdmi_avi_infoframe_from_display_mode(&frame, &hdmi->connector, mode); + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { + drm_hdmi_avi_infoframe_quant_range(&frame, &hdmi->connector, + mode, + hdmi->hdmi_data.rgb_limited_range ? + HDMI_QUANTIZATION_RANGE_LIMITED : + HDMI_QUANTIZATION_RANGE_FULL); + } else { + frame.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + frame.ycc_quantization_range = + HDMI_YCC_QUANTIZATION_RANGE_LIMITED; + } + if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) frame.colorspace = HDMI_COLORSPACE_YUV444; else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) @@ -1654,8 +1689,6 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; } - frame.scan_mode = HDMI_SCAN_MODE_NONE; - /* * The Designware IP uses a different byte format from standard * AVI info frames, though generally the bits are in the correct @@ -2010,18 +2043,19 @@ static void dw_hdmi_enable_video_path(struct dw_hdmi *hdmi) hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); /* Enable csc path */ - if (is_color_space_conversion(hdmi)) { + if (is_csc_needed(hdmi)) { hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE; hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); - } - /* Enable color space conversion if needed */ - if (is_color_space_conversion(hdmi)) hdmi_writeb(hdmi, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH, HDMI_MC_FLOWCTRL); - else + } else { + hdmi->mc_clkdis |= HDMI_MC_CLKDIS_CSCCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + hdmi_writeb(hdmi, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS, HDMI_MC_FLOWCTRL); + } } /* Workaround to clear the overflow condition */ @@ -2119,6 +2153,10 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) if (hdmi->hdmi_data.enc_out_bus_format == MEDIA_BUS_FMT_FIXED) hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + hdmi->hdmi_data.rgb_limited_range = hdmi->sink_is_hdmi && + drm_default_rgb_quant_range(mode) == + HDMI_QUANTIZATION_RANGE_LIMITED; + hdmi->hdmi_data.pix_repet_factor = 0; hdmi->hdmi_data.hdcp_enable = 0; hdmi->hdmi_data.video_mode.mdataenablepolarity = true; diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c index 1b39e8d37834..6650fe4cfc20 100644 --- a/drivers/gpu/drm/bridge/tc358768.c +++ b/drivers/gpu/drm/bridge/tc358768.c @@ -178,6 +178,8 @@ static int tc358768_clear_error(struct tc358768_priv *priv) static void tc358768_write(struct tc358768_priv *priv, u32 reg, u32 val) { + /* work around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81715 */ + int tmpval = val; size_t count = 2; if (priv->error) @@ -187,7 +189,7 @@ static void tc358768_write(struct tc358768_priv *priv, u32 reg, u32 val) if (reg < 0x100 || reg >= 0x600) count = 1; - priv->error = regmap_bulk_write(priv->regmap, reg, &val, count); + priv->error = regmap_bulk_write(priv->regmap, reg, &tmpval, count); } static void tc358768_read(struct tc358768_priv *priv, u32 reg, u32 *val) |