summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/bridge
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/bridge')
-rw-r--r--drivers/gpu/drm/bridge/Kconfig23
-rw-r--r--drivers/gpu/drm/bridge/Makefile2
-rw-r--r--drivers/gpu/drm/bridge/analogix/anx7625.c181
-rw-r--r--drivers/gpu/drm/bridge/analogix/anx7625.h1
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c6
-rw-r--r--drivers/gpu/drm/bridge/cdns-dsi.c2
-rw-r--r--drivers/gpu/drm/bridge/cros-ec-anx7688.c191
-rw-r--r--drivers/gpu/drm/bridge/ite-it66121.c10
-rw-r--r--drivers/gpu/drm/bridge/lontium-lt8912b.c3
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi83.c709
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi86.c4
11 files changed, 1033 insertions, 99 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index d25e900f07ef..85b673613687 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -50,6 +50,19 @@ config DRM_CHRONTEL_CH7033
If in doubt, say "N".
+config DRM_CROS_EC_ANX7688
+ tristate "ChromeOS EC ANX7688 bridge"
+ depends on OF
+ depends on I2C_CROS_EC_TUNNEL || COMPILE_TEST
+ select DRM_KMS_HELPER
+ select REGMAP_I2C
+ help
+ ChromeOS EC ANX7688 is an ultra-low power
+ 4K Ultra-HD (4096x2160p60) mobile HD transmitter
+ designed for ChromeOS devices. It converts HDMI
+ 2.0 to DisplayPort 1.3 Ultra-HD. It is connected
+ to the ChromeOS Embedded Controller.
+
config DRM_DISPLAY_CONNECTOR
tristate "Display connector support"
depends on OF
@@ -272,6 +285,16 @@ config DRM_TI_TFP410
help
Texas Instruments TFP410 DVI/HDMI Transmitter driver
+config DRM_TI_SN65DSI83
+ tristate "TI SN65DSI83 and SN65DSI84 DSI to LVDS bridge"
+ depends on OF
+ select DRM_KMS_HELPER
+ select REGMAP_I2C
+ select DRM_PANEL
+ select DRM_MIPI_DSI
+ help
+ Texas Instruments SN65DSI83 and SN65DSI84 DSI to LVDS Bridge driver
+
config DRM_TI_SN65DSI86
tristate "TI SN65DSI86 DSI to eDP bridge"
depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 965b54dccfe5..f2c73683cfcb 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -2,6 +2,7 @@
obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o
obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
+obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o
obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
@@ -22,6 +23,7 @@ obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
obj-$(CONFIG_DRM_TOSHIBA_TC358768) += tc358768.o
obj-$(CONFIG_DRM_TOSHIBA_TC358775) += tc358775.o
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
+obj-$(CONFIG_DRM_TI_SN65DSI83) += ti-sn65dsi83.o
obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c
index b4e349ca38fe..7519b7a0f29d 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.c
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.c
@@ -11,6 +11,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/types.h>
@@ -1005,33 +1006,6 @@ static void anx7625_power_on_init(struct anx7625_data *ctx)
}
}
-static void anx7625_chip_control(struct anx7625_data *ctx, int state)
-{
- struct device *dev = &ctx->client->dev;
-
- DRM_DEV_DEBUG_DRIVER(dev, "before set, power_state(%d).\n",
- atomic_read(&ctx->power_status));
-
- if (!ctx->pdata.low_power_mode)
- return;
-
- if (state) {
- atomic_inc(&ctx->power_status);
- if (atomic_read(&ctx->power_status) == 1)
- anx7625_power_on_init(ctx);
- } else {
- if (atomic_read(&ctx->power_status)) {
- atomic_dec(&ctx->power_status);
-
- if (atomic_read(&ctx->power_status) == 0)
- anx7625_power_standby(ctx);
- }
- }
-
- DRM_DEV_DEBUG_DRIVER(dev, "after set, power_state(%d).\n",
- atomic_read(&ctx->power_status));
-}
-
static void anx7625_init_gpio(struct anx7625_data *platform)
{
struct device *dev = &platform->client->dev;
@@ -1061,9 +1035,6 @@ static void anx7625_stop_dp_work(struct anx7625_data *ctx)
ctx->hpd_status = 0;
ctx->hpd_high_cnt = 0;
ctx->display_timing_valid = 0;
-
- if (ctx->pdata.low_power_mode == 0)
- anx7625_disable_pd_protocol(ctx);
}
static void anx7625_start_dp_work(struct anx7625_data *ctx)
@@ -1105,49 +1076,26 @@ static void anx7625_hpd_polling(struct anx7625_data *ctx)
int ret, val;
struct device *dev = &ctx->client->dev;
- if (atomic_read(&ctx->power_status) != 1) {
- DRM_DEV_DEBUG_DRIVER(dev, "No need to poling HPD status.\n");
- return;
- }
-
ret = readx_poll_timeout(anx7625_read_hpd_status_p0,
ctx, val,
((val & HPD_STATUS) || (val < 0)),
5000,
5000 * 100);
if (ret) {
- DRM_DEV_ERROR(dev, "HPD polling timeout!\n");
- } else {
- DRM_DEV_DEBUG_DRIVER(dev, "HPD raise up.\n");
- anx7625_reg_write(ctx, ctx->i2c.tcpc_client,
- INTR_ALERT_1, 0xFF);
- anx7625_reg_write(ctx, ctx->i2c.rx_p0_client,
- INTERFACE_CHANGE_INT, 0);
+ DRM_DEV_ERROR(dev, "no hpd.\n");
+ return;
}
- anx7625_start_dp_work(ctx);
-}
-
-static void anx7625_disconnect_check(struct anx7625_data *ctx)
-{
- if (atomic_read(&ctx->power_status) == 0)
- anx7625_stop_dp_work(ctx);
-}
-
-static void anx7625_low_power_mode_check(struct anx7625_data *ctx,
- int state)
-{
- struct device *dev = &ctx->client->dev;
+ DRM_DEV_DEBUG_DRIVER(dev, "system status: 0x%x. HPD raise up.\n", val);
+ anx7625_reg_write(ctx, ctx->i2c.tcpc_client,
+ INTR_ALERT_1, 0xFF);
+ anx7625_reg_write(ctx, ctx->i2c.rx_p0_client,
+ INTERFACE_CHANGE_INT, 0);
- DRM_DEV_DEBUG_DRIVER(dev, "low power mode check, state(%d).\n", state);
+ anx7625_start_dp_work(ctx);
- if (ctx->pdata.low_power_mode) {
- anx7625_chip_control(ctx, state);
- if (state)
- anx7625_hpd_polling(ctx);
- else
- anx7625_disconnect_check(ctx);
- }
+ if (!ctx->pdata.panel_bridge && ctx->bridge_attached)
+ drm_helper_hpd_irq_event(ctx->bridge.dev);
}
static void anx7625_remove_edid(struct anx7625_data *ctx)
@@ -1180,9 +1128,6 @@ static int anx7625_hpd_change_detect(struct anx7625_data *ctx)
int intr_vector, status;
struct device *dev = &ctx->client->dev;
- DRM_DEV_DEBUG_DRIVER(dev, "power_status=%d\n",
- (u32)atomic_read(&ctx->power_status));
-
status = anx7625_reg_write(ctx, ctx->i2c.tcpc_client,
INTR_ALERT_1, 0xFF);
if (status < 0) {
@@ -1228,22 +1173,25 @@ static void anx7625_work_func(struct work_struct *work)
struct anx7625_data, work);
mutex_lock(&ctx->lock);
+
+ if (pm_runtime_suspended(&ctx->client->dev))
+ goto unlock;
+
event = anx7625_hpd_change_detect(ctx);
- mutex_unlock(&ctx->lock);
if (event < 0)
- return;
+ goto unlock;
if (ctx->bridge_attached)
drm_helper_hpd_irq_event(ctx->bridge.dev);
+
+unlock:
+ mutex_unlock(&ctx->lock);
}
static irqreturn_t anx7625_intr_hpd_isr(int irq, void *data)
{
struct anx7625_data *ctx = (struct anx7625_data *)data;
- if (atomic_read(&ctx->power_status) != 1)
- return IRQ_NONE;
-
queue_work(ctx->workqueue, &ctx->work);
return IRQ_HANDLED;
@@ -1305,9 +1253,9 @@ static struct edid *anx7625_get_edid(struct anx7625_data *ctx)
return (struct edid *)edid;
}
- anx7625_low_power_mode_check(ctx, 1);
+ pm_runtime_get_sync(dev);
edid_num = sp_tx_edid_read(ctx, p_edid->edid_raw_data);
- anx7625_low_power_mode_check(ctx, 0);
+ pm_runtime_put_sync(dev);
if (edid_num < 1) {
DRM_DEV_ERROR(dev, "Fail to read EDID: %d\n", edid_num);
@@ -1611,10 +1559,7 @@ static void anx7625_bridge_enable(struct drm_bridge *bridge)
DRM_DEV_DEBUG_DRIVER(dev, "drm enable\n");
- anx7625_low_power_mode_check(ctx, 1);
-
- if (WARN_ON(!atomic_read(&ctx->power_status)))
- return;
+ pm_runtime_get_sync(dev);
anx7625_dp_start(ctx);
}
@@ -1624,14 +1569,11 @@ static void anx7625_bridge_disable(struct drm_bridge *bridge)
struct anx7625_data *ctx = bridge_to_anx7625(bridge);
struct device *dev = &ctx->client->dev;
- if (WARN_ON(!atomic_read(&ctx->power_status)))
- return;
-
DRM_DEV_DEBUG_DRIVER(dev, "drm disable\n");
anx7625_dp_stop(ctx);
- anx7625_low_power_mode_check(ctx, 0);
+ pm_runtime_put_sync(dev);
}
static enum drm_connector_status
@@ -1735,6 +1677,71 @@ static void anx7625_unregister_i2c_dummy_clients(struct anx7625_data *ctx)
i2c_unregister_device(ctx->i2c.tcpc_client);
}
+static int __maybe_unused anx7625_runtime_pm_suspend(struct device *dev)
+{
+ struct anx7625_data *ctx = dev_get_drvdata(dev);
+
+ mutex_lock(&ctx->lock);
+
+ anx7625_stop_dp_work(ctx);
+ anx7625_power_standby(ctx);
+
+ mutex_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static int __maybe_unused anx7625_runtime_pm_resume(struct device *dev)
+{
+ struct anx7625_data *ctx = dev_get_drvdata(dev);
+
+ mutex_lock(&ctx->lock);
+
+ anx7625_power_on_init(ctx);
+ anx7625_hpd_polling(ctx);
+
+ mutex_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static int __maybe_unused anx7625_resume(struct device *dev)
+{
+ struct anx7625_data *ctx = dev_get_drvdata(dev);
+
+ if (!ctx->pdata.intp_irq)
+ return 0;
+
+ if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) {
+ enable_irq(ctx->pdata.intp_irq);
+ anx7625_runtime_pm_resume(dev);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused anx7625_suspend(struct device *dev)
+{
+ struct anx7625_data *ctx = dev_get_drvdata(dev);
+
+ if (!ctx->pdata.intp_irq)
+ return 0;
+
+ if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) {
+ anx7625_runtime_pm_suspend(dev);
+ disable_irq(ctx->pdata.intp_irq);
+ flush_workqueue(ctx->workqueue);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops anx7625_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(anx7625_suspend, anx7625_resume)
+ SET_RUNTIME_PM_OPS(anx7625_runtime_pm_suspend,
+ anx7625_runtime_pm_resume, NULL)
+};
+
static int anx7625_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -1778,8 +1785,6 @@ static int anx7625_i2c_probe(struct i2c_client *client,
}
anx7625_init_gpio(platform);
- atomic_set(&platform->power_status, 0);
-
mutex_init(&platform->lock);
platform->pdata.intp_irq = client->irq;
@@ -1809,9 +1814,11 @@ static int anx7625_i2c_probe(struct i2c_client *client,
goto free_wq;
}
- if (platform->pdata.low_power_mode == 0) {
+ pm_runtime_enable(dev);
+
+ if (!platform->pdata.low_power_mode) {
anx7625_disable_pd_protocol(platform);
- atomic_set(&platform->power_status, 1);
+ pm_runtime_get_sync(dev);
}
/* Add work function */
@@ -1847,6 +1854,9 @@ static int anx7625_i2c_remove(struct i2c_client *client)
if (platform->pdata.intp_irq)
destroy_workqueue(platform->workqueue);
+ if (!platform->pdata.low_power_mode)
+ pm_runtime_put_sync_suspend(&client->dev);
+
anx7625_unregister_i2c_dummy_clients(platform);
kfree(platform);
@@ -1869,6 +1879,7 @@ static struct i2c_driver anx7625_driver = {
.driver = {
.name = "anx7625",
.of_match_table = anx_match_table,
+ .pm = &anx7625_pm_ops,
},
.probe = anx7625_i2c_probe,
.remove = anx7625_i2c_remove,
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.h b/drivers/gpu/drm/bridge/analogix/anx7625.h
index e4a086b3a3d7..034c3840028f 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.h
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.h
@@ -369,7 +369,6 @@ struct anx7625_i2c_client {
struct anx7625_data {
struct anx7625_platform_data pdata;
- atomic_t power_status;
int hpd_status;
int hpd_high_cnt;
/* Lock for work queue */
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c
index 0cd8f40fb690..5530fbf64f1e 100644
--- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c
@@ -2140,7 +2140,7 @@ cdns_mhdp_bridge_atomic_reset(struct drm_bridge *bridge)
if (!cdns_mhdp_state)
return NULL;
- __drm_atomic_helper_bridge_reset(bridge, &cdns_mhdp_state->base);
+ __drm_atomic_helper_bridge_reset(bridge, &cdns_mhdp_state->base);
return &cdns_mhdp_state->base;
}
@@ -2478,9 +2478,9 @@ static int cdns_mhdp_probe(struct platform_device *pdev)
clk_prepare_enable(clk);
pm_runtime_enable(dev);
- ret = pm_runtime_get_sync(dev);
+ ret = pm_runtime_resume_and_get(dev);
if (ret < 0) {
- dev_err(dev, "pm_runtime_get_sync failed\n");
+ dev_err(dev, "pm_runtime_resume_and_get failed\n");
pm_runtime_disable(dev);
goto clk_disable;
}
diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c
index 76373e31df92..b31281f76117 100644
--- a/drivers/gpu/drm/bridge/cdns-dsi.c
+++ b/drivers/gpu/drm/bridge/cdns-dsi.c
@@ -1028,7 +1028,7 @@ static ssize_t cdns_dsi_transfer(struct mipi_dsi_host *host,
struct mipi_dsi_packet packet;
int ret, i, tx_len, rx_len;
- ret = pm_runtime_get_sync(host->dev);
+ ret = pm_runtime_resume_and_get(host->dev);
if (ret < 0)
return ret;
diff --git a/drivers/gpu/drm/bridge/cros-ec-anx7688.c b/drivers/gpu/drm/bridge/cros-ec-anx7688.c
new file mode 100644
index 000000000000..0f6d907432e3
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cros-ec-anx7688.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * CrOS EC ANX7688 HDMI->DP bridge driver
+ *
+ * Copyright 2020 Google LLC
+ */
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_print.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+/* Register addresses */
+#define ANX7688_VENDOR_ID_REG 0x00
+#define ANX7688_DEVICE_ID_REG 0x02
+
+#define ANX7688_FW_VERSION_REG 0x80
+
+#define ANX7688_DP_BANDWIDTH_REG 0x85
+#define ANX7688_DP_LANE_COUNT_REG 0x86
+
+#define ANX7688_VENDOR_ID 0x1f29
+#define ANX7688_DEVICE_ID 0x7688
+
+/* First supported firmware version (0.85) */
+#define ANX7688_MINIMUM_FW_VERSION 0x0085
+
+static const struct regmap_config cros_ec_anx7688_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+struct cros_ec_anx7688 {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct drm_bridge bridge;
+ bool filter;
+};
+
+static inline struct cros_ec_anx7688 *
+bridge_to_cros_ec_anx7688(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct cros_ec_anx7688, bridge);
+}
+
+static bool cros_ec_anx7688_bridge_mode_fixup(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct cros_ec_anx7688 *anx = bridge_to_cros_ec_anx7688(bridge);
+ int totalbw, requiredbw;
+ u8 dpbw, lanecount;
+ u8 regs[2];
+ int ret;
+
+ if (!anx->filter)
+ return true;
+
+ /* Read both regs 0x85 (bandwidth) and 0x86 (lane count). */
+ ret = regmap_bulk_read(anx->regmap, ANX7688_DP_BANDWIDTH_REG, regs, 2);
+ if (ret < 0) {
+ DRM_ERROR("Failed to read bandwidth/lane count\n");
+ return false;
+ }
+ dpbw = regs[0];
+ lanecount = regs[1];
+
+ /* Maximum 0x19 bandwidth (6.75 Gbps Turbo mode), 2 lanes */
+ if (dpbw > 0x19 || lanecount > 2) {
+ DRM_ERROR("Invalid bandwidth/lane count (%02x/%d)\n", dpbw,
+ lanecount);
+ return false;
+ }
+
+ /* Compute available bandwidth (kHz) */
+ totalbw = dpbw * lanecount * 270000 * 8 / 10;
+
+ /* Required bandwidth (8 bpc, kHz) */
+ requiredbw = mode->clock * 8 * 3;
+
+ DRM_DEBUG_KMS("DP bandwidth: %d kHz (%02x/%d); mode requires %d Khz\n",
+ totalbw, dpbw, lanecount, requiredbw);
+
+ if (totalbw == 0) {
+ DRM_ERROR("Bandwidth/lane count are 0, not rejecting modes\n");
+ return true;
+ }
+
+ return totalbw >= requiredbw;
+}
+
+static const struct drm_bridge_funcs cros_ec_anx7688_bridge_funcs = {
+ .mode_fixup = cros_ec_anx7688_bridge_mode_fixup,
+};
+
+static int cros_ec_anx7688_bridge_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct cros_ec_anx7688 *anx7688;
+ u16 vendor, device, fw_version;
+ u8 buffer[4];
+ int ret;
+
+ anx7688 = devm_kzalloc(dev, sizeof(*anx7688), GFP_KERNEL);
+ if (!anx7688)
+ return -ENOMEM;
+
+ anx7688->client = client;
+ i2c_set_clientdata(client, anx7688);
+
+ anx7688->regmap = devm_regmap_init_i2c(client, &cros_ec_anx7688_regmap_config);
+ if (IS_ERR(anx7688->regmap)) {
+ ret = PTR_ERR(anx7688->regmap);
+ dev_err(dev, "regmap i2c init failed: %d\n", ret);
+ return ret;
+ }
+
+ /* Read both vendor and device id (4 bytes). */
+ ret = regmap_bulk_read(anx7688->regmap, ANX7688_VENDOR_ID_REG,
+ buffer, 4);
+ if (ret) {
+ dev_err(dev, "Failed to read chip vendor/device id\n");
+ return ret;
+ }
+
+ vendor = (u16)buffer[1] << 8 | buffer[0];
+ device = (u16)buffer[3] << 8 | buffer[2];
+ if (vendor != ANX7688_VENDOR_ID || device != ANX7688_DEVICE_ID) {
+ dev_err(dev, "Invalid vendor/device id %04x/%04x\n",
+ vendor, device);
+ return -ENODEV;
+ }
+
+ ret = regmap_bulk_read(anx7688->regmap, ANX7688_FW_VERSION_REG,
+ buffer, 2);
+ if (ret) {
+ dev_err(dev, "Failed to read firmware version\n");
+ return ret;
+ }
+
+ fw_version = (u16)buffer[0] << 8 | buffer[1];
+ dev_info(dev, "ANX7688 firmware version 0x%04x\n", fw_version);
+
+ anx7688->bridge.of_node = dev->of_node;
+
+ /* FW version >= 0.85 supports bandwidth/lane count registers */
+ if (fw_version >= ANX7688_MINIMUM_FW_VERSION)
+ anx7688->filter = true;
+ else
+ /* Warn, but not fail, for backwards compatibility */
+ DRM_WARN("Old ANX7688 FW version (0x%04x), not filtering\n",
+ fw_version);
+
+ anx7688->bridge.funcs = &cros_ec_anx7688_bridge_funcs;
+ drm_bridge_add(&anx7688->bridge);
+
+ return 0;
+}
+
+static int cros_ec_anx7688_bridge_remove(struct i2c_client *client)
+{
+ struct cros_ec_anx7688 *anx7688 = i2c_get_clientdata(client);
+
+ drm_bridge_remove(&anx7688->bridge);
+
+ return 0;
+}
+
+static const struct of_device_id cros_ec_anx7688_bridge_match_table[] = {
+ { .compatible = "google,cros-ec-anx7688" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, cros_ec_anx7688_bridge_match_table);
+
+static struct i2c_driver cros_ec_anx7688_bridge_driver = {
+ .probe_new = cros_ec_anx7688_bridge_probe,
+ .remove = cros_ec_anx7688_bridge_remove,
+ .driver = {
+ .name = "cros-ec-anx7688-bridge",
+ .of_match_table = cros_ec_anx7688_bridge_match_table,
+ },
+};
+
+module_i2c_driver(cros_ec_anx7688_bridge_driver);
+
+MODULE_DESCRIPTION("ChromeOS EC ANX7688 HDMI->DP bridge driver");
+MODULE_AUTHOR("Nicolas Boichat <drinkcat@chromium.org>");
+MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/ite-it66121.c b/drivers/gpu/drm/bridge/ite-it66121.c
index d8a60691fd32..7149ed40af83 100644
--- a/drivers/gpu/drm/bridge/ite-it66121.c
+++ b/drivers/gpu/drm/bridge/ite-it66121.c
@@ -898,14 +898,14 @@ static int it66121_probe(struct i2c_client *client,
return -ENXIO;
}
- ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
- if (!ep)
- return -EINVAL;
-
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
+ ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
+ if (!ep)
+ return -EINVAL;
+
ctx->dev = dev;
ctx->client = client;
@@ -943,7 +943,7 @@ static int it66121_probe(struct i2c_client *client,
ctx->regmap = devm_regmap_init_i2c(client, &it66121_regmap_config);
if (IS_ERR(ctx->regmap)) {
ite66121_power_off(ctx);
- return PTR_ERR(ctx);
+ return PTR_ERR(ctx->regmap);
}
regmap_read(ctx->regmap, IT66121_VENDOR_ID0_REG, &vendor_ids[0]);
diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c
index 443f1b47e031..76c720b535fb 100644
--- a/drivers/gpu/drm/bridge/lontium-lt8912b.c
+++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c
@@ -7,8 +7,6 @@
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
-#include <linux/gpio.h>
-#include <linux/of_gpio.h>
#include <linux/regmap.h>
#include <drm/drm_probe_helper.h>
@@ -755,7 +753,6 @@ static struct i2c_driver lt8912_i2c_driver = {
.driver = {
.name = "lt8912",
.of_match_table = lt8912_dt_match,
- .owner = THIS_MODULE,
},
.probe = lt8912_probe,
.remove = lt8912_remove,
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
new file mode 100644
index 000000000000..750f2172ef08
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
@@ -0,0 +1,709 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI SN65DSI83,84,85 driver
+ *
+ * Currently supported:
+ * - SN65DSI83
+ * = 1x Single-link DSI ~ 1x Single-link LVDS
+ * - Supported
+ * - Single-link LVDS mode tested
+ * - SN65DSI84
+ * = 1x Single-link DSI ~ 2x Single-link or 1x Dual-link LVDS
+ * - Supported
+ * - Dual-link LVDS mode tested
+ * - 2x Single-link LVDS mode unsupported
+ * (should be easy to add by someone who has the HW)
+ * - SN65DSI85
+ * = 2x Single-link or 1x Dual-link DSI ~ 2x Single-link or 1x Dual-link LVDS
+ * - Unsupported
+ * (should be easy to add by someone who has the HW)
+ *
+ * Copyright (C) 2021 Marek Vasut <marex@denx.de>
+ *
+ * Based on previous work of:
+ * Valentin Raevsky <valentin@compulab.co.il>
+ * Philippe Schenker <philippe.schenker@toradex.com>
+ */
+
+#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 <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>
+
+/* ID registers */
+#define REG_ID(n) (0x00 + (n))
+/* Reset and clock registers */
+#define REG_RC_RESET 0x09
+#define REG_RC_RESET_SOFT_RESET BIT(0)
+#define REG_RC_LVDS_PLL 0x0a
+#define REG_RC_LVDS_PLL_PLL_EN_STAT BIT(7)
+#define REG_RC_LVDS_PLL_LVDS_CLK_RANGE(n) (((n) & 0x7) << 1)
+#define REG_RC_LVDS_PLL_HS_CLK_SRC_DPHY BIT(0)
+#define REG_RC_DSI_CLK 0x0b
+#define REG_RC_DSI_CLK_DSI_CLK_DIVIDER(n) (((n) & 0x1f) << 3)
+#define REG_RC_DSI_CLK_REFCLK_MULTIPLIER(n) ((n) & 0x3)
+#define REG_RC_PLL_EN 0x0d
+#define REG_RC_PLL_EN_PLL_EN BIT(0)
+/* DSI registers */
+#define REG_DSI_LANE 0x10
+#define REG_DSI_LANE_LEFT_RIGHT_PIXELS BIT(7) /* DSI85-only */
+#define REG_DSI_LANE_DSI_CHANNEL_MODE_DUAL 0 /* DSI85-only */
+#define REG_DSI_LANE_DSI_CHANNEL_MODE_2SINGLE BIT(6) /* DSI85-only */
+#define REG_DSI_LANE_DSI_CHANNEL_MODE_SINGLE BIT(5)
+#define REG_DSI_LANE_CHA_DSI_LANES(n) (((n) & 0x3) << 3)
+#define REG_DSI_LANE_CHB_DSI_LANES(n) (((n) & 0x3) << 1)
+#define REG_DSI_LANE_SOT_ERR_TOL_DIS BIT(0)
+#define REG_DSI_EQ 0x11
+#define REG_DSI_EQ_CHA_DSI_DATA_EQ(n) (((n) & 0x3) << 6)
+#define REG_DSI_EQ_CHA_DSI_CLK_EQ(n) (((n) & 0x3) << 2)
+#define REG_DSI_CLK 0x12
+#define REG_DSI_CLK_CHA_DSI_CLK_RANGE(n) ((n) & 0xff)
+/* LVDS registers */
+#define REG_LVDS_FMT 0x18
+#define REG_LVDS_FMT_DE_NEG_POLARITY BIT(7)
+#define REG_LVDS_FMT_HS_NEG_POLARITY BIT(6)
+#define REG_LVDS_FMT_VS_NEG_POLARITY BIT(5)
+#define REG_LVDS_FMT_LVDS_LINK_CFG BIT(4) /* 0:AB 1:A-only */
+#define REG_LVDS_FMT_CHA_24BPP_MODE BIT(3)
+#define REG_LVDS_FMT_CHB_24BPP_MODE BIT(2)
+#define REG_LVDS_FMT_CHA_24BPP_FORMAT1 BIT(1)
+#define REG_LVDS_FMT_CHB_24BPP_FORMAT1 BIT(0)
+#define REG_LVDS_VCOM 0x19
+#define REG_LVDS_VCOM_CHA_LVDS_VOCM BIT(6)
+#define REG_LVDS_VCOM_CHB_LVDS_VOCM BIT(4)
+#define REG_LVDS_VCOM_CHA_LVDS_VOD_SWING(n) (((n) & 0x3) << 2)
+#define REG_LVDS_VCOM_CHB_LVDS_VOD_SWING(n) ((n) & 0x3)
+#define REG_LVDS_LANE 0x1a
+#define REG_LVDS_LANE_EVEN_ODD_SWAP BIT(6)
+#define REG_LVDS_LANE_CHA_REVERSE_LVDS BIT(5)
+#define REG_LVDS_LANE_CHB_REVERSE_LVDS BIT(4)
+#define REG_LVDS_LANE_CHA_LVDS_TERM BIT(1)
+#define REG_LVDS_LANE_CHB_LVDS_TERM BIT(0)
+#define REG_LVDS_CM 0x1b
+#define REG_LVDS_CM_CHA_LVDS_CM_ADJUST(n) (((n) & 0x3) << 4)
+#define REG_LVDS_CM_CHB_LVDS_CM_ADJUST(n) ((n) & 0x3)
+/* Video registers */
+#define REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW 0x20
+#define REG_VID_CHA_ACTIVE_LINE_LENGTH_HIGH 0x21
+#define REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW 0x24
+#define REG_VID_CHA_VERTICAL_DISPLAY_SIZE_HIGH 0x25
+#define REG_VID_CHA_SYNC_DELAY_LOW 0x28
+#define REG_VID_CHA_SYNC_DELAY_HIGH 0x29
+#define REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW 0x2c
+#define REG_VID_CHA_HSYNC_PULSE_WIDTH_HIGH 0x2d
+#define REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW 0x30
+#define REG_VID_CHA_VSYNC_PULSE_WIDTH_HIGH 0x31
+#define REG_VID_CHA_HORIZONTAL_BACK_PORCH 0x34
+#define REG_VID_CHA_VERTICAL_BACK_PORCH 0x36
+#define REG_VID_CHA_HORIZONTAL_FRONT_PORCH 0x38
+#define REG_VID_CHA_VERTICAL_FRONT_PORCH 0x3a
+#define REG_VID_CHA_TEST_PATTERN 0x3c
+/* IRQ registers */
+#define REG_IRQ_GLOBAL 0xe0
+#define REG_IRQ_GLOBAL_IRQ_EN BIT(0)
+#define REG_IRQ_EN 0xe1
+#define REG_IRQ_EN_CHA_SYNCH_ERR_EN BIT(7)
+#define REG_IRQ_EN_CHA_CRC_ERR_EN BIT(6)
+#define REG_IRQ_EN_CHA_UNC_ECC_ERR_EN BIT(5)
+#define REG_IRQ_EN_CHA_COR_ECC_ERR_EN BIT(4)
+#define REG_IRQ_EN_CHA_LLP_ERR_EN BIT(3)
+#define REG_IRQ_EN_CHA_SOT_BIT_ERR_EN BIT(2)
+#define REG_IRQ_EN_CHA_PLL_UNLOCK_EN BIT(0)
+#define REG_IRQ_STAT 0xe5
+#define REG_IRQ_STAT_CHA_SYNCH_ERR BIT(7)
+#define REG_IRQ_STAT_CHA_CRC_ERR BIT(6)
+#define REG_IRQ_STAT_CHA_UNC_ECC_ERR BIT(5)
+#define REG_IRQ_STAT_CHA_COR_ECC_ERR BIT(4)
+#define REG_IRQ_STAT_CHA_LLP_ERR BIT(3)
+#define REG_IRQ_STAT_CHA_SOT_BIT_ERR BIT(2)
+#define REG_IRQ_STAT_CHA_PLL_UNLOCK BIT(0)
+
+enum sn65dsi83_model {
+ MODEL_SN65DSI83,
+ MODEL_SN65DSI84,
+};
+
+struct sn65dsi83 {
+ struct drm_bridge bridge;
+ struct drm_display_mode mode;
+ struct device *dev;
+ struct regmap *regmap;
+ struct device_node *host_node;
+ struct mipi_dsi_device *dsi;
+ struct drm_bridge *panel_bridge;
+ struct gpio_desc *enable_gpio;
+ int dsi_lanes;
+ bool lvds_dual_link;
+ bool lvds_dual_link_even_odd_swap;
+ bool lvds_format_24bpp;
+ bool lvds_format_jeida;
+};
+
+static const struct regmap_range sn65dsi83_readable_ranges[] = {
+ regmap_reg_range(REG_ID(0), REG_ID(8)),
+ regmap_reg_range(REG_RC_LVDS_PLL, REG_RC_DSI_CLK),
+ regmap_reg_range(REG_RC_PLL_EN, REG_RC_PLL_EN),
+ regmap_reg_range(REG_DSI_LANE, REG_DSI_CLK),
+ regmap_reg_range(REG_LVDS_FMT, REG_LVDS_CM),
+ regmap_reg_range(REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW,
+ REG_VID_CHA_ACTIVE_LINE_LENGTH_HIGH),
+ regmap_reg_range(REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW,
+ REG_VID_CHA_VERTICAL_DISPLAY_SIZE_HIGH),
+ regmap_reg_range(REG_VID_CHA_SYNC_DELAY_LOW,
+ REG_VID_CHA_SYNC_DELAY_HIGH),
+ regmap_reg_range(REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW,
+ REG_VID_CHA_HSYNC_PULSE_WIDTH_HIGH),
+ regmap_reg_range(REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW,
+ REG_VID_CHA_VSYNC_PULSE_WIDTH_HIGH),
+ regmap_reg_range(REG_VID_CHA_HORIZONTAL_BACK_PORCH,
+ REG_VID_CHA_HORIZONTAL_BACK_PORCH),
+ regmap_reg_range(REG_VID_CHA_VERTICAL_BACK_PORCH,
+ REG_VID_CHA_VERTICAL_BACK_PORCH),
+ regmap_reg_range(REG_VID_CHA_HORIZONTAL_FRONT_PORCH,
+ REG_VID_CHA_HORIZONTAL_FRONT_PORCH),
+ regmap_reg_range(REG_VID_CHA_VERTICAL_FRONT_PORCH,
+ REG_VID_CHA_VERTICAL_FRONT_PORCH),
+ regmap_reg_range(REG_VID_CHA_TEST_PATTERN, REG_VID_CHA_TEST_PATTERN),
+ regmap_reg_range(REG_IRQ_GLOBAL, REG_IRQ_EN),
+ regmap_reg_range(REG_IRQ_STAT, REG_IRQ_STAT),
+};
+
+static const struct regmap_access_table sn65dsi83_readable_table = {
+ .yes_ranges = sn65dsi83_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(sn65dsi83_readable_ranges),
+};
+
+static const struct regmap_range sn65dsi83_writeable_ranges[] = {
+ regmap_reg_range(REG_RC_RESET, REG_RC_DSI_CLK),
+ regmap_reg_range(REG_RC_PLL_EN, REG_RC_PLL_EN),
+ regmap_reg_range(REG_DSI_LANE, REG_DSI_CLK),
+ regmap_reg_range(REG_LVDS_FMT, REG_LVDS_CM),
+ regmap_reg_range(REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW,
+ REG_VID_CHA_ACTIVE_LINE_LENGTH_HIGH),
+ regmap_reg_range(REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW,
+ REG_VID_CHA_VERTICAL_DISPLAY_SIZE_HIGH),
+ regmap_reg_range(REG_VID_CHA_SYNC_DELAY_LOW,
+ REG_VID_CHA_SYNC_DELAY_HIGH),
+ regmap_reg_range(REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW,
+ REG_VID_CHA_HSYNC_PULSE_WIDTH_HIGH),
+ regmap_reg_range(REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW,
+ REG_VID_CHA_VSYNC_PULSE_WIDTH_HIGH),
+ regmap_reg_range(REG_VID_CHA_HORIZONTAL_BACK_PORCH,
+ REG_VID_CHA_HORIZONTAL_BACK_PORCH),
+ regmap_reg_range(REG_VID_CHA_VERTICAL_BACK_PORCH,
+ REG_VID_CHA_VERTICAL_BACK_PORCH),
+ regmap_reg_range(REG_VID_CHA_HORIZONTAL_FRONT_PORCH,
+ REG_VID_CHA_HORIZONTAL_FRONT_PORCH),
+ regmap_reg_range(REG_VID_CHA_VERTICAL_FRONT_PORCH,
+ REG_VID_CHA_VERTICAL_FRONT_PORCH),
+ regmap_reg_range(REG_VID_CHA_TEST_PATTERN, REG_VID_CHA_TEST_PATTERN),
+ regmap_reg_range(REG_IRQ_GLOBAL, REG_IRQ_EN),
+ regmap_reg_range(REG_IRQ_STAT, REG_IRQ_STAT),
+};
+
+static const struct regmap_access_table sn65dsi83_writeable_table = {
+ .yes_ranges = sn65dsi83_writeable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(sn65dsi83_writeable_ranges),
+};
+
+static const struct regmap_range sn65dsi83_volatile_ranges[] = {
+ regmap_reg_range(REG_RC_RESET, REG_RC_RESET),
+ regmap_reg_range(REG_RC_LVDS_PLL, REG_RC_LVDS_PLL),
+ regmap_reg_range(REG_IRQ_STAT, REG_IRQ_STAT),
+};
+
+static const struct regmap_access_table sn65dsi83_volatile_table = {
+ .yes_ranges = sn65dsi83_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(sn65dsi83_volatile_ranges),
+};
+
+static const struct regmap_config sn65dsi83_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &sn65dsi83_readable_table,
+ .wr_table = &sn65dsi83_writeable_table,
+ .volatile_table = &sn65dsi83_volatile_table,
+ .cache_type = REGCACHE_RBTREE,
+ .max_register = REG_IRQ_STAT,
+};
+
+static struct sn65dsi83 *bridge_to_sn65dsi83(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct sn65dsi83, bridge);
+}
+
+static int sn65dsi83_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
+ struct device *dev = ctx->dev;
+ struct mipi_dsi_device *dsi;
+ struct mipi_dsi_host *host;
+ int ret = 0;
+
+ const struct mipi_dsi_device_info info = {
+ .type = "sn65dsi83",
+ .channel = 0,
+ .node = NULL,
+ };
+
+ host = of_find_mipi_dsi_host_by_node(ctx->host_node);
+ if (!host) {
+ dev_err(dev, "failed to find dsi host\n");
+ return -EPROBE_DEFER;
+ }
+
+ dsi = mipi_dsi_device_register_full(host, &info);
+ if (IS_ERR(dsi)) {
+ return dev_err_probe(dev, PTR_ERR(dsi),
+ "failed to create dsi device\n");
+ }
+
+ ctx->dsi = dsi;
+
+ dsi->lanes = ctx->dsi_lanes;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "failed to attach dsi to host\n");
+ goto err_dsi_attach;
+ }
+
+ return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
+ &ctx->bridge, flags);
+
+err_dsi_attach:
+ mipi_dsi_device_unregister(dsi);
+ return ret;
+}
+
+static void sn65dsi83_pre_enable(struct drm_bridge *bridge)
+{
+ struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
+
+ /*
+ * Reset the chip, pull EN line low for t_reset=10ms,
+ * then high for t_en=1ms.
+ */
+ regcache_mark_dirty(ctx->regmap);
+ gpiod_set_value(ctx->enable_gpio, 0);
+ usleep_range(10000, 11000);
+ gpiod_set_value(ctx->enable_gpio, 1);
+ usleep_range(1000, 1100);
+}
+
+static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx)
+{
+ /*
+ * The encoding of the LVDS_CLK_RANGE is as follows:
+ * 000 - 25 MHz <= LVDS_CLK < 37.5 MHz
+ * 001 - 37.5 MHz <= LVDS_CLK < 62.5 MHz
+ * 010 - 62.5 MHz <= LVDS_CLK < 87.5 MHz
+ * 011 - 87.5 MHz <= LVDS_CLK < 112.5 MHz
+ * 100 - 112.5 MHz <= LVDS_CLK < 137.5 MHz
+ * 101 - 137.5 MHz <= LVDS_CLK <= 154 MHz
+ * which is a range of 12.5MHz..162.5MHz in 50MHz steps, except that
+ * the ends of the ranges are clamped to the supported range. Since
+ * sn65dsi83_mode_valid() already filters the valid modes and limits
+ * the clock to 25..154 MHz, the range calculation can be simplified
+ * as follows:
+ */
+ int mode_clock = ctx->mode.clock;
+
+ if (ctx->lvds_dual_link)
+ mode_clock /= 2;
+
+ return (mode_clock - 12500) / 25000;
+}
+
+static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx)
+{
+ /*
+ * The encoding of the CHA_DSI_CLK_RANGE is as follows:
+ * 0x00 through 0x07 - Reserved
+ * 0x08 - 40 <= DSI_CLK < 45 MHz
+ * 0x09 - 45 <= DSI_CLK < 50 MHz
+ * ...
+ * 0x63 - 495 <= DSI_CLK < 500 MHz
+ * 0x64 - 500 MHz
+ * 0x65 through 0xFF - Reserved
+ * which is DSI clock in 5 MHz steps, clamped to 40..500 MHz.
+ * The DSI clock are calculated as:
+ * DSI_CLK = mode clock * bpp / dsi_data_lanes / 2
+ * the 2 is there because the bus is DDR.
+ */
+ return DIV_ROUND_UP(clamp((unsigned int)ctx->mode.clock *
+ mipi_dsi_pixel_format_to_bpp(ctx->dsi->format) /
+ ctx->dsi_lanes / 2, 40000U, 500000U), 5000U);
+}
+
+static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx)
+{
+ /* The divider is (DSI_CLK / LVDS_CLK) - 1, which really is: */
+ unsigned int dsi_div = mipi_dsi_pixel_format_to_bpp(ctx->dsi->format);
+
+ dsi_div /= ctx->dsi_lanes;
+
+ if (!ctx->lvds_dual_link)
+ dsi_div /= 2;
+
+ return dsi_div - 1;
+}
+
+static void sn65dsi83_enable(struct drm_bridge *bridge)
+{
+ struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
+ unsigned int pval;
+ u16 val;
+ int ret;
+
+ /* Clear reset, disable PLL */
+ regmap_write(ctx->regmap, REG_RC_RESET, 0x00);
+ regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00);
+
+ /* Reference clock derived from DSI link clock. */
+ regmap_write(ctx->regmap, REG_RC_LVDS_PLL,
+ REG_RC_LVDS_PLL_LVDS_CLK_RANGE(sn65dsi83_get_lvds_range(ctx)) |
+ REG_RC_LVDS_PLL_HS_CLK_SRC_DPHY);
+ regmap_write(ctx->regmap, REG_DSI_CLK,
+ REG_DSI_CLK_CHA_DSI_CLK_RANGE(sn65dsi83_get_dsi_range(ctx)));
+ regmap_write(ctx->regmap, REG_RC_DSI_CLK,
+ REG_RC_DSI_CLK_DSI_CLK_DIVIDER(sn65dsi83_get_dsi_div(ctx)));
+
+ /* Set number of DSI lanes and LVDS link config. */
+ regmap_write(ctx->regmap, REG_DSI_LANE,
+ REG_DSI_LANE_DSI_CHANNEL_MODE_SINGLE |
+ REG_DSI_LANE_CHA_DSI_LANES(~(ctx->dsi_lanes - 1)) |
+ /* CHB is DSI85-only, set to default on DSI83/DSI84 */
+ REG_DSI_LANE_CHB_DSI_LANES(3));
+ /* No equalization. */
+ regmap_write(ctx->regmap, REG_DSI_EQ, 0x00);
+
+ /* Set up sync signal polarity. */
+ val = (ctx->mode.flags & DRM_MODE_FLAG_NHSYNC ?
+ REG_LVDS_FMT_HS_NEG_POLARITY : 0) |
+ (ctx->mode.flags & DRM_MODE_FLAG_NVSYNC ?
+ REG_LVDS_FMT_VS_NEG_POLARITY : 0);
+
+ /* Set up bits-per-pixel, 18bpp or 24bpp. */
+ if (ctx->lvds_format_24bpp) {
+ val |= REG_LVDS_FMT_CHA_24BPP_MODE;
+ if (ctx->lvds_dual_link)
+ val |= REG_LVDS_FMT_CHB_24BPP_MODE;
+ }
+
+ /* Set up LVDS format, JEIDA/Format 1 or SPWG/Format 2 */
+ if (ctx->lvds_format_jeida) {
+ val |= REG_LVDS_FMT_CHA_24BPP_FORMAT1;
+ if (ctx->lvds_dual_link)
+ val |= REG_LVDS_FMT_CHB_24BPP_FORMAT1;
+ }
+
+ /* Set up LVDS output config (DSI84,DSI85) */
+ if (!ctx->lvds_dual_link)
+ val |= REG_LVDS_FMT_LVDS_LINK_CFG;
+
+ regmap_write(ctx->regmap, REG_LVDS_FMT, val);
+ regmap_write(ctx->regmap, REG_LVDS_VCOM, 0x05);
+ regmap_write(ctx->regmap, REG_LVDS_LANE,
+ (ctx->lvds_dual_link_even_odd_swap ?
+ REG_LVDS_LANE_EVEN_ODD_SWAP : 0) |
+ REG_LVDS_LANE_CHA_LVDS_TERM |
+ REG_LVDS_LANE_CHB_LVDS_TERM);
+ regmap_write(ctx->regmap, REG_LVDS_CM, 0x00);
+
+ val = cpu_to_le16(ctx->mode.hdisplay);
+ regmap_bulk_write(ctx->regmap, REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW,
+ &val, 2);
+ val = cpu_to_le16(ctx->mode.vdisplay);
+ regmap_bulk_write(ctx->regmap, REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW,
+ &val, 2);
+ /* 32 + 1 pixel clock to ensure proper operation */
+ val = cpu_to_le16(32 + 1);
+ regmap_bulk_write(ctx->regmap, REG_VID_CHA_SYNC_DELAY_LOW, &val, 2);
+ val = cpu_to_le16(ctx->mode.hsync_end - ctx->mode.hsync_start);
+ regmap_bulk_write(ctx->regmap, REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW,
+ &val, 2);
+ val = cpu_to_le16(ctx->mode.vsync_end - ctx->mode.vsync_start);
+ regmap_bulk_write(ctx->regmap, REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW,
+ &val, 2);
+ regmap_write(ctx->regmap, REG_VID_CHA_HORIZONTAL_BACK_PORCH,
+ ctx->mode.htotal - ctx->mode.hsync_end);
+ regmap_write(ctx->regmap, REG_VID_CHA_VERTICAL_BACK_PORCH,
+ ctx->mode.vtotal - ctx->mode.vsync_end);
+ regmap_write(ctx->regmap, REG_VID_CHA_HORIZONTAL_FRONT_PORCH,
+ ctx->mode.hsync_start - ctx->mode.hdisplay);
+ regmap_write(ctx->regmap, REG_VID_CHA_VERTICAL_FRONT_PORCH,
+ ctx->mode.vsync_start - ctx->mode.vdisplay);
+ regmap_write(ctx->regmap, REG_VID_CHA_TEST_PATTERN, 0x00);
+
+ /* Enable PLL */
+ regmap_write(ctx->regmap, REG_RC_PLL_EN, REG_RC_PLL_EN_PLL_EN);
+ usleep_range(3000, 4000);
+ ret = regmap_read_poll_timeout(ctx->regmap, REG_RC_LVDS_PLL, pval,
+ pval & REG_RC_LVDS_PLL_PLL_EN_STAT,
+ 1000, 100000);
+ if (ret) {
+ dev_err(ctx->dev, "failed to lock PLL, ret=%i\n", ret);
+ /* On failure, disable PLL again and exit. */
+ regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00);
+ return;
+ }
+
+ /* Trigger reset after CSR register update. */
+ regmap_write(ctx->regmap, REG_RC_RESET, REG_RC_RESET_SOFT_RESET);
+
+ /* Clear all errors that got asserted during initialization. */
+ regmap_read(ctx->regmap, REG_IRQ_STAT, &pval);
+ regmap_write(ctx->regmap, REG_IRQ_STAT, pval);
+}
+
+static void sn65dsi83_disable(struct drm_bridge *bridge)
+{
+ struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
+
+ /* Clear reset, disable PLL */
+ regmap_write(ctx->regmap, REG_RC_RESET, 0x00);
+ regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00);
+}
+
+static void sn65dsi83_post_disable(struct drm_bridge *bridge)
+{
+ struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
+
+ /* Put the chip in reset, pull EN line low. */
+ gpiod_set_value(ctx->enable_gpio, 0);
+}
+
+static enum drm_mode_status
+sn65dsi83_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ /* LVDS output clock range 25..154 MHz */
+ if (mode->clock < 25000)
+ return MODE_CLOCK_LOW;
+ if (mode->clock > 154000)
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static void sn65dsi83_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adj)
+{
+ struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
+
+ ctx->mode = *adj;
+}
+
+static bool sn65dsi83_mode_fixup(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adj)
+{
+ struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
+ u32 input_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ struct drm_encoder *encoder = bridge->encoder;
+ struct drm_device *ddev = encoder->dev;
+ struct drm_connector *connector;
+
+ /* The DSI format is always RGB888_1X24 */
+ list_for_each_entry(connector, &ddev->mode_config.connector_list, head) {
+ switch (connector->display_info.bus_formats[0]) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ ctx->lvds_format_24bpp = false;
+ ctx->lvds_format_jeida = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ ctx->lvds_format_24bpp = true;
+ ctx->lvds_format_jeida = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ ctx->lvds_format_24bpp = true;
+ ctx->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.
+ */
+ ctx->lvds_format_24bpp = true;
+ ctx->lvds_format_jeida = false;
+ dev_warn(ctx->dev,
+ "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n",
+ connector->display_info.bus_formats[0]);
+ break;
+ }
+
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &input_bus_format, 1);
+ }
+
+ return true;
+}
+
+static const struct drm_bridge_funcs sn65dsi83_funcs = {
+ .attach = sn65dsi83_attach,
+ .pre_enable = sn65dsi83_pre_enable,
+ .enable = sn65dsi83_enable,
+ .disable = sn65dsi83_disable,
+ .post_disable = sn65dsi83_post_disable,
+ .mode_valid = sn65dsi83_mode_valid,
+ .mode_set = sn65dsi83_mode_set,
+ .mode_fixup = sn65dsi83_mode_fixup,
+};
+
+static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
+{
+ struct drm_bridge *panel_bridge;
+ struct device *dev = ctx->dev;
+ struct device_node *endpoint;
+ struct drm_panel *panel;
+ int ret;
+
+ endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
+ ctx->dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
+ ctx->host_node = of_graph_get_remote_port_parent(endpoint);
+ of_node_put(endpoint);
+
+ if (ctx->dsi_lanes < 0 || ctx->dsi_lanes > 4)
+ return -EINVAL;
+ if (!ctx->host_node)
+ return -ENODEV;
+
+ ctx->lvds_dual_link = false;
+ ctx->lvds_dual_link_even_odd_swap = false;
+ if (model != MODEL_SN65DSI83) {
+ struct device_node *port2, *port3;
+ int dual_link;
+
+ 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 sn65dsi83_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ enum sn65dsi83_model model;
+ struct sn65dsi83 *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = dev;
+
+ if (dev->of_node) {
+ model = (enum sn65dsi83_model)(uintptr_t)
+ of_device_get_match_data(dev);
+ } else {
+ model = id->driver_data;
+ }
+
+ ctx->enable_gpio = devm_gpiod_get(ctx->dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->enable_gpio))
+ return PTR_ERR(ctx->enable_gpio);
+
+ ret = sn65dsi83_parse_dt(ctx, model);
+ if (ret)
+ return ret;
+
+ ctx->regmap = devm_regmap_init_i2c(client, &sn65dsi83_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 = &sn65dsi83_funcs;
+ ctx->bridge.of_node = dev->of_node;
+ drm_bridge_add(&ctx->bridge);
+
+ return 0;
+}
+
+static int sn65dsi83_remove(struct i2c_client *client)
+{
+ struct sn65dsi83 *ctx = i2c_get_clientdata(client);
+
+ mipi_dsi_detach(ctx->dsi);
+ mipi_dsi_device_unregister(ctx->dsi);
+ drm_bridge_remove(&ctx->bridge);
+ of_node_put(ctx->host_node);
+
+ return 0;
+}
+
+static struct i2c_device_id sn65dsi83_id[] = {
+ { "ti,sn65dsi83", MODEL_SN65DSI83 },
+ { "ti,sn65dsi84", MODEL_SN65DSI84 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, sn65dsi83_id);
+
+static const struct of_device_id sn65dsi83_match_table[] = {
+ { .compatible = "ti,sn65dsi83", .data = (void *)MODEL_SN65DSI83 },
+ { .compatible = "ti,sn65dsi84", .data = (void *)MODEL_SN65DSI84 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sn65dsi83_match_table);
+
+static struct i2c_driver sn65dsi83_driver = {
+ .probe = sn65dsi83_probe,
+ .remove = sn65dsi83_remove,
+ .id_table = sn65dsi83_id,
+ .driver = {
+ .name = "sn65dsi83",
+ .of_match_table = sn65dsi83_match_table,
+ },
+};
+module_i2c_driver(sn65dsi83_driver);
+
+MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
+MODULE_DESCRIPTION("TI SN65DSI83 DSI to LVDS bridge driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index bb0a0e1c6341..45a2969afb2b 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -1042,7 +1042,9 @@ exit:
pm_runtime_mark_last_busy(pdata->dev);
pm_runtime_put_autosuspend(pdata->dev);
- return ret ? ret : len;
+ if (ret)
+ return ret;
+ return len;
}
static int ti_sn_bridge_parse_dsi_host(struct ti_sn65dsi86 *pdata)