All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] [v1,2/2] drm/panel:Add Boe Himax8279d MIPI-DSI LCD panel
@ 2019-02-22  2:32 Jerry Han
  2019-02-23 22:38 ` Sam Ravnborg
  0 siblings, 1 reply; 2+ messages in thread
From: Jerry Han @ 2019-02-22  2:32 UTC (permalink / raw)
  To: dri-devel; +Cc: Jerry Han

Add panel driver for it.

Signed-off-by: Jerry Han <hanxu5@huaqin.corp-partner.google.com>
---
 MAINTAINERS                                  |   6 +
 drivers/gpu/drm/panel/Kconfig                |  11 +
 drivers/gpu/drm/panel/Makefile               |   1 +
 drivers/gpu/drm/panel/panel-boe-himax8279d.c | 593 +++++++++++++++++++++++++++
 4 files changed, 611 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-boe-himax8279d.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b2f710e..38e2881 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4636,6 +4636,12 @@ S:	Maintained
 F:	drivers/gpu/drm/tinydrm/ili9225.c
 F:	Documentation/devicetree/bindings/display/ilitek,ili9225.txt
 
+DRM DRIVER FOR BOE HIMAX8279D PANELS
+M:	Jerry Han <hanxu5@huaqin.corp-partner.google.com>
+S:	Maintained
+F:	drivers/gpu/drm/panel/panel-boe-himax8279d.c
+F:	Documentation/devicetree/bindings/display/panel/boe,himax8279d.txt
+
 DRM DRIVER FOR INTEL I810 VIDEO CARDS
 S:	Orphan / Obsolete
 F:	drivers/gpu/drm/i810/
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 6020c30..e5dcfd1 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -186,4 +186,15 @@ config DRM_PANEL_SITRONIX_ST7789V
 	  Say Y here if you want to enable support for the Sitronix
 	  ST7789V controller for 240x320 LCD panels
 
+config DRM_PANEL_BOE_HIMAX8279D
+	tristate "Boe Himax8279d panel"
+	depends on OF
+	depends on DRM_MIPI_DSI
+	depends on BACKLIGHT_CLASS_DEVICE
+	help
+	  Say Y here if you want to enable support for Same type
+	  TFT-LCD modules. The panel has a 1200x1920 resolution and uses
+	  24 bit RGB per pixel. It provides a MIPI DSI interface to
+	  the host and has a built-in LED backlight.
+
 endmenu
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 5ccaaa9..8a36543 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -19,3 +19,4 @@ obj-$(CONFIG_DRM_PANEL_SEIKO_43WVF1G) += panel-seiko-43wvf1g.o
 obj-$(CONFIG_DRM_PANEL_SHARP_LQ101R1SX01) += panel-sharp-lq101r1sx01.o
 obj-$(CONFIG_DRM_PANEL_SHARP_LS043T1LE01) += panel-sharp-ls043t1le01.o
 obj-$(CONFIG_DRM_PANEL_SITRONIX_ST7789V) += panel-sitronix-st7789v.o
+obj-$(CONFIG_DRM_PANEL_BOE_HIMAX8279D) += panel-boe-himax8279d.o
diff --git a/drivers/gpu/drm/panel/panel-boe-himax8279d.c b/drivers/gpu/drm/panel/panel-boe-himax8279d.c
new file mode 100644
index 0000000..2788bdd
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-boe-himax8279d.c
@@ -0,0 +1,593 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019, Huaqin Electronics Co., Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/backlight.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+
+#include <video/mipi_display.h>
+#include <linux/err.h>
+
+struct dsi_cmd_desc {
+	char wait;
+	char cmd;
+	char len;
+	char *payload;
+};
+
+struct dsi_panel_cmds {
+	char *buf;
+	int blen;
+	struct dsi_cmd_desc *cmds;
+	int cmd_cnt;
+};
+
+struct panel_info {
+	struct drm_panel base;
+	struct mipi_dsi_device *link;
+
+	struct backlight_device *backlight;
+	struct gpio_desc *reset_gpio;
+	struct gpio_desc *pp33000_gpio;
+	struct gpio_desc *pp18000_gpio;
+
+	u32 rst_seq[10];
+	u32 rst_seq_len;
+
+	struct dsi_panel_cmds *on_cmds;
+	struct dsi_panel_cmds *off_cmds;
+	struct drm_display_mode	*panel_display_mode;
+
+	unsigned int panel_width_mm;
+	unsigned int panel_height_mm;
+	unsigned int per_color_channel;
+
+	bool prepared;
+	bool enabled;
+};
+
+static int dsi_parse_reset_seq(struct device_node *np, u32 rst_seq[10],
+		u32 *rst_len, const char *name)
+{
+	int num = 0, i;
+	int rc;
+	struct property *data;
+	u32 tmp[10];
+	*rst_len = 0;
+	data = of_find_property(np, name, &num);
+
+	num /= sizeof(u32);
+	if (!data || !num || num > 10 || num % 2) {
+		pr_err("%s:%d, error reading %s, length found = %d\n",
+				__func__, __LINE__, name, num);
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32_array(np, name, tmp, num);
+	if (rc) {
+		pr_err("%s:%d, error reading %s, rc = %d\n",
+					__func__, __LINE__, name, rc);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < num; ++i) {
+		rst_seq[i] = tmp[i];
+		*rst_len = num;
+	}
+	return 0;
+}
+
+ssize_t mipi_dsi_dcs_write_delay(struct mipi_dsi_device *dsi, u8 cmd,
+		const void *data, size_t len, size_t time)
+{
+	ssize_t ret;
+
+	if (len == 0)
+		ret = mipi_dsi_dcs_write(dsi, cmd, NULL, 0);
+	else
+		ret = mipi_dsi_dcs_write(dsi, cmd, data, len);
+
+	msleep(time);
+
+	return ret;
+}
+
+static int write_cmds(struct mipi_dsi_device *dsi_device,
+					struct dsi_panel_cmds *init_cmds)
+{
+	struct dsi_cmd_desc *one_cmd = init_cmds->cmds;
+	int i = 0;
+	int ret;
+
+	for (i = 0; i < init_cmds->cmd_cnt; i++) {
+		if (one_cmd == NULL)
+			return -EINVAL;
+
+		ret = mipi_dsi_dcs_write_delay(dsi_device,
+				one_cmd->cmd, one_cmd->payload,
+				one_cmd->len, one_cmd->wait);
+		one_cmd++;
+	}
+
+	return ret;
+}
+
+static int parse_dcs_cmds(struct device_node *np,
+				struct dsi_panel_cmds *pcmds, char *cmd_key)
+{
+	const char *data;
+	int blen = 0, len, cnt = 0;
+	char *buf, *bp;
+	struct dsi_cmd_desc *temp_cmd;
+
+	data = of_get_property(np, cmd_key, &blen);
+	if (!data) {
+		pr_err("%s: failed, key=%s\n", __func__, cmd_key);
+		return -ENOMEM;
+	}
+
+	buf = kzalloc(sizeof(char) * blen, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	memcpy(buf, data, blen);
+
+	/* scan dcs commands */
+	bp = buf;
+	len = blen;
+
+	/* get how much cmd cnt  */
+	while (len > 0) {
+		cnt++;
+		len = len - *(bp);
+		bp = bp + *(bp);
+	}
+
+	pcmds->cmd_cnt = cnt;
+
+	pcmds->cmds = kcalloc(cnt, sizeof(struct dsi_cmd_desc), GFP_KERNEL);
+	if (!pcmds->cmds)
+		return -ENOMEM;
+	temp_cmd = pcmds->cmds;
+
+	bp = buf;
+	len = blen;
+
+	while (cnt != 0) {
+		temp_cmd->wait = *(bp+1);
+		temp_cmd->cmd = *(bp+2);
+		temp_cmd->len = *(bp) - 3;
+		temp_cmd->payload = bp + 3;
+
+		cnt--;
+		temp_cmd++;
+		bp = bp + *(bp);
+	}
+
+	pcmds->buf = buf;
+	pcmds->blen = blen;
+
+	return 0;
+}
+
+static int panel_parse_dt(struct panel_info *pinfo)
+{
+	struct device_node *panels_node;
+	struct device_node *parameter_node;
+	static const char *lcd_name;
+	static const char *panel_parameter;
+	struct drm_display_mode *display_mode;
+	struct mipi_dsi_device	*dsi_device = pinfo->link;
+	int ret = 0;
+	u32	tmp;
+
+	panels_node = of_find_node_by_name(NULL, "panels");
+	if (!panels_node) {
+		pr_err("Get panels node Fail!\n");
+		return -EINVAL;
+	}
+
+	panel_parameter = of_get_property(panels_node, "panel-parameter", NULL);
+	if (!panel_parameter) {
+		pr_err("Get panel parameter Fail!\n");
+		return -EINVAL;
+	}
+
+	parameter_node = of_find_node_by_name(panels_node, panel_parameter);
+	if (!parameter_node)
+		return -ENODEV;
+
+	/* get lcd name */
+	lcd_name = of_get_property(parameter_node, "dsi-panel-name", NULL);
+	if (!lcd_name) {
+		pr_err("Get lcd_name Fail!\n");
+		return -EINVAL;
+	}
+	pr_info("%s: Lcd Name = %s\n", __func__, lcd_name);
+
+	/* get lcd init code */
+	pinfo->on_cmds = kzalloc(sizeof(struct dsi_panel_cmds), GFP_KERNEL);
+	if (!(pinfo->on_cmds))
+		return -ENOMEM;
+
+	ret = parse_dcs_cmds(parameter_node, pinfo->on_cmds, "dsi-on-command");
+	if (ret < 0) {
+		pr_err("Get Dsi on command Fail\n");
+		return -EINVAL;
+	}
+
+	/* get lcd off code */
+	pinfo->off_cmds = kzalloc(sizeof(struct dsi_panel_cmds), GFP_KERNEL);
+	if (!(pinfo->off_cmds))
+		return -ENOMEM;
+
+	ret = parse_dcs_cmds(parameter_node, pinfo->off_cmds,
+			"dsi-off-command");
+	if (ret < 0) {
+		pr_err("Get Dsi off command Fail\n");
+		return -EINVAL;
+	}
+
+	/* get Porch  */
+	display_mode = kzalloc(sizeof(struct drm_display_mode), GFP_KERNEL);
+	if (!display_mode)
+		return -ENOMEM;
+	pinfo->panel_display_mode = display_mode;
+
+	ret = of_property_read_u32(parameter_node, "dsi-pixel-clk", &tmp);
+	display_mode->clock = (!ret ? tmp : 0);
+
+	ret = of_property_read_u32(parameter_node,
+			"dsi-panel-hzorizontal", &tmp);
+	display_mode->hdisplay = (!ret ? tmp : 0);
+
+	ret = of_property_read_u32(parameter_node, "dsi-h-front-porch", &tmp);
+	tmp = (!ret ? tmp : 0);
+	display_mode->hsync_start = display_mode->hdisplay + tmp;
+
+	ret = of_property_read_u32(parameter_node, "dsi-h-back-porch", &tmp);
+	tmp = (!ret ? tmp : 0);
+	display_mode->hsync_end = display_mode->hsync_start + tmp;
+
+	ret = of_property_read_u32(parameter_node, "dsi-h-pulse-width", &tmp);
+	tmp = (!ret ? tmp : 0);
+	display_mode->htotal = display_mode->hsync_end + tmp;
+
+	ret = of_property_read_u32(parameter_node, "dsi-panel-vertical", &tmp);
+	display_mode->vdisplay = (!ret ? tmp : 0);
+
+	ret = of_property_read_u32(parameter_node, "dsi-v-front-porch", &tmp);
+	tmp = (!ret ? tmp : 0);
+	display_mode->vsync_start = display_mode->vdisplay + tmp;
+
+	ret = of_property_read_u32(parameter_node, "dsi-v-back-porch", &tmp);
+	tmp = (!ret ? tmp : 0);
+	display_mode->vsync_end = display_mode->vsync_start + tmp;
+
+	ret = of_property_read_u32(parameter_node, "dsi-v-pulse-width", &tmp);
+	tmp = (!ret ? tmp : 0);
+	display_mode->vtotal = display_mode->vsync_end + tmp;
+
+	ret = of_property_read_u32(parameter_node, "dsi-panel-refresh", &tmp);
+	display_mode->vrefresh = (!ret ? tmp : 0);
+
+	/* get lans */
+	ret = of_property_read_u32(parameter_node, "dsi-panel-lanes", &tmp);
+	dsi_device->lanes  = (!ret ? tmp : 0);
+
+	ret = of_property_read_u32(parameter_node, "dsi-panel-format", &tmp);
+	dsi_device->format = (!ret ? tmp : 0);
+
+	ret = of_property_read_u32(parameter_node,
+			"dsi-panel-mode-flags", &tmp);
+	dsi_device->mode_flags = (!ret ? tmp : 0);
+
+	/* get WxH */
+	ret = of_property_read_u32(parameter_node, "dsi-panel-width-mm", &tmp);
+	pinfo->panel_width_mm  = (!ret ? tmp : 0);
+
+	ret = of_property_read_u32(parameter_node, "dsi-panel-height-mm", &tmp);
+	pinfo->panel_height_mm  = (!ret ? tmp : 0);
+
+	ret = of_property_read_u32(parameter_node, "per-color-channel", &tmp);
+	pinfo->per_color_channel = (!ret ? tmp : 0);
+
+	/* reset time */
+	ret = dsi_parse_reset_seq(parameter_node, pinfo->rst_seq,
+				&(pinfo->rst_seq_len), "dsi-reset-sequence");
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static inline struct panel_info *to_panel_info(struct drm_panel *panel)
+{
+	return container_of(panel, struct panel_info, base);
+}
+
+static int panel_disable(struct drm_panel *panel)
+{
+	struct panel_info *pinfo = to_panel_info(panel);
+
+	if (!pinfo->enabled)
+		return 0;
+
+	backlight_disable(pinfo->backlight);
+
+	pinfo->enabled = false;
+
+	return 0;
+}
+
+static int panel_unprepare(struct drm_panel *panel)
+{
+	struct panel_info *pinfo = to_panel_info(panel);
+	int err;
+
+	if (!pinfo->prepared)
+		return 0;
+
+	err = write_cmds(pinfo->link, pinfo->off_cmds);
+	if (err < 0) {
+		dev_err(panel->dev,
+				"failed to send DCS Off Code: %d\n", err);
+		return err;
+	}
+
+	usleep_range(1000, 1000);
+	gpiod_set_value_cansleep(pinfo->reset_gpio, 0);
+	gpiod_set_value_cansleep(pinfo->pp33000_gpio, 0);
+	gpiod_set_value_cansleep(pinfo->pp18000_gpio, 0);
+
+	pinfo->prepared = false;
+
+	return 0;
+}
+
+static int panel_prepare(struct drm_panel *panel)
+{
+	struct panel_info *pinfo = to_panel_info(panel);
+	int err, i;
+
+	if (pinfo->prepared)
+		return 0;
+
+	gpiod_set_value_cansleep(pinfo->pp18000_gpio, 1);
+	gpiod_set_value_cansleep(pinfo->pp33000_gpio, 1);
+
+	/* reset sequence */
+	for (i = 0; i < pinfo->rst_seq_len; ++i) {
+		gpiod_set_value_cansleep(pinfo->reset_gpio, pinfo->rst_seq[i]);
+		if (pinfo->rst_seq[++i])
+			usleep_range(pinfo->rst_seq[i] * 1000,
+						pinfo->rst_seq[i] * 1000);
+	}
+
+	/* send init code */
+	err = write_cmds(pinfo->link, pinfo->on_cmds);
+	if (err < 0) {
+		dev_err(panel->dev,
+				"failed to send DCS Init Code: %d\n", err);
+		goto poweroff;
+	}
+
+	pinfo->prepared = true;
+
+	return 0;
+
+poweroff:
+	gpiod_set_value_cansleep(pinfo->reset_gpio, 0);
+	gpiod_set_value_cansleep(pinfo->pp33000_gpio, 0);
+	gpiod_set_value_cansleep(pinfo->pp18000_gpio, 0);
+	return err;
+}
+
+static int panel_enable(struct drm_panel *panel)
+{
+	struct panel_info *pinfo = to_panel_info(panel);
+	int ret;
+
+	if (pinfo->enabled)
+		return 0;
+
+	ret = backlight_enable(pinfo->backlight);
+	if (ret) {
+		DRM_DEV_ERROR(panel->drm->dev,
+			      "Failed to enable backlight %d\n", ret);
+		return ret;
+	}
+
+	pinfo->enabled = true;
+
+	return 0;
+}
+
+static int panel_get_modes(struct drm_panel *panel)
+{
+	struct panel_info *pinfo = to_panel_info(panel);
+	struct drm_display_mode *mode;
+
+	if (!pinfo->panel_display_mode) {
+		pr_err("Get panel_display_mode fail!\n");
+		return -1;
+	}
+
+	mode = drm_mode_duplicate(panel->drm, pinfo->panel_display_mode);
+	if (!mode) {
+		pr_err("failed to add mode\n");
+		return -ENOMEM;
+	}
+
+	drm_mode_set_name(mode);
+
+	drm_mode_probed_add(panel->connector, mode);
+
+	panel->connector->display_info.width_mm = pinfo->panel_width_mm;
+	panel->connector->display_info.height_mm = pinfo->panel_height_mm;
+	panel->connector->display_info.bpc = pinfo->per_color_channel;
+
+	return 1;
+}
+
+static const struct drm_panel_funcs panel_funcs = {
+	.disable = panel_disable,
+	.unprepare = panel_unprepare,
+	.prepare = panel_prepare,
+	.enable = panel_enable,
+	.get_modes = panel_get_modes,
+};
+
+static const struct of_device_id panel_of_match[] = {
+	{ .compatible = "boe,himax8279d", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, panel_of_match);
+
+static int panel_add(struct panel_info *pinfo)
+{
+	struct device *dev = &pinfo->link->dev;
+	struct device_node *np;
+	int err;
+
+	pinfo->pp33000_gpio = devm_gpiod_get_optional(dev,
+							"pp33000",
+							GPIOD_OUT_HIGH);
+	if (IS_ERR(pinfo->pp33000_gpio)) {
+		err = PTR_ERR(pinfo->pp33000_gpio);
+		dev_err(dev, "failed to get pp33000 gpio: %d\n", err);
+		pinfo->pp33000_gpio = NULL;
+	}
+
+	pinfo->pp18000_gpio = devm_gpiod_get_optional(dev,
+							"pp18000",
+							GPIOD_OUT_HIGH);
+	if (IS_ERR(pinfo->pp18000_gpio)) {
+		err = PTR_ERR(pinfo->pp18000_gpio);
+		dev_err(dev, "failed to get pp18000 gpio: %d\n", err);
+		pinfo->pp18000_gpio = NULL;
+	}
+
+	pinfo->reset_gpio = devm_gpiod_get_optional(dev, "enable",
+							GPIOD_OUT_HIGH);
+	if (IS_ERR(pinfo->reset_gpio)) {
+		err = PTR_ERR(pinfo->reset_gpio);
+		dev_err(dev, "failed to get enable gpio: %d\n", err);
+		pinfo->reset_gpio = NULL;
+	}
+
+	np = of_parse_phandle(dev->of_node, "backlight", 0);
+	if (np) {
+		pinfo->backlight = of_find_backlight_by_node(np);
+		of_node_put(np);
+
+		if (!pinfo->backlight)
+			return -EPROBE_DEFER;
+	}
+
+	drm_panel_init(&pinfo->base);
+	pinfo->base.funcs = &panel_funcs;
+	pinfo->base.dev = &pinfo->link->dev;
+
+	err = drm_panel_add(&pinfo->base);
+	if (err < 0)
+		goto put_backlight;
+
+	return 0;
+
+put_backlight:
+	put_device(&pinfo->backlight->dev);
+
+	return err;
+}
+
+static void panel_del(struct panel_info *pinfo)
+{
+	if (pinfo->base.dev)
+		drm_panel_remove(&pinfo->base);
+
+	put_device(&pinfo->backlight->dev);
+}
+
+static int panel_probe(struct mipi_dsi_device *dsi)
+{
+	struct panel_info *pinfo;
+	int err;
+
+	pinfo = devm_kzalloc(&dsi->dev, sizeof(*pinfo), GFP_KERNEL);
+	if (!pinfo)
+		return -ENOMEM;
+
+	pinfo->link = dsi;
+	panel_parse_dt(pinfo);
+	mipi_dsi_set_drvdata(dsi, pinfo);
+
+	err = panel_add(pinfo);
+	if (err < 0)
+		return err;
+
+	return mipi_dsi_attach(dsi);
+}
+
+static int panel_remove(struct mipi_dsi_device *dsi)
+{
+	struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi);
+	int err;
+
+	err = panel_unprepare(&pinfo->base);
+	if (err < 0)
+		DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n",
+			      err);
+
+	err = panel_disable(&pinfo->base);
+	if (err < 0)
+		DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", err);
+
+	err = mipi_dsi_detach(dsi);
+	if (err < 0)
+		DRM_DEV_ERROR(&dsi->dev, "failed to detach from DSI host: %d\n",
+			      err);
+
+	drm_panel_detach(&pinfo->base);
+	panel_del(pinfo);
+
+	return 0;
+}
+
+static void panel_shutdown(struct mipi_dsi_device *dsi)
+{
+	struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi);
+
+	panel_unprepare(&pinfo->base);
+	panel_disable(&pinfo->base);
+}
+
+static struct mipi_dsi_driver panel_driver = {
+	.driver = {
+		.name = "panel-innolux-ota7290b",
+		.of_match_table = panel_of_match,
+	},
+	.probe = panel_probe,
+	.remove = panel_remove,
+	.shutdown = panel_shutdown,
+};
+module_mipi_dsi_driver(panel_driver);
+
+MODULE_AUTHOR("Jerry Han <hanxu5@huaqin.corp-partner.google.com>");
+MODULE_DESCRIPTION("Boe Himax8279d driver");
+MODULE_LICENSE("GPL v2");
-- 
1.9.1

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

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

* Re: [PATCH] [v1,2/2] drm/panel:Add Boe Himax8279d MIPI-DSI LCD panel
  2019-02-22  2:32 [PATCH] [v1,2/2] drm/panel:Add Boe Himax8279d MIPI-DSI LCD panel Jerry Han
@ 2019-02-23 22:38 ` Sam Ravnborg
  0 siblings, 0 replies; 2+ messages in thread
