All of lore.kernel.org
 help / color / mirror / Atom feed
* [[RFC]DPU PATCH 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp panel driver
@ 2018-04-18 12:19 ` Sandeep Panda
       [not found]   ` <1524054002-17869-1-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
                     ` (2 more replies)
  0 siblings, 3 replies; 21+ messages in thread
From: Sandeep Panda @ 2018-04-18 12:19 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: ryadav-sgV2jX0FEOL9JmXXK+q4OQ, Sandeep Panda,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	seanpaul-F7+t8E8rja9g9hUCZPvPmw,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw, jsanka-sgV2jX0FEOL9JmXXK+q4OQ,
	chandanu-sgV2jX0FEOL9JmXXK+q4OQ

Changelog:

v3 -> v4:
Current patchset separates out eDP panel specific resources from sn65dsi86
bridge driver and creates a separate driver for the innloux edp panel.

Now bridge driver check if any panel is attached or not to get the supported
modes. If any panel is attached then query from panel driver to get the modes,
or else probe the provided i2c adapter to read the modes from EDID.

Removed hardcoding of bridge init sequence. With this patchset bridge driver now
will program the init sequence based on the current mode set by drm framework.

Current patchset is not tested on actual bridge chip/panel.

Sandeep Panda (4):
  drm/bridge: add support for sn65dsi86 bridge driver
  dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings
  drm/panel: add Innolux TV123WAM eDP panel driver
  dt-bindings: Add Innolux TV123WAM panel bindings

 .../bindings/display/bridge/ti,sn65dsi86.txt       |  60 ++
 .../display/panel/innolux,edp-2k-panel.txt         |  21 +
 drivers/gpu/drm/bridge/ti-sn65dsi86.c              | 742 +++++++++++++++++++++
 drivers/gpu/drm/panel/panel-innolux-tv123wam.c     | 299 +++++++++
 4 files changed, 1122 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
 create mode 100644 Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
 create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
 create mode 100644 drivers/gpu/drm/panel/panel-innolux-tv123wam.c

-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver
       [not found]   ` <1524054002-17869-1-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
@ 2018-04-18 12:19     ` Sandeep Panda
  2018-04-18 14:11       ` Sean Paul
       [not found]       ` <1524054002-17869-2-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
  2018-04-18 12:20     ` [[RFC]DPU PATCH 2/4] dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings Sandeep Panda
                       ` (2 subsequent siblings)
  3 siblings, 2 replies; 21+ messages in thread
From: Sandeep Panda @ 2018-04-18 12:19 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: ryadav-sgV2jX0FEOL9JmXXK+q4OQ, Sandeep Panda,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	seanpaul-F7+t8E8rja9g9hUCZPvPmw,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw, jsanka-sgV2jX0FEOL9JmXXK+q4OQ,
	chandanu-sgV2jX0FEOL9JmXXK+q4OQ

Add support for TI's sn65dsi86 dsi2edp bridge chip.
The chip converts DSI transmitted signal to eDP signal,
which is fed to the connected eDP panel.

This chip can be controlled via either i2c interface or
dsi interface. Currently in driver all the control registers
are being accessed through i2c interface only.
Also as of now HPD support has not been added to bridge
chip driver.

Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
---
 drivers/gpu/drm/bridge/ti-sn65dsi86.c | 742 ++++++++++++++++++++++++++++++++++
 1 file changed, 742 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c

diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
new file mode 100644
index 0000000..c798f2f
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -0,0 +1,742 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/of_graph.h>
+#include <linux/regulator/consumer.h>
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+
+#define SN_BRIDGE_REVISION_ID 0x2
+
+/* Link Training specific registers */
+#define SN_DEVICE_REV_REG			0x08
+#define SN_REFCLK_FREQ_REG			0x0A
+#define SN_DSI_LANES_REG			0x10
+#define SN_DSIA_CLK_FREQ_REG			0x12
+#define SN_ENH_FRAME_REG			0x5A
+#define SN_SSC_CONFIG_REG			0x93
+#define SN_DATARATE_CONFIG_REG			0x94
+#define SN_PLL_ENABLE_REG			0x0D
+#define SN_SCRAMBLE_CONFIG_REG			0x95
+#define SN_AUX_WDATA0_REG			0x64
+#define SN_AUX_ADDR_19_16_REG			0x74
+#define SN_AUX_ADDR_15_8_REG			0x75
+#define SN_AUX_ADDR_7_0_REG			0x76
+#define SN_AUX_LENGTH_REG			0x77
+#define SN_AUX_CMD_REG				0x78
+#define SN_ML_TX_MODE_REG			0x96
+#define SN_PAGE_SELECT_REG			0xFF
+#define SN_AUX_RDATA0_REG			0x79
+/* video config specific registers */
+#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG	0x20
+#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG	0x21
+#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG	0x24
+#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG	0x25
+#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG	0x2C
+#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG	0x2D
+#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG	0x30
+#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG	0x31
+#define SN_CHA_HORIZONTAL_BACK_PORCH_REG	0x34
+#define SN_CHA_VERTICAL_BACK_PORCH_REG		0x36
+#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG	0x38
+#define SN_CHA_VERTICAL_FRONT_PORCH_REG		0x3A
+#define SN_DATA_FORMAT_REG			0x5B
+#define SN_COLOR_BAR_CONFIG_REG			0x3C
+
+#define DPPLL_CLK_SRC_REFCLK	0
+#define DPPLL_CLK_SRC_DSICLK	1
+#define MIN_DSI_CLK_FREQ_MHZ	40
+
+enum ti_sn_bridge_ref_clks {
+	SN_REF_CLK_12_MHZ = 0,
+	SN_REF_CLK_19_2_MHZ,
+	SN_REF_CLK_26_MHZ,
+	SN_REF_CLK_27_MHZ,
+	SN_REF_CLK_38_4_MHZ,
+	SN_REF_CLK_MAX,
+};
+
+struct ti_sn_bridge {
+	struct device *dev;
+	struct drm_bridge bridge;
+	struct drm_connector connector;
+	struct device_node *host_node;
+	struct mipi_dsi_device *dsi;
+	/* handle to the connected eDP panel */
+	struct drm_panel *panel;
+	int irq;
+	struct gpio_desc *irq_gpio;
+	/* list of gpios needed to enable the bridge functionality */
+	struct gpio_descs *enable_gpios;
+	unsigned int num_supplies;
+	struct regulator_bulk_data *supplies;
+	struct i2c_client *i2c_client;
+	struct i2c_adapter *ddc;
+	bool power_on;
+	u32 num_modes;
+	struct drm_display_mode curr_mode;
+};
+
+static int ti_sn_bridge_write(struct ti_sn_bridge *pdata, u8 reg, u8 val)
+{
+	struct i2c_client *client = pdata->i2c_client;
+	u8 buf[2] = {reg, val};
+	struct i2c_msg msg = {
+		.addr = client->addr,
+		.flags = 0,
+		.len = 2,
+		.buf = buf,
+	};
+
+	if (i2c_transfer(client->adapter, &msg, 1) < 1) {
+		DRM_ERROR("i2c write failed\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int ti_sn_bridge_read(struct ti_sn_bridge *pdata,
+					u8 reg, char *buf, u32 size)
+{
+	struct i2c_client *client = pdata->i2c_client;
+	struct i2c_msg msg[2] = {
+		{
+			.addr = client->addr,
+			.flags = 0,
+			.len = 1,
+			.buf = &reg,
+		},
+		{
+			.addr = client->addr,
+			.flags = I2C_M_RD,
+			.len = size,
+			.buf = buf,
+		}
+	};
+
+	if (i2c_transfer(client->adapter, msg, 2) != 2) {
+		DRM_ERROR("i2c read failed\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void ti_sn_bridge_gpio_configure(struct ti_sn_bridge *pdata, bool on)
+{
+	int value, i = 0, num = 0;
+	int *value_array = NULL;
+
+	if (!pdata)
+		return;
+
+	value = on ? 1 : 0;
+	num = pdata->enable_gpios->ndescs;
+	value_array = kmalloc(num * sizeof(int), GFP_KERNEL);
+	if (value_array) {
+		for (i = 0; i < num; i++)
+			value_array[i] = value;
+		gpiod_set_array_value(num, pdata->enable_gpios->desc,
+				      value_array);
+		kfree(value_array);
+	}
+
+	if (pdata->irq_gpio)
+		gpiod_set_value(pdata->irq_gpio, value);
+
+	DRM_DEBUG("config to: %d\n", value);
+}
+
+static void ti_sn_bridge_power_ctrl(struct ti_sn_bridge *pdata, bool enable)
+{
+	static int ref_count;
+
+	if (!pdata)
+		return;
+
+	if (enable)
+		ref_count++;
+	else
+		ref_count--;
+
+	if (enable && (ref_count == 1)) {
+		ti_sn_bridge_gpio_configure(pdata, true);
+
+		if (regulator_bulk_enable(pdata->num_supplies,
+						pdata->supplies)) {
+			DRM_ERROR("bridge regulator enable failed\n");
+			return;
+		}
+		pdata->power_on = true;
+	} else if (!enable && !ref_count) {
+		regulator_bulk_disable(pdata->num_supplies, pdata->supplies);
+
+		ti_sn_bridge_gpio_configure(pdata, false);
+		pdata->power_on = false;
+	} else {
+		DRM_DEBUG("ti_sn_bridge power ctrl: %d refcount: %d\n",
+							enable, ref_count);
+	}
+}
+
+/* Connector funcs */
+static struct ti_sn_bridge *
+connector_to_ti_sn_bridge(struct drm_connector *connector)
+{
+	return container_of(connector, struct ti_sn_bridge, connector);
+}
+
+static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
+{
+	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
+	struct drm_panel *panel = pdata->panel;
+	struct edid *edid;
+
+	if (panel) {
+		DRM_DEBUG("get mode from connected drm_panel\n");
+		pdata->num_modes = drm_panel_get_modes(panel);
+	} else {
+		/* get from EDID */
+		if (!pdata->ddc)
+			return 0;
+		ti_sn_bridge_power_ctrl(pdata, true);
+		edid = drm_get_edid(connector, pdata->ddc);
+		if (edid) {
+			drm_mode_connector_update_edid_property(connector,
+								edid);
+			pdata->num_modes = drm_add_edid_modes(connector, edid);
+			drm_edid_to_eld(connector, edid);
+			kfree(edid);
+		} else {
+			DRM_DEBUG("failed to get edid\n");
+		}
+		ti_sn_bridge_power_ctrl(pdata, false);
+	}
+
+	return pdata->num_modes;
+}
+
+static enum drm_mode_status
+ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
+			     struct drm_display_mode *mode)
+{
+	/* maximum supported resolution is 4K at 60 fps */
+	if (mode->clock > 594000)
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs ti_sn_bridge_connector_helper_funcs = {
+	.get_modes = ti_sn_bridge_connector_get_modes,
+	.mode_valid = ti_sn_bridge_connector_mode_valid,
+};
+
+static enum drm_connector_status
+ti_sn_bridge_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
+
+	/**
+	 * TODO: Currently if drm_panel is present, then always
+	 * return the status as connected. Need to add support to detect
+	 * device state for no panel(hot pluggable) scenarios.
+	 */
+	if (pdata->panel)
+		return connector_status_connected;
+	else
+		return connector_status_disconnected;
+}
+
+static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = ti_sn_bridge_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,
+};
+
+static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct ti_sn_bridge, bridge);
+}
+
+static int ti_sn_bridge_read_device_rev(struct ti_sn_bridge *pdata)
+{
+	u8 rev = 0;
+	int ret = 0;
+
+	ret = ti_sn_bridge_read(pdata, SN_DEVICE_REV_REG, &rev, 1);
+	if (ret)
+		goto exit;
+
+	if (rev == SN_BRIDGE_REVISION_ID) {
+		DRM_DEBUG("ti_sn_bridge revision id: 0x%x\n", rev);
+	} else {
+		DRM_ERROR("ti_sn_bridge revision id mismatch\n");
+		ret = -EINVAL;
+	}
+
+exit:
+	return ret;
+}
+
+static const char * const ti_sn_bridge_supply_names[] = {
+	"vccio",
+	"vcca",
+};
+
+static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
+{
+	unsigned int i;
+	int ret;
+
+	pdata->num_supplies = ARRAY_SIZE(ti_sn_bridge_supply_names);
+
+	pdata->supplies = devm_kcalloc(pdata->dev, pdata->num_supplies,
+				     sizeof(*pdata->supplies), GFP_KERNEL);
+	if (!pdata->supplies)
+		return -ENOMEM;
+
+	for (i = 0; i < pdata->num_supplies; i++)
+		pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
+
+	ret = devm_regulator_bulk_get(pdata->dev,
+			pdata->num_supplies, pdata->supplies);
+
+	return ret;
+}
+
+static void ti_sn_bridge_attach_panel(struct ti_sn_bridge *pdata)
+{
+	struct device_node *panel_node, *port, *endpoint;
+	struct drm_panel *panel = NULL;
+
+	port = of_graph_get_port_by_id(pdata->dev->of_node, 1);
+	if (port) {
+		endpoint = of_get_child_by_name(port, "endpoint");
+		of_node_put(port);
+		if (!endpoint) {
+			dev_err(pdata->dev, "no output endpoint found\n");
+			goto error;
+		}
+
+		panel_node = of_graph_get_remote_port_parent(endpoint);
+		of_node_put(endpoint);
+		if (!panel_node) {
+			dev_err(pdata->dev, "no output node found\n");
+			goto error;
+		}
+
+		panel = of_drm_find_panel(panel_node);
+		of_node_put(panel_node);
+		if (!panel) {
+			dev_err(pdata->dev, "no panel node found\n");
+			goto error;
+		}
+	}
+
+	pdata->panel = panel;
+	drm_panel_attach(panel, &pdata->connector);
+
+	return;
+
+error:
+	pdata->panel = NULL;
+}
+
+static int ti_sn_bridge_attach(struct drm_bridge *bridge)
+{
+	struct mipi_dsi_host *host;
+	struct mipi_dsi_device *dsi;
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	int ret;
+	const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
+						   .channel = 0,
+						   .node = NULL,
+						 };
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Parent encoder object not found");
+		return -ENODEV;
+	}
+
+	/* HPD not supported */
+	pdata->connector.polled = 0;
+
+	ret = drm_connector_init(bridge->dev, &pdata->connector,
+				 &ti_sn_bridge_connector_funcs,
+				 DRM_MODE_CONNECTOR_eDP);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector with drm\n");
+		return ret;
+	}
+
+	drm_connector_helper_add(&pdata->connector,
+				 &ti_sn_bridge_connector_helper_funcs);
+	drm_mode_connector_attach_encoder(&pdata->connector, bridge->encoder);
+
+	host = of_find_mipi_dsi_host_by_node(pdata->host_node);
+	if (!host) {
+		DRM_ERROR("failed to find dsi host\n");
+		return -ENODEV;
+	}
+
+	dsi = mipi_dsi_device_register_full(host, &info);
+	if (IS_ERR(dsi)) {
+		DRM_ERROR("failed to create dsi device\n");
+		ret = PTR_ERR(dsi);
+		goto err_dsi_device;
+	}
+
+	/* TODO: setting to 4 lanes always for now */
+	dsi->lanes = 4;
+	dsi->format = MIPI_DSI_FMT_RGB888;
+	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+			  MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
+
+	ret = mipi_dsi_attach(dsi);
+	if (ret < 0) {
+		DRM_ERROR("failed to attach dsi to host\n");
+		goto err_dsi_attach;
+	}
+
+	pdata->dsi = dsi;
+
+	DRM_DEBUG("bridge attached\n");
+	/* attach panel to bridge */
+	ti_sn_bridge_attach_panel(pdata);
+
+	return 0;
+
+err_dsi_attach:
+	mipi_dsi_device_unregister(dsi);
+err_dsi_device:
+	return ret;
+}
+
+static void ti_sn_bridge_mode_set(struct drm_bridge *bridge,
+				    struct drm_display_mode *mode,
+				    struct drm_display_mode *adj_mode)
+{
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+
+	DRM_DEBUG("mode_set: hdisplay=%d, vdisplay=%d, vrefresh=%d, clock=%d\n",
+		adj_mode->hdisplay, adj_mode->vdisplay,
+		adj_mode->vrefresh, adj_mode->clock);
+
+	drm_mode_copy(&pdata->curr_mode, adj_mode);
+}
+
+static void ti_sn_bridge_disable(struct drm_bridge *bridge)
+{
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	struct drm_panel *panel = pdata->panel;
+
+	if (panel) {
+		drm_panel_disable(panel);
+		drm_panel_unprepare(panel);
+	}
+
+	ti_sn_bridge_power_ctrl(pdata, false);
+}
+
+static void ti_sn_bridge_enable(struct drm_bridge *bridge)
+{
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	struct drm_panel *panel = pdata->panel;
+	struct drm_display_mode *mode = &pdata->curr_mode;
+	u32 bit_rate_mhz, clk_freq_mhz;
+	u8 val = 0;
+
+	if (!pdata->num_modes)
+		return;
+
+	ti_sn_bridge_power_ctrl(pdata, true);
+
+	if (panel)
+		drm_panel_prepare(panel);
+
+	/* CLK_SRC config */
+	ti_sn_bridge_write(pdata, SN_REFCLK_FREQ_REG,
+			(DPPLL_CLK_SRC_REFCLK | (SN_REF_CLK_19_2_MHZ << 0x1)));
+
+	/* DSI_A lane config */
+	ti_sn_bridge_read(pdata, SN_DSI_LANES_REG, &val, 0x1);
+	val |= ((0x4 - pdata->dsi->lanes) & 0x03) << 0x3;
+	ti_sn_bridge_write(pdata, SN_DSI_LANES_REG, val);
+
+	/* DP lane config */
+	ti_sn_bridge_read(pdata, SN_SSC_CONFIG_REG, &val, 0x1);
+	val |= ((pdata->dsi->lanes - 0x1) & 0x03) << 0x4;
+	ti_sn_bridge_write(pdata, SN_SSC_CONFIG_REG, val);
+
+	/* set dsi clk frequency value */
+	bit_rate_mhz = (mode->clock / 1000) *
+			mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
+	clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
+	/* for each increment in val, frequency increases by 5MHz */
+	val = (MIN_DSI_CLK_FREQ_MHZ / 0x5) +
+		(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 0x5) & 0xFF);
+	ti_sn_bridge_write(pdata, SN_DSIA_CLK_FREQ_REG, val);
+
+	ti_sn_bridge_write(pdata, SN_DATARATE_CONFIG_REG, 0x80); /* set HBR */
+
+	ti_sn_bridge_write(pdata, SN_PLL_ENABLE_REG, 0x1); /* Enable PLL */
+	usleep_range(10000, 10001);
+
+	/* DPCD Register 0x0010A in Sink */
+	ti_sn_bridge_write(pdata, SN_AUX_WDATA0_REG, 0x01);
+	ti_sn_bridge_write(pdata, SN_AUX_ADDR_19_16_REG, 0x00);
+	ti_sn_bridge_write(pdata, SN_AUX_ADDR_15_8_REG, 0x01);
+	ti_sn_bridge_write(pdata, SN_AUX_ADDR_7_0_REG, 0x0A);
+	ti_sn_bridge_write(pdata, SN_AUX_LENGTH_REG, 0x01);
+	ti_sn_bridge_write(pdata, SN_AUX_CMD_REG, 0x81);
+	usleep_range(10000, 10001);
+
+	/* Semi auto link training mode */
+	ti_sn_bridge_write(pdata, SN_ML_TX_MODE_REG, 0x0A);
+	usleep_range(20000, 20001);
+
+	/* config video parameters */
+	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
+			mode->hdisplay & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG,
+			(mode->hdisplay >> 0x8) & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
+			mode->vdisplay & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG,
+			(mode->hdisplay >> 0x8) & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
+			(mode->htotal - mode->hsync_end) & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
+			((mode->htotal - mode->hsync_end) >> 0x8) & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
+			(mode->vtotal - mode->vsync_end) & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
+			((mode->vtotal - mode->vsync_end) >> 0x8) & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
+			(mode->hsync_start - mode->hdisplay) & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_BACK_PORCH_REG,
+			(mode->vsync_start - mode->vdisplay) & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
+			(mode->hsync_end - mode->hsync_start) & 0xFF);
+	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_FRONT_PORCH_REG,
+			(mode->vsync_end - mode->vsync_start) & 0xFF);
+	if (pdata->dsi->format == MIPI_DSI_FMT_RGB888)
+		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x00);
+	else
+		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x01);
+	usleep_range(10000, 10001);
+
+	/* enable video stream */
+	ti_sn_bridge_read(pdata, SN_ENH_FRAME_REG, &val, 0x1);
+	val |= BIT(3);
+	ti_sn_bridge_write(pdata, SN_ENH_FRAME_REG, val);
+
+	if (panel)
+		drm_panel_enable(panel);
+}
+
+static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
+	.attach = ti_sn_bridge_attach,
+	.enable = ti_sn_bridge_enable,
+	.disable = ti_sn_bridge_disable,
+	.mode_set = ti_sn_bridge_mode_set,
+};
+
+static int ti_sn_bridge_parse_gpios(struct ti_sn_bridge *pdata)
+{
+	int ret = 0;
+
+	if (!pdata || !pdata->dev)
+		return -EINVAL;
+
+	pdata->enable_gpios = devm_gpiod_get_array(pdata->dev,
+						"enable", GPIOD_OUT_LOW);
+	if (IS_ERR(pdata->enable_gpios)) {
+		DRM_ERROR("failed to get enable gpio from DT\n");
+		ret = PTR_ERR(pdata->enable_gpios);
+		goto exit;
+	}
+
+	pdata->irq_gpio = devm_gpiod_get_optional(pdata->dev, "irq",
+						  GPIOD_OUT_LOW);
+	if (IS_ERR(pdata->irq_gpio))
+		DRM_ERROR("failed to get irq gpio from DT\n");
+
+exit:
+	return ret;
+}
+
+static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
+{
+	struct device_node *np = pdata->dev->of_node;
+	struct device_node *end_node;
+
+	end_node = of_graph_get_endpoint_by_regs(np, 0, 0);
+	if (!end_node) {
+		DRM_ERROR("remote endpoint not found\n");
+		return -ENODEV;
+	}
+
+	pdata->host_node = of_graph_get_remote_port_parent(end_node);
+	of_node_put(end_node);
+	if (!pdata->host_node) {
+		DRM_ERROR("remote node not found\n");
+		return -ENODEV;
+	}
+	of_node_put(pdata->host_node);
+
+	return 0;
+}
+
+static int ti_sn_bridge_probe(struct i2c_client *client,
+	 const struct i2c_device_id *id)
+{
+	struct ti_sn_bridge *pdata;
+	struct device_node *ddc_node;
+	int ret = 0;
+
+	if (!client || !client->dev.of_node) {
+		DRM_ERROR("invalid input\n");
+		return -EINVAL;
+	}
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		DRM_ERROR("device doesn't support I2C\n");
+		return -ENODEV;
+	}
+
+	pdata = devm_kzalloc(&client->dev,
+		sizeof(struct ti_sn_bridge), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->power_on = false;
+	pdata->dev = &client->dev;
+	pdata->i2c_client = client;
+	DRM_DEBUG("I2C address is %x\n", client->addr);
+
+	ret = ti_sn_bridge_parse_dsi_host(pdata);
+	if (ret) {
+		DRM_ERROR("failed to parse device tree\n");
+		goto err_dt_parse;
+	}
+
+	ret = ti_sn_bridge_parse_gpios(pdata);
+	if (ret) {
+		DRM_ERROR("failed to parse gpios from DT\n");
+		goto err_dt_parse;
+	}
+
+	ret = ti_sn_bridge_parse_regulators(pdata);
+	if (ret) {
+		DRM_ERROR("failed to parse regulators\n");
+		goto err_dt_parse;
+	}
+
+	ddc_node = of_parse_phandle(pdata->dev->of_node, "ddc-i2c-bus", 0);
+	if (ddc_node) {
+		pdata->ddc = of_find_i2c_adapter_by_node(ddc_node);
+		of_node_put(ddc_node);
+		if (!pdata->ddc) {
+			dev_dbg(pdata->dev, "failed to read ddc node\n");
+			return -EPROBE_DEFER;
+		}
+	} else {
+		dev_dbg(pdata->dev, "no ddc property found\n");
+	}
+
+	ti_sn_bridge_power_ctrl(pdata, true);
+	ret = ti_sn_bridge_read_device_rev(pdata);
+	if (ret) {
+		DRM_ERROR("failed to read chip rev\n");
+		goto err_config;
+	} else {
+		DRM_DEBUG("bridge chip enabled successfully\n");
+	}
+	ti_sn_bridge_power_ctrl(pdata, false);
+
+	i2c_set_clientdata(client, pdata);
+	dev_set_drvdata(&client->dev, pdata);
+
+	pdata->bridge.funcs = &ti_sn_bridge_funcs;
+	pdata->bridge.of_node = client->dev.of_node;
+
+	drm_bridge_add(&pdata->bridge);
+
+	return ret;
+
+err_config:
+	ti_sn_bridge_power_ctrl(pdata, false);
+err_dt_parse:
+	devm_kfree(&client->dev, pdata);
+	return ret;
+}
+
+static int ti_sn_bridge_remove(struct i2c_client *client)
+{
+	int ret = -EINVAL;
+	struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
+
+	if (!pdata)
+		goto end;
+
+	mipi_dsi_detach(pdata->dsi);
+	mipi_dsi_device_unregister(pdata->dsi);
+
+	drm_bridge_remove(&pdata->bridge);
+
+	disable_irq(pdata->irq);
+	free_irq(pdata->irq, pdata);
+
+	ti_sn_bridge_gpio_configure(pdata, false);
+	i2c_put_adapter(pdata->ddc);
+
+	devm_kfree(&client->dev, pdata);
+
+end:
+	return ret;
+}
+
+static struct i2c_device_id ti_sn_bridge_id[] = {
+	{ "ti,sn65dsi86", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
+
+static const struct of_device_id ti_sn_bridge_match_table[] = {
+	{.compatible = "ti,sn65dsi86"},
+	{}
+};
+MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
+
+static struct i2c_driver ti_sn_bridge_driver = {
+	.driver = {
+		.name = "ti_sn65dsi86",
+		.owner = THIS_MODULE,
+		.of_match_table = ti_sn_bridge_match_table,
+	},
+	.probe = ti_sn_bridge_probe,
+	.remove = ti_sn_bridge_remove,
+	.id_table = ti_sn_bridge_id,
+};
+
+module_i2c_driver(ti_sn_bridge_driver);
+MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [[RFC]DPU PATCH 2/4] dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings
       [not found]   ` <1524054002-17869-1-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
  2018-04-18 12:19     ` [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver Sandeep Panda
@ 2018-04-18 12:20     ` Sandeep Panda
  2018-04-18 13:58       ` Sean Paul
  2018-04-18 12:20     ` [[RFC]DPU PATCH 3/4] drm/panel: add Innolux TV123WAM eDP panel driver Sandeep Panda
  2018-04-18 12:20     ` [[RFC]DPU PATCH 4/4] dt-bindings: Add Innolux TV123WAM panel bindings Sandeep Panda
  3 siblings, 1 reply; 21+ messages in thread
From: Sandeep Panda @ 2018-04-18 12:20 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: ryadav-sgV2jX0FEOL9JmXXK+q4OQ, Sandeep Panda,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	seanpaul-F7+t8E8rja9g9hUCZPvPmw,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw, jsanka-sgV2jX0FEOL9JmXXK+q4OQ,
	chandanu-sgV2jX0FEOL9JmXXK+q4OQ

Document the bindings used for the sn65dsi86 DSI to eDP bridge.

Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
---
 .../bindings/display/bridge/ti,sn65dsi86.txt       | 60 ++++++++++++++++++++++
 1 file changed, 60 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt

diff --git a/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt b/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
new file mode 100644
index 0000000..9e2612e
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
@@ -0,0 +1,60 @@
+SN65DSI86 DSI to eDP bridge chip
+--------------------------------
+
+This is the binding for Texas Instruments SN65DSI86 bridge.
+
+Required properties:
+- compatible: Must be "ti,sn65dsi86"
+- reg: i2c address of the chip, 0x2d as per datasheet
+- enable-gpios: OF device-tree gpio specifications for bridge_en pin
+
+- vccio-supply: A 1.8V supply that powers up the digital IOs.
+- vcca-supply: A 1.2V supply that powers up the analog circuits.
+
+Optional properties:
+
+- irq-gpios: OF device-tree gpio specification for interrupt pin
+
+Required nodes:
+
+This device has two video ports. Their connections are modelled using the
+OF graph bindings specified in Documentation/devicetree/bindings/graph.txt.
+
+- Video port 0 for DSI input
+- Video port 1 for eDP output
+
+Example
+-------
+
+edp-bridge@2d {
+	compatible = "ti,sn65dsi86";
+	#address-cells = <1>;
+	#size-cells = <0>;
+	reg = <0x2d>;
+
+	enable-gpios = <&msmgpio 33 GPIO_ACTIVE_HIGH>;
+
+	vccio-supply = <&pm8916_l17>;
+	vcca-supply = <&pm8916_l6>;
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@0 {
+			reg = <0>;
+
+			edp_bridge_in: endpoint {
+				remote-endpoint = <&dsi_out>;
+			};
+		};
+
+		port@1 {
+			reg = <1>;
+
+			edp_bridge_out: endpoint {
+				remote-endpoint = <&edp_panel_in>;
+			};
+		};
+	};
+}
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [[RFC]DPU PATCH 3/4] drm/panel: add Innolux TV123WAM eDP panel driver
       [not found]   ` <1524054002-17869-1-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
  2018-04-18 12:19     ` [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver Sandeep Panda
  2018-04-18 12:20     ` [[RFC]DPU PATCH 2/4] dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings Sandeep Panda
@ 2018-04-18 12:20     ` Sandeep Panda
  2018-04-18 12:20     ` [[RFC]DPU PATCH 4/4] dt-bindings: Add Innolux TV123WAM panel bindings Sandeep Panda
  3 siblings, 0 replies; 21+ messages in thread
From: Sandeep Panda @ 2018-04-18 12:20 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: ryadav-sgV2jX0FEOL9JmXXK+q4OQ, Sandeep Panda,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	seanpaul-F7+t8E8rja9g9hUCZPvPmw,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw, jsanka-sgV2jX0FEOL9JmXXK+q4OQ,
	chandanu-sgV2jX0FEOL9JmXXK+q4OQ

Add support for Innolux TV123WAM 12.3" panel which
is an eDP panel.

Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
---
 drivers/gpu/drm/panel/panel-innolux-tv123wam.c | 299 +++++++++++++++++++++++++
 1 file changed, 299 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-innolux-tv123wam.c

diff --git a/drivers/gpu/drm/panel/panel-innolux-tv123wam.c b/drivers/gpu/drm/panel/panel-innolux-tv123wam.c
new file mode 100644
index 0000000..f149fa9
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-innolux-tv123wam.c
@@ -0,0 +1,299 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <drm/drmP.h>
+#include <drm/drm_panel.h>
+#include <video/display_timing.h>
+#include <video/videomode.h>
+
+struct panel_edp_2k_desc {
+	const struct drm_display_mode *modes;
+	unsigned int num_modes;
+	struct {
+		unsigned int width;
+		unsigned int height;
+	} size;
+	u32 bpc;
+	u32 bus_flags;
+};
+
+struct panel_edp_2k_data {
+	struct drm_panel base;
+	bool enabled;
+	bool prepared;
+
+	const struct panel_edp_2k_desc *desc;
+
+	struct regulator *supply;
+	struct gpio_desc *enable_gpio;
+	struct backlight_device *backlight;
+};
+
+static const struct drm_display_mode innolux_edp_2k_mode = {
+	.clock = 206016,
+	.hdisplay = 2160,
+	.hsync_start = 2160 + 80,
+	.hsync_end = 2160 + 80 + 48,
+	.htotal = 2160 + 80 + 48 + 32,
+	.vdisplay = 1440,
+	.vsync_start = 1440 + 27,
+	.vsync_end = 1440 + 27 + 3,
+	.vtotal = 1440 + 27 + 3 + 10,
+	.vrefresh = 60,
+	.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
+};
+
+static const struct panel_edp_2k_desc innolux_edp_2k = {
+	.modes = &innolux_edp_2k_mode,
+	.num_modes = 1,
+	.size = {
+		.width = 223,
+		.height = 125,
+	},
+	.bpc = 8,
+	.bus_flags = DRM_BUS_FLAG_PIXDATA_POSEDGE,
+};
+
+static const struct of_device_id platform_of_match[] = {
+	{
+		.compatible = "innolux, edp_2k_panel",
+		.data = &innolux_edp_2k,
+	},
+};
+
+static inline struct panel_edp_2k_data *
+to_panel_edp_2k_data(struct drm_panel *panel)
+{
+	return container_of(panel, struct panel_edp_2k_data, base);
+}
+
+static int panel_edp_2k_disable(struct drm_panel *panel)
+{
+	struct panel_edp_2k_data *pdata = to_panel_edp_2k_data(panel);
+
+	if (!pdata->enabled)
+		return 0;
+
+	if (pdata->backlight) {
+		pdata->backlight->props.power = FB_BLANK_POWERDOWN;
+		pdata->backlight->props.state |= BL_CORE_FBBLANK;
+		backlight_update_status(pdata->backlight);
+	}
+
+	pdata->enabled = false;
+
+	return 0;
+}
+
+static int panel_edp_2k_unprepare(struct drm_panel *panel)
+{
+	struct panel_edp_2k_data *pdata = to_panel_edp_2k_data(panel);
+
+	if (!pdata->prepared)
+		return 0;
+
+	if (pdata->enable_gpio)
+		gpiod_set_value_cansleep(pdata->enable_gpio, 0);
+
+	regulator_disable(pdata->supply);
+
+	pdata->prepared = false;
+
+	return 0;
+}
+
+static int panel_edp_2k_prepare(struct drm_panel *panel)
+{
+	struct panel_edp_2k_data *pdata = to_panel_edp_2k_data(panel);
+	int ret;
+
+	if (pdata->prepared)
+		return 0;
+
+	ret = regulator_enable(pdata->supply);
+	if (ret < 0) {
+		dev_err(panel->dev, "failed to enable supply: %d\n", ret);
+		return ret;
+	}
+
+	if (pdata->enable_gpio)
+		gpiod_set_value_cansleep(pdata->enable_gpio, 1);
+
+	pdata->prepared = true;
+
+	return 0;
+}
+
+static int panel_edp_2k_enable(struct drm_panel *panel)
+{
+	struct panel_edp_2k_data *pdata = to_panel_edp_2k_data(panel);
+
+	if (pdata->enabled)
+		return 0;
+
+	if (pdata->backlight) {
+		pdata->backlight->props.state &= ~BL_CORE_FBBLANK;
+		pdata->backlight->props.power = FB_BLANK_UNBLANK;
+		backlight_update_status(pdata->backlight);
+	}
+
+	pdata->enabled = true;
+
+	return 0;
+}
+
+static int panel_edp_2k_get_modes(struct drm_panel *panel)
+{
+	struct panel_edp_2k_data *pdata = to_panel_edp_2k_data(panel);
+	struct drm_connector *connector = pdata->base.connector;
+	struct drm_device *drm = pdata->base.drm;
+	struct drm_display_mode *mode;
+	unsigned int i, num = 0;
+
+	if (!pdata->desc || !connector || !drm)
+		return 0;
+
+	for (i = 0; i < pdata->desc->num_modes; i++) {
+		const struct drm_display_mode *m = &pdata->desc->modes[i];
+
+		mode = drm_mode_duplicate(drm, m);
+		if (!mode) {
+			dev_err(drm->dev, "failed to add mode %ux%u@%u\n",
+				m->hdisplay, m->vdisplay, m->vrefresh);
+			continue;
+		}
+
+		mode->type |= DRM_MODE_TYPE_DRIVER;
+
+		if (pdata->desc->num_modes == 1)
+			mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+		drm_mode_set_name(mode);
+
+		drm_mode_probed_add(connector, mode);
+		num++;
+	}
+
+	connector->display_info.bpc = pdata->desc->bpc;
+	connector->display_info.width_mm = pdata->desc->size.width;
+	connector->display_info.height_mm = pdata->desc->size.height;
+	connector->display_info.bus_flags = pdata->desc->bus_flags;
+
+	return num;
+}
+
+static const struct drm_panel_funcs panel_edp_2k_funcs = {
+	.disable = panel_edp_2k_disable,
+	.unprepare = panel_edp_2k_unprepare,
+	.prepare = panel_edp_2k_prepare,
+	.enable = panel_edp_2k_enable,
+	.get_modes = panel_edp_2k_get_modes,
+};
+
+static int panel_edp_2k_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct of_device_id *id;
+	struct device_node *backlight;
+	struct panel_edp_2k_data *pdata;
+	int ret;
+
+	id = of_match_node(platform_of_match, pdev->dev.of_node);
+	if (!id)
+		return -ENODEV;
+
+	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->enabled = false;
+	pdata->desc = id->data;
+
+	pdata->supply = devm_regulator_get(dev, "power");
+	if (IS_ERR(pdata->supply))
+		return PTR_ERR(pdata->supply);
+
+	pdata->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+	if (IS_ERR(pdata->enable_gpio)) {
+		ret = PTR_ERR(pdata->enable_gpio);
+		dev_err(dev, "failed to request GPIO: %d\n", ret);
+		return ret;
+	}
+
+	backlight = of_parse_phandle(dev->of_node, "backlight", 0);
+	if (backlight) {
+		pdata->backlight = of_find_backlight_by_node(backlight);
+		of_node_put(backlight);
+
+		if (!pdata->backlight)
+			return -EPROBE_DEFER;
+	}
+
+	drm_panel_init(&pdata->base);
+	pdata->base.dev = dev;
+	pdata->base.funcs = &panel_edp_2k_funcs;
+
+	ret = drm_panel_add(&pdata->base);
+	if (ret < 0)
+		goto free_backlight;
+
+	dev_set_drvdata(dev, pdata);
+
+	return 0;
+
+free_backlight:
+	if (pdata->backlight)
+		put_device(&pdata->backlight->dev);
+
+	return ret;
+}
+
+static int panel_edp_2k_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct panel_edp_2k_data *pdata = dev_get_drvdata(dev);
+
+	drm_panel_detach(&pdata->base);
+	drm_panel_remove(&pdata->base);
+
+	panel_edp_2k_disable(&pdata->base);
+	panel_edp_2k_unprepare(&pdata->base);
+
+	if (pdata->backlight)
+		put_device(&pdata->backlight->dev);
+
+	return 0;
+}
+
+static struct platform_driver panel_edp_2k_driver = {
+	.driver = {
+		.name = "innolux-2k-edp-panel",
+		.of_match_table = platform_of_match,
+	},
+	.probe = panel_edp_2k_probe,
+	.remove = panel_edp_2k_remove,
+};
+
+static int __init panel_edp_2k_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&panel_edp_2k_driver);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+module_init(panel_edp_2k_init);
+
+static void __exit panel_edp_2k_exit(void)
+{
+	platform_driver_unregister(&panel_edp_2k_driver);
+}
+module_exit(panel_edp_2k_exit);
+
+MODULE_DESCRIPTION("Innolux 2k eDP panel driver");
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [[RFC]DPU PATCH 4/4] dt-bindings: Add Innolux TV123WAM panel bindings
       [not found]   ` <1524054002-17869-1-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
                       ` (2 preceding siblings ...)
  2018-04-18 12:20     ` [[RFC]DPU PATCH 3/4] drm/panel: add Innolux TV123WAM eDP panel driver Sandeep Panda
@ 2018-04-18 12:20     ` Sandeep Panda
  2018-04-24 14:43       ` Rob Herring
  3 siblings, 1 reply; 21+ messages in thread
From: Sandeep Panda @ 2018-04-18 12:20 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: ryadav-sgV2jX0FEOL9JmXXK+q4OQ, Sandeep Panda,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	seanpaul-F7+t8E8rja9g9hUCZPvPmw,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw, jsanka-sgV2jX0FEOL9JmXXK+q4OQ,
	chandanu-sgV2jX0FEOL9JmXXK+q4OQ

The Innolux TV123WAM is a 12.3" eDP display panel
with 2160x1440 resolution.

Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
---
 .../bindings/display/panel/innolux,edp-2k-panel.txt | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt

diff --git a/Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt b/Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
new file mode 100644
index 0000000..19f271c
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
@@ -0,0 +1,21 @@
+Innolux TV123WAM 12.3 inch eDP display panel
+
+Required properties:
+- power-supply: regulator to provide the supply voltage
+- enable-gpios: GPIO pin to enable or disable the panel
+
+Optional properties:
+- backlight: phandle of the backlight device attached to the panel
+
+Example:
+
+	edp_panel: edp_panel {
+		compatible = "innolux, edp_2k_panel";
+		reg = <0>;
+
+		enable-gpios = <&msmgpio 32 GPIO_ACTIVE_HIGH>;
+
+		power-supply = <&pm8916_l8>;
+
+		backlight = <&backlight>;
+	};
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 2/4] dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings
  2018-04-18 12:20     ` [[RFC]DPU PATCH 2/4] dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings Sandeep Panda
