xref: /openbmc/linux/drivers/gpu/drm/panel/panel-innolux-p079zca.c (revision c69f94570d7ab4c8cbc5ba14729cc73a7dbdbfec)
114c8f2e9SChris Zhong /*
214c8f2e9SChris Zhong  * Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd
314c8f2e9SChris Zhong  *
414c8f2e9SChris Zhong  * This program is free software; you can redistribute it and/or modify
514c8f2e9SChris Zhong  * it under the terms of the GNU General Public License as published by
614c8f2e9SChris Zhong  * the Free Software Foundation; either version 2 of the License, or
714c8f2e9SChris Zhong  * (at your option) any later version.
814c8f2e9SChris Zhong  */
914c8f2e9SChris Zhong 
1014c8f2e9SChris Zhong #include <linux/backlight.h>
1114c8f2e9SChris Zhong #include <linux/gpio/consumer.h>
1214c8f2e9SChris Zhong #include <linux/module.h>
1314c8f2e9SChris Zhong #include <linux/of.h>
1414c8f2e9SChris Zhong #include <linux/regulator/consumer.h>
1514c8f2e9SChris Zhong 
1614c8f2e9SChris Zhong #include <drm/drmP.h>
1714c8f2e9SChris Zhong #include <drm/drm_crtc.h>
1814c8f2e9SChris Zhong #include <drm/drm_mipi_dsi.h>
1914c8f2e9SChris Zhong #include <drm/drm_panel.h>
2014c8f2e9SChris Zhong 
2114c8f2e9SChris Zhong #include <video/mipi_display.h>
2214c8f2e9SChris Zhong 
2314c8f2e9SChris Zhong struct innolux_panel {
2414c8f2e9SChris Zhong 	struct drm_panel base;
2514c8f2e9SChris Zhong 	struct mipi_dsi_device *link;
2614c8f2e9SChris Zhong 
2714c8f2e9SChris Zhong 	struct backlight_device *backlight;
2814c8f2e9SChris Zhong 	struct regulator *supply;
2914c8f2e9SChris Zhong 	struct gpio_desc *enable_gpio;
3014c8f2e9SChris Zhong 
3114c8f2e9SChris Zhong 	bool prepared;
3214c8f2e9SChris Zhong 	bool enabled;
3314c8f2e9SChris Zhong };
3414c8f2e9SChris Zhong 
3514c8f2e9SChris Zhong static inline struct innolux_panel *to_innolux_panel(struct drm_panel *panel)
3614c8f2e9SChris Zhong {
3714c8f2e9SChris Zhong 	return container_of(panel, struct innolux_panel, base);
3814c8f2e9SChris Zhong }
3914c8f2e9SChris Zhong 
4014c8f2e9SChris Zhong static int innolux_panel_disable(struct drm_panel *panel)
4114c8f2e9SChris Zhong {
4214c8f2e9SChris Zhong 	struct innolux_panel *innolux = to_innolux_panel(panel);
4314c8f2e9SChris Zhong 	int err;
4414c8f2e9SChris Zhong 
4514c8f2e9SChris Zhong 	if (!innolux->enabled)
4614c8f2e9SChris Zhong 		return 0;
4714c8f2e9SChris Zhong 
48d593bfdbSMeghana Madhyastha 	backlight_disable(innolux->backlight);
4914c8f2e9SChris Zhong 
5014c8f2e9SChris Zhong 	err = mipi_dsi_dcs_set_display_off(innolux->link);
5114c8f2e9SChris Zhong 	if (err < 0)
5214c8f2e9SChris Zhong 		DRM_DEV_ERROR(panel->dev, "failed to set display off: %d\n",
5314c8f2e9SChris Zhong 			      err);
5414c8f2e9SChris Zhong 
5514c8f2e9SChris Zhong 	innolux->enabled = false;
5614c8f2e9SChris Zhong 
5714c8f2e9SChris Zhong 	return 0;
5814c8f2e9SChris Zhong }
5914c8f2e9SChris Zhong 
6014c8f2e9SChris Zhong static int innolux_panel_unprepare(struct drm_panel *panel)
6114c8f2e9SChris Zhong {
6214c8f2e9SChris Zhong 	struct innolux_panel *innolux = to_innolux_panel(panel);
6314c8f2e9SChris Zhong 	int err;
6414c8f2e9SChris Zhong 
6514c8f2e9SChris Zhong 	if (!innolux->prepared)
6614c8f2e9SChris Zhong 		return 0;
6714c8f2e9SChris Zhong 
6814c8f2e9SChris Zhong 	err = mipi_dsi_dcs_enter_sleep_mode(innolux->link);
6914c8f2e9SChris Zhong 	if (err < 0) {
7014c8f2e9SChris Zhong 		DRM_DEV_ERROR(panel->dev, "failed to enter sleep mode: %d\n",
7114c8f2e9SChris Zhong 			      err);
7214c8f2e9SChris Zhong 		return err;
7314c8f2e9SChris Zhong 	}
7414c8f2e9SChris Zhong 
7514c8f2e9SChris Zhong 	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
7614c8f2e9SChris Zhong 
7714c8f2e9SChris Zhong 	/* T8: 80ms - 1000ms */
7814c8f2e9SChris Zhong 	msleep(80);
7914c8f2e9SChris Zhong 
8014c8f2e9SChris Zhong 	err = regulator_disable(innolux->supply);
8114c8f2e9SChris Zhong 	if (err < 0)
8214c8f2e9SChris Zhong 		return err;
8314c8f2e9SChris Zhong 
8414c8f2e9SChris Zhong 	innolux->prepared = false;
8514c8f2e9SChris Zhong 
8614c8f2e9SChris Zhong 	return 0;
8714c8f2e9SChris Zhong }
8814c8f2e9SChris Zhong 
8914c8f2e9SChris Zhong static int innolux_panel_prepare(struct drm_panel *panel)
9014c8f2e9SChris Zhong {
9114c8f2e9SChris Zhong 	struct innolux_panel *innolux = to_innolux_panel(panel);
9214c8f2e9SChris Zhong 	int err, regulator_err;
9314c8f2e9SChris Zhong 
9414c8f2e9SChris Zhong 	if (innolux->prepared)
9514c8f2e9SChris Zhong 		return 0;
9614c8f2e9SChris Zhong 
9714c8f2e9SChris Zhong 	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
9814c8f2e9SChris Zhong 
9914c8f2e9SChris Zhong 	err = regulator_enable(innolux->supply);
10014c8f2e9SChris Zhong 	if (err < 0)
10114c8f2e9SChris Zhong 		return err;
10214c8f2e9SChris Zhong 
10314c8f2e9SChris Zhong 	/* T2: 15ms - 1000ms */
10414c8f2e9SChris Zhong 	usleep_range(15000, 16000);
10514c8f2e9SChris Zhong 
10614c8f2e9SChris Zhong 	gpiod_set_value_cansleep(innolux->enable_gpio, 1);
10714c8f2e9SChris Zhong 
10814c8f2e9SChris Zhong 	/* T4: 15ms - 1000ms */
10914c8f2e9SChris Zhong 	usleep_range(15000, 16000);
11014c8f2e9SChris Zhong 
11114c8f2e9SChris Zhong 	err = mipi_dsi_dcs_exit_sleep_mode(innolux->link);
11214c8f2e9SChris Zhong 	if (err < 0) {
11314c8f2e9SChris Zhong 		DRM_DEV_ERROR(panel->dev, "failed to exit sleep mode: %d\n",
11414c8f2e9SChris Zhong 			      err);
11514c8f2e9SChris Zhong 		goto poweroff;
11614c8f2e9SChris Zhong 	}
11714c8f2e9SChris Zhong 
11814c8f2e9SChris Zhong 	/* T6: 120ms - 1000ms*/
11914c8f2e9SChris Zhong 	msleep(120);
12014c8f2e9SChris Zhong 
12114c8f2e9SChris Zhong 	err = mipi_dsi_dcs_set_display_on(innolux->link);
12214c8f2e9SChris Zhong 	if (err < 0) {
12314c8f2e9SChris Zhong 		DRM_DEV_ERROR(panel->dev, "failed to set display on: %d\n",
12414c8f2e9SChris Zhong 			      err);
12514c8f2e9SChris Zhong 		goto poweroff;
12614c8f2e9SChris Zhong 	}
12714c8f2e9SChris Zhong 
12814c8f2e9SChris Zhong 	/* T7: 5ms */
12914c8f2e9SChris Zhong 	usleep_range(5000, 6000);
13014c8f2e9SChris Zhong 
13114c8f2e9SChris Zhong 	innolux->prepared = true;
13214c8f2e9SChris Zhong 
13314c8f2e9SChris Zhong 	return 0;
13414c8f2e9SChris Zhong 
13514c8f2e9SChris Zhong poweroff:
13614c8f2e9SChris Zhong 	regulator_err = regulator_disable(innolux->supply);
13714c8f2e9SChris Zhong 	if (regulator_err)
13814c8f2e9SChris Zhong 		DRM_DEV_ERROR(panel->dev, "failed to disable regulator: %d\n",
13914c8f2e9SChris Zhong 			      regulator_err);
14014c8f2e9SChris Zhong 
14114c8f2e9SChris Zhong 	gpiod_set_value_cansleep(innolux->enable_gpio, 0);
14214c8f2e9SChris Zhong 	return err;
14314c8f2e9SChris Zhong }
14414c8f2e9SChris Zhong 
14514c8f2e9SChris Zhong static int innolux_panel_enable(struct drm_panel *panel)
14614c8f2e9SChris Zhong {
14714c8f2e9SChris Zhong 	struct innolux_panel *innolux = to_innolux_panel(panel);
14814c8f2e9SChris Zhong 	int ret;
14914c8f2e9SChris Zhong 
15014c8f2e9SChris Zhong 	if (innolux->enabled)
15114c8f2e9SChris Zhong 		return 0;
15214c8f2e9SChris Zhong 
153d593bfdbSMeghana Madhyastha 	ret = backlight_enable(innolux->backlight);
15414c8f2e9SChris Zhong 	if (ret) {
15514c8f2e9SChris Zhong 		DRM_DEV_ERROR(panel->drm->dev,
15614c8f2e9SChris Zhong 			      "Failed to enable backlight %d\n", ret);
15714c8f2e9SChris Zhong 		return ret;
15814c8f2e9SChris Zhong 	}
15914c8f2e9SChris Zhong 
16014c8f2e9SChris Zhong 	innolux->enabled = true;
16114c8f2e9SChris Zhong 
16214c8f2e9SChris Zhong 	return 0;
16314c8f2e9SChris Zhong }
16414c8f2e9SChris Zhong 
16514c8f2e9SChris Zhong static const struct drm_display_mode default_mode = {
16614c8f2e9SChris Zhong 	.clock = 56900,
16714c8f2e9SChris Zhong 	.hdisplay = 768,
16814c8f2e9SChris Zhong 	.hsync_start = 768 + 40,
16914c8f2e9SChris Zhong 	.hsync_end = 768 + 40 + 40,
17014c8f2e9SChris Zhong 	.htotal = 768 + 40 + 40 + 40,
17114c8f2e9SChris Zhong 	.vdisplay = 1024,
17214c8f2e9SChris Zhong 	.vsync_start = 1024 + 20,
17314c8f2e9SChris Zhong 	.vsync_end = 1024 + 20 + 4,
17414c8f2e9SChris Zhong 	.vtotal = 1024 + 20 + 4 + 20,
17514c8f2e9SChris Zhong 	.vrefresh = 60,
17614c8f2e9SChris Zhong };
17714c8f2e9SChris Zhong 
17814c8f2e9SChris Zhong static int innolux_panel_get_modes(struct drm_panel *panel)
17914c8f2e9SChris Zhong {
18014c8f2e9SChris Zhong 	struct drm_display_mode *mode;
18114c8f2e9SChris Zhong 
18214c8f2e9SChris Zhong 	mode = drm_mode_duplicate(panel->drm, &default_mode);
18314c8f2e9SChris Zhong 	if (!mode) {
18414c8f2e9SChris Zhong 		DRM_DEV_ERROR(panel->drm->dev, "failed to add mode %ux%ux@%u\n",
18514c8f2e9SChris Zhong 			      default_mode.hdisplay, default_mode.vdisplay,
18614c8f2e9SChris Zhong 			      default_mode.vrefresh);
18714c8f2e9SChris Zhong 		return -ENOMEM;
18814c8f2e9SChris Zhong 	}
18914c8f2e9SChris Zhong 
19014c8f2e9SChris Zhong 	drm_mode_set_name(mode);
19114c8f2e9SChris Zhong 
19214c8f2e9SChris Zhong 	drm_mode_probed_add(panel->connector, mode);
19314c8f2e9SChris Zhong 
19414c8f2e9SChris Zhong 	panel->connector->display_info.width_mm = 120;
19514c8f2e9SChris Zhong 	panel->connector->display_info.height_mm = 160;
19614c8f2e9SChris Zhong 	panel->connector->display_info.bpc = 8;
19714c8f2e9SChris Zhong 
19814c8f2e9SChris Zhong 	return 1;
19914c8f2e9SChris Zhong }
20014c8f2e9SChris Zhong 
20114c8f2e9SChris Zhong static const struct drm_panel_funcs innolux_panel_funcs = {
20214c8f2e9SChris Zhong 	.disable = innolux_panel_disable,
20314c8f2e9SChris Zhong 	.unprepare = innolux_panel_unprepare,
20414c8f2e9SChris Zhong 	.prepare = innolux_panel_prepare,
20514c8f2e9SChris Zhong 	.enable = innolux_panel_enable,
20614c8f2e9SChris Zhong 	.get_modes = innolux_panel_get_modes,
20714c8f2e9SChris Zhong };
20814c8f2e9SChris Zhong 
20914c8f2e9SChris Zhong static const struct of_device_id innolux_of_match[] = {
21014c8f2e9SChris Zhong 	{ .compatible = "innolux,p079zca", },
21114c8f2e9SChris Zhong 	{ }
21214c8f2e9SChris Zhong };
21314c8f2e9SChris Zhong MODULE_DEVICE_TABLE(of, innolux_of_match);
21414c8f2e9SChris Zhong 
21514c8f2e9SChris Zhong static int innolux_panel_add(struct innolux_panel *innolux)
21614c8f2e9SChris Zhong {
21714c8f2e9SChris Zhong 	struct device *dev = &innolux->link->dev;
21814c8f2e9SChris Zhong 	int err;
21914c8f2e9SChris Zhong 
22014c8f2e9SChris Zhong 	innolux->supply = devm_regulator_get(dev, "power");
22114c8f2e9SChris Zhong 	if (IS_ERR(innolux->supply))
22214c8f2e9SChris Zhong 		return PTR_ERR(innolux->supply);
22314c8f2e9SChris Zhong 
22414c8f2e9SChris Zhong 	innolux->enable_gpio = devm_gpiod_get_optional(dev, "enable",
22514c8f2e9SChris Zhong 						       GPIOD_OUT_HIGH);
22614c8f2e9SChris Zhong 	if (IS_ERR(innolux->enable_gpio)) {
22714c8f2e9SChris Zhong 		err = PTR_ERR(innolux->enable_gpio);
22814c8f2e9SChris Zhong 		dev_dbg(dev, "failed to get enable gpio: %d\n", err);
22914c8f2e9SChris Zhong 		innolux->enable_gpio = NULL;
23014c8f2e9SChris Zhong 	}
23114c8f2e9SChris Zhong 
232*c69f9457SMeghana Madhyastha 	innolux->backlight = devm_of_find_backlight(dev);
23314c8f2e9SChris Zhong 
234*c69f9457SMeghana Madhyastha 	if (IS_ERR(innolux->backlight))
235*c69f9457SMeghana Madhyastha 		return PTR_ERR(innolux->backlight);
23614c8f2e9SChris Zhong 
23714c8f2e9SChris Zhong 	drm_panel_init(&innolux->base);
23814c8f2e9SChris Zhong 	innolux->base.funcs = &innolux_panel_funcs;
23914c8f2e9SChris Zhong 	innolux->base.dev = &innolux->link->dev;
24014c8f2e9SChris Zhong 
241*c69f9457SMeghana Madhyastha 	return drm_panel_add(&innolux->base);
24214c8f2e9SChris Zhong }
24314c8f2e9SChris Zhong 
24414c8f2e9SChris Zhong static void innolux_panel_del(struct innolux_panel *innolux)
24514c8f2e9SChris Zhong {
24614c8f2e9SChris Zhong 	if (innolux->base.dev)
24714c8f2e9SChris Zhong 		drm_panel_remove(&innolux->base);
24814c8f2e9SChris Zhong }
24914c8f2e9SChris Zhong 
25014c8f2e9SChris Zhong static int innolux_panel_probe(struct mipi_dsi_device *dsi)
25114c8f2e9SChris Zhong {
25214c8f2e9SChris Zhong 	struct innolux_panel *innolux;
25314c8f2e9SChris Zhong 	int err;
25414c8f2e9SChris Zhong 
25514c8f2e9SChris Zhong 	dsi->lanes = 4;
25614c8f2e9SChris Zhong 	dsi->format = MIPI_DSI_FMT_RGB888;
25714c8f2e9SChris Zhong 	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
25814c8f2e9SChris Zhong 			  MIPI_DSI_MODE_LPM;
25914c8f2e9SChris Zhong 
26014c8f2e9SChris Zhong 	innolux = devm_kzalloc(&dsi->dev, sizeof(*innolux), GFP_KERNEL);
26114c8f2e9SChris Zhong 	if (!innolux)
26214c8f2e9SChris Zhong 		return -ENOMEM;
26314c8f2e9SChris Zhong 
26414c8f2e9SChris Zhong 	mipi_dsi_set_drvdata(dsi, innolux);
26514c8f2e9SChris Zhong 
26614c8f2e9SChris Zhong 	innolux->link = dsi;
26714c8f2e9SChris Zhong 
26814c8f2e9SChris Zhong 	err = innolux_panel_add(innolux);
26914c8f2e9SChris Zhong 	if (err < 0)
27014c8f2e9SChris Zhong 		return err;
27114c8f2e9SChris Zhong 
27214c8f2e9SChris Zhong 	err = mipi_dsi_attach(dsi);
27314c8f2e9SChris Zhong 	return err;
27414c8f2e9SChris Zhong }
27514c8f2e9SChris Zhong 
27614c8f2e9SChris Zhong static int innolux_panel_remove(struct mipi_dsi_device *dsi)
27714c8f2e9SChris Zhong {
27814c8f2e9SChris Zhong 	struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
27914c8f2e9SChris Zhong 	int err;
28014c8f2e9SChris Zhong 
28114c8f2e9SChris Zhong 	err = innolux_panel_unprepare(&innolux->base);
28214c8f2e9SChris Zhong 	if (err < 0)
28314c8f2e9SChris Zhong 		DRM_DEV_ERROR(&dsi->dev, "failed to unprepare panel: %d\n",
28414c8f2e9SChris Zhong 			      err);
28514c8f2e9SChris Zhong 
28614c8f2e9SChris Zhong 	err = innolux_panel_disable(&innolux->base);
28714c8f2e9SChris Zhong 	if (err < 0)
28814c8f2e9SChris Zhong 		DRM_DEV_ERROR(&dsi->dev, "failed to disable panel: %d\n", err);
28914c8f2e9SChris Zhong 
29014c8f2e9SChris Zhong 	err = mipi_dsi_detach(dsi);
29114c8f2e9SChris Zhong 	if (err < 0)
29214c8f2e9SChris Zhong 		DRM_DEV_ERROR(&dsi->dev, "failed to detach from DSI host: %d\n",
29314c8f2e9SChris Zhong 			      err);
29414c8f2e9SChris Zhong 
29514c8f2e9SChris Zhong 	drm_panel_detach(&innolux->base);
29614c8f2e9SChris Zhong 	innolux_panel_del(innolux);
29714c8f2e9SChris Zhong 
29814c8f2e9SChris Zhong 	return 0;
29914c8f2e9SChris Zhong }
30014c8f2e9SChris Zhong 
30114c8f2e9SChris Zhong static void innolux_panel_shutdown(struct mipi_dsi_device *dsi)
30214c8f2e9SChris Zhong {
30314c8f2e9SChris Zhong 	struct innolux_panel *innolux = mipi_dsi_get_drvdata(dsi);
30414c8f2e9SChris Zhong 
30514c8f2e9SChris Zhong 	innolux_panel_unprepare(&innolux->base);
30614c8f2e9SChris Zhong 	innolux_panel_disable(&innolux->base);
30714c8f2e9SChris Zhong }
30814c8f2e9SChris Zhong 
30914c8f2e9SChris Zhong static struct mipi_dsi_driver innolux_panel_driver = {
31014c8f2e9SChris Zhong 	.driver = {
31114c8f2e9SChris Zhong 		.name = "panel-innolux-p079zca",
31214c8f2e9SChris Zhong 		.of_match_table = innolux_of_match,
31314c8f2e9SChris Zhong 	},
31414c8f2e9SChris Zhong 	.probe = innolux_panel_probe,
31514c8f2e9SChris Zhong 	.remove = innolux_panel_remove,
31614c8f2e9SChris Zhong 	.shutdown = innolux_panel_shutdown,
31714c8f2e9SChris Zhong };
31814c8f2e9SChris Zhong module_mipi_dsi_driver(innolux_panel_driver);
31914c8f2e9SChris Zhong 
32014c8f2e9SChris Zhong MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
32114c8f2e9SChris Zhong MODULE_DESCRIPTION("Innolux P079ZCA panel driver");
32214c8f2e9SChris Zhong MODULE_LICENSE("GPL v2");
323