From: Sam Ravnborg @ 2019-02-23 22:38 UTC (permalink / raw)
  To: Jerry Han; +Cc: Jerry Han, dri-devel

Hi Jerry.

Thanks for this driver submission.
Please see the following for some review feedback.

	Sam

On Fri, Feb 22, 2019 at 10:32:08AM +0800, Jerry Han wrote:
> Add panel driver for it.
> 
> Signed-off-by: Jerry Han <hanxu5@huaqin.corp-partner.google.com>
> ---
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2019, Huaqin Electronics Co., Ltd
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
With the SPDX identifier this boilerplat can be dropped.

> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/regulator/consumer.h>
> +
> +#include <drm/drmP.h>
Please do not use drmP.h for new drivers, as this header file is deprecated.

> +#include <drm/drm_crtc.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_panel.h>
> +
> +#include <video/mipi_display.h>
> +#include <linux/err.h>
Please verify that you need err.h include, often it is not needed.
> +
> +struct dsi_cmd_desc {
> +	char wait;
> +	char cmd;
> +	char len;
> +	char *payload;
> +};
> +
> +struct dsi_panel_cmds {
> +	char *buf;
> +	int blen;
> +	struct dsi_cmd_desc *cmds;
> +	int cmd_cnt;
> +};
> +
> +struct panel_info {
> +	struct drm_panel base;
> +	struct mipi_dsi_device *link;
> +
> +	struct backlight_device *backlight;
> +	struct gpio_desc *reset_gpio;
> +	struct gpio_desc *pp33000_gpio;
> +	struct gpio_desc *pp18000_gpio;
> +
> +	u32 rst_seq[10];
> +	u32 rst_seq_len;
> +
> +	struct dsi_panel_cmds *on_cmds;
> +	struct dsi_panel_cmds *off_cmds;
> +	struct drm_display_mode	*panel_display_mode;
> +
> +	unsigned int panel_width_mm;
> +	unsigned int panel_height_mm;
> +	unsigned int per_color_channel;
> +
> +	bool prepared;
> +	bool enabled;
> +};
> +
> +static int dsi_parse_reset_seq(struct device_node *np, u32 rst_seq[10],
> +		u32 *rst_len, const char *name)
Indent following line so "u32" i aligned right after first opening '('
This comment is general, please fix for all functions.
> +{
> +	int num = 0, i;
> +	int rc;
> +	struct property *data;
> +	u32 tmp[10];
> +	*rst_len = 0;
> +	data = of_find_property(np, name, &num);
> +
> +	num /= sizeof(u32);
> +	if (!data || !num || num > 10 || num % 2) {
> +		pr_err("%s:%d, error reading %s, length found = %d\n",
> +				__func__, __LINE__, name, num);
> +		return -EINVAL;
> +	}
> +
> +	rc = of_property_read_u32_array(np, name, tmp, num);
> +	if (rc) {
> +		pr_err("%s:%d, error reading %s, rc = %d\n",
> +					__func__, __LINE__, name, rc);
> +		return -EINVAL;
> +	}
> +
> +	for (i = 0; i < num; ++i) {
> +		rst_seq[i] = tmp[i];
> +		*rst_len = num;
> +	}
> +	return 0;
> +}
The above function is used only to parse "dsi-reset-sequence".
First it looks sub-optimal the way it is done.
Second, there is no documentation of "dsi-reset-sequence".
Please verify what other drivers do that has a similar need.