@ 2018-04-18 13:58       ` Sean Paul
  2018-04-18 18:08         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  0 siblings, 1 reply; 21+ messages in thread
From: Sean Paul @ 2018-04-18 13:58 UTC (permalink / raw)
  To: Sandeep Panda
  Cc: devicetree, ryadav, linux-arm-msm, dri-devel, abhinavk,
	hoegsberg, freedreno, chandanu

On Wed, Apr 18, 2018 at 05:50:00PM +0530, Sandeep Panda wrote:
> Document the bindings used for the sn65dsi86 DSI to eDP bridge.
> 

Please add a changelog to your patches so reviewers know what has changed
between patch versions. 

> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
> ---
>  .../bindings/display/bridge/ti,sn65dsi86.txt       | 60 ++++++++++++++++++++++
>  1 file changed, 60 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
> 
> diff --git a/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt b/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
> new file mode 100644
> index 0000000..9e2612e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
> @@ -0,0 +1,60 @@
> +SN65DSI86 DSI to eDP bridge chip
> +--------------------------------
> +
> +This is the binding for Texas Instruments SN65DSI86 bridge.
> +
> +Required properties:
> +- compatible: Must be "ti,sn65dsi86"
> +- reg: i2c address of the chip, 0x2d as per datasheet
> +- enable-gpios: OF device-tree gpio specifications for bridge_en pin

The datasheet only has one enable pin, why gpios?

> +
> +- vccio-supply: A 1.8V supply that powers up the digital IOs.
> +- vcca-supply: A 1.2V supply that powers up the analog circuits.
> +
> +Optional properties:
> +
> +- irq-gpios: OF device-tree gpio specification for interrupt pin

From the last review, Rob said, 'Use "interrupts" property instead.'

You didn't provide responses to prior review comments, so it's unclear whether
you've intentionally or unintentionally ignored him :)

Sean

> +
> +Required nodes:
> +
> +This device has two video ports. Their connections are modelled using the
> +OF graph bindings specified in Documentation/devicetree/bindings/graph.txt.
> +
> +- Video port 0 for DSI input
> +- Video port 1 for eDP output
> +
> +Example
> +-------
> +
> +edp-bridge@2d {
> +	compatible = "ti,sn65dsi86";
> +	#address-cells = <1>;
> +	#size-cells = <0>;
> +	reg = <0x2d>;
> +
> +	enable-gpios = <&msmgpio 33 GPIO_ACTIVE_HIGH>;
> +
> +	vccio-supply = <&pm8916_l17>;
> +	vcca-supply = <&pm8916_l6>;
> +
> +	ports {
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		port@0 {
> +			reg = <0>;
> +
> +			edp_bridge_in: endpoint {
> +				remote-endpoint = <&dsi_out>;
> +			};
> +		};
> +
> +		port@1 {
> +			reg = <1>;
> +
> +			edp_bridge_out: endpoint {
> +				remote-endpoint = <&edp_panel_in>;
> +			};
> +		};
> +	};
> +}
> -- 
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> a Linux Foundation Collaborative Project
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver
  2018-04-18 12:19     ` [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver Sandeep Panda
@ 2018-04-18 14:11       ` Sean Paul
  2018-04-18 18:31         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  2018-04-19  4:37         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
       [not found]       ` <1524054002-17869-2-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
  1 sibling, 2 replies; 21+ messages in thread
From: Sean Paul @ 2018-04-18 14:11 UTC (permalink / raw)
  To: Sandeep Panda
  Cc: devicetree, ryadav, linux-arm-msm, dri-devel, abhinavk,
	hoegsberg, freedreno, chandanu

On Wed, Apr 18, 2018 at 05:49:59PM +0530, Sandeep Panda wrote:
> Add support for TI's sn65dsi86 dsi2edp bridge chip.
> The chip converts DSI transmitted signal to eDP signal,
> which is fed to the connected eDP panel.
> 
> This chip can be controlled via either i2c interface or
> dsi interface. Currently in driver all the control registers
> are being accessed through i2c interface only.
> Also as of now HPD support has not been added to bridge
> chip driver.
> 
> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
> ---
>  drivers/gpu/drm/bridge/ti-sn65dsi86.c | 742 ++++++++++++++++++++++++++++++++++
>  1 file changed, 742 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
> 
> diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> new file mode 100644
> index 0000000..c798f2f
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> @@ -0,0 +1,742 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +#include <linux/types.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/device.h>
> +#include <linux/platform_device.h>
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/gpio.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_graph.h>
> +#include <linux/regulator/consumer.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>

I have a hard time believing that you need all of these includes. Off the top of
my head, you could probably remove types, kernel, init, platform_device, delay,
drmP, drm_atomic, drm_crtc_helper. You could probably trim it even further with
the help of your compiler. These should also be in alphabetical order.

> +
> +#define SN_BRIDGE_REVISION_ID 0x2
> +
> +/* Link Training specific registers */
> +#define SN_DEVICE_REV_REG			0x08
> +#define SN_REFCLK_FREQ_REG			0x0A
> +#define SN_DSI_LANES_REG			0x10
> +#define SN_DSIA_CLK_FREQ_REG			0x12
> +#define SN_ENH_FRAME_REG			0x5A
> +#define SN_SSC_CONFIG_REG			0x93
> +#define SN_DATARATE_CONFIG_REG			0x94
> +#define SN_PLL_ENABLE_REG			0x0D
> +#define SN_SCRAMBLE_CONFIG_REG			0x95
> +#define SN_AUX_WDATA0_REG			0x64
> +#define SN_AUX_ADDR_19_16_REG			0x74
> +#define SN_AUX_ADDR_15_8_REG			0x75
> +#define SN_AUX_ADDR_7_0_REG			0x76
> +#define SN_AUX_LENGTH_REG			0x77
> +#define SN_AUX_CMD_REG				0x78
> +#define SN_ML_TX_MODE_REG			0x96
> +#define SN_PAGE_SELECT_REG			0xFF
> +#define SN_AUX_RDATA0_REG			0x79
> +/* video config specific registers */
> +#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG	0x20
> +#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG	0x21
> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG	0x24
> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG	0x25
> +#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG	0x2C
> +#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG	0x2D
> +#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG	0x30
> +#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG	0x31
> +#define SN_CHA_HORIZONTAL_BACK_PORCH_REG	0x34
> +#define SN_CHA_VERTICAL_BACK_PORCH_REG		0x36
> +#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG	0x38
> +#define SN_CHA_VERTICAL_FRONT_PORCH_REG		0x3A
> +#define SN_DATA_FORMAT_REG			0x5B
> +#define SN_COLOR_BAR_CONFIG_REG			0x3C
> +
> +#define DPPLL_CLK_SRC_REFCLK	0
> +#define DPPLL_CLK_SRC_DSICLK	1
> +#define MIN_DSI_CLK_FREQ_MHZ	40
> +
> +enum ti_sn_bridge_ref_clks {
> +	SN_REF_CLK_12_MHZ = 0,
> +	SN_REF_CLK_19_2_MHZ,
> +	SN_REF_CLK_26_MHZ,
> +	SN_REF_CLK_27_MHZ,
> +	SN_REF_CLK_38_4_MHZ,
> +	SN_REF_CLK_MAX,
> +};
> +
> +struct ti_sn_bridge {
> +	struct device *dev;
> +	struct drm_bridge bridge;
> +	struct drm_connector connector;
> +	struct device_node *host_node;
> +	struct mipi_dsi_device *dsi;
> +	/* handle to the connected eDP panel */
> +	struct drm_panel *panel;
> +	int irq;
> +	struct gpio_desc *irq_gpio;
> +	/* list of gpios needed to enable the bridge functionality */
> +	struct gpio_descs *enable_gpios;

Why are the enable gpios a list?

> +	unsigned int num_supplies;
> +	struct regulator_bulk_data *supplies;
> +	struct i2c_client *i2c_client;
> +	struct i2c_adapter *ddc;
> +	bool power_on;
> +	u32 num_modes;
> +	struct drm_display_mode curr_mode;
> +};
> +
> +static int ti_sn_bridge_write(struct ti_sn_bridge *pdata, u8 reg, u8 val)
> +{
> +	struct i2c_client *client = pdata->i2c_client;
> +	u8 buf[2] = {reg, val};
> +	struct i2c_msg msg = {
> +		.addr = client->addr,
> +		.flags = 0,
> +		.len = 2,
> +		.buf = buf,
> +	};
> +
> +	if (i2c_transfer(client->adapter, &msg, 1) < 1) {
> +		DRM_ERROR("i2c write failed\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ti_sn_bridge_read(struct ti_sn_bridge *pdata,
> +					u8 reg, char *buf, u32 size)
> +{
> +	struct i2c_client *client = pdata->i2c_client;
> +	struct i2c_msg msg[2] = {
> +		{
> +			.addr = client->addr,
> +			.flags = 0,
> +			.len = 1,
> +			.buf = &reg,
> +		},
> +		{
> +			.addr = client->addr,
> +			.flags = I2C_M_RD,
> +			.len = size,
> +			.buf = buf,
> +		}
> +	};
> +
> +	if (i2c_transfer(client->adapter, msg, 2) != 2) {
> +		DRM_ERROR("i2c read failed\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static void ti_sn_bridge_gpio_configure(struct ti_sn_bridge *pdata, bool on)
> +{
> +	int value, i = 0, num = 0;
> +	int *value_array = NULL;
> +
> +	if (!pdata)
> +		return;
> +
> +	value = on ? 1 : 0;
> +	num = pdata->enable_gpios->ndescs;
> +	value_array = kmalloc(num * sizeof(int), GFP_KERNEL);
> +	if (value_array) {
> +		for (i = 0; i < num; i++)
> +			value_array[i] = value;
> +		gpiod_set_array_value(num, pdata->enable_gpios->desc,
> +				      value_array);
> +		kfree(value_array);
> +	}
> +
> +	if (pdata->irq_gpio)
> +		gpiod_set_value(pdata->irq_gpio, value);
> +
> +	DRM_DEBUG("config to: %d\n", value);
> +}
> +
> +static void ti_sn_bridge_power_ctrl(struct ti_sn_bridge *pdata, bool enable)
> +{
> +	static int ref_count;
> +
> +	if (!pdata)
> +		return;
> +
> +	if (enable)
> +		ref_count++;
> +	else
> +		ref_count--;
> +
> +	if (enable && (ref_count == 1)) {
> +		ti_sn_bridge_gpio_configure(pdata, true);
> +
> +		if (regulator_bulk_enable(pdata->num_supplies,
> +						pdata->supplies)) {
> +			DRM_ERROR("bridge regulator enable failed\n");
> +			return;
> +		}
> +		pdata->power_on = true;
> +	} else if (!enable && !ref_count) {
> +		regulator_bulk_disable(pdata->num_supplies, pdata->supplies);
> +
> +		ti_sn_bridge_gpio_configure(pdata, false);
> +		pdata->power_on = false;
> +	} else {
> +		DRM_DEBUG("ti_sn_bridge power ctrl: %d refcount: %d\n",
> +							enable, ref_count);
> +	}
> +}
> +
> +/* Connector funcs */
> +static struct ti_sn_bridge *
> +connector_to_ti_sn_bridge(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct ti_sn_bridge, connector);
> +}
> +
> +static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
> +	struct drm_panel *panel = pdata->panel;
> +	struct edid *edid;
> +
> +	if (panel) {
> +		DRM_DEBUG("get mode from connected drm_panel\n");
> +		pdata->num_modes = drm_panel_get_modes(panel);
> +	} else {
> +		/* get from EDID */
> +		if (!pdata->ddc)
> +			return 0;
> +		ti_sn_bridge_power_ctrl(pdata, true);
> +		edid = drm_get_edid(connector, pdata->ddc);
> +		if (edid) {
> +			drm_mode_connector_update_edid_property(connector,
> +								edid);
> +			pdata->num_modes = drm_add_edid_modes(connector, edid);
> +			drm_edid_to_eld(connector, edid);
> +			kfree(edid);
> +		} else {
> +			DRM_DEBUG("failed to get edid\n");
> +		}
> +		ti_sn_bridge_power_ctrl(pdata, false);
> +	}
> +
> +	return pdata->num_modes;
> +}
> +
> +static enum drm_mode_status
> +ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
> +			     struct drm_display_mode *mode)
> +{
> +	/* maximum supported resolution is 4K at 60 fps */
> +	if (mode->clock > 594000)
> +		return MODE_CLOCK_HIGH;
> +
> +	return MODE_OK;
> +}
> +
> +static struct drm_connector_helper_funcs ti_sn_bridge_connector_helper_funcs = {
> +	.get_modes = ti_sn_bridge_connector_get_modes,
> +	.mode_valid = ti_sn_bridge_connector_mode_valid,
> +};
> +
> +static enum drm_connector_status
> +ti_sn_bridge_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
> +
> +	/**
> +	 * TODO: Currently if drm_panel is present, then always
> +	 * return the status as connected. Need to add support to detect
> +	 * device state for no panel(hot pluggable) scenarios.
> +	 */
> +	if (pdata->panel)
> +		return connector_status_connected;
> +	else
> +		return connector_status_disconnected;
> +}
> +
> +static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = {
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = ti_sn_bridge_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,
> +};
> +
> +static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct ti_sn_bridge, bridge);
> +}
> +
> +static int ti_sn_bridge_read_device_rev(struct ti_sn_bridge *pdata)
> +{
> +	u8 rev = 0;
> +	int ret = 0;
> +
> +	ret = ti_sn_bridge_read(pdata, SN_DEVICE_REV_REG, &rev, 1);
> +	if (ret)
> +		goto exit;
> +
> +	if (rev == SN_BRIDGE_REVISION_ID) {
> +		DRM_DEBUG("ti_sn_bridge revision id: 0x%x\n", rev);
> +	} else {
> +		DRM_ERROR("ti_sn_bridge revision id mismatch\n");
> +		ret = -EINVAL;
> +	}
> +
> +exit:
> +	return ret;
> +}
> +
> +static const char * const ti_sn_bridge_supply_names[] = {
> +	"vccio",
> +	"vcca",
> +};
> +
> +static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	pdata->num_supplies = ARRAY_SIZE(ti_sn_bridge_supply_names);
> +
> +	pdata->supplies = devm_kcalloc(pdata->dev, pdata->num_supplies,
> +				     sizeof(*pdata->supplies), GFP_KERNEL);
> +	if (!pdata->supplies)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < pdata->num_supplies; i++)
> +		pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
> +
> +	ret = devm_regulator_bulk_get(pdata->dev,
> +			pdata->num_supplies, pdata->supplies);
> +
> +	return ret;
> +}
> +
> +static void ti_sn_bridge_attach_panel(struct ti_sn_bridge *pdata)
> +{
> +	struct device_node *panel_node, *port, *endpoint;
> +	struct drm_panel *panel = NULL;
> +
> +	port = of_graph_get_port_by_id(pdata->dev->of_node, 1);
> +	if (port) {
> +		endpoint = of_get_child_by_name(port, "endpoint");
> +		of_node_put(port);
> +		if (!endpoint) {
> +			dev_err(pdata->dev, "no output endpoint found\n");
> +			goto error;
> +		}
> +
> +		panel_node = of_graph_get_remote_port_parent(endpoint);
> +		of_node_put(endpoint);
> +		if (!panel_node) {
> +			dev_err(pdata->dev, "no output node found\n");
> +			goto error;
> +		}
> +
> +		panel = of_drm_find_panel(panel_node);
> +		of_node_put(panel_node);
> +		if (!panel) {
> +			dev_err(pdata->dev, "no panel node found\n");
> +			goto error;
> +		}
> +	}
> +
> +	pdata->panel = panel;
> +	drm_panel_attach(panel, &pdata->connector);
> +
> +	return;
> +
> +error:
> +	pdata->panel = NULL;
> +}
> +
> +static int ti_sn_bridge_attach(struct drm_bridge *bridge)
> +{
> +	struct mipi_dsi_host *host;
> +	struct mipi_dsi_device *dsi;
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +	int ret;
> +	const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
> +						   .channel = 0,
> +						   .node = NULL,
> +						 };
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");
> +		return -ENODEV;
> +	}
> +
> +	/* HPD not supported */
> +	pdata->connector.polled = 0;
> +
> +	ret = drm_connector_init(bridge->dev, &pdata->connector,
> +				 &ti_sn_bridge_connector_funcs,
> +				 DRM_MODE_CONNECTOR_eDP);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	drm_connector_helper_add(&pdata->connector,
> +				 &ti_sn_bridge_connector_helper_funcs);
> +	drm_mode_connector_attach_encoder(&pdata->connector, bridge->encoder);
> +
> +	host = of_find_mipi_dsi_host_by_node(pdata->host_node);
> +	if (!host) {
> +		DRM_ERROR("failed to find dsi host\n");
> +		return -ENODEV;
> +	}
> +
> +	dsi = mipi_dsi_device_register_full(host, &info);
> +	if (IS_ERR(dsi)) {
> +		DRM_ERROR("failed to create dsi device\n");
> +		ret = PTR_ERR(dsi);
> +		goto err_dsi_device;
> +	}
> +
> +	/* TODO: setting to 4 lanes always for now */
> +	dsi->lanes = 4;
> +	dsi->format = MIPI_DSI_FMT_RGB888;
> +	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
> +			  MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
> +
> +	ret = mipi_dsi_attach(dsi);
> +	if (ret < 0) {
> +		DRM_ERROR("failed to attach dsi to host\n");
> +		goto err_dsi_attach;
> +	}
> +
> +	pdata->dsi = dsi;
> +
> +	DRM_DEBUG("bridge attached\n");
> +	/* attach panel to bridge */
> +	ti_sn_bridge_attach_panel(pdata);
> +
> +	return 0;
> +
> +err_dsi_attach:
> +	mipi_dsi_device_unregister(dsi);
> +err_dsi_device:
> +	return ret;
> +}
> +
> +static void ti_sn_bridge_mode_set(struct drm_bridge *bridge,
> +				    struct drm_display_mode *mode,
> +				    struct drm_display_mode *adj_mode)
> +{
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +
> +	DRM_DEBUG("mode_set: hdisplay=%d, vdisplay=%d, vrefresh=%d, clock=%d\n",
> +		adj_mode->hdisplay, adj_mode->vdisplay,
> +		adj_mode->vrefresh, adj_mode->clock);
> +
> +	drm_mode_copy(&pdata->curr_mode, adj_mode);
> +}
> +
> +static void ti_sn_bridge_disable(struct drm_bridge *bridge)
> +{
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +	struct drm_panel *panel = pdata->panel;
> +
> +	if (panel) {
> +		drm_panel_disable(panel);
> +		drm_panel_unprepare(panel);
> +	}
> +
> +	ti_sn_bridge_power_ctrl(pdata, false);
> +}
> +
> +static void ti_sn_bridge_enable(struct drm_bridge *bridge)
> +{
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +	struct drm_panel *panel = pdata->panel;
> +	struct drm_display_mode *mode = &pdata->curr_mode;
> +	u32 bit_rate_mhz, clk_freq_mhz;
> +	u8 val = 0;
> +
> +	if (!pdata->num_modes)
> +		return;
> +
> +	ti_sn_bridge_power_ctrl(pdata, true);
> +
> +	if (panel)
> +		drm_panel_prepare(panel);
> +
> +	/* CLK_SRC config */
> +	ti_sn_bridge_write(pdata, SN_REFCLK_FREQ_REG,
> +			(DPPLL_CLK_SRC_REFCLK | (SN_REF_CLK_19_2_MHZ << 0x1)));
> +
> +	/* DSI_A lane config */
> +	ti_sn_bridge_read(pdata, SN_DSI_LANES_REG, &val, 0x1);
> +	val |= ((0x4 - pdata->dsi->lanes) & 0x03) << 0x3;
> +	ti_sn_bridge_write(pdata, SN_DSI_LANES_REG, val);
> +
> +	/* DP lane config */
> +	ti_sn_bridge_read(pdata, SN_SSC_CONFIG_REG, &val, 0x1);
> +	val |= ((pdata->dsi->lanes - 0x1) & 0x03) << 0x4;
> +	ti_sn_bridge_write(pdata, SN_SSC_CONFIG_REG, val);
> +
> +	/* set dsi clk frequency value */
> +	bit_rate_mhz = (mode->clock / 1000) *
> +			mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
> +	clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
> +	/* for each increment in val, frequency increases by 5MHz */
> +	val = (MIN_DSI_CLK_FREQ_MHZ / 0x5) +
> +		(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 0x5) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_DSIA_CLK_FREQ_REG, val);
> +
> +	ti_sn_bridge_write(pdata, SN_DATARATE_CONFIG_REG, 0x80); /* set HBR */
> +
> +	ti_sn_bridge_write(pdata, SN_PLL_ENABLE_REG, 0x1); /* Enable PLL */
> +	usleep_range(10000, 10001);
> +
> +	/* DPCD Register 0x0010A in Sink */
> +	ti_sn_bridge_write(pdata, SN_AUX_WDATA0_REG, 0x01);
> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_19_16_REG, 0x00);
> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_15_8_REG, 0x01);
> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_7_0_REG, 0x0A);
> +	ti_sn_bridge_write(pdata, SN_AUX_LENGTH_REG, 0x01);
> +	ti_sn_bridge_write(pdata, SN_AUX_CMD_REG, 0x81);
> +	usleep_range(10000, 10001);
> +
> +	/* Semi auto link training mode */
> +	ti_sn_bridge_write(pdata, SN_ML_TX_MODE_REG, 0x0A);
> +	usleep_range(20000, 20001);
> +
> +	/* config video parameters */
> +	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
> +			mode->hdisplay & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG,
> +			(mode->hdisplay >> 0x8) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
> +			mode->vdisplay & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG,
> +			(mode->hdisplay >> 0x8) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
> +			(mode->htotal - mode->hsync_end) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
> +			((mode->htotal - mode->hsync_end) >> 0x8) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
> +			(mode->vtotal - mode->vsync_end) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
> +			((mode->vtotal - mode->vsync_end) >> 0x8) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
> +			(mode->hsync_start - mode->hdisplay) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_BACK_PORCH_REG,
> +			(mode->vsync_start - mode->vdisplay) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
> +			(mode->hsync_end - mode->hsync_start) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_FRONT_PORCH_REG,
> +			(mode->vsync_end - mode->vsync_start) & 0xFF);
> +	if (pdata->dsi->format == MIPI_DSI_FMT_RGB888)
> +		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x00);
> +	else
> +		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x01);
> +	usleep_range(10000, 10001);
> +
> +	/* enable video stream */
> +	ti_sn_bridge_read(pdata, SN_ENH_FRAME_REG, &val, 0x1);
> +	val |= BIT(3);
> +	ti_sn_bridge_write(pdata, SN_ENH_FRAME_REG, val);
> +
> +	if (panel)
> +		drm_panel_enable(panel);

As mentioned in the previous review, you should configure the mode in mode_set,
not enable. This eliminates the need for curr_mode.

