drm: bridge: add support for TI SN65DSI83 MIPI DSI -> LVDS bridge.
authorJosep Orga <jorga@somdevices.com>
Fri, 13 Aug 2021 10:34:01 +0000 (12:34 +0200)
committerJosep Orga <jorga@somdevices.com>
Fri, 13 Aug 2021 10:34:01 +0000 (12:34 +0200)
Based on https://github.com/karo-electronics/karo-tx-linux/commits/imx_5.10.9_1.0.0/drivers/gpu/drm/bridge/sn65dsi83
Commits 8ddea41926c08f5afdec3589f57a63d1d28b7aec and 9512d3fdb4b00b7b575e7349b3cfcdc3b20a7306.

Signed-off-by: Josep Orga <jorga@somdevices.com>
drivers/gpu/drm/bridge/Kconfig
drivers/gpu/drm/bridge/Makefile
drivers/gpu/drm/bridge/sn65dsi83/Kconfig [new file with mode: 0644]
drivers/gpu/drm/bridge/sn65dsi83/Makefile [new file with mode: 0644]
drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c [new file with mode: 0644]
drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h [new file with mode: 0644]
drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c [new file with mode: 0644]
drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h [new file with mode: 0644]

index 7e15d7c..2ad1b23 100644 (file)
@@ -266,6 +266,8 @@ source "drivers/gpu/drm/bridge/adv7511/Kconfig"
 
 source "drivers/gpu/drm/bridge/cadence/Kconfig"
 
+source "drivers/gpu/drm/bridge/sn65dsi83/Kconfig"
+
 source "drivers/gpu/drm/bridge/synopsys/Kconfig"
 
 config DRM_ITE_IT6263
index 17d70fc..0fa4733 100644 (file)
@@ -20,6 +20,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_I2C_SN65DSI83) += sn65dsi83/
 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/sn65dsi83/Kconfig b/drivers/gpu/drm/bridge/sn65dsi83/Kconfig
