1*306e6407SSvyatoslav Ryhel // SPDX-License-Identifier: GPL-2.0-only 2*306e6407SSvyatoslav Ryhel /* 3*306e6407SSvyatoslav Ryhel * Copyright (c) 2016 XiaoMi, Inc. 4*306e6407SSvyatoslav Ryhel * Copyright (c) 2024 Svyatoslav Ryhel <clamor95@gmail.com> 5*306e6407SSvyatoslav Ryhel */ 6*306e6407SSvyatoslav Ryhel 7*306e6407SSvyatoslav Ryhel #include <linux/delay.h> 8*306e6407SSvyatoslav Ryhel #include <linux/gpio/consumer.h> 9*306e6407SSvyatoslav Ryhel #include <linux/module.h> 10*306e6407SSvyatoslav Ryhel #include <linux/of.h> 11*306e6407SSvyatoslav Ryhel #include <linux/of_graph.h> 12*306e6407SSvyatoslav Ryhel #include <linux/regulator/consumer.h> 13*306e6407SSvyatoslav Ryhel 14*306e6407SSvyatoslav Ryhel #include <video/mipi_display.h> 15*306e6407SSvyatoslav Ryhel 16*306e6407SSvyatoslav Ryhel #include <drm/drm_connector.h> 17*306e6407SSvyatoslav Ryhel #include <drm/drm_crtc.h> 18*306e6407SSvyatoslav Ryhel #include <drm/drm_device.h> 19*306e6407SSvyatoslav Ryhel #include <drm/drm_mipi_dsi.h> 20*306e6407SSvyatoslav Ryhel #include <drm/drm_modes.h> 21*306e6407SSvyatoslav Ryhel #include <drm/drm_panel.h> 22*306e6407SSvyatoslav Ryhel #include <drm/drm_probe_helper.h> 23*306e6407SSvyatoslav Ryhel 24*306e6407SSvyatoslav Ryhel static const struct regulator_bulk_data sharp_supplies[] = { 25*306e6407SSvyatoslav Ryhel { .supply = "avdd" }, { .supply = "vddio" }, 26*306e6407SSvyatoslav Ryhel { .supply = "vsp" }, { .supply = "vsn" }, 27*306e6407SSvyatoslav Ryhel }; 28*306e6407SSvyatoslav Ryhel 29*306e6407SSvyatoslav Ryhel struct sharp_panel { 30*306e6407SSvyatoslav Ryhel struct drm_panel panel; 31*306e6407SSvyatoslav Ryhel struct mipi_dsi_device *dsi[2]; 32*306e6407SSvyatoslav Ryhel 33*306e6407SSvyatoslav Ryhel struct gpio_desc *reset_gpio; 34*306e6407SSvyatoslav Ryhel struct regulator_bulk_data *supplies; 35*306e6407SSvyatoslav Ryhel 36*306e6407SSvyatoslav Ryhel const struct drm_display_mode *mode; 37*306e6407SSvyatoslav Ryhel }; 38*306e6407SSvyatoslav Ryhel 39*306e6407SSvyatoslav Ryhel static inline struct sharp_panel *to_sharp_panel(struct drm_panel *panel) 40*306e6407SSvyatoslav Ryhel { 41*306e6407SSvyatoslav Ryhel return container_of(panel, struct sharp_panel, panel); 42*306e6407SSvyatoslav Ryhel } 43*306e6407SSvyatoslav Ryhel 44*306e6407SSvyatoslav Ryhel static void sharp_panel_reset(struct sharp_panel *sharp) 45*306e6407SSvyatoslav Ryhel { 46*306e6407SSvyatoslav Ryhel gpiod_set_value_cansleep(sharp->reset_gpio, 1); 47*306e6407SSvyatoslav Ryhel usleep_range(2000, 3000); 48*306e6407SSvyatoslav Ryhel gpiod_set_value_cansleep(sharp->reset_gpio, 0); 49*306e6407SSvyatoslav Ryhel usleep_range(2000, 3000); 50*306e6407SSvyatoslav Ryhel } 51*306e6407SSvyatoslav Ryhel 52*306e6407SSvyatoslav Ryhel static int sharp_panel_prepare(struct drm_panel *panel) 53*306e6407SSvyatoslav Ryhel { 54*306e6407SSvyatoslav Ryhel struct sharp_panel *sharp = to_sharp_panel(panel); 55*306e6407SSvyatoslav Ryhel struct device *dev = panel->dev; 56*306e6407SSvyatoslav Ryhel struct mipi_dsi_device *dsi0 = sharp->dsi[0]; 57*306e6407SSvyatoslav Ryhel struct mipi_dsi_device *dsi1 = sharp->dsi[1]; 58*306e6407SSvyatoslav Ryhel struct mipi_dsi_multi_context dsi_ctx = { .dsi = NULL }; 59*306e6407SSvyatoslav Ryhel int ret; 60*306e6407SSvyatoslav Ryhel 61*306e6407SSvyatoslav Ryhel ret = regulator_bulk_enable(ARRAY_SIZE(sharp_supplies), sharp->supplies); 62*306e6407SSvyatoslav Ryhel if (ret) { 63*306e6407SSvyatoslav Ryhel dev_err(dev, "error enabling regulators (%d)\n", ret); 64*306e6407SSvyatoslav Ryhel return ret; 65*306e6407SSvyatoslav Ryhel } 66*306e6407SSvyatoslav Ryhel 67*306e6407SSvyatoslav Ryhel msleep(24); 68*306e6407SSvyatoslav Ryhel 69*306e6407SSvyatoslav Ryhel if (sharp->reset_gpio) 70*306e6407SSvyatoslav Ryhel sharp_panel_reset(sharp); 71*306e6407SSvyatoslav Ryhel 72*306e6407SSvyatoslav Ryhel msleep(32); 73*306e6407SSvyatoslav Ryhel 74*306e6407SSvyatoslav Ryhel mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, MIPI_DCS_EXIT_SLEEP_MODE); 75*306e6407SSvyatoslav Ryhel mipi_dsi_msleep(&dsi_ctx, 120); 76*306e6407SSvyatoslav Ryhel 77*306e6407SSvyatoslav Ryhel mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 78*306e6407SSvyatoslav Ryhel MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0xff); 79*306e6407SSvyatoslav Ryhel mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 80*306e6407SSvyatoslav Ryhel MIPI_DCS_WRITE_POWER_SAVE, 0x01); 81*306e6407SSvyatoslav Ryhel mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, 82*306e6407SSvyatoslav Ryhel MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x2c); 83*306e6407SSvyatoslav Ryhel 84*306e6407SSvyatoslav Ryhel mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, MIPI_DCS_SET_DISPLAY_ON); 85*306e6407SSvyatoslav Ryhel 86*306e6407SSvyatoslav Ryhel return 0; 87*306e6407SSvyatoslav Ryhel } 88*306e6407SSvyatoslav Ryhel 89*306e6407SSvyatoslav Ryhel static int sharp_panel_unprepare(struct drm_panel *panel) 90*306e6407SSvyatoslav Ryhel { 91*306e6407SSvyatoslav Ryhel struct sharp_panel *sharp = to_sharp_panel(panel); 92*306e6407SSvyatoslav Ryhel struct mipi_dsi_device *dsi0 = sharp->dsi[0]; 93*306e6407SSvyatoslav Ryhel struct mipi_dsi_device *dsi1 = sharp->dsi[1]; 94*306e6407SSvyatoslav Ryhel struct mipi_dsi_multi_context dsi_ctx = { .dsi = NULL }; 95*306e6407SSvyatoslav Ryhel 96*306e6407SSvyatoslav Ryhel mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, MIPI_DCS_SET_DISPLAY_OFF); 97*306e6407SSvyatoslav Ryhel mipi_dsi_msleep(&dsi_ctx, 100); 98*306e6407SSvyatoslav Ryhel mipi_dsi_dual_dcs_write_seq_multi(&dsi_ctx, dsi0, dsi1, MIPI_DCS_ENTER_SLEEP_MODE); 99*306e6407SSvyatoslav Ryhel mipi_dsi_msleep(&dsi_ctx, 150); 100*306e6407SSvyatoslav Ryhel 101*306e6407SSvyatoslav Ryhel if (sharp->reset_gpio) 102*306e6407SSvyatoslav Ryhel gpiod_set_value_cansleep(sharp->reset_gpio, 1); 103*306e6407SSvyatoslav Ryhel 104*306e6407SSvyatoslav Ryhel return regulator_bulk_disable(ARRAY_SIZE(sharp_supplies), sharp->supplies); 105*306e6407SSvyatoslav Ryhel } 106*306e6407SSvyatoslav Ryhel 107*306e6407SSvyatoslav Ryhel static const struct drm_display_mode default_mode = { 108*306e6407SSvyatoslav Ryhel .clock = (1536 + 136 + 28 + 28) * (2048 + 14 + 8 + 2) * 60 / 1000, 109*306e6407SSvyatoslav Ryhel .hdisplay = 1536, 110*306e6407SSvyatoslav Ryhel .hsync_start = 1536 + 136, 111*306e6407SSvyatoslav Ryhel .hsync_end = 1536 + 136 + 28, 112*306e6407SSvyatoslav Ryhel .htotal = 1536 + 136 + 28 + 28, 113*306e6407SSvyatoslav Ryhel .vdisplay = 2048, 114*306e6407SSvyatoslav Ryhel .vsync_start = 2048 + 14, 115*306e6407SSvyatoslav Ryhel .vsync_end = 2048 + 14 + 8, 116*306e6407SSvyatoslav Ryhel .vtotal = 2048 + 14 + 8 + 2, 117*306e6407SSvyatoslav Ryhel .width_mm = 120, 118*306e6407SSvyatoslav Ryhel .height_mm = 160, 119*306e6407SSvyatoslav Ryhel .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, 120*306e6407SSvyatoslav Ryhel }; 121*306e6407SSvyatoslav Ryhel 122*306e6407SSvyatoslav Ryhel static int sharp_panel_get_modes(struct drm_panel *panel, 123*306e6407SSvyatoslav Ryhel struct drm_connector *connector) 124*306e6407SSvyatoslav Ryhel { 125*306e6407SSvyatoslav Ryhel return drm_connector_helper_get_modes_fixed(connector, &default_mode); 126*306e6407SSvyatoslav Ryhel } 127*306e6407SSvyatoslav Ryhel 128*306e6407SSvyatoslav Ryhel static const struct drm_panel_funcs sharp_panel_funcs = { 129*306e6407SSvyatoslav Ryhel .unprepare = sharp_panel_unprepare, 130*306e6407SSvyatoslav Ryhel .prepare = sharp_panel_prepare, 131*306e6407SSvyatoslav Ryhel .get_modes = sharp_panel_get_modes, 132*306e6407SSvyatoslav Ryhel }; 133*306e6407SSvyatoslav Ryhel 134*306e6407SSvyatoslav Ryhel static int sharp_panel_probe(struct mipi_dsi_device *dsi) 135*306e6407SSvyatoslav Ryhel { 136*306e6407SSvyatoslav Ryhel const struct mipi_dsi_device_info info = { "sharp-link1", 0, NULL }; 137*306e6407SSvyatoslav Ryhel struct device *dev = &dsi->dev; 138*306e6407SSvyatoslav Ryhel struct device_node *dsi_r; 139*306e6407SSvyatoslav Ryhel struct mipi_dsi_host *dsi_r_host; 140*306e6407SSvyatoslav Ryhel struct sharp_panel *sharp; 141*306e6407SSvyatoslav Ryhel int i, ret; 142*306e6407SSvyatoslav Ryhel 143*306e6407SSvyatoslav Ryhel sharp = devm_drm_panel_alloc(dev, struct sharp_panel, panel, 144*306e6407SSvyatoslav Ryhel &sharp_panel_funcs, DRM_MODE_CONNECTOR_DSI); 145*306e6407SSvyatoslav Ryhel if (IS_ERR(sharp)) 146*306e6407SSvyatoslav Ryhel return PTR_ERR(sharp); 147*306e6407SSvyatoslav Ryhel 148*306e6407SSvyatoslav Ryhel ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(sharp_supplies), 149*306e6407SSvyatoslav Ryhel sharp_supplies, &sharp->supplies); 150*306e6407SSvyatoslav Ryhel if (ret) 151*306e6407SSvyatoslav Ryhel return dev_err_probe(dev, ret, "failed to get supplies\n"); 152*306e6407SSvyatoslav Ryhel 153*306e6407SSvyatoslav Ryhel sharp->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); 154*306e6407SSvyatoslav Ryhel if (IS_ERR(sharp->reset_gpio)) 155*306e6407SSvyatoslav Ryhel return dev_err_probe(dev, PTR_ERR(sharp->reset_gpio), 156*306e6407SSvyatoslav Ryhel "failed to get reset GPIO\n"); 157*306e6407SSvyatoslav Ryhel 158*306e6407SSvyatoslav Ryhel /* Panel is always connected to two DSI hosts, DSI0 is left, DSI1 is right */ 159*306e6407SSvyatoslav Ryhel dsi_r = of_graph_get_remote_node(dsi->dev.of_node, 1, -1); 160*306e6407SSvyatoslav Ryhel if (!dsi_r) 161*306e6407SSvyatoslav Ryhel return dev_err_probe(dev, -ENODEV, "failed to find second DSI host node\n"); 162*306e6407SSvyatoslav Ryhel 163*306e6407SSvyatoslav Ryhel dsi_r_host = of_find_mipi_dsi_host_by_node(dsi_r); 164*306e6407SSvyatoslav Ryhel of_node_put(dsi_r); 165*306e6407SSvyatoslav Ryhel if (!dsi_r_host) 166*306e6407SSvyatoslav Ryhel return dev_err_probe(dev, -EPROBE_DEFER, "cannot get secondary DSI host\n"); 167*306e6407SSvyatoslav Ryhel 168*306e6407SSvyatoslav Ryhel sharp->dsi[1] = devm_mipi_dsi_device_register_full(dev, dsi_r_host, &info); 169*306e6407SSvyatoslav Ryhel if (IS_ERR(sharp->dsi[1])) 170*306e6407SSvyatoslav Ryhel return dev_err_probe(dev, PTR_ERR(sharp->dsi[1]), 171*306e6407SSvyatoslav Ryhel "second link registration failed\n"); 172*306e6407SSvyatoslav Ryhel 173*306e6407SSvyatoslav Ryhel sharp->dsi[0] = dsi; 174*306e6407SSvyatoslav Ryhel mipi_dsi_set_drvdata(dsi, sharp); 175*306e6407SSvyatoslav Ryhel 176*306e6407SSvyatoslav Ryhel ret = drm_panel_of_backlight(&sharp->panel); 177*306e6407SSvyatoslav Ryhel if (ret) 178*306e6407SSvyatoslav Ryhel return dev_err_probe(dev, ret, "Failed to get backlight\n"); 179*306e6407SSvyatoslav Ryhel 180*306e6407SSvyatoslav Ryhel drm_panel_add(&sharp->panel); 181*306e6407SSvyatoslav Ryhel 182*306e6407SSvyatoslav Ryhel for (i = 0; i < ARRAY_SIZE(sharp->dsi); i++) { 183*306e6407SSvyatoslav Ryhel if (!sharp->dsi[i]) 184*306e6407SSvyatoslav Ryhel continue; 185*306e6407SSvyatoslav Ryhel 186*306e6407SSvyatoslav Ryhel sharp->dsi[i]->lanes = 4; 187*306e6407SSvyatoslav Ryhel sharp->dsi[i]->format = MIPI_DSI_FMT_RGB888; 188*306e6407SSvyatoslav Ryhel sharp->dsi[i]->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM; 189*306e6407SSvyatoslav Ryhel 190*306e6407SSvyatoslav Ryhel ret = devm_mipi_dsi_attach(dev, sharp->dsi[i]); 191*306e6407SSvyatoslav Ryhel if (ret < 0) { 192*306e6407SSvyatoslav Ryhel drm_panel_remove(&sharp->panel); 193*306e6407SSvyatoslav Ryhel return dev_err_probe(dev, ret, "failed to attach to DSI%d\n", i); 194*306e6407SSvyatoslav Ryhel } 195*306e6407SSvyatoslav Ryhel } 196*306e6407SSvyatoslav Ryhel 197*306e6407SSvyatoslav Ryhel return 0; 198*306e6407SSvyatoslav Ryhel } 199*306e6407SSvyatoslav Ryhel 200*306e6407SSvyatoslav Ryhel static void sharp_panel_remove(struct mipi_dsi_device *dsi) 201*306e6407SSvyatoslav Ryhel { 202*306e6407SSvyatoslav Ryhel struct sharp_panel *sharp = mipi_dsi_get_drvdata(dsi); 203*306e6407SSvyatoslav Ryhel 204*306e6407SSvyatoslav Ryhel drm_panel_remove(&sharp->panel); 205*306e6407SSvyatoslav Ryhel } 206*306e6407SSvyatoslav Ryhel 207*306e6407SSvyatoslav Ryhel static const struct of_device_id sharp_of_match[] = { 208*306e6407SSvyatoslav Ryhel { .compatible = "sharp,lq079l1sx01" }, 209*306e6407SSvyatoslav Ryhel { } 210*306e6407SSvyatoslav Ryhel }; 211*306e6407SSvyatoslav Ryhel MODULE_DEVICE_TABLE(of, sharp_of_match); 212*306e6407SSvyatoslav Ryhel 213*306e6407SSvyatoslav Ryhel static struct mipi_dsi_driver sharp_panel_driver = { 214*306e6407SSvyatoslav Ryhel .driver = { 215*306e6407SSvyatoslav Ryhel .name = "panel-sharp-lq079l1sx01", 216*306e6407SSvyatoslav Ryhel .of_match_table = sharp_of_match, 217*306e6407SSvyatoslav Ryhel }, 218*306e6407SSvyatoslav Ryhel .probe = sharp_panel_probe, 219*306e6407SSvyatoslav Ryhel .remove = sharp_panel_remove, 220*306e6407SSvyatoslav Ryhel }; 221*306e6407SSvyatoslav Ryhel module_mipi_dsi_driver(sharp_panel_driver); 222*306e6407SSvyatoslav Ryhel 223*306e6407SSvyatoslav Ryhel MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); 224*306e6407SSvyatoslav Ryhel MODULE_DESCRIPTION("Sharp LQ079L1SX01 panel driver"); 225*306e6407SSvyatoslav Ryhel MODULE_LICENSE("GPL"); 226