It looks like one can specify an array of GPIOs and there are other ways to do this.

> +
> +ssize_t mipi_dsi_dcs_write_delay(struct mipi_dsi_device *dsi, u8 cmd,
> +		const void *data, size_t len, size_t time)
> +{
> +	ssize_t ret;
> +
> +	if (len == 0)
> +		ret = mipi_dsi_dcs_write(dsi, cmd, NULL, 0);
> +	else
> +		ret = mipi_dsi_dcs_write(dsi, cmd, data, len);
> +
> +	msleep(time);
> +
> +	return ret;
> +}
> +
> +static int write_cmds(struct mipi_dsi_device *dsi_device,
> +					struct dsi_panel_cmds *init_cmds)
> +{
> +	struct dsi_cmd_desc *one_cmd = init_cmds->cmds;
> +	int i = 0;
> +	int ret;
> +
> +	for (i = 0; i < init_cmds->cmd_cnt; i++) {
> +		if (one_cmd == NULL)
> +			return -EINVAL;
> +
> +		ret = mipi_dsi_dcs_write_delay(dsi_device,
> +				one_cmd->cmd, one_cmd->payload,
> +				one_cmd->len, one_cmd->wait);
> +		one_cmd++;
> +	}
> +
> +	return ret;
> +}
> +
> +static int parse_dcs_cmds(struct device_node *np,
> +				struct dsi_panel_cmds *pcmds, char *cmd_key)

