162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Lontium LT9211 bridge driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * LT9211 is capable of converting:
662306a36Sopenharmony_ci *   2xDSI/2xLVDS/1xDPI -> 2xDSI/2xLVDS/1xDPI
762306a36Sopenharmony_ci * Currently supported is:
862306a36Sopenharmony_ci *   1xDSI -> 1xLVDS
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Copyright (C) 2022 Marek Vasut <marex@denx.de>
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/bits.h>
1462306a36Sopenharmony_ci#include <linux/clk.h>
1562306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1662306a36Sopenharmony_ci#include <linux/i2c.h>
1762306a36Sopenharmony_ci#include <linux/media-bus-format.h>
1862306a36Sopenharmony_ci#include <linux/module.h>
1962306a36Sopenharmony_ci#include <linux/of_graph.h>
2062306a36Sopenharmony_ci#include <linux/regmap.h>
2162306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h>
2462306a36Sopenharmony_ci#include <drm/drm_bridge.h>
2562306a36Sopenharmony_ci#include <drm/drm_mipi_dsi.h>
2662306a36Sopenharmony_ci#include <drm/drm_of.h>
2762306a36Sopenharmony_ci#include <drm/drm_panel.h>
2862306a36Sopenharmony_ci#include <drm/drm_print.h>
2962306a36Sopenharmony_ci#include <drm/drm_probe_helper.h>
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define REG_PAGE_CONTROL			0xff
3262306a36Sopenharmony_ci#define REG_CHIPID0				0x8100
3362306a36Sopenharmony_ci#define REG_CHIPID0_VALUE			0x18
3462306a36Sopenharmony_ci#define REG_CHIPID1				0x8101
3562306a36Sopenharmony_ci#define REG_CHIPID1_VALUE			0x01
3662306a36Sopenharmony_ci#define REG_CHIPID2				0x8102
3762306a36Sopenharmony_ci#define REG_CHIPID2_VALUE			0xe3
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define REG_DSI_LANE				0xd000
4062306a36Sopenharmony_ci/* DSI lane count - 0 means 4 lanes ; 1, 2, 3 means 1, 2, 3 lanes. */
4162306a36Sopenharmony_ci#define REG_DSI_LANE_COUNT(n)			((n) & 3)
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistruct lt9211 {
4462306a36Sopenharmony_ci	struct drm_bridge		bridge;
4562306a36Sopenharmony_ci	struct device			*dev;
4662306a36Sopenharmony_ci	struct regmap			*regmap;
4762306a36Sopenharmony_ci	struct mipi_dsi_device		*dsi;
4862306a36Sopenharmony_ci	struct drm_bridge		*panel_bridge;
4962306a36Sopenharmony_ci	struct gpio_desc		*reset_gpio;
5062306a36Sopenharmony_ci	struct regulator		*vccio;
5162306a36Sopenharmony_ci	bool				lvds_dual_link;
5262306a36Sopenharmony_ci	bool				lvds_dual_link_even_odd_swap;
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic const struct regmap_range lt9211_rw_ranges[] = {
5662306a36Sopenharmony_ci	regmap_reg_range(0xff, 0xff),
5762306a36Sopenharmony_ci	regmap_reg_range(0x8100, 0x816b),
5862306a36Sopenharmony_ci	regmap_reg_range(0x8200, 0x82aa),
5962306a36Sopenharmony_ci	regmap_reg_range(0x8500, 0x85ff),
6062306a36Sopenharmony_ci	regmap_reg_range(0x8600, 0x86a0),
6162306a36Sopenharmony_ci	regmap_reg_range(0x8700, 0x8746),
6262306a36Sopenharmony_ci	regmap_reg_range(0xd000, 0xd0a7),
6362306a36Sopenharmony_ci	regmap_reg_range(0xd400, 0xd42c),
6462306a36Sopenharmony_ci	regmap_reg_range(0xd800, 0xd838),
6562306a36Sopenharmony_ci	regmap_reg_range(0xd9c0, 0xd9d5),
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic const struct regmap_access_table lt9211_rw_table = {
6962306a36Sopenharmony_ci	.yes_ranges = lt9211_rw_ranges,
7062306a36Sopenharmony_ci	.n_yes_ranges = ARRAY_SIZE(lt9211_rw_ranges),
7162306a36Sopenharmony_ci};
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic const struct regmap_range_cfg lt9211_range = {
7462306a36Sopenharmony_ci	.name = "lt9211",
7562306a36Sopenharmony_ci	.range_min = 0x0000,
7662306a36Sopenharmony_ci	.range_max = 0xda00,
7762306a36Sopenharmony_ci	.selector_reg = REG_PAGE_CONTROL,
7862306a36Sopenharmony_ci	.selector_mask = 0xff,
7962306a36Sopenharmony_ci	.selector_shift = 0,
8062306a36Sopenharmony_ci	.window_start = 0,
8162306a36Sopenharmony_ci	.window_len = 0x100,
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic const struct regmap_config lt9211_regmap_config = {
8562306a36Sopenharmony_ci	.reg_bits = 8,
8662306a36Sopenharmony_ci	.val_bits = 8,
8762306a36Sopenharmony_ci	.rd_table = &lt9211_rw_table,
8862306a36Sopenharmony_ci	.wr_table = &lt9211_rw_table,
8962306a36Sopenharmony_ci	.volatile_table	= &lt9211_rw_table,
9062306a36Sopenharmony_ci	.ranges = &lt9211_range,
9162306a36Sopenharmony_ci	.num_ranges = 1,
9262306a36Sopenharmony_ci	.cache_type = REGCACHE_RBTREE,
9362306a36Sopenharmony_ci	.max_register = 0xda00,
9462306a36Sopenharmony_ci};
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic struct lt9211 *bridge_to_lt9211(struct drm_bridge *bridge)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	return container_of(bridge, struct lt9211, bridge);
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic int lt9211_attach(struct drm_bridge *bridge,
10262306a36Sopenharmony_ci			 enum drm_bridge_attach_flags flags)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	struct lt9211 *ctx = bridge_to_lt9211(bridge);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
10762306a36Sopenharmony_ci				 &ctx->bridge, flags);
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic int lt9211_read_chipid(struct lt9211 *ctx)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	u8 chipid[3];
11362306a36Sopenharmony_ci	int ret;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	/* Read Chip ID registers and verify the chip can communicate. */
11662306a36Sopenharmony_ci	ret = regmap_bulk_read(ctx->regmap, REG_CHIPID0, chipid, 3);
11762306a36Sopenharmony_ci	if (ret < 0) {
11862306a36Sopenharmony_ci		dev_err(ctx->dev, "Failed to read Chip ID: %d\n", ret);
11962306a36Sopenharmony_ci		return ret;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	/* Test for known Chip ID. */
12362306a36Sopenharmony_ci	if (chipid[0] != REG_CHIPID0_VALUE || chipid[1] != REG_CHIPID1_VALUE ||
12462306a36Sopenharmony_ci	    chipid[2] != REG_CHIPID2_VALUE) {
12562306a36Sopenharmony_ci		dev_err(ctx->dev, "Unknown Chip ID: 0x%02x 0x%02x 0x%02x\n",
12662306a36Sopenharmony_ci			chipid[0], chipid[1], chipid[2]);
12762306a36Sopenharmony_ci		return -EINVAL;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	return 0;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic int lt9211_system_init(struct lt9211 *ctx)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	const struct reg_sequence lt9211_system_init_seq[] = {
13662306a36Sopenharmony_ci		{ 0x8201, 0x18 },
13762306a36Sopenharmony_ci		{ 0x8606, 0x61 },
13862306a36Sopenharmony_ci		{ 0x8607, 0xa8 },
13962306a36Sopenharmony_ci		{ 0x8714, 0x08 },
14062306a36Sopenharmony_ci		{ 0x8715, 0x00 },
14162306a36Sopenharmony_ci		{ 0x8718, 0x0f },
14262306a36Sopenharmony_ci		{ 0x8722, 0x08 },
14362306a36Sopenharmony_ci		{ 0x8723, 0x00 },
14462306a36Sopenharmony_ci		{ 0x8726, 0x0f },
14562306a36Sopenharmony_ci		{ 0x810b, 0xfe },
14662306a36Sopenharmony_ci	};
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	return regmap_multi_reg_write(ctx->regmap, lt9211_system_init_seq,
14962306a36Sopenharmony_ci				      ARRAY_SIZE(lt9211_system_init_seq));
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic int lt9211_configure_rx(struct lt9211 *ctx)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	const struct reg_sequence lt9211_rx_phy_seq[] = {
15562306a36Sopenharmony_ci		{ 0x8202, 0x44 },
15662306a36Sopenharmony_ci		{ 0x8204, 0xa0 },
15762306a36Sopenharmony_ci		{ 0x8205, 0x22 },
15862306a36Sopenharmony_ci		{ 0x8207, 0x9f },
15962306a36Sopenharmony_ci		{ 0x8208, 0xfc },
16062306a36Sopenharmony_ci		/* ORR with 0xf8 here to enable DSI DN/DP swap. */
16162306a36Sopenharmony_ci		{ 0x8209, 0x01 },
16262306a36Sopenharmony_ci		{ 0x8217, 0x0c },
16362306a36Sopenharmony_ci		{ 0x8633, 0x1b },
16462306a36Sopenharmony_ci	};
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	const struct reg_sequence lt9211_rx_cal_reset_seq[] = {
16762306a36Sopenharmony_ci		{ 0x8120, 0x7f },
16862306a36Sopenharmony_ci		{ 0x8120, 0xff },
16962306a36Sopenharmony_ci	};
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	const struct reg_sequence lt9211_rx_dig_seq[] = {
17262306a36Sopenharmony_ci		{ 0x8630, 0x85 },
17362306a36Sopenharmony_ci		/* 0x8588: BIT 6 set = MIPI-RX, BIT 4 unset = LVDS-TX */
17462306a36Sopenharmony_ci		{ 0x8588, 0x40 },
17562306a36Sopenharmony_ci		{ 0x85ff, 0xd0 },
17662306a36Sopenharmony_ci		{ REG_DSI_LANE, REG_DSI_LANE_COUNT(ctx->dsi->lanes) },
17762306a36Sopenharmony_ci		{ 0xd002, 0x05 },
17862306a36Sopenharmony_ci	};
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	const struct reg_sequence lt9211_rx_div_reset_seq[] = {
18162306a36Sopenharmony_ci		{ 0x810a, 0xc0 },
18262306a36Sopenharmony_ci		{ 0x8120, 0xbf },
18362306a36Sopenharmony_ci	};
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	const struct reg_sequence lt9211_rx_div_clear_seq[] = {
18662306a36Sopenharmony_ci		{ 0x810a, 0xc1 },
18762306a36Sopenharmony_ci		{ 0x8120, 0xff },
18862306a36Sopenharmony_ci	};
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	int ret;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_phy_seq,
19362306a36Sopenharmony_ci				     ARRAY_SIZE(lt9211_rx_phy_seq));
19462306a36Sopenharmony_ci	if (ret)
19562306a36Sopenharmony_ci		return ret;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_cal_reset_seq,
19862306a36Sopenharmony_ci				     ARRAY_SIZE(lt9211_rx_cal_reset_seq));
19962306a36Sopenharmony_ci	if (ret)
20062306a36Sopenharmony_ci		return ret;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_dig_seq,
20362306a36Sopenharmony_ci				     ARRAY_SIZE(lt9211_rx_dig_seq));
20462306a36Sopenharmony_ci	if (ret)
20562306a36Sopenharmony_ci		return ret;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_div_reset_seq,
20862306a36Sopenharmony_ci				     ARRAY_SIZE(lt9211_rx_div_reset_seq));
20962306a36Sopenharmony_ci	if (ret)
21062306a36Sopenharmony_ci		return ret;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	usleep_range(10000, 15000);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	return regmap_multi_reg_write(ctx->regmap, lt9211_rx_div_clear_seq,
21562306a36Sopenharmony_ci				      ARRAY_SIZE(lt9211_rx_div_clear_seq));
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic int lt9211_autodetect_rx(struct lt9211 *ctx,
21962306a36Sopenharmony_ci				const struct drm_display_mode *mode)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	u16 width, height;
22262306a36Sopenharmony_ci	u32 byteclk;
22362306a36Sopenharmony_ci	u8 buf[5];
22462306a36Sopenharmony_ci	u8 format;
22562306a36Sopenharmony_ci	u8 bc[3];
22662306a36Sopenharmony_ci	int ret;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	/* Measure ByteClock frequency. */
22962306a36Sopenharmony_ci	ret = regmap_write(ctx->regmap, 0x8600, 0x01);
23062306a36Sopenharmony_ci	if (ret)
23162306a36Sopenharmony_ci		return ret;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	/* Give the chip time to lock onto RX stream. */
23462306a36Sopenharmony_ci	msleep(100);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	/* Read the ByteClock frequency from the chip. */
23762306a36Sopenharmony_ci	ret = regmap_bulk_read(ctx->regmap, 0x8608, bc, sizeof(bc));
23862306a36Sopenharmony_ci	if (ret)
23962306a36Sopenharmony_ci		return ret;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	/* RX ByteClock in kHz */
24262306a36Sopenharmony_ci	byteclk = ((bc[0] & 0xf) << 16) | (bc[1] << 8) | bc[2];
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	/* Width/Height/Format Auto-detection */
24562306a36Sopenharmony_ci	ret = regmap_bulk_read(ctx->regmap, 0xd082, buf, sizeof(buf));
24662306a36Sopenharmony_ci	if (ret)
24762306a36Sopenharmony_ci		return ret;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	width = (buf[0] << 8) | buf[1];
25062306a36Sopenharmony_ci	height = (buf[3] << 8) | buf[4];
25162306a36Sopenharmony_ci	format = buf[2] & 0xf;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	if (format == 0x3) {		/* YUV422 16bit */
25462306a36Sopenharmony_ci		width /= 2;
25562306a36Sopenharmony_ci	} else if (format == 0xa) {	/* RGB888 24bit */
25662306a36Sopenharmony_ci		width /= 3;
25762306a36Sopenharmony_ci	} else {
25862306a36Sopenharmony_ci		dev_err(ctx->dev, "Unsupported DSI pixel format 0x%01x\n",
25962306a36Sopenharmony_ci			format);
26062306a36Sopenharmony_ci		return -EINVAL;
26162306a36Sopenharmony_ci	}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	if (width != mode->hdisplay) {
26462306a36Sopenharmony_ci		dev_err(ctx->dev,
26562306a36Sopenharmony_ci			"RX: Detected DSI width (%d) does not match mode hdisplay (%d)\n",
26662306a36Sopenharmony_ci			width, mode->hdisplay);
26762306a36Sopenharmony_ci		return -EINVAL;
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (height != mode->vdisplay) {
27162306a36Sopenharmony_ci		dev_err(ctx->dev,
27262306a36Sopenharmony_ci			"RX: Detected DSI height (%d) does not match mode vdisplay (%d)\n",
27362306a36Sopenharmony_ci			height, mode->vdisplay);
27462306a36Sopenharmony_ci		return -EINVAL;
27562306a36Sopenharmony_ci	}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	dev_dbg(ctx->dev, "RX: %dx%d format=0x%01x byteclock=%d kHz\n",
27862306a36Sopenharmony_ci		width, height, format, byteclk);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	return 0;
28162306a36Sopenharmony_ci}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_cistatic int lt9211_configure_timing(struct lt9211 *ctx,
28462306a36Sopenharmony_ci				   const struct drm_display_mode *mode)
28562306a36Sopenharmony_ci{
28662306a36Sopenharmony_ci	const struct reg_sequence lt9211_timing[] = {
28762306a36Sopenharmony_ci		{ 0xd00d, (mode->vtotal >> 8) & 0xff },
28862306a36Sopenharmony_ci		{ 0xd00e, mode->vtotal & 0xff },
28962306a36Sopenharmony_ci		{ 0xd00f, (mode->vdisplay >> 8) & 0xff },
29062306a36Sopenharmony_ci		{ 0xd010, mode->vdisplay & 0xff },
29162306a36Sopenharmony_ci		{ 0xd011, (mode->htotal >> 8) & 0xff },
29262306a36Sopenharmony_ci		{ 0xd012, mode->htotal & 0xff },
29362306a36Sopenharmony_ci		{ 0xd013, (mode->hdisplay >> 8) & 0xff },
29462306a36Sopenharmony_ci		{ 0xd014, mode->hdisplay & 0xff },
29562306a36Sopenharmony_ci		{ 0xd015, (mode->vsync_end - mode->vsync_start) & 0xff },
29662306a36Sopenharmony_ci		{ 0xd016, (mode->hsync_end - mode->hsync_start) & 0xff },
29762306a36Sopenharmony_ci		{ 0xd017, ((mode->vsync_start - mode->vdisplay) >> 8) & 0xff },
29862306a36Sopenharmony_ci		{ 0xd018, (mode->vsync_start - mode->vdisplay) & 0xff },
29962306a36Sopenharmony_ci		{ 0xd019, ((mode->hsync_start - mode->hdisplay) >> 8) & 0xff },
30062306a36Sopenharmony_ci		{ 0xd01a, (mode->hsync_start - mode->hdisplay) & 0xff },
30162306a36Sopenharmony_ci	};
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	return regmap_multi_reg_write(ctx->regmap, lt9211_timing,
30462306a36Sopenharmony_ci				      ARRAY_SIZE(lt9211_timing));
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic int lt9211_configure_plls(struct lt9211 *ctx,
30862306a36Sopenharmony_ci				 const struct drm_display_mode *mode)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	const struct reg_sequence lt9211_pcr_seq[] = {
31162306a36Sopenharmony_ci		{ 0xd026, 0x17 },
31262306a36Sopenharmony_ci		{ 0xd027, 0xc3 },
31362306a36Sopenharmony_ci		{ 0xd02d, 0x30 },
31462306a36Sopenharmony_ci		{ 0xd031, 0x10 },
31562306a36Sopenharmony_ci		{ 0xd023, 0x20 },
31662306a36Sopenharmony_ci		{ 0xd038, 0x02 },
31762306a36Sopenharmony_ci		{ 0xd039, 0x10 },
31862306a36Sopenharmony_ci		{ 0xd03a, 0x20 },
31962306a36Sopenharmony_ci		{ 0xd03b, 0x60 },
32062306a36Sopenharmony_ci		{ 0xd03f, 0x04 },
32162306a36Sopenharmony_ci		{ 0xd040, 0x08 },
32262306a36Sopenharmony_ci		{ 0xd041, 0x10 },
32362306a36Sopenharmony_ci		{ 0x810b, 0xee },
32462306a36Sopenharmony_ci		{ 0x810b, 0xfe },
32562306a36Sopenharmony_ci	};
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	unsigned int pval;
32862306a36Sopenharmony_ci	int ret;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	/* DeSSC PLL reference clock is 25 MHz XTal. */
33162306a36Sopenharmony_ci	ret = regmap_write(ctx->regmap, 0x822d, 0x48);
33262306a36Sopenharmony_ci	if (ret)
33362306a36Sopenharmony_ci		return ret;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	if (mode->clock < 44000) {
33662306a36Sopenharmony_ci		ret = regmap_write(ctx->regmap, 0x8235, 0x83);
33762306a36Sopenharmony_ci	} else if (mode->clock < 88000) {
33862306a36Sopenharmony_ci		ret = regmap_write(ctx->regmap, 0x8235, 0x82);
33962306a36Sopenharmony_ci	} else if (mode->clock < 176000) {
34062306a36Sopenharmony_ci		ret = regmap_write(ctx->regmap, 0x8235, 0x81);
34162306a36Sopenharmony_ci	} else {
34262306a36Sopenharmony_ci		dev_err(ctx->dev,
34362306a36Sopenharmony_ci			"Unsupported mode clock (%d kHz) above 176 MHz.\n",
34462306a36Sopenharmony_ci			mode->clock);
34562306a36Sopenharmony_ci		return -EINVAL;
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	if (ret)
34962306a36Sopenharmony_ci		return ret;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	/* Wait for the DeSSC PLL to stabilize. */
35262306a36Sopenharmony_ci	msleep(100);
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	ret = regmap_multi_reg_write(ctx->regmap, lt9211_pcr_seq,
35562306a36Sopenharmony_ci				     ARRAY_SIZE(lt9211_pcr_seq));
35662306a36Sopenharmony_ci	if (ret)
35762306a36Sopenharmony_ci		return ret;
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	/* PCR stability test takes seconds. */
36062306a36Sopenharmony_ci	ret = regmap_read_poll_timeout(ctx->regmap, 0xd087, pval, pval & 0x8,
36162306a36Sopenharmony_ci				       20000, 10000000);
36262306a36Sopenharmony_ci	if (ret)
36362306a36Sopenharmony_ci		dev_err(ctx->dev, "PCR unstable, ret=%i\n", ret);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	return ret;
36662306a36Sopenharmony_ci}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_cistatic int lt9211_configure_tx(struct lt9211 *ctx, bool jeida,
36962306a36Sopenharmony_ci			       bool bpp24, bool de)
37062306a36Sopenharmony_ci{
37162306a36Sopenharmony_ci	const struct reg_sequence system_lt9211_tx_phy_seq[] = {
37262306a36Sopenharmony_ci		/* DPI output disable */
37362306a36Sopenharmony_ci		{ 0x8262, 0x00 },
37462306a36Sopenharmony_ci		/* BIT(7) is LVDS dual-port */
37562306a36Sopenharmony_ci		{ 0x823b, 0x38 | (ctx->lvds_dual_link ? BIT(7) : 0) },
37662306a36Sopenharmony_ci		{ 0x823e, 0x92 },
37762306a36Sopenharmony_ci		{ 0x823f, 0x48 },
37862306a36Sopenharmony_ci		{ 0x8240, 0x31 },
37962306a36Sopenharmony_ci		{ 0x8243, 0x80 },
38062306a36Sopenharmony_ci		{ 0x8244, 0x00 },
38162306a36Sopenharmony_ci		{ 0x8245, 0x00 },
38262306a36Sopenharmony_ci		{ 0x8249, 0x00 },
38362306a36Sopenharmony_ci		{ 0x824a, 0x01 },
38462306a36Sopenharmony_ci		{ 0x824e, 0x00 },
38562306a36Sopenharmony_ci		{ 0x824f, 0x00 },
38662306a36Sopenharmony_ci		{ 0x8250, 0x00 },
38762306a36Sopenharmony_ci		{ 0x8253, 0x00 },
38862306a36Sopenharmony_ci		{ 0x8254, 0x01 },
38962306a36Sopenharmony_ci		/* LVDS channel order, Odd:Even 0x10..A:B, 0x40..B:A */
39062306a36Sopenharmony_ci		{ 0x8646, ctx->lvds_dual_link_even_odd_swap ? 0x40 : 0x10 },
39162306a36Sopenharmony_ci		{ 0x8120, 0x7b },
39262306a36Sopenharmony_ci		{ 0x816b, 0xff },
39362306a36Sopenharmony_ci	};
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	const struct reg_sequence system_lt9211_tx_dig_seq[] = {
39662306a36Sopenharmony_ci		{ 0x8559, 0x40 | (jeida ? BIT(7) : 0) |
39762306a36Sopenharmony_ci			  (de ? BIT(5) : 0) | (bpp24 ? BIT(4) : 0) },
39862306a36Sopenharmony_ci		{ 0x855a, 0xaa },
39962306a36Sopenharmony_ci		{ 0x855b, 0xaa },
40062306a36Sopenharmony_ci		{ 0x855c, ctx->lvds_dual_link ? BIT(0) : 0 },
40162306a36Sopenharmony_ci		{ 0x85a1, 0x77 },
40262306a36Sopenharmony_ci		{ 0x8640, 0x40 },
40362306a36Sopenharmony_ci		{ 0x8641, 0x34 },
40462306a36Sopenharmony_ci		{ 0x8642, 0x10 },
40562306a36Sopenharmony_ci		{ 0x8643, 0x23 },
40662306a36Sopenharmony_ci		{ 0x8644, 0x41 },
40762306a36Sopenharmony_ci		{ 0x8645, 0x02 },
40862306a36Sopenharmony_ci	};
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci	const struct reg_sequence system_lt9211_tx_pll_seq[] = {
41162306a36Sopenharmony_ci		/* TX PLL power down */
41262306a36Sopenharmony_ci		{ 0x8236, 0x01 },
41362306a36Sopenharmony_ci		{ 0x8237, ctx->lvds_dual_link ? 0x2a : 0x29 },
41462306a36Sopenharmony_ci		{ 0x8238, 0x06 },
41562306a36Sopenharmony_ci		{ 0x8239, 0x30 },
41662306a36Sopenharmony_ci		{ 0x823a, 0x8e },
41762306a36Sopenharmony_ci		{ 0x8737, 0x14 },
41862306a36Sopenharmony_ci		{ 0x8713, 0x00 },
41962306a36Sopenharmony_ci		{ 0x8713, 0x80 },
42062306a36Sopenharmony_ci	};
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	unsigned int pval;
42362306a36Sopenharmony_ci	int ret;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_phy_seq,
42662306a36Sopenharmony_ci				     ARRAY_SIZE(system_lt9211_tx_phy_seq));
42762306a36Sopenharmony_ci	if (ret)
42862306a36Sopenharmony_ci		return ret;
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_dig_seq,
43162306a36Sopenharmony_ci				     ARRAY_SIZE(system_lt9211_tx_dig_seq));
43262306a36Sopenharmony_ci	if (ret)
43362306a36Sopenharmony_ci		return ret;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_pll_seq,
43662306a36Sopenharmony_ci				     ARRAY_SIZE(system_lt9211_tx_pll_seq));
43762306a36Sopenharmony_ci	if (ret)
43862306a36Sopenharmony_ci		return ret;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	ret = regmap_read_poll_timeout(ctx->regmap, 0x871f, pval, pval & 0x80,
44162306a36Sopenharmony_ci				       10000, 1000000);
44262306a36Sopenharmony_ci	if (ret) {
44362306a36Sopenharmony_ci		dev_err(ctx->dev, "TX PLL unstable, ret=%i\n", ret);
44462306a36Sopenharmony_ci		return ret;
44562306a36Sopenharmony_ci	}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	ret = regmap_read_poll_timeout(ctx->regmap, 0x8720, pval, pval & 0x80,
44862306a36Sopenharmony_ci				       10000, 1000000);
44962306a36Sopenharmony_ci	if (ret) {
45062306a36Sopenharmony_ci		dev_err(ctx->dev, "TX PLL unstable, ret=%i\n", ret);
45162306a36Sopenharmony_ci		return ret;
45262306a36Sopenharmony_ci	}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	return 0;
45562306a36Sopenharmony_ci}
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_cistatic void lt9211_atomic_enable(struct drm_bridge *bridge,
45862306a36Sopenharmony_ci				 struct drm_bridge_state *old_bridge_state)
45962306a36Sopenharmony_ci{
46062306a36Sopenharmony_ci	struct lt9211 *ctx = bridge_to_lt9211(bridge);
46162306a36Sopenharmony_ci	struct drm_atomic_state *state = old_bridge_state->base.state;
46262306a36Sopenharmony_ci	const struct drm_bridge_state *bridge_state;
46362306a36Sopenharmony_ci	const struct drm_crtc_state *crtc_state;
46462306a36Sopenharmony_ci	const struct drm_display_mode *mode;
46562306a36Sopenharmony_ci	struct drm_connector *connector;
46662306a36Sopenharmony_ci	struct drm_crtc *crtc;
46762306a36Sopenharmony_ci	bool lvds_format_24bpp;
46862306a36Sopenharmony_ci	bool lvds_format_jeida;
46962306a36Sopenharmony_ci	u32 bus_flags;
47062306a36Sopenharmony_ci	int ret;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	ret = regulator_enable(ctx->vccio);
47362306a36Sopenharmony_ci	if (ret) {
47462306a36Sopenharmony_ci		dev_err(ctx->dev, "Failed to enable vccio: %d\n", ret);
47562306a36Sopenharmony_ci		return;
47662306a36Sopenharmony_ci	}
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	/* Deassert reset */
47962306a36Sopenharmony_ci	gpiod_set_value(ctx->reset_gpio, 1);
48062306a36Sopenharmony_ci	usleep_range(20000, 21000);	/* Very long post-reset delay. */
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	/* Get the LVDS format from the bridge state. */
48362306a36Sopenharmony_ci	bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
48462306a36Sopenharmony_ci	bus_flags = bridge_state->output_bus_cfg.flags;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	switch (bridge_state->output_bus_cfg.format) {
48762306a36Sopenharmony_ci	case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
48862306a36Sopenharmony_ci		lvds_format_24bpp = false;
48962306a36Sopenharmony_ci		lvds_format_jeida = true;
49062306a36Sopenharmony_ci		break;
49162306a36Sopenharmony_ci	case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
49262306a36Sopenharmony_ci		lvds_format_24bpp = true;
49362306a36Sopenharmony_ci		lvds_format_jeida = true;
49462306a36Sopenharmony_ci		break;
49562306a36Sopenharmony_ci	case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
49662306a36Sopenharmony_ci		lvds_format_24bpp = true;
49762306a36Sopenharmony_ci		lvds_format_jeida = false;
49862306a36Sopenharmony_ci		break;
49962306a36Sopenharmony_ci	default:
50062306a36Sopenharmony_ci		/*
50162306a36Sopenharmony_ci		 * Some bridges still don't set the correct
50262306a36Sopenharmony_ci		 * LVDS bus pixel format, use SPWG24 default
50362306a36Sopenharmony_ci		 * format until those are fixed.
50462306a36Sopenharmony_ci		 */
50562306a36Sopenharmony_ci		lvds_format_24bpp = true;
50662306a36Sopenharmony_ci		lvds_format_jeida = false;
50762306a36Sopenharmony_ci		dev_warn(ctx->dev,
50862306a36Sopenharmony_ci			 "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n",
50962306a36Sopenharmony_ci			 bridge_state->output_bus_cfg.format);
51062306a36Sopenharmony_ci		break;
51162306a36Sopenharmony_ci	}
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	/*
51462306a36Sopenharmony_ci	 * Retrieve the CRTC adjusted mode. This requires a little dance to go
51562306a36Sopenharmony_ci	 * from the bridge to the encoder, to the connector and to the CRTC.
51662306a36Sopenharmony_ci	 */
51762306a36Sopenharmony_ci	connector = drm_atomic_get_new_connector_for_encoder(state,
51862306a36Sopenharmony_ci							     bridge->encoder);
51962306a36Sopenharmony_ci	crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
52062306a36Sopenharmony_ci	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
52162306a36Sopenharmony_ci	mode = &crtc_state->adjusted_mode;
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	ret = lt9211_read_chipid(ctx);
52462306a36Sopenharmony_ci	if (ret)
52562306a36Sopenharmony_ci		return;
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	ret = lt9211_system_init(ctx);
52862306a36Sopenharmony_ci	if (ret)
52962306a36Sopenharmony_ci		return;
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	ret = lt9211_configure_rx(ctx);
53262306a36Sopenharmony_ci	if (ret)
53362306a36Sopenharmony_ci		return;
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	ret = lt9211_autodetect_rx(ctx, mode);
53662306a36Sopenharmony_ci	if (ret)
53762306a36Sopenharmony_ci		return;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	ret = lt9211_configure_timing(ctx, mode);
54062306a36Sopenharmony_ci	if (ret)
54162306a36Sopenharmony_ci		return;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	ret = lt9211_configure_plls(ctx, mode);
54462306a36Sopenharmony_ci	if (ret)
54562306a36Sopenharmony_ci		return;
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ci	ret = lt9211_configure_tx(ctx, lvds_format_jeida, lvds_format_24bpp,
54862306a36Sopenharmony_ci				  bus_flags & DRM_BUS_FLAG_DE_HIGH);
54962306a36Sopenharmony_ci	if (ret)
55062306a36Sopenharmony_ci		return;
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	dev_dbg(ctx->dev, "LT9211 enabled.\n");
55362306a36Sopenharmony_ci}
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_cistatic void lt9211_atomic_disable(struct drm_bridge *bridge,
55662306a36Sopenharmony_ci				  struct drm_bridge_state *old_bridge_state)
55762306a36Sopenharmony_ci{
55862306a36Sopenharmony_ci	struct lt9211 *ctx = bridge_to_lt9211(bridge);
55962306a36Sopenharmony_ci	int ret;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	/*
56262306a36Sopenharmony_ci	 * Put the chip in reset, pull nRST line low,
56362306a36Sopenharmony_ci	 * and assure lengthy 10ms reset low timing.
56462306a36Sopenharmony_ci	 */
56562306a36Sopenharmony_ci	gpiod_set_value(ctx->reset_gpio, 0);
56662306a36Sopenharmony_ci	usleep_range(10000, 11000);	/* Very long reset duration. */
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	ret = regulator_disable(ctx->vccio);
56962306a36Sopenharmony_ci	if (ret)
57062306a36Sopenharmony_ci		dev_err(ctx->dev, "Failed to disable vccio: %d\n", ret);
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	regcache_mark_dirty(ctx->regmap);
57362306a36Sopenharmony_ci}
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_cistatic enum drm_mode_status
57662306a36Sopenharmony_cilt9211_mode_valid(struct drm_bridge *bridge,
57762306a36Sopenharmony_ci		  const struct drm_display_info *info,
57862306a36Sopenharmony_ci		  const struct drm_display_mode *mode)
57962306a36Sopenharmony_ci{
58062306a36Sopenharmony_ci	/* LVDS output clock range 25..176 MHz */
58162306a36Sopenharmony_ci	if (mode->clock < 25000)
58262306a36Sopenharmony_ci		return MODE_CLOCK_LOW;
58362306a36Sopenharmony_ci	if (mode->clock > 176000)
58462306a36Sopenharmony_ci		return MODE_CLOCK_HIGH;
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	return MODE_OK;
58762306a36Sopenharmony_ci}
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci#define MAX_INPUT_SEL_FORMATS	1
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_cistatic u32 *
59262306a36Sopenharmony_cilt9211_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
59362306a36Sopenharmony_ci				 struct drm_bridge_state *bridge_state,
59462306a36Sopenharmony_ci				 struct drm_crtc_state *crtc_state,
59562306a36Sopenharmony_ci				 struct drm_connector_state *conn_state,
59662306a36Sopenharmony_ci				 u32 output_fmt,
59762306a36Sopenharmony_ci				 unsigned int *num_input_fmts)
59862306a36Sopenharmony_ci{
59962306a36Sopenharmony_ci	u32 *input_fmts;
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	*num_input_fmts = 0;
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
60462306a36Sopenharmony_ci			     GFP_KERNEL);
60562306a36Sopenharmony_ci	if (!input_fmts)
60662306a36Sopenharmony_ci		return NULL;
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	/* This is the DSI-end bus format */
60962306a36Sopenharmony_ci	input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
61062306a36Sopenharmony_ci	*num_input_fmts = 1;
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	return input_fmts;
61362306a36Sopenharmony_ci}
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_cistatic const struct drm_bridge_funcs lt9211_funcs = {
61662306a36Sopenharmony_ci	.attach			= lt9211_attach,
61762306a36Sopenharmony_ci	.mode_valid		= lt9211_mode_valid,
61862306a36Sopenharmony_ci	.atomic_enable		= lt9211_atomic_enable,
61962306a36Sopenharmony_ci	.atomic_disable		= lt9211_atomic_disable,
62062306a36Sopenharmony_ci	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
62162306a36Sopenharmony_ci	.atomic_destroy_state	= drm_atomic_helper_bridge_destroy_state,
62262306a36Sopenharmony_ci	.atomic_get_input_bus_fmts = lt9211_atomic_get_input_bus_fmts,
62362306a36Sopenharmony_ci	.atomic_reset		= drm_atomic_helper_bridge_reset,
62462306a36Sopenharmony_ci};
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_cistatic int lt9211_parse_dt(struct lt9211 *ctx)
62762306a36Sopenharmony_ci{
62862306a36Sopenharmony_ci	struct device_node *port2, *port3;
62962306a36Sopenharmony_ci	struct drm_bridge *panel_bridge;
63062306a36Sopenharmony_ci	struct device *dev = ctx->dev;
63162306a36Sopenharmony_ci	struct drm_panel *panel;
63262306a36Sopenharmony_ci	int dual_link;
63362306a36Sopenharmony_ci	int ret;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	ctx->vccio = devm_regulator_get(dev, "vccio");
63662306a36Sopenharmony_ci	if (IS_ERR(ctx->vccio))
63762306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(ctx->vccio),
63862306a36Sopenharmony_ci				     "Failed to get supply 'vccio'\n");
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_ci	ctx->lvds_dual_link = false;
64162306a36Sopenharmony_ci	ctx->lvds_dual_link_even_odd_swap = false;
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_ci	port2 = of_graph_get_port_by_id(dev->of_node, 2);
64462306a36Sopenharmony_ci	port3 = of_graph_get_port_by_id(dev->of_node, 3);
64562306a36Sopenharmony_ci	dual_link = drm_of_lvds_get_dual_link_pixel_order(port2, port3);
64662306a36Sopenharmony_ci	of_node_put(port2);
64762306a36Sopenharmony_ci	of_node_put(port3);
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_ci	if (dual_link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) {
65062306a36Sopenharmony_ci		ctx->lvds_dual_link = true;
65162306a36Sopenharmony_ci		/* Odd pixels to LVDS Channel A, even pixels to B */
65262306a36Sopenharmony_ci		ctx->lvds_dual_link_even_odd_swap = false;
65362306a36Sopenharmony_ci	} else if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) {
65462306a36Sopenharmony_ci		ctx->lvds_dual_link = true;
65562306a36Sopenharmony_ci		/* Even pixels to LVDS Channel A, odd pixels to B */
65662306a36Sopenharmony_ci		ctx->lvds_dual_link_even_odd_swap = true;
65762306a36Sopenharmony_ci	}
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci	ret = drm_of_find_panel_or_bridge(dev->of_node, 2, 0, &panel, &panel_bridge);
66062306a36Sopenharmony_ci	if (ret < 0)
66162306a36Sopenharmony_ci		return ret;
66262306a36Sopenharmony_ci	if (panel) {
66362306a36Sopenharmony_ci		panel_bridge = devm_drm_panel_bridge_add(dev, panel);
66462306a36Sopenharmony_ci		if (IS_ERR(panel_bridge))
66562306a36Sopenharmony_ci			return PTR_ERR(panel_bridge);
66662306a36Sopenharmony_ci	}
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_ci	ctx->panel_bridge = panel_bridge;
66962306a36Sopenharmony_ci
67062306a36Sopenharmony_ci	return 0;
67162306a36Sopenharmony_ci}
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_cistatic int lt9211_host_attach(struct lt9211 *ctx)
67462306a36Sopenharmony_ci{
67562306a36Sopenharmony_ci	const struct mipi_dsi_device_info info = {
67662306a36Sopenharmony_ci		.type = "lt9211",
67762306a36Sopenharmony_ci		.channel = 0,
67862306a36Sopenharmony_ci		.node = NULL,
67962306a36Sopenharmony_ci	};
68062306a36Sopenharmony_ci	struct device *dev = ctx->dev;
68162306a36Sopenharmony_ci	struct device_node *host_node;
68262306a36Sopenharmony_ci	struct device_node *endpoint;
68362306a36Sopenharmony_ci	struct mipi_dsi_device *dsi;
68462306a36Sopenharmony_ci	struct mipi_dsi_host *host;
68562306a36Sopenharmony_ci	int dsi_lanes;
68662306a36Sopenharmony_ci	int ret;
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
68962306a36Sopenharmony_ci	dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4);
69062306a36Sopenharmony_ci	host_node = of_graph_get_remote_port_parent(endpoint);
69162306a36Sopenharmony_ci	host = of_find_mipi_dsi_host_by_node(host_node);
69262306a36Sopenharmony_ci	of_node_put(host_node);
69362306a36Sopenharmony_ci	of_node_put(endpoint);
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci	if (!host)
69662306a36Sopenharmony_ci		return -EPROBE_DEFER;
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_ci	if (dsi_lanes < 0)
69962306a36Sopenharmony_ci		return dsi_lanes;
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_ci	dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
70262306a36Sopenharmony_ci	if (IS_ERR(dsi))
70362306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(dsi),
70462306a36Sopenharmony_ci				     "failed to create dsi device\n");
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_ci	ctx->dsi = dsi;
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_ci	dsi->lanes = dsi_lanes;
70962306a36Sopenharmony_ci	dsi->format = MIPI_DSI_FMT_RGB888;
71062306a36Sopenharmony_ci	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
71162306a36Sopenharmony_ci			  MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO_NO_HSA |
71262306a36Sopenharmony_ci			  MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP |
71362306a36Sopenharmony_ci			  MIPI_DSI_MODE_NO_EOT_PACKET;
71462306a36Sopenharmony_ci
71562306a36Sopenharmony_ci	ret = devm_mipi_dsi_attach(dev, dsi);
71662306a36Sopenharmony_ci	if (ret < 0) {
71762306a36Sopenharmony_ci		dev_err(dev, "failed to attach dsi to host: %d\n", ret);
71862306a36Sopenharmony_ci		return ret;
71962306a36Sopenharmony_ci	}
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_ci	return 0;
72262306a36Sopenharmony_ci}
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_cistatic int lt9211_probe(struct i2c_client *client)
72562306a36Sopenharmony_ci{
72662306a36Sopenharmony_ci	struct device *dev = &client->dev;
72762306a36Sopenharmony_ci	struct lt9211 *ctx;
72862306a36Sopenharmony_ci	int ret;
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
73162306a36Sopenharmony_ci	if (!ctx)
73262306a36Sopenharmony_ci		return -ENOMEM;
73362306a36Sopenharmony_ci
73462306a36Sopenharmony_ci	ctx->dev = dev;
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci	/*
73762306a36Sopenharmony_ci	 * Put the chip in reset, pull nRST line low,
73862306a36Sopenharmony_ci	 * and assure lengthy 10ms reset low timing.
73962306a36Sopenharmony_ci	 */
74062306a36Sopenharmony_ci	ctx->reset_gpio = devm_gpiod_get_optional(ctx->dev, "reset",
74162306a36Sopenharmony_ci						  GPIOD_OUT_LOW);
74262306a36Sopenharmony_ci	if (IS_ERR(ctx->reset_gpio))
74362306a36Sopenharmony_ci		return PTR_ERR(ctx->reset_gpio);
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	usleep_range(10000, 11000);	/* Very long reset duration. */
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci	ret = lt9211_parse_dt(ctx);
74862306a36Sopenharmony_ci	if (ret)
74962306a36Sopenharmony_ci		return ret;
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci	ctx->regmap = devm_regmap_init_i2c(client, &lt9211_regmap_config);
75262306a36Sopenharmony_ci	if (IS_ERR(ctx->regmap))
75362306a36Sopenharmony_ci		return PTR_ERR(ctx->regmap);
75462306a36Sopenharmony_ci
75562306a36Sopenharmony_ci	dev_set_drvdata(dev, ctx);
75662306a36Sopenharmony_ci	i2c_set_clientdata(client, ctx);
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	ctx->bridge.funcs = &lt9211_funcs;
75962306a36Sopenharmony_ci	ctx->bridge.of_node = dev->of_node;
76062306a36Sopenharmony_ci	drm_bridge_add(&ctx->bridge);
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci	ret = lt9211_host_attach(ctx);
76362306a36Sopenharmony_ci	if (ret)
76462306a36Sopenharmony_ci		drm_bridge_remove(&ctx->bridge);
76562306a36Sopenharmony_ci
76662306a36Sopenharmony_ci	return ret;
76762306a36Sopenharmony_ci}
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_cistatic void lt9211_remove(struct i2c_client *client)
77062306a36Sopenharmony_ci{
77162306a36Sopenharmony_ci	struct lt9211 *ctx = i2c_get_clientdata(client);
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci	drm_bridge_remove(&ctx->bridge);
77462306a36Sopenharmony_ci}
77562306a36Sopenharmony_ci
77662306a36Sopenharmony_cistatic struct i2c_device_id lt9211_id[] = {
77762306a36Sopenharmony_ci	{ "lontium,lt9211" },
77862306a36Sopenharmony_ci	{},
77962306a36Sopenharmony_ci};
78062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, lt9211_id);
78162306a36Sopenharmony_ci
78262306a36Sopenharmony_cistatic const struct of_device_id lt9211_match_table[] = {
78362306a36Sopenharmony_ci	{ .compatible = "lontium,lt9211" },
78462306a36Sopenharmony_ci	{},
78562306a36Sopenharmony_ci};
78662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, lt9211_match_table);
78762306a36Sopenharmony_ci
78862306a36Sopenharmony_cistatic struct i2c_driver lt9211_driver = {
78962306a36Sopenharmony_ci	.probe = lt9211_probe,
79062306a36Sopenharmony_ci	.remove = lt9211_remove,
79162306a36Sopenharmony_ci	.id_table = lt9211_id,
79262306a36Sopenharmony_ci	.driver = {
79362306a36Sopenharmony_ci		.name = "lt9211",
79462306a36Sopenharmony_ci		.of_match_table = lt9211_match_table,
79562306a36Sopenharmony_ci	},
79662306a36Sopenharmony_ci};
79762306a36Sopenharmony_cimodule_i2c_driver(lt9211_driver);
79862306a36Sopenharmony_ci
79962306a36Sopenharmony_ciMODULE_AUTHOR("Marek Vasut <marex@denx.de>");
80062306a36Sopenharmony_ciMODULE_DESCRIPTION("Lontium LT9211 DSI/LVDS/DPI bridge driver");
80162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
802