162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Panel driver for the Samsung S6D27A1 480x800 DPI RGB panel. 462306a36Sopenharmony_ci * Found in the Samsung Galaxy Ace 2 GT-I8160 mobile phone. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <drm/drm_mipi_dbi.h> 862306a36Sopenharmony_ci#include <drm/drm_modes.h> 962306a36Sopenharmony_ci#include <drm/drm_panel.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/delay.h> 1262306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1362306a36Sopenharmony_ci#include <linux/init.h> 1462306a36Sopenharmony_ci#include <linux/kernel.h> 1562306a36Sopenharmony_ci#include <linux/media-bus-format.h> 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/of.h> 1862306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 1962306a36Sopenharmony_ci#include <linux/spi/spi.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#include <video/mipi_display.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define S6D27A1_PASSWD_L2 0xF0 /* Password Command for Level 2 Control */ 2462306a36Sopenharmony_ci#define S6D27A1_RESCTL 0xB3 /* Resolution Select Control */ 2562306a36Sopenharmony_ci#define S6D27A1_PANELCTL2 0xB4 /* ASG Signal Control */ 2662306a36Sopenharmony_ci#define S6D27A1_READID1 0xDA /* Read panel ID 1 */ 2762306a36Sopenharmony_ci#define S6D27A1_READID2 0xDB /* Read panel ID 2 */ 2862306a36Sopenharmony_ci#define S6D27A1_READID3 0xDC /* Read panel ID 3 */ 2962306a36Sopenharmony_ci#define S6D27A1_DISPCTL 0xF2 /* Display Control */ 3062306a36Sopenharmony_ci#define S6D27A1_MANPWR 0xF3 /* Manual Control */ 3162306a36Sopenharmony_ci#define S6D27A1_PWRCTL1 0xF4 /* Power Control */ 3262306a36Sopenharmony_ci#define S6D27A1_SRCCTL 0xF6 /* Source Control */ 3362306a36Sopenharmony_ci#define S6D27A1_PANELCTL 0xF7 /* Panel Control*/ 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic const u8 s6d27a1_dbi_read_commands[] = { 3662306a36Sopenharmony_ci S6D27A1_READID1, 3762306a36Sopenharmony_ci S6D27A1_READID2, 3862306a36Sopenharmony_ci S6D27A1_READID3, 3962306a36Sopenharmony_ci 0, /* sentinel */ 4062306a36Sopenharmony_ci}; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistruct s6d27a1 { 4362306a36Sopenharmony_ci struct device *dev; 4462306a36Sopenharmony_ci struct mipi_dbi dbi; 4562306a36Sopenharmony_ci struct drm_panel panel; 4662306a36Sopenharmony_ci struct gpio_desc *reset; 4762306a36Sopenharmony_ci struct regulator_bulk_data regulators[2]; 4862306a36Sopenharmony_ci}; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic const struct drm_display_mode s6d27a1_480_800_mode = { 5162306a36Sopenharmony_ci /* 5262306a36Sopenharmony_ci * The vendor driver states that the S6D27A1 panel 5362306a36Sopenharmony_ci * has a pixel clock frequency of 49920000 Hz / 2 = 24960000 Hz. 5462306a36Sopenharmony_ci */ 5562306a36Sopenharmony_ci .clock = 24960, 5662306a36Sopenharmony_ci .hdisplay = 480, 5762306a36Sopenharmony_ci .hsync_start = 480 + 63, 5862306a36Sopenharmony_ci .hsync_end = 480 + 63 + 2, 5962306a36Sopenharmony_ci .htotal = 480 + 63 + 2 + 63, 6062306a36Sopenharmony_ci .vdisplay = 800, 6162306a36Sopenharmony_ci .vsync_start = 800 + 11, 6262306a36Sopenharmony_ci .vsync_end = 800 + 11 + 2, 6362306a36Sopenharmony_ci .vtotal = 800 + 11 + 2 + 10, 6462306a36Sopenharmony_ci .width_mm = 50, 6562306a36Sopenharmony_ci .height_mm = 84, 6662306a36Sopenharmony_ci .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC, 6762306a36Sopenharmony_ci}; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic inline struct s6d27a1 *to_s6d27a1(struct drm_panel *panel) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci return container_of(panel, struct s6d27a1, panel); 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic void s6d27a1_read_mtp_id(struct s6d27a1 *ctx) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci struct mipi_dbi *dbi = &ctx->dbi; 7762306a36Sopenharmony_ci u8 id1, id2, id3; 7862306a36Sopenharmony_ci int ret; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci ret = mipi_dbi_command_read(dbi, S6D27A1_READID1, &id1); 8162306a36Sopenharmony_ci if (ret) { 8262306a36Sopenharmony_ci dev_err(ctx->dev, "unable to read MTP ID 1\n"); 8362306a36Sopenharmony_ci return; 8462306a36Sopenharmony_ci } 8562306a36Sopenharmony_ci ret = mipi_dbi_command_read(dbi, S6D27A1_READID2, &id2); 8662306a36Sopenharmony_ci if (ret) { 8762306a36Sopenharmony_ci dev_err(ctx->dev, "unable to read MTP ID 2\n"); 8862306a36Sopenharmony_ci return; 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci ret = mipi_dbi_command_read(dbi, S6D27A1_READID3, &id3); 9162306a36Sopenharmony_ci if (ret) { 9262306a36Sopenharmony_ci dev_err(ctx->dev, "unable to read MTP ID 3\n"); 9362306a36Sopenharmony_ci return; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci dev_info(ctx->dev, "MTP ID: %02x %02x %02x\n", id1, id2, id3); 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic int s6d27a1_power_on(struct s6d27a1 *ctx) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci struct mipi_dbi *dbi = &ctx->dbi; 10162306a36Sopenharmony_ci int ret; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci /* Power up */ 10462306a36Sopenharmony_ci ret = regulator_bulk_enable(ARRAY_SIZE(ctx->regulators), 10562306a36Sopenharmony_ci ctx->regulators); 10662306a36Sopenharmony_ci if (ret) { 10762306a36Sopenharmony_ci dev_err(ctx->dev, "failed to enable regulators: %d\n", ret); 10862306a36Sopenharmony_ci return ret; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci msleep(20); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci /* Assert reset >=1 ms */ 11462306a36Sopenharmony_ci gpiod_set_value_cansleep(ctx->reset, 1); 11562306a36Sopenharmony_ci usleep_range(1000, 5000); 11662306a36Sopenharmony_ci /* De-assert reset */ 11762306a36Sopenharmony_ci gpiod_set_value_cansleep(ctx->reset, 0); 11862306a36Sopenharmony_ci /* Wait >= 10 ms */ 11962306a36Sopenharmony_ci msleep(20); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci /* 12262306a36Sopenharmony_ci * Exit sleep mode and initialize display - some hammering is 12362306a36Sopenharmony_ci * necessary. 12462306a36Sopenharmony_ci */ 12562306a36Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); 12662306a36Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_EXIT_SLEEP_MODE); 12762306a36Sopenharmony_ci msleep(120); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci /* Magic to unlock level 2 control of the display */ 13062306a36Sopenharmony_ci mipi_dbi_command(dbi, S6D27A1_PASSWD_L2, 0x5A, 0x5A); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci /* Configure resolution to 480RGBx800 */ 13362306a36Sopenharmony_ci mipi_dbi_command(dbi, S6D27A1_RESCTL, 0x22); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci mipi_dbi_command(dbi, S6D27A1_PANELCTL2, 0x00, 0x02, 0x03, 0x04, 0x05, 0x08, 0x00, 0x0c); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci mipi_dbi_command(dbi, S6D27A1_MANPWR, 0x01, 0x00, 0x00, 0x08, 0x08, 0x02, 0x00); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci mipi_dbi_command(dbi, S6D27A1_DISPCTL, 0x19, 0x00, 0x08, 0x0D, 0x03, 0x41, 0x3F); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci mipi_dbi_command(dbi, S6D27A1_PWRCTL1, 0x00, 0x00, 0x00, 0x00, 0x55, 14262306a36Sopenharmony_ci 0x44, 0x05, 0x88, 0x4B, 0x50); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci mipi_dbi_command(dbi, S6D27A1_SRCCTL, 0x03, 0x09, 0x8A, 0x00, 0x01, 0x16); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci mipi_dbi_command(dbi, S6D27A1_PANELCTL, 0x00, 0x05, 0x06, 0x07, 0x08, 14762306a36Sopenharmony_ci 0x01, 0x09, 0x0D, 0x0A, 0x0E, 14862306a36Sopenharmony_ci 0x0B, 0x0F, 0x0C, 0x10, 0x01, 14962306a36Sopenharmony_ci 0x11, 0x12, 0x13, 0x14, 0x05, 15062306a36Sopenharmony_ci 0x06, 0x07, 0x08, 0x01, 0x09, 15162306a36Sopenharmony_ci 0x0D, 0x0A, 0x0E, 0x0B, 0x0F, 15262306a36Sopenharmony_ci 0x0C, 0x10, 0x01, 0x11, 0x12, 15362306a36Sopenharmony_ci 0x13, 0x14); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci /* lock the level 2 control */ 15662306a36Sopenharmony_ci mipi_dbi_command(dbi, S6D27A1_PASSWD_L2, 0xA5, 0xA5); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci s6d27a1_read_mtp_id(ctx); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic int s6d27a1_power_off(struct s6d27a1 *ctx) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci /* Go into RESET and disable regulators */ 16662306a36Sopenharmony_ci gpiod_set_value_cansleep(ctx->reset, 1); 16762306a36Sopenharmony_ci return regulator_bulk_disable(ARRAY_SIZE(ctx->regulators), 16862306a36Sopenharmony_ci ctx->regulators); 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic int s6d27a1_unprepare(struct drm_panel *panel) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci struct s6d27a1 *ctx = to_s6d27a1(panel); 17462306a36Sopenharmony_ci struct mipi_dbi *dbi = &ctx->dbi; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_ENTER_SLEEP_MODE); 17762306a36Sopenharmony_ci msleep(120); 17862306a36Sopenharmony_ci return s6d27a1_power_off(to_s6d27a1(panel)); 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic int s6d27a1_disable(struct drm_panel *panel) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci struct s6d27a1 *ctx = to_s6d27a1(panel); 18462306a36Sopenharmony_ci struct mipi_dbi *dbi = &ctx->dbi; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_OFF); 18762306a36Sopenharmony_ci msleep(25); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return 0; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic int s6d27a1_prepare(struct drm_panel *panel) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci return s6d27a1_power_on(to_s6d27a1(panel)); 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic int s6d27a1_enable(struct drm_panel *panel) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci struct s6d27a1 *ctx = to_s6d27a1(panel); 20062306a36Sopenharmony_ci struct mipi_dbi *dbi = &ctx->dbi; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci mipi_dbi_command(dbi, MIPI_DCS_SET_DISPLAY_ON); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return 0; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic int s6d27a1_get_modes(struct drm_panel *panel, 20862306a36Sopenharmony_ci struct drm_connector *connector) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci struct s6d27a1 *ctx = to_s6d27a1(panel); 21162306a36Sopenharmony_ci struct drm_display_mode *mode; 21262306a36Sopenharmony_ci static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci mode = drm_mode_duplicate(connector->dev, &s6d27a1_480_800_mode); 21562306a36Sopenharmony_ci if (!mode) { 21662306a36Sopenharmony_ci dev_err(ctx->dev, "failed to add mode\n"); 21762306a36Sopenharmony_ci return -ENOMEM; 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci connector->display_info.bpc = 8; 22162306a36Sopenharmony_ci connector->display_info.width_mm = mode->width_mm; 22262306a36Sopenharmony_ci connector->display_info.height_mm = mode->height_mm; 22362306a36Sopenharmony_ci connector->display_info.bus_flags = 22462306a36Sopenharmony_ci DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE; 22562306a36Sopenharmony_ci drm_display_info_set_bus_formats(&connector->display_info, 22662306a36Sopenharmony_ci &bus_format, 1); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci drm_mode_set_name(mode); 22962306a36Sopenharmony_ci mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci drm_mode_probed_add(connector, mode); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci return 1; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic const struct drm_panel_funcs s6d27a1_drm_funcs = { 23762306a36Sopenharmony_ci .disable = s6d27a1_disable, 23862306a36Sopenharmony_ci .unprepare = s6d27a1_unprepare, 23962306a36Sopenharmony_ci .prepare = s6d27a1_prepare, 24062306a36Sopenharmony_ci .enable = s6d27a1_enable, 24162306a36Sopenharmony_ci .get_modes = s6d27a1_get_modes, 24262306a36Sopenharmony_ci}; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic int s6d27a1_probe(struct spi_device *spi) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci struct device *dev = &spi->dev; 24762306a36Sopenharmony_ci struct s6d27a1 *ctx; 24862306a36Sopenharmony_ci int ret; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 25162306a36Sopenharmony_ci if (!ctx) 25262306a36Sopenharmony_ci return -ENOMEM; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci ctx->dev = dev; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci /* 25762306a36Sopenharmony_ci * VCI is the analog voltage supply 25862306a36Sopenharmony_ci * VCCIO is the digital I/O voltage supply 25962306a36Sopenharmony_ci */ 26062306a36Sopenharmony_ci ctx->regulators[0].supply = "vci"; 26162306a36Sopenharmony_ci ctx->regulators[1].supply = "vccio"; 26262306a36Sopenharmony_ci ret = devm_regulator_bulk_get(dev, 26362306a36Sopenharmony_ci ARRAY_SIZE(ctx->regulators), 26462306a36Sopenharmony_ci ctx->regulators); 26562306a36Sopenharmony_ci if (ret) 26662306a36Sopenharmony_ci return dev_err_probe(dev, ret, "failed to get regulators\n"); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci ctx->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 26962306a36Sopenharmony_ci if (IS_ERR(ctx->reset)) { 27062306a36Sopenharmony_ci ret = PTR_ERR(ctx->reset); 27162306a36Sopenharmony_ci return dev_err_probe(dev, ret, "no RESET GPIO\n"); 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci ret = mipi_dbi_spi_init(spi, &ctx->dbi, NULL); 27562306a36Sopenharmony_ci if (ret) 27662306a36Sopenharmony_ci return dev_err_probe(dev, ret, "MIPI DBI init failed\n"); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci ctx->dbi.read_commands = s6d27a1_dbi_read_commands; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci drm_panel_init(&ctx->panel, dev, &s6d27a1_drm_funcs, 28162306a36Sopenharmony_ci DRM_MODE_CONNECTOR_DPI); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci ret = drm_panel_of_backlight(&ctx->panel); 28462306a36Sopenharmony_ci if (ret) 28562306a36Sopenharmony_ci return dev_err_probe(dev, ret, "failed to add backlight\n"); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci spi_set_drvdata(spi, ctx); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci drm_panel_add(&ctx->panel); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci return 0; 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cistatic void s6d27a1_remove(struct spi_device *spi) 29562306a36Sopenharmony_ci{ 29662306a36Sopenharmony_ci struct s6d27a1 *ctx = spi_get_drvdata(spi); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci drm_panel_remove(&ctx->panel); 29962306a36Sopenharmony_ci} 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_cistatic const struct of_device_id s6d27a1_match[] = { 30262306a36Sopenharmony_ci { .compatible = "samsung,s6d27a1", }, 30362306a36Sopenharmony_ci { /* sentinel */ }, 30462306a36Sopenharmony_ci}; 30562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, s6d27a1_match); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_cistatic struct spi_driver s6d27a1_driver = { 30862306a36Sopenharmony_ci .probe = s6d27a1_probe, 30962306a36Sopenharmony_ci .remove = s6d27a1_remove, 31062306a36Sopenharmony_ci .driver = { 31162306a36Sopenharmony_ci .name = "s6d27a1-panel", 31262306a36Sopenharmony_ci .of_match_table = s6d27a1_match, 31362306a36Sopenharmony_ci }, 31462306a36Sopenharmony_ci}; 31562306a36Sopenharmony_cimodule_spi_driver(s6d27a1_driver); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ciMODULE_AUTHOR("Markuss Broks <markuss.broks@gmail.com>"); 31862306a36Sopenharmony_ciMODULE_DESCRIPTION("Samsung S6D27A1 panel driver"); 31962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 320