This function is used to parse on and off commands fro mthe DT.
This is commands that in other drivers are part of the driver, and not something
to be read in the DT.
Also, un-documented properties are sued.

Please consider to use the same approach as used in other drivers, and accept that
the driver will hardcode more. And then less in the DT.

> +{
> +	const char *data;
> +	int blen = 0, len, cnt = 0;
> +	char *buf, *bp;
> +	struct dsi_cmd_desc *temp_cmd;
> +
> +	data = of_get_property(np, cmd_key, &blen);
> +	if (!data) {
> +		pr_err("%s: failed, key=%s\n", __func__, cmd_key);
> +		return -ENOMEM;
> +	}
> +
> +	buf = kzalloc(sizeof(char) * blen, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
> +	memcpy(buf, data, blen);
> +
> +	/* scan dcs commands */
> +	bp = buf;
> +	len = blen;
> +
> +	/* get how much cmd cnt  */
> +	while (len > 0) {
> +		cnt++;
> +		len = len - *(bp);
> +		bp = bp + *(bp);
> +	}
> +
> +	pcmds->cmd_cnt = cnt;
> +
> +	pcmds->cmds = kcalloc(cnt, sizeof(struct dsi_cmd_desc), GFP_KERNEL);
> +	if (!pcmds->cmds)
> +		return -ENOMEM;
> +	temp_cmd = pcmds->cmds;
> +
> +	bp = buf;
> +	len = blen;
> +
> +	while (cnt != 0) {
> +		temp_cmd->wait = *(bp+1);
> +		temp_cmd->cmd = *(bp+2);
> +		temp_cmd->len = *(bp) - 3;
> +		temp_cmd->payload = bp + 3;
> +
> +		cnt--;
> +		temp_cmd++;
> +		bp = bp + *(bp);
> +	}
> +
> +	pcmds->buf = buf;
> +	pcmds->blen = blen;
> +
> +	return 0;
> +}
> +
> +static int panel_parse_dt(struct panel_info *pinfo)

Likewise.
A lot of info is in this function read from DT, that is hardcoded in other drivers.
Follow what other drivers do and drop reading this from DT.

> +
> +static inline struct panel_info *to_panel_info(struct drm_panel *panel)
> +{
> +	return container_of(panel, struct panel_info, base);
> +}
> +
> +static int panel_disable(struct drm_panel *panel)
> +{
> +	struct panel_info *pinfo = to_panel_info(panel);
> +
> +	if (!pinfo->enabled)
> +		return 0;
> +
> +	backlight_disable(pinfo->backlight);
There is really (to the best of my knowledge) no need to have a boolean
to protect from callign backlight_disable() when already disabled.

> +
> +	pinfo->enabled = false;
> +
> +	return 0;
> +}
> +
> +static int panel_unprepare(struct drm_panel *panel)
> +{
> +	struct panel_info *pinfo = to_panel_info(panel);
> +	int err;
> +
> +	if (!pinfo->prepared)
> +		return 0;
> +
> +	err = write_cmds(pinfo->link, pinfo->off_cmds);
> +	if (err < 0) {
> +		dev_err(panel->dev,
> +				"failed to send DCS Off Code: %d\n", err);
> +		return err;
> +	}
> +
> +	usleep_range(1000, 1000);
> +	gpiod_set_value_cansleep(pinfo->reset_gpio, 0);
> +	gpiod_set_value_cansleep(pinfo->pp33000_gpio, 0);
> +	gpiod_set_value_cansleep(pinfo->pp18000_gpio, 0);
> +
> +	pinfo->prepared = false;
> +
> +	return 0;
> +}
> +
> +static int panel_prepare(struct drm_panel *panel)
> +{
> +	struct panel_info *pinfo = to_panel_info(panel);
> +	int err, i;
> +
> +	if (pinfo->prepared)
> +		return 0;
> +
> +	gpiod_set_value_cansleep(pinfo->pp18000_gpio, 1);
> +	gpiod_set_value_cansleep(pinfo->pp33000_gpio, 1);
> +
> +	/* reset sequence */
> +	for (i = 0; i < pinfo->rst_seq_len; ++i) {
> +		gpiod_set_value_cansleep(pinfo->reset_gpio, pinfo->rst_seq[i]);
> +		if (pinfo->rst_seq[++i])
> +			usleep_range(pinfo->rst_seq[i] * 1000,
> +						pinfo->rst_seq[i] * 1000);
> +	}
> +
> +	/* send init code */
> +	err = write_cmds(pinfo->link, pinfo->on_cmds);
> +	if (err < 0) {
> +		dev_err(panel->dev,
> +				"failed to send DCS Init Code: %d\n", err);
> +		goto poweroff;
> +	}
> +
> +	pinfo->prepared = true;
> +
> +	return 0;
> +
> +poweroff:
> +	gpiod_set_value_cansleep(pinfo->reset_gpio, 0);
> +	gpiod_set_value_cansleep(pinfo->pp33000_gpio, 0);
> +	gpiod_set_value_cansleep(pinfo->pp18000_gpio, 0);
The above sequence is used in two placed.
Put it in a helper function.

> +	return err;
> +}
> +
> +static int panel_enable(struct drm_panel *panel)
> +{
> +	struct panel_info *pinfo = to_panel_info(panel);
> +	int ret;
> +
> +	if (pinfo->enabled)
> +		return 0;
> +
> +	ret = backlight_enable(pinfo->backlight);
> +	if (ret) {
> +		DRM_DEV_ERROR(panel->drm->dev,
> +			      "Failed to enable backlight %d\n", ret);
> +		return ret;
> +	}
> +
> +	pinfo->enabled = true;
> +
> +	return 0;
> +}
> +
> +static int panel_get_modes(struct drm_panel *panel)
> +{
> +	struct panel_info *pinfo = to_panel_info(panel);
> +	struct drm_display_mode *mode;
> +
> +	if (!pinfo->panel_display_mode) {
> +		pr_err("Get panel_display_mode fail!\n");
> +		return -1;
> +	}
> +
> +	mode = drm_mode_duplicate(panel->drm, pinfo->panel_display_mode);
> +	if (!mode) {
> +		pr_err("failed to add mode\n");
> +		return -ENOMEM;
> +	}
> +
> +	drm_mode_set_name(mode);
> +
> +	drm_mode_probed_add(panel->connector, mode);
> +
> +	panel->connector->display_info.width_mm = pinfo->panel_width_mm;
> +	panel->connector->display_info.height_mm = pinfo->panel_height_mm;
> +	panel->connector->display_info.bpc = pinfo->per_color_channel;
> +
> +	return 1;
> +}
> +
> +static const struct drm_panel_funcs panel_funcs = {
> +	.disable = panel_disable,
> +	.unprepare = panel_unprepare,
> +	.prepare = panel_prepare,
> +	.enable = panel_enable,
> +	.get_modes = panel_get_modes,
> +};
> +
> +static const struct of_device_id panel_of_match[] = {
> +	{ .compatible = "boe,himax8279d", },
> +	{ }
To match other drivers use:
	{ /* Sentinel */ }

> +};
> +MODULE_DEVICE_TABLE(of, panel_of_match);
> +
> +static int panel_add(struct panel_info *pinfo)
> +{
> +	struct device *dev = &pinfo->link->dev;
> +	struct device_node *np;
> +	int err;
> +
	pinfo->pp33000_gpio = devm_gpiod_get_optional(dev,
						      "pp33000",
						      GPIOD_OUT_HIGH);
Indent is wrong here.
Please indent as shown above where the parameters starts right after
the opening '('
Same goes for several places in this file.

> +	if (IS_ERR(pinfo->pp33000_gpio)) {
> +		err = PTR_ERR(pinfo->pp33000_gpio);
> +		dev_err(dev, "failed to get pp33000 gpio: %d\n", err);
> +		pinfo->pp33000_gpio = NULL;
> +	}
> +
> +	pinfo->pp18000_gpio = devm_gpiod_get_optional(dev,
> +							"pp18000",
> +							GPIOD_OUT_HIGH);
> +	if (IS_ERR(pinfo->pp18000_gpio)) {
> +		err = PTR_ERR(pinfo->pp18000_gpio);
> +		dev_err(dev, "failed to get pp18000 gpio: %d\n", err);
> +		pinfo->pp18000_gpio = NULL;
> +	}
> +
> +	pinfo->reset_gpio = devm_gpiod_get_optional(dev, "enable",
> +							GPIOD_OUT_HIGH);
> +	if (IS_ERR(pinfo->reset_gpio)) {
> +		err = PTR_ERR(pinfo->reset_gpio);
> +		dev_err(dev, "failed to get enable gpio: %d\n", err);
> +		pinfo->reset_gpio = NULL;
> +	}
> +
> +	np = of_parse_phandle(dev->of_node, "backlight", 0);
> +	if (np) {
> +		pinfo->backlight = of_find_backlight_by_node(np);
> +		of_node_put(np);
> +
> +		if (!pinfo->backlight)
> +			return -EPROBE_DEFER;
> +	}
Use devm_of_find_backlight(). See other drivers how this
simplifies the drivers.
All put_device(&pinfo->backlight->dev) in this file can be dropped
as it is done automatically.

> +
> +static void panel_del(struct panel_info *pinfo)
> +{
> +	if (pinfo->base.dev)
> +		drm_panel_remove(&pinfo->base);
> +
> +	put_device(&pinfo->backlight->dev);
> +}
> +
> +static int panel_probe(struct mipi_dsi_device *dsi)
> +{
> +	struct panel_info *pinfo;
> +	int err;
> +
> +	pinfo = devm_kzalloc(&dsi->dev, sizeof(*pinfo), GFP_KERNEL);
> +	if (!pinfo)
> +		return -ENOMEM;
> +
> +	pinfo->link = dsi;
> +	panel_parse_dt(pinfo);
> +	mipi_dsi_set_drvdata(dsi, pinfo);
> +
> +	err = panel_add(pinfo);
> +	if (err < 0)
> +		return err;
> +
> +	return mipi_dsi_attach(dsi);
> +}
> +
> +static int panel_remove(struct mipi_dsi_device *dsi)
> +{
> +	struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi);
> +	int err;
> +
> +	err = panel_unprepare(&pinfo->base);
> +	if (err < 0)
> +		DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n",
> +			      err);
No need to writhe this, the called function does it already.
> +
> +	err = panel_disable(&pinfo->base);
> +	if (err < 0)
> +		DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", err);
> +
> +	err = mipi_dsi_detach(dsi);
> +	if (err < 0)
> +		DRM_DEV_ERROR(&dsi->dev, "failed to detach from DSI host: %d\n",
> +			      err);
> +
> +	drm_panel_detach(&pinfo->base);
> +	panel_del(pinfo);
Inline this function.
> +
> +	return 0;
> +}
> +
> +static void panel_shutdown(struct mipi_dsi_device *dsi)
> +{
> +	struct panel_info *pinfo = mipi_dsi_get_drvdata(dsi);
> +
> +	panel_unprepare(&pinfo->base);
> +	panel_disable(&pinfo->base);
> +}
> +
> +static struct mipi_dsi_driver panel_driver = {
> +	.driver = {
> +		.name = "panel-innolux-ota7290b",
> +		.of_match_table = panel_of_match,
> +	},
> +	.probe = panel_probe,
> +	.remove = panel_remove,
> +	.shutdown = panel_shutdown,
> +};
> +module_mipi_dsi_driver(panel_driver);
> +
> +MODULE_AUTHOR("Jerry Han <hanxu5@huaqin.corp-partner.google.com>");
> +MODULE_DESCRIPTION("Boe Himax8279d driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 1.9.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

end of thread, other threads:[~2019-02-23 22:38 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-02-22  2:32 [PATCH] [v1,2/2] drm/panel:Add Boe Himax8279d MIPI-DSI LCD panel Jerry Han
2019-02-23 22:38 ` Sam Ravnborg

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.