> +}
> +
> +static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
> +	.attach = ti_sn_bridge_attach,
> +	.enable = ti_sn_bridge_enable,
> +	.disable = ti_sn_bridge_disable,
> +	.mode_set = ti_sn_bridge_mode_set,
> +};
> +
> +static int ti_sn_bridge_parse_gpios(struct ti_sn_bridge *pdata)
> +{
> +	int ret = 0;
> +
> +	if (!pdata || !pdata->dev)
> +		return -EINVAL;
> +
> +	pdata->enable_gpios = devm_gpiod_get_array(pdata->dev,
> +						"enable", GPIOD_OUT_LOW);
> +	if (IS_ERR(pdata->enable_gpios)) {
> +		DRM_ERROR("failed to get enable gpio from DT\n");
> +		ret = PTR_ERR(pdata->enable_gpios);
> +		goto exit;
> +	}
> +
> +	pdata->irq_gpio = devm_gpiod_get_optional(pdata->dev, "irq",
> +						  GPIOD_OUT_LOW);
> +	if (IS_ERR(pdata->irq_gpio))
> +		DRM_ERROR("failed to get irq gpio from DT\n");
> +
> +exit:
> +	return ret;
> +}
> +
> +static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
> +{
> +	struct device_node *np = pdata->dev->of_node;
> +	struct device_node *end_node;
> +
> +	end_node = of_graph_get_endpoint_by_regs(np, 0, 0);
> +	if (!end_node) {
> +		DRM_ERROR("remote endpoint not found\n");
> +		return -ENODEV;
> +	}
> +
> +	pdata->host_node = of_graph_get_remote_port_parent(end_node);
> +	of_node_put(end_node);
> +	if (!pdata->host_node) {
> +		DRM_ERROR("remote node not found\n");
> +		return -ENODEV;
> +	}
> +	of_node_put(pdata->host_node);
> +
> +	return 0;
> +}
> +
> +static int ti_sn_bridge_probe(struct i2c_client *client,
> +	 const struct i2c_device_id *id)
> +{
> +	struct ti_sn_bridge *pdata;
> +	struct device_node *ddc_node;
> +	int ret = 0;
> +
> +	if (!client || !client->dev.of_node) {
> +		DRM_ERROR("invalid input\n");
> +		return -EINVAL;
> +	}
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> +		DRM_ERROR("device doesn't support I2C\n");
> +		return -ENODEV;
> +	}
> +
> +	pdata = devm_kzalloc(&client->dev,
> +		sizeof(struct ti_sn_bridge), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->power_on = false;
> +	pdata->dev = &client->dev;
> +	pdata->i2c_client = client;
> +	DRM_DEBUG("I2C address is %x\n", client->addr);
> +
> +	ret = ti_sn_bridge_parse_dsi_host(pdata);
> +	if (ret) {
> +		DRM_ERROR("failed to parse device tree\n");
> +		goto err_dt_parse;
> +	}
> +
> +	ret = ti_sn_bridge_parse_gpios(pdata);
> +	if (ret) {
> +		DRM_ERROR("failed to parse gpios from DT\n");
> +		goto err_dt_parse;
> +	}
> +
> +	ret = ti_sn_bridge_parse_regulators(pdata);
> +	if (ret) {
> +		DRM_ERROR("failed to parse regulators\n");
> +		goto err_dt_parse;
> +	}
> +
> +	ddc_node = of_parse_phandle(pdata->dev->of_node, "ddc-i2c-bus", 0);
> +	if (ddc_node) {
> +		pdata->ddc = of_find_i2c_adapter_by_node(ddc_node);
> +		of_node_put(ddc_node);
> +		if (!pdata->ddc) {
> +			dev_dbg(pdata->dev, "failed to read ddc node\n");
> +			return -EPROBE_DEFER;
> +		}
> +	} else {
> +		dev_dbg(pdata->dev, "no ddc property found\n");
> +	}
> +
> +	ti_sn_bridge_power_ctrl(pdata, true);
> +	ret = ti_sn_bridge_read_device_rev(pdata);
> +	if (ret) {
> +		DRM_ERROR("failed to read chip rev\n");
> +		goto err_config;
> +	} else {
> +		DRM_DEBUG("bridge chip enabled successfully\n");
> +	}
> +	ti_sn_bridge_power_ctrl(pdata, false);
> +
> +	i2c_set_clientdata(client, pdata);
> +	dev_set_drvdata(&client->dev, pdata);
> +
> +	pdata->bridge.funcs = &ti_sn_bridge_funcs;
> +	pdata->bridge.of_node = client->dev.of_node;
> +
> +	drm_bridge_add(&pdata->bridge);
> +
> +	return ret;
> +
> +err_config:
> +	ti_sn_bridge_power_ctrl(pdata, false);
> +err_dt_parse:
> +	devm_kfree(&client->dev, pdata);
> +	return ret;
> +}
> +
> +static int ti_sn_bridge_remove(struct i2c_client *client)
> +{
> +	int ret = -EINVAL;
> +	struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
> +
> +	if (!pdata)
> +		goto end;
> +
> +	mipi_dsi_detach(pdata->dsi);
> +	mipi_dsi_device_unregister(pdata->dsi);
> +
> +	drm_bridge_remove(&pdata->bridge);
> +
> +	disable_irq(pdata->irq);
> +	free_irq(pdata->irq, pdata);

This is uninitialized.

> +
> +	ti_sn_bridge_gpio_configure(pdata, false);
> +	i2c_put_adapter(pdata->ddc);
> +
> +	devm_kfree(&client->dev, pdata);
> +
> +end:
> +	return ret;
> +}
> +
> +static struct i2c_device_id ti_sn_bridge_id[] = {
> +	{ "ti,sn65dsi86", 0},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
> +
> +static const struct of_device_id ti_sn_bridge_match_table[] = {
> +	{.compatible = "ti,sn65dsi86"},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
> +
> +static struct i2c_driver ti_sn_bridge_driver = {
> +	.driver = {
> +		.name = "ti_sn65dsi86",
> +		.owner = THIS_MODULE,
> +		.of_match_table = ti_sn_bridge_match_table,
> +	},
> +	.probe = ti_sn_bridge_probe,
> +	.remove = ti_sn_bridge_remove,
> +	.id_table = ti_sn_bridge_id,
> +};
> +
> +module_i2c_driver(ti_sn_bridge_driver);
> +MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
> -- 
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> a Linux Foundation Collaborative Project
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp panel driver
  2018-04-18 12:19 ` [[RFC]DPU PATCH 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp panel driver Sandeep Panda
       [not found]   ` <1524054002-17869-1-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
@ 2018-04-18 14:12   ` Sean Paul
  2018-04-18 18:36     ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  2018-04-26  7:08   ` Andrzej Hajda
  2 siblings, 1 reply; 21+ messages in thread
From: Sean Paul @ 2018-04-18 14:12 UTC (permalink / raw)
  To: Sandeep Panda
  Cc: devicetree, ryadav, linux-arm-msm, dri-devel, abhinavk,
	hoegsberg, freedreno, chandanu

On Wed, Apr 18, 2018 at 05:49:58PM +0530, Sandeep Panda wrote:
> Changelog:
> 
> v3 -> v4:

I didn't really bother to do a thorough review given that there are obvious
mistakes and ignored feedback, it's a waste of time, tbh. Please go through
the previous reviews and resend a more polished version.

Sean

> Current patchset separates out eDP panel specific resources from sn65dsi86
> bridge driver and creates a separate driver for the innloux edp panel.
> 
> Now bridge driver check if any panel is attached or not to get the supported
> modes. If any panel is attached then query from panel driver to get the modes,
> or else probe the provided i2c adapter to read the modes from EDID.
> 
> Removed hardcoding of bridge init sequence. With this patchset bridge driver now
> will program the init sequence based on the current mode set by drm framework.
> 
> Current patchset is not tested on actual bridge chip/panel.
> 
> Sandeep Panda (4):
>   drm/bridge: add support for sn65dsi86 bridge driver
>   dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings
>   drm/panel: add Innolux TV123WAM eDP panel driver
>   dt-bindings: Add Innolux TV123WAM panel bindings
> 
>  .../bindings/display/bridge/ti,sn65dsi86.txt       |  60 ++
>  .../display/panel/innolux,edp-2k-panel.txt         |  21 +
>  drivers/gpu/drm/bridge/ti-sn65dsi86.c              | 742 +++++++++++++++++++++
>  drivers/gpu/drm/panel/panel-innolux-tv123wam.c     | 299 +++++++++
>  4 files changed, 1122 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
>  create mode 100644 Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
>  create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
>  create mode 100644 drivers/gpu/drm/panel/panel-innolux-tv123wam.c
> 
> -- 
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> a Linux Foundation Collaborative Project
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 2/4] dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings
  2018-04-18 13:58       ` Sean Paul
@ 2018-04-18 18:08         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  0 siblings, 0 replies; 21+ messages in thread
From: spanda-sgV2jX0FEOL9JmXXK+q4OQ @ 2018-04-18 18:08 UTC (permalink / raw)
  To: Sean Paul
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, ryadav-sgV2jX0FEOL9JmXXK+q4OQ,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	jsanka-sgV2jX0FEOL9JmXXK+q4OQ, chandanu-sgV2jX0FEOL9JmXXK+q4OQ

On 2018-04-18 19:28, Sean Paul wrote:
> On Wed, Apr 18, 2018 at 05:50:00PM +0530, Sandeep Panda wrote:
>> Document the bindings used for the sn65dsi86 DSI to eDP bridge.
>> 
> 
> Please add a changelog to your patches so reviewers know what has 
> changed
> between patch versions.
I will update the change log in the commit text of the patches.
> 
>> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
>> ---
>>  .../bindings/display/bridge/ti,sn65dsi86.txt       | 60 
>> ++++++++++++++++++++++
>>  1 file changed, 60 insertions(+)
>>  create mode 100644 
>> Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
>> 
>> diff --git 
>> a/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt 
>> b/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
>> new file mode 100644
>> index 0000000..9e2612e
>> --- /dev/null
>> +++ 
>> b/Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
>> @@ -0,0 +1,60 @@
>> +SN65DSI86 DSI to eDP bridge chip
>> +--------------------------------
>> +
>> +This is the binding for Texas Instruments SN65DSI86 bridge.
>> +
>> +Required properties:
>> +- compatible: Must be "ti,sn65dsi86"
>> +- reg: i2c address of the chip, 0x2d as per datasheet
>> +- enable-gpios: OF device-tree gpio specifications for bridge_en pin
> 
> The datasheet only has one enable pin, why gpios?
This is the convention followed by other bridge drivers also.
https://www.kernel.org/doc/Documentation/devicetree/bindings/gpio/gpio.txt
please the example mentioned here,
Example of a node using GPIOs:

	node {
		enable-gpios = <&qe_pio_e 18 GPIO_ACTIVE_HIGH>;
	};

> 
>> +
>> +- vccio-supply: A 1.8V supply that powers up the digital IOs.
>> +- vcca-supply: A 1.2V supply that powers up the analog circuits.
>> +
>> +Optional properties:
>> +
>> +- irq-gpios: OF device-tree gpio specification for interrupt pin
> 
> From the last review, Rob said, 'Use "interrupts" property instead.'
> 
> You didn't provide responses to prior review comments, so it's unclear 
> whether
> you've intentionally or unintentionally ignored him :)
Sorry missed it completely while addressing other review comments.
> 
> Sean
> 
>> +
>> +Required nodes:
>> +
>> +This device has two video ports. Their connections are modelled using 
>> the
>> +OF graph bindings specified in 
>> Documentation/devicetree/bindings/graph.txt.
>> +
>> +- Video port 0 for DSI input
>> +- Video port 1 for eDP output
>> +
>> +Example
>> +-------
>> +
>> +edp-bridge@2d {
>> +	compatible = "ti,sn65dsi86";
>> +	#address-cells = <1>;
>> +	#size-cells = <0>;
>> +	reg = <0x2d>;
>> +
>> +	enable-gpios = <&msmgpio 33 GPIO_ACTIVE_HIGH>;
>> +
>> +	vccio-supply = <&pm8916_l17>;
>> +	vcca-supply = <&pm8916_l6>;
>> +
>> +	ports {
>> +		#address-cells = <1>;
>> +		#size-cells = <0>;
>> +
>> +		port@0 {
>> +			reg = <0>;
>> +
>> +			edp_bridge_in: endpoint {
>> +				remote-endpoint = <&dsi_out>;
>> +			};
>> +		};
>> +
>> +		port@1 {
>> +			reg = <1>;
>> +
>> +			edp_bridge_out: endpoint {
>> +				remote-endpoint = <&edp_panel_in>;
>> +			};
>> +		};
>> +	};
>> +}
>> --
>> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora 
>> Forum,
>> a Linux Foundation Collaborative Project
>> 
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver
  2018-04-18 14:11       ` Sean Paul
@ 2018-04-18 18:31         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  2018-04-19  4:37         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  1 sibling, 0 replies; 21+ messages in thread
From: spanda-sgV2jX0FEOL9JmXXK+q4OQ @ 2018-04-18 18:31 UTC (permalink / raw)
  To: Sean Paul
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, ryadav-sgV2jX0FEOL9JmXXK+q4OQ,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	jsanka-sgV2jX0FEOL9JmXXK+q4OQ, chandanu-sgV2jX0FEOL9JmXXK+q4OQ

On 2018-04-18 19:41, Sean Paul wrote:
> On Wed, Apr 18, 2018 at 05:49:59PM +0530, Sandeep Panda wrote:
>> Add support for TI's sn65dsi86 dsi2edp bridge chip.
>> The chip converts DSI transmitted signal to eDP signal,
>> which is fed to the connected eDP panel.
>> 
>> This chip can be controlled via either i2c interface or
>> dsi interface. Currently in driver all the control registers
>> are being accessed through i2c interface only.
>> Also as of now HPD support has not been added to bridge
>> chip driver.
>> 
>> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
>> ---
>>  drivers/gpu/drm/bridge/ti-sn65dsi86.c | 742 
>> ++++++++++++++++++++++++++++++++++
>>  1 file changed, 742 insertions(+)
>>  create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> 
>> diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c 
>> b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> new file mode 100644
>> index 0000000..c798f2f
>> --- /dev/null
>> +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> @@ -0,0 +1,742 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +
>> +#include <linux/types.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/init.h>
>> +#include <linux/device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/delay.h>
>> +#include <linux/i2c.h>
>> +#include <linux/gpio.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/of_graph.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
> 
> I have a hard time believing that you need all of these includes. Off 
> the top of
> my head, you could probably remove types, kernel, init, 
> platform_device, delay,
> drmP, drm_atomic, drm_crtc_helper. You could probably trim it even 
> further with
> the help of your compiler. These should also be in alphabetical order.
> 
I will take care of alphabetical order and generic kernel header file 
inclusion. But the header files of
drmP, drm_atomic, drm_crtc_helper are required here.

drmP, drm_atomic, drm_crtc_helper all these are needed because of the 
drm api that we using in this driver
for modes, connectors and bridges. the drm header files i have already 
checked with compiler.


>> +
>> +#define SN_BRIDGE_REVISION_ID 0x2
>> +
>> +/* Link Training specific registers */
>> +#define SN_DEVICE_REV_REG			0x08
>> +#define SN_REFCLK_FREQ_REG			0x0A
>> +#define SN_DSI_LANES_REG			0x10
>> +#define SN_DSIA_CLK_FREQ_REG			0x12
>> +#define SN_ENH_FRAME_REG			0x5A
>> +#define SN_SSC_CONFIG_REG			0x93
>> +#define SN_DATARATE_CONFIG_REG			0x94
>> +#define SN_PLL_ENABLE_REG			0x0D
>> +#define SN_SCRAMBLE_CONFIG_REG			0x95
>> +#define SN_AUX_WDATA0_REG			0x64
>> +#define SN_AUX_ADDR_19_16_REG			0x74
>> +#define SN_AUX_ADDR_15_8_REG			0x75
>> +#define SN_AUX_ADDR_7_0_REG			0x76
>> +#define SN_AUX_LENGTH_REG			0x77
>> +#define SN_AUX_CMD_REG				0x78
>> +#define SN_ML_TX_MODE_REG			0x96
>> +#define SN_PAGE_SELECT_REG			0xFF
>> +#define SN_AUX_RDATA0_REG			0x79
>> +/* video config specific registers */
>> +#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG	0x20
>> +#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG	0x21
>> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG	0x24
>> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG	0x25
>> +#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG	0x2C
>> +#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG	0x2D
>> +#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG	0x30
>> +#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG	0x31
>> +#define SN_CHA_HORIZONTAL_BACK_PORCH_REG	0x34
>> +#define SN_CHA_VERTICAL_BACK_PORCH_REG		0x36
>> +#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG	0x38
>> +#define SN_CHA_VERTICAL_FRONT_PORCH_REG		0x3A
>> +#define SN_DATA_FORMAT_REG			0x5B
>> +#define SN_COLOR_BAR_CONFIG_REG			0x3C
>> +
>> +#define DPPLL_CLK_SRC_REFCLK	0
>> +#define DPPLL_CLK_SRC_DSICLK	1
>> +#define MIN_DSI_CLK_FREQ_MHZ	40
>> +
>> +enum ti_sn_bridge_ref_clks {
>> +	SN_REF_CLK_12_MHZ = 0,
>> +	SN_REF_CLK_19_2_MHZ,
>> +	SN_REF_CLK_26_MHZ,
>> +	SN_REF_CLK_27_MHZ,
>> +	SN_REF_CLK_38_4_MHZ,
>> +	SN_REF_CLK_MAX,
>> +};
>> +
>> +struct ti_sn_bridge {
>> +	struct device *dev;
>> +	struct drm_bridge bridge;
>> +	struct drm_connector connector;
>> +	struct device_node *host_node;
>> +	struct mipi_dsi_device *dsi;
>> +	/* handle to the connected eDP panel */
>> +	struct drm_panel *panel;
>> +	int irq;
>> +	struct gpio_desc *irq_gpio;
>> +	/* list of gpios needed to enable the bridge functionality */
>> +	struct gpio_descs *enable_gpios;
> 
> Why are the enable gpios a list?
> 
>> +	unsigned int num_supplies;
>> +	struct regulator_bulk_data *supplies;
>> +	struct i2c_client *i2c_client;
>> +	struct i2c_adapter *ddc;
>> +	bool power_on;
>> +	u32 num_modes;
>> +	struct drm_display_mode curr_mode;
>> +};
>> +
>> +static int ti_sn_bridge_write(struct ti_sn_bridge *pdata, u8 reg, u8 
>> val)
>> +{
>> +	struct i2c_client *client = pdata->i2c_client;
>> +	u8 buf[2] = {reg, val};
>> +	struct i2c_msg msg = {
>> +		.addr = client->addr,
>> +		.flags = 0,
>> +		.len = 2,
>> +		.buf = buf,
>> +	};
>> +
>> +	if (i2c_transfer(client->adapter, &msg, 1) < 1) {
>> +		DRM_ERROR("i2c write failed\n");
>> +		return -EIO;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_sn_bridge_read(struct ti_sn_bridge *pdata,
>> +					u8 reg, char *buf, u32 size)
>> +{
>> +	struct i2c_client *client = pdata->i2c_client;
>> +	struct i2c_msg msg[2] = {
>> +		{
>> +			.addr = client->addr,
>> +			.flags = 0,
>> +			.len = 1,
>> +			.buf = &reg,
>> +		},
>> +		{
>> +			.addr = client->addr,
>> +			.flags = I2C_M_RD,
>> +			.len = size,
>> +			.buf = buf,
>> +		}
>> +	};
>> +
>> +	if (i2c_transfer(client->adapter, msg, 2) != 2) {
>> +		DRM_ERROR("i2c read failed\n");
>> +		return -EIO;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void ti_sn_bridge_gpio_configure(struct ti_sn_bridge *pdata, 
>> bool on)
>> +{
>> +	int value, i = 0, num = 0;
>> +	int *value_array = NULL;
>> +
>> +	if (!pdata)
>> +		return;
>> +
>> +	value = on ? 1 : 0;
>> +	num = pdata->enable_gpios->ndescs;
>> +	value_array = kmalloc(num * sizeof(int), GFP_KERNEL);
>> +	if (value_array) {
>> +		for (i = 0; i < num; i++)
>> +			value_array[i] = value;
>> +		gpiod_set_array_value(num, pdata->enable_gpios->desc,
>> +				      value_array);
>> +		kfree(value_array);
>> +	}
>> +
>> +	if (pdata->irq_gpio)
>> +		gpiod_set_value(pdata->irq_gpio, value);
>> +
>> +	DRM_DEBUG("config to: %d\n", value);
>> +}
>> +
>> +static void ti_sn_bridge_power_ctrl(struct ti_sn_bridge *pdata, bool 
>> enable)
>> +{
>> +	static int ref_count;
>> +
>> +	if (!pdata)
>> +		return;
>> +
>> +	if (enable)
>> +		ref_count++;
>> +	else
>> +		ref_count--;
>> +
>> +	if (enable && (ref_count == 1)) {
>> +		ti_sn_bridge_gpio_configure(pdata, true);
>> +
>> +		if (regulator_bulk_enable(pdata->num_supplies,
>> +						pdata->supplies)) {
>> +			DRM_ERROR("bridge regulator enable failed\n");
>> +			return;
>> +		}
>> +		pdata->power_on = true;
>> +	} else if (!enable && !ref_count) {
>> +		regulator_bulk_disable(pdata->num_supplies, pdata->supplies);
>> +
>> +		ti_sn_bridge_gpio_configure(pdata, false);
>> +		pdata->power_on = false;
>> +	} else {
>> +		DRM_DEBUG("ti_sn_bridge power ctrl: %d refcount: %d\n",
>> +							enable, ref_count);
>> +	}
>> +}
>> +
>> +/* Connector funcs */
>> +static struct ti_sn_bridge *
>> +connector_to_ti_sn_bridge(struct drm_connector *connector)
>> +{
>> +	return container_of(connector, struct ti_sn_bridge, connector);
>> +}
>> +
>> +static int ti_sn_bridge_connector_get_modes(struct drm_connector 
>> *connector)
>> +{
>> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
>> +	struct drm_panel *panel = pdata->panel;
>> +	struct edid *edid;
>> +
>> +	if (panel) {
>> +		DRM_DEBUG("get mode from connected drm_panel\n");
>> +		pdata->num_modes = drm_panel_get_modes(panel);
>> +	} else {
>> +		/* get from EDID */
>> +		if (!pdata->ddc)
>> +			return 0;
>> +		ti_sn_bridge_power_ctrl(pdata, true);
>> +		edid = drm_get_edid(connector, pdata->ddc);
>> +		if (edid) {
>> +			drm_mode_connector_update_edid_property(connector,
>> +								edid);
>> +			pdata->num_modes = drm_add_edid_modes(connector, edid);
>> +			drm_edid_to_eld(connector, edid);
>> +			kfree(edid);
>> +		} else {
>> +			DRM_DEBUG("failed to get edid\n");
>> +		}
>> +		ti_sn_bridge_power_ctrl(pdata, false);
>> +	}
>> +
>> +	return pdata->num_modes;
>> +}
>> +
>> +static enum drm_mode_status
>> +ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
>> +			     struct drm_display_mode *mode)
>> +{
>> +	/* maximum supported resolution is 4K at 60 fps */
>> +	if (mode->clock > 594000)
>> +		return MODE_CLOCK_HIGH;
>> +
>> +	return MODE_OK;
>> +}
>> +
>> +static struct drm_connector_helper_funcs 
>> ti_sn_bridge_connector_helper_funcs = {
>> +	.get_modes = ti_sn_bridge_connector_get_modes,
>> +	.mode_valid = ti_sn_bridge_connector_mode_valid,
>> +};
>> +
>> +static enum drm_connector_status
>> +ti_sn_bridge_connector_detect(struct drm_connector *connector, bool 
>> force)
>> +{
>> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
>> +
>> +	/**
>> +	 * TODO: Currently if drm_panel is present, then always
>> +	 * return the status as connected. Need to add support to detect
>> +	 * device state for no panel(hot pluggable) scenarios.
>> +	 */
>> +	if (pdata->panel)
>> +		return connector_status_connected;
>> +	else
>> +		return connector_status_disconnected;
>> +}
>> +
>> +static const struct drm_connector_funcs ti_sn_bridge_connector_funcs 
>> = {
>> +	.fill_modes = drm_helper_probe_single_connector_modes,
>> +	.detect = ti_sn_bridge_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,
>> +};
>> +
>> +static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge 
>> *bridge)
>> +{
>> +	return container_of(bridge, struct ti_sn_bridge, bridge);
>> +}
>> +
>> +static int ti_sn_bridge_read_device_rev(struct ti_sn_bridge *pdata)
>> +{
>> +	u8 rev = 0;
>> +	int ret = 0;
>> +
>> +	ret = ti_sn_bridge_read(pdata, SN_DEVICE_REV_REG, &rev, 1);
>> +	if (ret)
>> +		goto exit;
>> +
>> +	if (rev == SN_BRIDGE_REVISION_ID) {
>> +		DRM_DEBUG("ti_sn_bridge revision id: 0x%x\n", rev);
>> +	} else {
>> +		DRM_ERROR("ti_sn_bridge revision id mismatch\n");
>> +		ret = -EINVAL;
>> +	}
>> +
>> +exit:
>> +	return ret;
>> +}
>> +
>> +static const char * const ti_sn_bridge_supply_names[] = {
>> +	"vccio",
>> +	"vcca",
>> +};
>> +
>> +static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
>> +{
>> +	unsigned int i;
>> +	int ret;
>> +
>> +	pdata->num_supplies = ARRAY_SIZE(ti_sn_bridge_supply_names);
>> +
>> +	pdata->supplies = devm_kcalloc(pdata->dev, pdata->num_supplies,
>> +				     sizeof(*pdata->supplies), GFP_KERNEL);
>> +	if (!pdata->supplies)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < pdata->num_supplies; i++)
>> +		pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
>> +
>> +	ret = devm_regulator_bulk_get(pdata->dev,
>> +			pdata->num_supplies, pdata->supplies);
>> +
>> +	return ret;
>> +}
>> +
>> +static void ti_sn_bridge_attach_panel(struct ti_sn_bridge *pdata)
>> +{
>> +	struct device_node *panel_node, *port, *endpoint;
>> +	struct drm_panel *panel = NULL;
>> +
>> +	port = of_graph_get_port_by_id(pdata->dev->of_node, 1);
>> +	if (port) {
>> +		endpoint = of_get_child_by_name(port, "endpoint");
>> +		of_node_put(port);
>> +		if (!endpoint) {
>> +			dev_err(pdata->dev, "no output endpoint found\n");
>> +			goto error;
>> +		}
>> +
>> +		panel_node = of_graph_get_remote_port_parent(endpoint);
>> +		of_node_put(endpoint);
>> +		if (!panel_node) {
>> +			dev_err(pdata->dev, "no output node found\n");
>> +			goto error;
>> +		}
>> +
>> +		panel = of_drm_find_panel(panel_node);
>> +		of_node_put(panel_node);
>> +		if (!panel) {
>> +			dev_err(pdata->dev, "no panel node found\n");
>> +			goto error;
>> +		}
>> +	}
>> +
>> +	pdata->panel = panel;
>> +	drm_panel_attach(panel, &pdata->connector);
>> +
>> +	return;
>> +
>> +error:
>> +	pdata->panel = NULL;
>> +}
>> +
>> +static int ti_sn_bridge_attach(struct drm_bridge *bridge)
>> +{
>> +	struct mipi_dsi_host *host;
>> +	struct mipi_dsi_device *dsi;
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	int ret;
>> +	const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
>> +						   .channel = 0,
>> +						   .node = NULL,
>> +						 };
>> +
>> +	if (!bridge->encoder) {
>> +		DRM_ERROR("Parent encoder object not found");
>> +		return -ENODEV;
>> +	}
>> +
>> +	/* HPD not supported */
>> +	pdata->connector.polled = 0;
>> +
>> +	ret = drm_connector_init(bridge->dev, &pdata->connector,
>> +				 &ti_sn_bridge_connector_funcs,
>> +				 DRM_MODE_CONNECTOR_eDP);
>> +	if (ret) {
>> +		DRM_ERROR("Failed to initialize connector with drm\n");
>> +		return ret;
>> +	}
>> +
>> +	drm_connector_helper_add(&pdata->connector,
>> +				 &ti_sn_bridge_connector_helper_funcs);
>> +	drm_mode_connector_attach_encoder(&pdata->connector, 
>> bridge->encoder);
>> +
>> +	host = of_find_mipi_dsi_host_by_node(pdata->host_node);
>> +	if (!host) {
>> +		DRM_ERROR("failed to find dsi host\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	dsi = mipi_dsi_device_register_full(host, &info);
>> +	if (IS_ERR(dsi)) {
>> +		DRM_ERROR("failed to create dsi device\n");
>> +		ret = PTR_ERR(dsi);
>> +		goto err_dsi_device;
>> +	}
>> +
>> +	/* TODO: setting to 4 lanes always for now */
>> +	dsi->lanes = 4;
>> +	dsi->format = MIPI_DSI_FMT_RGB888;
>> +	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | 
>> MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
>> +			  MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
>> +
>> +	ret = mipi_dsi_attach(dsi);
>> +	if (ret < 0) {
>> +		DRM_ERROR("failed to attach dsi to host\n");
>> +		goto err_dsi_attach;
>> +	}
>> +
>> +	pdata->dsi = dsi;
>> +
>> +	DRM_DEBUG("bridge attached\n");
>> +	/* attach panel to bridge */
>> +	ti_sn_bridge_attach_panel(pdata);
>> +
>> +	return 0;
>> +
>> +err_dsi_attach:
>> +	mipi_dsi_device_unregister(dsi);
>> +err_dsi_device:
>> +	return ret;
>> +}
>> +
>> +static void ti_sn_bridge_mode_set(struct drm_bridge *bridge,
>> +				    struct drm_display_mode *mode,
>> +				    struct drm_display_mode *adj_mode)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +
>> +	DRM_DEBUG("mode_set: hdisplay=%d, vdisplay=%d, vrefresh=%d, 
>> clock=%d\n",
>> +		adj_mode->hdisplay, adj_mode->vdisplay,
>> +		adj_mode->vrefresh, adj_mode->clock);
>> +
>> +	drm_mode_copy(&pdata->curr_mode, adj_mode);
>> +}
>> +
>> +static void ti_sn_bridge_disable(struct drm_bridge *bridge)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	struct drm_panel *panel = pdata->panel;
>> +
>> +	if (panel) {
>> +		drm_panel_disable(panel);
>> +		drm_panel_unprepare(panel);
>> +	}
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +}
>> +
>> +static void ti_sn_bridge_enable(struct drm_bridge *bridge)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	struct drm_panel *panel = pdata->panel;
>> +	struct drm_display_mode *mode = &pdata->curr_mode;
>> +	u32 bit_rate_mhz, clk_freq_mhz;
>> +	u8 val = 0;
>> +
>> +	if (!pdata->num_modes)
>> +		return;
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, true);
>> +
>> +	if (panel)
>> +		drm_panel_prepare(panel);
>> +
>> +	/* CLK_SRC config */
>> +	ti_sn_bridge_write(pdata, SN_REFCLK_FREQ_REG,
>> +			(DPPLL_CLK_SRC_REFCLK | (SN_REF_CLK_19_2_MHZ << 0x1)));
>> +
>> +	/* DSI_A lane config */
>> +	ti_sn_bridge_read(pdata, SN_DSI_LANES_REG, &val, 0x1);
>> +	val |= ((0x4 - pdata->dsi->lanes) & 0x03) << 0x3;
>> +	ti_sn_bridge_write(pdata, SN_DSI_LANES_REG, val);
>> +
>> +	/* DP lane config */
>> +	ti_sn_bridge_read(pdata, SN_SSC_CONFIG_REG, &val, 0x1);
>> +	val |= ((pdata->dsi->lanes - 0x1) & 0x03) << 0x4;
>> +	ti_sn_bridge_write(pdata, SN_SSC_CONFIG_REG, val);
>> +
>> +	/* set dsi clk frequency value */
>> +	bit_rate_mhz = (mode->clock / 1000) *
>> +			mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
>> +	clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
>> +	/* for each increment in val, frequency increases by 5MHz */
>> +	val = (MIN_DSI_CLK_FREQ_MHZ / 0x5) +
>> +		(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 0x5) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_DSIA_CLK_FREQ_REG, val);
>> +
>> +	ti_sn_bridge_write(pdata, SN_DATARATE_CONFIG_REG, 0x80); /* set HBR 
>> */
>> +
>> +	ti_sn_bridge_write(pdata, SN_PLL_ENABLE_REG, 0x1); /* Enable PLL */
>> +	usleep_range(10000, 10001);
>> +
>> +	/* DPCD Register 0x0010A in Sink */
>> +	ti_sn_bridge_write(pdata, SN_AUX_WDATA0_REG, 0x01);
>> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_19_16_REG, 0x00);
>> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_15_8_REG, 0x01);
>> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_7_0_REG, 0x0A);
>> +	ti_sn_bridge_write(pdata, SN_AUX_LENGTH_REG, 0x01);
>> +	ti_sn_bridge_write(pdata, SN_AUX_CMD_REG, 0x81);
>> +	usleep_range(10000, 10001);
>> +
>> +	/* Semi auto link training mode */
>> +	ti_sn_bridge_write(pdata, SN_ML_TX_MODE_REG, 0x0A);
>> +	usleep_range(20000, 20001);
>> +
>> +	/* config video parameters */
>> +	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
>> +			mode->hdisplay & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG,
>> +			(mode->hdisplay >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
>> +			mode->vdisplay & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG,
>> +			(mode->hdisplay >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
>> +			(mode->htotal - mode->hsync_end) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
>> +			((mode->htotal - mode->hsync_end) >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
>> +			(mode->vtotal - mode->vsync_end) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
>> +			((mode->vtotal - mode->vsync_end) >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
>> +			(mode->hsync_start - mode->hdisplay) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_BACK_PORCH_REG,
>> +			(mode->vsync_start - mode->vdisplay) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
>> +			(mode->hsync_end - mode->hsync_start) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_FRONT_PORCH_REG,
>> +			(mode->vsync_end - mode->vsync_start) & 0xFF);
>> +	if (pdata->dsi->format == MIPI_DSI_FMT_RGB888)
>> +		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x00);
>> +	else
>> +		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x01);
>> +	usleep_range(10000, 10001);
>> +
>> +	/* enable video stream */
>> +	ti_sn_bridge_read(pdata, SN_ENH_FRAME_REG, &val, 0x1);
>> +	val |= BIT(3);
>> +	ti_sn_bridge_write(pdata, SN_ENH_FRAME_REG, val);
>> +
>> +	if (panel)
>> +		drm_panel_enable(panel);
> 
> As mentioned in the previous review, you should configure the mode in 
> mode_set,
> not enable. This eliminates the need for curr_mode.

If we move this to mode_set then it does not match with the datasheet 
init sequence
provided in page 71. First mode_set is called in commit which will set 
the current mode.
after that when bridge enable gets called gpio and regulator enable and 
then the bridge registers
needs to be programmed based on current mode.

> 
>> +}
>> +
>> +static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
>> +	.attach = ti_sn_bridge_attach,
>> +	.enable = ti_sn_bridge_enable,
>> +	.disable = ti_sn_bridge_disable,
>> +	.mode_set = ti_sn_bridge_mode_set,
>> +};
>> +
>> +static int ti_sn_bridge_parse_gpios(struct ti_sn_bridge *pdata)
>> +{
>> +	int ret = 0;
>> +
>> +	if (!pdata || !pdata->dev)
>> +		return -EINVAL;
>> +
>> +	pdata->enable_gpios = devm_gpiod_get_array(pdata->dev,
>> +						"enable", GPIOD_OUT_LOW);
>> +	if (IS_ERR(pdata->enable_gpios)) {
>> +		DRM_ERROR("failed to get enable gpio from DT\n");
>> +		ret = PTR_ERR(pdata->enable_gpios);
>> +		goto exit;
>> +	}
>> +
>> +	pdata->irq_gpio = devm_gpiod_get_optional(pdata->dev, "irq",
>> +						  GPIOD_OUT_LOW);
>> +	if (IS_ERR(pdata->irq_gpio))
>> +		DRM_ERROR("failed to get irq gpio from DT\n");
>> +
>> +exit:
>> +	return ret;
>> +}
>> +
>> +static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
>> +{
>> +	struct device_node *np = pdata->dev->of_node;
>> +	struct device_node *end_node;
>> +
>> +	end_node = of_graph_get_endpoint_by_regs(np, 0, 0);
>> +	if (!end_node) {
>> +		DRM_ERROR("remote endpoint not found\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	pdata->host_node = of_graph_get_remote_port_parent(end_node);
>> +	of_node_put(end_node);
>> +	if (!pdata->host_node) {
>> +		DRM_ERROR("remote node not found\n");
>> +		return -ENODEV;
>> +	}
>> +	of_node_put(pdata->host_node);
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_sn_bridge_probe(struct i2c_client *client,
>> +	 const struct i2c_device_id *id)
>> +{
>> +	struct ti_sn_bridge *pdata;
>> +	struct device_node *ddc_node;
>> +	int ret = 0;
>> +
>> +	if (!client || !client->dev.of_node) {
>> +		DRM_ERROR("invalid input\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
>> +		DRM_ERROR("device doesn't support I2C\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	pdata = devm_kzalloc(&client->dev,
>> +		sizeof(struct ti_sn_bridge), GFP_KERNEL);
>> +	if (!pdata)
>> +		return -ENOMEM;
>> +
>> +	pdata->power_on = false;
>> +	pdata->dev = &client->dev;
>> +	pdata->i2c_client = client;
>> +	DRM_DEBUG("I2C address is %x\n", client->addr);
>> +
>> +	ret = ti_sn_bridge_parse_dsi_host(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to parse device tree\n");
>> +		goto err_dt_parse;
>> +	}
>> +
>> +	ret = ti_sn_bridge_parse_gpios(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to parse gpios from DT\n");
>> +		goto err_dt_parse;
>> +	}
>> +
>> +	ret = ti_sn_bridge_parse_regulators(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to parse regulators\n");
>> +		goto err_dt_parse;
>> +	}
>> +
>> +	ddc_node = of_parse_phandle(pdata->dev->of_node, "ddc-i2c-bus", 0);
>> +	if (ddc_node) {
>> +		pdata->ddc = of_find_i2c_adapter_by_node(ddc_node);
>> +		of_node_put(ddc_node);
>> +		if (!pdata->ddc) {
>> +			dev_dbg(pdata->dev, "failed to read ddc node\n");
>> +			return -EPROBE_DEFER;
>> +		}
>> +	} else {
>> +		dev_dbg(pdata->dev, "no ddc property found\n");
>> +	}
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, true);
>> +	ret = ti_sn_bridge_read_device_rev(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to read chip rev\n");
>> +		goto err_config;
>> +	} else {
>> +		DRM_DEBUG("bridge chip enabled successfully\n");
>> +	}
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +
>> +	i2c_set_clientdata(client, pdata);
>> +	dev_set_drvdata(&client->dev, pdata);
>> +
>> +	pdata->bridge.funcs = &ti_sn_bridge_funcs;
>> +	pdata->bridge.of_node = client->dev.of_node;
>> +
>> +	drm_bridge_add(&pdata->bridge);
>> +
>> +	return ret;
>> +
>> +err_config:
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +err_dt_parse:
>> +	devm_kfree(&client->dev, pdata);
>> +	return ret;
>> +}
>> +
>> +static int ti_sn_bridge_remove(struct i2c_client *client)
>> +{
>> +	int ret = -EINVAL;
>> +	struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
>> +
>> +	if (!pdata)
>> +		goto end;
>> +
>> +	mipi_dsi_detach(pdata->dsi);
>> +	mipi_dsi_device_unregister(pdata->dsi);
>> +
>> +	drm_bridge_remove(&pdata->bridge);
>> +
>> +	disable_irq(pdata->irq);
>> +	free_irq(pdata->irq, pdata);
> 
> This is uninitialized.
since irq is anyways not used in this driver yet so i will remove this 
part.
> 
>> +
>> +	ti_sn_bridge_gpio_configure(pdata, false);
>> +	i2c_put_adapter(pdata->ddc);
>> +
>> +	devm_kfree(&client->dev, pdata);
>> +
>> +end:
>> +	return ret;
>> +}
>> +
>> +static struct i2c_device_id ti_sn_bridge_id[] = {
>> +	{ "ti,sn65dsi86", 0},
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
>> +
>> +static const struct of_device_id ti_sn_bridge_match_table[] = {
>> +	{.compatible = "ti,sn65dsi86"},
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
>> +
>> +static struct i2c_driver ti_sn_bridge_driver = {
>> +	.driver = {
>> +		.name = "ti_sn65dsi86",
>> +		.owner = THIS_MODULE,
>> +		.of_match_table = ti_sn_bridge_match_table,
>> +	},
>> +	.probe = ti_sn_bridge_probe,
>> +	.remove = ti_sn_bridge_remove,
>> +	.id_table = ti_sn_bridge_id,
>> +};
>> +
>> +module_i2c_driver(ti_sn_bridge_driver);
>> +MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
>> --
>> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora 
>> Forum,
>> a Linux Foundation Collaborative Project
>> 
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp panel driver
  2018-04-18 14:12   ` [[RFC]DPU PATCH 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp panel driver Sean Paul
@ 2018-04-18 18:36     ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  0 siblings, 0 replies; 21+ messages in thread
From: spanda-sgV2jX0FEOL9JmXXK+q4OQ @ 2018-04-18 18:36 UTC (permalink / raw)
  To: Sean Paul
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, ryadav-sgV2jX0FEOL9JmXXK+q4OQ,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	jsanka-sgV2jX0FEOL9JmXXK+q4OQ, chandanu-sgV2jX0FEOL9JmXXK+q4OQ

On 2018-04-18 19:42, Sean Paul wrote:
> On Wed, Apr 18, 2018 at 05:49:58PM +0530, Sandeep Panda wrote:
>> Changelog:
>> 
>> v3 -> v4:
> 
> I didn't really bother to do a thorough review given that there are 
> obvious
> mistakes and ignored feedback, it's a waste of time, tbh. Please go 
> through
> the previous reviews and resend a more polished version.

I have replied to your review comments. Please review the edp driver, so 
that
i can address all the comments together while uploading next patchset.
> 
> Sean
> 
>> Current patchset separates out eDP panel specific resources from 
>> sn65dsi86
>> bridge driver and creates a separate driver for the innloux edp panel.
>> 
>> Now bridge driver check if any panel is attached or not to get the 
>> supported
>> modes. If any panel is attached then query from panel driver to get 
>> the modes,
>> or else probe the provided i2c adapter to read the modes from EDID.
>> 
>> Removed hardcoding of bridge init sequence. With this patchset bridge 
>> driver now
>> will program the init sequence based on the current mode set by drm 
>> framework.
>> 
>> Current patchset is not tested on actual bridge chip/panel.
>> 
>> Sandeep Panda (4):
>>   drm/bridge: add support for sn65dsi86 bridge driver
>>   dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings
>>   drm/panel: add Innolux TV123WAM eDP panel driver
>>   dt-bindings: Add Innolux TV123WAM panel bindings
>> 
>>  .../bindings/display/bridge/ti,sn65dsi86.txt       |  60 ++
>>  .../display/panel/innolux,edp-2k-panel.txt         |  21 +
>>  drivers/gpu/drm/bridge/ti-sn65dsi86.c              | 742 
>> +++++++++++++++++++++
>>  drivers/gpu/drm/panel/panel-innolux-tv123wam.c     | 299 +++++++++
>>  4 files changed, 1122 insertions(+)
>>  create mode 100644 
>> Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
>>  create mode 100644 
>> Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
>>  create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
>>  create mode 100644 drivers/gpu/drm/panel/panel-innolux-tv123wam.c
>> 
>> --
>> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora 
>> Forum,
>> a Linux Foundation Collaborative Project
>> 
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver
  2018-04-18 14:11       ` Sean Paul
  2018-04-18 18:31         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
@ 2018-04-19  4:37         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  1 sibling, 0 replies; 21+ messages in thread
From: spanda-sgV2jX0FEOL9JmXXK+q4OQ @ 2018-04-19  4:37 UTC (permalink / raw)
  To: Sean Paul
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, ryadav-sgV2jX0FEOL9JmXXK+q4OQ,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	jsanka-sgV2jX0FEOL9JmXXK+q4OQ, chandanu-sgV2jX0FEOL9JmXXK+q4OQ

On 2018-04-18 19:41, Sean Paul wrote:
> On Wed, Apr 18, 2018 at 05:49:59PM +0530, Sandeep Panda wrote:
>> Add support for TI's sn65dsi86 dsi2edp bridge chip.
>> The chip converts DSI transmitted signal to eDP signal,
>> which is fed to the connected eDP panel.
>> 
>> This chip can be controlled via either i2c interface or
>> dsi interface. Currently in driver all the control registers
>> are being accessed through i2c interface only.
>> Also as of now HPD support has not been added to bridge
>> chip driver.
>> 
>> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
>> ---
>>  drivers/gpu/drm/bridge/ti-sn65dsi86.c | 742 
>> ++++++++++++++++++++++++++++++++++
>>  1 file changed, 742 insertions(+)
>>  create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> 
>> diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c 
>> b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> new file mode 100644
>> index 0000000..c798f2f
>> --- /dev/null
>> +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> @@ -0,0 +1,742 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +
>> +#include <linux/types.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/init.h>
>> +#include <linux/device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/delay.h>
>> +#include <linux/i2c.h>
>> +#include <linux/gpio.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/of_graph.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
> 
> I have a hard time believing that you need all of these includes. Off 
> the top of
> my head, you could probably remove types, kernel, init, 
> platform_device, delay,
> drmP, drm_atomic, drm_crtc_helper. You could probably trim it even 
> further with
> the help of your compiler. These should also be in alphabetical order.
> 
>> +
>> +#define SN_BRIDGE_REVISION_ID 0x2
>> +
>> +/* Link Training specific registers */
>> +#define SN_DEVICE_REV_REG			0x08
>> +#define SN_REFCLK_FREQ_REG			0x0A
>> +#define SN_DSI_LANES_REG			0x10
>> +#define SN_DSIA_CLK_FREQ_REG			0x12
>> +#define SN_ENH_FRAME_REG			0x5A
>> +#define SN_SSC_CONFIG_REG			0x93
>> +#define SN_DATARATE_CONFIG_REG			0x94
>> +#define SN_PLL_ENABLE_REG			0x0D
>> +#define SN_SCRAMBLE_CONFIG_REG			0x95
>> +#define SN_AUX_WDATA0_REG			0x64
>> +#define SN_AUX_ADDR_19_16_REG			0x74
>> +#define SN_AUX_ADDR_15_8_REG			0x75
>> +#define SN_AUX_ADDR_7_0_REG			0x76
>> +#define SN_AUX_LENGTH_REG			0x77
>> +#define SN_AUX_CMD_REG				0x78
>> +#define SN_ML_TX_MODE_REG			0x96
>> +#define SN_PAGE_SELECT_REG			0xFF
>> +#define SN_AUX_RDATA0_REG			0x79
>> +/* video config specific registers */
>> +#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG	0x20
>> +#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG	0x21
>> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG	0x24
>> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG	0x25
>> +#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG	0x2C
>> +#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG	0x2D
>> +#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG	0x30
>> +#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG	0x31
>> +#define SN_CHA_HORIZONTAL_BACK_PORCH_REG	0x34
>> +#define SN_CHA_VERTICAL_BACK_PORCH_REG		0x36
>> +#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG	0x38
>> +#define SN_CHA_VERTICAL_FRONT_PORCH_REG		0x3A
>> +#define SN_DATA_FORMAT_REG			0x5B
>> +#define SN_COLOR_BAR_CONFIG_REG			0x3C
>> +
>> +#define DPPLL_CLK_SRC_REFCLK	0
>> +#define DPPLL_CLK_SRC_DSICLK	1
>> +#define MIN_DSI_CLK_FREQ_MHZ	40
>> +
>> +enum ti_sn_bridge_ref_clks {
>> +	SN_REF_CLK_12_MHZ = 0,
>> +	SN_REF_CLK_19_2_MHZ,
>> +	SN_REF_CLK_26_MHZ,
>> +	SN_REF_CLK_27_MHZ,
>> +	SN_REF_CLK_38_4_MHZ,
>> +	SN_REF_CLK_MAX,
>> +};
>> +
>> +struct ti_sn_bridge {
>> +	struct device *dev;
>> +	struct drm_bridge bridge;
>> +	struct drm_connector connector;
>> +	struct device_node *host_node;
>> +	struct mipi_dsi_device *dsi;
>> +	/* handle to the connected eDP panel */
>> +	struct drm_panel *panel;
>> +	int irq;
>> +	struct gpio_desc *irq_gpio;
>> +	/* list of gpios needed to enable the bridge functionality */
>> +	struct gpio_descs *enable_gpios;
> 
> Why are the enable gpios a list?

This as per the review comment in previous patchset
"I see IRQ and EN in the datasheet, but not the others. It seems like 
the aux_*
and edp_* gpios are always equal to en. So if you actually need them, 
don't
specify a new struct, just add irq_gpio to the main struct and add an 
array of
en_gpios to handle the rest."

I see in schematic 2 more gpios are needed to enable aux_channel 
communication
through the ddc. So i have put an array of enable gpios. Based on dt it 
will dynamically
parse one or more gpios.

> 
>> +	unsigned int num_supplies;
>> +	struct regulator_bulk_data *supplies;
>> +	struct i2c_client *i2c_client;
>> +	struct i2c_adapter *ddc;
>> +	bool power_on;
>> +	u32 num_modes;
>> +	struct drm_display_mode curr_mode;
>> +};
>> +
>> +static int ti_sn_bridge_write(struct ti_sn_bridge *pdata, u8 reg, u8 
>> val)
>> +{
>> +	struct i2c_client *client = pdata->i2c_client;
>> +	u8 buf[2] = {reg, val};
>> +	struct i2c_msg msg = {
>> +		.addr = client->addr,
>> +		.flags = 0,
>> +		.len = 2,
>> +		.buf = buf,
>> +	};
>> +
>> +	if (i2c_transfer(client->adapter, &msg, 1) < 1) {
>> +		DRM_ERROR("i2c write failed\n");
>> +		return -EIO;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_sn_bridge_read(struct ti_sn_bridge *pdata,
>> +					u8 reg, char *buf, u32 size)
>> +{
>> +	struct i2c_client *client = pdata->i2c_client;
>> +	struct i2c_msg msg[2] = {
>> +		{
>> +			.addr = client->addr,
>> +			.flags = 0,
>> +			.len = 1,
>> +			.buf = &reg,
>> +		},
>> +		{
>> +			.addr = client->addr,
>> +			.flags = I2C_M_RD,
>> +			.len = size,
>> +			.buf = buf,
>> +		}
>> +	};
>> +
>> +	if (i2c_transfer(client->adapter, msg, 2) != 2) {
>> +		DRM_ERROR("i2c read failed\n");
>> +		return -EIO;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void ti_sn_bridge_gpio_configure(struct ti_sn_bridge *pdata, 
>> bool on)
>> +{
>> +	int value, i = 0, num = 0;
>> +	int *value_array = NULL;
>> +
>> +	if (!pdata)
>> +		return;
>> +
>> +	value = on ? 1 : 0;
>> +	num = pdata->enable_gpios->ndescs;
>> +	value_array = kmalloc(num * sizeof(int), GFP_KERNEL);
>> +	if (value_array) {
>> +		for (i = 0; i < num; i++)
>> +			value_array[i] = value;
>> +		gpiod_set_array_value(num, pdata->enable_gpios->desc,
>> +				      value_array);
>> +		kfree(value_array);
>> +	}
>> +
>> +	if (pdata->irq_gpio)
>> +		gpiod_set_value(pdata->irq_gpio, value);
>> +
>> +	DRM_DEBUG("config to: %d\n", value);
>> +}
>> +
>> +static void ti_sn_bridge_power_ctrl(struct ti_sn_bridge *pdata, bool 
>> enable)
>> +{
>> +	static int ref_count;
>> +
>> +	if (!pdata)
>> +		return;
>> +
>> +	if (enable)
>> +		ref_count++;
>> +	else
>> +		ref_count--;
>> +
>> +	if (enable && (ref_count == 1)) {
>> +		ti_sn_bridge_gpio_configure(pdata, true);
>> +
>> +		if (regulator_bulk_enable(pdata->num_supplies,
>> +						pdata->supplies)) {
>> +			DRM_ERROR("bridge regulator enable failed\n");
>> +			return;
>> +		}
>> +		pdata->power_on = true;
>> +	} else if (!enable && !ref_count) {
>> +		regulator_bulk_disable(pdata->num_supplies, pdata->supplies);
>> +
>> +		ti_sn_bridge_gpio_configure(pdata, false);
>> +		pdata->power_on = false;
>> +	} else {
>> +		DRM_DEBUG("ti_sn_bridge power ctrl: %d refcount: %d\n",
>> +							enable, ref_count);
>> +	}
>> +}
>> +
>> +/* Connector funcs */
>> +static struct ti_sn_bridge *
>> +connector_to_ti_sn_bridge(struct drm_connector *connector)
>> +{
>> +	return container_of(connector, struct ti_sn_bridge, connector);
>> +}
>> +
>> +static int ti_sn_bridge_connector_get_modes(struct drm_connector 
>> *connector)
>> +{
>> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
>> +	struct drm_panel *panel = pdata->panel;
>> +	struct edid *edid;
>> +
>> +	if (panel) {
>> +		DRM_DEBUG("get mode from connected drm_panel\n");
>> +		pdata->num_modes = drm_panel_get_modes(panel);
>> +	} else {
>> +		/* get from EDID */
>> +		if (!pdata->ddc)
>> +			return 0;
>> +		ti_sn_bridge_power_ctrl(pdata, true);
>> +		edid = drm_get_edid(connector, pdata->ddc);
>> +		if (edid) {
>> +			drm_mode_connector_update_edid_property(connector,
>> +								edid);
>> +			pdata->num_modes = drm_add_edid_modes(connector, edid);
>> +			drm_edid_to_eld(connector, edid);
>> +			kfree(edid);
>> +		} else {
>> +			DRM_DEBUG("failed to get edid\n");
>> +		}
>> +		ti_sn_bridge_power_ctrl(pdata, false);
>> +	}
>> +
>> +	return pdata->num_modes;
>> +}
>> +
>> +static enum drm_mode_status
>> +ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
>> +			     struct drm_display_mode *mode)
>> +{
>> +	/* maximum supported resolution is 4K at 60 fps */
>> +	if (mode->clock > 594000)
>> +		return MODE_CLOCK_HIGH;
>> +
>> +	return MODE_OK;
>> +}
>> +
>> +static struct drm_connector_helper_funcs 
>> ti_sn_bridge_connector_helper_funcs = {
>> +	.get_modes = ti_sn_bridge_connector_get_modes,
>> +	.mode_valid = ti_sn_bridge_connector_mode_valid,
>> +};
>> +
>> +static enum drm_connector_status
>> +ti_sn_bridge_connector_detect(struct drm_connector *connector, bool 
>> force)
>> +{
>> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
>> +
>> +	/**
>> +	 * TODO: Currently if drm_panel is present, then always
>> +	 * return the status as connected. Need to add support to detect
>> +	 * device state for no panel(hot pluggable) scenarios.
>> +	 */
>> +	if (pdata->panel)
>> +		return connector_status_connected;
>> +	else
>> +		return connector_status_disconnected;
>> +}
>> +
>> +static const struct drm_connector_funcs ti_sn_bridge_connector_funcs 
>> = {
>> +	.fill_modes = drm_helper_probe_single_connector_modes,
>> +	.detect = ti_sn_bridge_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,
>> +};
>> +
>> +static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge 
>> *bridge)
>> +{
>> +	return container_of(bridge, struct ti_sn_bridge, bridge);
>> +}
>> +
>> +static int ti_sn_bridge_read_device_rev(struct ti_sn_bridge *pdata)
>> +{
>> +	u8 rev = 0;
>> +	int ret = 0;
>> +
>> +	ret = ti_sn_bridge_read(pdata, SN_DEVICE_REV_REG, &rev, 1);
>> +	if (ret)
>> +		goto exit;
>> +
>> +	if (rev == SN_BRIDGE_REVISION_ID) {
>> +		DRM_DEBUG("ti_sn_bridge revision id: 0x%x\n", rev);
>> +	} else {
>> +		DRM_ERROR("ti_sn_bridge revision id mismatch\n");
>> +		ret = -EINVAL;
>> +	}
>> +
>> +exit:
>> +	return ret;
>> +}
>> +
>> +static const char * const ti_sn_bridge_supply_names[] = {
>> +	"vccio",
>> +	"vcca",
>> +};
>> +
>> +static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
>> +{
>> +	unsigned int i;
>> +	int ret;
>> +
>> +	pdata->num_supplies = ARRAY_SIZE(ti_sn_bridge_supply_names);
>> +
>> +	pdata->supplies = devm_kcalloc(pdata->dev, pdata->num_supplies,
>> +				     sizeof(*pdata->supplies), GFP_KERNEL);
>> +	if (!pdata->supplies)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < pdata->num_supplies; i++)
>> +		pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
>> +
>> +	ret = devm_regulator_bulk_get(pdata->dev,
>> +			pdata->num_supplies, pdata->supplies);
>> +
>> +	return ret;
>> +}
>> +
>> +static void ti_sn_bridge_attach_panel(struct ti_sn_bridge *pdata)
>> +{
>> +	struct device_node *panel_node, *port, *endpoint;
>> +	struct drm_panel *panel = NULL;
>> +
>> +	port = of_graph_get_port_by_id(pdata->dev->of_node, 1);
>> +	if (port) {
>> +		endpoint = of_get_child_by_name(port, "endpoint");
>> +		of_node_put(port);
>> +		if (!endpoint) {
>> +			dev_err(pdata->dev, "no output endpoint found\n");
>> +			goto error;
>> +		}
>> +
>> +		panel_node = of_graph_get_remote_port_parent(endpoint);
>> +		of_node_put(endpoint);
>> +		if (!panel_node) {
>> +			dev_err(pdata->dev, "no output node found\n");
>> +			goto error;
>> +		}
>> +
>> +		panel = of_drm_find_panel(panel_node);
>> +		of_node_put(panel_node);
>> +		if (!panel) {
>> +			dev_err(pdata->dev, "no panel node found\n");
>> +			goto error;
>> +		}
>> +	}
>> +
>> +	pdata->panel = panel;
>> +	drm_panel_attach(panel, &pdata->connector);
>> +
>> +	return;
>> +
>> +error:
>> +	pdata->panel = NULL;
>> +}
>> +
>> +static int ti_sn_bridge_attach(struct drm_bridge *bridge)
>> +{
>> +	struct mipi_dsi_host *host;
>> +	struct mipi_dsi_device *dsi;
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	int ret;
>> +	const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
>> +						   .channel = 0,
>> +						   .node = NULL,
>> +						 };
>> +
>> +	if (!bridge->encoder) {
>> +		DRM_ERROR("Parent encoder object not found");
>> +		return -ENODEV;
>> +	}
>> +
>> +	/* HPD not supported */
>> +	pdata->connector.polled = 0;
>> +
>> +	ret = drm_connector_init(bridge->dev, &pdata->connector,
>> +				 &ti_sn_bridge_connector_funcs,
>> +				 DRM_MODE_CONNECTOR_eDP);
>> +	if (ret) {
>> +		DRM_ERROR("Failed to initialize connector with drm\n");
>> +		return ret;
>> +	}
>> +
>> +	drm_connector_helper_add(&pdata->connector,
>> +				 &ti_sn_bridge_connector_helper_funcs);
>> +	drm_mode_connector_attach_encoder(&pdata->connector, 
>> bridge->encoder);
>> +
>> +	host = of_find_mipi_dsi_host_by_node(pdata->host_node);
>> +	if (!host) {
>> +		DRM_ERROR("failed to find dsi host\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	dsi = mipi_dsi_device_register_full(host, &info);
>> +	if (IS_ERR(dsi)) {
>> +		DRM_ERROR("failed to create dsi device\n");
>> +		ret = PTR_ERR(dsi);
>> +		goto err_dsi_device;
>> +	}
>> +
>> +	/* TODO: setting to 4 lanes always for now */
>> +	dsi->lanes = 4;
>> +	dsi->format = MIPI_DSI_FMT_RGB888;
>> +	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | 
>> MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
>> +			  MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
>> +
>> +	ret = mipi_dsi_attach(dsi);
>> +	if (ret < 0) {
>> +		DRM_ERROR("failed to attach dsi to host\n");
>> +		goto err_dsi_attach;
>> +	}
>> +
>> +	pdata->dsi = dsi;
>> +
>> +	DRM_DEBUG("bridge attached\n");
>> +	/* attach panel to bridge */
>> +	ti_sn_bridge_attach_panel(pdata);
>> +
>> +	return 0;
>> +
>> +err_dsi_attach:
>> +	mipi_dsi_device_unregister(dsi);
>> +err_dsi_device:
>> +	return ret;
>> +}
>> +
>> +static void ti_sn_bridge_mode_set(struct drm_bridge *bridge,
>> +				    struct drm_display_mode *mode,
>> +				    struct drm_display_mode *adj_mode)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +
>> +	DRM_DEBUG("mode_set: hdisplay=%d, vdisplay=%d, vrefresh=%d, 
>> clock=%d\n",
>> +		adj_mode->hdisplay, adj_mode->vdisplay,
>> +		adj_mode->vrefresh, adj_mode->clock);
>> +
>> +	drm_mode_copy(&pdata->curr_mode, adj_mode);
>> +}
>> +
>> +static void ti_sn_bridge_disable(struct drm_bridge *bridge)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	struct drm_panel *panel = pdata->panel;
>> +
>> +	if (panel) {
>> +		drm_panel_disable(panel);
>> +		drm_panel_unprepare(panel);
>> +	}
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +}
>> +
>> +static void ti_sn_bridge_enable(struct drm_bridge *bridge)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	struct drm_panel *panel = pdata->panel;
>> +	struct drm_display_mode *mode = &pdata->curr_mode;
>> +	u32 bit_rate_mhz, clk_freq_mhz;
>> +	u8 val = 0;
>> +
>> +	if (!pdata->num_modes)
>> +		return;
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, true);
>> +
>> +	if (panel)
>> +		drm_panel_prepare(panel);
>> +
>> +	/* CLK_SRC config */
>> +	ti_sn_bridge_write(pdata, SN_REFCLK_FREQ_REG,
>> +			(DPPLL_CLK_SRC_REFCLK | (SN_REF_CLK_19_2_MHZ << 0x1)));
>> +
>> +	/* DSI_A lane config */
>> +	ti_sn_bridge_read(pdata, SN_DSI_LANES_REG, &val, 0x1);
>> +	val |= ((0x4 - pdata->dsi->lanes) & 0x03) << 0x3;
>> +	ti_sn_bridge_write(pdata, SN_DSI_LANES_REG, val);
>> +
>> +	/* DP lane config */
>> +	ti_sn_bridge_read(pdata, SN_SSC_CONFIG_REG, &val, 0x1);
>> +	val |= ((pdata->dsi->lanes - 0x1) & 0x03) << 0x4;
>> +	ti_sn_bridge_write(pdata, SN_SSC_CONFIG_REG, val);
>> +
>> +	/* set dsi clk frequency value */
>> +	bit_rate_mhz = (mode->clock / 1000) *
>> +			mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
>> +	clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
>> +	/* for each increment in val, frequency increases by 5MHz */
>> +	val = (MIN_DSI_CLK_FREQ_MHZ / 0x5) +
>> +		(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 0x5) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_DSIA_CLK_FREQ_REG, val);
>> +
>> +	ti_sn_bridge_write(pdata, SN_DATARATE_CONFIG_REG, 0x80); /* set HBR 
>> */
>> +
>> +	ti_sn_bridge_write(pdata, SN_PLL_ENABLE_REG, 0x1); /* Enable PLL */
>> +	usleep_range(10000, 10001);
>> +
>> +	/* DPCD Register 0x0010A in Sink */
>> +	ti_sn_bridge_write(pdata, SN_AUX_WDATA0_REG, 0x01);
>> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_19_16_REG, 0x00);
>> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_15_8_REG, 0x01);
>> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_7_0_REG, 0x0A);
>> +	ti_sn_bridge_write(pdata, SN_AUX_LENGTH_REG, 0x01);
>> +	ti_sn_bridge_write(pdata, SN_AUX_CMD_REG, 0x81);
>> +	usleep_range(10000, 10001);
>> +
>> +	/* Semi auto link training mode */
>> +	ti_sn_bridge_write(pdata, SN_ML_TX_MODE_REG, 0x0A);
>> +	usleep_range(20000, 20001);
>> +
>> +	/* config video parameters */
>> +	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
>> +			mode->hdisplay & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG,
>> +			(mode->hdisplay >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
>> +			mode->vdisplay & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG,
>> +			(mode->hdisplay >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
>> +			(mode->htotal - mode->hsync_end) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
>> +			((mode->htotal - mode->hsync_end) >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
>> +			(mode->vtotal - mode->vsync_end) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
>> +			((mode->vtotal - mode->vsync_end) >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
>> +			(mode->hsync_start - mode->hdisplay) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_BACK_PORCH_REG,
>> +			(mode->vsync_start - mode->vdisplay) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
>> +			(mode->hsync_end - mode->hsync_start) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_FRONT_PORCH_REG,
>> +			(mode->vsync_end - mode->vsync_start) & 0xFF);
>> +	if (pdata->dsi->format == MIPI_DSI_FMT_RGB888)
>> +		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x00);
>> +	else
>> +		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x01);
>> +	usleep_range(10000, 10001);
>> +
>> +	/* enable video stream */
>> +	ti_sn_bridge_read(pdata, SN_ENH_FRAME_REG, &val, 0x1);
>> +	val |= BIT(3);
>> +	ti_sn_bridge_write(pdata, SN_ENH_FRAME_REG, val);
>> +
>> +	if (panel)
>> +		drm_panel_enable(panel);
> 
> As mentioned in the previous review, you should configure the mode in 
> mode_set,
> not enable. This eliminates the need for curr_mode.
> 
>> +}
>> +
>> +static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
>> +	.attach = ti_sn_bridge_attach,
>> +	.enable = ti_sn_bridge_enable,
>> +	.disable = ti_sn_bridge_disable,
>> +	.mode_set = ti_sn_bridge_mode_set,
>> +};
>> +
>> +static int ti_sn_bridge_parse_gpios(struct ti_sn_bridge *pdata)
>> +{
>> +	int ret = 0;
>> +
>> +	if (!pdata || !pdata->dev)
>> +		return -EINVAL;
>> +
>> +	pdata->enable_gpios = devm_gpiod_get_array(pdata->dev,
>> +						"enable", GPIOD_OUT_LOW);
>> +	if (IS_ERR(pdata->enable_gpios)) {
>> +		DRM_ERROR("failed to get enable gpio from DT\n");
>> +		ret = PTR_ERR(pdata->enable_gpios);
>> +		goto exit;
>> +	}
>> +
>> +	pdata->irq_gpio = devm_gpiod_get_optional(pdata->dev, "irq",
>> +						  GPIOD_OUT_LOW);
>> +	if (IS_ERR(pdata->irq_gpio))
>> +		DRM_ERROR("failed to get irq gpio from DT\n");
>> +
>> +exit:
>> +	return ret;
>> +}
>> +
>> +static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
>> +{
>> +	struct device_node *np = pdata->dev->of_node;
>> +	struct device_node *end_node;
>> +
>> +	end_node = of_graph_get_endpoint_by_regs(np, 0, 0);
>> +	if (!end_node) {
>> +		DRM_ERROR("remote endpoint not found\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	pdata->host_node = of_graph_get_remote_port_parent(end_node);
>> +	of_node_put(end_node);
>> +	if (!pdata->host_node) {
>> +		DRM_ERROR("remote node not found\n");
>> +		return -ENODEV;
>> +	}
>> +	of_node_put(pdata->host_node);
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_sn_bridge_probe(struct i2c_client *client,
>> +	 const struct i2c_device_id *id)
>> +{
>> +	struct ti_sn_bridge *pdata;
>> +	struct device_node *ddc_node;
>> +	int ret = 0;
>> +
>> +	if (!client || !client->dev.of_node) {
>> +		DRM_ERROR("invalid input\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
>> +		DRM_ERROR("device doesn't support I2C\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	pdata = devm_kzalloc(&client->dev,
>> +		sizeof(struct ti_sn_bridge), GFP_KERNEL);
>> +	if (!pdata)
>> +		return -ENOMEM;
>> +
>> +	pdata->power_on = false;
>> +	pdata->dev = &client->dev;
>> +	pdata->i2c_client = client;
>> +	DRM_DEBUG("I2C address is %x\n", client->addr);
>> +
>> +	ret = ti_sn_bridge_parse_dsi_host(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to parse device tree\n");
>> +		goto err_dt_parse;
>> +	}
>> +
>> +	ret = ti_sn_bridge_parse_gpios(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to parse gpios from DT\n");
>> +		goto err_dt_parse;
>> +	}
>> +
>> +	ret = ti_sn_bridge_parse_regulators(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to parse regulators\n");
>> +		goto err_dt_parse;
>> +	}
>> +
>> +	ddc_node = of_parse_phandle(pdata->dev->of_node, "ddc-i2c-bus", 0);
>> +	if (ddc_node) {
>> +		pdata->ddc = of_find_i2c_adapter_by_node(ddc_node);
>> +		of_node_put(ddc_node);
>> +		if (!pdata->ddc) {
>> +			dev_dbg(pdata->dev, "failed to read ddc node\n");
>> +			return -EPROBE_DEFER;
>> +		}
>> +	} else {
>> +		dev_dbg(pdata->dev, "no ddc property found\n");
>> +	}
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, true);
>> +	ret = ti_sn_bridge_read_device_rev(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to read chip rev\n");
>> +		goto err_config;
>> +	} else {
>> +		DRM_DEBUG("bridge chip enabled successfully\n");
>> +	}
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +
>> +	i2c_set_clientdata(client, pdata);
>> +	dev_set_drvdata(&client->dev, pdata);
>> +
>> +	pdata->bridge.funcs = &ti_sn_bridge_funcs;
>> +	pdata->bridge.of_node = client->dev.of_node;
>> +
>> +	drm_bridge_add(&pdata->bridge);
>> +
>> +	return ret;
>> +
>> +err_config:
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +err_dt_parse:
>> +	devm_kfree(&client->dev, pdata);
>> +	return ret;
>> +}
>> +
>> +static int ti_sn_bridge_remove(struct i2c_client *client)
>> +{
>> +	int ret = -EINVAL;
>> +	struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
>> +
>> +	if (!pdata)
>> +		goto end;
>> +
>> +	mipi_dsi_detach(pdata->dsi);
>> +	mipi_dsi_device_unregister(pdata->dsi);
>> +
>> +	drm_bridge_remove(&pdata->bridge);
>> +
>> +	disable_irq(pdata->irq);
>> +	free_irq(pdata->irq, pdata);
> 
> This is uninitialized.
> 
>> +
>> +	ti_sn_bridge_gpio_configure(pdata, false);
>> +	i2c_put_adapter(pdata->ddc);
>> +
>> +	devm_kfree(&client->dev, pdata);
>> +
>> +end:
>> +	return ret;
>> +}
>> +
>> +static struct i2c_device_id ti_sn_bridge_id[] = {
>> +	{ "ti,sn65dsi86", 0},
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
>> +
>> +static const struct of_device_id ti_sn_bridge_match_table[] = {
>> +	{.compatible = "ti,sn65dsi86"},
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
>> +
>> +static struct i2c_driver ti_sn_bridge_driver = {
>> +	.driver = {
>> +		.name = "ti_sn65dsi86",
>> +		.owner = THIS_MODULE,
>> +		.of_match_table = ti_sn_bridge_match_table,
>> +	},
>> +	.probe = ti_sn_bridge_probe,
>> +	.remove = ti_sn_bridge_remove,
>> +	.id_table = ti_sn_bridge_id,
>> +};
>> +
>> +module_i2c_driver(ti_sn_bridge_driver);
>> +MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
>> --
>> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora 
>> Forum,
>> a Linux Foundation Collaborative Project
>> 
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver
       [not found]       ` <1524054002-17869-2-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
@ 2018-04-19 15:14         ` Jordan Crouse
       [not found]           ` <20180419151438.GA18652-9PYrDHPZ2Orvke4nUoYGnHL1okKdlPRT@public.gmane.org>
  0 siblings, 1 reply; 21+ messages in thread
From: Jordan Crouse @ 2018-04-19 15:14 UTC (permalink / raw)
  To: Sandeep Panda
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, ryadav-sgV2jX0FEOL9JmXXK+q4OQ,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	chandanu-sgV2jX0FEOL9JmXXK+q4OQ

On Wed, Apr 18, 2018 at 05:49:59PM +0530, Sandeep Panda wrote:
> Add support for TI's sn65dsi86 dsi2edp bridge chip.
> The chip converts DSI transmitted signal to eDP signal,
> which is fed to the connected eDP panel.
> 
> This chip can be controlled via either i2c interface or
> dsi interface. Currently in driver all the control registers
> are being accessed through i2c interface only.
> Also as of now HPD support has not been added to bridge
> chip driver.
> 
> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
> ---
>  drivers/gpu/drm/bridge/ti-sn65dsi86.c | 742 ++++++++++++++++++++++++++++++++++
>  1 file changed, 742 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
> 
> diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> new file mode 100644
> index 0000000..c798f2f
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> @@ -0,0 +1,742 @@
> +/* SPDX-License-Identifier: GPL-2.0 */

There should be a copyright here.

> +
> +#include <linux/types.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/device.h>
> +#include <linux/platform_device.h>
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/gpio.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_graph.h>
> +#include <linux/regulator/consumer.h>
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +
> +#define SN_BRIDGE_REVISION_ID 0x2
> +
> +/* Link Training specific registers */
> +#define SN_DEVICE_REV_REG			0x08
> +#define SN_REFCLK_FREQ_REG			0x0A
> +#define SN_DSI_LANES_REG			0x10
> +#define SN_DSIA_CLK_FREQ_REG			0x12
> +#define SN_ENH_FRAME_REG			0x5A
> +#define SN_SSC_CONFIG_REG			0x93
> +#define SN_DATARATE_CONFIG_REG			0x94
> +#define SN_PLL_ENABLE_REG			0x0D
> +#define SN_SCRAMBLE_CONFIG_REG			0x95
> +#define SN_AUX_WDATA0_REG			0x64
> +#define SN_AUX_ADDR_19_16_REG			0x74
> +#define SN_AUX_ADDR_15_8_REG			0x75
> +#define SN_AUX_ADDR_7_0_REG			0x76
> +#define SN_AUX_LENGTH_REG			0x77
> +#define SN_AUX_CMD_REG				0x78
> +#define SN_ML_TX_MODE_REG			0x96
> +#define SN_PAGE_SELECT_REG			0xFF
> +#define SN_AUX_RDATA0_REG			0x79
> +/* video config specific registers */
> +#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG	0x20
> +#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG	0x21
> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG	0x24
> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG	0x25
> +#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG	0x2C
> +#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG	0x2D
> +#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG	0x30
> +#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG	0x31
> +#define SN_CHA_HORIZONTAL_BACK_PORCH_REG	0x34
> +#define SN_CHA_VERTICAL_BACK_PORCH_REG		0x36
> +#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG	0x38
> +#define SN_CHA_VERTICAL_FRONT_PORCH_REG		0x3A
> +#define SN_DATA_FORMAT_REG			0x5B
> +#define SN_COLOR_BAR_CONFIG_REG			0x3C
> +
> +#define DPPLL_CLK_SRC_REFCLK	0
> +#define DPPLL_CLK_SRC_DSICLK	1
> +#define MIN_DSI_CLK_FREQ_MHZ	40
> +
> +enum ti_sn_bridge_ref_clks {
> +	SN_REF_CLK_12_MHZ = 0,
> +	SN_REF_CLK_19_2_MHZ,
> +	SN_REF_CLK_26_MHZ,
> +	SN_REF_CLK_27_MHZ,
> +	SN_REF_CLK_38_4_MHZ,
> +	SN_REF_CLK_MAX,
> +};
> +
> +struct ti_sn_bridge {
> +	struct device *dev;
> +	struct drm_bridge bridge;
> +	struct drm_connector connector;
> +	struct device_node *host_node;
> +	struct mipi_dsi_device *dsi;
> +	/* handle to the connected eDP panel */
> +	struct drm_panel *panel;
> +	int irq;
> +	struct gpio_desc *irq_gpio;
> +	/* list of gpios needed to enable the bridge functionality */
> +	struct gpio_descs *enable_gpios;
> +	unsigned int num_supplies;
> +	struct regulator_bulk_data *supplies;
> +	struct i2c_client *i2c_client;
> +	struct i2c_adapter *ddc;
> +	bool power_on;
> +	u32 num_modes;
> +	struct drm_display_mode curr_mode;
> +};
> +
> +static int ti_sn_bridge_write(struct ti_sn_bridge *pdata, u8 reg, u8 val)
> +{
> +	struct i2c_client *client = pdata->i2c_client;
> +	u8 buf[2] = {reg, val};
> +	struct i2c_msg msg = {
> +		.addr = client->addr,
> +		.flags = 0,
> +		.len = 2,
> +		.buf = buf,
> +	};
> +
> +	if (i2c_transfer(client->adapter, &msg, 1) < 1) {
> +		DRM_ERROR("i2c write failed\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ti_sn_bridge_read(struct ti_sn_bridge *pdata,
> +					u8 reg, char *buf, u32 size)
> +{
> +	struct i2c_client *client = pdata->i2c_client;
> +	struct i2c_msg msg[2] = {
> +		{
> +			.addr = client->addr,
> +			.flags = 0,
> +			.len = 1,
> +			.buf = &reg,
> +		},
> +		{
> +			.addr = client->addr,
> +			.flags = I2C_M_RD,
> +			.len = size,
> +			.buf = buf,
> +		}
> +	};
> +
> +	if (i2c_transfer(client->adapter, msg, 2) != 2) {
> +		DRM_ERROR("i2c read failed\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +static void ti_sn_bridge_gpio_configure(struct ti_sn_bridge *pdata, bool on)
> +{
> +	int value, i = 0, num = 0;
> +	int *value_array = NULL;
> +
> +	if (!pdata)
> +		return;

pdata should always be valid here.

> +	value = on ? 1 : 0;
> +	num = pdata->enable_gpios->ndescs;
> +	value_array = kmalloc(num * sizeof(int), GFP_KERNEL);
> +	if (value_array) {
> +		for (i = 0; i < num; i++)
> +			value_array[i] = value;
> +		gpiod_set_array_value(num, pdata->enable_gpios->desc,
> +				      value_array);
> +		kfree(value_array);
> +	}
> +
> +	if (pdata->irq_gpio)
> +		gpiod_set_value(pdata->irq_gpio, value);
> +
> +	DRM_DEBUG("config to: %d\n", value);
> +}
> +
> +static void ti_sn_bridge_power_ctrl(struct ti_sn_bridge *pdata, bool enable)
> +{
> +	static int ref_count;
> +
> +	if (!pdata)
> +		return;

pdata should always be valid here.

> +	if (enable)
> +		ref_count++;
> +	else
> +		ref_count--;
> +
> +	if (enable && (ref_count == 1)) {
> +		ti_sn_bridge_gpio_configure(pdata, true);
> +
> +		if (regulator_bulk_enable(pdata->num_supplies,
> +						pdata->supplies)) {
> +			DRM_ERROR("bridge regulator enable failed\n");
> +			return;
> +		}
> +		pdata->power_on = true;
> +	} else if (!enable && !ref_count) {
> +		regulator_bulk_disable(pdata->num_supplies, pdata->supplies);
> +
> +		ti_sn_bridge_gpio_configure(pdata, false);
> +		pdata->power_on = false;
> +	} else {
> +		DRM_DEBUG("ti_sn_bridge power ctrl: %d refcount: %d\n",
> +							enable, ref_count);
> +	}
> +}
> +
> +/* Connector funcs */
> +static struct ti_sn_bridge *
> +connector_to_ti_sn_bridge(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct ti_sn_bridge, connector);
> +}
> +
> +static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
> +	struct drm_panel *panel = pdata->panel;
> +	struct edid *edid;
> +
> +	if (panel) {
> +		DRM_DEBUG("get mode from connected drm_panel\n");
> +		pdata->num_modes = drm_panel_get_modes(panel);
> +	} else {
> +		/* get from EDID */
> +		if (!pdata->ddc)
> +			return 0;
> +		ti_sn_bridge_power_ctrl(pdata, true);
> +		edid = drm_get_edid(connector, pdata->ddc);
> +		if (edid) {
> +			drm_mode_connector_update_edid_property(connector,
> +								edid);
> +			pdata->num_modes = drm_add_edid_modes(connector, edid);
> +			drm_edid_to_eld(connector, edid);
> +			kfree(edid);
> +		} else {
> +			DRM_DEBUG("failed to get edid\n");
> +		}
> +		ti_sn_bridge_power_ctrl(pdata, false);
> +	}
> +
> +	return pdata->num_modes;
> +}
> +
> +static enum drm_mode_status
> +ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
> +			     struct drm_display_mode *mode)
> +{
> +	/* maximum supported resolution is 4K at 60 fps */
> +	if (mode->clock > 594000)
> +		return MODE_CLOCK_HIGH;
> +
> +	return MODE_OK;
> +}
> +
> +static struct drm_connector_helper_funcs ti_sn_bridge_connector_helper_funcs = {
> +	.get_modes = ti_sn_bridge_connector_get_modes,
> +	.mode_valid = ti_sn_bridge_connector_mode_valid,
> +};
> +
> +static enum drm_connector_status
> +ti_sn_bridge_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
> +
> +	/**
> +	 * TODO: Currently if drm_panel is present, then always
> +	 * return the status as connected. Need to add support to detect
> +	 * device state for no panel(hot pluggable) scenarios.
> +	 */
> +	if (pdata->panel)
> +		return connector_status_connected;
> +	else
> +		return connector_status_disconnected;
> +}
> +
> +static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = {
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = ti_sn_bridge_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,
> +};
> +
> +static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct ti_sn_bridge, bridge);
> +}
> +
> +static int ti_sn_bridge_read_device_rev(struct ti_sn_bridge *pdata)
> +{
> +	u8 rev = 0;
> +	int ret = 0;
> +
> +	ret = ti_sn_bridge_read(pdata, SN_DEVICE_REV_REG, &rev, 1);
> +	if (ret)
> +		goto exit;
> +
> +	if (rev == SN_BRIDGE_REVISION_ID) {
> +		DRM_DEBUG("ti_sn_bridge revision id: 0x%x\n", rev);
> +	} else {
> +		DRM_ERROR("ti_sn_bridge revision id mismatch\n");

I find it useful to print what the mismatch is so you can debug more
effectively.

> +		ret = -EINVAL;
> +	}
> +
> +exit:
> +	return ret;
> +}
> +
> +static const char * const ti_sn_bridge_supply_names[] = {
> +	"vccio",
> +	"vcca",
> +};
> +
> +static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	pdata->num_supplies = ARRAY_SIZE(ti_sn_bridge_supply_names);
> +
> +	pdata->supplies = devm_kcalloc(pdata->dev, pdata->num_supplies,
> +				     sizeof(*pdata->supplies), GFP_KERNEL);
> +	if (!pdata->supplies)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < pdata->num_supplies; i++)
> +		pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
> +
> +	ret = devm_regulator_bulk_get(pdata->dev,
> +			pdata->num_supplies, pdata->supplies);
> +
> +	return ret;

Nit: you can combine the above two lines and get rid of 'ret'.

> +}
> +
> +static void ti_sn_bridge_attach_panel(struct ti_sn_bridge *pdata)
> +{
> +	struct device_node *panel_node, *port, *endpoint;
> +	struct drm_panel *panel = NULL;
> +
> +	port = of_graph_get_port_by_id(pdata->dev->of_node, 1);
> +	if (port) {
> +		endpoint = of_get_child_by_name(port, "endpoint");
> +		of_node_put(port);
> +		if (!endpoint) {
> +			dev_err(pdata->dev, "no output endpoint found\n");
> +			goto error;
> +		}
> +
> +		panel_node = of_graph_get_remote_port_parent(endpoint);
> +		of_node_put(endpoint);
> +		if (!panel_node) {
> +			dev_err(pdata->dev, "no output node found\n");
> +			goto error;
> +		}
> +
> +		panel = of_drm_find_panel(panel_node);
> +		of_node_put(panel_node);
> +		if (!panel) {
> +			dev_err(pdata->dev, "no panel node found\n");
> +			goto error;
> +		}
> +	}
> +
> +	pdata->panel = panel;
> +	drm_panel_attach(panel, &pdata->connector);
> +
> +	return;
> +
> +error:
> +	pdata->panel = NULL;

Is this needed?  isn't pdata->panel already null when we come in?  If so
you can just return the error cases and remove the label.

> +}
> +
> +static int ti_sn_bridge_attach(struct drm_bridge *bridge)
> +{
> +	struct mipi_dsi_host *host;
> +	struct mipi_dsi_device *dsi;
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +	int ret;
> +	const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
> +						   .channel = 0,
> +						   .node = NULL,
> +						 };
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");

Missing \n on the end of the string.

> +		return -ENODEV;
> +	}
> +
> +	/* HPD not supported */
> +	pdata->connector.polled = 0;
> +
> +	ret = drm_connector_init(bridge->dev, &pdata->connector,
> +				 &ti_sn_bridge_connector_funcs,
> +				 DRM_MODE_CONNECTOR_eDP);

The actual #define is CONNNECTOR_eDP?

> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	drm_connector_helper_add(&pdata->connector,
> +				 &ti_sn_bridge_connector_helper_funcs);
> +	drm_mode_connector_attach_encoder(&pdata->connector, bridge->encoder);
> +
> +	host = of_find_mipi_dsi_host_by_node(pdata->host_node);
> +	if (!host) {
> +		DRM_ERROR("failed to find dsi host\n");

There isn't any tear down that needs to happen here?

> +		return -ENODEV;
> +	}
> +
> +	dsi = mipi_dsi_device_register_full(host, &info);
> +	if (IS_ERR(dsi)) {
> +		DRM_ERROR("failed to create dsi device\n");
> +		ret = PTR_ERR(dsi);
> +		goto err_dsi_device;

Above you return directly but here you goto a return.  You should use the same
consistent approach in both cases.

> +	}
> +
> +	/* TODO: setting to 4 lanes always for now */
> +	dsi->lanes = 4;
> +	dsi->format = MIPI_DSI_FMT_RGB888;
> +	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
> +			  MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
> +
> +	ret = mipi_dsi_attach(dsi);
> +	if (ret < 0) {
> +		DRM_ERROR("failed to attach dsi to host\n");
> +		goto err_dsi_attach;
> +	}
> +
> +	pdata->dsi = dsi;
> +
> +	DRM_DEBUG("bridge attached\n");
> +	/* attach panel to bridge */
> +	ti_sn_bridge_attach_panel(pdata);
> +
> +	return 0;
> +
> +err_dsi_attach:
> +	mipi_dsi_device_unregister(dsi);
> +err_dsi_device:
> +	return ret;
> +}
> +
> +static void ti_sn_bridge_mode_set(struct drm_bridge *bridge,
> +				    struct drm_display_mode *mode,
> +				    struct drm_display_mode *adj_mode)
> +{
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +
> +	DRM_DEBUG("mode_set: hdisplay=%d, vdisplay=%d, vrefresh=%d, clock=%d\n",
> +		adj_mode->hdisplay, adj_mode->vdisplay,
> +		adj_mode->vrefresh, adj_mode->clock);
> +
> +	drm_mode_copy(&pdata->curr_mode, adj_mode);
> +}
> +
> +static void ti_sn_bridge_disable(struct drm_bridge *bridge)
> +{
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +	struct drm_panel *panel = pdata->panel;
> +
> +	if (panel) {
> +		drm_panel_disable(panel);
> +		drm_panel_unprepare(panel);
> +	}
> +
> +	ti_sn_bridge_power_ctrl(pdata, false);
> +}
> +
> +static void ti_sn_bridge_enable(struct drm_bridge *bridge)
> +{
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +	struct drm_panel *panel = pdata->panel;
> +	struct drm_display_mode *mode = &pdata->curr_mode;
> +	u32 bit_rate_mhz, clk_freq_mhz;
> +	u8 val = 0;
> +
> +	if (!pdata->num_modes)
> +		return;
> +
> +	ti_sn_bridge_power_ctrl(pdata, true);
> +
> +	if (panel)
> +		drm_panel_prepare(panel);
> +
> +	/* CLK_SRC config */
> +	ti_sn_bridge_write(pdata, SN_REFCLK_FREQ_REG,
> +			(DPPLL_CLK_SRC_REFCLK | (SN_REF_CLK_19_2_MHZ << 0x1)));
> +
> +	/* DSI_A lane config */
> +	ti_sn_bridge_read(pdata, SN_DSI_LANES_REG, &val, 0x1);
> +	val |= ((0x4 - pdata->dsi->lanes) & 0x03) << 0x3;
> +	ti_sn_bridge_write(pdata, SN_DSI_LANES_REG, val);
> +
> +	/* DP lane config */
> +	ti_sn_bridge_read(pdata, SN_SSC_CONFIG_REG, &val, 0x1);
> +	val |= ((pdata->dsi->lanes - 0x1) & 0x03) << 0x4;
> +	ti_sn_bridge_write(pdata, SN_SSC_CONFIG_REG, val);
> +
> +	/* set dsi clk frequency value */
> +	bit_rate_mhz = (mode->clock / 1000) *
> +			mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
> +	clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
> +	/* for each increment in val, frequency increases by 5MHz */
> +	val = (MIN_DSI_CLK_FREQ_MHZ / 0x5) +
> +		(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 0x5) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_DSIA_CLK_FREQ_REG, val);
> +
> +	ti_sn_bridge_write(pdata, SN_DATARATE_CONFIG_REG, 0x80); /* set HBR */
> +
> +	ti_sn_bridge_write(pdata, SN_PLL_ENABLE_REG, 0x1); /* Enable PLL */
> +	usleep_range(10000, 10001);
> +
> +	/* DPCD Register 0x0010A in Sink */
> +	ti_sn_bridge_write(pdata, SN_AUX_WDATA0_REG, 0x01);
> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_19_16_REG, 0x00);
> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_15_8_REG, 0x01);
> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_7_0_REG, 0x0A);
> +	ti_sn_bridge_write(pdata, SN_AUX_LENGTH_REG, 0x01);
> +	ti_sn_bridge_write(pdata, SN_AUX_CMD_REG, 0x81);
> +	usleep_range(10000, 10001);
> +
> +	/* Semi auto link training mode */
> +	ti_sn_bridge_write(pdata, SN_ML_TX_MODE_REG, 0x0A);
> +	usleep_range(20000, 20001);
> +
> +	/* config video parameters */
> +	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
> +			mode->hdisplay & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG,
> +			(mode->hdisplay >> 0x8) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
> +			mode->vdisplay & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG,
> +			(mode->hdisplay >> 0x8) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
> +			(mode->htotal - mode->hsync_end) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
> +			((mode->htotal - mode->hsync_end) >> 0x8) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
> +			(mode->vtotal - mode->vsync_end) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
> +			((mode->vtotal - mode->vsync_end) >> 0x8) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
> +			(mode->hsync_start - mode->hdisplay) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_BACK_PORCH_REG,
> +			(mode->vsync_start - mode->vdisplay) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
> +			(mode->hsync_end - mode->hsync_start) & 0xFF);
> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_FRONT_PORCH_REG,
> +			(mode->vsync_end - mode->vsync_start) & 0xFF);
> +	if (pdata->dsi->format == MIPI_DSI_FMT_RGB888)
> +		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x00);
> +	else
> +		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x01);
> +	usleep_range(10000, 10001);
> +
> +	/* enable video stream */
> +	ti_sn_bridge_read(pdata, SN_ENH_FRAME_REG, &val, 0x1);
> +	val |= BIT(3);
> +	ti_sn_bridge_write(pdata, SN_ENH_FRAME_REG, val);
> +
> +	if (panel)
> +		drm_panel_enable(panel);
> +}
> +
> +static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
> +	.attach = ti_sn_bridge_attach,
> +	.enable = ti_sn_bridge_enable,
> +	.disable = ti_sn_bridge_disable,
> +	.mode_set = ti_sn_bridge_mode_set,
> +};
> +
> +static int ti_sn_bridge_parse_gpios(struct ti_sn_bridge *pdata)
> +{
> +	int ret = 0;
> +
> +	if (!pdata || !pdata->dev)
> +		return -EINVAL;

Both of these are known to be valid at this point.

> +	pdata->enable_gpios = devm_gpiod_get_array(pdata->dev,
> +						"enable", GPIOD_OUT_LOW);
> +	if (IS_ERR(pdata->enable_gpios)) {
> +		DRM_ERROR("failed to get enable gpio from DT\n");
> +		ret = PTR_ERR(pdata->enable_gpios);
> +		goto exit;
> +	}
> +
> +	pdata->irq_gpio = devm_gpiod_get_optional(pdata->dev, "irq",
> +						  GPIOD_OUT_LOW);
> +	if (IS_ERR(pdata->irq_gpio))
> +		DRM_ERROR("failed to get irq gpio from DT\n");

You need to set ret to PTR_ERR(pdata->irq_gpio) here.

> +
> +exit:
> +	return ret;
> +}
> +
> +static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
> +{
> +	struct device_node *np = pdata->dev->of_node;
> +	struct device_node *end_node;
> +
> +	end_node = of_graph_get_endpoint_by_regs(np, 0, 0);
> +	if (!end_node) {
> +		DRM_ERROR("remote endpoint not found\n");
> +		return -ENODEV;
> +	}
> +
> +	pdata->host_node = of_graph_get_remote_port_parent(end_node);
> +	of_node_put(end_node);
> +	if (!pdata->host_node) {
> +		DRM_ERROR("remote node not found\n");
> +		return -ENODEV;
> +	}
> +	of_node_put(pdata->host_node);
> +
> +	return 0;
> +}
> +
> +static int ti_sn_bridge_probe(struct i2c_client *client,
> +	 const struct i2c_device_id *id)
> +{
> +	struct ti_sn_bridge *pdata;
> +	struct device_node *ddc_node;
> +	int ret = 0;
> +
> +	if (!client || !client->dev.of_node) {

I doubt the i2c probe would send you a NULL value for client and you are
matching on a DT device, so there is a pretty solid argument that
client->dev.of_node will be valid as well.

> +		DRM_ERROR("invalid input\n");
> +		return -EINVAL;
> +	}
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> +		DRM_ERROR("device doesn't support I2C\n");
> +		return -ENODEV;
> +	}
> +
> +	pdata = devm_kzalloc(&client->dev,
> +		sizeof(struct ti_sn_bridge), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->power_on = false;
> +	pdata->dev = &client->dev;
> +	pdata->i2c_client = client;
> +	DRM_DEBUG("I2C address is %x\n", client->addr);
> +
> +	ret = ti_sn_bridge_parse_dsi_host(pdata);
> +	if (ret) {
> +		DRM_ERROR("failed to parse device tree\n");

ti_sn_bridge_parse_dsi_host prints a error message on every failure point - you
don't need another one here.

> +		goto err_dt_parse;
> +	}
> +
> +	ret = ti_sn_bridge_parse_gpios(pdata);
> +	if (ret) {
> +		DRM_ERROR("failed to parse gpios from DT\n");

Same - all the paths are covered in the child function.

> +		goto err_dt_parse;
> +	}
> +
> +	ret = ti_sn_bridge_parse_regulators(pdata);
> +	if (ret) {
> +		DRM_ERROR("failed to parse regulators\n");
> +		goto err_dt_parse;
> +	}
> +
> +	ddc_node = of_parse_phandle(pdata->dev->of_node, "ddc-i2c-bus", 0);
> +	if (ddc_node) {
> +		pdata->ddc = of_find_i2c_adapter_by_node(ddc_node);
> +		of_node_put(ddc_node);
> +		if (!pdata->ddc) {
> +			dev_dbg(pdata->dev, "failed to read ddc node\n");
> +			return -EPROBE_DEFER;
> +		}
> +	} else {
> +		dev_dbg(pdata->dev, "no ddc property found\n");
> +	}
> +
> +	ti_sn_bridge_power_ctrl(pdata, true);
> +	ret = ti_sn_bridge_read_device_rev(pdata);
> +	if (ret) {
> +		DRM_ERROR("failed to read chip rev\n");

ti_sn_bridge_read_device_rev has you covered with the error messages.

> +		goto err_config;
> +	} else {
> +		DRM_DEBUG("bridge chip enabled successfully\n");
> +	}

And also the debug messages for whatever its worth.

> +	ti_sn_bridge_power_ctrl(pdata, false);
> +
> +	i2c_set_clientdata(client, pdata);
> +	dev_set_drvdata(&client->dev, pdata);
> +
> +	pdata->bridge.funcs = &ti_sn_bridge_funcs;
> +	pdata->bridge.of_node = client->dev.of_node;
> +
> +	drm_bridge_add(&pdata->bridge);
> +
> +	return ret;
> +
> +err_config:
> +	ti_sn_bridge_power_ctrl(pdata, false);
> +err_dt_parse:
> +	devm_kfree(&client->dev, pdata);
> +	return ret;
> +}
> +
> +static int ti_sn_bridge_remove(struct i2c_client *client)
> +{
> +	int ret = -EINVAL;
> +	struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
> +
> +	if (!pdata)
> +		goto end;

pdata is very unlikely to be null here, but as long as this is a light weight
check, I don't think it hurts too much.

> +
> +	mipi_dsi_detach(pdata->dsi);
> +	mipi_dsi_device_unregister(pdata->dsi);
> +
> +	drm_bridge_remove(&pdata->bridge);
> +
> +	disable_irq(pdata->irq);
> +	free_irq(pdata->irq, pdata);
> +
> +	ti_sn_bridge_gpio_configure(pdata, false);
> +	i2c_put_adapter(pdata->ddc);
> +
> +	devm_kfree(&client->dev, pdata);
> +
> +end:
> +	return ret;
> +}
> +
> +static struct i2c_device_id ti_sn_bridge_id[] = {
> +	{ "ti,sn65dsi86", 0},
> +	{}

Missing a comma on the end of the empty entry.

> +};
> +MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
> +
> +static const struct of_device_id ti_sn_bridge_match_table[] = {
> +	{.compatible = "ti,sn65dsi86"},
> +	{}

Same.

> +};
> +MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
> +
> +static struct i2c_driver ti_sn_bridge_driver = {
> +	.driver = {
> +		.name = "ti_sn65dsi86",
> +		.owner = THIS_MODULE,
> +		.of_match_table = ti_sn_bridge_match_table,
> +	},
> +	.probe = ti_sn_bridge_probe,
> +	.remove = ti_sn_bridge_remove,
> +	.id_table = ti_sn_bridge_id,
> +};
> +
> +module_i2c_driver(ti_sn_bridge_driver);
> +MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");

Looks like you are missing the module license here.

> -- 
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> a Linux Foundation Collaborative Project
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver
       [not found]           ` <20180419151438.GA18652-9PYrDHPZ2Orvke4nUoYGnHL1okKdlPRT@public.gmane.org>
@ 2018-04-19 18:18             ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  0 siblings, 0 replies; 21+ messages in thread
From: spanda-sgV2jX0FEOL9JmXXK+q4OQ @ 2018-04-19 18:18 UTC (permalink / raw)
  To: Sandeep Panda
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, ryadav-sgV2jX0FEOL9JmXXK+q4OQ,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	chandanu-sgV2jX0FEOL9JmXXK+q4OQ

On 2018-04-19 20:44, Jordan Crouse wrote:
> On Wed, Apr 18, 2018 at 05:49:59PM +0530, Sandeep Panda wrote:
>> Add support for TI's sn65dsi86 dsi2edp bridge chip.
>> The chip converts DSI transmitted signal to eDP signal,
>> which is fed to the connected eDP panel.
>> 
>> This chip can be controlled via either i2c interface or
>> dsi interface. Currently in driver all the control registers
>> are being accessed through i2c interface only.
>> Also as of now HPD support has not been added to bridge
>> chip driver.
>> 
>> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
>> ---
>>  drivers/gpu/drm/bridge/ti-sn65dsi86.c | 742 
>> ++++++++++++++++++++++++++++++++++
>>  1 file changed, 742 insertions(+)
>>  create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> 
>> diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c 
>> b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> new file mode 100644
>> index 0000000..c798f2f
>> --- /dev/null
>> +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> @@ -0,0 +1,742 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
> 
> There should be a copyright here.
> 
>> +
>> +#include <linux/types.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/init.h>
>> +#include <linux/device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/delay.h>
>> +#include <linux/i2c.h>
>> +#include <linux/gpio.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/of_graph.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <drm/drmP.h>
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
>> +
>> +#define SN_BRIDGE_REVISION_ID 0x2
>> +
>> +/* Link Training specific registers */
>> +#define SN_DEVICE_REV_REG			0x08
>> +#define SN_REFCLK_FREQ_REG			0x0A
>> +#define SN_DSI_LANES_REG			0x10
>> +#define SN_DSIA_CLK_FREQ_REG			0x12
>> +#define SN_ENH_FRAME_REG			0x5A
>> +#define SN_SSC_CONFIG_REG			0x93
>> +#define SN_DATARATE_CONFIG_REG			0x94
>> +#define SN_PLL_ENABLE_REG			0x0D
>> +#define SN_SCRAMBLE_CONFIG_REG			0x95
>> +#define SN_AUX_WDATA0_REG			0x64
>> +#define SN_AUX_ADDR_19_16_REG			0x74
>> +#define SN_AUX_ADDR_15_8_REG			0x75
>> +#define SN_AUX_ADDR_7_0_REG			0x76
>> +#define SN_AUX_LENGTH_REG			0x77
>> +#define SN_AUX_CMD_REG				0x78
>> +#define SN_ML_TX_MODE_REG			0x96
>> +#define SN_PAGE_SELECT_REG			0xFF
>> +#define SN_AUX_RDATA0_REG			0x79
>> +/* video config specific registers */
>> +#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG	0x20
>> +#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG	0x21
>> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG	0x24
>> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG	0x25
>> +#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG	0x2C
>> +#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG	0x2D
>> +#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG	0x30
>> +#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG	0x31
>> +#define SN_CHA_HORIZONTAL_BACK_PORCH_REG	0x34
>> +#define SN_CHA_VERTICAL_BACK_PORCH_REG		0x36
>> +#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG	0x38
>> +#define SN_CHA_VERTICAL_FRONT_PORCH_REG		0x3A
>> +#define SN_DATA_FORMAT_REG			0x5B
>> +#define SN_COLOR_BAR_CONFIG_REG			0x3C
>> +
>> +#define DPPLL_CLK_SRC_REFCLK	0
>> +#define DPPLL_CLK_SRC_DSICLK	1
>> +#define MIN_DSI_CLK_FREQ_MHZ	40
>> +
>> +enum ti_sn_bridge_ref_clks {
>> +	SN_REF_CLK_12_MHZ = 0,
>> +	SN_REF_CLK_19_2_MHZ,
>> +	SN_REF_CLK_26_MHZ,
>> +	SN_REF_CLK_27_MHZ,
>> +	SN_REF_CLK_38_4_MHZ,
>> +	SN_REF_CLK_MAX,
>> +};
>> +
>> +struct ti_sn_bridge {
>> +	struct device *dev;
>> +	struct drm_bridge bridge;
>> +	struct drm_connector connector;
>> +	struct device_node *host_node;
>> +	struct mipi_dsi_device *dsi;
>> +	/* handle to the connected eDP panel */
>> +	struct drm_panel *panel;
>> +	int irq;
>> +	struct gpio_desc *irq_gpio;
>> +	/* list of gpios needed to enable the bridge functionality */
>> +	struct gpio_descs *enable_gpios;
>> +	unsigned int num_supplies;
>> +	struct regulator_bulk_data *supplies;
>> +	struct i2c_client *i2c_client;
>> +	struct i2c_adapter *ddc;
>> +	bool power_on;
>> +	u32 num_modes;
>> +	struct drm_display_mode curr_mode;
>> +};
>> +
>> +static int ti_sn_bridge_write(struct ti_sn_bridge *pdata, u8 reg, u8 
>> val)
>> +{
>> +	struct i2c_client *client = pdata->i2c_client;
>> +	u8 buf[2] = {reg, val};
>> +	struct i2c_msg msg = {
>> +		.addr = client->addr,
>> +		.flags = 0,
>> +		.len = 2,
>> +		.buf = buf,
>> +	};
>> +
>> +	if (i2c_transfer(client->adapter, &msg, 1) < 1) {
>> +		DRM_ERROR("i2c write failed\n");
>> +		return -EIO;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_sn_bridge_read(struct ti_sn_bridge *pdata,
>> +					u8 reg, char *buf, u32 size)
>> +{
>> +	struct i2c_client *client = pdata->i2c_client;
>> +	struct i2c_msg msg[2] = {
>> +		{
>> +			.addr = client->addr,
>> +			.flags = 0,
>> +			.len = 1,
>> +			.buf = &reg,
>> +		},
>> +		{
>> +			.addr = client->addr,
>> +			.flags = I2C_M_RD,
>> +			.len = size,
>> +			.buf = buf,
>> +		}
>> +	};
>> +
>> +	if (i2c_transfer(client->adapter, msg, 2) != 2) {
>> +		DRM_ERROR("i2c read failed\n");
>> +		return -EIO;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void ti_sn_bridge_gpio_configure(struct ti_sn_bridge *pdata, 
>> bool on)
>> +{
>> +	int value, i = 0, num = 0;
>> +	int *value_array = NULL;
>> +
>> +	if (!pdata)
>> +		return;
> 
> pdata should always be valid here.
> 
>> +	value = on ? 1 : 0;
>> +	num = pdata->enable_gpios->ndescs;
>> +	value_array = kmalloc(num * sizeof(int), GFP_KERNEL);
>> +	if (value_array) {
>> +		for (i = 0; i < num; i++)
>> +			value_array[i] = value;
>> +		gpiod_set_array_value(num, pdata->enable_gpios->desc,
>> +				      value_array);
>> +		kfree(value_array);
>> +	}
>> +
>> +	if (pdata->irq_gpio)
>> +		gpiod_set_value(pdata->irq_gpio, value);
>> +
>> +	DRM_DEBUG("config to: %d\n", value);
>> +}
>> +
>> +static void ti_sn_bridge_power_ctrl(struct ti_sn_bridge *pdata, bool 
>> enable)
>> +{
>> +	static int ref_count;
>> +
>> +	if (!pdata)
>> +		return;
> 
> pdata should always be valid here.
> 
>> +	if (enable)
>> +		ref_count++;
>> +	else
>> +		ref_count--;
>> +
>> +	if (enable && (ref_count == 1)) {
>> +		ti_sn_bridge_gpio_configure(pdata, true);
>> +
>> +		if (regulator_bulk_enable(pdata->num_supplies,
>> +						pdata->supplies)) {
>> +			DRM_ERROR("bridge regulator enable failed\n");
>> +			return;
>> +		}
>> +		pdata->power_on = true;
>> +	} else if (!enable && !ref_count) {
>> +		regulator_bulk_disable(pdata->num_supplies, pdata->supplies);
>> +
>> +		ti_sn_bridge_gpio_configure(pdata, false);
>> +		pdata->power_on = false;
>> +	} else {
>> +		DRM_DEBUG("ti_sn_bridge power ctrl: %d refcount: %d\n",
>> +							enable, ref_count);
>> +	}
>> +}
>> +
>> +/* Connector funcs */
>> +static struct ti_sn_bridge *
>> +connector_to_ti_sn_bridge(struct drm_connector *connector)
>> +{
>> +	return container_of(connector, struct ti_sn_bridge, connector);
>> +}
>> +
>> +static int ti_sn_bridge_connector_get_modes(struct drm_connector 
>> *connector)
>> +{
>> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
>> +	struct drm_panel *panel = pdata->panel;
>> +	struct edid *edid;
>> +
>> +	if (panel) {
>> +		DRM_DEBUG("get mode from connected drm_panel\n");
>> +		pdata->num_modes = drm_panel_get_modes(panel);
>> +	} else {
>> +		/* get from EDID */
>> +		if (!pdata->ddc)
>> +			return 0;
>> +		ti_sn_bridge_power_ctrl(pdata, true);
>> +		edid = drm_get_edid(connector, pdata->ddc);
>> +		if (edid) {
>> +			drm_mode_connector_update_edid_property(connector,
>> +								edid);
>> +			pdata->num_modes = drm_add_edid_modes(connector, edid);
>> +			drm_edid_to_eld(connector, edid);
>> +			kfree(edid);
>> +		} else {
>> +			DRM_DEBUG("failed to get edid\n");
>> +		}
>> +		ti_sn_bridge_power_ctrl(pdata, false);
>> +	}
>> +
>> +	return pdata->num_modes;
>> +}
>> +
>> +static enum drm_mode_status
>> +ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
>> +			     struct drm_display_mode *mode)
>> +{
>> +	/* maximum supported resolution is 4K at 60 fps */
>> +	if (mode->clock > 594000)
>> +		return MODE_CLOCK_HIGH;
>> +
>> +	return MODE_OK;
>> +}
>> +
>> +static struct drm_connector_helper_funcs 
>> ti_sn_bridge_connector_helper_funcs = {
>> +	.get_modes = ti_sn_bridge_connector_get_modes,
>> +	.mode_valid = ti_sn_bridge_connector_mode_valid,
>> +};
>> +
>> +static enum drm_connector_status
>> +ti_sn_bridge_connector_detect(struct drm_connector *connector, bool 
>> force)
>> +{
>> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
>> +
>> +	/**
>> +	 * TODO: Currently if drm_panel is present, then always
>> +	 * return the status as connected. Need to add support to detect
>> +	 * device state for no panel(hot pluggable) scenarios.
>> +	 */
>> +	if (pdata->panel)
>> +		return connector_status_connected;
>> +	else
>> +		return connector_status_disconnected;
>> +}
>> +
>> +static const struct drm_connector_funcs ti_sn_bridge_connector_funcs 
>> = {
>> +	.fill_modes = drm_helper_probe_single_connector_modes,
>> +	.detect = ti_sn_bridge_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,
>> +};
>> +
>> +static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge 
>> *bridge)
>> +{
>> +	return container_of(bridge, struct ti_sn_bridge, bridge);
>> +}
>> +
>> +static int ti_sn_bridge_read_device_rev(struct ti_sn_bridge *pdata)
>> +{
>> +	u8 rev = 0;
>> +	int ret = 0;
>> +
>> +	ret = ti_sn_bridge_read(pdata, SN_DEVICE_REV_REG, &rev, 1);
>> +	if (ret)
>> +		goto exit;
>> +
>> +	if (rev == SN_BRIDGE_REVISION_ID) {
>> +		DRM_DEBUG("ti_sn_bridge revision id: 0x%x\n", rev);
>> +	} else {
>> +		DRM_ERROR("ti_sn_bridge revision id mismatch\n");
> 
> I find it useful to print what the mismatch is so you can debug more
> effectively.
> 
>> +		ret = -EINVAL;
>> +	}
>> +
>> +exit:
>> +	return ret;
>> +}
>> +
>> +static const char * const ti_sn_bridge_supply_names[] = {
>> +	"vccio",
>> +	"vcca",
>> +};
>> +
>> +static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
>> +{
>> +	unsigned int i;
>> +	int ret;
>> +
>> +	pdata->num_supplies = ARRAY_SIZE(ti_sn_bridge_supply_names);
>> +
>> +	pdata->supplies = devm_kcalloc(pdata->dev, pdata->num_supplies,
>> +				     sizeof(*pdata->supplies), GFP_KERNEL);
>> +	if (!pdata->supplies)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < pdata->num_supplies; i++)
>> +		pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
>> +
>> +	ret = devm_regulator_bulk_get(pdata->dev,
>> +			pdata->num_supplies, pdata->supplies);
>> +
>> +	return ret;
> 
> Nit: you can combine the above two lines and get rid of 'ret'.
> 
>> +}
>> +
>> +static void ti_sn_bridge_attach_panel(struct ti_sn_bridge *pdata)
>> +{
>> +	struct device_node *panel_node, *port, *endpoint;
>> +	struct drm_panel *panel = NULL;
>> +
>> +	port = of_graph_get_port_by_id(pdata->dev->of_node, 1);
>> +	if (port) {
>> +		endpoint = of_get_child_by_name(port, "endpoint");
>> +		of_node_put(port);
>> +		if (!endpoint) {
>> +			dev_err(pdata->dev, "no output endpoint found\n");
>> +			goto error;
>> +		}
>> +
>> +		panel_node = of_graph_get_remote_port_parent(endpoint);
>> +		of_node_put(endpoint);
>> +		if (!panel_node) {
>> +			dev_err(pdata->dev, "no output node found\n");
>> +			goto error;
>> +		}
>> +
>> +		panel = of_drm_find_panel(panel_node);
>> +		of_node_put(panel_node);
>> +		if (!panel) {
>> +			dev_err(pdata->dev, "no panel node found\n");
>> +			goto error;
>> +		}
>> +	}
>> +
>> +	pdata->panel = panel;
>> +	drm_panel_attach(panel, &pdata->connector);
>> +
>> +	return;
>> +
>> +error:
>> +	pdata->panel = NULL;
> 
> Is this needed?  isn't pdata->panel already null when we come in?  If 
> so
> you can just return the error cases and remove the label.

Just making sure it is NULL, not some uninitialized value, since this 
will be used through out the
driver to detect if a panel is connected or not and HDP needs to be 
supported or not.
> 
>> +}
>> +
>> +static int ti_sn_bridge_attach(struct drm_bridge *bridge)
>> +{
>> +	struct mipi_dsi_host *host;
>> +	struct mipi_dsi_device *dsi;
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	int ret;
>> +	const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
>> +						   .channel = 0,
>> +						   .node = NULL,
>> +						 };
>> +
>> +	if (!bridge->encoder) {
>> +		DRM_ERROR("Parent encoder object not found");
> 
> Missing \n on the end of the string.
> 
>> +		return -ENODEV;
>> +	}
>> +
>> +	/* HPD not supported */
>> +	pdata->connector.polled = 0;
>> +
>> +	ret = drm_connector_init(bridge->dev, &pdata->connector,
>> +				 &ti_sn_bridge_connector_funcs,
>> +				 DRM_MODE_CONNECTOR_eDP);
> 
> The actual #define is CONNNECTOR_eDP?
yes.
> 
>> +	if (ret) {
>> +		DRM_ERROR("Failed to initialize connector with drm\n");
>> +		return ret;
>> +	}
>> +
>> +	drm_connector_helper_add(&pdata->connector,
>> +				 &ti_sn_bridge_connector_helper_funcs);
>> +	drm_mode_connector_attach_encoder(&pdata->connector, 
>> bridge->encoder);
>> +
>> +	host = of_find_mipi_dsi_host_by_node(pdata->host_node);
>> +	if (!host) {
>> +		DRM_ERROR("failed to find dsi host\n");
> 
> There isn't any tear down that needs to happen here?
> 
>> +		return -ENODEV;
>> +	}
>> +
>> +	dsi = mipi_dsi_device_register_full(host, &info);
>> +	if (IS_ERR(dsi)) {
>> +		DRM_ERROR("failed to create dsi device\n");
>> +		ret = PTR_ERR(dsi);
>> +		goto err_dsi_device;
> 
> Above you return directly but here you goto a return.  You should use 
> the same
> consistent approach in both cases.
> 
>> +	}
>> +
>> +	/* TODO: setting to 4 lanes always for now */
>> +	dsi->lanes = 4;
>> +	dsi->format = MIPI_DSI_FMT_RGB888;
>> +	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | 
>> MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
>> +			  MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
>> +
>> +	ret = mipi_dsi_attach(dsi);
>> +	if (ret < 0) {
>> +		DRM_ERROR("failed to attach dsi to host\n");
>> +		goto err_dsi_attach;
>> +	}
>> +
>> +	pdata->dsi = dsi;
>> +
>> +	DRM_DEBUG("bridge attached\n");
>> +	/* attach panel to bridge */
>> +	ti_sn_bridge_attach_panel(pdata);
>> +
>> +	return 0;
>> +
>> +err_dsi_attach:
>> +	mipi_dsi_device_unregister(dsi);
>> +err_dsi_device:
>> +	return ret;
>> +}
>> +
>> +static void ti_sn_bridge_mode_set(struct drm_bridge *bridge,
>> +				    struct drm_display_mode *mode,
>> +				    struct drm_display_mode *adj_mode)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +
>> +	DRM_DEBUG("mode_set: hdisplay=%d, vdisplay=%d, vrefresh=%d, 
>> clock=%d\n",
>> +		adj_mode->hdisplay, adj_mode->vdisplay,
>> +		adj_mode->vrefresh, adj_mode->clock);
>> +
>> +	drm_mode_copy(&pdata->curr_mode, adj_mode);
>> +}
>> +
>> +static void ti_sn_bridge_disable(struct drm_bridge *bridge)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	struct drm_panel *panel = pdata->panel;
>> +
>> +	if (panel) {
>> +		drm_panel_disable(panel);
>> +		drm_panel_unprepare(panel);
>> +	}
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +}
>> +
>> +static void ti_sn_bridge_enable(struct drm_bridge *bridge)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	struct drm_panel *panel = pdata->panel;
>> +	struct drm_display_mode *mode = &pdata->curr_mode;
>> +	u32 bit_rate_mhz, clk_freq_mhz;
>> +	u8 val = 0;
>> +
>> +	if (!pdata->num_modes)
>> +		return;
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, true);
>> +
>> +	if (panel)
>> +		drm_panel_prepare(panel);
>> +
>> +	/* CLK_SRC config */
>> +	ti_sn_bridge_write(pdata, SN_REFCLK_FREQ_REG,
>> +			(DPPLL_CLK_SRC_REFCLK | (SN_REF_CLK_19_2_MHZ << 0x1)));
>> +
>> +	/* DSI_A lane config */
>> +	ti_sn_bridge_read(pdata, SN_DSI_LANES_REG, &val, 0x1);
>> +	val |= ((0x4 - pdata->dsi->lanes) & 0x03) << 0x3;
>> +	ti_sn_bridge_write(pdata, SN_DSI_LANES_REG, val);
>> +
>> +	/* DP lane config */
>> +	ti_sn_bridge_read(pdata, SN_SSC_CONFIG_REG, &val, 0x1);
>> +	val |= ((pdata->dsi->lanes - 0x1) & 0x03) << 0x4;
>> +	ti_sn_bridge_write(pdata, SN_SSC_CONFIG_REG, val);
>> +
>> +	/* set dsi clk frequency value */
>> +	bit_rate_mhz = (mode->clock / 1000) *
>> +			mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
>> +	clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
>> +	/* for each increment in val, frequency increases by 5MHz */
>> +	val = (MIN_DSI_CLK_FREQ_MHZ / 0x5) +
>> +		(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 0x5) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_DSIA_CLK_FREQ_REG, val);
>> +
>> +	ti_sn_bridge_write(pdata, SN_DATARATE_CONFIG_REG, 0x80); /* set HBR 
>> */
>> +
>> +	ti_sn_bridge_write(pdata, SN_PLL_ENABLE_REG, 0x1); /* Enable PLL */
>> +	usleep_range(10000, 10001);
>> +
>> +	/* DPCD Register 0x0010A in Sink */
>> +	ti_sn_bridge_write(pdata, SN_AUX_WDATA0_REG, 0x01);
>> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_19_16_REG, 0x00);
>> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_15_8_REG, 0x01);
>> +	ti_sn_bridge_write(pdata, SN_AUX_ADDR_7_0_REG, 0x0A);
>> +	ti_sn_bridge_write(pdata, SN_AUX_LENGTH_REG, 0x01);
>> +	ti_sn_bridge_write(pdata, SN_AUX_CMD_REG, 0x81);
>> +	usleep_range(10000, 10001);
>> +
>> +	/* Semi auto link training mode */
>> +	ti_sn_bridge_write(pdata, SN_ML_TX_MODE_REG, 0x0A);
>> +	usleep_range(20000, 20001);
>> +
>> +	/* config video parameters */
>> +	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
>> +			mode->hdisplay & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG,
>> +			(mode->hdisplay >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
>> +			mode->vdisplay & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG,
>> +			(mode->hdisplay >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
>> +			(mode->htotal - mode->hsync_end) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
>> +			((mode->htotal - mode->hsync_end) >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
>> +			(mode->vtotal - mode->vsync_end) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
>> +			((mode->vtotal - mode->vsync_end) >> 0x8) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
>> +			(mode->hsync_start - mode->hdisplay) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_BACK_PORCH_REG,
>> +			(mode->vsync_start - mode->vdisplay) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
>> +			(mode->hsync_end - mode->hsync_start) & 0xFF);
>> +	ti_sn_bridge_write(pdata, SN_CHA_VERTICAL_FRONT_PORCH_REG,
>> +			(mode->vsync_end - mode->vsync_start) & 0xFF);
>> +	if (pdata->dsi->format == MIPI_DSI_FMT_RGB888)
>> +		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x00);
>> +	else
>> +		ti_sn_bridge_write(pdata, SN_DATA_FORMAT_REG, 0x01);
>> +	usleep_range(10000, 10001);
>> +
>> +	/* enable video stream */
>> +	ti_sn_bridge_read(pdata, SN_ENH_FRAME_REG, &val, 0x1);
>> +	val |= BIT(3);
>> +	ti_sn_bridge_write(pdata, SN_ENH_FRAME_REG, val);
>> +
>> +	if (panel)
>> +		drm_panel_enable(panel);
>> +}
>> +
>> +static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
>> +	.attach = ti_sn_bridge_attach,
>> +	.enable = ti_sn_bridge_enable,
>> +	.disable = ti_sn_bridge_disable,
>> +	.mode_set = ti_sn_bridge_mode_set,
>> +};
>> +
>> +static int ti_sn_bridge_parse_gpios(struct ti_sn_bridge *pdata)
>> +{
>> +	int ret = 0;
>> +
>> +	if (!pdata || !pdata->dev)
>> +		return -EINVAL;
> 
> Both of these are known to be valid at this point.
> 
>> +	pdata->enable_gpios = devm_gpiod_get_array(pdata->dev,
>> +						"enable", GPIOD_OUT_LOW);
>> +	if (IS_ERR(pdata->enable_gpios)) {
>> +		DRM_ERROR("failed to get enable gpio from DT\n");
>> +		ret = PTR_ERR(pdata->enable_gpios);
>> +		goto exit;
>> +	}
>> +
>> +	pdata->irq_gpio = devm_gpiod_get_optional(pdata->dev, "irq",
>> +						  GPIOD_OUT_LOW);
>> +	if (IS_ERR(pdata->irq_gpio))
>> +		DRM_ERROR("failed to get irq gpio from DT\n");
> 
> You need to set ret to PTR_ERR(pdata->irq_gpio) here.
> 
>> +
>> +exit:
>> +	return ret;
>> +}
>> +
>> +static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
>> +{
>> +	struct device_node *np = pdata->dev->of_node;
>> +	struct device_node *end_node;
>> +
>> +	end_node = of_graph_get_endpoint_by_regs(np, 0, 0);
>> +	if (!end_node) {
>> +		DRM_ERROR("remote endpoint not found\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	pdata->host_node = of_graph_get_remote_port_parent(end_node);
>> +	of_node_put(end_node);
>> +	if (!pdata->host_node) {
>> +		DRM_ERROR("remote node not found\n");
>> +		return -ENODEV;
>> +	}
>> +	of_node_put(pdata->host_node);
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_sn_bridge_probe(struct i2c_client *client,
>> +	 const struct i2c_device_id *id)
>> +{
>> +	struct ti_sn_bridge *pdata;
>> +	struct device_node *ddc_node;
>> +	int ret = 0;
>> +
>> +	if (!client || !client->dev.of_node) {
> 
> I doubt the i2c probe would send you a NULL value for client and you 
> are
> matching on a DT device, so there is a pretty solid argument that
> client->dev.of_node will be valid as well.
> 
>> +		DRM_ERROR("invalid input\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
>> +		DRM_ERROR("device doesn't support I2C\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	pdata = devm_kzalloc(&client->dev,
>> +		sizeof(struct ti_sn_bridge), GFP_KERNEL);
>> +	if (!pdata)
>> +		return -ENOMEM;
>> +
>> +	pdata->power_on = false;
>> +	pdata->dev = &client->dev;
>> +	pdata->i2c_client = client;
>> +	DRM_DEBUG("I2C address is %x\n", client->addr);
>> +
>> +	ret = ti_sn_bridge_parse_dsi_host(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to parse device tree\n");
> 
> ti_sn_bridge_parse_dsi_host prints a error message on every failure 
> point - you
> don't need another one here.
> 
>> +		goto err_dt_parse;
>> +	}
>> +
>> +	ret = ti_sn_bridge_parse_gpios(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to parse gpios from DT\n");
> 
> Same - all the paths are covered in the child function.
> 
>> +		goto err_dt_parse;
>> +	}
>> +
>> +	ret = ti_sn_bridge_parse_regulators(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to parse regulators\n");
>> +		goto err_dt_parse;
>> +	}
>> +
>> +	ddc_node = of_parse_phandle(pdata->dev->of_node, "ddc-i2c-bus", 0);
>> +	if (ddc_node) {
>> +		pdata->ddc = of_find_i2c_adapter_by_node(ddc_node);
>> +		of_node_put(ddc_node);
>> +		if (!pdata->ddc) {
>> +			dev_dbg(pdata->dev, "failed to read ddc node\n");
>> +			return -EPROBE_DEFER;
>> +		}
>> +	} else {
>> +		dev_dbg(pdata->dev, "no ddc property found\n");
>> +	}
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, true);
>> +	ret = ti_sn_bridge_read_device_rev(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to read chip rev\n");
> 
> ti_sn_bridge_read_device_rev has you covered with the error messages.
> 
>> +		goto err_config;
>> +	} else {
>> +		DRM_DEBUG("bridge chip enabled successfully\n");
>> +	}
> 
> And also the debug messages for whatever its worth.
> 
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +
>> +	i2c_set_clientdata(client, pdata);
>> +	dev_set_drvdata(&client->dev, pdata);
>> +
>> +	pdata->bridge.funcs = &ti_sn_bridge_funcs;
>> +	pdata->bridge.of_node = client->dev.of_node;
>> +
>> +	drm_bridge_add(&pdata->bridge);
>> +
>> +	return ret;
>> +
>> +err_config:
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +err_dt_parse:
>> +	devm_kfree(&client->dev, pdata);
>> +	return ret;
>> +}
>> +
>> +static int ti_sn_bridge_remove(struct i2c_client *client)
>> +{
>> +	int ret = -EINVAL;
>> +	struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
>> +
>> +	if (!pdata)
>> +		goto end;
> 
> pdata is very unlikely to be null here, but as long as this is a light 
> weight
> check, I don't think it hurts too much.
> 
>> +
>> +	mipi_dsi_detach(pdata->dsi);
>> +	mipi_dsi_device_unregister(pdata->dsi);
>> +
>> +	drm_bridge_remove(&pdata->bridge);
>> +
>> +	disable_irq(pdata->irq);
>> +	free_irq(pdata->irq, pdata);
>> +
>> +	ti_sn_bridge_gpio_configure(pdata, false);
>> +	i2c_put_adapter(pdata->ddc);
>> +
>> +	devm_kfree(&client->dev, pdata);
>> +
>> +end:
>> +	return ret;
>> +}
>> +
>> +static struct i2c_device_id ti_sn_bridge_id[] = {
>> +	{ "ti,sn65dsi86", 0},
>> +	{}
> 
> Missing a comma on the end of the empty entry.
> 
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
>> +
>> +static const struct of_device_id ti_sn_bridge_match_table[] = {
>> +	{.compatible = "ti,sn65dsi86"},
>> +	{}
> 
> Same.
> 
>> +};
>> +MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
>> +
>> +static struct i2c_driver ti_sn_bridge_driver = {
>> +	.driver = {
>> +		.name = "ti_sn65dsi86",
>> +		.owner = THIS_MODULE,
>> +		.of_match_table = ti_sn_bridge_match_table,
>> +	},
>> +	.probe = ti_sn_bridge_probe,
>> +	.remove = ti_sn_bridge_remove,
>> +	.id_table = ti_sn_bridge_id,
>> +};
>> +
>> +module_i2c_driver(ti_sn_bridge_driver);
>> +MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
> 
> Looks like you are missing the module license here.
> 
>> --
>> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora 
>> Forum,
>> a Linux Foundation Collaborative Project
>> 
>> _______________________________________________
>> dri-devel mailing list
>> dri-devel@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 4/4] dt-bindings: Add Innolux TV123WAM panel bindings
  2018-04-18 12:20     ` [[RFC]DPU PATCH 4/4] dt-bindings: Add Innolux TV123WAM panel bindings Sandeep Panda
@ 2018-04-24 14:43       ` Rob Herring
  2018-04-25  2:16         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  0 siblings, 1 reply; 21+ messages in thread
From: Rob Herring @ 2018-04-24 14:43 UTC (permalink / raw)
  To: Sandeep Panda
  Cc: devicetree, ryadav, linux-arm-msm, dri-devel, abhinavk,
	hoegsberg, freedreno, chandanu

On Wed, Apr 18, 2018 at 05:50:02PM +0530, Sandeep Panda wrote:
> The Innolux TV123WAM is a 12.3" eDP display panel
> with 2160x1440 resolution.

Why don't you just submit this for upstream?

> 
> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
> ---
>  .../bindings/display/panel/innolux,edp-2k-panel.txt | 21 +++++++++++++++++++++
>  1 file changed, 21 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
> 
> diff --git a/Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt b/Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
> new file mode 100644
> index 0000000..19f271c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
> @@ -0,0 +1,21 @@
> +Innolux TV123WAM 12.3 inch eDP display panel
> +
> +Required properties:
> +- power-supply: regulator to provide the supply voltage
> +- enable-gpios: GPIO pin to enable or disable the panel

Need to state the active state.

> +
> +Optional properties:
> +- backlight: phandle of the backlight device attached to the panel
> +
> +Example:
> +
> +	edp_panel: edp_panel {

panel {

> +		compatible = "innolux, edp_2k_panel";

spurious space                        ^

> +		reg = <0>;

Not documented. Should be dropped.

> +
> +		enable-gpios = <&msmgpio 32 GPIO_ACTIVE_HIGH>;
> +
> +		power-supply = <&pm8916_l8>;
> +

Remove the extra blank lines.

> +		backlight = <&backlight>;
> +	};
> -- 
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> a Linux Foundation Collaborative Project
> 
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 4/4] dt-bindings: Add Innolux TV123WAM panel bindings
  2018-04-24 14:43       ` Rob Herring
@ 2018-04-25  2:16         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
       [not found]           ` <cb59510635f0e7c010588a4603ae3f0f-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
  0 siblings, 1 reply; 21+ messages in thread
From: spanda-sgV2jX0FEOL9JmXXK+q4OQ @ 2018-04-25  2:16 UTC (permalink / raw)
  To: Rob Herring
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, ryadav-sgV2jX0FEOL9JmXXK+q4OQ,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	seanpaul-F7+t8E8rja9g9hUCZPvPmw, abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	jsanka-sgV2jX0FEOL9JmXXK+q4OQ, chandanu-sgV2jX0FEOL9JmXXK+q4OQ

On 2018-04-24 20:13, Rob Herring wrote:
> On Wed, Apr 18, 2018 at 05:50:02PM +0530, Sandeep Panda wrote:
>> The Innolux TV123WAM is a 12.3" eDP display panel
>> with 2160x1440 resolution.
> 
> Why don't you just submit this for upstream?
Sean Paul has suggested to reuse simple panel driver instead of having a 
new driver altogether. So all these dt-bindings wont be needed.
> 
>> 
>> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
>> ---
>>  .../bindings/display/panel/innolux,edp-2k-panel.txt | 21 
>> +++++++++++++++++++++
>>  1 file changed, 21 insertions(+)
>>  create mode 100644 
>> Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
>> 
>> diff --git 
>> a/Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt 
>> b/Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
>> new file mode 100644
>> index 0000000..19f271c
>> --- /dev/null
>> +++ 
>> b/Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
>> @@ -0,0 +1,21 @@
>> +Innolux TV123WAM 12.3 inch eDP display panel
>> +
>> +Required properties:
>> +- power-supply: regulator to provide the supply voltage
>> +- enable-gpios: GPIO pin to enable or disable the panel
> 
> Need to state the active state.
> 
>> +
>> +Optional properties:
>> +- backlight: phandle of the backlight device attached to the panel
>> +
>> +Example:
>> +
>> +	edp_panel: edp_panel {
> 
> panel {
> 
>> +		compatible = "innolux, edp_2k_panel";
> 
> spurious space                        ^
> 
>> +		reg = <0>;
> 
> Not documented. Should be dropped.
> 
>> +
>> +		enable-gpios = <&msmgpio 32 GPIO_ACTIVE_HIGH>;
>> +
>> +		power-supply = <&pm8916_l8>;
>> +
> 
> Remove the extra blank lines.
> 
>> +		backlight = <&backlight>;
>> +	};
>> --
>> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora 
>> Forum,
>> a Linux Foundation Collaborative Project
>> 
>> --
>> To unsubscribe from this list: send the line "unsubscribe devicetree" 
>> in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 4/4] dt-bindings: Add Innolux TV123WAM panel bindings
       [not found]           ` <cb59510635f0e7c010588a4603ae3f0f-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
@ 2018-04-25 14:40             ` Rob Herring
  0 siblings, 0 replies; 21+ messages in thread
From: Rob Herring @ 2018-04-25 14:40 UTC (permalink / raw)
  To: Sandeep Panda
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, ryadav-sgV2jX0FEOL9JmXXK+q4OQ,
	linux-arm-msm, dri-devel, Rob Clark,
	nganji-sgV2jX0FEOL9JmXXK+q4OQ, Sean Paul,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ, Kristian H. Kristensen,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	jsanka-sgV2jX0FEOL9JmXXK+q4OQ, chandanu-sgV2jX0FEOL9JmXXK+q4OQ

On Tue, Apr 24, 2018 at 9:16 PM,  <spanda@codeaurora.org> wrote:
> On 2018-04-24 20:13, Rob Herring wrote:
>>
>> On Wed, Apr 18, 2018 at 05:50:02PM +0530, Sandeep Panda wrote:
>>>
>>> The Innolux TV123WAM is a 12.3" eDP display panel
>>> with 2160x1440 resolution.
>>
>>
>> Why don't you just submit this for upstream?
>
> Sean Paul has suggested to reuse simple panel driver instead of having a new
> driver altogether. So all these dt-bindings wont be needed.

Yes it will unless there is already a binding for this panel. The
"simple panel" is not a binding, only a driver which matches on dozens
of different panels.
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp panel driver
  2018-04-18 12:19 ` [[RFC]DPU PATCH 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp panel driver Sandeep Panda
       [not found]   ` <1524054002-17869-1-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
  2018-04-18 14:12   ` [[RFC]DPU PATCH 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp panel driver Sean Paul
@ 2018-04-26  7:08   ` Andrzej Hajda
  2 siblings, 0 replies; 21+ messages in thread
From: Andrzej Hajda @ 2018-04-26  7:08 UTC (permalink / raw)
  To: Sandeep Panda, dri-devel, linux-arm-msm, freedreno, devicetree
  Cc: hoegsberg, chandanu, ryadav, abhinavk

On 18.04.2018 14:19, Sandeep Panda wrote:
> Changelog:
>
> v3 -> v4:
> Current patchset separates out eDP panel specific resources from sn65dsi86
> bridge driver and creates a separate driver for the innloux edp panel.
>
> Now bridge driver check if any panel is attached or not to get the supported
> modes. If any panel is attached then query from panel driver to get the modes,
> or else probe the provided i2c adapter to read the modes from EDID.
>
> Removed hardcoding of bridge init sequence. With this patchset bridge driver now
> will program the init sequence based on the current mode set by drm framework.
>
> Current patchset is not tested on actual bridge chip/panel.

One more thing, could you use proper prefixes in subject:
- no double braces,
- patch version,
- no DPU,
- RFC up to you, but in case of driver which you want to eventually
merge it seems unnecessary.

So subject should look more or less like:
[PATCH v4 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp
panel driver

Regards
Andrzej

>
> Sandeep Panda (4):
>   drm/bridge: add support for sn65dsi86 bridge driver
>   dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings
>   drm/panel: add Innolux TV123WAM eDP panel driver
>   dt-bindings: Add Innolux TV123WAM panel bindings
>
>  .../bindings/display/bridge/ti,sn65dsi86.txt       |  60 ++
>  .../display/panel/innolux,edp-2k-panel.txt         |  21 +
>  drivers/gpu/drm/bridge/ti-sn65dsi86.c              | 742 +++++++++++++++++++++
>  drivers/gpu/drm/panel/panel-innolux-tv123wam.c     | 299 +++++++++
>  4 files changed, 1122 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/ti,sn65dsi86.txt
>  create mode 100644 Documentation/devicetree/bindings/display/panel/innolux,edp-2k-panel.txt
>  create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
>  create mode 100644 drivers/gpu/drm/panel/panel-innolux-tv123wam.c
>

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver
  2018-04-20 19:36     ` Sean Paul
@ 2018-04-24  9:48       ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  0 siblings, 0 replies; 21+ messages in thread
From: spanda-sgV2jX0FEOL9JmXXK+q4OQ @ 2018-04-24  9:48 UTC (permalink / raw)
  To: Sean Paul
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, ryadav-sgV2jX0FEOL9JmXXK+q4OQ,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	jsanka-sgV2jX0FEOL9JmXXK+q4OQ, chandanu-sgV2jX0FEOL9JmXXK+q4OQ

On 2018-04-21 01:06, Sean Paul wrote:
> On Thu, Apr 19, 2018 at 11:26:05PM +0530, Sandeep Panda wrote:
>> Add support for TI's sn65dsi86 dsi2edp bridge chip.
>> The chip converts DSI transmitted signal to eDP signal,
>> which is fed to the connected eDP panel.
>> 
>> This chip can be controlled via either i2c interface or
>> dsi interface. Currently in driver all the control registers
>> are being accessed through i2c interface only.
>> Also as of now HPD support has not been added to bridge
>> chip driver.
>> 
>> Changes in v1:
>>  - Split the dt-bindings and the driver support into separate patches
>>    (Andrzej Hajda).
>>  - Use of gpiod APIs to parse and configure gpios instead of obsolete 
>> ones
>>    (Andrzej Hajda).
>>  - Use macros to define the register offsets (Andrzej Hajda).
>> 
>> Changes in v2:
>>  - Separate out edp panel specific HW resource handling from bridge
>>    driver and create a separate edp panel drivers to handle panel
>>    specific mode information and HW resources (Sean Paul).
>>  - Replace pr_* APIs to DRM_* APIs to log error or debug information
>>    (Sean Paul).
>>  - Remove some of the unnecessary structure/variable from driver (Sean
>>    Paul).
>>  - Rename the function and structure prefix "sn65dsi86" to 
>> "ti_sn_bridge"
>>    (Sean Paul / Rob Herring).
>>  - Remove most of the hard-coding and modified the bridge init 
>> sequence
>>    based on current mode (Sean Paul).
>>  - Remove the existing function to retrieve the EDID data and
>>    implemented this as an i2c_adapter and use drm_get_edid() (Sean 
>> Paul).
>>  - Remove the dummy irq handler implementation, will add back the
>>    proper irq handling later (Sean Paul).
>>  - Capture the required enable gpios in a single array based on dt 
>> entry
>>    instead of having individual descriptor for each gpio (Sean Paul).
>> 
>> Changes in v3:
>>  - Remove usage of irq_gpio and replace it as "interrupts" property 
>> (Rob
>>    Herring).
>>  - Remove the unnecessary header file inclusions (Sean Paul).
>>  - Rearrange the header files in alphabetical order (Sean Paul).
>>  - Use regmap interface to perform i2c transactions.
>>  - Update Copyright/License field and address other review comments
>>    (Jordan Crouse).
>> 
>> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
>> ---
>>  drivers/gpu/drm/bridge/ti-sn65dsi86.c | 690 
>> ++++++++++++++++++++++++++++++++++
> 
> What about Kconfig/Makefile?

Thought of adding this in another change once review is over. But will 
add to current change in next patchset.

> 
>>  1 file changed, 690 insertions(+)
>>  create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> 
>> diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c 
>> b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> new file mode 100644
>> index 0000000..a2a95f5
>> --- /dev/null
>> +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
>> @@ -0,0 +1,690 @@
>> +/*
>> + * Copyright (c) 2018, The Linux Foundation. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or 
>> modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only version 2 as published by the Free Software Foundation.
>> + *
>> + * 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.
>> + */
> 
> Use SPDX license

Will add back SPDX along with copyright in next patchset.

> 
>> +
>> +#include <drm/drmP.h>
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_mipi_dsi.h>
>> +#include <drm/drm_panel.h>
>> +#include <linux/gpio.h>
>> +#include <linux/i2c.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/of_graph.h>
>> +#include <linux/regmap.h>
>> +#include <linux/regulator/consumer.h>
>> +
>> +#define SN_BRIDGE_REVISION_ID 0x2
>> +
>> +/* Link Training specific registers */
>> +#define SN_DEVICE_REV_REG			0x08
>> +#define SN_REFCLK_FREQ_REG			0x0A
>> +#define SN_DSI_LANES_REG			0x10
>> +#define SN_DSIA_CLK_FREQ_REG			0x12
>> +#define SN_ENH_FRAME_REG			0x5A
>> +#define SN_SSC_CONFIG_REG			0x93
>> +#define SN_DATARATE_CONFIG_REG			0x94
>> +#define SN_PLL_ENABLE_REG			0x0D
>> +#define SN_SCRAMBLE_CONFIG_REG			0x95
>> +#define SN_AUX_WDATA0_REG			0x64
>> +#define SN_AUX_ADDR_19_16_REG			0x74
>> +#define SN_AUX_ADDR_15_8_REG			0x75
>> +#define SN_AUX_ADDR_7_0_REG			0x76
>> +#define SN_AUX_LENGTH_REG			0x77
>> +#define SN_AUX_CMD_REG				0x78
>> +#define SN_ML_TX_MODE_REG			0x96
>> +/* video config specific registers */
>> +#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG	0x20
>> +#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG	0x21
>> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG	0x24
>> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG	0x25
>> +#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG	0x2C
>> +#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG	0x2D
>> +#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG	0x30
>> +#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG	0x31
>> +#define SN_CHA_HORIZONTAL_BACK_PORCH_REG	0x34
>> +#define SN_CHA_VERTICAL_BACK_PORCH_REG		0x36
>> +#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG	0x38
>> +#define SN_CHA_VERTICAL_FRONT_PORCH_REG		0x3A
>> +#define SN_DATA_FORMAT_REG			0x5B
>> +
>> +#define MIN_DSI_CLK_FREQ_MHZ	40
>> +
>> +#define DPPLL_CLK_SRC_REFCLK	0
>> +#define DPPLL_CLK_SRC_DSICLK	1
>> +
>> +enum ti_sn_bridge_ref_clks {
>> +	SN_REF_CLK_12_MHZ = 0,
>> +	SN_REF_CLK_19_2_MHZ,
>> +	SN_REF_CLK_26_MHZ,
>> +	SN_REF_CLK_27_MHZ,
>> +	SN_REF_CLK_38_4_MHZ,
>> +	SN_REF_CLK_MAX,
>> +};
>> +
>> +struct ti_sn_bridge {
>> +	struct device *dev;
>> +	struct regmap *regmap;
>> +	struct drm_bridge bridge;
>> +	struct drm_connector connector;
>> +	struct device_node *host_node;
>> +	struct mipi_dsi_device *dsi;
>> +	/* handle to the connected eDP panel */
>> +	struct drm_panel *panel;
>> +	/* list of gpios needed to enable the bridge functionality */
>> +	struct gpio_descs *enable_gpios;
> 
> From my reading of the datasheet, these gpios can be used for a variety 
> of
> things, not just tied to enable (input/output). So, separate enable 
> input
> from the gpios, and make the gpios configurable via dt.
> 

So are you suggesting something like,
struct gpio_desc *enable; --> for enable gpio
struct gpio_descs *misc; --> for other gpios like aux_channel, mentioned 
in the schematic


>> +	int *value_array;
>> +	int intp_irq;
> 
> What's this used for?
> 
Will remove this for now and will add back while adding support for 
HPD/interrupts.

>> +	unsigned int num_supplies;
>> +	struct regulator_bulk_data *supplies;
>> +	struct i2c_client *i2c_client;
>> +	struct i2c_adapter *ddc;
>> +	u32 num_modes;
>> +	struct drm_display_mode curr_mode;
>> +};
>> +
>> +static const struct regmap_range ti_sn_bridge_volatile_ranges[] = {
>> +	{ .range_min = 0, .range_max = 0xff },
>> +};
>> +
>> +static const struct regmap_access_table ti_sn_bridge_volatile_table = 
>> {
>> +	.yes_ranges = ti_sn_bridge_volatile_ranges,
>> +	.n_yes_ranges = ARRAY_SIZE(ti_sn_bridge_volatile_ranges),
>> +};
>> +
>> +static const struct regmap_config ti_sn_bridge_regmap_config = {
>> +	.reg_bits = 8,
>> +	.val_bits = 8,
>> +	.volatile_table = &ti_sn_bridge_volatile_table,
>> +	.cache_type = REGCACHE_NONE,
>> +};
>> +
>> +static void ti_sn_bridge_gpio_configure(struct ti_sn_bridge *pdata, 
>> bool on)
>> +{
>> +	int value, i = 0;
>> +
>> +	value = on ? 1 : 0;
>> +
>> +	if (pdata->enable_gpios->ndescs && pdata->value_array) {
>> +		for (i = 0; i < pdata->enable_gpios->ndescs; i++)
>> +			pdata->value_array[i] = value;
>> +		gpiod_set_array_value(pdata->enable_gpios->ndescs,
>> +			pdata->enable_gpios->desc, pdata->value_array);
>> +	}
>> +
>> +	DRM_DEBUG("config to: %d\n", value);
>> +}
>> +
>> +static void ti_sn_bridge_power_ctrl(struct ti_sn_bridge *pdata, bool 
>> enable)
>> +{
>> +	static int ref_count;
> 
> I'm a little confused why you chose a static variable instead of 
> stashing this
> in the pdata. Also, this isn't locked, so you will have races between 
> setting
> ref_count and doing the enable/disable.

Want to limit the ref count to this function only, instead of exposing 
the ref count to
outisde. I will add mutex protection to this function, since it is 
called from multiple
places. Missed it.

> 
>> +
>> +	if (enable)
>> +		ref_count++;
>> +	else
>> +		ref_count--;
>> +
>> +	if (enable && (ref_count == 1)) {
>> +		ti_sn_bridge_gpio_configure(pdata, true);
>> +
>> +		if (regulator_bulk_enable(pdata->num_supplies,
>> +						pdata->supplies)) {
>> +			DRM_ERROR("bridge regulator enable failed\n");
>> +			return;
> 
> Why return void if you can fail?

We are not using the return value of this function anywhere. So just 
wanted to
put a print here in case, even though regulator_bulk_enable is highly 
unlikely to fail.

> 
>> +		}
>> +	} else if (!enable && !ref_count) {
>> +		regulator_bulk_disable(pdata->num_supplies, pdata->supplies);
>> +
>> +		ti_sn_bridge_gpio_configure(pdata, false);
>> +	} else {
>> +		DRM_DEBUG("ti_sn_bridge power ctrl: %d refcount: %d\n",
>> +							enable, ref_count);
>> +	}
>> +}
>> +
>> +/* Connector funcs */
>> +static struct ti_sn_bridge *
>> +connector_to_ti_sn_bridge(struct drm_connector *connector)
>> +{
>> +	return container_of(connector, struct ti_sn_bridge, connector);
>> +}
>> +
>> +static int ti_sn_bridge_connector_get_modes(struct drm_connector 
>> *connector)
>> +{
>> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
>> +	struct drm_panel *panel = pdata->panel;
>> +	struct edid *edid;
>> +
>> +	if (panel) {
>> +		DRM_DEBUG("get mode from connected drm_panel\n");
>> +		pdata->num_modes = drm_panel_get_modes(panel);
> 
> Just return early instead of indenting the rest of the function.
OK.
> 
>> +	} else {
>> +		/* get from EDID */
>> +		if (!pdata->ddc)
>> +			return 0;
>> +		ti_sn_bridge_power_ctrl(pdata, true);
>> +		edid = drm_get_edid(connector, pdata->ddc);
>> +		if (edid) {
>> +			drm_mode_connector_update_edid_property(connector,
>> +								edid);
>> +			pdata->num_modes = drm_add_edid_modes(connector, edid);
>> +			drm_edid_to_eld(connector, edid);
> 
> This is a static function inside drm_edid.c. How did this even compile?
> 
  This is not static in drm_edid.c, Atleast i can see this as a 
non-static function in 4.14 kernel, it is used by other drivers also.

>> +			kfree(edid);
>> +		} else {
>> +			DRM_DEBUG("failed to get edid\n");
> 
> Flip the if (edid) condition around and exit early.
OK
> 
>> +		}
>> +		ti_sn_bridge_power_ctrl(pdata, false);
>> +	}
>> +
>> +	return pdata->num_modes;
>> +}
>> +
>> +static enum drm_mode_status
>> +ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
>> +			     struct drm_display_mode *mode)
>> +{
>> +	/* maximum supported resolution is 4K at 60 fps */
>> +	if (mode->clock > 594000)
>> +		return MODE_CLOCK_HIGH;
>> +
>> +	return MODE_OK;
>> +}
>> +
>> +static struct drm_connector_helper_funcs 
>> ti_sn_bridge_connector_helper_funcs = {
>> +	.get_modes = ti_sn_bridge_connector_get_modes,
>> +	.mode_valid = ti_sn_bridge_connector_mode_valid,
>> +};
>> +
>> +static enum drm_connector_status
>> +ti_sn_bridge_connector_detect(struct drm_connector *connector, bool 
>> force)
>> +{
>> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
>> +
>> +	/**
>> +	 * TODO: Currently if drm_panel is present, then always
>> +	 * return the status as connected. Need to add support to detect
>> +	 * device state for no panel(hot pluggable) scenarios.
> 
> Perhaps then you should return unknown instead of disconnected.
OK
> 
>> +	 */
>> +	if (pdata->panel)
>> +		return connector_status_connected;
>> +	else
>> +		return connector_status_disconnected;
>> +}
>> +
>> +static const struct drm_connector_funcs ti_sn_bridge_connector_funcs 
>> = {
>> +	.fill_modes = drm_helper_probe_single_connector_modes,
>> +	.detect = ti_sn_bridge_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,
>> +};
>> +
>> +static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge 
>> *bridge)
>> +{
>> +	return container_of(bridge, struct ti_sn_bridge, bridge);
>> +}
>> +
>> +static int ti_sn_bridge_read_device_rev(struct ti_sn_bridge *pdata)
>> +{
>> +	unsigned int rev = 0;
>> +	int ret = 0;
>> +
>> +	ret = regmap_read(pdata->regmap, SN_DEVICE_REV_REG, &rev);
>> +	if (ret)
>> +		goto exit;
> 
> Why do you need a label just to return?
Ok, some kernel approvers dont allow multiple return statements so using 
label wherever possible through out the driver.
If its ok to have numerous return statements then will follow that 
through out.
> 
>> +
>> +	if (rev == SN_BRIDGE_REVISION_ID) {
>> +		DRM_DEBUG("ti_sn_bridge revision id: 0x%x\n", rev);
> 
> The revision is hardcoded, so this print isn't really useful.
> 
Ok, will remove this print from here and add a debug print in end of 
probe, the intention is to indicate that probe has been successful.
>> +	} else {
>> +		DRM_ERROR("ti_sn_bridge revision id: 0x%x mismatch\n", rev);
>> +		ret = -EINVAL;
>> +	}
>> +
>> +exit:
>> +	return ret;
>> +}
>> +
>> +static const char * const ti_sn_bridge_supply_names[] = {
>> +	"vccio",
>> +	"vcca",
>> +};
>> +
>> +static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
>> +{
>> +	unsigned int i;
>> +
>> +	pdata->num_supplies = ARRAY_SIZE(ti_sn_bridge_supply_names);
>> +
>> +	pdata->supplies = devm_kcalloc(pdata->dev, pdata->num_supplies,
>> +				     sizeof(*pdata->supplies), GFP_KERNEL);
>> +	if (!pdata->supplies)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < pdata->num_supplies; i++)
>> +		pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
>> +
>> +	return devm_regulator_bulk_get(pdata->dev,
>> +				pdata->num_supplies, pdata->supplies);
>> +}
>> +
>> +static int ti_sn_bridge_attach_panel(struct ti_sn_bridge *pdata)
>> +{
>> +	struct device_node *panel_node, *port, *endpoint;
>> +
>> +	pdata->panel = NULL;
>> +	port = of_graph_get_port_by_id(pdata->dev->of_node, 1);
>> +	if (port) {
>> +		endpoint = of_get_child_by_name(port, "endpoint");
>> +		of_node_put(port);
>> +		if (!endpoint) {
>> +			dev_err(pdata->dev, "no output endpoint found\n");
> 
> Why are you switching to dev_err?
will make this DRM_ERROR
> 
>> +			return -EINVAL;
>> +		}
>> +
>> +		panel_node = of_graph_get_remote_port_parent(endpoint);
>> +		of_node_put(endpoint);
>> +		if (!panel_node) {
>> +			dev_err(pdata->dev, "no output node found\n");
>> +			return -EINVAL;
>> +		}
>> +
>> +		pdata->panel = of_drm_find_panel(panel_node);
>> +		of_node_put(panel_node);
>> +		if (!pdata->panel) {
>> +			dev_err(pdata->dev, "no panel node found\n");
>> +			return -EINVAL;
>> +		}
>> +	}
>> +
>> +	drm_panel_attach(pdata->panel, &pdata->connector);
> 
> Why is this outside the if statement? If port is NULL, then 
> pdata->panel is
> NULL, and the first thing drm_panel_attach does is dereference panel.
Thanks for pointing out this, drm_panel_attach() should be inside if 
statement.
> 
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_sn_bridge_attach(struct drm_bridge *bridge)
>> +{
>> +	struct mipi_dsi_host *host;
>> +	struct mipi_dsi_device *dsi;
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	int ret;
>> +	const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
>> +						   .channel = 0,
>> +						   .node = NULL,
>> +						 };
>> +
>> +	if (!bridge->encoder) {
>> +		DRM_ERROR("Parent encoder object not found\n");
>> +		ret = -ENODEV;
>> +		goto error;
>> +	}
>> +
>> +	/* HPD not supported */
>> +	pdata->connector.polled = 0;
>> +
>> +	ret = drm_connector_init(bridge->dev, &pdata->connector,
>> +				 &ti_sn_bridge_connector_funcs,
>> +				 DRM_MODE_CONNECTOR_eDP);
>> +	if (ret) {
>> +		DRM_ERROR("Failed to initialize connector with drm\n");
>> +		goto error;
>> +	}
>> +
>> +	drm_connector_helper_add(&pdata->connector,
>> +				 &ti_sn_bridge_connector_helper_funcs);
>> +	drm_mode_connector_attach_encoder(&pdata->connector, 
>> bridge->encoder);
>> +
>> +	host = of_find_mipi_dsi_host_by_node(pdata->host_node);
>> +	if (!host) {
>> +		DRM_ERROR("failed to find dsi host\n");
>> +		ret = -ENODEV;
>> +		goto error;
>> +	}
>> +
>> +	dsi = mipi_dsi_device_register_full(host, &info);
>> +	if (IS_ERR(dsi)) {
>> +		DRM_ERROR("failed to create dsi device\n");
>> +		ret = PTR_ERR(dsi);
>> +		goto error;
> 
> All of these error labels just return ret. So, why not just return 
> directly?
> 
>> +	}
>> +
>> +	/* TODO: setting to 4 lanes always for now */
>> +	dsi->lanes = 4;
>> +	dsi->format = MIPI_DSI_FMT_RGB888;
>> +	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | 
>> MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
>> +			  MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
>> +
>> +	ret = mipi_dsi_attach(dsi);
>> +	if (ret < 0) {
>> +		DRM_ERROR("failed to attach dsi to host\n");
>> +		goto err_dsi_attach;
>> +	}
>> +
>> +	pdata->dsi = dsi;
>> +
>> +	DRM_DEBUG("bridge attached\n");
>> +	/* attach panel to bridge */
>> +	ti_sn_bridge_attach_panel(pdata);
>> +
>> +	return 0;
>> +
>> +err_dsi_attach:
>> +	mipi_dsi_device_unregister(dsi);
>> +error:
>> +	return ret;
>> +}
>> +
>> +static void ti_sn_bridge_mode_set(struct drm_bridge *bridge,
>> +				    struct drm_display_mode *mode,
>> +				    struct drm_display_mode *adj_mode)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +
>> +	DRM_DEBUG("mode_set: hdisplay=%d, vdisplay=%d, vrefresh=%d, 
>> clock=%d\n",
>> +		adj_mode->hdisplay, adj_mode->vdisplay,
>> +		adj_mode->vrefresh, adj_mode->clock);
>> +
>> +	drm_mode_copy(&pdata->curr_mode, adj_mode);
>> +}
>> +
>> +static void ti_sn_bridge_disable(struct drm_bridge *bridge)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	struct drm_panel *panel = pdata->panel;
>> +
>> +	if (panel) {
>> +		drm_panel_disable(panel);
>> +		drm_panel_unprepare(panel);
>> +	}
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +}
>> +
>> +static void ti_sn_bridge_enable(struct drm_bridge *bridge)
>> +{
>> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
>> +	struct drm_panel *panel = pdata->panel;
>> +	struct drm_display_mode *mode = &pdata->curr_mode;
>> +	unsigned int bit_rate_mhz, clk_freq_mhz;
>> +	unsigned int val = 0;
>> +
>> +	if (!pdata->num_modes)
>> +		return;
> 
> This relies on get_modes() to be called before enable, which is not 
> guaranteed
> (user defined modes). You also are racing with get_modes(), so it's 
> possible
> this is inaccurate anyways. Is there a good reason to gate enable on 
> num_modes?
> 

I will remove the num_modes check from here, framework should ensure 
bridge_enable
gets called only after set_mode is called.
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, true);
>> +
>> +	if (panel)
>> +		drm_panel_prepare(panel);
>> +
>> +	/* CLK_SRC config */
>> +	regmap_write(pdata->regmap, SN_REFCLK_FREQ_REG,
>> +			(DPPLL_CLK_SRC_REFCLK | (SN_REF_CLK_19_2_MHZ << 0x1)));
>> +
>> +	/* DSI_A lane config */
>> +	regmap_read(pdata->regmap, SN_DSI_LANES_REG, &val);
>> +	val |= ((0x4 - pdata->dsi->lanes) & 0x03) << 0x3;
> 
> These masks should be #defined, and imo the 4 would read better if it 
> were not
> listed in hex. Same goes below, masks and offsets need to be #defined.

Ok will remove hex notation.
> 
> You're also doing a bitwise OR on the current value of lanes. So since 
> the
> default value is 0x3, you'll never actually change anything. You need 
> to clear
> the current value before setting the new value. This applies below as 
> well.

yes, i should mask the bits first and then program the new value.
> 
> 
>> +	regmap_write(pdata->regmap, SN_DSI_LANES_REG, val);
>> +
>> +	/* DP lane config */
>> +	regmap_read(pdata->regmap, SN_SSC_CONFIG_REG, &val);
>> +	val |= ((pdata->dsi->lanes - 0x1) & 0x03) << 0x4;
>> +	regmap_write(pdata->regmap, SN_SSC_CONFIG_REG, val);
>> +
>> +	/* set dsi clk frequency value */
>> +	bit_rate_mhz = (mode->clock / 1000) *
>> +			mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
>> +	clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
>> +	/* for each increment in val, frequency increases by 5MHz */
>> +	val = (MIN_DSI_CLK_FREQ_MHZ / 0x5) +
>> +		(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 0x5) & 0xFF);
>> +	regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val);
>> +
>> +	regmap_write(pdata->regmap, SN_DATARATE_CONFIG_REG, 0x80); /* set 
>> HBR */
> 
> This should also be a function of the pclk, right?
Yes, will program this based on pclk.
> 
>> +
>> +	regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0x1); /* Enable PLL 
>> */
>> +	usleep_range(10000, 10050);
>> +
>> +	/* DPCD Register 0x0010A in Sink */
>> +	regmap_write(pdata->regmap, SN_AUX_WDATA0_REG, 0x01);
>> +	regmap_write(pdata->regmap, SN_AUX_ADDR_19_16_REG, 0x00);
>> +	regmap_write(pdata->regmap, SN_AUX_ADDR_15_8_REG, 0x01);
>> +	regmap_write(pdata->regmap, SN_AUX_ADDR_7_0_REG, 0x0A);
>> +	regmap_write(pdata->regmap, SN_AUX_LENGTH_REG, 0x01);
>> +	regmap_write(pdata->regmap, SN_AUX_CMD_REG, 0x81);
> 
> What are you doing here and why?
As per the datasheet we need to enable ASSR((Alternate Scrambler Seed 
Reset) in edp panel before we start link training.
This is step is done for that. It will common for all edp panels.

        "The SN65DSI86 only supports ASSR Display Authentication method 
and this method is enabled by default. An eDP panel
must support this Authentication method. Software will need to enable 
this method in the eDP panel at DisplayPort address
0x0010A."

> 
>> +	usleep_range(10000, 10050);
>> +
>> +	/* Semi auto link training mode */
>> +	regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A);
>> +	usleep_range(20000, 20050);
> 
> You should use msleep if you need to sleep for ~20ms
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/timers/timers-howto.txt
> 
>> +
>> +	/* config video parameters */
>> +	regmap_write(pdata->regmap, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
>> +			mode->hdisplay & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG,
>> +			(mode->hdisplay >> 0x8) & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
>> +			mode->vdisplay & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG,
>> +			(mode->vdisplay >> 0x8) & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
>> +			(mode->hsync_end - mode->hsync_start) & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
>> +			((mode->hsync_end - mode->hsync_start) >> 0x8) & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
>> +			(mode->vsync_end - mode->vsync_start) & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
>> +			((mode->vsync_end - mode->vsync_start) >> 0x8) & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
>> +			(mode->htotal - mode->hsync_end) & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_VERTICAL_BACK_PORCH_REG,
>> +			(mode->vtotal - mode->vsync_end) & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
>> +			(mode->hsync_start - mode->hdisplay) & 0xFF);
>> +	regmap_write(pdata->regmap, SN_CHA_VERTICAL_FRONT_PORCH_REG,
>> +			(mode->vsync_start - mode->vdisplay) & 0xFF);
>> +	if (pdata->dsi->format == MIPI_DSI_FMT_RGB888)
>> +		regmap_write(pdata->regmap, SN_DATA_FORMAT_REG, 0x00);
>> +	else
>> +		regmap_write(pdata->regmap, SN_DATA_FORMAT_REG, 0x01);
> 
> The format is hardcoded above.

> 
>> +	usleep_range(10000, 10050);
>> +
>> +	/* enable video stream */
>> +	regmap_read(pdata->regmap, SN_ENH_FRAME_REG, &val);
>> +	val |= BIT(3);
>> +	regmap_write(pdata->regmap, SN_ENH_FRAME_REG, val);
>> +
>> +	if (panel)
>> +		drm_panel_enable(panel);
>> +}
>> +
>> +static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
>> +	.attach = ti_sn_bridge_attach,
>> +	.enable = ti_sn_bridge_enable,
>> +	.disable = ti_sn_bridge_disable,
>> +	.mode_set = ti_sn_bridge_mode_set,
>> +};
>> +
>> +static int ti_sn_bridge_parse_gpios(struct ti_sn_bridge *pdata)
>> +{
>> +	int ret = 0;
>> +
>> +	pdata->enable_gpios = devm_gpiod_get_array(pdata->dev,
>> +						"enable", GPIOD_OUT_LOW);
>> +	if (IS_ERR(pdata->enable_gpios)) {
>> +		DRM_ERROR("failed to get enable gpio from DT\n");
>> +		ret = PTR_ERR(pdata->enable_gpios);
>> +		goto exit;
>> +	}
>> +	/* initialize the value array */
>> +	pdata->value_array = kmalloc(pdata->enable_gpios->ndescs * 
>> sizeof(int),
>> +								GFP_KERNEL);
>> +	if (!pdata->value_array) {
>> +		ret = -ENOMEM;
>> +		goto exit;
>> +	}
>> +	memset(pdata->value_array, 0,
>> +			pdata->enable_gpios->ndescs * sizeof(int));
>> +
>> +exit:
>> +	return ret;
>> +}
>> +
>> +static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
>> +{
>> +	struct device_node *np = pdata->dev->of_node;
>> +	struct device_node *end_node;
>> +
>> +	end_node = of_graph_get_endpoint_by_regs(np, 0, 0);
>> +	if (!end_node) {
>> +		DRM_ERROR("remote endpoint not found\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	pdata->host_node = of_graph_get_remote_port_parent(end_node);
>> +	of_node_put(end_node);
>> +	if (!pdata->host_node) {
>> +		DRM_ERROR("remote node not found\n");
>> +		return -ENODEV;
>> +	}
>> +	of_node_put(pdata->host_node);
>> +
>> +	return 0;
>> +}
>> +
>> +static int ti_sn_bridge_probe(struct i2c_client *client,
>> +	 const struct i2c_device_id *id)
>> +{
>> +	struct ti_sn_bridge *pdata;
>> +	struct device_node *ddc_node;
>> +	int ret = 0;
>> +
>> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
>> +		DRM_ERROR("device doesn't support I2C\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	pdata = devm_kzalloc(&client->dev,
>> +		sizeof(struct ti_sn_bridge), GFP_KERNEL);
>> +	if (!pdata)
>> +		return -ENOMEM;
>> +
>> +	pdata->dev = &client->dev;
>> +	pdata->i2c_client = client;
>> +	pdata->intp_irq = client->irq;
>> +	DRM_DEBUG("I2C address is %x\n", client->addr);
>> +
>> +	pdata->regmap = devm_regmap_init_i2c(client,
>> +				&ti_sn_bridge_regmap_config);
>> +	if (IS_ERR(pdata->regmap))
>> +		return PTR_ERR(pdata->regmap);
>> +
>> +	ret = ti_sn_bridge_parse_dsi_host(pdata);
>> +	if (ret)
>> +		goto err_dt_parse;
>> +
>> +	ret = ti_sn_bridge_parse_gpios(pdata);
>> +	if (ret)
>> +		goto err_dt_parse;
>> +
>> +	ret = ti_sn_bridge_parse_regulators(pdata);
>> +	if (ret) {
>> +		DRM_ERROR("failed to parse regulators\n");
>> +		goto err_reg_parse;
>> +	}
>> +
>> +	ddc_node = of_parse_phandle(pdata->dev->of_node, "ddc-i2c-bus", 0);
>> +	if (ddc_node) {
>> +		pdata->ddc = of_find_i2c_adapter_by_node(ddc_node);
>> +		of_node_put(ddc_node);
>> +		if (!pdata->ddc) {
>> +			dev_dbg(pdata->dev, "failed to read ddc node\n");
>> +			return -EPROBE_DEFER;
>> +		}
>> +	} else {
>> +		dev_dbg(pdata->dev, "no ddc property found\n");
>> +	}
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, true);
>> +	ret = ti_sn_bridge_read_device_rev(pdata);
>> +	if (ret)
>> +		goto err_config;
>> +	ti_sn_bridge_power_ctrl(pdata, false);
>> +
>> +	i2c_set_clientdata(client, pdata);
>> +	dev_set_drvdata(&client->dev, pdata);
> 
> drvdata doesn't seem to be used anywhere.
> 
>> +
>> +	pdata->bridge.funcs = &ti_sn_bridge_funcs;
>> +	pdata->bridge.of_node = client->dev.of_node;
>> +
>> +	drm_bridge_add(&pdata->bridge);
>> +
>> +	return ret;
>> +
>> +err_config:
>> +	ti_sn_bridge_power_ctrl(pdata, false);
> 
> Just turn it off before you check ret (similar to the of_node_put calls
> throughout the driver).
OK
> 
>> +err_reg_parse:
>> +	kfree(pdata->value_array);
>> +err_dt_parse:
>> +	devm_kfree(&client->dev, pdata);
> 
> If it's device managed, you don't need explicit frees.
Yes. will remove this.
> 
>> +	return ret;
>> +}
>> +
>> +static int ti_sn_bridge_remove(struct i2c_client *client)
>> +{
>> +	int ret = -EINVAL;
>> +	struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
>> +
>> +	if (!pdata)
>> +		goto end;
>> +
>> +	mipi_dsi_detach(pdata->dsi);
>> +	mipi_dsi_device_unregister(pdata->dsi);
>> +
>> +	ti_sn_bridge_power_ctrl(pdata, false);
> 
> If you want to refcount power_ctrl, you'll need to make sure that it's
> _actually_ off.
checked all the calls to power_ctrl in driver, we dont need a call to 
power_ctrl.
bridge_disable should be the place where last call to power_ctrl needs 
to be made.

> 
>> +	kfree(pdata->value_array);
>> +
>> +	drm_bridge_remove(&pdata->bridge);
>> +	i2c_put_adapter(pdata->ddc);
>> +
>> +	devm_kfree(&client->dev, pdata);
>> +
>> +end:
>> +	return ret;
>> +}
>> +
>> +static struct i2c_device_id ti_sn_bridge_id[] = {
>> +	{ "ti,sn65dsi86", 0},
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
>> +
>> +static const struct of_device_id ti_sn_bridge_match_table[] = {
>> +	{.compatible = "ti,sn65dsi86"},
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
>> +
>> +static struct i2c_driver ti_sn_bridge_driver = {
>> +	.driver = {
>> +		.name = "ti_sn65dsi86",
>> +		.owner = THIS_MODULE,
>> +		.of_match_table = ti_sn_bridge_match_table,
>> +	},
>> +	.probe = ti_sn_bridge_probe,
>> +	.remove = ti_sn_bridge_remove,
>> +	.id_table = ti_sn_bridge_id,
>> +};
>> +
>> +module_i2c_driver(ti_sn_bridge_driver);
>> +MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
>> +MODULE_LICENSE("GPL");
> 
> GPL v2?
> 
>> --
>> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora 
>> Forum,
>> a Linux Foundation Collaborative Project
>> 
_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver
  2018-04-19 17:56   ` [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver Sandeep Panda
@ 2018-04-20 19:36     ` Sean Paul
  2018-04-24  9:48       ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
  0 siblings, 1 reply; 21+ messages in thread
From: Sean Paul @ 2018-04-20 19:36 UTC (permalink / raw)
  To: Sandeep Panda
  Cc: devicetree, ryadav, linux-arm-msm, dri-devel, abhinavk,
	hoegsberg, freedreno, chandanu

On Thu, Apr 19, 2018 at 11:26:05PM +0530, Sandeep Panda wrote:
> Add support for TI's sn65dsi86 dsi2edp bridge chip.
> The chip converts DSI transmitted signal to eDP signal,
> which is fed to the connected eDP panel.
> 
> This chip can be controlled via either i2c interface or
> dsi interface. Currently in driver all the control registers
> are being accessed through i2c interface only.
> Also as of now HPD support has not been added to bridge
> chip driver.
> 
> Changes in v1:
>  - Split the dt-bindings and the driver support into separate patches
>    (Andrzej Hajda).
>  - Use of gpiod APIs to parse and configure gpios instead of obsolete ones
>    (Andrzej Hajda).
>  - Use macros to define the register offsets (Andrzej Hajda).
> 
> Changes in v2:
>  - Separate out edp panel specific HW resource handling from bridge
>    driver and create a separate edp panel drivers to handle panel
>    specific mode information and HW resources (Sean Paul).
>  - Replace pr_* APIs to DRM_* APIs to log error or debug information
>    (Sean Paul).
>  - Remove some of the unnecessary structure/variable from driver (Sean
>    Paul).
>  - Rename the function and structure prefix "sn65dsi86" to "ti_sn_bridge"
>    (Sean Paul / Rob Herring).
>  - Remove most of the hard-coding and modified the bridge init sequence
>    based on current mode (Sean Paul).
>  - Remove the existing function to retrieve the EDID data and
>    implemented this as an i2c_adapter and use drm_get_edid() (Sean Paul).
>  - Remove the dummy irq handler implementation, will add back the
>    proper irq handling later (Sean Paul).
>  - Capture the required enable gpios in a single array based on dt entry
>    instead of having individual descriptor for each gpio (Sean Paul).
> 
> Changes in v3:
>  - Remove usage of irq_gpio and replace it as "interrupts" property (Rob
>    Herring).
>  - Remove the unnecessary header file inclusions (Sean Paul).
>  - Rearrange the header files in alphabetical order (Sean Paul).
>  - Use regmap interface to perform i2c transactions.
>  - Update Copyright/License field and address other review comments
>    (Jordan Crouse).
> 
> Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
> ---
>  drivers/gpu/drm/bridge/ti-sn65dsi86.c | 690 ++++++++++++++++++++++++++++++++++

What about Kconfig/Makefile?

>  1 file changed, 690 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c
> 
> diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> new file mode 100644
> index 0000000..a2a95f5
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
> @@ -0,0 +1,690 @@
> +/*
> + * Copyright (c) 2018, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * 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.
> + */

Use SPDX license

> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_graph.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +
> +#define SN_BRIDGE_REVISION_ID 0x2
> +
> +/* Link Training specific registers */
> +#define SN_DEVICE_REV_REG			0x08
> +#define SN_REFCLK_FREQ_REG			0x0A
> +#define SN_DSI_LANES_REG			0x10
> +#define SN_DSIA_CLK_FREQ_REG			0x12
> +#define SN_ENH_FRAME_REG			0x5A
> +#define SN_SSC_CONFIG_REG			0x93
> +#define SN_DATARATE_CONFIG_REG			0x94
> +#define SN_PLL_ENABLE_REG			0x0D
> +#define SN_SCRAMBLE_CONFIG_REG			0x95
> +#define SN_AUX_WDATA0_REG			0x64
> +#define SN_AUX_ADDR_19_16_REG			0x74
> +#define SN_AUX_ADDR_15_8_REG			0x75
> +#define SN_AUX_ADDR_7_0_REG			0x76
> +#define SN_AUX_LENGTH_REG			0x77
> +#define SN_AUX_CMD_REG				0x78
> +#define SN_ML_TX_MODE_REG			0x96
> +/* video config specific registers */
> +#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG	0x20
> +#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG	0x21
> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG	0x24
> +#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG	0x25
> +#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG	0x2C
> +#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG	0x2D
> +#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG	0x30
> +#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG	0x31
> +#define SN_CHA_HORIZONTAL_BACK_PORCH_REG	0x34
> +#define SN_CHA_VERTICAL_BACK_PORCH_REG		0x36
> +#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG	0x38
> +#define SN_CHA_VERTICAL_FRONT_PORCH_REG		0x3A
> +#define SN_DATA_FORMAT_REG			0x5B
> +
> +#define MIN_DSI_CLK_FREQ_MHZ	40
> +
> +#define DPPLL_CLK_SRC_REFCLK	0
> +#define DPPLL_CLK_SRC_DSICLK	1
> +
> +enum ti_sn_bridge_ref_clks {
> +	SN_REF_CLK_12_MHZ = 0,
> +	SN_REF_CLK_19_2_MHZ,
> +	SN_REF_CLK_26_MHZ,
> +	SN_REF_CLK_27_MHZ,
> +	SN_REF_CLK_38_4_MHZ,
> +	SN_REF_CLK_MAX,
> +};
> +
> +struct ti_sn_bridge {
> +	struct device *dev;
> +	struct regmap *regmap;
> +	struct drm_bridge bridge;
> +	struct drm_connector connector;
> +	struct device_node *host_node;
> +	struct mipi_dsi_device *dsi;
> +	/* handle to the connected eDP panel */
> +	struct drm_panel *panel;
> +	/* list of gpios needed to enable the bridge functionality */
> +	struct gpio_descs *enable_gpios;

From my reading of the datasheet, these gpios can be used for a variety of
things, not just tied to enable (input/output). So, separate enable input
from the gpios, and make the gpios configurable via dt.

> +	int *value_array;
> +	int intp_irq;

What's this used for?

> +	unsigned int num_supplies;
> +	struct regulator_bulk_data *supplies;
> +	struct i2c_client *i2c_client;
> +	struct i2c_adapter *ddc;
> +	u32 num_modes;
> +	struct drm_display_mode curr_mode;
> +};
> +
> +static const struct regmap_range ti_sn_bridge_volatile_ranges[] = {
> +	{ .range_min = 0, .range_max = 0xff },
> +};
> +
> +static const struct regmap_access_table ti_sn_bridge_volatile_table = {
> +	.yes_ranges = ti_sn_bridge_volatile_ranges,
> +	.n_yes_ranges = ARRAY_SIZE(ti_sn_bridge_volatile_ranges),
> +};
> +
> +static const struct regmap_config ti_sn_bridge_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.volatile_table = &ti_sn_bridge_volatile_table,
> +	.cache_type = REGCACHE_NONE,
> +};
> +
> +static void ti_sn_bridge_gpio_configure(struct ti_sn_bridge *pdata, bool on)
> +{
> +	int value, i = 0;
> +
> +	value = on ? 1 : 0;
> +
> +	if (pdata->enable_gpios->ndescs && pdata->value_array) {
> +		for (i = 0; i < pdata->enable_gpios->ndescs; i++)
> +			pdata->value_array[i] = value;
> +		gpiod_set_array_value(pdata->enable_gpios->ndescs,
> +			pdata->enable_gpios->desc, pdata->value_array);
> +	}
> +
> +	DRM_DEBUG("config to: %d\n", value);
> +}
> +
> +static void ti_sn_bridge_power_ctrl(struct ti_sn_bridge *pdata, bool enable)
> +{
> +	static int ref_count;

I'm a little confused why you chose a static variable instead of stashing this
in the pdata. Also, this isn't locked, so you will have races between setting
ref_count and doing the enable/disable.

> +
> +	if (enable)
> +		ref_count++;
> +	else
> +		ref_count--;
> +
> +	if (enable && (ref_count == 1)) {
> +		ti_sn_bridge_gpio_configure(pdata, true);
> +
> +		if (regulator_bulk_enable(pdata->num_supplies,
> +						pdata->supplies)) {
> +			DRM_ERROR("bridge regulator enable failed\n");
> +			return;

Why return void if you can fail?

> +		}
> +	} else if (!enable && !ref_count) {
> +		regulator_bulk_disable(pdata->num_supplies, pdata->supplies);
> +
> +		ti_sn_bridge_gpio_configure(pdata, false);
> +	} else {
> +		DRM_DEBUG("ti_sn_bridge power ctrl: %d refcount: %d\n",
> +							enable, ref_count);
> +	}
> +}
> +
> +/* Connector funcs */
> +static struct ti_sn_bridge *
> +connector_to_ti_sn_bridge(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct ti_sn_bridge, connector);
> +}
> +
> +static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
> +	struct drm_panel *panel = pdata->panel;
> +	struct edid *edid;
> +
> +	if (panel) {
> +		DRM_DEBUG("get mode from connected drm_panel\n");
> +		pdata->num_modes = drm_panel_get_modes(panel);

Just return early instead of indenting the rest of the function.

> +	} else {
> +		/* get from EDID */
> +		if (!pdata->ddc)
> +			return 0;
> +		ti_sn_bridge_power_ctrl(pdata, true);
> +		edid = drm_get_edid(connector, pdata->ddc);
> +		if (edid) {
> +			drm_mode_connector_update_edid_property(connector,
> +								edid);
> +			pdata->num_modes = drm_add_edid_modes(connector, edid);
> +			drm_edid_to_eld(connector, edid);

This is a static function inside drm_edid.c. How did this even compile?

> +			kfree(edid);
> +		} else {
> +			DRM_DEBUG("failed to get edid\n");

Flip the if (edid) condition around and exit early.

> +		}
> +		ti_sn_bridge_power_ctrl(pdata, false);
> +	}
> +
> +	return pdata->num_modes;
> +}
> +
> +static enum drm_mode_status
> +ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
> +			     struct drm_display_mode *mode)
> +{
> +	/* maximum supported resolution is 4K at 60 fps */
> +	if (mode->clock > 594000)
> +		return MODE_CLOCK_HIGH;
> +
> +	return MODE_OK;
> +}
> +
> +static struct drm_connector_helper_funcs ti_sn_bridge_connector_helper_funcs = {
> +	.get_modes = ti_sn_bridge_connector_get_modes,
> +	.mode_valid = ti_sn_bridge_connector_mode_valid,
> +};
> +
> +static enum drm_connector_status
> +ti_sn_bridge_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
> +
> +	/**
> +	 * TODO: Currently if drm_panel is present, then always
> +	 * return the status as connected. Need to add support to detect
> +	 * device state for no panel(hot pluggable) scenarios.

Perhaps then you should return unknown instead of disconnected.

> +	 */
> +	if (pdata->panel)
> +		return connector_status_connected;
> +	else
> +		return connector_status_disconnected;
> +}
> +
> +static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = {
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = ti_sn_bridge_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,
> +};
> +
> +static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct ti_sn_bridge, bridge);
> +}
> +
> +static int ti_sn_bridge_read_device_rev(struct ti_sn_bridge *pdata)
> +{
> +	unsigned int rev = 0;
> +	int ret = 0;
> +
> +	ret = regmap_read(pdata->regmap, SN_DEVICE_REV_REG, &rev);
> +	if (ret)
> +		goto exit;

Why do you need a label just to return?

> +
> +	if (rev == SN_BRIDGE_REVISION_ID) {
> +		DRM_DEBUG("ti_sn_bridge revision id: 0x%x\n", rev);

The revision is hardcoded, so this print isn't really useful.

> +	} else {
> +		DRM_ERROR("ti_sn_bridge revision id: 0x%x mismatch\n", rev);
> +		ret = -EINVAL;
> +	}
> +
> +exit:
> +	return ret;
> +}
> +
> +static const char * const ti_sn_bridge_supply_names[] = {
> +	"vccio",
> +	"vcca",
> +};
> +
> +static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
> +{
> +	unsigned int i;
> +
> +	pdata->num_supplies = ARRAY_SIZE(ti_sn_bridge_supply_names);
> +
> +	pdata->supplies = devm_kcalloc(pdata->dev, pdata->num_supplies,
> +				     sizeof(*pdata->supplies), GFP_KERNEL);
> +	if (!pdata->supplies)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < pdata->num_supplies; i++)
> +		pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
> +
> +	return devm_regulator_bulk_get(pdata->dev,
> +				pdata->num_supplies, pdata->supplies);
> +}
> +
> +static int ti_sn_bridge_attach_panel(struct ti_sn_bridge *pdata)
> +{
> +	struct device_node *panel_node, *port, *endpoint;
> +
> +	pdata->panel = NULL;
> +	port = of_graph_get_port_by_id(pdata->dev->of_node, 1);
> +	if (port) {
> +		endpoint = of_get_child_by_name(port, "endpoint");
> +		of_node_put(port);
> +		if (!endpoint) {
> +			dev_err(pdata->dev, "no output endpoint found\n");

Why are you switching to dev_err?

> +			return -EINVAL;
> +		}
> +
> +		panel_node = of_graph_get_remote_port_parent(endpoint);
> +		of_node_put(endpoint);
> +		if (!panel_node) {
> +			dev_err(pdata->dev, "no output node found\n");
> +			return -EINVAL;
> +		}
> +
> +		pdata->panel = of_drm_find_panel(panel_node);
> +		of_node_put(panel_node);
> +		if (!pdata->panel) {
> +			dev_err(pdata->dev, "no panel node found\n");
> +			return -EINVAL;
> +		}
> +	}
> +
> +	drm_panel_attach(pdata->panel, &pdata->connector);

Why is this outside the if statement? If port is NULL, then pdata->panel is
NULL, and the first thing drm_panel_attach does is dereference panel.

> +
> +	return 0;
> +}
> +
> +static int ti_sn_bridge_attach(struct drm_bridge *bridge)
> +{
> +	struct mipi_dsi_host *host;
> +	struct mipi_dsi_device *dsi;
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +	int ret;
> +	const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
> +						   .channel = 0,
> +						   .node = NULL,
> +						 };
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found\n");
> +		ret = -ENODEV;
> +		goto error;
> +	}
> +
> +	/* HPD not supported */
> +	pdata->connector.polled = 0;
> +
> +	ret = drm_connector_init(bridge->dev, &pdata->connector,
> +				 &ti_sn_bridge_connector_funcs,
> +				 DRM_MODE_CONNECTOR_eDP);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		goto error;
> +	}
> +
> +	drm_connector_helper_add(&pdata->connector,
> +				 &ti_sn_bridge_connector_helper_funcs);
> +	drm_mode_connector_attach_encoder(&pdata->connector, bridge->encoder);
> +
> +	host = of_find_mipi_dsi_host_by_node(pdata->host_node);
> +	if (!host) {
> +		DRM_ERROR("failed to find dsi host\n");
> +		ret = -ENODEV;
> +		goto error;
> +	}
> +
> +	dsi = mipi_dsi_device_register_full(host, &info);
> +	if (IS_ERR(dsi)) {
> +		DRM_ERROR("failed to create dsi device\n");
> +		ret = PTR_ERR(dsi);
> +		goto error;

All of these error labels just return ret. So, why not just return directly?

> +	}
> +
> +	/* TODO: setting to 4 lanes always for now */
> +	dsi->lanes = 4;
> +	dsi->format = MIPI_DSI_FMT_RGB888;
> +	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
> +			  MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
> +
> +	ret = mipi_dsi_attach(dsi);
> +	if (ret < 0) {
> +		DRM_ERROR("failed to attach dsi to host\n");
> +		goto err_dsi_attach;
> +	}
> +
> +	pdata->dsi = dsi;
> +
> +	DRM_DEBUG("bridge attached\n");
> +	/* attach panel to bridge */
> +	ti_sn_bridge_attach_panel(pdata);
> +
> +	return 0;
> +
> +err_dsi_attach:
> +	mipi_dsi_device_unregister(dsi);
> +error:
> +	return ret;
> +}
> +
> +static void ti_sn_bridge_mode_set(struct drm_bridge *bridge,
> +				    struct drm_display_mode *mode,
> +				    struct drm_display_mode *adj_mode)
> +{
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +
> +	DRM_DEBUG("mode_set: hdisplay=%d, vdisplay=%d, vrefresh=%d, clock=%d\n",
> +		adj_mode->hdisplay, adj_mode->vdisplay,
> +		adj_mode->vrefresh, adj_mode->clock);
> +
> +	drm_mode_copy(&pdata->curr_mode, adj_mode);
> +}
> +
> +static void ti_sn_bridge_disable(struct drm_bridge *bridge)
> +{
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +	struct drm_panel *panel = pdata->panel;
> +
> +	if (panel) {
> +		drm_panel_disable(panel);
> +		drm_panel_unprepare(panel);
> +	}
> +
> +	ti_sn_bridge_power_ctrl(pdata, false);
> +}
> +
> +static void ti_sn_bridge_enable(struct drm_bridge *bridge)
> +{
> +	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
> +	struct drm_panel *panel = pdata->panel;
> +	struct drm_display_mode *mode = &pdata->curr_mode;
> +	unsigned int bit_rate_mhz, clk_freq_mhz;
> +	unsigned int val = 0;
> +
> +	if (!pdata->num_modes)
> +		return;

This relies on get_modes() to be called before enable, which is not guaranteed
(user defined modes). You also are racing with get_modes(), so it's possible
this is inaccurate anyways. Is there a good reason to gate enable on num_modes?

> +
> +	ti_sn_bridge_power_ctrl(pdata, true);
> +
> +	if (panel)
> +		drm_panel_prepare(panel);
> +
> +	/* CLK_SRC config */
> +	regmap_write(pdata->regmap, SN_REFCLK_FREQ_REG,
> +			(DPPLL_CLK_SRC_REFCLK | (SN_REF_CLK_19_2_MHZ << 0x1)));
> +
> +	/* DSI_A lane config */
> +	regmap_read(pdata->regmap, SN_DSI_LANES_REG, &val);
> +	val |= ((0x4 - pdata->dsi->lanes) & 0x03) << 0x3;

These masks should be #defined, and imo the 4 would read better if it were not
listed in hex. Same goes below, masks and offsets need to be #defined.

You're also doing a bitwise OR on the current value of lanes. So since the
default value is 0x3, you'll never actually change anything. You need to clear
the current value before setting the new value. This applies below as well.


> +	regmap_write(pdata->regmap, SN_DSI_LANES_REG, val);
> +
> +	/* DP lane config */
> +	regmap_read(pdata->regmap, SN_SSC_CONFIG_REG, &val);
> +	val |= ((pdata->dsi->lanes - 0x1) & 0x03) << 0x4;
> +	regmap_write(pdata->regmap, SN_SSC_CONFIG_REG, val);
> +
> +	/* set dsi clk frequency value */
> +	bit_rate_mhz = (mode->clock / 1000) *
> +			mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
> +	clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
> +	/* for each increment in val, frequency increases by 5MHz */
> +	val = (MIN_DSI_CLK_FREQ_MHZ / 0x5) +
> +		(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 0x5) & 0xFF);
> +	regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val);
> +
> +	regmap_write(pdata->regmap, SN_DATARATE_CONFIG_REG, 0x80); /* set HBR */

This should also be a function of the pclk, right?

> +
> +	regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0x1); /* Enable PLL */
> +	usleep_range(10000, 10050);
> +
> +	/* DPCD Register 0x0010A in Sink */
> +	regmap_write(pdata->regmap, SN_AUX_WDATA0_REG, 0x01);
> +	regmap_write(pdata->regmap, SN_AUX_ADDR_19_16_REG, 0x00);
> +	regmap_write(pdata->regmap, SN_AUX_ADDR_15_8_REG, 0x01);
> +	regmap_write(pdata->regmap, SN_AUX_ADDR_7_0_REG, 0x0A);
> +	regmap_write(pdata->regmap, SN_AUX_LENGTH_REG, 0x01);
> +	regmap_write(pdata->regmap, SN_AUX_CMD_REG, 0x81);

What are you doing here and why?

> +	usleep_range(10000, 10050);
> +
> +	/* Semi auto link training mode */
> +	regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A);
> +	usleep_range(20000, 20050);

You should use msleep if you need to sleep for ~20ms

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/timers/timers-howto.txt

> +
> +	/* config video parameters */
> +	regmap_write(pdata->regmap, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
> +			mode->hdisplay & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG,
> +			(mode->hdisplay >> 0x8) & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
> +			mode->vdisplay & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG,
> +			(mode->vdisplay >> 0x8) & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
> +			(mode->hsync_end - mode->hsync_start) & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
> +			((mode->hsync_end - mode->hsync_start) >> 0x8) & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
> +			(mode->vsync_end - mode->vsync_start) & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
> +			((mode->vsync_end - mode->vsync_start) >> 0x8) & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
> +			(mode->htotal - mode->hsync_end) & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_VERTICAL_BACK_PORCH_REG,
> +			(mode->vtotal - mode->vsync_end) & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
> +			(mode->hsync_start - mode->hdisplay) & 0xFF);
> +	regmap_write(pdata->regmap, SN_CHA_VERTICAL_FRONT_PORCH_REG,
> +			(mode->vsync_start - mode->vdisplay) & 0xFF);
> +	if (pdata->dsi->format == MIPI_DSI_FMT_RGB888)
> +		regmap_write(pdata->regmap, SN_DATA_FORMAT_REG, 0x00);
> +	else
> +		regmap_write(pdata->regmap, SN_DATA_FORMAT_REG, 0x01);

The format is hardcoded above.

> +	usleep_range(10000, 10050);
> +
> +	/* enable video stream */
> +	regmap_read(pdata->regmap, SN_ENH_FRAME_REG, &val);
> +	val |= BIT(3);
> +	regmap_write(pdata->regmap, SN_ENH_FRAME_REG, val);
> +
> +	if (panel)
> +		drm_panel_enable(panel);
> +}
> +
> +static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
> +	.attach = ti_sn_bridge_attach,
> +	.enable = ti_sn_bridge_enable,
> +	.disable = ti_sn_bridge_disable,
> +	.mode_set = ti_sn_bridge_mode_set,
> +};
> +
> +static int ti_sn_bridge_parse_gpios(struct ti_sn_bridge *pdata)
> +{
> +	int ret = 0;
> +
> +	pdata->enable_gpios = devm_gpiod_get_array(pdata->dev,
> +						"enable", GPIOD_OUT_LOW);
> +	if (IS_ERR(pdata->enable_gpios)) {
> +		DRM_ERROR("failed to get enable gpio from DT\n");
> +		ret = PTR_ERR(pdata->enable_gpios);
> +		goto exit;
> +	}
> +	/* initialize the value array */
> +	pdata->value_array = kmalloc(pdata->enable_gpios->ndescs * sizeof(int),
> +								GFP_KERNEL);
> +	if (!pdata->value_array) {
> +		ret = -ENOMEM;
> +		goto exit;
> +	}
> +	memset(pdata->value_array, 0,
> +			pdata->enable_gpios->ndescs * sizeof(int));
> +
> +exit:
> +	return ret;
> +}
> +
> +static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
> +{
> +	struct device_node *np = pdata->dev->of_node;
> +	struct device_node *end_node;
> +
> +	end_node = of_graph_get_endpoint_by_regs(np, 0, 0);
> +	if (!end_node) {
> +		DRM_ERROR("remote endpoint not found\n");
> +		return -ENODEV;
> +	}
> +
> +	pdata->host_node = of_graph_get_remote_port_parent(end_node);
> +	of_node_put(end_node);
> +	if (!pdata->host_node) {
> +		DRM_ERROR("remote node not found\n");
> +		return -ENODEV;
> +	}
> +	of_node_put(pdata->host_node);
> +
> +	return 0;
> +}
> +
> +static int ti_sn_bridge_probe(struct i2c_client *client,
> +	 const struct i2c_device_id *id)
> +{
> +	struct ti_sn_bridge *pdata;
> +	struct device_node *ddc_node;
> +	int ret = 0;
> +
> +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> +		DRM_ERROR("device doesn't support I2C\n");
> +		return -ENODEV;
> +	}
> +
> +	pdata = devm_kzalloc(&client->dev,
> +		sizeof(struct ti_sn_bridge), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->dev = &client->dev;
> +	pdata->i2c_client = client;
> +	pdata->intp_irq = client->irq;
> +	DRM_DEBUG("I2C address is %x\n", client->addr);
> +
> +	pdata->regmap = devm_regmap_init_i2c(client,
> +				&ti_sn_bridge_regmap_config);
> +	if (IS_ERR(pdata->regmap))
> +		return PTR_ERR(pdata->regmap);
> +
> +	ret = ti_sn_bridge_parse_dsi_host(pdata);
> +	if (ret)
> +		goto err_dt_parse;
> +
> +	ret = ti_sn_bridge_parse_gpios(pdata);
> +	if (ret)
> +		goto err_dt_parse;
> +
> +	ret = ti_sn_bridge_parse_regulators(pdata);
> +	if (ret) {
> +		DRM_ERROR("failed to parse regulators\n");
> +		goto err_reg_parse;
> +	}
> +
> +	ddc_node = of_parse_phandle(pdata->dev->of_node, "ddc-i2c-bus", 0);
> +	if (ddc_node) {
> +		pdata->ddc = of_find_i2c_adapter_by_node(ddc_node);
> +		of_node_put(ddc_node);
> +		if (!pdata->ddc) {
> +			dev_dbg(pdata->dev, "failed to read ddc node\n");
> +			return -EPROBE_DEFER;
> +		}
> +	} else {
> +		dev_dbg(pdata->dev, "no ddc property found\n");
> +	}
> +
> +	ti_sn_bridge_power_ctrl(pdata, true);
> +	ret = ti_sn_bridge_read_device_rev(pdata);
> +	if (ret)
> +		goto err_config;
> +	ti_sn_bridge_power_ctrl(pdata, false);
> +
> +	i2c_set_clientdata(client, pdata);
> +	dev_set_drvdata(&client->dev, pdata);

drvdata doesn't seem to be used anywhere.

> +
> +	pdata->bridge.funcs = &ti_sn_bridge_funcs;
> +	pdata->bridge.of_node = client->dev.of_node;
> +
> +	drm_bridge_add(&pdata->bridge);
> +
> +	return ret;
> +
> +err_config:
> +	ti_sn_bridge_power_ctrl(pdata, false);

Just turn it off before you check ret (similar to the of_node_put calls
throughout the driver).

> +err_reg_parse:
> +	kfree(pdata->value_array);
> +err_dt_parse:
> +	devm_kfree(&client->dev, pdata);

If it's device managed, you don't need explicit frees.

> +	return ret;
> +}
> +
> +static int ti_sn_bridge_remove(struct i2c_client *client)
> +{
> +	int ret = -EINVAL;
> +	struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
> +
> +	if (!pdata)
> +		goto end;
> +
> +	mipi_dsi_detach(pdata->dsi);
> +	mipi_dsi_device_unregister(pdata->dsi);
> +
> +	ti_sn_bridge_power_ctrl(pdata, false);

If you want to refcount power_ctrl, you'll need to make sure that it's
_actually_ off.

> +	kfree(pdata->value_array);
> +
> +	drm_bridge_remove(&pdata->bridge);
> +	i2c_put_adapter(pdata->ddc);
> +
> +	devm_kfree(&client->dev, pdata);
> +
> +end:
> +	return ret;
> +}
> +
> +static struct i2c_device_id ti_sn_bridge_id[] = {
> +	{ "ti,sn65dsi86", 0},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
> +
> +static const struct of_device_id ti_sn_bridge_match_table[] = {
> +	{.compatible = "ti,sn65dsi86"},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
> +
> +static struct i2c_driver ti_sn_bridge_driver = {
> +	.driver = {
> +		.name = "ti_sn65dsi86",
> +		.owner = THIS_MODULE,
> +		.of_match_table = ti_sn_bridge_match_table,
> +	},
> +	.probe = ti_sn_bridge_probe,
> +	.remove = ti_sn_bridge_remove,
> +	.id_table = ti_sn_bridge_id,
> +};
> +
> +module_i2c_driver(ti_sn_bridge_driver);
> +MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
> +MODULE_LICENSE("GPL");

GPL v2?

> -- 
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> a Linux Foundation Collaborative Project
> 

-- 
Sean Paul, Software Engineer, Google / Chromium OS
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver
       [not found] ` <1524160568-27583-1-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
@ 2018-04-19 17:56   ` Sandeep Panda
  2018-04-20 19:36     ` Sean Paul
  0 siblings, 1 reply; 21+ messages in thread
From: Sandeep Panda @ 2018-04-19 17:56 UTC (permalink / raw)
  To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: ryadav-sgV2jX0FEOL9JmXXK+q4OQ, Sandeep Panda,
	abhinavk-sgV2jX0FEOL9JmXXK+q4OQ,
	robdclark-Re5JQEeQqe8AvxtiuMwx3w, nganji-sgV2jX0FEOL9JmXXK+q4OQ,
	seanpaul-F7+t8E8rja9g9hUCZPvPmw,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw, jsanka-sgV2jX0FEOL9JmXXK+q4OQ,
	chandanu-sgV2jX0FEOL9JmXXK+q4OQ

Add support for TI's sn65dsi86 dsi2edp bridge chip.
The chip converts DSI transmitted signal to eDP signal,
which is fed to the connected eDP panel.

This chip can be controlled via either i2c interface or
dsi interface. Currently in driver all the control registers
are being accessed through i2c interface only.
Also as of now HPD support has not been added to bridge
chip driver.

Changes in v1:
 - Split the dt-bindings and the driver support into separate patches
   (Andrzej Hajda).
 - Use of gpiod APIs to parse and configure gpios instead of obsolete ones
   (Andrzej Hajda).
 - Use macros to define the register offsets (Andrzej Hajda).

Changes in v2:
 - Separate out edp panel specific HW resource handling from bridge
   driver and create a separate edp panel drivers to handle panel
   specific mode information and HW resources (Sean Paul).
 - Replace pr_* APIs to DRM_* APIs to log error or debug information
   (Sean Paul).
 - Remove some of the unnecessary structure/variable from driver (Sean
   Paul).
 - Rename the function and structure prefix "sn65dsi86" to "ti_sn_bridge"
   (Sean Paul / Rob Herring).
 - Remove most of the hard-coding and modified the bridge init sequence
   based on current mode (Sean Paul).
 - Remove the existing function to retrieve the EDID data and
   implemented this as an i2c_adapter and use drm_get_edid() (Sean Paul).
 - Remove the dummy irq handler implementation, will add back the
   proper irq handling later (Sean Paul).
 - Capture the required enable gpios in a single array based on dt entry
   instead of having individual descriptor for each gpio (Sean Paul).

Changes in v3:
 - Remove usage of irq_gpio and replace it as "interrupts" property (Rob
   Herring).
 - Remove the unnecessary header file inclusions (Sean Paul).
 - Rearrange the header files in alphabetical order (Sean Paul).
 - Use regmap interface to perform i2c transactions.
 - Update Copyright/License field and address other review comments
   (Jordan Crouse).

Signed-off-by: Sandeep Panda <spanda@codeaurora.org>
---
 drivers/gpu/drm/bridge/ti-sn65dsi86.c | 690 ++++++++++++++++++++++++++++++++++
 1 file changed, 690 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c

diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
new file mode 100644
index 0000000..a2a95f5
--- /dev/null
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -0,0 +1,690 @@
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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 <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define SN_BRIDGE_REVISION_ID 0x2
+
+/* Link Training specific registers */
+#define SN_DEVICE_REV_REG			0x08
+#define SN_REFCLK_FREQ_REG			0x0A
+#define SN_DSI_LANES_REG			0x10
+#define SN_DSIA_CLK_FREQ_REG			0x12
+#define SN_ENH_FRAME_REG			0x5A
+#define SN_SSC_CONFIG_REG			0x93
+#define SN_DATARATE_CONFIG_REG			0x94
+#define SN_PLL_ENABLE_REG			0x0D
+#define SN_SCRAMBLE_CONFIG_REG			0x95
+#define SN_AUX_WDATA0_REG			0x64
+#define SN_AUX_ADDR_19_16_REG			0x74
+#define SN_AUX_ADDR_15_8_REG			0x75
+#define SN_AUX_ADDR_7_0_REG			0x76
+#define SN_AUX_LENGTH_REG			0x77
+#define SN_AUX_CMD_REG				0x78
+#define SN_ML_TX_MODE_REG			0x96
+/* video config specific registers */
+#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG	0x20
+#define SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG	0x21
+#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG	0x24
+#define SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG	0x25
+#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG	0x2C
+#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG	0x2D
+#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG	0x30
+#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG	0x31
+#define SN_CHA_HORIZONTAL_BACK_PORCH_REG	0x34
+#define SN_CHA_VERTICAL_BACK_PORCH_REG		0x36
+#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG	0x38
+#define SN_CHA_VERTICAL_FRONT_PORCH_REG		0x3A
+#define SN_DATA_FORMAT_REG			0x5B
+
+#define MIN_DSI_CLK_FREQ_MHZ	40
+
+#define DPPLL_CLK_SRC_REFCLK	0
+#define DPPLL_CLK_SRC_DSICLK	1
+
+enum ti_sn_bridge_ref_clks {
+	SN_REF_CLK_12_MHZ = 0,
+	SN_REF_CLK_19_2_MHZ,
+	SN_REF_CLK_26_MHZ,
+	SN_REF_CLK_27_MHZ,
+	SN_REF_CLK_38_4_MHZ,
+	SN_REF_CLK_MAX,
+};
+
+struct ti_sn_bridge {
+	struct device *dev;
+	struct regmap *regmap;
+	struct drm_bridge bridge;
+	struct drm_connector connector;
+	struct device_node *host_node;
+	struct mipi_dsi_device *dsi;
+	/* handle to the connected eDP panel */
+	struct drm_panel *panel;
+	/* list of gpios needed to enable the bridge functionality */
+	struct gpio_descs *enable_gpios;
+	int *value_array;
+	int intp_irq;
+	unsigned int num_supplies;
+	struct regulator_bulk_data *supplies;
+	struct i2c_client *i2c_client;
+	struct i2c_adapter *ddc;
+	u32 num_modes;
+	struct drm_display_mode curr_mode;
+};
+
+static const struct regmap_range ti_sn_bridge_volatile_ranges[] = {
+	{ .range_min = 0, .range_max = 0xff },
+};
+
+static const struct regmap_access_table ti_sn_bridge_volatile_table = {
+	.yes_ranges = ti_sn_bridge_volatile_ranges,
+	.n_yes_ranges = ARRAY_SIZE(ti_sn_bridge_volatile_ranges),
+};
+
+static const struct regmap_config ti_sn_bridge_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.volatile_table = &ti_sn_bridge_volatile_table,
+	.cache_type = REGCACHE_NONE,
+};
+
+static void ti_sn_bridge_gpio_configure(struct ti_sn_bridge *pdata, bool on)
+{
+	int value, i = 0;
+
+	value = on ? 1 : 0;
+
+	if (pdata->enable_gpios->ndescs && pdata->value_array) {
+		for (i = 0; i < pdata->enable_gpios->ndescs; i++)
+			pdata->value_array[i] = value;
+		gpiod_set_array_value(pdata->enable_gpios->ndescs,
+			pdata->enable_gpios->desc, pdata->value_array);
+	}
+
+	DRM_DEBUG("config to: %d\n", value);
+}
+
+static void ti_sn_bridge_power_ctrl(struct ti_sn_bridge *pdata, bool enable)
+{
+	static int ref_count;
+
+	if (enable)
+		ref_count++;
+	else
+		ref_count--;
+
+	if (enable && (ref_count == 1)) {
+		ti_sn_bridge_gpio_configure(pdata, true);
+
+		if (regulator_bulk_enable(pdata->num_supplies,
+						pdata->supplies)) {
+			DRM_ERROR("bridge regulator enable failed\n");
+			return;
+		}
+	} else if (!enable && !ref_count) {
+		regulator_bulk_disable(pdata->num_supplies, pdata->supplies);
+
+		ti_sn_bridge_gpio_configure(pdata, false);
+	} else {
+		DRM_DEBUG("ti_sn_bridge power ctrl: %d refcount: %d\n",
+							enable, ref_count);
+	}
+}
+
+/* Connector funcs */
+static struct ti_sn_bridge *
+connector_to_ti_sn_bridge(struct drm_connector *connector)
+{
+	return container_of(connector, struct ti_sn_bridge, connector);
+}
+
+static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
+{
+	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
+	struct drm_panel *panel = pdata->panel;
+	struct edid *edid;
+
+	if (panel) {
+		DRM_DEBUG("get mode from connected drm_panel\n");
+		pdata->num_modes = drm_panel_get_modes(panel);
+	} else {
+		/* get from EDID */
+		if (!pdata->ddc)
+			return 0;
+		ti_sn_bridge_power_ctrl(pdata, true);
+		edid = drm_get_edid(connector, pdata->ddc);
+		if (edid) {
+			drm_mode_connector_update_edid_property(connector,
+								edid);
+			pdata->num_modes = drm_add_edid_modes(connector, edid);
+			drm_edid_to_eld(connector, edid);
+			kfree(edid);
+		} else {
+			DRM_DEBUG("failed to get edid\n");
+		}
+		ti_sn_bridge_power_ctrl(pdata, false);
+	}
+
+	return pdata->num_modes;
+}
+
+static enum drm_mode_status
+ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
+			     struct drm_display_mode *mode)
+{
+	/* maximum supported resolution is 4K at 60 fps */
+	if (mode->clock > 594000)
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static struct drm_connector_helper_funcs ti_sn_bridge_connector_helper_funcs = {
+	.get_modes = ti_sn_bridge_connector_get_modes,
+	.mode_valid = ti_sn_bridge_connector_mode_valid,
+};
+
+static enum drm_connector_status
+ti_sn_bridge_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector);
+
+	/**
+	 * TODO: Currently if drm_panel is present, then always
+	 * return the status as connected. Need to add support to detect
+	 * device state for no panel(hot pluggable) scenarios.
+	 */
+	if (pdata->panel)
+		return connector_status_connected;
+	else
+		return connector_status_disconnected;
+}
+
+static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.detect = ti_sn_bridge_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,
+};
+
+static struct ti_sn_bridge *bridge_to_ti_sn_bridge(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct ti_sn_bridge, bridge);
+}
+
+static int ti_sn_bridge_read_device_rev(struct ti_sn_bridge *pdata)
+{
+	unsigned int rev = 0;
+	int ret = 0;
+
+	ret = regmap_read(pdata->regmap, SN_DEVICE_REV_REG, &rev);
+	if (ret)
+		goto exit;
+
+	if (rev == SN_BRIDGE_REVISION_ID) {
+		DRM_DEBUG("ti_sn_bridge revision id: 0x%x\n", rev);
+	} else {
+		DRM_ERROR("ti_sn_bridge revision id: 0x%x mismatch\n", rev);
+		ret = -EINVAL;
+	}
+
+exit:
+	return ret;
+}
+
+static const char * const ti_sn_bridge_supply_names[] = {
+	"vccio",
+	"vcca",
+};
+
+static int ti_sn_bridge_parse_regulators(struct ti_sn_bridge *pdata)
+{
+	unsigned int i;
+
+	pdata->num_supplies = ARRAY_SIZE(ti_sn_bridge_supply_names);
+
+	pdata->supplies = devm_kcalloc(pdata->dev, pdata->num_supplies,
+				     sizeof(*pdata->supplies), GFP_KERNEL);
+	if (!pdata->supplies)
+		return -ENOMEM;
+
+	for (i = 0; i < pdata->num_supplies; i++)
+		pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
+
+	return devm_regulator_bulk_get(pdata->dev,
+				pdata->num_supplies, pdata->supplies);
+}
+
+static int ti_sn_bridge_attach_panel(struct ti_sn_bridge *pdata)
+{
+	struct device_node *panel_node, *port, *endpoint;
+
+	pdata->panel = NULL;
+	port = of_graph_get_port_by_id(pdata->dev->of_node, 1);
+	if (port) {
+		endpoint = of_get_child_by_name(port, "endpoint");
+		of_node_put(port);
+		if (!endpoint) {
+			dev_err(pdata->dev, "no output endpoint found\n");
+			return -EINVAL;
+		}
+
+		panel_node = of_graph_get_remote_port_parent(endpoint);
+		of_node_put(endpoint);
+		if (!panel_node) {
+			dev_err(pdata->dev, "no output node found\n");
+			return -EINVAL;
+		}
+
+		pdata->panel = of_drm_find_panel(panel_node);
+		of_node_put(panel_node);
+		if (!pdata->panel) {
+			dev_err(pdata->dev, "no panel node found\n");
+			return -EINVAL;
+		}
+	}
+
+	drm_panel_attach(pdata->panel, &pdata->connector);
+
+	return 0;
+}
+
+static int ti_sn_bridge_attach(struct drm_bridge *bridge)
+{
+	struct mipi_dsi_host *host;
+	struct mipi_dsi_device *dsi;
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	int ret;
+	const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
+						   .channel = 0,
+						   .node = NULL,
+						 };
+
+	if (!bridge->encoder) {
+		DRM_ERROR("Parent encoder object not found\n");
+		ret = -ENODEV;
+		goto error;
+	}
+
+	/* HPD not supported */
+	pdata->connector.polled = 0;
+
+	ret = drm_connector_init(bridge->dev, &pdata->connector,
+				 &ti_sn_bridge_connector_funcs,
+				 DRM_MODE_CONNECTOR_eDP);
+	if (ret) {
+		DRM_ERROR("Failed to initialize connector with drm\n");
+		goto error;
+	}
+
+	drm_connector_helper_add(&pdata->connector,
+				 &ti_sn_bridge_connector_helper_funcs);
+	drm_mode_connector_attach_encoder(&pdata->connector, bridge->encoder);
+
+	host = of_find_mipi_dsi_host_by_node(pdata->host_node);
+	if (!host) {
+		DRM_ERROR("failed to find dsi host\n");
+		ret = -ENODEV;
+		goto error;
+	}
+
+	dsi = mipi_dsi_device_register_full(host, &info);
+	if (IS_ERR(dsi)) {
+		DRM_ERROR("failed to create dsi device\n");
+		ret = PTR_ERR(dsi);
+		goto error;
+	}
+
+	/* TODO: setting to 4 lanes always for now */
+	dsi->lanes = 4;
+	dsi->format = MIPI_DSI_FMT_RGB888;
+	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+			  MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
+
+	ret = mipi_dsi_attach(dsi);
+	if (ret < 0) {
+		DRM_ERROR("failed to attach dsi to host\n");
+		goto err_dsi_attach;
+	}
+
+	pdata->dsi = dsi;
+
+	DRM_DEBUG("bridge attached\n");
+	/* attach panel to bridge */
+	ti_sn_bridge_attach_panel(pdata);
+
+	return 0;
+
+err_dsi_attach:
+	mipi_dsi_device_unregister(dsi);
+error:
+	return ret;
+}
+
+static void ti_sn_bridge_mode_set(struct drm_bridge *bridge,
+				    struct drm_display_mode *mode,
+				    struct drm_display_mode *adj_mode)
+{
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+
+	DRM_DEBUG("mode_set: hdisplay=%d, vdisplay=%d, vrefresh=%d, clock=%d\n",
+		adj_mode->hdisplay, adj_mode->vdisplay,
+		adj_mode->vrefresh, adj_mode->clock);
+
+	drm_mode_copy(&pdata->curr_mode, adj_mode);
+}
+
+static void ti_sn_bridge_disable(struct drm_bridge *bridge)
+{
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	struct drm_panel *panel = pdata->panel;
+
+	if (panel) {
+		drm_panel_disable(panel);
+		drm_panel_unprepare(panel);
+	}
+
+	ti_sn_bridge_power_ctrl(pdata, false);
+}
+
+static void ti_sn_bridge_enable(struct drm_bridge *bridge)
+{
+	struct ti_sn_bridge *pdata = bridge_to_ti_sn_bridge(bridge);
+	struct drm_panel *panel = pdata->panel;
+	struct drm_display_mode *mode = &pdata->curr_mode;
+	unsigned int bit_rate_mhz, clk_freq_mhz;
+	unsigned int val = 0;
+
+	if (!pdata->num_modes)
+		return;
+
+	ti_sn_bridge_power_ctrl(pdata, true);
+
+	if (panel)
+		drm_panel_prepare(panel);
+
+	/* CLK_SRC config */
+	regmap_write(pdata->regmap, SN_REFCLK_FREQ_REG,
+			(DPPLL_CLK_SRC_REFCLK | (SN_REF_CLK_19_2_MHZ << 0x1)));
+
+	/* DSI_A lane config */
+	regmap_read(pdata->regmap, SN_DSI_LANES_REG, &val);
+	val |= ((0x4 - pdata->dsi->lanes) & 0x03) << 0x3;
+	regmap_write(pdata->regmap, SN_DSI_LANES_REG, val);
+
+	/* DP lane config */
+	regmap_read(pdata->regmap, SN_SSC_CONFIG_REG, &val);
+	val |= ((pdata->dsi->lanes - 0x1) & 0x03) << 0x4;
+	regmap_write(pdata->regmap, SN_SSC_CONFIG_REG, val);
+
+	/* set dsi clk frequency value */
+	bit_rate_mhz = (mode->clock / 1000) *
+			mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
+	clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
+	/* for each increment in val, frequency increases by 5MHz */
+	val = (MIN_DSI_CLK_FREQ_MHZ / 0x5) +
+		(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 0x5) & 0xFF);
+	regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val);
+
+	regmap_write(pdata->regmap, SN_DATARATE_CONFIG_REG, 0x80); /* set HBR */
+
+	regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0x1); /* Enable PLL */
+	usleep_range(10000, 10050);
+
+	/* DPCD Register 0x0010A in Sink */
+	regmap_write(pdata->regmap, SN_AUX_WDATA0_REG, 0x01);
+	regmap_write(pdata->regmap, SN_AUX_ADDR_19_16_REG, 0x00);
+	regmap_write(pdata->regmap, SN_AUX_ADDR_15_8_REG, 0x01);
+	regmap_write(pdata->regmap, SN_AUX_ADDR_7_0_REG, 0x0A);
+	regmap_write(pdata->regmap, SN_AUX_LENGTH_REG, 0x01);
+	regmap_write(pdata->regmap, SN_AUX_CMD_REG, 0x81);
+	usleep_range(10000, 10050);
+
+	/* Semi auto link training mode */
+	regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A);
+	usleep_range(20000, 20050);
+
+	/* config video parameters */
+	regmap_write(pdata->regmap, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
+			mode->hdisplay & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_ACTIVE_LINE_LENGTH_HIGH_REG,
+			(mode->hdisplay >> 0x8) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
+			mode->vdisplay & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VERTICAL_DISPLAY_SIZE_HIGH_REG,
+			(mode->vdisplay >> 0x8) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
+			(mode->hsync_end - mode->hsync_start) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
+			((mode->hsync_end - mode->hsync_start) >> 0x8) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
+			(mode->vsync_end - mode->vsync_start) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
+			((mode->vsync_end - mode->vsync_start) >> 0x8) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
+			(mode->htotal - mode->hsync_end) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VERTICAL_BACK_PORCH_REG,
+			(mode->vtotal - mode->vsync_end) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
+			(mode->hsync_start - mode->hdisplay) & 0xFF);
+	regmap_write(pdata->regmap, SN_CHA_VERTICAL_FRONT_PORCH_REG,
+			(mode->vsync_start - mode->vdisplay) & 0xFF);
+	if (pdata->dsi->format == MIPI_DSI_FMT_RGB888)
+		regmap_write(pdata->regmap, SN_DATA_FORMAT_REG, 0x00);
+	else
+		regmap_write(pdata->regmap, SN_DATA_FORMAT_REG, 0x01);
+	usleep_range(10000, 10050);
+
+	/* enable video stream */
+	regmap_read(pdata->regmap, SN_ENH_FRAME_REG, &val);
+	val |= BIT(3);
+	regmap_write(pdata->regmap, SN_ENH_FRAME_REG, val);
+
+	if (panel)
+		drm_panel_enable(panel);
+}
+
+static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
+	.attach = ti_sn_bridge_attach,
+	.enable = ti_sn_bridge_enable,
+	.disable = ti_sn_bridge_disable,
+	.mode_set = ti_sn_bridge_mode_set,
+};
+
+static int ti_sn_bridge_parse_gpios(struct ti_sn_bridge *pdata)
+{
+	int ret = 0;
+
+	pdata->enable_gpios = devm_gpiod_get_array(pdata->dev,
+						"enable", GPIOD_OUT_LOW);
+	if (IS_ERR(pdata->enable_gpios)) {
+		DRM_ERROR("failed to get enable gpio from DT\n");
+		ret = PTR_ERR(pdata->enable_gpios);
+		goto exit;
+	}
+	/* initialize the value array */
+	pdata->value_array = kmalloc(pdata->enable_gpios->ndescs * sizeof(int),
+								GFP_KERNEL);
+	if (!pdata->value_array) {
+		ret = -ENOMEM;
+		goto exit;
+	}
+	memset(pdata->value_array, 0,
+			pdata->enable_gpios->ndescs * sizeof(int));
+
+exit:
+	return ret;
+}
+
+static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata)
+{
+	struct device_node *np = pdata->dev->of_node;
+	struct device_node *end_node;
+
+	end_node = of_graph_get_endpoint_by_regs(np, 0, 0);
+	if (!end_node) {
+		DRM_ERROR("remote endpoint not found\n");
+		return -ENODEV;
+	}
+
+	pdata->host_node = of_graph_get_remote_port_parent(end_node);
+	of_node_put(end_node);
+	if (!pdata->host_node) {
+		DRM_ERROR("remote node not found\n");
+		return -ENODEV;
+	}
+	of_node_put(pdata->host_node);
+
+	return 0;
+}
+
+static int ti_sn_bridge_probe(struct i2c_client *client,
+	 const struct i2c_device_id *id)
+{
+	struct ti_sn_bridge *pdata;
+	struct device_node *ddc_node;
+	int ret = 0;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		DRM_ERROR("device doesn't support I2C\n");
+		return -ENODEV;
+	}
+
+	pdata = devm_kzalloc(&client->dev,
+		sizeof(struct ti_sn_bridge), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	pdata->dev = &client->dev;
+	pdata->i2c_client = client;
+	pdata->intp_irq = client->irq;
+	DRM_DEBUG("I2C address is %x\n", client->addr);
+
+	pdata->regmap = devm_regmap_init_i2c(client,
+				&ti_sn_bridge_regmap_config);
+	if (IS_ERR(pdata->regmap))
+		return PTR_ERR(pdata->regmap);
+
+	ret = ti_sn_bridge_parse_dsi_host(pdata);
+	if (ret)
+		goto err_dt_parse;
+
+	ret = ti_sn_bridge_parse_gpios(pdata);
+	if (ret)
+		goto err_dt_parse;
+
+	ret = ti_sn_bridge_parse_regulators(pdata);
+	if (ret) {
+		DRM_ERROR("failed to parse regulators\n");
+		goto err_reg_parse;
+	}
+
+	ddc_node = of_parse_phandle(pdata->dev->of_node, "ddc-i2c-bus", 0);
+	if (ddc_node) {
+		pdata->ddc = of_find_i2c_adapter_by_node(ddc_node);
+		of_node_put(ddc_node);
+		if (!pdata->ddc) {
+			dev_dbg(pdata->dev, "failed to read ddc node\n");
+			return -EPROBE_DEFER;
+		}
+	} else {
+		dev_dbg(pdata->dev, "no ddc property found\n");
+	}
+
+	ti_sn_bridge_power_ctrl(pdata, true);
+	ret = ti_sn_bridge_read_device_rev(pdata);
+	if (ret)
+		goto err_config;
+	ti_sn_bridge_power_ctrl(pdata, false);
+
+	i2c_set_clientdata(client, pdata);
+	dev_set_drvdata(&client->dev, pdata);
+
+	pdata->bridge.funcs = &ti_sn_bridge_funcs;
+	pdata->bridge.of_node = client->dev.of_node;
+
+	drm_bridge_add(&pdata->bridge);
+
+	return ret;
+
+err_config:
+	ti_sn_bridge_power_ctrl(pdata, false);
+err_reg_parse:
+	kfree(pdata->value_array);
+err_dt_parse:
+	devm_kfree(&client->dev, pdata);
+	return ret;
+}
+
+static int ti_sn_bridge_remove(struct i2c_client *client)
+{
+	int ret = -EINVAL;
+	struct ti_sn_bridge *pdata = i2c_get_clientdata(client);
+
+	if (!pdata)
+		goto end;
+
+	mipi_dsi_detach(pdata->dsi);
+	mipi_dsi_device_unregister(pdata->dsi);
+
+	ti_sn_bridge_power_ctrl(pdata, false);
+	kfree(pdata->value_array);
+
+	drm_bridge_remove(&pdata->bridge);
+	i2c_put_adapter(pdata->ddc);
+
+	devm_kfree(&client->dev, pdata);
+
+end:
+	return ret;
+}
+
+static struct i2c_device_id ti_sn_bridge_id[] = {
+	{ "ti,sn65dsi86", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, ti_sn_bridge_id);
+
+static const struct of_device_id ti_sn_bridge_match_table[] = {
+	{.compatible = "ti,sn65dsi86"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, ti_sn_bridge_match_table);
+
+static struct i2c_driver ti_sn_bridge_driver = {
+	.driver = {
+		.name = "ti_sn65dsi86",
+		.owner = THIS_MODULE,
+		.of_match_table = ti_sn_bridge_match_table,
+	},
+	.probe = ti_sn_bridge_probe,
+	.remove = ti_sn_bridge_remove,
+	.id_table = ti_sn_bridge_id,
+};
+
+module_i2c_driver(ti_sn_bridge_driver);
+MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
+MODULE_LICENSE("GPL");
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

^ permalink raw reply related	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2018-04-26  7:08 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <CGME20180419072054epcas5p364c0eae57d11ff5ff22890223a79c271@epcas5p3.samsung.com>
2018-04-18 12:19 ` [[RFC]DPU PATCH 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp panel driver Sandeep Panda
     [not found]   ` <1524054002-17869-1-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2018-04-18 12:19     ` [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver Sandeep Panda
2018-04-18 14:11       ` Sean Paul
2018-04-18 18:31         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
2018-04-19  4:37         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
     [not found]       ` <1524054002-17869-2-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2018-04-19 15:14         ` Jordan Crouse
     [not found]           ` <20180419151438.GA18652-9PYrDHPZ2Orvke4nUoYGnHL1okKdlPRT@public.gmane.org>
2018-04-19 18:18             ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
2018-04-18 12:20     ` [[RFC]DPU PATCH 2/4] dt-bindings: drm/bridge: Document sn65dsi86 bridge bindings Sandeep Panda
2018-04-18 13:58       ` Sean Paul
2018-04-18 18:08         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
2018-04-18 12:20     ` [[RFC]DPU PATCH 3/4] drm/panel: add Innolux TV123WAM eDP panel driver Sandeep Panda
2018-04-18 12:20     ` [[RFC]DPU PATCH 4/4] dt-bindings: Add Innolux TV123WAM panel bindings Sandeep Panda
2018-04-24 14:43       ` Rob Herring
2018-04-25  2:16         ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
     [not found]           ` <cb59510635f0e7c010588a4603ae3f0f-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2018-04-25 14:40             ` Rob Herring
2018-04-18 14:12   ` [[RFC]DPU PATCH 0/4] Add suppport for sn65dsi86 bridge chip and Innolux 2k edp panel driver Sean Paul
2018-04-18 18:36     ` spanda-sgV2jX0FEOL9JmXXK+q4OQ
2018-04-26  7:08   ` Andrzej Hajda
2018-04-19 17:56 Sandeep Panda
     [not found] ` <1524160568-27583-1-git-send-email-spanda-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2018-04-19 17:56   ` [[RFC]DPU PATCH 1/4] drm/bridge: add support for sn65dsi86 bridge driver Sandeep Panda
2018-04-20 19:36     ` Sean Paul
2018-04-24  9:48       ` spanda-sgV2jX0FEOL9JmXXK+q4OQ

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.