On Wed, 30 Mar 2022 at 23:23, Marek Vasut <marex@xxxxxxx> wrote: > > Add driver for Lontium LT9211 Single/Dual-Link DSI/LVDS or Single DPI to > Single-link/Dual-Link DSI/LVDS or Single DPI bridge. This chip is highly > capable at converting formats, but sadly it is also highly undocumented. > > This driver is written without any documentation from Lontium and based > only on shreds of information available in various obscure example codes, > hence long runs of unknown register patches and lengthy delays in various > places. Whichever register meaning could be divined from its behavior has > at least a comment around it. > > Currently the only mode tested is Single-link DSI to Single-link LVDS. > Dual-link LVDS might work as well, the register programming is in place, > but is untested. > > Signed-off-by: Marek Vasut <marex@xxxxxxx> > Cc: Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx> > Cc: Lucas Stach <l.stach@xxxxxxxxxxxxxx> > Cc: Maxime Ripard <maxime@xxxxxxxxxx> > Cc: Robert Foss <robert.foss@xxxxxxxxxx> > Cc: Sam Ravnborg <sam@xxxxxxxxxxxx> > Cc: Thomas Zimmermann <tzimmermann@xxxxxxx> > To: dri-devel@xxxxxxxxxxxxxxxxxxxxx > --- > drivers/gpu/drm/bridge/Kconfig | 13 + > drivers/gpu/drm/bridge/Makefile | 1 + > drivers/gpu/drm/bridge/lontium-lt9211.c | 802 ++++++++++++++++++++++++ > 3 files changed, 816 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/lontium-lt9211.c > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > index 044a268d7c89..dd9f0b32a5a9 100644 > --- a/drivers/gpu/drm/bridge/Kconfig > +++ b/drivers/gpu/drm/bridge/Kconfig > @@ -98,6 +98,19 @@ config DRM_LONTIUM_LT8912B > Say M here if you want to support this hardware as a module. > The module will be named "lontium-lt8912b". > > +config DRM_LONTIUM_LT9211 > + tristate "Lontium LT9211 DSI/LVDS/DPI bridge" > + depends on OF > + select DRM_PANEL_BRIDGE > + select DRM_KMS_HELPER > + select DRM_MIPI_DSI > + select REGMAP_I2C > + help > + Driver for Lontium LT9211 Single/Dual-Link DSI/LVDS or Single DPI > + input to Single-link/Dual-Link DSI/LVDS or Single DPI output bridge > + chip. > + Please say Y if you have such hardware. > + > config DRM_LONTIUM_LT9611 > tristate "Lontium LT9611 DSI/HDMI bridge" > select SND_SOC_HDMI_CODEC if SND_SOC > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > index 0774e289f81b..f800b2331d9e 100644 > --- a/drivers/gpu/drm/bridge/Makefile > +++ b/drivers/gpu/drm/bridge/Makefile > @@ -6,6 +6,7 @@ obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o > obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o > obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o > obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o > +obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o > obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o > obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o > obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o > diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c > new file mode 100644 > index 000000000000..e92821fbc639 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/lontium-lt9211.c > @@ -0,0 +1,802 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Lontium LT9211 bridge driver > + * > + * LT9211 is capable of converting: > + * 2xDSI/2xLVDS/1xDPI -> 2xDSI/2xLVDS/1xDPI > + * Currently supported is: > + * 1xDSI -> 1xLVDS > + * > + * Copyright (C) 2022 Marek Vasut <marex@xxxxxxx> > + */ > + > +#include <linux/bits.h> > +#include <linux/clk.h> > +#include <linux/gpio/consumer.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/of_device.h> > +#include <linux/of_graph.h> > +#include <linux/regmap.h> > +#include <linux/regulator/consumer.h> > + > +#include <drm/drm_atomic_helper.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 <drm/drm_probe_helper.h> > + > +#define REG_PAGE_CONTROL 0xff > +#define REG_CHIPID0 0x8100 > +#define REG_CHIPID0_VALUE 0x18 > +#define REG_CHIPID1 0x8101 > +#define REG_CHIPID1_VALUE 0x01 > +#define REG_CHIPID2 0x8102 > +#define REG_CHIPID2_VALUE 0xe3 > + > +#define REG_DSI_LANE 0xd000 > +/* DSI lane count - 0 means 4 lanes ; 1, 2, 3 means 1, 2, 3 lanes. */ > +#define REG_DSI_LANE_COUNT(n) ((n) & 3) > + > +struct lt9211 { > + struct drm_bridge bridge; > + struct device *dev; > + struct regmap *regmap; > + struct mipi_dsi_device *dsi; > + struct drm_bridge *panel_bridge; > + struct gpio_desc *reset_gpio; > + struct regulator *vccio; > + bool lvds_dual_link; > + bool lvds_dual_link_even_odd_swap; > +}; > + > +static const struct regmap_range lt9211_rw_ranges[] = { > + regmap_reg_range(0xff, 0xff), > + regmap_reg_range(0x8100, 0x816b), > + regmap_reg_range(0x8200, 0x82aa), > + regmap_reg_range(0x8500, 0x85ff), > + regmap_reg_range(0x8600, 0x86a0), > + regmap_reg_range(0x8700, 0x8746), > + regmap_reg_range(0xd000, 0xd0a7), > + regmap_reg_range(0xd400, 0xd42c), > + regmap_reg_range(0xd800, 0xd838), > + regmap_reg_range(0xd9c0, 0xd9d5), > +}; > + > +static const struct regmap_access_table lt9211_rw_table = { > + .yes_ranges = lt9211_rw_ranges, > + .n_yes_ranges = ARRAY_SIZE(lt9211_rw_ranges), > +}; > + > +static const struct regmap_range_cfg lt9211_range = { > + .name = "lt9211", > + .range_min = 0x0000, > + .range_max = 0xda00, > + .selector_reg = REG_PAGE_CONTROL, > + .selector_mask = 0xff, > + .selector_shift = 0, > + .window_start = 0, > + .window_len = 0x100, > +}; > + > +static const struct regmap_config lt9211_regmap_config = { > + .reg_bits = 8, > + .val_bits = 8, > + .rd_table = <9211_rw_table, > + .wr_table = <9211_rw_table, > + .volatile_table = <9211_rw_table, > + .ranges = <9211_range, > + .num_ranges = 1, > + .cache_type = REGCACHE_RBTREE, > + .max_register = 0xda00, > +}; > + > +static struct lt9211 *bridge_to_lt9211(struct drm_bridge *bridge) > +{ > + return container_of(bridge, struct lt9211, bridge); > +} > + > +static int lt9211_attach(struct drm_bridge *bridge, > + enum drm_bridge_attach_flags flags) > +{ > + struct lt9211 *ctx = bridge_to_lt9211(bridge); > + > + return drm_bridge_attach(bridge->encoder, ctx->panel_bridge, > + &ctx->bridge, flags); > +} > + > +static int lt9211_read_chipid(struct lt9211 *ctx) > +{ > + u8 chipid[3]; > + int ret; > + > + /* Read Chip ID registers and verify the chip can communicate. */ > + ret = regmap_bulk_read(ctx->regmap, REG_CHIPID0, chipid, 3); > + if (ret < 0) { > + dev_err(ctx->dev, "Failed to read Chip ID: %d\n", ret); > + return ret; > + } > + > + /* Test for known Chip ID. */ > + if (chipid[0] != REG_CHIPID0_VALUE || chipid[1] != REG_CHIPID1_VALUE || > + chipid[2] != REG_CHIPID2_VALUE) { > + dev_err(ctx->dev, "Unknown Chip ID: 0x%02x 0x%02x 0x%02x\n", > + chipid[0], chipid[1], chipid[2]); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int lt9211_system_init(struct lt9211 *ctx) > +{ > + const struct reg_sequence lt9211_system_init_seq[] = { > + { 0x8201, 0x18 }, > + { 0x8606, 0x61 }, > + { 0x8607, 0xa8 }, > + { 0x8714, 0x08 }, > + { 0x8715, 0x00 }, > + { 0x8718, 0x0f }, > + { 0x8722, 0x08 }, > + { 0x8723, 0x00 }, > + { 0x8726, 0x0f }, > + { 0x810b, 0xfe }, > + }; > + > + return regmap_multi_reg_write(ctx->regmap, lt9211_system_init_seq, > + ARRAY_SIZE(lt9211_system_init_seq)); > +} > + > +static int lt9211_configure_rx(struct lt9211 *ctx) > +{ > + const struct reg_sequence lt9211_rx_phy_seq[] = { > + { 0x8202, 0x44 }, > + { 0x8204, 0xa0 }, > + { 0x8205, 0x22 }, > + { 0x8207, 0x9f }, > + { 0x8208, 0xfc }, > + /* ORR with 0xf8 here to enable DSI DN/DP swap. */ > + { 0x8209, 0x01 }, > + { 0x8217, 0x0c }, > + { 0x8633, 0x1b }, > + }; > + > + const struct reg_sequence lt9211_rx_cal_reset_seq[] = { > + { 0x8120, 0x7f }, > + { 0x8120, 0xff }, > + }; > + > + const struct reg_sequence lt9211_rx_dig_seq[] = { > + { 0x8630, 0x85 }, > + /* 0x8588: BIT 6 set = MIPI-RX, BIT 4 unset = LVDS-TX */ > + { 0x8588, 0x40 }, > + { 0x85ff, 0xd0 }, > + { REG_DSI_LANE, REG_DSI_LANE_COUNT(ctx->dsi->lanes) }, > + { 0xd002, 0x05 }, > + }; > + > + const struct reg_sequence lt9211_rx_div_reset_seq[] = { > + { 0x810a, 0xc0 }, > + { 0x8120, 0xbf }, > + }; > + > + const struct reg_sequence lt9211_rx_div_clear_seq[] = { > + { 0x810a, 0xc1 }, > + { 0x8120, 0xff }, > + }; > + > + int ret; > + > + ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_phy_seq, > + ARRAY_SIZE(lt9211_rx_phy_seq)); > + if (ret) > + return ret; > + > + ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_cal_reset_seq, > + ARRAY_SIZE(lt9211_rx_cal_reset_seq)); > + if (ret) > + return ret; > + > + ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_dig_seq, > + ARRAY_SIZE(lt9211_rx_dig_seq)); > + if (ret) > + return ret; > + > + ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_div_reset_seq, > + ARRAY_SIZE(lt9211_rx_div_reset_seq)); > + if (ret) > + return ret; > + > + usleep_range(10000, 15000); > + > + return regmap_multi_reg_write(ctx->regmap, lt9211_rx_div_clear_seq, > + ARRAY_SIZE(lt9211_rx_div_clear_seq)); > +} > + > +static int lt9211_autodetect_rx(struct lt9211 *ctx, > + const struct drm_display_mode *mode) > +{ > + u16 width, height; > + u32 byteclk; > + u8 buf[5]; > + u8 format; > + u8 bc[3]; > + int ret; > + > + /* Measure ByteClock frequency. */ > + ret = regmap_write(ctx->regmap, 0x8600, 0x01); > + if (ret) > + return ret; > + > + /* Give the chip time to lock onto RX stream. */ > + msleep(100); > + > + /* Read the ByteClock frequency from the chip. */ > + ret = regmap_bulk_read(ctx->regmap, 0x8608, bc, sizeof(bc)); > + if (ret) > + return ret; > + > + /* RX ByteClock in kHz */ > + byteclk = ((bc[0] & 0xf) << 16) | (bc[1] << 8) | bc[2]; > + > + /* Width/Height/Format Auto-detection */ > + ret = regmap_bulk_read(ctx->regmap, 0xd082, buf, sizeof(buf)); > + if (ret) > + return ret; > + > + width = (buf[0] << 8) | buf[1]; > + height = (buf[3] << 8) | buf[4]; > + format = buf[2] & 0xf; > + > + if (format == 0x3) { /* YUV422 16bit */ > + width /= 2; > + } else if (format == 0xa) { /* RGB888 24bit */ > + width /= 3; > + } else { > + dev_err(ctx->dev, "Unsupported DSI pixel format 0x%01x\n", > + format); > + return -EINVAL; > + } > + > + if (width != mode->hdisplay) { > + dev_err(ctx->dev, > + "RX: Detected DSI width (%d) does not match mode hdisplay (%d)\n", > + width, mode->hdisplay); > + return -EINVAL; > + } > + > + if (height != mode->vdisplay) { > + dev_err(ctx->dev, > + "RX: Detected DSI height (%d) does not match mode vdisplay (%d)\n", > + height, mode->vdisplay); > + return -EINVAL; > + } > + > + dev_dbg(ctx->dev, "RX: %dx%d format=0x%01x byteclock=%d kHz\n", > + width, height, format, byteclk); > + > + return 0; > +} > + > +static int lt9211_configure_timing(struct lt9211 *ctx, > + const struct drm_display_mode *mode) > +{ > + const struct reg_sequence lt9211_timing[] = { > + { 0xd00d, (mode->vtotal >> 8) & 0xff }, > + { 0xd00e, mode->vtotal & 0xff }, > + { 0xd00f, (mode->vdisplay >> 8) & 0xff }, > + { 0xd010, mode->vdisplay & 0xff }, > + { 0xd011, (mode->htotal >> 8) & 0xff }, > + { 0xd012, mode->htotal & 0xff }, > + { 0xd013, (mode->hdisplay >> 8) & 0xff }, > + { 0xd014, mode->hdisplay & 0xff }, > + { 0xd015, (mode->vsync_end - mode->vsync_start) & 0xff }, > + { 0xd016, (mode->hsync_end - mode->hsync_start) & 0xff }, > + { 0xd017, ((mode->vsync_start - mode->vdisplay) >> 8) & 0xff }, > + { 0xd018, (mode->vsync_start - mode->vdisplay) & 0xff }, > + { 0xd019, ((mode->hsync_start - mode->hdisplay) >> 8) & 0xff }, > + { 0xd01a, (mode->hsync_start - mode->hdisplay) & 0xff }, > + }; > + > + return regmap_multi_reg_write(ctx->regmap, lt9211_timing, > + ARRAY_SIZE(lt9211_timing)); > +} > + > +static int lt9211_configure_plls(struct lt9211 *ctx, > + const struct drm_display_mode *mode) > +{ > + const struct reg_sequence lt9211_pcr_seq[] = { > + { 0xd026, 0x17 }, > + { 0xd027, 0xc3 }, > + { 0xd02d, 0x30 }, > + { 0xd031, 0x10 }, > + { 0xd023, 0x20 }, > + { 0xd038, 0x02 }, > + { 0xd039, 0x10 }, > + { 0xd03a, 0x20 }, > + { 0xd03b, 0x60 }, > + { 0xd03f, 0x04 }, > + { 0xd040, 0x08 }, > + { 0xd041, 0x10 }, > + { 0x810b, 0xee }, > + { 0x810b, 0xfe }, > + }; > + > + unsigned int pval; > + int ret; > + > + /* DeSSC PLL reference clock is 25 MHz XTal. */ > + ret = regmap_write(ctx->regmap, 0x822d, 0x48); > + if (ret) > + return ret; > + > + if (mode->clock < 44000) { > + ret = regmap_write(ctx->regmap, 0x8235, 0x83); > + } else if (mode->clock < 88000) { > + ret = regmap_write(ctx->regmap, 0x8235, 0x82); > + } else if (mode->clock < 176000) { > + ret = regmap_write(ctx->regmap, 0x8235, 0x81); > + } else { > + dev_err(ctx->dev, > + "Unsupported mode clock (%d kHz) above 176 MHz.\n", > + mode->clock); > + return -EINVAL; > + } > + > + if (ret) > + return ret; > + > + /* Wait for the DeSSC PLL to stabilize. */ > + msleep(100); > + > + ret = regmap_multi_reg_write(ctx->regmap, lt9211_pcr_seq, > + ARRAY_SIZE(lt9211_pcr_seq)); > + if (ret) > + return ret; > + > + /* PCR stability test takes seconds. */ > + ret = regmap_read_poll_timeout(ctx->regmap, 0xd087, pval, pval & 0x8, > + 20000, 10000000); > + if (ret) > + dev_err(ctx->dev, "PCR unstable, ret=%i\n", ret); > + > + return ret; > +} > + > +static int lt9211_configure_tx(struct lt9211 *ctx, bool jeida, > + bool bpp24, bool de) > +{ > + const struct reg_sequence system_lt9211_tx_phy_seq[] = { > + /* DPI output disable */ > + { 0x8262, 0x00 }, > + /* BIT(7) is LVDS dual-port */ > + { 0x823b, 0x38 | (ctx->lvds_dual_link ? BIT(7) : 0) }, > + { 0x823e, 0x92 }, > + { 0x823f, 0x48 }, > + { 0x8240, 0x31 }, > + { 0x8243, 0x80 }, > + { 0x8244, 0x00 }, > + { 0x8245, 0x00 }, > + { 0x8249, 0x00 }, > + { 0x824a, 0x01 }, > + { 0x824e, 0x00 }, > + { 0x824f, 0x00 }, > + { 0x8250, 0x00 }, > + { 0x8253, 0x00 }, > + { 0x8254, 0x01 }, > + /* LVDS channel order, Odd:Even 0x10..A:B, 0x40..B:A */ > + { 0x8646, ctx->lvds_dual_link_even_odd_swap ? 0x40 : 0x10 }, > + { 0x8120, 0x7b }, > + { 0x816b, 0xff }, > + }; > + > + const struct reg_sequence system_lt9211_tx_dig_seq[] = { > + { 0x8559, 0x40 | (jeida ? BIT(7) : 0) | > + (de ? BIT(5) : 0) | (bpp24 ? BIT(4) : 0) }, > + { 0x855a, 0xaa }, > + { 0x855b, 0xaa }, > + { 0x855c, ctx->lvds_dual_link ? BIT(0) : 0 }, > + { 0x85a1, 0x77 }, > + { 0x8640, 0x40 }, > + { 0x8641, 0x34 }, > + { 0x8642, 0x10 }, > + { 0x8643, 0x23 }, > + { 0x8644, 0x41 }, > + { 0x8645, 0x02 }, > + }; > + > + const struct reg_sequence system_lt9211_tx_pll_seq[] = { > + /* TX PLL power down */ > + { 0x8236, 0x01 }, > + { 0x8237, ctx->lvds_dual_link ? 0x2a : 0x29 }, > + { 0x8238, 0x06 }, > + { 0x8239, 0x30 }, > + { 0x823a, 0x8e }, > + { 0x8737, 0x14 }, > + { 0x8713, 0x00 }, > + { 0x8713, 0x80 }, > + }; > + > + unsigned int pval; > + int ret; > + > + ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_phy_seq, > + ARRAY_SIZE(system_lt9211_tx_phy_seq)); > + if (ret) > + return ret; > + > + ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_dig_seq, > + ARRAY_SIZE(system_lt9211_tx_dig_seq)); > + if (ret) > + return ret; > + > + ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_pll_seq, > + ARRAY_SIZE(system_lt9211_tx_pll_seq)); > + if (ret) > + return ret; > + > + ret = regmap_read_poll_timeout(ctx->regmap, 0x871f, pval, pval & 0x80, > + 10000, 1000000); > + if (ret) { > + dev_err(ctx->dev, "TX PLL unstable, ret=%i\n", ret); > + return ret; > + } > + > + ret = regmap_read_poll_timeout(ctx->regmap, 0x8720, pval, pval & 0x80, > + 10000, 1000000); > + if (ret) { > + dev_err(ctx->dev, "TX PLL unstable, ret=%i\n", ret); > + return ret; > + } > + > + return 0; > +} > + > +static void lt9211_atomic_enable(struct drm_bridge *bridge, > + struct drm_bridge_state *old_bridge_state) > +{ > + struct lt9211 *ctx = bridge_to_lt9211(bridge); > + struct drm_atomic_state *state = old_bridge_state->base.state; > + const struct drm_bridge_state *bridge_state; > + const struct drm_crtc_state *crtc_state; > + const struct drm_display_mode *mode; > + struct drm_connector *connector; > + struct drm_crtc *crtc; > + bool lvds_format_24bpp; > + bool lvds_format_jeida; > + u32 bus_flags; > + int ret; > + > + ret = regulator_enable(ctx->vccio); > + if (ret) { > + dev_err(ctx->dev, "Failed to enable vccio: %d\n", ret); > + return; > + } > + > + /* Deassert reset */ > + gpiod_set_value(ctx->reset_gpio, 1); > + usleep_range(20000, 21000); /* Very long post-reset delay. */ > + > + /* Get the LVDS format from the bridge state. */ > + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); > + bus_flags = bridge_state->output_bus_cfg.flags; > + > + switch (bridge_state->output_bus_cfg.format) { > + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: > + lvds_format_24bpp = false; > + lvds_format_jeida = true; > + break; > + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: > + lvds_format_24bpp = true; > + lvds_format_jeida = true; > + break; > + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: > + lvds_format_24bpp = true; > + lvds_format_jeida = false; > + break; > + default: > + /* > + * Some bridges still don't set the correct > + * LVDS bus pixel format, use SPWG24 default > + * format until those are fixed. > + */ > + lvds_format_24bpp = true; > + lvds_format_jeida = false; > + dev_warn(ctx->dev, > + "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n", > + bridge_state->output_bus_cfg.format); > + break; > + } > + > + /* > + * Retrieve the CRTC adjusted mode. This requires a little dance to go > + * from the bridge to the encoder, to the connector and to the CRTC. > + */ > + connector = drm_atomic_get_new_connector_for_encoder(state, > + bridge->encoder); > + crtc = drm_atomic_get_new_connector_state(state, connector)->crtc; > + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); > + mode = &crtc_state->adjusted_mode; > + > + ret = lt9211_read_chipid(ctx); > + if (ret) > + return; > + > + ret = lt9211_system_init(ctx); > + if (ret) > + return; > + > + ret = lt9211_configure_rx(ctx); > + if (ret) > + return; > + > + ret = lt9211_autodetect_rx(ctx, mode); > + if (ret) > + return; > + > + ret = lt9211_configure_timing(ctx, mode); > + if (ret) > + return; > + > + ret = lt9211_configure_plls(ctx, mode); > + if (ret) > + return; > + > + ret = lt9211_configure_tx(ctx, lvds_format_jeida, lvds_format_24bpp, > + bus_flags & DRM_BUS_FLAG_DE_HIGH); > + if (ret) > + return; > + > + dev_dbg(ctx->dev, "LT9211 enabled.\n"); > +} > + > +static void lt9211_atomic_disable(struct drm_bridge *bridge, > + struct drm_bridge_state *old_bridge_state) > +{ > + struct lt9211 *ctx = bridge_to_lt9211(bridge); > + int ret; > + > + /* > + * Put the chip in reset, pull nRST line low, > + * and assure lengthy 10ms reset low timing. > + */ > + gpiod_set_value(ctx->reset_gpio, 0); > + usleep_range(10000, 11000); /* Very long reset duration. */ > + > + ret = regulator_disable(ctx->vccio); > + if (ret) > + dev_err(ctx->dev, "Failed to disable vccio: %d\n", ret); > + > + regcache_mark_dirty(ctx->regmap); > +} > + > +static enum drm_mode_status > +lt9211_mode_valid(struct drm_bridge *bridge, > + const struct drm_display_info *info, > + const struct drm_display_mode *mode) > +{ > + /* LVDS output clock range 25..176 MHz */ > + if (mode->clock < 25000) > + return MODE_CLOCK_LOW; > + if (mode->clock > 176000) > + return MODE_CLOCK_HIGH; > + > + return MODE_OK; > +} > + > +#define MAX_INPUT_SEL_FORMATS 1 > + > +static u32 * > +lt9211_atomic_get_input_bus_fmts(struct drm_bridge *bridge, > + struct drm_bridge_state *bridge_state, > + struct drm_crtc_state *crtc_state, > + struct drm_connector_state *conn_state, > + u32 output_fmt, > + unsigned int *num_input_fmts) > +{ > + u32 *input_fmts; > + > + *num_input_fmts = 0; > + > + input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts), > + GFP_KERNEL); > + if (!input_fmts) > + return NULL; > + > + /* This is the DSI-end bus format */ > + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; > + *num_input_fmts = 1; > + > + return input_fmts; > +} > + > +static const struct drm_bridge_funcs lt9211_funcs = { > + .attach = lt9211_attach, > + .mode_valid = lt9211_mode_valid, > + .atomic_enable = lt9211_atomic_enable, > + .atomic_disable = lt9211_atomic_disable, > + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, > + .atomic_get_input_bus_fmts = lt9211_atomic_get_input_bus_fmts, > + .atomic_reset = drm_atomic_helper_bridge_reset, > +}; > + > +static int lt9211_parse_dt(struct lt9211 *ctx) > +{ > + struct device_node *port2, *port3; > + struct drm_bridge *panel_bridge; > + struct device *dev = ctx->dev; > + struct drm_panel *panel; > + int dual_link; > + int ret; > + > + ctx->vccio = devm_regulator_get(dev, "vccio"); > + if (IS_ERR(ctx->vccio)) > + return dev_err_probe(dev, PTR_ERR(ctx->vccio), > + "Failed to get supply 'vccio'\n"); > + > + ctx->lvds_dual_link = false; > + ctx->lvds_dual_link_even_odd_swap = false; > + > + port2 = of_graph_get_port_by_id(dev->of_node, 2); > + port3 = of_graph_get_port_by_id(dev->of_node, 3); > + dual_link = drm_of_lvds_get_dual_link_pixel_order(port2, port3); > + of_node_put(port2); > + of_node_put(port3); > + > + if (dual_link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) { > + ctx->lvds_dual_link = true; > + /* Odd pixels to LVDS Channel A, even pixels to B */ > + ctx->lvds_dual_link_even_odd_swap = false; > + } else if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) { > + ctx->lvds_dual_link = true; > + /* Even pixels to LVDS Channel A, odd pixels to B */ > + ctx->lvds_dual_link_even_odd_swap = true; > + } > + > + ret = drm_of_find_panel_or_bridge(dev->of_node, 2, 0, &panel, &panel_bridge); > + if (ret < 0) > + return ret; > + if (panel) { > + panel_bridge = devm_drm_panel_bridge_add(dev, panel); > + if (IS_ERR(panel_bridge)) > + return PTR_ERR(panel_bridge); > + } > + > + ctx->panel_bridge = panel_bridge; > + > + return 0; > +} > + > +static int lt9211_host_attach(struct lt9211 *ctx) > +{ > + const struct mipi_dsi_device_info info = { > + .type = "lt9211", > + .channel = 0, > + .node = NULL, > + }; > + struct device *dev = ctx->dev; > + struct device_node *host_node; > + struct device_node *endpoint; > + struct mipi_dsi_device *dsi; > + struct mipi_dsi_host *host; > + int dsi_lanes; > + int ret; > + > + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); > + dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes"); > + host_node = of_graph_get_remote_port_parent(endpoint); > + host = of_find_mipi_dsi_host_by_node(host_node); > + of_node_put(host_node); > + of_node_put(endpoint); > + > + if (!host) > + return -EPROBE_DEFER; > + > + if (dsi_lanes < 0 || dsi_lanes > 4) > + return -EINVAL; > + > + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); > + if (IS_ERR(dsi)) > + return dev_err_probe(dev, PTR_ERR(dsi), > + "failed to create dsi device\n"); > + > + ctx->dsi = dsi; > + > + dsi->lanes = dsi_lanes; > + dsi->format = MIPI_DSI_FMT_RGB888; > + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | > + MIPI_DSI_MODE_VIDEO_HSE; > + > + ret = devm_mipi_dsi_attach(dev, dsi); > + if (ret < 0) { > + dev_err(dev, "failed to attach dsi to host: %d\n", ret); > + return ret; > + } > + > + return 0; > +} > + > +static int lt9211_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &client->dev; > + struct lt9211 *ctx; > + int ret; > + > + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); > + if (!ctx) > + return -ENOMEM; > + > + ctx->dev = dev; > + > + /* > + * Put the chip in reset, pull nRST line low, > + * and assure lengthy 10ms reset low timing. > + */ > + ctx->reset_gpio = devm_gpiod_get_optional(ctx->dev, "reset", > + GPIOD_OUT_LOW); > + if (IS_ERR(ctx->reset_gpio)) > + return PTR_ERR(ctx->reset_gpio); > + > + usleep_range(10000, 11000); /* Very long reset duration. */ > + > + ret = lt9211_parse_dt(ctx); > + if (ret) > + return ret; > + > + ctx->regmap = devm_regmap_init_i2c(client, <9211_regmap_config); > + if (IS_ERR(ctx->regmap)) > + return PTR_ERR(ctx->regmap); > + > + dev_set_drvdata(dev, ctx); > + i2c_set_clientdata(client, ctx); > + > + ctx->bridge.funcs = <9211_funcs; > + ctx->bridge.of_node = dev->of_node; > + drm_bridge_add(&ctx->bridge); > + > + ret = lt9211_host_attach(ctx); > + if (ret) > + drm_bridge_remove(&ctx->bridge); > + > + return ret; > +} > + > +static int lt9211_remove(struct i2c_client *client) > +{ > + struct lt9211 *ctx = i2c_get_clientdata(client); > + > + drm_bridge_remove(&ctx->bridge); > + > + return 0; > +} > + > +static struct i2c_device_id lt9211_id[] = { > + { "lontium,lt9211" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, lt9211_id); > + > +static const struct of_device_id lt9211_match_table[] = { > + { .compatible = "lontium,lt9211" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, lt9211_match_table); > + > +static struct i2c_driver lt9211_driver = { > + .probe = lt9211_probe, > + .remove = lt9211_remove, > + .id_table = lt9211_id, > + .driver = { > + .name = "lt9211", > + .of_match_table = lt9211_match_table, > + }, > +}; > +module_i2c_driver(lt9211_driver); > + > +MODULE_AUTHOR("Marek Vasut <marex@xxxxxxx>"); > +MODULE_DESCRIPTION("Lontium LT9211 DSI/LVDS/DPI bridge driver"); > +MODULE_LICENSE("GPL"); > -- > 2.35.1 I think some of the registers could be named, but overall I'm quite happy with this driver. Reviewed-by: Robert Foss <robert.foss@xxxxxxxxxx>