new file mode 100644 (file)
index 0000000..e1b8e80
--- /dev/null
@@ -0,0 +1,6 @@
+config DRM_I2C_SN65DSI83
+       bool "TI SN65DSI83 MIPI DSI to LVDS bridge"
+       depends on OF
+       select DRM_MIPI_DSI
+       help
+         TI SN65DSI83 MIPI DSI to LVDS bridge driver
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/Makefile b/drivers/gpu/drm/bridge/sn65dsi83/Makefile
new file mode 100644 (file)
index 0000000..dee7f49
--- /dev/null
@@ -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 (file)
index 0000000..211c7fc
--- /dev/null
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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/delay.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/drm_print.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 PLL_LOCK_BIT BIT(7)
+
+#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_dbg(&brg->client->dev, "%s\n", __func__);
+       gpiod_set_value_cansleep(brg->gpio_enable, 1);
+       /* Wait for 1ms for the internal voltage regulator to stabilize */
+       usleep_range(1000, 1100);
+
+       return 0;
+}
+
+static void sn65dsi83_brg_power_off(struct sn65dsi83_brg *brg)
+{
+       dev_dbg(&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
+        */
+       usleep_range(10000, 12000);
+}
+
+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_dbg(&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 inline int sn65dsi83_i2c_poll_timeout(struct i2c_client *client, u8 reg,
+                                            u64 timeout_us)
+{
+       int ret;
+       ktime_t timeout = ktime_add_us(ktime_get(), timeout_us);
+
+       for (;;) {
+               ret = sn65dsi83_read(client, reg);
+               if (ret < 0)
+                       return ret;
+               if (ret & PLL_LOCK_BIT)
+                       return 0;
+               if (timeout_us && ktime_compare(ktime_get(), timeout) > 0)
+                       return -ETIMEDOUT;
+               udelay(100);
+       }
+}
+
+static int sn65dsi83_brg_start_stream(struct sn65dsi83_brg *brg)
+{
+       int regval;
+       struct i2c_client *client = I2C_CLIENT(brg);
+
+       dev_dbg(&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) */
+       regval = sn65dsi83_i2c_poll_timeout(client, SN65DSI83_CORE_PLL, 200000);
+       if (regval < 0)
+               return regval;
+
+       /* Perform SW reset to apply changes */
+       SN65DSI83_WRITE(SN65DSI83_SOFT_RESET, 0x01);
+
+       /* Read CHA Error register */
+       regval = SN65DSI83_READ(SN65DSI83_CHA_ERR);
+       dev_dbg(&client->dev, "CHA (0x%02x) = 0x%02x", SN65DSI83_CHA_ERR,
+               regval);
+
+       if (!IS_ERR(brg->gpio_panel_enable))
+               gpiod_set_value_cansleep(brg->gpio_panel_enable, 1);
+
+       return 0;
+}
+
+static void sn65dsi83_brg_stop_stream(struct sn65dsi83_brg *brg)
+{
+       struct i2c_client *client = I2C_CLIENT(brg);
+
+       dev_dbg(&client->dev, "%s\n", __func__);
+       /* Clear the PLL_EN bit (CSR 0x0D.0) */
+       SN65DSI83_WRITE(SN65DSI83_PLL_EN, 0x00);
+
+       if (!IS_ERR(brg->gpio_panel_enable))
+               gpiod_set_value_cansleep(brg->gpio_panel_enable, 0);
+}
+
+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, "Resolution [ %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 (brg->de_neg_polarity)
+               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_dbg(&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_dbg(&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 (file)
index 0000000..659b24f
--- /dev/null
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 Markus Bauer <mb@karo-electronics.de>
+ */
+
+#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;
+       struct gpio_desc *gpio_panel_enable;
+       /* Bridge Panel Parameters */
+       struct videomode vm;
+       u32 width_mm;
+       u32 height_mm;
+       u32 format;
+       u32 bpp;
+
+       u8 num_dsi_lanes;
+       u8 burst_mode;
+       u8 de_neg_polarity;
+       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 (file)
index 0000000..e06c2dd
--- /dev/null
@@ -0,0 +1,429 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Markus Bauer <mb@karo-electronics.de>
+ */
+
+#include <linux/delay.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/drm_print.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_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_modes.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_dbg(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_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_DRIVE_NEGEDGE;
+       if (brg->vm.flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
+               *bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_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_dbg(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_dbg(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 const 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_dbg(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_dbg(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,
+                                     const struct drm_display_mode *mode,
+                                     const struct drm_display_mode *adj_mode)
+{
+       struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge);
+
+       dev_dbg(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, enum drm_bridge_attach_flags flags)
+{
+       struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge);
+       int ret;
+
+       dev_dbg(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_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;
+       u8 burst_mode = 0;
+       u8 de_neg_polarity = 0;
+       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);
+       burst_mode = of_property_read_bool(np, "ti,burst-mode");
+       de_neg_polarity = of_property_read_bool(np, "ti,de-neg-polarity");
+
+       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->burst_mode = burst_mode;
+       sn65dsi83->brg->de_neg_polarity = de_neg_polarity;
+
+       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->gpio_panel_enable =
+               devm_gpiod_get(dev, "enable-panel", GPIOD_OUT_LOW);
+       if (!IS_ERR(sn65dsi83->brg->gpio_panel_enable)) {
+               gpiod_set_value_cansleep(sn65dsi83->brg->gpio_panel_enable, 0);
+               msleep(200);
+       } else {
+               dev_warn(dev, "failed to parse enable panel gpio");
+       }
+
+       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_dbg(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;
+
+       drm_bridge_add(&sn65dsi83->bridge);
+
+       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_dbg(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;
+       if (sn65dsi83->brg->burst_mode)
+               dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST;
+       else
+               dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+
+       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_dbg(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_dbg(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", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, sn65dsi83_i2c_ids);
+
+static const struct of_device_id sn65dsi83_of_ids[] = {
+       { .compatible = "ti,sn65dsi83" },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sn65dsi83_of_ids);
+
+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)
+{
+       return i2c_add_driver(&sn65dsi83_driver);
+}
+module_init(sn65dsi83_init);
+
+static void __exit sn65dsi83_exit(void)
+{
+       i2c_del_driver(&sn65dsi83_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 (file)
index 0000000..228f0d0
--- /dev/null
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 Markus Bauer <mb@karo-electronics.de>
+ */
+
+#ifndef __SN65DSI83_TIMING_H__
+#define __SN65DSI83_TIMING_H__
+
+#include <video/display_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__ */