522 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Magnachip d53e6ea8966 MIPI-DSI panel driver4 * Copyright (C) 2023 Chris Morgan5 */6 7#include <drm/drm_mipi_dbi.h>8#include <drm/drm_mipi_dsi.h>9#include <drm/drm_modes.h>10#include <drm/drm_of.h>11#include <drm/drm_panel.h>12 13#include <linux/backlight.h>14#include <linux/delay.h>15#include <linux/gpio/consumer.h>16#include <linux/init.h>17#include <linux/kernel.h>18#include <linux/media-bus-format.h>19#include <linux/module.h>20#include <linux/of.h>21#include <linux/regulator/consumer.h>22#include <linux/spi/spi.h>23 24#include <video/mipi_display.h>25 26/* Forward declaration for use in backlight function */27struct d53e6ea8966;28 29/* Panel info, unique to each panel */30struct d53e6ea8966_panel_info {31 /** @display_modes: the supported display modes */32 const struct drm_display_mode *display_modes;33 /** @num_modes: the number of supported display modes */34 unsigned int num_modes;35 /** @width_mm: panel width in mm */36 u16 width_mm;37 /** @height_mm: panel height in mm */38 u16 height_mm;39 /** @bus_flags: drm bus flags for panel */40 u32 bus_flags;41 /** @panel_init_seq: panel specific init sequence */42 void (*panel_init_seq)(struct d53e6ea8966 *db);43 /** @backlight_register: panel backlight registration or NULL */44 int (*backlight_register)(struct d53e6ea8966 *db);45};46 47struct d53e6ea8966 {48 /** @dev: the container device */49 struct device *dev;50 /** @dbi: the DBI bus abstraction handle */51 struct mipi_dbi dbi;52 /** @panel: the DRM panel instance for this device */53 struct drm_panel panel;54 /** @reset: reset GPIO line */55 struct gpio_desc *reset;56 /** @enable: enable GPIO line */57 struct gpio_desc *enable;58 /** @reg_vdd: VDD supply regulator for panel logic */59 struct regulator *reg_vdd;60 /** @reg_elvdd: ELVDD supply regulator for panel display */61 struct regulator *reg_elvdd;62 /** @dsi_dev: DSI child device (panel) */63 struct mipi_dsi_device *dsi_dev;64 /** @bl_dev: pseudo-backlight device for oled panel */65 struct backlight_device *bl_dev;66 /** @panel_info: struct containing panel timing and info */67 const struct d53e6ea8966_panel_info *panel_info;68};69 70#define NUM_GAMMA_LEVELS 1671#define GAMMA_TABLE_COUNT 2372#define MAX_BRIGHTNESS (NUM_GAMMA_LEVELS - 1)73 74#define MCS_ELVSS_ON 0xb175#define MCS_TEMP_SWIRE 0xb276#define MCS_PASSWORD_0 0xf077#define MCS_PASSWORD_1 0xf178#define MCS_ANALOG_PWR_CTL_0 0xf479#define MCS_ANALOG_PWR_CTL_1 0xf580#define MCS_GTCON_SET 0xf781#define MCS_GATELESS_SIGNAL_SET 0xf882#define MCS_SET_GAMMA 0xf983 84static inline struct d53e6ea8966 *to_d53e6ea8966(struct drm_panel *panel)85{86 return container_of(panel, struct d53e6ea8966, panel);87}88 89/* Table of gamma values provided in datasheet */90static u8 ams495qa01_gamma[NUM_GAMMA_LEVELS][GAMMA_TABLE_COUNT] = {91 {0x01, 0x79, 0x78, 0x8d, 0xd9, 0xdf, 0xd5, 0xcb, 0xcf, 0xc5,92 0xe5, 0xe0, 0xe4, 0xdc, 0xb8, 0xd4, 0xfa, 0xed, 0xe6, 0x2f,93 0x00, 0x2f},94 {0x01, 0x7d, 0x7c, 0x92, 0xd7, 0xdd, 0xd2, 0xcb, 0xd0, 0xc6,95 0xe5, 0xe1, 0xe3, 0xda, 0xbd, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,96 0x00, 0x2f},97 {0x01, 0x7f, 0x7e, 0x95, 0xd7, 0xde, 0xd2, 0xcb, 0xcf, 0xc5,98 0xe5, 0xe3, 0xe3, 0xda, 0xbf, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,99 0x00, 0x2f},100 {0x01, 0x82, 0x81, 0x99, 0xd6, 0xdd, 0xd1, 0xca, 0xcf, 0xc3,101 0xe4, 0xe3, 0xe3, 0xda, 0xc2, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,102 0x00, 0x2f},103 {0x01, 0x84, 0x83, 0x9b, 0xd7, 0xde, 0xd2, 0xc8, 0xce, 0xc2,104 0xe4, 0xe3, 0xe2, 0xd9, 0xc3, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,105 0x00, 0x2f},106 {0x01, 0x87, 0x86, 0x9f, 0xd6, 0xdd, 0xd1, 0xc7, 0xce, 0xc1,107 0xe4, 0xe3, 0xe2, 0xd9, 0xc6, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,108 0x00, 0x2f},109 {0x01, 0x89, 0x89, 0xa2, 0xd5, 0xdb, 0xcf, 0xc8, 0xcf, 0xc2,110 0xe3, 0xe3, 0xe1, 0xd9, 0xc7, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,111 0x00, 0x2f},112 {0x01, 0x8b, 0x8b, 0xa5, 0xd5, 0xdb, 0xcf, 0xc7, 0xce, 0xc0,113 0xe3, 0xe3, 0xe1, 0xd8, 0xc7, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,114 0x00, 0x2f},115 {0x01, 0x8d, 0x8d, 0xa7, 0xd5, 0xdb, 0xcf, 0xc6, 0xce, 0xc0,116 0xe4, 0xe4, 0xe1, 0xd7, 0xc8, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,117 0x00, 0x2f},118 {0x01, 0x8f, 0x8f, 0xaa, 0xd4, 0xdb, 0xce, 0xc6, 0xcd, 0xbf,119 0xe3, 0xe3, 0xe1, 0xd7, 0xca, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,120 0x00, 0x2f},121 {0x01, 0x91, 0x91, 0xac, 0xd3, 0xda, 0xce, 0xc5, 0xcd, 0xbe,122 0xe3, 0xe3, 0xe0, 0xd7, 0xca, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,123 0x00, 0x2f},124 {0x01, 0x93, 0x93, 0xaf, 0xd3, 0xda, 0xcd, 0xc5, 0xcd, 0xbe,125 0xe2, 0xe3, 0xdf, 0xd6, 0xca, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,126 0x00, 0x2f},127 {0x01, 0x95, 0x95, 0xb1, 0xd2, 0xd9, 0xcc, 0xc4, 0xcd, 0xbe,128 0xe2, 0xe3, 0xdf, 0xd7, 0xcc, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,129 0x00, 0x2f},130 {0x01, 0x99, 0x99, 0xb6, 0xd1, 0xd9, 0xcc, 0xc3, 0xcb, 0xbc,131 0xe2, 0xe4, 0xdf, 0xd6, 0xcc, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,132 0x00, 0x2f},133 {0x01, 0x9c, 0x9c, 0xba, 0xd0, 0xd8, 0xcb, 0xc3, 0xcb, 0xbb,134 0xe2, 0xe4, 0xdf, 0xd6, 0xce, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,135 0x00, 0x2f},136 {0x01, 0x9f, 0x9f, 0xbe, 0xcf, 0xd7, 0xc9, 0xc2, 0xcb, 0xbb,137 0xe1, 0xe3, 0xde, 0xd6, 0xd0, 0xd3, 0xfa, 0xed, 0xe6, 0x2f,138 0x00, 0x2f},139};140 141/*142 * Table of elvss values provided in datasheet and corresponds to143 * gamma values.144 */145static u8 ams495qa01_elvss[NUM_GAMMA_LEVELS] = {146 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,147 0x15, 0x15, 0x14, 0x14, 0x13, 0x12,148};149 150static int ams495qa01_update_gamma(struct mipi_dbi *dbi, int brightness)151{152 int tmp = brightness;153 154 mipi_dbi_command_buf(dbi, MCS_SET_GAMMA, ams495qa01_gamma[tmp],155 ARRAY_SIZE(ams495qa01_gamma[tmp]));156 mipi_dbi_command(dbi, MCS_SET_GAMMA, 0x00);157 158 /* Undocumented command */159 mipi_dbi_command(dbi, 0x26, 0x00);160 161 mipi_dbi_command(dbi, MCS_TEMP_SWIRE, ams495qa01_elvss[tmp]);162 163 return 0;164}165 166static void ams495qa01_panel_init(struct d53e6ea8966 *db)167{168 struct mipi_dbi *dbi = &db->dbi;169 170 mipi_dbi_command(dbi, MCS_PASSWORD_0, 0x5a, 0x5a);171 mipi_dbi_command(dbi, MCS_PASSWORD_1, 0x5a, 0x5a);172 173 /* Undocumented commands */174 mipi_dbi_command(dbi, 0xb0, 0x02);175 mipi_dbi_command(dbi, 0xf3, 0x3b);176 177 mipi_dbi_command(dbi, MCS_ANALOG_PWR_CTL_0, 0x33, 0x42, 0x00, 0x08);178 mipi_dbi_command(dbi, MCS_ANALOG_PWR_CTL_1, 0x00, 0x06, 0x26, 0x35, 0x03);179 180 /* Undocumented commands */181 mipi_dbi_command(dbi, 0xf6, 0x02);182 mipi_dbi_command(dbi, 0xc6, 0x0b, 0x00, 0x00, 0x3c, 0x00, 0x22,183 0x00, 0x00, 0x00, 0x00);184 185 mipi_dbi_command(dbi, MCS_GTCON_SET, 0x20);186 mipi_dbi_command(dbi, MCS_TEMP_SWIRE, 0x06, 0x06, 0x06, 0x06);187 mipi_dbi_command(dbi, MCS_ELVSS_ON, 0x07, 0x00, 0x10);188 mipi_dbi_command(dbi, MCS_GATELESS_SIGNAL_SET, 0x7f, 0x7a,189 0x89, 0x67, 0x26, 0x38, 0x00, 0x00, 0x09,190 0x67, 0x70, 0x88, 0x7a, 0x76, 0x05, 0x09,191 0x23, 0x23, 0x23);192 193 /* Undocumented commands */194 mipi_dbi_command(dbi, 0xb5, 0xff, 0xef, 0x35, 0x42, 0x0d, 0xd7,195 0xff, 0x07, 0xff, 0xff, 0xfd, 0x00, 0x01,196 0xff, 0x05, 0x12, 0x0f, 0xff, 0xff, 0xff,197 0xff);198 mipi_dbi_command(dbi, 0xb4, 0x15);199 mipi_dbi_command(dbi, 0xb3, 0x00);200 201 ams495qa01_update_gamma(dbi, MAX_BRIGHTNESS);202}203 204static int d53e6ea8966_prepare(struct drm_panel *panel)205{206 struct d53e6ea8966 *db = to_d53e6ea8966(panel);207 int ret;208 209 /* Power up */210 ret = regulator_enable(db->reg_vdd);211 if (ret) {212 dev_err(db->dev, "failed to enable vdd regulator: %d\n", ret);213 return ret;214 }215 216 if (db->reg_elvdd) {217 ret = regulator_enable(db->reg_elvdd);218 if (ret) {219 dev_err(db->dev,220 "failed to enable elvdd regulator: %d\n", ret);221 regulator_disable(db->reg_vdd);222 return ret;223 }224 }225 226 /* Enable */227 if (db->enable)228 gpiod_set_value_cansleep(db->enable, 1);229 230 msleep(50);231 232 /* Reset */233 gpiod_set_value_cansleep(db->reset, 1);234 usleep_range(1000, 5000);235 gpiod_set_value_cansleep(db->reset, 0);236 msleep(20);237 238 db->panel_info->panel_init_seq(db);239 240 return 0;241}242 243static int d53e6ea8966_enable(struct drm_panel *panel)244{245 struct d53e6ea8966 *db = to_d53e6ea8966(panel);246 struct mipi_dbi *dbi = &db->dbi;247 248 mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE);249 msleep(200);250 mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON);251 usleep_range(10000, 15000);252 253 return 0;254}255 256static int d53e6ea8966_disable(struct drm_panel *panel)257{258 struct d53e6ea8966 *db = to_d53e6ea8966(panel);259 struct mipi_dbi *dbi = &db->dbi;260 261 mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF);262 msleep(20);263 mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE);264 msleep(100);265 266 return 0;267}268 269static int d53e6ea8966_unprepare(struct drm_panel *panel)270{271 struct d53e6ea8966 *db = to_d53e6ea8966(panel);272 273 if (db->enable)274 gpiod_set_value_cansleep(db->enable, 0);275 276 gpiod_set_value_cansleep(db->reset, 1);277 278 if (db->reg_elvdd)279 regulator_disable(db->reg_elvdd);280 281 regulator_disable(db->reg_vdd);282 msleep(100);283 284 return 0;285}286 287static int d53e6ea8966_get_modes(struct drm_panel *panel,288 struct drm_connector *connector)289{290 struct d53e6ea8966 *db = to_d53e6ea8966(panel);291 const struct d53e6ea8966_panel_info *panel_info = db->panel_info;292 struct drm_display_mode *mode;293 static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;294 unsigned int i;295 296 for (i = 0; i < panel_info->num_modes; i++) {297 mode = drm_mode_duplicate(connector->dev,298 &panel_info->display_modes[i]);299 if (!mode)300 return -ENOMEM;301 302 drm_mode_set_name(mode);303 drm_mode_probed_add(connector, mode);304 }305 306 connector->display_info.bpc = 8;307 connector->display_info.width_mm = panel_info->width_mm;308 connector->display_info.height_mm = panel_info->height_mm;309 connector->display_info.bus_flags = panel_info->bus_flags;310 311 drm_display_info_set_bus_formats(&connector->display_info,312 &bus_format, 1);313 314 return 1;315}316 317static const struct drm_panel_funcs d53e6ea8966_panel_funcs = {318 .disable = d53e6ea8966_disable,319 .enable = d53e6ea8966_enable,320 .get_modes = d53e6ea8966_get_modes,321 .prepare = d53e6ea8966_prepare,322 .unprepare = d53e6ea8966_unprepare,323};324 325static int ams495qa01_set_brightness(struct backlight_device *bd)326{327 struct d53e6ea8966 *db = bl_get_data(bd);328 struct mipi_dbi *dbi = &db->dbi;329 int brightness = backlight_get_brightness(bd);330 331 ams495qa01_update_gamma(dbi, brightness);332 333 return 0;334}335 336static const struct backlight_ops ams495qa01_backlight_ops = {337 .update_status = ams495qa01_set_brightness,338};339 340static int ams495qa01_backlight_register(struct d53e6ea8966 *db)341{342 struct backlight_properties props = {343 .type = BACKLIGHT_RAW,344 .brightness = MAX_BRIGHTNESS,345 .max_brightness = MAX_BRIGHTNESS,346 };347 struct device *dev = db->dev;348 int ret = 0;349 350 db->bl_dev = devm_backlight_device_register(dev, "panel", dev, db,351 &ams495qa01_backlight_ops,352 &props);353 if (IS_ERR(db->bl_dev)) {354 ret = PTR_ERR(db->bl_dev);355 dev_err(dev, "error registering backlight device (%d)\n", ret);356 }357 358 return ret;359}360 361static int d53e6ea8966_probe(struct spi_device *spi)362{363 struct device *dev = &spi->dev;364 struct mipi_dsi_host *dsi_host;365 struct d53e6ea8966 *db;366 int ret;367 struct mipi_dsi_device_info info = {368 .type = "d53e6ea8966",369 .channel = 0,370 .node = NULL,371 };372 373 db = devm_kzalloc(dev, sizeof(*db), GFP_KERNEL);374 if (!db)375 return -ENOMEM;376 377 spi_set_drvdata(spi, db);378 379 db->dev = dev;380 381 db->panel_info = of_device_get_match_data(dev);382 if (!db->panel_info)383 return -EINVAL;384 385 db->reg_vdd = devm_regulator_get(dev, "vdd");386 if (IS_ERR(db->reg_vdd))387 return dev_err_probe(dev, PTR_ERR(db->reg_vdd),388 "Failed to get vdd supply\n");389 390 db->reg_elvdd = devm_regulator_get_optional(dev, "elvdd");391 if (IS_ERR(db->reg_elvdd))392 db->reg_elvdd = NULL;393 394 db->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);395 if (IS_ERR(db->reset)) {396 ret = PTR_ERR(db->reset);397 return dev_err_probe(dev, ret, "no RESET GPIO\n");398 }399 400 db->enable = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);401 if (IS_ERR(db->enable)) {402 ret = PTR_ERR(db->enable);403 return dev_err_probe(dev, ret, "cannot get ENABLE GPIO\n");404 }405 406 ret = mipi_dbi_spi_init(spi, &db->dbi, NULL);407 if (ret)408 return dev_err_probe(dev, ret, "MIPI DBI init failed\n");409 410 dsi_host = drm_of_get_dsi_bus(dev);411 if (IS_ERR(dsi_host)) {412 ret = PTR_ERR(dsi_host);413 return dev_err_probe(dev, ret, "Error attaching DSI bus\n");414 }415 416 db->dsi_dev = devm_mipi_dsi_device_register_full(dev, dsi_host, &info);417 if (IS_ERR(db->dsi_dev)) {418 dev_err(dev, "failed to register dsi device: %ld\n",419 PTR_ERR(db->dsi_dev));420 return PTR_ERR(db->dsi_dev);421 }422 423 db->dsi_dev->lanes = 2;424 db->dsi_dev->format = MIPI_DSI_FMT_RGB888;425 db->dsi_dev->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |426 MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET;427 428 drm_panel_init(&db->panel, dev, &d53e6ea8966_panel_funcs,429 DRM_MODE_CONNECTOR_DSI);430 431 if (db->panel_info->backlight_register) {432 ret = db->panel_info->backlight_register(db);433 if (ret < 0)434 return ret;435 db->panel.backlight = db->bl_dev;436 }437 438 drm_panel_add(&db->panel);439 440 ret = devm_mipi_dsi_attach(dev, db->dsi_dev);441 if (ret < 0) {442 dev_err(dev, "mipi_dsi_attach failed: %d\n", ret);443 drm_panel_remove(&db->panel);444 return ret;445 }446 447 return 0;448}449 450static void d53e6ea8966_remove(struct spi_device *spi)451{452 struct d53e6ea8966 *db = spi_get_drvdata(spi);453 454 drm_panel_remove(&db->panel);455}456 457static const struct drm_display_mode ams495qa01_modes[] = {458 { /* 60hz */459 .clock = 33500,460 .hdisplay = 960,461 .hsync_start = 960 + 10,462 .hsync_end = 960 + 10 + 2,463 .htotal = 960 + 10 + 2 + 10,464 .vdisplay = 544,465 .vsync_start = 544 + 10,466 .vsync_end = 544 + 10 + 2,467 .vtotal = 544 + 10 + 2 + 10,468 .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,469 .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,470 },471 { /* 50hz */472 .clock = 27800,473 .hdisplay = 960,474 .hsync_start = 960 + 10,475 .hsync_end = 960 + 10 + 2,476 .htotal = 960 + 10 + 2 + 10,477 .vdisplay = 544,478 .vsync_start = 544 + 10,479 .vsync_end = 544 + 10 + 2,480 .vtotal = 544 + 10 + 2 + 10,481 .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,482 .type = DRM_MODE_TYPE_DRIVER,483 },484};485 486static const struct d53e6ea8966_panel_info ams495qa01_info = {487 .display_modes = ams495qa01_modes,488 .num_modes = ARRAY_SIZE(ams495qa01_modes),489 .width_mm = 117,490 .height_mm = 74,491 .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,492 .panel_init_seq = ams495qa01_panel_init,493 .backlight_register = ams495qa01_backlight_register,494};495 496static const struct of_device_id d53e6ea8966_match[] = {497 { .compatible = "samsung,ams495qa01", .data = &ams495qa01_info },498 { /* sentinel */ },499};500MODULE_DEVICE_TABLE(of, d53e6ea8966_match);501 502static const struct spi_device_id d53e6ea8966_ids[] = {503 { "ams495qa01", 0 },504 { /* sentinel */ },505};506MODULE_DEVICE_TABLE(spi, d53e6ea8966_ids);507 508static struct spi_driver d53e6ea8966_driver = {509 .driver = {510 .name = "d53e6ea8966-panel",511 .of_match_table = d53e6ea8966_match,512 },513 .id_table = d53e6ea8966_ids,514 .probe = d53e6ea8966_probe,515 .remove = d53e6ea8966_remove,516};517module_spi_driver(d53e6ea8966_driver);518 519MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>");520MODULE_DESCRIPTION("Magnachip d53e6ea8966 panel driver");521MODULE_LICENSE("GPL");522