Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/panel: add lincolntech lcd197 support

Add support for the Lincoln Technologies LCD197 1080x1920 DSI panel.

Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Link: https://lore.kernel.org/r/20240626142212.1341556-4-jbrunet@baylibre.com
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20240626142212.1341556-4-jbrunet@baylibre.com

authored by

Jerome Brunet and committed by
Neil Armstrong
c5207ed4 3ebc76c4

+274
+11
drivers/gpu/drm/panel/Kconfig
··· 328 328 24 bit RGB per pixel. It provides a MIPI DSI interface to 329 329 the host and has a built-in LED backlight. 330 330 331 + config DRM_PANEL_LINCOLNTECH_LCD197 332 + tristate "Lincoln Technologies lcd197 panel" 333 + depends on OF 334 + depends on DRM_MIPI_DSI 335 + depends on BACKLIGHT_CLASS_DEVICE 336 + help 337 + Say Y here if you want to enable support for lincolntech lcd197 338 + TFT-LCD modules. The panel has a 1080x1920 resolution and uses 339 + 24 bit RGB per pixel. It provides a MIPI DSI interface to 340 + the host. 341 + 331 342 config DRM_PANEL_LG_LB035Q02 332 343 tristate "LG LB035Q024573 RGB panel" 333 344 depends on GPIOLIB && OF && SPI
+1
drivers/gpu/drm/panel/Makefile
··· 33 33 obj-$(CONFIG_DRM_PANEL_KINGDISPLAY_KD097D04) += panel-kingdisplay-kd097d04.o 34 34 obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK050H3146W) += panel-leadtek-ltk050h3146w.o 35 35 obj-$(CONFIG_DRM_PANEL_LEADTEK_LTK500HD1829) += panel-leadtek-ltk500hd1829.o 36 + obj-$(CONFIG_DRM_PANEL_LINCOLNTECH_LCD197) += panel-lincolntech-lcd197.o 36 37 obj-$(CONFIG_DRM_PANEL_LG_LB035Q02) += panel-lg-lb035q02.o 37 38 obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o 38 39 obj-$(CONFIG_DRM_PANEL_LG_SW43408) += panel-lg-sw43408.o
+262
drivers/gpu/drm/panel/panel-lincolntech-lcd197.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright (C) 2024 BayLibre, SAS 4 + * Author: Jerome Brunet <jbrunet@baylibre.com> 5 + */ 6 + 7 + #include <linux/gpio/consumer.h> 8 + #include <linux/module.h> 9 + #include <linux/of.h> 10 + #include <linux/regulator/consumer.h> 11 + 12 + #include <video/mipi_display.h> 13 + 14 + #include <drm/drm_device.h> 15 + #include <drm/drm_probe_helper.h> 16 + #include <drm/drm_mipi_dsi.h> 17 + #include <drm/drm_modes.h> 18 + #include <drm/drm_panel.h> 19 + 20 + struct lincoln_lcd197_panel { 21 + struct drm_panel panel; 22 + struct mipi_dsi_device *dsi; 23 + struct regulator *supply; 24 + struct gpio_desc *enable_gpio; 25 + struct gpio_desc *reset_gpio; 26 + }; 27 + 28 + static inline 29 + struct lincoln_lcd197_panel *to_lincoln_lcd197_panel(struct drm_panel *panel) 30 + { 31 + return container_of(panel, struct lincoln_lcd197_panel, panel); 32 + } 33 + 34 + static int lincoln_lcd197_panel_prepare(struct drm_panel *panel) 35 + { 36 + struct lincoln_lcd197_panel *lcd = to_lincoln_lcd197_panel(panel); 37 + struct mipi_dsi_multi_context ctx = { .dsi = lcd->dsi }; 38 + int err; 39 + 40 + gpiod_set_value_cansleep(lcd->enable_gpio, 0); 41 + err = regulator_enable(lcd->supply); 42 + if (err < 0) 43 + return err; 44 + 45 + gpiod_set_value_cansleep(lcd->enable_gpio, 1); 46 + usleep_range(1000, 2000); 47 + gpiod_set_value_cansleep(lcd->reset_gpio, 1); 48 + usleep_range(5000, 6000); 49 + gpiod_set_value_cansleep(lcd->reset_gpio, 0); 50 + msleep(50); 51 + 52 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb9, 0xff, 0x83, 0x99); 53 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd2, 0x55); 54 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb1, 0x02, 0x04, 0x70, 0x90, 0x01, 55 + 0x32, 0x33, 0x11, 0x11, 0x4d, 0x57, 0x56, 0x73, 56 + 0x02, 0x02); 57 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb2, 0x00, 0x80, 0x80, 0xae, 0x0a, 58 + 0x0e, 0x75, 0x11, 0x00, 0x00, 0x00); 59 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb4, 0x00, 0xff, 0x04, 0xa4, 0x02, 60 + 0xa0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x02, 0x00, 61 + 0x24, 0x02, 0x04, 0x0a, 0x21, 0x03, 0x00, 0x00, 62 + 0x08, 0xa6, 0x88, 0x04, 0xa4, 0x02, 0xa0, 0x00, 63 + 0x00, 0x10, 0x00, 0x00, 0x02, 0x00, 0x24, 0x02, 64 + 0x04, 0x0a, 0x00, 0x00, 0x08, 0xa6, 0x00, 0x08, 65 + 0x11); 66 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, 67 + 0x00, 0x18, 0x18, 0x32, 0x10, 0x09, 0x00, 0x09, 68 + 0x32, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 69 + 0x00, 0x00, 0x11, 0x00, 0x02, 0x02, 0x03, 0x00, 70 + 0x00, 0x00, 0x0a, 0x40); 71 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd5, 0x18, 0x18, 0x18, 0x18, 0x21, 72 + 0x20, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x18, 73 + 0x18, 0x18, 0x18, 0x03, 0x02, 0x01, 0x00, 0x2f, 74 + 0x2f, 0x30, 0x30, 0x31, 0x31, 0x18, 0x18, 0x18, 75 + 0x18, 0x18, 0x18); 76 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd6, 0x18, 0x18, 0x18, 0x18, 0x20, 77 + 0x21, 0x19, 0x19, 0x18, 0x18, 0x19, 0x19, 0x18, 78 + 0x18, 0x18, 0x18, 0x00, 0x01, 0x02, 0x03, 0x2f, 79 + 0x2f, 0x30, 0x30, 0x31, 0x31, 0x18, 0x18, 0x18, 80 + 0x18, 0x18, 0x18); 81 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x01); 82 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x0a, 0xbe, 0xfa, 0xa0, 0x0a, 83 + 0xbe, 0xfa, 0xa0); 84 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x0f, 0xff, 0xff, 0xe0, 0x0f, 85 + 0xff, 0xff, 0xe0); 86 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbd, 0x02); 87 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xd8, 0x0f, 0xff, 0xff, 0xe0, 0x0f, 88 + 0xff, 0xff, 0xe0); 89 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xe0, 0x01, 0x11, 0x1c, 0x17, 0x39, 90 + 0x43, 0x54, 0x51, 0x5a, 0x64, 0x6c, 0x74, 0x7a, 91 + 0x83, 0x8d, 0x92, 0x99, 0xa4, 0xa9, 0xb4, 0xaa, 92 + 0xba, 0xbe, 0x63, 0x5e, 0x69, 0x73, 0x01, 0x11, 93 + 0x1c, 0x17, 0x39, 0x43, 0x54, 0x51, 0x5a, 0x64, 94 + 0x6c, 0x74, 0x7a, 0x83, 0x8d, 0x92, 0x99, 0xa4, 95 + 0xa7, 0xb2, 0xa9, 0xba, 0xbe, 0x63, 0x5e, 0x69, 96 + 0x73); 97 + mipi_dsi_usleep_range(&ctx, 200, 300); 98 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xb6, 0x92, 0x92); 99 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xcc, 0x00); 100 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xbf, 0x40, 0x41, 0x50, 0x49); 101 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc6, 0xff, 0xf9); 102 + mipi_dsi_dcs_write_seq_multi(&ctx, 0xc0, 0x25, 0x5a); 103 + mipi_dsi_dcs_write_seq_multi(&ctx, MIPI_DCS_SET_ADDRESS_MODE, 0x02); 104 + mipi_dsi_dcs_exit_sleep_mode_multi(&ctx); 105 + mipi_dsi_msleep(&ctx, 120); 106 + 107 + if (ctx.accum_err) { 108 + gpiod_set_value_cansleep(lcd->enable_gpio, 0); 109 + gpiod_set_value_cansleep(lcd->reset_gpio, 1); 110 + regulator_disable(lcd->supply); 111 + } 112 + 113 + return ctx.accum_err; 114 + } 115 + 116 + static int lincoln_lcd197_panel_unprepare(struct drm_panel *panel) 117 + { 118 + struct lincoln_lcd197_panel *lcd = to_lincoln_lcd197_panel(panel); 119 + struct mipi_dsi_multi_context ctx = { .dsi = lcd->dsi }; 120 + 121 + mipi_dsi_dcs_enter_sleep_mode_multi(&ctx); 122 + mipi_dsi_usleep_range(&ctx, 5000, 6000); 123 + gpiod_set_value_cansleep(lcd->enable_gpio, 0); 124 + gpiod_set_value_cansleep(lcd->reset_gpio, 1); 125 + regulator_disable(lcd->supply); 126 + 127 + return ctx.accum_err; 128 + } 129 + 130 + static int lincoln_lcd197_panel_enable(struct drm_panel *panel) 131 + { 132 + struct lincoln_lcd197_panel *lcd = to_lincoln_lcd197_panel(panel); 133 + struct mipi_dsi_multi_context ctx = { .dsi = lcd->dsi }; 134 + 135 + mipi_dsi_dcs_set_display_on_multi(&ctx); 136 + mipi_dsi_msleep(&ctx, 20); 137 + 138 + return ctx.accum_err; 139 + } 140 + 141 + static int lincoln_lcd197_panel_disable(struct drm_panel *panel) 142 + { 143 + struct lincoln_lcd197_panel *lcd = to_lincoln_lcd197_panel(panel); 144 + struct mipi_dsi_multi_context ctx = { .dsi = lcd->dsi }; 145 + 146 + mipi_dsi_dcs_set_display_off_multi(&ctx); 147 + mipi_dsi_msleep(&ctx, 50); 148 + 149 + return ctx.accum_err; 150 + } 151 + 152 + static const struct drm_display_mode lcd197_mode = { 153 + .clock = 154002, 154 + .hdisplay = 1080, 155 + .hsync_start = 1080 + 20, 156 + .hsync_end = 1080 + 20 + 6, 157 + .htotal = 1080 + 204, 158 + .vdisplay = 1920, 159 + .vsync_start = 1920 + 4, 160 + .vsync_end = 1920 + 4 + 4, 161 + .vtotal = 1920 + 79, 162 + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, 163 + .width_mm = 79, 164 + .height_mm = 125, 165 + .type = DRM_MODE_TYPE_DRIVER, 166 + }; 167 + 168 + static int lincoln_lcd197_panel_get_modes(struct drm_panel *panel, 169 + struct drm_connector *connector) 170 + { 171 + return drm_connector_helper_get_modes_fixed(connector, &lcd197_mode); 172 + } 173 + 174 + static const struct drm_panel_funcs lincoln_lcd197_panel_funcs = { 175 + .prepare = lincoln_lcd197_panel_prepare, 176 + .unprepare = lincoln_lcd197_panel_unprepare, 177 + .enable = lincoln_lcd197_panel_enable, 178 + .disable = lincoln_lcd197_panel_disable, 179 + .get_modes = lincoln_lcd197_panel_get_modes, 180 + }; 181 + 182 + static int lincoln_lcd197_panel_probe(struct mipi_dsi_device *dsi) 183 + { 184 + struct lincoln_lcd197_panel *lcd; 185 + struct device *dev = &dsi->dev; 186 + int err; 187 + 188 + dsi->lanes = 4; 189 + dsi->format = MIPI_DSI_FMT_RGB888; 190 + dsi->mode_flags = (MIPI_DSI_MODE_VIDEO | 191 + MIPI_DSI_MODE_VIDEO_BURST); 192 + 193 + lcd = devm_kzalloc(&dsi->dev, sizeof(*lcd), GFP_KERNEL); 194 + if (!lcd) 195 + return -ENOMEM; 196 + 197 + mipi_dsi_set_drvdata(dsi, lcd); 198 + lcd->dsi = dsi; 199 + 200 + lcd->supply = devm_regulator_get(dev, "power"); 201 + if (IS_ERR(lcd->supply)) 202 + return dev_err_probe(dev, PTR_ERR(lcd->supply), 203 + "failed to get power supply"); 204 + 205 + lcd->enable_gpio = devm_gpiod_get(dev, "enable", 206 + GPIOD_OUT_HIGH); 207 + if (IS_ERR(lcd->enable_gpio)) 208 + return dev_err_probe(dev, PTR_ERR(lcd->enable_gpio), 209 + "failed to get enable gpio"); 210 + 211 + lcd->reset_gpio = devm_gpiod_get(dev, "reset", 212 + GPIOD_OUT_HIGH); 213 + if (IS_ERR(lcd->reset_gpio)) 214 + return dev_err_probe(dev, PTR_ERR(lcd->reset_gpio), 215 + "failed to get reset gpio"); 216 + 217 + drm_panel_init(&lcd->panel, dev, 218 + &lincoln_lcd197_panel_funcs, DRM_MODE_CONNECTOR_DSI); 219 + 220 + err = drm_panel_of_backlight(&lcd->panel); 221 + if (err) 222 + return err; 223 + 224 + drm_panel_add(&lcd->panel); 225 + err = mipi_dsi_attach(dsi); 226 + if (err) 227 + drm_panel_remove(&lcd->panel); 228 + 229 + return err; 230 + } 231 + 232 + static void lincoln_lcd197_panel_remove(struct mipi_dsi_device *dsi) 233 + { 234 + struct lincoln_lcd197_panel *lcd = mipi_dsi_get_drvdata(dsi); 235 + int err; 236 + 237 + err = mipi_dsi_detach(dsi); 238 + if (err < 0) 239 + dev_err(&dsi->dev, "failed to detach from DSI host: %d\n", err); 240 + 241 + drm_panel_remove(&lcd->panel); 242 + } 243 + 244 + static const struct of_device_id lincoln_lcd197_of_match[] = { 245 + { .compatible = "lincolntech,lcd197", }, 246 + { /* sentinel */ } 247 + }; 248 + MODULE_DEVICE_TABLE(of, lincoln_lcd197_of_match); 249 + 250 + static struct mipi_dsi_driver lincoln_lcd197_panel_driver = { 251 + .driver = { 252 + .name = "panel-lincolntech-lcd197", 253 + .of_match_table = lincoln_lcd197_of_match, 254 + }, 255 + .probe = lincoln_lcd197_panel_probe, 256 + .remove = lincoln_lcd197_panel_remove, 257 + }; 258 + module_mipi_dsi_driver(lincoln_lcd197_panel_driver); 259 + 260 + MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 261 + MODULE_DESCRIPTION("Lincoln Technologies LCD197 panel driver"); 262 + MODULE_LICENSE("GPL");