summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValentin Raevsky <valentin@compulab.co.il>2019-02-19 11:50:09 +0200
committerPhilippe Schenker <philippe.schenker@toradex.com>2020-02-06 15:00:36 +0100
commit8a9e44182e2b8319f6c0c8824af2d3bff1d4c381 (patch)
tree8ca00053dce2e342b2700e55954f73de99ce54a7
parent9c623983abc48e9cf9d366900c50428e119e5f63 (diff)
sn65dsi83: Add ti,sn65dsi83 dsi to lvds bridge driver
Related-to: ELB-2289 Signed-off-by: Valentin Raevsky <valentin@compulab.co.il> Signed-off-by: Philippe Schenker <philippe.schenker@toradex.com>
-rw-r--r--drivers/gpu/drm/bridge/Kconfig2
-rw-r--r--drivers/gpu/drm/bridge/Makefile1
-rw-r--r--drivers/gpu/drm/bridge/sn65dsi83/Kconfig7
-rw-r--r--drivers/gpu/drm/bridge/sn65dsi83/Makefile2
-rw-r--r--drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c388
-rw-r--r--drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h55
-rw-r--r--drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c411
-rw-r--r--drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h33
8 files changed, 899 insertions, 0 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 4ef1bc1092fb..fe295afb4692 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -135,6 +135,8 @@ source "drivers/gpu/drm/bridge/adv7511/Kconfig"
source "drivers/gpu/drm/bridge/synopsys/Kconfig"
+source "drivers/gpu/drm/bridge/sn65dsi83/Kconfig"
+
config DRM_ITE_IT6263
tristate "ITE IT6263 LVDS/HDMI bridge"
depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 8119857c7ed4..b1c48b135ab2 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -17,3 +17,4 @@ obj-$(CONFIG_DRM_ITE_IT6263) += it6263.o
obj-$(CONFIG_DRM_NWL_DSI) += nwl-dsi.o
obj-$(CONFIG_DRM_SEC_MIPI_DSIM) += sec-dsim.o
obj-$(CONFIG_DRM_NXP_SEIKO_43WVFIG) += nxp-seiko-43wvfig.o
+obj-$(CONFIG_DRM_I2C_SN65DSI83) += sn65dsi83/
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/Kconfig b/drivers/gpu/drm/bridge/sn65dsi83/Kconfig
new file mode 100644
index 000000000000..1d8f37f689d3
--- /dev/null
+++ b/drivers/gpu/drm/bridge/sn65dsi83/Kconfig
@@ -0,0 +1,7 @@
+config DRM_I2C_SN65DSI83
+ bool "SN65DSI83 mipi dsi to lvds bridge"
+ depends on OF
+ select DRM_MIPI_DSI
+ default y
+ help
+ Support for the sn65dsi83 MIPI DSI to LVDS bridge
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/Makefile b/drivers/gpu/drm/bridge/sn65dsi83/Makefile
new file mode 100644
index 000000000000..dee7f493b323
--- /dev/null
+++ b/drivers/gpu/drm/bridge/sn65dsi83/Makefile
@@ -0,0 +1,2 @@
+sn65dsi83-objs := sn65dsi83_drv.o sn65dsi83_brg.o
+obj-$(CONFIG_DRM_I2C_SN65DSI83) := sn65dsi83.o
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c
new file mode 100644
index 000000000000..f4a7713635d6
--- /dev/null
+++ b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2018 CopuLab Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/i2c.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/slab.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_connector.h>
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include "sn65dsi83_brg.h"
+
+/* Register addresses */
+
+#define SN65DSI83_SOFT_RESET 0x09
+#define SN65DSI83_CORE_PLL 0x0A
+ #define LVDS_CLK_RANGE_SHIFT 1
+ #define HS_CLK_SRC_SHIFT 0
+
+#define SN65DSI83_PLL_DIV 0x0B
+ #define DSI_CLK_DIV_SHIFT 3
+
+#define SN65DSI83_PLL_EN 0x0D
+#define SN65DSI83_DSI_CFG 0x10
+ #define CHA_DSI_LANES_SHIFT 3
+
+#define SN65DSI83_DSI_EQ 0x11
+#define SN65DSI83_CHA_DSI_CLK_RNG 0x12
+#define SN65DSI83_CHB_DSI_CLK_RNG 0x13
+#define SN65DSI83_LVDS_MODE 0x18
+ #define DE_NEG_POLARITY_SHIFT 7
+ #define HS_NEG_POLARITY_SHIFT 6
+ #define VS_NEG_POLARITY_SHIFT 5
+ #define LVDS_LINK_CFG_SHIFT 4
+ #define CHA_24BPP_MODE_SHIFT 3
+ #define CHA_24BPP_FMT1_SHIFT 1
+
+#define SN65DSI83_LVDS_SIGN 0x19
+#define SN65DSI83_LVDS_TERM 0x1A
+#define SN65DSI83_LVDS_CM_ADJ 0x1B
+#define SN65DSI83_CHA_LINE_LEN_LO 0x20
+#define SN65DSI83_CHA_LINE_LEN_HI 0x21
+#define SN65DSI83_CHB_LINE_LEN_LO 0x22
+#define SN65DSI83_CHB_LINE_LEN_HI 0x23
+#define SN65DSI83_CHA_VERT_LINES_LO 0x24
+#define SN65DSI83_CHA_VERT_LINES_HI 0x25
+#define SN65DSI83_CHB_VERT_LINES_LO 0x26
+#define SN65DSI83_CHB_VERT_LINES_HI 0x27
+#define SN65DSI83_CHA_SYNC_DELAY_LO 0x28
+#define SN65DSI83_CHA_SYNC_DELAY_HI 0x29
+#define SN65DSI83_CHB_SYNC_DELAY_LO 0x2A
+#define SN65DSI83_CHB_SYNC_DELAY_HI 0x2B
+#define SN65DSI83_CHA_HSYNC_WIDTH_LO 0x2C
+#define SN65DSI83_CHA_HSYNC_WIDTH_HI 0x2D
+#define SN65DSI83_CHB_HSYNC_WIDTH_LO 0x2E
+#define SN65DSI83_CHB_HSYNC_WIDTH_HI 0x2F
+#define SN65DSI83_CHA_VSYNC_WIDTH_LO 0x30
+#define SN65DSI83_CHA_VSYNC_WIDTH_HI 0x31
+#define SN65DSI83_CHB_VSYNC_WIDTH_LO 0x32
+#define SN65DSI83_CHB_VSYNC_WIDTH_HI 0x33
+#define SN65DSI83_CHA_HORZ_BACKPORCH 0x34
+#define SN65DSI83_CHB_HORZ_BACKPORCH 0x35
+#define SN65DSI83_CHA_VERT_BACKPORCH 0x36
+#define SN65DSI83_CHB_VERT_BACKPORCH 0x37
+#define SN65DSI83_CHA_HORZ_FRONTPORCH 0x38
+#define SN65DSI83_CHB_HORZ_FRONTPORCH 0x39
+#define SN65DSI83_CHA_VERT_FRONTPORCH 0x3A
+#define SN65DSI83_CHB_VERT_FRONTPORCH 0x3B
+#define SN65DSI83_CHA_ERR 0xE5
+#define SN65DSI83_TEST_PATTERN 0x3C
+#define SN65DSI83_REG_3D 0x3D
+#define SN65DSI83_REG_3E 0x3E
+
+static int sn65dsi83_brg_power_on(struct sn65dsi83_brg *brg)
+{
+ dev_info(&brg->client->dev,"%s\n",__func__);
+ gpiod_set_value_cansleep(brg->gpio_enable, 1);
+ /* Wait for 1ms for the internal voltage regulator to stabilize */
+ msleep(1);
+
+ return 0;
+}
+
+static void sn65dsi83_brg_power_off(struct sn65dsi83_brg *brg)
+{
+ dev_info(&brg->client->dev,"%s\n",__func__);
+ gpiod_set_value_cansleep(brg->gpio_enable, 0);
+ /*
+ * The EN pin must be held low for at least 10 ms
+ * before being asserted high
+ */
+ msleep(10);
+}
+
+static int sn65dsi83_write(struct i2c_client *client, u8 reg, u8 val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+
+ if (ret)
+ dev_err(&client->dev, "failed to write at 0x%02x", reg);
+
+ dev_dbg(&client->dev, "%s: write reg 0x%02x data 0x%02x", __func__, reg, val);
+
+ return ret;
+}
+#define SN65DSI83_WRITE(reg,val) sn65dsi83_write(client, (reg) , (val))
+
+static int sn65dsi83_read(struct i2c_client *client, u8 reg)
+{
+ int ret;
+
+ dev_info(&client->dev, "client 0x%p", client);
+ ret = i2c_smbus_read_byte_data(client, reg);
+
+ if (ret < 0) {
+ dev_err(&client->dev, "failed reading at 0x%02x", reg);
+ return ret;
+ }
+
+ dev_dbg(&client->dev, "%s: read reg 0x%02x data 0x%02x", __func__, reg, ret);
+
+ return ret;
+}
+#define SN65DSI83_READ(reg) sn65dsi83_read(client, (reg))
+
+static int sn65dsi83_brg_start_stream(struct sn65dsi83_brg *brg)
+{
+ int regval;
+ struct i2c_client *client = I2C_CLIENT(brg);
+
+ dev_info(&client->dev,"%s\n",__func__);
+ /* Set the PLL_EN bit (CSR 0x0D.0) */
+ SN65DSI83_WRITE(SN65DSI83_PLL_EN, 0x1);
+ /* Wait for the PLL_LOCK bit to be set (CSR 0x0A.7) */
+ msleep(200);
+
+ /* Perform SW reset to apply changes */
+ SN65DSI83_WRITE(SN65DSI83_SOFT_RESET, 0x01);
+
+ /* Read CHA Error register */
+ regval = SN65DSI83_READ(SN65DSI83_CHA_ERR);
+ dev_info(&client->dev, "CHA (0x%02x) = 0x%02x",
+ SN65DSI83_CHA_ERR, regval);
+
+ return 0;
+}
+
+static void sn65dsi83_brg_stop_stream(struct sn65dsi83_brg *brg)
+{
+ struct i2c_client *client = I2C_CLIENT(brg);
+ dev_info(&client->dev,"%s\n",__func__);
+ /* Clear the PLL_EN bit (CSR 0x0D.0) */
+ SN65DSI83_WRITE(SN65DSI83_PLL_EN, 0x00);
+}
+
+static int sn65dsi83_calk_clk_range(int min_regval, int max_regval,
+ unsigned long min_clk, unsigned long inc,
+ unsigned long target_clk)
+{
+ int regval = min_regval;
+ unsigned long clk = min_clk;
+
+ while (regval <= max_regval) {
+ if ((clk <= target_clk) && (target_clk < (clk + inc)))
+ return regval;
+
+ regval++;
+ clk += inc;
+ }
+
+ return -1;
+}
+
+#define ABS(X) ((X) < 0 ? (-1 * (X)) : (X))
+static int sn65dsi83_calk_div(int min_regval, int max_regval, int min_div,
+ int inc, unsigned long source_clk,
+ unsigned long target_clk)
+{
+ int regval = min_regval;
+ int div = min_div;
+ unsigned long curr_delta;
+ unsigned long prev_delta = ABS(DIV_ROUND_UP(source_clk, div) -
+ target_clk);
+
+ while (regval <= max_regval) {
+ curr_delta = ABS(DIV_ROUND_UP(source_clk, div) - target_clk);
+ if (curr_delta > prev_delta)
+ return --regval;
+
+ regval++;
+ div += inc;
+ }
+
+ return -1;
+}
+
+static int sn65dsi83_brg_configure(struct sn65dsi83_brg *brg)
+{
+ int regval = 0;
+ struct i2c_client *client = I2C_CLIENT(brg);
+ struct videomode *vm = VM(brg);
+
+ u32 dsi_clk = (((PIXCLK * BPP(brg)) / DSI_LANES(brg)) >> 1);
+
+ dev_info(&client->dev, "DSI clock [ %u ] Hz\n",dsi_clk);
+ dev_info(&client->dev, "GeoMetry [ %d x %d ] Hz\n",HACTIVE,VACTIVE);
+
+ /* Reset PLL_EN and SOFT_RESET registers */
+ SN65DSI83_WRITE(SN65DSI83_SOFT_RESET,0x00);
+ SN65DSI83_WRITE(SN65DSI83_PLL_EN,0x00);
+
+ /* LVDS clock setup */
+ if ((25000000 <= PIXCLK) && (PIXCLK < 37500000))
+ regval = 0;
+ else
+ regval = sn65dsi83_calk_clk_range(0x01, 0x05, 37500000, 25000000,
+ PIXCLK);
+
+ if (regval < 0) {
+ dev_err(&client->dev, "failed to configure LVDS clock");
+ return -EINVAL;
+ }
+
+ regval = (regval << LVDS_CLK_RANGE_SHIFT);
+ regval |= (1 << HS_CLK_SRC_SHIFT); /* Use DSI clock */
+ SN65DSI83_WRITE(SN65DSI83_CORE_PLL,regval);
+
+ /* DSI clock range */
+ regval = sn65dsi83_calk_clk_range(0x08, 0x64, 40000000, 5000000, dsi_clk);
+ if (regval < 0) {
+ dev_err(&client->dev, "failed to configure DSI clock range\n");
+ return -EINVAL;
+ }
+ SN65DSI83_WRITE(SN65DSI83_CHA_DSI_CLK_RNG,regval);
+
+ /* DSI clock divider */
+ regval = sn65dsi83_calk_div(0x0, 0x18, 1, 1, dsi_clk, PIXCLK);
+ if (regval < 0) {
+ dev_err(&client->dev, "failed to calculate DSI clock divider");
+ return -EINVAL;
+ }
+
+ regval = regval << DSI_CLK_DIV_SHIFT;
+ SN65DSI83_WRITE(SN65DSI83_PLL_DIV,regval);
+
+ /* Configure DSI_LANES */
+ regval = SN65DSI83_READ(SN65DSI83_DSI_CFG);
+ regval &= ~(3 << CHA_DSI_LANES_SHIFT);
+ regval |= ((4 - DSI_LANES(brg)) << CHA_DSI_LANES_SHIFT);
+ SN65DSI83_WRITE(SN65DSI83_DSI_CFG,regval);
+
+ /* CHA_DSI_DATA_EQ - No Equalization */
+ /* CHA_DSI_CLK_EQ - No Equalization */
+ SN65DSI83_WRITE(SN65DSI83_DSI_EQ,0x00);
+
+ /* Video formats */
+ regval = 0;
+ if (FLAGS & DISPLAY_FLAGS_HSYNC_LOW)
+ regval |= (1 << HS_NEG_POLARITY_SHIFT);
+
+ if (FLAGS & DISPLAY_FLAGS_VSYNC_LOW)
+ regval |= (1 << VS_NEG_POLARITY_SHIFT);
+
+ if (FLAGS & DISPLAY_FLAGS_DE_LOW)
+ regval |= (1 << DE_NEG_POLARITY_SHIFT);
+
+ if (BPP(brg) == 24)
+ regval |= (1 << CHA_24BPP_MODE_SHIFT);
+
+ if (FORMAT(brg) == 1)
+ regval |= (1 << CHA_24BPP_FMT1_SHIFT);
+
+ regval |= (1 << LVDS_LINK_CFG_SHIFT);
+ SN65DSI83_WRITE(SN65DSI83_LVDS_MODE,regval);
+
+ /* Voltage and pins */
+ SN65DSI83_WRITE(SN65DSI83_LVDS_SIGN,0x00);
+ SN65DSI83_WRITE(SN65DSI83_LVDS_TERM,0x03);
+ SN65DSI83_WRITE(SN65DSI83_LVDS_CM_ADJ,0x00);
+
+ /* Configure sync delay to minimal allowed value */
+ SN65DSI83_WRITE(SN65DSI83_CHA_SYNC_DELAY_LO,0x21);
+ SN65DSI83_WRITE(SN65DSI83_CHA_SYNC_DELAY_HI,0x00);
+
+ /* Geometry */
+ SN65DSI83_WRITE(SN65DSI83_CHA_LINE_LEN_LO,LOW(HACTIVE));
+ SN65DSI83_WRITE(SN65DSI83_CHA_LINE_LEN_HI,HIGH(HACTIVE));
+
+ SN65DSI83_WRITE(SN65DSI83_CHA_VERT_LINES_LO,LOW(VACTIVE));
+ SN65DSI83_WRITE(SN65DSI83_CHA_VERT_LINES_HI,HIGH(VACTIVE));
+
+ SN65DSI83_WRITE(SN65DSI83_CHA_HSYNC_WIDTH_LO,LOW(HPW));
+ SN65DSI83_WRITE(SN65DSI83_CHA_HSYNC_WIDTH_HI,HIGH(HPW));
+
+ SN65DSI83_WRITE(SN65DSI83_CHA_VSYNC_WIDTH_LO,LOW(VPW));
+ SN65DSI83_WRITE(SN65DSI83_CHA_VSYNC_WIDTH_HI,HIGH(VPW));
+
+ SN65DSI83_WRITE(SN65DSI83_CHA_HORZ_BACKPORCH,LOW(HBP));
+ SN65DSI83_WRITE(SN65DSI83_CHA_VERT_BACKPORCH,LOW(VBP));
+
+ SN65DSI83_WRITE(SN65DSI83_CHA_HORZ_FRONTPORCH,LOW(HFP));
+ SN65DSI83_WRITE(SN65DSI83_CHA_VERT_FRONTPORCH,LOW(VFP));
+
+ SN65DSI83_WRITE(SN65DSI83_TEST_PATTERN,0x00);
+ SN65DSI83_WRITE(SN65DSI83_REG_3D,0x00);
+ SN65DSI83_WRITE(SN65DSI83_REG_3E,0x00);
+
+ /* mute channel B */
+ SN65DSI83_WRITE(SN65DSI83_CHB_DSI_CLK_RNG, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_LINE_LEN_LO, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_LINE_LEN_HI, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_VERT_LINES_LO, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_VERT_LINES_HI, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_SYNC_DELAY_LO, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_SYNC_DELAY_HI, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_HSYNC_WIDTH_LO, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_HSYNC_WIDTH_HI, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_VSYNC_WIDTH_LO, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_VSYNC_WIDTH_HI, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_HORZ_BACKPORCH, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_VERT_BACKPORCH, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_HORZ_FRONTPORCH, 0x00);
+ SN65DSI83_WRITE(SN65DSI83_CHB_VERT_FRONTPORCH, 0x00);
+ return 0;
+}
+
+static int sn65dsi83_brg_setup(struct sn65dsi83_brg *brg)
+{
+ struct i2c_client *client = I2C_CLIENT(brg);
+ dev_info(&client->dev,"%s\n",__func__);
+ sn65dsi83_brg_power_on(brg);
+ return sn65dsi83_brg_configure(brg);
+}
+
+static int sn65dsi83_brg_reset(struct sn65dsi83_brg *brg)
+{
+ /* Soft Reset reg value at power on should be 0x00 */
+ struct i2c_client *client = I2C_CLIENT(brg);
+ int ret = SN65DSI83_READ(SN65DSI83_SOFT_RESET);
+ dev_info(&client->dev,"%s\n",__func__);
+ if (ret != 0x00) {
+ dev_err(&client->dev,"Failed to reset the device");
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static struct sn65dsi83_brg_funcs brg_func = {
+ .power_on = sn65dsi83_brg_power_on,
+ .power_off = sn65dsi83_brg_power_off,
+ .setup = sn65dsi83_brg_setup,
+ .reset = sn65dsi83_brg_reset,
+ .start_stream = sn65dsi83_brg_start_stream,
+ .stop_stream = sn65dsi83_brg_stop_stream,
+};
+
+static struct sn65dsi83_brg brg = {
+ .funcs = &brg_func,
+};
+
+struct sn65dsi83_brg *sn65dsi83_brg_get(void) {
+ return &brg;
+}
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h
new file mode 100644
index 000000000000..9f23df8afedc
--- /dev/null
+++ b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h
@@ -0,0 +1,55 @@
+#ifndef _SN65DSI83_BRG_H__
+#define _SN65DSI83_BRG_H__
+
+#include <linux/i2c.h>
+#include <linux/gpio/consumer.h>
+#include <video/videomode.h>
+
+struct sn65dsi83_brg;
+struct sn65dsi83_brg_funcs {
+ int (*power_on)(struct sn65dsi83_brg *sn65dsi8383_brg);
+ void (*power_off)(struct sn65dsi83_brg *sn65dsi8383_brg);
+ int (*reset)(struct sn65dsi83_brg *sn65dsi8383_brg);
+ int (*setup)(struct sn65dsi83_brg *sn65dsi8383_brg);
+ int (*start_stream)(struct sn65dsi83_brg *sn65dsi8383_brg);
+ void (*stop_stream)(struct sn65dsi83_brg *sn65dsi8383_brg);
+};
+
+struct sn65dsi83_brg {
+ struct i2c_client *client;
+ struct gpio_desc *gpio_enable;
+ /* Bridge Panel Parameters */
+ struct videomode vm;
+ u32 width_mm;
+ u32 height_mm;
+ u32 format;
+ u32 bpp;
+
+ u8 num_dsi_lanes;
+ struct sn65dsi83_brg_funcs *funcs;
+};
+struct sn65dsi83_brg *sn65dsi83_brg_get(void);
+
+#define I2C_DEVICE(A) &(A)->client->dev
+#define I2C_CLIENT(A) (A)->client
+#define VM(A) &(A)->vm
+#define BPP(A) (A)->bpp
+#define FORMAT(A) (A)->format
+#define DSI_LANES(A) (A)->num_dsi_lanes
+
+/* The caller has to have a vm structure defined */
+#define PIXCLK vm->pixelclock
+#define HACTIVE vm->hactive
+#define HFP vm->hfront_porch
+#define HBP vm->hback_porch
+#define HPW vm->hsync_len
+#define VACTIVE vm->vactive
+#define VFP vm->vfront_porch
+#define VBP vm->vback_porch
+#define VPW vm->vsync_len
+#define FLAGS vm->flags
+
+#define HIGH(A) (((A) >> 8) & 0xFF)
+#define LOW(A) ((A) & 0xFF)
+
+#endif /* _SN65DSI83_BRG_H__ */
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c
new file mode 100644
index 000000000000..d3459cfb5523
--- /dev/null
+++ b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c
@@ -0,0 +1,411 @@
+/*
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/slab.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc_helper.h>
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include "sn65dsi83_timing.h"
+#include "sn65dsi83_brg.h"
+
+struct sn65dsi83 {
+ u8 channel_id;
+ enum drm_connector_status status;
+ bool powered;
+ struct drm_display_mode curr_mode;
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+ struct device_node *host_node;
+ struct mipi_dsi_device *dsi;
+ struct sn65dsi83_brg *brg;
+};
+
+static int sn65dsi83_attach_dsi(struct sn65dsi83 *sn65dsi83);
+#define DRM_DEVICE(A) A->dev->dev
+/* Connector funcs */
+static struct sn65dsi83 *connector_to_sn65dsi83(struct drm_connector *connector)
+{
+ return container_of(connector, struct sn65dsi83, connector);
+}
+
+static int sn65dsi83_connector_get_modes(struct drm_connector *connector)
+{
+ struct sn65dsi83 *sn65dsi83 = connector_to_sn65dsi83(connector);
+ struct sn65dsi83_brg *brg = sn65dsi83->brg;
+ struct device *dev = connector->dev->dev;
+ struct drm_display_mode *mode;
+ u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ u32 *bus_flags = &connector->display_info.bus_flags;
+ int ret;
+
+ dev_info(dev, "%s\n",__func__);
+ mode = drm_mode_create(connector->dev);
+ if (!mode) {
+ DRM_DEV_ERROR(dev, "Failed to create display mode!\n");
+ return 0;
+ }
+
+ drm_display_mode_from_videomode(&brg->vm, mode);
+ mode->width_mm = brg->width_mm;
+ mode->height_mm = brg->height_mm;
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_probed_add(connector, mode);
+ drm_mode_connector_list_update(connector);
+
+ connector->display_info.width_mm = mode->width_mm;
+ connector->display_info.height_mm = mode->height_mm;
+
+ if (brg->vm.flags & DISPLAY_FLAGS_DE_HIGH)
+ *bus_flags |= DRM_BUS_FLAG_DE_HIGH;
+ if (brg->vm.flags & DISPLAY_FLAGS_DE_LOW)
+ *bus_flags |= DRM_BUS_FLAG_DE_LOW;
+ if (brg->vm.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
+ *bus_flags |= DRM_BUS_FLAG_PIXDATA_NEGEDGE;
+ if (brg->vm.flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
+ *bus_flags |= DRM_BUS_FLAG_PIXDATA_POSEDGE;
+
+ ret = drm_display_info_set_bus_formats(&connector->display_info,
+ &bus_format, 1);
+ if (ret)
+ return ret;
+
+ return 1;
+}
+
+static enum drm_mode_status
+sn65dsi83_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct sn65dsi83 *sn65dsi83 = connector_to_sn65dsi83(connector);
+ struct device *dev = connector->dev->dev;
+ if (mode->clock > ( sn65dsi83->brg->vm.pixelclock / 1000 ))
+ return MODE_CLOCK_HIGH;
+
+ dev_info(dev, "%s: mode: %d*%d@%d is valid\n",__func__,
+ mode->hdisplay,mode->vdisplay,mode->clock);
+ return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs sn65dsi83_connector_helper_funcs = {
+ .get_modes = sn65dsi83_connector_get_modes,
+ .mode_valid = sn65dsi83_connector_mode_valid,
+};
+
+static enum drm_connector_status
+sn65dsi83_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct sn65dsi83 *sn65dsi83 = connector_to_sn65dsi83(connector);
+ struct device *dev = connector->dev->dev;
+ enum drm_connector_status status;
+ dev_info(dev, "%s\n",__func__);
+
+ status = connector_status_connected;
+ sn65dsi83->status = status;
+ return status;
+}
+
+int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
+ uint32_t maxX, uint32_t maxY);
+
+static struct drm_connector_funcs sn65dsi83_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = sn65dsi83_connector_detect,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/* Bridge funcs */
+static struct sn65dsi83 *bridge_to_sn65dsi83(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct sn65dsi83, bridge);
+}
+
+static void sn65dsi83_bridge_enable(struct drm_bridge *bridge)
+{
+ struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge);
+ dev_info(DRM_DEVICE(bridge),"%s\n",__func__);
+ sn65dsi83->brg->funcs->setup(sn65dsi83->brg);
+ sn65dsi83->brg->funcs->start_stream(sn65dsi83->brg);
+}
+
+static void sn65dsi83_bridge_disable(struct drm_bridge *bridge)
+{
+ struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge);
+ dev_info(DRM_DEVICE(bridge),"%s\n",__func__);
+ sn65dsi83->brg->funcs->stop_stream(sn65dsi83->brg);
+ sn65dsi83->brg->funcs->power_off(sn65dsi83->brg);
+}
+
+static void sn65dsi83_bridge_mode_set(struct drm_bridge *bridge,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge);
+ dev_info(DRM_DEVICE(bridge), "%s: mode: %d*%d@%d\n",__func__,
+ mode->hdisplay,mode->vdisplay,mode->clock);
+ drm_mode_copy(&sn65dsi83->curr_mode, adj_mode);
+}
+
+static int sn65dsi83_bridge_attach(struct drm_bridge *bridge)
+{
+ struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge);
+ int ret;
+
+ dev_info(DRM_DEVICE(bridge),"%s\n",__func__);
+ if (!bridge->encoder) {
+ DRM_ERROR("Parent encoder object not found");
+ return -ENODEV;
+ }
+
+ sn65dsi83->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
+
+ ret = drm_connector_init(bridge->dev, &sn65dsi83->connector,
+ &sn65dsi83_connector_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+ if (ret) {
+ DRM_ERROR("Failed to initialize connector with drm\n");
+ return ret;
+ }
+ drm_connector_helper_add(&sn65dsi83->connector,
+ &sn65dsi83_connector_helper_funcs);
+ drm_mode_connector_attach_encoder(&sn65dsi83->connector, bridge->encoder);
+
+ ret = sn65dsi83_attach_dsi(sn65dsi83);
+
+ return ret;
+}
+
+static struct drm_bridge_funcs sn65dsi83_bridge_funcs = {
+ .enable = sn65dsi83_bridge_enable,
+ .disable = sn65dsi83_bridge_disable,
+ .mode_set = sn65dsi83_bridge_mode_set,
+ .attach = sn65dsi83_bridge_attach,
+};
+
+static int sn65dsi83_parse_dt(struct device_node *np,
+ struct sn65dsi83 *sn65dsi83)
+{
+ struct device *dev = &sn65dsi83->brg->client->dev;
+ u32 num_lanes = 2, bpp = 24, format = 2, width = 149, height = 93;
+ struct device_node *endpoint;
+
+ endpoint = of_graph_get_next_endpoint(np, NULL);
+ if (!endpoint)
+ return -ENODEV;
+
+ sn65dsi83->host_node = of_graph_get_remote_port_parent(endpoint);
+ if (!sn65dsi83->host_node) {
+ of_node_put(endpoint);
+ return -ENODEV;
+ }
+
+ of_property_read_u32(np, "ti,dsi-lanes", &num_lanes);
+ of_property_read_u32(np, "ti,lvds-format", &format);
+ of_property_read_u32(np, "ti,lvds-bpp", &bpp);
+ of_property_read_u32(np, "ti,width-mm", &width);
+ of_property_read_u32(np, "ti,height-mm", &height);
+
+ if (num_lanes < 1 || num_lanes > 4) {
+ dev_err(dev, "Invalid dsi-lanes: %d\n", num_lanes);
+ return -EINVAL;
+ }
+ sn65dsi83->brg->num_dsi_lanes = num_lanes;
+
+ sn65dsi83->brg->gpio_enable = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(sn65dsi83->brg->gpio_enable)) {
+ dev_err(dev, "failed to parse enable gpio");
+ return PTR_ERR(sn65dsi83->brg->gpio_enable);
+ }
+
+ sn65dsi83->brg->format = format;
+ sn65dsi83->brg->bpp = bpp;
+
+ sn65dsi83->brg->width_mm = width;
+ sn65dsi83->brg->height_mm = height;
+
+ /* Read default timing if there is not device tree node for */
+ if ((of_get_videomode(np, &sn65dsi83->brg->vm, 0)) < 0)
+ videomode_from_timing(&panel_default_timing, &sn65dsi83->brg->vm);
+
+ of_node_put(endpoint);
+ of_node_put(sn65dsi83->host_node);
+
+ return 0;
+}
+
+static int sn65dsi83_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct sn65dsi83 *sn65dsi83;
+ struct device *dev = &i2c->dev;
+ int ret;
+
+ dev_info(dev,"%s\n",__func__);
+ if (!dev->of_node)
+ return -EINVAL;
+
+ sn65dsi83 = devm_kzalloc(dev, sizeof(*sn65dsi83), GFP_KERNEL);
+ if (!sn65dsi83)
+ return -ENOMEM;
+
+ /* Initialize it before DT parser */
+ sn65dsi83->brg = sn65dsi83_brg_get();
+ sn65dsi83->brg->client = i2c;
+
+ sn65dsi83->powered = false;
+ sn65dsi83->status = connector_status_disconnected;
+
+ i2c_set_clientdata(i2c, sn65dsi83);
+
+ ret = sn65dsi83_parse_dt(dev->of_node, sn65dsi83);
+ if (ret)
+ return ret;
+
+ sn65dsi83->brg->funcs->power_off(sn65dsi83->brg);
+ sn65dsi83->brg->funcs->power_on(sn65dsi83->brg);
+ ret = sn65dsi83->brg->funcs->reset(sn65dsi83->brg);
+ if (ret != 0x00) {
+ dev_err(dev, "Failed to reset the device");
+ return -ENODEV;
+ }
+ sn65dsi83->brg->funcs->power_off(sn65dsi83->brg);
+
+
+ sn65dsi83->bridge.funcs = &sn65dsi83_bridge_funcs;
+ sn65dsi83->bridge.of_node = dev->of_node;
+
+ ret = drm_bridge_add(&sn65dsi83->bridge);
+ if (ret) {
+ dev_err(dev, "failed to add sn65dsi83 bridge\n");
+ }
+
+ return ret;
+}
+
+static int sn65dsi83_attach_dsi(struct sn65dsi83 *sn65dsi83)
+{
+ struct device *dev = &sn65dsi83->brg->client->dev;
+ struct mipi_dsi_host *host;
+ struct mipi_dsi_device *dsi;
+ int ret = 0;
+ const struct mipi_dsi_device_info info = { .type = "sn65dsi83",
+ .channel = 0,
+ .node = NULL,
+ };
+
+ dev_info(dev, "%s\n",__func__);
+ host = of_find_mipi_dsi_host_by_node(sn65dsi83->host_node);
+ if (!host) {
+ dev_err(dev, "failed to find dsi host\n");
+ return -EPROBE_DEFER;
+ }
+
+ dsi = mipi_dsi_device_register_full(host, &info);
+ if (IS_ERR(dsi)) {
+ dev_err(dev, "failed to create dsi device\n");
+ ret = PTR_ERR(dsi);
+ return -ENODEV;
+ }
+
+ sn65dsi83->dsi = dsi;
+
+ dsi->lanes = sn65dsi83->brg->num_dsi_lanes;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "failed to attach dsi to host\n");
+ mipi_dsi_device_unregister(dsi);
+ }
+
+ return ret;
+}
+
+static void sn65dsi83_detach_dsi(struct sn65dsi83 *sn65dsi83)
+{
+ struct device *dev = &sn65dsi83->brg->client->dev;
+ dev_info(dev, "%s\n",__func__);
+ mipi_dsi_detach(sn65dsi83->dsi);
+ mipi_dsi_device_unregister(sn65dsi83->dsi);
+}
+
+static int sn65dsi83_remove(struct i2c_client *i2c)
+{
+ struct sn65dsi83 *sn65dsi83 = i2c_get_clientdata(i2c);
+ struct device *dev = &sn65dsi83->brg->client->dev;
+ dev_info(dev, "%s\n",__func__);
+
+ sn65dsi83_detach_dsi(sn65dsi83);
+ drm_bridge_remove(&sn65dsi83->bridge);
+
+ return 0;
+}
+
+static const struct i2c_device_id sn65dsi83_i2c_ids[] = {
+ { "sn65dsi83", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, sn65dsi83_i2c_ids);
+
+static const struct of_device_id sn65dsi83_of_ids[] = {
+ { .compatible = "ti,sn65dsi83" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sn65dsi83_of_ids);
+
+static struct mipi_dsi_driver sn65dsi83_dsi_driver = {
+ .driver.name = "sn65dsi83",
+};
+
+static struct i2c_driver sn65dsi83_driver = {
+ .driver = {
+ .name = "sn65dsi83",
+ .of_match_table = sn65dsi83_of_ids,
+ },
+ .id_table = sn65dsi83_i2c_ids,
+ .probe = sn65dsi83_probe,
+ .remove = sn65dsi83_remove,
+};
+
+static int __init sn65dsi83_init(void)
+{
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+ mipi_dsi_driver_register(&sn65dsi83_dsi_driver);
+
+ return i2c_add_driver(&sn65dsi83_driver);
+}
+module_init(sn65dsi83_init);
+
+static void __exit sn65dsi83_exit(void)
+{
+ i2c_del_driver(&sn65dsi83_driver);
+
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+ mipi_dsi_driver_unregister(&sn65dsi83_dsi_driver);
+}
+module_exit(sn65dsi83_exit);
+
+MODULE_AUTHOR("CompuLab <compulab@compula.co.il>");
+MODULE_DESCRIPTION("SN65DSI bridge driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h
new file mode 100644
index 000000000000..e9bb6633c376
--- /dev/null
+++ b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h
@@ -0,0 +1,33 @@
+#ifndef __SN65DSI83_TIMING_H__
+#define __SN65DSI83_TIMING_H__
+
+/* Default Video Parameters */
+#define PIXCLK_INIT 62500000
+
+#define HACTIVE_INIT 1280
+#define HPW_INIT 2
+#define HBP_INIT 6
+#define HFP_INIT 5
+
+#define VACTIVE_INIT 800
+#define VPW_INIT 1
+#define VBP_INIT 2
+#define VFP_INIT 3
+
+static const struct display_timing panel_default_timing = {
+ .pixelclock = { PIXCLK_INIT, PIXCLK_INIT, PIXCLK_INIT },
+ .hactive = { HACTIVE_INIT, HACTIVE_INIT, HACTIVE_INIT },
+ .hfront_porch = { HFP_INIT, HFP_INIT, HFP_INIT },
+ .hsync_len = { HPW_INIT, HPW_INIT, HPW_INIT },
+ .hback_porch = { HBP_INIT, HBP_INIT, HBP_INIT },
+ .vactive = { VACTIVE_INIT, VACTIVE_INIT, VACTIVE_INIT },
+ .vfront_porch = { VFP_INIT, VFP_INIT, VFP_INIT },
+ .vsync_len = { VPW_INIT, VPW_INIT, VPW_INIT },
+ .vback_porch = { VBP_INIT, VBP_INIT, VBP_INIT },
+ .flags = DISPLAY_FLAGS_HSYNC_LOW |
+ DISPLAY_FLAGS_VSYNC_LOW |
+ DISPLAY_FLAGS_DE_LOW |
+ DISPLAY_FLAGS_PIXDATA_NEGEDGE,
+};
+
+#endif /* __SN65DSI83_TIMING_H__ */